From: Yu Watanabe Date: Sat, 11 Apr 2026 17:59:16 +0000 (+0900) Subject: dhcp-message: introduce dhcp_message_get_option_domains() X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c588c311445a23b9ddc6b9647918e35be3b31991;p=thirdparty%2Fsystemd.git dhcp-message: introduce dhcp_message_get_option_domains() This is for e.g. DHCP option 119 (domain search). --- diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index c2c25ed6ff6..00e38ea9eec 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -6,6 +6,7 @@ #include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" +#include "dns-def.h" #include "dns-domain.h" #include "errno-util.h" #include "ether-addr-util.h" @@ -619,6 +620,108 @@ int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret) { return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret); } +int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret) { + int r; + + assert(message); + + /* This is mostly for SD_DHCP_OPTION_DOMAIN_SEARCH. */ + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + const uint8_t *buf = iov.iov_base; + size_t len = iov.iov_len; + + _cleanup_strv_free_ char **names = NULL; + size_t n_names = 0; + + _cleanup_free_ char *name = NULL; + size_t n = 0; + + for (size_t pos = 0, jump_barrier = 0, next_chunk = 0; pos < len;) { + uint8_t c = buf[pos++]; + + if (c == 0) { + /* End of name */ + + if (!string_is_safe(name, /* flags= */ 0)) + return -EBADMSG; + + _cleanup_free_ char *normalized = NULL; + r = normalize_dns_name(name, &normalized); + if (r < 0) + return r; + + r = strv_consume_with_size(&names, &n_names, TAKE_PTR(normalized)); + if (r < 0) + return r; + + if (next_chunk != 0) + pos = next_chunk; + + next_chunk = 0; + jump_barrier = pos; + + name = mfree(name); + n = 0; + + } else if (c <= 63) { + /* Literal label */ + + const char *label = (const char*) (buf + pos); + pos += c; + + if (pos >= len) + return -EBADMSG; + + if (!GREEDY_REALLOC(name, n + 1 + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (n != 0) + name[n++] = '.'; + + r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + + } else if (FLAGS_SET(c, 0xc0)) { + /* Pointer */ + + if (pos >= len) /* pointer is 2 bytes, hence we need to read at least one more byte. */ + return -EBADMSG; + + /* Save the current location so we don't end up re-parsing what's parsed so far. */ + if (next_chunk == 0) + next_chunk = pos + 1; + + pos = ((size_t) (c & ~0xc0) << 8) | ((size_t) buf[pos]); + + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + if (pos >= jump_barrier) + return -EBADMSG; + + jump_barrier = pos; + + } else + return -EBADMSG; + } + + if (!isempty(name)) /* trailing garbage?? Should not happen, but for safety. */ + return -EBADMSG; + + if (strv_isempty(names)) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(names); + return 0; +} + int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret) { int r; diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 4b85a26e922..e9e4551cd80 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -60,6 +60,7 @@ int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn); int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret); int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret); +int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret); int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret); int dhcp_message_get_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, struct iovec_wrapper *ret); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index effdf92dc70..ddb3823f254 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -330,4 +330,122 @@ TEST(dhcp_message) { ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); } +static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) { + _cleanup_strv_free_ char **strv = NULL; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH); + strv = strv_free(strv); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len / 2, data)); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len - len / 2, data + len / 2)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); +} + +static void test_domains_fail(size_t len, const uint8_t *data) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data)); + ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, /* ret= */ NULL), EBADMSG); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH); + + _cleanup_free_ uint8_t *sip = new(uint8_t, len + 1); + sip[0] = 0; + memcpy(sip + 1, data, len); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip)); + ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, /* ret= */ NULL), EBADMSG); +} + +TEST(domains) { + /* simple */ + static uint8_t simple[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 4, 'h', 'o', 'g', 'e', + 3, 'f', 'o', 'o', + 0, + }; + test_domains_one(ELEMENTSOF(simple), simple, + STRV_MAKE("example.com", "hoge.foo")); + + /* compressed */ + static uint8_t compressed[] = { + 4, 'h', 'o', 'g', 'e', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 3, 'f', 'o', 'o', + 0xc0, 5, + 3, 'b', 'a', 'r', + 0xc0, 18, + 3, 'b', 'a', 'z', + 0xc0, 0, + }; + test_domains_one(ELEMENTSOF(compressed), compressed, + STRV_MAKE("hoge.example.com", "foo.example.com", "bar.foo.example.com", "baz.hoge.example.com")); + + /* invalid pointer */ + static uint8_t invalid[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 3, 'f', 'o', 'o', + 0xc0, 0xff, + }; + test_domains_fail(ELEMENTSOF(invalid), invalid); + + /* forward pointer */ + static uint8_t forward[] = { + 4, 'h', 'o', 'g', 'e', + 0xc0, 11, + 3, 'f', 'o', 'o', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + }; + test_domains_fail(ELEMENTSOF(forward), forward); + + /* infinite loop */ + static uint8_t loop1[] = { + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop1), loop1); + + static uint8_t loop2[] = { + 0xc0, 0x02, + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop2), loop2); + + static uint8_t loop3[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop3), loop3); + + /* unterminated */ + static uint8_t unterminated[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + }; + test_domains_fail(ELEMENTSOF(unterminated), unterminated); + + /* truncated label */ + static uint8_t truncated[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', + }; + test_domains_fail(ELEMENTSOF(truncated), truncated); +} + DEFINE_TEST_MAIN(LOG_DEBUG);