Kodi server part 6: Always On VPN Client

This is the sixth part of a multi-part tutorial describing how to configure the "perfect" Kodi media centre running on top of ubuntu server.

Other parts of the tutorial may be found here:

This section covers configuration of an openvpn client connection to an external server, to anonymise traffic to and from the Kodi box and prevent bandwidth throttling of torrent traffic by your ISP.

You will also find some additional configuration to ensure the VPN does not break secure SSH connections from the public internet via your router, and some scripts to make sure Transmission binds to the local IP address of the VPN interface (so it doesn't try and send traffic out over your public connection if the VPN goes down).

Always on VPN connection

My ISP throttles torrent traffic, so I choose to run an always on VPN client connection on this box. The aim is to send all torrent traffic out through this interface.

I personally use a VPN service called privateinternetaccess (PIA), which I have found to be very useful.

First, install openvpn:

sudo apt-get update
sudo apt-get install openvpn

Now download the config files from your provider, into your home directory. For PIA, this can be achieved with:

cd ~
curl -O https://www.privateinternetaccess.com/openvpn/openvpn-strong-tcp.zip

Now unpack it (the unzip command will unpack it into a subdir called openvpn):

sudo apt-get install zip unzip
unzip -d openvpn openvpn-strong-tcp.zip

Now copy the files you need into the config dir. Here I've used the config file for Germany as an example, but you could use any of them:

sudo cp ~/openvpn/Germany.ovpn /etc/openvpn/
sudo cp ~/openvpn/ca.rsa.4096.crt /etc/openvpn/
sudo cp ~/openvpn/crl.rsa.4096.pem /etc/openvpn/

Openvpn only creates connections for files ending in .conf. I chose to create a symlink from the .ovpn config file. This way you can store many .ovpn configuration files in the directory and update the symlink if you want to use a different config:

cd /etc/openvpn
sudo ln -s Germany.ovpn client.conf

Ubuntu ships with a script that can be used to update which nameservers are used for DNS when the VPN is active, which is installed at /etc/openvpn/update-resolv-conf. We need to add to the openvpn config file (/etc/openssh/client.conf) to make sure this script gets called when the VPN starts and stops. Add these lines:

up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf

If your provider lets you authenticate with a username and password, you need to change the config so that openvpn knows without having to prompt interactively. Add this line (or modify existing lines):

auth-user-pass userpass.txt

Then create a file to store the username and password. This needs to be owned by root:root with read and write permissions for root only:

sudo touch userpass.txt
chmod 600 userpass.txt

Edit the file and add the username on the first line and the password on the second line. Save and close the file.

Now we can enable openvpn so it starts automatically at boot, and start it up (restart it instead if it's already running):

sudo systemctl enable openvpn
sudo systemctl start openvpn

Test your IP from the commandline to see if your IP address has changed :

curl icanhazip.com

Enabling SSH connections from the WAN when the VPN is running

The linux kernel uses routing tables to determine where it should send network traffic. When openvpn brings up the VPN interface, it changes the routing table so that the VPN becomes the default route for all traffic not destined for your local network.

Unfortunately, this means that if you SSH to the server from the WAN, the packets will come in on the wifi interface and be sent out on the VPN interface. The SSH client will be listening for a reply from your global IP address, not the VPN server's global IP address, and you will get a connection error.

The default routing table can be shown using the route command. Without the VPN running, the routing table will look something like this:

Kernel IP routing table                                                                                                                                                                                           
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface                                                                                                                                     
default         192.168.1.1     0.0.0.0         UG    0      0        0 wlp2s0                                                                                                                                    
192.168.1.0     *               255.255.255.0   U     0      0        0 wlp2s0

With the VPN, the routing table will have additional entries for the local network of the VPN server:

Kernel IP routing table                                                                                
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface                          
default         10.31.1.13      128.0.0.0       UG    0      0        0 tun0                           
default         192.168.1.1     0.0.0.0         UG    0      0        0 wlp2s0                         
10.31.1.1       10.31.1.13      255.255.255.255 UGH   0      0        0 tun0                           
10.31.1.13      *               255.255.255.255 UH    0      0        0 tun0                           
128.0.0.0       10.31.1.13      128.0.0.0       UG    0      0        0 tun0                           
6d.3e.32a9.ip4. 192.168.1.1     255.255.255.255 UGH   0      0        0 wlp2s0                         
192.168.1.0     *               255.255.255.0   U     0      0        0 wlp2s0

The routing tables can look quite confusing, but basically the kernel looks down the list and chooses the most specific entry for the traffic that is being sent, and uses that route to send the packet. The default destination is a catch-all for packets with no specific entry. The "gateway" is the next hop for packets that can't be sent directly (for example packets destined for the WAN when you are behind a router need to be sent to the router and not directly to the WAN address). The "genmask" is the netmask of the destination - in the last line, the genmask of 255.255.255.0 means that the rule applies to any 192.168.1.X address instead of just the single IP address.

To make sure traffic that arrives from your router gets sent back out via the router and not over the VPN, we need to add a new routing table.

The route command we used earlier shows the default table if no other arguments are given. However, we are free to define multiple routing tables. As before, the following commands still assume that your router is at 192.168.1.1 and the IP address of your server is 192.168.1.2. First, add a new rule to tell the kernel to use a new routing table for traffic from 192.168.1.2:

sudo ip rule add from 192.168.1.2 table 128

You can show the existing rules using this command:

sudo ip rule show

Output should look like this:

0:      from all lookup local 
32764:  from 192.168.2.2 lookup 128 
32766:  from all lookup main 
32767:  from all lookup default

Now we can create some rules in table 128. These assume that you have configured wireless internet and your wifi interface is wlp2s0:

sudo ip route add table 128 to 192.168.1.0/24 dev wlp2s0
sudo ip route add table 128 default via 192.168.1.1

You can see these rules in the new routing table if you use the ip route show table 128 command:

default via 192.168.1.1 dev wlp2s0 
192.168.1.0/24 dev wlp2s0  scope link

These entries specify that traffic to any 192.168.1.X address should be sent out using the wifi interface, and that the default route is via your router.

Now test a SSH connection from the WAN to the server while the VPN client is running (e.g. using a mobile phone, or from another server outside your LAN). If the new static route has worked, we need to make it permanent, which can be achieved by listing up commands in /etc/network/interfaces:

auto wlp2s0
iface wlp2s0 inet dhcp
    wpa-ssid "yourSSID"
    wpa-psk secrethash
    up ip rule add from 192.168.1.2 table 128 || true
    up ip route add table 128 to 192.168.1.0/24 dev wlp2s0 || true
    up ip route add table 128 default via 192.168.1.1 || true

In the above, the || true part is there to make sure that if any of the commands produce warnings (e.g. because the route already exists) then the interface configuration continues instead of exiting with an error.

Double check your have the commands entered correctly, and then restart the server and verify that the table has been added:

sudo ip rule show
sudo ip route show table 128

And test to see if the rest of the outgoing traffic is still passing through the VPN:

curl icanhazip.com

Binding Transmission to the IP of the VPN interface

Transmission does not have an option to bind to a specific interface. By default, transmission binds to 0.0.0.0, which means that packets will use the default route. When the VPN is up, the default route is the VPN, but if the VPN goes down for any reason Transmission will continue to send traffic on your normal IP address.

To get around this, we can bind transmission to the local IP address of the VPN interface. To see your current config, type:

ifconfig

Here's an example:

enp3s0    Link encap:Ethernet  HWaddr c0:3f:d5:69:6d:90  
          UP BROADCAST MULTICAST  MTU:1500  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:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

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:65536  Metric:1
          RX packets:118477 errors:0 dropped:0 overruns:0 frame:0
          TX packets:118477 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:1364691847 (1.3 GB)  TX bytes:1364691847 (1.3 GB)

tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  
          inet addr:10.30.1.22  P-t-P:10.30.1.21  Mask:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100 
          RX bytes:0 (0.0 B)  TX bytes:195 (195.0 B)

wlp2s0    Link encap:Ethernet  HWaddr 80:86:f2:bd:91:f6  
          inet addr:192.168.1.2  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::8286:f2ff:febd:91f6/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:9566008 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2290526 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:12721694676 (12.7 GB)  TX bytes:260800582 (260.8 MB)

In this example, there are four interfaces:

  • enp3s0 is the ethernet connection. At the moment it's not connected.
  • lo is the loopback address (localhost)
  • tun0 is the VPN, which has local ip address 10.30.1.22
  • wlp2s0 is the wireless card, which has local ip address 192.168.1.2

What we need to do is run a script every time the VPN client starts, which will stop transmission, update the address it should listen to in /etc/transmission-daemon/settings.json and then restart it again, causing it to bind to the IP address of the VPN interface.

The OpenVPN manual lists a number of events where you can define a script to run (jump to the section called SCRIPTING AND ENVIRONMENTAL VARIABLES). Not all of them are relevant to openvpn in client mode. The ones we care about are:

  • up - executed when openvpn has created the VPN tunnel
  • down - executed after the VPN tunnel is closed
  • ipchange - executed after the connection is first authenticated, or when the remote IP address of the server changes

We have already specified update-resolv-conf to change the nameservers for DNS when the vpn starts and stops.

What's great about these scripts is that they have access to some environment variables set by openvpn. To see what these are, we can edit the update-resolv-conf script and add the following line to the end of the script:

printenv > /etc/openvpn/printenv-updown.log

This will print the environment variables to a file next time the script is called. Restart openvpn to make this happen:

sudo service openvpn restart

If you look in this file you will see some environment variables like:

script_type=up
ifconfig_local=10.30.1.22

You can see in update-resolv-conf that the $script_type variable is used to take certain actions depending on whether the interface is being brought up or taken down. The $ifconfig_local variable contains the local IP of the interface, which is exactly what we need for our script. This is much simpler than other solutions I have seen online, which try to parse the output of ifconfig and chop out all of the unwanted bits - sometimes minor changes in the output of ifconfig can cause such approaches to break.

Open /etc/openvpn/update-resolv-conf again and add the following to the end of the file, which will allow us to write additional scripts without putting all of our changes in that file:

# added to call additional up and down scripts
case "$script_type" in
  up)
        /etc/openvpn/up
        ;;
  down)
        /etc/openvpn/down
        ;;
