Recently, a new Debian happened. At this point, I am used to upgrading my machines whenever that happens. I had been hoarding Debian systems here and there since I first installed a Debian Jessie back in the day.

Among the thousand of packages being upgraded, there was nftables and iptables. A friend of mine had warned me she had heard about changes done to nftables, though they would likely be backwards compatible. So, I confidently upgraded.

It turns out after rebooting my system and re-reunning my services that every single unit which needed to insert things into nat's POSTROUTING chain could not. Specifically, I got hit with the following error from my docker service:

Jun 23 11:06:59 <HOSTNAME> dockerd[40298]: time="2023-06-23T11:06:59.118341802+02:00" level=warning msg="could not create bridge network for id d1139036d539117fca5f7c7301a9c38e1da1c725534246506364d0fd09c26c6f bridge name br-d1139036d539 while booting up from persistent state: Failed to program NAT chain: Failed to inject DOCKER in PREROUTING chain: iptables failed: iptables --wait -t nat -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER: iptables v1.8.9 (nf_tables):  CHAIN_ADD failed (File exists): chain PREROUTING\n (exit status 4)"

Docker's integration with iptables and nftables has always been shoddy, so I wondered if it was a new thing that they had broken. I was less confident in that hypothesis when I realized my VPN tunnels had also broken (alongside every single unit that bound to its interface's IPv4 address), because the script that sets up post-routing rules had also spit out the same errors.

The Reason

What happens apparently is that, now, when iptables is used to insert nat rules, it tries to insert them in the table called inet nat. In nftables, tables can be split by network protocol (inet, ipv4 and ipv6). I used to have two declarations of tables in my /etc/nftables.conf, one of which looked like this:

table ip nat {
		type nat hook postrouting priority 0;
		oifname "upstream" masquerade;

I theorize that iptables did not have any inet nat table (because both ip nat and ip6 nat existed), so it tried to create one, which could not work, because inet overlaps both ip and ipv6. Yet, there seems to be no fallback on trying to use the distinct nat tables.

So anyways, I fused both tables into inet nat (and used that opportunity to change the priority of POSTROUTING to srcnat), which I believe was not possible before, but do not quote me on that. Rebooted, and everything suddenly worked.