]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-packet.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / resolve / resolved-dns-packet.c
index 6a9a1f732dd5ca43c6383a9b26f7b79804f5cb69..40f35475fdce63344927c304e969029adc2ba615 100644 (file)
@@ -1,5 +1,4 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -30,6 +29,8 @@
 
 #define EDNS0_OPT_DO (1<<15)
 
+assert_cc(DNS_PACKET_SIZE_START > DNS_PACKET_HEADER_SIZE)
+
 typedef struct DnsPacketRewinder {
         DnsPacket *packet;
         size_t saved_rindex;
@@ -40,29 +41,47 @@ 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 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 min_alloc_dsize,
+                size_t max_size) {
 
-int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
         DnsPacket *p;
         size_t a;
 
         assert(ret);
+        assert(max_size >= DNS_PACKET_HEADER_SIZE);
+
+        if (max_size > DNS_PACKET_SIZE_MAX)
+                max_size = DNS_PACKET_SIZE_MAX;
 
-        if (mtu <= UDP_PACKET_HEADER_SIZE)
+        /* The caller may not check what is going to be truly allocated, so do not allow to
+         * allocate a DNS packet bigger than DNS_PACKET_SIZE_MAX.
+         */
+        if (min_alloc_dsize > DNS_PACKET_SIZE_MAX) {
+                log_error("Requested packet data size too big: %zu", min_alloc_dsize);
+                return -EFBIG;
+        }
+
+        /* When dns_packet_new() is called with min_alloc_dsize == 0, allocate more than the
+         * absolute minimum (which is the dns packet header size), to avoid
+         * resizing immediately again after appending the first data to the packet.
+         */
+        if (min_alloc_dsize < DNS_PACKET_HEADER_SIZE)
                 a = DNS_PACKET_SIZE_START;
         else
-                a = mtu - UDP_PACKET_HEADER_SIZE;
-
-        if (a < DNS_PACKET_HEADER_SIZE)
-                a = DNS_PACKET_HEADER_SIZE;
+                a = min_alloc_dsize;
 
         /* round up to next page size */
         a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket));
 
         /* make sure we never allocate more than useful */
-        if (a > DNS_PACKET_SIZE_MAX)
-                a = DNS_PACKET_SIZE_MAX;
+        if (a > max_size)
+                a = max_size;
 
         p = malloc0(ALIGN(sizeof(DnsPacket)) + a);
         if (!p)
@@ -70,6 +89,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
 
         p->size = p->rindex = DNS_PACKET_HEADER_SIZE;
         p->allocated = a;
+        p->max_size = max_size;
         p->protocol = protocol;
         p->opt_start = p->opt_size = (size_t) -1;
         p->n_ref = 1;
@@ -129,13 +149,13 @@ void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool trun
         }
 }
 
-int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) {
+int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t min_alloc_dsize, bool dnssec_checking_disabled) {
         DnsPacket *p;
         int r;
 
         assert(ret);
 
-        r = dns_packet_new(&p, protocol, mtu);
+        r = dns_packet_new(&p, protocol, min_alloc_dsize, DNS_PACKET_SIZE_MAX);
         if (r < 0)
                 return r;
 
@@ -266,6 +286,7 @@ int dns_packet_validate_query(DnsPacket *p) {
         switch (p->protocol) {
 
         case DNS_PROTOCOL_LLMNR:
+        case DNS_PROTOCOL_DNS:
                 /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */
                 if (DNS_PACKET_QDCOUNT(p) != 1)
                         return -EBADMSG;
@@ -303,11 +324,13 @@ static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start
         assert(p);
 
         if (p->size + add > p->allocated) {
-                size_t a;
+                size_t a, ms;
 
                 a = PAGE_ALIGN((p->size + add) * 2);
-                if (a > DNS_PACKET_SIZE_MAX)
-                        a = DNS_PACKET_SIZE_MAX;
+
+                ms = dns_packet_size_max(p);
+                if (a > ms)
+                        a = ms;
 
                 if (p->size + add > a)
                         return -EMSGSIZE;
@@ -570,8 +593,9 @@ fail:
         return r;
 }
 
-int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) {
+int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, const DnsAnswerFlags flags, size_t *start) {
         size_t saved_size;
+        uint16_t class;
         int r;
 
         assert(p);
@@ -579,7 +603,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;
 
@@ -587,7 +611,8 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start)
         if (r < 0)
                 goto fail;
 
-        r = dns_packet_append_uint16(p, k->class, NULL);
+        class = flags & DNS_ANSWER_CACHE_FLUSH ? k->class | MDNS_RR_CACHE_FLUSH : k->class;
+        r = dns_packet_append_uint16(p, class, NULL);
         if (r < 0)
                 goto fail;
 
@@ -678,13 +703,15 @@ fail:
 }
 
 /* Append the OPT pseudo-RR described in RFC6891 */
-int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) {
+int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, int rcode, 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);
+        assert(rcode >= 0);
+        assert(rcode <= _DNS_RCODE_MAX);
 
         if (p->opt_start != (size_t) -1)
                 return -EBUSY;
@@ -703,13 +730,13 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, si
         if (r < 0)
                 goto fail;
 
