Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security

Resilient High Availability VPN with Kill Switch in OpenWRT Routers

0.00/5 (No votes)
16 Jul 2023CPOL8 min read 3.4K  
An algorithm and a set of scripts which work in a closed loop on an OpenWRT router that enable resilient and high availability VPN (openvpn) connection for all connected router clients
This article describes a script based algorithm and implementation for creating a kill switch enabled VPN connection that is resilient to VPN disconnections and non responding VPN servers on OpenWRT routers. This will result in high availability VPN connection to all router clients, and will avoid data leaks to regular network during the automatic reconnect attempts.

Introduction

This article is my conclusion of findings in search for configuration for OpenWRT routers that will enable VPN for all devices connected to it. I have written two other articles before on the same topic, but described in this article is the ultimate solution that enables kill switch when VPN is connecting or disabled, and also ensures VPN tunnel is auto restored when interrupted or inactive, using ping checks to any host of choice. Below is the algorithm diagram of what the scripts described in this article accomplish.

Resilient VPN Algorithm Flow Chart

Image 1

As shown in the above diagram, the scripts run in a closed loop, trying to keep the VPN tunnel alive and running, so that router clients will have a highly available VPN connection, without intervention requiring manual re-connection or reboot. Script file names are given in the diagram blocks for easy reference.

Kill Switch Flow Chart

Image 2

The kill switch flow chart is given above, and it ensures kill switch remains active even when interfaces are reconfigured or restarted.

Perquisite - Setting Up Router

First thing you need is a freshly flashed or re-setted OpenWRT router with OpenWRT version at or above 19.xx. You need to setup LAN and WIFI interfaces using configuration below. If you are not familiar with initial setup of OpenWRT routers, please refer to 'Setting up OpenWRT' section in this article.

Network Configuration

Make sure the network configuration file at /etc/config/network contains the following entries. Bolded lines are new.

config interface 'lan'
	option device 'br-lan'
	option proto 'static'
	option netmask '255.255.255.0'
	option ipaddr '192.168.3.1'
	list dns '208.67.222.222'
	list dns '208.67.220.220'
	
config interface 'wan'
	option device 'wan'
	option proto 'dhcp'
	option peerdns '0'

config interface 'ovpn'
	option proto 'none'
	option device 'tun0'
	option peerdns '0'
	
config interface 'wifi24'
	option proto 'static'
	option netmask '255.255.255.0'
	list dns '208.67.222.222'
	list dns '208.67.220.220'
	option device 'wlan1'
	option ipaddr '192.168.10.1'

config interface 'wifi50'
	option proto 'static'
	option netmask '255.255.255.0'
	option device 'wlan0'
	list dns '8.8.8.8'
	list dns '8.8.4.4'
	option ipaddr '192.168.11.1'

Make sure the DHCP configuration file at /etc/config/dhcp contains the following entries. Bolded lines are new.

config dhcp 'lan'
	option interface 'lan'
	option start '100'
	option limit '150'
	option leasetime '12h'
	option dhcpv4 'server'
	list dhcp_option '6,208.67.222.222,208.67.220.220'
	
config dhcp 'wifi24'
	option interface 'wifi24'
	option start '100'
	option limit '150'
	option leasetime '12h'
	list dhcp_option '6,208.67.222.222,208.67.220.220'

config dhcp 'wifi50'
	option interface 'wifi50'
	option start '100'
	option limit '150'
	option leasetime '12h'
	list dhcp_option '6,208.67.222.222,208.67.220.220'

Make sure the firewall configuration file at /etc/config/firewall contains the following entries. Bolded lines are new.

config zone
	option name 'lan'
	option input 'ACCEPT'
	option output 'ACCEPT'
	option forward 'ACCEPT'
	list network 'lan'
	list network 'wifi24'
	list network 'wifi50'

config zone
	option name 'wan'
	option input 'REJECT'
	option output 'ACCEPT'
	option forward 'REJECT'
	option masq '1'
	option mtu_fix '1'
	list network 'wan'
	list network 'ovpn'

