Click here to Skip to main content
16,020,261 members
Articles / General Programming / Internet

Self-Healing Randomized VPN with Kill Switch in OpenWRT Router

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
14 Feb 2023CPOL7 min read 9K   20   6   3
Scripts for a truly randomized and kill switch enabled VPN router
This article describes scripts and configuration to enable kill switch enabled VPN router that randomly chooses configuration files from a pool to randomize the VPN server it connects to in each attempt. The kill switch enables the traffic from interfaces not to leak during reconnects or router reboots.

Introduction

Those who are familiar with the standard way of enabling VPN on OpenWRT routers, as described in the OpenWRT site here and here will know that it does not allow ‘kill switch’, which means blocking the traffic from interfaces when openvpn command is not running or has exited. If the openvpn command fails due to disconnection or other reasons, then the network will fall back the WAN interface and internet traffic from clients leaks out to regular network until the command is restarted. This article describes how to implement VPN kill switch on LAN and wireless interfaces so that clients can only access internet from the tunnel if openvpn command is active, and blocked when command is not run yet or until a failed or bad tunnel is automatically restored.

This article also describes how a random configuration file from a directory can be chosen every time openvpn command runs, adding a level of randomization to which VPN server the client connects to.

Implementing the above concepts on an OpenWRT router will make any network much safer from tracking or logging by ISPs. This article is mostly creating a basic configuration and some scripts which enable such behaviour on the OpenWRT router.

Setting up OpenWRT

This article assumes the reader has a freshly flashed or resetted OpenWRT router with the initial configuration. After setting the main admin password at http://192.168.1.1/cgi-bin/luci/admin/system/admin/password, the router’s terminal can be accessed with ssh root@192.168.1.1 Firstly, the following commands have to be executed to set lan interface at 192.168.2.1.

uci set network.lan.proto='static'
uci set network.lan.ipaddr='192.168.2.1'
uci set network.lan.netmask='255.255.255.0'
uci commit network
/etc/init.d/network restart

At this point, the ssh connection will disconnect as LAN interface changes, and terminal can now be accessed at ssh root@192.168.2.1. The below commands will setup a new interface to be used with wireless device at 192.168.3.1.

uci set network.wifi24='interface'
uci set network.wifi24.proto='static'
uci set network.wifi24.ipaddr='192.168.3.1'
uci set network.wifi24.netmask='255.255.255.0'

uci set dhcp.wifi24='dhcp'
uci set dhcp.wifi24.interface='wifi24'
uci add_list firewall.@zone[0].network='wifi24'

uci commit dhcp
uci commit network
uci commit firewall

/etc/init.d/firewall restart
/etc/init.d/network restart 

This new interface can be associated with a wireless radio in luci at http://192.168.2.1/cgi-bin/luci/admin/network/wireless. Internet access should now be verified in both LAN devices and wireless devices before proceeding to the next step.

The following command will install openvpn on the router:

opkg update
opkg install openvpn-openssl 

This is the only package needed for this article and we can proceed to the next step of creating an interface ovpn for the tunnel tun0 which will be created by openvpn command. Execute the following commands in the terminal to create the interface.

uci set network.ovpn='interface'
uci set network.ovpn.proto='none'
uci set network.ovpn.ifname='tun0'

uci commit network
uci add_list firewall.@zone[1].network='ovpn'

uci commit firewall

/etc/init.d/firewall restart
/etc/init.d/network restart 

The creation of the currently inactive interface can be verified at http://192.168.2.1/cgi-bin/luci/admin/network/network.

Prevent DNS Leak

The DNS addresses of WAN will be used despite main traffic begin routed from the tunnel, leaking DNS information to the local network. This below configuration will avoid that:

uci set network.wan.peerdns='0'
uci set network.ovpn.peerdns='0'
uci commit network

/etc/init.d/network restart 

Setting up Folders and Files

There needs to exist a folder that will contain all the *.ovpn configurations that a script will randomly choose from to be passed to the openvpn command and a file to store the vpn provider credentials. Execute the below commands to create the folder and file.

mkdir /etc/openvpn/configs
touch /etc/openvpn/credentials 

This configs folder must be populated with ovpn configurations from your VPN provider, and the credentials can be populated with credentials in the following manner:

echo "<username of vpn provider>" >> /etc/openvpn/credentials
echo "<password of vpn provider>" >> /etc/openvpn/credentials 

Testing VPN Tunnel and Config

At this point, you can test the setup by executing the following test command using any config file.

openvpn –config /etc/openvpn/configs/<random-config-file> 
        --dev tun0 –auth-user-pass /etc/openvpn/credentials 

This should execute successfully now and a new tunnel tun0 should be created. A new router terminal can be used to verify pings through the tunnel.

ping -I tun0 8.8.8.8 

Internet connectivity should be verified from LAN and Wireless devices before proceeding to the next step. As reader will notice, all traffic is now protected by the tunnel, but the traffic will fall back to wan network when the openvpn command is exited. We are now ready to implement the ‘kill switch’ where when the openvpn command is not active, the internet from LAN and wireless devices is blocked.

Setting up Kill Switch Scripts

A new routing table has to be created on the router that will route the traffic from LAN and wireless interfaces to tunnel created by openvpn command and routes on this new table can be modified by scripts on the fly to enable the kill switch.

Creating New Routing Table

You can modify the file /etc/iproute2/rt_tables to add a new table we will call custom.

cat /etc/iproute2/rt_tables 

The table should look like this after modification is done by the tool of your choice. The line added is 40 custom.

#
# reserved values
#
128     prelocal
255     local
254     main
253     default
40      custom
0       unspec
#
# local
# 

Creating Kill Switch Script

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

Bash
#!/bin/sh

ip route flush table custom

interface_cidr=192.168.2.0/24
interface_name=br-lan
interface_gateway=192.168.2.1

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

interface_cidr=192.168.3.0/24
interface_name=wifi24
interface_gateway=192.168.3.1

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

Make the file executable:

chmod +x /etc/openvpn/kill-switch-setup.sh     

Executing this script by ./kill-switch-setup.sh will block the internet from LAN and wifi interfaces until the custom table is updated by scripts described later in this article, but the ssh and the luci interface can still be accessed. The script’s functionality can be tested by executing the script and to restore internet again, you can restart the network by the command below:

/etc/init.d/network restart 

The internet is restored by the above command as and when the network is restarted, the ip rules and routes are purged back to the originals. If you are unable to access ssh or internet due to flukes, restarting the router will fix the issue.

Creating OpenVPN Command ‘Up’ Script

The below script up.sh will be executed when the openvpn command is successful by passing it to openvpn parameter --up. This will update the custom routing with ip routes that will route traffic to the tunnel created. This should not be executed directly from the command line as the openvpn command itself will execute it and pass the parameters required.

Create a new 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_table_name=custom

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)
}'`

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

ip route flush cache 

Make the file executable:

chmod +x /etc/openvpn/up.sh 

Creating OpenVPN Command ‘Down’ Script

The below script down.sh will be executed when the openvpn command exits upon disconnection or ping failure by passing it to the parameter --down. This will remove the routes created by the up.sh command from the custom routing table and internet will blocked from the interfaces. This should not be executed directly from the command line as the openvpn command itself will execute it and pass the parameters required.

Create a new 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_table_name=custom

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)
}'`

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

ip route flush cache 

Make the file executable:

chmod +x /etc/openvpn/down.sh 

Setting up OpenVPN Client Start Scripts

As the openvpn command updates the kernel routing table when executed, kill switch is not possible, hence it has to be passed additional parameters which stop it from adding any routes to routing tables by itself and asks it to execute the up.sh and down.sh commands described above to add routes to custom routing table. The below script start-openvpn-client.sh also selects a random *.ovpn configuration file from /etc/openvpn/configs folder along with executing openvpn command with required parameters to enable kill switch setup.

Creating OpenVPN Randomized Client Start Script

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

Bash
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
openvpn --config ${path} --auth-user-pass /etc/openvpn/credentials 
--up /etc/openvpn/up.sh --down-pre --down /etc/openvpn/down.sh --route-noexec 
--dev tun0 --ping 10 --ping-exit 60 --persist-local-ip --script-security 2  

Make the file executable:

chmod +x /etc/openvpn/start-openvpn-client .sh 

This script can be tested directly now by executing ./start-openvpn-client .sh and verified by pinging through the tunnel created. As this script does not affect the main routing table, it can be started and exiting without breaking anything. If this script is run after the ./kill-switch-setup.sh is executed, the tunneled internet for LAN and wireless interfaces is activated with kill switch enabled. All effects of this script needs to be verified before the next step, which is – tunneled internet on LAN and wireless devices work when script is running and blocks out when script has exited.

Creating Entry Point Script

The script start.sh below will activate the kill switch on LAN and wireless interfaces and enable a self healing openvpn command that restarts itself on exit. This is intended to run automatically after the router starts and interfaces WAN and LAN, etc. are active.

Create a new file /etc/openvpn/start.sh with the following content:

Bash
sh /etc/openvpn/kill-switch-setup.sh
sh /etc/openvpn/start-openvpn-client.sh
exec sh /etc/openvpn/start-openvpn-client.sh 

Make the file executable:

chmod +x /etc/openvpn/start.sh 

To test this command manually, reboot the router, execute ./start.sh and verify tunnel pings and protected internet access on LAN and wireless devices.

Adding Entry Script to Router Startup

As described above, start.sh should only run after the WAN and LAN interfaces are active, hence a new hotplug script is to be created.

Create a new file /etc/hotplug.d/iface/99-ifup-wan-lan with the following content:

Bash
!/bin/sh

if [ "${ACTION}" == "ifup" ] && [ "${DEVICE}" = "wan" ]
then
    rm /tmp/wanstate
    echo started >> /tmp/wanstate

    wanstateret=`cat /tmp/wanstate`
    lanstateret=`cat /tmp/lanstate`
    vpnstateret=`cat /tmp/vpnstate`

    wanstarted=`echo "$wanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
    lanstarted=`echo "$lanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
    vpnstarted=`echo "$vpnstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`

    st=1

    if [ $wanstarted -eq $st ] && [ $lanstarted -eq $st ] && [ $vpnstarted -eq 0 ]
    then
            echo started >> /tmp/vpnstate
            (sh /etc/openvpn/start.sh >/dev/null 2>&1 )&
    fi
fi

if [ "${ACTION}" == "ifup" ] && [ "${DEVICE}" = "br-lan" ]
then
    rm /tmp/lanstate
    echo started >> /tmp/lanstate

    wanstateret=`cat /tmp/wanstate`
    lanstateret=`cat /tmp/lanstate`
    vpnstateret=`cat /tmp/vpnstate`

    wanstarted=`echo "$wanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
    lanstarted=`echo "$lanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
    vpnstarted=`echo "$vpnstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`

    st=1

    if [ $wanstarted -eq $st ] && [ $lanstarted -eq $st ] && [ $vpnstarted -eq 0 ]
    then
            echo started >> /tmp/vpnstate
            (sh /etc/openvpn/start.sh >/dev/null 2>&1 )&
    fi
fi

exit 0 

After this file is added, rebooting the router will automatically activate the kill switch vpn mode.

Further Modifications

As the main routing table remains unaffected by this method, other wan interfaces can be added and split tunnelling of interfaces can be enabled. Some interfaces can use a different tunnel than others by adding additional routing tables like custom_lan or custom_wireless and script modifications. Hope the reader uses a bit of imagination and the scripts above as reference for that.

Additional Notes

This method as been tested for stability on OpenWRT version 22.03. But it will hopefully work on future versions of OpenWRT and on 19.07 versions as well. I will post here if I get a chance to check on 19.07. Happy browsing!

History

  • 14th February, 2023: Initial version

License

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


Written By
Software Developer
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionissue with luci Pin
Member 161956453-Feb-24 19:13
Member 161956453-Feb-24 19:13 
Questioncode Pin
Ausouls15-Feb-23 21:31
Ausouls15-Feb-23 21:31 
AnswerRe: code Pin
hemanthk11916-Feb-23 4:40
hemanthk11916-Feb-23 4:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.