ASUS Merlin: Route via VPN for specific destination hosts

EDIT: 3rd Sept 2022 – Major updates & improvements

EDIT 8th Jan 2022 – A few improvements

  • You can now configure the interface for each domain.
  • FIXED: Rules now show as enabled in the GUI.
  • FIXED: Manually created rules are now preserved.

I’m a user of and a huge fan of the Merlin firmware for Asus routers. It rocks. I moved from DD-WRT a few years ago and haven’t looked back, much simpler and just as powerful.

One of the new features recently is the VPN Director. This lets you more easily set policy rules for what traffic should route via your VPN and even has a ‘kill switch’ which prevents that traffic from leaking out via your normal WAN connection if the VPN dies, which is great. However, it has a few limitations:

Merlin’s built in ‘kill switch’ has limitations

The ‘kill switch’ only applies to rules where you have specified a local IP. If you leave that blank to create a rule that applies to all local IPs trying to reach a specific destination IP, when the VPN is disabled, these packets will flow over the WAN. Damn.

No support for host names in destinations

You can only specify a destination IP or range, not a hostname. This sucks because it would be nice to have a policy rule to send all traffic to netflix.com (for example) via the VPN, but have all other traffic flow out directly over the WAN as normal.

This limitation makes sense because the sub-systems that handle this kind of routing use IP addresses not hostnames. Additionally, IPs change so you’d have to somehow keep doing DNS lookups and updating the rules. Annoying though – I wonder if there is a workaround?…

The solution

I’ve created a custom script that solves both problems by converting a list of hostnames to route via VPN into VPN Director policy rules as well as corresponding iptables rules to block traffic to those hosts routing via the WAN.

Limitations

Some domains (like netflix.com) use DNS-based load-balancing and as such, return different IPs each time you do a lookup.
This means that nslookup always gets different IPs and so the script will always think that IPs have changed.
It also means that clients will often be provided with an IP that is not the one the script got and so that traffic would slip past the checks and escape over the WAN 😦

I’ve tried to find a workaround for this, but sadly, I don’t think there is one. Providers like Netflix use huge IP ranges that change often. We don’t have a reliable way to get all of them at any time.
As such, I think it’s better to only use this script to direct specific traffic to smaller providers over the VPN, rather than send all traffic over the VPN and then add WAN exceptions.
Basically, don’t add hosts (like netflix.com) that have this issue.

I’ve also changed the cron job to update the rules run only once every 12 hours to reduce writes to JFFS.

Prerequisites

  • You should have already successfully configured your VPN and set the ‘Redirect Internet traffic through tunnel‘ setting to ‘VPN Director (policy rules)‘ mode.
  • You should be familiar with using user scripts and be familiar enough with scripting to be able to make sense of my script below. Don’t just copy/paste and hope you can trust me not to break your router.

Setup

As of Sept 3rd 2022, the code, installation instructions and testing advice are available in GitHub here:

https://github.com/kabadisha/host-based-vpn-routing

Enjoy!

