dhcp-message: introduce dhcp_message_get_option_domains()

This is for e.g. DHCP option 119 (domain search).
This commit is contained in:
Yu Watanabe
2026-05-13 09:41:24 +09:00
parent 295a4dd6ed
commit c588c31144
3 changed files with 222 additions and 0 deletions
+103
View File
@@ -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;
+1
View File
@@ -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);
+118
View File
@@ -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);