Use --add-forward-port
to set up a port forwarding.
From the firewall-cmd
man page:
--add-forward-port=port=portid[-portid]:proto=protocol[:toport=portid[-portid]][:toaddr=address[/mask]]
[--timeout=timeval]
Add the IPv4 forward port for zone. If zone is omitted, default
zone will be used. This option can be specified multiple times. If
a timeout is supplied, the rule will be active for the specified
amount of time and will be removed automatically afterwards.
timeval is either a number (of seconds) or number followed by one
of characters s (seconds), m (minutes), h (hours), for example 20m
or 1h.
The port can either be a single port number portid or a port range
portid-portid. The protocol can either be tcp, udp, sctp or dccp.
The destination address is a simple IP address.
So you would do something like:
firewall-cmd --zone=whatever --add-forward-port=port=80:proto=tcp:toport=8080
And if it does what you want, make it permanent.
Reason it's not working
It appears firewalld might be geared to handle firewalling local services, rather than routed services.
So the tftp settings will add in the end these nft rules when firewalld has been configured (on CentOS 8) with the zones files in OP (just showing the rules, not the whole ruleset here):
table inet firewalld {
chain filter_IN_external_allow {
udp dport 69 ct helper set "helper-tftp-udp"
}
chain filter_IN_internal_allow {
udp dport 69 ct helper set "helper-tftp-udp"
udp dport 69 ct state { new, untracked } accept
}
}
Those rules will never match and are thus useless: they are in the input path, not in the forward path.
With the running firewall, these (blindly copied) rules added at the right place: in the forward path, will make TFTP work:
nft insert rule inet firewalld filter_FWDI_internal_allow udp dport 69 ct helper set "helper-tftp-udp"
nft add rule inet firewalld filter_FWDI_internal_allow index 0 udp dport 69 ct state '{ new, untracked }' accept
So in the end a so-called direct option would still be an option so everything is stored in firewalld's configuration. Alas the documentation is a bit misleading:
Warning: Direct rules behavior is different depending on the value of
FirewallBackend. See CAVEATS in firewalld.direct(5).
Not reading carefully one would think with FirewallBackend=nftables
that it would behave differently by accepting nftables rules, but that's not the case:
# firewall-cmd --version
0.8.0
# firewall-cmd --direct --add-rule inet firewalld filter_FWDI_internal_allow 0 'udp dport 69 ct helper set "helper-tftp-udp" ct state new accept'
Error: INVALID_IPV: invalid argument: inet (choose from 'ipv4', 'ipv6', 'eb')
No need to test much more, this "feature" is documented there:
https://bugzilla.redhat.com/show_bug.cgi?id=1692964
and there:
https://github.com/firewalld/firewalld/issues/555
Direct rules still use iptables with the nftables backend. The CAVEAT is about the order of rules evaluation.
Handle this in an other table
I don't see the point anymore of doing this with firewall-cmd, which will add iptables rules along nftables rules. It just becomes cleaner to add an independent table. It'll just be in the ip family since filters for the specific IPv4 networks will also be added (inet would also be fine).
handletftp.nft
(to be loaded with nft -f 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"
}
}
As the table is different and the ruleset is never flushed, but instead the specific table is (atomically) deleted and recreated, this doesn't affect firewalld nor firewalld will affect it.
The priority doesn't matter much: that this chain is traversed before or after firewalld's chains won't change the fate of the packet (still in the hands of firewalld). Whatever the order, if the packet is accepted by firewalld it will also have activated the helper for this flow.
If you choose to use the nftables service to load this table, you'll have to edit it (eg: systemctl edit --full nftables
), because beside loading some probably inadequate default rules, it will flush all rules on stop or reload, disrupting firewalld.
Now, a TFTP transfer will work and activate the specific helper, as can be checked by running two conntrack
commands during the transfer:
# conntrack -E & conntrack -E expect
[1] 3635
[NEW] 300 proto=17 src=10.0.10.10 dst=10.0.10.11 sport=0 dport=56597 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=192.168.1.2 master-dst=10.0.10.10 sport=56597 dport=69 class=0 helper=tftp
[NEW] udp 17 29 src=192.168.1.2 dst=10.0.10.10 sport=56597 dport=69 [UNREPLIED] src=10.0.10.10 dst=10.0.10.11 sport=69 dport=56597 helper=tftp
[DESTROY] 299 proto=17 src=10.0.10.10 dst=10.0.10.11 sport=0 dport=56597 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=192.168.1.2 master-dst=10.0.10.10 sport=56597 dport=69 class=0 helper=tftp
[NEW] udp 17 30 src=10.0.10.10 dst=10.0.10.11 sport=42032 dport=56597 [UNREPLIED] src=192.168.1.2 dst=10.0.10.10 sport=56597 dport=42032
[UPDATE] udp 17 30 src=10.0.10.10 dst=10.0.10.11 sport=42032 dport=56597 src=192.168.1.2 dst=10.0.10.10 sport=56597 dport=42032
The 3rd NEW entry in the example above is actually tagged as RELATED (that's the whole role of the tftp helper: expect a certain type of packet to get it seen as related) which will be accepted by the firewall.
Best Answer
Source: GitHub issue reply from a firewalld’s collaborator
There are a couple things going on here.
iptables
will add rules tonftables
in the known tables:filter
,nat
,raw
, etc.iptables
rules (e.g.iptables -F -t filter
) which flushes all chains in thefilter
table. This corresponds to the knownnftables
table names mentioned above.Firewalld's
nftables
backend only touches thefirewalld
table, but tablesfilter
,nat
,raw
, etc. are touched indirectly viaiptables-nft
to support iptables direct rules. This behavior cannot be changed due to compatibility. Firewalld will not touch othernftables
tables though, e.g. a table namedfoobar
would not get flushed.Unfortunately we can't do anything here. We either a) maintain compatibility and let the
iptables
named tables get flushed or b) dropiptables
direct rule support. Option A is the most user friendly.