at 24.11-pre 5.4 kB view raw
1{ config, lib, ... }: 2 3with lib; 4 5let 6 cfg = config.networking.nat; 7 8 mkDest = externalIP: 9 if externalIP == null 10 then "masquerade" 11 else "snat ${externalIP}"; 12 dest = mkDest cfg.externalIP; 13 destIPv6 = mkDest cfg.externalIPv6; 14 15 toNftSet = list: concatStringsSep ", " list; 16 toNftRange = ports: replaceStrings [ ":" ] [ "-" ] (toString ports); 17 18 ifaceSet = toNftSet (map (x: ''"${x}"'') cfg.internalInterfaces); 19 ipSet = toNftSet cfg.internalIPs; 20 ipv6Set = toNftSet cfg.internalIPv6s; 21 oifExpr = optionalString (cfg.externalInterface != null) ''oifname "${cfg.externalInterface}"''; 22 23 # Whether given IP (plus optional port) is an IPv6. 24 isIPv6 = ip: length (lib.splitString ":" ip) > 2; 25 26 splitIPPorts = IPPorts: 27 let 28 matchIP = if isIPv6 IPPorts then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)"; 29 m = builtins.match "${matchIP}:([0-9-]+)" IPPorts; 30 in 31 { 32 IP = if m == null then throw "bad ip:ports `${IPPorts}'" else elemAt m 0; 33 ports = if m == null then throw "bad ip:ports `${IPPorts}'" else elemAt m 1; 34 }; 35 36 mkTable = { ipVer, dest, ipSet, forwardPorts, dmzHost }: 37 let 38 # nftables maps for port forward 39 # l4proto . dport : addr . port 40 fwdMap = toNftSet (map 41 (fwd: 42 with (splitIPPorts fwd.destination); 43 "${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}" 44 ) 45 forwardPorts); 46 47 # nftables maps for port forward loopback dnat 48 # daddr . l4proto . dport : addr . port 49 fwdLoopDnatMap = toNftSet (concatMap 50 (fwd: map 51 (loopbackip: 52 with (splitIPPorts fwd.destination); 53 "${loopbackip} . ${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}" 54 ) 55 fwd.loopbackIPs) 56 forwardPorts); 57 58 # nftables set for port forward loopback snat 59 # daddr . l4proto . dport 60 fwdLoopSnatSet = toNftSet (map 61 (fwd: 62 with (splitIPPorts fwd.destination); 63 "${IP} . ${fwd.proto} . ${ports}" 64 ) 65 forwardPorts); 66 in 67 '' 68 chain pre { 69 type nat hook prerouting priority dstnat; 70 71 ${optionalString (fwdMap != "") '' 72 iifname "${cfg.externalInterface}" meta l4proto { tcp, udp } dnat meta l4proto . th dport map { ${fwdMap} } comment "port forward" 73 ''} 74 75 ${optionalString (fwdLoopDnatMap != "") '' 76 meta l4proto { tcp, udp } dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from other hosts behind NAT" 77 ''} 78 79 ${optionalString (dmzHost != null) '' 80 iifname "${cfg.externalInterface}" dnat ${dmzHost} comment "dmz" 81 ''} 82 } 83 84 chain post { 85 type nat hook postrouting priority srcnat; 86 87 ${optionalString (ifaceSet != "") '' 88 iifname { ${ifaceSet} } ${oifExpr} ${dest} comment "from internal interfaces" 89 ''} 90 ${optionalString (ipSet != "") '' 91 ${ipVer} saddr { ${ipSet} } ${oifExpr} ${dest} comment "from internal IPs" 92 ''} 93 94 ${optionalString (fwdLoopSnatSet != "") '' 95 iifname != "${cfg.externalInterface}" ${ipVer} daddr . meta l4proto . th dport { ${fwdLoopSnatSet} } masquerade comment "port forward loopback snat" 96 ''} 97 } 98 99 chain out { 100 type nat hook output priority mangle; 101 102 ${optionalString (fwdLoopDnatMap != "") '' 103 meta l4proto { tcp, udp } dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from the host itself" 104 ''} 105 } 106 ''; 107 108in 109 110{ 111 112 config = mkIf (config.networking.nftables.enable && cfg.enable) { 113 114 assertions = [ 115 { 116 assertion = cfg.extraCommands == ""; 117 message = "extraCommands is incompatible with the nftables based nat module: ${cfg.extraCommands}"; 118 } 119 { 120 assertion = cfg.extraStopCommands == ""; 121 message = "extraStopCommands is incompatible with the nftables based nat module: ${cfg.extraStopCommands}"; 122 } 123 { 124 assertion = config.networking.nftables.rulesetFile == null; 125 message = "networking.nftables.rulesetFile conflicts with the nat module"; 126 } 127 ]; 128 129 networking.nftables.tables = { 130 "nixos-nat" = { 131 family = "ip"; 132 content = mkTable { 133 ipVer = "ip"; 134 inherit dest ipSet; 135 forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts; 136 inherit (cfg) dmzHost; 137 }; 138 }; 139 "nixos-nat6" = mkIf cfg.enableIPv6 { 140 family = "ip6"; 141 name = "nixos-nat"; 142 content = mkTable { 143 ipVer = "ip6"; 144 dest = destIPv6; 145 ipSet = ipv6Set; 146 forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts; 147 dmzHost = null; 148 }; 149 }; 150 }; 151 152 networking.firewall.extraForwardRules = optionalString config.networking.firewall.filterForward '' 153 ${optionalString (ifaceSet != "") '' 154 iifname { ${ifaceSet} } ${oifExpr} accept comment "from internal interfaces" 155 ''} 156 ${optionalString (ipSet != "") '' 157 ip saddr { ${ipSet} } ${oifExpr} accept comment "from internal IPs" 158 ''} 159 ${optionalString (ipv6Set != "") '' 160 ip6 saddr { ${ipv6Set} } ${oifExpr} accept comment "from internal IPv6s" 161 ''} 162 ''; 163 164 }; 165}