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

(With firewalld 0.4.4.6 or later, you should not use this, but instead create a rich rule for IPv4 masquerade. These versions have a misfeature that causes an IPv6 masquerading rule to also be added when using --add-masquerade, which will break IPv6 connectivity.)

firewall-cmd [--permanent] --zone=public --add-rich-rule='rule family=ipv4 masquerade'