CentOS 7 firewalld NAT router

gearsThis post outlines how to build a simple router using firewalld and dnsmasq on CentOS 7.3. The problem this router solves for us at SuniTAFE is that it isolates virtual networks being built or used by students on VMWare ESX hosts from the main college network, while still allowing student access to the college network for DNS forwarding and internet access purposes.

Install OS and Services

First up, install a “Minimal” version of CentOS 7 and then update the system software:

# yum update

The dnsmasq service (provides DHCP and DNS services to internal network) should already be installed, but ntpd (provides time service) will need installing:

# yum install ntp

Also install Open VM Tools if the router is on VMWare:

# yum install open-vm-tools

IP Forwarding

First up, enable IP forwarding in the kernel because Linux cannot function as a router without it. Add the following line to /etc/sysctl.conf:

net.ipv4.ip_forward = 1

This is a permanent change that will stick after each reboot. To force sysctl to reread its config file after editing it, run:

# sysctl -p

Firewall zones

The gateway/router has two virtual NICs, one in the students’ virtual network (the internal network) and one in the college network (the external network).

First, we need to permanently assign each NIC to its own firewall zone. Firewalld has several zones already pre-defined, which can be listed using the following command:

# firewall-cmd --get-zones

block dmz drop external home internal public trusted work

The pre-defined default zone is the public zone:

# firewall-cmd --get-default-zone


The simplest approach is to use the default public zone for the external network, and to assign the internal network to the pre-defined internal zone.

By default, firewalld automatically assigns all interfaces to the default zone:

firewall-cmd --list-all

public (default, active)
interfaces: eth1 eth2
services: dhcpv6-client ssh
masquerade: no
rich rules:

Here, firewalld knows about two interfaces, eth1 and eth2, and both are assigned to the public zone. We want to keep eth1 in the public zone and assign eth2 to the internal zone.

Initially, we tried using some firewall-cmd options for adding interfaces to zones (and removing them) as follows:

firewall-cmd --permanent --zone=public --remove-interface=eth2
firewall-cmd --permanent --zone=internal --add-interface=eth2

The problem with this approach is that every time firewalld or the server is restarted, all interfaces are reassigned back to the public zone despite the use of the –permanent option in the above commands.

It turns out that the only way to permanently assign an interface to a zone is to edit the interface’s configuration file (/etc/sysconfig/network-scripts/ifcfg-eth2 in this case) and add a ZONE option as follows:


If this option is missing or empty, the default firewalld zone will always be reassigned to the interface when it is restarted.

Before moving on, we should mention that if the router needs to serve multiple internal networks it is easy enough to define new custom zones if necessary:

firewall-cmd --permanent --new-zone=student1
firewall-cmd --reload

Note that only one new zone can be added at a time, and that the –permanent option must be used. Zones can also be removed if necessary:

firewall-cmd --permanent --delete-zone=student1

NAT Rules

The following rules allow machines on the isolated internal network (on eth2) to send NATed packets to the college network (on eth1), and also allow responses back. Machines on the college network cannot initiate communications with student machines on the internal network:

firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -o eth1 -j MASQUERADE

firewall-cmd --direct --add-rule ipv4 filter FORWARD 0 -i eth2 -o eth1 -j ACCEPT
firewall-cmd --direct --add-rule ipv4 filter FORWARD 0 -i eth1 -o eth2 -m state --state RELATED,ESTABLISHED -j ACCEPT

Pretty simple really. Note that as-is, this router forwards all traffic regardless of service, port or protocol but does not allow connections to the router itself apart from those pre-defined for the public interface (dhcpv6-client and ssh).

Currently configured –direct rules can be inspected as follows:

# firewall-cmd --permanent --direct --get-all-rules


Firewall Rules

Although we haven’t configured dnsmasq and ntpd yet, we may as well put the firewall rules they require in place now. As a DHCP, DNS and NTP server for the internal network, the router requires ports 53, 67 and 123 to be opened on the internal zone. Note that these are all UDP ports:

# firewall-cmd --permanent --zone=internal --add-port=53/udp
# firewall-cmd --permanent --zone=internal --add-port=67/udp
# firewall-cmd --permanent --zone=internal --add-port=123/udp
# firewall-cmd --reload

While we’re at it we may as well remove the unnecessary dhcpv6-client service from the public zone:

# firewall-cmd --permanent --zone=public --remove-service=dhcpv6-client
# firewall-cmd --reload

Network Services

DHCP and DNS configuration is very easy. The dnsmasq configuration file is located in /etc/dnsmasq.conf. By default, all entries in that file are commented out and use sensible defaults. Simply add the following directives to that file to define DHCP and DNS servers listening on on eth2. This file also defines a DHCP range ( to with 12 hour leases, and points DNS, Gateway and NTP server options to the router itself:


Dnsmasq functions as a forwarding DNS server. Valid forwarding servers need to be defined in ifcfg-* interface files and /etc/resolv.conf. Dnsmasq will also read any entries in /etc/hosts/.

Basic NTP functionality is even easier to configure. Uncomment and modify this line in /etc/ntp.conf, using an appropriate network definition for your case:

restrict mask nomodify notrap

Enable and start the dnsmasq and ntpd services:

# systemctl enable dnsmasq
# systenctl start dnsmasq
# systemctl enable ntpd
# systenctl start ntpd

NTP functionality can be verified as follows:

# ntpq -p

All currently listening services can be verified as follows:

# ss -tulpn

That’s it!