]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dhcp-message: introduce dhcp_message_{append,get}_option_routes()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 11 Apr 2026 21:18:50 +0000 (06:18 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 13 May 2026 00:41:24 +0000 (09:41 +0900)
These are for DHCP options 33 (static route), 121 (classless static
route), and 249 (private classless static route).

src/libsystemd-network/dhcp-message.c
src/libsystemd-network/dhcp-message.h
src/libsystemd-network/test-dhcp-message.c

index 50cc25aded68d8d5dc52846874765b6b8e0172ca..58d3664a07a75665404f7f6e5734389df693cee2 100644 (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;
 
index e9e4551cd80bca9cb930c9b73062c6267ff892bf..ad6572b272e629f818d249a1e4e0a1e81b03422e 100644 (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);
index 1aa5830008323697fed674a0d3ca99cc565943e0..70d8f09554df912280987e5aa690a70b8227d216 100644 (file)
@@ -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);