Using an External Server and a VPN to Escape NATs

Port forwarding over VPN in order to access machines behind NATs

 ·  4 minutes read

I have recently moved to a new flat and I love it, though unfortunately, not all is perfect. One would expect central London (literally 500m away from the actual geographical centre) would 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 at a high price point, a long contract and a month's wait for the installation.

This has led me to choose a 4G internet provider. The connection is mostly 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.

There is a problem though: they use a carrier-grade NAT which means I was no longer able to access my home server from outside my home network. Luckily I have a VPS that is not behind a NAT, so a solution was obvious, tunnel everything through there.

The solution

One easy solution to my problem would be to just 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 is very annoying to have to connect to the VPN every time, and even more annoying to have to give access to my VPN to anyone who needs access to my private Git server.

This made me choose a slightly more complex solution that solves the above issues: port forwarding some ports from another member of the VPN that has a unique external IP to my home server.

Setting up OpenVPN

From now on we will call the externally available server EXT and the one we would like to expose: HOME.

We now need 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.

Setting up port forwarding

The first thing to do when setting up port forwarding, is enabling it in the kernel (on EXT). Since we sadly still live in an IPv4 world (this is why we have CGNAT in the first place), we will 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

Now we just need to tell iptables which ports we would like to forward, and we should be done. Keep in mind: like sysctl, iptables commands only last until the next boot, so do not forget to make them persistent in whatever way is recommended for your distribution.

Now we need to decide which ports you would like to forward. For the sake of the example, we will forward TCP port 2222 for ssh and UDP ports 61001-62000 for mosh.

First we need to check HOME's address inside the VPN. This depends on your VPN setup, but it usually should be obvious from the ip command.

As root, assuming 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

Another reminder: make sure to make the iptables rules persistent.

Verifying it works

Connect to a different network 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 now all of your traffic to HOME is passed through EXT, even if you have direct access (same LAN) to HOME from your computer. This is due to the fact that the DNS name/ip address you are now using to connect to HOME is actually EXT's.

One simple solution would be to use a different DNS name depending if you have direct access or not. This is awfully annoying and manual in general, and even more annoying when using git.

A better solution, which is what I chose for my setup, is to configure your DNS to resolve differently inside your home network (configure your router if it supports it, it should). 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.

Finishing 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.

Please let me know if you spotted any mistakes or have any suggestions, and follow me on Twitter or RSS for updates.

security server vpn firewall