34 Comments on “ASUS Merlin: Route via VPN for specific destination hosts”

  1. Hi, thanks for the script, butt there a serious flaw on it, it completely removes any custom rules one added manually.
    Perhaps its best to check for rules already there and then add new ones to it?

    • Ok, so I think I have fixed it. I have done limited testing, so let me know if it works ok for you.
      All generated rules get the prefix ‘AUTO-DNS-‘ so the script can separate them from manually created rules.

    • Ok, so NOW it’s working. Was fighting a weird bug where the manually created rules were being deleted only when the job ran in cron, not when run manually. Turned out to be a different PATH invoking different versions of grep. I thought I was going mad.

  2. Hi Charles

    Amazing work, thanks for doing this. Appreciate the detailed comments within, makes it easier to see what’s going on. Quality work.

    Q: can this be used in the opposite? I want to funnel all traffic from a local IP thru the VPN and have certain domains/hostnames whitelisted so they just pass thru regular WAN.

    • Let me quickly note that I enabled the VPN connection for a specific streaming device (local IP) and then hostname|WAN’d all the hostnames I want to “whitelist”, but that didn’t work. Am I doing it right?

  3. Just a quick comment to those who are just getting to grips with split tunnelling. I found it easier to push a “service” such as netflix through the WAN by using x3mRouting Utility Scripts (https://github.com/Xentrk/x3mRouting) to “watch” for the domain names for the “service” in question (could be anything BBCiPLAYER, IPTV etc) I then added these to the vpn_director_host_rules.sh. That meant that I can set these as WAN only and the set all other IP’s as VPN (192.168.x.x/24).

  4. Thanks for the post and script!

    What would be the implications of replacing “nslookup $HOST” with “nslookup $HOST localhost”? Any idea if the local DNS server is up when firewall-start runs?

    One of the destination hosts I need to route through VPN is blocked by my ISP, not only the routing but also DNS queries (for example, running nslookup HOST 8.8.8.8 will time out due to DNS hijacking). I solved this by enabling DNS over TLS through the WAN configuration page, which works for the router clients but will not be used when running “nslookup $HOST”.

    • Answering my own question from above – I decided to install dig using entware / opkg and replaced the whole “for IP” line with the following:

      for IP in $(dig +short +tls $HOST @8.8.8.8 | sort); do

      Now the script’s DNS queries are performed securely without my ISP hijacking them.

  5. Hi, thank you for this great solution! This is exactly what I searched for. Im am Using the script for the vice versa case: I am routing everything through the vpn except domains listet in the script with
    …|WAN

    Two problems/findigs with this:
    1. The entries with |WAN are also added from the script in the IPTABLES “CUSTOM_FORWARD” with REJECT. This does not make sense as this traffic should be routed through WAN and this prevents me from accessing these sites. I changes this iptables-line in the script to
    iptables -I CUSTOM_FORWARD l -d $IP -o eth0 -j ACCEPT
    because I don´t need a Kill-Switch and I can access these websites when changed.

    2. In the VPN Director the rules are listed in the gui and look good, but when I access a website of these, the traffic still goes through the vpn. I have to manually go to the gui at vpn -> vpn director and push the “APPLY”-Button at the buttom of the side. Then the rules work as expected. This is also, when IPs change an additional rules are added by the script. Every time I have to manually push the “APPLY”-Button on the gui that these rules are applied/work.
    How can this be fixed and the rules apply without manual interaction?

    Thanks for the great work!

    Santa
    P.S.: Im am on Firmware-Version 386.8 on an ASUS RT-AX88U

  6. Here some more explanation for my “problem #2” above:

    Your Script executes
    service restart_vpnrouting0
    But in the systemlog, this only applies/executes the rules with target/Iface “OVPN1″ from vpn director rules not the ones with target/Iface”WAN” in it. Here what the systemlog shows every 10 minutes (cronjob):

    Sep 2 17:40:05 rc_service: service 14330:notify_rc restart_vpnrouting0
    Sep 2 17:40:06 openvpn-routing: Routing Alles in den VPN-Tunnel routen from 0.0.0.0/0 to any through ovpnc1
    Sep 2 17:40:06 openvpn-routing: Routing DNS-AUTO-whatismyipaddress.com from any to 104.16.154.36 through ovpnc1
    Sep 2 17:40:06 openvpn-routing: Routing DNS-AUTO-whatismyipaddress.com from any to 104.16.155.36 through ovpnc1

    But when I click the “APPLY”-Button on the gui (VPN->VPN Director->APPLY) not only the rules with target/Iface “OVPN1” are executed, but also the ones with target/Iface “WAN” (there are DNS-AUTO-rules with target/Iface WAN that I need, that I added in your script with “…|WAN”). Here what the systemlog shows when I manually click the “APPLY”-Button in the gui (example):

    Sep 2 17:44:41 rc_service: httpds 1423:notify_rc restart_vpnrouting0
    Sep 2 17:44:41 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.8.109 through main
    Sep 2 17:44:41 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.9.109 through main
    Sep 2 17:44:41 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 172.67.73.20 through main
    Sep 2 17:44:41 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.38 through main
    Sep 2 17:44:41 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.39 through main
    Sep 2 17:44:41 openvpn-routing: Routing DNS-AUTO-www.paypal.de from any to 151.101.65.21 through main
    Sep 2 17:44:41 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.36 through main
    Sep 2 17:44:41 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.37 through main
    Sep 2 17:44:41 openvpn-routing: Routing DNS-AUTO-www.paypal.com from any to 151.101.65.21 through main
    Sep 2 17:44:42 openvpn-routing: Routing Alles in den VPN-Tunnel routen from 0.0.0.0/0 to any through ovpnc1
    Sep 2 17:44:42 openvpn-routing: Routing DNS-AUTO-whatismyipaddress.com from any to 104.16.154.36 through ovpnc1

    How can I change the script, that all vpn director rules are applied/executed and NOT only the ones with target/Iface OPVN1? Because with the “service restart_vpnrouting0” in your script, the rules with target/Iface WAN are not used until I manually click the “APPLY”-Button in the gui … and I have to do this every 10 minuten (cronjob) after the script has run or my target/Iface WAN rules are not used/active.

    Thank you in Advance!
    Santa

    • Hey Santa,
      I have actually been working on a number of improvements to my scripts which I have now published on GitHub. You can find the link in the updated post above.

      I believe that the new version fixes problem 1 that you are facing.
      Problem 2, I’m not too sure about because I can’t test it here. However, I have an idea:
      1. Update to the new scripts and make sure it’s all working as expected.
      2. If you are still having the issue where you have to hit apply to get your WAN rules to work, then add service restart_dnsmasq after service restart_vpnrouting in vpn-director-hosts-update.sh

      Let me know if it works 🙂

  7. Hi Charles,
    I haven´t tested the new script yet, but I tried to add service restart_dnsmasq after service restart_vpnrouting, but it does not help.
    In the systelog only the ovpn1-rules are applied and not the wan-rules:

    Sep 3 13:16:01 rc_service: service 3069:notify_rc restart_vpnrouting
    Sep 3 13:16:01 openvpn-routing: Routing Alles in den VPN-Tunnel routen from 0.0.0.0/0 to any through ovpnc1
    Sep 3 13:16:01 openvpn-routing: Routing DNS-AUTO-whatismyipaddress.com from any to 104.16.154.36 through ovpnc1
    Sep 3 13:16:01 openvpn-routing: Routing DNS-AUTO-whatismyipaddress.com from any to 104.16.155.36 through ovpnc1
    Sep 3 13:16:51 rc_service: service 3433:notify_rc restart_dnsmasq

    any ideas?

    Santa

    • I suppose you could try service restart_wan, which will probably work, but it’s quite aggressive. It will literally refresh your whole WAN connection.

      Sadly, I couldn’t find any other command that will do what you need. We might have to look at the source code that gets triggered when you hit the button in the UI to see what commands it’s using. I’ll try and find it when I get some more time.

    • I’ve also noticed that for some sites (like Netflix) nslookup returns different results each time due to load balancing at the DNS level. As such, the rules get updated every time the script runs which is not great.

      I’m working on a fix for that too.

  8. Hi Charles,

    with the new script(s) and the
    service restart_vpnrouting
    in it, the vpn director rules are now applied correctly including the rules with the target/Iface WAN.
    THANK YOU VERY MUCH!

    Santa

  9. I let the script as it is.
    When I execute the command “service restart_vpnrouting” in SSH-Session, I see in systemlog that all rules are applied as expected including the WAN-Rules, But the WAN-rules (“main” in systemlog)) are applied five times and not only once. Example:

    Sep 3 15:59:27 rc_service: service 8470:notify_rc restart_vpnrouting
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.8.109 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.9.109 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 172.67.73.20 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.38 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.39 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-www.paypal.de from any to 151.101.65.21 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.36 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.37 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-www.paypal.com from any to 151.101.1.21 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.8.109 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.9.109 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 172.67.73.20 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.38 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.39 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-www.paypal.de from any to 151.101.65.21 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.36 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.37 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-www.paypal.com from any to 151.101.1.21 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.8.109 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.9.109 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 172.67.73.20 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.38 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.39 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-www.paypal.de from any to 151.101.65.21 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.36 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.37 through main
    Sep 3 15:59:28 openvpn-routing: Routing DNS-AUTO-www.paypal.com from any to 151.101.1.21 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.8.109 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.9.109 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 172.67.73.20 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.38 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.39 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-www.paypal.de from any to 151.101.65.21 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.36 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.37 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-www.paypal.com from any to 151.101.1.21 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.8.109 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 104.26.9.109 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-ipchicken.com from any to 172.67.73.20 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.38 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-paypal.de from any to 64.4.250.39 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-www.paypal.de from any to 151.101.65.21 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.36 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-paypal.com from any to 64.4.250.37 through main
    Sep 3 15:59:29 openvpn-routing: Routing DNS-AUTO-www.paypal.com from any to 151.101.1.21 through main
    Sep 3 15:59:29 openvpn-routing: Routing Alles in den VPN-Tunnel routen from 0.0.0.0/0 to any through ovpnc1

    Bute besides of the five-times-execution, the WAN-/main-rules work as needed.
    THANK YOU!

  10. As additional info to my last reply:

    When I press the “APPLY”-Button in the GUI, the rules with target/Iface WAN are applied five times, too. So this seems to be by-design and is no bug of your script.

    But I still have the problem, that the WAN-Rules are only applied, when I execute the command “service restart_vpnrouting” my myslf in the SSH-Session.
    When it is esecuted my your script, then again ONLY THE OVPN-rules are applied and NOT the WAN rules. Here the systemlog when the command is executed by your script:

    Sep 3 16:30:09 rc_service: service 6621:notify_rc restart_vpnrouting
    Sep 3 16:30:09 openvpn-routing: Routing Alles in den VPN-Tunnel routen from 0.0.0.0/0 to any through ovpnc1
    Sep 3 16:30:09 openvpn-routing: Routing DNS-AUTO-whatismyipaddress.com from any to 104.16.154.36 through ovpnc1
    Sep 3 16:30:09 openvpn-routing: Routing DNS-AUTO-whatismyipaddress.com from any to 104.16.155.36 through ovpnc1

    In my last reply (3:16 pm) you see the result when the command is executed my hand in ssh-session.

    Why is there a difference if the command is executed by script or by hand? When executed by your script, the WAN-rules are not applied at the moment.
    Any idea?

  11. And directly a additional info to my last reply:
    Now even with manually executing “service restart_vpnrouting” in SSH-Session, only the OVPN-rules are applied and no WAN-Rule. But I don´t know why!!

    As seen in my comment at 3:16pm, it worked at this time, when I executed it per hand. But now it does not work anymore with “service restart_vpnrouting”.

    • Hi Santa,
      I’ve taken a look at this and I think the logging in the System Log in the UI is inconsistent. I’ve seen similar differences to you in the logs depending on how the command is triggered, but the rules do work. I don’t know why this is.

      Can you try testing with traceroute?
      e.g. traceroute netflix.com compared to traceroute whatismyipaddress.com

      Traceroute should show you if the connection is going via WAN or via VPN based on the hops you see.

  12. Hi Charles,

    I have checked with sites like https://ipchicken.com and https://whatismyipaddress.com (opened in browser from a client pc) but unfortunately the systemlog is right: the WAN-rules are only applied when hitting the “APPLY” button in VPN Director and not when the script or manual in a SSH session “service restart_vpnrouting” is triggered.

    But I still don´t know, why this worked one time at 3:16 pm, and than no more.

    • In that case, I’m afraid I am out of ideas 😦

      By the way, if you click on the ‘Reply’ button on this comment, WordPress will keep our conversation grouped under a single comment thread, which makes it more organised.

      • I am too out of ideas.
        I now comment the “service restart_vpnrouting” out in the script to avoid discarding my WAN-rules every 10 minutes and will hit the APPLY-Button manually in GUI when I need it.
        A source code review of the APPLY-Button of VPN Director is to time-consuming only for this.
        THANK YOU FOR YOUR EFFORT!
        Santa

        • No problem and sorry I can’t seem to fix it for you.
          I did look at the source code, but I can’t follow it properly – it’s quite hard to see how it works.
          From what I can see, it looks like it is just calling restart_vpnrouting0. You can see it here on line 474

          Please let me know if you ever figure it out.

        • FYI, I have added a ‘Limitations’ section to the readme that I think you should take a look at. I’ve found a pretty big issue with the script today and I don’t think it’s a bug I can fix.

  13. Hi Charles,
    thank you. I read the “Limitations” and a solution could be, that the in the last runs “collected” IPs / rules would not be deleted in the next run of the script but with each run of the script you add the new IPs to the in the past collected list and the list will grow larger and larger each iteration (and will become better and better). And with the 10´s to x´s iteration of the script you will have all possible IP adresses of netflix and co. The problem could be, that the VPN Director only allows at maximum 199 entries in the list (as mentioned in the gui).

Leave a reply to John Cancel reply