dhcp-message: introduce dhcp_message_{append,get}_option_hostname() and related functions

These are for DHCP options 12 (Hostname) and 81 (FQDN) options.
This commit is contained in:
Yu Watanabe
2026-05-13 00:27:49 +09:00
parent aa9584a037
commit 28423ea18c
3 changed files with 190 additions and 0 deletions
+164
View File
@@ -6,7 +6,10 @@
#include "dhcp-client-id-internal.h"
#include "dhcp-message.h"
#include "dhcp-protocol.h"
#include "dns-domain.h"
#include "errno-util.h"
#include "ether-addr-util.h"
#include "hostname-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "ip-util.h"
@@ -247,6 +250,62 @@ int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message,
return dhcp_message_append_option(message, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, len, buf);
}
static int dhcp_message_append_option_fqdn(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *fqdn) {
int r;
assert(message);
assert(fqdn);
/* FIXME: Allow long fqdn, as now we support long option. */
uint8_t buf[3 + DHCP_MAX_FQDN_LENGTH];
/* RFC 4702 section 2.1
* The "E" bit indicates the encoding of the Domain Name field. 1 indicates canonical wire format,
* without compression. This encoding SHOULD be used by clients and MUST be supported by servers.
* A server MUST use the same encoding as that used by the client. A server that does not support
* the deprecated ASCII encoding MUST ignore Client FQDN options that use that encoding.
*
* Here, we unconditionally set the 'E' flag. Hence, sd_dhcp_server must ignore the option if a
* client does not set the 'E' flag in the request. */
buf[0] = flags | DHCP_FQDN_FLAG_E;
/* RFC 4702 section 2.2
* The two 1-octet RCODE1 and RCODE2 fields are deprecated. A client SHOULD set these to 0 when
* sending the option and SHOULD ignore them on receipt. A server SHOULD set these to 255 when
* sending the option and MUST ignore them on receipt. */
buf[1] = is_client ? 0 : 255;
buf[2] = is_client ? 0 : 255;
r = dns_name_to_wire_format(fqdn, buf + 3, sizeof(buf) - 3, false);
if (r <= 0)
return r;
return dhcp_message_append_option(message, SD_DHCP_OPTION_FQDN, 3 + r, buf);
}
int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname) {
assert(message);
/* Hostname (12) or FQDN (81)
*
* RFC 4702 section 3.1
* clients that send the Client FQDN option in their messages MUST NOT also send the Host Name option. */
if (isempty(hostname))
return 0;
if (dhcp_message_has_option(message, SD_DHCP_OPTION_HOST_NAME))
return -EEXIST;
if (dhcp_message_has_option(message, SD_DHCP_OPTION_FQDN))
return -EEXIST;
if (dns_name_is_single_label(hostname))
return dhcp_message_append_option_string(message, SD_DHCP_OPTION_HOST_NAME, hostname);
return dhcp_message_append_option_fqdn(message, flags, is_client, hostname);
}
int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) {
int r;
@@ -415,6 +474,111 @@ int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set
return 0;
}
static int normalize_dns_name(const char *name, char **ret) {
int r;
assert(name);
_cleanup_free_ char *normalized = NULL;
r = dns_name_normalize(name, /* flags= */ 0, &normalized);
if (r < 0)
return r;
if (is_localhost(normalized))
return -EINVAL;
if (dns_name_is_root(normalized))
return -EINVAL;
if (ret)
*ret = TAKE_PTR(normalized);
return 0;
}
int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn) {
int r;
assert(message);
_cleanup_(iovec_done) struct iovec iov = {};
r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_FQDN, &iov);
if (r < 0)
return r;
if (iov.iov_len <= 3)
return -EBADMSG;
uint8_t flags = *(uint8_t*) iov.iov_base;
if (!FLAGS_SET(flags, DHCP_FQDN_FLAG_E))
return -EOPNOTSUPP;
struct iovec i;
iovec_shift(&iov, 3, &i);
_cleanup_free_ char *name = NULL;
const uint8_t *p = i.iov_base;
r = dns_name_from_wire_format(&p, &i.iov_len, &name);
if (r < 0)
return r;
if (i.iov_len > 0) /* trailing garbage? */
return -EBADMSG;
if (isempty(name))
return -ENODATA;
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;
if (ret_flags)
*ret_flags = flags;
if (ret_fqdn)
*ret_fqdn = TAKE_PTR(normalized);
return 0;
}
int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret) {
int r;
assert(message);
/* Mainly for Host Name or Domain Name options. */
_cleanup_free_ char *name = NULL;
r = dhcp_message_get_option_string(message, code, &name);
if (r < 0)
return r;
_cleanup_free_ char *normalized = NULL;
r = normalize_dns_name(name, &normalized);
if (r < 0)
return r;
if (ret)
*ret = TAKE_PTR(normalized);
return 0;
}
int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret) {
int r;
assert(message);
/* FQDN option always takes precedence. */
r = dhcp_message_get_option_fqdn(message, /* ret_flags= */ NULL, ret);
if (ERRNO_IS_NEG_RESOURCE(r))
return r;
if (r >= 0)
return 0;
/* Then, fall back to Host Name option. */
return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret);
}
static int dhcp_message_verify_header(
const struct iovec *iov,
uint8_t op,
+4
View File
@@ -41,6 +41,7 @@ int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code,
int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data);
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);
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);
@@ -54,6 +55,9 @@ int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, si
int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret);
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);
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_parse(
const struct iovec *iov,
@@ -108,6 +108,12 @@ static void verify_prl(sd_dhcp_message *m, Set *expected) {
ASSERT_TRUE(set_equal(set, expected));
}
static void verify_hostname(sd_dhcp_message *m, const char *expected) {
_cleanup_free_ char *s = NULL;
ASSERT_OK(dhcp_message_get_option_hostname(m, &s));
ASSERT_STREQ(s, expected);
}
TEST(dhcp_message) {
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL;
@@ -143,6 +149,7 @@ TEST(dhcp_message) {
for (uint8_t i = SD_DHCP_OPTION_PRIVATE_BASE; i <= SD_DHCP_OPTION_PRIVATE_LAST; i++)
ASSERT_OK(set_ensure_put(&prl, /* hash_ops= */ NULL, UINT_TO_PTR(i)));
const char *hostname = "test-node.example.com";
const char *vendor_class = "hogehoge";
char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo");
@@ -222,6 +229,20 @@ TEST(dhcp_message) {
ASSERT_OK(dhcp_message_append_option_parameter_request_list(m, prl));
verify_prl(m, prl);
/* hostname */
ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, "hogehoge"));
ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST);
dhcp_message_remove_option(m, SD_DHCP_OPTION_FQDN);
ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST);
dhcp_message_remove_option(m, SD_DHCP_OPTION_HOST_NAME);
ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, "hogehoge.example.com"));
ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST);
dhcp_message_remove_option(m, SD_DHCP_OPTION_HOST_NAME);
ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST);
dhcp_message_remove_option(m, SD_DHCP_OPTION_FQDN);
ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname));
verify_hostname(m, hostname);
/* build and parse */
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
ASSERT_OK(dhcp_message_build(m, &iovw));
@@ -252,6 +273,7 @@ TEST(dhcp_message) {
verify_string(m2, vendor_class);
verify_client_id(m2, &id);
verify_prl(m2, prl);
verify_hostname(m2, hostname);
/* build again, and verify the packet */
_cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {};