Linux: How to force an application to use a given VPN tunnel
Somehow I have to use VPN services throughout the day:
- when pen-testing from abroads I really need to login to my company's network first. Otherwise my provider is kinda grumpy when I'm doing fast non-cloaked scans against large companies.
- also when pen-testing I like to use some cloaking VPNs to test the client's detection capabilities
- if I would ever use bit-torrent I'd really like to make sure that the torrent program can only communicate through a private proxy (as pia).
The easy solution would be to connect the openvpn tunnels on startup and just route all the traffic through the tunnels. Alas this is way to slow for daily use – and somehow error prone: if a tunnel dies and some pen-test is currently under progress traffic might escape into 'unsecured' public networks. The same would be true for torrents.
Just to state the obvious: all links to Private Internet Access contain my referral ID – if you want to sign up theere and use this link I'm getting some money (hopefully).
How to route/bind programs to the VPN interface
So let's change my openvpn client's configuration to not accept a new default route from the VPN service's DHCP server (once again, I'm using my privateinteraccess.com account):
client dev tun proto udp remote swiss.privateinternetaccess.com 1194 resolv-retry infinite nobind persist-key persist-tun ca ca.crt tls-client remote-cert-tls server auth-user-pass .pia comp-lzo verb 1 reneg-sec 0 # this stuff changes the routing behaviour script-security 2 route-noexec route-up /home/andy/route_up.sh
The route_up.sh is mostly empty: it just outputs environment variable (set by openvpn) that show my which IP address and routing addresses were forwarded to the route_up.sh script:
#!/bin/sh echo "$dev : $ifconfig_local -> $ifconfig_remote gw: $route_vpn_gateway" exit 0
After firing up the VPN tunnel (via
openvpn openvpn.vpn) the script outputs the following:
tun0 : 10.188.1.10 -> 10.188.1.9 gw: 10.188.1.9
and my routing table looks like I would expect it to be (my ip is 10.188.1.10, the gateway's IP is 10.188.1.9):
root@MinimalBastard:/home/andy# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.0.1 0.0.0.0 UG 0 0 0 eth0 10.188.1.9 0.0.0.0 255.255.255.255 UH 0 0 0 tun0 192.168.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
Now we need to create a new routing table, which I will call 'custom_table'.
# first create a table name $ echo "10 custom_table" >> /etc/iproute2/rt_tables # then add the vpn device as default route for this routing table $ ip route add default via 10.188.1.9 dev tun0 table custom_table # add rules that all traffic going to the gateway as well as # all traffic comming from my local VPN is routed through the # VPN's gateway $ ip rule add from 10.188.1.10/32 table custom_table $ ip rule add to 10.188.1.9/32 table custom_table # and flush the cache to make sure that the changes were commited $ ip route flush cache
Now is the perfect time to test what my external IP would be. To do this I use wget, which I bind to use the local VPN IP. wget connects to http://ipecho.net which will return my external IP address:
root@MinimalBastard:/home/andy# wget --bind-address=10.188.1.10 http://ipecho.net/plain -O - -q; echo -> 126.96.36.199
Which is a IP address belonging to the VPN service. Mission accomplished.
How does it behave when VPN tunnel dies?
Lets test this too: I'll just retry the wget command after closing the VPN tunnel:
root@MinimalBastard:/home/andy# wget --bind-address=10.188.1.10 http://ipecho.net/plain --2013-10-10 19:27:29-- http://ipecho.net/plain Resolving ipecho.net (ipecho.net)... 188.8.131.52 Connecting to ipecho.net (ipecho.net)|184.108.40.206|:80... failed: Cannot assign requested address. Retrying. --2013-10-10 19:27:30-- (try: 2) http://ipecho.net/plain Connecting to ipecho.net (ipecho.net)|220.127.116.11|:80... failed: Cannot assign requested address. Retrying. --2013-10-10 19:27:32-- (try: 3) http://ipecho.net/plain Connecting to ipecho.net (ipecho.net)|18.104.22.168|:80... failed: Cannot assign requested address. Retrying.
I really like this: when the VPN tunnel dies no communication will be routed through the default gateway (or leave the host at all).
WIP: start application through the openvpn config file
We can use the environment variables to set everything up through route_up.sh:
#!/bin/sh echo "$dev : $ifconfig_local -> $ifconfig_remote gw: $route_vpn_gateway" ip route add default via $route_vpn_gateway dev $dev table custom_table ip rule add from $ifconfig_local/32 table custom_table ip rule add to $route_vpn_gateway/32 table custom_table ip route flush cache exit 0 # PROBLEM: why is this not finishing? #wget --bind-address=$ifconfig_local http://ipecho.net/plain -O - -q; echo
My problem now is, that the wget command is not finishing (but the tunnel itself works if I comment out this final bit, I can use the same wget command on the command line).
This is kinda disappointing. I would really like to automatically start the pen-testing tools (or bittorrent clients) through the openvpn script – with this setup I wouldn't have to do anything manually.
Any suggestions? If so, please add comments.. I will update this post as soon as better solutions are found. Another thing I'm looking into is retrieving the PID of the started command and automatically adding futher iptable rules that block any traffic of the program in question that wouldn't use the configured routing table.