]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: enable RFC9460 SVCB and HTTPS records
authorRonan Pigott <ronan@rjp.ie>
Fri, 29 Dec 2023 18:29:13 +0000 (11:29 -0700)
committerRonan Pigott <ronan@rjp.ie>
Tue, 16 Jan 2024 17:49:26 +0000 (10:49 -0700)
src/resolve/resolved-dns-packet.c
src/resolve/resolved-dns-rr.c
src/resolve/resolved-dns-rr.h

index 6c45b1e5ef8a3e82b6ab602f41431cc6f2411298..29d9dab060974cc78272e88dc78b8cf069ff789b 100644 (file)
@@ -1166,6 +1166,31 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns
                 r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL);
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                r = dns_packet_append_uint16(p, rr->svcb.priority, NULL);
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_name(p, rr->svcb.target_name, false, false, NULL);
+                if (r < 0)
+                        goto fail;
+
+                LIST_FOREACH(params, i, rr->svcb.params) {
+                        r = dns_packet_append_uint16(p, i->key, NULL);
+                        if (r < 0)
+                                goto fail;
+
+                        r = dns_packet_append_uint16(p, i->length, NULL);
+                        if (r < 0)
+                                goto fail;
+
+                        r = dns_packet_append_blob(p, i->value, i->length, NULL);
+                        if (r < 0)
+                                goto fail;
+                }
+                break;
+
         case DNS_TYPE_CAA:
                 r = dns_packet_append_uint8(p, rr->caa.flags, NULL);
                 if (r < 0)
@@ -1672,6 +1697,41 @@ static bool loc_size_ok(uint8_t size) {
         return m <= 9 && e <= 9 && (m > 0 || e == 0);
 }
 
