diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:45 +0000 |
commit | efeb864cb547a2cbf96dc0053a8bdb4d9190b364 (patch) | |
tree | c0b83368f18be983fcc763200c4c24d633244588 /src/resolve/resolved-dns-transaction.c | |
parent | Releasing progress-linux version 255.5-1~progress7.99u1. (diff) | |
download | systemd-efeb864cb547a2cbf96dc0053a8bdb4d9190b364.tar.xz systemd-efeb864cb547a2cbf96dc0053a8bdb4d9190b364.zip |
Merging upstream version 256.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/resolve/resolved-dns-transaction.c | 282 |
1 files changed, 191 insertions, 91 deletions
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index ad8b88e..92ac075 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -28,6 +28,8 @@ static void dns_transaction_reset_answer(DnsTransaction *t) { t->received = dns_packet_unref(t->received); t->answer = dns_answer_unref(t->answer); t->answer_rcode = 0; + t->answer_ede_rcode = _DNS_EDE_RCODE_INVALID; + t->answer_ede_msg = mfree(t->answer_ede_msg); t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; t->answer_query_flags = 0; @@ -74,8 +76,12 @@ static void dns_transaction_close_connection( * and the reply we might still get from the server will be eaten up instead of resulting in an ICMP * port unreachable error message. */ - /* Skip the graveyard stuff when we're shutting down, since that requires running event loop */ - if (!t->scope->manager->event || sd_event_get_state(t->scope->manager->event) == SD_EVENT_FINISHED) + /* Skip the graveyard stuff when we're shutting down, since that requires running event loop. + * Note that this is also called from dns_transaction_free(). In that case, scope may be NULL. */ + if (!t->scope || + !t->scope->manager || + !t->scope->manager->event || + sd_event_get_state(t->scope->manager->event) == SD_EVENT_FINISHED) use_graveyard = false; if (use_graveyard && t->dns_udp_fd >= 0 && t->sent && !t->received) { @@ -283,6 +289,7 @@ int dns_transaction_new( .dns_udp_fd = -EBADF, .answer_source = _DNS_TRANSACTION_SOURCE_INVALID, .answer_dnssec_result = _DNSSEC_RESULT_INVALID, + .answer_ede_rcode = _DNS_EDE_RCODE_INVALID, .answer_nsec_ttl = UINT32_MAX, .key = dns_resource_key_ref(key), .query_flags = query_flags, @@ -496,7 +503,7 @@ static int dns_transaction_pick_server(DnsTransaction *t) { dns_server_unref(t->server); t->server = dns_server_ref(server); - t->n_picked_servers ++; + t->n_picked_servers++; log_debug("Using DNS server %s for transaction %u.", strna(dns_server_string_full(t->server)), t->id); @@ -895,8 +902,25 @@ static int dns_transaction_dnssec_ready(DnsTransaction *t) { /* We handle DNSSEC failures different from other errors, as we care about the DNSSEC * validation result */ - log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result)); - t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */ + log_debug("Auxiliary DNSSEC RR query failed validation: %s%s%s%s%s%s", + dnssec_result_to_string(dt->answer_dnssec_result), + dt->answer_ede_rcode >= 0 ? " (" : "", + dt->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(dt->answer_ede_rcode) : "", + (dt->answer_ede_rcode >= 0 && !isempty(dt->answer_ede_msg)) ? ": " : "", + dt->answer_ede_rcode >= 0 ? strempty(dt->answer_ede_msg) : "", + dt->answer_ede_rcode >= 0 ? ")" : ""); + + /* Copy error code over */ + t->answer_dnssec_result = dt->answer_dnssec_result; + t->answer_ede_rcode = dt->answer_ede_rcode; + r = free_and_strdup(&t->answer_ede_msg, dt->answer_ede_msg); + if (r < 0) + log_oom_debug(); + + /* The answer would normally be replaced by the validated subset, but at this point + * we aren't going to bother validating the rest, so just drop it. */ + t->answer = dns_answer_unref(t->answer); + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); return 0; @@ -1135,13 +1159,130 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt } } + if (DNS_PACKET_TC(p)) { + + /* Truncated packets for mDNS are not allowed. Give up immediately. */ + if (t->scope->protocol == DNS_PROTOCOL_MDNS) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + /* Response was truncated, let's try again with good old TCP */ + log_debug("Reply truncated, retrying via TCP."); + retry_with_tcp = true; + + } else if (t->scope->protocol == DNS_PROTOCOL_DNS && + DNS_PACKET_IS_FRAGMENTED(p)) { + + /* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */ + if (t->server) + dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p)); + + if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { + /* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack + * issues. (We don't do that on the lowest feature level however, since crappy DNS + * servers often do not implement TCP, hence falling back to TCP on fragmentation is + * counter-productive there.) */ + + log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)", + p->fragsize, p->size); + retry_with_tcp = true; + } + } + + if (retry_with_tcp) { + r = dns_transaction_emit_tcp(t); + if (r == -ESRCH) { + /* No servers found? Damn! */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); + return; + } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return; + } + if (r < 0) { + /* On LLMNR, if we cannot connect to the host, + * we immediately give up */ + if (t->scope->protocol != DNS_PROTOCOL_DNS) + goto fail; + + /* On DNS, couldn't send? Try immediately again, with a new server */ + if (dns_transaction_limited_retry(t)) + return; + + /* No new server to try, give up */ + dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); + } + + return; + } + + /* After the superficial checks, actually parse the message. */ + r = dns_packet_extract(p); + if (r < 0) { + if (t->server) { + dns_server_packet_invalid(t->server, t->current_feature_level); + + r = dns_transaction_maybe_restart(t); + if (r < 0) + goto fail; + if (r > 0) /* Transaction got restarted... */ + return; + } + + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + switch (t->scope->protocol) { - case DNS_PROTOCOL_DNS: + case DNS_PROTOCOL_DNS: { assert(t->server); + (void) dns_packet_ede_rcode(p, &t->answer_ede_rcode, &t->answer_ede_msg); + 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 (t->answer_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(t->answer_ede_rcode)) { + log_debug("Server returned error: %s (%s%s%s). Lookup failed.", + FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)), + FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode), + isempty(t->answer_ede_msg) ? "" : ": ", + strempty(t->answer_ede_msg)); + + t->answer_dnssec_result = DNSSEC_UPSTREAM_FAILURE; + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return; + } + + /* These codes probably indicate a transient error. Let's try again. */ + if (IN_SET(t->answer_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(t->answer_ede_rcode), + isempty(t->answer_ede_msg) ? "" : ": ", + strempty(t->answer_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(t->answer_ede_rcode), + isempty(t->answer_ede_msg) ? "" : ": ", + strempty(t->answer_ede_msg)); + break; + } /* Request failed, immediately try again with reduced features */ @@ -1198,7 +1339,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 (t->answer_ede_rcode >= 0) + log_debug("Server returned REFUSED (%s), switching servers, and retrying.", + FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode)); + else + log_debug("Server returned REFUSED, switching servers, and retrying."); if (dns_transaction_limited_retry(t)) return; @@ -1210,6 +1355,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: @@ -1220,83 +1366,6 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt assert_not_reached(); } - if (DNS_PACKET_TC(p)) { - - /* Truncated packets for mDNS are not allowed. Give up immediately. */ - if (t->scope->protocol == DNS_PROTOCOL_MDNS) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - /* Response was truncated, let's try again with good old TCP */ - log_debug("Reply truncated, retrying via TCP."); - retry_with_tcp = true; - - } else if (t->scope->protocol == DNS_PROTOCOL_DNS && - DNS_PACKET_IS_FRAGMENTED(p)) { - - /* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */ - if (t->server) - dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p)); - - if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { - /* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack - * issues. (We don't do that on the lowest feature level however, since crappy DNS - * servers often do not implement TCP, hence falling back to TCP on fragmentation is - * counter-productive there.) */ - - log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)", - p->fragsize, p->size); - retry_with_tcp = true; - } - } - - if (retry_with_tcp) { - r = dns_transaction_emit_tcp(t); - if (r == -ESRCH) { - /* No servers found? Damn! */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - return; - } - if (r == -EOPNOTSUPP) { - /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ - dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); - return; - } - if (r < 0) { - /* On LLMNR, if we cannot connect to the host, - * we immediately give up */ - if (t->scope->protocol != DNS_PROTOCOL_DNS) - goto fail; - - /* On DNS, couldn't send? Try immediately again, with a new server */ - if (dns_transaction_limited_retry(t)) - return; - - /* No new server to try, give up */ - dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); - } - - return; - } - - /* After the superficial checks, actually parse the message. */ - r = dns_packet_extract(p); - if (r < 0) { - if (t->server) { - dns_server_packet_invalid(t->server, t->current_feature_level); - - r = dns_transaction_maybe_restart(t); - if (r < 0) - goto fail; - if (r > 0) /* Transaction got restarted... */ - return; - } - - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - if (t->server) { /* Report that we successfully received a valid packet with a good rcode after we initially got a bad * rcode and subsequently downgraded the protocol */ @@ -1770,8 +1839,12 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { t->answer_source = DNS_TRANSACTION_CACHE; if (t->answer_rcode == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else + else { + if (t->received) + (void) dns_packet_ede_rcode(t->received, &t->answer_ede_rcode, &t->answer_ede_msg); + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + } return 0; } } @@ -2268,9 +2341,9 @@ static int dns_transaction_request_dnssec_rr_full(DnsTransaction *t, DnsResource r = dns_transaction_go(aux); if (r < 0) return r; - if (ret) - *ret = aux; } + if (ret) + *ret = aux; return 1; } @@ -2467,7 +2540,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r < 0) return r; - /* If we are requesting a DNSKEY, we can anticiapte that we will want the matching DS + /* If we are requesting a DNSKEY, we can anticipate that we will want the matching DS * in the near future. Let's request it in advance so we don't have to wait in the * common case. */ if (aux) { @@ -2545,6 +2618,10 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { return r; if (r == 0) continue; + + /* If we were looking for the DS RR, don't request it again. */ + if (dns_transaction_key(t)->type == DNS_TYPE_DS) + continue; } r = dnssec_has_rrsig(t->answer, rr->key); @@ -2618,6 +2695,21 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r < 0) return r; + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && dns_name_is_root(name)) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + /* We made it all the way to the root zone. If we are in allow-downgrade + * mode, we need to make at least one request that we can be certain should + * have been signed, to test for servers that are not dnssec aware. */ + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + log_debug("Requesting root zone SOA to probe dnssec support."); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + } + break; } @@ -2676,7 +2768,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * zones return NXDOMAIN even though they have further children. */ if (chased_insecure || was_signed) - /* In this case we already reqeusted what we need above. */ + /* In this case we already requested what we need above. */ name = NULL; else if (dns_transaction_key(t)->type == DNS_TYPE_DS) /* If the DS response is empty, we'll walk up the dns labels requesting DS until we @@ -2844,7 +2936,12 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * if (r == 0) continue; - return FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED); + if (!FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED)) + return false; + + /* We expect this to be signed when the DS record exists, and don't expect it to be + * signed when the DS record is proven not to exist. */ + return dns_answer_match_key(dt->answer, dns_transaction_key(dt), NULL); } return true; @@ -3523,14 +3620,17 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { bool have_nsec = false; r = dnssec_validate_records(t, phase, &have_nsec, &nvalidations, &validated); - if (r <= 0) + if (r <= 0) { + DNS_ANSWER_REPLACE(t->answer, TAKE_PTR(validated)); return r; + } if (nvalidations > DNSSEC_VALIDATION_MAX) { /* This reply requires an onerous number of signature validations to verify. Let's * not waste our time trying, as this shouldn't happen for well-behaved domains * anyway. */ t->answer_dnssec_result = DNSSEC_TOO_MANY_VALIDATIONS; + DNS_ANSWER_REPLACE(t->answer, TAKE_PTR(validated)); return 0; } |