From b93a5a1746636ea9cad81e93172dad20959fdd62 Mon Sep 17 00:00:00 2001 From: Valérian Galliat Date: Fri, 6 Feb 2015 23:55:51 +0100 Subject: nixos/nat: support IPv6 NAT --- nixos/modules/services/networking/nat.nix | 120 ++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix index 21ae9eb8b6d..45eb500fe8c 100644 --- a/nixos/modules/services/networking/nat.nix +++ b/nixos/modules/services/networking/nat.nix @@ -9,7 +9,14 @@ with lib; let cfg = config.networking.nat; - dest = if cfg.externalIP == null then "-j MASQUERADE" else "-j SNAT --to-source ${cfg.externalIP}"; + mkDest = externalIP: if externalIP == null + then "-j MASQUERADE" + else "-j SNAT --to-source ${externalIP}"; + dest = mkDest cfg.externalIP; + destIPv6 = mkDest cfg.externalIPv6; + + # Whether given IP (plus optional port) is an IPv6. + isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2; helpers = import ./helpers.nix { inherit config lib; }; @@ -28,63 +35,80 @@ let ${cfg.extraStopCommands} ''; - setupNat = '' - ${helpers} - # Create subchain where we store rules - ip46tables -w -t nat -N nixos-nat-pre - ip46tables -w -t nat -N nixos-nat-post - ip46tables -w -t nat -N nixos-nat-out - + mkSetupNat = { iptables, dest, internalIPs, forwardPorts }: '' # We can't match on incoming interface in POSTROUTING, so # mark packets coming from the internal interfaces. ${concatMapStrings (iface: '' - iptables -w -t nat -A nixos-nat-pre \ + ${iptables} -w -t nat -A nixos-nat-pre \ -i '${iface}' -j MARK --set-mark 1 '') cfg.internalInterfaces} # NAT the marked packets. ${optionalString (cfg.internalInterfaces != []) '' - iptables -w -t nat -A nixos-nat-post -m mark --mark 1 \ + ${iptables} -w -t nat -A nixos-nat-post -m mark --mark 1 \ ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest} ''} # NAT packets coming from the internal IPs. ${concatMapStrings (range: '' - iptables -w -t nat -A nixos-nat-post \ + ${iptables} -w -t nat -A nixos-nat-post \ -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest} - '') cfg.internalIPs} + '') internalIPs} # NAT from external ports to internal ports. ${concatMapStrings (fwd: '' - iptables -w -t nat -A nixos-nat-pre \ + ${iptables} -w -t nat -A nixos-nat-pre \ -i ${toString cfg.externalInterface} -p ${fwd.proto} \ --dport ${builtins.toString fwd.sourcePort} \ -j DNAT --to-destination ${fwd.destination} ${concatMapStrings (loopbackip: let - m = builtins.match "([0-9.]+):([0-9-]+)" fwd.destination; - destinationIP = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0; - destinationPorts = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1); + matchIP = if isIPv6 fwd.destination then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)"; + m = builtins.match "${matchIP}:([0-9-]+)" fwd.destination; + destinationIP = if m == null then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0; + destinationPorts = if m == null then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1); in '' # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself - iptables -w -t nat -A nixos-nat-out \ + ${iptables} -w -t nat -A nixos-nat-out \ -d ${loopbackip} -p ${fwd.proto} \ --dport ${builtins.toString fwd.sourcePort} \ -j DNAT --to-destination ${fwd.destination} # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT - iptables -w -t nat -A nixos-nat-pre \ + ${iptables} -w -t nat -A nixos-nat-pre \ -d ${loopbackip} -p ${fwd.proto} \ --dport ${builtins.toString fwd.sourcePort} \ -j DNAT --to-destination ${fwd.destination} - iptables -w -t nat -A nixos-nat-post \ + ${iptables} -w -t nat -A nixos-nat-post \ -d ${destinationIP} -p ${fwd.proto} \ --dport ${destinationPorts} \ -j SNAT --to-source ${loopbackip} '') fwd.loopbackIPs} - '') cfg.forwardPorts} + '') forwardPorts} + ''; + + setupNat = '' + ${helpers} + # Create subchains where we store rules + ip46tables -w -t nat -N nixos-nat-pre + ip46tables -w -t nat -N nixos-nat-post + ip46tables -w -t nat -N nixos-nat-out + + ${mkSetupNat { + iptables = "iptables"; + inherit dest; + inherit (cfg) internalIPs; + forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts; + }} + + ${optionalString cfg.enableIPv6 (mkSetupNat { + iptables = "ip6tables"; + dest = destIPv6; + internalIPs = cfg.internalIPv6s; + forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts; + })} ${optionalString (cfg.dmzHost != null) '' iptables -w -t nat -A nixos-nat-pre \ @@ -117,6 +141,15 @@ in ''; }; + networking.nat.enableIPv6 = mkOption { + type = types.bool; + default = false; + description = + '' + Whether to enable IPv6 NAT. + ''; + }; + networking.nat.internalInterfaces = mkOption { type = types.listOf types.str; default = []; @@ -141,6 +174,18 @@ in ''; }; + networking.nat.internalIPv6s = mkOption { + type = types.listOf types.str; + default = []; + example = [ "fc00::/64" ]; + description = + '' + The IPv6 address ranges for which to perform NAT. Packets + coming from these addresses (on any interface) and destined + for the external interface will be rewritten. + ''; + }; + networking.nat.externalInterface = mkOption { type = types.nullOr types.str; default = null; @@ -164,6 +209,19 @@ in ''; }; + networking.nat.externalIPv6 = mkOption { + type = types.nullOr types.str; + default = null; + example = "2001:dc0:2001:11::175"; + description = + '' + The public IPv6 address to which packets from the local + network are to be rewritten. If this is left empty, the + IP address associated with the external interface will be + used. + ''; + }; + networking.nat.forwardPorts = mkOption { type = with types; listOf (submodule { options = { @@ -176,7 +234,7 @@ in destination = mkOption { type = types.str; example = "10.0.0.1:80"; - description = "Forward connection to destination ip:port; to specify a port range, use ip:start-end"; + description = "Forward connection to destination ip:port (or [ipv6]:port); to specify a port range, use ip:start-end"; }; proto = mkOption { @@ -195,11 +253,15 @@ in }; }); default = []; - example = [ { sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; } ]; + example = [ + { sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; } + { sourcePort = 8080; destination = "[fc00::2]:80"; proto = "tcp"; } + ]; description = '' List of forwarded ports from the external interface to - internal destinations by using DNAT. + internal destinations by using DNAT. Destination can be + IPv6 if IPv6 NAT is enabled. ''; }; @@ -246,6 +308,9 @@ in (mkIf config.networking.nat.enable { assertions = [ + { assertion = cfg.enableIPv6 -> config.networking.enableIPv6; + message = "networking.nat.enableIPv6 requires networking.enableIPv6"; + } { assertion = (cfg.dmzHost != null) -> (cfg.externalInterface != null); message = "networking.nat.dmzHost requires networking.nat.externalInterface"; } @@ -261,6 +326,15 @@ in kernel.sysctl = { "net.ipv4.conf.all.forwarding" = mkOverride 99 true; "net.ipv4.conf.default.forwarding" = mkOverride 99 true; + } // optionalAttrs cfg.enableIPv6 { + # Do not prevent IPv6 autoconfiguration. + # See . + "net.ipv6.conf.all.accept_ra" = mkOverride 99 2; + "net.ipv6.conf.default.accept_ra" = mkOverride 99 2; + + # Forward IPv6 packets. + "net.ipv6.conf.all.forwarding" = mkOverride 99 true; + "net.ipv6.conf.default.forwarding" = mkOverride 99 true; }; }; -- cgit 1.4.1