Migrating my iptables setup to nftables

Wanting to become familiar with nftables, I decided to jump in at the deep end and just use it on my local workstation. The goal was to replace the existing iptables setup, ideally without any drawbacks. The following essay will guide you through what I have done in order to achieve that.
In order to be able to follow, you should already be familiar with iptables and at least have a rough idea of what nftables are. I don’t see much sense in reading the following text without already being an iptables user, but here is a recommendation of resources to consult regarding nftables (although it’s not strictly a must-have):
- LWN has a nice introductory article.
- On Linux Audit there is a short comparison between iptables and nftables.
- Pablo Neira Ayuso’s excellent nftables beginner workshop is available on YouTube.
- Finally there is Florian Westphal’s talk in which he dives deeply into the technical reasons why iptables is being replaced and why nftables is such a good substitute.
I don’t rely upon distributions facilities to manage firewall setups so much but rather have my own shell script, which provides a bit more convenience especially when it comes to keeping iptables and ip6tables in sync. This is how it looks:
#!/bin/bash # useful wrappers for failure analysis function cmd_or_print() { # command "$@" || echo "failed at: '$@'" } function ipt() { # iptables params cmd_or_print iptables "$@" } function ip6t { # ip6tables params cmd_or_print ip6tables "$@" } # have a simple way of doing things in iptables and ip6tables in parallel function ip46t() { # ip(6)tables params ipt "$@" ip6t "$@" } # interfaces wan=eth0 wan6=eth0 #vpn=tap23 vpn=tun+ #vpn=tap6 nstx=tun0 sixxs=sixxs0 # ip addresses sixxs_pop="212.224.0.188" my_ipv6_pfx="2001:41d0:8:c8e::/64" my_second_ipv4="85.214.71.2" # clear out everything for it in iptables ip6tables; do for table in filter mangle nat raw; do $it -t $table -nL >/dev/null 2>&1 || continue # non-existing table $it -t $table -F # delete rules $it -t $table -X # delete custom chains $it -t $table -Z # zero counters done done ### define custom chains # filter invalid packets ip46t -N invalid ip46t -A invalid -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP ip46t -A invalid -p tcp --tcp-flags SYN,RST SYN,RST -j DROP ip46t -N blacklist ip46t -A invalid -j blacklist # stateful firewalling if everything else fails ip46t -N states ip46t -A states -m state --state INVALID -j REJECT ip46t -A states -m state --state RELATED,ESTABLISHED -j ACCEPT # filtering input on the wan interface ip46t -N wan_in ipt -A wan_in -p icmp -j ACCEPT ip6t -A wan_in -p icmpv6 -j ACCEPT ip6t -A wan_in -m state --state NEW -m udp -p udp --dport 546 --sport 547 \ -s fe80::/10 -d fe80::/10 -j ACCEPT # dhcpv6 ip46t -A wan_in -p tcp --dport 22 -j ACCEPT # ssh ip46t -A wan_in -p esp -j ACCEPT # ipsec ipt -A wan_in -p ah -j ACCEPT # ipsec ip6t -A wan_in -m ah -j ACCEPT # ipsec ip46t -A wan_in -p tcp --dport 27374:27474 -j ACCEPT # super hidden port range ip46t -A wan_in -p udp --dport 27374:27474 -j ACCEPT # super hidden port range ipt -A wan_in -p ipv6 -j ACCEPT # allow ipv6 traffic, handled by ip6tables # set policy of chains to DROP for it in iptables ip6tables; do for chain in INPUT OUTPUT FORWARD; do $it -P $chain DROP done done # builtin chains ip46t -A INPUT -j invalid ip46t -A INPUT -j wan_in ip46t -A INPUT -i lo -j ACCEPT ip46t -A INPUT -i vnetbr0 -j ACCEPT ip46t -A INPUT -j states ip46t -A OUTPUT -j ACCEPT ip46t -A FORWARD -i vnetbr0 -j ACCEPT ip46t -A FORWARD -o vnetbr0 -j wan_in ip46t -A FORWARD -o vnetbr0 -j states ipt -t nat -A POSTROUTING -s 192.168.42.0/24 \! -o vnetbr0 -j MASQUERADE
Since it clears the whole table setup at first, after making changes I can simply fire it up again and then tell the distribution to save the rules so they are applied automatically during system boot up. Here is the ip(6)tables-save
output after having it applied:
*nat :PREROUTING ACCEPT [0:0] :INPUT ACCEPT [0:0] :OUTPUT ACCEPT [2:121] :POSTROUTING ACCEPT [2:121] [0:0] -A POSTROUTING -s 192.168.42.0/24 ! -o vnetbr0 -j MASQUERADE COMMIT *raw :PREROUTING ACCEPT [21:5125] :OUTPUT ACCEPT [22:2332] COMMIT *mangle :PREROUTING ACCEPT [23:5414] :INPUT ACCEPT [23:5414] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [22:2332] :POSTROUTING ACCEPT [21:2280] COMMIT *filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] :blacklist - [0:0] :invalid - [0:0] :states - [0:0] :wan_in - [0:0] [21:5125] -A INPUT -j invalid [21:5125] -A INPUT -j wan_in [0:0] -A INPUT -i lo -j ACCEPT [1:80] -A INPUT -i vnetbr0 -j ACCEPT [20:5045] -A INPUT -j states [0:0] -A FORWARD -i vnetbr0 -j ACCEPT [0:0] -A FORWARD -o vnetbr0 -j wan_in [0:0] -A FORWARD -o vnetbr0 -j states [21:2280] -A OUTPUT -j ACCEPT [0:0] -A invalid -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP [0:0] -A invalid -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP [21:5125] -A invalid -j blacklist [0:0] -A states -m state --state INVALID -j REJECT \ --reject-with icmp-port-unreachable [20:5045] -A states -m state --state RELATED,ESTABLISHED -j ACCEPT [0:0] -A wan_in -p icmp -j ACCEPT [0:0] -A wan_in -p tcp -m tcp --dport 22 -j ACCEPT [0:0] -A wan_in -p esp -j ACCEPT [0:0] -A wan_in -p ah -j ACCEPT [0:0] -A wan_in -p tcp -m tcp --dport 27374:27474 -j ACCEPT [0:0] -A wan_in -p udp -m udp --dport 27374:27474 -j ACCEPT [0:0] -A wan_in -p ipv6 -j ACCEPT COMMIT
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] :blacklist - [0:0] :invalid - [0:0] :states - [0:0] :wan_in - [0:0] [23:2255] -A INPUT -j invalid [23:2255] -A INPUT -j wan_in [18:1741] -A INPUT -i lo -j ACCEPT [0:0] -A INPUT -i vnetbr0 -j ACCEPT [4:450] -A INPUT -j states [0:0] -A FORWARD -i vnetbr0 -j ACCEPT [0:0] -A FORWARD -o vnetbr0 -j wan_in [0:0] -A FORWARD -o vnetbr0 -j states [26:2482] -A OUTPUT -j ACCEPT [0:0] -A invalid -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP [0:0] -A invalid -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP [23:2255] -A invalid -j blacklist [0:0] -A states -m state --state INVALID -j REJECT \ --reject-with icmp6-port-unreachable [4:450] -A states -m state --state RELATED,ESTABLISHED -j ACCEPT [1:64] -A wan_in -p ipv6-icmp -j ACCEPT [0:0] -A wan_in -s fe80::/10 -d fe80::/10 -p udp -m state --state NEW \ -m udp --sport 547 --dport 546 -j ACCEPT [0:0] -A wan_in -p tcp -m tcp --dport 22 -j ACCEPT [0:0] -A wan_in -p esp -j ACCEPT [0:0] -A wan_in -m ah -j ACCEPT [0:0] -A wan_in -p tcp -m tcp --dport 27374:27474 -j ACCEPT [0:0] -A wan_in -p udp -m udp --dport 27374:27474 -j ACCEPT COMMIT
Using the converter as a starting point
Since mid of February 2016, the iptables repository contains a command ip(6)tables-restore-translate
(still unreleased) to read from iptables-save output and generate a suitable nftables setup. In case it fails to translate a given statement into nftables syntax, it will output the problematic line as a comment. Sensing some relief from the tedious work of finding a suitable replacement for each and every rule, I decided to give it a go and continue with whatever it generates. This turned out to be a pretty good choice, considering the amount of work it saved me:
add table ip nat add chain ip nat PREROUTING { type filter hook prerouting priority 0; } add chain ip nat INPUT { type filter hook input priority 0; } add chain ip nat OUTPUT { type filter hook output priority 0; } add chain ip nat POSTROUTING { type filter hook postrouting priority 0; } add rule ip nat POSTROUTING oifname != vnetbr0 ip saddr 192.168.42.0 counter masquerade add table ip raw add chain ip raw PREROUTING { type filter hook prerouting priority 0; } add chain ip raw OUTPUT { type filter hook output priority 0; } add table ip mangle add chain ip mangle PREROUTING { type filter hook prerouting priority 0; } add chain ip mangle INPUT { type filter hook input priority 0; } add chain ip mangle FORWARD { type filter hook forward priority 0; } add chain ip mangle OUTPUT { type filter hook output priority 0; } add chain ip mangle POSTROUTING { type filter hook postrouting priority 0; } add table ip filter add chain ip filter INPUT { type filter hook input priority 0; } add chain ip filter FORWARD { type filter hook forward priority 0; } add chain ip filter OUTPUT { type filter hook output priority 0; } add chain ip filter blacklist add chain ip filter invalid add chain ip filter states add chain ip filter wan_in add rule ip filter INPUT counter jump invalid add rule ip filter INPUT counter jump wan_in add rule ip filter INPUT iifname lo counter accept add rule ip filter INPUT iifname vnetbr0 counter accept add rule ip filter INPUT counter jump states add rule ip filter FORWARD iifname vnetbr0 counter accept add rule ip filter FORWARD oifname vnetbr0 counter jump wan_in add rule ip filter FORWARD oifname vnetbr0 counter jump states add rule ip filter OUTPUT counter accept add rule ip filter invalid tcp flags & fin|syn == fin|syn counter drop add rule ip filter invalid tcp flags & syn|rst == syn|rst counter drop add rule ip filter invalid counter jump blacklist add rule ip filter states ct state invalid counter reject add rule ip filter states ct state related,established counter accept add rule ip filter wan_in ip protocol icmp counter accept add rule ip filter wan_in tcp dport 22 counter accept add rule ip filter wan_in ip protocol esp counter accept add rule ip filter wan_in ip protocol ah counter accept add rule ip filter wan_in tcp dport 27374-27474 counter accept add rule ip filter wan_in udp dport 27374-27474 counter accept add rule ip filter wan_in ip protocol ipv6 counter accept
add table ip6 filter add chain ip6 filter INPUT { type filter hook input priority 0; } add chain ip6 filter FORWARD { type filter hook forward priority 0; } add chain ip6 filter OUTPUT { type filter hook output priority 0; } add chain ip6 filter blacklist add chain ip6 filter invalid add chain ip6 filter states add chain ip6 filter wan_in add rule ip6 filter INPUT counter jump invalid add rule ip6 filter INPUT counter jump wan_in add rule ip6 filter INPUT iifname lo counter accept add rule ip6 filter INPUT iifname vnetbr0 counter accept add rule ip6 filter INPUT counter jump states add rule ip6 filter FORWARD iifname vnetbr0 counter accept add rule ip6 filter FORWARD oifname vnetbr0 counter jump wan_in add rule ip6 filter FORWARD oifname vnetbr0 counter jump states add rule ip6 filter OUTPUT counter accept add rule ip6 filter invalid tcp flags & fin|syn == fin|syn counter drop add rule ip6 filter invalid tcp flags & syn|rst == syn|rst counter drop add rule ip6 filter invalid counter jump blacklist add rule ip6 filter states ct state invalid counter reject add rule ip6 filter states ct state related,established counter accept add rule ip6 filter wan_in meta l4proto ipv6-icmp counter accept add rule ip6 filter wan_in ip6 saddr fe80:: ip6 daddr fe80:: \ ct state new udp sport 547 udp dport 546 counter accept add rule ip6 filter wan_in tcp dport 22 counter accept add rule ip6 filter wan_in meta l4proto esp counter accept add rule ip6 filter wan_in counter accept add rule ip6 filter wan_in tcp dport 27374-27474 counter accept add rule ip6 filter wan_in udp dport 27374-27474 counter accept
On a closer look, things turned out to be less than perfect even though there were no rules for which the converter failed to find a substitute. Here is a list of issues I found; together with how I solved them:
Everything you need to grow your career.
With your free Red Hat Developer program membership, unlock our library of cheat sheets and ebooks on next-generation application development.
SIGN UPMissing drop policy
The converter apparently ignored the drop policy of all the built-in chains, effectively annulling the whole firewall. Luckily, this is easy to fix in hindsight: Just add the missing policy
statement to the relevant chains like so:
add chain ip filter INPUT { type filter hook input priority 0; policy drop; }
Fixing the converter was easy, as explained in the patch‘s description.
Dropped -m ah
match
It seems like the converter assumed an Authentication Header match without parameters like I used in ip6tables setup would never fail, and so I simply dropped it. This is not true though, as it is the suggested replacement for iptables’ AH protocol match (i.e. -p ah
). Looking at the equivalent ESP header match, I assumed that the following would be a valid replacement:
nft add rule ip6 filter wan_in meta l4proto ah counter accept
Sadly, this is not completely correct, either: In fact it matches only if the AH header follows immediately after the IPv6 header – if there is any other extension header in between, it does not see it. To my surprise, there is no easy fix – matching on mere existence of an IPv6 extension header is actually a limitation to nftables, and at the time of this writing there was no implementation available. So the l4proto match above had to suffice for now, luckily IPv6 extension headers don’t see much use yet (other than AH and ESP, of course).
Missing prefix lengths in subnet definitions
I completely overlooked this and only found out about it when testing the results: The converter turned all subnet definitions into simple IP addresses, which then never matched (since those ending in zero are not used by hosts). Using the iptables-translate
utility is an easy way of reproducing the issue:
$ iptables-translate -A INPUT -s 10.0.0.0/8 -j ACCEPT nft add rule ip filter INPUT ip saddr 10.0.0.0 counter accept
Luckily, this was quite as easy to fix as the missing policy statement above.
TCP flags matches turned into a mess
This problem is not visible from looking only at the generated nftables rules, but becomes obvious when listing the ruleset after having it applied: The rule’s path to the kernel and back turns it from this:
tcp flags & fin|syn == fin|syn counter drop
into that:
tcp flags & (fin | syn) | syn == fin | syn
This is clearly a problem of the converter, which ignored the fact that binary AND precedes binary OR. The output of nftables does not seem like a mathematically correct transformation, either. But instead of trying to fix nftables in that aspect, I guess it would make more sense to have the converter generate the missing parentheses around the binary OR on the left side of the relational expression (see patch here and on top of that, I patched nftables’ parser to allow extra (though unnecessary) parentheses around the right side.
Wrong type of chains in nat
table
The replacement for iptables’ NAT table was generated with chains of type filter
, which is apparently wrong: The kernel will reject adding a rule with verdict masquerade
to them. Instead, the type has to be set to nat
to allow for that verdict. Luckily, this wasn’t hard to fix, either.
Optimizing the results
While reviewing the remaining converter output, a few optimizations struck:
- There is no need to keep empty chains (unless their policy diverts from the default of
accept
). And if a table becomes empty after removing all empty chains it contains, the whole table can be dropped. - The loop in
updateipt.sh
setting all built-in chain’s policy to DROP made it necessary to explicitly allow outgoing traffic in OUTPUT chains. By keeping the chain’s policy at the default, the explicit rule can go away and in turn the whole chain becomes empty so the above point applies here, as well. - There is no need to have a counter for every rule. In fact, most rules are totally uninteresting, so use counters only for rules, which should not see traffic (to spot problems or attacks) or are used for statistics/accounting (of which there are none in my setup).
After applying the things from above and concatenating the rules for IPv4 and IPv6 (the single nft
utility handles both), here’s the result:
add table ip nat add chain ip nat POSTROUTING { type nat hook postrouting priority 0; policy accept; } add rule ip nat POSTROUTING oifname != vnetbr0 ip saddr 192.168.42.0/24 masquerade add table ip filter add chain ip filter INPUT { type filter hook input priority 0; policy drop; } add chain ip filter FORWARD { type filter hook forward priority 0; policy drop; } add chain ip filter blacklist add chain ip filter invalid add chain ip filter states add chain ip filter wan_in add rule ip filter INPUT jump invalid add rule ip filter INPUT jump wan_in add rule ip filter INPUT iifname lo accept add rule ip filter INPUT iifname vnetbr0 accept add rule ip filter INPUT jump states add rule ip filter FORWARD iifname vnetbr0 accept add rule ip filter FORWARD oifname vnetbr0 jump wan_in add rule ip filter FORWARD oifname vnetbr0 jump states add rule ip filter invalid tcp flags & (fin|syn) == fin|syn counter drop add rule ip filter invalid tcp flags & (syn|rst) == syn|rst counter drop add rule ip filter invalid jump blacklist add rule ip filter states ct state invalid counter reject add rule ip filter states ct state related,established accept add rule ip filter wan_in ip protocol icmp accept add rule ip filter wan_in tcp dport 22 accept add rule ip filter wan_in ip protocol esp accept add rule ip filter wan_in ip protocol ah accept add rule ip filter wan_in tcp dport 27374-27474 accept add rule ip filter wan_in udp dport 27374-27474 accept add rule ip filter wan_in ip protocol ipv6 accept add table ip6 filter add chain ip6 filter INPUT { type filter hook input priority 0; policy drop; } add chain ip6 filter FORWARD { type filter hook forward priority 0; policy drop; } add chain ip6 filter blacklist add chain ip6 filter invalid add chain ip6 filter states add chain ip6 filter wan_in add rule ip6 filter INPUT jump invalid add rule ip6 filter INPUT jump wan_in add rule ip6 filter INPUT iifname lo accept add rule ip6 filter INPUT iifname vnetbr0 accept add rule ip6 filter INPUT jump states add rule ip6 filter FORWARD iifname vnetbr0 accept add rule ip6 filter FORWARD iifname vnetbr* oifname vnetbr* accept add rule ip6 filter FORWARD oifname vnetbr0 jump wan_in add rule ip6 filter FORWARD oifname vnetbr0 jump states add rule ip6 filter invalid tcp flags & (fin|syn) == fin|syn counter drop add rule ip6 filter invalid tcp flags & (syn|rst) == syn|rst counter drop add rule ip6 filter invalid jump blacklist add rule ip6 filter states ct state invalid counter reject add rule ip6 filter states ct state related,established accept add rule ip6 filter wan_in meta l4proto ipv6-icmp accept add rule ip6 filter wan_in ip6 saddr fe80::/10 ip6 daddr fe80::/10 \ ct state new udp sport 547 udp dport 546 accept add rule ip6 filter wan_in tcp dport 22 accept add rule ip6 filter wan_in meta l4proto esp accept add rule ip6 filter wan_in meta l4proto ah accept add rule ip6 filter wan_in tcp dport 27374-27474 accept add rule ip6 filter wan_in udp dport 27374-27474 accept
Making use of the new inet
table
Now for some real feature of nftables, namely handling of IPv4 and IPv6 traffic in a common table. For instance, the FORWARD chains above are exactly identical since there are no IP version specific rules in them. The only problem with consolidating them is that they reference wan_in
and states
chains and since the INPUT chain uses these as well, this ends up in an all or nothing approach for the whole filter tables. But in this case it still made sense since the tables were so similar. So one by one, first the FORWARD chain:
add table inet filter add chain inet filter FORWARD { type filter hook forward priority 0; policy drop; } add rule inet filter FORWARD iifname vnetbr0 accept add rule inet filter FORWARD oifname vnetbr0 jump wan_in add rule inet filter FORWARD oifname vnetbr0 jump states
Next the wan_in
and states
chains it depends upon:
add chain inet filter wan_in add rule inet filter wan_in ip protocol icmp accept add rule inet filter wan_in meta l4proto ipv6-icmp accept add rule inet filter wan_in ip6 saddr fe80:: ip6 daddr fe80:: \ ct state new udp sport 547 udp dport 546 accept add rule inet filter wan_in tcp dport 22 accept add rule inet filter wan_in ip protocol esp accept add rule inet filter wan_in meta l4proto esp accept add rule inet filter wan_in ip protocol ah accept add rule inet filter wan_in meta l4proto ah accept add rule inet filter wan_in tcp dport 27374-27474 accept add rule inet filter wan_in udp dport 27374-27474 accept add rule inet filter wan_in ip protocol ipv6 accept
add chain inet filter states add rule inet filter states ct state invalid counter reject add rule inet filter states ct state related,established accept
Finally the INPUT chain and its dependency, the invalid
chain (which in turn jumps to the empty blacklist
):
add chain inet filter INPUT { type filter hook input priority 0; policy drop; } add rule inet filter INPUT jump invalid add rule inet filter INPUT jump wan_in add rule inet filter INPUT iifname lo accept add rule inet filter INPUT iifname vnetbr0 accept add rule inet filter INPUT jump states
add chain inet filter invalid add chain inet filter blacklist add rule inet filter invalid tcp flags & (fin|syn) == fin|syn counter drop add rule inet filter invalid tcp flags & (syn|rst) == syn|rst counter drop add rule inet filter invalid jump blacklist
Of course, the statements above need to be reordered a bit so chains are created prior to rules referencing them.
Using the blacklist chain – or an alternative
The empty chain blacklist
is meant to be managed by an application layer IPS such as fail2ban. Adjusting that, is beyond the scope of this document, but here are example rules for adding IPv4/IPv6 addresses to it:
add rule inet filter blacklist ip saddr 192.168.42.3 drop add rule inet filter blacklist ip6 saddr feed:babe::3 drop
In nftables world though, this is considered inelegant and one would rather use named sets instead. To do so, two sets need to be created since a single one can’t hold addresses of different families:
add set inet filter blacklist4 { type ipv4_addr; } add set inet filter blacklist6 { type ipv6_addr; }
Next, drop rules are created in invalid
chain (since the IPS may work with the named sets as a full replacement to a dedicated chain, which includes clearing them completely):
add rule inet filter invalid ip saddr @blacklist4 drop add rule inet filter invalid ip6 saddr @blacklist6 drop
Adding IP addresses to the sets then works like so:
add element inet filter blacklist4 { 192.168.42.3 } add element inet filter blacklist6 { feed:babe::3 }
About meta l4proto
and unnamed sets
While testing correct functionality of the generated rules in inet
family tables, it showed that meta l4proto
matches are actually IP address family agnostic, so the IPv4 specific ones (i.e. ip protocol
) may be dropped altogether. Since there are multiple meta l4proto
matches in wan_in
chain, it makes sense to combine them using an anonymous set like so:
add rule inet filter wan_in meta l4proto { icmp, ipv6-icmp, esp, ah } accept
Rule set dump file formats
In order to load a rule set, nft
program supports reading from a file with some flexibility in its syntax. On one hand, just adding the quoted statements above works just fine. Though nft is also able to read a format just as nft list ruleset
displays, which might be a little clearer to read since it puts tables, chains, and rules in relation. Here is what the full rule set with all mentioned modifications looks like in “nested” representation:
table ip nat { chain POSTROUTING { type nat hook postrouting priority 0 policy accept oifname != vnetbr0 ip saddr 192.168.42.0/24 masquerade } } table inet filter { set blacklist4 { type ipv4_addr } set blacklist6 { type ipv6_addr } chain INPUT { type filter hook input priority 0 policy drop jump invalid jump wan_in iifname lo accept iifname vnetbr0 accept jump states } chain FORWARD { type filter hook forward priority 0 policy drop iifname vnetbr0 accept oifname vnetbr0 jump wan_in oifname vnetbr0 jump states } chain wan_in { ip6 saddr fe80:: ip6 daddr fe80:: \ ct state new udp sport 547 udp dport 546 accept meta l4proto { icmp, ipv6-icmp, esp, ah } accept tcp dport 22 accept tcp dport 27374-27474 accept udp dport 27374-27474 accept ip protocol ipv6 accept } chain states { ct state invalid counter reject ct state related,established accept } chain invalid { tcp flags & (fin|syn) == fin|syn counter drop tcp flags & (syn|rst) == syn|rst counter drop ip saddr @blacklist4 drop ip6 saddr @blacklist6 drop } }
Depending on personal preference, this may be decorated with comments (starting with the commonly used hash (#
) character) as well. Like with iptables, it is also possible to add comments to rules, which have the benefit of being part of nft list ruleset
output and therefore are present in whatever form rules are managed. The only requirement is that they appear at the end of the rule specification, and when entered on command line quotes have to be escaped:
add rule inet filter INPUT counter comment "useless rule having a comment"
Finishing up
So, satisfied with the outcome of my rule set conversion and optimization, the very last piece to complete the puzzle was to create a shell script equivalent to updateipt.sh
I started with. Given that I do not need any shell tricks anymore as well as there is support for comments, I can use nft
directly as script interpreter:
#!/sbin/nft -f
When it comes to clearing the tables (or rather ruleset
in this case) at the beginning, I found out that you could also mix the syntaxes described above. So one can simply add the following line as the first command before the actual table definitions:
flush ruleset
This is much easier than the common -F
, -X
, -Z
dance for all tables of both iptables and ip6tables!