]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
json-util: introduce json_dispatch_in_addr_data
authorNick Rosbrook <enr0n@ubuntu.com>
Tue, 23 Jun 2026 16:02:19 +0000 (12:02 -0400)
committerNick Rosbrook <enr0n@ubuntu.com>
Fri, 26 Jun 2026 13:12:10 +0000 (09:12 -0400)
Generalize json_dispatch_address from nss-resolve, and add support for
strings to be compatible with the existing json_dispatch_{in,in6}_addr
helpers. This will be used in a later commit.

src/libsystemd/sd-json/json-util.c
src/libsystemd/sd-json/json-util.h
src/libsystemd/sd-json/test-json.c

index 485b4a03c8cc5a3cb4d8842b97a469199b62be50..193ee714d0ac5c56d2ee3ded763a5cbc5ba4826a 100644 (file)
@@ -248,6 +248,61 @@ int json_dispatch_in6_addr(const char *name, sd_json_variant *variant, sd_json_d
         return 0;
 }
 
+int json_dispatch_in_addr_data(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+        struct in_addr_data *ret = ASSERT_PTR(userdata), data = { .family = AF_UNSPEC, .address = IN_ADDR_NULL };
+        int r;
+
+        assert(variant);
+
+        if (sd_json_variant_is_null(variant)) {
+                *ret = data;
+                return 0;
+        }
+
+        /* We support both a more human readable string based encoding and an array based encoding */
+        if (sd_json_variant_is_string(variant)) {
+                r = in_addr_from_string_auto(sd_json_variant_string(variant), &data.family, &data.address);
+                if (r < 0)
+                        return json_log(variant, flags, r,
+                                        "JSON field '%s' is not a valid IPv4 or IPv6 address string: %s", strna(name), sd_json_variant_string(variant));
+                *ret = data;
+                return 0;
+        }
+
+        if (!sd_json_variant_is_array(variant))
+                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
+
+        switch (sd_json_variant_elements(variant)) {
+        case sizeof(struct in_addr):
+                data.family = AF_INET;
+                break;
+        case sizeof(struct in6_addr):
+                data.family = AF_INET6;
+                break;
+        default:
+                return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name));
+        }
+
+        size_t k = 0;
+        sd_json_variant *i;
+        JSON_VARIANT_ARRAY_FOREACH(i, variant) {
+                if (!sd_json_variant_is_integer(i))
+                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an integer.", k, strna(name));
+
+                int64_t b = sd_json_variant_integer(i);
+                if (b < 0 || b > 0xff)
+                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
+                                        "Element %zu of JSON field '%s' is out of range 0%s255.",
+                                        k, strna(name), glyph(GLYPH_ELLIPSIS));
+
+                data.address.bytes[k++] = (uint8_t) b;
+        }
+        assert(k == FAMILY_ADDRESS_SIZE_SAFE(data.family));
+
+        *ret = data;
+        return 0;
+}
+
 int json_dispatch_const_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
         const char **p = ASSERT_PTR(userdata), *path;
 
