diff options
Diffstat (limited to 'src/resolve')
-rw-r--r-- | src/resolve/resolved-bus.c | 10 | ||||
-rw-r--r-- | src/resolve/resolved-dns-answer.c | 22 | ||||
-rw-r--r-- | src/resolve/resolved-dns-answer.h | 17 | ||||
-rw-r--r-- | src/resolve/resolved-dns-packet.c | 7 | ||||
-rw-r--r-- | src/resolve/resolved-dns-query.c | 27 | ||||
-rw-r--r-- | src/resolve/resolved-dns-rr.c | 17 | ||||
-rw-r--r-- | src/resolve/resolved-dns-rr.h | 1 | ||||
-rw-r--r-- | src/resolve/resolved-dns-scope.c | 34 | ||||
-rw-r--r-- | src/resolve/resolved-dns-scope.h | 1 | ||||
-rw-r--r-- | src/resolve/resolved-dns-stream.c | 45 | ||||
-rw-r--r-- | src/resolve/resolved-dns-stream.h | 1 | ||||
-rw-r--r-- | src/resolve/resolved-dns-stub.c | 10 | ||||
-rw-r--r-- | src/resolve/resolved-dns-transaction.c | 219 | ||||
-rw-r--r-- | src/resolve/resolved-dns-transaction.h | 5 | ||||
-rw-r--r-- | src/resolve/resolved-dns-trust-anchor.c | 10 | ||||
-rw-r--r-- | src/resolve/resolved-mdns.c | 2 |
16 files changed, 301 insertions, 127 deletions
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 919573c..b2d8273 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -12,6 +12,7 @@ #include "missing_capability.h" #include "resolved-bus.h" #include "resolved-def.h" +#include "resolved-dns-stream.h" #include "resolved-dns-synthesize.h" #include "resolved-dnssd-bus.h" #include "resolved-dnssd.h" @@ -1854,6 +1855,7 @@ static int bus_method_reset_server_features(sd_bus_message *message, void *userd bus_client_log(message, "server feature reset"); + (void) dns_stream_disconnect_all(m); manager_reset_server_features(m); return sd_bus_reply_method_return(message, NULL); @@ -2240,9 +2242,15 @@ static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_b if (b) return 0; - log_debug("Coming back from suspend, verifying all RRs..."); + log_debug("Coming back from suspend, closing all TCP connections..."); + (void) dns_stream_disconnect_all(m); + + log_debug("Coming back from suspend, resetting all probed server features..."); + manager_reset_server_features(m); + log_debug("Coming back from suspend, verifying all RRs..."); manager_verify_all(m); + return 0; } diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 3d42b0d..bf023a7 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -181,11 +181,23 @@ int dns_answer_add( exist = ordered_set_get(a->items, &tmp); if (exist) { - /* There's already an RR of the same RRset in place! Let's see if the TTLs more or less - * match. We don't really care if they match precisely, but we do care whether one is 0 and - * the other is not. See RFC 2181, Section 5.2. */ - if ((rr->ttl == 0) != (exist->rr->ttl == 0)) - return -EINVAL; + /* There's already an RR of the same RRset in place! Let's see if the TTLs more or + * less match. RFC 2181, Section 5.2 suggests clients should reject RRsets + * containing RRs with differing TTLs. We are more tolerant of this situation except + * if one RR has a zero TTL and the other a nonzero TTL. In mDNS, zero TTLs are + * special, so we must error in that case. */ + if ((rr->ttl == 0) != (exist->rr->ttl == 0)) { + if ((exist->flags | flags) & DNS_ANSWER_REFUSE_TTL_NO_MATCH) + return log_debug_errno( + SYNTHETIC_ERRNO(EINVAL), + "Refusing to merge RRs with zero TTL and non-zero TTL: %s vs. %s", + dns_resource_record_to_string(rr), + dns_resource_record_to_string(exist->rr)); + + log_debug("Merging RRs with zero TTL and non-zero TTL (not RFC 2181/5.2 compliant): %s vs. %s", + dns_resource_record_to_string(rr), + dns_resource_record_to_string(exist->rr)); + } /* Entry already exists, keep the entry with the higher TTL. */ if (rr->ttl > exist->rr->ttl) { diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index 93afea3..068803c 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -14,14 +14,15 @@ typedef struct DnsAnswerItem DnsAnswerItem; * Note that we usually encode the empty DnsAnswer object as a simple NULL. */ typedef enum DnsAnswerFlags { - DNS_ANSWER_AUTHENTICATED = 1 << 0, /* Item has been authenticated */ - DNS_ANSWER_CACHEABLE = 1 << 1, /* Item is subject to caching */ - DNS_ANSWER_SHARED_OWNER = 1 << 2, /* For mDNS: RRset may be owner by multiple peers */ - DNS_ANSWER_CACHE_FLUSH = 1 << 3, /* For mDNS: sets cache-flush bit in the rrclass of response records */ - DNS_ANSWER_GOODBYE = 1 << 4, /* For mDNS: item is subject to disappear */ - DNS_ANSWER_SECTION_ANSWER = 1 << 5, /* When parsing: RR originates from answer section */ - DNS_ANSWER_SECTION_AUTHORITY = 1 << 6, /* When parsing: RR originates from authority section */ - DNS_ANSWER_SECTION_ADDITIONAL = 1 << 7, /* When parsing: RR originates from additional section */ + DNS_ANSWER_AUTHENTICATED = 1 << 0, /* Item has been authenticated */ + DNS_ANSWER_CACHEABLE = 1 << 1, /* Item is subject to caching */ + DNS_ANSWER_SHARED_OWNER = 1 << 2, /* For mDNS: RRset may be owner by multiple peers */ + DNS_ANSWER_CACHE_FLUSH = 1 << 3, /* For mDNS: sets cache-flush bit in the rrclass of response records */ + DNS_ANSWER_GOODBYE = 1 << 4, /* For mDNS: item is subject to disappear */ + DNS_ANSWER_SECTION_ANSWER = 1 << 5, /* When parsing: RR originates from answer section */ + DNS_ANSWER_SECTION_AUTHORITY = 1 << 6, /* When parsing: RR originates from authority section */ + DNS_ANSWER_SECTION_ADDITIONAL = 1 << 7, /* When parsing: RR originates from additional section */ + DNS_ANSWER_REFUSE_TTL_NO_MATCH = 1 << 8, /* For mDNS; refuse to merge a zero TTL RR with a nonzero TTL RR */ DNS_ANSWER_MASK_SECTIONS = DNS_ANSWER_SECTION_ANSWER| DNS_ANSWER_SECTION_AUTHORITY| diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index ad0a34e..644f923 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -2365,8 +2365,11 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { } else { DnsAnswerFlags flags = 0; - if (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush) - flags |= DNS_ANSWER_SHARED_OWNER; + if (p->protocol == DNS_PROTOCOL_MDNS) { + flags |= DNS_ANSWER_REFUSE_TTL_NO_MATCH; + if (!cache_flush) + flags |= DNS_ANSWER_SHARED_OWNER; + } /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be * cached. Hence mark only those RRs as cacheable by default, but not the ones from diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 7eb6b97..16334c6 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -57,6 +57,21 @@ static void dns_query_candidate_stop(DnsQueryCandidate *c) { } } +static void dns_query_candidate_abandon(DnsQueryCandidate *c) { + DnsTransaction *t; + + assert(c); + + /* Abandon all the DnsTransactions attached to this query */ + + while ((t = set_steal_first(c->transactions))) { + t->wait_for_answer = true; + set_remove(t->notify_query_candidates, c); + set_remove(t->notify_query_candidates_done, c); + dns_transaction_gc(t); + } +} + static DnsQueryCandidate* dns_query_candidate_unlink(DnsQueryCandidate *c) { assert(c); @@ -354,6 +369,16 @@ static void dns_query_stop(DnsQuery *q) { dns_query_candidate_stop(c); } +static void dns_query_abandon(DnsQuery *q) { + assert(q); + + /* Thankfully transactions have their own timeouts */ + event_source_disable(q->timeout_event_source); + + LIST_FOREACH(candidates_by_query, c, q->candidates) + dns_query_candidate_abandon(c); +} + static void dns_query_unlink_candidates(DnsQuery *q) { assert(q); @@ -588,7 +613,7 @@ void dns_query_complete(DnsQuery *q, DnsTransactionState state) { (void) manager_monitor_send(q->manager, q->state, q->answer_rcode, q->answer_errno, q->question_idna, q->question_utf8, q->question_bypass, q->collected_questions, q->answer); - dns_query_stop(q); + dns_query_abandon(q); if (q->complete) q->complete(q); } diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index a4379c1..c449efa 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -181,6 +181,23 @@ bool dns_resource_key_is_dnssd_ptr(const DnsResourceKey *key) { dns_name_endswith(dns_resource_key_name(key), "_udp.local"); } +bool dns_resource_key_is_dnssd_two_label_ptr(const DnsResourceKey *key) { + assert(key); + + /* Check if this is a PTR resource key used in Service Instance + * Enumeration as described in RFC6763 § 4.1, excluding selective + * service names described in RFC6763 § 7.1. */ + + if (key->type != DNS_TYPE_PTR) + return false; + + const char *name = dns_resource_key_name(key); + if (dns_name_parent(&name) <= 0) + return false; + + return dns_name_equal(name, "_tcp.local") || dns_name_equal(name, "_udp.local"); +} + int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { int r; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index d558842..becc1c5 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -305,6 +305,7 @@ DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); const char* dns_resource_key_name(const DnsResourceKey *key); bool dns_resource_key_is_address(const DnsResourceKey *key); bool dns_resource_key_is_dnssd_ptr(const DnsResourceKey *key); +bool dns_resource_key_is_dnssd_two_label_ptr(const DnsResourceKey *key); int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain); int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 4085b01..4780bc2 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -424,7 +424,15 @@ static int dns_scope_socket( return r; } - if (ifindex != 0) { + bool addr_is_nonlocal = s->link && + !manager_find_link_address(s->manager, sa.sa.sa_family, sockaddr_in_addr(&sa.sa)) && + in_addr_is_localhost(sa.sa.sa_family, sockaddr_in_addr(&sa.sa)) == 0; + + if (addr_is_nonlocal && ifindex != 0) { + /* As a special exception we don't use UNICAST_IF if we notice that the specified IP address + * is on the local host. Otherwise, destination addresses on the local host result in + * EHOSTUNREACH, since Linux won't send the packets out of the specified interface, but + * delivers them directly to the local socket. */ r = socket_set_unicast_if(fd, sa.sa.sa_family, ifindex); if (r < 0) return r; @@ -463,19 +471,13 @@ static int dns_scope_socket( else { bool bound = false; - /* Let's temporarily bind the socket to the specified ifindex. The kernel currently takes - * only the SO_BINDTODEVICE/SO_BINDTOINDEX ifindex into account when making routing decisions + /* Let's temporarily bind the socket to the specified ifindex. Older kernels only take + * the SO_BINDTODEVICE/SO_BINDTOINDEX ifindex into account when making routing decisions * in connect() — and not IP_UNICAST_IF. We don't really want any of the other semantics of * SO_BINDTODEVICE/SO_BINDTOINDEX, hence we immediately unbind the socket after the fact * again. - * - * As a special exception we don't do this if we notice that the specified IP address is on - * the local host. SO_BINDTODEVICE in combination with destination addresses on the local - * host result in EHOSTUNREACH, since Linux won't send the packets out of the specified - * interface, but delivers them directly to the local socket. */ - if (s->link && - !manager_find_link_address(s->manager, sa.sa.sa_family, sockaddr_in_addr(&sa.sa)) && - in_addr_is_localhost(sa.sa.sa_family, sockaddr_in_addr(&sa.sa)) == 0) { + */ + if (addr_is_nonlocal) { r = socket_bind_to_ifindex(fd, ifindex); if (r < 0) return r; @@ -594,6 +596,7 @@ DnsScopeMatch dns_scope_good_domain( /* This returns the following return values: * * DNS_SCOPE_NO → This scope is not suitable for lookups of this domain, at all + * DNS_SCOPE_LAST_RESORT→ This scope is not suitable, unless we have no alternative * DNS_SCOPE_MAYBE → This scope is suitable, but only if nothing else wants it * DNS_SCOPE_YES_BASE+n → This scope is suitable, and 'n' suffix labels match * @@ -739,7 +742,7 @@ DnsScopeMatch dns_scope_good_domain( if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0)) - return DNS_SCOPE_MAYBE; + return DNS_SCOPE_LAST_RESORT; if ((dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ @@ -762,7 +765,7 @@ DnsScopeMatch dns_scope_good_domain( if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0)) - return DNS_SCOPE_MAYBE; + return DNS_SCOPE_LAST_RESORT; if ((dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ !is_gateway_hostname(domain) && /* don't resolve "_gateway" with LLMNR, let local synthesizing logic handle that */ @@ -1441,9 +1444,10 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { continue; } - /* Collect service types for _services._dns-sd._udp.local RRs in a set */ + /* Collect service types for _services._dns-sd._udp.local RRs in a set. Only two-label names + * (not selective names) are considered according to RFC6763 § 9. */ if (!scope->announced && - dns_resource_key_is_dnssd_ptr(z->rr->key)) { + dns_resource_key_is_dnssd_two_label_ptr(z->rr->key)) { if (!set_contains(types, dns_resource_key_name(z->rr->key))) { r = set_ensure_put(&types, &dns_name_hash_ops, dns_resource_key_name(z->rr->key)); if (r < 0) diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 1f9d22b..ffa81b2 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -18,6 +18,7 @@ typedef struct DnsScope DnsScope; typedef enum DnsScopeMatch { DNS_SCOPE_NO, + DNS_SCOPE_LAST_RESORT, DNS_SCOPE_MAYBE, DNS_SCOPE_YES_BASE, /* Add the number of matching labels to this */ DNS_SCOPE_YES_END = DNS_SCOPE_YES_BASE + DNS_N_LABELS_MAX, diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index 95fbb56..dd3da7b 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -195,7 +195,7 @@ static int dns_stream_identify(DnsStream *s) { /* Make sure all packets for this connection are sent on the same interface */ r = socket_set_unicast_if(s->fd, s->local.sa.sa_family, s->ifindex); if (r < 0) - log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF/IPV6_UNICAST_IF: %m"); + log_debug_errno(r, "Failed to invoke IP_UNICAST_IF/IPV6_UNICAST_IF: %m"); } s->identified = true; @@ -454,7 +454,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use if (progressed && s->timeout_event_source) { r = sd_event_source_set_time_relative(s->timeout_event_source, DNS_STREAM_ESTABLISHED_TIMEOUT_USEC); if (r < 0) - log_warning_errno(errno, "Couldn't restart TCP connection timeout, ignoring: %m"); + log_warning_errno(r, "Couldn't restart TCP connection timeout, ignoring: %m"); } return 0; @@ -593,3 +593,44 @@ void dns_stream_detach(DnsStream *s) { dns_server_unref_stream(s->server); } + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + dns_stream_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + dns_stream_unref); + +int dns_stream_disconnect_all(Manager *m) { + _cleanup_(set_freep) Set *closed = NULL; + int r; + + assert(m); + + /* Terminates all TCP connections (called after system suspend for example, to speed up recovery) */ + + log_info("Closing all remaining TCP connections."); + + bool restart; + do { + restart = false; + + LIST_FOREACH(streams, s, m->dns_streams) { + r = set_ensure_put(&closed, &dns_stream_hash_ops, s); + if (r < 0) + return log_oom(); + if (r > 0) { + /* Haven't seen this one before. Close it. */ + dns_stream_ref(s); + (void) dns_stream_complete(s, ECONNRESET); + + /* This might have a ripple effect, let's hence no look at the list further, + * but scan from the beginning again */ + restart = true; + break; + } + } + } while (restart); + + return 0; +} diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h index ba4a59e..912b9bf 100644 --- a/src/resolve/resolved-dns-stream.h +++ b/src/resolve/resolved-dns-stream.h @@ -126,3 +126,4 @@ static inline bool DNS_STREAM_QUEUED(DnsStream *s) { } void dns_stream_detach(DnsStream *s); +int dns_stream_disconnect_all(Manager *m); diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 11e24b8..4e903f1 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -837,12 +837,20 @@ static void dns_stub_query_complete(DnsQuery *query) { break; case DNS_TRANSACTION_NO_SERVERS: + /* We're not configured to give answers for this question. Refuse it. */ + (void) dns_stub_send_reply(q, DNS_RCODE_REFUSED); + break; + + case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: + /* This RR Type is not implemented */ + (void) dns_stub_send_reply(q, DNS_RCODE_NOTIMP); + break; + case DNS_TRANSACTION_INVALID_REPLY: case DNS_TRANSACTION_ERRNO: case DNS_TRANSACTION_ABORTED: case DNS_TRANSACTION_DNSSEC_FAILED: case DNS_TRANSACTION_NO_TRUST_ANCHOR: - case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: case DNS_TRANSACTION_NETWORK_DOWN: case DNS_TRANSACTION_NO_SOURCE: case DNS_TRANSACTION_STUB_LOOP: diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 21326fa..e23ec4c 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -175,6 +175,9 @@ DnsTransaction* dns_transaction_gc(DnsTransaction *t) { if (t->block_gc > 0) return t; + if (t->wait_for_answer && IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) + return t; + if (set_isempty(t->notify_query_candidates) && set_isempty(t->notify_query_candidates_done) && set_isempty(t->notify_zone_items) && @@ -2238,7 +2241,7 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource return 1; } -static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { +static int dns_transaction_request_dnssec_rr_full(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; DnsTransaction *aux; int r; @@ -2255,13 +2258,18 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * if (r < 0) return r; + if (ret) + *ret = NULL; 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 == -ELOOP) /* This would result in a cyclic dependency */ + if (r == -ELOOP) { /* This would result in a cyclic dependency */ + if (ret) + *ret = NULL; return 0; + } if (r < 0) return r; @@ -2269,11 +2277,19 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * r = dns_transaction_go(aux); if (r < 0) return r; + if (ret) + *ret = aux; } return 1; } +static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { + assert(t); + assert(key); + return dns_transaction_request_dnssec_rr_full(t, key, NULL); +} + static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) { int r; @@ -2374,6 +2390,8 @@ static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) { int dns_transaction_request_dnssec_keys(DnsTransaction *t) { DnsResourceRecord *rr; + /* Have we already requested a record that would be sufficient to validate an insecure delegation? */ + bool chased_insecure = false; int r; assert(t); @@ -2386,11 +2404,11 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * - For RRSIG we get the matching DNSKEY * - For DNSKEY we get the matching DS * - For unsigned SOA/NS we get the matching DS - * - For unsigned CNAME/DNAME/DS we get the parent SOA RR - * - For other unsigned RRs we get the matching SOA RR + * - For unsigned CNAME/DNAME/DS we get the parent DS RR + * - For other unsigned RRs we get the matching DS RR * - For SOA/NS queries with no matching response RR, and no NSEC/NSEC3, the DS RR - * - For DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR - * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR + * - For DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's DS RR + * - For other queries with no matching response RRs, and no NSEC/NSEC3, the DS RR */ if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) || t->scope->dnssec_mode == DNSSEC_NO) @@ -2417,6 +2435,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { case DNS_TYPE_RRSIG: { /* For each RRSIG we request the matching DNSKEY */ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; + DnsTransaction *aux; /* If this RRSIG is about a DNSKEY RR and the * signer is the same as the owner, then we @@ -2453,9 +2472,22 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", t->id, dns_resource_key_name(rr->key), rr->rrsig.key_tag); - r = dns_transaction_request_dnssec_rr(t, dnskey); + r = dns_transaction_request_dnssec_rr_full(t, dnskey, &aux); if (r < 0) return r; + + /* If we are requesting a DNSKEY, we can anticiapte 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) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = + dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(dnskey)); + if (!ds) + return -ENOMEM; + r = dns_transaction_request_dnssec_rr(t, ds); + if (r < 0) + return r; + } break; } @@ -2522,6 +2554,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); @@ -2530,6 +2566,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r > 0) continue; + chased_insecure = true; ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); if (!ds) return -ENOMEM; @@ -2546,11 +2583,11 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { case DNS_TYPE_DS: case DNS_TYPE_CNAME: case DNS_TYPE_DNAME: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; const char *name; /* CNAMEs and DNAMEs cannot be located at a - * zone apex, hence ask for the parent SOA for + * zone apex, hence ask for the parent DS for * unsigned CNAME/DNAME RRs, maybe that's the * apex. But do all that only if this is * actually a response to our original @@ -2584,25 +2621,40 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r == 0) continue; - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); - if (!soa) + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, name); + if (!ds) return -ENOMEM; - log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", + log_debug("Requesting parent DS to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", t->id, dns_resource_key_name(rr->key)); - r = dns_transaction_request_dnssec_rr(t, soa); + r = dns_transaction_request_dnssec_rr(t, ds); 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; } default: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; /* For other unsigned RRsets (including * NSEC/NSEC3!), look for proof the zone is - * unsigned, by requesting the SOA RR of the + * unsigned, by requesting the DS RR of the * zone. However, do so only if they are * directly relevant to our original * question. */ @@ -2619,13 +2671,13 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r > 0) continue; - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, dns_resource_key_name(rr->key)); - if (!soa) + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); + if (!ds) return -ENOMEM; - log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", + log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, dns_resource_key_name(rr->key), dns_resource_record_to_string(rr)); - r = dns_transaction_request_dnssec_rr(t, soa); + r = dns_transaction_request_dnssec_rr(t, ds); if (r < 0) return r; break; @@ -2640,49 +2692,38 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r < 0) return r; if (r > 0) { - const char *name, *signed_status; - uint16_t type = 0; - - name = dns_resource_key_name(dns_transaction_key(t)); - signed_status = dns_answer_contains_nsec_or_nsec3(t->answer) ? "signed" : "unsigned"; + const char *name = dns_resource_key_name(dns_transaction_key(t)); + bool was_signed = dns_answer_contains_nsec_or_nsec3(t->answer); - /* If this was a SOA or NS request, then check if there's a DS RR for the same domain. Note that this - * could also be used as indication that we are not at a zone apex, but in real world setups there are - * too many broken DNS servers (Hello, incapdns.net!) where non-terminal zones return NXDOMAIN even - * though they have further children. If this was a DS request, then it's signed when the parent zone - * is signed, hence ask the parent SOA in that case. If this was any other RR then ask for the SOA RR, - * to see if that is signed. */ - - if (dns_transaction_key(t)->type == DNS_TYPE_DS) { - r = dns_name_parent(&name); - if (r > 0) { - type = DNS_TYPE_SOA; - log_debug("Requesting parent SOA (%s %s) to validate transaction %" PRIu16 " (%s, %s empty DS response).", - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), name, t->id, - dns_resource_key_name(dns_transaction_key(t)), signed_status); - } else + /* If the response is empty, seek the DS for this name, just in case we're at a zone cut + * already, unless we just requested the DS, in which case we have to ask the parent to make + * progress. + * + * If this was an SOA or NS request, we could also skip to the parent, but in real world + * setups there are too many broken DNS servers (Hello, incapdns.net!) where non-terminal + * zones return NXDOMAIN even though they have further children. */ + + if (chased_insecure || was_signed) + /* In this case we already reqeusted 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 + * find a referral to the SOA or hit it anyway and get a positive DS response. */ + if (dns_name_parent(&name) <= 0) name = NULL; - } else if (IN_SET(dns_transaction_key(t)->type, DNS_TYPE_SOA, DNS_TYPE_NS)) { - - type = DNS_TYPE_DS; - log_debug("Requesting DS (%s %s) to validate transaction %" PRIu16 " (%s, %s empty SOA/NS response).", - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), name, t->id, name, signed_status); - - } else { - type = DNS_TYPE_SOA; - log_debug("Requesting SOA (%s %s) to validate transaction %" PRIu16 " (%s, %s empty non-SOA/NS/DS response).", - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), name, t->id, name, signed_status); - } - if (name) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + + log_debug("Requesting DS (%s %s) to validate transaction %" PRIu16 " (%s empty response).", + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), name, t->id, + dns_resource_key_name(dns_transaction_key(t))); - soa = dns_resource_key_new(dns_transaction_key(t)->class, type, name); - if (!soa) + ds = dns_resource_key_new(dns_transaction_key(t)->class, DNS_TYPE_DS, name); + if (!ds) return -ENOMEM; - r = dns_transaction_request_dnssec_rr(t, soa); + r = dns_transaction_request_dnssec_rr(t, ds); if (r < 0) return r; } @@ -2762,7 +2803,6 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * DnsTransaction *dt; /* For SOA or NS RRs we look for a matching DS transaction */ - SET_FOREACH(dt, t->dnssec_transactions) { if (dns_transaction_key(dt)->class != rr->key->class) @@ -2770,7 +2810,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * if (dns_transaction_key(dt)->type != DNS_TYPE_DS) continue; - r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), dns_resource_key_name(rr->key)); + r = dns_name_endswith(dns_resource_key_name(rr->key), dns_resource_key_name(dns_transaction_key(dt))); if (r < 0) return r; if (r == 0) @@ -2799,16 +2839,16 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * DnsTransaction *dt; /* - * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. + * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent DS. * - * DS RRs are signed if the parent is signed, hence also look at the parent SOA + * DS RRs are signed if the parent is signed, hence also look at the parent DS */ SET_FOREACH(dt, t->dnssec_transactions) { if (dns_transaction_key(dt)->class != rr->key->class) continue; - if (dns_transaction_key(dt)->type != DNS_TYPE_SOA) + if (dns_transaction_key(dt)->type != DNS_TYPE_DS) continue; if (!parent) { @@ -2826,13 +2866,18 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * } } - r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), parent); + r = dns_name_endswith(parent, dns_resource_key_name(dns_transaction_key(dt))); if (r < 0) return r; 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; @@ -2841,25 +2886,26 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * default: { DnsTransaction *dt; - /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */ + /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our DS lookup was authenticated */ SET_FOREACH(dt, t->dnssec_transactions) { - if (dns_transaction_key(dt)->class != rr->key->class) continue; - if (dns_transaction_key(dt)->type != DNS_TYPE_SOA) + if (dns_transaction_key(dt)->type != DNS_TYPE_DS) continue; - r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), dns_resource_key_name(rr->key)); + r = dns_name_endswith(dns_resource_key_name(rr->key), dns_resource_key_name(dns_transaction_key(dt))); if (r < 0) return r; if (r == 0) continue; - /* 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(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; @@ -2929,7 +2975,6 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) { char key_str[DNS_RESOURCE_KEY_STRING_MAX]; DnsTransaction *dt; const char *name; - uint16_t type = 0; int r; assert(t); @@ -2964,43 +3009,37 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) { name = dns_resource_key_name(dns_transaction_key(t)); - if (dns_transaction_key(t)->type == DNS_TYPE_DS) { - - /* We got a negative reply for this DS lookup? DS RRs are signed when their parent zone is signed, - * hence check the parent SOA in this case. */ - + if (IN_SET(dns_transaction_key(t)->type, DNS_TYPE_DS, DNS_TYPE_CNAME, DNS_TYPE_DNAME)) { + /* We got a negative reply for this DS/CNAME/DNAME lookup? Check the parent in this case to + * see if this answer should have been signed. */ r = dns_name_parent(&name); if (r < 0) return r; if (r == 0) return true; + } - type = DNS_TYPE_SOA; - - } else if (IN_SET(dns_transaction_key(t)->type, DNS_TYPE_SOA, DNS_TYPE_NS)) - /* We got a negative reply for this SOA/NS lookup? If so, check if there's a DS RR for this */ - type = DNS_TYPE_DS; - else - /* For all other negative replies, check for the SOA lookup */ - type = DNS_TYPE_SOA; - - /* For all other RRs we check the SOA on the same level to see + /* For all other RRs we check the DS on the same level to see * if it's signed. */ SET_FOREACH(dt, t->dnssec_transactions) { - if (dns_transaction_key(dt)->class != dns_transaction_key(t)->class) continue; - if (dns_transaction_key(dt)->type != type) + if (dns_transaction_key(dt)->type != DNS_TYPE_DS) continue; - r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), name); + r = dns_name_endswith(name, dns_resource_key_name(dns_transaction_key(dt))); if (r < 0) return r; 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); } /* If in doubt, require NSEC/NSEC3 */ diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index 5a0e357..2710e1b 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -135,6 +135,11 @@ struct DnsTransaction { unsigned block_gc; + /* Set when we're willing to let this transaction live beyond it's usefulness for the original query, + * for caching purposes. This blocks gc while there is still a chance we might still receive an + * answer. */ + bool wait_for_answer; + LIST_FIELDS(DnsTransaction, transactions_by_scope); LIST_FIELDS(DnsTransaction, transactions_by_stream); LIST_FIELDS(DnsTransaction, transactions_by_key); diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index 69a484d..6409615 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -163,7 +163,15 @@ static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { "private\0" /* Defined by RFC 8375. The most official choice. */ - "home.arpa\0"; + "home.arpa\0" + + /* RFC 8880 says because the 'ipv4only.arpa' zone has to + * be an insecure delegation, DNSSEC cannot be used to + * protect these answers from tampering by malicious + * devices on the path */ + "ipv4only.arpa\0" + "170.0.0.192.in-addr.arpa\0" + "171.0.0.192.in-addr.arpa\0"; const char *name; int r; diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index f5f1560..4fd47fc 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -315,7 +315,7 @@ static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) { } DNS_ANSWER_FOREACH_ITEM(item, answer) { - DnsAnswerFlags flags = item->flags; + DnsAnswerFlags flags = item->flags | DNS_ANSWER_REFUSE_TTL_NO_MATCH; /* The cache-flush bit must not be set in legacy unicast responses. * See section 6.7 of RFC 6762. */ if (legacy_query) |