diff --git a/man/resolvectl.xml b/man/resolvectl.xml index 7d6d4e3fbe..b7a28b47bb 100644 --- a/man/resolvectl.xml +++ b/man/resolvectl.xml @@ -72,6 +72,14 @@ If combined with (only supported in combination with ) will output the resource record data in a JSON object. + If systemd-resolved returns a resolution error while + is enabled, the error is output as a JSON object. It contains the queried + name, the resolved varlink error, and any error fields + supplied by systemd-resolved, such as rcode, + queryString, extendedDNSErrorCode, + extendedDNSErrorMessage, and result. Local invocation or + connection errors are still reported as regular errors. + diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index cbe4899162..9af5626059 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -278,6 +278,31 @@ static void print_ifindex_comment(int printed_so_far, int ifindex) { ansi_grey(), ifname, ansi_normal()); } +static int dump_resolve_error_json(const char *name, const char *error_id, sd_json_variant *parameters, int ret) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + int r; + + assert(name); + assert(!isempty(error_id)); + + if (parameters) + j = sd_json_variant_ref(parameters); + + r = sd_json_variant_set_field_string(&j, "name", name); + if (r < 0) + return r; + + r = sd_json_variant_set_field_string(&j, "error", error_id); + if (r < 0) + return r; + + r = sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); + if (r < 0) + return r; + + return ret; +} + static int varlink_log_resolve_error(const char *name, const char *error_id, sd_json_variant *reply, bool warn_missing) { int r; @@ -285,6 +310,26 @@ static int varlink_log_resolve_error(const char *name, const char *error_id, sd_ assert(!isempty(error_id)); int ret = sd_varlink_error_to_errno(error_id, reply); + _cleanup_(resolve_error_done) ResolveError error = { + .rcode = _DNS_RCODE_INVALID, + .ede_rcode = _DNS_EDE_RCODE_INVALID, + }; + if (reply) { + 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"); + } + + if (error.rcode == DNS_RCODE_NXDOMAIN && !warn_missing) + return -ENXIO; + + if (sd_json_format_enabled(arg_json_format_flags)) + return dump_resolve_error_json( + name, + error_id, + reply, + error.rcode == DNS_RCODE_NXDOMAIN ? -ENXIO : ret); + static const struct { const char *error_id; const char *msg; @@ -318,14 +363,6 @@ static int varlink_log_resolve_error(const char *name, const char *error_id, sd_ 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(" (", @@ -342,9 +379,6 @@ static int varlink_log_resolve_error(const char *name, const char *error_id, sd_ if (error.rcode != _DNS_RCODE_INVALID) { 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)); } diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index 41436aac38..ce7675e8ce 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -768,10 +768,14 @@ testcase_08_resolved() { (! run resolvectl query nope.forwarded.test) grep -qF "nope.forwarded.test" "$RUN_OUT" grep -qF "not found" "$RUN_OUT" + (! run resolvectl --json=short query -t A nope.forwarded.test) + jq -e '.name == "nope.forwarded.test" and .error == "io.systemd.Resolve.DNSError" and .rcode == 3 and .queryString == "nope.forwarded.test"' "$RUN_OUT" # SERVFAIL + EDE code 6: DNSSEC Bogus (! run resolvectl query edns-bogus-dnssec.forwarded.test) grep -qE "^edns-bogus-dnssec.forwarded.test:.+: upstream-failure \(DNSSEC Bogus\)" "$RUN_OUT" + (! run resolvectl --json=short query -t A edns-bogus-dnssec.forwarded.test) + jq -e '.name == "edns-bogus-dnssec.forwarded.test" and .error == "io.systemd.Resolve.DNSSECValidationFailed" and .result == "upstream-failure" and .extendedDNSErrorCode == 6' "$RUN_OUT" # Same thing, but over Varlink (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-bogus-dnssec.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSSECValidationFailed" "$RUN_OUT" @@ -782,6 +786,8 @@ testcase_08_resolved() { # SERVFAIL + EDE code 16: Censored + extra text (! run resolvectl query edns-extra-text.forwarded.test) grep -qE "^edns-extra-text.forwarded.test.+: SERVFAIL \(Censored: Nothing to see here!\)" "$RUN_OUT" + (! run resolvectl --json=short query -t A edns-extra-text.forwarded.test) + jq -e '.name == "edns-extra-text.forwarded.test" and .error == "io.systemd.Resolve.DNSError" and .rcode == 2 and .extendedDNSErrorCode == 16 and .extendedDNSErrorMessage == "Nothing to see here!" and .queryString == "edns-extra-text.forwarded.test"' "$RUN_OUT" (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-extra-text.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT" grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!","queryString":"edns-extra-text.forwarded.test"}' "$RUN_OUT"