]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-packet.c
resolved: move algorithm/digest definitions into resolved-dns-rr.h
[thirdparty/systemd.git] / src / resolve / resolved-dns-packet.c
index 12cd524c40ffd77df05ef414b53520f949c88728..2a010ef5078899bdeeb772136b472313a755a127 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
  ***/
 
-#include "utf8.h"
-#include "util.h"
-#include "strv.h"
-#include "unaligned.h"
+#include "alloc-util.h"
 #include "dns-domain.h"
 #include "resolved-dns-packet.h"
+#include "string-table.h"
+#include "strv.h"
+#include "unaligned.h"
+#include "utf8.h"
+#include "util.h"
+
+#define EDNS0_OPT_DO (1<<15)
 
 int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
         DnsPacket *p;
@@ -104,6 +108,8 @@ DnsPacket *dns_packet_ref(DnsPacket *p) {
         if (!p)
                 return NULL;
 
+        assert(!p->on_stack);
+
         assert(p->n_ref > 0);
         p->n_ref++;
         return p;
@@ -122,7 +128,9 @@ static void dns_packet_free(DnsPacket *p) {
         hashmap_free(p->names);
 
         free(p->_data);
-        free(p);
+
+        if (!p->on_stack)
+                free(p);
 }
 
 DnsPacket *dns_packet_unref(DnsPacket *p) {
@@ -265,7 +273,7 @@ static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start
         return 0;
 }
 
-static void dns_packet_truncate(DnsPacket *p, size_t sz) {
+void dns_packet_truncate(DnsPacket *p, size_t sz) {
         Iterator i;
         char *s;
         void *n;
@@ -275,7 +283,7 @@ static void dns_packet_truncate(DnsPacket *p, size_t sz) {
         if (p->size <= sz)
                 return;
 
-        HASHMAP_FOREACH_KEY(s, n, p->names, i) {
+        HASHMAP_FOREACH_KEY(n, s, p->names, i) {
 
                 if (PTR_TO_SIZE(n) < sz)
                         continue;
@@ -347,29 +355,36 @@ int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) {
 }
 
 int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) {
+        assert(p);
+        assert(s);
+
+        return dns_packet_append_raw_string(p, s, strlen(s), start);
+}
+
+int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) {
         void *d;
-        size_t l;
         int r;
 
         assert(p);
-        assert(s);
+        assert(s || size == 0);
 
-        l = strlen(s);
-        if (l > 255)
+        if (size > 255)
                 return -E2BIG;
 
-        r = dns_packet_extend(p, 1 + l, &d, start);
+        r = dns_packet_extend(p, 1 + size, &d, start);
         if (r < 0)
                 return r;
 
-        ((uint8_t*) d)[0] = (uint8_t) l;
-        memcpy(((uint8_t*) d) + 1, s, l);
+        ((uint8_t*) d)[0] = (uint8_t) size;
+
+        if (size > 0)
+                memcpy(((uint8_t*) d) + 1, s, size);
 
         return 0;
 }
 
 int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) {
-        void *w;
+        uint8_t *w;
         int r;
 
         assert(p);
@@ -378,24 +393,48 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start
         if (l > DNS_LABEL_MAX)
                 return -E2BIG;
 
-        r = dns_packet_extend(p, 1 + l, &w, start);
+        r = dns_packet_extend(p, 1 + l, (void**) &w, start);
         if (r < 0)
                 return r;
 
-        ((uint8_t*) w)[0] = (uint8_t) l;
-        memcpy(((uint8_t*) w) + 1, d, l);
+        *(w++) = (uint8_t) l;
+
+        if (p->canonical_form) {
+                size_t i;
+
+                /* Generate in canonical form, as defined by DNSSEC
+                 * RFC 4034, Section 6.2, i.e. all lower-case. */
+
+                for (i = 0; i < l; i++) {
+                        if (d[i] >= 'A' && d[i] <= 'Z')
+                                w[i] = (uint8_t) (d[i] - 'A' + 'a');
+                        else
+                                w[i] = (uint8_t) d[i];
+                }
+        } else
+                /* Otherwise, just copy the string unaltered. This is
+                 * essential for DNS-SD, where the casing of labels
+                 * matters and needs to be retained. */
+                memcpy(w, d, l);
 
         return 0;
 }
 
-int dns_packet_append_name(DnsPacket *p, const char *name,
-                           bool allow_compression, size_t *start) {
+int dns_packet_append_name(
+                DnsPacket *p,
+                const char *name,
+                bool allow_compression,
+                size_t *start) {
+
         size_t saved_size;
         int r;
 
         assert(p);
         assert(name);
 
+        if (p->refuse_compression)
+                allow_compression = false;
+
         saved_size = p->size;
 
         while (*name) {
@@ -508,23 +547,21 @@ static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t l
 
         assert(p);
         assert(types);
+        assert(length > 0);
 
         saved_size = p->size;
 
-        if (length != 0) {
-
-                r = dns_packet_append_uint8(p, window, NULL);
-                if (r < 0)
-                        goto fail;
+        r = dns_packet_append_uint8(p, window, NULL);
+        if (r < 0)
+                goto fail;
 
-                r = dns_packet_append_uint8(p, length, NULL);
-                if (r < 0)
-                        goto fail;
+        r = dns_packet_append_uint8(p, length, NULL);
+        if (r < 0)
+                goto fail;
 
-                r = dns_packet_append_blob(p, types, length, NULL);
-                if (r < 0)
-                        goto fail;
-        }
+        r = dns_packet_append_blob(p, types, length, NULL);
+        if (r < 0)
+                goto fail;
 
         if (start)
                 *start = saved_size;
@@ -538,7 +575,7 @@ fail:
 static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) {
         Iterator i;
         uint8_t window = 0;
-        uint8_t len = 0;
+        uint8_t entry = 0;
         uint8_t bitmaps[32] = {};
         unsigned n;
         size_t saved_size;
@@ -550,30 +587,24 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) {
         saved_size = p->size;
 
         BITMAP_FOREACH(n, types, i) {
-                uint8_t entry;
-
                 assert(n <= 0xffff);
 
-                if ((n << 8) != window) {
-                        r = dns_packet_append_type_window(p, window, len, bitmaps, NULL);
+                if ((n >> 8) != window && bitmaps[entry / 8] != 0) {
+                        r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL);
                         if (r < 0)
                                 goto fail;
 
-                        if (len > 0) {
-                                len = 0;
-                                zero(bitmaps);
-                        }
+                        zero(bitmaps);
                 }
 
-                window = n << 8;
-                len ++;
+                window = n >> 8;
 
                 entry = n & 255;
 
                 bitmaps[entry / 8] |= 1 << (7 - (entry % 8));
         }
 
-        r = dns_packet_append_type_window(p, window, len, bitmaps, NULL);
+        r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL);
         if (r < 0)
                 goto fail;
 
@@ -586,8 +617,59 @@ fail:
         return r;
 }
 
-int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) {
-        size_t saved_size, rdlength_offset, end, rdlength;
+/* Append the OPT pseudo-RR described in RFC6891 */
+int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) {
+        size_t saved_size;
+        int r;
+
+        assert(p);
+        /* we must never advertise supported packet size smaller than the legacy max */
+        assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX);
+
+        saved_size = p->size;
+
+        /* empty name */
+        r = dns_packet_append_uint8(p, 0, NULL);
+        if (r < 0)
+                return r;
+
+        /* type */
+        r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL);
+        if (r < 0)
+                goto fail;
+
+        /* maximum udp packet that can be received */
+        r = dns_packet_append_uint16(p, max_udp_size, NULL);
+        if (r < 0)
+                goto fail;
+
+        /* extended RCODE and VERSION */
+        r = dns_packet_append_uint16(p, 0, NULL);
+        if (r < 0)
+                goto fail;
+
+        /* flags: DNSSEC OK (DO), see RFC3225 */
+        r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL);
+        if (r < 0)
+                goto fail;
+
+        /* RDLENGTH */
+        r = dns_packet_append_uint16(p, 0, NULL);
+        if (r < 0)
+                goto fail;
+
+        if (start)
+                *start = saved_size;
+
+        return 0;
+
+fail:
+        dns_packet_truncate(p, saved_size);
+        return r;
+}
+
+int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) {
+        size_t saved_size, rdlength_offset, end, rdlength, rds;
         int r;
 
         assert(p);
@@ -608,6 +690,8 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
         if (r < 0)
                 goto fail;
 
+        rds = p->size - saved_size;
+
         switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) {
 
         case DNS_TYPE_SRV:
@@ -642,19 +726,20 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
                 break;
 
         case DNS_TYPE_SPF: /* exactly the same as TXT */
-        case DNS_TYPE_TXT: {
-                char **s;
+        case DNS_TYPE_TXT:
 
-                if (strv_isempty(rr->txt.strings)) {
+                if (!rr->txt.items) {
                         /* RFC 6763, section 6.1 suggests to generate
                          * single empty string for an empty array. */
 
-                        r = dns_packet_append_string(p, "", NULL);
+                        r = dns_packet_append_raw_string(p, NULL, 0, NULL);
                         if (r < 0)
                                 goto fail;
                 } else {
-                        STRV_FOREACH(s, rr->txt.strings) {
-                                r = dns_packet_append_string(p, *s, NULL);
+                        DnsTxtItem *i;
+
+                        LIST_FOREACH(items, i, rr->txt.items) {
+                                r = dns_packet_append_raw_string(p, i->data, i->length, NULL);
                                 if (r < 0)
                                         goto fail;
                         }
@@ -662,7 +747,6 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
 
                 r = 0;
                 break;
-        }
 
         case DNS_TYPE_A:
                 r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL);
@@ -761,15 +845,15 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
                 if (r < 0)
                         goto fail;
 
-                r = dns_packet_append_blob(p, rr->sshfp.key, rr->sshfp.key_size, NULL);
+                r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL);
                 break;
 
         case DNS_TYPE_DNSKEY:
-                r = dns_packet_append_uint16(p, dnskey_to_flags(rr), NULL);
+                r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL);
                 if (r < 0)
                         goto fail;
 
-                r = dns_packet_append_uint8(p, 3u, NULL);
+                r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL);
                 if (r < 0)
                         goto fail;
 
