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