]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-transaction.c
resolved: respond to local resolver requests on 127.0.0.53:53
[thirdparty/systemd.git] / src / resolve / resolved-dns-transaction.c
index 501f13063e40d7b897689f8bea568b029a0f4c13..09f60d3e76e5c9cdf2a033a6eeb6ec90e3cbeb07 100644 (file)
@@ -1,5 +1,3 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
 /***
   This file is part of systemd.
 
@@ -54,6 +52,7 @@ static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) {
 
         while ((z = set_steal_first(t->dnssec_transactions))) {
                 set_remove(z->notify_transactions, t);
+                set_remove(z->notify_transactions_done, t);
                 dns_transaction_gc(z);
         }
 }
@@ -61,7 +60,14 @@ static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) {
 static void dns_transaction_close_connection(DnsTransaction *t) {
         assert(t);
 
-        t->stream = dns_stream_free(t->stream);
+        if (t->stream) {
+                /* Let's detach the stream from our transaction, in case something else keeps a reference to it. */
+                t->stream->complete = NULL;
+                t->stream->on_packet = NULL;
+                t->stream->transaction = NULL;
+                t->stream = dns_stream_unref(t->stream);
+        }
+
         t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source);
         t->dns_udp_fd = safe_close(t->dns_udp_fd);
 }
@@ -102,20 +108,31 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
                 set_remove(c->transactions, t);
         set_free(t->notify_query_candidates);
 
+        while ((c = set_steal_first(t->notify_query_candidates_done)))
+                set_remove(c->transactions, t);
+        set_free(t->notify_query_candidates_done);
+
         while ((i = set_steal_first(t->notify_zone_items)))
                 i->probe_transaction = NULL;
         set_free(t->notify_zone_items);
 
+        while ((i = set_steal_first(t->notify_zone_items_done)))
+                i->probe_transaction = NULL;
+        set_free(t->notify_zone_items_done);
+
         while ((z = set_steal_first(t->notify_transactions)))
                 set_remove(z->dnssec_transactions, t);
         set_free(t->notify_transactions);
 
+        while ((z = set_steal_first(t->notify_transactions_done)))
+                set_remove(z->dnssec_transactions, t);
+        set_free(t->notify_transactions_done);
+
         dns_transaction_flush_dnssec_transactions(t);
         set_free(t->dnssec_transactions);
 
         dns_answer_unref(t->validated_keys);
         dns_resource_key_unref(t->key);
-        free(t->key_string);
 
         free(t);
         return NULL;
@@ -130,8 +147,11 @@ bool dns_transaction_gc(DnsTransaction *t) {
                 return true;
 
         if (set_isempty(t->notify_query_candidates) &&
+            set_isempty(t->notify_query_candidates_done) &&
             set_isempty(t->notify_zone_items) &&
-            set_isempty(t->notify_transactions)) {
+            set_isempty(t->notify_zone_items_done) &&
+            set_isempty(t->notify_transactions) &&
+            set_isempty(t->notify_transactions_done)) {
                 dns_transaction_free(t);
                 return false;
         }
@@ -212,7 +232,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
         LIST_PREPEND(transactions_by_scope, s->transactions, t);
         t->scope = s;
 
-        s->manager->n_transactions_total ++;
+        s->manager->n_transactions_total++;
 
         if (ret)
                 *ret = t;
@@ -240,6 +260,7 @@ static void dns_transaction_shuffle_id(DnsTransaction *t) {
 
 static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
         _cleanup_free_ char *pretty = NULL;
+        char key_str[DNS_RESOURCE_KEY_STRING_MAX];
         DnsZoneItem *z;
 
         assert(t);
@@ -248,15 +269,15 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
         if (manager_our_packet(t->scope->manager, p) != 0)
                 return;
 
-        in_addr_to_string(p->family, &p->sender, &pretty);
+        (void) in_addr_to_string(p->family, &p->sender, &pretty);
 
         log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.",
                   t->id,
-                  dns_transaction_key_string(t),
+                  dns_resource_key_to_string(t->key, key_str, sizeof key_str),
                   dns_protocol_to_string(t->scope->protocol),
                   t->scope->link ? t->scope->link->name : "*",
-                  t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family),
-                  pretty);
+                  af_to_name_short(t->scope->family),
+                  strnull(pretty));
 
         /* RFC 4795, Section 4.1 says that the peer with the
          * lexicographically smaller IP address loses */
@@ -268,6 +289,7 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
         log_debug("We have the lexicographically larger IP address and thus lost in the conflict.");
 
         t->block_gc++;
+
         while ((z = set_first(t->notify_zone_items))) {
                 /* First, make sure the zone item drops the reference
                  * to us */
@@ -286,22 +308,25 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
         DnsQueryCandidate *c;
         DnsZoneItem *z;
         DnsTransaction *d;
-        Iterator i;
         const char *st;
+        char key_str[DNS_RESOURCE_KEY_STRING_MAX];
 
         assert(t);
         assert(!DNS_TRANSACTION_IS_LIVE(state));
 
-        if (state == DNS_TRANSACTION_DNSSEC_FAILED)
+        if (state == DNS_TRANSACTION_DNSSEC_FAILED) {
+                dns_resource_key_to_string(t->key, key_str, sizeof key_str);
+
                 log_struct(LOG_NOTICE,
                            LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE),
-                           LOG_MESSAGE("DNSSEC validation failed for question %s: %s", dns_transaction_key_string(t), dnssec_result_to_string(t->answer_dnssec_result)),
+                           LOG_MESSAGE("DNSSEC validation failed for question %s: %s", key_str, dnssec_result_to_string(t->answer_dnssec_result)),
                            "DNS_TRANSACTION=%" PRIu16, t->id,
-                           "DNS_QUESTION=%s", dns_transaction_key_string(t),
+                           "DNS_QUESTION=%s", key_str,
                            "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result),
                            "DNS_SERVER=%s", dns_server_string(t->server),
                            "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level),
                            NULL);
+        }
 
         /* Note that this call might invalidate the query. Callers
          * should hence not attempt to access the query or transaction
@@ -314,10 +339,10 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
 
         log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).",
                   t->id,
-                  dns_transaction_key_string(t),
+                  dns_resource_key_to_string(t->key, key_str, sizeof key_str),
                   dns_protocol_to_string(t->scope->protocol),
                   t->scope->link ? t->scope->link->name : "*",
-                  t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family),
+                  af_to_name_short(t->scope->family),
                   st,
                   t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source),
                   t->answer_authenticated ? "authenticated" : "unsigned");
@@ -331,39 +356,17 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
          * transaction isn't freed while we are still looking at it */
         t->block_gc++;
 
