Selective Routing Using DD-WRT & OpenVPN

 

This is a quick guide on how I managed to configure OpenVPN on DD-WRT such that only traffic from some LAN clients and some ports is routed over the VPN tunnel.

Recently I signed up for a nice little VPN service Tiger VPN which so far seems quite nice – FYI, they have not asked me to recommend them or paid me in any way to say nice things about them. I just genuinely like the service 🙂

They supply OpenVPN configuration files so that you can easily roll your VPN client of choice if you don’t want to use their own client and they even have a script to automagically install on your DD-WRT enabled router.

The problem is that by default the setup will route all of your network’s outbound connections over the VPN.

I have managed to create a configuration which does the following:

  • Routes all traffic from a single LAN IP over the VPN.
  • Blocks traffic from the same LAN IP from reaching the internet when the VPN is down
    • This is often referred to as a kill switch.
  • Routes all traffic destined for a specific port over the VPN
  • Prevents requests to the same port when the VPN is down

Under Administration > Commands in the DD-WRT GUI, save the following as a custom script:

#!/bin/sh
# Some MASQUERADE line that I don't really understand.
iptables -I POSTROUTING -t nat -o tun1 -j MASQUERADE

# Set the default route for table 200 as over the VPN
ip route add default dev tun1 table 200

# Assign all outgoing connections from 192.168.11 to table 200 (so they go over the VPN)
ip rule add from 192.168.1.11 table 200

# Assign all packets marked with 11 to table 200 (so they go over the VPN)
ip rule add fwmark 11 table 200

# Flush the cache
ip route flush cache

# Mark all tcp packets whos destination port is 563 with 11 (so that it will be routed over the VPN)
iptables -t mangle -I PREROUTING --dport 563 -j MARK --set-mark 11

Under Administration > Commands, save the following to the firewall script:

# Prevent 192.168.1.11 from reaching the internet directly (so no connection if VPN down)
iptables -I FORWARD -s 192.168.1.11 -o vlan2 -j DROP

# Prevent 192.168.1.11 from connecting to port 563 directly (so no connection if VPN down)
iptables -I FORWARD -s 192.168.1.11 --dport 563 -o vlan2 -j DROP

The next step is a little bit trickier. Basically you have to use the DD-WRT GUI to configure as many of the OpenVPN settings as you can (based on what your provider specifies) and then do the rest in the ‘additional config’ box.

Use the DD-WRT OpenVPN documentation to help you identify which GUI settings correspond to which OpenVPN properties. Each time you click ‘Apply Settings’ you can check the raw generated config vis ssh by looking at /tmp/openvpncl/openvpn.conf

My settings for TigerVPN look like this (username and password removed obviously):

DD-WRT OpenVPN Settings

Under additional config for OpenVPN client I have the following:

# Write to a log file for easy viewing
log /tmp/tigervpn.log

# Mute messages that repeat a bunch of times
mute 50

# Do not accept the routes provided by the VPN server
# (will manage those myself)
route-nopull

# Keep the connection alive and attempt to reestablish it if it dies
keepalive 10 60

# Additional settings specified by VPN provider
tls-client
remote-cert-tls server

# Dont use auth-nocache as it prevents reconnection due to a bug
# auth-nocache

# Script to run when the link is established
# This sets up my custom routes and iptables rules
up /tmp/custom.sh

The really important setting there was the route-nopull. What this does is prevent the OpenVPN client from automatically installing routes provided by the VPN server. We do this because we want to use the routes set up in our scripts.

The final generated OpenVPN config (viewable under ‘/tmp/openvpncl/openvpn.conf’ on the router) looks like this:

ca /tmp/openvpncl/ca.crt
management 127.0.0.1 16
management-log-cache 100
verb 3
mute 3
syslog
writepid /var/run/openvpncl.pid
client
resolv-retry infinite
nobind
persist-key
persist-tun
script-security 2
dev tun1
proto udp
cipher aes-256-cbc
auth sha1
auth-user-pass /tmp/openvpncl/credentials
remote zur.tigervpn.com 1194
comp-lzo adaptive
tun-mtu 1500
mtu-disc yes
fast-io
tun-ipv6
# Write to a log file for easy viewing
log /tmp/tigervpn.log

# Mute messages that repeat a bunch of times
mute 50

# Do not accept the routes provided by the VPN server
# (will manage those myself)
route-nopull

# Keep the connection alive and attempt to reestablish it if it dies
keepalive 10 60

