diff -Nru bind9-9.16.13/bin/dig/dighost.c bind9-9.16.15/bin/dig/dighost.c
--- bind9-9.16.13/bin/dig/dighost.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/dig/dighost.c	2021-04-19 15:45:50.000000000 +0200
@@ -2935,7 +2935,11 @@
 	}
 	isc_buffer_usedregion(&query->sendbuf, &r);
 	debug("sending a request");
-	TIME_NOW(&query->time_sent);
+	if (query->lookup->use_usec) {
+		TIME_NOW_HIRES(&query->time_sent);
+	} else {
+		TIME_NOW(&query->time_sent);
+	}
 	INSIST(query->sock != NULL);
 	query->waiting_senddone = true;
 	sevent = isc_socket_socketevent(
@@ -3075,7 +3079,8 @@
  */
 static void
 requeue_or_update_exitcode(dig_lookup_t *lookup) {
-	if (lookup->eoferr == 0U) {
+	if (lookup->eoferr == 0U && lookup->retries > 1) {
+		--lookup->retries;
 		/*
 		 * Peer closed the connection prematurely for the first time
 		 * for this lookup.  Try again, keeping track of this failure.
@@ -3217,7 +3222,11 @@
 	debug("recvcount=%d", recvcount);
 	if (!query->first_soa_rcvd) {
 		debug("sending a request in launch_next_query");
-		TIME_NOW(&query->time_sent);
+		if (query->lookup->use_usec) {
+			TIME_NOW_HIRES(&query->time_sent);
+		} else {
+			TIME_NOW(&query->time_sent);
+		}
 		query->waiting_senddone = true;
 		isc_buffer_clear(&query->tmpsendbuf);
 		isc_buffer_putuint16(&query->tmpsendbuf,
@@ -3623,7 +3632,11 @@
 	INSIST(recvcount >= 0);
 
 	query = event->ev_arg;
-	TIME_NOW(&query->time_recv);
+	if (query->lookup->use_usec) {
+		TIME_NOW_HIRES(&query->time_recv);
+	} else {
+		TIME_NOW(&query->time_recv);
+	}
 
 	l = query->lookup;
 
diff -Nru bind9-9.16.13/bin/dig/dig.rst bind9-9.16.15/bin/dig/dig.rst
--- bind9-9.16.13/bin/dig/dig.rst	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/dig/dig.rst	2021-04-19 15:45:50.000000000 +0200
@@ -449,8 +449,8 @@
    ``+nssearch`` or ``+trace`` query option is used.
 
 ``+retry=T``
-   This option sets the number of times to retry UDP queries to server to ``T``
-   instead of the default, 2. Unlike ``+tries``, this does not include
+   This option sets the number of times to retry UDP and TCP queries to server to ``T``
+   instead of the default, 2.  Unlike ``+tries``, this does not include
    the initial query.
 
 ``+[no]rrcomments``
@@ -532,7 +532,7 @@
    default queries from a name server.
 
 ``+tries=T``
-   This option sets the number of times to try UDP queries to server to ``T``
+   This option sets the number of times to try UDP and TCP queries to server to ``T``
    instead of the default, 3. If ``T`` is less than or equal to zero,
    the number of tries is silently rounded up to 1.
 
diff -Nru bind9-9.16.13/bin/dig/host.c bind9-9.16.15/bin/dig/host.c
--- bind9-9.16.13/bin/dig/host.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/dig/host.c	2021-04-19 15:45:50.000000000 +0200
@@ -148,7 +148,11 @@
 	if (!short_form) {
 		char fromtext[ISC_SOCKADDR_FORMATSIZE];
 		isc_sockaddr_format(from, fromtext, sizeof(fromtext));
-		TIME_NOW(&now);
+		if (query->lookup->use_usec) {
+			TIME_NOW_HIRES(&now);
+		} else {
+			TIME_NOW(&now);
+		}
 		diff = (int)isc_time_microdiff(&now, &query->time_sent);
 		printf("Received %u bytes from %s in %d ms\n", bytes, fromtext,
 		       diff / 1000);
diff -Nru bind9-9.16.13/bin/named/config.c bind9-9.16.15/bin/named/config.c
--- bind9-9.16.13/bin/named/config.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/named/config.c	2021-04-19 15:45:50.000000000 +0200
@@ -195,7 +195,7 @@
 	servfail-ttl 1;\n\
 #	sortlist <none>\n\
 	stale-answer-enable false;\n\
-	stale-answer-client-timeout 1800; /* in milliseconds */\n\
+	stale-answer-client-timeout off;\n\
 	stale-answer-ttl 30; /* 30 seconds */\n\
 	stale-cache-enable true;\n\
 	stale-refresh-time 30; /* 30 seconds */\n\
diff -Nru bind9-9.16.13/bin/named/server.c bind9-9.16.15/bin/named/server.c
--- bind9-9.16.13/bin/named/server.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/named/server.c	2021-04-19 15:45:50.000000000 +0200
@@ -166,7 +166,18 @@
 #define EXCLBUFFERS	       4096
 #endif /* TUNE_LARGE */
 
-#define MAX_TCP_TIMEOUT 65535
+/* RFC7828 defines timeout as 16-bit value specified in units of 100
+ * milliseconds, so the maximum and minimum advertised and keepalive
+ * timeouts are capped by the data type (it's ~109 minutes)
+ */
+#define MIN_INITIAL_TIMEOUT    UINT32_C(2500)	/* 2.5 seconds */
+#define MAX_INITIAL_TIMEOUT    UINT32_C(120000) /* 2 minutes */
+#define MIN_IDLE_TIMEOUT       UINT32_C(100)	/* 0.1 seconds */
+#define MAX_IDLE_TIMEOUT       UINT32_C(120000) /* 2 minutes */
+#define MIN_KEEPALIVE_TIMEOUT  UINT32_C(100)	/* 0.1 seconds */
+#define MAX_KEEPALIVE_TIMEOUT  UINT32_C(UINT16_MAX * 100)
+#define MIN_ADVERTISED_TIMEOUT UINT32_C(0) /* No minimum */
+#define MAX_ADVERTISED_TIMEOUT UINT32_C(UINT16_MAX * 100)
 
 /*%
  * Check an operation for failure.  Assumes that the function
@@ -8315,7 +8326,7 @@
 	unsigned int maxsocks;
 	uint32_t softquota = 0;
 	uint32_t max;
-	unsigned int initial, idle, keepalive, advertised;
+	uint64_t initial, idle, keepalive, advertised;
 	dns_aclenv_t *env =
 		ns_interfacemgr_getaclenv(named_g_server->interfacemgr);
 
@@ -8572,62 +8583,67 @@
 	obj = NULL;
 	result = named_config_get(maps, "tcp-initial-timeout", &obj);
 	INSIST(result == ISC_R_SUCCESS);
-	initial = cfg_obj_asuint32(obj);
-	if (initial > 1200) {
+	initial = cfg_obj_asuint32(obj) * 100;
+	if (initial > MAX_INITIAL_TIMEOUT) {
 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
 			    "tcp-initial-timeout value is out of range: "
-			    "lowering to 1200");
-		initial = 1200;
-	} else if (initial < 25) {
+			    "lowering to %" PRIu32,
+			    MAX_INITIAL_TIMEOUT / 100);
+		initial = MAX_INITIAL_TIMEOUT;
+	} else if (initial < MIN_INITIAL_TIMEOUT) {
 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
 			    "tcp-initial-timeout value is out of range: "
-			    "raising to 25");
-		initial = 25;
+			    "raising to %" PRIu32,
+			    MIN_INITIAL_TIMEOUT / 100);
+		initial = MIN_INITIAL_TIMEOUT;
 	}
 
 	obj = NULL;
 	result = named_config_get(maps, "tcp-idle-timeout", &obj);
 	INSIST(result == ISC_R_SUCCESS);
-	idle = cfg_obj_asuint32(obj);
-	if (idle > 1200) {
+	idle = cfg_obj_asuint32(obj) * 100;
+	if (idle > MAX_IDLE_TIMEOUT) {
 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
 			    "tcp-idle-timeout value is out of range: "
-			    "lowering to 1200");
-		idle = 1200;
-	} else if (idle < 1) {
+			    "lowering to %" PRIu32,
+			    MAX_IDLE_TIMEOUT / 100);
+		idle = MAX_IDLE_TIMEOUT;
+	} else if (idle < MIN_IDLE_TIMEOUT) {
 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
 			    "tcp-idle-timeout value is out of range: "
-			    "raising to 1");
-		idle = 1;
+			    "raising to %" PRIu32,
+			    MIN_IDLE_TIMEOUT / 100);
+		idle = MIN_IDLE_TIMEOUT;
 	}
 
 	obj = NULL;
 	result = named_config_get(maps, "tcp-keepalive-timeout", &obj);
 	INSIST(result == ISC_R_SUCCESS);
-	keepalive = cfg_obj_asuint32(obj);
-	if (keepalive > MAX_TCP_TIMEOUT) {
+	keepalive = cfg_obj_asuint32(obj) * 100;
+	if (keepalive > MAX_KEEPALIVE_TIMEOUT) {
 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
 			    "tcp-keepalive-timeout value is out of range: "
-			    "lowering to %u",
-			    MAX_TCP_TIMEOUT);
-		keepalive = MAX_TCP_TIMEOUT;
-	} else if (keepalive < 1) {
+			    "lowering to %" PRIu32,
+			    MAX_KEEPALIVE_TIMEOUT / 100);
+		keepalive = MAX_KEEPALIVE_TIMEOUT;
+	} else if (keepalive < MIN_KEEPALIVE_TIMEOUT) {
 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
 			    "tcp-keepalive-timeout value is out of range: "
-			    "raising to 1");
-		keepalive = 1;
+			    "raising to %" PRIu32,
+			    MIN_KEEPALIVE_TIMEOUT / 100);
+		keepalive = MIN_KEEPALIVE_TIMEOUT;
 	}
 
 	obj = NULL;
 	result = named_config_get(maps, "tcp-advertised-timeout", &obj);
 	INSIST(result == ISC_R_SUCCESS);
-	advertised = cfg_obj_asuint32(obj);
-	if (advertised > MAX_TCP_TIMEOUT) {
+	advertised = cfg_obj_asuint32(obj) * 100;
+	if (advertised > MAX_ADVERTISED_TIMEOUT) {
 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
 			    "tcp-advertized-timeout value is out of range: "
-			    "lowering to %u",
-			    MAX_TCP_TIMEOUT);
-		advertised = MAX_TCP_TIMEOUT;
+			    "lowering to %" PRIu32,
+			    MAX_ADVERTISED_TIMEOUT / 100);
+		advertised = MAX_ADVERTISED_TIMEOUT;
 	}
 
 	isc_nm_settimeouts(named_g_nm, initial, idle, keepalive, advertised);
@@ -14755,6 +14771,12 @@
 
 		switch (result) {
 		case ISC_R_SUCCESS:
+			/*
+			 * Rekey after checkds command because the next key
+			 * event may have changed.
+			 */
+			dns_zone_rekey(zone, false);
+
 			if (use_keyid) {
 				char tagbuf[6];
 				snprintf(tagbuf, sizeof(tagbuf), "%u", keyid);
@@ -14799,6 +14821,12 @@
 
 		switch (result) {
 		case ISC_R_SUCCESS:
+			/*
+			 * Rekey after rollover command because the next key
+			 * event may have changed.
+			 */
+			dns_zone_rekey(zone, false);
+
 			if (use_keyid) {
 				char tagbuf[6];
 				snprintf(tagbuf, sizeof(tagbuf), "%u", keyid);
@@ -15895,7 +15923,7 @@
 named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
 	char *ptr;
 	isc_result_t result = ISC_R_SUCCESS;
-	unsigned int initial, idle, keepalive, advertised;
+	uint32_t initial, idle, keepalive, advertised;
 	char msg[128];
 
 	/* Skip the command name. */
@@ -15911,10 +15939,11 @@
 	ptr = next_token(lex, NULL);
 	if (ptr != NULL) {
 		CHECK(isc_parse_uint32(&initial, ptr, 10));
-		if (initial > 1200) {
+		initial *= 100;
+		if (initial > MAX_INITIAL_TIMEOUT) {
 			CHECK(ISC_R_RANGE);
 		}
-		if (initial < 25) {
+		if (initial < MIN_INITIAL_TIMEOUT) {
 			CHECK(ISC_R_RANGE);
 		}
 
@@ -15923,10 +15952,11 @@
 			return (ISC_R_UNEXPECTEDEND);
 		}
 		CHECK(isc_parse_uint32(&idle, ptr, 10));
-		if (idle > 1200) {
+		idle *= 100;
+		if (idle > MAX_IDLE_TIMEOUT) {
 			CHECK(ISC_R_RANGE);
 		}
-		if (idle < 1) {
+		if (idle < MIN_IDLE_TIMEOUT) {
 			CHECK(ISC_R_RANGE);
 		}
 
@@ -15935,10 +15965,11 @@
 			return (ISC_R_UNEXPECTEDEND);
 		}
 		CHECK(isc_parse_uint32(&keepalive, ptr, 10));
-		if (keepalive > MAX_TCP_TIMEOUT) {
+		keepalive *= 100;
+		if (keepalive > MAX_KEEPALIVE_TIMEOUT) {
 			CHECK(ISC_R_RANGE);
 		}
-		if (keepalive < 1) {
+		if (keepalive < MIN_KEEPALIVE_TIMEOUT) {
 			CHECK(ISC_R_RANGE);
 		}
 
@@ -15947,7 +15978,8 @@
 			return (ISC_R_UNEXPECTEDEND);
 		}
 		CHECK(isc_parse_uint32(&advertised, ptr, 10));
-		if (advertised > MAX_TCP_TIMEOUT) {
+		advertised *= 100;
+		if (advertised > MAX_ADVERTISED_TIMEOUT) {
 			CHECK(ISC_R_RANGE);
 		}
 
@@ -15960,13 +15992,15 @@
 		isc_task_endexclusive(named_g_server->task);
 	}
 
-	snprintf(msg, sizeof(msg), "tcp-initial-timeout=%u\n", initial);
+	snprintf(msg, sizeof(msg), "tcp-initial-timeout=%u\n", initial / 100);
 	CHECK(putstr(text, msg));
-	snprintf(msg, sizeof(msg), "tcp-idle-timeout=%u\n", idle);
+	snprintf(msg, sizeof(msg), "tcp-idle-timeout=%u\n", idle / 100);
 	CHECK(putstr(text, msg));
-	snprintf(msg, sizeof(msg), "tcp-keepalive-timeout=%u\n", keepalive);
+	snprintf(msg, sizeof(msg), "tcp-keepalive-timeout=%u\n",
+		 keepalive / 100);
 	CHECK(putstr(text, msg));
-	snprintf(msg, sizeof(msg), "tcp-advertised-timeout=%u", advertised);
+	snprintf(msg, sizeof(msg), "tcp-advertised-timeout=%u",
+		 advertised / 100);
 	CHECK(putstr(text, msg));
 
 cleanup:
diff -Nru bind9-9.16.13/bin/tests/fromhex.pl bind9-9.16.15/bin/tests/fromhex.pl
--- bind9-9.16.13/bin/tests/fromhex.pl	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/fromhex.pl	1970-01-01 01:00:00.000000000 +0100
@@ -1,45 +0,0 @@
-#!/usr/bin/perl
-#
-# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-#
-# 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 https://mozilla.org/MPL/2.0/.
-#
-# See the COPYRIGHT file distributed with this work for additional
-# information regarding copyright ownership.
-
-# Converts hex ascii into raw data.
-# (This can be used, for example, to construct input for "wire_data -d".)
-
-require 5.006.001;
-
-use strict;
-use IO::File;
-
-sub usage {
-    print ("Usage: packet.pl [file]\n");
-    exit 1;
-}
-
-my $file = "STDIN";
-if (@ARGV >= 1) {
-    my $filename = shift @ARGV;
-    open FH, "<$filename" or die "$filename: $!";
-    $file = "FH";
-}
-
-my $input = "";
-while (defined(my $line = <$file>) ) {
-    chomp $line;
-    $line =~ s/#.*$//;
-    $input .= $line;
-}
-
-$input =~ s/\s+//g;
-my $data = pack("H*", $input);
-my $len = length $data;
-
-binmode(STDOUT);
-print($data);
-exit(0);
diff -Nru bind9-9.16.13/bin/tests/system/checkzone/zones/good-cds-unsigned.db bind9-9.16.15/bin/tests/system/checkzone/zones/good-cds-unsigned.db
--- bind9-9.16.13/bin/tests/system/checkzone/zones/good-cds-unsigned.db	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/checkzone/zones/good-cds-unsigned.db	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,5 @@
+example.	0	SOA	. . 0 0 0 0 0
+example.	0	NS	.
+example.	0	CDS	0 0 0 00
+example.	0	CDNSKEY	0 3 0 AA==
+
diff -Nru bind9-9.16.13/bin/tests/system/conf.sh.common bind9-9.16.15/bin/tests/system/conf.sh.common
--- bind9-9.16.13/bin/tests/system/conf.sh.common	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/conf.sh.common	2021-04-19 15:45:50.000000000 +0200
@@ -91,6 +91,7 @@
 journal \
 kasp \
 keepalive \
+keymgr2kasp \
 legacy \
 limits \
 masterfile \
@@ -129,6 +130,7 @@
 statschannel \
 stub \
 synthfromdnssec \
+timeouts \
 tcp \
 tools \
 tsig \
diff -Nru bind9-9.16.13/bin/tests/system/digdelv/tests.sh bind9-9.16.15/bin/tests/system/digdelv/tests.sh
--- bind9-9.16.13/bin/tests/system/digdelv/tests.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/digdelv/tests.sh	2021-04-19 15:45:50.000000000 +0200
@@ -802,7 +802,7 @@
   echo_i "checking exit code for a retry upon TCP EOF (immediate -> immediate) ($n)"
   ret=0
   echo "no_response no_response" | sendcmd 10.53.0.5
-  dig_with_opts @10.53.0.5 example AXFR +tries=1 > dig.out.test$n 2>&1 && ret=1
+  dig_with_opts @10.53.0.5 example AXFR +tries=2 > dig.out.test$n 2>&1 && ret=1
   # Sanity check: ensure ans5 behaves as expected.
   [ `grep "communications error.*end of file" dig.out.test$n | wc -l` -eq 2 ] || ret=1
   if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -812,7 +812,7 @@
   echo_i "checking exit code for a retry upon TCP EOF (partial AXFR -> partial AXFR) ($n)"
   ret=0
   echo "partial_axfr partial_axfr" | sendcmd 10.53.0.5
-  dig_with_opts @10.53.0.5 example AXFR +tries=1 > dig.out.test$n 2>&1 && ret=1
+  dig_with_opts @10.53.0.5 example AXFR +tries=2 > dig.out.test$n 2>&1 && ret=1
   # Sanity check: ensure ans5 behaves as expected.
   [ `grep "communications error.*end of file" dig.out.test$n | wc -l` -eq 2 ] || ret=1
   if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -822,7 +822,7 @@
   echo_i "checking exit code for a retry upon TCP EOF (immediate -> partial AXFR) ($n)"
   ret=0
   echo "no_response partial_axfr" | sendcmd 10.53.0.5
-  dig_with_opts @10.53.0.5 example AXFR +tries=1 > dig.out.test$n 2>&1 && ret=1
+  dig_with_opts @10.53.0.5 example AXFR +tries=2 > dig.out.test$n 2>&1 && ret=1
   # Sanity check: ensure ans5 behaves as expected.
   [ `grep "communications error.*end of file" dig.out.test$n | wc -l` -eq 2 ] || ret=1
   if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -832,7 +832,7 @@
   echo_i "checking exit code for a retry upon TCP EOF (partial AXFR -> immediate) ($n)"
   ret=0
   echo "partial_axfr no_response" | sendcmd 10.53.0.5
-  dig_with_opts @10.53.0.5 example AXFR +tries=1 > dig.out.test$n 2>&1 && ret=1
+  dig_with_opts @10.53.0.5 example AXFR +tries=2 > dig.out.test$n 2>&1 && ret=1
   # Sanity check: ensure ans5 behaves as expected.
   [ `grep "communications error.*end of file" dig.out.test$n | wc -l` -eq 2 ] || ret=1
   if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -842,7 +842,7 @@
   echo_i "checking exit code for a retry upon TCP EOF (immediate -> complete AXFR) ($n)"
   ret=0
   echo "no_response complete_axfr" | sendcmd 10.53.0.5
-  dig_with_opts @10.53.0.5 example AXFR +tries=1 > dig.out.test$n 2>&1 || ret=1
+  dig_with_opts @10.53.0.5 example AXFR +tries=2 > dig.out.test$n 2>&1 || ret=1
   # Sanity check: ensure ans5 behaves as expected.
   [ `grep "communications error.*end of file" dig.out.test$n | wc -l` -eq 1 ] || ret=1
   if [ $ret -ne 0 ]; then echo_i "failed"; fi
@@ -852,7 +852,26 @@
   echo_i "checking exit code for a retry upon TCP EOF (partial AXFR -> complete AXFR) ($n)"
   ret=0
   echo "partial_axfr complete_axfr" | sendcmd 10.53.0.5
-  dig_with_opts @10.53.0.5 example AXFR +tries=1 > dig.out.test$n 2>&1 || ret=1
+  dig_with_opts @10.53.0.5 example AXFR +tries=2 > dig.out.test$n 2>&1 || ret=1
+  # Sanity check: ensure ans5 behaves as expected.
+  [ `grep "communications error.*end of file" dig.out.test$n | wc -l` -eq 1 ] || ret=1
+  if [ $ret -ne 0 ]; then echo_i "failed"; fi
+  status=$((status+ret))
+
+  n=$((n+1))
+  echo_i "checking +tries=1 won't retry twice upon TCP EOF ($n)"
+  ret=0
+  echo "no_response no_response" | sendcmd 10.53.0.5
+  dig_with_opts @10.53.0.5 example AXFR +tries=1 > dig.out.test$n 2>&1 && ret=1
+  # Sanity check: ensure ans5 behaves as expected.
+  [ `grep "communications error.*end of file" dig.out.test$n | wc -l` -eq 1 ] || ret=1
+  if [ $ret -ne 0 ]; then echo_i "failed"; fi
+  status=$((status+ret))
+
+  n=$((n+1))
+  echo_i "checking +retry=0 won't retry twice upon TCP EOF ($n)"
+  ret=0
+  dig_with_opts @10.53.0.5 example AXFR +retry=0 > dig.out.test$n 2>&1 && ret=1
   # Sanity check: ensure ans5 behaves as expected.
   [ `grep "communications error.*end of file" dig.out.test$n | wc -l` -eq 1 ] || ret=1
   if [ $ret -ne 0 ]; then echo_i "failed"; fi
diff -Nru bind9-9.16.13/bin/tests/system/fromhex.pl bind9-9.16.15/bin/tests/system/fromhex.pl
--- bind9-9.16.13/bin/tests/system/fromhex.pl	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/fromhex.pl	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,45 @@
+#!/usr/bin/perl
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+# Converts hex ascii into raw data.
+# (This can be used, for example, to construct input for "wire_data -d".)
+
+require 5.006.001;
+
+use strict;
+use IO::File;
+
+sub usage {
+    print ("Usage: packet.pl [file]\n");
+    exit 1;
+}
+
+my $file = "STDIN";
+if (@ARGV >= 1) {
+    my $filename = shift @ARGV;
+    open FH, "<$filename" or die "$filename: $!";
+    $file = "FH";
+}
+
+my $input = "";
+while (defined(my $line = <$file>) ) {
+    chomp $line;
+    $line =~ s/#.*$//;
+    $input .= $line;
+}
+
+$input =~ s/\s+//g;
+my $data = pack("H*", $input);
+my $len = length $data;
+
+binmode(STDOUT);
+print($data);
+exit(0);
diff -Nru bind9-9.16.13/bin/tests/system/journal/clean.sh bind9-9.16.15/bin/tests/system/journal/clean.sh
--- bind9-9.16.13/bin/tests/system/journal/clean.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/journal/clean.sh	2021-04-19 15:45:50.000000000 +0200
@@ -7,8 +7,10 @@
 # See the COPYRIGHT file distributed with this work for additional
 # information regarding copyright ownership.
 
+rm -f */*.db */*.jnl
+rm -f */named.conf
 rm -f */named.memstats
 rm -f */named.run
-rm -f */named.conf
-rm -f */*.db */*.jnl
 rm -f dig.out*
+rm -f journalprint.out.*
+rm -f ns1/managed-keys.bind
diff -Nru bind9-9.16.13/bin/tests/system/journal/ns1/managed-keys.bind.in bind9-9.16.15/bin/tests/system/journal/ns1/managed-keys.bind.in
--- bind9-9.16.13/bin/tests/system/journal/ns1/managed-keys.bind.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/journal/ns1/managed-keys.bind.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,2 @@
+.			0	IN	SOA	. . 3297 0 0 0 0
+.			0	IN	TYPE65533 \# 276 60621140598E0A83000000000101030803010001ACFFB409BCC939F8 31F7A1E5EC88F7A59255EC53040BE432027390A4CE896D6F9086F3C5 E177FBFE118163AAEC7AF1462C47945944C4E2C026BE5E98BBCDED25 978272E1E3E079C5094D573F0E83C92F02B32D3513B1550B826929C8 0DD0F92CAC966D17769FD5867B647C3F38029ABDC48152EB8F207159 ECC5D232C7C1537C79F4B7AC28FF11682F21681BF6D6ABA555032BF6 F9F036BEB2AAA5B3778D6EEBFBA6BF9EA191BE4AB0CAEA759E2F773A 1F9029C73ECB8D5735B9321DB085F1B8E2D8038FE2941992548CEE0D 67DD4547E11DD63AF9C9FC1C5466FB684CF009D7197C2CF79E792AB5 01E6A8A1CA519AF2CB9B5F6367E94C0D47502451357BE1B5
diff -Nru bind9-9.16.13/bin/tests/system/journal/ns1/managed-keys.bind.jnl.in bind9-9.16.15/bin/tests/system/journal/ns1/managed-keys.bind.jnl.in
--- bind9-9.16.13/bin/tests/system/journal/ns1/managed-keys.bind.jnl.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/journal/ns1/managed-keys.bind.jnl.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,704 @@
+3b42494e44204c4f472056390a000000
+00000cd20000020000000ce200002bf8
+00000038000000000000000000000000
+00000000000000000000000000000000
+00000cd20000020000000cd30000049c
+00000cd40000073800000cd5000009d8
+00000cd600000c7800000cd700000f18
+00000cd8000011b800000cd900001458
+00000cda000016f800000cdb00001998
+00000cdc00001c3800000cdd00001ed8
+00000cde0000217800000cdf00002418
+00000ce0000026b800000ce100002958
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+00000000000000000000000000000000
+0000029000000cd200000cd300000021
+0000060001000000000016000000000c
+d2000000000000000000000000000000
+000000011f00fffd0001000000000114
+6058a4c2598e0a830000000001010308
+03010001acffb409bcc939f831f7a1e5
+ec88f7a59255ec53040be432027390a4
+ce896d6f9086f3c5e177fbfe118163aa
+ec7af1462c47945944c4e2c026be5e98
+bbcded25978272e1e3e079c5094d573f
+0e83c92f02b32d3513b1550b826929c8
+0dd0f92cac966d17769fd5867b647c3f
+38029abdc48152eb8f207159ecc5d232
+c7c1537c79f4b7ac28ff11682f21681b
+f6d6aba555032bf6f9f036beb2aaa5b3
+778d6eebfba6bf9ea191be4ab0caea75
+9e2f773a1f9029c73ecb8d5735b9321d
+b085f1b8e2d8038fe2941992548cee0d
+67dd4547e11dd63af9c9fc1c5466fb68
+4cf009d7197c2cf79e792ab501e6a8a1
+ca519af2cb9b5f6367e94c0d47502451
+357be1b5000000210000060001000000
+000016000000000cd300000000000000
+0000000000000000000000011f00fffd
+00010000000001146058e4a0598e0a83
+000000000101030803010001acffb409
+bcc939f831f7a1e5ec88f7a59255ec53
+040be432027390a4ce896d6f9086f3c5
+e177fbfe118163aaec7af1462c479459
+44c4e2c026be5e98bbcded25978272e1
+e3e079c5094d573f0e83c92f02b32d35
+13b1550b826929c80dd0f92cac966d17
+769fd5867b647c3f38029abdc48152eb
+8f207159ecc5d232c7c1537c79f4b7ac
+28ff11682f21681bf6d6aba555032bf6
+f9f036beb2aaa5b3778d6eebfba6bf9e
+a191be4ab0caea759e2f773a1f9029c7
+3ecb8d5735b9321db085f1b8e2d8038f
+e2941992548cee0d67dd4547e11dd63a
+f9c9fc1c5466fb684cf009d7197c2cf7
+9e792ab501e6a8a1ca519af2cb9b5f63
+67e94c0d47502451357be1b500000290
+00000cd300000cd40000002100000600
+01000000000016000000000cd3000000
+00000000000000000000000000000001
+1f00fffd00010000000001146058e4a0
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cd40000000000000000000000
+00000000000000011f00fffd00010000
+000001146059f642598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cd4
+00000cd5000000000000002100000600
+01000000000016000000000cd4000000
+00000000000000000000000000000001
+1f00fffd00010000000001146059f642
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cd50000000000000000000000
+00000000000000011f00fffd00010000
+00000114605a283e598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cd5
+00000cd6000000000000002100000600
+01000000000016000000000cd5000000
+00000000000000000000000000000001
+1f00fffd0001000000000114605a283e
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cd60000000000000000000000
+00000000000000011f00fffd00010000
+00000114605b47c2598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cd6
+00000cd7000000000000002100000600
+01000000000016000000000cd6000000
+00000000000000000000000000000001
+1f00fffd0001000000000114605b47c2
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cd70000000000000000000000
+00000000000000011f00fffd00010000
+00000114605b79bf598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cd7
+00000cd8000000000000002100000600
+01000000000016000000000cd7000000
+00000000000000000000000000000001
+1f00fffd0001000000000114605b79bf
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cd80000000000000000000000
+00000000000000011f00fffd00010000
+00000114605c9943598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cd8
+00000cd9000000000000002100000600
+01000000000016000000000cd8000000
+00000000000000000000000000000001
+1f00fffd0001000000000114605c9943
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cd90000000000000000000000
+00000000000000011f00fffd00010000
+00000114605ccb40598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cd9
+00000cda000000000000002100000600
+01000000000016000000000cd9000000
+00000000000000000000000000000001
+1f00fffd0001000000000114605ccb40
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cda0000000000000000000000
+00000000000000011f00fffd00010000
+00000114605deac4598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cda
+00000cdb000000000000002100000600
+01000000000016000000000cda000000
+00000000000000000000000000000001
+1f00fffd0001000000000114605deac4
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cdb0000000000000000000000
+00000000000000011f00fffd00010000
+00000114605e1cc0598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cdb
+00000cdc000000000000002100000600
+01000000000016000000000cdb000000
+00000000000000000000000000000001
+1f00fffd0001000000000114605e1cc0
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cdc0000000000000000000000
+00000000000000011f00fffd00010000
+00000114605efb3a598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cdc
+00000cdd000000000000002100000600
+01000000000016000000000cdc000000
+00000000000000000000000000000001
+1f00fffd0001000000000114605efb3a
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cdd0000000000000000000000
+00000000000000011f00fffd00010000
+00000114605f6e40598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cdd
+00000cde000000000000002100000600
+01000000000016000000000cdd000000
+00000000000000000000000000000001
+1f00fffd0001000000000114605f6e40
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cde0000000000000000000000
+00000000000000011f00fffd00010000
+0000011460604cbb598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cde
+00000cdf000000000000002100000600
+01000000000016000000000cde000000
+00000000000000000000000000000001
+1f00fffd000100000000011460604cbb
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000cdf0000000000000000000000
+00000000000000011f00fffd00010000
+000001146060bfc0598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000cdf
+00000ce0000000000000002100000600
+01000000000016000000000cdf000000
+00000000000000000000000000000001
+1f00fffd00010000000001146060bfc0
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000ce00000000000000000000000
+00000000000000011f00fffd00010000
+0000011460619e3b598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000ce0
+00000ce1000000000000002100000600
+01000000000016000000000ce0000000
+00000000000000000000000000000001
+1f00fffd000100000000011460619e3b
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000ce10000000000000000000000
+00000000000000011f00fffd00010000
+0000011460621140598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b50000029000000ce1
+00000ce2000000000000002100000600
+01000000000016000000000ce1000000
+00000000000000000000000000000001
+1f00fffd000100000000011460621140
+598e0a83000000000101030803010001
+acffb409bcc939f831f7a1e5ec88f7a5
+9255ec53040be432027390a4ce896d6f
+9086f3c5e177fbfe118163aaec7af146
+2c47945944c4e2c026be5e98bbcded25
+978272e1e3e079c5094d573f0e83c92f
+02b32d3513b1550b826929c80dd0f92c
+ac966d17769fd5867b647c3f38029abd
+c48152eb8f207159ecc5d232c7c1537c
+79f4b7ac28ff11682f21681bf6d6aba5
+55032bf6f9f036beb2aaa5b3778d6eeb
+fba6bf9ea191be4ab0caea759e2f773a
+1f9029c73ecb8d5735b9321db085f1b8
+e2d8038fe2941992548cee0d67dd4547
+e11dd63af9c9fc1c5466fb684cf009d7
+197c2cf79e792ab501e6a8a1ca519af2
+cb9b5f6367e94c0d47502451357be1b5
+00000021000006000100000000001600
+0000000ce20000000000000000000000
+00000000000000011f00fffd00010000
+000001146062efbb598e0a8300000000
+0101030803010001acffb409bcc939f8
+31f7a1e5ec88f7a59255ec53040be432
+027390a4ce896d6f9086f3c5e177fbfe
+118163aaec7af1462c47945944c4e2c0
+26be5e98bbcded25978272e1e3e079c5
+094d573f0e83c92f02b32d3513b1550b
+826929c80dd0f92cac966d17769fd586
+7b647c3f38029abdc48152eb8f207159
+ecc5d232c7c1537c79f4b7ac28ff1168
+2f21681bf6d6aba555032bf6f9f036be
+b2aaa5b3778d6eebfba6bf9ea191be4a
+b0caea759e2f773a1f9029c73ecb8d57
+35b9321db085f1b8e2d8038fe2941992
+548cee0d67dd4547e11dd63af9c9fc1c
+5466fb684cf009d7197c2cf79e792ab5
+01e6a8a1ca519af2cb9b5f6367e94c0d
+47502451357be1b5
diff -Nru bind9-9.16.13/bin/tests/system/journal/setup.sh bind9-9.16.15/bin/tests/system/journal/setup.sh
--- bind9-9.16.13/bin/tests/system/journal/setup.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/journal/setup.sh	2021-04-19 15:45:50.000000000 +0200
@@ -40,3 +40,6 @@
 
 cp ns1/generic.db.in ns1/maxjournal2.db
 cp ns1/maxjournal2.jnl.saved ns1/maxjournal2.db.jnl
+
+cp ns1/managed-keys.bind.in ns1/managed-keys.bind
+$PERL ../fromhex.pl < ns1/managed-keys.bind.jnl.in > ns1/managed-keys.bind.jnl
diff -Nru bind9-9.16.13/bin/tests/system/journal/tests.sh bind9-9.16.15/bin/tests/system/journal/tests.sh
--- bind9-9.16.13/bin/tests/system/journal/tests.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/journal/tests.sh	2021-04-19 15:45:50.000000000 +0200
@@ -28,7 +28,7 @@
 dig_with_opts changed soa > dig.out.test$n
 grep 'status: NOERROR' dig.out.test$n > /dev/null || ret=1
 grep '2012010902' dig.out.test$n > /dev/null || ret=1
-grep 'zone changed/IN: retried using old journal format' ns1/named.run > /dev/null || ret=1
+grep 'zone changed/IN: journal rollforward completed successfully using old journal format' ns1/named.run > /dev/null || ret=1
 [ $ret -eq 0 ] || echo_i "failed"
 status=`expr $status + $ret`
 
@@ -62,7 +62,8 @@
 dig_with_opts changed2 soa > dig.out.test$n
 grep 'status: NOERROR' dig.out.test$n > /dev/null || ret=1
 grep '2012010902' dig.out.test$n > /dev/null || ret=1
-grep 'zone changed2/IN: retried using old journal format' ns1/named.run > /dev/null && ret=1
+grep 'zone changed2/IN: journal rollforward completed successfully: success' ns1/named.run > /dev/null || ret=1
+grep 'zone changed2/IN: journal rollforward completed successfully using old journal format' ns1/named.run > /dev/null && ret=1
 [ $ret -eq 0 ] || echo_i "failed"
 status=`expr $status + $ret`
 
@@ -72,7 +73,8 @@
 dig_with_opts unchanged2 soa > dig.out.test$n
 grep 'status: NOERROR' dig.out.test$n > /dev/null || ret=1
 grep '2012010901' dig.out.test$n > /dev/null || ret=1
-grep 'zone unchanged2/IN: retried using old journal format' ns1/named.run > /dev/null && ret=1
+grep 'zone unchanged2/IN: journal rollforward completed successfully' ns1/named.run > /dev/null && ret=1
+grep 'zone unchanged2/IN: journal rollforward completed successfully using old journal format' ns1/named.run > /dev/null && ret=1
 [ $ret -eq 0 ] || echo_i "failed"
 status=`expr $status + $ret`
 
@@ -90,7 +92,7 @@
 dig_with_opts -t soa ixfr > dig.out.test$n
 grep 'status: NOERROR' dig.out.test$n > /dev/null || ret=1
 grep '2012010902' dig.out.test$n > /dev/null || ret=1
-grep 'zone ixfr/IN: journal rollforward completed successfully: recoverable' ns1/named.run > /dev/null || ret=1
+grep 'zone ixfr/IN: journal rollforward completed successfully using old journal format: up to date' ns1/named.run > /dev/null || ret=1
 [ $ret -eq 0 ] || echo_i "failed"
 status=`expr $status + $ret`
 
@@ -107,7 +109,9 @@
 dig_with_opts -t soa hdr1d1d2d1d2 > dig.out.test$n
 grep 'status: NOERROR' dig.out.test$n > /dev/null || ret=1
 grep '2012010905' dig.out.test$n > /dev/null || ret=1
-grep 'zone hdr1d1d2d1d2/IN: journal rollforward completed successfully: recoverable' ns1/named.run > /dev/null || ret=1
+grep 'zone hdr1d1d2d1d2/IN: journal rollforward completed successfully using old journal format: success' ns1/named.run > /dev/null || ret=1
+grep 'zone_journal_compact: zone hdr1d1d2d1d2/IN: repair full journal' ns1/named.run > /dev/null || ret=1
+grep 'hdr1d1d2d1d2/IN: dns_journal_compact: success' ns1/named.run > /dev/null || ret=1
 [ $ret -eq 0 ] || echo_i "failed"
 status=`expr $status + $ret`
 
@@ -127,7 +131,9 @@
 dig_with_opts -t soa hdr1d2d1d2d1 > dig.out.test$n
 grep 'status: NOERROR' dig.out.test$n > /dev/null || ret=1
 grep '2012010905' dig.out.test$n > /dev/null || ret=1
-grep 'zone hdr1d2d1d2d1/IN: journal rollforward completed successfully: recoverable' ns1/named.run > /dev/null || ret=1
+grep 'zone hdr1d2d1d2d1/IN: journal rollforward completed successfully using old journal format: success' ns1/named.run > /dev/null || ret=1
+grep 'zone_journal_compact: zone hdr1d2d1d2d1/IN: repair full journal' ns1/named.run > /dev/null || ret=1
+grep 'zone hdr1d2d1d2d1/IN: dns_journal_compact: success' ns1/named.run > /dev/null || ret=1
 [ $ret -eq 0 ] || echo_i "failed"
 status=`expr $status + $ret`
 
@@ -151,6 +157,15 @@
 status=`expr $status + $ret`
 
 n=`expr $n + 1`
+echo_i "check upgrade of managed-keys.bind.jnl succeeded($n)"
+ret=0
+$JOURNALPRINT ns1/managed-keys.bind.jnl > journalprint.out.test$n
+lines=$(awk '$1 == "add" && $5 == "SOA" && $8 == "3297" { print }' journalprint.out.test$n | wc -l)
+test $lines -eq 1 || ret=1
+[ $ret -eq 0 ] || echo_i "failed"
+status=`expr $status + $ret`
+
+n=`expr $n + 1`
 echo_i "check journal downgrade/upgrade ($n)"
 ret=0
 cp ns1/changed.db.jnl ns1/temp.jnl
@@ -166,10 +181,15 @@
 n=`expr $n + 1`
 echo_i "check max-journal-size works after journal update ($n)"
 ret=0
-# a dump should have been triggered by repairing the journal,
-# which would have resulted in the journal already being
-# compacted.
-[ $(wc -c < ns1/maxjournal.db.jnl) -lt 4000 ] || ret=1
+# journal was repaired, it should still be big
+[ $(wc -c < ns1/maxjournal.db.jnl) -gt 12000 ] || ret=1
+# the zone hasn't been dumped yet, so 'rndc sync' should work without
+# needing a zone update first.
+rndc_with_opts 10.53.0.1 sync maxjournal
+check_size() (
+    [ $(wc -c < ns1/maxjournal.db.jnl) -lt 4000 ]
+)
+retry_quiet 10 check_size || ret=1
 [ $ret -eq 0 ] || echo_i "failed"
 status=`expr $status + $ret`
 
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns3/template2.db.in bind9-9.16.15/bin/tests/system/kasp/ns3/template2.db.in
--- bind9-9.16.13/bin/tests/system/kasp/ns3/template2.db.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns3/template2.db.in	2021-04-19 15:45:50.000000000 +0200
@@ -20,6 +20,6 @@
 ns3			A	10.53.0.3
 
 a			A	10.0.0.11
-b			A	10.0.0.2
-c			A	10.0.0.3
-d			A	10.0.0.4
+b			A	10.0.0.22
+c			A	10.0.0.33
+d			A	10.0.0.44
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns6/named2.conf.in bind9-9.16.15/bin/tests/system/kasp/ns6/named2.conf.in
--- bind9-9.16.13/bin/tests/system/kasp/ns6/named2.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns6/named2.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -35,28 +35,6 @@
 	inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
 };
 
-/* These are zones that migrate to dnssec-policy. */
-zone "migrate.kasp" {
-	type primary;
-	file "migrate.kasp.db";
-	allow-update { any; };
-	dnssec-policy "migrate";
-};
-
-zone "migrate-nomatch-algnum.kasp" {
-	type primary;
-	file "migrate-nomatch-algnum.kasp.db";
-	allow-update { any; };
-	dnssec-policy "migrate-nomatch-algnum";
-};
-
-zone "migrate-nomatch-alglen.kasp" {
-	type primary;
-	file "migrate-nomatch-alglen.kasp.db";
-	allow-update { any; };
-	dnssec-policy "migrate-nomatch-alglen";
-};
-
 /* Zones for testing going insecure. */
 zone "step1.going-insecure.kasp" {
         type master;
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns6/named.conf.in bind9-9.16.15/bin/tests/system/kasp/ns6/named.conf.in
--- bind9-9.16.13/bin/tests/system/kasp/ns6/named.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns6/named.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -36,45 +36,17 @@
 	inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
 };
 
-/* These are zones that migrate to dnssec-policy. */
-zone "migrate.kasp" {
-	type primary;
-	file "migrate.kasp.db";
-	auto-dnssec maintain;
-	allow-update { any; };
-	dnssec-dnskey-kskonly yes;
-	update-check-ksk yes;
-};
-
-zone "migrate-nomatch-algnum.kasp" {
-	type primary;
-	file "migrate-nomatch-algnum.kasp.db";
-	auto-dnssec maintain;
-	allow-update { any; };
-	dnssec-dnskey-kskonly yes;
-	update-check-ksk yes;
-};
-
-zone "migrate-nomatch-alglen.kasp" {
-	type primary;
-	file "migrate-nomatch-alglen.kasp.db";
-	auto-dnssec maintain;
-	allow-update { any; };
-	dnssec-dnskey-kskonly yes;
-	update-check-ksk yes;
-};
-
 /* These zones are going insecure. */
 zone "step1.going-insecure.kasp" {
 	type master;
 	file "step1.going-insecure.kasp.db";
-	dnssec-policy "migrate";
+	dnssec-policy "unsigning";
 };
 
 zone "step1.going-insecure-dynamic.kasp" {
 	type master;
 	file "step1.going-insecure-dynamic.kasp.db";
-	dnssec-policy "migrate";
+	dnssec-policy "unsigning";
 	allow-update { any; };
 };
 
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns6/policies/kasp.conf.in bind9-9.16.15/bin/tests/system/kasp/ns6/policies/kasp.conf.in
--- bind9-9.16.13/bin/tests/system/kasp/ns6/policies/kasp.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns6/policies/kasp.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -9,6 +9,15 @@
  * information regarding copyright ownership.
  */
 
+dnssec-policy "unsigning" {
+	dnskey-ttl 7200;
+
+	keys {
+		ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
+		zsk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@;
+	};
+};
+
 dnssec-policy "rsasha1" {
 	signatures-refresh P5D;
 	signatures-validity 30d;
@@ -46,54 +55,3 @@
 	parent-propagation-delay pt1h;
 	parent-ds-ttl 7200;
 };
-
-dnssec-policy "migrate" {
-	dnskey-ttl 7200;
-
-	keys {
-		ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
-		zsk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@;
-	};
-};
-
-/*
- * This policy tests migration from existing keys with 1024 bits RSASHA1 keys
- * to ECDSAP256SHA256 keys.
- */
-dnssec-policy "migrate-nomatch-algnum" {
-	dnskey-ttl 300;
-
-	keys {
-		ksk key-directory lifetime unlimited algorithm ecdsa256;
-		zsk key-directory lifetime P60D algorithm ecdsa256;
-	};
-
-	// Together 12h
-	zone-propagation-delay 3600;
-	max-zone-ttl 11h;
-
-	// Together 3h
-	parent-propagation-delay pt1h;
-	parent-ds-ttl 7200;
-};
-
-/*
- * This policy tests migration from existing keys with 1024 bits RSASHA1 keys
- * to 2048 bits RSASHA1 keys.
- */
-dnssec-policy "migrate-nomatch-alglen" {
-	dnskey-ttl 300;
-
-	keys {
-		ksk key-directory lifetime unlimited algorithm rsasha1 2048;
-		zsk key-directory lifetime P60D algorithm rsasha1 2048;
-	};
-
-	// Together 12h
-	zone-propagation-delay 3600;
-	max-zone-ttl 11h;
-
-	// Together 3h
-	parent-propagation-delay pt1h;
-	parent-ds-ttl 7200;
-};
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns6/setup.sh bind9-9.16.15/bin/tests/system/kasp/ns6/setup.sh
--- bind9-9.16.13/bin/tests/system/kasp/ns6/setup.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns6/setup.sh	2021-04-19 15:45:50.000000000 +0200
@@ -38,54 +38,6 @@
 O="OMNIPRESENT"
 U="UNRETENTIVE"
 
-# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy.
-setup migrate.kasp
-echo "$zone" >> zones
-ksktimes="-P now -A now -P sync now"
-zsktimes="-P now -A now"
-KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
-ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200        $zsktimes $zone 2> keygen.out.$zone.2)
-cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
-private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >> "$infile"
-private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >> "$infile"
-$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
-
-# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy, but this
-# time the existing keys do not match the policy.  The existing keys are
-# RSASHA1 keys, and will be migrated to a dnssec-policy that dictates
-# ECDSAP256SHA256 keys.
-setup migrate-nomatch-algnum.kasp
-echo "$zone" >> zones
-Tds="now-3h"     # Time according to dnssec-policy that DS will be OMNIPRESENT
-Tkey="now-3900s" # DNSKEY TTL + propagation delay
-Tsig="now-12h"   # Zone's maximum TTL + propagation delay
-ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}"
-zsktimes="-P ${Tsig} -A ${Tsig}"
-KSK=$($KEYGEN -a RSASHA1 -b 2048 -L 300 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
-ZSK=$($KEYGEN -a RSASHA1 -b 1024 -L 300        $zsktimes $zone 2> keygen.out.$zone.2)
-cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
-private_type_record $zone 5 "$KSK" >> "$infile"
-private_type_record $zone 5 "$ZSK" >> "$infile"
-$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
-
-# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy, but this
-# time the existing keys do not match the policy.  The existing keys are
-# 1024 bits RSASHA1 keys, and will be migrated to a dnssec-policy that
-# dictates 2048 bits RSASHA1 keys.
-setup migrate-nomatch-alglen.kasp
-echo "$zone" >> zones
-Tds="now-3h"     # Time according to dnssec-policy that DS will be OMNIPRESENT
-Tkey="now-3900s" # DNSKEY TTL + propagation delay
-Tsig="now-12h"   # Zone's maximum TTL + propagation delay
-ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}"
-zsktimes="-P ${Tsig} -A ${Tsig}"
-KSK=$($KEYGEN -a RSASHA1 -b 1024 -L 300 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
-ZSK=$($KEYGEN -a RSASHA1 -b 1024 -L 300        $zsktimes $zone 2> keygen.out.$zone.2)
-cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
-private_type_record $zone 5 "$KSK" >> "$infile"
-private_type_record $zone 5 "$ZSK" >> "$infile"
-$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
-
 # The child zones (step1, step2) beneath these zones represent the various
 # steps of unsigning a zone.
 for zn in going-insecure.kasp going-insecure-dynamic.kasp
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns7/named2.conf.in bind9-9.16.15/bin/tests/system/kasp/ns7/named2.conf.in
--- bind9-9.16.13/bin/tests/system/kasp/ns7/named2.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns7/named2.conf.in	1970-01-01 01:00:00.000000000 +0100
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
- *
- * 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/.
- *
- * See the COPYRIGHT file distributed with this work for additional
- * information regarding copyright ownership.
- */
-
-// NS7
-
-options {
-	query-source address 10.53.0.7;
-	notify-source 10.53.0.7;
-	transfer-source 10.53.0.7;
-	port @PORT@;
-	pid-file "named.pid";
-	listen-on { 10.53.0.7; };
-	listen-on-v6 { none; };
-	allow-transfer { any; };
-	recursion no;
-	key-directory ".";
-};
-
-key rndc_key {
-	secret "1234abcd8765";
-	algorithm hmac-sha256;
-};
-
-controls {
-	inet 10.53.0.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
-};
-
-key "external" {
-	algorithm "hmac-sha1";
-	secret "YPfMoAk6h+3iN8MDRQC004iSNHY=";
-};
-
-key "internal" {
-	algorithm "hmac-sha1";
-	secret "4xILSZQnuO1UKubXHkYUsvBRPu8=";
-};
-
-dnssec-policy "rsasha256" {
-	keys {
-		zsk key-directory lifetime P3M algorithm 8 1024;
-		ksk key-directory lifetime P1Y algorithm 8 2048;
-	};
-
-	dnskey-ttl 6h;
-	publish-safety 1h;
-	retire-safety 1h;
-
-	signatures-refresh 5d;
-	signatures-validity 14d;
-	signatures-validity-dnskey 14d;
-
-	max-zone-ttl 1d;
-	zone-propagation-delay 300;
-
-	parent-ds-ttl 86400;
-	parent-propagation-delay 3h;
-};
-
-view "external-view" {
-	zone "view-rsasha256.kasp" {
-		type master;
-		file "view-rsasha256.kasp.ext.db";
-		dnssec-policy "rsasha256";
-	};
-};
-
-view "internal-view" {
-	zone "view-rsasha256.kasp" {
-		type master;
-		file "view-rsasha256.kasp.int.db";
-		dnssec-policy "rsasha256";
-	};
-};
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns7/named.conf.in bind9-9.16.15/bin/tests/system/kasp/ns7/named.conf.in
--- bind9-9.16.13/bin/tests/system/kasp/ns7/named.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns7/named.conf.in	1970-01-01 01:00:00.000000000 +0100
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
- *
- * 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/.
- *
- * See the COPYRIGHT file distributed with this work for additional
- * information regarding copyright ownership.
- */
-
-// NS7
-
-options {
-	query-source address 10.53.0.7;
-	notify-source 10.53.0.7;
-	transfer-source 10.53.0.7;
-	port @PORT@;
-	pid-file "named.pid";
-	listen-on { 10.53.0.7; };
-	listen-on-v6 { none; };
-	allow-transfer { any; };
-	recursion no;
-	key-directory ".";
-};
-
-key rndc_key {
-	secret "1234abcd8765";
-	algorithm hmac-sha256;
-};
-
-controls {
-	inet 10.53.0.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
-};
-
-key "external" {
-        algorithm "hmac-sha1";
-        secret "YPfMoAk6h+3iN8MDRQC004iSNHY=";
-};
-
-key "internal" {
-        algorithm "hmac-sha1";
-        secret "4xILSZQnuO1UKubXHkYUsvBRPu8=";
-};
-
-view "ext" {
-        match-clients { key "external"; };
-
-	zone "view-rsasha256.kasp" {
-		type master;
-		file "view-rsasha256.kasp.ext.db";
-		auto-dnssec maintain;
-		inline-signing yes;
-		dnssec-dnskey-kskonly yes;
-		update-check-ksk yes;
-	};
-};
-
-view "int" {
-        match-clients { key "internal"; };
-
-	zone "view-rsasha256.kasp" {
-		type master;
-		file "view-rsasha256.kasp.int.db";
-		auto-dnssec maintain;
-		inline-signing yes;
-		dnssec-dnskey-kskonly yes;
-		update-check-ksk yes;
-	};
-};
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns7/setup.sh bind9-9.16.15/bin/tests/system/kasp/ns7/setup.sh
--- bind9-9.16.13/bin/tests/system/kasp/ns7/setup.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns7/setup.sh	1970-01-01 01:00:00.000000000 +0100
@@ -1,54 +0,0 @@
-#!/bin/sh -e
-#
-# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-#
-# 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 https://mozilla.org/MPL/2.0/.
-#
-# See the COPYRIGHT file distributed with this work for additional
-# information regarding copyright ownership.
-
-# shellcheck source=conf.sh
-. ../../conf.sh
-
-echo_i "ns7/setup.sh"
-
-private_type_record() {
-	_zone=$1
-	_algorithm=$2
-	_keyfile=$3
-
-	_id=$(keyfile_to_key_id "$_keyfile")
-
-	printf "%s. 0 IN TYPE65534 %s 5 %02x%04x0000\n" "$_zone" "\\#" "$_algorithm" "$_id"
-}
-
-# Make lines shorter by storing key states in environment variables.
-H="HIDDEN"
-R="RUMOURED"
-O="OMNIPRESENT"
-U="UNRETENTIVE"
-
-zone="view-rsasha256.kasp"
-algo="RSASHA256"
-num="8"
-echo "$zone" >> zones
-
-# Set up zones in views with auto-dnssec maintain to migrate to dnssec-policy.
-# The keys for these zones are in use long enough that they should start a
-# rollover for the ZSK (P3M), but not long enough to initiate a KSK rollover (P1Y).
-ksktimes="-P -186d -A -186d -P sync -186d"
-zsktimes="-P -186d -A -186d"
-KSK=$($KEYGEN -a $algo -L 21600 -b 2048 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
-ZSK=$($KEYGEN -a $algo -L 21600 -b 1024        $zsktimes $zone 2> keygen.out.$zone.2)
-
-echo_i "setting up zone $zone (external)"
-view="ext"
-zonefile="${zone}.${view}.db"
-cat template.$view.db.in "${KSK}.key" "${ZSK}.key" > "$zonefile"
-
-echo_i "setting up zone $zone (internal)"
-view="int"
-zonefile="${zone}.${view}.db"
-cat template.$view.db.in "${KSK}.key" "${ZSK}.key" > "$zonefile"
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns7/template.ext.db.in bind9-9.16.15/bin/tests/system/kasp/ns7/template.ext.db.in
--- bind9-9.16.13/bin/tests/system/kasp/ns7/template.ext.db.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns7/template.ext.db.in	1970-01-01 01:00:00.000000000 +0100
@@ -1,22 +0,0 @@
-; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-;
-; 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/.
-;
-; See the COPYRIGHT file distributed with this work for additional
-; information regarding copyright ownership.
-
-$TTL 300
-@		IN	SOA  mname1. . (
-			1       ; serial
-			20      ; refresh (20 seconds)
-			20      ; retry (20 seconds)
-			1814400 ; expire (3 weeks)
-			3600    ; minimum (1 hour)
-			)
-
-			NS	ns7
-ns7			A	10.53.0.7
-
-view			TXT	"external"
diff -Nru bind9-9.16.13/bin/tests/system/kasp/ns7/template.int.db.in bind9-9.16.15/bin/tests/system/kasp/ns7/template.int.db.in
--- bind9-9.16.13/bin/tests/system/kasp/ns7/template.int.db.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/ns7/template.int.db.in	1970-01-01 01:00:00.000000000 +0100
@@ -1,22 +0,0 @@
-; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-;
-; 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/.
-;
-; See the COPYRIGHT file distributed with this work for additional
-; information regarding copyright ownership.
-
-$TTL 300
-@		IN	SOA  mname1. . (
-			1       ; serial
-			20      ; refresh (20 seconds)
-			20      ; retry (20 seconds)
-			1814400 ; expire (3 weeks)
-			3600    ; minimum (1 hour)
-			)
-
-			NS	ns7
-ns7			A	10.53.0.7
-
-view			TXT	"internal"
diff -Nru bind9-9.16.13/bin/tests/system/kasp/README bind9-9.16.15/bin/tests/system/kasp/README
--- bind9-9.16.13/bin/tests/system/kasp/README	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/README	2021-04-19 15:45:50.000000000 +0200
@@ -12,7 +12,5 @@
 
 ns4 and ns5 are authoritative servers for various test domains related to views.
 
-ns6 is an authoritative server that tests changes in dnssec-policy.
-
-ns7 is an authoritative server that tests a specific case where zones
-using views migrate to dnssec-policy.
+ns6 is an authoritative server that tests changes in dnssec-policy (algorithm
+rollover).
diff -Nru bind9-9.16.13/bin/tests/system/kasp/setup.sh bind9-9.16.15/bin/tests/system/kasp/setup.sh
--- bind9-9.16.13/bin/tests/system/kasp/setup.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/setup.sh	2021-04-19 15:45:50.000000000 +0200
@@ -23,7 +23,6 @@
 copy_setports ns4/named.conf.in ns4/named.conf
 copy_setports ns5/named.conf.in ns5/named.conf
 copy_setports ns6/named.conf.in ns6/named.conf
-copy_setports ns7/named.conf.in ns7/named.conf
 
 if $SHELL ../testcrypto.sh ed25519; then
 	echo "yes" > ed25519-supported.file
@@ -61,7 +60,3 @@
 	cd ns6
 	$SHELL setup.sh
 )
-(
-	cd ns7
-	$SHELL setup.sh
-)
diff -Nru bind9-9.16.13/bin/tests/system/kasp/tests.sh bind9-9.16.15/bin/tests/system/kasp/tests.sh
--- bind9-9.16.13/bin/tests/system/kasp/tests.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp/tests.sh	2021-04-19 15:45:50.000000000 +0200
@@ -10,119 +10,16 @@
 # information regarding copyright ownership.
 
 # shellcheck source=conf.sh
+# shellcheck source=kasp.sh
 SYSTEMTESTTOP=..
 . "$SYSTEMTESTTOP/conf.sh"
+. "$SYSTEMTESTTOP/kasp.sh"
 
 start_time="$(TZ=UTC date +%s)"
 status=0
 n=0
 
 ###############################################################################
-# Constants                                                                   #
-###############################################################################
-DEFAULT_TTL=300
-
-###############################################################################
-# Query properties                                                            #
-###############################################################################
-TSIG=""
-SHA1="FrSt77yPTFx6hTs4i2tKLB9LmE0="
-SHA224="hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA=="
-SHA256="R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY="
-VIEW1="YPfMoAk6h+3iN8MDRQC004iSNHY="
-VIEW2="4xILSZQnuO1UKubXHkYUsvBRPu8="
-
-###############################################################################
-# Key properties                                                              #
-###############################################################################
-# ID
-# BASEFILE
-# EXPECT
-# ROLE
-# KSK
-# ZSK
-# LIFETIME
-# ALG_NUM
-# ALG_STR
-# ALG_LEN
-# CREATED
-# PUBLISHED
-# ACTIVE
-# RETIRED
-# REVOKED
-# REMOVED
-# GOAL
-# STATE_DNSKEY
-# STATE_ZRRSIG
-# STATE_KRRSIG
-# STATE_DS
-# EXPECT_ZRRSIG
-# EXPECT_KRRSIG
-# LEGACY
-
-key_key() {
-	echo "${1}__${2}"
-}
-
-key_get() {
-	eval "echo \${$(key_key "$1" "$2")}"
-}
-
-key_set() {
-	eval "$(key_key "$1" "$2")='$3'"
-}
-
-# Save certain values in the KEY array.
-key_save()
-{
-	# Save key id.
-	key_set "$1" ID "$KEY_ID"
-	# Save base filename.
-	key_set "$1" BASEFILE "$BASE_FILE"
-	# Save creation date.
-	key_set "$1" CREATED "${KEY_CREATED}"
-}
-
-# Clear key state.
-#
-# This will update either the KEY1, KEY2, or KEY3 array.
-key_clear() {
-	key_set "$1" "ID" 'no'
-	key_set "$1" "IDPAD" 'no'
-	key_set "$1" "EXPECT" 'no'
-	key_set "$1" "ROLE" 'none'
-	key_set "$1" "KSK" 'no'
-	key_set "$1" "ZSK" 'no'
-	key_set "$1" "LIFETIME" 'none'
-	key_set "$1" "ALG_NUM" '0'
-	key_set "$1" "ALG_STR" 'none'
-	key_set "$1" "ALG_LEN" '0'
-	key_set "$1" "CREATED" '0'
-	key_set "$1" "PUBLISHED" 'none'
-	key_set "$1" "SYNCPUBLISH" 'none'
-	key_set "$1" "ACTIVE" 'none'
-	key_set "$1" "RETIRED" 'none'
-	key_set "$1" "REVOKED" 'none'
-	key_set "$1" "REMOVED" 'none'
-	key_set "$1" "GOAL" 'none'
-	key_set "$1" "STATE_DNSKEY" 'none'
-	key_set "$1" "STATE_KRRSIG" 'none'
-	key_set "$1" "STATE_ZRRSIG" 'none'
-	key_set "$1" "STATE_DS" 'none'
-	key_set "$1" "EXPECT_ZRRSIG" 'no'
-	key_set "$1" "EXPECT_KRRSIG" 'no'
-	key_set "$1" "LEGACY" 'no'
-}
-
-# Start clear.
-# There can be at most 4 keys at the same time during a rollover:
-# 2x KSK, 2x ZSK
-key_clear "KEY1"
-key_clear "KEY2"
-key_clear "KEY3"
-key_clear "KEY4"
-
-###############################################################################
 # Utilities                                                                   #
 ###############################################################################
 
@@ -141,512 +38,11 @@
 	"$RNDC" -c "$SYSTEMTESTTOP/common/rndc.conf" -p "$CONTROLPORT" -s "$@"
 }
 
-# Print IDs of keys used for generating RRSIG records for RRsets of type $1
-# found in dig output file $2.
-get_keys_which_signed() {
-	_qtype=$1
-	_output=$2
-	# The key ID is the 11th column of the RRSIG record line.
-	awk -v qt="$_qtype" '$4 == "RRSIG" && $5 == qt {print $11}' < "$_output"
-}
-
-# Get the key ids from key files for zone $2 in directory $1.
-get_keyids() {
-	_dir=$1
-	_zone=$2
-	_regex="K${_zone}.+*+*.key"
-
-	find "${_dir}" -mindepth 1 -maxdepth 1 -name "${_regex}" | sed "s,$_dir/K${_zone}.+\([0-9]\{3\}\)+\([0-9]\{5\}\).key,\2,"
-}
-
-# By default log errors and don't quit immediately.
-_log=1
+# Log error and increment failure rate.
 log_error() {
-	test $_log -eq 1 && echo_i "error: $1"
+	echo_i "error: $1"
 	ret=$((ret+1))
 }
-# Set server key-directory ($1) and address ($2) for testing keys.
-set_server() {
-	DIR=$1
-	SERVER=$2
-}
-# Set zone name for testing keys.
-set_zone() {
-	ZONE=$1
-	DYNAMIC="no"
-}
-# By default zones are considered static.
-# When testing dynamic zones, call 'set_dynamic' after 'set_zone'.
-set_dynamic() {
-	DYNAMIC="yes"
-}
-
-# Set policy settings (name $1, number of keys $2, dnskey ttl $3) for testing keys.
-set_policy() {
-	POLICY=$1
-	NUM_KEYS=$2
-	DNSKEY_TTL=$3
-	CDS_DELETE="no"
-}
-# By default policies are considered to be secure.
-# If a zone sets its policy to "none", call 'set_cdsdelete' to tell the system
-# test to expect a CDS and CDNSKEY Delete record.
-set_cdsdelete() {
-	CDS_DELETE="yes"
-}
-
-# Set key properties for testing keys.
-# $1: Key to update (KEY1, KEY2, ...)
-# $2: Value
-set_keyrole() {
-	key_set "$1" "EXPECT" "yes"
-	key_set "$1" "ROLE" "$2"
-	key_set "$1" "KSK" "no"
-	key_set "$1" "ZSK" "no"
-	test "$2" = "ksk" && key_set "$1" "KSK" "yes"
-	test "$2" = "zsk" && key_set "$1" "ZSK" "yes"
-	test "$2" = "csk" && key_set "$1" "KSK" "yes"
-	test "$2" = "csk" && key_set "$1" "ZSK" "yes"
-}
-set_keylifetime() {
-	key_set "$1" "EXPECT" "yes"
-	key_set "$1" "LIFETIME" "$2"
-}
-# The algorithm value consists of three parts:
-# $2: Algorithm (number)
-# $3: Algorithm (string-format)
-# $4: Algorithm length
-set_keyalgorithm() {
-	key_set "$1" "EXPECT" "yes"
-	key_set "$1" "ALG_NUM" "$2"
-	key_set "$1" "ALG_STR" "$3"
-	key_set "$1" "ALG_LEN" "$4"
-}
-set_keysigning() {
-	key_set "$1" "EXPECT" "yes"
-	key_set "$1" "EXPECT_KRRSIG" "$2"
-}
-set_zonesigning() {
-	key_set "$1" "EXPECT" "yes"
-	key_set "$1" "EXPECT_ZRRSIG" "$2"
-}
-
-# Set key timing metadata. Set to "none" to unset.
-# $1: Key to update (KEY1, KEY2, ...)
-# $2: Time to update (PUBLISHED, SYNCPUBLISH, ACTIVE, RETIRED, REVOKED, or REMOVED).
-# $3: Value
-set_keytime() {
-	key_set "$1" "EXPECT" "yes"
-	key_set "$1" "$2" "$3"
-}
-
-# Set key timing metadata to a value plus additional time.
-# $1: Key to update (KEY1, KEY2, ...)
-# $2: Time to update (PUBLISHED, SYNCPUBLISH, ACTIVE, RETIRED, REVOKED, or REMOVED).
-# $3: Value
-# $4: Additional time.
-set_addkeytime() {
-	if [ -x "$PYTHON" ]; then
-		# Convert "%Y%m%d%H%M%S" format to epoch seconds.
-		# Then, add the additional time (can be negative).
-		_value=$3
-		_plus=$4
-		$PYTHON > python.out.$ZONE.$1.$2 <<EOF
-from datetime import datetime
-from datetime import timedelta
-_now = datetime.strptime("$_value", "%Y%m%d%H%M%S")
-_delta = timedelta(seconds=$_plus)
-_then = _now + _delta
-print(_then.strftime("%Y%m%d%H%M%S"));
-EOF
-		# Set the expected timing metadata.
-		key_set "$1" "$2" $(cat python.out.$ZONE.$1.$2)
-	fi
-}
-
-# Set key state metadata. Set to "none" to unset.
-# $1: Key to update (KEY1, KEY2, ...)
-# $2: Key state to update (GOAL, STATE_DNSKEY, STATE_ZRRSIG, STATE_KRRSIG, or STATE_DS)
-# $3: Value
-set_keystate() {
-	key_set "$1" "EXPECT" "yes"
-	key_set "$1" "$2" "$3"
-}
-
-# Check the key $1 with id $2.
-# This requires environment variables to be set.
-#
-# This will set the following environment variables for testing:
-# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
-# KEY_FILE="${BASE_FILE}.key"
-# PRIVATE_FILE="${BASE_FILE}.private"
-# STATE_FILE="${BASE_FILE}.state"
-# KEY_ID=$(echo $1 | sed 's/^0\{0,4\}//')
-# KEY_CREATED (from the KEY_FILE)
-check_key() {
-	_dir="$DIR"
-	_zone="$ZONE"
-	_role=$(key_get "$1" ROLE)
-	_key_idpad="$2"
-	_key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//')
-	_alg_num=$(key_get "$1" ALG_NUM)
-	_alg_numpad=$(printf "%03d" "$_alg_num")
-	_alg_string=$(key_get "$1" ALG_STR)
-	_length=$(key_get "$1" "ALG_LEN")
-	_dnskey_ttl="$DNSKEY_TTL"
-	_lifetime=$(key_get "$1" LIFETIME)
-	_legacy=$(key_get "$1" LEGACY)
-
-	_published=$(key_get "$1" PUBLISHED)
-	_active=$(key_get "$1" ACTIVE)
-	_retired=$(key_get "$1" RETIRED)
-	_revoked=$(key_get "$1" REVOKED)
-	_removed=$(key_get "$1" REMOVED)
-
-	_goal=$(key_get "$1" GOAL)
-	_state_dnskey=$(key_get "$1" STATE_DNSKEY)
-	_state_zrrsig=$(key_get "$1" STATE_ZRRSIG)
-	_state_krrsig=$(key_get "$1" STATE_KRRSIG)
-	_state_ds=$(key_get "$1" STATE_DS)
-
-	_ksk="no"
-	_zsk="no"
-	if [ "$_role" = "ksk" ]; then
-		_role2="key-signing"
-		_ksk="yes"
-		_flags="257"
-	elif [ "$_role" = "zsk" ]; then
-		_role2="zone-signing"
-		_zsk="yes"
-		_flags="256"
-	elif [ "$_role" = "csk" ]; then
-		_role2="key-signing"
-		_zsk="yes"
-		_ksk="yes"
-		_flags="257"
-	fi
-
-	BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
-	KEY_FILE="${BASE_FILE}.key"
-	PRIVATE_FILE="${BASE_FILE}.private"
-	STATE_FILE="${BASE_FILE}.state"
-	KEY_ID="${_key_id}"
-
-	# Check file existence.
-	[ -s "$KEY_FILE" ] || ret=1
-	[ -s "$PRIVATE_FILE" ] || ret=1
-	if [ "$_legacy" = "no" ]; then
-		[ -s "$STATE_FILE" ] || ret=1
-	fi
-	[ "$ret" -eq 0 ] || log_error "${BASE_FILE} files missing"
-	[ "$ret" -eq 0 ] || return
-
-	# Retrieve creation date.
-	grep "; Created:" "$KEY_FILE" > "${ZONE}.${KEY_ID}.${_alg_num}.created" || log_error "mismatch created comment in $KEY_FILE"
-	KEY_CREATED=$(awk '{print $3}' < "${ZONE}.${KEY_ID}.${_alg_num}.created")
-
-	grep "Created: ${KEY_CREATED}" "$PRIVATE_FILE" > /dev/null || log_error "mismatch created in $PRIVATE_FILE"
-	if [ "$_legacy" = "no" ]; then
-		grep "Generated: ${KEY_CREATED}" "$STATE_FILE" > /dev/null || log_error "mismatch generated in $STATE_FILE"
-	fi
-
-	test $_log -eq 1 && echo_i "check key file $BASE_FILE"
-
-	# Check the public key file.
-	grep "This is a ${_role2} key, keyid ${_key_id}, for ${_zone}." "$KEY_FILE" > /dev/null || log_error "mismatch top comment in $KEY_FILE"
-	grep "${_zone}\. ${_dnskey_ttl} IN DNSKEY ${_flags} 3 ${_alg_num}" "$KEY_FILE" > /dev/null || log_error "mismatch DNSKEY record in $KEY_FILE"
-	# Now check the private key file.
-	grep "Private-key-format: v1.3" "$PRIVATE_FILE" > /dev/null || log_error "mismatch private key format in $PRIVATE_FILE"
-	grep "Algorithm: ${_alg_num} (${_alg_string})" "$PRIVATE_FILE" > /dev/null || log_error "mismatch algorithm in $PRIVATE_FILE"
-	# Now check the key state file.
-	if [ "$_legacy" = "no" ]; then
-		grep "This is the state of key ${_key_id}, for ${_zone}." "$STATE_FILE" > /dev/null || log_error "mismatch top comment in $STATE_FILE"
-		if [ "$_lifetime" = "none" ]; then
-			grep "Lifetime: " "$STATE_FILE" > /dev/null && log_error "unexpected lifetime in $STATE_FILE"
-		else
-			grep "Lifetime: ${_lifetime}" "$STATE_FILE" > /dev/null || log_error "mismatch lifetime in $STATE_FILE"
-		fi
-		grep "Algorithm: ${_alg_num}" "$STATE_FILE" > /dev/null || log_error "mismatch algorithm in $STATE_FILE"
-		grep "Length: ${_length}" "$STATE_FILE" > /dev/null || log_error "mismatch length in $STATE_FILE"
-		grep "KSK: ${_ksk}" "$STATE_FILE" > /dev/null || log_error "mismatch ksk in $STATE_FILE"
-		grep "ZSK: ${_zsk}" "$STATE_FILE" > /dev/null || log_error "mismatch zsk in $STATE_FILE"
-
-		# Check key states.
-		if [ "$_goal" = "none" ]; then
-			grep "GoalState: " "$STATE_FILE" > /dev/null && log_error "unexpected goal state in $STATE_FILE"
-		else
-			grep "GoalState: ${_goal}" "$STATE_FILE" > /dev/null || log_error "mismatch goal state in $STATE_FILE"
-		fi
-
-		if [ "$_state_dnskey" = "none" ]; then
-			grep "DNSKEYState: " "$STATE_FILE" > /dev/null && log_error "unexpected dnskey state in $STATE_FILE"
-			grep "DNSKEYChange: " "$STATE_FILE" > /dev/null && log_error "unexpected dnskey change in $STATE_FILE"
-		else
-			grep "DNSKEYState: ${_state_dnskey}" "$STATE_FILE" > /dev/null || log_error "mismatch dnskey state in $STATE_FILE"
-			grep "DNSKEYChange: " "$STATE_FILE" > /dev/null || log_error "mismatch dnskey change in $STATE_FILE"
-		fi
-
-		if [ "$_state_zrrsig" = "none" ]; then
-			grep "ZRRSIGState: " "$STATE_FILE" > /dev/null && log_error "unexpected zrrsig state in $STATE_FILE"
-			grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null && log_error "unexpected zrrsig change in $STATE_FILE"
-		else
-			grep "ZRRSIGState: ${_state_zrrsig}" "$STATE_FILE" > /dev/null || log_error "mismatch zrrsig state in $STATE_FILE"
-			grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null || log_error "mismatch zrrsig change in $STATE_FILE"
-		fi
-
-		if [ "$_state_krrsig" = "none" ]; then
-			grep "KRRSIGState: " "$STATE_FILE" > /dev/null && log_error "unexpected krrsig state in $STATE_FILE"
-			grep "KRRSIGChange: " "$STATE_FILE" > /dev/null && log_error "unexpected krrsig change in $STATE_FILE"
-		else
-			grep "KRRSIGState: ${_state_krrsig}" "$STATE_FILE" > /dev/null || log_error "mismatch krrsig state in $STATE_FILE"
-			grep "KRRSIGChange: " "$STATE_FILE" > /dev/null || log_error "mismatch krrsig change in $STATE_FILE"
-		fi
-
-		if [ "$_state_ds" = "none" ]; then
-			grep "DSState: " "$STATE_FILE" > /dev/null && log_error "unexpected ds state in $STATE_FILE"
-			grep "DSChange: " "$STATE_FILE" > /dev/null && log_error "unexpected ds change in $STATE_FILE"
-		else
-			grep "DSState: ${_state_ds}" "$STATE_FILE" > /dev/null || log_error "mismatch ds state in $STATE_FILE"
-			grep "DSChange: " "$STATE_FILE" > /dev/null || log_error "mismatch ds change in $STATE_FILE"
-		fi
-	fi
-}
-
-# Check the key timing metadata for key $1.
-check_timingmetadata() {
-	_dir="$DIR"
-	_zone="$ZONE"
-	_key_idpad=$(key_get "$1" ID)
-	_key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//')
-	_alg_num=$(key_get "$1" ALG_NUM)
-	_alg_numpad=$(printf "%03d" "$_alg_num")
-
-	_published=$(key_get "$1" PUBLISHED)
-	_active=$(key_get "$1" ACTIVE)
-	_retired=$(key_get "$1" RETIRED)
-	_revoked=$(key_get "$1" REVOKED)
-	_removed=$(key_get "$1" REMOVED)
-
-	_goal=$(key_get "$1" GOAL)
-	_state_dnskey=$(key_get "$1" STATE_DNSKEY)
-	_state_zrrsig=$(key_get "$1" STATE_ZRRSIG)
-	_state_krrsig=$(key_get "$1" STATE_KRRSIG)
-	_state_ds=$(key_get "$1" STATE_DS)
-
-	_base_file=$(key_get "$1" BASEFILE)
-	_key_file="${_base_file}.key"
-	_private_file="${_base_file}.private"
-	_state_file="${_base_file}.state"
-
-	_published=$(key_get "$1" PUBLISHED)
-	_syncpublish=$(key_get "$1" SYNCPUBLISH)
-	_active=$(key_get "$1" ACTIVE)
-	_retired=$(key_get "$1" RETIRED)
-	_revoked=$(key_get "$1" REVOKED)
-	_removed=$(key_get "$1" REMOVED)
-
-	# Check timing metadata.
-	n=$((n+1))
-	echo_i "check key timing metadata for key $1 id ${_key_id} zone ${ZONE} ($n)"
-	ret=0
-
-	if [ "$_published" = "none" ]; then
-		grep "; Publish:" "${_key_file}" > /dev/null && log_error "unexpected publish comment in ${_key_file}"
-		grep "Publish:" "${_private_file}" > /dev/null && log_error "unexpected publish in ${_private_file}"
-		if [ "$_legacy" = "no" ]; then
-			grep "Published: " "${_state_file}" > /dev/null && log_error "unexpected publish in ${_state_file}"
-		fi
-	else
-		grep "; Publish: $_published" "${_key_file}" > /dev/null || log_error "mismatch publish comment in ${_key_file} (expected ${_published})"
-		grep "Publish: $_published" "${_private_file}" > /dev/null || log_error "mismatch publish in ${_private_file} (expected ${_published})"
-		if [ "$_legacy" = "no" ]; then
-			grep "Published: $_published" "${_state_file}" > /dev/null || log_error "mismatch publish in ${_state_file} (expected ${_published})"
-		fi
-	fi
-
-	if [ "$_syncpublish" = "none" ]; then
-		grep "; SyncPublish:" "${_key_file}" > /dev/null && log_error "unexpected syncpublish comment in ${_key_file}"
-		grep "SyncPublish:" "${_private_file}" > /dev/null && log_error "unexpected syncpublish in ${_private_file}"
-		if [ "$_legacy" = "no" ]; then
-			grep "PublishCDS: " "${_state_file}" > /dev/null && log_error "unexpected syncpublish in ${_state_file}"
-		fi
-	else
-		grep "; SyncPublish: $_syncpublish" "${_key_file}" > /dev/null || log_error "mismatch syncpublish comment in ${_key_file} (expected ${_syncpublish})"
-		grep "SyncPublish: $_syncpublish" "${_private_file}" > /dev/null || log_error "mismatch syncpublish in ${_private_file} (expected ${_syncpublish})"
-		if [ "$_legacy" = "no" ]; then
-			grep "PublishCDS: $_syncpublish" "${_state_file}" > /dev/null || log_error "mismatch syncpublish in ${_state_file} (expected ${_syncpublish})"
-		fi
-	fi
-
-	if [ "$_active" = "none" ]; then
-		grep "; Activate:" "${_key_file}" > /dev/null && log_error "unexpected active comment in ${_key_file}"
-		grep "Activate:" "${_private_file}" > /dev/null && log_error "unexpected active in ${_private_file}"
-		if [ "$_legacy" = "no" ]; then
-			grep "Active: " "${_state_file}" > /dev/null && log_error "unexpected active in ${_state_file}"
-		fi
-	else
-		grep "; Activate: $_active" "${_key_file}" > /dev/null || log_error "mismatch active comment in ${_key_file} (expected ${_active})"
-		grep "Activate: $_active" "${_private_file}" > /dev/null || log_error "mismatch active in ${_private_file} (expected ${_active})"
-		if [ "$_legacy" = "no" ]; then
-			grep "Active: $_active" "${_state_file}" > /dev/null || log_error "mismatch active in ${_state_file} (expected ${_active})"
-		fi
-	fi
-
-	if [ "$_retired" = "none" ]; then
-		grep "; Inactive:" "${_key_file}" > /dev/null && log_error "unexpected retired comment in ${_key_file}"
-		grep "Inactive:" "${_private_file}" > /dev/null && log_error "unexpected retired in ${_private_file}"
-		if [ "$_legacy" = "no" ]; then
-			grep "Retired: " "${_state_file}" > /dev/null && log_error "unexpected retired in ${_state_file}"
-		fi
-	else
-		grep "; Inactive: $_retired" "${_key_file}" > /dev/null || log_error "mismatch retired comment in ${_key_file} (expected ${_retired})"
-		grep "Inactive: $_retired" "${_private_file}" > /dev/null || log_error "mismatch retired in ${_private_file} (expected ${_retired})"
-		if [ "$_legacy" = "no" ]; then
-			grep "Retired: $_retired" "${_state_file}" > /dev/null || log_error "mismatch retired in ${_state_file} (expected ${_retired})"
-		fi
-	fi
-
-	if [ "$_revoked" = "none" ]; then
-		grep "; Revoke:" "${_key_file}" > /dev/null && log_error "unexpected revoked comment in ${_key_file}"
-		grep "Revoke:" "${_private_file}" > /dev/null && log_error "unexpected revoked in ${_private_file}"
-		if [ "$_legacy" = "no" ]; then
-			grep "Revoked: " "${_state_file}" > /dev/null && log_error "unexpected revoked in ${_state_file}"
-		fi
-	else
-		grep "; Revoke: $_revoked" "${_key_file}" > /dev/null || log_error "mismatch revoked comment in ${_key_file} (expected ${_revoked})"
-		grep "Revoke: $_revoked" "${_private_file}" > /dev/null || log_error "mismatch revoked in ${_private_file} (expected ${_revoked})"
-		if [ "$_legacy" = "no" ]; then
-			grep "Revoked: $_revoked" "${_state_file}" > /dev/null || log_error "mismatch revoked in ${_state_file} (expected ${_revoked})"
-		fi
-	fi
-
-	if [ "$_removed" = "none" ]; then
-		grep "; Delete:" "${_key_file}" > /dev/null && log_error "unexpected removed comment in ${_key_file}"
-		grep "Delete:" "${_private_file}" > /dev/null && log_error "unexpected removed in ${_private_file}"
-		if [ "$_legacy" = "no" ]; then
-			grep "Removed: " "${_state_file}" > /dev/null && log_error "unexpected removed in ${_state_file}"
-		fi
-	else
-		grep "; Delete: $_removed" "${_key_file}" > /dev/null || log_error "mismatch removed comment in ${_key_file} (expected ${_removed})"
-		grep "Delete: $_removed" "${_private_file}" > /dev/null || log_error "mismatch removed in ${_private_file} (expected ${_removed})"
-		if [ "$_legacy" = "no" ]; then
-			grep "Removed: $_removed" "${_state_file}" > /dev/null || log_error "mismatch removed in ${_state_file} (expected ${_removed})"
-		fi
-	fi
-
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
-
-check_keytimes() {
-	# The script relies on Python to set keytimes.
-	if [ -x "$PYTHON" ]; then
-
-		if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
-			check_timingmetadata "KEY1"
-		fi
-		if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
-			check_timingmetadata "KEY2"
-		fi
-		if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
-			check_timingmetadata "KEY3"
-		fi
-		if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
-			check_timingmetadata "KEY4"
-		fi
-	fi
-}
-
-# Check the key with key id $1 and see if it is unused.
-# This requires environment variables to be set.
-#
-# This will set the following environment variables for testing:
-# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
-# KEY_FILE="${BASE_FILE}.key"
-# PRIVATE_FILE="${BASE_FILE}.private"
-# STATE_FILE="${BASE_FILE}.state"
-# KEY_ID=$(echo $1 | sed 's/^0\{0,4\}//')
-key_unused() {
-	_dir=$DIR
-	_zone=$ZONE
-	_key_idpad=$1
-	_key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//')
-	_alg_num=$2
-        _alg_numpad=$(printf "%03d" "$_alg_num")
-
-	BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
-	KEY_FILE="${BASE_FILE}.key"
-	PRIVATE_FILE="${BASE_FILE}.private"
-	STATE_FILE="${BASE_FILE}.state"
-	KEY_ID="${_key_id}"
-
-	test $_log -eq 1 && echo_i "key unused $KEY_ID?"
-
-	# Check file existence.
-	[ -s "$KEY_FILE" ] || ret=1
-	[ -s "$PRIVATE_FILE" ] || ret=1
-	[ -s "$STATE_FILE" ] || ret=1
-	[ "$ret" -eq 0 ] || return
-
-	# Treat keys that have been removed from the zone as unused.
-	_check_removed=1
-	grep "; Created:" "$KEY_FILE" > created.key-${KEY_ID}.test${n} || _check_removed=0
-	grep "; Delete:" "$KEY_FILE" > unused.key-${KEY_ID}.test${n} || _check_removed=0
-	if [ "$_check_removed" -eq 1 ]; then
-		_created=$(awk '{print $3}' < created.key-${KEY_ID}.test${n})
-		_removed=$(awk '{print $3}' < unused.key-${KEY_ID}.test${n})
-		[ "$_removed" -le "$_created" ] && return
-	fi
-
-	# If no timing metadata is set, this key is unused.
-	grep "; Publish:" "$KEY_FILE" > /dev/null && log_error "unexpected publish comment in $KEY_FILE"
-	grep "; Activate:" "$KEY_FILE" > /dev/null && log_error "unexpected active comment in $KEY_FILE"
-	grep "; Inactive:" "$KEY_FILE" > /dev/null && log_error "unexpected retired comment in $KEY_FILE"
-	grep "; Revoke:" "$KEY_FILE" > /dev/null && log_error "unexpected revoked comment in $KEY_FILE"
-	grep "; Delete:" "$KEY_FILE" > /dev/null && log_error "unexpected removed comment in $KEY_FILE"
-
-	grep "Publish:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected publish in $PRIVATE_FILE"
-	grep "Activate:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected active in $PRIVATE_FILE"
-	grep "Inactive:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected retired in $PRIVATE_FILE"
-	grep "Revoke:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected revoked in $PRIVATE_FILE"
-	grep "Delete:" "$PRIVATE_FILE" > /dev/null && log_error "unexpected removed in $PRIVATE_FILE"
-
-	grep "Published: " "$STATE_FILE" > /dev/null && log_error "unexpected publish in $STATE_FILE"
-	grep "Active: " "$STATE_FILE" > /dev/null && log_error "unexpected active in $STATE_FILE"
-	grep "Retired: " "$STATE_FILE" > /dev/null && log_error "unexpected retired in $STATE_FILE"
-	grep "Revoked: " "$STATE_FILE" > /dev/null && log_error "unexpected revoked in $STATE_FILE"
-	grep "Removed: " "$STATE_FILE" > /dev/null && log_error "unexpected removed in $STATE_FILE"
-}
-
-# Test: dnssec-verify zone $1.
-dnssec_verify()
-{
-	n=$((n+1))
-	echo_i "dnssec-verify zone ${ZONE} ($n)"
-	ret=0
-	dig_with_opts "$ZONE" "@${SERVER}" AXFR > dig.out.axfr.test$n || log_error "dig ${ZONE} AXFR failed"
-	$VERIFY -z -o "$ZONE" dig.out.axfr.test$n > /dev/null || log_error "dnssec verify zone $ZONE failed"
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
-
-# Wait for the zone to be signed.
-# The apex NSEC record indicates that it is signed.
-_wait_for_nsec() {
-	dig_with_opts "@${SERVER}" "$ZONE" NSEC > "dig.out.nsec.test$n" || return 1
-	grep "NS SOA" "dig.out.nsec.test$n" > /dev/null || return 1
-	grep "${ZONE}\..*IN.*RRSIG" "dig.out.nsec.test$n" > /dev/null || return 1
-	return 0
-}
-
-wait_for_nsec() {
-	n=$((n+1))
-	ret=0
-	echo_i "wait for ${ZONE} to be signed ($n)"
-	retry_quiet 10 _wait_for_nsec  || log_error "wait for ${ZONE} to be signed failed"
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
 
 # Default next key event threshold. May be extended by wait periods.
 next_key_event_threshold=100
@@ -669,7 +65,7 @@
 lines=$(wc -l < "keygen.out.$POLICY.test$n")
 test "$lines" -eq $NUM_KEYS || log_error "wrong number of keys created for policy kasp: $lines"
 # Temporarily don't log errors because we are searching multiple files.
-_log=0
+disable_logerror
 
 # Key properties.
 set_keyrole      "KEY1" "csk"
@@ -719,7 +115,7 @@
 	status=$((status+ret))
 done
 # Turn error logs on again.
-_log=1
+enable_logerror
 
 n=$((n+1))
 echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)"
@@ -855,460 +251,6 @@
 
 next_key_event_threshold=$((next_key_event_threshold+i))
 
-check_numkeys() {
-	_numkeys=$(get_keyids "$DIR" "$ZONE" | wc -l)
-	test "$_numkeys" -eq "$NUM_KEYS" || return 1
-	return 0
-}
-
-# Check keys for a configured zone. This verifies:
-# 1. The right number of keys exist in the key pool ($1).
-# 2. The right number of keys is active. Checks KEY1, KEY2, KEY3, and KEY4.
-#
-# It is expected that KEY1, KEY2, KEY3, and KEY4 arrays are set correctly.
-# Found key identifiers are stored in the right key array.
-check_keys() {
-	n=$((n+1))
-	echo_i "check keys are created for zone ${ZONE} ($n)"
-	ret=0
-
-	echo_i "check number of keys for zone ${ZONE} in dir ${DIR} ($n)"
-	retry_quiet 10 check_numkeys || ret=1
-	if [ $ret -ne 0 ]; then
-		_numkeys=$(get_keyids "$DIR" "$ZONE" | wc -l)
-		log_error "bad number of key files ($_numkeys) for zone $ZONE (expected $NUM_KEYS)"
-		status=$((status+ret))
-	fi
-
-	# Temporarily don't log errors because we are searching multiple files.
-	_log=0
-
-	# Clear key ids.
-	key_set KEY1 ID "no"
-	key_set KEY2 ID "no"
-	key_set KEY3 ID "no"
-	key_set KEY4 ID "no"
-
-	# Check key files.
-	_ids=$(get_keyids "$DIR" "$ZONE")
-	for _id in $_ids; do
-		# There are three key files with the same algorithm.
-		# Check them until a match is found.
-		echo_i "check key id $_id"
-
-		if [ "no" = "$(key_get KEY1 ID)" ] && [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
-			ret=0
-			check_key "KEY1" "$_id"
-			test "$ret" -eq 0 && key_save KEY1 && continue
-		fi
-		if [ "no" = "$(key_get KEY2 ID)" ] && [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
-			ret=0
-			check_key "KEY2" "$_id"
-			test "$ret" -eq 0 && key_save KEY2 && continue
-		fi
-		if [ "no" = "$(key_get KEY3 ID)" ] && [ "$(key_get KEY3 EXPECT)" = "yes"  ]; then
-			ret=0
-			check_key "KEY3" "$_id"
-			test "$ret" -eq 0 && key_save KEY3 && continue
-		fi
-		if [ "no" = "$(key_get KEY4 ID)" ] && [ "$(key_get KEY4 EXPECT)" = "yes"  ]; then
-			ret=0
-			check_key "KEY4" "$_id"
-			test "$ret" -eq 0 && key_save KEY4 && continue
-		fi
-
-		# This may be an unused key. Assume algorithm of KEY1.
-		ret=0 && key_unused "$_id" "$(key_get KEY1 ALG_NUM)"
-		test "$ret" -eq 0 && continue
-
-		# If ret is still non-zero, none of the files matched.
-		test "$ret" -eq 0 || echo_i "failed"
-		status=$((status+1))
-	done
-
-	# Turn error logs on again.
-	_log=1
-
-	ret=0
-	if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
-		echo_i "KEY1 ID $(key_get KEY1 ID)"
-		test "no" = "$(key_get KEY1 ID)" && log_error "No KEY1 found for zone ${ZONE}"
-	fi
-	if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
-		echo_i "KEY2 ID $(key_get KEY2 ID)"
-		test "no" = "$(key_get KEY2 ID)" && log_error "No KEY2 found for zone ${ZONE}"
-	fi
-	if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
-		echo_i "KEY3 ID $(key_get KEY3 ID)"
-		test "no" = "$(key_get KEY3 ID)" && log_error "No KEY3 found for zone ${ZONE}"
-	fi
-	if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
-		echo_i "KEY4 ID $(key_get KEY4 ID)"
-		test "no" = "$(key_get KEY4 ID)" && log_error "No KEY4 found for zone ${ZONE}"
-	fi
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
-
-# Call rndc dnssec -status on server $1 for zone $2 and check output.
-# This is a loose verification, it just tests if the right policy
-# name is returned, and if all expected keys are listed.  The rndc
-# dnssec -status output also lists whether a key is published,
-# used for signing, is retired, or is removed, and if not when
-# it is scheduled to do so, and it shows the states for the various
-# DNSSEC records.
-check_dnssecstatus() {
-	_server=$1
-	_policy=$2
-	_zone=$3
-	_view=$4
-
-	n=$((n+1))
-	echo_i "check rndc dnssec -status output for ${_zone} (policy: $_policy) ($n)"
-	ret=0
-
-	rndccmd $_server dnssec -status $_zone in $_view > rndc.dnssec.status.out.$_zone.$n || log_error "rndc dnssec -status zone ${_zone} failed"
-
-	grep "dnssec-policy: ${_policy}" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "bad dnssec status for signed zone ${_zone}"
-	if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
-		grep "key: $(key_get KEY1 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "missing key $(key_get KEY1 ID) from dnssec status"
-	fi
-	if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
-		grep "key: $(key_get KEY2 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "missing key $(key_get KEY2 ID) from dnssec status"
-	fi
-	if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
-		grep "key: $(key_get KEY3 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "missing key $(key_get KEY3 ID) from dnssec status"
-	fi
-	if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
-		grep "key: $(key_get KEY4 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || log_error "missing key $(key_get KEY4 ID) from dnssec status"
-	fi
-
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
-
-# Check if RRset of type $1 in file $2 is signed with the right keys.
-# The right keys are the ones that expect a signature and matches the role $3.
-check_signatures() {
-	_qtype=$1
-	_file=$2
-	_role=$3
-
-	if [ "$_role" = "KSK" ]; then
-		_expect_type=EXPECT_KRRSIG
-	elif [ "$_role" = "ZSK" ]; then
-		_expect_type=EXPECT_ZRRSIG
-	fi
-
-	if [ "$(key_get KEY1 "$_expect_type")" = "yes" ] && [ "$(key_get KEY1 "$_role")" = "yes" ]; then
-		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY1 ID)$" > /dev/null || log_error "${_qtype} RRset not signed with key $(key_get KEY1 ID)"
-	elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
-		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY1 ID)$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY1 ID)"
-	fi
-
-	if [ "$(key_get KEY2 "$_expect_type")" = "yes" ] && [ "$(key_get KEY2 "$_role")" = "yes" ]; then
-		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY2 ID)$" > /dev/null || log_error "${_qtype} RRset not signed with key $(key_get KEY2 ID)"
-	elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
-		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY2 ID)$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY2 ID)"
-	fi
-
-	if [ "$(key_get KEY3 "$_expect_type")" = "yes" ] && [ "$(key_get KEY3 "$_role")" = "yes" ]; then
-		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY3 ID)$" > /dev/null || log_error "${_qtype} RRset not signed with key $(key_get KEY3 ID)"
-	elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
-		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY3 ID)$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY3 ID)"
-	fi
-
-	if [ "$(key_get KEY4 "$_expect_type")" = "yes" ] && [ "$(key_get KEY4 "$_role")" = "yes" ]; then
-		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY4 ID)$" > /dev/null || log_error "${_qtype} RRset not signed with key $(key_get KEY4 ID)"
-	elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
-		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY4 ID)$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY4 ID)"
-	fi
-}
-
-response_has_cds_for_key() (
-	awk -v zone="${ZONE%%.}." \
-	    -v ttl="${DNSKEY_TTL}" \
-	    -v qtype="CDS" \
-	    -v keyid="$(key_get "${1}" ID)" \
-	    -v keyalg="$(key_get "${1}" ALG_NUM)" \
-	    -v hashalg="2" \
-	    'BEGIN { ret=1; }
-	     $1 == zone && $2 == ttl && $4 == qtype && $5 == keyid && $6 == keyalg && $7 == hashalg { ret=0; exit; }
-	     END { exit ret; }' \
-	    "$2"
-)
-
-response_has_cdnskey_for_key() (
-	awk -v zone="${ZONE%%.}." \
-	    -v ttl="${DNSKEY_TTL}" \
-	    -v qtype="CDNSKEY" \
-	    -v flags="257" \
-	    -v keyalg="$(key_get "${1}" ALG_NUM)" \
-	    'BEGIN { ret=1; }
-	     $1 == zone && $2 == ttl && $4 == qtype && $5 == flags && $7 == keyalg { ret=0; exit; }
-	     END { exit ret; }' \
-	    "$2"
-)
-
-# Test CDS and CDNSKEY publication.
-check_cds() {
-
-	n=$((n+1))
-	echo_i "check CDS and CDNSKEY rrset are signed correctly for zone ${ZONE} ($n)"
-	ret=0
-
-	dig_with_opts "$ZONE" "@${SERVER}" "CDS" > "dig.out.$DIR.test$n.cds" || log_error "dig ${ZONE} CDS failed"
-	grep "status: NOERROR" "dig.out.$DIR.test$n.cds" > /dev/null || log_error "mismatch status in DNS response"
-
-	dig_with_opts "$ZONE" "@${SERVER}" "CDNSKEY" > "dig.out.$DIR.test$n.cdnskey" || log_error "dig ${ZONE} CDNSKEY failed"
-	grep "status: NOERROR" "dig.out.$DIR.test$n.cdnskey" > /dev/null || log_error "mismatch status in DNS response"
-
-	if [ "$CDS_DELETE" = "no" ]; then
-		grep "CDS.*0 0 0 00" "dig.out.$DIR.test$n.cds" > /dev/null && log_error "unexpected CDS DELETE record in DNS response"
-		grep "CDNSKEY.*0 3 0 AA==" "dig.out.$DIR.test$n.cdnskey" > /dev/null && log_error "unexpected CDNSKEY DELETE record in DNS response"
-	else
-		grep "CDS.*0 0 0 00" "dig.out.$DIR.test$n.cds" > /dev/null || log_error "missing CDS DELETE record in DNS response"
-		grep "CDNSKEY.*0 3 0 AA==" "dig.out.$DIR.test$n.cdnskey" > /dev/null || log_error "missing CDNSKEY DELETE record in DNS response"
-	fi
-
-	if [ "$(key_get KEY1 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY1 STATE_DS)" = "omnipresent" ]; then
-		response_has_cds_for_key KEY1 "dig.out.$DIR.test$n.cds" || log_error "missing CDS record in response for key $(key_get KEY1 ID)"
-		check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK"
-		response_has_cdnskey_for_key KEY1 "dig.out.$DIR.test$n.cdnskey" || log_error "missing CDNSKEY record in response for key $(key_get KEY1 ID)"
-		check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK"
-	elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
-		response_has_cds_for_key KEY1 "dig.out.$DIR.test$n.cds" && log_error "unexpected CDS record in response for key $(key_get KEY1 ID)"
-		# KEY1 should not have an associated CDNSKEY, but there may be
-		# one for another key.  Since the CDNSKEY has no field for key
-		# id, it is hard to check what key the CDNSKEY may belong to
-		# so let's skip this check for now.
-	fi
-
-	if [ "$(key_get KEY2 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY2 STATE_DS)" = "omnipresent" ]; then
-		response_has_cds_for_key KEY2 "dig.out.$DIR.test$n.cds" || log_error "missing CDS record in response for key $(key_get KEY2 ID)"
-		check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK"
-		response_has_cdnskey_for_key KEY2 "dig.out.$DIR.test$n.cdnskey" || log_error "missing CDNSKEY record in response for key $(key_get KEY2 ID)"
-		check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK"
-	elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
-		response_has_cds_for_key KEY2 "dig.out.$DIR.test$n.cds" && log_error "unexpected CDS record in response for key $(key_get KEY2 ID)"
-		# KEY2 should not have an associated CDNSKEY, but there may be
-		# one for another key.  Since the CDNSKEY has no field for key
-		# id, it is hard to check what key the CDNSKEY may belong to
-		# so let's skip this check for now.
-	fi
-
-	if [ "$(key_get KEY3 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY3 STATE_DS)" = "omnipresent" ]; then
-		response_has_cds_for_key KEY3 "dig.out.$DIR.test$n.cds" || log_error "missing CDS record in response for key $(key_get KEY3 ID)"
-		check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK"
-		response_has_cdnskey_for_key KEY3 "dig.out.$DIR.test$n.cdnskey" || log_error "missing CDNSKEY record in response for key $(key_get KEY3 ID)"
-		check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK"
-	elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
-		response_has_cds_for_key KEY3 "dig.out.$DIR.test$n.cds" && log_error "unexpected CDS record in response for key $(key_get KEY3 ID)"
-		# KEY3 should not have an associated CDNSKEY, but there may be
-		# one for another key.  Since the CDNSKEY has no field for key
-		# id, it is hard to check what key the CDNSKEY may belong to
-		# so let's skip this check for now.
-	fi
-
-	if [ "$(key_get KEY4 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DS)" = "omnipresent" ]; then
-		response_has_cds_for_key KEY4 "dig.out.$DIR.test$n.cds" || log_error "missing CDS record in response for key $(key_get KEY4 ID)"
-		check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK"
-		response_has_cdnskey_for_key KEY4 "dig.out.$DIR.test$n.cdnskey" || log_error "missing CDNSKEY record in response for key $(key_get KEY4 ID)"
-		check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK"
-	elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
-		response_has_cds_for_key KEY4 "dig.out.$DIR.test$n.cds" && log_error "unexpected CDS record in response for key $(key_get KEY4 ID)"
-		# KEY4 should not have an associated CDNSKEY, but there may be
-		# one for another key.  Since the CDNSKEY has no field for key
-		# id, it is hard to check what key the CDNSKEY may belong to
-		# so let's skip this check for now.
-	fi
-
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
-
-# Test the apex of a configured zone. This checks that the SOA and DNSKEY
-# RRsets are signed correctly and with the appropriate keys.
-check_apex() {
-	# Test DNSKEY query.
-	_qtype="DNSKEY"
-	n=$((n+1))
-	echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)"
-	ret=0
-	dig_with_opts "$ZONE" "@${SERVER}" $_qtype > "dig.out.$DIR.test$n" || log_error "dig ${ZONE} ${_qtype} failed"
-	grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || log_error "mismatch status in DNS response"
-
-	if [ "$(key_get KEY1 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY1 STATE_DNSKEY)" = "omnipresent" ]; then
-		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY1 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY1 ID)"
-		check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
-		numkeys=$((numkeys+1))
-	elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
-		grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY1 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY1 ID)"
-	fi
-
-	if [ "$(key_get KEY2 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY2 STATE_DNSKEY)" = "omnipresent" ]; then
-		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY2 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY2 ID)"
-		check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
-		numkeys=$((numkeys+1))
-	elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
-		grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY2 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY2 ID)"
-	fi
-
-	if [ "$(key_get KEY3 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY3 STATE_DNSKEY)" = "omnipresent" ]; then
-		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY3 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY3 ID)"
-		check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
-		numkeys=$((numkeys+1))
-	elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
-		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY3 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY3 ID)"
-	fi
-
-	if [ "$(key_get KEY4 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DNSKEY)" = "omnipresent" ]; then
-		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY4 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response for key $(key_get KEY4 ID)"
-		check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
-		numkeys=$((numkeys+1))
-	elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
-		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY4 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && log_error "unexpected ${_qtype} record in response for key $(key_get KEY4 ID)"
-	fi
-
-	lines=$(get_keys_which_signed $_qtype "dig.out.$DIR.test$n" | wc -l)
-	check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-
-	# Test SOA query.
-	_qtype="SOA"
-	n=$((n+1))
-	echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)"
-	ret=0
-	dig_with_opts "$ZONE" "@${SERVER}" $_qtype > "dig.out.$DIR.test$n" || log_error "dig ${ZONE} ${_qtype} failed"
-	grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || log_error "mismatch status in DNS response"
-	grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*" "dig.out.$DIR.test$n" > /dev/null || log_error "missing ${_qtype} record in response"
-	lines=$(get_keys_which_signed $_qtype "dig.out.$DIR.test$n" | wc -l)
-	check_signatures $_qtype "dig.out.$DIR.test$n" "ZSK"
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-
-	# Test CDS and CDNSKEY publication.
-	check_cds
-}
-
-# Test an RRset below the apex and verify it is signed correctly.
-check_subdomain() {
-	_qtype="A"
-	n=$((n+1))
-	echo_i "check ${_qtype} a.${ZONE} rrset is signed correctly for zone ${ZONE} ($n)"
-	ret=0
-	dig_with_opts "a.$ZONE" "@${SERVER}" $_qtype > "dig.out.$DIR.test$n" || log_error "dig a.${ZONE} ${_qtype} failed"
-	grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || log_error "mismatch status in DNS response"
-	grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*10\.0\.0\.1" "dig.out.$DIR.test$n" > /dev/null || log_error "missing a.${ZONE} ${_qtype} record in response"
-	lines=$(get_keys_which_signed $_qtype "dig.out.$DIR.test$n" | wc -l)
-	check_signatures $_qtype "dig.out.$DIR.test$n" "ZSK"
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
-
-# Check if "CDS/CDNSKEY Published" is logged.
-check_cdslog() {
-	_dir=$1
-	_zone=$2
-	_key=$3
-
-	_alg=$(key_get $_key ALG_STR)
-	_id=$(key_get $_key ID)
-
-	n=$((n+1))
-	echo_i "check CDS/CDNSKEY publication is logged in ${_dir}/named.run for key ${_zone}/${_alg}/${_id} ($n)"
-	ret=0
-
-	grep "CDS for key ${_zone}/${_alg}/${_id} is now published" "${_dir}/named.run" > /dev/null || ret=1
-	grep "CDNSKEY for key ${_zone}/${_alg}/${_id} is now published" "${_dir}/named.run" > /dev/null || ret=1
-
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
-
-#
-# Utility to call after 'rndc dnssec -checkds|-rollover'.
-#
-_loadkeys_on() {
-	_server=$1
-	_dir=$2
-	_zone=$3
-
-	nextpart $_dir/named.run > /dev/null
-	rndccmd $_server loadkeys $_zone in $_view > rndc.dnssec.loadkeys.out.$_zone.$n
-
-	if [ "${DYNAMIC}" = "yes" ]; then
-		wait_for_log 20 "zone ${_zone}/IN: next key event" $_dir/named.run || return 1
-	else
-		# inline-signing zone adds "(signed)"
-		wait_for_log 20 "zone ${_zone}/IN (signed): next key event" $_dir/named.run || return 1
-	fi
-}
-
-# Tell named that the DS for the key in given zone has been seen in the
-# parent (this does not actually has to be true, we just issue the command
-# to make named believe it can continue with the rollover).
-rndc_checkds() {
-	_server=$1
-	_dir=$2
-	_key=$3
-	_when=$4
-	_what=$5
-	_zone=$6
-	_view=$7
-
-	_keycmd=""
-	if [ "${_key}" != "-" ]; then
-		_keyid=$(key_get $_key ID)
-		_keycmd=" -key ${_keyid}"
-	fi
-
-	_whencmd=""
-	if [ "${_when}" != "now" ]; then
-		_whencmd=" -when ${_when}"
-	fi
-
-	n=$((n+1))
-	echo_i "calling rndc dnssec -checkds${_keycmd}${_whencmd} ${_what} zone ${_zone} in ${_view} ($n)"
-	ret=0
-
-	rndccmd $_server dnssec -checkds $_keycmd $_whencmd $_what $_zone in $_view > rndc.dnssec.checkds.out.$_zone.$n || log_error "rndc dnssec -checkds${_keycmd}${_whencmd} ${_what} zone ${_zone} failed"
-
-	if [ "$ret" -eq 0 ]; then
-		 _loadkeys_on $_server $_dir $_zone || log_error "loadkeys zone ${_zone} failed ($n)"
-	fi
-
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
-
-# Tell named to schedule a key rollover.
-rndc_rollover() {
-	_server=$1
-	_dir=$2
-	_keyid=$3
-	_when=$4
-	_zone=$5
-	_view=$6
-
-	_whencmd=""
-	if [ "${_when}" != "now" ]; then
-		_whencmd="-when ${_when}"
-	fi
-
-	n=$((n+1))
-	echo_i "calling rndc dnssec -rollover key ${_keyid} ${_whencmd} zone ${_zone} ($n)"
-	ret=0
-
-	rndccmd $_server dnssec -rollover -key $_keyid $_whencmd $_zone in $_view > rndc.dnssec.rollover.out.$_zone.$n || log_error "rndc dnssec -rollover (key ${_keyid} when ${_when}) zone ${_zone} failed"
-
-	_loadkeys_on $_server $_dir $_zone || log_error "loadkeys zone ${_zone} failed ($n)"
-
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
-
 #
 # Zone: default.kasp.
 #
@@ -1358,22 +300,29 @@
 rndccmd 10.53.0.3 reload "$ZONE" > /dev/null || log_error "rndc reload zone ${ZONE} failed"
 
 update_is_signed() {
-	dig_with_opts "a.${ZONE}" "@${SERVER}" A > "dig.out.$DIR.test$n.a" || return 1
-	grep "status: NOERROR" "dig.out.$DIR.test$n.a" > /dev/null || return 1
-	grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" "dig.out.$DIR.test$n.a" > /dev/null || return 1
-	lines=$(get_keys_which_signed A "dig.out.$DIR.test$n.a" | wc -l)
-	test "$lines" -eq 1 || return 1
-	get_keys_which_signed A "dig.out.$DIR.test$n.a" | grep "^${KEY_ID}$" > /dev/null || return 1
+	ip_a=$1
+	ip_d=$2
 
-	dig_with_opts "d.${ZONE}" "@${SERVER}" A > "dig.out.$DIR.test$n".d || return 1
-	grep "status: NOERROR" "dig.out.$DIR.test$n".d > /dev/null || return 1
-	grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" "dig.out.$DIR.test$n".d > /dev/null || return 1
-	lines=$(get_keys_which_signed A "dig.out.$DIR.test$n".d | wc -l)
-	test "$lines" -eq 1 || return 1
-	get_keys_which_signed A "dig.out.$DIR.test$n".d | grep "^${KEY_ID}$" > /dev/null || return 1
+	if [ "$ip_a" != "-" ]; then
+		dig_with_opts "a.${ZONE}" "@${SERVER}" A > "dig.out.$DIR.test$n.a" || return 1
+		grep "status: NOERROR" "dig.out.$DIR.test$n.a" > /dev/null || return 1
+		grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*${ip_a}" "dig.out.$DIR.test$n.a" > /dev/null || return 1
+		lines=$(get_keys_which_signed A "dig.out.$DIR.test$n.a" | wc -l)
+		test "$lines" -eq 1 || return 1
+		get_keys_which_signed A "dig.out.$DIR.test$n.a" | grep "^${KEY_ID}$" > /dev/null || return 1
+	fi
+
+	if [ "$ip_d" != "-" ]; then
+		dig_with_opts "d.${ZONE}" "@${SERVER}" A > "dig.out.$DIR.test$n".d || return 1
+		grep "status: NOERROR" "dig.out.$DIR.test$n".d > /dev/null || return 1
+		grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*${ip_d}" "dig.out.$DIR.test$n".d > /dev/null || return 1
+		lines=$(get_keys_which_signed A "dig.out.$DIR.test$n".d | wc -l)
+		test "$lines" -eq 1 || return 1
+		get_keys_which_signed A "dig.out.$DIR.test$n".d | grep "^${KEY_ID}$" > /dev/null || return 1
+	fi
 }
 
-retry_quiet 10 update_is_signed || ret=1
+retry_quiet 10 update_is_signed "10.0.0.11" "10.0.0.44" || ret=1
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
@@ -1401,12 +350,42 @@
 echo zone ${ZONE}
 echo server 10.53.0.3 "$PORT"
 echo update del "a.${ZONE}" 300 A 10.0.0.1
-echo update add "a.${ZONE}" 300 A 10.0.0.11
+echo update add "a.${ZONE}" 300 A 10.0.0.101
 echo update add "d.${ZONE}" 300 A 10.0.0.4
 echo send
 ) | $NSUPDATE
 
-retry_quiet 10 update_is_signed || ret=1
+retry_quiet 10 update_is_signed "10.0.0.101" "10.0.0.4" || ret=1
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+# Update zone with nsupdate (reverting the above change).
+n=$((n+1))
+echo_i "nsupdate zone and check that new record is signed for zone ${ZONE} ($n)"
+ret=0
+(
+echo zone ${ZONE}
+echo server 10.53.0.3 "$PORT"
+echo update add "a.${ZONE}" 300 A 10.0.0.1
+echo update del "a.${ZONE}" 300 A 10.0.0.101
+echo update del "d.${ZONE}" 300 A 10.0.0.4
+echo send
+) | $NSUPDATE
+
+retry_quiet 10 update_is_signed "10.0.0.1" "-" || ret=1
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+# Update zone with freeze/thaw.
+n=$((n+1))
+echo_i "modify zone file and check that new record is signed for zone ${ZONE} ($n)"
+ret=0
+rndccmd 10.53.0.3 freeze "$ZONE" > /dev/null || log_error "rndc freeze zone ${ZONE} failed"
+sleep 1
+echo "d.${ZONE}. 300 A 10.0.0.44" >> "${DIR}/${ZONE}.db"
+rndccmd 10.53.0.3 thaw "$ZONE" > /dev/null || log_error "rndc thaw zone ${ZONE} failed"
+
+retry_quiet 10 update_is_signed "10.0.0.1" "10.0.0.44" || ret=1
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
@@ -1495,17 +474,24 @@
 
 basefile=$(key_get KEY1 BASEFILE)
 
+_wait_for_metadata() {
+	_expr=$1
+	_file=$2
+	grep "$_expr" $_file > /dev/null || return 1
+	return 0
+}
+
 n=$((n+1))
 echo_i "checkds publish correctly sets DSPublish for zone $ZONE ($n)"
 rndc_checkds "$SERVER" "$DIR" "-" "20190102121314" "published" "$ZONE"
-grep "DSPublish: 20190102121314" "${basefile}.state" > /dev/null || log_error "DSPublish not set in ${basefile}"
+retry_quiet 3 _wait_for_metadata "DSPublish: 20190102121314" "${basefile}.state" || log_error "bad DSPublish in ${basefile}.state"
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
 n=$((n+1))
 echo_i "checkds withdraw correctly sets DSRemoved for zone $ZONE ($n)"
 rndc_checkds "$SERVER" "$DIR" "-" "20200102121314" "withdrawn" "$ZONE"
-grep "DSRemoved: 20200102121314" "${basefile}.state" > /dev/null || log_error "DSRemoved not set in ${basefile}"
+retry_quiet 3 _wait_for_metadata "DSRemoved: 20200102121314" "${basefile}.state" || log_error "bad DSRemoved in ${basefile}.state"
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
@@ -1573,8 +559,8 @@
 n=$((n+1))
 echo_i "checkds withdrawn does not set DSRemoved for zone $ZONE (multiple KSK) ($n)"
 rndc_checkds "$SERVER" "$DIR" "-" "20190102121314" "withdrawn" "$ZONE"
-grep "DSRemoved:" "${basefile1}.state" > /dev/null && log_error "DSPublish incorrectly set in ${basefile1}"
-grep "DSRemoved:" "${basefile2}.state" > /dev/null && log_error "DSPublish incorrectly set in ${basefile2}"
+grep "DSRemoved:" "${basefile1}.state" > /dev/null && log_error "DSRemoved incorrectly set in ${basefile1}"
+grep "DSRemoved:" "${basefile2}.state" > /dev/null && log_error "DSRemoved incorrectly set in ${basefile2}"
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
@@ -1597,7 +583,7 @@
 n=$((n+1))
 echo_i "checkds published -key correctly sets DSPublish for key $(key_get KEY1 ID) zone $ZONE (multiple KSK) ($n)"
 rndc_checkds "$SERVER" "$DIR" KEY1 "20190102121314" "published" "$ZONE"
-grep "DSPublish: 20190102121314" "${basefile1}.state" > /dev/null || log_error "DSPublish not set in ${basefile1}"
+retry_quiet 3 _wait_for_metadata "DSPublish: 20190102121314" "${basefile1}.state" || log_error "bad DSPublish in ${basefile1}.state"
 grep "DSPublish:" "${basefile2}.state" > /dev/null && log_error "DSPublish incorrectly set in ${basefile2}"
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
@@ -1605,8 +591,8 @@
 n=$((n+1))
 echo_i "checkds withdrawn -key correctly sets DSRemoved for key $(key_get KEY2 ID) zone $ZONE (multiple KSK) ($n)"
 rndc_checkds "$SERVER" "$DIR" KEY2 "20200102121314" "withdrawn" "$ZONE"
-grep "DSRemoved: 20200102121314" "${basefile2}.state" > /dev/null || log_error "DSRemoved not set in ${basefile2}"
 grep "DSRemoved:" "${basefile1}.state" > /dev/null && log_error "DSPublish incorrectly set in ${basefile1}"
+retry_quiet 3 _wait_for_metadata "DSRemoved: 20200102121314" "${basefile2}.state" || log_error "bad DSRemoved in ${basefile2}.state"
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
@@ -1645,14 +631,14 @@
 n=$((n+1))
 echo_i "checkds publish correctly sets DSPublish for zone $ZONE ($n)"
 rndc_checkds "$SERVER" "$DIR" "-" "20190102121314" "published" "$ZONE"
-grep "DSPublish: 20190102121314" "${basefile}.state" > /dev/null || log_error "DSPublish not set in ${basefile}"
+retry_quiet 3 _wait_for_metadata "DSPublish: 20190102121314" "${basefile}.state" || log_error "bad DSPublish in ${basefile}.state"
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
 n=$((n+1))
 echo_i "checkds withdraw correctly sets DSRemoved for zone $ZONE ($n)"
 rndc_checkds "$SERVER" "$DIR" "-" "20200102121314" "withdrawn" "$ZONE"
-grep "DSRemoved: 20200102121314" "${basefile}.state" > /dev/null || log_error "DSRemoved not set in ${basefile}"
+retry_quiet 3 _wait_for_metadata "DSRemoved: 20200102121314" "${basefile}.state" || log_error "bad DSRemoved in ${basefile}.state"
 test "$ret" -eq 0 || echo_i "failed"
 status=$((status+ret))
 
@@ -3045,13 +2031,10 @@
 check_subdomain
 dnssec_verify
 
-check_next_key_event() {
+_check_next_key_event() {
 	_expect=$1
 
-	n=$((n+1))
-	echo_i "check next key event for zone ${ZONE} ($n)"
-	ret=0
-	grep "zone ${ZONE}.*: next key event in .* seconds" "${DIR}/named.run" > "keyevent.out.$ZONE.test$n" || log_error "no next key event for zone ${ZONE}"
+	grep "zone ${ZONE}.*: next key event in .* seconds" "${DIR}/named.run" > "keyevent.out.$ZONE.test$n" || return 1
 
 	# Get the latest next key event.
 	if [ "${DYNAMIC}" = "yes" ]; then
@@ -3066,11 +2049,21 @@
 	_expectmin=$((_expect-next_key_event_threshold))
 	_expectmax=$((_expect+next_key_event_threshold))
 
-	test $_expectmin -le "$_time" || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})"
-	test $_expectmax -ge "$_time" || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})"
+	test $_expectmin -le "$_time" || return 1
+	test $_expectmax -ge "$_time" || return 1
 
+	return 0
+}
+
+check_next_key_event() {
+	n=$((n+1))
+	echo_i "check next key event for zone ${ZONE} ($n)"
+	ret=0
+
+	retry_quiet 3 _check_next_key_event $1 || log_error "bad next key event time for zone ${ZONE} (expect ${_expect})"
 	test "$ret" -eq 0 || echo_i "failed"
 	status=$((status+ret))
+
 }
 
 # Next key event is when the DNSKEY RRset becomes OMNIPRESENT: DNSKEY TTL plus
@@ -4466,186 +3459,6 @@
 check_next_key_event 3600
 
 #
-# Testing good migration.
-#
-set_zone "migrate.kasp"
-set_policy "none" "2" "7200"
-set_server "ns6" "10.53.0.6"
-
-init_migration_match() {
-	key_clear        "KEY1"
-	key_set          "KEY1" "LEGACY" "yes"
-	set_keyrole      "KEY1" "ksk"
-	set_keylifetime  "KEY1" "0"
-	set_keyalgorithm "KEY1" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS"
-	set_keysigning   "KEY1" "yes"
-	set_zonesigning  "KEY1" "no"
-
-	key_clear        "KEY2"
-	key_set          "KEY2" "LEGACY" "yes"
-	set_keyrole      "KEY2" "zsk"
-	set_keylifetime  "KEY2" "5184000"
-	set_keyalgorithm "KEY2" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS"
-	set_keysigning   "KEY2" "no"
-	set_zonesigning  "KEY2" "yes"
-
-	key_clear        "KEY3"
-	key_clear        "KEY4"
-
-	set_keystate "KEY1" "GOAL"         "omnipresent"
-	set_keystate "KEY1" "STATE_DNSKEY" "rumoured"
-	set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
-	set_keystate "KEY1" "STATE_DS"     "rumoured"
-
-	set_keystate "KEY2" "GOAL"         "omnipresent"
-	set_keystate "KEY2" "STATE_DNSKEY" "rumoured"
-	set_keystate "KEY2" "STATE_ZRRSIG" "rumoured"
-}
-init_migration_match
-
-# Make sure the zone is signed with legacy keys.
-check_keys
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-
-# These keys are immediately published and activated.
-rollover_predecessor_keytimes 0
-check_keytimes
-check_apex
-check_subdomain
-dnssec_verify
-
-# Remember legacy key tags.
-_migrate_ksk=$(key_get KEY1 ID)
-_migrate_zsk=$(key_get KEY2 ID)
-
-#
-# Testing migration with unmatched existing keys (different algorithm).
-#
-set_zone "migrate-nomatch-algnum.kasp"
-set_policy "none" "2" "300"
-set_server "ns6" "10.53.0.6"
-
-init_migration_nomatch_algnum() {
-	key_clear        "KEY1"
-	key_set          "KEY1" "LEGACY" "yes"
-	set_keyrole      "KEY1" "ksk"
-	set_keyalgorithm "KEY1" "5" "RSASHA1" "2048"
-	set_keysigning   "KEY1" "yes"
-	set_zonesigning  "KEY1" "no"
-
-	key_clear        "KEY2"
-	key_set          "KEY2" "LEGACY" "yes"
-	set_keyrole      "KEY2" "zsk"
-	set_keyalgorithm "KEY2" "5" "RSASHA1" "1024"
-	set_keysigning   "KEY2" "no"
-	set_zonesigning  "KEY2" "yes"
-
-	key_clear        "KEY3"
-	key_clear        "KEY4"
-
-	set_keystate "KEY1" "GOAL"         "omnipresent"
-	set_keystate "KEY1" "STATE_DNSKEY" "omnipresent"
-	set_keystate "KEY1" "STATE_KRRSIG" "omnipresent"
-	set_keystate "KEY1" "STATE_DS"     "omnipresent"
-
-	set_keystate "KEY2" "GOAL"         "omnipresent"
-	set_keystate "KEY2" "STATE_DNSKEY" "omnipresent"
-	set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent"
-}
-init_migration_nomatch_algnum
-
-# Make sure the zone is signed with legacy keys.
-check_keys
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-
-# The KSK is immediately published and activated.
-# -P     : now-3900s
-# -P sync: now-3h
-# -A     : now-3900s
-created=$(key_get KEY1 CREATED)
-set_addkeytime "KEY1" "PUBLISHED"   "${created}" -3900
-set_addkeytime "KEY1" "ACTIVE"      "${created}" -3900
-set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -10800
-# The ZSK is immediately published and activated.
-# -P: now-12h
-# -A: now-12h
-created=$(key_get KEY2 CREATED)
-set_addkeytime "KEY2" "PUBLISHED"   "${created}" -43200
-set_addkeytime "KEY2" "ACTIVE"      "${created}" -43200
-check_keytimes
-check_apex
-check_subdomain
-dnssec_verify
-
-# Remember legacy key tags.
-_migratenomatch_algnum_ksk=$(key_get KEY1 ID)
-_migratenomatch_algnum_zsk=$(key_get KEY2 ID)
-
-#
-# Testing migration with unmatched existing keys (different length).
-#
-set_zone "migrate-nomatch-alglen.kasp"
-set_policy "none" "2" "300"
-set_server "ns6" "10.53.0.6"
-
-init_migration_nomatch_alglen() {
-	key_clear        "KEY1"
-	key_set          "KEY1" "LEGACY" "yes"
-	set_keyrole      "KEY1" "ksk"
-	set_keyalgorithm "KEY1" "5" "RSASHA1" "1024"
-	set_keysigning   "KEY1" "yes"
-	set_zonesigning  "KEY1" "no"
-
-	key_clear        "KEY2"
-	key_set          "KEY2" "LEGACY" "yes"
-	set_keyrole      "KEY2" "zsk"
-	set_keyalgorithm "KEY2" "5" "RSASHA1" "1024"
-	set_keysigning   "KEY2" "no"
-	set_zonesigning  "KEY2" "yes"
-
-	key_clear        "KEY3"
-	key_clear        "KEY4"
-
-	set_keystate "KEY1" "GOAL"         "omnipresent"
-	set_keystate "KEY1" "STATE_DNSKEY" "omnipresent"
-	set_keystate "KEY1" "STATE_KRRSIG" "omnipresent"
-	set_keystate "KEY1" "STATE_DS"     "omnipresent"
-
-	set_keystate "KEY2" "GOAL"         "omnipresent"
-	set_keystate "KEY2" "STATE_DNSKEY" "omnipresent"
-	set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent"
-}
-init_migration_nomatch_alglen
-
-# Make sure the zone is signed with legacy keys.
-check_keys
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-
-# Set expected key times:
-# - The KSK is immediately published and activated.
-#   P     : now-3900s
-#   P sync: now-3h
-#   A     : now-3900s
-created=$(key_get KEY1 CREATED)
-set_addkeytime "KEY1" "PUBLISHED"   "${created}" -3900
-set_addkeytime "KEY1" "ACTIVE"      "${created}" -3900
-set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -10800
-# - The ZSK is immediately published and activated.
-#   P: now-12h
-#   A: now-12h
-created=$(key_get KEY2 CREATED)
-set_addkeytime "KEY2" "PUBLISHED"   "${created}" -43200
-set_addkeytime "KEY2" "ACTIVE"      "${created}" -43200
-check_keytimes
-check_apex
-check_subdomain
-dnssec_verify
-
-# Remember legacy key tags.
-_migratenomatch_alglen_ksk=$(key_get KEY1 ID)
-_migratenomatch_alglen_zsk=$(key_get KEY2 ID)
-
-#
 # Testing going insecure.
 #
 
@@ -4653,7 +3466,7 @@
 # Zone step1.going-insecure.kasp
 #
 set_zone "step1.going-insecure.kasp"
-set_policy "migrate" "2" "7200"
+set_policy "unsigning" "2" "7200"
 set_server "ns6" "10.53.0.6"
 
 # Policy parameters.
@@ -4714,7 +3527,7 @@
 
 set_zone "step1.going-insecure-dynamic.kasp"
 set_dynamic
-set_policy "migrate" "2" "7200"
+set_policy "unsigning" "2" "7200"
 set_server "ns6" "10.53.0.6"
 init_migration_insecure
 
@@ -4825,8 +3638,6 @@
 set_zone "step2.going-insecure.kasp"
 set_policy "none" "2" "7200"
 set_server "ns6" "10.53.0.6"
-# Expect a CDS/CDNSKEY Delete Record.
-set_cdsdelete
 
 # The DS is long enough removed from the zone to be considered HIDDEN.
 # This means the DNSKEY and the KSK signatures can be removed.
@@ -4895,8 +3706,6 @@
 set_dynamic
 set_policy "none" "2" "7200"
 set_server "ns6" "10.53.0.6"
-# Expect a CDS/CDNSKEY Delete Record.
-set_cdsdelete
 
 # The DS is long enough removed from the zone to be considered HIDDEN.
 # This means the DNSKEY and the KSK signatures can be removed.
@@ -4921,287 +3730,6 @@
 check_next_key_event 7500
 
 #
-# Testing migration.
-#
-set_zone "migrate.kasp"
-set_policy "migrate" "2" "7200"
-set_server "ns6" "10.53.0.6"
-
-# Key properties, timings and metadata should be the same as legacy keys above.
-# However, because the zsk has a lifetime, kasp will set the retired time.
-init_migration_match
-key_set     "KEY1" "LEGACY"  "no"
-key_set     "KEY2" "LEGACY"  "no"
-
-# Various signing policy checks.
-check_keys
-wait_for_done_signing
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-
-# Set expected key times:
-rollover_predecessor_keytimes 0
-# - Key now has lifetime of 60 days (5184000 seconds).
-#   The key is removed after Iret = TTLsig + Dprp + Dsgn + retire-safety.
-#   TTLsig:        1d (86400 seconds)
-#   Dprp:          5m (300 seconds)
-#   Dsgn:          9d (777600 seconds)
-#   retire-safety: 1h (3600 seconds)
-#   IretZSK:       10d65m (867900 seconds)
-IretZSK=867900
-Lzsk=5184000
-active=$(key_get KEY2 ACTIVE)
-set_addkeytime "KEY2" "RETIRED"     "${active}"  "${Lzsk}"
-retired=$(key_get KEY2 RETIRED)
-set_addkeytime "KEY2" "REMOVED"     "${retired}" "${IretZSK}"
-
-# Continue signing policy checks.
-check_keytimes
-check_apex
-check_subdomain
-dnssec_verify
-
-# Check key tags, should be the same.
-n=$((n+1))
-echo_i "check that of zone ${ZONE} migration to dnssec-policy uses the same keys ($n)"
-ret=0
-[ $_migrate_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
-[ $_migrate_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
-status=$((status+ret))
-
-# Test migration to dnssec-policy, existing keys do not match key algorithm.
-set_zone "migrate-nomatch-algnum.kasp"
-set_policy "migrate-nomatch-algnum" "4" "300"
-set_server "ns6" "10.53.0.6"
-
-# The legacy keys need to be retired, but otherwise stay present until the
-# new keys are omnipresent, and can be used to construct a chain of trust.
-init_migration_nomatch_algnum
-
-key_set      "KEY1" "LEGACY"  "no"
-set_keystate "KEY1" "GOAL"    "hidden"
-
-key_set      "KEY2" "LEGACY"  "no"
-set_keystate "KEY2" "GOAL"    "hidden"
-
-set_keyrole      "KEY3" "ksk"
-set_keylifetime  "KEY3" "0"
-set_keyalgorithm "KEY3" "13" "ECDSAP256SHA256" "256"
-set_keysigning   "KEY3" "yes"
-set_zonesigning  "KEY3" "no"
-
-set_keyrole      "KEY4" "zsk"
-set_keylifetime  "KEY4" "5184000"
-set_keyalgorithm "KEY4" "13" "ECDSAP256SHA256" "256"
-set_keysigning   "KEY4" "no"
-set_zonesigning  "KEY4" "yes"
-
-set_keystate "KEY3" "GOAL"         "omnipresent"
-set_keystate "KEY3" "STATE_DNSKEY" "rumoured"
-set_keystate "KEY3" "STATE_KRRSIG" "rumoured"
-set_keystate "KEY3" "STATE_DS"     "hidden"
-
-set_keystate "KEY4" "GOAL"         "omnipresent"
-set_keystate "KEY4" "STATE_DNSKEY" "rumoured"
-set_keystate "KEY4" "STATE_ZRRSIG" "rumoured"
-
-# Various signing policy checks.
-check_keys
-wait_for_done_signing
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-
-# Set expected key times:
-# - KSK must be retired since it no longer matches the policy.
-#   P     : now-3900s
-#   P sync: now-3h
-#   A     : now-3900s
-# - The key is removed after the retire interval:
-#   IretKSK = TTLds + DprpP + retire_safety.
-#   TTLds:         2h (7200 seconds)
-#   Dprp:          1h (3600 seconds)
-#   retire-safety: 1h (3600 seconds)
-#   IretKSK:       4h (14400 seconds)
-IretKSK=14400
-created=$(key_get KEY1 CREATED)
-set_addkeytime "KEY1" "PUBLISHED"   "${created}" -3900
-set_addkeytime "KEY1" "ACTIVE"      "${created}" -3900
-set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -10800
-keyfile=$(key_get KEY1 BASEFILE)
-grep "; Inactive:" "${keyfile}.key" > retired.test${n}.ksk
-retired=$(awk '{print $3}' < retired.test${n}.ksk)
-set_keytime    "KEY1" "RETIRED" "${retired}"
-set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretKSK}"
-# - ZSK must be retired since it no longer matches the policy.
-#   P: now-12h
-#   A: now-12h
-# - The key is removed after the retire interval:
-#   IretZSK = TTLsig + Dprp + Dsgn + retire-safety.
-#   TTLsig:        11h (39600 seconds)
-#   Dprp:          1h (3600 seconds)
-#   Dsgn:          9d (777600 seconds)
-#   retire-safety: 1h (3600 seconds)
-#   IretZSK:       9d13h (824400 seconds)
-IretZSK=824400
-Lzsk=5184000
-created=$(key_get KEY2 CREATED)
-set_addkeytime "KEY2" "PUBLISHED"   "${created}" -43200
-set_addkeytime "KEY2" "ACTIVE"      "${created}" -43200
-keyfile=$(key_get KEY2 BASEFILE)
-grep "; Inactive:" "${keyfile}.key" > retired.test${n}.zsk
-retired=$(awk '{print $3}' < retired.test${n}.zsk)
-set_keytime    "KEY2" "RETIRED" "${retired}"
-set_addkeytime "KEY2" "REMOVED" "${retired}" "${IretZSK}"
-# - The new KSK is immediately published and activated.
-created=$(key_get KEY3 CREATED)
-set_keytime    "KEY3" "PUBLISHED"   "${created}"
-set_keytime    "KEY3" "ACTIVE"      "${created}"
-# - It takes TTLsig + Dprp + publish-safety hours to propagate the zone.
-#   TTLsig:         11h (39600 seconds)
-#   Dprp:           1h (3600 seconds)
-#   publish-safety: 1h (3600 seconds)
-#   Ipub:           13h (46800 seconds)
-Ipub=46800
-set_addkeytime "KEY3" "SYNCPUBLISH" "${created}" "${Ipub}"
-# - The ZSK is immediately published and activated.
-created=$(key_get KEY4 CREATED)
-set_keytime    "KEY4" "PUBLISHED"   "${created}"
-set_keytime    "KEY4" "ACTIVE"      "${created}"
-active=$(key_get KEY4 ACTIVE)
-set_addkeytime "KEY4" "RETIRED"     "${active}"  "${Lzsk}"
-retired=$(key_get KEY4 RETIRED)
-set_addkeytime "KEY4" "REMOVED"     "${retired}" "${IretZSK}"
-
-# Continue signing policy checks.
-check_keytimes
-check_apex
-check_subdomain
-dnssec_verify
-
-# Check key tags, should be the same.
-n=$((n+1))
-echo_i "check that of zone ${ZONE} migration to dnssec-policy keeps existing keys ($n)"
-ret=0
-[ $_migratenomatch_algnum_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
-[ $_migratenomatch_algnum_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
-status=$((status+ret))
-
-# Test migration to dnssec-policy, existing keys do not match key length.
-set_zone "migrate-nomatch-alglen.kasp"
-set_policy "migrate-nomatch-alglen" "4" "300"
-set_server "ns6" "10.53.0.6"
-
-# The legacy keys need to be retired, but otherwise stay present until the
-# new keys are omnipresent, and can be used to construct a chain of trust.
-init_migration_nomatch_alglen
-
-key_set      "KEY1" "LEGACY"  "no"
-set_keystate "KEY1" "GOAL"    "hidden"
-
-key_set      "KEY2" "LEGACY"  "no"
-set_keystate "KEY2" "GOAL"    "hidden"
-
-set_keyrole      "KEY3" "ksk"
-set_keylifetime  "KEY3" "0"
-set_keyalgorithm "KEY3" "5" "RSASHA1" "2048"
-set_keysigning   "KEY3" "yes"
-set_zonesigning  "KEY3" "no"
-
-set_keyrole      "KEY4" "zsk"
-set_keylifetime  "KEY4" "5184000"
-set_keyalgorithm "KEY4" "5" "RSASHA1" "2048"
-set_keysigning   "KEY4" "no"
-# This key is considered to be prepublished, so it is not yet signing.
-set_zonesigning  "KEY4" "no"
-
-set_keystate "KEY3" "GOAL"         "omnipresent"
-set_keystate "KEY3" "STATE_DNSKEY" "rumoured"
-set_keystate "KEY3" "STATE_KRRSIG" "rumoured"
-set_keystate "KEY3" "STATE_DS"     "hidden"
-
-set_keystate "KEY4" "GOAL"         "omnipresent"
-set_keystate "KEY4" "STATE_DNSKEY" "rumoured"
-set_keystate "KEY4" "STATE_ZRRSIG" "hidden"
-
-# Various signing policy checks.
-check_keys
-wait_for_done_signing
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
-
-# Set expected key times:
-# - KSK must be retired since it no longer matches the policy.
-#   P     : now-3900s
-#   P sync: now-3h
-#   A     : now-3900s
-# - The key is removed after the retire interval:
-#   IretKSK = TTLds + DprpP + retire_safety.
-#   TTLds:         2h (7200 seconds)
-#   Dprp:          1h (3600 seconds)
-#   retire-safety: 1h (3600 seconds)
-#   IretKSK:       4h (14400 seconds)
-IretKSK=14400
-created=$(key_get KEY1 CREATED)
-set_addkeytime "KEY1" "PUBLISHED"   "${created}" -3900
-set_addkeytime "KEY1" "ACTIVE"      "${created}" -3900
-set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -10800
-keyfile=$(key_get KEY1 BASEFILE)
-grep "; Inactive:" "${keyfile}.key" > retired.test${n}.ksk
-retired=$(awk '{print $3}' < retired.test${n}.ksk)
-set_keytime    "KEY1" "RETIRED" "${retired}"
-set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretKSK}"
-# - ZSK must be retired since it no longer matches the policy.
-#   P: now-12h
-#   A: now-12h
-# - The key is removed after the retire interval:
-#   IretZSK = TTLsig + Dprp + Dsgn + retire-safety.
-#   TTLsig:         11h (39600 seconds)
-#   Dprp:           1h (3600 seconds)
-#   Dsgn:           9d (777600 seconds)
-#   publish-safety: 1h (3600 seconds)
-#   IretZSK:        9d13h (824400 seconds)
-IretZSK=824400
-Lzsk=5184000
-created=$(key_get KEY2 CREATED)
-set_addkeytime "KEY2" "PUBLISHED"   "${created}" -43200
-set_addkeytime "KEY2" "ACTIVE"      "${created}" -43200
-keyfile=$(key_get KEY2 BASEFILE)
-grep "; Inactive:" "${keyfile}.key" > retired.test${n}.zsk
-retired=$(awk '{print $3}' < retired.test${n}.zsk)
-set_keytime    "KEY2" "RETIRED" "${retired}"
-set_addkeytime "KEY2" "REMOVED" "${retired}" "${IretZSK}"
-# - The new KSK is immediately published and activated.
-created=$(key_get KEY3 CREATED)
-set_keytime    "KEY3" "PUBLISHED"   "${created}"
-set_keytime    "KEY3" "ACTIVE"      "${created}"
-# - It takes TTLsig + Dprp + publish-safety hours to propagate the zone.
-#   TTLsig:         11h (39600 seconds)
-#   Dprp:           1h (3600 seconds)
-#   publish-safety: 1h (3600 seconds)
-#   Ipub:           13h (46800 seconds)
-Ipub=46800
-set_addkeytime "KEY3" "SYNCPUBLISH" "${created}" "${Ipub}"
-# - The ZSK is immediately published and activated.
-created=$(key_get KEY4 CREATED)
-set_keytime    "KEY4" "PUBLISHED"   "${created}"
-set_keytime    "KEY4" "ACTIVE"      "${created}"
-active=$(key_get KEY4 ACTIVE)
-set_addkeytime "KEY4" "RETIRED"     "${active}"  "${Lzsk}"
-retired=$(key_get KEY4 RETIRED)
-set_addkeytime "KEY4" "REMOVED"     "${retired}" "${IretZSK}"
-
-# Continue signing policy checks.
-check_keytimes
-check_apex
-check_subdomain
-dnssec_verify
-
-# Check key tags, should be the same.
-n=$((n+1))
-echo_i "check that of zone ${ZONE} migration to dnssec-policy keeps existing keys ($n)"
-ret=0
-[ $_migratenomatch_alglen_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
-[ $_migratenomatch_alglen_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
-status=$((status+ret))
-
-#
 # Testing KSK/ZSK algorithm rollover.
 #
 
@@ -5922,230 +4450,5 @@
 # an unlimited lifetime.  Fallback to the default loadkeys interval.
 check_next_key_event 3600
 
-#
-# Testing good migration with views.
-#
-init_view_migration() {
-	key_clear        "KEY1"
-	key_set          "KEY1" "LEGACY" "yes"
-	set_keyrole      "KEY1" "ksk"
-	set_keylifetime  "KEY1" "0"
-	set_keysigning   "KEY1" "yes"
-	set_zonesigning  "KEY1" "no"
-
-	key_clear        "KEY2"
-	key_set          "KEY2" "LEGACY" "yes"
-	set_keyrole      "KEY2" "zsk"
-	set_keylifetime  "KEY2" "0"
-	set_keysigning   "KEY2" "no"
-	set_zonesigning  "KEY2" "yes"
-
-	key_clear        "KEY3"
-	key_clear        "KEY4"
-
-	set_keystate "KEY1" "GOAL"         "omnipresent"
-	set_keystate "KEY1" "STATE_DNSKEY" "rumoured"
-	set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
-	set_keystate "KEY1" "STATE_DS"     "rumoured"
-
-	set_keystate "KEY2" "GOAL"         "omnipresent"
-	set_keystate "KEY2" "STATE_DNSKEY" "rumoured"
-	set_keystate "KEY2" "STATE_ZRRSIG" "rumoured"
-}
-
-set_keytimes_view_migration() {
-	# Key is six months in use.
-	created=$(key_get KEY1 CREATED)
-	set_addkeytime "KEY1" "PUBLISHED"   "${created}" -16070400
-	set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -16070400
-	set_addkeytime "KEY1" "ACTIVE"      "${created}" -16070400
-	created=$(key_get KEY2 CREATED)
-	set_addkeytime "KEY2" "PUBLISHED"   "${created}" -16070400
-	set_addkeytime "KEY2" "ACTIVE"      "${created}" -16070400
-}
-
-# Zone view.rsasha256.kasp (external)
-set_zone "view-rsasha256.kasp"
-set_policy "rsasha256" "2" "21600"
-set_server "ns7" "10.53.0.7"
-init_view_migration
-set_keyalgorithm "KEY1" "8" "RSASHA256" "2048"
-set_keyalgorithm "KEY2" "8" "RSASHA256" "1024"
-TSIG="hmac-sha1:external:$VIEW1"
-wait_for_nsec
-# Make sure the zone is signed with legacy keys.
-check_keys
-set_keytimes_view_migration
-check_keytimes
-dnssec_verify
-
-n=$((n+1))
-# check subdomain
-echo_i "check TXT $ZONE (view ext) rrset is signed correctly ($n)"
-ret=0
-dig_with_opts "view.${ZONE}" "@${SERVER}" TXT > "dig.out.$DIR.test$n.txt" || log_error "dig view.${ZONE} TXT failed"
-grep "status: NOERROR" "dig.out.$DIR.test$n.txt" > /dev/null || log_error "mismatch status in DNS response"
-grep "view.${ZONE}\..*${DEFAULT_TTL}.*IN.*TXT.*external" "dig.out.$DIR.test$n.txt" > /dev/null || log_error "missing view.${ZONE} TXT record in response"
-check_signatures TXT "dig.out.$DIR.test$n.txt" "ZSK"
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status+ret))
-
-# Remember legacy key tags.
-_migrate_ext8_ksk=$(key_get KEY1 ID)
-_migrate_ext8_zsk=$(key_get KEY2 ID)
-
-# Zone view.rsasha256.kasp (internal)
-set_zone "view-rsasha256.kasp"
-set_policy "rsasha256" "2" "21600"
-set_server "ns7" "10.53.0.7"
-init_view_migration
-set_keyalgorithm "KEY1" "8" "RSASHA256" "2048"
-set_keyalgorithm "KEY2" "8" "RSASHA256" "1024"
-TSIG="hmac-sha1:internal:$VIEW2"
-wait_for_nsec
-# Make sure the zone is signed with legacy keys.
-check_keys
-set_keytimes_view_migration
-check_keytimes
-dnssec_verify
-
-n=$((n+1))
-# check subdomain
-echo_i "check TXT $ZONE (view int) rrset is signed correctly ($n)"
-ret=0
-dig_with_opts "view.${ZONE}" "@${SERVER}" TXT > "dig.out.$DIR.test$n.txt" || log_error "dig view.${ZONE} TXT failed"
-grep "status: NOERROR" "dig.out.$DIR.test$n.txt" > /dev/null || log_error "mismatch status in DNS response"
-grep "view.${ZONE}\..*${DEFAULT_TTL}.*IN.*TXT.*internal" "dig.out.$DIR.test$n.txt" > /dev/null || log_error "missing view.${ZONE} TXT record in response"
-check_signatures TXT "dig.out.$DIR.test$n.txt" "ZSK"
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status+ret))
-
-# Remember legacy key tags.
-_migrate_int8_ksk=$(key_get KEY1 ID)
-_migrate_int8_zsk=$(key_get KEY2 ID)
-
-# Reconfig dnssec-policy.
-echo_i "reconfig to switch to dnssec-policy"
-copy_setports ns7/named2.conf.in ns7/named.conf
-rndc_reconfig ns7 10.53.0.7
-
-# Calculate time passed to correctly check for next key events.
-now="$(TZ=UTC date +%s)"
-time_passed=$((now-start_time))
-echo_i "${time_passed} seconds passed between start of tests and reconfig"
-
-#
-# Testing migration (RSASHA256, views).
-#
-set_zone "view-rsasha256.kasp"
-set_policy "rsasha256" "3" "21600"
-set_server "ns7" "10.53.0.7"
-init_migration_match
-set_keyalgorithm "KEY1" "8" "RSASHA256" "2048"
-set_keyalgorithm "KEY2" "8" "RSASHA256" "1024"
-# Key properties, timings and metadata should be the same as legacy keys above.
-# However, because the keys have a lifetime, kasp will set the retired time.
-key_set          "KEY1" "LEGACY" "no"
-set_keylifetime  "KEY1" "31536000"
-set_keystate     "KEY1" "STATE_DNSKEY" "omnipresent"
-set_keystate     "KEY1" "STATE_KRRSIG" "omnipresent"
-set_keystate     "KEY1" "STATE_DS"     "omnipresent"
-
-key_set          "KEY2" "LEGACY" "no"
-set_keylifetime  "KEY2" "8035200"
-set_keystate     "KEY2" "STATE_DNSKEY" "omnipresent"
-set_keystate     "KEY2" "STATE_ZRRSIG" "omnipresent"
-# The ZSK needs to be replaced.
-set_keystate     "KEY2" "GOAL" "hidden"
-set_keystate     "KEY3" "GOAL" "omnipresent"
-set_keyrole      "KEY3" "zsk"
-set_keylifetime  "KEY3" "8035200"
-set_keyalgorithm "KEY3" "8" "RSASHA256" "1024"
-set_keysigning   "KEY3" "no"
-set_zonesigning  "KEY3" "no" # not yet
-set_keystate     "KEY3" "STATE_DNSKEY" "rumoured"
-set_keystate     "KEY3" "STATE_ZRRSIG" "hidden"
-
-# Various signing policy checks (external).
-TSIG="hmac-sha1:external:$VIEW1"
-check_keys
-wait_for_done_signing
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" "external-view"
-set_keytimes_view_migration
-
-# Set expected key times:
-published=$(key_get KEY1 PUBLISHED)
-set_keytime "KEY1" "ACTIVE"      "${published}"
-set_keytime "KEY1" "SYNCPUBLISH" "${published}"
-# Lifetime: 1 year (8035200 seconds)
-active=$(key_get KEY1 ACTIVE)
-set_addkeytime "KEY1" "RETIRED" "${active}"  "31536000"
-# Retire interval:
-# DS TTL:                  1d
-# Parent zone propagation: 3h
-# Retire safety:           1h
-# Total:                   100800 seconds
-retired=$(key_get KEY1 RETIRED)
-set_addkeytime "KEY1" "REMOVED" "${retired}" "100800"
-
-published=$(key_get KEY2 PUBLISHED)
-set_keytime "KEY2" "ACTIVE" "${published}"
-# Lifetime: 3 months (8035200 seconds)
-active=$(key_get KEY2 ACTIVE)
-set_addkeytime "KEY2" "RETIRED" "${active}" "8035200"
-# Retire interval:
-# Sign delay:             9d (14-5)
-# Max zone TTL:           1d
-# Retire safety:          1h
-# Zone propagation delay: 300s
-# Total:                  867900 seconds
-retired=$(key_get KEY2 RETIRED)
-set_addkeytime "KEY2" "REMOVED" "${retired}" "867900"
-
-created=$(key_get KEY3 CREATED)
-set_keytime    "KEY3" "PUBLISHED" "${created}"
-# Publication interval:
-# DNSKEY TTL:             6h
-# Publish safety:         1h
-# Zone propagation delay: 300s
-# Total:                  25500 seconds
-set_addkeytime "KEY3" "ACTIVE" "${created}" "25500"
-# Lifetime: 3 months (8035200 seconds)
-active=$(key_get KEY3 ACTIVE)
-set_addkeytime "KEY3" "RETIRED" "${active}" "8035200"
-# Retire interval:
-# Sign delay:             9d (14-5)
-# Max zone TTL:           1d
-# Retire safety:          1h
-# Zone propagation delay: 300s
-# Total:                  867900 seconds
-retired=$(key_get KEY3 RETIRED)
-set_addkeytime "KEY3" "REMOVED" "${retired}" "867900"
-
-# Continue signing policy checks.
-check_keytimes
-check_apex
-dnssec_verify
-
-# Various signing policy checks (external).
-TSIG="hmac-sha1:internal:$VIEW2"
-check_keys
-wait_for_done_signing
-check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" "internal-view"
-set_keytimes_view_migration
-check_keytimes
-check_apex
-dnssec_verify
-
-# Check key tags, should be the same.
-n=$((n+1))
-echo_i "check that of zone ${ZONE} migration to dnssec-policy uses the same keys ($n)"
-ret=0
-[ $_migrate_ext8_ksk = $_migrate_int8_ksk ] || log_error "mismatch ksk tag"
-[ $_migrate_ext8_zsk = $_migrate_int8_zsk ] || log_error "mismatch zsk tag"
-[ $_migrate_ext8_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
-[ $_migrate_ext8_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
-status=$((status+ret))
-
 echo_i "exit status: $status"
 [ $status -eq 0 ] || exit 1
diff -Nru bind9-9.16.13/bin/tests/system/kasp.sh bind9-9.16.15/bin/tests/system/kasp.sh
--- bind9-9.16.13/bin/tests/system/kasp.sh	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/kasp.sh	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,1095 @@
+#!/bin/sh
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+#
+# Common configuration data for kasp system tests, to be sourced into
+# other shell scripts.
+#
+
+# shellcheck source=conf.sh
+. ../conf.sh
+
+###############################################################################
+# Constants                                                                   #
+###############################################################################
+DEFAULT_TTL=300
+
+###############################################################################
+# Query properties                                                            #
+###############################################################################
+TSIG=""
+SHA1="FrSt77yPTFx6hTs4i2tKLB9LmE0="
+SHA224="hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA=="
+SHA256="R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY="
+VIEW1="YPfMoAk6h+3iN8MDRQC004iSNHY="
+VIEW2="4xILSZQnuO1UKubXHkYUsvBRPu8="
+
+###############################################################################
+# Key properties                                                              #
+###############################################################################
+# ID
+# BASEFILE
+# EXPECT
+# ROLE
+# KSK
+# ZSK
+# LIFETIME
+# ALG_NUM
+# ALG_STR
+# ALG_LEN
+# CREATED
+# PUBLISHED
+# ACTIVE
+# RETIRED
+# REVOKED
+# REMOVED
+# GOAL
+# STATE_DNSKEY
+# STATE_ZRRSIG
+# STATE_KRRSIG
+# STATE_DS
+# EXPECT_ZRRSIG
+# EXPECT_KRRSIG
+# LEGACY
+
+key_key() {
+	echo "${1}__${2}"
+}
+
+key_get() {
+	eval "echo \${$(key_key "$1" "$2")}"
+}
+
+key_set() {
+	eval "$(key_key "$1" "$2")='$3'"
+}
+
+# Save certain values in the KEY array.
+key_save()
+{
+	# Save key id.
+	key_set "$1" ID "$KEY_ID"
+	# Save base filename.
+	key_set "$1" BASEFILE "$BASE_FILE"
+	# Save creation date.
+	key_set "$1" CREATED "${KEY_CREATED}"
+}
+
+# Clear key state.
+#
+# This will update either the KEY1, KEY2, or KEY3 array.
+key_clear() {
+	key_set "$1" "ID" 'no'
+	key_set "$1" "IDPAD" 'no'
+	key_set "$1" "EXPECT" 'no'
+	key_set "$1" "ROLE" 'none'
+	key_set "$1" "KSK" 'no'
+	key_set "$1" "ZSK" 'no'
+	key_set "$1" "LIFETIME" 'none'
+	key_set "$1" "ALG_NUM" '0'
+	key_set "$1" "ALG_STR" 'none'
+	key_set "$1" "ALG_LEN" '0'
+	key_set "$1" "CREATED" '0'
+	key_set "$1" "PUBLISHED" 'none'
+	key_set "$1" "SYNCPUBLISH" 'none'
+	key_set "$1" "ACTIVE" 'none'
+	key_set "$1" "RETIRED" 'none'
+	key_set "$1" "REVOKED" 'none'
+	key_set "$1" "REMOVED" 'none'
+	key_set "$1" "GOAL" 'none'
+	key_set "$1" "STATE_DNSKEY" 'none'
+	key_set "$1" "STATE_KRRSIG" 'none'
+	key_set "$1" "STATE_ZRRSIG" 'none'
+	key_set "$1" "STATE_DS" 'none'
+	key_set "$1" "EXPECT_ZRRSIG" 'no'
+	key_set "$1" "EXPECT_KRRSIG" 'no'
+	key_set "$1" "LEGACY" 'no'
+}
+
+# Start clear.
+# There can be at most 4 keys at the same time during a rollover:
+# 2x KSK, 2x ZSK
+key_clear "KEY1"
+key_clear "KEY2"
+key_clear "KEY3"
+key_clear "KEY4"
+
+###############################################################################
+# Utilities                                                                   #
+###############################################################################
+
+# Call dig with default options.
+_dig_with_opts() {
+
+	if [ -n "$TSIG" ]; then
+		"$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" -y "$TSIG" "$@"
+	else
+		"$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" "$@"
+	fi
+}
+
+# RNDC.
+_rndccmd() {
+	"$RNDC" -c ../common/rndc.conf -p "$CONTROLPORT" -s "$@"
+}
+
+# Print IDs of keys used for generating RRSIG records for RRsets of type $1
+# found in dig output file $2.
+get_keys_which_signed() {
+	_qtype=$1
+	_output=$2
+	# The key ID is the 11th column of the RRSIG record line.
+	awk -v qt="$_qtype" '$4 == "RRSIG" && $5 == qt {print $11}' < "$_output"
+}
+
+# Get the key ids from key files for zone $2 in directory $1.
+get_keyids() {
+	_dir=$1
+	_zone=$2
+	_regex="K${_zone}.+*+*.key"
+
+	find "${_dir}" -mindepth 1 -maxdepth 1 -name "${_regex}" | sed "s,$_dir/K${_zone}.+\([0-9]\{3\}\)+\([0-9]\{5\}\).key,\2,"
+}
+
+# By default log errors and don't quit immediately.
+_log=1
+_log_error() {
+	test $_log -eq 1 && echo_i "error: $1"
+	ret=$((ret+1))
+}
+disable_logerror() {
+	_log=0
+}
+enable_logerror() {
+	_log=1
+}
+
+# Set server key-directory ($1) and address ($2) for testing keys.
+set_server() {
+	DIR=$1
+	SERVER=$2
+}
+# Set zone name for testing keys.
+set_zone() {
+	ZONE=$1
+	DYNAMIC="no"
+}
+# By default zones are considered static.
+# When testing dynamic zones, call 'set_dynamic' after 'set_zone'.
+set_dynamic() {
+	DYNAMIC="yes"
+}
+
+# Set policy settings (name $1, number of keys $2, dnskey ttl $3) for testing keys.
+set_policy() {
+	POLICY=$1
+	NUM_KEYS=$2
+	DNSKEY_TTL=$3
+	CDS_DELETE="no"
+}
+# By default policies are considered to be secure.
+# If a zone sets its policy to "none", call 'set_cdsdelete' to tell the system
+# test to expect a CDS and CDNSKEY Delete record.
+set_cdsdelete() {
+	CDS_DELETE="yes"
+}
+
+# Set key properties for testing keys.
+# $1: Key to update (KEY1, KEY2, ...)
+# $2: Value
+set_keyrole() {
+	key_set "$1" "EXPECT" "yes"
+	key_set "$1" "ROLE" "$2"
+	key_set "$1" "KSK" "no"
+	key_set "$1" "ZSK" "no"
+	test "$2" = "ksk" && key_set "$1" "KSK" "yes"
+	test "$2" = "zsk" && key_set "$1" "ZSK" "yes"
+	test "$2" = "csk" && key_set "$1" "KSK" "yes"
+	test "$2" = "csk" && key_set "$1" "ZSK" "yes"
+}
+set_keylifetime() {
+	key_set "$1" "EXPECT" "yes"
+	key_set "$1" "LIFETIME" "$2"
+}
+# The algorithm value consists of three parts:
+# $2: Algorithm (number)
+# $3: Algorithm (string-format)
+# $4: Algorithm length
+set_keyalgorithm() {
+	key_set "$1" "EXPECT" "yes"
+	key_set "$1" "ALG_NUM" "$2"
+	key_set "$1" "ALG_STR" "$3"
+	key_set "$1" "ALG_LEN" "$4"
+}
+set_keysigning() {
+	key_set "$1" "EXPECT" "yes"
+	key_set "$1" "EXPECT_KRRSIG" "$2"
+}
+set_zonesigning() {
+	key_set "$1" "EXPECT" "yes"
+	key_set "$1" "EXPECT_ZRRSIG" "$2"
+}
+
+# Set key timing metadata. Set to "none" to unset.
+# $1: Key to update (KEY1, KEY2, ...)
+# $2: Time to update (PUBLISHED, SYNCPUBLISH, ACTIVE, RETIRED, REVOKED, or REMOVED).
+# $3: Value
+set_keytime() {
+	key_set "$1" "EXPECT" "yes"
+	key_set "$1" "$2" "$3"
+}
+
+# Set key timing metadata to a value plus additional time.
+# $1: Key to update (KEY1, KEY2, ...)
+# $2: Time to update (PUBLISHED, SYNCPUBLISH, ACTIVE, RETIRED, REVOKED, or REMOVED).
+# $3: Value
+# $4: Additional time.
+set_addkeytime() {
+	if [ -x "$PYTHON" ]; then
+		# Convert "%Y%m%d%H%M%S" format to epoch seconds.
+		# Then, add the additional time (can be negative).
+		_value=$3
+		_plus=$4
+		$PYTHON > python.out.$ZONE.$1.$2 <<EOF
+from datetime import datetime
+from datetime import timedelta
+_now = datetime.strptime("$_value", "%Y%m%d%H%M%S")
+_delta = timedelta(seconds=$_plus)
+_then = _now + _delta
+print(_then.strftime("%Y%m%d%H%M%S"));
+EOF
+		# Set the expected timing metadata.
+		key_set "$1" "$2" $(cat python.out.$ZONE.$1.$2)
+	fi
+}
+
+# Set key state metadata. Set to "none" to unset.
+# $1: Key to update (KEY1, KEY2, ...)
+# $2: Key state to update (GOAL, STATE_DNSKEY, STATE_ZRRSIG, STATE_KRRSIG, or STATE_DS)
+# $3: Value
+set_keystate() {
+	key_set "$1" "EXPECT" "yes"
+	key_set "$1" "$2" "$3"
+}
+
+# Check the key $1 with id $2.
+# This requires environment variables to be set.
+#
+# This will set the following environment variables for testing:
+# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
+# KEY_FILE="${BASE_FILE}.key"
+# PRIVATE_FILE="${BASE_FILE}.private"
+# STATE_FILE="${BASE_FILE}.state"
+# KEY_ID=$(echo $1 | sed 's/^0\{0,4\}//')
+# KEY_CREATED (from the KEY_FILE)
+check_key() {
+	_dir="$DIR"
+	_zone="$ZONE"
+	_role=$(key_get "$1" ROLE)
+	_key_idpad="$2"
+	_key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//')
+	_alg_num=$(key_get "$1" ALG_NUM)
+	_alg_numpad=$(printf "%03d" "$_alg_num")
+	_alg_string=$(key_get "$1" ALG_STR)
+	_length=$(key_get "$1" "ALG_LEN")
+	_dnskey_ttl="$DNSKEY_TTL"
+	_lifetime=$(key_get "$1" LIFETIME)
+	_legacy=$(key_get "$1" LEGACY)
+
+	_published=$(key_get "$1" PUBLISHED)
+	_active=$(key_get "$1" ACTIVE)
+	_retired=$(key_get "$1" RETIRED)
+	_revoked=$(key_get "$1" REVOKED)
+	_removed=$(key_get "$1" REMOVED)
+
+	_goal=$(key_get "$1" GOAL)
+	_state_dnskey=$(key_get "$1" STATE_DNSKEY)
+	_state_zrrsig=$(key_get "$1" STATE_ZRRSIG)
+	_state_krrsig=$(key_get "$1" STATE_KRRSIG)
+	_state_ds=$(key_get "$1" STATE_DS)
+
+	_ksk="no"
+	_zsk="no"
+	if [ "$_role" = "ksk" ]; then
+		_role2="key-signing"
+		_ksk="yes"
+		_flags="257"
+	elif [ "$_role" = "zsk" ]; then
+		_role2="zone-signing"
+		_zsk="yes"
+		_flags="256"
+	elif [ "$_role" = "csk" ]; then
+		_role2="key-signing"
+		_zsk="yes"
+		_ksk="yes"
+		_flags="257"
+	fi
+
+	BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
+	KEY_FILE="${BASE_FILE}.key"
+	PRIVATE_FILE="${BASE_FILE}.private"
+	STATE_FILE="${BASE_FILE}.state"
+	KEY_ID="${_key_id}"
+
+	# Check file existence.
+	[ -s "$KEY_FILE" ] || ret=1
+	[ -s "$PRIVATE_FILE" ] || ret=1
+	if [ "$_legacy" = "no" ]; then
+		[ -s "$STATE_FILE" ] || ret=1
+	fi
+	[ "$ret" -eq 0 ] || _log_error "${BASE_FILE} files missing"
+	[ "$ret" -eq 0 ] || return
+
+	# Retrieve creation date.
+	grep "; Created:" "$KEY_FILE" > "${ZONE}.${KEY_ID}.${_alg_num}.created" || _log_error "mismatch created comment in $KEY_FILE"
+	KEY_CREATED=$(awk '{print $3}' < "${ZONE}.${KEY_ID}.${_alg_num}.created")
+
+	grep "Created: ${KEY_CREATED}" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch created in $PRIVATE_FILE"
+	if [ "$_legacy" = "no" ]; then
+		grep "Generated: ${KEY_CREATED}" "$STATE_FILE" > /dev/null || _log_error "mismatch generated in $STATE_FILE"
+	fi
+
+	test $_log -eq 1 && echo_i "check key file $BASE_FILE"
+
+	# Check the public key file.
+	grep "This is a ${_role2} key, keyid ${_key_id}, for ${_zone}." "$KEY_FILE" > /dev/null || _log_error "mismatch top comment in $KEY_FILE"
+	grep "${_zone}\. ${_dnskey_ttl} IN DNSKEY ${_flags} 3 ${_alg_num}" "$KEY_FILE" > /dev/null || _log_error "mismatch DNSKEY record in $KEY_FILE"
+	# Now check the private key file.
+	grep "Private-key-format: v1.3" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch private key format in $PRIVATE_FILE"
+	grep "Algorithm: ${_alg_num} (${_alg_string})" "$PRIVATE_FILE" > /dev/null || _log_error "mismatch algorithm in $PRIVATE_FILE"
+	# Now check the key state file.
+	if [ "$_legacy" = "no" ]; then
+		grep "This is the state of key ${_key_id}, for ${_zone}." "$STATE_FILE" > /dev/null || _log_error "mismatch top comment in $STATE_FILE"
+		if [ "$_lifetime" = "none" ]; then
+			grep "Lifetime: " "$STATE_FILE" > /dev/null && _log_error "unexpected lifetime in $STATE_FILE"
+		else
+			grep "Lifetime: ${_lifetime}" "$STATE_FILE" > /dev/null || _log_error "mismatch lifetime in $STATE_FILE"
+		fi
+		grep "Algorithm: ${_alg_num}" "$STATE_FILE" > /dev/null || _log_error "mismatch algorithm in $STATE_FILE"
+		grep "Length: ${_length}" "$STATE_FILE" > /dev/null || _log_error "mismatch length in $STATE_FILE"
+		grep "KSK: ${_ksk}" "$STATE_FILE" > /dev/null || _log_error "mismatch ksk in $STATE_FILE"
+		grep "ZSK: ${_zsk}" "$STATE_FILE" > /dev/null || _log_error "mismatch zsk in $STATE_FILE"
+
+		# Check key states.
+		if [ "$_goal" = "none" ]; then
+			grep "GoalState: " "$STATE_FILE" > /dev/null && _log_error "unexpected goal state in $STATE_FILE"
+		else
+			grep "GoalState: ${_goal}" "$STATE_FILE" > /dev/null || _log_error "mismatch goal state in $STATE_FILE"
+		fi
+
+		if [ "$_state_dnskey" = "none" ]; then
+			grep "DNSKEYState: " "$STATE_FILE" > /dev/null && _log_error "unexpected dnskey state in $STATE_FILE"
+			grep "DNSKEYChange: " "$STATE_FILE" > /dev/null && _log_error "unexpected dnskey change in $STATE_FILE"
+		else
+			grep "DNSKEYState: ${_state_dnskey}" "$STATE_FILE" > /dev/null || _log_error "mismatch dnskey state in $STATE_FILE"
+			grep "DNSKEYChange: " "$STATE_FILE" > /dev/null || _log_error "mismatch dnskey change in $STATE_FILE"
+		fi
+
+		if [ "$_state_zrrsig" = "none" ]; then
+			grep "ZRRSIGState: " "$STATE_FILE" > /dev/null && _log_error "unexpected zrrsig state in $STATE_FILE"
+			grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null && _log_error "unexpected zrrsig change in $STATE_FILE"
+		else
+			grep "ZRRSIGState: ${_state_zrrsig}" "$STATE_FILE" > /dev/null || _log_error "mismatch zrrsig state in $STATE_FILE"
+			grep "ZRRSIGChange: " "$STATE_FILE" > /dev/null || _log_error "mismatch zrrsig change in $STATE_FILE"
+		fi
+
+		if [ "$_state_krrsig" = "none" ]; then
+			grep "KRRSIGState: " "$STATE_FILE" > /dev/null && _log_error "unexpected krrsig state in $STATE_FILE"
+			grep "KRRSIGChange: " "$STATE_FILE" > /dev/null && _log_error "unexpected krrsig change in $STATE_FILE"
+		else
+			grep "KRRSIGState: ${_state_krrsig}" "$STATE_FILE" > /dev/null || _log_error "mismatch krrsig state in $STATE_FILE"
+			grep "KRRSIGChange: " "$STATE_FILE" > /dev/null || _log_error "mismatch krrsig change in $STATE_FILE"
+		fi
+
+		if [ "$_state_ds" = "none" ]; then
+			grep "DSState: " "$STATE_FILE" > /dev/null && _log_error "unexpected ds state in $STATE_FILE"
+			grep "DSChange: " "$STATE_FILE" > /dev/null && _log_error "unexpected ds change in $STATE_FILE"
+		else
+			grep "DSState: ${_state_ds}" "$STATE_FILE" > /dev/null || _log_error "mismatch ds state in $STATE_FILE"
+			grep "DSChange: " "$STATE_FILE" > /dev/null || _log_error "mismatch ds change in $STATE_FILE"
+		fi
+	fi
+}
+
+# Check the key timing metadata for key $1.
+check_timingmetadata() {
+	_dir="$DIR"
+	_zone="$ZONE"
+	_key_idpad=$(key_get "$1" ID)
+	_key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//')
+	_alg_num=$(key_get "$1" ALG_NUM)
+	_alg_numpad=$(printf "%03d" "$_alg_num")
+
+	_published=$(key_get "$1" PUBLISHED)
+	_active=$(key_get "$1" ACTIVE)
+	_retired=$(key_get "$1" RETIRED)
+	_revoked=$(key_get "$1" REVOKED)
+	_removed=$(key_get "$1" REMOVED)
+
+	_goal=$(key_get "$1" GOAL)
+	_state_dnskey=$(key_get "$1" STATE_DNSKEY)
+	_state_zrrsig=$(key_get "$1" STATE_ZRRSIG)
+	_state_krrsig=$(key_get "$1" STATE_KRRSIG)
+	_state_ds=$(key_get "$1" STATE_DS)
+
+	_base_file=$(key_get "$1" BASEFILE)
+	_key_file="${_base_file}.key"
+	_private_file="${_base_file}.private"
+	_state_file="${_base_file}.state"
+
+	_published=$(key_get "$1" PUBLISHED)
+	_syncpublish=$(key_get "$1" SYNCPUBLISH)
+	_active=$(key_get "$1" ACTIVE)
+	_retired=$(key_get "$1" RETIRED)
+	_revoked=$(key_get "$1" REVOKED)
+	_removed=$(key_get "$1" REMOVED)
+
+	# Check timing metadata.
+	n=$((n+1))
+	echo_i "check key timing metadata for key $1 id ${_key_id} zone ${ZONE} ($n)"
+	ret=0
+
+	if [ "$_published" = "none" ]; then
+		grep "; Publish:" "${_key_file}" > /dev/null && _log_error "unexpected publish comment in ${_key_file}"
+		grep "Publish:" "${_private_file}" > /dev/null && _log_error "unexpected publish in ${_private_file}"
+		if [ "$_legacy" = "no" ]; then
+			grep "Published: " "${_state_file}" > /dev/null && _log_error "unexpected publish in ${_state_file}"
+		fi
+	else
+		grep "; Publish: $_published" "${_key_file}" > /dev/null || _log_error "mismatch publish comment in ${_key_file} (expected ${_published})"
+		grep "Publish: $_published" "${_private_file}" > /dev/null || _log_error "mismatch publish in ${_private_file} (expected ${_published})"
+		if [ "$_legacy" = "no" ]; then
+			grep "Published: $_published" "${_state_file}" > /dev/null || _log_error "mismatch publish in ${_state_file} (expected ${_published})"
+		fi
+	fi
+
+	if [ "$_syncpublish" = "none" ]; then
+		grep "; SyncPublish:" "${_key_file}" > /dev/null && _log_error "unexpected syncpublish comment in ${_key_file}"
+		grep "SyncPublish:" "${_private_file}" > /dev/null && _log_error "unexpected syncpublish in ${_private_file}"
+		if [ "$_legacy" = "no" ]; then
+			grep "PublishCDS: " "${_state_file}" > /dev/null && _log_error "unexpected syncpublish in ${_state_file}"
+		fi
+	else
+		grep "; SyncPublish: $_syncpublish" "${_key_file}" > /dev/null || _log_error "mismatch syncpublish comment in ${_key_file} (expected ${_syncpublish})"
+		grep "SyncPublish: $_syncpublish" "${_private_file}" > /dev/null || _log_error "mismatch syncpublish in ${_private_file} (expected ${_syncpublish})"
+		if [ "$_legacy" = "no" ]; then
+			grep "PublishCDS: $_syncpublish" "${_state_file}" > /dev/null || _log_error "mismatch syncpublish in ${_state_file} (expected ${_syncpublish})"
+		fi
+	fi
+
+	if [ "$_active" = "none" ]; then
+		grep "; Activate:" "${_key_file}" > /dev/null && _log_error "unexpected active comment in ${_key_file}"
+		grep "Activate:" "${_private_file}" > /dev/null && _log_error "unexpected active in ${_private_file}"
+		if [ "$_legacy" = "no" ]; then
+			grep "Active: " "${_state_file}" > /dev/null && _log_error "unexpected active in ${_state_file}"
+		fi
+	else
+		grep "; Activate: $_active" "${_key_file}" > /dev/null || _log_error "mismatch active comment in ${_key_file} (expected ${_active})"
+		grep "Activate: $_active" "${_private_file}" > /dev/null || _log_error "mismatch active in ${_private_file} (expected ${_active})"
+		if [ "$_legacy" = "no" ]; then
+			grep "Active: $_active" "${_state_file}" > /dev/null || _log_error "mismatch active in ${_state_file} (expected ${_active})"
+		fi
+	fi
+
+	if [ "$_retired" = "none" ]; then
+		grep "; Inactive:" "${_key_file}" > /dev/null && _log_error "unexpected retired comment in ${_key_file}"
+		grep "Inactive:" "${_private_file}" > /dev/null && _log_error "unexpected retired in ${_private_file}"
+		if [ "$_legacy" = "no" ]; then
+			grep "Retired: " "${_state_file}" > /dev/null && _log_error "unexpected retired in ${_state_file}"
+		fi
+	else
+		grep "; Inactive: $_retired" "${_key_file}" > /dev/null || _log_error "mismatch retired comment in ${_key_file} (expected ${_retired})"
+		grep "Inactive: $_retired" "${_private_file}" > /dev/null || _log_error "mismatch retired in ${_private_file} (expected ${_retired})"
+		if [ "$_legacy" = "no" ]; then
+			grep "Retired: $_retired" "${_state_file}" > /dev/null || _log_error "mismatch retired in ${_state_file} (expected ${_retired})"
+		fi
+	fi
+
+	if [ "$_revoked" = "none" ]; then
+		grep "; Revoke:" "${_key_file}" > /dev/null && _log_error "unexpected revoked comment in ${_key_file}"
+		grep "Revoke:" "${_private_file}" > /dev/null && _log_error "unexpected revoked in ${_private_file}"
+		if [ "$_legacy" = "no" ]; then
+			grep "Revoked: " "${_state_file}" > /dev/null && _log_error "unexpected revoked in ${_state_file}"
+		fi
+	else
+		grep "; Revoke: $_revoked" "${_key_file}" > /dev/null || _log_error "mismatch revoked comment in ${_key_file} (expected ${_revoked})"
+		grep "Revoke: $_revoked" "${_private_file}" > /dev/null || _log_error "mismatch revoked in ${_private_file} (expected ${_revoked})"
+		if [ "$_legacy" = "no" ]; then
+			grep "Revoked: $_revoked" "${_state_file}" > /dev/null || _log_error "mismatch revoked in ${_state_file} (expected ${_revoked})"
+		fi
+	fi
+
+	if [ "$_removed" = "none" ]; then
+		grep "; Delete:" "${_key_file}" > /dev/null && _log_error "unexpected removed comment in ${_key_file}"
+		grep "Delete:" "${_private_file}" > /dev/null && _log_error "unexpected removed in ${_private_file}"
+		if [ "$_legacy" = "no" ]; then
+			grep "Removed: " "${_state_file}" > /dev/null && _log_error "unexpected removed in ${_state_file}"
+		fi
+	else
+		grep "; Delete: $_removed" "${_key_file}" > /dev/null || _log_error "mismatch removed comment in ${_key_file} (expected ${_removed})"
+		grep "Delete: $_removed" "${_private_file}" > /dev/null || _log_error "mismatch removed in ${_private_file} (expected ${_removed})"
+		if [ "$_legacy" = "no" ]; then
+			grep "Removed: $_removed" "${_state_file}" > /dev/null || _log_error "mismatch removed in ${_state_file} (expected ${_removed})"
+		fi
+	fi
+
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+check_keytimes() {
+	# The script relies on Python to set keytimes.
+	if [ -x "$PYTHON" ]; then
+
+		if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
+			check_timingmetadata "KEY1"
+		fi
+		if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
+			check_timingmetadata "KEY2"
+		fi
+		if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
+			check_timingmetadata "KEY3"
+		fi
+		if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
+			check_timingmetadata "KEY4"
+		fi
+	fi
+}
+
+# Check the key with key id $1 and see if it is unused.
+# This requires environment variables to be set.
+#
+# This will set the following environment variables for testing:
+# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
+# KEY_FILE="${BASE_FILE}.key"
+# PRIVATE_FILE="${BASE_FILE}.private"
+# STATE_FILE="${BASE_FILE}.state"
+# KEY_ID=$(echo $1 | sed 's/^0\{0,4\}//')
+key_unused() {
+	_dir=$DIR
+	_zone=$ZONE
+	_key_idpad=$1
+	_key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//')
+	_alg_num=$2
+        _alg_numpad=$(printf "%03d" "$_alg_num")
+
+	BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}"
+	KEY_FILE="${BASE_FILE}.key"
+	PRIVATE_FILE="${BASE_FILE}.private"
+	STATE_FILE="${BASE_FILE}.state"
+	KEY_ID="${_key_id}"
+
+	test $_log -eq 1 && echo_i "key unused $KEY_ID?"
+
+	# Check file existence.
+	[ -s "$KEY_FILE" ] || ret=1
+	[ -s "$PRIVATE_FILE" ] || ret=1
+	[ -s "$STATE_FILE" ] || ret=1
+	[ "$ret" -eq 0 ] || return
+
+	# Treat keys that have been removed from the zone as unused.
+	_check_removed=1
+	grep "; Created:" "$KEY_FILE" > created.key-${KEY_ID}.test${n} || _check_removed=0
+	grep "; Delete:" "$KEY_FILE" > unused.key-${KEY_ID}.test${n} || _check_removed=0
+	if [ "$_check_removed" -eq 1 ]; then
+		_created=$(awk '{print $3}' < created.key-${KEY_ID}.test${n})
+		_removed=$(awk '{print $3}' < unused.key-${KEY_ID}.test${n})
+		[ "$_removed" -le "$_created" ] && return
+	fi
+
+	# If no timing metadata is set, this key is unused.
+	grep "; Publish:" "$KEY_FILE" > /dev/null && _log_error "unexpected publish comment in $KEY_FILE"
+	grep "; Activate:" "$KEY_FILE" > /dev/null && _log_error "unexpected active comment in $KEY_FILE"
+	grep "; Inactive:" "$KEY_FILE" > /dev/null && _log_error "unexpected retired comment in $KEY_FILE"
+	grep "; Revoke:" "$KEY_FILE" > /dev/null && _log_error "unexpected revoked comment in $KEY_FILE"
+	grep "; Delete:" "$KEY_FILE" > /dev/null && _log_error "unexpected removed comment in $KEY_FILE"
+
+	grep "Publish:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected publish in $PRIVATE_FILE"
+	grep "Activate:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected active in $PRIVATE_FILE"
+	grep "Inactive:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected retired in $PRIVATE_FILE"
+	grep "Revoke:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected revoked in $PRIVATE_FILE"
+	grep "Delete:" "$PRIVATE_FILE" > /dev/null && _log_error "unexpected removed in $PRIVATE_FILE"
+
+	grep "Published: " "$STATE_FILE" > /dev/null && _log_error "unexpected publish in $STATE_FILE"
+	grep "Active: " "$STATE_FILE" > /dev/null && _log_error "unexpected active in $STATE_FILE"
+	grep "Retired: " "$STATE_FILE" > /dev/null && _log_error "unexpected retired in $STATE_FILE"
+	grep "Revoked: " "$STATE_FILE" > /dev/null && _log_error "unexpected revoked in $STATE_FILE"
+	grep "Removed: " "$STATE_FILE" > /dev/null && _log_error "unexpected removed in $STATE_FILE"
+}
+
+# Test: dnssec-verify zone $1.
+dnssec_verify()
+{
+	n=$((n+1))
+	echo_i "dnssec-verify zone ${ZONE} ($n)"
+	ret=0
+	_dig_with_opts "$ZONE" "@${SERVER}" AXFR > dig.out.axfr.test$n || _log_error "dig ${ZONE} AXFR failed"
+	$VERIFY -z -o "$ZONE" dig.out.axfr.test$n > /dev/null || _log_error "dnssec verify zone $ZONE failed"
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+# Wait for the zone to be signed.
+# The apex NSEC record indicates that it is signed.
+_wait_for_nsec() {
+	_dig_with_opts "@${SERVER}" "$ZONE" NSEC > "dig.out.nsec.test$n" || return 1
+	grep "NS SOA" "dig.out.nsec.test$n" > /dev/null || return 1
+	grep "${ZONE}\..*IN.*RRSIG" "dig.out.nsec.test$n" > /dev/null || return 1
+	return 0
+}
+wait_for_nsec() {
+	n=$((n+1))
+	ret=0
+	echo_i "wait for ${ZONE} to be signed ($n)"
+	retry_quiet 10 _wait_for_nsec  || _log_error "wait for ${ZONE} to be signed failed"
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+check_numkeys() {
+	_numkeys=$(get_keyids "$DIR" "$ZONE" | wc -l)
+	test "$_numkeys" -eq "$NUM_KEYS" || return 1
+	return 0
+}
+
+_check_keys() {
+	ret=0
+
+	# Clear key ids.
+	key_set KEY1 ID "no"
+	key_set KEY2 ID "no"
+	key_set KEY3 ID "no"
+	key_set KEY4 ID "no"
+
+	# Check key files.
+	_ids=$(get_keyids "$DIR" "$ZONE")
+	for _id in $_ids; do
+		# There are three key files with the same algorithm.
+		# Check them until a match is found.
+		ret=0
+		echo_i "check key id $_id"
+
+		if [ "no" = "$(key_get KEY1 ID)" ] && [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
+			ret=0
+			check_key "KEY1" "$_id"
+			test "$ret" -eq 0 && key_save KEY1 && continue
+		fi
+		if [ "no" = "$(key_get KEY2 ID)" ] && [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
+			ret=0
+			check_key "KEY2" "$_id"
+			test "$ret" -eq 0 && key_save KEY2 && continue
+		fi
+		if [ "no" = "$(key_get KEY3 ID)" ] && [ "$(key_get KEY3 EXPECT)" = "yes"  ]; then
+			ret=0
+			check_key "KEY3" "$_id"
+			test "$ret" -eq 0 && key_save KEY3 && continue
+		fi
+		if [ "no" = "$(key_get KEY4 ID)" ] && [ "$(key_get KEY4 EXPECT)" = "yes"  ]; then
+			ret=0
+			check_key "KEY4" "$_id"
+			test "$ret" -eq 0 && key_save KEY4 && continue
+		fi
+
+		# This may be an unused key. Assume algorithm of KEY1.
+		ret=0 && key_unused "$_id" "$(key_get KEY1 ALG_NUM)"
+		test "$ret" -eq 0 && continue
+
+		# If ret is still non-zero, none of the files matched.
+		echo_i "failed"
+		return 1
+	done
+
+	return 0
+}
+
+# Check keys for a configured zone. This verifies:
+# 1. The right number of keys exist in the key pool ($1).
+# 2. The right number of keys is active. Checks KEY1, KEY2, KEY3, and KEY4.
+#
+# It is expected that KEY1, KEY2, KEY3, and KEY4 arrays are set correctly.
+# Found key identifiers are stored in the right key array.
+check_keys() {
+	n=$((n+1))
+	echo_i "check keys are created for zone ${ZONE} ($n)"
+	ret=0
+
+	echo_i "check number of keys for zone ${ZONE} in dir ${DIR} ($n)"
+	retry_quiet 10 check_numkeys || ret=1
+	if [ $ret -ne 0 ]; then
+		_numkeys=$(get_keyids "$DIR" "$ZONE" | wc -l)
+		_log_error "bad number of key files ($_numkeys) for zone $ZONE (expected $NUM_KEYS)"
+		status=$((status+ret))
+	fi
+
+	# Temporarily don't log errors because we are searching multiple files.
+	disable_logerror
+
+	retry_quiet 3 _check_keys || ret=1
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+
+	# Turn error logs on again.
+	enable_logerror
+
+	ret=0
+	if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
+		echo_i "KEY1 ID $(key_get KEY1 ID)"
+		test "no" = "$(key_get KEY1 ID)" && _log_error "No KEY1 found for zone ${ZONE}"
+	fi
+	if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
+		echo_i "KEY2 ID $(key_get KEY2 ID)"
+		test "no" = "$(key_get KEY2 ID)" && _log_error "No KEY2 found for zone ${ZONE}"
+	fi
+	if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
+		echo_i "KEY3 ID $(key_get KEY3 ID)"
+		test "no" = "$(key_get KEY3 ID)" && _log_error "No KEY3 found for zone ${ZONE}"
+	fi
+	if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
+		echo_i "KEY4 ID $(key_get KEY4 ID)"
+		test "no" = "$(key_get KEY4 ID)" && _log_error "No KEY4 found for zone ${ZONE}"
+	fi
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+# Call rndc dnssec -status on server $1 for zone $2 and check output.
+# This is a loose verification, it just tests if the right policy
+# name is returned, and if all expected keys are listed.  The rndc
+# dnssec -status output also lists whether a key is published,
+# used for signing, is retired, or is removed, and if not when
+# it is scheduled to do so, and it shows the states for the various
+# DNSSEC records.
+check_dnssecstatus() {
+	_server=$1
+	_policy=$2
+	_zone=$3
+	_view=$4
+
+	n=$((n+1))
+	echo_i "check rndc dnssec -status output for ${_zone} (policy: $_policy) ($n)"
+	ret=0
+
+	_rndccmd $_server dnssec -status $_zone in $_view > rndc.dnssec.status.out.$_zone.$n || _log_error "rndc dnssec -status zone ${_zone} failed"
+
+	grep "dnssec-policy: ${_policy}" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "bad dnssec status for signed zone ${_zone}"
+	if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
+		grep "key: $(key_get KEY1 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "missing key $(key_get KEY1 ID) from dnssec status"
+	fi
+	if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
+		grep "key: $(key_get KEY2 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "missing key $(key_get KEY2 ID) from dnssec status"
+	fi
+	if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
+		grep "key: $(key_get KEY3 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "missing key $(key_get KEY3 ID) from dnssec status"
+	fi
+	if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
+		grep "key: $(key_get KEY4 ID)" rndc.dnssec.status.out.$_zone.$n > /dev/null || _log_error "missing key $(key_get KEY4 ID) from dnssec status"
+	fi
+
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+# Check if RRset of type $1 in file $2 is signed with the right keys.
+# The right keys are the ones that expect a signature and matches the role $3.
+check_signatures() {
+	_qtype=$1
+	_file=$2
+	_role=$3
+
+	if [ "$_role" = "KSK" ]; then
+		_expect_type=EXPECT_KRRSIG
+	elif [ "$_role" = "ZSK" ]; then
+		_expect_type=EXPECT_ZRRSIG
+	fi
+
+	if [ "$(key_get KEY1 "$_expect_type")" = "yes" ] && [ "$(key_get KEY1 "$_role")" = "yes" ]; then
+		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY1 ID)$" > /dev/null || _log_error "${_qtype} RRset not signed with key $(key_get KEY1 ID)"
+	elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
+		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY1 ID)$" > /dev/null && _log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY1 ID)"
+	fi
+
+	if [ "$(key_get KEY2 "$_expect_type")" = "yes" ] && [ "$(key_get KEY2 "$_role")" = "yes" ]; then
+		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY2 ID)$" > /dev/null || _log_error "${_qtype} RRset not signed with key $(key_get KEY2 ID)"
+	elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
+		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY2 ID)$" > /dev/null && _log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY2 ID)"
+	fi
+
+	if [ "$(key_get KEY3 "$_expect_type")" = "yes" ] && [ "$(key_get KEY3 "$_role")" = "yes" ]; then
+		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY3 ID)$" > /dev/null || _log_error "${_qtype} RRset not signed with key $(key_get KEY3 ID)"
+	elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
+		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY3 ID)$" > /dev/null && _log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY3 ID)"
+	fi
+
+	if [ "$(key_get KEY4 "$_expect_type")" = "yes" ] && [ "$(key_get KEY4 "$_role")" = "yes" ]; then
+		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY4 ID)$" > /dev/null || _log_error "${_qtype} RRset not signed with key $(key_get KEY4 ID)"
+	elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
+		get_keys_which_signed "$_qtype" "$_file" | grep "^$(key_get KEY4 ID)$" > /dev/null && _log_error "${_qtype} RRset signed unexpectedly with key $(key_get KEY4 ID)"
+	fi
+}
+
+response_has_cds_for_key() (
+	awk -v zone="${ZONE%%.}." \
+	    -v ttl="${DNSKEY_TTL}" \
+	    -v qtype="CDS" \
+	    -v keyid="$(key_get "${1}" ID)" \
+	    -v keyalg="$(key_get "${1}" ALG_NUM)" \
+	    -v hashalg="2" \
+	    'BEGIN { ret=1; }
+	     $1 == zone && $2 == ttl && $4 == qtype && $5 == keyid && $6 == keyalg && $7 == hashalg { ret=0; exit; }
+	     END { exit ret; }' \
+	    "$2"
+)
+
+response_has_cdnskey_for_key() (
+	awk -v zone="${ZONE%%.}." \
+	    -v ttl="${DNSKEY_TTL}" \
+	    -v qtype="CDNSKEY" \
+	    -v flags="257" \
+	    -v keyalg="$(key_get "${1}" ALG_NUM)" \
+	    'BEGIN { ret=1; }
+	     $1 == zone && $2 == ttl && $4 == qtype && $5 == flags && $7 == keyalg { ret=0; exit; }
+	     END { exit ret; }' \
+	    "$2"
+)
+
+# Test CDS and CDNSKEY publication.
+check_cds() {
+
+	n=$((n+1))
+	echo_i "check CDS and CDNSKEY rrset are signed correctly for zone ${ZONE} ($n)"
+	ret=0
+
+	_dig_with_opts "$ZONE" "@${SERVER}" "CDS" > "dig.out.$DIR.test$n.cds" || _log_error "dig ${ZONE} CDS failed"
+	grep "status: NOERROR" "dig.out.$DIR.test$n.cds" > /dev/null || _log_error "mismatch status in DNS response"
+
+	_dig_with_opts "$ZONE" "@${SERVER}" "CDNSKEY" > "dig.out.$DIR.test$n.cdnskey" || _log_error "dig ${ZONE} CDNSKEY failed"
+	grep "status: NOERROR" "dig.out.$DIR.test$n.cdnskey" > /dev/null || _log_error "mismatch status in DNS response"
+
+	if [ "$CDS_DELETE" = "no" ]; then
+		grep "CDS.*0 0 0 00" "dig.out.$DIR.test$n.cds" > /dev/null && _log_error "unexpected CDS DELETE record in DNS response"
+		grep "CDNSKEY.*0 3 0 AA==" "dig.out.$DIR.test$n.cdnskey" > /dev/null && _log_error "unexpected CDNSKEY DELETE record in DNS response"
+	else
+		grep "CDS.*0 0 0 00" "dig.out.$DIR.test$n.cds" > /dev/null || _log_error "missing CDS DELETE record in DNS response"
+		grep "CDNSKEY.*0 3 0 AA==" "dig.out.$DIR.test$n.cdnskey" > /dev/null || _log_error "missing CDNSKEY DELETE record in DNS response"
+	fi
+
+	if [ "$(key_get KEY1 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY1 STATE_DS)" = "omnipresent" ]; then
+		response_has_cds_for_key KEY1 "dig.out.$DIR.test$n.cds" || _log_error "missing CDS record in response for key $(key_get KEY1 ID)"
+		check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK"
+		response_has_cdnskey_for_key KEY1 "dig.out.$DIR.test$n.cdnskey" || _log_error "missing CDNSKEY record in response for key $(key_get KEY1 ID)"
+		check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK"
+	elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
+		response_has_cds_for_key KEY1 "dig.out.$DIR.test$n.cds" && _log_error "unexpected CDS record in response for key $(key_get KEY1 ID)"
+		# KEY1 should not have an associated CDNSKEY, but there may be
+		# one for another key.  Since the CDNSKEY has no field for key
+		# id, it is hard to check what key the CDNSKEY may belong to
+		# so let's skip this check for now.
+	fi
+
+	if [ "$(key_get KEY2 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY2 STATE_DS)" = "omnipresent" ]; then
+		response_has_cds_for_key KEY2 "dig.out.$DIR.test$n.cds" || _log_error "missing CDS record in response for key $(key_get KEY2 ID)"
+		check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK"
+		response_has_cdnskey_for_key KEY2 "dig.out.$DIR.test$n.cdnskey" || _log_error "missing CDNSKEY record in response for key $(key_get KEY2 ID)"
+		check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK"
+	elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
+		response_has_cds_for_key KEY2 "dig.out.$DIR.test$n.cds" && _log_error "unexpected CDS record in response for key $(key_get KEY2 ID)"
+		# KEY2 should not have an associated CDNSKEY, but there may be
+		# one for another key.  Since the CDNSKEY has no field for key
+		# id, it is hard to check what key the CDNSKEY may belong to
+		# so let's skip this check for now.
+	fi
+
+	if [ "$(key_get KEY3 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY3 STATE_DS)" = "omnipresent" ]; then
+		response_has_cds_for_key KEY3 "dig.out.$DIR.test$n.cds" || _log_error "missing CDS record in response for key $(key_get KEY3 ID)"
+		check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK"
+		response_has_cdnskey_for_key KEY3 "dig.out.$DIR.test$n.cdnskey" || _log_error "missing CDNSKEY record in response for key $(key_get KEY3 ID)"
+		check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK"
+	elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
+		response_has_cds_for_key KEY3 "dig.out.$DIR.test$n.cds" && _log_error "unexpected CDS record in response for key $(key_get KEY3 ID)"
+		# KEY3 should not have an associated CDNSKEY, but there may be
+		# one for another key.  Since the CDNSKEY has no field for key
+		# id, it is hard to check what key the CDNSKEY may belong to
+		# so let's skip this check for now.
+	fi
+
+	if [ "$(key_get KEY4 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DS)" = "omnipresent" ]; then
+		response_has_cds_for_key KEY4 "dig.out.$DIR.test$n.cds" || _log_error "missing CDS record in response for key $(key_get KEY4 ID)"
+		check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK"
+		response_has_cdnskey_for_key KEY4 "dig.out.$DIR.test$n.cdnskey" || _log_error "missing CDNSKEY record in response for key $(key_get KEY4 ID)"
+		check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK"
+	elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
+		response_has_cds_for_key KEY4 "dig.out.$DIR.test$n.cds" && _log_error "unexpected CDS record in response for key $(key_get KEY4 ID)"
+		# KEY4 should not have an associated CDNSKEY, but there may be
+		# one for another key.  Since the CDNSKEY has no field for key
+		# id, it is hard to check what key the CDNSKEY may belong to
+		# so let's skip this check for now.
+	fi
+
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+# Test the apex of a configured zone. This checks that the SOA and DNSKEY
+# RRsets are signed correctly and with the appropriate keys.
+check_apex() {
+	# Test DNSKEY query.
+	_qtype="DNSKEY"
+	n=$((n+1))
+	echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)"
+	ret=0
+	_dig_with_opts "$ZONE" "@${SERVER}" $_qtype > "dig.out.$DIR.test$n" || _log_error "dig ${ZONE} ${_qtype} failed"
+	grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || _log_error "mismatch status in DNS response"
+
+	if [ "$(key_get KEY1 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY1 STATE_DNSKEY)" = "omnipresent" ]; then
+		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY1 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || _log_error "missing ${_qtype} record in response for key $(key_get KEY1 ID)"
+		check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
+		numkeys=$((numkeys+1))
+	elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then
+		grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY1 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && _log_error "unexpected ${_qtype} record in response for key $(key_get KEY1 ID)"
+	fi
+
+	if [ "$(key_get KEY2 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY2 STATE_DNSKEY)" = "omnipresent" ]; then
+		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY2 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || _log_error "missing ${_qtype} record in response for key $(key_get KEY2 ID)"
+		check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
+		numkeys=$((numkeys+1))
+	elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then
+		grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY2 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && _log_error "unexpected ${_qtype} record in response for key $(key_get KEY2 ID)"
+	fi
+
+	if [ "$(key_get KEY3 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY3 STATE_DNSKEY)" = "omnipresent" ]; then
+		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY3 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || _log_error "missing ${_qtype} record in response for key $(key_get KEY3 ID)"
+		check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
+		numkeys=$((numkeys+1))
+	elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then
+		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY3 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && _log_error "unexpected ${_qtype} record in response for key $(key_get KEY3 ID)"
+	fi
+
+	if [ "$(key_get KEY4 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DNSKEY)" = "omnipresent" ]; then
+		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY4 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null || _log_error "missing ${_qtype} record in response for key $(key_get KEY4 ID)"
+		check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
+		numkeys=$((numkeys+1))
+	elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then
+		grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*$(key_get KEY4 ALG_NUM)" "dig.out.$DIR.test$n" > /dev/null && _log_error "unexpected ${_qtype} record in response for key $(key_get KEY4 ID)"
+	fi
+
+	lines=$(get_keys_which_signed $_qtype "dig.out.$DIR.test$n" | wc -l)
+	check_signatures $_qtype "dig.out.$DIR.test$n" "KSK"
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+
+	# Test SOA query.
+	_qtype="SOA"
+	n=$((n+1))
+	echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)"
+	ret=0
+	_dig_with_opts "$ZONE" "@${SERVER}" $_qtype > "dig.out.$DIR.test$n" || _log_error "dig ${ZONE} ${_qtype} failed"
+	grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || _log_error "mismatch status in DNS response"
+	grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*" "dig.out.$DIR.test$n" > /dev/null || _log_error "missing ${_qtype} record in response"
+	lines=$(get_keys_which_signed $_qtype "dig.out.$DIR.test$n" | wc -l)
+	check_signatures $_qtype "dig.out.$DIR.test$n" "ZSK"
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+
+	# Test CDS and CDNSKEY publication.
+	check_cds
+}
+
+# Test an RRset below the apex and verify it is signed correctly.
+check_subdomain() {
+	_qtype="A"
+	n=$((n+1))
+	echo_i "check ${_qtype} a.${ZONE} rrset is signed correctly for zone ${ZONE} ($n)"
+	ret=0
+	_dig_with_opts "a.$ZONE" "@${SERVER}" $_qtype > "dig.out.$DIR.test$n" || _log_error "dig a.${ZONE} ${_qtype} failed"
+	grep "status: NOERROR" "dig.out.$DIR.test$n" > /dev/null || _log_error "mismatch status in DNS response"
+	grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*10\.0\.0\.1" "dig.out.$DIR.test$n" > /dev/null || _log_error "missing a.${ZONE} ${_qtype} record in response"
+	lines=$(get_keys_which_signed $_qtype "dig.out.$DIR.test$n" | wc -l)
+	check_signatures $_qtype "dig.out.$DIR.test$n" "ZSK"
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+# Check if "CDS/CDNSKEY Published" is logged.
+check_cdslog() {
+	_dir=$1
+	_zone=$2
+	_key=$3
+
+	_alg=$(key_get $_key ALG_STR)
+	_id=$(key_get $_key ID)
+
+	n=$((n+1))
+	echo_i "check CDS/CDNSKEY publication is logged in ${_dir}/named.run for key ${_zone}/${_alg}/${_id} ($n)"
+	ret=0
+
+	grep "CDS for key ${_zone}/${_alg}/${_id} is now published" "${_dir}/named.run" > /dev/null || ret=1
+	grep "CDNSKEY for key ${_zone}/${_alg}/${_id} is now published" "${_dir}/named.run" > /dev/null || ret=1
+
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+# Tell named that the DS for the key in given zone has been seen in the
+# parent (this does not actually has to be true, we just issue the command
+# to make named believe it can continue with the rollover).
+rndc_checkds() {
+	_server=$1
+	_dir=$2
+	_key=$3
+	_when=$4
+	_what=$5
+	_zone=$6
+	_view=$7
+
+	_keycmd=""
+	if [ "${_key}" != "-" ]; then
+		_keyid=$(key_get $_key ID)
+		_keycmd=" -key ${_keyid}"
+	fi
+
+	_whencmd=""
+	if [ "${_when}" != "now" ]; then
+		_whencmd=" -when ${_when}"
+	fi
+
+	n=$((n+1))
+	echo_i "calling rndc dnssec -checkds${_keycmd}${_whencmd} ${_what} zone ${_zone} in ${_view} ($n)"
+	ret=0
+
+	_rndccmd $_server dnssec -checkds $_keycmd $_whencmd $_what $_zone in $_view > rndc.dnssec.checkds.out.$_zone.$n || _log_error "rndc dnssec -checkds${_keycmd}${_whencmd} ${_what} zone ${_zone} failed"
+
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+# Tell named to schedule a key rollover.
+rndc_rollover() {
+	_server=$1
+	_dir=$2
+	_keyid=$3
+	_when=$4
+	_zone=$5
+	_view=$6
+
+	_whencmd=""
+	if [ "${_when}" != "now" ]; then
+		_whencmd="-when ${_when}"
+	fi
+
+	n=$((n+1))
+	echo_i "calling rndc dnssec -rollover key ${_keyid} ${_whencmd} zone ${_zone} ($n)"
+	ret=0
+
+	_rndccmd $_server dnssec -rollover -key $_keyid $_whencmd $_zone in $_view > rndc.dnssec.rollover.out.$_zone.$n || _log_error "rndc dnssec -rollover (key ${_keyid} when ${_when}) zone ${_zone} failed"
+
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/clean.sh bind9-9.16.15/bin/tests/system/keymgr2kasp/clean.sh
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/clean.sh	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/clean.sh	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+set -e
+
+rm -f ns*/K*.private ns*/K*.key ns*/K*.state
+rm -f ns*/named.conf ns*/kasp.conf
+rm -f ns*/named.memstats ns*/named.run
+rm -f ns*/keygen.out* ns*/signer.out*
+rm -f ns*/zones
+rm -f ns*/dsset-*
+rm -f ns*/*.db ns*/*.db.jnl ns*/*.db.jbk
+rm -f ns*/*.db.signed* ns*/*.db.infile
+rm -f ns*/managed-keys.bind*
+rm -f ns*/*.mkeys*
+rm -f ./*.created
+rm -f ./created.key-*
+rm -f ./dig.out*
+rm -f ./python.out.*
+rm -f ./retired.*
+rm -f ./rndc.dnssec.*
+rm -f ./unused.key*
+
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/kasp.conf.in bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/kasp.conf.in
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/kasp.conf.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/kasp.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+dnssec-policy "migrate" {
+	dnskey-ttl 7200;
+
+	keys {
+		ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
+		zsk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@;
+	};
+};
+
+dnssec-policy "timing-metadata" {
+	dnskey-ttl 300;
+
+	signatures-refresh P1W;
+	signatures-validity P2W;
+	signatures-validity-dnskey P2W;
+
+	keys {
+		ksk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@;
+		zsk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@;
+	};
+
+	// Together 12h
+	zone-propagation-delay 3600;
+	max-zone-ttl 11h;
+
+	// Together 3h
+	parent-propagation-delay pt1h;
+	parent-ds-ttl 7200;
+};
+
+/*
+ * This policy tests migration from existing keys with 1024 bits RSASHA1 keys
+ * to ECDSAP256SHA256 keys.
+ */
+dnssec-policy "migrate-nomatch-algnum" {
+	dnskey-ttl 300;
+
+	keys {
+		ksk key-directory lifetime unlimited algorithm ecdsa256;
+		zsk key-directory lifetime P60D algorithm ecdsa256;
+	};
+
+	// Together 12h
+	zone-propagation-delay 3600;
+	max-zone-ttl 11h;
+
+	// Together 3h
+	parent-propagation-delay pt1h;
+	parent-ds-ttl 7200;
+};
+
+/*
+ * This policy tests migration from existing keys with 1024 bits RSASHA1 keys
+ * to 2048 bits RSASHA1 keys.
+ */
+dnssec-policy "migrate-nomatch-alglen" {
+	dnskey-ttl 300;
+
+	keys {
+		ksk key-directory lifetime unlimited algorithm rsasha1 2048;
+		zsk key-directory lifetime P60D algorithm rsasha1 2048;
+	};
+
+	// Together 12h
+	zone-propagation-delay 3600;
+	max-zone-ttl 11h;
+
+	// Together 3h
+	parent-propagation-delay pt1h;
+	parent-ds-ttl 7200;
+};
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/named2.conf.in bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/named2.conf.in
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/named2.conf.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/named2.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+// NS3
+
+include "kasp.conf";
+
+options {
+	query-source address 10.53.0.3;
+	notify-source 10.53.0.3;
+	transfer-source 10.53.0.3;
+	port @PORT@;
+	pid-file "named.pid";
+	listen-on { 10.53.0.3; };
+	listen-on-v6 { none; };
+	allow-transfer { any; };
+	recursion no;
+};
+
+key rndc_key {
+	secret "1234abcd8765";
+	algorithm hmac-sha256;
+};
+
+controls {
+	inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+/* These are zones that migrate to dnssec-policy. */
+zone "migrate.kasp" {
+	type primary;
+	file "migrate.kasp.db";
+	allow-update { any; };
+	dnssec-policy "migrate";
+};
+
+zone "rumoured.kasp" {
+	type primary;
+	file "rumoured.kasp.db";
+	allow-update { any; };
+	dnssec-policy "timing-metadata";
+};
+
+zone "omnipresent.kasp" {
+	type primary;
+	file "omnipresent.kasp.db";
+	allow-update { any; };
+	dnssec-policy "timing-metadata";
+};
+
+zone "migrate-nomatch-algnum.kasp" {
+	type primary;
+	file "migrate-nomatch-algnum.kasp.db";
+	allow-update { any; };
+	dnssec-policy "migrate-nomatch-algnum";
+};
+
+zone "migrate-nomatch-alglen.kasp" {
+	type primary;
+	file "migrate-nomatch-alglen.kasp.db";
+	allow-update { any; };
+	dnssec-policy "migrate-nomatch-alglen";
+};
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/named.conf.in bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/named.conf.in
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/named.conf.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/named.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+// NS3
+
+options {
+	query-source address 10.53.0.3;
+	notify-source 10.53.0.3;
+	transfer-source 10.53.0.3;
+	port @PORT@;
+	pid-file "named.pid";
+	listen-on { 10.53.0.3; };
+	listen-on-v6 { none; };
+	allow-transfer { any; };
+	recursion no;
+	key-directory ".";
+};
+
+key rndc_key {
+	secret "1234abcd8765";
+	algorithm hmac-sha256;
+};
+
+controls {
+	inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+/* These are zones that migrate to dnssec-policy. */
+zone "migrate.kasp" {
+	type primary;
+	file "migrate.kasp.db";
+	auto-dnssec maintain;
+	allow-update { any; };
+	dnssec-dnskey-kskonly yes;
+	update-check-ksk yes;
+};
+
+zone "rumoured.kasp" {
+	type primary;
+	file "rumoured.kasp.db";
+	auto-dnssec maintain;
+	allow-update { any; };
+	dnssec-dnskey-kskonly yes;
+	update-check-ksk yes;
+};
+
+zone "omnipresent.kasp" {
+	type primary;
+	file "omnipresent.kasp.db";
+	auto-dnssec maintain;
+	allow-update { any; };
+	dnssec-dnskey-kskonly yes;
+	update-check-ksk yes;
+};
+
+zone "migrate-nomatch-algnum.kasp" {
+	type primary;
+	file "migrate-nomatch-algnum.kasp.db";
+	auto-dnssec maintain;
+	allow-update { any; };
+	dnssec-dnskey-kskonly yes;
+	update-check-ksk yes;
+};
+
+zone "migrate-nomatch-alglen.kasp" {
+	type primary;
+	file "migrate-nomatch-alglen.kasp.db";
+	auto-dnssec maintain;
+	allow-update { any; };
+	dnssec-dnskey-kskonly yes;
+	update-check-ksk yes;
+};
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/setup.sh bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/setup.sh
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/setup.sh	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/setup.sh	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,121 @@
+#!/bin/sh -e
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+# shellcheck source=conf.sh
+. ../../conf.sh
+
+echo_i "ns3/setup.sh"
+
+setup() {
+	zone="$1"
+	echo_i "setting up zone: $zone"
+	zonefile="${zone}.db"
+	infile="${zone}.db.infile"
+}
+
+private_type_record() {
+	_zone=$1
+	_algorithm=$2
+	_keyfile=$3
+
+	_id=$(keyfile_to_key_id "$_keyfile")
+
+	printf "%s. 0 IN TYPE65534 %s 5 %02x%04x0000\n" "$_zone" "\\#" "$_algorithm" "$_id"
+}
+
+
+# Make lines shorter by storing key states in environment variables.
+H="HIDDEN"
+R="RUMOURED"
+O="OMNIPRESENT"
+U="UNRETENTIVE"
+
+# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy.
+setup migrate.kasp
+echo "$zone" >> zones
+ksktimes="-P now -A now -P sync now"
+zsktimes="-P now -A now"
+KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
+ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200        $zsktimes $zone 2> keygen.out.$zone.2)
+cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
+private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >> "$infile"
+private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >> "$infile"
+$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
+
+# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy, but this
+# time the existing keys do not match the policy.  The existing keys are
+# RSASHA1 keys, and will be migrated to a dnssec-policy that dictates
+# ECDSAP256SHA256 keys.
+setup migrate-nomatch-algnum.kasp
+echo "$zone" >> zones
+Tds="now-3h"     # Time according to dnssec-policy that DS will be OMNIPRESENT
+Tkey="now-3900s" # DNSKEY TTL + propagation delay
+Tsig="now-12h"   # Zone's maximum TTL + propagation delay
+ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}"
+zsktimes="-P ${Tkey} -A ${Tsig}"
+KSK=$($KEYGEN -a RSASHA1 -b 2048 -L 300 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
+ZSK=$($KEYGEN -a RSASHA1 -b 1024 -L 300        $zsktimes $zone 2> keygen.out.$zone.2)
+cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
+private_type_record $zone 5 "$KSK" >> "$infile"
+private_type_record $zone 5 "$ZSK" >> "$infile"
+$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
+
+# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy, but this
+# time the existing keys do not match the policy.  The existing keys are
+# 1024 bits RSASHA1 keys, and will be migrated to a dnssec-policy that
+# dictates 2048 bits RSASHA1 keys.
+setup migrate-nomatch-alglen.kasp
+echo "$zone" >> zones
+Tds="now-3h"     # Time according to dnssec-policy that DS will be OMNIPRESENT
+Tkey="now-3900s" # DNSKEY TTL + propagation delay
+Tsig="now-12h"   # Zone's maximum TTL + propagation delay
+ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}"
+zsktimes="-P ${Tkey} -A ${Tsig}"
+KSK=$($KEYGEN -a RSASHA1 -b 1024 -L 300 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
+ZSK=$($KEYGEN -a RSASHA1 -b 1024 -L 300        $zsktimes $zone 2> keygen.out.$zone.2)
+cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
+private_type_record $zone 5 "$KSK" >> "$infile"
+private_type_record $zone 5 "$ZSK" >> "$infile"
+$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
+
+#
+# Set up zones to test time metadata correctly sets state.
+#
+
+# Key states expected to be rumoured after migration.
+setup rumoured.kasp
+echo "$zone" >> zones
+Tds="now-2h"
+Tkey="now-300s"
+Tsig="now-11h"
+ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}"
+zsktimes="-P ${Tkey} -A ${Tsig}"
+KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
+ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300        $zsktimes $zone 2> keygen.out.$zone.2)
+cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
+private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >> "$infile"
+private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >> "$infile"
+$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
+
+# Key states expected to be omnipresent after migration.
+setup omnipresent.kasp
+echo "$zone" >> zones
+Tds="now-3h"     # Time according to dnssec-policy that DS will be OMNIPRESENT
+Tkey="now-3900s" # DNSKEY TTL + propagation delay
+Tsig="now-12h"   # Zone's maximum TTL + propagation delay
+ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}"
+zsktimes="-P ${Tkey} -A ${Tsig}"
+KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
+ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300        $zsktimes $zone 2> keygen.out.$zone.2)
+cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile"
+private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >> "$infile"
+private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >> "$infile"
+$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/template.db.in bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/template.db.in
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns3/template.db.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns3/template.db.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,25 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 300
+@		IN	SOA  mname1. . (
+			1       ; serial
+			20      ; refresh (20 seconds)
+			20      ; retry (20 seconds)
+			1814400 ; expire (3 weeks)
+			3600    ; minimum (1 hour)
+			)
+
+			NS	ns3
+ns3			A	10.53.0.3
+
+a			A	10.0.0.1
+b			A	10.0.0.2
+c			A	10.0.0.3
+
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/named2.conf.in bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/named2.conf.in
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/named2.conf.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/named2.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+// NS4
+
+options {
+	query-source address 10.53.0.4;
+	notify-source 10.53.0.4;
+	transfer-source 10.53.0.4;
+	port @PORT@;
+	pid-file "named.pid";
+	listen-on { 10.53.0.4; };
+	listen-on-v6 { none; };
+	allow-transfer { any; };
+	recursion no;
+	key-directory ".";
+};
+
+key rndc_key {
+	secret "1234abcd8765";
+	algorithm hmac-sha256;
+};
+
+controls {
+	inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+key "external" {
+	algorithm "hmac-sha1";
+	secret "YPfMoAk6h+3iN8MDRQC004iSNHY=";
+};
+
+key "internal" {
+	algorithm "hmac-sha1";
+	secret "4xILSZQnuO1UKubXHkYUsvBRPu8=";
+};
+
+dnssec-policy "rsasha256" {
+	keys {
+		zsk key-directory lifetime P3M algorithm 8 1024;
+		ksk key-directory lifetime P1Y algorithm 8 2048;
+	};
+
+	dnskey-ttl 300;
+	publish-safety 1h;
+	retire-safety 1h;
+
+	signatures-refresh 5d;
+	signatures-validity 14d;
+	signatures-validity-dnskey 14d;
+
+	max-zone-ttl 1d;
+	zone-propagation-delay 300;
+
+	parent-ds-ttl 86400;
+	parent-propagation-delay 3h;
+};
+
+view "external-view" {
+	zone "view-rsasha256.kasp" {
+		type master;
+		file "view-rsasha256.kasp.ext.db";
+		dnssec-policy "rsasha256";
+	};
+};
+
+view "internal-view" {
+	zone "view-rsasha256.kasp" {
+		type master;
+		file "view-rsasha256.kasp.int.db";
+		dnssec-policy "rsasha256";
+	};
+};
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/named.conf.in bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/named.conf.in
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/named.conf.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/named.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+// NS4
+
+options {
+	query-source address 10.53.0.4;
+	notify-source 10.53.0.4;
+	transfer-source 10.53.0.4;
+	port @PORT@;
+	pid-file "named.pid";
+	listen-on { 10.53.0.4; };
+	listen-on-v6 { none; };
+	allow-transfer { any; };
+	recursion no;
+	key-directory ".";
+};
+
+key rndc_key {
+	secret "1234abcd8765";
+	algorithm hmac-sha256;
+};
+
+controls {
+	inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+key "external" {
+        algorithm "hmac-sha1";
+        secret "YPfMoAk6h+3iN8MDRQC004iSNHY=";
+};
+
+key "internal" {
+        algorithm "hmac-sha1";
+        secret "4xILSZQnuO1UKubXHkYUsvBRPu8=";
+};
+
+view "ext" {
+        match-clients { key "external"; };
+
+	zone "view-rsasha256.kasp" {
+		type master;
+		file "view-rsasha256.kasp.ext.db";
+		auto-dnssec maintain;
+		inline-signing yes;
+		dnssec-dnskey-kskonly yes;
+		update-check-ksk yes;
+	};
+};
+
+view "int" {
+        match-clients { key "internal"; };
+
+	zone "view-rsasha256.kasp" {
+		type master;
+		file "view-rsasha256.kasp.int.db";
+		auto-dnssec maintain;
+		inline-signing yes;
+		dnssec-dnskey-kskonly yes;
+		update-check-ksk yes;
+	};
+};
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/setup.sh bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/setup.sh
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/setup.sh	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/setup.sh	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,54 @@
+#!/bin/sh -e
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+# shellcheck source=conf.sh
+. ../../conf.sh
+
+echo_i "ns4/setup.sh"
+
+private_type_record() {
+	_zone=$1
+	_algorithm=$2
+	_keyfile=$3
+
+	_id=$(keyfile_to_key_id "$_keyfile")
+
+	printf "%s. 0 IN TYPE65534 %s 5 %02x%04x0000\n" "$_zone" "\\#" "$_algorithm" "$_id"
+}
+
+# Make lines shorter by storing key states in environment variables.
+H="HIDDEN"
+R="RUMOURED"
+O="OMNIPRESENT"
+U="UNRETENTIVE"
+
+zone="view-rsasha256.kasp"
+algo="RSASHA256"
+num="8"
+echo "$zone" >> zones
+
+# Set up zones in views with auto-dnssec maintain to migrate to dnssec-policy.
+# The keys for these zones are in use long enough that they should start a
+# rollover for the ZSK (P3M), but not long enough to initiate a KSK rollover (P1Y).
+ksktimes="-P -186d -A -186d -P sync -186d"
+zsktimes="-P -186d -A -186d"
+KSK=$($KEYGEN -a $algo -L 300 -b 2048 -f KSK $ksktimes $zone 2> keygen.out.$zone.1)
+ZSK=$($KEYGEN -a $algo -L 300 -b 1024        $zsktimes $zone 2> keygen.out.$zone.2)
+
+echo_i "setting up zone $zone (external)"
+view="ext"
+zonefile="${zone}.${view}.db"
+cat template.$view.db.in "${KSK}.key" "${ZSK}.key" > "$zonefile"
+
+echo_i "setting up zone $zone (internal)"
+view="int"
+zonefile="${zone}.${view}.db"
+cat template.$view.db.in "${KSK}.key" "${ZSK}.key" > "$zonefile"
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/template.ext.db.in bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/template.ext.db.in
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/template.ext.db.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/template.ext.db.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,22 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 300
+@		IN	SOA  mname1. . (
+			1       ; serial
+			20      ; refresh (20 seconds)
+			20      ; retry (20 seconds)
+			1814400 ; expire (3 weeks)
+			3600    ; minimum (1 hour)
+			)
+
+			NS	ns4
+ns4			A	10.53.0.4
+
+view			TXT	"external"
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/template.int.db.in bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/template.int.db.in
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/ns4/template.int.db.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/ns4/template.int.db.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,22 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 300
+@		IN	SOA  mname1. . (
+			1       ; serial
+			20      ; refresh (20 seconds)
+			20      ; retry (20 seconds)
+			1814400 ; expire (3 weeks)
+			3600    ; minimum (1 hour)
+			)
+
+			NS	ns4
+ns4			A	10.53.0.4
+
+view			TXT	"internal"
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/README bind9-9.16.15/bin/tests/system/keymgr2kasp/README
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/README	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/README	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,10 @@
+Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+
+See COPYRIGHT in the source root or https://isc.org/copyright.html for terms.
+
+The test setup for migrating to KASP tests.
+
+ns3 is an authoritative server for the various test domains.
+
+ns4 is an authoritative server that tests a specific case where zones
+using views migrate to dnssec-policy.
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/setup.sh bind9-9.16.15/bin/tests/system/keymgr2kasp/setup.sh
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/setup.sh	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/setup.sh	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,32 @@
+#!/bin/sh -e
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+# shellcheck source=conf.sh
+. ../conf.sh
+
+set -e
+
+$SHELL clean.sh
+
+copy_setports ns3/named.conf.in ns3/named.conf
+copy_setports ns4/named.conf.in ns4/named.conf
+
+copy_setports ns3/kasp.conf.in ns3/kasp.conf
+
+# Setup zones
+(
+	cd ns3
+	$SHELL setup.sh
+)
+(
+	cd ns4
+	$SHELL setup.sh
+)
diff -Nru bind9-9.16.13/bin/tests/system/keymgr2kasp/tests.sh bind9-9.16.15/bin/tests/system/keymgr2kasp/tests.sh
--- bind9-9.16.13/bin/tests/system/keymgr2kasp/tests.sh	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/keymgr2kasp/tests.sh	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,948 @@
+#!/bin/sh
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+# shellcheck source=conf.sh
+. ../conf.sh
+# shellcheck source=kasp.sh
+. ../kasp.sh
+
+start_time="$(TZ=UTC date +%s)"
+status=0
+n=0
+
+###############################################################################
+# Utilities                                                                   #
+###############################################################################
+
+# Call dig with default options.
+dig_with_opts() {
+
+	if [ -n "$TSIG" ]; then
+		"$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" -y "$TSIG" "$@"
+	else
+		"$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" "$@"
+	fi
+}
+
+# Log error and increment failure rate.
+log_error() {
+	echo_i "error: $1"
+	ret=$((ret+1))
+}
+
+# Default next key event threshold. May be extended by wait periods.
+next_key_event_threshold=100
+
+###############################################################################
+# Tests                                                                       #
+###############################################################################
+
+set_retired_removed() {
+	_Lkey=$2
+	_Iret=$3
+
+	_active=$(key_get $1 ACTIVE)
+	set_addkeytime "${1}" "RETIRED" "${_active}"  "${_Lkey}"
+	_retired=$(key_get $1 RETIRED)
+	set_addkeytime "${1}" "REMOVED" "${_retired}" "${_Iret}"
+}
+
+rollover_predecessor_keytimes() {
+	_addtime=$1
+
+	_created=$(key_get KEY1 CREATED)
+
+	set_addkeytime  "KEY1" "PUBLISHED"   "${_created}" "${_addtime}"
+	set_addkeytime  "KEY1" "SYNCPUBLISH" "${_created}" "${_addtime}"
+	set_addkeytime  "KEY1" "ACTIVE"      "${_created}" "${_addtime}"
+	[ "$Lksk" = 0 ] || set_retired_removed "KEY1" "${Lksk}" "${IretKSK}"
+
+	_created=$(key_get KEY2 CREATED)
+	set_addkeytime  "KEY2" "PUBLISHED"   "${_created}" "${_addtime}"
+	set_addkeytime  "KEY2" "ACTIVE"      "${_created}" "${_addtime}"
+	[ "$Lzsk" = 0 ] || set_retired_removed "KEY2" "${Lzsk}" "${IretZSK}"
+}
+
+# Policy parameters.
+# Lksk: unlimited
+# Lzsk: unlimited
+Lksk=0
+Lzsk=0
+
+
+#################################################
+# Test state before switching to dnssec-policy. #
+#################################################
+
+# Set expected key properties for migration tests.
+# $1 $2: Algorithm number and string.
+# $3 $4: KSK and ZSK size.
+init_migration_keys() {
+	key_clear        "KEY1"
+	key_set          "KEY1" "LEGACY" "yes"
+	set_keyrole      "KEY1" "ksk"
+	set_keylifetime  "KEY1" "none"
+	set_keyalgorithm "KEY1" "$1" "$2" "$3"
+	set_keysigning   "KEY1" "yes"
+	set_zonesigning  "KEY1" "no"
+
+	key_clear        "KEY2"
+	key_set          "KEY2" "LEGACY" "yes"
+	set_keyrole      "KEY2" "zsk"
+	set_keylifetime  "KEY2" "none"
+	set_keyalgorithm "KEY2" "$1" "$2" "$4"
+	set_keysigning   "KEY2" "no"
+	set_zonesigning  "KEY2" "yes"
+
+	key_clear        "KEY3"
+	key_clear        "KEY4"
+}
+
+# Set expected key states for migration tests.
+# $1: Goal
+# $2: States
+init_migration_states() {
+	set_keystate "KEY1" "GOAL"         "$1"
+	set_keystate "KEY1" "STATE_DNSKEY" "$2"
+	set_keystate "KEY1" "STATE_KRRSIG" "$2"
+	set_keystate "KEY1" "STATE_DS"     "$2"
+
+	set_keystate "KEY2" "GOAL"         "$1"
+	set_keystate "KEY2" "STATE_DNSKEY" "$2"
+	set_keystate "KEY2" "STATE_ZRRSIG" "$2"
+}
+
+#
+# Testing a good migration.
+#
+set_zone "migrate.kasp"
+set_policy "none" "2" "7200"
+set_server "ns3" "10.53.0.3"
+
+init_migration_keys "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" "$DEFAULT_BITS"
+init_migration_states "omnipresent" "rumoured"
+
+# Make sure the zone is signed with legacy keys.
+check_keys
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+# These keys are immediately published and activated.
+rollover_predecessor_keytimes 0
+check_keytimes
+check_apex
+check_subdomain
+dnssec_verify
+# Remember legacy key tags.
+_migrate_ksk=$(key_get KEY1 ID)
+_migrate_zsk=$(key_get KEY2 ID)
+
+#
+# Testing key states derived from key timing metadata (rumoured).
+#
+set_zone "rumoured.kasp"
+set_policy "none" "2" "300"
+set_server "ns3" "10.53.0.3"
+
+init_migration_keys "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" "$DEFAULT_BITS"
+init_migration_states "omnipresent" "rumoured"
+
+# Make sure the zone is signed with legacy keys.
+check_keys
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+check_apex
+check_subdomain
+dnssec_verify
+# Remember legacy key tags.
+_rumoured_ksk=$(key_get KEY1 ID)
+_rumoured_zsk=$(key_get KEY2 ID)
+
+#
+# Testing key states derived from key timing metadata (omnipresent).
+#
+set_zone "omnipresent.kasp"
+set_policy "none" "2" "300"
+set_server "ns3" "10.53.0.3"
+
+init_migration_keys "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" "$DEFAULT_BITS"
+init_migration_states "omnipresent" "omnipresent"
+
+# Make sure the zone is signed with legacy keys.
+check_keys
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+check_apex
+check_subdomain
+dnssec_verify
+# Remember legacy key tags.
+_omnipresent_ksk=$(key_get KEY1 ID)
+_omnipresent_zsk=$(key_get KEY2 ID)
+
+#
+# Testing migration with unmatched existing keys (different algorithm).
+#
+set_zone "migrate-nomatch-algnum.kasp"
+set_policy "none" "2" "300"
+set_server "ns3" "10.53.0.3"
+
+init_migration_keys "5" "RSASHA1" "2048" "1024"
+init_migration_states "omnipresent" "omnipresent"
+
+# Make sure the zone is signed with legacy keys.
+check_keys
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+
+# The KSK is immediately published and activated.
+# -P     : now-3900s
+# -P sync: now-3h
+# -A     : now-3900s
+created=$(key_get KEY1 CREATED)
+set_addkeytime "KEY1" "PUBLISHED"   "${created}" -3900
+set_addkeytime "KEY1" "ACTIVE"      "${created}" -3900
+set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -10800
+# The ZSK is immediately published and activated.
+# -P: now-3900s
+# -A: now-12h
+created=$(key_get KEY2 CREATED)
+set_addkeytime "KEY2" "PUBLISHED"   "${created}" -3900
+set_addkeytime "KEY2" "ACTIVE"      "${created}" -43200
+check_keytimes
+check_apex
+check_subdomain
+dnssec_verify
+
+# Remember legacy key tags.
+_migratenomatch_algnum_ksk=$(key_get KEY1 ID)
+_migratenomatch_algnum_zsk=$(key_get KEY2 ID)
+
+#
+# Testing migration with unmatched existing keys (different length).
+#
+set_zone "migrate-nomatch-alglen.kasp"
+set_policy "none" "2" "300"
+set_server "ns3" "10.53.0.3"
+
+init_migration_keys "5" "RSASHA1" "1024" "1024"
+init_migration_states "omnipresent" "omnipresent"
+
+# Make sure the zone is signed with legacy keys.
+check_keys
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+
+# Set expected key times:
+# - The KSK is immediately published and activated.
+#   P     : now-3900s
+#   P sync: now-3h
+#   A     : now-3900s
+created=$(key_get KEY1 CREATED)
+set_addkeytime "KEY1" "PUBLISHED"   "${created}" -3900
+set_addkeytime "KEY1" "ACTIVE"      "${created}" -3900
+set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -10800
+# - The ZSK is immediately published and activated.
+#   P: now-3900s
+#   A: now-12h
+created=$(key_get KEY2 CREATED)
+set_addkeytime "KEY2" "PUBLISHED"   "${created}" -3900
+set_addkeytime "KEY2" "ACTIVE"      "${created}" -43200
+check_keytimes
+check_apex
+check_subdomain
+dnssec_verify
+
+# Remember legacy key tags.
+_migratenomatch_alglen_ksk=$(key_get KEY1 ID)
+_migratenomatch_alglen_zsk=$(key_get KEY2 ID)
+
+
+#############
+# Reconfig. #
+#############
+echo_i "reconfig (migration to dnssec-policy)"
+copy_setports ns3/named2.conf.in ns3/named.conf
+rndc_reconfig ns3 10.53.0.3
+
+# Calculate time passed to correctly check for next key events.
+now="$(TZ=UTC date +%s)"
+time_passed=$((now-start_time))
+echo_i "${time_passed} seconds passed between start of tests and reconfig"
+
+# Wait until we have seen "zone_rekey done:" message for this key.
+_wait_for_done_signing() {
+	_zone=$1
+
+	_ksk=$(key_get $2 KSK)
+	_zsk=$(key_get $2 ZSK)
+	if [ "$_ksk" = "yes" ]; then
+		_role="KSK"
+		_expect_type=EXPECT_KRRSIG
+	elif [ "$_zsk" = "yes" ]; then
+		_role="ZSK"
+		_expect_type=EXPECT_ZRRSIG
+	fi
+
+	if [ "$(key_get ${2} $_expect_type)" = "yes" ] && [ "$(key_get $2 $_role)" = "yes" ]; then
+		_keyid=$(key_get $2 ID)
+		_keyalg=$(key_get $2 ALG_STR)
+		echo_i "wait for zone ${_zone} is done signing with $2 ${_zone}/${_keyalg}/${_keyid}"
+		grep "zone_rekey done: key ${_keyid}/${_keyalg}" "${DIR}/named.run" > /dev/null || return 1
+	fi
+
+	return 0
+}
+wait_for_done_signing() {
+	n=$((n+1))
+	echo_i "wait for zone ${ZONE} is done signing ($n)"
+	ret=0
+
+	retry_quiet 30 _wait_for_done_signing ${ZONE} KEY1 || ret=1
+	retry_quiet 30 _wait_for_done_signing ${ZONE} KEY2 || ret=1
+	retry_quiet 30 _wait_for_done_signing ${ZONE} KEY3 || ret=1
+	retry_quiet 30 _wait_for_done_signing ${ZONE} KEY4 || ret=1
+
+	test "$ret" -eq 0 || echo_i "failed"
+	status=$((status+ret))
+}
+
+
+################################################
+# Test state after switching to dnssec-policy. #
+################################################
+
+# Policy parameters.
+# ZSK now has lifetime of 60 days (5184000 seconds).
+# The key is removed after Iret = TTLsig + Dprp + Dsgn + retire-safety.
+Lzsk=5184000
+IretZSK=867900
+
+#
+# Testing good migration.
+#
+set_zone "migrate.kasp"
+set_policy "migrate" "2" "7200"
+set_server "ns3" "10.53.0.3"
+
+# Key properties, timings and metadata should be the same as legacy keys above.
+# However, because the zsk has a lifetime, kasp will set the retired time.
+init_migration_keys "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" "$DEFAULT_BITS"
+init_migration_states "omnipresent" "rumoured"
+key_set "KEY1" "LEGACY" "no"
+key_set "KEY2" "LEGACY" "no"
+set_keylifetime "KEY1" "${Lksk}"
+set_keylifetime "KEY2" "${Lzsk}"
+
+# Various signing policy checks.
+check_keys
+wait_for_done_signing
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+
+# Set expected key times:
+rollover_predecessor_keytimes 0
+
+# - Key now has lifetime of 60 days (5184000 seconds).
+#   The key is removed after Iret = TTLsig + Dprp + Dsgn + retire-safety.
+#   TTLsig:        1d (86400 seconds)
+#   Dprp:          5m (300 seconds)
+#   Dsgn:          9d (777600 seconds)
+#   retire-safety: 1h (3600 seconds)
+#   IretZSK:       10d65m (867900 seconds)
+active=$(key_get KEY2 ACTIVE)
+set_addkeytime "KEY2" "RETIRED"     "${active}"  "${Lzsk}"
+retired=$(key_get KEY2 RETIRED)
+set_addkeytime "KEY2" "REMOVED"     "${retired}" "${IretZSK}"
+
+# Continue signing policy checks.
+check_keytimes
+check_apex
+check_subdomain
+dnssec_verify
+
+# Check key tags, should be the same.
+n=$((n+1))
+echo_i "check that of zone ${ZONE} migration to dnssec-policy uses the same keys ($n)"
+ret=0
+[ $_migrate_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
+[ $_migrate_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+#
+# Test migration to dnssec-policy, existing keys do not match key algorithm.
+#
+set_zone "migrate-nomatch-algnum.kasp"
+set_policy "migrate-nomatch-algnum" "4" "300"
+set_server "ns3" "10.53.0.3"
+# The legacy keys need to be retired, but otherwise stay present until the
+# new keys are omnipresent, and can be used to construct a chain of trust.
+init_migration_keys "5" "RSASHA1" "2048" "1024"
+init_migration_states "hidden" "omnipresent"
+key_set "KEY1" "LEGACY" "no"
+key_set "KEY2" "LEGACY" "no"
+
+set_keyrole      "KEY3" "ksk"
+set_keylifetime  "KEY3" "0"
+set_keyalgorithm "KEY3" "13" "ECDSAP256SHA256" "256"
+set_keysigning   "KEY3" "yes"
+set_zonesigning  "KEY3" "no"
+
+set_keyrole      "KEY4" "zsk"
+set_keylifetime  "KEY4" "5184000"
+set_keyalgorithm "KEY4" "13" "ECDSAP256SHA256" "256"
+set_keysigning   "KEY4" "no"
+set_zonesigning  "KEY4" "yes"
+
+set_keystate "KEY3" "GOAL"         "omnipresent"
+set_keystate "KEY3" "STATE_DNSKEY" "rumoured"
+set_keystate "KEY3" "STATE_KRRSIG" "rumoured"
+set_keystate "KEY3" "STATE_DS"     "hidden"
+
+set_keystate "KEY4" "GOAL"         "omnipresent"
+set_keystate "KEY4" "STATE_DNSKEY" "rumoured"
+set_keystate "KEY4" "STATE_ZRRSIG" "rumoured"
+
+# Various signing policy checks.
+check_keys
+wait_for_done_signing
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+
+# Set expected key times:
+# - KSK must be retired since it no longer matches the policy.
+#   P     : now-3900s
+#   P sync: now-3h
+#   A     : now-3900s
+# - The key is removed after the retire interval:
+#   IretKSK = TTLds + DprpP + retire_safety.
+#   TTLds:         2h (7200 seconds)
+#   Dprp:          1h (3600 seconds)
+#   retire-safety: 1h (3600 seconds)
+#   IretKSK:       4h (14400 seconds)
+IretKSK=14400
+created=$(key_get KEY1 CREATED)
+set_addkeytime "KEY1" "PUBLISHED"   "${created}" -3900
+set_addkeytime "KEY1" "ACTIVE"      "${created}" -3900
+set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -10800
+keyfile=$(key_get KEY1 BASEFILE)
+grep "; Inactive:" "${keyfile}.key" > retired.test${n}.ksk
+retired=$(awk '{print $3}' < retired.test${n}.ksk)
+set_keytime    "KEY1" "RETIRED" "${retired}"
+set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretKSK}"
+# - ZSK must be retired since it no longer matches the policy.
+#   P: now-3900s
+#   A: now-12h
+# - The key is removed after the retire interval:
+#   IretZSK = TTLsig + Dprp + Dsgn + retire-safety.
+#   TTLsig:        11h (39600 seconds)
+#   Dprp:          1h (3600 seconds)
+#   Dsgn:          9d (777600 seconds)
+#   retire-safety: 1h (3600 seconds)
+#   IretZSK:       9d13h (824400 seconds)
+IretZSK=824400
+Lzsk=5184000
+created=$(key_get KEY2 CREATED)
+set_addkeytime "KEY2" "PUBLISHED"   "${created}" -3900
+set_addkeytime "KEY2" "ACTIVE"      "${created}" -43200
+keyfile=$(key_get KEY2 BASEFILE)
+grep "; Inactive:" "${keyfile}.key" > retired.test${n}.zsk
+retired=$(awk '{print $3}' < retired.test${n}.zsk)
+set_keytime    "KEY2" "RETIRED" "${retired}"
+set_addkeytime "KEY2" "REMOVED" "${retired}" "${IretZSK}"
+# - The new KSK is immediately published and activated.
+created=$(key_get KEY3 CREATED)
+set_keytime    "KEY3" "PUBLISHED"   "${created}"
+set_keytime    "KEY3" "ACTIVE"      "${created}"
+# - It takes TTLsig + Dprp + publish-safety hours to propagate the zone.
+#   TTLsig:         11h (39600 seconds)
+#   Dprp:           1h (3600 seconds)
+#   publish-safety: 1h (3600 seconds)
+#   Ipub:           13h (46800 seconds)
+Ipub=46800
+set_addkeytime "KEY3" "SYNCPUBLISH" "${created}" "${Ipub}"
+# - The ZSK is immediately published and activated.
+created=$(key_get KEY4 CREATED)
+set_keytime    "KEY4" "PUBLISHED"   "${created}"
+set_keytime    "KEY4" "ACTIVE"      "${created}"
+active=$(key_get KEY4 ACTIVE)
+set_addkeytime "KEY4" "RETIRED"     "${active}"  "${Lzsk}"
+retired=$(key_get KEY4 RETIRED)
+set_addkeytime "KEY4" "REMOVED"     "${retired}" "${IretZSK}"
+
+# Continue signing policy checks.
+check_keytimes
+check_apex
+check_subdomain
+dnssec_verify
+
+# Check key tags, should be the same.
+n=$((n+1))
+echo_i "check that of zone ${ZONE} migration to dnssec-policy keeps existing keys ($n)"
+ret=0
+[ $_migratenomatch_algnum_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
+[ $_migratenomatch_algnum_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+#
+# Test migration to dnssec-policy, existing keys do not match key length.
+#
+set_zone "migrate-nomatch-alglen.kasp"
+set_policy "migrate-nomatch-alglen" "4" "300"
+set_server "ns3" "10.53.0.3"
+
+# The legacy keys need to be retired, but otherwise stay present until the
+# new keys are omnipresent, and can be used to construct a chain of trust.
+init_migration_keys "5" "RSASHA1" "1024" "1024"
+init_migration_states "hidden" "omnipresent"
+key_set "KEY1" "LEGACY" "no"
+key_set "KEY2" "LEGACY" "no"
+
+set_keyrole      "KEY3" "ksk"
+set_keylifetime  "KEY3" "0"
+set_keyalgorithm "KEY3" "5" "RSASHA1" "2048"
+set_keysigning   "KEY3" "yes"
+set_zonesigning  "KEY3" "no"
+
+set_keyrole      "KEY4" "zsk"
+set_keylifetime  "KEY4" "5184000"
+set_keyalgorithm "KEY4" "5" "RSASHA1" "2048"
+set_keysigning   "KEY4" "no"
+# This key is considered to be prepublished, so it is not yet signing.
+set_zonesigning  "KEY4" "no"
+
+set_keystate "KEY3" "GOAL"         "omnipresent"
+set_keystate "KEY3" "STATE_DNSKEY" "rumoured"
+set_keystate "KEY3" "STATE_KRRSIG" "rumoured"
+set_keystate "KEY3" "STATE_DS"     "hidden"
+
+set_keystate "KEY4" "GOAL"         "omnipresent"
+set_keystate "KEY4" "STATE_DNSKEY" "rumoured"
+set_keystate "KEY4" "STATE_ZRRSIG" "hidden"
+
+# Various signing policy checks.
+check_keys
+wait_for_done_signing
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+
+# Set expected key times:
+# - KSK must be retired since it no longer matches the policy.
+#   P     : now-3900s
+#   P sync: now-3h
+#   A     : now-3900s
+# - The key is removed after the retire interval:
+#   IretKSK = TTLds + DprpP + retire_safety.
+#   TTLds:         2h (7200 seconds)
+#   Dprp:          1h (3600 seconds)
+#   retire-safety: 1h (3600 seconds)
+#   IretKSK:       4h (14400 seconds)
+IretKSK=14400
+created=$(key_get KEY1 CREATED)
+set_addkeytime "KEY1" "PUBLISHED"   "${created}" -3900
+set_addkeytime "KEY1" "ACTIVE"      "${created}" -3900
+set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -10800
+keyfile=$(key_get KEY1 BASEFILE)
+grep "; Inactive:" "${keyfile}.key" > retired.test${n}.ksk
+retired=$(awk '{print $3}' < retired.test${n}.ksk)
+set_keytime    "KEY1" "RETIRED" "${retired}"
+set_addkeytime "KEY1" "REMOVED" "${retired}" "${IretKSK}"
+# - ZSK must be retired since it no longer matches the policy.
+#   P: now-3900s
+#   A: now-12h
+# - The key is removed after the retire interval:
+#   IretZSK = TTLsig + Dprp + Dsgn + retire-safety.
+#   TTLsig:         11h (39600 seconds)
+#   Dprp:           1h (3600 seconds)
+#   Dsgn:           9d (777600 seconds)
+#   publish-safety: 1h (3600 seconds)
+#   IretZSK:        9d13h (824400 seconds)
+IretZSK=824400
+Lzsk=5184000
+created=$(key_get KEY2 CREATED)
+set_addkeytime "KEY2" "PUBLISHED"   "${created}" -3900
+set_addkeytime "KEY2" "ACTIVE"      "${created}" -43200
+keyfile=$(key_get KEY2 BASEFILE)
+grep "; Inactive:" "${keyfile}.key" > retired.test${n}.zsk
+retired=$(awk '{print $3}' < retired.test${n}.zsk)
+set_keytime    "KEY2" "RETIRED" "${retired}"
+set_addkeytime "KEY2" "REMOVED" "${retired}" "${IretZSK}"
+# - The new KSK is immediately published and activated.
+created=$(key_get KEY3 CREATED)
+set_keytime    "KEY3" "PUBLISHED"   "${created}"
+set_keytime    "KEY3" "ACTIVE"      "${created}"
+# - It takes TTLsig + Dprp + publish-safety hours to propagate the zone.
+#   TTLsig:         11h (39600 seconds)
+#   Dprp:           1h (3600 seconds)
+#   publish-safety: 1h (3600 seconds)
+#   Ipub:           13h (46800 seconds)
+Ipub=46800
+set_addkeytime "KEY3" "SYNCPUBLISH" "${created}" "${Ipub}"
+# - The ZSK is immediately published and activated.
+created=$(key_get KEY4 CREATED)
+set_keytime    "KEY4" "PUBLISHED"   "${created}"
+set_keytime    "KEY4" "ACTIVE"      "${created}"
+active=$(key_get KEY4 ACTIVE)
+set_addkeytime "KEY4" "RETIRED"     "${active}"  "${Lzsk}"
+retired=$(key_get KEY4 RETIRED)
+set_addkeytime "KEY4" "REMOVED"     "${retired}" "${IretZSK}"
+
+# Continue signing policy checks.
+check_keytimes
+check_apex
+check_subdomain
+dnssec_verify
+
+# Check key tags, should be the same.
+n=$((n+1))
+echo_i "check that of zone ${ZONE} migration to dnssec-policy keeps existing keys ($n)"
+ret=0
+[ $_migratenomatch_alglen_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
+[ $_migratenomatch_alglen_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+########################################################
+# Testing key states derived from key timing metadata. #
+########################################################
+
+# Policy parameters.
+# KSK has lifetime of 60 days (5184000 seconds).
+# The KSK is removed after Iret = DprpP + TTLds + retire-safety =
+# 4h = 14400 seconds.
+Lksk=5184000
+IretKSK=14400
+# ZSK has lifetime of 60 days (5184000 seconds).
+# The ZSK is removed after Iret = TTLsig + Dprp + Dsgn + retire-safety =
+# 181h = 651600 seconds.
+Lzsk=5184000
+IretZSK=651600
+
+#
+# Testing rumoured state.
+#
+set_zone "rumoured.kasp"
+set_policy "timing-metadata" "2" "300"
+set_server "ns3" "10.53.0.3"
+
+# Key properties, timings and metadata should be the same as legacy keys above.
+init_migration_keys "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" "$DEFAULT_BITS"
+init_migration_states "omnipresent" "rumoured"
+key_set "KEY1" "LEGACY" "no"
+key_set "KEY2" "LEGACY" "no"
+set_keylifetime "KEY1" "${Lksk}"
+set_keylifetime "KEY2" "${Lzsk}"
+
+# Various signing policy checks.
+check_keys
+wait_for_done_signing
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+
+# Set expected key times:
+#
+# Tds="now-2h"    (7200)
+# Tkey="now-300s" (300)
+# Tsig="now-11h"  (39600)
+created=$(key_get KEY1 CREATED)
+set_addkeytime      "KEY1" "PUBLISHED"   "${created}" -300
+set_addkeytime      "KEY1" "ACTIVE"      "${created}" -300
+set_addkeytime      "KEY1" "SYNCPUBLISH" "${created}"  -7200
+set_retired_removed "KEY1" "${Lksk}" "${IretKSK}"
+created=$(key_get KEY2 CREATED)
+set_addkeytime      "KEY2" "PUBLISHED"   "${created}"  -300
+set_addkeytime      "KEY2" "ACTIVE"      "${created}"  -39600
+set_retired_removed "KEY2" "${Lzsk}" "${IretZSK}"
+
+# Continue signing policy checks.
+check_keytimes
+check_apex
+check_subdomain
+dnssec_verify
+
+# Check key tags, should be the same.
+n=$((n+1))
+echo_i "check that of zone ${ZONE} migration to dnssec-policy uses the same keys ($n)"
+ret=0
+[ $_rumoured_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
+[ $_rumoured_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+#
+# Testing omnipresent state.
+#
+set_zone "omnipresent.kasp"
+set_policy "timing-metadata" "2" "300"
+set_server "ns3" "10.53.0.3"
+
+# Key properties, timings and metadata should be the same as legacy keys above.
+init_migration_keys "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" "$DEFAULT_BITS"
+init_migration_states "omnipresent" "omnipresent"
+key_set "KEY1" "LEGACY" "no"
+key_set "KEY2" "LEGACY" "no"
+set_keylifetime "KEY1" "${Lksk}"
+set_keylifetime "KEY2" "${Lzsk}"
+
+# Various signing policy checks.
+check_keys
+wait_for_done_signing
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
+
+# Set expected key times:
+#
+# Tds="now-3h"     (10800)
+# Tkey="now-3900s" (3900)
+# Tsig="now-12h"   (43200)
+created=$(key_get KEY1 CREATED)
+set_addkeytime      "KEY1" "PUBLISHED"   "${created}" -3900
+set_addkeytime      "KEY1" "ACTIVE"      "${created}" -3900
+set_addkeytime      "KEY1" "SYNCPUBLISH" "${created}"  -10800
+set_retired_removed "KEY1" "${Lksk}" "${IretKSK}"
+created=$(key_get KEY2 CREATED)
+set_addkeytime      "KEY2" "PUBLISHED"   "${created}"  -3900
+set_addkeytime      "KEY2" "ACTIVE"      "${created}"  -43200
+set_retired_removed "KEY2" "${Lzsk}" "${IretZSK}"
+
+# Continue signing policy checks.
+check_keytimes
+check_apex
+check_subdomain
+dnssec_verify
+
+# Check key tags, should be the same.
+n=$((n+1))
+echo_i "check that of zone ${ZONE} migration to dnssec-policy uses the same keys ($n)"
+ret=0
+[ $_omnipresent_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
+[ $_omnipresent_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+
+######################################
+# Testing good migration with views. #
+######################################
+init_view_migration() {
+	key_clear        "KEY1"
+	key_set          "KEY1" "LEGACY" "yes"
+	set_keyrole      "KEY1" "ksk"
+	set_keylifetime  "KEY1" "0"
+	set_keysigning   "KEY1" "yes"
+	set_zonesigning  "KEY1" "no"
+
+	key_clear        "KEY2"
+	key_set          "KEY2" "LEGACY" "yes"
+	set_keyrole      "KEY2" "zsk"
+	set_keylifetime  "KEY2" "0"
+	set_keysigning   "KEY2" "no"
+	set_zonesigning  "KEY2" "yes"
+
+	key_clear        "KEY3"
+	key_clear        "KEY4"
+
+	set_keystate "KEY1" "GOAL"         "omnipresent"
+	set_keystate "KEY1" "STATE_DNSKEY" "rumoured"
+	set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
+	set_keystate "KEY1" "STATE_DS"     "rumoured"
+
+	set_keystate "KEY2" "GOAL"         "omnipresent"
+	set_keystate "KEY2" "STATE_DNSKEY" "rumoured"
+	set_keystate "KEY2" "STATE_ZRRSIG" "rumoured"
+}
+
+set_keytimes_view_migration() {
+	# Key is six months in use.
+	created=$(key_get KEY1 CREATED)
+	set_addkeytime "KEY1" "PUBLISHED"   "${created}" -16070400
+	set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -16070400
+	set_addkeytime "KEY1" "ACTIVE"      "${created}" -16070400
+	created=$(key_get KEY2 CREATED)
+	set_addkeytime "KEY2" "PUBLISHED"   "${created}" -16070400
+	set_addkeytime "KEY2" "ACTIVE"      "${created}" -16070400
+}
+
+# Zone view.rsasha256.kasp (external)
+set_zone "view-rsasha256.kasp"
+set_policy "rsasha256" "2" "300"
+set_server "ns4" "10.53.0.4"
+init_view_migration
+set_keyalgorithm "KEY1" "8" "RSASHA256" "2048"
+set_keyalgorithm "KEY2" "8" "RSASHA256" "1024"
+TSIG="hmac-sha1:external:$VIEW1"
+wait_for_nsec
+# Make sure the zone is signed with legacy keys.
+check_keys
+set_keytimes_view_migration
+check_keytimes
+dnssec_verify
+
+n=$((n+1))
+# check subdomain
+echo_i "check TXT $ZONE (view ext) rrset is signed correctly ($n)"
+ret=0
+dig_with_opts "view.${ZONE}" "@${SERVER}" TXT > "dig.out.$DIR.test$n.txt" || log_error "dig view.${ZONE} TXT failed"
+grep "status: NOERROR" "dig.out.$DIR.test$n.txt" > /dev/null || log_error "mismatch status in DNS response"
+grep "view.${ZONE}\..*${DEFAULT_TTL}.*IN.*TXT.*external" "dig.out.$DIR.test$n.txt" > /dev/null || log_error "missing view.${ZONE} TXT record in response"
+check_signatures TXT "dig.out.$DIR.test$n.txt" "ZSK"
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+# Remember legacy key tags.
+_migrate_ext8_ksk=$(key_get KEY1 ID)
+_migrate_ext8_zsk=$(key_get KEY2 ID)
+
+# Zone view.rsasha256.kasp (internal)
+set_zone "view-rsasha256.kasp"
+set_policy "rsasha256" "2" "300"
+set_server "ns4" "10.53.0.4"
+init_view_migration
+set_keyalgorithm "KEY1" "8" "RSASHA256" "2048"
+set_keyalgorithm "KEY2" "8" "RSASHA256" "1024"
+TSIG="hmac-sha1:internal:$VIEW2"
+wait_for_nsec
+# Make sure the zone is signed with legacy keys.
+check_keys
+set_keytimes_view_migration
+check_keytimes
+dnssec_verify
+
+n=$((n+1))
+# check subdomain
+echo_i "check TXT $ZONE (view int) rrset is signed correctly ($n)"
+ret=0
+dig_with_opts "view.${ZONE}" "@${SERVER}" TXT > "dig.out.$DIR.test$n.txt" || log_error "dig view.${ZONE} TXT failed"
+grep "status: NOERROR" "dig.out.$DIR.test$n.txt" > /dev/null || log_error "mismatch status in DNS response"
+grep "view.${ZONE}\..*${DEFAULT_TTL}.*IN.*TXT.*internal" "dig.out.$DIR.test$n.txt" > /dev/null || log_error "missing view.${ZONE} TXT record in response"
+check_signatures TXT "dig.out.$DIR.test$n.txt" "ZSK"
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+# Remember legacy key tags.
+_migrate_int8_ksk=$(key_get KEY1 ID)
+_migrate_int8_zsk=$(key_get KEY2 ID)
+
+# Reconfig dnssec-policy.
+echo_i "reconfig to switch to dnssec-policy"
+copy_setports ns4/named2.conf.in ns4/named.conf
+rndc_reconfig ns4 10.53.0.4
+
+# Calculate time passed to correctly check for next key events.
+now="$(TZ=UTC date +%s)"
+time_passed=$((now-start_time))
+echo_i "${time_passed} seconds passed between start of tests and reconfig"
+
+#
+# Testing migration (RSASHA256, views).
+#
+set_zone "view-rsasha256.kasp"
+set_policy "rsasha256" "3" "300"
+set_server "ns4" "10.53.0.4"
+init_migration_keys "8" "RSASHA256" "2048" "1024"
+init_migration_states "omnipresent" "rumoured"
+# Key properties, timings and metadata should be the same as legacy keys above.
+# However, because the keys have a lifetime, kasp will set the retired time.
+key_set          "KEY1" "LEGACY" "no"
+set_keylifetime  "KEY1" "31536000"
+set_keystate     "KEY1" "STATE_DNSKEY" "omnipresent"
+set_keystate     "KEY1" "STATE_KRRSIG" "omnipresent"
+set_keystate     "KEY1" "STATE_DS"     "omnipresent"
+
+key_set          "KEY2" "LEGACY" "no"
+set_keylifetime  "KEY2" "8035200"
+set_keystate     "KEY2" "STATE_DNSKEY" "omnipresent"
+set_keystate     "KEY2" "STATE_ZRRSIG" "omnipresent"
+# The ZSK needs to be replaced.
+set_keystate     "KEY2" "GOAL" "hidden"
+set_keystate     "KEY3" "GOAL" "omnipresent"
+set_keyrole      "KEY3" "zsk"
+set_keylifetime  "KEY3" "8035200"
+set_keyalgorithm "KEY3" "8" "RSASHA256" "1024"
+set_keysigning   "KEY3" "no"
+set_zonesigning  "KEY3" "no" # not yet
+set_keystate     "KEY3" "STATE_DNSKEY" "rumoured"
+set_keystate     "KEY3" "STATE_ZRRSIG" "hidden"
+
+# Various signing policy checks (external).
+TSIG="hmac-sha1:external:$VIEW1"
+check_keys
+wait_for_done_signing
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" "external-view"
+set_keytimes_view_migration
+
+# Set expected key times:
+published=$(key_get KEY1 PUBLISHED)
+set_keytime "KEY1" "ACTIVE"      "${published}"
+set_keytime "KEY1" "SYNCPUBLISH" "${published}"
+# Lifetime: 1 year (8035200 seconds)
+active=$(key_get KEY1 ACTIVE)
+set_addkeytime "KEY1" "RETIRED" "${active}"  "31536000"
+# Retire interval:
+# DS TTL:                  1d
+# Parent zone propagation: 3h
+# Retire safety:           1h
+# Total:                   100800 seconds
+retired=$(key_get KEY1 RETIRED)
+set_addkeytime "KEY1" "REMOVED" "${retired}" "100800"
+
+published=$(key_get KEY2 PUBLISHED)
+set_keytime "KEY2" "ACTIVE" "${published}"
+# Lifetime: 3 months (8035200 seconds)
+active=$(key_get KEY2 ACTIVE)
+set_addkeytime "KEY2" "RETIRED" "${active}" "8035200"
+# Retire interval:
+# Sign delay:             9d (14-5)
+# Max zone TTL:           1d
+# Retire safety:          1h
+# Zone propagation delay: 300s
+# Total:                  867900 seconds
+retired=$(key_get KEY2 RETIRED)
+set_addkeytime "KEY2" "REMOVED" "${retired}" "867900"
+
+created=$(key_get KEY3 CREATED)
+set_keytime    "KEY3" "PUBLISHED" "${created}"
+# Publication interval:
+# DNSKEY TTL:             300s
+# Publish safety:         1h
+# Zone propagation delay: 300s
+# Total:                  4200 seconds
+set_addkeytime "KEY3" "ACTIVE" "${created}" "4200"
+# Lifetime: 3 months (8035200 seconds)
+active=$(key_get KEY3 ACTIVE)
+set_addkeytime "KEY3" "RETIRED" "${active}" "8035200"
+# Retire interval:
+# Sign delay:             9d (14-5)
+# Max zone TTL:           1d
+# Retire safety:          1h
+# Zone propagation delay: 300s
+# Total:                  867900 seconds
+retired=$(key_get KEY3 RETIRED)
+set_addkeytime "KEY3" "REMOVED" "${retired}" "867900"
+
+# Continue signing policy checks.
+check_keytimes
+check_apex
+dnssec_verify
+
+# Various signing policy checks (external).
+TSIG="hmac-sha1:internal:$VIEW2"
+check_keys
+wait_for_done_signing
+check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" "internal-view"
+set_keytimes_view_migration
+check_keytimes
+check_apex
+dnssec_verify
+
+# Check key tags, should be the same.
+n=$((n+1))
+echo_i "check that of zone ${ZONE} migration to dnssec-policy uses the same keys ($n)"
+ret=0
+[ $_migrate_ext8_ksk = $_migrate_int8_ksk ] || log_error "mismatch ksk tag"
+[ $_migrate_ext8_zsk = $_migrate_int8_zsk ] || log_error "mismatch zsk tag"
+[ $_migrate_ext8_ksk = $(key_get KEY1 ID) ] || log_error "mismatch ksk tag"
+[ $_migrate_ext8_zsk = $(key_get KEY2 ID) ] || log_error "mismatch zsk tag"
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+echo_i "exit status: $status"
+[ $status -eq 0 ] || exit 1
diff -Nru bind9-9.16.13/bin/tests/system/nsec3/tests.sh bind9-9.16.15/bin/tests/system/nsec3/tests.sh
--- bind9-9.16.13/bin/tests/system/nsec3/tests.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/nsec3/tests.sh	2021-04-19 15:45:50.000000000 +0200
@@ -11,6 +11,8 @@
 
 # shellcheck source=conf.sh
 . ../conf.sh
+# shellcheck source=kasp.sh
+. ../kasp.sh
 
 # Log errors and increment $ret.
 log_error() {
@@ -28,11 +30,6 @@
     "$RNDC" -c ../common/rndc.conf -p "$CONTROLPORT" -s "$@"
 }
 
-# Set server key-directory ($1) and address ($2) for testing nsec3.
-set_server() {
-	DIR=$1
-	SERVER=$2
-}
 # Set zone name ($1) and policy ($2) for testing nsec3.
 set_zone_policy() {
 	ZONE=$1
@@ -79,18 +76,6 @@
 	test "$ret" -eq 0 || echo_i "failed"
 	status=$((status+ret))
 }
-
-# Test: dnssec-verify zone $1.
-dnssec_verify()
-{
-	n=$((n+1))
-	echo_i "dnssec-verify zone ${ZONE} ($n)"
-	ret=0
-	dig_with_opts "$ZONE" "@${SERVER}" AXFR > dig.out.test$n.axfr || log_error "dig ${ZONE} AXFR failed"
-	$VERIFY -z -o "$ZONE" dig.out.test$n.axfr > /dev/null || log_error "dnssec verify zone $ZONE failed"
-	test "$ret" -eq 0 || echo_i "failed"
-	status=$((status+ret))
-}
 
 # Test: check NSEC in answers
 _check_nsec_nsec3param()
diff -Nru bind9-9.16.13/bin/tests/system/nsupdate/tests.sh bind9-9.16.15/bin/tests/system/nsupdate/tests.sh
--- bind9-9.16.13/bin/tests/system/nsupdate/tests.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/nsupdate/tests.sh	2021-04-19 15:45:50.000000000 +0200
@@ -1080,7 +1080,7 @@
 $NSUPDATE <<END > nsupdate.out 2>&1 && ret=1
     server unresolvable..
 END
-grep "couldn't get address for 'unresolvable..': not found" nsupdate.out > /dev/null || ret=1
+grep "couldn't get address for 'unresolvable..':" nsupdate.out > /dev/null || ret=1
 grep "syntax error" nsupdate.out > /dev/null || ret=1
 [ $ret = 0 ] || { echo_i "failed"; status=1; }
 
@@ -1090,7 +1090,8 @@
 $NSUPDATE -i <<END > nsupdate.out 2>&1 || ret=1
     server unresolvable..
 END
-grep "couldn't get address for 'unresolvable..': not found" nsupdate.out > /dev/null || ret=1
+grep "couldn't get address for 'unresolvable..':" nsupdate.out > /dev/null || ret=1
+grep "syntax error" nsupdate.out > /dev/null && ret=1
 [ $ret = 0 ] || { echo_i "failed"; status=1; }
 
 n=`expr $n + 1`
diff -Nru bind9-9.16.13/bin/tests/system/run.sh bind9-9.16.15/bin/tests/system/run.sh
--- bind9-9.16.13/bin/tests/system/run.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/run.sh	2021-04-19 15:45:50.000000000 +0200
@@ -142,9 +142,7 @@
         echoinfo "I:$systest:stopping servers"
         if ! $PERL stop.pl "$systest"; then
             echoinfo "I:$systest:stopping servers failed"
-            echofail "R:$systest:FAIL"
-            echoend  "E:$systest:$(date_with_args)"
-            exit 1
+            return 1
         fi
     fi
 }
@@ -198,10 +196,10 @@
 if test -f $systest/clean.sh
 then
     if ! ( cd "${systest}" && $SHELL clean.sh "$@" ); then
-    echowarn "I:$systest:clean.sh script failed"
-    echofail "R:$systest:FAIL"
-    echoend  "E:$systest:$(date_with_args)"
-    exit 1
+        echowarn "I:$systest:clean.sh script failed"
+        echofail "R:$systest:FAIL"
+        echoend  "E:$systest:$(date_with_args)"
+        exit 1
     fi
 fi
 
@@ -209,10 +207,10 @@
 if test -f $systest/setup.sh
 then
     if ! ( cd "${systest}" && $SHELL setup.sh "$@" ); then
-    echowarn "I:$systest:setup.sh script failed"
-    echofail "R:$systest:FAIL"
-    echoend  "E:$systest:$(date_with_args)"
-    exit 1
+        echowarn "I:$systest:setup.sh script failed"
+        echofail "R:$systest:FAIL"
+        echoend  "E:$systest:$(date_with_args)"
+        exit 1
     fi
 fi
 
@@ -224,22 +222,22 @@
     ( cd "$systest" && $SHELL tests.sh "$@" )
     status=$?
     run=$((run+1))
-    stop_servers
+    stop_servers || status=1
 fi
 
 if [ -n "$PYTEST" ]; then
     run=$((run+1))
     for test in $(cd "${systest}" && find . -name "tests*.py"); do
-    start_servers
-    rm -f "$systest/$test.status"
-    test_status=0
-    (cd "$systest" && "$PYTEST" -v "$test" "$@" || echo "$?" > "$test.status") | SYSTESTDIR="$systest" cat_d
-    if [ -f "$systest/$test.status" ]; then
-        echo_i "FAILED"
-        test_status=$(cat "$systest/$test.status")
-    fi
-    status=$((status+test_status))
-    stop_servers
+        start_servers
+        rm -f "$systest/$test.status"
+        test_status=0
+        (cd "$systest" && "$PYTEST" -v "$test" "$@" || echo "$?" > "$test.status") | SYSTESTDIR="$systest" cat_d
+        if [ -f "$systest/$test.status" ]; then
+            echo_i "FAILED"
+            test_status=$(cat "$systest/$test.status")
+        fi
+        status=$((status+test_status))
+        stop_servers || status=1
     done
 else
     echoinfo "I:$systest:pytest not installed, skipping python tests"
diff -Nru bind9-9.16.13/bin/tests/system/serve-stale/ns1/named1.conf.in bind9-9.16.15/bin/tests/system/serve-stale/ns1/named1.conf.in
--- bind9-9.16.13/bin/tests/system/serve-stale/ns1/named1.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/serve-stale/ns1/named1.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -30,7 +30,6 @@
 	max-stale-ttl 3600;
 	stale-answer-ttl 4;
 	stale-answer-enable yes;
-	stale-answer-client-timeout disabled;
 	stale-cache-enable yes;
 	stale-refresh-time 30;
 	servfail-ttl 0;
diff -Nru bind9-9.16.13/bin/tests/system/serve-stale/ns1/named2.conf.in bind9-9.16.15/bin/tests/system/serve-stale/ns1/named2.conf.in
--- bind9-9.16.13/bin/tests/system/serve-stale/ns1/named2.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/serve-stale/ns1/named2.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -30,7 +30,6 @@
 	max-stale-ttl 20;
 	stale-answer-ttl 3;
 	stale-answer-enable yes;
-	stale-answer-client-timeout disabled;
 	stale-cache-enable yes;
 	servfail-ttl 0;
 };
diff -Nru bind9-9.16.13/bin/tests/system/serve-stale/ns1/named3.conf.in bind9-9.16.15/bin/tests/system/serve-stale/ns1/named3.conf.in
--- bind9-9.16.13/bin/tests/system/serve-stale/ns1/named3.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/serve-stale/ns1/named3.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -30,7 +30,6 @@
 	max-stale-ttl 20;
 	stale-answer-ttl 3;
 	stale-answer-enable yes;
-	stale-answer-client-timeout disabled;
 	stale-cache-enable yes;
 	stale-refresh-time 0;
 	servfail-ttl 0;
diff -Nru bind9-9.16.13/bin/tests/system/serve-stale/ns3/named2.conf.in bind9-9.16.15/bin/tests/system/serve-stale/ns3/named2.conf.in
--- bind9-9.16.13/bin/tests/system/serve-stale/ns3/named2.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/serve-stale/ns3/named2.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -37,6 +37,7 @@
 	stale-cache-enable yes;
 	stale-answer-ttl 3;
 	stale-refresh-time 0;
+	stale-answer-client-timeout 1800;
 	max-stale-ttl 3600;
 	resolver-query-timeout 10;
 };
diff -Nru bind9-9.16.13/bin/tests/system/serve-stale/ns3/named3.conf.in bind9-9.16.15/bin/tests/system/serve-stale/ns3/named3.conf.in
--- bind9-9.16.13/bin/tests/system/serve-stale/ns3/named3.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/serve-stale/ns3/named3.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -36,7 +36,6 @@
 	stale-answer-enable yes;
 	stale-cache-enable yes;
 	stale-answer-ttl 3;
-	stale-answer-client-timeout off;
 	stale-refresh-time 0;
 	max-stale-ttl 3600;
 	resolver-query-timeout 10;
diff -Nru bind9-9.16.13/bin/tests/system/serve-stale/ns3/named6.conf.in bind9-9.16.15/bin/tests/system/serve-stale/ns3/named6.conf.in
--- bind9-9.16.13/bin/tests/system/serve-stale/ns3/named6.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/serve-stale/ns3/named6.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -36,7 +36,6 @@
 	stale-answer-enable no;
 	stale-cache-enable yes;
 	stale-answer-ttl 3;
-	stale-answer-client-timeout disabled;
 	stale-refresh-time 4;
 	resolver-query-timeout 10;
 	fetches-per-zone 1 fail;
diff -Nru bind9-9.16.13/bin/tests/system/serve-stale/ns3/named7.conf.in bind9-9.16.15/bin/tests/system/serve-stale/ns3/named7.conf.in
--- bind9-9.16.13/bin/tests/system/serve-stale/ns3/named7.conf.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/serve-stale/ns3/named7.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -40,7 +40,6 @@
 	stale-answer-enable yes;
 	stale-cache-enable yes;
 	stale-answer-ttl 3;
-	stale-answer-client-timeout disabled;
 	stale-refresh-time 4;
 	resolver-query-timeout 10;
 	fetches-per-zone 1 fail;
diff -Nru bind9-9.16.13/bin/tests/system/serve-stale/tests.sh bind9-9.16.15/bin/tests/system/serve-stale/tests.sh
--- bind9-9.16.13/bin/tests/system/serve-stale/tests.sh	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/serve-stale/tests.sh	2021-04-19 15:45:50.000000000 +0200
@@ -1574,10 +1574,10 @@
 status=$((status+ret))
 if [ $ret != 0 ]; then echo_i "failed"; fi
 
-########################################################
-# Test for stale-answer-client-timeout (default 1.8s). #
-########################################################
-echo_i "test stale-answer-client-timeout (default 1.8)"
+################################################
+# Test for stale-answer-client-timeout (1.8s). #
+################################################
+echo_i "test stale-answer-client-timeout (1.8)"
 
 n=$((n+1))
 echo_i "updating ns3/named.conf ($n)"
@@ -1637,6 +1637,11 @@
 # Allow RRset to become stale.
 sleep 2
 
+echo_i "sending queries for tests $((n+1))-$((n+2))..."
+$DIG -p ${PORT} +tries=1 +timeout=10  @10.53.0.3 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} +tries=1 +timeout=10  @10.53.0.3 nodata.example TXT > dig.out.test$((n+2))
+wait
+
 # We configured a long value of 30 seconds for resolver-query-timeout.
 # That should give us enough time to receive an stale answer from cache
 # after stale-answer-client-timeout timer of 1.8 sec triggers.
@@ -1644,7 +1649,6 @@
 echo_i "check stale data.example comes from cache (default stale-answer-client-timeout) ($n)"
 nextpart ns3/named.run > /dev/null
 t1=`$PERL -e 'print time()'`
-$DIG -p ${PORT} +tries=1 +timeout=10  @10.53.0.3 data.example TXT > dig.out.test$n
 t2=`$PERL -e 'print time()'`
 wait_for_log 5 "data.example client timeout, stale answer used" ns3/named.run || ret=1
 ret=0
@@ -1657,26 +1661,8 @@
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status+ret))
 
-echo_i "sending queries for tests $((n+1))-$((n+2))..."
-$DIG -p ${PORT} +tries=1 +timeout=3  @10.53.0.3 nodata.example TXT > dig.out.test$((n+1)) &
-$DIG -p ${PORT} +tries=1 +timeout=30  @10.53.0.3 nodata.example TXT > dig.out.test$((n+2))
-wait
-
-# Since nodata.example is cached as NXRRSET and marked as stale at this point,
-# BIND must not return this RRset when stale-answer-client-timeout triggers,
-# instead, it must attempt to refresh the RRset. Since the authoritative
-# server is disabled and we are using resolver-query-timeout value of 10
-# seconds, we expect this query with a timeout of 3 seconds to time out.
-n=$((n+1))
-echo_i "check query for nodata.example times out (default stale-answer-client-timeout) ($n)"
-grep "connection timed out" dig.out.test$n > /dev/null || ret=1
-if [ $ret != 0 ]; then echo_i "failed"; fi
-status=$((status+ret))
-
-# For this query we expect BIND to return stale NXRRSET data for
-# nodata.example after resolver-query-timeout expires.
 n=$((n+1))
-echo_i "check stale nodata.example comes from cache after resolver-query-timeout expires (default stale-answer-client-timeout) ($n)"
+echo_i "check stale nodata.example comes from cache (default stale-answer-client-timeout) ($n)"
 grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
 grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
 grep "example\..*3.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
@@ -1778,6 +1764,18 @@
 
 n=$((n+1))
 ret=0
+echo_i "check stale nodata.example comes from cache (stale-answer-client-timeout 0) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
+wait_for_log 5 "nodata.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*3.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+ret=0
 echo_i "check stale data.example comes from cache (stale-answer-client-timeout 0) ($n)"
 nextpart ns3/named.run > /dev/null
 $DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
@@ -1816,39 +1814,18 @@
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status+ret))
 
-n=$((n+1))
-echo_i "disable responses from authoritative server ($n)"
-ret=0
-$DIG -p ${PORT} @10.53.0.2 txt disable  > dig.out.test$n
-grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
-grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
-if [ $ret != 0 ]; then echo_i "failed"; fi
-status=$((status+ret))
-
-echo_i "sending queries for tests $((n+1))-$((n+2))..."
-$DIG -p ${PORT} +tries=1 +timeout=3  @10.53.0.3 nodata.example TXT > dig.out.test$((n+1)) &
-$DIG -p ${PORT} +tries=1 +timeout=30  @10.53.0.3 nodata.example TXT > dig.out.test$((n+2))
-wait
-
-# Since nodata.example is cached as NXRRSET and marked as stale at this point,
-# BIND must not prompty return this RRset due to
-# stale-answer-client-timeout == 0, instead, it must attempt to refresh the
-# RRset. Since the authoritative server is disabled and we are using
-# resolver-query-timeout value of 10 seconds, we expect this query with a
-# timeout of 3 seconds to time out.
-n=$((n+1))
-echo_i "check query for nodata.example times out (stale-answer-client-timeout 0) ($n)"
-grep "connection timed out" dig.out.test$n > /dev/null || ret=1
-if [ $ret != 0 ]; then echo_i "failed"; fi
-status=$((status+ret))
+wait_for_nodata_refresh() {
+	$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
+	grep "status: NOERROR" dig.out.test$n > /dev/null || return 1
+	grep "ANSWER: 0," dig.out.test$n > /dev/null || return 1
+	grep "example\..*[12].*IN.*SOA" dig.out.test$n > /dev/null || return 1
+	return 0
+}
 
-# For this query we expect BIND to return stale NXRRSET data for
-# nodata.example after resolver-query-timeout expires.
 n=$((n+1))
-echo_i "check stale nodata.example comes from cache after resolver-query-timeout expires (stale-answer-client-timeout 0) ($n)"
-grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
-grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
-grep "example\..*3.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+ret=0
+echo_i "check stale nodata.example was refreshed (stale-answer-client-timeout 0) ($n)"
+retry_quiet 10 wait_for_nodata_refresh || ret=1
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status+ret))
 
diff -Nru bind9-9.16.13/bin/tests/system/timeouts/clean.sh bind9-9.16.15/bin/tests/system/timeouts/clean.sh
--- bind9-9.16.13/bin/tests/system/timeouts/clean.sh	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/timeouts/clean.sh	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+rm -f ./ns*/managed-keys.bind*
+rm -f ./ns*/named.conf
+rm -f ./ns*/named.lock
+rm -f ./ns*/named.memstats
+rm -f ./ns*/named.run*
+rm -f ./ns*/named.stats
+rm -rf ./.cache ./__pycache__
+rm -f ./ns*/large.db
diff -Nru bind9-9.16.13/bin/tests/system/timeouts/conftest.py bind9-9.16.15/bin/tests/system/timeouts/conftest.py
--- bind9-9.16.13/bin/tests/system/timeouts/conftest.py	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/timeouts/conftest.py	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,59 @@
+############################################################################
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+############################################################################
+
+import os
+import pytest
+
+
+def pytest_configure(config):
+    config.addinivalue_line(
+        "markers", "dnspython: mark tests that need dnspython to function"
+    )
+    config.addinivalue_line(
+        "markers", "dnspython2: mark tests that need dnspython >= 2.0.0"
+    )
+
+
+def pytest_collection_modifyitems(config, items):
+    # pylint: disable=unused-argument,unused-import,too-many-branches
+    # pylint: disable=import-outside-toplevel
+
+    # Test for dnspython module
+    skip_dnspython = pytest.mark.skip(
+        reason="need dnspython module to run")
+    try:
+        import dns.query  # noqa: F401
+    except ModuleNotFoundError:
+        for item in items:
+            if "dnspython" in item.keywords:
+                item.add_marker(skip_dnspython)
+
+    # Test for dnspython >= 2.0.0 module
+    skip_dnspython2 = pytest.mark.skip(
+        reason="need dnspython >= 2.0.0 module to run")
+    try:
+        from dns.query import send_tcp  # noqa: F401
+    except ImportError:
+        for item in items:
+            if "dnspython2" in item.keywords:
+                item.add_marker(skip_dnspython2)
+
+
+@pytest.fixture
+def port(request):
+    # pylint: disable=unused-argument
+    env_port = os.getenv("PORT")
+    if port is None:
+        env_port = 5300
+    else:
+        env_port = int(env_port)
+
+    return env_port
diff -Nru bind9-9.16.13/bin/tests/system/timeouts/ns1/example.db bind9-9.16.15/bin/tests/system/timeouts/ns1/example.db
--- bind9-9.16.13/bin/tests/system/timeouts/ns1/example.db	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/timeouts/ns1/example.db	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,23 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 300	; 5 minutes
+@			SOA	mname1. . (
+				2000062101 ; serial
+				20         ; refresh (20 seconds)
+				20         ; retry (20 seconds)
+				1814400    ; expire (3 weeks)
+				3600       ; minimum (1 hour)
+				)
+			NS	ns1
+ns1			A	10.53.0.1
+@			A	10.53.0.1
+a			A	10.53.0.1
+b			A	10.53.0.1
+$INCLUDE large.db
diff -Nru bind9-9.16.13/bin/tests/system/timeouts/ns1/named.conf.in bind9-9.16.15/bin/tests/system/timeouts/ns1/named.conf.in
--- bind9-9.16.13/bin/tests/system/timeouts/ns1/named.conf.in	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/timeouts/ns1/named.conf.in	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+include "../../common/rndc.key";
+
+controls {
+	inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+options {
+	query-source address 10.53.0.1;
+	notify-source 10.53.0.1;
+	transfer-source 10.53.0.1;
+	port @PORT@;
+	pid-file "named.pid";
+	listen-on { 10.53.0.1; };
+	listen-on-v6 { none; };
+	recursion no;
+	notify no;
+	tcp-initial-timeout 20;
+	tcp-idle-timeout 50;
+};
+
+zone "." {
+	type primary;
+	file "root.db";
+};
+
+zone "example." {
+	type primary;
+	file "example.db";
+	check-integrity no;
+};
diff -Nru bind9-9.16.13/bin/tests/system/timeouts/ns1/root.db bind9-9.16.15/bin/tests/system/timeouts/ns1/root.db
--- bind9-9.16.13/bin/tests/system/timeouts/ns1/root.db	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/timeouts/ns1/root.db	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,22 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 300
+.			IN SOA	gson.isc.org. a.root.servers.nil. (
+				2000042100   	; serial
+				600         	; refresh
+				600         	; retry
+				1200    	; expire
+				600       	; minimum
+				)
+.			NS	a.root-servers.nil.
+a.root-servers.nil.	A	10.53.0.1
+
+example.		NS	ns1.example.
+ns1.example.		A	10.53.0.1
diff -Nru bind9-9.16.13/bin/tests/system/timeouts/prereq.sh bind9-9.16.15/bin/tests/system/timeouts/prereq.sh
--- bind9-9.16.13/bin/tests/system/timeouts/prereq.sh	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/timeouts/prereq.sh	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+if test -n "$PYTHON"
+then
+    if $PYTHON -c "from dns.query import send_tcp" 2> /dev/null
+    then
+        :
+    else
+        echo_i "This test requires the dnspython >= 2.0.0 module." >&2
+        exit 1
+    fi
+else
+    echo_i "This test requires Python and the dnspython module." >&2
+    exit 1
+fi
+
+exit 0
diff -Nru bind9-9.16.13/bin/tests/system/timeouts/setup.sh bind9-9.16.15/bin/tests/system/timeouts/setup.sh
--- bind9-9.16.13/bin/tests/system/timeouts/setup.sh	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/timeouts/setup.sh	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+. ../conf.sh
+
+copy_setports ns1/named.conf.in ns1/named.conf
+
+#
+# Generate a large enough zone, so the transfer takes longer than
+# tcp-initial-timeout interval
+#
+$PYTHON -c "
+for a in range(150000):
+    print('%s IN NS a' % (a))
+    print('%s IN NS b' % (a))" > ns1/large.db
diff -Nru bind9-9.16.13/bin/tests/system/timeouts/tests-tcp.py bind9-9.16.15/bin/tests/system/timeouts/tests-tcp.py
--- bind9-9.16.13/bin/tests/system/timeouts/tests-tcp.py	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/timeouts/tests-tcp.py	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,161 @@
+#!/usr/bin/python3
+############################################################################
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+############################################################################
+
+# pylint: disable=unused-variable
+
+import socket
+import time
+
+import pytest
+
+TIMEOUT = 10
+
+
+def create_msg(qname, qtype):
+    import dns.message
+    msg = dns.message.make_query(qname, qtype, want_dnssec=True,
+                                 use_edns=0, payload=4096)
+
+    return msg
+
+
+def timeout():
+    return time.time() + TIMEOUT
+
+
+@pytest.mark.dnspython
+@pytest.mark.dnspython2
+def test_initial_timeout(port):
+    #
+    # The initial timeout is 2.5 seconds, so this should timeout
+    #
+    import dns.query
+
+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+        sock.connect(("10.53.0.1", port))
+
+        time.sleep(3)
+
+        msg = create_msg("example.", "A")
+
+        with pytest.raises(EOFError):
+            try:
+                (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+                (response, rtime) = dns.query.receive_tcp(sock, timeout())
+            except ConnectionResetError as e:
+                raise EOFError from e
+
+
+@pytest.mark.dnspython
+@pytest.mark.dnspython2
+def test_idle_timeout(port):
+    #
+    # The idle timeout is 5 second, so sending the second message must fail
+    #
+    import dns.rcode
+
+    msg = create_msg("example.", "A")
+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+        sock.connect(("10.53.0.1", port))
+
+        time.sleep(1)
+
+        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+        (response, rtime) = dns.query.receive_tcp(sock, timeout())
+
+        time.sleep(3)
+
+        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+        (response, rtime) = dns.query.receive_tcp(sock, timeout())
+
+        time.sleep(6)
+
+        with pytest.raises(EOFError):
+            try:
+                (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+                (response, rtime) = dns.query.receive_tcp(sock, timeout())
+            except ConnectionResetError as e:
+                raise EOFError from e
+
+
+@pytest.mark.dnspython
+@pytest.mark.dnspython2
+def test_pipelining_timeout(port):
+    #
+    # The pipelining should only timeout after the last message is received
+    #
+    import dns.query
+
+    msg = create_msg("example.", "A")
+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+        sock.connect(("10.53.0.1", port))
+
+        time.sleep(1)
+
+        # Send and receive 25 DNS queries
+        for n in range(25):
+            (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+        for n in range(25):
+            (response, rtime) = dns.query.receive_tcp(sock, timeout())
+
+        time.sleep(3)
+
+        # Send and receive 25 DNS queries
+        for n in range(25):
+            (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+        for n in range(25):
+            (response, rtime) = dns.query.receive_tcp(sock, timeout())
+
+        time.sleep(6)
+
+        with pytest.raises(EOFError):
+            try:
+                (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+                (response, rtime) = dns.query.receive_tcp(sock, timeout())
+            except ConnectionResetError as e:
+                raise EOFError from e
+
+
+@pytest.mark.dnspython
+@pytest.mark.dnspython2
+def test_long_axfr(port):
+    #
+    # The timers should not fire during AXFR, thus the connection should not
+    # close abruptly
+    #
+    import dns.query
+    import dns.rdataclass
+    import dns.rdatatype
+
+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+        sock.connect(("10.53.0.1", port))
+
+        name = dns.name.from_text("example.")
+        msg = create_msg("example.", "AXFR")
+        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+
+        # Receive the initial DNS message with SOA
+        (response, rtime) = dns.query.receive_tcp(sock, timeout(),
+                                                  one_rr_per_rrset=True)
+        soa = response.get_rrset(dns.message.ANSWER, name,
+                                 dns.rdataclass.IN, dns.rdatatype.SOA)
+        assert soa is not None
+
+        # Pull DNS message from wire until the second SOA is received
+        while True:
+            (response, rtime) = dns.query.receive_tcp(sock, timeout(),
+                                                      one_rr_per_rrset=True)
+            soa = response.get_rrset(dns.message.ANSWER, name,
+                                     dns.rdataclass.IN, dns.rdatatype.SOA)
+            if soa is not None:
+                break
+        assert soa is not None
diff -Nru bind9-9.16.13/bin/tests/system/tsiggss/authsock.pl bind9-9.16.15/bin/tests/system/tsiggss/authsock.pl
--- bind9-9.16.13/bin/tests/system/tsiggss/authsock.pl	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/bin/tests/system/tsiggss/authsock.pl	2021-04-19 15:45:50.000000000 +0200
@@ -42,6 +42,11 @@
 print $pid "$$\n";
 close($pid);
 
+# close gracefully
+sub rmpid { unlink "$pidfile"; exit 1; };
+$SIG{INT} = \&rmpid;
+$SIG{TERM} = \&rmpid;
+
 if ($timeout != 0) {
     # die after the given timeout
     alarm($timeout);
diff -Nru bind9-9.16.13/CHANGES bind9-9.16.15/CHANGES
--- bind9-9.16.13/CHANGES	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/CHANGES	2021-04-19 15:45:50.000000000 +0200
@@ -1,3 +1,89 @@
+	--- 9.16.15 released ---
+
+5621.	[bug]		Due to a backporting mistake in change 5609, named
+			binaries built against a Kerberos/GSSAPI library whose
+			header files did not define the GSS_SPNEGO_MECHANISM
+			preprocessor macro were not able to start if their
+			configuration included the "tkey-gssapi-credential"
+			option. This has been fixed. [GL #2634]
+
+5620.	[bug]		If zone journal files written by BIND 9.16.11 or earlier
+			were present when BIND was upgraded, the zone file for
+			that zone could have been inadvertently rewritten with
+			the current zone contents. This caused the original zone
+			file structure (e.g. comments, $INCLUDE directives) to
+			be lost, although the zone data itself was preserved.
+			This has been fixed. [GL #2623]
+
+	--- 9.16.14 released ---
+
+5617.	[security]	A specially crafted GSS-TSIG query could cause a buffer
+			overflow in the ISC implementation of SPNEGO.
+			(CVE-2021-25216) [GL #2604]
+
+5616.	[security]	named crashed when a DNAME record placed in the ANSWER
+			section during DNAME chasing turned out to be the final
+			answer to a client query. (CVE-2021-25215) [GL #2540]
+
+5615.	[security]	Insufficient IXFR checks could result in named serving a
+			zone without an SOA record at the apex, leading to a
+			RUNTIME_CHECK assertion failure when the zone was
+			subsequently refreshed. This has been fixed by adding an
+			owner name check for all SOA records which are included
+			in a zone transfer. (CVE-2021-25214) [GL #2467]
+
+5614.	[bug]		Ensure all resources are properly cleaned up when a call
+			to gss_accept_sec_context() fails. [GL #2620]
+
+5613.	[bug]		It was possible to write an invalid transaction header
+			in the journal file for a managed-keys database after
+			upgrading. This has been fixed. Invalid headers in
+			existing journal files are detected and named is able
+			to recover from them. [GL #2600]
+
+5611.	[func]		Set "stale-answer-client-timeout" to "off" by default.
+			[GL #2608]
+
+5610.	[bug]		Prevent a crash which could happen when a lookup
+			triggered by "stale-answer-client-timeout" was attempted
+			right after recursion for a client query finished.
+			[GL #2594]
+
+5609.	[func]		The ISC implementation of SPNEGO was removed from BIND 9
+			source code. It was no longer necessary as all major
+			contemporary Kerberos/GSSAPI libraries include support
+			for SPNEGO. [GL #2607]
+
+5608.	[bug]		When sending queries over TCP, dig now properly handles
+			"+tries=1 +retry=0" by not retrying the connection when
+			the remote server closes the connection prematurely.
+			[GL #2490]
+
+5607.	[bug]		As "rndc dnssec -checkds" and "rndc dnssec -rollover"
+			commands may affect the next scheduled key event,
+			reconfiguration of zone keys is now triggered after
+			receiving either of these commands to prevent
+			unnecessary key rollover delays. [GL #2488]
+
+5606.	[bug]		CDS/CDNSKEY DELETE records are now removed when a zone
+			transitions from a secure to an insecure state.
+			named-checkzone also no longer reports an error when
+			such records are found in an unsigned zone. [GL #2517]
+
+5605.	[bug]		"dig -u" now uses the CLOCK_REALTIME clock source for
+			more accurate time reporting. [GL #2592]
+
+5603.	[bug]		Fix a memory leak that occurred when named failed to
+			bind a UDP socket to a network interface. [GL #2575]
+
+5602.	[bug]		Fix TCPDNS and TLSDNS timers in Network Manager. This
+			makes the "tcp-initial-timeout" and "tcp-idle-timeout"
+			options work correctly again. [GL #2583]
+
+5601.	[bug]		Zones using KASP could not be thawed after they were
+			frozen using "rndc freeze". This has been fixed.
+			[GL #2523]
+
 	--- 9.16.13 released ---
 
 5597.	[bug]		When serve-stale was enabled and starting the recursive
diff -Nru bind9-9.16.13/configure bind9-9.16.15/configure
--- bind9-9.16.13/configure	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/configure	2021-04-19 15:45:50.000000000 +0200
@@ -705,7 +705,6 @@
 LIBCAP_LIBS
 DST_EXTRA_SRCS
 DST_EXTRA_OBJS
-USE_ISC_SPNEGO
 READLINE_LIB
 BIND9_CO_RULE
 LIBTOOL_MODE_UNINSTALL
@@ -928,7 +927,6 @@
 enable_symtable
 enable_tcp_fastopen
 with_readline
-enable_isc_spnego
 enable_chroot
 enable_linux_caps
 enable_fixed_rrset
@@ -1634,7 +1632,6 @@
   --enable-symtable       use internal symbol table for backtrace
                           [all|minimal(default)|none]
   --disable-tcp-fastopen  disable TCP Fast Open support [default=yes]
-  --disable-isc-spnego    use SPNEGO from GSSAPI library
   --disable-chroot        disable chroot
   --disable-linux-caps    disable Linux capabilities
   --enable-fixed-rrset    enable fixed rrset ordering [default=no]
@@ -17527,8 +17524,8 @@
     else
 	KRB5_CONFIG="$use_gssapi"
     fi
-    gssapi_cflags=`$KRB5_CONFIG --cflags gssapi`
-    gssapi_libs=`$KRB5_CONFIG --libs gssapi`
+    gssapi_cflags=`$KRB5_CONFIG --cflags gssapi krb5`
+    gssapi_libs=`$KRB5_CONFIG --libs gssapi krb5`
     saved_cppflags="$CPPFLAGS"
     CPPFLAGS="$gssapi_cflags $CPPFLAGS"
     for ac_header in gssapi.h gssapi/gssapi.h
@@ -19548,35 +19545,6 @@
 
 fi
 
-#
-# Use our own SPNEGO implementation?
-#
-# [pairwise: --enable-isc-spnego, --disable-isc-spnego]
-# Check whether --enable-isc-spnego was given.
-if test "${enable_isc_spnego+set}" = set; then :
-  enableval=$enable_isc_spnego;
-fi
-
-
-if test -n "$USE_GSSAPI"
-then
-	case "$enable_isc_spnego" in
-		yes|'')
-			USE_ISC_SPNEGO='-DUSE_ISC_SPNEGO'
-			DST_EXTRA_OBJS="$DST_EXTRA_OBJS spnego.$O"
-			DST_EXTRA_SRCS="$DST_EXTRA_SRCS spnego.c"
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: using SPNEGO from lib/dns" >&5
-$as_echo "using SPNEGO from lib/dns" >&6; }
-			;;
-		no)
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: using SPNEGO from GSSAPI library" >&5
-$as_echo "using SPNEGO from GSSAPI library" >&6; }
-			;;
-	esac
-fi
-
-
-
 
 
 
diff -Nru bind9-9.16.13/configure.ac bind9-9.16.15/configure.ac
--- bind9-9.16.13/configure.ac	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/configure.ac	2021-04-19 15:45:50.000000000 +0200
@@ -993,8 +993,8 @@
     else
 	KRB5_CONFIG="$use_gssapi"
     fi
-    gssapi_cflags=`$KRB5_CONFIG --cflags gssapi`
-    gssapi_libs=`$KRB5_CONFIG --libs gssapi`
+    gssapi_cflags=`$KRB5_CONFIG --cflags gssapi krb5`
+    gssapi_libs=`$KRB5_CONFIG --libs gssapi krb5`
     saved_cppflags="$CPPFLAGS"
     CPPFLAGS="$gssapi_cflags $CPPFLAGS"
     AC_CHECK_HEADERS(gssapi.h gssapi/gssapi.h,
@@ -1735,31 +1735,6 @@
 	AC_CHECK_HEADERS(readline/history.h)
 fi
 
-#
-# Use our own SPNEGO implementation?
-#
-# [pairwise: --enable-isc-spnego, --disable-isc-spnego]
-AC_ARG_ENABLE(isc-spnego,
-	      AS_HELP_STRING([--disable-isc-spnego],
-			     [use SPNEGO from GSSAPI library]))
-
-if test -n "$USE_GSSAPI"
-then
-	case "$enable_isc_spnego" in
-		yes|'')
-			USE_ISC_SPNEGO='-DUSE_ISC_SPNEGO'
-			DST_EXTRA_OBJS="$DST_EXTRA_OBJS spnego.$O"
-			DST_EXTRA_SRCS="$DST_EXTRA_SRCS spnego.c"
-			AC_MSG_RESULT(using SPNEGO from lib/dns)
-			;;
-		no)
-			AC_MSG_RESULT(using SPNEGO from GSSAPI library)
-			;;
-	esac
-fi
-
-AC_SUBST(USE_ISC_SPNEGO)
-
 AC_SUBST(DST_EXTRA_OBJS)
 AC_SUBST(DST_EXTRA_SRCS)
 
diff -Nru bind9-9.16.13/debian/changelog bind9-9.16.15/debian/changelog
--- bind9-9.16.13/debian/changelog	2021-03-18 14:23:49.000000000 +0100
+++ bind9-9.16.15/debian/changelog	2021-04-29 09:11:32.000000000 +0200
@@ -1,3 +1,20 @@
+bind9 (1:9.16.15-1) unstable; urgency=high
+
+  * New upstream version 9.16.15 (Closes: #987741, #987742, #987743)
+   + CVE-2021-25214: A malformed incoming IXFR transfer could trigger an
+     assertion failure in ``named``, causing it to quit abnormally.
+   + CVE-2021-25215: ``named`` crashed when a DNAME record placed in the
+     ANSWER section during DNAME chasing turned out to be the final
+     answer to a client query.
+   + CVE-2021-25216: When a server's configuration set the
+    ``tkey-gssapi-keytab`` or ``tkey-gssapi-credential`` option, a
+    specially crafted GSS-TSIG query could cause a buffer overflow in
+    the ISC implementation of SPNEGO (a protocol enabling negotiation of
+    the security mechanism used for GSSAPI authentication).
+  * Add patches to implement I-D draft-hardaker-dnsop-nsec3-guidance
+
+ -- OndÅ™ej SurÃ½ <ondrej@debian.org>  Thu, 29 Apr 2021 09:11:32 +0200
+
 bind9 (1:9.16.13-1) unstable; urgency=medium
 
   * New upstream version 9.16.13
diff -Nru bind9-9.16.13/debian/patches/0004-Add-TCP-timeouts-system-test.patch bind9-9.16.15/debian/patches/0004-Add-TCP-timeouts-system-test.patch
--- bind9-9.16.13/debian/patches/0004-Add-TCP-timeouts-system-test.patch	2021-03-18 14:23:49.000000000 +0100
+++ bind9-9.16.15/debian/patches/0004-Add-TCP-timeouts-system-test.patch	1970-01-01 01:00:00.000000000 +0100
@@ -1,452 +0,0 @@
-From: =?utf-8?b?T25kxZllaiBTdXLDvQ==?= <ondrej@sury.org>
-Date: Wed, 17 Mar 2021 12:20:40 +0100
-Subject: Add TCP timeouts system test
-
-The system tests were missing a test that would test tcp-initial-timeout
-and tcp-idle-timeout.
-
-This commit adds new "timeouts" system test that adds:
-
-  * Test that waits longer than tcp-initial-timeout and then checks
-    whether the socket was closed
-
-  * Test that sends and receives DNS message then waits longer than
-    tcp-initial-timeout but shorter time than tcp-idle-timeout than
-    sends DNS message again than waits longer than tcp-idle-timeout
-    and checks whether the socket was closed
-
-  * Similar test, but bursting 25 DNS messages than waiting longer than
-    tcp-initial-timeout and shorter than tcp-idle-timeout than do second
-    25 DNS message burst
-
-  * Check whether transfer longer than tcp-initial-timeout succeeds
----
- .pylintrc                                   |   1 +
- bin/tests/system/conf.sh.common             |   1 +
- bin/tests/system/timeouts/clean.sh          |  19 ++++
- bin/tests/system/timeouts/conftest.py       |  59 ++++++++++
- bin/tests/system/timeouts/ns1/example.db    |  23 ++++
- bin/tests/system/timeouts/ns1/named.conf.in |  41 +++++++
- bin/tests/system/timeouts/ns1/root.db       |  22 ++++
- bin/tests/system/timeouts/setup.sh          |  23 ++++
- bin/tests/system/timeouts/tests-tcp.py      | 161 ++++++++++++++++++++++++++++
- 9 files changed, 350 insertions(+)
- create mode 100644 bin/tests/system/timeouts/clean.sh
- create mode 100644 bin/tests/system/timeouts/conftest.py
- create mode 100644 bin/tests/system/timeouts/ns1/example.db
- create mode 100644 bin/tests/system/timeouts/ns1/named.conf.in
- create mode 100644 bin/tests/system/timeouts/ns1/root.db
- create mode 100644 bin/tests/system/timeouts/setup.sh
- create mode 100644 bin/tests/system/timeouts/tests-tcp.py
-
-diff --git a/.pylintrc b/.pylintrc
-index 750e50e..178a06a 100644
---- a/.pylintrc
-+++ b/.pylintrc
-@@ -5,3 +5,4 @@ disable=
-     C0116, # missing-function-docstring
-     R0801, # duplicate-code
-     C0103, # invalid-name
-+    C0415,# import-outside-toplevel
-diff --git a/bin/tests/system/conf.sh.common b/bin/tests/system/conf.sh.common
-index 405f88b..1d7ba69 100644
---- a/bin/tests/system/conf.sh.common
-+++ b/bin/tests/system/conf.sh.common
-@@ -129,6 +129,7 @@ statistics \
- statschannel \
- stub \
- synthfromdnssec \
-+timeouts \
- tcp \
- tools \
- tsig \
-diff --git a/bin/tests/system/timeouts/clean.sh b/bin/tests/system/timeouts/clean.sh
-new file mode 100644
-index 0000000..b177285
---- /dev/null
-+++ b/bin/tests/system/timeouts/clean.sh
-@@ -0,0 +1,19 @@
-+#!/bin/sh
-+#
-+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-+#
-+# 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 https://mozilla.org/MPL/2.0/.
-+#
-+# See the COPYRIGHT file distributed with this work for additional
-+# information regarding copyright ownership.
-+
-+rm -f ./ns*/managed-keys.bind*
-+rm -f ./ns*/named.conf
-+rm -f ./ns*/named.lock
-+rm -f ./ns*/named.memstats
-+rm -f ./ns*/named.run*
-+rm -f ./ns*/named.stats
-+rm -rf ./.cache ./__pycache__
-+rm -f ./ns*/large.db
-diff --git a/bin/tests/system/timeouts/conftest.py b/bin/tests/system/timeouts/conftest.py
-new file mode 100644
-index 0000000..368fe9c
---- /dev/null
-+++ b/bin/tests/system/timeouts/conftest.py
-@@ -0,0 +1,59 @@
-+############################################################################
-+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-+#
-+# 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 https://mozilla.org/MPL/2.0/.
-+#
-+# See the COPYRIGHT file distributed with this work for additional
-+# information regarding copyright ownership.
-+############################################################################
-+
-+import os
-+import pytest
-+
-+
-+def pytest_configure(config):
-+    config.addinivalue_line(
-+        "markers", "dnspython: mark tests that need dnspython to function"
-+    )
-+    config.addinivalue_line(
-+        "markers", "dnspython2: mark tests that need dnspython >= 2.0.0"
-+    )
-+
-+
-+def pytest_collection_modifyitems(config, items):
-+    # pylint: disable=unused-argument,unused-import,too-many-branches
-+    # pylint: disable=import-outside-toplevel
-+
-+    # Test for dnspython module
-+    skip_dnspython = pytest.mark.skip(
-+        reason="need dnspython module to run")
-+    try:
-+        import dns.query  # noqa: F401
-+    except ModuleNotFoundError:
-+        for item in items:
-+            if "dnspython" in item.keywords:
-+                item.add_marker(skip_dnspython)
-+
-+    # Test for dnspython >= 2.0.0 module
-+    skip_dnspython2 = pytest.mark.skip(
-+        reason="need dnspython >= 2.0.0 module to run")
-+    try:
-+        from dns.query import send_tcp  # noqa: F401
-+    except ImportError:
-+        for item in items:
-+            if "dnspython2" in item.keywords:
-+                item.add_marker(skip_dnspython2)
-+
-+
-+@pytest.fixture
-+def port(request):
-+    # pylint: disable=unused-argument
-+    env_port = os.getenv("PORT")
-+    if port is None:
-+        env_port = 5300
-+    else:
-+        env_port = int(env_port)
-+
-+    return env_port
-diff --git a/bin/tests/system/timeouts/ns1/example.db b/bin/tests/system/timeouts/ns1/example.db
-new file mode 100644
-index 0000000..310a31b
---- /dev/null
-+++ b/bin/tests/system/timeouts/ns1/example.db
-@@ -0,0 +1,23 @@
-+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-+;
-+; 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/.
-+;
-+; See the COPYRIGHT file distributed with this work for additional
-+; information regarding copyright ownership.
-+
-+$TTL 300	; 5 minutes
-+@			SOA	mname1. . (
-+				2000062101 ; serial
-+				20         ; refresh (20 seconds)
-+				20         ; retry (20 seconds)
-+				1814400    ; expire (3 weeks)
-+				3600       ; minimum (1 hour)
-+				)
-+			NS	ns1
-+ns1			A	10.53.0.1
-+@			A	10.53.0.1
-+a			A	10.53.0.1
-+b			A	10.53.0.1
-+$INCLUDE large.db
-diff --git a/bin/tests/system/timeouts/ns1/named.conf.in b/bin/tests/system/timeouts/ns1/named.conf.in
-new file mode 100644
-index 0000000..7923524
---- /dev/null
-+++ b/bin/tests/system/timeouts/ns1/named.conf.in
-@@ -0,0 +1,41 @@
-+/*
-+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-+ *
-+ * 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/.
-+ *
-+ * See the COPYRIGHT file distributed with this work for additional
-+ * information regarding copyright ownership.
-+ */
-+
-+include "../../common/rndc.key";
-+
-+controls {
-+	inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
-+};
-+
-+options {
-+	query-source address 10.53.0.1;
-+	notify-source 10.53.0.1;
-+	transfer-source 10.53.0.1;
-+	port @PORT@;
-+	pid-file "named.pid";
-+	listen-on { 10.53.0.1; };
-+	listen-on-v6 { none; };
-+	recursion no;
-+	notify no;
-+	tcp-initial-timeout 20;
-+	tcp-idle-timeout 50;
-+};
-+
-+zone "." {
-+	type primary;
-+	file "root.db";
-+};
-+
-+zone "example." {
-+	type primary;
-+	file "example.db";
-+	check-integrity no;
-+};
-diff --git a/bin/tests/system/timeouts/ns1/root.db b/bin/tests/system/timeouts/ns1/root.db
-new file mode 100644
-index 0000000..2c481ea
---- /dev/null
-+++ b/bin/tests/system/timeouts/ns1/root.db
-@@ -0,0 +1,22 @@
-+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-+;
-+; 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/.
-+;
-+; See the COPYRIGHT file distributed with this work for additional
-+; information regarding copyright ownership.
-+
-+$TTL 300
-+.			IN SOA	gson.isc.org. a.root.servers.nil. (
-+				2000042100   	; serial
-+				600         	; refresh
-+				600         	; retry
-+				1200    	; expire
-+				600       	; minimum
-+				)
-+.			NS	a.root-servers.nil.
-+a.root-servers.nil.	A	10.53.0.1
-+
-+example.		NS	ns1.example.
-+ns1.example.		A	10.53.0.1
-diff --git a/bin/tests/system/timeouts/setup.sh b/bin/tests/system/timeouts/setup.sh
-new file mode 100644
-index 0000000..2af68a2
---- /dev/null
-+++ b/bin/tests/system/timeouts/setup.sh
-@@ -0,0 +1,23 @@
-+#!/bin/sh
-+#
-+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-+#
-+# 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 https://mozilla.org/MPL/2.0/.
-+#
-+# See the COPYRIGHT file distributed with this work for additional
-+# information regarding copyright ownership.
-+
-+. ../conf.sh
-+
-+copy_setports ns1/named.conf.in ns1/named.conf
-+
-+#
-+# Generate a large enough zone, so the transfer takes longer than
-+# tcp-initial-timeout interval
-+#
-+$PYTHON -c "
-+for a in range(150000):
-+    print('%s IN NS a' % (a))
-+    print('%s IN NS b' % (a))" > ns1/large.db
-diff --git a/bin/tests/system/timeouts/tests-tcp.py b/bin/tests/system/timeouts/tests-tcp.py
-new file mode 100644
-index 0000000..03f69da
---- /dev/null
-+++ b/bin/tests/system/timeouts/tests-tcp.py
-@@ -0,0 +1,161 @@
-+#!/usr/bin/python3
-+############################################################################
-+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-+#
-+# 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 https://mozilla.org/MPL/2.0/.
-+#
-+# See the COPYRIGHT file distributed with this work for additional
-+# information regarding copyright ownership.
-+############################################################################
-+
-+# pylint: disable=unused-variable
-+
-+import socket
-+import time
-+
-+import pytest
-+
-+TIMEOUT = 10
-+
-+
-+def create_msg(qname, qtype):
-+    import dns.message
-+    msg = dns.message.make_query(qname, qtype, want_dnssec=True,
-+                                 use_edns=0, payload=4096)
-+
-+    return msg
-+
-+
-+def timeout():
-+    return time.time() + TIMEOUT
-+
-+
-+@pytest.mark.dnspython
-+@pytest.mark.dnspython2
-+def test_initial_timeout(port):
-+    #
-+    # The initial timeout is 2.5 seconds, so this should timeout
-+    #
-+    import dns.query
-+
-+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
-+        sock.connect(("10.53.0.1", port))
-+
-+        time.sleep(3)
-+
-+        msg = create_msg("example.", "A")
-+
-+        with pytest.raises(EOFError):
-+            try:
-+                (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
-+                (response, rtime) = dns.query.receive_tcp(sock, timeout())
-+            except ConnectionResetError as e:
-+                raise EOFError from e
-+
-+
-+@pytest.mark.dnspython
-+@pytest.mark.dnspython2
-+def test_idle_timeout(port):
-+    #
-+    # The idle timeout is 5 second, so sending the second message must fail
-+    #
-+    import dns.rcode
-+
-+    msg = create_msg("example.", "A")
-+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
-+        sock.connect(("10.53.0.1", port))
-+
-+        time.sleep(1)
-+
-+        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
-+        (response, rtime) = dns.query.receive_tcp(sock, timeout())
-+
-+        time.sleep(3)
-+
-+        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
-+        (response, rtime) = dns.query.receive_tcp(sock, timeout())
-+
-+        time.sleep(6)
-+
-+        with pytest.raises(EOFError):
-+            try:
-+                (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
-+                (response, rtime) = dns.query.receive_tcp(sock, timeout())
-+            except ConnectionResetError as e:
-+                raise EOFError from e
-+
-+
-+@pytest.mark.dnspython
-+@pytest.mark.dnspython2
-+def test_pipelining_timeout(port):
-+    #
-+    # The pipelining should only timeout after the last message is received
-+    #
-+    import dns.query
-+
-+    msg = create_msg("example.", "A")
-+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
-+        sock.connect(("10.53.0.1", port))
-+
-+        time.sleep(1)
-+
-+        # Send and receive 25 DNS queries
-+        for n in range(25):
-+            (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
-+        for n in range(25):
-+            (response, rtime) = dns.query.receive_tcp(sock, timeout())
-+
-+        time.sleep(3)
-+
-+        # Send and receive 25 DNS queries
-+        for n in range(25):
-+            (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
-+        for n in range(25):
-+            (response, rtime) = dns.query.receive_tcp(sock, timeout())
-+
-+        time.sleep(6)
-+
-+        with pytest.raises(EOFError):
-+            try:
-+                (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
-+                (response, rtime) = dns.query.receive_tcp(sock, timeout())
-+            except ConnectionResetError as e:
-+                raise EOFError from e
-+
-+
-+@pytest.mark.dnspython
-+@pytest.mark.dnspython2
-+def test_long_axfr(port):
-+    #
-+    # The timers should not fire during AXFR, thus the connection should not
-+    # close abruptly
-+    #
-+    import dns.query
-+    import dns.rdataclass
-+    import dns.rdatatype
-+
-+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
-+        sock.connect(("10.53.0.1", port))
-+
-+        name = dns.name.from_text("example.")
-+        msg = create_msg("example.", "AXFR")
-+        (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
-+
-+        # Receive the initial DNS message with SOA
-+        (response, rtime) = dns.query.receive_tcp(sock, timeout(),
-+                                                  one_rr_per_rrset=True)
-+        soa = response.get_rrset(dns.message.ANSWER, name,
-+                                 dns.rdataclass.IN, dns.rdatatype.SOA)
-+        assert soa is not None
-+
-+        # Pull DNS message from wire until the second SOA is received
-+        while True:
-+            (response, rtime) = dns.query.receive_tcp(sock, timeout(),
-+                                                      one_rr_per_rrset=True)
-+            soa = response.get_rrset(dns.message.ANSWER, name,
-+                                     dns.rdataclass.IN, dns.rdatatype.SOA)
-+            if soa is not None:
-+                break
-+        assert soa is not None
diff -Nru bind9-9.16.13/debian/patches/0004-Reduce-nsec3-max-iterations-to-150.patch bind9-9.16.15/debian/patches/0004-Reduce-nsec3-max-iterations-to-150.patch
--- bind9-9.16.13/debian/patches/0004-Reduce-nsec3-max-iterations-to-150.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0004-Reduce-nsec3-max-iterations-to-150.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,222 @@
+From: Mark Andrews <marka@isc.org>
+Date: Mon, 19 Apr 2021 16:32:54 +1000
+Subject: Reduce nsec3 max iterations to 150
+
+---
+ bin/dnssec/dnssec-signzone.c |  7 ++---
+ bin/named/server.c           |  3 +-
+ lib/dns/include/dns/nsec3.h  | 14 ++-------
+ lib/dns/nsec3.c              | 75 ++------------------------------------------
+ lib/dns/tests/nsec3_test.c   |  9 +++---
+ lib/ns/update.c              |  9 ++----
+ 6 files changed, 17 insertions(+), 100 deletions(-)
+
+diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c
+index e69ea4f..26641fa 100644
+--- a/bin/dnssec/dnssec-signzone.c
++++ b/bin/dnssec/dnssec-signzone.c
+@@ -3855,7 +3855,6 @@ main(int argc, char *argv[]) {
+ 	warnifallksk(gdb);
+ 
+ 	if (IS_NSEC3) {
+-		unsigned int max;
+ 		bool answer;
+ 
+ 		hash_length = dns_nsec3_hashlength(dns_hash_sha1);
+@@ -3874,12 +3873,10 @@ main(int argc, char *argv[]) {
+ 			      "NSEC-only DNSKEY");
+ 		}
+ 
+-		result = dns_nsec3_maxiterations(gdb, NULL, mctx, &max);
+-		check_result(result, "dns_nsec3_maxiterations()");
+-		if (nsec3iter > max) {
++		if (nsec3iter > dns_nsec3_maxiterations()) {
+ 			fatal("NSEC3 iterations too big for weakest DNSKEY "
+ 			      "strength. Maximum iterations allowed %u.",
+-			      max);
++			      dns_nsec3_maxiterations());
+ 		}
+ 	} else {
+ 		hashlist_init(&hashlist, 0, 0); /* silence clang */
+diff --git a/bin/named/server.c b/bin/named/server.c
+index 343dc6a..a0adacd 100644
+--- a/bin/named/server.c
++++ b/bin/named/server.c
+@@ -14435,7 +14435,8 @@ named_server_signing(named_server_t *server, isc_lex_t *lex,
+ 				return (ISC_R_BADNUMBER);
+ 			}
+ 
+-			if (hash > 0xffU || flags > 0xffU) {
++			if (hash > 0xffU || flags > 0xffU ||
++			    iter > dns_nsec3_maxiterations()) {
+ 				return (ISC_R_RANGE);
+ 			}
+ 
+diff --git a/lib/dns/include/dns/nsec3.h b/lib/dns/include/dns/nsec3.h
+index 1c12a46..2f178b6 100644
+--- a/lib/dns/include/dns/nsec3.h
++++ b/lib/dns/include/dns/nsec3.h
+@@ -208,18 +208,10 @@ dns_nsec3_activex(dns_db_t *db, dns_dbversion_t *version, bool complete,
+  *	'answer' to be non NULL.
+  */
+ 
+-isc_result_t
+-dns_nsec3_maxiterations(dns_db_t *db, dns_dbversion_t *version, isc_mem_t *mctx,
+-			unsigned int *iterationsp);
++unsigned int
++dns_nsec3_maxiterations(void);
+ /*%<
+- * Find the maximum permissible number of iterations allowed based on
+- * the key strength.
+- *
+- * Requires:
+- *	'db' to be valid.
+- *	'version' to be valid or NULL.
+- *	'mctx' to be valid.
+- *	'iterationsp' to be non NULL.
++ * Return the maximum permissible number of NSEC3 iterations.
+  */
+ 
+ bool
+diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c
+index a6401e9..b5df23e 100644
+--- a/lib/dns/nsec3.c
++++ b/lib/dns/nsec3.c
+@@ -1878,78 +1878,9 @@ try_private:
+ 	return (result);
+ }
+ 
+-isc_result_t
+-dns_nsec3_maxiterations(dns_db_t *db, dns_dbversion_t *version, isc_mem_t *mctx,
+-			unsigned int *iterationsp) {
+-	dns_dbnode_t *node = NULL;
+-	dns_rdataset_t rdataset;
+-	dst_key_t *key = NULL;
+-	isc_buffer_t buffer;
+-	isc_result_t result;
+-	unsigned int bits, minbits = 4096;
+-
+-	result = dns_db_getoriginnode(db, &node);
+-	if (result != ISC_R_SUCCESS) {
+-		return (result);
+-	}
+-
+-	dns_rdataset_init(&rdataset);
+-	result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, 0,
+-				     0, &rdataset, NULL);
+-	dns_db_detachnode(db, &node);
+-	if (result == ISC_R_NOTFOUND) {
+-		*iterationsp = 0;
+-		return (ISC_R_SUCCESS);
+-	}
+-	if (result != ISC_R_SUCCESS) {
+-		goto failure;
+-	}
+-
+-	for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+-	     result = dns_rdataset_next(&rdataset))
+-	{
+-		dns_rdata_t rdata = DNS_RDATA_INIT;
+-		dns_rdataset_current(&rdataset, &rdata);
+-
+-		REQUIRE(rdata.type == dns_rdatatype_key ||
+-			rdata.type == dns_rdatatype_dnskey);
+-		REQUIRE(rdata.length > 3);
+-
+-		/* Skip unsupported algorithms when
+-		 * calculating the maximum iterations.
+-		 */
+-		if (!dst_algorithm_supported(rdata.data[3])) {
+-			continue;
+-		}
+-
+-		isc_buffer_init(&buffer, rdata.data, rdata.length);
+-		isc_buffer_add(&buffer, rdata.length);
+-		CHECK(dst_key_fromdns(dns_db_origin(db), rdataset.rdclass,
+-				      &buffer, mctx, &key));
+-		bits = dst_key_size(key);
+-		dst_key_free(&key);
+-		if (minbits > bits) {
+-			minbits = bits;
+-		}
+-	}
+-	if (result != ISC_R_NOMORE) {
+-		goto failure;
+-	}
+-
+-	if (minbits <= 1024) {
+-		*iterationsp = 150;
+-	} else if (minbits <= 2048) {
+-		*iterationsp = 500;
+-	} else {
+-		*iterationsp = 2500;
+-	}
+-	result = ISC_R_SUCCESS;
+-
+-failure:
+-	if (dns_rdataset_isassociated(&rdataset)) {
+-		dns_rdataset_disassociate(&rdataset);
+-	}
+-	return (result);
++unsigned int
++dns_nsec3_maxiterations(void) {
++	return (150);
+ }
+ 
+ isc_result_t
+diff --git a/lib/dns/tests/nsec3_test.c b/lib/dns/tests/nsec3_test.c
+index a71b3c9..a9868dd 100644
+--- a/lib/dns/tests/nsec3_test.c
++++ b/lib/dns/tests/nsec3_test.c
+@@ -60,8 +60,7 @@ iteration_test(const char *file, unsigned int expected) {
+ 	result = dns_test_loaddb(&db, dns_dbtype_zone, "test", file);
+ 	assert_int_equal(result, ISC_R_SUCCESS);
+ 
+-	result = dns_nsec3_maxiterations(db, NULL, dt_mctx, &iterations);
+-	assert_int_equal(result, ISC_R_SUCCESS);
++	iterations = dns_nsec3_maxiterations();
+ 
+ 	assert_int_equal(iterations, expected);
+ 
+@@ -138,10 +137,10 @@ max_iterations(void **state) {
+ 	UNUSED(state);
+ 
+ 	iteration_test("testdata/nsec3/1024.db", 150);
+-	iteration_test("testdata/nsec3/2048.db", 500);
+-	iteration_test("testdata/nsec3/4096.db", 2500);
++	iteration_test("testdata/nsec3/2048.db", 150);
++	iteration_test("testdata/nsec3/4096.db", 150);
+ 	iteration_test("testdata/nsec3/min-1024.db", 150);
+-	iteration_test("testdata/nsec3/min-2048.db", 500);
++	iteration_test("testdata/nsec3/min-2048.db", 150);
+ }
+ 
+ /* check dns_nsec3param_salttotext() */
+diff --git a/lib/ns/update.c b/lib/ns/update.c
+index 277bc1b..8add90a 100644
+--- a/lib/ns/update.c
++++ b/lib/ns/update.c
+@@ -1994,7 +1994,7 @@ check_dnssec(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
+ 	dns_difftuple_t *tuple;
+ 	bool nseconly = false, nsec3 = false;
+ 	isc_result_t result;
+-	unsigned int iterations = 0, max;
++	unsigned int iterations = 0;
+ 	dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
+ 
+ 	/* Scan the tuples for an NSEC-only DNSKEY or an NSEC3PARAM */
+@@ -2049,12 +2049,9 @@ check_dnssec(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
+ 
+ 	/* Verify NSEC3 params */
+ 	CHECK(get_iterations(db, ver, privatetype, &iterations));
+-	CHECK(dns_nsec3_maxiterations(db, ver, client->mctx, &max));
+-	if (max != 0 && iterations > max) {
++	if (iterations > dns_nsec3_maxiterations()) {
+ 		update_log(client, zone, ISC_LOG_ERROR,
+-			   "too many NSEC3 iterations (%u) for "
+-			   "weakest DNSKEY (%u)",
+-			   iterations, max);
++			   "too many NSEC3 iterations (%u)", iterations);
+ 		result = DNS_R_REFUSED;
+ 		goto failure;
+ 	}
diff -Nru bind9-9.16.13/debian/patches/0005-Check-dnssec-policy-nsec3param-iterations-limit.patch bind9-9.16.15/debian/patches/0005-Check-dnssec-policy-nsec3param-iterations-limit.patch
--- bind9-9.16.13/debian/patches/0005-Check-dnssec-policy-nsec3param-iterations-limit.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0005-Check-dnssec-policy-nsec3param-iterations-limit.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,51 @@
+From: Mark Andrews <marka@isc.org>
+Date: Wed, 21 Apr 2021 11:57:46 +1000
+Subject: Check dnssec-policy nsec3param iterations limit
+
+---
+ bin/tests/system/checkconf/bad-kasp-iterations.conf  | 14 ++++++++++++++
+ bin/tests/system/checkconf/good-kasp-iterations.conf | 14 ++++++++++++++
+ 2 files changed, 28 insertions(+)
+ create mode 100644 bin/tests/system/checkconf/bad-kasp-iterations.conf
+ create mode 100644 bin/tests/system/checkconf/good-kasp-iterations.conf
+
+diff --git a/bin/tests/system/checkconf/bad-kasp-iterations.conf b/bin/tests/system/checkconf/bad-kasp-iterations.conf
+new file mode 100644
+index 0000000..041ca76
+--- /dev/null
++++ b/bin/tests/system/checkconf/bad-kasp-iterations.conf
+@@ -0,0 +1,14 @@
++/*
++ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
++ *
++ * 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/.
++ *
++ * See the COPYRIGHT file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++dnssec-policy too-many-iterations {
++	nsec3param iterations 151;
++};
+diff --git a/bin/tests/system/checkconf/good-kasp-iterations.conf b/bin/tests/system/checkconf/good-kasp-iterations.conf
+new file mode 100644
+index 0000000..e197957
+--- /dev/null
++++ b/bin/tests/system/checkconf/good-kasp-iterations.conf
+@@ -0,0 +1,14 @@
++/*
++ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
++ *
++ * 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/.
++ *
++ * See the COPYRIGHT file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++dnssec-policy max-iterations {
++	nsec3param iterations 150;
++};
diff -Nru bind9-9.16.13/debian/patches/0005-Fix-TCPDNS-and-TLSDNS-timers.patch bind9-9.16.15/debian/patches/0005-Fix-TCPDNS-and-TLSDNS-timers.patch
--- bind9-9.16.13/debian/patches/0005-Fix-TCPDNS-and-TLSDNS-timers.patch	2021-03-18 14:23:49.000000000 +0100
+++ bind9-9.16.15/debian/patches/0005-Fix-TCPDNS-and-TLSDNS-timers.patch	1970-01-01 01:00:00.000000000 +0100
@@ -1,1337 +0,0 @@
-From: =?utf-8?b?T25kxZllaiBTdXLDvQ==?= <ondrej@sury.org>
-Date: Tue, 16 Mar 2021 09:03:02 +0100
-Subject: Fix TCPDNS and TLSDNS timers
-
-After the TCPDNS refactoring the initial and idle timers were broken and
-only the tcp-initial-timeout was always applied on the whole TCP
-connection.
-
-This broke any TCP connection that took longer than tcp-initial-timeout,
-most often this would affect large zone AXFRs.
-
-This commit changes the timeout logic in this way:
-
-  * On TCP connection accept the tcp-initial-timeout is applied
-    and the timer is started
-  * When we are processing and/or sending any DNS message the timer is
-    stopped
-  * When we stop processing all DNS messages, the tcp-idle-timeout
-    is applied and the timer is started again
----
- lib/isc/include/isc/netmgr.h |   6 +-
- lib/isc/netmgr/netmgr-int.h  |  31 ++++-
- lib/isc/netmgr/netmgr.c      | 315 ++++++++++++++++++++++++++++++++++++++++---
- lib/isc/netmgr/tcp.c         |  62 ++-------
- lib/isc/netmgr/tcpdns.c      | 189 ++++++++++++--------------
- lib/isc/netmgr/tlsdns.c      | 124 ++++++-----------
- lib/isc/netmgr/udp.c         |  56 ++------
- lib/isc/win32/libisc.def.in  |   1 +
- 8 files changed, 483 insertions(+), 301 deletions(-)
-
-diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h
-index 455101b..cab20b6 100644
---- a/lib/isc/include/isc/netmgr.h
-+++ b/lib/isc/include/isc/netmgr.h
-@@ -162,9 +162,11 @@ isc_nmhandle_setdata(isc_nmhandle_t *handle, void *arg,
- 
- void
- isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
-+void
-+isc_nmhandle_cleartimeout(isc_nmhandle_t *handle);
- /*%<
-- * Set the read/recv timeout for the socket connected to 'handle'
-- * to 'timeout', and reset the timer.
-+ * Set/clear the read/recv timeout for the socket connected to 'handle'
-+ * to 'timeout', and reset the timer, in miliseconds.
-  *
-  * When this is called on a 'wrapper' socket handle (for example,
-  * a TCPDNS socket wrapping a TCP connection), the timer is set for
-diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h
-index 0d7bdae..8b26dfb 100644
---- a/lib/isc/netmgr/netmgr-int.h
-+++ b/lib/isc/netmgr/netmgr-int.h
-@@ -670,6 +670,12 @@ enum {
- 	STATID_ACTIVE = 10
- };
- 
-+typedef void (*isc_nm_closehandlecb_t)(void *arg);
-+/*%<
-+ * Opaque callback function, used for isc_nmhandle 'reset' and 'free'
-+ * callbacks.
-+ */
-+
- struct isc_nmsocket {
- 	/*% Unlocked, RO */
- 	int magic;
-@@ -892,7 +898,7 @@ struct isc_nmsocket {
- 	 * as the argument whenever a handle's references drop
- 	 * to zero, after its reset callback has been called.
- 	 */
--	isc_nm_opaquecb_t closehandle_cb;
-+	isc_nm_closehandlecb_t closehandle_cb;
- 
- 	isc_nmhandle_t *recv_handle;
- 	isc_nm_recv_cb_t recv_cb;
-@@ -1030,6 +1036,16 @@ isc__nmsocket_clearcb(isc_nmsocket_t *sock);
-  * Clear the recv and accept callbacks in 'sock'.
-  */
- 
-+void
-+isc__nmsocket_timer_stop(isc_nmsocket_t *sock);
-+void
-+isc__nmsocket_timer_start(isc_nmsocket_t *sock);
-+void
-+isc__nmsocket_timer_restart(isc_nmsocket_t *sock);
-+/*%<
-+ * Start/stop/restart the read timeout on the socket
-+ */
-+
- void
- isc__nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
- 		  isc_result_t eresult);
-@@ -1054,7 +1070,7 @@ isc__nm_async_readcb(isc__networker_t *worker, isc__netievent_t *ev0);
- 
- void
- isc__nm_sendcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
--	       isc_result_t eresult);
-+	       isc_result_t eresult, bool async);
- void
- isc__nm_async_sendcb(isc__networker_t *worker, isc__netievent_t *ev0);
- /*%<
-@@ -1110,7 +1126,7 @@ isc__nm_udp_stoplistening(isc_nmsocket_t *sock);
- void
- isc__nm_udp_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
- /*%<
-- * Set the recv timeout for the UDP socket associated with 'handle'.
-+ * Set or clear the recv timeout for the UDP socket associated with 'handle'.
-  */
- 
- void
-@@ -1542,3 +1558,12 @@ NETIEVENT_DECL(pause);
- NETIEVENT_DECL(resume);
- NETIEVENT_DECL(shutdown);
- NETIEVENT_DECL(stop);
-+
-+void
-+isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
-+void
-+isc__nm_tcp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
-+void
-+isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
-+void
-+isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
-diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c
-index be365db..9d8efb2 100644
---- a/lib/isc/netmgr/netmgr.c
-+++ b/lib/isc/netmgr/netmgr.c
-@@ -1536,27 +1536,311 @@ isc_nmhandle_setdata(isc_nmhandle_t *handle, void *arg,
- 	handle->dofree = dofree;
- }
- 
-+static void
-+isc__nmsocket_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
-+	REQUIRE(VALID_NMSOCK(sock));
-+	switch (sock->type) {
-+	case isc_nm_udpsocket:
-+		isc__nm_udp_failed_read_cb(sock, result);
-+		return;
-+	case isc_nm_tcpsocket:
-+		isc__nm_tcp_failed_read_cb(sock, result);
-+		return;
-+	case isc_nm_tcpdnssocket:
-+		isc__nm_tcpdns_failed_read_cb(sock, result);
-+		return;
-+	case isc_nm_tlsdnssocket:
-+		isc__nm_tlsdns_failed_read_cb(sock, result);
-+		return;
-+	default:
-+		INSIST(0);
-+		ISC_UNREACHABLE();
-+	}
-+}
-+
-+static void
-+isc__nmsocket_readtimeout_cb(uv_timer_t *timer) {
-+	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer);
-+
-+	REQUIRE(VALID_NMSOCK(sock));
-+	REQUIRE(sock->tid == isc_nm_tid());
-+	REQUIRE(sock->reading);
-+
-+	isc__nmsocket_failed_read_cb(sock, ISC_R_TIMEDOUT);
-+}
-+
- void
--isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
--	REQUIRE(VALID_NMHANDLE(handle));
-+isc__nmsocket_timer_restart(isc_nmsocket_t *sock) {
-+	REQUIRE(VALID_NMSOCK(sock));
- 
--	switch (handle->sock->type) {
-+	if (sock->read_timeout == 0) {
-+		return;
-+	}
-+
-+	int r = uv_timer_start(&sock->timer, isc__nmsocket_readtimeout_cb,
-+			       sock->read_timeout, 0);
-+	RUNTIME_CHECK(r == 0);
-+}
-+
-+void
-+isc__nmsocket_timer_start(isc_nmsocket_t *sock) {
-+	REQUIRE(VALID_NMSOCK(sock));
-+
-+	if (uv_is_active((uv_handle_t *)&sock->timer)) {
-+		return;
-+	}
-+
-+	isc__nmsocket_timer_restart(sock);
-+}
-+
-+void
-+isc__nmsocket_timer_stop(isc_nmsocket_t *sock) {
-+	REQUIRE(VALID_NMSOCK(sock));
-+
-+	if (!uv_is_active((uv_handle_t *)&sock->timer)) {
-+		return;
-+	}
-+
-+	int r = uv_timer_stop(&sock->timer);
-+	RUNTIME_CHECK(r == 0);
-+}
-+
-+isc__nm_uvreq_t *
-+isc__nm_get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr) {
-+	isc__nm_uvreq_t *req = NULL;
-+
-+	req = isc__nm_uvreq_get(sock->mgr, sock);
-+	req->cb.recv = sock->recv_cb;
-+	req->cbarg = sock->recv_cbarg;
-+
-+	if (atomic_load(&sock->client)) {
-+		isc_nmhandle_attach(sock->statichandle, &req->handle);
-+	} else {
-+		req->handle = isc__nmhandle_get(sock, sockaddr, NULL);
-+	}
-+
-+	return req;
-+}
-+
-+/*%<
-+ * Allocator for read operations. Limited to size 2^16.
-+ *
-+ * Note this doesn't actually allocate anything, it just assigns the
-+ * worker's receive buffer to a socket, and marks it as "in use".
-+ */
-+void
-+isc__nm_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
-+	isc_nmsocket_t *sock = uv_handle_get_data(handle);
-+	isc__networker_t *worker = NULL;
-+
-+	REQUIRE(VALID_NMSOCK(sock));
-+	REQUIRE(isc__nm_in_netthread());
-+
-+	switch (sock->type) {
- 	case isc_nm_udpsocket:
--		isc__nm_udp_settimeout(handle, timeout);
-+		REQUIRE(size <= ISC_NETMGR_RECVBUF_SIZE);
-+		size = ISC_NETMGR_RECVBUF_SIZE;
- 		break;
--	case isc_nm_tcpsocket:
--		isc__nm_tcp_settimeout(handle, timeout);
-+	case isc_nm_tcpdnssocket:
-+		break;
-+	case isc_nm_tlsdnssocket:
-+		/*
-+		 * We need to limit the individual chunks to be read, so the
-+		 * BIO_write() will always succeed and the consumed before the
-+		 * next readcb is called.
-+		 */
-+		if (size >= ISC_NETMGR_TLSBUF_SIZE) {
-+			size = ISC_NETMGR_TLSBUF_SIZE;
-+		}
-+		break;
-+	default:
-+		INSIST(0);
-+		ISC_UNREACHABLE();
-+	}
-+
-+	worker = &sock->mgr->workers[sock->tid];
-+	INSIST(!worker->recvbuf_inuse);
-+
-+	buf->base = worker->recvbuf;
-+	buf->len = size;
-+	worker->recvbuf_inuse = true;
-+}
-+
-+void
-+isc__nm_start_reading(isc_nmsocket_t *sock) {
-+	int r;
-+
-+	if (sock->reading) {
-+		return;
-+	}
-+
-+	switch (sock->type) {
-+	case isc_nm_udpsocket:
-+		r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb,
-+				      isc__nm_udp_read_cb);
- 		break;
- 	case isc_nm_tcpdnssocket:
--		isc__nm_tcpdns_settimeout(handle, timeout);
-+		r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb,
-+				  isc__nm_tcpdns_read_cb);
- 		break;
- 	case isc_nm_tlsdnssocket:
--		isc__nm_tlsdns_settimeout(handle, timeout);
-+		r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb,
-+				  isc__nm_tlsdns_read_cb);
- 		break;
- 	default:
- 		INSIST(0);
- 		ISC_UNREACHABLE();
- 	}
-+	RUNTIME_CHECK(r == 0);
-+	sock->reading = true;
-+}
-+
-+void
-+isc__nm_stop_reading(isc_nmsocket_t *sock) {
-+	int r;
-+
-+	if (!sock->reading) {
-+		return;
-+	}
-+
-+	switch (sock->type) {
-+	case isc_nm_udpsocket:
-+		r = uv_udp_recv_stop(&sock->uv_handle.udp);
-+		break;
-+	case isc_nm_tcpdnssocket:
-+	case isc_nm_tlsdnssocket:
-+		r = uv_read_stop(&sock->uv_handle.stream);
-+		break;
-+	default:
-+		INSIST(0);
-+		ISC_UNREACHABLE();
-+	}
-+	RUNTIME_CHECK(r == 0);
-+	sock->reading = false;
-+}
-+
-+bool
-+isc__nm_inactive(isc_nmsocket_t *sock) {
-+	return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
-+		atomic_load(&sock->mgr->closing) ||
-+		(sock->server != NULL && !isc__nmsocket_active(sock->server)));
-+}
-+
-+static isc_result_t
-+processbuffer(isc_nmsocket_t *sock) {
-+	switch (sock->type) {
-+	case isc_nm_tcpdnssocket:
-+		return (isc__nm_tcpdns_processbuffer(sock));
-+	case isc_nm_tlsdnssocket:
-+		return (isc__nm_tcpdns_processbuffer(sock));
-+	default:
-+		INSIST(0);
-+		ISC_UNREACHABLE();
-+	}
-+}
-+
-+/*
-+ * Process a DNS message.
-+ *
-+ * If we only have an incomplete DNS message, we don't touch any
-+ * timers. If we do have a full message, reset the timer.
-+ *
-+ * Stop reading if this is a client socket, or if the server socket
-+ * has been set to sequential mode, or the number of queries we are
-+ * processing simultaneously has reached the clients-per-connection
-+ * limit. In this case we'll be called again by resume_processing()
-+ * later.
-+ */
-+void
-+isc__nm_process_sock_buffer(isc_nmsocket_t *sock) {
-+	for (;;) {
-+		int_fast32_t ah = atomic_load(&sock->ah);
-+		isc_result_t result = processbuffer(sock);
-+		switch (result) {
-+		case ISC_R_NOMORE:
-+			/*
-+			 * Don't reset the timer until we have a
-+			 * full DNS message.
-+			 */
-+			isc__nm_start_reading(sock);
-+			/*
-+			 * Start the timer only if there are no externally used
-+			 * active handles, there's always one active handle
-+			 * attached internally to sock->recv_handle in
-+			 * accept_connection()
-+			 */
-+			if (ah == 1) {
-+				isc__nmsocket_timer_start(sock);
-+			}
-+			return;
-+		case ISC_R_CANCELED:
-+			isc__nmsocket_timer_stop(sock);
-+			isc__nm_stop_reading(sock);
-+			return;
-+		case ISC_R_SUCCESS:
-+			/*
-+			 * Stop the timer on the successful message read, this
-+			 * also allows to restart the timer when we have no more
-+			 * data.
-+			 */
-+			isc__nmsocket_timer_stop(sock);
-+
-+			if (atomic_load(&sock->client) ||
-+			    atomic_load(&sock->sequential) ||
-+			    ah >= STREAM_CLIENTS_PER_CONN)
-+			{
-+				isc__nm_stop_reading(sock);
-+				return;
-+			}
-+			break;
-+		default:
-+			INSIST(0);
-+		}
-+	}
-+}
-+
-+void
-+isc__nm_resume_processing(void *arg) {
-+	isc_nmsocket_t *sock = (isc_nmsocket_t *)arg;
-+
-+	REQUIRE(VALID_NMSOCK(sock));
-+	REQUIRE(sock->tid == isc_nm_tid());
-+	REQUIRE(!atomic_load(&sock->client));
-+
-+	if (isc__nm_inactive(sock)) {
-+		return;
-+	}
-+
-+	isc__nm_process_sock_buffer(sock);
-+}
-+
-+void
-+isc_nmhandle_cleartimeout(isc_nmhandle_t *handle) {
-+	REQUIRE(VALID_NMHANDLE(handle));
-+	REQUIRE(VALID_NMSOCK(handle->sock));
-+
-+	switch (handle->sock->type) {
-+	default:
-+		handle->sock->read_timeout = 0;
-+
-+		if (uv_is_active((uv_handle_t *)&handle->sock->timer)) {
-+			isc__nmsocket_timer_stop(handle->sock);
-+		}
-+	}
-+}
-+
-+void
-+isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
-+	REQUIRE(VALID_NMHANDLE(handle));
-+	REQUIRE(VALID_NMSOCK(handle->sock));
-+
-+	switch (handle->sock->type) {
-+	default:
-+		handle->sock->read_timeout = timeout;
-+		if (uv_is_active((uv_handle_t *)&handle->sock->timer)) {
-+			isc__nmsocket_timer_restart(handle->sock);
-+		}
-+	}
- }
- 
- void *
-@@ -1865,22 +2149,23 @@ isc__nm_async_readcb(isc__networker_t *worker, isc__netievent_t *ev0) {
- 
- void
- isc__nm_sendcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
--	       isc_result_t eresult) {
-+	       isc_result_t eresult, bool async) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(VALID_UVREQ(uvreq));
- 	REQUIRE(VALID_NMHANDLE(uvreq->handle));
- 
--	if (eresult == ISC_R_SUCCESS) {
-+	if (!async) {
- 		isc__netievent_sendcb_t ievent = { .sock = sock,
- 						   .req = uvreq,
- 						   .result = eresult };
- 		isc__nm_async_sendcb(NULL, (isc__netievent_t *)&ievent);
--	} else {
--		isc__netievent_sendcb_t *ievent = isc__nm_get_netievent_sendcb(
--			sock->mgr, sock, uvreq, eresult);
--		isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
--				       (isc__netievent_t *)ievent);
-+		return;
- 	}
-+
-+	isc__netievent_sendcb_t *ievent =
-+		isc__nm_get_netievent_sendcb(sock->mgr, sock, uvreq, eresult);
-+	isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
-+			       (isc__netievent_t *)ievent);
- }
- 
- void
-diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c
-index 24f43c9..f100171 100644
---- a/lib/isc/netmgr/tcp.c
-+++ b/lib/isc/netmgr/tcp.c
-@@ -86,9 +86,6 @@ stop_tcp_parent(isc_nmsocket_t *sock);
- static void
- stop_tcp_child(isc_nmsocket_t *sock);
- 
--static void
--start_sock_timer(isc_nmsocket_t *sock);
--
- static void
- start_reading(isc_nmsocket_t *sock);
- 
-@@ -716,6 +713,11 @@ destroy:
- 	}
- }
- 
-+void
-+isc__nm_tcp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
-+	failed_read_cb(sock, result);
-+}
-+
- static void
- failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
- 	       isc_result_t eresult) {
-@@ -723,7 +725,7 @@ failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
- 	REQUIRE(VALID_UVREQ(req));
- 
- 	if (req->cb.send != NULL) {
--		isc__nm_sendcb(sock, req, eresult);
-+		isc__nm_sendcb(sock, req, eresult, true);
- 	} else {
- 		isc__nm_uvreq_put(&req, sock);
- 	}
-@@ -741,35 +743,6 @@ get_read_req(isc_nmsocket_t *sock) {
- 	return req;
- }
- 
--static void
--readtimeout_cb(uv_timer_t *timer) {
--	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer);
--
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(sock->tid == isc_nm_tid());
--	REQUIRE(sock->reading);
--
--	/*
--	 * Timeout; stop reading and process whatever we have.
--	 */
--	failed_read_cb(sock, ISC_R_TIMEDOUT);
--}
--
--static void
--start_sock_timer(isc_nmsocket_t *sock) {
--	if (sock->read_timeout > 0) {
--		int r = uv_timer_start(&sock->timer, readtimeout_cb,
--				       sock->read_timeout, 0);
--		REQUIRE(r == 0);
--	}
--}
--
--static void
--stop_sock_timer(isc_nmsocket_t *sock) {
--	int r = uv_timer_stop(&sock->timer);
--	REQUIRE(r == 0);
--}
--
- static void
- start_reading(isc_nmsocket_t *sock) {
- 	if (sock->reading) {
-@@ -779,8 +752,6 @@ start_reading(isc_nmsocket_t *sock) {
- 	int r = uv_read_start(&sock->uv_handle.stream, tcp_alloc_cb, read_cb);
- 	REQUIRE(r == 0);
- 	sock->reading = true;
--
--	start_sock_timer(sock);
- }
- 
- static void
-@@ -793,7 +764,7 @@ stop_reading(isc_nmsocket_t *sock) {
- 	REQUIRE(r == 0);
- 	sock->reading = false;
- 
--	stop_sock_timer(sock);
-+	isc__nmsocket_timer_stop(sock);
- }
- 
- void
-@@ -876,6 +847,7 @@ isc__nm_async_tcpstartread(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	}
- 
- 	start_reading(sock);
-+	isc__nmsocket_timer_start(sock);
- }
- 
- void
-@@ -994,7 +966,7 @@ read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
- 	/* The readcb could have paused the reading */
- 	if (sock->reading) {
- 		/* The timer will be updated */
--		start_sock_timer(sock);
-+		isc__nmsocket_timer_restart(sock);
- 	}
- 
- free:
-@@ -1196,7 +1168,7 @@ tcp_send_cb(uv_write_t *req, int status) {
- 		return;
- 	}
- 
--	isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS);
-+	isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, false);
- }
- 
- /*
-@@ -1476,20 +1448,6 @@ isc__nm_async_tcpcancel(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	failed_read_cb(sock, ISC_R_EOF);
- }
- 
--void
--isc__nm_tcp_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
--	isc_nmsocket_t *sock = NULL;
--
--	REQUIRE(VALID_NMHANDLE(handle));
--
--	sock = handle->sock;
--
--	sock->read_timeout = timeout;
--	if (uv_is_active((uv_handle_t *)&sock->timer)) {
--		start_sock_timer(sock);
--	}
--}
--
- int_fast32_t
- isc__nm_tcp_listener_nactive(isc_nmsocket_t *listener) {
- 	int_fast32_t nactive;
-diff --git a/lib/isc/netmgr/tcpdns.c b/lib/isc/netmgr/tcpdns.c
-index d7b8bbd..378f828 100644
---- a/lib/isc/netmgr/tcpdns.c
-+++ b/lib/isc/netmgr/tcpdns.c
-@@ -69,8 +69,6 @@ tcpdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
- static void
- tcpdns_close_direct(isc_nmsocket_t *sock);
- 
--static isc_result_t
--tcpdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
- static void
- tcpdns_connect_cb(uv_connect_t *uvreq, int status);
- 
-@@ -101,9 +99,6 @@ stop_tcpdns_parent(isc_nmsocket_t *sock);
- static void
- stop_tcpdns_child(isc_nmsocket_t *sock);
- 
--static void
--start_sock_timer(isc_nmsocket_t *sock);
--
- static void
- process_sock_buffer(isc_nmsocket_t *sock);
- 
-@@ -730,6 +725,7 @@ failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(result != ISC_R_SUCCESS);
- 
-+	isc__nmsocket_timer_stop(sock);
- 	stop_reading(sock);
- 
- 	if (!sock->recv_read) {
-@@ -753,6 +749,11 @@ destroy:
- 	}
- }
- 
-+void
-+isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
-+	failed_read_cb(sock, result);
-+}
-+
- static void
- failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
- 	       isc_result_t eresult) {
-@@ -760,7 +761,7 @@ failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
- 	REQUIRE(VALID_UVREQ(req));
- 
- 	if (req->cb.send != NULL) {
--		isc__nm_sendcb(sock, req, eresult);
-+		isc__nm_sendcb(sock, req, eresult, true);
- 	} else {
- 		isc__nm_uvreq_put(&req, sock);
- 	}
-@@ -783,36 +784,6 @@ get_read_req(isc_nmsocket_t *sock) {
- 	return (req);
- }
- 
--static void
--readtimeout_cb(uv_timer_t *timer) {
--	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer);
--
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(sock->tid == isc_nm_tid());
--	REQUIRE(sock->reading);
--
--	/*
--	 * Timeout; stop reading and process whatever we have.
--	 */
--
--	failed_read_cb(sock, ISC_R_TIMEDOUT);
--}
--
--static void
--start_sock_timer(isc_nmsocket_t *sock) {
--	if (sock->read_timeout > 0) {
--		int r = uv_timer_start(&sock->timer, readtimeout_cb,
--				       sock->read_timeout, 0);
--		RUNTIME_CHECK(r == 0);
--	}
--}
--
--static void
--stop_sock_timer(isc_nmsocket_t *sock) {
--	int r = uv_timer_stop(&sock->timer);
--	RUNTIME_CHECK(r == 0);
--}
--
- static void
- start_reading(isc_nmsocket_t *sock) {
- 	int r;
-@@ -824,8 +795,6 @@ start_reading(isc_nmsocket_t *sock) {
- 	r = uv_read_start(&sock->uv_handle.stream, tcpdns_alloc_cb, read_cb);
- 	RUNTIME_CHECK(r == 0);
- 	sock->reading = true;
--
--	start_sock_timer(sock);
- }
- 
- static void
-@@ -839,8 +808,6 @@ stop_reading(isc_nmsocket_t *sock) {
- 	r = uv_read_stop(&sock->uv_handle.stream);
- 	RUNTIME_CHECK(r == 0);
- 	sock->reading = false;
--
--	stop_sock_timer(sock);
- }
- 
- void
-@@ -1209,7 +1176,7 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- 	 * prep_destroy()->tcpdns_close_direct().
- 	 */
- 	isc_nmhandle_attach(handle, &csock->recv_handle);
--	start_reading(csock);
-+	process_sock_buffer(csock);
- 
- 	/*
- 	 * The initial timer has been set, update the read timeout for the next
-@@ -1284,7 +1251,7 @@ tcpdns_send_cb(uv_write_t *req, int status) {
- 		return;
- 	}
- 
--	isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS);
-+	isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, false);
- }
- 
- /*
-@@ -1292,48 +1259,70 @@ tcpdns_send_cb(uv_write_t *req, int status) {
-  */
- void
- isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0) {
--	isc_result_t result;
- 	isc__netievent_tcpdnssend_t *ievent =
- 		(isc__netievent_tcpdnssend_t *)ev0;
-+
-+	REQUIRE(ievent->sock->type == isc_nm_tcpdnssocket);
-+	REQUIRE(ievent->sock->tid == isc_nm_tid());
-+	REQUIRE(VALID_NMSOCK(ievent->sock));
-+	REQUIRE(VALID_UVREQ(ievent->req));
-+	REQUIRE(ievent->sock->tid == isc_nm_tid());
-+
-+	isc_result_t result;
- 	isc_nmsocket_t *sock = ievent->sock;
- 	isc__nm_uvreq_t *uvreq = ievent->req;
-+	uv_buf_t bufs[2] = { { .base = uvreq->tcplen, .len = 2 },
-+			     { .base = uvreq->uvbuf.base,
-+			       .len = uvreq->uvbuf.len } };
-+	int nbufs = 2;
-+	int r;
- 
- 	UNUSED(worker);
- 
--	REQUIRE(sock->type == isc_nm_tcpdnssocket);
--	REQUIRE(sock->tid == isc_nm_tid());
--
--	result = tcpdns_send_direct(sock, uvreq);
--	if (result != ISC_R_SUCCESS) {
--		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
--		failed_send_cb(sock, uvreq, result);
-+	if (inactive(sock)) {
-+		result = ISC_R_CANCELED;
-+		goto fail;
- 	}
--}
--
--static isc_result_t
--tcpdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
--	int r;
- 
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(VALID_UVREQ(req));
--	REQUIRE(sock->tid == isc_nm_tid());
--	REQUIRE(sock->type == isc_nm_tcpdnssocket);
-+	r = uv_try_write(&sock->uv_handle.stream, bufs, nbufs);
- 
--	uv_buf_t bufs[2] = { { .base = req->tcplen, .len = 2 },
--			     { .base = req->uvbuf.base,
--			       .len = req->uvbuf.len } };
-+	if (r == (int)(bufs[0].len + bufs[1].len)) {
-+		/* Wrote everything */
-+		isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, true);
-+		return;
-+	}
- 
--	if (inactive(sock)) {
--		return (ISC_R_CANCELED);
-+	if (r == 1) {
-+		/* Partial write of DNSMSG length */
-+		bufs[0].base = uvreq->tcplen + 1;
-+		bufs[0].len = 1;
-+	} else if (r > 0) {
-+		/* Partial write of DNSMSG */
-+		nbufs = 1;
-+		bufs[0].base = uvreq->uvbuf.base + (r - 2);
-+		bufs[0].len = uvreq->uvbuf.len - (r - 2);
-+	} else if (r == UV_ENOSYS || r == UV_EAGAIN) {
-+		/* uv_try_write not support, send asynchronously */
-+	} else {
-+		/* error sending data */
-+		result = isc__nm_uverr2result(r);
-+		goto fail;
- 	}
- 
--	r = uv_write(&req->uv_req.write, &sock->uv_handle.stream, bufs, 2,
-+	r = uv_write(&uvreq->uv_req.write, &sock->uv_handle.stream, bufs, nbufs,
- 		     tcpdns_send_cb);
- 	if (r < 0) {
--		return (isc__nm_uverr2result(r));
-+		result = isc__nm_uverr2result(r);
-+		goto fail;
- 	}
- 
--	return (ISC_R_SUCCESS);
-+	return;
-+
-+fail:
-+	if (result != ISC_R_SUCCESS) {
-+		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
-+		failed_send_cb(sock, uvreq, result);
-+	}
- }
- 
- static void
-@@ -1462,6 +1451,7 @@ tcpdns_close_direct(isc_nmsocket_t *sock) {
- 		isc_nmhandle_detach(&sock->recv_handle);
- 	}
- 
-+	isc__nmsocket_timer_stop(sock);
- 	stop_reading(sock);
- 	uv_close((uv_handle_t *)&sock->timer, timer_close_cb);
- }
-@@ -1567,21 +1557,6 @@ isc__nm_async_tcpdnscancel(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	failed_read_cb(sock, ISC_R_EOF);
- }
- 
--void
--isc__nm_tcpdns_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
--	isc_nmsocket_t *sock = NULL;
--
--	REQUIRE(VALID_NMHANDLE(handle));
--	REQUIRE(VALID_NMSOCK(handle->sock));
--
--	sock = handle->sock;
--
--	sock->read_timeout = timeout;
--	if (uv_is_active((uv_handle_t *)&sock->timer)) {
--		start_sock_timer(sock);
--	}
--}
--
- void
- isc_nm_tcpdns_sequential(isc_nmhandle_t *handle) {
- 	isc_nmsocket_t *sock = NULL;
-@@ -1601,6 +1576,7 @@ isc_nm_tcpdns_sequential(isc_nmhandle_t *handle) {
- 	 * is released.
- 	 */
- 
-+	isc__nmsocket_timer_stop(sock);
- 	stop_reading(sock);
- 	atomic_store(&sock->sequential, true);
- }
-@@ -1618,33 +1594,47 @@ isc_nm_tcpdns_keepalive(isc_nmhandle_t *handle, bool value) {
- 	atomic_store(&sock->keepalive, value);
- }
- 
-+/*
-+ * Process a DNS message.
-+ *
-+ * If we only have an incomplete DNS message, we don't touch any
-+ * timers. If we do have a full message, reset the timer.
-+ *
-+ * Stop reading if this is a client socket, or if the server socket
-+ * has been set to sequential mode, or the number of queries we are
-+ * processing simultaneously has reached the clients-per-connection
-+ * limit. In this event we'll be called again by resume_processing()
-+ * later.
-+ */
- static void
- process_sock_buffer(isc_nmsocket_t *sock) {
--	/*
--	 * 1. When process_buffer receives incomplete DNS message,
--	 *    we don't touch any timers
--	 *
--	 * 2. When we receive at least one full DNS message, we stop the timers
--	 *    until resume_processing calls this function again and restarts the
--	 *    reading and the timers
--	 */
--
--	/*
--	 * Process a DNS messages.  Stop if this is client socket, or the server
--	 * socket has been set to sequential mode or the number of queries we
--	 * are processing simultaneously have reached the clients-per-connection
--	 * limit.
--	 */
- 	for (;;) {
-+		int_fast32_t ah = atomic_load(&sock->ah);
- 		isc_result_t result = processbuffer(sock);
- 		switch (result) {
- 		case ISC_R_NOMORE:
-+			/*
-+			 * Don't reset the timer until we have a
-+			 * full DNS message.
-+			 */
- 			start_reading(sock);
-+			/* Start the timer if there are no active handles */
-+			if (ah == 1) {
-+				isc__nmsocket_timer_start(sock);
-+			}
- 			return;
- 		case ISC_R_CANCELED:
-+			isc__nmsocket_timer_stop(sock);
- 			stop_reading(sock);
- 			return;
- 		case ISC_R_SUCCESS:
-+			/*
-+			 * Stop the timer on the successful message read, this
-+			 * also allows to restart the timer when we have no more
-+			 * data.
-+			 */
-+			isc__nmsocket_timer_stop(sock);
-+
- 			if (atomic_load(&sock->client) ||
- 			    atomic_load(&sock->sequential) ||
- 			    atomic_load(&sock->ah) >= TCPDNS_CLIENTS_PER_CONN)
-@@ -1672,10 +1662,5 @@ resume_processing(void *arg) {
- 		return;
- 	}
- 
--	if (atomic_load(&sock->ah) == 0) {
--		/* Nothing is active; sockets can timeout now */
--		start_sock_timer(sock);
--	}
--
- 	process_sock_buffer(sock);
- }
-diff --git a/lib/isc/netmgr/tlsdns.c b/lib/isc/netmgr/tlsdns.c
-index eaa8921..a7ce21e 100644
---- a/lib/isc/netmgr/tlsdns.c
-+++ b/lib/isc/netmgr/tlsdns.c
-@@ -100,9 +100,6 @@ start_reading(isc_nmsocket_t *sock);
- static void
- stop_reading(isc_nmsocket_t *sock);
- 
--static void
--start_sock_timer(isc_nmsocket_t *sock);
--
- static void
- process_sock_buffer(isc_nmsocket_t *sock);
- 
-@@ -372,7 +369,8 @@ tlsdns_connect_cb(uv_connect_t *uvreq, int status) {
- 
- 	sock->tls.pending_req = req;
- 
--	start_reading(sock);
-+	process_sock_buffer(sock);
-+
- 	result = tls_cycle(sock);
- 	if (result != ISC_R_SUCCESS) {
- 		goto error;
-@@ -861,6 +859,7 @@ failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(result != ISC_R_SUCCESS);
- 
-+	isc__nmsocket_timer_stop(sock);
- 	stop_reading(sock);
- 
- 	if (sock->tls.pending_req) {
-@@ -890,6 +889,11 @@ destroy:
- 	}
- }
- 
-+void
-+isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
-+	failed_read_cb(sock, result);
-+}
-+
- static void
- failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
- 	       isc_result_t eresult) {
-@@ -897,7 +901,7 @@ failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
- 	REQUIRE(VALID_UVREQ(req));
- 
- 	if (req->cb.send != NULL) {
--		isc__nm_sendcb(sock, req, eresult);
-+		isc__nm_sendcb(sock, req, eresult, true);
- 	} else {
- 		isc__nm_uvreq_put(&req, sock);
- 	}
-@@ -920,36 +924,6 @@ get_read_req(isc_nmsocket_t *sock) {
- 	return (req);
- }
- 
--static void
--readtimeout_cb(uv_timer_t *timer) {
--	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer);
--
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(sock->tid == isc_nm_tid());
--	REQUIRE(sock->reading);
--
--	/*
--	 * Timeout; stop reading and process whatever we have.
--	 */
--
--	failed_read_cb(sock, ISC_R_TIMEDOUT);
--}
--
--static void
--start_sock_timer(isc_nmsocket_t *sock) {
--	if (sock->read_timeout > 0) {
--		int r = uv_timer_start(&sock->timer, readtimeout_cb,
--				       sock->read_timeout, 0);
--		RUNTIME_CHECK(r == 0);
--	}
--}
--
--static void
--stop_sock_timer(isc_nmsocket_t *sock) {
--	int r = uv_timer_stop(&sock->timer);
--	RUNTIME_CHECK(r == 0);
--}
--
- static void
- start_reading(isc_nmsocket_t *sock) {
- 	int r;
-@@ -961,8 +935,6 @@ start_reading(isc_nmsocket_t *sock) {
- 	r = uv_read_start(&sock->uv_handle.stream, tlsdns_alloc_cb, read_cb);
- 	RUNTIME_CHECK(r == 0);
- 	sock->reading = true;
--
--	start_sock_timer(sock);
- }
- 
- static void
-@@ -976,8 +948,6 @@ stop_reading(isc_nmsocket_t *sock) {
- 	r = uv_read_stop(&sock->uv_handle.stream);
- 	RUNTIME_CHECK(r == 0);
- 	sock->reading = false;
--
--	stop_sock_timer(sock);
- }
- 
- void
-@@ -1069,7 +1039,6 @@ isc__nm_async_tlsdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
- 
- 	result = tls_cycle(sock);
- 	if (result != ISC_R_SUCCESS) {
--		stop_reading(sock);
- 		failed_read_cb(sock, result);
- 	}
- }
-@@ -1216,7 +1185,7 @@ tls_cycle_input(isc_nmsocket_t *sock) {
- 		if (sock->tls.state == TLS_STATE_NONE &&
- 		    !SSL_is_init_finished(sock->tls.ssl)) {
- 			sock->tls.state = TLS_STATE_HANDSHAKE;
--			start_reading(sock);
-+			process_sock_buffer(sock);
- 		}
- 		/* else continue reading */
- 		break;
-@@ -1269,9 +1238,8 @@ static void
- tls_error(isc_nmsocket_t *sock, isc_result_t result) {
- 	switch (sock->tls.state) {
- 	case TLS_STATE_HANDSHAKE:
--		stop_reading(sock);
--		break;
- 	case TLS_STATE_IO:
-+		isc__nmsocket_timer_stop(sock);
- 		stop_reading(sock);
- 		break;
- 	case TLS_STATE_ERROR:
-@@ -1719,7 +1687,7 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- 
- 	isc_nmhandle_detach(&handle);
- 
--	start_reading(csock);
-+	process_sock_buffer(csock);
- 
- 	/*
- 	 * sock is now attached to the handle.
-@@ -1842,7 +1810,7 @@ tlsdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
- 		/* SSL_write_ex() doesn't do partial writes */
- 		INSIST(sendlen == bytes);
- 
--		isc__nm_sendcb(sock, req, ISC_R_SUCCESS);
-+		isc__nm_sendcb(sock, req, ISC_R_SUCCESS, true);
- 		async_tlsdns_cycle(sock);
- 		return (ISC_R_SUCCESS);
- 	}
-@@ -2012,6 +1980,7 @@ tlsdns_close_direct(isc_nmsocket_t *sock) {
- 		isc_nmhandle_detach(&sock->recv_handle);
- 	}
- 
-+	isc__nmsocket_timer_stop(sock);
- 	stop_reading(sock);
- 	uv_close((uv_handle_t *)&sock->timer, timer_close_cb);
- }
-@@ -2123,21 +2092,6 @@ isc__nm_async_tlsdnscancel(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	failed_read_cb(sock, ISC_R_EOF);
- }
- 
--void
--isc__nm_tlsdns_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
--	isc_nmsocket_t *sock = NULL;
--
--	REQUIRE(VALID_NMHANDLE(handle));
--	REQUIRE(VALID_NMSOCK(handle->sock));
--
--	sock = handle->sock;
--
--	sock->read_timeout = timeout;
--	if (uv_is_active((uv_handle_t *)&sock->timer)) {
--		start_sock_timer(sock);
--	}
--}
--
- void
- isc_nm_tlsdns_sequential(isc_nmhandle_t *handle) {
- 	isc_nmsocket_t *sock = NULL;
-@@ -2157,6 +2111,7 @@ isc_nm_tlsdns_sequential(isc_nmhandle_t *handle) {
- 	 * is released.
- 	 */
- 
-+	isc__nmsocket_timer_stop(sock);
- 	stop_reading(sock);
- 	atomic_store(&sock->sequential, true);
- }
-@@ -2174,33 +2129,47 @@ isc_nm_tlsdns_keepalive(isc_nmhandle_t *handle, bool value) {
- 	atomic_store(&sock->keepalive, value);
- }
- 
-+/*
-+ * Process a DNS message.
-+ *
-+ * If we only have an incomplete DNS message, we don't touch any
-+ * timers. If we do have a full message, reset the timer.
-+ *
-+ * Stop reading if this is a client socket, or if the server socket
-+ * has been set to sequential mode, or the number of queries we are
-+ * processing simultaneously has reached the clients-per-connection
-+ * limit. In this event we'll be called again by resume_processing()
-+ * later.
-+ */
- static void
- process_sock_buffer(isc_nmsocket_t *sock) {
--	/*
--	 * 1. When process_buffer receives incomplete DNS message,
--	 *    we don't touch any timers
--	 *
--	 * 2. When we receive at least one full DNS message, we stop the timers
--	 *    until resume_processing calls this function again and restarts the
--	 *    reading and the timers
--	 */
--
--	/*
--	 * Process a DNS messages.  Stop if this is client socket, or the server
--	 * socket has been set to sequential mode or the number of queries we
--	 * are processing simultaneously have reached the clients-per-connection
--	 * limit.
--	 */
- 	for (;;) {
-+		int_fast32_t ah = atomic_load(&sock->ah);
- 		isc_result_t result = processbuffer(sock);
- 		switch (result) {
- 		case ISC_R_NOMORE:
-+			/*
-+			 * Don't reset the timer until we have a
-+			 * full DNS message.
-+			 */
- 			start_reading(sock);
-+			/* Start the timer if there are no active handles */
-+			if (ah == 1) {
-+				isc__nmsocket_timer_start(sock);
-+			}
- 			return;
- 		case ISC_R_CANCELED:
-+			isc__nmsocket_timer_stop(sock);
- 			stop_reading(sock);
- 			return;
- 		case ISC_R_SUCCESS:
-+			/*
-+			 * Stop the timer on the successful message read, this
-+			 * also allows to restart the timer when we have no more
-+			 * data.
-+			 */
-+			isc__nmsocket_timer_stop(sock);
-+
- 			if (atomic_load(&sock->client) ||
- 			    atomic_load(&sock->sequential) ||
- 			    atomic_load(&sock->ah) >= TLSDNS_CLIENTS_PER_CONN)
-@@ -2228,10 +2197,5 @@ resume_processing(void *arg) {
- 		return;
- 	}
- 
--	if (atomic_load(&sock->ah) == 0) {
--		/* Nothing is active; sockets can timeout now */
--		start_sock_timer(sock);
--	}
--
- 	process_sock_buffer(sock);
- }
-diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c
-index 411b8de..b735a7b 100644
---- a/lib/isc/netmgr/udp.c
-+++ b/lib/isc/netmgr/udp.c
-@@ -596,7 +596,7 @@ udp_send_cb(uv_udp_send_t *req, int status) {
- 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
- 	}
- 
--	isc__nm_sendcb(sock, uvreq, result);
-+	isc__nm_sendcb(sock, uvreq, result, false);
- }
- 
- /*
-@@ -900,6 +900,11 @@ failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	}
- }
- 
-+void
-+isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
-+	failed_read_cb(sock, result);
-+}
-+
- static void
- failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
- 	       isc_result_t eresult) {
-@@ -907,7 +912,7 @@ failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
- 	REQUIRE(VALID_UVREQ(req));
- 
- 	if (req->cb.send != NULL) {
--		isc__nm_sendcb(sock, req, eresult);
-+		isc__nm_sendcb(sock, req, eresult, true);
- 	} else {
- 		isc__nm_uvreq_put(&req, sock);
- 	}
-@@ -930,20 +935,6 @@ get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr) {
- 	return req;
- }
- 
--static void
--readtimeout_cb(uv_timer_t *handle) {
--	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle);
--
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(sock->tid == isc_nm_tid());
--	REQUIRE(sock->reading);
--
--	/*
--	 * Timeout; stop reading and process whatever we have.
--	 */
--	failed_read_cb(sock, ISC_R_TIMEDOUT);
--}
--
- /*
-  * Asynchronous 'udpread' call handler: start or resume reading on a
-  * socket; pause reading and call the 'recv' callback after each
-@@ -966,21 +957,7 @@ isc__nm_async_udpread(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	}
- 
- 	start_reading(sock);
--}
--
--static void
--start_sock_timer(isc_nmsocket_t *sock) {
--	if (sock->read_timeout > 0) {
--		int r = uv_timer_start(&sock->timer, readtimeout_cb,
--				       sock->read_timeout, 0);
--		REQUIRE(r == 0);
--	}
--}
--
--static void
--stop_sock_timer(isc_nmsocket_t *sock) {
--	int r = uv_timer_stop(&sock->timer);
--	REQUIRE(r == 0);
-+	isc__nmsocket_timer_start(sock);
- }
- 
- static void
-@@ -993,8 +970,6 @@ start_reading(isc_nmsocket_t *sock) {
- 				  udp_read_cb);
- 	REQUIRE(r == 0);
- 	sock->reading = true;
--
--	start_sock_timer(sock);
- }
- 
- static void
-@@ -1007,7 +982,7 @@ stop_reading(isc_nmsocket_t *sock) {
- 	REQUIRE(r == 0);
- 	sock->reading = false;
- 
--	stop_sock_timer(sock);
-+	isc__nmsocket_timer_stop(sock);
- }
- 
- void
-@@ -1263,16 +1238,3 @@ isc__nm_async_udpcancel(isc__networker_t *worker, isc__netievent_t *ev0) {
- 
- 	failed_read_cb(sock, ISC_R_EOF);
- }
--
--void
--isc__nm_udp_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
--	REQUIRE(VALID_NMHANDLE(handle));
--	REQUIRE(VALID_NMSOCK(handle->sock));
--
--	isc_nmsocket_t *sock = handle->sock;
--
--	sock->read_timeout = timeout;
--	if (uv_is_active((uv_handle_t *)&sock->timer)) {
--		start_sock_timer(sock);
--	}
--}
-diff --git a/lib/isc/win32/libisc.def.in b/lib/isc/win32/libisc.def.in
-index 1455933..5b0835a 100644
---- a/lib/isc/win32/libisc.def.in
-+++ b/lib/isc/win32/libisc.def.in
-@@ -450,6 +450,7 @@ isc_netaddr_unspec
- isc_netscope_pton
- isc__nmhandle_attach
- isc__nmhandle_detach
-+isc_nmhandle_cleartimeout
- isc_nmhandle_getdata
- isc_nmhandle_getextra
- isc_nmhandle_is_stream
diff -Nru bind9-9.16.13/debian/patches/0006-Merge-the-common-parts-between-udp-tcpdns-and-tlsdns.patch bind9-9.16.15/debian/patches/0006-Merge-the-common-parts-between-udp-tcpdns-and-tlsdns.patch
--- bind9-9.16.13/debian/patches/0006-Merge-the-common-parts-between-udp-tcpdns-and-tlsdns.patch	2021-03-18 14:23:49.000000000 +0100
+++ bind9-9.16.15/debian/patches/0006-Merge-the-common-parts-between-udp-tcpdns-and-tlsdns.patch	1970-01-01 01:00:00.000000000 +0100
@@ -1,1705 +0,0 @@
-From: =?utf-8?b?T25kxZllaiBTdXLDvQ==?= <ondrej@sury.org>
-Date: Thu, 18 Mar 2021 09:27:38 +0100
-Subject: Merge the common parts between udp, tcpdns and tlsdns protocol
-
-The udp, tcpdns and tlsdns contained lot of cut&paste code or code that
-was very similar making the stack harder to maintain as any change to
-one would have to be copied to the the other protocols.
-
-In this commit, we merge the common parts into the common functions
-under isc__nm_<foo> namespace and just keep the little differences based
-on the socket type.
----
- lib/isc/netmgr/netmgr-int.h |  48 ++++++
- lib/isc/netmgr/netmgr.c     |  84 +++++++++-
- lib/isc/netmgr/tcpdns.c     | 319 ++++----------------------------------
- lib/isc/netmgr/tlsdns.c     | 369 +++++++-------------------------------------
- lib/isc/netmgr/udp.c        | 155 +++----------------
- 5 files changed, 238 insertions(+), 737 deletions(-)
-
-diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h
-index 8b26dfb..016f982 100644
---- a/lib/isc/netmgr/netmgr-int.h
-+++ b/lib/isc/netmgr/netmgr-int.h
-@@ -39,6 +39,8 @@
- 
- #define ISC_NETMGR_TID_UNKNOWN -1
- 
-+#define ISC_NETMGR_TLSBUF_SIZE 65536
-+
- #if !defined(WIN32)
- /*
-  * New versions of libuv support recvmmsg on unices.
-@@ -1567,3 +1569,49 @@ void
- isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
- void
- isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
-+
-+isc_result_t
-+isc__nm_tcpdns_processbuffer(isc_nmsocket_t *sock);
-+isc_result_t
-+isc__nm_tlsdns_processbuffer(isc_nmsocket_t *sock);
-+
-+isc__nm_uvreq_t *
-+isc__nm_get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr);
-+
-+void
-+isc__nm_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf);
-+
-+void
-+isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
-+		    const struct sockaddr *addr, unsigned flags);
-+void
-+isc__nm_tcpdns_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
-+void
-+isc__nm_tlsdns_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
-+
-+void
-+isc__nm_start_reading(isc_nmsocket_t *sock);
-+void
-+isc__nm_stop_reading(isc_nmsocket_t *sock);
-+void
-+isc__nm_process_sock_buffer(isc_nmsocket_t *sock);
-+void
-+isc__nm_resume_processing(void *arg);
-+bool
-+isc__nm_inactive(isc_nmsocket_t *sock);
-+
-+void
-+isc__nm_alloc_dnsbuf(isc_nmsocket_t *sock, size_t len);
-+
-+void
-+isc__nm_failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-+		       isc_result_t eresult);
-+void
-+isc__nm_failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult);
-+void
-+isc__nm_failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-+			  isc_result_t eresult);
-+void
-+isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
-+
-+#define STREAM_CLIENTS_PER_CONN 23
-diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c
-index 9d8efb2..e919a0d 100644
---- a/lib/isc/netmgr/netmgr.c
-+++ b/lib/isc/netmgr/netmgr.c
-@@ -1536,8 +1536,86 @@ isc_nmhandle_setdata(isc_nmhandle_t *handle, void *arg,
- 	handle->dofree = dofree;
- }
- 
--static void
--isc__nmsocket_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
-+void
-+isc__nm_alloc_dnsbuf(isc_nmsocket_t *sock, size_t len) {
-+	REQUIRE(len <= NM_BIG_BUF);
-+
-+	if (sock->buf == NULL) {
-+		/* We don't have the buffer at all */
-+		size_t alloc_len = len < NM_REG_BUF ? NM_REG_BUF : NM_BIG_BUF;
-+		sock->buf = isc_mem_allocate(sock->mgr->mctx, alloc_len);
-+		sock->buf_size = alloc_len;
-+	} else {
-+		/* We have the buffer but it's too small */
-+		sock->buf = isc_mem_reallocate(sock->mgr->mctx, sock->buf,
-+					       NM_BIG_BUF);
-+		sock->buf_size = NM_BIG_BUF;
-+	}
-+}
-+
-+void
-+isc__nm_failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-+		       isc_result_t eresult) {
-+	REQUIRE(VALID_NMSOCK(sock));
-+	REQUIRE(VALID_UVREQ(req));
-+
-+	if (req->cb.send != NULL) {
-+		isc__nm_sendcb(sock, req, eresult, true);
-+	} else {
-+		isc__nm_uvreq_put(&req, sock);
-+	}
-+}
-+
-+void
-+isc__nm_failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) {
-+	REQUIRE(sock->accepting);
-+	REQUIRE(sock->server);
-+
-+	/*
-+	 * Detach the quota early to make room for other connections;
-+	 * otherwise it'd be detached later asynchronously, and clog
-+	 * the quota unnecessarily.
-+	 */
-+	if (sock->quota != NULL) {
-+		isc_quota_detach(&sock->quota);
-+	}
-+
-+	isc__nmsocket_detach(&sock->server);
-+
-+	sock->accepting = false;
-+
-+	switch (eresult) {
-+	case ISC_R_NOTCONNECTED:
-+		/* IGNORE: The client disconnected before we could accept */
-+		break;
-+	default:
-+		isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
-+			      ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
-+			      "Accepting TCP connection failed: %s",
-+			      isc_result_totext(eresult));
-+	}
-+}
-+
-+void
-+isc__nm_failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-+			  isc_result_t eresult) {
-+	REQUIRE(VALID_NMSOCK(sock));
-+	REQUIRE(VALID_UVREQ(req));
-+	REQUIRE(sock->tid == isc_nm_tid());
-+	REQUIRE(atomic_load(&sock->connecting));
-+	REQUIRE(req->cb.connect != NULL);
-+
-+	atomic_store(&sock->connecting, false);
-+
-+	isc__nmsocket_clearcb(sock);
-+
-+	isc__nm_connectcb(sock, req, eresult);
-+
-+	isc__nmsocket_prep_destroy(sock);
-+}
-+
-+void
-+isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	switch (sock->type) {
- 	case isc_nm_udpsocket:
-@@ -1566,7 +1644,7 @@ isc__nmsocket_readtimeout_cb(uv_timer_t *timer) {
- 	REQUIRE(sock->tid == isc_nm_tid());
- 	REQUIRE(sock->reading);
- 
--	isc__nmsocket_failed_read_cb(sock, ISC_R_TIMEDOUT);
-+	isc__nm_failed_read_cb(sock, ISC_R_TIMEDOUT);
- }
- 
- void
-diff --git a/lib/isc/netmgr/tcpdns.c b/lib/isc/netmgr/tcpdns.c
-index 378f828..276e5e8 100644
---- a/lib/isc/netmgr/tcpdns.c
-+++ b/lib/isc/netmgr/tcpdns.c
-@@ -34,7 +34,6 @@
- #include "netmgr-int.h"
- #include "uv-compat.h"
- 
--#define TCPDNS_CLIENTS_PER_CONN 23
- /*%<
-  *
-  * Maximum number of simultaneous handles in flight supported for a single
-@@ -57,12 +56,6 @@ can_log_tcpdns_quota(void) {
- 	return (false);
- }
- 
--static void
--tcpdns_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf);
--
--static void
--resume_processing(void *arg);
--
- static isc_result_t
- tcpdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
- 
-@@ -75,9 +68,6 @@ tcpdns_connect_cb(uv_connect_t *uvreq, int status);
- static void
- tcpdns_connection_cb(uv_stream_t *server, int status);
- 
--static void
--read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
--
- static void
- tcpdns_close_cb(uv_handle_t *uvhandle);
- 
-@@ -87,98 +77,11 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota);
- static void
- quota_accept_cb(isc_quota_t *quota, void *sock0);
- 
--static void
--failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult);
--
--static void
--failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
--	       isc_result_t eresult);
--
- static void
- stop_tcpdns_parent(isc_nmsocket_t *sock);
- static void
- stop_tcpdns_child(isc_nmsocket_t *sock);
- 
--static void
--process_sock_buffer(isc_nmsocket_t *sock);
--
--static void
--stop_reading(isc_nmsocket_t *sock);
--
--static isc__nm_uvreq_t *
--get_read_req(isc_nmsocket_t *sock);
--
--static inline void
--alloc_dnsbuf(isc_nmsocket_t *sock, size_t len) {
--	REQUIRE(len <= NM_BIG_BUF);
--
--	if (sock->buf == NULL) {
--		/* We don't have the buffer at all */
--		size_t alloc_len = len < NM_REG_BUF ? NM_REG_BUF : NM_BIG_BUF;
--		sock->buf = isc_mem_allocate(sock->mgr->mctx, alloc_len);
--		sock->buf_size = alloc_len;
--	} else {
--		/* We have the buffer but it's too small */
--		sock->buf = isc_mem_reallocate(sock->mgr->mctx, sock->buf,
--					       NM_BIG_BUF);
--		sock->buf_size = NM_BIG_BUF;
--	}
--}
--
--static bool
--inactive(isc_nmsocket_t *sock) {
--	return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
--		atomic_load(&sock->mgr->closing) ||
--		(sock->server != NULL && !isc__nmsocket_active(sock->server)));
--}
--
--static void
--failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) {
--	REQUIRE(sock->accepting);
--	REQUIRE(sock->server);
--
--	/*
--	 * Detach the quota early to make room for other connections;
--	 * otherwise it'd be detached later asynchronously, and clog
--	 * the quota unnecessarily.
--	 */
--	if (sock->quota != NULL) {
--		isc_quota_detach(&sock->quota);
--	}
--
--	isc__nmsocket_detach(&sock->server);
--
--	sock->accepting = false;
--
--	switch (eresult) {
--	case ISC_R_NOTCONNECTED:
--		/* IGNORE: The client disconnected before we could accept */
--		break;
--	default:
--		isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
--			      ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
--			      "Accepting TCP connection failed: %s",
--			      isc_result_totext(eresult));
--	}
--}
--
--static void
--failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
--		  isc_result_t eresult) {
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(VALID_UVREQ(req));
--	REQUIRE(sock->tid == isc_nm_tid());
--	REQUIRE(atomic_load(&sock->connecting));
--	REQUIRE(req->cb.connect != NULL);
--
--	atomic_store(&sock->connecting, false);
--
--	isc__nmsocket_clearcb(sock);
--	isc__nm_connectcb(sock, req, eresult);
--
--	isc__nmsocket_prep_destroy(sock);
--}
--
- static isc_result_t
- tcpdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
- 	isc__networker_t *worker = NULL;
-@@ -326,7 +229,7 @@ tcpdns_connect_cb(uv_connect_t *uvreq, int status) {
- 	return;
- 
- error:
--	failed_connect_cb(sock, req, result);
-+	isc__nm_failed_connect_cb(sock, req, result);
- }
- 
- isc_result_t
-@@ -643,7 +546,7 @@ tcpdns_connection_cb(uv_stream_t *server, int status) {
- 	REQUIRE(VALID_NMSOCK(ssock));
- 	REQUIRE(ssock->tid == isc_nm_tid());
- 
--	if (inactive(ssock)) {
-+	if (isc__nm_inactive(ssock)) {
- 		result = ISC_R_CANCELED;
- 		goto done;
- 	}
-@@ -720,13 +623,13 @@ isc__nm_async_tcpdnsstop(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	}
- }
- 
--static void
--failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
-+void
-+isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(result != ISC_R_SUCCESS);
- 
- 	isc__nmsocket_timer_stop(sock);
--	stop_reading(sock);
-+	isc__nm_stop_reading(sock);
- 
- 	if (!sock->recv_read) {
- 		goto destroy;
-@@ -734,7 +637,7 @@ failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	sock->recv_read = false;
- 
- 	if (sock->recv_cb != NULL) {
--		isc__nm_uvreq_t *req = get_read_req(sock);
-+		isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
- 		isc__nmsocket_clearcb(sock);
- 		isc__nm_readcb(sock, req, result);
- 	}
-@@ -749,67 +652,6 @@ destroy:
- 	}
- }
- 
--void
--isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
--	failed_read_cb(sock, result);
--}
--
--static void
--failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
--	       isc_result_t eresult) {
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(VALID_UVREQ(req));
--
--	if (req->cb.send != NULL) {
--		isc__nm_sendcb(sock, req, eresult, true);
--	} else {
--		isc__nm_uvreq_put(&req, sock);
--	}
--}
--
--static isc__nm_uvreq_t *
--get_read_req(isc_nmsocket_t *sock) {
--	isc__nm_uvreq_t *req = NULL;
--
--	req = isc__nm_uvreq_get(sock->mgr, sock);
--	req->cb.recv = sock->recv_cb;
--	req->cbarg = sock->recv_cbarg;
--
--	if (atomic_load(&sock->client)) {
--		isc_nmhandle_attach(sock->statichandle, &req->handle);
--	} else {
--		req->handle = isc__nmhandle_get(sock, NULL, NULL);
--	}
--
--	return (req);
--}
--
--static void
--start_reading(isc_nmsocket_t *sock) {
--	int r;
--
--	if (sock->reading) {
--		return;
--	}
--
--	r = uv_read_start(&sock->uv_handle.stream, tcpdns_alloc_cb, read_cb);
--	RUNTIME_CHECK(r == 0);
--	sock->reading = true;
--}
--
--static void
--stop_reading(isc_nmsocket_t *sock) {
--	int r;
--
--	if (!sock->reading) {
--		return;
--	}
--
--	r = uv_read_stop(&sock->uv_handle.stream);
--	RUNTIME_CHECK(r == 0);
--	sock->reading = false;
--}
--
- void
- isc__nm_tcpdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
- 	REQUIRE(VALID_NMHANDLE(handle));
-@@ -847,31 +689,6 @@ isc__nm_tcpdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
- 	return;
- }
- 
--/*%<
-- * Allocator for TCP read operations. Limited to size 2^16.
-- *
-- * Note this doesn't actually allocate anything, it just assigns the
-- * worker's receive buffer to a socket, and marks it as "in use".
-- */
--static void
--tcpdns_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
--	isc_nmsocket_t *sock = uv_handle_get_data(handle);
--	isc__networker_t *worker = NULL;
--
--	UNUSED(size);
--
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(sock->type == isc_nm_tcpdnssocket);
--	REQUIRE(isc__nm_in_netthread());
--
--	worker = &sock->mgr->workers[sock->tid];
--	INSIST(!worker->recvbuf_inuse);
--
--	buf->base = worker->recvbuf;
--	buf->len = size;
--	worker->recvbuf_inuse = true;
--}
--
- void
- isc__nm_async_tcpdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	isc__netievent_tcpdnsread_t *ievent =
-@@ -883,13 +700,13 @@ isc__nm_async_tcpdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(sock->tid == isc_nm_tid());
- 
--	if (inactive(sock)) {
-+	if (isc__nm_inactive(sock)) {
- 		sock->reading = true;
--		failed_read_cb(sock, ISC_R_CANCELED);
-+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
- 		return;
- 	}
- 
--	process_sock_buffer(sock);
-+	isc__nm_process_sock_buffer(sock);
- }
- 
- /*
-@@ -901,8 +718,8 @@ isc__nm_async_tcpdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
-  *
-  * The caller will need to unreference the handle.
-  */
--static isc_result_t
--processbuffer(isc_nmsocket_t *sock) {
-+isc_result_t
-+isc__nm_tcpdns_processbuffer(isc_nmsocket_t *sock) {
- 	size_t len;
- 	isc__nm_uvreq_t *req = NULL;
- 	isc_nmhandle_t *handle = NULL;
-@@ -910,7 +727,7 @@ processbuffer(isc_nmsocket_t *sock) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(sock->tid == isc_nm_tid());
- 
--	if (inactive(sock)) {
-+	if (isc__nm_inactive(sock)) {
- 		return (ISC_R_CANCELED);
- 	}
- 
-@@ -931,7 +748,7 @@ processbuffer(isc_nmsocket_t *sock) {
- 		return (ISC_R_NOMORE);
- 	}
- 
--	req = get_read_req(sock);
-+	req = isc__nm_get_read_req(sock, NULL);
- 	REQUIRE(VALID_UVREQ(req));
- 
- 	/*
-@@ -975,8 +792,9 @@ processbuffer(isc_nmsocket_t *sock) {
- 	return (ISC_R_SUCCESS);
- }
- 
--static void
--read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
-+void
-+isc__nm_tcpdns_read_cb(uv_stream_t *stream, ssize_t nread,
-+		       const uv_buf_t *buf) {
- 	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)stream);
- 	uint8_t *base = NULL;
- 	size_t len;
-@@ -986,8 +804,8 @@ read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
- 	REQUIRE(sock->reading);
- 	REQUIRE(buf != NULL);
- 
--	if (inactive(sock)) {
--		failed_read_cb(sock, ISC_R_CANCELED);
-+	if (isc__nm_inactive(sock)) {
-+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
- 		goto free;
- 	}
- 
-@@ -997,8 +815,7 @@ read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
- 					 sock->statsindex[STATID_RECVFAIL]);
- 		}
- 
--		failed_read_cb(sock, isc__nm_uverr2result(nread));
--
-+		isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nread));
- 		goto free;
- 	}
- 
-@@ -1015,7 +832,7 @@ read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
- 	 */
- 
- 	if (sock->buf_len + len > sock->buf_size) {
--		alloc_dnsbuf(sock, sock->buf_len + len);
-+		isc__nm_alloc_dnsbuf(sock, sock->buf_len + len);
- 	}
- 	memmove(sock->buf + sock->buf_len, base, len);
- 	sock->buf_len += len;
-@@ -1024,7 +841,7 @@ read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
- 		sock->read_timeout = atomic_load(&sock->mgr->idle);
- 	}
- 
--	process_sock_buffer(sock);
-+	isc__nm_process_sock_buffer(sock);
- free:
- 	isc__nm_free_uvbuf(sock, buf);
- }
-@@ -1086,7 +903,7 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- 	REQUIRE(VALID_NMSOCK(ssock));
- 	REQUIRE(ssock->tid == isc_nm_tid());
- 
--	if (inactive(ssock)) {
-+	if (isc__nm_inactive(ssock)) {
- 		if (quota != NULL) {
- 			isc_quota_detach(&quota);
- 		}
-@@ -1168,7 +985,7 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- 
- 	csock->read_timeout = atomic_load(&csock->mgr->init);
- 
--	csock->closehandle_cb = resume_processing;
-+	csock->closehandle_cb = isc__nm_resume_processing;
- 
- 	/*
- 	 * We need to keep the handle alive until we fail to read or connection
-@@ -1176,7 +993,7 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- 	 * prep_destroy()->tcpdns_close_direct().
- 	 */
- 	isc_nmhandle_attach(handle, &csock->recv_handle);
--	process_sock_buffer(csock);
-+	isc__nm_process_sock_buffer(csock);
- 
- 	/*
- 	 * The initial timer has been set, update the read timeout for the next
-@@ -1199,7 +1016,7 @@ failure:
- 
- 	atomic_store(&csock->active, false);
- 
--	failed_accept_cb(csock, result);
-+	isc__nm_failed_accept_cb(csock, result);
- 
- 	isc__nmsocket_prep_destroy(csock);
- 
-@@ -1247,7 +1064,8 @@ tcpdns_send_cb(uv_write_t *req, int status) {
- 
- 	if (status < 0) {
- 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
--		failed_send_cb(sock, uvreq, isc__nm_uverr2result(status));
-+		isc__nm_failed_send_cb(sock, uvreq,
-+				       isc__nm_uverr2result(status));
- 		return;
- 	}
- 
-@@ -1279,7 +1097,7 @@ isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0) {
- 
- 	UNUSED(worker);
- 
--	if (inactive(sock)) {
-+	if (isc__nm_inactive(sock)) {
- 		result = ISC_R_CANCELED;
- 		goto fail;
- 	}
-@@ -1321,7 +1139,7 @@ isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0) {
- fail:
- 	if (result != ISC_R_SUCCESS) {
- 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
--		failed_send_cb(sock, uvreq, result);
-+		isc__nm_failed_send_cb(sock, uvreq, result);
- 	}
- }
- 
-@@ -1452,7 +1270,7 @@ tcpdns_close_direct(isc_nmsocket_t *sock) {
- 	}
- 
- 	isc__nmsocket_timer_stop(sock);
--	stop_reading(sock);
-+	isc__nm_stop_reading(sock);
- 	uv_close((uv_handle_t *)&sock->timer, timer_close_cb);
- }
- 
-@@ -1514,7 +1332,7 @@ isc__nm_tcpdns_shutdown(isc_nmsocket_t *sock) {
- 	}
- 
- 	if (sock->statichandle) {
--		failed_read_cb(sock, ISC_R_CANCELED);
-+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
- 		return;
- 	}
- 
-@@ -1554,7 +1372,7 @@ isc__nm_async_tcpdnscancel(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(sock->tid == isc_nm_tid());
- 
--	failed_read_cb(sock, ISC_R_EOF);
-+	isc__nm_failed_read_cb(sock, ISC_R_EOF);
- }
- 
- void
-@@ -1577,7 +1395,7 @@ isc_nm_tcpdns_sequential(isc_nmhandle_t *handle) {
- 	 */
- 
- 	isc__nmsocket_timer_stop(sock);
--	stop_reading(sock);
-+	isc__nm_stop_reading(sock);
- 	atomic_store(&sock->sequential, true);
- }
- 
-@@ -1593,74 +1411,3 @@ isc_nm_tcpdns_keepalive(isc_nmhandle_t *handle, bool value) {
- 
- 	atomic_store(&sock->keepalive, value);
- }
--
--/*
-- * Process a DNS message.
-- *
-- * If we only have an incomplete DNS message, we don't touch any
-- * timers. If we do have a full message, reset the timer.
-- *
-- * Stop reading if this is a client socket, or if the server socket
-- * has been set to sequential mode, or the number of queries we are
-- * processing simultaneously has reached the clients-per-connection
-- * limit. In this event we'll be called again by resume_processing()
-- * later.
-- */
--static void
--process_sock_buffer(isc_nmsocket_t *sock) {
--	for (;;) {
--		int_fast32_t ah = atomic_load(&sock->ah);
--		isc_result_t result = processbuffer(sock);
--		switch (result) {
--		case ISC_R_NOMORE:
--			/*
--			 * Don't reset the timer until we have a
--			 * full DNS message.
--			 */
--			start_reading(sock);
--			/* Start the timer if there are no active handles */
--			if (ah == 1) {
--				isc__nmsocket_timer_start(sock);
--			}
--			return;
--		case ISC_R_CANCELED:
--			isc__nmsocket_timer_stop(sock);
--			stop_reading(sock);
--			return;
--		case ISC_R_SUCCESS:
--			/*
--			 * Stop the timer on the successful message read, this
--			 * also allows to restart the timer when we have no more
--			 * data.
--			 */
--			isc__nmsocket_timer_stop(sock);
--
--			if (atomic_load(&sock->client) ||
--			    atomic_load(&sock->sequential) ||
--			    atomic_load(&sock->ah) >= TCPDNS_CLIENTS_PER_CONN)
--			{
--				stop_reading(sock);
--				return;
--			}
--			break;
--		default:
--			INSIST(0);
--		}
--	}
--}
--
--static void
--resume_processing(void *arg) {
--	isc_nmsocket_t *sock = (isc_nmsocket_t *)arg;
--
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(sock->tid == isc_nm_tid());
--	REQUIRE(sock->type == isc_nm_tcpdnssocket);
--	REQUIRE(!atomic_load(&sock->client));
--
--	if (inactive(sock)) {
--		return;
--	}
--
--	process_sock_buffer(sock);
--}
-diff --git a/lib/isc/netmgr/tlsdns.c b/lib/isc/netmgr/tlsdns.c
-index a7ce21e..8d8e000 100644
---- a/lib/isc/netmgr/tlsdns.c
-+++ b/lib/isc/netmgr/tlsdns.c
-@@ -35,9 +35,6 @@
- #include "openssl_shim.h"
- #include "uv-compat.h"
- 
--#define TLS_BUF_SIZE 65536
--
--#define TLSDNS_CLIENTS_PER_CONN 23
- /*%<
-  *
-  * Maximum number of simultaneous handles in flight supported for a single
-@@ -50,12 +47,6 @@ static atomic_uint_fast32_t last_tlsdnsquota_log = ATOMIC_VAR_INIT(0);
- static void
- tls_error(isc_nmsocket_t *sock, isc_result_t result);
- 
--static void
--tlsdns_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf);
--
--static void
--resume_processing(void *arg);
--
- static isc_result_t
- tlsdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
- 
-@@ -70,9 +61,6 @@ tlsdns_connect_cb(uv_connect_t *uvreq, int status);
- static void
- tlsdns_connection_cb(uv_stream_t *server, int status);
- 
--static void
--read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
--
- static void
- tlsdns_close_cb(uv_handle_t *uvhandle);
- 
-@@ -82,30 +70,11 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota);
- static void
- quota_accept_cb(isc_quota_t *quota, void *sock0);
- 
--static void
--failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult);
--
--static void
--failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
--	       isc_result_t eresult);
--
- static void
- stop_tlsdns_parent(isc_nmsocket_t *sock);
- static void
- stop_tlsdns_child(isc_nmsocket_t *sock);
- 
--static void
--start_reading(isc_nmsocket_t *sock);
--
--static void
--stop_reading(isc_nmsocket_t *sock);
--
--static void
--process_sock_buffer(isc_nmsocket_t *sock);
--
--static isc__nm_uvreq_t *
--get_read_req(isc_nmsocket_t *sock);
--
- static void
- async_tlsdns_cycle(isc_nmsocket_t *sock) __attribute__((unused));
- 
-@@ -125,78 +94,6 @@ can_log_tlsdns_quota(void) {
- 	return (false);
- }
- 
--static inline void
--alloc_dnsbuf(isc_nmsocket_t *sock, size_t len) {
--	REQUIRE(len <= NM_BIG_BUF);
--
--	if (sock->buf == NULL) {
--		/* We don't have the buffer at all */
--		size_t alloc_len = len < NM_REG_BUF ? NM_REG_BUF : NM_BIG_BUF;
--		sock->buf = isc_mem_allocate(sock->mgr->mctx, alloc_len);
--		sock->buf_size = alloc_len;
--	} else {
--		/* We have the buffer but it's too small */
--		sock->buf = isc_mem_reallocate(sock->mgr->mctx, sock->buf,
--					       NM_BIG_BUF);
--		sock->buf_size = NM_BIG_BUF;
--	}
--}
--
--static bool
--inactive(isc_nmsocket_t *sock) {
--	return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
--		atomic_load(&sock->mgr->closing) ||
--		(sock->server != NULL && !isc__nmsocket_active(sock->server)));
--}
--
--static void
--failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) {
--	REQUIRE(sock->accepting);
--	REQUIRE(sock->server);
--
--	/*
--	 * Detach the quota early to make room for other connections;
--	 * otherwise it'd be detached later asynchronously, and clog
--	 * the quota unnecessarily.
--	 */
--	if (sock->quota != NULL) {
--		isc_quota_detach(&sock->quota);
--	}
--
--	isc__nmsocket_detach(&sock->server);
--
--	sock->accepting = false;
--
--	switch (eresult) {
--	case ISC_R_NOTCONNECTED:
--		/* IGNORE: The client disconnected before we could accept */
--		break;
--	default:
--		isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
--			      ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
--			      "Accepting TCP connection failed: %s",
--			      isc_result_totext(eresult));
--	}
--}
--
--static void
--failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
--		  isc_result_t eresult) {
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(VALID_UVREQ(req));
--	REQUIRE(sock->tid == isc_nm_tid());
--	REQUIRE(atomic_load(&sock->connecting));
--	REQUIRE(req->cb.connect != NULL);
--
--	atomic_store(&sock->connecting, false);
--
--	isc__nmsocket_clearcb(sock);
--
--	isc__nm_connectcb(sock, req, eresult);
--
--	isc__nmsocket_prep_destroy(sock);
--}
--
- static isc_result_t
- tlsdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
- 	isc__networker_t *worker = NULL;
-@@ -341,12 +238,12 @@ tlsdns_connect_cb(uv_connect_t *uvreq, int status) {
- 	/*
- 	 *
- 	 */
--	r = BIO_new_bio_pair(&sock->tls.ssl_wbio, TLS_BUF_SIZE,
--			     &sock->tls.app_rbio, TLS_BUF_SIZE);
-+	r = BIO_new_bio_pair(&sock->tls.ssl_wbio, ISC_NETMGR_TLSBUF_SIZE,
-+			     &sock->tls.app_rbio, ISC_NETMGR_TLSBUF_SIZE);
- 	RUNTIME_CHECK(r == 1);
- 
--	r = BIO_new_bio_pair(&sock->tls.ssl_rbio, TLS_BUF_SIZE,
--			     &sock->tls.app_wbio, TLS_BUF_SIZE);
-+	r = BIO_new_bio_pair(&sock->tls.ssl_rbio, ISC_NETMGR_TLSBUF_SIZE,
-+			     &sock->tls.app_wbio, ISC_NETMGR_TLSBUF_SIZE);
- 	RUNTIME_CHECK(r == 1);
- 
- #if HAVE_SSL_SET0_RBIO && HAVE_SSL_SET0_WBIO
-@@ -369,7 +266,7 @@ tlsdns_connect_cb(uv_connect_t *uvreq, int status) {
- 
- 	sock->tls.pending_req = req;
- 
--	process_sock_buffer(sock);
-+	isc__nm_process_sock_buffer(sock);
- 
- 	result = tls_cycle(sock);
- 	if (result != ISC_R_SUCCESS) {
-@@ -379,7 +276,7 @@ tlsdns_connect_cb(uv_connect_t *uvreq, int status) {
- 	return;
- 
- error:
--	failed_connect_cb(sock, req, result);
-+	isc__nm_failed_connect_cb(sock, req, result);
- }
- 
- isc_result_t
-@@ -702,7 +599,7 @@ tlsdns_connection_cb(uv_stream_t *server, int status) {
- 	REQUIRE(VALID_NMSOCK(ssock));
- 	REQUIRE(ssock->tid == isc_nm_tid());
- 
--	if (inactive(ssock)) {
-+	if (isc__nm_inactive(ssock)) {
- 		result = ISC_R_CANCELED;
- 		goto done;
- 	}
-@@ -854,18 +751,18 @@ isc__nm_async_tlsdnsstop(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	}
- }
- 
--static void
--failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
-+void
-+isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(result != ISC_R_SUCCESS);
- 
- 	isc__nmsocket_timer_stop(sock);
--	stop_reading(sock);
-+	isc__nm_stop_reading(sock);
- 
- 	if (sock->tls.pending_req) {
- 		isc__nm_uvreq_t *req = sock->tls.pending_req;
- 		sock->tls.pending_req = NULL;
--		failed_connect_cb(sock, req, ISC_R_CANCELED);
-+		isc__nm_failed_connect_cb(sock, req, ISC_R_CANCELED);
- 	}
- 
- 	if (!sock->recv_read) {
-@@ -874,7 +771,7 @@ failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	sock->recv_read = false;
- 
- 	if (sock->recv_cb != NULL) {
--		isc__nm_uvreq_t *req = get_read_req(sock);
-+		isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
- 		isc__nmsocket_clearcb(sock);
- 		isc__nm_readcb(sock, req, result);
- 	}
-@@ -889,67 +786,6 @@ destroy:
- 	}
- }
- 
--void
--isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
--	failed_read_cb(sock, result);
--}
--
--static void
--failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
--	       isc_result_t eresult) {
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(VALID_UVREQ(req));
--
--	if (req->cb.send != NULL) {
--		isc__nm_sendcb(sock, req, eresult, true);
--	} else {
--		isc__nm_uvreq_put(&req, sock);
--	}
--}
--
--static isc__nm_uvreq_t *
--get_read_req(isc_nmsocket_t *sock) {
--	isc__nm_uvreq_t *req = NULL;
--
--	req = isc__nm_uvreq_get(sock->mgr, sock);
--	req->cb.recv = sock->recv_cb;
--	req->cbarg = sock->recv_cbarg;
--
--	if (atomic_load(&sock->client)) {
--		isc_nmhandle_attach(sock->statichandle, &req->handle);
--	} else {
--		req->handle = isc__nmhandle_get(sock, NULL, NULL);
--	}
--
--	return (req);
--}
--
--static void
--start_reading(isc_nmsocket_t *sock) {
--	int r;
--
--	if (sock->reading) {
--		return;
--	}
--
--	r = uv_read_start(&sock->uv_handle.stream, tlsdns_alloc_cb, read_cb);
--	RUNTIME_CHECK(r == 0);
--	sock->reading = true;
--}
--
--static void
--stop_reading(isc_nmsocket_t *sock) {
--	int r;
--
--	if (!sock->reading) {
--		return;
--	}
--
--	r = uv_read_stop(&sock->uv_handle.stream);
--	RUNTIME_CHECK(r == 0);
--	sock->reading = false;
--}
--
- void
- isc__nm_tlsdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
- 	REQUIRE(VALID_NMHANDLE(handle));
-@@ -987,38 +823,6 @@ isc__nm_tlsdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
- 	return;
- }
- 
--/*%<
-- * Allocator for TCP read operations. Limited to size 2^16.
-- *
-- * Note this doesn't actually allocate anything, it just assigns the
-- * worker's receive buffer to a socket, and marks it as "in use".
-- */
--static void
--tlsdns_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
--	isc_nmsocket_t *sock = uv_handle_get_data(handle);
--	isc__networker_t *worker = NULL;
--
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(sock->type == isc_nm_tlsdnssocket);
--	REQUIRE(isc__nm_in_netthread());
--
--	/*
--	 * We need to limit the individual chunks to be read, so the BIO_write()
--	 * will always succeed and the consumed before the next readcb is
--	 * called.
--	 */
--	if (size >= TLS_BUF_SIZE) {
--		size = TLS_BUF_SIZE;
--	}
--
--	worker = &sock->mgr->workers[sock->tid];
--	INSIST(!worker->recvbuf_inuse);
--
--	buf->base = worker->recvbuf;
--	buf->len = size;
--	worker->recvbuf_inuse = true;
--}
--
- void
- isc__nm_async_tlsdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	isc__netievent_tlsdnsread_t *ievent =
-@@ -1031,15 +835,15 @@ isc__nm_async_tlsdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(sock->tid == isc_nm_tid());
- 
--	if (inactive(sock)) {
-+	if (isc__nm_inactive(sock)) {
- 		sock->reading = true;
--		failed_read_cb(sock, ISC_R_CANCELED);
-+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
- 		return;
- 	}
- 
- 	result = tls_cycle(sock);
- 	if (result != ISC_R_SUCCESS) {
--		failed_read_cb(sock, result);
-+		isc__nm_failed_read_cb(sock, result);
- 	}
- }
- 
-@@ -1052,8 +856,8 @@ isc__nm_async_tlsdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
-  *
-  * The caller will need to unreference the handle.
-  */
--static isc_result_t
--processbuffer(isc_nmsocket_t *sock) {
-+isc_result_t
-+isc__nm_tlsdns_processbuffer(isc_nmsocket_t *sock) {
- 	size_t len;
- 	isc__nm_uvreq_t *req = NULL;
- 	isc_nmhandle_t *handle = NULL;
-@@ -1061,7 +865,7 @@ processbuffer(isc_nmsocket_t *sock) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(sock->tid == isc_nm_tid());
- 
--	if (inactive(sock)) {
-+	if (isc__nm_inactive(sock)) {
- 		return (ISC_R_CANCELED);
- 	}
- 
-@@ -1082,7 +886,7 @@ processbuffer(isc_nmsocket_t *sock) {
- 		return (ISC_R_NOMORE);
- 	}
- 
--	req = get_read_req(sock);
-+	req = isc__nm_get_read_req(sock, NULL);
- 	REQUIRE(VALID_UVREQ(req));
- 
- 	/*
-@@ -1139,12 +943,13 @@ tls_cycle_input(isc_nmsocket_t *sock) {
- 			(void)SSL_peek(sock->tls.ssl, &(char){ '\0' }, 0);
- 
- 			int pending = SSL_pending(sock->tls.ssl);
--			if (pending > TLS_BUF_SIZE) {
--				pending = TLS_BUF_SIZE;
-+			if (pending > ISC_NETMGR_TLSBUF_SIZE) {
-+				pending = ISC_NETMGR_TLSBUF_SIZE;
- 			}
- 
- 			if ((sock->buf_len + pending) > sock->buf_size) {
--				alloc_dnsbuf(sock, sock->buf_len + pending);
-+				isc__nm_alloc_dnsbuf(sock,
-+						     sock->buf_len + pending);
- 			}
- 
- 			len = 0;
-@@ -1153,7 +958,7 @@ tls_cycle_input(isc_nmsocket_t *sock) {
- 					 sock->buf_size - sock->buf_len, &len);
- 			if (rv != 1) {
- 				/* Process what's in the buffer so far */
--				process_sock_buffer(sock);
-+				isc__nm_process_sock_buffer(sock);
- 
- 				/* FIXME: Should we call failed_read_cb()? */
- 				break;
-@@ -1163,7 +968,7 @@ tls_cycle_input(isc_nmsocket_t *sock) {
- 
- 			sock->buf_len += len;
- 
--			process_sock_buffer(sock);
-+			isc__nm_process_sock_buffer(sock);
- 		}
- 	} else if (!SSL_is_init_finished(sock->tls.ssl)) {
- 		if (SSL_is_server(sock->tls.ssl)) {
-@@ -1185,7 +990,7 @@ tls_cycle_input(isc_nmsocket_t *sock) {
- 		if (sock->tls.state == TLS_STATE_NONE &&
- 		    !SSL_is_init_finished(sock->tls.ssl)) {
- 			sock->tls.state = TLS_STATE_HANDSHAKE;
--			process_sock_buffer(sock);
-+			isc__nm_process_sock_buffer(sock);
- 		}
- 		/* else continue reading */
- 		break;
-@@ -1240,7 +1045,7 @@ tls_error(isc_nmsocket_t *sock, isc_result_t result) {
- 	case TLS_STATE_HANDSHAKE:
- 	case TLS_STATE_IO:
- 		isc__nmsocket_timer_stop(sock);
--		stop_reading(sock);
-+		isc__nm_stop_reading(sock);
- 		break;
- 	case TLS_STATE_ERROR:
- 		return;
-@@ -1304,8 +1109,8 @@ tls_cycle_output(isc_nmsocket_t *sock) {
- 			break;
- 		}
- 
--		if (pending > TLS_BUF_SIZE) {
--			pending = TLS_BUF_SIZE;
-+		if (pending > ISC_NETMGR_TLSBUF_SIZE) {
-+			pending = ISC_NETMGR_TLSBUF_SIZE;
- 		}
- 
- 		sock->tls.senddata.base = isc_mem_get(sock->mgr->mctx, pending);
-@@ -1436,8 +1241,9 @@ isc__nm_async_tlsdnscycle(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	}
- }
- 
--static void
--read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
-+void
-+isc__nm_tlsdns_read_cb(uv_stream_t *stream, ssize_t nread,
-+		       const uv_buf_t *buf) {
- 	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)stream);
- 	size_t len;
- 	isc_result_t result;
-@@ -1448,8 +1254,8 @@ read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
- 	REQUIRE(sock->reading);
- 	REQUIRE(buf != NULL);
- 
--	if (inactive(sock)) {
--		failed_read_cb(sock, ISC_R_CANCELED);
-+	if (isc__nm_inactive(sock)) {
-+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
- 		goto free;
- 	}
- 
-@@ -1459,7 +1265,7 @@ read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
- 					 sock->statsindex[STATID_RECVFAIL]);
- 		}
- 
--		failed_read_cb(sock, isc__nm_uverr2result(nread));
-+		isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nread));
- 
- 		goto free;
- 	}
-@@ -1474,13 +1280,13 @@ read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
- 	rv = BIO_write_ex(sock->tls.app_wbio, buf->base, (size_t)nread, &len);
- 
- 	if (rv <= 0 || (size_t)nread != len) {
--		failed_read_cb(sock, ISC_R_TLSERROR);
-+		isc__nm_failed_read_cb(sock, ISC_R_TLSERROR);
- 		goto free;
- 	}
- 
- 	result = tls_cycle(sock);
- 	if (result != ISC_R_SUCCESS) {
--		failed_read_cb(sock, result);
-+		isc__nm_failed_read_cb(sock, result);
- 	}
- free:
- 	async_tlsdns_cycle(sock);
-@@ -1544,7 +1350,7 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- 	REQUIRE(VALID_NMSOCK(ssock));
- 	REQUIRE(ssock->tid == isc_nm_tid());
- 
--	if (inactive(ssock)) {
-+	if (isc__nm_inactive(ssock)) {
- 		if (quota != NULL) {
- 			isc_quota_detach(&quota);
- 		}
-@@ -1637,12 +1443,12 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- 
- 	RUNTIME_CHECK(csock->tls.ssl != NULL);
- 
--	r = BIO_new_bio_pair(&csock->tls.ssl_wbio, TLS_BUF_SIZE,
--			     &csock->tls.app_rbio, TLS_BUF_SIZE);
-+	r = BIO_new_bio_pair(&csock->tls.ssl_wbio, ISC_NETMGR_TLSBUF_SIZE,
-+			     &csock->tls.app_rbio, ISC_NETMGR_TLSBUF_SIZE);
- 	RUNTIME_CHECK(r == 1);
- 
--	r = BIO_new_bio_pair(&csock->tls.ssl_rbio, TLS_BUF_SIZE,
--			     &csock->tls.app_wbio, TLS_BUF_SIZE);
-+	r = BIO_new_bio_pair(&csock->tls.ssl_rbio, ISC_NETMGR_TLSBUF_SIZE,
-+			     &csock->tls.app_wbio, ISC_NETMGR_TLSBUF_SIZE);
- 	RUNTIME_CHECK(r == 1);
- 
- #if HAVE_SSL_SET0_RBIO && HAVE_SSL_SET0_WBIO
-@@ -1668,7 +1474,7 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- 
- 	csock->read_timeout = atomic_load(&csock->mgr->init);
- 
--	csock->closehandle_cb = resume_processing;
-+	csock->closehandle_cb = isc__nm_resume_processing;
- 
- 	/*
- 	 * We need to keep the handle alive until we fail to read or connection
-@@ -1687,7 +1493,7 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- 
- 	isc_nmhandle_detach(&handle);
- 
--	process_sock_buffer(csock);
-+	isc__nm_process_sock_buffer(csock);
- 
- 	/*
- 	 * sock is now attached to the handle.
-@@ -1699,7 +1505,7 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
- failure:
- 	atomic_store(&csock->active, false);
- 
--	failed_accept_cb(csock, result);
-+	isc__nm_failed_accept_cb(csock, result);
- 
- 	isc__nmsocket_prep_destroy(csock);
- 
-@@ -1755,7 +1561,7 @@ isc__nm_async_tlsdnssend(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	result = tlsdns_send_direct(sock, uvreq);
- 	if (result != ISC_R_SUCCESS) {
- 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
--		failed_send_cb(sock, uvreq, result);
-+		isc__nm_failed_send_cb(sock, uvreq, result);
- 	}
- }
- 
-@@ -1786,7 +1592,7 @@ tlsdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
- 		return (result);
- 	}
- 
--	if (inactive(sock)) {
-+	if (isc__nm_inactive(sock)) {
- 		return (ISC_R_CANCELED);
- 	}
- 
-@@ -1981,7 +1787,7 @@ tlsdns_close_direct(isc_nmsocket_t *sock) {
- 	}
- 
- 	isc__nmsocket_timer_stop(sock);
--	stop_reading(sock);
-+	isc__nm_stop_reading(sock);
- 	uv_close((uv_handle_t *)&sock->timer, timer_close_cb);
- }
- 
-@@ -2045,11 +1851,11 @@ isc__nm_tlsdns_shutdown(isc_nmsocket_t *sock) {
- 	if (sock->tls.pending_req) {
- 		isc__nm_uvreq_t *req = sock->tls.pending_req;
- 		sock->tls.pending_req = NULL;
--		failed_connect_cb(sock, req, ISC_R_CANCELED);
-+		isc__nm_failed_connect_cb(sock, req, ISC_R_CANCELED);
- 	}
- 
- 	if (sock->statichandle) {
--		failed_read_cb(sock, ISC_R_CANCELED);
-+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
- 		return;
- 	}
- 
-@@ -2089,7 +1895,7 @@ isc__nm_async_tlsdnscancel(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(sock->tid == isc_nm_tid());
- 
--	failed_read_cb(sock, ISC_R_EOF);
-+	isc__nm_failed_read_cb(sock, ISC_R_EOF);
- }
- 
- void
-@@ -2112,7 +1918,7 @@ isc_nm_tlsdns_sequential(isc_nmhandle_t *handle) {
- 	 */
- 
- 	isc__nmsocket_timer_stop(sock);
--	stop_reading(sock);
-+	isc__nm_stop_reading(sock);
- 	atomic_store(&sock->sequential, true);
- }
- 
-@@ -2128,74 +1934,3 @@ isc_nm_tlsdns_keepalive(isc_nmhandle_t *handle, bool value) {
- 
- 	atomic_store(&sock->keepalive, value);
- }
--
--/*
-- * Process a DNS message.
-- *
-- * If we only have an incomplete DNS message, we don't touch any
-- * timers. If we do have a full message, reset the timer.
-- *
-- * Stop reading if this is a client socket, or if the server socket
-- * has been set to sequential mode, or the number of queries we are
-- * processing simultaneously has reached the clients-per-connection
-- * limit. In this event we'll be called again by resume_processing()
-- * later.
-- */
--static void
--process_sock_buffer(isc_nmsocket_t *sock) {
--	for (;;) {
--		int_fast32_t ah = atomic_load(&sock->ah);
--		isc_result_t result = processbuffer(sock);
--		switch (result) {
--		case ISC_R_NOMORE:
--			/*
--			 * Don't reset the timer until we have a
--			 * full DNS message.
--			 */
--			start_reading(sock);
--			/* Start the timer if there are no active handles */
--			if (ah == 1) {
--				isc__nmsocket_timer_start(sock);
--			}
--			return;
--		case ISC_R_CANCELED:
--			isc__nmsocket_timer_stop(sock);
--			stop_reading(sock);
--			return;
--		case ISC_R_SUCCESS:
--			/*
--			 * Stop the timer on the successful message read, this
--			 * also allows to restart the timer when we have no more
--			 * data.
--			 */
--			isc__nmsocket_timer_stop(sock);
--
--			if (atomic_load(&sock->client) ||
--			    atomic_load(&sock->sequential) ||
--			    atomic_load(&sock->ah) >= TLSDNS_CLIENTS_PER_CONN)
--			{
--				stop_reading(sock);
--				return;
--			}
--			break;
--		default:
--			INSIST(0);
--		}
--	}
--}
--
--static void
--resume_processing(void *arg) {
--	isc_nmsocket_t *sock = (isc_nmsocket_t *)arg;
--
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(sock->tid == isc_nm_tid());
--	REQUIRE(sock->type == isc_nm_tlsdnssocket);
--	REQUIRE(!atomic_load(&sock->client));
--
--	if (inactive(sock)) {
--		return;
--	}
--
--	process_sock_buffer(sock);
--}
-diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c
-index b735a7b..b20a869 100644
---- a/lib/isc/netmgr/udp.c
-+++ b/lib/isc/netmgr/udp.c
-@@ -50,33 +50,11 @@ timer_close_cb(uv_handle_t *handle);
- static void
- udp_close_direct(isc_nmsocket_t *sock);
- 
--static void
--failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
--
--static void
--failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
--	       isc_result_t eresult);
--
- static void
- stop_udp_parent(isc_nmsocket_t *sock);
- static void
- stop_udp_child(isc_nmsocket_t *sock);
- 
--static void
--start_reading(isc_nmsocket_t *sock);
--static void
--stop_reading(isc_nmsocket_t *sock);
--
--static isc__nm_uvreq_t *
--get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr);
--
--static bool
--inactive(isc_nmsocket_t *sock) {
--	return (!isc__nmsocket_active(sock) ||
--		atomic_load(&sock->mgr->closing) ||
--		(sock->server != NULL && !isc__nmsocket_active(sock->server)));
--}
--
- static uv_os_sock_t
- isc__nm_udp_lb_socket(sa_family_t sa_family) {
- 	isc_result_t result;
-@@ -192,32 +170,6 @@ isc_nm_listenudp(isc_nm_t *mgr, isc_nmiface_t *iface, isc_nm_recv_cb_t cb,
- 	return (result);
- }
- 
--/*%<
-- * Allocator for UDP recv operations. Limited to size 20 * (2^16 + 2),
-- * which allows enough space for recvmmsg() to get multiple messages at
-- * a time.
-- *
-- * Note this doesn't actually allocate anything, it just assigns the
-- * worker's receive buffer to a socket, and marks it as "in use".
-- */
--static void
--udp_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
--	isc_nmsocket_t *sock = uv_handle_get_data(handle);
--	isc__networker_t *worker = NULL;
--
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(sock->type == isc_nm_udpsocket);
--	REQUIRE(isc__nm_in_netthread());
--	REQUIRE(size <= ISC_NETMGR_RECVBUF_SIZE);
--
--	worker = &sock->mgr->workers[sock->tid];
--	INSIST(!worker->recvbuf_inuse);
--
--	buf->base = worker->recvbuf;
--	buf->len = ISC_NETMGR_RECVBUF_SIZE;
--	worker->recvbuf_inuse = true;
--}
--
- /*
-  * Asynchronous 'udplisten' call handler: start listening on a UDP socket.
-  */
-@@ -306,7 +258,8 @@ isc__nm_async_udplisten(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	uv_send_buffer_size(&sock->uv_handle.handle,
- 			    &(int){ ISC_SEND_BUFFER_SIZE });
- #endif
--	r = uv_udp_recv_start(&sock->uv_handle.udp, udp_alloc_cb, udp_recv_cb);
-+	r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb,
-+			      udp_recv_cb);
- 	if (r != 0) {
- 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]);
- 		goto done;
-@@ -430,7 +383,7 @@ udp_recv_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
- 	 *   we can free the buffer and bail.
- 	 */
- 	if (addr == NULL) {
--		failed_read_cb(sock, ISC_R_EOF);
-+		isc__nm_failed_read_cb(sock, ISC_R_EOF);
- 		goto free;
- 	}
- 
-@@ -438,19 +391,19 @@ udp_recv_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
- 	 * - If the socket is no longer active.
- 	 */
- 	if (!isc__nmsocket_active(sock)) {
--		failed_read_cb(sock, ISC_R_CANCELED);
-+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
- 		goto free;
- 	}
- 
- 	if (nrecv < 0) {
--		failed_read_cb(sock, isc__nm_uverr2result(nrecv));
-+		isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nrecv));
- 		goto free;
- 	}
- 
- 	result = isc_sockaddr_fromsockaddr(&sockaddr, addr);
- 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
- 
--	req = get_read_req(sock, &sockaddr);
-+	req = isc__nm_get_read_req(sock, &sockaddr);
- 
- 	/*
- 	 * The callback will be called synchronously, because result is
-@@ -570,15 +523,15 @@ isc__nm_async_udpsend(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	REQUIRE(sock->tid == isc_nm_tid());
- 	UNUSED(worker);
- 
--	if (inactive(sock)) {
--		failed_send_cb(sock, uvreq, ISC_R_CANCELED);
-+	if (isc__nm_inactive(sock)) {
-+		isc__nm_failed_send_cb(sock, uvreq, ISC_R_CANCELED);
- 		return;
- 	}
- 
- 	result = udp_send_direct(sock, uvreq, &ievent->peer);
- 	if (result != ISC_R_SUCCESS) {
- 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
--		failed_send_cb(sock, uvreq, result);
-+		isc__nm_failed_send_cb(sock, uvreq, result);
- 	}
- }
- 
-@@ -614,7 +567,7 @@ udp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
- 	REQUIRE(sock->tid == isc_nm_tid());
- 	REQUIRE(sock->type == isc_nm_udpsocket);
- 
--	if (inactive(sock)) {
-+	if (isc__nm_inactive(sock)) {
- 		return (ISC_R_CANCELED);
- 	}
- 
-@@ -839,9 +792,9 @@ isc_nm_udpconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
- 	return (result);
- }
- 
--static void
--udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
--	    const struct sockaddr *addr, unsigned flags) {
-+void
-+isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
-+		    const struct sockaddr *addr, unsigned flags) {
- 	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle);
- 	REQUIRE(VALID_NMSOCK(sock));
- 
-@@ -854,17 +807,17 @@ udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
- 	 * does not.
- 	 */
- 	if (!sock->parent) {
--		stop_reading(sock);
-+		isc__nm_stop_reading(sock);
- 	}
- }
- 
--static void
--failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
-+void
-+isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(result != ISC_R_SUCCESS);
- 
- 	if (atomic_load(&sock->client)) {
--		stop_reading(sock);
-+		isc__nm_stop_reading(sock);
- 
- 		if (!sock->recv_read) {
- 			goto destroy;
-@@ -872,7 +825,7 @@ failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 		sock->recv_read = false;
- 
- 		if (sock->recv_cb != NULL) {
--			isc__nm_uvreq_t *req = get_read_req(sock, NULL);
-+			isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
- 			isc__nmsocket_clearcb(sock);
- 			isc__nm_readcb(sock, req, result);
- 		}
-@@ -895,46 +848,11 @@ failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
- 	sock->recv_read = false;
- 
- 	if (sock->recv_cb != NULL) {
--		isc__nm_uvreq_t *req = get_read_req(sock, NULL);
-+		isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
- 		isc__nm_readcb(sock, req, result);
- 	}
- }
- 
--void
--isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
--	failed_read_cb(sock, result);
--}
--
--static void
--failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
--	       isc_result_t eresult) {
--	REQUIRE(VALID_NMSOCK(sock));
--	REQUIRE(VALID_UVREQ(req));
--
--	if (req->cb.send != NULL) {
--		isc__nm_sendcb(sock, req, eresult, true);
--	} else {
--		isc__nm_uvreq_put(&req, sock);
--	}
--}
--
--static isc__nm_uvreq_t *
--get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr) {
--	isc__nm_uvreq_t *req = NULL;
--
--	req = isc__nm_uvreq_get(sock->mgr, sock);
--	req->cb.recv = sock->recv_cb;
--	req->cbarg = sock->recv_cbarg;
--
--	if (atomic_load(&sock->client)) {
--		isc_nmhandle_attach(sock->statichandle, &req->handle);
--	} else {
--		req->handle = isc__nmhandle_get(sock, sockaddr, NULL);
--	}
--
--	return req;
--}
--
- /*
-  * Asynchronous 'udpread' call handler: start or resume reading on a
-  * socket; pause reading and call the 'recv' callback after each
-@@ -950,41 +868,16 @@ isc__nm_async_udpread(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	REQUIRE(VALID_NMSOCK(sock));
- 	REQUIRE(sock->tid == isc_nm_tid());
- 
--	if (inactive(sock)) {
-+	if (isc__nm_inactive(sock)) {
- 		sock->reading = true;
--		failed_read_cb(sock, ISC_R_CANCELED);
-+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
- 		return;
- 	}
- 
--	start_reading(sock);
-+	isc__nm_start_reading(sock);
- 	isc__nmsocket_timer_start(sock);
- }
- 
--static void
--start_reading(isc_nmsocket_t *sock) {
--	if (sock->reading) {
--		return;
--	}
--
--	int r = uv_udp_recv_start(&sock->uv_handle.udp, udp_alloc_cb,
--				  udp_read_cb);
--	REQUIRE(r == 0);
--	sock->reading = true;
--}
--
--static void
--stop_reading(isc_nmsocket_t *sock) {
--	if (!sock->reading) {
--		return;
--	}
--
--	int r = uv_udp_recv_stop(&sock->uv_handle.udp);
--	REQUIRE(r == 0);
--	sock->reading = false;
--
--	isc__nmsocket_timer_stop(sock);
--}
--
- void
- isc__nm_udp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
- 	REQUIRE(VALID_NMHANDLE(handle));
-@@ -1192,7 +1085,7 @@ isc__nm_udp_shutdown(isc_nmsocket_t *sock) {
- 	 * interested in the callback.
- 	 */
- 	if (sock->statichandle) {
--		failed_read_cb(sock, ISC_R_CANCELED);
-+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
- 		return;
- 	}
- 
-@@ -1236,5 +1129,5 @@ isc__nm_async_udpcancel(isc__networker_t *worker, isc__netievent_t *ev0) {
- 	REQUIRE(sock->tid == isc_nm_tid());
- 	REQUIRE(atomic_load(&sock->client));
- 
--	failed_read_cb(sock, ISC_R_EOF);
-+	isc__nm_failed_read_cb(sock, ISC_R_EOF);
- }
diff -Nru bind9-9.16.13/debian/patches/0006-Warn-if-there-is-excessive-NSEC3-iterations.patch bind9-9.16.15/debian/patches/0006-Warn-if-there-is-excessive-NSEC3-iterations.patch
--- bind9-9.16.13/debian/patches/0006-Warn-if-there-is-excessive-NSEC3-iterations.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0006-Warn-if-there-is-excessive-NSEC3-iterations.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,74 @@
+From: Mark Andrews <marka@isc.org>
+Date: Fri, 23 Apr 2021 14:23:00 +1000
+Subject: Warn if there is excessive NSEC3 iterations
+
+---
+ lib/dns/zone.c | 32 ++++++++++++++++++++++----------
+ 1 file changed, 22 insertions(+), 10 deletions(-)
+
+diff --git a/lib/dns/zone.c b/lib/dns/zone.c
+index f47fa7c..dc1b9c4 100644
+--- a/lib/dns/zone.c
++++ b/lib/dns/zone.c
+@@ -3902,13 +3902,12 @@ cleanup:
+ 
+ static isc_result_t
+ check_nsec3param(dns_zone_t *zone, dns_db_t *db) {
++	bool ok = false;
+ 	dns_dbnode_t *node = NULL;
+-	dns_rdataset_t rdataset;
+ 	dns_dbversion_t *version = NULL;
+ 	dns_rdata_nsec3param_t nsec3param;
+-	bool ok = false;
++	dns_rdataset_t rdataset;
+ 	isc_result_t result;
+-	dns_rdata_t rdata = DNS_RDATA_INIT;
+ 	bool dynamic = (zone->type == dns_zone_master)
+ 			       ? dns_zone_isdynamic(zone, false)
+ 			       : false;
+@@ -3939,18 +3938,21 @@ check_nsec3param(dns_zone_t *zone, dns_db_t *db) {
+ 		goto cleanup;
+ 	}
+ 
+-	/*
+-	 * For dynamic zones we must support every algorithm so we can
+-	 * regenerate all the NSEC3 chains.
+-	 * For non-dynamic zones we only need to find a supported algorithm.
+-	 */
+ 	for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ 	     result = dns_rdataset_next(&rdataset))
+ 	{
++		dns_rdata_t rdata = DNS_RDATA_INIT;
++
+ 		dns_rdataset_current(&rdataset, &rdata);
+ 		result = dns_rdata_tostruct(&rdata, &nsec3param, NULL);
+-		dns_rdata_reset(&rdata);
+-		INSIST(result == ISC_R_SUCCESS);
++		RUNTIME_CHECK(result == ISC_R_SUCCESS);
++
++		/*
++		 * For dynamic zones we must support every algorithm so we
++		 * can regenerate all the NSEC3 chains.
++		 * For non-dynamic zones we only need to find a supported
++		 * algorithm.
++		 */
+ 		if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NSEC3TESTZONE) &&
+ 		    nsec3param.hash == DNS_NSEC3_UNKNOWNALG && !dynamic)
+ 		{
+@@ -3978,6 +3980,16 @@ check_nsec3param(dns_zone_t *zone, dns_db_t *db) {
+ 		} else {
+ 			ok = true;
+ 		}
++
++		/*
++		 * Warn if the zone has excessive NSEC3 iterations.
++		 */
++		if (nsec3param.iterations > dns_nsec3_maxiterations()) {
++			dnssec_log(zone, ISC_LOG_WARNING,
++				   "excessive NSEC3PARAM iterations %u > %u",
++				   nsec3param.iterations,
++				   dns_nsec3_maxiterations());
++		}
+ 	}
+ 	if (result == ISC_R_NOMORE) {
+ 		result = ISC_R_SUCCESS;
diff -Nru bind9-9.16.13/debian/patches/0007-Change-the-isc_nm_-get-set-timeouts-to-work-with-mil.patch bind9-9.16.15/debian/patches/0007-Change-the-isc_nm_-get-set-timeouts-to-work-with-mil.patch
--- bind9-9.16.13/debian/patches/0007-Change-the-isc_nm_-get-set-timeouts-to-work-with-mil.patch	2021-03-18 14:23:49.000000000 +0100
+++ bind9-9.16.15/debian/patches/0007-Change-the-isc_nm_-get-set-timeouts-to-work-with-mil.patch	1970-01-01 01:00:00.000000000 +0100
@@ -1,360 +0,0 @@
-From: =?utf-8?b?T25kxZllaiBTdXLDvQ==?= <ondrej@sury.org>
-Date: Thu, 18 Mar 2021 11:16:45 +0100
-Subject: Change the isc_nm_(get|set)timeouts() to work with milliseconds
-
-The RFC7828 specifies the keepalive interval to be 16-bit, specified in
-units of 100 milliseconds and the configuration options tcp-*-timeouts
-are following the suit.  The units of 100 milliseconds are very
-unintuitive and while we can't change the configuration and presentation
-format, we should not follow this weird unit in the API.
-
-This commit changes the isc_nm_(get|set)timeouts() functions to work
-with milliseconds and convert the values to milliseconds before passing
-them to the function, not just internally.
----
- bin/named/server.c           | 104 ++++++++++++++++++++++++++-----------------
- lib/isc/include/isc/netmgr.h |  10 ++---
- lib/isc/netmgr/netmgr-int.h  |   2 +-
- lib/isc/netmgr/netmgr.c      |  16 +++----
- lib/isc/tests/tcpdns_test.c  |   1 -
- lib/isc/tests/tlsdns_test.c  |   1 -
- lib/ns/client.c              |   1 +
- 7 files changed, 78 insertions(+), 57 deletions(-)
-
-diff --git a/bin/named/server.c b/bin/named/server.c
-index d8e8d67..a90241e 100644
---- a/bin/named/server.c
-+++ b/bin/named/server.c
-@@ -166,7 +166,18 @@
- #define EXCLBUFFERS	       4096
- #endif /* TUNE_LARGE */
- 
--#define MAX_TCP_TIMEOUT 65535
-+/* RFC7828 defines timeout as 16-bit value specified in units of 100
-+ * milliseconds, so the maximum and minimum advertised and keepalive
-+ * timeouts are capped by the data type (it's ~109 minutes)
-+ */
-+#define MIN_INITIAL_TIMEOUT    UINT32_C(2500)	/* 2.5 seconds */
-+#define MAX_INITIAL_TIMEOUT    UINT32_C(120000) /* 2 minutes */
-+#define MIN_IDLE_TIMEOUT       UINT32_C(100)	/* 0.1 seconds */
-+#define MAX_IDLE_TIMEOUT       UINT32_C(120000) /* 2 minutes */
-+#define MIN_KEEPALIVE_TIMEOUT  UINT32_C(100)	/* 0.1 seconds */
-+#define MAX_KEEPALIVE_TIMEOUT  UINT32_C(UINT16_MAX * 100)
-+#define MIN_ADVERTISED_TIMEOUT UINT32_C(0) /* No minimum */
-+#define MAX_ADVERTISED_TIMEOUT UINT32_C(UINT16_MAX * 100)
- 
- /*%
-  * Check an operation for failure.  Assumes that the function
-@@ -8315,7 +8326,7 @@ load_configuration(const char *filename, named_server_t *server,
- 	unsigned int maxsocks;
- 	uint32_t softquota = 0;
- 	uint32_t max;
--	unsigned int initial, idle, keepalive, advertised;
-+	uint64_t initial, idle, keepalive, advertised;
- 	dns_aclenv_t *env =
- 		ns_interfacemgr_getaclenv(named_g_server->interfacemgr);
- 
-@@ -8572,62 +8583,67 @@ load_configuration(const char *filename, named_server_t *server,
- 	obj = NULL;
- 	result = named_config_get(maps, "tcp-initial-timeout", &obj);
- 	INSIST(result == ISC_R_SUCCESS);
--	initial = cfg_obj_asuint32(obj);
--	if (initial > 1200) {
-+	initial = cfg_obj_asuint32(obj) * 100;
-+	if (initial > MAX_INITIAL_TIMEOUT) {
- 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
- 			    "tcp-initial-timeout value is out of range: "
--			    "lowering to 1200");
--		initial = 1200;
--	} else if (initial < 25) {
-+			    "lowering to %" PRIu32,
-+			    MAX_INITIAL_TIMEOUT / 100);
-+		initial = MAX_INITIAL_TIMEOUT;
-+	} else if (initial < MIN_INITIAL_TIMEOUT) {
- 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
- 			    "tcp-initial-timeout value is out of range: "
--			    "raising to 25");
--		initial = 25;
-+			    "raising to %" PRIu32,
-+			    MIN_INITIAL_TIMEOUT / 100);
-+		initial = MIN_INITIAL_TIMEOUT;
- 	}
- 
- 	obj = NULL;
- 	result = named_config_get(maps, "tcp-idle-timeout", &obj);
- 	INSIST(result == ISC_R_SUCCESS);
--	idle = cfg_obj_asuint32(obj);
--	if (idle > 1200) {
-+	idle = cfg_obj_asuint32(obj) * 100;
-+	if (idle > MAX_IDLE_TIMEOUT) {
- 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
- 			    "tcp-idle-timeout value is out of range: "
--			    "lowering to 1200");
--		idle = 1200;
--	} else if (idle < 1) {
-+			    "lowering to %" PRIu32,
-+			    MAX_IDLE_TIMEOUT / 100);
-+		idle = MAX_IDLE_TIMEOUT;
-+	} else if (idle < MIN_IDLE_TIMEOUT) {
- 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
- 			    "tcp-idle-timeout value is out of range: "
--			    "raising to 1");
--		idle = 1;
-+			    "raising to %" PRIu32,
-+			    MIN_IDLE_TIMEOUT / 100);
-+		idle = MIN_IDLE_TIMEOUT;
- 	}
- 
- 	obj = NULL;
- 	result = named_config_get(maps, "tcp-keepalive-timeout", &obj);
- 	INSIST(result == ISC_R_SUCCESS);
--	keepalive = cfg_obj_asuint32(obj);
--	if (keepalive > MAX_TCP_TIMEOUT) {
-+	keepalive = cfg_obj_asuint32(obj) * 100;
-+	if (keepalive > MAX_KEEPALIVE_TIMEOUT) {
- 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
- 			    "tcp-keepalive-timeout value is out of range: "
--			    "lowering to %u",
--			    MAX_TCP_TIMEOUT);
--		keepalive = MAX_TCP_TIMEOUT;
--	} else if (keepalive < 1) {
-+			    "lowering to %" PRIu32,
-+			    MAX_KEEPALIVE_TIMEOUT / 100);
-+		keepalive = MAX_KEEPALIVE_TIMEOUT;
-+	} else if (keepalive < MIN_KEEPALIVE_TIMEOUT) {
- 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
- 			    "tcp-keepalive-timeout value is out of range: "
--			    "raising to 1");
--		keepalive = 1;
-+			    "raising to %" PRIu32,
-+			    MIN_KEEPALIVE_TIMEOUT / 100);
-+		keepalive = MIN_KEEPALIVE_TIMEOUT;
- 	}
- 
- 	obj = NULL;
- 	result = named_config_get(maps, "tcp-advertised-timeout", &obj);
- 	INSIST(result == ISC_R_SUCCESS);
--	advertised = cfg_obj_asuint32(obj);
--	if (advertised > MAX_TCP_TIMEOUT) {
-+	advertised = cfg_obj_asuint32(obj) * 100;
-+	if (advertised > MAX_ADVERTISED_TIMEOUT) {
- 		cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
- 			    "tcp-advertized-timeout value is out of range: "
--			    "lowering to %u",
--			    MAX_TCP_TIMEOUT);
--		advertised = MAX_TCP_TIMEOUT;
-+			    "lowering to %" PRIu32,
-+			    MAX_ADVERTISED_TIMEOUT / 100);
-+		advertised = MAX_ADVERTISED_TIMEOUT;
- 	}
- 
- 	isc_nm_settimeouts(named_g_nm, initial, idle, keepalive, advertised);
-@@ -15895,7 +15911,7 @@ isc_result_t
- named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
- 	char *ptr;
- 	isc_result_t result = ISC_R_SUCCESS;
--	unsigned int initial, idle, keepalive, advertised;
-+	uint32_t initial, idle, keepalive, advertised;
- 	char msg[128];
- 
- 	/* Skip the command name. */
-@@ -15911,10 +15927,11 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
- 	ptr = next_token(lex, NULL);
- 	if (ptr != NULL) {
- 		CHECK(isc_parse_uint32(&initial, ptr, 10));
--		if (initial > 1200) {
-+		initial *= 100;
-+		if (initial > MAX_INITIAL_TIMEOUT) {
- 			CHECK(ISC_R_RANGE);
- 		}
--		if (initial < 25) {
-+		if (initial < MIN_INITIAL_TIMEOUT) {
- 			CHECK(ISC_R_RANGE);
- 		}
- 
-@@ -15923,10 +15940,11 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
- 			return (ISC_R_UNEXPECTEDEND);
- 		}
- 		CHECK(isc_parse_uint32(&idle, ptr, 10));
--		if (idle > 1200) {
-+		idle *= 100;
-+		if (idle > MAX_IDLE_TIMEOUT) {
- 			CHECK(ISC_R_RANGE);
- 		}
--		if (idle < 1) {
-+		if (idle < MIN_IDLE_TIMEOUT) {
- 			CHECK(ISC_R_RANGE);
- 		}
- 
-@@ -15935,10 +15953,11 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
- 			return (ISC_R_UNEXPECTEDEND);
- 		}
- 		CHECK(isc_parse_uint32(&keepalive, ptr, 10));
--		if (keepalive > MAX_TCP_TIMEOUT) {
-+		keepalive *= 100;
-+		if (keepalive > MAX_KEEPALIVE_TIMEOUT) {
- 			CHECK(ISC_R_RANGE);
- 		}
--		if (keepalive < 1) {
-+		if (keepalive < MIN_KEEPALIVE_TIMEOUT) {
- 			CHECK(ISC_R_RANGE);
- 		}
- 
-@@ -15947,7 +15966,8 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
- 			return (ISC_R_UNEXPECTEDEND);
- 		}
- 		CHECK(isc_parse_uint32(&advertised, ptr, 10));
--		if (advertised > MAX_TCP_TIMEOUT) {
-+		advertised *= 100;
-+		if (advertised > MAX_ADVERTISED_TIMEOUT) {
- 			CHECK(ISC_R_RANGE);
- 		}
- 
-@@ -15960,13 +15980,15 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
- 		isc_task_endexclusive(named_g_server->task);
- 	}
- 
--	snprintf(msg, sizeof(msg), "tcp-initial-timeout=%u\n", initial);
-+	snprintf(msg, sizeof(msg), "tcp-initial-timeout=%u\n", initial / 100);
- 	CHECK(putstr(text, msg));
--	snprintf(msg, sizeof(msg), "tcp-idle-timeout=%u\n", idle);
-+	snprintf(msg, sizeof(msg), "tcp-idle-timeout=%u\n", idle / 100);
- 	CHECK(putstr(text, msg));
--	snprintf(msg, sizeof(msg), "tcp-keepalive-timeout=%u\n", keepalive);
-+	snprintf(msg, sizeof(msg), "tcp-keepalive-timeout=%u\n",
-+		 keepalive / 100);
- 	CHECK(putstr(text, msg));
--	snprintf(msg, sizeof(msg), "tcp-advertised-timeout=%u", advertised);
-+	snprintf(msg, sizeof(msg), "tcp-advertised-timeout=%u",
-+		 advertised / 100);
- 	CHECK(putstr(text, msg));
- 
- cleanup:
-diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h
-index cab20b6..8e58567 100644
---- a/lib/isc/include/isc/netmgr.h
-+++ b/lib/isc/include/isc/netmgr.h
-@@ -166,7 +166,7 @@ void
- isc_nmhandle_cleartimeout(isc_nmhandle_t *handle);
- /*%<
-  * Set/clear the read/recv timeout for the socket connected to 'handle'
-- * to 'timeout', and reset the timer, in miliseconds.
-+ * to 'timeout' (in milliseconds), and reset the timer.
-  *
-  * When this is called on a 'wrapper' socket handle (for example,
-  * a TCPDNS socket wrapping a TCP connection), the timer is set for
-@@ -433,10 +433,10 @@ void
- isc_nm_settimeouts(isc_nm_t *mgr, uint32_t init, uint32_t idle,
- 		   uint32_t keepalive, uint32_t advertised);
- /*%<
-- * Sets the initial, idle, and keepalive timeout values to use for
-- * TCP connections, and the timeout value to advertise in responses using
-+ * Sets the initial, idle, and keepalive timeout values (in milliseconds) to use
-+ * for TCP connections, and the timeout value to advertise in responses using
-  * the EDNS TCP Keepalive option (which should ordinarily be the same
-- * as 'keepalive'), in tenths of seconds.
-+ * as 'keepalive').
-  *
-  * Requires:
-  * \li	'mgr' is a valid netmgr.
-@@ -447,7 +447,7 @@ isc_nm_gettimeouts(isc_nm_t *mgr, uint32_t *initial, uint32_t *idle,
- 		   uint32_t *keepalive, uint32_t *advertised);
- /*%<
-  * Gets the initial, idle, keepalive, or advertised timeout values,
-- * in tenths of seconds.
-+ * in milliseconds.
-  *
-  * Any integer pointer parameter not set to NULL will be updated to
-  * contain the corresponding timeout value.
-diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h
-index 016f982..07f95cc 100644
---- a/lib/isc/netmgr/netmgr-int.h
-+++ b/lib/isc/netmgr/netmgr-int.h
-@@ -1449,7 +1449,7 @@ isc__nm_socket_dontfrag(uv_os_sock_t fd, sa_family_t sa_family);
- isc_result_t
- isc__nm_socket_connectiontimeout(uv_os_sock_t fd, int timeout_ms);
- /*%<
-- * Set the connection timeout in miliseconds, on non-Linux platforms,
-+ * Set the connection timeout in milliseconds, on non-Linux platforms,
-  * the minimum value must be at least 1000 (1 second).
-  */
- 
-diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c
-index e919a0d..deb68bd 100644
---- a/lib/isc/netmgr/netmgr.c
-+++ b/lib/isc/netmgr/netmgr.c
-@@ -509,10 +509,10 @@ isc_nm_settimeouts(isc_nm_t *mgr, uint32_t init, uint32_t idle,
- 		   uint32_t keepalive, uint32_t advertised) {
- 	REQUIRE(VALID_NM(mgr));
- 
--	atomic_store(&mgr->init, init * 100);
--	atomic_store(&mgr->idle, idle * 100);
--	atomic_store(&mgr->keepalive, keepalive * 100);
--	atomic_store(&mgr->advertised, advertised * 100);
-+	atomic_store(&mgr->init, init);
-+	atomic_store(&mgr->idle, idle);
-+	atomic_store(&mgr->keepalive, keepalive);
-+	atomic_store(&mgr->advertised, advertised);
- }
- 
- void
-@@ -521,19 +521,19 @@ isc_nm_gettimeouts(isc_nm_t *mgr, uint32_t *initial, uint32_t *idle,
- 	REQUIRE(VALID_NM(mgr));
- 
- 	if (initial != NULL) {
--		*initial = atomic_load(&mgr->init) / 100;
-+		*initial = atomic_load(&mgr->init);
- 	}
- 
- 	if (idle != NULL) {
--		*idle = atomic_load(&mgr->idle) / 100;
-+		*idle = atomic_load(&mgr->idle);
- 	}
- 
- 	if (keepalive != NULL) {
--		*keepalive = atomic_load(&mgr->keepalive) / 100;
-+		*keepalive = atomic_load(&mgr->keepalive);
- 	}
- 
- 	if (advertised != NULL) {
--		*advertised = atomic_load(&mgr->advertised) / 100;
-+		*advertised = atomic_load(&mgr->advertised);
- 	}
- }
- 
-diff --git a/lib/isc/tests/tcpdns_test.c b/lib/isc/tests/tcpdns_test.c
-index 9325f04..52dbac6 100644
---- a/lib/isc/tests/tcpdns_test.c
-+++ b/lib/isc/tests/tcpdns_test.c
-@@ -242,7 +242,6 @@ nm_setup(void **state) {
- 	for (size_t i = 0; i < MAX_NM; i++) {
- 		nm[i] = isc_nm_start(test_mctx, nworkers);
- 		assert_non_null(nm[i]);
--		isc_nm_settimeouts(nm[i], 1000, 1000, 1000, 1000);
- 	}
- 
- 	*state = nm;
-diff --git a/lib/isc/tests/tlsdns_test.c b/lib/isc/tests/tlsdns_test.c
-index d6e2ff2..ceee83d 100644
---- a/lib/isc/tests/tlsdns_test.c
-+++ b/lib/isc/tests/tlsdns_test.c
-@@ -249,7 +249,6 @@ nm_setup(void **state) {
- 	for (size_t i = 0; i < MAX_NM; i++) {
- 		nm[i] = isc_nm_start(test_mctx, nworkers);
- 		assert_non_null(nm[i]);
--		isc_nm_settimeouts(nm[i], 1000, 1000, 1000, 1000);
- 	}
- 
- 	*state = nm;
-diff --git a/lib/ns/client.c b/lib/ns/client.c
-index 70d6596..2077e86 100644
---- a/lib/ns/client.c
-+++ b/lib/ns/client.c
-@@ -1037,6 +1037,7 @@ no_nsid:
- 
- 		isc_nm_gettimeouts(isc_nmhandle_netmgr(client->handle), NULL,
- 				   NULL, NULL, &adv);
-+		adv /= 100; /* units of 100 milliseconds */
- 		isc_buffer_init(&buf, advtimo, sizeof(advtimo));
- 		isc_buffer_putuint16(&buf, (uint16_t)adv);
- 		ednsopts[count].code = DNS_OPT_TCP_KEEPALIVE;
diff -Nru bind9-9.16.13/debian/patches/0007-nsupdate-reject-attempts-to-add-NSEC3PARAM-with-exce.patch bind9-9.16.15/debian/patches/0007-nsupdate-reject-attempts-to-add-NSEC3PARAM-with-exce.patch
--- bind9-9.16.13/debian/patches/0007-nsupdate-reject-attempts-to-add-NSEC3PARAM-with-exce.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0007-nsupdate-reject-attempts-to-add-NSEC3PARAM-with-exce.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,77 @@
+From: Mark Andrews <marka@isc.org>
+Date: Fri, 23 Apr 2021 10:03:53 +1000
+Subject: nsupdate: reject attempts to add NSEC3PARAM with excessive
+ iterations
+
+---
+ bin/nsupdate/nsupdate.c            | 14 ++++++++++++++
+ bin/tests/system/nsupdate/tests.sh | 17 ++++++++++++++++-
+ 2 files changed, 30 insertions(+), 1 deletion(-)
+
+diff --git a/bin/nsupdate/nsupdate.c b/bin/nsupdate/nsupdate.c
+index d96071a..b2b45d6 100644
+--- a/bin/nsupdate/nsupdate.c
++++ b/bin/nsupdate/nsupdate.c
+@@ -56,6 +56,7 @@
+ #include <dns/masterdump.h>
+ #include <dns/message.h>
+ #include <dns/name.h>
++#include <dns/nsec3.h>
+ #include <dns/rcode.h>
+ #include <dns/rdata.h>
+ #include <dns/rdataclass.h>
+@@ -1979,6 +1980,19 @@ parseclass:
+ 		}
+ 	}
+ 
++	if (!isdelete && rdata->type == dns_rdatatype_nsec3param) {
++		dns_rdata_nsec3param_t nsec3param;
++
++		result = dns_rdata_tostruct(rdata, &nsec3param, NULL);
++		check_result(result, "dns_rdata_tostruct");
++		if (nsec3param.iterations > dns_nsec3_maxiterations()) {
++			fprintf(stderr,
++				"NSEC3PARAM has excessive iterations (> %u)\n",
++				dns_nsec3_maxiterations());
++			goto failure;
++		}
++	}
++
+ doneparsing:
+ 
+ 	result = dns_message_gettemprdatalist(updatemsg, &rdatalist);
+diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh
+index d1d72e4..006e839 100755
+--- a/bin/tests/system/nsupdate/tests.sh
++++ b/bin/tests/system/nsupdate/tests.sh
+@@ -1132,6 +1132,8 @@ grep "UPDATE, status: NOERROR" nsupdate.out-$n > /dev/null 2>&1 || ret=1
+ grep "UPDATE, status: FORMERR" nsupdate.out-$n > /dev/null 2>&1 || ret=1
+ [ $ret = 0 ] || { echo_i "failed"; status=1; }
+ 
++n=`expr $n + 1`
++ret=0
+ echo_i "check that DS to the zone apex is ignored ($n)"
+ $DIG $DIGOPTS +tcp +norec example DS @10.53.0.3 > dig.out.pre.test$n || ret=1
+ grep "status: NOERROR" dig.out.pre.test$n > /dev/null || ret=1
+@@ -1151,7 +1153,20 @@ grep "status: NOERROR" dig.out.post.test$n > /dev/null || ret=1
+ grep "ANSWER: 0," dig.out.post.test$n > /dev/null || ret=1
+ [ $ret = 0 ] || { echo_i "failed"; status=1; }
+ 
+-if $FEATURETEST --gssapi ; then
++n=`expr $n + 1`
++ret=0
++echo_i "check that excessive NSEC3PARAM iterations are rejected by nsupdate ($n)"
++$NSUPDATE -d <<END > nsupdate.out-$n 2>&1 && ret=1
++server 10.53.0.3 ${PORT}
++zone example
++update add example 0 in NSEC3PARAM 1 0 151 -
++END
++grep "NSEC3PARAM has excessive iterations (> 150)" nsupdate.out-$n || ret=1
++[ $ret = 0 ] || { echo_i "failed"; status=1; }
++
++if ! $FEATURETEST --gssapi ; then
++  echo_i "SKIPPED: GSSAPI tests"
++else
+   n=`expr $n + 1`
+   ret=0
+   echo_i "check krb5-self match ($n)"
diff -Nru bind9-9.16.13/debian/patches/0008-Check-that-named-rejects-excessive-iterations-via-UP.patch bind9-9.16.15/debian/patches/0008-Check-that-named-rejects-excessive-iterations-via-UP.patch
--- bind9-9.16.13/debian/patches/0008-Check-that-named-rejects-excessive-iterations-via-UP.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0008-Check-that-named-rejects-excessive-iterations-via-UP.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,45 @@
+From: Mark Andrews <marka@isc.org>
+Date: Fri, 23 Apr 2021 10:28:06 +1000
+Subject: Check that named rejects excessive iterations via UPDATE
+
+---
+ bin/tests/system/nsupdate/tests.sh       | 3 ++-
+ bin/tests/system/nsupdate/update_test.pl | 4 ++++
+ 2 files changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh
+index 006e839..a5ac670 100755
+--- a/bin/tests/system/nsupdate/tests.sh
++++ b/bin/tests/system/nsupdate/tests.sh
+@@ -311,6 +311,7 @@ then
+     {
+       $PERL update_test.pl -s 10.53.0.1 -p ${PORT} update.nil. || ret=1
+     } | cat_i
++    grep "updating zone 'update.nil/IN': too many NSEC3 iterations (151)" ns1/named.run > /dev/null || ret=1
+     [ $ret -eq 1 ] && { echo_i "failed"; status=1; }
+ else
+     echo_i "The second part of this test requires the Net::DNS library." >&2
+@@ -1161,7 +1162,7 @@ server 10.53.0.3 ${PORT}
+ zone example
+ update add example 0 in NSEC3PARAM 1 0 151 -
+ END
+-grep "NSEC3PARAM has excessive iterations (> 150)" nsupdate.out-$n || ret=1
++grep "NSEC3PARAM has excessive iterations (> 150)" nsupdate.out-$n >/dev/null || ret=1
+ [ $ret = 0 ] || { echo_i "failed"; status=1; }
+ 
+ if ! $FEATURETEST --gssapi ; then
+diff --git a/bin/tests/system/nsupdate/update_test.pl b/bin/tests/system/nsupdate/update_test.pl
+index 894a572..fd6a542 100644
+--- a/bin/tests/system/nsupdate/update_test.pl
++++ b/bin/tests/system/nsupdate/update_test.pl
+@@ -410,6 +410,10 @@ test("NOERROR", ["update", rr_add("u.$zone 300 NS ns.u.$zone")]);
+ 
+ test("NOERROR", ["update", rr_del("u.$zone NS ns.u.$zone")]);
+ 
++section("Excessive NSEC3PARAM iterations");
++test("REFUSED", ["update", rr_add("$zone 300 NSEC3PARAM 1 0 151 -")]);
++test("NOERROR", ["update", rr_add("$zone 300 NSEC3PARAM 1 0 150 -")]);
++
+ if ($failures) {
+     print "$failures tests failed.\n";
+ } else {
diff -Nru bind9-9.16.13/debian/patches/0009-Check-NSEC3-iterations-with-dnssec-signzone.patch bind9-9.16.15/debian/patches/0009-Check-NSEC3-iterations-with-dnssec-signzone.patch
--- bind9-9.16.13/debian/patches/0009-Check-NSEC3-iterations-with-dnssec-signzone.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0009-Check-NSEC3-iterations-with-dnssec-signzone.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,172 @@
+From: Mark Andrews <marka@isc.org>
+Date: Fri, 23 Apr 2021 11:46:31 +1000
+Subject: Check NSEC3 iterations with dnssec-signzone
+
+---
+ .../signer/general/Kexample.com.+008+15002.key     |  5 ++++
+ .../signer/general/Kexample.com.+008+15002.private | 13 +++++++++
+ .../signer/general/Kexample.com.+008+63613.key     |  5 ++++
+ .../signer/general/Kexample.com.+008+63613.private | 13 +++++++++
+ bin/tests/system/dnssec/signer/general/test9.zone  | 17 ++++++++++++
+ bin/tests/system/dnssec/tests.sh                   | 32 +++++++++++++++++++---
+ 6 files changed, 81 insertions(+), 4 deletions(-)
+ create mode 100644 bin/tests/system/dnssec/signer/general/Kexample.com.+008+15002.key
+ create mode 100644 bin/tests/system/dnssec/signer/general/Kexample.com.+008+15002.private
+ create mode 100644 bin/tests/system/dnssec/signer/general/Kexample.com.+008+63613.key
+ create mode 100644 bin/tests/system/dnssec/signer/general/Kexample.com.+008+63613.private
+ create mode 100644 bin/tests/system/dnssec/signer/general/test9.zone
+
+diff --git a/bin/tests/system/dnssec/signer/general/Kexample.com.+008+15002.key b/bin/tests/system/dnssec/signer/general/Kexample.com.+008+15002.key
+new file mode 100644
+index 0000000..d4b8efb
+--- /dev/null
++++ b/bin/tests/system/dnssec/signer/general/Kexample.com.+008+15002.key
+@@ -0,0 +1,5 @@
++; This is a key-signing key, keyid 15002, for example.com.
++; Created: 20210423012926 (Fri Apr 23 11:29:26 2021)
++; Publish: 20210423012926 (Fri Apr 23 11:29:26 2021)
++; Activate: 20210423012926 (Fri Apr 23 11:29:26 2021)
++example.com. IN DNSKEY 257 3 8 AwEAAdp+oCXl7vpKA3Mmyndx6/iA+wLrtxeMUiWL7uWJ9ZF24EdS8Dye 63p0lGlyvjvM9T5dTiyEpTAdutEBr79H0MlDqIBqpadrCdJRI2S4kC+0 nq5+Aj2CEyiAamPGujwWeXwtfLAvVPfBqs42PBr6wPQIJOByFYDaZBU3 enUEWgHYy/7OnJDrt0QlswKphR6SvYtyuixiUR8J/WouWXglUY5qlC7Z vVDxs9E4q7B1mfKCyoqcFMKPh9lzEBH+IfUZ543xXEYf2BEztKB1SZ2R QnpYedjATGDcgPis46uA2gHMfvDYJTQ5UqTBtveGb3Wsqc0oRXVPMEoY 3WnWhaKDzkk=
+diff --git a/bin/tests/system/dnssec/signer/general/Kexample.com.+008+15002.private b/bin/tests/system/dnssec/signer/general/Kexample.com.+008+15002.private
+new file mode 100644
+index 0000000..72b8e2e
+--- /dev/null
++++ b/bin/tests/system/dnssec/signer/general/Kexample.com.+008+15002.private
+@@ -0,0 +1,13 @@
++Private-key-format: v1.3
++Algorithm: 8 (RSASHA256)
++Modulus: 2n6gJeXu+koDcybKd3Hr+ID7Auu3F4xSJYvu5Yn1kXbgR1LwPJ7renSUaXK+O8z1Pl1OLISlMB260QGvv0fQyUOogGqlp2sJ0lEjZLiQL7Sern4CPYITKIBqY8a6PBZ5fC18sC9U98GqzjY8GvrA9Agk4HIVgNpkFTd6dQRaAdjL/s6ckOu3RCWzAqmFHpK9i3K6LGJRHwn9ai5ZeCVRjmqULtm9UPGz0TirsHWZ8oLKipwUwo+H2XMQEf4h9RnnjfFcRh/YETO0oHVJnZFCelh52MBMYNyA+Kzjq4DaAcx+8NglNDlSpMG294ZvdaypzShFdU8wShjdadaFooPOSQ==
++PublicExponent: AQAB
++PrivateExponent: SD4X64/0DTONonRP+2Biej8DP7r6RcHyo1F6QtDzrg4VJ+AHaLPO/iUvsRHsTk99QwqMv3F4QMmDrHmXR3KSWQmS3Crm7M0aaTzErBfOLMfWs7EcQoQQm5KiGq1phFaWAnXzxTlRKb4SIK6T/wOr6sQKlV+DNqB++Pjn92rh67vLM8kZBUzWI14Vl9N0ib+xOOFH1oYFo7ynDgMfJhpnQSkuRfyQls3aD1eKQsNazRtZ7lFi2S0HR/V0AKYH2AQi7SdL5wH6hYba5cHfpKSw7PebI0lYkUJ4PAg3Xw7DPMkg8O0hkpLICpU8x7MPqQQ74eKDaEY+fjbL0KLL0Dy9UQ==
++Prime1: /IDRb7WzMY6wp14LqDORULoUnmiQOqkRjOQnCoEXT2KVpYwPmGMG+GR40hrMFgqqAZFVmi56VBoasWpYbSBEqM4aJv1JVimMPREk23v5i+TY93kxICO/ee9/v0hXgLmrKUkS1Kwu4a1PxLX5U/LAzXPR6zF+EHP9OKFjDRWHqN0=
++Prime2: 3YU9QdtsXofjNmlDETRwemKv45pa0oVNPmNvS1vtzIpQ3m/QSuhJxzyTgSP9x1XMiIsg63er3LOCtkRifXVE1IBrfIUgchp8YD5LsyesRl2ielE8Hw8PwSA1YjUVu90yRHcVfbZJ8lm2KyRKHgDWXz94t2Xnm/9M5XjUGuNW7l0=
++Exponent1: 7KIkpJYZyvW4ZAFk10sMgiUBMbs4f2D2i509YUC9ga4YJD7wVpVncN1nxS9L19RCopl7KbUo+yxDm8TX/dzhu3j7VVLFqbPiM1Cfw/mZUhszoii3ezFFPpbOl4rKRl66I0TSGvEKNoDfYrBPavby7Rf/wHRveifZRXspgpeMvRk=
++Exponent2: yvvtjuxW2CRiopg/+YL40lyd2cy2DpRRnKqW8BHzzGquAbWpwwopmOS8MSjewgqv2irK5pmJJTpku0nciiOsB6EJXVfLzGLSt4o96ZOf+/aPDNBla/xsLkaqRCxqlvPwvOX2DnS8O9PS5qNhOy7/QNYzcrJxUfPV7awTh/Pr040=
++Coefficient: PHxU1tqPKTpI/8nABvso0SRerc1m+RPWGRk7s/SVcADSBvEW7fUDcwiZeRfK9MdlwPvLiVozbYnRbgRQl8GuKSqAD1+Cnvn2yOQk81AgNKbuKPwF7UvKIdq/c/xnhj2bvZUVSavJ91ux/RlZNP50378Ks8bj5HJl1xzAMVHXB5o=
++Created: 20210423012926
++Publish: 20210423012926
++Activate: 20210423012926
+diff --git a/bin/tests/system/dnssec/signer/general/Kexample.com.+008+63613.key b/bin/tests/system/dnssec/signer/general/Kexample.com.+008+63613.key
+new file mode 100644
+index 0000000..990b837
+--- /dev/null
++++ b/bin/tests/system/dnssec/signer/general/Kexample.com.+008+63613.key
+@@ -0,0 +1,5 @@
++; This is a zone-signing key, keyid 63613, for example.com.
++; Created: 20210423012810 (Fri Apr 23 11:28:10 2021)
++; Publish: 20210423012810 (Fri Apr 23 11:28:10 2021)
++; Activate: 20210423012810 (Fri Apr 23 11:28:10 2021)
++example.com. IN DNSKEY 256 3 8 AwEAAZzun7bYfjmGDwUEn4pyJG34vsiawRMW6pEdoNMH87ozxriOzgG6 /4zTjEv8JyYjGQz2k2vcoWWcD+86xD5IUqfa1pdXXUU8bdhG9DBtW/K1 mc4P6g8heU+0f++mq/L4TPlWVZUG8lVH4H8mD6r8PsVK7v/QR7wMeg9b JpCYyxon2A9rZ4zS0J9kX9bfciQVh6ODGVENctwEK5FNp5u0VonPEIx3 5Kj+IVn/mHpfbz4LaC02s7C6Kgvn3ToFFSJczwbOaexl/d+/ai8FLJi7 8UgiAq5/178bcVLItMeY6aD5eQGkRtr2c3JZ/JR4Nf+TQAWFBnl9NSDa RH4Qa55ZNqs=
+diff --git a/bin/tests/system/dnssec/signer/general/Kexample.com.+008+63613.private b/bin/tests/system/dnssec/signer/general/Kexample.com.+008+63613.private
+new file mode 100644
+index 0000000..1765d3f
+--- /dev/null
++++ b/bin/tests/system/dnssec/signer/general/Kexample.com.+008+63613.private
+@@ -0,0 +1,13 @@
++Private-key-format: v1.3
++Algorithm: 8 (RSASHA256)
++Modulus: nO6ftth+OYYPBQSfinIkbfi+yJrBExbqkR2g0wfzujPGuI7OAbr/jNOMS/wnJiMZDPaTa9yhZZwP7zrEPkhSp9rWl1ddRTxt2Eb0MG1b8rWZzg/qDyF5T7R/76ar8vhM+VZVlQbyVUfgfyYPqvw+xUru/9BHvAx6D1smkJjLGifYD2tnjNLQn2Rf1t9yJBWHo4MZUQ1y3AQrkU2nm7RWic8QjHfkqP4hWf+Yel9vPgtoLTazsLoqC+fdOgUVIlzPBs5p7GX9379qLwUsmLvxSCICrn/XvxtxUsi0x5jpoPl5AaRG2vZzcln8lHg1/5NABYUGeX01INpEfhBrnlk2qw==
++PublicExponent: AQAB
++PrivateExponent: N4egcDzO/V/YdLgcFAsrpNY9/BH2e+DCA7NuMv4/WgX0LV4quyYGQzigDksdNzt4I8Qkiig53BCK+uXahwdkaAzhng/F6zfkzoDc6z3nKUzlLasn8U6w9Gk0VAKwGXuPETNheShKG68hWxyGssQrGfjX9SEoIPxxPHnOfZ/zTj95KAnVV5qPz90xVAb0+FUrLXAt72KuRwepOTlsETsMFDKe17uUCqCCdX98Ko0u14wrO6zGRQtNhUsfvNB/pY8fvbHD1GcCTbFSx4FxsUsZMrNtMsvMe3HN9ggC0Y9htbH9HV0hS0w9SKCUyoeOVwf/JZL4hlfoe8+jglsyJpAgoQ==
++Prime1: zeXvO3PT4iXv9GlGeebl39pF1sXs8tXY4B9VHUJGGSYlyOlyCEy4URQJIPfuL6VjFKCErSxUJSrGz0HyQuKr8l9qP/0MGxGRH7wxvUR7YTmai84yyQ4fFENRmn8bzxGwj0MVHIW7cKC59j7nWT24gseT21/NP5m8EnPsjz/K40M=
++Prime2: wx5vVFSydUfr8HtOHNS1kRrTjhnQOfjmj8SxGi72Hk+mgi9fBCTC5fRLifd80wGbgyFk1vZOXeStOC8L3IlnBGLX0O9MNip+vVX3hRzIRhLwHhL1ygN3xEd04qwVH0XJ8+4A0XCzh/FJgW59F62geN6gwedo7GmZAOSZUBAyRHk=
++Exponent1: IlkqeLuQ7Fgx2I87b5iiXp62Keco6TXdkT4I3/GvagCgKw0utc2+rd/uye4ycQZhKg7BM3aCrxScx/STaq8PykY6nmQjgdyDXkzx60YiYwzOCGakuD+/1YyJb4Gm7PthffTN780rgNV/UGIcDBoszrxmoSExR1vpMRbfruIQgas=
++Exponent2: or0Os/KUibc79W6Snv9WlLkgPAQRpViQzNaLtD/47R0Xzrs975HNsMgJ/P+bb86Ga1994MC8ahmh1BuBNCax8nmScWQ1V3QoEzjRYoe4DqIa/aposF4mFWJX/fry/wtRPo+CxSzPwJGh8j86PHaHQmjFAhVFcPE+OP1xVdK8alk=
++Coefficient: r5wPmPXUF5pVC0Y7La3jVkL4w/3wvq9LBz91tH9gA8OUNLpDDBuFZISiJdhOZ4JVw+qSSoHcEa+3Phd+BqxmXzwZDU1Fqta9mLDDGCqCWjVQOopeeJgrvkv9P0TIzEuoGmW50cQhyqHYCtuUxjOnHfiQSc53p7rfD4Vom1VQ3Ok=
++Created: 20210423012810
++Publish: 20210423012810
++Activate: 20210423012810
+diff --git a/bin/tests/system/dnssec/signer/general/test9.zone b/bin/tests/system/dnssec/signer/general/test9.zone
+new file mode 100644
+index 0000000..5cfac09
+--- /dev/null
++++ b/bin/tests/system/dnssec/signer/general/test9.zone
+@@ -0,0 +1,17 @@
++; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
++;
++; 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/.
++;
++; See the COPYRIGHT file distributed with this work for additional
++; information regarding copyright ownership.
++
++;	This is a zone which has two DNSKEY records, both of which have
++; existing private key files available.  They should be loaded automatically
++; and the zone correctly signed.
++;
++$TTL 3600
++example.com.	IN	SOA ns hostmaster 00090000 1200 3600 604800 300
++$include Kexample.com.+008+63613.key
++$include Kexample.com.+008+15002.key
+diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh
+index ebbe91b..72af042 100644
+--- a/bin/tests/system/dnssec/tests.sh
++++ b/bin/tests/system/dnssec/tests.sh
+@@ -1313,7 +1313,7 @@ status=$((status+ret))
+ echo_ic "one non-KSK DNSKEY ($n)"
+ ret=0
+ (
+-cd signer/general || exit 1
++cd signer/general || exit 0
+ rm -f signed.zone
+ $SIGNER -f signed.zone -o example.com. test2.zone > signer.out.$n
+ test -f signed.zone
+@@ -1325,7 +1325,7 @@ status=$((status+ret))
+ echo_ic "one KSK DNSKEY ($n)"
+ ret=0
+ (
+-cd signer/general || exit 1
++cd signer/general || exit 0
+ rm -f signed.zone
+ $SIGNER -f signed.zone -o example.com. test3.zone > signer.out.$n
+ test -f signed.zone
+@@ -1373,7 +1373,7 @@ status=$((status+ret))
+ echo_ic "two DNSKEY, both private keys missing ($n)"
+ ret=0
+ (
+-cd signer/general || exit 1
++cd signer/general || exit 0
+ rm -f signed.zone
+ $SIGNER -f signed.zone -o example.com. test7.zone > signer.out.$n
+ test -f signed.zone
+@@ -1385,7 +1385,7 @@ status=$((status+ret))
+ echo_ic "two DNSKEY, one private key missing ($n)"
+ ret=0
+ (
+-cd signer/general || exit 1
++cd signer/general || exit 0
+ rm -f signed.zone
+ $SIGNER -f signed.zone -o example.com. test8.zone > signer.out.$n
+ test -f signed.zone
+@@ -1394,6 +1394,30 @@ n=$((n+1))
+ test "$ret" -eq 0 || echo_i "failed"
+ status=$((status+ret))
+ 
++echo_ic "check that dnssec-signzone rejects excessive NSEC3 iterations ($n)"
++ret=0
++(
++cd signer/general || exit 0
++rm -f signed.zone
++$SIGNER -f signed.zone -3 - -H 151 -o example.com. test9.zone > signer.out.$n
++test -f signed.zone
++) && ret=1
++n=$((n+1))
++test "$ret" -eq 0 || echo_i "failed"
++status=$((status+ret))
++
++echo_ic "check that dnssec-signzone accepts maximum NSEC3 iterations ($n)"
++ret=0
++(
++cd signer/general || exit 1
++rm -f signed.zone
++$SIGNER -f signed.zone -3 - -H 150 -o example.com. test9.zone > signer.out.$n
++test -f signed.zone
++) || ret=1
++n=$((n+1))
++test "$ret" -eq 0 || echo_i "failed"
++status=$((status+ret))
++
+ echo_i "checking that a key using an unsupported algorithm cannot be generated ($n)"
+ ret=0
+ zone=example
diff -Nru bind9-9.16.13/debian/patches/0010-Check-that-excessive-iterations-in-logged-by-named-w.patch bind9-9.16.15/debian/patches/0010-Check-that-excessive-iterations-in-logged-by-named-w.patch
--- bind9-9.16.13/debian/patches/0010-Check-that-excessive-iterations-in-logged-by-named-w.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0010-Check-that-excessive-iterations-in-logged-by-named-w.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,210 @@
+From: Mark Andrews <marka@isc.org>
+Date: Wed, 17 Feb 2021 16:33:49 +1100
+Subject: Check that excessive iterations in logged by named when
+
+loading an existing zone or transfering from the primary.
+---
+ bin/dnssec/dnssec-signzone.c                       | 20 +++++++++++++----
+ bin/tests/system/dnssec/clean.sh                   |  1 +
+ bin/tests/system/dnssec/ns1/root.db.in             |  2 ++
+ bin/tests/system/dnssec/ns1/sign.sh                |  1 +
+ bin/tests/system/dnssec/ns2/named.conf.in          |  5 +++++
+ bin/tests/system/dnssec/ns2/sign.sh                | 11 ++++++++++
+ .../system/dnssec/ns2/too-many-iterations.db.in    | 25 ++++++++++++++++++++++
+ bin/tests/system/dnssec/ns3/named.conf.in          |  6 ++++++
+ bin/tests/system/dnssec/ns4/named4.conf.in         |  1 +
+ bin/tests/system/dnssec/tests.sh                   |  8 +++++++
+ 10 files changed, 76 insertions(+), 4 deletions(-)
+ create mode 100644 bin/tests/system/dnssec/ns2/too-many-iterations.db.in
+
+diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c
+index 26641fa..8de0f98 100644
+--- a/bin/dnssec/dnssec-signzone.c
++++ b/bin/dnssec/dnssec-signzone.c
+@@ -176,8 +176,9 @@ static bool remove_orphansigs = false;
+ static bool remove_inactkeysigs = false;
+ static bool output_dnssec_only = false;
+ static bool output_stdout = false;
+-bool set_maxttl = false;
++static bool set_maxttl = false;
+ static dns_ttl_t maxttl = 0;
++static bool no_max_check = false;
+ 
+ #define INCSTAT(counter)            \
+ 	if (printstats) {           \
+@@ -3437,6 +3438,12 @@ main(int argc, char *argv[]) {
+ 
+ 		case 'H':
+ 			set_iter = true;
++			/* too-many is NOT DOCUMENTED */
++			if (strcmp(isc_commandline_argument, "too-many") == 0) {
++				nsec3iter = 151;
++				no_max_check = true;
++				break;
++			}
+ 			nsec3iter = strtoul(isc_commandline_argument, &endp, 0);
+ 			if (*endp != '\0') {
+ 				fatal("iterations must be numeric");
+@@ -3874,9 +3881,14 @@ main(int argc, char *argv[]) {
+ 		}
+ 
+ 		if (nsec3iter > dns_nsec3_maxiterations()) {
+-			fatal("NSEC3 iterations too big for weakest DNSKEY "
+-			      "strength. Maximum iterations allowed %u.",
+-			      dns_nsec3_maxiterations());
++			if (no_max_check) {
++				fprintf(stderr,
++					"Ignoring max iterations check.\n");
++			} else {
++				fatal("NSEC3 iterations too big. Maximum "
++				      "iterations allowed %u.",
++				      dns_nsec3_maxiterations());
++			}
+ 		}
+ 	} else {
+ 		hashlist_init(&hashlist, 0, 0); /* silence clang */
+diff --git a/bin/tests/system/dnssec/clean.sh b/bin/tests/system/dnssec/clean.sh
+index e660c33..adcb52f 100644
+--- a/bin/tests/system/dnssec/clean.sh
++++ b/bin/tests/system/dnssec/clean.sh
+@@ -25,6 +25,7 @@ rm -f ./canonical?.*
+ rm -f ./delv.out*
+ rm -f ./delve.out*
+ rm -f ./dig.out.*
++rm -f ./ns2/too-many-iterations.db
+ rm -f ./dnssectools.out*
+ rm -f ./dsfromkey.out.*
+ rm -f ./keygen.err
+diff --git a/bin/tests/system/dnssec/ns1/root.db.in b/bin/tests/system/dnssec/ns1/root.db.in
+index 09855f7..9c1296e 100644
+--- a/bin/tests/system/dnssec/ns1/root.db.in
++++ b/bin/tests/system/dnssec/ns1/root.db.in
+@@ -31,3 +31,5 @@ ns6.optout-tld.		A	10.53.0.6
+ in-addr.arpa.		NS	ns2.example.
+ inprogress.		NS	ns10.inprogress.
+ ns10.inprogress.	A	10.53.0.10
++too-many-iterations.	NS	ns2.too-many-iterations.
++ns2.too-many-iterations.	A	10.53.0.2
+diff --git a/bin/tests/system/dnssec/ns1/sign.sh b/bin/tests/system/dnssec/ns1/sign.sh
+index 95888ae..dd5385e 100644
+--- a/bin/tests/system/dnssec/ns1/sign.sh
++++ b/bin/tests/system/dnssec/ns1/sign.sh
+@@ -26,6 +26,7 @@ echo_i "ns1/sign.sh"
+ 
+ cp "../ns2/dsset-example$TP" .
+ cp "../ns2/dsset-in-addr.arpa$TP" .
++cp "../ns2/dsset-too-many-iterations$TP" .
+ 
+ grep "$DEFAULT_ALGORITHM_NUMBER [12] " "../ns2/dsset-algroll$TP" > "dsset-algroll$TP"
+ cp "../ns6/dsset-optout-tld$TP" .
+diff --git a/bin/tests/system/dnssec/ns2/named.conf.in b/bin/tests/system/dnssec/ns2/named.conf.in
+index 4b75918..3928ec0 100644
+--- a/bin/tests/system/dnssec/ns2/named.conf.in
++++ b/bin/tests/system/dnssec/ns2/named.conf.in
+@@ -191,4 +191,9 @@ zone "hours-vs-days" {
+ 	allow-update { any; };
+ };
+ 
++zone "too-many-iterations" {
++	type master;
++	file "too-many-iterations.db.signed";
++};
++
+ include "trusted.conf";
+diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh
+index b255d37..e8b255b 100644
+--- a/bin/tests/system/dnssec/ns2/sign.sh
++++ b/bin/tests/system/dnssec/ns2/sign.sh
+@@ -318,3 +318,14 @@ key1=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone -f KSK "$
+ key2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone")
+ $SETTIME -P sync now "$key1" > /dev/null
+ cat "$infile" > "$zonefile.signed"
++
++#
++# Negative result from this zone should come back as insecure.
++#
++zone=too-many-iterations
++infile=too-many-iterations.db.in
++zonefile=too-many-iterations.db
++key1=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone -f KSK "$zone")
++key2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone")
++cat "$infile" "$key1.key" "$key2.key" > "$zonefile"
++"$SIGNER" -P -3 - -H too-many -g -o "$zone" "$zonefile" > /dev/null 2>&1
+diff --git a/bin/tests/system/dnssec/ns2/too-many-iterations.db.in b/bin/tests/system/dnssec/ns2/too-many-iterations.db.in
+new file mode 100644
+index 0000000..e6f502f
+--- /dev/null
++++ b/bin/tests/system/dnssec/ns2/too-many-iterations.db.in
+@@ -0,0 +1,25 @@
++; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
++;
++; 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/.
++;
++; See the COPYRIGHT file distributed with this work for additional
++; information regarding copyright ownership.
++
++$TTL 30	; 5 minutes
++@			IN SOA	mname1. . (
++				2000042407 ; serial
++				20         ; refresh (20 seconds)
++				20         ; retry (20 seconds)
++				1814400    ; expire (3 weeks)
++				30       ; minimum (1 hour)
++				)
++			NS	ns2
++ns2			A	10.53.0.2
++ns3			A	10.53.0.3
++
++a			A	10.0.0.1
++*.a			A	10.0.0.3
++b			A	10.0.0.2
++d			A	10.0.0.4
+diff --git a/bin/tests/system/dnssec/ns3/named.conf.in b/bin/tests/system/dnssec/ns3/named.conf.in
+index 196a13d..233db0c 100644
+--- a/bin/tests/system/dnssec/ns3/named.conf.in
++++ b/bin/tests/system/dnssec/ns3/named.conf.in
+@@ -363,6 +363,12 @@ zone "revoked.trusted" {
+ 	file "revoked.trusted.db.signed";
+ };
+ 
++zone "too-many-iterations" {
++	type secondary;
++	primaries { 10.53.0.2; };
++	file "too-many-iterations.bk";
++};
++
+ include "siginterval.conf";
+ 
+ include "trusted.conf";
+diff --git a/bin/tests/system/dnssec/ns4/named4.conf.in b/bin/tests/system/dnssec/ns4/named4.conf.in
+index 77590b3..6e5b40a 100644
+--- a/bin/tests/system/dnssec/ns4/named4.conf.in
++++ b/bin/tests/system/dnssec/ns4/named4.conf.in
+@@ -42,6 +42,7 @@ view rec {
+ 	recursion yes;
+ 	dnssec-validation yes;
+ 	dnssec-accept-expired yes;
++	minimal-responses no;
+ 
+ 	zone "." {
+ 		type hint;
+diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh
+index 72af042..2a8aab9 100644
+--- a/bin/tests/system/dnssec/tests.sh
++++ b/bin/tests/system/dnssec/tests.sh
+@@ -4298,5 +4298,13 @@ n=$((n+1))
+ test "$ret" -eq 0 || echo_i "failed"
+ status=$((status+ret))
+ 
++echo_i "checking excessive NSEC3 iteration warnings in named.run ($n)"
++ret=0
++grep "zone too-many-iterations/IN: excessive NSEC3PARAM iterations [0-9]* > 150" ns2/named.run >/dev/null 2>&1 || ret=1
++grep "zone too-many-iterations/IN: excessive NSEC3PARAM iterations [0-9]* > 150" ns3/named.run >/dev/null 2>&1 || ret=1
++n=$((n+1))
++test "$ret" -eq 0 || echo_i "failed"
++status=$((status+ret))
++
+ echo_i "exit status: $status"
+ [ $status -eq 0 ] || exit 1
diff -Nru bind9-9.16.13/debian/patches/0011-dnssec-policy-reduce-NSEC3-iterations-to-150.patch bind9-9.16.15/debian/patches/0011-dnssec-policy-reduce-NSEC3-iterations-to-150.patch
--- bind9-9.16.13/debian/patches/0011-dnssec-policy-reduce-NSEC3-iterations-to-150.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0011-dnssec-policy-reduce-NSEC3-iterations-to-150.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,141 @@
+From: Matthijs Mekking <matthijs@isc.org>
+Date: Thu, 29 Apr 2021 09:54:51 +0200
+Subject: dnssec-policy: reduce NSEC3 iterations to 150
+
+When reducing the number of NSEC3 iterations to 150, commit
+aa26cde2aea459d682f6f609a7c902ef9a7a35eb added tests for dnssec-policy
+to check that a too high iteration count is a configuration failure.
+
+The test is not sufficient because 151 was always too high for
+ECDSAP256SHA256. The test should check for a different algorithm.
+
+There was an existing test case that checks for NSEC3 iterations.
+Update the test with the new maximum values.
+
+Update the code in 'kaspconf.c' to allow at most 150 iterations.
+---
+ bin/tests/system/checkconf/bad-kasp-iterations.conf  | 14 --------------
+ bin/tests/system/checkconf/good-kasp-iterations.conf | 14 --------------
+ bin/tests/system/checkconf/kasp-bad-nsec3-iter.conf  |  8 ++++----
+ bin/tests/system/checkconf/tests.sh                  |  2 --
+ lib/isccfg/kaspconf.c                                |  8 ++------
+ 5 files changed, 6 insertions(+), 40 deletions(-)
+ delete mode 100644 bin/tests/system/checkconf/bad-kasp-iterations.conf
+ delete mode 100644 bin/tests/system/checkconf/good-kasp-iterations.conf
+
+diff --git a/bin/tests/system/checkconf/bad-kasp-iterations.conf b/bin/tests/system/checkconf/bad-kasp-iterations.conf
+deleted file mode 100644
+index 041ca76..0000000
+--- a/bin/tests/system/checkconf/bad-kasp-iterations.conf
++++ /dev/null
+@@ -1,14 +0,0 @@
+-/*
+- * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+- *
+- * 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/.
+- *
+- * See the COPYRIGHT file distributed with this work for additional
+- * information regarding copyright ownership.
+- */
+-
+-dnssec-policy too-many-iterations {
+-	nsec3param iterations 151;
+-};
+diff --git a/bin/tests/system/checkconf/good-kasp-iterations.conf b/bin/tests/system/checkconf/good-kasp-iterations.conf
+deleted file mode 100644
+index e197957..0000000
+--- a/bin/tests/system/checkconf/good-kasp-iterations.conf
++++ /dev/null
+@@ -1,14 +0,0 @@
+-/*
+- * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+- *
+- * 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/.
+- *
+- * See the COPYRIGHT file distributed with this work for additional
+- * information regarding copyright ownership.
+- */
+-
+-dnssec-policy max-iterations {
+-	nsec3param iterations 150;
+-};
+diff --git a/bin/tests/system/checkconf/kasp-bad-nsec3-iter.conf b/bin/tests/system/checkconf/kasp-bad-nsec3-iter.conf
+index 49874e2..5d6e726 100644
+--- a/bin/tests/system/checkconf/kasp-bad-nsec3-iter.conf
++++ b/bin/tests/system/checkconf/kasp-bad-nsec3-iter.conf
+@@ -27,28 +27,28 @@ dnssec-policy "rsasha256" {
+ 	keys {
+ 		csk lifetime P10Y algorithm rsasha256 2048;
+ 	};
+-	nsec3param iterations 500;
++	nsec3param iterations 150;
+ };
+ 
+ dnssec-policy "rsasha256-bad" {
+ 	keys {
+ 		csk lifetime P10Y algorithm rsasha256 2048;
+ 	};
+-	nsec3param iterations 501;
++	nsec3param iterations 151;
+ };
+ 
+ dnssec-policy "rsasha512" {
+ 	keys {
+ 		csk lifetime P10Y algorithm rsasha512 4096;
+ 	};
+-	nsec3param iterations 2500;
++	nsec3param iterations 150;
+ };
+ 
+ dnssec-policy "rsasha512-bad" {
+ 	keys {
+ 		csk lifetime P10Y algorithm rsasha512 4096;
+ 	};
+-	nsec3param iterations 2501;
++	nsec3param iterations 151;
+ };
+ 
+ zone "example.net" {
+diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh
+index 19baf1c..4864e4c 100644
+--- a/bin/tests/system/checkconf/tests.sh
++++ b/bin/tests/system/checkconf/tests.sh
+@@ -514,8 +514,6 @@ echo_i "checking named-checkconf kasp nsec3 iterations errors ($n)"
+ ret=0
+ $CHECKCONF kasp-bad-nsec3-iter.conf > checkconf.out$n 2>&1 && ret=1
+ grep "dnssec-policy: nsec3 iterations value 151 out of range" < checkconf.out$n > /dev/null || ret=1
+-grep "dnssec-policy: nsec3 iterations value 501 out of range" < checkconf.out$n > /dev/null || ret=1
+-grep "dnssec-policy: nsec3 iterations value 2501 out of range" < checkconf.out$n > /dev/null || ret=1
+ lines=$(wc -l < "checkconf.out$n")
+ if [ $lines != 3 ]; then ret=1; fi
+ if [ $ret != 0 ]; then echo_i "failed"; fi
+diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c
+index b8fa1f8..5b45847 100644
+--- a/lib/isccfg/kaspconf.c
++++ b/lib/isccfg/kaspconf.c
+@@ -22,6 +22,7 @@
+ #include <dns/kasp.h>
+ #include <dns/keyvalues.h>
+ #include <dns/log.h>
++#include <dns/nsec3.h>
+ #include <dns/result.h>
+ #include <dns/secalg.h>
+ 
+@@ -213,12 +214,7 @@ cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
+ 		return (DNS_R_NSEC3BADALG);
+ 	}
+ 
+-	/* See RFC 5155 Section 10.3 for iteration limits. */
+-	if (min_keysize <= 1024 && iter > 150) {
+-		ret = DNS_R_NSEC3ITERRANGE;
+-	} else if (min_keysize <= 2048 && iter > 500) {
+-		ret = DNS_R_NSEC3ITERRANGE;
+-	} else if (min_keysize <= 4096 && iter > 2500) {
++	if (iter > dns_nsec3_maxiterations()) {
+ 		ret = DNS_R_NSEC3ITERRANGE;
+ 	}
+ 
diff -Nru bind9-9.16.13/debian/patches/0012-Mark-DNSSEC-responses-with-NSEC3-records-that-exceed.patch bind9-9.16.15/debian/patches/0012-Mark-DNSSEC-responses-with-NSEC3-records-that-exceed.patch
--- bind9-9.16.13/debian/patches/0012-Mark-DNSSEC-responses-with-NSEC3-records-that-exceed.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0012-Mark-DNSSEC-responses-with-NSEC3-records-that-exceed.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,110 @@
+From: Mark Andrews <marka@isc.org>
+Date: Wed, 17 Feb 2021 16:33:49 +1100
+Subject: Mark DNSSEC responses with NSEC3 records that exceed 150 as insecure
+
+---
+ lib/dns/include/dns/nsec3.h |  1 +
+ lib/dns/nsec3.c             |  9 ++++++++-
+ lib/dns/validator.c         | 36 ++++++++++++++++++++++++++++++++++--
+ 3 files changed, 43 insertions(+), 3 deletions(-)
+
+diff --git a/lib/dns/include/dns/nsec3.h b/lib/dns/include/dns/nsec3.h
+index 2f178b6..2c9bb75 100644
+--- a/lib/dns/include/dns/nsec3.h
++++ b/lib/dns/include/dns/nsec3.h
+@@ -25,6 +25,7 @@
+ #include <dns/types.h>
+ 
+ #define DNS_NSEC3_SALTSIZE 255
++#define DNS_NSEC3_MAXITERATIONS 150U
+ 
+ /*
+  * hash = 1, flags =1, iterations = 2, salt length = 1, salt = 255 (max)
+diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c
+index b5df23e..7ff9e8d 100644
+--- a/lib/dns/nsec3.c
++++ b/lib/dns/nsec3.c
+@@ -1880,7 +1880,7 @@ try_private:
+ 
+ unsigned int
+ dns_nsec3_maxiterations(void) {
+-	return (150);
++	return (DNS_NSEC3_MAXITERATIONS);
+ }
+ 
+ isc_result_t
+@@ -2022,6 +2022,13 @@ dns_nsec3_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
+ 	first = true;
+ 
+ 	while (qlabels >= zlabels) {
++		/*
++		 * If there are too many iterations reject the NSEC3 record.
++		 */
++		if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) {
++			return (DNS_R_NSEC3ITERRANGE);
++		}
++
+ 		length = isc_iterated_hash(hash, nsec3.hash, nsec3.iterations,
+ 					   nsec3.salt, nsec3.salt_length,
+ 					   qname->ndata, qname->length);
+diff --git a/lib/dns/validator.c b/lib/dns/validator.c
+index 27adb5a..e54fc70 100644
+--- a/lib/dns/validator.c
++++ b/lib/dns/validator.c
+@@ -2245,6 +2245,26 @@ findnsec3proofs(dns_validator_t *val) {
+ 		if (unknown) {
+ 			val->attributes |= VALATTR_FOUNDUNKNOWN;
+ 		}
++		if (result == DNS_R_NSEC3ITERRANGE) {
++			/*
++			 * We don't really know which NSEC3 record provides
++			 * which proof.  Just populate them.
++			 */
++			if (NEEDNOQNAME(val) &&
++			    proofs[DNS_VALIDATOR_NOQNAMEPROOF] == NULL) {
++				proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name;
++			} else if (setclosest) {
++				proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name;
++			} else if (NEEDNODATA(val) &&
++				   proofs[DNS_VALIDATOR_NODATAPROOF] == NULL) {
++				proofs[DNS_VALIDATOR_NODATAPROOF] = name;
++			} else if (NEEDNOWILDCARD(val) &&
++				   proofs[DNS_VALIDATOR_NOWILDCARDPROOF] ==
++					   NULL) {
++				proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name;
++			}
++			return (result);
++		}
+ 		if (result != ISC_R_SUCCESS) {
+ 			continue;
+ 		}
+@@ -2501,7 +2521,13 @@ validate_nx(dns_validator_t *val, bool resume) {
+ 	 */
+ 	if (!NEEDNODATA(val) && !NEEDNOWILDCARD(val) && NEEDNOQNAME(val)) {
+ 		if (!FOUNDNOQNAME(val)) {
+-			findnsec3proofs(val);
++			result = findnsec3proofs(val);
++			if (result == DNS_R_NSEC3ITERRANGE) {
++				validator_log(val, ISC_LOG_DEBUG(3),
++					      "too many iterations");
++				markanswer(val, "validate_nx (3)", NULL);
++				return (ISC_R_SUCCESS);
++			}
+ 		}
+ 
+ 		if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && !FOUNDOPTOUT(val))
+@@ -2531,7 +2557,13 @@ validate_nx(dns_validator_t *val, bool resume) {
+ 	}
+ 
+ 	if (!FOUNDNOQNAME(val) && !FOUNDNODATA(val)) {
+-		findnsec3proofs(val);
++		result = findnsec3proofs(val);
++		if (result == DNS_R_NSEC3ITERRANGE) {
++			validator_log(val, ISC_LOG_DEBUG(3),
++				      "too many iterations");
++			markanswer(val, "validate_nx (4)", NULL);
++			return (ISC_R_SUCCESS);
++		}
+ 	}
+ 
+ 	/*
diff -Nru bind9-9.16.13/debian/patches/0013-Check-insecure-responses-returned-with-too-many-NSEC.patch bind9-9.16.15/debian/patches/0013-Check-insecure-responses-returned-with-too-many-NSEC.patch
--- bind9-9.16.13/debian/patches/0013-Check-insecure-responses-returned-with-too-many-NSEC.patch	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/debian/patches/0013-Check-insecure-responses-returned-with-too-many-NSEC.patch	2021-04-29 09:11:32.000000000 +0200
@@ -0,0 +1,69 @@
+From: Mark Andrews <marka@isc.org>
+Date: Wed, 17 Feb 2021 16:33:49 +1100
+Subject: Check insecure responses returned with too many NSEC3 iterations
+
+---
+ bin/tests/system/dnssec/tests.sh | 51 ++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 51 insertions(+)
+
+diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh
+index 2a8aab9..626b0b8 100644
+--- a/bin/tests/system/dnssec/tests.sh
++++ b/bin/tests/system/dnssec/tests.sh
+@@ -4306,5 +4306,56 @@ n=$((n+1))
+ test "$ret" -eq 0 || echo_i "failed"
+ status=$((status+ret))
+ 
++# Check that the validating resolver will fallback to insecure if the answer
++# contains NSEC3 records with high iteration count.
++echo_i "checking fallback to insecure when NSEC3 iterations is too high (nxdomain) ($n)"
++ret=0
++dig_with_opts @10.53.0.2 does-not-exist.too-many-iterations > dig.out.ns2.test$n || ret=1
++dig_with_opts @10.53.0.4 does-not-exist.too-many-iterations > dig.out.ns4.test$n || ret=1
++digcomp dig.out.ns2.test$n dig.out.ns4.test$n || ret=1
++grep "flags: qr rd ra;" dig.out.ns4.test$n >/dev/null || ret=1
++grep "status: NXDOMAIN" dig.out.ns4.test$n >/dev/null || ret=1
++grep "ANSWER: 0, AUTHORITY: 6" dig.out.ns4.test$n > /dev/null || ret=1
++n=$((n+1))
++test "$ret" -eq 0 || echo_i "failed"
++status=$((status+ret))
++
++echo_i "checking fallback to insecure when NSEC3 iterations is too high (nodata) ($n)"
++ret=0
++dig_with_opts @10.53.0.2 a.too-many-iterations txt > dig.out.ns2.test$n || ret=1
++dig_with_opts @10.53.0.4 a.too-many-iterations txt > dig.out.ns4.test$n || ret=1
++digcomp dig.out.ns2.test$n dig.out.ns4.test$n || ret=1
++grep "flags: qr rd ra;" dig.out.ns4.test$n >/dev/null || ret=1
++grep "status: NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
++grep "ANSWER: 0, AUTHORITY: 4" dig.out.ns4.test$n > /dev/null || ret=1
++n=$((n+1))
++test "$ret" -eq 0 || echo_i "failed"
++status=$((status+ret))
++
++echo_i "checking fallback to insecure when NSEC3 iterations is too high (wildcard) ($n)"
++ret=0
++dig_with_opts @10.53.0.2 wild.a.too-many-iterations > dig.out.ns2.test$n || ret=1
++dig_with_opts @10.53.0.4 wild.a.too-many-iterations > dig.out.ns4.test$n || ret=1
++digcomp dig.out.ns2.test$n dig.out.ns4.test$n || ret=1
++grep "flags: qr rd ra;" dig.out.ns4.test$n >/dev/null || ret=1
++grep "status: NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
++grep 'wild\.a\.too-many-iterations\..*A.10\.0\.0\.3' dig.out.ns4.test$n >/dev/null || ret=1
++grep "ANSWER: 2, AUTHORITY: 4" dig.out.ns4.test$n > /dev/null || ret=1
++n=$((n+1))
++test "$ret" -eq 0 || echo_i "failed"
++status=$((status+ret))
++
++echo_i "checking fallback to insecure when NSEC3 iterations is too high (wildcard nodata) ($n)"
++ret=0
++dig_with_opts @10.53.0.2 type100 wild.a.too-many-iterations > dig.out.ns2.test$n || ret=1
++dig_with_opts @10.53.0.4 type100 wild.a.too-many-iterations > dig.out.ns4.test$n || ret=1
++digcomp dig.out.ns2.test$n dig.out.ns4.test$n || ret=1
++grep "flags: qr rd ra;" dig.out.ns4.test$n >/dev/null || ret=1
++grep "status: NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
++grep "ANSWER: 0, AUTHORITY: 8" dig.out.ns4.test$n > /dev/null || ret=1
++n=$((n+1))
++test "$ret" -eq 0 || echo_i "failed"
++status=$((status+ret))
++
+ echo_i "exit status: $status"
+ [ $status -eq 0 ] || exit 1
diff -Nru bind9-9.16.13/debian/patches/series bind9-9.16.15/debian/patches/series
--- bind9-9.16.13/debian/patches/series	2021-03-18 14:23:49.000000000 +0100
+++ bind9-9.16.15/debian/patches/series	2021-04-29 09:11:32.000000000 +0200
@@ -1,7 +1,13 @@
 0001-Add_--install-layout=deb_to_setup.py_call.patch
 0002-python-fix-for-dist-packages.patch
 0003-Add-patch-to-fix-sphinx-build-failure-on-Ubuntu-Xeni.patch
-0004-Add-TCP-timeouts-system-test.patch
-0005-Fix-TCPDNS-and-TLSDNS-timers.patch
-0006-Merge-the-common-parts-between-udp-tcpdns-and-tlsdns.patch
-0007-Change-the-isc_nm_-get-set-timeouts-to-work-with-mil.patch
+0004-Reduce-nsec3-max-iterations-to-150.patch
+0005-Check-dnssec-policy-nsec3param-iterations-limit.patch
+0006-Warn-if-there-is-excessive-NSEC3-iterations.patch
+0007-nsupdate-reject-attempts-to-add-NSEC3PARAM-with-exce.patch
+0008-Check-that-named-rejects-excessive-iterations-via-UP.patch
+0009-Check-NSEC3-iterations-with-dnssec-signzone.patch
+0010-Check-that-excessive-iterations-in-logged-by-named-w.patch
+0011-dnssec-policy-reduce-NSEC3-iterations-to-150.patch
+0012-Mark-DNSSEC-responses-with-NSEC3-records-that-exceed.patch
+0013-Check-insecure-responses-returned-with-too-many-NSEC.patch
diff -Nru bind9-9.16.13/doc/arm/notes.rst bind9-9.16.15/doc/arm/notes.rst
--- bind9-9.16.13/doc/arm/notes.rst	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/doc/arm/notes.rst	2021-04-19 15:45:50.000000000 +0200
@@ -59,6 +59,8 @@
 information about each release, source code, and pre-compiled versions
 for Microsoft Windows operating systems.
 
+.. include:: ../notes/notes-9.16.15.rst
+.. include:: ../notes/notes-9.16.14.rst
 .. include:: ../notes/notes-9.16.13.rst
 .. include:: ../notes/notes-9.16.12.rst
 .. include:: ../notes/notes-9.16.11.rst
diff -Nru bind9-9.16.13/doc/arm/reference.rst bind9-9.16.15/doc/arm/reference.rst
--- bind9-9.16.13/doc/arm/reference.rst	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/doc/arm/reference.rst	2021-04-19 15:45:50.000000000 +0200
@@ -1847,18 +1847,21 @@
    log category.
 
 ``stale-answer-client-timeout``
-   This option defines the amount of time ``named`` waits before attempting to
-   answer the query with a stale RRset from cache. If a stale answer is found,
-   ``named`` continues the ongoing fetches, attempting to refresh the RRset in
-   cache until the ``resolver-query-timeout`` interval is reached.
+   This option defines the amount of time (in milliseconds) that ``named``
+   waits before attempting to answer the query with a stale RRset from cache.
+   If a stale answer is found, ``named`` continues the ongoing fetches,
+   attempting to refresh the RRset in cache until the
+   ``resolver-query-timeout`` interval is reached.
 
-   The default value is ``1800`` (in milliseconds) and the maximum value is
-   bounded to ``resolver-query-timeout`` minus one second. A value of ``0``
-   immediately returns a cached RRset if available, and still attempts a refresh
-   of the data in cache.
+   This option is off by default, which is equivalent to setting it to
+   ``off`` or ``disabled``. It also has no effect if ``stale-answer-enable``
+   is disabled.
 
-   The option can be disabled by setting the value to ``off`` or ``disabled``.
-   It also has no effect if ``stale-answer-enable`` is disabled.
+   The maximum value for this option is ``resolver-query-timeout`` minus
+   one second. The minimum value, ``0``, causes a cached RRset to be
+   immediately returned if it is available while still attempting to
+   refresh the data in cache. :rfc:`8767` recommends a value of ``1800``
+   (milliseconds).
 
 ``stale-cache-enable``
    If ``yes``, enable the retaining of "stale" cached answers.  Default ``yes``.
diff -Nru bind9-9.16.13/doc/dnssec-guide/signing.rst bind9-9.16.15/doc/dnssec-guide/signing.rst
--- bind9-9.16.13/doc/dnssec-guide/signing.rst	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/doc/dnssec-guide/signing.rst	2021-04-19 15:45:50.000000000 +0200
@@ -736,7 +736,7 @@
 
    dnssec-policy standard {
        dnskey-ttl 600;
-       key {
+       keys {
            ksk lifetime 365d algorithm ecdsap256sha256;
            zsk lifetime 60d algorithm ecdsap256sha256;
        };
diff -Nru bind9-9.16.13/doc/man/dig.1in bind9-9.16.15/doc/man/dig.1in
--- bind9-9.16.13/doc/man/dig.1in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/doc/man/dig.1in	2021-04-19 15:45:50.000000000 +0200
@@ -461,8 +461,8 @@
 \fB+nssearch\fP or \fB+trace\fP query option is used.
 .TP
 .B \fB+retry=T\fP
-This option sets the number of times to retry UDP queries to server to \fBT\fP
-instead of the default, 2. Unlike \fB+tries\fP, this does not include
+This option sets the number of times to retry UDP and TCP queries to server to \fBT\fP
+instead of the default, 2.  Unlike \fB+tries\fP, this does not include
 the initial query.
 .TP
 .B \fB+[no]rrcomments\fP
@@ -544,7 +544,7 @@
 default queries from a name server.
 .TP
 .B \fB+tries=T\fP
-This option sets the number of times to try UDP queries to server to \fBT\fP
+This option sets the number of times to try UDP and TCP queries to server to \fBT\fP
 instead of the default, 3. If \fBT\fP is less than or equal to zero,
 the number of tries is silently rounded up to 1.
 .TP
diff -Nru bind9-9.16.13/doc/notes/notes-9.16.14.rst bind9-9.16.15/doc/notes/notes-9.16.14.rst
--- bind9-9.16.13/doc/notes/notes-9.16.14.rst	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/doc/notes/notes-9.16.14.rst	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,18 @@
+.. 
+   Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+   
+   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 https://mozilla.org/MPL/2.0/.
+   
+   See the COPYRIGHT file distributed with this work for additional
+   information regarding copyright ownership.
+
+Notes for BIND 9.16.14
+----------------------
+
+.. note::
+
+   The BIND 9.16.14 release was withdrawn after a backporting bug was
+   discovered during pre-release testing. ISC would like to acknowledge
+   the assistance of Natan Segal of Bluecat Networks.
diff -Nru bind9-9.16.13/doc/notes/notes-9.16.15.rst bind9-9.16.15/doc/notes/notes-9.16.15.rst
--- bind9-9.16.13/doc/notes/notes-9.16.15.rst	1970-01-01 01:00:00.000000000 +0100
+++ bind9-9.16.15/doc/notes/notes-9.16.15.rst	2021-04-19 15:45:50.000000000 +0200
@@ -0,0 +1,104 @@
+.. 
+   Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+   
+   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 https://mozilla.org/MPL/2.0/.
+   
+   See the COPYRIGHT file distributed with this work for additional
+   information regarding copyright ownership.
+
+Notes for BIND 9.16.15
+----------------------
+
+Security Fixes
+~~~~~~~~~~~~~~
+
+- A malformed incoming IXFR transfer could trigger an assertion failure
+  in ``named``, causing it to quit abnormally. (CVE-2021-25214)
+
+  ISC would like to thank Greg Kuechle of SaskTel for bringing this
+  vulnerability to our attention. [GL #2467]
+
+- ``named`` crashed when a DNAME record placed in the ANSWER section
+  during DNAME chasing turned out to be the final answer to a client
+  query. (CVE-2021-25215)
+
+  ISC would like to thank `Siva Kakarla`_ for bringing this
+  vulnerability to our attention. [GL #2540]
+
+.. _Siva Kakarla: https://github.com/sivakesava1
+
+- When a server's configuration set the ``tkey-gssapi-keytab`` or
+  ``tkey-gssapi-credential`` option, a specially crafted GSS-TSIG query
+  could cause a buffer overflow in the ISC implementation of SPNEGO (a
+  protocol enabling negotiation of the security mechanism used for
+  GSSAPI authentication). This flaw could be exploited to crash
+  ``named`` binaries compiled for 64-bit platforms, and could enable
+  remote code execution when ``named`` was compiled for 32-bit
+  platforms. (CVE-2021-25216)
+
+  This vulnerability was reported to us as ZDI-CAN-13347 by Trend Micro
+  Zero Day Initiative. [GL #2604]
+
+Feature Changes
+~~~~~~~~~~~~~~~
+
+- The ISC implementation of SPNEGO was removed from BIND 9 source code.
+  Instead, BIND 9 now always uses the SPNEGO implementation provided by
+  the system GSSAPI library when it is built with GSSAPI support. All
+  major contemporary Kerberos/GSSAPI libraries contain an implementation
+  of the SPNEGO mechanism. [GL #2607]
+
+- The default value for the ``stale-answer-client-timeout`` option was
+  changed from ``1800`` (ms) to ``off``. The default value may be
+  changed again in future releases as this feature matures. [GL #2608]
+
+Bug Fixes
+~~~~~~~~~
+
+- TCP idle and initial timeouts were being incorrectly applied: only the
+  ``tcp-initial-timeout`` was applied on the whole connection, even if
+  the connection were still active, which could prevent a large zone
+  transfer from being sent back to the client. The default setting for
+  ``tcp-initial-timeout`` was 30 seconds, which meant that any TCP
+  connection taking more than 30 seconds was abruptly terminated. This
+  has been fixed. [GL #2583]
+
+- When ``stale-answer-client-timeout`` was set to a positive value and
+  recursion for a client query completed when ``named`` was about to
+  look for a stale answer, an assertion could fail in
+  ``query_respond()``, resulting in a crash. This has been fixed.
+  [GL #2594]
+
+- If zone journal files written by BIND 9.16.11 or earlier were present
+  when BIND was upgraded to BIND 9.16.13 or BIND 9.16.14, the zone file
+  for that zone could have been inadvertently rewritten with the current
+  zone contents. This caused the original zone file structure (e.g.
+  comments, ``$INCLUDE`` directives) to be lost, although the zone data
+  itself was preserved. [GL #2623]
+
+- After upgrading to BIND 9.16.13, journal files for trust anchor
+  databases (e.g. ``managed-keys.bind.jnl``) could be left in a corrupt
+  state. (Other zone journal files were not affected.) This has been
+  fixed. If a corrupt journal file is detected, ``named`` can now
+  recover from it. [GL #2600]
+
+- When sending queries over TCP, ``dig`` now properly handles ``+tries=1
+  +retry=0`` by not retrying the connection when the remote server
+  closes the connection prematurely. [GL #2490]
+
+- CDS/CDNSKEY DELETE records are now removed when a zone transitions
+  from a secure to an insecure state. ``named-checkzone`` also no longer
+  reports an error when such records are found in an unsigned zone.
+  [GL #2517]
+
+- Zones using KASP could not be thawed after they were frozen using
+  ``rndc freeze``. This has been fixed. [GL #2523]
+
+- After ``rndc checkds -checkds`` or ``rndc dnssec -rollover`` is used,
+  ``named`` now immediately attempts to reconfigure zone keys. This
+  change prevents unnecessary key rollover delays. [GL #2488]
+
+- Previously, a memory leak could occur when ``named`` failed to bind a
+  UDP socket to a network interface. This has been fixed. [GL #2575]
diff -Nru bind9-9.16.13/.gitlab-ci.yml bind9-9.16.15/.gitlab-ci.yml
--- bind9-9.16.13/.gitlab-ci.yml	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/.gitlab-ci.yml	2021-04-19 15:45:50.000000000 +0200
@@ -515,6 +515,7 @@
   only:
     - schedules
     - tags
+    - web
 
 # Jobs for doc builds on Debian 10 "buster" (amd64)
 
@@ -773,6 +774,7 @@
   only:
     - schedules
     - tags
+    - web
 
 system:gcc:tarball:
   <<: *base_image
@@ -786,6 +788,7 @@
   only:
     - schedules
     - tags
+    - web
 
 unit:gcc:tarball:
   <<: *base_image
@@ -798,6 +801,7 @@
   only:
     - schedules
     - tags
+    - web
 
 # Jobs for regular GCC builds on Debian "sid" (i386)
 
diff -Nru bind9-9.16.13/lib/dns/dst_internal.h bind9-9.16.15/lib/dns/dst_internal.h
--- bind9-9.16.13/lib/dns/dst_internal.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/dst_internal.h	2021-04-19 15:45:50.000000000 +0200
@@ -66,9 +66,6 @@
 #include ISC_PLATFORM_GSSAPI_KRB5_HEADER
 #endif /* ifdef ISC_PLATFORM_GSSAPI_KRB5_HEADER */
 #endif /* ifdef WIN32 */
-#ifndef GSS_SPNEGO_MECHANISM
-#define GSS_SPNEGO_MECHANISM ((void *)0)
-#endif /* ifndef GSS_SPNEGO_MECHANISM */
 #endif /* ifdef GSSAPI */
 
 ISC_LANG_BEGINDECLS
diff -Nru bind9-9.16.13/lib/dns/gssapictx.c bind9-9.16.15/lib/dns/gssapictx.c
--- bind9-9.16.13/lib/dns/gssapictx.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/gssapictx.c	2021-04-19 15:45:50.000000000 +0200
@@ -43,16 +43,6 @@
 #include "dst_internal.h"
 
 /*
- * If we're using our own SPNEGO implementation (see configure.in),
- * pull it in now.  Otherwise, we just use whatever GSSAPI supplies.
- */
-#if defined(GSSAPI) && defined(USE_ISC_SPNEGO)
-#include "spnego.h"
-#define gss_accept_sec_context gss_accept_sec_context_spnego
-#define gss_init_sec_context   gss_init_sec_context_spnego
-#endif /* if defined(GSSAPI) && defined(USE_ISC_SPNEGO) */
-
-/*
  * Solaris8 apparently needs an explicit OID set, and Solaris10 needs
  * one for anything but Kerberos.  Supplying an explicit OID set
  * doesn't appear to hurt anything in other implementations, so we
@@ -66,26 +56,23 @@
 #include ISC_PLATFORM_KRB5HEADER
 #endif /* ifdef WIN32 */
 
+#ifndef GSS_KRB5_MECHANISM
 static unsigned char krb5_mech_oid_bytes[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7,
 					       0x12, 0x01, 0x02, 0x02 };
+static gss_OID_desc __gss_krb5_mechanism_oid_desc = {
+	sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes
+};
+#define GSS_KRB5_MECHANISM (&__gss_krb5_mechanism_oid_desc)
+#endif /* ifndef GSS_KRB5_MECHANISM */
 
-#ifndef USE_ISC_SPNEGO
+#ifndef GSS_SPNEGO_MECHANISM
 static unsigned char spnego_mech_oid_bytes[] = { 0x2b, 0x06, 0x01,
 						 0x05, 0x05, 0x02 };
-#endif /* ifndef USE_ISC_SPNEGO */
-
-static gss_OID_desc mech_oid_set_array[] = {
-	{ sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes },
-#ifndef USE_ISC_SPNEGO
-	{ sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes },
-#endif /* ifndef USE_ISC_SPNEGO */
+static gss_OID_desc __gss_spnego_mechanism_oid_desc = {
+	sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes
 };
-
-static gss_OID_set_desc mech_oid_set = { sizeof(mech_oid_set_array) /
-						 sizeof(*mech_oid_set_array),
-					 mech_oid_set_array };
-
-#endif /* ifdef GSSAPI */
+#define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc)
+#endif /* ifndef GSS_SPNEGO_MECHANISM */
 
 #define REGION_TO_GBUFFER(r, gb)          \
 	do {                              \
@@ -106,7 +93,6 @@
 			goto out;            \
 	} while (0)
 
-#ifdef GSSAPI
 static inline void
 name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer,
 		gss_buffer_desc *gbuffer) {
@@ -188,9 +174,7 @@
 			gss_error_tostring(gret, minor, buf, sizeof(buf)));
 	}
 }
-#endif /* ifdef GSSAPI */
 
-#ifdef GSSAPI
 /*
  * check for the most common configuration errors.
  *
@@ -244,12 +228,43 @@
 	}
 	krb5_free_context(krb5_ctx);
 }
-#endif /* ifdef GSSAPI */
+
+static OM_uint32
+mech_oid_set_create(OM_uint32 *minor, gss_OID_set *mech_oid_set) {
+	OM_uint32 gret;
+
+	gret = gss_create_empty_oid_set(minor, mech_oid_set);
+	if (gret != GSS_S_COMPLETE) {
+		return (gret);
+	}
+
+	gret = gss_add_oid_set_member(minor, GSS_KRB5_MECHANISM, mech_oid_set);
+	if (gret != GSS_S_COMPLETE) {
+		goto release;
+	}
+
+	gret = gss_add_oid_set_member(minor, GSS_SPNEGO_MECHANISM,
+				      mech_oid_set);
+	if (gret != GSS_S_COMPLETE) {
+		goto release;
+	}
+
+release:
+	REQUIRE(gss_release_oid_set(minor, mech_oid_set) == GSS_S_COMPLETE);
+
+	return (gret);
+}
+
+static void
+mech_oid_set_release(gss_OID_set *mech_oid_set) {
+	OM_uint32 minor;
+
+	REQUIRE(gss_release_oid_set(&minor, mech_oid_set) == GSS_S_COMPLETE);
+}
 
 isc_result_t
 dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
 		       dns_gss_cred_id_t *cred) {
-#ifdef GSSAPI
 	isc_result_t result;
 	isc_buffer_t namebuf;
 	gss_name_t gname;
@@ -259,6 +274,7 @@
 	OM_uint32 lifetime;
 	gss_cred_usage_t usage;
 	char buf[1024];
+	gss_OID_set mech_oid_set = NULL;
 
 	REQUIRE(cred != NULL && *cred == NULL);
 
@@ -302,7 +318,14 @@
 		usage = GSS_C_ACCEPT;
 	}
 
-	gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE, &mech_oid_set,
+	gret = mech_oid_set_create(&minor, &mech_oid_set);
+	if (gret != GSS_S_COMPLETE) {
+		gss_log(3, "failed to create OID_set: %s",
+			gss_error_tostring(gret, minor, buf, sizeof(buf)));
+		return (ISC_R_FAILURE);
+	}
+
+	gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE, mech_oid_set,
 				usage, (gss_cred_id_t *)cred, NULL, &lifetime);
 
 	if (gret != GSS_S_COMPLETE) {
@@ -325,6 +348,8 @@
 	result = ISC_R_SUCCESS;
 
 cleanup:
+	mech_oid_set_release(&mech_oid_set);
+
 	if (gname != NULL) {
 		gret = gss_release_name(&minor, &gname);
 		if (gret != GSS_S_COMPLETE) {
@@ -335,22 +360,12 @@
 	}
 
 	return (result);
-#else  /* ifdef GSSAPI */
-	REQUIRE(cred != NULL && *cred == NULL);
-
-	UNUSED(name);
-	UNUSED(initiate);
-	UNUSED(cred);
-
-	return (ISC_R_NOTIMPLEMENTED);
-#endif /* ifdef GSSAPI */
 }
 
 bool
 dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
 				    const dns_name_t *name,
 				    const dns_name_t *realm, bool subdomain) {
-#ifdef GSSAPI
 	char sbuf[DNS_NAME_FORMATSIZE];
 	char rbuf[DNS_NAME_FORMATSIZE];
 	char *sname;
@@ -422,20 +437,12 @@
 	}
 
 	return (true);
-#else  /* ifdef GSSAPI */
-	UNUSED(signer);
-	UNUSED(name);
-	UNUSED(realm);
-	UNUSED(subdomain);
-	return (false);
-#endif /* ifdef GSSAPI */
 }
 
 bool
 dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
 				  const dns_name_t *name,
 				  const dns_name_t *realm, bool subdomain) {
-#ifdef GSSAPI
 	char sbuf[DNS_NAME_FORMATSIZE];
 	char rbuf[DNS_NAME_FORMATSIZE];
 	char *sname;
@@ -510,18 +517,10 @@
 	}
 
 	return (true);
-#else  /* ifdef GSSAPI */
-	UNUSED(signer);
-	UNUSED(name);
-	UNUSED(realm);
-	UNUSED(subdomain);
-	return (false);
-#endif /* ifdef GSSAPI */
 }
 
 isc_result_t
 dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
-#ifdef GSSAPI
 	OM_uint32 gret, minor;
 	char buf[1024];
 
@@ -536,14 +535,8 @@
 	*cred = NULL;
 
 	return (ISC_R_SUCCESS);
-#else  /* ifdef GSSAPI */
-	UNUSED(cred);
-
-	return (ISC_R_NOTIMPLEMENTED);
-#endif /* ifdef GSSAPI */
 }
 
-#ifdef GSSAPI
 /*
  * Format a gssapi error message info into a char ** on the given memory
  * context. This is used to return gssapi error messages back up the
@@ -565,13 +558,11 @@
 		(*err_message) = isc_mem_strdup(mctx, estr);
 	}
 }
-#endif /* ifdef GSSAPI */
 
 isc_result_t
 dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
 		   isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
 		   isc_mem_t *mctx, char **err_message) {
-#ifdef GSSAPI
 	isc_region_t r;
 	isc_buffer_t namebuf;
 	gss_name_t gname;
@@ -653,16 +644,6 @@
 	}
 	(void)gss_release_name(&minor, &gname);
 	return (result);
-#else  /* ifdef GSSAPI */
-	UNUSED(name);
-	UNUSED(intoken);
-	UNUSED(outtoken);
-	UNUSED(gssctx);
-	UNUSED(mctx);
-	UNUSED(err_message);
-
-	return (ISC_R_NOTIMPLEMENTED);
-#endif /* ifdef GSSAPI */
 }
 
 isc_result_t
@@ -670,7 +651,6 @@
 		     isc_region_t *intoken, isc_buffer_t **outtoken,
 		     dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
 		     isc_mem_t *mctx) {
-#ifdef GSSAPI
 	isc_region_t r;
 	isc_buffer_t namebuf;
 	gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
@@ -753,6 +733,9 @@
 	default:
 		gss_log(3, "failed gss_accept_sec_context: %s",
 			gss_error_tostring(gret, minor, buf, sizeof(buf)));
+		if (gouttoken.length > 0U) {
+			(void)gss_release_buffer(&minor, &gouttoken);
+		}
 		return (result);
 	}
 
@@ -820,22 +803,10 @@
 	}
 
 	return (result);
-#else  /* ifdef GSSAPI */
-	UNUSED(cred);
-	UNUSED(gssapi_keytab);
-	UNUSED(intoken);
-	UNUSED(outtoken);
-	UNUSED(ctxout);
-	UNUSED(principal);
-	UNUSED(mctx);
-
-	return (ISC_R_NOTIMPLEMENTED);
-#endif /* ifdef GSSAPI */
 }
 
 isc_result_t
 dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
-#ifdef GSSAPI
 	OM_uint32 gret, minor;
 	char buf[1024];
 
@@ -852,16 +823,10 @@
 			gss_error_tostring(gret, minor, buf, sizeof(buf)));
 	}
 	return (ISC_R_SUCCESS);
-#else  /* ifdef GSSAPI */
-	UNUSED(mctx);
-	UNUSED(gssctx);
-	return (ISC_R_NOTIMPLEMENTED);
-#endif /* ifdef GSSAPI */
 }
 
 char *
 gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
-#ifdef GSSAPI
 	gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
 			msg_major = GSS_C_EMPTY_BUFFER;
 	OM_uint32 msg_ctx, minor_stat;
@@ -886,13 +851,96 @@
 		(void)gss_release_buffer(&minor_stat, &msg_minor);
 	}
 	return (buf);
+}
+
 #else  /* ifdef GSSAPI */
+
+isc_result_t
+dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
+		       dns_gss_cred_id_t *cred) {
+	REQUIRE(cred != NULL && *cred == NULL);
+
+	UNUSED(name);
+	UNUSED(initiate);
+	UNUSED(cred);
+
+	return (ISC_R_NOTIMPLEMENTED);
+}
+
+bool
+dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
+				    const dns_name_t *name,
+				    const dns_name_t *realm, bool subdomain) {
+	UNUSED(signer);
+	UNUSED(name);
+	UNUSED(realm);
+	UNUSED(subdomain);
+	return (false);
+}
+
+bool
+dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
+				  const dns_name_t *name,
+				  const dns_name_t *realm, bool subdomain) {
+	UNUSED(signer);
+	UNUSED(name);
+	UNUSED(realm);
+	UNUSED(subdomain);
+	return (false);
+}
+
+isc_result_t
+dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
+	UNUSED(cred);
+
+	return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
+		   isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
+		   isc_mem_t *mctx, char **err_message) {
+	UNUSED(name);
+	UNUSED(intoken);
+	UNUSED(outtoken);
+	UNUSED(gssctx);
+	UNUSED(mctx);
+	UNUSED(err_message);
+
+	return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
+		     isc_region_t *intoken, isc_buffer_t **outtoken,
+		     dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
+		     isc_mem_t *mctx) {
+	UNUSED(cred);
+	UNUSED(gssapi_keytab);
+	UNUSED(intoken);
+	UNUSED(outtoken);
+	UNUSED(ctxout);
+	UNUSED(principal);
+	UNUSED(mctx);
+
+	return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
+	UNUSED(mctx);
+	UNUSED(gssctx);
+	return (ISC_R_NOTIMPLEMENTED);
+}
+
+char *
+gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
 	snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", major,
 		 minor);
 
 	return (buf);
-#endif /* ifdef GSSAPI */
 }
+#endif /* ifdef GSSAPI */
 
 void
 gss_log(int level, const char *fmt, ...) {
diff -Nru bind9-9.16.13/lib/dns/include/dns/db.h bind9-9.16.15/lib/dns/include/dns/db.h
--- bind9-9.16.13/lib/dns/include/dns/db.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/include/dns/db.h	2021-04-19 15:45:50.000000000 +0200
@@ -259,12 +259,12 @@
 #define DNS_DBFIND_STALEENABLED 0x0800
 
 /*
- * DNS_DBFIND_STALEONLY: This flag is used when we want stale data from the
+ * DNS_DBFIND_STALETIMEOUT: This flag is used when we want stale data from the
  * database, but not due to a failure in resolution, it also doesn't require
- * stale-refresh-time window timer to be active. As long as there is a stale
- * RRset available, it should be returned.
+ * stale-refresh-time window timer to be active. As long as there is stale
+ * data available, it should be returned.
  */
-#define DNS_DBFIND_STALEONLY 0x1000
+#define DNS_DBFIND_STALETIMEOUT 0x1000
 
 /*
  * DNS_DBFIND_STALESTART: This flag is used to activate stale-refresh-time
diff -Nru bind9-9.16.13/lib/dns/include/dns/journal.h bind9-9.16.15/lib/dns/include/dns/journal.h
--- bind9-9.16.13/lib/dns/include/dns/journal.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/include/dns/journal.h	2021-04-19 15:45:50.000000000 +0200
@@ -187,6 +187,12 @@
  * Find out if a journal is empty.
  */
 
+bool
+dns_journal_recovered(dns_journal_t *j);
+/*<
+ * Find out if the journal could be opened using old journal format
+ */
+
 uint32_t
 dns_journal_first_serial(dns_journal_t *j);
 uint32_t
@@ -248,28 +254,23 @@
  */
 
 isc_result_t
-dns_journal_rollforward(isc_mem_t *mctx, dns_db_t *db, unsigned int options,
-			const char *filename);
+dns_journal_rollforward(dns_journal_t *j, dns_db_t *db, unsigned int options);
 /*%<
  * Roll forward (play back) the journal file "filename" into the
  * database "db".  This should be called when the server starts
  * after a shutdown or crash.
  *
  * Requires:
- *\li   'mctx' is a valid memory context.
+ *\li   'journal' is a valid journal
  *\li	'db' is a valid database which does not have a version
  *           open for writing.
- *\li   'filename' is the name of the journal file belonging to 'db'.
  *
  * Returns:
- *\li	DNS_R_NOJOURNAL when journal does not exist.
  *\li	ISC_R_NOTFOUND when current serial in not in journal.
  *\li	ISC_R_RANGE when current serial in not in journals range.
  *\li	DNS_R_UPTODATE when the database was already up to date.
  *\li	ISC_R_SUCCESS journal has been applied successfully to the
  *      database without any issues.
- *\li	DNS_R_RECOVERABLE if successful or up to date, but the journal
- *      was found to contain at least one outdated transaction header.
  *
  *	others
  */
diff -Nru bind9-9.16.13/lib/dns/include/dns/rdataset.h bind9-9.16.15/lib/dns/include/dns/rdataset.h
--- bind9-9.16.13/lib/dns/include/dns/rdataset.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/include/dns/rdataset.h	2021-04-19 15:45:50.000000000 +0200
@@ -190,6 +190,7 @@
 #define DNS_RDATASETATTR_STALE	      0x01000000
 #define DNS_RDATASETATTR_ANCIENT      0x02000000
 #define DNS_RDATASETATTR_STALE_WINDOW 0x04000000
+#define DNS_RDATASETATTR_STALE_ADDED  0x08000000
 
 /*%
  * _OMITDNSSEC:
diff -Nru bind9-9.16.13/lib/dns/journal.c bind9-9.16.15/lib/dns/journal.c
--- bind9-9.16.13/lib/dns/journal.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/journal.c	2021-04-19 15:45:50.000000000 +0200
@@ -945,6 +945,29 @@
 			  ? sizeof(journal_rawxhdr_t)
 			  : sizeof(journal_rawxhdr_ver1_t);
 
+	/*
+	 * Handle <size, serial0, serial1, 0> transaction header.
+	 */
+	if (j->header_ver1 && j->xhdr_version == XHDR_VERSION1) {
+		uint32_t value;
+
+		CHECK(journal_read(j, &value, sizeof(value)));
+		if (value != 0L) {
+			CHECK(journal_seek(j, pos->offset + 12));
+		} else {
+			j->recovered = true;
+			hdrsize += 4;
+		}
+	} else if (j->header_ver1 && j->xhdr_version == XHDR_VERSION2 &&
+		   xhdr.count == pos->serial && xhdr.serial1 == 0U &&
+		   isc_serial_ge(xhdr.serial0, xhdr.count))
+	{
+		xhdr.serial1 = xhdr.serial0;
+		xhdr.serial0 = xhdr.count;
+		xhdr.count = 0;
+		j->recovered = true;
+	}
+
 	if ((isc_offset_t)(pos->offset + hdrsize + xhdr.size) < pos->offset) {
 		isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR,
 			      "%s: offset too large", j->filename);
@@ -954,6 +977,9 @@
 	pos->offset += hdrsize + xhdr.size;
 	pos->serial = xhdr.serial1;
 	return (ISC_R_SUCCESS);
+
+failure:
+	return (result);
 }
 
 /*
@@ -1101,7 +1127,6 @@
 dns_journal_begin_transaction(dns_journal_t *j) {
 	uint32_t offset;
 	isc_result_t result;
-	journal_rawxhdr_t hdr;
 
 	REQUIRE(DNS_JOURNAL_VALID(j));
 	REQUIRE(j->state == JOURNAL_STATE_WRITE ||
@@ -1128,8 +1153,7 @@
 	 * space.  It will be filled in when the transaction is
 	 * finished.
 	 */
-	memset(&hdr, 0, sizeof(hdr));
-	CHECK(journal_write(j, &hdr, sizeof(hdr)));
+	CHECK(journal_write_xhdr(j, 0, 0, 0, 0));
 	j->x.pos[1].offset = j->offset;
 
 	j->state = JOURNAL_STATE_TRANSACTION;
@@ -1320,7 +1344,8 @@
 	if (j->state == JOURNAL_STATE_TRANSACTION) {
 		isc_offset_t offset;
 		offset = (j->x.pos[1].offset - j->x.pos[0].offset) -
-			 sizeof(journal_rawxhdr_t);
+			 (j->header_ver1 ? sizeof(journal_rawxhdr_ver1_t)
+					 : sizeof(journal_rawxhdr_t));
 		/*
 		 * Update the transaction header.
 		 */
@@ -1425,8 +1450,8 @@
 
 /* XXX Share code with incoming IXFR? */
 
-static isc_result_t
-roll_forward(dns_journal_t *j, dns_db_t *db, unsigned int options) {
+isc_result_t
+dns_journal_rollforward(dns_journal_t *j, dns_db_t *db, unsigned int options) {
 	isc_buffer_t source; /* Transaction data from disk */
 	isc_buffer_t target; /* Ditto after _fromwire check */
 	uint32_t db_serial;  /* Database SOA serial */
@@ -1573,40 +1598,6 @@
 }
 
 isc_result_t
-dns_journal_rollforward(isc_mem_t *mctx, dns_db_t *db, unsigned int options,
-			const char *filename) {
-	dns_journal_t *j = NULL;
-	isc_result_t result;
-
-	REQUIRE(DNS_DB_VALID(db));
-	REQUIRE(filename != NULL);
-
-	result = dns_journal_open(mctx, filename, DNS_JOURNAL_READ, &j);
-	if (result == ISC_R_NOTFOUND) {
-		isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "no journal file, but "
-							"that's OK");
-		return (DNS_R_NOJOURNAL);
-	}
-	if (result != ISC_R_SUCCESS) {
-		return (result);
-	}
-
-	if (JOURNAL_EMPTY(&j->header)) {
-		CHECK(DNS_R_UPTODATE);
-	}
-
-	result = roll_forward(j, db, options);
-	if ((result == ISC_R_SUCCESS || result == DNS_R_UPTODATE) &&
-	    j->recovered) {
-		result = DNS_R_RECOVERABLE;
-	}
-
-failure:
-	dns_journal_destroy(&j);
-	return (result);
-}
-
-isc_result_t
 dns_journal_print(isc_mem_t *mctx, uint32_t flags, const char *filename,
 		  FILE *file) {
 	dns_journal_t *j = NULL;
@@ -1763,6 +1754,11 @@
 	return (JOURNAL_EMPTY(&j->header));
 }
 
+bool
+dns_journal_recovered(dns_journal_t *j) {
+	return (j->recovered);
+}
+
 uint32_t
 dns_journal_first_serial(dns_journal_t *j) {
 	return (j->header.begin.serial);
@@ -1977,6 +1973,30 @@
 				      xhdr.serial0);
 			FAIL(ISC_R_UNEXPECTED);
 		}
+
+		/*
+		 * Handle <size, serial0, serial1, 0> transaction header.
+		 */
+		if (j->header_ver1 && j->xhdr_version == XHDR_VERSION1) {
+			uint32_t value;
+
+			CHECK(journal_read(j, &value, sizeof(value)));
+			if (value != 0L) {
+				CHECK(journal_seek(j, save.offset + 12));
+			} else {
+				j->recovered = true;
+			}
+		} else if (j->header_ver1 && j->xhdr_version == XHDR_VERSION2 &&
+			   xhdr.count == j->it.current_serial &&
+			   xhdr.serial1 == 0U &&
+			   isc_serial_ge(xhdr.serial0, xhdr.count))
+		{
+			xhdr.serial1 = xhdr.serial0;
+			xhdr.serial0 = xhdr.count;
+			xhdr.count = 0;
+			j->recovered = true;
+		}
+
 		j->it.xsize = xhdr.size;
 		j->it.xpos = 0;
 	}
@@ -2390,7 +2410,7 @@
 }
 
 static uint32_t
-rrcount(char *buf, unsigned int size) {
+rrcount(unsigned char *buf, unsigned int size) {
 	isc_buffer_t b;
 	uint32_t rrsize, count = 0;
 
@@ -2406,6 +2426,28 @@
 	return (count);
 }
 
+static bool
+check_delta(unsigned char *buf, size_t size) {
+	isc_buffer_t b;
+	uint32_t rrsize;
+
+	isc_buffer_init(&b, buf, size);
+	isc_buffer_add(&b, size);
+	while (isc_buffer_remaininglength(&b) > 0) {
+		if (isc_buffer_remaininglength(&b) < 4) {
+			return (false);
+		}
+		rrsize = isc_buffer_getuint32(&b);
+		/* "." + type + class + ttl + rdlen => 11U */
+		if (rrsize < 11U || isc_buffer_remaininglength(&b) < rrsize) {
+			return (false);
+		}
+		isc_buffer_forward(&b, rrsize);
+	}
+
+	return (true);
+}
+
 isc_result_t
 dns_journal_compact(isc_mem_t *mctx, char *filename, uint32_t serial,
 		    uint32_t flags, uint32_t target_size) {
@@ -2417,7 +2459,7 @@
 	journal_rawheader_t rawheader;
 	unsigned int len;
 	size_t namelen;
-	char *buf = NULL;
+	unsigned char *buf = NULL;
 	unsigned int size = 0;
 	isc_result_t result;
 	unsigned int indexend;
@@ -2574,22 +2616,22 @@
 			}
 			CHECK(result);
 
+			size = xhdr.size;
+			buf = isc_mem_get(mctx, size);
+			result = journal_read(j1, buf, size);
+
 			/*
 			 * If we're repairing an outdated journal, the
 			 * xhdr format may be wrong.
 			 */
-			if (rewrite &&
-			    (xhdr.serial0 != serial ||
-			     isc_serial_le(xhdr.serial1, xhdr.serial0)))
-			{
-				if (j1->xhdr_version == XHDR_VERSION2 &&
-				    xhdr.count == serial) {
+			if (rewrite && (result != ISC_R_SUCCESS ||
+					!check_delta(buf, size))) {
+				if (j1->xhdr_version == XHDR_VERSION2) {
 					/* XHDR_VERSION2 -> XHDR_VERSION1 */
 					j1->xhdr_version = XHDR_VERSION1;
 					CHECK(journal_seek(j1, offset));
 					CHECK(journal_read_xhdr(j1, &xhdr));
-				} else if (j1->xhdr_version == XHDR_VERSION1 &&
-					   xhdr.serial1 == serial) {
+				} else if (j1->xhdr_version == XHDR_VERSION1) {
 					/* XHDR_VERSION1 -> XHDR_VERSION2 */
 					j1->xhdr_version = XHDR_VERSION2;
 					CHECK(journal_seek(j1, offset));
@@ -2597,16 +2639,47 @@
 				}
 
 				/* Check again */
-				if (xhdr.serial0 != serial ||
-				    isc_serial_le(xhdr.serial1, xhdr.serial0)) {
+				isc_mem_put(mctx, buf, size);
+				size = xhdr.size;
+				buf = isc_mem_get(mctx, size);
+				CHECK(journal_read(j1, buf, size));
+
+				if (!check_delta(buf, size)) {
 					CHECK(ISC_R_UNEXPECTED);
 				}
+			} else {
+				CHECK(result);
 			}
 
-			size = xhdr.size;
-			buf = isc_mem_get(mctx, size);
-			CHECK(journal_read(j1, buf, size));
+			/*
+			 * Recover from incorrectly written transaction header.
+			 * The incorrect header was written as size, serial0,
+			 * serial1, and 0.  XHDR_VERSION2 is expecting size,
+			 * count, serial0, and serial1.
+			 */
+			if (j1->xhdr_version == XHDR_VERSION2 &&
+			    xhdr.count == serial && xhdr.serial1 == 0U &&
+			    isc_serial_ge(xhdr.serial0, xhdr.count))
+			{
+				xhdr.serial1 = xhdr.serial0;
+				xhdr.serial0 = xhdr.count;
+				xhdr.count = 0;
+			}
+
+			/*
+			 * Check that xhdr is consistent.
+			 */
+			if (xhdr.serial0 != serial ||
+			    isc_serial_le(xhdr.serial1, xhdr.serial0)) {
+				CHECK(ISC_R_UNEXPECTED);
+			}
 
+			/*
+			 * Extract record count from the transaction.  This
+			 * is needed when converting from XHDR_VERSION1 to
+			 * XHDR_VERSION2, and when recovering from an
+			 * incorrectly written XHDR_VERSION2.
+			 */
 			count = rrcount(buf, size);
 			CHECK(journal_write_xhdr(j2, xhdr.size, count,
 						 xhdr.serial0, xhdr.serial1));
diff -Nru bind9-9.16.13/lib/dns/keymgr.c bind9-9.16.15/lib/dns/keymgr.c
--- bind9-9.16.13/lib/dns/keymgr.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/keymgr.c	2021-04-19 15:45:50.000000000 +0200
@@ -1549,23 +1549,23 @@
 	/* Get time metadata. */
 	ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active);
 	if (active <= now && ret == ISC_R_SUCCESS) {
-		dns_ttl_t key_ttl = dst_key_getttl(key->key);
-		key_ttl += dns_kasp_zonepropagationdelay(kasp);
-		if ((active + key_ttl) <= now) {
-			dnskey_state = OMNIPRESENT;
+		dns_ttl_t zone_ttl = dns_kasp_zonemaxttl(kasp);
+		zone_ttl += dns_kasp_zonepropagationdelay(kasp);
+		if ((active + zone_ttl) <= now) {
+			zrrsig_state = OMNIPRESENT;
 		} else {
-			dnskey_state = RUMOURED;
+			zrrsig_state = RUMOURED;
 		}
 		goal_state = OMNIPRESENT;
 	}
 	ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub);
 	if (pub <= now && ret == ISC_R_SUCCESS) {
-		dns_ttl_t zone_ttl = dns_kasp_zonemaxttl(kasp);
-		zone_ttl += dns_kasp_zonepropagationdelay(kasp);
-		if ((pub + zone_ttl) <= now) {
-			zrrsig_state = OMNIPRESENT;
+		dns_ttl_t key_ttl = dst_key_getttl(key->key);
+		key_ttl += dns_kasp_zonepropagationdelay(kasp);
+		if ((pub + key_ttl) <= now) {
+			dnskey_state = OMNIPRESENT;
 		} else {
-			zrrsig_state = RUMOURED;
+			dnskey_state = RUMOURED;
 		}
 		goal_state = OMNIPRESENT;
 	}
diff -Nru bind9-9.16.13/lib/dns/Makefile.in bind9-9.16.15/lib/dns/Makefile.in
--- bind9-9.16.13/lib/dns/Makefile.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/Makefile.in	2021-04-19 15:45:50.000000000 +0200
@@ -22,8 +22,6 @@
 
 @BIND9_MAKE_INCLUDES@
 
-USE_ISC_SPNEGO = @USE_ISC_SPNEGO@
-
 CINCLUDES =	-I. -I${top_srcdir}/lib/dns -Iinclude ${DNS_INCLUDES} \
 		${ISC_INCLUDES} \
 		${FSTRM_CFLAGS} \
@@ -34,7 +32,7 @@
 		${LMDB_CFLAGS} \
 		${MAXMINDDB_CFLAGS}
 
-CDEFINES =	@USE_GSSAPI@ ${USE_ISC_SPNEGO}
+CDEFINES =	@USE_GSSAPI@
 
 CWARNINGS =
 
@@ -213,5 +211,3 @@
 	$(PROTOC_C) --c_out=. --proto_path ${srcdir} dnstap.proto
 
 dnstap.pb-c.@O@: dnstap.pb-c.c
-
-spnego.@O@: spnego_asn1.c spnego.h
diff -Nru bind9-9.16.13/lib/dns/rbtdb.c bind9-9.16.15/lib/dns/rbtdb.c
--- bind9-9.16.13/lib/dns/rbtdb.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/rbtdb.c	2021-04-19 15:45:50.000000000 +0200
@@ -4563,11 +4563,11 @@
 				RDATASET_ATTR_SET(header,
 						  RDATASET_ATTR_STALE_WINDOW);
 				return (false);
-			} else if ((search->options & DNS_DBFIND_STALEONLY) !=
-				   0) {
+			} else if ((search->options &
+				    DNS_DBFIND_STALETIMEOUT) != 0) {
 				/*
-				 * We want stale RRset only, so we don't skip
-				 * it.
+				 * We want stale RRset due to timeout, so we
+				 * don't skip it.
 				 */
 				return (false);
 			}
@@ -6769,6 +6769,13 @@
 	INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb);
 
 	if (rbtdb->common.methods == &zone_methods) {
+		/*
+		 * SOA records are only allowed at top of zone.
+		 */
+		if (rdataset->type == dns_rdatatype_soa &&
+		    node != rbtdb->origin_node) {
+			return (DNS_R_NOTZONETOP);
+		}
 		RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
 		REQUIRE(((rbtnode->nsec == DNS_RBT_NSEC_NSEC3 &&
 			  (rdataset->type == dns_rdatatype_nsec3 ||
diff -Nru bind9-9.16.13/lib/dns/spnego.asn1 bind9-9.16.15/lib/dns/spnego.asn1
--- bind9-9.16.13/lib/dns/spnego.asn1	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/spnego.asn1	1970-01-01 01:00:00.000000000 +0100
@@ -1,50 +0,0 @@
--- Copyright (C) The Internet Society 2005.  This version of
--- this module is part of RFC 4178; see the RFC itself for
--- full legal notices.
-
--- (The above copyright notice is per RFC 3978 5.6 (a), q.v.)
-
--- This is the SPNEGO ASN.1 module from RFC 4178, tweaked
--- to get the Heimdal ASN.1 compiler to accept it.
-
-SPNEGOASNOneSpec DEFINITIONS ::= BEGIN
-
-MechType ::= OBJECT IDENTIFIER
-
-MechTypeList ::= SEQUENCE OF MechType
-
-ContextFlags ::= BIT STRING {
-    delegFlag       (0),
-    mutualFlag      (1),
-    replayFlag      (2),
-    sequenceFlag    (3),
-    anonFlag        (4),
-    confFlag        (5),
-    integFlag       (6)
-}
-
-NegTokenInit ::= SEQUENCE {
-    mechTypes       [0] MechTypeList,
-    reqFlags        [1] ContextFlags  OPTIONAL,
-    mechToken       [2] OCTET STRING  OPTIONAL,
-    mechListMIC     [3] OCTET STRING  OPTIONAL
-}
-
-NegTokenResp ::= SEQUENCE {
-    negState       [0] ENUMERATED {
-	accept-completed    (0),
-	accept-incomplete   (1),
-	reject              (2),
-	request-mic         (3)
-    }                                 OPTIONAL,
-    supportedMech   [1] MechType      OPTIONAL,
-    responseToken   [2] OCTET STRING  OPTIONAL,
-    mechListMIC     [3] OCTET STRING  OPTIONAL
-}
-
-NegotiationToken ::= CHOICE {
-    negTokenInit    [0] NegTokenInit,
-    negTokenResp    [1] NegTokenResp
-}
-
-END
diff -Nru bind9-9.16.13/lib/dns/spnego_asn1.c bind9-9.16.15/lib/dns/spnego_asn1.c
--- bind9-9.16.13/lib/dns/spnego_asn1.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/spnego_asn1.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,985 +0,0 @@
-/*
- * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
- *
- * 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 https://mozilla.org/MPL/2.0/.
- *
- * See the COPYRIGHT file distributed with this work for additional
- * information regarding copyright ownership.
- */
-
-/*! \file
- * \brief Method routines generated from SPNEGO ASN.1 module.
- * See spnego_asn1.pl for details.  Do not edit.
- */
-
-/* Generated from spnego.asn1 */
-/* Do not edit */
-
-#ifndef __asn1_h__
-#define __asn1_h__
-
-#ifndef __asn1_common_definitions__
-#define __asn1_common_definitions__
-
-typedef struct octet_string {
-	size_t length;
-	void *data;
-} octet_string;
-
-typedef char *general_string;
-
-typedef char *utf8_string;
-
-typedef struct oid {
-	size_t length;
-	unsigned *components;
-} oid;
-
-#define ASN1_MALLOC_ENCODE(T, B, BL, S, L, R)                             \
-	do {                                                              \
-		(BL) = length_##T((S));                                   \
-		(B) = malloc((BL));                                       \
-		if ((B) == NULL) {                                        \
-			(R) = ENOMEM;                                     \
-		} else {                                                  \
-			(R) = encode_##T(((unsigned char *)(B)) + (BL)-1, \
-					 (BL), (S), (L));                 \
-			if ((R) != 0) {                                   \
-				free((B));                                \
-				(B) = NULL;                               \
-			}                                                 \
-		}                                                         \
-	} while (0)
-
-#endif /* ifndef __asn1_common_definitions__ */
-
-/*
- * MechType ::= OBJECT IDENTIFIER
- */
-
-typedef oid MechType;
-
-static int
-encode_MechType(unsigned char *, size_t, const MechType *, size_t *);
-static int
-decode_MechType(const unsigned char *, size_t, MechType *, size_t *);
-static void
-free_MechType(MechType *);
-/* unused declaration: length_MechType */
-/* unused declaration: copy_MechType */
-
-/*
- * MechTypeList ::= SEQUENCE OF MechType
- */
-
-typedef struct MechTypeList {
-	unsigned int len;
-	MechType *val;
-} MechTypeList;
-
-static int
-encode_MechTypeList(unsigned char *, size_t, const MechTypeList *, size_t *);
-static int
-decode_MechTypeList(const unsigned char *, size_t, MechTypeList *, size_t *);
-static void
-free_MechTypeList(MechTypeList *);
-/* unused declaration: length_MechTypeList */
-/* unused declaration: copy_MechTypeList */
-
-/*
- * ContextFlags ::= BIT STRING { delegFlag(0), mutualFlag(1), replayFlag(2),
- * sequenceFlag(3), anonFlag(4), confFlag(5), integFlag(6) }
- */
-
-typedef struct ContextFlags {
-	unsigned int delegFlag	  : 1;
-	unsigned int mutualFlag	  : 1;
-	unsigned int replayFlag	  : 1;
-	unsigned int sequenceFlag : 1;
-	unsigned int anonFlag	  : 1;
-	unsigned int confFlag	  : 1;
-	unsigned int integFlag	  : 1;
-} ContextFlags;
-
-static int
-encode_ContextFlags(unsigned char *, size_t, const ContextFlags *, size_t *);
-static int
-decode_ContextFlags(const unsigned char *, size_t, ContextFlags *, size_t *);
-static void
-free_ContextFlags(ContextFlags *);
-/* unused declaration: length_ContextFlags */
-/* unused declaration: copy_ContextFlags */
-/* unused declaration: ContextFlags2int */
-/* unused declaration: int2ContextFlags */
-/* unused declaration: asn1_ContextFlags_units */
-
-/*
- * NegTokenInit ::= SEQUENCE { mechTypes[0]    MechTypeList, reqFlags[1]
- * ContextFlags OPTIONAL, mechToken[2]    OCTET STRING OPTIONAL,
- * mechListMIC[3]  OCTET STRING OPTIONAL }
- */
-
-typedef struct NegTokenInit {
-	MechTypeList mechTypes;
-	ContextFlags *reqFlags;
-	octet_string *mechToken;
-	octet_string *mechListMIC;
-} NegTokenInit;
-
-static int
-encode_NegTokenInit(unsigned char *, size_t, const NegTokenInit *, size_t *);
-static int
-decode_NegTokenInit(const unsigned char *, size_t, NegTokenInit *, size_t *);
-static void
-free_NegTokenInit(NegTokenInit *);
-/* unused declaration: length_NegTokenInit */
-/* unused declaration: copy_NegTokenInit */
-
-/*
- * NegTokenResp ::= SEQUENCE { negState[0]       ENUMERATED {
- * accept-completed(0), accept-incomplete(1), reject(2), request-mic(3) }
- * OPTIONAL, supportedMech[1]  MechType OPTIONAL, responseToken[2]  OCTET
- * STRING OPTIONAL, mechListMIC[3]    OCTET STRING OPTIONAL }
- */
-
-typedef struct NegTokenResp {
-	enum {
-		accept_completed = 0,
-		accept_incomplete = 1,
-		reject = 2,
-		request_mic = 3
-	} *
-		negState;
-
-	MechType *supportedMech;
-	octet_string *responseToken;
-	octet_string *mechListMIC;
-} NegTokenResp;
-
-static int
-encode_NegTokenResp(unsigned char *, size_t, const NegTokenResp *, size_t *);
-static int
-decode_NegTokenResp(const unsigned char *, size_t, NegTokenResp *, size_t *);
-static void
-free_NegTokenResp(NegTokenResp *);
-/* unused declaration: length_NegTokenResp */
-/* unused declaration: copy_NegTokenResp */
-
-#endif /* __asn1_h__ */
-/* Generated from spnego.asn1 */
-/* Do not edit */
-
-#define BACK                \
-	if (e)              \
-		return (e); \
-	p -= l;             \
-	len -= l;           \
-	ret += l;           \
-	POST(p);            \
-	POST(len);          \
-	POST(ret)
-
-static int
-encode_MechType(unsigned char *p, size_t len, const MechType *data,
-		size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-
-	e = encode_oid(p, len, data, &l);
-	BACK;
-	*size = ret;
-	return (0);
-}
-
-#define FORW               \
-	if (e)             \
-		goto fail; \
-	p += l;            \
-	len -= l;          \
-	ret += l;          \
-	POST(p);           \
-	POST(len);         \
-	POST(ret)
-
-static int
-decode_MechType(const unsigned char *p, size_t len, MechType *data,
-		size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-
-	memset(data, 0, sizeof(*data));
-	e = decode_oid(p, len, data, &l);
-	FORW;
-	if (size) {
-		*size = ret;
-	}
-	return (0);
-fail:
-	free_MechType(data);
-	return (e);
-}
-
-static void
-free_MechType(MechType *data) {
-	free_oid(data);
-}
-
-/* unused function: length_MechType */
-
-/* unused function: copy_MechType */
-
-/* Generated from spnego.asn1 */
-/* Do not edit */
-
-static int
-encode_MechTypeList(unsigned char *p, size_t len, const MechTypeList *data,
-		    size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int i, e;
-
-	for (i = (data)->len - 1; i >= 0; --i) {
-		size_t oldret = ret;
-		ret = 0;
-		e = encode_MechType(p, len, &(data)->val[i], &l);
-		BACK;
-		ret += oldret;
-	}
-	e = der_put_length_and_tag(p, len, ret, ASN1_C_UNIV, CONS, UT_Sequence,
-				   &l);
-	BACK;
-	*size = ret;
-	return (0);
-}
-
-static int
-decode_MechTypeList(const unsigned char *p, size_t len, MechTypeList *data,
-		    size_t *size) {
-	size_t ret = 0, reallen;
-	size_t l;
-	int e;
-
-	memset(data, 0, sizeof(*data));
-	reallen = 0;
-	e = der_match_tag_and_length(p, len, ASN1_C_UNIV, CONS, UT_Sequence,
-				     &reallen, &l);
-	FORW;
-	if (len < reallen) {
-		return (ASN1_OVERRUN);
-	}
-	len = reallen;
-	{
-		size_t origlen = len;
-		size_t oldret = ret;
-		ret = 0;
-		(data)->len = 0;
-		(data)->val = NULL;
-		while (ret < origlen) {
-			void *old = (data)->val;
-			(data)->len++;
-			(data)->val =
-				realloc((data)->val,
-					sizeof(*((data)->val)) * (data)->len);
-			if ((data)->val == NULL) {
-				(data)->val = old;
-				(data)->len--;
-				return (ENOMEM);
-			}
-			e = decode_MechType(p, len,
-					    &(data)->val[(data)->len - 1], &l);
-			FORW;
-			len = origlen - ret;
-		}
-		ret += oldret;
-	}
-	if (size) {
-		*size = ret;
-	}
-	return (0);
-fail:
-	free_MechTypeList(data);
-	return (e);
-}
-
-static void
-free_MechTypeList(MechTypeList *data) {
-	while ((data)->len) {
-		free_MechType(&(data)->val[(data)->len - 1]);
-		(data)->len--;
-	}
-	free((data)->val);
-	(data)->val = NULL;
-}
-
-/* unused function: length_MechTypeList */
-
-/* unused function: copy_MechTypeList */
-
-/* Generated from spnego.asn1 */
-/* Do not edit */
-
-static int
-encode_ContextFlags(unsigned char *p, size_t len, const ContextFlags *data,
-		    size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-
-	{
-		unsigned char c = 0;
-		*p-- = c;
-		len--;
-		ret++;
-		c = 0;
-		*p-- = c;
-		len--;
-		ret++;
-		c = 0;
-		*p-- = c;
-		len--;
-		ret++;
-		c = 0;
-		if (data->integFlag) {
-			c |= 1 << 1;
-		}
-		if (data->confFlag) {
-			c |= 1 << 2;
-		}
-		if (data->anonFlag) {
-			c |= 1 << 3;
-		}
-		if (data->sequenceFlag) {
-			c |= 1 << 4;
-		}
-		if (data->replayFlag) {
-			c |= 1 << 5;
-		}
-		if (data->mutualFlag) {
-			c |= 1 << 6;
-		}
-		if (data->delegFlag) {
-			c |= 1 << 7;
-		}
-		*p-- = c;
-		*p-- = 0;
-		len -= 2;
-		ret += 2;
-	}
-
-	e = der_put_length_and_tag(p, len, ret, ASN1_C_UNIV, PRIM, UT_BitString,
-				   &l);
-	BACK;
-	*size = ret;
-	return (0);
-}
-
-static int
-decode_ContextFlags(const unsigned char *p, size_t len, ContextFlags *data,
-		    size_t *size) {
-	size_t ret = 0, reallen;
-	size_t l;
-	int e;
-
-	memset(data, 0, sizeof(*data));
-	reallen = 0;
-	e = der_match_tag_and_length(p, len, ASN1_C_UNIV, PRIM, UT_BitString,
-				     &reallen, &l);
-	FORW;
-	if (len < reallen) {
-		return (ASN1_OVERRUN);
-	}
-	p++;
-	len--;
-	POST(len);
-	reallen--;
-	ret++;
-	data->delegFlag = (*p >> 7) & 1;
-	data->mutualFlag = (*p >> 6) & 1;
-	data->replayFlag = (*p >> 5) & 1;
-	data->sequenceFlag = (*p >> 4) & 1;
-	data->anonFlag = (*p >> 3) & 1;
-	data->confFlag = (*p >> 2) & 1;
-	data->integFlag = (*p >> 1) & 1;
-	ret += reallen;
-	if (size) {
-		*size = ret;
-	}
-	return (0);
-fail:
-	free_ContextFlags(data);
-	return (e);
-}
-
-static void
-free_ContextFlags(ContextFlags *data) {
-	(void)data;
-}
-
-/* unused function: length_ContextFlags */
-
-/* unused function: copy_ContextFlags */
-
-/* unused function: ContextFlags2int */
-
-/* unused function: int2ContextFlags */
-
-/* unused variable: ContextFlags_units */
-
-/* unused function: asn1_ContextFlags_units */
-
-/* Generated from spnego.asn1 */
-/* Do not edit */
-
-static int
-encode_NegTokenInit(unsigned char *p, size_t len, const NegTokenInit *data,
-		    size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-
-	if ((data)->mechListMIC) {
-		size_t oldret = ret;
-		ret = 0;
-		e = encode_octet_string(p, len, (data)->mechListMIC, &l);
-		BACK;
-		e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 3,
-					   &l);
-		BACK;
-		ret += oldret;
-	}
-	if ((data)->mechToken) {
-		size_t oldret = ret;
-		ret = 0;
-		e = encode_octet_string(p, len, (data)->mechToken, &l);
-		BACK;
-		e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 2,
-					   &l);
-		BACK;
-		ret += oldret;
-	}
-	if ((data)->reqFlags) {
-		size_t oldret = ret;
-		ret = 0;
-		e = encode_ContextFlags(p, len, (data)->reqFlags, &l);
-		BACK;
-		e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 1,
-					   &l);
-		BACK;
-		ret += oldret;
-	}
-	{
-		size_t oldret = ret;
-		ret = 0;
-		e = encode_MechTypeList(p, len, &(data)->mechTypes, &l);
-		BACK;
-		e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 0,
-					   &l);
-		BACK;
-		ret += oldret;
-	}
-	e = der_put_length_and_tag(p, len, ret, ASN1_C_UNIV, CONS, UT_Sequence,
-				   &l);
-	BACK;
-	*size = ret;
-	return (0);
-}
-
-static int
-decode_NegTokenInit(const unsigned char *p, size_t len, NegTokenInit *data,
-		    size_t *size) {
-	size_t ret = 0, reallen;
-	size_t l;
-	int e;
-
-	memset(data, 0, sizeof(*data));
-	reallen = 0;
-	e = der_match_tag_and_length(p, len, ASN1_C_UNIV, CONS, UT_Sequence,
-				     &reallen, &l);
-	FORW;
-	{
-		int dce_fix;
-		if ((dce_fix = fix_dce(reallen, &len)) < 0) {
-			e = ASN1_BAD_FORMAT;
-			goto fail;
-		}
-		{
-			size_t newlen, oldlen;
-
-			e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 0, &l);
-			FORW;
-			{
-				e = der_get_length(p, len, &newlen, &l);
-				FORW;
-				{
-					int mydce_fix;
-					oldlen = len;
-					if ((mydce_fix = fix_dce(newlen,
-								 &len)) < 0) {
-						e = ASN1_BAD_FORMAT;
-						goto fail;
-					}
-					e = decode_MechTypeList(
-						p, len, &(data)->mechTypes, &l);
-					FORW;
-					if (mydce_fix) {
-						e = der_match_tag_and_length(
-							p, len, (Der_class)0,
-							(Der_type)0, 0,
-							&reallen, &l);
-						FORW;
-					} else {
-						len = oldlen - newlen;
-					}
-				}
-			}
-		}
-		{
-			size_t newlen, oldlen;
-
-			e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 1, &l);
-			if (e) {
-				(data)->reqFlags = NULL;
-			} else {
-				p += l;
-				len -= l;
-				ret += l;
-				e = der_get_length(p, len, &newlen, &l);
-				FORW;
-				{
-					int mydce_fix;
-					oldlen = len;
-					if ((mydce_fix = fix_dce(newlen,
-								 &len)) < 0) {
-						e = ASN1_BAD_FORMAT;
-						goto fail;
-					}
-					(data)->reqFlags = malloc(
-						sizeof(*(data)->reqFlags));
-					if ((data)->reqFlags == NULL) {
-						e = ENOMEM;
-						goto fail;
-					}
-					e = decode_ContextFlags(
-						p, len, (data)->reqFlags, &l);
-					FORW;
-					if (mydce_fix) {
-						e = der_match_tag_and_length(
-							p, len, (Der_class)0,
-							(Der_type)0, 0,
-							&reallen, &l);
-						FORW;
-					} else {
-						len = oldlen - newlen;
-					}
-				}
-			}
-		}
-		{
-			size_t newlen, oldlen;
-
-			e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 2, &l);
-			if (e) {
-				(data)->mechToken = NULL;
-			} else {
-				p += l;
-				len -= l;
-				ret += l;
-				e = der_get_length(p, len, &newlen, &l);
-				FORW;
-				{
-					int mydce_fix;
-					oldlen = len;
-					if ((mydce_fix = fix_dce(newlen,
-								 &len)) < 0) {
-						e = ASN1_BAD_FORMAT;
-						goto fail;
-					}
-					(data)->mechToken = malloc(
-						sizeof(*(data)->mechToken));
-					if ((data)->mechToken == NULL) {
-						e = ENOMEM;
-						goto fail;
-					}
-					e = decode_octet_string(
-						p, len, (data)->mechToken, &l);
-					FORW;
-					if (mydce_fix) {
-						e = der_match_tag_and_length(
-							p, len, (Der_class)0,
-							(Der_type)0, 0,
-							&reallen, &l);
-						FORW;
-					} else {
-						len = oldlen - newlen;
-					}
-				}
-			}
-		}
-		{
-			size_t newlen, oldlen;
-
-			e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 3, &l);
-			if (e) {
-				(data)->mechListMIC = NULL;
-			} else {
-				p += l;
-				len -= l;
-				ret += l;
-				e = der_get_length(p, len, &newlen, &l);
-				FORW;
-				{
-					int mydce_fix;
-					oldlen = len;
-					if ((mydce_fix = fix_dce(newlen,
-								 &len)) < 0) {
-						e = ASN1_BAD_FORMAT;
-						goto fail;
-					}
-					(data)->mechListMIC = malloc(
-						sizeof(*(data)->mechListMIC));
-					if ((data)->mechListMIC == NULL) {
-						e = ENOMEM;
-						goto fail;
-					}
-					e = decode_octet_string(
-						p, len, (data)->mechListMIC,
-						&l);
-					FORW;
-					if (mydce_fix) {
-						e = der_match_tag_and_length(
-							p, len, (Der_class)0,
-							(Der_type)0, 0,
-							&reallen, &l);
-						FORW;
-					} else {
-						len = oldlen - newlen;
-					}
-				}
-			}
-		}
-		if (dce_fix) {
-			e = der_match_tag_and_length(p, len, (Der_class)0,
-						     (Der_type)0, 0, &reallen,
-						     &l);
-			FORW;
-		}
-	}
-	if (size) {
-		*size = ret;
-	}
-	return (0);
-fail:
-	free_NegTokenInit(data);
-	return (e);
-}
-
-static void
-free_NegTokenInit(NegTokenInit *data) {
-	free_MechTypeList(&(data)->mechTypes);
-	if ((data)->reqFlags) {
-		free_ContextFlags((data)->reqFlags);
-		free((data)->reqFlags);
-		(data)->reqFlags = NULL;
-	}
-	if ((data)->mechToken) {
-		free_octet_string((data)->mechToken);
-		free((data)->mechToken);
-		(data)->mechToken = NULL;
-	}
-	if ((data)->mechListMIC) {
-		free_octet_string((data)->mechListMIC);
-		free((data)->mechListMIC);
-		(data)->mechListMIC = NULL;
-	}
-}
-
-/* unused function: length_NegTokenInit */
-
-/* unused function: copy_NegTokenInit */
-
-/* Generated from spnego.asn1 */
-/* Do not edit */
-
-static int
-encode_NegTokenResp(unsigned char *p, size_t len, const NegTokenResp *data,
-		    size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-
-	if ((data)->mechListMIC) {
-		size_t oldret = ret;
-		ret = 0;
-		e = encode_octet_string(p, len, (data)->mechListMIC, &l);
-		BACK;
-		e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 3,
-					   &l);
-		BACK;
-		ret += oldret;
-	}
-	if ((data)->responseToken) {
-		size_t oldret = ret;
-		ret = 0;
-		e = encode_octet_string(p, len, (data)->responseToken, &l);
-		BACK;
-		e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 2,
-					   &l);
-		BACK;
-		ret += oldret;
-	}
-	if ((data)->supportedMech) {
-		size_t oldret = ret;
-		ret = 0;
-		e = encode_MechType(p, len, (data)->supportedMech, &l);
-		BACK;
-		e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 1,
-					   &l);
-		BACK;
-		ret += oldret;
-	}
-	if ((data)->negState) {
-		size_t oldret = ret;
-		ret = 0;
-		e = encode_enumerated(p, len, (data)->negState, &l);
-		BACK;
-		e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 0,
-					   &l);
-		BACK;
-		ret += oldret;
-	}
-	e = der_put_length_and_tag(p, len, ret, ASN1_C_UNIV, CONS, UT_Sequence,
-				   &l);
-	BACK;
-	*size = ret;
-	return (0);
-}
-
-static int
-decode_NegTokenResp(const unsigned char *p, size_t len, NegTokenResp *data,
-		    size_t *size) {
-	size_t ret = 0, reallen;
-	size_t l;
-	int e;
-
-	/* cppcheck-suppress uninitvar */
-	memset(data, 0, sizeof(*data));
-	reallen = 0;
-	e = der_match_tag_and_length(p, len, ASN1_C_UNIV, CONS, UT_Sequence,
-				     &reallen, &l);
-	FORW;
-	{
-		int dce_fix;
-		if ((dce_fix = fix_dce(reallen, &len)) < 0) {
-			return (ASN1_BAD_FORMAT);
-		}
-		{
-			size_t newlen, oldlen;
-
-			e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 0, &l);
-			if (e) {
-				(data)->negState = NULL;
-			} else {
-				p += l;
-				len -= l;
-				ret += l;
-				e = der_get_length(p, len, &newlen, &l);
-				FORW;
-				{
-					int mydce_fix;
-					oldlen = len;
-					if ((mydce_fix = fix_dce(newlen,
-								 &len)) < 0) {
-						return (ASN1_BAD_FORMAT);
-					}
-					(data)->negState = malloc(
-						sizeof(*(data)->negState));
-					if ((data)->negState == NULL) {
-						return (ENOMEM);
-					}
-					e = decode_enumerated(
-						p, len, (data)->negState, &l);
-					FORW;
-					if (mydce_fix) {
-						e = der_match_tag_and_length(
-							p, len, (Der_class)0,
-							(Der_type)0, 0,
-							&reallen, &l);
-						FORW;
-					} else {
-						len = oldlen - newlen;
-					}
-				}
-			}
-		}
-		{
-			size_t newlen, oldlen;
-
-			e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 1, &l);
-			if (e) {
-				(data)->supportedMech = NULL;
-			} else {
-				p += l;
-				len -= l;
-				ret += l;
-				e = der_get_length(p, len, &newlen, &l);
-				FORW;
-				{
-					int mydce_fix;
-					oldlen = len;
-					if ((mydce_fix = fix_dce(newlen,
-								 &len)) < 0) {
-						return (ASN1_BAD_FORMAT);
-					}
-					(data)->supportedMech = malloc(
-						sizeof(*(data)->supportedMech));
-					if ((data)->supportedMech == NULL) {
-						return (ENOMEM);
-					}
-					e = decode_MechType(
-						p, len, (data)->supportedMech,
-						&l);
-					FORW;
-					if (mydce_fix) {
-						e = der_match_tag_and_length(
-							p, len, (Der_class)0,
-							(Der_type)0, 0,
-							&reallen, &l);
-						FORW;
-					} else {
-						len = oldlen - newlen;
-					}
-				}
-			}
-		}
-		{
-			size_t newlen, oldlen;
-
-			e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 2, &l);
-			if (e) {
-				(data)->responseToken = NULL;
-			} else {
-				p += l;
-				len -= l;
-				ret += l;
-				e = der_get_length(p, len, &newlen, &l);
-				FORW;
-				{
-					int mydce_fix;
-					oldlen = len;
-					if ((mydce_fix = fix_dce(newlen,
-								 &len)) < 0) {
-						return (ASN1_BAD_FORMAT);
-					}
-					(data)->responseToken = malloc(
-						sizeof(*(data)->responseToken));
-					if ((data)->responseToken == NULL) {
-						return (ENOMEM);
-					}
-					e = decode_octet_string(
-						p, len, (data)->responseToken,
-						&l);
-					FORW;
-					if (mydce_fix) {
-						e = der_match_tag_and_length(
-							p, len, (Der_class)0,
-							(Der_type)0, 0,
-							&reallen, &l);
-						FORW;
-					} else {
-						len = oldlen - newlen;
-					}
-				}
-			}
-		}
-		{
-			size_t newlen, oldlen;
-
-			e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 3, &l);
-			if (e) {
-				(data)->mechListMIC = NULL;
-			} else {
-				p += l;
-				len -= l;
-				ret += l;
-				e = der_get_length(p, len, &newlen, &l);
-				FORW;
-				{
-					int mydce_fix;
-					oldlen = len;
-					if ((mydce_fix = fix_dce(newlen,
-								 &len)) < 0) {
-						return (ASN1_BAD_FORMAT);
-					}
-					(data)->mechListMIC = malloc(
-						sizeof(*(data)->mechListMIC));
-					if ((data)->mechListMIC == NULL) {
-						return (ENOMEM);
-					}
-					e = decode_octet_string(
-						p, len, (data)->mechListMIC,
-						&l);
-					FORW;
-					if (mydce_fix) {
-						e = der_match_tag_and_length(
-							p, len, (Der_class)0,
-							(Der_type)0, 0,
-							&reallen, &l);
-						FORW;
-					} else {
-						len = oldlen - newlen;
-					}
-				}
-			}
-		}
-		if (dce_fix) {
-			e = der_match_tag_and_length(p, len, (Der_class)0,
-						     (Der_type)0, 0, &reallen,
-						     &l);
-			FORW;
-		}
-	}
-	if (size) {
-		*size = ret;
-	}
-	return (0);
-fail:
-	free_NegTokenResp(data);
-	return (e);
-}
-
-static void
-free_NegTokenResp(NegTokenResp *data) {
-	if ((data)->negState) {
-		free((data)->negState);
-		(data)->negState = NULL;
-	}
-	if ((data)->supportedMech) {
-		free_MechType((data)->supportedMech);
-		free((data)->supportedMech);
-		(data)->supportedMech = NULL;
-	}
-	if ((data)->responseToken) {
-		free_octet_string((data)->responseToken);
-		free((data)->responseToken);
-		(data)->responseToken = NULL;
-	}
-	if ((data)->mechListMIC) {
-		free_octet_string((data)->mechListMIC);
-		free((data)->mechListMIC);
-		(data)->mechListMIC = NULL;
-	}
-}
-
-/* unused function: length_NegTokenResp */
-
-/* unused function: copy_NegTokenResp */
-
-/* Generated from spnego.asn1 */
-/* Do not edit */
-
-/* CHOICE */
-/* unused variable: asn1_NegotiationToken_dummy_holder */
diff -Nru bind9-9.16.13/lib/dns/spnego_asn1.pl bind9-9.16.15/lib/dns/spnego_asn1.pl
--- bind9-9.16.13/lib/dns/spnego_asn1.pl	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/spnego_asn1.pl	1970-01-01 01:00:00.000000000 +0100
@@ -1,191 +0,0 @@
-#!/bin/bin/perl -w
-#
-# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-#
-# 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 https://mozilla.org/MPL/2.0/.
-#
-# See the COPYRIGHT file distributed with this work for additional
-# information regarding copyright ownership.
-
-# Our SPNEGO implementation uses some functions generated by the
-# Heimdal ASN.1 compiler, which this script then whacks a bit to make
-# them work properly in this stripped down implementation.  We don't
-# want to require our users to have a copy of the compiler, so we ship
-# the output of this script, but we need to keep the script around in
-# any case to cope with future changes to the SPNEGO ASN.1 code, so we
-# might as well supply the script for users who want it.
-
-# Overall plan: run the ASN.1 compiler, run each of its output files
-# through indent, fix up symbols and whack everything to be static.
-# We use indent for two reasons: (1) to whack the Heimdal compiler's
-# output into something closer to ISC's coding standard, and (2) to
-# make it easier for this script to parse the result.
-
-# Output from this script is C code which we expect to be #included
-# into another C file, which is why everything generated by this
-# script is marked "static".  The intent is to minimize the number of
-# extern symbols exported by the SPNEGO implementation, to avoid
-# potential conflicts with the GSSAPI libraries.
-
-###
-
-# Filename of the ASN.1 specification.  Hardcoded for the moment
-# since this script is intended for compiling exactly one module.
-
-my $asn1_source = $ENV{ASN1_SOURCE} || "spnego.asn1";
-
-# Heimdal ASN.1 compiler.  This script was written using the version
-# from Heimdal 0.7.1.  To build this, download a copy of
-# heimdal-0.7.1.tar.gz, configure and build with the default options,
-# then look for the compiler in heimdal-0.7.1/lib/asn1/asn1_compile.
-
-my $asn1_compile = $ENV{ASN1_COMPILE} || "asn1_compile";
-
-# BSD indent program.  This script was written using the version of
-# indent that comes with FreeBSD 4.11-STABLE.  The GNU project, as
-# usual, couldn't resist the temptation to monkey with indent's
-# command line syntax, so this probably won't work with GNU indent.
-
-my $indent = $ENV{INDENT} || "indent";
-
-###
-
-# Step 1: run the compiler.  Input is the ASN.1 file.  Outputs are a
-# header file (name specified on command line without the .h suffix),
-# a file called "asn1_files" listing the names of the other output
-# files, and a set of files containing C code generated by the
-# compiler for each data type that the compiler found.
-
-if (! -r $asn1_source || system($asn1_compile, $asn1_source, "asn1")) {
-    die("Couldn't compile ASN.1 source file $asn1_source\n");
-}
-
-my @files = ("asn1.h");
-
-open(F, "asn1_files")
-    or die("Couldn't open asn1_files: $!\n");
-push(@files, split)
-    while (<F>);
-close(F);
-
-unlink("asn1_files");
-
-###
-
-# Step 2: generate header block.
-
-print(q~/*
- * Copyright (C) 2006  Internet Systems Consortium, Inc. ("ISC")
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-/*! \file
- * \brief Method routines generated from SPNEGO ASN.1 module.
- * See spnego_asn1.pl for details.  Do not edit.
- */
-
-~);
-
-###
-
-# Step 3: read and process each generated file, then delete it.
-
-my $output;
-
-for my $file (@files) {
-
-    my $is_static = 0;
-
-    system($indent, "-di1", "-ldi1", $file) == 0
-	or die("Couldn't indent $file");
-
-    unlink("$file.BAK");
-
-    open(F, $file)
-	or die("Couldn't open $file: $!");
-
-    while (<F>) {
-
-	# Symbol name fixups
-
-	s/heim_general_string/general_string/g;
-	s/heim_octet_string/octet_string/g;
-	s/heim_oid/oid/g;
-	s/heim_utf8_string/utf8_string/g;
-
-	# Convert all externs to statics
-
-	if (/^static/) {
-	    $is_static = 1;
-	}
-
-	if (!/^typedef/ &&
-	    !$is_static &&
-	    /^[A-Za-z_][0-9A-Za-z_]*[ \t]*($|[^:0-9A-Za-z_])/) {
-	    $_ = "static " . $_;
-	    $is_static = 1;
-	}
-
-	if (/[{};]/) {
-	    $is_static = 0;
-	}
-
-	# Suppress file inclusion, pass anything else through
-
-	if (!/#include/) {
-	    $output .= $_;
-	}
-    }
-
-    close(F);
-    unlink($file);
-}
-
-# Step 4: Delete unused stuff to avoid code bloat and compiler warnings.
-
-my @unused_functions = qw(ContextFlags2int
-			  int2ContextFlags
-			  asn1_ContextFlags_units
-			  length_NegTokenInit
-			  copy_NegTokenInit
-			  length_NegTokenResp
-			  copy_NegTokenResp
-			  length_MechTypeList
-			  length_MechType
-			  copy_MechTypeList
-			  length_ContextFlags
-			  copy_ContextFlags
-			  copy_MechType);
-
-$output =~ s<^static [^\n]+\n$_\(.+?^}></* unused function: $_ */\n>ms
-    foreach (@unused_functions);
-
-$output =~ s<^static .+$_\(.*\);$></* unused declaration: $_ */>m
-    foreach (@unused_functions);
-
-$output =~ s<^static struct units ContextFlags_units\[\].+?^};>
-            </* unused variable: ContextFlags_units */>ms;
-
-$output =~ s<^static int asn1_NegotiationToken_dummy_holder = 1;>
-            </* unused variable: asn1_NegotiationToken_dummy_holder */>ms;
-
-$output =~ s<^static void\nfree_ContextFlags\(ContextFlags \* data\)\n{\n>
-            <$&\t(void)data;\n>ms;
-
-# Step 5: Write the result.
-
-print($output);
-
diff -Nru bind9-9.16.13/lib/dns/spnego.c bind9-9.16.15/lib/dns/spnego.c
--- bind9-9.16.13/lib/dns/spnego.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/spnego.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,1759 +0,0 @@
-/*
- * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
- *
- * 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 https://mozilla.org/MPL/2.0/.
- *
- * See the COPYRIGHT file distributed with this work for additional
- * information regarding copyright ownership.
- */
-
-/*! \file
- * \brief
- * Portable SPNEGO implementation.
- *
- * This is part of a portable implementation of the SPNEGO protocol
- * (RFCs 2478 and 4178).  This implementation uses the RFC 4178 ASN.1
- * module but is not a full implementation of the RFC 4178 protocol;
- * at the moment, we only support GSS-TSIG with Kerberos
- * authentication, so we only need enough of the SPNEGO protocol to
- * support that.
- *
- * The files that make up this portable SPNEGO implementation are:
- * \li	spnego.c	(this file)
- * \li	spnego.h	(API SPNEGO exports to the rest of lib/dns)
- * \li	spnego.asn1	(SPNEGO ASN.1 module)
- * \li	spnego_asn1.c	(routines generated from spngo.asn1)
- * \li	spnego_asn1.pl	(perl script to generate spnego_asn1.c)
- *
- * Everything but the functions exported in spnego.h is static, to
- * avoid possible conflicts with other libraries (particularly Heimdal,
- * since much of this code comes from Heimdal by way of mod_auth_kerb).
- *
- * spnego_asn1.c is shipped as part of lib/dns because generating it
- * requires both Perl and the Heimdal ASN.1 compiler.  See
- * spnego_asn1.pl for further details.  We've tried to eliminate all
- * compiler warnings from the generated code, but you may see a few
- * when using a compiler version we haven't tested yet.
- */
-
-/*
- * Portions of this code were derived from mod_auth_kerb and Heimdal.
- * These packages are available from:
- *
- *   http://modauthkerb.sourceforge.net/
- *   http://www.pdc.kth.se/heimdal/
- *
- * and were released under the following licenses:
- *
- * ----------------------------------------------------------------
- *
- * Copyright (c) 2004 Masarykova universita
- * (Masaryk University, Brno, Czech Republic)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * 3. Neither the name of the University nor the names of its contributors may
- *    be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * ----------------------------------------------------------------
- *
- * Copyright (c) 1997 - 2003 Kungliga Tekniska Högskolan
- * (Royal Institute of Technology, Stockholm, Sweden).
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * 3. Neither the name of the Institute nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-/*
- * XXXSRA We should omit this file entirely in Makefile.in via autoconf,
- * but this will keep it from generating errors until that's written.
- */
-
-#ifdef GSSAPI
-
-/*
- * XXXSRA Some of the following files are almost certainly unnecessary,
- * but using this list (borrowed from gssapictx.c) gets rid of some
- * whacky compilation errors when building with MSVC and should be
- * harmless in any case.
- */
-
-#include <errno.h>
-#include <inttypes.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-#include <isc/buffer.h>
-#include <isc/dir.h>
-#include <isc/lex.h>
-#include <isc/mem.h>
-#include <isc/once.h>
-#include <isc/random.h>
-#include <isc/safe.h>
-#include <isc/string.h>
-#include <isc/time.h>
-#include <isc/util.h>
-
-#include <dns/fixedname.h>
-#include <dns/keyvalues.h>
-#include <dns/log.h>
-#include <dns/name.h>
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-#include <dns/result.h>
-#include <dns/types.h>
-
-#include <dst/gssapi.h>
-#include <dst/result.h>
-
-#include "dst_internal.h"
-
-/*
- * The API we export
- */
-#include "spnego.h"
-
-/* asn1_err.h */
-/* Generated from ../../../lib/asn1/asn1_err.et */
-
-#ifndef ERROR_TABLE_BASE_asn1
-/* these may be brought in already via gssapi_krb5.h */
-typedef enum asn1_error_number {
-	ASN1_BAD_TIMEFORMAT = 1859794432,
-	ASN1_MISSING_FIELD = 1859794433,
-	ASN1_MISPLACED_FIELD = 1859794434,
-	ASN1_TYPE_MISMATCH = 1859794435,
-	ASN1_OVERFLOW = 1859794436,
-	ASN1_OVERRUN = 1859794437,
-	ASN1_BAD_ID = 1859794438,
-	ASN1_BAD_LENGTH = 1859794439,
-	ASN1_BAD_FORMAT = 1859794440,
-	ASN1_PARSE_ERROR = 1859794441
-} asn1_error_number;
-
-#define ERROR_TABLE_BASE_asn1 1859794432
-#endif /* ifndef ERROR_TABLE_BASE_asn1 */
-
-#define __asn1_common_definitions__
-
-typedef struct octet_string {
-	size_t length;
-	void *data;
-} octet_string;
-
-typedef char *general_string;
-
-typedef char *utf8_string;
-
-typedef struct oid {
-	size_t length;
-	unsigned *components;
-} oid;
-
-/* der.h */
-
-typedef enum {
-	ASN1_C_UNIV = 0,
-	ASN1_C_APPL = 1,
-	ASN1_C_CONTEXT = 2,
-	ASN1_C_PRIVATE = 3
-} Der_class;
-
-typedef enum { PRIM = 0, CONS = 1 } Der_type;
-
-/* Universal tags */
-
-enum {
-	UT_Boolean = 1,
-	UT_Integer = 2,
-	UT_BitString = 3,
-	UT_OctetString = 4,
-	UT_Null = 5,
-	UT_OID = 6,
-	UT_Enumerated = 10,
-	UT_Sequence = 16,
-	UT_Set = 17,
-	UT_PrintableString = 19,
-	UT_IA5String = 22,
-	UT_UTCTime = 23,
-	UT_GeneralizedTime = 24,
-	UT_VisibleString = 26,
-	UT_GeneralString = 27
-};
-
-#define ASN1_INDEFINITE 0xdce0deed
-
-static int
-der_get_length(const unsigned char *p, size_t len, size_t *val, size_t *size);
-
-static int
-der_get_octet_string(const unsigned char *p, size_t len, octet_string *data,
-		     size_t *size);
-static int
-der_get_oid(const unsigned char *p, size_t len, oid *data, size_t *size);
-static int
-der_get_tag(const unsigned char *p, size_t len, Der_class *xclass,
-	    Der_type *type, int *tag, size_t *size);
-
-static int
-der_match_tag(const unsigned char *p, size_t len, Der_class xclass,
-	      Der_type type, int tag, size_t *size);
-static int
-der_match_tag_and_length(const unsigned char *p, size_t len, Der_class xclass,
-			 Der_type type, int tag, size_t *length_ret,
-			 size_t *size);
-
-static int
-decode_oid(const unsigned char *p, size_t len, oid *k, size_t *size);
-
-static int
-decode_enumerated(const unsigned char *p, size_t len, void *num, size_t *size);
-
-static int
-decode_octet_string(const unsigned char *, size_t, octet_string *, size_t *);
-
-static int
-der_put_int(unsigned char *p, size_t len, int val, size_t *);
-
-static int
-der_put_length(unsigned char *p, size_t len, size_t val, size_t *);
-
-static int
-der_put_octet_string(unsigned char *p, size_t len, const octet_string *data,
-		     size_t *);
-static int
-der_put_oid(unsigned char *p, size_t len, const oid *data, size_t *size);
-static int
-der_put_tag(unsigned char *p, size_t len, Der_class xclass, Der_type type,
-	    int tag, size_t *);
-static int
-der_put_length_and_tag(unsigned char *, size_t, size_t, Der_class, Der_type,
-		       int, size_t *);
-
-static int
-encode_enumerated(unsigned char *p, size_t len, const void *data, size_t *);
-
-static int
-encode_octet_string(unsigned char *p, size_t len, const octet_string *k,
-		    size_t *);
-static int
-encode_oid(unsigned char *p, size_t len, const oid *k, size_t *);
-
-static void
-free_octet_string(octet_string *k);
-
-static void
-free_oid(oid *k);
-
-static size_t
-length_len(size_t len);
-
-static int
-fix_dce(size_t reallen, size_t *len);
-
-/*
- * Include stuff generated by the ASN.1 compiler.
- */
-
-#include "spnego_asn1.c"
-
-/*
- * Force the oid arrays to be uint64_t aligned to silence warnings
- * about the arrays not being properly aligned for (void *).
- */
-typedef union {
-	unsigned char b[8];
-	uint64_t _align;
-} aligned8;
-typedef union {
-	unsigned char b[16];
-	uint64_t _align[2];
-} aligned16;
-
-static aligned16 gss_krb5_mech_oid_bytes = { { 0x2a, 0x86, 0x48, 0x86, 0xf7,
-					       0x12, 0x01, 0x02, 0x02 } };
-
-static gss_OID_desc gss_krb5_mech_oid_desc = { 9, gss_krb5_mech_oid_bytes.b };
-
-static gss_OID GSS_KRB5_MECH = &gss_krb5_mech_oid_desc;
-
-static aligned16 gss_mskrb5_mech_oid_bytes = { { 0x2a, 0x86, 0x48, 0x82, 0xf7,
-						 0x12, 0x01, 0x02, 0x02 } };
-
-static gss_OID_desc gss_mskrb5_mech_oid_desc = { 9,
-						 gss_mskrb5_mech_oid_bytes.b };
-
-static gss_OID GSS_MSKRB5_MECH = &gss_mskrb5_mech_oid_desc;
-
-static aligned8 gss_spnego_mech_oid_bytes = { { 0x2b, 0x06, 0x01, 0x05, 0x05,
-						0x02 } };
-
-static gss_OID_desc gss_spnego_mech_oid_desc = { 6,
-						 gss_spnego_mech_oid_bytes.b };
-
-static gss_OID GSS_SPNEGO_MECH = &gss_spnego_mech_oid_desc;
-
-/* spnegokrb5_locl.h */
-
-static OM_uint32
-gssapi_spnego_encapsulate(OM_uint32 *, unsigned char *, size_t, gss_buffer_t,
-			  const gss_OID);
-
-static OM_uint32
-gssapi_spnego_decapsulate(OM_uint32 *, gss_buffer_t, unsigned char **, size_t *,
-			  const gss_OID);
-
-/* mod_auth_kerb.c */
-
-/*
- * Check whether 'token' matches the SPNEGO OID.  If it does, return 'true';
- * otherwise, return 'false'.
- */
-static bool
-cmp_gss_type(gss_buffer_t token, gss_OID gssoid) {
-	unsigned char *p;
-	size_t len;
-
-	if (token->length == 0U) {
-		return (false);
-	}
-
-	p = token->value;
-	if (*p++ != 0x60) {
-		return (false);
-	}
-	len = *p++;
-	if (len & 0x80) {
-		if ((len & 0x7f) > 4U) {
-			return (false);
-		}
-		p += len & 0x7f;
-	}
-	if (*p++ != 0x06) {
-		return (false);
-	}
-
-	if (((OM_uint32)*p++) != gssoid->length) {
-		return (false);
-	}
-
-	return (isc_safe_memequal(p, gssoid->elements, gssoid->length));
-}
-
-/* accept_sec_context.c */
-/*
- * SPNEGO wrapper for Kerberos5 GSS-API kouril@ics.muni.cz, 2003 (mostly
- * based on Heimdal code)
- */
-
-static OM_uint32
-code_NegTokenArg(OM_uint32 *minor_status, const NegTokenResp *resp,
-		 unsigned char **outbuf, size_t *outbuf_size) {
-	OM_uint32 ret;
-	u_char *buf;
-	size_t buf_size, buf_len = 0;
-
-	buf_size = 1024;
-	buf = malloc(buf_size);
-	if (buf == NULL) {
-		*minor_status = ENOMEM;
-		return (GSS_S_FAILURE);
-	}
-	do {
-		ret = encode_NegTokenResp(buf + buf_size - 1, buf_size, resp,
-					  &buf_len);
-		if (ret == 0) {
-			size_t tmp;
-
-			ret = der_put_length_and_tag(
-				buf + buf_size - buf_len - 1,
-				buf_size - buf_len, buf_len, ASN1_C_CONTEXT,
-				CONS, 1, &tmp);
-			if (ret == 0) {
-				buf_len += tmp;
-			}
-		}
-		if (ret) {
-			if (ret == ASN1_OVERFLOW) {
-				u_char *tmp;
-
-				buf_size *= 2;
-				tmp = realloc(buf, buf_size);
-				if (tmp == NULL) {
-					*minor_status = ENOMEM;
-					free(buf);
-					return (GSS_S_FAILURE);
-				}
-				buf = tmp;
-			} else {
-				*minor_status = ret;
-				free(buf);
-				return (GSS_S_FAILURE);
-			}
-		}
-	} while (ret == ASN1_OVERFLOW);
-
-	*outbuf = malloc(buf_len);
-	if (*outbuf == NULL) {
-		*minor_status = ENOMEM;
-		free(buf);
-		return (GSS_S_FAILURE);
-	}
-	memmove(*outbuf, buf + buf_size - buf_len, buf_len);
-	*outbuf_size = buf_len;
-
-	free(buf);
-
-	return (GSS_S_COMPLETE);
-}
-
-static OM_uint32
-send_reject(OM_uint32 *minor_status, gss_buffer_t output_token) {
-	NegTokenResp resp;
-	OM_uint32 ret;
-
-	resp.negState = malloc(sizeof(*resp.negState));
-	if (resp.negState == NULL) {
-		*minor_status = ENOMEM;
-		return (GSS_S_FAILURE);
-	}
-	*(resp.negState) = reject;
-
-	resp.supportedMech = NULL;
-	resp.responseToken = NULL;
-	resp.mechListMIC = NULL;
-
-	ret = code_NegTokenArg(minor_status, &resp,
-			       (unsigned char **)&output_token->value,
-			       &output_token->length);
-	free_NegTokenResp(&resp);
-	if (ret) {
-		return (ret);
-	}
-
-	return (GSS_S_BAD_MECH);
-}
-
-static OM_uint32
-send_accept(OM_uint32 *minor_status, gss_buffer_t output_token,
-	    gss_buffer_t mech_token, const gss_OID pref) {
-	NegTokenResp resp;
-	OM_uint32 ret;
-
-	memset(&resp, 0, sizeof(resp));
-	resp.negState = malloc(sizeof(*resp.negState));
-	if (resp.negState == NULL) {
-		*minor_status = ENOMEM;
-		return (GSS_S_FAILURE);
-	}
-	*(resp.negState) = accept_completed;
-
-	resp.supportedMech = malloc(sizeof(*resp.supportedMech));
-	if (resp.supportedMech == NULL) {
-		free_NegTokenResp(&resp);
-		*minor_status = ENOMEM;
-		return (GSS_S_FAILURE);
-	}
-	ret = der_get_oid(pref->elements, pref->length, resp.supportedMech,
-			  NULL);
-	if (ret) {
-		free_NegTokenResp(&resp);
-		*minor_status = ENOMEM;
-		return (GSS_S_FAILURE);
-	}
-	if (mech_token != NULL && mech_token->length != 0U) {
-		resp.responseToken = malloc(sizeof(*resp.responseToken));
-		if (resp.responseToken == NULL) {
-			free_NegTokenResp(&resp);
-			*minor_status = ENOMEM;
-			return (GSS_S_FAILURE);
-		}
-		resp.responseToken->length = mech_token->length;
-		resp.responseToken->data = mech_token->value;
-	}
-
-	ret = code_NegTokenArg(minor_status, &resp,
-			       (unsigned char **)&output_token->value,
-			       &output_token->length);
-	if (resp.responseToken != NULL) {
-		free(resp.responseToken);
-		resp.responseToken = NULL;
-	}
-	free_NegTokenResp(&resp);
-	if (ret) {
-		return (ret);
-	}
-
-	return (GSS_S_COMPLETE);
-}
-
-OM_uint32
-gss_accept_sec_context_spnego(OM_uint32 *minor_status,
-			      gss_ctx_id_t *context_handle,
-			      const gss_cred_id_t acceptor_cred_handle,
-			      const gss_buffer_t input_token_buffer,
-			      const gss_channel_bindings_t input_chan_bindings,
-			      gss_name_t *src_name, gss_OID *mech_type,
-			      gss_buffer_t output_token, OM_uint32 *ret_flags,
-			      OM_uint32 *time_rec,
-			      gss_cred_id_t *delegated_cred_handle) {
-	NegTokenInit init_token;
-	OM_uint32 major_status = GSS_S_COMPLETE;
-	OM_uint32 minor_status2;
-	gss_buffer_desc ibuf, obuf;
-	gss_buffer_t ot = NULL;
-	gss_OID pref = GSS_KRB5_MECH;
-	unsigned char *buf;
-	size_t buf_size;
-	size_t len, taglen, ni_len;
-	int found = 0;
-	int ret;
-	unsigned i;
-
-	/*
-	 * Before doing anything else, see whether this is a SPNEGO
-	 * PDU.  If not, dispatch to the GSSAPI library and get out.
-	 */
-
-	if (!cmp_gss_type(input_token_buffer, GSS_SPNEGO_MECH)) {
-		return (gss_accept_sec_context(
-			minor_status, context_handle, acceptor_cred_handle,
-			input_token_buffer, input_chan_bindings, src_name,
-			mech_type, output_token, ret_flags, time_rec,
-			delegated_cred_handle));
-	}
-
-	/*
-	 * If we get here, it's SPNEGO.
-	 */
-
-	memset(&init_token, 0, sizeof(init_token));
-
-	ret = gssapi_spnego_decapsulate(minor_status, input_token_buffer, &buf,
-					&buf_size, GSS_SPNEGO_MECH);
-	if (ret) {
-		return (ret);
-	}
-
-	ret = der_match_tag_and_length(buf, buf_size, ASN1_C_CONTEXT, CONS, 0,
-				       &len, &taglen);
-	if (ret) {
-		return (ret);
-	}
-
-	ret = decode_NegTokenInit(buf + taglen, len, &init_token, &ni_len);
-	if (ret) {
-		*minor_status = EINVAL; /* XXX */
-		return (GSS_S_DEFECTIVE_TOKEN);
-	}
-
-	for (i = 0; !found && i < init_token.mechTypes.len; ++i) {
-		unsigned char mechbuf[17];
-		size_t mech_len;
-
-		ret = der_put_oid(mechbuf + sizeof(mechbuf) - 1,
-				  sizeof(mechbuf), &init_token.mechTypes.val[i],
-				  &mech_len);
-		if (ret) {
-			free_NegTokenInit(&init_token);
-			return (GSS_S_DEFECTIVE_TOKEN);
-		}
-		if (mech_len == GSS_KRB5_MECH->length &&
-		    isc_safe_memequal(GSS_KRB5_MECH->elements,
-				      mechbuf + sizeof(mechbuf) - mech_len,
-				      mech_len))
-		{
-			found = 1;
-			break;
-		}
-		if (mech_len == GSS_MSKRB5_MECH->length &&
-		    isc_safe_memequal(GSS_MSKRB5_MECH->elements,
-				      mechbuf + sizeof(mechbuf) - mech_len,
-				      mech_len))
-		{
-			found = 1;
-			if (i == 0) {
-				pref = GSS_MSKRB5_MECH;
-			}
-			break;
-		}
-	}
-
-	if (!found) {
-		free_NegTokenInit(&init_token);
-		return (send_reject(minor_status, output_token));
-	}
-
-	if (i == 0 && init_token.mechToken != NULL) {
-		ibuf.length = init_token.mechToken->length;
-		ibuf.value = init_token.mechToken->data;
-
-		major_status = gss_accept_sec_context(
-			minor_status, context_handle, acceptor_cred_handle,
-			&ibuf, input_chan_bindings, src_name, mech_type, &obuf,
-			ret_flags, time_rec, delegated_cred_handle);
-		if (GSS_ERROR(major_status)) {
-			free_NegTokenInit(&init_token);
-			send_reject(&minor_status2, output_token);
-			return (major_status);
-		}
-		ot = &obuf;
-	}
-	ret = send_accept(&minor_status2, output_token, ot, pref);
-	free_NegTokenInit(&init_token);
-	if (ot != NULL && ot->length != 0U) {
-		gss_release_buffer(&minor_status2, ot);
-	}
-
-	return (ret != GSS_S_COMPLETE ? (OM_uint32)ret : major_status);
-}
-
-/* decapsulate.c */
-
-static OM_uint32
-gssapi_verify_mech_header(u_char **str, size_t total_len, const gss_OID mech) {
-	size_t len, len_len, mech_len, foo;
-	int e;
-	u_char *p = *str;
-
-	if (total_len < 1U) {
-		return (GSS_S_DEFECTIVE_TOKEN);
-	}
-	if (*p++ != 0x60) {
-		return (GSS_S_DEFECTIVE_TOKEN);
-	}
-	e = der_get_length(p, total_len - 1, &len, &len_len);
-	if (e || 1 + len_len + len != total_len) {
-		return (GSS_S_DEFECTIVE_TOKEN);
-	}
-	p += len_len;
-	if (*p++ != 0x06) {
-		return (GSS_S_DEFECTIVE_TOKEN);
-	}
-	e = der_get_length(p, total_len - 1 - len_len - 1, &mech_len, &foo);
-	if (e) {
-		return (GSS_S_DEFECTIVE_TOKEN);
-	}
-	p += foo;
-	if (mech_len != mech->length) {
-		return (GSS_S_BAD_MECH);
-	}
-	if (!isc_safe_memequal(p, mech->elements, mech->length)) {
-		return (GSS_S_BAD_MECH);
-	}
-	p += mech_len;
-	*str = p;
-	return (GSS_S_COMPLETE);
-}
-
-/*
- * Remove the GSS-API wrapping from `in_token' giving `buf and buf_size' Does
- * not copy data, so just free `in_token'.
- */
-
-static OM_uint32
-gssapi_spnego_decapsulate(OM_uint32 *minor_status,
-			  gss_buffer_t input_token_buffer, unsigned char **buf,
-			  size_t *buf_len, const gss_OID mech) {
-	u_char *p;
-	OM_uint32 ret;
-
-	p = input_token_buffer->value;
-	ret = gssapi_verify_mech_header(&p, input_token_buffer->length, mech);
-	if (ret) {
-		*minor_status = ret;
-		return (GSS_S_FAILURE);
-	}
-	*buf_len = input_token_buffer->length -
-		   (p - (u_char *)input_token_buffer->value);
-	*buf = p;
-	return (GSS_S_COMPLETE);
-}
-
-/* der_free.c */
-
-static void
-free_octet_string(octet_string *k) {
-	free(k->data);
-	k->data = NULL;
-}
-
-static void
-free_oid(oid *k) {
-	free(k->components);
-	k->components = NULL;
-}
-
-/* der_get.c */
-
-/*
- * All decoding functions take a pointer `p' to first position in which to
- * read, from the left, `len' which means the maximum number of characters we
- * are able to read, `ret' were the value will be returned and `size' where
- * the number of used bytes is stored. Either 0 or an error code is returned.
- */
-
-static int
-der_get_unsigned(const unsigned char *p, size_t len, unsigned *ret,
-		 size_t *size) {
-	unsigned val = 0;
-	size_t oldlen = len;
-
-	while (len--) {
-		val = val * 256 + *p++;
-	}
-	*ret = val;
-	if (size) {
-		*size = oldlen;
-	}
-	return (0);
-}
-
-static int
-der_get_int(const unsigned char *p, size_t len, int *ret, size_t *size) {
-	int val = 0;
-	size_t oldlen = len;
-
-	if (len > 0U) {
-		val = (signed char)*p++;
-		while (--len) {
-			val = val * 256 + *p++;
-		}
-	}
-	*ret = val;
-	if (size) {
-		*size = oldlen;
-	}
-	return (0);
-}
-
-static int
-der_get_length(const unsigned char *p, size_t len, size_t *val, size_t *size) {
-	size_t v;
-
-	if (len <= 0U) {
-		return (ASN1_OVERRUN);
-	}
-	--len;
-	v = *p++;
-	if (v < 128U) {
-		*val = v;
-		if (size) {
-			*size = 1;
-		}
-	} else {
-		int e;
-		size_t l;
-		unsigned tmp;
-
-		if (v == 0x80U) {
-			*val = ASN1_INDEFINITE;
-			if (size) {
-				*size = 1;
-			}
-			return (0);
-		}
-		v &= 0x7F;
-		if (len < v) {
-			return (ASN1_OVERRUN);
-		}
-		e = der_get_unsigned(p, v, &tmp, &l);
-		if (e) {
-			return (e);
-		}
-		*val = tmp;
-		if (size) {
-			*size = l + 1;
-		}
-	}
-	return (0);
-}
-
-static int
-der_get_octet_string(const unsigned char *p, size_t len, octet_string *data,
-		     size_t *size) {
-	data->length = len;
-	if (len != 0U) {
-		data->data = malloc(len);
-		if (data->data == NULL) {
-			return (ENOMEM);
-		}
-		memmove(data->data, p, len);
-	} else {
-		data->data = NULL;
-	}
-	if (size) {
-		*size = len;
-	}
-	return (0);
-}
-
-static int
-der_get_oid(const unsigned char *p, size_t len, oid *data, size_t *size) {
-	int n;
-	size_t oldlen = len;
-
-	data->components = NULL;
-	data->length = 0;
-	if (len < 1U) {
-		return (ASN1_OVERRUN);
-	}
-
-	data->components = malloc((len + 1) * sizeof(*data->components));
-	if (data->components == NULL) {
-		return (ENOMEM);
-	}
-	data->components[0] = (*p) / 40;
-	data->components[1] = (*p) % 40;
-	--len;
-	++p;
-	for (n = 2; len > 0U; ++n) {
-		unsigned u = 0;
-
-		do {
-			--len;
-			u = u * 128 + (*p++ % 128);
-		} while (len > 0U && p[-1] & 0x80);
-		data->components[n] = u;
-	}
-	if (p[-1] & 0x80) {
-		free_oid(data);
-		return (ASN1_OVERRUN);
-	}
-	data->length = n;
-	if (size) {
-		*size = oldlen;
-	}
-	return (0);
-}
-
-static int
-der_get_tag(const unsigned char *p, size_t len, Der_class *xclass,
-	    Der_type *type, int *tag, size_t *size) {
-	if (len < 1U) {
-		return (ASN1_OVERRUN);
-	}
-	*xclass = (Der_class)(((*p) >> 6) & 0x03);
-	*type = (Der_type)(((*p) >> 5) & 0x01);
-	*tag = (*p) & 0x1F;
-	if (size) {
-		*size = 1;
-	}
-	return (0);
-}
-
-static int
-der_match_tag(const unsigned char *p, size_t len, Der_class xclass,
-	      Der_type type, int tag, size_t *size) {
-	size_t l;
-	Der_class thisclass;
-	Der_type thistype;
-	int thistag;
-	int e;
-
-	e = der_get_tag(p, len, &thisclass, &thistype, &thistag, &l);
-	if (e) {
-		return (e);
-	}
-	if (xclass != thisclass || type != thistype) {
-		return (ASN1_BAD_ID);
-	}
-	if (tag > thistag) {
-		return (ASN1_MISPLACED_FIELD);
-	}
-	if (tag < thistag) {
-		return (ASN1_MISSING_FIELD);
-	}
-	if (size) {
-		*size = l;
-	}
-	return (0);
-}
-
-static int
-der_match_tag_and_length(const unsigned char *p, size_t len, Der_class xclass,
-			 Der_type type, int tag, size_t *length_ret,
-			 size_t *size) {
-	size_t l, ret = 0;
-	int e;
-
-	e = der_match_tag(p, len, xclass, type, tag, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	ret += l;
-	e = der_get_length(p, len, length_ret, &l);
-	if (e) {
-		return (e);
-	}
-	/* p += l; */
-	len -= l;
-	POST(len);
-	ret += l;
-	if (size) {
-		*size = ret;
-	}
-	return (0);
-}
-
-static int
-decode_enumerated(const unsigned char *p, size_t len, void *num, size_t *size) {
-	size_t ret = 0;
-	size_t l, reallen;
-	int e;
-
-	e = der_match_tag(p, len, ASN1_C_UNIV, PRIM, UT_Enumerated, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	ret += l;
-	e = der_get_length(p, len, &reallen, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	ret += l;
-	e = der_get_int(p, reallen, num, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	POST(p);
-	POST(len);
-	ret += l;
-	if (size) {
-		*size = ret;
-	}
-	return (0);
-}
-
-static int
-decode_octet_string(const unsigned char *p, size_t len, octet_string *k,
-		    size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-	size_t slen;
-
-	k->data = NULL;
-	k->length = 0;
-
-	e = der_match_tag(p, len, ASN1_C_UNIV, PRIM, UT_OctetString, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	ret += l;
-
-	e = der_get_length(p, len, &slen, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	ret += l;
-	if (len < slen) {
-		return (ASN1_OVERRUN);
-	}
-
-	e = der_get_octet_string(p, slen, k, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	POST(p);
-	POST(len);
-	ret += l;
-	if (size) {
-		*size = ret;
-	}
-	return (0);
-}
-
-static int
-decode_oid(const unsigned char *p, size_t len, oid *k, size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-	size_t slen;
-
-	e = der_match_tag(p, len, ASN1_C_UNIV, PRIM, UT_OID, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	ret += l;
-
-	e = der_get_length(p, len, &slen, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	ret += l;
-	if (len < slen) {
-		return (ASN1_OVERRUN);
-	}
-
-	e = der_get_oid(p, slen, k, &l);
-	if (e) {
-		return (e);
-	}
-	p += l;
-	len -= l;
-	POST(p);
-	POST(len);
-	ret += l;
-	if (size) {
-		*size = ret;
-	}
-	return (0);
-}
-
-static int
-fix_dce(size_t reallen, size_t *len) {
-	if (reallen == ASN1_INDEFINITE) {
-		return (1);
-	}
-	if (*len < reallen) {
-		return (-1);
-	}
-	*len = reallen;
-	return (0);
-}
-
-/* der_length.c */
-
-static size_t
-len_unsigned(unsigned val) {
-	size_t ret = 0;
-
-	do {
-		++ret;
-		val /= 256;
-	} while (val);
-	return (ret);
-}
-
-static size_t
-length_len(size_t len) {
-	if (len < 128U) {
-		return (1);
-	} else {
-		return (len_unsigned((unsigned int)len) + 1);
-	}
-}
-
-/* der_put.c */
-
-/*
- * All encoding functions take a pointer `p' to first position in which to
- * write, from the right, `len' which means the maximum number of characters
- * we are able to write.  The function returns the number of characters
- * written in `size' (if non-NULL). The return value is 0 or an error.
- */
-
-static int
-der_put_unsigned(unsigned char *p, size_t len, unsigned val, size_t *size) {
-	unsigned char *base = p;
-
-	if (val) {
-		while (len > 0U && val) {
-			*p-- = val % 256;
-			val /= 256;
-			--len;
-		}
-		if (val != 0) {
-			return (ASN1_OVERFLOW);
-		} else {
-			*size = base - p;
-			return (0);
-		}
-	} else if (len < 1U) {
-		return (ASN1_OVERFLOW);
-	} else {
-		*p = 0;
-		*size = 1;
-		return (0);
-	}
-}
-
-static int
-der_put_int(unsigned char *p, size_t len, int val, size_t *size) {
-	unsigned char *base = p;
-
-	if (val >= 0) {
-		do {
-			if (len < 1U) {
-				return (ASN1_OVERFLOW);
-			}
-			*p-- = val % 256;
-			len--;
-			val /= 256;
-		} while (val);
-		if (p[1] >= 128) {
-			if (len < 1U) {
-				return (ASN1_OVERFLOW);
-			}
-			*p-- = 0;
-			len--;
-			POST(len);
-		}
-	} else {
-		val = ~val;
-		do {
-			if (len < 1U) {
-				return (ASN1_OVERFLOW);
-			}
-			*p-- = ~(val % 256);
-			len--;
-			val /= 256;
-		} while (val);
-		if (p[1] < 128) {
-			if (len < 1U) {
-				return (ASN1_OVERFLOW);
-			}
-			*p-- = 0xff;
-			len--;
-			POST(len);
-		}
-	}
-	*size = base - p;
-	return (0);
-}
-
-static int
-der_put_length(unsigned char *p, size_t len, size_t val, size_t *size) {
-	if (len < 1U) {
-		return (ASN1_OVERFLOW);
-	}
-	if (val < 128U) {
-		*p = (unsigned char)val;
-		*size = 1;
-		return (0);
-	} else {
-		size_t l;
-		int e;
-
-		e = der_put_unsigned(p, len - 1, (unsigned int)val, &l);
-		if (e) {
-			return (e);
-		}
-		p -= l;
-		*p = 0x80 | (unsigned char)l;
-		*size = l + 1;
-		return (0);
-	}
-}
-
-static int
-der_put_octet_string(unsigned char *p, size_t len, const octet_string *data,
-		     size_t *size) {
-	if (len < data->length) {
-		return (ASN1_OVERFLOW);
-	}
-	p -= data->length;
-	len -= data->length;
-	POST(len);
-	memmove(p + 1, data->data, data->length);
-	*size = data->length;
-	return (0);
-}
-
-static int
-der_put_oid(unsigned char *p, size_t len, const oid *data, size_t *size) {
-	unsigned char *base = p;
-	size_t n;
-
-	for (n = data->length; n >= 3u; --n) {
-		unsigned u = data->components[n - 1];
-
-		if (len < 1U) {
-			return (ASN1_OVERFLOW);
-		}
-		*p-- = u % 128;
-		u /= 128;
-		--len;
-		while (u > 0) {
-			if (len < 1U) {
-				return (ASN1_OVERFLOW);
-			}
-			*p-- = 128 + u % 128;
-			u /= 128;
-			--len;
-		}
-	}
-	if (len < 1U) {
-		return (ASN1_OVERFLOW);
-	}
-	*p-- = 40 * data->components[0] + data->components[1];
-	*size = base - p;
-	return (0);
-}
-
-static int
-der_put_tag(unsigned char *p, size_t len, Der_class xclass, Der_type type,
-	    int tag, size_t *size) {
-	if (len < 1U) {
-		return (ASN1_OVERFLOW);
-	}
-	*p = (xclass << 6) | (type << 5) | tag; /* XXX */
-	*size = 1;
-	return (0);
-}
-
-static int
-der_put_length_and_tag(unsigned char *p, size_t len, size_t len_val,
-		       Der_class xclass, Der_type type, int tag, size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-
-	e = der_put_length(p, len, len_val, &l);
-	if (e) {
-		return (e);
-	}
-	p -= l;
-	len -= l;
-	ret += l;
-	e = der_put_tag(p, len, xclass, type, tag, &l);
-	if (e) {
-		return (e);
-	}
-	p -= l;
-	len -= l;
-	POST(p);
-	POST(len);
-	ret += l;
-	*size = ret;
-	return (0);
-}
-
-static int
-encode_enumerated(unsigned char *p, size_t len, const void *data,
-		  size_t *size) {
-	unsigned num = *(const unsigned *)data;
-	size_t ret = 0;
-	size_t l;
-	int e;
-
-	e = der_put_int(p, len, num, &l);
-	if (e) {
-		return (e);
-	}
-	p -= l;
-	len -= l;
-	ret += l;
-	e = der_put_length_and_tag(p, len, l, ASN1_C_UNIV, PRIM, UT_Enumerated,
-				   &l);
-	if (e) {
-		return (e);
-	}
-	p -= l;
-	len -= l;
-	POST(p);
-	POST(len);
-	ret += l;
-	*size = ret;
-	return (0);
-}
-
-static int
-encode_octet_string(unsigned char *p, size_t len, const octet_string *k,
-		    size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-
-	e = der_put_octet_string(p, len, k, &l);
-	if (e) {
-		return (e);
-	}
-	p -= l;
-	len -= l;
-	ret += l;
-	e = der_put_length_and_tag(p, len, l, ASN1_C_UNIV, PRIM, UT_OctetString,
-				   &l);
-	if (e) {
-		return (e);
-	}
-	p -= l;
-	len -= l;
-	POST(p);
-	POST(len);
-	ret += l;
-	*size = ret;
-	return (0);
-}
-
-static int
-encode_oid(unsigned char *p, size_t len, const oid *k, size_t *size) {
-	size_t ret = 0;
-	size_t l;
-	int e;
-
-	e = der_put_oid(p, len, k, &l);
-	if (e) {
-		return (e);
-	}
-	p -= l;
-	len -= l;
-	ret += l;
-	e = der_put_length_and_tag(p, len, l, ASN1_C_UNIV, PRIM, UT_OID, &l);
-	if (e) {
-		return (e);
-	}
-	p -= l;
-	len -= l;
-	POST(p);
-	POST(len);
-	ret += l;
-	*size = ret;
-	return (0);
-}
-
-/* encapsulate.c */
-
-static void
-gssapi_encap_length(size_t data_len, size_t *len, size_t *total_len,
-		    const gss_OID mech) {
-	size_t len_len;
-
-	*len = 1 + 1 + mech->length + data_len;
-
-	len_len = length_len(*len);
-
-	*total_len = 1 + len_len + *len;
-}
-
-static u_char *
-gssapi_mech_make_header(u_char *p, size_t len, const gss_OID mech) {
-	int e;
-	size_t len_len, foo;
-
-	*p++ = 0x60;
-	len_len = length_len(len);
-	e = der_put_length(p + len_len - 1, len_len, len, &foo);
-	if (e || foo != len_len) {
-		return (NULL);
-	}
-	p += len_len;
-	*p++ = 0x06;
-	*p++ = mech->length;
-	memmove(p, mech->elements, mech->length);
-	p += mech->length;
-	return (p);
-}
-
-/*
- * Give it a krb5_data and it will encapsulate with extra GSS-API wrappings.
- */
-
-static OM_uint32
-gssapi_spnego_encapsulate(OM_uint32 *minor_status, unsigned char *buf,
-			  size_t buf_size, gss_buffer_t output_token,
-			  const gss_OID mech) {
-	size_t len, outer_len;
-	u_char *p;
-
-	gssapi_encap_length(buf_size, &len, &outer_len, mech);
-
-	output_token->length = outer_len;
-	output_token->value = malloc(outer_len);
-	if (output_token->value == NULL) {
-		*minor_status = ENOMEM;
-		return (GSS_S_FAILURE);
-	}
-	p = gssapi_mech_make_header(output_token->value, len, mech);
-	if (p == NULL) {
-		if (output_token->length != 0U) {
-			gss_release_buffer(minor_status, output_token);
-		}
-		return (GSS_S_FAILURE);
-	}
-	memmove(p, buf, buf_size);
-	return (GSS_S_COMPLETE);
-}
-
-/* init_sec_context.c */
-/*
- * SPNEGO wrapper for Kerberos5 GSS-API kouril@ics.muni.cz, 2003 (mostly
- * based on Heimdal code)
- */
-
-static int
-add_mech(MechTypeList *mech_list, gss_OID mech) {
-	MechType *tmp;
-	int ret;
-
-	tmp = realloc(mech_list->val, (mech_list->len + 1) * sizeof(*tmp));
-	if (tmp == NULL) {
-		return (ENOMEM);
-	}
-	mech_list->val = tmp;
-
-	ret = der_get_oid(mech->elements, mech->length,
-			  &mech_list->val[mech_list->len], NULL);
-	if (ret) {
-		return (ret);
-	}
-
-	mech_list->len++;
-	return (0);
-}
-
-/*
- * return the length of the mechanism in token or -1
- * (which implies that the token was bad - GSS_S_DEFECTIVE_TOKEN
- */
-
-static ssize_t
-gssapi_krb5_get_mech(const u_char *ptr, size_t total_len,
-		     const u_char **mech_ret) {
-	size_t len, len_len, mech_len, foo;
-	const u_char *p = ptr;
-	int e;
-
-	if (total_len < 1U) {
-		return (-1);
-	}
-	if (*p++ != 0x60) {
-		return (-1);
-	}
-	e = der_get_length(p, total_len - 1, &len, &len_len);
-	if (e || 1 + len_len + len != total_len) {
-		return (-1);
-	}
-	p += len_len;
-	if (*p++ != 0x06) {
-		return (-1);
-	}
-	e = der_get_length(p, total_len - 1 - len_len - 1, &mech_len, &foo);
-	if (e) {
-		return (-1);
-	}
-	p += foo;
-	*mech_ret = p;
-	return (mech_len);
-}
-
-static OM_uint32
-spnego_initial(OM_uint32 *minor_status,
-	       const gss_cred_id_t initiator_cred_handle,
-	       gss_ctx_id_t *context_handle, const gss_name_t target_name,
-	       const gss_OID mech_type, OM_uint32 req_flags, OM_uint32 time_req,
-	       const gss_channel_bindings_t input_chan_bindings,
-	       const gss_buffer_t input_token, gss_OID *actual_mech_type,
-	       gss_buffer_t output_token, OM_uint32 *ret_flags,
-	       OM_uint32 *time_rec) {
-	NegTokenInit token_init;
-	OM_uint32 major_status, minor_status2;
-	gss_buffer_desc krb5_output_token = GSS_C_EMPTY_BUFFER;
-	unsigned char *buf = NULL;
-	size_t buf_size;
-	size_t len = 0;
-	int ret;
-
-	(void)mech_type;
-
-	memset(&token_init, 0, sizeof(token_init));
-
-	ret = add_mech(&token_init.mechTypes, GSS_KRB5_MECH);
-	if (ret) {
-		*minor_status = ret;
-		ret = GSS_S_FAILURE;
-		goto end;
-	}
-
-	major_status = gss_init_sec_context(
-		minor_status, initiator_cred_handle, context_handle,
-		target_name, GSS_KRB5_MECH, req_flags, time_req,
-		input_chan_bindings, input_token, actual_mech_type,
-		&krb5_output_token, ret_flags, time_rec);
-	if (GSS_ERROR(major_status)) {
-		ret = major_status;
-		goto end;
-	}
-	if (krb5_output_token.length > 0U) {
-		token_init.mechToken = malloc(sizeof(*token_init.mechToken));
-		if (token_init.mechToken == NULL) {
-			*minor_status = ENOMEM;
-			ret = GSS_S_FAILURE;
-			goto end;
-		}
-		token_init.mechToken->data = krb5_output_token.value;
-		token_init.mechToken->length = krb5_output_token.length;
-	}
-	/*
-	 * The MS implementation of SPNEGO seems to not like the mechListMIC
-	 * field, so we omit it (it's optional anyway)
-	 */
-
-	buf_size = 1024;
-	buf = malloc(buf_size);
-	if (buf == NULL) {
-		*minor_status = ENOMEM;
-		ret = GSS_S_FAILURE;
-		goto end;
-	}
-
-	do {
-		ret = encode_NegTokenInit(buf + buf_size - 1, buf_size,
-					  &token_init, &len);
-		if (ret == 0) {
-			size_t tmp;
-
-			ret = der_put_length_and_tag(
-				buf + buf_size - len - 1, buf_size - len, len,
-				ASN1_C_CONTEXT, CONS, 0, &tmp);
-			if (ret == 0) {
-				len += tmp;
-			}
-		}
-		if (ret) {
-			if (ret == ASN1_OVERFLOW) {
-				u_char *tmp;
-
-				buf_size *= 2;
-				tmp = realloc(buf, buf_size);
-				if (tmp == NULL) {
-					*minor_status = ENOMEM;
-					ret = GSS_S_FAILURE;
-					goto end;
-				}
-				buf = tmp;
-			} else {
-				*minor_status = ret;
-				ret = GSS_S_FAILURE;
-				goto end;
-			}
-		}
-	} while (ret == ASN1_OVERFLOW);
-
-	ret = gssapi_spnego_encapsulate(minor_status, buf + buf_size - len, len,
-					output_token, GSS_SPNEGO_MECH);
-	if (ret == GSS_S_COMPLETE) {
-		ret = major_status;
-	}
-
-end:
-	if (token_init.mechToken != NULL) {
-		free(token_init.mechToken);
-		token_init.mechToken = NULL;
-	}
-	free_NegTokenInit(&token_init);
-	if (krb5_output_token.length != 0U) {
-		gss_release_buffer(&minor_status2, &krb5_output_token);
-	}
-	if (buf) {
-		free(buf);
-	}
-
-	return (ret);
-}
-
-static OM_uint32
-spnego_reply(OM_uint32 *minor_status, const gss_cred_id_t initiator_cred_handle,
-	     gss_ctx_id_t *context_handle, const gss_name_t target_name,
-	     const gss_OID mech_type, OM_uint32 req_flags, OM_uint32 time_req,
-	     const gss_channel_bindings_t input_chan_bindings,
-	     const gss_buffer_t input_token, gss_OID *actual_mech_type,
-	     gss_buffer_t output_token, OM_uint32 *ret_flags,
-	     OM_uint32 *time_rec) {
-	OM_uint32 ret;
-	NegTokenResp resp;
-	unsigned char *buf;
-	size_t buf_size;
-	u_char oidbuf[17];
-	size_t oidlen;
-	gss_buffer_desc sub_token;
-	ssize_t mech_len;
-	const u_char *p;
-	size_t len, taglen;
-
-	(void)mech_type;
-
-	output_token->length = 0;
-	output_token->value = NULL;
-
-	/*
-	 * SPNEGO doesn't include gss wrapping on SubsequentContextToken
-	 * like the Kerberos 5 mech does. But lets check for it anyway.
-	 */
-
-	mech_len = gssapi_krb5_get_mech(input_token->value, input_token->length,
-					&p);
-
-	if (mech_len < 0) {
-		buf = input_token->value;
-		buf_size = input_token->length;
-	} else if ((size_t)mech_len == GSS_KRB5_MECH->length &&
-		   isc_safe_memequal(GSS_KRB5_MECH->elements, p, mech_len))
-	{
-		return (gss_init_sec_context(
-			minor_status, initiator_cred_handle, context_handle,
-			target_name, GSS_KRB5_MECH, req_flags, time_req,
-			input_chan_bindings, input_token, actual_mech_type,
-			output_token, ret_flags, time_rec));
-	} else if ((size_t)mech_len == GSS_SPNEGO_MECH->length &&
-		   isc_safe_memequal(GSS_SPNEGO_MECH->elements, p, mech_len))
-	{
-		ret = gssapi_spnego_decapsulate(minor_status, input_token, &buf,
-						&buf_size, GSS_SPNEGO_MECH);
-		if (ret) {
-			return (ret);
-		}
-	} else {
-		return (GSS_S_BAD_MECH);
-	}
-
-	ret = der_match_tag_and_length(buf, buf_size, ASN1_C_CONTEXT, CONS, 1,
-				       &len, &taglen);
-	if (ret) {
-		return (ret);
-	}
-
-	if (len > buf_size - taglen) {
-		return (ASN1_OVERRUN);
-	}
-
-	ret = decode_NegTokenResp(buf + taglen, len, &resp, NULL);
-	if (ret) {
-		free_NegTokenResp(&resp);
-		*minor_status = ENOMEM;
-		return (GSS_S_FAILURE);
-	}
-
-	if (resp.negState == NULL || *(resp.negState) == reject ||
-	    resp.supportedMech == NULL)
-	{
-		free_NegTokenResp(&resp);
-		return (GSS_S_BAD_MECH);
-	}
-
-	ret = der_put_oid(oidbuf + sizeof(oidbuf) - 1, sizeof(oidbuf),
-			  resp.supportedMech, &oidlen);
-	if (ret || oidlen != GSS_KRB5_MECH->length ||
-	    !isc_safe_memequal(oidbuf + sizeof(oidbuf) - oidlen,
-			       GSS_KRB5_MECH->elements, oidlen))
-	{
-		free_NegTokenResp(&resp);
-		return (GSS_S_BAD_MECH);
-	}
-
-	if (resp.responseToken != NULL) {
-		sub_token.length = resp.responseToken->length;
-		sub_token.value = resp.responseToken->data;
-	} else {
-		sub_token.length = 0;
-		sub_token.value = NULL;
-	}
-
-	ret = gss_init_sec_context(minor_status, initiator_cred_handle,
-				   context_handle, target_name, GSS_KRB5_MECH,
-				   req_flags, time_req, input_chan_bindings,
-				   &sub_token, actual_mech_type, output_token,
-				   ret_flags, time_rec);
-	if (ret) {
-		free_NegTokenResp(&resp);
-		return (ret);
-	}
-
-	/*
-	 * XXXSRA I don't think this limited implementation ever needs
-	 * to check the MIC -- our preferred mechanism (Kerberos)
-	 * authenticates its own messages and is the only mechanism
-	 * we'll accept, so if the mechanism negotiation completes
-	 * successfully, we don't need the MIC.  See RFC 4178.
-	 */
-
-	free_NegTokenResp(&resp);
-	return (ret);
-}
-
-OM_uint32
-gss_init_sec_context_spnego(
-	OM_uint32 *minor_status, const gss_cred_id_t initiator_cred_handle,
-	gss_ctx_id_t *context_handle, const gss_name_t target_name,
-	const gss_OID mech_type, OM_uint32 req_flags, OM_uint32 time_req,
-	const gss_channel_bindings_t input_chan_bindings,
-	const gss_buffer_t input_token, gss_OID *actual_mech_type,
-	gss_buffer_t output_token, OM_uint32 *ret_flags, OM_uint32 *time_rec) {
-	/* Dirty trick to suppress compiler warnings */
-
-	/* Figure out whether we're starting over or processing a reply */
-
-	if (input_token == GSS_C_NO_BUFFER || input_token->length == 0U) {
-		return (spnego_initial(minor_status, initiator_cred_handle,
-				       context_handle, target_name, mech_type,
-				       req_flags, time_req, input_chan_bindings,
-				       input_token, actual_mech_type,
-				       output_token, ret_flags, time_rec));
-	} else {
-		return (spnego_reply(minor_status, initiator_cred_handle,
-				     context_handle, target_name, mech_type,
-				     req_flags, time_req, input_chan_bindings,
-				     input_token, actual_mech_type,
-				     output_token, ret_flags, time_rec));
-	}
-}
-
-#endif /* GSSAPI */
diff -Nru bind9-9.16.13/lib/dns/spnego.h bind9-9.16.15/lib/dns/spnego.h
--- bind9-9.16.13/lib/dns/spnego.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/spnego.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
- *
- * 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 https://mozilla.org/MPL/2.0/.
- *
- * See the COPYRIGHT file distributed with this work for additional
- * information regarding copyright ownership.
- */
-
-/*! \file
- * \brief
- * Entry points into portable SPNEGO implementation.
- * See spnego.c for information on the SPNEGO implementation itself.
- */
-
-#ifndef _SPNEGO_H_
-#define _SPNEGO_H_
-
-/*%
- * Wrapper for GSSAPI gss_init_sec_context(), using portable SPNEGO
- * implementation instead of the one that's part of the GSSAPI
- * library.  Takes arguments identical to the standard GSSAPI
- * function, uses standard gss_init_sec_context() to handle
- * everything inside the SPNEGO wrapper.
- */
-OM_uint32
-gss_init_sec_context_spnego(OM_uint32 *, const gss_cred_id_t, gss_ctx_id_t *,
-			    const gss_name_t, const gss_OID, OM_uint32,
-			    OM_uint32, const gss_channel_bindings_t,
-			    const gss_buffer_t, gss_OID *, gss_buffer_t,
-			    OM_uint32 *, OM_uint32 *);
-
-/*%
- * Wrapper for GSSAPI gss_accept_sec_context(), using portable SPNEGO
- * implementation instead of the one that's part of the GSSAPI
- * library.  Takes arguments identical to the standard GSSAPI
- * function.  Checks the OID of the input token to see if it's SPNEGO;
- * if so, processes it, otherwise hands the call off to the standard
- * gss_accept_sec_context() function.
- */
-OM_uint32
-gss_accept_sec_context_spnego(OM_uint32 *, gss_ctx_id_t *, const gss_cred_id_t,
-			      const gss_buffer_t, const gss_channel_bindings_t,
-			      gss_name_t *, gss_OID *, gss_buffer_t,
-			      OM_uint32 *, OM_uint32 *, gss_cred_id_t *);
-
-#endif /* ifndef _SPNEGO_H_ */
diff -Nru bind9-9.16.13/lib/dns/win32/libdns.def.in bind9-9.16.15/lib/dns/win32/libdns.def.in
--- bind9-9.16.13/lib/dns/win32/libdns.def.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/win32/libdns.def.in	2021-04-19 15:45:50.000000000 +0200
@@ -429,6 +429,7 @@
 dns_journal_next_rr
 dns_journal_open
 dns_journal_print
+dns_journal_recovered
 dns_journal_rollforward
 dns_journal_set_sourceserial
 dns_journal_write_transaction
diff -Nru bind9-9.16.13/lib/dns/win32/libdns.vcxproj.filters.in bind9-9.16.15/lib/dns/win32/libdns.vcxproj.filters.in
--- bind9-9.16.13/lib/dns/win32/libdns.vcxproj.filters.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/win32/libdns.vcxproj.filters.in	2021-04-19 15:45:50.000000000 +0200
@@ -331,9 +331,6 @@
       <Filter>Dst Source Files</Filter>
     </ClCompile>
 @END PKCS11
-    <ClCompile Include="..\spnego.c">
-      <Filter>Dst Source Files</Filter>
-    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\code.h">
@@ -667,8 +664,5 @@
       <Filter>Dst Header Files</Filter>
     </ClInclude>
 @END PKCS11
-    <ClInclude Include="..\spnego.h">
-      <Filter>Dst Header Files</Filter>
-    </ClInclude>
   </ItemGroup>
 </Project>
diff -Nru bind9-9.16.13/lib/dns/win32/libdns.vcxproj.in bind9-9.16.15/lib/dns/win32/libdns.vcxproj.in
--- bind9-9.16.13/lib/dns/win32/libdns.vcxproj.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/win32/libdns.vcxproj.in	2021-04-19 15:45:50.000000000 +0200
@@ -59,7 +59,7 @@
       <WarningLevel>Level4</WarningLevel>
       <TreatWarningAsError>false</TreatWarningAsError>
       <Optimization>Disabled</Optimization>
-      <PreprocessorDefinitions>BIND9;WIN32;@USE_GSSAPI@@USE_ISC_SPNEGO@_DEBUG;_WINDOWS;_USRDLL;LIBDNS_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>BIND9;WIN32;@USE_GSSAPI@_DEBUG;_WINDOWS;_USRDLL;LIBDNS_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
       <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;@LIBXML2_INC@@OPENSSL_INC@@GSSAPI_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <FunctionLevelLinking>true</FunctionLevelLinking>
@@ -89,7 +89,7 @@
       <Optimization>MaxSpeed</Optimization>
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions>
-      <PreprocessorDefinitions>BIND9;WIN32;@USE_GSSAPI@@USE_ISC_SPNEGO@NDEBUG;_WINDOWS;_USRDLL;LIBDNS_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>BIND9;WIN32;@USE_GSSAPI@NDEBUG;_WINDOWS;_USRDLL;LIBDNS_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles>
       <AdditionalIncludeDirectories>.\;..\..\..\;include;..\include;..\..\isc;..\..\isc\win32;..\..\isc\win32\include;..\..\isc\include;@LIBXML2_INC@@OPENSSL_INC@@GSSAPI_INC@@GEOIP_INC@%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
@@ -203,7 +203,6 @@
     <ClCompile Include="..\sdb.c" />
     <ClCompile Include="..\sdlz.c" />
     <ClCompile Include="..\soa.c" />
-    <ClCompile Include="..\spnego.c" />
     <ClCompile Include="..\ssu.c" />
     <ClCompile Include="..\ssu_external.c" />
     <ClCompile Include="..\stats.c" />
@@ -339,7 +338,6 @@
     <ClInclude Include="..\include\dst\result.h" />
     <ClInclude Include="..\rbtdb.h" />
     <ClInclude Include="..\rdatalist_p.h" />
-    <ClInclude Include="..\spnego.h" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
diff -Nru bind9-9.16.13/lib/dns/xfrin.c bind9-9.16.15/lib/dns/xfrin.c
--- bind9-9.16.13/lib/dns/xfrin.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/xfrin.c	2021-04-19 15:45:50.000000000 +0200
@@ -498,6 +498,20 @@
 		FAIL(DNS_R_FORMERR);
 	}
 
+	/*
+	 * Immediately reject the entire transfer if the RR that is currently
+	 * being processed is an SOA record that is not placed at the zone
+	 * apex.
+	 */
+	if (rdata->type == dns_rdatatype_soa &&
+	    !dns_name_equal(&xfr->name, name)) {
+		char namebuf[DNS_NAME_FORMATSIZE];
+		dns_name_format(name, namebuf, sizeof(namebuf));
+		xfrin_log(xfr, ISC_LOG_DEBUG(3), "SOA name mismatch: '%s'",
+			  namebuf);
+		FAIL(DNS_R_NOTZONETOP);
+	}
+
 redo:
 	switch (xfr->state) {
 	case XFRST_SOAQUERY:
diff -Nru bind9-9.16.13/lib/dns/zone.c bind9-9.16.15/lib/dns/zone.c
--- bind9-9.16.13/lib/dns/zone.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/dns/zone.c	2021-04-19 15:45:50.000000000 +0200
@@ -890,6 +890,9 @@
 setrl(isc_ratelimiter_t *rl, unsigned int *rate, unsigned int value);
 static void
 zone_journal_compact(dns_zone_t *zone, dns_db_t *db, uint32_t serial);
+static isc_result_t
+zone_journal_rollforward(dns_zone_t *zone, dns_db_t *db, bool *needdump,
+			 bool *fixjournal);
 
 #define ENTER zone_debuglog(zone, me, 1, "enter")
 
@@ -1806,11 +1809,6 @@
 		return (true);
 	}
 
-	/* Kasp zones are always dynamic. */
-	if (dns_zone_use_kasp(zone)) {
-		return (true);
-	}
-
 	/* If !ignore_freeze, we need check whether updates are disabled.  */
 	if (zone->type == dns_zone_master &&
 	    (!zone->update_disabled || ignore_freeze) &&
@@ -2059,8 +2057,8 @@
 	is_dynamic = dns_zone_isdynamic(zone, false);
 	if (zone->db != NULL && is_dynamic) {
 		/*
-		 * This is a slave, stub, dynamically updated, or kasp enabled
-		 * zone being reloaded.  Do nothing - the database we already
+		 * This is a slave, stub, or dynamically updated zone being
+		 * reloaded.  Do nothing - the database we already
 		 * have is guaranteed to be up-to-date.
 		 */
 		if (zone->type == dns_zone_master && !hasraw) {
@@ -2372,6 +2370,16 @@
 	if (inline_raw(zone)) {
 		result = zone_load(zone->secure, DNS_ZONELOADFLAG_THAW, false);
 	} else {
+		/*
+		 * When thawing a zone, we don't know what changes
+		 * have been made. If we do DNSSEC maintenance on this
+		 * zone, schedule a full sign for this zone.
+		 */
+		if (zone->type == dns_zone_master &&
+		    DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN))
+		{
+			DNS_ZONEKEY_SETOPTION(zone, DNS_ZONEKEY_FULLSIGN);
+		}
 		result = zone_load(zone, DNS_ZONELOADFLAG_THAW, false);
 	}
 
@@ -4708,7 +4716,6 @@
 	bool hasinclude = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HASINCLUDE);
 	bool nomaster = false;
 	bool had_db = false;
-	unsigned int options;
 	dns_include_t *inc;
 	bool is_dynamic = false;
 
@@ -4796,46 +4803,11 @@
 	    !DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOMERGE) &&
 	    !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED))
 	{
-		if (zone->type == dns_zone_master &&
-		    (inline_secure(zone) ||
-		     (zone->update_acl != NULL || zone->ssutable != NULL)))
-		{
-			options = DNS_JOURNALOPT_RESIGN;
-		} else {
-			options = 0;
-		}
-		result = dns_journal_rollforward(zone->mctx, db, options,
-						 zone->journal);
-		if (result != ISC_R_SUCCESS && result != DNS_R_RECOVERABLE &&
-		    result != ISC_R_NOTFOUND && result != DNS_R_UPTODATE &&
-		    result != DNS_R_NOJOURNAL && result != ISC_R_RANGE)
-		{
-			dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
-				      ISC_LOG_ERROR,
-				      "journal rollforward failed: %s",
-				      dns_result_totext(result));
-			goto cleanup;
-		}
-		if (result == ISC_R_NOTFOUND || result == ISC_R_RANGE) {
-			dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
-				      ISC_LOG_ERROR,
-				      "journal rollforward failed: "
-				      "journal out of sync with zone");
+		result = zone_journal_rollforward(zone, db, &needdump,
+						  &fixjournal);
+		if (result != ISC_R_SUCCESS) {
 			goto cleanup;
 		}
-		dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1),
-			      "journal rollforward completed "
-			      "successfully: %s",
-			      dns_result_totext(result));
-		if (result == ISC_R_SUCCESS) {
-			needdump = true;
-		} else if (result == DNS_R_RECOVERABLE) {
-			dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
-				      ISC_LOG_ERROR,
-				      "retried using old journal format");
-			needdump = true;
-			fixjournal = true;
-		}
 	}
 
 	/*
@@ -5156,17 +5128,15 @@
 
 	result = ISC_R_SUCCESS;
 
+	if (fixjournal) {
+		DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FIXJOURNAL);
+		zone_journal_compact(zone, zone->db, 0);
+	}
 	if (needdump) {
 		if (zone->type == dns_zone_key) {
 			zone_needdump(zone, 30);
 		} else {
-			if (fixjournal) {
-				DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FIXJOURNAL);
-				zone_journal_compact(zone, zone->db, 0);
-				zone_needdump(zone, 0);
-			} else {
-				zone_needdump(zone, DNS_DUMP_DELAY);
-			}
+			zone_needdump(zone, DNS_DUMP_DELAY);
 		}
 	}
 
@@ -10987,6 +10957,7 @@
 	isc_time_t now;
 	isc_result_t result;
 	bool dumping, load_pending, viewok;
+	bool need_notify;
 
 	REQUIRE(DNS_ZONE_VALID(zone));
 	ENTER;
@@ -11069,11 +11040,15 @@
 	 * Secondaries send notifies before backing up to disk,
 	 * primaries after.
 	 */
-	if ((zone->type == dns_zone_slave || zone->type == dns_zone_mirror) &&
-	    (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) ||
-	     DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) &&
-	    isc_time_compare(&now, &zone->notifytime) >= 0)
-	{
+	LOCK_ZONE(zone);
+	need_notify = (zone->type == dns_zone_slave ||
+		       zone->type == dns_zone_mirror) &&
+		      (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) ||
+		       DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) &&
+		      (isc_time_compare(&now, &zone->notifytime) >= 0);
+	UNLOCK_ZONE(zone);
+
+	if (need_notify) {
 		zone_notify(zone, &now);
 	}
 
@@ -11372,6 +11347,82 @@
 	UNLOCK_ZONE(zone);
 }
 
+static isc_result_t
+zone_journal_rollforward(dns_zone_t *zone, dns_db_t *db, bool *needdump,
+			 bool *fixjournal) {
+	dns_journal_t *journal = NULL;
+	unsigned int options;
+	isc_result_t result;
+
+	if (zone->type == dns_zone_master &&
+	    (inline_secure(zone) ||
+	     (zone->update_acl != NULL || zone->ssutable != NULL)))
+	{
+		options = DNS_JOURNALOPT_RESIGN;
+	} else {
+		options = 0;
+	}
+
+	result = dns_journal_open(zone->mctx, zone->journal, DNS_JOURNAL_READ,
+				  &journal);
+	if (result == ISC_R_NOTFOUND) {
+		dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(3),
+			      "no journal file, but that's OK ");
+		return (ISC_R_SUCCESS);
+	} else if (result != ISC_R_SUCCESS) {
+		dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR,
+			      "journal open failed: %s",
+			      dns_result_totext(result));
+		return (result);
+	}
+
+	if (dns_journal_empty(journal)) {
+		dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1),
+			      "journal empty");
+		dns_journal_destroy(&journal);
+		return (ISC_R_SUCCESS);
+	}
+
+	result = dns_journal_rollforward(journal, db, options);
+	switch (result) {
+	case ISC_R_SUCCESS:
+		*needdump = true;
+		/* FALLTHROUGH */
+	case DNS_R_UPTODATE:
+		if (dns_journal_recovered(journal)) {
+			*fixjournal = true;
+			dns_zone_logc(
+				zone, DNS_LOGCATEGORY_ZONELOAD,
+				ISC_LOG_DEBUG(1),
+				"journal rollforward completed successfully "
+				"using old journal format: %s",
+				dns_result_totext(result));
+		} else {
+			dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
+				      ISC_LOG_DEBUG(1),
+				      "journal rollforward completed "
+				      "successfully: %s",
+				      dns_result_totext(result));
+		}
+
+		dns_journal_destroy(&journal);
+		return (ISC_R_SUCCESS);
+	case ISC_R_NOTFOUND:
+	case ISC_R_RANGE:
+		dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR,
+			      "journal rollforward failed: journal out of sync "
+			      "with zone");
+		dns_journal_destroy(&journal);
+		return (result);
+	default:
+		dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR,
+			      "journal rollforward failed: %s",
+			      dns_result_totext(result));
+		dns_journal_destroy(&journal);
+		return (result);
+	}
+}
+
 static void
 zone_journal_compact(dns_zone_t *zone, dns_db_t *db, uint32_t serial) {
 	isc_result_t result;
@@ -17061,9 +17112,16 @@
 			if (soacount != 1) {
 				dns_zone_log(zone, ISC_LOG_ERROR,
 					     "transferred zone "
-					     "has %d SOA record%s",
-					     soacount,
-					     (soacount != 0) ? "s" : "");
+					     "has %d SOA records",
+					     soacount);
+				if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS))
+				{
+					zone->refresh = DNS_ZONE_DEFAULTREFRESH;
+					zone->retry = DNS_ZONE_DEFAULTRETRY;
+				}
+				DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS);
+				zone_unload(zone);
+				goto next_master;
 			}
 			if (nscount == 0) {
 				dns_zone_log(zone, ISC_LOG_ERROR,
@@ -19942,7 +20000,12 @@
 	}
 
 	if (result == ISC_R_SUCCESS) {
-		bool insecure = dns_zone_secure_to_insecure(zone, false);
+		/*
+		 * Publish CDS/CDNSKEY DELETE records if the zone is
+		 * transitioning from secure to insecure.
+		 */
+		bool cds_delete = dns_zone_secure_to_insecure(zone, false);
+		isc_stdtime_t when;
 
 		/*
 		 * Only update DNSKEY TTL if we have a policy.
@@ -19979,9 +20042,36 @@
 			goto failure;
 		}
 
+		if (cds_delete) {
+			/*
+			 * Only publish CDS/CDNSKEY DELETE records if there is
+			 * a KSK that can be used to verify the RRset. This
+			 * means there must be a key with the KSK role that is
+			 * published and is used for signing.
+			 */
+			cds_delete = false;
+			for (key = ISC_LIST_HEAD(dnskeys); key != NULL;
+			     key = ISC_LIST_NEXT(key, link)) {
+				dst_key_t *dstk = key->key;
+				bool ksk = false;
+				(void)dst_key_getbool(dstk, DST_BOOL_KSK, &ksk);
+				if (!ksk) {
+					continue;
+				}
+
+				if (dst_key_haskasp(dstk) &&
+				    dst_key_is_published(dstk, now, &when) &&
+				    dst_key_is_signing(dstk, DST_BOOL_KSK, now,
+						       &when))
+				{
+					cds_delete = true;
+					break;
+				}
+			}
+		}
 		result = dns_dnssec_syncdelete(&cdsset, &cdnskeyset,
 					       &zone->origin, zone->rdclass,
-					       ttl, &diff, mctx, insecure);
+					       ttl, &diff, mctx, cds_delete);
 		if (result != ISC_R_SUCCESS) {
 			dnssec_log(zone, ISC_LOG_ERROR,
 				   "zone_rekey:couldn't update CDS/CDNSKEY "
@@ -20343,6 +20433,7 @@
 	unsigned char buffer[DNS_DS_BUFFERSIZE];
 	unsigned char algorithms[256];
 	unsigned int i;
+	bool empty = false;
 
 	enum { notexpected = 0, expected = 1, found = 2 };
 
@@ -20378,14 +20469,8 @@
 	result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey,
 				     dns_rdatatype_none, 0, &dnskey, NULL);
 	if (result == ISC_R_NOTFOUND) {
-		if (dns_rdataset_isassociated(&cds)) {
-			result = DNS_R_BADCDS;
-		} else {
-			result = DNS_R_BADCDNSKEY;
-		}
-		goto failure;
-	}
-	if (result != ISC_R_SUCCESS) {
+		empty = true;
+	} else if (result != ISC_R_SUCCESS) {
 		goto failure;
 	}
 
@@ -20415,6 +20500,12 @@
 				delete = true;
 				continue;
 			}
+
+			if (empty) {
+				result = DNS_R_BADCDS;
+				goto failure;
+			}
+
 			CHECK(dns_rdata_tostruct(&crdata, &structcds, NULL));
 			if (algorithms[structcds.algorithm] == 0) {
 				algorithms[structcds.algorithm] = expected;
@@ -20482,6 +20573,12 @@
 				delete = true;
 				continue;
 			}
+
+			if (empty) {
+				result = DNS_R_BADCDNSKEY;
+				goto failure;
+			}
+
 			CHECK(dns_rdata_tostruct(&crdata, &structcdnskey,
 						 NULL));
 			if (algorithms[structcdnskey.algorithm] == 0) {
diff -Nru bind9-9.16.13/lib/isc/include/isc/netmgr.h bind9-9.16.15/lib/isc/include/isc/netmgr.h
--- bind9-9.16.13/lib/isc/include/isc/netmgr.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/include/isc/netmgr.h	2021-04-19 15:45:50.000000000 +0200
@@ -162,9 +162,11 @@
 
 void
 isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+void
+isc_nmhandle_cleartimeout(isc_nmhandle_t *handle);
 /*%<
- * Set the read/recv timeout for the socket connected to 'handle'
- * to 'timeout', and reset the timer.
+ * Set/clear the read/recv timeout for the socket connected to 'handle'
+ * to 'timeout' (in milliseconds), and reset the timer.
  *
  * When this is called on a 'wrapper' socket handle (for example,
  * a TCPDNS socket wrapping a TCP connection), the timer is set for
@@ -431,10 +433,10 @@
 isc_nm_settimeouts(isc_nm_t *mgr, uint32_t init, uint32_t idle,
 		   uint32_t keepalive, uint32_t advertised);
 /*%<
- * Sets the initial, idle, and keepalive timeout values to use for
- * TCP connections, and the timeout value to advertise in responses using
+ * Sets the initial, idle, and keepalive timeout values (in milliseconds) to use
+ * for TCP connections, and the timeout value to advertise in responses using
  * the EDNS TCP Keepalive option (which should ordinarily be the same
- * as 'keepalive'), in tenths of seconds.
+ * as 'keepalive').
  *
  * Requires:
  * \li	'mgr' is a valid netmgr.
@@ -445,7 +447,7 @@
 		   uint32_t *keepalive, uint32_t *advertised);
 /*%<
  * Gets the initial, idle, keepalive, or advertised timeout values,
- * in tenths of seconds.
+ * in milliseconds.
  *
  * Any integer pointer parameter not set to NULL will be updated to
  * contain the corresponding timeout value.
diff -Nru bind9-9.16.13/lib/isc/include/isc/util.h bind9-9.16.15/lib/isc/include/isc/util.h
--- bind9-9.16.13/lib/isc/include/isc/util.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/include/isc/util.h	2021-04-19 15:45:50.000000000 +0200
@@ -54,8 +54,6 @@
 #elif WIN32
 #define ISC_CONSTRUCTOR(priority)
 #define ISC_DESTRUCTOR(priority)
-#else
-#error Either __attribute__((constructor|destructor))__ or DllMain support needed to compile BIND 9.
 #endif
 
 /*%
@@ -340,6 +338,8 @@
  * Time
  */
 #define TIME_NOW(tp) RUNTIME_CHECK(isc_time_now((tp)) == ISC_R_SUCCESS)
+#define TIME_NOW_HIRES(tp) \
+	RUNTIME_CHECK(isc_time_now_hires((tp)) == ISC_R_SUCCESS)
 
 /*%
  * Alignment
diff -Nru bind9-9.16.13/lib/isc/lib.c bind9-9.16.15/lib/isc/lib.c
--- bind9-9.16.13/lib/isc/lib.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/lib.c	2021-04-19 15:45:50.000000000 +0200
@@ -17,10 +17,15 @@
 #include <isc/tls.h>
 #include <isc/util.h>
 
+#include "config.h"
 #include "mem_p.h"
 #include "tls_p.h"
 #include "trampoline_p.h"
 
+#ifndef ISC_CONSTRUCTOR
+#error Either __attribute__((constructor|destructor))__ or DllMain support needed to compile BIND 9.
+#endif
+
 /***
  *** Functions
  ***/
diff -Nru bind9-9.16.13/lib/isc/netmgr/netmgr.c bind9-9.16.15/lib/isc/netmgr/netmgr.c
--- bind9-9.16.13/lib/isc/netmgr/netmgr.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/netmgr/netmgr.c	2021-04-19 15:45:50.000000000 +0200
@@ -509,10 +509,10 @@
 		   uint32_t keepalive, uint32_t advertised) {
 	REQUIRE(VALID_NM(mgr));
 
-	atomic_store(&mgr->init, init * 100);
-	atomic_store(&mgr->idle, idle * 100);
-	atomic_store(&mgr->keepalive, keepalive * 100);
-	atomic_store(&mgr->advertised, advertised * 100);
+	atomic_store(&mgr->init, init);
+	atomic_store(&mgr->idle, idle);
+	atomic_store(&mgr->keepalive, keepalive);
+	atomic_store(&mgr->advertised, advertised);
 }
 
 void
@@ -521,19 +521,19 @@
 	REQUIRE(VALID_NM(mgr));
 
 	if (initial != NULL) {
-		*initial = atomic_load(&mgr->init) / 100;
+		*initial = atomic_load(&mgr->init);
 	}
 
 	if (idle != NULL) {
-		*idle = atomic_load(&mgr->idle) / 100;
+		*idle = atomic_load(&mgr->idle);
 	}
 
 	if (keepalive != NULL) {
-		*keepalive = atomic_load(&mgr->keepalive) / 100;
+		*keepalive = atomic_load(&mgr->keepalive);
 	}
 
 	if (advertised != NULL) {
-		*advertised = atomic_load(&mgr->advertised) / 100;
+		*advertised = atomic_load(&mgr->advertised);
 	}
 }
 
@@ -1537,26 +1537,388 @@
 }
 
 void
-isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
-	REQUIRE(VALID_NMHANDLE(handle));
+isc__nm_alloc_dnsbuf(isc_nmsocket_t *sock, size_t len) {
+	REQUIRE(len <= NM_BIG_BUF);
 
-	switch (handle->sock->type) {
-	case isc_nm_udpsocket:
-		isc__nm_udp_settimeout(handle, timeout);
+	if (sock->buf == NULL) {
+		/* We don't have the buffer at all */
+		size_t alloc_len = len < NM_REG_BUF ? NM_REG_BUF : NM_BIG_BUF;
+		sock->buf = isc_mem_allocate(sock->mgr->mctx, alloc_len);
+		sock->buf_size = alloc_len;
+	} else {
+		/* We have the buffer but it's too small */
+		sock->buf = isc_mem_reallocate(sock->mgr->mctx, sock->buf,
+					       NM_BIG_BUF);
+		sock->buf_size = NM_BIG_BUF;
+	}
+}
+
+void
+isc__nm_failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+		       isc_result_t eresult) {
+	REQUIRE(VALID_NMSOCK(sock));
+	REQUIRE(VALID_UVREQ(req));
+
+	if (req->cb.send != NULL) {
+		isc__nm_sendcb(sock, req, eresult, true);
+	} else {
+		isc__nm_uvreq_put(&req, sock);
+	}
+}
+
+void
+isc__nm_failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) {
+	REQUIRE(sock->accepting);
+	REQUIRE(sock->server);
+
+	/*
+	 * Detach the quota early to make room for other connections;
+	 * otherwise it'd be detached later asynchronously, and clog
+	 * the quota unnecessarily.
+	 */
+	if (sock->quota != NULL) {
+		isc_quota_detach(&sock->quota);
+	}
+
+	isc__nmsocket_detach(&sock->server);
+
+	sock->accepting = false;
+
+	switch (eresult) {
+	case ISC_R_NOTCONNECTED:
+		/* IGNORE: The client disconnected before we could accept */
 		break;
+	default:
+		isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+			      ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
+			      "Accepting TCP connection failed: %s",
+			      isc_result_totext(eresult));
+	}
+}
+
+void
+isc__nm_failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+			  isc_result_t eresult) {
+	REQUIRE(VALID_NMSOCK(sock));
+	REQUIRE(VALID_UVREQ(req));
+	REQUIRE(sock->tid == isc_nm_tid());
+	REQUIRE(atomic_load(&sock->connecting));
+	REQUIRE(req->cb.connect != NULL);
+
+	atomic_store(&sock->connecting, false);
+
+	isc__nmsocket_clearcb(sock);
+
+	isc__nm_connectcb(sock, req, eresult);
+
+	isc__nmsocket_prep_destroy(sock);
+}
+
+void
+isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
+	REQUIRE(VALID_NMSOCK(sock));
+	switch (sock->type) {
+	case isc_nm_udpsocket:
+		isc__nm_udp_failed_read_cb(sock, result);
+		return;
 	case isc_nm_tcpsocket:
-		isc__nm_tcp_settimeout(handle, timeout);
+		isc__nm_tcp_failed_read_cb(sock, result);
+		return;
+	case isc_nm_tcpdnssocket:
+		isc__nm_tcpdns_failed_read_cb(sock, result);
+		return;
+	case isc_nm_tlsdnssocket:
+		isc__nm_tlsdns_failed_read_cb(sock, result);
+		return;
+	default:
+		INSIST(0);
+		ISC_UNREACHABLE();
+	}
+}
+
+static void
+isc__nmsocket_readtimeout_cb(uv_timer_t *timer) {
+	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer);
+
+	REQUIRE(VALID_NMSOCK(sock));
+	REQUIRE(sock->tid == isc_nm_tid());
+	REQUIRE(sock->reading);
+
+	isc__nm_failed_read_cb(sock, ISC_R_TIMEDOUT);
+}
+
+void
+isc__nmsocket_timer_restart(isc_nmsocket_t *sock) {
+	REQUIRE(VALID_NMSOCK(sock));
+
+	if (sock->read_timeout == 0) {
+		return;
+	}
+
+	int r = uv_timer_start(&sock->timer, isc__nmsocket_readtimeout_cb,
+			       sock->read_timeout, 0);
+	RUNTIME_CHECK(r == 0);
+}
+
+void
+isc__nmsocket_timer_start(isc_nmsocket_t *sock) {
+	REQUIRE(VALID_NMSOCK(sock));
+
+	if (uv_is_active((uv_handle_t *)&sock->timer)) {
+		return;
+	}
+
+	isc__nmsocket_timer_restart(sock);
+}
+
+void
+isc__nmsocket_timer_stop(isc_nmsocket_t *sock) {
+	REQUIRE(VALID_NMSOCK(sock));
+
+	if (!uv_is_active((uv_handle_t *)&sock->timer)) {
+		return;
+	}
+
+	int r = uv_timer_stop(&sock->timer);
+	RUNTIME_CHECK(r == 0);
+}
+
+isc__nm_uvreq_t *
+isc__nm_get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr) {
+	isc__nm_uvreq_t *req = NULL;
+
+	req = isc__nm_uvreq_get(sock->mgr, sock);
+	req->cb.recv = sock->recv_cb;
+	req->cbarg = sock->recv_cbarg;
+
+	if (atomic_load(&sock->client)) {
+		isc_nmhandle_attach(sock->statichandle, &req->handle);
+	} else {
+		req->handle = isc__nmhandle_get(sock, sockaddr, NULL);
+	}
+
+	return req;
+}
+
+/*%<
+ * Allocator for read operations. Limited to size 2^16.
+ *
+ * Note this doesn't actually allocate anything, it just assigns the
+ * worker's receive buffer to a socket, and marks it as "in use".
+ */
+void
+isc__nm_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
+	isc_nmsocket_t *sock = uv_handle_get_data(handle);
+	isc__networker_t *worker = NULL;
+
+	REQUIRE(VALID_NMSOCK(sock));
+	REQUIRE(isc__nm_in_netthread());
+
+	switch (sock->type) {
+	case isc_nm_udpsocket:
+		REQUIRE(size <= ISC_NETMGR_RECVBUF_SIZE);
+		size = ISC_NETMGR_RECVBUF_SIZE;
+		break;
+	case isc_nm_tcpdnssocket:
+		break;
+	case isc_nm_tlsdnssocket:
+		/*
+		 * We need to limit the individual chunks to be read, so the
+		 * BIO_write() will always succeed and the consumed before the
+		 * next readcb is called.
+		 */
+		if (size >= ISC_NETMGR_TLSBUF_SIZE) {
+			size = ISC_NETMGR_TLSBUF_SIZE;
+		}
+		break;
+	default:
+		INSIST(0);
+		ISC_UNREACHABLE();
+	}
+
+	worker = &sock->mgr->workers[sock->tid];
+	INSIST(!worker->recvbuf_inuse);
+
+	buf->base = worker->recvbuf;
+	buf->len = size;
+	worker->recvbuf_inuse = true;
+}
+
+void
+isc__nm_start_reading(isc_nmsocket_t *sock) {
+	int r;
+
+	if (sock->reading) {
+		return;
+	}
+
+	switch (sock->type) {
+	case isc_nm_udpsocket:
+		r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb,
+				      isc__nm_udp_read_cb);
 		break;
 	case isc_nm_tcpdnssocket:
-		isc__nm_tcpdns_settimeout(handle, timeout);
+		r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb,
+				  isc__nm_tcpdns_read_cb);
 		break;
 	case isc_nm_tlsdnssocket:
-		isc__nm_tlsdns_settimeout(handle, timeout);
+		r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb,
+				  isc__nm_tlsdns_read_cb);
 		break;
 	default:
 		INSIST(0);
 		ISC_UNREACHABLE();
 	}
+	RUNTIME_CHECK(r == 0);
+	sock->reading = true;
+}
+
+void
+isc__nm_stop_reading(isc_nmsocket_t *sock) {
+	int r;
+
+	if (!sock->reading) {
+		return;
+	}
+
+	switch (sock->type) {
+	case isc_nm_udpsocket:
+		r = uv_udp_recv_stop(&sock->uv_handle.udp);
+		break;
+	case isc_nm_tcpdnssocket:
+	case isc_nm_tlsdnssocket:
+		r = uv_read_stop(&sock->uv_handle.stream);
+		break;
+	default:
+		INSIST(0);
+		ISC_UNREACHABLE();
+	}
+	RUNTIME_CHECK(r == 0);
+	sock->reading = false;
+}
+
+bool
+isc__nm_inactive(isc_nmsocket_t *sock) {
+	return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
+		atomic_load(&sock->mgr->closing) ||
+		(sock->server != NULL && !isc__nmsocket_active(sock->server)));
+}
+
+static isc_result_t
+processbuffer(isc_nmsocket_t *sock) {
+	switch (sock->type) {
+	case isc_nm_tcpdnssocket:
+		return (isc__nm_tcpdns_processbuffer(sock));
+	case isc_nm_tlsdnssocket:
+		return (isc__nm_tcpdns_processbuffer(sock));
+	default:
+		INSIST(0);
+		ISC_UNREACHABLE();
+	}
+}
+
+/*
+ * Process a DNS message.
+ *
+ * If we only have an incomplete DNS message, we don't touch any
+ * timers. If we do have a full message, reset the timer.
+ *
+ * Stop reading if this is a client socket, or if the server socket
+ * has been set to sequential mode, or the number of queries we are
+ * processing simultaneously has reached the clients-per-connection
+ * limit. In this case we'll be called again by resume_processing()
+ * later.
+ */
+void
+isc__nm_process_sock_buffer(isc_nmsocket_t *sock) {
+	for (;;) {
+		int_fast32_t ah = atomic_load(&sock->ah);
+		isc_result_t result = processbuffer(sock);
+		switch (result) {
+		case ISC_R_NOMORE:
+			/*
+			 * Don't reset the timer until we have a
+			 * full DNS message.
+			 */
+			isc__nm_start_reading(sock);
+			/*
+			 * Start the timer only if there are no externally used
+			 * active handles, there's always one active handle
+			 * attached internally to sock->recv_handle in
+			 * accept_connection()
+			 */
+			if (ah == 1) {
+				isc__nmsocket_timer_start(sock);
+			}
+			return;
+		case ISC_R_CANCELED:
+			isc__nmsocket_timer_stop(sock);
+			isc__nm_stop_reading(sock);
+			return;
+		case ISC_R_SUCCESS:
+			/*
+			 * Stop the timer on the successful message read, this
+			 * also allows to restart the timer when we have no more
+			 * data.
+			 */
+			isc__nmsocket_timer_stop(sock);
+
+			if (atomic_load(&sock->client) ||
+			    atomic_load(&sock->sequential) ||
+			    ah >= STREAM_CLIENTS_PER_CONN)
+			{
+				isc__nm_stop_reading(sock);
+				return;
+			}
+			break;
+		default:
+			INSIST(0);
+		}
+	}
+}
+
+void
+isc__nm_resume_processing(void *arg) {
+	isc_nmsocket_t *sock = (isc_nmsocket_t *)arg;
+
+	REQUIRE(VALID_NMSOCK(sock));
+	REQUIRE(sock->tid == isc_nm_tid());
+	REQUIRE(!atomic_load(&sock->client));
+
+	if (isc__nm_inactive(sock)) {
+		return;
+	}
+
+	isc__nm_process_sock_buffer(sock);
+}
+
+void
+isc_nmhandle_cleartimeout(isc_nmhandle_t *handle) {
+	REQUIRE(VALID_NMHANDLE(handle));
+	REQUIRE(VALID_NMSOCK(handle->sock));
+
+	switch (handle->sock->type) {
+	default:
+		handle->sock->read_timeout = 0;
+
+		if (uv_is_active((uv_handle_t *)&handle->sock->timer)) {
+			isc__nmsocket_timer_stop(handle->sock);
+		}
+	}
+}
+
+void
+isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
+	REQUIRE(VALID_NMHANDLE(handle));
+	REQUIRE(VALID_NMSOCK(handle->sock));
+
+	switch (handle->sock->type) {
+	default:
+		handle->sock->read_timeout = timeout;
+		if (uv_is_active((uv_handle_t *)&handle->sock->timer)) {
+			isc__nmsocket_timer_restart(handle->sock);
+		}
+	}
 }
 
 void *
@@ -1865,22 +2227,23 @@
 
 void
 isc__nm_sendcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
-	       isc_result_t eresult) {
+	       isc_result_t eresult, bool async) {
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(VALID_UVREQ(uvreq));
 	REQUIRE(VALID_NMHANDLE(uvreq->handle));
 
-	if (eresult == ISC_R_SUCCESS) {
+	if (!async) {
 		isc__netievent_sendcb_t ievent = { .sock = sock,
 						   .req = uvreq,
 						   .result = eresult };
 		isc__nm_async_sendcb(NULL, (isc__netievent_t *)&ievent);
-	} else {
-		isc__netievent_sendcb_t *ievent = isc__nm_get_netievent_sendcb(
-			sock->mgr, sock, uvreq, eresult);
-		isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
-				       (isc__netievent_t *)ievent);
+		return;
 	}
+
+	isc__netievent_sendcb_t *ievent =
+		isc__nm_get_netievent_sendcb(sock->mgr, sock, uvreq, eresult);
+	isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+			       (isc__netievent_t *)ievent);
 }
 
 void
diff -Nru bind9-9.16.13/lib/isc/netmgr/netmgr-int.h bind9-9.16.15/lib/isc/netmgr/netmgr-int.h
--- bind9-9.16.13/lib/isc/netmgr/netmgr-int.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/netmgr/netmgr-int.h	2021-04-19 15:45:50.000000000 +0200
@@ -39,6 +39,8 @@
 
 #define ISC_NETMGR_TID_UNKNOWN -1
 
+#define ISC_NETMGR_TLSBUF_SIZE 65536
+
 #if !defined(WIN32)
 /*
  * New versions of libuv support recvmmsg on unices.
@@ -670,6 +672,12 @@
 	STATID_ACTIVE = 10
 };
 
+typedef void (*isc_nm_closehandlecb_t)(void *arg);
+/*%<
+ * Opaque callback function, used for isc_nmhandle 'reset' and 'free'
+ * callbacks.
+ */
+
 struct isc_nmsocket {
 	/*% Unlocked, RO */
 	int magic;
@@ -892,7 +900,7 @@
 	 * as the argument whenever a handle's references drop
 	 * to zero, after its reset callback has been called.
 	 */
-	isc_nm_opaquecb_t closehandle_cb;
+	isc_nm_closehandlecb_t closehandle_cb;
 
 	isc_nmhandle_t *recv_handle;
 	isc_nm_recv_cb_t recv_cb;
@@ -1031,6 +1039,16 @@
  */
 
 void
+isc__nmsocket_timer_stop(isc_nmsocket_t *sock);
+void
+isc__nmsocket_timer_start(isc_nmsocket_t *sock);
+void
+isc__nmsocket_timer_restart(isc_nmsocket_t *sock);
+/*%<
+ * Start/stop/restart the read timeout on the socket
+ */
+
+void
 isc__nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
 		  isc_result_t eresult);
 void
@@ -1054,7 +1072,7 @@
 
 void
 isc__nm_sendcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
-	       isc_result_t eresult);
+	       isc_result_t eresult, bool async);
 void
 isc__nm_async_sendcb(isc__networker_t *worker, isc__netievent_t *ev0);
 /*%<
@@ -1110,7 +1128,7 @@
 void
 isc__nm_udp_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
 /*%<
- * Set the recv timeout for the UDP socket associated with 'handle'.
+ * Set or clear the recv timeout for the UDP socket associated with 'handle'.
  */
 
 void
@@ -1431,7 +1449,7 @@
 isc_result_t
 isc__nm_socket_connectiontimeout(uv_os_sock_t fd, int timeout_ms);
 /*%<
- * Set the connection timeout in miliseconds, on non-Linux platforms,
+ * Set the connection timeout in milliseconds, on non-Linux platforms,
  * the minimum value must be at least 1000 (1 second).
  */
 
@@ -1542,3 +1560,58 @@
 NETIEVENT_DECL(resume);
 NETIEVENT_DECL(shutdown);
 NETIEVENT_DECL(stop);
+
+void
+isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
+void
+isc__nm_tcp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
+void
+isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
+void
+isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
+
+isc_result_t
+isc__nm_tcpdns_processbuffer(isc_nmsocket_t *sock);
+isc_result_t
+isc__nm_tlsdns_processbuffer(isc_nmsocket_t *sock);
+
+isc__nm_uvreq_t *
+isc__nm_get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr);
+
+void
+isc__nm_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf);
+
+void
+isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
+		    const struct sockaddr *addr, unsigned flags);
+void
+isc__nm_tcpdns_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
+void
+isc__nm_tlsdns_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
+
+void
+isc__nm_start_reading(isc_nmsocket_t *sock);
+void
+isc__nm_stop_reading(isc_nmsocket_t *sock);
+void
+isc__nm_process_sock_buffer(isc_nmsocket_t *sock);
+void
+isc__nm_resume_processing(void *arg);
+bool
+isc__nm_inactive(isc_nmsocket_t *sock);
+
+void
+isc__nm_alloc_dnsbuf(isc_nmsocket_t *sock, size_t len);
+
+void
+isc__nm_failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+		       isc_result_t eresult);
+void
+isc__nm_failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult);
+void
+isc__nm_failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+			  isc_result_t eresult);
+void
+isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
+
+#define STREAM_CLIENTS_PER_CONN 23
diff -Nru bind9-9.16.13/lib/isc/netmgr/tcp.c bind9-9.16.15/lib/isc/netmgr/tcp.c
--- bind9-9.16.13/lib/isc/netmgr/tcp.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/netmgr/tcp.c	2021-04-19 15:45:50.000000000 +0200
@@ -87,9 +87,6 @@
 stop_tcp_child(isc_nmsocket_t *sock);
 
 static void
-start_sock_timer(isc_nmsocket_t *sock);
-
-static void
 start_reading(isc_nmsocket_t *sock);
 
 static void
@@ -716,6 +713,11 @@
 	}
 }
 
+void
+isc__nm_tcp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
+	failed_read_cb(sock, result);
+}
+
 static void
 failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
 	       isc_result_t eresult) {
@@ -723,7 +725,7 @@
 	REQUIRE(VALID_UVREQ(req));
 
 	if (req->cb.send != NULL) {
-		isc__nm_sendcb(sock, req, eresult);
+		isc__nm_sendcb(sock, req, eresult, true);
 	} else {
 		isc__nm_uvreq_put(&req, sock);
 	}
@@ -742,35 +744,6 @@
 }
 
 static void
-readtimeout_cb(uv_timer_t *timer) {
-	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer);
-
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(sock->tid == isc_nm_tid());
-	REQUIRE(sock->reading);
-
-	/*
-	 * Timeout; stop reading and process whatever we have.
-	 */
-	failed_read_cb(sock, ISC_R_TIMEDOUT);
-}
-
-static void
-start_sock_timer(isc_nmsocket_t *sock) {
-	if (sock->read_timeout > 0) {
-		int r = uv_timer_start(&sock->timer, readtimeout_cb,
-				       sock->read_timeout, 0);
-		REQUIRE(r == 0);
-	}
-}
-
-static void
-stop_sock_timer(isc_nmsocket_t *sock) {
-	int r = uv_timer_stop(&sock->timer);
-	REQUIRE(r == 0);
-}
-
-static void
 start_reading(isc_nmsocket_t *sock) {
 	if (sock->reading) {
 		return;
@@ -779,8 +752,6 @@
 	int r = uv_read_start(&sock->uv_handle.stream, tcp_alloc_cb, read_cb);
 	REQUIRE(r == 0);
 	sock->reading = true;
-
-	start_sock_timer(sock);
 }
 
 static void
@@ -793,7 +764,7 @@
 	REQUIRE(r == 0);
 	sock->reading = false;
 
-	stop_sock_timer(sock);
+	isc__nmsocket_timer_stop(sock);
 }
 
 void
@@ -876,6 +847,7 @@
 	}
 
 	start_reading(sock);
+	isc__nmsocket_timer_start(sock);
 }
 
 void
@@ -994,7 +966,7 @@
 	/* The readcb could have paused the reading */
 	if (sock->reading) {
 		/* The timer will be updated */
-		start_sock_timer(sock);
+		isc__nmsocket_timer_restart(sock);
 	}
 
 free:
@@ -1196,7 +1168,7 @@
 		return;
 	}
 
-	isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS);
+	isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, false);
 }
 
 /*
@@ -1476,20 +1448,6 @@
 	failed_read_cb(sock, ISC_R_EOF);
 }
 
-void
-isc__nm_tcp_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
-	isc_nmsocket_t *sock = NULL;
-
-	REQUIRE(VALID_NMHANDLE(handle));
-
-	sock = handle->sock;
-
-	sock->read_timeout = timeout;
-	if (uv_is_active((uv_handle_t *)&sock->timer)) {
-		start_sock_timer(sock);
-	}
-}
-
 int_fast32_t
 isc__nm_tcp_listener_nactive(isc_nmsocket_t *listener) {
 	int_fast32_t nactive;
diff -Nru bind9-9.16.13/lib/isc/netmgr/tcpdns.c bind9-9.16.15/lib/isc/netmgr/tcpdns.c
--- bind9-9.16.13/lib/isc/netmgr/tcpdns.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/netmgr/tcpdns.c	2021-04-19 15:45:50.000000000 +0200
@@ -34,7 +34,6 @@
 #include "netmgr-int.h"
 #include "uv-compat.h"
 
-#define TCPDNS_CLIENTS_PER_CONN 23
 /*%<
  *
  * Maximum number of simultaneous handles in flight supported for a single
@@ -57,20 +56,12 @@
 	return (false);
 }
 
-static void
-tcpdns_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf);
-
-static void
-resume_processing(void *arg);
-
 static isc_result_t
 tcpdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
 
 static void
 tcpdns_close_direct(isc_nmsocket_t *sock);
 
-static isc_result_t
-tcpdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
 static void
 tcpdns_connect_cb(uv_connect_t *uvreq, int status);
 
@@ -78,9 +69,6 @@
 tcpdns_connection_cb(uv_stream_t *server, int status);
 
 static void
-read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
-
-static void
 tcpdns_close_cb(uv_handle_t *uvhandle);
 
 static isc_result_t
@@ -90,100 +78,10 @@
 quota_accept_cb(isc_quota_t *quota, void *sock0);
 
 static void
-failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult);
-
-static void
-failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-	       isc_result_t eresult);
-
-static void
 stop_tcpdns_parent(isc_nmsocket_t *sock);
 static void
 stop_tcpdns_child(isc_nmsocket_t *sock);
 
-static void
-start_sock_timer(isc_nmsocket_t *sock);
-
-static void
-process_sock_buffer(isc_nmsocket_t *sock);
-
-static void
-stop_reading(isc_nmsocket_t *sock);
-
-static isc__nm_uvreq_t *
-get_read_req(isc_nmsocket_t *sock);
-
-static inline void
-alloc_dnsbuf(isc_nmsocket_t *sock, size_t len) {
-	REQUIRE(len <= NM_BIG_BUF);
-
-	if (sock->buf == NULL) {
-		/* We don't have the buffer at all */
-		size_t alloc_len = len < NM_REG_BUF ? NM_REG_BUF : NM_BIG_BUF;
-		sock->buf = isc_mem_allocate(sock->mgr->mctx, alloc_len);
-		sock->buf_size = alloc_len;
-	} else {
-		/* We have the buffer but it's too small */
-		sock->buf = isc_mem_reallocate(sock->mgr->mctx, sock->buf,
-					       NM_BIG_BUF);
-		sock->buf_size = NM_BIG_BUF;
-	}
-}
-
-static bool
-inactive(isc_nmsocket_t *sock) {
-	return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
-		atomic_load(&sock->mgr->closing) ||
-		(sock->server != NULL && !isc__nmsocket_active(sock->server)));
-}
-
-static void
-failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) {
-	REQUIRE(sock->accepting);
-	REQUIRE(sock->server);
-
-	/*
-	 * Detach the quota early to make room for other connections;
-	 * otherwise it'd be detached later asynchronously, and clog
-	 * the quota unnecessarily.
-	 */
-	if (sock->quota != NULL) {
-		isc_quota_detach(&sock->quota);
-	}
-
-	isc__nmsocket_detach(&sock->server);
-
-	sock->accepting = false;
-
-	switch (eresult) {
-	case ISC_R_NOTCONNECTED:
-		/* IGNORE: The client disconnected before we could accept */
-		break;
-	default:
-		isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
-			      ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
-			      "Accepting TCP connection failed: %s",
-			      isc_result_totext(eresult));
-	}
-}
-
-static void
-failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-		  isc_result_t eresult) {
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(VALID_UVREQ(req));
-	REQUIRE(sock->tid == isc_nm_tid());
-	REQUIRE(atomic_load(&sock->connecting));
-	REQUIRE(req->cb.connect != NULL);
-
-	atomic_store(&sock->connecting, false);
-
-	isc__nmsocket_clearcb(sock);
-	isc__nm_connectcb(sock, req, eresult);
-
-	isc__nmsocket_prep_destroy(sock);
-}
-
 static isc_result_t
 tcpdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
 	isc__networker_t *worker = NULL;
@@ -331,7 +229,7 @@
 	return;
 
 error:
-	failed_connect_cb(sock, req, result);
+	isc__nm_failed_connect_cb(sock, req, result);
 }
 
 isc_result_t
@@ -648,7 +546,7 @@
 	REQUIRE(VALID_NMSOCK(ssock));
 	REQUIRE(ssock->tid == isc_nm_tid());
 
-	if (inactive(ssock)) {
+	if (isc__nm_inactive(ssock)) {
 		result = ISC_R_CANCELED;
 		goto done;
 	}
@@ -725,12 +623,13 @@
 	}
 }
 
-static void
-failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
+void
+isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(result != ISC_R_SUCCESS);
 
-	stop_reading(sock);
+	isc__nmsocket_timer_stop(sock);
+	isc__nm_stop_reading(sock);
 
 	if (!sock->recv_read) {
 		goto destroy;
@@ -738,7 +637,7 @@
 	sock->recv_read = false;
 
 	if (sock->recv_cb != NULL) {
-		isc__nm_uvreq_t *req = get_read_req(sock);
+		isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
 		isc__nmsocket_clearcb(sock);
 		isc__nm_readcb(sock, req, result);
 	}
@@ -753,96 +652,6 @@
 	}
 }
 
-static void
-failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-	       isc_result_t eresult) {
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(VALID_UVREQ(req));
-
-	if (req->cb.send != NULL) {
-		isc__nm_sendcb(sock, req, eresult);
-	} else {
-		isc__nm_uvreq_put(&req, sock);
-	}
-}
-
-static isc__nm_uvreq_t *
-get_read_req(isc_nmsocket_t *sock) {
-	isc__nm_uvreq_t *req = NULL;
-
-	req = isc__nm_uvreq_get(sock->mgr, sock);
-	req->cb.recv = sock->recv_cb;
-	req->cbarg = sock->recv_cbarg;
-
-	if (atomic_load(&sock->client)) {
-		isc_nmhandle_attach(sock->statichandle, &req->handle);
-	} else {
-		req->handle = isc__nmhandle_get(sock, NULL, NULL);
-	}
-
-	return (req);
-}
-
-static void
-readtimeout_cb(uv_timer_t *timer) {
-	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer);
-
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(sock->tid == isc_nm_tid());
-	REQUIRE(sock->reading);
-
-	/*
-	 * Timeout; stop reading and process whatever we have.
-	 */
-
-	failed_read_cb(sock, ISC_R_TIMEDOUT);
-}
-
-static void
-start_sock_timer(isc_nmsocket_t *sock) {
-	if (sock->read_timeout > 0) {
-		int r = uv_timer_start(&sock->timer, readtimeout_cb,
-				       sock->read_timeout, 0);
-		RUNTIME_CHECK(r == 0);
-	}
-}
-
-static void
-stop_sock_timer(isc_nmsocket_t *sock) {
-	int r = uv_timer_stop(&sock->timer);
-	RUNTIME_CHECK(r == 0);
-}
-
-static void
-start_reading(isc_nmsocket_t *sock) {
-	int r;
-
-	if (sock->reading) {
-		return;
-	}
-
-	r = uv_read_start(&sock->uv_handle.stream, tcpdns_alloc_cb, read_cb);
-	RUNTIME_CHECK(r == 0);
-	sock->reading = true;
-
-	start_sock_timer(sock);
-}
-
-static void
-stop_reading(isc_nmsocket_t *sock) {
-	int r;
-
-	if (!sock->reading) {
-		return;
-	}
-
-	r = uv_read_stop(&sock->uv_handle.stream);
-	RUNTIME_CHECK(r == 0);
-	sock->reading = false;
-
-	stop_sock_timer(sock);
-}
-
 void
 isc__nm_tcpdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
 	REQUIRE(VALID_NMHANDLE(handle));
@@ -880,31 +689,6 @@
 	return;
 }
 
-/*%<
- * Allocator for TCP read operations. Limited to size 2^16.
- *
- * Note this doesn't actually allocate anything, it just assigns the
- * worker's receive buffer to a socket, and marks it as "in use".
- */
-static void
-tcpdns_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
-	isc_nmsocket_t *sock = uv_handle_get_data(handle);
-	isc__networker_t *worker = NULL;
-
-	UNUSED(size);
-
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(sock->type == isc_nm_tcpdnssocket);
-	REQUIRE(isc__nm_in_netthread());
-
-	worker = &sock->mgr->workers[sock->tid];
-	INSIST(!worker->recvbuf_inuse);
-
-	buf->base = worker->recvbuf;
-	buf->len = size;
-	worker->recvbuf_inuse = true;
-}
-
 void
 isc__nm_async_tcpdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
 	isc__netievent_tcpdnsread_t *ievent =
@@ -916,13 +700,13 @@
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(sock->tid == isc_nm_tid());
 
-	if (inactive(sock)) {
+	if (isc__nm_inactive(sock)) {
 		sock->reading = true;
-		failed_read_cb(sock, ISC_R_CANCELED);
+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
 		return;
 	}
 
-	process_sock_buffer(sock);
+	isc__nm_process_sock_buffer(sock);
 }
 
 /*
@@ -934,8 +718,8 @@
  *
  * The caller will need to unreference the handle.
  */
-static isc_result_t
-processbuffer(isc_nmsocket_t *sock) {
+isc_result_t
+isc__nm_tcpdns_processbuffer(isc_nmsocket_t *sock) {
 	size_t len;
 	isc__nm_uvreq_t *req = NULL;
 	isc_nmhandle_t *handle = NULL;
@@ -943,7 +727,7 @@
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(sock->tid == isc_nm_tid());
 
-	if (inactive(sock)) {
+	if (isc__nm_inactive(sock)) {
 		return (ISC_R_CANCELED);
 	}
 
@@ -964,7 +748,7 @@
 		return (ISC_R_NOMORE);
 	}
 
-	req = get_read_req(sock);
+	req = isc__nm_get_read_req(sock, NULL);
 	REQUIRE(VALID_UVREQ(req));
 
 	/*
@@ -1008,8 +792,9 @@
 	return (ISC_R_SUCCESS);
 }
 
-static void
-read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
+void
+isc__nm_tcpdns_read_cb(uv_stream_t *stream, ssize_t nread,
+		       const uv_buf_t *buf) {
 	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)stream);
 	uint8_t *base = NULL;
 	size_t len;
@@ -1019,8 +804,8 @@
 	REQUIRE(sock->reading);
 	REQUIRE(buf != NULL);
 
-	if (inactive(sock)) {
-		failed_read_cb(sock, ISC_R_CANCELED);
+	if (isc__nm_inactive(sock)) {
+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
 		goto free;
 	}
 
@@ -1030,8 +815,7 @@
 					 sock->statsindex[STATID_RECVFAIL]);
 		}
 
-		failed_read_cb(sock, isc__nm_uverr2result(nread));
-
+		isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nread));
 		goto free;
 	}
 
@@ -1048,7 +832,7 @@
 	 */
 
 	if (sock->buf_len + len > sock->buf_size) {
-		alloc_dnsbuf(sock, sock->buf_len + len);
+		isc__nm_alloc_dnsbuf(sock, sock->buf_len + len);
 	}
 	memmove(sock->buf + sock->buf_len, base, len);
 	sock->buf_len += len;
@@ -1057,7 +841,7 @@
 		sock->read_timeout = atomic_load(&sock->mgr->idle);
 	}
 
-	process_sock_buffer(sock);
+	isc__nm_process_sock_buffer(sock);
 free:
 	isc__nm_free_uvbuf(sock, buf);
 }
@@ -1119,7 +903,7 @@
 	REQUIRE(VALID_NMSOCK(ssock));
 	REQUIRE(ssock->tid == isc_nm_tid());
 
-	if (inactive(ssock)) {
+	if (isc__nm_inactive(ssock)) {
 		if (quota != NULL) {
 			isc_quota_detach(&quota);
 		}
@@ -1201,7 +985,7 @@
 
 	csock->read_timeout = atomic_load(&csock->mgr->init);
 
-	csock->closehandle_cb = resume_processing;
+	csock->closehandle_cb = isc__nm_resume_processing;
 
 	/*
 	 * We need to keep the handle alive until we fail to read or connection
@@ -1209,7 +993,7 @@
 	 * prep_destroy()->tcpdns_close_direct().
 	 */
 	isc_nmhandle_attach(handle, &csock->recv_handle);
-	start_reading(csock);
+	isc__nm_process_sock_buffer(csock);
 
 	/*
 	 * The initial timer has been set, update the read timeout for the next
@@ -1232,7 +1016,7 @@
 
 	atomic_store(&csock->active, false);
 
-	failed_accept_cb(csock, result);
+	isc__nm_failed_accept_cb(csock, result);
 
 	isc__nmsocket_prep_destroy(csock);
 
@@ -1280,11 +1064,12 @@
 
 	if (status < 0) {
 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
-		failed_send_cb(sock, uvreq, isc__nm_uverr2result(status));
+		isc__nm_failed_send_cb(sock, uvreq,
+				       isc__nm_uverr2result(status));
 		return;
 	}
 
-	isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS);
+	isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, false);
 }
 
 /*
@@ -1292,48 +1077,70 @@
  */
 void
 isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0) {
-	isc_result_t result;
 	isc__netievent_tcpdnssend_t *ievent =
 		(isc__netievent_tcpdnssend_t *)ev0;
+
+	REQUIRE(ievent->sock->type == isc_nm_tcpdnssocket);
+	REQUIRE(ievent->sock->tid == isc_nm_tid());
+	REQUIRE(VALID_NMSOCK(ievent->sock));
+	REQUIRE(VALID_UVREQ(ievent->req));
+	REQUIRE(ievent->sock->tid == isc_nm_tid());
+
+	isc_result_t result;
 	isc_nmsocket_t *sock = ievent->sock;
 	isc__nm_uvreq_t *uvreq = ievent->req;
+	uv_buf_t bufs[2] = { { .base = uvreq->tcplen, .len = 2 },
+			     { .base = uvreq->uvbuf.base,
+			       .len = uvreq->uvbuf.len } };
+	int nbufs = 2;
+	int r;
 
 	UNUSED(worker);
 
-	REQUIRE(sock->type == isc_nm_tcpdnssocket);
-	REQUIRE(sock->tid == isc_nm_tid());
-
-	result = tcpdns_send_direct(sock, uvreq);
-	if (result != ISC_R_SUCCESS) {
-		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
-		failed_send_cb(sock, uvreq, result);
+	if (isc__nm_inactive(sock)) {
+		result = ISC_R_CANCELED;
+		goto fail;
 	}
-}
 
-static isc_result_t
-tcpdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
-	int r;
+	r = uv_try_write(&sock->uv_handle.stream, bufs, nbufs);
 
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(VALID_UVREQ(req));
-	REQUIRE(sock->tid == isc_nm_tid());
-	REQUIRE(sock->type == isc_nm_tcpdnssocket);
-
-	uv_buf_t bufs[2] = { { .base = req->tcplen, .len = 2 },
-			     { .base = req->uvbuf.base,
-			       .len = req->uvbuf.len } };
+	if (r == (int)(bufs[0].len + bufs[1].len)) {
+		/* Wrote everything */
+		isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, true);
+		return;
+	}
 
-	if (inactive(sock)) {
-		return (ISC_R_CANCELED);
+	if (r == 1) {
+		/* Partial write of DNSMSG length */
+		bufs[0].base = uvreq->tcplen + 1;
+		bufs[0].len = 1;
+	} else if (r > 0) {
+		/* Partial write of DNSMSG */
+		nbufs = 1;
+		bufs[0].base = uvreq->uvbuf.base + (r - 2);
+		bufs[0].len = uvreq->uvbuf.len - (r - 2);
+	} else if (r == UV_ENOSYS || r == UV_EAGAIN) {
+		/* uv_try_write not support, send asynchronously */
+	} else {
+		/* error sending data */
+		result = isc__nm_uverr2result(r);
+		goto fail;
 	}
 
-	r = uv_write(&req->uv_req.write, &sock->uv_handle.stream, bufs, 2,
+	r = uv_write(&uvreq->uv_req.write, &sock->uv_handle.stream, bufs, nbufs,
 		     tcpdns_send_cb);
 	if (r < 0) {
-		return (isc__nm_uverr2result(r));
+		result = isc__nm_uverr2result(r);
+		goto fail;
 	}
 
-	return (ISC_R_SUCCESS);
+	return;
+
+fail:
+	if (result != ISC_R_SUCCESS) {
+		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
+		isc__nm_failed_send_cb(sock, uvreq, result);
+	}
 }
 
 static void
@@ -1462,7 +1269,8 @@
 		isc_nmhandle_detach(&sock->recv_handle);
 	}
 
-	stop_reading(sock);
+	isc__nmsocket_timer_stop(sock);
+	isc__nm_stop_reading(sock);
 	uv_close((uv_handle_t *)&sock->timer, timer_close_cb);
 }
 
@@ -1524,7 +1332,7 @@
 	}
 
 	if (sock->statichandle) {
-		failed_read_cb(sock, ISC_R_CANCELED);
+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
 		return;
 	}
 
@@ -1564,22 +1372,7 @@
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(sock->tid == isc_nm_tid());
 
-	failed_read_cb(sock, ISC_R_EOF);
-}
-
-void
-isc__nm_tcpdns_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
-	isc_nmsocket_t *sock = NULL;
-
-	REQUIRE(VALID_NMHANDLE(handle));
-	REQUIRE(VALID_NMSOCK(handle->sock));
-
-	sock = handle->sock;
-
-	sock->read_timeout = timeout;
-	if (uv_is_active((uv_handle_t *)&sock->timer)) {
-		start_sock_timer(sock);
-	}
+	isc__nm_failed_read_cb(sock, ISC_R_EOF);
 }
 
 void
@@ -1601,7 +1394,8 @@
 	 * is released.
 	 */
 
-	stop_reading(sock);
+	isc__nmsocket_timer_stop(sock);
+	isc__nm_stop_reading(sock);
 	atomic_store(&sock->sequential, true);
 }
 
@@ -1617,65 +1411,3 @@
 
 	atomic_store(&sock->keepalive, value);
 }
-
-static void
-process_sock_buffer(isc_nmsocket_t *sock) {
-	/*
-	 * 1. When process_buffer receives incomplete DNS message,
-	 *    we don't touch any timers
-	 *
-	 * 2. When we receive at least one full DNS message, we stop the timers
-	 *    until resume_processing calls this function again and restarts the
-	 *    reading and the timers
-	 */
-
-	/*
-	 * Process a DNS messages.  Stop if this is client socket, or the server
-	 * socket has been set to sequential mode or the number of queries we
-	 * are processing simultaneously have reached the clients-per-connection
-	 * limit.
-	 */
-	for (;;) {
-		isc_result_t result = processbuffer(sock);
-		switch (result) {
-		case ISC_R_NOMORE:
-			start_reading(sock);
-			return;
-		case ISC_R_CANCELED:
-			stop_reading(sock);
-			return;
-		case ISC_R_SUCCESS:
-			if (atomic_load(&sock->client) ||
-			    atomic_load(&sock->sequential) ||
-			    atomic_load(&sock->ah) >= TCPDNS_CLIENTS_PER_CONN)
-			{
-				stop_reading(sock);
-				return;
-			}
-			break;
-		default:
-			INSIST(0);
-		}
-	}
-}
-
-static void
-resume_processing(void *arg) {
-	isc_nmsocket_t *sock = (isc_nmsocket_t *)arg;
-
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(sock->tid == isc_nm_tid());
-	REQUIRE(sock->type == isc_nm_tcpdnssocket);
-	REQUIRE(!atomic_load(&sock->client));
-
-	if (inactive(sock)) {
-		return;
-	}
-
-	if (atomic_load(&sock->ah) == 0) {
-		/* Nothing is active; sockets can timeout now */
-		start_sock_timer(sock);
-	}
-
-	process_sock_buffer(sock);
-}
diff -Nru bind9-9.16.13/lib/isc/netmgr/tlsdns.c bind9-9.16.15/lib/isc/netmgr/tlsdns.c
--- bind9-9.16.13/lib/isc/netmgr/tlsdns.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/netmgr/tlsdns.c	2021-04-19 15:45:50.000000000 +0200
@@ -35,9 +35,6 @@
 #include "openssl_shim.h"
 #include "uv-compat.h"
 
-#define TLS_BUF_SIZE 65536
-
-#define TLSDNS_CLIENTS_PER_CONN 23
 /*%<
  *
  * Maximum number of simultaneous handles in flight supported for a single
@@ -50,12 +47,6 @@
 static void
 tls_error(isc_nmsocket_t *sock, isc_result_t result);
 
-static void
-tlsdns_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf);
-
-static void
-resume_processing(void *arg);
-
 static isc_result_t
 tlsdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
 
@@ -71,9 +62,6 @@
 tlsdns_connection_cb(uv_stream_t *server, int status);
 
 static void
-read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
-
-static void
 tlsdns_close_cb(uv_handle_t *uvhandle);
 
 static isc_result_t
@@ -83,33 +71,11 @@
 quota_accept_cb(isc_quota_t *quota, void *sock0);
 
 static void
-failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult);
-
-static void
-failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-	       isc_result_t eresult);
-
-static void
 stop_tlsdns_parent(isc_nmsocket_t *sock);
 static void
 stop_tlsdns_child(isc_nmsocket_t *sock);
 
 static void
-start_reading(isc_nmsocket_t *sock);
-
-static void
-stop_reading(isc_nmsocket_t *sock);
-
-static void
-start_sock_timer(isc_nmsocket_t *sock);
-
-static void
-process_sock_buffer(isc_nmsocket_t *sock);
-
-static isc__nm_uvreq_t *
-get_read_req(isc_nmsocket_t *sock);
-
-static void
 async_tlsdns_cycle(isc_nmsocket_t *sock) __attribute__((unused));
 
 static isc_result_t
@@ -128,78 +94,6 @@
 	return (false);
 }
 
-static inline void
-alloc_dnsbuf(isc_nmsocket_t *sock, size_t len) {
-	REQUIRE(len <= NM_BIG_BUF);
-
-	if (sock->buf == NULL) {
-		/* We don't have the buffer at all */
-		size_t alloc_len = len < NM_REG_BUF ? NM_REG_BUF : NM_BIG_BUF;
-		sock->buf = isc_mem_allocate(sock->mgr->mctx, alloc_len);
-		sock->buf_size = alloc_len;
-	} else {
-		/* We have the buffer but it's too small */
-		sock->buf = isc_mem_reallocate(sock->mgr->mctx, sock->buf,
-					       NM_BIG_BUF);
-		sock->buf_size = NM_BIG_BUF;
-	}
-}
-
-static bool
-inactive(isc_nmsocket_t *sock) {
-	return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
-		atomic_load(&sock->mgr->closing) ||
-		(sock->server != NULL && !isc__nmsocket_active(sock->server)));
-}
-
-static void
-failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) {
-	REQUIRE(sock->accepting);
-	REQUIRE(sock->server);
-
-	/*
-	 * Detach the quota early to make room for other connections;
-	 * otherwise it'd be detached later asynchronously, and clog
-	 * the quota unnecessarily.
-	 */
-	if (sock->quota != NULL) {
-		isc_quota_detach(&sock->quota);
-	}
-
-	isc__nmsocket_detach(&sock->server);
-
-	sock->accepting = false;
-
-	switch (eresult) {
-	case ISC_R_NOTCONNECTED:
-		/* IGNORE: The client disconnected before we could accept */
-		break;
-	default:
-		isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
-			      ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
-			      "Accepting TCP connection failed: %s",
-			      isc_result_totext(eresult));
-	}
-}
-
-static void
-failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-		  isc_result_t eresult) {
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(VALID_UVREQ(req));
-	REQUIRE(sock->tid == isc_nm_tid());
-	REQUIRE(atomic_load(&sock->connecting));
-	REQUIRE(req->cb.connect != NULL);
-
-	atomic_store(&sock->connecting, false);
-
-	isc__nmsocket_clearcb(sock);
-
-	isc__nm_connectcb(sock, req, eresult);
-
-	isc__nmsocket_prep_destroy(sock);
-}
-
 static isc_result_t
 tlsdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
 	isc__networker_t *worker = NULL;
@@ -344,12 +238,12 @@
 	/*
 	 *
 	 */
-	r = BIO_new_bio_pair(&sock->tls.ssl_wbio, TLS_BUF_SIZE,
-			     &sock->tls.app_rbio, TLS_BUF_SIZE);
+	r = BIO_new_bio_pair(&sock->tls.ssl_wbio, ISC_NETMGR_TLSBUF_SIZE,
+			     &sock->tls.app_rbio, ISC_NETMGR_TLSBUF_SIZE);
 	RUNTIME_CHECK(r == 1);
 
-	r = BIO_new_bio_pair(&sock->tls.ssl_rbio, TLS_BUF_SIZE,
-			     &sock->tls.app_wbio, TLS_BUF_SIZE);
+	r = BIO_new_bio_pair(&sock->tls.ssl_rbio, ISC_NETMGR_TLSBUF_SIZE,
+			     &sock->tls.app_wbio, ISC_NETMGR_TLSBUF_SIZE);
 	RUNTIME_CHECK(r == 1);
 
 #if HAVE_SSL_SET0_RBIO && HAVE_SSL_SET0_WBIO
@@ -372,7 +266,8 @@
 
 	sock->tls.pending_req = req;
 
-	start_reading(sock);
+	isc__nm_process_sock_buffer(sock);
+
 	result = tls_cycle(sock);
 	if (result != ISC_R_SUCCESS) {
 		goto error;
@@ -381,7 +276,7 @@
 	return;
 
 error:
-	failed_connect_cb(sock, req, result);
+	isc__nm_failed_connect_cb(sock, req, result);
 }
 
 isc_result_t
@@ -704,7 +599,7 @@
 	REQUIRE(VALID_NMSOCK(ssock));
 	REQUIRE(ssock->tid == isc_nm_tid());
 
-	if (inactive(ssock)) {
+	if (isc__nm_inactive(ssock)) {
 		result = ISC_R_CANCELED;
 		goto done;
 	}
@@ -856,17 +751,18 @@
 	}
 }
 
-static void
-failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
+void
+isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(result != ISC_R_SUCCESS);
 
-	stop_reading(sock);
+	isc__nmsocket_timer_stop(sock);
+	isc__nm_stop_reading(sock);
 
 	if (sock->tls.pending_req) {
 		isc__nm_uvreq_t *req = sock->tls.pending_req;
 		sock->tls.pending_req = NULL;
-		failed_connect_cb(sock, req, ISC_R_CANCELED);
+		isc__nm_failed_connect_cb(sock, req, ISC_R_CANCELED);
 	}
 
 	if (!sock->recv_read) {
@@ -875,7 +771,7 @@
 	sock->recv_read = false;
 
 	if (sock->recv_cb != NULL) {
-		isc__nm_uvreq_t *req = get_read_req(sock);
+		isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
 		isc__nmsocket_clearcb(sock);
 		isc__nm_readcb(sock, req, result);
 	}
@@ -890,96 +786,6 @@
 	}
 }
 
-static void
-failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-	       isc_result_t eresult) {
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(VALID_UVREQ(req));
-
-	if (req->cb.send != NULL) {
-		isc__nm_sendcb(sock, req, eresult);
-	} else {
-		isc__nm_uvreq_put(&req, sock);
-	}
-}
-
-static isc__nm_uvreq_t *
-get_read_req(isc_nmsocket_t *sock) {
-	isc__nm_uvreq_t *req = NULL;
-
-	req = isc__nm_uvreq_get(sock->mgr, sock);
-	req->cb.recv = sock->recv_cb;
-	req->cbarg = sock->recv_cbarg;
-
-	if (atomic_load(&sock->client)) {
-		isc_nmhandle_attach(sock->statichandle, &req->handle);
-	} else {
-		req->handle = isc__nmhandle_get(sock, NULL, NULL);
-	}
-
-	return (req);
-}
-
-static void
-readtimeout_cb(uv_timer_t *timer) {
-	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer);
-
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(sock->tid == isc_nm_tid());
-	REQUIRE(sock->reading);
-
-	/*
-	 * Timeout; stop reading and process whatever we have.
-	 */
-
-	failed_read_cb(sock, ISC_R_TIMEDOUT);
-}
-
-static void
-start_sock_timer(isc_nmsocket_t *sock) {
-	if (sock->read_timeout > 0) {
-		int r = uv_timer_start(&sock->timer, readtimeout_cb,
-				       sock->read_timeout, 0);
-		RUNTIME_CHECK(r == 0);
-	}
-}
-
-static void
-stop_sock_timer(isc_nmsocket_t *sock) {
-	int r = uv_timer_stop(&sock->timer);
-	RUNTIME_CHECK(r == 0);
-}
-
-static void
-start_reading(isc_nmsocket_t *sock) {
-	int r;
-
-	if (sock->reading) {
-		return;
-	}
-
-	r = uv_read_start(&sock->uv_handle.stream, tlsdns_alloc_cb, read_cb);
-	RUNTIME_CHECK(r == 0);
-	sock->reading = true;
-
-	start_sock_timer(sock);
-}
-
-static void
-stop_reading(isc_nmsocket_t *sock) {
-	int r;
-
-	if (!sock->reading) {
-		return;
-	}
-
-	r = uv_read_stop(&sock->uv_handle.stream);
-	RUNTIME_CHECK(r == 0);
-	sock->reading = false;
-
-	stop_sock_timer(sock);
-}
-
 void
 isc__nm_tlsdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
 	REQUIRE(VALID_NMHANDLE(handle));
@@ -1017,38 +823,6 @@
 	return;
 }
 
-/*%<
- * Allocator for TCP read operations. Limited to size 2^16.
- *
- * Note this doesn't actually allocate anything, it just assigns the
- * worker's receive buffer to a socket, and marks it as "in use".
- */
-static void
-tlsdns_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
-	isc_nmsocket_t *sock = uv_handle_get_data(handle);
-	isc__networker_t *worker = NULL;
-
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(sock->type == isc_nm_tlsdnssocket);
-	REQUIRE(isc__nm_in_netthread());
-
-	/*
-	 * We need to limit the individual chunks to be read, so the BIO_write()
-	 * will always succeed and the consumed before the next readcb is
-	 * called.
-	 */
-	if (size >= TLS_BUF_SIZE) {
-		size = TLS_BUF_SIZE;
-	}
-
-	worker = &sock->mgr->workers[sock->tid];
-	INSIST(!worker->recvbuf_inuse);
-
-	buf->base = worker->recvbuf;
-	buf->len = size;
-	worker->recvbuf_inuse = true;
-}
-
 void
 isc__nm_async_tlsdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
 	isc__netievent_tlsdnsread_t *ievent =
@@ -1061,16 +835,15 @@
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(sock->tid == isc_nm_tid());
 
-	if (inactive(sock)) {
+	if (isc__nm_inactive(sock)) {
 		sock->reading = true;
-		failed_read_cb(sock, ISC_R_CANCELED);
+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
 		return;
 	}
 
 	result = tls_cycle(sock);
 	if (result != ISC_R_SUCCESS) {
-		stop_reading(sock);
-		failed_read_cb(sock, result);
+		isc__nm_failed_read_cb(sock, result);
 	}
 }
 
@@ -1083,8 +856,8 @@
  *
  * The caller will need to unreference the handle.
  */
-static isc_result_t
-processbuffer(isc_nmsocket_t *sock) {
+isc_result_t
+isc__nm_tlsdns_processbuffer(isc_nmsocket_t *sock) {
 	size_t len;
 	isc__nm_uvreq_t *req = NULL;
 	isc_nmhandle_t *handle = NULL;
@@ -1092,7 +865,7 @@
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(sock->tid == isc_nm_tid());
 
-	if (inactive(sock)) {
+	if (isc__nm_inactive(sock)) {
 		return (ISC_R_CANCELED);
 	}
 
@@ -1113,7 +886,7 @@
 		return (ISC_R_NOMORE);
 	}
 
-	req = get_read_req(sock);
+	req = isc__nm_get_read_req(sock, NULL);
 	REQUIRE(VALID_UVREQ(req));
 
 	/*
@@ -1170,12 +943,13 @@
 			(void)SSL_peek(sock->tls.ssl, &(char){ '\0' }, 0);
 
 			int pending = SSL_pending(sock->tls.ssl);
-			if (pending > TLS_BUF_SIZE) {
-				pending = TLS_BUF_SIZE;
+			if (pending > ISC_NETMGR_TLSBUF_SIZE) {
+				pending = ISC_NETMGR_TLSBUF_SIZE;
 			}
 
 			if ((sock->buf_len + pending) > sock->buf_size) {
-				alloc_dnsbuf(sock, sock->buf_len + pending);
+				isc__nm_alloc_dnsbuf(sock,
+						     sock->buf_len + pending);
 			}
 
 			len = 0;
@@ -1184,7 +958,7 @@
 					 sock->buf_size - sock->buf_len, &len);
 			if (rv != 1) {
 				/* Process what's in the buffer so far */
-				process_sock_buffer(sock);
+				isc__nm_process_sock_buffer(sock);
 
 				/* FIXME: Should we call failed_read_cb()? */
 				break;
@@ -1194,7 +968,7 @@
 
 			sock->buf_len += len;
 
-			process_sock_buffer(sock);
+			isc__nm_process_sock_buffer(sock);
 		}
 	} else if (!SSL_is_init_finished(sock->tls.ssl)) {
 		if (SSL_is_server(sock->tls.ssl)) {
@@ -1216,7 +990,7 @@
 		if (sock->tls.state == TLS_STATE_NONE &&
 		    !SSL_is_init_finished(sock->tls.ssl)) {
 			sock->tls.state = TLS_STATE_HANDSHAKE;
-			start_reading(sock);
+			isc__nm_process_sock_buffer(sock);
 		}
 		/* else continue reading */
 		break;
@@ -1269,10 +1043,9 @@
 tls_error(isc_nmsocket_t *sock, isc_result_t result) {
 	switch (sock->tls.state) {
 	case TLS_STATE_HANDSHAKE:
-		stop_reading(sock);
-		break;
 	case TLS_STATE_IO:
-		stop_reading(sock);
+		isc__nmsocket_timer_stop(sock);
+		isc__nm_stop_reading(sock);
 		break;
 	case TLS_STATE_ERROR:
 		return;
@@ -1336,8 +1109,8 @@
 			break;
 		}
 
-		if (pending > TLS_BUF_SIZE) {
-			pending = TLS_BUF_SIZE;
+		if (pending > ISC_NETMGR_TLSBUF_SIZE) {
+			pending = ISC_NETMGR_TLSBUF_SIZE;
 		}
 
 		sock->tls.senddata.base = isc_mem_get(sock->mgr->mctx, pending);
@@ -1468,8 +1241,9 @@
 	}
 }
 
-static void
-read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
+void
+isc__nm_tlsdns_read_cb(uv_stream_t *stream, ssize_t nread,
+		       const uv_buf_t *buf) {
 	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)stream);
 	size_t len;
 	isc_result_t result;
@@ -1480,8 +1254,8 @@
 	REQUIRE(sock->reading);
 	REQUIRE(buf != NULL);
 
-	if (inactive(sock)) {
-		failed_read_cb(sock, ISC_R_CANCELED);
+	if (isc__nm_inactive(sock)) {
+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
 		goto free;
 	}
 
@@ -1491,7 +1265,7 @@
 					 sock->statsindex[STATID_RECVFAIL]);
 		}
 
-		failed_read_cb(sock, isc__nm_uverr2result(nread));
+		isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nread));
 
 		goto free;
 	}
@@ -1506,13 +1280,13 @@
 	rv = BIO_write_ex(sock->tls.app_wbio, buf->base, (size_t)nread, &len);
 
 	if (rv <= 0 || (size_t)nread != len) {
-		failed_read_cb(sock, ISC_R_TLSERROR);
+		isc__nm_failed_read_cb(sock, ISC_R_TLSERROR);
 		goto free;
 	}
 
 	result = tls_cycle(sock);
 	if (result != ISC_R_SUCCESS) {
-		failed_read_cb(sock, result);
+		isc__nm_failed_read_cb(sock, result);
 	}
 free:
 	async_tlsdns_cycle(sock);
@@ -1576,7 +1350,7 @@
 	REQUIRE(VALID_NMSOCK(ssock));
 	REQUIRE(ssock->tid == isc_nm_tid());
 
-	if (inactive(ssock)) {
+	if (isc__nm_inactive(ssock)) {
 		if (quota != NULL) {
 			isc_quota_detach(&quota);
 		}
@@ -1669,12 +1443,12 @@
 
 	RUNTIME_CHECK(csock->tls.ssl != NULL);
 
-	r = BIO_new_bio_pair(&csock->tls.ssl_wbio, TLS_BUF_SIZE,
-			     &csock->tls.app_rbio, TLS_BUF_SIZE);
+	r = BIO_new_bio_pair(&csock->tls.ssl_wbio, ISC_NETMGR_TLSBUF_SIZE,
+			     &csock->tls.app_rbio, ISC_NETMGR_TLSBUF_SIZE);
 	RUNTIME_CHECK(r == 1);
 
-	r = BIO_new_bio_pair(&csock->tls.ssl_rbio, TLS_BUF_SIZE,
-			     &csock->tls.app_wbio, TLS_BUF_SIZE);
+	r = BIO_new_bio_pair(&csock->tls.ssl_rbio, ISC_NETMGR_TLSBUF_SIZE,
+			     &csock->tls.app_wbio, ISC_NETMGR_TLSBUF_SIZE);
 	RUNTIME_CHECK(r == 1);
 
 #if HAVE_SSL_SET0_RBIO && HAVE_SSL_SET0_WBIO
@@ -1700,7 +1474,7 @@
 
 	csock->read_timeout = atomic_load(&csock->mgr->init);
 
-	csock->closehandle_cb = resume_processing;
+	csock->closehandle_cb = isc__nm_resume_processing;
 
 	/*
 	 * We need to keep the handle alive until we fail to read or connection
@@ -1719,7 +1493,7 @@
 
 	isc_nmhandle_detach(&handle);
 
-	start_reading(csock);
+	isc__nm_process_sock_buffer(csock);
 
 	/*
 	 * sock is now attached to the handle.
@@ -1731,7 +1505,7 @@
 failure:
 	atomic_store(&csock->active, false);
 
-	failed_accept_cb(csock, result);
+	isc__nm_failed_accept_cb(csock, result);
 
 	isc__nmsocket_prep_destroy(csock);
 
@@ -1787,7 +1561,7 @@
 	result = tlsdns_send_direct(sock, uvreq);
 	if (result != ISC_R_SUCCESS) {
 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
-		failed_send_cb(sock, uvreq, result);
+		isc__nm_failed_send_cb(sock, uvreq, result);
 	}
 }
 
@@ -1818,7 +1592,7 @@
 		return (result);
 	}
 
-	if (inactive(sock)) {
+	if (isc__nm_inactive(sock)) {
 		return (ISC_R_CANCELED);
 	}
 
@@ -1842,7 +1616,7 @@
 		/* SSL_write_ex() doesn't do partial writes */
 		INSIST(sendlen == bytes);
 
-		isc__nm_sendcb(sock, req, ISC_R_SUCCESS);
+		isc__nm_sendcb(sock, req, ISC_R_SUCCESS, true);
 		async_tlsdns_cycle(sock);
 		return (ISC_R_SUCCESS);
 	}
@@ -2012,7 +1786,8 @@
 		isc_nmhandle_detach(&sock->recv_handle);
 	}
 
-	stop_reading(sock);
+	isc__nmsocket_timer_stop(sock);
+	isc__nm_stop_reading(sock);
 	uv_close((uv_handle_t *)&sock->timer, timer_close_cb);
 }
 
@@ -2076,11 +1851,11 @@
 	if (sock->tls.pending_req) {
 		isc__nm_uvreq_t *req = sock->tls.pending_req;
 		sock->tls.pending_req = NULL;
-		failed_connect_cb(sock, req, ISC_R_CANCELED);
+		isc__nm_failed_connect_cb(sock, req, ISC_R_CANCELED);
 	}
 
 	if (sock->statichandle) {
-		failed_read_cb(sock, ISC_R_CANCELED);
+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
 		return;
 	}
 
@@ -2120,22 +1895,7 @@
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(sock->tid == isc_nm_tid());
 
-	failed_read_cb(sock, ISC_R_EOF);
-}
-
-void
-isc__nm_tlsdns_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
-	isc_nmsocket_t *sock = NULL;
-
-	REQUIRE(VALID_NMHANDLE(handle));
-	REQUIRE(VALID_NMSOCK(handle->sock));
-
-	sock = handle->sock;
-
-	sock->read_timeout = timeout;
-	if (uv_is_active((uv_handle_t *)&sock->timer)) {
-		start_sock_timer(sock);
-	}
+	isc__nm_failed_read_cb(sock, ISC_R_EOF);
 }
 
 void
@@ -2157,7 +1917,8 @@
 	 * is released.
 	 */
 
-	stop_reading(sock);
+	isc__nmsocket_timer_stop(sock);
+	isc__nm_stop_reading(sock);
 	atomic_store(&sock->sequential, true);
 }
 
@@ -2173,65 +1934,3 @@
 
 	atomic_store(&sock->keepalive, value);
 }
-
-static void
-process_sock_buffer(isc_nmsocket_t *sock) {
-	/*
-	 * 1. When process_buffer receives incomplete DNS message,
-	 *    we don't touch any timers
-	 *
-	 * 2. When we receive at least one full DNS message, we stop the timers
-	 *    until resume_processing calls this function again and restarts the
-	 *    reading and the timers
-	 */
-
-	/*
-	 * Process a DNS messages.  Stop if this is client socket, or the server
-	 * socket has been set to sequential mode or the number of queries we
-	 * are processing simultaneously have reached the clients-per-connection
-	 * limit.
-	 */
-	for (;;) {
-		isc_result_t result = processbuffer(sock);
-		switch (result) {
-		case ISC_R_NOMORE:
-			start_reading(sock);
-			return;
-		case ISC_R_CANCELED:
-			stop_reading(sock);
-			return;
-		case ISC_R_SUCCESS:
-			if (atomic_load(&sock->client) ||
-			    atomic_load(&sock->sequential) ||
-			    atomic_load(&sock->ah) >= TLSDNS_CLIENTS_PER_CONN)
-			{
-				stop_reading(sock);
-				return;
-			}
-			break;
-		default:
-			INSIST(0);
-		}
-	}
-}
-
-static void
-resume_processing(void *arg) {
-	isc_nmsocket_t *sock = (isc_nmsocket_t *)arg;
-
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(sock->tid == isc_nm_tid());
-	REQUIRE(sock->type == isc_nm_tlsdnssocket);
-	REQUIRE(!atomic_load(&sock->client));
-
-	if (inactive(sock)) {
-		return;
-	}
-
-	if (atomic_load(&sock->ah) == 0) {
-		/* Nothing is active; sockets can timeout now */
-		start_sock_timer(sock);
-	}
-
-	process_sock_buffer(sock);
-}
diff -Nru bind9-9.16.13/lib/isc/netmgr/udp.c bind9-9.16.15/lib/isc/netmgr/udp.c
--- bind9-9.16.13/lib/isc/netmgr/udp.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/netmgr/udp.c	2021-04-19 15:45:50.000000000 +0200
@@ -51,32 +51,10 @@
 udp_close_direct(isc_nmsocket_t *sock);
 
 static void
-failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
-
-static void
-failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-	       isc_result_t eresult);
-
-static void
 stop_udp_parent(isc_nmsocket_t *sock);
 static void
 stop_udp_child(isc_nmsocket_t *sock);
 
-static void
-start_reading(isc_nmsocket_t *sock);
-static void
-stop_reading(isc_nmsocket_t *sock);
-
-static isc__nm_uvreq_t *
-get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr);
-
-static bool
-inactive(isc_nmsocket_t *sock) {
-	return (!isc__nmsocket_active(sock) ||
-		atomic_load(&sock->mgr->closing) ||
-		(sock->server != NULL && !isc__nmsocket_active(sock->server)));
-}
-
 static uv_os_sock_t
 isc__nm_udp_lb_socket(sa_family_t sa_family) {
 	isc_result_t result;
@@ -192,32 +170,6 @@
 	return (result);
 }
 
-/*%<
- * Allocator for UDP recv operations. Limited to size 20 * (2^16 + 2),
- * which allows enough space for recvmmsg() to get multiple messages at
- * a time.
- *
- * Note this doesn't actually allocate anything, it just assigns the
- * worker's receive buffer to a socket, and marks it as "in use".
- */
-static void
-udp_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
-	isc_nmsocket_t *sock = uv_handle_get_data(handle);
-	isc__networker_t *worker = NULL;
-
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(sock->type == isc_nm_udpsocket);
-	REQUIRE(isc__nm_in_netthread());
-	REQUIRE(size <= ISC_NETMGR_RECVBUF_SIZE);
-
-	worker = &sock->mgr->workers[sock->tid];
-	INSIST(!worker->recvbuf_inuse);
-
-	buf->base = worker->recvbuf;
-	buf->len = ISC_NETMGR_RECVBUF_SIZE;
-	worker->recvbuf_inuse = true;
-}
-
 /*
  * Asynchronous 'udplisten' call handler: start listening on a UDP socket.
  */
@@ -306,7 +258,8 @@
 	uv_send_buffer_size(&sock->uv_handle.handle,
 			    &(int){ ISC_SEND_BUFFER_SIZE });
 #endif
-	r = uv_udp_recv_start(&sock->uv_handle.udp, udp_alloc_cb, udp_recv_cb);
+	r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb,
+			      udp_recv_cb);
 	if (r != 0) {
 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]);
 		goto done;
@@ -430,7 +383,7 @@
 	 *   we can free the buffer and bail.
 	 */
 	if (addr == NULL) {
-		failed_read_cb(sock, ISC_R_EOF);
+		isc__nm_failed_read_cb(sock, ISC_R_EOF);
 		goto free;
 	}
 
@@ -438,19 +391,19 @@
 	 * - If the socket is no longer active.
 	 */
 	if (!isc__nmsocket_active(sock)) {
-		failed_read_cb(sock, ISC_R_CANCELED);
+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
 		goto free;
 	}
 
 	if (nrecv < 0) {
-		failed_read_cb(sock, isc__nm_uverr2result(nrecv));
+		isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nrecv));
 		goto free;
 	}
 
 	result = isc_sockaddr_fromsockaddr(&sockaddr, addr);
 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
 
-	req = get_read_req(sock, &sockaddr);
+	req = isc__nm_get_read_req(sock, &sockaddr);
 
 	/*
 	 * The callback will be called synchronously, because result is
@@ -570,15 +523,15 @@
 	REQUIRE(sock->tid == isc_nm_tid());
 	UNUSED(worker);
 
-	if (inactive(sock)) {
-		failed_send_cb(sock, uvreq, ISC_R_CANCELED);
+	if (isc__nm_inactive(sock)) {
+		isc__nm_failed_send_cb(sock, uvreq, ISC_R_CANCELED);
 		return;
 	}
 
 	result = udp_send_direct(sock, uvreq, &ievent->peer);
 	if (result != ISC_R_SUCCESS) {
 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
-		failed_send_cb(sock, uvreq, result);
+		isc__nm_failed_send_cb(sock, uvreq, result);
 	}
 }
 
@@ -596,7 +549,7 @@
 		isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]);
 	}
 
-	isc__nm_sendcb(sock, uvreq, result);
+	isc__nm_sendcb(sock, uvreq, result, false);
 }
 
 /*
@@ -614,7 +567,7 @@
 	REQUIRE(sock->tid == isc_nm_tid());
 	REQUIRE(sock->type == isc_nm_udpsocket);
 
-	if (inactive(sock)) {
+	if (isc__nm_inactive(sock)) {
 		return (ISC_R_CANCELED);
 	}
 
@@ -839,9 +792,9 @@
 	return (result);
 }
 
-static void
-udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
-	    const struct sockaddr *addr, unsigned flags) {
+void
+isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
+		    const struct sockaddr *addr, unsigned flags) {
 	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle);
 	REQUIRE(VALID_NMSOCK(sock));
 
@@ -854,17 +807,17 @@
 	 * does not.
 	 */
 	if (!sock->parent) {
-		stop_reading(sock);
+		isc__nm_stop_reading(sock);
 	}
 }
 
-static void
-failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
+void
+isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(result != ISC_R_SUCCESS);
 
 	if (atomic_load(&sock->client)) {
-		stop_reading(sock);
+		isc__nm_stop_reading(sock);
 
 		if (!sock->recv_read) {
 			goto destroy;
@@ -872,7 +825,7 @@
 		sock->recv_read = false;
 
 		if (sock->recv_cb != NULL) {
-			isc__nm_uvreq_t *req = get_read_req(sock, NULL);
+			isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
 			isc__nmsocket_clearcb(sock);
 			isc__nm_readcb(sock, req, result);
 		}
@@ -895,55 +848,11 @@
 	sock->recv_read = false;
 
 	if (sock->recv_cb != NULL) {
-		isc__nm_uvreq_t *req = get_read_req(sock, NULL);
+		isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
 		isc__nm_readcb(sock, req, result);
 	}
 }
 
-static void
-failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
-	       isc_result_t eresult) {
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(VALID_UVREQ(req));
-
-	if (req->cb.send != NULL) {
-		isc__nm_sendcb(sock, req, eresult);
-	} else {
-		isc__nm_uvreq_put(&req, sock);
-	}
-}
-
-static isc__nm_uvreq_t *
-get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr) {
-	isc__nm_uvreq_t *req = NULL;
-
-	req = isc__nm_uvreq_get(sock->mgr, sock);
-	req->cb.recv = sock->recv_cb;
-	req->cbarg = sock->recv_cbarg;
-
-	if (atomic_load(&sock->client)) {
-		isc_nmhandle_attach(sock->statichandle, &req->handle);
-	} else {
-		req->handle = isc__nmhandle_get(sock, sockaddr, NULL);
-	}
-
-	return req;
-}
-
-static void
-readtimeout_cb(uv_timer_t *handle) {
-	isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle);
-
-	REQUIRE(VALID_NMSOCK(sock));
-	REQUIRE(sock->tid == isc_nm_tid());
-	REQUIRE(sock->reading);
-
-	/*
-	 * Timeout; stop reading and process whatever we have.
-	 */
-	failed_read_cb(sock, ISC_R_TIMEDOUT);
-}
-
 /*
  * Asynchronous 'udpread' call handler: start or resume reading on a
  * socket; pause reading and call the 'recv' callback after each
@@ -959,55 +868,14 @@
 	REQUIRE(VALID_NMSOCK(sock));
 	REQUIRE(sock->tid == isc_nm_tid());
 
-	if (inactive(sock)) {
+	if (isc__nm_inactive(sock)) {
 		sock->reading = true;
-		failed_read_cb(sock, ISC_R_CANCELED);
+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
 		return;
 	}
 
-	start_reading(sock);
-}
-
-static void
-start_sock_timer(isc_nmsocket_t *sock) {
-	if (sock->read_timeout > 0) {
-		int r = uv_timer_start(&sock->timer, readtimeout_cb,
-				       sock->read_timeout, 0);
-		REQUIRE(r == 0);
-	}
-}
-
-static void
-stop_sock_timer(isc_nmsocket_t *sock) {
-	int r = uv_timer_stop(&sock->timer);
-	REQUIRE(r == 0);
-}
-
-static void
-start_reading(isc_nmsocket_t *sock) {
-	if (sock->reading) {
-		return;
-	}
-
-	int r = uv_udp_recv_start(&sock->uv_handle.udp, udp_alloc_cb,
-				  udp_read_cb);
-	REQUIRE(r == 0);
-	sock->reading = true;
-
-	start_sock_timer(sock);
-}
-
-static void
-stop_reading(isc_nmsocket_t *sock) {
-	if (!sock->reading) {
-		return;
-	}
-
-	int r = uv_udp_recv_stop(&sock->uv_handle.udp);
-	REQUIRE(r == 0);
-	sock->reading = false;
-
-	stop_sock_timer(sock);
+	isc__nm_start_reading(sock);
+	isc__nmsocket_timer_start(sock);
 }
 
 void
@@ -1217,7 +1085,7 @@
 	 * interested in the callback.
 	 */
 	if (sock->statichandle) {
-		failed_read_cb(sock, ISC_R_CANCELED);
+		isc__nm_failed_read_cb(sock, ISC_R_CANCELED);
 		return;
 	}
 
@@ -1261,18 +1129,5 @@
 	REQUIRE(sock->tid == isc_nm_tid());
 	REQUIRE(atomic_load(&sock->client));
 
-	failed_read_cb(sock, ISC_R_EOF);
-}
-
-void
-isc__nm_udp_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
-	REQUIRE(VALID_NMHANDLE(handle));
-	REQUIRE(VALID_NMSOCK(handle->sock));
-
-	isc_nmsocket_t *sock = handle->sock;
-
-	sock->read_timeout = timeout;
-	if (uv_is_active((uv_handle_t *)&sock->timer)) {
-		start_sock_timer(sock);
-	}
+	isc__nm_failed_read_cb(sock, ISC_R_EOF);
 }
diff -Nru bind9-9.16.13/lib/isc/tests/Kyuafile bind9-9.16.15/lib/isc/tests/Kyuafile
--- bind9-9.16.13/lib/isc/tests/Kyuafile	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/tests/Kyuafile	2021-04-19 15:45:50.000000000 +0200
@@ -29,4 +29,3 @@
 tap_test_program{name='taskpool_test'}
 tap_test_program{name='time_test'}
 tap_test_program{name='timer_test'}
-tap_test_program{name='tlsdns_test'}
diff -Nru bind9-9.16.13/lib/isc/tests/tcpdns_test.c bind9-9.16.15/lib/isc/tests/tcpdns_test.c
--- bind9-9.16.13/lib/isc/tests/tcpdns_test.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/tests/tcpdns_test.c	2021-04-19 15:45:50.000000000 +0200
@@ -242,7 +242,6 @@
 	for (size_t i = 0; i < MAX_NM; i++) {
 		nm[i] = isc_nm_start(test_mctx, nworkers);
 		assert_non_null(nm[i]);
-		isc_nm_settimeouts(nm[i], 1000, 1000, 1000, 1000);
 	}
 
 	*state = nm;
diff -Nru bind9-9.16.13/lib/isc/tests/time_test.c bind9-9.16.15/lib/isc/tests/time_test.c
--- bind9-9.16.13/lib/isc/tests/time_test.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/tests/time_test.c	2021-04-19 15:45:50.000000000 +0200
@@ -128,7 +128,7 @@
 	UNUSED(state);
 
 	setenv("TZ", "America/Los_Angeles", 1);
-	result = isc_time_now(&t);
+	result = isc_time_now_hires(&t);
 	assert_int_equal(result, ISC_R_SUCCESS);
 
 	/* check formatting: yyyy-mm-ddThh:mm:ss.ssssssZ */
@@ -236,7 +236,7 @@
 	UNUSED(state);
 
 	setenv("TZ", "America/Los_Angeles", 1);
-	result = isc_time_now(&t);
+	result = isc_time_now_hires(&t);
 	assert_int_equal(result, ISC_R_SUCCESS);
 
 	/* check formatting: yyyy-mm-ddThh:mm:ss.ssssss */
diff -Nru bind9-9.16.13/lib/isc/tests/tlsdns_test.c bind9-9.16.15/lib/isc/tests/tlsdns_test.c
--- bind9-9.16.13/lib/isc/tests/tlsdns_test.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/tests/tlsdns_test.c	2021-04-19 15:45:50.000000000 +0200
@@ -249,7 +249,6 @@
 	for (size_t i = 0; i < MAX_NM; i++) {
 		nm[i] = isc_nm_start(test_mctx, nworkers);
 		assert_non_null(nm[i]);
-		isc_nm_settimeouts(nm[i], 1000, 1000, 1000, 1000);
 	}
 
 	*state = nm;
diff -Nru bind9-9.16.13/lib/isc/tls.c bind9-9.16.15/lib/isc/tls.c
--- bind9-9.16.13/lib/isc/tls.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/tls.c	2021-04-19 15:45:50.000000000 +0200
@@ -9,10 +9,12 @@
  * information regarding copyright ownership.
  */
 
+#include <openssl/bn.h>
 #include <openssl/conf.h>
 #include <openssl/err.h>
 #include <openssl/opensslv.h>
 #include <openssl/rand.h>
+#include <openssl/rsa.h>
 
 #include <isc/atomic.h>
 #include <isc/log.h>
@@ -276,11 +278,19 @@
 		rsa = NULL;
 		ASN1_INTEGER_set(X509_get_serialNumber(cert), 1);
 
+#if OPENSSL_VERSION_NUMBER < 0x10101000L
 		X509_gmtime_adj(X509_get_notBefore(cert), 0);
+#else
+		X509_gmtime_adj(X509_getm_notBefore(cert), 0);
+#endif
 		/*
 		 * We set the vailidy for 10 years.
 		 */
+#if OPENSSL_VERSION_NUMBER < 0x10101000L
 		X509_gmtime_adj(X509_get_notAfter(cert), 3650 * 24 * 3600);
+#else
+		X509_gmtime_adj(X509_getm_notAfter(cert), 3650 * 24 * 3600);
+#endif
 
 		X509_set_pubkey(cert, pkey);
 
diff -Nru bind9-9.16.13/lib/isc/unix/include/isc/time.h bind9-9.16.15/lib/isc/unix/include/isc/time.h
--- bind9-9.16.13/lib/isc/unix/include/isc/time.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/unix/include/isc/time.h	2021-04-19 15:45:50.000000000 +0200
@@ -151,6 +151,26 @@
  */
 
 isc_result_t
+isc_time_now_hires(isc_time_t *t);
+/*%<
+ * Set 't' to the current absolute time. Uses higher resolution clocks
+ * recommended when microsecond accuracy is required.
+ *
+ * Requires:
+ *
+ *\li	't' is a valid pointer.
+ *
+ * Returns:
+ *
+ *\li	Success
+ *\li	Unexpected error
+ *		Getting the time from the system failed.
+ *\li	Out of range
+ *		The time from the system is too large to be represented
+ *		in the current definition of isc_time_t.
+ */
+
+isc_result_t
 isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i);
 /*%<
  * Set *t to the current absolute time + i.
diff -Nru bind9-9.16.13/lib/isc/unix/time.c bind9-9.16.15/lib/isc/unix/time.c
--- bind9-9.16.13/lib/isc/unix/time.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/unix/time.c	2021-04-19 15:45:50.000000000 +0200
@@ -33,6 +33,10 @@
 #define NS_PER_US 1000	     /*%< Nanoseconds per microsecond. */
 #define NS_PER_MS 1000000    /*%< Nanoseconds per millisecond. */
 
+#if defined(CLOCK_REALTIME)
+#define CLOCKSOURCE_HIRES CLOCK_REALTIME
+#endif /* #if defined(CLOCK_REALTIME) */
+
 #if defined(CLOCK_REALTIME_COARSE)
 #define CLOCKSOURCE CLOCK_REALTIME_COARSE
 #elif defined(CLOCK_REALTIME_FAST)
@@ -41,6 +45,10 @@
 #define CLOCKSOURCE CLOCK_REALTIME
 #endif /* if defined(CLOCK_REALTIME_COARSE) */
 
+#if !defined(CLOCKSOURCE_HIRES)
+#define CLOCKSOURCE_HIRES CLOCKSOURCE
+#endif /* #ifndef CLOCKSOURCE_HIRES */
+
 /*%
  *** Intervals
  ***/
@@ -106,14 +114,14 @@
 	return (false);
 }
 
-isc_result_t
-isc_time_now(isc_time_t *t) {
+static inline isc_result_t
+time_now(isc_time_t *t, clockid_t clock) {
 	struct timespec ts;
 	char strbuf[ISC_STRERRORSIZE];
 
 	REQUIRE(t != NULL);
 
-	if (clock_gettime(CLOCKSOURCE, &ts) == -1) {
+	if (clock_gettime(clock, &ts) == -1) {
 		strerror_r(errno, strbuf, sizeof(strbuf));
 		UNEXPECTED_ERROR(__FILE__, __LINE__, "%s", strbuf);
 		return (ISC_R_UNEXPECTED);
@@ -139,6 +147,16 @@
 }
 
 isc_result_t
+isc_time_now_hires(isc_time_t *t) {
+	return time_now(t, CLOCKSOURCE_HIRES);
+}
+
+isc_result_t
+isc_time_now(isc_time_t *t) {
+	return time_now(t, CLOCKSOURCE);
+}
+
+isc_result_t
 isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i) {
 	struct timespec ts;
 	char strbuf[ISC_STRERRORSIZE];
diff -Nru bind9-9.16.13/lib/isc/win32/include/isc/time.h bind9-9.16.15/lib/isc/win32/include/isc/time.h
--- bind9-9.16.13/lib/isc/win32/include/isc/time.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/win32/include/isc/time.h	2021-04-19 15:45:50.000000000 +0200
@@ -165,6 +165,26 @@
  */
 
 isc_result_t
+isc_time_now_hires(isc_time_t *t);
+/*%<
+ * Set 't' to the current absolute time. Uses higher resolution clocks
+ * recommended when microsecond accuracy is required.
+ *
+ * Requires:
+ *
+ *\li	't' is a valid pointer.
+ *
+ * Returns:
+ *
+ *\li	Success
+ *\li	Unexpected error
+ *		Getting the time from the system failed.
+ *\li	Out of range
+ *		The time from the system is too large to be represented
+ *		in the current definition of isc_time_t.
+ */
+
+isc_result_t
 isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i);
 /*
  * Set *t to the current absolute time + i.
diff -Nru bind9-9.16.13/lib/isc/win32/libisc.def.in bind9-9.16.15/lib/isc/win32/libisc.def.in
--- bind9-9.16.13/lib/isc/win32/libisc.def.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/win32/libisc.def.in	2021-04-19 15:45:50.000000000 +0200
@@ -450,6 +450,7 @@
 isc_netscope_pton
 isc__nmhandle_attach
 isc__nmhandle_detach
+isc_nmhandle_cleartimeout
 isc_nmhandle_getdata
 isc_nmhandle_getextra
 isc_nmhandle_is_stream
@@ -696,6 +697,7 @@
 isc_time_microdiff
 isc_time_nanoseconds
 isc_time_now
+isc_time_now_hires
 isc_time_nowplusinterval
 isc_time_parsehttptimestamp
 isc_time_secondsastimet
diff -Nru bind9-9.16.13/lib/isc/win32/time.c bind9-9.16.15/lib/isc/win32/time.c
--- bind9-9.16.13/lib/isc/win32/time.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/isc/win32/time.c	2021-04-19 15:45:50.000000000 +0200
@@ -125,6 +125,15 @@
 }
 
 isc_result_t
+isc_time_now_hires(isc_time_t *t) {
+	REQUIRE(t != NULL);
+
+	GetSystemTimePreciseAsFileTime(&t->absolute);
+
+	return (ISC_R_SUCCESS);
+}
+
+isc_result_t
 isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i) {
 	ULARGE_INTEGER i1;
 
diff -Nru bind9-9.16.13/lib/ns/client.c bind9-9.16.15/lib/ns/client.c
--- bind9-9.16.13/lib/ns/client.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/ns/client.c	2021-04-19 15:45:50.000000000 +0200
@@ -1037,6 +1037,7 @@
 
 		isc_nm_gettimeouts(isc_nmhandle_netmgr(client->handle), NULL,
 				   NULL, NULL, &adv);
+		adv /= 100; /* units of 100 milliseconds */
 		isc_buffer_init(&buf, advtimo, sizeof(advtimo));
 		isc_buffer_putuint16(&buf, (uint16_t)adv);
 		ednsopts[count].code = DNS_OPT_TCP_KEEPALIVE;
@@ -2508,7 +2509,7 @@
 	isc_mutex_destroy(&manager->reclock);
 	isc_mutex_destroy(&manager->lock);
 
-	isc_mem_put(manager->mctx, manager, sizeof(*manager));
+	isc_mem_put(mctx, manager, sizeof(*manager));
 
 	return (result);
 }
diff -Nru bind9-9.16.13/lib/ns/include/ns/client.h bind9-9.16.15/lib/ns/include/ns/client.h
--- bind9-9.16.13/lib/ns/include/ns/client.h	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/ns/include/ns/client.h	2021-04-19 15:45:50.000000000 +0200
@@ -178,6 +178,7 @@
 	ns_clientmgr_t * manager;
 	ns_clientstate_t state;
 	int		 nupdates;
+	bool		 nodetach;
 	bool		 shuttingdown;
 	unsigned int	 attributes;
 	isc_task_t *	 task;
diff -Nru bind9-9.16.13/lib/ns/interfacemgr.c bind9-9.16.15/lib/ns/interfacemgr.c
--- bind9-9.16.13/lib/ns/interfacemgr.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/ns/interfacemgr.c	2021-04-19 15:45:50.000000000 +0200
@@ -550,6 +550,7 @@
 	LOCK(&ifp->mgr->lock);
 	ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link);
 	UNLOCK(&ifp->mgr->lock);
+	ns_interface_shutdown(ifp);
 	ns_interface_detach(&ifp);
 	return (result);
 }
diff -Nru bind9-9.16.13/lib/ns/Makefile.in bind9-9.16.15/lib/ns/Makefile.in
--- bind9-9.16.13/lib/ns/Makefile.in	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/ns/Makefile.in	2021-04-19 15:45:50.000000000 +0200
@@ -16,8 +16,6 @@
 
 @BIND9_MAKE_INCLUDES@
 
-USE_ISC_SPNEGO = @USE_ISC_SPNEGO@
-
 CINCLUDES =	-I. -I${top_srcdir}/lib/ns -Iinclude \
 		${NS_INCLUDES} ${DNS_INCLUDES} ${ISC_INCLUDES} \
 		${OPENSSL_CFLAGS} @DST_GSSAPI_INC@ \
diff -Nru bind9-9.16.13/lib/ns/query.c bind9-9.16.15/lib/ns/query.c
--- bind9-9.16.13/lib/ns/query.c	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/lib/ns/query.c	2021-04-19 15:45:50.000000000 +0200
@@ -135,8 +135,11 @@
 /*% Was the query already answered due to stale-answer-client-timeout? */
 #define QUERY_ANSWERED(q) (((q)->attributes & NS_QUERYATTR_ANSWERED) != 0)
 
-/*% Does the query only wants to check for stale RRset? */
-#define QUERY_STALEONLY(q) (((q)->dboptions & DNS_DBFIND_STALEONLY) != 0)
+/*% Does the query allow stale data in the response? */
+#define QUERY_STALEOK(q) (((q)->attributes & NS_QUERYATTR_STALEOK) != 0)
+
+/*% Does the query wants to check for stale RRset due to a timeout? */
+#define QUERY_STALETIMEOUT(q) (((q)->dboptions & DNS_DBFIND_STALETIMEOUT) != 0)
 
 /*% Does the rdataset 'r' have an attached 'No QNAME Proof'? */
 #define NOQNAME(r) (((r)->attributes & DNS_RDATASETATTR_NOQNAME) != 0)
@@ -487,6 +490,9 @@
 static void
 query_addauth(query_ctx_t *qctx);
 
+static void
+query_clear_stale(ns_client_t *client);
+
 /*
  * Increment query statistics counters.
  */
@@ -560,7 +566,7 @@
 	inc_stats(client, counter);
 	ns_client_send(client);
 
-	if (!QUERY_STALEONLY(&client->query)) {
+	if (!client->nodetach) {
 		isc_nmhandle_detach(&client->reqhandle);
 	}
 }
@@ -590,7 +596,7 @@
 
 	ns_client_error(client, result);
 
-	if (!QUERY_STALEONLY(&client->query)) {
+	if (!client->nodetach) {
 		isc_nmhandle_detach(&client->reqhandle);
 	}
 }
@@ -606,7 +612,7 @@
 	}
 	ns_client_drop(client, result);
 
-	if (!QUERY_STALEONLY(&client->query)) {
+	if (!client->nodetach) {
 		isc_nmhandle_detach(&client->reqhandle);
 	}
 }
@@ -2177,6 +2183,10 @@
 		if ((rdataset->attributes & DNS_RDATASETATTR_REQUIRED) != 0) {
 			mrdataset->attributes |= DNS_RDATASETATTR_REQUIRED;
 		}
+		if ((rdataset->attributes & DNS_RDATASETATTR_STALE_ADDED) != 0)
+		{
+			mrdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED;
+		}
 		return;
 	} else if (result == DNS_R_NXDOMAIN) {
 		/*
@@ -3938,8 +3948,13 @@
 	rpzs = client->view->rpzs;
 	st = client->query.rpz_st;
 
-	if (rpzs == NULL ||
-	    (st != NULL && (st->state & DNS_RPZ_REWRITTEN) != 0)) {
+	if (rpzs == NULL) {
+		return (ISC_R_NOTFOUND);
+	}
+	if (st != NULL && (st->state & DNS_RPZ_REWRITTEN) != 0) {
+		return (DNS_R_DISALLOWED);
+	}
+	if (RECURSING(client)) {
 		return (DNS_R_DISALLOWED);
 	}
 
@@ -5177,7 +5192,7 @@
 		dns_db_detach(&qctx->zdb);
 	}
 
-	if (qctx->event != NULL && !QUERY_STALEONLY(&qctx->client->query)) {
+	if (qctx->event != NULL && !qctx->client->nodetach) {
 		free_devent(qctx->client, ISC_EVENT_PTR(&qctx->event),
 			    &qctx->event);
 	}
@@ -5639,7 +5654,7 @@
 	REQUIRE(orig_qctx->client != NULL);
 
 	qctx_copy(orig_qctx, &qctx);
-	qctx.client->query.dboptions &= ~(DNS_DBFIND_STALEONLY |
+	qctx.client->query.dboptions &= ~(DNS_DBFIND_STALETIMEOUT |
 					  DNS_DBFIND_STALEOK |
 					  DNS_DBFIND_STALEENABLED);
 
@@ -5679,10 +5694,11 @@
 	dns_clientinfomethods_t cm;
 	dns_clientinfo_t ci;
 	dns_name_t *rpzqname = NULL;
+	char namebuf[DNS_NAME_FORMATSIZE];
 	unsigned int dboptions;
 	dns_ttl_t stale_refresh = 0;
 	bool dbfind_stale = false;
-	bool stale_only = false;
+	bool stale_timeout = false;
 	bool stale_found = false;
 	bool refresh_rrset = false;
 	bool stale_refresh_window = false;
@@ -5719,7 +5735,7 @@
 		 * to refresh the RRset will still take place if an
 		 * active RRset is not available.
 		 */
-		qctx->client->query.dboptions |= DNS_DBFIND_STALEONLY;
+		qctx->client->query.dboptions |= DNS_DBFIND_STALETIMEOUT;
 	}
 
 	dboptions = qctx->client->query.dboptions;
@@ -5773,29 +5789,22 @@
 				(dboptions & DNS_DBFIND_STALEENABLED) != 0);
 
 	/*
-	 * If DNS_DBFIND_STALEONLY is set, a stale positive answer is requested.
+	 * If DNS_DBFIND_STALETIMEOUT is set, a stale answer is requested.
 	 * This can happen if 'stale-answer-client-timeout' is enabled.
 	 *
-	 * If 'stale-answer-client-timeout' is set to 0, and a stale positive
+	 * If 'stale-answer-client-timeout' is set to 0, and a stale
 	 * answer is found, send it to the client, and try to refresh the
-	 * RRset. If a stale negative answer is found, continue with recursion
-	 * (perhaps the query will be resolved eventually and the answer from
-	 * the authoritative is returned to the client, or the query will
-	 * timeout, in that case DNS_DBFIND_STALEOK may be set, and a stale
-	 * negative answer is returned (or SERVFAIL).
+	 * RRset.
 	 *
-	 * If 'stale-answer-client-timeout' is non-zero, and a stale positive
+	 * If 'stale-answer-client-timeout' is non-zero, and a stale
 	 * answer is found, send it to the client. Don't try to refresh the
-	 * RRset because a fetch is already in progress. If a stale negative
-	 * answer is found, then abort the lookup and the client has to wait
-	 * until recursion is finished.
+	 * RRset because a fetch is already in progress.
 	 */
-	stale_only = ((dboptions & DNS_DBFIND_STALEONLY) != 0);
+	stale_timeout = ((dboptions & DNS_DBFIND_STALETIMEOUT) != 0);
 
-	if (dbfind_stale ||
-	    (STALE(qctx->rdataset) && (stale_only || stale_refresh_window)))
-	{
-		char namebuf[DNS_NAME_FORMATSIZE];
+	if (dbfind_stale || stale_refresh_window || stale_timeout) {
+		dns_name_format(qctx->client->query.qname, namebuf,
+				sizeof(namebuf));
 
 		inc_stats(qctx->client, ns_statscounter_trystale);
 
@@ -5808,128 +5817,102 @@
 		} else {
 			stale_found = false;
 		}
+	}
 
-		dns_name_format(qctx->client->query.qname, namebuf,
-				sizeof(namebuf));
-
-		if (dbfind_stale) {
-			isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
-				      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
-				      "%s resolver failure, stale answer %s",
-				      namebuf,
-				      stale_found ? "used" : "unavailable");
-			if (!stale_found) {
-				/*
-				 * Resolver failure, no stale data, nothing
-				 * more we can do, return SERVFAIL.
-				 */
-				QUERY_ERROR(qctx, DNS_R_SERVFAIL);
-				return (ns_query_done(qctx));
-			}
+	if (dbfind_stale) {
+		isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
+			      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+			      "%s resolver failure, stale answer %s", namebuf,
+			      stale_found ? "used" : "unavailable");
+		if (!stale_found) {
+			/*
+			 * Resolver failure, no stale data, nothing more we
+			 * can do, return SERVFAIL.
+			 */
+			QUERY_ERROR(qctx, DNS_R_SERVFAIL);
+			return (ns_query_done(qctx));
+		}
+	} else if (stale_refresh_window) {
+		/*
+		 * A recent lookup failed, so during this time window we are
+		 * allowed to return stale data immediately.
+		 */
+		isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
+			      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+			      "%s query within stale refresh time, stale "
+			      "answer %s",
+			      namebuf, stale_found ? "used" : "unavailable");
 
-			stale_only = false;
-		} else if (stale_refresh_window) {
+		if (!stale_found) {
 			/*
-			 * A recent lookup failed, so during this time window
-			 * we are allowed to return stale data immediately.
+			 * During the stale refresh window explicitly do not try
+			 * to refresh the data, because a recent lookup failed.
 			 */
-			isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
-				      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
-				      "%s query within stale refresh time, "
-				      "stale answer %s",
-				      namebuf,
-				      stale_found ? "used" : "unavailable");
+			QUERY_ERROR(qctx, DNS_R_SERVFAIL);
+			return (ns_query_done(qctx));
+		}
+	} else if (stale_timeout) {
+		qctx->client->query.attributes |= NS_QUERYATTR_STALEOK;
+		qctx->rdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED;
 
+		if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) {
 			if (!stale_found) {
 				/*
-				 * During the stale refresh window explicitly
-				 * do not try to refresh the data, because a
-				 * recent lookup failed.
+				 * We have nothing useful in cache to return
+				 * immediately.
 				 */
-				QUERY_ERROR(qctx, DNS_R_SERVFAIL);
-				return (ns_query_done(qctx));
-			}
-
-			stale_only = false;
-		} else if ((dboptions & DNS_DBFIND_STALEONLY) != 0) {
-			if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) {
-				if (stale_found && result == ISC_R_SUCCESS) {
-					/*
-					 * Immediately return the stale answer,
-					 * start a resolver fetch to refresh
-					 * the data in cache.
-					 */
-					isc_log_write(
-						ns_lctx,
-						NS_LOGCATEGORY_SERVE_STALE,
-						NS_LOGMODULE_QUERY,
-						ISC_LOG_INFO,
-						"%s stale answer used, an "
-						"attempt to refresh the RRset "
-						"will still be made",
-						namebuf);
-					refresh_rrset = true;
-				} else {
-					/*
-					 * We have nothing useful in cache to
-					 * return immediately.
-					 */
-					qctx_clean(qctx);
-					qctx_freedata(qctx);
-					dns_db_attach(
-						qctx->client->view->cachedb,
-						&qctx->db);
-					qctx->client->query.dboptions &=
-						~DNS_DBFIND_STALEONLY;
-					qctx->options &= ~DNS_GETDB_STALEFIRST;
-					if (qctx->client->query.fetch != NULL) {
-						dns_resolver_destroyfetch(
-							&qctx->client->query
-								 .fetch);
-					}
-					return query_lookup(qctx);
+				qctx_clean(qctx);
+				qctx_freedata(qctx);
+				dns_db_attach(qctx->client->view->cachedb,
+					      &qctx->db);
+				qctx->client->query.dboptions &=
+					~DNS_DBFIND_STALETIMEOUT;
+				qctx->options &= ~DNS_GETDB_STALEFIRST;
+				if (qctx->client->query.fetch != NULL) {
+					dns_resolver_destroyfetch(
+						&qctx->client->query.fetch);
 				}
+				return query_lookup(qctx);
 			} else {
 				/*
-				 * The 'stale-answer-client-timeout' triggered,
-				 * return the stale answer if available,
-				 * otherwise wait until the resolver finishes.
+				 * Immediately return the stale answer, start a
+				 * resolver fetch to refresh the data in cache.
 				 */
 				isc_log_write(
 					ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
 					NS_LOGMODULE_QUERY, ISC_LOG_INFO,
-					"%s client timeout, stale answer %s",
-					namebuf,
-					stale_found ? "used" : "unavailable");
-				if (!stale_found || result != ISC_R_SUCCESS) {
-					return (result);
-				}
+					"%s stale answer used, an attempt to "
+					"refresh the RRset will still be made",
+					namebuf);
+				refresh_rrset = STALE(qctx->rdataset);
+				qctx->client->nodetach = refresh_rrset;
+			}
+		} else {
+			/*
+			 * The 'stale-answer-client-timeout' triggered, return
+			 * the stale answer if available, otherwise wait until
+			 * the resolver finishes.
+			 */
+			isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
+				      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+				      "%s client timeout, stale answer %s",
+				      namebuf,
+				      stale_found ? "used" : "unavailable");
+			if (!stale_found) {
+				return (result);
 			}
 		}
-	} else if (stale_only && result != ISC_R_SUCCESS) {
-		/*
-		 * This is a staleonly lookup and no stale answer was found
-		 * in cache. Treat as we don't have an answer and wait for
-		 * the resolver fetch to finish.
-		 */
-		if ((qctx->options & DNS_GETDB_STALEFIRST) == 0) {
-			return (result);
-		}
-	} else {
-		stale_only = false;
 	}
 
-	if (!stale_only) {
+	if (stale_timeout && stale_found) {
 		/*
-		 * The DNS_DBFIND_STALEONLY option may be set, but if we
-		 * didn't have stale data in cache, or we responded with
-		 * a stale answer because of 'stale-refresh-time', this is
-		 * actually not a 'stale-only' lookup. Clear the flag to
-		 * allow the client to be detach the handle.
+		 * Mark RRsets that we are adding to the client message on a
+		 * lookup during 'stale-answer-client-timeout', so we can
+		 * clean it up if needed when we resume from recursion.
 		 */
-		qctx->client->query.dboptions &= ~DNS_DBFIND_STALEONLY;
+		qctx->client->query.attributes |= NS_QUERYATTR_STALEOK;
+		qctx->rdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED;
 	}
-
 	result = query_gotanswer(qctx, result);
 
 	if (refresh_rrset) {
@@ -5948,20 +5931,74 @@
 }
 
 /*
- * Create a new query context with the sole intent
- * of looking up for a stale RRset in cache.
- * If an entry is found, we mark the original query as
- * answered, in order to avoid answering the query twice,
- * when the original fetch finishes.
+ * Clear all rdatasets from the message that are in the given section and
+ * that have the 'attr' attribute set.
+ */
+static void
+message_clearrdataset(dns_message_t *msg, unsigned int attr) {
+	unsigned int i;
+	dns_name_t *name, *next_name;
+	dns_rdataset_t *rds, *next_rds;
+
+	/*
+	 * Clean up name lists by calling the rdataset disassociate function.
+	 */
+	for (i = DNS_SECTION_ANSWER; i < DNS_SECTION_MAX; i++) {
+		name = ISC_LIST_HEAD(msg->sections[i]);
+		while (name != NULL) {
+			next_name = ISC_LIST_NEXT(name, link);
+
+			rds = ISC_LIST_HEAD(name->list);
+			while (rds != NULL) {
+				next_rds = ISC_LIST_NEXT(rds, link);
+				if ((rds->attributes & attr) != attr) {
+					rds = next_rds;
+					continue;
+				}
+				ISC_LIST_UNLINK(name->list, rds, link);
+				INSIST(dns_rdataset_isassociated(rds));
+				dns_rdataset_disassociate(rds);
+				isc_mempool_put(msg->rdspool, rds);
+				rds = next_rds;
+			}
+
+			if (ISC_LIST_EMPTY(name->list)) {
+				ISC_LIST_UNLINK(msg->sections[i], name, link);
+				if (dns_name_dynamic(name)) {
+					dns_name_free(name, msg->mctx);
+				}
+				isc_mempool_put(msg->namepool, name);
+			}
+
+			name = next_name;
+		}
+	}
+}
+
+/*
+ * Clear any rdatasets from the client's message that were added on a lookup
+ * due to a client timeout.
+ */
+static void
+query_clear_stale(ns_client_t *client) {
+	message_clearrdataset(client->message, DNS_RDATASETATTR_STALE_ADDED);
+}
+
+/*
+ * Create a new query context with the sole intent of looking up for a stale
+ * RRset in cache. If an entry is found, we mark the original query as
+ * answered, in order to avoid answering the query twice, when the original
+ * fetch finishes.
  */
 static inline void
-query_lookup_staleonly(ns_client_t *client) {
+query_lookup_stale(ns_client_t *client) {
 	query_ctx_t qctx;
 
 	qctx_init(client, NULL, client->query.qtype, &qctx);
 	dns_db_attach(client->view->cachedb, &qctx.db);
 	client->query.attributes &= ~NS_QUERYATTR_RECURSIONOK;
-	client->query.dboptions |= DNS_DBFIND_STALEONLY;
+	client->query.dboptions |= DNS_DBFIND_STALETIMEOUT;
+	client->nodetach = true;
 	(void)query_lookup(&qctx);
 	if (qctx.node != NULL) {
 		dns_db_detachnode(qctx.db, &qctx.node);
@@ -6000,13 +6037,21 @@
 	CTRACE(ISC_LOG_DEBUG(3), "fetch_callback");
 
 	if (event->ev_type == DNS_EVENT_TRYSTALE) {
-		query_lookup_staleonly(client);
+		query_lookup_stale(client);
 		isc_event_free(ISC_EVENT_PTR(&event));
 		return;
-	} else {
-		client->query.dboptions &= ~DNS_DBFIND_STALEONLY;
 	}
 
+	/*
+	 * We are resuming from recursion. Reset any attributes, options
+	 * that a lookup due to stale-answer-client-timeout may have set.
+	 */
+	if (client->view->cachedb != NULL && client->view->recursion) {
+		client->query.attributes |= NS_QUERYATTR_RECURSIONOK;
+	}
+	client->query.dboptions &= ~DNS_DBFIND_STALETIMEOUT;
+	client->nodetach = false;
+
 	LOCK(&client->query.fetchlock);
 	if (client->query.fetch != NULL) {
 		/*
@@ -6797,6 +6842,7 @@
 	switch (rresult) {
 	case ISC_R_SUCCESS:
 		break;
+	case ISC_R_NOTFOUND:
 	case DNS_R_DISALLOWED:
 		return (result);
 	case DNS_R_DELEGATION:
@@ -6804,6 +6850,7 @@
 		 * recursing for NS names or addresses,
 		 * so save the main query state
 		 */
+		INSIST(!RECURSING(qctx->client));
 		qctx->rpz_st->q.qtype = qctx->qtype;
 		qctx->rpz_st->q.is_zone = qctx->is_zone;
 		qctx->rpz_st->q.authoritative = qctx->authoritative;
@@ -7205,15 +7252,30 @@
 		return (ns_query_done(qctx));
 	}
 
-	if (!RECURSING(qctx->client) &&
-	    !dns_name_equal(qctx->client->query.qname, dns_rootname))
-	{
+	if (!dns_name_equal(qctx->client->query.qname, dns_rootname)) {
 		result = query_checkrpz(qctx, result);
+		if (result == ISC_R_NOTFOUND) {
+			/*
+			 * RPZ not configured for this view.
+			 */
+			goto root_key_sentinel;
+		}
+		if (RECURSING(qctx->client) && result == DNS_R_DISALLOWED) {
+			/*
+			 * We are recursing, and thus RPZ processing is not
+			 * allowed at the moment. This could happen on a
+			 * "stale-answer-client-timeout" lookup. In this case,
+			 * bail out and wait for recursion to complete, as we
+			 * we can't perform the RPZ rewrite rules.
+			 */
+			return (result);
+		}
 		if (result == ISC_R_COMPLETE) {
 			return (ns_query_done(qctx));
 		}
 	}
 
+root_key_sentinel:
 	/*
 	 * If required, handle special "root-key-sentinel-is-ta-<keyid>" and
 	 * "root-key-sentinel-not-ta-<keyid>" labels by returning SERVFAIL.
@@ -7668,6 +7730,21 @@
 
 	CALL_HOOK(NS_QUERY_ADDANSWER_BEGIN, qctx);
 
+	/*
+	 * On normal lookups, clear any rdatasets that were added on a
+	 * lookup due to stale-answer-client-timeout.
+	 */
+	if (QUERY_STALEOK(&qctx->client->query) &&
+	    !QUERY_STALETIMEOUT(&qctx->client->query))
+	{
+		query_clear_stale(qctx->client);
+		/*
+		 * We can clear the attribute to prevent redundant clearing
+		 * in subsequent lookups.
+		 */
+		qctx->client->query.attributes &= ~DNS_RDATASETATTR_STALE_ADDED;
+	}
+
 	if (qctx->dns64) {
 		result = query_dns64(qctx);
 		qctx->noqname = NULL;
@@ -7702,7 +7779,7 @@
 		ns_client_putrdataset(qctx->client, &qctx->rdataset);
 	} else {
 		if (!qctx->is_zone && RECURSIONOK(qctx->client) &&
-		    !QUERY_STALEONLY(&qctx->client->query))
+		    !QUERY_STALETIMEOUT(&qctx->client->query))
 		{
 			query_prefetch(qctx->client, qctx->fname,
 				       qctx->rdataset);
@@ -7808,10 +7885,16 @@
 	query_addnoqnameproof(qctx);
 
 	/*
-	 * We shouldn't ever fail to add 'rdataset'
-	 * because it's already in the answer.
+	 * 'qctx->rdataset' will only be non-NULL here if the ANSWER section of
+	 * the message to be sent to the client already contains an RRset with
+	 * the same owner name and the same type as 'qctx->rdataset'.  This
+	 * should never happen, with one exception: when chasing DNAME records,
+	 * one of the DNAME records placed in the ANSWER section may turn out
+	 * to be the final answer to the client's query, but we have no way of
+	 * knowing that until now.  In such a case, 'qctx->rdataset' will be
+	 * freed later, so we do not need to free it here.
 	 */
-	INSIST(qctx->rdataset == NULL || QUERY_ANSWERED(&qctx->client->query));
+	INSIST(qctx->rdataset == NULL || qctx->qtype == dns_rdatatype_dname);
 
 	query_addauth(qctx);
 
@@ -11244,7 +11327,7 @@
 ns_query_done(query_ctx_t *qctx) {
 	isc_result_t result;
 	const dns_namelist_t *secs = qctx->client->message->sections;
-	bool query_stale_only;
+	bool nodetach;
 
 	CCTRACE(ISC_LOG_DEBUG(3), "ns_query_done");
 
@@ -11314,7 +11397,7 @@
 	 * resume when recursion ends.
 	 */
 	if (RECURSING(qctx->client) &&
-	    (!QUERY_STALEONLY(&qctx->client->query) ||
+	    (!QUERY_STALETIMEOUT(&qctx->client->query) ||
 	     ((qctx->options & DNS_GETDB_STALEFIRST) != 0)))
 	{
 		return (qctx->result);
@@ -11327,10 +11410,8 @@
 	 * to the AA bit if the auth-nxdomain config option
 	 * says so, then render and send the response.
 	 */
-	if (!QUERY_ANSWERED(qctx->client)) {
-		query_setup_sortlist(qctx);
-		query_glueanswer(qctx);
-	}
+	query_setup_sortlist(qctx);
+	query_glueanswer(qctx);
 
 	if (qctx->client->message->rcode == dns_rcode_nxdomain &&
 	    qctx->view->auth_nxdomain)
@@ -11356,10 +11437,9 @@
 	 * Client may have been detached after query_send(), so
 	 * we test and store the flag state here, for safety.
 	 */
-	query_stale_only = QUERY_STALEONLY(&qctx->client->query);
+	nodetach = qctx->client->nodetach;
 	query_send(qctx->client);
-
-	if (!query_stale_only) {
+	if (!nodetach) {
 		qctx->detach_client = true;
 	}
 	return (qctx->result);
diff -Nru bind9-9.16.13/.pylintrc bind9-9.16.15/.pylintrc
--- bind9-9.16.13/.pylintrc	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/.pylintrc	2021-04-19 15:45:50.000000000 +0200
@@ -5,3 +5,4 @@
     C0116, # missing-function-docstring
     R0801, # duplicate-code
     C0103, # invalid-name
+    C0415,# import-outside-toplevel
diff -Nru bind9-9.16.13/srcid bind9-9.16.15/srcid
--- bind9-9.16.13/srcid	2021-03-11 17:16:18.000000000 +0100
+++ bind9-9.16.15/srcid	2021-04-19 16:15:31.000000000 +0200
@@ -1 +1 @@
-SRCID=072e758
+SRCID=4469e3e
diff -Nru bind9-9.16.13/version bind9-9.16.15/version
--- bind9-9.16.13/version	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/version	2021-04-19 15:45:50.000000000 +0200
@@ -5,7 +5,7 @@
 DESCRIPTION="(Stable Release)"
 MAJORVER=9
 MINORVER=16
-PATCHVER=13
+PATCHVER=15
 RELEASETYPE=
 RELEASEVER=
 EXTENSIONS=
diff -Nru bind9-9.16.13/win32utils/Configure bind9-9.16.15/win32utils/Configure
--- bind9-9.16.13/win32utils/Configure	2021-03-11 14:20:59.000000000 +0100
+++ bind9-9.16.15/win32utils/Configure	2021-04-19 15:45:50.000000000 +0200
@@ -315,8 +315,7 @@
 
 my @substdefd = ("PK11_LIB_LOCATION",
                  "USE_GSSAPI",
-                 "USE_PYTHON",
-                 "USE_ISC_SPNEGO");
+                 "USE_PYTHON");
 
 # conditions
 
@@ -345,7 +344,6 @@
 my @enablelist = ("developer",
                   "fixed-rrset",
                   "intrinsics",
-                  "isc-spnego",
                   "native-pkcs11",
                   "openssl-hash",
                   "querytrace");
@@ -399,7 +397,6 @@
 "  enable-intrinsics     enable intrinsic/atomic functions [default=yes]\n",
 "  enable-native-pkcs11  use native PKCS#11 for all crypto [default=no]\n",
 "  enable-openssl-hash   use OpenSSL for hash functions [default=yes]\n",
-"  enable-isc-spnego     use SPNEGO from lib/dns [default=yes]\n",
 "  enable-fixed-rrset    enable fixed rrset ordering [default=no]\n",
 "  enable-developer      enable developer build settings [default=no]\n",
 "  enable-querytrace     enable very verbose query trace [default=no]\n",
@@ -442,7 +439,6 @@
 my $cryptolib = "";
 my $enable_native_pkcs11 = "no";
 my $enable_crypto_rand = "yes";
-my $enable_isc_spnego = "yes";
 my $enable_fixed_rrset = "no";
 my $enable_developer = "no";
 my $enable_querytrace = "no";
@@ -653,10 +649,6 @@
         if ($val =~ /^yes$/i) {
             $enable_native_pkcs11 = "yes";
         }
-    } elsif ($key =~ /^isc-spnego$/i) {
-        if ($val =~ /^no$/i) {
-            $enable_isc_spnego = "no";
-        }
     } elsif ($key =~ /^fixed-rrset$/i) {
         if ($val =~ /^yes$/i) {
             $enable_fixed_rrset = "yes";
@@ -907,11 +899,6 @@
         print "native-pkcs11: disabled\n";
     }
     print "openssl-hash: enabled\n";
-    if ($enable_isc_spnego eq "yes") {
-        print "isc-spnego: enabled\n";
-    } else {
-        print "isc-spnego: disabled\n";
-    }
     if ($enable_fixed_rrset eq "yes") {
         print "fixed-rrset: enabled\n";
     } else {
@@ -1711,16 +1698,6 @@
     $configdll{"WSHELP_DLL"} = "$wshelp_dll";
 }
 
-# enable-isc-spnego
-if ($use_gssapi ne "yes") {
-    $enable_isc_spnego = "no";
-} elsif ($enable_isc_spnego eq "yes") {
-    if ($use_gssapi eq "no") {
-        die "No GSSAPI for SPNEGO\n";
-    }
-    $configdefd{"USE_ISC_SPNEGO"} = "USE_ISC_SPNEGO";
-}
-
 # disable-auto-validation
 $configdefh{"VALIDATION_DEFAULT"} = "\"$validation_default\"";
 
@@ -2796,7 +2773,6 @@
 #  --enable-symtable incompatible with DLLs (or libtool)
 #  --enable-ipv6 included without a way to disable it
 #  --enable-atomic supported (renamed to intrinsic)
-#  --enable-isc-spnego supported (part of GSSAPI)
 #  --enable-fixed-rrset supported
 #  --enable-querytrace supported
 #  --enable-full-report supported by verbose
