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;
 
 			/*
