]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #30661 from rpigott/resolved-https-record
authorLuca Boccassi <bluca@debian.org>
Wed, 17 Jan 2024 16:20:35 +0000 (16:20 +0000)
committerGitHub <noreply@github.com>
Wed, 17 Jan 2024 16:20:35 +0000 (16:20 +0000)
resolved: support RFC 9460 SVCB and HTTPS records

src/basic/escape.c
src/basic/escape.h
src/resolve/resolved-dns-packet.c
src/resolve/resolved-dns-packet.h
src/resolve/resolved-dns-rr.c
src/resolve/resolved-dns-rr.h
src/shared/varlink-io.systemd.Resolve.Monitor.c
src/test/test-escape.c
test/knot-data/zones/test.zone
test/units/testsuite-75.sh

index 75a1d68e967254a73b5ffdde951fb2ffd2798241..d95f35e79807ff470c808837996bc702179205be 100644 (file)
@@ -471,6 +471,33 @@ char* octescape(const char *s, size_t len) {
         return buf;
 }
 
+char* decescape(const char *s, const char *bad, size_t len) {
+        char *buf, *t;
+
+        /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */
+
+        assert(s || len == 0);
+
+        t = buf = new(char, len * 4 + 1);
+        if (!buf)
+                return NULL;
+
+        for (size_t i = 0; i < len; i++) {
+                uint8_t u = (uint8_t) s[i];
+
+                if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || strchr(bad, u)) {
+                        *(t++) = '\\';
+                        *(t++) = '0' + (u / 100);
+                        *(t++) = '0' + ((u / 10) % 10);
+                        *(t++) = '0' + (u % 10);
+                } else
+                        *(t++) = u;
+        }
+
+        *t = 0;
+        return buf;
+}
+
 static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
         assert(bad);
         assert(t);
index 318da6f220347fb6cb1a880a80e64371c07cb1f4..65caf0dbcfa3b1660fdfadd500115ad8a1850f9b 100644 (file)
@@ -65,6 +65,7 @@ static inline char* xescape(const char *s, const char *bad) {
         return xescape_full(s, bad, SIZE_MAX, 0);
 }
 char* octescape(const char *s, size_t len);
+char* decescape(const char *s, const char *bad, size_t len);
 char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags);
 
 char* shell_escape(const char *s, const char *bad);
index 44e1e4faabf3390bf7f0cbe4c7a84a673421c162..a7d04449b2f78bb33bd4bb36d8268fdbac79a4b9 100644 (file)
@@ -1183,6 +1183,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)
@@ -1689,6 +1714,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,
@@ -2123,6 +2183,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)
@@ -2801,6 +2907,27 @@ const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]) {
         return snprintf_ok(buf, DECIMAL_STR_MAX(int), "%i", i);
 }
 
+static const char* const dns_svc_param_key_table[_DNS_SVC_PARAM_KEY_MAX_DEFINED] = {
+        [DNS_SVC_PARAM_KEY_MANDATORY]       = "mandatory",
+        [DNS_SVC_PARAM_KEY_ALPN]            = "alpn",
+        [DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN] = "no-default-alpn",
+        [DNS_SVC_PARAM_KEY_PORT]            = "port",
+        [DNS_SVC_PARAM_KEY_IPV4HINT]        = "ipv4hint",
+        [DNS_SVC_PARAM_KEY_ECH]             = "ech",
+        [DNS_SVC_PARAM_KEY_IPV6HINT]        = "ipv6hint",
+        [DNS_SVC_PARAM_KEY_DOHPATH]         = "dohpath",
+        [DNS_SVC_PARAM_KEY_OHTTP]           = "ohttp",
+};
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_svc_param_key, int);
+
+const char *format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]) {
+        const char *p = dns_svc_param_key_to_string(i);
+        if (p)
+                return p;
+
+        return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i);
+}
+
 static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = {
         [DNS_PROTOCOL_DNS]   = "dns",
         [DNS_PROTOCOL_MDNS]  = "mdns",
index 705fc511d6bd9bc9b3ccfef95de77db27d0bb0af..393b7b2364a5b5bcedf2929b974f61823e47f9f0 100644 (file)
@@ -361,6 +361,25 @@ const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]);
 const char* dns_protocol_to_string(DnsProtocol p) _const_;
 DnsProtocol dns_protocol_from_string(const char *s) _pure_;
 
