From 801d25ef0f117e283e9bacabb9eb96cdbbab5de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 2 Jun 2020 18:21:04 +0200 Subject: [PATCH 1/5] man: rework description of Domains= We said that ~domains "do not define a search path", which is mighty confusing, because this is exactly what they do. So let's try to make this a bit easier for the reader: start by saying that there are two things going on here, and describe each one from user's POV. --- man/resolved.conf.xml | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 9be41baaa5a..33265f755cb 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -67,20 +67,28 @@ Domains= - A space-separated list of domains. These domains are used as search suffixes when resolving - single-label hostnames (domain names which contain no dot), in order to qualify them into fully-qualified - domain names (FQDNs). Search domains are strictly processed in the order they are specified, until the name - with the suffix appended is found. For compatibility reasons, if this setting is not specified, the search - domains listed in /etc/resolv.conf are used instead, if that file exists and any domains - are configured in it. This setting defaults to the empty list. + A space-separated list of domains optionally prefixed with ~, + used for two distinct purposes described below. Defaults to the empty list. - Specified domain names may optionally be prefixed with ~. In this case they do not - define a search path, but preferably direct DNS queries for the indicated domains to the DNS servers configured - with the system DNS= setting (see above), in case additional, suitable per-link DNS servers - are known. If no per-link DNS servers are known using the ~ syntax has no effect. Use the - construct ~. (which is composed of ~ to indicate a routing domain and - . to indicate the DNS root domain that is the implied suffix of all DNS domains) to use the - system DNS server defined with DNS= preferably for all domains. + Any domains not prefixed with ~ are used as search + suffixes when resolving single-label hostnames (domain names which contain no dot), in order to + qualify them into fully-qualified domain names (FQDNs). These "search domains" are strictly processed + in the order they are specified in, until the name with the suffix appended is found. For + compatibility reasons, if this setting is not specified, the search domains listed in + /etc/resolv.conf with the search keyword are used instead, if + that file exists and any domains are configured in it. + + The domains prefixed with ~ are called "routing domains". All domains listed + here (both search domains and routing domains after removing the ~ prefix) define + a search path that preferably directs DNS queries to this inteface. This search path has an effect + only when suitable per-link DNS servers are known. Such servers may be defined through the + DNS= setting (see above) and dynamically at run time, for example from DHCP + leases. If no per-link DNS servers are known, routing domains have no effect. + + Use the construct ~. (which is composed from ~ to + indicate a routing domain and . to indicate the DNS root domain that is the + implied suffix of all DNS domains) to use the DNS servers defined for this link preferably for all + domains. From 7877e5ca7c7f654478d7bce458a28edb7a157fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 3 Jun 2020 12:53:27 +0200 Subject: [PATCH 2/5] resolved: add dns_query_candidate_freep() --- src/resolve/resolved-dns-query.c | 11 ++++------- src/resolve/resolved-dns-query.h | 2 ++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index d6eca6dfdd7..7b6e20af91f 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -513,7 +513,7 @@ static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { } static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { - DnsQueryCandidate *c; + _cleanup_(dns_query_candidate_freep) DnsQueryCandidate *c = NULL; int r; assert(q); @@ -530,18 +530,15 @@ static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { r = dns_query_candidate_next_search_domain(c); if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ - goto fail; + return r; } r = dns_query_candidate_setup_transactions(c); if (r < 0) - goto fail; + return r; + TAKE_PTR(c); return 0; - -fail: - dns_query_candidate_free(c); - return r; } static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index fc7ccf553e9..fe8a2195571 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -102,6 +102,8 @@ enum { }; DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQueryCandidate*, dns_query_candidate_free); + void dns_query_candidate_notify(DnsQueryCandidate *c); int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); From c2f1e83e2704c1a1f9c49f5ca62b763c37396d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 3 Jun 2020 13:09:59 +0200 Subject: [PATCH 3/5] resolved: drop bit-field annotations for fields in Manager Access to bit fields is less efficient, and since the Manager is a singleton, a byte or two of space in the structure doesn't matter at all. (And in this particular case, because of alignment issues, we wouldn't save anything anyway.) --- src/resolve/resolved-manager.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 6fa5e734bbb..f8710b40857 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -70,9 +70,9 @@ struct Manager { LIST_HEAD(DnsSearchDomain, search_domains); unsigned n_search_domains; - bool need_builtin_fallbacks:1; + bool need_builtin_fallbacks; + bool read_resolv_conf; - bool read_resolv_conf:1; struct stat resolv_conf_stat; DnsTrustAnchor trust_anchor; From 3b5bd7d6b89266ab8355f4baa1541c28149a085f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 3 Jun 2020 13:10:23 +0200 Subject: [PATCH 4/5] resolved: optionally allow single-label A/AAAA queries --- src/resolve/resolved-dns-query.c | 8 ++++---- src/resolve/resolved-dns-scope.c | 27 ++++++++++++++------------- src/resolve/resolved-dns-scope.h | 2 +- src/resolve/resolved-gperf.gperf | 21 +++++++++++---------- src/resolve/resolved-manager.h | 1 + src/resolve/resolved.conf.in | 1 + 6 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 7b6e20af91f..914f464dd74 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -524,12 +524,12 @@ static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { return r; /* If this a single-label domain on DNS, we might append a suitable search domain first. */ - if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0 && - dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna))) { - /* OK, we need a search domain now. Let's find one for this scope */ + if (!FLAGS_SET(q->flags, SD_RESOLVED_NO_SEARCH) && + dns_scope_name_wants_search_domain(s, dns_question_first_name(q->question_idna))) { + /* OK, we want a search domain now. Let's find one for this scope */ r = dns_query_candidate_next_search_domain(c); - if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ + if (r < 0) return r; } diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index d06e428011b..1a5fef13dcc 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -619,7 +619,7 @@ DnsScopeMatch dns_scope_good_domain( manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ 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 + * particularly 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 @@ -651,20 +651,21 @@ bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { if (s->protocol == DNS_PROTOCOL_DNS) { - /* On classic DNS, looking up non-address RRs is always - * fine. (Specifically, we want to permit looking up - * DNSKEY and DS records on the root and top-level - * domains.) */ + /* On classic DNS, looking up non-address RRs is always fine. (Specifically, we want to + * permit looking up DNSKEY and DS records on the root and top-level domains.) */ if (!dns_resource_key_is_address(key)) return true; - /* However, we refuse to look up A and AAAA RRs on the - * root and single-label domains, under the assumption - * that those should be resolved via LLMNR or search - * path only, and should not be leaked onto the - * internet. */ - return !(dns_name_is_single_label(dns_resource_key_name(key)) || - dns_name_is_root(dns_resource_key_name(key))); + /* Unless explicitly overridden, we refuse to look up A and AAAA RRs on the root and + * single-label domains, under the assumption that those should be resolved via LLMNR or + * search path only, and should not be leaked onto the internet. */ + const char* name = dns_resource_key_name(key); + + if (!s->manager->resolve_unicast_single_label && + dns_name_is_single_label(name)) + return false; + + return !dns_name_is_root(name); } /* On mDNS and LLMNR, send A and AAAA queries only on the @@ -1169,7 +1170,7 @@ DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { return s->manager->search_domains; } -bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { +bool dns_scope_name_wants_search_domain(DnsScope *s, const char *name) { assert(s); if (s->protocol != DNS_PROTOCOL_DNS) diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 974692be5b1..b356b921205 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -99,7 +99,7 @@ void dns_scope_dump(DnsScope *s, FILE *f); DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s); -bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name); +bool dns_scope_name_wants_search_domain(DnsScope *s, const char *name); bool dns_scope_network_good(DnsScope *s); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index 4a451ccc4c7..553da8d2518 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -18,13 +18,14 @@ struct ConfigPerfItem; %struct-type %includes %% -Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 -Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 -Resolve.Domains, config_parse_search_domains, 0, 0 -Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) -Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support) -Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) -Resolve.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Manager, dns_over_tls_mode) -Resolve.Cache, config_parse_dns_cache_mode, DNS_CACHE_MODE_YES, offsetof(Manager, enable_cache) -Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode) -Resolve.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts) +Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 +Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 +Resolve.Domains, config_parse_search_domains, 0, 0 +Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) +Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support) +Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) +Resolve.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Manager, dns_over_tls_mode) +Resolve.Cache, config_parse_dns_cache_mode, DNS_CACHE_MODE_YES, offsetof(Manager, enable_cache) +Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode) +Resolve.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts) +Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, offsetof(Manager, resolve_unicast_single_label) diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index f8710b40857..59944df7469 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -72,6 +72,7 @@ struct Manager { bool need_builtin_fallbacks; bool read_resolv_conf; + bool resolve_unicast_single_label; struct stat resolv_conf_stat; diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 85822e316c1..082ad716261 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -22,3 +22,4 @@ #Cache=yes #DNSStubListener=yes #ReadEtcHosts=yes +#ResolveUnicastSingleLabel=no From 2bd5e1b272cf6f639e024794cb236de1a2f9835e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 3 Jun 2020 17:01:34 +0200 Subject: [PATCH 5/5] man: document the new option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also correct "stub resolver" → "systemd-resolved" in one other option. --- NEWS | 8 ++++++-- man/resolved.conf.xml | 23 ++++++++++++++++++++--- man/systemd-resolved.service.xml | 18 ++++++++++-------- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index b27d38be770..da9402867eb 100644 --- a/NEWS +++ b/NEWS @@ -87,6 +87,12 @@ CHANGES WITH 246 in spe: used, the DNS-over-TLS certificate is validated to match the specified hostname. + * systemd-resolved may be configured to forward single-label DNS names. + This is not standard-conformant, but may make sense in setups where + public DNS servers are not used. + + * systemd-resolved's DNS-over-TLS support gained SNI validation. + * The fs.suid_dumpable sysctl is set to 2 / "suidsafe". This allows systemd-coredump to save core files for suid processes. When saving the core file, systemd-coredump will use the effective uid and gid of @@ -528,8 +534,6 @@ CHANGES WITH 245: * systemd-sysusers gained support for creating users with the primary group named differently than the user. - * systemd-resolved's DNS-over-TLS support gained SNI validation. - * systemd-growfs (i.e. the x-systemd.growfs mount option in /etc/fstab) gained support for growing XFS partitions. Previously it supported only ext4 and btrfs partitions. diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 33265f755cb..0e9b90c1cd9 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -266,11 +266,28 @@ ReadEtcHosts= - Takes a boolean argument. If yes (the default), the DNS stub resolver will read - /etc/hosts, and try to resolve hosts or address by using the entries in the file before - sending query to DNS servers. + Takes a boolean argument. If yes (the default), + systemd-resolved will read /etc/hosts, and try to resolve + hosts or address by using the entries in the file before sending query to DNS servers. + + + ResolveUnicastSingleLabel= + Takes a boolean argument. When false (the default), + systemd-resolved will not resolve A and AAAA queries for single-label names over + classic DNS. Note that such names may still be resolved if search domains are specified (see + Domains= above), or using other mechanisms, in particular via LLMNR or from + /etc/hosts. When true, queries for single-label names will be forwarded to + global DNS servers even if no search domains are defined. + + + This option is provided for compatibility with configurations where public DNS + servers are not used. Forwarding single-label names to servers not under your control is + not standard-conformant, see IAB + Statement, and may create a privacy and security risk. + diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index 6e1ee9f4a51..914607e3f8d 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -135,14 +135,16 @@ IPv6. Resolution of address records (A and AAAA) via unicast DNS (i.e. not LLMNR or - MulticastDNS) for non-synthesized single-label names is only allowed for non-top-level domains. This - means that such records can only be resolved when search domains are defined. For any interface which - defines search domains, such look-ups are routed to that interface, suffixed with each of the search - domains defined on that interface in turn. When global search domains are defined, such look-ups are - routed to all interfaces, suffixed by each of the global search domains in turn. The details of which - servers are queried and how the final reply is chosen are described below. Note that this means that - address queries for single-label names are never sent out to remote DNS servers, and if no search - domains are defined, resolution will fail. + MulticastDNS) for non-synthesized single-label names is allowed for non-top-level domains. This means + that such records can be resolved when search domains are defined. For any interface which defines + search domains, such look-ups are routed to that interface, suffixed with each of the search domains + defined on that interface in turn. When global search domains are defined, such look-ups are routed to + all interfaces, suffixed by each of the global search domains in turn. Additionally, lookup of + single-label names via unicast DNS may be enabled with the + ResolveUnicastSingleLabel=yes setting. The details of which servers are queried and + how the final reply is chosen are described below. Note that this means that address queries for + single-label names are never sent out to remote DNS servers by default, and if no search domains are + defined, resolution will fail. Other multi-label names are routed to all local interfaces that have a DNS server configured, plus the globally configured DNS servers if there are any. Note that by default, lookups for