Port forwarding with firewall-cmd

Matteo Giani asked:

I have several VMs running on top of a server (Virtual Machine Manager, VMM). I’d like to forward port 80 on the server to port 80 of one of my VMs. The host runs CentOS7, so firewalld is in charge. Apparently, firewalld is also used by VMM to handle the virtual connections, so I can’t just throw it out of the window.

I basically reproduced all commands in this thread without success, which boils down to:

firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toport=80:toaddr=192.168.122.224

or

firewall-cmd --permanent --zone=public --add-rich-rule 'rule family=ipv4 forward-port port=80 protocol=tcp to-port=80 to-addr=192.168.122.224'

The current status of firewalld is the following:

[[email protected] ~]# firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp8s0
  sources: 
  services: ssh dhcpv6-client samba smtp http
  ports: 25/tcp
  protocols: 
  masquerade: yes
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
  rule family="ipv4" forward-port port="80" protocol="tcp" to-port="80" to-addr="192.168.xxx.xxx"

This is the output of

iptables -L -n -v

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target     prot opt in     out     source               destination         
13917 8976K ACCEPT     all  --  *      virbr0  0.0.0.0/0               192.168.122.0/24     ctstate RELATED,ESTABLISHED
13539 2093K ACCEPT     all  --  virbr0 *       192.168.122.0/24     0.0.0.0/0           
0     0 ACCEPT     all  --  virbr0 virbr0  0.0.0.0/0            0.0.0.0/0           
4   240 REJECT     all  --  *      virbr0  0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable <-----
1   133 REJECT     all  --  virbr0 *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable
0     0 ACCEPT     all  --  virbr1 virbr1  0.0.0.0/0            0.0.0.0/0           
0     0 REJECT     all  --  *      virbr1  0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable
0     0 REJECT     all  --  virbr1 *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable
0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
0     0 FORWARD_direct  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
0     0 FORWARD_IN_ZONES_SOURCE  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
0     0 FORWARD_IN_ZONES  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
0     0 FORWARD_OUT_ZONES_SOURCE  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
0     0 FORWARD_OUT_ZONES  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID
0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-host-prohibited

the problem is the 2nd rule, that seem to reject my packages (I can see the number increasing when trying to connect). Indeed, if I remove it with:

iptables -D FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable

my port forwarding works. What am I missing?

My answer:


Mixing firewalld with libvirt can get you into this sort of situation.

By default with a “NAT” network, libvirt’s networking will set up masquerade rules so that the VMs can access the legacy IPv4 Internet. But, libvirt blocks all incoming connections to “NAT” networks.

The solution is to change the network to a normal routed network, and then let firewalld handle any masquerading that you might require.

For instance, you would edit the network XML and change

  <forward mode='nat'/>

to

  <forward mode='route'/>

I recommend you change all libvirt NAT networks to routed, in this scenario, not just the one you want to forward ports to, if you want to allow the networks to communicate with each other.

Then you set masquerading on the firewalld zone corresponding to the interface connected to the rest of the Internet. For example:

firewall-cmd [--permanent] --zone=public --add-masquerade

View the full question and any other answers on Server Fault.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.