10

Hi I am looking for a VPN Monitor/Kill Switch application that will ensure my VPN connection is always connected. Should my secured connection drop, the application will drop the applications that it's monitoring to prevent data leak. I know there are such applications for Windows. However, I have yet to find a suitable alternative for Linux.

1
  • Added an alternative solution (OpenVPN down hook)
    – Marcus
    Apr 2 at 8:52

4 Answers 4

5

There are couple of solutions, depending on the service you want to hook.

OpenVPN device down event

OpenVPN supports a hook for the device down event, that allows running a script.

Just add the option --down <script_name> to the OpenVPN startup command; for example, if it's in a you have a systemd unit:

ExecStart=/usr/sbin/openvpn --down /path/to/kill_switch.sh

The put whatever you wish in the script:

#!/bin/bash

pkill -f transmission
pkill -f deluge

NetworkManager VPN down event

On Ubuntu, the network monitor has callbacks for network events, so that you can write a script to kill the apps you want. Example:

Edit /etc/NetworkManager/dispatcher.d/50vpndownkillapps.sh:

#!/bin/bash

if [[ $1 == "tun0" && $2 == "vpn-down" ]]; then
  pkill -f transmission
  pkill -f deluge
fi

The make it executable: chmod 755 /etc/NetworkManager/dispatcher.d/50vpndownkillapps.sh, and enjoy :-)

It also assumes that the VPN adapter is tun0, which is the standard for OpenVPN configurations.

2
  • 1
    For an unknown reason, ARGV did start with 'tun0' for a long while, and suddenly changed to 'tun1' without notice. So, to keep the kill switch working despite this first (useless) changing value, I had to change the test to if ARGV.last == 'vpn-down'
    – zezollo
    May 22, 2018 at 17:19
  • If this doesn't work, use systemctl status NetworkManager-dispatcher.service to debug it.
    – sezanzeb
    Mar 30 at 20:11
3
+50

I had this same need and I developed my own solution as there seems to be no dedicated tool for this on Linux. There's no need to drop/close opened applications! :)

You need to setup iptables firewall, so your machine can connect ONLY to specified VPN servers (no other traffic allowed, except local, so there will be no "leaks"). Here's a script for that (found it on the web):

#!/bin/bash

# iptables setup on a local pc
# dropping all traffic not going trough vpn
# allowes traffic in local area network
# special rules for UPNP and Multicast discovery

FW="/sbin/iptables"
LCL="192.168.1.0/24"
VPN="10.0.0.0/12"
local_interface="eno1"
virtual_interface="tun0"

# VPN Servers
servers=(
123.123.123.123
124.124.124.124
)

#---------------------------------------------------------------
# Remove old rules and tables
#---------------------------------------------------------------
echo "Deleting old iptables rules..."
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

echo "Setting up new rules..."

#---------------------------------------------------------------
# Default Policy - Drop anything!
#---------------------------------------------------------------
$FW -P INPUT DROP
$FW -P FORWARD DROP
$FW -P OUTPUT DROP

#---------------------------------------------------------------
# Allow all local connections via loopback.
#---------------------------------------------------------------
$FW -A INPUT  -i lo  -j ACCEPT
$FW -A OUTPUT -o lo  -j ACCEPT

#---------------------------------------------------------------
# Allow Multicast for local network.
#---------------------------------------------------------------
$FW -A INPUT  -j ACCEPT -p igmp -s $LCL -d 224.0.0.0/4 -i $local_interface
$FW -A OUTPUT -j ACCEPT -p igmp -s $LCL -d 224.0.0.0/4 -o $local_interface

#---------------------------------------------------------------
# UPnP uses IGMP multicast to find media servers.
# Accept IGMP broadcast packets.
# Send SSDP Packets.
#---------------------------------------------------------------
$FW -A INPUT  -j ACCEPT -p igmp -s $LCL -d 239.0.0.0/8  -i $local_interface
$FW -A OUTPUT -j ACCEPT -p udp  -s $LCL -d 239.255.255.250 --dport 1900  -o $local_interface

#---------------------------------------------------------------
# Allow all bidirectional traffic from your firewall to the
# local area network
#---------------------------------------------------------------
$FW -A INPUT  -j ACCEPT -s $LCL -i $local_interface
$FW -A OUTPUT -j ACCEPT -d $LCL -o $local_interface

#---------------------------------------------------------------
# Allow all bidirectional traffic from your firewall to the
# virtual privat network
#---------------------------------------------------------------

$FW -A INPUT  -j ACCEPT -i $virtual_interface
$FW -A OUTPUT -j ACCEPT -o $virtual_interface

#---------------------------------------------------------------
# Connection to VPN servers (UDP 443)
#---------------------------------------------------------------
server_count=${#servers[@]}
for (( c = 0; c < $server_count; c++ ))
do
    $FW -A INPUT  -j ACCEPT -p udp -s ${servers[c]} --sport 1194 -i $local_interface
    $FW -A OUTPUT -j ACCEPT -p udp -d ${servers[c]} --dport 1194 -o $local_interface
    $FW -A INPUT  -j ACCEPT -p tcp -s ${servers[c]} --sport 443 -i $local_interface
    $FW -A OUTPUT -j ACCEPT -p tcp -d ${servers[c]} --dport 443 -o $local_interface
done

#---------------------------------------------------------------
# Log all dropped packages, debug only.
# View in /var/log/syslog or /var/log/messages
#---------------------------------------------------------------
#iptables -N logging
#iptables -A INPUT -j logging
#iptables -A OUTPUT -j logging
#iptables -A logging -m limit --limit 2/min -j LOG --log-prefix "IPTables general: " --log-level 7
#iptables -A logging -j DROP


# Disable internet for "no-internet" user
#iptables -A OUTPUT -m owner --gid-owner no-internet -j DROP

You will need to setup table servers=(). Just specify there IP's of your favourite VPN servers.

Also check that other variables at the beginning of the script are set properly, otherwise it will block your entire connection.

Be sure to make iptables backup with:

sudo iptables-save > working.iptables.rules

(restore with sudo iptables-restore < working.iptables.rules)

It supports TCP and UDP connections, if you need only one of those, remove unwanted two lines from for () loop. Also check if your provider is using same ports - might be different.

Run this script with f.e. sudo /home/user/vpn.sh.

If you want to load it on boot (iptables usually resets after re-boot), add to your /etc/rc.local file f.e. line like bash /home/user/vpn.sh.


Next part is VPN auto-connector & monitor. Here's my own contraption for this:

#!/bin/bash

# CONNECTIONS
# Those values can be checked by running `nmcli con show`

vpn=(
85e60352-9e93-4be4-8b80-f6aae28d3c94
)

# NUMBER OF CONNECTIONS
total=${#vpn[@]}

# SLEEP
amount=10 # number of seconds to wait after each connection checking cycle
countdown=true # enable/disable animated countdown
skip=1 # how many seconds to substract between each animated countdown iteration

# LOGS
dir='/home/user/logs-vpn' # directory for storing logs
name='vpn' # prefix/name for a log file
seperate=true # create a seperate log file for each init session or log to single file
init=false # log init event (with logging setup)
start=false # log vpn start event
yes=false # log connected events
no=false # log disconnected events

# STYLE
clean='\e[1A\033[K' # clean & move to previous line
default='\e[0m' # default
blink='\e[5m' # blinking (works only in couple terminals, e.g. XTerm or tty)
dim='\e[2m' # dim/half-bright
disconnected='\e[91m' # light red
connected='\e[92m' # light green
count='\e[94m' # light blue
reconnecting='\e[96m' # light cyan
initializing='\e[93m' # light yellow
connection='\e[1m\e[91m' # bold light red

# SETUP
time=$(date +"%Y-%m-%d_%H-%M-%S")
if $separate; then
    file="$dir/$time.log"
else
    file="$dir/$name.log"
fi

# RESET
reset # reset screen
tput civis -- invisible # disable cursor

# RE-TIME
time=$(date +"%Y.%m.%d %H:%M:%S")

# INITIALIZATION
if $init; then
    printf "$time INIT" >> $file
    if $yes; then
        printf " -y" >> $file
    fi
    if $no; then
        printf " -n" >> $file
    fi
    printf "\n" >> $file
fi

# START CONNECTION
con=$(nmcli con show --active | grep "  vpn")
if [[ $con == '' ]]; then

    if $start; then
        printf "$time START\n" >> $file
    fi

    time=$(date +"%H:%M:%S")
    echo -e "${dim}[$time]${default} ${initializing}INITIALIZING...${default}"
    echo ""
    echo ""

    random=$(((RANDOM % $total)-1))
    try=${vpn[$random]}

    (sleep 1s && nmcli con up uuid $try) >& /dev/null
    sleep 10s
fi

# LOOP
while [ "true" ]; do
        time=$(date +"%H:%M:%S")

        # CLEAN AFTER COUNTDOWN
        if $countdown; then
            echo -en $clean
            echo -en $clean
        fi

        # CHECK CONNECTION
        con=$(nmcli con show --active | grep "  vpn" | cut -f1 -d " ")

        if [[ $con == '' ]]; then
                if $no; then
                    printf "$time NO\n" >> $file
                fi
                echo -e "${dim}[$time]${default} ${disconnected}DISCONNECTED !!${default}"
                echo -e "${blink}${reconnecting}re-connecting ...${default}"

                random=$(((RANDOM % $total)-1))
                try=${vpn[$random]}

                (sleep 1s && nmcli con up uuid $try) >& /dev/null
        else
                if $yes; then
                    printf "$time YES\n" >> $file
                fi

                arr=(${con//./ })

                echo -en $clean
                echo -e "${dim}[$time]${default} ${connected}CONNECTED${default} (${connection}${arr[0]^^}${default})"
        fi

        # SLEEP
        if $countdown; then
            echo -e "${count}$amount${default}"
            for (( c=$amount; c>=1; c=c-$skip )); do
                echo -en $clean
                echo -e "${count}$c${default}"
                sleep $skip
            done
            echo -e "${count}0${default}"
        else
            sleep $amount
        fi
done

It will auto-connect on start and monitor your connection with given interval (amount=10 gives 10 seconds interval) and re-connect on connection lost. Got logging feature and some other options.

Check your connections UUID's using nmcli con show and add your favourites (matching with IP's added to firewall) to vpn=() table. Everytime it will randomly select a connection specified in this table.

You can add it to your auto-start (does not need sudo priviledge). Here's an example how to start it in terminal:

mate-terminal --command="/home/user/vpn-reconnect.sh"

...and here's how it looks running in terminal:

enter image description here

...and here's how a leak-proof ping looks like after your VPN connection drops:

enter image description here

Enjoy :)

3
  • 1
    Regarding loading the script at boot, why won't you simply use /etc/rc.local? Dec 28, 2016 at 10:47
  • Beautiful idea (works like charm!), thank you :)
    – GreggD
    Dec 28, 2016 at 10:56
  • This is awesome, thank you so much. Verified to still be working as of July 2017.
    – Norr
    Jul 15, 2017 at 2:13
2

I've been able to setup a simple VPN kill switch with UFW. It works with all the vpn's i have.

Here's my ufw settings:

sudo ufw default deny outgoing
sudo ufw default deny incoming`
sudo ufw allow out 443/tcp
sudo ufw allow out 1194/udp
sudo ufw allow out on tun0 from any to any port 80
sudo ufw allow out on tun0 from any to any port 53
sudo ufw allow out on tun0 from any to any port 67
sudo ufw allow out on tun0 from any to any port 68

Works for me just fine :)

1
  • Looks OK, but I guess sudo ufw allow out 443/tcp permits secure website leakage when the VPN is not connected. Wouldn't you want to stop that? An HTTPS site with AJAX or WebSockets might re-connect in the background on its own, perhaps via a JavaScript timer.
    – halfer
    Dec 11, 2017 at 11:27
0

I solved this problem by setting up Ufw to block all outgoing traffic, and then whitelisting all VPN nodes by referencing their individual IP addresses. This is not as onerous as it sounds: VPNs in my experience allow the use of DNS lookup to get their various IP addresses.

I have written a PHP program to do this, called ufw-vpn. I've used it for a couple of years, with various small enhancements made over time. You'll need PHP installed of course, and Git if you wish to clone it rather than download it.

You can also grab it using wget:

cd /path/to/a/folder
wget https://github.com/halfer/ufw-vpn/archive/master.zip
unzip master.zip
cd ufw-vpn-master

Then run the command to check it looks OK (without parameters just renders a syntax message):

php ufw-vpn.php

Now, assuming your VPN supports it, you can use a fully-qualified domain to get a list of servers for a region (you will need to find this in your provider's documentation, or perhaps from their support department):

php ufw-vpn.php earth.all.vpn.example.org add

That should give you a big list of firewall rules to add. To install them easily, you can just do this:

php ufw-vpn.php earth.all.vpn.example.org add > add-rules.sh
chmod u+x add-rules.sh && sudo add-rules.sh

From time to time VPN providers will update their IP addresses, so you'll need to update yours to match. You can do so via a diff:

php ufw-vpn.php earth.all.vpn.example.org diff > diff-rules.sh
chmod u+x diff-rules.sh && sudo diff-rules.sh

For a diff, it is worth checking the rules before executing that, as it will delete anything not belonging to the VPN. So, if you have some custom rules, they will need to be taken out before running.

More docs are available at the repo, and it's all open source, so you can check the code for security issues. Bug reports and feature suggestions are very welcome.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .