diff --git a/NEWS b/NEWS
index 7db370516a..8d93d0c813 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
@@ -538,8 +544,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 9be41baaa5..0e9b90c1cd 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.
@@ -258,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 6e1ee9f4a5..914607e3f8 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
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index d6eca6dfdd..914f464dd7 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);
@@ -524,24 +524,21 @@ 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. */
- goto fail;
+ if (r < 0)
+ 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 fc7ccf553e..fe8a219557 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);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index d06e428011..1a5fef13dc 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 974692be5b..b356b92120 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 4a451ccc4c..553da8d251 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 6fa5e734bb..59944df746 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -70,9 +70,10 @@ 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 resolve_unicast_single_label;
- bool read_resolv_conf:1;
struct stat resolv_conf_stat;
DnsTrustAnchor trust_anchor;
diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in
index 85822e316c..082ad71626 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