From: Yu Watanabe Date: Tue, 24 Mar 2026 20:25:04 +0000 (+0900) Subject: dhcp-message: allow to serialize/deserialize sd_dhcp_message object X-Git-Tag: v261-rc1~156 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c5997b2bbfd1efee997a7fe297c060d26b6b9757;p=thirdparty%2Fsystemd.git dhcp-message: allow to serialize/deserialize sd_dhcp_message object By using this, we can expose received DHCP message in Describe() DBus/Varlink methods. Preparation for later change. --- diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index e0ef4f06fb4..07664588301 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -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; +} diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index e003d9cc5bf..41b22d24880 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -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); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 7f5d6669a94..5194cffcc24 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -2,6 +2,8 @@ #include +#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) { diff --git a/src/libsystemd-network/tlv-util.c b/src/libsystemd-network/tlv-util.c index 68574e718c7..488b6c5bd1e 100644 --- a/src/libsystemd-network/tlv-util.c +++ b/src/libsystemd-network/tlv-util.c @@ -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; +} diff --git a/src/libsystemd-network/tlv-util.h b/src/libsystemd-network/tlv-util.h index 5344c287032..1bdcadccb80 100644 --- a/src/libsystemd-network/tlv-util.h +++ b/src/libsystemd-network/tlv-util.h @@ -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);