]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolvectl: use varlink instead of dbus for ResolveRecord
authorNick Rosbrook <enr0n@ubuntu.com>
Fri, 19 Jun 2026 19:01:01 +0000 (15:01 -0400)
committerNick Rosbrook <enr0n@ubuntu.com>
Fri, 26 Jun 2026 20:03:18 +0000 (16:03 -0400)
src/resolve/resolvectl.c
src/shared/resolve-varlink-util.c
src/shared/resolve-varlink-util.h

index 2cfd5855649ddd01d2240f43b0f337a78ce97e9e..327cf10140719cc1911cfe0652605ee7ff4bd86b 100644 (file)
@@ -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");
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, 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);
-
-        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;
-        }
 
-        ts = now(CLOCK_MONOTONIC) - ts;
+        usec_t ts = now(CLOCK_MONOTONIC);
 
-        r = sd_bus_message_enter_container(reply, 'a', "(iqqay)");
+        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 bus_log_parse_error(r);
+                return log_error_errno(r, "Failed to issue varlink call: %m");
 
-        while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) {
-                uint16_t c, t;
-                int ifindex;
-                const void *d;
-                size_t l;
+        ts = now(CLOCK_MONOTONIC) - ts;
 
-                assert_cc(sizeof(int) == sizeof(int32_t));
+        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);
 
-                r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t);
-                if (r < 0)
-                        return bus_log_parse_error(r);
+                if (streq(error_id, "io.systemd.Resolve.ResourceRecordTypeObsolete"))
+                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Specified DNS resource record type %" PRIu16 " is obsolete", type);
 
-                r = sd_bus_message_read_array(reply, 'y', &d, &l);
-                if (r < 0)
-                        return bus_log_parse_error(r);
+                return varlink_log_resolve_error(name, error_id, v, warn_missing);
+        }
 
-                r = sd_bus_message_exit_container(reply);
+        _cleanup_(resolve_record_reply_done) ResolveRecordReply reply = {};
+        r = dispatch_resolve_record_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply);
+        if (r < 0)
+                return r;
+
+        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);
+                        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;
 }
index 877e298999a05d4d279e8f6c7735ba415afce6d6..c3a638f3ad2f49cfcd60ce6901a4ce1f870a8b0f 100644 (file)
@@ -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;
+}
index 9fbc8a1492bbfb479c7cc08467fb26ee8ad99560..58e8f4d9184f63cd5e3131eabf576f8d4876b128 100644 (file)
@@ -1,6 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <sys/uio.h>
+
 #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);