Disabling libvirt’s Automatic iptables NAT Rules: Manual Firewall Management for KVM Guests


4 views

Many sysadmins and developers working with KVM/libvirt environments face this common frustration: the hypervisor automatically manages iptables rules for NAT networks, often interfering with custom firewall configurations. While this automation helps beginners, it becomes problematic when you need precise control over network traffic.

Libvirt creates several chains when managing NAT networks:

# Typical libvirt-created chains
*nat
-A POSTROUTING -s 192.168.122.0/24 ! -d 192.168.122.0/24 -j MASQUERADE
-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT
-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT
-A FORWARD -d 192.168.122.0/24 -o virbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT

The cleanest solution is to modify libvirt's network XML definition to disable firewall management:

<network>
  <name>custom-net</name>
  <forward mode='nat'/>
  <bridge name='virbr1' stp='on' delay='0'/>
  <ip address='192.168.100.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.100.100' end='192.168.100.200'/>
    </dhcp>
  </ip>
  <!-- Critical firewall setting -->
  <forwarder dev='eth0'/>
  <firewall>
    <enabled value='no'/>
  </firewall>
</network>

For global control across all networks, modify libvirtd's configuration:

# /etc/libvirt/libvirtd.conf
# Set to 0 to disable firewall management
filter = "no"

Remember to restart libvirtd after changes:

systemctl restart libvirtd

For existing networks that can't be redefined, use virsh commands:

# Temporarily stop network
virsh net-destroy default

# Modify network definition
virsh net-edit default

# Add <firewall enabled='no'/> then:
virsh net-start default

Check if changes took effect by examining iptables after network start:

iptables -L -n -v | grep -i virbr
iptables -t nat -L -n -v | grep -i masq

If rules persist, ensure you're not using NetworkManager with libvirt integration, which might recreate rules.


Many sysadmins and developers working with KVM/libvirt encounter frustration when the hypervisor automatically manages iptables rules for NAT networks. The auto-generated rules often conflict with carefully crafted firewall configurations, particularly in environments requiring custom network security policies.

When you start a libvirt network (typically defined in XML at /etc/libvirt/qemu/networks/), the daemon automatically:

1. Creates forward chain rules in the filter table
2. Adds masquerading rules in the nat table
3. Establishes connection tracking helpers

Method 1: Network XML Modification

Add this parameter to your network definition:

<network>
  <name>private</name>
  <forward mode='nat'/>
  <trustGuestRxFilters>yes</trustGuestRxFilters>
  <!-- Critical setting below -->
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.2' end='192.168.122.254'/>
    </dhcp>
  </ip>
</network>

Method 2: Daemon Configuration

Edit /etc/libvirt/libvirtd.conf:

# Set this to 0 to disable all firewall management
firewall_driver = "none"

# Alternative: Use nftables instead if preferred
# firewall_driver = "nftables"

Method 3: Hook Script Approach

Create an executable script at /etc/libvirt/hooks/network:

#!/bin/bash
NETWORK="$1"
ACTION="$2"

if [ "$ACTION" = "started" ]; then
    # Your custom iptables rules here
    iptables -D LIBVIRT_FWI -o virbr0 -j REJECT --reject-with icmp-port-unreachable
    iptables -t nat -D POSTROUTING -s 192.168.122.0/24 ! -d 192.168.122.0/24 -j MASQUERADE
fi

After implementing any solution:

# Restart libvirt
systemctl restart libvirtd

# Start your network
virsh net-start default

# Verify rules
iptables -L -n -v
iptables -t nat -L -n -v

For complex environments, consider:

  • Bridging instead of NAT
  • Network namespaces isolation
  • Separate physical interface for VM traffic

Remember that changes might affect VM network connectivity, so test in non-production environments first.