summaryrefslogtreecommitdiffstats
path: root/src/libsystemd-network
diff options
context:
space:
mode:
authorRonan Pigott <ronan@rjp.ie>2024-01-16 08:01:46 +0100
committerRonan Pigott <ronan@rjp.ie>2024-09-14 07:57:51 +0200
commit25c33e350042b2678bcc9543e1972b84b32731e2 (patch)
treeeb2882bae8928e8a24cdc62009f8257396e6911f /src/libsystemd-network
parentnetwork: Introduce sd_dns_resolver (diff)
downloadsystemd-25c33e350042b2678bcc9543e1972b84b32731e2.tar.xz
systemd-25c33e350042b2678bcc9543e1972b84b32731e2.zip
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.
Diffstat (limited to 'src/libsystemd-network')
-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/sd-dhcp-lease.c150
3 files changed, 154 insertions, 0 deletions
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/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c
index 37f4b3b2c9..8c46f8f770 100644
--- a/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/libsystemd-network/sd-dhcp-lease.c
@@ -11,6 +11,7 @@
#include <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)