From 8c18bb6547c2138f2f17b921ec06f2c1f7cd17cd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 22 Mar 2026 16:19:06 +0900 Subject: [PATCH 1/8] dhcp-message: introduce sd_dhcp_message object and several functions for the object --- src/libsystemd-network/dhcp-message.c | 349 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 53 ++++ src/libsystemd-network/dhcp-protocol.h | 8 + src/libsystemd-network/meson.build | 4 + src/libsystemd-network/test-dhcp-message.c | 134 ++++++++ 5 files changed, 548 insertions(+) create mode 100644 src/libsystemd-network/dhcp-message.c create mode 100644 src/libsystemd-network/dhcp-message.h create mode 100644 src/libsystemd-network/test-dhcp-message.c diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c new file mode 100644 index 0000000000..eb43c7e6e5 --- /dev/null +++ b/src/libsystemd-network/dhcp-message.c @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#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 "ip-util.h" + +static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { + if (!message) + return NULL; + + tlv_done(&message->options); + return mfree(message); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_message, sd_dhcp_message, dhcp_message_free); + +int dhcp_message_new(sd_dhcp_message **ret) { + assert(ret); + + sd_dhcp_message *message = new(sd_dhcp_message, 1); + if (!message) + return -ENOMEM; + + *message = (sd_dhcp_message) { + .n_ref = 1, + .options = TLV_INIT(TLV_DHCP4), + }; + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_message_init_header( + sd_dhcp_message *message, + uint8_t op, + uint32_t xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr) { + + assert(message); + assert(IN_SET(op, BOOTREQUEST, BOOTREPLY)); + + /* RFC 2131 section 4.1.1: + * The client MUST include its hardware address in the ’chaddr’ field, if necessary for delivery of + * DHCP reply messages. + * + * RFC 4390 section 2.1: + * A DHCP client, when working over an IPoIB interface, MUST follow the following rules: + * "htype" (hardware address type) MUST be 32 [ARPPARAM]. + * "hlen" (hardware address length) MUST be 0. + * "chaddr" (client hardware address) field MUST be zeroed. + * + * Note, the maximum hardware address length (HW_ADDR_MAX_SIZE) is 32, but the size of the chaddr + * field is 16. + * + * Also, ARP type is 2 bytes, but the htype field is 1 byte. */ + + if (arp_type == ARPHRD_INFINIBAND) + hw_addr = NULL; + + if (hw_addr && hw_addr->length > sizeof_field(DHCPMessageHeader, chaddr)) + return -EINVAL; + + message->header = (DHCPMessageHeader) { + .op = op, + .htype = arp_type <= UINT8_MAX ? arp_type : 0, + .hlen = hw_addr ? hw_addr->length : 0, + .xid = htobe32(xid), + .magic = htobe32(DHCP_MAGIC_COOKIE), + }; + + if (hw_addr) + memcpy_safe(message->header.chaddr, hw_addr->bytes, hw_addr->length); + return 0; +} + +void dhcp_message_set_broadcast_flag(sd_dhcp_message *message, bool b) { + assert(message); + + SET_FLAG(message->header.flags, htobe16(0x8000), b); +} + +bool dhcp_message_has_broadcast_flag(sd_dhcp_message *message) { + assert(message); + + return FLAGS_SET(message->header.flags, htobe16(0x8000)); +} + +int dhcp_message_get_hw_addr(sd_dhcp_message *message, struct hw_addr_data *ret) { + assert(message); + assert(ret); + + if (message->header.hlen > sizeof_field(DHCPMessageHeader, chaddr)) + return -EBADMSG; + + ret->length = message->header.hlen; + memcpy_safe(ret->bytes, message->header.chaddr, message->header.hlen); + return 0; +} + +bool dhcp_message_has_option(sd_dhcp_message *message, uint8_t code) { + assert(message); + return tlv_contains(&message->options, code); +} + +void dhcp_message_remove_option(sd_dhcp_message *message, uint8_t code) { + assert(message); + tlv_remove(&message->options, code); +} + +int dhcp_message_append_option(sd_dhcp_message *message, uint8_t code, size_t length, const void *data) { + assert(message); + return tlv_append(&message->options, code, length, data); +} + +int dhcp_message_append_option_tlv(sd_dhcp_message *message, const TLV *tlv) { + assert(message); + return tlv_append_tlv(&message->options, tlv); +} + +int dhcp_message_append_option_flag(sd_dhcp_message *message, uint8_t code) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, /* length= */ 0, /* data= */ NULL); +} + +int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t data) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, sizeof(uint8_t), &data); +} + +int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + be16_t b = htobe16(data); + return dhcp_message_append_option(message, code, sizeof(be16_t), &b); +} + +int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, sizeof(be32_t), &data); +} + +int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { + int r; + + assert(message); + + struct iovec iov; + r = tlv_get_full(&message->options, code, length, ret ? &iov : NULL); + if (r < 0) + return r; + + if (ret) + memcpy_safe(ret, iov.iov_base, iov.iov_len); + return 0; +} + +int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret) { + assert(message); + return tlv_get_alloc(&message->options, code, ret); +} + +int dhcp_message_get_option_flag(sd_dhcp_message *message, uint8_t code) { + assert(message); + return dhcp_message_get_option(message, code, /* length= */ 0, /* ret= */ NULL); +} + +int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t *ret) { + assert(message); + return dhcp_message_get_option(message, code, sizeof(uint8_t), ret); +} + +int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret) { + be16_t b; + int r; + + assert(message); + + r = dhcp_message_get_option(message, code, sizeof(be16_t), ret ? &b : NULL); + if (r < 0) + return r; + + if (ret) + *ret = be16toh(b); + return 0; +} + +int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret) { + assert(message); + return dhcp_message_get_option(message, code, sizeof(be32_t), ret); +} + +static int dhcp_message_verify_header( + const struct iovec *iov, + uint8_t op, + const uint32_t *xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr) { + + assert(iov); + assert(iovec_is_valid(iov)); + assert(IN_SET(op, 0, BOOTREQUEST, BOOTREPLY)); /* when 0, both BOOTREQUEST and BOOTREPLY are accepted */ + + POINTER_MAY_BE_NULL(xid); + POINTER_MAY_BE_NULL(hw_addr); + + if (iov->iov_len < sizeof(DHCPMessageHeader)) + return -EBADMSG; + + const DHCPMessageHeader *header = iov->iov_base; + + if (!IN_SET(header->op, BOOTREQUEST, BOOTREPLY)) + return -EBADMSG; + if (op != 0 && header->op != op) + return -EBADMSG; + + if (xid && be32toh(header->xid) != *xid) + return -EBADMSG; + + if (arp_type <= UINT8_MAX && header->htype != arp_type) + return -EBADMSG; + + if (header->hlen > sizeof_field(DHCPMessageHeader, chaddr)) + return -EBADMSG; + + if (hw_addr && memcmp_nn(header->chaddr, header->hlen, hw_addr->bytes, hw_addr->length) != 0) + return -EBADMSG; + + return 0; +} + +int dhcp_message_parse( + const struct iovec *iov, + uint8_t op, + const uint32_t *xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr, + sd_dhcp_message **ret) { + + int r; + + assert(iov); + assert(ret); + + r = dhcp_message_verify_header(iov, op, xid, arp_type, hw_addr); + if (r < 0) + return r; + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + memcpy(&message->header, iov->iov_base, sizeof(DHCPMessageHeader)); + + if (be32toh(message->header.magic) != DHCP_MAGIC_COOKIE) { + /* Should be BOOTP, and no options. */ + *ret = TAKE_PTR(message); + return 0; + } + + /* In the BOOTP protocol (RFC 951), the vendor field (magic + options) is 64 bytes, but here we do + * not check the length, and support all DHCP options even if we are running as BOOTP client. */ + + r = tlv_parse(&message->options, &IOVEC_SHIFT(iov, sizeof(DHCPMessageHeader))); + if (r < 0) + return r; + + /* Parse SD_DHCP_OPTION_OVERLOAD (52) to determine if we should parse sname and/or file. */ + uint8_t overload = DHCP_OVERLOAD_NONE; + (void) dhcp_message_get_option_u8(message, SD_DHCP_OPTION_OVERLOAD, &overload); + + if (FLAGS_SET(overload, DHCP_OVERLOAD_FILE)) { + r = tlv_parse(&message->options, &IOVEC_MAKE(message->header.file, sizeof(message->header.file))); + if (r < 0) + return r; + } + + if (FLAGS_SET(overload, DHCP_OVERLOAD_SNAME)) { + r = tlv_parse(&message->options, &IOVEC_MAKE(message->header.sname, sizeof(message->header.sname))); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret) { + int r; + + assert(message); + assert(ret); + + size_t size = size_add(sizeof(DHCPMessageHeader), tlv_size(&message->options)); + if (size > UDP_PAYLOAD_MAX_SIZE) + return -E2BIG; + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = iovw_extend(&iovw, &message->header, sizeof(DHCPMessageHeader)); + if (r < 0) + return r; + + struct iovec options; + r = tlv_build(&message->options, &options); + if (r < 0) + return r; + + r = iovw_consume_iov(&iovw, &options); + if (r < 0) + return r; + + /* For compatibility with other implementations and network appliances, the message size should be at + * least 300 bytes, which is the size of BOOTP message. */ + size_t padding_size = LESS_BY(BOOTP_MESSAGE_SIZE, size); + if (padding_size > 0) { + uint8_t *padding = new0(uint8_t, padding_size); + if (!padding) + return -ENOMEM; + + r = iovw_consume(&iovw, padding, padding_size); + if (r < 0) + return r; + } + + *ret = TAKE_STRUCT(iovw); + return 0; +} diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h new file mode 100644 index 0000000000..a4ff1c8a65 --- /dev/null +++ b/src/libsystemd-network/dhcp-message.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#include "sparse-endian.h" +#include "tlv-util.h" + +typedef struct sd_dhcp_message sd_dhcp_message; + +sd_dhcp_message* sd_dhcp_message_ref(sd_dhcp_message *p); +sd_dhcp_message* sd_dhcp_message_unref(sd_dhcp_message *p); +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_message*, sd_dhcp_message_unref); + +int dhcp_message_new(sd_dhcp_message **ret); + +int dhcp_message_init_header( + sd_dhcp_message *message, + uint8_t op, + uint32_t xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr); + +void dhcp_message_set_broadcast_flag(sd_dhcp_message *message, bool b); +bool dhcp_message_has_broadcast_flag(sd_dhcp_message *message); +int dhcp_message_get_hw_addr(sd_dhcp_message *message, struct hw_addr_data *ret); + +bool dhcp_message_has_option(sd_dhcp_message *message, uint8_t code); +void dhcp_message_remove_option(sd_dhcp_message *message, uint8_t code); + +int dhcp_message_append_option(sd_dhcp_message *message, uint8_t code, size_t length, const void *data); +int dhcp_message_append_option_tlv(sd_dhcp_message *message, const TLV *tlv); +int dhcp_message_append_option_flag(sd_dhcp_message *message, uint8_t code); +int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t data); +int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data); +int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t 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); +int dhcp_message_get_option_flag(sd_dhcp_message *message, uint8_t code); +int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t *ret); +int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret); +int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret); + +int dhcp_message_parse( + const struct iovec *iov, + uint8_t op, + const uint32_t *xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr, + sd_dhcp_message **ret); + +int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret); diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index c2df5a574a..00d232a74e 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -13,6 +13,7 @@ #include "sd-forward.h" #include "sparse-endian.h" #include "time-util.h" +#include "tlv-util.h" /* RFC 8925 - IPv6-Only Preferred Option for DHCPv4 3.4. * MIN_V6ONLY_WAIT: The lower boundary for V6ONLY_WAIT. Value: 300 seconds */ @@ -49,6 +50,13 @@ struct DHCPMessage { typedef struct DHCPMessage DHCPMessage; assert_cc(sizeof(DHCPMessageHeader) == offsetof(DHCPMessage, options)); +struct sd_dhcp_message { + unsigned n_ref; + + DHCPMessageHeader header; + TLV options; +}; + struct DHCPPacket { struct iphdr ip; struct udphdr udp; diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 6239056e3b..b21f138aa6 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -3,6 +3,7 @@ libsystemd_network_sources = files( 'arp-util.c', 'dhcp-client-send.c', + 'dhcp-message.c', 'dhcp-network.c', 'dhcp-option.c', 'dhcp-packet.c', @@ -77,6 +78,9 @@ executables += [ network_test_template + { 'sources' : files('test-dhcp-client.c'), }, + network_test_template + { + 'sources' : files('test-dhcp-message.c'), + }, network_test_template + { 'sources' : files('test-dhcp-option.c'), }, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c new file mode 100644 index 0000000000..407fc0f16a --- /dev/null +++ b/src/libsystemd-network/test-dhcp-message.c @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#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 "tests.h" + +static void verify_header(sd_dhcp_message *m, uint32_t xid, const struct hw_addr_data *hw_addr) { + ASSERT_EQ(be32toh(m->header.xid), xid); + + ASSERT_FALSE(dhcp_message_has_broadcast_flag(m)); + dhcp_message_set_broadcast_flag(m, true); + ASSERT_TRUE(dhcp_message_has_broadcast_flag(m)); + dhcp_message_set_broadcast_flag(m, false); + ASSERT_FALSE(dhcp_message_has_broadcast_flag(m)); + + struct hw_addr_data a; + ASSERT_OK(dhcp_message_get_hw_addr(m, &a)); + ASSERT_TRUE(hw_addr_equal(&a, hw_addr)); +} + +static void verify_flag(sd_dhcp_message *m) { + ASSERT_TRUE(dhcp_message_has_option(m, SD_DHCP_OPTION_RAPID_COMMIT)); + ASSERT_OK(dhcp_message_get_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT)); + ASSERT_ERROR(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_RAPID_COMMIT, NULL), ENODATA); /* size mismatch */ +} + +static void verify_u8(sd_dhcp_message *m, uint8_t expected) { + uint8_t u; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &u)); + ASSERT_EQ(u, expected); +} + +static void verify_u16(sd_dhcp_message *m, uint16_t expected) { + uint16_t u; + ASSERT_OK(dhcp_message_get_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &u)); + ASSERT_EQ(u, expected); +} + +static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a.s_addr)); + ASSERT_EQ(a.s_addr, expected->s_addr); +} + +TEST(dhcp_message) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + + ASSERT_OK(dhcp_message_new(&m)); + ASSERT_NOT_NULL(m); + + uint32_t xid = random_u32(); + + struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, + }; + + /* 192.0.2.42 */ + struct in_addr addr = { .s_addr = htobe32(0xC000022a) }; + + ASSERT_OK(dhcp_message_init_header( + m, + BOOTREQUEST, + xid, + ARPHRD_ETHER, + &hw_addr)); + + /* header */ + verify_header(m, xid, &hw_addr); + + 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); + + /* 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)); + ASSERT_ERROR(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT), EEXIST); + verify_flag(m); + + /* u8 */ + ASSERT_ERROR(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_DISCOVER)); + ASSERT_ERROR(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_REQUEST), EEXIST); + verify_u8(m, DHCP_DISCOVER); + + /* u16 */ + ASSERT_OK(dhcp_message_append_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 512)); + ASSERT_ERROR(dhcp_message_append_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 1024), EEXIST); + ASSERT_ERROR(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 32), EEXIST); + verify_u16(m, 512); + + /* address */ + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr)); + ASSERT_ERROR(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr), EEXIST); + verify_address(m, &addr); + + /* build and parse */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + ASSERT_OK(dhcp_message_build(m, &iovw)); + + _cleanup_(iovec_done) struct iovec joined = {}; + ASSERT_OK(iovw_concat(&iovw, &joined)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m2 = NULL; + ASSERT_OK(dhcp_message_parse( + &joined, + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + &hw_addr, + &m2)); + + ASSERT_EQ(memcmp(&m2->header, &m->header, sizeof(m->header)), 0); + + /* verify parsed message */ + verify_header(m2, xid, &hw_addr); + verify_flag(m2); + verify_u8(m2, DHCP_DISCOVER); + verify_u16(m2, 512); + verify_address(m2, &addr); + + /* build again, and verify the packet */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; + ASSERT_OK(dhcp_message_build(m2, &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 7b97d772a25954d9b35e6856aed323ac54fa468b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 13 Apr 2026 11:23:03 +0900 Subject: [PATCH 2/8] dhcp-message: introduce dhcp_message_{append,get}_option_sec() These are for e.g. DHCP options 51 (lease time), 58 (renewal time), and 59 (rebinding time). --- src/libsystemd-network/dhcp-message.c | 21 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 ++ src/libsystemd-network/test-dhcp-message.c | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index eb43c7e6e5..85976ec6ed 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -9,6 +9,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" +#include "network-common.h" static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { if (!message) @@ -161,6 +162,11 @@ int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32 return dhcp_message_append_option(message, code, sizeof(be32_t), &data); } +int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t usec) { + assert(message); + return dhcp_message_append_option_be32(message, code, usec_to_be32_sec(usec)); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -211,6 +217,21 @@ int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t return dhcp_message_get_option(message, code, sizeof(be32_t), ret); } +int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *ret) { + int r; + + assert(message); + + be32_t t; + r = dhcp_message_get_option_be32(message, code, &t); + if (r < 0) + return r; + + if (ret) + *ret = be32_sec_to_usec(t, max_as_infinity); + 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 a4ff1c8a65..6086cda3c5 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -34,6 +34,7 @@ int dhcp_message_append_option_flag(sd_dhcp_message *message, uint8_t code); int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t data); int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data); int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data); +int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t usec); 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); @@ -41,6 +42,7 @@ int dhcp_message_get_option_flag(sd_dhcp_message *message, uint8_t code); int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t *ret); int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret); int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret); +int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *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 407fc0f16a..3ac8a66d21 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -42,6 +42,18 @@ static void verify_u16(sd_dhcp_message *m, uint16_t expected) { ASSERT_EQ(u, expected); } +static void verify_sec(sd_dhcp_message *m, usec_t expected) { + usec_t t; + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, /* max_as_infinity= */ false, &t)); + ASSERT_EQ(t, expected); + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, /* max_as_infinity= */ true, &t)); + ASSERT_EQ(t, expected); + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_RENEWAL_TIME, /* max_as_infinity= */ false, &t)); + ASSERT_EQ(t, UINT32_MAX * USEC_PER_SEC); + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_RENEWAL_TIME, /* max_as_infinity= */ true, &t)); + ASSERT_EQ(t, USEC_INFINITY); +} + static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { struct in_addr a; ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a.s_addr)); @@ -61,6 +73,8 @@ TEST(dhcp_message) { .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, }; + usec_t lease_time = USEC_PER_DAY; + /* 192.0.2.42 */ struct in_addr addr = { .s_addr = htobe32(0xC000022a) }; @@ -95,6 +109,11 @@ TEST(dhcp_message) { ASSERT_ERROR(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 32), EEXIST); verify_u16(m, 512); + /* sec */ + ASSERT_OK(dhcp_message_append_option_sec(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, lease_time)); + ASSERT_OK(dhcp_message_append_option_sec(m, SD_DHCP_OPTION_RENEWAL_TIME, USEC_INFINITY)); + verify_sec(m, lease_time); + /* address */ ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr)); ASSERT_ERROR(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr), EEXIST); @@ -123,6 +142,7 @@ TEST(dhcp_message) { verify_flag(m2); verify_u8(m2, DHCP_DISCOVER); verify_u16(m2, 512); + verify_sec(m2, lease_time); verify_address(m2, &addr); /* build again, and verify the packet */ From 4b2ff0a00f1aa9ac491d6e73dce6d59af7261f4f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 14:46:59 +0900 Subject: [PATCH 3/8] dhcp-message: introduce dhcp_message_{append,get}_option_address() These are equivalent to dhcp_message_{append,get}_option_be32(), but for type safety. These are for e.g. DHCP options 50 (requested IP address). --- src/libsystemd-network/dhcp-message.c | 11 +++++++++++ src/libsystemd-network/dhcp-message.h | 2 ++ src/libsystemd-network/test-dhcp-message.c | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 85976ec6ed..6344a7a41a 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -167,6 +167,12 @@ int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_ return dhcp_message_append_option_be32(message, code, usec_to_be32_sec(usec)); } +int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr) { + assert(message); + assert(addr); + return dhcp_message_append_option_be32(message, code, addr->s_addr); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -232,6 +238,11 @@ int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max return 0; } +int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret) { + assert(message); + return dhcp_message_get_option_be32(message, code, ret ? &ret->s_addr : NULL); +} + 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 6086cda3c5..e0e5d87588 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -35,6 +35,7 @@ int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_ int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data); int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data); 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_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); @@ -43,6 +44,7 @@ int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t * int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret); int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret); 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_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 3ac8a66d21..92d22d4de1 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -58,6 +58,8 @@ static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { struct in_addr a; ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a.s_addr)); ASSERT_EQ(a.s_addr, expected->s_addr); + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a)); + ASSERT_EQ(a.s_addr, expected->s_addr); } TEST(dhcp_message) { @@ -117,6 +119,9 @@ TEST(dhcp_message) { /* address */ ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr)); ASSERT_ERROR(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr), EEXIST); + ASSERT_ERROR(dhcp_message_append_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &addr), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &addr)); verify_address(m, &addr); /* build and parse */ From 174a61135cad747dfcaa66bb9312fe63e94a8364 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:19:41 +0900 Subject: [PATCH 4/8] dhcp-message: introduce dhcp_message_{append,get}_option_addresses() These functions support multiple addresses. These are for e.g. DHCP options 6 (DNS servers), 42 (NTP servers) and so on. --- src/libsystemd-network/dhcp-message.c | 38 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 ++ src/libsystemd-network/test-dhcp-message.c | 31 ++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 6344a7a41a..816d16955d 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -173,6 +173,19 @@ int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, c return dhcp_message_append_option_be32(message, code, addr->s_addr); } +int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr) { + assert(message); + assert(n_addr == 0 || addr); + + if (n_addr == 0) + return 0; + + if (size_multiply_overflow(sizeof(struct in_addr), n_addr)) + return -ENOBUFS; + + return dhcp_message_append_option(message, code, sizeof(struct in_addr) * n_addr, addr); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -243,6 +256,31 @@ int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, stru return dhcp_message_get_option_be32(message, code, ret ? &ret->s_addr : NULL); } +int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr) { + int r; + + assert(message); + assert(ret_n_addr || !ret_addr); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + if (iov.iov_len % sizeof(struct in_addr) != 0) + return -EBADMSG; + + size_t n = iov.iov_len / sizeof(struct in_addr); + if (n == 0) + return -ENODATA; + + if (ret_addr) + *ret_addr = (struct in_addr*) TAKE_PTR(iov.iov_base); + if (ret_n_addr) + *ret_n_addr = n; + 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 e0e5d87588..6d4e1939b0 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -36,6 +36,7 @@ int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint1 int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data); 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_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); @@ -45,6 +46,7 @@ int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret); 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_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 92d22d4de1..7c1c255f95 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -62,6 +62,23 @@ static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { ASSERT_EQ(a.s_addr, expected->s_addr); } +static void verify_addresses( + sd_dhcp_message *m, + size_t n_ntp, const struct in_addr *ntp) { + + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_NTP_SERVER, &a.s_addr)); + ASSERT_EQ(a.s_addr, ntp->s_addr); + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_NTP_SERVER, &a)); + ASSERT_EQ(a.s_addr, ntp->s_addr); + + size_t n; + _cleanup_free_ struct in_addr *addrs = NULL; + ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, &n, &addrs)); + ASSERT_EQ(n, n_ntp); + ASSERT_EQ(memcmp(addrs, ntp, sizeof(struct in_addr) * n), 0); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -80,6 +97,14 @@ TEST(dhcp_message) { /* 192.0.2.42 */ struct in_addr addr = { .s_addr = htobe32(0xC000022a) }; + /* 192.0.2.1 - 4 */ + struct in_addr ntp[4] = { + { .s_addr = htobe32(0xC0000201) }, + { .s_addr = htobe32(0xC0000202) }, + { .s_addr = htobe32(0xC0000203) }, + { .s_addr = htobe32(0xC0000204) }, + }; + ASSERT_OK(dhcp_message_init_header( m, BOOTREQUEST, @@ -124,6 +149,11 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &addr)); verify_address(m, &addr); + /* multiple addresses */ + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_NTP_SERVER, &ntp[0])); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, ELEMENTSOF(ntp) - 1, ntp + 1)); + verify_addresses(m, ELEMENTSOF(ntp), ntp); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -149,6 +179,7 @@ TEST(dhcp_message) { verify_u16(m2, 512); verify_sec(m2, lease_time); verify_address(m2, &addr); + verify_addresses(m2, ELEMENTSOF(ntp), ntp); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From 1c4ee9e96993d213917c58c5d6ff5578c59311ed Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 14:41:29 +0900 Subject: [PATCH 5/8] 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. --- src/libsystemd-network/dhcp-message.c | 48 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 + src/libsystemd-network/test-dhcp-message.c | 38 +++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 816d16955d..eafc84c179 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 6d4e1939b0..d47d7ba409 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 7c1c255f95..31e0754d93 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 = {}; From a6a6e371f76f5fac2abd6255be076941a2841403 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:51:39 +0900 Subject: [PATCH 6/8] dhcp-message: introduce dhcp_message_{append,get}_option_client_id() These are for DHCP option 61 (client ID). --- src/libsystemd-network/dhcp-message.c | 28 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 3 +++ src/libsystemd-network/test-dhcp-message.c | 17 +++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index eafc84c179..0882422d11 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" @@ -202,6 +203,19 @@ int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, co return dhcp_message_append_option(message, code, strlen(data), data); } +int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id) { + assert(message); + assert(id); + + if (!sd_dhcp_client_id_is_set(id)) + return -EINVAL; + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER)) + return -EEXIST; + + return dhcp_message_append_option(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER, id->size, id->raw); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -329,6 +343,20 @@ int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char return 0; } +int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret) { + int r; + + assert(message); + assert(ret); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER, &iov); + if (r < 0) + return r; + + return sd_dhcp_client_id_set_raw(ret, iov.iov_base, iov.iov_len); +} + 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 d47d7ba409..50c1810c7d 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dhcp-client-id.h" #include "sd-forward.h" #include "sparse-endian.h" @@ -38,6 +39,7 @@ int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, 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_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id); 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); @@ -49,6 +51,7 @@ int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max 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_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *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 31e0754d93..cca5c0603b 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "dhcp-client-id-internal.h" #include "dhcp-message.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" @@ -94,6 +95,12 @@ static void verify_multiple_strings(sd_dhcp_message *m, char * const *expected) ASSERT_STREQ(s, joined); } +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)); + ASSERT_EQ(client_id_compare_func(&id, expected), 0); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -120,6 +127,11 @@ TEST(dhcp_message) { { .s_addr = htobe32(0xC0000204) }, }; + sd_dhcp_client_id id = { + .raw = { 1, 3, 3, 3, 3, 3, 3, }, + .size = 7, + }; + const char *vendor_class = "hogehoge"; char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); @@ -190,6 +202,10 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class)); verify_string(m, vendor_class); + /* client ID */ + ASSERT_OK(dhcp_message_append_option_client_id(m, &id)); + verify_client_id(m, &id); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -218,6 +234,7 @@ TEST(dhcp_message) { verify_address(m2, &addr); verify_addresses(m2, ELEMENTSOF(ntp), ntp); verify_string(m2, vendor_class); + verify_client_id(m2, &id); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From aa9584a0371a4e90b12869e7e7a945d46e4218b6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:47:45 +0900 Subject: [PATCH 7/8] dhcp-message: introduce dhcp_message_{append,get}_option_parameter_request_list() These are for DHCP option 55 (parameter request list). --- src/libsystemd-network/dhcp-message.c | 58 ++++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 2 + src/libsystemd-network/test-dhcp-message.c | 17 +++++++ 3 files changed, 77 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 0882422d11..a2d0cb8c67 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -11,6 +11,8 @@ #include "iovec-wrapper.h" #include "ip-util.h" #include "network-common.h" +#include "set.h" +#include "sort-util.h" #include "string-util.h" static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { @@ -216,6 +218,35 @@ int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp return dhcp_message_append_option(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER, id->size, id->raw); } +static int cmp_uint8(const uint8_t *a, const uint8_t *b) { + assert(a); + assert(b); + + return CMP(*a, *b); +} + +int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl) { + assert(message); + + size_t len = set_size(prl); + if (len == 0) + return 0; + + _cleanup_free_ uint8_t *buf = new(uint8_t, len); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + void *q; + SET_FOREACH(q, prl) + *p++ = PTR_TO_UINT8(q); + + /* Sort the options to make the message reproducible. */ + typesafe_qsort(buf, len, cmp_uint8); + + return dhcp_message_append_option(message, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, len, buf); +} + int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { int r; @@ -357,6 +388,33 @@ int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_i return sd_dhcp_client_id_set_raw(ret, iov.iov_base, iov.iov_len); } +int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret) { + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, &iov); + if (r < 0) + return r; + + if (!iovec_is_set(&iov)) + return -ENODATA; + + if (!ret) + return 0; + + _cleanup_set_free_ Set *prl = NULL; + for (struct iovec i = iov; iovec_is_set(&i); iovec_inc(&i, 1)) { + r = set_ensure_put(&prl, /* hash_ops= */ NULL, UINT8_TO_PTR(*(uint8_t*) i.iov_base)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(prl); + 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 50c1810c7d..4ceba1eec1 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -40,6 +40,7 @@ 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_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_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); @@ -52,6 +53,7 @@ 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_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_parse( const struct iovec *iov, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index cca5c0603b..bdb3ef921a 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -10,6 +10,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "random-util.h" +#include "set.h" #include "strv.h" #include "tests.h" @@ -101,6 +102,12 @@ static void verify_client_id(sd_dhcp_message *m, const sd_dhcp_client_id *expect ASSERT_EQ(client_id_compare_func(&id, expected), 0); } +static void verify_prl(sd_dhcp_message *m, Set *expected) { + _cleanup_set_free_ Set *set = NULL; + ASSERT_OK(dhcp_message_get_option_parameter_request_list(m, &set)); + ASSERT_TRUE(set_equal(set, expected)); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -132,6 +139,10 @@ TEST(dhcp_message) { .size = 7, }; + _cleanup_set_free_ Set *prl = NULL; + 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 *vendor_class = "hogehoge"; char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); @@ -206,6 +217,11 @@ TEST(dhcp_message) { ASSERT_OK(dhcp_message_append_option_client_id(m, &id)); verify_client_id(m, &id); + /* parameter request list */ + ASSERT_OK(dhcp_message_append_option_parameter_request_list(m, prl)); + ASSERT_OK(dhcp_message_append_option_parameter_request_list(m, prl)); + verify_prl(m, prl); + /* build and parse */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; ASSERT_OK(dhcp_message_build(m, &iovw)); @@ -235,6 +251,7 @@ TEST(dhcp_message) { verify_addresses(m2, ELEMENTSOF(ntp), ntp); verify_string(m2, vendor_class); verify_client_id(m2, &id); + verify_prl(m2, prl); /* build again, and verify the packet */ _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; From 28423ea18ceeae387ec4a67229f525b62bddcc3d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 15:42:18 +0900 Subject: [PATCH 8/8] dhcp-message: introduce dhcp_message_{append,get}_option_hostname() and related functions These are for DHCP options 12 (Hostname) and 81 (FQDN) options. --- src/libsystemd-network/dhcp-message.c | 164 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 4 + src/libsystemd-network/test-dhcp-message.c | 22 +++ 3 files changed, 190 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index a2d0cb8c67..92f7d5ed8c 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -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, diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 4ceba1eec1..ad16883892 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -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, diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index bdb3ef921a..816edc9fc6 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -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 = {};