]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dhcp-message: introduce sd_dhcp_message object and several functions for the object
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 22 Mar 2026 07:19:06 +0000 (16:19 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 12 May 2026 15:27:49 +0000 (00:27 +0900)
src/libsystemd-network/dhcp-message.c [new file with mode: 0644]
src/libsystemd-network/dhcp-message.h [new file with mode: 0644]
src/libsystemd-network/dhcp-protocol.h
src/libsystemd-network/meson.build
src/libsystemd-network/test-dhcp-message.c [new file with mode: 0644]

diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c
new file mode 100644 (file)
index 0000000..eb43c7e
--- /dev/null
@@ -0,0 +1,349 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <net/if_arp.h>
+
+#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 (file)
index 0000000..a4ff1c8
--- /dev/null
@@ -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);
index c2df5a574a89e5bf646f85a9f77d1e3766ebad44..00d232a74eacd40c0907c07918ae8ed5df0c23e3 100644 (file)
@@ -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;
index 6239056e3b4b1250e9a23daadbcdfaaf6ca9c8ef..b21f138aa6bc9edfa2f69a9839f9f937cf92e16f 100644 (file)
@@ -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 (file)
index 0000000..407fc0f
--- /dev/null
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <net/if_arp.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 "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);