Policy-based routing on Linux to forward packets from a subnet or process through a VPN

In my last post, I covered how to route packages from a specific VLAN through a VPN on the USG. Here, I will show how to use policy-based routing on Linux to route packets from specific processes or subnets through a VPN connection on a Linux host in your LAN instead. You could then point to this host as the next-hop for a VLAN on your USG to achieve the same effect as in my last post.

Note that this post will assume a modern tooling including firewalld and NetworkManager, and that subnet is your LAN. This post will send packets coming from to VPN, but you could customize that as you see fit (e.g. send specific only hosts from your normal LAN subnet instead).

VPN network interface setup

First, let's create a VPN firewalld zone so we can easily apply firewall rules just to the VPN connection:

firewall-cmd --permanent --new-zone=VPN
firewall-cmd --reload

Next, create the VPN interface with NetworkManager:


# Setup VPN connection with NetworkManager
dnf install -y NetworkManager-openvpn
nmcli c add type vpn ifname vpn con-name vpn vpn-type openvpn
nmcli c mod vpn connection.zone "VPN"
nmcli c mod vpn connection.autoconnect "yes"
nmcli c mod vpn ipv4.method "auto"
nmcli c mod vpn ipv6.method "auto"

# Ensure it is never set as default route, nor listen to its DNS settings
# (doing so would push the VPN DNS for all lookups)
nmcli c mod vpn ipv4.never-default "yes"
nmcli c mod vpn ipv4.ignore-auto-dns on
nmcli c mod vpn ipv6.never-default "yes"
nmcli c mod vpn ipv6.ignore-auto-dns on

# Set configuration options
nmcli c mod vpn vpn.data "comp-lzo = adaptive, ca = /etc/openvpn/keys/vpn-ca.crt, password-flags = 0, connection-type = password, remote = remote.vpnhost.tld, username = $VPN_USER, reneg-seconds = 0"

# Configure VPN secrets for passwordless start
cat << EOF >> /etc/NetworkManager/system-connections/vpn

systemctl restart NetworkManager

Configure routing table and policy-based routing

Normally, a host has a single routing table and therefore only 1 default gateway. Static routes can be configured for next-hops, this is configuring the system to route based a packet's destination address, and we want to know how route based on the source address of a packet. For this, we need multiple routing tables (one for normal traffic, another for VPN traffic) and Policy Based Routing (PBR) to define rules on how to select the right one.

First, let's create a second routing table for VPN connections:

cat << EOF >> /etc/iproute2/rt_tables
100 vpn

Next, setup an IP rule to select between routing tables for incoming packets based on their source addres:

# Replace this with your LAN interface

# Route incoming packets on VPN subnet towards VPN interface
cat << EOF >> /etc/sysconfig/network-scripts/rule-$IFACE
from table vpn

Now that we can properly select which routing table to use, we need to configure routes on the vpn routing table:

cat << EOF > /etc/sysconfig/network-scripts/route-$IFACE
# Always allow LAN connectivity dev $IFACE scope link metric 98 table vpn dev $IFACE scope link metric 99 table vpn

# Blackhole by default to avoid privacy leaks if VPN disconnects
blackhole metric 100 table vpn

You'll note that nowhere do we actually define the default gateway - because we can't yet. VPN connections often dynamically allocate IPs, so we'll need to configure the default route for the VPN table to match that particular IP each time we start the VPN connection (we'll do so with a smaller metric figure than the blackhole above of 100, thereby avoiding the blackhole rule).

So, we will configure NetworkManager to trigger a script upon bringing up the VPN interface:

cat << EOF > /etc/NetworkManager/dispatcher.d/90-vpn
VPN_UUID="\$(nmcli con show vpn | grep uuid | tr -s ' ' | cut -d' ' -f2)"

if [ "\$CONNECTION_UUID" == "\$VPN_UUID" ];then
  /usr/local/bin/configure_vpn_routes "\$INTERFACE" "\$ACTION"

In that script, we will read the IP address of the VPN interface and install it as the default route. When the VPN is deactivated, we'll do the opposite and cleanup the route we added:

