Routing All Traffic Over a VPN Under Linux

I’ve recently been setting up a machine that has to send all data over a VPN which feels like it should be really simple but has actually turned out to be tougher than I expected. I thought I could set it up by using routing and I think it’s probably possible but way more hassle than I was willing to put up with. The idea was to remove the default route through the real interface, I’ll call it eth0, and set up a default route through the VPN interface, I’ll call it tun0. The problem with this is the communication necessary to set up the VPN requires communication through the default route of eth0.

This causes a catch-22 situation, you need to communicate over the non-VPN interface, eth0, and you also mustn’t communicate over it. This can be solved by removing the default route over eth0 and then adding a route to just your VPN server so that the connection can be established. Unfortunately this turned up another problem. It seems that Network Manager won’t start a VPN connection unless it has a valid default route over eth0. I tried a range of different configurations but I never managed to get it to work. The closest I got was setting up the default route for eth0 to point at localhost thus losing all the packets. It moved the problem in Network Manager but I gave up at that point and looked at using the firewall instead.

Using the Firewall to Filter Traffic

The basic idea with the firewall filtering is the same as the routing. If the packet is outbound it must got over the VPN (tun0) unless it’s establishing the VPN itself in which case it goes out over the normal interface (eth0). The following script will achieve this using UFW (Uncomplicated Firewall). The only thing that might need changing is the port number at the top but this is the standard port for OpenVPN.

The script must be run as root and once installed the rules will be applied automatically at boot time.

#!/bin/bash
 
# Set this to the VPN port. OpenVPN usually uses port 1194.
VPN_DST_PORT=1194
 
# Check to see if the script is running as root. It needs to be
# run as root to update the firewall.
if [[ $EUID -ne 0 ]]; then
   printf "Please run as root:\nsudo %s\n" "${0}" 
   exit 1
fi
 
# Reset the ufw config
ufw --force reset
         
# By default block all incoming and outgoing traffic
ufw default deny incoming
ufw default deny outgoing
 
# Outbound communiction via VPN is allowed
ufw allow out on tun0
 
# Allow connection to the VPN port. This is needed to set up the VPN. If the
# destination IP is also known and fixed this could be limited further. Using
# just the port is a good compromise for most commercial VPN services.
ufw allow out $VPN_DST_PORT

# Allow DNS queries.
# Caution, if the VPN goes down this will allow DNS queries to leak out
# since it allows DNS queries out over non-VPN interfaces. This rule isn't
# absolutely needed.
#ufw allow out 53
 
# Allow connections to local IPv4 networks. 
# Comment in the ranges used.
#ufw allow out to 10.0.0.0/8
#ufw allow out to 172.16.0.0/12
ufw allow out to 192.168.0.0/16
# Allow IPv4 local multicasts
ufw allow out to 224.0.0.0/24
ufw allow out to 239.0.0.0/8
 
# Allow local IPv6 connections
#ufw allow out to fe80::/64
# Allow IPv6 link-local multicasts
#ufw allow out to ff01::/16
# Allow IPv6 site-local multicasts
#ufw allow out to ff02::/16
#ufw allow out to ff05::/16
 
# Enable the firewall
ufw enable

This script is loosely based on the script found here also see here.

Other Reading