]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: add support for answering DNSSEC questions on the stub
authorLennart Poettering <lennart@poettering.net>
Thu, 5 Nov 2020 10:01:52 +0000 (11:01 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 9 Feb 2021 16:58:25 +0000 (17:58 +0100)
This substantially beefs up the local DNS stub feature set in order to
allow local clients to do DNSSEC validation through the stub.

Previously we'd return NOTIMP if we'd get a DO or DO+CD lookup. With
this change we'll instead:

1. If we get DO+CD requests (i.e. DNSSEC with no local checking) we'll
   proxy DNS queries and response mostly unmodified to/from upstream DNS
   servers if possible (this is called "bypass" mode).  We will patch in
   new request IDs, (and patch them back out on reply), so that we can
   sanely keep track of things.  We'll also maintain a minimal local
   cache for such lookups, always keeping the whole DNS packets in it
   (if we reply from cache we'll patch the TTLs of all included RRs).

2. If we get DO requests without CD (i.e. DNSSEC with local checking)
   we'll resolve and validate locally. In this mode we will not proxy
   packets, but generate our own. We will however cache the combination
   of answer RRs (along with their packet section assignments) we got
   back in the cache, and use this information to generate reply packets
   from the DNS stub.

In both cases: if we determine a lookup is to be answered from LLMNR or
mDNS we'll always revert to non-DNSSEC, non-proxy operation as before.
Answers will lack the DO bit then, since the data cannot be validated
via DNSSEC by the clients.

To make this logic more debuggable, this also adds query flags for
turning off RR sources. i.e. cache/network/zone/trust anchor/local
synthesis may now be disabled individually for each lookup.

The cache is substantially updated to make all this work: in addition to
caching simple RRs for lookup RR keys, we'll now cache the whole packets
and the whole combination of RRs, so that we can answer DO and DO+CD
replies sensibly according to the rules described above. This sounds
wasteful, but given that the
DnsResourceRecord/DnsResourceKey/DnsAnswer/DnsPacket
objects are all ref-counted and we try to merge references the actual
additional memory used should be limited (but this might be something to
optimize further later on).

To implement classic RR key lookups and new-style packet proxy lookups
(i.e. the ones necessary for DO+CD packet proxying, as described above)
DnsTransaction and DnsQuery objects now always maintain either a
DnsResourceKey/DnsQuestion as lookup key or a DnsPacket for "bypass"
mode.

Fixes: #4621 #17218
16 files changed:
src/libsystemd/sd-bus/bus-common-errors.c
src/libsystemd/sd-bus/bus-common-errors.h
src/resolve/resolved-bus.c
src/resolve/resolved-def.h
src/resolve/resolved-dns-cache.c
src/resolve/resolved-dns-cache.h
src/resolve/resolved-dns-query.c
src/resolve/resolved-dns-query.h
src/resolve/resolved-dns-scope.c
src/resolve/resolved-dns-scope.h
src/resolve/resolved-dns-stub.c
src/resolve/resolved-dns-transaction.c
src/resolve/resolved-dns-transaction.h
src/resolve/resolved-dns-zone.c
src/resolve/resolved-mdns.c
src/resolve/resolved-varlink.c

index 4966db07b35d5dcf015845fdbbc3bf6dae070e27..cb0708d0987043fc7e2ddf67a70f3633336af7b8 100644 (file)
@@ -75,6 +75,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK,                 ENXIO),
         SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY,                    EBUSY),
         SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN,                 ENETDOWN),
+        SD_BUS_ERROR_MAP(BUS_ERROR_NO_SOURCE,                    ESRCH),
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DNSSD_SERVICE,        ENOENT),
         SD_BUS_ERROR_MAP(BUS_ERROR_DNSSD_SERVICE_EXISTS,         EEXIST),
 
index 56eb5551ec6fef894146e54cf36d7f92de80630a..28332836b5196e39d831575678ab06c518456096 100644 (file)
@@ -73,6 +73,7 @@
 #define BUS_ERROR_NO_SUCH_LINK                 "org.freedesktop.resolve1.NoSuchLink"
 #define BUS_ERROR_LINK_BUSY                    "org.freedesktop.resolve1.LinkBusy"
 #define BUS_ERROR_NETWORK_DOWN                 "org.freedesktop.resolve1.NetworkDown"
+#define BUS_ERROR_NO_SOURCE                    "org.freedesktop.resolve1.NoSource"
 #define BUS_ERROR_NO_SUCH_DNSSD_SERVICE        "org.freedesktop.resolve1.NoSuchDnssdService"
 #define BUS_ERROR_DNSSD_SERVICE_EXISTS         "org.freedesktop.resolve1.DnssdServiceExists"
 #define _BUS_ERROR_DNS                         "org.freedesktop.resolve1.DnsError."
index dd27c50b498289b82c593efcf49c8ecc2cbd21f4..6db8261ac020a1a89bc390430f6aba082e7ab8ab 100644 (file)
@@ -101,6 +101,9 @@ static int reply_query_state(DnsQuery *q) {
                  * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */
                 return sd_bus_reply_method_errorf(q->bus_request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q));
 
+        case DNS_TRANSACTION_NO_SOURCE:
+                return sd_bus_reply_method_errorf(q->bus_request, BUS_ERROR_NO_SOURCE, "All suitable resolution sources turned off");
+
         case DNS_TRANSACTION_RCODE_FAILURE: {
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
 
@@ -274,8 +277,8 @@ static int validate_and_mangle_flags(
          *
          * 1. Checks that the interface index is either 0 (meaning *all* interfaces) or positive
          *
-         * 2. Only the protocols flags and the NO_CNAME flag are set, at most. Plus additional flags specific
-         *    to our method, passed in the "ok" parameter.
+         * 2. Only the protocols flags and a bunch of NO_XYZ flags are set, at most. Plus additional flags
+         *    specific to our method, passed in the "ok" parameter.
          *
          * 3. If zero protocol flags are specified it is automatically turned into *all* protocols. This way
          *    clients can simply pass 0 as flags and all will work as it should. They can also use this so
@@ -283,7 +286,15 @@ static int validate_and_mangle_flags(
          *    to mean "all supported protocols".
          */
 
-        if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok))
+        if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|
+                       SD_RESOLVED_NO_CNAME|
+                       SD_RESOLVED_NO_VALIDATE|
+                       SD_RESOLVED_NO_SYNTHESIZE|
+                       SD_RESOLVED_NO_CACHE|
+                       SD_RESOLVED_NO_ZONE|
+                       SD_RESOLVED_NO_TRUST_ANCHOR|
+                       SD_RESOLVED_NO_NETWORK|
+                       ok))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
 
         if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
@@ -406,7 +417,7 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
         if (r < 0 && r != -EALREADY)
                 return r;
 
-        r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, ifindex, flags);
+        r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, ifindex, flags);
         if (r < 0)
                 return r;
 
@@ -548,7 +559,7 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s
         if (r < 0)
                 return r;
 
-        r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
+        r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH);
         if (r < 0)
                 return r;
 
@@ -724,14 +735,12 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
         if (r < 0)
                 return r;
 
-        r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
+        /* Setting SD_RESOLVED_CLAMP_TTL: let's request that the TTL is fixed up for locally cached entries,
+         * after all we return it in the wire format blob. */
+        r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL);
         if (r < 0)
                 return r;
 
-        /* Let's request that the TTL is fixed up for locally cached entries, after all we return it in the wire format
-         * blob */
-        q->clamp_ttl = true;
-
         q->bus_request = sd_bus_message_ref(message);
         q->complete = bus_method_resolve_record_complete;
 
@@ -1088,7 +1097,7 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin
         if (r < 0)
                 return r;
 
-        r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
+        r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
         if (r < 0)
                 return r;
 
@@ -1258,7 +1267,7 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s
         if (r < 0)
                 return r;
 
-        r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH);
+        r = dns_query_new(m, &q, question_utf8, question_idna, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH);
         if (r < 0)
                 return r;
 
index 21eb6994e62302755ae39ab9330d178aff1655e4..133cf0556f69047a721df702c300554b3eb8c26d 100644 (file)
 /* Output: Result is authenticated */
 #define SD_RESOLVED_AUTHENTICATED   (UINT64_C(1) << 9)
 
+/* Input: Don't DNSSEC validate request */
+#define SD_RESOLVED_NO_VALIDATE     (UINT64_C(1) << 10)
+
+/* Input: Don't answer request from locally synthesized records (which includes /etc/hosts) */
+#define SD_RESOLVED_NO_SYNTHESIZE   (UINT64_C(1) << 11)
+
+/* Input: Don't answer request from cache */
+#define SD_RESOLVED_NO_CACHE        (UINT64_C(1) << 12)
+
+/* Input: Don't answer request from locally registered public LLMNR/mDNS RRs */
+#define SD_RESOLVED_NO_ZONE         (UINT64_C(1) << 13)
+
+/* Input: Don't answer request from locally registered public LLMNR/mDNS RRs */
+#define SD_RESOLVED_NO_TRUST_ANCHOR (UINT64_C(1) << 14)
+
+/* Input: Don't go to network for this request */
+#define SD_RESOLVED_NO_NETWORK      (UINT64_C(1) << 15)
+
+/* Input: Require that request is answered from a "primary" answer, i.e. not from RRs acquired as
+ * side-effect of a previous transaction */
+#define SD_RESOLVED_REQUIRE_PRIMARY (UINT64_C(1) << 16)
+
+/* Input: If reply is answered from cache, the TTLs will be adjusted by age of cache entry */
+#define SD_RESOLVED_CLAMP_TTL       (UINT64_C(1) << 17)
+
 #define SD_RESOLVED_LLMNR           (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6)
 #define SD_RESOLVED_MDNS            (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6)
 #define SD_RESOLVED_PROTOCOLS_ALL   (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)
index 925d5a3af2b0c74a36648d9d3931911bae5f3862..478141e730c357124ac553b53af1c480fe586566 100644 (file)
@@ -34,13 +34,16 @@ enum DnsCacheItemType {
 
 struct DnsCacheItem {
         DnsCacheItemType type;
-        DnsResourceKey *key;
-        DnsResourceRecord *rr;
+        DnsResourceKey *key;     /* The key for this item, i.e. the lookup key */
+        DnsResourceRecord *rr;   /* The RR for this item, i.e. the lookup value for positive queries */
+        DnsAnswer *answer;       /* The full validated answer, if this is an RRset acquired via a "primary" lookup */
+        DnsPacket *full_packet;  /* The full packet this information was acquired with */
         int rcode;
 
         usec_t until;
         bool authenticated:1;
         bool shared_owner:1;
+        DnssecResult dnssec_result;
 
         int ifindex;
         int owner_family;
@@ -50,6 +53,12 @@ struct DnsCacheItem {
         LIST_FIELDS(DnsCacheItem, by_key);
 };
 
+/* Returns true if this is a cache item created as result of an explicit lookup, or created as "side-effect"
+ * of another request. "Primary" entries will carry the full answer data (with NSEC, …) that can aso prove
+ * wildcard expansion, non-existance and such, while entries that were created as "side-effect" just contain
+ * immediate RR data for the specified RR key, but nothing else. */
+#define DNS_CACHE_ITEM_IS_PRIMARY(item) (!!(item)->answer)
+
 static const char *dns_cache_item_type_to_string(DnsCacheItem *item) {
         assert(item);
 
@@ -77,6 +86,8 @@ static void dns_cache_item_free(DnsCacheItem *i) {
 
         dns_resource_record_unref(i->rr);
         dns_resource_key_unref(i->key);
+        dns_answer_unref(i->answer);
+        dns_packet_unref(i->full_packet);
         free(i);
 }
 
@@ -340,8 +351,11 @@ static void dns_cache_item_update_positive(
                 DnsCache *c,
                 DnsCacheItem *i,
                 DnsResourceRecord *rr,
+                DnsAnswer *answer,
+                DnsPacket *full_packet,
                 bool authenticated,
                 bool shared_owner,
+                DnssecResult dnssec_result,
                 usec_t timestamp,
                 int ifindex,
                 int owner_family,
@@ -367,9 +381,18 @@ static void dns_cache_item_update_positive(
         dns_resource_key_unref(i->key);
         i->key = dns_resource_key_ref(rr->key);
 
-        i->until = calculate_until(rr, (uint32_t) -1, timestamp, false);
+        dns_answer_ref(answer);
+        dns_answer_unref(i->answer);
+        i->answer = answer;
+
+        dns_packet_ref(full_packet);
+        dns_packet_unref(i->full_packet);
+        i->full_packet = full_packet;
+
+        i->until = calculate_until(rr, UINT32_MAX, timestamp, false);
         i->authenticated = authenticated;
         i->shared_owner = shared_owner;
+        i->dnssec_result = dnssec_result;
 
         i->ifindex = ifindex;
 
@@ -382,8 +405,11 @@ static void dns_cache_item_update_positive(
 static int dns_cache_put_positive(
                 DnsCache *c,
                 DnsResourceRecord *rr,
+                DnsAnswer *answer,
+                DnsPacket *full_packet,
                 bool authenticated,
                 bool shared_owner,
+                DnssecResult dnssec_result,
                 usec_t timestamp,
                 int ifindex,
                 int owner_family,
@@ -420,8 +446,11 @@ static int dns_cache_put_positive(
                                 c,
                                 existing,
                                 rr,
+                                answer,
+                                full_packet,
                                 authenticated,
                                 shared_owner,
+                                dnssec_result,
                                 timestamp,
                                 ifindex,
                                 owner_family,
@@ -444,9 +473,12 @@ static int dns_cache_put_positive(
                 .type = DNS_CACHE_POSITIVE,
                 .key = dns_resource_key_ref(rr->key),
                 .rr = dns_resource_record_ref(rr),
+                .answer = dns_answer_ref(answer),
+                .full_packet = dns_packet_ref(full_packet),
                 .until = calculate_until(rr, (uint32_t) -1, timestamp, false),
                 .authenticated = authenticated,
                 .shared_owner = shared_owner,
+                .dnssec_result = dnssec_result,
                 .ifindex = ifindex,
                 .owner_family = owner_family,
                 .owner_address = *owner_address,
@@ -481,7 +513,10 @@ static int dns_cache_put_negative(
                 DnsCache *c,
                 DnsResourceKey *key,
                 int rcode,
+                DnsAnswer *answer,
+                DnsPacket *full_packet,
                 bool authenticated,
+                DnssecResult dnssec_result,
                 uint32_t nsec_ttl,
                 usec_t timestamp,
                 DnsResourceRecord *soa,
@@ -532,10 +567,13 @@ static int dns_cache_put_negative(
                         rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA :
                         rcode == DNS_RCODE_NXDOMAIN ? DNS_CACHE_NXDOMAIN : DNS_CACHE_RCODE,
                 .authenticated = authenticated,
+                .dnssec_result = dnssec_result,
                 .owner_family = owner_family,
                 .owner_address = *owner_address,
                 .prioq_idx = PRIOQ_IDX_NULL,
                 .rcode = rcode,
+                .answer = dns_answer_ref(answer),
+                .full_packet = dns_packet_ref(full_packet),
         };
 
         i->until =
@@ -630,7 +668,9 @@ int dns_cache_put(
                 DnsResourceKey *key,
                 int rcode,
                 DnsAnswer *answer,
+                DnsPacket *full_packet,
                 bool authenticated,
+                DnssecResult dnssec_result,
                 uint32_t nsec_ttl,
                 usec_t timestamp,
                 int owner_family,
@@ -661,6 +701,7 @@ int dns_cache_put(
                                 log_debug("Not caching negative entry without a SOA record: %s",
                                           dns_resource_key_to_string(key, key_str, sizeof key_str));
                         }
+
                         return 0;
                 }
 
@@ -685,18 +726,49 @@ int dns_cache_put(
 
         /* Second, add in positive entries for all contained RRs */
         DNS_ANSWER_FOREACH_ITEM(item, answer) {
-                if ((item->flags & DNS_ANSWER_CACHEABLE) == 0 ||
+                int primary = false;
+
+                if (!FLAGS_SET(item->flags, DNS_ANSWER_CACHEABLE) ||
                     !rr_eligible(item->rr))
                         continue;
 
+                if (key) {
+                        /* We store the auxiliary RRs and packet data in the cache only if they were in
+                         * direct response to the original query. If we cache an RR we also received, and
+                         * that is just auxiliary information we can't use the data, hence don't. */
+
+                        primary = dns_resource_key_match_rr(key, item->rr, NULL);
+                        if (primary < 0)
+                                return primary;
+                        if (primary == 0) {
+                                primary = dns_resource_key_match_cname_or_dname(key, item->rr->key, NULL);
+                                if (primary < 0)
+                                        return primary;
+                        }
+                }
+
+                if (!primary) {
+                        DnsCacheItem *first;
+
+                        /* Do not replace existing cache items for primary lookups with non-primary
+                         * data. After all the primary lookup data is a lot more useful. */
+                        first = hashmap_get(c->by_key, item->rr->key);
+                        if (first && DNS_CACHE_ITEM_IS_PRIMARY(first))
+                                return 0;
+                }
+
                 r = dns_cache_put_positive(
                                 c,
                                 item->rr,
+                                primary ? answer : NULL,
+                                primary ? full_packet : NULL,
                                 item->flags & DNS_ANSWER_AUTHENTICATED,
                                 item->flags & DNS_ANSWER_SHARED_OWNER,
+                                dnssec_result,
                                 timestamp,
                                 item->ifindex,
-                                owner_family, owner_address);
+                                owner_family,
+                                owner_address);
                 if (r < 0)
                         goto fail;
         }
@@ -746,7 +818,10 @@ int dns_cache_put(
                         c,
                         key,
                         rcode,
+                        answer,
+                        full_packet,
                         authenticated,
+                        dnssec_result,
                         nsec_ttl,
                         timestamp,
                         soa,
@@ -828,7 +903,59 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
         return NULL;
 }
 
-int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **ret, bool *authenticated) {
+static int answer_add_clamp_ttl(
+                DnsAnswer **answer,
+                DnsResourceRecord *rr,
+                int ifindex,
+                DnsAnswerFlags answer_flags,
+                DnsResourceRecord *rrsig,
+                uint64_t query_flags,
+                usec_t until,
+                usec_t current) {
+
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *patched = NULL, *patched_rrsig = NULL;
+        int r;
+
+        assert(answer);
+        assert(rr);
+
+        if (FLAGS_SET(query_flags, SD_RESOLVED_CLAMP_TTL)) {
+                patched = dns_resource_record_ref(rr);
+
+                r = dns_resource_record_clamp_ttl(&patched, LESS_BY(until, current) / USEC_PER_SEC);
+                if (r < 0)
+                        return r;
+
+                rr = patched;
+
+                if (rrsig) {
+                        patched_rrsig = dns_resource_record_ref(rrsig);
+                        r = dns_resource_record_clamp_ttl(&patched_rrsig, LESS_BY(until, current) / USEC_PER_SEC);
+                        if (r < 0)
+                                return r;
+
+                        rrsig = patched_rrsig;
+                }
+        }
+
+        r = dns_answer_add_extend(answer, rr, ifindex, answer_flags, rrsig);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int dns_cache_lookup(
+                DnsCache *c,
+                DnsResourceKey *key,
+                uint64_t query_flags,
+                int *ret_rcode,
+                DnsAnswer **ret_answer,
+                DnsPacket **ret_full_packet,
+                bool *ret_authenticated,
+                DnssecResult *ret_dnssec_result) {
+
+        _cleanup_(dns_packet_unrefp) DnsPacket *full_packet = NULL;
         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
         char key_str[DNS_RESOURCE_KEY_STRING_MAX];
         unsigned n = 0;
@@ -838,27 +965,19 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod
         bool have_authenticated = false, have_non_authenticated = false;
         usec_t current;
         int found_rcode = -1;
+        DnssecResult dnssec_result = -1;
+        int have_dnssec_result = -1;
 
         assert(c);
         assert(key);
-        assert(rcode);
-        assert(ret);
-        assert(authenticated);
 
         if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
-                /* If we have ANY lookups we don't use the cache, so
-                 * that the caller refreshes via the network. */
+                /* If we have ANY lookups we don't use the cache, so that the caller refreshes via the
+                 * network. */
 
                 log_debug("Ignoring cache for ANY lookup: %s",
                           dns_resource_key_to_string(key, key_str, sizeof key_str));
-
-                c->n_miss++;
-
-                *ret = NULL;
-                *rcode = DNS_RCODE_SUCCESS;
-                *authenticated = false;
-
-                return 0;
+                goto miss;
         }
 
         first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key);
@@ -867,31 +986,80 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod
 
                 log_debug("Cache miss for %s",
                           dns_resource_key_to_string(key, key_str, sizeof key_str));
+                goto miss;
+        }
 
-                c->n_miss++;
+        if (FLAGS_SET(query_flags, SD_RESOLVED_CLAMP_TTL))
+                current = now(clock_boottime_or_monotonic());
 
-                *ret = NULL;
-                *rcode = DNS_RCODE_SUCCESS;
-                *authenticated = false;
+        LIST_FOREACH(by_key, j, first) {
+                /* If the caller doesn't allow us to answer questions from cache data learned from
+                 * "side-effect", skip this entry. */
+                if (FLAGS_SET(query_flags, SD_RESOLVED_REQUIRE_PRIMARY) &&
+                    !DNS_CACHE_ITEM_IS_PRIMARY(j)) {
+                        log_debug("Primary answer was requested for cache lookup for %s, which we don't have.",
+                                  dns_resource_key_to_string(key, key_str, sizeof key_str));
 
-                return 0;
-        }
+                        goto miss;
+                }
 
-        LIST_FOREACH(by_key, j, first) {
-                if (j->rr) {
+                if (j->type == DNS_CACHE_NXDOMAIN)
+                        nxdomain = true;
+                else if (j->type == DNS_CACHE_RCODE)
+                        found_rcode = j->rcode;
+                else if (j->rr) {
                         if (j->rr->key->type == DNS_TYPE_NSEC)
                                 nsec = j;
 
                         n++;
-                } else if (j->type == DNS_CACHE_NXDOMAIN)
-                        nxdomain = true;
-                else if (j->type == DNS_CACHE_RCODE)
-                        found_rcode = j->rcode;
+                }
 
                 if (j->authenticated)
                         have_authenticated = true;
                 else
                         have_non_authenticated = true;
+
+                if (j->dnssec_result < 0) {
+                        have_dnssec_result = false; /* an entry without dnssec result? then invalidate things for good */
+                        dnssec_result = _DNSSEC_RESULT_INVALID;
+                } else if (have_dnssec_result < 0) {
+                        have_dnssec_result = true; /* So far no result seen, let's pick this one up */
+                        dnssec_result = j->dnssec_result;
+                } else if (have_dnssec_result > 0 && j->dnssec_result != dnssec_result) {
+                        have_dnssec_result = false; /* conflicting result seen? then invalidate for good */
+                        dnssec_result = _DNSSEC_RESULT_INVALID;
+                }
+
+                /* Append the answer RRs to our answer. Ideally we have the answer object, which we
+                 * preferably use. But if the cached entry was generated as "side-effect" of a reply,
+                 * i.e. from validated auxiliary records rather than from the main reply, then we use the
+                 * individual RRs only instead. */
+                if (j->answer) {
+
+                        /* Minor optimization, if the full answer object of this and the previous RR is the
+                         * same, don't bother adding it again. Typically we store a full RRset here, hence
+                         * that should be the case. */
+                        if (!j->by_key_prev || j->answer != j->by_key_prev->answer) {
+                                DnsAnswerItem *item;
+
+                                DNS_ANSWER_FOREACH_ITEM(item, j->answer) {
+                                        r = answer_add_clamp_ttl(&answer, item->rr, item->ifindex, item->flags, item->rrsig, query_flags, j->until, current);
+                                        if (r < 0)
+                                                return r;
+                                }
+                        }
+
+                } else if (j->rr) {
+                        r = answer_add_clamp_ttl(&answer, j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0, NULL, query_flags, j->until, current);
+                        if (r < 0)
+                                return r;
+                }
+
+                /* We'll return any packet we have for this. Typically all cache entries for the same key
+                 * should come from the same packet anyway, hence it doesn't really matter which packet we
+                 * return here, they should all resolve to the same anyway. */
+                if (!full_packet && j->full_packet)
+                        full_packet = dns_packet_ref(j->full_packet);
         }
 
         if (found_rcode >= 0) {
@@ -899,28 +1067,41 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod
                           dns_rcode_to_string(found_rcode),
                           dns_resource_key_to_string(key, key_str, sizeof(key_str)));
 
-                *ret = NULL;
-                *rcode = found_rcode;
-                *authenticated = false;
+                if (ret_rcode)
+                        *ret_rcode = found_rcode;
+                if (ret_answer)
+                        *ret_answer = TAKE_PTR(answer);
+                if (ret_full_packet)
+                        *ret_full_packet = TAKE_PTR(full_packet);
+                if (ret_authenticated)
+                        *ret_authenticated = false;
+                if (ret_dnssec_result)
+                        *ret_dnssec_result = dnssec_result;
 
                 c->n_hit++;
                 return 1;
         }
 
         if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) {
-                /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from
-                 * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */
+                /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC
+                 * RRs from the lower-zone of a zone cut, but the DS RRs are on the upper zone. */
 
                 log_debug("NSEC NODATA cache hit for %s",
                           dns_resource_key_to_string(key, key_str, sizeof key_str));
 
-                /* We only found an NSEC record that matches our name.
-                 * If it says the type doesn't exist report
-                 * NODATA. Otherwise report a cache miss. */
+                /* We only found an NSEC record that matches our name.  If it says the type doesn't exist
+                 * report NODATA. Otherwise report a cache miss. */
 
-                *ret = NULL;
-                *rcode = DNS_RCODE_SUCCESS;
-                *authenticated = nsec->authenticated;
+                if (ret_rcode)
+                        *ret_rcode = DNS_RCODE_SUCCESS;
+                if (ret_answer)
+                        *ret_answer = TAKE_PTR(answer);
+                if (ret_full_packet)
+                        *ret_full_packet = TAKE_PTR(full_packet);
+                if (ret_authenticated)
+                        *ret_authenticated = nsec->authenticated;
+                if (ret_dnssec_result)
+                        *ret_dnssec_result = nsec->dnssec_result;
 
                 if (!bitmap_isset(nsec->rr->nsec.types, key->type) &&
                     !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) &&
@@ -941,46 +1122,49 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod
         if (n <= 0) {
                 c->n_hit++;
 
-                *ret = NULL;
-                *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
-                *authenticated = have_authenticated && !have_non_authenticated;
-                return 1;
-        }
-
-        answer = dns_answer_new(n);
-        if (!answer)
-                return -ENOMEM;
-
-        if (clamp_ttl)
-                current = now(clock_boottime_or_monotonic());
+                if (ret_rcode)
+                        *ret_rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
+                if (ret_answer)
+                        *ret_answer = TAKE_PTR(answer);
+                if (ret_full_packet)
+                        *ret_full_packet = TAKE_PTR(full_packet);
+                if (ret_authenticated)
+                        *ret_authenticated = have_authenticated && !have_non_authenticated;
+                if (ret_dnssec_result)
+                        *ret_dnssec_result = dnssec_result;
 
-        LIST_FOREACH(by_key, j, first) {
-                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
-
-                if (!j->rr)
-                        continue;
-
-                if (clamp_ttl) {
-                        rr = dns_resource_record_ref(j->rr);
-
-                        r = dns_resource_record_clamp_ttl(&rr, LESS_BY(j->until, current) / USEC_PER_SEC);
-                        if (r < 0)
-                                return r;
-                }
-
-                r = dns_answer_add(answer, rr ?: j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0, NULL);
-                if (r < 0)
-                        return r;
+                return 1;
         }
 
         c->n_hit++;
 
-        *ret = answer;
-        *rcode = DNS_RCODE_SUCCESS;
-        *authenticated = have_authenticated && !have_non_authenticated;
-        answer = NULL;
+        if (ret_rcode)
+                *ret_rcode = DNS_RCODE_SUCCESS;
+        if (ret_answer)
+                *ret_answer = TAKE_PTR(answer);
+        if (ret_full_packet)
+                *ret_full_packet = TAKE_PTR(full_packet);
+        if (ret_authenticated)
+                *ret_authenticated = have_authenticated && !have_non_authenticated;
+        if (ret_dnssec_result)
+                *ret_dnssec_result = dnssec_result;
 
         return n;
+
+miss:
+        if (ret_rcode)
+                *ret_rcode = DNS_RCODE_SUCCESS;
+        if (ret_answer)
+                *ret_answer = NULL;
+        if (ret_full_packet)
+                *ret_full_packet = NULL;
+        if (ret_authenticated)
+                *ret_authenticated = false;
+        if (ret_dnssec_result)
+                *ret_dnssec_result = _DNSSEC_RESULT_INVALID;
+
+        c->n_miss++;
+        return 0;
 }
 
 int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {
index 4ab213dc9cd5317d610f0924f41f187d274de42c..1d53e5da0daca3280cb52f2b91f1aaafe8256ad4 100644 (file)
@@ -5,6 +5,7 @@
 #include "list.h"
 #include "prioq.h"
 #include "resolve-util.h"
+#include "resolved-dns-dnssec.h"
 #include "time-util.h"
 
 typedef struct DnsCache {
@@ -22,8 +23,29 @@ typedef struct DnsCache {
 void dns_cache_flush(DnsCache *c);
 void dns_cache_prune(DnsCache *c);
 
-int dns_cache_put(DnsCache *c, DnsCacheMode cache_mode, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
-int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **answer, bool *authenticated);
+int dns_cache_put(
+                DnsCache *c,
+                DnsCacheMode cache_mode,
+                DnsResourceKey *key,
+                int rcode,
+                DnsAnswer *answer,
+                DnsPacket *full_packet,
+                bool authenticated,
+                DnssecResult dnssec_result,
+                uint32_t nsec_ttl,
+                usec_t timestamp,
+                int owner_family,
+                const union in_addr_union *owner_address);
+
+int dns_cache_lookup(
+                DnsCache *c,
+                DnsResourceKey *key,
+                uint64_t query_flags,
+                int *ret_rcode,
+                DnsAnswer **ret_answer,
+                DnsPacket **ret_full_packet,
+                bool *ret_authenticated,
+                DnssecResult *ret_dnssec_result);
 
 int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address);
 
index 8ee4fd8309348647d86d0c68204686e42d7daf51..562924e67c37d8cf04023b07fe501fc0395f6cae 100644 (file)
@@ -97,20 +97,35 @@ static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) {
         return 1;
 }
 
-static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) {
+static int dns_query_candidate_add_transaction(
+                DnsQueryCandidate *c,
+                DnsResourceKey *key,
+                DnsPacket *bypass) {
+
         _cleanup_(dns_transaction_gcp) DnsTransaction *t = NULL;
         int r;
 
         assert(c);
-        assert(key);
 
-        t = dns_scope_find_transaction(c->scope, key, true);
-        if (!t) {
-                r = dns_transaction_new(&t, c->scope, key);
+        if (key) {
+                /* Regular lookup with a resource key */
+                assert(!bypass);
+
+                t = dns_scope_find_transaction(c->scope, key, c->query->flags);
+                if (!t) {
+                        r = dns_transaction_new(&t, c->scope, key, NULL, c->query->flags);
+                        if (r < 0)
+                                return r;
+                } else if (set_contains(c->transactions, t))
+                        return 0;
+        } else {
+                /* "Bypass" lookup with a query packet */
+                assert(bypass);
+
+                r = dns_transaction_new(&t, c->scope, NULL, bypass, c->query->flags);
                 if (r < 0)
                         return r;
-        } else if (set_contains(c->transactions, t))
-                return 0;
+        }
 
         r = set_ensure_allocated(&t->notify_query_candidates_done, NULL);
         if (r < 0)
@@ -126,7 +141,6 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
                 return r;
         }
 
-        t->clamp_ttl = c->query->clamp_ttl;
         TAKE_PTR(t);
         return 1;
 }
@@ -214,6 +228,21 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
 
         dns_query_candidate_stop(c);
 
+        if (c->query->question_bypass) {
+                /* If this is a bypass query, then pass the original query packet along to the transaction */
+
+                assert(dns_question_size(c->query->question_bypass->question) == 1);
+
+                if (!dns_scope_good_key(c->scope, c->query->question_bypass->question->keys[0]))
+                        return 0;
+
+                r = dns_query_candidate_add_transaction(c, NULL, c->query->question_bypass);
+                if (r < 0)
+                        goto fail;
+
+                return 1;
+        }
+
         question = dns_query_question_for_protocol(c->query, c->scope->protocol);
 
         /* Create one transaction per question key */
@@ -233,7 +262,7 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
                 if (!dns_scope_good_key(c->scope, qkey))
                         continue;
 
-                r = dns_query_candidate_add_transaction(c, qkey);
+                r = dns_query_candidate_add_transaction(c, qkey, NULL);
                 if (r < 0)
                         goto fail;
 
@@ -321,6 +350,7 @@ static void dns_query_reset_answer(DnsQuery *q) {
         q->answer_protocol = _DNS_PROTOCOL_INVALID;
         q->answer_family = AF_UNSPEC;
         q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain);
+        q->answer_full_packet = dns_packet_unref(q->answer_full_packet);
 }
 
 DnsQuery *dns_query_free(DnsQuery *q) {
@@ -340,6 +370,7 @@ DnsQuery *dns_query_free(DnsQuery *q) {
 
         dns_question_unref(q->question_idna);
         dns_question_unref(q->question_utf8);
+        dns_packet_unref(q->question_bypass);
 
         dns_query_reset_answer(q);
 
@@ -351,13 +382,15 @@ DnsQuery *dns_query_free(DnsQuery *q) {
                 varlink_unref(q->varlink_request);
         }
 
-        dns_packet_unref(q->request_dns_packet);
-        dns_packet_unref(q->reply_dns_packet);
+        dns_packet_unref(q->request_packet);
+        dns_answer_unref(q->reply_answer);
+        dns_answer_unref(q->reply_authoritative);
+        dns_answer_unref(q->reply_additional);
 
-        if (q->request_dns_stream) {
+        if (q->request_stream) {
                 /* Detach the stream from our query, in case something else keeps a reference to it. */
-                (void) set_remove(q->request_dns_stream->queries, q);
-                q->request_dns_stream = dns_stream_unref(q->request_dns_stream);
+                (void) set_remove(q->request_stream->queries, q);
+                q->request_stream = dns_stream_unref(q->request_stream);
         }
 
         free(q->request_address_string);
@@ -375,36 +408,27 @@ int dns_query_new(
                 DnsQuery **ret,
                 DnsQuestion *question_utf8,
                 DnsQuestion *question_idna,
+                DnsPacket *question_bypass,
                 int ifindex,
                 uint64_t flags) {
 
         _cleanup_(dns_query_freep) DnsQuery *q = NULL;
+        char key_str[DNS_RESOURCE_KEY_STRING_MAX];
         DnsResourceKey *key;
-        bool good = false;
         int r;
-        char key_str[DNS_RESOURCE_KEY_STRING_MAX];
 
         assert(m);
 
-        if (dns_question_size(question_utf8) > 0) {
-                r = dns_question_is_valid_for_query(question_utf8);
-                if (r < 0)
-                        return r;
-                if (r == 0)
+        if (question_bypass) {
+                /* It's either a "bypass" query, or a regular one, but can't be both. */
+                if (question_utf8 || question_idna)
                         return -EINVAL;
 
-                good = true;
-        }
+        } else {
+                bool good = false;
 
-        /* If the IDNA and UTF8 questions are the same, merge their references */
-        r = dns_question_is_equal(question_idna, question_utf8);
-        if (r < 0)
-                return r;
-        if (r > 0)
-                question_idna = question_utf8;
-        else {
-                if (dns_question_size(question_idna) > 0) {
-                        r = dns_question_is_valid_for_query(question_idna);
+                if (dns_question_size(question_utf8) > 0) {
+                        r = dns_question_is_valid_for_query(question_utf8);
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -412,10 +436,28 @@ int dns_query_new(
 
                         good = true;
                 }
-        }
 
-        if (!good) /* don't allow empty queries */
-                return -EINVAL;
+                /* If the IDNA and UTF8 questions are the same, merge their references */
+                r = dns_question_is_equal(question_idna, question_utf8);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        question_idna = question_utf8;
+                else {
+                        if (dns_question_size(question_idna) > 0) {
+                                r = dns_question_is_valid_for_query(question_idna);
+                                if (r < 0)
+                                        return r;
+                                if (r == 0)
+                                        return -EINVAL;
+
+                                good = true;
+                        }
+                }
+
+                if (!good) /* don't allow empty queries */
+                        return -EINVAL;
+        }
 
         if (m->n_dns_queries >= QUERIES_MAX)
                 return -EBUSY;
@@ -427,6 +469,7 @@ int dns_query_new(
         *q = (DnsQuery) {
                 .question_utf8 = dns_question_ref(question_utf8),
                 .question_idna = dns_question_ref(question_idna),
+                .question_bypass = dns_packet_ref(question_bypass),
                 .ifindex = ifindex,
                 .flags = flags,
                 .answer_dnssec_result = _DNSSEC_RESULT_INVALID,
@@ -434,21 +477,27 @@ int dns_query_new(
                 .answer_family = AF_UNSPEC,
         };
 
-        /* First dump UTF8  question */
-        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) {
-                r = dns_question_contains(question_utf8, key);
-                if (r < 0)
-                        return r;
-                if (r > 0)
-                        continue;
+        if (question_bypass) {
+                DNS_QUESTION_FOREACH(key, question_bypass->question)
+                        log_debug("Looking up bypass packet for %s.",
+                                  dns_resource_key_to_string(key, key_str, sizeof key_str));
+        } else {
+                /* First dump UTF8  question */
+                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) {
+                        r = dns_question_contains(question_utf8, key);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                continue;
 
-                log_debug("Looking up IDNA RR for %s.",
-                          dns_resource_key_to_string(key, key_str, sizeof key_str));
+                        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);
@@ -457,8 +506,8 @@ int dns_query_new(
 
         if (ret)
                 *ret = q;
-        q = NULL;
 
+        TAKE_PTR(q);
         return 0;
 }
 
@@ -559,9 +608,12 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
                     DNS_TRANSACTION_NOT_FOUND))
                 return 0;
 
+        if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE))
+                return 0;
+
         r = dns_synthesize_answer(
                         q->manager,
-                        q->question_utf8,
+                        q->question_bypass ? q->question_bypass->question : q->question_utf8,
                         q->ifindex,
                         &answer);
         if (r == -ENXIO) {
@@ -601,9 +653,12 @@ static int dns_query_try_etc_hosts(DnsQuery *q) {
         /* 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. */
 
+        if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE))
+                return 0;
+
         r = manager_etc_hosts_lookup(
                         q->manager,
-                        q->question_utf8,
+                        q->question_bypass ? q->question_bypass->question : q->question_utf8,
                         &answer);
         if (r <= 0)
                 return r;
@@ -757,6 +812,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
                 q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
                 q->answer_authenticated = false;
                 q->answer_errno = c->error_code;
+                q->answer_full_packet = dns_packet_unref(q->answer_full_packet);
         }
 
         SET_FOREACH(t, c->transactions) {
@@ -764,14 +820,24 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
                 switch (t->state) {
 
                 case DNS_TRANSACTION_SUCCESS: {
-                        /* We found a successfully reply, merge it into the answer */
-                        r = dns_answer_extend(&q->answer, t->answer);
-                        if (r < 0)
-                                goto fail;
+                        /* We found a successful reply, merge it into the answer */
+
+                        if (state == DNS_TRANSACTION_SUCCESS) {
+                                r = dns_answer_extend(&q->answer, t->answer);
+                                if (r < 0)
+                                        goto fail;
+                        } else {
+                                /* Override non-successful previous answers */
+                                dns_answer_unref(q->answer);
+                                q->answer = dns_answer_ref(t->answer);
+                        }
 
                         q->answer_rcode = t->answer_rcode;
                         q->answer_errno = 0;
 
+                        dns_packet_unref(q->answer_full_packet);
+                        q->answer_full_packet = dns_packet_ref(t->received);
+
                         if (t->answer_authenticated) {
                                 has_authenticated = true;
                                 dnssec_result_authenticated = t->answer_dnssec_result;
@@ -800,11 +866,14 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
                         if (q->answer_authenticated && !t->answer_authenticated)
                                 continue;
 
-                        q->answer = dns_answer_unref(q->answer);
+                        dns_answer_unref(q->answer);
+                        q->answer = dns_answer_ref(t->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;
+                        dns_packet_unref(q->answer_full_packet);
+                        q->answer_full_packet = dns_packet_ref(t->received);
 
                         state = t->state;
                         break;
@@ -998,6 +1067,9 @@ int dns_query_process_cname(DnsQuery *q) {
 DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) {
         assert(q);
 
+        if (q->question_bypass)
+                return q->question_bypass->question;
+
         switch (protocol) {
 
         case DNS_PROTOCOL_DNS:
@@ -1018,6 +1090,9 @@ const char *dns_query_string(DnsQuery *q) {
 
         /* Returns a somewhat useful human-readable lookup key string for this query */
 
+        if (q->question_bypass)
+                return dns_question_first_name(q->question_bypass->question);
+
         if (q->request_address_string)
                 return q->request_address_string;
 
index 133076dbf071199b91c74023fa71aaa4afb397de..a7916712c11ec6d1f1ae9eee7673ff2cb88192a7 100644 (file)
@@ -49,12 +49,13 @@ struct DnsQuery {
         DnsQuestion *question_idna;
         DnsQuestion *question_utf8;
 
+        /* If this is not a question by ourselves, but a "bypass" request, we propagate the original packet
+         * here, and use that instead. */
+        DnsPacket *question_bypass;
+
         uint64_t flags;
         int ifindex;
 
-        /* If true, the RR TTLs of the answer will be clamped by their current left validity in the cache */
-        bool clamp_ttl;
-
         DnsTransactionState state;
         unsigned n_cname_redirects;
 
@@ -71,6 +72,7 @@ struct DnsQuery {
         DnsSearchDomain *answer_search_domain;
         int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */
         bool previous_redirect_unauthenticated;
+        DnsPacket *answer_full_packet;
 
         /* Bus + Varlink client information */
         sd_bus_message *bus_request;
@@ -82,9 +84,11 @@ struct DnsQuery {
         char *request_address_string;
 
         /* DNS stub information */
-        DnsPacket *request_dns_packet;
-        DnsStream *request_dns_stream;
-        DnsPacket *reply_dns_packet;
+        DnsPacket *request_packet;
+        DnsStream *request_stream;
+        DnsAnswer *reply_answer;
+        DnsAnswer *reply_authoritative;
+        DnsAnswer *reply_additional;
         DnsStubListenerExtra *stub_listener_extra;
 
         /* Completion callback */
@@ -109,7 +113,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQueryCandidate*, dns_query_candidate_unref);
 
 void dns_query_candidate_notify(DnsQueryCandidate *c);
 
-int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags);
+int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, DnsPacket *question_bypass, int family, uint64_t flags);
 DnsQuery *dns_query_free(DnsQuery *q);
 
 int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for);
index 6573edd630ef4947161e36c8260f0f54411a73d2..cee93a2c04a79a7b2bdebee30a5e053d41cb6c21 100644 (file)
@@ -925,26 +925,50 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
         }
 }
 
-DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) {
-        DnsTransaction *t;
+DnsTransaction *dns_scope_find_transaction(
+                DnsScope *scope,
+                DnsResourceKey *key,
+                uint64_t query_flags) {
+
+        DnsTransaction *first, *t;
 
         assert(scope);
         assert(key);
 
-        /* Try to find an ongoing transaction that is a equal to the
-         * specified question */
-        t = hashmap_get(scope->transactions_by_key, key);
-        if (!t)
-                return NULL;
+        /* Iterate through the list of transactions with a matching key */
+        first = hashmap_get(scope->transactions_by_key, key);
+        LIST_FOREACH(transactions_by_key, t, first) {
+
+                /* These four flags must match exactly: we cannot use a validated response for a
+                 * non-validating client, and we cannot use a non-validated response for a validating
+                 * client. Similar, if the sources don't match things aren't usable either. */
+                if (((query_flags ^ t->query_flags) &
+                     (SD_RESOLVED_NO_VALIDATE|
+                     SD_RESOLVED_NO_ZONE|
+                      SD_RESOLVED_NO_TRUST_ANCHOR|
+                      SD_RESOLVED_NO_NETWORK)) != 0)
+                        continue;
 
-        /* Refuse reusing transactions that completed based on cached
-         * data instead of a real packet, if that's requested. */
-        if (!cache_ok &&
-            IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) &&
-            t->answer_source != DNS_TRANSACTION_NETWORK)
-                return NULL;
+                /* We can reuse a primary query if a regular one is requested, but not vice versa */
+                if ((query_flags & SD_RESOLVED_REQUIRE_PRIMARY) &&
+                    !(t->query_flags & SD_RESOLVED_REQUIRE_PRIMARY))
+                        continue;
+
+                /* Don't reuse a transaction that allowed caching when we got told not to use it */
+                if ((query_flags & SD_RESOLVED_NO_CACHE) &&
+                    !(t->query_flags & SD_RESOLVED_NO_CACHE))
+                        continue;
+
+                /* If we are are asked to clamp ttls an the existing transaction doesn't do it, we can't
+                 * reuse */
+                if ((query_flags & SD_RESOLVED_CLAMP_TTL) &&
+                    !(t->query_flags & SD_RESOLVED_CLAMP_TTL))
+                        continue;
+
+                return t;
+        }
 
-        return t;
+        return NULL;
 }
 
 static int dns_scope_make_conflict_packet(
index de05c0838f4d6c79d156ef3ac245de8657e5b862..c7317a6bd867b3a7c9d288d3930dba01eca392fe 100644 (file)
@@ -53,14 +53,12 @@ struct DnsScope {
 
         LIST_HEAD(DnsQueryCandidate, query_candidates);
 
-        /* Note that we keep track of ongoing transactions in two
-         * ways: once in a hashmap, indexed by the rr key, and once in
-         * a linked list. We use the hashmap to quickly find
-         * transactions we can reuse for a key. But note that there
-         * might be multiple transactions for the same key (because
-         * the zone probing can't reuse a transaction answered from
-         * the zone or the cache), and the hashmap only tracks the
-         * most recent entry. */
+        /* Note that we keep track of ongoing transactions in two ways: once in a hashmap, indexed by the rr
+         * key, and once in a linked list. We use the hashmap to quickly find transactions we can reuse for a
+         * key. But note that there might be multiple transactions for the same key (because the associated
+         * query flags might differ in incompatible ways: e.g. we may not reuse a non-validating transaction
+         * as validating. Hence we maintain a per-key list of transactions, which we iterate through to find
+         * one we can reuse with matching flags. */
         Hashmap *transactions_by_key;
         LIST_HEAD(DnsTransaction, transactions);
 
@@ -90,7 +88,7 @@ int dns_scope_mdns_membership(DnsScope *s, bool b);
 int dns_scope_make_reply_packet(DnsScope *s, uint16_t id, int rcode, DnsQuestion *q, DnsAnswer *answer, DnsAnswer *soa, bool tentative, DnsPacket **ret);
 void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p);
 
-DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok);
+DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, uint64_t query_flags);
 
 int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr);
 void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p);
index 6a3dc9901ae8326e93147a373be567739b865d9c..8c252bfafe5af0b0d7c438c6563aae89ec92907f 100644 (file)
@@ -85,69 +85,259 @@ DnsStubListenerExtra *dns_stub_listener_extra_free(DnsStubListenerExtra *p) {
         return mfree(p);
 }
 
-static int dns_stub_make_reply_packet(
-                DnsPacket **p,
-                size_t max_size,
-                DnsQuestion *q,
+static int dns_stub_collect_answer_by_question(
+                DnsAnswer **reply,
                 DnsAnswer *answer,
-                bool *ret_truncated) {
+                DnsQuestion *question,
+                bool with_rrsig) { /* Add RRSIG RR matching each RR */
 
-        bool truncated = false;
-        DnsResourceRecord *rr;
-        unsigned c = 0;
+        DnsAnswerItem *item;
         int r;
 
-        assert(p);
+        assert(reply);
 
-        /* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence
-         * roundtrips aren't expensive. */
+        /* Copies all RRs from 'answer' into 'reply', if they match 'question'. */
 
-        if (!*p) {
-                r = dns_packet_new(p, DNS_PROTOCOL_DNS, 0, max_size);
-                if (r < 0)
-                        return r;
+        DNS_ANSWER_FOREACH_ITEM(item, answer) {
+
+                if (question) {
+                        bool match = false;
 
-                r = dns_packet_append_question(*p, q);
+                        r = dns_question_matches_rr(question, item->rr, NULL);
+                        if (r < 0)
+                                return r;
+                        else if (r > 0)
+                                match = true;
+                        else {
+                                r = dns_question_matches_cname_or_dname(question, item->rr, NULL);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0)
+                                        match = true;
+                        }
+
+                        if (!match)
+                                continue;
+                }
+
+                r = dns_answer_add_extend(reply, item->rr, item->ifindex, item->flags, item->rrsig);
                 if (r < 0)
                         return r;
 
-                DNS_PACKET_HEADER(*p)->qdcount = htobe16(dns_question_size(q));
+                if (with_rrsig && item->rrsig) {
+                        r = dns_answer_add_extend(reply, item->rrsig, item->ifindex, item->flags, NULL);
+                        if (r < 0)
+                                return r;
+                }
         }
 
-        DNS_ANSWER_FOREACH(rr, answer) {
+        return 0;
+}
 
-                r = dns_question_matches_rr(q, rr, NULL);
-                if (r < 0)
-                        return r;
-                if (r > 0)
-                        goto add;
+static int dns_stub_collect_answer_by_section(
+                DnsAnswer **reply,
+                DnsAnswer *answer,
+                DnsAnswerFlags section,
+                DnsAnswer *exclude1,
+                DnsAnswer *exclude2,
+                bool with_dnssec) { /* Include DNSSEC RRs. RRSIG, NSEC, … */
 
-                r = dns_question_matches_cname_or_dname(q, rr, NULL);
-                if (r < 0)
-                        return r;
-                if (r > 0)
-                        goto add;
+        DnsAnswerItem *item;
+        unsigned c = 0;
+        int r;
 
-                continue;
-        add:
-                r = dns_packet_append_rr(*p, rr, 0, NULL, NULL);
-                if (r == -EMSGSIZE) {
-                        truncated = true;
-                        break;
-                }
+        assert(reply);
+
+        /* Copies all RRs from 'answer' into 'reply', if they originate from the specified section. Also,
+         * avoid any RRs listed in 'exclude'. */
+
+        DNS_ANSWER_FOREACH_ITEM(item, answer) {
+
+                if (dns_answer_contains(exclude1, item->rr) ||
+                    dns_answer_contains(exclude2, item->rr))
+                        continue;
+
+                if (!with_dnssec &&
+                    dns_type_is_dnssec(item->rr->key->type))
+                        continue;
+
+                if (((item->flags ^ section) & (DNS_ANSWER_SECTION_ANSWER|DNS_ANSWER_SECTION_AUTHORITY|DNS_ANSWER_SECTION_ADDITIONAL)) != 0)
+                        continue;
+
+                r = dns_answer_add_extend(reply, item->rr, item->ifindex, item->flags, item->rrsig);
                 if (r < 0)
                         return r;
 
                 c++;
+
+                if (with_dnssec && item->rrsig) {
+                        r = dns_answer_add_extend(reply, item->rrsig, item->ifindex, item->flags, NULL);
+                        if (r < 0)
+                                return r;
+
+                        c++;
+                }
         }
 
+        return (int) c;
+}
+
+static int dns_stub_assign_sections(
+                DnsQuery *q,
+                DnsQuestion *question,
+                bool edns0_do) {
+
+        int r;
+
+        assert(q);
+        assert(question);
+
+        /* Let's assign the 'answer' and 'answer_auxiliary' RRs we collected to their respective sections in
+         * the reply datagram. We try to reproduce a section assignment similar to what the upstream DNS
+         * server responded to us. We use the DNS_ANSWER_SECTION_xyz flags to match things up, which is where
+         * the original upstream's packet section assignment is stored in the DnsAnswer object. Not all RRs
+         * in the 'answer' and 'answer_auxiliary' objects come with section information though (for example,
+         * because they were synthesized locally, and not from a DNS packet). To deal with that we extend the
+         * assignment logic a bit: anything from the 'answer' object that directly matches the original
+         * question is always put in the ANSWER section, regardless if it carries section info, or what that
+         * section info says. Then, anything from the 'answer' and 'answer_auxiliary' objects that is from
+         * the ANSWER or AUTHORITY sections, and wasn't already added to the ANSWER section is placed in the
+         * AUTHORITY section. Everything else from either object is added to the ADDITIONAL section. */
+
+        /* Include all RRs that directly answer the question in the answer section */
+        r = dns_stub_collect_answer_by_question(
+                        &q->reply_answer,
+                        q->answer,
+                        question,
+                        edns0_do);
+        if (r < 0)
+                return r;
+
+        /* Include all RRs that originate from the answer or authority sections, and aren't listed in the
+         * answer section, in the authority section */
+        r = dns_stub_collect_answer_by_section(
+                        &q->reply_authoritative,
+                        q->answer,
+                        DNS_ANSWER_SECTION_ANSWER,
+                        q->reply_answer, NULL,
+                        edns0_do);
+        if (r < 0)
+                return r;
+
+        /* Include all RRs that originate from the answer or authority sections, and aren't listed in the
+         * answer section, in the authority section */
+        r = dns_stub_collect_answer_by_section(
+                        &q->reply_authoritative,
+                        q->answer,
+                        DNS_ANSWER_SECTION_AUTHORITY,
+                        q->reply_answer, NULL,
+                        edns0_do);
+        if (r < 0)
+                return r;
+
+        /* Include all RRs that originate from the additional sections in the additional section (except if
+         * already listed in the other two sections). Also add all RRs with no section marking. */
+        r = dns_stub_collect_answer_by_section(
+                        &q->reply_additional,
+                        q->answer,
+                        DNS_ANSWER_SECTION_ADDITIONAL,
+                        q->reply_answer, q->reply_authoritative,
+                        edns0_do);
+        if (r < 0)
+                return r;
+        r = dns_stub_collect_answer_by_section(
+                        &q->reply_additional,
+                        q->answer,
+                        0,
+                        q->reply_answer, q->reply_authoritative,
+                        edns0_do);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int dns_stub_make_reply_packet(
+                DnsPacket **ret,
+                size_t max_size,
+                DnsQuestion *q,
+                bool *ret_truncated) {
+
+        _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+        bool tc = false;
+        int r;
+
+        assert(ret);
+
+        r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, max_size);
+        if (r < 0)
+                return r;
+
+        r = dns_packet_append_question(p, q);
+        if (r == -EMSGSIZE)
+                tc = true;
+        else if (r < 0)
+                return r;
+
         if (ret_truncated)
-                *ret_truncated = truncated;
-        else if (truncated)
+                *ret_truncated = tc;
+        else if (tc)
                 return -EMSGSIZE;
 
-        DNS_PACKET_HEADER(*p)->ancount = htobe16(be16toh(DNS_PACKET_HEADER(*p)->ancount) + c);
+        DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q));
 
+        *ret = TAKE_PTR(p);
+        return 0;
+}
+
+static int dns_stub_add_reply_packet_body(
+                DnsPacket *p,
+                DnsAnswer *answer,
+                DnsAnswer *authoritative,
+                DnsAnswer *additional,
+                bool edns0_do, /* Client expects DNSSEC RRs? */
+                bool *truncated) {
+
+        unsigned n_answer = 0, n_authoritative = 0, n_additional = 0;
+        bool tc = false;
+        int r;
+
+        assert(p);
+
+        /* Add the three sections to the packet. If the answer section doesn't fit we'll signal that as
+         * truncation. If the authoritative section doesn't fit and we are in DNSSEC mode, also signal
+         * truncation. In all other cases where things don't fit don't signal truncation, as for those cases
+         * the dropped RRs should not be essential. */
+
+        r = dns_packet_append_answer(p, answer, &n_answer);
+        if (r == -EMSGSIZE)
+                tc = true;
+        else if (r < 0)
+                return r;
+        else {
+                r = dns_packet_append_answer(p, authoritative, &n_authoritative);
+                if (r == -EMSGSIZE) {
+                        if (edns0_do)
+                                tc = true;
+                } else if (r < 0)
+                        return r;
+                else {
+                        r = dns_packet_append_answer(p, additional, &n_additional);
+                        if (r < 0 && r != -EMSGSIZE)
+                                return r;
+                }
+        }
+
+        if (tc) {
+                if (!truncated)
+                        return -EMSGSIZE;
+
+                *truncated = true;
+        }
+
+        DNS_PACKET_HEADER(p)->ancount = htobe16(n_answer);
+        DNS_PACKET_HEADER(p)->nscount = htobe16(n_authoritative);
+        DNS_PACKET_HEADER(p)->arcount = htobe16(n_additional);
         return 0;
 }
 
@@ -159,6 +349,7 @@ static int dns_stub_finish_reply_packet(
                 bool add_opt,   /* add an OPT RR to this packet? */
                 bool edns0_do,  /* set the EDNS0 DNSSEC OK bit? */
                 bool ad,        /* set the DNSSEC authenticated data bit? */
+                bool cd,        /* set the DNSSEC checking disabled bit? */
                 uint16_t max_udp_size) { /* The maximum UDP datagram size to advertise to clients */
 
         int r;
@@ -171,19 +362,21 @@ static int dns_stub_finish_reply_packet(
                         tc = true;
                 else if (r < 0)
                         return r;
-
         } else {
                 /* If the client can't to EDNS0, don't do DO either */
                 edns0_do = false;
 
-                /* If the client didn't do EDNS, clamp the rcode to 4 bit */
+                /* If we don't do EDNS, clamp the rcode to 4 bit */
                 if (rcode > 0xF)
                         rcode = DNS_RCODE_SERVFAIL;
         }
 
-        /* Don't set the AD bit unless DO is on, too */
-        if (!edns0_do)
+        /* Don't set the AD or CD bit unless DO is on, too */
+        if (!edns0_do) {
                 ad = false;
+                cd = false;
+
+        }
 
         DNS_PACKET_HEADER(p)->id = id;
 
@@ -195,7 +388,7 @@ static int dns_stub_finish_reply_packet(
                                                               1  /* rd */,
                                                               1  /* ra */,
                                                               ad /* ad */,
-                                                               /* cd */,
+                                                              cd /* cd */,
                                                               rcode));
 
         return 0;
@@ -231,6 +424,66 @@ static int dns_stub_send(
         return 0;
 }
 
+static int dns_stub_send_reply(
+                DnsQuery *q,
+                int rcode) {
+
+        _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+        bool truncated, edns0_do;
+        int r;
+
+        assert(q);
+
+        /* Reply with DNSSEC DO set? Only if client supports it; and we did any DNSSEC verification
+         * ourselves, or consider the data fully authenticated because we generated it locally, or
+         * the client set cd */
+        edns0_do =
+                DNS_PACKET_DO(q->request_packet) &&
+                (q->answer_dnssec_result >= 0 ||        /* we did proper DNSSEC validation … */
+                 dns_query_fully_authenticated(q) ||    /* … or we considered it authentic otherwise … */
+                 DNS_PACKET_CD(q->request_packet));     /* … or client set CD */
+
+        r = dns_stub_assign_sections(
+                        q,
+                        q->request_packet->question,
+                        edns0_do);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to assign sections: %m");
+
+        r = dns_stub_make_reply_packet(
+                        &reply,
+                        DNS_PACKET_PAYLOAD_SIZE_MAX(q->request_packet),
+                        q->request_packet->question,
+                        &truncated);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to build reply packet: %m");
+
+        r = dns_stub_add_reply_packet_body(
+                        reply,
+                        q->reply_answer,
+                        q->reply_authoritative,
+                        q->reply_additional,
+                        edns0_do,
+                        &truncated);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to append reply packet body: %m");
+
+        r = dns_stub_finish_reply_packet(
+                        reply,
+                        DNS_PACKET_ID(q->request_packet),
+                        rcode,
+                        truncated,
+                        !!q->request_packet->opt,
+                        edns0_do,
+                        dns_query_fully_authenticated(q),
+                        DNS_PACKET_CD(q->request_packet),
+                        q->stub_listener_extra ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to build failure packet: %m");
+
+        return dns_stub_send(q->manager, q->stub_listener_extra, q->request_stream, q->request_packet, reply);
+}
+
 static int dns_stub_send_failure(
                 Manager *m,
                 DnsStubListenerExtra *l,
@@ -240,12 +493,17 @@ static int dns_stub_send_failure(
                 bool authenticated) {
 
         _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+        bool truncated;
         int r;
 
         assert(m);
         assert(p);
 
-        r = dns_stub_make_reply_packet(&reply, DNS_PACKET_PAYLOAD_SIZE_MAX(p), p->question, NULL, NULL);
+        r = dns_stub_make_reply_packet(
+                        &reply,
+                        DNS_PACKET_PAYLOAD_SIZE_MAX(p),
+                        p->question,
+                        &truncated);
         if (r < 0)
                 return log_debug_errno(r, "Failed to make failure packet: %m");
 
@@ -253,10 +511,11 @@ static int dns_stub_send_failure(
                         reply,
                         DNS_PACKET_ID(p),
                         rcode,
-                        /* truncated = */ false,
+                        truncated,
                         !!p->opt,
                         DNS_PACKET_DO(p),
                         authenticated,
+                        DNS_PACKET_CD(p),
                         l ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX);
         if (r < 0)
                 return log_debug_errno(r, "Failed to build failure packet: %m");
@@ -264,27 +523,87 @@ static int dns_stub_send_failure(
         return dns_stub_send(m, l, s, p, reply);
 }
 
+static int dns_stub_patch_bypass_reply_packet(
+                DnsPacket **ret,       /* Where to place the patched packet */
+                DnsPacket *original,   /* The packet to patch */
+                DnsPacket *request) {  /* The packet the patched packet shall look like a reply to */
+        _cleanup_(dns_packet_unrefp) DnsPacket *c = NULL;
+        int r;
+
+        assert(ret);
+        assert(original);
+        assert(request);
+
+        r = dns_packet_dup(&c, original);
+        if (r < 0)
+                return r;
+
+        /* Extract the packet, so that we know where the OPT field is */
+        r = dns_packet_extract(c);
+        if (r < 0)
+                return r;
+
+        /* Copy over the original client request ID, so that we can make the upstream query look like our own reply. */
+        DNS_PACKET_HEADER(c)->id = DNS_PACKET_HEADER(request)->id;
+
+        /* Patch in our own maximum datagram size, if EDNS0 was on */
+        r = dns_packet_patch_max_udp_size(c, ADVERTISE_DATAGRAM_SIZE_MAX);
+        if (r < 0)
+                return r;
+
+        /* Lower all TTLs by the time passed since we received the datagram. */
+        if (timestamp_is_set(original->timestamp)) {
+                r = dns_packet_patch_ttls(c, original->timestamp);
+                if (r < 0)
+                        return r;
+        }
+
+        /* Our upstream connection might have supported larger DNS requests than our downstream one, hence
+         * set the TC bit if our reply is larger than what the client supports, and truncate. */
+        if (c->size > DNS_PACKET_PAYLOAD_SIZE_MAX(request)) {
+                log_debug("Artificially truncating stub response, as advertised size of client is smaller than upstream one.");
+                dns_packet_truncate(c, DNS_PACKET_PAYLOAD_SIZE_MAX(request));
+                DNS_PACKET_HEADER(c)->flags = htobe16(be16toh(DNS_PACKET_HEADER(c)->flags) | DNS_PACKET_FLAG_TC);
+        }
+
+        *ret = TAKE_PTR(c);
+        return 0;
+}
+
 static void dns_stub_query_complete(DnsQuery *q) {
         int r;
 
         assert(q);
-        assert(q->request_dns_packet);
+        assert(q->request_packet);
 
-        switch (q->state) {
+        if (q->question_bypass) {
+                /* This is a bypass reply. If so, let's propagate the upstream packet, if we have it and it
+                 * is regular DNS. (We can't do this if the upstream packet is LLMNR or mDNS, since the
+                 * packets are not 100% compatible.) */
 
-        case DNS_TRANSACTION_SUCCESS: {
-                bool truncated;
+                if (q->answer_full_packet &&
+                    q->answer_full_packet->protocol == DNS_PROTOCOL_DNS) {
+                        _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
 
-                r = dns_stub_make_reply_packet(&q->reply_dns_packet, DNS_PACKET_PAYLOAD_SIZE_MAX(q->request_dns_packet), q->question_idna, q->answer, &truncated);
-                if (r < 0) {
-                        log_debug_errno(r, "Failed to build reply packet: %m");
-                        break;
+                        r = dns_stub_patch_bypass_reply_packet(&reply, q->answer_full_packet, q->request_packet);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to patch bypass reply packet: %m");
+                        else
+                                (void) dns_stub_send(q->manager, q->stub_listener_extra, q->request_stream, q->request_packet, reply);
+
+                        dns_query_free(q);
+                        return;
                 }
+        }
+
+        switch (q->state) {
 
-                if (!truncated) {
+        case DNS_TRANSACTION_SUCCESS:
+                /* Follow CNAMEs, and accumulate answers. Except if DNSSEC is requested, then let the client do that. */
+                if (!DNS_PACKET_DO(q->request_packet)) {
                         r = dns_query_process_cname(q);
-                        if (r == -ELOOP) {
-                                (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false);
+                        if (r == -ELOOP) { /* CNAME loop */
+                                (void) dns_stub_send_reply(q, DNS_RCODE_SERVFAIL);
                                 break;
                         }
                         if (r < 0) {
@@ -295,30 +614,15 @@ static void dns_stub_query_complete(DnsQuery *q) {
                                 return;
                 }
 
-                r = dns_stub_finish_reply_packet(
-                                q->reply_dns_packet,
-                                DNS_PACKET_ID(q->request_dns_packet),
-                                q->answer_rcode,
-                                truncated,
-                                !!q->request_dns_packet->opt,
-                                DNS_PACKET_DO(q->request_dns_packet),
-                                dns_query_fully_authenticated(q),
-                                q->stub_listener_extra ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX);
-                if (r < 0) {
-                        log_debug_errno(r, "Failed to finish reply packet: %m");
-                        break;
-                }
-
-                (void) dns_stub_send(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, q->reply_dns_packet);
+                (void) dns_stub_send_reply(q, q->answer_rcode);
                 break;
-        }
 
         case DNS_TRANSACTION_RCODE_FAILURE:
-                (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, q->answer_rcode, dns_query_fully_authenticated(q));
+                (void) dns_stub_send_reply(q, q->answer_rcode);
                 break;
 
         case DNS_TRANSACTION_NOT_FOUND:
-                (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN, dns_query_fully_authenticated(q));
+                (void) dns_stub_send_reply(q, DNS_RCODE_NXDOMAIN);
                 break;
 
         case DNS_TRANSACTION_TIMEOUT:
@@ -334,7 +638,8 @@ static void dns_stub_query_complete(DnsQuery *q) {
         case DNS_TRANSACTION_NO_TRUST_ANCHOR:
         case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED:
         case DNS_TRANSACTION_NETWORK_DOWN:
-                (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false);
+        case DNS_TRANSACTION_NO_SOURCE:
+                (void) dns_stub_send_reply(q, DNS_RCODE_SERVFAIL);
                 break;
 
         case DNS_TRANSACTION_NULL:
@@ -416,23 +721,29 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea
         }
 
         if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) {
-                log_debug("Got request with DNSSEC CD bit set, refusing.");
-                dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false);
-                return;
-        }
-
-        r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH);
+                log_debug("Got request with DNSSEC checking disabled, enabling bypass logic.");
+
+                r = dns_query_new(m, &q, NULL, NULL, p, 0,
+                                  SD_RESOLVED_PROTOCOLS_ALL|
+                                  SD_RESOLVED_NO_CNAME|
+                                  SD_RESOLVED_NO_SEARCH|
+                                  SD_RESOLVED_NO_VALIDATE|
+                                  SD_RESOLVED_REQUIRE_PRIMARY|
+                                  SD_RESOLVED_CLAMP_TTL);
+        } else
+                r = dns_query_new(m, &q, p->question, p->question, NULL, 0,
+                                  SD_RESOLVED_PROTOCOLS_ALL|
+                                  SD_RESOLVED_NO_SEARCH|
+                                  (DNS_PACKET_DO(p) ? SD_RESOLVED_NO_CNAME|SD_RESOLVED_REQUIRE_PRIMARY : 0)|
+                                  SD_RESOLVED_CLAMP_TTL);
         if (r < 0) {
                 log_error_errno(r, "Failed to generate query object: %m");
                 dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false);
                 return;
         }
 
-        /* Request that the TTL is corrected by the cached time for this lookup, so that we return vaguely useful TTLs */
-        q->clamp_ttl = true;
-
-        q->request_dns_packet = dns_packet_ref(p);
-        q->request_dns_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */
+        q->request_packet = dns_packet_ref(p);
+        q->request_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */
         q->stub_listener_extra = l;
         q->complete = dns_stub_query_complete;
 
index 8b3cb7fe1fd8b4c4a854938ce7858bd1c23236bc..a9b9ebcfe173ae219cd412440c377e8445157762 100644 (file)
@@ -88,13 +88,23 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
         dns_server_unref(t->server);
 
         if (t->scope) {
-                hashmap_remove_value(t->scope->transactions_by_key, t->key, t);
-                LIST_REMOVE(transactions_by_scope, t->scope->transactions, t);
+                if (t->key) {
+                        DnsTransaction *first;
+
+                        first = hashmap_get(t->scope->transactions_by_key, t->key);
+                        LIST_REMOVE(transactions_by_key, first, t);
+                        if (first)
+                                hashmap_replace(t->scope->transactions_by_key, first->key, first);
+                        else
+                                hashmap_remove(t->scope->transactions_by_key, t->key);
+                }
 
-                if (t->id != 0)
-                        hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id));
+                LIST_REMOVE(transactions_by_scope, t->scope->transactions, t);
         }
 
+        if (t->id != 0)
+                hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id));
+
         while ((c = set_steal_first(t->notify_query_candidates)))
                 set_remove(c->transactions, t);
         set_free(t->notify_query_candidates);
@@ -124,6 +134,7 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
 
         dns_answer_unref(t->validated_keys);
         dns_resource_key_unref(t->key);
+        dns_packet_unref(t->bypass);
 
         return mfree(t);
 }
@@ -165,13 +176,9 @@ static uint16_t pick_new_id(Manager *m) {
         return new_id;
 }
 
-int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) {
-        _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL;
-        int r;
-
-        assert(ret);
-        assert(s);
-        assert(key);
+static int key_ok(
+                DnsScope *scope,
+                DnsResourceKey *key) {
 
         /* Don't allow looking up invalid or pseudo RRs */
         if (!dns_type_is_valid_query(key->type))
@@ -183,6 +190,49 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
         if (!IN_SET(key->class, DNS_CLASS_IN, DNS_CLASS_ANY))
                 return -EOPNOTSUPP;
 
+        /* Don't allows DNSSEC RRs to be looked up via LLMNR/mDNS. They don't really make sense
+         * there, and it speeds up our queries if we refuse this early */
+        if (scope->protocol != DNS_PROTOCOL_DNS &&
+            dns_type_is_dnssec(key->type))
+                return -EOPNOTSUPP;
+
+        return 0;
+}
+
+int dns_transaction_new(
+                DnsTransaction **ret,
+                DnsScope *s,
+                DnsResourceKey *key,
+                DnsPacket *bypass,
+                uint64_t query_flags) {
+
+        _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL;
+        int r;
+
+        assert(ret);
+        assert(s);
+
+        if (key) {
+                assert(!bypass);
+
+                r = key_ok(s, key);
+                if (r < 0)
+                        return r;
+        } else {
+                DnsResourceKey *qk;
+                assert(bypass);
+
+                r = dns_packet_validate_query(bypass);
+                if (r < 0)
+                        return r;
+
+                DNS_QUESTION_FOREACH(qk, bypass->question) {
+                        r = key_ok(s, qk);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
         if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX)
                 return -EBUSY;
 
@@ -190,9 +240,11 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
         if (r < 0)
                 return r;
 
-        r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops);
-        if (r < 0)
-                return r;
+        if (key) {
+                r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops);
+                if (r < 0)
+                        return r;
+        }
 
         t = new(DnsTransaction, 1);
         if (!t)
@@ -204,6 +256,8 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
                 .answer_dnssec_result = _DNSSEC_RESULT_INVALID,
                 .answer_nsec_ttl = (uint32_t) -1,
                 .key = dns_resource_key_ref(key),
+                .query_flags = query_flags,
+                .bypass = dns_packet_ref(bypass),
                 .current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID,
                 .clamp_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID,
                 .id = pick_new_id(s->manager),
@@ -215,10 +269,17 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
                 return r;
         }
 
-        r = hashmap_replace(s->transactions_by_key, t->key, t);
-        if (r < 0) {
-                hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id));
-                return r;
+        if (t->key) {
+                DnsTransaction *first;
+
+                first = hashmap_get(s->transactions_by_key, t->key);
+                LIST_PREPEND(transactions_by_key, first, t);
+
+                r = hashmap_replace(s->transactions_by_key, first->key, first);
+                if (r < 0) {
+                        LIST_REMOVE(transactions_by_key, first, t);
+                        return r;
+                }
         }
 
         LIST_PREPEND(transactions_by_scope, s->transactions, t);
@@ -229,8 +290,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
         if (ret)
                 *ret = t;
 
-        t = NULL;
-
+        TAKE_PTR(t);
         return 0;
 }
 
@@ -328,7 +388,8 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
         else
                 st = dns_transaction_state_to_string(state);
 
-        log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).",
+        log_debug("%s transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).",
+                  t->bypass ? "Bypass" : "Regular",
                   t->id,
                   dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str),
                   dns_protocol_to_string(t->scope->protocol),
@@ -336,7 +397,8 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
                   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");
+                  FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) ? "not validated" :
+                  (t->answer_authenticated ? "authenticated" : "unsigned"));
 
         t->state = state;
 
@@ -550,8 +612,11 @@ static int on_stream_packet(DnsStream *s) {
 }
 
 static uint16_t dns_transaction_port(DnsTransaction *t) {
+        assert(t);
+
         if (t->server->port > 0)
                 return t->server->port;
+
         return DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level) ? 853 : 53;
 }
 
@@ -563,6 +628,7 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) {
         int r;
 
         assert(t);
+        assert(t->sent);
 
         dns_transaction_close_connection(t);
 
@@ -573,12 +639,14 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) {
                 if (r < 0)
                         return r;
 
-                if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(dns_transaction_key(t)->type))
-                        return -EOPNOTSUPP;
+                if (!t->bypass) {
+                        if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(dns_transaction_key(t)->type))
+                                return -EOPNOTSUPP;
 
-                r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level);
-                if (r < 0)
-                        return r;
+                        r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level);
+                        if (r < 0)
+                                return r;
+                }
 
                 if (t->server->stream && (DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level) == t->server->stream->encrypted))
                         s = dns_stream_ref(t->server->stream);
@@ -682,6 +750,10 @@ static void dns_transaction_cache_answer(DnsTransaction *t) {
         if (t->scope->manager->enable_cache == DNS_CACHE_MODE_NO)
                 return;
 
+        /* If validation is turned off for this transaction, but DNSSEC is on, then let's not cache this */
+        if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) && t->scope->dnssec_mode != DNSSEC_NO)
+                return;
+
         /* Packet from localhost? */
         if (!t->scope->manager->cache_from_localhost &&
             in_addr_is_localhost(t->received->family, &t->received->sender) != 0)
@@ -692,7 +764,12 @@ static void dns_transaction_cache_answer(DnsTransaction *t) {
                       dns_transaction_key(t),
                       t->answer_rcode,
                       t->answer,
+                      DNS_PACKET_CD(t->received) ? t->received : NULL, /* only cache full packets with CD on,
+                                                                        * since our usecase for caching them
+                                                                        * is "bypass" mode which is only
+                                                                        * enabled for CD packets. */
                       t->answer_authenticated,
+                      t->answer_dnssec_result,
                       t->answer_nsec_ttl,
                       0,
                       t->received->family,
@@ -977,7 +1054,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
         case DNS_PROTOCOL_DNS:
                 assert(t->server);
 
-                if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
+                if (!t->bypass &&
+                    IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
 
                         /* Request failed, immediately try again with reduced features */
 
@@ -1121,7 +1199,10 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
                 }
         }
 
