KVM/libvirt: Forward Ports to guests with Iptables
I've been playing in the past few weeks with kvm/qemu and libvirt to set up some VMs. If you've been using libvirt before, you'll for sure agree that Libvirt is particularly awesome when it comes to managing virtual machines, their underlying storage and networks. However, if you happen to use NAT-ed networking and want to allow external access to services offered by your VMs, you’ve got to do some manual work. In this post, I'll try to demystify the process of NAT port forwarding to VMs.
Assign static IP address to VM
First, we need to list available networks. to do so, we use the following command:
$ virsh net-list
Name State Autostart Persistent
----------------------------------------------------------
xhubnet active yes yes
To see the xhubnet
network information:
$ virsh net-dumpxml xhubnet
<network>
<name>xhubnet</name>
<uuid>2ba36c4f-46c9-4767-a9fc-6808cf001a19</uuid>
<forward mode='nat'>
<nat>
<port start='1024' end='65535'/>
</nat>
</forward>
<bridge name='virbr1' stp='on' delay='0'/>
<mac address='52:54:00:8e:b5:88'/>
<domain name='xhubnet'/>
<ip address='192.168.111.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.111.120' end='192.168.111.254'/>
</dhcp>
</ip>
</network>
Where it's clearly indicated that the DHCP range is between 192.168.111.120 and 192.168.111.254.
Now, The first step toward setting up a public IP to our VM, is to get it MAC address. Again this is easy to achieve using virsh
CLI:
$ virsh dumpxml VM_NAME | grep -i '<mac'
<mac address='52:54:00:e2:95:c6'/>
The last step is to edit the network config to add new VM config using the following command:
$ virsh net-edit xhubnet
and add this line after the <range ...>
section:
<host mac='52:54:00:e2:95:c6' name='VM_NAME' ip='192.168.111.36'/>
To make changes effective, we need to start DHCP service using:
$ virsh net-destroy xhubnet
$ virsh net-start xhubnet
Restarting DHCP service should be enough, However in some cases you need to restart the libvirt-bin
service:
$ virsh shutdown VM_NAME
$ /etc/init.d/libvirt-bin restart
$ virsh start VM_NAME
$ ping -a 192.168.111.36
Configuring the Firewall to port forward
By default, guests that are connected via a virtual network with <forward mode='nat'/>
can make any outgoing network connection they like. Incoming connections are allowed from the host, and from other guests connected to the same libvirt network, but all other incoming connections are blocked by iptables rules.
If we would like to make a service that is on a guest behind a NATed virtual network publicly available, we need to setup the necessary iptables rules to forward incoming connections to the host on any given port.
Let's suppose for example that we need to forward the forward incoming connections to port 9867 on host machine to port 22 on guest machine. below are the needed rules to achieve that:
# connections from outside
$ iptables -I FORWARD -o virbr1 -d 192.168.111.36 -j ACCEPT
$ iptables -t nat -I PREROUTING -p tcp --dport 9867 -j DNAT --to 192.168.111.36:22
# Masquerade local subnet
$ iptables -I FORWARD -o virbr1 -d 192.168.111.36 -j ACCEPT
$ iptables -t nat -A POSTROUTING -s 192.168.111.0/24 -j MASQUERADE
$ iptables -A FORWARD -o virbr1 -m state --state RELATED,ESTABLISHED -j ACCEPT
$ iptables -A FORWARD -i virbr1 -o eth0 -j ACCEPT
$ iptables -A FORWARD -i virbr1 -o lo -j ACCEPT
where virbr1
is the interface in 192.168.111.0/24
subnet and eth0
is interface with public IP address.
Now that we have set up port forwarding, we can save this to our permanent rule set and load the rule set:
$ service netfilter-persistent save
$ service netfilter-persistent reload
Now, test that your VM is accessible through your firewall's public IP address:
$ ssh user@PUBLIC_IP -p 9867
This should work!