1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-01-08 21:17:47 +03:00

Merge pull request #11050 from poettering/resolved-domain-route

resolved: beef up domain routing
This commit is contained in:
Lennart Poettering 2018-12-21 18:03:58 +01:00 committed by GitHub
commit 44f52cce9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 470 additions and 116 deletions

View File

@ -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>

View File

@ -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>

View File

@ -547,6 +547,17 @@
name servers limited to a specific link.</para>
</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>

View File

@ -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;

View File

@ -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",

View File

@ -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)

View File

@ -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);

View File

@ -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);
};

View File

@ -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 },

View File

@ -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),

View File

@ -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);

View File

@ -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;
/* 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))
c = dns_name_count_labels(d->name);
if (c < 0)
continue;
if (c > n_best)
n_best = c;
}
/* 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);
}

View File

@ -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);

View File

@ -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",

View File

@ -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);

View File

@ -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),

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -159,6 +159,7 @@ InvertRule=
RouterPreference=
DNSLifetimeSec=
DNS=
DNSDefaultRoute=
RouterLifetimeSec=
Domains=
EmitDNS=

View File

@ -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