# Additional settings specified by VPN provider
tls-client
remote-cert-tls server

# Script to run when the link is established
# This sets up my custom routes and iptables rules
up /tmp/custom.sh

 

28 Comments on “Selective Routing Using DD-WRT & OpenVPN”

  1. This is incredibly helpful – thank you very much for sharing this valuable info. I will try it in due course.

    • You are most welcome 🙂
      Let me know how you get on and check back on the page for updates – I have some ideas about routing based on hostname that I’m going to try tomorrow…

  2. Instead of only using 192.168.1.11, how would it be to use a VAP (virtual access point) instead?

    Thus, the real access point would route traffic through the normal internet connection (with the 192.168.1.0/24 range), which your VAP would router through the vpn, using the range of 192.168.2.0/24.

    It’s just something I would like to set up as well.

    • I’m not 100% sure, but I would imagine that you just replace 192.168.1.11 in the scripts above with 192.168.2.0/24. Like this:

      # Prevent the whole 192.168.2.xxx range from reaching the internet directly (so no connection if VPN down)
      iptables -I FORWARD -s 192.168.2.0/24 -o vlan2 -j DROP
       
      # Prevent the whole 192.168.2.xxx range from connecting to port 563 directly (so no connection if VPN down)
      iptables -I FORWARD -s 192.168.2.0/24 --dport 563 -o vlan2 -j DROP
      

      FYI – I removed the -i interface flag so that we are just matching based on IP – which works fine.

      You’d have to test it. Let me know if you get it working.

      Cheers,
      Charlie

  3. Great article, I am new to this DD-WRT world. Your article helps quite a bit. What if I my private ip address is not just one ip like yours 192.168.2.11? Should I just swap the ip with the whole network id like 192.168.1.0 and use the command. For command that marks the packets flags? In your example, you use 11 which matches the last octets. Can the flag be some other value like “02”? and is it a hex value?

    • Hi Khan,
      I was targeting a specific IP so just stuck it’s IP in.
      If you want to match a whole set of IPs I believe you can use a netmask. For example, to match the whole subnet from 192.168.1.1 to 192.168.1.254 you can use the following value: 192.168.1.1/24

      The logic behind netmasks has always confused me to be honest, but there are plenty of online calculators to help like this one: https://www.iplocation.net/subnet-calculator

      Hope this helps

  4. Thanks for putting this together – got really hopeful when I found it, but unfortunately Its hasn’t been working for me.

    Basically, I can’t establish a VPN connection when using the custom script.
    I can only get a VPN connection when I comment out ‘up /tmp/custom.sh’ call – which suggests (to me) that it’s the custom script causing the problem?

    The only modification I’ve made are
    – change IP to …1.25 (from …1.11)
    – change the tag to 25 (from 11)
    – change the network to ‘vlan1’ (from vlan2 – this is because on my status page, there’s not vlan2 running, just vlan1, so I assumed you’re running yours on your second ethernet connect?)
    – change port to 51921 (from 563 – because the port that my IP/application is listening on is 51921 (this is also the port that my VPN opens).The application’s GUI is listening on 8181, but I didn’t think this was the one to use – that sound right?)

    Beyond that, everything is copy/paste what you have above.

    Any Ideas what I am doing wrong?

    • Not sure, but it seems like maybe it’s trying to run before tun1 is established/exists?
      Is there a way to get it to run only when tun1 has established connection? Somew kind of try loop?

      I’ve little/no coding experience, but is there something to the effect of

      ‘tun1 up /tmp/custom.sh’ ? with a re-try elements so it keeps attempting until tun1 is up?

      This guy has done something similar using the older dd-wrt build instructions (from PIA), but it looks to be attempting the same thing – just without the tagging and port recognition.
      There’s also a bug with this one that it attempts to run ‘ip route add default dev tun1 table 200’ on startup. As tun1 has been established yet, it just fails to filter the IP traffic, so when the vpn comes up, nothing runs through it (not even global network traffic.). To fix it, you just have to run the ‘ip route add default dev tun1 table 200’ on an ad-hoc basis – which works, I assume, because by this point tun1 is up.

  5. This guide was exactly what I was looking for. Very useful indeed.

    The only problem I’m having is that when I kill the VPN using `killall openvpn`, the traffic from the IP address I’ve chosen (192.168.0.11 in your example) reverts to being routed via my ISP. I’m wondering if the `vlan2` interface isn’t correct for my setup. How did you determine which interface is being used for the internet connection? I’ve tried using a wildcard `vlan+`, as per the `iptables` manual, however this doesn’t help.

    • Using the invert argument, ! before the VPN interface name solved it for me in the end:
      iptables -I FORWARD -s 192.168.0.11 -o ! tun1 -j DROP

  6. Thank you so much for your dd-wrt work. You have been an immense help.
    I assume you are using asus rt-n66u still (you wrote the dd-wrt bible for that one).

    Have you ever tested to see if disabling wireless access to router works?

    I’m using build 29974 and no matter what I try I can still log into the webgui thru wireless (that’s a security risk, aircrack my wpa2 key and then brute or just sniff http when I log in-https has issues)

    • Hi Mike,
      Thanks! – Glad it’s been helpful 🙂
      Yes, i’m still using the n66u, but no I haven’t tried blocking the web GUI over WiFi.
      If the option isn’t working, then I would try blocking it manually using iptables.
      Personally though, I wouldn’t bother – someone spending the time to crack my home network is a pretty low risk.

      Charlie

      • Great Thread Charles. I would love to have code options like this with a corresponding GUI for DD-WRT and OpenVPN. I use an OpenVPN compatible VPN with my DD-WRT 1200AC Router. I would love to be able to select traffic to not be routed through the VPN from a list of top Region-sensitive streaming services. In my case, I’m trying to solve this for Amazon, but I also use other streaming services.

  7. Nice write-up, I think it’s very close to what I might need. How can I route all traffic through the VPN tunnel with the exception of specific IP destinations? More specifically, I’m being blocked from Netflix when I run through VPN. I’d like to bypass the VPN for netflix IP’s and ALL other traffic be tunneled. I know, I know….netflix has a ton of IP’s – let me worry about managing that aspect. Any thoughts?

  8. HI,
    Thank you for your write up it helped me a lot to configure the client. Is it possible to run the client and the server simultaneously, or only client mode or server mode can run at once?

    Thank you very much

  9. I am able to successfully route specific ports through VPN configured on my router but as I have transmission & aria2 installed on my router, the downloads go through the VPN. Is it possible to make transmission download using my ISP instead of VPN

  10. I get this error in the log file, looks like the custom.sh is not created on time for it to be recognized… any idea? TIA

    Options error: –up script fails with ‘/tmp/custom.sh’: No such file or directory
    Options error: Please correct this error.
    Use –help for more information.

      • I got it working to route all traffic on second Wifi over the VPN except I can’t remember exact steps. My notes had the below and I used this guide when I was doing it

        Startup: sh /jffs/etc/config/splittunneling.wanup
        chmod 777 /jffs
        chmod 777 /jffs/etc
        chmod 777 /jffs/etc/config
        chmod 777 /jffs/etc/config/splittunneling.wanup

        #!/bin/sh
        ISP_GATEWAY=$(ip route show default | awk ‘/default/ {print $3}’)
        ip rule add from 172.20.31.1/24 table 200
        ip rule add from 172.20.30.19 table 200
        ip route add default via ${ISP_GATEWAY} dev eth0 table 200
        ip route flush cache

        http://jeffrice.net/2016/09/29/selective-split-vpn-routing-for-dd-wrt-whitelisting-servers-and-clients/

        • Thanks.
          I think it’s more a timing problem. I just got the error after a reboot. If I initialize the VPN later again, the same script will be executed.

  11. What if I wanted to do the opposite? – route all traffic over the VPN EXCEPT a particular IP or interface?

  12. You did not create this config. Kabadisha on the DD WRT forums did and you just copied and pasted from this link. Give credit where it’s due.

    dd-wrt.com/phpBB2/viewtopic.php?p=941308

  13. Hi thanks for the detailed write up. Any suggestions if I want all the clients to go through VPN except a few? I have tried policy based routing and but that doesn’t work (vpn shows connected, but no traffic passes through). I have the clients I want excluded from the VPN on a static lan address outside of the DHCP ip range. Thanks a bunch!

  14. Hi Charles

    Is there a way to route traffic to a specific domain around the VPN? I’d like to put devices like Apple TV behind the VPN but I often get apps (particularly the ones like Netflix) either complaining about a VPN or I get the equivalent of a 404. So I leave the Apple TV in an address range outside the VPN using policy-based routing. Thanks.

Leave a reply to Fer Cancel reply