@@ -886,6 +970,9 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
         if (start)
                 *start = saved_size;
 
+        if (rdata_start)
+                *rdata_start = rds;
+
         return 0;
 
 fail:
@@ -933,6 +1020,42 @@ int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) {
         return 0;
 }
 
+static int dns_packet_read_memdup(
+                DnsPacket *p, size_t size,
+                void **ret, size_t *ret_size,
+                size_t *ret_start) {
+
+        const void *src;
+        size_t start;
+        int r;
+
+        assert(p);
+        assert(ret);
+
+        r = dns_packet_read(p, size, &src, &start);
+        if (r < 0)
+                return r;
+
+        if (size <= 0)
+                *ret = NULL;
+        else {
+                void *copy;
+
+                copy = memdup(src, size);
+                if (!copy)
+                        return -ENOMEM;
+
+                *ret = copy;
+        }
+
+        if (ret_size)
+                *ret_size = size;
+        if (ret_start)
+                *ret_start = start;
+
+        return 0;
+}
+
 int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) {
         const void *d;
         int r;
@@ -1025,8 +1148,41 @@ fail:
         return r;
 }
 
-int dns_packet_read_name(DnsPacket *p, char **_ret,
-                         bool allow_compression, size_t *start) {
+int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) {
+        size_t saved_rindex;
+        uint8_t c;
+        int r;
+
+        assert(p);
+
+        saved_rindex = p->rindex;
+
+        r = dns_packet_read_uint8(p, &c, NULL);
+        if (r < 0)
+                goto fail;
+
+        r = dns_packet_read(p, c, ret, NULL);
+        if (r < 0)
+                goto fail;
+
+        if (size)
+                *size = c;
+        if (start)
+                *start = saved_rindex;
+
+        return 0;
+
+fail:
+        dns_packet_rewind(p, saved_rindex);
+        return r;
+}
+
+int dns_packet_read_name(
+                DnsPacket *p,
+                char **_ret,
+                bool allow_compression,
+                size_t *start) {
+
         size_t saved_rindex, after_rindex = 0, jump_barrier;
         _cleanup_free_ char *ret = NULL;
         size_t n = 0, allocated = 0;
@@ -1036,6 +1192,9 @@ int dns_packet_read_name(DnsPacket *p, char **_ret,
         assert(p);
         assert(_ret);
 
+        if (p->refuse_compression)
+                allow_compression = false;
+
         saved_rindex = p->rindex;
         jump_barrier = p->rindex;
 
@@ -1050,7 +1209,6 @@ int dns_packet_read_name(DnsPacket *p, char **_ret,
                         /* End of name */
                         break;
                 else if (c <= 63) {
-                        _cleanup_free_ char *t = NULL;
                         const char *label;
 
                         /* Literal label */
@@ -1058,21 +1216,20 @@ int dns_packet_read_name(DnsPacket *p, char **_ret,
                         if (r < 0)
                                 goto fail;
 
-                        r = dns_label_escape(label, c, &t);
-                        if (r < 0)
-                                goto fail;
-
-                        if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1)) {
+                        if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) {
                                 r = -ENOMEM;
                                 goto fail;
                         }
 
-                        if (!first)
-                                ret[n++] = '.';
-                        else
+                        if (first)
                                 first = false;
+                        else
+                                ret[n++] = '.';
+
+                        r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
+                        if (r < 0)
+                                goto fail;
 
-                        memcpy(ret + n, t, r);
                         n += r;
                         continue;
                 } else if (allow_compression && (c & 0xc0) == 0xc0) {
@@ -1128,6 +1285,7 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
         uint8_t window;
         uint8_t length;
         const uint8_t *bitmap;
+        uint8_t bit = 0;
         unsigned i;
         bool found = false;
         size_t saved_rindex;
@@ -1159,10 +1317,10 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
 
         for (i = 0; i < length; i++) {
                 uint8_t bitmask = 1 << 7;
-                uint8_t bit = 0;
 
                 if (!bitmap[i]) {
                         found = false;
+                        bit += 8;
                         continue;
                 }
 
@@ -1172,9 +1330,12 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
                         if (bitmap[i] & bitmask) {
                                 uint16_t n;
 
-                                /* XXX: ignore pseudo-types? see RFC4034 section 4.1.2 */
                                 n = (uint16_t) window << 8 | (uint16_t) bit;
 
+                                /* Ignore pseudo-types. see RFC4034 section 4.1.2 */
+                                if (dns_type_is_pseudo(n))
+                                        continue;
+
                                 r = bitmap_set(*types, n);
                                 if (r < 0)
                                         goto fail;
@@ -1197,6 +1358,38 @@ fail:
         return r;
 }
 
+static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) {
+        size_t saved_rindex;
+        int r;
+
+        saved_rindex = p->rindex;
+
+        while (p->rindex < saved_rindex + size) {
+                r = dns_packet_read_type_window(p, types, NULL);
+                if (r < 0)
+                        goto fail;
+
+                /* don't read past end of current RR */
+                if (p->rindex > saved_rindex + size) {
+                        r = -EBADMSG;
+                        goto fail;
+                }
+        }
+
+        if (p->rindex != saved_rindex + size) {
+                r = -EBADMSG;
+                goto fail;
+        }
+
+        if (start)
+                *start = saved_rindex;
+
+        return 0;
+fail:
+        dns_packet_rewind(p, saved_rindex);
+        return r;
+}
+
 int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) {
         _cleanup_free_ char *name = NULL;
         uint16_t class, type;
@@ -1239,49 +1432,17 @@ fail:
         return r;
 }
 
-static int dns_packet_read_public_key(DnsPacket *p, size_t length,
-                                      void **dp, size_t *lengthp,
-                                      size_t *start) {
-        int r;
-        const void *d;
-        void *d2;
-
-        r = dns_packet_read(p, length, &d, NULL);
-        if (r < 0)
-                return r;
-
-        d2 = memdup(d, length);
-        if (!d2)
-                return -ENOMEM;
-
-        *dp = d2;
-        *lengthp = length;
-        return 0;
-}
-
 static bool loc_size_ok(uint8_t size) {
         uint8_t m = size >> 4, e = size & 0xF;
 
         return m <= 9 && e <= 9 && (m > 0 || e == 0);
 }
 
-static int dnskey_parse_flags(DnsResourceRecord *rr, uint16_t flags) {
-        assert(rr);
-
-        if (flags & ~(DNSKEY_FLAG_SEP | DNSKEY_FLAG_ZONE_KEY))
-                return -EBADMSG;
-
-        rr->dnskey.zone_key_flag = flags & DNSKEY_FLAG_ZONE_KEY;
-        rr->dnskey.sep_flag = flags & DNSKEY_FLAG_SEP;
-        return 0;
-}
-
 int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
         _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
         size_t saved_rindex, offset;
         uint16_t rdlength;
-        const void *d;
         int r;
 
         assert(p);
@@ -1342,6 +1503,10 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                 r = dns_packet_read_name(p, &rr->ptr.name, true, NULL);
                 break;
 
+        case DNS_TYPE_OPT: /* we only care about the header */
+                r = 0;
+                break;
+
         case DNS_TYPE_HINFO:
                 r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL);
                 if (r < 0)
@@ -1353,24 +1518,37 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
         case DNS_TYPE_SPF: /* exactly the same as TXT */
         case DNS_TYPE_TXT:
                 if (rdlength <= 0) {
+                        DnsTxtItem *i;
                         /* RFC 6763, section 6.1 suggests to treat
                          * empty TXT RRs as equivalent to a TXT record
                          * with a single empty string. */
 
-                        r = strv_extend(&rr->txt.strings, "");
-                        if (r < 0)
-                                goto fail;
+                        i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */
+                        if (!i)
+                                return -ENOMEM;
+
+                        rr->txt.items = i;
                 } else {
+                        DnsTxtItem *last = NULL;
+
                         while (p->rindex < offset + rdlength) {
-                                char *s;
+                                DnsTxtItem *i;
+                                const void *data;
+                                size_t sz;
 
-                                r = dns_packet_read_string(p, &s, NULL);
+                                r = dns_packet_read_raw_string(p, &data, &sz, NULL);
                                 if (r < 0)
-                                        goto fail;
+                                        return r;
 
-                                r = strv_consume(&rr->txt.strings, s);
-                                if (r < 0)
-                                        goto fail;
+                                i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */
+                                if (!i)
+                                        return -ENOMEM;
+
+                                memcpy(i->data, data, sz);
+                                i->length = sz;
+
+                                LIST_INSERT_AFTER(items, rr->txt.items, last, i);
+                                last = i;
                         }
                 }
 
@@ -1492,12 +1670,19 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                 if (r < 0)
                         goto fail;
 
-                r = dns_packet_read_public_key(p, rdlength - 4,
-                                               &rr->ds.digest, &rr->ds.digest_size,
-                                               NULL);
+                r = dns_packet_read_memdup(p, rdlength - 4,
+                                           &rr->ds.digest, &rr->ds.digest_size,
+                                           NULL);
                 if (r < 0)
                         goto fail;
 
+                if (rr->ds.digest_size <= 0) {
+                        /* the accepted size depends on the algorithm, but for now
+                           just ensure that the value is greater than zero */
+                        r = -EBADMSG;
+                        goto fail;
+                }
+
                 break;
         case DNS_TYPE_SSHFP:
                 r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL);
@@ -1508,42 +1693,44 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                 if (r < 0)
                         goto fail;
 
-                r = dns_packet_read_public_key(p, rdlength - 2,
-                                               &rr->sshfp.key, &rr->sshfp.key_size,
-                                               NULL);
-                break;
+                r = dns_packet_read_memdup(p, rdlength - 2,
+                                           &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size,
+                                           NULL);
 
-        case DNS_TYPE_DNSKEY: {
-                uint16_t flags;
-                uint8_t proto;
+                if (rr->sshfp.fingerprint_size <= 0) {
+                        /* the accepted size depends on the algorithm, but for now
+                           just ensure that the value is greater than zero */
+                        r = -EBADMSG;
+                        goto fail;
+                }
+
+                break;
 
-                r = dns_packet_read_uint16(p, &flags, NULL);
+        case DNS_TYPE_DNSKEY:
+                r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL);
                 if (r < 0)
                         goto fail;
 
-                r = dnskey_parse_flags(rr, flags);
+                r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL);
                 if (r < 0)
                         goto fail;
 
-                r = dns_packet_read_uint8(p, &proto, NULL);
+                r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL);
                 if (r < 0)
                         goto fail;
 
-                /* protocol is required to be always 3 */
-                if (proto != 3) {
+                r = dns_packet_read_memdup(p, rdlength - 4,
+                                           &rr->dnskey.key, &rr->dnskey.key_size,
+                                           NULL);
+
+                if (rr->dnskey.key_size <= 0) {
+                        /* the accepted size depends on the algorithm, but for now
+                           just ensure that the value is greater than zero */
                         r = -EBADMSG;
                         goto fail;
                 }
 
-                r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL);
-                if (r < 0)
-                        goto fail;
-
-                r = dns_packet_read_public_key(p, rdlength - 4,
-                                               &rr->dnskey.key, &rr->dnskey.key_size,
-                                               NULL);
                 break;
-        }
 
         case DNS_TYPE_RRSIG:
                 r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL);
@@ -1578,9 +1765,17 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                 if (r < 0)
                         goto fail;
 
-                r = dns_packet_read_public_key(p, offset + rdlength - p->rindex,
-                                               &rr->rrsig.signature, &rr->rrsig.signature_size,
-                                               NULL);
+                r = dns_packet_read_memdup(p, offset + rdlength - p->rindex,
+                                           &rr->rrsig.signature, &rr->rrsig.signature_size,
+                                           NULL);
+
+                if (rr->rrsig.signature_size <= 0) {
+                        /* the accepted size depends on the algorithm, but for now
+                           just ensure that the value is greater than zero */
+                        r = -EBADMSG;
+                        goto fail;
+                }
+
                 break;
 
         case DNS_TYPE_NSEC:
@@ -1588,11 +1783,13 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                 if (r < 0)
                         goto fail;
 
-                while (p->rindex != offset + rdlength) {
-                        r = dns_packet_read_type_window(p, &rr->nsec.types, NULL);
-                        if (r < 0)
-                                goto fail;
-                }
+                r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL);
+                if (r < 0)
+                        goto fail;
+
+                /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself
+                 * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records
+                 * without the NSEC bit set. */
 
                 break;
 
@@ -1611,57 +1808,41 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
                 if (r < 0)
                         goto fail;
 
+                /* this may be zero */
                 r = dns_packet_read_uint8(p, &size, NULL);
                 if (r < 0)
                         goto fail;
 
-                rr->nsec3.salt_size = size;
-
-                r = dns_packet_read_blob(p, &d, rr->nsec3.salt_size, NULL);
+                r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL);
                 if (r < 0)
                         goto fail;
 
-                rr->nsec3.salt = memdup(d, rr->nsec3.salt_size);
-                if (!rr->nsec3.salt) {
-                        r = -ENOMEM;
-                        goto fail;
-                }
-
                 r = dns_packet_read_uint8(p, &size, NULL);
                 if (r < 0)
                         goto fail;
 
-                rr->nsec3.next_hashed_name_size = size;
-
-                r = dns_packet_read(p, rr->nsec3.next_hashed_name_size, &d, NULL);
-                if (r < 0)
+                if (size <= 0) {
+                        r = -EBADMSG;
                         goto fail;
+                }
 
-                rr->nsec3.next_hashed_name = memdup(d, rr->nsec3.next_hashed_name_size);
-                if (!rr->nsec3.next_hashed_name) {
-                        r = -ENOMEM;
+                r = dns_packet_read_memdup(p, size, &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, NULL);
+                if (r < 0)
                         goto fail;
-                }
 
