]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-transaction.c
Merge pull request #2115 from dvdhrm/rbtree
[thirdparty/systemd.git] / src / resolve / resolved-dns-transaction.c
index 8c4f23a4dae36014db106f98370db6e0850cd451..90f07e6c4b9f6a6fb773c58f7b3398c42dfe8342 100644 (file)
@@ -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"
@@ -243,7 +244,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;
         }
@@ -384,6 +385,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;
 
@@ -418,7 +431,22 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
         case DNS_PROTOCOL_DNS:
                 assert(t->server);
 
-                dns_server_packet_received(t->server, ts - t->start_usec);
+                if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
+
+                        /* 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);
+
+                        r = dns_transaction_go(t);
+                        if (r < 0) {
+                                dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+                                return;
+                        }
+
+                        return;
+                } else
+                        dns_server_packet_received(t->server, t->current_features, ts - t->start_usec, p->size);
 
                 break;
         case DNS_PROTOCOL_LLMNR:
@@ -431,6 +459,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
         }
 
         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) {
@@ -439,7 +474,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
                         return;
                 }
                 if (r < 0) {
-                        /* On LLMNR, if we cannot connect to the host,
+                        /* On LLMNR and mDNS, if we cannot connect to the host,
                          * we immediately give up */
                         if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
                                 dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
@@ -466,21 +501,32 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
                 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);
+        if (t->scope->protocol == DNS_PROTOCOL_DNS) {
+                /* 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;
+                }
 
-        /* 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;
+                /* 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);
         }
 
-        /* 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), 0, p->family, &p->sender);
-
         if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
                 dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
         else
@@ -530,10 +576,13 @@ static int dns_transaction_emit(DnsTransaction *t) {
                 t->server = dns_server_ref(server);
         }
 
-        r = dns_scope_emit(t->scope, t->dns_udp_fd, t->sent);
+        r = dns_scope_emit(t->scope, t->dns_udp_fd, t->server, t->sent);
         if (r < 0)
                 return r;
 
+        if (t->server)
+                t->current_features = t->server->possible_features;
+
         return 0;
 }
 
@@ -544,50 +593,34 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
         assert(s);
         assert(t);
 
-        /* Timeout reached? Try again, with a new server */
-        dns_transaction_next_dns_server(t);
+        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);
 
-        /* ... and possibly increased timeout */
-        if (t->server)
-                dns_server_packet_lost(t->server, usec - t->start_usec);
-        else
-                dns_scope_packet_lost(t->scope, usec - t->start_usec);
+                        dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec);
 
-        r = dns_transaction_go(t);
-        if (r < 0)
-                dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
-
-        return 0;
-}
-
-static int dns_transaction_make_packet(DnsTransaction *t) {
-        _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
-        int r;
+                        break;
+                case DNS_PROTOCOL_LLMNR:
+                case DNS_PROTOCOL_MDNS:
+                        dns_scope_packet_lost(t->scope, usec - t->start_usec);
 
-        assert(t);
-
-        if (t->sent)
-                return 0;
+                        break;
+                default:
+                        assert_not_reached("Invalid DNS protocol.");
+                }
 
-        r = dns_packet_new_query(&p, t->scope->protocol, 0);
-        if (r < 0)
-                return r;
+                if (t->initial_jitter_scheduled)
+                        t->initial_jitter_elapsed = true;
+        }
 
-        r = dns_scope_good_key(t->scope, t->key);
-        if (r < 0)
-                return r;
-        if (r == 0)
-                return -EDOM;
+        /* ...and try again with a new server */
+        dns_transaction_next_dns_server(t);
 
-        r = dns_packet_append_key(p, t->key, NULL);
+        r = dns_transaction_go(t);
         if (r < 0)
-                return r;
-
-        DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
-        DNS_PACKET_HEADER(p)->id = t->id;
-
-        t->sent = p;
-        p = NULL;
+                dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
 
         return 0;
 }
@@ -601,17 +634,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_next_attempt(DnsTransaction *t, usec_t ts) {
         bool had_stream;
-        usec_t ts;
         int r;
 
         assert(t);
@@ -620,11 +654,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;
@@ -637,8 +666,6 @@ 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);
@@ -646,7 +673,21 @@ int dns_transaction_go(DnsTransaction *t) {
         t->answer_rcode = 0;
         t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
 
-        /* Check the zone, but obly if this transaction is not used
+        /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */
+        if (t->scope->protocol == DNS_PROTOCOL_DNS) {
+                r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, t->key, &t->answer);
+                if (r < 0)
+                        return r;
+                if (r > 0) {
+                        t->answer_rcode = DNS_RCODE_SUCCESS;
+                        t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR;
+                        t->answer_authenticated = true;
+                        dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
+                        return 0;
+                }
+        }
+
+        /* Check the zone, but only if this transaction is not used
          * for probing or verifying a zone item. */
         if (set_isempty(t->zone_items)) {
 
@@ -656,6 +697,7 @@ int dns_transaction_go(DnsTransaction *t) {
                 if (r > 0) {
                         t->answer_rcode = DNS_RCODE_SUCCESS;
                         t->answer_source = DNS_TRANSACTION_ZONE;
+                        t->answer_authenticated = true;
                         dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
                         return 0;
                 }
@@ -673,7 +715,7 @@ int dns_transaction_go(DnsTransaction *t) {
                 /* Let's then prune all outdated entries */
                 dns_cache_prune(&t->scope->cache);
 
-                r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer);
+                r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer, &t->answer_authenticated);
                 if (r < 0)
                         return r;
                 if (r > 0) {
@@ -686,31 +728,203 @@ 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 coaleasce 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_next_attempt(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);
+        DNS_PACKET_HEADER(p)->id = t->id;
+
+        /* 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_next_attempt(t, ts);
+        if (r <= 0)
+                return r;
+
+        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->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;
         }
 
@@ -734,11 +948,13 @@ int dns_transaction_go(DnsTransaction *t) {
                  * always be made via TCP on LLMNR */
                 r = dns_transaction_open_tcp(t);
         } else {
-                /* Try via UDP, and if that fails due to large size try via TCP */
+                /* Try via UDP, and if that fails due to large size or lack of
+                 * support try via TCP */
                 r = dns_transaction_emit(t);
-                if (r == -EMSGSIZE)
+                if (r == -EMSGSIZE || r == -EAGAIN)
                         r = dns_transaction_open_tcp(t);
         }
+
         if (r == -ESRCH) {
                 /* No servers to send this to? */
                 dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
@@ -755,16 +971,20 @@ 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;
 }
 
@@ -786,5 +1006,6 @@ static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MA
         [DNS_TRANSACTION_NETWORK] = "network",
         [DNS_TRANSACTION_CACHE] = "cache",
         [DNS_TRANSACTION_ZONE] = "zone",
+        [DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor",
 };
 DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource);