Blob Blame History Raw
From 623bb196ca62f513345a93fffd77a60704261f38 Mon Sep 17 00:00:00 2001
From: Stepan Broz <sbroz@redhat.com>
Date: Mon, 6 May 2024 19:40:19 +0200
Subject: [PATCH] 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)
(cherry picked from commit 0ceed03ebea395da1a39ad1cb39205ce75a3f768)

Backport isc_ht API changes from BIND 9.18

To prevent allocating large hashtable in dns_message, we need to
backport the improvements to isc_ht API from BIND 9.18+ that includes
support for case insensitive keys and incremental rehashing of the
hashtables.

(cherry picked from commit a4baf324159ec3764195c949cb56c861d9f173ff)
(cherry picked from commit 2fc28056b33018f7f78b625409eb44c32d5c9b11)

fix a message parsing regression

the fix for CVE-2023-4408 introduced a regression in the message
parser, which could cause a crash if duplicate rdatasets were found
in the question section. this commit ensures that rdatasets are
correctly disassociated and freed when this occurs.

(cherry picked from commit 4c19d35614f8cd80d8748156a5bad361e19abc28)
(cherry picked from commit 98ab8c81cc7739dc220aa3f50efa3061774de8ba)

fix another message parsing regression

The fix for CVE-2023-4408 introduced a regression in the message
parser, which could cause a crash if an rdata type that can only
occur in the question was found in another section.

(cherry picked from commit 510f1de8a6add516b842a55750366944701d3d9a)
(cherry picked from commit bbbcaf8b2ec17d2cad28841ea86078168072ae2f)

Apply various tweaks specific to BIND 9.11

(cherry picked from commit c6026cbbaa9d297910af350fa6cc45788cc9f397)

Fix case insensitive matching in isc_ht hash table implementation

The case insensitive matching in isc_ht was basically completely broken
as only the hashvalue computation was case insensitive, but the key
comparison was always case sensitive.

(cherry picked from commit c462d65b2fd0db362947db4a18a87df78f8d8e5d)
(cherry picked from commit 418b3793598a1e1c7e391bb317866d405cd03501)

Add a system test for mixed-case data for the same owner

We were missing a test where a single owner name would have multiple
types with a different case.  The generated RRSIGs and NSEC records will
then have different case than the signed records and message parser have
to cope with that and treat everything as the same owner.

(cherry picked from commit c8b623d87f0fb8f9cba8dea5c6a4b600953895e7)
(cherry picked from commit 1f9bbe1fe34b7a2c9765431e8a86b460afc9b323)