+/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */
+enum {
+        DNS_SVC_PARAM_KEY_MANDATORY        = 0,     /* RFC 9460 section 8 */
+        DNS_SVC_PARAM_KEY_ALPN             = 1,     /* RFC 9460 section 7.1 */
+        DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN  = 2,     /* RFC 9460 Section 7.1 */
+        DNS_SVC_PARAM_KEY_PORT             = 3,     /* RFC 9460 section 7.2 */
+        DNS_SVC_PARAM_KEY_IPV4HINT         = 4,     /* RFC 9460 section 7.3 */
+        DNS_SVC_PARAM_KEY_ECH              = 5,     /* RFC 9460 */
+        DNS_SVC_PARAM_KEY_IPV6HINT         = 6,     /* RFC 9460 section 7.3 */
+        DNS_SVC_PARAM_KEY_DOHPATH          = 7,     /* RFC 9461 */
+        DNS_SVC_PARAM_KEY_OHTTP            = 8,
+        _DNS_SVC_PARAM_KEY_MAX_DEFINED,
+        DNS_SVC_PARAM_KEY_INVALID          = 65535  /* RFC 9460 */
+};
+
+const char* dns_svc_param_key_to_string(int i) _const_;
+const char *format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]);
+#define FORMAT_DNS_SVC_PARAM_KEY(i) format_dns_svc_param_key(i, (char [DECIMAL_STR_MAX(uint16_t)+3]) {})
+
 #define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) })
 #define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })
 
index 2bdcc7c1dcd5097966ed25700ede899c6b0862de..7d824ee806526f6338609fdfe7b69b795d60922f 100644 (file)
@@ -15,6 +15,7 @@
 #include "string-util.h"
 #include "strv.h"
 #include "terminal-util.h"
+#include "unaligned.h"
 
 DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) {
         DnsResourceKey *k;
@@ -469,6 +470,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 +683,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) &&
@@ -814,6 +827,107 @@ static char *format_txt(DnsTxtItem *first) {
         return s;
 }
 
+static char *format_svc_param_value(DnsSvcParam *i) {
+        _cleanup_free_ char *value = NULL;
+
+        assert(i);
+
+        switch (i->key) {
+        case DNS_SVC_PARAM_KEY_ALPN: {
+                size_t offset = 0;
+                _cleanup_strv_free_ char **values_strv = NULL;
+                while (offset < i->length) {
+                        size_t sz = (uint8_t) i->value[offset++];
+
+                        char *alpn = cescape_length((char *)&i->value[offset], sz);
+                        if (!alpn)
+                                return NULL;
+
+                        if (strv_push(&values_strv, alpn) < 0)
+                                return NULL;
+
+                        offset += sz;
+                }
+                value = strv_join(values_strv, ",");
+                if (!value)
+                        return NULL;
+                break;
+
+        }
+        case DNS_SVC_PARAM_KEY_PORT: {
+                uint16_t port = unaligned_read_be16(i->value);
+                if (asprintf(&value, "%" PRIu16, port) < 0)
+                        return NULL;
+                return TAKE_PTR(value);
+        }
+        case DNS_SVC_PARAM_KEY_IPV4HINT: {
+                const struct in_addr *addrs = i->value_in_addr;
+                _cleanup_strv_free_ char **values_strv = NULL;
+                for (size_t n = 0; n < i->length / sizeof (struct in_addr); n++) {
+                        char *addr;
+                        if (in_addr_to_string(AF_INET, (const union in_addr_union*) &addrs[n], &addr) < 0)
+                                return NULL;
+                        if (strv_push(&values_strv, addr) < 0)
+                                return NULL;
+                }
+                return strv_join(values_strv, ",");
+        }
+        case DNS_SVC_PARAM_KEY_IPV6HINT: {
+                const struct in6_addr *addrs = i->value_in6_addr;
+                _cleanup_strv_free_ char **values_strv = NULL;
+                for (size_t n = 0; n < i->length / sizeof (struct in6_addr); n++) {
+                        char *addr;
+                        if (in_addr_to_string(AF_INET6, (const union in_addr_union*) &addrs[n], &addr) < 0)
+                                return NULL;
+                        if (strv_push(&values_strv, addr) < 0)
+                                return NULL;
+                }
+                return strv_join(values_strv, ",");
+        }
+        default: {
+                value = decescape((char *)&i->value, " ,", i->length);
+                if (!value)
+                        return NULL;
+                break;
+        }
+        }
+
+        char *qvalue;
+        if (asprintf(&qvalue, "\"%s\"", value) < 0)
+                return NULL;
+        return qvalue;
+}
+
+static char *format_svc_param(DnsSvcParam *i) {
+        const char *key = FORMAT_DNS_SVC_PARAM_KEY(i->key);
+        _cleanup_free_ char *value = NULL;
+
+        assert(i);
+
+        if (i->length == 0)
+                return strdup(key);
+
+        value = format_svc_param_value(i);
+        if (!value)
+                return NULL;
+
+        return strjoin(key, "=", value);
+}
+
+static char *format_svc_params(DnsSvcParam *first) {
+        _cleanup_strv_free_ char **params = NULL;
+
+        LIST_FOREACH(params, i, first) {
+                char *param = format_svc_param(i);
+                if (!param)
+                        return NULL;
+                if (strv_push(&params, param) < 0)
+                        return NULL;
+        }
+
+        return strv_join(params, " ");
+}
+
 const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
         _cleanup_free_ char *s = NULL, *t = NULL;
         char k[DNS_RESOURCE_KEY_STRING_MAX];
@@ -1124,6 +1238,19 @@ const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
 
                 break;
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS:
+                t = format_svc_params(rr->svcb.params);
+                if (!t)
+                        return NULL;
+                r = asprintf(&s, "%s %d %s %s", k, rr->svcb.priority,
+                             isempty(rr->svcb.target_name) ? "." : rr->svcb.target_name,
+                             t);
+                if (r < 0)
+                        return NULL;
+
+                break;
+
         case DNS_TYPE_OPENPGPKEY:
                 r = asprintf(&s, "%s", k);
                 if (r < 0)
@@ -1445,6 +1572,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 +1795,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 +1920,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 +1963,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;
 
@@ -1930,10 +2124,33 @@ static int txt_to_json(DnsTxtItem *items, JsonVariant **ret) {
         r = json_variant_new_array(ret, elements, n);
 
 finalize:
-        for (size_t i = 0; i < n; i++)
-                json_variant_unref(elements[i]);
+        json_variant_unref_many(elements, n);
+        return r;
+}
+
+static int svc_params_to_json(DnsSvcParam *params, JsonVariant **ret) {
+        JsonVariant **elements = NULL;
+        size_t n = 0;
+        int r;
+
+        assert(ret);
+
+        LIST_FOREACH(params, i, params) {
+                if (!GREEDY_REALLOC(elements, n + 1)) {
+                        r = -ENOMEM;
+                        goto finalize;
+                }
+
+                r = json_variant_new_base64(elements + n, i->value, i->length);
+                if (r < 0)
+                        goto finalize;
+
+                n++;
+        }
 
-        free(elements);
+        r = json_variant_new_array(ret, elements, n);
+finalize:
+        json_variant_unref_many(elements, n);
         return r;
 }
 
@@ -2112,6 +2329,21 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret) {
                                                   JSON_BUILD_PAIR("matchingType", JSON_BUILD_UNSIGNED(rr->tlsa.matching_type)),
                                                   JSON_BUILD_PAIR("data", JSON_BUILD_HEX(rr->tlsa.data, rr->tlsa.data_size))));
 
