1

Hi dear esteemed community,

I'm having a hard time porting my very functional iptables firewall to nftables. No issues with input/output/forward stuffs, it's mainly the conntrack marking. What I currently do is the following:

1/ I create three routing tables with the ip command, along with rules and conntrack marks. Each of them has one default route, either my FDDI, my VPN or my 4G connexion.

ip route add table maincnx default dev $WAN via 192.168.1.2
ip route add table maincnx 192.168.0.0/24 dev $LAN src 192.168.0.1
ip route add table maincnx 192.168.1.0/24 dev $WAN src 192.168.1.1
ip rule add from 192.168.1.2 table maincnx

[[ $VPN ]] && ip route add table vpnclient default dev $VPNIF via $VPNCLIENTIP
[[ $VPN ]] && ip route add table vpnclient $VPNCLIENTROUTE dev $VPNIF src $VPNCLIENTIP
[[ $VPN ]] && ip route add table vpnclient 192.168.0.0/24 dev $LAN src 192.168.0.1
[[ $VPN ]] && ip route add table vpnclient 192.168.1.0/24 dev $WAN src 192.168.1.1
ip rule add from $VPNCLIENTIP table vpnclient

ip route add table altcnx default dev $WAN2 via 192.168.2.2
ip route add table altcnx 192.168.0.0/24 dev $LAN src 192.168.0.1
ip route add table altcnx 192.168.1.0/24 dev $WAN src 192.168.1.1
ip route add table altcnx 192.168.2.0/24 dev $WAN2 src 192.168.2.1
ip rule add from 192.168.2.2 table altcnx

ip rule add from all fwmark 1 table maincnx
[[ $VPN ]] && ip rule add from all fwmark 2 table vpnclient
ip rule add from all fwmark 3 table altcnx
ip route flush cache

2/ Then, I put some iptables rules together: (I left the comments if anyone is already struggling with the Iptables version)

$IPTABLES -t mangle -A PREROUTING -j CONNMARK --restore-mark # Restore mark previously set
$IPTABLES -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPT # If a mark exists: skip
$IPTABLES -t mangle -A PREROUTING -s 192.168.0.5 -p tcp --sport 50001  -j MARK --set-mark 2 # route through VPN
$IPTABLES -t mangle -A PREROUTING -s 192.168.0.3 -j MARK --set-mark 2
$IPTABLES -t mangle -A PREROUTING -s 192.168.0.4 -j MARK --set-mark 3 # route through 4G
$IPTABLES -t mangle -A POSTROUTING -j CONNMARK --save-mark # save marks to avoid retagging

3/ The associated Postrouting:

$IPTABLES -t nat -A POSTROUTING -o $WAN -j SNAT --to-source 192.168.1.1
$IPTABLES -t nat -A POSTROUTING -o $WAN2 -j SNAT --to-source 192.168.2.1
[[ $VPN ]] && $IPTABLES -t nat -A POSTROUTING -o $VPNIF -j SNAT --to-source $VPNCLIENTIP

ps: $VPN is obviously a variable set to 1 if the VPN is up & running when the script is launched. There are a few other things to make this work like IP rules cleanup and some prerouting/forward, but it's not the point here, if you're interested, comment, I'll post them in full.

Typology: the gateway has 3 eth: 0/1/2, using ips 192.168.1.1 (FDDI), 192.168.0.1 (LAN), 192.168.2.1 (4G) and the gateways are 192.168.1.2 for FDDI and 192.168.2.2 for 4G, the VPN sits on a TUN0 device which IP is somewhat around 10.8.0.x.

So basically, when 192.168.0.5 in initiating a connexion toward a 50001:tcp port, it is routed through the VPN. 192.168.0.3 is constantly using the VPN whatever it's trying to connect to and 192.168.0.4 is going through the 4G connexion and all others are, by default, using routing table 1 and going through the FDDI connexion.

Question: I'm guessing the Ip part of the job stays the same with nftables but what are the equivalent command in nftables to have the mangling and postrouting done in the same as iptables does it here?

1 Answer 1

1

iptables-translate is provided along any modern iptables installation (or might be packaged separately, search for it). It will (attempt) to translate iptables rules into nftables rules. That's an easy way if one doesn't want to read all the documentation (including use for this tool) and man page.

It doesn't require root to be used. For this case (completing a few variables with dummy information to cope with the way OP creates rules):