Enable WiFi

Steps to enable WiFi are given here.

After this initial step, reboot the router and test the internet connectivity on LAN and WiFi clients of the router for sanity. Interface IP address (example: 192.168.11.1) in configuration files above are arbitrary, but are used again in the kill switch scripts, hence make appropriate changes to addresses there as well if needed. This article also assumes three client interfaces lan, wlan0 and wlan1, omissions or additions can be made accordingly.

Install Packages

Two packages are needed for the scripts to work, run the below commands to install those.

opkg update
opkg install openvpn-openssl pingcheck

After the above commands are complete, remove all lines from /etc/config/pingcheck configuration file to avoid accidental script calls. This configuration file will be populated again in later steps.

Folder Structure of all Scripts

Below is the folder structure for all the files to be created on the router. Each file content is described in sections below. Make sure all these files are set as executable after creation.

Image 3

Step 1: Kill Switch Scripts Setup

A single hot plug script described in this section will activate kill switch for all configured client interfaces. The method used to achieve this is to use a separate custom routing table for each interface on which routes will be added or deleted dynamically by scripts described in later sections. This ensures traffic from the interfaces flows only through a working VPN tunnel.

Add New Routing Tables

Add the following lines to add three new routing tables, at /etc/iproute2/rt_tables. Bolded lines are new. This created three new routing tables which will be associated with the three client interfaces.

#
# reserved values
#
128     prelocal
255     local
254     main
253     default
40      custom_lan
39      custom_wlan0
38      custom_wlan1
0       unspec
#
# local
#

Add Kill Switch Helper Scripts

Below is the folder structure for the kill switch helper scripts and contents of the scripts. Create them in the same locations and set them as executable.

Image 4

activate-kill-switch-for-interface.sh

Create file /etc/openvpn/kill-switch/activate-kill-switch-for-interface.sh with the following content:

Bash
#!/bin/sh

interface_cidr=$1
interface_name=$2
interface_gateway=$3
table_name=$4

ip route flush $interface_cidr
ip rule add from $interface_cidr lookup $table_name
ip rule add from all to $interface_cidr lookup $table_name
ip route add $interface_cidr dev $interface_name scope link src $interface_gateway table $table_name
ip route add default via $interface_gateway table $table_name

ip route flush cache

kill-switch-setup-lan.sh

Create file /etc/openvpn/kill-switch/kill-switch-setup-lan.sh with the following content:

Bash
#!/bin/sh

/etc/openvpn/kill-switch/activate-kill-switch-for-interface.sh 192.168.3.0/24 br-lan 192.168.3.1 custom_lan

kill-switch-setup-wlan0.sh

Create file /etc/openvpn/kill-switch/kill-switch-setup-wlan0.sh with the following content:

Bash
#!/bin/sh

/etc/openvpn/kill-switch/activate-kill-switch-for-interface.sh 192.168.11.0/24 wlan0 192.168.11.1 custom_wlan0

kill-switch-setup-wlan1.sh

Create file /etc/openvpn/kill-switch/kill-switch-setup-wlan1.sh with the following content:

Bash
#!/bin/sh

/etc/openvpn/kill-switch/activate-kill-switch-for-interface.sh 192.168.10.0/24 wlan1 192.168.10.1 custom_wlan1

Add Hot-Plug Script

A hot-plug script will watch interfaces and modify routes in routing tables with the help of the scripts above, to ensure the interfaces do not use the main routing table and the regular internet. You can read about OpenWRT hot-plug scripts here.

99-ifup-wan-interfaces

Create file /etc/hotplug.d/iface/99-wan-interfaces with the following content:

Bash
#!/bin/sh

wanstateret=`cat /tmp/wanstate`
lanstateret=`cat /tmp/lanstate`
wlan0stateret=`cat /tmp/wlan0state`
wlan1stateret=`cat /tmp/wlan1state`

wanstarted=`echo "$wanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
lanstarted=`echo "$lanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
wlan0started=`echo "$wlan0stateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
wlan1started=`echo "$wlan1stateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`

