python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / networking / nat.nix
blob0b70ae47ccf523420c4b2c463ae5eb06326fb722
1 # This module enables Network Address Translation (NAT).
2 # XXX: todo: support multiple upstream links
3 # see http://yesican.chsoft.biz/lartc/MultihomedLinuxNetworking.html
5 { config, lib, pkgs, ... }:
7 with lib;
9 let
10   cfg = config.networking.nat;
12   mkDest = externalIP: if externalIP == null
13                        then "-j MASQUERADE"
14                        else "-j SNAT --to-source ${externalIP}";
15   dest = mkDest cfg.externalIP;
16   destIPv6 = mkDest cfg.externalIPv6;
18   # Whether given IP (plus optional port) is an IPv6.
19   isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2;
21   helpers = import ./helpers.nix { inherit config lib; };
23   flushNat = ''
24     ${helpers}
25     ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
26     ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true
27     ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true
28     ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
29     ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true
30     ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true
31     ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true
32     ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true
33     ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true
35     ${cfg.extraStopCommands}
36   '';
38   mkSetupNat = { iptables, dest, internalIPs, forwardPorts }: ''
39     # We can't match on incoming interface in POSTROUTING, so
40     # mark packets coming from the internal interfaces.
41     ${concatMapStrings (iface: ''
42       ${iptables} -w -t nat -A nixos-nat-pre \
43         -i '${iface}' -j MARK --set-mark 1
44     '') cfg.internalInterfaces}
46     # NAT the marked packets.
47     ${optionalString (cfg.internalInterfaces != []) ''
48       ${iptables} -w -t nat -A nixos-nat-post -m mark --mark 1 \
49         ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
50     ''}
52     # NAT packets coming from the internal IPs.
53     ${concatMapStrings (range: ''
54       ${iptables} -w -t nat -A nixos-nat-post \
55         -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
56     '') internalIPs}
58     # NAT from external ports to internal ports.
59     ${concatMapStrings (fwd: ''
60       ${iptables} -w -t nat -A nixos-nat-pre \
61         -i ${toString cfg.externalInterface} -p ${fwd.proto} \
62         --dport ${builtins.toString fwd.sourcePort} \
63         -j DNAT --to-destination ${fwd.destination}
65       ${concatMapStrings (loopbackip:
66         let
67           matchIP          = if isIPv6 fwd.destination then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
68           m                = builtins.match "${matchIP}:([0-9-]+)" fwd.destination;
69           destinationIP    = if m == null then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0;
70           destinationPorts = if m == null then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1);
71         in ''
72           # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
73           ${iptables} -w -t nat -A nixos-nat-out \
74             -d ${loopbackip} -p ${fwd.proto} \
75             --dport ${builtins.toString fwd.sourcePort} \
76             -j DNAT --to-destination ${fwd.destination}
78           # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
79           ${iptables} -w -t nat -A nixos-nat-pre \
80             -d ${loopbackip} -p ${fwd.proto} \
81             --dport ${builtins.toString fwd.sourcePort} \
82             -j DNAT --to-destination ${fwd.destination}
84           ${iptables} -w -t nat -A nixos-nat-post \
85             -d ${destinationIP} -p ${fwd.proto} \
86             --dport ${destinationPorts} \
87             -j SNAT --to-source ${loopbackip}
88         '') fwd.loopbackIPs}
89     '') forwardPorts}
90   '';
92   setupNat = ''
93     ${helpers}
94     # Create subchains where we store rules
95     ip46tables -w -t nat -N nixos-nat-pre
96     ip46tables -w -t nat -N nixos-nat-post
97     ip46tables -w -t nat -N nixos-nat-out
99     ${mkSetupNat {
100       iptables = "iptables";
101       inherit dest;
102       inherit (cfg) internalIPs;
103       forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
104     }}
106     ${optionalString cfg.enableIPv6 (mkSetupNat {
107       iptables = "ip6tables";
108       dest = destIPv6;
109       internalIPs = cfg.internalIPv6s;
110       forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
111     })}
113     ${optionalString (cfg.dmzHost != null) ''
114       iptables -w -t nat -A nixos-nat-pre \
115         -i ${toString cfg.externalInterface} -j DNAT \
116         --to-destination ${cfg.dmzHost}
117     ''}
119     ${cfg.extraCommands}
121     # Append our chains to the nat tables
122     ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre
123     ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post
124     ip46tables -w -t nat -A OUTPUT -j nixos-nat-out
125   '';
131   ###### interface
133   options = {
135     networking.nat.enable = mkOption {
136       type = types.bool;
137       default = false;
138       description =
139         lib.mdDoc ''
140           Whether to enable Network Address Translation (NAT).
141         '';
142     };
144     networking.nat.enableIPv6 = mkOption {
145       type = types.bool;
146       default = false;
147       description =
148         lib.mdDoc ''
149           Whether to enable IPv6 NAT.
150         '';
151     };
153     networking.nat.internalInterfaces = mkOption {
154       type = types.listOf types.str;
155       default = [];
156       example = [ "eth0" ];
157       description =
158         lib.mdDoc ''
159           The interfaces for which to perform NAT. Packets coming from
160           these interface and destined for the external interface will
161           be rewritten.
162         '';
163     };
165     networking.nat.internalIPs = mkOption {
166       type = types.listOf types.str;
167       default = [];
168       example = [ "192.168.1.0/24" ];
169       description =
170         lib.mdDoc ''
171           The IP address ranges for which to perform NAT.  Packets
172           coming from these addresses (on any interface) and destined
173           for the external interface will be rewritten.
174         '';
175     };
177     networking.nat.internalIPv6s = mkOption {
178       type = types.listOf types.str;
179       default = [];
180       example = [ "fc00::/64" ];
181       description =
182         lib.mdDoc ''
183           The IPv6 address ranges for which to perform NAT.  Packets
184           coming from these addresses (on any interface) and destined
185           for the external interface will be rewritten.
186         '';
187     };
189     networking.nat.externalInterface = mkOption {
190       type = types.nullOr types.str;
191       default = null;
192       example = "eth1";
193       description =
194         lib.mdDoc ''
195           The name of the external network interface.
196         '';
197     };
199     networking.nat.externalIP = mkOption {
200       type = types.nullOr types.str;
201       default = null;
202       example = "203.0.113.123";
203       description =
204         lib.mdDoc ''
205           The public IP address to which packets from the local
206           network are to be rewritten.  If this is left empty, the
207           IP address associated with the external interface will be
208           used.
209         '';
210     };
212     networking.nat.externalIPv6 = mkOption {
213       type = types.nullOr types.str;
214       default = null;
215       example = "2001:dc0:2001:11::175";
216       description =
217         lib.mdDoc ''
218           The public IPv6 address to which packets from the local
219           network are to be rewritten.  If this is left empty, the
220           IP address associated with the external interface will be
221           used.
222         '';
223     };
225     networking.nat.forwardPorts = mkOption {
226       type = with types; listOf (submodule {
227         options = {
228           sourcePort = mkOption {
229             type = types.either types.int (types.strMatching "[[:digit:]]+:[[:digit:]]+");
230             example = 8080;
231             description = lib.mdDoc "Source port of the external interface; to specify a port range, use a string with a colon (e.g. \"60000:61000\")";
232           };
234           destination = mkOption {
235             type = types.str;
236             example = "10.0.0.1:80";
237             description = lib.mdDoc "Forward connection to destination ip:port (or [ipv6]:port); to specify a port range, use ip:start-end";
238           };
240           proto = mkOption {
241             type = types.str;
242             default = "tcp";
243             example = "udp";
244             description = lib.mdDoc "Protocol of forwarded connection";
245           };
247           loopbackIPs = mkOption {
248             type = types.listOf types.str;
249             default = [];
250             example = literalExpression ''[ "55.1.2.3" ]'';
251             description = lib.mdDoc "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort' from the host itself and from other hosts behind NAT";
252           };
253         };
254       });
255       default = [];
256       example = [
257         { sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; }
258         { sourcePort = 8080; destination = "[fc00::2]:80"; proto = "tcp"; }
259       ];
260       description =
261         lib.mdDoc ''
262           List of forwarded ports from the external interface to
263           internal destinations by using DNAT. Destination can be
264           IPv6 if IPv6 NAT is enabled.
265         '';
266     };
268     networking.nat.dmzHost = mkOption {
269       type = types.nullOr types.str;
270       default = null;
271       example = "10.0.0.1";
272       description =
273         lib.mdDoc ''
274           The local IP address to which all traffic that does not match any
275           forwarding rule is forwarded.
276         '';
277     };
279     networking.nat.extraCommands = mkOption {
280       type = types.lines;
281       default = "";
282       example = "iptables -A INPUT -p icmp -j ACCEPT";
283       description =
284         lib.mdDoc ''
285           Additional shell commands executed as part of the nat
286           initialisation script.
287         '';
288     };
290     networking.nat.extraStopCommands = mkOption {
291       type = types.lines;
292       default = "";
293       example = "iptables -D INPUT -p icmp -j ACCEPT || true";
294       description =
295         lib.mdDoc ''
296           Additional shell commands executed as part of the nat
297           teardown script.
298         '';
299     };
301   };
304   ###### implementation
306   config = mkMerge [
307     { networking.firewall.extraCommands = mkBefore flushNat; }
308     (mkIf config.networking.nat.enable {
310       assertions = [
311         { assertion = cfg.enableIPv6           -> config.networking.enableIPv6;
312           message = "networking.nat.enableIPv6 requires networking.enableIPv6";
313         }
314         { assertion = (cfg.dmzHost != null)    -> (cfg.externalInterface != null);
315           message = "networking.nat.dmzHost requires networking.nat.externalInterface";
316         }
317         { assertion = (cfg.forwardPorts != []) -> (cfg.externalInterface != null);
318           message = "networking.nat.forwardPorts requires networking.nat.externalInterface";
319         }
320       ];
322       # Use the same iptables package as in config.networking.firewall.
323       # When the firewall is enabled, this should be deduplicated without any
324       # error.
325       environment.systemPackages = [ config.networking.firewall.package ];
327       boot = {
328         kernelModules = [ "nf_nat_ftp" ];
329         kernel.sysctl = {
330           "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
331           "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
332         } // optionalAttrs cfg.enableIPv6 {
333           # Do not prevent IPv6 autoconfiguration.
334           # See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
335           "net.ipv6.conf.all.accept_ra" = mkOverride 99 2;
336           "net.ipv6.conf.default.accept_ra" = mkOverride 99 2;
338           # Forward IPv6 packets.
339           "net.ipv6.conf.all.forwarding" = mkOverride 99 true;
340           "net.ipv6.conf.default.forwarding" = mkOverride 99 true;
341         };
342       };
344       networking.firewall = mkIf config.networking.firewall.enable {
345         extraCommands = setupNat;
346         extraStopCommands = flushNat;
347       };
349       systemd.services = mkIf (!config.networking.firewall.enable) { nat = {
350         description = "Network Address Translation";
351         wantedBy = [ "network.target" ];
352         after = [ "network-pre.target" "systemd-modules-load.service" ];
353         path = [ config.networking.firewall.package ];
354         unitConfig.ConditionCapability = "CAP_NET_ADMIN";
356         serviceConfig = {
357           Type = "oneshot";
358           RemainAfterExit = true;
359         };
361         script = flushNat + setupNat;
363         postStop = flushNat;
364       }; };
365     })
366   ];