-        SET_FOREACH(c, t->notify_query_candidates, i)
+        SET_FOREACH_MOVE(c, t->notify_query_candidates_done, t->notify_query_candidates)
                 dns_query_candidate_notify(c);
-        SET_FOREACH(z, t->notify_zone_items, i)
-                dns_zone_item_notify(z);
+        SWAP_TWO(t->notify_query_candidates, t->notify_query_candidates_done);
 
-        if (!set_isempty(t->notify_transactions)) {
-                DnsTransaction **nt;
-                unsigned j, n = 0;
-
-                /* We need to be careful when notifying other
-                 * transactions, as that might destroy other
-                 * transactions in our list. Hence, in order to be
-                 * able to safely iterate through the list of
-                 * transactions, take a GC lock on all of them
-                 * first. Then, in a second loop, notify them, but
-                 * first unlock that specific transaction. */
-
-                nt = newa(DnsTransaction*, set_size(t->notify_transactions));
-                SET_FOREACH(d, t->notify_transactions, i) {
-                        nt[n++] = d;
-                        d->block_gc++;
-                }
-
-                assert(n == set_size(t->notify_transactions));
+        SET_FOREACH_MOVE(z, t->notify_zone_items_done, t->notify_zone_items)
+                dns_zone_item_notify(z);
+        SWAP_TWO(t->notify_zone_items, t->notify_zone_items_done);
 
-                for (j = 0; j < n; j++) {
-                        if (set_contains(t->notify_transactions, nt[j]))
-                                dns_transaction_notify(nt[j], t);
-
-                        nt[j]->block_gc--;
-                        dns_transaction_gc(nt[j]);
-                }
-        }
+        SET_FOREACH_MOVE(d, t->notify_transactions_done, t->notify_transactions)
+                dns_transaction_notify(d, t);
+        SWAP_TWO(t->notify_transactions, t->notify_transactions_done);
 
         t->block_gc--;
         dns_transaction_gc(t);
@@ -408,8 +411,12 @@ static void dns_transaction_retry(DnsTransaction *t) {
 }
 
 static int dns_transaction_maybe_restart(DnsTransaction *t) {
+        int r;
+
         assert(t);
 
+        /* Returns > 0 if the transaction was restarted, 0 if not */
+
         if (!t->server)
                 return 0;
 
@@ -424,7 +431,12 @@ static int dns_transaction_maybe_restart(DnsTransaction *t) {
 
         log_debug("Server feature level is now lower than when we began our transaction. Restarting with new ID.");
         dns_transaction_shuffle_id(t);
-        return dns_transaction_go(t);
+
+        r = dns_transaction_go(t);
+        if (r < 0)
+                return r;
+
+        return 1;
 }
 
 static int on_stream_complete(DnsStream *s, int error) {
@@ -439,7 +451,7 @@ static int on_stream_complete(DnsStream *s, int error) {
         t = s->transaction;
         p = dns_packet_ref(s->read_packet);
 
-        t->stream = dns_stream_free(t->stream);
+        dns_transaction_close_connection(t);
 
         if (ERRNO_IS_DISCONNECT(error)) {
                 usec_t usec;
@@ -524,7 +536,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
                          * the IP address, in case this is a reverse
                          * PTR lookup */
 
-                        r = dns_name_address(DNS_RESOURCE_KEY_NAME(t->key), &family, &address);
+                        r = dns_name_address(dns_resource_key_name(t->key), &family, &address);
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -551,7 +563,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
 
         r = dns_stream_write_packet(t->stream, t->sent);
         if (r < 0) {
-                t->stream = dns_stream_free(t->stream);
+                t->stream = dns_stream_unref(t->stream);
                 return r;
         }
 
@@ -561,8 +573,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
         /* The interface index is difficult to determine if we are
          * connecting to the local host, hence fill this in right away
          * instead of determining it from the socket */
-        if (t->scope->link)
-                t->stream->ifindex = t->scope->link->ifindex;
+        t->stream->ifindex = dns_scope_ifindex(t->scope);
 
         dns_transaction_reset_answer(t);
 
@@ -802,12 +813,9 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
         switch (t->scope->protocol) {
 
         case DNS_PROTOCOL_LLMNR:
-                assert(t->scope->link);
-
-                /* For LLMNR we will not accept any packets from other
-                 * interfaces */
+                /* For LLMNR we will not accept any packets from other interfaces */
 
-                if (p->ifindex != t->scope->link->ifindex)
+                if (p->ifindex != dns_scope_ifindex(t->scope))
                         return;
 
                 if (p->family != t->scope->family)
@@ -824,10 +832,9 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
                 break;
 
         case DNS_PROTOCOL_MDNS:
-                assert(t->scope->link);
-
                 /* For mDNS we will not accept any packets from other interfaces */
-                if (p->ifindex != t->scope->link->ifindex)
+
+                if (p->ifindex != dns_scope_ifindex(t->scope))
                         return;
 
                 if (p->family != t->scope->family)
@@ -1211,7 +1218,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
                         return 0;
                 }
 
-                if (dns_name_is_root(DNS_RESOURCE_KEY_NAME(t->key)) &&
+                if (dns_name_is_root(dns_resource_key_name(t->key)) &&
                     t->key->type == DNS_TYPE_DS) {
 
                         /* Hmm, this is a request for the root DS? A
@@ -1239,8 +1246,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
                                  * might be DS RRs, but we don't know
                                  * them, and the DNS server won't tell
                                  * them to us (and even if it would,
-                                 * we couldn't validate it and trust
-                                 * it). */
+                                 * we couldn't validate and trust them. */
                                 dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR);
 
                         return 0;
@@ -1251,7 +1257,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
          * for probing or verifying a zone item. */
         if (set_isempty(t->notify_zone_items)) {
 
-                r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL);
+                r = dns_zone_lookup(&t->scope->zone, t->key, dns_scope_ifindex(t->scope), &t->answer, NULL, NULL);
                 if (r < 0)
                         return r;
                 if (r > 0) {
@@ -1275,7 +1281,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
                 /* Let's then prune all outdated entries */
                 dns_cache_prune(&t->scope->cache);
 
-                r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer, &t->answer_authenticated);
+                r = dns_cache_lookup(&t->scope->cache, t->key, t->clamp_ttl, &t->answer_rcode, &t->answer, &t->answer_authenticated);
                 if (r < 0)
                         return r;
                 if (r > 0) {
@@ -1374,7 +1380,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
                 other->state = DNS_TRANSACTION_PENDING;
                 other->next_attempt_after = ts;
 
-                qdcount ++;
+                qdcount++;
 
                 if (dns_key_is_shared(other->key))
                         add_known_answers = true;
@@ -1427,21 +1433,25 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
 int dns_transaction_go(DnsTransaction *t) {
         usec_t ts;
         int r;
+        char key_str[DNS_RESOURCE_KEY_STRING_MAX];
 
         assert(t);
 
+        /* Returns > 0 if the transaction is now pending, returns 0 if could be processed immediately and has finished
+         * now. */
+
         assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
 
         r = dns_transaction_prepare(t, ts);
         if (r <= 0)
                 return r;
 
-        log_debug("Excercising transaction %" PRIu16 " for <%s> on scope %s on %s/%s.",
+        log_debug("Transaction %" PRIu16 " for <%s> scope %s on %s/%s.",
                   t->id,
-                  dns_transaction_key_string(t),
+                  dns_resource_key_to_string(t->key, key_str, sizeof key_str),
                   dns_protocol_to_string(t->scope->protocol),
                   t->scope->link ? t->scope->link->name : "*",
-                  t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family));
+                  af_to_name_short(t->scope->family));
 
         if (!t->initial_jitter_scheduled &&
             (t->scope->protocol == DNS_PROTOCOL_LLMNR ||
@@ -1496,8 +1506,8 @@ int dns_transaction_go(DnsTransaction *t) {
                 return r;
 
         if (t->scope->protocol == DNS_PROTOCOL_LLMNR &&
-            (dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "in-addr.arpa") > 0 ||
-             dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "ip6.arpa") > 0)) {
+            (dns_name_endswith(dns_resource_key_name(t->key), "in-addr.arpa") > 0 ||
+             dns_name_endswith(dns_resource_key_name(t->key), "ip6.arpa") > 0)) {
 
                 /* RFC 4795, Section 2.4. says reverse lookups shall
                  * always be made via TCP on LLMNR */
@@ -1604,11 +1614,14 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource
                 if (r < 0)
                         return r;
                 if (r > 0) {
-                        log_debug("Detected potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).",
+                        char s[DNS_RESOURCE_KEY_STRING_MAX], saux[DNS_RESOURCE_KEY_STRING_MAX];
+
+                        log_debug("Potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).",
                                   aux->id,
-                                  strna(dns_transaction_key_string(aux)),
+                                  dns_resource_key_to_string(t->key, s, sizeof s),
                                   t->id,
-                                  strna(dns_transaction_key_string(t)));
+                                  dns_resource_key_to_string(aux->key, saux, sizeof saux));
+
                         return -ELOOP;
                 }
         }
@@ -1621,6 +1634,10 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource
         if (r < 0)
                 goto gc;
 
+        r = set_ensure_allocated(&aux->notify_transactions_done, NULL);
+        if (r < 0)
+                goto gc;
+
         r = set_put(t->dnssec_transactions, aux);
         if (r < 0)
                 goto gc;
@@ -1680,7 +1697,7 @@ static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const
 
         assert(t);
 
-        /* Check whether the specified name is in the the NTA
+        /* Check whether the specified name is in the NTA
          * database, either in the global one, or the link-local
          * one. */
 
@@ -1710,7 +1727,7 @@ static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) {
 
         /* Is this key explicitly listed as a negative trust anchor?
          * If so, it's nothing we need to care about */
-        r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key));
+        r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key));
         if (r < 0)
                 return r;
         if (r > 0)
@@ -1801,7 +1818,8 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
          * - For unsigned SOA/NS we get the matching DS
          * - For unsigned CNAME/DNAME/DS we get the parent SOA RR
          * - For other unsigned RRs we get the matching SOA RR
-         * - For SOA/NS/DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR
+         * - For SOA/NS queries with no matching response RR, and no NSEC/NSEC3, the DS RR
+         * - For DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR
          * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR
          */
 
@@ -1818,7 +1836,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                         continue;
 
                 /* If this RR is in the negative trust anchor, we don't need to validate it. */
-                r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key));
+                r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key));
                 if (r < 0)
                         return r;
                 if (r > 0)
@@ -1835,7 +1853,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                          * already have the DNSKEY, and we don't have
                          * to look for more. */
                         if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) {
-                                r = dns_name_equal(rr->rrsig.signer, DNS_RESOURCE_KEY_NAME(rr->key));
+                                r = dns_name_equal(rr->rrsig.signer, dns_resource_key_name(rr->key));
                                 if (r < 0)
                                         return r;
                                 if (r > 0)
@@ -1853,7 +1871,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                          * in another transaction whose additonal RRs
                          * point back to the original transaction, and
                          * we deadlock. */
-                        r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), rr->rrsig.signer);
+                        r = dns_name_endswith(dns_resource_key_name(t->key), rr->rrsig.signer);
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -1863,7 +1881,8 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                         if (!dnskey)
                                 return -ENOMEM;
 
-                        log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.key_tag);
+                        log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").",
+                                  t->id, dns_resource_key_name(rr->key), rr->rrsig.key_tag);
                         r = dns_transaction_request_dnssec_rr(t, dnskey);
                         if (r < 0)
                                 return r;
@@ -1881,17 +1900,18 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                          * up in request loops, and want to keep
                          * additional traffic down. */
 
-                        r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), DNS_RESOURCE_KEY_NAME(rr->key));
+                        r = dns_name_endswith(dns_resource_key_name(t->key), dns_resource_key_name(rr->key));
                         if (r < 0)
                                 return r;
                         if (r == 0)
                                 continue;
 
-                        ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key));
+                        ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key));
                         if (!ds)
                                 return -ENOMEM;
 
-                        log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr, false));
+                        log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").",
+                                  t->id, dns_resource_key_name(rr->key), dnssec_keytag(rr, false));
                         r = dns_transaction_request_dnssec_rr(t, ds);
                         if (r < 0)
                                 return r;
@@ -1922,11 +1942,12 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                         if (r > 0)
                                 continue;
 
-                        ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key));
+                        ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key));
                         if (!ds)
                                 return -ENOMEM;
 
-                        log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key));
+                        log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).",
+                                  t->id, dns_resource_key_name(rr->key));
                         r = dns_transaction_request_dnssec_rr(t, ds);
                         if (r < 0)
                                 return r;
@@ -1968,7 +1989,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                         if (r > 0)
                                 continue;
 
-                        name = DNS_RESOURCE_KEY_NAME(rr->key);
+                        name = dns_resource_key_name(rr->key);
                         r = dns_name_parent(&name);
                         if (r < 0)
                                 return r;
@@ -1979,7 +2000,8 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                         if (!soa)
                                 return -ENOMEM;
 
-                        log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key));
+                        log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).",
+                                  t->id, dns_resource_key_name(rr->key));
                         r = dns_transaction_request_dnssec_rr(t, soa);
                         if (r < 0)
                                 return r;
@@ -2009,11 +2031,12 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                         if (r > 0)
                                 continue;
 
-                        soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, DNS_RESOURCE_KEY_NAME(rr->key));
+                        soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, dns_resource_key_name(rr->key));
                         if (!soa)
                                 return -ENOMEM;
 
-                        log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dns_resource_record_to_string(rr));
+                        log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).",
+                                  t->id, dns_resource_key_name(rr->key), dns_resource_record_to_string(rr));
                         r = dns_transaction_request_dnssec_rr(t, soa);
                         if (r < 0)
                                 return r;
@@ -2030,30 +2053,42 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
                 return r;
         if (r > 0) {
                 const char *name;
+                uint16_t type = 0;
 
-                name = DNS_RESOURCE_KEY_NAME(t->key);
+                name = dns_resource_key_name(t->key);
 
-                /* If this was a SOA or NS request, then this
-                 * indicates that we are not at a zone apex, hence ask
-                 * the parent name instead. If this was a DS request,
-                 * then it's signed when the parent zone is signed,
-                 * hence ask the parent in that case, too. */
+                /* If this was a SOA or NS request, then check if there's a DS RR for the same domain. Note that this
+                 * could also be used as indication that we are not at a zone apex, but in real world setups there are
+                 * too many broken DNS servers (Hello, incapdns.net!) where non-terminal zones return NXDOMAIN even
+                 * though they have further children. If this was a DS request, then it's signed when the parent zone
+                 * is signed, hence ask the parent SOA in that case. If this was any other RR then ask for the SOA RR,
+                 * to see if that is signed. */
 
-                if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) {
+                if (t->key->type == DNS_TYPE_DS) {
                         r = dns_name_parent(&name);
-                        if (r < 0)
-                                return r;
-                        if (r > 0)
-                                log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key));
-                        else
+                        if (r > 0) {
+                                type = DNS_TYPE_SOA;
+                                log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty DS response).",
+                                          t->id, dns_resource_key_name(t->key));
+                        else
                                 name = NULL;
-                } else
-                        log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key));
+
+                } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS)) {
+
+                        type = DNS_TYPE_DS;
+                        log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS response).",
+                                  t->id, dns_resource_key_name(t->key));
+
+                } else {
+                        type = DNS_TYPE_SOA;
+                        log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).",
+                                  t->id, dns_resource_key_name(t->key));
+                }
 
                 if (name) {
                         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL;
 
-                        soa = dns_resource_key_new(t->key->class, DNS_TYPE_SOA, name);
+                        soa = dns_resource_key_new(t->key->class, type, name);
                         if (!soa)
                                 return -ENOMEM;
 
@@ -2120,7 +2155,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
         if (dns_type_is_pseudo(rr->key->type))
                 return -EINVAL;
 
-        r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key));
+        r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key));
         if (r < 0)
                 return r;
         if (r > 0)
@@ -2146,7 +2181,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
                         if (dt->key->type != DNS_TYPE_DS)
                                 continue;
 
-                        r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key));
+                        r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key));
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -2189,7 +2224,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
                                 continue;
 
                         if (!parent) {
-                                parent = DNS_RESOURCE_KEY_NAME(rr->key);
+                                parent = dns_resource_key_name(rr->key);
                                 r = dns_name_parent(&parent);
                                 if (r < 0)
                                         return r;
@@ -2203,7 +2238,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
                                 }
                         }
 
-                        r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), parent);
+                        r = dns_name_equal(dns_resource_key_name(dt->key), parent);
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -2228,7 +2263,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
                         if (dt->key->type != DNS_TYPE_SOA)
                                 continue;
 
-                        r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key));
+                        r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key));
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -2275,7 +2310,7 @@ static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKe
         if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE)
                 return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */
 
-        tld = DNS_RESOURCE_KEY_NAME(key);
+        tld = dns_resource_key_name(key);
         r = dns_name_parent(&tld);
         if (r < 0)
                 return r;
@@ -2290,7 +2325,7 @@ static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKe
                 if (dt->key->class != key->class)
                         continue;
 
-                r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), tld);
+                r = dns_name_equal(dns_resource_key_name(dt->key), tld);
                 if (r < 0)
                         return r;
                 if (r == 0)
@@ -2307,8 +2342,10 @@ static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKe
 }
 
 static int dns_transaction_requires_nsec(DnsTransaction *t) {
+        char key_str[DNS_RESOURCE_KEY_STRING_MAX];
         DnsTransaction *dt;
         const char *name;
+        uint16_t type = 0;
         Iterator i;
         int r;
 
@@ -2323,7 +2360,7 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) {
         if (dns_type_is_pseudo(t->key->type))
                 return -EINVAL;
 
-        r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key));
+        r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key));
         if (r < 0)
                 return r;
         if (r > 0)
@@ -2337,28 +2374,32 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) {
                  * exist, and we are in downgrade mode, hence ignore
                  * that fact that we didn't get any NSEC RRs.*/
 
-                log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", dns_transaction_key_string(t));
+                log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.",
+                         dns_resource_key_to_string(t->key, key_str, sizeof key_str));
                 return false;
         }
 
