OpenWrt Forum Archive

Topic: Howto - Selective VPN routing based on URLs

The content of this topic has been archived on 13 Apr 2018. There are no obvious gaps in this topic, but there may still be some posts missing at the end.

Hi everyone - long time lurker.

TV is fun, especially when you're between jobs. Alas, I live in a part of the world which is not America. I also like watching German and Swiss TV, because I am learning German.

I wanted to build a solution that would selectively route traffic to certain domains over one of three VPNs, and leave the rest of my traffic going over the normal connection. The script would periodically update the routing table if the IP addresses behind the URLs change.

My provider of choice is my-private-network.co.uk - they offer VPNs based in a number of questions worldwide. This solution may work with others, but I haven't tested.

This is my first attempt at any kind of scripting - so I appreciate any constructive ideas any of you might have, particularly on the security side of things!

Here we go:

1. Install openvpn and sed (will need it later)

opkg update
opkg install sed
# We need a more security-liberal openvpn package than the stock, as we'll be reading usernames and passwords from a plain text file. I've built one for ar71xx-based devices.
# https://rapidshare.com/files/154560410/openvpn_2.2.1-5_ar71xx.ipk
# To use the above, you *may* also need to place the following libraries in your /usr/lib/ folder
# https://rapidshare.com/files/1828402193/libcrypto.so.0.9.8
# https://rapidshare.com/files/2822494240/libssl.so.0.9.8
opkg install 'Wherever you put the above package'

2. Use symlinks so that the openvpn instances will be seperate (and clear) when running

ln -s /usr/sbin/openvpn /root/mpn/CHEvpn
ln -s /usr/sbin/openvpn /root/mpn/DEUvpn
ln -s /usr/sbin/openvpn /root/mpn/USAvpn

3. Enter your providers CA

cat > /root/mpn/ca.crt << END
-----BEGIN CERTIFICATE-----
YOUR PROVIDERS CERTIFICATE
-----END CERTIFICATE-----
END

4. Making symlinks for each of the VPN instances

ln -s /root/mpn/ca.crt /root/mpn/caCHE.crt
ln -s /root/mpn/ca.crt /root/mpn/caDEU.crt
ln -s /root/mpn/ca.crt /root/mpn/caUSA.crt

5. Create a password file. N.B. Storing a plaintext password is not exactly best practice, but I couldn't find another way to solve this problem (at least, with my current provider). Ensure, as you should always do anyway, that you do not use this same password for any other services - assume it has been compromised the moment you write this file.

cat > /root/mpn/user.txt << END1
YOUR_VPN_PROVIDERS_USERNAME
PASSWORD
END1

6. Writing the config files for each of the VPN instances

cat > /root/mpn/CHE.conf << END2
script-security 3
client
daemon
remote YOUR_VPN_PROVIDERS_SERVER_FOR_CHE
dev tun1
proto udp
port 80
resolv-retry infinite
nobind
route-delay 1
route-method ipapi
dhcp-option DNS 8.8.8.8
route-nopull
mute-replay-warnings
auth-user-pass /root/mpn/user.txt
ca /root/mpn/caCHE.crt
keepalive 10 30
verb 1
mssfix 1396
END2

cat > /root/mpn/DEU.conf << END3
script-security 3
client
daemon
remote YOUR_VPN_PROVIDERS_SERVER_FOR_DEU
dev tun2
proto udp
port 80
resolv-retry infinite
nobind
route-delay 1
route-method ipapi
dhcp-option DNS 8.8.8.8
route-nopull
mute-replay-warnings
auth-user-pass /root/mpn/user.txt
ca /root/mpn/caDEU.crt
keepalive 10 30
verb 1
mssfix 1396
END3

cat > /root/mpn/USA.conf << END4
script-security 3
client
daemon
remote YOUR_VPN_PROVIDERS_SERVER_FOR_USA
dev tun3
proto udp
port 80
resolv-retry infinite
nobind
route-delay 1
route-method ipapi
dhcp-option DNS 8.8.8.8
route-nopull
mute-replay-warnings
auth-user-pass /root/mpn/user.txt
ca /root/mpn/caUSA.crt
keepalive 10 30
verb 1
mssfix 1396
END4

N.B. Each VPN instance has a specified dev - this is needed later.

7. Now you've got to create /root/mpn/URLroute.conf - the format is simply URL then 1, 2 or 3 separated by a tab - 1, 2 and 3 denote CHE (Swiss), DEU (German) or USA (American) VPNs respectively. I'm going to leave out this one - it's up to you to decide which URLs you want to route to which VPN.


8. Add the VPN instances as interfaces to /etc/config/network

config interface 'CHEvpn'
    option ifname 'tun1'
    option proto 'none'

config interface 'DEUvpn'
    option ifname 'tun2'
    option proto 'none'
    
config interface 'USAvpn'
    option ifname 'tun3'
    option proto 'none'

9. Let the VPN instances through the firewall by adding the following to /etc/config/firewall

config zone
    option output 'ACCEPT'
    option name 'CHEvpn'
    option masq '1'
    option network 'CHEvpn'
    option input 'REJECT'
    option forward 'REJECT'

config forwarding
    option dest 'CHEvpn'
    option src 'lan'

config zone
    option name 'DEUvpn'
    option network 'DEUvpn'
    option input 'REJECT'
    option output 'ACCEPT'
    option forward 'REJECT'
    option masq '1'

config forwarding
    option dest 'DEUvpn'
    option src 'lan'

config zone
    option name 'USAvpn'
    option network 'USAvpn'
    option input 'REJECT'
    option output 'ACCEPT'
    option forward 'REJECT'
    option masq '1'

config forwarding
    option dest 'USAvpn'
    option src 'lan'

10. Here's the script that does the magic:

cat > /root/mpn/URLroute.sh << END5
#!/bin/sh        
vpnroutecheck()
{
# The URL and correct VPN gateway is passed into the function via the following:
URL=$1
gw=$2
# Now we lookup new IPs - following code filters out other information and sets the current IPs as the variables IP_A_CURRENT and IP_B_CURRENT. Most services I use have either one or two IPs for each URL.
IP=`nslookup $URL 8.8.8.8 | egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' -o | grep -v '8\.8\.8\.8'`;IP_A_CURRENT=`echo $IP | awk '{print $1}'`;IP_B_CURRENT=`echo $IP | awk '{print $2}'`
# Look up old IPs from the temp file:
IP_OLD=`grep -n -m 1 $URL /tmp/VPN_routing_tempfile`
# Check if there is a previous IP entry in our temp file for this URL (which should match the routing table). If there is, then compare against current IP and update if necessary. Otherwise, just add the IP to both the routing table and temp file.
if [ "$IP_OLD" ]; then
    # Filter out the IP addresses from the information in our temp file
    IP_A_OLD=`echo $IP_OLD | awk '{print $2}'`;IP_B_OLD=`echo $IP_OLD | awk '{print $3}'`
    # Check old IPs against new, if change then update routing table 
    i=0
    #echo $i
    if  [ "$IP_A_OLD" == "$IP_A_CURRENT" -o "$IP_B_OLD" == "$IP_A_CURRENT" ]; then 
        i=`expr $i + 1`
    fi
    #echo $i
    if  [ "$IP_A_OLD" == "$IP_B_CURRENT" -o "$IP_B_OLD" == "$IP_B_CURRENT" ]; then 
        i=`expr $i + 1`
    fi
    #echo $i
    #if [ "$i" == 2 ]; then     
        #echo "Condition 2: Everything's ok"
        #echo $URL
        #echo $IP_A_OLD $IP_A_CURRENT
        #echo $IP_B_OLD $IP_B_CURRENT
    #fi
    if [ "$i" -lt 2 ]; then     
        #echo "Condition 0 or 1: Rerouting"
        #echo $URL
        #echo $IP_A_OLD $IP_A_CURRENT
        #echo $IP_B_OLD $IP_B_CURRENT
        if [ -n "$IP_A_OLD" ]; then
        if [ "`grep -c $IP_A_OLD "/tmp/VPN_routing_tempfile"`" == 1 ]; then
            route del -net $IP_A_OLD netmask 255.255.255.255
        fi
        fi
        if [ -n "$IP_B_OLD" ]; then
        if [ "`grep -c $IP_B_OLD "/tmp/VPN_routing_tempfile"`" == 1 ]; then
            route del -net $IP_B_OLD netmask 255.255.255.255
        fi
        fi
        if [ -n "$IP_A_CURRENT" ]; then
        if [ "`grep -c $IP_A_CURRENT "/tmp/VPN_routing_tempfile"`" == 0 ]; then
            route add -net $IP_A_CURRENT netmask 255.255.255.255 gw $gw
        fi
        fi
        if [ -n "$IP_B_CURRENT" ]; then
        if [ "`grep -c $IP_B_CURRENT "/tmp/VPN_routing_tempfile"`" == 0 ]; then
            route add -net $IP_B_CURRENT netmask 255.255.255.255 gw $gw
        fi
        fi                                        
        # The data in the temp file needs to be replaced. First we find the line number: 
        IP_OLD_LINE=`echo $IP_OLD | sed -n 's/^\([0-9]*\)[:].*/\1/p'`
        # Then replace the old IPs with the current ones - NB: The stock Busybox sed doesn't work. 
        /usr/bin/sed -i ''"$IP_OLD_LINE"' c\'"$URL"'\t'"$IP_A_CURRENT"'\t'"$IP_B_CURRENT"'' /tmp/VPN_routing_tempfile
    fi
else
    #echo "Condition 3: New entry"
    #echo $URL
    #echo $IP_A_CURRENT
    #echo $IP_B_CURRENT
    if [ -n "$IP_A_CURRENT" ]; then
    if [ "`grep -c $IP_A_CURRENT "/tmp/VPN_routing_tempfile"`" == 0 ]; then
            route add -net $IP_A_CURRENT netmask 255.255.255.255 gw $gw
    fi
    fi
    if [ -n "$IP_B_CURRENT" ]; then
    if [ "`grep -c $IP_B_CURRENT "/tmp/VPN_routing_tempfile"`" == 0 ]; then
            route add -net $IP_B_CURRENT netmask 255.255.255.255 gw $gw
    fi
    fi
    # Append the IPs to the end of temp file
    echo -e "$URL\t$IP_A_CURRENT\t$IP_B_CURRENT" >> /tmp/VPN_routing_tempfile
fi
return
}
vpnroutebuild()
{
# The URL and correct VPN gateway is passed into the function via the following:
URL=$1
gw=$2
# Look up IPs 
IP=`nslookup $URL 8.8.8.8 | egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' -o | grep -v '8\.8\.8\.8'`;IP_A_CURRENT=`echo $IP | awk '{print $1}'`;IP_B_CURRENT=`echo $IP | awk '{print $2}'`
# Route the IPs to the correct VPN gateway
#echo $IP_A_CURRENT
#echo $IP_B_CURRENT
if [ -n "$IP_A_CURRENT" ]; then
if [ "`grep -c $IP_A_CURRENT "/tmp/VPN_routing_tempfile"`" == 0 ]; then
            route add -net $IP_A_CURRENT netmask 255.255.255.255 gw $gw
fi
fi
if [ -n "$IP_B_CURRENT" ]; then
if [ "`grep -c $IP_B_CURRENT "/tmp/VPN_routing_tempfile"`" == 0 ]; then
            route add -net $IP_B_CURRENT netmask 255.255.255.255 gw $gw
fi
fi
# Append the IPs to the end of temp file
echo -e "$URL\t$IP_A_CURRENT\t$IP_B_CURRENT" >> /tmp/VPN_routing_tempfile
echo $URL ok
return
}

