47

How can I forward ports on a server running libvirt/KVM to specified ports on VM's, when using NAT?

For example, the host has a public IP of 1.2.3.4. I want to forward port 80 to 10.0.0.1 and port 22 to 10.0.0.2.

I assume I need to add iptables rules, but I'm not sure where is appropriate and what exactly should be specified.

Output of iptables -L

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     udp  --  anywhere             anywhere            udp dpt:domain 
ACCEPT     tcp  --  anywhere             anywhere            tcp dpt:domain 
ACCEPT     udp  --  anywhere             anywhere            udp dpt:bootps 
ACCEPT     tcp  --  anywhere             anywhere            tcp dpt:bootps 

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             10.0.0.0/24         state RELATED,ESTABLISHED 
ACCEPT     all  --  10.0.0.0/24          anywhere            
ACCEPT     all  --  anywhere             anywhere            
REJECT     all  --  anywhere             anywhere            reject-with icmp-port-unreachable 
REJECT     all  --  anywhere             anywhere            reject-with icmp-port-unreachable 

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Output of ifconfig

eth0      Link encap:Ethernet  HWaddr 00:1b:fc:46:73:b9  
          inet addr:192.168.1.14  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::21b:fcff:fe46:73b9/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:201 errors:0 dropped:0 overruns:0 frame:0
          TX packets:85 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:31161 (31.1 KB)  TX bytes:12090 (12.0 KB)
          Interrupt:17 

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

virbr1    Link encap:Ethernet  HWaddr ca:70:d1:77:b2:48  
          inet addr:10.0.0.1  Bcast:10.0.0.255  Mask:255.255.255.0
          inet6 addr: fe80::c870:d1ff:fe77:b248/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:468 (468.0 B)

I'm using Ubuntu 10.04.

steveh7
  • 473

7 Answers7

48

The latest stable release for libvirt for Ubuntu is version 0.7.5, which doesn't have some newer features (i.e. script hooks and network filters) which make automatic network configuration easier. That said, here's how to enable port forwarding for libvirt 0.7.5 on Ubuntu 10.04 Lucid Lynx.

These iptables rules should do the trick:

iptables -t nat -I PREROUTING -p tcp -d 1.2.3.4 --dport 80 -j DNAT --to-destination 10.0.0.1:80
iptables -t nat -I PREROUTING -p tcp -d 1.2.3.4 --dport 22 -j DNAT --to-destination 10.0.0.2:22
iptables -I FORWARD -m state -d 10.0.0.0/24 --state NEW,RELATED,ESTABLISHED -j ACCEPT

The default KVM NAT config provides a rule similar to the 3rd I gave above, but it omits the NEW state, which is essential for accepting incoming connections.

If you write a startup script to add these rules and you're not careful, libvirt 0.7.5 overrides them by inserting its own. So, in order to make sure these rules are applied properly on startup, you need to make sure libvirt has initialized before you insert your rules.

Add the following lines to /etc/rc.local, before the line exit 0:

(
# Make sure the libvirt has started and has initialized its network.
while [ `ps -e | grep -c libvirtd` -lt 1 ]; do
        sleep 1
done
sleep 10
# Set up custom iptables rules.
iptables -t nat -I PREROUTING -p tcp -d 1.2.3.4 --dport 80 -j DNAT --to-destination 10.0.0.1:80
iptables -t nat -I PREROUTING -p tcp -d 1.2.3.4 --dport 22 -j DNAT --to-destination 10.0.0.2:22
iptables -I FORWARD -m state -d 10.0.0.0/24 --state NEW,RELATED,ESTABLISHED -j ACCEPT
) &

The sleep 10 above is a hack to make sure the libvirt daemon has had a chance to initialize its iptables rules before we add our own. I can't wait until they release libvirt version 0.8.3 for Ubuntu.

Eddie C.
  • 549
  • 1
  • 3
  • 12
22

There is a way to set up port redirection on the fly when the guest is using user-mode networking, I blogged about it here:

http://blog.adamspiers.org/2012/01/23/port-redirection-from-kvm-host-to-guest/

You can see the details there, but for convenience, here is the solution I figured out:

virsh qemu-monitor-command --hmp sles11 'hostfwd_add ::2222-:22'

This one-liner is a lot easier than the other answers but only works in some scenarios (user mode network stack).

chicks
  • 3,915
  • 10
  • 29
  • 37
10

A more "official"[1] way to do this is to create a hook script as described at the libvirt website:

