There are many articles explaining how to do NAT and masquerading with Linux using iptables or other quite low-level tools. This is all not necessary these days, NetworkManager and firewalld can do the dirty work for us! If you have RHEL7, CentOS7 or any modern Fedora or pretty much anything which is more recent, it’s super easy.

In my scenario I want to NAT traffic from an internal network 192.168.199.0/24 to external (virtual NAT actually) network 192.168.122.0/24. My host has two NICs eth0 and eth1 connected to these networks in this order. Kernel has forwarding turned off (the default setting for Red Hats):

# sysctl -a | grep ip_forward
net.ipv4.ip_forward = 0

It’s really easy! Both my interfaces are in the public zone:

# firewall-cmd --get-active-zone
public
  interfaces: eth1 eth0

All I need to is to move them into pre-defined zones internal and external.

# nmcli c mod eth0 connection.zone internal
# nmcli c mod eth1 connection.zone external

You can stop reading now, Linux has been configured as NAT with masquarade now with the stwo simple commands. Anyway, let’s do some analysis. Look, firewalld noticed I want to do NAT and enabled IP forwarding in the kernel for me.

# sysctl -a | grep ip_forward
net.ipv4.ip_forward = 1

Masquerade is already enabled on the pre-defined zone called external, again no need to do anything here. Look:

# firewall-cmd --zone=external --query-masquerade
yes

The external pre-defined zone is quite strict similarly to public zone, only ssh service is allowed. In my case I wanted to allow HTTP(s) traffic as well:

# firewall-cmd --zone=external --add-service=http
success
# firewall-cmd --zone=external --add-service=http --permanent
success

It’s worth noting that internal pre-defined zone is also quite strict. Remember, 70 per-cent of all security attacks come from the inside! Since I am performing just some testing in isolated environment, I can afford to open it up completely as I plan to run some extra services like DHCP, DNS and TFTP on that server.

# firewall-cmd --list-all --zone=internal
internal (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: ssh mdns samba-client dhcpv6-client
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

So I gave it the accept target policy. Do not try at home, this is dangerous!

# firewall-cmd --permanent --zone=internal --set-target=ACCEPT
success
# systemctl restart firewalld

Have fun!