QEMU/networkd/nftables

Remember this post when I was all enthusiastic about using a local server with QEMU and some apparently simple network setup using systemd-networkd? Remember when you were like Oh!, I've gotta try this out! But wait — what about forwarding ports?

There was supposed to be a fourth section going something like "nftables — Now, let's add some port-forwarding!". Later, it became something like "Oh, it's a little more complicated — let's just complete it once I've got all the informations!". It then became "God, this is awfully fucked up", before finally becoming "Oh, it's actually pretty logic - I'm just really dumb."

Well, welcome to that follow-up.

systemd-networkd

As stated, I decided to use nftables for port-forwarding. Yet packets didn't seem to even enter my prerouting chain, which was rather unfortunate, as it is there where things like "this packet will get redirected to this host" should happen.

After asking on #netfilter and getting friendly troubleshooting advice (and involuntary invoking a pro-systemd vs. anti-systemd debate when I stated that the IPMasquerade option in my systemd-networkd setup might perhaps have had some bad influence on the matter), I decided to tear my network and its configuration down and restart from scratch (while people on #netfilter were ripping each other's head off about the purpose of systemd-networkd and its apparent non-conformity to some RFC).

When everything was back up, my setup looked almost the same, except for the missing IPMasquerade:

# /etc/systemd/network/qemu0.netdev

[NetDev]
Name=qemu0
Kind=bridge
# /etc/systemd/network/qemu0.network

[Match]
Name=qemu0

[Network]
172.16.10.1/24
IPForward=yes

nftables: masquerade

nftables is an effort to replace the existing netfilter/iptables framework. Although it is not production-ready, I decided to bleed and replace my existing iptables setup with nftables, since in the long run, learning the Ins and Outs of a filtering system that has a hard-to-parse syntax and that will soon get replaced didn't seem very lucrative to me.

Hence, the masquerading takes place in /etc/nftables.conf:

#!/usr/sbin/nft -f

table ip nat {
  chain prerouting {
    type nat hook prerouting priority 0;
  }

  chain postrouting {
    type nat hook postrouting priority 0;
    ip saddr 172.16.10.1/24 oifname {"wlp3s0", "enp0s25"} masquerade
  }
}

Looks sane enough, and works.

nftables: port-forwarding

Packet filtering is a topic that could fill a new article on its own — I usually stick to this mental picture (I may stand corrected):

Alright! So let's add some classic rule: one that forwards connections to the host's port 2222 to the guest's port 22:

#!/usr/sbin/nft -f

table ip nat {
  chain prerouting {
    type nat hook prerouting priority 0;
    tcp dport 2222 dnat 172.16.10.2:22
  }

  chain postrouting {
    type nat hook postrouting priority 0;
    ip saddr 172.16.10.1/24 oifname {"wlp3s0", "enp0s25"} masquerade
  }
}

The Troubleshooting

Although everything looks fine, we've got the above-stated problem: Packets don't enter nftables' prerouting chain. Packets not entering the prerouting chain apparently ate an entire Friday afternoon away from me.

Let's prevent that for you:

As already stated in the original article, systemd-networkd provides the options IPForward and IPMasquerade. The tricky thing about IPForward is that if you don't specify it, it will set the sysctl option net.ipv4.conf.<interface>.forwarding to 0 — regardless of whether you set net.ipv4.ip_forward to 1 in /etc/sysctl.d/99-ip_forward.conf. But we already knew that.

The other tricky thing is that this is actually wrong:

Also, IPMasquerade replaces iptables/nftables rules for masquerading in this simple case.

This is incorrect because systemd will not handle IP masquerading on its own, but use iptables. It's actually good, because it doesn't reinvent the wheel, but delegate the filtering to an appropriate tool: the firewall. It also makes sense that if we remove the option, no iptables rules for masquerading will be added.

Yet … in my nftables setup, while packets seemed to enter the postrouting chain, they refused to do the same for prerouting.

At this point, I reread all documentation I could find on creating a NAT with port forwarding for nftables, before stumbling over this:

You cannot use iptables and nft to perform NAT at the same time. So make sure that the iptable_nat module is unloaded:

rmmod iptable_nat

Friday evening. Weekend. It's all fine.

read more

2021

2019

2017

2015