summaryrefslogtreecommitdiffstats
path: root/src/resolve
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 02:22:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 02:22:46 +0000
commitdf66ab078d7415dcd983ff92d225098db2c7244a (patch)
treeb8c6ffe2ccf913c52d04e69d9c2d62181f738ed4 /src/resolve
parentReleasing progress-linux version 252.23-1~deb12u1~progress6.99u1. (diff)
downloadsystemd-df66ab078d7415dcd983ff92d225098db2c7244a.tar.xz
systemd-df66ab078d7415dcd983ff92d225098db2c7244a.zip
Merging upstream version 252.25.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/resolve')
-rw-r--r--src/resolve/resolved-bus.c10
-rw-r--r--src/resolve/resolved-dns-answer.c22
-rw-r--r--src/resolve/resolved-dns-answer.h17
-rw-r--r--src/resolve/resolved-dns-packet.c7
-rw-r--r--src/resolve/resolved-dns-query.c27
-rw-r--r--src/resolve/resolved-dns-rr.c17
-rw-r--r--src/resolve/resolved-dns-rr.h1
-rw-r--r--src/resolve/resolved-dns-scope.c34
-rw-r--r--src/resolve/resolved-dns-scope.h1
-rw-r--r--src/resolve/resolved-dns-stream.c45
-rw-r--r--src/resolve/resolved-dns-stream.h1
-rw-r--r--src/resolve/resolved-dns-stub.c10
-rw-r--r--src/resolve/resolved-dns-transaction.c219
-rw-r--r--src/resolve/resolved-dns-transaction.h5
-rw-r--r--src/resolve/resolved-dns-trust-anchor.c10
-rw-r--r--src/resolve/resolved-mdns.c2
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)