From: Yu Watanabe Date: Sat, 11 Apr 2026 21:18:50 +0000 (+0900) Subject: dhcp-message: introduce dhcp_message_{append,get}_option_routes() X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ff9d98423c6101ce7e1bd8787ba03e220fd78451;p=thirdparty%2Fsystemd.git 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). --- diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 50cc25aded6..58d3664a07a 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -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; diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index e9e4551cd80..ad6572b272e 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -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); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 1aa58300083..70d8f09554d 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -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);