summaryrefslogtreecommitdiff
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.c921
1 files changed, 813 insertions, 108 deletions
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 1103a34c6f..f09788b0c6 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -24,6 +24,7 @@
#include "dns-domain.h"
#include "fd-util.h"
#include "random-util.h"
+#include "resolved-dns-cache.h"
#include "resolved-dns-transaction.h"
#include "resolved-llmnr.h"
#include "string-table.h"
@@ -31,6 +32,7 @@
DnsTransaction* dns_transaction_free(DnsTransaction *t) {
DnsQueryCandidate *c;
DnsZoneItem *i;
+ DnsTransaction *z;
if (!t)
return NULL;
@@ -58,14 +60,25 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
dns_resource_key_unref(t->key);
- while ((c = set_steal_first(t->query_candidates)))
+ while ((c = set_steal_first(t->notify_query_candidates)))
set_remove(c->transactions, t);
+ set_free(t->notify_query_candidates);
- set_free(t->query_candidates);
-
- while ((i = set_steal_first(t->zone_items)))
+ while ((i = set_steal_first(t->notify_zone_items)))
i->probe_transaction = NULL;
- set_free(t->zone_items);
+ set_free(t->notify_zone_items);
+
+ while ((z = set_steal_first(t->notify_transactions)))
+ set_remove(z->dnssec_transactions, t);
+ set_free(t->notify_transactions);
+
+ while ((z = set_steal_first(t->dnssec_transactions))) {
+ set_remove(z->notify_transactions, t);
+ dns_transaction_gc(z);
+ }
+ set_free(t->dnssec_transactions);
+
+ dns_answer_unref(t->validated_keys);
free(t);
return NULL;
@@ -79,7 +92,9 @@ void dns_transaction_gc(DnsTransaction *t) {
if (t->block_gc > 0)
return;
- if (set_isempty(t->query_candidates) && set_isempty(t->zone_items))
+ if (set_isempty(t->notify_query_candidates) &&
+ set_isempty(t->notify_zone_items) &&
+ set_isempty(t->notify_transactions))
dns_transaction_free(t);
}
@@ -91,6 +106,14 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
assert(s);
assert(key);
+ /* Don't allow looking up invalid or pseudo RRs */
+ if (!dns_type_is_valid_query(key->type))
+ return -EINVAL;
+
+ /* We only support the IN class */
+ if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY)
+ return -EOPNOTSUPP;
+
r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL);
if (r < 0)
return r;
@@ -105,6 +128,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
t->dns_udp_fd = -1;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
+ t->dnssec_result = _DNSSEC_RESULT_INVALID;
t->key = dns_resource_key_ref(key);
/* Find a fresh, unused transaction id */
@@ -174,7 +198,7 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
log_debug("We have the lexicographically larger IP address and thus lost in the conflict.");
t->block_gc++;
- while ((z = set_first(t->zone_items))) {
+ while ((z = set_first(t->notify_zone_items))) {
/* First, make sure the zone item drops the reference
* to us */
dns_zone_item_probe_stop(z);
@@ -191,10 +215,11 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
DnsQueryCandidate *c;
DnsZoneItem *z;
+ DnsTransaction *d;
Iterator i;
assert(t);
- assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
+ assert(!DNS_TRANSACTION_IS_LIVE(state));
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
@@ -214,10 +239,12 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
/* Notify all queries that are interested, but make sure the
* transaction isn't freed while we are still looking at it */
t->block_gc++;
- SET_FOREACH(c, t->query_candidates, i)
- dns_query_candidate_ready(c);
- SET_FOREACH(z, t->zone_items, i)
- dns_zone_item_ready(z);
+ SET_FOREACH(c, t->notify_query_candidates, i)
+ dns_query_candidate_notify(c);
+ SET_FOREACH(z, t->notify_zone_items, i)
+ dns_zone_item_notify(z);
+ SET_FOREACH(d, t->notify_transactions, i)
+ dns_transaction_notify(d, t);
t->block_gc--;
dns_transaction_gc(t);
@@ -243,7 +270,7 @@ static int on_stream_complete(DnsStream *s, int error) {
}
if (dns_packet_validate_reply(p) <= 0) {
- log_debug("Invalid LLMNR TCP packet.");
+ log_debug("Invalid TCP reply packet.");
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return 0;
}
@@ -324,6 +351,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
t->server = dns_server_ref(server);
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
+ t->n_answer_cacheable = 0;
t->answer_rcode = 0;
t->stream->complete = on_stream_complete;
t->stream->transaction = t;
@@ -347,6 +375,62 @@ static void dns_transaction_next_dns_server(DnsTransaction *t) {
dns_scope_next_dns_server(t->scope);
}
+static void dns_transaction_cache_answer(DnsTransaction *t) {
+ assert(t);
+
+ /* For mDNS we cache whenever we get the packet, rather than
+ * in each transaction. */
+ if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR))
+ return;
+
+ /* We never cache if this packet is from the local host, under
+ * the assumption that a locally running DNS server would
+ * cache this anyway, and probably knows better when to flush
+ * the cache then we could. */
+ if (!DNS_PACKET_SHALL_CACHE(t->received))
+ return;
+
+ dns_cache_put(&t->scope->cache,
+ t->key,
+ t->answer_rcode,
+ t->answer,
+ t->n_answer_cacheable,
+ t->answer_authenticated,
+ 0,
+ t->received->family,
+ &t->received->sender);
+}
+
+static void dns_transaction_process_dnssec(DnsTransaction *t) {
+ int r;
+
+ assert(t);
+
+ /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */
+ if (!set_isempty(t->dnssec_transactions))
+ return;
+
+ /* All our auxiliary DNSSEC transactions are complete now. Try
+ * to validate our RRset now. */
+ r = dns_transaction_validate_dnssec(t);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+
+ if (!IN_SET(t->dnssec_result, _DNSSEC_RESULT_INVALID, DNSSEC_VALIDATED, DNSSEC_NO_SIGNATURE /* FOR NOW! */)) {
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ return;
+ }
+
+ dns_transaction_cache_answer(t);
+
+ if (t->answer_rcode == DNS_RCODE_SUCCESS)
+ dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
+ else
+ dns_transaction_complete(t, DNS_TRANSACTION_FAILURE);
+}
+
void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
usec_t ts;
int r;
@@ -361,7 +445,10 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
* should hence not attempt to access the query or transaction
* after calling this function. */
+ log_debug("Processing incoming packet on transaction %" PRIu16".", t->id);
+
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_LLMNR:
assert(t->scope->link);
@@ -384,6 +471,18 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
break;
+ case DNS_PROTOCOL_MDNS:
+ assert(t->scope->link);
+
+ /* For mDNS we will not accept any packets from other interfaces */
+ if (p->ifindex != t->scope->link->ifindex)
+ return;
+
+ if (p->family != t->scope->family)
+ return;
+
+ break;
+
case DNS_PROTOCOL_DNS:
break;
@@ -415,12 +514,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_DNS:
assert(t->server);
if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
- /* request failed, immediately try again with reduced features */
+ /* Request failed, immediately try again with reduced features */
log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p)));
dns_server_packet_failed(t->server, t->current_features);
@@ -436,16 +536,24 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
dns_server_packet_received(t->server, t->current_features, ts - t->start_usec, p->size);
break;
+
case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_MDNS:
dns_scope_packet_received(t->scope, ts - t->start_usec);
-
break;
+
default:
- break;
+ assert_not_reached("Invalid DNS protocol.");
}
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 */
r = dns_transaction_open_tcp(t);
if (r == -ESRCH) {
@@ -474,41 +582,54 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
}
}
- /* Parse and update the cache */
+ /* Parse message, if it isn't parsed yet. */
r = dns_packet_extract(p);
if (r < 0) {
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
- /* Only consider responses with equivalent query section to the request */
- if (p->question->n_keys != 1 || dns_resource_key_equal(p->question->keys[0], t->key) <= 0) {
- dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
- return;
+ if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) {
+
+ /* Only consider responses with equivalent query section to the request */
+ r = dns_packet_is_reply_for(p, t->key);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+ if (r == 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
+ return;
+ }
+
+ /* Install the answer as answer to the transaction */
+ dns_answer_unref(t->answer);
+ t->answer = dns_answer_ref(p->answer);
+ t->answer_rcode = DNS_PACKET_RCODE(p);
+ t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p);
+
+ /* According to RFC 4795, section 2.9. only the RRs
+ * from the answer section shall be cached. However,
+ * if we know the message is authenticated, we might
+ * as well cache everything. */
+ if (t->answer_authenticated)
+ t->n_answer_cacheable = (unsigned) -1; /* everything! */
+ else
+ t->n_answer_cacheable = DNS_PACKET_ANCOUNT(t->received); /* only the answer section */
+
+ r = dns_transaction_request_dnssec_keys(t);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+ if (r > 0) {
+ /* There are DNSSEC transactions pending now. Update the state accordingly. */
+ t->state = DNS_TRANSACTION_VALIDATING;
+ return;
+ }
}
- /* Install the answer as answer to the transaction */
- dns_answer_unref(t->answer);
- t->answer = dns_answer_ref(p->answer);
- t->answer_rcode = DNS_PACKET_RCODE(p);
- t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p);
-
- /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */
- if (DNS_PACKET_SHALL_CACHE(p))
- dns_cache_put(&t->scope->cache,
- t->key,
- DNS_PACKET_RCODE(p),
- p->answer,
- DNS_PACKET_ANCOUNT(p),
- t->answer_authenticated,
- 0,
- p->family,
- &p->sender);
-
- if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
- dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
- else
- dns_transaction_complete(t, DNS_TRANSACTION_FAILURE);
+ dns_transaction_process_dnssec(t);
}
static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
@@ -527,7 +648,7 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use
DNS_PACKET_ID(p) == t->id)
dns_transaction_process_reply(t, p);
else
- log_debug("Invalid DNS packet.");
+ log_debug("Invalid DNS packet, ignoring.");
return 0;
}
@@ -571,21 +692,26 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
assert(s);
assert(t);
- /* Timeout reached? Increase the timeout for the server used */
- switch (t->scope->protocol) {
- case DNS_PROTOCOL_DNS:
- assert(t->server);
+ if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) {
+ /* Timeout reached? Increase the timeout for the server used */
+ switch (t->scope->protocol) {
+ case DNS_PROTOCOL_DNS:
+ assert(t->server);
- dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec);
+ dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec);
- break;
- case DNS_PROTOCOL_LLMNR:
- case DNS_PROTOCOL_MDNS:
- dns_scope_packet_lost(t->scope, usec - t->start_usec);
+ break;
+ case DNS_PROTOCOL_LLMNR:
+ case DNS_PROTOCOL_MDNS:
+ dns_scope_packet_lost(t->scope, usec - t->start_usec);
- break;
- default:
- assert_not_reached("Invalid DNS protocol.");
+ break;
+ default:
+ assert_not_reached("Invalid DNS protocol.");
+ }
+
+ if (t->initial_jitter_scheduled)
+ t->initial_jitter_elapsed = true;
}
/* ...and try again with a new server */
@@ -598,38 +724,6 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
return 0;
}
-static int dns_transaction_make_packet(DnsTransaction *t) {
- _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
- int r;
-
- assert(t);
-
- if (t->sent)
- return 0;
-
- r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
- if (r < 0)
- return r;
-
- r = dns_scope_good_key(t->scope, t->key);
- if (r < 0)
- return r;
- if (r == 0)
- return -EDOM;
-
- r = dns_packet_append_key(p, t->key, NULL);
- if (r < 0)
- return r;
-
- DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
- DNS_PACKET_HEADER(p)->id = t->id;
-
- t->sent = p;
- p = NULL;
-
- return 0;
-}
-
static usec_t transaction_get_resend_timeout(DnsTransaction *t) {
assert(t);
assert(t->scope);
@@ -639,17 +733,18 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) {
assert(t->server);
return t->server->resend_timeout;
- case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_MDNS:
+ assert(t->n_attempts > 0);
+ return (1 << (t->n_attempts - 1)) * USEC_PER_SEC;
+ case DNS_PROTOCOL_LLMNR:
return t->scope->resend_timeout;
default:
assert_not_reached("Invalid DNS protocol.");
}
}
-int dns_transaction_go(DnsTransaction *t) {
+static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
bool had_stream;
- usec_t ts;
int r;
assert(t);
@@ -658,11 +753,6 @@ int dns_transaction_go(DnsTransaction *t) {
dns_transaction_stop(t);
- log_debug("Excercising transaction on scope %s on %s/%s",
- dns_protocol_to_string(t->scope->protocol),
- t->scope->link ? t->scope->link->name : "*",
- t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family));
-
if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) {
dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
return 0;
@@ -675,12 +765,11 @@ int dns_transaction_go(DnsTransaction *t) {
return 0;
}
- assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
-
t->n_attempts++;
t->start_usec = ts;
t->received = dns_packet_unref(t->received);
t->answer = dns_answer_unref(t->answer);
+ t->n_answer_cacheable = 0;
t->answer_rcode = 0;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
@@ -700,7 +789,7 @@ int dns_transaction_go(DnsTransaction *t) {
/* Check the zone, but only if this transaction is not used
* for probing or verifying a zone item. */
- if (set_isempty(t->zone_items)) {
+ if (set_isempty(t->notify_zone_items)) {
r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL);
if (r < 0)
@@ -716,7 +805,7 @@ int dns_transaction_go(DnsTransaction *t) {
/* Check the cache, but only if this transaction is not used
* for probing or verifying a zone item. */
- if (set_isempty(t->zone_items)) {
+ if (set_isempty(t->notify_zone_items)) {
/* Before trying the cache, let's make sure we figured out a
* server to use. Should this cause a change of server this
@@ -739,31 +828,210 @@ int dns_transaction_go(DnsTransaction *t) {
}
}
- if (t->scope->protocol == DNS_PROTOCOL_LLMNR && !t->initial_jitter) {
- usec_t jitter;
+ return 1;
+}
+
+static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
+
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ bool add_known_answers = false;
+ DnsTransaction *other;
+ unsigned qdcount;
+ usec_t ts;
+ 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);
+
+ r = dns_packet_new_query(&p, t->scope->protocol, 0, false);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_append_key(p, t->key, NULL);
+ if (r < 0)
+ return r;
+
+ qdcount = 1;
+
+ if (dns_key_is_shared(t->key))
+ add_known_answers = true;
+
+ /*
+ * 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 contraints allow them to be sent.
+ */
+
+ assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
+
+ LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) {
+
+ /* Skip ourselves */
+ if (other == t)
+ continue;
+
+ if (other->state != DNS_TRANSACTION_PENDING)
+ continue;
+
+ if (other->next_attempt_after > ts)
+ continue;
+
+ if (qdcount >= UINT16_MAX)
+ break;
+
+ r = dns_packet_append_key(p, other->key, NULL);
+
+ /*
+ * 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;
+
+ r = dns_transaction_prepare(other, ts);
+ if (r <= 0)
+ continue;
+
+ ts += transaction_get_resend_timeout(other);
+
+ r = sd_event_add_time(
+ other->scope->manager->event,
+ &other->timeout_event_source,
+ clock_boottime_or_monotonic(),
+ ts, 0,
+ on_transaction_timeout, other);
+ if (r < 0)
+ return r;
+
+ other->state = DNS_TRANSACTION_PENDING;
+ other->next_attempt_after = ts;
+
+ qdcount ++;
+
+ if (dns_key_is_shared(other->key))
+ add_known_answers = true;
+ }
+
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount);
+
+ /* Append known answer section if we're asking for any shared record */
+ if (add_known_answers) {
+ r = dns_cache_export_shared_to_packet(&t->scope->cache, p);
+ if (r < 0)
+ return r;
+ }
+
+ t->sent = p;
+ p = NULL;
+
+ return 0;
+}
+
+static int dns_transaction_make_packet(DnsTransaction *t) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ int r;
+
+ assert(t);
+
+ if (t->scope->protocol == DNS_PROTOCOL_MDNS)
+ return dns_transaction_make_packet_mdns(t);
+
+ if (t->sent)
+ return 0;
+
+ r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
+ if (r < 0)
+ return r;
+
+ r = dns_scope_good_key(t->scope, t->key);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EDOM;
+
+ r = dns_packet_append_key(p, t->key, NULL);
+ if (r < 0)
+ return r;
+
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
+ DNS_PACKET_HEADER(p)->id = t->id;
+
+ t->sent = p;
+ p = NULL;
+
+ return 0;
+}
+
+int dns_transaction_go(DnsTransaction *t) {
+ usec_t ts;
+ int r;
+
+ assert(t);
+
+ assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
+
+ r = dns_transaction_prepare(t, ts);
+ if (r <= 0)
+ return r;
+
+ if (log_get_max_level() >= LOG_DEBUG) {
+ _cleanup_free_ char *ks = NULL;
+
+ (void) dns_resource_key_to_string(t->key, &ks);
+
+ log_debug("Excercising transaction for <%s> on scope %s on %s/%s",
+ ks ? strstrip(ks) : "???",
+ dns_protocol_to_string(t->scope->protocol),
+ t->scope->link ? t->scope->link->name : "*",
+ t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family));
+ }
+
+ if (!t->initial_jitter_scheduled &&
+ (t->scope->protocol == DNS_PROTOCOL_LLMNR ||
+ t->scope->protocol == DNS_PROTOCOL_MDNS)) {
+ usec_t jitter, accuracy;
/* RFC 4795 Section 2.7 suggests all queries should be
* delayed by a random time from 0 to JITTER_INTERVAL. */
- t->initial_jitter = true;
+ t->initial_jitter_scheduled = true;
random_bytes(&jitter, sizeof(jitter));
- jitter %= LLMNR_JITTER_INTERVAL_USEC;
+
+ switch (t->scope->protocol) {
+ case DNS_PROTOCOL_LLMNR:
+ jitter %= LLMNR_JITTER_INTERVAL_USEC;
+ accuracy = LLMNR_JITTER_INTERVAL_USEC;
+ break;
+ case DNS_PROTOCOL_MDNS:
+ jitter %= MDNS_JITTER_RANGE_USEC;
+ jitter += MDNS_JITTER_MIN_USEC;
+ accuracy = MDNS_JITTER_RANGE_USEC;
+ break;
+ default:
+ assert_not_reached("bad protocol");
+ }
r = sd_event_add_time(
t->scope->manager->event,
&t->timeout_event_source,
clock_boottime_or_monotonic(),
- ts + jitter,
- LLMNR_JITTER_INTERVAL_USEC,
+ ts + jitter, accuracy,
on_transaction_timeout, t);
if (r < 0)
return r;
t->n_attempts = 0;
+ t->next_attempt_after = ts;
t->state = DNS_TRANSACTION_PENDING;
- log_debug("Delaying LLMNR transaction for " USEC_FMT "us.", jitter);
+ log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter);
return 0;
}
@@ -810,22 +1078,458 @@ int dns_transaction_go(DnsTransaction *t) {
return dns_transaction_go(t);
}
+ ts += transaction_get_resend_timeout(t);
+
r = sd_event_add_time(
t->scope->manager->event,
&t->timeout_event_source,
clock_boottime_or_monotonic(),
- ts + transaction_get_resend_timeout(t), 0,
+ ts, 0,
on_transaction_timeout, t);
if (r < 0)
return r;
t->state = DNS_TRANSACTION_PENDING;
+ t->next_attempt_after = ts;
+
+ return 1;
+}
+
+static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) {
+ DnsTransaction *aux;
+ int r;
+
+ assert(t);
+ assert(ret);
+ assert(key);
+
+ aux = dns_scope_find_transaction(t->scope, key, true);
+ if (!aux) {
+ r = dns_transaction_new(&aux, t->scope, key);
+ if (r < 0)
+ return r;
+ } else {
+ if (set_contains(t->dnssec_transactions, aux)) {
+ *ret = aux;
+ return 0;
+ }
+ }
+
+ r = set_ensure_allocated(&t->dnssec_transactions, NULL);
+ if (r < 0)
+ goto gc;
+
+ r = set_ensure_allocated(&aux->notify_transactions, NULL);
+ if (r < 0)
+ goto gc;
+
+ r = set_put(t->dnssec_transactions, aux);
+ if (r < 0)
+ goto gc;
+
+ r = set_put(aux->notify_transactions, t);
+ if (r < 0) {
+ (void) set_remove(t->dnssec_transactions, aux);
+ goto gc;
+ }
+
+ *ret = aux;
+ return 1;
+
+gc:
+ dns_transaction_gc(aux);
+ return r;
+}
+
+static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL;
+ DnsTransaction *aux;
+ int r;
+
+ assert(t);
+ assert(key);
+
+ /* Try to get the data from the trust anchor */
+ r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, key, &a);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ r = dns_answer_extend(&t->validated_keys, a);
+ if (r < 0)
+ return r;
+
+ return 0;
+ }
+
+ /* This didn't work, ask for it via the network/cache then. */
+ r = dns_transaction_add_dnssec_transaction(t, key, &aux);
+ if (r < 0)
+ return r;
+
+ if (aux->state == DNS_TRANSACTION_NULL) {
+ r = dns_transaction_go(aux);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(t);
+
+ if (t->scope->dnssec_mode != DNSSEC_YES)
+ return 0;
+
+ DNS_ANSWER_FOREACH(rr, t->answer) {
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_RRSIG: {
+ /* For each RRSIG we request the matching DNSKEY */
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL;
+
+ /* If this RRSIG is about a DNSKEY RR and the
+ * signer is the same as the owner, then we
+ * already have the DNSKEY, and we don't have
+ * to look for more. */
+ if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) {
+ r = dns_name_equal(rr->rrsig.signer, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+ }
+
+ /* If the signer is not a parent of the owner,
+ * then the signature is bogus, let's ignore
+ * it. */
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.signer);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer);
+ if (!dnskey)
+ return -ENOMEM;
+
+ log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, rr->rrsig.key_tag);
+
+ r = dns_transaction_request_dnssec_rr(t, dnskey);
+ if (r < 0)
+ return r;
+ break;
+ }
+
+ case DNS_TYPE_DNSKEY: {
+ /* For each DNSKEY we request the matching DS */
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL;
+
+ ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (!ds)
+ return -ENOMEM;
+
+ log_debug("Requesting DS to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, dnssec_keytag(rr));
+
+ r = dns_transaction_request_dnssec_rr(t, ds);
+ if (r < 0)
+ return r;
+
+ break;
+ }}
+ }
+
+ return !set_isempty(t->dnssec_transactions);
+}
+
+void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) {
+ int r;
+
+ assert(t);
+ assert(IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING));
+ assert(source);
+
+ /* Invoked whenever any of our auxiliary DNSSEC transactions
+ completed its work. We simply copy the answer from that
+ transaction over. */
+
+ if (source->state != DNS_TRANSACTION_SUCCESS) {
+ log_debug("Auxiliary DNSSEC RR query failed.");
+ t->dnssec_result = DNSSEC_FAILED_AUXILIARY;
+ } else {
+ r = dns_answer_extend(&t->validated_keys, source->answer);
+ if (r < 0) {
+ log_error_errno(r, "Failed to merge validated DNSSEC key data: %m");
+ t->dnssec_result = DNSSEC_FAILED_AUXILIARY;
+ }
+ }
+
+ /* Detach us from the DNSSEC transaction. */
+ (void) set_remove(t->dnssec_transactions, source);
+ (void) set_remove(source->notify_transactions, t);
+
+ /* If the state is still PENDING, we are still in the loop
+ * that adds further DNSSEC transactions, hence don't check if
+ * we are ready yet. If the state is VALIDATING however, we
+ * should check if we are complete now. */
+ if (t->state == DNS_TRANSACTION_VALIDATING)
+ dns_transaction_process_dnssec(t);
+}
+
+static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) {
+ int r;
+
+ assert(t);
+ assert(rr);
+
+ /* Check if the specified RR is the "primary" response,
+ * i.e. either matches the question precisely or is a
+ * CNAME/DNAME for it, or is any kind of NSEC/NSEC3 RR */
+
+ if (IN_SET(rr->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3))
+ return 1;
+
+ r = dns_resource_key_match_rr(t->key, rr, NULL);
+ if (r != 0)
+ return r;
+
+ r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL);
+ if (r != 0)
+ return r;
+
+ return 0;
+}
+
+static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) {
+ DnsResourceRecord *rr;
+ int ifindex, r;
+
+ assert(t);
+
+ /* Add all DNSKEY RRs from the answer that are validated by DS
+ * RRs from the list of validated keys to the lis of validated
+ * keys. */
+
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) {
+
+ r = dnssec_verify_dnskey_search(rr, t->validated_keys);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* If so, the DNSKEY is validated too. */
+ r = dns_answer_add_extend(&t->validated_keys, rr, ifindex);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dns_transaction_validate_dnssec(DnsTransaction *t) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
+ bool dnskeys_finalized = false;
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(t);
+
+ /* We have now collected all DS and DNSKEY RRs in
+ * t->validated_keys, let's see which RRs we can now
+ * authenticate with that. */
+
+ if (t->scope->dnssec_mode != DNSSEC_YES)
+ return 0;
+
+ /* Already validated */
+ if (t->dnssec_result != _DNSSEC_RESULT_INVALID)
+ return 0;
+
+ if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) {
+ t->dnssec_result = DNSSEC_VALIDATED;
+ t->answer_authenticated = true;
+ return 0;
+ }
+
+ if (log_get_max_level() >= LOG_DEBUG) {
+ _cleanup_free_ char *ks = NULL;
+
+ (void) dns_resource_key_to_string(t->key, &ks);
+ log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, ks ? strstrip(ks) : "???");
+ }
+
+ /* First see if there are DNSKEYs we already known a validated DS for. */
+ r = dns_transaction_validate_dnskey_by_ds(t);
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ bool changed = false;
+
+ DNS_ANSWER_FOREACH(rr, t->answer) {
+ DnssecResult result;
+
+ if (rr->key->type == DNS_TYPE_RRSIG)
+ continue;
+
+ r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result);
+ if (r < 0)
+ return r;
+
+ if (log_get_max_level() >= LOG_DEBUG) {
+ _cleanup_free_ char *rrs = NULL;
+
+ (void) dns_resource_record_to_string(rr, &rrs);
+ log_debug("Looking at %s: %s", rrs ? strstrip(rrs) : "???", dnssec_result_to_string(result));
+ }
+
+ if (result == DNSSEC_VALIDATED) {
+
+ /* Add the validated RRset to the new list of validated RRsets */
+ r = dns_answer_copy_by_key(&validated, t->answer, rr->key);
+ if (r < 0)
+ return r;
+
+ if (rr->key->type == DNS_TYPE_DNSKEY) {
+ /* If we just validated a
+ * DNSKEY RRset, then let's
+ * add these keys to the set
+ * of validated keys for this
+ * transaction. */
+
+ r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key);
+ if (r < 0)
+ return r;
+ }
+
+ /* Now, remove this RRset from the RRs still to process */
+ r = dns_answer_remove_by_key(&t->answer, rr->key);
+ if (r < 0)
+ return r;
+
+ /* Exit the loop, we dropped something from the answer, start from the beginning */
+ changed = true;
+ break;
+
+ } else if (dnskeys_finalized) {
+ /* If we haven't read all DNSKEYs yet
+ * a negative result of the validation
+ * is irrelevant, as there might be
+ * more DNSKEYs coming. */
+
+ r = dns_transaction_is_primary_response(t, rr);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* This is a primary response
+ * to our question, and it
+ * failed validation. That's
+ * fatal. */
+ t->dnssec_result = result;
+ return 0;
+ }
+
+ /* This is just some auxiliary
+ * data. Just remove the RRset and
+ * continue. */
+ r = dns_answer_remove_by_key(&t->answer, rr->key);
+ if (r < 0)
+ return r;
+
+ /* Exit the loop, we dropped something from the answer, start from the beginning */
+ changed = true;
+ break;
+ }
+ }
+
+ if (changed)
+ continue;
+
+ if (!dnskeys_finalized) {
+ /* OK, now we know we have added all DNSKEYs
+ * we possibly could to our validated
+ * list. Now run the whole thing once more,
+ * and strip everything we still cannot
+ * validate.
+ */
+ dnskeys_finalized = true;
+ continue;
+ }
+
+ /* We're done */
+ break;
+ }
+
+ dns_answer_unref(t->answer);
+ t->answer = validated;
+ validated = NULL;
+
+ /* Everything that's now in t->answer is known to be good, hence cacheable. */
+ t->n_answer_cacheable = (unsigned) -1; /* everything! */
+
+ /* At this point the answer only contains validated
+ * RRsets. Now, let's see if it actually answers the question
+ * we asked. If so, great! If it doesn't, then see if
+ * NSEC/NSEC3 can prove this. */
+ r = dns_answer_match_key(t->answer, t->key);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* Yes, it answer the question, everything is authenticated. */
+ t->dnssec_result = DNSSEC_VALIDATED;
+ t->answer_rcode = DNS_RCODE_SUCCESS;
+ t->answer_authenticated = true;
+ } else if (r == 0) {
+ DnssecNsecResult nr;
+
+ /* Bummer! Let's check NSEC/NSEC3 */
+ r = dnssec_test_nsec(t->answer, t->key, &nr);
+ if (r < 0)
+ return r;
+
+ switch (nr) {
+
+ case DNSSEC_NSEC_NXDOMAIN:
+ /* NSEC proves the domain doesn't exist. Very good. */
+ t->dnssec_result = DNSSEC_VALIDATED;
+ t->answer_rcode = DNS_RCODE_NXDOMAIN;
+ t->answer_authenticated = true;
+ break;
+
+ case DNSSEC_NSEC_NODATA:
+ /* NSEC proves that there's no data here, very good. */
+ t->dnssec_result = DNSSEC_VALIDATED;
+ t->answer_rcode = DNS_RCODE_SUCCESS;
+ t->answer_authenticated = true;
+ break;
+
+ case DNSSEC_NSEC_NO_RR:
+ /* No NSEC data? Bummer! */
+ t->dnssec_result = DNSSEC_UNSIGNED;
+ break;
+
+ case DNSSEC_NSEC_FOUND:
+ /* NSEC says it needs to be there, but we couldn't find it? Bummer! */
+ t->dnssec_result = DNSSEC_NSEC_MISMATCH;
+ break;
+
+ default:
+ assert_not_reached("Unexpected NSEC result.");
+ }
+ }
+
return 1;
}
static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = {
[DNS_TRANSACTION_NULL] = "null",
[DNS_TRANSACTION_PENDING] = "pending",
+ [DNS_TRANSACTION_VALIDATING] = "validating",
[DNS_TRANSACTION_FAILURE] = "failure",
[DNS_TRANSACTION_SUCCESS] = "success",
[DNS_TRANSACTION_NO_SERVERS] = "no-servers",
@@ -834,6 +1538,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX]
[DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply",
[DNS_TRANSACTION_RESOURCES] = "resources",
[DNS_TRANSACTION_ABORTED] = "aborted",
+ [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed",
};
DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState);