summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--man/systemd.network.xml23
-rw-r--r--src/libsystemd-network/dhcp-lease-internal.h3
-rw-r--r--src/libsystemd-network/dhcp-option.h1
-rw-r--r--src/libsystemd-network/dhcp6-lease-internal.h3
-rw-r--r--src/libsystemd-network/dhcp6-option.c77
-rw-r--r--src/libsystemd-network/dns-resolver-internal.h59
-rw-r--r--src/libsystemd-network/meson.build1
-rw-r--r--src/libsystemd-network/ndisc-option.c297
-rw-r--r--src/libsystemd-network/ndisc-option.h30
-rw-r--r--src/libsystemd-network/network-internal.c94
-rw-r--r--src/libsystemd-network/network-internal.h3
-rw-r--r--src/libsystemd-network/sd-dhcp-lease.c167
-rw-r--r--src/libsystemd-network/sd-dhcp6-lease.c103
-rw-r--r--src/libsystemd-network/sd-dns-resolver.c388
-rw-r--r--src/libsystemd-network/sd-dns-resolver.h44
-rw-r--r--src/libsystemd-network/sd-ndisc-router.c7
-rw-r--r--src/libsystemd-network/test-dhcp6-client.c13
-rw-r--r--src/network/networkd-dhcp-common.h1
-rw-r--r--src/network/networkd-dhcp4.c7
-rw-r--r--src/network/networkd-dhcp6.c6
-rw-r--r--src/network/networkd-dns.c31
-rw-r--r--src/network/networkd-dns.h1
-rw-r--r--src/network/networkd-json.c115
-rw-r--r--src/network/networkd-link.h1
-rw-r--r--src/network/networkd-ndisc.c169
-rw-r--r--src/network/networkd-ndisc.h7
-rw-r--r--src/network/networkd-network-gperf.gperf3
-rw-r--r--src/network/networkd-network.c3
-rw-r--r--src/network/networkd-network.h3
-rw-r--r--src/network/networkd-state-file.c125
-rw-r--r--src/resolve/resolved-dns-packet.c42
-rw-r--r--src/resolve/resolved-dns-packet.h36
-rw-r--r--src/shared/dns-domain.c69
-rw-r--r--src/shared/dns-domain.h1
-rw-r--r--src/shared/socket-netlink.c9
-rw-r--r--src/shared/socket-netlink.h1
-rw-r--r--src/systemd/sd-dhcp-lease.h2
-rw-r--r--src/systemd/sd-dhcp-protocol.h3
-rw-r--r--src/systemd/sd-dhcp6-lease.h2
-rw-r--r--src/systemd/sd-dhcp6-protocol.h5
-rw-r--r--src/systemd/sd-ndisc-router.h6
-rw-r--r--src/test/test-dns-domain.c89
-rw-r--r--test/fuzz/fuzz-dhcp-client/dnr_v4bin0 -> 300 bytes
-rw-r--r--test/fuzz/fuzz-dhcp6-client/dnr_v6bin0 -> 90 bytes
-rw-r--r--test/fuzz/fuzz-ndisc-rs/encrypted-dnsbin0 -> 152 bytes
-rwxr-xr-xtest/test-network/systemd-networkd-tests.py101
46 files changed, 2028 insertions, 123 deletions
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index d2a6773883..c12dc68801 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2606,6 +2606,18 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
</varlistentry>
<varlistentry>
+ <term><varname>UseDNR=</varname></term>
+ <listitem>
+ <para>When true, designated resolvers advertised by the DHCP server will be used as encrypted
+ DNS servers. See <ulink url="https://datatracker.ietf.org/doc/html/rfc9463">RFC 9463</ulink>.</para>
+
+ <para>Defaults to unset, and the value for <varname>UseDNS=</varname> will be used.</para>
+
+ <xi:include href="version-info.xml" xpointer="v257"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>UseMTU=</varname></term>
<listitem>
<para>When true, the interface maximum transmission unit from the DHCP server will be used on
@@ -3131,6 +3143,7 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
<varlistentry>
<term><varname>UseDNS=</varname></term>
+ <term><varname>UseDNR=</varname></term>
<term><varname>UseNTP=</varname></term>
<term><varname>UseHostname=</varname></term>
<term><varname>UseDomains=</varname></term>
@@ -3419,6 +3432,16 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
</varlistentry>
<varlistentry>
+ <term><varname>UseDNR=</varname></term>
+ <listitem>
+ <para> When true, the DNR servers received in the Router Advertisement will be used. Defaults to
+ the value of <option>UseDNS=</option>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v257"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>UseDomains=</varname></term>
<listitem>
<para>Takes a boolean, or the special value <literal>route</literal>. When true, the domain name
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 <stdint.h>
#include "sd-dhcp-option.h"
+#include "dns-resolver-internal.h"
#include "dhcp-protocol.h"
#include "hash-funcs.h"
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 <inttypes.h>
#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/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
index 83f40f3f02..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;
@@ -820,74 +821,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 +828,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 +855,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/dns-resolver-internal.h b/src/libsystemd-network/dns-resolver-internal.h
new file mode 100644
index 0000000000..e5c185c2e0
--- /dev/null
+++ b/src/libsystemd-network/dns-resolver-internal.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <errno.h>
+
+#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_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);
+
+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/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/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c
index af38c32be1..1071d98b19 100644
--- a/src/libsystemd-network/ndisc-option.c
+++ b/src/libsystemd-network/ndisc-option.c
@@ -3,6 +3,7 @@
#include <linux/ipv6.h>
#include <netinet/icmp6.h>
+#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,283 @@ 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_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);
@@ -1376,6 +1665,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);
}
@@ -1487,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;
}
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 <sys/uio.h>
#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/network-internal.c b/src/libsystemd-network/network-internal.c
index a2b88963da..1563655b79 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,
@@ -123,6 +124,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/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c
index 37f4b3b2c9..1c0cd6829b 100644
--- a/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/libsystemd-network/sd-dhcp-lease.c
@@ -11,6 +11,7 @@
#include <unistd.h>
#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)
@@ -1140,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);
@@ -1248,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,
@@ -1285,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,
@@ -1387,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/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c
index 261ab83c64..647dea88bd 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"
@@ -464,6 +465,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;
@@ -870,6 +963,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)
@@ -920,6 +1022,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/libsystemd-network/sd-dns-resolver.c b/src/libsystemd-network/sd-dns-resolver.c
new file mode 100644
index 0000000000..548672cd33
--- /dev/null
+++ b/src/libsystemd-network/sd-dns-resolver.c
@@ -0,0 +1,388 @@
+/* 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 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;
+
+ 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/libsystemd-network/sd-dns-resolver.h b/src/libsystemd-network/sd-dns-resolver.h
new file mode 100644
index 0000000000..79d00ba9bc
--- /dev/null
+++ b/src/libsystemd-network/sd-dns-resolver.h
@@ -0,0 +1,44 @@
+#ifndef SD_DNS_RESOLVER_H
+#define SD_DNS_RESOLVER_H
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#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 */
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/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index 29cbdc95b4..9f28ec8bec 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);
@@ -750,8 +747,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,
@@ -832,8 +829,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,
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-dhcp4.c b/src/network/networkd-dhcp4.c
index ecda55e5be..2dd29bca94 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -1564,6 +1564,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 (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");
+ }
+
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-dhcp6.c b/src/network/networkd-dhcp6.c
index f1ecf64205..5294b43240 100644
--- a/src/network/networkd-dhcp6.c
+++ b/src/network/networkd-dhcp6.c
@@ -641,6 +641,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 d3da34da83..a8164912dc 100644
--- a/src/network/networkd-dns.c
+++ b/src/network/networkd-dns.c
@@ -94,6 +94,37 @@ 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;
+ 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();
+ }
+
+ /* 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-json.c b/src/network/networkd-json.c
index 452df9a17f..7ccf653f74 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/network/networkd-link.h b/src/network/networkd-link.h
index e86839af8e..89278f3a43 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -170,6 +170,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 fc06e5c38b..46f3954725 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"
@@ -1826,6 +1827,147 @@ 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;
+
+ 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");
+
+ 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;
@@ -1877,6 +2019,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;
@@ -1889,6 +2034,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;
@@ -1987,6 +2133,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);
@@ -2014,6 +2168,7 @@ static int ndisc_setup_expire(Link *link) {
NDiscDNSSL *dnssl;
NDiscRDNSS *rdnss;
NDiscPREF64 *p64;
+ NDiscDNR *dnr;
Address *address;
Route *route;
int r;
@@ -2066,6 +2221,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;
@@ -2322,6 +2480,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;
}
@@ -2505,7 +2671,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);
@@ -2515,6 +2681,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/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index b223ccaccd..50be74e55f 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -228,6 +228,7 @@ NextHop.Group, config_parse_nexthop_section,
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)
@@ -278,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)
@@ -307,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 1fff264036..3d399fe876 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -389,6 +389,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,
@@ -408,6 +409,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,
@@ -483,6 +485,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 66a8328e29..b4ab117928 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;
@@ -185,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;
@@ -343,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;
diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c
index 481e2d930b..4cbf6ec92f 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"
@@ -113,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;
@@ -124,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;
@@ -134,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;
}
@@ -522,6 +578,60 @@ static void serialize_addresses(
fputc('\n', f);
}
+static void serialize_resolvers(
+ FILE *f,
+ const char *lvalue,
+ bool *space,
+ sd_dhcp_lease *lease,
+ bool conditional,
+ sd_dhcp6_lease *lease6,
+ bool conditional6) {
+
+ 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 (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);
+
+ return;
+}
+
static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, UseDomains use_domains) {
bool space = false;
const char *p;
@@ -665,6 +775,21 @@ 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),
+ 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,
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index c32a1a9a67..f9991d86ab 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -2881,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",
@@ -2955,27 +2976,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 437c220b6a..6fe30bca73 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -363,33 +363,33 @@ 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_;
+#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 } })
+
+#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) })
+#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb } })
+
+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 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_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 */
+ 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 } })
-
-#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) })
-#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb } })
-
-extern const struct hash_ops dns_packet_hash_ops;
-
static inline uint64_t SD_RESOLVED_FLAGS_MAKE(
DnsProtocol protocol,
int family,
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c
index 3635167b33..c9963ade93 100644
--- a/src/shared/dns-domain.c
+++ b/src/shared/dns-domain.c
@@ -905,6 +905,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/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/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 */
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;
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
diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c
index 8088055295..053a93663c 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;
diff --git a/test/fuzz/fuzz-dhcp-client/dnr_v4 b/test/fuzz/fuzz-dhcp-client/dnr_v4
new file mode 100644
index 0000000000..2294736277
--- /dev/null
+++ b/test/fuzz/fuzz-dhcp-client/dnr_v4
Binary files differ
diff --git a/test/fuzz/fuzz-dhcp6-client/dnr_v6 b/test/fuzz/fuzz-dhcp6-client/dnr_v6
new file mode 100644
index 0000000000..142de34289
--- /dev/null
+++ b/test/fuzz/fuzz-dhcp6-client/dnr_v6
Binary files differ
diff --git a/test/fuzz/fuzz-ndisc-rs/encrypted-dns b/test/fuzz/fuzz-ndisc-rs/encrypted-dns
new file mode 100644
index 0000000000..59240efcc8
--- /dev/null
+++ b/test/fuzz/fuzz-ndisc-rs/encrypted-dns
Binary files differ
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 687fda7b3d..b193f86da9 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,54 @@ 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 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}'
@@ -7253,6 +7304,56 @@ 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)
+ 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())
+
+ 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}")
+ 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)}',
+ 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):
def check(self, ipv4, ipv6):
os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True)