From: Ronan Pigott Date: Mon, 18 Dec 2023 05:33:31 +0000 (-0700) Subject: resolved: support RFC 8914 EDE error codes X-Git-Tag: v256-rc1~1318^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ac6844460ca1c01eaf2cb209ffa21c200d21a8f8;p=thirdparty%2Fsystemd.git resolved: support RFC 8914 EDE error codes If the server is able to indicate an extended error to us, using a degraded feature set is unlikely to help. --- diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 8eb75082cd3..b0b5bab6bd0 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "dns-domain.h" +#include "escape.h" #include "memory-util.h" #include "resolved-dns-packet.h" #include "set.h" @@ -2570,6 +2571,78 @@ bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b) { return dns_packet_compare_func(a, b) == 0; } +int dns_packet_ede_rcode(DnsPacket *p, char **ret_ede_msg) { + assert(p); + + _cleanup_free_ char *msg = NULL, *msg_escaped = NULL; + int ede_rcode = _DNS_EDNS_OPT_MAX_DEFINED; + int r; + const uint8_t *d; + size_t l; + + if (!p->opt) + return _DNS_EDE_RCODE_INVALID; + + d = p->opt->opt.data; + l = p->opt->opt.data_size; + + while (l > 0) { + uint16_t code, length; + + if (l < 4U) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "EDNS0 variable part has invalid size."); + + code = unaligned_read_be16(d); + length = unaligned_read_be16(d + 2); + + if (l < 4U + length) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "Truncated option in EDNS0 variable part."); + + if (code == DNS_EDNS_OPT_EXT_ERROR) { + if (length < 2U) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "EDNS0 truncated EDE info code."); + ede_rcode = unaligned_read_be16(d + 4); + r = make_cstring((char *)d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg); + if (r < 0) + return log_debug_errno(r, "Invalid EDE text in opt"); + else if (!utf8_is_valid(msg)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid EDE text in opt"); + else if (ede_rcode < _DNS_EDNS_OPT_MAX_DEFINED) { + msg_escaped = cescape(msg); + if (!msg_escaped) + return -ENOMEM; + } + break; + } + + d += 4U + length; + l -= 4U + length; + } + + if (ret_ede_msg) + *ret_ede_msg = TAKE_PTR(msg_escaped); + + return ede_rcode; +} + +bool dns_ede_rcode_is_dnssec(int ede_rcode) { + return IN_SET(ede_rcode, + DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG, + DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST, + DNS_EDE_RCODE_DNSSEC_INDETERMINATE, + DNS_EDE_RCODE_DNSSEC_BOGUS, + DNS_EDE_RCODE_SIG_EXPIRED, + DNS_EDE_RCODE_SIG_NOT_YET_VALID, + DNS_EDE_RCODE_DNSKEY_MISSING, + DNS_EDE_RCODE_RRSIG_MISSING, + DNS_EDE_RCODE_NO_ZONE_KEY_BIT, + DNS_EDE_RCODE_NSEC_MISSING + ); +} + int dns_packet_has_nsid_request(DnsPacket *p) { bool has_nsid = false; const uint8_t *d; @@ -2693,7 +2766,7 @@ static const char* const dns_ede_rcode_table[_DNS_EDE_RCODE_MAX_DEFINED] = { [DNS_EDE_RCODE_TRANSPORT_POLICY] = "Impossible Transport Policy", [DNS_EDE_RCODE_SYNTHESIZED] = "Synthesized", }; -DEFINE_STRING_TABLE_LOOKUP(dns_ede_rcode, int); +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_ede_rcode, int); const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]) { const char *p = dns_ede_rcode_to_string(i); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 497a572b04a..8f062d24ece 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -253,6 +253,8 @@ int dns_packet_extract(DnsPacket *p); bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b); +int dns_packet_ede_rcode(DnsPacket *p, char **ret_ede_msg); +bool dns_ede_rcode_is_dnssec(int ede_rcode); int dns_packet_has_nsid_request(DnsPacket *p); /* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */ diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 702434336c3..0f934ef7bb6 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -1200,11 +1200,51 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt switch (t->scope->protocol) { - case DNS_PROTOCOL_DNS: + case DNS_PROTOCOL_DNS: { + int ede_rcode; + _cleanup_free_ char *ede_msg = NULL; + assert(t->server); + ede_rcode = dns_packet_ede_rcode(p, &ede_msg); + if (ede_rcode < 0 && ede_rcode != -EINVAL) + log_debug_errno(ede_rcode, "Unable to extract EDE error code from packet, ignoring: %m"); + if (!t->bypass && IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { + /* If the server has replied with detailed error data, using a degraded feature set + * will likely not help anyone. Examine the detailed error to determine the best + * course of action. */ + if (ede_rcode >= 0 && DNS_PACKET_RCODE(p) == DNS_RCODE_SERVFAIL) { + /* These codes are related to DNSSEC configuration errors. If accurate, + * this is the domain operator's problem, and retrying won't help. */ + if (dns_ede_rcode_is_dnssec(ede_rcode)) { + log_debug("Server returned error: %s (%s%s%s). Lookup failed.", + FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)), + FORMAT_DNS_EDE_RCODE(ede_rcode), + isempty(ede_msg) ? "" : ": ", ede_msg); + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return; + } + + /* These codes probably indicate a transient error. Let's try again. */ + if (IN_SET(ede_rcode, DNS_EDE_RCODE_NOT_READY, DNS_EDE_RCODE_NET_ERROR)) { + log_debug("Server returned error: %s (%s%s%s), retrying transaction.", + FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)), + FORMAT_DNS_EDE_RCODE(ede_rcode), + isempty(ede_msg) ? "" : ": ", ede_msg); + dns_transaction_retry(t, false); + return; + } + + /* OK, the query failed, but we still shouldn't degrade the feature set for + * this server. */ + log_debug("Server returned error: %s (%s%s%s)", + FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)), + FORMAT_DNS_EDE_RCODE(ede_rcode), + isempty(ede_msg) ? "" : ": ", ede_msg); + break; + } /* No EDE rcode, or EDE rcode we don't understand */ /* Request failed, immediately try again with reduced features */ @@ -1261,7 +1301,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt if (DNS_PACKET_RCODE(p) == DNS_RCODE_REFUSED) { /* This server refused our request? If so, try again, use a different server */ - log_debug("Server returned REFUSED, switching servers, and retrying."); + if (ede_rcode > 0) + log_debug("Server returned REFUSED (%s), switching servers, and retrying.", + FORMAT_DNS_EDE_RCODE(ede_rcode)); + else + log_debug("Server returned REFUSED, switching servers, and retrying."); if (dns_transaction_limited_retry(t)) return; @@ -1273,6 +1317,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt dns_server_packet_truncated(t->server, t->current_feature_level); break; + } case DNS_PROTOCOL_LLMNR: case DNS_PROTOCOL_MDNS: