I have a Debian box running firewalld set up a as gateway NAT/router. This device has two NICS; wan --- public interface, assigned to the external firewalld zone, dynamically assigned IP address using DHCP ---, and lan1 --- private interface, assigned to the trusted firewalld zone, statically assigned IP address of 192.168.0.1/16.
The system is also configured to do NAT and port forwarding using firewalld:
(router) $> sudo firewall-cmd --info-zone=external
external (active)
target: DROP
icmp-block-inversion: yes
interfaces: wan
sources:
services: http https ssh
ports: 8443/tcp
protocols:
forward: yes
masquerade: yes
forward-ports:
port=8443:proto=tcp:toport=8443:toaddr=192.168.0.2
port=443:proto=tcp:toport=443:toaddr=192.168.0.2
port=80:proto=tcp:toport=80:toaddr=192.168.0.2
source-ports:
icmp-blocks: echo-reply echo-request fragmentation-needed neighbour-advertisement neighbour-solicitation packet-too-big port-unreachable router-advertisement router-solicitation time-exceeded
rich rules:
(router) $> sudo firewall-cmd --info-zone=trusted
trusted (active)
target: ACCEPT
icmp-block-inversion: no
interfaces: lan1 lo
sources:
services: ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
(router) $> sudo sysctl -n net.ipv4.ip_forward
1
So far, so good. Devices within the internal 192.168.0.0/16 network can access the internet without any issues, and incoming connections on the wan interface to ports 80, 443, and 8443 are correctly forwarded to 192.168.0.2 where a reverse proxy instance is listening.
The problem arises when I want to use the public-facing IP address of the router from an internal device to access the reverse proxy on 192.168.0.2. To illustrate this, I have two public DNS records associated with the public address of wan; hq.mydomain.tld, which is proxied through Cloudflare, and gateway.mydomain.tld, which instead points directly to the public IP address of wan.
If I hit hq.mydomain.tld from an internal device, the request in properly routed to 192.168.0.2, as expected, as the request is first going to Cloudflare and then proxied back to my gateway:
$> curl -sS -D - https://hq.mydomain.tld -o /dev/null -L
HTTP/2 200
date: Sat, 16 Dec 2023 16:43:09 GMT
content-type: text/html; charset=utf-8
cache-control: public, max-age=600
...
However, if I instead hit gateway.mydomain.tld from an internal device, it seems as if no rules are applied and the request instead directly hits the router. Nothing is listening on the forwarded ports on the router, and so the request immediately fails.
$> curl -sS -D - https://gateway.mydomain.tld -o /dev/null -L
curl: (7) Failed to connect to gateway.mydomain.tld port 443 after 713 ms: Couldn't connect to server
The same thing happens if instead of the public hostname I use the public IP of the router.
$> curl -sS -D - https://a.b.c.d -o /dev/null -L
curl: (7) Failed to connect to a.b.c.d port 443 after 18 ms: Couldn't connect to server
This also happens if I curl from the router itself, either using it's own public IP address or localhost:
(router) $> curl -sS -D - https://a.b.c.d. -o /dev/null -L
curl: (7) Failed to connect to a.b.c.d port 443 after 0 ms: Couldn't connect to server
(router) $> curl -sS -D - https://localhost -o /dev/null -L
curl: (7) Failed to connect to localhost port 443 after 0 ms: Couldn't connect to server
My question is then: how do I configure firewalld (or its backend, nftables) to apply the public port forwarding rules to traffic originating from the internal network or from the router itself?
Note that I can't use rules which directly use the public IP of wan, as this is a DHCP-assigned IP address and could change at any moment, rendering the rules useless.
I have tried a bunch of different things without success:
Setting up identical port forwarding rules on the
trustedzone.Using direct firewalld port forwarding rules on the loopback interface.
Finally, I tried creating a firewalld policy using
trusted(the internal zone) as a the ingress zone andHOSTas the egress zone:(router) $> sudo firewall-cmd --info-policy=internal_fwd internal_fwd (active) priority: -1 target: CONTINUE ingress-zones: trusted egress-zones: HOST services: ports: protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules:I really thought this would work, as such a policy applies to any traffic coming from the internal network that terminates in the router. However, it seems you can't use a policy with an
egress-zones: HOSTto forward ports to another device:(router) $> sudo firewall-cmd --permanent --policy=internal_fwd --add-forward-port=port=8443:proto=tcp:toport=8443:toaddr=192.168.0.2 Error: INVALID_FORWARD: Policy 'internal_fwd': A 'forward-port' with 'to-addr' is invalid for egress zone 'HOST'