Firewall local port forwarding

Hey there,

It’s about month as I’m trying to resolve one strange issue with firewall on Fedora 38.
For simplifying it and to avoid additional questions about my personal configuration I will describe it on booting Live Fedora 38 Workstation image from USB.
Here is steps to easily reproduce that what I’m talking about.

  1. Boot Fedora 38 Live image.
  2. Install additionally nmap (it will be needs later to experiment).
  3. Configure firewall to forwarding all incoming TCP traffic from port 80 to local port 8080
sudo firewall-cmd --add-rich-rule='rule family=ipv4 forward-port to-port=8080 protocol=tcp port=80'
sudo firewall-cmd --add-rich-rule='rule family=ipv6 forward-port to-port=8080 protocol=tcp port=80'
  1. Start simple mock to response on port 8080
    sudo nc -lp 8080 <<< hello
  2. Try to access to port 80 on this computer (it doesn’t matter to use locahost or real IP address)
    curl localhost

What I expected here is to get response - hello, but instead of this curl: (7) Failed to connect to localhost port 80 after 0 ms: Couldn’t connect to server
But the most fanny thing here is that it work fine when you execute curl with IP of this computer, from the device in the same network with your computer.

I would really appreciate for any explanations why it shouldn’t work, because I think that there are no reasons for such behavior.

I don’t know if such rich rule should work, but what about this one?

sudo firewall-cmd --add-forward-port=port=80:proto=tcp:toport=8080:toaddr=127.0.0.1

As I already seen it the rich rules is more preferable way to organize port forwarding. And of course I already tried it. This is one what I started with.

Or. Looking at some documentation[1] it seems that the syntax for port forwarding is

forward-port port="port value" protocol="tcp|udp|sctp|dccp" to-port="port value" to-addr="address"

where to-addr looks mandatory and not [optional].

This rule works for me:

sudo firewall-cmd --add-rich-rule='rule family=ipv4 forward-port to-port=8080 protocol=tcp port=80 to-addr=127.0.0.1'

  1. i.e. Documentation - Manual Pages - firewalld.richlanguage | firewalld
    or 5.15. Configuring Complex Firewall Rules with the "Rich Language" Syntax Red Hat Enterprise Linux 7 | Red Hat Customer Portal ↩︎

This case didn’t work as well. I already tried most of the basic approaches.

1 Like

You are correct. It doesn’t work.

Edit: it doesn’t work from localhost. From another host in the network, it works. :face_with_raised_eyebrow:

That seems logical. One is forwarding an incoming connection to a different port on the localhost address.

@mykola
What happens if you try to connect from the local host to port 80 on the LAN ip of the local host.? It should be seen and treated the same as a connection from another host on the lan.

This is pretty old
https://bugzilla.redhat.com/show_bug.cgi?id=1445918#c6
it is not strictly related to Fedora Linux, and it is using iptables and not nft who is used on Fedora Linux nowadays.
But maybe: firewalld implements forward-ports using the iptables nat PREROUTING chain. This chain is not used for packets sent over the loopback interface as packets over the loopback should not be routed.

It’s the same.

Failed to connect to 192.168.0.10 port 80

Ok. I think that the point is something related to loopback device, treated differently, ok, I don’t know :sweat_smile: This is interesting: nftables destination nat block local access to port - Unix & Linux Stack Exchange

So, in addition to rich rules you used on your first post, add these rules

sudo nft add chain inet firewalld loopback-nat '{ type nat hook output priority -100; policy accept; }'
sudo nft add rule inet firewalld loopback-nat oif lo tcp dport 80 counter redirect to :8080

Now it works from localhost, but… well we should find a way to use firewalld-cmd instead of use nft directly…

1 Like

It works now. Needs to remember this “magic spell”. Thanks a lot.

One more answer with explanation how it is all organized: iptables - firewall-cmd not allowing loopback redirect - Server Fault

The above solution is correct, but it cannot survive a firewall restart or a system reboot, so here’s a persistent configuration:

sudo firewall-cmd --permanent --direct --add-rule \
    ipv4 nat OUTPUT 0 -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo firewall-cmd --permanent --direct --add-rule \
    ipv6 nat OUTPUT 0 -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo firewall-cmd --reload 
1 Like

Works as well. Thanks.

1 Like