My introduction to the future of Linux firewalls
nftables is one of those things I’ve been aware of for the past few years, but have never bothered actually putting the time into learning beyond knowing “its like iptables but better”.
So, what is nftables, and how is it better than iptables?
This weekend, I took some time to learn enough nftables to port over my existing ufw firewall rules (and some NAT logic).
iptables, nftables, and iptables-nft
iptables is a userspace utility for Linux systems that helps configure kernel-internal packet filtering rules. It specifically interacts with Netfilter, which is a callback system in the kernel, allowing kernel modules to manipulate or drop traffic.
nftables is a replacement for iptables that’s been around since 2014. It is known for being more efficient due to what Wikipedia calls “less code duplication”. It is also designed to be easily extended with support for new protocols as needed. The configuration language of nftables is actually what drew me to it, as it seems a lot more “sane” in my eyes.
Of course, many enterprise users of Linux move very slowly, and don’t tend to update things unless absolutely required (if it works, it works). So, to deal with these users, an additional utility exists called iptables-nft. iptables-nft allows users to use the iptables command like normal, but in the background it quietly translates everything over to nftables rules without the users really noticing.
You are probably using iptables-nft on your Linux system right now. Check with iptables -V.
Some basic firewall rules
nftables has a very nice configuration format, and will actually read config files from disk on start (unlike iptables and its iptables-restore helper).
To get started with an nftables firewall, create a new table.
I’ve chosen to use the inet table type since it can support both IPv4 and IPv6 rules at the same time, but there are ip and ip6 tables if you prefer separating your rules.
Inside this table, you can define chains, and tell nftables how to hook them into the kernel’s internal filtering system.
Note the type filter hook <name> priority filter statement. This is whats telling nftables about each chain’s position in the kernel packet filtering flow.
The policy <drop|accept> statements define the default action of each chain if no rules are matched.
So, let’s say we want to allow traffic in from enp1s0 to local port 22. To do this, we’d add a new input rule:
Now, lets say I have a secondary interface, enp2s0, that I’d like to use as a LAN, with traffic flowing through this machine out of enp1s0. To do so, I’d add a new forward rule:
There are also other filter options I haven’t covered here. I’ve been beginning to play with some DPI rules for example, and they are equally easy to write as the simple interface rules shown above.
NAT
Continuing my traffic forwarding example from above, nftables also makes NAT rules quite easy to work with.
NAT rules live in their own table(s). For this example, I’ll write an IPv4 NAT:
This example takes any traffic from the LAN interface (which I’ve arbitrarily decided lives in 192.168.0.0/24), and source-NATs it via 192.0.2.1 on the WAN interface (enp1s0).
My thoughts so far
I’ve quite enjoyed my adventure moving to nftables so far.
Of course, I’m also a little worried I’ve fundamentally screwed something up in my production config, leaving a hole open, but according to my adhoc penetration testing on that machine, everything is fine. So 🤷♂️.
I will now proceed to send this post to all my friends, telling them to try re-writing their firewalls.
(sorry lol)