at 17.09-beta 8.6 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4let 5 cfg = config.services.redsocks; 6in 7{ 8 ##### interface 9 options = { 10 services.redsocks = { 11 enable = mkOption { 12 type = types.bool; 13 default = false; 14 description = "Whether to enable redsocks."; 15 }; 16 17 log_debug = mkOption { 18 type = types.bool; 19 default = false; 20 description = "Log connection progress."; 21 }; 22 23 log_info = mkOption { 24 type = types.bool; 25 default = false; 26 description = "Log start and end of client sessions."; 27 }; 28 29 log = mkOption { 30 type = types.str; 31 default = "stderr"; 32 description = 33 '' 34 Where to send logs. 35 36 Possible values are: 37 - stderr 38 - file:/path/to/file 39 - syslog:FACILITY where FACILITY is any of "daemon", "local0", 40 etc. 41 ''; 42 }; 43 44 chroot = mkOption { 45 type = with types; nullOr str; 46 default = null; 47 description = 48 '' 49 Chroot under which to run redsocks. Log file is opened before 50 chroot, but if logging to syslog /etc/localtime may be required. 51 ''; 52 }; 53 54 redsocks = mkOption { 55 description = 56 '' 57 Local port to proxy associations to be performed. 58 59 The example shows how to configure a proxy to handle port 80 as HTTP 60 relay, and all other ports as HTTP connect. 61 ''; 62 example = [ 63 { port = 23456; proxy = "1.2.3.4:8080"; type = "http-relay"; 64 redirectCondition = "--dport 80"; 65 doNotRedirect = [ "-d 1.2.0.0/16" ]; 66 } 67 { port = 23457; proxy = "1.2.3.4:8080"; type = "http-connect"; 68 redirectCondition = true; 69 doNotRedirect = [ "-d 1.2.0.0/16" ]; 70 } 71 ]; 72 type = types.listOf (types.submodule { options = { 73 ip = mkOption { 74 type = types.str; 75 default = "127.0.0.1"; 76 description = 77 '' 78 IP on which redsocks should listen. Defaults to 127.0.0.1 for 79 security reasons. 80 ''; 81 }; 82 83 port = mkOption { 84 type = types.int; 85 default = 12345; 86 description = "Port on which redsocks should listen."; 87 }; 88 89 proxy = mkOption { 90 type = types.str; 91 description = 92 '' 93 Proxy through which redsocks should forward incoming traffic. 94 Example: "example.org:8080" 95 ''; 96 }; 97 98 type = mkOption { 99 type = types.enum [ "socks4" "socks5" "http-connect" "http-relay" ]; 100 description = "Type of proxy."; 101 }; 102 103 login = mkOption { 104 type = with types; nullOr str; 105 default = null; 106 description = "Login to send to proxy."; 107 }; 108 109 password = mkOption { 110 type = with types; nullOr str; 111 default = null; 112 description = 113 '' 114 Password to send to proxy. WARNING, this will end up 115 world-readable in the store! Awaiting 116 https://github.com/NixOS/nix/issues/8 to be able to fix. 117 ''; 118 }; 119 120 disclose_src = mkOption { 121 type = types.enum [ "false" "X-Forwarded-For" "Forwarded_ip" 122 "Forwarded_ipport" ]; 123 default = "false"; 124 description = 125 '' 126 Way to disclose client IP to the proxy. 127 - "false": do not disclose 128 http-connect supports the following ways: 129 - "X-Forwarded-For": add header "X-Forwarded-For: IP" 130 - "Forwarded_ip": add header "Forwarded: for=IP" (see RFC7239) 131 - "Forwarded_ipport": add header 'Forwarded: for="IP:port"' 132 ''; 133 }; 134 135 redirectInternetOnly = mkOption { 136 type = types.bool; 137 default = true; 138 description = "Exclude all non-globally-routable IPs from redsocks"; 139 }; 140 141 doNotRedirect = mkOption { 142 type = with types; listOf str; 143 default = []; 144 description = 145 '' 146 Iptables filters that if matched will get the packet off of 147 redsocks. 148 ''; 149 example = [ "-d 1.2.3.4" ]; 150 }; 151 152 redirectCondition = mkOption { 153 type = with types; either bool str; 154 default = false; 155 description = 156 '' 157 Conditions to make outbound packets go through this redsocks 158 instance. 159 160 If set to false, no packet will be forwarded. If set to true, 161 all packets will be forwarded (except packets excluded by 162 redirectInternetOnly). 163 164 If set to a string, this is an iptables filter that will be 165 matched against packets before getting them into redsocks. For 166 example, setting it to "--dport 80" will only send 167 packets to port 80 to redsocks. Note "-p tcp" is always 168 implicitly added, as udp can only be proxied through redudp or 169 the like. 170 ''; 171 }; 172 };}); 173 }; 174 175 # TODO: Add support for redudp and dnstc 176 }; 177 }; 178 179 ##### implementation 180 config = let 181 redsocks_blocks = concatMapStrings (block: 182 let proxy = splitString ":" block.proxy; in 183 '' 184 redsocks { 185 local_ip = ${block.ip}; 186 local_port = ${toString block.port}; 187 188 ip = ${elemAt proxy 0}; 189 port = ${elemAt proxy 1}; 190 type = ${block.type}; 191 192 ${optionalString (block.login != null) "login = \"${block.login}\";"} 193 ${optionalString (block.password != null) "password = \"${block.password}\";"} 194 195 disclose_src = ${block.disclose_src}; 196 } 197 '') cfg.redsocks; 198 configfile = pkgs.writeText "redsocks.conf" 199 '' 200 base { 201 log_debug = ${if cfg.log_debug then "on" else "off" }; 202 log_info = ${if cfg.log_info then "on" else "off" }; 203 log = ${cfg.log}; 204 205 daemon = off; 206 redirector = iptables; 207 208 user = redsocks; 209 group = redsocks; 210 ${optionalString (cfg.chroot != null) "chroot = ${cfg.chroot};"} 211 } 212 213 ${redsocks_blocks} 214 ''; 215 internetOnly = [ # TODO: add ipv6-equivalent 216 "-d 0.0.0.0/8" 217 "-d 10.0.0.0/8" 218 "-d 127.0.0.0/8" 219 "-d 169.254.0.0/16" 220 "-d 172.16.0.0/12" 221 "-d 192.168.0.0/16" 222 "-d 224.168.0.0/4" 223 "-d 240.168.0.0/4" 224 ]; 225 redCond = block: 226 optionalString (isString block.redirectCondition) block.redirectCondition; 227 iptables = concatImapStrings (idx: block: 228 let chain = "REDSOCKS${toString idx}"; doNotRedirect = 229 concatMapStringsSep "\n" 230 (f: "ip46tables -t nat -A ${chain} ${f} -j RETURN 2>/dev/null || true") 231 (block.doNotRedirect ++ (optionals block.redirectInternetOnly internetOnly)); 232 in 233 optionalString (block.redirectCondition != false) 234 '' 235 ip46tables -t nat -F ${chain} 2>/dev/null || true 236 ip46tables -t nat -N ${chain} 2>/dev/null || true 237 ${doNotRedirect} 238 ip46tables -t nat -A ${chain} -p tcp -j REDIRECT --to-ports ${toString block.port} 239 240 # TODO: show errors, when it will be easily possible by a switch to 241 # iptables-restore 242 ip46tables -t nat -A OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true 243 '' 244 ) cfg.redsocks; 245 in 246 mkIf cfg.enable { 247 users.groups.redsocks = {}; 248 users.users.redsocks = { 249 description = "Redsocks daemon"; 250 group = "redsocks"; 251 isSystemUser = true; 252 }; 253 254 systemd.services.redsocks = { 255 description = "Redsocks"; 256 after = [ "network.target" ]; 257 wantedBy = [ "multi-user.target" ]; 258 script = "${pkgs.redsocks}/bin/redsocks -c ${configfile}"; 259 }; 260 261 networking.firewall.extraCommands = iptables; 262 263 networking.firewall.extraStopCommands = 264 concatImapStringsSep "\n" (idx: block: 265 let chain = "REDSOCKS${toString idx}"; in 266 optionalString (block.redirectCondition != false) 267 "ip46tables -t nat -D OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true" 268 ) cfg.redsocks; 269 }; 270}