cat << EOF > /usr/local/bin/configure_vpn_routes
# Configures a secondary routing table for use with VPN interface


zone="\$(nmcli -t --fields connection.zone c show vpn | cut -d':' -f2)"

clear_vpn_routes() {
  /sbin/ip route show via 192.168/16 table \$table | while read route;do
    /sbin/ip route delete \$route table \$table

clear_vpn_rules() {
  keep=\$(ip rule show from 192.168/16)
  /sbin/ip rule show from 192.168/16 | while read line;do
    rule="\$(echo \$line | cut -d':' -f2-)"
    (echo "\$keep" | grep -q "\$rule") && continue
    /sbin/ip rule delete \$rule

if [ "\$action" = "vpn-up" ];then
  ip="\$(/sbin/ip route get oif \$interface | head -n 1 | cut -d' ' -f5)"

  # Modify default route
  clear_vpn_routes \$vpn_table
  /sbin/ip route add default via \$ip dev \$interface table \$vpn_table

elif [ "\$action" = "vpn-down" ];then
  # Remove VPN routes
  clear_vpn_routes \$vpn_table
chmod 755 /usr/local/bin/configure_vpn_routes

Bring up the VPN interface:

nmcli c up vpn

That's all, enjoy!

Sending all packets from a user through the VPN

I find this technique particularly versatile as one can also easily force all traffic from a particular user through the VPN tunnel:

# Replace this with your LAN interface

# Username (or UID) of user who's traffic to send over VPN

# Send any marked packets using VPN routing table
cat << EOF >> /etc/sysconfig/network-scripts/rule-$IFACE
fwmark 0x50 table vpn

# Mark all packets originating from processes owned by this user
firewall-cmd --permanent --direct --add-rule ipv4 mangle OUTPUT 0 -m owner --uid-owner $USERNAME -j MARK --set-mark 0x50
# Enable masquerade on the VPN zone (enables IP forwarding between interfaces)
firewall-cmd --permanent --add-masquerade --zone=VPN

firewall-cmd --reload

Note 0x50 is arbitrary, as long as it the rule and firewall rule match, you're fine.

Routing packets from a VLAN through a VPN with Ubiquity routers

A little while back, I posted this on Reddit about setting up a Ubiquity Unifi Security Gateway (USG) or Edge Router Lite (ERL) to selectively route packets through a VPN interface; I wanted to elaborate a bit on the setup for this.

The goal

The goal was have my Unifi device establish two networks, one that behaves normally and another that routes all traffic through a VPN interface automatically. The value prop for a setup like this is that you can avoid having to configure each device & the VPN on each separately; simply connect to the network and that's it. It's simple, uses a single VPN connection for multiple devices and even lets friends & family use it easily with zero configuration.

In my case, I setup a second SSID and tied to different subnet (tagged VLAN) so simply by switching networks, you could gain VPN protection. Normally, this is very difficult to do because the router has a single default route; all packets not destined for local networks will exit using said default route.

This technique is made possible through the use of policy-based routing, which establishes multiple routing tables and rules on when to use a given table. This permits the router to determine the next-hop based on the source address, not the destination address.


Here are some the basic steps to getting your USG configured:

# Setup route using table #1 with next-hop as VPN, blackhole if VPN is down
set protocols static table 1 route blackhole distance 100
set protocols static table 1 interface-route next-hop-interface vtun0 distance 2

# Set rules for when to send packets using routes from table 1
set firewall modify SOURCE_ROUTE rule 10 description "Traffic from VLAN 11 to VPN"
set firewall modify SOURCE_ROUTE rule 10 source address
set firewall modify SOURCE_ROUTE rule 10 modify table 1
set firewall modify SOURCE_ROUTE rule 10 action modify

# Apply the rule
set interfaces ethernet eth1 vif 11 firewall in modify SOURCE_ROUTE

As long as the Ubiquity router is the default gateway (it should be if it's serving DHCP), machines on network will now automatically route packets through the USG and then the USG will pick the vtun0 interface as its next-hop for those packets. Packets from other sources (e.g. your regular LAN) are routed normally through the WAN link.

Note that you can also set the next-hop to a host, by using this static route instead of the one above (note removal of the blackhole - you'll need to do that on the host specified in next-hop to avoid privacy leaks):

# Setup route table #2 with next-hop as VPN via local server
set protocols static table 1 route next-hop

This is useful if you have a home server connected to VPN, and want to route packets through its VPN connection instead of the USG (some additional setup required; more on that in this post).


If this setup does not work as expected, the easiest way to troubleshoot is to verify connectivity. tcpdump will be your best friend here. Something like tcpdump -i interface -A port 80 will trace all HTTP traffic on the interface supplied, and

You'll want to verify connectivity in this order:

1. Verify source packets leave a host

Use any machine on the network to test and start generating some traffic, ensuring the packets do hit the network. You can use something simple like curl google.com to trigger some traffic, and monitor the network interface with tcpdump per above to make sure the packets are sent out.

2. Verify packets are reaching your next-hop

Your next-hop is probably the USG/ERL router, but could also be an IP on your network as well. Now that we've confirmed packets are leaving, make sure they are arriving by inspecting the LAN-side interface on your configured next-hop.

Connect over SSH to the next hop (if it's a USG/ERL read this) and run sudo -i. Use ip a to list all interfaces & configured IPs; this should let you pick out the interface name associated to an IP on the network.

Now run tcpdump against that interface and then generate some traffic on the test host from step 1. Do you see them arriving?

3. Verify packets are exiting your next-hop on the VPN interface

If you've made it this far packets arrive on your next-hop so let's make sure it's forwarding out through the right interface. First, list all configured policy-based routing rules with ip rule - you should expect to see 0 (default table) and 1 (for certain marked packets). List out each routing table using ip rule show table X to make sure things look as you'd expect. For example, ip route show table 1:

default dev vtun0  scope link
blackhole default  metric 100

Or if you configured next-hop as a host instead:

default via dev eth1.11 

Now run tcpdump on the shown interface and verify if packets are existing as expected.

Note for Unifi users

Lastly, note that if you use a USG instead of a ERL, these settings will not be persisted. Your settings will be overwritten by Unifi Controller after any provision or reboot operation -- you will need to manually persist them by exporting to a config.gateway.json file.

Configuring multiple DHCP reservations [fixed IPs] for the same host with a Unifi Security Gateway

I just picked up some new networking gear, so this will be the first of a multi-part blog post about my learnings configuring Unifi gear.

One issue I noticed right away was that it is not possible, via CLI nor GUI, to configure fixed IP address for a host that relies on more than 1 of the configured networks/VLANs. Since I have a home server (user VLAN) that is also hosting the controller softare (management VLAN) and also acts as a gateway for sending packets over its VPN interface (VPN VLAN), this was necessary for me.

It is possible but requires a bit of manual configuration using a config.gateway.json file. First, if you have configured a fixed IP for the host, unset it.

Then, merge in the DHCP mappings in your config.gateway.json file:


The key here is that the string child of the static-mapping node must be unique. Unifi will put in the MAC separated by dashes by default, so above I just tacked on the VLAN name to each name.

Re-provision your USG and you should be good to go. If you run into trouble an want to debug DHCP req/ack sequences, setup verbose logging:

set service dhcp-server global-parameters 'log-facility local2;'
set system syslog file dhcpd facility local2 level debug
set system syslog file dhcpd archive files 5
set system syslog file dhcpd archive size 5000

You'll find the DHCP log under /var/log/user/dhcpd. Simply reboot to go back to normal logging.

Connecting Azure IoT Hub to Azure Functions and other Event-Hub compatible services

IoT Hub is a great, scalable way to manage your IoT devices. Did you know you can trigger an Azure Function when receiving a device-to-cloud message the same way you can for an Event Hub message?

Building the Event Hub connection string

Every IoT hub offers a read-only Event Hub-compatible endpoint that can be used with normal Event Hub consumers. This includes Azure Functions, which provides an Event Hub trigger.

You can view your IoT Hub's Event Hub-compatible by opening your IoT Hub resource in the Azure portal and navigating to the Endpoints blade, then clicking the Events (messages/events) endpoint. You will find the Event Hub-compatible name, and Event Hub-compatible endpoint (in the format of sb://iothub-ns-foo.servicebus.windows.net/) - keep note of these for later.

IoT Hub - Access Policy

Next, navigate to the IoT hub's Shared Access Policies blade. You will need to either use one of the existing policies or add your own, and the policy you choose must have the Service connect permission. Click any policy to view its associated primary key.

IoT Hub - Access Policy

Now, we can use these four pieces of information to construct an Event Hub connection string, which follows the format Endpoint=sb://EH_COMPATIBLE_ENDPOINT;EntityPath=EH_COMPATIBLE_NAME;SharedAccessKeyNa‌​me=IOTHUB_POLICY_NAME;Share‌​dAccessKey=IOTHUB_POLICY_KEY.

Configuring Azure Functions

We will now setup an Azure Function with a trigger using the Event Hub-compatible information.

Open the Azure Function resource and switch to the Integrate tab. Add a new trigger and choose Event Hub, which will reveal the following settings page:

Azure Functions - Trigger configuration

Enter the Event Hub-compatible name collected from IoT Hub earlier under Event Hub name and then press new to enter the Event Hub connection string derived earlier.

Azure Functions - Configure connection string

Lastly, we need to add code under the Develop tab with a parameter that matches the Event parameter name field, so use the following:

using System;

public static void Run(string myEventHubMessage, TraceWriter log)
    log.Info($"C# Queue trigger function processed: {myEventHubMessage}");

Testing the connection

Under the Function's Develop tab, click the Logs button from the toolbar to open the execution log. Next, send a cloud-to-device message -- if you haven't setup a device, simple_sample_device.js from the IoT Hub Node.js SDK does nicely.

After sending your message, you should see that your Function triggered successfully.

What about in ARM templates?

This manual setup is fine for testing, but what about automation for when moving to production?

ARM templates provide a reference() function that resolves a resource's runtime state. It's perfect for obtaining the Event Hub-compatible name that was provisioned as it was created:

    "parameters": {
        "iotHubName": {
            "defaultValue": "iothub2functions",
            "type": "String"
    "variables": {
        "iotHubApiVersion": "2016-02-03",
        "iotHubPolicyName": "service"
    "outputs": {
        "eventHubConnectionString": {
            "type": "string",
            "value": "[concat('Endpoint=',reference(resourceId('Microsoft.Devices/IoTHubs',parameters('iotHubName'))).eventHubEndpoints.events.endpoint,';EntityPath=',reference(resourceId('Microsoft.Devices/IoTHubs',parameters('iotHubName'))).eventHubEndpoints.events.path,';SharedAccessKeyName=',variables('iotHubPolicyName'),';SharedAccessKey=',listKeys(resourceId('Microsoft.Devices/IotHubs/Iothubkeys', parameters('iotHubName'),variables('iotHubPolicyName')), variables('iotHubApiVersion')).primaryKey)]"

Getting started with the Skype for Business SDKs

The Skype for Business App SDK and Skype Web SDK are great ways to build the unified communication experience provided by the Skype for Business (previously Lync) server directly into web and mobile applications and deliver new interactive and collaborative experiences into your application.

If you're getting started with the Skype SDKs and/or Office 365 for the first time, the documentation available on the The Skype Developer Platform is a great place to start, but there are a number of steps you'll need to work through before coding.

In this post, I'll detail how to get setup with Skype for Business Online (Office 365) using a developer tenant and get coding as quickly as possible. As well, this is a good time to note that that at this time, some features of the SDKs are in preview. More features are coming out with every release!

Good to know: compatibility & platform features

As of writing (Jan. 2017), both the App SDK and Web SDK support connecting to on-premise and online (via Office 365) deployments.

However, at this time there are some important distinctions between the capabilities of the SDKs on the two platforms and in compatibility with O365 vs on-premise for meeting join. These differences will be rounded out in future SDK releases (for details on roadmap, see the Andrew Bybee's Ignite 2016 talk).


The App SDK is available for iOS and Android apps, and supports the unauthenticated workflow. Meeting join, IM and audio-video communication is possible but done anonymously, using only a meeting link. There is no opportunity for a user to sign-in, so tasks like contact management are not available.


Contrary to the App SDK, the Web SDK has better support for authenticated workflows. Contact management, 1:1 conversations and more is available from the Web SDK after the user has authenticated. Anonymous meeting join, where the end-user joins a conference without signing in, is available but only when connecting to on-premise Skype for Business deployments at this time.

It's also important to note that both Chrome and Firefox have announced the deprecation of NPAPI plugins, which prevents the use of the Skype for Business Web App Plug-In. As such, audio-video functionality will be limited in Chrome and Firefox at this time.

IE11 and Safari both work with the web plug-in, and Edge supports A/V calling without any plugins via its built-in ORTC stack.

Setup an Office developer tenant

In order to get started, you'll need to be an administrator on Office 365 tenant and have 2-3 users that are licensed with Skype for Business. If you do not already have such an account, sign up at dev.office.com/devprogram to obtain a free Office developer account.

Once your account is activated, visit portal.office.com, click the Admin tile, then add 2-3 users and assign them a developer license.


If you intent to build a mobile app, you are now ready to go - all you need is your app and a meeting link! Check out the meeting join samples for Android and iOS, and the App SDK documentation

You can generate a meeting link using the Skype for Business integration with Outlook, the desktop clients or the Lync Web Scheduler. To create them programtically, use the UCWA API. Meeting links follow the format https://meet.lync.com/tenant_name/organizer_username/meeting_id.


Because the Web SDK supports authenticated workflows, it requires some additional setup in order to authenticate users against Azure AD.

Creating an Azure AD Application

Registering an application will allow the Web SDK to perform OAuth authentication aginst users in an Azure AD directory. Previously, managing Azure AD applications required delving into the classic portal (instructions here) but fortunately Azure AD management is now available (in preview) in the new portal:

  1. Go to portal.azure.com
  2. Select Azure Active Directory > App registrations > Add
    Azure Portal - AAD
  3. Choose a human-readable name for your application and a unique sign-on URL. If unsure, use something like https://tenantID.onmicrosoft.com/appname - you can change it later once you move to production. Leave the type as Web app / API.

    Azure Portal - AAD - Create

  4. Select the Manifest button to open the manifest editor:

    Azure Portal - AAD - Manage

    Then set oauth2AllowImplicitFlow is set to true:

    Azure Portal - AAD - Manifest

  5. Next, navigate to the application's Properties blade and change Multi-tenanted to Yes. This will ensure that people outside of your Office tenant can use this application.

    Enabling multi-tenantancy on the application

  6. Navigate to the Reply URLs blade and enter the URLs at which your application will be hosted. Note that while developing your application, adding http://localhost is convinient but be aware that it means anyone who stands up their own site on http://localhost and knows your client ID can authenticate against your application. Tread carefully!

  7. Navigate to the Required Permissions blade and add delegated permissions for Skype for Business Online:

    Azure Portal - AAD - Permissions

    Azure Portal - AAD - Permissions 2

That's it - your application is now configured. However before it can authenticate users with the Web SDK, you must consent to the application using an admin user from your Office tenant. Open a In-Private/Incognito browsing window and visit this URL, replacing both CLIENT_ID and REDIRECT_URI as configured above: https://login.microsoftonline.com/common/oauth2/authorize?response_type=id_token&client_id=CLIENT_ID&redirect_uri=https://REDIRECT_URI&response_mode=form_post&nonce=...&resource=https://webdir.online.lync.com&prompt=admin_consent

Now check out the Web SDK documentation try running the Web SDK samples, or use the interactive Web SDK sample.