Downsides of VPN

Everybody who’s worked remotely is probably familiar with VPNs. You run some application on your machine, such as Cisco AnyConnect or OpenConnect, which have you type in some credentials, some magic happens in the background, and suddenly you can reach some internal company servers.

You might also be familiar with the downsides of VPNs, such as:

  • Not being able to resolve some websites while connected to the VPN.
  • Terrible slowdowns as a result of proxying all trafic through the VPN.
  • Or just the hassle of having to set up proxy configurations just so you can access the outside world.

This seems dumb to me. I have a perfectly working network connection on my machine already that I use to access the internet when not connected to the VPN, so why can’t I just use that to do all my normal network access, and only use the VPN connection for applications that absolutely need it (such as a single SSH session for example)? The answer is: it’s possible, but doesn’t seem well supported.

How do VPNs work?

Keep in mind that I mostly figured this out on my own with some googling (which didn’t yield much results) and just experimenting with my own setup, a Windows 10 machine. So, I don’t really know what I’m talking about.

I noticed that these VPN clients create a virtual network interface, on Windows this is a TAP-Windows v6 Adapter, which is created by OpenConnect (I believe it uses a TUN driver on Linux, but very similar idea).

These VPN clients also set up several routes in the OS’ routing table. The routing table is assentially a list of IP addresses and masks, with a network interface, gateway, and “Metric” (a sort of priority) associated with them. You can see the table by using route print (route -n on Linux) which looks something like this (irrelevant sections removed, and obfuscated for obvious reasons):

IPv4 Route Table
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
          0.0.0.0          0.0.0.0    142.236.178.1   142.236.178.35     26
        127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
        127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
  127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
    142.236.178.0    255.255.255.0         On-link    142.236.178.35    281
   142.236.178.35  255.255.255.255         On-link    142.236.178.35    281
  142.236.178.255  255.255.255.255         On-link    142.236.178.35    281
        224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
        224.0.0.0        240.0.0.0         On-link    142.236.178.35    281
  255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
  255.255.255.255  255.255.255.255         On-link    142.236.178.35    281
===========================================================================
Persistent Routes:
  None

When you connect to a VPN using e.g. OpenConnect, some routes are added here (the 1.2.50.2 interface):

IPv4 Route Table
===========================================================================
Active Routes:
Network Destination        Netmask          Gateway       Interface  Metric
          0.0.0.0          0.0.0.0    142.236.178.1   142.236.178.35     26
          0.0.0.0          0.0.0.0         1.2.32.1         1.2.50.2      2
         1.2.32.0    255.255.224.0         On-link          1.2.50.2    257
         1.2.50.2  255.255.255.255         On-link          1.2.50.2    257
       1.2.63.255  255.255.255.255         On-link          1.2.50.2    257
        127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
        127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
  127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
    142.236.178.0    255.255.255.0         On-link    142.236.178.35    281
   142.236.178.35  255.255.255.255         On-link    142.236.178.35    281
  142.236.178.255  255.255.255.255         On-link    142.236.178.35    281
        224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
        224.0.0.0        240.0.0.0         On-link    142.236.178.35    281
        224.0.0.0        240.0.0.0         On-link          1.2.50.2    257
  255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
  255.255.255.255  255.255.255.255         On-link    142.236.178.35    281
  255.255.255.255  255.255.255.255         On-link          1.2.50.2    257
===========================================================================
Persistent Routes:
  Network Address          Netmask  Gateway Address  Metric
          0.0.0.0          0.0.0.0         1.2.32.1       1

Note the “Metric” collumn. This is essentially the priority by which these routes function. A route with a lower metric will be chosen before one with a higher metric.

The VPN client sets a default route (0.0.0.0) with a high priority (2), as a way to catch all outgoing network connections, and route them trough the VPN client’s virtual network interface. At that point the VPN client software takes over and handles to actual tunnelling to the VPN network.

Routing all traffic through my normal internet connection

First step is to change my default route from the VPN network interface back to my normal, physical network interface. This can be done simply by changing the metric of the VPN interface, and setting it to a lower value than my normal interface. In powershell, I can manipulate my network interfaces using a set of (G|S)et-NetIpInterface functions. To list all network interfaces and their metrics I can use Get-NetIpInterface (ip link show on Linux):

> Get-NetIpInterface

ifIndex InterfaceAlias                  AddressFamily NlMtu(Bytes) InterfaceMetric Dhcp     ConnectionState PolicyStore
------- --------------                  ------------- ------------ --------------- ----     --------------- -----------
20      OpenConnect                     IPv6                  1300              35 Enabled  Connected       ActiveStore
10      Ethernet 3                      IPv6                  1500              25 Enabled  Connected       ActiveStore
1       Loopback Pseudo-Interface 1     IPv6            4294967295              75 Disabled Connected       ActiveStore
20      OpenConnect                     IPv4                  1300               1 Disabled Connected       ActiveStore
10      Ethernet 3                      IPv4                  1500              25 Enabled  Connected       ActiveStore
1       Loopback Pseudo-Interface 1     IPv4            4294967295              75 Disabled Connected       ActiveStore

The OpenConnect interface is the one we want to set to a lower priority. It’s index is 20, so I can use (with admin permissions) Set-NetIpInterface -InterfaceIndex 20 -InterfaceMetric 35:

> Set-NetIpInterface -interfaceindex 20 -interfacemetric 35
> Get-NetIpInterface

ifIndex InterfaceAlias                  AddressFamily NlMtu(Bytes) InterfaceMetric Dhcp     ConnectionState PolicyStore
------- --------------                  ------------- ------------ --------------- ----     --------------- -----------
20      OpenConnect                     IPv6                  1300              35 Enabled  Connected       ActiveStore
10      Ethernet 3                      IPv6                  1500              25 Enabled  Connected       ActiveStore
1       Loopback Pseudo-Interface 1     IPv6            4294967295              75 Disabled Connected       ActiveStore
20      OpenConnect                     IPv4                  1300              35 Disabled Connected       ActiveStore
10      Ethernet 3                      IPv4                  1500              25 Enabled  Connected       ActiveStore
1       Loopback Pseudo-Interface 1     IPv4            4294967295              75 Disabled Connected       ActiveStore

And that’s it… Now when I run e.g. FireFox and try to access the internet, it will use my normal physical network connection to connect to the internet, and I’m no longer burdened by the downsides of going through the VPN.

So, what’s the point? I’ve essentially turned on my VPN, and then turned it off again. Well, the difference is that, even though, by default, network is routed through my normal network connection, I’m still connected to the VPN, so I can now try to find a way to route single connections (e.g. a single SSH session) through the VPN, while not affecting my normal network traffic.

Binding a single SSH session to the VPN’s network interface

Can I bind an ssh session to the VPN’s network interface explicitly? It look like ssh has a -b option where I can specify a bind_address of the interface to bind to. Great! Except, it doesn’t seem to work (at least not on the Windows OpenSSH client):

> ssh -b 1.2.50.2 my-server -v
OpenSSH_for_Windows_7.7p1, LibreSSL 2.6.5
debug1: Reading configuration data C:\\Users\\Jorn/.ssh/config
debug1: C:\\Users\\Jorn/.ssh/config line 7: Applying options for my-server
debug1: Connecting to my.server.com [123.123.12.8] port 22.
debug1: ssh_create_socket: bound to 1.2.50.2
debug1: connect to address 123.123.12.8 port 22: Invalid argument
ssh: connect to host my.server.com port 22: Invalid argument

It seems like the -b option is tripping up the actual connection. ssh also has a -B option, where you can specify the name of the interface instead, but this just fails with BindInterface not supported on this platform. (the typical using-Unix-tools-on-Windows experience).

For what it’s worth, I did also try this through WSL, which does except the -b and -B, but then fails with a timeout instead. Some trickery seems to be happening that is routing the traffic through the default interface still. Printing the routing table in WSL with route -n shows the relevant interfaces, but the table doesn’t get updated when I change the metric of one of them. Maybe this actually works on a real Unix machine? (Haven’t tried it out yet).

Workaround

The only workaround I’ve found so far is temporarily setting the VPN interface as highest priority, connecting with ssh, and then setting the priority back.

> Set-NetIpInterface -interfaceindex 20 -interfacemetric 1
# connect with ssh in another shell
> Set-NetIpInterface -interfaceindex 20 -interfacemetric 35

The ssh session will continue to function, which is enough for my purposes.

I’d like to be able to do something similar with FireFox, i.e. bind a single FireFox instance to the VPN network interface, but from googling around, it doesn’t seem that binding a FireFox instance to a particular network interface is supported :(

Closing thoughts

Is this really such a weird idea? Why isn’t this better supported? Or am I just doing something wrong?

When looking things up on the internet one of the alternatives I’ve found is the suggestion to just add the relavant target addresses to my routing table myself. Except, I have no idea what those addresses are. I’m not just accessing things on the VPN, I’m also accessing things that are only resolvable when connecting through the VPN, and I have no way of knowing the range of IP addresses that those things will occupy.

It’s also been suggested that this is such an uncommen case that better support for this is not needed. I strongly disagree. Especially with people working from home increasingly during the COVID-19 pandamic, VPN use (and thus mutliple network interfaces per device) has surely gone up significantly?

FireFox doesn’t seem to support binding to a particular network interface, but maybe there are other browsers that do (I’m not hopeful though).

At least I have gotten what I wanted working at least somewhat, though it annoyingly requires some mechanical manual steps (still have to investigate some wrapper scripts).