esac

Now create two new files and make them executable:

sudo touch up down
sudo chmod +x up down

Paste the following into up:

#!/bin/sh

# script to be run when the VPN is brought up

logger "Local IP of VPN interface is $ifconfig_local"

# rebind transmission to the local IP of the VPN interface
# transmission settings.json looks something like:
# {
# ...
#       "bind-address-ipv4": "0.0.0.0",
# ...
# }
# settings.json is overwritten when transmission exits.
# If file is edited and transmission is restarted, the edit is lost because
# transmission dumps the current config to the file when it stops.

logger "stopping transmission"
systemctl stop transmission-daemon
wait 10

logger "replacing bind IP with current local IP of VPN interface"

# the following command could be used if the version of sed installed had the -c option, but it is only included in redhat versions
# see https://stackoverflow.com/questions/2610167/how-to-sed-search-and-replace-without-changing-ownership#2610198
#sed -i -r -c 's/("bind-address-ipv4": ")([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(",)/\1'$ifconfig_local'\3/g' /etc/transmission-daemon/settings.json
# instead execute sed without the -c option and then restore the proper permissions and ownership manually
sed -i -r 's/("bind-address-ipv4": ")([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(",)/\1'$ifconfig_local'\3/g' /etc/transmission-daemon/settings.json