-        /* maximum udp packet that can be received */
+        /* class: 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);
+        r = dns_packet_append_uint16(p, ((uint16_t) rcode & 0x0FF0) << 4, NULL);
         if (r < 0)
                 goto fail;
 
@@ -719,9 +746,8 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, si
                 goto fail;
 
         /* RDLENGTH */
-
-        if (edns0_do) {
-                /* If DO is on, also append RFC6975 Algorithm data */
+        if (edns0_do && !DNS_PACKET_QR(p)) {
+                /* If DO is on and this is not a reply, also append RFC6975 Algorithm data */
 
                 static const uint8_t rfc6975[] = {
 
@@ -752,7 +778,6 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, si
                 r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL);
         } else
                 r = dns_packet_append_uint16(p, 0, NULL);
-
         if (r < 0)
                 goto fail;
 
@@ -792,8 +817,10 @@ int dns_packet_truncate_opt(DnsPacket *p) {
         return 1;
 }
 
-int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) {
+int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAnswerFlags flags, size_t *start, size_t *rdata_start) {
+
         size_t saved_size, rdlength_offset, end, rdlength, rds;
+        uint32_t ttl;
         int r;
 
         assert(p);
@@ -801,11 +828,12 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
 
         saved_size = p->size;
 
-        r = dns_packet_append_key(p, rr->key, NULL);
+        r = dns_packet_append_key(p, rr->key, flags, NULL);
         if (r < 0)
                 goto fail;
 
-        r = dns_packet_append_uint32(p, rr->ttl, NULL);
+        ttl = flags & DNS_ANSWER_GOODBYE ? 0 : rr->ttl;
+        r = dns_packet_append_uint32(p, ttl, NULL);
         if (r < 0)
                 goto fail;
 
@@ -1086,6 +1114,18 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
                 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 */
@@ -1124,6 +1164,37 @@ fail:
         return r;
 }
 
+int dns_packet_append_question(DnsPacket *p, DnsQuestion *q) {
+        DnsResourceKey *key;
+        int r;
+
+        assert(p);
+
+        DNS_QUESTION_FOREACH(key, q) {
+                r = dns_packet_append_key(p, key, 0, NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a) {
+        DnsResourceRecord *rr;
+        DnsAnswerFlags flags;
+        int r;
+
+        assert(p);
+
+        DNS_ANSWER_FOREACH_FLAGS(rr, flags, a) {
+                r = dns_packet_append_rr(p, rr, flags, NULL, NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) {
         assert(p);
 
@@ -1444,7 +1515,7 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
 
                 found = true;
 
-                while (bitmask) {
+                for (; bitmask; bit++, bitmask >>= 1)
                         if (bitmap[i] & bitmask) {
                                 uint16_t n;
 
@@ -1458,10 +1529,6 @@ static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *sta
                                 if (r < 0)
                                         return r;
                         }
-
-                        bit ++;
-                        bitmask >>= 1;
-                }
         }
 
         if (!found)
@@ -1967,6 +2034,21 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
 
                 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:
@@ -2004,8 +2086,10 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) {
         assert(rr->key->type == DNS_TYPE_OPT);
 
         /* Check that the version is 0 */
-        if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0)
-                return false;
+        if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) {
+                *rfc6975 = false;
+                return true; /* if it's not version 0, it's OK, but we will ignore the OPT field contents */
+        }
 
         p = rr->opt.data;
         l = rr->opt.data_size;
@@ -2084,7 +2168,7 @@ int dns_packet_extract(DnsPacket *p) {
 
                 for (i = 0; i < n; i++) {
                         _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
-                        bool cache_flush;
+                        bool cache_flush = false;
 
                         r = dns_packet_read_rr(p, &rr, &cache_flush, NULL);
                         if (r < 0)
@@ -2105,8 +2189,8 @@ int dns_packet_extract(DnsPacket *p) {
                                         continue;
                                 }
 
-                                if (!dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key))) {
-                                        /* If the OPT RR qis not owned by the root domain, then it is bad, let's ignore
+                                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;
@@ -2128,16 +2212,27 @@ int dns_packet_extract(DnsPacket *p) {
                                         continue;
                                 }
 
-                                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;
+                                if (DNS_PACKET_QR(p)) {
+                                        /* Additional checks for responses */
+
+                                        if (!DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(rr)) {
+                                                /* If this is a reply and we don't know the EDNS version then something
+                                                 * is weird... */
+                                                log_debug("EDNS version newer that our request, bad server.");
+                                                return -EBADMSG;
+                                        }
+
+                                        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);
@@ -2194,6 +2289,9 @@ int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) {
         if (r < 0)
                 return r;
 
+        if (!p->question)
+                return 0;
+
         if (p->question->n_keys != 1)
                 return 0;
 
@@ -2219,6 +2317,7 @@ static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
         [DNS_RCODE_BADNAME] = "BADNAME",
         [DNS_RCODE_BADALG] = "BADALG",
         [DNS_RCODE_BADTRUNC] = "BADTRUNC",
+        [DNS_RCODE_BADCOOKIE] = "BADCOOKIE",
 };
 DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int);