6315.   [security]      Speed up parsing of DNS messages with many different
                        names. (CVE-2023-4408) [GL #4234]

Fix assertion failure in nslookup/dig/mdig when message has
 multiple SIG(0) options.

When parsing message with DNS_MESSAGE_BESTEFFORT (used exclusively in
tools, never in named itself) if we hit an invalid SIG(0) in wrong
place we continue parsing the message, and put the sig0 in msg->sig0.
If we then hit another sig0 in a proper place we see that msg->sig0
is already 'taken' and we don't free name and rdataset, and we don't
set seen_problem. This causes an assertion failure.
This fixes that issue by setting seen_problem if we hit second sig0,
tsig or opt, which causes name and rdataset to be always freed.

(cherry picked from commit 51a55ddbb73f8707de3d1b8cda15c8f61585bacb)
(cherry picked from commit 736d8c5b80d02f10555f324abef920a7257f9f43)
---
 .../system/dnssec/ns3/secure.example.db.in    |   5 +
 bin/tests/system/dnssec/ns3/sign.sh           | 112 ++--
 bin/tests/system/dnssec/tests.sh              |  15 +
 lib/dns/catz.c                                |  50 +-
 lib/dns/include/dns/message.h                 |  39 --
 lib/dns/include/dns/name.h                    |   9 +-
 lib/dns/message.c                             | 624 +++++++++++-------
 lib/dns/name.c                                |   1 +
 lib/isc/ht.c                                  | 598 ++++++++++++-----
 lib/isc/include/isc/ht.h                      |  45 +-
 lib/isc/tests/ht_test.c                       |  75 ++-
 11 files changed, 1032 insertions(+), 541 deletions(-)

diff --git a/bin/tests/system/dnssec/ns3/secure.example.db.in b/bin/tests/system/dnssec/ns3/secure.example.db.in
index 9d310d8..3b713d8 100644
--- a/bin/tests/system/dnssec/ns3/secure.example.db.in
+++ b/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
diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh
index 99e9b49..ce5b2ee 100644
--- a/bin/tests/system/dnssec/ns3/sign.sh
+++ b/bin/tests/system/dnssec/ns3/sign.sh
@@ -28,7 +28,9 @@ keyname=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 768 -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
@@ -313,11 +315,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
@@ -326,10 +328,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
@@ -338,10 +340,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.*
 
 #
@@ -351,10 +353,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
@@ -364,12 +366,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
@@ -379,12 +381,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.
@@ -447,10 +449,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
 
@@ -462,12 +464,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
@@ -477,10 +479,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
@@ -490,21 +492,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.
@@ -512,13 +514,13 @@ zskname=`$KEYGEN -q -3 -r $RANDFILE $zone`
 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
@@ -526,8 +528,8 @@ $SIGNER -S -r $RANDFILE -o $zone $zonefile > /dev/null 2>&1
 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
 
 #
@@ -551,10 +553,10 @@ sed -e 's/bogus/badds/g' < dsset-bogus.example$TP > dsset-badds.example$TP
 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
 
 #
@@ -563,10 +565,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
diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh
index 91a4822..e8d8f7e 100644
--- a/bin/tests/system/dnssec/tests.sh
+++ b/bin/tests/system/dnssec/tests.sh
@@ -699,6 +699,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. \
diff --git a/lib/dns/catz.c b/lib/dns/catz.c
index 767c710..98ddd01 100644
--- a/lib/dns/catz.c
+++ b/lib/dns/catz.c
@@ -395,33 +395,21 @@ dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) {
 
 	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
@@ -565,7 +553,6 @@ dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) {
 
 	result = ISC_R_SUCCESS;
 
-cleanup:
 	if (iter1 != NULL)
 		isc_ht_iter_destroy(&iter1);
 	if (iter2 != NULL)
@@ -605,9 +592,7 @@ dns_catz_new_zones(dns_catz_zones_t **catzsp, dns_catz_zonemodmethods_t *zmm,
 	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;
@@ -624,7 +609,6 @@ dns_catz_new_zones(dns_catz_zones_t **catzsp, dns_catz_zonemodmethods_t *zmm,
 
   cleanup_ht:
 	isc_ht_destroy(&new_zones->zones);
-  cleanup_refcount:
 	isc_refcount_destroy(&new_zones->refs);
   cleanup_mutex:
 	isc_mutex_destroy(&new_zones->lock);
@@ -667,9 +651,7 @@ dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **zonep,
 	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,
@@ -698,7 +680,6 @@ dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **zonep,
 
   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));
@@ -800,8 +781,7 @@ dns_catz_zone_detach(dns_catz_zone_t **zonep) {
 	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))
@@ -860,8 +840,7 @@ dns_catz_catzs_detach(dns_catz_zones_t **catzsp) {
 		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;)
 			{
@@ -1970,8 +1949,7 @@ dns_catz_prereconfig(dns_catz_zones_t *catzs) {
 
 	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))
@@ -1993,8 +1971,7 @@ dns_catz_postreconfig(dns_catz_zones_t *catzs) {
 	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;)
 	{
@@ -2036,5 +2013,6 @@ dns_catz_postreconfig(dns_catz_zones_t *catzs) {
 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);
 }
diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h
index 868a87d..6682450 100644
--- a/lib/dns/include/dns/message.h
+++ b/lib/dns/include/dns/message.h
@@ -739,45 +739,6 @@ dns_message_findtype(dns_name_t *name, dns_rdatatype_t type,
  *\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);
diff --git a/lib/dns/include/dns/name.h b/lib/dns/include/dns/name.h
index 93ddacd..9f60081 100644
--- a/lib/dns/include/dns/name.h
+++ b/lib/dns/include/dns/name.h
@@ -67,6 +67,7 @@
 #include <stdio.h>
 
 #include <isc/boolean.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. */
@@ -110,6 +111,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')
@@ -170,7 +172,7 @@ LIBDNS_EXTERNAL_DATA extern dns_name_t *dns_wildcardname;
 	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) { \
@@ -178,12 +180,12 @@ LIBDNS_EXTERNAL_DATA extern dns_name_t *dns_wildcardname;
 	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	\
 }
 
 /*%
@@ -1373,6 +1375,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) \
diff --git a/lib/dns/message.c b/lib/dns/message.c
index 9210c26..c97b7bd 100644
--- a/lib/dns/message.c
+++ b/lib/dns/message.c
@@ -19,6 +19,8 @@
 #include <ctype.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?) */
@@ -159,6 +161,9 @@ msgblock_allocate(isc_mem_t *, unsigned int, unsigned int);
 #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);
 
@@ -470,7 +475,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;
@@ -491,10 +496,11 @@ msgresetsigs(dns_message_t *msg, isc_boolean_t replying) {
 			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))
@@ -504,13 +510,10 @@ msgresetsigs(dns_message_t *msg, isc_boolean_t replying) {
 		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);
@@ -812,6 +815,18 @@ dns_message_destroy(dns_message_t **msgp) {
 	isc_mem_putanddetach(&msg->mctx, msg, sizeof(dns_message_t));
 }
 
+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)
@@ -831,28 +846,36 @@ findname(dns_name_t **foundname, dns_name_t *target,
 	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);
+#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
 
-	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);
-		}
+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);
 	}
-
-	return (ISC_R_NOTFOUND);
+	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
@@ -980,6 +1003,18 @@ getrdata(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		}					\
 	} 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)
@@ -991,13 +1026,15 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 	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;
-	isc_boolean_t free_name;
+	isc_boolean_t free_name = ISC_FALSE;
 	isc_boolean_t best_effort;
 	isc_boolean_t seen_problem;
+	isc_ht_t *name_map = NULL;
+	isc_boolean_t free_ht = ISC_FALSE;
 
 	section = &msg->sections[DNS_SECTION_QUESTION];
 
@@ -1005,9 +1042,14 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 	seen_problem = ISC_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)
@@ -1030,13 +1072,20 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		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.
@@ -1048,18 +1097,28 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		 * 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 = ISC_FALSE;
-		} else {
-			isc_mempool_put(msg->namepool, name);
+			break;
+		case ISC_R_EXISTS:
+			dns_message_puttempname(msg, &name);
 			name = name2;
 			name2 = NULL;
-			free_name = ISC_FALSE;
+			break;
+		default:
+			/*
+			 * Unreachable
+			 */
+			break;
 		}
 
+		free_name = ISC_FALSE;
+
 		/*
 		 * Get type and class.
 		 */
@@ -1087,13 +1146,6 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		if (rdtype == dns_rdatatype_tkey)
 			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.
 		 */
@@ -1102,7 +1154,7 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 			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;
@@ -1115,32 +1167,74 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		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 = ISC_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);
 }
@@ -1220,24 +1314,26 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 	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;
-	isc_boolean_t free_name = ISC_FALSE, free_rdataset = ISC_FALSE;
-	isc_boolean_t preserve_order, best_effort, seen_problem;
-	isc_boolean_t issigzero;
-
-	preserve_order = ISC_TF(options & DNS_MESSAGEPARSE_PRESERVEORDER);
-	best_effort = ISC_TF(options & DNS_MESSAGEPARSE_BESTEFFORT);
-	seen_problem = ISC_FALSE;
-
-	section = &msg->sections[sectionid];
+	dns_namelist_t *section = &msg->sections[sectionid];
+	isc_boolean_t free_name = ISC_FALSE, seen_problem = ISC_FALSE;
+	isc_boolean_t free_ht = ISC_FALSE;
+	isc_boolean_t preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0);
+	isc_boolean_t best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
+	isc_boolean_t isedns, issigzero, istsig;
+	isc_ht_t *name_map = NULL;
+
+	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;
@@ -1245,11 +1341,15 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 
 		skip_name_search = ISC_FALSE;
 		skip_type_search = ISC_FALSE;
-		free_rdataset = ISC_FALSE;
+		isedns = ISC_FALSE;
+		issigzero = ISC_FALSE;
+		istsig = ISC_FALSE;
+		found_rdataset = NULL;
 
 		name = isc_mempool_get(msg->namepool);
-		if (name == NULL)
+		if (name == NULL) {
 			return (ISC_R_NOMEMORY);
+		}
 		free_name = ISC_TRUE;
 
 		offsets = newoffsets(msg);
@@ -1265,8 +1365,9 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		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
@@ -1286,9 +1387,10 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		 * 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;
 		}
@@ -1297,15 +1399,17 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		 * 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
@@ -1315,7 +1419,9 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		    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.
@@ -1328,10 +1434,13 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 			if (sectionid != DNS_SECTION_ADDITIONAL ||
 			    rdclass != dns_rdataclass_any ||
 			    count != msg->counts[sectionid]  - 1)
+			{
 				DO_ERROR(DNS_R_BADTSIG);
-			msg->sigstart = recstart;
-			skip_name_search = ISC_TRUE;
-			skip_type_search = ISC_TRUE;
+			} else {
+				skip_name_search = ISC_TRUE;
+				skip_type_search = ISC_TRUE;
+				istsig = ISC_TRUE;
+			}
 		} else if (rdtype == dns_rdatatype_opt) {
 			/*
 			 * The name of an OPT record must be ".", it
@@ -1341,9 +1450,13 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 			if (!dns_name_equal(dns_rootname, name) ||
 			    sectionid != DNS_SECTION_ADDITIONAL ||
 			    msg->opt != NULL)
+			{
 				DO_ERROR(DNS_R_FORMERR);
-			skip_name_search = ISC_TRUE;
-			skip_type_search = ISC_TRUE;
+			} else {
+				skip_name_search = ISC_TRUE;
+				skip_type_search = ISC_TRUE;
+				isedns = ISC_TRUE;
+			}
 		} else if (rdtype == dns_rdatatype_tkey) {
 			/*
 			 * A TKEY must be in the additional section if this
@@ -1354,13 +1467,16 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 			 */
 			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);
+			}
 		}
 
 		/*
@@ -1386,7 +1502,8 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 			goto cleanup;
 		}
 		if (msg->opcode == dns_opcode_update &&
-		    update(sectionid, rdclass)) {
+		    update(sectionid, rdclass))
+		{
 			if (rdatalen != 0) {
 				result = DNS_R_FORMERR;
 				goto cleanup;
@@ -1406,46 +1523,54 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 			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;
-		issigzero = ISC_FALSE;
-		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)
+				{
 					DO_ERROR(DNS_R_BADSIG0);
-				msg->sigstart = recstart;
-				skip_name_search = ISC_TRUE;
-				skip_type_search = ISC_TRUE;
-				issigzero = ISC_TRUE;
+				} else {
+					skip_name_search = ISC_TRUE;
+					skip_type_search = ISC_TRUE;
+					issigzero = ISC_TRUE;
+				}
 			} 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,
-					  ISC_FALSE)) {
+		    !dns_rdata_checkowner(name, msg->rdclass, rdtype, ISC_FALSE))
+		{
 			result = DNS_R_BADOWNERNAME;
 			goto cleanup;
 		}
@@ -1456,109 +1581,156 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		 * to the end of the message.
 		 */
 		if (preserve_order || msg->opcode == dns_opcode_update ||
-		    skip_name_search) {
-			if (rdtype != dns_rdatatype_opt &&
-			    rdtype != dns_rdatatype_tsig &&
-			    !issigzero)
-			{
+		    skip_name_search)
+		{
+			if (!isedns && !istsig && !issigzero) {
 				ISC_LIST_APPEND(*section, name, link);
 				free_name = ISC_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:
+				/*
+				 * Unreachable
+				 */
+				break;
 			}
 			free_name = ISC_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);
+			}
+			if (ISC_LIST_EMPTY(name->list)) {
+				result = ISC_R_SUCCESS;
+				goto skip_rds_check;
+			}
+			if (name->ht == NULL) {
+				isc_ht_init(&name->ht, msg->mctx, 1,
+					    ISC_HT_CASE_SENSITIVE);
+				free_ht = ISC_TRUE;
 
-			rdataset = NULL;
-			result = dns_message_find(name, rdclass, rdtype,
-						   covers, &rdataset);
-		}
+				INSIST(ISC_LIST_HEAD(name->list) ==
+				       ISC_LIST_TAIL(name->list));
 
-		/*
-		 * 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);
-			}
-		}
+				dns_rdataset_t *old_rdataset =
+					ISC_LIST_HEAD(name->list);
 
-		if (result == ISC_R_NOTFOUND) {
-			rdataset = isc_mempool_get(msg->rdspool);
-			if (rdataset == NULL) {
-				result = ISC_R_NOMEMORY;
-				goto cleanup;
-			}
-			free_rdataset = ISC_TRUE;
+				result = rds_hash_add(name->ht, old_rdataset,
+						      NULL);
 
-			rdatalist = newrdatalist(msg);
-			if (rdatalist == NULL) {
-				result = ISC_R_NOMEMORY;
-				goto cleanup;
+				INSIST(result == ISC_R_SUCCESS);
 			}
+			result = rds_hash_add(name->ht, 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 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;
+
+				if (!dns_rdatatype_issingleton(rdtype)) {
+					break;
+				}
 
-			if (rdtype != dns_rdatatype_opt &&
-			    rdtype != dns_rdatatype_tsig &&
-			    !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 = ISC_FALSE;
+				break;
+			default:
+				/*
+				 * Unreachable
+				 */
+				break;
 			}
 		}
 
