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.