From: Yu Watanabe Date: Sun, 19 Apr 2026 05:41:29 +0000 (+0900) Subject: dhcp-message: introduce dhcp_message_{append,get}_option_string() X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1c4ee9e96993d213917c58c5d6ff5578c59311ed;p=thirdparty%2Fsystemd.git dhcp-message: introduce dhcp_message_{append,get}_option_string() These are for DHCP options that takes a string e.g. DHCP options 17 (root path), 60 (vendor class identifier), and so on. --- diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 816d16955d6..eafc84c179e 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -10,6 +10,7 @@ #include "iovec-wrapper.h" #include "ip-util.h" #include "network-common.h" +#include "string-util.h" static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { if (!message) @@ -186,6 +187,21 @@ int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, return dhcp_message_append_option(message, code, sizeof(struct in_addr) * n_addr, addr); } +int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data) { + assert(message); + + if (isempty(data)) + return 0; + + if (!string_is_safe(data, STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)) + return -EINVAL; + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, strlen(data), data); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -281,6 +297,38 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si return 0; } +int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret) { + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + if (!iovec_is_set(&iov)) + return -ENODATA; + + /* Allow NUL at the end for buggy DHCP servers, but refuse intermediate NUL. */ + if (memchr(iov.iov_base, 0, iov.iov_len - 1)) + return -EBADMSG; + + /* Note, dhcp_message_get_option_alloc() -> tlv_get_alloc() allocates an extra byte to make + * iov.iov_base can be handled as a NUL-terminated string. Hence, we can directly pass it to + * isempty() and string_is_safe(). */ + + if (isempty(iov.iov_base)) + return -ENODATA; + + if (!string_is_safe(iov.iov_base, STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(iov.iov_base); + return 0; +} + static int dhcp_message_verify_header( const struct iovec *iov, uint8_t op, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 6d4e1939b0f..d47d7ba4094 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -37,6 +37,7 @@ int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32 int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t 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_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); @@ -47,6 +48,7 @@ int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *ret); 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_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 7c1c255f951..31e0754d931 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -2,12 +2,14 @@ #include +#include "alloc-util.h" #include "dhcp-message.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" #include "random-util.h" +#include "strv.h" #include "tests.h" static void verify_header(sd_dhcp_message *m, uint32_t xid, const struct hw_addr_data *hw_addr) { @@ -79,6 +81,19 @@ static void verify_addresses( ASSERT_EQ(memcmp(addrs, ntp, sizeof(struct in_addr) * n), 0); } +static void verify_string(sd_dhcp_message *m, const char *expected) { + _cleanup_free_ char *s = NULL; + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, &s)); + ASSERT_STREQ(s, expected); +} + +static void verify_multiple_strings(sd_dhcp_message *m, char * const *expected) { + _cleanup_free_ char *s = NULL; + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_ROOT_PATH, &s)); + _cleanup_free_ char *joined = ASSERT_NOT_NULL(strv_join(expected, /* separator= */ "")); + ASSERT_STREQ(s, joined); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -105,6 +120,9 @@ TEST(dhcp_message) { { .s_addr = htobe32(0xC0000204) }, }; + const char *vendor_class = "hogehoge"; + char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); + ASSERT_OK(dhcp_message_init_header( m, BOOTREQUEST, @@ -118,6 +136,11 @@ TEST(dhcp_message) { ASSERT_ERROR(dhcp_message_append_option(m, SD_DHCP_OPTION_PAD, 0, NULL), EINVAL); ASSERT_ERROR(dhcp_message_append_option(m, SD_DHCP_OPTION_END, 0, NULL), EINVAL); + /* multiple strings */ + STRV_FOREACH(s, root_path) + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_ROOT_PATH, strlen(*s), *s)); + verify_multiple_strings(m, root_path); + /* flag */ ASSERT_ERROR(dhcp_message_get_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT), ENODATA); ASSERT_OK(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT)); @@ -154,6 +177,19 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, ELEMENTSOF(ntp) - 1, ntp + 1)); verify_addresses(m, ELEMENTSOF(ntp), ntp); + /* string */ + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, 0, NULL)); + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, 1, "\0")); + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, 9, "hoge\0hoge")); + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), EBADMSG); + ASSERT_ERROR(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER); + ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class)); + verify_string(m, vendor_class); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -174,12 +210,14 @@ TEST(dhcp_message) { /* verify parsed message */ verify_header(m2, xid, &hw_addr); + verify_multiple_strings(m2, root_path); verify_flag(m2); verify_u8(m2, DHCP_DISCOVER); verify_u16(m2, 512); verify_sec(m2, lease_time); verify_address(m2, &addr); verify_addresses(m2, ELEMENTSOF(ntp), ntp); + verify_string(m2, vendor_class); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {};