Merge pull request #16059 from keszybz/resolve-single-label-names

Optionally resolve single label names
This commit is contained in:
Lennart Poettering 2020-06-22 14:00:31 +02:00 committed by GitHub
commit 7830b5c103
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 97 additions and 63 deletions

8
NEWS
View file

@ -87,6 +87,12 @@ CHANGES WITH 246 in spe:
used, the DNS-over-TLS certificate is validated to match the used, the DNS-over-TLS certificate is validated to match the
specified hostname. 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 * The fs.suid_dumpable sysctl is set to 2 / "suidsafe". This allows
systemd-coredump to save core files for suid processes. When saving systemd-coredump to save core files for suid processes. When saving
the core file, systemd-coredump will use the effective uid and gid of 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 * systemd-sysusers gained support for creating users with the primary
group named differently than the user. 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) * systemd-growfs (i.e. the x-systemd.growfs mount option in /etc/fstab)
gained support for growing XFS partitions. Previously it supported gained support for growing XFS partitions. Previously it supported
only ext4 and btrfs partitions. only ext4 and btrfs partitions.

View file

@ -67,20 +67,28 @@
<varlistentry> <varlistentry>
<term><varname>Domains=</varname></term> <term><varname>Domains=</varname></term>
<listitem><para>A space-separated list of domains. These domains are used as search suffixes when resolving <listitem><para>A space-separated list of domains optionally prefixed with <literal>~</literal>,
single-label hostnames (domain names which contain no dot), in order to qualify them into fully-qualified used for two distinct purposes described below. Defaults to the empty list.</para>
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 <filename>/etc/resolv.conf</filename> are used instead, if that file exists and any domains
are configured in it. This setting defaults to the empty list.</para>
<para>Specified domain names may optionally be prefixed with <literal>~</literal>. In this case they do not <para>Any domains <emphasis>not</emphasis> prefixed with <literal>~</literal> are used as search
define a search path, but preferably direct DNS queries for the indicated domains to the DNS servers configured suffixes when resolving single-label hostnames (domain names which contain no dot), in order to
with the system <varname>DNS=</varname> setting (see above), in case additional, suitable per-link DNS servers qualify them into fully-qualified domain names (FQDNs). These "search domains" are strictly processed
are known. If no per-link DNS servers are known using the <literal>~</literal> syntax has no effect. Use the in the order they are specified in, until the name with the suffix appended is found. For
construct <literal>~.</literal> (which is composed of <literal>~</literal> to indicate a routing domain and compatibility reasons, if this setting is not specified, the search domains listed in
<literal>.</literal> to indicate the DNS root domain that is the implied suffix of all DNS domains) to use the <filename>/etc/resolv.conf</filename> with the <varname>search</varname> keyword are used instead, if
system DNS server defined with <varname>DNS=</varname> preferably for all domains.</para></listitem> that file exists and any domains are configured in it.</para>
<para>The domains prefixed with <literal>~</literal> are called "routing domains". All domains listed
here (both search domains and routing domains after removing the <literal>~</literal> 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
<varname>DNS=</varname> 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.</para>
<para>Use the construct <literal>~.</literal> (which is composed from <literal>~</literal> to
indicate a routing domain and <literal>.</literal> 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.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -258,11 +266,28 @@
<varlistentry> <varlistentry>
<term><varname>ReadEtcHosts=</varname></term> <term><varname>ReadEtcHosts=</varname></term>
<listitem><para>Takes a boolean argument. If <literal>yes</literal> (the default), the DNS stub resolver will read <listitem><para>Takes a boolean argument. If <literal>yes</literal> (the default),
<filename>/etc/hosts</filename>, and try to resolve hosts or address by using the entries in the file before <command>systemd-resolved</command> will read <filename>/etc/hosts</filename>, and try to resolve
sending query to DNS servers.</para></listitem> hosts or address by using the entries in the file before sending query to DNS servers.
</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>ResolveUnicastSingleLabel=</varname></term>
<listitem><para>Takes a boolean argument. When false (the default),
<command>systemd-resolved</command> 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
<varname>Domains=</varname> above), or using other mechanisms, in particular via LLMNR or from
<filename>/etc/hosts</filename>. When true, queries for single-label names will be forwarded to
global DNS servers even if no search domains are defined.
</para>
<para>This option is provided for compatibility with configurations where <emphasis>public DNS
servers are not used</emphasis>. Forwarding single-label names to servers not under your control is
not standard-conformant, see <ulink
url="https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/">IAB
Statement</ulink>, and may create a privacy and security risk.</para></listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View file

@ -135,14 +135,16 @@
IPv6.</para></listitem> IPv6.</para></listitem>
<listitem><para>Resolution of address records (A and AAAA) via unicast DNS (i.e. not LLMNR or <listitem><para>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 MulticastDNS) for non-synthesized single-label names is allowed for non-top-level domains. This means
means that such records can only be resolved when search domains are defined. For any interface which that such records can be resolved when search domains are defined. For any interface which defines
defines search domains, such look-ups are routed to that interface, suffixed with each of the search search domains, such look-ups are routed to that interface, suffixed with each of the search domains
domains defined on that interface in turn. When global search domains are defined, such look-ups are defined on that interface in turn. When global search domains are defined, such look-ups are routed to
routed to all interfaces, suffixed by each of the global search domains in turn. The details of which all interfaces, suffixed by each of the global search domains in turn. Additionally, lookup of
servers are queried and how the final reply is chosen are described below. Note that this means that single-label names via unicast DNS may be enabled with the
address queries for single-label names are never sent out to remote DNS servers, and if no search <varname>ResolveUnicastSingleLabel=yes</varname> setting. The details of which servers are queried and
domains are defined, resolution will fail.</para></listitem> 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.</para></listitem>
<listitem><para>Other multi-label names are routed to all local interfaces that have a DNS server <listitem><para>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 configured, plus the globally configured DNS servers if there are any. Note that by default, lookups for

View file

@ -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) { static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) {
DnsQueryCandidate *c; _cleanup_(dns_query_candidate_freep) DnsQueryCandidate *c = NULL;
int r; int r;
assert(q); assert(q);
@ -524,24 +524,21 @@ static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) {
return r; return r;
/* If this a single-label domain on DNS, we might append a suitable search domain first. */ /* If this a single-label domain on DNS, we might append a suitable search domain first. */
if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0 && if (!FLAGS_SET(q->flags, SD_RESOLVED_NO_SEARCH) &&
dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna))) { dns_scope_name_wants_search_domain(s, dns_question_first_name(q->question_idna))) {
/* OK, we need a search domain now. Let's find one for this scope */ /* OK, we want a search domain now. Let's find one for this scope */
r = dns_query_candidate_next_search_domain(c); 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)
goto fail; return r;
} }
r = dns_query_candidate_setup_transactions(c); r = dns_query_candidate_setup_transactions(c);
if (r < 0) if (r < 0)
goto fail; return r;
TAKE_PTR(c);
return 0; return 0;
fail:
dns_query_candidate_free(c);
return r;
} }
static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {

View file

@ -102,6 +102,8 @@ enum {
}; };
DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQueryCandidate*, dns_query_candidate_free);
void dns_query_candidate_notify(DnsQueryCandidate *c); 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); int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags);

View file

@ -619,7 +619,7 @@ DnsScopeMatch dns_scope_good_domain(
manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ 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 return DNS_SCOPE_YES_BASE + 1; /* Return +1, as we consider ourselves authoritative
* for single-label names, i.e. one label. This is * 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 * other scope won't pull all traffic away from
* us. (If people actually want to pull traffic away * us. (If people actually want to pull traffic away
* from us they should turn off LLMNR on the * 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) { if (s->protocol == DNS_PROTOCOL_DNS) {
/* On classic DNS, looking up non-address RRs is always /* On classic DNS, looking up non-address RRs is always fine. (Specifically, we want to
* fine. (Specifically, we want to permit looking up * permit looking up DNSKEY and DS records on the root and top-level domains.) */
* DNSKEY and DS records on the root and top-level
* domains.) */
if (!dns_resource_key_is_address(key)) if (!dns_resource_key_is_address(key))
return true; return true;
/* However, we refuse to look up A and AAAA RRs on the /* Unless explicitly overridden, we refuse to look up A and AAAA RRs on the root and
* root and single-label domains, under the assumption * single-label domains, under the assumption that those should be resolved via LLMNR or
* that those should be resolved via LLMNR or search * search path only, and should not be leaked onto the internet. */
* path only, and should not be leaked onto the const char* name = dns_resource_key_name(key);
* internet. */
return !(dns_name_is_single_label(dns_resource_key_name(key)) || if (!s->manager->resolve_unicast_single_label &&
dns_name_is_root(dns_resource_key_name(key))); 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 /* 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; 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); assert(s);
if (s->protocol != DNS_PROTOCOL_DNS) if (s->protocol != DNS_PROTOCOL_DNS)

View file

@ -99,7 +99,7 @@ void dns_scope_dump(DnsScope *s, FILE *f);
DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s); 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); bool dns_scope_network_good(DnsScope *s);

View file

@ -18,13 +18,14 @@ struct ConfigPerfItem;
%struct-type %struct-type
%includes %includes
%% %%
Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0
Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0
Resolve.Domains, config_parse_search_domains, 0, 0 Resolve.Domains, config_parse_search_domains, 0, 0
Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support)
Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support) Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support)
Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) 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.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.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.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.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts)
Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, offsetof(Manager, resolve_unicast_single_label)

View file

@ -70,9 +70,10 @@ struct Manager {
LIST_HEAD(DnsSearchDomain, search_domains); LIST_HEAD(DnsSearchDomain, search_domains);
unsigned n_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; struct stat resolv_conf_stat;
DnsTrustAnchor trust_anchor; DnsTrustAnchor trust_anchor;

View file

@ -22,3 +22,4 @@
#Cache=yes #Cache=yes
#DNSStubListener=yes #DNSStubListener=yes
#ReadEtcHosts=yes #ReadEtcHosts=yes
#ResolveUnicastSingleLabel=no