Conditional multiple OpenVPN routing by hostname or IP

Using OpenWRT/LEDE, connect to one or more OpenVPN instances and conditionally divert (split tunneling) one or more outgoing traffic to specific VPN route by destination host names or IP addresses.

Updates

  • 2020-01-29: With mwan3 2.8.1-1 on OpenWRT 19.07, it seems that tracking must be set in order for it to work.
  • 2020-04-02: James White pointed out that gateway metric was redundant. I have removed them from this post and have verified that everything continued working as before. Thank you.

Motivation

If you connect to VPN from your computer, the VPN server usually pushes routes that makes your computer go through it for all outgoing connections. This is fine if this is what you really want. For most people, VPN is required only for visiting one or more websites. This computer networking concept is also known as split tunneling.

Taking it one step further, we may want to connect to multiple OpenVPN servers at the same time with different VPN server being used to serve specific outgoing traffic.

I have a working solution utilising OpenWRT and sharing the setup in this post.

Requirements

You should be familiar with OpenWRT before attempting this as the complexity of this setup can be rather daunting.

The following are required:

  1. openvpn-easy and openvpn-openssl for establishing OpenVPN client connections
  2. luci-app-openvpn for GUI in LuCI which can be handy for starting/stopping VPN connections but I dislike using it for configuring
  3. dnsmasq-full for IPset tagging so that we can route by host names, not just IP addresses
  4. mwan3 and luci-app-mwan3 for creating multiple virtual WAN adapters, one for each OpenVPN connection

Install the required packages using the following commands (or LuCI GUI if you prefer):

opkg update
opkg install openvpn-easyrsa
opkg install openvpn-openssl
opkg install luci-app-openvpn
opkg install dnsmasq-full
opkg install mwan3
opkg install luci-app-mwan3

Configuring IPset rules

When a DNS lookup is performed and that the host name matches specific names that we have defined, it will be tagged with an IPset. A load balancer can then be configured to route specific IPset tags to go through specific VPN route.

Configuring OpenVPN client

Even if you already have this configured, please read this section especially routing related details.

Example: Private Internet Access

Below is an example of OpenVPN client configuration for PIA, I have setup two client instances. Edit /etc/config/openvpn:

config openvpn 'usvpn'
    option nobind '1'
    option float '1'
    option client '1'
    option reneg_sec '0'
    option verb '1'
    option persist_tun '1'
    option persist_key '1'
    option remote_cert_tls 'server'
    option dev 'tun-usvpn'
    option proto 'udp'
    option crl_verify '/etc/openvpn/crl.rsa.4096.pem'
    option ca '/etc/openvpn/ca.rsa.4096.crt'
    option tls_client '1'
    option resolv_retry 'infinite'
    option auth 'SHA256'
    option auth_user_pass '/etc/openvpn/privateinternetaccess-userpass.txt'
    option route_nopull '1'
    option remote 'xxx.privateinternetaccess.com'
    option enabled '1'
    option port '1197'
    option cipher 'AES-256-CBC'
    option comp_lzo 'yes'

config openvpn 'ukvpn'
    option nobind '1'
    option float '1'
    option client '1'
    option reneg_sec '0'
    option verb '1'
    option persist_tun '1'
    option persist_key '1'
    option remote_cert_tls 'server'
    option dev 'tun-ukvpn'
    option proto 'udp'
    option crl_verify '/etc/openvpn/crl.rsa.4096.pem'
    option ca '/etc/openvpn/ca.rsa.4096.crt'
    option tls_client '1'
    option resolv_retry 'infinite'
    option auth 'SHA256'
    option auth_user_pass '/etc/openvpn/privateinternetaccess-userpass.txt'
    option route_nopull '1'
    option remote 'xxx.privateinternetaccess.com'
    option enabled '1'
    option port '1197'
    option cipher 'AES-256-CBC'
    option comp_lzo 'yes'

Several configuration details that you must note:

  1. The PEM and CA files of Private Internet Access have been copied into /etc/openvpn.
  2. A text file needs to be created /etc/openvpn/privateinternetaccess-userpass.txt where the first line contains the username and the second line contains the password.
  3. route_nopull must be '1' as we do not want the server to tell us what routes to use.
  4. remote must be set to servers of your choice.
  5. The other parameters should be configured as described by your VPN provider.

