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, ... }:
10 cfg = config.networking.nat;
15 else "-j SNAT --to-source ${externalIP}";
16 dest = mkDest cfg.externalIP;
17 destIPv6 = mkDest cfg.externalIPv6;
19 # Whether given IP (plus optional port) is an IPv6.
20 isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2;
22 helpers = import ./helpers.nix { inherit config lib; };
26 ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
27 ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true
28 ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true
29 ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
30 ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true
31 ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true
32 ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true
33 ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true
34 ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true
36 ${cfg.extraStopCommands}
39 mkSetupNat = { iptables, dest, internalIPs, forwardPorts }: ''
40 # We can't match on incoming interface in POSTROUTING, so
41 # mark packets coming from the internal interfaces.
42 ${concatMapStrings (iface: ''
43 ${iptables} -w -t nat -A nixos-nat-pre \
44 -i '${iface}' -j MARK --set-mark 1
45 '') cfg.internalInterfaces}
47 # NAT the marked packets.
48 ${optionalString (cfg.internalInterfaces != []) ''
49 ${iptables} -w -t nat -A nixos-nat-post -m mark --mark 1 \
50 ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
53 # NAT packets coming from the internal IPs.
54 ${concatMapStrings (range: ''
55 ${iptables} -w -t nat -A nixos-nat-post \
56 -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
59 # NAT from external ports to internal ports.
60 ${concatMapStrings (fwd: ''
61 ${iptables} -w -t nat -A nixos-nat-pre \
62 -i ${toString cfg.externalInterface} -p ${fwd.proto} \
63 --dport ${builtins.toString fwd.sourcePort} \
64 -j DNAT --to-destination ${fwd.destination}
66 ${concatMapStrings (loopbackip:
68 matchIP = if isIPv6 fwd.destination then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
69 m = builtins.match "${matchIP}:([0-9-]+)" fwd.destination;
70 destinationIP = if m == null then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0;
71 destinationPorts = if m == null then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1);
73 # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
74 ${iptables} -w -t nat -A nixos-nat-out \
75 -d ${loopbackip} -p ${fwd.proto} \
76 --dport ${builtins.toString fwd.sourcePort} \
77 -j DNAT --to-destination ${fwd.destination}
79 # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
80 ${iptables} -w -t nat -A nixos-nat-pre \
81 -d ${loopbackip} -p ${fwd.proto} \
82 --dport ${builtins.toString fwd.sourcePort} \
83 -j DNAT --to-destination ${fwd.destination}
85 ${iptables} -w -t nat -A nixos-nat-post \
86 -d ${destinationIP} -p ${fwd.proto} \
87 --dport ${destinationPorts} \
88 -j SNAT --to-source ${loopbackip}
95 # Create subchains where we store rules
96 ip46tables -w -t nat -N nixos-nat-pre
97 ip46tables -w -t nat -N nixos-nat-post
98 ip46tables -w -t nat -N nixos-nat-out
101 iptables = "iptables";
103 inherit (cfg) internalIPs;
104 forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
107 ${optionalString cfg.enableIPv6 (mkSetupNat {
108 iptables = "ip6tables";
110 internalIPs = cfg.internalIPv6s;
111 forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
114 ${optionalString (cfg.dmzHost != null) ''
115 iptables -w -t nat -A nixos-nat-pre \
116 -i ${toString cfg.externalInterface} -j DNAT \
117 --to-destination ${cfg.dmzHost}
122 # Append our chains to the nat tables
123 ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre
124 ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post
125 ip46tables -w -t nat -A OUTPUT -j nixos-nat-out
134 networking.nat.extraCommands = mkOption {
137 example = "iptables -A INPUT -p icmp -j ACCEPT";
139 Additional shell commands executed as part of the nat
140 initialisation script.
142 This option is incompatible with the nftables based nat module.
146 networking.nat.extraStopCommands = mkOption {
149 example = "iptables -D INPUT -p icmp -j ACCEPT || true";
151 Additional shell commands executed as part of the nat
154 This option is incompatible with the nftables based nat module.
161 config = mkIf (!config.networking.nftables.enable)
163 ({ networking.firewall.extraCommands = mkBefore flushNat; })
164 (mkIf config.networking.nat.enable {
166 networking.firewall = mkIf config.networking.firewall.enable {
167 extraCommands = setupNat;
168 extraStopCommands = flushNat;
171 systemd.services = mkIf (!config.networking.firewall.enable) {
173 description = "Network Address Translation";
174 wantedBy = [ "network.target" ];
175 after = [ "network-pre.target" "systemd-modules-load.service" ];
176 path = [ config.networking.firewall.package ];
177 unitConfig.ConditionCapability = "CAP_NET_ADMIN";
181 RemainAfterExit = true;
184 script = flushNat + setupNat;