Nftables

From Extremely Corporate Wiki
Jump to navigation Jump to search


nftables is the successor to iptables. It is a packet classification framework for Linux which is very useful for configuring firewalls. This article is only a summary of features relevant to our own use case. For more general documentation, visit the official nftables wiki (quick reference).

Snippets

Here are some snippets that are useful. The inet family applies to both IPv4 and IPv6.

Common Ruleset

Here's what I usually put in every configuration:

  1. Reset the table with my manually defined rules
  2. Accept all output traffic by default
  3. Drop all input and forward traffic by default
  4. Accept all traffic on the loopback interface
  5. Accept all traffic for related and established connections
  6. Accept all traffic over LAN
#!/usr/sbin/nft -f

# reset the 'filter' table without disturbing others
table inet filter
delete table inet filter

add table inet filter
add chain inet filter input { type filter hook input priority 0; policy drop; }
add chain inet filter forward { type filter hook forward priority 0; policy drop; }
add chain inet filter output { type filter hook output priority 0; policy accept; }

# Accept all loopback traffic
add rule inet filter input iif lo accept
add rule inet filter output iif lo accept
add rule inet filter forward iif lo accept

# Accept established connections
add rule inet filter input ct state related,established accept
add rule inet filter forward ct state related,established accept
add rule inet filter output ct state related,established accept

# LAN connections
add rule inet filter input ip saddr 192.168.2.0/24 accept
add rule inet filter forward ip saddr 192.168.2.0/24 accept

Steam

Allow the Steam client to authenticate and download files. This solution is taken from here. The nftables rules below were translated from the linked iptables rules.

# Rule for Steam
add rule inet filter forward tcp dport 80 accept
add rule inet filter forward tcp dport 443 accept
add rule inet filter forward tcp dport 27000-27030 accept
add rule inet filter forward ip daddr 192.168.2.1 accept

This is at least enough to get Team Fortress 2 working for me. Your mileage may vary.

Rate Limit

Add this above protocol rules but after the rules to accept LAN/loopback traffic to make sure that local connections are not rate limited.

NOTE: I initially got this very wrong. See: https://www.spinics.net/lists/netfilter/msg59918.html for a detailed explanation. Specifically:

Alright, penny finally dropped - burst value = amount of tokens in the bucket and limit rate over X/period = times the bucket can be refilled over period...

I had it that limit rate X/period = token in bucket for period and burst = amount of tokens to exceed, e.g. 300/day would max out at 305...

So if I want 300/day then I go with > limit rate over 1/day burst 300 packets

Thank you once more for the taking the time and making the effort.

user "kfm" on the netfilter mailing list

The above quote isn't actually 100% right, to get 300/day you would want the rate to be 300/day (refill 300 every day) as opposed to 1/day which only refills 1 every day. I've included it since it's the most succinct excerpt from that email thread.

# Connection rate limit
add set inet filter rlimit_v4 { type ipv4_addr; flags dynamic, timeout; timeout 5m; }
add set inet filter rlimit_v6 { type ipv6_addr; flags dynamic, timeout; timeout 5m; }

# This will give 60 connection attempts which refill at a rate of 1 every 2 seconds
add rule inet filter input ct state new, untracked limit rate over 30/minute burst 60 packets add @rlimit_v4 { ip saddr }
add rule inet filter input ct state new, untracked limit rate over 30/minute burst 60 packets add @rlimit_v6 { ip6 saddr }

add rule inet filter input ip saddr @rlimit_v4 drop
add rule inet filter input ip6 saddr @rlimit_v6 drop

Personally, I also add an extra condition a connection must satisfy in order to get added to the r_limit sets: the traffic must be over the physical network interface (iifname "eth0" or whatever physical interfaces come up when you run ip link show). This makes sure connections coming through over WireGuard don't get blocked.

Connection Limit

Limit the number of connections originating from a single source address to 20. Add this above protocol rules but after the rules to accept LAN/loopback traffic to make sure that local connection are not limited.

# Connection limit
add set inet filter climit_v4 { type ipv4_addr; flags dynamic; }
add set inet filter climit_v6 { type ipv6_addr; flags dynamic; }

add rule inet filter output ct state new add @climit_v4 { ip saddr ct count over 20 } drop
add rule inet filter output ct state new add @climit_v6 { ip6 saddr ct count over 20 } drop

The climit_v4 and climit_v6 sets do not have timeouts as elements will automatically be removed when ct count falls to 0 (see nftables wiki).

Block New Outgoing LAN Connections

This rule prevents access to local addresses for users other than root. If you use this rule, make sure you have the rule for the output chain from the "Accept established connections" section. If you don't, bidirectional communication such as SSH will be impossible over your LAN. The goal for this rule is to prevent the computer from establishing new connections to devices on the same LAN.

# Deny access to LAN
add rule inet filter output meta skuid != root ip daddr 192.168.2.0/24 drop
add rule inet filter output meta skuid != root ip6 daddr fe80::/64 drop

Obviously, you should replace 192.168.2.0/24 and fe80::/64 with your network's default gateway if it's something other than those values. To allow additional users besides root to establish new connections over LAN, replace root with a set containing the UIDs you wish to allow like so: meta skuid != { root, 1000, 1001 }.

You might also want to add an exception for DNS look-ups to your modem (typically UDP port 53).

If a user needs to connect to a device on the LAN, but only for a specific purpose, consider adding a rule to explicitly allow only what is required. For example, here is a rule which allows the lp user to communicate with a printer over IPP (port 631):

# allow CUPS to access printer
add rule inet filter output meta skuid lp ip daddr <printer address> tcp dport 631 accept

GPG

https://blog.samuel.domains/blog/security/iptables-gpg-keys-retrieval-firewall-rules

systemd

The default systemd unit for nftables flushes all tables on restart. This is not great because it will erase auto-generated rules such as those generated by Fail2ban in addition to reloading your config. What I recommend is to include the first 3 lines of my common ruleset which makes sure the manually configured table is reset whenever the config is loaded, without touching any other tables. After that, you can override the nftables.service unit as follows:

# /etc/systemd/system/nftables.service.d/override.conf
# Don't flush the entire ruleset on restart. Otherwise, Fail2ban's rules get
# wiped out along with my own manually defined rules.
[Service]
ExecStop=

After doing this, restarting the nftables.service unit will still cleanly reload your config without disrupting other rulesets.