+        case DNS_TYPE_SVCB:
+        case DNS_TYPE_HTTPS: {
+                _cleanup_(json_variant_unrefp) JsonVariant *p = NULL;
+                r = svc_params_to_json(rr->svcb.params, &p);
+                if (r < 0)
+                        return r;
+
+                return json_build(ret,
+                                  JSON_BUILD_OBJECT(
+                                                  JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)),
+                                                  JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->svcb.priority)),
+                                                  JSON_BUILD_PAIR("target", JSON_BUILD_STRING(rr->svcb.target_name)),
+                                                  JSON_BUILD_PAIR("params", JSON_BUILD_VARIANT(p))));
+        }
+
         case DNS_TYPE_CAA:
                 return json_build(ret,
                                   JSON_BUILD_OBJECT(
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);
index 96a58ca768d1b25c686cb6d0206390aa0b1793db..e46d1975d7bf0997b6386a76ac804a859633e560 100644 (file)
@@ -59,7 +59,9 @@ VARLINK_DEFINE_STRUCT_TYPE(
                 VARLINK_DEFINE_FIELD(matchingType, VARLINK_INT, VARLINK_NULLABLE),
                 VARLINK_DEFINE_FIELD(data, VARLINK_STRING, VARLINK_NULLABLE),
                 VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE),
-                VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE));
+                VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE),
+                VARLINK_DEFINE_FIELD(target, VARLINK_STRING, VARLINK_NULLABLE),
+                VARLINK_DEFINE_FIELD(params, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY));
 
 VARLINK_DEFINE_STRUCT_TYPE(
                 ResourceRecordArray,
index 21786ae72aaa856cbeb07632ab444643da6cdfcd..364e0f395643ab54c7937b0cf5013ad2007fbe96 100644 (file)
@@ -239,4 +239,22 @@ TEST(octescape) {
         test_octescape_one("\123\213\222", "\123\\213\\222");
 }
 
+static void test_decescape_one(const char *s, const char *bad, const char *expected) {
+        _cleanup_free_ char *ret = NULL;
+
+        assert_se(ret = decescape(s, bad, strlen_ptr(s)));
+        log_debug("decescape(\"%s\") → \"%s\" (expected: \"%s\")", strnull(s), ret, expected);
+        assert_se(streq(ret, expected));
+}
+
+TEST(decescape) {
+        test_decescape_one(NULL, "bad", "");
+        test_decescape_one("foo", "", "foo");
+        test_decescape_one("foo", "f", "\\102oo");
+        test_decescape_one("foo", "o", "f\\111\\111");
+        test_decescape_one("go\"bb\\ledyg\x03ook\r\n", "", "go\\034bb\\092ledyg\\003ook\\013\\010");
+        test_decescape_one("\\xff\xff" "f", "f", "\\092x\\102\\102\\255\\102");
+        test_decescape_one("all", "all", "\\097\\108\\108");
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index ba5fcebc2d31b2d0f86eeedbf542004165deab61..065ff7e2a00f92414eefae3a2d8483801142d40b 100644 (file)
@@ -19,3 +19,6 @@ ns1.unsigned          AAAA fd00:dead:beef:cafe::1
 onlinesign            NS   ns1.unsigned
 signed                NS   ns1.unsigned
 unsigned              NS   ns1.unsigned
+
+svcb                  SVCB  1   .   alpn=dot ipv4hint=10.0.0.1 ipv6hint=fd00:dead:beef:cafe::1
+https                 HTTPS 1   .   alpn="h2,h3"
index f1fb5d943a5ff5b18ab62cb1511dd069b535ced6..92f6b8e030dd7cc0a891b4a38717948b27e5cc2f 100755 (executable)
@@ -371,6 +371,12 @@ run dig +noall +authority +comments SRV .
 grep -qF "status: NOERROR" "$RUN_OUT"
 grep -qE "IN\s+SOA\s+ns1\.unsigned\.test\." "$RUN_OUT"
 
+run resolvectl query -t SVCB svcb.test
+grep -qF 'alpn="dot"' "$RUN_OUT"
+grep -qF "ipv4hint=10.0.0.1" "$RUN_OUT"
+
+run resolvectl query -t HTTPS https.test
+grep -qF 'alpn="h2,h3"' "$RUN_OUT"
 
 : "--- ZONE: unsigned.test. ---"
 run dig @ns1.unsigned.test +short unsigned.test A unsigned.test AAAA