-        name = DNS_RESOURCE_KEY_NAME(t->key);
+        name = dns_resource_key_name(t->key);
 
-        if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) {
+        if (t->key->type == DNS_TYPE_DS) {
 
-                /* We got a negative reply for this SOA/NS lookup? If
-                 * so, then we are not at a zone apex, and thus should
-                 * look at the result of the parent SOA lookup.
-                 *
-                 * We got a negative reply for this DS lookup? DS RRs
-                 * are signed when their parent zone is signed, hence
-                 * also check the parent SOA in this case. */
+                /* We got a negative reply for this DS lookup? DS RRs are signed when their parent zone is signed,
+                 * hence check the parent SOA in this case. */
 
                 r = dns_name_parent(&name);
                 if (r < 0)
                         return r;
                 if (r == 0)
                         return true;
-        }
+
+                type = DNS_TYPE_SOA;
+
+        } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS))
+                /* We got a negative reply for this SOA/NS lookup? If so, check if there's a DS RR for this */
+                type = DNS_TYPE_DS;
+        else
+                /* For all other negative replies, check for the SOA lookup */
+                type = DNS_TYPE_SOA;
 
         /* For all other RRs we check the SOA on the same level to see
          * if it's signed. */
@@ -2367,10 +2408,10 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) {
 
                 if (dt->key->class != t->key->class)
                         continue;
-                if (dt->key->type != DNS_TYPE_SOA)
+                if (dt->key->type != type)
                         continue;
 
-                r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), name);
+                r = dns_name_equal(dns_resource_key_name(dt->key), name);
                 if (r < 0)
                         return r;
                 if (r == 0)
@@ -2392,7 +2433,7 @@ static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRe
          * the specified RRset is authenticated (i.e. has a matching
          * DS RR). */
 
-        r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key));
+        r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key));
         if (r < 0)
                 return r;
         if (r > 0)
@@ -2415,7 +2456,7 @@ static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRe
 
                         if (dt->key->type == DNS_TYPE_DNSKEY) {
 
-                                r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer);
+                                r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer);
                                 if (r < 0)
                                         return r;
                                 if (r == 0)
@@ -2432,7 +2473,7 @@ static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRe
 
                         } else if (dt->key->type == DNS_TYPE_DS) {
 
-                                r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer);
+                                r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer);
                                 if (r < 0)
                                         return r;
                                 if (r == 0)
@@ -2462,7 +2503,7 @@ static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr
          * not to be signed, there's a problem with the DNS server */
 
         return rr->key->class == DNS_CLASS_IN &&
-                dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key));
+                dns_name_is_root(dns_resource_key_name(rr->key));
 }
 
 static int dns_transaction_check_revoked_trust_anchors(DnsTransaction *t) {
@@ -2543,343 +2584,347 @@ static int dns_transaction_copy_validated(DnsTransaction *t) {
         return 0;
 }
 
-int dns_transaction_validate_dnssec(DnsTransaction *t) {
-        _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
-        enum {
-                PHASE_DNSKEY,   /* Phase #1, only validate DNSKEYs */
-                PHASE_NSEC,     /* Phase #2, only validate NSEC+NSEC3 */
-                PHASE_ALL,      /* Phase #3, validate everything else */
-        } phase;
+typedef enum {
+        DNSSEC_PHASE_DNSKEY,   /* Phase #1, only validate DNSKEYs */
+        DNSSEC_PHASE_NSEC,     /* Phase #2, only validate NSEC+NSEC3 */
+        DNSSEC_PHASE_ALL,      /* Phase #3, validate everything else */
+} Phase;
+
+static int dnssec_validate_records(
+                DnsTransaction *t,
+                Phase phase,
+                bool *have_nsec,
+                DnsAnswer **validated) {
+
         DnsResourceRecord *rr;
-        DnsAnswerFlags flags;
         int r;
 
-        assert(t);
+        /* Returns negative on error, 0 if validation failed, 1 to restart validation, 2 when finished. */
 
-        /* We have now collected all DS and DNSKEY RRs in
-         * t->validated_keys, let's see which RRs we can now
-         * authenticate with that. */
+        DNS_ANSWER_FOREACH(rr, t->answer) {
+                DnsResourceRecord *rrsig = NULL;
+                DnssecResult result;
 
-        if (t->scope->dnssec_mode == DNSSEC_NO)
-                return 0;
+                switch (rr->key->type) {
+                case DNS_TYPE_RRSIG:
+                        continue;
 
-        /* Already validated */
-        if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID)
-                return 0;
+                case DNS_TYPE_DNSKEY:
+                        /* We validate DNSKEYs only in the DNSKEY and ALL phases */
+                        if (phase == DNSSEC_PHASE_NSEC)
+                                continue;
+                        break;
 
-        /* Our own stuff needs no validation */
-        if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) {
-                t->answer_dnssec_result = DNSSEC_VALIDATED;
-                t->answer_authenticated = true;
-                return 0;
-        }
+                case DNS_TYPE_NSEC:
+                case DNS_TYPE_NSEC3:
+                        *have_nsec = true;
 
-        /* Cached stuff is not affected by validation. */
-        if (t->answer_source != DNS_TRANSACTION_NETWORK)
-                return 0;
+                        /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */
+                        if (phase == DNSSEC_PHASE_DNSKEY)
+                                continue;
+                        break;
 
-        if (!dns_transaction_dnssec_supported_full(t)) {
-                /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */
-                t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
-                log_debug("Not validating response for %" PRIu16 ", server lacks DNSSEC support.", t->id);
-                return 0;
-        }
+                default:
+                        /* We validate all other RRs only in the ALL phases */
+                        if (phase != DNSSEC_PHASE_ALL)
+                                continue;
+                }
 
-        log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t));
+                r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig);
+                if (r < 0)
+                        return r;
 
-        /* First, see if this response contains any revoked trust
-         * anchors we care about */
-        r = dns_transaction_check_revoked_trust_anchors(t);
-        if (r < 0)
-                return r;
+                log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result));
 
-        /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */
-        r = dns_transaction_copy_validated(t);
-        if (r < 0)
-                return r;
+                if (result == DNSSEC_VALIDATED) {
 
-        /* Second, see if there are DNSKEYs we already know a
-         * validated DS for. */
-        r = dns_transaction_validate_dnskey_by_ds(t);
-        if (r < 0)
-                return r;
+                        if (rr->key->type == DNS_TYPE_DNSKEY) {
+                                /* If we just validated a DNSKEY RRset, then let's add these keys to
+                                 * the set of validated keys for this transaction. */
 
-        /* Fourth, remove all DNSKEY and DS RRs again that our trust
-         * anchor says are revoked. After all we might have marked
-         * some keys revoked above, but they might still be lingering
-         * in our validated_keys list. */
-        r = dns_transaction_invalidate_revoked_keys(t);
-        if (r < 0)
-                return r;
+                                r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED);
+                                if (r < 0)
+                                        return r;
 
-        phase = PHASE_DNSKEY;
-        for (;;) {
-                bool changed = false, have_nsec = false;
+                                /* Some of the DNSKEYs we just added might already have been revoked,
+                                 * remove them again in that case. */
+                                r = dns_transaction_invalidate_revoked_keys(t);
+                                if (r < 0)
+                                        return r;
+                        }
 
-                DNS_ANSWER_FOREACH(rr, t->answer) {
-                        DnsResourceRecord *rrsig = NULL;
-                        DnssecResult result;
+                        /* Add the validated RRset to the new list of validated
+                         * RRsets, and remove it from the unvalidated RRsets.
+                         * We mark the RRset as authenticated and cacheable. */
+                        r = dns_answer_move_by_key(validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE);
+                        if (r < 0)
+                                return r;
 
-                        switch (rr->key->type) {
+                        manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key);
 
-                        case DNS_TYPE_RRSIG:
-                                continue;
+                        /* Exit the loop, we dropped something from the answer, start from the beginning */
+                        return 1;
+                }
 
-                        case DNS_TYPE_DNSKEY:
-                                /* We validate DNSKEYs only in the DNSKEY and ALL phases */
-                                if (phase == PHASE_NSEC)
-                                        continue;
-                                break;
+                /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as
+                 * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet,
+                 * we cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */
+                if (phase != DNSSEC_PHASE_ALL)
+                        continue;
 
-                        case DNS_TYPE_NSEC:
-                        case DNS_TYPE_NSEC3:
-                                have_nsec = true;
+                if (result == DNSSEC_VALIDATED_WILDCARD) {
+                        bool authenticated = false;
+                        const char *source;
 
-                                /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */
-                                if (phase == PHASE_DNSKEY)
-                                        continue;
+                        /* This RRset validated, but as a wildcard. This means we need
+                         * to prove via NSEC/NSEC3 that no matching non-wildcard RR exists.*/
 
-                                break;
+                        /* First step, determine the source of synthesis */
+                        r = dns_resource_record_source(rrsig, &source);
+                        if (r < 0)
+                                return r;
 
-                        default:
-                                /* We validate all other RRs only in the ALL phases */
-                                if (phase != PHASE_ALL)
-                                        continue;
+                        r = dnssec_test_positive_wildcard(*validated,
+                                                          dns_resource_key_name(rr->key),
+                                                          source,
+                                                          rrsig->rrsig.signer,
+                                                          &authenticated);
 
-                                break;
+                        /* Unless the NSEC proof showed that the key really doesn't exist something is off. */
+                        if (r == 0)
+                                result = DNSSEC_INVALID;
+                        else {
+                                r = dns_answer_move_by_key(validated, &t->answer, rr->key,
+                                                           authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0);
+                                if (r < 0)
+                                        return r;
+
+                                manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, rr->key);
+
+                                /* Exit the loop, we dropped something from the answer, start from the beginning */
+                                return 1;
                         }
+                }
 
-                        r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig);
+                if (result == DNSSEC_NO_SIGNATURE) {
+                        r = dns_transaction_requires_rrsig(t, rr);
                         if (r < 0)
                                 return r;
+                        if (r == 0) {
+                                /* Data does not require signing. In that case, just copy it over,
+                                 * but remember that this is by no means authenticated.*/
+                                r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0);
+                                if (r < 0)
+                                        return r;
 
-                        log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result));
+                                manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key);
+                                return 1;
+                        }
 
-                        if (result == DNSSEC_VALIDATED) {
+                        r = dns_transaction_known_signed(t, rr);
+                        if (r < 0)
+                                return r;
+                        if (r > 0) {
+                                /* This is an RR we know has to be signed. If it isn't this means
+                                 * the server is not attaching RRSIGs, hence complain. */
 
-                                if (rr->key->type == DNS_TYPE_DNSKEY) {
-                                        /* If we just validated a
-                                         * DNSKEY RRset, then let's
-                                         * add these keys to the set
-                                         * of validated keys for this
-                                         * transaction. */
+                                dns_server_packet_rrsig_missing(t->server, t->current_feature_level);
 
-                                        r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED);
-                                        if (r < 0)
-                                                return r;
+                                if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) {
 
-                                        /* some of the DNSKEYs we just
-                                         * added might already have
-                                         * been revoked, remove them
-                                         * again in that case. */
-                                        r = dns_transaction_invalidate_revoked_keys(t);
+                                        /* Downgrading is OK? If so, just consider the information unsigned */
+
+                                        r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0);
                                         if (r < 0)
                                                 return r;
-                                }
 
-                                /* Add the validated RRset to the new
-                                 * list of validated RRsets, and
-                                 * remove it from the unvalidated
-                                 * RRsets. We mark the RRset as
-                                 * authenticated and cacheable. */
-                                r = dns_answer_move_by_key(&validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE);
-                                if (r < 0)
-                                        return r;
-
-                                manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key);
+                                        manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key);
+                                        return 1;
+                                }
 
-                                /* Exit the loop, we dropped something from the answer, start from the beginning */
-                                changed = true;
-                                break;
+                                /* Otherwise, fail */
+                                t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
+                                return 0;
                         }
 
-                        /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as
-                         * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet, we
-                         * cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */
-                        if (phase != PHASE_ALL)
-                                continue;
+                        r = dns_transaction_in_private_tld(t, rr->key);
+                        if (r < 0)
+                                return r;
+                        if (r > 0) {
+                                char s[DNS_RESOURCE_KEY_STRING_MAX];
 
-                        if (result == DNSSEC_VALIDATED_WILDCARD) {
-                                bool authenticated = false;
-                                const char *source;
+                                /* The data is from a TLD that is proven not to exist, and we are in downgrade
+                                 * mode, hence ignore the fact that this was not signed. */
 
-                                /* This RRset validated, but as a wildcard. This means we need to prove via NSEC/NSEC3
-                                 * that no matching non-wildcard RR exists.*/
+                                log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.",
+                                         dns_resource_key_to_string(rr->key, s, sizeof s));
 
-                                /* First step, determine the source of synthesis */
-                                r = dns_resource_record_source(rrsig, &source);
+                                r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0);
                                 if (r < 0)
                                         return r;
 
-                                r = dnssec_test_positive_wildcard(
-                                                validated,
-                                                DNS_RESOURCE_KEY_NAME(rr->key),
-                                                source,
-                                                rrsig->rrsig.signer,
-                                                &authenticated);
-
-                                /* Unless the NSEC proof showed that the key really doesn't exist something is off. */
-                                if (r == 0)
-                                        result = DNSSEC_INVALID;
-                                else {
-                                        r = dns_answer_move_by_key(&validated, &t->answer, rr->key, authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0);
-                                        if (r < 0)
-                                                return r;
+                                manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key);
+                                return 1;
+                        }
+                }
 
-                                        manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, rr->key);
+                if (IN_SET(result,
+                           DNSSEC_MISSING_KEY,
+                           DNSSEC_SIGNATURE_EXPIRED,
+                           DNSSEC_UNSUPPORTED_ALGORITHM)) {
 
-                                        /* Exit the loop, we dropped something from the answer, start from the beginning */
-                                        changed = true;
-                                        break;
-                                }
-                        }
+                        r = dns_transaction_dnskey_authenticated(t, rr);
+                        if (r < 0 && r != -ENXIO)
+                                return r;
+                        if (r == 0) {
+                                /* The DNSKEY transaction was not authenticated, this means there's
+                                 * no DS for this, which means it's OK if no keys are found for this signature. */
 
-                        if (result == DNSSEC_NO_SIGNATURE) {
-                                r = dns_transaction_requires_rrsig(t, rr);
+                                r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0);
                                 if (r < 0)
                                         return r;
-                                if (r == 0) {
-                                        /* Data does not require signing. In that case, just copy it over,
-                                         * but remember that this is by no means authenticated.*/
-                                        r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
-                                        if (r < 0)
-                                                return r;
 
-                                        manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key);
-                                        changed = true;
-                                        break;
-                                }
+                                manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key);
+                                return 1;
+                        }
+                }
 
-                                r = dns_transaction_known_signed(t, rr);
+                r = dns_transaction_is_primary_response(t, rr);
+                if (r < 0)
+                        return r;
+                if (r > 0) {
+                        /* Look for a matching DNAME for this CNAME */
+                        r = dns_answer_has_dname_for_cname(t->answer, rr);
+                        if (r < 0)
+                                return r;
+                        if (r == 0) {
+                                /* Also look among the stuff we already validated */
+                                r = dns_answer_has_dname_for_cname(*validated, rr);
                                 if (r < 0)
                                         return r;
-                                if (r > 0) {
-                                        /* This is an RR we know has to be signed. If it isn't this means
-                                         * the server is not attaching RRSIGs, hence complain. */
-
-                                        dns_server_packet_rrsig_missing(t->server, t->current_feature_level);
+                        }
 
-                                        if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) {
+                        if (r == 0) {
+                                if (IN_SET(result,
+                                           DNSSEC_INVALID,
+                                           DNSSEC_SIGNATURE_EXPIRED,
+                                           DNSSEC_NO_SIGNATURE))
+                                        manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key);
+                                else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */
+                                        manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key);
+
+                                /* This is a primary response to our question, and it failed validation.
+                                 * That's fatal. */
+                                t->answer_dnssec_result = result;
+                                return 0;
+                        }
 
-                                                /* Downgrading is OK? If so, just consider the information unsigned */
+                        /* This is a primary response, but we do have a DNAME RR
+                         * in the RR that can replay this CNAME, hence rely on
+                         * that, and we can remove the CNAME in favour of it. */
+                }
 
-                                                r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
-                                                if (r < 0)
-                                                        return r;
+                /* This is just some auxiliary data. Just remove the RRset and continue. */
+                r = dns_answer_remove_by_key(&t->answer, rr->key);
+                if (r < 0)
+                        return r;
 
-                                                manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key);
-                                                changed = true;
-                                                break;
-                                        }
+                /* We dropped something from the answer, start from the beginning. */
+                return 1;
+        }
 
-                                        /* Otherwise, fail */
-                                        t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
-                                        return 0;
-                                }
+        return 2; /* Finito. */
+}
 
-                                r = dns_transaction_in_private_tld(t, rr->key);
-                                if (r < 0)
-                                        return r;
-                                if (r > 0) {
-                                        _cleanup_free_ char *s = NULL;
+int dns_transaction_validate_dnssec(DnsTransaction *t) {
+        _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
+        Phase phase;
+        DnsAnswerFlags flags;
+        int r;
+        char key_str[DNS_RESOURCE_KEY_STRING_MAX];
 
-                                        /* The data is from a TLD that is proven not to exist, and we are in downgrade
-                                         * mode, hence ignore the fact that this was not signed. */
+        assert(t);
 
-                                        (void) dns_resource_key_to_string(rr->key, &s);
-                                        log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", strna(s ? strstrip(s) : NULL));
+        /* We have now collected all DS and DNSKEY RRs in
+         * t->validated_keys, let's see which RRs we can now
+         * authenticate with that. */
 
-                                        r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
-                                        if (r < 0)
-                                                return r;
+        if (t->scope->dnssec_mode == DNSSEC_NO)
+                return 0;
 
-                                        manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key);
-                                        changed = true;
-                                        break;
-                                }
-                        }
+        /* Already validated */
+        if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID)
+                return 0;
 
-                        if (IN_SET(result,
-                                   DNSSEC_MISSING_KEY,
-                                   DNSSEC_SIGNATURE_EXPIRED,
-                                   DNSSEC_UNSUPPORTED_ALGORITHM)) {
+        /* Our own stuff needs no validation */
+        if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) {
+                t->answer_dnssec_result = DNSSEC_VALIDATED;
+                t->answer_authenticated = true;
+                return 0;
+        }
 
