From 25c33e350042b2678bcc9543e1972b84b32731e2 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Tue, 16 Jan 2024 00:01:46 -0700 Subject: [PATCH] network: parse RFC9463 DHCPv4 DNR option This option is another way for DHCP servers to indicate preferred DNS servers for the network, but includes more detailed info like the server name, transport (DoT/DoH/DoQ etc.), and port. Allow our DHCPv4 client to parse this option. --- src/libsystemd-network/dhcp-lease-internal.h | 3 + src/libsystemd-network/dhcp-option.h | 1 + src/libsystemd-network/sd-dhcp-lease.c | 150 +++++++++++++++++++ src/systemd/sd-dhcp-lease.h | 2 + src/systemd/sd-dhcp-protocol.h | 3 +- 5 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index b7bc142ef78..394c71bdf0f 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 aaa8f847b1a..047c8f34155 100644 --- a/src/libsystemd-network/dhcp-option.h +++ b/src/libsystemd-network/dhcp-option.h @@ -4,6 +4,7 @@ #include #include "sd-dhcp-option.h" +#include "dns-resolver-internal.h" #include "dhcp-protocol.h" #include "hash-funcs.h" diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 37f4b3b2c94..8c46f8f770b 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -11,6 +11,7 @@ #include #include "sd-dhcp-lease.h" +#include "dns-resolver-internal.h" #include "alloc-util.h" #include "dhcp-lease-internal.h" @@ -26,6 +27,7 @@ #include "network-common.h" #include "network-internal.h" #include "parse-util.h" +#include "sort-util.h" #include "stdio-util.h" #include "string-util.h" #include "strv.h" @@ -229,6 +231,17 @@ int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **ret) { return 0; } +int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers) { + assert_return(lease, -EINVAL); + assert_return(ret_resolvers, -EINVAL); + + if (!lease->dnr) + return -ENODATA; + + *ret_resolvers = lease->dnr; + return lease->n_dnr; +} + int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); @@ -418,6 +431,7 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++) free(lease->servers[i].addr); + dns_resolver_done_many(lease->dnr, lease->n_dnr); free(lease->static_routes); free(lease->classless_routes); free(lease->vendor_specific); @@ -559,6 +573,133 @@ static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_a return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret); } +static int lease_parse_dns_name(const uint8_t *optval, size_t optlen, char **ret) { + _cleanup_free_ char *name = NULL; + int r; + + assert(optval); + assert(ret); + + r = dns_name_from_wire_format(&optval, &optlen, &name); + if (r < 0) + return r; + if (r == 0 || optlen != 0) + return -EBADMSG; + + *ret = TAKE_PTR(name); + return r; +} + +static int lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver **ret_dnr, size_t *ret_n_dnr) { + int r; + sd_dns_resolver *res_list = NULL; + size_t n_resolvers = 0; + CLEANUP_ARRAY(res_list, n_resolvers, dns_resolver_done_many); + + assert(option || len == 0); + assert(ret_dnr); + + _cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {}; + + size_t offset = 0; + while (offset < len) { + /* Instance Data length */ + if (offset + 2 > len) + return -EBADMSG; + size_t ilen = unaligned_read_be16(option + offset); + if (offset + ilen + 2 > len) + return -EBADMSG; + offset += 2; + size_t iend = offset + ilen; + + /* priority */ + if (offset + 2 > len) + return -EBADMSG; + res.priority = unaligned_read_be16(option + offset); + offset += 2; + + /* Authenticated Domain Name */ + if (offset + 1 > len) + return -EBADMSG; + ilen = option[offset++]; + if (offset + ilen > iend) + return -EBADMSG; + + r = lease_parse_dns_name(option + offset, ilen, &res.auth_name); + if (r < 0) + return r; + if (dns_name_is_root(res.auth_name)) + return -EBADMSG; + offset += ilen; + + /* RFC9463 § 3.1.6: In ADN-only mode, server omits everything after the ADN. + * We don't support these, but they are not invalid. */ + if (offset == iend) { + log_debug("Received ADN-only DNRv4 option, ignoring."); + sd_dns_resolver_done(&res); + continue; + } + + /* IPv4 addrs */ + if (offset + 1 > len) + return -EBADMSG; + ilen = option[offset++]; + if (offset + ilen > iend) + return -EBADMSG; + + size_t n_addrs; + _cleanup_free_ struct in_addr *addrs = NULL; + r = lease_parse_in_addrs(option + offset, ilen, &addrs, &n_addrs); + if (r < 0) + return r; + offset += ilen; + + /* RFC9463 § 3.1.8: option MUST include at least one valid IP addr */ + if (!n_addrs) + return -EBADMSG; + + res.addrs = new(union in_addr_union, n_addrs); + if (!res.addrs) + return -ENOMEM; + for (size_t i = 0; i < n_addrs; i++) { + union in_addr_union addr = {.in = addrs[i]}; + /* RFC9463 § 5.2 client MUST discard multicast and host loopback addresses */ + if (in_addr_is_multicast(AF_INET, &addr) || + in_addr_is_localhost(AF_INET, &addr)) + return -EBADMSG; + res.addrs[i] = addr; + } + res.n_addrs = n_addrs; + res.family = AF_INET; + + /* service params */ + r = dnr_parse_svc_params(option + offset, iend-offset, &res); + if (r < 0) + return r; + if (r == 0) { + /* We can't use this record, but it was not invalid. */ + log_debug("Received DNRv4 option with unsupported SvcParams, ignoring."); + sd_dns_resolver_done(&res); + continue; + } + offset = iend; + + /* Append the latest resolver */ + if (!GREEDY_REALLOC0(res_list, n_resolvers+1)) + return -ENOMEM; + + res_list[n_resolvers++] = TAKE_STRUCT(res); + } + + typesafe_qsort(*ret_dnr, *ret_n_dnr, dns_resolver_prio_compare); + + dns_resolver_done_many(*ret_dnr, *ret_n_dnr); + *ret_dnr = TAKE_PTR(res_list); + *ret_n_dnr = n_resolvers; + + return n_resolvers; +} + static int lease_parse_static_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) { int r; @@ -879,6 +1020,15 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void break; } + case SD_DHCP_OPTION_V4_DNR: + r = lease_parse_dnr(option, len, &lease->dnr, &lease->n_dnr); + if (r < 0) { + log_debug_errno(r, "Failed to parse network-designated resolvers, ignoring: %m"); + return 0; + } + + break; + case SD_DHCP_OPTION_VENDOR_SPECIFIC: if (len <= 0) diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index eb5970e405a..2265b8b9aad 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 d8b7537da29..c25498502da 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 */ -- 2.47.3