@@ -1572,8 +1744,9 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		 */
 		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. */
@@ -1588,43 +1761,40 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 		 * already set if best-effort parsing is enabled otherwise
 		 * there will only be at most one of each.
 		 */
-		if (rdtype == dns_rdatatype_opt && msg->opt == NULL) {
+		if (isedns) {
 			dns_rcode_t ercode;
 
 			msg->opt = rdataset;
-			rdataset = NULL;
-			free_rdataset = ISC_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 = ISC_FALSE;
-		} else if (issigzero && msg->sig0 == NULL) {
+		} else if (issigzero) {
 			msg->sig0 = rdataset;
 			msg->sig0name = name;
-			rdataset = NULL;
-			free_rdataset = ISC_FALSE;
+			msg->sigstart = recstart;
 			free_name = ISC_FALSE;
-		} else if (rdtype == dns_rdatatype_tsig && msg->tsig == NULL) {
+		} else if (istsig) {
 			msg->tsig = rdataset;
 			msg->tsigname = name;
-			/* Windows doesn't like TSIG names to be compressed. */
+			msg->sigstart = recstart;
+			/*
+			 * Windows doesn't like TSIG names to be compressed.
+			 */
 			msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
-			rdataset = NULL;
-			free_rdataset = ISC_FALSE;
 			free_name = ISC_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 = ISC_FALSE;
+			if (free_name) {
+				dns_message_puttempname(msg, &name);
+			}
+			free_name = ISC_FALSE;
 		}
 		INSIST(free_name == ISC_FALSE);
-		INSIST(free_rdataset == ISC_FALSE);
 	}
 
 	/*
@@ -1635,20 +1805,32 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
 	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);
 }
@@ -2380,11 +2562,11 @@ dns_message_renderreset(dns_message_t *msg) {
 		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);
 	}
 }
 
@@ -2477,24 +2659,6 @@ dns_message_findname(dns_message_t *msg, dns_section_t section,
 	return (result);
 }
 
-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)
@@ -2587,6 +2751,9 @@ dns_message_puttempname(dns_message_t *msg, dns_name_t **item) {
 	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);
@@ -2602,14 +2769,19 @@ dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item) {
 	*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
@@ -2774,7 +2946,7 @@ dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) {
 
  cleanup:
 	dns_rdataset_disassociate(opt);
-	dns_message_puttemprdataset(msg, &opt);
+	dns__message_puttemprdataset(msg, &opt);
 	return (result);
 }
 
diff --git a/lib/dns/name.c b/lib/dns/name.c
index e597c63..b3b127b 100644
--- a/lib/dns/name.c
+++ b/lib/dns/name.c
@@ -213,6 +213,7 @@ dns_name_invalidate(dns_name_t *name) {
 	name->offsets = NULL;
 	name->buffer = NULL;
 	ISC_LINK_INIT(name, link);
+	INSIST(name->ht == NULL);
 }
 
 isc_boolean_t
diff --git a/lib/isc/ht.c b/lib/isc/ht.c
index a86e3ec..0cd3a2c 100644
--- a/lib/isc/ht.c
+++ b/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/.
@@ -19,71 +21,290 @@
 #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) ((isc_uint64_t)(1) << (bits))
 
 struct isc_ht_node {
 	void *value;
 	isc_ht_node_t *next;
+	isc_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;
+	isc_boolean_t case_sensitive;
+	size_t size[2];
+	isc_uint8_t hashbits[2];
+	isc_ht_node_t **table[2];
+	isc_uint8_t hindex;
+	isc_uint32_t hiter; /* rehashing iterator */
 };
 
 struct isc_ht_iter {
 	isc_ht_t *ht;
 	size_t i;
+	isc_uint8_t hindex;
 	isc_ht_node_t *cur;
 };
 
