diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index b3895df6cf..4baa2f3868 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -48,6 +48,7 @@ #include "resolvectl.h" #include "resolved-def.h" #include "resolved-util.h" +#include "resolve-varlink-util.h" #include "set.h" #include "socket-netlink.h" #include "string-table.h" @@ -278,20 +279,97 @@ static void print_ifindex_comment(int printed_so_far, int ifindex) { ansi_grey(), ifname, ansi_normal()); } -static int resolve_host_error(const char *name, int r, const sd_bus_error *error) { - if (sd_bus_error_has_name(error, BUS_ERROR_DNS_NXDOMAIN)) - return log_error_errno(r, "%s: %s", name, bus_error_message(error, r)); +static int varlink_log_resolve_error(const char *name, const char *error_id, sd_json_variant *reply) { + int r; - return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(error, r)); + assert(name); + assert(!isempty(error_id)); + + int ret = sd_varlink_error_to_errno(error_id, reply); + static const struct { + const char *error_id; + const char *msg; + } error_message_table[] = { + { "io.systemd.Resolve.NoNameServers", "No appropriate name servers or networks for name found" }, + { "io.systemd.Resolve.QueryTimedOut", "Query timed out" }, + { "io.systemd.Resolve.MaxAttemptsReached", "All attempts to contact name servers or networks failed" }, + { "io.systemd.Resolve.InvalidReply", "Received invalid reply" }, + { "io.systemd.Resolve.QueryAborted", "Query aborted" }, + { "io.systemd.Resolve.QueryRefused", "DNS query type refused" }, + { "io.systemd.Resolve.NoTrustAnchor", "No suitable trust anchor known" }, + { "io.systemd.Resolve.ResourceRecordTypeUnsupported", "Server does not support requested resource record type" }, + { "io.systemd.Resolve.NetworkDown", "Network is down" }, + { "io.systemd.Resolve.NoSource", "All suitable resolution sources turned off" }, + { "io.systemd.Resolve.StubLoop", "Configured DNS server loops back to us" }, + { "io.systemd.Resolve.ZoneTransfersNotPermitted", "Zone transfers not permitted via this programming interface" }, + }; + FOREACH_ELEMENT(em, error_message_table) + if (streq(em->error_id, error_id)) + return log_error_errno(ret, "%s: resolve call failed: %s", name, em->msg); + + if (streq(error_id, "io.systemd.Resolve.NoSuchResourceRecord")) + return log_error_errno(ret, "%s: resolve call failed: '%s' does not have any RR of the requested type", name, name); + + if (streq(error_id, "io.systemd.Resolve.CNAMELoop")) + return log_error_errno(ret, "%s: resolve call failed: CNAME loop detected, or CNAME resolving disabled on '%s'", name, name); + + if (streq(error_id, "io.systemd.Resolve.ServiceNotProvided")) + return log_error_errno(ret, "%s: resolve call failed: '%s' does not provide the requested service", name, name); + + if (streq(error_id, "io.systemd.Resolve.InconsistentServiceRecords")) + return log_error_errno(ret, "%s: resolve call failed: '%s' does not provide a consistent set of service resource records", name, name); + + _cleanup_(resolve_error_done) ResolveError error = { + .rcode = _DNS_RCODE_INVALID, + .ede_rcode = _DNS_EDE_RCODE_INVALID, + }; + r = dispatch_resolve_error(/* name = */ NULL, reply, SD_JSON_LOG, &error); + if (r < 0) + log_debug_errno(r, "Failed to dispatch error JSON, ignoring: %m"); + + _cleanup_free_ char *msg_extended = NULL; + if (error.ede_rcode >= 0) { + msg_extended = strjoin(" (", + FORMAT_DNS_EDE_RCODE(error.ede_rcode), + !isempty(error.ede_msg) ? ": " : "", + strempty(error.ede_msg), + ")"); + if (!msg_extended) + return log_oom(); + } + + if (streq(error_id, "io.systemd.Resolve.DNSSECValidationFailed")) + return log_error_errno(ret, "%s: resolve call failed: DNSSEC validation failed: %s%s", name, error.result, strempty(msg_extended)); + + if (error.rcode != _DNS_RCODE_INVALID) { + if (error.rcode == DNS_RCODE_NXDOMAIN) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "%s: resolve call failed: Name '%s' not found%s%s", + name, error.query_string ?: name, error.ede_rcode >= 0 ? ":" : "", strempty(msg_extended)); + + return log_error_errno(ret, "%s: resolve call failed: Could not resolve '%s', server or network returned error: %s%s", + name, error.query_string ?: name, FORMAT_DNS_RCODE(error.rcode), strempty(msg_extended)); + } + + return log_error_errno(ret, "%s: resolve call failed: %s", name, error_id); } -static int resolve_host(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *canonical = NULL; - unsigned c = 0; - uint64_t flags; - usec_t ts; +static int varlink_connect_with_query_timeout(sd_varlink **vl) { + int r; + + assert(vl); + + r = sd_varlink_connect_address(vl, "/run/systemd/resolve/io.systemd.Resolve"); + if (r < 0) + return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); + + r = sd_varlink_set_relative_timeout(*vl, SD_RESOLVED_QUERY_TIMEOUT_USEC); + if (r < 0) + return log_error_errno(r, "Failed to set query timeout: %m"); + + return 0; +} + +static int resolve_host(const char *name) { int r; assert(name); @@ -301,85 +379,69 @@ static int resolve_host(sd_bus *bus, const char *name) { log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(arg_ifname) ? "*" : arg_ifname); - r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveHostname"); + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_with_query_timeout(&vl); if (r < 0) - return bus_log_create_error(r); + return r; - r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); + usec_t ts = now(CLOCK_MONOTONIC); + + const char *error_id = NULL; + sd_json_variant *v = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Resolve.ResolveHostname", + &v, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", name), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_ifindex > 0, "ifindex", arg_ifindex), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_family != AF_UNSPEC, "family", arg_family), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", arg_flags)); if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return resolve_host_error(name, r, &error); + return log_error_errno(r, "Failed to issue varlink call: %m"); ts = now(CLOCK_MONOTONIC) - ts; - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (!isempty(error_id)) + return varlink_log_resolve_error(name, error_id, v); + + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply reply = {}; + r = dispatch_resolve_hostname_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); if (r < 0) - return bus_log_parse_error(r); + return r; - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + bool first = true; + FOREACH_ARRAY(address, reply.addresses, reply.n_addresses) { _cleanup_free_ char *pretty = NULL; - int ifindex, family, k; - union in_addr_union a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "i", &ifindex); - if (r < 0) - return bus_log_parse_error(r); - - sd_bus_error_free(&error); - r = bus_message_read_in_addr_auto(reply, &error, &family, &a); - if (r < 0 && !sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) - return log_error_errno(r, "%s: systemd-resolved returned invalid result: %s", name, bus_error_message(&error, r)); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) { - log_debug_errno(r, "%s: systemd-resolved returned invalid result, ignoring: %s", name, bus_error_message(&error, r)); - continue; - } - - r = in_addr_ifindex_to_string(family, &a, ifindex, &pretty); + r = in_addr_ifindex_to_string(address->family, &address->in_addr.address, address->ifindex, &pretty); if (r < 0) return log_error_errno(r, "Failed to print address for %s: %m", name); - k = printf("%*s%s %s%s%s", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - ansi_highlight(), pretty, ansi_normal()); + int k = printf("%*s%s %s%s%s", + (int) strlen(name), + first ? name : "", + first ? ":" : " ", + ansi_highlight(), + pretty, + ansi_normal()); - print_ifindex_comment(k, ifindex); + print_ifindex_comment(k, address->ifindex); fputc('\n', stdout); - c++; + first = false; } - if (r < 0) - return bus_log_parse_error(r); - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "st", &canonical, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(name, canonical)) + if (!streq(name, reply.name)) printf("%*s%s (%s)\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - canonical); + (int) strlen(name), + reply.n_addresses == 0 ? name : "", + reply.n_addresses == 0 ? ":" : " ", + reply.name); - if (c == 0) - return log_error_errno(SYNTHETIC_ERRNO(ESRCH), - "%s: no addresses found", name); + if (reply.n_addresses == 0) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "%s: no addresses found", name); - print_source(flags, ts); + print_source(reply.flags, ts); return 0; } @@ -827,7 +889,7 @@ static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r >= 0) RET_GATHER(ret, resolve_address(bus, family, &a, ifindex)); else - RET_GATHER(ret, resolve_host(bus, *p)); + RET_GATHER(ret, resolve_host(*p)); } } diff --git a/src/shared/meson.build b/src/shared/meson.build index 6fa4216305..7b2a8b5ee2 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -185,6 +185,7 @@ shared_sources = files( 'resize-fs.c', 'resolve-hook-util.c', 'resolve-util.c', + 'resolve-varlink-util.c', 'rm-rf.c', 'seccomp-util.c', 'securebits-util.c', diff --git a/src/shared/resolve-varlink-util.c b/src/shared/resolve-varlink-util.c new file mode 100644 index 0000000000..3459cfc9d6 --- /dev/null +++ b/src/shared/resolve-varlink-util.c @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "dns-packet.h" +#include "json-util.h" +#include "resolve-varlink-util.h" + +void resolve_error_done(ResolveError *error) { + if (!error) + return; + + error->ede_msg = mfree(error->ede_msg); + error->query_string = mfree(error->query_string); + error->result = mfree(error->result); +} + +int dispatch_resolve_error(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_error_dispatch_table[] = { + { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(ResolveError, rcode), 0 }, + { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(ResolveError, ede_rcode), 0 }, + { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveError, ede_msg), 0 }, + { "queryString", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveError, query_string), 0 }, + { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveError, result), 0 }, + {}, + }; + ResolveError *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_error_done) ResolveError error = { + .rcode = _DNS_RCODE_INVALID, + .ede_rcode = _DNS_EDE_RCODE_INVALID, + }; + r = sd_json_dispatch(variant, resolve_error_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &error); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(error); + return 0; +} + +static int dispatch_resolved_address(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_address_dispatch_table[] = { + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(ResolvedAddress, ifindex), SD_JSON_RELAX }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(ResolvedAddress, family), SD_JSON_MANDATORY }, + { "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(ResolvedAddress, in_addr), SD_JSON_MANDATORY }, + {}, + }; + ResolvedAddress *ret = ASSERT_PTR(userdata); + int r; + + ResolvedAddress address = {}; + r = sd_json_dispatch(variant, resolved_address_dispatch_table, flags & ~SD_JSON_MANDATORY, &address); + if (r < 0) + return r; + + if (address.family != address.in_addr.family) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "'%s' address and family are inconsistent.", strna(name)); + + *ret = TAKE_STRUCT(address); + return 0; +} + +static int dispatch_resolved_address_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolveHostnameReply *reply = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(reply->addresses, reply->n_addresses + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_address(name, v, flags, &reply->addresses[reply->n_addresses++]); + if (r < 0) + return r; + } + + return 0; +} + +void resolve_hostname_reply_done(ResolveHostnameReply *reply) { + if (!reply) + return; + + reply->name = mfree(reply->name); + reply->addresses = mfree(reply->addresses); + reply->n_addresses = 0; +} + +int dispatch_resolve_hostname_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_hostname_reply_dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveHostnameReply, name), SD_JSON_MANDATORY }, + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveHostnameReply, flags), SD_JSON_MANDATORY }, + { "addresses", SD_JSON_VARIANT_ARRAY, dispatch_resolved_address_array, 0, SD_JSON_MANDATORY }, + {}, + }; + ResolveHostnameReply *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply reply = {}; + r = sd_json_dispatch(variant, resolve_hostname_reply_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &reply); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(reply); + return 0; +} diff --git a/src/shared/resolve-varlink-util.h b/src/shared/resolve-varlink-util.h new file mode 100644 index 0000000000..4e94fbe8c6 --- /dev/null +++ b/src/shared/resolve-varlink-util.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "in-addr-util.h" +#include "shared-forward.h" + +typedef struct ResolveError { + int rcode; + int ede_rcode; + char *ede_msg; + char *query_string; + char *result; +} ResolveError; + +void resolve_error_done(ResolveError *error); + +int dispatch_resolve_error(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +typedef struct ResolvedAddress { + int ifindex; + int family; + struct in_addr_data in_addr; +} ResolvedAddress; + +typedef struct ResolveHostnameReply { + char *name; + uint64_t flags; + ResolvedAddress *addresses; + size_t n_addresses; +} ResolveHostnameReply; + +void resolve_hostname_reply_done(ResolveHostnameReply *reply); + +int dispatch_resolve_hostname_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);