resolvectl: use varlink instead of dbus for ResolveRecord

This commit is contained in:
Nick Rosbrook
2026-06-26 16:03:18 -04:00
parent 5ff17fb750
commit cfac7ec169
3 changed files with 156 additions and 103 deletions
+67 -103
View 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");
_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;
}
+72
View 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;
}
+17
View 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);