-isc_result_t
-isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, isc_uint8_t bits) {
-	isc_ht_t *ht = NULL;
+static isc_ht_node_t *
+isc__ht_find(const isc_ht_t *ht, const unsigned char *key,
+	     const isc_uint32_t keysize, const isc_uint32_t hashval, const isc_uint8_t idx);
+static void
+isc__ht_add(isc_ht_t *ht, const unsigned char *key, const isc_uint32_t keysize,
+	    const isc_uint32_t hashval, const isc_uint8_t idx, void *value);
+static isc_result_t
+isc__ht_delete(isc_ht_t *ht, const unsigned char *key, const isc_uint32_t keysize,
+	       const isc_uint32_t hashval, const isc_uint8_t idx);
+
+static isc_uint32_t
+rehash_bits(isc_ht_t *ht, size_t newcount);
+
+static void
+hashtable_new(isc_ht_t *ht, const isc_uint8_t idx, const isc_uint8_t bits);
+static void
+hashtable_free(isc_ht_t *ht, const isc_uint8_t idx);
+static void
+hashtable_rehash(isc_ht_t *ht, isc_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 isc_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
+};
+
+static int
+memcasecmp(const void *vs1, const void *vs2, size_t len) {
+	isc_uint8_t const *s1 = vs1;
+	isc_uint8_t const *s2 = vs2;
 	size_t i;
+	for (i = 0; i < len; i++) {
+		isc_uint8_t u1 = s1[i];
+		isc_uint8_t u2 = s2[i];
+		int U1 = maptolower[u1];
+		int U2 = maptolower[u2];
+		int diff = U1 - U2;
+		if (diff) {
+			return diff;
+		}
+	}
+	return 0;
+}
 
-	REQUIRE(htp != NULL && *htp == NULL);
-	REQUIRE(mctx != NULL);
-	REQUIRE(bits >= 1 && bits <= (sizeof(size_t)*8 - 1));
+static isc_boolean_t
+isc__ht_node_match(isc_ht_node_t *node, const isc_uint32_t hashval,
+		const isc_uint8_t *key, isc_uint32_t keysize, isc_boolean_t case_sensitive) {
+	return (node->hashval == hashval && node->keysize == keysize &&
+		(case_sensitive ? (memcmp(node->key, key, keysize) == 0)
+				: (memcasecmp(node->key, key, keysize) == 0)));
+}
+
+static isc_uint32_t
+hash_32(isc_uint32_t val, unsigned int bits) {
+	REQUIRE(bits <= HT_MAX_BITS);
+	/* High bits are more random. */
+	return (val * GOLDEN_RATIO_32 >> (32 - bits));
+}
+
+static isc_boolean_t
+rehashing_in_progress(const isc_ht_t *ht) {
+	return (ht->table[HT_NEXTTABLE(ht->hindex)] != NULL);
+}
 
-	ht = isc_mem_get(mctx, sizeof(struct isc_ht));
-	if (ht == NULL) {
-		return (ISC_R_NOMEMORY);
+static isc_boolean_t
+hashtable_is_overcommited(isc_ht_t *ht) {
+	return (ht->count >= (ht->size[ht->hindex] * HT_OVERCOMMIT));
+}
+
+static isc_uint32_t
+rehash_bits(isc_ht_t *ht, size_t newcount) {
+	isc_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, isc_uint32_t newbits) {
+	isc_uint8_t oldindex = ht->hindex;
+	isc_uint32_t oldbits = ht->hashbits[oldindex];
+	isc_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);
 
-	ht->size = ((size_t)1<<bits);
-	ht->mask = ((size_t)1<<bits)-1;
-	ht->count = 0;
+	hashtable_new(ht, newindex, newbits);
 
-	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);
+	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];
+	isc_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;
+
+	/* 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) {
+		isc_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) {
+	isc_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 isc_uint8_t idx, const isc_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 isc_uint8_t idx) {
+	size_t size = ht->size[idx] * sizeof(isc_ht_node_t *);
+	size_t i;
+
+	for (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, isc_uint8_t bits,
+	    unsigned int options) {
+	isc_ht_t *ht = NULL;
+	isc_boolean_t 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
@@ -91,125 +312,190 @@ isc_ht_destroy(isc_ht_t **htp) {
 	isc_ht_t *ht;
 	size_t i;
 
+
 	REQUIRE(htp != NULL);
+	REQUIRE(ISC_HT_VALID(*htp));
 
 	ht = *htp;
-	REQUIRE(ISC_HT_VALID(ht));
-
+	*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 (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,
-	   isc_uint32_t keysize, void *value)
-{
+static void
+isc__ht_add(isc_ht_t *ht, const unsigned char *key, const isc_uint32_t keysize,
+	    const isc_uint32_t hashval, const isc_uint8_t idx, void *value) {
 	isc_ht_node_t *node;
 	isc_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 isc_uint32_t keysize,
+	   void *value) {
+	isc_uint32_t hashval;
+
 	REQUIRE(ISC_HT_VALID(ht));
 	REQUIRE(key != NULL && keysize > 0);
 
-	hash = isc_hash_function(key, keysize, ISC_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 isc_uint32_t keysize, const isc_uint32_t hashval,
+	     const isc_uint8_t idx) {
+	isc_uint32_t hash;
+	isc_uint8_t findex = idx;
+	isc_ht_node_t *node;
+
+nexttable:
+	hash = hash_32(hashval, ht->hashbits[findex]);
+	for (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,
-	    isc_uint32_t keysize, void **valuep)
-{
+	    const isc_uint32_t keysize, void **valuep) {
+	isc_uint32_t hashval;
 	isc_ht_node_t *node;
-	isc_uint32_t hash;
 
 	REQUIRE(ISC_HT_VALID(ht));
 	REQUIRE(key != NULL && keysize > 0);
 	REQUIRE(valuep == NULL || *valuep == NULL);
 
-	hash = isc_hash_function(key, keysize, ISC_TRUE, NULL);
-	node = ht->table[hash & ht->mask];
-	while (node != NULL) {
-		if (keysize == node->keysize &&
-		    memcmp(key, node->key, keysize) == 0) {
-			*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, isc_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 isc_uint32_t keysize,
+	       const isc_uint32_t hashval, const isc_uint8_t idx) {
+	isc_ht_node_t *prev = NULL;
 	isc_uint32_t hash;
+	isc_ht_node_t *node;
 
-	REQUIRE(ISC_HT_VALID(ht));
-	REQUIRE(key != NULL && keysize > 0);
-
-	prev = NULL;
-	hash = isc_hash_function(key, keysize, ISC_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
+	hash = hash_32(hashval, ht->hashbits[idx]);
+
+	for (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 isc_uint32_t keysize) {
+	isc_uint32_t hashval;
+	isc_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;
 
@@ -217,16 +503,13 @@ isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp) {
 	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
@@ -237,26 +520,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
@@ -265,58 +570,35 @@ 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;
-	isc_uint32_t hash;
+	isc_ht_node_t *dnode = NULL;
+	isc_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, ISC_TRUE,
-				 NULL);
-	node = ht->table[hash & ht->mask];
-	while (node != to_delete) {
-		prev = node;
-		node = node->next;
-		INSIST(node != NULL);
-	}
+	result = isc_ht_iter_next(it);
 
-	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--;
+	dresult = isc__ht_delete(ht, dnode->key, dnode->keysize, dnode->hashval,
+				 dindex);
+	INSIST(dresult == ISC_R_SUCCESS);
 
 	return (result);
 }
@@ -331,8 +613,8 @@ isc_ht_iter_current(isc_ht_iter_t *it, void **valuep) {
 }
 
 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);
@@ -341,9 +623,9 @@ isc_ht_iter_currentkey(isc_ht_iter_t *it, unsigned char **key, size_t *keysize)
 	*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);
 }
diff --git a/lib/isc/include/isc/ht.h b/lib/isc/include/isc/ht.h
index ea9eab7..843e671 100644
--- a/lib/isc/include/isc/ht.h
+++ b/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,30 +13,33 @@
 
 /* ! \file */
 