index de7790a6d30901e43befecb6da435cb7827b063a..2032566fdd0c330b6031d692f1863880a7861e25 100644 (file)
@@ -119,6 +119,7 @@ int json_dispatch_const_user_group_name(const char *name, sd_json_variant *varia
 int json_dispatch_const_unit_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
 int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
 int json_dispatch_in6_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+int json_dispatch_in_addr_data(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
 int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
 int json_dispatch_const_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
 int json_dispatch_strv_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
index bb94dfea4b96ee0d82352a72701aa0abf1f47595..6992d095c666f004843c0ec17a1407bf2e1ac7a7 100644 (file)
@@ -13,6 +13,7 @@
 #include "fd-util.h"
 #include "format-util.h"
 #include "fileio.h"
+#include "in-addr-util.h"
 #include "io-util.h"
 #include "iovec-util.h"
 #include "json-internal.h"
@@ -1998,4 +1999,197 @@ TEST(json_dispatch_in6_addr) {
                 ASSERT_EQ(data.addr.s6_addr[i], 0);
 }
 
+TEST(json_dispatch_in_addr_data) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
+
+        /* 192.168.1.1 = { 192, 168, 1, 1 } */
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", JSON_BUILD_IN4_ADDR(&(const struct in_addr) { .s_addr = htobe32(0xC0A80101U) })),
+                                             SD_JSON_BUILD_PAIR("null_addr", SD_JSON_BUILD_NULL))));
+
+        struct {
+                struct in_addr_data addr;
+                struct in_addr_data null_addr;
+        } data = {};
+
+        ASSERT_OK(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr",      _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(typeof(data), addr)      },
+                                        { "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(typeof(data), null_addr) },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &data));
+
+        ASSERT_EQ(be32toh(data.addr.address.in.s_addr), 0xC0A80101U);
+        ASSERT_EQ(data.null_addr.address.in.s_addr, 0U);
+        ASSERT_EQ(data.addr.family, AF_INET);
+        ASSERT_EQ(data.null_addr.family, AF_UNSPEC);
+
+        struct in_addr_data dummy = {};
+
+        /* Too few bytes (3 instead of 4) */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(192), SD_JSON_BUILD_UNSIGNED(168), SD_JSON_BUILD_UNSIGNED(1))))));
+        ASSERT_ERROR(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &dummy), EINVAL);
+
+        /* Too many bytes (5 instead of 4) */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(192), SD_JSON_BUILD_UNSIGNED(168), SD_JSON_BUILD_UNSIGNED(1), SD_JSON_BUILD_UNSIGNED(1), SD_JSON_BUILD_UNSIGNED(0))))));
+        ASSERT_ERROR(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &dummy), EINVAL);
+
+        /* Not an array or string */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                                SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_BOOLEAN(true)))));
+        ASSERT_ERROR(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &dummy), EINVAL);
+
+        /* A string */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", JSON_BUILD_CONST_STRING("192.168.1.1")))));
+        zero(data);
+        ASSERT_OK(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &data));
+        ASSERT_EQ(be32toh(data.addr.address.in.s_addr), 0xC0A80101U);
+        ASSERT_EQ(data.addr.family, AF_INET);
+
+        /* Byte value out of range (> 255) */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(256), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1))))));
+        ASSERT_ERROR(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &dummy), EINVAL);
+
+        /* Negative element */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_INTEGER(-1), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1))))));
+        ASSERT_ERROR(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &dummy), EINVAL);
+
+        /* ::1 */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", JSON_BUILD_IN6_ADDR(&(const struct in6_addr) { .s6_addr = { [15] = 1 } })),
+                                             SD_JSON_BUILD_PAIR("null_addr", SD_JSON_BUILD_NULL))));
+
+        zero(data);
+        ASSERT_OK(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr",      _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(typeof(data), addr)      },
+                                        { "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(typeof(data), null_addr) },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &data));
+
+        ASSERT_EQ(data.addr.address.in6.s6_addr[15], 1);
+        for (size_t i = 0; i < 15; i++)
+                ASSERT_EQ(data.addr.address.in6.s6_addr[i], 0);
+        for (size_t i = 0; i < 16; i++)
+                ASSERT_EQ(data.null_addr.address.in6.s6_addr[i], 0);
+        ASSERT_EQ(data.addr.family, AF_INET6);
+        ASSERT_EQ(data.null_addr.family, AF_UNSPEC);
+
+        /* Too few bytes (15 instead of 16) */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(
+                                                                                SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
+                                                                                SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
+                                                                                SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
+                                                                                SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1))))));
+        ASSERT_ERROR(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &dummy), EINVAL);
+
+        /* Too many bytes (17 instead of 16) */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(
+                                                                                SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
+                                                                                SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
+                                                                                SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
+                                                                                SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1),
+                                                                                SD_JSON_BUILD_UNSIGNED(0))))));
+        ASSERT_ERROR(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &dummy), EINVAL);
+
+        /* Not an array */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_BOOLEAN(true)))));
+        ASSERT_ERROR(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &dummy), EINVAL);
+
+        /* A string */
+        j = sd_json_variant_unref(j);
+        ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("addr", JSON_BUILD_CONST_STRING("::1")))));
+
+        zero(data);
+        ASSERT_OK(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, 0 },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &data));
+
+        ASSERT_EQ(data.addr.address.in6.s6_addr[15], 1);
+        for (size_t i = 0; i < 15; i++)
+                ASSERT_EQ(data.addr.address.in6.s6_addr[i], 0);
+        ASSERT_EQ(data.addr.family, AF_INET6);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);