dhcp-message: introduce dhcp_message_{append,get}_option_routes()

These are for DHCP options 33 (static route), 121 (classless static
route), and 249 (private classless static route).
This commit is contained in:
Yu Watanabe
2026-05-13 09:41:24 +09:00
parent 08249b359b
commit ff9d98423c
3 changed files with 263 additions and 0 deletions
+216
View File
@@ -6,11 +6,13 @@
#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 "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"
@@ -227,6 +229,88 @@ 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_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id) {
assert(message);
assert(id);
@@ -512,6 +596,138 @@ 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_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret) {
int r;
+2
View File
@@ -39,6 +39,7 @@ 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_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);
@@ -55,6 +56,7 @@ 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_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);
@@ -6,6 +6,7 @@
#include "dhcp-client-id-internal.h"
#include "dhcp-message.h"
#include "dhcp-protocol.h"
#include "dhcp-route.h"
#include "ether-addr-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
@@ -105,6 +106,25 @@ 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_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));
@@ -177,6 +197,24 @@ TEST(dhcp_message) {
{ .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,
},
};
sd_dhcp_client_id id = {
.raw = { 1, 3, 3, 3, 3, 3, 3, },
.size = 7,
@@ -277,6 +315,12 @@ 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);
/* client ID */
ASSERT_OK(dhcp_message_append_option_client_id(m, &id));
verify_client_id(m, &id);
@@ -338,6 +382,7 @@ TEST(dhcp_message) {
verify_address(m2, &addr);
verify_addresses(m2, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip);
verify_string(m2, vendor_class);
verify_routes(m2, ELEMENTSOF(routes), routes);
verify_client_id(m2, &id);
verify_prl(m2, prl);
verify_hostname(m2, hostname);