vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / networking / nat-nftables.nix
blob7aa93d8a64b162bde7e733e47d80ec25f655a8c2
1 { config, lib, ... }:
3 with lib;
5 let
6   cfg = config.networking.nat;
8   mkDest = externalIP:
9     if externalIP == null
10     then "masquerade"
11     else "snat ${externalIP}";
12   dest = mkDest cfg.externalIP;
13   destIPv6 = mkDest cfg.externalIPv6;
15   toNftSet = list: concatStringsSep ", " list;
16   toNftRange = ports: replaceStrings [ ":" ] [ "-" ] (toString ports);
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}"'';
23   # Whether given IP (plus optional port) is an IPv6.
24   isIPv6 = ip: length (lib.splitString ":" ip) > 2;
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     };
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);
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);
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;
71         ${optionalString (fwdMap != "") ''
72           iifname "${cfg.externalInterface}" meta l4proto { tcp, udp } dnat meta l4proto . th dport map { ${fwdMap} } comment "port forward"
73         ''}
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         ''}
79         ${optionalString (dmzHost != null) ''
80           iifname "${cfg.externalInterface}" dnat ${dmzHost} comment "dmz"
81         ''}
82       }
84       chain post {
85         type nat hook postrouting priority srcnat;
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         ''}
94         ${optionalString (fwdLoopSnatSet != "") ''
95           iifname != "${cfg.externalInterface}" ${ipVer} daddr . meta l4proto . th dport { ${fwdLoopSnatSet} } masquerade comment "port forward loopback snat"
96         ''}
97       }
99       chain out {
100         type nat hook output priority mangle;
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     '';
112   config = mkIf (config.networking.nftables.enable && cfg.enable) {
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     ];
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     };
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     '';
164   };