$ export IPTABLES=/usr/sbin/iptables-translate
$ $IPTABLES -V
iptables-translate v1.8.7 (nf_tables)

$ export WAN=wan WAN2=wan2 VPNIF=vpnif VPNCLIENTIP=192.0.2.2 VPN=1
$ cat > /tmp/rules.bash <<'EOF'
$IPTABLES -t mangle -A PREROUTING -j CONNMARK --restore-mark # Restore mark previously set
$IPTABLES -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPT # If a mark exists: skip
$IPTABLES -t mangle -A PREROUTING -s 192.168.0.5 -p tcp --sport 50001  -j MARK --set-mark 2 # route through VPN
$IPTABLES -t mangle -A PREROUTING -s 192.168.0.3 -j MARK --set-mark 2
$IPTABLES -t mangle -A PREROUTING -s 192.168.0.4 -j MARK --set-mark 3 # route through 4G
$IPTABLES -t mangle -A POSTROUTING -j CONNMARK --save-mark # save marks to avoid retagging

$IPTABLES -t nat -A POSTROUTING -o $WAN -j SNAT --to-source 192.168.1.1
$IPTABLES -t nat -A POSTROUTING -o $WAN2 -j SNAT --to-source 192.168.2.1
[[ $VPN ]] && $IPTABLES -t nat -A POSTROUTING -o $VPNIF -j SNAT --to-source $VPNCLIENTIP
EOF

result:

$ bash /tmp/rules.bash
nft add rule ip mangle PREROUTING counter meta mark set ct mark
nft add rule ip mangle PREROUTING mark != 0x0 counter accept
nft add rule ip mangle PREROUTING ip saddr 192.168.0.5 tcp sport 50001 counter meta mark set 0x2 
nft add rule ip mangle PREROUTING ip saddr 192.168.0.3 counter meta mark set 0x2 
nft add rule ip mangle PREROUTING ip saddr 192.168.0.4 counter meta mark set 0x3 
nft add rule ip mangle POSTROUTING counter ct mark set mark
nft add rule ip nat POSTROUTING oifname "wan" counter snat to 192.168.1.1
nft add rule ip nat POSTROUTING oifname "wan2" counter snat to 192.168.2.1
nft add rule ip nat POSTROUTING oifname "vpnif" counter snat to 192.0.2.2

Translator might not know or be able to translate some parts and would leave the line commented out when this happens, for example:

$ /usr/sbin/iptables-translate -A INPUT -s 192.0.2.2 -j LED --led-trigger-id test
nft # -A INPUT -s 192.0.2.2 -j LED --led-trigger-id test 

As this dumped translation didn't include any comment, one can assume the translation is ok. Just rename correctly the tables, chains (and content back into variables) to be compatible with what was chosen in the nftables ruleset and reuse it. Improve or simplify if needed (eg: counter expression is only needed for debug or statistics, not for actual operations).

It can often be improved with newer features provided by nftables (eg: using maps for better factorization) if needed, but that's not within the scope of this answer.


Caveat: don't just blindly trust the translation, nftables peculiarities must still be known.

The translation tool doesn't know about context of use, so OP must still understand, even if checking expressions and statements discovered from the translation only after. I'm just giving an example not needed by OP: when a (conn)mark is set in OUTPUT hooks for the specific purpose of rerouting (nothing special for marks set in PREROUTING since it's done before routing and this doesn't apply for FORWARD), the correct way to do this with nftables is to set a mark in a chain that is of type route hook output instead of type filter hook output which wouldn't trigger a reroute:

Supported chain types

[...]

Type Families Hooks Description
[...]
route ip, ip6 output If a packet has traversed a chain of this type and is about to be accepted, a new route lookup is performed if relevant parts of the IP header have changed. This allows to e.g. implement policy routing selectors in nftables.

... in order to have a reroute happen at all. Here nft add rule ip mangle OUTPUT wouldn't tell about this detail because usually mangle is just transformed into filter when using nftables. One has to understand how it's working: translation can't do everything.

1
  • Thanks very much appreciated. Indeed iptables-translate is a very appreciable tool in the matter. It won't be a 1:1 translation given the new architecture of nftables but if I can retain most of my original comfort, it'll give me time to explore this great new tool.
    – Kameo
    Aug 15, 2021 at 14:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .