Pi: Make a VPN gateway with UPnP port forwarding
Tunneling your traffic over an encrypted VPN can be good for both privacy concerns and circumventing geoblocking. If a service is only offered in a specific country or blocked at your current location. My use case is a bit of both. Currently living in the USA which is the biggest surveillance state on earth I want my traffic to originate from my home country, Sweden, where I know the law and whats allowed and not allowed. Avoiding the mighty force of the NSA completely can only be done by unplugging but at least it’s a little bit better. Also several services I want to use is only offered in Sweden, like local Swedish news as an example. Both of these can be solved by setting up a VPN tunnel back Sweden!
The basic setup of my system is a local network 192.168.0.0/24 that is connected to my Comcast router. Behind that I have my “server”, a bunch of Raspberry Pi’s, Banana Pi’s and Odroid’s running different services. I have seen a lot of tutorials that run the VPN, in this case OpenVPN, on the same box as their torrent box or media player. Due to the heavy load on the CPU when encrypting and decrypting I prefer to have it on a separate box. It also follow my overall network design with separating services so I can jank out what ever box I want and run tests or make changes without it effecting everyday operations. Also this way I will have a gateway that can provide the VPN tunnel to several different clients.
On the other end I have a commercial VPN provider in Sweden. I have my own servers with gigabit speed but I still use the commercial one I got long ago since it’s prepaid for a long time still. There are a bunch of different VPN providers out there providing different type of services. What VPN provider to use is outside the scope of this post but you need to use one that supports OpenVPN.
I have used these steps to setup a VPN gateway on both the Raspberry Pi and the Banana Pi, both running Raspbian. Without mayor tweaks this will work on most major linux distributions.
Setting up the basics
As for any project first bring the Pi on the network make sure SSH works, change the root password, and update it. When that is done we need to setup a static IP-address on the box. There are several reasons why this is a better approach then DHCP and they can vary a bit from case to case. Number one is that it needs to have the same IP all the time since other machines will use it as their default gateway. That can be done by DHCP reservation even on my crappy Comcast router but I can’t give it specifik configuration like DNS etcetera. That will end up being a mess once the tunnel is up and the box tries to talk to the “internal” Comcast DNS over the internet. So we start by editing the interfaces.
[bash]sudo nano /etc/network/interfaces[/bash]
In the newer version of Raspbian you will find iface eth0 inet manual already. On versions previous to Raspbian Jessie (2016-02-26) it was set to auto. If you use an earlier version it will proberbly say iface eth0 inet auto then you should change it to the config below, of course changing out the values to correspond with your network.
[bash]iface eth0 inet manual
dns-nameservers 184.108.40.206 220.127.116.11[/bash]
If your running Jessie and/or your pi is set to iface eth0 inet manual already without any configuration below and receiving a DHCP address you need to edit /etc/dhcpcd.conf instead. At the end of that file you add:
static domain_name_servers=18.104.22.168 22.214.171.124[/bash]
When that is saved do a sudo reboot and try to connect to the new ip after a while.
Banana Pi: If your running this on a Banana Pi you need to update the /etc/resolv.conf as well. Just put your preferred DNS servers in there, one peer line.
First update the Pi if you haven’t already done so.
[bash]sudo apt-get update
sudo apt-get dist-upgrade[/bash]
Then install OpenVPN.
[bash]sudo apt-get install openvpn[/bash]
Most commercial VPN providers provide you with a .conf file for your OpenVPN client. It contains all the settings and certificates for connection to their service. In addition to this you have a username and a password. That works great running it from your desktop but this is a stand alone box that we want to bring the tunnel up after a power failure for example. So we need to open the .conf file in a text editor and add a line that reference another file containing our username and password. In the .conf file I add this line:
Then a create the swe.auth file and put the username on the first line and the password on the second line. I then use WinSCP to transfer these files into /etc/openvpn/, you can use a USB-stick or just copy paste the contents through nano over SSH if you like as well. All the .conf files in the /etc/openvpn folder will automatically be ingested when the OpenVPN daemon starts and it will try to bring up the tunnel. So we restart the OpenVPN daemon:
[bash]sudo service openvpn restart[/bash]
To test if we are connected we can use wget -qO- http://ipecho.net/plain ; echo, of course it’s a good idea to do this before your bring up the tunnel as well so you have something to compare it to. You can always check from your desktop running against your regular router to see the difference.
Now the box it self can use the VPN tunnel but it isn’t a gateway yet. So first we need to enable IP routing. First command adds the configuration to the sysctl.conf file and the second loads them in. These settings will be loaded from the file upon reboot as well so it’s persistent.
[bash]sudo /bin/su -c "echo -e ‘n#Enable IP Routingnnet.ipv4.ip_forward = 1’ > /etc/sysctl.conf"
sudo sysctl -p[/bash]
When the settings are loaded with sudo sysctl -p you should see this printed on the screen:
[bash]net.ipv4.ip_forward = 1[/bash]
Setup iptables (firewall)
Now the box can route but we also need to setup some routing rules for the traffic to flow from the internal network, over the tunnel and out on the internet at the other end. We do this by using iptables. Iptables is a basic rule based firewall pre-installed in most Linux distributions. It can look complicated at first but when you get the hang of it it’s pretty straight forward. You have your basic chains INPUT, OUTPUT and FORWARD. The names are pretty self explanatory but here is a brief, simplified breakdown of the three:
- INPUT – all traffic coming into the box (with the box as destination) from the outside on any of the network interfaces.
- OUTPUT – all traffic leaving the box with the box as it’s origin.
- FORWARD – all traffic forwarded (and routed) between the box different network interfaces.
All chains have a set of rules that iptables try to match the traffic to. If no rule match it will go to the chains default policy. Breaking down this line by line will give you a better understanding of what we are doing here.
[bash]sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE[/bash]
First we enable NAT (network address translation), it’s the same technique as your router uses. You have private ip-addresses on your internal network (at least most people do) and they can not be routed out onto the internet. You need to use your single public IP for all your clients behind the router. NAT keeps track on who requested what so when the response comes back it can send it to the correct internal host.
[bash]sudo iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT[/bash]
We setup a rule allowing all requests coming in on eth0 (the internal physical network card) to be routed over the tunnel (tun0). In this setup we allow all traffic from inside to outside to go through.
[bash]sudo iptables -A FORWARD -i tun0 -o eth0 -m state –state RELATED,ESTABLISHED -j ACCEPT[/bash]
We den need to allow the responses to come back into the network but we only want to allow responses. By specifying –state RELATED,ESTABLISHED we only allow traffic back in that we initiated from the inside.
[bash]iptables -A INPUT -i lo -j ACCEPT[/bash]
Let the box talk to it self. This rule is very importent since we are setting a default policy on INPUT to DROP.
[bash]sudo iptables -A INPUT -i eth0 -p icmp -j ACCEPT[/bash]
We allow hosts on the local network to ping the box.
[bash]sudo iptables -A INPUT -i eth0 -p tcp –dport 22 -j ACCEPT[/bash]
We allow SSH from hosts on the internal network.
[bash]sudo iptables -A INPUT -m state –state ESTABLISHED,RELATED -j ACCEPT[/bash]
We allow traffic responses from anyone as long as we established it.
[bash]sudo iptables -P FORWARD DROP
sudo iptables -P INPUT DROP[/bash]
We set the default policys to drop everything but the traffic we specifically allowed by setting the default policy to drop for both the FORWARD and the INPUT chain. In this case we leave the OUTPUT chain untouched with it’s default policy of ALLOW. That means that all traffic the box tries to send out will go through. If we where to run this on a torrent box for or any other box that are only allowed onto the internet over the VPN we would need to setup the OUTPUT chain to only allow DNS and VPN (udp 1194). That would prevent any traffic from the local host to reach the internet if the tunnel is down. Since we are building a VPN gateway where we limit the traffic from other hosts with the FORWARD chain no traffic will get through from other clients when the tunnel is down. All internal clients will use the box as there default gateway, the only forwarding rules in place are between eth0 and tun0 which means that if the tunnel is down no traffic can be forwarded.
[bash]sudo apt-get install iptables-persistent
sudo systemctl enable netfilter-persistent[/bash]
We make the iptable rules we just added persistent after reboot, just answer yes on the questions in the install. The second command will make it persistent after reboot. If you change any iptable rules after this just run the command below to save them. A reference to iptables can be found here http://ipset.netfilter.org/iptables.man.html
[bash]sudo netfilter-persistent save[/bash]
UPnP port forwarding
To be able to use UPnP enabled devices and software that needs to open ports with out VPN tunnel we use linux-igd. It’s basically a daemon that processes UPnP port forwarding requests and updates the iptables. These updates are non persistent so they clear out every time we reboot. We will also set this up so it starts automatically with the VPN tunnel and also destroys all the forwards when then tunnel goes down or is disabled. First we install the deamon:
[bash]sudo apt-get install linux-igd[/bash]
Then we need two bash scripts that can execute when the tunnel comes up or goes down. So in the /etc/openvpn folder we can just use nano to create the scripts.
[bash]sudo nano /etc/openvpn/tunnel.up[/bash]
This script will run when the tunnel comes up and need to start the upnp daemon. So we put this into it:
/usr/sbin/upnpd tun0 eth0[/bash]
The upnpd takes two parameters first the public interface, in this case the tunnel (tun0) and the second one is the internal interface (eth0). Then we need a script for killing it all of when the tunnel goes down.
[bash]sudo nano /etc/openvpn/tunnel.down[/bash]
In this one we need to kill of all the opened ports.
Make the scripts executable:
[bash]sudo chmod +x /etc/openvpn/tunnel.up /etc/openvpn/tunnel.down[/bash]
And then we need to reference them in our OpenVPN config file. In this case /etc/openvpn/swe.conf so we open that with nano and add these lines at the bottom:
[bash]# add up and down script for uPNP
We also need to to open the port for the UPnP requests coming in. I read through a lot of documentation on this and there are a number of ports mentioned that’s needed for this. It took me quite a while to figure this one out. Almost all documentation at least agree on UDP 1900,5351,5353 and TCP 49152. That was the ports I found after a while as well, using Transmission as the client requesting UPnP port forward. You can read more about how to troubleshoot this on my serverfault question “Allow UPnP and NAT-PMP request in iptables”. In the end these two lines worked with my Transmission implementation:
[bash]sudo iptables -A INPUT -i eth0 -p udp -m multiport –dports 1900,5351,5353 -j ACCEPT
sudo iptables -A INPUT -i eth0 -p tcp -m multiport –dports 49152 -j ACCEPT
sudo netfilter-persistent save[/bash]
Now just restart the OpenVPN service and the tunnel should come up and work with UPnP port forwarding.
Manual port forwarding
UPnP port forwarding is convenient and easy to use but not all implementations supports it. Maybe you don’t want any device on the inside to be able to open ports to the rest of the world. If any of these are true in your case you can of course do manual port forwarding with iptables. In this example we want to listen on tcp port 666 and forward that to internal ip 192.168.0.6.
[bash]sudo iptables -t nat -A PREROUTING -p tcp –dport 666 -j DNAT –to 192.168.0.6
sudo iptables -t filter -A FORWARD -p tcp -d 192.168.0.6 –dport 666 -j ACCEPT
sudo netfilter-persistent save[/bash]
First line is to setup a NAT rule for that port. The second one is to allow the traffic through to the internal IP-address. The last line saves the rules so they are persistent after reboot.
Clients logon delay
If you use this gateway for your other Pi implementations as a default gateway you will probably experience a logon delay running SSH into the box. Since the Pi can’t do reverse DNS (IP-address -> hostname) for internal hosts anymore it will wait for the timeout when you try to logon. In short, for the log it resolves the incoming IP-address connecting over SSH to a hostname. Since the, in this case Googles, public DNS can’t lookup your internal boxes it will timeout. This results in a delay from when you put in your username and press enter until the password prompt appears. This can easalie be fixed by editing /etc/ssh/sshd_config and disabling the reverse lookup for SSH logins. Just add this line at the end.