-        /* Install the answer as answer to the transaction */
+        /* Install the answer as answer to the transaction. We ref the answer twice here: the main `answer`
+         * field is later replaced by the DNSSEC validated subset. The 'answer_auxiliary' field carries the
+         * original complete record set, including RRSIG and friends. We use this when passing data to
+         * clients that ask for DNSSEC metadata. */
         dns_answer_unref(t->answer);
         t->answer = dns_answer_ref(p->answer);
         t->answer_rcode = DNS_PACKET_RCODE(p);
@@ -1226,7 +1307,7 @@ static int dns_transaction_emit_udp(DnsTransaction *t) {
                 if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP || DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level))
                         return -EAGAIN; /* Sorry, can't do UDP, try TCP! */
 
-                if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(dns_transaction_key(t)->type))
+                if (!t->bypass && !dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(dns_transaction_key(t)->type))
                         return -EOPNOTSUPP;
 
                 if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */
@@ -1248,9 +1329,11 @@ static int dns_transaction_emit_udp(DnsTransaction *t) {
                         t->dns_udp_fd = fd;
                 }
 
-                r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level);
-                if (r < 0)
-                        return r;
+                if (!t->bypass) {
+                        r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level);
+                        if (r < 0)
+                                return r;
+                }
         } else
                 dns_transaction_close_connection(t);
 
@@ -1372,7 +1455,8 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
         dns_transaction_flush_dnssec_transactions(t);
 
         /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */
-        if (t->scope->protocol == DNS_PROTOCOL_DNS) {
+        if (t->scope->protocol == DNS_PROTOCOL_DNS &&
+            !FLAGS_SET(t->query_flags, SD_RESOLVED_NO_TRUST_ANCHOR)) {
                 r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, dns_transaction_key(t), &t->answer);
                 if (r < 0)
                         return r;
@@ -1387,42 +1471,32 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
                 if (dns_name_is_root(dns_resource_key_name(dns_transaction_key(t))) &&
                     dns_transaction_key(t)->type == DNS_TYPE_DS) {
 
-                        /* Hmm, this is a request for the root DS? A
-                         * DS RR doesn't exist in the root zone, and
-                         * if our trust anchor didn't know it either,
-                         * this means we cannot do any DNSSEC logic
-                         * anymore. */
+                        /* Hmm, this is a request for the root DS? A DS RR doesn't exist in the root zone,
+                         * and if our trust anchor didn't know it either, this means we cannot do any DNSSEC
+                         * logic anymore. */
 
                         if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) {
-                                /* We are in downgrade mode. In this
-                                 * case, synthesize an unsigned empty
-                                 * response, so that the any lookup
-                                 * depending on this one can continue
-                                 * assuming there was no DS, and hence
-                                 * the root zone was unsigned. */
+                                /* We are in downgrade mode. In this case, synthesize an unsigned empty
+                                 * response, so that the any lookup depending on this one can continue
+                                 * assuming there was no DS, and hence the root zone was unsigned. */
 
                                 t->answer_rcode = DNS_RCODE_SUCCESS;
                                 t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR;
                                 t->answer_authenticated = false;
                                 dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
                         } else
-                                /* If we are not in downgrade mode,
-                                 * then fail the lookup, because we
-                                 * cannot reasonably answer it. There
-                                 * 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 and trust them. */
+                                /* If we are not in downgrade mode, then fail the lookup, because we cannot
+                                 * reasonably answer it. There 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 and trust them. */
                                 dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR);
 
                         return 0;
                 }
         }
 
-        /* Check the zone, but only if this transaction is not used
-         * for probing or verifying a zone item. */
-        if (set_isempty(t->notify_zone_items)) {
-
+        /* Check the zone. */
+        if (!FLAGS_SET(t->query_flags, SD_RESOLVED_NO_ZONE)) {
                 r = dns_zone_lookup(&t->scope->zone, dns_transaction_key(t), dns_scope_ifindex(t->scope), &t->answer, NULL, NULL);
                 if (r < 0)
                         return r;
@@ -1435,31 +1509,48 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
                 }
         }
 
-        /* Check the cache, but only if this transaction is not used
-         * for probing or verifying a zone item. */
-        if (set_isempty(t->notify_zone_items)) {
+        /* Check the cache. */
+        if (!FLAGS_SET(t->query_flags, SD_RESOLVED_NO_CACHE)) {
 
-                /* Before trying the cache, let's make sure we figured out a
-                 * server to use. Should this cause a change of server this
-                 * might flush the cache. */
+                /* Before trying the cache, let's make sure we figured out a server to use. Should this cause
+                 * a change of server this might flush the cache. */
                 (void) dns_scope_get_dns_server(t->scope);
 
                 /* Let's then prune all outdated entries */
                 dns_cache_prune(&t->scope->cache);
 
-                r = dns_cache_lookup(&t->scope->cache, dns_transaction_key(t), t->clamp_ttl, &t->answer_rcode, &t->answer, &t->answer_authenticated);
+                r = dns_cache_lookup(
+                                &t->scope->cache,
+                                dns_transaction_key(t),
+                                t->query_flags,
+                                &t->answer_rcode,
+                                &t->answer,
+                                &t->received,
+                                &t->answer_authenticated,
+                                &t->answer_dnssec_result);
                 if (r < 0)
                         return r;
                 if (r > 0) {
-                        t->answer_source = DNS_TRANSACTION_CACHE;
-                        if (t->answer_rcode == DNS_RCODE_SUCCESS)
-                                dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
-                        else
-                                dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE);
-                        return 0;
+                        if (t->bypass && t->scope->protocol == DNS_PROTOCOL_DNS && !t->received)
+                                /* When bypass mode is on, do not use cached data unless it came with a full
+                                 * packet. */
+                                dns_transaction_reset_answer(t);
+                        else {
+                                t->answer_source = DNS_TRANSACTION_CACHE;
+                                if (t->answer_rcode == DNS_RCODE_SUCCESS)
+                                        dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
+                                else
+                                        dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE);
+                                return 0;
+                        }
                 }
         }
 
+        if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_NETWORK)) {
+                dns_transaction_complete(t, DNS_TRANSACTION_NO_SOURCE);
+                return 0;
+        }
+
         return 1;
 }
 
@@ -1606,19 +1697,31 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
         if (t->sent)
                 return 0;
 
-        r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO);
-        if (r < 0)
-                return r;
+        if (t->bypass && t->bypass->protocol == t->scope->protocol) {
+                /* If bypass logic is enabled and the protocol if the original packet and our scope match,
+                 * take the original packet, copy it, and patch in our new ID */
+                r = dns_packet_dup(&p, t->bypass);
+                if (r < 0)
+                        return r;
+        } else {
+                r = dns_packet_new_query(
+                                &p, t->scope->protocol,
+                                /* min_alloc_dsize = */ 0,
+                                /* dnssec_cd = */ !FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) &&
+                                                  t->scope->dnssec_mode != DNSSEC_NO);
+                if (r < 0)
+                        return r;
 
-        r = dns_packet_append_key(p, dns_transaction_key(t), 0, NULL);
-        if (r < 0)
-                return r;
+                r = dns_packet_append_key(p, dns_transaction_key(t), 0, NULL);
+                if (r < 0)
+                        return r;
+
+                DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
+        }
 
-        DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
         DNS_PACKET_HEADER(p)->id = t->id;
 
         t->sent = TAKE_PTR(p);
-
         return 0;
 }
 
@@ -1639,12 +1742,14 @@ int dns_transaction_go(DnsTransaction *t) {
         if (r <= 0)
                 return r;
 
-        log_debug("Transaction %" PRIu16 " for <%s> scope %s on %s/%s.",
+        log_debug("%s transaction %" PRIu16 " for <%s> scope %s on %s/%s (validate=%s).",
+                  t->bypass ? "Bypass" : "Regular",
                   t->id,
                   dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str),
                   dns_protocol_to_string(t->scope->protocol),
                   t->scope->link ? t->scope->link->ifname : "*",
-                  af_to_name_short(t->scope->family));
+                  af_to_name_short(t->scope->family),
+                  yes_no(!FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE)));
 
         if (!t->initial_jitter_scheduled &&
             IN_SET(t->scope->protocol, DNS_PROTOCOL_LLMNR, DNS_PROTOCOL_MDNS)) {
@@ -1790,9 +1895,9 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource
         assert(ret);
         assert(key);
 
-        aux = dns_scope_find_transaction(t->scope, key, true);
+        aux = dns_scope_find_transaction(t->scope, key, t->query_flags);
         if (!aux) {
-                r = dns_transaction_new(&aux, t->scope, key);
+                r = dns_transaction_new(&aux, t->scope, key, NULL, t->query_flags);
                 if (r < 0)
                         return r;
         } else {
@@ -2002,7 +2107,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
          * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR
          */
 
-        if (t->scope->dnssec_mode == DNSSEC_NO)
+        if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) || t->scope->dnssec_mode == DNSSEC_NO)
                 return 0;
         if (t->answer_source != DNS_TRANSACTION_NETWORK)
                 return 0; /* We only need to validate stuff from the network */
@@ -3052,11 +3157,10 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
 
         assert(t);
 
-        /* We have now collected all DS and DNSKEY RRs in
-         * t->validated_keys, let's see which RRs we can now
+        /* We have now collected all DS and DNSKEY RRs in t->validated_keys, let's see which RRs we can now
          * authenticate with that. */
 
-        if (t->scope->dnssec_mode == DNSSEC_NO)
+        if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) || t->scope->dnssec_mode == DNSSEC_NO)
                 return 0;
 
         /* Already validated */
@@ -3256,6 +3360,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX]
         [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported",
         [DNS_TRANSACTION_NETWORK_DOWN] = "network-down",
         [DNS_TRANSACTION_NOT_FOUND] = "not-found",
+        [DNS_TRANSACTION_NO_SOURCE] = "no-source",
 };
 DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState);
 
index 9a3b54061c03d22f6523c9448d3878ce8a584921..93ff6b3d58495b17e0eb1b3cac318984f9f5a4c2 100644 (file)
@@ -5,6 +5,7 @@
 #include "in-addr-util.h"
 
 typedef struct DnsTransaction DnsTransaction;
+typedef struct DnsTransactionFinder DnsTransactionFinder;
 typedef enum DnsTransactionState DnsTransactionState;
 typedef enum DnsTransactionSource DnsTransactionSource;
 
@@ -31,6 +32,7 @@ enum DnsTransactionState {
         DNS_TRANSACTION_RR_TYPE_UNSUPPORTED,
         DNS_TRANSACTION_NETWORK_DOWN,
         DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */
+        DNS_TRANSACTION_NO_SOURCE, /* All suitable DnsTransactionSource turned off */
         _DNS_TRANSACTION_STATE_MAX,
         _DNS_TRANSACTION_STATE_INVALID = -1
 };
@@ -49,7 +51,10 @@ enum DnsTransactionSource {
 struct DnsTransaction {
         DnsScope *scope;
 
-        DnsResourceKey *key;
+        DnsResourceKey *key;         /* For regular lookups the RR key to look for */
+        DnsPacket *bypass;           /* For bypass lookups the full original request packet */
+
+        uint64_t query_flags;
 
         DnsTransactionState state;
 
@@ -60,8 +65,6 @@ struct DnsTransaction {
         bool initial_jitter_scheduled:1;
         bool initial_jitter_elapsed:1;
 
-        bool clamp_ttl:1;
-
         bool probing:1;
 
         DnsPacket *sent, *received;
@@ -133,9 +136,10 @@ struct DnsTransaction {
 
         LIST_FIELDS(DnsTransaction, transactions_by_scope);
         LIST_FIELDS(DnsTransaction, transactions_by_stream);
+        LIST_FIELDS(DnsTransaction, transactions_by_key);
 };
 
-int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key);
+int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key, DnsPacket *bypass, uint64_t flags);
 DnsTransaction* dns_transaction_free(DnsTransaction *t);
 
 bool dns_transaction_gc(DnsTransaction *t);
@@ -153,7 +157,18 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t);
 static inline DnsResourceKey *dns_transaction_key(DnsTransaction *t) {
         assert(t);
 
-        return t->key;
+        /* Return the lookup key of this transaction. Either takes the lookup key from the bypass packet if
+         * we are a bypass transaction. Or take the configured key for regular transactions. */
+
+        if (t->key)
+                return t->key;
+
+        assert(t->bypass);
+
+        if (dns_question_isempty(t->bypass->question))
+                return NULL;
+
+        return t->bypass->question->keys[0];
 }
 
 const char* dns_transaction_state_to_string(DnsTransactionState p) _const_;
index b8d3ee3941a70674c77aa8bf6b30c1eb97630343..4c5e1fe3727308616abaa4fec69bde9f9f9f870f 100644 (file)
@@ -170,7 +170,10 @@ static int dns_zone_item_probe_start(DnsZoneItem *i)  {
         if (i->probe_transaction)
                 return 0;
 
-        t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false);
+        t = dns_scope_find_transaction(
+                        i->scope,
+                        &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)),
+                        SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
         if (!t) {
                 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
 
@@ -178,7 +181,7 @@ static int dns_zone_item_probe_start(DnsZoneItem *i)  {
                 if (!key)
                         return -ENOMEM;
 
-                r = dns_transaction_new(&t, i->scope, key);
+                r = dns_transaction_new(&t, i->scope, key, NULL, SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
                 if (r < 0)
                         return r;
         }
index a09374719892a26fb575d2b05d3cd790cf18ec3e..9f570153261d5a7f6c2ab8cb796930dae43f9025 100644 (file)
@@ -301,25 +301,25 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us
                                 rr->ttl = 1;
                         }
 
-                        t = dns_scope_find_transaction(scope, rr->key, false);
+                        t = dns_scope_find_transaction(scope, rr->key, SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
                         if (t)
                                 dns_transaction_process_reply(t, p);
 
                         /* Also look for the various types of ANY transactions */
-                        t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false);
+                        t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
                         if (t)
                                 dns_transaction_process_reply(t, p);
 
-                        t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), false);
+                        t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
                         if (t)
                                 dns_transaction_process_reply(t, p);
 
-                        t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false);
+                        t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
                         if (t)
                                 dns_transaction_process_reply(t, p);
                 }
 
-                dns_cache_put(&scope->cache, scope->manager->enable_cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender);
+                dns_cache_put(&scope->cache, scope->manager->enable_cache, NULL, DNS_PACKET_RCODE(p), p->answer, NULL, 0, _DNSSEC_RESULT_INVALID, (uint32_t) -1, 0, p->family, &p->sender);
 
         } else if (dns_packet_validate_query(p) > 0)  {
                 log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));
index 9a8a4f3013f6e7717849d1aff842a0b6ebfcac0d..f948206e478d48c999cdd07a7f5a5378d8fd6a72 100644 (file)
@@ -57,6 +57,9 @@ static int reply_query_state(DnsQuery *q) {
         case DNS_TRANSACTION_NETWORK_DOWN:
                 return varlink_error(q->varlink_request, "io.systemd.Resolve.NetworkDown", NULL);
 
+        case DNS_TRANSACTION_NO_SOURCE:
+                return varlink_error(q->varlink_request, "io.systemd.Resolve.NoSource", NULL);
+
         case DNS_TRANSACTION_NOT_FOUND:
                 /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we
                  * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */
@@ -103,7 +106,7 @@ static bool validate_and_mangle_flags(
         /* This checks that the specified client-provided flags parameter actually makes sense, and mangles
          * it slightly. Specifically:
          *
-         * 1. We check that only the protocol flags and the NO_CNAME flag are on at most, plus the
+         * 1. We check that only the protocol flags and a bunch of NO_XYZ flags are on at most, plus the
          *    method-specific flags specified in 'ok'.
          *
          * 2. If no protocols are enabled we automatically convert that to "all protocols are enabled".
@@ -114,7 +117,15 @@ static bool validate_and_mangle_flags(
          * "everything".
          */
 
-        if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok))
+        if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|
+                       SD_RESOLVED_NO_CNAME|
+                       SD_RESOLVED_NO_VALIDATE|
+                       SD_RESOLVED_NO_SYNTHESIZE|
+                       SD_RESOLVED_NO_CACHE|
+                       SD_RESOLVED_NO_ZONE|
+                       SD_RESOLVED_NO_TRUST_ANCHOR|
+                       SD_RESOLVED_NO_NETWORK|
+                       ok))
                 return false;
 
         if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
@@ -312,7 +323,7 @@ static int vl_method_resolve_hostname(Varlink *link, JsonVariant *parameters, Va
         if (r < 0 && r != -EALREADY)
                 return r;
 
-        r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, p.ifindex, p.flags);
+        r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, p.ifindex, p.flags);
         if (r < 0)
                 return r;
 
@@ -481,7 +492,7 @@ static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, Var
         if (r < 0)
                 return r;
 
-        r = dns_query_new(m, &q, question, question, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH);
+        r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH);
         if (r < 0)
                 return r;