+static bool dns_svc_param_is_valid(DnsSvcParam *i) {
+        if (!i)
+                return false;
+
+        switch (i->key) {
+        /* RFC 9460, section 7.1.1: alpn-ids must exactly fill SvcParamValue */
+        case DNS_SVC_PARAM_KEY_ALPN: {
+                size_t sz = 0;
+                if (i->length <= 0)
+                        return false;
+                while (sz < i->length)
+                        sz += 1 + i->value[sz]; /* N.B. will not overflow */
+                return sz == i->length;
+        }
+
+        /* RFC 9460, section 7.1.1: value must be empty */
+        case DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN:
+                return i->length == 0;
+
+        /* RFC 9460, section 7.2 */
+        case DNS_SVC_PARAM_KEY_PORT:
+                return i->length == 2;
+
+        /* RFC 9460, section 7.3: addrs must exactly fill SvcParamValue */
+        case DNS_SVC_PARAM_KEY_IPV4HINT:
+                return i->length % (sizeof (struct in_addr)) == 0;
+        case DNS_SVC_PARAM_KEY_IPV6HINT:
+                return i->length % (sizeof (struct in6_addr)) == 0;
+
+        /* Otherwise, permit any value */
+        default:
+                return true;
+        }
+}
+
 int dns_packet_read_rr(
                 DnsPacket *p,
                 DnsResourceRecord **ret,
@@ -2106,6 +2166,52 @@ int dns_packet_read_rr(
 
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                r = dns_packet_read_uint16(p, &rr->svcb.priority, NULL);
+                if (r < 0)
+                        return r;
+
+                r = dns_packet_read_name(p, &rr->svcb.target_name, false /* uncompressed */, NULL);
+                if (r < 0)
+                        return r;
+
+                DnsSvcParam *last = NULL;
+                while (p->rindex - offset < rdlength) {
+                        _cleanup_free_ DnsSvcParam *i = NULL;
+                        uint16_t svc_param_key;
+                        uint16_t sz;
+
+                        r = dns_packet_read_uint16(p, &svc_param_key, NULL);
+                        if (r < 0)
+                                return r;
+                        /* RFC 9460, section 2.2 says we must consider an RR malformed if SvcParamKeys are
+                         * not in strictly increasing order */
+                        if (last && last->key >= svc_param_key)
+                                return -EBADMSG;
+
+                        r = dns_packet_read_uint16(p, &sz, NULL);
+                        if (r < 0)
+                                return r;
+
+                        i = malloc0(offsetof(DnsSvcParam, value) + sz);
+                        if (!i)
+                                return -ENOMEM;
+
+                        i->key = svc_param_key;
+                        i->length = sz;
+                        r = dns_packet_read_blob(p, &i->value, sz, NULL);
+                        if (r < 0)
+                                return r;
+                        if (!dns_svc_param_is_valid(i))
+                                return -EBADMSG;
+
+                        LIST_INSERT_AFTER(params, rr->svcb.params, last, i);
+                        last = TAKE_PTR(i);
+                }
+
+                break;
+
         case DNS_TYPE_CAA:
                 r = dns_packet_read_uint8(p, &rr->caa.flags, NULL);
                 if (r < 0)
index 2bdcc7c1dcd5097966ed25700ede899c6b0862de..3b0fece1775c51df2aa11d820696c0719163b65b 100644 (file)
@@ -469,6 +469,12 @@ static DnsResourceRecord* dns_resource_record_free(DnsResourceRecord *rr) {
                         free(rr->tlsa.data);
                         break;
 
+                case DNS_TYPE_SVCB:
+                case DNS_TYPE_HTTPS:
+                        free(rr->svcb.target_name);
+                        dns_svc_param_free_all(rr->svcb.params);
+                        break;
+
                 case DNS_TYPE_CAA:
                         free(rr->caa.tag);
                         free(rr->caa.value);
@@ -676,6 +682,12 @@ int dns_resource_record_payload_equal(const DnsResourceRecord *a, const DnsResou
                        a->tlsa.matching_type == b->tlsa.matching_type &&
                        FIELD_EQUAL(a->tlsa, b->tlsa, data);
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                return a->svcb.priority == b->svcb.priority &&
+                       dns_name_equal(a->svcb.target_name, b->svcb.target_name) &&
+                       dns_svc_params_equal(a->svcb.params, b->svcb.params);
+
         case DNS_TYPE_CAA:
                 return a->caa.flags == b->caa.flags &&
                        streq(a->caa.tag, b->caa.tag) &&
@@ -1445,6 +1457,16 @@ void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash *
                 siphash24_compress_safe(rr->tlsa.data, rr->tlsa.data_size, state);
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                dns_name_hash_func(rr->svcb.target_name, state);
+                siphash24_compress_typesafe(rr->svcb.priority, state);
+                LIST_FOREACH(params, j, rr->svcb.params) {
+                        siphash24_compress_typesafe(j->key, state);
+                        siphash24_compress_safe(j->value, j->length, state);
+                }
+                break;
+
         case DNS_TYPE_CAA:
                 siphash24_compress_typesafe(rr->caa.flags, state);
                 string_hash_func(rr->caa.tag, state);
@@ -1658,6 +1680,17 @@ DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) {
                 copy->caa.value_size = rr->caa.value_size;
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                copy->svcb.priority = rr->svcb.priority;
+                copy->svcb.target_name = strdup(rr->svcb.target_name);
+                if (!copy->svcb.target_name)
+                        return NULL;
+                copy->svcb.params = dns_svc_params_copy(rr->svcb.params);
+                if (rr->svcb.params && !copy->svcb.params)
+                        return NULL;
+                break;
+
         case DNS_TYPE_OPT:
         default:
                 copy->generic.data = memdup(rr->generic.data, rr->generic.data_size);
@@ -1772,6 +1805,13 @@ DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *first) {
         return NULL;
 }
 
+DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *first) {
+        LIST_FOREACH(params, i, first)
+                free(i);
+
+        return NULL;
+}
+
 bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
         DnsTxtItem *bb = b;
 
@@ -1808,6 +1848,45 @@ DnsTxtItem *dns_txt_item_copy(DnsTxtItem *first) {
         return copy;
 }
 
+bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b) {
+        DnsSvcParam *bb = b;
+
+        if (a == b)
+                return true;
+
+        LIST_FOREACH(params, aa, a) {
+                if (!bb)
+                        return false;
+
+                if (aa->key != bb->key)
+                        return false;
+
+                if (memcmp_nn(aa->value, aa->length, bb->value, bb->length) != 0)
+                        return false;
+
+                bb = bb->params_next;
+        }
+
+        return !bb;
+}
+
+DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first) {
+        DnsSvcParam *copy = NULL, *end = NULL;
+
+        LIST_FOREACH(params, i, first) {
+                DnsSvcParam *j;
+
+                j = memdup(i, offsetof(DnsSvcParam, value) + i->length);
+                if (!j)
+                        return dns_svc_param_free_all(copy);
+
+                LIST_INSERT_AFTER(params, copy, end, j);
+                end = j;
+        }
+
+        return copy;
+}
+
 int dns_txt_item_new_empty(DnsTxtItem **ret) {
         DnsTxtItem *i;
 
index fd15cc343d859ef3b632c4d2bb362532746e59b3..961d3c785265df1897e50a850789665b211ac482 100644 (file)
@@ -16,6 +16,7 @@
 typedef struct DnsResourceKey DnsResourceKey;
 typedef struct DnsResourceRecord DnsResourceRecord;
 typedef struct DnsTxtItem DnsTxtItem;
+typedef struct DnsSvcParam DnsSvcParam;
 
 /* DNSKEY RR flags */
 #define DNSKEY_FLAG_SEP            (UINT16_C(1) << 0)
@@ -90,6 +91,17 @@ struct DnsTxtItem {
         uint8_t data[];
 };
 
+struct DnsSvcParam {
+        uint16_t key;
+        size_t length;
+        LIST_FIELDS(DnsSvcParam, params);
+        union {
+                DECLARE_FLEX_ARRAY(uint8_t, value);
+                DECLARE_FLEX_ARRAY(struct in_addr, value_in_addr);
+                DECLARE_FLEX_ARRAY(struct in6_addr, value_in6_addr);
+        };
+};
+
 struct DnsResourceRecord {
         unsigned n_ref;
         uint32_t ttl;
@@ -243,6 +255,13 @@ struct DnsResourceRecord {
                         uint8_t matching_type;
                 } tlsa;
 
+                /* https://tools.ietf.org/html/rfc9460 */
+                struct {
+                        uint16_t priority;
+                        char *target_name;
+                        DnsSvcParam *params;
+                } svcb, https;
+
                 /* https://tools.ietf.org/html/rfc6844 */
                 struct {
                         char *tag;
@@ -368,6 +387,10 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
 DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i);
 int dns_txt_item_new_empty(DnsTxtItem **ret);
 
+DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *i);
+bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b);
+DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first);
+
 int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, size_t size);
 
 int dns_resource_key_to_json(DnsResourceKey *key, JsonVariant **ret);