-#ifndef ISC_HT_H
-#define ISC_HT_H 1
+#pragma once
 
 #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, isc_uint8_t bits);
+void
+isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, isc_uint8_t bits,
+		unsigned int options);
 
 /*%
  * Destroy hashtable, freeing everything
@@ -51,6 +56,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
@@ -58,15 +64,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, isc_uint32_t keysize,
-		   void *value);
+isc_ht_add(isc_ht_t *ht, const unsigned char *key, const isc_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
@@ -74,20 +83,21 @@ isc_ht_add(isc_ht_t *ht, const unsigned char *key, isc_uint32_t keysize,
  */
 isc_result_t
 isc_ht_find(const isc_ht_t *ht, const unsigned char *key,
-	    isc_uint32_t keysize, void **valuep);
+	    const isc_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, isc_uint32_t keysize);
+isc_ht_delete(isc_ht_t *ht, const unsigned char *key, const isc_uint32_t keysize);
 
 /*%
  * Create an iterator for the hashtable; point '*itp' to it.
@@ -96,7 +106,7 @@ isc_ht_delete(isc_ht_t *ht, const unsigned char *key, isc_uint32_t keysize);
  *\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);
 
 /*%
@@ -176,6 +186,5 @@ isc_ht_iter_currentkey(isc_ht_iter_t *it, unsigned char **key, size_t *keysize);
  * 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);
diff --git a/lib/isc/tests/ht_test.c b/lib/isc/tests/ht_test.c
index 11ffb79..f1b2782 100644
--- a/lib/isc/tests/ht_test.c
+++ b/lib/isc/tests/ht_test.c
@@ -52,8 +52,7 @@ static void test_ht_full(int bits, uintptr_t count) {
 				  NULL, &mctx, 0);
 	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
 
-	result = isc_ht_init(&ht, mctx, bits);
-	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+	isc_ht_init(&ht, mctx, bits, ISC_HT_CASE_SENSITIVE);
 	ATF_REQUIRE(ht != NULL);
 
 	for (i = 1; i < count; i++) {
@@ -203,8 +202,7 @@ static void test_ht_iterator() {
 				  NULL, &mctx, 0);
 	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
 
-	result = isc_ht_init(&ht, mctx, 16);
-	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+	isc_ht_init(&ht, mctx, 16, ISC_HT_CASE_SENSITIVE);
 	ATF_REQUIRE(ht != NULL);
 	for (i = 1; i <= count; i++) {
 		/*
@@ -218,8 +216,7 @@ static void test_ht_iterator() {
 	}
 
 	walked = 0;
-	result = isc_ht_iter_create(ht, &iter);
-	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+	isc_ht_iter_create(ht, &iter);
 
 	for (result = isc_ht_iter_first(iter);
 	     result == ISC_R_SUCCESS;
@@ -303,6 +300,61 @@ static void test_ht_iterator() {
 	ATF_REQUIRE_EQ(ht, NULL);
 }
 
+static void test_ht_case() {
+
+	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);
+	ATF_REQUIRE_EQ(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);
+	ATF_REQUIRE(ht != NULL);
+
+	result = isc_ht_add(ht, lower, 16, (void *)lower);
+	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+
+	result = isc_ht_add(ht, same, 16, (void *)same);
+	ATF_REQUIRE_EQ(result, ISC_R_EXISTS);
+
+	result = isc_ht_add(ht, upper, 16, (void *)upper);
+	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+
+	result = isc_ht_find(ht, mixed, 16, &f);
+	ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND);
+	ATF_REQUIRE_EQ(f, NULL);
+
+	isc_ht_destroy(&ht);
+	ATF_REQUIRE_EQ(ht, NULL);
+
+	isc_ht_init(&ht, mctx, 8, ISC_HT_CASE_INSENSITIVE);
+	ATF_REQUIRE(ht != NULL);
+
+	result = isc_ht_add(ht, lower, 16, (void *)lower);
+	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+
+	result = isc_ht_add(ht, same, 16, (void *)same);
+	ATF_REQUIRE_EQ(result, ISC_R_EXISTS);
+
+	result = isc_ht_add(ht, upper, 16, (void *)upper);
+	ATF_REQUIRE_EQ(result, ISC_R_EXISTS);
+
+	result = isc_ht_find(ht, mixed, 16, &f);
+	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+	ATF_REQUIRE_EQ(f, &lower);
+
+	isc_ht_destroy(&ht);
+	ATF_REQUIRE_EQ(ht, NULL);
+}
+
 ATF_TC(isc_ht_20);
 ATF_TC_HEAD(isc_ht_20, tc) {
 	atf_tc_set_md_var(tc, "descr", "20 bit, 200K elements test");
@@ -357,6 +409,16 @@ ATF_TC_BODY(isc_ht_iterator, tc) {
 	test_ht_iterator();
 }
 
+ATF_TC(isc_ht_case);
+ATF_TC_HEAD(isc_ht_case, tc) {
+	atf_tc_set_md_var(tc, "descr", "case sensitivity test");
+}
+
+ATF_TC_BODY(isc_ht_case, tc) {
+	UNUSED(tc);
+	test_ht_case();
+}
+
 /*
  * Main
  */
@@ -366,5 +428,6 @@ ATF_TP_ADD_TCS(tp) {
 	ATF_TP_ADD_TC(tp, isc_ht_1);
 /*	ATF_TP_ADD_TC(tp, isc_ht_32); */
 	ATF_TP_ADD_TC(tp, isc_ht_iterator);
+	ATF_TP_ADD_TC(tp, isc_ht_case);
 	return (atf_no_error());
 }
-- 
2.45.0