]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
networkd-json: add human-readable address strings for IP addresses
authorgvenugo3 <gvenugo3@asu.edu>
Fri, 23 Jan 2026 02:38:30 +0000 (19:38 -0700)
committerLennart Poettering <lennart@poettering.net>
Wed, 4 Feb 2026 09:34:30 +0000 (10:34 +0100)
Add JSON_BUILD_PAIR_IN_ADDR_WITH_STRING, JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING,
and JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING macros (plus _NON_NULL variants) that
emit both the binary address array and a human-readable string representation.

For example, an address field now emits both:
  "Address": [192, 168, 1, 1]
  "AddressString": "192.168.1.1"

This improves usability of the JSON/Varlink output for debugging and tooling
that consumes networkd state.

Updated networkd-json.c to use these new macros for addresses, neighbors,
nexthops, routes, routing policy rules, DNS servers, NTP servers, SIP servers,
domains, DHCPv6 prefixes, and link-local addresses.

Also updated the Varlink interface definitions to include the new string fields.

src/libsystemd-network/sd-dhcp-server-lease.c
src/libsystemd/sd-json/json-util.h
src/libsystemd/sd-json/sd-json.c
src/network/networkd-json.c
src/shared/varlink-io.systemd.Network.c

index b8807391bdc65170f58edd58e5c4ad609b87666f..171d809faabb566bef92258ed6d218016a0b8ce4 100644 (file)
@@ -244,7 +244,7 @@ static int dhcp_server_lease_append_json(sd_dhcp_server_lease *lease, sd_json_va
         return sd_json_buildo(
                         ret,
                         SD_JSON_BUILD_PAIR_BYTE_ARRAY("ClientId", lease->client_id.raw, lease->client_id.size),
-                        JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &(struct in_addr) { .s_addr = lease->address }),
+                        JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL("Address", &(struct in_addr) { .s_addr = lease->address }),
                         JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname),
                         SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressType", lease->htype),
                         SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressLength", lease->hlen),
index 12c11a316a724be9c1644d31e49d0d277b9e0e0e..334a2506dc885f7cf0116ac45629fd8180e73d2e 100644 (file)
@@ -190,6 +190,12 @@ enum {
         _JSON_BUILD_PAIR_TRISTATE_NON_NULL,
         _JSON_BUILD_PAIR_PIDREF_NON_NULL,
         _JSON_BUILD_PAIR_DEVNUM,
+        _JSON_BUILD_PAIR_IN_ADDR_WITH_STRING,
+        _JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING,
+        _JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING,
+        _JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL,
+        _JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING_NON_NULL,
+        _JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL,
 
         _SD_JSON_BUILD_REALLYMAX,
 };
@@ -237,6 +243,12 @@ enum {
 #define JSON_BUILD_PAIR_OCTESCAPE_NON_EMPTY(name, v, n) _JSON_BUILD_PAIR_HEX_NON_EMPTY, (const char*) { name }, (const void*) { v }, (size_t) { n }
 #define JSON_BUILD_PAIR_TRISTATE_NON_NULL(name, i) _JSON_BUILD_PAIR_TRISTATE_NON_NULL, (const char*) { name }, (int) { i }
 #define JSON_BUILD_PAIR_PIDREF_NON_NULL(name, p) _JSON_BUILD_PAIR_PIDREF_NON_NULL, (const char*) { name }, (const PidRef*) { p }
+#define JSON_BUILD_PAIR_IN_ADDR_WITH_STRING(name, f, v) _JSON_BUILD_PAIR_IN_ADDR_WITH_STRING, (const char*) { name }, (int) { f }, (const union in_addr_union*) { v }
+#define JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING(name, v) _JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING, (const char*) { name }, (const struct in6_addr*) { v }
+#define JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING(name, v) _JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING, (const char*) { name }, (const struct in_addr*) { v }
+#define JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL(name, f, v) _JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL, (const char*) { name }, (int) { f }, (const union in_addr_union*) { v }
+#define JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING_NON_NULL(name, v) _JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING_NON_NULL, (const char*) { name }, (const struct in6_addr*) { v }
+#define JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL(name, v) _JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL, (const char*) { name }, (const struct in_addr*) { v }
 
 #define JSON_BUILD_PAIR_IOVEC_BASE64(name, iov) SD_JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_BASE64(iov))
 #define JSON_BUILD_PAIR_IOVEC_HEX(name, iov) SD_JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_HEX(iov))
index c18807444de2e962f4ab949e700d11ecf7b23ff1..f263ab674002ba437547f21ff869a1adae6e70dc 100644 (file)
@@ -4741,7 +4741,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                 if (r < 0)
                                         goto finish;
 
-                                r = sd_json_variant_new_array_bytes(&add_more, a->bytes, FAMILY_ADDRESS_SIZE(f));
+                                r = sd_json_variant_new_array_bytes(&add_more, a->bytes, FAMILY_ADDRESS_SIZE_SAFE(f));
                                 if (r < 0)
                                         goto finish;
                         }
