mirror of
https://github.com/systemd/systemd.git
synced 2024-12-26 03:22:00 +03:00
818e46ae7c
One document should only contain one main headline, both for structuring and for visual reasons. Relevant cases found via: git grep -A2 '^# ' docs/
269 lines
15 KiB
Markdown
269 lines
15 KiB
Markdown
---
|
||
title: systemd-resolved and VPNs
|
||
category: Networking
|
||
layout: default
|
||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||
---
|
||
|
||
# `systemd-resolved.service` and VPNs
|
||
|
||
`systemd-resolved.service` supports routing lookups for specific domains to specific
|
||
interfaces. This is useful for hooking up VPN software with systemd-resolved
|
||
and making sure the exact right lookups end up on the VPN and on the other
|
||
interfaces.
|
||
|
||
For a verbose explanation of `systemd-resolved.service`'s domain routing logic,
|
||
see its [man
|
||
page](https://www.freedesktop.org/software/systemd/man/systemd-resolved.service.html). This
|
||
document is supposed to provide examples to use the concepts for the specific
|
||
purpose of managing VPN DNS configuration.
|
||
|
||
Let's first define two distinct VPN use-cases:
|
||
|
||
1. *Corporate* VPNs, i.e. VPNs that open access to a specific set of additional
|
||
hosts. Only specific domains should be resolved via the VPN's DNS servers,
|
||
and everything that is not related to the company's domain names should go
|
||
to regular, non-VPN DNS instead.
|
||
|
||
2. *Privacy* VPNs, i.e. VPNs that should be used for basically all DNS traffic,
|
||
once they are up. If this type of VPN is used, any regular, non-VPN DNS
|
||
servers should not get any traffic anymore.
|
||
|
||
Then, let's briefly introduce three DNS routing concepts that software managing
|
||
a network interface may configure.
|
||
|
||
1. Search domains: these are traditional DNS configuration parameters and are
|
||
used to suffix non-qualified domain names (i.e. single-label ones), to turn
|
||
them into fully qualified domain names. Traditionally (before
|
||
`systemd-resolved.service`), search domain names are attached to a system's
|
||
IP configuration as a whole, in `systemd-resolved.service` they are
|
||
associated to individual interfaces instead, since they are typically
|
||
acquired through some network associated concept, such as a DHCP, IPv6RA or
|
||
PPP lease. Most importantly though: in `systemd-resolved.service` they are
|
||
not just used to suffix single-label domain names, but also for routing
|
||
domain name lookups: if a network interface has a search domain `foo.com`
|
||
configured on it, then any lookups for names ending in `.foo.com` (or for
|
||
`foo.com` itself) are preferably routed to the DNS servers configured on the
|
||
same network interface.
|
||
|
||
2. Routing domains: these are very similar to search domains, but are purely
|
||
about DNS domain name lookup routing — they are not used for qualifying
|
||
single-label domain names. When it comes to routing assigning a routing
|
||
domain to a network interface is identical to assigning a search domain to
|
||
it.
|
||
|
||
Why the need to have both concepts, i.e. search *and* routing domains?
|
||
Mostly because in many cases the qualifying of single-label names is not
|
||
desirable (since security-sensitive), but needs to be supported for specific
|
||
use-cases. Routing domains are a concept `systemd-resolved.service`
|
||
introduced, while search domains are traditionally available and are part of
|
||
DHCP/IPv6RA/PPP leases and thus universally supported. In many cases routing
|
||
domains are probably the more appropriate concept, but not easily available,
|
||
since not part of DHCP/IPv6RA/PPP.
|
||
|
||
Routing domains for `systemd-resolved.service` are usually presented along
|
||
with search domains in mostly the same way, but prefixed with `~` to
|
||
differentiate them. i.e. `~foo.com` is a configured routing domain, while
|
||
`foo.com` would be a configured search domain.
|
||
|
||
One routing domain is particularly interesting: `~.` — the catch-all routing
|
||
domain. (The *dot* domain `.` is how DNS denotes the "root" domain, i.e. the
|
||
parent domain of all domains, but itself.) When used on an interface any DNS
|
||
traffic is preferably routed to its DNS servers. (A search domain – i.e. `.`
|
||
instead of `~.` — would have the same effect, but given that it's mostly
|
||
pointless to suffix an unqualified domain with `.`, we generally declare it
|
||
as a routing domain, not a search domain).
|
||
|
||
Routing domains also have particular relevance when it comes to the reverse
|
||
lookup DNS domains `.in-addr.arpa` and `.ip6.arpa`. An interface that has
|
||
these (or sub-domains thereof) defined as routing domains, will be preferably
|
||
used for doing reverse IP to domain name lookups. e.g. declaring
|
||
`~168.192.in-addr.arpa` on an interface means that all lookups to find the
|
||
domain names for IPv4 addresses 192.168.x.y are preferable routed to it.
|
||
|
||
3. The `default-route` boolean. This is a simple boolean value that may be set
|
||
on an interface. If true (the default), any DNS lookups for which no
|
||
matching routing or search domains are defined are routed to interfaces
|
||
marked like this. If false then the DNS servers on this interface are not
|
||
considered for routing lookups to except for the ones listed in the
|
||
search/routing domain list. An interface that has no search/routing domain
|
||
associated and also has this boolean off is not considered for *any*
|
||
lookups.
|
||
|
||
One more thing to mention: in `systemd-resolved.service` if lookups match the
|
||
search/routing domains of multiple interfaces at once, then they are sent to
|
||
all of them in parallel, and the first positive reply used. If all lookups fail
|
||
the last negative reply is used. This means the DNS zones on the relevant
|
||
interfaces are "merged": domains existing on one but not the other will "just
|
||
work" and vice versa.
|
||
|
||
And one more note: the domain routing logic implemented is a tiny bit more
|
||
complex that what described above: if there two interfaces have search domains
|
||
that are suffix of each other, and a name is looked up that matches both, the
|
||
interface with the longer match will win and get the lookup routed to is DNS
|
||
servers. Only if the match has the same length, then both will be used in
|
||
parallel. Example: one interface has `~foo.example.com` as routing domain, and
|
||
another one `example.com` has search domain. A lookup for
|
||
`waldo.foo.example.com` is the exclusively routed to the first interface's DNS
|
||
server, since it matches by three suffix labels instead of just two. The fact
|
||
that the matching length is taken into consideration for the routing decision
|
||
is particularly relevant if you have one interface with the `~.` routing domain
|
||
and another one with `~corp.company.example` — both suffixes match a lookup for
|
||
`foo.corp.company.example`, but the latter interface wins, since the match is
|
||
for four labels, while the other is for zero labels.
|
||
|
||
## Putting it Together
|
||
|
||
Let's discuss how the three DNS routing concepts above are best used for a
|
||
reasonably complex scenario consisting of:
|
||
|
||
1. One VPN interface of the *corporate* kind, maybe called `company0`. It makes
|
||
available a bunch of servers, all in the domain `corp.company.example`.
|
||
|
||
2. One VPN interface of the *privacy* kind, maybe called `privacy0`. When it is
|
||
up all DNS traffic shall preferably routed to its DNS servers.
|
||
|
||
3. One regular WiFi interface, maybe called `wifi0`. It has a regular DNS
|
||
server on it.
|
||
|
||
Here's how to best configure this for `systemd-resolved.service`:
|
||
|
||
1. `company0` should get a routing domain `~corp.company.example`
|
||
configured. (A search domain `corp.company.example` would work too, if
|
||
qualifying of single-label names is desired or the VPN lease information
|
||
does not provide for the concept of routing domains, but does support search
|
||
domains.) This interface should also set `default-route` to false, to ensure
|
||
that really only the DNS lookups for the company's servers are routed there
|
||
and nothing else. Finally, it might make sense to also configure a routing
|
||
domain `~2.0.192.in-addr.arpa` on the interface, ensuring that all IPv4
|
||
addresses from the 192.0.2.x range are preferably resolved via the DNS
|
||
server on this interface (assuming that that's the IPv4 address range the
|
||
company uses internally).
|
||
|
||
2. `privacy0` should get a routing domain `~.` configured. The setting of
|
||
`default-route` for this interface is then irrelevant. This means: once the
|
||
interface is up, all DNS traffic is preferably routed there.
|
||
|
||
3. `wifi0` should not get any special settings, except possibly whatever the
|
||
local WiFi router considers suitable as search domain, for example
|
||
`fritz.box`. The default `true` setting for `default-route` is good too.
|
||
|
||
With this configuration if only `wifi0` is up, all DNS traffic goes to its DNS
|
||
server, since there are no other interfaces with better matching DNS
|
||
configuration. If `privacy0` is then upped, all DNS traffic will exclusively go
|
||
to this interface now — with the exception of names below the `fritz.box`
|
||
domain, which will continue to go directly to `wifi0`, as the search domain
|
||
there says so. Now, if `company0` is also upped, it will receive DNS traffic
|
||
for the company's internal domain and internal IP subnet range, but nothing
|
||
else. If `privacy0` is then downed again, `wifi0` will get the regular DNS
|
||
traffic again, and `company0` will still get the company's internal domain and
|
||
IP subnet traffic and nothing else. Everything hence works as intended.
|
||
|
||
## How to Implement this in Your VPN Software
|
||
|
||
Most likely you want to expose a boolean in some way that declares whether a
|
||
specific VPN is of the *corporate* or the *privacy* kind:
|
||
|
||
1. If managing a *corporate* VPN, you configure any search domains the user or
|
||
the VPN contact point provided. And you set `default-route` to false. If you
|
||
have IP subnet information for the VPN, it might make sense to insert
|
||
`~….in-addr.arpa` and `~….ip6.arpa` reverse lookup routing domains for it.
|
||
|
||
2. If managing a *privacy* VPN, you include `~.` in the routing domains, the
|
||
value for `default-route` is actually irrelevant, but I'd set it to true. No
|
||
need to configure any reverse lookup routing domains for it.
|
||
|
||
(If you also manage regular WiFi/Ethernet devices, just configure them as
|
||
traditional, i.e. with any search domains as acquired, do not set `~.` though,
|
||
and do not disable `default-route`.)
|
||
|
||
## The APIs
|
||
|
||
Now we determined how we want to configure things, but how do you actually get
|
||
the configuration to `systemd-resolved.service`? There are three relevant
|
||
interfaces:
|
||
|
||
1. Ideally, you use D-Bus and talk to [`systemd-resolved.service`'s D-Bus
|
||
API](https://www.freedesktop.org/software/systemd/man/org.freedesktop.resolve1.html)
|
||
directly. Use `SetLinkDomains()` to set the per-interface search and routing
|
||
domains on the interfaces you manage, and `SetLinkDefaultRoute()` to manage
|
||
the `default-route` boolean, all on the `org.freedesktop.resolve1.Manager`
|
||
interface of the `/org/freedesktop/resolve1` object.
|
||
|
||
2. If that's not in the cards, you may shell out to
|
||
[`resolvectl`](https://www.freedesktop.org/software/systemd/man/resolvectl.html),
|
||
which is a thin wrapper around the D-Bus interface mentioned above. Use
|
||
`resolvectl domain <iface> …` to set the search/routing domains and
|
||
`resolvectl default-route <iface> …` to set the `default-route` boolean.
|
||
|
||
Example use from a shell callout of your VPN software for a *corporate* VPN:
|
||
|
||
resolvectl domain corporate0 '~corp-company.example' '~2.0.192.in-addr.arpa'
|
||
resolvectl default-route corporate0 false
|
||
resolvectl dns corporate0 192.0.2.1
|
||
|
||
Example use from a shell callout of your VPN software for a *privacy* VPN:
|
||
|
||
resolvectl domain privacy0 '~.'
|
||
resolvectl default-route privacy0 true
|
||
resolvectl dns privacy0 8.8.8.8
|
||
|
||
3. If you don't want to use any `systemd-resolved` commands, you may use the
|
||
`resolvconf` wrapper we provide. `resolvectl` is actually a multi-call
|
||
binary and may be symlinked to `resolvconf`, and when invoked like that
|
||
behaves in a way that is largely compatible with FreeBSD's and
|
||
Ubuntu's/Debian's
|
||
[`resolvconf(8)`](https://manpages.ubuntu.com/manpages/trusty/man8/resolvconf.8.html)
|
||
tool. When the `-x` switch is specified, the `~.` routing domain is
|
||
automatically appended to the domain list configured, as appropriate for a
|
||
*privacy* VPN. Note that the `resolvconf` interface only covers *privacy*
|
||
VPNs and regular network interfaces (such as WiFi or Ethernet) well. The
|
||
*corporate* kind of VPN is not well covered, since the interface cannot
|
||
propagate the `default-route` boolean, nor can be used to configure the
|
||
`~….in-addr.arpa` or `~.ip6.arpa` routing domains.
|
||
|
||
## Ordering
|
||
|
||
When configuring per-interface DNS configuration settings it is wise to
|
||
configure everything *before* actually upping the interface. Once the interface
|
||
is up `systemd-resolved.service` might start using it, and hence it's important
|
||
to have everything configured properly (this is particularly relevant when
|
||
LLMNR or MulticastDNS is enabled, since that works without any explicitly
|
||
configured DNS configuration). It is also wise to configure search/routing
|
||
domains and the `default-route` boolean *before* configuring the DNS servers,
|
||
as the former without the latter has no effect, but the latter without the
|
||
former will result in DNS traffic possibly being generated, in a non-desirable
|
||
way given that the routing information is not set yet.
|
||
|
||
## Downgrading Search Domains to Routing Domains
|
||
|
||
Many VPN implementations provide a way how VPN servers can inform VPN clients
|
||
about search domains to use. In some cases it might make sense to install those
|
||
as routing domains instead of search domains. Unqualified domain names usually
|
||
imply a context of locality: the same unqualified name typically is expected to
|
||
resolve to one system in one local network, and to another one in a different
|
||
network. Search domains thus generally come with security implications: they
|
||
might cause that unqualified domains are resolved in a different (possibly
|
||
remote) context, contradicting user expectations. Thus it might be wise to
|
||
downgrade *search domains* provided by VPN servers to *routing domains*, so
|
||
that local unqualified name resolution remains untouched and strictly maintains
|
||
its local focus — in particular in the aforementioned less trusted *corporate*
|
||
VPN scenario.
|
||
|
||
To illustrate this further, here's an example for an attack scenario using
|
||
search domains: a user assumes the printer system they daily contact under the
|
||
unqualified name "printer" is the network printer in their basement (with the
|
||
fully qualified domain name "printer.home"). Sometimes the user joins the
|
||
corporate VPN of their employer, which comes with a search domain
|
||
"foocorp.example", so that the user's confidential documents (maybe a job
|
||
application to a competing company) might end up being printed on
|
||
"printer.foocorp.example" instead of "printer.home". If the local VPN software
|
||
had downgraded the VPN's search domain to a routing domain "~foocorp.example",
|
||
this mismapping would not have happened.
|
||
|
||
When connecting to untrusted WiFi networks it might be wise to go one step
|
||
further even: suppress installation of search/routing domains by the network
|
||
entirely, to ensure that the local DNS information is only used for name
|
||
resolution of qualified names and only when no better DNS configuration is
|
||
available.
|