-                r = dns_packet_append_types(p, rr->nsec3.types, NULL);
+                r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL);
                 if (r < 0)
                         goto fail;
 
+                /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */
+
                 break;
         }
         default:
         unparseable:
-                r = dns_packet_read(p, rdlength, &d, NULL);
+                r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.size, NULL);
                 if (r < 0)
                         goto fail;
-
-                rr->generic.data = memdup(d, rdlength);
-                if (!rr->generic.data) {
-                        r = -ENOMEM;
-                        goto fail;
-                }
-
-                rr->generic.size = rdlength;
                 break;
         }
         if (r < 0)
@@ -1732,7 +1913,7 @@ int dns_packet_extract(DnsPacket *p) {
                         if (r < 0)
                                 goto finish;
 
-                        r = dns_answer_add(answer, rr);
+                        r = dns_answer_add(answer, rr, p->ifindex);
                         if (r < 0)
                                 goto finish;
                 }
@@ -1781,17 +1962,3 @@ static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = {
         [DNS_PROTOCOL_LLMNR] = "llmnr",
 };
 DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol);
-
-static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = {
-        [DNSSEC_ALGORITHM_RSAMD5]             = "RSAMD5",
-        [DNSSEC_ALGORITHM_DH]                 = "DH",
-        [DNSSEC_ALGORITHM_DSA]                = "DSA",
-        [DNSSEC_ALGORITHM_ECC]                = "ECC",
-        [DNSSEC_ALGORITHM_RSASHA1]            = "RSASHA1",
-        [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1]     = "DSA-NSEC3-SHA1",
-        [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1",
-        [DNSSEC_ALGORITHM_INDIRECT]           = "INDIRECT",
-        [DNSSEC_ALGORITHM_PRIVATEDNS]         = "PRIVATEDNS",
-        [DNSSEC_ALGORITHM_PRIVATEOID]         = "PRIVATEOID",
-};
-DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int);