Linux NAT Firewall Setup
A NAT Firewall is needed in many situations to allow a private network to communicate with the larger world, the most common example of this would be a home WiFi router that provides a private network space and then links to a WAN connection provided by an ISP.
I was recently setting up such a system at work to allow systems on a private network to be able to communicate with the internet for software updates, etc. There is a lot of info out there to set this up, many older guides focus on iptables rules, but I wanted something that used the newer firewall-cmd software. After some googling and piecing together some things, I came up with the following script.
Firewall-cmd has several predefined ‘zones’ that control how the firewall behaves. For the NAT, I needed to set up the Internal Zone for my private network, and the External Zone for the WAN connection. The masquerade function here basically takes the outgoing traffic from the private network and makes it appear to be coming from the external network, and then pass returned traffic back to the correct system on the private side. Lastly, some iptables style rules are added as a ‘direct configuration’ to firewall-cmd to forward the traffic between these zones.
#!/bin/bash # Define interfaces for each zone, Internal Zone = LAN, External Zone = WAN EXTERNAL=ens2f0np0 INTERNAL=ens2f1np1 # Run the following commands on LINUX box that will act as a firewall or NAT gateway firewall-cmd --set-default-zone=internal firewall-cmd --get-active-zone firewall-cmd --change-interface=$EXTERNAL --zone=external --permanent firewall-cmd --change-interface=$INTERNAL --zone=internal --permanent # These next two lines may not be necessary but won't hurt... firewall-cmd --remove-interface=$EXTERNAL --zone=public --permanent firewall-cmd --remove-interface=$INTERNAL --zone=public --permanent # set masquerading to internal and internal zones firewall-cmd --zone=external --add-masquerade --permanent firewall-cmd --zone=internal --add-masquerade --permanent # set direct rules to forward between zones firewall-cmd --direct --permanent --add-rule ipv4 nat POSTROUTING 0 -o $EXTERNAL -j MASQUERADE firewall-cmd --direct --permanent --add-rule ipv4 filter FORWARD 0 -i $INTERNAL -o $EXTERNAL -j ACCEPT firewall-cmd --direct --permanent --add-rule ipv4 filter FORWARD 0 -i $EXTERNAL -o $INTERNAL -m state --state RELATED,ESTABLISHED -j ACCEPT firewall-cmd --reload # This next bit is needed for RHEL 9, Rocky 9, Fedora 35+ # create new policy to allow traffic forwarding between zones firewall-cmd --permanent --new-policy policy_int_to_ext firewall-cmd --permanent --policy policy_int_to_ext --add-ingress-zone internal firewall-cmd --permanent --policy policy_int_to_ext --add-egress-zone external firewall-cmd --permanent --policy policy_int_to_ext --set-priority 100 firewall-cmd --permanent --policy policy_int_to_ext --set-target ACCEPT firewall-cmd --reload
That last block of code gets around a change to how firewall-cmd functions in newer systems. RHEL 8/Rocky Linux 8/Fedora 34 (and prior) didn’t require any additional work, but RHEL9 (etc) implement a new default policy that DENIES traffic forwarding between zones, so for this NAT to work we need to set up a new policy that ALLOWS traffic forwarding specifically between these zones. I was quite stumped at first when I set up my RHEL 9 system for NAT and it didn’t work as expected, and after some googling I found this thread which shed light on the issue, https://forums.fedoraforum.org/showthread.php?327324-IP-Masquerade-NAT-not-working-in-Fedora-35. There may be a better solution for this down the road, but for now this additional policy will let things function as needed.
2 Responses on “Linux NAT Firewall Setup”
Why not just ” firewall-cmd –zone=internal –set-target=ACCEPT” ?
A NAT needs to accept traffic from one network and forward to another, and then accept replies and forward back where they need to reach. So a NAT setup is much more complicated than simply accepting traffic for a single zone.