aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2018-12-21 18:03:58 +0100
committerGitHub <noreply@github.com>2018-12-21 18:03:58 +0100
commit44f52cce9ee9976b465841a811a7a9963947bc7d (patch)
tree94a3a8ad8318968490265fd7f1fc9e02f705d240
parentMerge pull request #11210 from thom311/dhcp-set-client-id-no-inval (diff)
parentman: document new systemd-resolved.service(8) routing features in more detail (diff)
downloadsystemd-44f52cce9ee9976b465841a811a7a9963947bc7d.tar.gz
systemd-44f52cce9ee9976b465841a811a7a9963947bc7d.tar.bz2
systemd-44f52cce9ee9976b465841a811a7a9963947bc7d.zip
Merge pull request #11050 from poettering/resolved-domain-route
resolved: beef up domain routing
-rw-r--r--man/resolvectl.xml23
-rw-r--r--man/systemd-resolved.service.xml62
-rw-r--r--man/systemd.network.xml11
-rw-r--r--src/libsystemd/sd-network/sd-network.c19
-rw-r--r--src/network/networkd-link.c2
-rw-r--r--src/network/networkd-network-gperf.gperf1
-rw-r--r--src/network/networkd-network.c14
-rw-r--r--src/network/networkd-network.h9
-rw-r--r--src/resolve/resolvectl.c64
-rw-r--r--src/resolve/resolved-bus.c5
-rw-r--r--src/resolve/resolved-dns-query.c27
-rw-r--r--src/resolve/resolved-dns-scope.c167
-rw-r--r--src/resolve/resolved-dns-scope.h10
-rw-r--r--src/resolve/resolved-dns-server.c30
-rw-r--r--src/resolve/resolved-dns-server.h4
-rw-r--r--src/resolve/resolved-link-bus.c52
-rw-r--r--src/resolve/resolved-link-bus.h1
-rw-r--r--src/resolve/resolved-link.c62
-rw-r--r--src/resolve/resolved-link.h2
-rw-r--r--src/resolve/resolved-resolv-conf.c15
-rw-r--r--src/systemd/sd-network.h3
-rw-r--r--test/fuzz/fuzz-network-parser/directives.network1
-rwxr-xr-xtest/networkd-test.py2
23 files changed, 470 insertions, 116 deletions
diff --git a/man/resolvectl.xml b/man/resolvectl.xml
index e07893dd1..defd592aa 100644
--- a/man/resolvectl.xml
+++ b/man/resolvectl.xml
@@ -241,6 +241,7 @@
<varlistentry>
<term><option>dns [<replaceable>LINK</replaceable> [<replaceable>SERVER</replaceable>…]]</option></term>
<term><option>domain [<replaceable>LINK</replaceable> [<replaceable>DOMAIN</replaceable>…]]</option></term>
+ <term><option>default-route [<replaceable>LINK</replaceable> [<replaceable>BOOL</replaceable>…]]</option></term>
<term><option>llmnr [<replaceable>LINK</replaceable> [<replaceable>MODE</replaceable>]]</option></term>
<term><option>mdns [<replaceable>LINK</replaceable> [<replaceable>MODE</replaceable>]]</option></term>
<term><option>dnssec [<replaceable>LINK</replaceable> [<replaceable>MODE</replaceable>]]</option></term>
@@ -248,18 +249,21 @@
<term><option>nta [<replaceable>LINK</replaceable> [<replaceable>DOMAIN</replaceable>…]]</option></term>
<listitem>
- <para>Get/set per-interface DNS configuration. These commands may be used to configure various DNS
- settings for network interfaces that aren't managed by
+ <para>Get/set per-interface DNS configuration. These commands may be used to configure various DNS settings
+ for network interfaces that aren't managed by
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. (These
commands will fail when used on interfaces that are managed by <command>systemd-networkd</command>, please
configure their DNS settings directly inside the <filename>.network</filename> files instead.) These commands
may be used to inform <command>systemd-resolved</command> about per-interface DNS configuration determined
through external means. The <option>dns</option> command expects IPv4 or IPv6 address specifications of DNS
servers to use. The <option>domain</option> command expects valid DNS domains, possibly prefixed with
- <literal>~</literal>, and configures a per-interface search or route-only domain. The <option>llmnr</option>,
- <option>mdns</option>, <option>dnssec</option> and <option>dnsovertls</option> commands may be used to configure
- the per-interface LLMNR, MulticastDNS, DNSSEC and DNSOverTLS settings. Finally, <option>nta</option> command
- may be used to configure additional per-interface DNSSEC NTA domains.</para>
+ <literal>~</literal>, and configures a per-interface search or route-only domain. The
+ <option>default-route</option> command expects a boolean paremeter, and configures whether the link may be
+ used as default route for DNS lookups, i.e. if it is suitable for lookups on domains no other link explicitly
+ is configured for. The <option>llmnr</option>, <option>mdns</option>, <option>dnssec</option> and
+ <option>dnsovertls</option> commands may be used to configure the per-interface LLMNR, MulticastDNS, DNSSEC
+ and DNSOverTLS settings. Finally, <option>nta</option> command may be used to configure additional
+ per-interface DNSSEC NTA domains.</para>
<para>Options <option>dns</option>, <option>domain</option> and <option>nta</option> can take
a single empty string argument to clear their respective value lists.</para>
@@ -274,9 +278,10 @@
<listitem><para>Revert the per-interface DNS configuration. If the DNS configuration is reverted all
per-interface DNS setting are reset to their defaults, undoing all effects of <option>dns</option>,
- <option>domain</option>, <option>llmnr</option>, <option>mdns</option>, <option>dnssec</option>,
- <option>dnsovertls</option>, <option>nta</option>. Note that when a network interface disappears all
- configuration is lost automatically, an explicit reverting is not necessary in that case.</para></listitem>
+ <option>domain</option>, <option>default-route</option>, <option>llmnr</option>, <option>mdns</option>,
+ <option>dnssec</option>, <option>dnsovertls</option>, <option>nta</option>. Note that when a network interface
+ disappears all configuration is lost automatically, an explicit reverting is not necessary in that
+ case.</para></listitem>
</varlistentry>
</variablelist>
diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml
index 71593686a..d7334e01e 100644
--- a/man/systemd-resolved.service.xml
+++ b/man/systemd-resolved.service.xml
@@ -66,14 +66,21 @@
<filename>/etc/systemd/resolved.conf</filename>, the per-link static settings in
<filename>/etc/systemd/network/*.network</filename> files (in case
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> is
- used), the per-link dynamic settings received over DHCP, and any DNS server information made available by other
- system services. See
+ used), the per-link dynamic settings received over DHCP, user request made via
+ <citerefentry><refentrytitle>resolvectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, and any DNS server
+ information made available by other system services. See
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> and
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details
about systemd's own configuration files for DNS servers. To improve compatibility,
<filename>/etc/resolv.conf</filename> is read in order to discover configured system DNS servers, but only if it is
- not a symlink to <filename>/run/systemd/resolve/stub-resolv.conf</filename> or
- <filename>/run/systemd/resolve/resolv.conf</filename> (see below).</para>
+ not a symlink to <filename>/run/systemd/resolve/stub-resolv.conf</filename>,
+ <filename>/usr/lib/systemd/resolv.conf</filename> or <filename>/run/systemd/resolve/resolv.conf</filename> (see
+ below).</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Synthetic Records</title>
<para><command>systemd-resolved</command> synthesizes DNS resource records (RRs) for the following cases:</para>
@@ -99,6 +106,10 @@
to their configured addresses and back, but they will not affect lookups for
non-address types (like MX).</para></listitem>
</itemizedlist>
+ </refsect1>
+
+ <refsect1>
+ <title>Protocols and Routing</title>
<para>Lookup requests are routed to the available DNS servers, LLMNR and MulticastDNS interfaces according to the
following rules:</para>
@@ -132,16 +143,45 @@
lookup zones on all matching interfaces). If the lookup failed on
all interfaces, the last failing response is returned.</para>
- <para>Routing of lookups may be influenced by configuring
- per-interface domain names. See
- <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
- for details. Lookups for a hostname ending in one of the
- per-interface domains are exclusively routed to the matching
- interfaces.</para>
+ <para>Routing of lookups may be influenced by configuring per-interface domain names and other settings. See
+ <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry> and
+ <citerefentry><refentrytitle>resolvectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for details. The
+ following query routing logic applies for unicast DNS traffic:</para>
+
+ <itemizedlist>
+ <listitem><para>If a name to look up matches (that is: is equal to or has as suffix) any of the configured search
+ or route-only domains of any link (or the globally configured DNS settings), the "best matching"
+ search/route-only domain is determined: the matching one with the most labels. The query is then sent to all DNS
+ servers of any links or the globally configured DNS servers associated with this "best matching"
+ search/route-only domain. (Note that more than one link might have this same "best matching" search/route-only
+ domain configured, in which case the query is sent to all of them in parallel).</para></listitem>
+
+ <listitem><para>If a query does not match any configured search/route-only domain (neither per-link nor global),
+ it is sent to all DNS servers that are configured on links with the "DNS default route" option set, as well as
+ the globally configured DNS server.</para></listitem>
+
+ <listitem><para>If there is no link configured as "DNS default route" and no global DNS server configured, the
+ compiled-in fallback DNS server is used.</para></listitem>
+
+ <listitem><para>Otherwise the query is failed as no suitable DNS servers could be determined.</para></listitem>
+ </itemizedlist>
+
+ <para>The "DNS default route" option is a boolean setting configureable with <command>resolvectl</command> or in
+ <filename>.network</filename> files. If not set, it is implicitly determined based on the configured DNS domains
+ for a link: if there's any route-only domain (not matching <literal>~.</literal>) it defaults to false, otherwise
+ to true.</para>
+
+ <para>Effectively this means: in order to preferably route all DNS queries not explicitly matched by
+ search/route-only domain configuration to a specific link, configure a <literal>~.</literal> route-only domain on
+ it. This will ensure that other links will not be considered for the queries (unless they too carry such a
+ route-only domain). In order to route all such DNS queries to a specific link only in case no other link is
+ preferable, then set the "DNS default route" option for the link to true, and do not configure a
+ <literal>~.</literal> route-only domain on it. Finally, in order to ensure that a specific link never receives any
+ DNS traffic not matching any of its configured search/route-only domains, set the "DNS default route" option for it
+ to false.</para>
<para>See the <ulink url="https://www.freedesktop.org/wiki/Software/systemd/resolved"> resolved D-Bus API
Documentation</ulink> for information about the APIs <filename>systemd-resolved</filename> provides.</para>
-
</refsect1>
<refsect1>
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 865b46f40..ee464ffff 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -548,6 +548,17 @@
</listitem>
</varlistentry>
<varlistentry>
+ <term><varname>DNSDefaultRoute=</varname></term>
+ <listitem>
+ <para>Takes a boolean argument. If true, this link's configured DNS servers are used for resolving domain
+ names that do not match any link's configured <varname>Domains=</varname> setting. If false, this link's
+ configured DNS servers are never used for such domains, and are exclusively used for resolving names that
+ match at least one of the domains configured on this link. If not specified defaults to an automatic mode:
+ queries not matching any link's configured domains will be routed to this link if it has no routing-only
+ domains configured.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
<term><varname>NTP=</varname></term>
<listitem>
<para>An NTP server address. This option may be specified more than once. This setting is read by
diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c
index 4b66a9220..d4b5e248c 100644
--- a/src/libsystemd/sd-network/sd-network.c
+++ b/src/libsystemd/sd-network/sd-network.c
@@ -204,6 +204,25 @@ _public_ int sd_network_link_get_route_domains(int ifindex, char ***ret) {
return network_link_get_strv(ifindex, "ROUTE_DOMAINS", ret);
}
+_public_ int sd_network_link_get_dns_default_route(int ifindex) {
+ char path[STRLEN("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1];
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert_return(ifindex > 0, -EINVAL);
+
+ xsprintf(path, "/run/systemd/netif/links/%i", ifindex);
+
+ r = parse_env_file(NULL, path, "DNS_DEFAULT_ROUTE", &s);
+ if (r == -ENOENT)
+ return -ENODATA;
+ if (r < 0)
+ return r;
+ if (isempty(s))
+ return -ENODATA;
+ return parse_boolean(s);
+}
+
static int network_link_get_ifindexes(int ifindex, const char *key, int **ret) {
char path[STRLEN("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1];
_cleanup_free_ int *ifis = NULL;
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index d73e85cf2..e2851df31 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -3929,6 +3929,8 @@ int link_save(Link *link) {
resolve_support_to_string(link->network->llmnr));
fprintf(f, "MDNS=%s\n",
resolve_support_to_string(link->network->mdns));
+ if (link->network->dns_default_route >= 0)
+ fprintf(f, "DNS_DEFAULT_ROUTE=%s\n", yes_no(link->network->dns_default_route));
if (link->network->dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID)
fprintf(f, "DNS_OVER_TLS=%s\n",
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 48d8ae52f..5d8aede59 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -58,6 +58,7 @@ Network.Address, config_parse_address,
Network.Gateway, config_parse_gateway, 0, 0
Network.Domains, config_parse_domains, 0, 0
Network.DNS, config_parse_dns, 0, 0
+Network.DNSDefaultRoute, config_parse_tristate, 0, offsetof(Network, dns_default_route)
Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr)
Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns)
Network.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Network, dns_over_tls_mode)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 62dc6a0bf..ccc1c3ce8 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -168,6 +168,7 @@ int network_load_one(Manager *manager, const char *filename) {
.lldp_mode = LLDP_MODE_ROUTERS_ONLY,
+ .dns_default_route = -1,
.llmnr = RESOLVE_SUPPORT_YES,
.mdns = RESOLVE_SUPPORT_NO,
.dnssec_mode = _DNSSEC_MODE_INVALID,
@@ -657,7 +658,6 @@ int config_parse_domains(
* routing domain, unconditionally. */
is_route = true;
domain = "."; /* make sure we don't allow empty strings, thus write the root domain as "." */
-
} else {
r = dns_name_normalize(domain, 0, &normalized);
if (r < 0) {
@@ -673,16 +673,12 @@ int config_parse_domains(
}
}
- if (is_route) {
+ if (is_route)
r = strv_extend(&n->route_domains, domain);
- if (r < 0)
- return log_oom();
-
- } else {
+ else
r = strv_extend(&n->search_domains, domain);
- if (r < 0)
- return log_oom();
- }
+ if (r < 0)
+ return log_oom();
}
strv_uniq(n->route_domains);
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 3a72c5bd9..f6e62cdd7 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -260,17 +260,20 @@ struct Network {
Hashmap *prefixes_by_section;
Hashmap *rules_by_section;
+ /* All kinds of DNS configuration */
struct in_addr_data *dns;
unsigned n_dns;
-
- char **search_domains, **route_domains, **ntp, **bind_carrier;
-
+ char **search_domains, **route_domains;
+ int dns_default_route;
ResolveSupport llmnr;
ResolveSupport mdns;
DnssecMode dnssec_mode;
DnsOverTlsMode dns_over_tls_mode;
Set *dnssec_negative_trust_anchors;
+ char **ntp;
+ char **bind_carrier;
+
LIST_FIELDS(Network, networks);
};
diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c
index 8a175ebef..4d533f851 100644
--- a/src/resolve/resolvectl.c
+++ b/src/resolve/resolvectl.c
@@ -67,6 +67,7 @@ typedef enum StatusMode {
STATUS_ALL,
STATUS_DNS,
STATUS_DOMAIN,
+ STATUS_DEFAULT_ROUTE,
STATUS_LLMNR,
STATUS_MDNS,
STATUS_PRIVATE,
@@ -1369,6 +1370,7 @@ struct link_info {
char **domains;
char **ntas;
bool dnssec_supported;
+ bool default_route;
};
static void link_info_clear(struct link_info *p) {
@@ -1384,6 +1386,7 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode
{ "DNS", "a(iay)", map_link_dns_servers, offsetof(struct link_info, dns) },
{ "CurrentDNSServer", "(iay)", map_link_current_dns_server, offsetof(struct link_info, current_dns) },
{ "Domains", "a(sb)", map_link_domains, offsetof(struct link_info, domains) },
+ { "DefaultRoute", "b", NULL, offsetof(struct link_info, default_route) },
{ "LLMNR", "s", NULL, offsetof(struct link_info, llmnr) },
{ "MulticastDNS", "s", NULL, offsetof(struct link_info, mdns) },
{ "DNSOverTLS", "s", NULL, offsetof(struct link_info, dns_over_tls) },
@@ -1439,6 +1442,14 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode
if (mode == STATUS_NTA)
return status_print_strv_ifindex(ifindex, name, link_info.ntas);
+ if (mode == STATUS_DEFAULT_ROUTE) {
+ printf("%sLink %i (%s)%s: %s\n",
+ ansi_highlight(), ifindex, name, ansi_normal(),
+ yes_no(link_info.default_route));
+
+ return 0;
+ }
+
if (mode == STATUS_LLMNR) {
printf("%sLink %i (%s)%s: %s\n",
ansi_highlight(), ifindex, name, ansi_normal(),
@@ -1487,11 +1498,13 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode
link_info.scopes_mask & SD_RESOLVED_MDNS_IPV4 ? " mDNS/IPv4" : "",
link_info.scopes_mask & SD_RESOLVED_MDNS_IPV6 ? " mDNS/IPv6" : "");
- printf(" LLMNR setting: %s\n"
+ printf("DefaultRoute setting: %s\n"
+ " LLMNR setting: %s\n"
"MulticastDNS setting: %s\n"
" DNSOverTLS setting: %s\n"
" DNSSEC setting: %s\n"
" DNSSEC supported: %s\n",
+ yes_no(link_info.default_route),
strna(link_info.llmnr),
strna(link_info.mdns),
strna(link_info.dns_over_tls),
@@ -2020,6 +2033,51 @@ static int verb_domain(int argc, char **argv, void *userdata) {
return 0;
}
+static int verb_default_route(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus *bus = userdata;
+ int r, b;
+
+ assert(bus);
+
+ if (argc >= 2) {
+ r = ifname_mangle(argv[1]);
+ if (r < 0)
+ return r;
+ }
+
+ if (arg_ifindex <= 0)
+ return status_all(bus, STATUS_DEFAULT_ROUTE);
+
+ if (argc < 3)
+ return status_ifindex(bus, arg_ifindex, NULL, STATUS_DEFAULT_ROUTE, NULL);
+
+ b = parse_boolean(argv[2]);
+ if (b < 0)
+ return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
+
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "SetLinkDefaultRoute",
+ &error,
+ NULL,
+ "ib", arg_ifindex, b);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY))
+ return log_interface_is_managed(r, arg_ifindex);
+
+ if (arg_ifindex_permissive &&
+ sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK))
+ return 0;
+
+ return log_error_errno(r, "Failed to set default route configuration: %s", bus_error_message(&error, r));
+ }
+
+ return 0;
+}
+
static int verb_llmnr(int argc, char **argv, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus = userdata;
@@ -2407,6 +2465,7 @@ static int native_help(void) {
" reset-server-features Forget learnt DNS server feature levels\n"
" dns [LINK [SERVER...]] Get/set per-interface DNS server address\n"
" domain [LINK [DOMAIN...]] Get/set per-interface search domain\n"
+ " default-route [LINK [BOOL]] Get/set per-interface default route flag\n"
" llmnr [LINK [MODE]] Get/set per-interface LLMNR mode\n"
" mdns [LINK [MODE]] Get/set per-interface MulticastDNS mode\n"
" dnsovertls [LINK [MODE]] Get/set per-interface DNS-over-TLS mode\n"
@@ -2950,9 +3009,10 @@ static int native_main(int argc, char *argv[], sd_bus *bus) {
{ "reset-server-features", VERB_ANY, 1, 0, reset_server_features },
{ "dns", VERB_ANY, VERB_ANY, 0, verb_dns },
{ "domain", VERB_ANY, VERB_ANY, 0, verb_domain },
+ { "default-route", VERB_ANY, 3, 0, verb_default_route },
{ "llmnr", VERB_ANY, 3, 0, verb_llmnr },
{ "mdns", VERB_ANY, 3, 0, verb_mdns },
- { "dnsovertls", VERB_ANY, 3, 0, verb_dns_over_tls },
+ { "dnsovertls", VERB_ANY, 3, 0, verb_dns_over_tls },
{ "dnssec", VERB_ANY, 3, 0, verb_dnssec },
{ "nta", VERB_ANY, VERB_ANY, 0, verb_nta },
{ "revert", VERB_ANY, 2, 0, verb_revert_link },
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index fbe823da6..5b547ba7e 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -1530,6 +1530,10 @@ static int bus_method_set_link_domains(sd_bus_message *message, void *userdata,
return call_link_method(userdata, message, bus_link_method_set_domains, error);
}
+static int bus_method_set_link_default_route(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ return call_link_method(userdata, message, bus_link_method_set_default_route, error);
+}
+
static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return call_link_method(userdata, message, bus_link_method_set_llmnr, error);
}
@@ -1855,6 +1859,7 @@ static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_METHOD("GetLink", "i", "o", bus_method_get_link, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, 0),
SD_BUS_METHOD("SetLinkDomains", "ia(sb)", NULL, bus_method_set_link_domains, 0),
+ SD_BUS_METHOD("SetLinkDefaultRoute", "ib", NULL, bus_method_set_link_default_route, 0),
SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, 0),
SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, 0),
SD_BUS_METHOD("SetLinkDNSOverTLS", "is", NULL, bus_method_set_link_dns_over_tls, 0),
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index 746ff1b8b..7a4f97754 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -682,22 +682,15 @@ int dns_query_go(DnsQuery *q) {
continue;
match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
- if (match < 0)
- return match;
-
- if (match == DNS_SCOPE_NO)
+ if (match < 0) {
+ log_debug("Couldn't check if '%s' matches against scope, ignoring.", name);
continue;
+ }
- found = match;
-
- if (match == DNS_SCOPE_YES) {
+ if (match > found) { /* Does this match better? If so, remember how well it matched, and the first one
+ * that matches this well */
+ found = match;
first = s;
- break;
- } else {
- assert(match == DNS_SCOPE_MAYBE);
-
- if (!first)
- first = s;
}
}
@@ -725,10 +718,12 @@ int dns_query_go(DnsQuery *q) {
continue;
match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
- if (match < 0)
- goto fail;
+ if (match < 0) {
+ log_debug("Couldn't check if '%s' matches agains scope, ignoring.", name);
+ continue;
+ }
- if (match != found)
+ if (match < found)
continue;
r = dns_query_add_candidate(q, s);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index 35c3804db..972e661d7 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -30,15 +30,17 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int
assert(m);
assert(ret);
- s = new0(DnsScope, 1);
+ s = new(DnsScope, 1);
if (!s)
return -ENOMEM;
- s->manager = m;
- s->link = l;
- s->protocol = protocol;
- s->family = family;
- s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC;
+ *s = (DnsScope) {
+ .manager = m,
+ .link = l,
+ .protocol = protocol,
+ .family = family,
+ .resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC,
+ };
if (protocol == DNS_PROTOCOL_DNS) {
/* Copy DNSSEC mode from the link if it is set there,
@@ -457,9 +459,40 @@ int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *add
return dns_scope_socket(s, SOCK_STREAM, family, address, server, port, ret_socket_address);
}
-DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) {
+static DnsScopeMatch accept_link_local_reverse_lookups(const char *domain) {
+ assert(domain);
+
+ if (dns_name_endswith(domain, "254.169.in-addr.arpa") > 0)
+ return DNS_SCOPE_YES_BASE + 4; /* 4 labels match */
+
+ if (dns_name_endswith(domain, "8.e.f.ip6.arpa") > 0 ||
+ dns_name_endswith(domain, "9.e.f.ip6.arpa") > 0 ||
+ dns_name_endswith(domain, "a.e.f.ip6.arpa") > 0 ||
+ dns_name_endswith(domain, "b.e.f.ip6.arpa") > 0)
+ return DNS_SCOPE_YES_BASE + 5; /* 5 labels match */
+
+ return _DNS_SCOPE_MATCH_INVALID;
+}
+
+DnsScopeMatch dns_scope_good_domain(
+ DnsScope *s,
+ int ifindex,
+ uint64_t flags,
+ const char *domain) {
+
DnsSearchDomain *d;
+ /* This returns the following return values:
+ *
+ * DNS_SCOPE_NO → This scope is not suitable for lookups of this domain, at all
+ * DNS_SCOPE_MAYBE → This scope is suitable, but only if nothing else wants it
+ * DNS_SCOPE_YES_BASE+n → This scope is suitable, and 'n' suffix labels match
+ *
+ * (The idea is that the caller will only use the scopes with the longest 'n' returned. If no scopes return
+ * DNS_SCOPE_YES_BASE+n, then it should use those which returned DNS_SCOPE_MAYBE. It should never use those
+ * which returned DNS_SCOPE_NO.)
+ */
+
assert(s);
assert(domain);
@@ -494,23 +527,35 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
switch (s->protocol) {
case DNS_PROTOCOL_DNS: {
- DnsServer *dns_server;
+ int n_best = -1;
/* Never route things to scopes that lack DNS servers */
- dns_server = dns_scope_get_dns_server(s);
- if (!dns_server)
+ if (!dns_scope_get_dns_server(s))
return DNS_SCOPE_NO;
/* Always honour search domains for routing queries, except if this scope lacks DNS servers. Note that
* we return DNS_SCOPE_YES here, rather than just DNS_SCOPE_MAYBE, which means other wildcard scopes
* won't be considered anymore. */
LIST_FOREACH(domains, d, dns_scope_get_search_domains(s))
- if (dns_name_endswith(domain, d->name) > 0)
- return DNS_SCOPE_YES;
+ if (dns_name_endswith(domain, d->name) > 0) {
+ int c;
+
+ c = dns_name_count_labels(d->name);
+ if (c < 0)
+ continue;
+
+ if (c > n_best)
+ n_best = c;
+ }
- /* If the DNS server has route-only domains, don't send other requests to it. This would be a privacy
- * violation, will most probably fail anyway, and adds unnecessary load. */
- if (dns_server_limited_domains(dns_server))
+ /* Let's return the number of labels in the best matching result */
+ if (n_best >= 0) {
+ assert(n_best <= DNS_SCOPE_YES_END - DNS_SCOPE_YES_BASE);
+ return DNS_SCOPE_YES_BASE + n_best;
+ }
+
+ /* See if this scope is suitable as default route. */
+ if (!dns_scope_is_default_route(s))
return DNS_SCOPE_NO;
/* Exclude link-local IP ranges */
@@ -528,25 +573,48 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
return DNS_SCOPE_NO;
}
- case DNS_PROTOCOL_MDNS:
+ case DNS_PROTOCOL_MDNS: {
+ DnsScopeMatch m;
+
+ m = accept_link_local_reverse_lookups(domain);
+ if (m >= 0)
+ return m;
+
if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) ||
- (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) ||
- (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */
+ (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0))
+ return DNS_SCOPE_MAYBE;
+
+ if ((dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */
dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */
manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */
- return DNS_SCOPE_MAYBE;
+ return DNS_SCOPE_YES_BASE + 1; /* Return +1, as the top-level .local domain matches, i.e. one label */
return DNS_SCOPE_NO;
+ }
+
+ case DNS_PROTOCOL_LLMNR: {
+ DnsScopeMatch m;
+
+ m = accept_link_local_reverse_lookups(domain);
+ if (m >= 0)
+ return m;
- case DNS_PROTOCOL_LLMNR:
if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) ||
- (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) ||
- (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */
+ (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0))
+ return DNS_SCOPE_MAYBE;
+
+ if ((dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */
!is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */
manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */
- return DNS_SCOPE_MAYBE;
+ return DNS_SCOPE_YES_BASE + 1; /* Return +1, as we consider ourselves authoritative for
+ * single-label names, i.e. one label. This is particular
+ * relevant as it means a "." route on some other scope won't
+ * pull all traffic away from us. (If people actually want to
+ * pull traffic away from us they should turn off LLMNR on the
+ * link) */
return DNS_SCOPE_NO;
+ }
default:
assert_not_reached("Unknown scope protocol");
@@ -1322,3 +1390,56 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) {
return 0;
}
+
+static bool dns_scope_has_route_only_domains(DnsScope *scope) {
+ DnsSearchDomain *domain, *first;
+ bool route_only = false;
+
+ assert(scope);
+ assert(scope->protocol == DNS_PROTOCOL_DNS);
+
+ /* Returns 'true' if this scope is suitable for queries to specific domains only. For that we check
+ * if there are any route-only domains on this interface, as a heuristic to discern VPN-style links
+ * from non-VPN-style links. Returns 'false' for all other cases, i.e. if the scope is intended to
+ * take queries to arbitrary domains, i.e. has no routing domains set. */
+
+ if (scope->link)
+ first = scope->link->search_domains;
+ else
+ first = scope->manager->search_domains;
+
+ LIST_FOREACH(domains, domain, first) {
+ /* "." means "any domain", thus the interface takes any kind of traffic. Thus, we exit early
+ * here, as it doesn't really matter whether this link has any route-only domains or not,
+ * "~." really trumps everything and clearly indicates that this interface shall receive all
+ * traffic it can get. */
+ if (dns_name_is_root(DNS_SEARCH_DOMAIN_NAME(domain)))
+ return false;
+
+ if (domain->route_only)
+ route_only = true;
+ }
+
+ return route_only;
+}
+
+bool dns_scope_is_default_route(DnsScope *scope) {
+ assert(scope);
+
+ /* Only use DNS scopes as default routes */
+ if (scope->protocol != DNS_PROTOCOL_DNS)
+ return false;
+
+ /* The global DNS scope is always suitable as default route */
+ if (!scope->link)
+ return true;
+
+ /* Honour whatever is explicitly configured. This is really the best approach, and trumps any
+ * automatic logic. */
+ if (scope->link->default_route >= 0)
+ return scope->link->default_route;
+
+ /* Otherwise check if we have any route-only domains, as a sensible heuristic: if so, let's not
+ * volunteer as default route. */
+ return !dns_scope_has_route_only_domains(scope);
+}
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index 04e93f8f7..f4b45c4f2 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -18,9 +18,10 @@ typedef struct DnsScope DnsScope;
typedef enum DnsScopeMatch {
DNS_SCOPE_NO,
DNS_SCOPE_MAYBE,
- DNS_SCOPE_YES,
+ DNS_SCOPE_YES_BASE, /* Add the number of matching labels to this */
+ DNS_SCOPE_YES_END = DNS_SCOPE_YES_BASE + DNS_N_LABELS_MAX,
_DNS_SCOPE_MATCH_MAX,
- _DNS_SCOPE_INVALID = -1
+ _DNS_SCOPE_MATCH_INVALID = -1
} DnsScopeMatch;
struct DnsScope {
@@ -28,6 +29,8 @@ struct DnsScope {
DnsProtocol protocol;
int family;
+
+ /* Copied at scope creation time from the link/manager */
DnssecMode dnssec_mode;
DnsOverTlsMode dns_over_tls_mode;
@@ -104,5 +107,6 @@ int dns_scope_ifindex(DnsScope *s);
int dns_scope_announce(DnsScope *scope, bool goodbye);
int dns_scope_add_dnssd_services(DnsScope *scope);
-
int dns_scope_remove_dnssd_services(DnsScope *scope);
+
+bool dns_scope_is_default_route(DnsScope *scope);
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c
index 3e69741b8..b85eb7527 100644
--- a/src/resolve/resolved-dns-server.c
+++ b/src/resolve/resolved-dns-server.c
@@ -580,26 +580,6 @@ void dns_server_warn_downgrade(DnsServer *server) {
server->warned_downgrade = true;
}
-bool dns_server_limited_domains(DnsServer *server) {
- DnsSearchDomain *domain;
- bool domain_restricted = false;
-
- /* Check if the server has route-only domains without ~., i. e. whether
- * it should only be used for particular domains */
- if (!server->link)
- return false;
-
- LIST_FOREACH(domains, domain, server->link->search_domains)
- if (domain->route_only) {
- domain_restricted = true;
- /* ~. means "any domain", thus it is a global server */
- if (dns_name_is_root(DNS_SEARCH_DOMAIN_NAME(domain)))
- return false;
- }
-
- return domain_restricted;
-}
-
static void dns_server_hash_func(const DnsServer *s, struct siphash *state) {
assert(s);
@@ -906,6 +886,16 @@ void dns_server_unref_stream(DnsServer *s) {
dns_stream_unref(ref);
}
+DnsScope *dns_server_scope(DnsServer *s) {
+ assert(s);
+ assert((s->type == DNS_SERVER_LINK) == !!s->link);
+
+ if (s->link)
+ return s->link->unicast_scope;
+
+ return s->manager->unicast_scope;
+}
+
static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = {
[DNS_SERVER_SYSTEM] = "system",
[DNS_SERVER_FALLBACK] = "fallback",
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index 6e73f32df..3c4627bca 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -122,8 +122,6 @@ bool dns_server_dnssec_supported(DnsServer *server);
void dns_server_warn_downgrade(DnsServer *server);
-bool dns_server_limited_domains(DnsServer *server);
-
DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, int ifindex);
void dns_server_unlink_all(DnsServer *first);
@@ -153,3 +151,5 @@ void dns_server_reset_features_all(DnsServer *s);
void dns_server_dump(DnsServer *s, FILE *f);
void dns_server_unref_stream(DnsServer *s);
+
+DnsScope *dns_server_scope(DnsServer *s);
diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c
index b1581740d..96093fff5 100644
--- a/src/resolve/resolved-link-bus.c
+++ b/src/resolve/resolved-link-bus.c
@@ -107,6 +107,31 @@ static int property_get_domains(
return sd_bus_message_close_container(reply);
}
+static int property_get_default_route(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Link *l = userdata;
+
+ assert(reply);
+ assert(l);
+
+ /* Return what is configured, if there's something configured */
+ if (l->default_route >= 0)
+ return sd_bus_message_append(reply, "b", l->default_route);
+
+ /* Otherwise report what is in effect */
+ if (l->unicast_scope)
+ return sd_bus_message_append(reply, "b", dns_scope_is_default_route(l->unicast_scope));
+
+ return sd_bus_message_append(reply, "b", false);
+}
+
static int property_get_scopes_mask(
sd_bus *bus,
const char *path,
@@ -346,6 +371,31 @@ clear:
return r;
}
+int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Link *l = userdata;
+ int r, b;
+
+ assert(message);
+ assert(l);
+
+ r = verify_unmanaged_link(l, error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(message, "b", &b);
+ if (r < 0)
+ return r;
+
+ if (l->default_route != b) {
+ l->default_route = b;
+
+ (void) link_save_user(l);
+ (void) manager_write_resolv_conf(l->manager);
+ }
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Link *l = userdata;
ResolveSupport mode;
@@ -550,6 +600,7 @@ const sd_bus_vtable link_vtable[] = {
SD_BUS_PROPERTY("DNS", "a(iay)", property_get_dns, 0, 0),
SD_BUS_PROPERTY("CurrentDNSServer", "(iay)", property_get_current_dns_server, offsetof(Link, current_dns_server), 0),
SD_BUS_PROPERTY("Domains", "a(sb)", property_get_domains, 0, 0),
+ SD_BUS_PROPERTY("DefaultRoute", "b", property_get_default_route, 0, 0),
SD_BUS_PROPERTY("LLMNR", "s", bus_property_get_resolve_support, offsetof(Link, llmnr_support), 0),
SD_BUS_PROPERTY("MulticastDNS", "s", bus_property_get_resolve_support, offsetof(Link, mdns_support), 0),
SD_BUS_PROPERTY("DNSOverTLS", "s", property_get_dns_over_tls_mode, 0, 0),
@@ -559,6 +610,7 @@ const sd_bus_vtable link_vtable[] = {
SD_BUS_METHOD("SetDNS", "a(iay)", NULL, bus_link_method_set_dns_servers, 0),
SD_BUS_METHOD("SetDomains", "a(sb)", NULL, bus_link_method_set_domains, 0),
+ SD_BUS_METHOD("SetDefaultRoute", "b", NULL, bus_link_method_set_default_route, 0),
SD_BUS_METHOD("SetLLMNR", "s", NULL, bus_link_method_set_llmnr, 0),
SD_BUS_METHOD("SetMulticastDNS", "s", NULL, bus_link_method_set_mdns, 0),
SD_BUS_METHOD("SetDNSOverTLS", "s", NULL, bus_link_method_set_dns_over_tls, 0),
diff --git a/src/resolve/resolved-link-bus.h b/src/resolve/resolved-link-bus.h
index 6b8092fdb..671725101 100644
--- a/src/resolve/resolved-link-bus.h
+++ b/src/resolve/resolved-link-bus.h
@@ -13,6 +13,7 @@ int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***
int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd_bus_error *error);
diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c
index 2cfb848a8..44f70acea 100644
--- a/src/resolve/resolved-link.c
+++ b/src/resolve/resolved-link.c
@@ -30,16 +30,19 @@ int link_new(Manager *m, Link **ret, int ifindex) {
if (r < 0)
return r;
- l = new0(Link, 1);
+ l = new(Link, 1);
if (!l)
return -ENOMEM;
- l->ifindex = ifindex;
- l->llmnr_support = RESOLVE_SUPPORT_YES;
- l->mdns_support = RESOLVE_SUPPORT_NO;
- l->dnssec_mode = _DNSSEC_MODE_INVALID;
- l->dns_over_tls_mode = _DNS_OVER_TLS_MODE_INVALID;
- l->operstate = IF_OPER_UNKNOWN;
+ *l = (Link) {
+ .ifindex = ifindex,
+ .default_route = -1,
+ .llmnr_support = RESOLVE_SUPPORT_YES,
+ .mdns_support = RESOLVE_SUPPORT_NO,
+ .dnssec_mode = _DNSSEC_MODE_INVALID,
+ .dns_over_tls_mode = _DNS_OVER_TLS_MODE_INVALID,
+ .operstate = IF_OPER_UNKNOWN,
+ };
if (asprintf(&l->state_file, "/run/systemd/resolve/netif/%i", ifindex) < 0)
return -ENOMEM;
@@ -60,6 +63,7 @@ int link_new(Manager *m, Link **ret, int ifindex) {
void link_flush_settings(Link *l) {
assert(l);
+ l->default_route = -1;
l->llmnr_support = RESOLVE_SUPPORT_YES;
l->mdns_support = RESOLVE_SUPPORT_NO;
l->dnssec_mode = _DNSSEC_MODE_INVALID;
@@ -297,6 +301,27 @@ clear:
return r;
}
+static int link_update_default_route(Link *l) {
+ int r;
+
+ assert(l);
+
+ r = sd_network_link_get_dns_default_route(l->ifindex);
+ if (r == -ENODATA) {
+ r = 0;
+ goto clear;
+ }
+ if (r < 0)
+ goto clear;
+
+ l->default_route = r > 0;
+ return 0;
+
+clear:
+ l->default_route = -1;
+ return r;
+}
+
static int link_update_llmnr_support(Link *l) {
_cleanup_free_ char *b = NULL;
int r;
@@ -613,6 +638,10 @@ static void link_read_settings(Link *l) {
r = link_update_search_domains(l);
if (r < 0)
log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name);
+
+ r = link_update_default_route(l);
+ if (r < 0)
+ log_warning_errno(r, "Failed to read default route setting for interface %s, proceeding anyway: %m", l->name);
}
int link_update(Link *l) {
@@ -1109,7 +1138,8 @@ static bool link_needs_save(Link *l) {
if (l->llmnr_support != RESOLVE_SUPPORT_YES ||
l->mdns_support != RESOLVE_SUPPORT_NO ||
- l->dnssec_mode != _DNSSEC_MODE_INVALID)
+ l->dnssec_mode != _DNSSEC_MODE_INVALID ||
+ l->dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID)
return true;
if (l->dns_servers ||
@@ -1119,6 +1149,9 @@ static bool link_needs_save(Link *l) {
if (!set_isempty(l->dnssec_negative_trust_anchors))
return true;
+ if (l->default_route >= 0)
+ return true;
+
return false;
}
@@ -1161,6 +1194,9 @@ int link_save_user(Link *l) {
if (v)
fprintf(f, "DNSSEC=%s\n", v);
+ if (l->default_route >= 0)
+ fprintf(f, "DEFAULT_ROUTE=%s\n", yes_no(l->default_route));
+
if (l->dns_servers) {
DnsServer *server;
@@ -1242,7 +1278,8 @@ int link_load_user(Link *l) {
*dnssec = NULL,
*servers = NULL,
*domains = NULL,
- *ntas = NULL;
+ *ntas = NULL,
+ *default_route = NULL;
ResolveSupport s;
const char *p;
@@ -1265,7 +1302,8 @@ int link_load_user(Link *l) {
"DNSSEC", &dnssec,
"SERVERS", &servers,
"DOMAINS", &domains,
- "NTAS", &ntas);
+ "NTAS", &ntas,
+ "DEFAULT_ROUTE", &default_route);
if (r == -ENOENT)
return 0;
if (r < 0)
@@ -1282,6 +1320,10 @@ int link_load_user(Link *l) {
if (s >= 0)
l->mdns_support = s;
+ r = parse_boolean(default_route);
+ if (r >= 0)
+ l->default_route = r;
+
/* If we can't recognize the DNSSEC setting, then set it to invalid, so that the daemon default is used. */
l->dnssec_mode = dnssec_mode_from_string(dnssec);
diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h
index 81ab2056a..f95ea37a4 100644
--- a/src/resolve/resolved-link.h
+++ b/src/resolve/resolved-link.h
@@ -51,6 +51,8 @@ struct Link {
LIST_HEAD(DnsSearchDomain, search_domains);
unsigned n_search_domains;
+ int default_route;
+
ResolveSupport llmnr_support;
ResolveSupport mdns_support;
DnsOverTlsMode dns_over_tls_mode;
diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c
index 5fcd59d87..5205071d3 100644
--- a/src/resolve/resolved-resolv-conf.c
+++ b/src/resolve/resolved-resolv-conf.c
@@ -217,6 +217,8 @@ clear:
}
static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
+ DnsScope *scope;
+
assert(s);
assert(f);
assert(count);
@@ -226,13 +228,12 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
return;
}
- /* Check if the DNS server is limited to particular domains;
- * resolv.conf does not have a syntax to express that, so it must not
- * appear as a global name server to avoid routing unrelated domains to
- * it (which is a privacy violation, will most probably fail anyway,
- * and adds unnecessary load) */
- if (dns_server_limited_domains(s)) {
- log_debug("DNS server %s has route-only domains, not using as global name server", dns_server_string(s));
+ /* Check if the scope this DNS server belongs to is suitable as 'default' route for lookups; resolv.conf does
+ * not have a syntax to express that, so it must not appear as a global name server to avoid routing unrelated
+ * domains to it (which is a privacy violation, will most probably fail anyway, and adds unnecessary load) */
+ scope = dns_server_scope(s);
+ if (scope && !dns_scope_is_default_route(scope)) {
+ log_debug("Scope of DNS server %s has only route-only domains, not using as global name server", dns_server_string(s));
return;
}
diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h
index c8b7226bc..cc6bca9f5 100644
--- a/src/systemd/sd-network.h
+++ b/src/systemd/sd-network.h
@@ -151,6 +151,9 @@ int sd_network_link_get_search_domains(int ifindex, char ***domains);
/* Get the route DNS domain names for a given link. */
int sd_network_link_get_route_domains(int ifindex, char ***domains);
+/* Get whether this link shall be used as 'default route' for DNS queries */
+int sd_network_link_get_dns_default_route(int ifindex);
+
/* Get the carrier interface indexes to which current link is bound to. */
int sd_network_link_get_carrier_bound_to(int ifindex, int **ifindexes);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index cab87bf98..209132f23 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -159,6 +159,7 @@ InvertRule=
RouterPreference=
DNSLifetimeSec=
DNS=
+DNSDefaultRoute=
RouterLifetimeSec=
Domains=
EmitDNS=
diff --git a/test/networkd-test.py b/test/networkd-test.py
index 176d52a02..7011abc96 100755
--- a/test/networkd-test.py
+++ b/test/networkd-test.py
@@ -652,7 +652,7 @@ Domains= ~company ~lab''')
conf = '/run/systemd/resolved.conf.d/test-disable-dnssec.conf'
os.makedirs(os.path.dirname(conf), exist_ok=True)
with open(conf, 'w') as f:
- f.write('[Resolve]\nDNSSEC=no')
+ f.write('[Resolve]\nDNSSEC=no\nLLMNR=no\nMulticastDNS=no\n')
self.addCleanup(os.remove, conf)
# create /etc/hosts bind mount which resolves my.example for IPv4