From: Ronan Pigott Date: Fri, 29 Dec 2023 18:29:13 +0000 (-0700) Subject: resolved: enable RFC9460 SVCB and HTTPS records X-Git-Tag: v256-rc1~1119^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e7634d6b05bb2b707284f251a27b1247677419c8;p=thirdparty%2Fsystemd.git resolved: enable RFC9460 SVCB and HTTPS records --- diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 6c45b1e5ef8..29d9dab0609 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -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) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 2bdcc7c1dcd..3b0fece1775 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -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; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index fd15cc343d8..961d3c78526 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -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);