How to make sure all your network traffic is routed through your VPN?
That’s a question many people can’t easily answer. Typical VPN setups rely on the prioritization of the operating system to route network traffic through the VPN. This setup works but connections from the outside are still coming in and once the connection to the VPN closes, the normal connection is used seamlessly.
This is nice but it defeats the purpose of a VPN for some folks as it might leak data through and because of unsecured connections (see the latest news about the KRACK vulnerability in WPA2).
What can you do about this issue?
There are multiple ways to combat the issue of disconnecting from a VPN: kill-switches that kill specific applications or black-holes that discard any non-VPN traffic.
In this article I will walk you through the steps needed to configure your Linux system to prevent any data being send to any destination that isn’t your VPN.
In order to configure everything to prevent data leakage we need a few things to be setup first:
- A network and internet connection; it doesn’t make sense to secure a connection that is non-existent.
- A keyboard (a mouse is optional).
- Root access on the system in question.
Actually securing the connection and preventing data leaking
The examples here are all done on a Debian 9 system but should be applicable to most Linux distributions.
All the commands assume your primary network connection to be on
eth0 and your VPN to be on
The local network in these examples is on
Installing the necessary packages
So first we install
apt install ufw
That should be the first step done.
There is just one caveat:
ufw is disabled by default because it would prevent incoming connections like ones over SSH. Before we activate
ufw, we have to set up our rules.
Adding the necessary rules to
ufw allows all outgoing traffic, this is great for ordinary use-cases but some applications send out data we don’t want to be routed through the internet before going through our VPN.
ufw also reads rules from the top to the bottom: beginning with rule 1, going through each of them and applying the first rule that matches. This behaviour is inherited from
So to begin, we allow local network traffic, that way we don’t accidentally prevent ourselves from connecting to our system over SSH:
ufw allow in on eth0 from 192.168.0.0/24 ufw allow out on eth0 to 192.168.0.0/24
Now that we can reach our local network, we can enable the firewall and deny outgoing connections that are not explicitly allowed:
ufw enable ufw default deny outgoing
ufw about its status reveals the installed rules:
# ufw status verbose Status: active Logging: on (low) Default: deny (incoming), deny (outgoing), allow (routed) New profiles: skip To Action From -- ------ ---- Anywhere on eth0 ALLOW IN 192.168.0.0/24 192.168.0.0/24 ALLOW OUT Anywhere on eth0
Pinging a known IP like
18.104.22.168 fails spectacularly, just like we configured:
# ping 22.214.171.124 PING 126.96.36.199 (188.8.131.52) 56(84) bytes of data. ping: sendmsg: Operation not permitted ping: sendmsg: Operation not permitted ^C --- 184.108.40.206 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1013ms
Now even our VPN is blocked, so let’s allow our system to connect to our VPN (replace
<VPN IP> with the ip address or network of your VPN):
ufw allow in on eth0 from <VPN IP> ufw allow out on eth0 to <VPN IP>
Connecting to our VPN works fine now, but traffic through the VPN tunnel itself is still blocked by our default rule:
# ip a ... 4: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100 link/none inet 10.7.7.18/24 brd 10.7.7.255 scope global tun0 valid_lft forever preferred_lft forever # ping 220.127.116.11 -I tun0 PING 18.104.22.168 (22.214.171.124) from 10.7.7.18 tun0: 56(84) bytes of data. ping: sendmsg: Operation not permitted ping: sendmsg: Operation not permitted ^C --- 126.96.36.199 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1020ms
So let us allow traffic through the VPN tunnel now:
ufw allow in on tun0 from any ufw allow out on tun0 to any
Verifying our rules
Lastly we verify our rules to make sure that all traffic through our VPN is allowed but traffic through our ordinary connection is restricted to local and VPN traffic only:
# ping 188.8.131.52 -I tun0 PING 184.108.40.206 (220.127.116.11) from 10.7.7.18 tun0: 56(84) bytes of data. 64 bytes from 18.104.22.168: icmp_seq=1 ttl=56 time=49.4 ms 64 bytes from 22.214.171.124: icmp_seq=2 ttl=56 time=48.4 ms 64 bytes from 126.96.36.199: icmp_seq=3 ttl=56 time=49.1 ms ^C --- 188.8.131.52 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003ms rtt min/avg/max/mdev = 48.476/49.017/49.405/0.394 ms # ping 184.108.40.206 -I eth0 PING 220.127.116.11 (18.104.22.168) from 192.168.0.2 eth0: 56(84) bytes of data. ping: sendmsg: Operation not permitted ping: sendmsg: Operation not permitted ^C --- 22.214.171.124 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1002ms # ping <VPN IP> -I eth0 PING <VPN IP> from 192.168.0.2 eth0: 56(84) bytes of data. 64 bytes from <VPN IP>: icmp_seq=1 ttl=54 time=42.7 ms 64 bytes from <VPN IP>: icmp_seq=2 ttl=54 time=41.2 ms 64 bytes from <VPN IP>: icmp_seq=3 ttl=54 time=41.8 ms ^C --- <VPN IP> ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003ms rtt min/avg/max/mdev = 41.275/41.951/42.750/0.630 ms
The steps above are all that is necessary to filter IP packets and deny any stray packets that want to leave on your primary, non-VPN connection but still allow you to communicate with local services like DNS or DHCP and connect to the system using SSH, FTP and more.