From 0ceed03ebea395da1a39ad1cb39205ce75a3f768 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
Date: Mon, 11 Sep 2023 10:35:28 +0200
Subject: [PATCH 1/9] Use hashtable when parsing a message

When parsing messages use a hashtable instead of a linear search to reduce
the amount of work done in findname when there's more than one name in
the section.

There are two hashtables:

1) hashtable for owner names - that's constructed for each section when we
hit the second name in the section and destroyed right after parsing
that section;

2) per-name hashtable - for each name in the section, we construct a new
hashtable for that name if there are more than one rdataset for that
particular name.

(cherry picked from commit b8a96317544c7b310b4f74360825a87b6402ddc2)
Signed-off-by: Ismael Luceno <iluceno@suse.de>
---
 lib/dns/include/dns/message.h |  39 ----
 lib/dns/include/dns/name.h    |   9 +-
 lib/dns/message.c             | 424 ++++++++++++++++++++++------------
 lib/dns/name.c                |   1 +
 4 files changed, 279 insertions(+), 194 deletions(-)

Index: bind-9.11.22/lib/dns/include/dns/message.h
===================================================================
--- bind-9.11.22.orig/lib/dns/include/dns/message.h
+++ bind-9.11.22/lib/dns/include/dns/message.h
@@ -747,45 +747,6 @@ dns_message_findtype(dns_name_t *name, d
  *\li	#ISC_R_NOTFOUND		-- the desired type does not exist.
  */
 
-isc_result_t
-dns_message_find(dns_name_t *name, dns_rdataclass_t rdclass,
-		 dns_rdatatype_t type, dns_rdatatype_t covers,
-		 dns_rdataset_t **rdataset);
-/*%<
- * Search the name for the specified rdclass and type.  If it is found,
- * *rdataset is filled in with a pointer to that rdataset.
- *
- * Requires:
- *\li	if '**rdataset' is non-NULL, *rdataset needs to be NULL.
- *
- *\li	'type' be a valid type, and NOT dns_rdatatype_any.
- *
- *\li	If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type.
- *	Otherwise it should be 0.
- *
- * Returns:
- *\li	#ISC_R_SUCCESS		-- all is well.
- *\li	#ISC_R_NOTFOUND		-- the desired type does not exist.
- */
-
-void
-dns_message_movename(dns_message_t *msg, dns_name_t *name,
-		     dns_section_t fromsection,
-		     dns_section_t tosection);
-/*%<
- * Move a name from one section to another.
- *
- * Requires:
- *
- *\li	'msg' be valid.
- *
- *\li	'name' must be a name already in 'fromsection'.
- *
- *\li	'fromsection' must be a valid section.
- *
- *\li	'tosection' must be a valid section.
- */
-
 void
 dns_message_addname(dns_message_t *msg, dns_name_t *name,
 		    dns_section_t section);
Index: bind-9.11.22/lib/dns/include/dns/name.h
===================================================================
--- bind-9.11.22.orig/lib/dns/include/dns/name.h
+++ bind-9.11.22/lib/dns/include/dns/name.h
@@ -68,6 +68,7 @@
 #include <inttypes.h>
 #include <stdbool.h>
 
+#include <isc/ht.h>
 #include <isc/lang.h>
 #include <isc/magic.h>
 #include <isc/region.h>		/* Required for storage size of dns_label_t. */
@@ -111,6 +112,7 @@ struct dns_name {
 	isc_buffer_t *			buffer;
 	ISC_LINK(dns_name_t)		link;
 	ISC_LIST(dns_rdataset_t)	list;
+	isc_ht_t *ht;
 };
 
 #define DNS_NAME_MAGIC			ISC_MAGIC('D','N','S','n')
@@ -171,7 +173,7 @@ LIBDNS_EXTERNAL_DATA extern dns_name_t *
 	A, (sizeof(A) - 1), sizeof(B), \
 	DNS_NAMEATTR_READONLY, \
 	B, NULL, { (void *)-1, (void *)-1}, \
-	{NULL, NULL} \
+	{NULL, NULL}, NULL			    \
 }
 
 #define DNS_NAME_INITABSOLUTE(A,B) { \
@@ -179,12 +181,12 @@ LIBDNS_EXTERNAL_DATA extern dns_name_t *
 	A, sizeof(A), sizeof(B), \
 	DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE, \
 	B, NULL, { (void *)-1, (void *)-1}, \
-	{NULL, NULL} \
+	{NULL, NULL}, NULL			    \
 }
 
 #define DNS_NAME_INITEMPTY { \
 	DNS_NAME_MAGIC, NULL, 0, 0, 0, NULL, NULL, \
-	{ (void *)-1, (void *)-1 }, { NULL, NULL } \
+	{ (void *)-1, (void *)-1 }, { NULL, NULL }, NULL	\
 }
 
 /*%
@@ -1374,6 +1376,7 @@ do { \
 	_n->buffer = NULL; \
 	ISC_LINK_INIT(_n, link); \
 	ISC_LIST_INIT(_n->list); \
+	_n->ht = NULL; \
 } while (0)
 
 #define DNS_NAME_RESET(n) \
Index: bind-9.11.22/lib/dns/message.c
===================================================================
--- bind-9.11.22.orig/lib/dns/message.c
+++ bind-9.11.22/lib/dns/message.c
@@ -21,6 +21,8 @@
 #include <stdbool.h>
 
 #include <isc/buffer.h>
+#include <isc/hash.h>
+#include <isc/ht.h>
 #include <isc/mem.h>
 #include <isc/print.h>
 #include <isc/string.h> /* Required for HP/UX (and others?) */
@@ -188,6 +190,9 @@ msgblock_allocate(isc_mem_t *, unsigned
 #define msgblock_get(block, type) \
 	((type *)msgblock_internalget(block, sizeof(type)))
 
+static void
+dns__message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **rdatasetp);
+
 static inline void *
 msgblock_internalget(dns_msgblock_t *, unsigned int);
 
@@ -499,7 +504,7 @@ msgresetopt(dns_message_t *msg)
 		}
 		INSIST(dns_rdataset_isassociated(msg->opt));
 		dns_rdataset_disassociate(msg->opt);
-		isc_mempool_put(msg->rdspool, msg->opt);
+		dns__message_puttemprdataset(msg, &msg->opt);
 		msg->opt = NULL;
 		msg->cc_ok = 0;
 		msg->cc_bad = 0;
@@ -520,10 +525,11 @@ msgresetsigs(dns_message_t *msg, bool re
 			msg->querytsig = msg->tsig;
 		} else {
 			dns_rdataset_disassociate(msg->tsig);
-			isc_mempool_put(msg->rdspool, msg->tsig);
+			dns__message_puttemprdataset(msg, &msg->tsig);
 			if (msg->querytsig != NULL) {
 				dns_rdataset_disassociate(msg->querytsig);
-				isc_mempool_put(msg->rdspool, msg->querytsig);
+				dns__message_puttemprdataset(msg,
+							     &msg->querytsig);
 			}
 		}
 		if (dns_name_dynamic(msg->tsigname))