Ensure that multiple concurrent VPN networks do not share the same gateway IP subnet

After connecting to an OpenVPN server, the VPN network will have a gateway that you will be sending traffic to. This gateway is usually in the IP of 10.x.y.z. It is very important that multiple concurrent VPN networks do not share the same gateway IP subnet.

Starting VPN connections

This is where OpenVPN LuCI GUI comes in handy. Start all instances of the OpenVPN clients.

For command line geeks:

/etc/init.d/openvpn restart

Check the System Log to verify that VPN connections established successfully. Otherwise some errors will show up.

Creating virtual network adapter for each OpenVPN client

Edit /etc/config/network. Add the following:

config interface 'usvpn'
    option proto 'none'
    option ifname 'tun-usvpn'
    option delegate '0'

config interface 'ukvpn'
    option proto 'none'
    option ifname 'tun-ukvpn'
    option delegate '0'

Allowing VPN traffic to go through the firewall

Edit /etc/config/firewall. Add the following:

config zone
    option input 'ACCEPT'
    option forward 'REJECT'
    option output 'ACCEPT'
    option name 'usvpn'
    option masq '1'
    option network 'usvpn'

config zone
    option input 'ACCEPT'
    option forward 'REJECT'
    option output 'ACCEPT'
    option name 'ukvpn'
    option masq '1'
    option network 'ukvpn'

config forwarding
    option dest 'usvpn'
    option src 'lan'

config forwarding
    option dest 'ukvpn'
    option src 'lan'

Configuring multiple WAN connections

For this purpose, we need to utilise mwan3 load balancer as installed earlier. Edit /etc/config/mwan3 and delete all lines then add the following:

config rule 'default_rule'
    option dest_ip '0.0.0.0/0'
    option proto 'all'
    option sticky '0'
    option use_policy 'wan_only'

config policy 'wan_only'
    option last_resort 'unreachable'
    list use_member 'wan1'

config policy 'usvpn_only'
    list use_member 'usvpn1'
    option last_resort 'unreachable'

config policy 'ukvpn_only'
    list use_member 'ukvpn1'
    option last_resort 'unreachable'

config member 'wan1'
    option interface 'wan'

config member 'usvpn1'
    option interface 'usvpn'

config member 'ukvpn1'
    option interface 'ukvpn'

config interface 'wan'
    option enabled '1'
    option count '1'
    option timeout '2'
    option down '3'
    option up '8'
    option reliability '1'
    option initial_state 'online'
    option family 'ipv4'
    option track_method 'ping'
    list track_ip '8.8.8.8'
    list track_ip '8.8.4.4'
    option max_ttl '60'
    option check_quality '0'
    option interval '300'

config interface 'usvpn'
    option enabled '1'
    option count '1'
    option timeout '2'
    option down '3'
    option up '8'
    option reliability '1'
    option initial_state 'online'
    option family 'ipv4'
    option track_method 'ping'
    list track_ip '8.8.8.8'
    list track_ip '8.8.4.4'
    option max_ttl '60'
    option check_quality '0'
    option interval '300'

config interface 'ukvpn'
    option enabled '1'
    option count '1'
    option timeout '2'
    option down '3'
    option up '8'
    option reliability '1'
    option initial_state 'online'
    option family 'ipv4'
    option track_method 'ping'
    list track_ip '8.8.8.8'
    list track_ip '8.8.4.4'
    option max_ttl '60'
    option check_quality '0'
    option interval '300'

config globals 'globals'
    option local_source 'none'
    option mmx_mask '0xff00'
    option rtmon_interval '5'

This serves as a base template having 1 WAN and 2 VPN interfaces. Redundant routes can be configured, i.e. load balancing between two or more OpenVPN routes but I will not be demonstrating that.

Go to MWAN Rule Configuration in LuCI. You should see one rule defined there named default_rule. Rules placed below this will never be parsed. Useful for disabling rules without deleting them. Rules placed above will be parsed top-down.

Restart mwan3 and ensure internet still works as usual:

/usr/sbin/mwan3 restart

Multiple WAN rule configuration

I recommend using luci-app-mwan3 for configuring. If you prefer editing the rules on file directly, it is at /etc/config/mwan3.

Note that rule name must not be longer than 15 characters. Rules are matched top-down. Rule processing stops after the first match is found.

Connections coming from a source IP to be routed through specific VPN

