]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dhcp-message: allow to serialize/deserialize sd_dhcp_message object
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 24 Mar 2026 20:25:04 +0000 (05:25 +0900)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 15 May 2026 12:23:58 +0000 (14:23 +0200)
By using this, we can expose received DHCP message in Describe()
DBus/Varlink methods. Preparation for later change.

src/libsystemd-network/dhcp-message.c
src/libsystemd-network/dhcp-message.h
src/libsystemd-network/test-dhcp-message.c
src/libsystemd-network/tlv-util.c
src/libsystemd-network/tlv-util.h

index e0ef4f06fb481b4c67644785fd66a628b974182d..07664588301de0d91dff0d9d7568d966819828d8 100644 (file)
@@ -17,6 +17,7 @@
 #include "iovec-util.h"
 #include "iovec-wrapper.h"
 #include "ip-util.h"
+#include "json-util.h"
 #include "network-common.h"
 #include "set.h"
 #include "sort-util.h"
@@ -1459,3 +1460,188 @@ int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret) {
         *ret = TAKE_STRUCT(iovw);
         return 0;
 }
+
+int dhcp_message_build_json(sd_dhcp_message *message, sd_json_variant **ret) {
+        int r;
+
+        assert(message);
+        assert(message->header.hlen <= sizeof(message->header.chaddr));
+        assert(ret);
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        r = sd_json_buildo(
+                        &v,
+                        SD_JSON_BUILD_PAIR_UNSIGNED("op", message->header.op),
+                        SD_JSON_BUILD_PAIR_UNSIGNED("htype", message->header.htype),
+                        SD_JSON_BUILD_PAIR_UNSIGNED("hops", message->header.hops),
+                        SD_JSON_BUILD_PAIR_UNSIGNED("xid", be32toh(message->header.xid)),
+                        SD_JSON_BUILD_PAIR_UNSIGNED("secs", be16toh(message->header.secs)),
+                        SD_JSON_BUILD_PAIR_UNSIGNED("flags", be16toh(message->header.flags)),
+                        JSON_BUILD_PAIR_HEX_NON_EMPTY("ciaddr", &message->header.ciaddr, sizeof(message->header.ciaddr)),
+                        JSON_BUILD_PAIR_HEX_NON_EMPTY("yiaddr", &message->header.yiaddr, sizeof(message->header.yiaddr)),
+                        JSON_BUILD_PAIR_HEX_NON_EMPTY("siaddr", &message->header.siaddr, sizeof(message->header.siaddr)),
+                        JSON_BUILD_PAIR_HEX_NON_EMPTY("giaddr", &message->header.giaddr, sizeof(message->header.giaddr)),
+                        JSON_BUILD_PAIR_HEX_NON_EMPTY("chaddr", message->header.chaddr, message->header.hlen));
+        if (r < 0)
+                return r;
+
+        uint8_t overload = DHCP_OVERLOAD_NONE;
+        (void) dhcp_message_get_option_u8(message, SD_DHCP_OPTION_OVERLOAD, &overload);
+
+        if (!FLAGS_SET(overload, DHCP_OVERLOAD_SNAME) && !eqzero(message->header.sname)) {
+                r = sd_json_variant_merge_objectbo(
+                                &v,
+                                JSON_BUILD_PAIR_HEX_NON_EMPTY("sname", message->header.sname, sizeof(message->header.sname)));
+                if (r < 0)
+                        return r;
+        }
+
+        if (!FLAGS_SET(overload, DHCP_OVERLOAD_FILE) && !eqzero(message->header.file)) {
+                r = sd_json_variant_merge_objectbo(
+                                &v,
+                                JSON_BUILD_PAIR_HEX_NON_EMPTY("file", message->header.file, sizeof(message->header.file)));
+                if (r < 0)
+                        return r;
+        }
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
+        r = tlv_build_json(&message->options, &w);
+        if (r < 0)
+                return r;
+
+        r = sd_json_variant_merge_objectbo(
+                        &v,
+                        JSON_BUILD_PAIR_VARIANT_NON_NULL("options", w));
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(v);
+        return 0;
+}
+
+typedef struct MessageParam {
+        uint8_t op;
+        uint8_t htype;
+        uint8_t hops;
+        uint32_t xid;
+        uint16_t secs;
+        uint16_t flags;
+        struct iovec ciaddr;
+        struct iovec yiaddr;
+        struct iovec siaddr;
+        struct iovec giaddr;
+        struct iovec chaddr;
+        struct iovec sname;
+        struct iovec file;
+        TLV *options;
+} MessageParam;
+
+static void message_param_done(MessageParam *p) {
+        assert(p);
+
+        iovec_done(&p->ciaddr);
+        iovec_done(&p->yiaddr);
+        iovec_done(&p->siaddr);
+        iovec_done(&p->giaddr);
+        iovec_done(&p->chaddr);
+        iovec_done(&p->sname);
+        iovec_done(&p->file);
+        tlv_unref(p->options);
+}
+
+static int dispatch_options(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) {
+        TLV **options = ASSERT_PTR(userdata);
+        int r;
+
+        if (*options)
+                return -EINVAL; /* multiple options field? */
+
+        _cleanup_(tlv_unrefp) TLV *tlv = tlv_new(TLV_DHCP4);
+        if (!tlv)
+                return -ENOMEM;
+
+        r = tlv_parse_json(tlv, v);
+        if (r < 0)
+                return r;
+
+        *options = TAKE_PTR(tlv);
+        return 0;
+}
+
+int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret) {
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "op",      _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8,    offsetof(MessageParam, op),      SD_JSON_MANDATORY },
+                { "htype",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8,    offsetof(MessageParam, htype),   0                 },
+                { "hops",    _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8,    offsetof(MessageParam, hops),    0                 },
+                { "xid",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32,   offsetof(MessageParam, xid),     0                 },
+                { "secs",    _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16,   offsetof(MessageParam, secs),    0                 },
+                { "flags",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16,   offsetof(MessageParam, flags),   0                 },
+                { "ciaddr",  SD_JSON_VARIANT_STRING,        json_dispatch_unhex_iovec, offsetof(MessageParam, ciaddr),  0                 },
+                { "yiaddr",  SD_JSON_VARIANT_STRING,        json_dispatch_unhex_iovec, offsetof(MessageParam, yiaddr),  0                 },
+                { "siaddr",  SD_JSON_VARIANT_STRING,        json_dispatch_unhex_iovec, offsetof(MessageParam, siaddr),  0                 },
+                { "giaddr",  SD_JSON_VARIANT_STRING,        json_dispatch_unhex_iovec, offsetof(MessageParam, giaddr),  0                 },
+                { "chaddr",  SD_JSON_VARIANT_STRING,        json_dispatch_unhex_iovec, offsetof(MessageParam, chaddr),  0                 },
+                { "sname",   SD_JSON_VARIANT_STRING,        json_dispatch_unhex_iovec, offsetof(MessageParam, sname),   0                 },
+                { "file",    SD_JSON_VARIANT_STRING,        json_dispatch_unhex_iovec, offsetof(MessageParam, file),    0                 },
+                { "options", SD_JSON_VARIANT_ARRAY,         dispatch_options,          offsetof(MessageParam, options), 0                 },
+                {},
+        };
+
+        int r;
+
+        assert(v);
+        assert(ret);
+
+        _cleanup_(message_param_done) MessageParam p = {};
+        r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
+        if (r < 0)
+                return r;
+
+        if (!IN_SET(p.op, BOOTREQUEST, BOOTREPLY))
+                return -EINVAL;
+        if (!iovec_is_valid(&p.ciaddr) || !IN_SET(p.ciaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.ciaddr)))
+                return -EINVAL;
+        if (!iovec_is_valid(&p.yiaddr) || !IN_SET(p.yiaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.yiaddr)))
+                return -EINVAL;
+        if (!iovec_is_valid(&p.siaddr) || !IN_SET(p.siaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.siaddr)))
+                return -EINVAL;
+        if (!iovec_is_valid(&p.giaddr) || !IN_SET(p.giaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.giaddr)))
+                return -EINVAL;
+        if (!iovec_is_valid(&p.chaddr) || p.chaddr.iov_len > sizeof_field(sd_dhcp_message, header.chaddr))
+                return -EINVAL;
+        if (!iovec_is_valid(&p.sname) || p.sname.iov_len > sizeof_field(sd_dhcp_message, header.sname))
+                return -EINVAL;
+        if (!iovec_is_valid(&p.file) || p.file.iov_len > sizeof_field(sd_dhcp_message, header.file))
+                return -EINVAL;
+
+        _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL;
+        r = dhcp_message_new(&message);
+        if (r < 0)
+                return r;
+
+        message->header = (DHCPMessageHeader) {
+                .op = p.op,
+                .htype = p.htype,
+                .hlen = p.chaddr.iov_len,
+                .hops = p.hops,
+                .xid = htobe32(p.xid),
+                .secs = htobe16(p.secs),
+                .flags = htobe16(p.flags),
+                .magic = htobe32(DHCP_MAGIC_COOKIE),
+        };
+
+        memcpy_safe(&message->header.ciaddr, p.ciaddr.iov_base, p.ciaddr.iov_len);
+        memcpy_safe(&message->header.yiaddr, p.yiaddr.iov_base, p.yiaddr.iov_len);
+        memcpy_safe(&message->header.siaddr, p.siaddr.iov_base, p.siaddr.iov_len);
+        memcpy_safe(&message->header.giaddr, p.giaddr.iov_base, p.giaddr.iov_len);
+        memcpy_safe(message->header.chaddr, p.chaddr.iov_base, p.chaddr.iov_len);
+        memcpy_safe(message->header.sname, p.sname.iov_base, p.sname.iov_len);
+        memcpy_safe(message->header.file, p.file.iov_base, p.file.iov_len);
+        if (p.options) {
+                message->options = TAKE_STRUCT(*p.options);
+                p.options = mfree(p.options);
+        }
+
+        *ret = TAKE_PTR(message);
+        return 0;
+}
index e003d9cc5bf20ea98b40ce8668b8bf0dddf841f7..41b22d248809231cd024f743099718111bb57020 100644 (file)
@@ -90,3 +90,6 @@ int dhcp_message_parse(
                 sd_dhcp_message **ret);
 
 int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret);
+
+int dhcp_message_build_json(sd_dhcp_message *message, sd_json_variant **ret);
+int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret);
index 7f5d6669a94956d5a79671b4f06eb4b202ba5b6f..5194cffcc24ecfed554811107fcf55919ca2d5c5 100644 (file)
@@ -2,6 +2,8 @@
 
 #include <net/if_arp.h>
 
+#include "sd-json.h"
+
 #include "alloc-util.h"
 #include "dhcp-client-id-internal.h"
 #include "dhcp-message.h"
@@ -439,6 +441,17 @@ TEST(dhcp_message) {
         _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {};
         ASSERT_OK(dhcp_message_build(m2, &iovw2));
         ASSERT_TRUE(iovw_equal(&iovw, &iovw2));
+
+        /* json */
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        ASSERT_OK(dhcp_message_build_json(m, &v));
+
+        _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m3 = NULL;
+        ASSERT_OK(dhcp_message_parse_json(v, &m3));
+
+        _cleanup_(iovw_done_free) struct iovec_wrapper iovw3 = {};
+        ASSERT_OK(dhcp_message_build(m3, &iovw3));
+        ASSERT_TRUE(iovw_equal(&iovw, &iovw3));
 }
 
 static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) {
index 68574e718c741a92cba589e3e5f26d39a62b393e..488b6c5bd1e423ed3fad978574c6ded5ab553f38 100644 (file)
@@ -4,6 +4,7 @@
 #include "hashmap.h"
 #include "iovec-util.h"
 #include "iovec-wrapper.h"
+#include "json-util.h"
 #include "tlv-util.h"
 #include "unaligned.h"
 
@@ -503,3 +504,71 @@ int tlv_build(const TLV *tlv, struct iovec *ret) {
         *ret = IOVEC_MAKE(TAKE_PTR(buf), sz);
         return 0;
 }
+
+int tlv_build_json(const TLV *tlv, sd_json_variant **ret) {
+        int r;
+
+        assert(tlv);
+        assert(ret);
+
+        /* Sort by tags, for reproducibility. */
+        _cleanup_free_ void **sorted = NULL;
+        size_t n;
+        r = hashmap_dump_keys_sorted(tlv->entries, &sorted, &n);
+        if (r < 0)
+                return r;
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        FOREACH_ARRAY(tagp, sorted, n) {
+                uint32_t tag = PTR_TO_UINT32(*tagp);
+                struct iovec_wrapper *iovw = ASSERT_PTR(tlv_get_all(tlv, tag));
+
+                FOREACH_ARRAY(iov, iovw->iovec, iovw->count) {
+                        r = sd_json_variant_append_arraybo(
+                                        &v,
+                                        SD_JSON_BUILD_PAIR_UNSIGNED("tag", tag),
+                                        JSON_BUILD_PAIR_IOVEC_HEX("data", iov));
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        *ret = TAKE_PTR(v);
+        return 0;
+}
+
+typedef struct TLVParam {
+        uint32_t tag;
+        struct iovec data;
+} TLVParam;
+
+static void tlv_param_done(TLVParam *p) {
+        iovec_done(&p->data);
+}
+
+int tlv_parse_json(TLV *tlv, sd_json_variant *v) {
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "tag",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32,   offsetof(TLVParam, tag),  SD_JSON_MANDATORY },
+                { "data", SD_JSON_VARIANT_STRING,        json_dispatch_unhex_iovec, offsetof(TLVParam, data), SD_JSON_MANDATORY },
+                {},
+        };
+
+        int r;
+
+        assert(tlv);
+        assert(v);
+
+        sd_json_variant *e;
+        JSON_VARIANT_ARRAY_FOREACH(e, v) {
+                _cleanup_(tlv_param_done) TLVParam p = {};
+                r = sd_json_dispatch(e, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
+                if (r < 0)
+                        return r;
+
+                r = tlv_append(tlv, p.tag, p.data.iov_len, p.data.iov_base);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
index 5344c28703244b665e8f4c46b9b2baa356681120..1bdcadccb805a7719809a2048efda88666def104 100644 (file)
@@ -80,3 +80,6 @@ int tlv_append_tlv(TLV *tlv, const TLV *source);
 int tlv_parse(TLV *tlv, const struct iovec *iov);
 size_t tlv_size(const TLV *tlv);
 int tlv_build(const TLV *tlv, struct iovec *ret);
+
+int tlv_build_json(const TLV *tlv, sd_json_variant **ret);
+int tlv_parse_json(TLV *tlv, sd_json_variant *v);