The firewalld utility, when using the nftables backend, will not flush tables that don't belong to it:
Only flush firewalld’s rules
Since nftables allows namespaces (via tables) firewalld no longer does
a complete flush of firewall rules. It will only flush rules in the
firewalld table. This avoids scenarios where custom user rules or
rules installed by other tools are unexpectedly wiped out when
firewalld was restarted or reloaded.
The same has just to be done when managing other tables.
Actually in the previous answer, it's already done: the nftables rules idempotently deletes only their own table: handletftp
.
Sadly, that's not the case for nftables.service
for the stop action:
ExecStop=/sbin/nft flush ruleset
One must just ensure that the stop part of the systemd service doesn't directly flush all rules while still doing the job. This job will be delegated into dedicated nftables rules for the stop action.
So here's a practical method: duplicate (eg: systemctl cat nftables.services
) and alter nftables.service
into an instantiated version local-idempotent-nft@.service
to be put in /etc/systemd/system/local-idempotent-nft@.service
:
[Unit]
Description=Idempotent nftables rules for %I
Wants=network-pre.target
Before=network-pre.target
[Service]
Type=oneshot
ProtectSystem=full
ProtectHome=true
ExecStart=/sbin/nft -f /etc/nftables/idempotent/%I.nft
# As the rules are idempotent, ExecReload is same as ExecStart
ExecReload=/sbin/nft -f /etc/nftables/idempotent/%I.nft
# The stop rules should only have the first boilerplate parts
ExecStop=/sbin/nft -f /etc/nftables/idempotent/stop-%I.nft
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Create the dedicated configuration directory used above:
mkdir -p /etc/nftables/idempotent
Place rules that for each table defined, always start like this, so loading the rules is independent of other tables and idempotent (example with tables ip foo
and bridge bar
):
table ip foo
delete table ip foo
table bridge bar
delete table bridge bar
table ip foo {
...
}
table bridge bar {
....
}
or just use one table per file. The flush ruleset
statement is prohibited since it's global.
The reason a table is created, deleted and recreated is to get the result idempotent: while deleting a non-existing table is an error and would atomically make the whole loading fail, declaring an existing table without defining it (by adding an empty table) never fails and does nothing except creating it empty if it didn't exist before. In both cases (didn't exist at boot, exists at reload) delete can now work, leaving the place to really define the table right after. All this is happening in the same transaction and is still atomic: no packet will ever be evaluated with a missing ip foo table if it existed before during this.
Prepare a stop version of above that will only delete (here the empty declarations aren't strictly needed and could be removed if there was only one table, but should be kept if there's more than one table: failure is for the whole transaction):
table ip foo
delete table ip foo
table bridge bar
delete table bridge bar
and put everything in their location:
/etc/nftables/idempotent/foobar.nft
/etc/nftables/idempotent/stop-foobar.nft
This can now be activated with:
systemctl enable --now local-idempotent-nft@foobar
Example from previous OP's question:
in /etc/nftables/idempotent/handletftp.nft
:
table ip handletftp
delete table ip handletftp
table ip handletftp {
ct helper helper-tftp {
type "tftp" protocol udp
}
chain sethelper {
type filter hook forward priority 0; policy accept;
ip saddr 192.168.1.0/24 ip daddr 10.0.10.10 udp dport 69 ct helper set "helper-tftp"
}
}
In /etc/nftables/idempotent/stop-handletftp.nft
table ip handletftp
delete table ip handletftp
Enabling and starting it:
systemctl enable --now local-idempotent-nft@handletftp
Stopping it:
systemctl stop local-idempotent-nft@handletftp
which will leave firewalld's rules in place. Likewise, stopping or restarting firewalld will leave these rules in place.
There are probably improvements to do:
- nftables has an include statement which could be used somehow to avoid duplication of boilerplate.
- the specific example about TFTP relies on the loading of
nf_nat_tftp
which won't be done automatically (contrary to nf_conntrack_tftp
which is automatically loaded from reference in the rules or contrary to firewalld which would load nf_nat_tftp
explicitly). So related but non-strictly nftables configurations should be kept in mind (this one setting could simply be put in /etc/modules-load.d/
).