chown debian-transmission:debian-transmission /etc/transmission-daemon/settings.json
chmod 660 /etc/transmission-daemon/settings.json

logger "restarting transmission-daemon"
systemctl restart transmission-daemon

exit 0

And the down script:

#!/bin/sh
# script to be run when the VPN is brought down
logger "stopping transmission"
systemctl stop transmission-daemon

Now let's test. Before reloading anything, have a look and see which IP addresses transmission is currently bound to:

sudo netstat -tulpn | grep transmission

You should see output like the following but without the header, which I've added back in:

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:51413        0.0.0.0:*               LISTEN      19885/transmission-
tcp        0      0 0.0.0.0:9091            0.0.0.0:*               LISTEN      19885/transmission-
tcp6       0      0 :::51413                :::*                    LISTEN      19885/transmission-
udp        0      0 0.0.0.0:51413        0.0.0.0:*                           19885/transmission-

Now restart openvpn:

sudo service openvpn restart

The logger lines in the script should appear in the journal (you can view these as they are written in another SSH window with sudo journalctl -f) and if you run the netstat command again you should see:

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 10.30.1.22:51413        0.0.0.0:*               LISTEN      19885/transmission-
tcp        0      0 0.0.0.0:9091            0.0.0.0:*               LISTEN      19885/transmission-
tcp6       0      0 :::51413                :::*                    LISTEN      19885/transmission-
udp        0      0 10.30.1.22:51413        0.0.0.0:*                           19885/transmission-

So transmission has successfully bound to the IP address of the VPN interface. If you have IPv6 enabled, note that this hasn't stopped transmission from listening on all addresses for IPv6 connections (so you would need to make further changes). Also note that transmission is still listening on all interfaces for the web UI on port 9091 (although access is controlled using the whitelist as mentioned earlier).

There's a useful tool called iftop that will show you a summary of traffic on a given interface. I find it quite useful for checking that all outgoing traffic is on the right interface. You can install it like this:

sudo apt-get update
sudo apt-get install iftop

And then use it to check traffic on the wifi like this:

sudo iftop

Or if you want to see the traffic being passed through the VPN tunnel, specify the interface:

sudo iftop -i tun0

Problems with this section? Let me know in the comments. Otherwise, continue to part 7 - firewall configuration.

Type: 

Add new comment