@@ -1036,7 +1036,7 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
10361036 return 0 ;
10371037}
10381038
1039- int dns_query_process_cname (DnsQuery * q ) {
1039+ int dns_query_process_cname_one (DnsQuery * q ) {
10401040 _cleanup_ (dns_resource_record_unrefp ) DnsResourceRecord * cname = NULL ;
10411041 DnsQuestion * question ;
10421042 DnsResourceRecord * rr ;
@@ -1046,6 +1046,22 @@ int dns_query_process_cname(DnsQuery *q) {
10461046
10471047 assert (q );
10481048
1049+ /* Processes a CNAME redirect if there's one. Returns one of three values:
1050+ *
1051+ * CNAME_QUERY_MATCH → direct RR match, caller should just use the RRs in this answer (and not
1052+ * bother with any CNAME/DNAME stuff)
1053+ *
1054+ * CNAME_QUERY_NOMATCH → no match at all, neither direct nor CNAME/DNAME, caller might decide to
1055+ * restart query or take things as NODATA reply.
1056+ *
1057+ * CNAME_QUERY_CNAME → no direct RR match, but a CNAME/DNAME match that we now followed for one step.
1058+ *
1059+ * The function might also return a failure, in particular -ELOOP if we encountered too many
1060+ * CNAMEs/DNAMEs in a chain or if following CNAMEs/DNAMEs was turned off.
1061+ *
1062+ * Note that this function doesn't actually restart the query. The caller can decide to do that in
1063+ * case of CNAME_QUERY_CNAME, though. */
1064+
10491065 if (!IN_SET (q -> state , DNS_TRANSACTION_SUCCESS , DNS_TRANSACTION_NULL ))
10501066 return DNS_QUERY_NOMATCH ;
10511067
@@ -1112,17 +1128,63 @@ int dns_query_process_cname(DnsQuery *q) {
11121128 if (r < 0 )
11131129 return r ;
11141130
1115- /* Let's see if the answer can already answer the new redirected question */
1116- r = dns_query_process_cname (q );
1117- if (r != DNS_QUERY_NOMATCH )
1118- return r ;
1131+ return DNS_QUERY_CNAME ; /* Tell caller that we did a single CNAME/DNAME redirection step */
1132+ }
11191133
1120- /* OK, it cannot, let's begin with the new query */
1121- r = dns_query_go (q );
1122- if (r < 0 )
1123- return r ;
1134+ int dns_query_process_cname_many (DnsQuery * q ) {
1135+ int r ;
11241136
1125- return DNS_QUERY_RESTARTED ; /* We restarted the query for a new cname */
1137+ assert (q );
1138+
1139+ /* Follows CNAMEs through the current packet: as long as the current packet can fulfill our
1140+ * redirected CNAME queries we keep going, and restart the query once the current packet isn't good
1141+ * enough anymore. It's a wrapper around dns_query_process_cname_one() and returns the same values,
1142+ * but with extended semantics. Specifically:
1143+ *
1144+ * DNS_QUERY_MATCH → as above
1145+ *
1146+ * DNS_QUERY_CNAME → we ran into a CNAME/DNAME redirect that we could not answer from the current
1147+ * message, and thus restarted the query to resolve it.
1148+ *
1149+ * DNS_QUERY_NOMATCH → we reached the end of CNAME/DNAME chain, and there are no direct matches nor a
1150+ * CNAME/DNAME match. i.e. this is a NODATA case.
1151+ *
1152+ * Note that this function will restart the query for the caller if needed, and that's the case
1153+ * DNS_QUERY_CNAME is returned.
1154+ */
1155+
1156+ r = dns_query_process_cname_one (q );
1157+ if (r != DNS_QUERY_CNAME )
1158+ return r ; /* The first redirect is special: if it doesn't answer the question that's no
1159+ * reason to restart the query, we just accept this as a NODATA answer. */
1160+
1161+ for (;;) {
1162+ r = dns_query_process_cname_one (q );
1163+ if (r < 0 || r == DNS_QUERY_MATCH )
1164+ return r ;
1165+ if (r == DNS_QUERY_NOMATCH ) {
1166+ /* OK, so we followed one or more CNAME/DNAME RR but the existing packet can't answer
1167+ * this. Let's restart the query hence, with the new question. Why the different
1168+ * handling than the first chain element? Because if the server answers a direct
1169+ * question with an empty answer then this is a NODATA response. But if it responds
1170+ * with a CNAME chain that ultimately is incomplete (i.e. a non-empty but truncated
1171+ * CNAME chain) then we better follow up ourselves and ask for the rest of the
1172+ * chain. This is particular relevant since our cache will store CNAME/DNAME
1173+ * redirects that we learnt about for lookups of certain DNS types, but later on we
1174+ * can reuse this data even for other DNS types, but in that case need to follow up
1175+ * with the final lookup of the chain ourselves with the RR type we ourselves are
1176+ * interested in. */
1177+ r = dns_query_go (q );
1178+ if (r < 0 )
1179+ return r ;
1180+
1181+ return DNS_QUERY_CNAME ;
1182+ }
1183+
1184+ /* So we found a CNAME that the existing packet already answers, again via a CNAME, let's
1185+ * continue going then. */
1186+ assert (r == DNS_QUERY_CNAME );
1187+ }
11261188}
11271189
11281190DnsQuestion * dns_query_question_for_protocol (DnsQuery * q , DnsProtocol protocol ) {
0 commit comments