t->dns_udp_event_source = sd_event_source_disable_unref(t->dns_udp_event_source);
- /* If we have an UDP socket where we sent a packet, but never received one, then add it to the socket
+ /* If we have a UDP socket where we sent a packet, but never received one, then add it to the socket
* graveyard, instead of closing it right away. That way it will stick around for a moment longer,
* and the reply we might still get from the server will be eaten up instead of resulting in an ICMP
* port unreachable error message. */
return -ENOMEM;
*t = (DnsTransaction) {
- .dns_udp_fd = -1,
+ .dns_udp_fd = -EBADF,
.answer_source = _DNS_TRANSACTION_SOURCE_INVALID,
.answer_dnssec_result = _DNSSEC_RESULT_INVALID,
.answer_nsec_ttl = UINT32_MAX,
.bypass = dns_packet_ref(bypass),
.current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID,
.clamp_feature_level_servfail = _DNS_SERVER_FEATURE_LEVEL_INVALID,
- .clamp_feature_level_nxdomain = _DNS_SERVER_FEATURE_LEVEL_INVALID,
.id = pick_new_id(s->manager),
};
/* If we changed the server invalidate the feature level clamping, as the new server might have completely
* different properties. */
- if (server != t->server) {
+ if (server != t->server)
t->clamp_feature_level_servfail = _DNS_SERVER_FEATURE_LEVEL_INVALID;
- t->clamp_feature_level_nxdomain = _DNS_SERVER_FEATURE_LEVEL_INVALID;
- }
t->current_feature_level = dns_server_possible_feature_level(server);
if (t->clamp_feature_level_servfail != _DNS_SERVER_FEATURE_LEVEL_INVALID &&
t->current_feature_level > t->clamp_feature_level_servfail)
t->current_feature_level = t->clamp_feature_level_servfail;
- if (t->clamp_feature_level_nxdomain != _DNS_SERVER_FEATURE_LEVEL_INVALID &&
- t->current_feature_level > t->clamp_feature_level_nxdomain)
- t->current_feature_level = t->clamp_feature_level_nxdomain;
log_debug("Using feature level %s for transaction %u.", dns_server_feature_level_to_string(t->current_feature_level), t->id);
static int dns_transaction_emit_tcp(DnsTransaction *t) {
usec_t stream_timeout_usec = DNS_STREAM_DEFAULT_TIMEOUT_USEC;
_cleanup_(dns_stream_unrefp) DnsStream *s = NULL;
- _cleanup_close_ int fd = -1;
+ _cleanup_close_ int fd = -EBADF;
union sockaddr_union sa;
DnsStreamType type;
int r;
if (r < 0)
return r;
- fd = -1;
+ fd = -EBADF;
#if ENABLE_DNS_OVER_TLS
if (t->scope->protocol == DNS_PROTOCOL_DNS &&
t->answer_rcode,
t->answer,
DNS_PACKET_CD(t->received) ? t->received : NULL, /* only cache full packets with CD on,
- * since our usecase for caching them
+ * since our use case for caching them
* is "bypass" mode which is only
* enabled for CD packets. */
t->answer_query_flags,
t->answer_dnssec_result,
t->answer_nsec_ttl,
t->received->family,
- &t->received->sender);
+ &t->received->sender,
+ t->scope->manager->stale_retention_usec);
}
static bool dns_transaction_dnssec_is_live(DnsTransaction *t) {
if (t->state != DNS_TRANSACTION_PENDING)
return;
+ /* Increment the total failure counter only when it is the first attempt at querying and the upstream
+ * server returns a failure response code. This ensures a more accurate count of the number of queries
+ * that received a failure response code, as it doesn't consider retries. */
+
+ if (t->n_attempts == 1 && !IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
+ t->scope->manager->n_failure_responses_total++;
+
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function. */
return;
}
- if (t->scope->protocol == DNS_PROTOCOL_DNS &&
- !t->bypass &&
- DNS_PACKET_RCODE(p) == DNS_RCODE_NXDOMAIN &&
- p->opt && !DNS_PACKET_DO(p) &&
- DNS_SERVER_FEATURE_LEVEL_IS_EDNS0(t->current_feature_level) &&
- DNS_SERVER_FEATURE_LEVEL_IS_UDP(t->current_feature_level) &&
- t->scope->dnssec_mode != DNSSEC_YES) {
-
- /* Some captive portals are special in that the Aruba/Datavalet hardware will miss
- * replacing the packets with the local server IP to point to the authenticated side
- * of the network if EDNS0 is enabled. Instead they return NXDOMAIN, with DO bit set
- * to zero... nothing to see here, yet respond with the captive portal IP, when using
- * the more simple UDP level.
- *
- * Common portal names that fail like so are:
- * secure.datavalet.io
- * securelogin.arubanetworks.com
- * securelogin.networks.mycompany.com
- *
- * Thus retry NXDOMAIN RCODES with a lower feature level.
- *
- * Do not lower the server's tracked feature level, as the captive portal should not
- * be lying for the wider internet (e.g. _other_ queries were observed fine with
- * EDNS0 on these networks, post auth), i.e. let's just lower the level transaction's
- * feature level.
- *
- * This is reported as https://github.com/dns-violations/dns-violations/blob/master/2018/DVE-2018-0001.md
- */
-
- t->clamp_feature_level_nxdomain = DNS_SERVER_FEATURE_LEVEL_UDP;
-
- log_debug("Server returned error %s in EDNS0 mode, retrying transaction with reduced feature level %s (DVE-2018-0001 mitigation)",
- FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)),
- dns_server_feature_level_to_string(t->clamp_feature_level_nxdomain));
-
- dns_transaction_retry(t, false /* use the same server */);
- 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 */
assert(t->scope);
r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p);
- if (ERRNO_IS_DISCONNECT(r)) {
- usec_t usec;
-
- /* UDP connection failures get reported via ICMP and then are possibly delivered to us on the
- * next recvmsg(). Treat this like a lost packet. */
+ if (r < 0) {
+ if (ERRNO_IS_DISCONNECT(r)) {
+ usec_t usec;
- log_debug_errno(r, "Connection failure for DNS UDP packet: %m");
- assert_se(sd_event_now(t->scope->manager->event, CLOCK_BOOTTIME, &usec) >= 0);
- dns_server_packet_lost(t->server, IPPROTO_UDP, t->current_feature_level);
+ /* UDP connection failures get reported via ICMP and then are possibly delivered to us on the
+ * next recvmsg(). Treat this like a lost packet. */
- dns_transaction_close_connection(t, /* use_graveyard = */ false);
+ log_debug_errno(r, "Connection failure for DNS UDP packet: %m");
+ assert_se(sd_event_now(t->scope->manager->event, CLOCK_BOOTTIME, &usec) >= 0);
+ dns_server_packet_lost(t->server, IPPROTO_UDP, t->current_feature_level);
- if (dns_transaction_limited_retry(t)) /* Try a different server */
- return 0;
+ dns_transaction_close_connection(t, /* use_graveyard = */ false);
- dns_transaction_complete_errno(t, r);
- return 0;
- }
- if (r < 0) {
+ if (dns_transaction_limited_retry(t)) /* Try a different server */
+ return 0;
+ }
dns_transaction_complete_errno(t, r);
return 0;
}
assert(s);
+ t->seen_timeout = true;
+
if (t->initial_jitter_scheduled && !t->initial_jitter_elapsed) {
log_debug("Initial jitter phase for transaction %" PRIu16 " elapsed.", t->id);
t->initial_jitter_elapsed = true;
dns_transaction_stop_timeout(t);
+ if (t->n_attempts == 1 && t->seen_timeout)
+ t->scope->manager->n_timeouts_total++;
+
if (!dns_scope_network_good(t->scope)) {
dns_transaction_complete(t, DNS_TRANSACTION_NETWORK_DOWN);
return 0;
/* Let's then prune all outdated entries */
dns_cache_prune(&t->scope->cache);
+ /* For the initial attempt or when no stale data is requested, disable serve stale
+ * and answer the question from the cache (honors ttl property).
+ * On the second attempt, if StaleRetentionSec is greater than zero,
+ * try to answer the question using stale date (honors until property) */
+ uint64_t query_flags = t->query_flags;
+ if (t->n_attempts == 1 || t->scope->manager->stale_retention_usec == 0)
+ query_flags |= SD_RESOLVED_NO_STALE;
+
r = dns_cache_lookup(
&t->scope->cache,
dns_transaction_key(t),
- t->query_flags,
+ query_flags,
&t->answer_rcode,
&t->answer,
&t->received,
* packet. */
dns_transaction_reset_answer(t);
else {
+ if (t->n_attempts > 1 && !FLAGS_SET(query_flags, SD_RESOLVED_NO_STALE)) {
+
+ if (t->answer_rcode == DNS_RCODE_SUCCESS) {
+ if (t->seen_timeout)
+ t->scope->manager->n_timeouts_served_stale_total++;
+ else
+ t->scope->manager->n_failure_responses_served_stale_total++;
+ }
+
+ char key_str[DNS_RESOURCE_KEY_STRING_MAX];
+ log_debug("Serve Stale response rcode=%s for %s",
+ FORMAT_DNS_RCODE(t->answer_rcode),
+ dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str));
+ }
+
t->answer_source = DNS_TRANSACTION_CACHE;
if (t->answer_rcode == DNS_RCODE_SUCCESS)
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
return dns_packet_append_answer(p, answer, nscount);
}
-static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
+static int mdns_make_dummy_packet(DnsTransaction *t, DnsPacket **ret_packet, Set **ret_keys) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
_cleanup_set_free_ Set *keys = NULL;
- unsigned qdcount, ancount = 0 /* avoid false maybe-uninitialized warning */, nscount;
bool add_known_answers = false;
+ unsigned qdcount;
usec_t ts;
int r;
assert(t);
+ assert(t->scope);
assert(t->scope->protocol == DNS_PROTOCOL_MDNS);
+ assert(ret_packet);
+ assert(ret_keys);
- /* Discard any previously prepared packet, so we can start over and coalesce again */
- t->sent = dns_packet_unref(t->sent);
-
- /* First, create a dummy packet to calculate packet size. */
r = dns_packet_new_query(&p, t->scope->protocol, 0, false);
if (r < 0)
return r;
if (r < 0)
return r;
- /*
- * For mDNS, we want to coalesce as many open queries in pending transactions into one single
- * query packet on the wire as possible. To achieve that, we iterate through all pending transactions
- * in our current scope, and see whether their timing constraints allow them to be sent.
- */
-
assert_se(sd_event_now(t->scope->manager->event, CLOCK_BOOTTIME, &ts) >= 0);
- for (bool restart = true; restart;) {
- restart = false;
- LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) {
- size_t saved_packet_size;
- bool append = false;
-
- /* Skip ourselves */
- if (other == t)
- continue;
+ LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) {
- if (other->state != DNS_TRANSACTION_PENDING)
- continue;
-
- if (other->next_attempt_after > ts)
- continue;
+ /* Skip ourselves */
+ if (other == t)
+ continue;
- if (!set_contains(keys, dns_transaction_key(other))) {
- r = dns_packet_append_key(p, dns_transaction_key(other), 0, &saved_packet_size);
- /* If we can't stuff more questions into the packet, just give up.
- * One of the 'other' transactions will fire later and take care of the rest. */
- if (r == -EMSGSIZE)
- break;
- if (r < 0)
- return r;
+ if (other->state != DNS_TRANSACTION_PENDING)
+ continue;
- r = dns_packet_append_zone(p, t, dns_transaction_key(other), NULL);
- if (r == -EMSGSIZE)
- break;
- if (r < 0)
- return r;
+ if (other->next_attempt_after > ts)
+ continue;
- append = true;
- }
+ if (!set_contains(keys, dns_transaction_key(other))) {
+ size_t saved_packet_size;
- r = dns_transaction_prepare(other, ts);
+ r = dns_packet_append_key(p, dns_transaction_key(other), 0, &saved_packet_size);
+ /* If we can't stuff more questions into the packet, just give up.
+ * One of the 'other' transactions will fire later and take care of the rest. */
+ if (r == -EMSGSIZE)
+ break;
if (r < 0)
return r;
- if (r == 0) {
- if (append)
- dns_packet_truncate(p, saved_packet_size);
- /* In this case, not only this transaction, but multiple transactions may be
- * freed. Hence, we need to restart the loop. */
- restart = true;
+ r = dns_packet_append_zone(p, t, dns_transaction_key(other), NULL);
+ if (r == -EMSGSIZE) {
+ dns_packet_truncate(p, saved_packet_size);
break;
}
+ if (r < 0)
+ return r;
- usec_t timeout = transaction_get_resend_timeout(other);
- r = dns_transaction_setup_timeout(other, timeout, usec_add(ts, timeout));
+ r = set_ensure_put(&keys, &dns_resource_key_hash_ops, dns_transaction_key(other));
if (r < 0)
return r;
+ }
+
+ r = dns_transaction_prepare(other, ts);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ /* In this case, not only this transaction, but multiple transactions may be
+ * freed. Hence, we need to restart the loop. */
+ return -EAGAIN;
- if (dns_key_is_shared(dns_transaction_key(other)))
- add_known_answers = true;
+ usec_t timeout = transaction_get_resend_timeout(other);
+ r = dns_transaction_setup_timeout(other, timeout, usec_add(ts, timeout));
+ if (r < 0)
+ return r;
- if (append) {
- r = set_ensure_put(&keys, &dns_resource_key_hash_ops, dns_transaction_key(other));
- if (r < 0)
- return r;
- }
+ if (dns_key_is_shared(dns_transaction_key(other)))
+ add_known_answers = true;
- qdcount++;
- if (qdcount >= UINT16_MAX)
- break;
- }
+ qdcount++;
+ if (qdcount >= UINT16_MAX)
+ break;
}
- /* Append known answer section if we're asking for any shared record */
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount);
+
+ /* Append known answers section if we're asking for any shared record */
if (add_known_answers) {
r = dns_cache_export_shared_to_packet(&t->scope->cache, p, ts, 0);
if (r < 0)
return r;
+ }
+
+ *ret_packet = TAKE_PTR(p);
+ *ret_keys = TAKE_PTR(keys);
+ return add_known_answers;
+}
+
+static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL, *dummy = NULL;
+ _cleanup_set_free_ Set *keys = NULL;
+ bool add_known_answers;
+ DnsResourceKey *k;
+ unsigned c;
+ int r;
+
+ assert(t);
+ assert(t->scope->protocol == DNS_PROTOCOL_MDNS);
+
+ /* Discard any previously prepared packet, so we can start over and coalesce again */
+ t->sent = dns_packet_unref(t->sent);
+
+ /* First, create a dummy packet to calculate the number of known answers to be appended in the first packet. */
+ for (;;) {
+ r = mdns_make_dummy_packet(t, &dummy, &keys);
+ if (r == -EAGAIN)
+ continue;
+ if (r < 0)
+ return r;
- ancount = be16toh(DNS_PACKET_HEADER(p)->ancount);
+ add_known_answers = r;
+ break;
}
/* Then, create actual packet. */
- p = dns_packet_unref(p);
r = dns_packet_new_query(&p, t->scope->protocol, 0, false);
if (r < 0)
return r;
/* Questions */
- DnsResourceKey *k;
+ c = 0;
SET_FOREACH(k, keys) {
r = dns_packet_append_key(p, k, 0, NULL);
if (r < 0)
return r;
+ c++;
}
- DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount);
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(c);
/* Known answers */
if (add_known_answers) {
- r = dns_cache_export_shared_to_packet(&t->scope->cache, p, ts, ancount);
+ usec_t ts;
+
+ assert_se(sd_event_now(t->scope->manager->event, CLOCK_BOOTTIME, &ts) >= 0);
+
+ r = dns_cache_export_shared_to_packet(&t->scope->cache, p, ts, be16toh(DNS_PACKET_HEADER(dummy)->ancount));
if (r < 0)
return r;
}
/* Authorities */
- nscount = 0;
+ c = 0;
SET_FOREACH(k, keys) {
- r = dns_packet_append_zone(p, t, k, &nscount);
+ r = dns_packet_append_zone(p, t, k, &c);
if (r < 0)
return r;
}
- DNS_PACKET_HEADER(p)->nscount = htobe16(nscount);
+ DNS_PACKET_HEADER(p)->nscount = htobe16(c);
t->sent = TAKE_PTR(p);
return 0;
log_debug("Sending query via TCP since it is too large.");
else if (r == -EAGAIN)
log_debug("Sending query via TCP since UDP isn't supported or DNS-over-TLS is selected.");
- if (IN_SET(r, -EMSGSIZE, -EAGAIN))
+ else if (r == -EPERM)
+ log_debug("Sending query via TCP since UDP is blocked.");
+ if (IN_SET(r, -EMSGSIZE, -EAGAIN, -EPERM))
r = dns_transaction_emit_tcp(t);
}
if (r == -ELOOP) {
dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
return 0;
}
- if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(r)) {
+ if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_NEG_DISCONNECT(r)) {
/* On LLMNR, if we cannot connect to a host via TCP when doing reverse lookups. This means we cannot
* answer this request with this protocol. */
dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND);
if (r == 0)
continue;
- return FLAGS_SET(t->answer_query_flags, SD_RESOLVED_AUTHENTICATED);
+ return FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED);
}
return true;
/* We found the transaction that was supposed to find the SOA RR for us. It was
* successful, but found no RR for us. This means we are not at a zone cut. In this
* case, we require authentication if the SOA lookup was authenticated too. */
- return FLAGS_SET(t->answer_query_flags, SD_RESOLVED_AUTHENTICATED);
+ return FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED);
}
return true;