How to Use an External Server with OpenVPN to Escape NATs

I have recently moved to a new flat and I love it, though unfortunately not all is perfect. One might expect central London (literally 500m away from the actual geographical centre) to have the best internet connection London has to offer. Well, one would be wrong; I am no longer able to get anything better than lousy ADSL2+, and this amazing offering comes with a high price, a long contract, and a month’s wait for the installation. This has led me to choose a 4G internet provider; the connection is usually better than what I would have expected to get with any of the landline providers, is much cheaper, and I had it up and running less than 24hrs after I joined.

However, there is a problem with this: the 4G provider uses a carrier-grade NAT, that made impossible to access my home server from outside my home network. Luckily, I have a VPS that is not behind a NAT so the solution was obvious: tunnel everything through it.

The Solution

One easy solution to my problem could be to run a VPN, connect my home server to it, and then connect to the VPN every time I want to access my home server. This solution works, but it’s annoying to connect to the VPN every time and even more annoying to give VPN access to anyone who needs access to my private Git server. This led to me choosing a slightly more complex solution that solves this issue: forward specific ports from a VPN that has a unique external IP address to my home server.

Setup OpenVPN

From now on I’ll call the externally available server EXT and the one I’ll expose HOME. First, it’s necessary to setup an OpenVPN server on EXT, however, setting up OpenVPN is beyond the scope of this post. I recommend following the Arch Linux guide, and the OpenVPN official documentation.

With that being said, there are a few important OpenVPN configurations you should make sure to have in your OpenVPN server (EXT):

# ... SNIP ...
# Allow client to client communication
client-to-client
# Send pings to keep connection alive
keepalive 15 60
ping-timer-rem
# Persistent ip addresses
ifconfig-pool-persist /etc/openvpn/ipp

At this point you should make sure you can connect to the VPN and access services running on HOME from EXT.

Setup Port Forwarding

The first thing to do when setting up port forwarding, is to enable it in the kernel (on EXT). Sadly, since we still live in an IPv4 world (this is why we have CGNAT in the first place), I’ll only deal with the IPv4 case.

As root:

# Enable on the running system (temporary)
$ sysctl -w net.ipv4.ip_forward=1
# Enable on boot (depends on your distribution)
$ echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.d/51-ip.conf

The last step is to tell iptables which ports should be forwarded.  For the sake of the example, I’ll forward TCP port 2222 for ssh and UDP ports 61001-62000 for Mosh. The first step is to check HOME’s ip address using the command ip addr show.

Then, complete the following commands as root, for this example HOME’s ip address is 192.168.87.10:

# Allow IP forwarding
$ iptables -A POSTROUTING -j MASQUERADE
# Forward the relevant ports to HOME
$ iptables -A PREROUTING -p tcp --dport 2222 -j DNAT --to-destination 192.168.87.10:22
$ iptables -A PREROUTING -p udp --dport 61001 -j DNAT --to-destination 192.168.87.10:61001-62000

# Enable connections to and from the machine (in case restricted)
$ iptables -A FORWARD -d 192.168.87.10 -p tcp --dport 22 -j ACCEPT
$ iptables -A FORWARD -s 192.168.87.10 -p tcp --sport 22 -j ACCEPT
$ iptables -A FORWARD -s 192.168.87.10 -p udp -m state --state RELATED,ESTABLISHED --sport 61001:62000 -j ACCEPT
$ iptables -A FORWARD -d 192.168.87.10 -p udp --dport 61001:62000 -j ACCEPT

Important note: commands like sysctl and iptables only last until the next boot, so don’t forget to make them persistent in whatever way is recommended for your distribution.

Verify Functionality

Connect to a network different than the one your home server is on, and try to connect to ssh using port 2222 with EXT’s ip/hostname. For example:

$ ssh -p 2222 user@EXT

You should now be successfully logged into HOME.

One Additional Trick

One annoyance that comes with this method is that all traffic to HOME is passed through EXT, even if the user’s device has direct access to HOME by being on the same LAN. This is due to the fact that the DNS name and ip address you are now using to connect to HOME is actually EXT’s.

One simple solution is to use a different domain name depending on whether or not the device has direct access. This is awfully annoying, especially when using git.

The better solution I chose for my setup is to configure DNS to resolve differently inside my home network, many routers support configuration for this. So for example, I use git.stosb.com for my git server. The public DNS is a CNAME to stosb.com, but inside my network, it resolves to HOME’s local address, which means everything will just connect to it directly.

Final Notes

I would love to hear if you have any corrections or better ideas on how to implement this. I would especially love to hear if you have any comments on how to harden this setup even further. I have a feeling the iptables rules could be a bit more strict. Please let me know.

This article was originally posted on Tom’s personal blog, and has been approved to be posted here.

Author: Tom Hacohen

Tom has been a core developer and part of the leading team at SHR (Openmoko), he is currently a core developer for EFL.