#Check vpns are up, if not restart them
if [ -n "`pgrep CHEvpn`" -a -n "`pgrep DEUvpn`" -a -n "`pgrep USAvpn`" ]; then
    echo "VPNs are up, as they should be"
else
    #echo "VPNs have a problem. Making sure processes are killed, then restarting them"
    pkill CHEvpn
    sleep 1
    /root/mpn/CHEvpn --config /root/mpn/CHE.conf
    pkill DEUvpn
    sleep 1
    /root/mpn/DEUvpn --config /root/mpn/DEU.conf
    pkill USAvpn
    sleep 1
    /root/mpn/USAvpn --config /root/mpn/USA.conf
    sleep 10
    rm /tmp/VPN_routing_tempfile
fi
#Parameterise the tunnels
tun1gw=`route | grep 'tun1' | grep 'UH' | egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' -o | grep -v '255\.255\.255\.255'`
tun2gw=`route | grep 'tun2' | grep 'UH' | egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' -o | grep -v '255\.255\.255\.255'`
tun3gw=`route | grep 'tun3' | grep 'UH' | egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' -o | grep -v '255\.255\.255\.255'`
NUM_REDIRECTS=`grep -c "." /root/mpn/URLroute.conf`
# Check temp file is there, if not flush and rebuild
if [ -s '/tmp/VPN_routing_tempfile' ]; then
    for CONF_LINE_NO in `seq $NUM_REDIRECTS`
    do
        LINE=`sed -n ''$CONF_LINE_NO'p' /root/mpn/URLroute.conf`
        URL=`echo $LINE | awk '{print $1}'`
        gw_var=`echo $LINE | awk '{print $2}'`
        if [ "$gw_var" == 1 ]; then
            gw=$tun1gw
        fi
        if [ "$gw_var" == 2 ]; then
            gw=$tun2gw
        fi
        if [ "$gw_var" == 3 ]; then
            gw=$tun3gw
        fi
        vpnroutecheck $URL $gw
    done
else
    ip route flush dev tun1 via $tun1gw
    ip route flush dev tun2 via $tun2gw
    ip route flush dev tun3 via $tun3gw
    touch /tmp/VPN_routing_tempfile
    for CONF_LINE_NO in `seq $NUM_REDIRECTS`
    do
        LINE=`sed -n ''$CONF_LINE_NO'p' /root/mpn/URLroute.conf`
        URL=`echo $LINE | awk '{print $1}'`
        gw_var=`echo $LINE | awk '{print $2}'`
        if [ "$gw_var" == 1 ]; then
            gw=$tun1gw
        fi
        if [ "$gw_var" == 2 ]; then
            gw=$tun2gw
        fi
        if [ "$gw_var" == 3 ]; then
            gw=$tun3gw
        fi
        vpnroutebuild $URL $gw
    done
fi

END5

11. Make it executable

chmod 755 /root/mpn/URLroute.sh

12. Done. Add it to Cron to update regularly, I have it going every 10 mins - works almost flawlessly.

There are some vpn services today offer Selective Routing feature, some works on most devices, including Internet enabled TV.

VpnSelect.com seems to be interesting. The idea - you can choose sites you want to access, so it doesn't affect local traffic. As well, you can use services from different countries at the same time. For example, you watch Hulu US on TV in leaving room, and someone else can watch Netflix UK from Xbox connected to TV in his bedroom.

(Last edited by Fred73L on 24 Dec 2012, 05:43)

The discussion might have continued from here.