diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h index b4c2c8ee9cd..d90a0837dfb 100644 --- a/lib/dns/include/dns/message.h +++ b/lib/dns/include/dns/message.h @@ -284,6 +284,7 @@ struct dns_message { unsigned int tkey : 1; unsigned int rdclass_set : 1; unsigned int fuzzing : 1; + unsigned int has_dname : 1; unsigned int opt_reserved; unsigned int sig_reserved; @@ -1527,4 +1528,11 @@ dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl); * \li 'pttl != NULL'. */ +bool +dns_message_hasdname(dns_message_t *msg); +/*%< + * Return whether a DNAME was detected in the ANSWER section of a QUERY + * message when it was parsed. + */ + ISC_LANG_ENDDECLS diff --git a/lib/dns/message.c b/lib/dns/message.c index 22d328c0f1f..541a854db0b 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -428,6 +428,7 @@ msginit(dns_message_t *m) { m->cc_bad = 0; m->tkey = 0; m->rdclass_set = 0; + m->has_dname = 0; m->querytsig = NULL; m->indent.string = "\t"; m->indent.count = 0; @@ -1654,6 +1655,11 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, */ msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS; free_name = false; + } else if (rdtype == dns_rdatatype_dname && + sectionid == DNS_SECTION_ANSWER && + msg->opcode == dns_opcode_query) + { + msg->has_dname = 1; } rdataset = NULL; @@ -4798,3 +4804,9 @@ dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl) { return ISC_R_SUCCESS; } + +bool +dns_message_hasdname(dns_message_t *msg) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + return msg->has_dname; +} diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index ff845ec1d84..960f1cd2d09 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -808,6 +808,7 @@ typedef struct respctx { bool get_nameservers; /* get a new NS rrset at * zone cut? */ bool resend; /* resend this query? */ + bool secured; /* message was signed or had a valid cookie */ bool nextitem; /* invalid response; keep * listening for the correct one */ bool truncated; /* response was truncated */ @@ -7141,7 +7142,8 @@ mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external, * locally served zone. */ static inline bool -name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) { +name_external(const dns_name_t *name, dns_rdatatype_t type, respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; isc_result_t result; dns_forwarders_t *forwarders = NULL; dns_fixedname_t fixed, zfixed; @@ -7154,7 +7156,7 @@ name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) { dns_namereln_t rel; apex = (ISDUALSTACK(fctx->addrinfo) || !ISFORWARDER(fctx->addrinfo)) - ? fctx->domain + ? rctx->ns_name != NULL ? rctx->ns_name : fctx->domain : fctx->fwdname; /* @@ -7263,7 +7265,7 @@ check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type, result = dns_message_findname(rctx->query->rmessage, section, addname, dns_rdatatype_any, 0, &name, NULL); if (result == ISC_R_SUCCESS) { - external = name_external(name, type, fctx); + external = name_external(name, type, rctx); if (type == dns_rdatatype_a) { for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; @@ -7893,6 +7895,47 @@ betterreferral(respctx_t *rctx) { return false; } +static bool +rctx_need_tcpretry(respctx_t *rctx) { + resquery_t *query = rctx->query; + if ((rctx->retryopts & DNS_FETCHOPT_TCP) != 0) { + /* TCP is already in the retry flags */ + return false; + } + + /* + * If the message was secured, no need to continue. + */ + if (rctx->secured) { + return false; + } + + /* + * Currently the only extra reason why we might need to + * retry a UDP response over TCP is a DNAME in the message. + */ + if (dns_message_hasdname(query->rmessage)) { + return true; + } + + return false; +} + +static isc_result_t +rctx_tcpretry(respctx_t *rctx) { + /* + * Do we need to retry a UDP response over TCP? + */ + if (rctx_need_tcpretry(rctx)) { + rctx->retryopts |= DNS_FETCHOPT_TCP; + rctx->resend = true; + rctx_done(rctx, ISC_R_SUCCESS); + return ISC_R_COMPLETE; + } + + return ISC_R_SUCCESS; +} + /* * resquery_response(): * Handles responses received in response to iterative queries sent by @@ -8082,6 +8125,17 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { return; } + /* + * Remember whether this message was signed or had a + * valid client cookie; if not, we may need to retry over + * TCP later. + */ + if (query->rmessage->cc_ok || query->rmessage->tsig != NULL || + query->rmessage->sig0 != NULL) + { + rctx.secured = true; + } + /* * The dispatcher should ensure we only get responses with QR * set. @@ -8093,10 +8147,7 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { * TCP. This may be a misconfigured anycast server or an attempt * to send a spoofed response. Skip if we have a valid tsig. */ - if (dns_message_gettsig(query->rmessage, NULL) == NULL && - !query->rmessage->cc_ok && !query->rmessage->cc_bad && - (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) - { + if (!rctx.secured && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) { unsigned char cookie[COOKIE_BUFFER_SIZE]; if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie, sizeof(cookie)) > CLIENT_COOKIE_SIZE) @@ -8108,8 +8159,7 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { isc_log_write( dns_lctx, DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, - "missing expected cookie " - "from %s", + "missing expected cookie from %s", addrbuf); } rctx.retryopts |= DNS_FETCHOPT_TCP; @@ -8119,6 +8169,17 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { } } + /* + * Check whether we need to retry over TCP for some other reason. + */ + result = rctx_tcpretry(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Check for EDNS issues. + */ rctx_edns(&rctx); /* @@ -8850,8 +8911,8 @@ rctx_answer_positive(respctx_t *rctx) { } /* - * Cache records in the authority section, if - * there are any suitable for caching. + * Cache records in the authority section, if there are + * any suitable for caching. */ rctx_authority_positive(rctx); @@ -8923,7 +8984,7 @@ rctx_answer_scan(respctx_t *rctx) { /* * Don't accept DNAME from parent namespace. */ - if (name_external(name, dns_rdatatype_dname, fctx)) { + if (name_external(name, dns_rdatatype_dname, rctx)) { continue; } @@ -9224,14 +9285,14 @@ rctx_answer_dname(respctx_t *rctx) { /* * rctx_authority_positive(): - * Examine the records in the authority section (if there are any) for a - * positive answer. We expect the names for all rdatasets in this - * section to be subdomains of the domain being queried; any that are - * not are skipped. We expect to find only *one* owner name; any names - * after the first one processed are ignored. We expect to find only - * rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever - * remains can be cached at trust level authauthority or additional - * (depending on whether the AA bit was set on the answer). + * If a positive answer was received over TCP or secured with a cookie + * or TSIG, examine the authority section. We expect names for all + * rdatasets in this section to be subdomains of the domain being queried; + * any that are not are skipped. We expect to find only *one* owner name; + * any names after the first one processed are ignored. We expect to find + * only rdatasets of type NS; all others are ignored. Whatever remains can + * be cached at trust level authauthority or additional (depending on + * whether the AA bit was set on the answer). */ static void rctx_authority_positive(respctx_t *rctx) { @@ -9239,6 +9300,11 @@ rctx_authority_positive(respctx_t *rctx) { bool done = false; isc_result_t result; + /* If it's spoofable, don't cache it. */ + if (!rctx->secured && (rctx->query->options & DNS_FETCHOPT_TCP) == 0) { + return; + } + result = dns_message_firstname(rctx->query->rmessage, DNS_SECTION_AUTHORITY); while (!done && result == ISC_R_SUCCESS) { @@ -9247,7 +9313,9 @@ rctx_authority_positive(respctx_t *rctx) { dns_message_currentname(rctx->query->rmessage, DNS_SECTION_AUTHORITY, &name); - if (!name_external(name, dns_rdatatype_ns, fctx)) { + if (!name_external(name, dns_rdatatype_ns, rctx) && + dns_name_issubdomain(fctx->name, name)) + { dns_rdataset_t *rdataset = NULL; /*