MacOS – In MacOS, how can I bypass a VPN for a specific site using its hostname

macosNetworkvpn

I'm setting up an IKEv2 VPN in macOS 10.12.3. It's working, but I'd like to configure specific websites to bypass the VPN.

I'm aware of a method using the route command to add a static route, but that only works for a specific IP address. If the site I'm connecting to changes its IP (which is a distinct possibility) then its traffic will start going through the VPN again.

How can I bypass the VPN using the site's hostname? e.g. any request to www.somewebsite.com will not use the VPN, regardless of what IP address www.somewebsite.com resolves to?

Best Answer

The most simple way to create a static and persistent route to a host is creating a script addroute.sh (root:wheel 755) in /usr/local/bin. Replace the host name and the gateway though.

#!/bin/bash

sleep 10
route -n add -host $(dig +short example.com) 192.168.0.1

and load the script with a launch daemon usr.addroute.plist (root:wheel 644) in /Library/LaunchDaemon/:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>usr.addroute</string>
        <key>ProgramArguments</key>
            <array>
                <string>/bin/sh</string>
                <string>-c</string>
                <string>/usr/local/bin/addroute.sh</string>
            </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StandardErrorPath</key>
        <string>/tmp/usr.addroute.err</string>
        <key>StandardOutPath</key>
        <string>/tmp/usr.addroute.out</string>
</dict>
</plist>

Load it with: sudo launchctl load /Library/LaunchDaemon/usr.addroute.plist.

If everything works remove the Standard* keys/strings in the plist and the related files in /tmp.

The sleep 10 line is a sledgehammer approach - a route can only be added after a certain point in time: network has to be up for the specific interface - and may be replaced with something more elegant outlined in this answer here.


Depending on the VPN's or your default environment's DNS capabilities you may also have to add a route for a DNS server (e.g. Google's 8.8.8.8) and modify the dig command to dig @8.8.8.8 +short example.com.


If you reboot your host rarely and the remote hosts' IPs change often, use the following script (which can be improved probably):

#!/bin/bash

sleep 10

Hostname=hostname
GateWay=gateway_IP
IPHost=$(dig +short $Hostname)
RouteExist=$(netstat -nr -f inet | grep $IPHost | wc -l)
NewRoute=$(netstat -nr -f inet | grep $IPHost | awk '{print $1}')
OldHost=$(cat /usr/local/bin/addroute)

if [ $RouteExist -eq 0 ]
then
    route -n add -host $IPHost $GateWay
    RouteExist=1
    NewRoute=$(netstat -nr -f inet | grep $IPHost | awk '{print $1}')
    echo $NewRoute > /usr/local/bin/addroute 
fi

if [ $IPHost != $OldHost ]

then
    route -n delete -host $OldHost $GateWay
    NewRoute=$(netstat -nr -f inet | grep $IPHost | awk '{print $1}')
    echo $NewRoute > /usr/local/bin/addroute  
fi

and plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>usr.addroute</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/sh</string>
        <string>-c</string>
        <string>/usr/local/bin/addroute.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardErrorPath</key>
    <string>/tmp/usr.addroute.err</string>
    <key>StandardOutPath</key>
    <string>/tmp/usr.addroute.out</string>
    <key>StartInterval</key>
    <integer>60</integer>
</dict>
</plist>

An additional proxy file is needed:

sudo touch /usr/local/bin/addroute 

Please adjust the StartInterval (in seconds) in your plist as required and enter a proper host name and gateway IP in the shell script.

The first if routine adds a route to a remote host after booting your host and - later - a new route if the remote host's IP changes. The second if routine removes an outdated route.