273 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			273 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| { config, lib, pkgs, ... }:
 | |
| 
 | |
| with lib;
 | |
| let
 | |
|   cfg = config.services.redsocks;
 | |
| in
 | |
| {
 | |
|   ##### interface
 | |
|   options = {
 | |
|     services.redsocks = {
 | |
|       enable = mkOption {
 | |
|         type = types.bool;
 | |
|         default = false;
 | |
|         description = "Whether to enable redsocks.";
 | |
|       };
 | |
| 
 | |
|       log_debug = mkOption {
 | |
|         type = types.bool;
 | |
|         default = false;
 | |
|         description = "Log connection progress.";
 | |
|       };
 | |
| 
 | |
|       log_info = mkOption {
 | |
|         type = types.bool;
 | |
|         default = false;
 | |
|         description = "Log start and end of client sessions.";
 | |
|       };
 | |
| 
 | |
|       log = mkOption {
 | |
|         type = types.str;
 | |
|         default = "stderr";
 | |
|         description =
 | |
|           ''
 | |
|             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 =
 | |
|           ''
 | |
|             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 =
 | |
|           ''
 | |
|             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 =
 | |
|               ''
 | |
|                 IP on which redsocks should listen. Defaults to 127.0.0.1 for
 | |
|                 security reasons.
 | |
|               '';
 | |
|           };
 | |
| 
 | |
|           port = mkOption {
 | |
|             type = types.int;
 | |
|             default = 12345;
 | |
|             description = "Port on which redsocks should listen.";
 | |
|           };
 | |
| 
 | |
|           proxy = mkOption {
 | |
|             type = types.str;
 | |
|             description =
 | |
|               ''
 | |
|                 Proxy through which redsocks should forward incoming traffic.
 | |
|                 Example: "example.org:8080"
 | |
|               '';
 | |
|           };
 | |
| 
 | |
|           type = mkOption {
 | |
|             type = types.enum [ "socks4" "socks5" "http-connect" "http-relay" ];
 | |
|             description = "Type of proxy.";
 | |
|           };
 | |
| 
 | |
|           login = mkOption {
 | |
|             type = with types; nullOr str;
 | |
|             default = null;
 | |
|             description = "Login to send to proxy.";
 | |
|           };
 | |
| 
 | |
|           password = mkOption {
 | |
|             type = with types; nullOr str;
 | |
|             default = null;
 | |
|             description =
 | |
|               ''
 | |
|                 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 =
 | |
|               ''
 | |
|                 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;
 | |
|             description = "Exclude all non-globally-routable IPs from redsocks";
 | |
|           };
 | |
| 
 | |
|           doNotRedirect = mkOption {
 | |
|             type = with types; listOf str;
 | |
|             default = [];
 | |
|             description =
 | |
|               ''
 | |
|                 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 =
 | |
|               ''
 | |
|                 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 ];
 | |
| }
 | 
