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:
- Use NetworkManager to manage VPN connections.
- Disable systemd-resolved.
- 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.
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:
- Disable
systemd-resolved
. - Install
dnsmasq
. - Configure different forwarders for different domains.
- 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.