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