]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-packet.c
Merge pull request #2664 from zonque/bootchart-removal
[thirdparty/systemd.git] / src / resolve / resolved-dns-packet.c
index 4750bf1f5dbe0b0a94f5d4b4e49d6809668d1cc0..64913da573be248c01d4d72d5845618277063746 100644 (file)
@@ -1,5 +1,3 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
 /***
   This file is part of systemd.
 
 
 #define EDNS0_OPT_DO (1<<15)
 
+typedef struct DnsPacketRewinder {
+        DnsPacket *packet;
+        size_t saved_rindex;
+} DnsPacketRewinder;
+
+static void rewind_dns_packet(DnsPacketRewinder *rewinder) {
+        if (rewinder->packet)
+                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)
+
 int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
         DnsPacket *p;
         size_t a;
@@ -433,8 +444,7 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_
 
         ((uint8_t*) d)[0] = (uint8_t) size;
 
-        if (size > 0)
-                memcpy(((uint8_t*) d) + 1, s, size);
+        memcpy_safe(((uint8_t*) d) + 1, s, size);
 
         return 0;
 }
@@ -466,12 +476,8 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonica
                 /* 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];
-                }
+                for (i = 0; i < l; i++)
+                        w[i] = (uint8_t) ascii_tolower(d[i]);
         } else
                 /* Otherwise, just copy the string unaltered. This is
                  * essential for DNS-SD, where the casing of labels
@@ -503,7 +509,6 @@ int dns_packet_append_name(
                 const char *z = name;
                 char label[DNS_LABEL_MAX];
                 size_t n = 0;
-                int k;
 
                 if (allow_compression)
                         n = PTR_TO_SIZE(hashmap_get(p->names, name));
@@ -523,17 +528,6 @@ int dns_packet_append_name(
                 if (r < 0)
                         goto fail;
 
-                if (p->protocol == DNS_PROTOCOL_DNS)
-                        k = dns_label_apply_idna(label, r, label, sizeof(label));
-                else
-                        k = dns_label_undo_idna(label, r, label, sizeof(label));
-                if (k < 0) {
-                        r = k;
-                        goto fail;
-                }
-                if (k > 0)
-                        r = k;
-
                 r = dns_packet_append_label(p, label, r, canonical_candidate, &n);
                 if (r < 0)
                         goto fail;
@@ -583,7 +577,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start)
 
         saved_size = p->size;
 
-        r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL);
+        r = dns_packet_append_name(p, dns_resource_key_name(k), true, true, NULL);
         if (r < 0)
                 goto fail;
 
@@ -1074,11 +1068,40 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
 
                 break;
 
+        case DNS_TYPE_TLSA:
+                r = dns_packet_append_uint8(p, rr->tlsa.cert_usage, NULL);
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_uint8(p, rr->tlsa.selector, NULL);
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_uint8(p, rr->tlsa.matching_type, NULL);
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL);
+                break;
+
+        case DNS_TYPE_CAA:
+                r = dns_packet_append_uint8(p, rr->caa.flags, NULL);
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_string(p, rr->caa.tag, NULL);
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_size, NULL);
+                break;
+
         case DNS_TYPE_OPT:
+        case DNS_TYPE_OPENPGPKEY:
         case _DNS_TYPE_INVALID: /* unparseable */
         default:
 
-                r = dns_packet_append_blob(p, rr->generic.data, rr->generic.size, NULL);
+                r = dns_packet_append_blob(p, rr->generic.data, rr->generic.data_size, NULL);
                 break;
         }
         if (r < 0)
@@ -1087,7 +1110,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
         /* Let's calculate the actual data size and update the field */
         rdlength = p->size - rdlength_offset - sizeof(uint16_t);
         if (rdlength > 0xFFFF) {
-                r = ENOSPC;
+                r = -ENOSPC;
                 goto fail;
         }
 
