diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 82036f2cfeb..f5b313b67c7 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -2229,6 +2229,8 @@ Boolean Options autodetection of DNS COOKIE support to determine when to retry a request over TCP. + For DNAME lookups the default is ``yes`` and it is enforced. Servers + serving DNAME must correctly support DNS over TCP. .. note:: If a UDP response is signed using TSIG, :iscman:`named` accepts it even if diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h index c105d0a8c08..a3ad4ba2ad7 100644 --- a/lib/dns/include/dns/message.h +++ b/lib/dns/include/dns/message.h @@ -262,6 +262,7 @@ struct dns_message { unsigned int rdclass_set : 1; /* 14 */ unsigned int fuzzing : 1; /* 15 */ unsigned int free_pools : 1; /* 16 */ + unsigned int has_dname : 1; /* 17 */ unsigned int : 0; unsigned int opt_reserved; @@ -1482,4 +1483,11 @@ dns_message_createpools(isc_mem_t *mctx, isc_mempool_t **namepoolp, void dns_message_destroypools(isc_mempool_t **namepoolp, isc_mempool_t **rdspoolp); +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 8b9fd19bd74..19bb6a85300 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -463,6 +463,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; @@ -1644,6 +1645,11 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx, */ msg->tsigname->attributes.nocompress = true; free_name = false; + } else if (rdtype == dns_rdatatype_dname && + sectionid == DNS_SECTION_ANSWER && + msg->opcode == dns_opcode_query) + { + msg->has_dname = 1; } rdataset = NULL; @@ -5145,3 +5151,9 @@ dns_message_destroypools(isc_mempool_t **namepoolp, isc_mempool_t **rdspoolp) { isc_mempool_destroy(rdspoolp); isc_mempool_destroy(namepoolp); } + +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 bcac380e8fa..adc1c767478 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -815,6 +815,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 */ @@ -6680,7 +6681,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_name_t *apex = NULL; @@ -6690,7 +6692,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; /* @@ -6794,7 +6796,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; @@ -7550,6 +7552,119 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { isc_mem_putanddetach(&rctx->mctx, rctx, sizeof(*rctx)); } +static isc_result_t +rctx_cookiecheck(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + /* + * If the message was secured or TCP is already in the + * retry flags, no need to continue. + */ + if (rctx->secured || (rctx->retryopts & DNS_FETCHOPT_TCP) != 0) { + return ISC_R_SUCCESS; + } + + /* + * If we've had a cookie from the same server previously, + * retry with TCP. This may be a misconfigured anycast server + * or an attempt to send a spoofed response. + */ + if (dns_adb_getcookie(query->addrinfo, NULL, 0) > CLIENT_COOKIE_SIZE) { + if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "missing expected cookie from %s", + addrbuf); + } + rctx->retryopts |= DNS_FETCHOPT_TCP; + rctx->resend = true; + rctx_done(rctx, ISC_R_SUCCESS); + return ISC_R_COMPLETE; + } + + /* + * Retry over TCP if require-cookie is true. + */ + if (fctx->res->view->peers != NULL) { + isc_result_t result; + dns_peer_t *peer = NULL; + bool required = false; + isc_netaddr_t netaddr; + + isc_netaddr_fromsockaddr(&netaddr, &query->addrinfo->sockaddr); + result = dns_peerlist_peerbyaddr(fctx->res->view->peers, + &netaddr, &peer); + if (result == ISC_R_SUCCESS) { + dns_peer_getrequirecookie(peer, &required); + } + if (!required) { + return ISC_R_SUCCESS; + } + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "missing required cookie from %s", + addrbuf); + } + + rctx->retryopts |= DNS_FETCHOPT_TCP; + rctx->resend = true; + rctx_done(rctx, ISC_R_SUCCESS); + return ISC_R_COMPLETE; + } + + return ISC_R_SUCCESS; +} + +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; +} + static void resquery_response_continue(void *arg, isc_result_t result) { respctx_t *rctx = arg; @@ -7567,6 +7682,17 @@ resquery_response_continue(void *arg, isc_result_t result) { goto cleanup; } + /* + * 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. @@ -7574,75 +7700,24 @@ resquery_response_continue(void *arg, isc_result_t result) { INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0); /* - * If we have had a server cookie and don't get one retry over - * TCP. This may be a misconfigured anycast server or an attempt - * to send a spoofed response. Additionally retry over TCP if - * require-cookie is true and we don't have a got client cookie. - * Skip if we have a valid TSIG. + * Check for cookie issues; if found, maybe retry over TCP. */ - if (dns_message_gettsig(query->rmessage, NULL) == NULL && - !query->rmessage->cc_ok && !query->rmessage->cc_bad && - (rctx->retryopts & DNS_FETCHOPT_TCP) == 0) - { - if (dns_adb_getcookie(query->addrinfo, NULL, 0) > - CLIENT_COOKIE_SIZE) - { - if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { - char addrbuf[ISC_SOCKADDR_FORMATSIZE]; - isc_sockaddr_format(&query->addrinfo->sockaddr, - addrbuf, sizeof(addrbuf)); - isc_log_write( - dns_lctx, DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, - "missing expected cookie " - "from %s", - addrbuf); - } - rctx->retryopts |= DNS_FETCHOPT_TCP; - rctx->resend = true; - rctx_done(rctx, result); - goto cleanup; - } else if (fctx->res->view->peers != NULL) { - dns_peer_t *peer = NULL; - isc_netaddr_t netaddr; - isc_netaddr_fromsockaddr(&netaddr, - &query->addrinfo->sockaddr); - result = dns_peerlist_peerbyaddr(fctx->res->view->peers, - &netaddr, &peer); - if (result == ISC_R_SUCCESS) { - bool required = false; - result = dns_peer_getrequirecookie(peer, - &required); - if (result == ISC_R_SUCCESS && required) { - if (isc_log_wouldlog(dns_lctx, - ISC_LOG_INFO)) - { - char addrbuf - [ISC_SOCKADDR_FORMATSIZE]; - isc_sockaddr_format( - &query->addrinfo - ->sockaddr, - addrbuf, - sizeof(addrbuf)); - isc_log_write( - dns_lctx, - DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_RESOLVER, - ISC_LOG_INFO, - "missing required " - "cookie " - "from %s", - addrbuf); - } - rctx->retryopts |= DNS_FETCHOPT_TCP; - rctx->resend = true; - rctx_done(rctx, result); - goto cleanup; - } - } - } + result = rctx_cookiecheck(rctx); + if (result == ISC_R_COMPLETE) { + goto cleanup; + } + + /* + * Check whether we need to retry over TCP for some other reason. + */ + result = rctx_tcpretry(rctx); + if (result == ISC_R_COMPLETE) { + goto cleanup; } + /* + * Check for EDNS issues. + */ rctx_edns(rctx); /* @@ -8374,8 +8449,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); @@ -8447,7 +8522,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; } @@ -8748,14 +8823,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) { @@ -8763,6 +8838,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) { @@ -8771,7 +8851,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; /*