1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-14 04:58:28 +03:00

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.
This commit is contained in:
Ronan Pigott 2024-01-16 00:01:46 -07:00
parent 1e2ead52e1
commit 25c33e3500
5 changed files with 158 additions and 1 deletions

View 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;

View 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"

View 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)

View 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);

View 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 */