]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: don't accept responses to query unless they completely answer our questions 19079/head
authorLennart Poettering <lennart@poettering.net>
Mon, 22 Mar 2021 17:27:46 +0000 (18:27 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 22 Mar 2021 17:40:06 +0000 (18:40 +0100)
When we checking if the responses we collected for a DnsQuery are
sufficient to complete it we previously only check if one of the
collected response RRs matches at least one of the question RR keys.

This changes the logic to require that there must be at least one
response RR matched *each* of the question RR keys before considering
the answer complete.

Otherwise we might end up accepting an A reply as complete answer for an
A/AAAA query and vice versa, but we want to make sure we wait until we
get a reply on both types before returning this to the user in all
cases.

This has been broken for basically forever, but didn't surface until
b1eea703e01da1e280e179fb119449436a0c9b8e since until then we'd basically
ignore the auxiliary RRs included in CNAME/DNAME replies. Once that
commit was made we'd start using the auxiliary RRs included in
CNAME/DNAME replies but those typically included only A or only AAAA
which we then took for complete.

Fixe: #19049

src/resolve/resolved-dns-query.c
src/resolve/resolved-dns-query.h

index c5805111d21ff7bfa71096a00c6dd295562ce921..8bc06079830937a3fabd8d0d2c8eeea118f57cb5 100644 (file)
@@ -433,6 +433,14 @@ int dns_query_new(
         } else {
                 bool good = false;
 
+                /* This (primarily) checks two things:
+                 *
+                 * 1. That the question is not empty
+                 * 2. That all RR keys in the question objects are for the same domain
+                 *
+                 * Or in other words, a single DnsQuery object may be used to look up A+AAAA combination for
+                 * the same domain name, or SRV+TXT (for DNS-SD services), but not for unrelated lookups. */
+
                 if (dns_question_size(question_utf8) > 0) {
                         r = dns_question_is_valid_for_query(question_utf8);
                         if (r < 0)
@@ -1032,6 +1040,8 @@ int dns_query_process_cname(DnsQuery *q) {
         _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL;
         DnsQuestion *question;
         DnsResourceRecord *rr;
+        bool full_match = true;
+        DnsResourceKey *k;
         int r;
 
         assert(q);
@@ -1041,13 +1051,44 @@ int dns_query_process_cname(DnsQuery *q) {
 
         question = dns_query_question_for_protocol(q, q->answer_protocol);
 
-        DNS_ANSWER_FOREACH(rr, q->answer) {
-                r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
-                if (r < 0)
-                        return r;
-                if (r > 0)
-                        return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */
+        /* Small reminder: our question will consist of one or more RR keys that match in name, but not in
+         * record type. Specifically, when we do an address lookup the question will typically consist of one
+         * A and one AAAA key lookup for the same domain name. When we get a response from a server we need
+         * to check if the answer answers all our questions to use it. Note that a response of CNAME/DNAME
+         * can answer both an A and the AAAA question for us, but an A/AAAA response only the relevant
+         * type.
+         *
+         * Hence we first check of the answers we collected are sufficient to answer all our questions
+         * directly. If one question wasn't answered we go on, waiting for more replies. However, if there's
+         * a CNAME/DNAME response we use it, and redirect to it, regardless if it was a response to the A or
+         * the AAAA query.*/
+
+        DNS_QUESTION_FOREACH(k, question) {
+                bool match = false;
+
+                DNS_ANSWER_FOREACH(rr, q->answer) {
+                        r = dns_resource_key_match_rr(k, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
+                        if (r < 0)
+                                return r;
+                        if (r > 0) {
+                                match = true; /* Yay, we found an RR that matches the key we are looking for */
+                                break;
+                        }
+                }
+
+                if (!match) {
+                        /* Hmm. :-( there's no response for this key. This doesn't match. */
+                        full_match = false;
+                        break;
+                }
+        }
 
+        if (full_match)
+                return DNS_QUERY_MATCH; /* The answer can answer our question in full, no need to follow CNAMEs/DNAMEs */
+
+        /* Let's see if there is a CNAME/DNAME to match. This case is simpler: we accept the CNAME/DNAME that
+         * matches any of our questions. */
+        DNS_ANSWER_FOREACH(rr, q->answer) {
                 r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
                 if (r < 0)
                         return r;
@@ -1056,7 +1097,7 @@ int dns_query_process_cname(DnsQuery *q) {
         }
 
         if (!cname)
-                return DNS_QUERY_NOMATCH; /* No match and no cname to follow */
+                return DNS_QUERY_NOMATCH; /* No match and no CNAME/DNAME to follow */
 
         if (q->flags & SD_RESOLVED_NO_CNAME)
                 return -ELOOP;
index 5d12171b0a1290b0660287e3ce1127a0732feb8c..5d96cc06f84839d0cb997415cc16ce760799a50f 100644 (file)
@@ -45,7 +45,14 @@ struct DnsQuery {
          * that even on classic DNS some labels might use UTF8 encoding. Specifically, DNS-SD service names
          * (in contrast to their domain suffixes) use UTF-8 encoding even on DNS. Thus, the difference
          * between these two fields is mostly relevant only for explicit *hostname* lookups as well as the
-         * domain suffixes of service lookups. */
+         * domain suffixes of service lookups.
+         *
+         * Note that questions may consist of multiple RR keys at once, but they must be for the same domain
+         * name. This is used for A+AAAA and TXT+SRV lookups: we'll allocate a single DnsQuery object for
+         * them instead of two separate ones. That allows us minor optimizations with response handling:
+         * CNAME/DNAMEs of the first reply we get can already be used to follow the CNAME/DNAME chain for
+         * both, and we can take benefit of server replies that oftentimes put A responses into AAAA queries
+         * and vice versa (in the additional section). */
         DnsQuestion *question_idna;
         DnsQuestion *question_utf8;