killswitchlanstateret=`cat /tmp/killswitchlanstate`
killswitchwlan0stateret=`cat /tmp/killswitchwlan0state`
killswitchwlan1stateret=`cat /tmp/killswitchwlan1state`

killswitchlanstarted=`echo "$killswitchlanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
killswitchwlan0started=`echo "$killswitchwlan0stateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
killswitchwlan1started=`echo "$killswitchwlan1stateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`

activatelankillswitch=0
activatewlan0killswitch=0
activatewlan1killswitch=0

if [ "${ACTION}" == "ifdown" ] && [ "${INTERFACE}" = "lan" ]
then
    rm /tmp/killswitchlanstate
fi

if [ "${ACTION}" == "ifdown" ] && [ "${INTERFACE}" = "wifi50" ]
then
    rm /tmp/killswitchwlan0state
fi

if [ "${ACTION}" == "ifdown" ] && [ "${INTERFACE}" = "wifi24" ]
then
    rm /tmp/killswitchwlan1state
fi

if [ "${ACTION}" == "ifup" ] && [ "${DEVICE}" = "wan" ]
then
    echo started > /tmp/wanstate
    if [ $lanstarted -eq 1 ] && [ $killswitchlanstarted -eq 0 ]
    then
        activatelankillswitch=1
    fi
    if [ $wlan0started -eq 1 ] && [ $killswitchwlan0started -eq 0 ]
    then
        activatewlan0killswitch=1
    fi
    if [ $wlan1started -eq 1 ] && [ $killswitchwlan1started -eq 0 ]
    then
        activatewlan1killswitch=1
    fi
fi

if [ "${ACTION}" == "ifup" ] && [ "${DEVICE}" = "br-lan" ]
then
    echo started > /tmp/lanstate
    if [ $wanstarted -eq 1 ] && [ $killswitchlanstarted -eq 0 ]
    then
        activatelankillswitch=1
    fi
fi

if [ "${ACTION}" == "ifup" ] && [ "${DEVICE}" = "wlan0" ]
then
    echo started > /tmp/wlan0state
    if [ $wanstarted -eq 1 ] && [ $killswitchwlan0started -eq 0 ]
    then
        activatewlan0killswitch=1
    fi
fi

if [ "${ACTION}" == "ifup" ] && [ "${DEVICE}" = "wlan1" ]
then
    echo started > /tmp/wlan1state
    if [ $wanstarted -eq 1 ] && [ $killswitchwlan1started -eq 0 ]
    then
        activatewlan1killswitch=1
    fi
fi

if [ $activatelankillswitch -eq 1 ]
then
    echo started > /tmp/killswitchlanstate
    /etc/openvpn/kill-switch/kill-switch-setup-lan.sh
fi

if [ $activatewlan0killswitch -eq 1 ]
then
    echo started > /tmp/killswitchwlan0state
    /etc/openvpn/kill-switch/kill-switch-setup-wlan0.sh
fi

if [ $activatewlan1killswitch -eq 1 ]
then
    echo started > /tmp/killswitchwlan1state
    /etc/openvpn/kill-switch/kill-switch-setup-wlan1.sh
fi

exit 0

After creating this hot-plug script, internet for connected clients will be disabled on reboot and interface restarts, so do not panic. Internet will start working when rest of the setup is done and VPN connection is successful.

Step 2: Resilient VPN Scripts Setup

Add Resilient Folder Scripts

Below is the folder structure for the resilient VPN scripts and contents of the scripts. Create them in the same locations and set them as executable.

Image 5

check-vpn-connection.sh

Create file /etc/openvpn/resilient/check-vpn-connection.sh with the following content:

Bash
#!/bin/sh

/etc/openvpn/leds/init-complete-led.sh off
/etc/openvpn/leds/connecting-led.sh on

connectedstatus=0
ovpnconnectedstatus=0

i=0
while [ $i -le 30 ]
do
    sleep 1
    i=`expr $i + 1`
    if grep "Initialization Sequence Completed" /tmp/openvpn-main-log; then
        connectedstatus=1
        break;
    fi
done

/etc/openvpn/leds/connecting-led.sh off

if [ $connectedstatus -eq 1 ]
then
    /etc/openvpn/leds/init-complete-led.sh on
    # check pingcheck status for 40 seconds
    i=0
    while [ $i -le 40 ]
    do
        sleep 1
        i=`expr $i + 1
        ovpnstateret=`cat /tmp/pingcheck-ovpnstate`
        ovpnonline=`echo "$ovpnstateret" online | awk '{ print ($1 == $2) ? 1 : 0 }'`
        if [ $ovpnonline -eq 1 ]
        then
            ovpnconnectedstatus=1
            break;
        fi
    done
fi

if [ $connectedstatus -eq 0 ] || [ $ovpnconnectedstatus -eq 0 ]
then
    sh /etc/openvpn/resilient/kill-vpn.sh
fi

kill-vpn.sh

Create file /etc/openvpn/resilient/kill-vpn.sh with the following content:

Bash
#!/bin/sh
openvpnpid=$(pidof openvpn)
kill $openvpnpid
echo "Killed openvpn"

resilient-vpn-pingcheck-controller.sh

Create file /etc/openvpn/resilient/resilient-vpn-pingcheck-controller.sh with the following content:

Bash
#!/bin/sh

wanstateret=`cat /tmp/pingcheck-wanstate`
ovpnstateret=`cat /tmp/pingcheck-ovpnstate`

wanonline=`echo "$wanstateret" online | awk '{ print ($1 == $2) ? 1 : 0 }'`
ovpnonline=`echo "$ovpnstateret" online | awk '{ print ($1 == $2) ? 1 : 0 }'`

resilientrunningret=`cat /tmp/resilientrunningstatus`
resilientrunningstatus=`echo "$resilientrunningret" running | awk '{ print ($1 == $2) ? 1 : 0 }'`

if [ $wanonline -eq 1 ]
then
    if [ $ovpnonline -eq 0 ] 
    then
        /etc/openvpn/leds/connected-led.sh off
        if [ $resilientrunningstatus -eq 0 ]
        then
            (sh /etc/openvpn/resilient/start-resilient-vpn.sh >/dev/null 2>&1 )&
            echo running > /tmp/resilientrunningstatus
        fi
        if [ $resilientrunningstatus -eq 1 ]
        then
            sh /etc/openvpn/resilient/kill-vpn.sh
        fi
    fi
    if [ $ovpnonline -eq 1 ] 
    then
        /etc/openvpn/leds/init-complete-led.sh off
        /etc/openvpn/leds/connected-led.sh on
    fi
fi
if [ $wanonline -eq 0 ]
then
    if [ $ovpnonline -eq 1 ] 
    then
        /etc/openvpn/leds/init-complete-led.sh off
        /etc/openvpn/leds/connected-led.sh on
    fi
    if [ $ovpnonline -eq 0 ] 
    then
        sh /etc/openvpn/resilient/stop-resilient-vpn.sh
        echo stopped > /tmp/resilientrunningstatus
    fi
fi

start-openvpn-client.sh

Create file /etc/openvpn/resilient/start-openvpn-client.sh with the following content:

dir='/etc/openvpn/configs'
n_files=`/bin/ls -1 "$dir" | wc -l | cut -f1`
rand_num=`awk "BEGIN{srand();print int($n_files * rand()) + 1;}"`
file=`/bin/ls -1 "$dir" | sed -ne "${rand_num}p"`
path=`cd $dir && echo "$PWD/$file"` # Converts to full path.
echo "Chosen file ${path}"
echo "${path}" > /tmp/openvpn-server.log
rm /tmp/openvpn-main-log
openvpn --config ${path} --log /tmp/openvpn-main-log 
        --auth-user-pass /etc/openvpn/credentials --up /etc/openvpn/up.sh 
        --down-pre --down /etc/openvpn/down.sh --route-noexec --dev tun0 
        --persist-local-ip --script-security 2

start-resilient-vpn.sh

Create file /etc/openvpn/resilient/start-resilient-vpn.sh with the following content:

Bash
#!/bin/sh

rm /tmp/openvpn-main-log
(sh /etc/openvpn/resilient/check-vpn-connection.sh >/dev/null 2>&1 )&
sh /etc/openvpn/resilient/start-openvpn-client.sh

checkscriptpid=$(pgrep -f "sh /etc/openvpn/resilient/check-vpn-connection.sh")
kill $checkscriptpid
/etc/openvpn/leds/connecting-led.sh off

if grep "AUTH_FAILED" /tmp/openvpn-main-log; then
    # Wait for some time to stop overloading  
    /etc/openvpn/leds/auth-failed-led.sh on
    pause_connect_seconds=60
    sleep $pause_connect_seconds
    /etc/openvpn/leds/auth-failed-led.sh off
fi

exec sh /etc/openvpn/resilient/start-resilient-vpn.sh

stop-resilient-vpn.sh

Create file /etc/openvpn/resilient/stop-resilient-vpn.sh with the following content:

Bash
#!/bin/sh

resilientscriptpid=$(pgrep -f "sh /etc/openvpn/resilient/start-resilient-vpn.sh")
kill $resilientscriptpid
checkscriptpid=$(pgrep -f "sh /etc/openvpn/resilient/check-vpn-connection.sh")
kill $checkscriptpid
sh /etc/openvpn/resilient/kill-vpn.sh
openvpnscriptpid=$(pgrep -f "sh /etc/openvpn/resilient/start-openvpn-client.sh")
kill $openvpnscriptpid
rm /tmp/openvpn-main-log
sh /etc/openvpn/resilient/kill-vpn.sh
/etc/openvpn/leds/connected-led.sh off
/etc/openvpn/leds/connecting-led.sh off
/etc/openvpn/leds/init-complete-led.sh off

Add Route Creating Scripts

These scripts are called by the openvpn command and up.sh create routes in the custom routing tables to enable VPN internet access to interfaces when running. down.sh removes the routes.

Image 6

up.sh

Create file /etc/openvpn/up.sh with the following content.

Bash
#!/bin/sh

wanstrifconfig=$(ip -4 -o addr show wan)
wan_cidr=$(echo $wanstrifconfig | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\/[0-9]\{1,\}')

wan_interface_name=wan

vpn_gateway=$route_vpn_gateway
vpn_local=$ifconfig_local
vpn_mask=$ifconfig_netmask
remote_ip=$trusted_ip
device_name=$dev
router_gateway=$route_net_gateway

vpn_interface_cidr=`awk -v val="$vpn_gateway|$vpn_mask" '
function count1s(N){
    c = 0
    for(i=0; i<8; ++i) if(and(2**i, N)) ++c
    return c
}
function subnetmaskToPrefix(input) {
    split(input, inputParts, "|")
    split(inputParts[2], subnetParts, ".")
    split(inputParts[1], mainParts, ".")
  
  if (subnetParts[1] == 0 ) {
        mainParts[1] = 0
    }
    if (subnetParts[2] == 0 ) {
        mainParts[2] = 0
    }
    if (subnetParts[3] == 0 ) {
        mainParts[3] = 0
    }
    if (subnetParts[4] == 0 ) {
        mainParts[4] = 0
    }

     printf "%d.%d.%d.%d/%d", mainParts[1], mainParts[2], 
     mainParts[3], mainParts[4], count1s(subnetParts[1]) + count1s 
}
BEGIN {
    subnetmaskToPrefix(val)
}'`

