} 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)
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL;
DnsQuestion *question;
DnsResourceRecord *rr;
+ bool full_match = true;
+ DnsResourceKey *k;
int r;
assert(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;
}
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;
* 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;