So, why does libvirt set its rules up like this?
Since the default policy is ACCEPT, and all rules are ACCEPT, that means that all traffic coming into the host is accepted. So why bother allowing DNS and BOOTP (DHCP) traffic? Answer: because libvirt wants to ensure that, even if the INPUT chain has a different policy, all guests (traffic originating on the virbr0 interface) will receive DNS and DHCP service from the host.
Chain INPUT (policy ACCEPT 380 packets, 37990 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT udp -- virbr0 any anywhere anywhere udp dpt:domain
0 0 ACCEPT tcp -- virbr0 any anywhere anywhere tcp dpt:domain
0 0 ACCEPT udp -- virbr0 any anywhere anywhere udp dpt:bootps
0 0 ACCEPT tcp -- virbr0 any anywhere anywhere tcp dpt:bootps
Rule 2 allows any outgoing guest traffic to be forwarded. Rule 1 then works with rule 2 so incoming packets that are a part of an established (outgoing) connection are passed as well.
Rule 3 ensures that all guests can talk to each other.
All other traffic to and from guests is dropped with an ICMP rejection so you find out immediately that something went wrong, you're not just blackholed.
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- any virbr0 anywhere 192.168.122.0/24 ctstate RELATED,ESTABLISHED
0 0 ACCEPT all -- virbr0 any 192.168.122.0/24 anywhere
0 0 ACCEPT all -- virbr0 virbr0 anywhere anywhere
0 0 REJECT all -- any virbr0 anywhere anywhere reject-with icmp-port-unreachable
0 0 REJECT all -- virbr0 any anywhere anywhere reject-with icmp-port-unreachable
Finally, DHCP packets originating on the host are explicitly allowed to be sent to guests.
Why isn't DNS explicitly enabled? If input and output policies are DENY, DHCP will still work, and DNS requests will still arrive, but replies will be dropped. This seems asymmetric.
Chain OUTPUT (policy ACCEPT 333 packets, 38232 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT udp -- any virbr0 anywhere anywhere udp dpt:bootpc
Prerouting is used to mangle the destination so that the packet can be routed to a different location.
Chain PREROUTING (policy ACCEPT 333 packets, 22179 bytes)
pkts bytes target prot opt in out source destination
Definitely nothing in INPUT and OUTPUT, since they only affect packets originated or terminated by the host.
Chain INPUT (policy ACCEPT 333 packets, 22179 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 23 packets, 1708 bytes)
pkts bytes target prot opt in out source destination
But postrouting has some action.
Multicast packets originating on a guest get dumped out to the internet unmasqueraded.
Broadcasts from guests also get dumped onto the host's interface. That seems odd to me since there's no way for replies to reach the guest. What's the benefit to allowing this?
TCP and UDP packets originating on a guest and leaving the network will be masqueraded. (not if they're destined for the host of course because then they'll transit the non-nat INPUT chain). The source port will be replaced with a host-specified port between 1024 and 65535. Because the guest doesn't have a routable IP address, the host needs to mangle the source address. (why does it need to do this in POSTROUTING? theoretically, couldn't it be done in the FORWARD chain?)
I'm guessing they have separate tcp, udp, and all masq targets so they can have separate byte counters? Otherwise, I don't see any difference between these rules.
Chain POSTROUTING (policy ACCEPT 23 packets, 1708 bytes)
pkts bytes target prot opt in out source destination
2 261 RETURN all -- * * 192.168.122.0/24 224.0.0.0/24
0 0 RETURN all -- * * 192.168.122.0/24 255.255.255.255
4 240 MASQUERADE tcp -- * * 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
258 18901 MASQUERADE udp -- * * 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
0 0 MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24
Chain POSTROUTING (policy ACCEPT 213 packets, 28840 bytes)
pkts bytes target prot opt in out source destination
0 0 CHECKSUM udp -- any virbr0 anywhere anywhere udp dpt:bootpc CHECKSUM fill
Nothing interesting here. I'm guessing this rule just fixes some lazy DHCP software that doesn't bother checksumming?