]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dhcp-message: introduce dhcp_message_{append,get}_option_6rd()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 12 Apr 2026 19:02:39 +0000 (04:02 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 13 May 2026 00:41:24 +0000 (09:41 +0900)
These are for DHCP option 212 (6rd).

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

index 58d3664a07a75665404f7f6e5734389df693cee2..676e87d6779a58a8ca48395e84a95930c5537509 100644 (file)
@@ -311,6 +311,56 @@ int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, si
         }
 }
 
+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);
@@ -728,6 +778,78 @@ int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_
         }
 }
 
+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;
 
index ad6572b272e629f818d249a1e4e0a1e81b03422e..f754bde05ad727a69430cde7a620fd83100e39ca 100644 (file)
@@ -40,6 +40,13 @@ int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, c
 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);
@@ -57,6 +64,13 @@ int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, stru
 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);
index 70d8f09554df912280987e5aa690a70b8227d216..dd4f49f1fb16e40bf9f5559c52a6bbbed3d88c66 100644 (file)
@@ -125,6 +125,28 @@ static void verify_routes(sd_dhcp_message *m, size_t n_expected, const sd_dhcp_r
         }
 }
 
+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));
@@ -215,6 +237,17 @@ TEST(dhcp_message) {
                 },
         };
 
+        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,
@@ -321,6 +354,16 @@ TEST(dhcp_message) {
         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);
@@ -383,6 +426,7 @@ TEST(dhcp_message) {
         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);