-                                r = dns_transaction_dnskey_authenticated(t, rr);
-                                if (r < 0 && r != -ENXIO)
-                                        return r;
-                                if (r == 0) {
-                                        /* The DNSKEY transaction was not authenticated, this means there's
-                                         * no DS for this, which means it's OK if no keys are found for this signature. */
+        /* Cached stuff is not affected by validation. */
+        if (t->answer_source != DNS_TRANSACTION_NETWORK)
+                return 0;
 
-                                        r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
-                                        if (r < 0)
-                                                return r;
+        if (!dns_transaction_dnssec_supported_full(t)) {
+                /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */
+                t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
+                log_debug("Not validating response for %" PRIu16 ", server lacks DNSSEC support.", t->id);
+                return 0;
+        }
 
-                                        manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key);
-                                        changed = true;
-                                        break;
-                                }
-                        }
+        log_debug("Validating response from transaction %" PRIu16 " (%s).",
+                  t->id,
+                  dns_resource_key_to_string(t->key, key_str, sizeof key_str));
 
-                        r = dns_transaction_is_primary_response(t, rr);
-                        if (r < 0)
-                                return r;
-                        if (r > 0) {
+        /* First, see if this response contains any revoked trust
+         * anchors we care about */
+        r = dns_transaction_check_revoked_trust_anchors(t);
+        if (r < 0)
+                return r;
 
-                                /* Look for a matching DNAME for this CNAME */
-                                r = dns_answer_has_dname_for_cname(t->answer, rr);
-                                if (r < 0)
-                                        return r;
-                                if (r == 0) {
-                                        /* Also look among the stuff we already validated */
-                                        r = dns_answer_has_dname_for_cname(validated, rr);
-                                        if (r < 0)
-                                                return r;
-                                }
+        /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */
+        r = dns_transaction_copy_validated(t);
+        if (r < 0)
+                return r;
 
-                                if (r == 0) {
-                                        if (IN_SET(result,
-                                                   DNSSEC_INVALID,
-                                                   DNSSEC_SIGNATURE_EXPIRED,
-                                                   DNSSEC_NO_SIGNATURE))
-                                                manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key);
-                                        else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */
-                                                manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key);
-
-                                        /* This is a primary response to our question, and it failed validation. That's
-                                         * fatal. */
-                                        t->answer_dnssec_result = result;
-                                        return 0;
-                                }
+        /* Second, see if there are DNSKEYs we already know a
+         * validated DS for. */
+        r = dns_transaction_validate_dnskey_by_ds(t);
+        if (r < 0)
+                return r;
 
-                                /* This is a primary response, but we do have a DNAME RR in the RR that can replay this
-                                 * CNAME, hence rely on that, and we can remove the CNAME in favour of it. */
-                        }
+        /* Fourth, remove all DNSKEY and DS RRs again that our trust
+         * anchor says are revoked. After all we might have marked
+         * some keys revoked above, but they might still be lingering
+         * in our validated_keys list. */
+        r = dns_transaction_invalidate_revoked_keys(t);
+        if (r < 0)
+                return r;
 
-                        /* This is just some auxiliary data. Just remove the RRset and continue. */
-                        r = dns_answer_remove_by_key(&t->answer, rr->key);
-                        if (r < 0)
-                                return r;
+        phase = DNSSEC_PHASE_DNSKEY;
+        for (;;) {
+                bool have_nsec = false;
 
-                        /* Exit the loop, we dropped something from the answer, start from the beginning */
-                        changed = true;
-                        break;
-                }
+                r = dnssec_validate_records(t, phase, &have_nsec, &validated);
+                if (r <= 0)
+                        return r;
 
-                /* Restart the inner loop as long as we managed to achieve something */
-                if (changed)
+                /* Try again as long as we managed to achieve something */
+                if (r == 1)
                         continue;
 
-                if (phase == PHASE_DNSKEY && have_nsec) {
+                if (phase == DNSSEC_PHASE_DNSKEY && have_nsec) {
                         /* OK, we processed all DNSKEYs, and there are NSEC/NSEC3 RRs, look at those now. */
-                        phase = PHASE_NSEC;
+                        phase = DNSSEC_PHASE_NSEC;
                         continue;
                 }
 
-                if (phase != PHASE_ALL) {
-                        /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now. Note that in this
-                         * third phase we start to remove RRs we couldn't validate. */
-                        phase = PHASE_ALL;
+                if (phase != DNSSEC_PHASE_ALL) {
+                        /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now.
+                         * Note that in this third phase we start to remove RRs we couldn't validate. */
+                        phase = DNSSEC_PHASE_ALL;
                         continue;
                 }
 
@@ -2923,7 +2968,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
 
                 case DNSSEC_NSEC_NXDOMAIN:
                         /* NSEC proves the domain doesn't exist. Very good. */
-                        log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t));
+                        log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str);
                         t->answer_dnssec_result = DNSSEC_VALIDATED;
                         t->answer_rcode = DNS_RCODE_NXDOMAIN;
                         t->answer_authenticated = authenticated;
@@ -2933,7 +2978,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
 
                 case DNSSEC_NSEC_NODATA:
                         /* NSEC proves that there's no data here, very good. */
-                        log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t));
+                        log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str);
                         t->answer_dnssec_result = DNSSEC_VALIDATED;
                         t->answer_rcode = DNS_RCODE_SUCCESS;
                         t->answer_authenticated = authenticated;
@@ -2943,7 +2988,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
 
                 case DNSSEC_NSEC_OPTOUT:
                         /* NSEC3 says the data might not be signed */
-                        log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t));
+                        log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str);
                         t->answer_dnssec_result = DNSSEC_UNSIGNED;
                         t->answer_authenticated = false;
 
@@ -2988,17 +3033,6 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
         return 1;
 }
 
-const char *dns_transaction_key_string(DnsTransaction *t) {
-        assert(t);
-
-        if (!t->key_string) {
-                if (dns_resource_key_to_string(t->key, &t->key_string) < 0)
-                        return "n/a";
-        }
-
-        return strstrip(t->key_string);
-}
-
 static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = {
         [DNS_TRANSACTION_NULL] = "null",
         [DNS_TRANSACTION_PENDING] = "pending",