6 cfg = config.networking.nat;
8 mkDest = externalIP: if externalIP == null then "masquerade" else "snat ${externalIP}";
9 dest = mkDest cfg.externalIP;
10 destIPv6 = mkDest cfg.externalIPv6;
12 toNftSet = list: concatStringsSep ", " list;
13 toNftRange = ports: replaceStrings [ ":" ] [ "-" ] (toString ports);
15 ifaceSet = toNftSet (map (x: ''"${x}"'') cfg.internalInterfaces);
16 ipSet = toNftSet cfg.internalIPs;
17 ipv6Set = toNftSet cfg.internalIPv6s;
18 oifExpr = optionalString (cfg.externalInterface != null) ''oifname "${cfg.externalInterface}"'';
20 # Whether given IP (plus optional port) is an IPv6.
21 isIPv6 = ip: length (lib.splitString ":" ip) > 2;
26 matchIP = if isIPv6 IPPorts then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
27 m = builtins.match "${matchIP}:([0-9-]+)" IPPorts;
30 IP = if m == null then throw "bad ip:ports `${IPPorts}'" else elemAt m 0;
31 ports = if m == null then throw "bad ip:ports `${IPPorts}'" else elemAt m 1;
44 # nftables maps for port forward
45 # [daddr .] l4proto . dport : addr . port
49 with (splitIPPorts fwd.destination);
51 optionalString (externalIP != null) "${externalIP} . "
52 }${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}"
56 # nftables maps for port forward loopback dnat
57 # daddr . l4proto . dport : addr . port
58 fwdLoopDnatMap = toNftSet (
63 with (splitIPPorts fwd.destination);
64 "${loopbackip} . ${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}"
69 # nftables set for port forward loopback snat
70 # daddr . l4proto . dport
71 fwdLoopSnatSet = toNftSet (
72 map (fwd: with (splitIPPorts fwd.destination); "${IP} . ${fwd.proto} . ${ports}") forwardPorts
77 type nat hook prerouting priority dstnat;
79 ${optionalString (fwdMap != "") ''
80 iifname "${cfg.externalInterface}" meta l4proto { tcp, udp } dnat ${
81 optionalString (externalIP != null) "${ipVer} daddr . "
82 }meta l4proto . th dport map { ${fwdMap} } comment "port forward"
85 ${optionalString (fwdLoopDnatMap != "") ''
86 meta l4proto { tcp, udp } dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from other hosts behind NAT"
89 ${optionalString (dmzHost != null) ''
90 iifname "${cfg.externalInterface}" dnat ${dmzHost} comment "dmz"
95 type nat hook postrouting priority srcnat;
97 ${optionalString (ifaceSet != "") ''
98 iifname { ${ifaceSet} } ${oifExpr} ${dest} comment "from internal interfaces"
100 ${optionalString (ipSet != "") ''
101 ${ipVer} saddr { ${ipSet} } ${oifExpr} ${dest} comment "from internal IPs"
104 ${optionalString (fwdLoopSnatSet != "") ''
105 iifname != "${cfg.externalInterface}" ${ipVer} daddr . meta l4proto . th dport { ${fwdLoopSnatSet} } masquerade comment "port forward loopback snat"
110 type nat hook output priority mangle;
112 ${optionalString (fwdLoopDnatMap != "") ''
113 meta l4proto { tcp, udp } dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from the host itself"
122 config = mkIf (config.networking.nftables.enable && cfg.enable) {
126 assertion = cfg.extraCommands == "";
127 message = "extraCommands is incompatible with the nftables based nat module: ${cfg.extraCommands}";
130 assertion = cfg.extraStopCommands == "";
131 message = "extraStopCommands is incompatible with the nftables based nat module: ${cfg.extraStopCommands}";
134 assertion = config.networking.nftables.rulesetFile == null;
135 message = "networking.nftables.rulesetFile conflicts with the nat module";
139 networking.nftables.tables = {
145 forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
146 inherit (cfg) dmzHost externalIP;
149 "nixos-nat6" = mkIf cfg.enableIPv6 {
156 forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
158 externalIP = cfg.externalIPv6;
163 networking.firewall.extraForwardRules = optionalString config.networking.firewall.filterForward ''
164 ${optionalString (ifaceSet != "") ''
165 iifname { ${ifaceSet} } ${oifExpr} accept comment "from internal interfaces"
167 ${optionalString (ipSet != "") ''
168 ip saddr { ${ipSet} } ${oifExpr} accept comment "from internal IPs"
170 ${optionalString (ipv6Set != "") ''
171 ip6 saddr { ${ipv6Set} } ${oifExpr} accept comment "from internal IPv6s"