http://wiki.libvirt.org/page/Networking#Forwarding_Incoming_Connections

... basically this script will be invoked when a KVM guest is booted-up. The script itself will add the appropriate iptable rules (similar to Isaac Sutherland's answer above) with the 'NEW' connection state correctly added. Note that you must modify the script with the correct values for your hosts and ports.

[1] though the libvirt documentation itself says this is kind of a hack, go figure

Antony Nguyen
  • 361
  • 3
  • 6
3

The "only" way we can make a port forward using KVM (libvirt) with the "default network" (virbr0) is using the hack/workaround informed by @Antony Nguyen . Or more simply you can use libvirt-hook-qemu.

This thread has a complete explanation of how to solve this problem for CentOS 7 (and certainly for other distros) using libvirt-hook-qemu: https://superuser.com/a/1475915/195840 .

Eduardo LĂșcio
  • 283
  • 5
  • 15
0

On Ubuntu 20.04 I made me the following script, which is saved as /etc/libvirt/hooks/allow-portfw (chmod +x):

#!/bin/bash

CHAIN=LIBVIRT_FWI IPTCMD="iptables -L $CHAIN -vn" FILTERCMD="grep -v -e Chain -e pkts -e reject-with -e DNAT"

while $IPTCMD | grep ESTABLISHED | grep -v DNAT >/dev/null do IF=$($IPTCMD | $FILTERCMD | awk '{print $7}' | head -n1) NET=$($IPTCMD | $FILTERCMD | awk '{print $9}' | head -n1) iptables -D $CHAIN -o $IF -d $NET -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT iptables -I $CHAIN 1 -o $IF -d $NET -m conntrack --ctstate DNAT,RELATED,ESTABLISHED -j ACCEPT done

the entry situation is, that we are missing NEW btw. the DNAT flag for the FORWARDING chain, which is automatically created.

sudo iptables -L LIBVIRT_FWI -vn
Chain LIBVIRT_FWI (1 references)
 pkts bytes target     prot opt in     out     source               destination         
1741K 2661M ACCEPT     all  --  *      virbr0  0.0.0.0/0            192.168.122.0/24     ctstate RELATED,ESTABLISHED
   38  1972 REJECT     all  --  *      virbr0  0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable

My script adds the DNAT flag to all definitions.

That way, I just added the portforwarding in iptables with

iptables -A PREROUTING -i mylanIF -p tcp --dport 3389 -j DNAT --to-destination 192.168.122.110:3389

You can use the packages iptables-persistent to save this state (but for sure you have to drop the libvirt chains from the dump)

Or u use ufw or any other firewall script for it.

My script is based on the findings from this address https://www.cyberciti.biz/faq/kvm-forward-ports-to-guests-vm-with-ufw-on-linux/

Thomas
  • 187
0

On Ubuntu 20.04, I just successfully setup like below. It might give you a hint ;)

$ sudo virsh list
     Id   Name       State
--------------------------
 1    MY-KVM-3   running

$ sudo nano /etc/libvirt/hooks/qemu

Customize and add following:

#!/bin/bash

IMPORTANT: Change the "VM NAME" string to match your actual VM Name.

In order to create rules to other VMs, just duplicate the below block and configure

it accordingly.

if [ "${1}" = "MY-KVM-3" ]; then

Update the following variables to fit your setup

GUEST_IP=192.168.122.3 GUEST_PORT=22 HOST_PORT=2022

if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then /sbin/iptables -D FORWARD -p tcp -d $GUEST_IP -j ACCEPT /sbin/iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT fi if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then /sbin/iptables -I FORWARD -p tcp -d $GUEST_IP -j ACCEPT /sbin/iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT fi

Update the following variables to fit your setup

GUEST_PORT=3389 HOST_PORT=3390

if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then /sbin/iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT fi if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then /sbin/iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT fi

Add new port before here

if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then /sbin/iptables -D FORWARD -o virbr0 -d $GUEST_IP -j ACCEPT fi if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then sleep 5 /sbin/iptables -I FORWARD -o virbr0 -d $GUEST_IP -j ACCEPT fi fi

Reboot and try to your guest with command below:

$ ssh username@host_ip_address -p2022

Or connect Remote Desktop with port 3390, if you have setup XRDP already.

Almas
  • 101
-1
iptables -t nat -I PREROUTING -d 1.2.3.4 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1
 iptables -t nat -I PREROUTING -d 1.2.3.4 -p tcp --dport 22 -j DNAT --to-destination 10.0.0.1
topdog
  • 3,558