]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-packet.c
resolved: add DNS_RR_REPLACE
[thirdparty/systemd.git] / src / resolve / resolved-dns-packet.c
index 59be55a0eb027747f0cc487439c9d17f03d750c8..d6fb4880b0c966c1ce59b93e09510f7495f2cb5c 100644 (file)
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #if HAVE_GCRYPT
-#include <gcrypt.h>
+#  include <gcrypt.h>
 #endif
 
 #include "alloc-util.h"
@@ -29,8 +29,11 @@ static void rewind_dns_packet(DnsPacketRewinder *rewinder) {
                 dns_packet_rewind(rewinder->packet, rewinder->saved_rindex);
 }
 
-#define INIT_REWINDER(rewinder, p) do { rewinder.packet = p; rewinder.saved_rindex = p->rindex; } while (0)
-#define CANCEL_REWINDER(rewinder) do { rewinder.packet = NULL; } while (0)
+#define REWINDER_INIT(p) {                              \
+                .packet = (p),                          \
+                .saved_rindex = (p)->rindex,            \
+        }
+#define CANCEL_REWINDER(rewinder) do { (rewinder).packet = NULL; } while (0)
 
 int dns_packet_new(
                 DnsPacket **ret,
@@ -82,8 +85,8 @@ int dns_packet_new(
                 .rindex = DNS_PACKET_HEADER_SIZE,
                 .allocated = a,
                 .max_size = max_size,
-                .opt_start = (size_t) -1,
-                .opt_size = (size_t) -1,
+                .opt_start = SIZE_MAX,
+                .opt_size = SIZE_MAX,
         };
 
         *ret = p;
@@ -99,7 +102,7 @@ void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool trun
 
         h = DNS_PACKET_HEADER(p);
 
-        switch(p->protocol) {
+        switch (p->protocol) {
         case DNS_PROTOCOL_LLMNR:
                 assert(!truncated);
 
@@ -182,8 +185,8 @@ int dns_packet_dup(DnsPacket **ret, DnsPacket *p) {
                 .rindex = DNS_PACKET_HEADER_SIZE,
                 .allocated = p->size,
                 .max_size = p->max_size,
-                .opt_start = (size_t) -1,
-                .opt_size = (size_t) -1,
+                .opt_start = SIZE_MAX,
+                .opt_size = SIZE_MAX,
         };
 
         memcpy(DNS_PACKET_DATA(c), DNS_PACKET_DATA(p), p->size);
@@ -326,13 +329,10 @@ int dns_packet_validate_query(DnsPacket *p) {
                 break;
 
         case DNS_PROTOCOL_MDNS:
-                /* RFC 6762, Section 18 */
-                if (DNS_PACKET_AA(p)    != 0 ||
-                    DNS_PACKET_RD(p)    != 0 ||
-                    DNS_PACKET_RA(p)    != 0 ||
-                    DNS_PACKET_AD(p)    != 0 ||
-                    DNS_PACKET_CD(p)    != 0 ||
-                    DNS_PACKET_RCODE(p) != 0)
+                /* RFC 6762, Section 18 specifies that messages with non-zero RCODE
+                 * must be silently ignored, and that we must ignore the values of
+                 * AA, RD, RA, AD, and CD bits. */
+                if (DNS_PACKET_RCODE(p) != 0)
                         return -EBADMSG;
 
                 break;
@@ -518,15 +518,12 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonica
 
         *(w++) = (uint8_t) l;
 
-        if (p->canonical_form && canonical_candidate) {
-                size_t i;
-
+        if (p->canonical_form && canonical_candidate)
                 /* Generate in canonical form, as defined by DNSSEC
                  * RFC 4034, Section 6.2, i.e. all lower-case. */
-
-                for (i = 0; i < l; i++)
+                for (size_t i = 0; i < l; i++)
                         w[i] = (uint8_t) ascii_tolower(d[i]);
-        else
+        else
                 /* Otherwise, just copy the string unaltered. This is
                  * essential for DNS-SD, where the casing of labels
                  * matters and needs to be retained. */
@@ -630,7 +627,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, const DnsAnswer
         if (r < 0)
                 goto fail;
 
-        class = flags & DNS_ANSWER_CACHE_FLUSH ? k->class | MDNS_RR_CACHE_FLUSH : k->class;
+        class = flags & DNS_ANSWER_CACHE_FLUSH ? k->class | MDNS_RR_CACHE_FLUSH_OR_QU : k->class;
         r = dns_packet_append_uint16(p, class, NULL);
         if (r < 0)
                 goto fail;
@@ -726,8 +723,9 @@ int dns_packet_append_opt(
                 uint16_t max_udp_size,
                 bool edns0_do,
                 bool include_rfc6975,
+                const char *nsid,
                 int rcode,
-                size_t *start) {
+                size_t *ret_start) {
 
         size_t saved_size;
         int r;
@@ -738,10 +736,10 @@ int dns_packet_append_opt(
         assert(rcode >= 0);
         assert(rcode <= _DNS_RCODE_MAX);
 
-        if (p->opt_start != (size_t) -1)
+        if (p->opt_start != SIZE_MAX)
                 return -EBUSY;
 
-        assert(p->opt_size == (size_t) -1);
+        assert(p->opt_size == SIZE_MAX);
 
         saved_size = p->size;
 
@@ -770,7 +768,6 @@ int dns_packet_append_opt(
         if (r < 0)
                 goto fail;
 
-        /* RDLENGTH */
         if (edns0_do && include_rfc6975) {
                 /* If DO is on and this is requested, also append RFC6975 Algorithm data. This is supposed to
                  * be done on queries, not on replies, hencer callers should turn this off when finishing off
@@ -779,7 +776,7 @@ int dns_packet_append_opt(
                 static const uint8_t rfc6975[] = {
 
                         0, 5, /* OPTION_CODE: DAU */
-#if HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600
+#if PREFER_OPENSSL || (HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600)
                         0, 7, /* LIST_LENGTH */
 #else
                         0, 6, /* LIST_LENGTH */
@@ -790,7 +787,7 @@ int dns_packet_append_opt(
                         DNSSEC_ALGORITHM_RSASHA512,
                         DNSSEC_ALGORITHM_ECDSAP256SHA256,
                         DNSSEC_ALGORITHM_ECDSAP384SHA384,
-#if HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600
+#if PREFER_OPENSSL || (HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600)
                         DNSSEC_ALGORITHM_ED25519,
 #endif
 
@@ -805,11 +802,32 @@ int dns_packet_append_opt(
                         NSEC3_ALGORITHM_SHA1,
                 };
 
-                r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL);
+                r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); /* RDLENGTH */
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); /* the payload, as defined above */
+
+        } else if (nsid) {
+
+                if (strlen(nsid) > UINT16_MAX - 4) {
+                        r = -E2BIG;
+                        goto fail;
+                }
+
+                r = dns_packet_append_uint16(p, 4 + strlen(nsid), NULL); /* RDLENGTH */
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_uint16(p, 3, NULL); /* OPTION-CODE: NSID */
                 if (r < 0)
                         goto fail;
 
-                r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL);
+                r = dns_packet_append_uint16(p, strlen(nsid), NULL); /* OPTION-LENGTH */
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_blob(p, nsid, strlen(nsid), NULL);
         } else
                 r = dns_packet_append_uint16(p, 0, NULL);
         if (r < 0)
@@ -820,8 +838,8 @@ int dns_packet_append_opt(
         p->opt_start = saved_size;
         p->opt_size = p->size - saved_size;
 
-        if (start)
-                *start = saved_size;
+        if (ret_start)
+                *ret_start = saved_size;
 
         return 0;
 
@@ -833,12 +851,12 @@ fail:
 int dns_packet_truncate_opt(DnsPacket *p) {
         assert(p);
 
-        if (p->opt_start == (size_t) -1) {
-                assert(p->opt_size == (size_t) -1);
+        if (p->opt_start == SIZE_MAX) {
+                assert(p->opt_size == SIZE_MAX);
                 return 0;
         }
 
-        assert(p->opt_size != (size_t) -1);
+        assert(p->opt_size != SIZE_MAX);
         assert(DNS_PACKET_ARCOUNT(p) > 0);
 
         if (p->opt_start + p->opt_size != p->size)
@@ -846,7 +864,7 @@ int dns_packet_truncate_opt(DnsPacket *p) {
 
         dns_packet_truncate(p, p->opt_start);
         DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1);
-        p->opt_start = p->opt_size = (size_t) -1;
+        p->opt_start = p->opt_size = SIZE_MAX;
 
         return 1;
 }
@@ -923,15 +941,12 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns
                         r = dns_packet_append_raw_string(p, NULL, 0, NULL);
                         if (r < 0)
                                 goto fail;
-                } else {
-                        DnsTxtItem *i;
-
+                } else
                         LIST_FOREACH(items, i, rr->txt.items) {
                                 r = dns_packet_append_raw_string(p, i->data, i->length, NULL);
                                 if (r < 0)
                                         goto fail;
                         }
-                }
 
                 r = 0;
                 break;
@@ -1355,15 +1370,14 @@ int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) {
 }
 
 int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) {
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
+        assert(p);
+
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
         const void *d;
         char *t;
         uint8_t c;
         int r;
 
-        assert(p);
-        INIT_REWINDER(rewinder, p);
-
         r = dns_packet_read_uint8(p, &c, NULL);
         if (r < 0)
                 return r;
@@ -1394,13 +1408,12 @@ int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) {
 }
 
 int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) {
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
+        assert(p);
+
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
         uint8_t c;
         int r;
 
-        assert(p);
-        INIT_REWINDER(rewinder, p);
-
         r = dns_packet_read_uint8(p, &c, NULL);
         if (r < 0)
                 return r;
@@ -1424,18 +1437,15 @@ int dns_packet_read_name(
                 bool allow_compression,
                 size_t *ret_start) {
 
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
-        size_t after_rindex = 0, jump_barrier;
+        assert(p);
+
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
+        size_t after_rindex = 0, jump_barrier = p->rindex;
         _cleanup_free_ char *name = NULL;
-        size_t n = 0, allocated = 0;
         bool first = true;
+        size_t n = 0;
         int r;
 
-        assert(p);
-
-        INIT_REWINDER(rewinder, p);
-        jump_barrier = p->rindex;
-
         if (p->refuse_compression)
                 allow_compression = false;
 
@@ -1457,7 +1467,7 @@ int dns_packet_read_name(
                         if (r < 0)
                                 return r;
 
-                        if (!GREEDY_REALLOC(name, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                        if (!GREEDY_REALLOC(name, n + !first + DNS_LABEL_ESCAPED_MAX))
                                 return -ENOMEM;
 
                         if (first)
@@ -1493,7 +1503,7 @@ int dns_packet_read_name(
                         return -EBADMSG;
         }
 
-        if (!GREEDY_REALLOC(name, allocated, n + 1))
+        if (!GREEDY_REALLOC(name, n + 1))
                 return -ENOMEM;
 
         name[n] = 0;
@@ -1512,19 +1522,16 @@ int dns_packet_read_name(
 }
 
 static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) {
-        uint8_t window;
-        uint8_t length;
+        assert(p);
+        assert(types);
+
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
+        uint8_t window, length;
         const uint8_t *bitmap;
         uint8_t bit = 0;
-        unsigned i;
         bool found = false;
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
         int r;
 
-        assert(p);
-        assert(types);
-        INIT_REWINDER(rewinder, p);
-
         r = bitmap_ensure_allocated(types);
         if (r < 0)
                 return r;
@@ -1544,7 +1551,7 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
         if (r < 0)
                 return r;
 
-        for (i = 0; i < length; i++) {
+        for (uint8_t i = 0; i < length; i++) {
                 uint8_t bitmask = 1 << 7;
 
                 if (!bitmap[i]) {
@@ -1582,11 +1589,9 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
 }
 
 static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) {
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
         int r;
 
-        INIT_REWINDER(rewinder, p);
-
         while (p->rindex < rewinder.saved_rindex + size) {
                 r = dns_packet_read_type_window(p, types, NULL);
                 if (r < 0)
@@ -1610,18 +1615,17 @@ static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t siz
 int dns_packet_read_key(
                 DnsPacket *p,
                 DnsResourceKey **ret,
-                bool *ret_cache_flush,
+                bool *ret_cache_flush_or_qu,
                 size_t *ret_start) {
 
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
+        assert(p);
+
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
         _cleanup_free_ char *name = NULL;
-        bool cache_flush = false;
+        bool cache_flush_or_qu = false;
         uint16_t class, type;
         int r;
 
-        assert(p);
-        INIT_REWINDER(rewinder, p);
-
         r = dns_packet_read_name(p, &name, true, NULL);
         if (r < 0)
                 return r;
@@ -1635,11 +1639,11 @@ int dns_packet_read_key(
                 return r;
 
         if (p->protocol == DNS_PROTOCOL_MDNS) {
-                /* See RFC6762, Section 10.2 */
+                /* See RFC6762, sections 5.4 and 10.2 */
 
-                if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) {
-                        class &= ~MDNS_RR_CACHE_FLUSH;
-                        cache_flush = true;
+                if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH_OR_QU)) {
+                        class &= ~MDNS_RR_CACHE_FLUSH_OR_QU;
+                        cache_flush_or_qu = true;
                 }
         }
 
@@ -1654,8 +1658,8 @@ int dns_packet_read_key(
                 *ret = key;
         }
 
-        if (ret_cache_flush)
-                *ret_cache_flush = cache_flush;
+        if (ret_cache_flush_or_qu)
+                *ret_cache_flush_or_qu = cache_flush_or_qu;
         if (ret_start)
                 *ret_start = rewinder.saved_rindex;
 
@@ -1675,18 +1679,16 @@ int dns_packet_read_rr(
                 bool *ret_cache_flush,
                 size_t *ret_start) {
 
+        assert(p);
+
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
         _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
         size_t offset;
         uint16_t rdlength;
         bool cache_flush;
         int r;
 
-        assert(p);
-
-        INIT_REWINDER(rewinder, p);
-
         r = dns_packet_read_key(p, &key, &cache_flush, NULL);
         if (r < 0)
                 return r;
@@ -2181,7 +2183,7 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) {
 
 static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) {
         _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
-        unsigned n, i;
+        unsigned n;
         int r;
 
         n = DNS_PACKET_QDCOUNT(p);
@@ -2201,17 +2203,14 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question)
                 if (r < 0)
                         return r;
 
-                for (i = 0; i < n; i++) {
+                for (unsigned i = 0; i < n; i++) {
                         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
-                        bool cache_flush;
+                        bool qu;
 
-                        r = dns_packet_read_key(p, &key, &cache_flush, NULL);
+                        r = dns_packet_read_key(p, &key, &qu, NULL);
                         if (r < 0)
                                 return r;
 
-                        if (cache_flush)
-                                return -EBADMSG;
-
                         if (!dns_type_is_valid_query(key->type))
                                 return -EBADMSG;
 
@@ -2222,7 +2221,7 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question)
                                 /* Already in the Question, let's skip */
                                 continue;
 
-                        r = dns_question_add_raw(question, key);
+                        r = dns_question_add_raw(question, key, qu ? DNS_QUESTION_WANTS_UNICAST_REPLY : 0);
                         if (r < 0)
                                 return r;
                 }
@@ -2235,7 +2234,7 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question)
 
 static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) {
         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
-        unsigned n, i;
+        unsigned n;
         _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL;
         bool bad_opt = false;
         int r;
@@ -2248,11 +2247,23 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) {
         if (!answer)
                 return -ENOMEM;
 
-        for (i = 0; i < n; i++) {
+        for (unsigned i = 0; i < n; i++) {
                 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
                 bool cache_flush = false;
                 size_t start;
 
+                if (p->rindex == p->size && p->opt) {
+                        /* If we reached the end of the packet already, but there are still more RRs
+                         * declared, then that's a corrupt packet. Let's accept the packet anyway, since it's
+                         * apparently a common bug in routers. Let's however suppress OPT support in this
+                         * case, so that we force the rest of the logic into lowest DNS baseline support. Or
+                         * to say this differently: if the DNS server doesn't even get the RR counts right,
+                         * it's highly unlikely it gets EDNS right. */
+                        log_debug("More resource records declared in packet than included, suppressing OPT.");
+                        bad_opt = true;
+                        break;
+                }
+
                 r = dns_packet_read_rr(p, &rr, &cache_flush, &start);
                 if (r < 0)
                         return r;
@@ -2345,15 +2356,16 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) {
                                 return r;
                 }
 
-                /* Remember this RR, so that we potentically can merge it's ->key object with the
+                /* Remember this RR, so that we can potentially merge its ->key object with the
                  * next RR. Note that we only do this if we actually decided to keep the RR around.
                  */
-                dns_resource_record_unref(previous);
-                previous = dns_resource_record_ref(rr);
+                DNS_RR_REPLACE(previous, dns_resource_record_ref(rr));
         }
 
-        if (bad_opt)
+        if (bad_opt) {
                 p->opt = dns_resource_record_unref(p->opt);
+                p->opt_start = p->opt_size = SIZE_MAX;
+        }
 
         *ret_answer = TAKE_PTR(answer);
 
@@ -2361,15 +2373,16 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) {
 }
 
 int dns_packet_extract(DnsPacket *p) {
-        _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
-        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {};
-        int r;
+        assert(p);
 
         if (p->extracted)
                 return 0;
 
-        INIT_REWINDER(rewinder, p);
+        _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
+        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+        _unused_ _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
+        int r;
+
         dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE);
 
         r = dns_packet_extract_question(p, &question);
@@ -2380,9 +2393,14 @@ int dns_packet_extract(DnsPacket *p) {
         if (r < 0)
                 return r;
 
+        if (p->rindex < p->size)  {
+                log_debug("Trailing garbage in packet, suppressing OPT.");
+                p->opt = dns_resource_record_unref(p->opt);
+                p->opt_start = p->opt_size = SIZE_MAX;
+        }
+
         p->question = TAKE_PTR(question);
         p->answer = TAKE_PTR(answer);
-
         p->extracted = true;
 
         /* no CANCEL, always rewind */
@@ -2413,17 +2431,17 @@ int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) {
         if (p->question->n_keys != 1)
                 return 0;
 
-        return dns_resource_key_equal(p->question->keys[0], key);
+        return dns_resource_key_equal(dns_question_first_key(p->question), key);
 }
 
 int dns_packet_patch_max_udp_size(DnsPacket *p, uint16_t max_udp_size) {
         assert(p);
         assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX);
 
-        if (p->opt_start == (size_t) -1) /* No OPT section, nothing to patch */
+        if (p->opt_start == SIZE_MAX) /* No OPT section, nothing to patch */
                 return 0;
 
-        assert(p->opt_size != (size_t) -1);
+        assert(p->opt_size != SIZE_MAX);
         assert(p->opt_size >= 5);
 
         unaligned_write_be16(DNS_PACKET_DATA(p) + p->opt_start + 3, max_udp_size);
@@ -2431,15 +2449,13 @@ int dns_packet_patch_max_udp_size(DnsPacket *p, uint16_t max_udp_size) {
 }
 
 static int patch_rr(DnsPacket *p, usec_t age) {
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
         size_t ttl_index;
         uint32_t ttl;
         uint16_t type, rdlength;
         int r;
 
-        INIT_REWINDER(rewinder, p);
-
-        /* Patches the RR at the current rindex, substracts the specified time from the TTL */
+        /* Patches the RR at the current rindex, subtracts the specified time from the TTL */
 
         r = dns_packet_read_name(p, NULL, true, NULL);
         if (r < 0)
@@ -2475,33 +2491,31 @@ static int patch_rr(DnsPacket *p, usec_t age) {
 }
 
 int dns_packet_patch_ttls(DnsPacket *p, usec_t timestamp) {
-        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {};
-        unsigned i, n;
-        usec_t k;
-        int r;
-
         assert(p);
         assert(timestamp_is_set(timestamp));
 
         /* Adjusts all TTLs in the packet by subtracting the time difference between now and the specified timestamp */
 
-        k = now(clock_boottime_or_monotonic());
+        _unused_ _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = REWINDER_INIT(p);
+        unsigned n;
+        usec_t k;
+        int r;
+
+        k = now(CLOCK_BOOTTIME);
         assert(k >= timestamp);
         k -= timestamp;
 
-        INIT_REWINDER(rewinder, p);
-
         dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE);
 
         n = DNS_PACKET_QDCOUNT(p);
-        for (i = 0; i < n; i++) {
+        for (unsigned i = 0; i < n; i++) {
                 r = dns_packet_read_key(p, NULL, NULL, NULL);
                 if (r < 0)
                         return r;
         }
 
         n = DNS_PACKET_RRCOUNT(p);
-        for (i = 0; i < n; i++) {
+        for (unsigned i = 0; i < n; i++) {
 
                 /* DNS servers suck, hence the RR count is in many servers off. If we reached the end
                  * prematurely, accept that, exit early */
@@ -2535,32 +2549,96 @@ static int dns_packet_compare_func(const DnsPacket *x, const DnsPacket *y) {
 
 DEFINE_HASH_OPS(dns_packet_hash_ops, DnsPacket, dns_packet_hash_func, dns_packet_compare_func);
 
+bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b) {
+        return dns_packet_compare_func(a, b) == 0;
+}
+
+int dns_packet_has_nsid_request(DnsPacket *p) {
+        bool has_nsid = false;
+        const uint8_t *d;
+        size_t l;
+
+        assert(p);
+
+        if (!p->opt)
+                return false;
+
+        d = p->opt->opt.data;
+        l = p->opt->opt.data_size;
+
+        while (l > 0) {
+                uint16_t code, length;
+
+                if (l < 4U)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                               "EDNS0 variable part has invalid size.");
+
+                code = unaligned_read_be16(d);
+                length = unaligned_read_be16(d + 2);
+
+                if (l < 4U + length)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                               "Truncated option in EDNS0 variable part.");
+
+                if (code == 3) {
+                        if (has_nsid)
+                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                                       "Duplicate NSID option in EDNS0 variable part.");
+
+                        if (length != 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                                       "Non-empty NSID option in DNS request.");
+
+                        has_nsid = true;
+                }
+
+                d += 4U + length;
+                l -= 4U + length;
+        }
+
+        return has_nsid;
+}
+
+size_t dns_packet_size_unfragmented(DnsPacket *p) {
+        assert(p);
+
+        if (p->fragsize == 0) /* Wasn't fragmented */
+                return p->size;
+
+        /* The fragment size (p->fragsize) covers the whole (fragmented) IP packet, while the regular packet
+         * size (p->size) only covers the DNS part. Thus, subtract the UDP header from the largest fragment
+         * size, in order to determine which size of DNS packet would have gone through without
+         * fragmenting. */
+
+        return LESS_BY(p->fragsize, udp_header_size(p->family));
+}
+
 static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
-        [DNS_RCODE_SUCCESS] = "SUCCESS",
-        [DNS_RCODE_FORMERR] = "FORMERR",
-        [DNS_RCODE_SERVFAIL] = "SERVFAIL",
-        [DNS_RCODE_NXDOMAIN] = "NXDOMAIN",
-        [DNS_RCODE_NOTIMP] = "NOTIMP",
-        [DNS_RCODE_REFUSED] = "REFUSED",
-        [DNS_RCODE_YXDOMAIN] = "YXDOMAIN",
-        [DNS_RCODE_YXRRSET] = "YRRSET",
-        [DNS_RCODE_NXRRSET] = "NXRRSET",
-        [DNS_RCODE_NOTAUTH] = "NOTAUTH",
-        [DNS_RCODE_NOTZONE] = "NOTZONE",
-        [DNS_RCODE_BADVERS] = "BADVERS",
-        [DNS_RCODE_BADKEY] = "BADKEY",
-        [DNS_RCODE_BADTIME] = "BADTIME",
-        [DNS_RCODE_BADMODE] = "BADMODE",
-        [DNS_RCODE_BADNAME] = "BADNAME",
-        [DNS_RCODE_BADALG] = "BADALG",
-        [DNS_RCODE_BADTRUNC] = "BADTRUNC",
+        [DNS_RCODE_SUCCESS]   = "SUCCESS",
+        [DNS_RCODE_FORMERR]   = "FORMERR",
+        [DNS_RCODE_SERVFAIL]  = "SERVFAIL",
+        [DNS_RCODE_NXDOMAIN]  = "NXDOMAIN",
+        [DNS_RCODE_NOTIMP]    = "NOTIMP",
+        [DNS_RCODE_REFUSED]   = "REFUSED",
+        [DNS_RCODE_YXDOMAIN]  = "YXDOMAIN",
+        [DNS_RCODE_YXRRSET]   = "YRRSET",
+        [DNS_RCODE_NXRRSET]   = "NXRRSET",
+        [DNS_RCODE_NOTAUTH]   = "NOTAUTH",
+        [DNS_RCODE_NOTZONE]   = "NOTZONE",
+        [DNS_RCODE_BADVERS]   = "BADVERS",
+        [DNS_RCODE_BADKEY]    = "BADKEY",
+        [DNS_RCODE_BADTIME]   = "BADTIME",
+        [DNS_RCODE_BADMODE]   = "BADMODE",
+        [DNS_RCODE_BADNAME]   = "BADNAME",
+        [DNS_RCODE_BADALG]    = "BADALG",
+        [DNS_RCODE_BADTRUNC]  = "BADTRUNC",
         [DNS_RCODE_BADCOOKIE] = "BADCOOKIE",
 };
 DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int);
 
 static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = {
-        [DNS_PROTOCOL_DNS] = "dns",
-        [DNS_PROTOCOL_MDNS] = "mdns",
+        [DNS_PROTOCOL_DNS]   = "dns",
+        [DNS_PROTOCOL_MDNS]  = "mdns",
         [DNS_PROTOCOL_LLMNR] = "llmnr",
 };
 DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol);