void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
        usec_t ts;
        int r;

        assert(t);
        assert(p);
        assert(t->scope);
        assert(t->scope->manager);

        if (t->state != DNS_TRANSACTION_PENDING)
                return;

        /* Note that this call might invalidate the query. Callers
         * should hence not attempt to access the query or transaction
         * after calling this function. */

        log_debug("Processing incoming packet on transaction %" PRIu16" (rcode=%s).",
                  t->id, dns_rcode_to_string(DNS_PACKET_RCODE(p)));

        switch (t->scope->protocol) {

        case DNS_PROTOCOL_LLMNR:
                /* For LLMNR we will not accept any packets from other interfaces */

                if (p->ifindex != dns_scope_ifindex(t->scope))
                        return;

                if (p->family != t->scope->family)
                        return;

                /* Tentative packets are not full responses but still
                 * useful for identifying uniqueness conflicts during
                 * probing. */
                if (DNS_PACKET_LLMNR_T(p)) {
                        dns_transaction_tentative(t, p);
                        return;
                }

                break;

        case DNS_PROTOCOL_MDNS:
                /* For mDNS we will not accept any packets from other interfaces */

                if (p->ifindex != dns_scope_ifindex(t->scope))
                        return;

                if (p->family != t->scope->family)
                        return;

                break;

        case DNS_PROTOCOL_DNS:
                /* Note that we do not need to verify the
                 * addresses/port numbers of incoming traffic, as we
                 * invoked connect() on our UDP socket in which case
                 * the kernel already does the needed verification for
                 * us. */
                break;

        default:
                assert_not_reached("Invalid DNS protocol.");
        }

        if (t->received != p) {
                dns_packet_unref(t->received);
                t->received = dns_packet_ref(p);
        }

        t->answer_source = DNS_TRANSACTION_NETWORK;

        if (p->ipproto == IPPROTO_TCP) {
                if (DNS_PACKET_TC(p)) {
                        /* Truncated via TCP? Somebody must be fucking with us */
                        dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
                        return;
                }

                if (DNS_PACKET_ID(p) != t->id) {
                        /* Not the reply to our query? Somebody must be fucking with us */
                        dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
                        return;
                }
        }

        assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);

        switch (t->scope->protocol) {

        case DNS_PROTOCOL_DNS:
                assert(t->server);

                if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {

                        /* Request failed, immediately try again with reduced features */

                        if (t->current_feature_level <= DNS_SERVER_FEATURE_LEVEL_UDP) {

                                /* This was already at UDP feature level? If so, it doesn't make sense to downgrade
                                 * this transaction anymore, but let's see if it might make sense to send the request
                                 * to a different DNS server instead. If not let's process the response, and accept the
                                 * rcode. Note that we don't retry on TCP, since that's a suitable way to mitigate
                                 * packet loss, but is not going to give us better rcodes should we actually have
                                 * managed to get them already at UDP level. */

                                if (t->n_picked_servers < dns_scope_get_n_dns_servers(t->scope)) {
                                        /* We tried fewer servers on this transaction than we know, let's try another one then */
                                        dns_transaction_retry(t, true);
                                        return;
                                }

                                /* Give up, accept the rcode */
                                log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p)));
                                break;
                        }

                        /* Reduce this feature level by one and try again. */
                        switch (t->current_feature_level) {
                        case DNS_SERVER_FEATURE_LEVEL_TLS_DO:
                                t->clamp_feature_level = DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN;
                                break;
                        case DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN + 1:
                                /* Skip plain TLS when TLS is not supported */
                                t->clamp_feature_level = DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN - 1;
                                break;
                        default:
                                t->clamp_feature_level = t->current_feature_level - 1;
                        }

                        log_debug("Server returned error %s, retrying transaction with reduced feature level %s.",
                                  dns_rcode_to_string(DNS_PACKET_RCODE(p)),
                                  dns_server_feature_level_to_string(t->clamp_feature_level));

                        dns_transaction_retry(t, false /* use the same server */);
                        return;
                }

                if (DNS_PACKET_RCODE(p) == DNS_RCODE_REFUSED) {
                        /* This server refused our request? If so, try again, use a different server */
                        log_debug("Server returned REFUSED, switching servers, and retrying.");
                        dns_transaction_retry(t, true /* pick a new server */);
                        return;
                }

                if (DNS_PACKET_TC(p))
                        dns_server_packet_truncated(t->server, t->current_feature_level);

                break;

        case DNS_PROTOCOL_LLMNR:
        case DNS_PROTOCOL_MDNS:
                dns_scope_packet_received(t->scope, ts - t->start_usec);
                break;

        default:
                assert_not_reached("Invalid DNS protocol.");
        }

        if (DNS_PACKET_TC(p)) {

                /* Truncated packets for mDNS are not allowed. Give up immediately. */
                if (t->scope->protocol == DNS_PROTOCOL_MDNS) {
                        dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
                        return;
                }

                log_debug("Reply truncated, retrying via TCP.");

                /* Response was truncated, let's try again with good old TCP */
                r = dns_transaction_emit_tcp(t);
                if (r == -ESRCH) {
                        /* No servers found? Damn! */
                        dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
                        return;
                }
                if (r == -EOPNOTSUPP) {
                        /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC  */
                        dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
                        return;
                }
                if (r < 0) {
                        /* On LLMNR, if we cannot connect to the host,
                         * we immediately give up */
                        if (t->scope->protocol != DNS_PROTOCOL_DNS)
                                goto fail;

                        /* On DNS, couldn't send? Try immediately again, with a new server */
                        dns_transaction_retry(t, true);
                }

                return;
        }

        /* After the superficial checks, actually parse the message. */
        r = dns_packet_extract(p);
        if (r < 0) {
                dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
                return;
        }

        if (t->server) {
                /* Report that we successfully received a valid packet with a good rcode after we initially got a bad
                 * rcode and subsequently downgraded the protocol */

                if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN) &&
                    t->clamp_feature_level != _DNS_SERVER_FEATURE_LEVEL_INVALID)
                        dns_server_packet_rcode_downgrade(t->server, t->clamp_feature_level);

                /* Report that the OPT RR was missing */
                if (!p->opt)
                        dns_server_packet_bad_opt(t->server, t->current_feature_level);

                /* Report that we successfully received a packet */
                dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, p->size);
        }

        /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */
        r = dns_transaction_maybe_restart(t);
        if (r < 0)
                goto fail;
        if (r > 0) /* Transaction got restarted... */
                return;

        if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR, DNS_PROTOCOL_MDNS)) {

                /* When dealing with protocols other than mDNS only consider responses with
                 * equivalent query section to the request. For mDNS this check doesn't make
                 * sense, because the section 6 of RFC6762 states that "Multicast DNS responses MUST NOT
                 * contain any questions in the Question Section". */
                if (t->scope->protocol != DNS_PROTOCOL_MDNS) {
                        r = dns_packet_is_reply_for(p, t->key);
                        if (r < 0)
                                goto fail;
                        if (r == 0) {
                                dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
                                return;
                        }
                }

                /* Install the answer as answer to the transaction */
                dns_answer_unref(t->answer);
                t->answer = dns_answer_ref(p->answer);
                t->answer_rcode = DNS_PACKET_RCODE(p);
                t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
                t->answer_authenticated = false;

                r = dns_transaction_fix_rcode(t);
                if (r < 0)
                        goto fail;

                /* Block GC while starting requests for additional DNSSEC RRs */
                t->block_gc++;
                r = dns_transaction_request_dnssec_keys(t);
                t->block_gc--;

                /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */
                if (!dns_transaction_gc(t))
                        return;

                /* Requesting additional keys might have resulted in
                 * this transaction to fail, since the auxiliary
                 * request failed for some reason. If so, we are not
                 * in pending state anymore, and we should exit
                 * quickly. */
                if (t->state != DNS_TRANSACTION_PENDING)
                        return;
                if (r < 0)
                        goto fail;
                if (r > 0) {
                        /* There are DNSSEC transactions pending now. Update the state accordingly. */
                        t->state = DNS_TRANSACTION_VALIDATING;
                        dns_transaction_close_connection(t);
                        dns_transaction_stop_timeout(t);
                        return;
                }
        }

        dns_transaction_process_dnssec(t);
        return;

fail:
        t->answer_errno = -r;
        dns_transaction_complete(t, DNS_TRANSACTION_ERRNO);
}