Split Tunneling on VPN via Routing Table

Split Tunneling

Whenever I connect to VPN on my mac, my default route is modified to this:

$ netstat -nr
Routing tables

Internet:
Destination        Gateway            Flags        Refs      Use   Netif Expire
default            utun1              UCS            34        0   utun1 
default            192.168.2.1        UGScI           0        0     en0

What this does is, route all my traffic, even the one which is outside of VPN network routed through the VPN interface utun1. The second default route via 192.168.1.1 is not even used. This is unnecessary and sometimes counterproductive as the VPN network takes extra load on bandwidth/resources for the IPs outside of its network and even bans sites which do not require banning.

You can easily change your routing table to circumvent this using the script below. This way you access public IPs directly and private IPs over VPN. This concept is called Split Tunneling

Modifying Routing Table

Now to modify routing table for split tunneling, all you need to do is to find out the subnet of the IPs in your VPN you need access to. Let't take for example, if the subnets of my VPN private space are 10.109.0.0 and 10.110.0.0 (Any IPs starting with 10.109 and 10.110):

#! /usr/bin/env bash
if (( EUID != 0 )); then
  echo "Please, run this command with sudo" 1>&2
  exit 1
fi
WIRELESS_INTERFACE=en0
TUNNEL_INTERFACE=utun0
GATEWAY=$(netstat -nrf inet | grep default | grep $WIRELESS_INTERFACE | awk '{print $2}')

echo "Resetting routes with gateway => $GATEWAY"
echo
route -n delete default -ifscope $WIRELESS_INTERFACE
route -n delete -net default -interface $TUNNEL_INTERFACE
route -n add -net default $GATEWAY
for subnet in  10.109 10.110
do
  route -n add -net $subnet -interface $TUNNEL_INTERFACE
done

Just save the script as /usr/bin/vpn and whenever you connect to vpn, just run sudo vpn. This is what the script does:

  • Finds wireless router IP (en0)
  • Remove the default routes set by VPN
  • Set the IP found for en0 as the default gateway
  • Add the specific routes for your VPN subnet

If you are connecting over wired network, just change the en0 in script to en1. Don't change your VPN DNS to make sure you can resolve private domains on the VPN.

Looking at the routing table again after running the script:

$ netstat -nr
Routing tables

Internet:
Destination        Gateway            Flags        Refs      Use   Netif Expire
default            192.168.2.1        UGSc            1        0     en0
...
10.109                 utun1              USc            0        0   utun1
10.110                 utun1              USc            0        0   utun1

As you can see now, our default gate is back to wifi router IP - 192.168.1.1 on en0 and there are some routes for the subnets inside the VPN.

You can test the route as well with a route command. Route for any IP outside of your VPN:

$ route get google.com                                            
route to: lga15s29-in-f5.1e100.net
destination: default
   mask: default
   gateway: 192.168.1.1
   interface: en0
   flags: <UP,GATEWAY,DONE,STATIC,PRCLONING>
   recvpipe  sendpipe  ssthresh  rtt,msec    rttvar  hopcount      mtu     expire
            0         0         0         0         0         0      1500         0

Route for IP inside your VPN:

$ route get 10.109.10.135
route to: 10.109.10.135
destination: 156.107.0.0
   mask: 255.255.0.0
   interface: utun0
   flags: <UP,GATEWAY,DONE,STATIC,PRCLONING>
   recvpipe  sendpipe  ssthresh  rtt,msec    rttvar  hopcount      mtu     expire
            0         0         0         0         0         0      1280         0

To reset the routing table back, just disconnect from the VPN. Same script can be used on Linux with some modifications.