for vpn_table_name in custom_lan custom_wlan0 custom_wlan1 ; do
    ip route add 0.0.0.0/1 via $vpn_gateway dev $device_name table $vpn_table_name
    ip route add 128.0.0.0/1 via $vpn_gateway dev $device_name table $vpn_table_name
    ip route add $vpn_interface_cidr dev $device_name scope link src 
                 $vpn_local table $vpn_table_name
    ip route add $remote_ip via $router_gateway dev wan table $vpn_table_name
    ip route add $wan_cidr dev $wan_interface_name table $vpn_table_name
done

ip route flush cache

down.sh

Create file /etc/openvpn/down.sh with the following content:

Bash
#!/bin/sh

wanstrifconfig=$(ip -4 -o addr show wan)
wan_cidr=$(echo $wanstrifconfig | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\/[0-9]\{1,\}')

wan_interface_name=wan

vpn_gateway=$route_vpn_gateway
vpn_local=$ifconfig_local
vpn_mask=$ifconfig_netmask
remote_ip=$trusted_ip
device_name=$dev
router_gateway=$route_net_gateway

vpn_interface_cidr=`awk -v val="$vpn_gateway|$vpn_mask" '
function count1s(N){
    c = 0
    for(i=0; i<8; ++i) if(and(2**i, N)) ++c
    return c
}
function subnetmaskToPrefix(input) {
    split(input, inputParts, "|")
    split(inputParts[2], subnetParts, ".")
    split(inputParts[1], mainParts, ".")
  
  if (subnetParts[1] == 0 ) {
        mainParts[1] = 0
    }
    if (subnetParts[2] == 0 ) {
        mainParts[2] = 0
    }
    if (subnetParts[3] == 0 ) {
        mainParts[3] = 0
    }
    if (subnetParts[4] == 0 ) {
        mainParts[4] = 0
    }

     printf "%d.%d.%d.%d/%d", mainParts[1], mainParts[2], 
             mainParts[3], mainParts[4], count1s(subnetParts[1]) + count1s
}
BEGIN {
    subnetmaskToPrefix(val)
}'`

for vpn_table_name in custom_lan custom_wlan0 custom_wlan1 ; do
    ip route flush 0.0.0.0/1 via $vpn_gateway dev $device_name table $vpn_table_name
    ip route flush 128.0.0.0/1 via $vpn_gateway dev $device_name table $vpn_table_name
    ip route flush $vpn_interface_cidr dev $device_name scope link 
               src $vpn_local table $vpn_table_name
    ip route flush $remote_ip via $router_gateway dev wan table $vpn_table_name
    ip route flush $wan_cidr dev $wan_interface_name table $vpn_table_name
done

ip route flush cache

Add LED Control Helper Scripts

Tunnel status and connection status of the scripts can displayed on hardware LEDs which are usually available on OpenWRT routers so that a quick feedback of the tunnel status can be obtained without needing to access the terminal. The following helper scripts are used by other scripts and should be modified according to LEDs available on the router model. You can read about LED control on OpenWRT routers here. You can see the LEDs available on your router using the below command:

ls /sys/class/leds/

Below is the folder structure for the led control scripts and contents of the scripts. Create them in the same locations and set them as executable.

Image 7

auth-failed-led.sh

Create file /etc/openvpn/leds/auth-failed-led.sh with the following content:

Bash
#!/bin/sh

status=$1
if [ "${status}" == "on" ]
then
    echo timer > /sys/class/leds/{your_custom_auth_failed_led}/trigger
fi
if [ "${status}" == "off" ]
then
    echo none > /sys/class/leds/{your_custom_auth_failed_led}/trigger
fi

connected-led.sh

Create file /etc/openvpn/leds/connected-led.sh with the following content:

Bash
#!/bin/sh

status=$1
if [ "${status}" == "on" ]
then
    echo default-on > /sys/class/leds/{your_custom_connected_led}/trigger
fi
if [ "${status}" == "off" ]
then
    echo none > /sys/class/leds/{your_custom_connected_led}/trigger
fi

connecting-led.sh

Create file /etc/openvpn/leds/connecting-led.sh with the following content:

Bash
#!/bin/sh

status=$1
if [ "${status}" == "on" ]
then
    echo timer > /sys/class/leds/{your_custom_connecting_led}/trigger
fi
if [ "${status}" == "off" ]
then
    echo none > /sys/class/leds/{your_custom_connecting_led}/trigger
fi

inactive-waiting-led.sh

Create file /etc/openvpn/leds/inactive-waiting-led.sh with the following content:

Bash
#!/bin/sh

status=$1
if [ "${status}" == "on" ]
then
    echo timer > /sys/class/leds/{your_custom_inactive_led}/trigger
fi
if [ "${status}" == "off" ]
then
    echo none > /sys/class/leds/{your_custom_inactive_led}/trigger
fi

init-complete-led.sh

Create file /etc/openvpn/leds/init-complete-led.sh with the following content:

Bash
#!/bin/sh

status=$1
if [ "${status}" == "on" ]
then
    echo default-on > /sys/class/leds/{your_custom_complete_led}/trigger
fi
if [ "${status}" == "off" ]
then
    echo none > /sys/class/leds/{your_custom_complete_led}/trigger
fi

Step 3: VPN Provider Files Setup

The configs folder needs to be populated with *.ovpn configuration files from VPN provider and the credentials file with the authentication details, example given below.

Image 8

credentials

Create file /etc/openvpn/credentials with the following contents:

{your_vpn_username}
{your_vpn_password}

Testing VPN Provider

You can run the following command manually in terminal to test your VPN config files and credentials.

sh /etc/openvpn/resilient/start-openvpn-client.sh

In a different terminal, you can check the logs from the above script using the command below:

cat /etc/tmp/openvpn-main-log

If the VPN provider is working with the given ovpn configuration file and credentials, it will show up in the logs.

Step 4: Ping Checker Scripts Setup

If you have going through resilient controller script, it controls the VPN connection based on statuses which are setup by ping checker scripts. Read about pingcheck in the link here. The ping check scripts invoke the controller which controls the VPN connection.

Below is the folder structure for the ping check scripts and contents of the scripts. Create them in the same locations and set them as executable.

Image 9

pingcheck-online.sh

Create file /etc/pingcheck/online.d/pingcheck-online.sh with the following content:

Bash
#!/bin/sh

if [ "${INTERFACE}" == "wan" ]
then
    echo online > /tmp/pingcheck-wanstate
fi

if [ "${INTERFACE}" == "ovpn" ]
then
    echo online > /tmp/pingcheck-ovpnstate
fi

sh /etc/openvpn/resilient/resilient-vpn-pingcheck-controller.sh

pingcheck-offline.sh

Create file /etc/pingcheck/offline.d/pingcheck-offline.sh with the following content:

Bash
#!/bin/sh

if [ "${INTERFACE}" == "wan" ]
then
    echo offline > /tmp/pingcheck-wanstate
fi

if [ "${INTERFACE}" == "ovpn" ]
then
    echo offline > /tmp/pingcheck-ovpnstate
fi

sh /etc/openvpn/resilient/resilient-vpn-pingcheck-controller.sh

pingcheck-panic.sh

Create file /etc/pingcheck/panic.d/pingcheck-panic.sh with the following content:

Bash
#!/bin/sh

echo online > /tmp/pingcheck-panicstate
sh /etc/openvpn/resilient/resilient-vpn-pingcheck-controller.sh

Final Step: Ping Checker Configuration

Add the following content to ping checker configuration file at /etc/config/pingcheck:

config default
        option host 208.67.222.222
        option interval 10
        option timeout 120
        option panic 4

config interface
        option name wan

config interface
        option name ovpn
        option host 208.67.222.222
        option interval 10
        option timeout 40

The timeouts can be modified according to user preference, just remember that WAN timeout must be greater than VPN timeout. Reboot the router and check for internet access in clients and VPN connectivity.

(Optional) - Alternate Resilient Controller Mode

For folks with intermittent internet connectivity, the above algorithm will result in reconnection every time the internet goes out for more than timeout seconds parameter described in /etc/config/pingcheck configuration file, which will be inefficient reconnects for a few minutes of blackout. If the connection is to be preserved for at least a few minutes in the hopes that the internet comes back meanwhile, below is the alternate script for resilient connection. This mode is not recommended if internet is usually stable.

Update the following content in ping checker configuration file at /etc/config/pingcheck.

config default
        option host 208.67.222.222
        option interval 10
        option timeout 30
        option panic 4

config interface
        option name wan

config interface
        option name ovpn
        option host 208.67.222.222
        option interval 10
        option timeout 50

Note that WAN timeout is lower than VPN timeout in this mode. Bolded lines are changed. Note that panic 4 means connection will be preserved for four minutes.

resilient-vpn-pingcheck-controller.sh

Replace /etc/openvpn/resilient/resilient-vpn-pingcheck-controller.sh with the following content for alternate mode of operation.

Bash
#!/bin/sh

wanstateret=`cat /tmp/pingcheck-wanstate`
ovpnstateret=`cat /tmp/pingcheck-ovpnstate`

wanonline=`echo "$wanstateret" online | awk '{ print ($1 == $2) ? 1 : 0 }'`
ovpnonline=`echo "$ovpnstateret" online | awk '{ print ($1 == $2) ? 1 : 0 }'`

resilientrunningret=`cat /tmp/resilientrunningstatus`
resilientrunningstatus=`echo "$resilientrunningret" running | 
                        awk '{ print ($1 == $2) ? 1 : 0 }'`

panicstateret=`cat /tmp/pingcheck-panicstate`
panicstate=`echo "$panicstateret" online | awk '{ print ($1 == $2) ? 1 : 0 }'`

wanoutfirstret=`cat /tmp/resilient-wanoutfirst`
wanoutfirststatus=`echo "$wanoutfirstret" yes | awk '{ print ($1 == $2) ? 1 : 0 }'`

if [ $wanonline -eq 1 ]
then
    if [ $ovpnonline -eq 0 ] && [ $wanoutfirststatus -eq 1 ]
    then
        rm /tmp/resilient-wanoutfirst
        (sh /etc/openvpn/resilient/check-vpn-connection.sh >/dev/null 2>&1 )&
    fi
    if [ $ovpnonline -eq 0 ] && [ $wanoutfirststatus -eq 0 ]
    then
        /etc/openvpn/leds/connected-led.sh off
        if [ $resilientrunningstatus -eq 0 ]
        then
            (sh /etc/openvpn/resilient/start-resilient-vpn.sh >/dev/null 2>&1 )&
            echo running > /tmp/resilientrunningstatus
        fi
        if [ $resilientrunningstatus -eq 1 ]
        then
            sh /etc/openvpn/resilient/kill-vpn.sh
        fi
    fi
    if [ $ovpnonline -eq 1 ] 
    then
        /etc/openvpn/leds/init-complete-led.sh off
        /etc/openvpn/leds/connected-led.sh on
    fi
fi

if [ $wanonline -eq 0 ]
then
    if [ $ovpnonline -eq 0 ] && [ $panicstate -eq 1 ]
    then
        sh /etc/openvpn/resilient/stop-resilient-vpn.sh
        echo stopped > /tmp/resilientrunningstatus
        rm /tmp/pingcheck-panicstate
    fi
    if [ $ovpnonline -eq 1 ] 
    then
        /etc/openvpn/leds/init-complete-led.sh on
        /etc/openvpn/leds/connected-led.sh on
        echo yes > /tmp/resilient-wanoutfirst
    fi
fi

Diagnosis

If something is wrong, you can check the following steps:

  • Check executable status of all scripts
  • Check log file at: /tmp/openvpn-main-log
  • Remove the hot-plug script and test internet access
  • Run manually start-openvpn-client.sh and check log file at /tmp/openvpn-main-log

Epilogue

This will be my final article on this topic as I cannot see any improvements to be made from my end and the objective of a high availability randomized kill switch enabled VPN router is achieved. I hope my effort will make it easy for folks seeking the same. Contact me if you need any clarification or help setting up!

History

  • 16th July, 2023: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)