···305305 </listitem>
306306 <listitem>
307307 <para>
308308+ The <literal>firewall</literal> and <literal>nat</literal>
309309+ module now has a nftables based implementation. Enable
310310+ <literal>networking.nftables</literal> to use it.
311311+ </para>
312312+ </listitem>
313313+ <listitem>
314314+ <para>
308315 The <literal>services.fwupd</literal> module now allows
309316 arbitrary daemon settings to be configured in a structured
310317 manner
+2
nixos/doc/manual/release-notes/rl-2305.section.md
···86868787- Resilio sync secret keys can now be provided using a secrets file at runtime, preventing these secrets from ending up in the Nix store.
88888989+- The `firewall` and `nat` module now has a nftables based implementation. Enable `networking.nftables` to use it.
9090+8991- The `services.fwupd` module now allows arbitrary daemon settings to be configured in a structured manner ([`services.fwupd.daemonSettings`](#opt-services.fwupd.daemonSettings)).
90929193- The `unifi-poller` package and corresponding NixOS module have been renamed to `unpoller` to match upstream.
···11+{ config, lib, pkgs, ... }:
22+33+with lib;
44+55+let
66+77+ cfg = config.networking.firewall;
88+99+ ifaceSet = concatStringsSep ", " (
1010+ map (x: ''"${x}"'') cfg.trustedInterfaces
1111+ );
1212+1313+ portsToNftSet = ports: portRanges: concatStringsSep ", " (
1414+ map (x: toString x) ports
1515+ ++ map (x: "${toString x.from}-${toString x.to}") portRanges
1616+ );
1717+1818+in
1919+2020+{
2121+2222+ options = {
2323+2424+ networking.firewall = {
2525+ extraInputRules = mkOption {
2626+ type = types.lines;
2727+ default = "";
2828+ example = "ip6 saddr { fc00::/7, fe80::/10 } tcp dport 24800 accept";
2929+ description = lib.mdDoc ''
3030+ Additional nftables rules to be appended to the input-allow
3131+ chain.
3232+3333+ This option only works with the nftables based firewall.
3434+ '';
3535+ };
3636+3737+ extraForwardRules = mkOption {
3838+ type = types.lines;
3939+ default = "";
4040+ example = "iifname wg0 accept";
4141+ description = lib.mdDoc ''
4242+ Additional nftables rules to be appended to the forward-allow
4343+ chain.
4444+4545+ This option only works with the nftables based firewall.
4646+ '';
4747+ };
4848+ };
4949+5050+ };
5151+5252+ config = mkIf (cfg.enable && config.networking.nftables.enable) {
5353+5454+ assertions = [
5555+ {
5656+ assertion = cfg.extraCommands == "";
5757+ message = "extraCommands is incompatible with the nftables based firewall: ${cfg.extraCommands}";
5858+ }
5959+ {
6060+ assertion = cfg.extraStopCommands == "";
6161+ message = "extraStopCommands is incompatible with the nftables based firewall: ${cfg.extraStopCommands}";
6262+ }
6363+ {
6464+ assertion = cfg.pingLimit == null || !(hasPrefix "--" cfg.pingLimit);
6565+ message = "nftables syntax like \"2/second\" should be used in networking.firewall.pingLimit";
6666+ }
6767+ {
6868+ assertion = config.networking.nftables.rulesetFile == null;
6969+ message = "networking.nftables.rulesetFile conflicts with the firewall";
7070+ }
7171+ ];
7272+7373+ networking.nftables.ruleset = ''
7474+7575+ table inet nixos-fw {
7676+7777+ ${optionalString (cfg.checkReversePath != false) ''
7878+ chain rpfilter {
7979+ type filter hook prerouting priority mangle + 10; policy drop;
8080+8181+ meta nfproto ipv4 udp sport . udp dport { 67 . 68, 68 . 67 } accept comment "DHCPv4 client/server"
8282+ fib saddr . mark ${optionalString (cfg.checkReversePath != "loose") ". iif"} oif exists accept
8383+8484+ ${optionalString cfg.logReversePathDrops ''
8585+ log level info prefix "rpfilter drop: "
8686+ ''}
8787+8888+ }
8989+ ''}
9090+9191+ chain input {
9292+ type filter hook input priority filter; policy drop;
9393+9494+ ${optionalString (ifaceSet != "") ''iifname { ${ifaceSet} } accept comment "trusted interfaces"''}
9595+9696+ # Some ICMPv6 types like NDP is untracked
9797+ ct state vmap { invalid : drop, established : accept, related : accept, * : jump input-allow } comment "*: new and untracked"
9898+9999+ ${optionalString cfg.logRefusedConnections ''
100100+ tcp flags syn / fin,syn,rst,ack log level info prefix "refused connection: "
101101+ ''}
102102+ ${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
103103+ pkttype broadcast log level info prefix "refused broadcast: "
104104+ pkttype multicast log level info prefix "refused multicast: "
105105+ ''}
106106+ ${optionalString cfg.logRefusedPackets ''
107107+ pkttype host log level info prefix "refused packet: "
108108+ ''}
109109+110110+ ${optionalString cfg.rejectPackets ''
111111+ meta l4proto tcp reject with tcp reset
112112+ reject
113113+ ''}
114114+115115+ }
116116+117117+ chain input-allow {
118118+119119+ ${concatStrings (mapAttrsToList (iface: cfg:
120120+ let
121121+ ifaceExpr = optionalString (iface != "default") "iifname ${iface}";
122122+ tcpSet = portsToNftSet cfg.allowedTCPPorts cfg.allowedTCPPortRanges;
123123+ udpSet = portsToNftSet cfg.allowedUDPPorts cfg.allowedUDPPortRanges;
124124+ in
125125+ ''
126126+ ${optionalString (tcpSet != "") "${ifaceExpr} tcp dport { ${tcpSet} } accept"}
127127+ ${optionalString (udpSet != "") "${ifaceExpr} udp dport { ${udpSet} } accept"}
128128+ ''
129129+ ) cfg.allInterfaces)}
130130+131131+ ${optionalString cfg.allowPing ''
132132+ icmp type echo-request ${optionalString (cfg.pingLimit != null) "limit rate ${cfg.pingLimit}"} accept comment "allow ping"
133133+ ''}
134134+135135+ icmpv6 type != { nd-redirect, 139 } accept comment "Accept all ICMPv6 messages except redirects and node information queries (type 139). See RFC 4890, section 4.4."
136136+ ip6 daddr fe80::/64 udp dport 546 accept comment "DHCPv6 client"
137137+138138+ ${cfg.extraInputRules}
139139+140140+ }
141141+142142+ ${optionalString cfg.filterForward ''
143143+ chain forward {
144144+ type filter hook forward priority filter; policy drop;
145145+146146+ ct state vmap { invalid : drop, established : accept, related : accept, * : jump forward-allow } comment "*: new and untracked"
147147+148148+ }
149149+150150+ chain forward-allow {
151151+152152+ icmpv6 type != { router-renumbering, 139 } accept comment "Accept all ICMPv6 messages except renumbering and node information queries (type 139). See RFC 4890, section 4.3."
153153+154154+ ct status dnat accept comment "allow port forward"
155155+156156+ ${cfg.extraForwardRules}
157157+158158+ }
159159+ ''}
160160+161161+ }
162162+163163+ '';
164164+165165+ };
166166+167167+}
+141-439
nixos/modules/services/networking/firewall.nix
···11-/* This module enables a simple firewall.
22-33- The firewall can be customised in arbitrary ways by setting
44- ‘networking.firewall.extraCommands’. For modularity, the firewall
55- uses several chains:
66-77- - ‘nixos-fw’ is the main chain for input packet processing.
88-99- - ‘nixos-fw-accept’ is called for accepted packets. If you want
1010- additional logging, or want to reject certain packets anyway, you
1111- can insert rules at the start of this chain.
1212-1313- - ‘nixos-fw-log-refuse’ and ‘nixos-fw-refuse’ are called for
1414- refused packets. (The former jumps to the latter after logging
1515- the packet.) If you want additional logging, or want to accept
1616- certain packets anyway, you can insert rules at the start of
1717- this chain.
1818-1919- - ‘nixos-fw-rpfilter’ is used as the main chain in the mangle table,
2020- called from the built-in ‘PREROUTING’ chain. If the kernel
2121- supports it and `cfg.checkReversePath` is set this chain will
2222- perform a reverse path filter test.
2323-2424- - ‘nixos-drop’ is used while reloading the firewall in order to drop
2525- all traffic. Since reloading isn't implemented in an atomic way
2626- this'll prevent any traffic from leaking through while reloading
2727- the firewall. However, if the reloading fails, the ‘firewall-stop’
2828- script will be called which in return will effectively disable the
2929- complete firewall (in the default configuration).
3030-3131-*/
3232-331{ config, lib, pkgs, ... }:
342353with lib;
···386397 cfg = config.networking.firewall;
4084141- inherit (config.boot.kernelPackages) kernel;
4242-4343- kernelHasRPFilter = ((kernel.config.isEnabled or (x: false)) "IP_NF_MATCH_RPFILTER") || (kernel.features.netfilterRPFilter or false);
4444-4545- helpers = import ./helpers.nix { inherit config lib; };
4646-4747- writeShScript = name: text: let dir = pkgs.writeScriptBin name ''
4848- #! ${pkgs.runtimeShell} -e
4949- ${text}
5050- ''; in "${dir}/bin/${name}";
5151-5252- defaultInterface = { default = mapAttrs (name: value: cfg.${name}) commonOptions; };
5353- allInterfaces = defaultInterface // cfg.interfaces;
5454-5555- startScript = writeShScript "firewall-start" ''
5656- ${helpers}
5757-5858- # Flush the old firewall rules. !!! Ideally, updating the
5959- # firewall would be atomic. Apparently that's possible
6060- # with iptables-restore.
6161- ip46tables -D INPUT -j nixos-fw 2> /dev/null || true
6262- for chain in nixos-fw nixos-fw-accept nixos-fw-log-refuse nixos-fw-refuse; do
6363- ip46tables -F "$chain" 2> /dev/null || true
6464- ip46tables -X "$chain" 2> /dev/null || true
6565- done
6666-6767-6868- # The "nixos-fw-accept" chain just accepts packets.
6969- ip46tables -N nixos-fw-accept
7070- ip46tables -A nixos-fw-accept -j ACCEPT
7171-7272-7373- # The "nixos-fw-refuse" chain rejects or drops packets.
7474- ip46tables -N nixos-fw-refuse
7575-7676- ${if cfg.rejectPackets then ''
7777- # Send a reset for existing TCP connections that we've
7878- # somehow forgotten about. Send ICMP "port unreachable"
7979- # for everything else.
8080- ip46tables -A nixos-fw-refuse -p tcp ! --syn -j REJECT --reject-with tcp-reset
8181- ip46tables -A nixos-fw-refuse -j REJECT
8282- '' else ''
8383- ip46tables -A nixos-fw-refuse -j DROP
8484- ''}
8585-8686-8787- # The "nixos-fw-log-refuse" chain performs logging, then
8888- # jumps to the "nixos-fw-refuse" chain.
8989- ip46tables -N nixos-fw-log-refuse
9090-9191- ${optionalString cfg.logRefusedConnections ''
9292- ip46tables -A nixos-fw-log-refuse -p tcp --syn -j LOG --log-level info --log-prefix "refused connection: "
9393- ''}
9494- ${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
9595- ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type broadcast \
9696- -j LOG --log-level info --log-prefix "refused broadcast: "
9797- ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type multicast \
9898- -j LOG --log-level info --log-prefix "refused multicast: "
9999- ''}
100100- ip46tables -A nixos-fw-log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse
101101- ${optionalString cfg.logRefusedPackets ''
102102- ip46tables -A nixos-fw-log-refuse \
103103- -j LOG --log-level info --log-prefix "refused packet: "
104104- ''}
105105- ip46tables -A nixos-fw-log-refuse -j nixos-fw-refuse
106106-107107-108108- # The "nixos-fw" chain does the actual work.
109109- ip46tables -N nixos-fw
110110-111111- # Clean up rpfilter rules
112112- ip46tables -t mangle -D PREROUTING -j nixos-fw-rpfilter 2> /dev/null || true
113113- ip46tables -t mangle -F nixos-fw-rpfilter 2> /dev/null || true
114114- ip46tables -t mangle -X nixos-fw-rpfilter 2> /dev/null || true
115115-116116- ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
117117- # Perform a reverse-path test to refuse spoofers
118118- # For now, we just drop, as the mangle table doesn't have a log-refuse yet
119119- ip46tables -t mangle -N nixos-fw-rpfilter 2> /dev/null || true
120120- ip46tables -t mangle -A nixos-fw-rpfilter -m rpfilter --validmark ${optionalString (cfg.checkReversePath == "loose") "--loose"} -j RETURN
121121-122122- # Allows this host to act as a DHCP4 client without first having to use APIPA
123123- iptables -t mangle -A nixos-fw-rpfilter -p udp --sport 67 --dport 68 -j RETURN
124124-125125- # Allows this host to act as a DHCPv4 server
126126- iptables -t mangle -A nixos-fw-rpfilter -s 0.0.0.0 -d 255.255.255.255 -p udp --sport 68 --dport 67 -j RETURN
127127-128128- ${optionalString cfg.logReversePathDrops ''
129129- ip46tables -t mangle -A nixos-fw-rpfilter -j LOG --log-level info --log-prefix "rpfilter drop: "
130130- ''}
131131- ip46tables -t mangle -A nixos-fw-rpfilter -j DROP
132132-133133- ip46tables -t mangle -A PREROUTING -j nixos-fw-rpfilter
134134- ''}
135135-136136- # Accept all traffic on the trusted interfaces.
137137- ${flip concatMapStrings cfg.trustedInterfaces (iface: ''
138138- ip46tables -A nixos-fw -i ${iface} -j nixos-fw-accept
139139- '')}
140140-141141- # Accept packets from established or related connections.
142142- ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept
143143-144144- # Accept connections to the allowed TCP ports.
145145- ${concatStrings (mapAttrsToList (iface: cfg:
146146- concatMapStrings (port:
147147- ''
148148- ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
149149- ''
150150- ) cfg.allowedTCPPorts
151151- ) allInterfaces)}
152152-153153- # Accept connections to the allowed TCP port ranges.
154154- ${concatStrings (mapAttrsToList (iface: cfg:
155155- concatMapStrings (rangeAttr:
156156- let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
157157- ''
158158- ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
159159- ''
160160- ) cfg.allowedTCPPortRanges
161161- ) allInterfaces)}
162162-163163- # Accept packets on the allowed UDP ports.
164164- ${concatStrings (mapAttrsToList (iface: cfg:
165165- concatMapStrings (port:
166166- ''
167167- ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
168168- ''
169169- ) cfg.allowedUDPPorts
170170- ) allInterfaces)}
171171-172172- # Accept packets on the allowed UDP port ranges.
173173- ${concatStrings (mapAttrsToList (iface: cfg:
174174- concatMapStrings (rangeAttr:
175175- let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
176176- ''
177177- ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
178178- ''
179179- ) cfg.allowedUDPPortRanges
180180- ) allInterfaces)}
181181-182182- # Optionally respond to ICMPv4 pings.
183183- ${optionalString cfg.allowPing ''
184184- iptables -w -A nixos-fw -p icmp --icmp-type echo-request ${optionalString (cfg.pingLimit != null)
185185- "-m limit ${cfg.pingLimit} "
186186- }-j nixos-fw-accept
187187- ''}
188188-189189- ${optionalString config.networking.enableIPv6 ''
190190- # Accept all ICMPv6 messages except redirects and node
191191- # information queries (type 139). See RFC 4890, section
192192- # 4.4.
193193- ip6tables -A nixos-fw -p icmpv6 --icmpv6-type redirect -j DROP
194194- ip6tables -A nixos-fw -p icmpv6 --icmpv6-type 139 -j DROP
195195- ip6tables -A nixos-fw -p icmpv6 -j nixos-fw-accept
196196-197197- # Allow this host to act as a DHCPv6 client
198198- ip6tables -A nixos-fw -d fe80::/64 -p udp --dport 546 -j nixos-fw-accept
199199- ''}
200200-201201- ${cfg.extraCommands}
202202-203203- # Reject/drop everything else.
204204- ip46tables -A nixos-fw -j nixos-fw-log-refuse
205205-206206-207207- # Enable the firewall.
208208- ip46tables -A INPUT -j nixos-fw
209209- '';
210210-211211- stopScript = writeShScript "firewall-stop" ''
212212- ${helpers}
213213-214214- # Clean up in case reload fails
215215- ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
216216-217217- # Clean up after added ruleset
218218- ip46tables -D INPUT -j nixos-fw 2>/dev/null || true
219219-220220- ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
221221- ip46tables -t mangle -D PREROUTING -j nixos-fw-rpfilter 2>/dev/null || true
222222- ''}
223223-224224- ${cfg.extraStopCommands}
225225- '';
226226-227227- reloadScript = writeShScript "firewall-reload" ''
228228- ${helpers}
229229-230230- # Create a unique drop rule
231231- ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
232232- ip46tables -F nixos-drop 2>/dev/null || true
233233- ip46tables -X nixos-drop 2>/dev/null || true
234234- ip46tables -N nixos-drop
235235- ip46tables -A nixos-drop -j DROP
236236-237237- # Don't allow traffic to leak out until the script has completed
238238- ip46tables -A INPUT -j nixos-drop
239239-240240- ${cfg.extraStopCommands}
241241-242242- if ${startScript}; then
243243- ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
244244- else
245245- echo "Failed to reload firewall... Stopping"
246246- ${stopScript}
247247- exit 1
248248- fi
249249- '';
250250-2519 canonicalizePortList =
25210 ports: lib.unique (builtins.sort builtins.lessThan ports);
25311···25715 default = [ ];
25816 apply = canonicalizePortList;
25917 example = [ 22 80 ];
260260- description =
261261- lib.mdDoc ''
262262- List of TCP ports on which incoming connections are
263263- accepted.
264264- '';
1818+ description = lib.mdDoc ''
1919+ List of TCP ports on which incoming connections are
2020+ accepted.
2121+ '';
26522 };
2662326724 allowedTCPPortRanges = mkOption {
26825 type = types.listOf (types.attrsOf types.port);
26926 default = [ ];
270270- example = [ { from = 8999; to = 9003; } ];
271271- description =
272272- lib.mdDoc ''
273273- A range of TCP ports on which incoming connections are
274274- accepted.
275275- '';
2727+ example = [{ from = 8999; to = 9003; }];
2828+ description = lib.mdDoc ''
2929+ A range of TCP ports on which incoming connections are
3030+ accepted.
3131+ '';
27632 };
2773327834 allowedUDPPorts = mkOption {
···28036 default = [ ];
28137 apply = canonicalizePortList;
28238 example = [ 53 ];
283283- description =
284284- lib.mdDoc ''
285285- List of open UDP ports.
286286- '';
3939+ description = lib.mdDoc ''
4040+ List of open UDP ports.
4141+ '';
28742 };
2884328944 allowedUDPPortRanges = mkOption {
29045 type = types.listOf (types.attrsOf types.port);
29146 default = [ ];
292292- example = [ { from = 60000; to = 61000; } ];
293293- description =
294294- lib.mdDoc ''
295295- Range of open UDP ports.
296296- '';
4747+ example = [{ from = 60000; to = 61000; }];
4848+ description = lib.mdDoc ''
4949+ Range of open UDP ports.
5050+ '';
29751 };
29852 };
29953···3015530256{
30357304304- ###### interface
305305-30658 options = {
3075930860 networking.firewall = {
30961 enable = mkOption {
31062 type = types.bool;
31163 default = true;
312312- description =
313313- lib.mdDoc ''
314314- Whether to enable the firewall. This is a simple stateful
315315- firewall that blocks connection attempts to unauthorised TCP
316316- or UDP ports on this machine. It does not affect packet
317317- forwarding.
318318- '';
6464+ description = lib.mdDoc ''
6565+ Whether to enable the firewall. This is a simple stateful
6666+ firewall that blocks connection attempts to unauthorised TCP
6767+ or UDP ports on this machine.
6868+ '';
31969 };
3207032171 package = mkOption {
32272 type = types.package;
323323- default = pkgs.iptables;
324324- defaultText = literalExpression "pkgs.iptables";
7373+ default = if config.networking.nftables.enable then pkgs.nftables else pkgs.iptables;
7474+ defaultText = literalExpression ''if config.networking.nftables.enable then "pkgs.nftables" else "pkgs.iptables"'';
32575 example = literalExpression "pkgs.iptables-legacy";
326326- description =
327327- lib.mdDoc ''
328328- The iptables package to use for running the firewall service.
329329- '';
7676+ description = lib.mdDoc ''
7777+ The package to use for running the firewall service.
7878+ '';
33079 };
3318033281 logRefusedConnections = mkOption {
33382 type = types.bool;
33483 default = true;
335335- description =
336336- lib.mdDoc ''
337337- Whether to log rejected or dropped incoming connections.
338338- Note: The logs are found in the kernel logs, i.e. dmesg
339339- or journalctl -k.
340340- '';
8484+ description = lib.mdDoc ''
8585+ Whether to log rejected or dropped incoming connections.
8686+ Note: The logs are found in the kernel logs, i.e. dmesg
8787+ or journalctl -k.
8888+ '';
34189 };
3429034391 logRefusedPackets = mkOption {
34492 type = types.bool;
34593 default = false;
346346- description =
347347- lib.mdDoc ''
348348- Whether to log all rejected or dropped incoming packets.
349349- This tends to give a lot of log messages, so it's mostly
350350- useful for debugging.
351351- Note: The logs are found in the kernel logs, i.e. dmesg
352352- or journalctl -k.
353353- '';
9494+ description = lib.mdDoc ''
9595+ Whether to log all rejected or dropped incoming packets.
9696+ This tends to give a lot of log messages, so it's mostly
9797+ useful for debugging.
9898+ Note: The logs are found in the kernel logs, i.e. dmesg
9999+ or journalctl -k.
100100+ '';
354101 };
355102356103 logRefusedUnicastsOnly = mkOption {
357104 type = types.bool;
358105 default = true;
359359- description =
360360- lib.mdDoc ''
361361- If {option}`networking.firewall.logRefusedPackets`
362362- and this option are enabled, then only log packets
363363- specifically directed at this machine, i.e., not broadcasts
364364- or multicasts.
365365- '';
106106+ description = lib.mdDoc ''
107107+ If {option}`networking.firewall.logRefusedPackets`
108108+ and this option are enabled, then only log packets
109109+ specifically directed at this machine, i.e., not broadcasts
110110+ or multicasts.
111111+ '';
366112 };
367113368114 rejectPackets = mkOption {
369115 type = types.bool;
370116 default = false;
371371- description =
372372- lib.mdDoc ''
373373- If set, refused packets are rejected rather than dropped
374374- (ignored). This means that an ICMP "port unreachable" error
375375- message is sent back to the client (or a TCP RST packet in
376376- case of an existing connection). Rejecting packets makes
377377- port scanning somewhat easier.
378378- '';
117117+ description = lib.mdDoc ''
118118+ If set, refused packets are rejected rather than dropped
119119+ (ignored). This means that an ICMP "port unreachable" error
120120+ message is sent back to the client (or a TCP RST packet in
121121+ case of an existing connection). Rejecting packets makes
122122+ port scanning somewhat easier.
123123+ '';
379124 };
380125381126 trustedInterfaces = mkOption {
382127 type = types.listOf types.str;
383128 default = [ ];
384129 example = [ "enp0s2" ];
385385- description =
386386- lib.mdDoc ''
387387- Traffic coming in from these interfaces will be accepted
388388- unconditionally. Traffic from the loopback (lo) interface
389389- will always be accepted.
390390- '';
130130+ description = lib.mdDoc ''
131131+ Traffic coming in from these interfaces will be accepted
132132+ unconditionally. Traffic from the loopback (lo) interface
133133+ will always be accepted.
134134+ '';
391135 };
392136393137 allowPing = mkOption {
394138 type = types.bool;
395139 default = true;
396396- description =
397397- lib.mdDoc ''
398398- Whether to respond to incoming ICMPv4 echo requests
399399- ("pings"). ICMPv6 pings are always allowed because the
400400- larger address space of IPv6 makes network scanning much
401401- less effective.
402402- '';
140140+ description = lib.mdDoc ''
141141+ Whether to respond to incoming ICMPv4 echo requests
142142+ ("pings"). ICMPv6 pings are always allowed because the
143143+ larger address space of IPv6 makes network scanning much
144144+ less effective.
145145+ '';
403146 };
404147405148 pingLimit = mkOption {
406149 type = types.nullOr (types.separatedString " ");
407150 default = null;
408151 example = "--limit 1/minute --limit-burst 5";
409409- description =
410410- lib.mdDoc ''
411411- If pings are allowed, this allows setting rate limits
412412- on them. If non-null, this option should be in the form of
413413- flags like "--limit 1/minute --limit-burst 5"
414414- '';
152152+ description = lib.mdDoc ''
153153+ If pings are allowed, this allows setting rate limits on them.
154154+155155+ For the iptables based firewall, it should be set like
156156+ "--limit 1/minute --limit-burst 5".
157157+158158+ For the nftables based firewall, it should be set like
159159+ "2/second" or "1/minute burst 5 packets".
160160+ '';
415161 };
416162417163 checkReversePath = mkOption {
418418- type = types.either types.bool (types.enum ["strict" "loose"]);
419419- default = kernelHasRPFilter;
420420- defaultText = literalMD "`true` if supported by the chosen kernel";
164164+ type = types.either types.bool (types.enum [ "strict" "loose" ]);
165165+ default = true;
166166+ defaultText = literalMD "`true` except if the iptables based firewall is in use and the kernel lacks rpfilter support";
421167 example = "loose";
422422- description =
423423- lib.mdDoc ''
424424- Performs a reverse path filter test on a packet. If a reply
425425- to the packet would not be sent via the same interface that
426426- the packet arrived on, it is refused.
168168+ description = lib.mdDoc ''
169169+ Performs a reverse path filter test on a packet. If a reply
170170+ to the packet would not be sent via the same interface that
171171+ the packet arrived on, it is refused.
427172428428- If using asymmetric routing or other complicated routing, set
429429- this option to loose mode or disable it and setup your own
430430- counter-measures.
173173+ If using asymmetric routing or other complicated routing, set
174174+ this option to loose mode or disable it and setup your own
175175+ counter-measures.
431176432432- This option can be either true (or "strict"), "loose" (only
433433- drop the packet if the source address is not reachable via any
434434- interface) or false. Defaults to the value of
435435- kernelHasRPFilter.
436436- '';
177177+ This option can be either true (or "strict"), "loose" (only
178178+ drop the packet if the source address is not reachable via any
179179+ interface) or false.
180180+ '';
437181 };
438182439183 logReversePathDrops = mkOption {
440184 type = types.bool;
441185 default = false;
442442- description =
443443- lib.mdDoc ''
444444- Logs dropped packets failing the reverse path filter test if
445445- the option networking.firewall.checkReversePath is enabled.
446446- '';
186186+ description = lib.mdDoc ''
187187+ Logs dropped packets failing the reverse path filter test if
188188+ the option networking.firewall.checkReversePath is enabled.
189189+ '';
190190+ };
191191+192192+ filterForward = mkOption {
193193+ type = types.bool;
194194+ default = false;
195195+ description = lib.mdDoc ''
196196+ Enable filtering in IP forwarding.
197197+198198+ This option only works with the nftables based firewall.
199199+ '';
447200 };
448201449202 connectionTrackingModules = mkOption {
450203 type = types.listOf types.str;
451204 default = [ ];
452205 example = [ "ftp" "irc" "sane" "sip" "tftp" "amanda" "h323" "netbios_sn" "pptp" "snmp" ];
453453- description =
454454- lib.mdDoc ''
455455- List of connection-tracking helpers that are auto-loaded.
456456- The complete list of possible values is given in the example.
206206+ description = lib.mdDoc ''
207207+ List of connection-tracking helpers that are auto-loaded.
208208+ The complete list of possible values is given in the example.
457209458458- As helpers can pose as a security risk, it is advised to
459459- set this to an empty list and disable the setting
460460- networking.firewall.autoLoadConntrackHelpers unless you
461461- know what you are doing. Connection tracking is disabled
462462- by default.
210210+ As helpers can pose as a security risk, it is advised to
211211+ set this to an empty list and disable the setting
212212+ networking.firewall.autoLoadConntrackHelpers unless you
213213+ know what you are doing. Connection tracking is disabled
214214+ by default.
463215464464- Loading of helpers is recommended to be done through the
465465- CT target. More info:
466466- https://home.regit.org/netfilter-en/secure-use-of-helpers/
467467- '';
216216+ Loading of helpers is recommended to be done through the
217217+ CT target. More info:
218218+ https://home.regit.org/netfilter-en/secure-use-of-helpers/
219219+ '';
468220 };
469221470222 autoLoadConntrackHelpers = mkOption {
471223 type = types.bool;
472224 default = false;
473473- description =
474474- lib.mdDoc ''
475475- Whether to auto-load connection-tracking helpers.
476476- See the description at networking.firewall.connectionTrackingModules
477477-478478- (needs kernel 3.5+)
479479- '';
480480- };
225225+ description = lib.mdDoc ''
226226+ Whether to auto-load connection-tracking helpers.
227227+ See the description at networking.firewall.connectionTrackingModules
481228482482- extraCommands = mkOption {
483483- type = types.lines;
484484- default = "";
485485- example = "iptables -A INPUT -p icmp -j ACCEPT";
486486- description =
487487- lib.mdDoc ''
488488- Additional shell commands executed as part of the firewall
489489- initialisation script. These are executed just before the
490490- final "reject" firewall rule is added, so they can be used
491491- to allow packets that would otherwise be refused.
492492- '';
229229+ (needs kernel 3.5+)
230230+ '';
493231 };
494232495233 extraPackages = mkOption {
496234 type = types.listOf types.package;
497235 default = [ ];
498236 example = literalExpression "[ pkgs.ipset ]";
499499- description =
500500- lib.mdDoc ''
501501- Additional packages to be included in the environment of the system
502502- as well as the path of networking.firewall.extraCommands.
503503- '';
237237+ description = lib.mdDoc ''
238238+ Additional packages to be included in the environment of the system
239239+ as well as the path of networking.firewall.extraCommands.
240240+ '';
504241 };
505242506506- extraStopCommands = mkOption {
507507- type = types.lines;
508508- default = "";
509509- example = "iptables -P INPUT ACCEPT";
510510- description =
511511- lib.mdDoc ''
512512- Additional shell commands executed as part of the firewall
513513- shutdown script. These are executed just after the removal
514514- of the NixOS input rule, or if the service enters a failed
515515- state.
516516- '';
243243+ interfaces = mkOption {
244244+ default = { };
245245+ type = with types; attrsOf (submodule [{ options = commonOptions; }]);
246246+ description = lib.mdDoc ''
247247+ Interface-specific open ports.
248248+ '';
517249 };
518250519519- interfaces = mkOption {
520520- default = { };
521521- type = with types; attrsOf (submodule [ { options = commonOptions; } ]);
522522- description =
523523- lib.mdDoc ''
524524- Interface-specific open ports.
525525- '';
251251+ allInterfaces = mkOption {
252252+ internal = true;
253253+ visible = false;
254254+ default = { default = mapAttrs (name: value: cfg.${name}) commonOptions; } // cfg.interfaces;
255255+ type = with types; attrsOf (submodule [{ options = commonOptions; }]);
256256+ description = lib.mdDoc ''
257257+ All open ports.
258258+ '';
526259 };
527260 } // commonOptions;
528261529262 };
530263531264532532- ###### implementation
533533-534534- # FIXME: Maybe if `enable' is false, the firewall should still be
535535- # built but not started by default?
536265 config = mkIf cfg.enable {
537266267267+ assertions = [
268268+ {
269269+ assertion = cfg.filterForward -> config.networking.nftables.enable;
270270+ message = "filterForward only works with the nftables based firewall";
271271+ }
272272+ ];
273273+538274 networking.firewall.trustedInterfaces = [ "lo" ];
539275540276 environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
···544280 boot.extraModprobeConfig = optionalString cfg.autoLoadConntrackHelpers ''
545281 options nf_conntrack nf_conntrack_helper=1
546282 '';
547547-548548- assertions = [
549549- # This is approximately "checkReversePath -> kernelHasRPFilter",
550550- # but the checkReversePath option can include non-boolean
551551- # values.
552552- { assertion = cfg.checkReversePath == false || kernelHasRPFilter;
553553- message = "This kernel does not support rpfilter"; }
554554- ];
555555-556556- systemd.services.firewall = {
557557- description = "Firewall";
558558- wantedBy = [ "sysinit.target" ];
559559- wants = [ "network-pre.target" ];
560560- before = [ "network-pre.target" ];
561561- after = [ "systemd-modules-load.service" ];
562562-563563- path = [ cfg.package ] ++ cfg.extraPackages;
564564-565565- # FIXME: this module may also try to load kernel modules, but
566566- # containers don't have CAP_SYS_MODULE. So the host system had
567567- # better have all necessary modules already loaded.
568568- unitConfig.ConditionCapability = "CAP_NET_ADMIN";
569569- unitConfig.DefaultDependencies = false;
570570-571571- reloadIfChanged = true;
572572-573573- serviceConfig = {
574574- Type = "oneshot";
575575- RemainAfterExit = true;
576576- ExecStart = "@${startScript} firewall-start";
577577- ExecReload = "@${reloadScript} firewall-reload";
578578- ExecStop = "@${stopScript} firewall-stop";
579579- };
580580- };
581283582284 };
583285
···77with lib;
8899let
1010- cfg = config.networking.nat;
11101212- mkDest = externalIP: if externalIP == null
1313- then "-j MASQUERADE"
1414- else "-j SNAT --to-source ${externalIP}";
1515- dest = mkDest cfg.externalIP;
1616- destIPv6 = mkDest cfg.externalIPv6;
1717-1818- # Whether given IP (plus optional port) is an IPv6.
1919- isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2;
2020-2121- helpers = import ./helpers.nix { inherit config lib; };
2222-2323- flushNat = ''
2424- ${helpers}
2525- ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
2626- ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true
2727- ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true
2828- ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
2929- ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true
3030- ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true
3131- ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true
3232- ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true
3333- ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true
3434-3535- ${cfg.extraStopCommands}
3636- '';
3737-3838- mkSetupNat = { iptables, dest, internalIPs, forwardPorts }: ''
3939- # We can't match on incoming interface in POSTROUTING, so
4040- # mark packets coming from the internal interfaces.
4141- ${concatMapStrings (iface: ''
4242- ${iptables} -w -t nat -A nixos-nat-pre \
4343- -i '${iface}' -j MARK --set-mark 1
4444- '') cfg.internalInterfaces}
4545-4646- # NAT the marked packets.
4747- ${optionalString (cfg.internalInterfaces != []) ''
4848- ${iptables} -w -t nat -A nixos-nat-post -m mark --mark 1 \
4949- ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
5050- ''}
5151-5252- # NAT packets coming from the internal IPs.
5353- ${concatMapStrings (range: ''
5454- ${iptables} -w -t nat -A nixos-nat-post \
5555- -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
5656- '') internalIPs}
5757-5858- # NAT from external ports to internal ports.
5959- ${concatMapStrings (fwd: ''
6060- ${iptables} -w -t nat -A nixos-nat-pre \
6161- -i ${toString cfg.externalInterface} -p ${fwd.proto} \
6262- --dport ${builtins.toString fwd.sourcePort} \
6363- -j DNAT --to-destination ${fwd.destination}
6464-6565- ${concatMapStrings (loopbackip:
6666- let
6767- matchIP = if isIPv6 fwd.destination then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
6868- m = builtins.match "${matchIP}:([0-9-]+)" fwd.destination;
6969- destinationIP = if m == null then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0;
7070- destinationPorts = if m == null then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1);
7171- in ''
7272- # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
7373- ${iptables} -w -t nat -A nixos-nat-out \
7474- -d ${loopbackip} -p ${fwd.proto} \
7575- --dport ${builtins.toString fwd.sourcePort} \
7676- -j DNAT --to-destination ${fwd.destination}
7777-7878- # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
7979- ${iptables} -w -t nat -A nixos-nat-pre \
8080- -d ${loopbackip} -p ${fwd.proto} \
8181- --dport ${builtins.toString fwd.sourcePort} \
8282- -j DNAT --to-destination ${fwd.destination}
8383-8484- ${iptables} -w -t nat -A nixos-nat-post \
8585- -d ${destinationIP} -p ${fwd.proto} \
8686- --dport ${destinationPorts} \
8787- -j SNAT --to-source ${loopbackip}
8888- '') fwd.loopbackIPs}
8989- '') forwardPorts}
9090- '';
9191-9292- setupNat = ''
9393- ${helpers}
9494- # Create subchains where we store rules
9595- ip46tables -w -t nat -N nixos-nat-pre
9696- ip46tables -w -t nat -N nixos-nat-post
9797- ip46tables -w -t nat -N nixos-nat-out
9898-9999- ${mkSetupNat {
100100- iptables = "iptables";
101101- inherit dest;
102102- inherit (cfg) internalIPs;
103103- forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
104104- }}
105105-106106- ${optionalString cfg.enableIPv6 (mkSetupNat {
107107- iptables = "ip6tables";
108108- dest = destIPv6;
109109- internalIPs = cfg.internalIPv6s;
110110- forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
111111- })}
112112-113113- ${optionalString (cfg.dmzHost != null) ''
114114- iptables -w -t nat -A nixos-nat-pre \
115115- -i ${toString cfg.externalInterface} -j DNAT \
116116- --to-destination ${cfg.dmzHost}
117117- ''}
118118-119119- ${cfg.extraCommands}
120120-121121- # Append our chains to the nat tables
122122- ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre
123123- ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post
124124- ip46tables -w -t nat -A OUTPUT -j nixos-nat-out
125125- '';
1111+ cfg = config.networking.nat;
1261212713in
1281412915{
13016131131- ###### interface
132132-13317 options = {
1341813519 networking.nat.enable = mkOption {
13620 type = types.bool;
13721 default = false;
138138- description =
139139- lib.mdDoc ''
140140- Whether to enable Network Address Translation (NAT).
141141- '';
2222+ description = lib.mdDoc ''
2323+ Whether to enable Network Address Translation (NAT).
2424+ '';
14225 };
1432614427 networking.nat.enableIPv6 = mkOption {
14528 type = types.bool;
14629 default = false;
147147- description =
148148- lib.mdDoc ''
149149- Whether to enable IPv6 NAT.
150150- '';
3030+ description = lib.mdDoc ''
3131+ Whether to enable IPv6 NAT.
3232+ '';
15133 };
1523415335 networking.nat.internalInterfaces = mkOption {
15436 type = types.listOf types.str;
155155- default = [];
3737+ default = [ ];
15638 example = [ "eth0" ];
157157- description =
158158- lib.mdDoc ''
159159- The interfaces for which to perform NAT. Packets coming from
160160- these interface and destined for the external interface will
161161- be rewritten.
162162- '';
3939+ description = lib.mdDoc ''
4040+ The interfaces for which to perform NAT. Packets coming from
4141+ these interface and destined for the external interface will
4242+ be rewritten.
4343+ '';
16344 };
1644516546 networking.nat.internalIPs = mkOption {
16647 type = types.listOf types.str;
167167- default = [];
4848+ default = [ ];
16849 example = [ "192.168.1.0/24" ];
169169- description =
170170- lib.mdDoc ''
171171- The IP address ranges for which to perform NAT. Packets
172172- coming from these addresses (on any interface) and destined
173173- for the external interface will be rewritten.
174174- '';
5050+ description = lib.mdDoc ''
5151+ The IP address ranges for which to perform NAT. Packets
5252+ coming from these addresses (on any interface) and destined
5353+ for the external interface will be rewritten.
5454+ '';
17555 };
1765617757 networking.nat.internalIPv6s = mkOption {
17858 type = types.listOf types.str;
179179- default = [];
5959+ default = [ ];
18060 example = [ "fc00::/64" ];
181181- description =
182182- lib.mdDoc ''
183183- The IPv6 address ranges for which to perform NAT. Packets
184184- coming from these addresses (on any interface) and destined
185185- for the external interface will be rewritten.
186186- '';
6161+ description = lib.mdDoc ''
6262+ The IPv6 address ranges for which to perform NAT. Packets
6363+ coming from these addresses (on any interface) and destined
6464+ for the external interface will be rewritten.
6565+ '';
18766 };
1886718968 networking.nat.externalInterface = mkOption {
19069 type = types.nullOr types.str;
19170 default = null;
19271 example = "eth1";
193193- description =
194194- lib.mdDoc ''
195195- The name of the external network interface.
196196- '';
7272+ description = lib.mdDoc ''
7373+ The name of the external network interface.
7474+ '';
19775 };
1987619977 networking.nat.externalIP = mkOption {
20078 type = types.nullOr types.str;
20179 default = null;
20280 example = "203.0.113.123";
203203- description =
204204- lib.mdDoc ''
205205- The public IP address to which packets from the local
206206- network are to be rewritten. If this is left empty, the
207207- IP address associated with the external interface will be
208208- used.
209209- '';
8181+ description = lib.mdDoc ''
8282+ The public IP address to which packets from the local
8383+ network are to be rewritten. If this is left empty, the
8484+ IP address associated with the external interface will be
8585+ used.
8686+ '';
21087 };
2118821289 networking.nat.externalIPv6 = mkOption {
21390 type = types.nullOr types.str;
21491 default = null;
21592 example = "2001:dc0:2001:11::175";
216216- description =
217217- lib.mdDoc ''
218218- The public IPv6 address to which packets from the local
219219- network are to be rewritten. If this is left empty, the
220220- IP address associated with the external interface will be
221221- used.
222222- '';
9393+ description = lib.mdDoc ''
9494+ The public IPv6 address to which packets from the local
9595+ network are to be rewritten. If this is left empty, the
9696+ IP address associated with the external interface will be
9797+ used.
9898+ '';
22399 };
224100225101 networking.nat.forwardPorts = mkOption {
···246122247123 loopbackIPs = mkOption {
248124 type = types.listOf types.str;
249249- default = [];
125125+ default = [ ];
250126 example = literalExpression ''[ "55.1.2.3" ]'';
251127 description = lib.mdDoc "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort' from the host itself and from other hosts behind NAT";
252128 };
253129 };
254130 });
255255- default = [];
131131+ default = [ ];
256132 example = [
257133 { sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; }
258134 { sourcePort = 8080; destination = "[fc00::2]:80"; proto = "tcp"; }
259135 ];
260260- description =
261261- lib.mdDoc ''
262262- List of forwarded ports from the external interface to
263263- internal destinations by using DNAT. Destination can be
264264- IPv6 if IPv6 NAT is enabled.
265265- '';
136136+ description = lib.mdDoc ''
137137+ List of forwarded ports from the external interface to
138138+ internal destinations by using DNAT. Destination can be
139139+ IPv6 if IPv6 NAT is enabled.
140140+ '';
266141 };
267142268143 networking.nat.dmzHost = mkOption {
269144 type = types.nullOr types.str;
270145 default = null;
271146 example = "10.0.0.1";
272272- description =
273273- lib.mdDoc ''
274274- The local IP address to which all traffic that does not match any
275275- forwarding rule is forwarded.
276276- '';
277277- };
278278-279279- networking.nat.extraCommands = mkOption {
280280- type = types.lines;
281281- default = "";
282282- example = "iptables -A INPUT -p icmp -j ACCEPT";
283283- description =
284284- lib.mdDoc ''
285285- Additional shell commands executed as part of the nat
286286- initialisation script.
287287- '';
288288- };
289289-290290- networking.nat.extraStopCommands = mkOption {
291291- type = types.lines;
292292- default = "";
293293- example = "iptables -D INPUT -p icmp -j ACCEPT || true";
294294- description =
295295- lib.mdDoc ''
296296- Additional shell commands executed as part of the nat
297297- teardown script.
298298- '';
147147+ description = lib.mdDoc ''
148148+ The local IP address to which all traffic that does not match any
149149+ forwarding rule is forwarded.
150150+ '';
299151 };
300152301153 };
302154303155304304- ###### implementation
305305-306306- config = mkMerge [
307307- { networking.firewall.extraCommands = mkBefore flushNat; }
308308- (mkIf config.networking.nat.enable {
309309-310310- assertions = [
311311- { assertion = cfg.enableIPv6 -> config.networking.enableIPv6;
312312- message = "networking.nat.enableIPv6 requires networking.enableIPv6";
313313- }
314314- { assertion = (cfg.dmzHost != null) -> (cfg.externalInterface != null);
315315- message = "networking.nat.dmzHost requires networking.nat.externalInterface";
316316- }
317317- { assertion = (cfg.forwardPorts != []) -> (cfg.externalInterface != null);
318318- message = "networking.nat.forwardPorts requires networking.nat.externalInterface";
319319- }
320320- ];
156156+ config = mkIf config.networking.nat.enable {
321157322322- # Use the same iptables package as in config.networking.firewall.
323323- # When the firewall is enabled, this should be deduplicated without any
324324- # error.
325325- environment.systemPackages = [ config.networking.firewall.package ];
158158+ assertions = [
159159+ {
160160+ assertion = cfg.enableIPv6 -> config.networking.enableIPv6;
161161+ message = "networking.nat.enableIPv6 requires networking.enableIPv6";
162162+ }
163163+ {
164164+ assertion = (cfg.dmzHost != null) -> (cfg.externalInterface != null);
165165+ message = "networking.nat.dmzHost requires networking.nat.externalInterface";
166166+ }
167167+ {
168168+ assertion = (cfg.forwardPorts != [ ]) -> (cfg.externalInterface != null);
169169+ message = "networking.nat.forwardPorts requires networking.nat.externalInterface";
170170+ }
171171+ ];
326172327327- boot = {
328328- kernelModules = [ "nf_nat_ftp" ];
329329- kernel.sysctl = {
330330- "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
331331- "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
332332- } // optionalAttrs cfg.enableIPv6 {
333333- # Do not prevent IPv6 autoconfiguration.
334334- # See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
335335- "net.ipv6.conf.all.accept_ra" = mkOverride 99 2;
336336- "net.ipv6.conf.default.accept_ra" = mkOverride 99 2;
173173+ # Use the same iptables package as in config.networking.firewall.
174174+ # When the firewall is enabled, this should be deduplicated without any
175175+ # error.
176176+ environment.systemPackages = [ config.networking.firewall.package ];
337177338338- # Forward IPv6 packets.
339339- "net.ipv6.conf.all.forwarding" = mkOverride 99 true;
340340- "net.ipv6.conf.default.forwarding" = mkOverride 99 true;
341341- };
342342- };
178178+ boot = {
179179+ kernelModules = [ "nf_nat_ftp" ];
180180+ kernel.sysctl = {
181181+ "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
182182+ "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
183183+ } // optionalAttrs cfg.enableIPv6 {
184184+ # Do not prevent IPv6 autoconfiguration.
185185+ # See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
186186+ "net.ipv6.conf.all.accept_ra" = mkOverride 99 2;
187187+ "net.ipv6.conf.default.accept_ra" = mkOverride 99 2;
343188344344- networking.firewall = mkIf config.networking.firewall.enable {
345345- extraCommands = setupNat;
346346- extraStopCommands = flushNat;
189189+ # Forward IPv6 packets.
190190+ "net.ipv6.conf.all.forwarding" = mkOverride 99 true;
191191+ "net.ipv6.conf.default.forwarding" = mkOverride 99 true;
347192 };
193193+ };
348194349349- systemd.services = mkIf (!config.networking.firewall.enable) { nat = {
350350- description = "Network Address Translation";
351351- wantedBy = [ "network.target" ];
352352- after = [ "network-pre.target" "systemd-modules-load.service" ];
353353- path = [ config.networking.firewall.package ];
354354- unitConfig.ConditionCapability = "CAP_NET_ADMIN";
355355-356356- serviceConfig = {
357357- Type = "oneshot";
358358- RemainAfterExit = true;
359359- };
360360-361361- script = flushNat + setupNat;
362362-363363- postStop = flushNat;
364364- }; };
365365- })
366366- ];
195195+ };
367196}
+10-16
nixos/modules/services/networking/nftables.nix
···1212 default = false;
1313 description =
1414 lib.mdDoc ''
1515- Whether to enable nftables. nftables is a Linux-based packet
1616- filtering framework intended to replace frameworks like iptables.
1717-1818- This conflicts with the standard networking firewall, so make sure to
1919- disable it before using nftables.
1515+ Whether to enable nftables and use nftables based firewall if enabled.
1616+ nftables is a Linux-based packet filtering framework intended to
1717+ replace frameworks like iptables.
20182119 Note that if you have Docker enabled you will not be able to use
2220 nftables without intervention. Docker uses iptables internally to
···7977 lib.mdDoc ''
8078 The ruleset to be used with nftables. Should be in a format that
8179 can be loaded using "/bin/nft -f". The ruleset is updated atomically.
8080+ This option conflicts with rulesetFile.
8281 '';
8382 };
8483 networking.nftables.rulesetFile = mkOption {
8585- type = types.path;
8686- default = pkgs.writeTextFile {
8787- name = "nftables-rules";
8888- text = cfg.ruleset;
8989- };
9090- defaultText = literalMD ''a file with the contents of {option}`networking.nftables.ruleset`'';
8484+ type = types.nullOr types.path;
8585+ default = null;
9186 description =
9287 lib.mdDoc ''
9388 The ruleset file to be used with nftables. Should be in a format that
9489 can be loaded using "nft -f". The ruleset is updated atomically.
9090+ This option conflicts with ruleset and nftables based firewall.
9591 '';
9692 };
9793 };
···9995 ###### implementation
1009610197 config = mkIf cfg.enable {
102102- assertions = [{
103103- assertion = config.networking.firewall.enable == false;
104104- message = "You can not use nftables and iptables at the same time. networking.firewall.enable must be set to false.";
105105- }];
10698 boot.blacklistedKernelModules = [ "ip_tables" ];
10799 environment.systemPackages = [ pkgs.nftables ];
108100 networking.networkmanager.firewallBackend = mkDefault "nftables";
···116108 rulesScript = pkgs.writeScript "nftables-rules" ''
117109 #! ${pkgs.nftables}/bin/nft -f
118110 flush ruleset
119119- include "${cfg.rulesetFile}"
111111+ ${if cfg.rulesetFile != null then ''
112112+ include "${cfg.rulesetFile}"
113113+ '' else cfg.ruleset}
120114 '';
121115 in {
122116 Type = "oneshot";