From 427166c3b0775e8ea929b6bf68fdd82aac168d3f Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Tue, 23 Jan 2024 12:29:32 -0700 Subject: [PATCH 01/20] dns: introduce dns_name_from_wire_format This is implemented in various places, but it is better to share this code. --- src/shared/dns-domain.c | 69 +++++++++++++++++++++++++++++ src/shared/dns-domain.h | 1 + src/test/test-dns-domain.c | 89 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index ba24a77dad..48c59b064f 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -898,6 +898,75 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo return out - buffer; } +/* Decode a domain name according to RFC 1035 Section 3.1, without compression */ +int dns_name_from_wire_format(const uint8_t **data, size_t *len, char **ret) { + _cleanup_free_ char *domain = NULL; + const uint8_t *optval; + size_t optlen, n = 0; + int r; + + assert(data); + assert(len); + assert(*data || *len == 0); + assert(ret); + + optval = *data; + optlen = *len; + + for (;;) { + const char *label; + uint8_t c; + + /* Unterminated name */ + if (optlen == 0) + return -EBADMSG; + + /* RFC 1035 § 3.1 total length of encoded name is limited to 255 octets */ + if (*len - optlen > 255) + return -EMSGSIZE; + + c = *optval; + optval++; + optlen--; + + if (c == 0) + /* End label */ + break; + if (c > DNS_LABEL_MAX) + return -EBADMSG; + if (c > optlen) + return -EMSGSIZE; + + /* Literal label */ + label = (const char*) optval; + optval += c; + optlen -= c; + + if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (n != 0) + domain[n++] = '.'; + + r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + } + + if (!GREEDY_REALLOC(domain, n + 1)) + return -ENOMEM; + + domain[n] = '\0'; + + *ret = TAKE_PTR(domain); + *data = optval; + *len = optlen; + + return n; +} + static bool srv_type_label_is_valid(const char *label, size_t n) { assert(label); diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 8ad00d6e4b..edfb2f00ca 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -79,6 +79,7 @@ bool dns_name_is_root(const char *name); bool dns_name_is_single_label(const char *name); int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical); +int dns_name_from_wire_format(const uint8_t **data, size_t *len, char **ret); bool dns_srv_type_is_valid(const char *name); bool dnssd_srv_type_is_valid(const char *name); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index d775601dae..de66199f54 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -119,6 +119,95 @@ TEST(dns_name_to_wire_format) { test_dns_name_to_wire_format_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", out4, sizeof(out4), sizeof(out4)); } +static void test_dns_name_from_wire_format_one(const char *expect, const uint8_t *what, size_t len, int ret) { + _cleanup_free_ char *name = NULL; + int r; + + log_info("%s, %s, %zu, →%d", what, strnull(expect), len, ret); + + r = dns_name_from_wire_format(&what, &len, &name); + assert_se(r == ret); + + if (r >= 0) { + assert(expect); /* for gcc */ + assert_se(memcmp(name, expect, r) == 0); + } +} + +TEST(dns_name_from_wire_format) { + static const uint8_t in0[] = { 0 }; + static const uint8_t in1[] = { 3, 'f', 'o', 'o', 0 }; + static const uint8_t in2[] = { 5, 'h', 'a', 'l', 'l', 'o', 3, 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 }; + static const uint8_t in2_1[] = { 5, 'h', 'a', 'l', 'l', 'o', 3, 'f', 'o', 'o', 0, 'b', 'a', 'r', 0 }; + static const uint8_t in3[] = { 4, ' ', 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 }; + static const uint8_t in4[] = { 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 3, 'a', '1', '2', 0 }; /* 255 octets */ + static const uint8_t in5[] = { 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 3, 'a', '1', '2', 0 }; /* 265 octets */ + + test_dns_name_from_wire_format_one("", in0, sizeof(in0), strlen("")); + + test_dns_name_from_wire_format_one("foo", in1, sizeof(in1), strlen("foo")); + test_dns_name_from_wire_format_one(NULL, in1, sizeof(in1) - 1, -EBADMSG); + + test_dns_name_from_wire_format_one("hallo.foo.bar", in2, sizeof(in2), strlen("hallo.foo.bar")); + test_dns_name_from_wire_format_one("hallo.foo", in2_1, sizeof(in2_1), strlen("hallo.foo")); + + test_dns_name_from_wire_format_one("\\032foo.bar", in3, sizeof(in3), strlen("\\032foo.bar")); + + test_dns_name_from_wire_format_one(NULL, in5, sizeof(in5), -EMSGSIZE); + test_dns_name_from_wire_format_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", in4, sizeof(in4), 253); +} + static void test_dns_label_unescape_suffix_one(const char *what, const char *expect1, const char *expect2, size_t buffer_sz, int ret1, int ret2) { char buffer[buffer_sz]; const char *label; From 1e2ead52e1796e65a13d73cd8fc792390bd4fad4 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Fri, 23 Feb 2024 17:12:46 -0700 Subject: [PATCH 02/20] network: Introduce sd_dns_resolver This type will be used to represent a "designated resolver", and the necessary info for communicating with it. Beyond and address endpoint, we may need to know the dns transport, authenticated domain name, DoH path, etc. --- src/resolve/resolved-dns-packet.c | 23 +- src/resolve/resolved-dns-packet.h | 19 -- src/resolve/resolved-dns-rr.c | 2 + src/shared/dns-resolver-internal.h | 57 +++++ src/shared/meson.build | 1 + src/shared/sd-dns-resolver.c | 362 +++++++++++++++++++++++++++++ src/shared/socket-netlink.c | 9 + src/shared/socket-netlink.h | 1 + src/systemd/meson.build | 1 + src/systemd/sd-dns-resolver.h | 44 ++++ 10 files changed, 479 insertions(+), 40 deletions(-) create mode 100644 src/shared/dns-resolver-internal.h create mode 100644 src/shared/sd-dns-resolver.c create mode 100644 src/systemd/sd-dns-resolver.h diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index c32a1a9a67..1ab09f26d6 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -4,6 +4,8 @@ # include #endif +#include "dns-resolver-internal.h" + #include "alloc-util.h" #include "dns-domain.h" #include "escape.h" @@ -2955,27 +2957,6 @@ 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", diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 13d65a02c3..ddee259b1f 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -362,25 +362,6 @@ 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 } }) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 6a4bd6aecd..ed525883f2 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -2,6 +2,8 @@ #include +#include "dns-resolver-internal.h" + #include "alloc-util.h" #include "dns-domain.h" #include "dns-type.h" diff --git a/src/shared/dns-resolver-internal.h b/src/shared/dns-resolver-internal.h new file mode 100644 index 0000000000..08ef782408 --- /dev/null +++ b/src/shared/dns-resolver-internal.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sd-dns-resolver.h" + +#include "macro.h" +#include "list.h" +#include "socket-netlink.h" + +/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */ +enum { + DNS_SVC_PARAM_KEY_MANDATORY = 0, /* RFC 9460 § 8 */ + DNS_SVC_PARAM_KEY_ALPN = 1, /* RFC 9460 § 7.1 */ + DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN = 2, /* RFC 9460 § 7.1 */ + DNS_SVC_PARAM_KEY_PORT = 3, /* RFC 9460 § 7.2 */ + DNS_SVC_PARAM_KEY_IPV4HINT = 4, /* RFC 9460 § 7.3 */ + DNS_SVC_PARAM_KEY_ECH = 5, /* RFC 9460 */ + DNS_SVC_PARAM_KEY_IPV6HINT = 6, /* RFC 9460 § 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]) {}) + +/* Represents a "designated resolver" */ +/* typedef struct sd_dns_resolver sd_dns_resolver; */ +struct sd_dns_resolver { + uint16_t priority; + char *auth_name; + int family; + union in_addr_union *addrs; + size_t n_addrs; + sd_dns_alpn_flags transports; + uint16_t port; + char *dohpath; +}; + +void siphash24_compress_resolver(const sd_dns_resolver *res, struct siphash *state); + +int dns_resolvers_to_dot_addrs(const sd_dns_resolver *resolvers, size_t n_resolvers, + struct in_addr_full ***ret_addrs, size_t *ret_n_addrs); + +int dns_resolver_prio_compare(const sd_dns_resolver *a, const sd_dns_resolver *b); + +int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *resolver); + +int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolvers, char ***ret_names); + +void sd_dns_resolver_done(sd_dns_resolver *res); + +void dns_resolver_done_many(sd_dns_resolver *resolvers, size_t n); diff --git a/src/shared/meson.build b/src/shared/meson.build index 8fc39429e5..548b26d8c3 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -154,6 +154,7 @@ shared_sources = files( 'resize-fs.c', 'resolve-util.c', 'rm-rf.c', + 'sd-dns-resolver.c', 'securebits-util.c', 'selinux-util.c', 'serialize.c', diff --git a/src/shared/sd-dns-resolver.c b/src/shared/sd-dns-resolver.c new file mode 100644 index 0000000000..4c76df18d1 --- /dev/null +++ b/src/shared/sd-dns-resolver.c @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dns-resolver-internal.h" +#include "macro.h" +#include "unaligned.h" +#include "socket-netlink.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" + +void sd_dns_resolver_done(sd_dns_resolver *res) { + assert(res); + + res->auth_name = mfree(res->auth_name); + res->addrs = mfree(res->addrs); + res->dohpath = mfree(res->dohpath); +} + +sd_dns_resolver *sd_dns_resolver_unref(sd_dns_resolver *res) { + if (!res) + return NULL; + + sd_dns_resolver_done(res); + return mfree(res); +} + +void dns_resolver_done_many(sd_dns_resolver resolvers[], size_t n) { + assert(resolvers || n == 0); + + FOREACH_ARRAY(res, resolvers, n) + sd_dns_resolver_done(res); + + free(resolvers); +} + +int dns_resolver_prio_compare(const sd_dns_resolver *a, const sd_dns_resolver *b) { + return CMP(ASSERT_PTR(a)->priority, ASSERT_PTR(b)->priority); +} + +int sd_dns_resolver_get_priority(sd_dns_resolver *res, uint16_t *ret_priority) { + assert_return(res, -EINVAL); + assert_return(ret_priority, -EINVAL); + + *ret_priority = res->priority; + return 0; +} + +int sd_dns_resolver_get_adn(sd_dns_resolver *res, const char **ret_adn) { + assert_return(res, -EINVAL); + assert_return(ret_adn, -EINVAL); + + /* Without adn only Do53 can be supported */ + if (!res->auth_name) + return -ENODATA; + + *ret_adn = res->auth_name; + return 0; +} + +int sd_dns_resolver_get_inet_addresses(sd_dns_resolver *res, const struct in_addr **ret_addrs, size_t + *ret_n_addrs) { + assert_return(res, -EINVAL); + assert_return(ret_addrs, -EINVAL); + assert_return(ret_n_addrs, -EINVAL); + assert_return(res->family == AF_INET, -EINVAL); + + /* ADN-only mode has no addrs */ + if (res->n_addrs == 0) + return -ENODATA; + + struct in_addr *addrs = new(struct in_addr, res->n_addrs); + if (!addrs) + return -ENOMEM; + + for (size_t i = 0; i < res->n_addrs; i++) + addrs[i] = res->addrs[i].in; + *ret_addrs = addrs; + *ret_n_addrs = res->n_addrs; + + return 0; +} + +int sd_dns_resolver_get_inet6_addresses(sd_dns_resolver *res, const struct in6_addr **ret_addrs, size_t + *ret_n_addrs) { + assert_return(res, -EINVAL); + assert_return(ret_addrs, -EINVAL); + assert_return(ret_n_addrs, -EINVAL); + assert_return(res->family == AF_INET6, -EINVAL); + + /* ADN-only mode has no addrs */ + if (res->n_addrs == 0) + return -ENODATA; + + struct in6_addr *addrs = new(struct in6_addr, res->n_addrs); + if (!addrs) + return -ENOMEM; + + for (size_t i = 0; i < res->n_addrs; i++) + addrs[i] = res->addrs[i].in6; + *ret_addrs = addrs; + *ret_n_addrs = res->n_addrs; + + return 0; +} + +int sd_dns_resolver_get_alpn(sd_dns_resolver *res, sd_dns_alpn_flags *ret_alpn) { + assert_return(res, -EINVAL); + assert_return(ret_alpn, -EINVAL); + + /* ADN-only mode has no transports */ + if (!res->transports) + return -ENODATA; + + *ret_alpn = res->transports; + return 0; +} + +int sd_dns_resolver_get_port(sd_dns_resolver *res, uint16_t *ret_port) { + assert_return(res, -EINVAL); + assert_return(ret_port, -EINVAL); + + /* port = 0 is the default port */ + *ret_port = res->port; + return 0; +} + +int sd_dns_resolver_get_dohpath(sd_dns_resolver *res, const char **ret_dohpath) { + assert_return(res, -EINVAL); + assert_return(ret_dohpath, -EINVAL); + + /* only present in DoH resolvers */ + if (!res->dohpath) + return -ENODATA; + + *ret_dohpath = res->dohpath; + return 0; +} + +void siphash24_compress_resolver(const sd_dns_resolver *res, struct siphash *state) { + assert(res); + + siphash24_compress_typesafe(res->priority, state); + siphash24_compress_typesafe(res->transports, state); + siphash24_compress_typesafe(res->port, state); + + siphash24_compress_string(res->auth_name, state); + siphash24_compress_string(res->dohpath, state); + + siphash24_compress_typesafe(res->family, state); + FOREACH_ARRAY(addr, res->addrs, res->n_addrs) + siphash24_compress_typesafe(*addr, state); +} + +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]) { + assert(buf); + + 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); +} + +int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *resolver) { + size_t offset = 0; + int r; + + assert(option || len == 0); + assert(resolver); + + sd_dns_alpn_flags transports = 0; + uint16_t port = 0; + _cleanup_free_ char *dohpath = NULL; + bool alpn = false; + + uint16_t lastkey = 0; + while (offset < len) { + if (offset + 4 > len) + return -EBADMSG; + + uint16_t key = unaligned_read_be16(&option[offset]); + offset += 2; + + /* RFC9460 § 2.2 SvcParam MUST appear in strictly increasing numeric order */ + if (lastkey >= key) + return -EBADMSG; + lastkey = key; + + uint16_t plen = unaligned_read_be16(&option[offset]); + offset += 2; + if (offset + plen > len) + return -EBADMSG; + + switch (key) { + /* Mandatory keys must be understood by the client, otherwise the record should be discarded. + * Automatic mandatory keys must not appear in the mandatory parameter, so these are all + * supplementary. We don't understand any supplementary keys, so if the mandatory parameter + * is present, we cannot use this record.*/ + case DNS_SVC_PARAM_KEY_MANDATORY: + if (plen > 0) + return -EBADMSG; + break; + + case DNS_SVC_PARAM_KEY_ALPN: + if (plen == 0) + return 0; + alpn = true; /* alpn is required. Record that the requirement is met. */ + + size_t poff = offset; + size_t pend = offset + plen; + while (poff < pend) { + uint8_t alen = option[poff++]; + if (poff + alen > len) + return -EBADMSG; + if (memcmp_nn(&option[poff], alen, "dot", STRLEN("dot")) == 0) + transports |= SD_DNS_ALPN_DOT; + if (memcmp_nn(&option[poff], alen, "h2", STRLEN("h2")) == 0) + transports |= SD_DNS_ALPN_HTTP_2_TLS; + if (memcmp_nn(&option[poff], alen, "h3", STRLEN("h3")) == 0) + transports |= SD_DNS_ALPN_HTTP_3; + if (memcmp_nn(&option[poff], alen, "doq", STRLEN("doq")) == 0) + transports |= SD_DNS_ALPN_DOQ; + poff += alen; + } + if (poff != pend) + return -EBADMSG; + break; + + case DNS_SVC_PARAM_KEY_PORT: + if (plen != sizeof(uint16_t)) + return -EBADMSG; + port = unaligned_read_be16(&option[offset]); + /* Server should indicate default port by omitting this param */ + if (port == 0) + return -EBADMSG; + break; + + /* RFC9463 § 5.1 service params MUST NOT include ipv4hint/ipv6hint */ + case DNS_SVC_PARAM_KEY_IPV4HINT: + case DNS_SVC_PARAM_KEY_IPV6HINT: + return -EBADMSG; + + case DNS_SVC_PARAM_KEY_DOHPATH: + r = make_cstring((const char*) &option[offset], plen, + MAKE_CSTRING_REFUSE_TRAILING_NUL, &dohpath); + if (ERRNO_IS_NEG_RESOURCE(r)) + return r; + if (r < 0) + return -EBADMSG; + /* dohpath is a RFC6750 URI template. We don't parse these, but at least check the + * charset is reasonable. */ + if (!in_charset(dohpath, URI_VALID "{}")) + return -EBADMSG; + break; + + default: + break; + } + offset += plen; + } + if (offset != len) + return -EBADMSG; + + /* DNR cannot be used without alpn */ + if (!alpn) + return -EBADMSG; + + /* RFC9461 § 5: If the [SvcParam] indicates support for HTTP, "dohpath" MUST be present. */ + if (!dohpath && (FLAGS_SET(transports, SD_DNS_ALPN_HTTP_2_TLS) || + FLAGS_SET(transports, SD_DNS_ALPN_HTTP_3))) + return -EBADMSG; + + /* No useful transports */ + if (!transports) + return 0; + + resolver->transports = transports; + resolver->port = port; + free_and_replace(resolver->dohpath, dohpath); + return transports; +} + +int dns_resolvers_to_dot_addrs(const sd_dns_resolver *resolvers, size_t n_resolvers, + struct in_addr_full ***ret_addrs, size_t *ret_n_addrs) { + assert(ret_addrs); + assert(ret_n_addrs); + assert(resolvers || n_resolvers == 0); + + struct in_addr_full **addrs = NULL; + size_t n = 0; + CLEANUP_ARRAY(addrs, n, in_addr_full_array_free); + + FOREACH_ARRAY(res, resolvers, n_resolvers) { + if (!FLAGS_SET(res->transports, SD_DNS_ALPN_DOT)) + continue; + + FOREACH_ARRAY(i, res->addrs, res->n_addrs) { + _cleanup_(in_addr_full_freep) struct in_addr_full *addr = NULL; + int r; + + addr = new0(struct in_addr_full, 1); + if (!addr) + return -ENOMEM; + if (!GREEDY_REALLOC(addrs, n+1)) + return -ENOMEM; + + r = free_and_strdup(&addr->server_name, res->auth_name); + if (r < 0) + return r; + addr->family = res->family; + addr->port = res->port; + addr->address = *i; + + addrs[n++] = TAKE_PTR(addr); + } + } + + *ret_addrs = TAKE_PTR(addrs); + *ret_n_addrs = n; + return n; +} + +int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolvers, char ***ret_names) { + assert(ret_names); + int r; + + _cleanup_strv_free_ char **names = NULL; + size_t len = 0; + + struct in_addr_full **addrs = NULL; + size_t n = 0; + CLEANUP_ARRAY(addrs, n, in_addr_full_array_free); + + r = dns_resolvers_to_dot_addrs(resolvers, n_resolvers, &addrs, &n); + if (r < 0) + return r; + + FOREACH_ARRAY(addr, addrs, n) { + const char *name = in_addr_full_to_string(*addr); + if (!name) + return -ENOMEM; + r = strv_extend_with_size(&names, &len, name); + if (r < 0) + return r; + } + + *ret_names = TAKE_PTR(names); + return len; +} diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index 2580c84ead..09477449b9 100644 --- a/src/shared/socket-netlink.c +++ b/src/shared/socket-netlink.c @@ -347,6 +347,15 @@ struct in_addr_full *in_addr_full_free(struct in_addr_full *a) { return mfree(a); } +void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n) { + assert(addrs || n == 0); + + FOREACH_ARRAY(a, addrs, n) + in_addr_full_freep(a); + + free(addrs); +} + int in_addr_full_new( int family, const union in_addr_union *a, diff --git a/src/shared/socket-netlink.h b/src/shared/socket-netlink.h index a6edb4c4bd..12647b3d14 100644 --- a/src/shared/socket-netlink.h +++ b/src/shared/socket-netlink.h @@ -39,6 +39,7 @@ struct in_addr_full { struct in_addr_full *in_addr_full_free(struct in_addr_full *a); DEFINE_TRIVIAL_CLEANUP_FUNC(struct in_addr_full*, in_addr_full_free); +void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n); int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret); int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret); const char* in_addr_full_to_string(struct in_addr_full *a); diff --git a/src/systemd/meson.build b/src/systemd/meson.build index 06ba4cbc07..edcbc873e9 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -35,6 +35,7 @@ _not_installed_headers = [ 'sd-dhcp6-lease.h', 'sd-dhcp6-option.h', 'sd-dhcp6-protocol.h', + 'sd-dns-resolver.h', 'sd-ipv4acd.h', 'sd-ipv4ll.h', 'sd-lldp-rx.h', diff --git a/src/systemd/sd-dns-resolver.h b/src/systemd/sd-dns-resolver.h new file mode 100644 index 0000000000..79d00ba9bc --- /dev/null +++ b/src/systemd/sd-dns-resolver.h @@ -0,0 +1,44 @@ +#ifndef SD_DNS_RESOLVER_H +#define SD_DNS_RESOLVER_H + +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dns_resolver sd_dns_resolver; + +/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids */ +typedef enum sd_dns_alpn_flags { + /* There isn't really an alpn reserved for Do53 service, but designated resolvers may or may not offer + * Do53 service, so we should probably have a flag to represent this capability. Unfortunately DNR + * does not indicate the status to us.*/ + SD_DNS_ALPN_DO53 = 1 << 0, + /* SD_DNS_ALPN_HTTP_1_1, "http/1.1" [RFC9112] */ + SD_DNS_ALPN_HTTP_2_TLS = 1 << 1, /* "h2" [RFC9113] [RFC9461] */ + /* SD_DNS_ALPN_HTTP_2_TCP, "h2c" [RFC9113] */ + SD_DNS_ALPN_HTTP_3 = 1 << 2, /* "h3" [RFC9114] [RFC9461] */ + SD_DNS_ALPN_DOT = 1 << 3, /* "dot" [RFC7858] [RFC9461] */ + SD_DNS_ALPN_DOQ = 1 << 4, /* "doq" [RFC9250] [RFC9461] */ + + _SD_ENUM_FORCE_S64(SD_DNS_ALPN) +} sd_dns_alpn_flags; + +int sd_dns_resolver_get_priority(sd_dns_resolver *res, uint16_t *ret_priority); +int sd_dns_resolver_get_adn(sd_dns_resolver *res, const char **ret_adn); +int sd_dns_resolver_get_inet_addresses(sd_dns_resolver *res, const struct in_addr **ret_addrs, size_t *n); +int sd_dns_resolver_get_inet6_addresses(sd_dns_resolver *res, const struct in6_addr **ret_addrs, size_t *n); +int sd_dns_resolver_get_alpn(sd_dns_resolver *res, sd_dns_alpn_flags *ret_alpn); +int sd_dns_resolver_get_port(sd_dns_resolver *res, uint16_t *ret_port); +int sd_dns_resolver_get_dohpath(sd_dns_resolver *res, const char **ret_dohpath); + +sd_dns_resolver *sd_dns_resolver_unref(sd_dns_resolver *res); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dns_resolver, sd_dns_resolver_unref); + +_SD_END_DECLARATIONS; + +#endif /* SD_DNS_RESOLVER_H */ From 25c33e350042b2678bcc9543e1972b84b32731e2 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Tue, 16 Jan 2024 00:01:46 -0700 Subject: [PATCH 03/20] network: parse RFC9463 DHCPv4 DNR option This option is another way for DHCP servers to indicate preferred DNS servers for the network, but includes more detailed info like the server name, transport (DoT/DoH/DoQ etc.), and port. Allow our DHCPv4 client to parse this option. --- src/libsystemd-network/dhcp-lease-internal.h | 3 + src/libsystemd-network/dhcp-option.h | 1 + src/libsystemd-network/sd-dhcp-lease.c | 150 +++++++++++++++++++ src/systemd/sd-dhcp-lease.h | 2 + src/systemd/sd-dhcp-protocol.h | 3 +- 5 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index b7bc142ef7..394c71bdf0 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -55,6 +55,9 @@ struct sd_dhcp_lease { DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; + sd_dns_resolver *dnr; + size_t n_dnr; + struct sd_dhcp_route *static_routes; size_t n_static_routes; struct sd_dhcp_route *classless_routes; diff --git a/src/libsystemd-network/dhcp-option.h b/src/libsystemd-network/dhcp-option.h index aaa8f847b1..047c8f3415 100644 --- a/src/libsystemd-network/dhcp-option.h +++ b/src/libsystemd-network/dhcp-option.h @@ -4,6 +4,7 @@ #include #include "sd-dhcp-option.h" +#include "dns-resolver-internal.h" #include "dhcp-protocol.h" #include "hash-funcs.h" diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 37f4b3b2c9..8c46f8f770 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -11,6 +11,7 @@ #include #include "sd-dhcp-lease.h" +#include "dns-resolver-internal.h" #include "alloc-util.h" #include "dhcp-lease-internal.h" @@ -26,6 +27,7 @@ #include "network-common.h" #include "network-internal.h" #include "parse-util.h" +#include "sort-util.h" #include "stdio-util.h" #include "string-util.h" #include "strv.h" @@ -229,6 +231,17 @@ int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **ret) { return 0; } +int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers) { + assert_return(lease, -EINVAL); + assert_return(ret_resolvers, -EINVAL); + + if (!lease->dnr) + return -ENODATA; + + *ret_resolvers = lease->dnr; + return lease->n_dnr; +} + int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); @@ -418,6 +431,7 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++) free(lease->servers[i].addr); + dns_resolver_done_many(lease->dnr, lease->n_dnr); free(lease->static_routes); free(lease->classless_routes); free(lease->vendor_specific); @@ -559,6 +573,133 @@ static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_a return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret); } +static int lease_parse_dns_name(const uint8_t *optval, size_t optlen, char **ret) { + _cleanup_free_ char *name = NULL; + int r; + + assert(optval); + assert(ret); + + r = dns_name_from_wire_format(&optval, &optlen, &name); + if (r < 0) + return r; + if (r == 0 || optlen != 0) + return -EBADMSG; + + *ret = TAKE_PTR(name); + return r; +} + +static int lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver **ret_dnr, size_t *ret_n_dnr) { + int r; + sd_dns_resolver *res_list = NULL; + size_t n_resolvers = 0; + CLEANUP_ARRAY(res_list, n_resolvers, dns_resolver_done_many); + + assert(option || len == 0); + assert(ret_dnr); + + _cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {}; + + size_t offset = 0; + while (offset < len) { + /* Instance Data length */ + if (offset + 2 > len) + return -EBADMSG; + size_t ilen = unaligned_read_be16(option + offset); + if (offset + ilen + 2 > len) + return -EBADMSG; + offset += 2; + size_t iend = offset + ilen; + + /* priority */ + if (offset + 2 > len) + return -EBADMSG; + res.priority = unaligned_read_be16(option + offset); + offset += 2; + + /* Authenticated Domain Name */ + if (offset + 1 > len) + return -EBADMSG; + ilen = option[offset++]; + if (offset + ilen > iend) + return -EBADMSG; + + r = lease_parse_dns_name(option + offset, ilen, &res.auth_name); + if (r < 0) + return r; + if (dns_name_is_root(res.auth_name)) + return -EBADMSG; + offset += ilen; + + /* RFC9463 § 3.1.6: In ADN-only mode, server omits everything after the ADN. + * We don't support these, but they are not invalid. */ + if (offset == iend) { + log_debug("Received ADN-only DNRv4 option, ignoring."); + sd_dns_resolver_done(&res); + continue; + } + + /* IPv4 addrs */ + if (offset + 1 > len) + return -EBADMSG; + ilen = option[offset++]; + if (offset + ilen > iend) + return -EBADMSG; + + size_t n_addrs; + _cleanup_free_ struct in_addr *addrs = NULL; + r = lease_parse_in_addrs(option + offset, ilen, &addrs, &n_addrs); + if (r < 0) + return r; + offset += ilen; + + /* RFC9463 § 3.1.8: option MUST include at least one valid IP addr */ + if (!n_addrs) + return -EBADMSG; + + res.addrs = new(union in_addr_union, n_addrs); + if (!res.addrs) + return -ENOMEM; + for (size_t i = 0; i < n_addrs; i++) { + union in_addr_union addr = {.in = addrs[i]}; + /* RFC9463 § 5.2 client MUST discard multicast and host loopback addresses */ + if (in_addr_is_multicast(AF_INET, &addr) || + in_addr_is_localhost(AF_INET, &addr)) + return -EBADMSG; + res.addrs[i] = addr; + } + res.n_addrs = n_addrs; + res.family = AF_INET; + + /* service params */ + r = dnr_parse_svc_params(option + offset, iend-offset, &res); + if (r < 0) + return r; + if (r == 0) { + /* We can't use this record, but it was not invalid. */ + log_debug("Received DNRv4 option with unsupported SvcParams, ignoring."); + sd_dns_resolver_done(&res); + continue; + } + offset = iend; + + /* Append the latest resolver */ + if (!GREEDY_REALLOC0(res_list, n_resolvers+1)) + return -ENOMEM; + + res_list[n_resolvers++] = TAKE_STRUCT(res); + } + + typesafe_qsort(*ret_dnr, *ret_n_dnr, dns_resolver_prio_compare); + + dns_resolver_done_many(*ret_dnr, *ret_n_dnr); + *ret_dnr = TAKE_PTR(res_list); + *ret_n_dnr = n_resolvers; + + return n_resolvers; +} + static int lease_parse_static_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) { int r; @@ -879,6 +1020,15 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void break; } + case SD_DHCP_OPTION_V4_DNR: + r = lease_parse_dnr(option, len, &lease->dnr, &lease->n_dnr); + if (r < 0) { + log_debug_errno(r, "Failed to parse network-designated resolvers, ignoring: %m"); + return 0; + } + + break; + case SD_DHCP_OPTION_VENDOR_SPECIFIC: if (len <= 0) diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index eb5970e405..2265b8b9aa 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -32,6 +32,7 @@ _SD_BEGIN_DECLARATIONS; typedef struct sd_dhcp_lease sd_dhcp_lease; typedef struct sd_dhcp_route sd_dhcp_route; +typedef struct sd_dns_resolver sd_dns_resolver; sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); @@ -75,6 +76,7 @@ int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains); int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **captive_portal); +int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers); int sd_dhcp_lease_get_static_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); diff --git a/src/systemd/sd-dhcp-protocol.h b/src/systemd/sd-dhcp-protocol.h index d8b7537da2..c25498502d 100644 --- a/src/systemd/sd-dhcp-protocol.h +++ b/src/systemd/sd-dhcp-protocol.h @@ -171,7 +171,8 @@ enum { SD_DHCP_OPTION_PORT_PARAMS = 159, /* [RFC7618] */ /* option code 160 is unassigned [RFC7710][RFC8910] */ SD_DHCP_OPTION_MUD_URL = 161, /* [RFC8520] */ - /* option codes 162-174 are unassigned [RFC3942] */ + SD_DHCP_OPTION_V4_DNR = 162, /* [RFC9463] */ + /* option codes 163-174 are unassigned [RFC3942] */ /* option codes 175-177 are temporary assigned. */ /* option codes 178-207 are unassigned [RFC3942] */ SD_DHCP_OPTION_PXELINUX_MAGIC = 208, /* [RFC5071] Deprecated */ From 869381589d759ee05de04ab6284007bf0ee2422e Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Tue, 16 Jan 2024 00:04:07 -0700 Subject: [PATCH 04/20] network: Introduce UseDNR DHCPv4 option This option will control the use of DNR for choosing DNS servers on the link. Defaults to the value of UseDNS so that in most cases they will be toggled together. --- man/systemd.network.xml | 13 +++++++++++++ src/network/networkd-dhcp4.c | 7 +++++++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 5 +++++ 5 files changed, 27 insertions(+) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index e743e6d2a0..eb6eaf3ce2 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2605,6 +2605,18 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix + + UseDNR= + + When true, designated resolvers advertised by the DHCP server will be used as encrypted + DNS servers. See RFC 9463. + + Defaults to unset, and the value for UseDNS= will be used. + + + + + UseMTU= @@ -3131,6 +3143,7 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix UseDNS= + UseDNR= UseNTP= UseHostname= UseDomains= diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 0a784553d0..4d3f429a30 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1557,6 +1557,13 @@ static int dhcp4_configure(Link *link) { if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for SIP server: %m"); } + + if (network_dhcp_use_dnr(link->network)) { + r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_V4_DNR); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for DNR: %m"); + } + if (link->network->dhcp_use_captive_portal) { r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL); if (r < 0) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 9f23542493..89be7e8ae2 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -228,6 +228,7 @@ NextHop.Group, config_parse_nexthop_group, DHCPv4.RequestAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_request_address) DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) DHCPv4.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp_use_dns) +DHCPv4.UseDNR, config_parse_tristate, 0, offsetof(Network, dhcp_use_dnr) DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns) DHCPv4.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp_use_ntp) DHCPv4.RoutesToNTP, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_ntp) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 8ccf215a71..873894d82c 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -388,6 +388,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_use_sip = true, .dhcp_use_captive_portal = true, .dhcp_use_dns = -1, + .dhcp_use_dnr = -1, .dhcp_routes_to_dns = true, .dhcp_use_domains = _USE_DOMAINS_INVALID, .dhcp_use_hostname = true, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 66a8328e29..1a92257059 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -153,6 +153,7 @@ struct Network { int dhcp_ipv6_only_mode; int dhcp_use_rapid_commit; int dhcp_use_dns; + int dhcp_use_dnr; bool dhcp_routes_to_dns; int dhcp_use_ntp; bool dhcp_routes_to_ntp; @@ -420,6 +421,10 @@ int network_load(Manager *manager, OrderedHashmap **networks); int network_reload(Manager *manager); int network_load_one(Manager *manager, OrderedHashmap **networks, const char *filename); int network_verify(Network *network); +static inline int network_dhcp_use_dnr(Network *network) { + assert(network); + return network->dhcp_use_dnr < 0 ? network->dhcp_use_dns : network->dhcp_use_dnr; +} int manager_build_dhcp_pd_subnet_ids(Manager *manager); From b0e716310d6dfea9692141f63d1ad12fea5d6e5c Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Fri, 23 Feb 2024 17:24:40 -0700 Subject: [PATCH 05/20] network: Add serialization for DoT resolvers For now only DoT is supported, so DoT resolvers are represented using the existing configuration format. --- src/libsystemd-network/network-internal.c | 94 +++++++++++++++++++++++ src/libsystemd-network/network-internal.h | 3 + src/network/networkd-state-file.c | 1 + 3 files changed, 98 insertions(+) diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index c8aa021ee0..9a1d1fda16 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -14,6 +14,7 @@ #include "log.h" #include "network-internal.h" #include "parse-util.h" +#include "strv.h" size_t serialize_in_addrs(FILE *f, const struct in_addr *addresses, @@ -131,6 +132,99 @@ int deserialize_in6_addrs(struct in6_addr **ret, const char *string) { return size; } +int serialize_dnr(FILE *f, const sd_dns_resolver *dnr, size_t n_dnr, bool *with_leading_space) { + int r; + + bool _space = false; + if (!with_leading_space) + with_leading_space = &_space; + + int n = 0; + _cleanup_strv_free_ char **names = NULL; + r = dns_resolvers_to_dot_strv(dnr, n_dnr, &names); + if (r < 0) + return r; + if (r > 0) + fputstrv(f, names, NULL, with_leading_space); + n += r; + return n; +} + +static int coalesce_dnr(sd_dns_resolver *dnr, size_t n_dnr, int family, const char *auth_name, + union in_addr_union *addr) { + assert(dnr || n_dnr == 0); + assert(auth_name); + assert(addr); + + /* Look through list of DNR for matching resolvers to add our addr to. Since DoT is assumed, no need + * to compare transports/dohpath/etc. */ + FOREACH_ARRAY(res, dnr, n_dnr) { + if (family == res->family && streq(auth_name, res->auth_name)) { + if (!GREEDY_REALLOC(res->addrs, res->n_addrs + 1)) + return -ENOMEM; + res->addrs[res->n_addrs++] = *addr; + return true; + } + } + + return false; +} + +/* Deserialized resolvers are assumed to offer DoT service. */ +int deserialize_dnr(sd_dns_resolver **ret, const char *string) { + int r; + + assert(ret); + assert(string); + + sd_dns_resolver *dnr = NULL; + size_t n = 0; + CLEANUP_ARRAY(dnr, n, dns_resolver_done_many); + int priority = 0; + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + uint16_t port; + int family; + _cleanup_free_ union in_addr_union *addr = new(union in_addr_union, 1); + _cleanup_free_ char *auth_name = NULL; + + r = in_addr_port_ifindex_name_from_string_auto(word, &family, addr, &port, NULL, &auth_name); + if (r < 0) + return r; + + r = coalesce_dnr(dnr, n, family, auth_name, addr); + if (r < 0) + return r; + if (r > 0) + continue; + + if (!GREEDY_REALLOC(dnr, n+1)) + return -ENOMEM; + + priority = n+1; + dnr[n++] = (sd_dns_resolver) { + .priority = priority, /* not serialized, but this will preserve the order */ + .auth_name = TAKE_PTR(auth_name), + .family = family, + .addrs = TAKE_PTR(addr), + .n_addrs = 1, + .transports = SD_DNS_ALPN_DOT, + .port = port, + }; + } + + *ret = TAKE_PTR(dnr); + return n; +} + void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) { assert(f); assert(key); diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index 5aa225e977..6a102e92c4 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -17,6 +17,9 @@ void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, bool *with_leading_space); int deserialize_in6_addrs(struct in6_addr **addresses, const char *string); +int serialize_dnr(FILE *f, const sd_dns_resolver *dnr, size_t n_dnr, bool *with_leading_space); +int deserialize_dnr(sd_dns_resolver **ret, const char *string); + /* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ struct sd_dhcp_route; struct sd_dhcp_lease; diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index 481e2d930b..d8d05c6ffd 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "dns-domain.h" +#include "dns-resolver-internal.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" From 3fd6708cde0fa4283e2335aca288a40a49406f3c Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Tue, 16 Jan 2024 00:04:41 -0700 Subject: [PATCH 06/20] network: Serialize DNR servers Implement serialization/deserialization for DNR servers. This re-uses the string format in place for user configuration of DoT servers, and as a consequence non-DoT servers are discarded when recording the link configuration, for correctness. This also enables sd-resolved to use these servers as it would other DNS servers. --- src/libsystemd-network/sd-dhcp-lease.c | 17 +++++++ src/network/networkd-dhcp4.c | 2 +- src/network/networkd-dns.c | 25 +++++++++++ src/network/networkd-dns.h | 1 + src/network/networkd-network.h | 4 -- src/network/networkd-state-file.c | 61 ++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 5 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 8c46f8f770..1c0cd6829b 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -1290,6 +1290,14 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { fputc('\n', f); } + sd_dns_resolver *resolvers; + r = sd_dhcp_lease_get_dnr(lease, &resolvers); + if (r > 0) { + fputs("DNR=", f); + serialize_dnr(f, resolvers, r, NULL); + fputc('\n', f); + } + r = sd_dhcp_lease_get_ntp(lease, &addresses); if (r > 0) { fputs("NTP=", f); @@ -1398,6 +1406,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { *next_server = NULL, *broadcast = NULL, *dns = NULL, + *dnr = NULL, *ntp = NULL, *sip = NULL, *pop3 = NULL, @@ -1435,6 +1444,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { "NEXT_SERVER", &next_server, "BROADCAST", &broadcast, "DNS", &dns, + "DNR", &dnr, "NTP", &ntp, "SIP", &sip, "POP3", &pop3, @@ -1537,6 +1547,13 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { lease->servers[SD_DHCP_LEASE_DNS].size = r; } + if (dnr) { + r = deserialize_dnr(&lease->dnr, dnr); + if (r < 0) + log_debug_errno(r, "Failed to deserialize DNR servers %s, ignoring: %m", dnr); + lease->n_dnr = r; + } + if (ntp) { r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp); if (r < 0) diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 4d3f429a30..b1cda06c98 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1558,7 +1558,7 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for SIP server: %m"); } - if (network_dhcp_use_dnr(link->network)) { + if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4)) { r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_V4_DNR); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for DNR: %m"); diff --git a/src/network/networkd-dns.c b/src/network/networkd-dns.c index 74a371b6d7..fb2b282bc8 100644 --- a/src/network/networkd-dns.c +++ b/src/network/networkd-dns.c @@ -94,6 +94,31 @@ bool link_get_use_dns(Link *link, NetworkConfigSource proto) { return true; } +bool link_get_use_dnr(Link *link, NetworkConfigSource proto) { + int n; + + assert(link); + + if (!link->network) + return false; + + switch (proto) { + case NETWORK_CONFIG_SOURCE_DHCP4: + n = link->network->dhcp_use_dnr; + break; + default: + assert_not_reached(); + } + + /* If set explicitly, use that */ + if (n >= 0) + return n; + + /* Otherwise, default to the same as the UseDNS setting. After all, + * this is just another way for the server to tell us about DNS configuration. */ + return link_get_use_dns(link, proto); +} + int config_parse_domains( const char *unit, const char *filename, diff --git a/src/network/networkd-dns.h b/src/network/networkd-dns.h index 915cb32fb1..e273d10328 100644 --- a/src/network/networkd-dns.h +++ b/src/network/networkd-dns.h @@ -17,6 +17,7 @@ typedef enum UseDomains { UseDomains link_get_use_domains(Link *link, NetworkConfigSource proto); bool link_get_use_dns(Link *link, NetworkConfigSource proto); +bool link_get_use_dnr(Link *link, NetworkConfigSource proto); const char* use_domains_to_string(UseDomains p) _const_; UseDomains use_domains_from_string(const char *s) _pure_; diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 1a92257059..fbcc5be9c5 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -421,10 +421,6 @@ int network_load(Manager *manager, OrderedHashmap **networks); int network_reload(Manager *manager); int network_load_one(Manager *manager, OrderedHashmap **networks, const char *filename); int network_verify(Network *network); -static inline int network_dhcp_use_dnr(Network *network) { - assert(network); - return network->dhcp_use_dnr < 0 ? network->dhcp_use_dns : network->dhcp_use_dnr; -} int manager_build_dhcp_pd_subnet_ids(Manager *manager); diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index d8d05c6ffd..14d4572751 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -114,6 +114,24 @@ static int link_put_dns(Link *link, OrderedSet **s) { } } + if (link->dhcp_lease && link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4)) { + sd_dns_resolver *resolvers; + + r = sd_dhcp_lease_get_dnr(link->dhcp_lease, &resolvers); + if (r >= 0) { + struct in_addr_full **dot_servers; + size_t n = 0; + CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free); + + r = dns_resolvers_to_dot_addrs(resolvers, r, &dot_servers, &n); + if (r < 0) + return r; + r = ordered_set_put_dns_servers(s, link->ifindex, dot_servers, n); + if (r < 0) + return r; + } + } + if (link->dhcp6_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) { const struct in6_addr *addresses; @@ -523,6 +541,42 @@ static void serialize_addresses( fputc('\n', f); } +static void serialize_resolvers( + FILE *f, + const char *lvalue, + bool *space, + sd_dhcp_lease *lease, + bool conditional) { + + bool _space = false; + if (!space) + space = &_space; + + if (lvalue) + fprintf(f, "%s=", lvalue); + + if (lease && conditional) { + sd_dns_resolver *resolvers; + _cleanup_strv_free_ char **names = NULL; + int r; + + r = sd_dhcp_lease_get_dnr(lease, &resolvers); + if (r < 0) + return (void) log_warning_errno(r, "Failed to get DNR from DHCP lease, ignoring."); + + r = dns_resolvers_to_dot_strv(resolvers, r, &names); + if (r < 0) + return (void) log_warning_errno(r, "Failed to get DoT servers from DHCP DNR, ignoring."); + if (r > 0) + fputstrv(f, names, NULL, space); + } + + if (lvalue) + fputc('\n', f); + + return; +} + static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, UseDomains use_domains) { bool space = false; const char *p; @@ -666,6 +720,13 @@ static int link_save(Link *link) { space = false; link_save_dns(link, f, link->network->dns, link->network->n_dns, &space); + /* DNR resolvers are not required to provide Do53 service, however resolved doesn't + * know how to handle such a server so for now Do53 service is required, and + * assumed. */ + serialize_resolvers(f, NULL, &space, + link->dhcp_lease, + link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4)); + serialize_addresses(f, NULL, &space, NULL, link->dhcp_lease, From 7957154e06e847318ae29565109f0be44dbe6d48 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Tue, 16 Jan 2024 16:07:54 -0700 Subject: [PATCH 07/20] test-network: add test for DHCPv4 DNR This will test that networkd/resolved can understand the V4_DNR DHCP option. --- test/test-network/systemd-networkd-tests.py | 69 +++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 687fda7b3d..66e14e6f1d 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -17,8 +17,10 @@ import argparse import datetime +import enum import errno import itertools +import ipaddress import json import os import pathlib @@ -27,6 +29,7 @@ import re import shutil import signal import socket +import struct import subprocess import sys import time @@ -742,6 +745,30 @@ def stop_by_pid_file(pid_file): print(f"Unexpected exception when waiting for {pid} to die: {e.errno}") rm_f(pid_file) +def dnr_v4_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None): + b = bytes() + pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c + pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n) + ipv4 = ipaddress.IPv4Address + class SvcParam(enum.Enum): + ALPN = 1 + DOHPATH = 7 + + data = pyton(prio) + + adn = adn.rstrip('.') + '.' + data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.'))) + + if not addrs: # adn-only mode + return pack(data, 2) + + data += pack(b.join(ipv4(addr).packed for addr in addrs)) + data += pyton(SvcParam.ALPN.value) + pack(b.join(pack(alpn.encode('ascii')) for alpn in alpns), 2) + if dohpath is not None: + data += pyton(SvcParam.DOHPATH.value) + pack(dohpath.encode('utf-8'), 2) + + return pack(data, 2) + def start_dnsmasq(*additional_options, interface='veth-peer', ra_mode=None, ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'): if ra_mode: ra_mode = f',{ra_mode}' @@ -7253,6 +7280,48 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): check(self, False, False, True) check(self, False, False, False) + def test_dhcp_client_use_dnr(self): + def check(self, ipv4, ipv6): + os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) + with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + f.write('[DHCPv4]\nUseDNS=') + f.write('yes' if ipv4 else 'no') + f.write('\n[DHCPv6]\nUseDNS=') + f.write('yes' if ipv6 else 'no') + f.write('\n[IPv6AcceptRA]\nUseDNS=no') + + networkctl_reload() + self.wait_online('veth99:routable') + + # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. + self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') + self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + + # make resolved re-read the link state file + resolvectl('revert', 'veth99') + + output = resolvectl('dns', 'veth99') + print(output) + if ipv4: + self.assertIn('8.8.8.8#dns.google', output) + self.assertIn('0.7.4.2#homer.simpson', output) + else: + self.assertNotIn('8.8.8.8#dns.google', output) + + check_json(networkctl_json()) + + copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + + start_networkd() + self.wait_online('veth-peer:carrier') + dnr_v4 = dnr_v4_instance_data(adn = "dns.google", addrs = ["8.8.8.8", "8.8.4.4"]) + dnr_v4 += dnr_v4_instance_data(adn = "homer.simpson", addrs = ["0.7.4.2"], alpns = ("dot","h2","h3"), dohpath = "/springfield{?dns}") + masq = lambda bs: ':'.join(f"{b:02x}" for b in bs) + start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}') + + check(self, True, False) + check(self, False, False) + def test_dhcp_client_use_captive_portal(self): def check(self, ipv4, ipv6): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) From 2d9822b634680f1be1d20920aceddac76de110eb Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Wed, 24 Jan 2024 17:53:35 -0700 Subject: [PATCH 08/20] test-dhcp6: terminate fqdn option The encoded fqdn in this option must be properly terminated. We will soon validate that this field is correctly encoded, so correct it in the test. --- src/libsystemd-network/test-dhcp6-client.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 7afd464dc0..8c89365140 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -745,8 +745,8 @@ static const uint8_t msg_reply[] = { 0x00, SD_DHCP6_OPTION_DOMAIN, 0x00, 0x0b, 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, /* Client FQDN */ - 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12, - 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', + 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x13, + 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, /* Vendor specific options */ 0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES, @@ -827,8 +827,8 @@ static const uint8_t msg_advertise[] = { 0x00, SD_DHCP6_OPTION_DOMAIN, 0x00, 0x0b, 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, /* Client FQDN */ - 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12, - 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', + 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x13, + 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00, /* Vendor specific options */ 0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES, From 1be9b30a3b1768cac1e5010574e265aca86d8f11 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Tue, 23 Jan 2024 13:12:30 -0700 Subject: [PATCH 09/20] dhcp6: use dns_name_from_wire_format Convert some of the option parsing to use dns_name_from_wire_format, introduced earlier. No change in behavior intended. --- src/libsystemd-network/dhcp6-option.c | 76 ++-------------------- src/libsystemd-network/test-dhcp6-client.c | 5 +- 2 files changed, 5 insertions(+), 76 deletions(-) diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 83f40f3f02..787cc37d00 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -820,74 +820,6 @@ int dhcp6_option_parse_addresses( return 0; } -static int parse_domain(const uint8_t **data, size_t *len, char **ret) { - _cleanup_free_ char *domain = NULL; - const uint8_t *optval; - size_t optlen, n = 0; - int r; - - assert(data); - assert(len); - assert(*data || *len == 0); - assert(ret); - - optval = *data; - optlen = *len; - - if (optlen <= 1) - return -ENODATA; - - for (;;) { - const char *label; - uint8_t c; - - if (optlen == 0) - break; - - c = *optval; - optval++; - optlen--; - - if (c == 0) - /* End label */ - break; - if (c > 63) - return -EBADMSG; - if (c > optlen) - return -EMSGSIZE; - - /* Literal label */ - label = (const char*) optval; - optval += c; - optlen -= c; - - if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - if (n != 0) - domain[n++] = '.'; - - r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - n += r; - } - - if (n > 0) { - if (!GREEDY_REALLOC(domain, n + 1)) - return -ENOMEM; - - domain[n] = '\0'; - } - - *ret = TAKE_PTR(domain); - *data = optval; - *len = optlen; - - return n; -} - int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret) { _cleanup_free_ char *domain = NULL; int r; @@ -895,7 +827,7 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **r assert(optval || optlen == 0); assert(ret); - r = parse_domain(&optval, &optlen, &domain); + r = dns_name_from_wire_format(&optval, &optlen, &domain); if (r < 0) return r; if (r == 0) @@ -922,11 +854,11 @@ int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, cha while (optlen > 0) { _cleanup_free_ char *name = NULL; - r = parse_domain(&optval, &optlen, &name); + r = dns_name_from_wire_format(&optval, &optlen, &name); if (r < 0) return r; - if (r == 0) - continue; + if (dns_name_is_root(name)) /* root domain */ + return -EBADMSG; r = strv_consume(&names, TAKE_PTR(name)); if (r < 0) diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 8c89365140..882c04afbb 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -165,10 +165,7 @@ TEST(parse_domain) { domain = mfree(domain); data = (uint8_t []) { 4, 't', 'e', 's', 't' }; - assert_se(dhcp6_option_parse_domainname(data, 5, &domain) >= 0); - assert_se(domain); - assert_se(streq(domain, "test")); - domain = mfree(domain); + assert_se(dhcp6_option_parse_domainname(data, 5, &domain) < 0); data = (uint8_t []) { 0 }; assert_se(dhcp6_option_parse_domainname(data, 1, &domain) < 0); From a07e83cc58f6498fca26190063c8616f587aa831 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Wed, 17 Jan 2024 16:49:02 -0700 Subject: [PATCH 10/20] network: Parse RFC9463 DHCPv6 DNR option Implement the parsing for V6_DNR DHCPv6 option. This does the same as the DHCP V4_DNR option. --- src/libsystemd-network/dhcp6-lease-internal.h | 3 + src/libsystemd-network/sd-dhcp6-lease.c | 103 ++++++++++++++++++ src/systemd/sd-dhcp6-lease.h | 2 + src/systemd/sd-dhcp6-protocol.h | 5 +- 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index e76a108f60..60cd84f2d8 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -8,6 +8,7 @@ #include #include "sd-dhcp6-lease.h" +#include "dns-resolver-internal.h" #include "dhcp6-option.h" #include "dhcp6-protocol.h" @@ -38,6 +39,8 @@ struct sd_dhcp6_lease { struct in6_addr *dns; size_t dns_count; + sd_dns_resolver *dnr; + size_t n_dnr; char **domains; struct in6_addr *ntp; size_t ntp_count; diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index e5d6547588..22707b771d 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -9,6 +9,7 @@ #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" #include "network-common.h" +#include "sort-util.h" #include "strv.h" #include "unaligned.h" @@ -438,6 +439,98 @@ int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) { return strv_length(lease->domains); } +static int dhcp6_lease_add_dnr(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { + int r; + + assert(lease); + + _cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {}; + + size_t offset = 0; + + /* priority */ + if (optlen - offset < sizeof(uint16_t)) + return -EBADMSG; + res.priority = unaligned_read_be16(optval + offset); + offset += sizeof(uint16_t); + + /* adn */ + if (optlen - offset < sizeof(uint16_t)) + return -EBADMSG; + size_t ilen = unaligned_read_be16(optval + offset); + offset += sizeof(uint16_t); + if (offset + ilen > optlen) + return -EBADMSG; + + r = dhcp6_option_parse_domainname(optval + offset, ilen, &res.auth_name); + if (r < 0) + return r; + offset += ilen; + + /* RFC9463 § 3.1.6: adn only mode */ + if (offset == optlen) + return 0; + + /* addrs */ + if (optlen - offset < sizeof(uint16_t)) + return -EBADMSG; + ilen = unaligned_read_be16(optval + offset); + offset += sizeof(uint16_t); + if (offset + ilen > optlen) + return -EBADMSG; + + _cleanup_free_ struct in6_addr *addrs = NULL; + size_t n_addrs = 0; + + r = dhcp6_option_parse_addresses(optval + offset, ilen, &addrs, &n_addrs); + if (r < 0) + return r; + if (n_addrs == 0) + return -EBADMSG; + offset += ilen; + + res.addrs = new(union in_addr_union, n_addrs); + if (!res.addrs) + return -ENOMEM + + for (size_t i = 0; i < n_addrs; i++) { + union in_addr_union addr = {.in6 = addrs[i]}; + /* RFC9463 § 6.2 client MUST discard multicast and host loopback addresses */ + if (in_addr_is_multicast(AF_INET6, &addr) || + in_addr_is_localhost(AF_INET6, &addr)) + return -EBADMSG; + res.addrs[i] = addr; + } + res.n_addrs = n_addrs; + res.family = AF_INET6; + + /* svc params */ + r = dnr_parse_svc_params(optval + offset, optlen-offset, &res); + if (r < 0) + return r; + + /* Append this resolver */ + if (!GREEDY_REALLOC(lease->dnr, lease->n_dnr+1)) + return -ENOMEM; + + lease->dnr[lease->n_dnr++] = TAKE_STRUCT(res); + + typesafe_qsort(lease->dnr, lease->n_dnr, dns_resolver_prio_compare); + + return 1; +} + +int sd_dhcp6_lease_get_dnr(sd_dhcp6_lease *lease, sd_dns_resolver **ret) { + assert_return(lease, -EINVAL); + assert_return(ret, -EINVAL); + + if (!lease->dnr) + return -ENODATA; + + *ret = lease->dnr; + return lease->n_dnr; +} + int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { int r; @@ -851,6 +944,15 @@ static int dhcp6_lease_parse_message( irt = unaligned_be32_sec_to_usec(optval, /* max_as_infinity = */ false); break; + case SD_DHCP6_OPTION_V6_DNR: + r = dhcp6_lease_add_dnr(lease, optval, optlen); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to parse DNR option, ignoring: %m"); + if (r == 0) + log_dhcp6_client(client, "Received ADN-only DNRv6 option, ignoring."); + + break; + case SD_DHCP6_OPTION_VENDOR_OPTS: r = dhcp6_lease_add_vendor_option(lease, optval, optlen); if (r < 0) @@ -904,6 +1006,7 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { dhcp6_ia_free(lease->ia_na); dhcp6_ia_free(lease->ia_pd); free(lease->dns); + dns_resolver_done_many(lease->dnr, lease->n_dnr); free(lease->fqdn); free(lease->captive_portal); strv_free(lease->domains); diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h index e18d57817f..d6bcceb2a2 100644 --- a/src/systemd/sd-dhcp6-lease.h +++ b/src/systemd/sd-dhcp6-lease.h @@ -30,6 +30,7 @@ _SD_BEGIN_DECLARATIONS; typedef struct sd_dhcp6_lease sd_dhcp6_lease; +typedef struct sd_dns_resolver sd_dns_resolver; int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret); int sd_dhcp6_lease_get_t1(sd_dhcp6_lease *lease, uint64_t *ret); @@ -74,6 +75,7 @@ int sd_dhcp6_lease_get_pd_lifetime_timestamp( int sd_dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease); int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret); +int sd_dhcp6_lease_get_dnr(sd_dhcp6_lease *lease, sd_dns_resolver **ret); int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret); int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret); int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret); diff --git a/src/systemd/sd-dhcp6-protocol.h b/src/systemd/sd-dhcp6-protocol.h index 78c80f7c7e..b1d9ca1ffa 100644 --- a/src/systemd/sd-dhcp6-protocol.h +++ b/src/systemd/sd-dhcp6-protocol.h @@ -165,8 +165,9 @@ enum { SD_DHCP6_OPTION_SLAP_QUAD = 140, /* RFC 8948 */ SD_DHCP6_OPTION_V6_DOTS_RI = 141, /* RFC 8973 */ SD_DHCP6_OPTION_V6_DOTS_ADDRESS = 142, /* RFC 8973 */ - SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF = 143 /* RFC 6153 */ - /* option codes 144-65535 are unassigned */ + SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF = 143, /* RFC 6153 */ + SD_DHCP6_OPTION_V6_DNR = 144 /* RFC 9463 */ + /* option codes 145-65535 are unassigned */ }; _SD_END_DECLARATIONS; From c691f9d98459d5422861e08eb9c43ba25bc3ecba Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Wed, 17 Jan 2024 18:16:47 -0700 Subject: [PATCH 11/20] network: Introduce UseDNR DHCPv6 option This is equivalent to the DHCPv4 option introduced earlier. --- src/libsystemd-network/dhcp6-option.c | 1 + src/network/networkd-dhcp-common.h | 1 + src/network/networkd-dhcp6.c | 6 ++++++ src/network/networkd-dns.c | 3 +++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + 7 files changed, 14 insertions(+) diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 787cc37d00..507cd3547d 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -206,6 +206,7 @@ bool dhcp6_option_can_request(uint16_t option) { case SD_DHCP6_OPTION_V6_DOTS_RI: case SD_DHCP6_OPTION_V6_DOTS_ADDRESS: case SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF: + case SD_DHCP6_OPTION_V6_DNR: return true; default: return false; diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index 194930a87c..778378f5ff 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -5,6 +5,7 @@ #include "conf-parser.h" #include "dhcp-duid-internal.h" +#include "dns-resolver-internal.h" #include "in-addr-util.h" #include "set.h" #include "time-util.h" diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 3f773cbb99..fdab0e0e23 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -646,6 +646,12 @@ static int dhcp6_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request domains: %m"); } + if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP6)) { + r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_V6_DNR); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request DNR: %m"); + } + if (link->network->dhcp6_use_captive_portal > 0) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_CAPTIVE_PORTAL); if (r < 0) diff --git a/src/network/networkd-dns.c b/src/network/networkd-dns.c index fb2b282bc8..b57785a92a 100644 --- a/src/network/networkd-dns.c +++ b/src/network/networkd-dns.c @@ -106,6 +106,9 @@ bool link_get_use_dnr(Link *link, NetworkConfigSource proto) { case NETWORK_CONFIG_SOURCE_DHCP4: n = link->network->dhcp_use_dnr; break; + case NETWORK_CONFIG_SOURCE_DHCP6: + n = link->network->dhcp6_use_dnr; + break; default: assert_not_reached(); } diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 89be7e8ae2..61dd333e1d 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -279,6 +279,7 @@ DHCPv4.RapidCommit, config_parse_tristate, DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address) DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix) DHCPv6.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp6_use_dns) +DHCPv6.UseDNR, config_parse_tristate, 0, offsetof(Network, dhcp6_use_dnr) DHCPv6.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp6_use_hostname) DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Network, dhcp6_use_domains) DHCPv6.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp6_use_ntp) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 873894d82c..6339e25cef 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -408,6 +408,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp6_use_address = true, .dhcp6_use_pd_prefix = true, .dhcp6_use_dns = -1, + .dhcp6_use_dnr = -1, .dhcp6_use_domains = _USE_DOMAINS_INVALID, .dhcp6_use_hostname = true, .dhcp6_use_ntp = -1, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index fbcc5be9c5..74fefde07a 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -186,6 +186,7 @@ struct Network { bool dhcp6_send_hostname; bool dhcp6_send_hostname_set; int dhcp6_use_dns; + int dhcp6_use_dnr; bool dhcp6_use_hostname; int dhcp6_use_ntp; bool dhcp6_use_captive_portal; From 168ad243cc7d5632e24dfc75f28d8e25c2076f83 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Wed, 17 Jan 2024 19:27:21 -0700 Subject: [PATCH 12/20] network: Serialize DHCPv6 DNR servers This serializes DNR servers acquired by V6_DNR option, equivalent to the V4_DNR option. --- src/network/networkd-state-file.c | 43 +++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index 14d4572751..e6c4ec64c0 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -143,6 +143,25 @@ static int link_put_dns(Link *link, OrderedSet **s) { } } + if (link->dhcp6_lease && link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP6)) { + sd_dns_resolver *resolvers; + + r = sd_dhcp6_lease_get_dnr(link->dhcp6_lease, &resolvers); + if (r >= 0 ) { + struct in_addr_full **dot_servers; + size_t n = 0; + CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free); + + r = dns_resolvers_to_dot_addrs(resolvers, r, &dot_servers, &n); + if (r < 0) + return r; + + r = ordered_set_put_dns_servers(s, link->ifindex, dot_servers, n); + if (r < 0) + return r; + } + } + if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) { NDiscRDNSS *a; @@ -546,7 +565,9 @@ static void serialize_resolvers( const char *lvalue, bool *space, sd_dhcp_lease *lease, - bool conditional) { + bool conditional, + sd_dhcp6_lease *lease6, + bool conditional6) { bool _space = false; if (!space) @@ -571,6 +592,22 @@ static void serialize_resolvers( fputstrv(f, names, NULL, space); } + if (lease6 && conditional6) { + sd_dns_resolver *resolvers; + _cleanup_strv_free_ char **names = NULL; + int r; + + r = sd_dhcp6_lease_get_dnr(lease6, &resolvers); + if (r < 0) + return (void) log_warning_errno(r, "Failed to get DNR from DHCPv6 lease, ignoring."); + + r = dns_resolvers_to_dot_strv(resolvers, r, &names); + if (r < 0) + return (void) log_warning_errno(r, "Failed to get DoT servers from DHCPv6 DNR, ignoring."); + if (r > 0) + fputstrv(f, names, NULL, space); + } + if (lvalue) fputc('\n', f); @@ -725,7 +762,9 @@ static int link_save(Link *link) { * assumed. */ serialize_resolvers(f, NULL, &space, link->dhcp_lease, - link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4)); + link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4), + link->dhcp6_lease, + link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP6)); serialize_addresses(f, NULL, &space, NULL, From cb386795c2f9d6064384480c3cc83eb7dd166c3a Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Wed, 17 Jan 2024 19:35:20 -0700 Subject: [PATCH 13/20] test-network: add DHCPv6 DNR test Same as the DHCPv4 test. --- test/test-network/systemd-networkd-tests.py | 34 ++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 66e14e6f1d..b193f86da9 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -769,6 +769,30 @@ def dnr_v4_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None): return pack(data, 2) +def dnr_v6_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None): + b = bytes() + pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c + pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n) + ipv6 = ipaddress.IPv6Address + class SvcParam(enum.Enum): + ALPN = 1 + DOHPATH = 7 + + data = pyton(prio) + + adn = adn.rstrip('.') + '.' + data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.')), 2) + + if not addrs: # adn-only mode + return data + + data += pack(b.join(ipv6(addr).packed for addr in addrs), 2) + data += pyton(SvcParam.ALPN.value) + pack(b.join(pack(alpn.encode('ascii')) for alpn in alpns), 2) + if dohpath is not None: + data += pyton(SvcParam.DOHPATH.value) + pack(dohpath.encode('utf-8'), 2) + + return data + def start_dnsmasq(*additional_options, interface='veth-peer', ra_mode=None, ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'): if ra_mode: ra_mode = f',{ra_mode}' @@ -7307,6 +7331,10 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.assertIn('0.7.4.2#homer.simpson', output) else: self.assertNotIn('8.8.8.8#dns.google', output) + if ipv6: + self.assertIn('2001:4860:4860::8888#dns.google', output) + else: + self.assertNotIn('2001:4860:4860::8888#dns.google', output) check_json(networkctl_json()) @@ -7316,10 +7344,14 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.wait_online('veth-peer:carrier') dnr_v4 = dnr_v4_instance_data(adn = "dns.google", addrs = ["8.8.8.8", "8.8.4.4"]) dnr_v4 += dnr_v4_instance_data(adn = "homer.simpson", addrs = ["0.7.4.2"], alpns = ("dot","h2","h3"), dohpath = "/springfield{?dns}") + dnr_v6 = dnr_v6_instance_data(adn = "dns.google", addrs = ["2001:4860:4860::8888", "2001:4860:4860::8844"]) masq = lambda bs: ':'.join(f"{b:02x}" for b in bs) - start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}') + start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}', + f'--dhcp-option=option6:144,{masq(dnr_v6)}') + check(self, True, True) check(self, True, False) + check(self, False, True) check(self, False, False) def test_dhcp_client_use_captive_portal(self): From 0c90d1d2f2433451c43e6b69c02186195e2d317b Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Fri, 19 Jan 2024 18:26:26 -0700 Subject: [PATCH 14/20] ndisc: Parse RFC9463 encrypted DNS (DNR) option This option is equivalent to the V4/V6 DNR options for DHCP. --- src/libsystemd-network/ndisc-option.c | 157 +++++++++++++++++++++ src/libsystemd-network/ndisc-option.h | 30 ++++ src/libsystemd-network/sd-ndisc-router.c | 7 + src/network/networkd-link.h | 1 + src/network/networkd-ndisc.c | 166 ++++++++++++++++++++++- src/network/networkd-ndisc.h | 7 + src/systemd/sd-ndisc-router.h | 6 + 7 files changed, 373 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c index af38c32be1..afa07c92c5 100644 --- a/src/libsystemd-network/ndisc-option.c +++ b/src/libsystemd-network/ndisc-option.c @@ -3,6 +3,7 @@ #include #include +#include "dns-resolver-internal.h" #include "dns-domain.h" #include "ether-addr-util.h" #include "hostname-util.h" @@ -89,6 +90,13 @@ static void ndisc_dnssl_done(sd_ndisc_dnssl *dnssl) { strv_free(dnssl->domains); } +static void ndisc_dnr_done(sd_ndisc_dnr *dnr) { + if (!dnr) + return; + + sd_dns_resolver_unref(dnr->resolver); +} + sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) { if (!option) return NULL; @@ -109,6 +117,10 @@ sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) { case SD_NDISC_OPTION_CAPTIVE_PORTAL: free(option->captive_portal); break; + + case SD_NDISC_OPTION_ENCRYPTED_DNS: + ndisc_dnr_done(&option->encrypted_dns); + break; } return mfree(option); @@ -1270,6 +1282,147 @@ static int ndisc_option_build_prefix64(const sd_ndisc_option *option, usec_t tim return 0; } +int ndisc_option_add_encrypted_dns_internal( + Set **options, + size_t offset, + sd_dns_resolver *res, + usec_t lifetime, + usec_t valid_until) { + assert(options); + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_ENCRYPTED_DNS, offset); + if (!p) + return -ENOMEM; + + p->encrypted_dns = (sd_ndisc_dnr) { + .resolver = res, + .lifetime = lifetime, + .valid_until = valid_until, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_get_dns_name(const uint8_t *optval, size_t optlen, char **ret) { + _cleanup_free_ char *name = NULL; + int r; + + assert(optval || optlen == 0); + assert(ret); + + r = dns_name_from_wire_format(&optval, &optlen, &name); + if (r < 0) + return r; + if (r == 0 || optlen != 0) + return -EBADMSG; + + *ret = TAKE_PTR(name); + return r; +} + +static int ndisc_option_parse_encrypted_dns(Set **options, size_t offset, size_t len, const uint8_t *opt) { + int r; + + assert(options); + assert(opt); + _cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {}; + usec_t lifetime; + size_t ilen; + + /* Every field up to and including adn must be present */ + if (len < 2*8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_ENCRYPTED_DNS) + return -EBADMSG; + + size_t off = 2; + + /* Priority */ + res.priority = unaligned_read_be16(opt + off); + /* Alias mode is not allowed */ + if (res.priority == 0) + return -EBADMSG; + off += sizeof(uint16_t); + + /* Lifetime */ + lifetime = unaligned_be32_sec_to_usec(opt + off, /* max_as_infinity = */ true); + off += sizeof(uint32_t); + + /* adn field (length + dns-name) */ + ilen = unaligned_read_be16(opt + off); + off += sizeof(uint16_t); + if (off + ilen > len) + return -EBADMSG; + + r = ndisc_get_dns_name(opt + off, ilen, &res.auth_name); + if (r < 0) + return r; + if (dns_name_is_root(res.auth_name)) + return -EBADMSG; + off += ilen; + + /* This is the last field in adn-only mode, sans padding */ + if (8 * DIV_ROUND_UP(off, 8) == len && memeqzero(opt + off, len - off)) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Received ADN-only encrypted DNS option, ignoring."); + + /* Fields following the variable (octets) length adn field are no longer certain to be aligned. */ + + /* addrs (length + packed struct in6_addr) */ + if (off + sizeof(uint16_t) > len) + return -EBADMSG; + ilen = unaligned_read_be16(opt + off); + off += sizeof(uint16_t); + if (off + ilen > len || ilen % (sizeof(struct in6_addr)) != 0) + return -EBADMSG; + + size_t n_addrs = ilen / (sizeof(struct in6_addr)); + if (n_addrs == 0) + return -EBADMSG; + res.addrs = new(union in_addr_union, n_addrs); + if (!res.addrs) + return -ENOMEM; + + for (size_t i = 0; i < n_addrs; i++) { + union in_addr_union addr; + memcpy(&addr.in6, opt + off, sizeof(struct in6_addr)); + if (in_addr_is_multicast(AF_INET6, &addr) || + in_addr_is_localhost(AF_INET, &addr)) + return -EBADMSG; + res.addrs[i] = addr; + off += sizeof(struct in6_addr); + } + res.n_addrs = n_addrs; + res.family = AF_INET6; + + /* SvcParam field. (length + SvcParams) */ + if (off + sizeof(uint16_t) > len) + return -EBADMSG; + ilen = unaligned_read_be16(opt + off); + off += sizeof(uint16_t); + if (off + ilen > len) + return -EBADMSG; + + r = dnr_parse_svc_params(opt + off, ilen, &res); + if (r < 0) + return r; + if (r == 0) /* This indicates a valid message we don't support */ + return -EOPNOTSUPP; + off += ilen; + + /* the remaining padding bytes must be zeroed */ + if (len - off >= 8 || !memeqzero(opt + off, len - off)) + return -EBADMSG; + + sd_dns_resolver *new_res = new(sd_dns_resolver, 1); + if (!new_res) + return -ENOMEM; + + *new_res = TAKE_STRUCT(res); + + return ndisc_option_add_encrypted_dns(options, offset, new_res, lifetime); +} + static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) { assert(options); assert(opt); @@ -1376,6 +1529,10 @@ int ndisc_parse_options(ICMP6Packet *packet, Set **ret_options) { r = ndisc_option_parse_prefix64(&options, offset, length, opt); break; + case SD_NDISC_OPTION_ENCRYPTED_DNS: + r = ndisc_option_parse_encrypted_dns(&options, offset, length, opt); + break; + default: r = ndisc_option_parse_default(&options, offset, length, opt); } diff --git a/src/libsystemd-network/ndisc-option.h b/src/libsystemd-network/ndisc-option.h index d7bd86147b..5752533e0f 100644 --- a/src/libsystemd-network/ndisc-option.h +++ b/src/libsystemd-network/ndisc-option.h @@ -9,6 +9,7 @@ #include #include "sd-ndisc-protocol.h" +#include "sd-dns-resolver.h" #include "icmp6-packet.h" #include "macro.h" @@ -66,6 +67,12 @@ typedef struct sd_ndisc_prefix64 { usec_t valid_until; } sd_ndisc_prefix64; +typedef struct sd_ndisc_dnr { + sd_dns_resolver *resolver; + usec_t lifetime; + usec_t valid_until; +} sd_ndisc_dnr; + typedef struct sd_ndisc_option { uint8_t type; size_t offset; @@ -83,6 +90,7 @@ typedef struct sd_ndisc_option { sd_ndisc_dnssl dnssl; /* SD_NDISC_OPTION_DNSSL */ char *captive_portal; /* SD_NDISC_OPTION_CAPTIVE_PORTAL */ sd_ndisc_prefix64 prefix64; /* SD_NDISC_OPTION_PREF64 */ + sd_ndisc_dnr encrypted_dns; /* SD_NDISC_OPTION_ENCRYPTED_DNS */ }; } sd_ndisc_option; @@ -327,4 +335,26 @@ static inline int ndisc_option_set_prefix64( return ndisc_option_add_prefix64_internal(options, 0, prefixlen, prefix, lifetime, valid_until); } +int ndisc_option_add_encrypted_dns_internal( + Set **options, + size_t offset, + sd_dns_resolver *res, + usec_t lifetime, + usec_t valid_until); +static inline int ndisc_option_add_encrypted_dns( + Set **options, + size_t offset, + sd_dns_resolver *res, + usec_t lifetime) { + return ndisc_option_add_encrypted_dns_internal(options, offset, res, lifetime, USEC_INFINITY); +} +static inline int ndisc_option_set_encrypted_dns( + Set **options, + size_t offset, + sd_dns_resolver *res, + usec_t lifetime, + usec_t valid_until) { + return ndisc_option_add_encrypted_dns_internal(options, 0, res, lifetime, valid_until); +} + int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr, Set *options, usec_t timestamp); diff --git a/src/libsystemd-network/sd-ndisc-router.c b/src/libsystemd-network/sd-ndisc-router.c index 9ca737d493..c978547f81 100644 --- a/src/libsystemd-network/sd-ndisc-router.c +++ b/src/libsystemd-network/sd-ndisc-router.c @@ -8,9 +8,12 @@ #include "sd-ndisc.h" #include "alloc-util.h" +#include "dns-domain.h" +#include "dns-resolver-internal.h" #include "ndisc-internal.h" #include "ndisc-router-internal.h" #include "string-table.h" +#include "unaligned.h" static sd_ndisc_router* ndisc_router_free(sd_ndisc_router *rt) { if (!rt) @@ -91,6 +94,7 @@ DEFINE_GET_TIMESTAMP(route_get_lifetime); DEFINE_GET_TIMESTAMP(rdnss_get_lifetime); DEFINE_GET_TIMESTAMP(dnssl_get_lifetime); DEFINE_GET_TIMESTAMP(prefix64_get_lifetime); +DEFINE_GET_TIMESTAMP(encrypted_dns_get_lifetime); int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { const struct nd_router_advert *a; @@ -342,3 +346,6 @@ DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, lifetime, uint64_t); DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefixlen, uint8_t); DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefix, struct in6_addr); DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, lifetime, uint64_t); + +DEFINE_GETTER(encrypted_dns, SD_NDISC_OPTION_ENCRYPTED_DNS, lifetime, uint64_t); +DEFINE_GETTER(encrypted_dns, SD_NDISC_OPTION_ENCRYPTED_DNS, resolver, sd_dns_resolver*); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 27bc299bc5..5816acace1 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -167,6 +167,7 @@ typedef struct Link { Set *ndisc_captive_portals; Set *ndisc_pref64; Set *ndisc_redirects; + Set *ndisc_dnr; uint32_t ndisc_mtu; unsigned ndisc_messages; bool ndisc_configured:1; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 81835c06e5..6110bcdec9 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -22,6 +22,7 @@ #include "networkd-route.h" #include "networkd-state-file.h" #include "networkd-sysctl.h" +#include "sort-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -1828,6 +1829,144 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) { return 0; } +static NDiscDNR* ndisc_dnr_free(NDiscDNR *x) { + if (!x) + return NULL; + + sd_dns_resolver_done(&x->resolver); + return mfree(x); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(NDiscDNR*, ndisc_dnr_free); + +static int ndisc_dnr_compare_func(const NDiscDNR *a, const NDiscDNR *b) { + return CMP(a->resolver.priority, b->resolver.priority) || + strcmp_ptr(a->resolver.auth_name, b->resolver.auth_name) || + CMP(a->resolver.transports, b->resolver.transports) || + CMP(a->resolver.port, b->resolver.port) || + strcmp_ptr(a->resolver.dohpath, b->resolver.dohpath) || + CMP(a->resolver.family, b->resolver.family) || + CMP(a->resolver.n_addrs, b->resolver.n_addrs) || + memcmp(a->resolver.addrs, b->resolver.addrs, sizeof(a->resolver.addrs[0]) * a->resolver.n_addrs); +} + +static void ndisc_dnr_hash_func(const NDiscDNR *x, struct siphash *state) { + assert(x); + + siphash24_compress_resolver(&x->resolver, state); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_dnr_hash_ops, + NDiscDNR, + ndisc_dnr_hash_func, + ndisc_dnr_compare_func, + ndisc_dnr_free); + +static int sd_dns_resolver_copy(const sd_dns_resolver *a, sd_dns_resolver *b) { + int r; + + assert(a); + assert(b); + + _cleanup_(sd_dns_resolver_done) sd_dns_resolver c = { + .priority = a->priority, + .transports = a->transports, + .port = a->port, + /* .auth_name */ + .family = a->family, + /* .addrs */ + /* .n_addrs */ + /* .dohpath */ + }; + + /* auth_name */ + r = strdup_to(&c.auth_name, a->auth_name); + if (r < 0) + return r; + + /* addrs, n_addrs */ + c.addrs = newdup(union in_addr_union, a->addrs, a->n_addrs); + if (!c.addrs) + return r; + c.n_addrs = a->n_addrs; + + /* dohpath */ + r = strdup_to(&c.dohpath, a->dohpath); + if (r < 0) + return r; + + *b = TAKE_STRUCT(c); + return 0; +} + +static int ndisc_router_process_encrypted_dns(Link *link, sd_ndisc_router *rt) { + int r; + + assert(link); + assert(link->network); + assert(rt); + + struct in6_addr router; + usec_t lifetime_usec; + sd_dns_resolver *res; + _cleanup_(ndisc_dnr_freep) NDiscDNR *new_entry = NULL; + + r = sd_ndisc_router_get_sender_address(rt, &router); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); + + r = sd_ndisc_router_encrypted_dns_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get lifetime of RA message: %m"); + + r = sd_ndisc_router_encrypted_dns_get_resolver(rt, &res); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get encrypted dns resolvers: %m"); + + NDiscDNR *dnr, d = { .resolver = *res }; + if (lifetime_usec == 0) { + dnr = set_remove(link->ndisc_dnr, &d); + if (dnr) { + ndisc_dnr_free(dnr); + link_dirty(link); + } + return 0; + } + + dnr = set_get(link->ndisc_dnr, &d); + if (dnr) { + dnr->router = router; + dnr->lifetime_usec = lifetime_usec; + return 0; + } + + new_entry = new(NDiscDNR, 1); + if (!new_entry) + return log_oom(); + + *new_entry = (NDiscDNR) { + .router = router, + /* .resolver, */ + .lifetime_usec = lifetime_usec, + }; + r = sd_dns_resolver_copy(res, &new_entry->resolver); + if (r < 0) + return log_oom(); + + /* Not sorted by priority */ + r = set_ensure_put(&link->ndisc_dnr, &ndisc_dnr_hash_ops, new_entry); + if (r < 0) + return log_oom(); + + assert(r > 0); + TAKE_PTR(new_entry); + + link_dirty(link); + + return 0; +} + static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { size_t n_captive_portal = 0; int r; @@ -1879,6 +2018,9 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { case SD_NDISC_OPTION_PREF64: r = ndisc_router_process_pref64(link, rt); break; + case SD_NDISC_OPTION_ENCRYPTED_DNS: + r = ndisc_router_process_encrypted_dns(link, rt); + break; } if (r < 0 && r != -EBADMSG) return r; @@ -1891,6 +2033,7 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t NDiscRDNSS *rdnss; NDiscCaptivePortal *cp; NDiscPREF64 *p64; + NDiscDNR *dnr; Address *address; Route *route; int r, ret = 0; @@ -1989,6 +2132,14 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t * the 'updated' flag. */ } + SET_FOREACH(dnr, link->ndisc_dnr) { + if (dnr->lifetime_usec > timestamp_usec) + continue; /* The resolver is still valid */ + + ndisc_dnr_free(set_remove(link->ndisc_dnr, dnr)); + updated = true; + } + if (updated) link_dirty(link); @@ -2016,6 +2167,7 @@ static int ndisc_setup_expire(Link *link) { NDiscDNSSL *dnssl; NDiscRDNSS *rdnss; NDiscPREF64 *p64; + NDiscDNR *dnr; Address *address; Route *route; int r; @@ -2068,6 +2220,9 @@ static int ndisc_setup_expire(Link *link) { SET_FOREACH(p64, link->ndisc_pref64) lifetime_usec = MIN(lifetime_usec, p64->lifetime_usec); + SET_FOREACH(dnr, link->ndisc_dnr) + lifetime_usec = MIN(lifetime_usec, dnr->lifetime_usec); + if (lifetime_usec == USEC_INFINITY) return 0; @@ -2324,6 +2479,14 @@ static int ndisc_neighbor_handle_router_message(Link *link, sd_ndisc_neighbor *n p64->router = current_address; } + NDiscDNR *dnr; + SET_FOREACH(dnr, link->ndisc_dnr) { + if (!in6_addr_equal(&dnr->router, &original_address)) + continue; + + dnr->router = current_address; + } + return 0; } @@ -2507,7 +2670,7 @@ int ndisc_stop(Link *link) { void ndisc_flush(Link *link) { assert(link); - /* Remove all addresses, routes, RDNSS, DNSSL, and Captive Portal entries, without exception. */ + /* Remove all addresses, routes, RDNSS, DNSSL, DNR, and Captive Portal entries, without exception. */ (void) ndisc_drop_outdated(link, /* router = */ NULL, /* timestamp_usec = */ USEC_INFINITY); (void) ndisc_drop_redirect(link, /* router = */ NULL); @@ -2517,6 +2680,7 @@ void ndisc_flush(Link *link) { link->ndisc_captive_portals = set_free(link->ndisc_captive_portals); link->ndisc_pref64 = set_free(link->ndisc_pref64); link->ndisc_redirects = set_free(link->ndisc_redirects); + link->ndisc_dnr = set_free(link->ndisc_dnr); link->ndisc_mtu = 0; } diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index 6094881ac5..2ee07d3f72 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -2,6 +2,7 @@ #pragma once #include "conf-parser.h" +#include "dns-resolver-internal.h" #include "time-util.h" typedef struct Address Address; @@ -49,6 +50,12 @@ typedef struct NDiscPREF64 { struct in6_addr prefix; } NDiscPREF64; +typedef struct NDiscDNR { + struct in6_addr router; + usec_t lifetime_usec; + sd_dns_resolver resolver; +} NDiscDNR; + static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) { return ((char*) n) + ALIGN(sizeof(NDiscDNSSL)); } diff --git a/src/systemd/sd-ndisc-router.h b/src/systemd/sd-ndisc-router.h index b07fefba3b..a7f1365381 100644 --- a/src/systemd/sd-ndisc-router.h +++ b/src/systemd/sd-ndisc-router.h @@ -28,6 +28,7 @@ _SD_BEGIN_DECLARATIONS; typedef struct sd_ndisc_router sd_ndisc_router; +typedef struct sd_dns_resolver sd_dns_resolver; sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt); sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt); @@ -87,6 +88,11 @@ int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, uint8_t *ret); int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); int sd_ndisc_router_prefix64_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +/* Specific option access: SD_NDISC_OPTION_ENCRYPTED_DNS */ +int sd_ndisc_router_encrypted_dns_get_resolver(sd_ndisc_router *rt, sd_dns_resolver **ret); +int sd_ndisc_router_encrypted_dns_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_encrypted_dns_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); + _SD_END_DECLARATIONS; #endif From 9c683c0e1f431ea2c0f1c2136128ab2c6be0e08d Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Mon, 22 Jan 2024 14:37:42 -0700 Subject: [PATCH 15/20] network: Introduce IPv6RA UseDNR= option Same as the DHCP v4/v6 options, this controls the use of DNR received from ipv6ra. --- man/systemd.network.xml | 10 ++++++++++ src/network/networkd-dns.c | 3 +++ src/network/networkd-ndisc.c | 3 +++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + 6 files changed, 19 insertions(+) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index eb6eaf3ce2..567d41bf23 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -3431,6 +3431,16 @@ Token=prefixstable:2002:da8:1:: + + UseDNR= + + When true, the DNR servers received in the Router Advertisement will be used. Defaults to + the value of . + + + + + UseDomains= diff --git a/src/network/networkd-dns.c b/src/network/networkd-dns.c index b57785a92a..25a96eb486 100644 --- a/src/network/networkd-dns.c +++ b/src/network/networkd-dns.c @@ -109,6 +109,9 @@ bool link_get_use_dnr(Link *link, NetworkConfigSource proto) { case NETWORK_CONFIG_SOURCE_DHCP6: n = link->network->dhcp6_use_dnr; break; + case NETWORK_CONFIG_SOURCE_NDISC: + n = link->network->ndisc_use_dnr; + break; default: assert_not_reached(); } diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 6110bcdec9..53d1297d1a 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -1912,6 +1912,9 @@ static int ndisc_router_process_encrypted_dns(Link *link, sd_ndisc_router *rt) { sd_dns_resolver *res; _cleanup_(ndisc_dnr_freep) NDiscDNR *new_entry = NULL; + if (!link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_NDISC)) + return 0; + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 61dd333e1d..62662654a2 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -309,6 +309,7 @@ IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_onlink_prefix) IPv6AcceptRA.UsePREF64, config_parse_bool, 0, offsetof(Network, ndisc_use_pref64) IPv6AcceptRA.UseDNS, config_parse_tristate, 0, offsetof(Network, ndisc_use_dns) +IPv6AcceptRA.UseDNR, config_parse_tristate, 0, offsetof(Network, ndisc_use_dnr) IPv6AcceptRA.UseDomains, config_parse_use_domains, 0, offsetof(Network, ndisc_use_domains) IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ndisc_use_mtu) IPv6AcceptRA.UseHopLimit, config_parse_bool, 0, offsetof(Network, ndisc_use_hop_limit) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 6339e25cef..3691f7730d 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -484,6 +484,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .ndisc = -1, .ndisc_use_redirect = true, .ndisc_use_dns = -1, + .ndisc_use_dnr = -1, .ndisc_use_gateway = true, .ndisc_use_captive_portal = true, .ndisc_use_route_prefix = true, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 74fefde07a..b4ab117928 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -345,6 +345,7 @@ struct Network { /* NDisc support */ int ndisc; + int ndisc_use_dnr; bool ndisc_use_redirect; int ndisc_use_dns; bool ndisc_use_gateway; From 65187c46ef6d43df40f84d0a1bbcf5a61f7c1556 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Mon, 22 Jan 2024 14:37:42 -0700 Subject: [PATCH 16/20] network: Serialize ipv6ra DNR Serialize DNR servers acquired by ipv6ra option, same as the V4/V6 DNR DHCP options. --- src/network/networkd-state-file.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index e6c4ec64c0..4cbf6ec92f 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -172,6 +172,24 @@ static int link_put_dns(Link *link, OrderedSet **s) { } } + if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_NDISC)) { + NDiscDNR *a; + + SET_FOREACH(a, link->ndisc_dnr) { + struct in_addr_full **dot_servers = NULL; + size_t n = 0; + CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free); + + r = dns_resolvers_to_dot_addrs(&a->resolver, 1, &dot_servers, &n); + if (r < 0) + return r; + + r = ordered_set_put_dns_servers(s, link->ifindex, dot_servers, n); + if (r < 0) + return r; + } + } + return 0; } @@ -766,6 +784,12 @@ static int link_save(Link *link) { link->dhcp6_lease, link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP6)); + if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_NDISC)) { + NDiscDNR *dnr; + SET_FOREACH(dnr, link->ndisc_dnr) + serialize_dnr(f, &dnr->resolver, 1, &space); + } + serialize_addresses(f, NULL, &space, NULL, link->dhcp_lease, From 8ef7b6e656cfa6b9a99cadba92360ac25273f2e9 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Wed, 24 Jan 2024 23:49:03 -0700 Subject: [PATCH 17/20] test/fuzz: add dnr packets The structure of DNR options is considerably more complicated than most DHCP options, and as a result the fuzzer has poor coverage of these code paths. This adds some DNR packets to the fuzzing corpus, not with the intent of capturing some specific edge case, but with the intent to rapidly improve the fuzzers' coverage of these codepaths by giving it a valid example to begin with. Also include an ndisc router advert with a few Encrypted DNS options, for the same purpose. --- test/fuzz/fuzz-dhcp-client/dnr_v4 | Bin 0 -> 300 bytes test/fuzz/fuzz-dhcp6-client/dnr_v6 | Bin 0 -> 90 bytes test/fuzz/fuzz-ndisc-rs/encrypted-dns | Bin 0 -> 152 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/fuzz/fuzz-dhcp-client/dnr_v4 create mode 100644 test/fuzz/fuzz-dhcp6-client/dnr_v6 create mode 100644 test/fuzz/fuzz-ndisc-rs/encrypted-dns diff --git a/test/fuzz/fuzz-dhcp-client/dnr_v4 b/test/fuzz/fuzz-dhcp-client/dnr_v4 new file mode 100644 index 0000000000000000000000000000000000000000..2294736277414122742dc37ccb8dbd49c3554c80 GIT binary patch literal 300 zcmZQ#U}G3*V38SvBm*NSb7E35b46tp0}CS)GYbPF0}FFXehEYhC=8NjU}0rr=YUB8 E025XT9{>OV literal 0 HcmV?d00001 diff --git a/test/fuzz/fuzz-dhcp6-client/dnr_v6 b/test/fuzz/fuzz-dhcp6-client/dnr_v6 new file mode 100644 index 0000000000000000000000000000000000000000..142de3428901ab425cfef71718664e0f6200c12a GIT binary patch literal 90 zcmZQ)0D=h&>I{qwoXm+y$;=g%RSXOQ3Ls$wU<69DFsI~~z?A}(fs|t^Wr8UM0Kk$7 Axc~qF literal 0 HcmV?d00001 diff --git a/test/fuzz/fuzz-ndisc-rs/encrypted-dns b/test/fuzz/fuzz-ndisc-rs/encrypted-dns new file mode 100644 index 0000000000000000000000000000000000000000..59240efcc80ab0fc0caad31ed318e4ec59759377 GIT binary patch literal 152 zcmZo;`2XL50RbkkF);oI0tQazipnbH#H3^f1_1>|-W>=fLM99x42%pc%qjUL3=_B+ zn4s!`YC(D!7!**|hgG2RXYFB-1?p!9>1WC?Vqj Date: Wed, 3 Apr 2024 13:16:05 -0700 Subject: [PATCH 18/20] network: add dnr resolvers to networkctl status json output --- src/libsystemd-network/sd-dhcp6-lease.c | 2 +- src/network/networkd-json.c | 115 ++++++++++++++++++++++++ src/shared/dns-resolver-internal.h | 2 + src/shared/sd-dns-resolver.c | 26 ++++++ 4 files changed, 144 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 22707b771d..2729984e30 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -491,7 +491,7 @@ static int dhcp6_lease_add_dnr(sd_dhcp6_lease *lease, const uint8_t *optval, siz res.addrs = new(union in_addr_union, n_addrs); if (!res.addrs) - return -ENOMEM + return -ENOMEM; for (size_t i = 0; i < n_addrs; i++) { union in_addr_union addr = {.in6 = addrs[i]}; diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index 4eafc62c7e..24f3378d57 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -503,6 +503,117 @@ static int dns_append_json(Link *link, sd_json_variant **v) { return json_variant_set_field_non_null(v, "DNS", array); } +static int dnr_append_json_one(Link *link, const struct sd_dns_resolver *res, NetworkConfigSource s, const union in_addr_union *p, sd_json_variant **array) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *addrs_array = NULL; + _cleanup_strv_free_ char **transports = NULL; + int r; + + assert(link); + assert(res); + assert(array); + + FOREACH_ARRAY(addr, res->addrs, res->n_addrs) { + r = sd_json_variant_append_arrayb( + &addrs_array, + JSON_BUILD_IN_ADDR(addr, res->family)); + if (r < 0) + return r; + } + + r = dns_resolver_transports_to_strv(res->transports, &transports); + if (r < 0) + return r; + + //FIXME ifindex? + return sd_json_variant_append_arrayb( + array, + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_INTEGER("Family", res->family), + SD_JSON_BUILD_PAIR_INTEGER("Priority", res->priority), + JSON_BUILD_PAIR_VARIANT_NON_NULL("Addresses", addrs_array), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("Port", res->port), + JSON_BUILD_PAIR_STRING_NON_EMPTY("ServerName", res->auth_name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("DoHPath", res->dohpath), + JSON_BUILD_PAIR_STRV_NON_EMPTY("Transports", transports), + SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)), + JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, res->family))); +} + +static int dnr_append_json(Link *link, sd_json_variant **v) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + int r; + + assert(link); + assert(v); + + if (!link->network) + return 0; + + if (link->dhcp_lease && link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4)) { + struct sd_dns_resolver *dnr; + union in_addr_union s; + int n_dnr; + + r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &s.in); + if (r < 0) + return r; + + n_dnr = sd_dhcp_lease_get_dnr(link->dhcp_lease, &dnr); + if (n_dnr < 0) + return 0; + + FOREACH_ARRAY(res, dnr, n_dnr) { + r = dnr_append_json_one(link, + res, + NETWORK_CONFIG_SOURCE_DHCP4, + &s, + &array); + if (r < 0) + return r; + } + } + + if (link->dhcp6_lease && link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP6)) { + struct sd_dns_resolver *dnr; + union in_addr_union s; + int n_dnr; + + r = sd_dhcp6_lease_get_server_address(link->dhcp6_lease, &s.in6); + if (r < 0) + return r; + + n_dnr = sd_dhcp6_lease_get_dnr(link->dhcp6_lease, &dnr); + if (n_dnr < 0) + return 0; + + FOREACH_ARRAY(res, dnr, n_dnr) { + r = dnr_append_json_one(link, + res, + NETWORK_CONFIG_SOURCE_DHCP6, + &s, + &array); + if (r < 0) + return r; + } + } + + if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_NDISC)) { + NDiscDNR *a; + + SET_FOREACH(a, link->ndisc_dnr) { + r = dnr_append_json_one(link, + &a->resolver, + NETWORK_CONFIG_SOURCE_NDISC, + &(union in_addr_union) { .in6 = a->router }, + &array); + if (r < 0) + return r; + } + } + + return json_variant_set_field_non_null(v, "DNR", array); +} + static int server_append_json_one_addr(int family, const union in_addr_union *a, NetworkConfigSource s, const union in_addr_union *p, sd_json_variant **array) { assert(IN_SET(family, AF_INET, AF_INET6)); assert(a); @@ -1268,6 +1379,10 @@ int link_build_json(Link *link, sd_json_variant **ret) { if (r < 0) return r; + r = dnr_append_json(link, &v); + if (r < 0) + return r; + r = ntp_append_json(link, &v); if (r < 0) return r; diff --git a/src/shared/dns-resolver-internal.h b/src/shared/dns-resolver-internal.h index 08ef782408..e5c185c2e0 100644 --- a/src/shared/dns-resolver-internal.h +++ b/src/shared/dns-resolver-internal.h @@ -43,6 +43,8 @@ struct sd_dns_resolver { void siphash24_compress_resolver(const sd_dns_resolver *res, struct siphash *state); +int dns_resolver_transports_to_strv(sd_dns_alpn_flags transports, char ***ret); + int dns_resolvers_to_dot_addrs(const sd_dns_resolver *resolvers, size_t n_resolvers, struct in_addr_full ***ret_addrs, size_t *ret_n_addrs); diff --git a/src/shared/sd-dns-resolver.c b/src/shared/sd-dns-resolver.c index 4c76df18d1..548672cd33 100644 --- a/src/shared/sd-dns-resolver.c +++ b/src/shared/sd-dns-resolver.c @@ -174,6 +174,32 @@ const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i); } +int dns_resolver_transports_to_strv(sd_dns_alpn_flags transports, char ***ret) { + _cleanup_strv_free_ char **ans = NULL; + + assert(ret); + + if (FLAGS_SET(transports, SD_DNS_ALPN_DO53)) { + /* Do53 has no ALPN, this flag is only for our own usage. */ + } + + if (FLAGS_SET(transports, SD_DNS_ALPN_HTTP_2_TLS)) + if (strv_extend(&ans, "h2") < 0) + return -ENOMEM; + if (FLAGS_SET(transports, SD_DNS_ALPN_HTTP_3)) + if (strv_extend(&ans, "h3") < 0) + return -ENOMEM; + if (FLAGS_SET(transports, SD_DNS_ALPN_DOT)) + if (strv_extend(&ans, "dot") < 0) + return -ENOMEM; + if (FLAGS_SET(transports, SD_DNS_ALPN_DOQ)) + if (strv_extend(&ans, "doq") < 0) + return -ENOMEM; + + *ret = TAKE_PTR(ans); + return 0; +} + int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *resolver) { size_t offset = 0; int r; From e3a23b1679901e310e5b6d5e13ef8f5e46e7dfd3 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Wed, 3 Apr 2024 19:04:33 -0700 Subject: [PATCH 19/20] ndisc: implement ndisc_option_build_encrypted_dns This is only used by the fuzzer so far. --- src/libsystemd-network/ndisc-option.c | 140 ++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c index afa07c92c5..1071d98b19 100644 --- a/src/libsystemd-network/ndisc-option.c +++ b/src/libsystemd-network/ndisc-option.c @@ -1423,6 +1423,142 @@ static int ndisc_option_parse_encrypted_dns(Set **options, size_t offset, size_t return ndisc_option_add_encrypted_dns(options, offset, new_res, lifetime); } +static int ndisc_option_build_encrypted_dns(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { + int r; + + assert(option); + assert(option->type == SD_NDISC_OPTION_ENCRYPTED_DNS); + assert(ret); + + size_t off, len, ilen, plen, poff; + + /* Everything up to adn field is required, so we need at least 2*8 bytes */ + _cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8); + if (!buf) + return -ENOMEM; + + _cleanup_strv_free_ char **alpns = NULL; + const sd_dns_resolver *res = option->encrypted_dns.resolver; + be32_t lifetime = usec_to_be32_sec(MIN(option->encrypted_dns.lifetime, + usec_sub_unsigned(option->encrypted_dns.valid_until, timestamp))); + + /* Type (Length field filled in last) */ + buf[0] = option->type; + + /* Priority */ + off = 2; + unaligned_write_be16(buf + off, res->priority); + off += sizeof(be16_t); + + /* Lifetime */ + memcpy(buf + off, &lifetime, sizeof(be32_t)); + off += sizeof(be32_t); + + /* ADN */ + //FIXME can the wire format be longer than this? + ilen = strlen(res->auth_name) + 2; + + /* From now on, there isn't guaranteed to be enough space to put each field */ + if (!GREEDY_REALLOC(buf, off + sizeof(uint16_t) + ilen)) + return -ENOMEM; + + r = dns_name_to_wire_format(res->auth_name, buf + off + sizeof(uint16_t), ilen, /* canonical = */ false); + if (r < 0) + return r; + unaligned_write_be16(buf + off, (uint16_t) r); + off += sizeof(uint16_t) + r; + + /* ADN-only mode */ + if (res->n_addrs == 0) + goto padding; + + /* addrs */ + if (size_multiply_overflow(sizeof(struct in6_addr), res->n_addrs)) + return -ENOMEM; + + ilen = res->n_addrs * sizeof(struct in6_addr); + if (!GREEDY_REALLOC(buf, off + sizeof(uint16_t) + ilen)) + return -ENOMEM; + + unaligned_write_be16(buf + off, ilen); + off += sizeof(uint16_t); + + FOREACH_ARRAY(addr, res->addrs, res->n_addrs) { + memcpy(buf + off, &addr->in6, sizeof(struct in6_addr)); + off += sizeof(struct in6_addr); + } + + /* SvcParam, MUST appear in order */ + poff = off + sizeof(uint16_t); + + /* ALPN */ + dns_resolver_transports_to_strv(res->transports, &alpns); + + /* res needs to have at least one valid transport */ + if (strv_isempty(alpns)) + return -EINVAL; + + plen = 0; + STRV_FOREACH(alpn, alpns) + plen += sizeof(uint8_t) + strlen(*alpn); + + if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen)) + return -ENOMEM; + + unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_ALPN); + poff += sizeof(uint16_t); + unaligned_write_be16(buf + poff, plen); + poff += sizeof(uint16_t); + + STRV_FOREACH(alpn, alpns) { + size_t alen = strlen(*alpn); + buf[poff++] = alen; + memcpy(buf + poff, *alpn, alen); + poff += alen; + } + + /* port */ + if (res->port > 0) { + plen = 2; + if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen)) + return -ENOMEM; + + unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_PORT); + poff += sizeof(uint16_t); + unaligned_write_be16(buf + poff, plen); + poff += sizeof(uint16_t); + unaligned_write_be16(buf + poff, res->port); + poff += sizeof(uint16_t); + } + + /* dohpath */ + if (res->dohpath) { + plen = strlen(res->dohpath); + if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen)) + return -ENOMEM; + + unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_DOHPATH); + poff += sizeof(uint16_t); + unaligned_write_be16(buf + poff, plen); + poff += sizeof(uint16_t); + memcpy(buf + poff, res->dohpath, plen); + poff += plen; + } + + unaligned_write_be16(buf + off, LESS_BY(poff, off)); + off = poff; + +padding: + len = DIV_ROUND_UP(off, 8); + if (!GREEDY_REALLOC(buf, 8*len)) + return -ENOMEM; + memzero(buf + off, 8*len - off); + + buf[1] = len; + *ret = TAKE_PTR(buf); + return 0; +} + static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) { assert(options); assert(opt); @@ -1644,6 +1780,10 @@ int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr, r = ndisc_option_build_prefix64(option, timestamp, &buf); break; + case SD_NDISC_OPTION_ENCRYPTED_DNS: + r = ndisc_option_build_encrypted_dns(option, timestamp, &buf); + break; + default: continue; } From ee2108dcd5f62593a3081b57cc92b3535d8096fa Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Tue, 25 Jun 2024 17:01:59 -0700 Subject: [PATCH 20/20] resolve: move sd-* api into libsystemd-network This duplicates the svc param constants for the benefit of the resolved-core library. --- .../dns-resolver-internal.h | 0 src/libsystemd-network/meson.build | 1 + .../sd-dns-resolver.c | 0 .../sd-dns-resolver.h | 0 src/resolve/resolved-dns-packet.c | 23 +++++++++++++++++-- src/resolve/resolved-dns-packet.h | 19 +++++++++++++++ src/resolve/resolved-dns-rr.c | 2 -- src/shared/meson.build | 1 - src/systemd/meson.build | 1 - 9 files changed, 41 insertions(+), 6 deletions(-) rename src/{shared => libsystemd-network}/dns-resolver-internal.h (100%) rename src/{shared => libsystemd-network}/sd-dns-resolver.c (100%) rename src/{systemd => libsystemd-network}/sd-dns-resolver.h (100%) diff --git a/src/shared/dns-resolver-internal.h b/src/libsystemd-network/dns-resolver-internal.h similarity index 100% rename from src/shared/dns-resolver-internal.h rename to src/libsystemd-network/dns-resolver-internal.h diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 718495cd8e..262ce9c4ef 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -23,6 +23,7 @@ sources = files( 'sd-dhcp-server.c', 'sd-dhcp6-client.c', 'sd-dhcp6-lease.c', + 'sd-dns-resolver.c', 'sd-ipv4acd.c', 'sd-ipv4ll.c', 'sd-lldp-rx.c', diff --git a/src/shared/sd-dns-resolver.c b/src/libsystemd-network/sd-dns-resolver.c similarity index 100% rename from src/shared/sd-dns-resolver.c rename to src/libsystemd-network/sd-dns-resolver.c diff --git a/src/systemd/sd-dns-resolver.h b/src/libsystemd-network/sd-dns-resolver.h similarity index 100% rename from src/systemd/sd-dns-resolver.h rename to src/libsystemd-network/sd-dns-resolver.h diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 1ab09f26d6..f9991d86ab 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -4,8 +4,6 @@ # include #endif -#include "dns-resolver-internal.h" - #include "alloc-util.h" #include "dns-domain.h" #include "escape.h" @@ -2883,6 +2881,27 @@ size_t dns_packet_size_unfragmented(DnsPacket *p) { return LESS_BY(p->fragsize, udp_header_size(p->family)); } +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_rcode_table[_DNS_RCODE_MAX_DEFINED] = { [DNS_RCODE_SUCCESS] = "SUCCESS", [DNS_RCODE_FORMERR] = "FORMERR", diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index ddee259b1f..0b22e557cf 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -370,6 +370,25 @@ DnsProtocol dns_protocol_from_string(const char *s) _pure_; extern const struct hash_ops dns_packet_hash_ops; +/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */ +enum { + DNS_SVC_PARAM_KEY_MANDATORY = 0, /* RFC 9460 § 8 */ + DNS_SVC_PARAM_KEY_ALPN = 1, /* RFC 9460 § 7.1 */ + DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN = 2, /* RFC 9460 § 7.1 */ + DNS_SVC_PARAM_KEY_PORT = 3, /* RFC 9460 § 7.2 */ + DNS_SVC_PARAM_KEY_IPV4HINT = 4, /* RFC 9460 § 7.3 */ + DNS_SVC_PARAM_KEY_ECH = 5, /* RFC 9460 */ + DNS_SVC_PARAM_KEY_IPV6HINT = 6, /* RFC 9460 § 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]) {}) + static inline uint64_t SD_RESOLVED_FLAGS_MAKE( DnsProtocol protocol, int family, diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index ed525883f2..6a4bd6aecd 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -2,8 +2,6 @@ #include -#include "dns-resolver-internal.h" - #include "alloc-util.h" #include "dns-domain.h" #include "dns-type.h" diff --git a/src/shared/meson.build b/src/shared/meson.build index 548b26d8c3..8fc39429e5 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -154,7 +154,6 @@ shared_sources = files( 'resize-fs.c', 'resolve-util.c', 'rm-rf.c', - 'sd-dns-resolver.c', 'securebits-util.c', 'selinux-util.c', 'serialize.c', diff --git a/src/systemd/meson.build b/src/systemd/meson.build index edcbc873e9..06ba4cbc07 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -35,7 +35,6 @@ _not_installed_headers = [ 'sd-dhcp6-lease.h', 'sd-dhcp6-option.h', 'sd-dhcp6-protocol.h', - 'sd-dns-resolver.h', 'sd-ipv4acd.h', 'sd-ipv4ll.h', 'sd-lldp-rx.h',