RHEL/CentOS Now to add nftable rules to firewalld on system boot?

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 [email protected] to be put in /etc/systemd/system/[email protected]:

[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/).