in Networking

How to Route Selected IPs over VPN on an EdgeRouter (EdgeOS)

Recently I had a need to route certain traffic on the internal network over a VPN. As I use a Ubiquiti EdgeRouter this howto guide is the result of my efforts.

This guide assumes you have a working OpenVPN connection on the interface vtun0. Keep in mind, your show commands may show extra information based on your individual network setups as well however for brevity I have just included the relevant sections.

Create a new address group containing the LAN IPs you want to send over the VPN. Simply add all the internal IPs you wish to route over the network by repeating the second command.

Alternatively, you can specify a range or subnet instead of a single IP. You can also create the group in the UI if you wish under Firewall/NAT > Firewall/NAT Groups > Add Group.

[email protected]# set firewall group address-group VPN description "VPN IPs to route"
[email protected]# set firewall group address-group VPN address 192.168.1.110
[email protected]# set firewall group address-group VPN address 192.168.1.120-192.168.1.130
[email protected]# set firewall group address-group VPN address 192.168.2.0/24
[email protected]# show firewall group                  
 address-group VPN {
     address 192.168.1.110-192.168.1.115
     description ""
 }

Create a second routing table to route over the VPN interface. We will use table 2 to route all traffic marked for the table over the vtun0 interface.

[email protected]# set protocols static table 2 interface-route 0.0.0.0/0 next-hop-interface vtun0
[email protected]# show protocols static table 2
 interface-route 0.0.0.0/0 {
     next-hop-interface vtun0 {
     }
 }

Create a firewall modify rule to modify the routing table we use for VPN traffic. We will mark all traffic from the source address group we created in step 1 to use routing table 2 we created in step 2.

[email protected]# set firewall modify VPN_IP rule 10 action modify
[email protected]# set firewall modify VPN_IP rule 10 description "VPN routing"
[email protected]# set firewall modify VPN_IP rule 10 source group address-group VPN
[email protected]# set firewall modify VPN_IP rule 10 modify table 2
[email protected]# show firewall modify
 modify VPN_IP {
     rule 10 {
         action modify
         description "Host-specific route"
         modify {
             table 2
         }
         source {
             group {
                 address-group VPN
             }
         }
     }
 }

Setup NAT masquerading for VPN interface to ensure that we have bi-directional traffic between the address group and VPN.

[email protected]# set service nat rule 5002 outbound-interface vtun0
[email protected]# set service nat rule 5002 type masquerade
[email protected]# show service nat
 rule 5002 {
     log enable
     outbound-interface vtun0
     type masquerade
 }

Finally, run the modify firewall rules on switch0 traffic (may differ depending on your LAN setup). Once this command is committed all traffic from the VPN address group will commence using the VPN interface.

[email protected]# set interfaces switch switch0 firewall in modify VPN_IP
[email protected]# show interfaces switch switch0 firewall 
 in {
     modify VPN_IP
 }

After these steps you should have the selected VPN clients going out over the VPN, however, there is one caveat, these clients will still be using the DNS servers that your router specifies, whether that is itself or a third party DNS such as Google.

If you wish for the VPN clients to use the DNS server that is pulled down from OpenVPN, which is useful for performing geo-location on Netflix for example, one additional step is required.

I like to use DNAT on the router to intercept all port 53 (DNS) traffic from the VPN clients and then force it to a specific DNS server. While you can easily create a hardcoded entry, if your VPN server sends a different DNS server each time depending on where you connect, we can use an up script to automate the DNAT rule with the dynamic DNS server IP.

[email protected]# show service nat rule 4 
 description "VPN DNS"
 destination {
     port 53
 }
 inbound-interface switch0
 inside-address {
     address 10.89.0.1
     port 53
 }
 log disable
 protocol tcp_udp
 source {
     group {
         address-group VPN
     }
 }
 type destination
[email protected]# set service nat rule 4 inside-address address 10.2.1.1

We can automate updating the destination DNS server using an OpenVPN up/down script that parses the DNS server from the DHCP options and sets the address.

First, ensure the following config options are set in the OpenVPN configuration file. route-noexec is used to exclude VPN routing but still allows the up/down script to receive the foreign_option_* shell variables.

route-noexec

script-security 2
up /config/scripts/openvpn/update-resolv-conf-dnat
down /config/scripts/openvpn/update-resolv-conf-dnat

Next up create the shell script in /config/scripts/openvpn to ensure it survives firmware upgrades.  The script will run each time the VPN reconnects and it will update the destination IP with the nameserver passed over DHCP.  Be sure to replace rule 4 with the correct rule number for your system.

#!/bin/bash
#
# Parses DHCP options from openvpn to update resolv.conf
# To use set as 'up' and 'down' script in your openvpn *.conf:
# up /config/scripts/openvpn/update-resolv-conf-dnat
# down /config/scripts/openvpn/update-resolv-conf-dnat
#
# Used snippets of resolvconf script by Thomas Hood and Chris Hanson.
# Licensed under the GNU GPL.  See /usr/share/common-licenses/GPL.
#
# Example envs set from openvpn:
#
#     foreign_option_1='dhcp-option DNS 193.43.27.132'
#     foreign_option_2='dhcp-option DNS 193.43.27.133'
#     foreign_option_3='dhcp-option DOMAIN be.bnc.ch'
#

[ "$script_type" ] || exit 0
[ "$dev" ] || exit 0

split_into_parts()
{
        part1="$1"
        part2="$2"
        part3="$3"
}

case "$script_type" in
  up)
        NMSRV=""
        for optionvarname in ${!foreign_option_*} ; do
                option="${!optionvarname}"
                split_into_parts $option
                if [ "$part1" = "dhcp-option" ] ; then
                        if [ "$part2" = "DNS" ] ; then
                                NMSRV="$part3"
                        fi
                fi
        done
        [ "$NMSRV" ] || exit 0
        cw=/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper    
        $cw begin                                     
        $cw set service nat rule 4 inside-address address $NMSRV
        $cw commit                                              
        ;;                                                      
  down)                                                         
        ;;                                                      
esac