Iptables NAT & virtual machine networking

iptablesnat;networkingvirtualization

Currently I'm using the following script to make several virtual machines access the Internet and allow some ports being forwarded through the host's NAT inside a host-only network.

host_interface=eth0 # name of host machine interface connected to the Internet
vnet_interface=vboxnet0 # name of host machine's local network interface
host_ip[0]=... # array contains external IP addresses for every guest
guest_ip[0]=... # array contains internal IP addresses for every guest
guest_ports[0]=21,22,80,443 # array of port lists for every guest

iptables -P INPUT ACCEPT
iptables -F INPUT
iptables -P OUTPUT ACCEPT
iptables -F OUTPUT
iptables -P FORWARD ACCEPT
iptables -F FORWARD
iptables -t nat -F

echo "1" > /proc/sys/net/ipv4/ip_forward

for i in ${!host_ip[*]}
do
        iptables -t nat -A PREROUTING -i $host_interface -d ${host_ip[$i]} -p tcp -m multiport ! --dports "${guest_ports[$i]}" -j ACCEPT
        iptables -t nat -A PREROUTING -i $host_interface -d ${host_ip[$i]} -j DNAT -p tcp -m multiport --dports "${guest_ports[$i]}" --to-destination ${guest_ip[$i]}
done

# forward all packets from already established connections
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

for i in ${!guest_ip[*]}
do
        # allow forwarding new connections from host to guest
        iptables -A FORWARD -i $host_interface -o $vnet_interface -d ${guest_ip[$i]} -m state --state NEW -j ACCEPT

        # allow forwarding new connections from guest to host
        iptables -A FORWARD -i $vnet_interface -o $host_interface -s ${guest_ip[$i]} -m state --state NEW -j ACCEPT

        iptables -t nat -A POSTROUTING -o $host_interface -s ${guest_ip[$i]} -j SNAT --to-source ${host_ip[$i]}
done

The only thing is missing here is an ability to access any guest machine by it's EXTERNAL IP address (within local network of course). I'm getting "Connection refused" message every time I trying to connect to the open port of the same machine or another guest (but able to connect to the host from any guest). Internal (private) IPs works fine, I'm able to connect to any guest and host from anywhere within local network.
Tried adding something like

iptables -t nat -A PREROUTING -i $vnet_interface -d ${host_ip[$i]} -j DNAT --to-destination ${guest_ip[$i]}

but it seems that it is insufficient.

Best Answer

If I understand correctly, your problem is that your VM guests can't access themselves or the other guests by their external IP addresses. The problem you are having is clearly illustrated on this page (just imagine the router is your VM host, and the web server is one of your VM guests). You need to configure "Hairpin NAT".

The solution, to quote that same page, is that

an additional NAT rule needs to be introduced on the [VM host] to enforce that all reply traffic flows through the [VM host], despite the client and server being on the same subnet.

In your case, such rules could look something like this:

iptables -t nat -A PREROUTING -s $host_only_network -d ${guest_ip[$i]} -o $vnet_interface -p tcp -m multiport --dports "${guest_ports[$i]}" -j MASQUERADE

where:

host_only_network=192.168.1.0/24  # replace with whatever applies to your guest network

I'll leave it to you completely debug this, as complicated NAT setups tend to give me serious headaches.