Developer's blog

Go to Notes

Working With Multiple VPNs

I work on several projects. Each of them require a VPN connection. Sometimes, I work from remote locations and I need to access my home network. It’s one more VPN connection. It’s convenient to have an ability to access different private networks at the same time. Let’s discuss the issues of multi-VPN setups and possible.

TL;DR:

  1. Use NetworkManager to manage VPN connections.
  2. Disable systemd-resolved.
  3. Use dnsmasq to process DNS requests.

The most common commercial VPN I’d encountered is Cisco AnyConnect. For my personal projects, I use WireGuard. When I need to access my home network, I have an OpenVPN server on my router. It’s an interesting task to make all these connections work at the same time without conflicts.

Each VPN connection has routes and DNS configurations. AnyConnect and OpenVPN push routes and DNS to the client during the connection startup and WireGuard has a static setup in the config file. Some VPN setups provide a lot of routes or even try to route all client’s traffic though the VPN connection. It’s useful when working from untrusted location like cafe, hotel or airport and there are a lot of free and commercial VPN solutions that work this way. But when there are several active VPN connections, this behavior can cause different issues.

Most corporate networks have private DNS servers with addresses of internal resources like an issue tracker, a source code repository or a monitoring system. VPN connection changes the system DNS settings, so it’s possible to resolve private domains and to access internal resources. Most of the time, the operating system works with only one DNS server or several servers that work identically. This scheme does not work for a multi-VPN setup, because it’s required to process different domains in different ways. That is why an addition configuration is required.

The main idea of multi-VPN setup is to restrict routes for each connection and to configure different DNS servers for different domains.

There is an official Cisco AnyConnect utility for Linux, but I prefer to use OpenConnect. It allows to change routes that the client receives from the server and seamlessly integrates with NetworkManager, so I can manage all my network connections in one place.

OpenConnect command looks like sudo openconnect vpn.example.com. It asks username and password and then creates a connection. To stop this connection, Ctrl-C can be used. OpenConnect executes vpnc-script when the connection starts to set the routing and name service up. On Kubuntu 22.04, the location of this script is /usr/share/vpnc-scripts/vpnc-script. The default script accepts all routes and DNS settings from the server. The script can be redefined with --script option. From the Stack Exchange post I’d found the way to create a wrapper for the default script that restricts the routes for the connection, but there is an easier way to solve this problem — vpn-slice. Assume I want to route only 10.0.8.0/24 network through the VPN connection. It can be achieved this way:

sudo openconnect vpn.example.com --script 'vpn-slice 10.0.8.0/24'

Any additional routes or DNS config will be ignored.

For the WireGuard the configuration looks like:

[Interface]
PrivateKey = client-private-key
Address = 10.0.8.2/32 # IP address of the client in the VPN network
DNS = 10.0.8.5

[Peer]
PublicKey = remote-public-key
AllowedIPs = 10.0.8.0/24
Endpoint = 1.2.3.4:12345 # Public address of VPN server

It’s easy to restrict routes by editing the value of the AllowedIPs option. Just remove unnecessary networks. DNS option makes WireGuard to change the system DNS server on the connection startup. For multi-VPN setup, It’s better to remove this option and configure domain name resolution manually in a different way.

For the OpenVPN connection, use route-nopull option to restrict routes. It says that the client should ignore routes from the server, so it’s possible to define all required routes in the client’s config file:

route 10.0.8.0 255.255.255.0
route-nopull

I prefer to manage all my VPN connections through the NetworkManager GUI. First, I install the packages:

sudo apt install network-manager-openconnect network-manager-openvpn

OpenConnect connection has a few settings, so it’s easy to create and configure new connection through the network settings GUI of Gnome or KDE. In the IPv4 section choose Automatic (Only addresses) option, press Routes button, configure necessary networks and select Ignore automatically obtained routes checkbox.

vpn config

It’s difficult to configure OpenVPN through the GUI because there are a lot of different settings. That is why I prefer to import the connection config to the NetworkManager with nmcli connection import type openvpn file home.ovpn where home.ovpn is a connection config file name.

For the WireGuard I import a connection with a command nmcli connection import type wireguard file wg0.conf where wg0.conf is a name of a config file. After the import, connection activates automatically. To turn the WireGuard VPN off, use the command nmcli connection down wg0. It is also possible to control WireGuard connection through KDE GUI. There is no support for WireGuard in a Gnome NetworkManager GUI (here is a ticket in the Gnome’s GitLab) but it is always possible to use nm-connection-editor as a workaround.

To solve the DNS problem, I execute such steps:

  1. Disable systemd-resolved.
  2. Install dnsmasq.
  3. Configure different forwarders for different domains.
  4. Make dnsmasq the system resolver.

systemd-resolved is a part of systemd that provides network name resolution, LLMNR and MulticastDNS resolver and responder. It can be used as a network service and through a D-Bus API. The most applications use only basic feature of DNS resolution so it’s safe to disable systemd-resolved. I tried to set different DNS servers for different domains with systemd-resolved, but it’s a too hard task. From the discussion on GitHub, I’d found that it’s possible, but the configuration is obscure and it makes more troubles than solves. So I’d disabled systemd-resolved and switched to the dnsmasq. To disable systemd-resolved in the config /etc/NetworkManager/NetworkManager.conf add dns=none to the [main] section and execute the following commands:

systemctl stop systemd-resolved
systemctl disable systemd-resolved
rm /etc/resolv.conf
systemctl restart NetworkManager

dnsmasq is a DNS forwarder that can be used as a local DNS server. It does not host DNS zones itself, but only forwards requests to the configured servers. Install dnsmasq with apt install dnsmasq and add such lines to the /etc/dnsmasq.conf:

# Do not use or poll /etc/resolv.conf
no-resolv
no-poll

# Expose dns server for the local machine only
interface=lo

# Disable DNS cache, so there will be less issues if DNS server
# of the VPN connection returns different domains for external
# and internal clients.
cache-size=0
no-negcache

# Set one DNS forwarder for first domain
server=/example1.com/1.2.3.4

# Set another DNS forwarder for second domain
server=/example2.com/5.6.7.8

# Set default DNS forwarder
server=8.8.8.8

Start the server:

systemctl start dnsmasq
systemctl enable dnsmasq

Set dnsmasq as a system resolver in /etc/resolv.conf:

nameserver 127.0.0.1

Such setup allows me to work simultaneously with several projects in different private networks, resolve domain names correctly and send only required traffic to the particular VPN connection.