Split DNS with systemd

I've had to set up VPNs for organizations in the past. Every time I'd set one up with an intranet domain, all DNS requests from users would be forwarded to the organization's DNS server. This worked and was simple but I didn't like it, because I'd have to manage more private data from users than I needed. What if we could avoid forwarding regular requests to the organization's DNS server? This is one of the applications of what is often called split DNS, and I'll show you an example here.

Note that what I'm about to describe doesn't apply to VPN you'd use for all your network traffic. In such a case, forwarding all DNS requests via the VPN is actually important.

Example with a corporate VPN

Let's say the internal domain is intranet.example.com where users have access services such as gitlab.intranet.example.com. There are different ways to achieve this. In general, if you have many users and many services, setting up a DNS server for the organization is the way to go. Unfortunately, after users have configured their system to use that DNS server, that server receives all DNS requests, not just those for gitlab.intranet.example.com. At least that's my experience with Windows or Linux.

Typical DNS configuration with a VPN
Typical DNS configuration with a VPN

Note that it is often possible to add what is called a "secondary DNS server". That server is used when the primary server is unavailable regardless of the domain of DNS queries. This feature is thus useless for split DNS.

Solution for Linux with systemd

systemd, now the most common init system, comes with systemd-resolved. Instead of editing /etc/resolv.conf, you can configure the resolver with resolvectl.

For example, with the main interface named enp0s3 and the VPN interface named tun0, you can use the following command:

resolvectl domain tun0 ~intranet.example.com

Afterwards, only requests for intranet.example.com and its subdomains are sent to the DNS server via the VPN. Other requests are forwarded to the DNS server available via your LAN.

Split DNS configuration with a VPN
With the split DNS configured, only requests for the internal domain go to the corporate DNS server.

You can check the configuration with the command resolvectl:

Global
  [...]
Link 2 (enp0s3)
    Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6
         Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 192.0.2.1
       DNS Servers: 192.0.2.1 2001:db8:aaaa:3

Link 3 (tun0)
    Current Scopes: DNS
         Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 2001:db8:bbbb:3
       DNS Servers: 2001:db8:bbbb:3
        DNS Domain: ~internal

If you want to be sure that it is correctly configured, you can ask resolvectl to do some queries. For example:

> resolvectl query example.com
example.com: 2606:2800:220:1:248:1893:25c8:1946 -- link: enp0s3
             93.184.216.34                     -- link: enp0s3

-- Information acquired via protocol DNS in 5.3ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network

We can see here that it used the local Ethernet interface, enp0s3, not tun0.

To be extra sure that it worked as expected, I monitored outgoing DNS requests with Wireshark and observed the result of running ping foo.example.com or ping foo.intranet.example.com.

Use with WireGuard and OpenVPN

With WireGuard, if you use wg-quick:

[Interface]
PrivateKey = ...
Address = 2001:db8:bbbb:1
DNS = 2001:db8:bbbb:3
PostUp = resolvectl domain tun0 ~intranet.example.com

[Peer]
...

I haven't tested split DNS with OpenVPN but it should be possible to have it run the desired command with the up option in the client configuration:

script-security 2
up resolvectl domain tun0 ~intranet.example.com

See this StackExchange question for more information.

Conclusion

The new systemd component, systemd-resolved, gives you the power to do split DNS over different network interfaces. This is useful because it reduces the amount of private data sent to the DNS server available via the VPN.

If you don't have systemd or need more control over your DNS requests, I would probably recommend using a local resolver such as Unbound or dnsmasq.