From: Yu Watanabe Date: Sun, 12 Apr 2026 19:02:39 +0000 (+0900) Subject: dhcp-message: introduce dhcp_message_{append,get}_option_6rd() X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=64846d9d8a1d15cc8b1629272eeb711fef684be6;p=thirdparty%2Fsystemd.git dhcp-message: introduce dhcp_message_{append,get}_option_6rd() These are for DHCP option 212 (6rd). --- diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 58d3664a07a..676e87d6779 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -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; diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index ad6572b272e..f754bde05ad 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -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); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 70d8f09554d..dd4f49f1fb1 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -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);