1{ config, pkgs, lib, ... }:
2with lib;
3let
4 cfg = config.networking.nftables;
5in
6{
7 ###### interface
8
9 options = {
10 networking.nftables.enable = mkOption {
11 type = types.bool;
12 default = false;
13 description =
14 ''
15 Whether to enable nftables. nftables is a Linux-based packet
16 filtering framework intended to replace frameworks like iptables.
17
18 This conflicts with the standard networking firewall, so make sure to
19 disable it before using nftables.
20
21 Note that if you have Docker enabled you will not be able to use
22 nftables without intervention. Docker uses iptables internally to
23 setup NAT for containers. This module disables the ip_tables kernel
24 module, however Docker automatically loads the module. Please see [1]
25 for more information.
26
27 There are other programs that use iptables internally too, such as
28 libvirt.
29
30 [1]: https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273
31 '';
32 };
33 networking.nftables.ruleset = mkOption {
34 type = types.lines;
35 example = ''
36 # Check out https://wiki.nftables.org/ for better documentation.
37 # Table for both IPv4 and IPv6.
38 table inet filter {
39 # Block all incomming connections traffic except SSH and "ping".
40 chain input {
41 type filter hook input priority 0;
42
43 # accept any localhost traffic
44 iifname lo accept
45
46 # accept traffic originated from us
47 ct state {established, related} accept
48
49 # ICMP
50 # routers may also want: mld-listener-query, nd-router-solicit
51 ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
52 ip protocol icmp icmp type { destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept
53
54 # allow "ping"
55 ip6 nexthdr icmp icmpv6 type echo-request accept
56 ip protocol icmp icmp type echo-request accept
57
58 # accept SSH connections (required for a server)
59 tcp dport 22 accept
60
61 # count and drop any other traffic
62 counter drop
63 }
64
65 # Allow all outgoing connections.
66 chain output {
67 type filter hook output priority 0;
68 accept
69 }
70
71 chain forward {
72 type filter hook forward priority 0;
73 accept
74 }
75 }
76 '';
77 description =
78 ''
79 The ruleset to be used with nftables. Should be in a format that
80 can be loaded using "/bin/nft -f". The ruleset is updated atomically.
81 '';
82 };
83 networking.nftables.rulesetFile = mkOption {
84 type = types.path;
85 default = pkgs.writeTextFile {
86 name = "nftables-rules";
87 text = cfg.ruleset;
88 };
89 description =
90 ''
91 The ruleset file to be used with nftables. Should be in a format that
92 can be loaded using "nft -f". The ruleset is updated atomically.
93 '';
94 };
95 };
96
97 ###### implementation
98
99 config = mkIf cfg.enable {
100 assertions = [{
101 assertion = config.networking.firewall.enable == false;
102 message = "You can not use nftables with services.networking.firewall.";
103 }];
104 boot.blacklistedKernelModules = [ "ip_tables" ];
105 environment.systemPackages = [ pkgs.nftables ];
106 systemd.services.nftables = {
107 description = "nftables firewall";
108 before = [ "network-pre.target" ];
109 wants = [ "network-pre.target" ];
110 wantedBy = [ "multi-user.target" ];
111 reloadIfChanged = true;
112 serviceConfig = let
113 rulesScript = pkgs.writeScript "nftables-rules" ''
114 #! ${pkgs.nftables}/bin/nft -f
115 flush ruleset
116 include "${cfg.rulesetFile}"
117 '';
118 checkScript = pkgs.writeScript "nftables-check" ''
119 #! ${pkgs.stdenv.shell} -e
120 if $(${pkgs.kmod}/bin/lsmod | grep -q ip_tables); then
121 echo "Unload ip_tables before using nftables!" 1>&2
122 exit 1
123 else
124 ${rulesScript}
125 fi
126 '';
127 in {
128 Type = "oneshot";
129 RemainAfterExit = true;
130 ExecStart = checkScript;
131 ExecReload = checkScript;
132 ExecStop = "${pkgs.nftables}/bin/nft flush ruleset";
133 };
134 };
135 };
136}