normcap: fix on GNOME wayland when used via keybind or alt-f2 (#351763)
[NixPkgs.git] / nixos / modules / services / networking / nat-iptables.nix
blob804dc0e0340c0eeccdf917f3b0ffd121a846b5e6
1 # This module enables Network Address Translation (NAT).
2 # XXX: todo: support multiple upstream links
3 # see http://yesican.chsoft.biz/lartc/MultihomedLinuxNetworking.html
6   config,
7   lib,
8   pkgs,
9   ...
12 with lib;
14 let
15   cfg = config.networking.nat;
17   mkDest =
18     externalIP: if externalIP == null then "-j MASQUERADE" else "-j SNAT --to-source ${externalIP}";
19   dest = mkDest cfg.externalIP;
20   destIPv6 = mkDest cfg.externalIPv6;
22   # Whether given IP (plus optional port) is an IPv6.
23   isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2;
25   helpers = import ./helpers.nix { inherit config lib; };
27   flushNat = ''
28     ${helpers}
29     ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
30     ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true
31     ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true
32     ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
33     ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true
34     ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true
35     ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true
36     ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true
37     ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true
38     ip46tables -w -t filter -D FORWARD -j nixos-filter-forward 2>/dev/null || true
39     ip46tables -w -t filter -F nixos-filter-forward 2>/dev/null || true
40     ip46tables -w -t filter -X nixos-filter-forward 2>/dev/null || true
42     ${cfg.extraStopCommands}
43   '';
45   mkSetupNat =
46     {
47       iptables,
48       dest,
49       internalIPs,
50       forwardPorts,
51       externalIp,
52     }:
53     ''
54       # We can't match on incoming interface in POSTROUTING, so
55       # mark packets coming from the internal interfaces.
56       ${concatMapStrings (iface: ''
57         ${iptables} -w -t nat -A nixos-nat-pre \
58           -i '${iface}' -j MARK --set-mark 1
59         ${iptables} -w -t filter -A nixos-filter-forward \
60           -i '${iface}' ${
61             optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"
62           } -j ACCEPT
63       '') cfg.internalInterfaces}
65       # NAT the marked packets.
66       ${optionalString (cfg.internalInterfaces != [ ]) ''
67         ${iptables} -w -t nat -A nixos-nat-post -m mark --mark 1 \
68           ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
69       ''}
71       # NAT packets coming from the internal IPs.
72       ${concatMapStrings (range: ''
73         ${iptables} -w -t nat -A nixos-nat-post \
74           -s '${range}' ${
75             optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"
76           } ${dest}
77         ${iptables} -w -t filter -A nixos-filter-forward \
78           -s '${range}' ${
79             optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"
80           } -j ACCEPT
81       '') internalIPs}
83       # Related connections are allowed
84       ${iptables} -w -t filter -A nixos-filter-forward \
85         -m state --state ESTABLISHED,RELATED -j ACCEPT
87       # NAT from external ports to internal ports.
88       ${concatMapStrings (fwd: ''
89         ${iptables} -w -t nat -A nixos-nat-pre \
90           -i ${toString cfg.externalInterface} -p ${fwd.proto} \
91           ${
92             optionalString (externalIp != null) "-d ${externalIp}"
93           } --dport ${builtins.toString fwd.sourcePort} \
94           -j DNAT --to-destination ${fwd.destination}
95         ${iptables} -w -t filter -A nixos-filter-forward \
96           -i ${toString cfg.externalInterface} -p ${fwd.proto} \
97           --dport ${builtins.toString fwd.sourcePort} -j ACCEPT
99         ${concatMapStrings (
100           loopbackip:
101           let
102             matchIP = if isIPv6 fwd.destination then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
103             m = builtins.match "${matchIP}:([0-9-]+)" fwd.destination;
104             destinationIP = if m == null then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0;
105             destinationPorts =
106               if m == null then
107                 throw "bad ip:ports `${fwd.destination}'"
108               else
109                 builtins.replaceStrings [ "-" ] [ ":" ] (elemAt m 1);
110           in
111           ''
112             # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
113             ${iptables} -w -t nat -A nixos-nat-out \
114               -d ${loopbackip} -p ${fwd.proto} \
115               --dport ${builtins.toString fwd.sourcePort} \
116               -j DNAT --to-destination ${fwd.destination}
118             # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
119             ${concatMapStrings (range: ''
120               ${iptables} -w -t nat -A nixos-nat-pre \
121                 -d ${loopbackip} -p ${fwd.proto} -s '${range}' \
122                 --dport ${builtins.toString fwd.sourcePort} \
123                 -j DNAT --to-destination ${fwd.destination}
124               ${iptables} -w -t nat -A nixos-nat-post \
125                 -d ${destinationIP} -p ${fwd.proto} \
126                 -s '${range}' --dport ${destinationPorts} \
127                 -j SNAT --to-source ${loopbackip}
128               ${iptables} -w -t filter -A nixos-filter-forward \
129                 -d ${destinationIP} -p ${fwd.proto} \
130                 -s '${range}' --dport ${destinationPorts} -j ACCEPT
131             '') internalIPs}
132             ${concatMapStrings (iface: ''
133               ${iptables} -w -t nat -A nixos-nat-pre \
134                 -d ${loopbackip} -p ${fwd.proto} -i '${iface}' \
135                 --dport ${builtins.toString fwd.sourcePort} \
136                 -j DNAT --to-destination ${fwd.destination}
137               ${iptables} -w -t nat -A nixos-nat-post \
138                 -d ${destinationIP} -p ${fwd.proto} \
139                 -i '${iface}' --dport ${destinationPorts} \
140                 -j SNAT --to-source ${loopbackip}
141               ${iptables} -w -t filter -A nixos-filter-forward \
142                 -d ${destinationIP} -p ${fwd.proto} \
143                 -i '${iface}' --dport ${destinationPorts} -j ACCEPT
144             '') cfg.internalInterfaces}
145           ''
146         ) fwd.loopbackIPs}
147       '') forwardPorts}
148     '';
150   setupNat = ''
151     ${helpers}
152     # Create subchains where we store rules
153     ip46tables -w -t nat -N nixos-nat-pre
154     ip46tables -w -t nat -N nixos-nat-post
155     ip46tables -w -t nat -N nixos-nat-out
156     ip46tables -w -t filter -N nixos-filter-forward
158     ${mkSetupNat {
159       iptables = "iptables";
160       inherit dest;
161       inherit (cfg) internalIPs;
162       forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
163       externalIp = cfg.externalIP;
164     }}
166     ${optionalString cfg.enableIPv6 (mkSetupNat {
167       iptables = "ip6tables";
168       dest = destIPv6;
169       internalIPs = cfg.internalIPv6s;
170       forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
171       externalIp = cfg.externalIPv6;
172     })}
174     ${optionalString (cfg.dmzHost != null) ''
175       iptables -w -t nat -A nixos-nat-pre \
176         -i ${toString cfg.externalInterface} -j DNAT \
177         --to-destination ${cfg.dmzHost}
178     ''}
180     ${cfg.extraCommands}
182     # Append our chains to the nat tables
183     ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre
184     ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post
185     ip46tables -w -t nat -A OUTPUT -j nixos-nat-out
186     ip46tables -w -t filter -A FORWARD -j nixos-filter-forward
187   '';
193   options = {
195     networking.nat.extraCommands = mkOption {
196       type = types.lines;
197       default = "";
198       example = "iptables -A INPUT -p icmp -j ACCEPT";
199       description = ''
200         Additional shell commands executed as part of the nat
201         initialisation script.
203         This option is incompatible with the nftables based nat module.
204       '';
205     };
207     networking.nat.extraStopCommands = mkOption {
208       type = types.lines;
209       default = "";
210       example = "iptables -D INPUT -p icmp -j ACCEPT || true";
211       description = ''
212         Additional shell commands executed as part of the nat
213         teardown script.
215         This option is incompatible with the nftables based nat module.
216       '';
217     };
219   };
221   config = mkIf (!config.networking.nftables.enable) (mkMerge [
222     ({ networking.firewall.extraCommands = mkBefore flushNat; })
223     (mkIf config.networking.nat.enable {
225       networking.firewall = mkIf config.networking.firewall.enable {
226         extraCommands = setupNat;
227         extraStopCommands = flushNat;
228       };
230       systemd.services = mkIf (!config.networking.firewall.enable) {
231         nat = {
232           description = "Network Address Translation";
233           wantedBy = [ "network.target" ];
234           after = [
235             "network-pre.target"
236             "systemd-modules-load.service"
237           ];
238           path = [ config.networking.firewall.package ];
239           unitConfig.ConditionCapability = "CAP_NET_ADMIN";
241           serviceConfig = {
242             Type = "oneshot";
243             RemainAfterExit = true;
244           };
246           script = flushNat + setupNat;
248           postStop = flushNat;
249         };
250       };
251     })
252   ]);