2020-04-24 23:36:52 +00:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
let
|
|
|
|
cfg = config.services.redsocks;
|
|
|
|
in
|
|
|
|
{
|
|
|
|
##### interface
|
|
|
|
options = {
|
|
|
|
services.redsocks = {
|
|
|
|
enable = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc "Whether to enable redsocks.";
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
log_debug = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc "Log connection progress.";
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
log_info = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc "Log start and end of client sessions.";
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
log = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "stderr";
|
|
|
|
description =
|
2022-08-21 13:32:41 +00:00
|
|
|
lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Where to send logs.
|
|
|
|
|
|
|
|
Possible values are:
|
|
|
|
- stderr
|
|
|
|
- file:/path/to/file
|
|
|
|
- syslog:FACILITY where FACILITY is any of "daemon", "local0",
|
|
|
|
etc.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
chroot = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
description =
|
2022-08-12 12:06:08 +00:00
|
|
|
lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Chroot under which to run redsocks. Log file is opened before
|
|
|
|
chroot, but if logging to syslog /etc/localtime may be required.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
redsocks = mkOption {
|
|
|
|
description =
|
2022-08-12 12:06:08 +00:00
|
|
|
lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Local port to proxy associations to be performed.
|
|
|
|
|
|
|
|
The example shows how to configure a proxy to handle port 80 as HTTP
|
|
|
|
relay, and all other ports as HTTP connect.
|
|
|
|
'';
|
|
|
|
example = [
|
|
|
|
{ port = 23456; proxy = "1.2.3.4:8080"; type = "http-relay";
|
|
|
|
redirectCondition = "--dport 80";
|
|
|
|
doNotRedirect = [ "-d 1.2.0.0/16" ];
|
|
|
|
}
|
|
|
|
{ port = 23457; proxy = "1.2.3.4:8080"; type = "http-connect";
|
|
|
|
redirectCondition = true;
|
|
|
|
doNotRedirect = [ "-d 1.2.0.0/16" ];
|
|
|
|
}
|
|
|
|
];
|
|
|
|
type = types.listOf (types.submodule { options = {
|
|
|
|
ip = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "127.0.0.1";
|
|
|
|
description =
|
2022-08-12 12:06:08 +00:00
|
|
|
lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
IP on which redsocks should listen. Defaults to 127.0.0.1 for
|
|
|
|
security reasons.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
port = mkOption {
|
2022-12-17 10:02:37 +00:00
|
|
|
type = types.port;
|
2020-04-24 23:36:52 +00:00
|
|
|
default = 12345;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc "Port on which redsocks should listen.";
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
proxy = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
description =
|
2022-08-12 12:06:08 +00:00
|
|
|
lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Proxy through which redsocks should forward incoming traffic.
|
|
|
|
Example: "example.org:8080"
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
type = mkOption {
|
|
|
|
type = types.enum [ "socks4" "socks5" "http-connect" "http-relay" ];
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc "Type of proxy.";
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
login = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc "Login to send to proxy.";
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
password = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
description =
|
2022-08-12 12:06:08 +00:00
|
|
|
lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Password to send to proxy. WARNING, this will end up
|
|
|
|
world-readable in the store! Awaiting
|
|
|
|
https://github.com/NixOS/nix/issues/8 to be able to fix.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
disclose_src = mkOption {
|
|
|
|
type = types.enum [ "false" "X-Forwarded-For" "Forwarded_ip"
|
|
|
|
"Forwarded_ipport" ];
|
|
|
|
default = "false";
|
|
|
|
description =
|
2022-08-21 13:32:41 +00:00
|
|
|
lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Way to disclose client IP to the proxy.
|
|
|
|
- "false": do not disclose
|
|
|
|
http-connect supports the following ways:
|
|
|
|
- "X-Forwarded-For": add header "X-Forwarded-For: IP"
|
|
|
|
- "Forwarded_ip": add header "Forwarded: for=IP" (see RFC7239)
|
|
|
|
- "Forwarded_ipport": add header 'Forwarded: for="IP:port"'
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
redirectInternetOnly = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc "Exclude all non-globally-routable IPs from redsocks";
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
doNotRedirect = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
description =
|
2022-08-12 12:06:08 +00:00
|
|
|
lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Iptables filters that if matched will get the packet off of
|
|
|
|
redsocks.
|
|
|
|
'';
|
|
|
|
example = [ "-d 1.2.3.4" ];
|
|
|
|
};
|
|
|
|
|
|
|
|
redirectCondition = mkOption {
|
|
|
|
type = with types; either bool str;
|
|
|
|
default = false;
|
|
|
|
description =
|
2022-08-12 12:06:08 +00:00
|
|
|
lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Conditions to make outbound packets go through this redsocks
|
|
|
|
instance.
|
|
|
|
|
|
|
|
If set to false, no packet will be forwarded. If set to true,
|
|
|
|
all packets will be forwarded (except packets excluded by
|
|
|
|
redirectInternetOnly).
|
|
|
|
|
|
|
|
If set to a string, this is an iptables filter that will be
|
|
|
|
matched against packets before getting them into redsocks. For
|
|
|
|
example, setting it to "--dport 80" will only send
|
|
|
|
packets to port 80 to redsocks. Note "-p tcp" is always
|
|
|
|
implicitly added, as udp can only be proxied through redudp or
|
|
|
|
the like.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};});
|
|
|
|
};
|
|
|
|
|
|
|
|
# TODO: Add support for redudp and dnstc
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
##### implementation
|
|
|
|
config = let
|
|
|
|
redsocks_blocks = concatMapStrings (block:
|
|
|
|
let proxy = splitString ":" block.proxy; in
|
|
|
|
''
|
|
|
|
redsocks {
|
|
|
|
local_ip = ${block.ip};
|
|
|
|
local_port = ${toString block.port};
|
|
|
|
|
|
|
|
ip = ${elemAt proxy 0};
|
|
|
|
port = ${elemAt proxy 1};
|
|
|
|
type = ${block.type};
|
|
|
|
|
|
|
|
${optionalString (block.login != null) "login = \"${block.login}\";"}
|
|
|
|
${optionalString (block.password != null) "password = \"${block.password}\";"}
|
|
|
|
|
|
|
|
disclose_src = ${block.disclose_src};
|
|
|
|
}
|
|
|
|
'') cfg.redsocks;
|
|
|
|
configfile = pkgs.writeText "redsocks.conf"
|
|
|
|
''
|
|
|
|
base {
|
|
|
|
log_debug = ${if cfg.log_debug then "on" else "off" };
|
|
|
|
log_info = ${if cfg.log_info then "on" else "off" };
|
|
|
|
log = ${cfg.log};
|
|
|
|
|
|
|
|
daemon = off;
|
|
|
|
redirector = iptables;
|
|
|
|
|
|
|
|
user = redsocks;
|
|
|
|
group = redsocks;
|
|
|
|
${optionalString (cfg.chroot != null) "chroot = ${cfg.chroot};"}
|
|
|
|
}
|
|
|
|
|
|
|
|
${redsocks_blocks}
|
|
|
|
'';
|
|
|
|
internetOnly = [ # TODO: add ipv6-equivalent
|
|
|
|
"-d 0.0.0.0/8"
|
|
|
|
"-d 10.0.0.0/8"
|
|
|
|
"-d 127.0.0.0/8"
|
|
|
|
"-d 169.254.0.0/16"
|
|
|
|
"-d 172.16.0.0/12"
|
|
|
|
"-d 192.168.0.0/16"
|
|
|
|
"-d 224.168.0.0/4"
|
|
|
|
"-d 240.168.0.0/4"
|
|
|
|
];
|
|
|
|
redCond = block:
|
|
|
|
optionalString (isString block.redirectCondition) block.redirectCondition;
|
|
|
|
iptables = concatImapStrings (idx: block:
|
|
|
|
let chain = "REDSOCKS${toString idx}"; doNotRedirect =
|
|
|
|
concatMapStringsSep "\n"
|
|
|
|
(f: "ip46tables -t nat -A ${chain} ${f} -j RETURN 2>/dev/null || true")
|
|
|
|
(block.doNotRedirect ++ (optionals block.redirectInternetOnly internetOnly));
|
|
|
|
in
|
|
|
|
optionalString (block.redirectCondition != false)
|
|
|
|
''
|
|
|
|
ip46tables -t nat -F ${chain} 2>/dev/null || true
|
|
|
|
ip46tables -t nat -N ${chain} 2>/dev/null || true
|
|
|
|
${doNotRedirect}
|
|
|
|
ip46tables -t nat -A ${chain} -p tcp -j REDIRECT --to-ports ${toString block.port}
|
|
|
|
|
|
|
|
# TODO: show errors, when it will be easily possible by a switch to
|
|
|
|
# iptables-restore
|
|
|
|
ip46tables -t nat -A OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true
|
|
|
|
''
|
|
|
|
) cfg.redsocks;
|
|
|
|
in
|
|
|
|
mkIf cfg.enable {
|
|
|
|
users.groups.redsocks = {};
|
|
|
|
users.users.redsocks = {
|
|
|
|
description = "Redsocks daemon";
|
|
|
|
group = "redsocks";
|
|
|
|
isSystemUser = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
systemd.services.redsocks = {
|
|
|
|
description = "Redsocks";
|
|
|
|
after = [ "network.target" ];
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
script = "${pkgs.redsocks}/bin/redsocks -c ${configfile}";
|
|
|
|
};
|
|
|
|
|
|
|
|
networking.firewall.extraCommands = iptables;
|
|
|
|
|
|
|
|
networking.firewall.extraStopCommands =
|
|
|
|
concatImapStringsSep "\n" (idx: block:
|
|
|
|
let chain = "REDSOCKS${toString idx}"; in
|
|
|
|
optionalString (block.redirectCondition != false)
|
|
|
|
"ip46tables -t nat -D OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true"
|
|
|
|
) cfg.redsocks;
|
|
|
|
};
|
|
|
|
|
|
|
|
meta.maintainers = with lib.maintainers; [ ekleog ];
|
|
|
|
}
|