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.

Netfilter diagram

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.