@@ -4896,6 +4896,207 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                         break;
                 }
 
+                case _JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL:
+                case _JSON_BUILD_PAIR_IN_ADDR_WITH_STRING: {
+                        const union in_addr_union *a;
+                        const char *n;
+                        int f;
+
+                        if (current->expect != EXPECT_OBJECT_KEY) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        n = va_arg(ap, const char *);
+                        f = va_arg(ap, int);
+                        a = va_arg(ap, const union in_addr_union *);
+
+                        if (current->n_suppress == 0) {
+                                bool have_address = a && in_addr_is_set(f, a);
+
+                                if (have_address || command != _JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL) {
+                                        _cleanup_free_ char *addr_str = NULL, *string_key_name = NULL;
+                                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *string_key = NULL, *string_value = NULL;
+
+                                        /* For non-NON_NULL variant, always convert address to string (even if all zeros).
+                                         * For NON_NULL variant, we only get here when have_address is true. */
+                                        if (a) {
+                                                r = in_addr_to_string(f, a, &addr_str);
+                                                if (r < 0)
+                                                        goto finish;
+                                        }
+
+                                        string_key_name = strjoin(n, "String");
+                                        if (!string_key_name) {
+                                                r = -ENOMEM;
+                                                goto finish;
+                                        }
+
+                                        r = sd_json_variant_new_string(&add, n);
+                                        if (r < 0)
+                                                goto finish;
+
+                                        r = sd_json_variant_new_array_bytes(&add_more, a->bytes, FAMILY_ADDRESS_SIZE_SAFE(f));
+                                        if (r < 0)
+                                                goto finish;
+
+                                        r = sd_json_variant_new_string(&string_key, string_key_name);
+                                        if (r < 0)
+                                                goto finish;
+
+                                        r = sd_json_variant_new_string(&string_value, addr_str);
+                                        if (r < 0)
+                                                goto finish;
+
+                                        if (!GREEDY_REALLOC(current->elements, current->n_elements + 2)) {
+                                                r = -ENOMEM;
+                                                goto finish;
+                                        }
+
+                                        current->elements[current->n_elements++] = TAKE_PTR(string_key);
+                                        current->elements[current->n_elements++] = TAKE_PTR(string_value);
+                                }
+                        }
+
+                        n_subtract = 4; /* we generated two pairs (binary and string) */
+
+                        current->expect = EXPECT_OBJECT_KEY;
+                        break;
+                }
+
+                case _JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING_NON_NULL:
+                case _JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING: {
+                        const struct in6_addr *a;
+                        const char *n;
+
+                        if (current->expect != EXPECT_OBJECT_KEY) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        n = va_arg(ap, const char *);
+                        a = va_arg(ap, const struct in6_addr *);
+
+                        if (current->n_suppress == 0) {
+                                bool have_address = a && in6_addr_is_set(a);
+
+                                if (have_address || command != _JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING_NON_NULL) {
+                                        _cleanup_free_ char *addr_str = NULL, *string_key_name = NULL;
+                                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *string_key = NULL, *string_value = NULL;
+
+                                        /* For non-NON_NULL variant, always convert address to string (even if all zeros).
+                                         * For NON_NULL variant, we only get here when have_address is true. */
+                                        if (a) {
+                                                r = in6_addr_to_string(a, &addr_str);
+                                                if (r < 0)
+                                                        goto finish;
+                                        }
+
+                                        string_key_name = strjoin(n, "String");
+                                        if (!string_key_name) {
+                                                r = -ENOMEM;
+                                                goto finish;
+                                        }
+
+                                        r = sd_json_variant_new_string(&add, n);
+                                        if (r < 0)
+                                                goto finish;
+
+                                        r = sd_json_variant_new_array_bytes(&add_more, a, sizeof(struct in6_addr));
+                                        if (r < 0)
+                                                goto finish;
+
+                                        r = sd_json_variant_new_string(&string_key, string_key_name);
+                                        if (r < 0)
+                                                goto finish;
+
+                                        r = sd_json_variant_new_string(&string_value, addr_str);
+                                        if (r < 0)
+                                                goto finish;
+
+                                        if (!GREEDY_REALLOC(current->elements, current->n_elements + 2)) {
+                                                r = -ENOMEM;
+                                                goto finish;
+                                        }
+
+                                        current->elements[current->n_elements++] = TAKE_PTR(string_key);
+                                        current->elements[current->n_elements++] = TAKE_PTR(string_value);
+                                }
+                        }
+
+                        n_subtract = 4; /* we generated two pairs (binary and string) */
+
+                        current->expect = EXPECT_OBJECT_KEY;
+                        break;
+                }
+
+                case _JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL:
+                case _JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING: {
+                        const struct in_addr *a;
+                        const char *n;
+
+                        if (current->expect != EXPECT_OBJECT_KEY) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        n = va_arg(ap, const char *);
+                        a = va_arg(ap, const struct in_addr *);
+
+                        if (current->n_suppress == 0) {
+                                bool have_address = a && !in4_addr_is_null(a);
+
+                                if (have_address || command != _JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL) {
+                                        _cleanup_free_ char *addr_str = NULL, *string_key_name = NULL;
+                                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *string_key = NULL, *string_value = NULL;
+
+                                        /* For non-NON_NULL variant, always convert address to string (even if all zeros).
+                                         * For NON_NULL variant, we only get here when have_address is true. */
+                                        if (a) {
+                                                union in_addr_union addr_union = { .in = *a };
+                                                r = in_addr_to_string(AF_INET, &addr_union, &addr_str);
+                                                if (r < 0)
+                                                        goto finish;
+                                        }
+
+                                        string_key_name = strjoin(n, "String");
+                                        if (!string_key_name) {
+                                                r = -ENOMEM;
+                                                goto finish;
+                                        }
+
+                                        r = sd_json_variant_new_string(&add, n);
+                                        if (r < 0)
+                                                goto finish;
+
+                                        r = sd_json_variant_new_array_bytes(&add_more, a, sizeof(struct in_addr));
+                                        if (r < 0)
+                                                goto finish;
+
+                                        r = sd_json_variant_new_string(&string_key, string_key_name);
+                                        if (r < 0)
+                                                goto finish;
+
+                                        r = sd_json_variant_new_string(&string_value, addr_str);
+                                        if (r < 0)
+                                                goto finish;
+
+                                        if (!GREEDY_REALLOC(current->elements, current->n_elements + 2)) {
+                                                r = -ENOMEM;
+                                                goto finish;
+                                        }
+
+                                        current->elements[current->n_elements++] = TAKE_PTR(string_key);
+                                        current->elements[current->n_elements++] = TAKE_PTR(string_value);
+                                }
+                        }
+
+                        n_subtract = 4; /* we generated two pairs (binary and string) */
+
+                        current->expect = EXPECT_OBJECT_KEY;
+                        break;
+                }
+
                 case _JSON_BUILD_PAIR_CALLBACK_NON_NULL: {
                         sd_json_build_callback_t cb;
                         void *userdata;
index cd09ed29c9741bf46311b8a0180577ff79050b24..584f07249d4b2771ec7911bb47cb106cb39441e8 100644 (file)
@@ -10,6 +10,7 @@
 #include "dhcp-server-lease-internal.h"
 #include "dhcp6-lease-internal.h"
 #include "extract-word.h"
+#include "in-addr-util.h"
 #include "ip-protocol-list.h"
 #include "json-util.h"
 #include "netif-util.h"
@@ -42,11 +43,11 @@ static int address_append_json(Address *address, bool serializing, sd_json_varia
         r = sd_json_buildo(
                         &v,
                         SD_JSON_BUILD_PAIR_INTEGER("Family", address->family),
-                        JSON_BUILD_PAIR_IN_ADDR("Address", &address->in_addr, address->family),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Peer", &address->in_addr_peer, address->family),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING("Address", address->family, &address->in_addr),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("Peer", address->family, &address->in_addr_peer),
                         SD_JSON_BUILD_PAIR_UNSIGNED("PrefixLength", address->prefixlen),
                         SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(address->source)),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &address->provider, address->family));
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("ConfigProvider", address->family, &address->provider));
         if (r < 0)
                 return r;
 
@@ -67,7 +68,7 @@ static int address_append_json(Address *address, bool serializing, sd_json_varia
 
                 r = sd_json_variant_merge_objectbo(
                                 &v,
-                                JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Broadcast", &address->broadcast),
+                                JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL("Broadcast", &address->broadcast),
                                 SD_JSON_BUILD_PAIR_UNSIGNED("Scope", address->scope),
                                 SD_JSON_BUILD_PAIR_STRING("ScopeString", scope),
                                 SD_JSON_BUILD_PAIR_UNSIGNED("Flags", address->flags),
@@ -125,7 +126,7 @@ static int neighbor_append_json(Neighbor *n, sd_json_variant **array) {
         return sd_json_variant_append_arraybo(
                         array,
                         SD_JSON_BUILD_PAIR_INTEGER("Family", n->dst_addr.family),
-                        JSON_BUILD_PAIR_IN_ADDR("Destination", &n->dst_addr.address, n->dst_addr.family),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING("Destination", n->dst_addr.family, &n->dst_addr.address),
                         JSON_BUILD_PAIR_HW_ADDR("LinkLayerAddress", &n->ll_addr),
                         SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(n->source)),
                         SD_JSON_BUILD_PAIR_STRING("ConfigState", state));
@@ -180,7 +181,7 @@ static int nexthop_append_json(NextHop *n, bool serializing, sd_json_variant **a
                         SD_JSON_BUILD_PAIR_UNSIGNED("ID", n->id),
                         SD_JSON_BUILD_PAIR_INTEGER("Family", n->family),
                         SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(n->source)),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &n->provider, n->family));
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("ConfigProvider", n->family, &n->provider));
         if (r < 0)
                 return r;
 
@@ -206,7 +207,7 @@ static int nexthop_append_json(NextHop *n, bool serializing, sd_json_variant **a
 
                 r = sd_json_variant_merge_objectbo(
                                 &v,
-                                JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &n->gw.address, n->family),
+                                JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("Gateway", n->family, &n->gw.address),
                                 SD_JSON_BUILD_PAIR_UNSIGNED("Flags", n->flags),
                                 SD_JSON_BUILD_PAIR_STRING("FlagsString", strempty(flags)),
                                 SD_JSON_BUILD_PAIR_UNSIGNED("Protocol", n->protocol),
@@ -260,13 +261,12 @@ static int route_append_json(Route *route, bool serializing, sd_json_variant **a
         r = sd_json_buildo(
                         &v,
                         SD_JSON_BUILD_PAIR_INTEGER("Family", route->family),
-                        JSON_BUILD_PAIR_IN_ADDR("Destination", &route->dst, route->family),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING("Destination", route->family, &route->dst),
                         SD_JSON_BUILD_PAIR_UNSIGNED("DestinationPrefixLength", route->dst_prefixlen),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &route->nexthop.gw, route->nexthop.family),
-                        SD_JSON_BUILD_PAIR_CONDITION(route->src_prefixlen > 0,
-                                                     "Source", JSON_BUILD_IN_ADDR(&route->src, route->family)),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("Gateway", route->nexthop.family, &route->nexthop.gw),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("Source", route->family, route->src_prefixlen > 0 ? &route->src : NULL),
                         JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("SourcePrefixLength", route->src_prefixlen),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("PreferredSource", &route->prefsrc, route->family),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("PreferredSource", route->family, &route->prefsrc),
                         SD_JSON_BUILD_PAIR_UNSIGNED("TOS", route->tos),
                         SD_JSON_BUILD_PAIR_UNSIGNED("Scope", route->scope),
                         SD_JSON_BUILD_PAIR_UNSIGNED("Protocol", route->protocol),
@@ -276,7 +276,7 @@ static int route_append_json(Route *route, bool serializing, sd_json_variant **a
                         SD_JSON_BUILD_PAIR_UNSIGNED("Flags", route->flags),
                         JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NextHopID", route->nexthop_id),
                         SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(route->source)),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &route->provider, route->family));
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("ConfigProvider", route->family, &route->provider));
         if (r < 0)
                 return r;
 
@@ -383,10 +383,10 @@ static int routing_policy_rule_append_json(RoutingPolicyRule *rule, sd_json_vari
         return sd_json_variant_append_arraybo(
                         array,
                         SD_JSON_BUILD_PAIR_INTEGER("Family", rule->family),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("FromPrefix", &rule->from.address, rule->family),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("FromPrefix", rule->family, &rule->from.address),
                         SD_JSON_BUILD_PAIR_CONDITION(in_addr_is_set(rule->family, &rule->from.address),
                                                      "FromPrefixLength", SD_JSON_BUILD_UNSIGNED(rule->from.prefixlen)),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ToPrefix", &rule->to.address, rule->family),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("ToPrefix", rule->family, &rule->to.address),
                         SD_JSON_BUILD_PAIR_CONDITION(in_addr_is_set(rule->family, &rule->to.address),
                                                      "ToPrefixLength", SD_JSON_BUILD_UNSIGNED(rule->to.prefixlen)),
                         SD_JSON_BUILD_PAIR_UNSIGNED("Protocol", rule->protocol),
@@ -510,12 +510,12 @@ static int dns_append_json_one(Link *link, const struct in_addr_full *a, Network
         return sd_json_variant_append_arraybo(
                         array,
                         SD_JSON_BUILD_PAIR_INTEGER("Family", a->family),
-                        JSON_BUILD_PAIR_IN_ADDR("Address", &a->address, a->family),
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING("Address", a->family, &a->address),
                         JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("Port", a->port),
                         SD_JSON_BUILD_PAIR_CONDITION(a->ifindex != 0, "InterfaceIndex", SD_JSON_BUILD_INTEGER(a->ifindex)),
                         JSON_BUILD_PAIR_STRING_NON_EMPTY("ServerName", a->server_name),
                         SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, a->family));
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("ConfigProvider", a->family, p));
 }
 
 static int dns_append_json(Link *link, sd_json_variant **v) {
@@ -709,16 +709,24 @@ static int dnr_append_json(Link *link, sd_json_variant **v) {
 }
 
 static int server_append_json_one_addr(int family, const union in_addr_union *a, NetworkConfigSource s, const union in_addr_union *p, sd_json_variant **array) {
+        _cleanup_free_ char *address_str = NULL;
+        int r;
+
         assert(IN_SET(family, AF_INET, AF_INET6));
         assert(a);
         assert(array);
 
+        r = in_addr_to_string(family, a, &address_str);
+        if (r < 0)
+                return r;
+
         return sd_json_variant_append_arraybo(
                         array,
                         SD_JSON_BUILD_PAIR_INTEGER("Family", family),
                         JSON_BUILD_PAIR_IN_ADDR("Address", a, family),
+                        SD_JSON_BUILD_PAIR_STRING("AddressString", address_str),
                         SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family));
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("ConfigProvider", family, p));
 }
 
 static int server_append_json_one_fqdn(int family, const char *fqdn, NetworkConfigSource s, const union in_addr_union *p, sd_json_variant **array) {
@@ -730,7 +738,7 @@ static int server_append_json_one_fqdn(int family, const char *fqdn, NetworkConf
                         array,
                         SD_JSON_BUILD_PAIR_STRING("Server", fqdn),
                         SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family));
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("ConfigProvider", family, p));
 }
 
 static int server_append_json_one_string(const char *str, NetworkConfigSource s, sd_json_variant **array) {
@@ -829,7 +837,7 @@ static int domain_append_json(int family, const char *domain, NetworkConfigSourc
                         array,
                         SD_JSON_BUILD_PAIR_STRING("Domain", domain),
                         SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)),
