From c5f08a2e5cf1f246c908ddaaefbccdcfbdbaa754 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 19 Jun 2026 15:00:59 -0400 Subject: [PATCH 1/9] json-util: generalize json_dispatch_address_family There are several places where address family is dispatched from JSON, so take json_dispatch_address_family from networkd and put it in json-util.c. Update the all instances in the tree to use this new function, adding SD_JSON_RELAX to the dispatch flags if AF_UNSPEC should be allowed in that case. --- src/libsystemd/sd-json/json-util.c | 24 ++++++++++++++++++++++++ src/libsystemd/sd-json/json-util.h | 1 + src/network/networkd-serialize.c | 22 ---------------------- src/nss-resolve/nss-resolve.c | 23 +++-------------------- src/resolve/resolvectl.c | 2 +- src/resolve/resolved-varlink.c | 15 +++------------ src/shared/dns-configuration.c | 14 +++++++------- 7 files changed, 39 insertions(+), 62 deletions(-) diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index c6727ac760..485b4a03c8 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -918,3 +918,27 @@ int json_variant_compare(sd_json_variant *a, sd_json_variant *b) { return CMP(sd_json_variant_type(a), sd_json_variant_type(b)); } + +int json_dispatch_address_family(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + int *family = ASSERT_PTR(userdata), r; + + assert(variant); + + if (sd_json_variant_is_negative(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), + "JSON field '%s' for an address family cannot be negative.", strna(name)); + + int k = AF_UNSPEC; + if (!sd_json_variant_is_null(variant)) { + r = sd_json_dispatch_int(name, variant, flags, &k); + if (r < 0) + return r; + } + + if (!IN_SET(k, AF_INET, AF_INET6) && !(FLAGS_SET(flags, SD_JSON_RELAX) && k == AF_UNSPEC)) + return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), + "JSON field '%s' out of range for an address family.", strna(name)); + + *family = k; + return 0; +} diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index ecb4e2d633..de7790a6d3 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -133,6 +133,7 @@ int json_dispatch_log_level(const char *name, sd_json_variant *variant, sd_json_ int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_job_id(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_address_family(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); static inline int json_variant_unbase64_iovec(sd_json_variant *v, struct iovec *ret) { return sd_json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL); diff --git a/src/network/networkd-serialize.c b/src/network/networkd-serialize.c index 394f03ced6..224fcc93a0 100644 --- a/src/network/networkd-serialize.c +++ b/src/network/networkd-serialize.c @@ -102,28 +102,6 @@ int manager_set_serialization_fd(Manager *manager, int fd, const char *name) { static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_network_config_source, NetworkConfigSource, network_config_source_from_string); -static int json_dispatch_address_family(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - int r, *i = ASSERT_PTR(userdata); - int64_t i64; - - assert_return(variant, -EINVAL); - - if (FLAGS_SET(flags, SD_JSON_RELAX) && sd_json_variant_is_null(variant)) { - *i = AF_UNSPEC; - return 0; - } - - r = sd_json_dispatch_int64(name, variant, flags, &i64); - if (r < 0) - return r; - - if (!IN_SET(i64, AF_INET, AF_INET6) && !(FLAGS_SET(flags, SD_JSON_RELAX) && i64 == AF_UNSPEC)) - return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds for an address family.", strna(name)); - - *i = (int) i64; - return 0; -} - typedef struct AddressParam { int family; struct iovec address; diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c index 86eaf4e3b3..9f4225be3f 100644 --- a/src/nss-resolve/nss-resolve.c +++ b/src/nss-resolve/nss-resolve.c @@ -78,23 +78,6 @@ static uint32_t ifindex_to_scopeid(int family, const void *a, int ifindex) { return in6_addr_is_link_local(&in6) ? ifindex : 0; } -static int json_dispatch_family(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - int *family = ASSERT_PTR(userdata); - int64_t t; - - assert(variant); - - if (!sd_json_variant_is_integer(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); - - t = sd_json_variant_integer(variant); - if (t < 0 || t > INT_MAX) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid family.", strna(name)); - - *family = (int) t; - return 0; -} - typedef struct ResolveHostnameReply { sd_json_variant *addresses; char *name; @@ -159,9 +142,9 @@ static int json_dispatch_address(const char *name, sd_json_variant *variant, sd_ } static const sd_json_dispatch_field address_parameters_dispatch_table[] = { - { "ifindex", SD_JSON_VARIANT_INTEGER, json_dispatch_ifindex, offsetof(AddressParameters, ifindex), 0 }, - { "family", SD_JSON_VARIANT_INTEGER, json_dispatch_family, offsetof(AddressParameters, family), SD_JSON_MANDATORY }, - { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_address, 0, SD_JSON_MANDATORY }, + { "ifindex", SD_JSON_VARIANT_INTEGER, json_dispatch_ifindex, offsetof(AddressParameters, ifindex), 0 }, + { "family", SD_JSON_VARIANT_INTEGER, json_dispatch_address_family, offsetof(AddressParameters, family), SD_JSON_MANDATORY }, + { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_address, 0, SD_JSON_MANDATORY }, {} }; diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 67be81195a..b3895df6cf 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -2386,7 +2386,7 @@ static int dump_cache_scope(sd_json_variant *scope) { static const sd_json_dispatch_field dispatch_table[] = { { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, protocol), SD_JSON_MANDATORY }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(struct scope_info, family), 0 }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(struct scope_info, family), SD_JSON_RELAX }, { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct scope_info, ifindex), SD_JSON_RELAX }, { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 }, { "cache", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct scope_info, cache), SD_JSON_MANDATORY }, diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 2ac4ee9555..db26ccb11a 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -346,7 +346,7 @@ static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *paramet static const sd_json_dispatch_field dispatch_table[] = { { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX }, { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, name), SD_JSON_MANDATORY }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParameters, family), 0 }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(LookupParameters, family), SD_JSON_RELAX }, { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, {} }; @@ -377,9 +377,6 @@ static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *paramet if (r == 0) return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name")); - if (!IN_SET(p.family, AF_UNSPEC, AF_INET, AF_INET6)) - return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); - if (validate_and_mangle_query_flags(m, &p.flags, p.name, SD_RESOLVED_NO_SEARCH) < 0) return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); @@ -475,7 +472,7 @@ finish: static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParameters, family), SD_JSON_MANDATORY }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(LookupParameters, family), SD_JSON_MANDATORY }, { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(LookupParameters, address), SD_JSON_MANDATORY }, { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, {} @@ -501,9 +498,6 @@ static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *paramete if (r != 0) return r; - if (!IN_SET(p.family, AF_INET, AF_INET6)) - return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); - if (FAMILY_ADDRESS_SIZE(p.family) != p.address.iov_len) return sd_varlink_error(link, "io.systemd.Resolve.BadAddressSize", NULL); @@ -961,7 +955,7 @@ static int vl_method_resolve_service(sd_varlink* link, sd_json_variant* paramete { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParametersResolveService, type), 0 }, { "domain", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParametersResolveService, domain), SD_JSON_MANDATORY }, { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParametersResolveService, ifindex), SD_JSON_RELAX }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParametersResolveService, family), 0 }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(LookupParametersResolveService, family), SD_JSON_RELAX }, { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParametersResolveService, flags), 0 }, {} }; @@ -987,9 +981,6 @@ static int vl_method_resolve_service(sd_varlink* link, sd_json_variant* paramete if (r != 0) return r; - if (!IN_SET(p.family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); - if (isempty(p.name)) p.name = NULL; else if (!dns_service_name_is_valid(p.name)) diff --git a/src/shared/dns-configuration.c b/src/shared/dns-configuration.c index 1194e87258..01b59bbd89 100644 --- a/src/shared/dns-configuration.c +++ b/src/shared/dns-configuration.c @@ -36,7 +36,7 @@ static int dispatch_dns_server(const char *name, sd_json_variant *variant, sd_js static const sd_json_dispatch_field dns_server_dispatch_table[] = { { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(DNSServer, addr), SD_JSON_MANDATORY }, { "addressString", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, - { "family", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(DNSServer, family), SD_JSON_MANDATORY }, + { "family", SD_JSON_VARIANT_INTEGER, json_dispatch_address_family, offsetof(DNSServer, family), SD_JSON_MANDATORY }, { "port", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint16, offsetof(DNSServer, port), 0 }, { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSServer, ifindex), SD_JSON_RELAX }, { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSServer, server_name), 0 }, @@ -177,12 +177,12 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( static int dispatch_dns_scope(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { static const sd_json_dispatch_field dns_scope_dispatch_table[] = { - { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, protocol), SD_JSON_MANDATORY }, - { "family", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(DNSScope, family), 0 }, - { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, ifname), 0 }, - { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSScope, ifindex), SD_JSON_RELAX }, - { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, dnssec_mode_str), 0 }, - { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, dns_over_tls_mode_str), 0 }, + { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, protocol), SD_JSON_MANDATORY }, + { "family", SD_JSON_VARIANT_INTEGER, json_dispatch_address_family, offsetof(DNSScope, family), SD_JSON_RELAX }, + { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, ifname), 0 }, + { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSScope, ifindex), SD_JSON_RELAX }, + { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, dnssec_mode_str), 0 }, + { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, dns_over_tls_mode_str), 0 }, {}, }; DNSScope **ret = ASSERT_PTR(userdata); From 3f73f1ef75d5c8da66eda2cff270676e70becf9b Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Tue, 23 Jun 2026 12:02:19 -0400 Subject: [PATCH 2/9] json-util: introduce json_dispatch_in_addr_data 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 | 55 ++++++++ src/libsystemd/sd-json/json-util.h | 1 + src/libsystemd/sd-json/test-json.c | 194 +++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+) diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index 485b4a03c8..193ee714d0 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -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; diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index de7790a6d3..2032566fdd 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -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); diff --git a/src/libsystemd/sd-json/test-json.c b/src/libsystemd/sd-json/test-json.c index bb94dfea4b..6992d095c6 100644 --- a/src/libsystemd/sd-json/test-json.c +++ b/src/libsystemd/sd-json/test-json.c @@ -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); From cc6bc699c7b857e7b775145a807653d9bacece4b Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 19 Jun 2026 15:01:00 -0400 Subject: [PATCH 3/9] resolve: add query string field to io.systemd.Resolve.DNSError This is preparation for using varlink methods more in resolvectl. In particular, this is helpful for proving more accurate error messages, and maintaining compatibility with existing error messages from the DBus API. --- src/resolve/resolved-varlink.c | 6 ++++-- src/shared/varlink-io.systemd.Resolve.c | 1 + test/units/TEST-75-RESOLVED.sh | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index db26ccb11a..2617b62775 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -135,7 +135,8 @@ static int reply_query_state(DnsQuery *q) { /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ return sd_varlink_errorbo(q->varlink_request, "io.systemd.Resolve.DNSError", - SD_JSON_BUILD_PAIR_INTEGER("rcode", DNS_RCODE_NXDOMAIN)); + SD_JSON_BUILD_PAIR_INTEGER("rcode", DNS_RCODE_NXDOMAIN), + JSON_BUILD_PAIR_STRING_NON_EMPTY("queryString", dns_query_string(q))); case DNS_TRANSACTION_RCODE_FAILURE: return sd_varlink_errorbo(q->varlink_request, "io.systemd.Resolve.DNSError", @@ -143,7 +144,8 @@ static int reply_query_state(DnsQuery *q) { SD_JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0, "extendedDNSErrorCode", SD_JSON_BUILD_INTEGER(q->answer_ede_rcode)), SD_JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg), - "extendedDNSErrorMessage", SD_JSON_BUILD_STRING(q->answer_ede_msg))); + "extendedDNSErrorMessage", SD_JSON_BUILD_STRING(q->answer_ede_msg)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("queryString", dns_query_string(q))); case DNS_TRANSACTION_NULL: case DNS_TRANSACTION_PENDING: diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c index e8de8b39f8..6c4ef094ca 100644 --- a/src/shared/varlink-io.systemd.Resolve.c +++ b/src/shared/varlink-io.systemd.Resolve.c @@ -337,6 +337,7 @@ static SD_VARLINK_DEFINE_ERROR(StubLoop); static SD_VARLINK_DEFINE_ERROR( DNSError, SD_VARLINK_DEFINE_FIELD(rcode, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_FIELD(queryString, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(extendedDNSErrorCode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_ERROR(CNAMELoop); diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index abc6b0d8ac..7a740a1c2e 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -770,7 +770,7 @@ testcase_08_resolved() { grep -qE "^edns-extra-text.forwarded.test.+: SERVFAIL \(Censored: Nothing to see here!\)" "$RUN_OUT" (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-extra-text.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT" - grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!"}' "$RUN_OUT" + grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!","queryString":"edns-extra-text.forwarded.test"}' "$RUN_OUT" journalctl --sync journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Censored: Nothing to see here!\)" @@ -779,7 +779,7 @@ testcase_08_resolved() { grep -qE "^edns-code-zero.forwarded.test:.+: SERVFAIL \(Other: 🐱\)" "$RUN_OUT" (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-code-zero.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT" - grep -qF '{"rcode":2,"extendedDNSErrorCode":0,"extendedDNSErrorMessage":"🐱"}' "$RUN_OUT" + grep -qF '{"rcode":2,"extendedDNSErrorCode":0,"extendedDNSErrorMessage":"🐱","queryString":"edns-code-zero.forwarded.test"}' "$RUN_OUT" journalctl --sync journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Other: 🐱\)" @@ -788,7 +788,7 @@ testcase_08_resolved() { grep -qE "^edns-invalid-code.forwarded.test:.+: SERVFAIL \([0-9]+\)" "$RUN_OUT" (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT" - grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+}' "$RUN_OUT" + grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"queryString":"edns-invalid-code.forwarded.test"}' "$RUN_OUT" journalctl --sync journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+\)" @@ -797,7 +797,7 @@ testcase_08_resolved() { grep -qE '^edns-invalid-code-with-extra-text.forwarded.test:.+: SERVFAIL \([0-9]+: Hello \[#\]\$%~ World\)' "$RUN_OUT" (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code-with-extra-text.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT" - grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"extendedDNSErrorMessage":"Hello \[#\]\$%~ World"}' "$RUN_OUT" + grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"extendedDNSErrorMessage":"Hello \[#\]\$%~ World","queryString":"edns-invalid-code-with-extra-text.forwarded.test"}' "$RUN_OUT" journalctl --sync journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+: Hello \[\#\]\\$%~ World\)" } From 35d611622a0c4277e1a3e0b8866a800c0fc6fcf8 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 19 Jun 2026 15:01:00 -0400 Subject: [PATCH 4/9] resolvectl: use varlink instead of dbus for ResolveHostname --- src/resolve/resolvectl.c | 204 +++++++++++++++++++----------- src/shared/meson.build | 1 + src/shared/resolve-varlink-util.c | 107 ++++++++++++++++ src/shared/resolve-varlink-util.h | 34 +++++ 4 files changed, 275 insertions(+), 71 deletions(-) create mode 100644 src/shared/resolve-varlink-util.c create mode 100644 src/shared/resolve-varlink-util.h diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index b3895df6cf..4baa2f3868 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -48,6 +48,7 @@ #include "resolvectl.h" #include "resolved-def.h" #include "resolved-util.h" +#include "resolve-varlink-util.h" #include "set.h" #include "socket-netlink.h" #include "string-table.h" @@ -278,20 +279,97 @@ static void print_ifindex_comment(int printed_so_far, int ifindex) { ansi_grey(), ifname, ansi_normal()); } -static int resolve_host_error(const char *name, int r, const sd_bus_error *error) { - if (sd_bus_error_has_name(error, BUS_ERROR_DNS_NXDOMAIN)) - return log_error_errno(r, "%s: %s", name, bus_error_message(error, r)); +static int varlink_log_resolve_error(const char *name, const char *error_id, sd_json_variant *reply) { + int r; - return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(error, r)); + assert(name); + assert(!isempty(error_id)); + + int ret = sd_varlink_error_to_errno(error_id, reply); + static const struct { + const char *error_id; + const char *msg; + } error_message_table[] = { + { "io.systemd.Resolve.NoNameServers", "No appropriate name servers or networks for name found" }, + { "io.systemd.Resolve.QueryTimedOut", "Query timed out" }, + { "io.systemd.Resolve.MaxAttemptsReached", "All attempts to contact name servers or networks failed" }, + { "io.systemd.Resolve.InvalidReply", "Received invalid reply" }, + { "io.systemd.Resolve.QueryAborted", "Query aborted" }, + { "io.systemd.Resolve.QueryRefused", "DNS query type refused" }, + { "io.systemd.Resolve.NoTrustAnchor", "No suitable trust anchor known" }, + { "io.systemd.Resolve.ResourceRecordTypeUnsupported", "Server does not support requested resource record type" }, + { "io.systemd.Resolve.NetworkDown", "Network is down" }, + { "io.systemd.Resolve.NoSource", "All suitable resolution sources turned off" }, + { "io.systemd.Resolve.StubLoop", "Configured DNS server loops back to us" }, + { "io.systemd.Resolve.ZoneTransfersNotPermitted", "Zone transfers not permitted via this programming interface" }, + }; + FOREACH_ELEMENT(em, error_message_table) + if (streq(em->error_id, error_id)) + return log_error_errno(ret, "%s: resolve call failed: %s", name, em->msg); + + if (streq(error_id, "io.systemd.Resolve.NoSuchResourceRecord")) + return log_error_errno(ret, "%s: resolve call failed: '%s' does not have any RR of the requested type", name, name); + + if (streq(error_id, "io.systemd.Resolve.CNAMELoop")) + return log_error_errno(ret, "%s: resolve call failed: CNAME loop detected, or CNAME resolving disabled on '%s'", name, name); + + if (streq(error_id, "io.systemd.Resolve.ServiceNotProvided")) + return log_error_errno(ret, "%s: resolve call failed: '%s' does not provide the requested service", name, name); + + if (streq(error_id, "io.systemd.Resolve.InconsistentServiceRecords")) + return log_error_errno(ret, "%s: resolve call failed: '%s' does not provide a consistent set of service resource records", name, name); + + _cleanup_(resolve_error_done) ResolveError error = { + .rcode = _DNS_RCODE_INVALID, + .ede_rcode = _DNS_EDE_RCODE_INVALID, + }; + r = dispatch_resolve_error(/* name = */ NULL, reply, SD_JSON_LOG, &error); + if (r < 0) + log_debug_errno(r, "Failed to dispatch error JSON, ignoring: %m"); + + _cleanup_free_ char *msg_extended = NULL; + if (error.ede_rcode >= 0) { + msg_extended = strjoin(" (", + FORMAT_DNS_EDE_RCODE(error.ede_rcode), + !isempty(error.ede_msg) ? ": " : "", + strempty(error.ede_msg), + ")"); + if (!msg_extended) + return log_oom(); + } + + if (streq(error_id, "io.systemd.Resolve.DNSSECValidationFailed")) + return log_error_errno(ret, "%s: resolve call failed: DNSSEC validation failed: %s%s", name, error.result, strempty(msg_extended)); + + if (error.rcode != _DNS_RCODE_INVALID) { + if (error.rcode == DNS_RCODE_NXDOMAIN) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "%s: resolve call failed: Name '%s' not found%s%s", + name, error.query_string ?: name, error.ede_rcode >= 0 ? ":" : "", strempty(msg_extended)); + + return log_error_errno(ret, "%s: resolve call failed: Could not resolve '%s', server or network returned error: %s%s", + name, error.query_string ?: name, FORMAT_DNS_RCODE(error.rcode), strempty(msg_extended)); + } + + return log_error_errno(ret, "%s: resolve call failed: %s", name, error_id); } -static int resolve_host(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *canonical = NULL; - unsigned c = 0; - uint64_t flags; - usec_t ts; +static int varlink_connect_with_query_timeout(sd_varlink **vl) { + int r; + + assert(vl); + + r = sd_varlink_connect_address(vl, "/run/systemd/resolve/io.systemd.Resolve"); + if (r < 0) + return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); + + r = sd_varlink_set_relative_timeout(*vl, SD_RESOLVED_QUERY_TIMEOUT_USEC); + if (r < 0) + return log_error_errno(r, "Failed to set query timeout: %m"); + + return 0; +} + +static int resolve_host(const char *name) { int r; assert(name); @@ -301,85 +379,69 @@ static int resolve_host(sd_bus *bus, const char *name) { log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(arg_ifname) ? "*" : arg_ifname); - r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveHostname"); + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_with_query_timeout(&vl); if (r < 0) - return bus_log_create_error(r); + return r; - r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); + usec_t ts = now(CLOCK_MONOTONIC); + + const char *error_id = NULL; + sd_json_variant *v = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Resolve.ResolveHostname", + &v, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", name), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_ifindex > 0, "ifindex", arg_ifindex), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_family != AF_UNSPEC, "family", arg_family), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", arg_flags)); if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return resolve_host_error(name, r, &error); + return log_error_errno(r, "Failed to issue varlink call: %m"); ts = now(CLOCK_MONOTONIC) - ts; - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (!isempty(error_id)) + return varlink_log_resolve_error(name, error_id, v); + + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply reply = {}; + r = dispatch_resolve_hostname_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); if (r < 0) - return bus_log_parse_error(r); + return r; - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + bool first = true; + FOREACH_ARRAY(address, reply.addresses, reply.n_addresses) { _cleanup_free_ char *pretty = NULL; - int ifindex, family, k; - union in_addr_union a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "i", &ifindex); - if (r < 0) - return bus_log_parse_error(r); - - sd_bus_error_free(&error); - r = bus_message_read_in_addr_auto(reply, &error, &family, &a); - if (r < 0 && !sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) - return log_error_errno(r, "%s: systemd-resolved returned invalid result: %s", name, bus_error_message(&error, r)); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) { - log_debug_errno(r, "%s: systemd-resolved returned invalid result, ignoring: %s", name, bus_error_message(&error, r)); - continue; - } - - r = in_addr_ifindex_to_string(family, &a, ifindex, &pretty); + r = in_addr_ifindex_to_string(address->family, &address->in_addr.address, address->ifindex, &pretty); if (r < 0) return log_error_errno(r, "Failed to print address for %s: %m", name); - k = printf("%*s%s %s%s%s", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - ansi_highlight(), pretty, ansi_normal()); + int k = printf("%*s%s %s%s%s", + (int) strlen(name), + first ? name : "", + first ? ":" : " ", + ansi_highlight(), + pretty, + ansi_normal()); - print_ifindex_comment(k, ifindex); + print_ifindex_comment(k, address->ifindex); fputc('\n', stdout); - c++; + first = false; } - if (r < 0) - return bus_log_parse_error(r); - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "st", &canonical, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(name, canonical)) + if (!streq(name, reply.name)) printf("%*s%s (%s)\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - canonical); + (int) strlen(name), + reply.n_addresses == 0 ? name : "", + reply.n_addresses == 0 ? ":" : " ", + reply.name); - if (c == 0) - return log_error_errno(SYNTHETIC_ERRNO(ESRCH), - "%s: no addresses found", name); + if (reply.n_addresses == 0) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "%s: no addresses found", name); - print_source(flags, ts); + print_source(reply.flags, ts); return 0; } @@ -827,7 +889,7 @@ static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r >= 0) RET_GATHER(ret, resolve_address(bus, family, &a, ifindex)); else - RET_GATHER(ret, resolve_host(bus, *p)); + RET_GATHER(ret, resolve_host(*p)); } } diff --git a/src/shared/meson.build b/src/shared/meson.build index 6fa4216305..7b2a8b5ee2 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -185,6 +185,7 @@ shared_sources = files( 'resize-fs.c', 'resolve-hook-util.c', 'resolve-util.c', + 'resolve-varlink-util.c', 'rm-rf.c', 'seccomp-util.c', 'securebits-util.c', diff --git a/src/shared/resolve-varlink-util.c b/src/shared/resolve-varlink-util.c new file mode 100644 index 0000000000..3459cfc9d6 --- /dev/null +++ b/src/shared/resolve-varlink-util.c @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "dns-packet.h" +#include "json-util.h" +#include "resolve-varlink-util.h" + +void resolve_error_done(ResolveError *error) { + if (!error) + return; + + error->ede_msg = mfree(error->ede_msg); + error->query_string = mfree(error->query_string); + error->result = mfree(error->result); +} + +int dispatch_resolve_error(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_error_dispatch_table[] = { + { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(ResolveError, rcode), 0 }, + { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(ResolveError, ede_rcode), 0 }, + { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveError, ede_msg), 0 }, + { "queryString", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveError, query_string), 0 }, + { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveError, result), 0 }, + {}, + }; + ResolveError *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_error_done) ResolveError error = { + .rcode = _DNS_RCODE_INVALID, + .ede_rcode = _DNS_EDE_RCODE_INVALID, + }; + r = sd_json_dispatch(variant, resolve_error_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &error); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(error); + return 0; +} + +static int dispatch_resolved_address(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_address_dispatch_table[] = { + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(ResolvedAddress, ifindex), SD_JSON_RELAX }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(ResolvedAddress, family), SD_JSON_MANDATORY }, + { "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(ResolvedAddress, in_addr), SD_JSON_MANDATORY }, + {}, + }; + ResolvedAddress *ret = ASSERT_PTR(userdata); + int r; + + ResolvedAddress address = {}; + r = sd_json_dispatch(variant, resolved_address_dispatch_table, flags & ~SD_JSON_MANDATORY, &address); + if (r < 0) + return r; + + if (address.family != address.in_addr.family) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "'%s' address and family are inconsistent.", strna(name)); + + *ret = TAKE_STRUCT(address); + return 0; +} + +static int dispatch_resolved_address_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolveHostnameReply *reply = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(reply->addresses, reply->n_addresses + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_address(name, v, flags, &reply->addresses[reply->n_addresses++]); + if (r < 0) + return r; + } + + return 0; +} + +void resolve_hostname_reply_done(ResolveHostnameReply *reply) { + if (!reply) + return; + + reply->name = mfree(reply->name); + reply->addresses = mfree(reply->addresses); + reply->n_addresses = 0; +} + +int dispatch_resolve_hostname_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_hostname_reply_dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveHostnameReply, name), SD_JSON_MANDATORY }, + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveHostnameReply, flags), SD_JSON_MANDATORY }, + { "addresses", SD_JSON_VARIANT_ARRAY, dispatch_resolved_address_array, 0, SD_JSON_MANDATORY }, + {}, + }; + ResolveHostnameReply *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply reply = {}; + r = sd_json_dispatch(variant, resolve_hostname_reply_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &reply); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(reply); + return 0; +} diff --git a/src/shared/resolve-varlink-util.h b/src/shared/resolve-varlink-util.h new file mode 100644 index 0000000000..4e94fbe8c6 --- /dev/null +++ b/src/shared/resolve-varlink-util.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "in-addr-util.h" +#include "shared-forward.h" + +typedef struct ResolveError { + int rcode; + int ede_rcode; + char *ede_msg; + char *query_string; + char *result; +} ResolveError; + +void resolve_error_done(ResolveError *error); + +int dispatch_resolve_error(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +typedef struct ResolvedAddress { + int ifindex; + int family; + struct in_addr_data in_addr; +} ResolvedAddress; + +typedef struct ResolveHostnameReply { + char *name; + uint64_t flags; + ResolvedAddress *addresses; + size_t n_addresses; +} ResolveHostnameReply; + +void resolve_hostname_reply_done(ResolveHostnameReply *reply); + +int dispatch_resolve_hostname_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); From 5ff17fb750c0485469c73064cffcbbb0d29c6f0f Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 19 Jun 2026 15:01:01 -0400 Subject: [PATCH 5/9] resolvectl: use varlink instead of dbus for ResolveAddress --- src/resolve/resolvectl.c | 103 ++++++++++++------------------ src/shared/resolve-varlink-util.c | 70 ++++++++++++++++++++ src/shared/resolve-varlink-util.h | 15 +++++ 3 files changed, 125 insertions(+), 63 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 4baa2f3868..2cfd585564 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -446,16 +446,9 @@ static int resolve_host(const char *name) { return 0; } -static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *pretty = NULL; - uint64_t flags; - unsigned c = 0; - usec_t ts; +static int resolve_address(int family, const union in_addr_union *address, int ifindex) { int r; - assert(bus); assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); @@ -465,80 +458,64 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a if (ifindex <= 0) ifindex = arg_ifindex; + _cleanup_free_ char *pretty = NULL; r = in_addr_ifindex_to_string(family, address, ifindex, &pretty); if (r < 0) return log_oom(); log_debug("Resolving %s.", pretty); - r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveAddress"); + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_with_query_timeout(&vl); if (r < 0) - return bus_log_create_error(r); + return r; - r = sd_bus_message_append(req, "ii", ifindex, family); + usec_t ts = now(CLOCK_MONOTONIC); + + const char *error_id = NULL; + sd_json_variant *v = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Resolve.ResolveAddress", + &v, + &error_id, + SD_JSON_BUILD_PAIR_BYTE_ARRAY("address", &address->bytes, FAMILY_ADDRESS_SIZE_SAFE(family)), + SD_JSON_BUILD_PAIR_UNSIGNED("family", family), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(ifindex > 0, "ifindex", ifindex), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", arg_flags)); if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "t", arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); + return log_error_errno(r, "Failed to issue varlink call: %m"); ts = now(CLOCK_MONOTONIC) - ts; - r = sd_bus_message_enter_container(reply, 'a', "(is)"); + if (!isempty(error_id)) + return varlink_log_resolve_error(pretty, error_id, v); + + _cleanup_(resolve_address_reply_done) ResolveAddressReply reply = {}; + r = dispatch_resolve_address_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); if (r < 0) - return bus_log_create_error(r); + return r; - while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { - const char *n; - int k; + bool first = true; + FOREACH_ARRAY(name, reply.names, reply.n_names) { + int k = printf("%*s%s %s%s%s", + (int) strlen(pretty), + first ? pretty : "", + first ? ":" : " ", + ansi_highlight(), + name->name, + ansi_normal()); - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "is", &ifindex, &n); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - - k = printf("%*s%s %s%s%s", - (int) strlen(pretty), c == 0 ? pretty : "", - c == 0 ? ":" : " ", - ansi_highlight(), n, ansi_normal()); - - print_ifindex_comment(k, ifindex); + print_ifindex_comment(k, name->ifindex); fputc('\n', stdout); - c++; + first = false; } - if (r < 0) - return bus_log_parse_error(r); - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); + if (reply.n_names == 0) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "%s: no names found", pretty); - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (c == 0) - return log_error_errno(SYNTHETIC_ERRNO(ESRCH), - "%s: no names found", pretty); - - print_source(flags, ts); + print_source(reply.flags, ts); return 0; } @@ -887,7 +864,7 @@ static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { r = in_addr_ifindex_from_string_auto(*p, &family, &a, &ifindex); if (r >= 0) - RET_GATHER(ret, resolve_address(bus, family, &a, ifindex)); + RET_GATHER(ret, resolve_address(family, &a, ifindex)); else RET_GATHER(ret, resolve_host(*p)); } diff --git a/src/shared/resolve-varlink-util.c b/src/shared/resolve-varlink-util.c index 3459cfc9d6..877e298999 100644 --- a/src/shared/resolve-varlink-util.c +++ b/src/shared/resolve-varlink-util.c @@ -105,3 +105,73 @@ int dispatch_resolve_hostname_reply(const char *name, sd_json_variant *variant, *ret = TAKE_STRUCT(reply); return 0; } + +static void resolved_name_done(ResolvedName *name) { + if (!name) + return; + + name->name = mfree(name->name); +} + +static int dispatch_resolved_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_name_dispatch_table[] = { + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(ResolvedName, ifindex), SD_JSON_RELAX }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedName, name), SD_JSON_MANDATORY }, + {}, + }; + ResolvedName *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolved_name_done) ResolvedName resolved_name = {}; + r = sd_json_dispatch(variant, resolved_name_dispatch_table, flags & ~SD_JSON_MANDATORY, &resolved_name); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(resolved_name); + return 0; +} + +static int dispatch_resolved_name_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolveAddressReply *reply = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(reply->names, reply->n_names + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_name(name, v, flags, &reply->names[reply->n_names++]); + if (r < 0) + return r; + } + + return 0; +} + +void resolve_address_reply_done(ResolveAddressReply *reply) { + if (!reply) + return; + + FOREACH_ARRAY(n, reply->names, reply->n_names) + resolved_name_done(n); + reply->names = mfree(reply->names); + reply->n_names = 0; +} + +int dispatch_resolve_address_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_address_reply_dispatch_table[] = { + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveAddressReply, flags), SD_JSON_MANDATORY }, + { "names", SD_JSON_VARIANT_ARRAY, dispatch_resolved_name_array, 0, SD_JSON_MANDATORY }, + {}, + }; + ResolveAddressReply *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_address_reply_done) ResolveAddressReply reply = {}; + r = sd_json_dispatch(variant, resolve_address_reply_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &reply); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(reply); + return 0; +} diff --git a/src/shared/resolve-varlink-util.h b/src/shared/resolve-varlink-util.h index 4e94fbe8c6..9fbc8a1492 100644 --- a/src/shared/resolve-varlink-util.h +++ b/src/shared/resolve-varlink-util.h @@ -32,3 +32,18 @@ typedef struct ResolveHostnameReply { void resolve_hostname_reply_done(ResolveHostnameReply *reply); int dispatch_resolve_hostname_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +typedef struct ResolvedName { + int ifindex; + char *name; +} ResolvedName; + +typedef struct ResolveAddressReply { + uint64_t flags; + ResolvedName *names; + size_t n_names; +} ResolveAddressReply; + +void resolve_address_reply_done(ResolveAddressReply *reply); + +int dispatch_resolve_address_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); From cfac7ec169892b387447245d0aa81e712639f11c Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 19 Jun 2026 15:01:01 -0400 Subject: [PATCH 6/9] resolvectl: use varlink instead of dbus for ResolveRecord --- src/resolve/resolvectl.c | 170 ++++++++++++------------------ src/shared/resolve-varlink-util.c | 72 +++++++++++++ src/shared/resolve-varlink-util.h | 17 +++ 3 files changed, 156 insertions(+), 103 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 2cfd585564..327cf10140 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -279,7 +279,7 @@ static void print_ifindex_comment(int printed_so_far, int ifindex) { ansi_grey(), ifname, ansi_normal()); } -static int varlink_log_resolve_error(const char *name, const char *error_id, sd_json_variant *reply) { +static int varlink_log_resolve_error(const char *name, const char *error_id, sd_json_variant *reply, bool warn_missing) { int r; assert(name); @@ -342,9 +342,13 @@ static int varlink_log_resolve_error(const char *name, const char *error_id, sd_ return log_error_errno(ret, "%s: resolve call failed: DNSSEC validation failed: %s%s", name, error.result, strempty(msg_extended)); if (error.rcode != _DNS_RCODE_INVALID) { - if (error.rcode == DNS_RCODE_NXDOMAIN) + if (error.rcode == DNS_RCODE_NXDOMAIN) { + if (!warn_missing) + return -ENXIO; + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "%s: resolve call failed: Name '%s' not found%s%s", name, error.query_string ?: name, error.ede_rcode >= 0 ? ":" : "", strempty(msg_extended)); + } return log_error_errno(ret, "%s: resolve call failed: Could not resolve '%s', server or network returned error: %s%s", name, error.query_string ?: name, FORMAT_DNS_RCODE(error.rcode), strempty(msg_extended)); @@ -403,7 +407,7 @@ static int resolve_host(const char *name) { ts = now(CLOCK_MONOTONIC) - ts; if (!isempty(error_id)) - return varlink_log_resolve_error(name, error_id, v); + return varlink_log_resolve_error(name, error_id, v, /* warn_missing = */ true); _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply reply = {}; r = dispatch_resolve_hostname_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); @@ -489,7 +493,7 @@ static int resolve_address(int family, const union in_addr_union *address, int i ts = now(CLOCK_MONOTONIC) - ts; if (!isempty(error_id)) - return varlink_log_resolve_error(pretty, error_id, v); + return varlink_log_resolve_error(pretty, error_id, v, /* warn_missing = */ true); _cleanup_(resolve_address_reply_done) ResolveAddressReply reply = {}; r = dispatch_resolve_address_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); @@ -520,15 +524,10 @@ static int resolve_address(int family, const union in_addr_union *address, int i return 0; } -static int output_rr_packet(const void *d, size_t l, int ifindex) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; +static int output_rr_packet(DnsResourceRecord *rr, int ifindex) { int r; - assert(d || l == 0); - - r = dns_resource_record_new_from_raw(&rr, d, l); - if (r < 0) - return log_error_errno(r, "Failed to parse RR: %m"); + assert(rr); if (sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; @@ -611,14 +610,7 @@ static bool single_label_nonsynthetic(const char *name) { return !streq(name, first_label); } -static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type, bool warn_missing) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *idnafied = NULL; - bool needs_authentication = false; - unsigned n = 0; - uint64_t flags; - usec_t ts; +static int resolve_record(const char *name, uint16_t class, uint16_t type, bool warn_missing) { int r; assert(name); @@ -629,6 +621,7 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_ log_notice("(Note that search domains are not appended when --type= is specified. " "Please specify fully qualified domain names, or remove --type= switch from invocation in order to request regular hostname resolution.)"); + _cleanup_free_ char *idnafied = NULL; r = idna_candidate(name, &idnafied); if (r < 0) return r; @@ -637,85 +630,76 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_ "Please specify translated domain names — i.e. '%s' — when resolving raw records, or remove --type= switch from invocation in order to request regular hostname resolution.", idnafied); - r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveRecord"); + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_with_query_timeout(&vl); if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - if (warn_missing || r != -ENXIO) - log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); return r; - } + + usec_t ts = now(CLOCK_MONOTONIC); + + const char *error_id = NULL; + sd_json_variant *v = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Resolve.ResolveRecord", + &v, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", name), + SD_JSON_BUILD_PAIR_UNSIGNED("type", type), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_ifindex > 0, "ifindex", arg_ifindex), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("class", class), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", arg_flags)); + if (r < 0) + return log_error_errno(r, "Failed to issue varlink call: %m"); ts = now(CLOCK_MONOTONIC) - ts; - r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); + if (!isempty(error_id)) { + if (streq(error_id, "io.systemd.Resolve.ResourceRecordTypeInvalidForQuery")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified resource record type %" PRIu16 " may not be used in a query", type); + + if (streq(error_id, "io.systemd.Resolve.ResourceRecordTypeObsolete")) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Specified DNS resource record type %" PRIu16 " is obsolete", type); + + return varlink_log_resolve_error(name, error_id, v, warn_missing); + } + + _cleanup_(resolve_record_reply_done) ResolveRecordReply reply = {}; + r = dispatch_resolve_record_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); if (r < 0) - return bus_log_parse_error(r); + return r; - while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { - uint16_t c, t; - int ifindex; - const void *d; - size_t l; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); + bool needs_authentication = false; + FOREACH_ARRAY(record, reply.records, reply.n_records) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + r = dns_resource_record_new_from_raw(&rr, record->raw.iov_base, record->raw.iov_len); if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &d, &l); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); + return log_error_errno(r, "Failed to parse RR: %m"); if (arg_raw == RAW_PACKET) { - uint64_t u64 = htole64(l); + uint64_t u64 = htole64(record->raw.iov_len); fwrite(&u64, sizeof(u64), 1, stdout); - fwrite(d, 1, l, stdout); + fwrite(record->raw.iov_base, 1, record->raw.iov_len, stdout); } else { - r = output_rr_packet(d, l, ifindex); + r = output_rr_packet(rr, record->ifindex); if (r < 0) return r; } - if (dns_type_needs_authentication(t)) + if (dns_type_needs_authentication(rr->key->type)) needs_authentication = true; - - n++; } - if (r < 0) - return bus_log_parse_error(r); - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (n == 0) { + if (reply.n_records == 0) { if (warn_missing) log_error("%s: no records found", name); return -ESRCH; } - print_source(flags, ts); + print_source(reply.flags, ts); - if ((flags & SD_RESOLVED_AUTHENTICATED) == 0 && needs_authentication) { + if ((reply.flags & SD_RESOLVED_AUTHENTICATED) == 0 && needs_authentication) { fflush(stdout); fprintf(stderr, "\n%s" @@ -730,12 +714,11 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_ return 0; } -static int resolve_rfc4501(sd_bus *bus, const char *name) { +static int resolve_rfc4501(const char *name) { uint16_t type = 0, class = 0; const char *p, *q, *n; int r; - assert(bus); assert(name); assert(startswith(name, "dns:")); @@ -833,7 +816,7 @@ static int resolve_rfc4501(sd_bus *bus, const char *name) { if (type == 0) type = arg_type ?: DNS_TYPE_A; - return resolve_record(bus, n, class, type, true); + return resolve_record(n, class, type, true); invalid: return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -843,21 +826,16 @@ invalid: VERB(verb_query, "query", "HOSTNAME|ADDRESS…", 2, VERB_ANY, 0, "Resolve domain names, IPv4 and IPv6 addresses"); static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int ret = 0, r; - r = acquire_bus(&bus); - if (r < 0) - return r; - if (arg_type != 0) STRV_FOREACH(p, strv_skip(argv, 1)) - RET_GATHER(ret, resolve_record(bus, *p, arg_class, arg_type, true)); + RET_GATHER(ret, resolve_record(*p, arg_class, arg_type, true)); else STRV_FOREACH(p, strv_skip(argv, 1)) { if (startswith(*p, "dns:")) - RET_GATHER(ret, resolve_rfc4501(bus, *p)); + RET_GATHER(ret, resolve_rfc4501(*p)); else { int family, ifindex; union in_addr_union a; @@ -1074,10 +1052,9 @@ static int verb_service(int argc, char *argv[], uintptr_t _data, void *userdata) } #if HAVE_OPENSSL -static int resolve_openpgp(sd_bus *bus, const char *address) { +static int resolve_openpgp(const char *address) { int r; - assert(bus); assert(address); const char *domain = strrchr(address, '@'); @@ -1108,7 +1085,6 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { log_debug("Looking up \"%s\".", full); r = resolve_record( - bus, full, arg_class ?: DNS_CLASS_IN, arg_type ?: DNS_TYPE_OPENPGPKEY, @@ -1128,7 +1104,6 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { log_debug("Looking up \"%s\".", full); return resolve_record( - bus, full, arg_class ?: DNS_CLASS_IN, arg_type ?: DNS_TYPE_OPENPGPKEY, @@ -1140,18 +1115,13 @@ VERB(verb_openpgp, "openpgp", "EMAIL@DOMAIN…", 2, VERB_ANY, 0, "Query OpenPGP public key"); static int verb_openpgp(int argc, char *argv[], uintptr_t _data, void *userdata) { #if HAVE_OPENSSL - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r, ret = 0; - - r = acquire_bus(&bus); - if (r < 0) - return r; + int ret = 0; if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); STRV_FOREACH(p, strv_skip(argv, 1)) - RET_GATHER(ret, resolve_openpgp(bus, *p)); + RET_GATHER(ret, resolve_openpgp(*p)); return ret; #else @@ -1159,13 +1129,12 @@ static int verb_openpgp(int argc, char *argv[], uintptr_t _data, void *userdata) #endif } -static int resolve_tlsa(sd_bus *bus, const char *family, const char *address) { +static int resolve_tlsa(const char *family, const char *address) { const char *port; uint16_t port_num = 443; _cleanup_free_ char *full = NULL; int r; - assert(bus); assert(address); port = strrchr(address, ':'); @@ -1186,7 +1155,7 @@ static int resolve_tlsa(sd_bus *bus, const char *family, const char *address) { log_debug("Looking up \"%s\".", full); - return resolve_record(bus, full, + return resolve_record(full, arg_class ?: DNS_CLASS_IN, arg_type ?: DNS_TYPE_TLSA, true); } @@ -1198,17 +1167,12 @@ static bool service_family_is_valid(const char *s) { VERB(verb_tlsa, "tlsa", "DOMAIN[:PORT]…", 2, VERB_ANY, 0, "Query TLS public key"); static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; const char *family = "tcp"; char **args; - int r, ret = 0; + int ret = 0; assert(argc >= 2); - r = acquire_bus(&bus); - if (r < 0) - return r; - if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); @@ -1219,7 +1183,7 @@ static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { args = strv_skip(argv, 1); STRV_FOREACH(p, args) - RET_GATHER(ret, resolve_tlsa(bus, family, *p)); + RET_GATHER(ret, resolve_tlsa(family, *p)); return ret; } diff --git a/src/shared/resolve-varlink-util.c b/src/shared/resolve-varlink-util.c index 877e298999..c3a638f3ad 100644 --- a/src/shared/resolve-varlink-util.c +++ b/src/shared/resolve-varlink-util.c @@ -3,6 +3,7 @@ #include "sd-json.h" #include "dns-packet.h" +#include "iovec-util.h" #include "json-util.h" #include "resolve-varlink-util.h" @@ -175,3 +176,74 @@ int dispatch_resolve_address_reply(const char *name, sd_json_variant *variant, s *ret = TAKE_STRUCT(reply); return 0; } + +static void resolved_record_done(ResolvedRecord *record) { + if (!record) + return; + + iovec_done(&record->raw); +} + +static int dispatch_resolved_record(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_record_dispatch_table[] = { + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(ResolvedRecord, ifindex), SD_JSON_RELAX }, + { "raw", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_unbase64_iovec, offsetof(ResolvedRecord, raw), SD_JSON_MANDATORY }, + { "rr", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + {}, + }; + ResolvedRecord *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolved_record_done) ResolvedRecord record = {}; + r = sd_json_dispatch(variant, resolved_record_dispatch_table, flags & ~SD_JSON_MANDATORY, &record); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(record); + return 0; +} + +static int dispatch_resolved_record_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolveRecordReply *reply = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(reply->records, reply->n_records + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_record(name, v, flags, &reply->records[reply->n_records++]); + if (r < 0) + return r; + } + + return 0; +} + +void resolve_record_reply_done(ResolveRecordReply *reply) { + if (!reply) + return; + + FOREACH_ARRAY(record, reply->records, reply->n_records) + resolved_record_done(record); + reply->records = mfree(reply->records); + reply->n_records = 0; +} + +int dispatch_resolve_record_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_record_reply_dispatch_table[] = { + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveRecordReply, flags), SD_JSON_MANDATORY }, + { "rrs", SD_JSON_VARIANT_ARRAY, dispatch_resolved_record_array, 0, SD_JSON_MANDATORY }, + {}, + }; + ResolveRecordReply *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_record_reply_done) ResolveRecordReply reply = {}; + r = sd_json_dispatch(variant, resolve_record_reply_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &reply); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(reply); + return 0; +} diff --git a/src/shared/resolve-varlink-util.h b/src/shared/resolve-varlink-util.h index 9fbc8a1492..58e8f4d918 100644 --- a/src/shared/resolve-varlink-util.h +++ b/src/shared/resolve-varlink-util.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "in-addr-util.h" #include "shared-forward.h" @@ -47,3 +49,18 @@ typedef struct ResolveAddressReply { void resolve_address_reply_done(ResolveAddressReply *reply); int dispatch_resolve_address_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +typedef struct ResolvedRecord { + int ifindex; + struct iovec raw; +} ResolvedRecord; + +typedef struct ResolveRecordReply { + uint64_t flags; + ResolvedRecord *records; + size_t n_records; +} ResolveRecordReply; + +void resolve_record_reply_done(ResolveRecordReply *reply); + +int dispatch_resolve_record_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); From 3ecf3aba94f1a248c0e46aa3772b5d1fdd043c8d Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 19 Jun 2026 15:01:02 -0400 Subject: [PATCH 7/9] test: relax grep for DNS query refusal The punctuation is not strictly important for the error. --- test/units/TEST-75-RESOLVED.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index 7a740a1c2e..d9757b3855 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -1258,13 +1258,13 @@ testcase_14_refuse_record_types() { grep -qF "127.128.0.5" "$RUN_OUT" (! run resolvectl query localhost5 --type=SRV) - grep -qF "DNS query type refused." "$RUN_OUT" + grep -qF "DNS query type refused" "$RUN_OUT" (! run resolvectl query localhost5 --type=TXT) - grep -qF "DNS query type refused." "$RUN_OUT" + grep -qF "DNS query type refused" "$RUN_OUT" (! run resolvectl query localhost5 --type=AAAA) - grep -qF "DNS query type refused." "$RUN_OUT" + grep -qF "DNS query type refused" "$RUN_OUT" run resolvectl query localhost5 --type=A grep -qF "127.128.0.5" "$RUN_OUT" @@ -1304,7 +1304,7 @@ testcase_14_refuse_record_types() { grep -qF "does not have any RR of the requested type" "$RUN_OUT" (! run resolvectl query localhost5 --type=AAAA) - grep -qF "DNS query type refused." "$RUN_OUT" + grep -qF "DNS query type refused" "$RUN_OUT" run resolvectl service _mysvc._tcp signed.test grep -qF "myservice.signed.test:1234" "$RUN_OUT" From 4e1943a209dc3f7e1bcc0c340a242286b62f496d Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 19 Jun 2026 15:01:02 -0400 Subject: [PATCH 8/9] nss-resolve: use resolve-varlink-util.h helpers Re-factor using the shared code introduced for resolvectl. --- src/nss-resolve/nss-resolve.c | 241 +++++++--------------------------- 1 file changed, 45 insertions(+), 196 deletions(-) diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c index 9f4225be3f..55e4216f53 100644 --- a/src/nss-resolve/nss-resolve.c +++ b/src/nss-resolve/nss-resolve.c @@ -14,6 +14,7 @@ #include "json-util.h" #include "netlink-util.h" #include "nss-util.h" +#include "resolve-varlink-util.h" #include "resolved-def.h" #include "signal-util.h" #include "string-util.h" @@ -64,90 +65,6 @@ static int connect_to_resolved(sd_varlink **ret) { return 0; } -static uint32_t ifindex_to_scopeid(int family, const void *a, int ifindex) { - struct in6_addr in6; - - if (family != AF_INET6 || ifindex == 0) - return 0; - - /* Some apps can't deal with the scope ID attached to non-link-local addresses. Hence, let's suppress that. */ - - assert(sizeof(in6) == FAMILY_ADDRESS_SIZE(AF_INET6)); - memcpy(&in6, a, sizeof(struct in6_addr)); - - return in6_addr_is_link_local(&in6) ? ifindex : 0; -} - -typedef struct ResolveHostnameReply { - sd_json_variant *addresses; - char *name; - uint64_t flags; -} ResolveHostnameReply; - -static void resolve_hostname_reply_destroy(ResolveHostnameReply *p) { - assert(p); - - sd_json_variant_unref(p->addresses); - free(p->name); -} - -static const sd_json_dispatch_field resolve_hostname_reply_dispatch_table[] = { - { "addresses", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(ResolveHostnameReply, addresses), SD_JSON_MANDATORY }, - { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveHostnameReply, name), 0 }, - { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveHostnameReply, flags), 0 }, - {} -}; - -typedef struct AddressParameters { - int ifindex; - int family; - union in_addr_union address; - size_t address_size; -} AddressParameters; - -static int json_dispatch_address(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - AddressParameters *p = ASSERT_PTR(userdata); - union in_addr_union buf = {}; - sd_json_variant *i; - size_t n, k = 0; - - assert(variant); - - if (!sd_json_variant_is_array(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); - - n = sd_json_variant_elements(variant); - if (!IN_SET(n, 4, 16)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); - - JSON_VARIANT_ARRAY_FOREACH(i, variant) { - int64_t b; - - 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)); - - 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)); - - buf.bytes[k++] = (uint8_t) b; - } - - p->address = buf; - p->address_size = k; - - return 0; -} - -static const sd_json_dispatch_field address_parameters_dispatch_table[] = { - { "ifindex", SD_JSON_VARIANT_INTEGER, json_dispatch_ifindex, offsetof(AddressParameters, ifindex), 0 }, - { "family", SD_JSON_VARIANT_INTEGER, json_dispatch_address_family, offsetof(AddressParameters, family), SD_JSON_MANDATORY }, - { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_address, 0, SD_JSON_MANDATORY }, - {} -}; - static uint64_t query_flag( const char *name, const int value, @@ -191,6 +108,19 @@ static int query_ifindex(void) { return ifindex; } +static uint32_t ifindex_to_scopeid(ResolvedAddress *addr) { + assert(addr); + + if (addr->family != AF_INET6 || addr->ifindex == 0) + return 0; + + /* Some apps can't deal with the scope ID attached to non-link-local addresses. Hence, let's suppress that. */ + if (in6_addr_is_link_local(&addr->in_addr.address.in6)) + return addr->ifindex; + + return 0; +} + enum nss_status _nss_resolve_gethostbyname4_r( const char *name, struct gaih_addrtuple **pat, @@ -200,8 +130,8 @@ enum nss_status _nss_resolve_gethostbyname4_r( _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *cparams = NULL; - _cleanup_(resolve_hostname_reply_destroy) ResolveHostnameReply p = {}; - sd_json_variant *rparams, *entry; + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply p = {}; + sd_json_variant *rparams; int r; PROTECT_ERRNO; @@ -244,28 +174,17 @@ enum nss_status _nss_resolve_gethostbyname4_r( goto not_found; } - r = sd_json_dispatch(rparams, resolve_hostname_reply_dispatch_table, nss_json_dispatch_flags, &p); + r = dispatch_resolve_hostname_reply(NULL, rparams, nss_json_dispatch_flags, &p); if (r < 0) goto fail; - if (sd_json_variant_is_blank_array(p.addresses)) + if (p.n_addresses == 0) goto not_found; size_t n_addresses = 0; - JSON_VARIANT_ARRAY_FOREACH(entry, p.addresses) { - AddressParameters q = {}; - - r = sd_json_dispatch(entry, address_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - if (!IN_SET(q.family, AF_INET, AF_INET6)) + FOREACH_ARRAY(entry, p.addresses, p.n_addresses) { + if (!IN_SET(entry->family, AF_INET, AF_INET6)) continue; - if (q.address_size != FAMILY_ADDRESS_SIZE(q.family)) { - r = -EINVAL; - goto fail; - } - n_addresses++; } @@ -292,22 +211,16 @@ enum nss_status _nss_resolve_gethostbyname4_r( struct gaih_addrtuple *r_tuple = NULL, *r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); - JSON_VARIANT_ARRAY_FOREACH(entry, p.addresses) { - AddressParameters q = {}; - - r = sd_json_dispatch(entry, address_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - if (!IN_SET(q.family, AF_INET, AF_INET6)) + FOREACH_ARRAY(entry, p.addresses, p.n_addresses) { + if (!IN_SET(entry->family, AF_INET, AF_INET6)) continue; r_tuple = (struct gaih_addrtuple*) (buffer + idx); r_tuple->next = (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); r_tuple->name = r_name; - r_tuple->family = q.family; - r_tuple->scopeid = ifindex_to_scopeid(q.family, &q.address, q.ifindex); - memcpy(r_tuple->addr, &q.address, q.address_size); + r_tuple->family = entry->family; + r_tuple->scopeid = ifindex_to_scopeid(entry); + memcpy(r_tuple->addr, entry->in_addr.address.bytes, FAMILY_ADDRESS_SIZE_SAFE(entry->family)); idx += ALIGN(sizeof(struct gaih_addrtuple)); } @@ -364,8 +277,8 @@ enum nss_status _nss_resolve_gethostbyname3_r( _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *cparams = NULL; - _cleanup_(resolve_hostname_reply_destroy) ResolveHostnameReply p = {}; - sd_json_variant *rparams, *entry; + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply p = {}; + sd_json_variant *rparams; int r; PROTECT_ERRNO; @@ -412,28 +325,17 @@ enum nss_status _nss_resolve_gethostbyname3_r( goto not_found; } - r = sd_json_dispatch(rparams, resolve_hostname_reply_dispatch_table, nss_json_dispatch_flags, &p); + r = dispatch_resolve_hostname_reply(NULL, rparams, nss_json_dispatch_flags, &p); if (r < 0) goto fail; - if (sd_json_variant_is_blank_array(p.addresses)) + if (p.n_addresses == 0) goto no_data; size_t n_addresses = 0; - JSON_VARIANT_ARRAY_FOREACH(entry, p.addresses) { - AddressParameters q = {}; - - r = sd_json_dispatch(entry, address_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - if (q.family != af) + FOREACH_ARRAY(entry, p.addresses, p.n_addresses) { + if (entry->family != af) continue; - if (q.address_size != FAMILY_ADDRESS_SIZE(q.family)) { - r = -EINVAL; - goto fail; - } - n_addresses++; } @@ -468,22 +370,16 @@ enum nss_status _nss_resolve_gethostbyname3_r( char *r_addr = buffer + idx; size_t i = 0; - JSON_VARIANT_ARRAY_FOREACH(entry, p.addresses) { - AddressParameters q = {}; - - r = sd_json_dispatch(entry, address_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - if (q.family != af) + FOREACH_ARRAY(entry, p.addresses, p.n_addresses) { + if (entry->family != af) continue; - if (q.address_size != alen) { + if (FAMILY_ADDRESS_SIZE_SAFE(entry->family) != alen) { r = -EINVAL; goto fail; } - memcpy(r_addr + i*ALIGN(alen), &q.address, alen); + memcpy(r_addr + i*ALIGN(alen), entry->in_addr.address.bytes, alen); i++; } @@ -540,40 +436,6 @@ try_again: return NSS_STATUS_TRYAGAIN; } -typedef struct ResolveAddressReply { - sd_json_variant *names; - uint64_t flags; -} ResolveAddressReply; - -static void resolve_address_reply_destroy(ResolveAddressReply *p) { - assert(p); - - sd_json_variant_unref(p->names); -} - -static const sd_json_dispatch_field resolve_address_reply_dispatch_table[] = { - { "names", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(ResolveAddressReply, names), SD_JSON_MANDATORY }, - { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveAddressReply, flags), 0 }, - {} -}; - -typedef struct NameParameters { - int ifindex; - char *name; -} NameParameters; - -static void name_parameters_destroy(NameParameters *p) { - assert(p); - - free(p->name); -} - -static const sd_json_dispatch_field name_parameters_dispatch_table[] = { - { "ifindex", SD_JSON_VARIANT_INTEGER, json_dispatch_ifindex, offsetof(NameParameters, ifindex), 0 }, - { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(NameParameters, name), SD_JSON_MANDATORY }, - {} -}; - enum nss_status _nss_resolve_gethostbyaddr2_r( const void* addr, socklen_t len, int af, @@ -584,8 +446,8 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *cparams = NULL; - _cleanup_(resolve_address_reply_destroy) ResolveAddressReply p = {}; - sd_json_variant *rparams, *entry; + _cleanup_(resolve_address_reply_done) ResolveAddressReply p = {}; + sd_json_variant *rparams; int r; PROTECT_ERRNO; @@ -634,25 +496,18 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( goto not_found; } - r = sd_json_dispatch(rparams, resolve_address_reply_dispatch_table, nss_json_dispatch_flags, &p); + r = dispatch_resolve_address_reply(NULL, rparams, nss_json_dispatch_flags, &p); if (r < 0) goto fail; - if (sd_json_variant_is_blank_array(p.names)) + if (p.n_names == 0) goto not_found; size_t ms = 0, idx; - JSON_VARIANT_ARRAY_FOREACH(entry, p.names) { - _cleanup_(name_parameters_destroy) NameParameters q = {}; + FOREACH_ARRAY(entry, p.names, p.n_names) + ms += ALIGN(strlen(entry->name) + 1); - r = sd_json_dispatch(entry, name_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - ms += ALIGN(strlen(q.name) + 1); - } - - size_t n_names = sd_json_variant_elements(p.names); + size_t n_names = p.n_names; ms += ALIGN(len) + /* the address */ 2 * sizeof(char*) + /* pointer to the address, plus trailing NULL */ n_names * sizeof(char*); /* pointers to aliases, plus trailing NULL */ @@ -683,16 +538,10 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( char *r_name = buffer + idx; size_t i = 0; - JSON_VARIANT_ARRAY_FOREACH(entry, p.names) { - _cleanup_(name_parameters_destroy) NameParameters q = {}; - - r = sd_json_dispatch(entry, name_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - size_t l = strlen(q.name); + FOREACH_ARRAY(entry, p.names, p.n_names) { + size_t l = strlen(entry->name); char *z = buffer + idx; - memcpy(z, q.name, l + 1); + memcpy(z, entry->name, l + 1); if (i > 0) ((char**) r_aliases)[i - 1] = z; From fa6fe468f4108ba835c5cd3b34ee888d925b0d17 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 19 Jun 2026 15:01:03 -0400 Subject: [PATCH 9/9] resolvectl: use varlink instead of dbus for ResolveService --- src/resolve/resolvectl.c | 190 +++++++++--------------------- src/shared/resolve-varlink-util.c | 127 ++++++++++++++++++++ src/shared/resolve-varlink-util.h | 28 +++++ 3 files changed, 212 insertions(+), 133 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 327cf10140..af7ed43672 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -18,7 +18,6 @@ #include "bus-common-errors.h" #include "bus-error.h" #include "bus-locator.h" -#include "bus-message-util.h" #include "bus-util.h" #include "crypto-util.h" #include "dns-configuration.h" @@ -27,7 +26,6 @@ #include "dns-rr.h" #include "errno-list.h" #include "errno-util.h" -#include "escape.h" #include "format-ifname.h" #include "format-table.h" #include "glyph-util.h" @@ -851,18 +849,9 @@ static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { return ret; } -static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { - const char *canonical_name, *canonical_type, *canonical_domain; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - size_t indent, sz; - uint64_t flags; - const char *p; - unsigned c; - usec_t ts; +static int resolve_service(const char *name, const char *type, const char *domain) { int r; - assert(bus); assert(domain); name = empty_to_null(name); @@ -875,141 +864,83 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons else log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(arg_ifname) ? "*" : arg_ifname); - r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveService"); + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_with_query_timeout(&vl); if (r < 0) - return bus_log_create_error(r); + return r; - r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); + usec_t ts = now(CLOCK_MONOTONIC); + + const char *error_id = NULL; + sd_json_variant *v = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Resolve.ResolveService", + &v, + &error_id, + SD_JSON_BUILD_PAIR_STRING("domain", domain), + JSON_BUILD_PAIR_STRING_NON_EMPTY("name", name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("type", type), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_ifindex > 0, "ifindex", arg_ifindex), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_family != AF_UNSPEC, "family", arg_family), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", arg_flags)); if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Failed to issue varlink call: %m"); ts = now(CLOCK_MONOTONIC) - ts; - r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); + if (!isempty(error_id)) + return varlink_log_resolve_error(domain, error_id, v, /* warn_missing = */ true); + + _cleanup_(resolve_service_reply_done) ResolveServiceReply reply = {}; + r = dispatch_resolve_service_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); if (r < 0) - return bus_log_parse_error(r); + return r; - indent = - (name ? strlen(name) + 1 : 0) + - (type ? strlen(type) + 1 : 0) + - strlen(domain) + 2; - - c = 0; - while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { - uint16_t priority, weight, port; - const char *hostname, *canonical; - - r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); - if (r < 0) - return bus_log_parse_error(r); + size_t indent = (name ? strlen(name) + 1 : 0) + + (type ? strlen(type) + 1 : 0) + + strlen(domain) + 2; + bool first = true; + FOREACH_ARRAY(service, reply.services, reply.n_services) { if (name) - printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); + printf("%*s%s", (int) strlen(name), first ? name : "", first ? "/" : " "); if (type) - printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); + printf("%*s%s", (int) strlen(type), first ? type : "", first ? "/" : " "); printf("%*s%s %s:%u [priority=%u, weight=%u]\n", - (int) strlen(domain), c == 0 ? domain : "", - c == 0 ? ":" : " ", - hostname, port, - priority, weight); + (int) strlen(domain), + first ? domain : "", + first ? ":" : " ", + service->hostname, + service->port, + service->priority, + service->weight); - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + FOREACH_ARRAY(address, service->addresses, service->n_addresses) { _cleanup_free_ char *pretty = NULL; - int ifindex, family, k; - union in_addr_union a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "i", &ifindex); - if (r < 0) - return bus_log_parse_error(r); - - sd_bus_error_free(&error); - r = bus_message_read_in_addr_auto(reply, &error, &family, &a); - if (r < 0 && !sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) - return log_error_errno(r, "%s: systemd-resolved returned invalid result: %s", name, bus_error_message(&error, r)); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) { - log_debug_errno(r, "%s: systemd-resolved returned invalid result, ignoring: %s", name, bus_error_message(&error, r)); - continue; - } - - r = in_addr_ifindex_to_string(family, &a, ifindex, &pretty); + r = in_addr_ifindex_to_string(address->family, &address->in_addr.address, address->ifindex, &pretty); if (r < 0) return log_error_errno(r, "Failed to print address for %s: %m", name); - k = printf("%*s%s", (int) indent, "", pretty); - print_ifindex_comment(k, ifindex); + int k = printf("%*s%s", (int) indent, "", pretty); + + print_ifindex_comment(k, address->ifindex); fputc('\n', stdout); } - if (r < 0) - return bus_log_parse_error(r); - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); + if (service->canonical_name && !streq_ptr(service->hostname, service->canonical_name)) + printf("%*s(%s)\n", (int) indent, "", service->canonical_name); - r = sd_bus_message_read(reply, "s", &canonical); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(hostname, canonical)) - printf("%*s(%s)\n", (int) indent, "", canonical); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - c++; + first = false; } - if (r < 0) - return bus_log_parse_error(r); - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); + STRV_FOREACH(p, reply.txt) + printf("%*s%s\n", (int) indent, "", *p); - r = sd_bus_message_enter_container(reply, 'a', "ay"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { - _cleanup_free_ char *escaped = NULL; - - escaped = cescape_length(p, sz); - if (!escaped) - return log_oom(); - - printf("%*s%s\n", (int) indent, "", escaped); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); - if (r < 0) - return bus_log_parse_error(r); - - canonical_name = empty_to_null(canonical_name); - canonical_type = empty_to_null(canonical_type); + const char *canonical_name = empty_to_null(reply.canonical.name); + const char *canonical_type = empty_to_null(reply.canonical.type); + const char *canonical_domain = reply.canonical.domain; if (!streq_ptr(name, canonical_name) || !streq_ptr(type, canonical_type) || @@ -1025,7 +956,7 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons printf("%s)\n", canonical_domain); } - print_source(flags, ts); + print_source(reply.flags, ts); return 0; } @@ -1033,22 +964,15 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons VERB(verb_service, "service", "[[NAME] TYPE] DOMAIN", 2, 4, 0, "Resolve service (SRV)"); static int verb_service(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); if (argc == 2) - return resolve_service(bus, NULL, NULL, argv[1]); + return resolve_service(NULL, NULL, argv[1]); else if (argc == 3) - return resolve_service(bus, NULL, argv[1], argv[2]); + return resolve_service(NULL, argv[1], argv[2]); else - return resolve_service(bus, argv[1], argv[2], argv[3]); + return resolve_service(argv[1], argv[2], argv[3]); } #if HAVE_OPENSSL diff --git a/src/shared/resolve-varlink-util.c b/src/shared/resolve-varlink-util.c index c3a638f3ad..779770a523 100644 --- a/src/shared/resolve-varlink-util.c +++ b/src/shared/resolve-varlink-util.c @@ -6,6 +6,7 @@ #include "iovec-util.h" #include "json-util.h" #include "resolve-varlink-util.h" +#include "strv.h" void resolve_error_done(ResolveError *error) { if (!error) @@ -247,3 +248,129 @@ int dispatch_resolve_record_reply(const char *name, sd_json_variant *variant, sd *ret = TAKE_STRUCT(reply); return 0; } + +static void resolved_service_done(ResolvedService *service) { + if (!service) + return; + + service->hostname = mfree(service->hostname); + service->canonical_name = mfree(service->canonical_name); + service->addresses = mfree(service->addresses); + service->n_addresses = 0; +} + +static int dispatch_resolved_service_address_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolvedService *service = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(service->addresses, service->n_addresses + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_address(name, v, flags, &service->addresses[service->n_addresses++]); + if (r < 0) + return r; + } + + return 0; +} + +static int dispatch_resolved_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_service_dispatch_table[] = { + { "priority", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(ResolvedService, priority), SD_JSON_MANDATORY }, + { "weight", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(ResolvedService, weight), SD_JSON_MANDATORY }, + { "port", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(ResolvedService, port), SD_JSON_MANDATORY }, + { "hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedService, hostname), SD_JSON_MANDATORY }, + { "canonicalName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedService, canonical_name), SD_JSON_NULLABLE }, + { "addresses", SD_JSON_VARIANT_ARRAY, dispatch_resolved_service_address_array, 0, SD_JSON_NULLABLE }, + {}, + }; + ResolvedService *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolved_service_done) ResolvedService service = {}; + r = sd_json_dispatch(variant, resolved_service_dispatch_table, flags & ~SD_JSON_MANDATORY, &service); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(service); + return 0; +} + +static int dispatch_resolved_service_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolveServiceReply *reply = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(reply->services, reply->n_services + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_service(name, v, flags, &reply->services[reply->n_services++]); + if (r < 0) + return r; + } + + return 0; +} + +static void resolved_canonical_done(ResolvedCanonical *canonical) { + if (!canonical) + return; + + canonical->name = mfree(canonical->name); + canonical->type = mfree(canonical->type); + canonical->domain = mfree(canonical->domain); +} + +static int dispatch_resolved_canonical(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_canonical_dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedCanonical, name), SD_JSON_NULLABLE }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedCanonical, type), SD_JSON_MANDATORY }, + { "domain", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedCanonical, domain), SD_JSON_MANDATORY }, + {}, + }; + ResolvedCanonical *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolved_canonical_done) ResolvedCanonical canonical = {}; + r = sd_json_dispatch(variant, resolved_canonical_dispatch_table, flags & ~SD_JSON_MANDATORY, &canonical); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(canonical); + return 0; +} + +void resolve_service_reply_done(ResolveServiceReply *reply) { + if (!reply) + return; + + FOREACH_ARRAY(service, reply->services, reply->n_services) + resolved_service_done(service); + reply->services = mfree(reply->services); + reply->n_services = 0; + reply->txt = strv_free(reply->txt); + resolved_canonical_done(&reply->canonical); +} + +int dispatch_resolve_service_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_service_reply_dispatch_table[] = { + { "services", SD_JSON_VARIANT_ARRAY, dispatch_resolved_service_array, 0, SD_JSON_MANDATORY }, + { "txt", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(ResolveServiceReply, txt), SD_JSON_NULLABLE }, + { "canonical", SD_JSON_VARIANT_OBJECT, dispatch_resolved_canonical, offsetof(ResolveServiceReply, canonical), SD_JSON_MANDATORY }, + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveServiceReply, flags), SD_JSON_MANDATORY }, + {}, + }; + ResolveServiceReply *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_service_reply_done) ResolveServiceReply reply = {}; + r = sd_json_dispatch(variant, resolve_service_reply_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &reply); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(reply); + return 0; +} diff --git a/src/shared/resolve-varlink-util.h b/src/shared/resolve-varlink-util.h index 58e8f4d918..a05e18daea 100644 --- a/src/shared/resolve-varlink-util.h +++ b/src/shared/resolve-varlink-util.h @@ -64,3 +64,31 @@ typedef struct ResolveRecordReply { void resolve_record_reply_done(ResolveRecordReply *reply); int dispatch_resolve_record_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +typedef struct ResolvedService { + uint16_t priority; + uint16_t weight; + uint16_t port; + char *hostname; + char *canonical_name; + ResolvedAddress *addresses; + size_t n_addresses; +} ResolvedService; + +typedef struct ResolvedCanonical { + char *name; + char *type; + char *domain; +} ResolvedCanonical; + +typedef struct ResolveServiceReply { + ResolvedService *services; + size_t n_services; + char **txt; + ResolvedCanonical canonical; + uint64_t flags; +} ResolveServiceReply; + +void resolve_service_reply_done(ResolveServiceReply *reply); + +int dispatch_resolve_service_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);