KVM: Creating a guest VM on a NAT network

In a previous article I showed how to use a bridged network to give a KVM Guest access to the same network as the Host.

Now we will go over NAT networks which allow the KVM Guest to reach outside the network, but hosts from the outside cannot reach the guest directly.  This opens up the amount of network space available and  provides application isolation, and could also offer a level of security if a firewall was introduced.

Ensure IP forwarding is enabled

Before going forward, make sure IP forwarding is enabled on the host.

# this needs to be "1"
cat /proc/sys/net/ipv4/ip_forward

If it is not enabled, then set it on the fly using:

sudo sysctl -w net.ipv4.ip_forward=1

And then enable it permanently by editing the “/etc/sysctl.conf” file with the following key:

net.ipv4.ip_forward=1

Create libvirt NAT network

First check the currently defined networks recognized by libvirt.  Use the following command:

virsh net-list --all

To create a new NAT network, create an xml file “nat223.xml”.  Use either the physical network or bridge name connected to the physical network for the “dev” attribute of the <forward> element.

cat <<EOF > nat223.xml
<network>
  <name>nat223</name>
  <forward mode='nat' dev='br0'/>
  <bridge name='virbr223' stp='on' delay='2'/>
  <ip address='192.168.223.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.223.141' end='192.168.223.254'/>
      <host name='myclone3' ip='192.168.223.143'/>
    </dhcp>
  </ip>
</network>
EOF

If you do not set the “dev” attribute to either the physical network device or bridge, then you will get unexpected behavior because it may take shortcuts and forward traffic directly from one KVM network to another using internal interfaces.  By using the physical interface or bridge connected to the physical interface, traffic will be redirected by the outermost network level on the host and ensure the iptables rules are applied correctly

Then create the network using these commands:

# create libvirt NAT network (net-create is for transient)
virsh net-define nat223.xml
virsh net-start nat223
virsh net-autostart nat223

# NAT state should be active, autostart, and persistent
virsh net-list --all

As described in the libvirt firwall documentation, rules are created in iptables to support this new network.

# virtual bridge virbr223 created
ip a | grep 223

# iptables FORWARD rules for nat223
sudo iptables -L FORWARD -nv --line-number | grep virbr223

# iptables NAT POSTROUTING chain
sudo iptables -t nat -L -n -v --line-number | grep 223

Create VM using NAT network

If you are creating guest OS from the console using virt-install, then simply replace the default network specifier with the name of the “nat223” network like below.

--network network=nat223

If using the virt-manager GUI, the network can be selected from the network source pulldown.

Once the guest VM is up and configured, you can run the command “ip a” and see that the new host now has an IP address randomly assigned from the DHCP pool defined in nat223.xml

This new guest VM should be able to reach out the public network, but no one will  be able to initiate connections back to you (not even the from the Host VM).  Guest VM in the same 192.168.223.0/24 NAT can communicate freely with one another.

Create VM, assigned specific IP from DHCP Pool

Notice the nat223.xml has a snippet that looks like:

<dhcp>
  <range start='192.168.223.141' end='192.168.223.254'/>
  <host name='myclone3' ip='192.168.223.143'/>
</dhcp>

If you create a guest VM with the name “myclone3”, then instead of getting a random IP address from the DHCP pool, it will instead receive the one specified “192.168.223.143”.  Using virt-install, the parameter used would be:

--name=myclone3

You can also specify by MAC address using the attribute “mac” in the <host> element, but then you must start up the guest first so the MAC can be randomly generated.  So it is easier to just use the name.

DHCP Leases

To view the current DHCP leases on a network, use this command:

virsh net-dhcp-leases nat223

Isolation from other NAT

Although the Host OS and public networks cannot initiate connections back to guests in the NAT network, take note that KVM guests in other NAT do have the ability to reach it.

 

REFERENCES

libvirt docs, network definition

libvirt docs, network filters and rules, IPtables differences for isolated/nat/routed

NAT KVM guest, forward ports with iptables for external access

libvirt wiki, VirtualNetworking docs

NAT bridge with DCHP and also set of static IP

libvirt xml definition for network section

libvirt, use nat, routed, isolated, pre-existing host bridge, macvtap

suse, libvirt network types

Show NAT rules in iptables

Enable IP forwarding

Disable hardware checksums when using pfsense with virtio

DMZ virtualization with vmware infra, PDF

KVM hook for port forwarding to NAT guest

 

NOTES

If you wanted one NAT network to have access to other NAT, that would require adding an extra iptables nat rule.

# get rule numbers
sudo iptables -t nat -L -n -v --line-number
# delete specific rule
sudo iptables -t nat -D POSTROUTING <number>

Watching packet stats on FORWARD or POSTROUTING chain

watch -n 1 "sudo iptables -L -v -n --line-number | grep FORWARD -A20"
watch -n 1 "sudo iptables -t nat -L -n -v --line-number"

Making sure each virtual bridge is associated with only single vnet

sudo apt-get install bridge-utils -y
brctl show

Check arp table

sudo apt-get install arping -y
arp -n

Show traffic on vnet from KVM host

brctl show
tcpdump -i vnet<N> -n