From 9a1bbc6642e241c8901f159253b40e19761fb059 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 13:31:29 +0100 Subject: resolved: use structured initialization for DnsScope --- src/resolve/resolved-dns-scope.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 35c3804dba..8ea46054a6 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, -- cgit v1.2.3 From dc2bc986eb3883ad4ae6dc7de41a17fc078f3fae Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 13:49:03 +0100 Subject: resolved: check dns_over_tls_mode in link_needs_save() This was forgotten when DoT was added. --- src/resolve/resolved-link.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 2cfb848a8b..0f9a0e9421 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -1109,7 +1109,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 || -- cgit v1.2.3 From 2bfdd6dc548fbe30d153278f63f43b9e466ff0ca Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 22:26:46 +0100 Subject: resolved: rename_DNS_SCOPE_INVALID → _DNS_SCOPE_MATCH_INVALID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The _INVALID and _MAX enum fields should always use the full name of thenum. --- src/resolve/resolved-dns-scope.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 04e93f8f73..37a720b1ec 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -20,7 +20,7 @@ typedef enum DnsScopeMatch { DNS_SCOPE_MAYBE, DNS_SCOPE_YES, _DNS_SCOPE_MATCH_MAX, - _DNS_SCOPE_INVALID = -1 + _DNS_SCOPE_MATCH_INVALID = -1 } DnsScopeMatch; struct DnsScope { -- cgit v1.2.3 From 89307df39492952b9ea76c6f4b320af9efd33980 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 13:00:19 +0100 Subject: resolved: add comment, explaining when Scope variables are copied from Link --- src/resolve/resolved-dns-scope.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 37a720b1ec..7b7236751b 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -28,6 +28,8 @@ struct DnsScope { DnsProtocol protocol; int family; + + /* Copied at scope creation time from the link/manager */ DnssecMode dnssec_mode; DnsOverTlsMode dns_over_tls_mode; -- cgit v1.2.3 From a97a3b256cd6c56ab1d817440d3b8acb3272ee17 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 16:25:00 +0100 Subject: resolved: rework how we determine which scope to send a query to Fixes: #10830 #9825 #9472 --- src/resolve/resolved-dns-query.c | 27 +++++++++++---------------- src/resolve/resolved-dns-scope.c | 37 ++++++++++++++++++++++++++++++++++--- src/resolve/resolved-dns-scope.h | 3 ++- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 746ff1b8be..7a4f97754b 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 8ea46054a6..59f76b0ae9 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -459,9 +459,25 @@ 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) { +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); @@ -497,6 +513,7 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co 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); @@ -507,8 +524,22 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co * 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; + } + + /* 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; + } /* 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. */ diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 7b7236751b..546d01c656 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -18,7 +18,8 @@ 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_MATCH_INVALID = -1 } DnsScopeMatch; -- cgit v1.2.3 From 1750854916df6a777e6c12451eb5eb2aebbc93fe Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 22:27:19 +0100 Subject: resolved: bind .local domains to mDNS with DNS_SCOPE_YES, similar LLMNR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, we'd return DNS_SCOPE_MAYBE for all domain lookups matching LLMNR or mDNS. Let's upgrade this to DNS_SCOPE_YES, to make the binding stronger. The effect of this is that even if "local" is defined as routing domain on some iface, we'll still lookup domains in local via mDNS — if mDNS is turned on. This should not be limiting, as people who don't want such lookups should turn off mDNS altogether, as it is useless if nothing is routed to it. This also has the nice benefit that mDNS/LLMR continue to work if people use "~." as routing domain on some interface. Similar for LLMNR and single label names. Similar also for the link local IPv4 and IPv6 reverse lookups. Fixes: #10125 --- src/resolve/resolved-dns-scope.c | 54 ++++++++++++++++++++++++++++++++++------ test/networkd-test.py | 2 +- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 59f76b0ae9..c2e4d55a32 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -459,6 +459,21 @@ 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); } +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, @@ -561,25 +576,48 @@ DnsScopeMatch dns_scope_good_domain( 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"); diff --git a/test/networkd-test.py b/test/networkd-test.py index 176d52a028..7011abc965 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 -- cgit v1.2.3 From f76fa088994a74be5aae636e4695bc27a50f0cc2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 12:08:18 +0100 Subject: resolved: rework dns_server_limited_domains(), replace by dns_scope_has_route_only_domains() The function dns_server_limited_domains() was very strange as it enumerate the domains associated with a DnsScope object to determine whether any "route-only" domains, but did so as a function associated with a DnsServer object. Let's clear this up, and replace it by a function associated with a DnsScope instead. This makes more sense philosphically and allows us to reduce the loops through which we need to jump to determine whether a scope is suitable for default routing a bit. --- src/resolve/resolved-dns-scope.c | 33 +++++++++++++++++++++++++++++---- src/resolve/resolved-dns-scope.h | 3 ++- src/resolve/resolved-dns-server.c | 30 ++++++++++-------------------- src/resolve/resolved-dns-server.h | 4 ++-- src/resolve/resolved-resolv-conf.c | 15 ++++++++------- 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index c2e4d55a32..be45485998 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -527,12 +527,10 @@ DnsScopeMatch dns_scope_good_domain( 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 @@ -558,7 +556,7 @@ DnsScopeMatch dns_scope_good_domain( /* 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)) + if (dns_scope_has_route_only_domains(s)) return DNS_SCOPE_NO; /* Exclude link-local IP ranges */ @@ -1393,3 +1391,30 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) { return 0; } + +bool dns_scope_has_route_only_domains(DnsScope *scope) { + DnsSearchDomain *domain, *first; + bool route_only = false; + + /* Check if the scope has any route-only domains except for "~.", i. e. whether it should only be + * used for particular domains */ + + if (scope->protocol != DNS_PROTOCOL_DNS) + return false; + + 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. */ + if (dns_name_is_root(DNS_SEARCH_DOMAIN_NAME(domain))) + return false; + + if (domain->route_only) + route_only = true; + } + + return route_only; +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 546d01c656..6f47a5593f 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -107,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_has_route_only_domains(DnsScope *scope); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 3e69741b88..b85eb75273 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 6e73f32df4..3c4627bca5 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-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 5fcd59d876..832251b610 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 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) */ + scope = dns_server_scope(s); + if (scope && dns_scope_has_route_only_domains(scope)) { + log_debug("Scope of DNS server %s has only route-only domains, not using as global name server", dns_server_string(s)); return; } -- cgit v1.2.3 From ca5394d26039f58ea47255be61c265fee4c9597c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 12:40:07 +0100 Subject: resolved: add an explicit way to configure whether a link is useful as default route Previously, we'd use a link as "default" route depending on whether there are route-only domains defined on it or not. (If there are, it would not be used as default route, if there aren't it would.) Let's make this explicit and add a link variable controlling this. The variable is not changeable from the outside yet, but subsequent commits are supposed to add that. Note that making this configurable adds a certain amount of redundancy, as there are now two ways to ensure a link does not receive "default" lookup (i.e. DNS queries matching no configured route): 1. By ensuring that at least one other link configures a route on it (for example by add "." to its search list) 2. By setting this new boolean to false. But this is exactly what is intended with this patch: that there is an explicit way to configure on the link itself whether it receives 'default' traffic, rather than require this to be configured on other links. The variable added is a tri-state: if true, the link is suitable for recieving "default" traffic. If false, the link is not suitable for it. If unset (i.e. negative) the original logic of "has this route-only routes" is used, to ensure compatibility with the status quo ante. --- src/resolve/resolved-dns-scope.c | 43 ++++++++++++++++++++++++++++++-------- src/resolve/resolved-dns-scope.h | 2 +- src/resolve/resolved-link.c | 34 ++++++++++++++++++++++-------- src/resolve/resolved-link.h | 2 ++ src/resolve/resolved-resolv-conf.c | 8 +++---- 5 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index be45485998..972e661d72 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -554,9 +554,8 @@ DnsScopeMatch dns_scope_good_domain( return DNS_SCOPE_YES_BASE + n_best; } - /* 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_scope_has_route_only_domains(s)) + /* 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 */ @@ -1392,15 +1391,17 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) { return 0; } -bool dns_scope_has_route_only_domains(DnsScope *scope) { +static bool dns_scope_has_route_only_domains(DnsScope *scope) { DnsSearchDomain *domain, *first; bool route_only = false; - /* Check if the scope has any route-only domains except for "~.", i. e. whether it should only be - * used for particular domains */ + assert(scope); + assert(scope->protocol == DNS_PROTOCOL_DNS); - if (scope->protocol != DNS_PROTOCOL_DNS) - return false; + /* 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; @@ -1408,7 +1409,10 @@ bool dns_scope_has_route_only_domains(DnsScope *scope) { first = scope->manager->search_domains; LIST_FOREACH(domains, domain, first) { - /* "." means "any domain", thus the interface takes any kind of traffic. */ + /* "." 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; @@ -1418,3 +1422,24 @@ bool dns_scope_has_route_only_domains(DnsScope *scope) { 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 6f47a5593f..f4b45c4f20 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -109,4 +109,4 @@ 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_has_route_only_domains(DnsScope *scope); +bool dns_scope_is_default_route(DnsScope *scope); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 0f9a0e9421..351d51a30a 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; @@ -1120,6 +1124,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; } @@ -1162,6 +1169,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; @@ -1243,7 +1253,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; @@ -1266,7 +1277,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) @@ -1283,6 +1295,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 81ab2056ac..f95ea37a4f 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 832251b610..5205071d3f 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -228,11 +228,11 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { return; } - /* Check if the scope this DNS server belongs to 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) */ + /* 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_has_route_only_domains(scope)) { + 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; } -- cgit v1.2.3 From 77673795dcf5797491e7f785cbf5077d29a15db4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 13:10:11 +0100 Subject: resolved: add bus API to set per-link "default route" boolean --- src/resolve/resolved-bus.c | 5 ++++ src/resolve/resolved-link-bus.c | 52 +++++++++++++++++++++++++++++++++++++++++ src/resolve/resolved-link-bus.h | 1 + 3 files changed, 58 insertions(+) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index fbe823da66..5b547ba7ee 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-link-bus.c b/src/resolve/resolved-link-bus.c index b1581740d8..96093fff53 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 6b8092fdb4..671725101e 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); -- cgit v1.2.3 From efe55c8165b8309a79f37a187ad11534e29ecc82 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 13:30:14 +0100 Subject: resolvectl: minor whitespace fix --- src/resolve/resolvectl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 8a175ebefc..97ebbc3bf3 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -2952,7 +2952,7 @@ static int native_main(int argc, char *argv[], sd_bus *bus) { { "domain", VERB_ANY, VERB_ANY, 0, verb_domain }, { "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 }, -- cgit v1.2.3 From f2fd3cdb454f745e73d7706a5f92ee1f6ee70e79 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 13:29:41 +0100 Subject: resolvectl: add support for reading/writing per-link 'default-route' boolean --- man/resolvectl.xml | 23 +++++++++++------- src/resolve/resolvectl.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/man/resolvectl.xml b/man/resolvectl.xml index e07893dd15..defd592aa9 100644 --- a/man/resolvectl.xml +++ b/man/resolvectl.xml @@ -241,6 +241,7 @@ + @@ -248,18 +249,21 @@ - Get/set per-interface DNS configuration. These commands may be used to configure various DNS - settings for network interfaces that aren't managed by + Get/set per-interface DNS configuration. These commands may be used to configure various DNS settings + for network interfaces that aren't managed by systemd-networkd.service8. (These commands will fail when used on interfaces that are managed by systemd-networkd, please configure their DNS settings directly inside the .network files instead.) These commands may be used to inform systemd-resolved about per-interface DNS configuration determined through external means. The command expects IPv4 or IPv6 address specifications of DNS servers to use. The command expects valid DNS domains, possibly prefixed with - ~, and configures a per-interface search or route-only domain. The , - , and commands may be used to configure - the per-interface LLMNR, MulticastDNS, DNSSEC and DNSOverTLS settings. Finally, command - may be used to configure additional per-interface DNSSEC NTA domains. + ~, and configures a per-interface search or route-only domain. The + 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 , , and + commands may be used to configure the per-interface LLMNR, MulticastDNS, DNSSEC + and DNSOverTLS settings. Finally, command may be used to configure additional + per-interface DNSSEC NTA domains. Options , and can take a single empty string argument to clear their respective value lists. @@ -274,9 +278,10 @@ 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 , - , , , , - , . Note that when a network interface disappears all - configuration is lost automatically, an explicit reverting is not necessary in that case. + , , , , + , , . Note that when a network interface + disappears all configuration is lost automatically, an explicit reverting is not necessary in that + case. diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 97ebbc3bf3..4d533f851a 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,6 +3009,7 @@ 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 }, -- cgit v1.2.3 From 09451975df30663e803826e1634a6b8b54b6dae2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 15:34:37 +0100 Subject: networkd: small simplification --- src/network/networkd-network.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 62dc6a0bf7..16a48a8a6d 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -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); -- cgit v1.2.3 From 7ece6f58970710207ee29dabc7fc2920dca4b335 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 15:34:04 +0100 Subject: networkd: permit DNS "DefaultRoute" configuration in .network files --- man/systemd.network.xml | 11 +++++++++++ src/network/networkd-link.c | 2 ++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 2 +- src/network/networkd-network.h | 9 ++++++--- test/fuzz/fuzz-network-parser/directives.network | 1 + 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 865b46f403..ee464ffff4 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -547,6 +547,17 @@ name servers limited to a specific link. + + DNSDefaultRoute= + + 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 Domains= 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. + + NTP= diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index d73e85cf25..e2851df31a 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 48d8ae52fa..5d8aede593 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 16a48a8a6d..ccc1c3ce89 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) { diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 3a72c5bd9a..f6e62cdd79 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/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index cab87bf98f..209132f239 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= -- cgit v1.2.3 From c629354e84cd154ac30ec0a70ff7d937f02dff0b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 13:47:53 +0100 Subject: sd-network: add new API sd_network_link_get_dns_default_route() This simply reads from networkd's state files whether a link shall be used as DNS default route. --- src/libsystemd/sd-network/sd-network.c | 19 +++++++++++++++++++ src/systemd/sd-network.h | 3 +++ 2 files changed, 22 insertions(+) diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index 4b66a92203..d4b5e248cc 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/systemd/sd-network.h b/src/systemd/sd-network.h index c8b7226bca..cc6bca9f5e 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); -- cgit v1.2.3 From fdb4d3138a8af63a662f429fa8330c0717e79a6f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 13:48:28 +0100 Subject: resolved: read DNS default route option from networkd --- src/resolve/resolved-link.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 351d51a30a..44f70aceaa 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -301,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; @@ -617,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) { -- cgit v1.2.3 From 396c716c62a402c8efa4835d1cc94eda4e5cbcf2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 16:08:40 +0100 Subject: man: split long section in systemd-resolved.service man page into three Also, do some minor updating. --- man/systemd-resolved.service.xml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index 71593686a0..3d7bb52d66 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -66,14 +66,21 @@ /etc/systemd/resolved.conf, the per-link static settings in /etc/systemd/network/*.network files (in case systemd-networkd.service8 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 + resolvectl1, and any DNS server + information made available by other system services. See resolved.conf5 and systemd.network5 for details about systemd's own configuration files for DNS servers. To improve compatibility, /etc/resolv.conf is read in order to discover configured system DNS servers, but only if it is - not a symlink to /run/systemd/resolve/stub-resolv.conf or - /run/systemd/resolve/resolv.conf (see below). + not a symlink to /run/systemd/resolve/stub-resolv.conf, + /usr/lib/systemd/resolv.conf or /run/systemd/resolve/resolv.conf (see + below). + + + + + Synthetic Records systemd-resolved synthesizes DNS resource records (RRs) for the following cases: @@ -99,6 +106,10 @@ to their configured addresses and back, but they will not affect lookups for non-address types (like MX). + + + + Protocols and Routing Lookup requests are routed to the available DNS servers, LLMNR and MulticastDNS interfaces according to the following rules: -- cgit v1.2.3 From 2e88625f03891ea4f121d31c019db7a3a5092dbb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 4 Dec 2018 16:09:11 +0100 Subject: man: document new systemd-resolved.service(8) routing features in more detail --- man/systemd-resolved.service.xml | 43 +++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index 3d7bb52d66..d7334e01ec 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -143,16 +143,45 @@ lookup zones on all matching interfaces). If the lookup failed on all interfaces, the last failing response is returned. - Routing of lookups may be influenced by configuring - per-interface domain names. See - systemd.network5 - for details. Lookups for a hostname ending in one of the - per-interface domains are exclusively routed to the matching - interfaces. + Routing of lookups may be influenced by configuring per-interface domain names and other settings. See + systemd.network5 and + resolvectl1 for details. The + following query routing logic applies for unicast DNS traffic: + + + 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). + + 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. + + If there is no link configured as "DNS default route" and no global DNS server configured, the + compiled-in fallback DNS server is used. + + Otherwise the query is failed as no suitable DNS servers could be determined. + + + The "DNS default route" option is a boolean setting configureable with resolvectl or in + .network 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 ~.) it defaults to false, otherwise + to true. + + 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 ~. 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 + ~. 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. See the resolved D-Bus API Documentation for information about the APIs systemd-resolved provides. - -- cgit v1.2.3