network: parse RFC9463 DHCPv4 DNR option
authorRonan Pigott <ronan@rjp.ie>
Tue, 16 Jan 2024 07:01:46 +0000 (00:01 -0700)
committerRonan Pigott <ronan@rjp.ie>
Sat, 14 Sep 2024 05:57:51 +0000 (22:57 -0700)
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
src/libsystemd-network/dhcp-option.h
src/libsystemd-network/sd-dhcp-lease.c
src/systemd/sd-dhcp-lease.h
src/systemd/sd-dhcp-protocol.h

index b7bc142ef78f3652f77ff5f77da5bb7f52e7cfc8..394c71bdf0fe60db7a985d8ecdac832a405589fe 100644 (file)
@@ -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;
index aaa8f847b1a0036153010cabb01f8e694e860e17..047c8f34155a317e4c2e79ad906a2dc53185cb72 100644 (file)
@@ -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"
index 37f4b3b2c94f75747cb45c4466e566ecee14ff26..8c46f8f770bdad2a5184deb8cb0e4a0896ab0e02 100644 (file)
@@ -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)
index eb5970e405a8868cecbd053026511272c7924539..2265b8b9aad861a811707fe141d3f0072076a2f5 100644 (file)
@@ -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);
index d8b7537da29c53105ef9276f3b915bb1e5dfa8ce..c25498502dac9ec354ab196b0b159840edab7ee2 100644 (file)
@@ -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 */