summary refs log tree commit diff
path: root/nixos/modules/services/networking/nat.nix
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2021-08-04 10:43:07 +0000
committerAlyssa Ross <hi@alyssa.is>2021-08-04 10:43:07 +0000
commit62614cbef7da005c1eda8c9400160f6bcd6546b8 (patch)
treec2630f69080637987b68acb1ee8676d2681fe304 /nixos/modules/services/networking/nat.nix
parentd9c82ed3044c72cecf01c6ea042489d30914577c (diff)
parente24069138dfec3ef94f211f1da005bb5395adc11 (diff)
downloadnixpkgs-62614cbef7da005c1eda8c9400160f6bcd6546b8.tar
nixpkgs-62614cbef7da005c1eda8c9400160f6bcd6546b8.tar.gz
nixpkgs-62614cbef7da005c1eda8c9400160f6bcd6546b8.tar.bz2
nixpkgs-62614cbef7da005c1eda8c9400160f6bcd6546b8.tar.lz
nixpkgs-62614cbef7da005c1eda8c9400160f6bcd6546b8.tar.xz
nixpkgs-62614cbef7da005c1eda8c9400160f6bcd6546b8.tar.zst
nixpkgs-62614cbef7da005c1eda8c9400160f6bcd6546b8.zip
Merge branch 'nixpkgs-update' into master
Diffstat (limited to 'nixos/modules/services/networking/nat.nix')
-rw-r--r--nixos/modules/services/networking/nat.nix120
1 files 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 <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
+          "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;
         };
       };