dhcp-message: introduce several more functions to parse/append DHCP options (#42063)

This commit is contained in:
Daan De Meyer
2026-05-13 11:51:18 +02:00
committed by GitHub
11 changed files with 1311 additions and 42 deletions
@@ -12,12 +12,6 @@
#include "sd-forward.h"
#include "list.h"
struct sd_dhcp_route {
struct in_addr dst_addr;
struct in_addr gw_addr;
unsigned char dst_prefixlen;
};
struct sd_dhcp_raw_option {
LIST_FIELDS(struct sd_dhcp_raw_option, options);
+748 -4
View File
@@ -6,10 +6,14 @@
#include "dhcp-client-id-internal.h"
#include "dhcp-message.h"
#include "dhcp-protocol.h"
#include "dhcp-route.h"
#include "dns-def.h"
#include "dns-domain.h"
#include "dns-resolver-internal.h"
#include "errno-util.h"
#include "ether-addr-util.h"
#include "hostname-util.h"
#include "in-addr-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "ip-util.h"
@@ -17,6 +21,7 @@
#include "set.h"
#include "sort-util.h"
#include "string-util.h"
#include "unaligned.h"
static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) {
if (!message)
@@ -190,6 +195,24 @@ int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code,
if (size_multiply_overflow(sizeof(struct in_addr), n_addr))
return -ENOBUFS;
if (code == SD_DHCP_OPTION_SIP_SERVER) {
if (dhcp_message_has_option(message, SD_DHCP_OPTION_SIP_SERVER))
return -EEXIST;
size_t len = size_add(1, sizeof(struct in_addr) * n_addr);
if (len == SIZE_MAX)
return -ENOBUFS;
_cleanup_free_ uint8_t *buf = new(uint8_t, len);
if (!buf)
return -ENOMEM;
buf[0] = 1; /* 'enc' field, 0: domains, 1: addresses */
memcpy(buf + 1, addr, sizeof(struct in_addr) * n_addr);
return dhcp_message_append_option(message, code, len, buf);
}
return dhcp_message_append_option(message, code, sizeof(struct in_addr) * n_addr, addr);
}
@@ -208,6 +231,138 @@ int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, co
return dhcp_message_append_option(message, code, strlen(data), data);
}
static int dhcp_message_append_option_static_routes(sd_dhcp_message *message, size_t n_routes, const sd_dhcp_route *routes) {
int r;
assert(message);
assert(routes || n_routes == 0);
if (n_routes == 0)
return 0;
if (size_multiply_overflow(2 * sizeof(struct in_addr), n_routes))
return -ENOBUFS;
_cleanup_free_ struct in_addr *buf = new(struct in_addr, 2 * n_routes);
if (!buf)
return -ENOMEM;
size_t count = 0;
FOREACH_ARRAY(route, routes, n_routes) {
uint8_t prefixlen;
r = in4_addr_default_prefixlen(&route->dst_addr, &prefixlen);
if (r < 0)
return r;
if (prefixlen != route->dst_prefixlen)
return -EINVAL;
struct in_addr dst = route->dst_addr;
(void) in4_addr_mask(&dst, prefixlen);
buf[count++] = dst;
buf[count++] = route->gw_addr;
}
assert(count == 2 * n_routes);
return dhcp_message_append_option_addresses(message, SD_DHCP_OPTION_STATIC_ROUTE, 2 * n_routes, buf);
}
static int dhcp_message_append_option_classless_static_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes) {
assert(message);
assert(routes || n_routes == 0);
assert(IN_SET(code,
SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,
SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE));
if (n_routes == 0)
return 0;
if (size_multiply_overflow(1 + 2 * sizeof(struct in_addr), n_routes))
return -ENOBUFS;
_cleanup_free_ uint8_t *buf = new(uint8_t, (1 + 2 * sizeof(struct in_addr)) * n_routes);
if (!buf)
return -ENOMEM;
uint8_t *p = buf;
FOREACH_ARRAY(route, routes, n_routes) {
if (route->dst_prefixlen > sizeof(struct in_addr) * 8)
return -EINVAL;
*p++ = route->dst_prefixlen;
struct in_addr dst = route->dst_addr;
(void) in4_addr_mask(&dst, route->dst_prefixlen);
p = mempcpy(p, &dst, DIV_ROUND_UP(route->dst_prefixlen, 8));
p = mempcpy(p, &route->gw_addr, sizeof(struct in_addr));
}
return dhcp_message_append_option(message, code, p - buf, buf);
}
int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes) {
switch (code) {
case SD_DHCP_OPTION_STATIC_ROUTE:
return dhcp_message_append_option_static_routes(message, n_routes, routes);
case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE:
return dhcp_message_append_option_classless_static_routes(message, code, n_routes, routes);
default:
return -EINVAL;
}
}
int dhcp_message_append_option_6rd(
sd_dhcp_message *message,
uint8_t ipv4masklen,
uint8_t prefixlen,
const struct in6_addr *prefix,
size_t n_br_addresses,
const struct in_addr *br_addresses) {
assert(message);
assert(prefix);
assert(n_br_addresses == 0 || br_addresses);
/* See RFC 5969 Section 7.1.1 and dhcp_message_get_option_6rd() below. */
if (dhcp_message_has_option(message, SD_DHCP_OPTION_6RD))
return -EEXIST;
if (ipv4masklen > 32)
return -EINVAL;
if (32 - ipv4masklen + prefixlen > 128)
return -EINVAL;
if (n_br_addresses == 0)
return -EINVAL;
if (size_multiply_overflow(sizeof(struct in_addr), n_br_addresses))
return -ENOBUFS;
size_t buflen = size_add(2 + sizeof(struct in6_addr), sizeof(struct in_addr) * n_br_addresses);
if (buflen == SIZE_MAX)
return -ENOBUFS;
_cleanup_free_ uint8_t *buf = new(uint8_t, buflen);
if (!buf)
return -ENOMEM;
uint8_t *p = buf;
*p++ = ipv4masklen;
*p++ = prefixlen;
struct in6_addr masked = *prefix;
(void) in6_addr_mask(&masked, prefixlen);
p = mempcpy(p, &masked, sizeof(struct in6_addr));
memcpy(p, br_addresses, n_br_addresses * sizeof(struct in_addr));
return dhcp_message_append_option(message, SD_DHCP_OPTION_6RD, buflen, buf);
}
int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id) {
assert(message);
assert(id);
@@ -306,6 +461,46 @@ int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags,
return dhcp_message_append_option_fqdn(message, flags, is_client, hostname);
}
int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, const TLV *tlv) {
int r;
assert(message);
if (tlv_isempty(tlv))
return 0;
if (dhcp_message_has_option(message, code))
return -EEXIST;
_cleanup_(iovec_done) struct iovec iov = {};
r = tlv_build(tlv, &iov);
if (r < 0)
return r;
return dhcp_message_append_option(message, code, iov.iov_len, iov.iov_base);
}
int dhcp_message_append_option_length_prefixed_data(
sd_dhcp_message *message,
uint8_t code,
size_t length_size,
const struct iovec_wrapper *iovw) {
int r;
assert(message);
_cleanup_(iovec_done) struct iovec iov = {};
r = iovw_merge(iovw, length_size, &iov);
if (r < 0)
return r;
if (!iovec_is_set(&iov))
return 0;
return dhcp_message_append_option(message, code, iov.iov_len, iov.iov_base);
}
int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) {
int r;
@@ -382,11 +577,22 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si
assert(message);
assert(ret_n_addr || !ret_addr);
_cleanup_(iovec_done) struct iovec iov = {};
r = dhcp_message_get_option_alloc(message, code, &iov);
_cleanup_(iovec_done) struct iovec iov_free = {};
r = dhcp_message_get_option_alloc(message, code, &iov_free);
if (r < 0)
return r;
struct iovec iov = iov_free;
if (code == SD_DHCP_OPTION_SIP_SERVER) {
if (!iovec_is_set(&iov))
return -EBADMSG;
if (*(uint8_t*) iov.iov_base != 1) /* 'enc' field, 0: domains, 1: addresses */
return -ENODATA;
iovec_inc(&iov, 1);
}
if (iov.iov_len % sizeof(struct in_addr) != 0)
return -EBADMSG;
@@ -394,8 +600,17 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si
if (n == 0)
return -ENODATA;
if (ret_addr)
*ret_addr = (struct in_addr*) TAKE_PTR(iov.iov_base);
if (ret_addr) {
if (code == SD_DHCP_OPTION_SIP_SERVER) {
struct in_addr *addr = newdup(struct in_addr, iov.iov_base, n);
if (!addr)
return -ENOMEM;
*ret_addr = addr;
} else {
*ret_addr = iov.iov_base;
TAKE_STRUCT(iov_free);
}
}
if (ret_n_addr)
*ret_n_addr = n;
return 0;
@@ -433,6 +648,210 @@ int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char
return 0;
}
static int dhcp_message_get_option_static_routes(sd_dhcp_message *message, size_t *ret_n_routes, sd_dhcp_route **ret_routes) {
int r;
assert(message);
size_t n;
_cleanup_free_ struct in_addr *addrs = NULL;
r = dhcp_message_get_option_addresses(message, SD_DHCP_OPTION_STATIC_ROUTE, &n, &addrs);
if (r < 0)
return r;
if (n % 2 != 0)
return -EBADMSG;
_cleanup_free_ sd_dhcp_route *routes = NULL;
size_t n_routes = 0;
for (size_t i = 0; i < n; i += 2) {
struct in_addr dst = addrs[i];
uint8_t prefixlen;
if (in4_addr_default_prefixlen(&dst, &prefixlen) < 0)
continue;
(void) in4_addr_mask(&dst, prefixlen);
/* RFC 2132 section 5.8:
* The default route (0.0.0.0) is an illegal destination for a static route.*/
if (in4_addr_is_null(&dst))
continue;
if (!ret_routes) {
n_routes++;
continue;
}
if (!GREEDY_REALLOC(routes, n_routes + 1))
return -ENOMEM;
routes[n_routes++] = (struct sd_dhcp_route) {
.dst_addr = dst,
.gw_addr = addrs[i + 1],
.dst_prefixlen = prefixlen,
};
}
if (n_routes == 0)
return -ENODATA;
if (ret_routes)
*ret_routes = TAKE_PTR(routes);
if (ret_n_routes)
*ret_n_routes = n_routes;
return 0;
}
static int dhcp_message_get_option_classless_static_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes) {
int r;
assert(message);
assert(IN_SET(code,
SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,
SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE));
_cleanup_(iovec_done) struct iovec iov = {};
r = dhcp_message_get_option_alloc(message, code, &iov);
if (r < 0)
return r;
_cleanup_free_ sd_dhcp_route *routes = NULL;
size_t n_routes = 0;
for (struct iovec i = iov; iovec_is_set(&i);) {
uint8_t prefixlen = *(uint8_t*) i.iov_base;
iovec_inc(&i, 1);
if (prefixlen > 32)
return -EBADMSG;
size_t n = DIV_ROUND_UP(prefixlen, 8);
if (n > i.iov_len)
return -EBADMSG;
struct in_addr dst = {};
memcpy_safe(&dst, i.iov_base, n);
(void) in4_addr_mask(&dst, prefixlen);
iovec_inc(&i, n);
if (i.iov_len < sizeof(struct in_addr))
return -EBADMSG;
struct in_addr gw;
memcpy(&gw, i.iov_base, sizeof(struct in_addr));
iovec_inc(&i, sizeof(struct in_addr));
if (!ret_routes) {
n_routes++;
continue;
}
if (!GREEDY_REALLOC(routes, n_routes + 1))
return -ENOMEM;
routes[n_routes++] = (struct sd_dhcp_route) {
.dst_addr = dst,
.gw_addr = gw,
.dst_prefixlen = prefixlen,
};
}
if (n_routes == 0)
return -ENODATA;
if (ret_routes)
*ret_routes = TAKE_PTR(routes);
if (ret_n_routes)
*ret_n_routes = n_routes;
return 0;
}
int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes) {
switch (code) {
case SD_DHCP_OPTION_STATIC_ROUTE:
return dhcp_message_get_option_static_routes(message, ret_n_routes, ret_routes);
case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE:
return dhcp_message_get_option_classless_static_routes(message, code, ret_n_routes, ret_routes);
default:
return -EINVAL;
}
}
int dhcp_message_get_option_6rd(
sd_dhcp_message *message,
uint8_t *ret_ipv4masklen,
uint8_t *ret_prefixlen,
struct in6_addr *ret_prefix,
size_t *ret_n_br_addresses,
struct in_addr **ret_br_addresses) {
int r;
assert(message);
assert(ret_n_br_addresses || !ret_br_addresses);
/* See RFC 5969 Section 7.1.1 */
_cleanup_(iovec_done) struct iovec iov = {};
r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_6RD, &iov);
if (r < 0)
return r;
/* option-length: The length of the DHCP option in octets (22 octets with one BR IPv4 address). */
if (iov.iov_len < 2 + sizeof(struct in6_addr) + sizeof(struct in_addr) ||
(iov.iov_len - 2 - sizeof(struct in6_addr)) % sizeof(struct in_addr) != 0)
return -EBADMSG;
size_t n_br_addresses = (iov.iov_len - 2 - sizeof(struct in6_addr)) / sizeof(struct in_addr);
assert(n_br_addresses > 0); /* We have already checked that in the above. */
const uint8_t *p = iov.iov_base;
/* IPv4MaskLen: The number of high-order bits that are identical across all CE IPv4 addresses
* within a given 6rd domain. This may be any value between 0 and 32. Any value
* greater than 32 is invalid. */
uint8_t ipv4masklen = *p++;
if (ipv4masklen > 32)
return -EBADMSG;
/* 6rdPrefixLen: The IPv6 prefix length of the SP's 6rd IPv6 prefix in number of bits. For the
* purpose of bounds checking by DHCP option processing, the sum of
* (32 - IPv4MaskLen) + 6rdPrefixLen MUST be less than or equal to 128. */
uint8_t prefixlen = *p++;
if (32 - ipv4masklen + prefixlen > 128)
return -EBADMSG;
/* 6rdPrefix: The service provider's 6rd IPv6 prefix represented as a 16-octet IPv6 address.
* The bits in the prefix after the 6rdPrefixlen number of bits are reserved and
* MUST be initialized to zero by the sender and ignored by the receiver. */
struct in6_addr prefix;
memcpy(&prefix, p, sizeof(struct in6_addr));
(void) in6_addr_mask(&prefix, prefixlen);
p += sizeof(struct in6_addr);
/* 6rdBRIPv4Address: One or more IPv4 addresses of the 6rd Border Relays for a given 6rd domain. */
if (ret_br_addresses) {
struct in_addr *br_addresses = newdup(struct in_addr, p, n_br_addresses);
if (!br_addresses)
return -ENOMEM;
*ret_br_addresses = br_addresses;
}
if (ret_ipv4masklen)
*ret_ipv4masklen = ipv4masklen;
if (ret_prefixlen)
*ret_prefixlen = prefixlen;
if (ret_prefix)
*ret_prefix = prefix;
if (ret_n_br_addresses)
*ret_n_br_addresses = n_br_addresses;
return 0;
}
int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret) {
int r;
@@ -579,6 +998,331 @@ int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret) {
return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret);
}
int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret) {
int r;
assert(message);
/* This is mostly for SD_DHCP_OPTION_DOMAIN_SEARCH and SD_DHCP_OPTION_SIP_SERVER. */
_cleanup_(iovec_done) struct iovec iov = {};
r = dhcp_message_get_option_alloc(message, code, &iov);
if (r < 0)
return r;
const uint8_t *buf = iov.iov_base;
size_t len = iov.iov_len;
if (code == SD_DHCP_OPTION_SIP_SERVER) {
if (len == 0)
return -EBADMSG;
if (buf[0] != 0) /* 'enc' field, 0: domains, 1: addresses */
return -ENODATA;
len--;
buf++;
}
_cleanup_strv_free_ char **names = NULL;
size_t n_names = 0;
_cleanup_free_ char *name = NULL;
size_t n = 0;
for (size_t pos = 0, jump_barrier = 0, next_chunk = 0; pos < len;) {
uint8_t c = buf[pos++];
if (c == 0) {
/* End of name */
if (!string_is_safe(name, /* flags= */ 0))
return -EBADMSG;
_cleanup_free_ char *normalized = NULL;
r = normalize_dns_name(name, &normalized);
if (r < 0)
return r;
r = strv_consume_with_size(&names, &n_names, TAKE_PTR(normalized));
if (r < 0)
return r;
if (next_chunk != 0)
pos = next_chunk;
next_chunk = 0;
jump_barrier = pos;
name = mfree(name);
n = 0;
} else if (c <= 63) {
/* Literal label */
const char *label = (const char*) (buf + pos);
pos += c;
if (pos >= len)
return -EBADMSG;
if (!GREEDY_REALLOC(name, n + 1 + DNS_LABEL_ESCAPED_MAX))
return -ENOMEM;
if (n != 0)
name[n++] = '.';
r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX);
if (r < 0)
return r;
n += r;
} else if (FLAGS_SET(c, 0xc0)) {
/* Pointer */
if (pos >= len) /* pointer is 2 bytes, hence we need to read at least one more byte. */
return -EBADMSG;
/* Save the current location so we don't end up re-parsing what's parsed so far. */
if (next_chunk == 0)
next_chunk = pos + 1;
pos = ((size_t) (c & ~0xc0) << 8) | ((size_t) buf[pos]);
/* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */
if (pos >= jump_barrier)
return -EBADMSG;
jump_barrier = pos;
} else
return -EBADMSG;
}
if (!isempty(name)) /* trailing garbage?? Should not happen, but for safety. */
return -EBADMSG;
if (strv_isempty(names))
return -EBADMSG;
if (ret)
*ret = TAKE_PTR(names);
return 0;
}
int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret) {
int r;
assert(message);
assert(!FLAGS_SET(flags, TLV_TEMPORARY));
_cleanup_(iovec_done) struct iovec iov = {};
r = dhcp_message_get_option_alloc(message, code, &iov);
if (r < 0)
return r;
_cleanup_(tlv_unrefp) TLV *tlv = tlv_new(flags);
if (!tlv)
return -ENOMEM;
r = tlv_parse(tlv, &iov);
if (r < 0)
return r;
if (tlv_isempty(tlv))
return -ENODATA;
if (ret)
*ret = TAKE_PTR(tlv);
return 0;
}
int dhcp_message_get_option_length_prefixed_data(
sd_dhcp_message *message,
uint8_t code,
size_t length_size,
struct iovec_wrapper *ret) {
int r;
assert(message);
_cleanup_(iovec_done) struct iovec iov = {};
r = dhcp_message_get_option_alloc(message, code, &iov);
if (r < 0)
return r;
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
r = iovec_split(&iov, length_size, &iovw);
if (r < 0)
return r;
if (iovw_isempty(&iovw))
return -ENODATA;
if (ret)
*ret = TAKE_STRUCT(iovw);
return 0;
}
static int parse_dnr_one(const struct iovec *iov, sd_dns_resolver *ret) {
int r;
assert(iovec_is_set(iov));
assert(ret);
_cleanup_(sd_dns_resolver_done) sd_dns_resolver resolver = {};
struct iovec i = *iov;
/* service priority */
if (i.iov_len < sizeof(be16_t))
return -EBADMSG;
resolver.priority = unaligned_read_be16(i.iov_base);
iovec_inc(&i, sizeof(be16_t));
/* RFC 9460 section 2.4.1:
* When SvcPriority is 0, the SVCB record is in AliasMode.
*
* We do not support the alias mode. But the entry itself is not invalid. */
if (resolver.priority == 0) {
*ret = (sd_dns_resolver) {};
return 0;
}
/* authentication domain name */
if (!iovec_is_set(&i))
return -EBADMSG;
size_t name_len = *(uint8_t*) i.iov_base;
iovec_inc(&i, 1);
if (i.iov_len < name_len)
return -EBADMSG;
const uint8_t *name_buf = i.iov_base;
iovec_inc(&i, name_len);
r = dns_name_from_wire_format(&name_buf, &name_len, &resolver.auth_name);
if (r < 0)
return r;
if (r == 0 || name_len != 0)
return -EBADMSG;
r = dns_name_is_valid_ldh(resolver.auth_name);
if (r < 0)
return r;
if (r == 0)
return -EBADMSG;
if (dns_name_is_root(resolver.auth_name))
return -EBADMSG;
/* RFC9463 section 3.1.6: In ADN-only mode, server omits everything after the ADN.
*
* We don't support these, but they are not invalid. */
if (!iovec_is_set(&i)) {
*ret = (sd_dns_resolver) {};
return 0;
}
/* IPv4 addresses */
size_t n = *(uint8_t*) i.iov_base;
iovec_inc(&i, 1);
if (n % sizeof(struct in_addr) != 0)
return -EBADMSG;
n /= sizeof(struct in_addr);
/* RFC9463 section 3.1.8: option MUST include at least one valid IP addr */
if (n == 0)
return -EBADMSG;
resolver.family = AF_INET;
resolver.n_addrs = n;
resolver.addrs = new(union in_addr_union, n);
if (!resolver.addrs)
return -ENOMEM;
for (size_t j = 0; j < n; j++) {
if (i.iov_len < sizeof(struct in_addr))
return -EBADMSG;
struct in_addr a;
memcpy(&a, i.iov_base, sizeof(struct in_addr));
iovec_inc(&i, sizeof(struct in_addr));
/* RFC9463 section 5.2: client MUST discard multicast and host loopback addresses */
if (in4_addr_is_multicast(&a) || in4_addr_is_localhost(&a))
return -EBADMSG;
resolver.addrs[j] = (union in_addr_union) { .in = a };
}
/* service params */
r = dnr_parse_svc_params(i.iov_base, i.iov_len, &resolver);
if (r < 0)
return r;
if (r == 0) {
/* We can't use this record, but it is not invalid. */
*ret = (sd_dns_resolver) {};
return 0;
}
*ret = TAKE_STRUCT(resolver);
return 1;
}
int dhcp_message_get_option_dnr(sd_dhcp_message *message, size_t *ret_n_resolvers, sd_dns_resolver **ret_resolvers) {
int r;
assert(message);
assert(ret_n_resolvers || !ret_resolvers);
/* See RFC 9463 section 5.1 */
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
r = dhcp_message_get_option_length_prefixed_data(message, SD_DHCP_OPTION_V4_DNR, /* length_size= */ 2, &iovw);
if (r < 0)
return r;
sd_dns_resolver *resolvers = NULL;
size_t n_resolvers = 0;
CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array);
FOREACH_ARRAY(i, iovw.iovec, iovw.count) {
_cleanup_(sd_dns_resolver_done) sd_dns_resolver dnr = {};
r = parse_dnr_one(i, &dnr);
if (r < 0)
return r;
if (r == 0)
continue;
if (!ret_resolvers) {
n_resolvers++;
continue;
}
if (!GREEDY_REALLOC(resolvers, n_resolvers + 1))
return -ENOMEM;
resolvers[n_resolvers++] = TAKE_STRUCT(dnr);
}
if (n_resolvers == 0) /* no supported resolver */
return -ENODATA;
if (ret_resolvers) {
/* Sort the resolvers with their priorities. */
typesafe_qsort(resolvers, n_resolvers, dns_resolver_prio_compare);
*ret_resolvers = TAKE_PTR(resolvers);
}
if (ret_n_resolvers)
*ret_n_resolvers = n_resolvers;
return 0;
}
static int dhcp_message_verify_header(
const struct iovec *iov,
uint8_t op,
+22
View File
@@ -39,9 +39,19 @@ int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_
int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr);
int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr);
int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data);
int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes);
int dhcp_message_append_option_6rd(
sd_dhcp_message *message,
uint8_t ipv4masklen,
uint8_t prefixlen,
const struct in6_addr *prefix,
size_t n_br_addresses,
const struct in_addr *br_addresses);
int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id);
int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl);
int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname);
int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, const TLV *tlv);
int dhcp_message_append_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, const struct iovec_wrapper *iovw);
int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret);
int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret);
@@ -53,11 +63,23 @@ int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max
int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret);
int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr);
int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret);
int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes);
int dhcp_message_get_option_6rd(
sd_dhcp_message *message,
uint8_t *ret_ipv4masklen,
uint8_t *ret_prefixlen,
struct in6_addr *ret_prefix,
size_t *ret_n_br_addresses,
struct in_addr **ret_br_addresses);
int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret);
int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret);
int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn);
int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret);
int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret);
int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret);
int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret);
int dhcp_message_get_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, struct iovec_wrapper *ret);
int dhcp_message_get_option_dnr(sd_dhcp_message *message, size_t *ret_n_resolvers, sd_dns_resolver **ret_resolvers);
int dhcp_message_parse(
const struct iovec *iov,
+29
View File
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-dhcp-lease.h"
#include "dhcp-route.h" /* IWYU pragma: keep */
int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *ret) {
assert_return(route, -EINVAL);
assert_return(ret, -EINVAL);
*ret = route->dst_addr;
return 0;
}
int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *ret) {
assert_return(route, -EINVAL);
assert_return(ret, -EINVAL);
*ret = route->dst_prefixlen;
return 0;
}
int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *ret) {
assert_return(route, -EINVAL);
assert_return(ret, -EINVAL);
*ret = route->gw_addr;
return 0;
}
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-forward.h"
#include "in-addr-util.h" /* IWYU pragma: keep */
struct sd_dhcp_route {
struct in_addr dst_addr;
struct in_addr gw_addr;
uint8_t dst_prefixlen;
};
+1
View File
@@ -8,6 +8,7 @@ libsystemd_network_sources = files(
'dhcp-option.c',
'dhcp-packet.c',
'dhcp-protocol.c',
'dhcp-route.c',
'dhcp6-network.c',
'dhcp6-option.c',
'dhcp6-protocol.c',
+3 -1
View File
@@ -3,8 +3,10 @@
#include <arpa/inet.h>
#include <stdio.h>
#include "sd-dhcp-lease.h"
#include "alloc-util.h"
#include "dhcp-lease-internal.h"
#include "dhcp-route.h"
#include "dns-resolver-internal.h"
#include "extract-word.h"
#include "hexdecoct.h"
+1 -24
View File
@@ -11,6 +11,7 @@
#include "alloc-util.h"
#include "dhcp-lease-internal.h"
#include "dhcp-option.h"
#include "dhcp-route.h"
#include "dns-def.h"
#include "dns-domain.h"
#include "dns-resolver-internal.h"
@@ -1802,27 +1803,3 @@ int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret) {
*ret = lease->timezone;
return 0;
}
int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) {
assert_return(route, -EINVAL);
assert_return(destination, -EINVAL);
*destination = route->dst_addr;
return 0;
}
int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) {
assert_return(route, -EINVAL);
assert_return(length, -EINVAL);
*length = route->dst_prefixlen;
return 0;
}
int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) {
assert_return(route, -EINVAL);
assert_return(gateway, -EINVAL);
*gateway = route->gw_addr;
return 0;
}
+491 -3
View File
@@ -6,6 +6,9 @@
#include "dhcp-client-id-internal.h"
#include "dhcp-message.h"
#include "dhcp-protocol.h"
#include "dhcp-route.h"
#include "dns-packet.h"
#include "dns-resolver-internal.h"
#include "ether-addr-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
@@ -68,7 +71,8 @@ static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) {
static void verify_addresses(
sd_dhcp_message *m,
size_t n_ntp, const struct in_addr *ntp) {
size_t n_ntp, const struct in_addr *ntp,
size_t n_sip, const struct in_addr *sip) {
struct in_addr a;
ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_NTP_SERVER, &a.s_addr));
@@ -81,6 +85,14 @@ static void verify_addresses(
ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, &n, &addrs));
ASSERT_EQ(n, n_ntp);
ASSERT_EQ(memcmp(addrs, ntp, sizeof(struct in_addr) * n), 0);
ASSERT_ERROR(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_SIP_SERVER, NULL), ENODATA);
ASSERT_ERROR(dhcp_message_get_option_address(m, SD_DHCP_OPTION_SIP_SERVER, NULL), ENODATA);
addrs = mfree(addrs);
ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, &n, &addrs));
ASSERT_EQ(n, n_sip);
ASSERT_EQ(memcmp(addrs, sip, sizeof(struct in_addr) * n), 0);
}
static void verify_string(sd_dhcp_message *m, const char *expected) {
@@ -96,6 +108,47 @@ static void verify_multiple_strings(sd_dhcp_message *m, char * const *expected)
ASSERT_STREQ(s, joined);
}
static void verify_routes(sd_dhcp_message *m, size_t n_expected, const sd_dhcp_route *expected) {
uint8_t code;
FOREACH_ARGUMENT(code,
SD_DHCP_OPTION_STATIC_ROUTE,
SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,
SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE) {
_cleanup_free_ sd_dhcp_route *routes = NULL;
size_t n;
ASSERT_OK(dhcp_message_get_option_routes(m, code, &n, &routes));
ASSERT_EQ(n, n_expected);
for (size_t i = 0; i < n; i++) {
ASSERT_EQ(routes[i].dst_addr.s_addr, expected[i].dst_addr.s_addr);
ASSERT_EQ(routes[i].gw_addr.s_addr, expected[i].gw_addr.s_addr);
ASSERT_EQ(routes[i].dst_prefixlen, expected[i].dst_prefixlen);
}
}
}
static void verify_6rd(
sd_dhcp_message *m,
uint8_t expected_ipv4masklen,
uint8_t expected_prefixlen,
const struct in6_addr *expected_prefix,
size_t expected_n_br_addresses,
const struct in_addr *expected_br_addresses) {
uint8_t ipv4masklen, prefixlen;
struct in6_addr prefix;
size_t n_br_addresses;
_cleanup_free_ struct in_addr *br_addresses = NULL;
ASSERT_OK(dhcp_message_get_option_6rd(m, NULL, NULL, NULL, NULL, NULL));
ASSERT_OK(dhcp_message_get_option_6rd(m, &ipv4masklen, &prefixlen, &prefix, &n_br_addresses, &br_addresses));
ASSERT_EQ(ipv4masklen, expected_ipv4masklen);
ASSERT_EQ(prefixlen, expected_prefixlen);
ASSERT_TRUE(in6_addr_equal(&prefix, expected_prefix));
ASSERT_EQ(n_br_addresses, expected_n_br_addresses);
ASSERT_EQ(memcmp(br_addresses, expected_br_addresses, sizeof(struct in_addr) * n_br_addresses), 0);
}
static void verify_client_id(sd_dhcp_message *m, const sd_dhcp_client_id *expected) {
sd_dhcp_client_id id = {};
ASSERT_OK(dhcp_message_get_option_client_id(m, &id));
@@ -114,6 +167,26 @@ static void verify_hostname(sd_dhcp_message *m, const char *expected) {
ASSERT_STREQ(s, expected);
}
static void verify_sub_tlv(sd_dhcp_message *m, TLV *expected) {
_cleanup_(tlv_unrefp) TLV *tlv = NULL;
ASSERT_OK(dhcp_message_get_option_sub_tlv(
m,
SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION,
TLV_DHCP4_SUBOPTION,
&tlv));
_cleanup_(iovec_done) struct iovec iov = {}, iov_expected = {};
ASSERT_OK(tlv_build(tlv, &iov));
ASSERT_OK(tlv_build(expected, &iov_expected));
ASSERT_TRUE(iovec_equal(&iov, &iov_expected));
}
static void verify_length_prefixed_data(sd_dhcp_message *m, const struct iovec_wrapper *expected) {
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
ASSERT_OK(dhcp_message_get_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &iovw));
ASSERT_TRUE(iovw_equal(&iovw, expected));
}
TEST(dhcp_message) {
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL;
@@ -140,6 +213,43 @@ TEST(dhcp_message) {
{ .s_addr = htobe32(0xC0000204) },
};
/* 192.0.2.17 - 20 */
struct in_addr sip[4] = {
{ .s_addr = htobe32(0xC0000211) },
{ .s_addr = htobe32(0xC0000212) },
{ .s_addr = htobe32(0xC0000213) },
{ .s_addr = htobe32(0xC0000214) },
};
struct sd_dhcp_route routes[3] = {
{ /* class A: 10.0.0.0/8 -> 192.0.2.33 */
.dst_addr = { .s_addr = htobe32(0x0A000000) },
.gw_addr = { .s_addr = htobe32(0xC0000221) },
.dst_prefixlen = 8,
},
{ /* class B: 172.16.0.0/16 -> 192.0.2.34 */
.dst_addr = { .s_addr = htobe32(0xAC100000) },
.gw_addr = { .s_addr = htobe32(0xC0000222) },
.dst_prefixlen = 16,
},
{ /* class C: 192.168.0.0/24 -> 192.0.2.35 */
.dst_addr = { .s_addr = htobe32(0xC0A80000) },
.gw_addr = { .s_addr = htobe32(0xC0000223) },
.dst_prefixlen = 24,
},
};
uint8_t sixrd_ipv4masklen = 24;
uint8_t sixrd_prefixlen = 64;
struct in6_addr sixrd_prefix = {
.s6_addr = { 0x20, 0x01, 0x0d, 0xb8, },
};
struct in_addr sixrd_br_addresses[3] = {
{ .s_addr = htobe32(0xC0000231) },
{ .s_addr = htobe32(0xC0000232) },
{ .s_addr = htobe32(0xC0000233) },
};
sd_dhcp_client_id id = {
.raw = { 1, 3, 3, 3, 3, 3, 3, },
.size = 7,
@@ -153,6 +263,23 @@ TEST(dhcp_message) {
const char *vendor_class = "hogehoge";
char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo");
_cleanup_(iovw_done_free) struct iovec_wrapper user_class = {}, user_class_1 = {}, user_class_2 = {};
FOREACH_STRING(s, "hoge", "foo", "bar") {
ASSERT_OK(iovw_extend(&user_class, s, strlen(s)));
ASSERT_OK(iovw_extend(&user_class_1, s, strlen(s)));
}
FOREACH_STRING(s, "aaa", "bbb", "ccc") {
ASSERT_OK(iovw_extend(&user_class, s, strlen(s)));
ASSERT_OK(iovw_extend(&user_class_2, s, strlen(s)));
}
_cleanup_(tlv_done) TLV vendor = TLV_INIT(TLV_DHCP4_SUBOPTION);
for (unsigned i = 0; i < 3; i++) {
uint8_t buf[255];
memset(buf, 42 + i, sizeof(buf));
ASSERT_OK(tlv_append(&vendor, i + 1, 255, buf));
}
ASSERT_OK(dhcp_message_init_header(
m,
BOOTREQUEST,
@@ -205,7 +332,10 @@ TEST(dhcp_message) {
/* multiple addresses */
ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_NTP_SERVER, &ntp[0]));
ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, ELEMENTSOF(ntp) - 1, ntp + 1));
verify_addresses(m, ELEMENTSOF(ntp), ntp);
ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, ELEMENTSOF(sip), sip));
ASSERT_ERROR(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, ELEMENTSOF(sip), sip), EEXIST);
ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, 0, NULL));
verify_addresses(m, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip);
/* string */
ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA);
@@ -220,6 +350,22 @@ TEST(dhcp_message) {
ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class));
verify_string(m, vendor_class);
/* routes */
ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_STATIC_ROUTE, ELEMENTSOF(routes), routes));
ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, ELEMENTSOF(routes), routes));
ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, ELEMENTSOF(routes), routes));
verify_routes(m, ELEMENTSOF(routes), routes);
/* 6rd */
ASSERT_ERROR(dhcp_message_append_option_6rd(m, 33, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses), EINVAL);
ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, 127, &sixrd_prefix, 1, sixrd_br_addresses), EINVAL);
ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 0, sixrd_br_addresses), EINVAL);
ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, SIZE_MAX, sixrd_br_addresses), ENOBUFS);
ASSERT_OK(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses));
ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses), EEXIST);
ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_6RD, ELEMENTSOF(sixrd_br_addresses) - 1, sixrd_br_addresses + 1));
verify_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, ELEMENTSOF(sixrd_br_addresses), sixrd_br_addresses);
/* client ID */
ASSERT_OK(dhcp_message_append_option_client_id(m, &id));
verify_client_id(m, &id);
@@ -243,6 +389,16 @@ TEST(dhcp_message) {
ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname));
verify_hostname(m, hostname);
/* vendor specific */
ASSERT_OK(dhcp_message_append_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, &vendor));
ASSERT_ERROR(dhcp_message_append_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, &vendor), EEXIST);
verify_sub_tlv(m, &vendor);
/* user class */
ASSERT_OK(dhcp_message_append_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &user_class_1));
ASSERT_OK(dhcp_message_append_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &user_class_2));
verify_length_prefixed_data(m, &user_class);
/* build and parse */
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
ASSERT_OK(dhcp_message_build(m, &iovw));
@@ -269,11 +425,15 @@ TEST(dhcp_message) {
verify_u16(m2, 512);
verify_sec(m2, lease_time);
verify_address(m2, &addr);
verify_addresses(m2, ELEMENTSOF(ntp), ntp);
verify_addresses(m2, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip);
verify_string(m2, vendor_class);
verify_routes(m2, ELEMENTSOF(routes), routes);
verify_6rd(m2, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, ELEMENTSOF(sixrd_br_addresses), sixrd_br_addresses);
verify_client_id(m2, &id);
verify_prl(m2, prl);
verify_hostname(m2, hostname);
verify_sub_tlv(m2, &vendor);
verify_length_prefixed_data(m2, &user_class);
/* build again, and verify the packet */
_cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {};
@@ -281,4 +441,332 @@ TEST(dhcp_message) {
ASSERT_TRUE(iovw_equal(&iovw, &iovw2));
}
static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) {
_cleanup_strv_free_ char **strv = NULL;
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL;
ASSERT_OK(dhcp_message_new(&m));
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data));
ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv));
ASSERT_TRUE(strv_equal(strv, expected));
dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH);
strv = strv_free(strv);
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len / 2, data));
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len - len / 2, data + len / 2));
ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv));
ASSERT_TRUE(strv_equal(strv, expected));
strv = strv_free(strv);
_cleanup_free_ uint8_t *sip = new(uint8_t, len + 1);
sip[0] = 0;
memcpy(sip + 1, data, len);
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip));
ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, &strv));
ASSERT_TRUE(strv_equal(strv, expected));
dhcp_message_remove_option(m, SD_DHCP_OPTION_SIP_SERVER);
strv = strv_free(strv);
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, (len + 1) / 2, sip));
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1 - (len + 1) / 2, sip + (len + 1) / 2));
ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, &strv));
ASSERT_TRUE(strv_equal(strv, expected));
}
static void test_domains_fail(size_t len, const uint8_t *data) {
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL;
ASSERT_OK(dhcp_message_new(&m));
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data));
ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, /* ret= */ NULL), EBADMSG);
dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH);
_cleanup_free_ uint8_t *sip = new(uint8_t, len + 1);
sip[0] = 0;
memcpy(sip + 1, data, len);
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip));
ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, /* ret= */ NULL), EBADMSG);
}
TEST(domains) {
/* simple */
static uint8_t simple[] = {
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
4, 'h', 'o', 'g', 'e',
3, 'f', 'o', 'o',
0,
};
test_domains_one(ELEMENTSOF(simple), simple,
STRV_MAKE("example.com", "hoge.foo"));
/* compressed */
static uint8_t compressed[] = {
4, 'h', 'o', 'g', 'e',
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
3, 'f', 'o', 'o',
0xc0, 5,
3, 'b', 'a', 'r',
0xc0, 18,
3, 'b', 'a', 'z',
0xc0, 0,
};
test_domains_one(ELEMENTSOF(compressed), compressed,
STRV_MAKE("hoge.example.com", "foo.example.com", "bar.foo.example.com", "baz.hoge.example.com"));
/* invalid pointer */
static uint8_t invalid[] = {
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
3, 'f', 'o', 'o',
0xc0, 0xff,
};
test_domains_fail(ELEMENTSOF(invalid), invalid);
/* forward pointer */
static uint8_t forward[] = {
4, 'h', 'o', 'g', 'e',
0xc0, 11,
3, 'f', 'o', 'o',
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
};
test_domains_fail(ELEMENTSOF(forward), forward);
/* infinite loop */
static uint8_t loop1[] = {
0xc0, 0x00,
};
test_domains_fail(ELEMENTSOF(loop1), loop1);
static uint8_t loop2[] = {
0xc0, 0x02,
0xc0, 0x00,
};
test_domains_fail(ELEMENTSOF(loop2), loop2);
static uint8_t loop3[] = {
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
0xc0, 0x00,
};
test_domains_fail(ELEMENTSOF(loop3), loop3);
/* unterminated */
static uint8_t unterminated[] = {
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
};
test_domains_fail(ELEMENTSOF(unterminated), unterminated);
/* truncated label */
static uint8_t truncated[] = {
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o',
};
test_domains_fail(ELEMENTSOF(truncated), truncated);
}
TEST(dnr) {
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL;
ASSERT_OK(dhcp_message_new(&m));
sd_dns_resolver *resolvers = NULL;
size_t n_resolvers = 0;
CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array);
static uint8_t data[] = {
/* Instance 1 */
/* length */
0, 78,
/* priority */
0, 1,
/* authentication domain name */
22,
8, 'r', 'e', 's', 'o', 'l', 'v', 'e', 'r',
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
/* addresses */
8,
192, 0, 2, 1,
192, 0, 2, 2,
/* service parameters */
/* ALPN */
0, DNS_SVC_PARAM_KEY_ALPN,
0, 14,
2, 'h', '2',
2, 'h', '3',
3, 'd', 'o', 't',
3, 'd', 'o', 'q',
/* port */
0, DNS_SVC_PARAM_KEY_PORT,
0, 2,
0, 42,
/* DoH path*/
0, DNS_SVC_PARAM_KEY_DOHPATH,
0, 16,
'/', 'd', 'n', 's', '-', 'q', 'u', 'e', 'r', 'y', '{', '?', 'd', 'n', 's', '}',
/* Instance 2 */
/* length */
0, 44,
/* priority */
0, 2,
/* authentication domain name */
22,
8, 'h', 'o', 'g', 'e', 'h', 'o', 'g', 'e',
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
/* addresses */
4,
192, 0, 2, 3,
/* service parameters */
/* ALPN */
0, DNS_SVC_PARAM_KEY_ALPN,
0, 4,
3, 'd', 'o', 't',
/* port */
0, DNS_SVC_PARAM_KEY_PORT,
0, 2,
0, 33,
/* Instance 3 (no address, ignored) */
/* length */
0, 20,
/* priority */
0, 3,
/* authentication domain name */
17,
3, 'f', 'o', 'o',
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
/* Instance 4 (unknown alpn, ignored) */
/* length */
0, 37,
/* priority */
0, 4,
/* authentication domain name */
20,
6, 'b', 'a', 'r', 'b', 'a', 'z',
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
/* addresses */
4,
192, 0, 2, 4,
/* service parameters */
/* ALPN */
0, DNS_SVC_PARAM_KEY_ALPN,
0, 5,
4, 'h', 'o', 'g', 'e',
};
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(data), data));
ASSERT_OK(dhcp_message_get_option_dnr(m, &n_resolvers, &resolvers));
ASSERT_EQ(n_resolvers, 2u);
ASSERT_EQ(resolvers[0].priority, 1u);
ASSERT_STREQ(resolvers[0].auth_name, "resolver.example.com");
ASSERT_EQ(resolvers[0].family, AF_INET);
ASSERT_EQ(resolvers[0].n_addrs, 2u);
ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[0].family, &resolvers[0].addrs[0]), "192.0.2.1");
ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[0].family, &resolvers[0].addrs[1]), "192.0.2.2");
ASSERT_EQ(resolvers[0].transports, SD_DNS_ALPN_HTTP_2_TLS | SD_DNS_ALPN_HTTP_3 | SD_DNS_ALPN_DOT | SD_DNS_ALPN_DOQ);
ASSERT_EQ(resolvers[0].port, 42u);
ASSERT_STREQ(resolvers[0].dohpath, "/dns-query{?dns}");
ASSERT_EQ(resolvers[1].priority, 2u);
ASSERT_STREQ(resolvers[1].auth_name, "hogehoge.example.com");
ASSERT_EQ(resolvers[1].family, AF_INET);
ASSERT_EQ(resolvers[1].n_addrs, 1u);
ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[1].family, &resolvers[1].addrs[0]), "192.0.2.3");
ASSERT_EQ(resolvers[1].transports, SD_DNS_ALPN_DOT);
ASSERT_EQ(resolvers[1].port, 33u);
ASSERT_NULL(resolvers[1].dohpath);
/* missing DoH path */
static uint8_t invalid[] = {
/* length */
0, 35,
/* priority */
0, 5,
/* authentication domain name */
20,
6, 'b', 'a', 'r', 'b', 'a', 'z',
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
/* addresses */
4,
192, 0, 2, 5,
/* service parameters */
/* ALPN */
0, DNS_SVC_PARAM_KEY_ALPN,
0, 3,
2, 'h', '2',
};
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid), invalid));
ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EBADMSG);
dhcp_message_remove_option(m, SD_DHCP_OPTION_V4_DNR);
/* missing ALPN */
static uint8_t invalid2[] = {
/* length */
0, 28,
/* priority */
0, 6,
/* authentication domain name */
20,
6, 'b', 'a', 'r', 'b', 'a', 'z',
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o', 'm',
0,
/* addresses */
4,
192, 0, 2, 6,
};
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid2), invalid2));
ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EBADMSG);
dhcp_message_remove_option(m, SD_DHCP_OPTION_V4_DNR);
/* truncated domain name */
static uint8_t invalid3[] = {
/* length */
0, 34,
/* priority */
0, 7,
/* authentication domain name */
18,
6, 'b', 'a', 'r', 'b', 'a', 'z',
7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
3, 'c', 'o',
/* addresses */
4,
192, 0, 2, 7,
/* service parameters */
/* ALPN */
0, DNS_SVC_PARAM_KEY_ALPN,
0, 4,
3, 'd', 'o', 't',
};
ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid3), invalid3));
ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EMSGSIZE);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
+1 -1
View File
@@ -3,7 +3,7 @@
#include <arpa/inet.h>
#include "alloc-util.h"
#include "dhcp-lease-internal.h"
#include "dhcp-route.h"
#include "hashmap.h"
#include "hostname-setup.h"
#include "network-internal.h"
+3 -3
View File
@@ -86,9 +86,9 @@ int sd_dhcp_lease_get_6rd(
size_t *ret_n_br_addresses);
int sd_dhcp_lease_has_6rd(sd_dhcp_lease *lease);
int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination);
int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length);
int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway);
int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *ret);
int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *ret);
int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *ret);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref);