nixos/README.md: relax the requirement of providing option defaults (#334509)
[NixPkgs.git] / nixos / modules / services / networking / nat-nftables.nix
blob891559071c9e8ad2df3be790103466b942578b36
1 { config, lib, ... }:
3 with lib;
5 let
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;
23   splitIPPorts =
24     IPPorts:
25     let
26       matchIP = if isIPv6 IPPorts then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
27       m = builtins.match "${matchIP}:([0-9-]+)" IPPorts;
28     in
29     {
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;
32     };
34   mkTable =
35     {
36       ipVer,
37       dest,
38       ipSet,
39       forwardPorts,
40       dmzHost,
41       externalIP,
42     }:
43     let
44       # nftables maps for port forward
45       # [daddr .] l4proto . dport : addr . port
46       fwdMap = toNftSet (
47         map (
48           fwd:
49           with (splitIPPorts fwd.destination);
50           "${
51             optionalString (externalIP != null) "${externalIP} . "
52           }${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}"
53         ) forwardPorts
54       );
56       # nftables maps for port forward loopback dnat
57       # daddr . l4proto . dport : addr . port
58       fwdLoopDnatMap = toNftSet (
59         concatMap (
60           fwd:
61           map (
62             loopbackip:
63             with (splitIPPorts fwd.destination);
64             "${loopbackip} . ${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}"
65           ) fwd.loopbackIPs
66         ) forwardPorts
67       );
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
73       );
74     in
75     ''
76       chain pre {
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"
83         ''}
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"
87         ''}
89         ${optionalString (dmzHost != null) ''
90           iifname "${cfg.externalInterface}" dnat ${dmzHost} comment "dmz"
91         ''}
92       }
94       chain post {
95         type nat hook postrouting priority srcnat;
97         ${optionalString (ifaceSet != "") ''
98           iifname { ${ifaceSet} } ${oifExpr} ${dest} comment "from internal interfaces"
99         ''}
100         ${optionalString (ipSet != "") ''
101           ${ipVer} saddr { ${ipSet} } ${oifExpr} ${dest} comment "from internal IPs"
102         ''}
104         ${optionalString (fwdLoopSnatSet != "") ''
105           iifname != "${cfg.externalInterface}" ${ipVer} daddr . meta l4proto . th dport { ${fwdLoopSnatSet} } masquerade comment "port forward loopback snat"
106         ''}
107       }
109       chain out {
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"
114         ''}
115       }
116     '';
122   config = mkIf (config.networking.nftables.enable && cfg.enable) {
124     assertions = [
125       {
126         assertion = cfg.extraCommands == "";
127         message = "extraCommands is incompatible with the nftables based nat module: ${cfg.extraCommands}";
128       }
129       {
130         assertion = cfg.extraStopCommands == "";
131         message = "extraStopCommands is incompatible with the nftables based nat module: ${cfg.extraStopCommands}";
132       }
133       {
134         assertion = config.networking.nftables.rulesetFile == null;
135         message = "networking.nftables.rulesetFile conflicts with the nat module";
136       }
137     ];
139     networking.nftables.tables = {
140       "nixos-nat" = {
141         family = "ip";
142         content = mkTable {
143           ipVer = "ip";
144           inherit dest ipSet;
145           forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
146           inherit (cfg) dmzHost externalIP;
147         };
148       };
149       "nixos-nat6" = mkIf cfg.enableIPv6 {
150         family = "ip6";
151         name = "nixos-nat";
152         content = mkTable {
153           ipVer = "ip6";
154           dest = destIPv6;
155           ipSet = ipv6Set;
156           forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
157           dmzHost = null;
158           externalIP = cfg.externalIPv6;
159         };
160       };
161     };
163     networking.firewall.extraForwardRules = optionalString config.networking.firewall.filterForward ''
164       ${optionalString (ifaceSet != "") ''
165         iifname { ${ifaceSet} } ${oifExpr} accept comment "from internal interfaces"
166       ''}
167       ${optionalString (ipSet != "") ''
168         ip saddr { ${ipSet} } ${oifExpr} accept comment "from internal IPs"
169       ''}
170       ${optionalString (ipv6Set != "") ''
171         ip6 saddr { ${ipv6Set} } ${oifExpr} accept comment "from internal IPv6s"
172       ''}
173     '';
175   };