-                        JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family));
+                        JSON_BUILD_PAIR_IN_ADDR_WITH_STRING_NON_NULL("ConfigProvider", family, p));
 }
 
 static int sip_append_json(Link *link, sd_json_variant **v) {
@@ -1103,10 +1111,10 @@ static int pref64_append_json(Link *link, sd_json_variant **v) {
         SET_FOREACH(i, link->ndisc_pref64) {
                 r = sd_json_variant_append_arraybo(
                                 &array,
-                                JSON_BUILD_PAIR_IN6_ADDR_NON_NULL("Prefix", &i->prefix),
+                                JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING("Prefix", &i->prefix),
                                 SD_JSON_BUILD_PAIR_UNSIGNED("PrefixLength", i->prefix_len),
                                 JSON_BUILD_PAIR_FINITE_USEC("LifetimeUSec", i->lifetime_usec),
-                                JSON_BUILD_PAIR_IN6_ADDR_NON_NULL("ConfigProvider", &i->router));
+                                JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING_NON_NULL("ConfigProvider", &i->router));
                 if (r < 0)
                         return r;
         }
@@ -1222,6 +1230,7 @@ static int dhcp6_client_pd_append_json(Link *link, sd_json_variant **v) {
                 usec_t lifetime_preferred_usec, lifetime_valid_usec;
                 struct in6_addr prefix;
                 uint8_t prefix_len;
+                _cleanup_free_ char *prefix_str = NULL;
 
                 r = sd_dhcp6_lease_get_pd_prefix(link->dhcp6_lease, &prefix, &prefix_len);
                 if (r < 0)
@@ -1232,9 +1241,16 @@ static int dhcp6_client_pd_append_json(Link *link, sd_json_variant **v) {
                 if (r < 0)
                         return r;
 
+                if (in6_addr_is_set(&prefix)) {
+                        r = in6_addr_to_string(&prefix, &prefix_str);
+                        if (r < 0)
+                                return r;
+                }
+
                 r = sd_json_variant_append_arraybo(
                                 &array,
                                 JSON_BUILD_PAIR_IN6_ADDR("Prefix", &prefix),
+                                JSON_BUILD_PAIR_STRING_NON_EMPTY("PrefixString", prefix_str),
                                 SD_JSON_BUILD_PAIR_UNSIGNED("PrefixLength", prefix_len),
                                 JSON_BUILD_PAIR_FINITE_USEC("PreferredLifetimeUSec", lifetime_preferred_usec),
                                 JSON_BUILD_PAIR_FINITE_USEC("ValidLifetimeUSec", lifetime_valid_usec));
@@ -1343,6 +1359,7 @@ static int dhcp_client_pd_append_json(Link *link, sd_json_variant **v) {
         struct in6_addr sixrd_prefix;
         const struct in_addr *br_addresses;
         size_t n_br_addresses = 0;
+        _cleanup_free_ char *prefix_str = NULL;
         int r;
 
         assert(link);
@@ -1356,6 +1373,12 @@ static int dhcp_client_pd_append_json(Link *link, sd_json_variant **v) {
         if (r < 0)
                 return r;
 
+        if (in6_addr_is_set(&sixrd_prefix)) {
+                r = in6_addr_to_string(&sixrd_prefix, &prefix_str);
+                if (r < 0)
+                        return r;
+        }
+
         FOREACH_ARRAY(br_address, br_addresses, n_br_addresses) {
                 r = sd_json_variant_append_arrayb(&addresses, JSON_BUILD_IN4_ADDR(br_address));
                 if (r < 0)
@@ -1365,6 +1388,7 @@ static int dhcp_client_pd_append_json(Link *link, sd_json_variant **v) {
         r = sd_json_buildo(
                         &array,
                         JSON_BUILD_PAIR_IN6_ADDR("Prefix", &sixrd_prefix),
+                        JSON_BUILD_PAIR_STRING_NON_EMPTY("PrefixString", prefix_str),
                         SD_JSON_BUILD_PAIR_UNSIGNED("PrefixLength", sixrd_prefixlen),
                         SD_JSON_BUILD_PAIR_UNSIGNED("IPv4MaskLength", ipv4masklen),
                         JSON_BUILD_PAIR_VARIANT_NON_NULL("BorderRouters", addresses));
@@ -1502,7 +1526,7 @@ int link_build_json(Link *link, sd_json_variant **ret) {
                         JSON_BUILD_PAIR_HW_ADDR_NON_NULL("HardwareAddress", &link->hw_addr),
                         JSON_BUILD_PAIR_HW_ADDR_NON_NULL("PermanentHardwareAddress", &link->permanent_hw_addr),
                         JSON_BUILD_PAIR_HW_ADDR_NON_NULL("BroadcastAddress", &link->bcast_addr),
-                        JSON_BUILD_PAIR_IN6_ADDR_NON_NULL("IPv6LinkLocalAddress", &link->ipv6ll_address),
+                        JSON_BUILD_PAIR_IN6_ADDR_WITH_STRING_NON_NULL("IPv6LinkLocalAddress", &link->ipv6ll_address),
                         /* wlan information */
                         SD_JSON_BUILD_PAIR_CONDITION(link->wlan_iftype > 0, "WirelessLanInterfaceType",
                                                      SD_JSON_BUILD_UNSIGNED(link->wlan_iftype)),
index f4623ad5763ec398badea90d15ab81bb0345f220..1fdb6a7b6594c91add33354a289fdc6a9a812311 100644 (file)
@@ -2,6 +2,13 @@
 
 #include "varlink-io.systemd.Network.h"
 
+/* Helper macro to define address fields with both binary and string representation */
+#define SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(field_name, comment, flags) \
+    SD_VARLINK_FIELD_COMMENT(comment), \
+    SD_VARLINK_DEFINE_FIELD(field_name, SD_VARLINK_INT, SD_VARLINK_ARRAY | (flags)), \
+    SD_VARLINK_FIELD_COMMENT(comment " (human-readable format)"), \
+    SD_VARLINK_DEFINE_FIELD(field_name##String, SD_VARLINK_STRING, (flags))
+
 static SD_VARLINK_DEFINE_ENUM_TYPE(
                 LinkState,
                 SD_VARLINK_DEFINE_ENUM_VALUE(pending),
@@ -35,12 +42,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 RoutingPolicyRule,
                 SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"),
                 SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0),
-                SD_VARLINK_FIELD_COMMENT("Source address prefix to match"),
-                SD_VARLINK_DEFINE_FIELD(FromPrefix, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(FromPrefix, "Source address prefix to match", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Length of source prefix in bits"),
                 SD_VARLINK_DEFINE_FIELD(FromPrefixLength, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
-                SD_VARLINK_FIELD_COMMENT("Destination address prefix to match"),
-                SD_VARLINK_DEFINE_FIELD(ToPrefix, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ToPrefix, "Destination address prefix to match", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Length of destination prefix in bits"),
                 SD_VARLINK_DEFINE_FIELD(ToPrefixLength, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Routing protocol identifier"),
@@ -92,18 +97,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 Route,
                 SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"),
                 SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0),
-                SD_VARLINK_FIELD_COMMENT("Destination network address"),
-                SD_VARLINK_DEFINE_FIELD(Destination, SD_VARLINK_INT, SD_VARLINK_ARRAY),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Destination, "Destination network address", 0),
                 SD_VARLINK_FIELD_COMMENT("Destination network prefix length"),
                 SD_VARLINK_DEFINE_FIELD(DestinationPrefixLength, SD_VARLINK_INT, 0),
-                SD_VARLINK_FIELD_COMMENT("Gateway address for this route"),
-                SD_VARLINK_DEFINE_FIELD(Gateway, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
-                SD_VARLINK_FIELD_COMMENT("Source address prefix for route selection"),
-                SD_VARLINK_DEFINE_FIELD(Source, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Gateway, "Gateway address for this route", SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Source, "Source address prefix for route selection", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Source prefix length"),
                 SD_VARLINK_DEFINE_FIELD(SourcePrefixLength, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
-                SD_VARLINK_FIELD_COMMENT("Preferred source address for outgoing packets"),
-                SD_VARLINK_DEFINE_FIELD(PreferredSource, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(PreferredSource, "Preferred source address for outgoing packets", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Type of Service (TOS) field"),
                 SD_VARLINK_DEFINE_FIELD(TOS, SD_VARLINK_INT, 0),
                 SD_VARLINK_FIELD_COMMENT("Route scope (RT_SCOPE_* value)"),
@@ -138,8 +139,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SD_VARLINK_DEFINE_FIELD(LifetimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Configuration source for this route"),
                 SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0),
-                SD_VARLINK_FIELD_COMMENT("Address of the configuration provider"),
-                SD_VARLINK_DEFINE_FIELD(ConfigProvider, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Configuration state of this route"),
                 SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Interface index for serialization"),
@@ -162,8 +162,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SD_VARLINK_DEFINE_FIELD(ID, SD_VARLINK_INT, 0),
                 SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"),
                 SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0),
-                SD_VARLINK_FIELD_COMMENT("Gateway address for this next hop"),
-                SD_VARLINK_DEFINE_FIELD(Gateway, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Gateway, "Gateway address for this next hop", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Next hop flags (RTNH_F_* values)"),
                 SD_VARLINK_DEFINE_FIELD(Flags, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Human-readable flags string"),
@@ -178,8 +177,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SD_VARLINK_DEFINE_FIELD_BY_TYPE(Group, NextHopGroup, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Configuration source for this next hop"),
                 SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0),
-                SD_VARLINK_FIELD_COMMENT("Address of the configuration provider"),
-                SD_VARLINK_DEFINE_FIELD(ConfigProvider, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Configuration state of this next hop"),
                 SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
 
@@ -210,8 +208,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 DNS,
                 SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"),
                 SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0),
-                SD_VARLINK_FIELD_COMMENT("DNS server IP address"),
-                SD_VARLINK_DEFINE_FIELD(Address, SD_VARLINK_INT, SD_VARLINK_ARRAY),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Address, "DNS server IP address", 0),
                 SD_VARLINK_FIELD_COMMENT("DNS server port number"),
                 SD_VARLINK_DEFINE_FIELD(Port, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Interface index for link-local DNS servers"),
@@ -220,34 +217,29 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SD_VARLINK_DEFINE_FIELD(ServerName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Configuration source for this DNS server"),
                 SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0),
-                SD_VARLINK_FIELD_COMMENT("Address of the configuration provider"),
-                SD_VARLINK_DEFINE_FIELD(ConfigProvider, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE));
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE));
 
 static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 NTP,
                 SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6) for address-based servers"),
                 SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
-                SD_VARLINK_FIELD_COMMENT("NTP server IP address"),
-                SD_VARLINK_DEFINE_FIELD(Address, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Address, "NTP server IP address", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("NTP server"),
                 SD_VARLINK_DEFINE_FIELD(Server, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Configuration source for this NTP server"),
                 SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0),
-                SD_VARLINK_FIELD_COMMENT("Address of the configuration provider"),
-                SD_VARLINK_DEFINE_FIELD(ConfigProvider, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE));
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE));
 
 static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SIP,
                 SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6) for address-based servers"),
                 SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
-                SD_VARLINK_FIELD_COMMENT("SIP server IP address"),
-                SD_VARLINK_DEFINE_FIELD(Address, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Address, "SIP server IP address", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("SIP server domain name"),
                 SD_VARLINK_DEFINE_FIELD(Domain, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Configuration source for this SIP server"),
                 SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0),
-                SD_VARLINK_FIELD_COMMENT("Address of the configuration provider"),
-                SD_VARLINK_DEFINE_FIELD(ConfigProvider, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE));
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE));
 
 static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 Domain,
@@ -255,8 +247,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SD_VARLINK_DEFINE_FIELD(Domain, SD_VARLINK_STRING, 0),
                 SD_VARLINK_FIELD_COMMENT("Configuration source for this domain"),
                 SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0),
-                SD_VARLINK_FIELD_COMMENT("Address of the configuration provider"),
-                SD_VARLINK_DEFINE_FIELD(ConfigProvider, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE));
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE));
 
 static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 DNSSECNegativeTrustAnchor,
@@ -282,12 +273,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 Pref64,
                 SD_VARLINK_FIELD_COMMENT("IPv6 prefix for NAT64/DNS64"),
                 SD_VARLINK_DEFINE_FIELD(Prefix, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Human-readable IPv6 prefix"),
+                SD_VARLINK_DEFINE_FIELD(PrefixString, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Length of the prefix in bits"),
                 SD_VARLINK_DEFINE_FIELD(PrefixLength, SD_VARLINK_INT, 0),
                 SD_VARLINK_FIELD_COMMENT("Lifetime of the prefix in microseconds"),
                 SD_VARLINK_DEFINE_FIELD(LifetimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
-                SD_VARLINK_FIELD_COMMENT("Address of router that provided this prefix"),
-                SD_VARLINK_DEFINE_FIELD(ConfigProvider, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE));
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of router that provided this prefix", SD_VARLINK_NULLABLE));
 
 static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 NDisc,
@@ -298,18 +290,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 Address,
                 SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"),
                 SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0),
-                SD_VARLINK_FIELD_COMMENT("IP address"),
-                SD_VARLINK_DEFINE_FIELD(Address, SD_VARLINK_INT, SD_VARLINK_ARRAY),
-                SD_VARLINK_FIELD_COMMENT("Peer address for point-to-point interfaces"),
-                SD_VARLINK_DEFINE_FIELD(Peer, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Address, "IP address", 0),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Peer, "Peer address for point-to-point interfaces", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Network prefix length"),
                 SD_VARLINK_DEFINE_FIELD(PrefixLength, SD_VARLINK_INT, 0),
                 SD_VARLINK_FIELD_COMMENT("Configuration source for this address"),
                 SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0),
-                SD_VARLINK_FIELD_COMMENT("Address of the configuration provider (DHCP server, router, etc.)"),
-                SD_VARLINK_DEFINE_FIELD(ConfigProvider, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
-                SD_VARLINK_FIELD_COMMENT("Broadcast address for IPv4 networks"),
-                SD_VARLINK_DEFINE_FIELD(Broadcast, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider (DHCP server, router, etc.)", SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Broadcast, "Broadcast address for IPv4 networks", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Address scope (RT_SCOPE_* value)"),
                 SD_VARLINK_DEFINE_FIELD(Scope, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Human-readable scope string"),
@@ -335,8 +323,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 Neighbor,
                 SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"),
                 SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0),
-                SD_VARLINK_FIELD_COMMENT("IP address of the neighbor"),
-                SD_VARLINK_DEFINE_FIELD(Destination, SD_VARLINK_INT, SD_VARLINK_ARRAY),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Destination, "IP address of the neighbor", 0),
                 SD_VARLINK_FIELD_COMMENT("Link layer (MAC) address of the neighbor"),
                 SD_VARLINK_DEFINE_FIELD(LinkLayerAddress, SD_VARLINK_INT, SD_VARLINK_ARRAY),
                 SD_VARLINK_FIELD_COMMENT("Configuration source for this neighbor entry"),
@@ -366,12 +353,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 DHCPv6ClientPD,
                 SD_VARLINK_FIELD_COMMENT("Delegated IPv6 prefix"),
                 SD_VARLINK_DEFINE_FIELD(Prefix, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Human-readable delegated IPv6 prefix"),
+                SD_VARLINK_DEFINE_FIELD(PrefixString, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Length of the delegated prefix in bits"),
                 SD_VARLINK_DEFINE_FIELD(PrefixLength, SD_VARLINK_INT, 0),
                 SD_VARLINK_FIELD_COMMENT("Preferred lifetime of the prefix in microseconds"),
-                SD_VARLINK_DEFINE_FIELD(PreferredLifetimeUsec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_FIELD(PreferredLifetimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Valid lifetime of the prefix in microseconds"),
-                SD_VARLINK_DEFINE_FIELD(ValidLifetimeUsec, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
+                SD_VARLINK_DEFINE_FIELD(ValidLifetimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
 
 static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 DHCPv6ClientVendorOption,
@@ -397,8 +386,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 DHCPServerLease,
                 SD_VARLINK_FIELD_COMMENT("DHCP client identifier"),
                 SD_VARLINK_DEFINE_FIELD(ClientId, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
-                SD_VARLINK_FIELD_COMMENT("Address assigned to the client"),
-                SD_VARLINK_DEFINE_FIELD(Address, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(Address, "Address assigned to the client", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Hostname provided by the DHCP client"),
                 SD_VARLINK_DEFINE_FIELD(Hostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Hardware address type (ARPHRD_* value)"),
@@ -459,8 +447,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SD_VARLINK_DEFINE_FIELD(PermanentHardwareAddress, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Broadcast address for Ethernet interfaces"),
                 SD_VARLINK_DEFINE_FIELD(BroadcastAddress, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
-                SD_VARLINK_FIELD_COMMENT("IPv6 link-local address"),
-                SD_VARLINK_DEFINE_FIELD(IPv6LinkLocalAddress, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(IPv6LinkLocalAddress, "IPv6 link-local address", SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Wireless LAN interface type (NL80211_IFTYPE_* value)"),
                 SD_VARLINK_DEFINE_FIELD(WirelessLanInterfaceType, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Human-readable wireless LAN interface type"),