Systemd-resolved DNS configuration for VPN
Published:
Updated:
Some guidance about configuring/fixing domain name resolution with a corporate Virtual Private Network (VPN), especially OpenVPN and with systemd-based Linux systems. This configuration uses the internal/private corporate resolvers for resolving internal/private domain names while using the ISP resolver for general domain names. This might help if your VPN is struggling these days because of the COVID-19 threat 😷.
I've been helping some people to make corporate VPNs (OpenVPN-based) work correctly, since yesterday. The problem is the resolution of domain names:
-
either private internal domain names are not resolved because the internal corporate resolver is used;
-
or general domain name resolution is slowish because the internal corporate resolver is used and the VPN is currently struggling with the new load.
A good solution is to use the internal coporate resolver for internal domain names but another (eg. the ISP) resolver for other domain names. Depending on your configuration and how you are running the VPN (CLI, NetworkManager, etc.), this might be already what is happening (you might find some information in this post in order to check this).
Below are some instructions about one solution to make this work on
systemd-based Linux systems using systemd-resolved.
There are solution to achieve this without systemd-resolved
(I think it is possible to do that with dnsmasq
).
Manual configuration
Summary
If you are using a Linux system with systemd-resolved
, a quick fix is
something like this (your need to adjust these values):
# Configure internal corporate domain name resolvers:
resolvectl dns tun0 192.0.2.53 192.0.2.54
# Only use the internal corporate resolvers for domain names under these:
resolvectl domain tun0 "~foo.example.com" "~bar.example.com"
# Not super nice, but might be needed:
resolvectl dnssec tun0 off
If you don't have the resolvectl
commands, you might use instead:
systemd-resolve -i tun0 \
--set-dns=192.0.2.53 --set-dns=192.0.2.54 \
--set-domain=foo.example.com --set-domain=bar.example.com \
--set-dnssec=off # <- Not super nice, but might be needed.
Prerequisites
First check if you have systemd-resolved
installed and running:
systemctl status systemd-resolved
Check if you have the resolvectl
too:
resolvectl
An alternative is to use systemd-resolve
:
systemd-resolve --status
Check if have the resolve
mechanism enabled in nssswitch.conf
:
cat /etc/nsswitch.conf | grep ^hosts: | grep resolve
Install the needed libraries. For example on Debian or Ubuntu AMD64 systems:
dpkg -l | grep libnss-resolve
Note: I am installing both the 64 bit and 32 bit libraries. If you do not install the 32 bit libraries, proper DNS resolution might fail for 32 programs.
Configure DNS resolution
If all these prerequisites are satisfied, you can configure the DNS resolution with something like:
resolvectl dns tun0 192.0.2.53 192.0.2.54
resolvectl domain tun0 "~foo.example.com" "~bar.example.com"
Alternatively, if you don't have resolvectl
:
systemd-resolve -i tun0 \
--set-dns=192.0.2.53 --set-dns=192.0.2.54 \
--set-domain=~foo.example.com --set-domain=~bar.example.com
This configures systemd-resolved to use the corporate internal resolvers 192.0.2.53 and 192.0.2.54
for resolving all domain names under foo.example.com
and bar.example.com
(eg. some-service.foo.example.com
). tun0
is the network interface reprensing the VPN.
You have to adjust these parameters (IP address, domain names).
Note: the ~
in front of a domain prevents the domain to
be added to the search list (i.e., trying to resolve the test
host name
will not search test.foo.example.com
if a ~
was used in front
of foo.example.com
).
You can check your system-resolved configuration with:
resolvectl
If you don't have resolvectl
:
systemd-resolve --status
You can now test domain name resolution (assuming some-service.foo.example.com
actually exists):
resolvectl query some-service.foo.example.com
If you don't have resolvectl
:
systemd-resolve some-service.foo.example.com
If this works, you should have:
some-service.foo.example.com: 192.0.2.22 -- link: tun0 -- Information acquired via protocol DNS in 1.7ms. -- Data is authenticated: no
In my case, this failed because of:
some-service.foo.example.com: resolve call failed: DNSSEC validation failed: failed-auxiliary
A quick/dirty fix is to disabled DNSSEC:
resolvectl dnssec tun0 off
If you don't have resolvectl
:
systemd-resolve -i tun0 \
--set-dns=192.0.2.53 --set-dns=192.0.2.54 \
--set-domain=foo.example.com --set-domain=bar.example.com \
--set-dnssec=off
Hooking with OpenVPN (CLI)
If you are using OpenVPN directly from the CLI (not through Network Manager),
you can automate this with the --up
options.
For example:
openvpn --config client.ovpn --script-security 2 --up ./manual-config
where ./manual-config
is a shell script such as:
#!/bin/sh
set -e
resolvectl dns $dev 192.0.2.53 192.0.2.54
resolvectl domain $dev "~foo.example.com" "~bar.example.com"
resolvectl dnssec $dev off
or
#!/bin/sh
systemd-resolve -i $dev \
--set-dns=192.0.2.53 --set-dns=192.0.2.54 \
--set-domain=foo.example.com --set-domain=bar.example.com \
--set-dnssec=off # <- Not super nice, but might be needed.
OpenVPN sets the dev
environment variable
to the name of the VPN TUN/TAP
(i.e., virtual) network device (eg. tun0
) when calling the hooks.
Don't forget to make this script executable:
chmod u+x ./manual-config
Using update-systemd-resolved
For OpenVPN, an alternative is to use the
update-systemd-resolved
script:
openvpn \
--config client.ovpn \
--up /etc/openvpn/update-systemd-resolved \
--down /etc/openvpn/update-systemd-resolved \
--down-pre \
On Debian and Ubuntu, this script is in the openvpn-systemd-resolved
package.
This automatically pulls the configuration advertised by the VPN server:
- resolver to use;
- domain names;
- whether to disable DNSSEC.
If this fails, the previous section might help you pinpoint to the problem:
- check the configuration (
resolvectl
); - test internal/external names resolution (
resolvectl query some-service.foo.example.com
) - disable DNSSEC if needed (
resolvectl dnssec tun0 off
).