@@ -1231,80 +1254,67 @@ 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) {
-        size_t saved_rindex;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
         const void *d;
         char *t;
         uint8_t c;
         int r;
 
         assert(p);
-
-        saved_rindex = p->rindex;
+        INIT_REWINDER(rewinder, p);
 
         r = dns_packet_read_uint8(p, &c, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         r = dns_packet_read(p, c, &d, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
-        if (memchr(d, 0, c)) {
-                r = -EBADMSG;
-                goto fail;
-        }
+        if (memchr(d, 0, c))
+                return -EBADMSG;
 
         t = strndup(d, c);
-        if (!t) {
-                r = -ENOMEM;
-                goto fail;
-        }
+        if (!t)
+                return -ENOMEM;
 
         if (!utf8_is_valid(t)) {
                 free(t);
-                r = -EBADMSG;
-                goto fail;
+                return -EBADMSG;
         }
 
         *ret = t;
 
         if (start)
-                *start = saved_rindex;
+                *start = rewinder.saved_rindex;
+        CANCEL_REWINDER(rewinder);
 
         return 0;
-
-fail:
-        dns_packet_rewind(p, saved_rindex);
-        return r;
 }
 
 int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) {
-        size_t saved_rindex;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
         uint8_t c;
         int r;
 
         assert(p);
-
-        saved_rindex = p->rindex;
+        INIT_REWINDER(rewinder, p);
 
         r = dns_packet_read_uint8(p, &c, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         r = dns_packet_read(p, c, ret, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         if (size)
                 *size = c;
         if (start)
-                *start = saved_rindex;
+                *start = rewinder.saved_rindex;
+        CANCEL_REWINDER(rewinder);
 
         return 0;
-
-fail:
-        dns_packet_rewind(p, saved_rindex);
-        return r;
 }
 
 int dns_packet_read_name(
@@ -1313,7 +1323,8 @@ int dns_packet_read_name(
                 bool allow_compression,
                 size_t *start) {
 
-        size_t saved_rindex, after_rindex = 0, jump_barrier;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
+        size_t after_rindex = 0, jump_barrier;
         _cleanup_free_ char *ret = NULL;
         size_t n = 0, allocated = 0;
         bool first = true;
@@ -1321,19 +1332,18 @@ int dns_packet_read_name(
 
         assert(p);
         assert(_ret);
+        INIT_REWINDER(rewinder, p);
+        jump_barrier = p->rindex;
 
         if (p->refuse_compression)
                 allow_compression = false;
 
-        saved_rindex = p->rindex;
-        jump_barrier = p->rindex;
-
         for (;;) {
                 uint8_t c, d;
 
                 r = dns_packet_read_uint8(p, &c, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 if (c == 0)
                         /* End of name */
@@ -1344,12 +1354,10 @@ int dns_packet_read_name(
                         /* Literal label */
                         r = dns_packet_read(p, c, (const void**) &label, NULL);
                         if (r < 0)
-                                goto fail;
+                                return r;
 
-                        if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) {
-                                r = -ENOMEM;
-                                goto fail;
-                        }
+                        if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                                return -ENOMEM;
 
                         if (first)
                                 first = false;
@@ -1358,7 +1366,7 @@ int dns_packet_read_name(
 
                         r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
                         if (r < 0)
-                                goto fail;
+                                return r;
 
                         n += r;
                         continue;
@@ -1368,13 +1376,11 @@ int dns_packet_read_name(
                         /* Pointer */
                         r = dns_packet_read_uint8(p, &d, NULL);
                         if (r < 0)
-                                goto fail;
+                                return r;
 
                         ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d;
-                        if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) {
-                                r = -EBADMSG;
-                                goto fail;
-                        }
+                        if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier)
+                                return -EBADMSG;
 
                         if (after_rindex == 0)
                                 after_rindex = p->rindex;
@@ -1382,16 +1388,12 @@ int dns_packet_read_name(
                         /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */
                         jump_barrier = ptr;
                         p->rindex = ptr;
-                } else {
-                        r = -EBADMSG;
-                        goto fail;
-                }
+                } else
+                        return -EBADMSG;
         }
 
-        if (!GREEDY_REALLOC(ret, allocated, n + 1)) {
-                r = -ENOMEM;
-                goto fail;
-        }
+        if (!GREEDY_REALLOC(ret, allocated, n + 1))
+                return -ENOMEM;
 
         ret[n] = 0;
 
@@ -1402,13 +1404,10 @@ int dns_packet_read_name(
         ret = NULL;
 
         if (start)
-                *start = saved_rindex;
+                *start = rewinder.saved_rindex;
+        CANCEL_REWINDER(rewinder);
 
         return 0;
-
-fail:
-        dns_packet_rewind(p, saved_rindex);
-        return r;
 }
 
 static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) {
@@ -1418,32 +1417,31 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
         uint8_t bit = 0;
         unsigned i;
         bool found = false;
-        size_t saved_rindex;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
         int r;
 
         assert(p);
         assert(types);
-
-        saved_rindex = p->rindex;
+        INIT_REWINDER(rewinder, p);
 
         r = bitmap_ensure_allocated(types);
         if (r < 0)
-                goto fail;
+                return r;
 
         r = dns_packet_read_uint8(p, &window, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         r = dns_packet_read_uint8(p, &length, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         if (length == 0 || length > 32)
                 return -EBADMSG;
 
         r = dns_packet_read(p, length, (const void **)&bitmap, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         for (i = 0; i < length; i++) {
                 uint8_t bitmask = 1 << 7;
@@ -1468,10 +1466,10 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
 
                                 r = bitmap_set(*types, n);
                                 if (r < 0)
-                                        goto fail;
+                                        return r;
                         }
 
-                        bit ++;
+                        bit++;
                         bitmask >>= 1;
                 }
         }
@@ -1480,70 +1478,61 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
                 return -EBADMSG;
 
         if (start)
-                *start = saved_rindex;
+                *start = rewinder.saved_rindex;
+        CANCEL_REWINDER(rewinder);
 
         return 0;
-fail:
-        dns_packet_rewind(p, saved_rindex);
-        return r;
 }
 
 static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) {
-        size_t saved_rindex;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
         int r;
 
-        saved_rindex = p->rindex;
+        INIT_REWINDER(rewinder, p);
 
-        while (p->rindex < saved_rindex + size) {
+        while (p->rindex < rewinder.saved_rindex + size) {
                 r = dns_packet_read_type_window(p, types, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 /* don't read past end of current RR */
-                if (p->rindex > saved_rindex + size) {
-                        r = -EBADMSG;
-                        goto fail;
-                }
+                if (p->rindex > rewinder.saved_rindex + size)
+                        return -EBADMSG;
         }
 
-        if (p->rindex != saved_rindex + size) {
-                r = -EBADMSG;
-                goto fail;
-        }
+        if (p->rindex != rewinder.saved_rindex + size)
+                return -EBADMSG;
 
         if (start)
-                *start = saved_rindex;
+                *start = rewinder.saved_rindex;
+        CANCEL_REWINDER(rewinder);
 
         return 0;
-fail:
-        dns_packet_rewind(p, saved_rindex);
-        return r;
 }
 
 int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) {
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
         _cleanup_free_ char *name = NULL;
         bool cache_flush = false;
         uint16_t class, type;
         DnsResourceKey *key;
-        size_t saved_rindex;
         int r;
 
         assert(p);
         assert(ret);
-
-        saved_rindex = p->rindex;
+        INIT_REWINDER(rewinder, p);
 
         r = dns_packet_read_name(p, &name, true, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         r = dns_packet_read_uint16(p, &type, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         r = dns_packet_read_uint16(p, &class, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         if (p->protocol == DNS_PROTOCOL_MDNS) {
                 /* See RFC6762, Section 10.2 */
@@ -1555,10 +1544,8 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flus
         }
 
         key = dns_resource_key_new_consume(class, type, name);
-        if (!key) {
-                r = -ENOMEM;
-                goto fail;
-        }
+        if (!key)
+                return -ENOMEM;
 
         name = NULL;
         *ret = key;
@@ -1566,12 +1553,10 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flus
         if (ret_cache_flush)
                 *ret_cache_flush = cache_flush;
         if (start)
-                *start = saved_rindex;
+                *start = rewinder.saved_rindex;
+        CANCEL_REWINDER(rewinder);
 
         return 0;
-fail:
-        dns_packet_rewind(p, saved_rindex);
-        return r;
 }
 
 static bool loc_size_ok(uint8_t size) {
@@ -1583,7 +1568,8 @@ static bool loc_size_ok(uint8_t size) {
 int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) {
         _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
-        size_t saved_rindex, offset;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder;
+        size_t offset;
         uint16_t rdlength;
         bool cache_flush;
         int r;
@@ -1591,27 +1577,22 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
         assert(p);
         assert(ret);
 
-        saved_rindex = p->rindex;
+        INIT_REWINDER(rewinder, p);
 
         r = dns_packet_read_key(p, &key, &cache_flush, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
-        if (!dns_class_is_valid_rr(key->class)||
-            !dns_type_is_valid_rr(key->type)) {
-                r = -EBADMSG;
-                goto fail;
-        }
+        if (!dns_class_is_valid_rr(key->class) || !dns_type_is_valid_rr(key->type))
+                return -EBADMSG;
 
         rr = dns_resource_record_new(key);
-        if (!rr) {
-                r = -ENOMEM;
-                goto fail;
-        }
+        if (!rr)
+                return -ENOMEM;
 
         r = dns_packet_read_uint32(p, &rr->ttl, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
         /* RFC 2181, Section 8, suggests to
          * treat a TTL with the MSB set as a zero TTL. */
@@ -1620,12 +1601,10 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
 
         r = dns_packet_read_uint16(p, &rdlength, NULL);
         if (r < 0)
-                goto fail;
+                return r;
 
-        if (p->rindex + rdlength > p->size) {
-                r = -EBADMSG;
-                goto fail;
-        }
+        if (p->rindex + rdlength > p->size)
+                return -EBADMSG;
 
         offset = p->rindex;
 
@@ -1634,13 +1613,13 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
         case DNS_TYPE_SRV:
                 r = dns_packet_read_uint16(p, &rr->srv.priority, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
                 r = dns_packet_read_uint16(p, &rr->srv.weight, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
                 r = dns_packet_read_uint16(p, &rr->srv.port, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
                 r = dns_packet_read_name(p, &rr->srv.name, true, NULL);
                 break;
 
@@ -1654,7 +1633,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
         case DNS_TYPE_HINFO:
                 r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_string(p, &rr->hinfo.os, NULL);
                 break;
@@ -1710,27 +1689,27 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
         case DNS_TYPE_SOA:
                 r = dns_packet_read_name(p, &rr->soa.mname, true, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_name(p, &rr->soa.rname, true, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint32(p, &rr->soa.serial, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint32(p, &rr->soa.retry, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint32(p, &rr->soa.expire, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL);
                 break;
@@ -1738,7 +1717,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
         case DNS_TYPE_MX:
                 r = dns_packet_read_uint16(p, &rr->mx.priority, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL);
                 break;
@@ -1749,49 +1728,43 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
 
                 r = dns_packet_read_uint8(p, &t, &pos);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 if (t == 0) {
                         rr->loc.version = t;
 
                         r = dns_packet_read_uint8(p, &rr->loc.size, NULL);
                         if (r < 0)
-                                goto fail;
+                                return r;
 
-                        if (!loc_size_ok(rr->loc.size)) {
-                                r = -EBADMSG;
-                                goto fail;
-                        }
+                        if (!loc_size_ok(rr->loc.size))
+                                return -EBADMSG;
 
                         r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL);
                         if (r < 0)
-                                goto fail;
+                                return r;
 
-                        if (!loc_size_ok(rr->loc.horiz_pre)) {
-                                r = -EBADMSG;
-                                goto fail;
-                        }
+                        if (!loc_size_ok(rr->loc.horiz_pre))
+                                return -EBADMSG;
 
                         r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL);
                         if (r < 0)
-                                goto fail;
+                                return r;
 
-                        if (!loc_size_ok(rr->loc.vert_pre)) {
-                                r = -EBADMSG;
-                                goto fail;
-                        }
+                        if (!loc_size_ok(rr->loc.vert_pre))
+                                return -EBADMSG;
 
                         r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL);
                         if (r < 0)
-                                goto fail;
+                                return r;
 
                         r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL);
                         if (r < 0)
-                                goto fail;
+                                return r;
 
                         r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL);
                         if (r < 0)
-                                goto fail;
+                                return r;
 
                         break;
                 } else {
@@ -1804,122 +1777,114 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
         case DNS_TYPE_DS:
                 r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_memdup(p, rdlength - 4,
                                            &rr->ds.digest, &rr->ds.digest_size,
                                            NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
-                if (rr->ds.digest_size <= 0) {
+                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;
-                }
+                        return -EBADMSG;
 
                 break;
 
         case DNS_TYPE_SSHFP:
                 r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_memdup(p, rdlength - 2,
                                            &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size,
                                            NULL);
 
-                if (rr->sshfp.fingerprint_size <= 0) {
+                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;
-                }
+                        return -EBADMSG;
 
                 break;
 
         case DNS_TYPE_DNSKEY:
                 r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_memdup(p, rdlength - 4,
                                            &rr->dnskey.key, &rr->dnskey.key_size,
                                            NULL);
 
-                if (rr->dnskey.key_size <= 0) {
+                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;
-                }
+                        return -EBADMSG;
 
                 break;
 
         case DNS_TYPE_RRSIG:
                 r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_memdup(p, offset + rdlength - p->rindex,
                                            &rr->rrsig.signature, &rr->rrsig.signature_size,
                                            NULL);
 
-                if (rr->rrsig.signature_size <= 0) {
+                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;
-                }
+                        return -EBADMSG;
 
                 break;
 
@@ -1934,11 +1899,9 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
 
                 r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 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
@@ -1951,61 +1914,96 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
 
                 r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 /* this may be zero */
                 r = dns_packet_read_uint8(p, &size, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 r = dns_packet_read_uint8(p, &size, NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
-                if (size <= 0) {
-                        r = -EBADMSG;
-                        goto fail;
-                }
+                if (size <= 0)
+                        return -EBADMSG;
 
-                r = dns_packet_read_memdup(p, size, &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, NULL);
+                r = dns_packet_read_memdup(p, size,
+                                           &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size,
+                                           NULL);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 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;
         }
 
+        case DNS_TYPE_TLSA:
+                r = dns_packet_read_uint8(p, &rr->tlsa.cert_usage, NULL);
+                if (r < 0)
+                        return r;
+
+                r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL);
+                if (r < 0)
+                        return r;
+
+                r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL);
+                if (r < 0)
+                        return r;
+
+                r = dns_packet_read_memdup(p, rdlength - 3,
+                                           &rr->tlsa.data, &rr->tlsa.data_size,
+                                           NULL);
+
+                if (rr->tlsa.data_size <= 0)
+                        /* the accepted size depends on the algorithm, but for now
+                           just ensure that the value is greater than zero */
+                        return -EBADMSG;
+
+                break;
+
+        case DNS_TYPE_CAA:
+                r = dns_packet_read_uint8(p, &rr->caa.flags, NULL);
+                if (r < 0)
+                        return r;
+
+                r = dns_packet_read_string(p, &rr->caa.tag, NULL);
+                if (r < 0)
+                        return r;
+
+                r = dns_packet_read_memdup(p,
+                                           rdlength + offset - p->rindex,
+                                           &rr->caa.value, &rr->caa.value_size, NULL);
+
+                break;
+
         case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */
+        case DNS_TYPE_OPENPGPKEY:
         default:
         unparseable:
-                r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.size, NULL);
-                if (r < 0)
-                        goto fail;
+                r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.data_size, NULL);
+
                 break;
         }
         if (r < 0)
-                goto fail;
-        if (p->rindex != offset + rdlength) {
-                r = -EBADMSG;
-                goto fail;
-        }
+                return r;
+        if (p->rindex != offset + rdlength)
+                return -EBADMSG;
 
         *ret = rr;
         rr = NULL;
@@ -2013,34 +2011,72 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
         if (ret_cache_flush)
                 *ret_cache_flush = cache_flush;
         if (start)
-                *start = saved_rindex;
+                *start = rewinder.saved_rindex;
+        CANCEL_REWINDER(rewinder);
 
         return 0;
-fail:
-        dns_packet_rewind(p, saved_rindex);
-        return r;
+}
+
+static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) {
+        const uint8_t* p;
+        bool found_dau_dhu_n3u = false;
+        size_t l;
+
+        /* Checks whether the specified OPT RR is well-formed and whether it contains RFC6975 data (which is not OK in
+         * a reply). */
+
+        assert(rr);
+        assert(rr->key->type == DNS_TYPE_OPT);
+
+        /* Check that the version is 0 */
+        if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0)
+                return false;
+
+        p = rr->opt.data;
+        l = rr->opt.data_size;
+        while (l > 0) {
+                uint16_t option_code, option_length;
+
+                /* At least four bytes for OPTION-CODE and OPTION-LENGTH are required */
+                if (l < 4U)
+                        return false;
+
+                option_code = unaligned_read_be16(p);
+                option_length = unaligned_read_be16(p + 2);
+
+                if (l < option_length + 4U)
+                        return false;
+
+                /* RFC 6975 DAU, DHU or N3U fields found. */
+                if (IN_SET(option_code, 5, 6, 7))
+                        found_dau_dhu_n3u = true;
+
+                p += option_length + 4U;
+                l -= option_length + 4U;
+        }
+
+        *rfc6975 = found_dau_dhu_n3u;
+        return true;
 }
 
 int dns_packet_extract(DnsPacket *p) {
         _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
-        size_t saved_rindex;
+        _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {};
         unsigned n, i;
         int r;
 
         if (p->extracted)
                 return 0;
 
-        saved_rindex = p->rindex;
+        INIT_REWINDER(rewinder, p);
         dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE);
 
         n = DNS_PACKET_QDCOUNT(p);
         if (n > 0) {
                 question = dns_question_new(n);
-                if (!question) {
-                        r = -ENOMEM;
-                        goto finish;
-                }
+                if (!question)
+                        return -ENOMEM;
 
                 for (i = 0; i < n; i++) {
                         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
@@ -2048,31 +2084,28 @@ int dns_packet_extract(DnsPacket *p) {
 
                         r = dns_packet_read_key(p, &key, &cache_flush, NULL);
                         if (r < 0)
-                                goto finish;
+                                return r;
 
-                        if (cache_flush) {
-                                r = -EBADMSG;
-                                goto finish;
-                        }
+                        if (cache_flush)
+                                return -EBADMSG;
 
-                        if (!dns_type_is_valid_query(key->type)) {
-                                r = -EBADMSG;
-                                goto finish;
-                        }
+                        if (!dns_type_is_valid_query(key->type))
+                                return -EBADMSG;
 
                         r = dns_question_add(question, key);
                         if (r < 0)
-                                goto finish;
+                                return r;
                 }
         }
 
         n = DNS_PACKET_RRCOUNT(p);
         if (n > 0) {
+                _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL;
+                bool bad_opt = false;
+
                 answer = dns_answer_new(n);
-                if (!answer) {
-                        r = -ENOMEM;
-                        goto finish;
-                }
+                if (!answer)
+                        return -ENOMEM;
 
                 for (i = 0; i < n; i++) {
                         _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
@@ -2080,45 +2113,80 @@ int dns_packet_extract(DnsPacket *p) {
 
                         r = dns_packet_read_rr(p, &rr, &cache_flush, NULL);
                         if (r < 0)
-                                goto finish;
+                                return r;
+
+                        /* Try to reduce memory usage a bit */
+                        if (previous)
+                                dns_resource_key_reduce(&rr->key, &previous->key);
 
                         if (rr->key->type == DNS_TYPE_OPT) {
+                                bool has_rfc6975;
+
+                                if (p->opt || bad_opt) {
+                                        /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong
+                                         * with the server, and if one is valid we wouldn't know which one. */
+                                        log_debug("Multiple OPT RRs detected, ignoring all.");
+                                        bad_opt = true;
+                                        continue;
+                                }
 
-                                if (!dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key))) {
-                                        r = -EBADMSG;
-                                        goto finish;
+                                if (!dns_name_is_root(dns_resource_key_name(rr->key))) {
+                                        /* If the OPT RR is not owned by the root domain, then it is bad, let's ignore
+                                         * it. */
+                                        log_debug("OPT RR is not owned by root domain, ignoring.");
+                                        bad_opt = true;
+                                        continue;
                                 }
 
-                                /* The OPT RR is only valid in the Additional section */
                                 if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) {
-                                        r = -EBADMSG;
-                                        goto finish;
+                                        /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint
+                                         * the EDNS implementation is borked, like the Belkin one is, hence ignore
+                                         * it. */
+                                        log_debug("OPT RR in wrong section, ignoring.");
+                                        bad_opt = true;
+                                        continue;
+                                }
+
+                                if (!opt_is_good(rr, &has_rfc6975)) {
+                                        log_debug("Malformed OPT RR, ignoring.");
+                                        bad_opt = true;
+                                        continue;
                                 }
 
-                                /* Two OPT RRs? */
-                                if (p->opt) {
-                                        r = -EBADMSG;
-                                        goto finish;
+                                if (has_rfc6975) {
+                                        /* If the OPT RR contains RFC6975 algorithm data, then this is indication that
+                                         * the server just copied the OPT it got from us (which contained that data)
+                                         * back into the reply. If so, then it doesn't properly support EDNS, as
+                                         * RFC6975 makes it very clear that the algorithm data should only be contained
+                                         * in questions, never in replies. Crappy Belkin routers copy the OPT data for
+                                         * example, hence let's detect this so that we downgrade early. */
+                                        log_debug("OPT RR contained RFC6975 data, ignoring.");
+                                        bad_opt = true;
+                                        continue;
                                 }
 
                                 p->opt = dns_resource_record_ref(rr);
                         } else {
 
-                                /* According to RFC 4795, section
-                                 * 2.9. only the RRs from the Answer
-                                 * section shall be cached. Hence mark
-                                 * only those RRs as cacheable by
-                                 * default, but not the ones from the
-                                 * Additional or Authority
-                                 * sections. */
+                                /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be
+                                 * cached. Hence mark only those RRs as cacheable by default, but not the ones from the
+                                 * Additional or Authority sections. */
 
                                 r = dns_answer_add(answer, rr, p->ifindex,
                                                    (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) |
                                                    (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0));
                                 if (r < 0)
-                                        goto finish;
+                                        return r;
                         }
+
+                        /* Remember this RR, so that we potentically can merge it's ->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);
                 }
+
+                if (bad_opt)
+                        p->opt = dns_resource_record_unref(p->opt);
         }
 
         p->question = question;
@@ -2129,11 +2197,8 @@ int dns_packet_extract(DnsPacket *p) {
 
         p->extracted = true;
 
-        r = 0;
-
-finish:
-        p->rindex = saved_rindex;
-        return r;
+        /* no CANCEL, always rewind */
+        return 0;
 }
 
 int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) {