]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-dns-transaction.c
question: drop dns_question_is_superset() which we don't use anymore
[thirdparty/systemd.git] / src / resolve / resolved-dns-transaction.c
index 5540cd386e2e4b131ca681dbf33501ab37b8c722..37f47c47c064966274950674f85f503f6390527d 100644 (file)
 ***/
 
 #include "af-list.h"
-
-#include "resolved-llmnr.h"
-#include "resolved-dns-transaction.h"
+#include "alloc-util.h"
+#include "dns-domain.h"
+#include "fd-util.h"
 #include "random-util.h"
+#include "resolved-dns-transaction.h"
+#include "resolved-llmnr.h"
+#include "string-table.h"
 
 DnsTransaction* dns_transaction_free(DnsTransaction *t) {
         DnsQuery *q;
@@ -34,24 +37,25 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
 
         sd_event_source_unref(t->timeout_event_source);
 
-        dns_question_unref(t->question);
         dns_packet_unref(t->sent);
         dns_packet_unref(t->received);
         dns_answer_unref(t->cached);
 
-        sd_event_source_unref(t->dns_event_source);
-        safe_close(t->dns_fd);
+        sd_event_source_unref(t->dns_udp_event_source);
+        safe_close(t->dns_udp_fd);
 
         dns_server_unref(t->server);
         dns_stream_free(t->stream);
 
         if (t->scope) {
-                LIST_REMOVE(transactions_by_scope, t->scope->transactions, t);
+                hashmap_remove(t->scope->transactions, t->key);
 
                 if (t->id != 0)
                         hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id));
         }
 
+        dns_resource_key_unref(t->key);
+
         while ((q = set_steal_first(t->queries)))
                 set_remove(q->transactions, t);
         set_free(t->queries);
@@ -76,26 +80,30 @@ void dns_transaction_gc(DnsTransaction *t) {
                 dns_transaction_free(t);
 }
 
-int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsQuestion *q) {
+int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) {
         _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL;
         int r;
 
         assert(ret);
         assert(s);
-        assert(q);
+        assert(key);
 
         r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL);
         if (r < 0)
                 return r;
 
+        r = hashmap_ensure_allocated(&s->transactions, &dns_resource_key_hash_ops);
+        if (r < 0)
+                return r;
+
         t = new0(DnsTransaction, 1);
         if (!t)
                 return -ENOMEM;
 
-        t->dns_fd = -1;
-
-        t->question = dns_question_ref(q);
+        t->dns_udp_fd = -1;
+        t->key = dns_resource_key_ref(key);
 
+        /* Find a fresh, unused transaction id */
         do
                 random_bytes(&t->id, sizeof(t->id));
         while (t->id == 0 ||
@@ -107,7 +115,12 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsQuestion *q) {
                 return r;
         }
 
-        LIST_PREPEND(transactions_by_scope, s->transactions, t);
+        r = hashmap_put(s->transactions, t->key, t);
+        if (r < 0) {
+                hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id));
+                return r;
+        }
+
         t->scope = s;
 
         if (ret)
@@ -175,9 +188,6 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
         assert(t);
         assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
 
-        if (!IN_SET(t->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
-                return;
-
         /* Note that this call might invalidate the query. Callers
          * should hence not attempt to access the query or transaction
          * after calling this function. */
@@ -243,7 +253,7 @@ static int on_stream_complete(DnsStream *s, int error) {
 }
 
 static int dns_transaction_open_tcp(DnsTransaction *t) {
-        _cleanup_(dns_server_unrefp) DnsServer *server = NULL;
+        DnsServer *server = NULL;
         _cleanup_close_ int fd = -1;
         int r;
 
@@ -252,11 +262,13 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
         if (t->stream)
                 return 0;
 
-        if (t->scope->protocol == DNS_PROTOCOL_DNS)
+        switch (t->scope->protocol) {
+        case DNS_PROTOCOL_DNS:
                 fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53, &server);
-        else if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
+                break;
 
-                /* When we already received a query to this (but it was truncated), send to its sender address */
+        case DNS_PROTOCOL_LLMNR:
+                /* When we already received a reply to this (but it was truncated), send to its sender address */
                 if (t->received)
                         fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL);
                 else {
@@ -266,16 +278,23 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
                         /* Otherwise, try to talk to the owner of a
                          * the IP address, in case this is a reverse
                          * PTR lookup */
-                        r = dns_question_extract_reverse_address(t->question, &family, &address);
+
+                        r = dns_name_address(DNS_RESOURCE_KEY_NAME(t->key), &family, &address);
                         if (r < 0)
                                 return r;
                         if (r == 0)
                                 return -EINVAL;
+                        if (family != t->scope->family)
+                                return -ESRCH;
 
                         fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT, NULL);
                 }
-        } else
+
+                break;
+
+        default:
                 return -EAFNOSUPPORT;
+        }
 
         if (fd < 0)
                 return fd;
@@ -292,7 +311,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
                 return r;
         }
 
-
         dns_server_unref(t->server);
         t->server = dns_server_ref(server);
         t->received = dns_packet_unref(t->received);
@@ -312,24 +330,28 @@ static void dns_transaction_next_dns_server(DnsTransaction *t) {
         assert(t);
 
         t->server = dns_server_unref(t->server);
-        t->dns_event_source = sd_event_source_unref(t->dns_event_source);
-        t->dns_fd = safe_close(t->dns_fd);
+        t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source);
+        t->dns_udp_fd = safe_close(t->dns_udp_fd);
 
         dns_scope_next_dns_server(t->scope);
 }
 
 void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
+        usec_t ts;
         int r;
 
         assert(t);
         assert(p);
         assert(t->state == DNS_TRANSACTION_PENDING);
+        assert(t->scope);
+        assert(t->scope->manager);
 
         /* Note that this call might invalidate the query. Callers
          * should hence not attempt to access the query or transaction
          * after calling this function. */
 
-        if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
+        switch (t->scope->protocol) {
+        case DNS_PROTOCOL_LLMNR:
                 assert(t->scope->link);
 
                 /* For LLMNR we will not accept any packets from other
@@ -348,24 +370,14 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
                         dns_transaction_tentative(t, p);
                         return;
                 }
-        }
-
-        if (t->scope->protocol == DNS_PROTOCOL_DNS) {
-
-                /* For DNS we are fine with accepting packets on any
-                 * interface, but the source IP address must be the
-                 * one of the DNS server we queried */
-
-                assert(t->server);
 
-                if (t->server->family != p->family)
-                        return;
+                break;
 
-                if (!in_addr_equal(p->family, &p->sender, &t->server->address))
-                        return;
+        case DNS_PROTOCOL_DNS:
+                break;
 
-                if (p->sender_port != 53)
-                        return;
+        default:
+                assert_not_reached("Invalid DNS protocol.");
         }
 
         if (t->received != p) {
@@ -387,6 +399,24 @@ 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);
+
+                dns_server_packet_received(t->server, ts - t->start_usec);
+
+                break;
+        case DNS_PROTOCOL_LLMNR:
+        case DNS_PROTOCOL_MDNS:
+                dns_scope_packet_received(t->scope, ts - t->start_usec);
+
+                break;
+        default:
+                break;
+        }
+
         if (DNS_PACKET_TC(p)) {
                 /* Response was truncated, let's try again with good old TCP */
                 r = dns_transaction_open_tcp(t);
@@ -424,14 +454,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
         }
 
         /* Only consider responses with equivalent query section to the request */
-        if (!dns_question_is_superset(p->question, t->question) ||
-            !dns_question_is_superset(t->question, p->question)) {
+        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;
         }
 
         /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */
-        dns_cache_put(&t->scope->cache, p->question, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender);
+        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);
@@ -439,6 +468,56 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
                 dns_transaction_complete(t, DNS_TRANSACTION_FAILURE);
 }
 
+static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+        DnsTransaction *t = userdata;
+        int r;
+
+        assert(t);
+        assert(t->scope);
+
+        r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p);
+        if (r <= 0)
+                return r;
+
+        if (dns_packet_validate_reply(p) > 0 &&
+            DNS_PACKET_ID(p) == t->id)
+                dns_transaction_process_reply(t, p);
+        else
+                log_debug("Invalid DNS packet.");
+
+        return 0;
+}
+
+static int dns_transaction_emit(DnsTransaction *t) {
+        int r;
+
+        assert(t);
+
+        if (t->scope->protocol == DNS_PROTOCOL_DNS && !t->server) {
+                DnsServer *server = NULL;
+                _cleanup_close_ int fd = -1;
+
+                fd = dns_scope_udp_dns_socket(t->scope, &server);
+                if (fd < 0)
+                        return fd;
+
+                r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t);
+                if (r < 0)
+                        return r;
+
+                t->dns_udp_fd = fd;
+                fd = -1;
+                t->server = dns_server_ref(server);
+        }
+
+        r = dns_scope_emit(t->scope, t->dns_udp_fd, t->sent);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) {
         DnsTransaction *t = userdata;
         int r;
@@ -449,6 +528,12 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
         /* Timeout reached? Try again, with a new server */
         dns_transaction_next_dns_server(t);
 
+        /* ... 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);
+
         r = dns_transaction_go(t);
         if (r < 0)
                 dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
@@ -458,7 +543,6 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
 
 static int dns_transaction_make_packet(DnsTransaction *t) {
         _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
-        unsigned n, added = 0;
         int r;
 
         assert(t);
@@ -470,24 +554,17 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
         if (r < 0)
                 return r;
 
-        for (n = 0; n < t->question->n_keys; n++) {
-                r = dns_scope_good_key(t->scope, t->question->keys[n]);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        continue;
-
-                r = dns_packet_append_key(p, t->question->keys[n], NULL);
-                if (r < 0)
-                        return r;
-
-                added++;
-        }
-
-        if (added <= 0)
+        r = dns_scope_good_key(t->scope, t->key);
+        if (r < 0)
+                return r;
+        if (r == 0)
                 return -EDOM;
 
-        DNS_PACKET_HEADER(p)->qdcount = htobe16(added);
+        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;
@@ -496,8 +573,26 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
         return 0;
 }
 
+static usec_t transaction_get_resend_timeout(DnsTransaction *t) {
+        assert(t);
+        assert(t->scope);
+
+        switch (t->scope->protocol) {
+        case DNS_PROTOCOL_DNS:
+                assert(t->server);
+
+                return t->server->resend_timeout;
+        case DNS_PROTOCOL_LLMNR:
+        case DNS_PROTOCOL_MDNS:
+                return t->scope->resend_timeout;
+        default:
+                assert_not_reached("Invalid DNS protocol.");
+        }
+}
+
 int dns_transaction_go(DnsTransaction *t) {
         bool had_stream;
+        usec_t ts;
         int r;
 
         assert(t);
@@ -523,12 +618,28 @@ 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->server = dns_server_unref(t->server);
+        t->start_usec = ts;
         t->received = dns_packet_unref(t->received);
         t->cached = dns_answer_unref(t->cached);
         t->cached_rcode = 0;
 
+        /* Check the zone, but obly if this transaction is not used
+         * for probing or verifying a zone item. */
+        if (set_isempty(t->zone_items)) {
+
+                r = dns_zone_lookup(&t->scope->zone, t->key, &t->cached, NULL, NULL);
+                if (r < 0)
+                        return r;
+                if (r > 0) {
+                        t->cached_rcode = DNS_RCODE_SUCCESS;
+                        dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
+                        return 0;
+                }
+        }
+
         /* Check the cache, but only if this transaction is not used
          * for probing or verifying a zone item. */
         if (set_isempty(t->zone_items)) {
@@ -541,11 +652,10 @@ 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->question, &t->cached_rcode, &t->cached);
+                r = dns_cache_lookup(&t->scope->cache, t->key, &t->cached_rcode, &t->cached);
                 if (r < 0)
                         return r;
                 if (r > 0) {
-                        log_debug("Cache hit!");
                         if (t->cached_rcode == DNS_RCODE_SUCCESS)
                                 dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
                         else
@@ -569,7 +679,7 @@ int dns_transaction_go(DnsTransaction *t) {
                                 t->scope->manager->event,
                                 &t->timeout_event_source,
                                 clock_boottime_or_monotonic(),
-                                now(clock_boottime_or_monotonic()) + jitter,
+                                ts + jitter,
                                 LLMNR_JITTER_INTERVAL_USEC,
                                 on_transaction_timeout, t);
                 if (r < 0)
@@ -582,8 +692,6 @@ int dns_transaction_go(DnsTransaction *t) {
                 return 0;
         }
 
-        log_debug("Cache miss!");
-
         /* Otherwise, we need to ask the network */
         r = dns_transaction_make_packet(t);
         if (r == -EDOM) {
@@ -597,20 +705,16 @@ int dns_transaction_go(DnsTransaction *t) {
                 return r;
 
         if (t->scope->protocol == DNS_PROTOCOL_LLMNR &&
-            (dns_question_endswith(t->question, "in-addr.arpa") > 0 ||
-             dns_question_endswith(t->question, "ip6.arpa") > 0)) {
+            (dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "in-addr.arpa") > 0 ||
+             dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "ip6.arpa") > 0)) {
 
                 /* RFC 4795, Section 2.4. says reverse lookups shall
                  * always be made via TCP on LLMNR */
                 r = dns_transaction_open_tcp(t);
         } else {
-                DnsServer *server;
-
                 /* Try via UDP, and if that fails due to large size try via TCP */
-                r = dns_scope_emit(t->scope, t, t->sent, &server);
-                if (r >= 0)
-                        t->server = dns_server_ref(server);
-                else if (r == -EMSGSIZE)
+                r = dns_transaction_emit(t);
+                if (r == -EMSGSIZE)
                         r = dns_transaction_open_tcp(t);
         }
         if (r == -ESRCH) {
@@ -633,7 +737,7 @@ int dns_transaction_go(DnsTransaction *t) {
                         t->scope->manager->event,
                         &t->timeout_event_source,
                         clock_boottime_or_monotonic(),
-                        now(clock_boottime_or_monotonic()) + TRANSACTION_TIMEOUT_USEC(t->scope->protocol), 0,
+                        ts + transaction_get_resend_timeout(t), 0,
                         on_transaction_timeout, t);
         if (r < 0)
                 return r;
@@ -642,52 +746,6 @@ int dns_transaction_go(DnsTransaction *t) {
         return 1;
 }
 
-static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
-        _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
-        DnsTransaction *t = userdata;
-        int r;
-
-        assert(t);
-        assert(t->scope);
-
-        r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p);
-        if (r <= 0)
-                return r;
-
-        if (dns_packet_validate_reply(p) > 0 &&
-            DNS_PACKET_ID(p) == t->id) {
-                dns_transaction_process_reply(t, p);
-        } else
-                log_debug("Invalid DNS packet.");
-
-        return 0;
-}
-
-int transaction_dns_fd(DnsTransaction *t) {
-        int r;
-
-        assert(t);
-        assert(t->scope);
-        assert(t->scope->manager);
-
-        if (t->dns_fd >= 0)
-                return t->dns_fd;
-
-        t->dns_fd = socket(t->scope->family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
-        if (t->dns_fd < 0)
-                return -errno;
-
-        r = sd_event_add_io(t->scope->manager->event, &t->dns_event_source, t->dns_fd, EPOLLIN, on_dns_packet, t);
-        if (r < 0)
-                goto fail;
-
-        return t->dns_fd;
-
-fail:
-        t->dns_fd = safe_close(t->dns_fd);
-        return r;
-}
-
 static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = {
         [DNS_TRANSACTION_NULL] = "null",
         [DNS_TRANSACTION_PENDING] = "pending",