Merge pull request #19079 from poettering/resolved-ipv6-cache-fix

fix CNAME/DNAME following in combined A/AAAA replies
This commit is contained in:
Luca Boccassi
2021-03-22 23:20:11 +00:00
committed by GitHub
2 changed files with 59 additions and 11 deletions

View 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)
@@ -982,12 +990,12 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna);
if (r < 0)
return r;
else if (r > 0)
if (r > 0)
log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna));
k = dns_question_is_equal(q->question_idna, q->question_utf8);
if (k < 0)
return r;
return k;
if (k > 0) {
/* Same question? Shortcut new question generation */
nq_utf8 = dns_question_ref(nq_idna);
@@ -996,7 +1004,7 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8);
if (k < 0)
return k;
else if (k > 0)
if (k > 0)
log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8));
}
@@ -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;

View 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;