@@ -533,13 +539,10 @@ msgresetsigs(dns_message_t *msg, bool re
 		msg->tsigname = NULL;
 	} else if (msg->querytsig != NULL && !replying) {
 		dns_rdataset_disassociate(msg->querytsig);
-		isc_mempool_put(msg->rdspool, msg->querytsig);
-		msg->querytsig = NULL;
+		dns__message_puttemprdataset(msg, &msg->querytsig);
 	}
 	if (msg->sig0 != NULL) {
-		INSIST(dns_rdataset_isassociated(msg->sig0));
-		dns_rdataset_disassociate(msg->sig0);
-		isc_mempool_put(msg->rdspool, msg->sig0);
+		dns__message_puttemprdataset(msg, &msg->sig0);
 		if (msg->sig0name != NULL) {
 			if (dns_name_dynamic(msg->sig0name))
 				dns_name_free(msg->sig0name, msg->mctx);
@@ -842,6 +845,18 @@ dns_message_destroy(dns_message_t **msgp
 }
 
 static isc_result_t
+name_hash_add(isc_ht_t *ht, dns_name_t *name, dns_name_t **foundp) {
+	isc_result_t result = isc_ht_find(ht, name->ndata, name->length,
+					  (void **)foundp);
+	if (result == ISC_R_SUCCESS) {
+		return (ISC_R_EXISTS);
+	}
+	result = isc_ht_add(ht, name->ndata, name->length, (void *)name);
+	INSIST(result == ISC_R_SUCCESS);
+	return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
 findname(dns_name_t **foundname, dns_name_t *target,
 	 dns_namelist_t *section)
 {
@@ -860,28 +875,36 @@ findname(dns_name_t **foundname, dns_nam
 	return (ISC_R_NOTFOUND);
 }
 
-isc_result_t
-dns_message_find(dns_name_t *name, dns_rdataclass_t rdclass,
-		 dns_rdatatype_t type, dns_rdatatype_t covers,
-		 dns_rdataset_t **rdataset)
-{
-	dns_rdataset_t *curr;
-
-	REQUIRE(name != NULL);
-	REQUIRE(rdataset == NULL || *rdataset == NULL);
-
-	for (curr = ISC_LIST_TAIL(name->list);
-	     curr != NULL;
-	     curr = ISC_LIST_PREV(curr, link)) {
-		if (curr->rdclass == rdclass &&
-		    curr->type == type && curr->covers == covers) {
-			if (rdataset != NULL)
-				*rdataset = curr;
-			return (ISC_R_SUCCESS);
-		}
-	}
+#ifdef _WIN32
+__pragma(pack(push, 1))
+typedef struct rds_key {
+	dns_rdataclass_t rdclass;
+	dns_rdatatype_t type;
+	dns_rdatatype_t covers;
+} rds_key_t;
+__pragma(pack(pop))
+#else
+typedef struct __attribute__((__packed__)) rds_key {
+	dns_rdataclass_t rdclass;
+	dns_rdatatype_t type;
+	dns_rdatatype_t covers;
+} rds_key_t;
+#endif
 
-	return (ISC_R_NOTFOUND);
+static isc_result_t
+rds_hash_add(isc_ht_t *ht, dns_rdataset_t *rds, dns_rdataset_t **foundp) {
+	rds_key_t key = { .rdclass = rds->rdclass,
+			  .type = rds->type,
+			  .covers = rds->covers };
+	isc_result_t result = isc_ht_find(ht, (const unsigned char *)&key,
+					  sizeof(key), (void **)foundp);
+	if (result == ISC_R_SUCCESS) {
+		return (ISC_R_EXISTS);
+	}
+	result = isc_ht_add(ht, (const unsigned char *)&key, sizeof(key),
+			    (void *)rds);
+	INSIST(result == ISC_R_SUCCESS);
+	return (ISC_R_SUCCESS);
 }
 
 isc_result_t
@@ -1009,6 +1032,18 @@ getrdata(isc_buffer_t *source, dns_messa
 		}					\
 	} while (0)
 
+static void
+cleanup_name_hashmaps(dns_namelist_t *section) {
+	dns_name_t *name = NULL;
+	for (name = ISC_LIST_HEAD(*section); name != NULL;
+	     name = ISC_LIST_NEXT(name, link))
+	{
+		if (name->ht != NULL) {
+			isc_ht_destroy(&name->ht);
+		}
+	}
+}
+
 static isc_result_t
 getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 	     unsigned int options)
@@ -1020,13 +1055,15 @@ getquestions(isc_buffer_t *source, dns_m
 	dns_offsets_t *offsets;
 	dns_rdataset_t *rdataset;
 	dns_rdatalist_t *rdatalist;
-	isc_result_t result;
+	isc_result_t result = ISC_R_SUCCESS;
 	dns_rdatatype_t rdtype;
 	dns_rdataclass_t rdclass;
 	dns_namelist_t *section;
-	bool free_name;
+	bool free_name = false;
 	bool best_effort;
 	bool seen_problem;
+	isc_ht_t *name_map = NULL;
+	bool free_ht = false;
 
 	section = &msg->sections[DNS_SECTION_QUESTION];
 
@@ -1034,9 +1071,14 @@ getquestions(isc_buffer_t *source, dns_m
 	seen_problem = false;
 
 	name = NULL;
+	name2 = NULL;
 	rdataset = NULL;
 	rdatalist = NULL;
 
+	if (msg->counts[DNS_SECTION_QUESTION] > 1) {
+		isc_ht_init(&name_map, msg->mctx, 1, ISC_HT_CASE_INSENSITIVE);
+	}
+
 	for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) {
 		name = isc_mempool_get(msg->namepool);
 		if (name == NULL)
@@ -1059,13 +1101,20 @@ getquestions(isc_buffer_t *source, dns_m
 		if (result != ISC_R_SUCCESS)
 			goto cleanup;
 
+
+		/* If there is only one QNAME, skip the duplicity checks */
+		if (name_map == NULL) {
+			result = ISC_R_SUCCESS;
+			goto skip_name_check;
+		}
+
 		/*
 		 * Run through the section, looking to see if this name
 		 * is already there.  If it is found, put back the allocated
 		 * name since we no longer need it, and set our name pointer
 		 * to point to the name we found.
 		 */
-		result = findname(&name2, name, section);
+		result = name_hash_add(name_map, name, &name2);
 
 		/*
 		 * If it is the first name in the section, accept it.
@@ -1077,18 +1126,25 @@ getquestions(isc_buffer_t *source, dns_m
 		 * this should be legal or not.  In either case we no longer
 		 * need this name pointer.
 		 */
-		if (result != ISC_R_SUCCESS) {
-			if (!ISC_LIST_EMPTY(*section))
+	skip_name_check:
+		switch (result) {
+		case ISC_R_SUCCESS:
+			if (!ISC_LIST_EMPTY(*section)) {
 				DO_ERROR(DNS_R_FORMERR);
+			}
 			ISC_LIST_APPEND(*section, name, link);
-			free_name = false;
-		} else {
-			isc_mempool_put(msg->namepool, name);
+			break;
+		case ISC_R_EXISTS:
+			dns_message_puttempname(msg, &name);
 			name = name2;
 			name2 = NULL;
-			free_name = false;
+			break;
+		default:
+			ISC_UNREACHABLE();
 		}
 
+		free_name = false;
+
 		/*
 		 * Get type and class.
 		 */
@@ -1117,13 +1173,6 @@ getquestions(isc_buffer_t *source, dns_m
 			msg->tkey = 1;
 
 		/*
-		 * Can't ask the same question twice.
-		 */
-		result = dns_message_find(name, rdclass, rdtype, 0, NULL);
-		if (result == ISC_R_SUCCESS)
-			DO_ERROR(DNS_R_FORMERR);
-
-		/*
 		 * Allocate a new rdatalist.
 		 */
 		rdatalist = newrdatalist(msg);
@@ -1131,7 +1180,7 @@ getquestions(isc_buffer_t *source, dns_m
 			result = ISC_R_NOMEMORY;
 			goto cleanup;
 		}
-		rdataset =  isc_mempool_get(msg->rdspool);
+		dns_message_gettemprdataset(msg, &rdataset);
 		if (rdataset == NULL) {
 			result = ISC_R_NOMEMORY;
 			goto cleanup;
@@ -1144,32 +1193,74 @@ getquestions(isc_buffer_t *source, dns_m
 		rdatalist->type = rdtype;
 		rdatalist->rdclass = rdclass;
 
-		dns_rdataset_init(rdataset);
 		result = dns_rdatalist_tordataset(rdatalist, rdataset);
-		if (result != ISC_R_SUCCESS)
-			goto cleanup;
+		RUNTIME_CHECK(result == ISC_R_SUCCESS);
 
 		rdataset->attributes |= DNS_RDATASETATTR_QUESTION;
 
+		/*
+		 * Skip the duplicity check for first rdataset
+		 */
+		if (ISC_LIST_EMPTY(name->list)) {
+			goto skip_rds_check;
+		}
+
+		/*
+		 * Can't ask the same question twice.
+		 */
+		if (name->ht == NULL) {
+			isc_ht_init(&name->ht, msg->mctx, 1,
+				    ISC_HT_CASE_SENSITIVE);
+			free_ht = true;
+
+			dns_rdataset_t *old_rdataset = NULL;
+			for (old_rdataset = ISC_LIST_HEAD(name->list);
+			     old_rdataset != NULL;
+			     old_rdataset = ISC_LIST_NEXT(old_rdataset, link))
+			{
+				result = rds_hash_add(name->ht, old_rdataset,
+						      NULL);
+				INSIST(result == ISC_R_SUCCESS);
+			}
+		}
+		result = rds_hash_add(name->ht, rdataset, NULL);
+		if (result == ISC_R_EXISTS) {
+			DO_ERROR(DNS_R_FORMERR);
+		}
+
+	skip_rds_check:
 		ISC_LIST_APPEND(name->list, rdataset, link);
+
 		rdataset = NULL;
 	}
 
-	if (seen_problem)
-		return (DNS_R_RECOVERABLE);
-	return (ISC_R_SUCCESS);
+
+	if (seen_problem) {
+		result = DNS_R_RECOVERABLE;
+	}
 
  cleanup:
 	if (rdataset != NULL) {
-		INSIST(!dns_rdataset_isassociated(rdataset));
-		isc_mempool_put(msg->rdspool, rdataset);
+		if (dns_rdataset_isassociated(rdataset)) {
+			dns_rdataset_disassociate(rdataset);
+		}
+		dns_message_puttemprdataset(msg, &rdataset);
 	}
 #if 0
 	if (rdatalist != NULL)
 		isc_mempool_put(msg->rdlpool, rdatalist);
 #endif
-	if (free_name)
-		isc_mempool_put(msg->namepool, name);
+	if (free_name) {
+		dns_message_puttempname(msg, &name);
+	}
+
+	if (free_ht) {
+		cleanup_name_hashmaps(section);
+	}
+
+	if (name_map != NULL) {
+		isc_ht_destroy(&name_map);
+	}
 
 	return (result);
 }
@@ -1249,24 +1340,26 @@ getsection(isc_buffer_t *source, dns_mes
 	unsigned int count, rdatalen;
 	dns_name_t *name = NULL;
 	dns_name_t *name2 = NULL;
-	dns_offsets_t *offsets;
-	dns_rdataset_t *rdataset;
-	dns_rdatalist_t *rdatalist;
-	isc_result_t result;
+	dns_offsets_t *offsets = NULL;
+	dns_rdataset_t *rdataset = NULL;
+	dns_rdataset_t *found_rdataset = NULL;
+	dns_rdatalist_t *rdatalist = NULL;
+	isc_result_t result = ISC_R_SUCCESS;
 	dns_rdatatype_t rdtype, covers;
 	dns_rdataclass_t rdclass;
-	dns_rdata_t *rdata;
+	dns_rdata_t *rdata = NULL;
 	dns_ttl_t ttl;
-	dns_namelist_t *section;
-	bool free_name = false, free_rdataset = false;
-	bool preserve_order, best_effort, seen_problem;
+	dns_namelist_t *section = &msg->sections[sectionid];
+	bool free_name = false, seen_problem = false;
+	bool free_ht = false;
+	bool preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0);
+	bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
 	bool isedns, issigzero, istsig;
+	isc_ht_t *name_map = NULL;
 
-	preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0);
-	best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
-	seen_problem = false;
-
-	section = &msg->sections[sectionid];
+	if (msg->counts[sectionid] > 1) {
+		isc_ht_init(&name_map, msg->mctx, 1, ISC_HT_CASE_INSENSITIVE);
+	}
 
 	for (count = 0; count < msg->counts[sectionid]; count++) {
 		int recstart = source->current;
@@ -1274,14 +1367,15 @@ getsection(isc_buffer_t *source, dns_mes
 
 		skip_name_search = false;
 		skip_type_search = false;
-		free_rdataset = false;
 		isedns = false;
 		issigzero = false;
 		istsig = false;
+		found_rdataset = NULL;
 
 		name = isc_mempool_get(msg->namepool);
-		if (name == NULL)
+		if (name == NULL) {
 			return (ISC_R_NOMEMORY);
+		}
 		free_name = true;
 
 		offsets = newoffsets(msg);
@@ -1297,8 +1391,9 @@ getsection(isc_buffer_t *source, dns_mes
 		isc_buffer_remainingregion(source, &r);
 		isc_buffer_setactive(source, r.length);
 		result = getname(name, source, msg, dctx);
-		if (result != ISC_R_SUCCESS)
+		if (result != ISC_R_SUCCESS) {
 			goto cleanup;
+		}
 
 		/*
 		 * Get type, class, ttl, and rdatalen.  Verify that at least
@@ -1318,9 +1413,10 @@ getsection(isc_buffer_t *source, dns_mes
 		 * established a class.  Do so now.
 		 */
 		if (msg->rdclass_set == 0 &&
-		    rdtype != dns_rdatatype_opt &&	/* class is UDP SIZE */
-		    rdtype != dns_rdatatype_tsig &&	/* class is ANY */
-		    rdtype != dns_rdatatype_tkey) {	/* class is undefined */
+		    rdtype != dns_rdatatype_opt &&  /* class is UDP SIZE */
+		    rdtype != dns_rdatatype_tsig && /* class is ANY */
+		    rdtype != dns_rdatatype_tkey)   /* class is undefined */
+		{
 			msg->rdclass = rdclass;
 			msg->rdclass_set = 1;
 		}
@@ -1329,15 +1425,17 @@ getsection(isc_buffer_t *source, dns_mes
 		 * If this class is different than the one in the question
 		 * section, bail.
 		 */
-		if (msg->opcode != dns_opcode_update
-		    && rdtype != dns_rdatatype_tsig
-		    && rdtype != dns_rdatatype_opt
-		    && rdtype != dns_rdatatype_key /* in a TKEY query */
-		    && rdtype != dns_rdatatype_sig /* SIG(0) */
-		    && rdtype != dns_rdatatype_tkey /* Win2000 TKEY */
-		    && msg->rdclass != dns_rdataclass_any
-		    && msg->rdclass != rdclass)
+		if (msg->opcode != dns_opcode_update &&
+		    rdtype != dns_rdatatype_tsig &&
+		    rdtype != dns_rdatatype_opt &&
+		    rdtype != dns_rdatatype_key &&  /* in a TKEY query */
+		    rdtype != dns_rdatatype_sig &&  /* SIG(0) */
+		    rdtype != dns_rdatatype_tkey && /* Win2000 TKEY */
+		    msg->rdclass != dns_rdataclass_any &&
+		    msg->rdclass != rdclass)
+		{
 			DO_ERROR(DNS_R_FORMERR);
+		}
 
 		/*
 		 * If this is not a TKEY query/response then the KEY
@@ -1347,7 +1445,9 @@ getsection(isc_buffer_t *source, dns_mes
 		    rdtype == dns_rdatatype_key &&
 		    msg->rdclass != dns_rdataclass_any &&
 		    msg->rdclass != rdclass)
+		{
 			DO_ERROR(DNS_R_FORMERR);
+		}
 
 		/*
 		 * Special type handling for TSIG, OPT, and TKEY.
@@ -1359,7 +1459,8 @@ getsection(isc_buffer_t *source, dns_mes
 			 */
 			if (sectionid != DNS_SECTION_ADDITIONAL ||
 			    rdclass != dns_rdataclass_any ||
-			    count != msg->counts[sectionid]  - 1) {
+			    count != msg->counts[sectionid] - 1)
+			{
 				DO_ERROR(DNS_R_BADTSIG);
 			} else {
 				skip_name_search = true;
@@ -1374,7 +1475,8 @@ getsection(isc_buffer_t *source, dns_mes
 			 */
 			if (!dns_name_equal(dns_rootname, name) ||
 			    sectionid != DNS_SECTION_ADDITIONAL ||
-			    msg->opt != NULL) {
+			    msg->opt != NULL)
+			{
 				DO_ERROR(DNS_R_FORMERR);
 			} else {
 				skip_name_search = true;
@@ -1391,13 +1493,16 @@ getsection(isc_buffer_t *source, dns_mes
 			 */
 			dns_section_t tkeysection;
 
-			if ((msg->flags & DNS_MESSAGEFLAG_QR) == 0)
+			if ((msg->flags & DNS_MESSAGEFLAG_QR) == 0) {
 				tkeysection = DNS_SECTION_ADDITIONAL;
-			else
+			} else {
 				tkeysection = DNS_SECTION_ANSWER;
+			}
 			if (sectionid != tkeysection &&
 			    sectionid != DNS_SECTION_ANSWER)
+			{
 				DO_ERROR(DNS_R_FORMERR);
+			}
 		}
 
 		/*
@@ -1423,7 +1528,8 @@ getsection(isc_buffer_t *source, dns_mes
 			goto cleanup;
 		}
 		if (msg->opcode == dns_opcode_update &&
-		    update(sectionid, rdclass)) {
+		    update(sectionid, rdclass))
+		{
 			if (rdatalen != 0) {
 				result = DNS_R_FORMERR;
 				goto cleanup;
@@ -1443,26 +1549,31 @@ getsection(isc_buffer_t *source, dns_mes
 			result = ISC_R_SUCCESS;
 		} else if (rdclass == dns_rdataclass_none &&
 			   msg->opcode == dns_opcode_update &&
-			   sectionid == DNS_SECTION_UPDATE) {
+			   sectionid == DNS_SECTION_UPDATE)
+		{
 			result = getrdata(source, msg, dctx, msg->rdclass,
 					  rdtype, rdatalen, rdata);
-		} else
-			result = getrdata(source, msg, dctx, rdclass,
-					  rdtype, rdatalen, rdata);
-		if (result != ISC_R_SUCCESS)
+		} else {
+			result = getrdata(source, msg, dctx, rdclass, rdtype,
+					  rdatalen, rdata);
+		}
+		if (result != ISC_R_SUCCESS) {
 			goto cleanup;
+		}
 		rdata->rdclass = rdclass;
-		if (rdtype == dns_rdatatype_rrsig &&
-		    rdata->flags == 0) {
+		if (rdtype == dns_rdatatype_rrsig && rdata->flags == 0) {
 			covers = dns_rdata_covers(rdata);
-			if (covers == 0)
+			if (covers == 0) {
 				DO_ERROR(DNS_R_FORMERR);
+			}
 		} else if (rdtype == dns_rdatatype_sig /* SIG(0) */ &&
-			   rdata->flags == 0) {
+			   rdata->flags == 0)
+		{
 			covers = dns_rdata_covers(rdata);
 			if (covers == 0) {
 				if (sectionid != DNS_SECTION_ADDITIONAL ||
-				    count != msg->counts[sectionid]  - 1) {
+				    count != msg->counts[sectionid] - 1)
+				{
 					DO_ERROR(DNS_R_BADSIG0);
 				} else {
 					skip_name_search = true;
@@ -1472,17 +1583,20 @@ getsection(isc_buffer_t *source, dns_mes
 			} else {
 				if (msg->rdclass != dns_rdataclass_any &&
 				    msg->rdclass != rdclass)
+				{
 					DO_ERROR(DNS_R_FORMERR);
+				}
 			}
-		} else
+		} else {
 			covers = 0;
+		}
 
 		/*
 		 * Check the ownername of NSEC3 records
 		 */
 		if (rdtype == dns_rdatatype_nsec3 &&
-		    !dns_rdata_checkowner(name, msg->rdclass, rdtype,
-					  false)) {
+		    !dns_rdata_checkowner(name, msg->rdclass, rdtype, false))
+		{
 			result = DNS_R_BADOWNERNAME;
 			goto cleanup;
 		}
@@ -1493,103 +1607,152 @@ getsection(isc_buffer_t *source, dns_mes
 		 * to the end of the message.
 		 */
 		if (preserve_order || msg->opcode == dns_opcode_update ||
-		    skip_name_search) {
+		    skip_name_search)
+		{
 			if (!isedns && !istsig && !issigzero) {
 				ISC_LIST_APPEND(*section, name, link);
 				free_name = false;
 			}
 		} else {
+			if (name_map == NULL) {
+				result = ISC_R_SUCCESS;
+				goto skip_name_check;
+			}
+
 			/*
 			 * Run through the section, looking to see if this name
 			 * is already there.  If it is found, put back the
 			 * allocated name since we no longer need it, and set
 			 * our name pointer to point to the name we found.
 			 */
-			result = findname(&name2, name, section);
+			result = name_hash_add(name_map, name, &name2);
 
 			/*
 			 * If it is a new name, append to the section.
 			 */
-			if (result == ISC_R_SUCCESS) {
-				isc_mempool_put(msg->namepool, name);
-				name = name2;
-			} else {
+		skip_name_check:
+			switch (result) {
+			case ISC_R_SUCCESS:
 				ISC_LIST_APPEND(*section, name, link);
+				break;
+			case ISC_R_EXISTS:
+				dns_message_puttempname(msg, &name);
+				name = name2;
+				name2 = NULL;
+				break;
+			default:
+				ISC_UNREACHABLE();
 			}
 			free_name = false;
 		}
 
+		dns_message_gettemprdataset(msg, &rdataset);
+		if (rdataset == NULL) {
+			result = ISC_R_NOMEMORY;
+			goto cleanup;
+		}
+
+		rdatalist = newrdatalist(msg);
+		if (rdatalist == NULL) {
+			result = ISC_R_NOMEMORY;
+			goto cleanup;
+		}
+
+		rdatalist->type = rdtype;
+		rdatalist->covers = covers;
+		rdatalist->rdclass = rdclass;
+		rdatalist->ttl = ttl;
+
+		RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) ==
+			      ISC_R_SUCCESS);
+		dns_rdataset_setownercase(rdataset, name);
+		rdatalist = NULL;
+
 		/*
 		 * Search name for the particular type and class.
 		 * Skip this stage if in update mode or this is a meta-type.
 		 */
-		if (preserve_order || msg->opcode == dns_opcode_update ||
-		    skip_type_search)
-			result = ISC_R_NOTFOUND;
-		else {
+		if (isedns || istsig || issigzero) {
+			/* Skip adding the rdataset to the tables */
+		} else if (preserve_order || msg->opcode == dns_opcode_update ||
+			   skip_type_search)
+		{
+			result = ISC_R_SUCCESS;
+
+			ISC_LIST_APPEND(name->list, rdataset, link);
+		} else {
 			/*
 			 * If this is a type that can only occur in
 			 * the question section, fail.
 			 */
-			if (dns_rdatatype_questiononly(rdtype))
+			if (dns_rdatatype_questiononly(rdtype)) {
 				DO_ERROR(DNS_R_FORMERR);
-
-			rdataset = NULL;
-			result = dns_message_find(name, rdclass, rdtype,
-						   covers, &rdataset);
-		}
-
-		/*
-		 * If we found an rdataset that matches, we need to
-		 * append this rdata to that set.  If we did not, we need
-		 * to create a new rdatalist, store the important bits there,
-		 * convert it to an rdataset, and link the latter to the name.
-		 * Yuck.  When appending, make certain that the type isn't
-		 * a singleton type, such as SOA or CNAME.
-		 *
-		 * Note that this check will be bypassed when preserving order,
-		 * the opcode is an update, or the type search is skipped.
-		 */
-		if (result == ISC_R_SUCCESS) {
-			if (dns_rdatatype_issingleton(rdtype)) {
-				dns_rdata_t *first;
-				dns_rdatalist_fromrdataset(rdataset,
-							   &rdatalist);
-				first = ISC_LIST_HEAD(rdatalist->rdata);
-				INSIST(first != NULL);
-				if (dns_rdata_compare(rdata, first) != 0)
-					DO_ERROR(DNS_R_FORMERR);
 			}
-		}
 
-		if (result == ISC_R_NOTFOUND) {
-			rdataset = isc_mempool_get(msg->rdspool);
-			if (rdataset == NULL) {
-				result = ISC_R_NOMEMORY;
-				goto cleanup;
+			if (ISC_LIST_EMPTY(name->list)) {
+				result = ISC_R_SUCCESS;
+				goto skip_rds_check;
 			}
-			free_rdataset = true;
 
-			rdatalist = newrdatalist(msg);
-			if (rdatalist == NULL) {
-				result = ISC_R_NOMEMORY;
-				goto cleanup;
+			if (name->ht == NULL) {
+				isc_ht_init(&name->ht, msg->mctx, 1,
+					    ISC_HT_CASE_SENSITIVE);
+				free_ht = true;
+
+				INSIST(ISC_LIST_HEAD(name->list) ==
+				       ISC_LIST_TAIL(name->list));
+
+				dns_rdataset_t *old_rdataset =
+					ISC_LIST_HEAD(name->list);
+
+				result = rds_hash_add(name->ht, old_rdataset,
+						      NULL);
+
+				INSIST(result == ISC_R_SUCCESS);
 			}
+			result = rds_hash_add(name->ht, rdataset,
+					      &found_rdataset);
+
+			/*
+			 * If we found an rdataset that matches, we need to
+			 * append this rdata to that set.  If we did not, we
+			 * need to create a new rdatalist, store the important
+			 * bits there, convert it to an rdataset, and link the
+			 * latter to the name. Yuck.  When appending, make
+			 * certain that the type isn't a singleton type, such as
+			 * SOA or CNAME.
+			 *
+			 * Note that this check will be bypassed when preserving
+			 * order, the opcode is an update, or the type search is
+			 * skipped.
+			 */
+		skip_rds_check:
+			switch (result) {
+			case ISC_R_EXISTS:
+				/* Free the rdataset we used as the key */
+				dns_rdataset_disassociate(rdataset);
+				dns__message_puttemprdataset(msg, &rdataset);
+				result = ISC_R_SUCCESS;
+				rdataset = found_rdataset;
 
-			rdatalist->type = rdtype;
-			rdatalist->covers = covers;
-			rdatalist->rdclass = rdclass;
-			rdatalist->ttl = ttl;
-
-			dns_rdataset_init(rdataset);
-			RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist,
-							       rdataset)
-				      == ISC_R_SUCCESS);
-			dns_rdataset_setownercase(rdataset, name);
+				if (!dns_rdatatype_issingleton(rdtype)) {
+					break;
+				}
 
-			if (!isedns && !istsig && !issigzero) {
+				dns_rdatalist_fromrdataset(rdataset,
+							   &rdatalist);
+				dns_rdata_t *first =
+					ISC_LIST_HEAD(rdatalist->rdata);
+				INSIST(first != NULL);
+				if (dns_rdata_compare(rdata, first) != 0) {
+					DO_ERROR(DNS_R_FORMERR);
+				}
+				break;
+			case ISC_R_SUCCESS:
 				ISC_LIST_APPEND(name->list, rdataset, link);
-				free_rdataset = false;
+				break;
+			default:
+				ISC_UNREACHABLE();
 			}
 		}
 
@@ -1603,8 +1766,9 @@ getsection(isc_buffer_t *source, dns_mes
 		 */
 		if (ttl != rdataset->ttl) {
 			rdataset->attributes |= DNS_RDATASETATTR_TTLADJUSTED;
-			if (ttl < rdataset->ttl)
+			if (ttl < rdataset->ttl) {
 				rdataset->ttl = ttl;
+			}
 		}
 
 		/* Append this rdata to the rdataset. */
@@ -1623,20 +1787,16 @@ getsection(isc_buffer_t *source, dns_mes
 			dns_rcode_t ercode;
 
 			msg->opt = rdataset;
-			rdataset = NULL;
-			free_rdataset = false;
 			ercode = (dns_rcode_t)
 				((msg->opt->ttl & DNS_MESSAGE_EDNSRCODE_MASK)
 				 >> 20);
 			msg->rcode |= ercode;
-			isc_mempool_put(msg->namepool, name);
+			dns_message_puttempname(msg, &name);
 			free_name = false;
 		} else if (issigzero) {
 			msg->sig0 = rdataset;
 			msg->sig0name = name;
 			msg->sigstart = recstart;
-			rdataset = NULL;
-			free_rdataset = false;
 			free_name = false;
 		} else if (istsig) {
 			msg->tsig = rdataset;
@@ -1646,20 +1806,17 @@ getsection(isc_buffer_t *source, dns_mes
 			 * Windows doesn't like TSIG names to be compressed.
 			 */
 			msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
-			rdataset = NULL;
-			free_rdataset = false;
 			free_name = false;
 		}
+		rdataset = NULL;
 
 		if (seen_problem) {
-			if (free_name)
-				isc_mempool_put(msg->namepool, name);
-			if (free_rdataset)
-				isc_mempool_put(msg->rdspool, rdataset);
-			free_name = free_rdataset = false;
+			if (free_name) {
+				dns_message_puttempname(msg, &name);
+			}
+			free_name = false;
 		}
 		INSIST(free_name == false);
-		INSIST(free_rdataset == false);
 	}
 
 	/*
@@ -1670,20 +1827,32 @@ getsection(isc_buffer_t *source, dns_mes
 	if (sectionid == DNS_SECTION_AUTHORITY &&
 	    msg->opcode == dns_opcode_query &&
 	    ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) &&
-	    ((msg->flags & DNS_MESSAGEFLAG_TC) == 0) &&
-	    !preserve_order &&
+	    ((msg->flags & DNS_MESSAGEFLAG_TC) == 0) && !preserve_order &&
 	    !auth_signed(section))
+	{
 		DO_ERROR(DNS_R_FORMERR);
+	}
 
-	if (seen_problem)
-		return (DNS_R_RECOVERABLE);
-	return (ISC_R_SUCCESS);
+	if (seen_problem) {
+		result = DNS_R_RECOVERABLE;
+	}
 
- cleanup:
-	if (free_name)
-		isc_mempool_put(msg->namepool, name);
-	if (free_rdataset)
+cleanup:
+	if (rdataset != NULL && rdataset != found_rdataset) {
+		dns_rdataset_disassociate(rdataset);
 		isc_mempool_put(msg->rdspool, rdataset);
+	}
+	if (free_name) {
+		dns_message_puttempname(msg, &name);
+	}
+
+	if (free_ht) {
+		cleanup_name_hashmaps(section);
+	}
+
+	if (name_map != NULL) {
+		isc_ht_destroy(&name_map);
+	}
 
 	return (result);
 }
@@ -2415,11 +2584,11 @@ dns_message_renderreset(dns_message_t *m
 		dns_message_puttempname(msg, &msg->tsigname);
 	if (msg->tsig != NULL) {
 		dns_rdataset_disassociate(msg->tsig);
-		dns_message_puttemprdataset(msg, &msg->tsig);
+		dns__message_puttemprdataset(msg, &msg->tsig);
 	}
 	if (msg->sig0 != NULL) {
 		dns_rdataset_disassociate(msg->sig0);
-		dns_message_puttemprdataset(msg, &msg->sig0);
+		dns__message_puttemprdataset(msg, &msg->sig0);
 	}
 }
 
@@ -2513,24 +2682,6 @@ dns_message_findname(dns_message_t *msg,
 }
 
 void
-dns_message_movename(dns_message_t *msg, dns_name_t *name,
-		     dns_section_t fromsection,
-		     dns_section_t tosection)
-{
-	REQUIRE(msg != NULL);
-	REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER);
-	REQUIRE(name != NULL);
-	REQUIRE(VALID_NAMED_SECTION(fromsection));
-	REQUIRE(VALID_NAMED_SECTION(tosection));
-
-	/*
-	 * Unlink the name from the old section
-	 */
-	ISC_LIST_UNLINK(msg->sections[fromsection], name, link);
-	ISC_LIST_APPEND(msg->sections[tosection], name, link);
-}
-
-void
 dns_message_addname(dns_message_t *msg, dns_name_t *name,
 		    dns_section_t section)
 {
@@ -2622,6 +2773,9 @@ dns_message_puttempname(dns_message_t *m
 	REQUIRE(DNS_MESSAGE_VALID(msg));
 	REQUIRE(item != NULL && *item != NULL);
 
+	if ((*item)->ht != NULL) {
+		isc_ht_destroy(&(*item)->ht);
+	}
 	if (dns_name_dynamic(*item))
 		dns_name_free(*item, msg->mctx);
 	isc_mempool_put(msg->namepool, *item);
@@ -2637,14 +2791,19 @@ dns_message_puttemprdata(dns_message_t *
 	*item = NULL;
 }
 
+static void
+dns__message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
+	isc_mempool_put(msg->rdspool, *item);
+	*item = NULL;
+}
+
 void
 dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item) {
 	REQUIRE(DNS_MESSAGE_VALID(msg));
 	REQUIRE(item != NULL && *item != NULL);
 
 	REQUIRE(!dns_rdataset_isassociated(*item));
-	isc_mempool_put(msg->rdspool, *item);
-	*item = NULL;
+	dns__message_puttemprdataset(msg, item);
 }
 
 void
@@ -2809,7 +2968,7 @@ dns_message_setopt(dns_message_t *msg, d
 
  cleanup:
 	dns_rdataset_disassociate(opt);
-	dns_message_puttemprdataset(msg, &opt);
+	dns__message_puttemprdataset(msg, &opt);
 	return (result);
 }
 
Index: bind-9.11.22/lib/dns/name.c
===================================================================
--- bind-9.11.22.orig/lib/dns/name.c
+++ bind-9.11.22/lib/dns/name.c
@@ -215,6 +215,7 @@ dns_name_invalidate(dns_name_t *name) {
 	name->offsets = NULL;
 	name->buffer = NULL;
 	ISC_LINK_INIT(name, link);
+	INSIST(name->ht == NULL);
 }
 
 bool
Index: bind-9.11.22/lib/dns/catz.c
===================================================================
--- bind-9.11.22.orig/lib/dns/catz.c
+++ bind-9.11.22/lib/dns/catz.c
@@ -398,33 +398,21 @@ dns_catz_zones_merge(dns_catz_zone_t *ta
 
 	dns_name_format(&target->name, czname, DNS_NAME_FORMATSIZE);
 
-	result = isc_ht_init(&toadd, target->catzs->mctx, 16);
-	if (result != ISC_R_SUCCESS)
-		goto cleanup;
+	isc_ht_init(&toadd, target->catzs->mctx, 16, ISC_HT_CASE_SENSITIVE);
 
-	result = isc_ht_init(&tomod, target->catzs->mctx, 16);
-	if (result != ISC_R_SUCCESS)
-		goto cleanup;
+	isc_ht_init(&tomod, target->catzs->mctx, 16, ISC_HT_CASE_SENSITIVE);
 
-	result = isc_ht_iter_create(newzone->entries, &iter1);
-	if (result != ISC_R_SUCCESS)
-		goto cleanup;
+	isc_ht_iter_create(newzone->entries, &iter1);
 
-	result = isc_ht_iter_create(target->entries, &iter2);
-	if (result != ISC_R_SUCCESS)
-		goto cleanup;
+	isc_ht_iter_create(target->entries, &iter2);
 
 	/*
 	 * We can create those iterators now, even though toadd and tomod are
 	 * empty
 	 */
-	result = isc_ht_iter_create(toadd, &iteradd);
-	if (result != ISC_R_SUCCESS)
-		goto cleanup;
+	isc_ht_iter_create(toadd, &iteradd);
 
-	result = isc_ht_iter_create(tomod, &itermod);
-	if (result != ISC_R_SUCCESS)
-		goto cleanup;
+	isc_ht_iter_create(tomod, &itermod);
 
 	/*
 	 * First - walk the new zone and find all nodes that are not in the
@@ -570,7 +558,6 @@ dns_catz_zones_merge(dns_catz_zone_t *ta
 
 	result = ISC_R_SUCCESS;
 
-cleanup:
 	if (iter1 != NULL)
 		isc_ht_iter_destroy(&iter1);
 	if (iter2 != NULL)
@@ -610,9 +597,7 @@ dns_catz_new_zones(dns_catz_zones_t **ca
 	if (result != ISC_R_SUCCESS)
 		goto cleanup_mutex;
 
-	result = isc_ht_init(&new_zones->zones, mctx, 4);
-	if (result != ISC_R_SUCCESS)
-		goto cleanup_refcount;
+	isc_ht_init(&new_zones->zones, mctx, 4, ISC_HT_CASE_SENSITIVE);
 
 	isc_mem_attach(mctx, &new_zones->mctx);
 	new_zones->zmm = zmm;
@@ -629,7 +614,6 @@ dns_catz_new_zones(dns_catz_zones_t **ca
 
   cleanup_ht:
 	isc_ht_destroy(&new_zones->zones);
-  cleanup_refcount:
 	isc_refcount_destroy(&new_zones->refs);
   cleanup_mutex:
 	isc_mutex_destroy(&new_zones->lock);
@@ -672,9 +656,7 @@ dns_catz_new_zone(dns_catz_zones_t *catz
 	if (result != ISC_R_SUCCESS)
 		goto cleanup_newzone;
 
-	result = isc_ht_init(&new_zone->entries, catzs->mctx, 4);
-	if (result != ISC_R_SUCCESS)
-		goto cleanup_name;
+	isc_ht_init(&new_zone->entries, catzs->mctx, 4, ISC_HT_CASE_SENSITIVE);
 
 	new_zone->updatetimer = NULL;
 	result = isc_timer_create(catzs->timermgr, isc_timertype_inactive,
@@ -703,7 +685,6 @@ dns_catz_new_zone(dns_catz_zones_t *catz
 
   cleanup_ht:
 	isc_ht_destroy(&new_zone->entries);
-  cleanup_name:
 	dns_name_free(&new_zone->name, catzs->mctx);
   cleanup_newzone:
 	isc_mem_put(catzs->mctx, new_zone, sizeof(*new_zone));
@@ -805,8 +786,7 @@ dns_catz_zone_detach(dns_catz_zone_t **z
 	if (refs == 0) {
 		isc_mem_t *mctx = zone->catzs->mctx;
 		if (zone->entries != NULL) {
-			result = isc_ht_iter_create(zone->entries, &iter);
-			INSIST(result == ISC_R_SUCCESS);
+			isc_ht_iter_create(zone->entries, &iter);
 			for (result = isc_ht_iter_first(iter);
 			     result == ISC_R_SUCCESS;
 			     result = isc_ht_iter_delcurrent_next(iter))
@@ -865,8 +845,7 @@ dns_catz_catzs_detach(dns_catz_zones_t *
 		catzs->magic = 0;
 		DESTROYLOCK(&catzs->lock);
 		if (catzs->zones != NULL) {
-			result = isc_ht_iter_create(catzs->zones, &iter);
-			INSIST(result == ISC_R_SUCCESS);
+			isc_ht_iter_create(catzs->zones, &iter);
 			for (result = isc_ht_iter_first(iter);
 			     result == ISC_R_SUCCESS;)
 			{
@@ -1996,8 +1975,7 @@ dns_catz_prereconfig(dns_catz_zones_t *c
 
 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
 
-	result = isc_ht_iter_create(catzs->zones, &iter);
-	INSIST(result == ISC_R_SUCCESS);
+	isc_ht_iter_create(catzs->zones, &iter);
 	for (result = isc_ht_iter_first(iter);
 	     result == ISC_R_SUCCESS;
 	     result = isc_ht_iter_next(iter))
@@ -2019,8 +1997,7 @@ dns_catz_postreconfig(dns_catz_zones_t *
 	REQUIRE(DNS_CATZ_ZONES_VALID(catzs));
 
 	LOCK(&catzs->lock);
-	result = isc_ht_iter_create(catzs->zones, &iter);
-	INSIST(result == ISC_R_SUCCESS);
+	isc_ht_iter_create(catzs->zones, &iter);
 	for (result = isc_ht_iter_first(iter);
 	     result == ISC_R_SUCCESS;)
 	{
@@ -2062,5 +2039,6 @@ dns_catz_postreconfig(dns_catz_zones_t *
 isc_result_t
 dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp) {
 	REQUIRE(DNS_CATZ_ZONE_VALID(catz));
-	return (isc_ht_iter_create(catz->entries, itp));
+	isc_ht_iter_create(catz->entries, itp);
+	return (ISC_R_SUCCESS);
 }
Index: bind-9.11.22/lib/isc/ht.c
===================================================================
--- bind-9.11.22.orig/lib/isc/ht.c
+++ bind-9.11.22/lib/isc/ht.c
@@ -1,6 +1,8 @@
 /*
  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
  *
+ * SPDX-License-Identifier: MPL-2.0
+ *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
@@ -10,208 +12,483 @@
  */
 
 #include <config.h>
-
 #include <inttypes.h>
 #include <string.h>
 
 #include <isc/hash.h>
 #include <isc/ht.h>
-#include <isc/types.h>
 #include <isc/magic.h>
 #include <isc/mem.h>
 #include <isc/result.h>
+#include <isc/types.h>
 #include <isc/util.h>
 
-
 typedef struct isc_ht_node isc_ht_node_t;
 
-#define ISC_HT_MAGIC			ISC_MAGIC('H', 'T', 'a', 'b')
-#define ISC_HT_VALID(ht)		ISC_MAGIC_VALID(ht, ISC_HT_MAGIC)
+#define ISC_HT_MAGIC	 ISC_MAGIC('H', 'T', 'a', 'b')
+#define ISC_HT_VALID(ht) ISC_MAGIC_VALID(ht, ISC_HT_MAGIC)
+
+#define HT_NO_BITS    0
+#define HT_MIN_BITS   1
+#define HT_MAX_BITS   32
+#define HT_OVERCOMMIT 3
+
+#define HT_NEXTTABLE(idx)      ((idx == 0) ? 1 : 0)
+#define TRY_NEXTTABLE(idx, ht) (idx == ht->hindex && rehashing_in_progress(ht))
+
+#define GOLDEN_RATIO_32 0x61C88647
+
+#define HASHSIZE(bits) (UINT64_C(1) << (bits))
 
 struct isc_ht_node {
 	void *value;
 	isc_ht_node_t *next;
+	uint32_t hashval;
 	size_t keysize;
-	unsigned char key[FLEXIBLE_ARRAY_MEMBER];
+	unsigned char key[];
 };
 
 struct isc_ht {
 	unsigned int magic;
 	isc_mem_t *mctx;
-	size_t size;
-	size_t mask;
-	unsigned int count;
-	isc_ht_node_t **table;
+	size_t count;
+	bool case_sensitive;
+	size_t size[2];
+	uint8_t hashbits[2];
+	isc_ht_node_t **table[2];
+	uint8_t hindex;
+	uint32_t hiter; /* rehashing iterator */
 };
 
 struct isc_ht_iter {
 	isc_ht_t *ht;
 	size_t i;
+	uint8_t hindex;
 	isc_ht_node_t *cur;
 };
 
-isc_result_t
-isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits) {
-	isc_ht_t *ht = NULL;
-	size_t i;
+static isc_ht_node_t *
+isc__ht_find(const isc_ht_t *ht, const unsigned char *key,
+	     const uint32_t keysize, const uint32_t hashval, const uint8_t idx);
+static void
+isc__ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+	    const uint32_t hashval, const uint8_t idx, void *value);
+static isc_result_t
+isc__ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+	       const uint32_t hashval, const uint8_t idx);
+
+static uint32_t
+rehash_bits(isc_ht_t *ht, size_t newcount);
+
+static void
+hashtable_new(isc_ht_t *ht, const uint8_t idx, const uint8_t bits);
+static void
+hashtable_free(isc_ht_t *ht, const uint8_t idx);
+static void
+hashtable_rehash(isc_ht_t *ht, uint32_t newbits);
+static void
+hashtable_rehash_one(isc_ht_t *ht);
+static void
+maybe_rehash(isc_ht_t *ht, size_t newcount);
+
+static isc_result_t
+isc__ht_iter_next(isc_ht_iter_t *it);
+
+static uint8_t maptolower[] = {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+	0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+	0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+	0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+	0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73,
+	0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
+	0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+	0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83,
+	0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
+	0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+	0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3,
+	0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+	0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb,
+	0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+	0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3,
+	0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb,
+	0xfc, 0xfd, 0xfe, 0xff
+};
 
-	REQUIRE(htp != NULL && *htp == NULL);
-	REQUIRE(mctx != NULL);
-	REQUIRE(bits >= 1 && bits <= (sizeof(size_t)*8 - 1));
+static int
+memcasecmp(const void *vs1, const void *vs2, size_t len) {
+	uint8_t const *s1 = vs1;
+	uint8_t const *s2 = vs2;
+	for (size_t i = 0; i < len; i++) {
+		uint8_t u1 = s1[i];
+		uint8_t u2 = s2[i];
+		int U1 = maptolower[u1];
+		int U2 = maptolower[u2];
+		int diff = U1 - U2;
+		if (diff) {
+			return diff;
+		}
+	}
+	return 0;
+}
+
+static bool
+isc__ht_node_match(isc_ht_node_t *node, const uint32_t hashval,
+		   const uint8_t *key, uint32_t keysize, bool case_sensitive) {
+	return (node->hashval == hashval && node->keysize == keysize &&
+		(case_sensitive ? (memcmp(node->key, key, keysize) == 0)
+				: (memcasecmp(node->key, key, keysize) == 0)));
+}
 
-	ht = isc_mem_get(mctx, sizeof(struct isc_ht));
-	if (ht == NULL) {
-		return (ISC_R_NOMEMORY);
+static uint32_t
+hash_32(uint32_t val, unsigned int bits) {
+	REQUIRE(bits <= HT_MAX_BITS);
+	/* High bits are more random. */
+	return (val * GOLDEN_RATIO_32 >> (32 - bits));
+}
+
+static bool
+rehashing_in_progress(const isc_ht_t *ht) {
+	return (ht->table[HT_NEXTTABLE(ht->hindex)] != NULL);
+}
+
+static bool
+hashtable_is_overcommited(isc_ht_t *ht) {
+	return (ht->count >= (ht->size[ht->hindex] * HT_OVERCOMMIT));
+}
+
+static uint32_t
+rehash_bits(isc_ht_t *ht, size_t newcount) {
+	uint32_t newbits = ht->hashbits[ht->hindex];
+
+	while (newcount >= HASHSIZE(newbits) && newbits <= HT_MAX_BITS) {
+		newbits += 1;
 	}
 
-	ht->mctx = NULL;
-	isc_mem_attach(mctx, &ht->mctx);
+	return (newbits);
+}
+
+/*
+ * Rebuild the hashtable to reduce the load factor
+ */
+static void
+hashtable_rehash(isc_ht_t *ht, uint32_t newbits) {
+	uint8_t oldindex = ht->hindex;
+	uint32_t oldbits = ht->hashbits[oldindex];
+	uint8_t newindex = HT_NEXTTABLE(oldindex);
+
+	REQUIRE(ht->hashbits[oldindex] >= HT_MIN_BITS);
+	REQUIRE(ht->hashbits[oldindex] <= HT_MAX_BITS);
+	REQUIRE(ht->table[oldindex] != NULL);
+
+	REQUIRE(newbits <= HT_MAX_BITS);
+	REQUIRE(ht->hashbits[newindex] == HT_NO_BITS);
+	REQUIRE(ht->table[newindex] == NULL);
+
+	REQUIRE(newbits > oldbits);
+
+	hashtable_new(ht, newindex, newbits);
+
+	ht->hindex = newindex;
+
+	hashtable_rehash_one(ht);
+}
+
+static void
+hashtable_rehash_one(isc_ht_t *ht) {
+	isc_ht_node_t **newtable = ht->table[ht->hindex];
+	uint32_t oldsize = ht->size[HT_NEXTTABLE(ht->hindex)];
+	isc_ht_node_t **oldtable = ht->table[HT_NEXTTABLE(ht->hindex)];
+	isc_ht_node_t *node = NULL;
+	isc_ht_node_t *nextnode;
 
-	ht->size = ((size_t)1<<bits);
-	ht->mask = ((size_t)1<<bits)-1;
-	ht->count = 0;
-
-	ht->table = isc_mem_get(ht->mctx, ht->size * sizeof(isc_ht_node_t*));
-	if (ht->table == NULL) {
-		isc_mem_putanddetach(&ht->mctx, ht, sizeof(struct isc_ht));
-		return (ISC_R_NOMEMORY);
+	/* Find first non-empty node */
+	while (ht->hiter < oldsize && oldtable[ht->hiter] == NULL) {
+		ht->hiter++;
 	}
 
-	for (i = 0; i < ht->size; i++) {
-		ht->table[i] = NULL;
+	/* Rehashing complete */
+	if (ht->hiter == oldsize) {
+		hashtable_free(ht, HT_NEXTTABLE(ht->hindex));
+		ht->hiter = 0;
+		return;
 	}
 
+	/* Move the first non-empty node from old hashtable to new hashtable */
+	for (node = oldtable[ht->hiter]; node != NULL; node = nextnode) {
+		uint32_t hash = hash_32(node->hashval,
+					ht->hashbits[ht->hindex]);
+		nextnode = node->next;
+		node->next = newtable[hash];
+		newtable[hash] = node;
+	}
+
+	oldtable[ht->hiter] = NULL;
+
+	ht->hiter++;
+}
+
+static void
+maybe_rehash(isc_ht_t *ht, size_t newcount) {
+	uint32_t newbits = rehash_bits(ht, newcount);
+
+	if (ht->hashbits[ht->hindex] < newbits && newbits <= HT_MAX_BITS) {
+		hashtable_rehash(ht, newbits);
+	}
+}
+
+static void
+hashtable_new(isc_ht_t *ht, const uint8_t idx, const uint8_t bits) {
+	size_t size;
+	REQUIRE(ht->hashbits[idx] == HT_NO_BITS);
+	REQUIRE(ht->table[idx] == NULL);
+	REQUIRE(bits >= HT_MIN_BITS);
+	REQUIRE(bits <= HT_MAX_BITS);
+
+	ht->hashbits[idx] = bits;
+	ht->size[idx] = HASHSIZE(ht->hashbits[idx]);
+
+	size = ht->size[idx] * sizeof(isc_ht_node_t *);
+
+	ht->table[idx] = isc_mem_get(ht->mctx, size);
+	INSIST(ht->table[idx] != NULL);
+	memset(ht->table[idx], 0, size);
+}
+
+static void
+hashtable_free(isc_ht_t *ht, const uint8_t idx) {
+	size_t size = ht->size[idx] * sizeof(isc_ht_node_t *);
+
+	for (size_t i = 0; i < ht->size[idx]; i++) {
+		isc_ht_node_t *node = ht->table[idx][i];
+		while (node != NULL) {
+			isc_ht_node_t *next = node->next;
+			ht->count--;
+			isc_mem_put(ht->mctx, node,
+				    sizeof(*node) + node->keysize);
+			node = next;
+		}
+	}
+
+	isc_mem_put(ht->mctx, ht->table[idx], size);
+	ht->hashbits[idx] = HT_NO_BITS;
+	ht->table[idx] = NULL;
+}
+
+void
+isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits,
+	    unsigned int options) {
+	isc_ht_t *ht = NULL;
+	bool case_sensitive = ((options & ISC_HT_CASE_INSENSITIVE) == 0);
+
+	REQUIRE(htp != NULL && *htp == NULL);
+	REQUIRE(mctx != NULL);
+	REQUIRE(bits >= 1 && bits <= HT_MAX_BITS);
+
+	ht = isc_mem_get(mctx, sizeof(*ht));
+	INSIST(ht != NULL);
+	*ht = (isc_ht_t){
+		.case_sensitive = case_sensitive,
+	};
+
+	isc_mem_attach(mctx, &ht->mctx);
+
+	hashtable_new(ht, 0, bits);
+
 	ht->magic = ISC_HT_MAGIC;
 
 	*htp = ht;
-	return (ISC_R_SUCCESS);
 }
 
 void
 isc_ht_destroy(isc_ht_t **htp) {
 	isc_ht_t *ht;
-	size_t i;
 
 	REQUIRE(htp != NULL);
-	REQUIRE(ISC_HT_VALID((*htp)));
+	REQUIRE(ISC_HT_VALID(*htp));
 
 	ht = *htp;
+	*htp = NULL;
 	ht->magic = 0;
 
-	for (i = 0; i < ht->size; i++) {
-		isc_ht_node_t *node = ht->table[i];
-		while (node != NULL) {
-			isc_ht_node_t *next = node->next;
-			ht->count--;
-			isc_mem_put(ht->mctx, node,
-				    offsetof(isc_ht_node_t, key) +
-				    node->keysize);
-			node = next;
+	for (size_t i = 0; i <= 1; i++) {
+		if (ht->table[i] != NULL) {
+			hashtable_free(ht, i);
 		}
 	}
 
 	INSIST(ht->count == 0);
 
-	isc_mem_put(ht->mctx, ht->table, ht->size * sizeof(isc_ht_node_t*));
-	isc_mem_putanddetach(&ht->mctx, ht, sizeof(struct isc_ht));
-
-	*htp = NULL;
+	isc_mem_putanddetach(&ht->mctx, ht, sizeof(*ht));
 }
 
-isc_result_t
-isc_ht_add(isc_ht_t *ht, const unsigned char *key,
-	   uint32_t keysize, void *value)
-{
+static void
+isc__ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+	    const uint32_t hashval, const uint8_t idx, void *value) {
 	isc_ht_node_t *node;
 	uint32_t hash;
 
+	hash = hash_32(hashval, ht->hashbits[idx]);
+
+	node = isc_mem_get(ht->mctx, sizeof(*node) + keysize);
+	INSIST(node != NULL);
+	*node = (isc_ht_node_t){
+		.keysize = keysize,
+		.hashval = hashval,
+		.next = ht->table[idx][hash],
+		.value = value,
+	};
+
+	memmove(node->key, key, keysize);
+
+	ht->count++;
+	ht->table[idx][hash] = node;
+}
+
+isc_result_t
+isc_ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+	   void *value) {
+	uint32_t hashval;
+
 	REQUIRE(ISC_HT_VALID(ht));
 	REQUIRE(key != NULL && keysize > 0);
 
-	hash = isc_hash_function(key, keysize, true, NULL);
-	node = ht->table[hash & ht->mask];
-	while (node != NULL) {
-		if (keysize == node->keysize &&
-		    memcmp(key, node->key, keysize) == 0) {
-			return (ISC_R_EXISTS);
-		}
-		node = node->next;
+	if (rehashing_in_progress(ht)) {
+		/* Rehash in progress */
+		hashtable_rehash_one(ht);
+	} else if (hashtable_is_overcommited(ht)) {
+		/* Rehash requested */
+		maybe_rehash(ht, ht->count);
 	}
 
-	node = isc_mem_get(ht->mctx, offsetof(isc_ht_node_t, key) + keysize);
-	if (node == NULL)
-		return (ISC_R_NOMEMORY);
+	hashval = isc_hash_function(key, keysize, ht->case_sensitive, NULL);
 
-	memmove(node->key, key, keysize);
-	node->keysize = keysize;
-	node->next = ht->table[hash & ht->mask];
-	node->value = value;
+	if (isc__ht_find(ht, key, keysize, hashval, ht->hindex) != NULL) {
+		return (ISC_R_EXISTS);
+	}
+
+	isc__ht_add(ht, key, keysize, hashval, ht->hindex, value);
 
-	ht->count++;
-	ht->table[hash & ht->mask] = node;
 	return (ISC_R_SUCCESS);
 }
 
+static isc_ht_node_t *
+isc__ht_find(const isc_ht_t *ht, const unsigned char *key,
+	     const uint32_t keysize, const uint32_t hashval,
+	     const uint8_t idx) {
+	uint32_t hash;
+	uint8_t findex = idx;
+
+nexttable:
+	hash = hash_32(hashval, ht->hashbits[findex]);
+	for (isc_ht_node_t *node = ht->table[findex][hash]; node != NULL;
+	     node = node->next)
+	{
+		if (isc__ht_node_match(node, hashval, key, keysize,
+				       ht->case_sensitive))
+		{
+			return (node);
+		}
+	}
+	if (TRY_NEXTTABLE(findex, ht)) {
+		/*
+		 * Rehashing in progress, check the other table
+		 */
+		findex = HT_NEXTTABLE(findex);
+		goto nexttable;
+	}
+
+	return (NULL);
+}
+
 isc_result_t
 isc_ht_find(const isc_ht_t *ht, const unsigned char *key,
-	    uint32_t keysize, void **valuep)
-{
+	    const uint32_t keysize, void **valuep) {
+	uint32_t hashval;
 	isc_ht_node_t *node;
-	uint32_t hash;
 
 	REQUIRE(ISC_HT_VALID(ht));
 	REQUIRE(key != NULL && keysize > 0);
 	REQUIRE(valuep == NULL || *valuep == NULL);
 
-	hash = isc_hash_function(key, keysize, true, NULL);
-	node = ht->table[hash & ht->mask];
-	while (node != NULL) {
-		if (keysize == node->keysize &&
-		    memcmp(key, node->key, keysize) == 0) {
-			if (valuep != NULL) {
-				*valuep = node->value;
-			}
-			return (ISC_R_SUCCESS);
-		}
-		node = node->next;
+	hashval = isc_hash_function(key, keysize, ht->case_sensitive, NULL);
+
+	node = isc__ht_find(ht, key, keysize, hashval, ht->hindex);
+	if (node == NULL) {
+		return (ISC_R_NOTFOUND);
 	}
 
-	return (ISC_R_NOTFOUND);
+	if (valuep != NULL) {
+		*valuep = node->value;
+	}
+	return (ISC_R_SUCCESS);
 }
 
-isc_result_t
-isc_ht_delete(isc_ht_t *ht, const unsigned char *key, uint32_t keysize) {
-	isc_ht_node_t *node, *prev;
+static isc_result_t
+isc__ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+	       const uint32_t hashval, const uint8_t idx) {
+	isc_ht_node_t *prev = NULL;
 	uint32_t hash;
 
-	REQUIRE(ISC_HT_VALID(ht));
-	REQUIRE(key != NULL && keysize > 0);
+	hash = hash_32(hashval, ht->hashbits[idx]);
 
-	prev = NULL;
-	hash = isc_hash_function(key, keysize, true, NULL);
-	node = ht->table[hash & ht->mask];
-	while (node != NULL) {
-		if (keysize == node->keysize &&
-		    memcmp(key, node->key, keysize) == 0) {
-			if (prev == NULL)
-				ht->table[hash & ht->mask] = node->next;
-			else
+	for (isc_ht_node_t *node = ht->table[idx][hash]; node != NULL;
+	     prev = node, node = node->next)
+	{
+		if (isc__ht_node_match(node, hashval, key, keysize,
+				       ht->case_sensitive))
+		{
+			if (prev == NULL) {
+				ht->table[idx][hash] = node->next;
+			} else {
 				prev->next = node->next;
+			}
 			isc_mem_put(ht->mctx, node,
-				    offsetof(isc_ht_node_t, key) +
-				    node->keysize);
+				    sizeof(*node) + node->keysize);
 			ht->count--;
 
 			return (ISC_R_SUCCESS);
 		}
-
-		prev = node;
-		node = node->next;
 	}
+
 	return (ISC_R_NOTFOUND);
 }
 
 isc_result_t
+isc_ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize) {
+	uint32_t hashval;
+	uint8_t hindex;
+	isc_result_t result;
+
+	REQUIRE(ISC_HT_VALID(ht));
+	REQUIRE(key != NULL && keysize > 0);
+
+	if (rehashing_in_progress(ht)) {
+		/* Rehash in progress */
+		hashtable_rehash_one(ht);
+	}
+
+	hindex = ht->hindex;
+	hashval = isc_hash_function(key, keysize, ht->case_sensitive, NULL);
+nexttable:
+	result = isc__ht_delete(ht, key, keysize, hashval, hindex);
+
+	if (result == ISC_R_NOTFOUND && TRY_NEXTTABLE(hindex, ht)) {
+		/*
+		 * Rehashing in progress, check the other table
+		 */
+		hindex = HT_NEXTTABLE(hindex);
+		goto nexttable;
+	}
+
+	return (result);
+}
+
+void
 isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp) {
 	isc_ht_iter_t *it;
 
@@ -219,16 +496,13 @@ isc_ht_iter_create(isc_ht_t *ht, isc_ht_
 	REQUIRE(itp != NULL && *itp == NULL);
 
 	it = isc_mem_get(ht->mctx, sizeof(isc_ht_iter_t));
-	if (it == NULL)
-		return (ISC_R_NOMEMORY);
-
-	it->ht = ht;
-	it->i = 0;
-	it->cur = NULL;
+	INSIST(it != NULL);
+	*it = (isc_ht_iter_t){
+		.ht = ht,
+		.hindex = ht->hindex,
+	};
 
 	*itp = it;
-
-	return (ISC_R_SUCCESS);
 }
 
 void
@@ -239,26 +513,48 @@ isc_ht_iter_destroy(isc_ht_iter_t **itp)
 	REQUIRE(itp != NULL && *itp != NULL);
 
 	it = *itp;
-	ht = it->ht;
-	isc_mem_put(ht->mctx, it, sizeof(isc_ht_iter_t));
-
 	*itp = NULL;
+	ht = it->ht;
+	isc_mem_put(ht->mctx, it, sizeof(*it));
 }
 
 isc_result_t
 isc_ht_iter_first(isc_ht_iter_t *it) {
+	isc_ht_t *ht;
+
 	REQUIRE(it != NULL);
 
+	ht = it->ht;
+
+	it->hindex = ht->hindex;
 	it->i = 0;
-	while (it->i < it->ht->size && it->ht->table[it->i] == NULL)
+
+	return (isc__ht_iter_next(it));
+}
+
+static isc_result_t
+isc__ht_iter_next(isc_ht_iter_t *it) {
+	isc_ht_t *ht = it->ht;
+
+	while (it->i < ht->size[it->hindex] &&
+	       ht->table[it->hindex][it->i] == NULL)
+	{
 		it->i++;
+	}
 
-	if (it->i == it->ht->size)
-		return (ISC_R_NOMORE);
+	if (it->i < ht->size[it->hindex]) {
+		it->cur = ht->table[it->hindex][it->i];
 
-	it->cur = it->ht->table[it->i];
+		return (ISC_R_SUCCESS);
+	}
 
-	return (ISC_R_SUCCESS);
+	if (TRY_NEXTTABLE(it->hindex, ht)) {
+		it->hindex = HT_NEXTTABLE(it->hindex);
+		it->i = 0;
+		return (isc__ht_iter_next(it));
+	}
+
+	return (ISC_R_NOMORE);
 }
 
 isc_result_t
@@ -267,58 +563,36 @@ isc_ht_iter_next(isc_ht_iter_t *it) {
 	REQUIRE(it->cur != NULL);
 
 	it->cur = it->cur->next;
-	if (it->cur == NULL) {
-		do {
-			it->i++;
-		} while (it->i < it->ht->size && it->ht->table[it->i] == NULL);
-		if (it->i >= it->ht->size)
-			return (ISC_R_NOMORE);
-		it->cur = it->ht->table[it->i];
+
+	if (it->cur != NULL) {
+		return (ISC_R_SUCCESS);
 	}
 
-	return (ISC_R_SUCCESS);
+	it->i++;
+
+	return (isc__ht_iter_next(it));
 }
 
 isc_result_t
 isc_ht_iter_delcurrent_next(isc_ht_iter_t *it) {
 	isc_result_t result = ISC_R_SUCCESS;
-	isc_ht_node_t *to_delete = NULL;
-	isc_ht_node_t *prev = NULL;
-	isc_ht_node_t *node = NULL;
-	uint32_t hash;
+	isc_ht_node_t *dnode = NULL;
+	uint8_t dindex;
 	isc_ht_t *ht;
+	isc_result_t dresult;
+
 	REQUIRE(it != NULL);
 	REQUIRE(it->cur != NULL);
-	to_delete = it->cur;
+
 	ht = it->ht;
+	dnode = it->cur;
+	dindex = it->hindex;
 
-	it->cur = it->cur->next;
-	if (it->cur == NULL) {
-		do {
-			it->i++;
-		} while (it->i < ht->size && ht->table[it->i] == NULL);
-		if (it->i >= ht->size)
-			result = ISC_R_NOMORE;
-		else
-			it->cur = ht->table[it->i];
-	}
-
-	hash = isc_hash_function(to_delete->key, to_delete->keysize, true,
-				 NULL);
-	node = ht->table[hash & ht->mask];
-	while (node != to_delete) {
-		prev = node;
-		node = node->next;
-		INSIST(node != NULL);
-	}
-
-	if (prev == NULL)
-		ht->table[hash & ht->mask] = node->next;
-	else
-		prev->next = node->next;
-	isc_mem_put(ht->mctx, node,
-		    offsetof(isc_ht_node_t, key) + node->keysize);
-	ht->count--;
+	result = isc_ht_iter_next(it);
+
+	dresult = isc__ht_delete(ht, dnode->key, dnode->keysize, dnode->hashval,
+				 dindex);
+	INSIST(dresult == ISC_R_SUCCESS);
 
 	return (result);
 }
@@ -333,8 +607,8 @@ isc_ht_iter_current(isc_ht_iter_t *it, v
 }
 
 void
-isc_ht_iter_currentkey(isc_ht_iter_t *it, unsigned char **key, size_t *keysize)
-{
+isc_ht_iter_currentkey(isc_ht_iter_t *it, unsigned char **key,
+		       size_t *keysize) {
 	REQUIRE(it != NULL);
 	REQUIRE(it->cur != NULL);
 	REQUIRE(key != NULL && *key == NULL);
@@ -343,9 +617,9 @@ isc_ht_iter_currentkey(isc_ht_iter_t *it
 	*keysize = it->cur->keysize;
 }
 
-unsigned int
-isc_ht_count(isc_ht_t *ht) {
+size_t
+isc_ht_count(const isc_ht_t *ht) {
 	REQUIRE(ISC_HT_VALID(ht));
 
-	return(ht->count);
+	return (ht->count);
 }
Index: bind-9.11.22/lib/isc/include/isc/ht.h
===================================================================
--- bind-9.11.22.orig/lib/isc/include/isc/ht.h
+++ bind-9.11.22/lib/isc/include/isc/ht.h
@@ -1,6 +1,8 @@
 /*
  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
  *
+ * SPDX-License-Identifier: MPL-2.0
+ *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
@@ -11,32 +13,35 @@
 
 /* ! \file */
 
-#ifndef ISC_HT_H
-#define ISC_HT_H 1
+#pragma once
 
 #include <inttypes.h>
 #include <string.h>
 
-#include <isc/types.h>
 #include <isc/result.h>
+#include <isc/types.h>
 
-typedef struct isc_ht isc_ht_t;
+typedef struct isc_ht	   isc_ht_t;
 typedef struct isc_ht_iter isc_ht_iter_t;
 
+enum { ISC_HT_CASE_SENSITIVE = 0x00, ISC_HT_CASE_INSENSITIVE = 0x01 };
+
 /*%
  * Initialize hashtable at *htp, using memory context and size of (1<<bits)
  *
+ * If 'options' contains ISC_HT_CASE_INSENSITIVE, then upper- and lower-case
+ * letters in key values will generate the same hash values; this can be used
+ * when the key for a hash table is a DNS name.
+ *
  * Requires:
  *\li	'htp' is not NULL and '*htp' is NULL.
  *\li	'mctx' is a valid memory context.
  *\li	'bits' >=1 and 'bits' <=32
  *
- * Returns:
- *\li	#ISC_R_NOMEMORY		-- not enough memory to create pool
- *\li	#ISC_R_SUCCESS		-- all is well.
  */
-isc_result_t
-isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits);
+void
+isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits,
+	    unsigned int options);
 
 /*%
  * Destroy hashtable, freeing everything
@@ -53,6 +58,7 @@ isc_ht_destroy(isc_ht_t **htp);
  *
  * Requires:
  *\li	'ht' is a valid hashtable
+ *\li   write-lock
  *
  * Returns:
  *\li	#ISC_R_NOMEMORY		-- not enough memory to create pool
@@ -60,15 +66,18 @@ isc_ht_destroy(isc_ht_t **htp);
  *\li	#ISC_R_SUCCESS		-- all is well.
  */
 isc_result_t
-isc_ht_add(isc_ht_t *ht, const unsigned char *key, uint32_t keysize,
-		   void *value);
+isc_ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+	   void *value);
 
 /*%
  * Find a node matching 'key'/'keysize' in hashtable 'ht';
- * if found, set 'value' to its value
+ * if found, set '*valuep' to its value. (If 'valuep' is NULL,
+ * then simply return SUCCESS or NOTFOUND to indicate whether the
+ * key exists in the hashtable.)
  *
  * Requires:
  * \li	'ht' is a valid hashtable
+ * \li  read-lock
  *
  * Returns:
  * \li	#ISC_R_SUCCESS		-- success
@@ -76,20 +85,21 @@ isc_ht_add(isc_ht_t *ht, const unsigned
  */
 isc_result_t
 isc_ht_find(const isc_ht_t *ht, const unsigned char *key,
-	    uint32_t keysize, void **valuep);
+	    const uint32_t keysize, void **valuep);
 
 /*%
  * Delete node from hashtable
  *
  * Requires:
  *\li	ht is a valid hashtable
+ *\li   write-lock
  *
  * Returns:
  *\li	#ISC_R_NOTFOUND		-- key not found
  *\li	#ISC_R_SUCCESS		-- all is well
  */
 isc_result_t
-isc_ht_delete(isc_ht_t *ht, const unsigned char *key, uint32_t keysize);
+isc_ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize);
 
 /*%
  * Create an iterator for the hashtable; point '*itp' to it.
@@ -98,7 +108,7 @@ isc_ht_delete(isc_ht_t *ht, const unsign
  *\li	'ht' is a valid hashtable
  *\li	'itp' is non NULL and '*itp' is NULL.
  */
-isc_result_t
+void
 isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp);
 
 /*%
@@ -117,7 +127,7 @@ isc_ht_iter_destroy(isc_ht_iter_t **itp)
  *\li	'it' is non NULL.
  *
  * Returns:
- * \li 	#ISC_R_SUCCESS	-- success
+ * \li	#ISC_R_SUCCESS	-- success
  * \li	#ISC_R_NOMORE	-- no data in the hashtable
  */
 isc_result_t
@@ -130,7 +140,7 @@ isc_ht_iter_first(isc_ht_iter_t *it);
  *\li	'it' is non NULL.
  *
  * Returns:
- * \li 	#ISC_R_SUCCESS	-- success
+ * \li	#ISC_R_SUCCESS	-- success
  * \li	#ISC_R_NOMORE	-- end of hashtable reached
  */
 isc_result_t
@@ -143,13 +153,12 @@ isc_ht_iter_next(isc_ht_iter_t *it);
  *\li	'it' is non NULL.
  *
  * Returns:
- * \li 	#ISC_R_SUCCESS	-- success
+ * \li	#ISC_R_SUCCESS	-- success
  * \li	#ISC_R_NOMORE	-- end of hashtable reached
  */
 isc_result_t
 isc_ht_iter_delcurrent_next(isc_ht_iter_t *it);
 
-
 /*%
  * Set 'value' to the current value under the iterator
  *
@@ -178,6 +187,5 @@ isc_ht_iter_currentkey(isc_ht_iter_t *it
  * Requires:
  *\li	'ht' is a valid hashtable
  */
-unsigned int
-isc_ht_count(isc_ht_t *ht);
-#endif
+size_t
+isc_ht_count(const isc_ht_t *ht);
Index: bind-9.11.22/lib/isc/tests/ht_test.c
===================================================================
--- bind-9.11.22.orig/lib/isc/tests/ht_test.c
+++ bind-9.11.22/lib/isc/tests/ht_test.c
@@ -60,8 +60,7 @@ test_ht_full(int bits, uintptr_t count)
 				  NULL, &mctx, 0);
 	assert_int_equal(result, ISC_R_SUCCESS);
 
-	result = isc_ht_init(&ht, mctx, bits);
-	assert_int_equal(result, ISC_R_SUCCESS);
+	isc_ht_init(&ht, mctx, bits, ISC_HT_CASE_SENSITIVE);
 	assert_non_null(ht);
 
 	for (i = 1; i < count; i++) {
@@ -214,8 +213,7 @@ test_ht_iterator() {
 				  NULL, &mctx, 0);
 	assert_int_equal(result, ISC_R_SUCCESS);
 
-	result = isc_ht_init(&ht, mctx, 16);
-	assert_int_equal(result, ISC_R_SUCCESS);
+	isc_ht_init(&ht, mctx, 16, ISC_HT_CASE_SENSITIVE);
 	assert_non_null(ht);
 	for (i = 1; i <= count; i++) {
 		/*
@@ -229,8 +227,7 @@ test_ht_iterator() {
 	}
 
 	walked = 0;
-	result = isc_ht_iter_create(ht, &iter);
-	assert_int_equal(result, ISC_R_SUCCESS);
+	isc_ht_iter_create(ht, &iter);
 
 	for (result = isc_ht_iter_first(iter);
 	     result == ISC_R_SUCCESS;
@@ -348,9 +345,69 @@ isc_ht_iterator_test(void **state) {
 	test_ht_iterator();
 }
 
+static void
+isc_ht_case(void **state) {
+	UNUSED(state);
+
+	isc_ht_t *ht = NULL;
+	void *f = NULL;
+	isc_result_t result = ISC_R_UNSET;
+	isc_mem_t *mctx = NULL;
+
+	result = isc_mem_createx2(0, 0, default_memalloc, default_memfree,
+				  NULL, &mctx, 0);
+	assert_int_equal(result, ISC_R_SUCCESS);
+
+	unsigned char lower[16] = { "test case" };
+	unsigned char same[16] = { "test case" };
+	unsigned char upper[16] = { "TEST CASE" };
+	unsigned char mixed[16] = { "tEsT CaSe" };
+
+	isc_ht_init(&ht, mctx, 8, ISC_HT_CASE_SENSITIVE);
+	assert_non_null(ht);
+
+	result = isc_ht_add(ht, lower, 16, (void *)lower);
+	assert_int_equal(result, ISC_R_SUCCESS);
+
+	result = isc_ht_add(ht, same, 16, (void *)same);
+	assert_int_equal(result, ISC_R_EXISTS);
+
+	result = isc_ht_add(ht, upper, 16, (void *)upper);
+	assert_int_equal(result, ISC_R_SUCCESS);
+
+	result = isc_ht_find(ht, mixed, 16, &f);
+	assert_int_equal(result, ISC_R_NOTFOUND);
+	assert_null(f);
+
+	isc_ht_destroy(&ht);
+	assert_null(ht);
+
+	isc_ht_init(&ht, mctx, 8, ISC_HT_CASE_INSENSITIVE);
+	assert_non_null(ht);
+
+	result = isc_ht_add(ht, lower, 16, (void *)lower);
+	assert_int_equal(result, ISC_R_SUCCESS);
+
+	result = isc_ht_add(ht, same, 16, (void *)same);
+	assert_int_equal(result, ISC_R_EXISTS);
+
+	result = isc_ht_add(ht, upper, 16, (void *)upper);
+	assert_int_equal(result, ISC_R_EXISTS);
+
+	result = isc_ht_find(ht, mixed, 16, &f);
+	assert_int_equal(result, ISC_R_SUCCESS);
+	assert_ptr_equal(f, &lower);
+
+	isc_ht_destroy(&ht);
+	assert_null(ht);
+
+	isc_mem_detach(&mctx);
+}
+
 int
 main(void) {
 	const struct CMUnitTest tests[] = {
+		cmocka_unit_test(isc_ht_case),
 		cmocka_unit_test(isc_ht_20),
 		cmocka_unit_test(isc_ht_8),
 		cmocka_unit_test(isc_ht_1),
Index: bind-9.11.22/CHANGES
===================================================================
--- bind-9.11.22.orig/CHANGES
+++ bind-9.11.22/CHANGES
@@ -1,3 +1,6 @@
+6315.	[security]	Speed up parsing of DNS messages with many different
+			names. (CVE-2023-4408) [GL #4234]
+
 	--- 9.11.22 released ---
 
 5481.	[security]	"update-policy" rules of type "subdomain" were
Index: bind-9.11.22/lib/dns/win32/libdns.def.in
===================================================================
--- bind-9.11.22.orig/lib/dns/win32/libdns.def.in
+++ bind-9.11.22/lib/dns/win32/libdns.def.in
@@ -519,7 +519,6 @@ dns_message_checksig
 dns_message_create
 dns_message_currentname
 dns_message_destroy
-dns_message_find
 dns_message_findname
 dns_message_findtype
 dns_message_firstname
@@ -540,7 +539,6 @@ dns_message_logfmtpacket
 dns_message_logfmtpacket2
 dns_message_logpacket
 dns_message_logpacket2
-dns_message_movename
 dns_message_nextname
 dns_message_parse
 dns_message_peekheader
Index: bind-9.11.22/bin/tests/system/dnssec/ns3/secure.example.db.in
===================================================================
--- bind-9.11.22.orig/bin/tests/system/dnssec/ns3/secure.example.db.in
+++ bind-9.11.22/bin/tests/system/dnssec/ns3/secure.example.db.in
@@ -44,3 +44,8 @@ rrsigonly		A	10.0.0.29
 cnameandkey		CNAME	@
 cnamenokey		CNAME	@
 dnameandkey		DNAME	@
+
+mixedcase		A	10.0.0.30
+mixedCASE		TXT	"mixed case"
+MIXEDcase		AAAA	2002::
+mIxEdCaSe		LOC	37 52 56.788 N 121 54 55.02 W 1120m 10m 100m 10m
Index: bind-9.11.22/bin/tests/system/dnssec/ns3/sign.sh
===================================================================
--- bind-9.11.22.orig/bin/tests/system/dnssec/ns3/sign.sh
+++ bind-9.11.22/bin/tests/system/dnssec/ns3/sign.sh
@@ -15,39 +15,38 @@ SYSTEMTESTTOP=../..
 echo_i "ns3/sign.sh"
 
 infile=key.db.in
-for tld in managed trusted
-do
+for tld in managed trusted; do
 	# A secure zone to test.
 	zone=secure.${tld}
 	zonefile=${zone}.db
 
-	keyname1=`$KEYGEN -f KSK -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
-	cat $infile $keyname1.key > $zonefile
-	$SIGNER -z -P -3 - -o $zone -r $RANDFILE -O full -f ${zonefile}.signed $zonefile > /dev/null 2>&1
-	DSFILE=dsset-`echo ${zone} |sed -e "s/\.$//g"`$TP
-	$DSFROMKEY -A -f ${zonefile}.signed $zone > $DSFILE
+	keyname1=$($KEYGEN -f KSK -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
+	cat $infile $keyname1.key >$zonefile
+	$SIGNER -z -P -3 - -o $zone -r $RANDFILE -O full -f ${zonefile}.signed $zonefile >/dev/null 2>&1
+	DSFILE=dsset-$(echo ${zone} | sed -e "s/\.$//g")$TP
+	$DSFROMKEY -A -f ${zonefile}.signed $zone >$DSFILE
 
 	# Zone to test trust anchor with unsupported algorithm.
 	zone=unsupported.${tld}
 	zonefile=${zone}.db
 
-	keyname2=`$KEYGEN -f KSK -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
-	cat $infile $keyname2.key > $zonefile
-	$SIGNER -z -P -3 - -o $zone -r $RANDFILE -O full -f ${zonefile}.tmp $zonefile > /dev/null 2>&1
-	awk '$4 == "DNSKEY" { $7 = 255 } $4 == "RRSIG" { $6 = 255 } { print }' ${zonefile}.tmp > ${zonefile}.signed
-	DSFILE=dsset-`echo ${zone} |sed -e "s/\.$//g"`$TP
-	$DSFROMKEY -A -f ${zonefile}.signed $zone > $DSFILE
+	keyname2=$($KEYGEN -f KSK -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
+	cat $infile $keyname2.key >$zonefile
+	$SIGNER -z -P -3 - -o $zone -r $RANDFILE -O full -f ${zonefile}.tmp $zonefile >/dev/null 2>&1
+	awk '$4 == "DNSKEY" { $7 = 255 } $4 == "RRSIG" { $6 = 255 } { print }' ${zonefile}.tmp >${zonefile}.signed
+	DSFILE=dsset-$(echo ${zone} | sed -e "s/\.$//g")$TP
+	$DSFROMKEY -A -f ${zonefile}.signed $zone >$DSFILE
 
 	# Make trusted-keys and managed keys conf sections for ns8.
 	mv ${keyname2}.key ${keyname2}.tmp
-	awk '$1 == "unsupported.'"${tld}"'." { $6 = 255 } { print }' ${keyname2}.tmp > ${keyname2}.key
+	awk '$1 == "unsupported.'"${tld}"'." { $6 = 255 } { print }' ${keyname2}.tmp >${keyname2}.key
 
 	case $tld in
 	"managed")
-		keyfile_to_managed_keys $keyname1 $keyname2 > ../ns8/managed.conf
+		keyfile_to_managed_keys $keyname1 $keyname2 >../ns8/managed.conf
 		;;
 	"trusted")
-		keyfile_to_trusted_keys $keyname1 $keyname2 > ../ns8/trusted.conf
+		keyfile_to_trusted_keys $keyname1 $keyname2 >../ns8/trusted.conf
 		;;
 	esac
 done
@@ -58,49 +57,51 @@ zone=secure.example.
 infile=secure.example.db.in
 zonefile=secure.example.db
 
-cnameandkey=`$KEYGEN -T KEY -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n host cnameandkey.$zone`
-dnameandkey=`$KEYGEN -T KEY -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n host dnameandkey.$zone`
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+cnameandkey=$($KEYGEN -T KEY -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n host cnameandkey.$zone)
+dnameandkey=$($KEYGEN -T KEY -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n host dnameandkey.$zone)
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $cnameandkey.key $dnameandkey.key $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -D -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
+cat "$zonefile" "$zonefile".signed >"$zonefile".tmp
+mv "$zonefile".tmp "$zonefile".signed
 
 zone=bogus.example.
 infile=bogus.example.db.in
 zonefile=bogus.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 zone=dynamic.example.
 infile=dynamic.example.db.in
 zonefile=dynamic.example.db
 
-keyname1=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
-keyname2=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b 1024 -n zone -f KSK $zone`
+keyname1=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
+keyname2=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b 1024 -n zone -f KSK $zone)
 
 cat $infile $keyname1.key $keyname2.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 zone=keyless.example.
 infile=generic.example.db.in
 zonefile=keyless.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 # Change the signer field of the a.b.keyless.example SIG A
 # to point to a provably nonexistent KEY record.
 mv $zonefile.signed $zonefile.tmp
-<$zonefile.tmp $PERL -p -e 's/ keyless.example/ b.keyless.example/
+$PERL <$zonefile.tmp -p -e 's/ keyless.example/ b.keyless.example/
     if /^a.b.keyless.example/../NXT/;' >$zonefile.signed
 rm -f $zonefile.tmp
 
@@ -111,11 +112,11 @@ zone=secure.nsec3.example.
 infile=secure.nsec3.example.db.in
 zonefile=secure.nsec3.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 #  NSEC3/NSEC3 test zone
@@ -124,11 +125,11 @@ zone=nsec3.nsec3.example.
 infile=nsec3.nsec3.example.db.in
 zonefile=nsec3.nsec3.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -3 - -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 #  OPTOUT/NSEC3 test zone
@@ -137,11 +138,11 @@ zone=optout.nsec3.example.
 infile=optout.nsec3.example.db.in
 zonefile=optout.nsec3.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -3 - -A -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -A -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A nsec3 zone (non-optout).
@@ -150,11 +151,11 @@ zone=nsec3.example.
 infile=nsec3.example.db.in
 zonefile=nsec3.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -g -3 - -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -g -3 - -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 #  OPTOUT/NSEC test zone
@@ -163,11 +164,11 @@ zone=secure.optout.example.
 infile=secure.optout.example.db.in
 zonefile=secure.optout.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 #  OPTOUT/NSEC3 test zone
@@ -176,11 +177,11 @@ zone=nsec3.optout.example.
 infile=nsec3.optout.example.db.in
 zonefile=nsec3.optout.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -3 - -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 #  OPTOUT/OPTOUT test zone
@@ -189,11 +190,11 @@ zone=optout.optout.example.
 infile=optout.optout.example.db.in
 zonefile=optout.optout.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -3 - -A -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -A -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A optout nsec3 zone.
@@ -202,11 +203,11 @@ zone=optout.example.
 infile=optout.example.db.in
 zonefile=optout.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS  -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -g -3 - -A -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -g -3 - -A -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A nsec3 zone (non-optout) with unknown nsec3 hash algorithm (-U).
@@ -215,11 +216,11 @@ zone=nsec3-unknown.example.
 infile=nsec3-unknown.example.db.in
 zonefile=nsec3-unknown.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -3 - -U -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -U -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A optout nsec3 zone with a unknown nsec3 hash algorithm (-U).
@@ -228,11 +229,11 @@ zone=optout-unknown.example.
 infile=optout-unknown.example.db.in
 zonefile=optout-unknown.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -3 - -U -A -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -U -A -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A zone that is signed with an unknown DNSKEY algorithm.
@@ -242,16 +243,16 @@ zone=dnskey-unknown.example
 infile=dnskey-unknown.example.db.in
 zonefile=dnskey-unknown.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -3 - -r $RANDFILE -o $zone -O full -f ${zonefile}.tmp $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -r $RANDFILE -o $zone -O full -f ${zonefile}.tmp $zonefile >/dev/null 2>&1
 
-awk '$4 == "DNSKEY" { $7 = 100 } $4 == "RRSIG" { $6 = 100 } { print }' ${zonefile}.tmp > ${zonefile}.signed
+awk '$4 == "DNSKEY" { $7 = 100 } $4 == "RRSIG" { $6 = 100 } { print }' ${zonefile}.tmp >${zonefile}.signed
 
 DSFILE=dsset-${zone}${TP}
-$DSFROMKEY -A -f ${zonefile}.signed $zone > $DSFILE
+$DSFROMKEY -A -f ${zonefile}.signed $zone >$DSFILE
 
 #
 # A zone that is signed with an unsupported DNSKEY algorithm (3).
@@ -261,16 +262,16 @@ zone=dnskey-unsupported.example
 infile=dnskey-unsupported.example.db.in
 zonefile=dnskey-unsupported.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
-cat $infile $keyname.key > $zonefile
+cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -3 - -r $RANDFILE -o $zone -O full -f ${zonefile}.tmp $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -r $RANDFILE -o $zone -O full -f ${zonefile}.tmp $zonefile >/dev/null 2>&1
 
-awk '$4 == "DNSKEY" { $7 = 255 } $4 == "RRSIG" { $6 = 255 } { print }' ${zonefile}.tmp > ${zonefile}.signed
+awk '$4 == "DNSKEY" { $7 = 255 } $4 == "RRSIG" { $6 = 255 } { print }' ${zonefile}.tmp >${zonefile}.signed
 
 DSFILE="dsset-${zone}${TP}"
-$DSFROMKEY -A -f ${zonefile}.signed $zone > $DSFILE
+$DSFROMKEY -A -f ${zonefile}.signed $zone >$DSFILE
 
 #
 # A zone with a published unsupported DNSKEY algorithm (Reserved).
@@ -280,12 +281,12 @@ zone=dnskey-unsupported-2.example
 infile=dnskey-unsupported-2.example.db.in
 zonefile=dnskey-unsupported-2.example.db
 
-ksk=`$KEYGEN -f KSK -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
-zsk=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+ksk=$($KEYGEN -f KSK -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
+zsk=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
-cat $infile $ksk.key $zsk.key unsupported-algorithm.key > $zonefile
+cat $infile $ksk.key $zsk.key unsupported-algorithm.key >$zonefile
 
-$SIGNER -P -3 - -r $RANDFILE -o $zone -f ${zonefile}.signed $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -r $RANDFILE -o $zone -f ${zonefile}.signed $zonefile >/dev/null 2>&1
 
 #
 # A zone with a unknown DNSKEY algorithm + unknown NSEC3 hash algorithm (-U).
@@ -295,16 +296,16 @@ zone=dnskey-nsec3-unknown.example
 infile=dnskey-nsec3-unknown.example.db.in
 zonefile=dnskey-nsec3-unknown.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -3 - -r $RANDFILE -o $zone -U -O full -f ${zonefile}.tmp $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -r $RANDFILE -o $zone -U -O full -f ${zonefile}.tmp $zonefile >/dev/null 2>&1
 
-awk '$4 == "DNSKEY" { $7 = 100; print } $4 == "RRSIG" { $6 = 100; print } { print }' ${zonefile}.tmp > ${zonefile}.signed
+awk '$4 == "DNSKEY" { $7 = 100; print } $4 == "RRSIG" { $6 = 100; print } { print }' ${zonefile}.tmp >${zonefile}.signed
 
 DSFILE=dsset-${zone}${TP}
-$DSFROMKEY -A -f ${zonefile}.signed $zone > $DSFILE
+$DSFROMKEY -A -f ${zonefile}.signed $zone >$DSFILE
 
 #
 # A multiple parameter nsec3 zone.
@@ -313,21 +314,21 @@ zone=multiple.example.
 infile=multiple.example.db.in
 zonefile=multiple.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 mv $zonefile.signed $zonefile
-$SIGNER -P -u3 - -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -u3 - -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 mv $zonefile.signed $zonefile
-$SIGNER -P -u3 AAAA -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -u3 AAAA -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 mv $zonefile.signed $zonefile
-$SIGNER -P -u3 BBBB -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -u3 BBBB -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 mv $zonefile.signed $zonefile
-$SIGNER -P -u3 CCCC -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -u3 CCCC -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 mv $zonefile.signed $zonefile
-$SIGNER -P -u3 DDDD -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -u3 DDDD -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A RSASHA256 zone.
@@ -336,11 +337,11 @@ zone=rsasha256.example.
 infile=rsasha256.example.db.in
 zonefile=rsasha256.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a RSASHA256 -b 1024 -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a RSASHA256 -b 1024 -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A RSASHA512 zone.
@@ -349,11 +350,11 @@ zone=rsasha512.example.
 infile=rsasha512.example.db.in
 zonefile=rsasha512.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a RSASHA512 -b 1024 -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a RSASHA512 -b 1024 -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A zone with the DNSKEY set only signed by the KSK
@@ -362,10 +363,10 @@ zone=kskonly.example.
 infile=kskonly.example.db.in
 zonefile=kskonly.example.db
 
-kskname=`$KEYGEN -q -r $RANDFILE -fk $zone`
-zskname=`$KEYGEN -q -r $RANDFILE $zone`
+kskname=$($KEYGEN -q -r $RANDFILE -fk $zone)
+zskname=$($KEYGEN -q -r $RANDFILE $zone)
 cat $infile $kskname.key $zskname.key >$zonefile
-$SIGNER -x -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -x -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A zone with the expired signatures
@@ -374,10 +375,10 @@ zone=expired.example.
 infile=expired.example.db.in
 zonefile=expired.example.db
 
-kskname=`$KEYGEN -q -r $RANDFILE -fk $zone`
-zskname=`$KEYGEN -q -r $RANDFILE $zone`
+kskname=$($KEYGEN -q -r $RANDFILE -fk $zone)
+zskname=$($KEYGEN -q -r $RANDFILE $zone)
 cat $infile $kskname.key $zskname.key >$zonefile
-$SIGNER -P -r $RANDFILE -o $zone -s -1d -e +1h $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone -s -1d -e +1h $zonefile >/dev/null 2>&1
 rm -f $kskname.* $zskname.*
 
 #
@@ -387,10 +388,10 @@ zone=update-nsec3.example.
 infile=update-nsec3.example.db.in
 zonefile=update-nsec3.example.db
 
-kskname=`$KEYGEN -q -3 -r $RANDFILE -fk $zone`
-zskname=`$KEYGEN -q -3 -r $RANDFILE $zone`
+kskname=$($KEYGEN -q -3 -r $RANDFILE -fk $zone)
+zskname=$($KEYGEN -q -3 -r $RANDFILE $zone)
 cat $infile $kskname.key $zskname.key >$zonefile
-$SIGNER -P -3 - -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A NSEC signed zone that will have auto-dnssec enabled and
@@ -400,12 +401,12 @@ zone=auto-nsec.example.
 infile=auto-nsec.example.db.in
 zonefile=auto-nsec.example.db
 
-kskname=`$KEYGEN -q -r $RANDFILE -fk $zone`
-zskname=`$KEYGEN -q -r $RANDFILE $zone`
-kskname=`$KEYGEN -q -r $RANDFILE -fk $zone`
-zskname=`$KEYGEN -q -r $RANDFILE $zone`
+kskname=$($KEYGEN -q -r $RANDFILE -fk $zone)
+zskname=$($KEYGEN -q -r $RANDFILE $zone)
+kskname=$($KEYGEN -q -r $RANDFILE -fk $zone)
+zskname=$($KEYGEN -q -r $RANDFILE $zone)
 cat $infile $kskname.key $zskname.key >$zonefile
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A NSEC3 signed zone that will have auto-dnssec enabled and
@@ -415,12 +416,12 @@ zone=auto-nsec3.example.
 infile=auto-nsec3.example.db.in
 zonefile=auto-nsec3.example.db
 
-kskname=`$KEYGEN -q -3 -r $RANDFILE -fk $zone`
-zskname=`$KEYGEN -q -3 -r $RANDFILE $zone`
-kskname=`$KEYGEN -q -3 -r $RANDFILE -fk $zone`
-zskname=`$KEYGEN -q -3 -r $RANDFILE $zone`
+kskname=$($KEYGEN -q -3 -r $RANDFILE -fk $zone)
+zskname=$($KEYGEN -q -3 -r $RANDFILE $zone)
+kskname=$($KEYGEN -q -3 -r $RANDFILE -fk $zone)
+zskname=$($KEYGEN -q -3 -r $RANDFILE $zone)
 cat $infile $kskname.key $zskname.key >$zonefile
-$SIGNER -P -3 - -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -3 - -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # Secure below cname test zone.
@@ -428,9 +429,9 @@ $SIGNER -P -3 - -r $RANDFILE -o $zone $z
 zone=secure.below-cname.example.
 infile=secure.below-cname.example.db.in
 zonefile=secure.below-cname.example.db
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 cat $infile $keyname.key >$zonefile
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # Patched TTL test zone.
@@ -441,12 +442,12 @@ zonefile=ttlpatch.example.db
 signedfile=ttlpatch.example.db.signed
 patchedfile=ttlpatch.example.db.patched
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -f $signedfile -o $zone $zonefile > /dev/null 2>&1
-$CHECKZONE -D -s full $zone $signedfile 2> /dev/null | \
-    awk '{$2 = "3600"; print}' > $patchedfile
+$SIGNER -P -r $RANDFILE -f $signedfile -o $zone $zonefile >/dev/null 2>&1
+$CHECKZONE -D -s full $zone $signedfile 2>/dev/null |
+	awk '{$2 = "3600"; print}' >$patchedfile
 
 #
 # Separate DNSSEC records.
@@ -456,11 +457,11 @@ infile=split-dnssec.example.db.in
 zonefile=split-dnssec.example.db
 signedfile=split-dnssec.example.db.signed
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 cat $infile $keyname.key >$zonefile
-echo '$INCLUDE "'"$signedfile"'"' >> $zonefile
-: > $signedfile
-$SIGNER -P -r $RANDFILE -D -o $zone $zonefile > /dev/null 2>&1
+echo '$INCLUDE "'"$signedfile"'"' >>$zonefile
+: >$signedfile
+$SIGNER -P -r $RANDFILE -D -o $zone $zonefile >/dev/null 2>&1
 
 #
 # Separate DNSSEC records smart signing.
@@ -470,11 +471,11 @@ infile=split-smart.example.db.in
 zonefile=split-smart.example.db
 signedfile=split-smart.example.db.signed
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 cp $infile $zonefile
-echo '$INCLUDE "'"$signedfile"'"' >> $zonefile
-: > $signedfile
-$SIGNER -P -S -r $RANDFILE -D -o $zone $zonefile > /dev/null 2>&1
+echo '$INCLUDE "'"$signedfile"'"' >>$zonefile
+: >$signedfile
+$SIGNER -P -S -r $RANDFILE -D -o $zone $zonefile >/dev/null 2>&1
 
 #
 # Zone with signatures about to expire, but no private key to replace them
@@ -483,10 +484,10 @@ zone="expiring.example."
 infile="expiring.example.db.in"
 zonefile="expiring.example.db"
 signedfile="expiring.example.db.signed"
-kskname=`$KEYGEN -q -r $RANDFILE $zone`
-zskname=`$KEYGEN -q -r $RANDFILE -f KSK $zone`
+kskname=$($KEYGEN -q -r $RANDFILE $zone)
+zskname=$($KEYGEN -q -r $RANDFILE -f KSK $zone)
 cp $infile $zonefile
-$SIGNER -S -r $RANDFILE -e now+1mi -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -S -r $RANDFILE -e now+1mi -o $zone $zonefile >/dev/null 2>&1
 mv -f ${zskname}.private ${zskname}.private.moved
 mv -f ${kskname}.private ${kskname}.private.moved
 
@@ -498,12 +499,12 @@ infile="upper.example.db.in"
 zonefile="upper.example.db"
 lower="upper.example.db.lower"
 signedfile="upper.example.db.signed"
-kskname=`$KEYGEN -q -r $RANDFILE $zone`
-zskname=`$KEYGEN -q -r $RANDFILE -f KSK $zone`
+kskname=$($KEYGEN -q -r $RANDFILE $zone)
+zskname=$($KEYGEN -q -r $RANDFILE -f KSK $zone)
 cp $infile $zonefile
-$SIGNER -P -S -r $RANDFILE -o $zone -f $lower $zonefile > /dev/null 2>/dev/null
-$CHECKZONE -D upper.example $lower 2>/dev/null | \
-	sed '/RRSIG/s/ upper.example. / UPPER.EXAMPLE. /' > $signedfile
+$SIGNER -P -S -r $RANDFILE -o $zone -f $lower $zonefile >/dev/null 2>/dev/null
+$CHECKZONE -D upper.example $lower 2>/dev/null |
+	sed '/RRSIG/s/ upper.example. / UPPER.EXAMPLE. /' >$signedfile
 
 #
 # Check that the signer's name is in lower case when zone name is in
@@ -513,10 +514,10 @@ zone="LOWER.EXAMPLE."
 infile="lower.example.db.in"
 zonefile="lower.example.db"
 signedfile="lower.example.db.signed"
-kskname=`$KEYGEN -q -r $RANDFILE $zone`
-zskname=`$KEYGEN -q -r $RANDFILE -f KSK $zone`
+kskname=$($KEYGEN -q -r $RANDFILE $zone)
+zskname=$($KEYGEN -q -r $RANDFILE -f KSK $zone)
 cp $infile $zonefile
-$SIGNER -P -S -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -S -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # Zone with signatures about to expire, and dynamic, but configured
@@ -526,21 +527,21 @@ zone="nosign.example."
 infile="nosign.example.db.in"
 zonefile="nosign.example.db"
 signedfile="nosign.example.db.signed"
-kskname=`$KEYGEN -q -r $RANDFILE $zone`
-zskname=`$KEYGEN -q -r $RANDFILE -f KSK $zone`
+kskname=$($KEYGEN -q -r $RANDFILE $zone)
+zskname=$($KEYGEN -q -r $RANDFILE -f KSK $zone)
 cp $infile $zonefile
-$SIGNER -S -r $RANDFILE -e now+1mi -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -S -r $RANDFILE -e now+1mi -o $zone $zonefile >/dev/null 2>&1
 # preserve a normalized copy of the NS RRSIG for comparison later
-$CHECKZONE -D nosign.example nosign.example.db.signed 2>/dev/null | \
-        awk '$4 == "RRSIG" && $5 == "NS" {$2 = ""; print}' | \
-        sed 's/[ 	][ 	]*/ /g'> ../nosign.before
+$CHECKZONE -D nosign.example nosign.example.db.signed 2>/dev/null |
+	awk '$4 == "RRSIG" && $5 == "NS" {$2 = ""; print}' |
+	sed 's/[ 	][ 	]*/ /g' >../nosign.before
 
 #
 # An inline signing zone
 #
 zone=inline.example.
-kskname=`$KEYGEN -q -3 -r $RANDFILE -fk $zone`
-zskname=`$KEYGEN -q -3 -r $RANDFILE $zone`
+kskname=$($KEYGEN -q -3 -r $RANDFILE -fk $zone)
+zskname=$($KEYGEN -q -3 -r $RANDFILE $zone)
 
 #
 # publish a new key while deactivating another key at the same time.
@@ -548,13 +549,13 @@ zskname=`$KEYGEN -q -3 -r $RANDFILE $zon
 zone=publish-inactive.example
 infile=publish-inactive.example.db.in
 zonefile=publish-inactive.example.db
-now=`date -u +%Y%m%d%H%M%S`
-kskname=`$KEYGEN -q -r $RANDFILE -f KSK $zone`
-kskname=`$KEYGEN -P $now+90s -A $now+3600s -q -r $RANDFILE -f KSK $zone`
-kskname=`$KEYGEN -I $now+90s -q -r $RANDFILE -f KSK $zone`
-zskname=`$KEYGEN -q -r $RANDFILE $zone`
+now=$(date -u +%Y%m%d%H%M%S)
+kskname=$($KEYGEN -q -r $RANDFILE -f KSK $zone)
+kskname=$($KEYGEN -P $now+90s -A $now+3600s -q -r $RANDFILE -f KSK $zone)
+kskname=$($KEYGEN -I $now+90s -q -r $RANDFILE -f KSK $zone)
+zskname=$($KEYGEN -q -r $RANDFILE $zone)
 cp $infile $zonefile
-$SIGNER -S -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -S -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A zone which will change its sig-validity-interval
@@ -562,8 +563,8 @@ $SIGNER -S -r $RANDFILE -o $zone $zonefi
 zone=siginterval.example
 infile=siginterval.example.db.in
 zonefile=siginterval.example.db
-kskname=`$KEYGEN -q -3 -r $RANDFILE -fk $zone`
-zskname=`$KEYGEN -q -3 -r $RANDFILE $zone`
+kskname=$($KEYGEN -q -3 -r $RANDFILE -fk $zone)
+zskname=$($KEYGEN -q -3 -r $RANDFILE $zone)
 cp $infile $zonefile
 
 #
@@ -574,12 +575,12 @@ zone=badds.example.
 infile=bogus.example.db.in
 zonefile=badds.example.db
 
-keyname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone`
+keyname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n zone $zone)
 
 cat $infile $keyname.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
-sed -e 's/bogus/badds/g' < dsset-bogus.example$TP > dsset-badds.example$TP
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
+sed -e 's/bogus/badds/g' <dsset-bogus.example$TP >dsset-badds.example$TP
 
 #
 # A zone with future signatures.
@@ -587,10 +588,10 @@ sed -e 's/bogus/badds/g' < dsset-bogus.e
 zone=future.example
 infile=future.example.db.in
 zonefile=future.example.db
-kskname=`$KEYGEN -q -r $RANDFILE -f KSK $zone`
-zskname=`$KEYGEN -q -r $RANDFILE $zone`
+kskname=$($KEYGEN -q -r $RANDFILE -f KSK $zone)
+zskname=$($KEYGEN -q -r $RANDFILE $zone)
 cat $infile $kskname.key $zskname.key >$zonefile
-$SIGNER -P -s +3600 -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -s +3600 -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 cp -f $kskname.key trusted-future.key
 
 #
@@ -599,10 +600,10 @@ cp -f $kskname.key trusted-future.key
 zone=managed-future.example
 infile=managed-future.example.db.in
 zonefile=managed-future.example.db
-kskname=`$KEYGEN -q -r $RANDFILE -f KSK $zone`
-zskname=`$KEYGEN -q -r $RANDFILE $zone`
+kskname=$($KEYGEN -q -r $RANDFILE -f KSK $zone)
+zskname=$($KEYGEN -q -r $RANDFILE $zone)
 cat $infile $kskname.key $zskname.key >$zonefile
-$SIGNER -P -s +3600 -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -s +3600 -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A zone with a revoked key
@@ -611,14 +612,14 @@ zone=revkey.example.
 infile=generic.example.db.in
 zonefile=revkey.example.db
 
-ksk1=`$KEYGEN -q -r $RANDFILE -3fk $zone`
-ksk1=`$REVOKE $ksk1`
-ksk2=`$KEYGEN -q -r $RANDFILE -3fk $zone`
-zsk1=`$KEYGEN -q -r $RANDFILE -3 $zone`
+ksk1=$($KEYGEN -q -r $RANDFILE -3fk $zone)
+ksk1=$($REVOKE $ksk1)
+ksk2=$($KEYGEN -q -r $RANDFILE -3fk $zone)
+zsk1=$($KEYGEN -q -r $RANDFILE -3 $zone)
 
 cat $infile ${ksk1}.key ${ksk2}.key ${zsk1}.key >$zonefile
 
-$SIGNER -P -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -o $zone $zonefile >/dev/null 2>&1
 
 #
 # Check that NSEC3 are correctly signed and returned from below a DNAME
@@ -626,10 +627,10 @@ $SIGNER -P -r $RANDFILE -o $zone $zonefi
 zone=dname-at-apex-nsec3.example
 infile=dname-at-apex-nsec3.example.db.in
 zonefile=dname-at-apex-nsec3.example.db
-kskname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -3fk $zone`
-zskname=`$KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -3 $zone`
+kskname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -3fk $zone)
+zskname=$($KEYGEN -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -3 $zone)
 cat $infile $kskname.key $zskname.key >$zonefile
-$SIGNER -P -r $RANDFILE -3 - -o $zone $zonefile > /dev/null 2>&1
+$SIGNER -P -r $RANDFILE -3 - -o $zone $zonefile >/dev/null 2>&1
 
 #
 # A NSEC zone with occuded data at the delegation
@@ -637,11 +638,11 @@ $SIGNER -P -r $RANDFILE -3 - -o $zone $z
 zone=occluded.example
 infile=occluded.example.db.in
 zonefile=occluded.example.db
-kskname=`"$KEYGEN" -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -fk "$zone"`
-zskname=`"$KEYGEN" -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS "$zone"`
-dnskeyname=`"$KEYGEN" -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -fk "delegation.$zone"`
-keyname=`"$KEYGEN" -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n ENTITY -T KEY "delegation.$zone"`
-$DSFROMKEY "$dnskeyname.key" > "dsset-delegation.${zone}$TP"
+kskname=$("$KEYGEN" -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -fk "$zone")
+zskname=$("$KEYGEN" -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS "$zone")
+dnskeyname=$("$KEYGEN" -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -fk "delegation.$zone")
+keyname=$("$KEYGEN" -q -r $RANDFILE -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n ENTITY -T KEY "delegation.$zone")
+$DSFROMKEY "$dnskeyname.key" >"dsset-delegation.${zone}$TP"
 cat "$infile" "${kskname}.key" "${zskname}.key" "${keyname}.key" \
-    "${dnskeyname}.key" "dsset-delegation.${zone}$TP" >"$zonefile"
-"$SIGNER" -P -r $RANDFILE -o "$zone" "$zonefile" > /dev/null 2>&1
+	"${dnskeyname}.key" "dsset-delegation.${zone}$TP" >"$zonefile"
+"$SIGNER" -P -r $RANDFILE -o "$zone" "$zonefile" >/dev/null 2>&1
Index: bind-9.11.22/bin/tests/system/dnssec/tests.sh
===================================================================
--- bind-9.11.22.orig/bin/tests/system/dnssec/tests.sh
+++ bind-9.11.22/bin/tests/system/dnssec/tests.sh
@@ -743,6 +743,21 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=`expr $status + $ret`
 
+echo_i "checking mixed-case positive validation ($n)"
+ret=0
+for type in a txt aaaa loc; do
+  $DIG $DIGOPTS +noauth mixedcase.secure.example. \
+    @10.53.0.3 $type >dig.out.$type.ns3.test$n || ret=1
+  $DIG $DIGOPTS +noauth mixedcase.secure.example. \
+    @10.53.0.4 $type >dig.out.$type.ns4.test$n || ret=1
+  digcomp --lc dig.out.$type.ns3.test$n dig.out.$type.ns4.test$n || ret=1
+  grep "status: NOERROR" dig.out.$type.ns4.test$n >/dev/null || ret=1
+  grep "flags:.*ad.*QUERY" dig.out.$type.ns4.test$n >/dev/null || ret=1
+done
+n=$((n + 1))
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status + ret))
+
 echo_i "checking multi-stage positive validation NSEC/NSEC3 ($n)"
 ret=0
 $DIG $DIGOPTS +noauth a.nsec3.example. \
