Bypass VPN by Hostname

3 minute read Published:

If you run VPN on your router, you may have had cases where you want Internet traffic to bypass the VPN. Most solutions I’ve found use some form of a script originally found on LinksysInfo. However these approaches are IP based, which can be a problem if those IPs change, or if the service you’re accessing uses a CDN. If your router is running firmware that supports ipset, you can use dnsmasq and iptables to solve this problem.

This example uses release 112 of Tomato by Shibby. Some versions of utilities are not the latest available, so syntax may be slightly different than readily available man pages. The code snippets below include a commented reference to where they go in the admin GUI, but this is just what worked for me.

The core of the solution is to create an ipset for hosts of interest, configure Dnsmasq to populate that ipset and then use iptables rules to route outgoing traffic to those IPs over the WAN instead of over the VPN.

# Administration > Scripts > Init

ipset --create bypass_vpn iphash
modprobe ipt_set

The first thing you need to do is create an ipset and load the ipt_set kernel module. Here we’ve created an ipset named bypass_vpn of type iphash. Note that newer versions of ipset use a different syntax, hash:ip, for the set type. If you later get errors from iptables when trying to create rules with the ipset, you probably forgot to load the kernel module.

# Advanced > DHCP/DNS > Dnsmasq Custom configuration

server=/example.com/192.168.1.1
ipset=/example.com/bypass_vpn

#log-queries

Next you need to tell Dnsmasq to route DNS requests to a DNS server that is going to give you location appropriate results. This is particularly important if you’re trying to get traffic to use a local CDN. In the above example, example.com is our host of interest, 192.168.1.1 is the IP of a DNS server that will resolve that host to a desirable IP and bypass_vpn is the ipset to store the results in for later use by iptables. You will need a server/ipset pair for every hostname you wish to bypass the VPN.

When you’re first setting things up, you’ll probably want to also add log-queries to the Dnsmasq config, which will log DNS requests to the system log at /var/log/messages. In addition to hostnames you’re already aware of, be on the lookout for hosts referred to by CNAME records.

# Administration > Scripts > WAN Up

# The commands needed to create the routing table for packets marked with a
# 1 are not shown here. See the aforementioned LinksysInfo thread for more
# information.

# MARK = 0; all traffic goes through VPN by default
iptables -t mangle -A PREROUTING -i br0 -j MARK --set-mark 0

# MARK = 1; bypass VPN for bypass_vpn ipset
iptables -t mangle -A PREROUTING -i br0 -m set --set bypass_vpn dst -j MARK --set-mark 1

Finally you need to create rules to mark packets matching the ipset. In the above example, br0 is the interface the packets are entering through, bypass_vpn is the ipset containing the IPs to match and 1 is the mark value corresponding to a routing table that bypasses the VPN. This is not a complete iptables configuration. Only the packet marking rules are shown. Note that the --match-set option mentioned in the iptables man page did not work for me.