summaryrefslogtreecommitdiffstats
path: root/src/resolve/resolved-dns-transaction.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/resolve/resolved-dns-transaction.c')
-rw-r--r--src/resolve/resolved-dns-transaction.c282
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;
}