]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-query.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / resolve / resolved-dns-query.c
index fc5bf4020f6b6c98fe67877d70d9ac159442f66c..227d0b5d11d00aecf33917cc00af6e6c4b9566ea 100644 (file)
@@ -1,5 +1,4 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
 
 #include "alloc-util.h"
 #include "dns-domain.h"
+#include "dns-type.h"
 #include "hostname-util.h"
 #include "local-addresses.h"
 #include "resolved-dns-query.h"
+#include "resolved-dns-synthesize.h"
+#include "resolved-etc-hosts.h"
 #include "string-util.h"
 
 /* How long to wait for the query in total */
-#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC)
+#define QUERY_TIMEOUT_USEC (60 * USEC_PER_SEC)
 
 #define CNAME_MAX 8
 #define QUERIES_MAX 2048
@@ -61,6 +63,7 @@ static void dns_query_candidate_stop(DnsQueryCandidate *c) {
 
         while ((t = set_steal_first(c->transactions))) {
                 set_remove(t->notify_query_candidates, c);
+                set_remove(t->notify_query_candidates_done, c);
                 dns_transaction_gc(t);
         }
 }
@@ -81,9 +84,7 @@ DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) {
         if (c->scope)
                 LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c);
 
-        free(c);
-
-        return NULL;
+        return mfree(c);
 }
 
 static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) {
@@ -91,17 +92,20 @@ static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) {
 
         assert(c);
 
-        if (c->search_domain && c->search_domain->linked) {
+        if (c->search_domain && c->search_domain->linked)
                 next = c->search_domain->domains_next;
+        else
+                next = dns_scope_get_search_domains(c->scope);
 
+        for (;;) {
                 if (!next) /* We hit the end of the list */
                         return 0;
 
-        } else {
-                next = dns_scope_get_search_domains(c->scope);
+                if (!next->route_only)
+                        break;
 
-                if (!next) /* OK, there's nothing. */
-                        return 0;
+                /* Skip over route-only domains */
+                next = next->domains_next;
         }
 
         dns_search_domain_unref(c->search_domain);
@@ -135,6 +139,10 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
         if (r < 0)
                 goto gc;
 
+        r = set_ensure_allocated(&t->notify_query_candidates_done, NULL);
+        if (r < 0)
+                goto gc;
+
         r = set_put(t->notify_query_candidates, c);
         if (r < 0)
                 goto gc;
@@ -145,6 +153,7 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
                 goto gc;
         }
 
+        t->clamp_ttl = c->query->clamp_ttl;
         return 1;
 
 gc:
@@ -156,6 +165,7 @@ static int dns_query_candidate_go(DnsQueryCandidate *c) {
         DnsTransaction *t;
         Iterator i;
         int r;
+        unsigned n = 0;
 
         assert(c);
 
@@ -167,8 +177,14 @@ static int dns_query_candidate_go(DnsQueryCandidate *c) {
                 r = dns_transaction_go(t);
                 if (r < 0)
                         return r;
+
+                n++;
         }
 
+        /* If there was nothing to start, then let's proceed immediately */
+        if (n == 0)
+                dns_query_candidate_notify(c);
+
         return 0;
 }
 
@@ -180,7 +196,7 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) {
         assert(c);
 
         if (c->error_code != 0)
-                return DNS_TRANSACTION_RESOURCES;
+                return DNS_TRANSACTION_ERRNO;
 
         SET_FOREACH(t, c->transactions, i) {
 
@@ -217,6 +233,31 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) {
         return state;
 }
 
+static bool dns_query_candidate_is_routable(DnsQueryCandidate *c, uint16_t type) {
+        int family;
+
+        assert(c);
+
+        /* Checks whether the specified RR type matches an address family that is routable on the link(s) the scope of
+         * this candidate belongs to. Specifically, whether there's a routable IPv4 address on it if we query an A RR,
+         * or a routable IPv6 address if we query an AAAA RR. */
+
+        if (!c->query->suppress_unroutable_family)
+                return true;
+
+        if (c->scope->protocol != DNS_PROTOCOL_DNS)
+                return true;
+
+        family = dns_type_to_af(type);
+        if (family < 0)
+                return true;
+
+        if (c->scope->link)
+                return link_relevant(c->scope->link, family, false);
+        else
+                return manager_routable(c->scope->manager, family);
+}
+
 static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
         DnsQuestion *question;
         DnsResourceKey *key;
@@ -231,14 +272,24 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
         /* Create one transaction per question key */
         DNS_QUESTION_FOREACH(key, question) {
                 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL;
+                DnsResourceKey *qkey;
+
+                if (!dns_query_candidate_is_routable(c, key->type))
+                        continue;
 
                 if (c->search_domain) {
                         r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name);
                         if (r < 0)
                                 goto fail;
-                }
 
-                r = dns_query_candidate_add_transaction(c, new_key ?: key);
+                        qkey = new_key;
+                } else
+                        qkey = key;
+
+                if (!dns_scope_good_key(c->scope, qkey))
+                        continue;
+
+                r = dns_query_candidate_add_transaction(c, qkey);
                 if (r < 0)
                         goto fail;
 
@@ -322,6 +373,7 @@ static void dns_query_reset_answer(DnsQuery *q) {
         q->answer = dns_answer_unref(q->answer);
         q->answer_rcode = 0;
         q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
+        q->answer_errno = 0;
         q->answer_authenticated = false;
         q->answer_protocol = _DNS_PROTOCOL_INVALID;
         q->answer_family = AF_UNSPEC;
@@ -351,6 +403,17 @@ DnsQuery *dns_query_free(DnsQuery *q) {
         sd_bus_message_unref(q->request);
         sd_bus_track_unref(q->bus_track);
 
+        dns_packet_unref(q->request_dns_packet);
+        dns_packet_unref(q->reply_dns_packet);
+
+        if (q->request_dns_stream) {
+                /* Detach the stream from our query, in case something else keeps a reference to it. */
+                q->request_dns_stream->complete = NULL;
+                q->request_dns_stream->on_packet = NULL;
+                q->request_dns_stream->query = NULL;
+                dns_stream_unref(q->request_dns_stream);
+        }
+
         free(q->request_address_string);
 
         if (q->manager) {
@@ -358,9 +421,7 @@ DnsQuery *dns_query_free(DnsQuery *q) {
                 q->manager->n_dns_queries--;
         }
 
-        free(q);
-
-        return NULL;
+        return mfree(q);
 }
 
 int dns_query_new(
@@ -368,12 +429,14 @@ int dns_query_new(
                 DnsQuery **ret,
                 DnsQuestion *question_utf8,
                 DnsQuestion *question_idna,
-                int ifindex, uint64_t flags) {
+                int ifindex,
+                uint64_t flags) {
 
         _cleanup_(dns_query_freep) DnsQuery *q = NULL;
         DnsResourceKey *key;
         bool good = false;
         int r;
+        char key_str[DNS_RESOURCE_KEY_STRING_MAX];
 
         assert(m);
 
@@ -424,31 +487,20 @@ int dns_query_new(
         q->answer_family = AF_UNSPEC;
 
         /* First dump UTF8  question */
-        DNS_QUESTION_FOREACH(key, question_utf8) {
-                _cleanup_free_ char *p = NULL;
-
-                r = dns_resource_key_to_string(key, &p);
-                if (r < 0)
-                        return r;
-
-                log_debug("Looking up RR for %s.", strstrip(p));
-        }
+        DNS_QUESTION_FOREACH(key, question_utf8)
+                log_debug("Looking up RR for %s.",
+                          dns_resource_key_to_string(key, key_str, sizeof key_str));
 
         /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */
         DNS_QUESTION_FOREACH(key, question_idna) {
-                _cleanup_free_ char *p = NULL;
-
                 r = dns_question_contains(question_utf8, key);
                 if (r < 0)
                         return r;
                 if (r > 0)
                         continue;
 
-                r = dns_resource_key_to_string(key, &p);
-                if (r < 0)
-                        return r;
-
-                log_debug("Looking up IDNA RR for %s.", strstrip(p));
+                log_debug("Looking up IDNA RR for %s.",
+                          dns_resource_key_to_string(key, key_str, sizeof key_str));
         }
 
         LIST_PREPEND(queries, m->dns_queries, q);
@@ -466,7 +518,7 @@ int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) {
         assert(q);
         assert(auxiliary_for);
 
-        /* Ensure that that the query is not auxiliary yet, and
+        /* Ensure that the query is not auxiliary yet, and
          * nothing else is auxiliary to it either */
         assert(!q->auxiliary_for);
         assert(!q->auxiliary_queries);
@@ -547,413 +599,84 @@ fail:
         return r;
 }
 
-static int SYNTHESIZE_IFINDEX(int ifindex) {
-
-        /* When the caller asked for resolving on a specific
-         * interface, we synthesize the answer for that
-         * interface. However, if nothing specific was claimed and we
-         * only return localhost RRs, we synthesize the answer for
-         * localhost. */
-
-        if (ifindex > 0)
-                return ifindex;
-
-        return LOOPBACK_IFINDEX;
-}
-
-static int SYNTHESIZE_FAMILY(uint64_t flags) {
-
-        /* Picks an address family depending on set flags. This is
-         * purely for synthesized answers, where the family we return
-         * for the reply should match what was requested in the
-         * question, even though we are synthesizing the answer
-         * here. */
-
-        if (!(flags & SD_RESOLVED_DNS)) {
-                if (flags & SD_RESOLVED_LLMNR_IPV4)
-                        return AF_INET;
-                if (flags & SD_RESOLVED_LLMNR_IPV6)
-                        return AF_INET6;
-        }
-
-        return AF_UNSPEC;
-}
-
-static DnsProtocol SYNTHESIZE_PROTOCOL(uint64_t flags) {
-
-        /* Similar as SYNTHESIZE_FAMILY() but does this for the
-         * protocol. If resolving via DNS was requested, we claim it
-         * was DNS. Similar, if nothing specific was
-         * requested. However, if only resolving via LLMNR was
-         * requested we return that. */
-
-        if (flags & SD_RESOLVED_DNS)
-                return DNS_PROTOCOL_DNS;
-        if (flags & SD_RESOLVED_LLMNR)
-                return DNS_PROTOCOL_LLMNR;
-
-        return DNS_PROTOCOL_DNS;
-}
-
-static int dns_type_to_af(uint16_t t) {
-        switch (t) {
-
-        case DNS_TYPE_A:
-                return AF_INET;
-
-        case DNS_TYPE_AAAA:
-                return AF_INET6;
-
-        case DNS_TYPE_ANY:
-                return AF_UNSPEC;
-
-        default:
-                return -EINVAL;
-        }
-}
-
-static int synthesize_localhost_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
-        int r;
-
-        assert(q);
-        assert(key);
-        assert(answer);
-
-        r = dns_answer_reserve(answer, 2);
-        if (r < 0)
-                return r;
-
-        if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) {
-                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
-
-                rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, DNS_RESOURCE_KEY_NAME(key));
-                if (!rr)
-                        return -ENOMEM;
-
-                rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK);
-
-                r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
-                if (r < 0)
-                        return r;
-        }
-
-        if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) {
-                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
-
-                rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, DNS_RESOURCE_KEY_NAME(key));
-                if (!rr)
-                        return -ENOMEM;
-
-                rr->aaaa.in6_addr = in6addr_loopback;
-
-                r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
-                if (r < 0)
-                        return r;
-        }
-
-        return 0;
-}
-
-static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) {
-        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
-
-        rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from);
-        if (!rr)
-                return -ENOMEM;
-
-        rr->ptr.name = strdup(to);
-        if (!rr->ptr.name)
-                return -ENOMEM;
-
-        return dns_answer_add(*answer, rr, ifindex, flags);
-}
-
-static int synthesize_localhost_ptr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
-        int r;
-
-        assert(q);
-        assert(key);
-        assert(answer);
-
-        if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) {
-                r = dns_answer_reserve(answer, 1);
-                if (r < 0)
-                        return r;
-
-                r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
-                if (r < 0)
-                        return r;
-        }
-
-        return 0;
-}
-
-static int answer_add_addresses_rr(
-                DnsAnswer **answer,
-                const char *name,
-                struct local_address *addresses,
-                unsigned n_addresses) {
-
-        unsigned j;
-        int r;
-
-        assert(answer);
-        assert(name);
-
-        r = dns_answer_reserve(answer, n_addresses);
-        if (r < 0)
-                return r;
-
-        for (j = 0; j < n_addresses; j++) {
-                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
-
-                r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name);
-                if (r < 0)
-                        return r;
-
-                r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED);
-                if (r < 0)
-                        return r;
-        }
-
-        return 0;
-}
-
-static int answer_add_addresses_ptr(
-                DnsAnswer **answer,
-                const char *name,
-                struct local_address *addresses,
-                unsigned n_addresses,
-                int af, const union in_addr_union *match) {
-
-        unsigned j;
-        int r;
-
-        assert(answer);
-        assert(name);
-
-        for (j = 0; j < n_addresses; j++) {
-                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
-
-                if (af != AF_UNSPEC) {
-
-                        if (addresses[j].family != af)
-                                continue;
-
-                        if (match && !in_addr_equal(af, match, &addresses[j].address))
-                                continue;
-                }
-
-                r = dns_answer_reserve(answer, 1);
-                if (r < 0)
-                        return r;
-
-                r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name);
-                if (r < 0)
-                        return r;
-
-                r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED);
-                if (r < 0)
-                        return r;
-        }
-
-        return 0;
-}
-
-static int synthesize_system_hostname_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
-        _cleanup_free_ struct local_address *addresses = NULL;
-        int n = 0, af;
-
-        assert(q);
-        assert(key);
-        assert(answer);
-
-        af = dns_type_to_af(key->type);
-        if (af >= 0) {
-                n = local_addresses(q->manager->rtnl, q->ifindex, af, &addresses);
-                if (n < 0)
-                        return n;
-
-                if (n == 0) {
-                        struct local_address buffer[2];
-
-                        /* If we have no local addresses then use ::1
-                         * and 127.0.0.2 as local ones. */
-
-                        if (af == AF_INET || af == AF_UNSPEC)
-                                buffer[n++] = (struct local_address) {
-                                        .family = AF_INET,
-                                        .ifindex = SYNTHESIZE_IFINDEX(q->ifindex),
-                                        .address.in.s_addr = htobe32(0x7F000002),
-                                };
-
-                        if (af == AF_INET6 || af == AF_UNSPEC)
-                                buffer[n++] = (struct local_address) {
-                                        .family = AF_INET6,
-                                        .ifindex = SYNTHESIZE_IFINDEX(q->ifindex),
-                                        .address.in6 = in6addr_loopback,
-                                };
-
-                        return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), buffer, n);
-                }
-        }
-
-        return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n);
-}
-
-static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) {
-        _cleanup_free_ struct local_address *addresses = NULL;
-        int n, r;
-
-        assert(q);
-        assert(address);
-        assert(answer);
-
-        if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) {
-
-                /* Always map the IPv4 address 127.0.0.2 to the local
-                 * hostname, in addition to "localhost": */
-
-                r = dns_answer_reserve(answer, 3);
-                if (r < 0)
-                        return r;
-
-                r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->llmnr_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
-                if (r < 0)
-                        return r;
-
-                r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->mdns_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
-                if (r < 0)
-                        return r;
-
-                r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
-                if (r < 0)
-                        return r;
-
-                return 0;
-        }
-
-        n = local_addresses(q->manager->rtnl, q->ifindex, af, &addresses);
-        if (n < 0)
-                return n;
-
-        r = answer_add_addresses_ptr(answer, q->manager->llmnr_hostname, addresses, n, af, address);
-        if (r < 0)
-                return r;
-
-        return answer_add_addresses_ptr(answer, q->manager->mdns_hostname, addresses, n, af, address);
-}
-
-static int synthesize_gateway_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
-        _cleanup_free_ struct local_address *addresses = NULL;
-        int n = 0, af;
-
-        assert(q);
-        assert(key);
-        assert(answer);
-
-        af = dns_type_to_af(key->type);
-        if (af >= 0) {
-                n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses);
-                if (n < 0)
-                        return n;
-        }
-
-        return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n);
-}
-
-static int synthesize_gateway_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) {
-        _cleanup_free_ struct local_address *addresses = NULL;
-        int n;
-
-        assert(q);
-        assert(address);
-        assert(answer);
-
-        n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses);
-        if (n < 0)
-                return n;
-
-        return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address);
-}
-
 static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
-        DnsResourceKey *key;
         int r;
 
         assert(q);
         assert(state);
 
-        /* Tries to synthesize localhost RR replies where appropriate */
+        /* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the
+         * the normal lookup finished. The data from the network hence takes precedence over the data we
+         * synthesize. (But note that many scopes refuse to resolve certain domain names) */
 
         if (!IN_SET(*state,
                     DNS_TRANSACTION_RCODE_FAILURE,
                     DNS_TRANSACTION_NO_SERVERS,
                     DNS_TRANSACTION_TIMEOUT,
-                    DNS_TRANSACTION_ATTEMPTS_MAX_REACHED))
+                    DNS_TRANSACTION_ATTEMPTS_MAX_REACHED,
+                    DNS_TRANSACTION_NETWORK_DOWN,
+                    DNS_TRANSACTION_NOT_FOUND))
                 return 0;
 
-        DNS_QUESTION_FOREACH(key, q->question_utf8) {
-                union in_addr_union address;
-                const char *name;
-                int af;
+        r = dns_synthesize_answer(
+                        q->manager,
+                        q->question_utf8,
+                        q->ifindex,
+                        &answer);
+        if (r == -ENXIO) {
+                /* If we get ENXIO this tells us to generate NXDOMAIN unconditionally. */
+
+                dns_query_reset_answer(q);
+                q->answer_rcode = DNS_RCODE_NXDOMAIN;
+                q->answer_protocol = dns_synthesize_protocol(q->flags);
+                q->answer_family = dns_synthesize_family(q->flags);
+                q->answer_authenticated = true;
+                *state = DNS_TRANSACTION_RCODE_FAILURE;
 
-                if (key->class != DNS_CLASS_IN &&
-                    key->class != DNS_CLASS_ANY)
-                        continue;
-
-                name = DNS_RESOURCE_KEY_NAME(key);
-
-                if (is_localhost(name)) {
-
-                        r = synthesize_localhost_rr(q, key, &answer);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to synthesize localhost RRs: %m");
-
-                } else if (manager_is_own_hostname(q->manager, name)) {
+                return 0;
+        }
+        if (r <= 0)
+                return r;
 
-                        r = synthesize_system_hostname_rr(q, key, &answer);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to synthesize system hostname RRs: %m");
+        dns_query_reset_answer(q);
 
-                } else if (is_gateway_hostname(name)) {
+        q->answer = answer;
+        answer = NULL;
+        q->answer_rcode = DNS_RCODE_SUCCESS;
+        q->answer_protocol = dns_synthesize_protocol(q->flags);
+        q->answer_family = dns_synthesize_family(q->flags);
+        q->answer_authenticated = true;
 
-                        r = synthesize_gateway_rr(q, key, &answer);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to synthesize gateway RRs: %m");
+        *state = DNS_TRANSACTION_SUCCESS;
 
-                } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) ||
-                           dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) {
+        return 1;
+}
 
-                        r = synthesize_localhost_ptr(q, key, &answer);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m");
+static int dns_query_try_etc_hosts(DnsQuery *q) {
+        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+        int r;
 
-                } else if (dns_name_address(name, &af, &address) > 0) {
+        assert(q);
 
-                        r = synthesize_system_hostname_ptr(q, af, &address, &answer);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m");
+        /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is done. The
+         * data from /etc/hosts hence takes precedence over the network. */
 
-                        r = synthesize_gateway_ptr(q, af, &address, &answer);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m");
-                }
-        }
+        r = manager_etc_hosts_lookup(
+                        q->manager,
+                        q->question_utf8,
+                        &answer);
+        if (r <= 0)
+                return r;
 
-        if (!answer)
-                return 0;
+        dns_query_reset_answer(q);
 
-        dns_answer_unref(q->answer);
         q->answer = answer;
         answer = NULL;
-
         q->answer_rcode = DNS_RCODE_SUCCESS;
-        q->answer_protocol = SYNTHESIZE_PROTOCOL(q->flags);
-        q->answer_family = SYNTHESIZE_FAMILY(q->flags);
-
-        *state = DNS_TRANSACTION_SUCCESS;
+        q->answer_protocol = dns_synthesize_protocol(q->flags);
+        q->answer_family = dns_synthesize_family(q->flags);
+        q->answer_authenticated = true;
 
         return 1;
 }
@@ -969,6 +692,14 @@ int dns_query_go(DnsQuery *q) {
         if (q->state != DNS_TRANSACTION_NULL)
                 return 0;
 
+        r = dns_query_try_etc_hosts(q);
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                dns_query_complete(q, DNS_TRANSACTION_SUCCESS);
+                return 1;
+        }
+
         LIST_FOREACH(scopes, s, q->manager->dns_scopes) {
                 DnsScopeMatch match;
                 const char *name;
@@ -1000,7 +731,10 @@ int dns_query_go(DnsQuery *q) {
         if (found == DNS_SCOPE_NO) {
                 DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS;
 
-                dns_query_synthesize_reply(q, &state);
+                r = dns_query_synthesize_reply(q, &state);
+                if (r < 0)
+                        return r;
+
                 dns_query_complete(q, state);
                 return 1;
         }
@@ -1029,10 +763,7 @@ int dns_query_go(DnsQuery *q) {
                         goto fail;
         }
 
-        q->answer = dns_answer_unref(q->answer);
-        q->answer_rcode = 0;
-        q->answer_family = AF_UNSPEC;
-        q->answer_protocol = _DNS_PROTOCOL_INVALID;
+        dns_query_reset_answer(q);
 
         r = sd_event_add_time(
                         q->manager->event,
@@ -1078,24 +809,36 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
         assert(q);
 
         if (!c) {
-                dns_query_synthesize_reply(q, &state);
+                r = dns_query_synthesize_reply(q, &state);
+                if (r < 0)
+                        goto fail;
+
                 dns_query_complete(q, state);
                 return;
         }
 
+        if (c->error_code != 0) {
+                /* If the candidate had an error condition of its own, start with that. */
+                state = DNS_TRANSACTION_ERRNO;
+                q->answer = dns_answer_unref(q->answer);
+                q->answer_rcode = 0;
+                q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
+                q->answer_authenticated = false;
+                q->answer_errno = c->error_code;
+        }
+
         SET_FOREACH(t, c->transactions, i) {
 
                 switch (t->state) {
 
                 case DNS_TRANSACTION_SUCCESS: {
-                        /* We found a successfuly reply, merge it into the answer */
+                        /* We found a successfully reply, merge it into the answer */
                         r = dns_answer_extend(&q->answer, t->answer);
-                        if (r < 0) {
-                                dns_query_complete(q, DNS_TRANSACTION_RESOURCES);
-                                return;
-                        }
+                        if (r < 0)
+                                goto fail;
 
                         q->answer_rcode = t->answer_rcode;
+                        q->answer_errno = 0;
 
                         if (t->answer_authenticated) {
                                 has_authenticated = true;
@@ -1117,15 +860,19 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
                         continue;
 
                 default:
-                        /* Any kind of failure? Store the data away,
-                         * if there's nothing stored yet. */
-
+                        /* Any kind of failure? Store the data away, if there's nothing stored yet. */
                         if (state == DNS_TRANSACTION_SUCCESS)
                                 continue;
 
+                        /* If there's already an authenticated negative reply stored, then prefer that over any unauthenticated one */
+                        if (q->answer_authenticated && !t->answer_authenticated)
+                                continue;
+
                         q->answer = dns_answer_unref(q->answer);
                         q->answer_rcode = t->answer_rcode;
                         q->answer_dnssec_result = t->answer_dnssec_result;
+                        q->answer_authenticated = t->answer_authenticated;
+                        q->answer_errno = t->answer_errno;
 
                         state = t->state;
                         break;
@@ -1143,8 +890,16 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
         dns_search_domain_unref(q->answer_search_domain);
         q->answer_search_domain = dns_search_domain_ref(c->search_domain);
 
-        dns_query_synthesize_reply(q, &state);
+        r = dns_query_synthesize_reply(q, &state);
+        if (r < 0)
+                goto fail;
+
         dns_query_complete(q, state);
+        return;
+
+fail:
+        q->answer_errno = -r;
+        dns_query_complete(q, DNS_TRANSACTION_ERRNO);
 }
 
 void dns_query_ready(DnsQuery *q) {
@@ -1202,7 +957,7 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
 
         assert(q);
 
-        q->n_cname_redirects ++;
+        q->n_cname_redirects++;
         if (q->n_cname_redirects > CNAME_MAX)
                 return -ELOOP;
 
@@ -1230,6 +985,17 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
         if (r == 0 && k == 0) /* No actual cname happened? */
                 return -ELOOP;
 
+        if (q->answer_protocol == DNS_PROTOCOL_DNS) {
+                /* Don't permit CNAME redirects from unicast DNS to LLMNR or MulticastDNS, so that global resources
+                 * cannot invade the local namespace. The opposite way we permit: local names may redirect to global
+                 * ones. */
+
+                q->flags &= ~(SD_RESOLVED_LLMNR|SD_RESOLVED_MDNS); /* mask away the local protocols */
+        }
+
+        /* Turn off searching for the new name */
+        q->flags |= SD_RESOLVED_NO_SEARCH;
+
         dns_question_unref(q->question_idna);
         q->question_idna = nq_idna;
         nq_idna = NULL;
@@ -1240,10 +1006,8 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
 
         dns_query_free_candidates(q);
         dns_query_reset_answer(q);
-        q->state = DNS_TRANSACTION_NULL;
 
-        /* Turn off searching for the new name */
-        q->flags |= SD_RESOLVED_NO_SEARCH;
+        q->state = DNS_TRANSACTION_NULL;
 
         return 0;
 }
@@ -1281,6 +1045,9 @@ int dns_query_process_cname(DnsQuery *q) {
         if (q->flags & SD_RESOLVED_NO_CNAME)
                 return -ELOOP;
 
+        if (!q->answer_authenticated)
+                q->previous_redirect_unauthenticated = true;
+
         /* OK, let's actually follow the CNAME */
         r = dns_query_cname_redirect(q, cname);
         if (r < 0)
@@ -1368,3 +1135,9 @@ const char *dns_query_string(DnsQuery *q) {
 
         return dns_question_first_name(q->question_idna);
 }
+
+bool dns_query_fully_authenticated(DnsQuery *q) {
+        assert(q);
+
+        return q->answer_authenticated && !q->previous_redirect_unauthenticated;
+}