Usage: When you want to enforce all-time VPN for a particular LAN machine.

config rule 'machine1_usvpn'
    option src_ip '192.168.x.x/32'
    option proto 'all'
    option sticky '0'
    option use_policy 'usvpn_only'

As added protection, you should add a firewall rule to deny this particular machine by MAC address from ever going out via direct WAN. Edit /etc/config/firewall and add the following:

config rule
    option src 'lan'
    option dest 'wan'
    option name 'Deny-machine1-WAN'
    option proto 'all'
    option src_mac 'xx:xx:xx:xx:xx:xx'
    option target 'REJECT'

Connections to a destination IP to be routed through specific VPN

Usage: When you know the destination IP and that never changes such as DNS server. An Anycast DNS server responds to lookup request with IP address of a server closest to it so performing lookup on a host name via default WAN or via a VPN may yield different results.

Do not use this on IP addresses that may change! There are better ways to handle that.

config rule 'pia_dns1'
    option dest_ip '199.85.126.10/32'
    option dest_port '53'
    option proto 'udp'
    option sticky '0'
    option use_policy 'usvpn_only'

Connections tagged with specific IPset tag to route through specific VPN

Usage: When the domain name is known but the IP address may change over time. You are likely to use this a lot.

Firstly, ensure that your VPN provider is not blocked by the service; setup a direct VPN to verify this.

After verifying, we can now proceed to setting up the IPset rules. Starting with something simple, I will detail how to setup for Pandora Internet Radio.

Edit /etc/dnsmasq.conf, add the following line:

ipset=/pandora.com/usvpn

This tags lookups made for pandora.com and its subdomains with usvpn.

Note: You need to install dnsmasq-full. The standard dnsmasq package will not start with the presence of ipset line. This also means you must have the pre-requisite packages installed before restoring backup (if you ever need to).

Edit /etc/config/dhcp, add the following line above the entry where your preferred DNS servers are set. Example:

config dnsmasq
    ...
    list server '/pandora.com/199.85.126.10'
    list server '8.8.8.8'

In the example above, 8.8.8.8 is Google’s DNS server. 199.85.126.10 is Norton ConnectSafe DNS Server. What this setup means is if we are performing a lookup on pandora.com (including its subdomains), the lookup will be performed by Norton’s while other domains will be resolved using Google’s.

In the previous section, I have detailed how to send traffic to VPN by a specific IP address and destination port number. You will likely need to setup that as a pre-requisite for this technique to work as an AnyCast DNS server will likely provide you an IP address closer to you instead of your VPN server.

Restart dnsmasq:

/etc/init.d/dnsmasq restart

Finally, configure mwan3 with the following rule to send all traffic with IPset of usvpn via the usvpn_only link.:

config rule 'ipset_usvpn'
    option proto 'all'
    option sticky '0'
    option ipset 'usvpn'
    option use_policy 'usvpn_only'

Identifying dependencies so that rules can be setup for them too

Usage: When a website links to third party domains for content.

Firstly, we need to use tools to help us capture all domain names that are implicitly contacted when we browse a website; it may then be loading content from server 1, server 2, etc. The list of these servers must be identified for this IPset method to work.

Using Chrome, navigate to chrome://net-internals/#dns. Clear the DNS cache. Some plugins are continuously pinging servers in the background. These should be disabled temporarily to prevent interference.

Navigate to the website using Chrome incognito mode and the list of DNS lookups made will begin to show up. You now need to setup IPset rules for these domains.

Allow IPv6 routing

Usage: If you want IPv6 to work while being able to use mwan3.

Under MWAN Policy Configuration, set the WAN policy’s “Last resort” to default (use main routing table).

config policy 'wan_only'
    list use_member 'wan_member'
    option last_resort 'default'

You should not set this for your VPN policies unless you intentionally want it to fallback to default routing if the VPN becomes unreachable.

Troubleshooting

Tweaking and restarting OpenVPN configuration or Firewall settings sometimes causes outgoing internet connections to fail

Try restarting mwan3, /usr/sbin/mwan3 restart. If this fails to restore connectivity, reboot the router.

Known issues

This solution does not work on LEDE mwan3 2.0

MWAN3 doesn’t support correctly WAN based on OpenVPN tun. As per suggestion by Kevin David in comments, please install mwan3 version 2.6.8 instead.