diff --git a/extra-repos/systemd/systemd-stage0/APKBUILD b/extra-repos/systemd/systemd-stage0/APKBUILD index b0a37ef95..ddfb7738a 100644 --- a/extra-repos/systemd/systemd-stage0/APKBUILD +++ b/extra-repos/systemd/systemd-stage0/APKBUILD @@ -5,7 +5,7 @@ pkgname=systemd-stage0 pkgver=261.2 _pkgver="${pkgver/_/-}" # pkgrel must match systemd! -pkgrel=0 +pkgrel=1 pkgdesc="Minimal systemd bootstrap package" url="https://github.com/systemd/systemd" # armhf: pmb#2618 diff --git a/extra-repos/systemd/systemd/0001-json-util-generalize-json_dispatch_address_family.patch b/extra-repos/systemd/systemd/0001-json-util-generalize-json_dispatch_address_family.patch new file mode 100644 index 000000000..00e3bb9e4 --- /dev/null +++ b/extra-repos/systemd/systemd/0001-json-util-generalize-json_dispatch_address_family.patch @@ -0,0 +1,249 @@ +From c5f08a2e5cf1f246c908ddaaefbccdcfbdbaa754 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Fri, 19 Jun 2026 15:00:59 -0400 +Subject: [PATCH] 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); +-- +2.55.0 + diff --git a/extra-repos/systemd/systemd/0001-json-util-introduce-json_dispatch_in_addr_data.patch b/extra-repos/systemd/systemd/0001-json-util-introduce-json_dispatch_in_addr_data.patch new file mode 100644 index 000000000..7cf48e726 --- /dev/null +++ b/extra-repos/systemd/systemd/0001-json-util-introduce-json_dispatch_in_addr_data.patch @@ -0,0 +1,305 @@ +From 3f73f1ef75d5c8da66eda2cff270676e70becf9b Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Tue, 23 Jun 2026 12:02:19 -0400 +Subject: [PATCH] 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); +-- +2.55.0 + diff --git a/extra-repos/systemd/systemd/0001-resolvectl-use-varlink-instead-of-dbus-for-ResolveHo.patch b/extra-repos/systemd/systemd/0001-resolvectl-use-varlink-instead-of-dbus-for-ResolveHo.patch new file mode 100644 index 000000000..6f72cbd33 --- /dev/null +++ b/extra-repos/systemd/systemd/0001-resolvectl-use-varlink-instead-of-dbus-for-ResolveHo.patch @@ -0,0 +1,440 @@ +From 35d611622a0c4277e1a3e0b8866a800c0fc6fcf8 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Fri, 19 Jun 2026 15:01:00 -0400 +Subject: [PATCH] resolvectl: use varlink instead of dbus for ResolveHostname + +--- + src/resolve/resolvectl.c | 202 +++++++++++++++++++----------- + src/shared/meson.build | 1 + + src/shared/resolve-varlink-util.c | 107 ++++++++++++++++ + src/shared/resolve-varlink-util.h | 34 +++++ + 4 files changed, 274 insertions(+), 70 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; ++ ++ 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); + +- return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(error, r)); ++ 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"); +- if (r < 0) +- return bus_log_create_error(r); +- +- r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); ++ _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; + +- ts = now(CLOCK_MONOTONIC); ++ usec_t ts = now(CLOCK_MONOTONIC); + +- r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); ++ 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 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); +-- +2.55.0 + diff --git a/extra-repos/systemd/systemd/43380.patch b/extra-repos/systemd/systemd/43380.patch new file mode 100644 index 000000000..8814f9dd0 --- /dev/null +++ b/extra-repos/systemd/systemd/43380.patch @@ -0,0 +1,418 @@ +From 98e7cc36f019e48a3f20db9787ccd38d899957f5 Mon Sep 17 00:00:00 2001 +From: Clayton Craft +Date: Thu, 13 Aug 2026 13:51:30 -0700 +Subject: [PATCH 1/4] timesyncd: resolve NTP hostnames through resolved's + varlink interface + +With DNSSEC on, resolved can't validate anything until the clock is +right, and without an RTC the only thing that fixes the clock is talking +to the NTP servers we're trying to resolve. On glibc the timesyncd unit +sets SYSTEMD_NSS_RESOLVE_VALIDATE=0 and nss-resolve turns that into a +per-query flag, which breaks the chicken/egg. musl (and other systems +not using nss-resolve) have no NSS, so nothing reads it there and we're +stuck. + +So talk to resolved directly when it's there, and pass +SD_RESOLVED_NO_VALIDATE until we've synced the clock ourselves. +NTP-related lookups after that are validated normally, which is better +than what we get from nss. If resolved isn't around we just keep using +getaddrinfo(). + +The reply handler duplicates the tail of the getaddrinfo handler, since +the two differ in how they report failures. Varlink discards what the +callback returns, so it has to move on to the next host itself. + +This also drops SYSTEMD_NSS_RESOLVE_VALIDATE=0 from the unit because +nss-resolve talks to resolved over the same socket we do, so the +getaddrinfo() path is only ever taken when resolved is unreachable, and +then nothing validates anyway. +--- + src/timesync/timesyncd-manager.c | 136 +++++++++++++++++++++++++++-- + src/timesync/timesyncd-manager.h | 1 + + units/systemd-timesyncd.service.in | 4 - + 3 files changed, 132 insertions(+), 9 deletions(-) + +diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c +index b0f22516dfe3..7b2ed4f831a0 100644 +--- a/src/timesync/timesyncd-manager.c ++++ b/src/timesync/timesyncd-manager.c +@@ -10,8 +10,10 @@ + + #include "sd-bus.h" + #include "sd-daemon.h" ++#include "sd-json.h" + #include "sd-messages.h" + #include "sd-network.h" ++#include "sd-varlink.h" + + #include "alloc-util.h" + #include "clock-util.h" +@@ -23,6 +25,7 @@ + #include "format-util.h" + #include "fs-util.h" + #include "hashmap.h" ++#include "in-addr-util.h" + #include "list.h" + #include "log.h" + #include "logarithm.h" +@@ -30,6 +33,8 @@ + #include "random-util.h" + #include "ratelimit.h" + #include "resolve-private.h" ++#include "resolve-varlink-util.h" ++#include "resolved-def.h" + #include "socket-util.h" + #include "string-util.h" + #include "strv.h" +@@ -780,6 +785,119 @@ static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct ad + return manager_begin(m); + } + ++static int manager_resolve_varlink_reply( ++ sd_varlink *link, ++ sd_json_variant *parameters, ++ const char *error_id, ++ sd_varlink_reply_flags_t flags, ++ void *userdata) { ++ ++ Manager *m = ASSERT_PTR(userdata); ++ int r; ++ ++ assert(m->current_server_name); ++ ++ _unused_ _cleanup_(sd_varlink_unrefp) sd_varlink *link_free = TAKE_PTR(m->resolve_varlink); ++ ++ if (!isempty(error_id)) { ++ log_debug("Failed to resolve %s: %s", m->current_server_name->string, error_id); ++ ++ /* Try next one */ ++ return manager_connect(m); ++ } ++ ++ _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply reply = {}; ++ r = dispatch_resolve_hostname_reply(/* name = */ NULL, parameters, SD_JSON_LOG, &reply); ++ if (r < 0) { ++ log_warning_errno(r, "Failed to parse reply for %s, ignoring: %m", m->current_server_name->string); ++ ++ return manager_connect(m); ++ } ++ ++ FOREACH_ARRAY(entry, reply.addresses, reply.n_addresses) { ++ _cleanup_free_ char *pretty = NULL; ++ union sockaddr_union sa = {}; ++ ServerAddress *a; ++ ++ if (!IN_SET(entry->family, AF_INET, AF_INET6)) { ++ log_debug("Ignoring unsuitable address protocol for %s.", m->current_server_name->string); ++ continue; ++ } ++ ++ r = sockaddr_set_in_addr(&sa, entry->family, &entry->in_addr.address, 123); ++ if (r < 0) { ++ log_warning_errno(r, "Failed to convert address for %s, trying next host: %m", m->current_server_name->string); ++ return manager_connect(m); ++ } ++ ++ /* Only link-local addresses need a scope, setting it on a global one pins us to one interface. */ ++ if (entry->family == AF_INET6 && in6_addr_is_link_local(&entry->in_addr.address.in6)) ++ sa.in6.sin6_scope_id = entry->ifindex; ++ ++ r = server_address_new(m->current_server_name, &a, &sa, sockaddr_len(&sa)); ++ if (r < 0) { ++ log_warning_errno(r, "Failed to add server address, trying next host: %m"); ++ return manager_connect(m); ++ } ++ ++ (void) server_address_pretty(a, &pretty); ++ log_debug("Resolved address %s for %s.", strna(pretty), m->current_server_name->string); ++ } ++ ++ if (!m->current_server_name->addresses) { ++ log_error("Failed to find suitable address for host %s.", m->current_server_name->string); ++ ++ /* Try next host */ ++ return manager_connect(m); ++ } ++ ++ manager_set_server_address(m, m->current_server_name->addresses); ++ ++ return manager_begin(m); ++} ++ ++/* Resolve the current server name through resolved's Varlink interface. This returns 0 if resolved is ++ * unavailable */ ++static int manager_resolve_varlink(Manager *m) { ++ _cleanup_(sd_varlink_close_unrefp) sd_varlink *link = NULL; ++ int r; ++ ++ assert(m); ++ assert(m->current_server_name); ++ ++ r = sd_varlink_connect_address(&link, "/run/systemd/resolve/io.systemd.Resolve"); ++ if (r < 0) { ++ log_debug_errno(r, "Failed to connect to systemd-resolved, using getaddrinfo(): %m"); ++ return 0; ++ } ++ ++ (void) sd_varlink_set_description(link, "timesyncd-resolve"); ++ (void) sd_varlink_set_relative_timeout(link, SD_RESOLVED_QUERY_TIMEOUT_USEC); ++ ++ r = sd_varlink_attach_event(link, m->event, SD_EVENT_PRIORITY_NORMAL); ++ if (r < 0) ++ return log_error_errno(r, "Failed to attach Varlink connection to event loop: %m"); ++ ++ r = sd_varlink_bind_reply(link, manager_resolve_varlink_reply); ++ if (r < 0) ++ return log_error_errno(r, "Failed to bind Varlink reply callback: %m"); ++ ++ sd_varlink_set_userdata(link, m); ++ ++ /* Ask resolved to skip DNSSEC validation as long as our clock is not synchronized yet. */ ++ r = sd_varlink_invokebo( ++ link, ++ "io.systemd.Resolve.ResolveHostname", ++ SD_JSON_BUILD_PAIR_STRING("name", m->current_server_name->string), ++ SD_JSON_BUILD_PAIR_CONDITION(!socket_ipv6_is_enabled(), "family", SD_JSON_BUILD_INTEGER(AF_INET)), ++ SD_JSON_BUILD_PAIR_UNSIGNED("flags", m->synchronized ? 0 : SD_RESOLVED_NO_VALIDATE)); ++ if (r < 0) ++ return log_error_errno(r, "Failed to call ResolveHostname(): %m"); ++ ++ m->resolve_varlink = TAKE_PTR(link); ++ return 1; ++} ++ + static int manager_retry_connect(sd_event_source *source, usec_t usec, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + +@@ -868,15 +986,22 @@ int manager_connect(Manager *m) { + manager_set_server_name(m, f); + } + +- /* Tell the resolver to reread /etc/resolv.conf, in +- * case it changed. */ +- res_init(); +- + /* Flush out any previously resolved addresses */ + server_name_flush_addresses(m->current_server_name); + + log_debug("Resolving %s...", m->current_server_name->string); + ++ /* Prefer resolved's varlink interface. If that's unavailable, then fall through to getaddrinfo below. */ ++ r = manager_resolve_varlink(m); ++ if (r < 0) ++ return r; ++ if (r > 0) ++ return 1; ++ ++ /* Tell the resolver to reread /etc/resolv.conf, in ++ * case it changed. */ ++ res_init(); ++ + struct addrinfo hints = { + .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG, + .ai_socktype = SOCK_DGRAM, +@@ -901,6 +1026,7 @@ void manager_disconnect(Manager *m) { + assert(m); + + m->resolve_query = sd_resolve_query_unref(m->resolve_query); ++ m->resolve_varlink = sd_varlink_close_unref(m->resolve_varlink); + + m->event_timer = sd_event_source_unref(m->event_timer); + +@@ -1035,7 +1161,7 @@ static bool manager_is_connected(Manager *m) { + + /* Return true when the manager is sending a request, resolving a server name, or + * in a poll interval. */ +- return m->server_socket >= 0 || m->resolve_query || m->event_timer; ++ return m->server_socket >= 0 || m->resolve_query || m->resolve_varlink || m->event_timer; + } + + static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +diff --git a/src/timesync/timesyncd-manager.h b/src/timesync/timesyncd-manager.h +index 9966b64ac697..8c1e30f48fd5 100644 +--- a/src/timesync/timesyncd-manager.h ++++ b/src/timesync/timesyncd-manager.h +@@ -41,6 +41,7 @@ typedef struct Manager { + + /* peer */ + sd_resolve_query *resolve_query; ++ sd_varlink *resolve_varlink; + sd_event_source *event_receive; + ServerName *current_server_name; + ServerAddress *current_server_address; +diff --git a/units/systemd-timesyncd.service.in b/units/systemd-timesyncd.service.in +index 560bcee92f4c..bc64e2414007 100644 +--- a/units/systemd-timesyncd.service.in ++++ b/units/systemd-timesyncd.service.in +@@ -22,10 +22,6 @@ Wants=time-set.target + AmbientCapabilities=CAP_SYS_TIME + BusName=org.freedesktop.timesync1 + CapabilityBoundingSet=CAP_SYS_TIME +-# Turn off DNSSEC validation for hostname look-ups, since those need the +-# correct time to work, but we likely won't acquire that without NTP. Let's +-# break this chicken-and-egg cycle here. +-Environment=SYSTEMD_NSS_RESOLVE_VALIDATE=0 + ExecStart={{LIBEXECDIR}}/systemd-timesyncd + LockPersonality=yes + MemoryDenyWriteExecute=yes + +From 6279910be7b26239ce7eb442b227a879cb66e24e Mon Sep 17 00:00:00 2001 +From: Clayton Craft +Date: Thu, 13 Aug 2026 14:01:59 -0700 +Subject: [PATCH 2/4] timesyncd: order after systemd-resolved + +Our first resolve happens way before sockets.target, and +systemd-resolved-varlink.socket has no ordering of its own, so there's +nothing making sure the socket is listening by then. Without this the +connect fails and we quietly fall back to getaddrinfo(), which is exactly +the case the varlink path is there for. + +Order after both the service and the socket since either one is enough +on its own but neither covers every install situation. For example +resolved binds the socket itself when nothing hands it one, and on +systems where only the socket is enabled the service isn't started at +boot at all. +--- + units/systemd-timesyncd.service.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/units/systemd-timesyncd.service.in b/units/systemd-timesyncd.service.in +index bc64e2414007..c1809710477b 100644 +--- a/units/systemd-timesyncd.service.in ++++ b/units/systemd-timesyncd.service.in +@@ -13,7 +13,7 @@ Documentation=man:systemd-timesyncd.service(8) + ConditionCapability=CAP_SYS_TIME + ConditionVirtualization=!container + DefaultDependencies=no +-After=systemd-sysusers.service ++After=systemd-sysusers.service systemd-resolved.service systemd-resolved-varlink.socket + Before=time-set.target sysinit.target shutdown.target + Conflicts=shutdown.target + Wants=time-set.target + +From 8027ff6ecd12058d08e0bb6dce046b86828f7dd4 Mon Sep 17 00:00:00 2001 +From: Clayton Craft +Date: Fri, 14 Aug 2026 15:19:56 -0700 +Subject: [PATCH 3/4] timesyncd: don't ask for AAAA when IPv6 is disabled + +ipv6_is_supported only tells us the kernel has IPv6, not that +it's usable. ipv6_is_enabled also checks the sysctl that turns +it off, so we stop resolving addresses we can't use. +--- + src/timesync/timesyncd-manager.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c +index 7b2ed4f831a0..4b3978bebc7f 100644 +--- a/src/timesync/timesyncd-manager.c ++++ b/src/timesync/timesyncd-manager.c +@@ -1005,7 +1005,7 @@ int manager_connect(Manager *m) { + struct addrinfo hints = { + .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG, + .ai_socktype = SOCK_DGRAM, +- .ai_family = socket_ipv6_is_supported() ? AF_UNSPEC : AF_INET, ++ .ai_family = socket_ipv6_is_enabled() ? AF_UNSPEC : AF_INET, + }; + + r = resolve_getaddrinfo(m->resolve, &m->resolve_query, m->current_server_name->string, "123", &hints, manager_resolve_handler, NULL, m); + +From d52a6931643a384b1184b1c30badb739ff885f78 Mon Sep 17 00:00:00 2001 +From: Clayton Craft +Date: Fri, 14 Aug 2026 14:25:01 -0700 +Subject: [PATCH 4/4] test: cover both of timesyncd's resolver paths + +This checks that we resolve through resolved when it's running, and fall back +to getaddrinfo when it isn't. +--- + test/units/TEST-45-TIMEDATE.sh | 71 ++++++++++++++++++++++++++++++++++ + 1 file changed, 71 insertions(+) + +diff --git a/test/units/TEST-45-TIMEDATE.sh b/test/units/TEST-45-TIMEDATE.sh +index 820c3ae9f52e..0af99fc96ce7 100755 +--- a/test/units/TEST-45-TIMEDATE.sh ++++ b/test/units/TEST-45-TIMEDATE.sh +@@ -248,6 +248,19 @@ assert_timesyncd_state() { + return 1 + } + ++assert_timesyncd_log() { ++ local cursor="${1:?}" ++ local match="${2:?}" ++ ++ for _ in {0..29}; do ++ journalctl --sync ++ journalctl -q -u systemd-timesyncd.service --after-cursor="$(<"$cursor")" --grep "$match" >/dev/null && return 0 ++ sleep .5 ++ done ++ ++ return 1 ++} ++ + testcase_ntp() { + # timesyncd has ConditionVirtualization=!container by default; drop/mock that for testing + if systemd-detect-virt --container --quiet; then +@@ -409,6 +422,64 @@ EOF + rm -f /run/systemd/network/ntp99.* + } + ++teardown_timesyncd_resolve() { ++ set +eu ++ ++ systemctl start systemd-resolved-varlink.socket ++ systemctl start systemd-resolved.service ++ ++ rm -rf /run/systemd/system/systemd-timesyncd.service.d ++ systemctl daemon-reload ++ systemctl stop systemd-timesyncd ++} ++ ++testcase_timesyncd_resolve() { ++ local cursor ++ ++ if systemd-detect-virt -cq; then ++ echo "This test case requires a VM, skipping..." ++ return 0 ++ fi ++ ++ if ! command -v resolvectl >/dev/null; then ++ echo "This test requires systemd-resolved, skipping..." ++ return 0 ++ fi ++ ++ trap teardown_timesyncd_resolve RETURN ++ ++ mkdir -p /run/systemd/system/systemd-timesyncd.service.d ++ cat >/run/systemd/system/systemd-timesyncd.service.d/10-debug.conf <