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.