Adventures in Rolling Your Own Router: Part IV
In my previous post, I created a virtual
router, LAN, and client in order to test out my plan for the Debian router. In
this post, I’ll set up NAT using nftables.
Installing nftables
As I discovered in the last post, Debian Buster apparently doesn’t come with
nftables installed, but instead has an iptables-nft shim layer on top of
the nf_tables kernel subsystem that accepts regular iptables rule syntax.
It also comes with iptables-legacy that you can use the alternatives
system to switch to. The details of this are all laid out in the
wiki.
To get started with nftables we just need to start and enable it:
jmp@debrouter:~$ sudo apt install nftables
jmp@debrouter:~$ sudo systemctl enable nftables
Replacing iptables-legacy Rules
Since setting IPMasquerade=true in /etc/systemd/network/internal.network
caused systemd-networkd to create iptables-legacy rules, we need to make
a change to that file.
jmp@debrouter:~$ cat /etc/systemd/network/internal.network
[Match]
Name=intern0
[Network]
Address=192.168.77.1/24
IPForward=ipv4
# Currently systemd-networkd uses iptables-legacy.
# Masquerade is manually configured with nftables.
# IPMasquerade=true
After a reboot, the iptables-legacy rules are gone.
Now, we need to manually set up masquerading using nftables directly. There
are some instructions on the nftables
wiki,
but in general documentation for nftables is still sparse. If you’re looking
for a good primer on nftables, check out this
video from the Open Source
Summit.
nftables controls netfilter. In this regard, it’s similar to iptables.

Unlike iptables, nftables does not come with all the netfilter default
tables and chains set up. This is for performance reasons - if you don’t need
them, they don’t get executed. However, this means we need to create the
tables and chains that we do need.
Since masquerade is applied in the postrouting chain of the nat table,
we first need to create the nat table.
jmp@debrouter:~$ sudo nft add table nat
It looks like this automatically creates this table in the ip family which
means chains in this table will apply only to IPV4. This is good, because we
don’t need NAT for IPv6.
Next, we need to create the postrouting chain.
https://github.com/LukasJoswiak/etch
jmp@debrouter:~$ sudo nft 'add chain nat postrouting { type nat hook postrouting priority 100 ; }'
Finally, we can add a rule to the chain to enable masquerade.
We specify that only packets originating in the LAN and being sent out on the
external interface should have masquerade applied.
jmp@debrouter:~$ sudo nft add rule nat postrouting ip saddr 192.168.77.0/24 oifname extern0 masquerade
That’s all it takes! We can now ping servers on the Internet from debclient
again.
Persistence of nftables Ruleset
We want these rules to survive the next reboot, so we can add them to
/etc/nftables.conf.
#!/usr/sbin/nft -f
flush ruleset
table ip nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
ip saddr 192.168.77.0/24 oifname extern0 masquerade;
}
}
In the next post, we will create a simple
firewall using nftables.