Index | Thread | Search

From:
Stuart Henderson <stu@spacehopper.org>
Subject:
nsd 4.15.2
To:
tech <tech@openbsd.org>
Date:
Sun, 20 Sep 2026 10:50:01 +0100

Download raw body.

Thread
  • Stuart Henderson:

    nsd 4.15.2

Index: doc/RELNOTES
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/doc/RELNOTES,v
diff -u -p -r1.21 RELNOTES
--- doc/RELNOTES	14 Jul 2026 16:20:05 -0000	1.21
+++ doc/RELNOTES	20 Sep 2026 09:47:57 -0000
@@ -1,5 +1,117 @@
 NSD RELEASE NOTES
 
+4.15.2
+================
+FEATURES:
+	- Merge #494: RFC 7830 EDNS0 padding option support with DoT
+	  With padding suggestions from RFC 8467 for responses
+	  Thanks kdrenard for the suggestion
+	- Merge #498: Support for the HHIT and BRID RR types
+	- Merge #499: Support for the "docpath" and "pvd"
+	  SVCB Service Parameter Keys
+	- Merge #500: Support for the "oots" SVCB Service Parameter Key
+	- An `allow-proxy` configuration file option to specify allow access
+	  control list for the PROXYv2 protocol port.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the suggestion.
+	- Merge #502: An `udp-padding-port` configuration file option to enable
+	  responding to the padding option on UDP, but on specific ports only.
+	  This is convenient for setups where NSD is behind a frontend handling
+	  the DNS over TLS.
+	- `--enable-multiple-catalog-zones` option to configure to enable
+	  experimental support for multiple catalog consumer zones.
+
+BUG FIXES:
+	- Merge #495 from ruuda: Include inttypes.h from metrics.c.
+	- Merge #496 from maertsen: Fix allow-query syntax in catalog
+	  documentation.
+	- Fix that IXFR queries with overly large SOA records do not cause an
+	  assertion failure in the server child process. The malformed SOA
+	  record is rejected with FORMERR.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix that notify ahead of the zone serial does not cause perpetual
+	  retransfer. After one round, and after another retry, it gives up,
+	  and forgets the notified serial number. This allows the primary a
+	  moment to retrieve the new zone version.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix that AXFR out for a zone with an oversize record does not loop.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix that IXFR out for a zone with an oversize record does not loop.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix serve childs crashing when queried for a very long CNAME chain
+	  on ASAN builds. The maximum CNAME chain in responses is set to 18.
+	  The value can be configured with the --with-max-cname-chain=number
+	  option to configure.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to have failed zone transfers retry after a time, instead of in a
+	  loop, for transfers with failed contents.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix that an increase verbosity level is needed to warn about unfound
+	  RRs to be deleted (from IXFR). Verbosity 2 for unfound RRsets and
+	  unfound RDATA. Verbosity 3 to log which RR in the existing set does
+	  not match.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to ignore NSEC3s with wrong non-base32hex chars in owner label.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix Y2038 bug, where the server process would fail with undefined
+	  behaviour, if it was built with address sanitizer undefined checks
+	  enabled, for the timestamp in EDNS cookies.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to not let `nsd-control zonestatus` crash the xfrd processes
+	  when configured with multiple catalog consumers.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to prevent a configured primary from hampering loading its own
+	  zone by providing a transfer with the OPT RR in the answer section
+	  and the UDP Payload size (i.e. class) of the OPT RR set to something
+	  else than 1 (IN).
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to ignore NSEC3 records with malformed owner name. In depth fix
+	  to clear key for NSEC3 tree collisions. And a fix to limit the
+	  reenactment of failed reloads, that ordinarily marks transfers as
+	  corrupt, so that for crashes it waits with that zone for retry. This
+	  allows other zones to update in the meantime.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix nsd-control reconfig so that change of a secondary zone while it
+	  is on the udp or tcp waiting list does not crash after that.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to reset the length of the incoming packet buffer after a failed
+	  PROXYv2 header on UDP. It can cause an assertion failure on the next
+	  packet.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix XoT transfer authentication to not fall back to the common name
+	  if a DNS name is present in the certificate. This fixes the RFC6125
+	  conformance.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix dnstap over TLS certificate check for reconnection. Also clean up
+	  file descriptor and SSL state on connection failures.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix client certificate checking by linking the tls-auth-name
+	  requirement on an access control list item, to the other requirements
+	  (IP address and/or TSIG key) on the same list item.
+	  Thanks to voguemerry for the report
+
+4.15.1
+================
+FEATURES:
+BUG FIXES:
+	- Fix for CVE-2026-18664: IP range access control restrictions are
+	  bypassed for some unintended IP.
+	  Thanks to Qifan Zhang, Palo Alto Networks for the report
+	  Thanks to Claude and Ada Logics for the report
+	  https://www.nlnetlabs.nl/downloads/nsd/CVE-2026-18664.txt
+	- Fix for CVE-2026-18916: Any remote client can denial TCP service by
+	  throttling the TCP receive window (down to 1).
+	  Thanks to Akhil Koul (https://github.com/akoul) for the report
+	  https://www.nlnetlabs.nl/downloads/nsd/CVE-2026-18916.txt
+	- Fix for CVE-2026-19401: Any remote client can denial UDP service by
+	  sending a specifically crafted query with multiple DNS Cookie options.
+	  Thanks to Qifan Zhang, Palo Alto Networks for the report
+	  Thanks to afldl <zhangph@yandex.com> for the report
+	  https://www.nlnetlabs.nl/downloads/nsd/CVE-2026-19401.txt
+	- Fix for CVE-2026-19538: Anyone with access to the proxy protocol port
+	  over TCP or TLS can bypass BLOCKED access control items.
+	  Thanks to Qifan Zhang, Palo Alto Networks for the report
+	  https://www.nlnetlabs.nl/downloads/nsd/CVE-2026-19538.txt
+
 4.15.0
 ================
 FEATURES:
Index: simdzone/CHANGELOG.md
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/simdzone/CHANGELOG.md,v
diff -u -p -r1.1.1.4 CHANGELOG.md
--- simdzone/CHANGELOG.md	14 Jul 2026 16:19:06 -0000	1.1.1.4
+++ simdzone/CHANGELOG.md	20 Sep 2026 09:47:57 -0000
@@ -5,6 +5,21 @@ All notable changes to simdzone will be 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.2.6] - 2026-09-02
+
+### Added
+
+- Support for the HHIT and BRID RR types.
+- Support for the "docpath", "pvd" and "oots" SVCB Service Parameters
+
+### Fixed
+
+- TXT records with more than 6465 rdata elements, crashes simdzone parsing.
+  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+- Fix unit test implicit conversion change of signedness warning.
+- Fix unit test delimiters_overflow_txt to add block size padding for simd
+  operations.
+
 ## [0.2.5] - 2026-07-07
 
 ### Added
Index: axfr.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/axfr.c,v
diff -u -p -r1.26 axfr.c
--- axfr.c	14 Jul 2026 16:20:05 -0000	1.26
+++ axfr.c	20 Sep 2026 09:47:57 -0000
@@ -140,8 +140,20 @@ query_axfr(struct nsd *nsd, struct query
 							}
 						}
 					}
-					if (!added)
+					if (!added) {
+						if(total_added == 0) {
+							/* RR wire encoding exceeds TCP_MAX_MESSAGE_LEN; cannot transmit. */
+							char apexstr[MAXDOMAINLEN * 5];
+							domain_to_string_buf(query->axfr_zone->apex, apexstr);
+							VERBOSITY(2, (LOG_ERR, "axfr: RR at %s in zone %s too large for any DNS message "
+								"(wire encoding exceeds %d bytes), aborting transfer",
+								domain_to_string(query->axfr_current_rrset->rrs[query->axfr_current_rr]->owner),
+								apexstr, TCP_MAX_MESSAGE_LEN));
+							RCODE_SET(query->packet, RCODE_SERVFAIL);
+							query->axfr_is_done = 1;
+						}
 						goto return_answer;
+					}
 					++total_added;
 					++query->axfr_current_rr;
 				}
Index: buffer.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/buffer.h,v
diff -u -p -r1.5 buffer.h
--- buffer.h	3 Sep 2025 18:46:48 -0000	1.5
+++ buffer.h	20 Sep 2026 09:47:57 -0000
@@ -260,6 +260,14 @@ buffer_write(buffer_type *buffer, const 
 	buffer->_position += count;
 }
 
+static inline void
+buffer_fill(buffer_type *buffer, const int c, size_t count)
+{
+	assert(buffer_available_at(buffer, buffer->_position, count));
+	memset(buffer->_data + buffer->_position, c, count);
+	buffer->_position += count;
+}
+
 static inline int
 try_buffer_write_at(buffer_type *buffer, size_t at, const void *data, size_t count)
 {
Index: config.h.in
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/config.h.in,v
diff -u -p -r1.48 config.h.in
--- config.h.in	26 Jun 2026 08:55:55 -0000	1.48
+++ config.h.in	20 Sep 2026 09:47:57 -0000
@@ -545,6 +545,9 @@
 /* Define to the maximum message length to pass to syslog. */
 #undef MAXSYSLOGMSGLEN
 
+/* Define to the default maximum number of CNAMEs to follow. */
+#undef MAX_CNAME_CHAIN
+
 /* Define this to cleanup memory at exit (eg. for valgrind, etc.) */
 #undef MEMCLEAN
 
@@ -556,6 +559,10 @@
 
 /* Define if mkdir has one argument. */
 #undef MKDIR_HAS_ONE_ARG
+
+/* Define this to enable experimental support for more than one catalog
+   consumer zone. */
+#undef MULTIPLE_CATALOG_CONSUMER_ZONES
 
 /* Undefine this to enable internal runtime checks. */
 #undef NDEBUG
Index: configlexer.lex
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/configlexer.lex,v
diff -u -p -r1.28 configlexer.lex
--- configlexer.lex	21 Mar 2026 21:36:36 -0000	1.28
+++ configlexer.lex	20 Sep 2026 09:47:57 -0000
@@ -315,6 +315,7 @@ tls-auth-port{COLON}		{ LEXOUT(("v(%s) "
 tls-auth-xfr-only{COLON}		{ LEXOUT(("v(%s) ", yytext)); return VAR_TLS_AUTH_XFR_ONLY;}
 tls-cert-bundle{COLON}	{ LEXOUT(("v(%s) ", yytext)); return VAR_TLS_CERT_BUNDLE; }
 proxy-protocol-port{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_PROXY_PROTOCOL_PORT; }
+allow-proxy{COLON}	{ LEXOUT(("v(%s) ", yytext)); return VAR_ALLOW_PROXY;}
 answer-cookie{COLON}	{ LEXOUT(("v(%s) ", yytext)); return VAR_ANSWER_COOKIE;}
 cookie-secret{COLON}	{ LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET;}
 cookie-secret-file{COLON}	{ LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET_FILE;}
@@ -337,6 +338,7 @@ xdp-program-path{COLON}		{ LEXOUT(("v(%s
 xdp-program-load{COLON}		{ LEXOUT(("v(%s) ", yytext)); return VAR_XDP_PROGRAM_LOAD; }
 xdp-bpffs-path{COLON}		{ LEXOUT(("v(%s) ", yytext)); return VAR_XDP_BPFFS_PATH; }
 xdp-force-copy{COLON}		{ LEXOUT(("v(%s) ", yytext)); return VAR_XDP_FORCE_COPY; }
+udp-padding-port{COLON}		{ LEXOUT(("v(%s) ", yytext)); return VAR_UDP_PADDING_PORT;}
 {NEWLINE}		{ LEXOUT(("NL\n")); cfg_parser->line++;}
 
 servers={UNQUOTEDLETTER}*	{
Index: configparser.y
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/configparser.y,v
diff -u -p -r1.42 configparser.y
--- configparser.y	21 Mar 2026 21:36:36 -0000	1.42
+++ configparser.y	20 Sep 2026 09:47:57 -0000
@@ -134,6 +134,7 @@ struct component {
 %token VAR_TLS_AUTH_XFR_ONLY
 %token VAR_TLS_CERT_BUNDLE
 %token VAR_PROXY_PROTOCOL_PORT
+%token VAR_ALLOW_PROXY
 %token VAR_CPU_AFFINITY
 %token VAR_XFRD_CPU_AFFINITY
 %token <llng> VAR_SERVER_CPU_AFFINITY
@@ -144,6 +145,7 @@ struct component {
 %token VAR_METRICS_INTERFACE
 %token VAR_METRICS_PORT
 %token VAR_METRICS_PATH
+%token VAR_UDP_PADDING_PORT
 
 /* dnstap */
 %token VAR_DNSTAP
@@ -539,12 +541,18 @@ server_option:
     { cfg_parser->opt->tls_cert_bundle = region_strdup(cfg_parser->opt->region, $2); }
   | VAR_PROXY_PROTOCOL_PORT number
     {
-      struct proxy_protocol_port_list* elem = region_alloc_zero(
+      struct port_list* elem = region_alloc_zero(
 	cfg_parser->opt->region, sizeof(*elem));
       elem->port = $2;
       elem->next = cfg_parser->opt->proxy_protocol_port;
       cfg_parser->opt->proxy_protocol_port = elem;
     }
+  | VAR_ALLOW_PROXY STRING
+    {
+      acl_options_type* acl = parse_acl_info(cfg_parser->opt->region, $2,
+	"NOKEY");
+      append_acl(&cfg_parser->opt->allow_proxy, acl);
+    }
   | VAR_ANSWER_COOKIE boolean
     { cfg_parser->opt->answer_cookie = $2; }
   | VAR_COOKIE_SECRET STRING
@@ -680,6 +688,14 @@ server_option:
 #ifdef USE_METRICS
       cfg_parser->opt->metrics_path = region_strdup(cfg_parser->opt->region, $2);
 #endif /* USE_METRICS */
+    }
+  | VAR_UDP_PADDING_PORT number
+    {
+      struct port_list* elem = region_alloc_zero(
+	cfg_parser->opt->region, sizeof(*elem));
+      elem->port = $2;
+      elem->next = cfg_parser->opt->udp_padding_port;
+      cfg_parser->opt->udp_padding_port = elem;
     }
   ;
 
Index: configure
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/configure,v
diff -u -p -r1.66 configure
--- configure	14 Jul 2026 16:20:05 -0000	1.66
+++ configure	20 Sep 2026 09:47:57 -0000
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.71 for NSD 4.15.0.
+# Generated by GNU Autoconf 2.71 for NSD 4.15.2.
 #
 # Report bugs to <https://github.com/NLnetLabs/nsd/issues or nsd-bugs@nlnetlabs.nl>.
 #
@@ -612,8 +612,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='NSD'
 PACKAGE_TARNAME='nsd'
-PACKAGE_VERSION='4.15.0'
-PACKAGE_STRING='NSD 4.15.0'
+PACKAGE_VERSION='4.15.2'
+PACKAGE_STRING='NSD 4.15.2'
 PACKAGE_BUGREPORT='https://github.com/NLnetLabs/nsd/issues or nsd-bugs@nlnetlabs.nl'
 PACKAGE_URL=''
 
@@ -782,8 +782,10 @@ enable_largefile
 enable_recvmmsg
 with_facility
 with_tcp_timeout
+with_max_cname_chain
 enable_root_server
 enable_ipv6
+enable_multiple_catalog_zones
 enable_bind8_stats
 enable_zone_stats
 enable_checking
@@ -1381,7 +1383,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures NSD 4.15.0 to adapt to many kinds of systems.
+\`configure' configures NSD 4.15.2 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1447,7 +1449,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of NSD 4.15.0:";;
+     short | recursive ) echo "Configuration of NSD 4.15.2:";;
    esac
   cat <<\_ACEOF
 
@@ -1466,6 +1468,9 @@ Optional Features:
                           problems for IPv6
   --enable-root-server    Configure NSD as a root server (obsolete)
   --disable-ipv6          Disables IPv6 support
+  --enable-multiple-catzones
+                          Enable experimental support for more than one
+                          catalog consumer zone
   --disable-bind8-stats   Disable BIND8 like NSTATS & XSTATS and statistics in
                           nsd-control
   --disable-zone-stats    Disable per-zone statistics gathering (if enabled,
@@ -1525,6 +1530,8 @@ Optional Packages:
   --with-facility=name    Syslog default facility (LOG_DAEMON)
   --with-tcp-timeout=number
                           Limit the default tcp timeout
+  --with-max-cname-chain=number
+                          Limit the maximum number of CNAMEs to follow
   --with-ssl=pathname     enable SSL (will check /usr/local/ssl /usr/lib/ssl
                           /usr/ssl /usr/pkg /usr/sfw /usr/local /usr
                           /usr/local/opt/openssl)
@@ -1635,7 +1642,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-NSD configure 4.15.0
+NSD configure 4.15.2
 generated by GNU Autoconf 2.71
 
 Copyright (C) 2021 Free Software Foundation, Inc.
@@ -2292,7 +2299,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by NSD $as_me 4.15.0, which was
+It was created by NSD $as_me 4.15.2, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   $ $0$ac_configure_args_raw
@@ -10509,6 +10516,18 @@ fi
 printf "%s\n" "#define TCP_TIMEOUT $tcp_timeout" >>confdefs.h
 
 
+max_cname_chain=18
+
+# Check whether --with-max_cname_chain was given.
+if test ${with_max_cname_chain+y}
+then :
+  withval=$with_max_cname_chain; max_cname_chain=$withval
+fi
+
+
+printf "%s\n" "#define MAX_CNAME_CHAIN $max_cname_chain" >>confdefs.h
+
+
 # Check whether --enable-root-server was given.
 if test ${enable_root_server+y}
 then :
@@ -10532,6 +10551,22 @@ printf "%s\n" "#define INET6 /**/" >>con
 		;;
 esac
 
+# Check whether --enable-multiple-catalog-zones was given.
+if test ${enable_multiple_catalog_zones+y}
+then :
+  enableval=$enable_multiple_catalog_zones;
+fi
+
+case "$enable_multiple_catalog_zones" in
+        yes)
+
+printf "%s\n" "#define MULTIPLE_CATALOG_CONSUMER_ZONES /**/" >>confdefs.h
+
+		;;
+        no|*)
+                ;;
+esac
+
 # Check whether --enable-bind8-stats was given.
 if test ${enable_bind8_stats+y}
 then :
@@ -13165,7 +13200,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_wri
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by NSD $as_me 4.15.0, which was
+This file was extended by NSD $as_me 4.15.2, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -13229,7 +13264,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config='$ac_cs_config_escaped'
 ac_cs_version="\\
-NSD config.status 4.15.0
+NSD config.status 4.15.2
 configured by $0, generated by GNU Autoconf 2.71,
   with options \\"\$ac_cs_config\\"
 
Index: configure.ac
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/configure.ac,v
diff -u -p -r1.65 configure.ac
--- configure.ac	14 Jul 2026 16:20:05 -0000	1.65
+++ configure.ac	20 Sep 2026 09:47:57 -0000
@@ -7,7 +7,7 @@ sinclude(dnstap/dnstap.m4)
 
 # autoconf-2.70 is needed for @runstatedir@
 AC_PREREQ([2.70])
-AC_INIT([NSD],[4.15.0],[https://github.com/NLnetLabs/nsd/issues or nsd-bugs@nlnetlabs.nl])
+AC_INIT([NSD],[4.15.2],[https://github.com/NLnetLabs/nsd/issues or nsd-bugs@nlnetlabs.nl])
 AC_CONFIG_HEADERS([config.h])
 
 #
@@ -987,6 +987,16 @@ AC_ARG_WITH([tcp_timeout],
 AC_DEFINE_UNQUOTED([TCP_TIMEOUT], $tcp_timeout, [Define to the default tcp timeout.])
 
 dnl
+dnl Determine the maximum CNAME chain length to return
+dnl Defaults to 18 as a tribute to our dear colleague
+dnl
+max_cname_chain=18
+AC_ARG_WITH([max_cname_chain],
+	AS_HELP_STRING([--with-max-cname-chain=number],[Limit the maximum number of CNAMEs to follow]),
+	[max_cname_chain=$withval])
+AC_DEFINE_UNQUOTED([MAX_CNAME_CHAIN], $max_cname_chain, [Define to the default maximum number of CNAMEs to follow.])
+
+dnl
 dnl Features
 dnl
 AC_ARG_ENABLE(root-server, AS_HELP_STRING([--enable-root-server],[Configure NSD as a root server (obsolete)]))
@@ -998,6 +1008,15 @@ case "$enable_ipv6" in
         yes|*)
 		AC_DEFINE_UNQUOTED([INET6], [], [Define this to enable IPv6 support.])
 		;;
+esac
+
+AC_ARG_ENABLE(multiple-catalog-zones, AS_HELP_STRING([--enable-multiple-catzones],[Enable experimental support for more than one catalog consumer zone]))
+case "$enable_multiple_catalog_zones" in
+        yes)
+		AC_DEFINE_UNQUOTED([MULTIPLE_CATALOG_CONSUMER_ZONES], [], [Define this to enable experimental support for more than one catalog consumer zone.])
+		;;
+        no|*)
+                ;;
 esac
 
 AC_ARG_ENABLE(bind8-stats, AS_HELP_STRING([--disable-bind8-stats],[Disable BIND8 like NSTATS & XSTATS and statistics in nsd-control]))
Index: difffile.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/difffile.c,v
diff -u -p -r1.26 difffile.c
--- difffile.c	14 Jul 2026 16:20:05 -0000	1.26
+++ difffile.c	20 Sep 2026 09:47:57 -0000
@@ -505,7 +505,7 @@ find_rr_num(rrset_type* rrset, uint16_t 
 		}
 	}
 	/* this is odd. Log why rr cannot be found. */
-	if (!add) {
+	if (!add && verbosity >= 3) {
 		debug_find_rr_num(rrset, type, klass, rr);
 	}
 	return -1;
@@ -522,6 +522,7 @@ nsec3_delete_rr_trigger(namedb_type* db,
 	/* see if the domain was an NSEC3-domain in the chain, but no longer */
 	if(rr->type == TYPE_NSEC3 && rr->owner->nsec3 &&
 		rr->owner->nsec3->nsec3_node.key &&
+		nsec3_has_owner_for_zone(rr->owner, zone) &&
 		nsec3_rr_uses_params(rr, zone) &&
 		nsec3_in_chain_count(rr->owner, zone) <= 1) {
 		domain_type* prev = nsec3_chain_find_prev(zone, rr->owner);
@@ -640,6 +641,7 @@ nsec3_add_rr_trigger(namedb_type* db, rr
 	 * in the udb has been adjusted) */
 	if(zone->nsec3_param && rr->type == TYPE_NSEC3 &&
 		(!rr->owner->nsec3 || !rr->owner->nsec3->nsec3_node.key)
+		&& nsec3_has_owner_for_zone(rr->owner, zone)
 		&& nsec3_rr_uses_params(rr, zone)) {
 		if(!zone->nsec3_last) {
 			/* all nsec3s have previously been deleted, but
@@ -735,8 +737,9 @@ delete_RR(namedb_type* db, const dname_t
 	rrset = domain_find_rrset_and_prev(domain, zone, type, &rrset_prev);
 #endif
 	if(!rrset) {
-		log_msg(LOG_WARNING, "diff: rrset %s does not exist",
-			dname_to_string(dname,0));
+		VERBOSITY(2, (LOG_WARNING,
+			"diff: RRset to delete from <%s, %s> does not exist",
+			dname_to_string(dname,0), rrtype_to_string(type)));
 		buffer_skip(packet, rdatalen);
 		*softfail = 1;
 		return 1; /* not fatal error */
@@ -776,8 +779,9 @@ delete_RR(namedb_type* db, const dname_t
 			&& rrset->rr_count != 0)
 			rrnum = 0; /* replace existing SOA if no match */
 		if(rrnum == -1) {
-			log_msg(LOG_WARNING, "diff: RR <%s, %s> does not exist",
-				dname_to_string(dname,0), rrtype_to_string(type));
+			VERBOSITY(2, (LOG_WARNING,
+				"diff: RR to delete from RRset <%s, %s> does not exist",
+				dname_to_string(dname,0), rrtype_to_string(type)));
 			*softfail = 1;
 			return 1; /* not fatal error */
 		}
@@ -1316,7 +1320,7 @@ apply_ixfr(nsd_type* nsd, FILE *in, uint
 			region_destroy(region);
 			return 0;
 		}
-		if (klass != CLASS_IN) {
+		if (klass != CLASS_IN && type != TYPE_OPT) {
 			log_msg(LOG_ERR, "bad xfr non-IN-class RR %s %s %s",
 				dname_to_string(owner,0),
 				rrclass_to_string(klass),
Index: dns.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/dns.c,v
diff -u -p -r1.24 dns.c
--- dns.c	21 Mar 2026 21:36:36 -0000	1.24
+++ dns.c	20 Sep 2026 09:47:57 -0000
@@ -404,6 +404,14 @@ static const struct nsd_rdata_descriptor
 	FIELD("target", RDATA_LITERAL_DNAME)
 };
 
+static const struct nsd_rdata_descriptor hhit_rdata_fields[] = {
+	FIELD("cbor blob", RDATA_REMAINDER)
+};
+
+static const struct nsd_rdata_descriptor brid_rdata_fields[] = {
+	FIELD("cbor blob", RDATA_REMAINDER)
+};
+
 static const struct nsd_rdata_descriptor spf_rdata_fields[] = {
 	FIELD("text", RDATA_REMAINDER)
 };
@@ -780,9 +788,15 @@ const nsd_type_descriptor_type type_desc
 	TYPE("DSYNC", TYPE_DSYNC, TYPE_HAS_LITERAL_DNAME,
 		read_dsync_rdata, write_generic_rdata,
 		print_dsync_rdata, dsync_rdata_fields),
+	/* 67 */
+	TYPE("HHIT", TYPE_HHIT, TYPE_HAS_NO_REFS,
+		read_hhit_rdata, write_generic_rdata,
+		print_hhit_rdata, hhit_rdata_fields),
+	/* 68 */
+	TYPE("BRID", TYPE_BRID, TYPE_HAS_NO_REFS,
+		read_brid_rdata, write_generic_rdata,
+		print_brid_rdata, brid_rdata_fields),
 
-	UNKNOWN_TYPE(67),
-	UNKNOWN_TYPE(68),
 	UNKNOWN_TYPE(69),
 	UNKNOWN_TYPE(70),
 	UNKNOWN_TYPE(71),
Index: dns.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/dns.h,v
diff -u -p -r1.23 dns.h
--- dns.h	21 Mar 2026 21:36:36 -0000	1.23
+++ dns.h	20 Sep 2026 09:47:57 -0000
@@ -152,6 +152,8 @@ typedef enum nsd_rc nsd_rc_type;
 #define TYPE_SVCB	64	/* RFC 9460 */
 #define TYPE_HTTPS	65	/* RFC 9460 */
 #define TYPE_DSYNC	66	/* RFC 9859 */
+#define TYPE_HHIT	67	/* RFC 9886 */
+#define TYPE_BRID	68	/* RFC 9886 */
 
 #define TYPE_SPF        99      /* RFC 4408 */
 
@@ -193,6 +195,9 @@ typedef enum nsd_rc nsd_rc_type;
 #define SVCB_KEY_DOHPATH		7
 #define SVCB_KEY_OHTTP			8
 #define SVCB_KEY_TLS_SUPPORTED_GROUPS	9
+#define SVCB_KEY_DOCPATH		10
+#define SVCB_KEY_PVD			11
+#define SVCB_KEY_OOTS			12
 
 #define MAXLABELLEN	63
 #define MAXDOMAINLEN	255
@@ -209,6 +214,7 @@ typedef enum nsd_rc nsd_rc_type;
 #define EUI64ADDRLEN	(64/8)
 
 #define NSEC3_HASH_LEN 20
+#define NSEC3_OWNER_LABEL_LEN (NSEC3_HASH_LEN * 8 / 5)
 
 /*
  * The following RDATA values are used in nsd_rdata_descriptor.length to
Index: edns.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/edns.c,v
diff -u -p -r1.9 edns.c
--- edns.c	3 Sep 2025 18:46:48 -0000	1.9
+++ edns.c	20 Sep 2026 09:47:57 -0000
@@ -20,6 +20,7 @@
 #include "edns.h"
 #include "nsd.h"
 #include "query.h"
+#include "options.h"
 
 #if !defined(HAVE_SSL) || !defined(HAVE_CRYPTO_MEMCMP)
 /* we need fixed time compare, pull it in from tsig.c */
@@ -71,6 +72,8 @@ edns_init_record(edns_record_type *edns)
 	edns->dnssec_ok = 0;
 	edns->nsid = 0;
 	edns->zoneversion = 0;
+	edns->padding = 0;
+	edns->cookie_seen = 0;
 	edns->cookie_status = COOKIE_NOT_PRESENT;
 	edns->cookie_len = 0;
 	edns->ede = -1; /* -1 means no Extended DNS Error */
@@ -88,7 +91,7 @@ edns_handle_option(uint16_t optcode, uin
 	switch(optcode) {
 	case NSID_CODE:
 		/* is NSID enabled? */
-		if(nsd->nsid_len > 0) {
+		if(nsd->nsid_len > 0 && !edns->nsid) {
 			edns->nsid = 1;
 			/* we have to check optlen, and move the buffer along */
 			buffer_skip(packet, optlen);
@@ -101,7 +104,8 @@ edns_handle_option(uint16_t optcode, uin
 		break;
 	case COOKIE_CODE:
 		/* Cookies enabled? */
-		if(nsd->do_answer_cookie) {
+		if(nsd->do_answer_cookie && !edns->cookie_seen) {
+			edns->cookie_seen = 1;
 			if (optlen == 8) 
 				edns->cookie_status = COOKIE_INVALID;
 			else if (optlen < 16 || optlen > 40)
@@ -117,6 +121,11 @@ edns_handle_option(uint16_t optcode, uin
 			buffer_skip(packet, optlen);
 		}
 		break;
+	case PADDING_CODE:
+		if(query->tcp || query->may_pad)
+			edns->padding = 1;
+		buffer_skip(packet, optlen);
+		break;
 	case ZONEVERSION_CODE:
 		edns->zoneversion = 1;
 		if(optlen > 0)
@@ -257,11 +266,8 @@ void cookie_verify(query_type *q, struct
 
 	q->edns.cookie_status = COOKIE_INVALID;
 
-	cookie_time = (q->edns.cookie[12] << 24)
-	            | (q->edns.cookie[13] << 16)
-	            | (q->edns.cookie[14] <<  8)
-	            |  q->edns.cookie[15];
-	
+	cookie_time = read_uint32(q->edns.cookie + 12);
+
 	now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL));
 
 	if(compare_1982(now_uint32, cookie_time) > 0) {
Index: edns.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/edns.h,v
diff -u -p -r1.7 edns.h
--- edns.h	6 Sep 2025 17:41:37 -0000	1.7
+++ edns.h	20 Sep 2026 09:47:57 -0000
@@ -19,6 +19,7 @@ struct query;
 #define OPT_HDR 4U                      /* NSID opt header length */
 #define NSID_CODE       3               /* nsid option code */
 #define COOKIE_CODE    10               /* COOKIE option code */
+#define PADDING_CODE   12               /* Padding option code */
 #define EDE_CODE       15               /* Extended DNS Errors option code */
 #define ZONEVERSION_CODE 19             /* ZONEVERSION option code */
 #define DNSSEC_OK_MASK  0x8000U         /* do bit mask */
@@ -26,6 +27,9 @@ struct query;
 /* https://iana.org/assignments/dns-parameters/#zoneversion-type-values */
 #define ZONEVERSION_SOA_SERIAL 0
 
+/* See RFC 8467 */
+#define PADDING_BLOCK_SZ 468
+
 struct edns_data
 {
 	unsigned char ok[OPT_LEN];
@@ -64,6 +68,8 @@ struct edns_record
 	int                dnssec_ok;
 	int                nsid;
 	int                zoneversion;
+	int                padding;
+	int		   cookie_seen;
 	cookie_status_type cookie_status;
 	size_t             cookie_len;
 	uint8_t            cookie[40];
Index: ipc.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/ipc.c,v
diff -u -p -r1.14 ipc.c
--- ipc.c	6 Sep 2025 17:41:37 -0000	1.14
+++ ipc.c	20 Sep 2026 09:47:57 -0000
@@ -574,10 +574,19 @@ xfrd_handle_ipc_read(struct event* handl
 		if(!xfrd->reload_failed) {
 			xfrd_check_failed_updates();
 			xfrd->reload_cmd_first_sent = 0;
+			xfrd->num_reload_failed_repeat = 0;
+			xfrd->num_xfrs_in_reload = 0;
 		} else {
+			if(xfrd->num_reload_failed_repeat++ >
+				xfrd->num_xfrs_in_reload) {
+				/* More fails than zones, it may be that
+				 * reload crashes, delete failed updates. */
+				xfrd_check_failed_updates();
+			}
 			/* make reload happen again, right away */
 			xfrd_set_reload_now(xfrd);
 		}
+		xfrd->num_xfrs_in_reload = 0;
 		xfrd_prepare_zones_for_reload();
 		xfrd->reload_failed = 0;
 		break;
Index: ixfr.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/ixfr.c,v
diff -u -p -r1.7 ixfr.c
--- ixfr.c	14 Jul 2026 16:20:05 -0000	1.7
+++ ixfr.c	20 Sep 2026 09:47:57 -0000
@@ -439,6 +439,7 @@ static int parse_qserial(struct buffer* 
 {
 	unsigned int i;
 	uint16_t type, rdlen;
+	size_t rdpos;
 	/* we must have a SOA in the authority section */
 	if(NSCOUNT(packet) == 0)
 		return 0;
@@ -460,6 +461,7 @@ static int parse_qserial(struct buffer* 
 		type = buffer_read_u16(packet);
 		buffer_skip(packet, 6);
 		rdlen = buffer_read_u16(packet);
+		rdpos = buffer_position(packet);
 		if(!buffer_available(packet, rdlen))
 			return 0;
 		if(type == TYPE_SOA) {
@@ -471,6 +473,9 @@ static int parse_qserial(struct buffer* 
 				return 0; /* malformed rname */
 			if(!buffer_available(packet, 4))
 				return 0;
+			if(buffer_position(packet) + 20 !=
+				rdpos + (size_t)rdlen)
+				return 0; /* malformed SOA rdata */
 			*qserial = buffer_read_u32(packet);
 			return 1;
 		}
@@ -697,6 +702,18 @@ static uint16_t ixfr_copy_rrs_into_packe
 			query->ixfr_pos_of_newsoa = buffer_position(query->packet);
 		} else {
 			/* cannot add another RR, so return */
+			if(total_added == 0) {
+				/* RR exceeds TCP_MAX_MESSAGE_LEN (65535 bytes):
+				 * cannot fit in any DNS message. Abort the
+				 * IXFR transfer rather than spinning. */
+				VERBOSITY(2, (LOG_ERR, "ixfr_out: SOA RR in zone %s too large for any DNS message "
+					"(wire encoding exceeds %d bytes), aborting IXFR transfer",
+
+					domain_to_string(query->zone->apex),
+					TCP_MAX_MESSAGE_LEN));
+				RCODE_SET(query->packet, RCODE_SERVFAIL);
+				query->ixfr_is_done = 1;
+			}
 			return total_added;
 		}
 	}
@@ -710,6 +727,18 @@ static uint16_t ixfr_copy_rrs_into_packe
 			total_added++;
 		} else {
 			/* cannot add another RR, so return */
+			if(total_added == 0) {
+				/* RR exceeds TCP_MAX_MESSAGE_LEN (65535 bytes):
+				 * cannot fit in any DNS message. Abort the
+				 * IXFR transfer rather than spinning. */
+				VERBOSITY(2, (LOG_ERR, "ixfr_out: SOA RR in zone %s too large for any DNS message "
+					"(wire encoding exceeds %d bytes), aborting IXFR transfer",
+
+					domain_to_string(query->zone->apex),
+					TCP_MAX_MESSAGE_LEN));
+				RCODE_SET(query->packet, RCODE_SERVFAIL);
+				query->ixfr_is_done = 1;
+			}
 			return total_added;
 		}
 	}
@@ -726,6 +755,21 @@ static uint16_t ixfr_copy_rrs_into_packe
 		} else {
 			/* the next record does not fit in the remaining
 			 * space of the packet */
+			if(total_added == 0) {
+				/* RR exceeds TCP_MAX_MESSAGE_LEN (65535 bytes):
+				 * cannot fit in any DNS message. Abort the
+				 * IXFR transfer rather than spinning. */
+				char apexstr[MAXDOMAINLEN * 5];
+				char* ownerstr = "";
+				if(rrlen)
+					ownerstr = wiredname2str(query->ixfr_data->del + query->ixfr_count_del);
+				domain_to_string_buf(query->zone->apex, apexstr);
+				VERBOSITY(2, (LOG_ERR, "ixfr_out: RR at %s in zone %s too large for any DNS message "
+					"(wire encoding exceeds %d bytes), aborting IXFR transfer",
+					ownerstr, apexstr, TCP_MAX_MESSAGE_LEN));
+				RCODE_SET(query->packet, RCODE_SERVFAIL);
+				query->ixfr_is_done = 1;
+			}
 			return total_added;
 		}
 	}
@@ -742,6 +786,21 @@ static uint16_t ixfr_copy_rrs_into_packe
 		} else {
 			/* the next record does not fit in the remaining
 			 * space of the packet */
+			if(total_added == 0) {
+				/* RR exceeds TCP_MAX_MESSAGE_LEN (65535 bytes):
+				 * cannot fit in any DNS message. Abort the
+				 * IXFR transfer rather than spinning. */
+				char apexstr[MAXDOMAINLEN * 5];
+				char* ownerstr = "";
+				if(rrlen)
+					ownerstr = wiredname2str(query->ixfr_data->add + query->ixfr_count_add);
+				domain_to_string_buf(query->zone->apex, apexstr);
+				VERBOSITY(2, (LOG_ERR, "ixfr_out: RR at %s in zone %s too large for any DNS message "
+					"(wire encoding exceeds %d bytes), aborting IXFR transfer",
+					ownerstr, apexstr, TCP_MAX_MESSAGE_LEN));
+				RCODE_SET(query->packet, RCODE_SERVFAIL);
+				query->ixfr_is_done = 1;
+			}
 			return total_added;
 		}
 	}
@@ -757,8 +816,10 @@ query_state_type query_ixfr(struct nsd *
 		return QUERY_PROCESSED;
 
 	pktcompression_init(&pcomp);
-	if (query->maxlen > IXFR_MAX_MESSAGE_LEN)
+	if (query->maxlen > IXFR_MAX_MESSAGE_LEN) {
+		buffer_set_position(query->packet, QHEADERSZ);
 		query->maxlen = IXFR_MAX_MESSAGE_LEN;
+	}
 
 	assert(!query_overflow(query));
 	/* only keep running values for most packets */
@@ -883,7 +944,8 @@ query_state_type query_ixfr(struct nsd *
 
 	total_added = ixfr_copy_rrs_into_packet(query, &pcomp);
 
-	while(query->ixfr_count_add >= query->ixfr_data->add_len) {
+	while(!query->ixfr_is_done &&
+		query->ixfr_count_add >= query->ixfr_data->add_len) {
 		struct ixfr_data* next = ixfr_data_next(query->zone->ixfr,
 			query->ixfr_data);
 		/* finished the ixfr_data */
Index: metrics.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/metrics.c,v
diff -u -p -r1.1.1.3 metrics.c
--- metrics.c	14 Jul 2026 16:19:06 -0000	1.1.1.3
+++ metrics.c	20 Sep 2026 09:47:57 -0000
@@ -19,6 +19,7 @@
 #include <event2/event.h>
 #include <event2/http.h>
 #include <ctype.h>
+#include <inttypes.h>
 
 #include "nsd.h"
 #include "xfrd.h"
Index: namedb.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/namedb.c,v
diff -u -p -r1.18 namedb.c
--- namedb.c	14 Jul 2026 16:20:05 -0000	1.18
+++ namedb.c	20 Sep 2026 09:47:57 -0000
@@ -320,7 +320,10 @@ void zone_add_domain_in_hash_tree(region
 		return;
 	memset(node, 0, sizeof(rbnode_type));
 	node->key = domain;
-	rbtree_insert(*tree, node);
+	if(!rbtree_insert(*tree, node)) {
+		/* collision: clear key to prevent null content key in node. */
+		node->key = NULL;
+	}
 }
 
 domain_table_type *
Index: nsd-checkconf.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/nsd-checkconf.8.in,v
diff -u -p -r1.48 nsd-checkconf.8.in
--- nsd-checkconf.8.in	14 Jul 2026 16:20:05 -0000	1.48
+++ nsd-checkconf.8.in	20 Sep 2026 09:47:57 -0000
@@ -1,4 +1,4 @@
-.TH "nsd\-checkconf" "8" "jul  7, 2026" "NLnet Labs" "nsd 4.15.0"
+.TH "nsd\-checkconf" "8" "sep  2, 2026" "NLnet Labs" "nsd 4.15.2"
 .\" Copyright (c) 2001\-2024, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
Index: nsd-checkconf.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/nsd-checkconf.c,v
diff -u -p -r1.39 nsd-checkconf.c
--- nsd-checkconf.c	6 Sep 2025 17:41:37 -0000	1.39
+++ nsd-checkconf.c	20 Sep 2026 09:47:57 -0000
@@ -512,11 +512,17 @@ config_print_zone(nsd_options_type* opt,
 			return;
 		}
 		if(strcasecmp(o, "proxy_protocol_port") == 0) {
-			struct proxy_protocol_port_list* p;
+			struct port_list* p;
 			for(p = opt->proxy_protocol_port; p; p = p->next)
 				printf("%d\n", p->port);
 			return;
 		}
+		if(strcasecmp(o, "udp_padding_port") == 0) {
+			struct port_list* p;
+			for(p = opt->udp_padding_port; p; p = p->next)
+				printf("%d\n", p->port);
+			return;
+		}
 		printf("Server option not handled: %s\n", o);
 		exit(1);
 	}
@@ -735,9 +741,14 @@ config_test_print_server(nsd_options_typ
 		print_string_var("cookie-secret-file:", "");
 	}
 	if(opt->proxy_protocol_port) {
-		struct proxy_protocol_port_list* p;
+		struct port_list* p;
 		for(p = opt->proxy_protocol_port; p; p = p->next)
 			printf("\tproxy-protocol-port: %d\n", p->port);
+	}
+	if(opt->udp_padding_port) {
+		struct port_list* p;
+		for(p = opt->udp_padding_port; p; p = p->next)
+			printf("\tudp-padding-port: %d\n", p->port);
 	}
 
 #ifdef USE_METRICS
Index: nsd-checkzone.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/nsd-checkzone.8.in,v
diff -u -p -r1.32 nsd-checkzone.8.in
--- nsd-checkzone.8.in	14 Jul 2026 16:20:05 -0000	1.32
+++ nsd-checkzone.8.in	20 Sep 2026 09:47:57 -0000
@@ -1,4 +1,4 @@
-.TH "nsd\-checkzone" "8" "jul  7, 2026" "NLnet Labs" "nsd 4.15.0"
+.TH "nsd\-checkzone" "8" "sep  2, 2026" "NLnet Labs" "nsd 4.15.2"
 .\" Copyright (c) 2014-2024, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
Index: nsd-control.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/nsd-control.8.in,v
diff -u -p -r1.36 nsd-control.8.in
--- nsd-control.8.in	14 Jul 2026 16:20:05 -0000	1.36
+++ nsd-control.8.in	20 Sep 2026 09:47:57 -0000
@@ -1,4 +1,4 @@
-.TH "nsd\-control" "8" "jul  7, 2026" "NLnet Labs" "nsd 4.15.0"
+.TH "nsd\-control" "8" "sep  2, 2026" "NLnet Labs" "nsd 4.15.2"
 .\" Copyright (c) 2011-2024, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
Index: nsd.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/nsd.8.in,v
diff -u -p -r1.50 nsd.8.in
--- nsd.8.in	14 Jul 2026 16:20:05 -0000	1.50
+++ nsd.8.in	20 Sep 2026 09:47:57 -0000
@@ -1,9 +1,9 @@
-.TH "NSD" "8" "jul  7, 2026" "NLnet Labs" "NSD 4.15.0"
+.TH "NSD" "8" "sep  2, 2026" "NLnet Labs" "NSD 4.15.2"
 .\" Copyright (c) 2001\-2024, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
 .B nsd
-\- Name Server Daemon (NSD) version 4.15.0.
+\- Name Server Daemon (NSD) version 4.15.2.
 .SH "SYNOPSIS"
 .B nsd
 .RB [ \-4 ] 
Index: nsd.conf.5.in
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/nsd.conf.5.in,v
diff -u -p -r1.57 nsd.conf.5.in
--- nsd.conf.5.in	14 Jul 2026 16:20:05 -0000	1.57
+++ nsd.conf.5.in	20 Sep 2026 09:47:57 -0000
@@ -1,4 +1,4 @@
-.TH "nsd.conf" "5" "jul  7, 2026" "NLnet Labs" "nsd 4.15.0"
+.TH "nsd.conf" "5" "sep  2, 2026" "NLnet Labs" "nsd 4.15.2"
 .\" Copyright (c) 2001\-2024, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
@@ -631,6 +631,18 @@ The port number for proxy protocol servi
 times, additional port numbers can be used for proxy protocol service. The
 interface definitions that use this port number expect PROXYv2 proxy protocol
 traffic, for UDP, TCP and for TLS service.
+.TP
+.B allow\-proxy:\fR <IP netblock>
+The allowed proxy senders, that can send PROXYv2 traffic. Without an
+allow\-proxy option any IP address is allowed. It can be given multiple times
+to allow multiple netblocks, or with /32 or /128 specific addresses can be
+listed.
+.TP
+.B udp\-padding\-port:\fR <number>
+UDP requests received over this port containing an EDNS0 padding option will,
+unlike ordinary UDP requests, be answered with a padded response. The option
+may be given more than once.
+Listening to the port needs to be configured separately with a \fBip\-address\fR or \fBinterface\fR option.
 .TP
 .B xdp\-interface:\fR <string>
 The interface to use XDP with. This enables the use of AF_XDP sockets for UDP
Index: nsd.conf.sample.in
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/nsd.conf.sample.in,v
diff -u -p -r1.27 nsd.conf.sample.in
--- nsd.conf.sample.in	21 Mar 2026 21:36:36 -0000	1.27
+++ nsd.conf.sample.in	20 Sep 2026 09:47:57 -0000
@@ -287,6 +287,10 @@ server:
 	# expect PROXYv2. For UDP and TCP/TLS interfaces.
 	# proxy-protocol-port: portno for each of the port numbers.
 
+	# Allowed proxy senders. Without an allow-proxy option, any IP address
+	# is allowed. This is the outer, proxy source address.
+	# allow-proxy: 192.0.2.0/24
+
 	# The interface to use XDP with. Default is "", disabled.
 	# xdp-interface: eth0
 
Index: nsec3.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/nsec3.c,v
diff -u -p -r1.31 nsec3.c
--- nsec3.c	14 Jul 2026 16:20:05 -0000	1.31
+++ nsec3.c	20 Sep 2026 09:47:57 -0000
@@ -70,9 +70,11 @@ cmp_nsec3_tree(const void* x, const void
 	const domain_type* a = (const domain_type*)x;
 	const domain_type* b = (const domain_type*)y;
 	/* labelcount + 32long label */
-	assert(dname_name(domain_dname_const(a))[0] == 32);
-	assert(dname_name(domain_dname_const(b))[0] == 32);
-	return memcmp(dname_name(domain_dname_const(a)), dname_name(domain_dname_const(b)), 33);
+	assert(dname_name(domain_dname_const(a))[0] == NSEC3_OWNER_LABEL_LEN);
+	assert(dname_name(domain_dname_const(b))[0] == NSEC3_OWNER_LABEL_LEN);
+	return memcmp( dname_name(domain_dname_const(a))
+	             , dname_name(domain_dname_const(b))
+	             , NSEC3_OWNER_LABEL_LEN + 1);
 }
 
 void nsec3_zone_trees_create(struct region* region, zone_type* zone)
@@ -244,13 +246,14 @@ check_apex_soa(namedb_type* namedb, zone
 		return NULL;
 	}
 	for(j=0; j<nsec3_rrset->rr_count; j++) {
-		if(nsec3_has_soa(nsec3_rrset->rrs[j])) {
+		if(nsec3_has_soa(nsec3_rrset->rrs[j]) &&
+			nsec3_rr_uses_params(nsec3_rrset->rrs[j], zone)) {
 			region_destroy(tmpregion);
 			return nsec3_rrset->rrs[j];
 		}
 	}
 	if(!nolog) {
-		log_msg(LOG_ERR, "%s NSEC3PARAM entry: hash(apex) NSEC3 has no SOA flag.",
+		log_msg(LOG_ERR, "%s NSEC3PARAM entry: hash(apex) NSEC3 has no SOA flag or different params.",
 			domain_to_string(zone->apex));
 		log_msg(LOG_ERR, "hash(apex)= %s",
 			dname_to_string(hashed_apex, NULL));
@@ -342,14 +345,89 @@ nsec3_rdata_params_ok(const rr_type *prr
 		(memcmp(prr->rdata+2, rr->rdata+2, prr->rdlength-2) == 0);
 }
 
+
+/* Copied verbatim from simdzone/src/generic/base32.h */
+static const uint8_t b32rmap[256] = {
+  0xfd, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /*   0 -   7 */
+  0xff, 0xfe, 0xfe, 0xfe,  0xfe, 0xfe, 0xff, 0xff,  /*   8 -  15 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /*  16 -  23 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /*  24 -  31 */
+  0xfe, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /*  32 -  39 */
+  0xff, 0xff, 0xff, 0x3e,  0xff, 0xff, 0xff, 0x3f,  /*  40 -  47 */
+  0x00, 0x01, 0x02, 0x03,  0x04, 0x05, 0x06, 0x07,  /*  48 -  55 */
+  0x08, 0x09, 0xff, 0xff,  0xff, 0xfd, 0xff, 0xff,  /*  56 -  63 */
+  0xff, 0x0a, 0x0b, 0x0c,  0x0d, 0x0e, 0x0f, 0x10,  /*  64 -  71 */
+  0x11, 0x12, 0x13, 0x14,  0x15, 0x16, 0x17, 0x18,  /*  72 -  79 */
+  0x19, 0x1a, 0x1b, 0x1c,  0x1d, 0x1e, 0x1f, 0xff,  /*  80 -  87 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /*  88 -  95 */
+  0xff, 0x0a, 0x0b, 0x0c,  0x0d, 0x0e, 0x0f, 0x10,  /*  96 - 103 */
+  0x11, 0x12, 0x13, 0x14,  0x15, 0x16, 0x17, 0x18,  /* 104 - 111 */
+  0x19, 0x1a, 0x1b, 0x1c,  0x1d, 0x1e, 0x1f, 0xff,  /* 112 - 119 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 120 - 127 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 128 - 135 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 136 - 143 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 144 - 151 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 152 - 159 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 160 - 167 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 168 - 175 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 176 - 183 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 184 - 191 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 192 - 199 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 200 - 207 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 208 - 215 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 216 - 223 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 224 - 231 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 232 - 239 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 240 - 247 */
+  0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff,  /* 248 - 255 */
+};
+
+
 int
 nsec3_rr_uses_params(rr_type* rr, zone_type* zone)
 {
+	/* TODO: Test validness with base32hex parser from simdzone */
+	const uint8_t* wire;
+
 	if(!rr || rr->rdlength < 6)
 		return 0;
-	if(dname_name(domain_dname_const(rr->owner))[0] != 32)
-		return 0; /* owner label not valid base32hex SHA-1 hash */
-	return nsec3_rdata_params_ok(zone->nsec3_param, rr);
+	wire = dname_name(domain_dname_const(rr->owner));
+	if(wire[0] == NSEC3_OWNER_LABEL_LEN
+	&& b32rmap[wire[ 1]] < 32 && b32rmap[wire[ 2]] < 32
+	&& b32rmap[wire[ 3]] < 32 && b32rmap[wire[ 4]] < 32
+	&& b32rmap[wire[ 5]] < 32 && b32rmap[wire[ 6]] < 32
+	&& b32rmap[wire[ 7]] < 32 && b32rmap[wire[ 8]] < 32
+	&& b32rmap[wire[ 9]] < 32 && b32rmap[wire[10]] < 32
+	&& b32rmap[wire[11]] < 32 && b32rmap[wire[12]] < 32
+	&& b32rmap[wire[13]] < 32 && b32rmap[wire[14]] < 32
+	&& b32rmap[wire[15]] < 32 && b32rmap[wire[16]] < 32
+	&& b32rmap[wire[17]] < 32 && b32rmap[wire[18]] < 32
+	&& b32rmap[wire[19]] < 32 && b32rmap[wire[20]] < 32
+	&& b32rmap[wire[21]] < 32 && b32rmap[wire[22]] < 32
+	&& b32rmap[wire[23]] < 32 && b32rmap[wire[24]] < 32
+	&& b32rmap[wire[25]] < 32 && b32rmap[wire[26]] < 32
+	&& b32rmap[wire[27]] < 32 && b32rmap[wire[28]] < 32
+	&& b32rmap[wire[29]] < 32 && b32rmap[wire[30]] < 32
+	&& b32rmap[wire[31]] < 32 && b32rmap[wire[32]] < 32)
+		return nsec3_rdata_params_ok(zone->nsec3_param, rr);
+	return 0;
+}
+
+int
+nsec3_has_owner_for_zone(domain_type* domain, zone_type* zone)
+{
+	if(dname_name(domain_dname_const(domain))[0] != NSEC3_OWNER_LABEL_LEN)
+		return 0; /* not b32.name */
+	if(!(((size_t)domain_dname(domain)->label_count) ==
+		((size_t)domain_dname(zone->apex)->label_count)+1 &&
+		((size_t)domain_dname(domain)->name_size) ==
+		((size_t)domain_dname(zone->apex)->name_size) +
+		NSEC3_OWNER_LABEL_LEN+1 &&
+		memcmp(dname_name(domain_dname_const(domain))+
+		NSEC3_OWNER_LABEL_LEN+1, dname_name(domain_dname_const(
+		zone->apex)), domain_dname(zone->apex)->name_size) == 0))
+		return 0; /* not b32.zonename */
+	return 1;
 }
 
 int
@@ -360,6 +438,8 @@ nsec3_in_chain_count(domain_type* domain
 	int count = 0;
 	if(!rrset || !zone->nsec3_param)
 		return 0; /* no NSEC3s, none in the chain */
+	if(!nsec3_has_owner_for_zone(domain, zone))
+		return 0; /* wrong owner name */
 	for(i=0; i<rrset->rr_count; i++) {
 		if(nsec3_rr_uses_params(rrset->rrs[i], zone))
 			count++;
@@ -690,20 +770,43 @@ nsec3_precompile_domain_ds(struct namedb
 		cmp_dshash_tree, domain, &domain->nsec3->ds_parent_hash->node);
 }
 
-static void
-parse_nsec3_name(const dname_type* dname, uint8_t* hash, size_t buflen)
+static inline int
+b32hex_p8ton5(const uint8_t* p, uint8_t* n)
+{
+	uint8_t ofs;
+	if((ofs = b32rmap[p[0]]) > 31) return 0;
+	n[0]  = (uint8_t)(ofs << 3);
+	if((ofs = b32rmap[p[1]]) > 31) return 0;
+	n[0] |= (uint8_t)(ofs >> 2);
+	n[1]  = (uint8_t)(ofs << 6);
+	if((ofs = b32rmap[p[2]]) > 31) return 0;
+	n[1] |= (uint8_t)(ofs << 1);
+	if((ofs = b32rmap[p[3]]) > 31) return 0;
+	n[1] |= (uint8_t)(ofs >> 4);
+	n[2]  = (uint8_t)(ofs << 4);
+	if((ofs = b32rmap[p[4]]) > 31) return 0;
+	n[2] |= (uint8_t)(ofs >> 1);
+	n[3]  = (uint8_t)(ofs << 7);
+	if((ofs = b32rmap[p[5]]) > 31) return 0;
+	n[3] |= (uint8_t)(ofs << 2);
+	if((ofs = b32rmap[p[6]]) > 31) return 0;
+	n[3] |= (uint8_t)(ofs >> 3);
+	n[4]  = (uint8_t)(ofs << 5);
+	if((ofs = b32rmap[p[7]]) > 31) return 0;
+	n[4] |= ofs;
+	return 1;
+}
+
+static int
+parse_nsec3_name(const dname_type* dname, uint8_t* hash)
 {
-	/* first label must be the match, */
-	size_t lablen = (buflen-1) * 8 / 5;
+	/* TODO: Do this with base32hex parser from simdzone */
 	const uint8_t* wire = dname_name(dname);
-	assert(lablen == 32 && buflen == NSEC3_HASH_LEN+1);
-	/* labels of length 32 for SHA1, and must have space+1 for convert */
-	if(wire[0] != lablen) {
-		/* not NSEC3 */
-		memset(hash, 0, buflen);
-		return;
-	}
-	(void)b32_pton((char*)wire+1, hash, buflen);
+	return wire[0] == NSEC3_OWNER_LABEL_LEN
+	    && b32hex_p8ton5(wire +  1, hash)
+	    && b32hex_p8ton5(wire +  9, hash +  5)
+	    && b32hex_p8ton5(wire + 17, hash + 10)
+	    && b32hex_p8ton5(wire + 25, hash + 15);
 }
 
 void
@@ -904,8 +1007,13 @@ process_range(zone_type* zone, domain_ty
 	 * already allocated, and we need not allocate it here */
 	/* set start */
 	if(start) {
-		uint8_t hash[NSEC3_HASH_LEN+1];
-		parse_nsec3_name(domain_dname(start), hash, sizeof(hash));
+		uint8_t hash[NSEC3_HASH_LEN];
+		if(!parse_nsec3_name(domain_dname(start), hash)) {
+			log_msg( LOG_WARNING
+			       , "NSEC3 %s skipped due to invalid owner name"
+			       , dname_to_string(domain_dname(start), 0));
+			return;
+		}
 		/* if exact match on first, set is_exact */
 		if(process_first(zone->hashtree, hash, &p, init_lookup_key_hash_tree)) {
 			((domain_type*)(p->key))->nsec3->nsec3_cover = nsec3;
@@ -930,8 +1038,13 @@ process_range(zone_type* zone, domain_ty
 	}
 	/* set end */
 	if(end) {
-		uint8_t hash[NSEC3_HASH_LEN+1];
-		parse_nsec3_name(domain_dname(end), hash, sizeof(hash));
+		uint8_t hash[NSEC3_HASH_LEN];
+		if(!parse_nsec3_name(domain_dname(end), hash)) {
+			log_msg( LOG_WARNING
+			       , "NSEC3 %s skipped due to invalid owner name"
+			       , dname_to_string(domain_dname(end), 0));
+			return;
+		}
 		process_end(zone->hashtree, hash, &p_end, init_lookup_key_hash_tree);
 		process_end(zone->wchashtree, hash, &pwc_end, init_lookup_key_wc_tree);
 		process_end(zone->dshashtree, hash, &pds_end, init_lookup_key_ds_tree);
Index: nsec3.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/nsec3.h,v
diff -u -p -r1.5 nsec3.h
--- nsec3.h	14 Jul 2026 16:20:05 -0000	1.5
+++ nsec3.h	20 Sep 2026 09:47:57 -0000
@@ -80,6 +80,8 @@ void nsec3_hash_and_store(struct zone* z
 int nsec3_rr_uses_params(struct rr* rr, struct zone* zone);
 /* number of NSEC3s that are in the zone chain */
 int nsec3_in_chain_count(struct domain* domain, struct zone* zone);
+/* if the NSEC3 record is b32.zone name */
+int nsec3_has_owner_for_zone(struct domain* domain, struct zone* zone);
 /* find previous NSEC3, or, lastinzone, or, NULL */
 struct domain* nsec3_chain_find_prev(struct zone* zone, struct domain* domain);
 /* clear nsec3 precompile for the zone */
Index: options.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/options.c,v
diff -u -p -r1.35 options.c
--- options.c	14 Jul 2026 16:20:05 -0000	1.35
+++ options.c	20 Sep 2026 09:47:57 -0000
@@ -152,6 +152,8 @@ nsd_options_create(region_type* region)
 	opt->tls_cert_bundle = NULL;
 	opt->tls_auth_xfr_only = 0;
 	opt->proxy_protocol_port = NULL;
+	opt->allow_proxy = NULL;
+	opt->udp_padding_port = NULL;
 	opt->answer_cookie = 1;
 	opt->cookie_secret = NULL;
 	opt->cookie_staging_secret = NULL;
@@ -1963,6 +1965,28 @@ key_options_add_modify(struct nsd_option
 }
 
 int
+acl_check_incoming_proxy(struct acl_options* acl, struct query* q,
+	struct acl_options** reason)
+{
+	if(reason)
+		*reason = NULL;
+
+	while(acl)
+	{
+		DEBUG(DEBUG_XFRD,2, (LOG_INFO, "proxy testing allow-proxy acl %s",
+			acl->ip_address_spec));
+		if(acl_addr_matches_proxy(acl, q)) {
+			if(reason)
+				*reason = acl;
+			return 1;
+		}
+		acl = acl->next;
+	}
+
+	return -1;
+}
+
+int
 acl_check_incoming_block_proxy(struct acl_options* acl, struct query* q,
 	struct acl_options** reason)
 {
@@ -2025,7 +2049,11 @@ acl_check_incoming(struct acl_options* a
 			continue;
 		}
 #endif
-		if(acl_addr_matches(acl, q) && acl_key_matches(acl, q)) {
+		if(acl_addr_matches(acl, q) && acl_key_matches(acl, q)
+#ifdef HAVE_SSL
+		&& acl_tls_auth_name_matches(acl, q)
+#endif
+		) {
 			if(!match)
 			{
 				match = acl; /* remember first match */
@@ -2037,27 +2065,6 @@ acl_check_incoming(struct acl_options* a
 				return -1;
 			}
 		}
-#ifdef HAVE_SSL
-		/* we are in a acl with tls_auth */
-		if (acl->tls_auth_name) {
-			/* we have auth_domain_name in tls_auth */
-			if (acl->tls_auth_options && acl->tls_auth_options->auth_domain_name) {
-				if (!acl_tls_hostname_matches(q->tls_auth, acl->tls_auth_options->auth_domain_name)) {
-					VERBOSITY(3, (LOG_WARNING,
-							"client cert does not match %s %s",
-							acl->tls_auth_name, acl->tls_auth_options->auth_domain_name));
-					q->cert_cn = NULL;
-					return -1;
-				}
-				VERBOSITY(5, (LOG_INFO, "%s %s verified",
-					acl->tls_auth_name, acl->tls_auth_options->auth_domain_name));
-				q->cert_cn = acl->tls_auth_options->auth_domain_name;
-			} else {
-				/* nsd gives error on start for this, but check just in case */
-				log_msg(LOG_ERR, "auth-domain-name not defined in %s", acl->tls_auth_name);
-			}
-		}
-#endif
 		number++;
 		acl = acl->next;
 	}
@@ -2221,9 +2228,9 @@ acl_addr_match_range_v4(uint32_t* minval
 	/* check treats x as one huge number */
 
 	/* if outside bounds, we are done */
-	if(*minval > *x)
+	if(ntohl(*minval) > ntohl(*x))
 		return 0;
-	if(*maxval < *x)
+	if(ntohl(*maxval) < ntohl(*x))
 		return 0;
 
 	return 1;
@@ -2244,10 +2251,10 @@ acl_addr_match_range_v6(uint32_t* minval
 	{
 		/* if outside bounds, we are done */
 		if(checkmin)
-			if(minval[i] > x[i])
+			if(ntohl(minval[i]) > ntohl(x[i]))
 				return 0;
 		if(checkmax)
-			if(maxval[i] < x[i])
+			if(ntohl(maxval[i]) < ntohl(x[i]))
 				return 0;
 		/* if x is equal to a bound, that bound needs further checks */
 		if(checkmin && minval[i]!=x[i])
@@ -2270,13 +2277,18 @@ acl_addr_match_range_v6(uint32_t* minval
  * Copyright (C) 2012, iSEC Partners.
  * License: MIT License
  * Author:  Alban Diquet
+ *
+ * Modified 20260805 W.C.A. Wijngaards - added san_present for RFC6125
+ * conformance change.
  */
 static int matches_subject_alternative_name(
-	const char *acl_cert_cn, size_t acl_cert_cn_len, const X509 *cert)
+	const char *acl_cert_cn, size_t acl_cert_cn_len, const X509 *cert,
+	int* san_present)
 {
 	int result = 0;
 	int san_names_nb = -1;
 	STACK_OF(GENERAL_NAME) *san_names = NULL;
+	*san_present = 0;
 
 	/* Try to extract the names within the SAN extension from the certificate */
 	san_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
@@ -2293,6 +2305,7 @@ static int matches_subject_alternative_n
 		/* Skip non-DNS SAN entries. */
 		if (current_name->type != GEN_DNS)
 			continue;
+		*san_present = 1; /* DNS SAN entry is present */
 #if HAVE_ASN1_STRING_GET0_DATA
 		str = (const char *)ASN1_STRING_get0_data(current_name->d.dNSName);
 #else
@@ -2385,7 +2398,7 @@ static int matches_common_name(
 int
 acl_tls_hostname_matches(SSL* tls_auth, const char *acl_cert_cn)
 {
-	int result = 0;
+	int result = 0, san_present;
 	size_t acl_cert_cn_len;
 	X509 *client_cert;
 
@@ -2413,8 +2426,9 @@ acl_tls_hostname_matches(SSL* tls_auth, 
 	 */
 
 	acl_cert_cn_len = strlen(acl_cert_cn);
-	/* semi follow RFC6125#section-6.4.4 check SAN DNS first */
-	if (!(result = matches_subject_alternative_name(acl_cert_cn, acl_cert_cn_len, client_cert)))
+	/* follow RFC6125#section-6.4.4 check SAN DNS first, and
+	 * common name if there is no SAN DNS present. */
+	if (!(result = matches_subject_alternative_name(acl_cert_cn, acl_cert_cn_len, client_cert, &san_present)) && !san_present)
 		result = matches_common_name(acl_cert_cn, acl_cert_cn_len, client_cert);
 
 	X509_free(client_cert);
@@ -2462,6 +2476,35 @@ acl_key_matches(struct acl_options* acl,
 	return 1;
 }
 
+#ifdef HAVE_SSL
+int
+acl_tls_auth_name_matches(struct acl_options* acl, struct query* q)
+{
+	/* If no name specified, no name is required  */
+	if (!acl->tls_auth_name)
+		return 1;
+
+	/* we have auth_domain_name in tls_auth */
+	if (!acl->tls_auth_options
+	||  !acl->tls_auth_options->auth_domain_name) {
+		/* nsd gives error on start for this, but check just in case */
+		log_msg(LOG_ERR, "auth-domain-name not defined in %s", acl->tls_auth_name);
+		return 0;
+	}
+	if (!acl_tls_hostname_matches(q->tls_auth,
+				acl->tls_auth_options->auth_domain_name)) {
+		VERBOSITY(6, (LOG_DEBUG, "client cert does not match %s %s",
+			acl->tls_auth_name,
+			acl->tls_auth_options->auth_domain_name));
+		return 0;
+	}
+	VERBOSITY(5, (LOG_INFO, "%s %s verified", acl->tls_auth_name,
+		acl->tls_auth_options->auth_domain_name));
+	q->cert_cn = acl->tls_auth_options->auth_domain_name;
+	return 1;
+}
+#endif
+
 int
 acl_same_host(struct acl_options* a, struct acl_options* b)
 {
@@ -3173,10 +3216,8 @@ resolve_interface_names(struct nsd_optio
 }
 
 int
-sockaddr_uses_proxy_protocol_port(struct nsd_options* options,
-	struct sockaddr* addr)
+sockaddr_uses_port(struct sockaddr* addr, struct port_list* p)
 {
-	struct proxy_protocol_port_list* p;
 	int port;
 #ifdef INET6
 	struct sockaddr_storage* ss = (struct sockaddr_storage*)addr;
@@ -3196,7 +3237,6 @@ sockaddr_uses_proxy_protocol_port(struct
 		return 0; /* unknown family */
 	}
 #endif
-	p = options->proxy_protocol_port;
 	while(p) {
 		if(p->port == port)
 			return 1;
Index: options.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/options.h,v
diff -u -p -r1.33 options.h
--- options.h	26 Jun 2026 08:31:25 -0000	1.33
+++ options.h	20 Sep 2026 09:47:57 -0000
@@ -21,7 +21,7 @@ struct dname;
 struct tsig_key;
 struct buffer;
 struct nsd;
-struct proxy_protocol_port_list;
+struct port_list;
 
 
 typedef struct nsd_options nsd_options_type;
@@ -147,7 +147,11 @@ struct nsd_options {
 	int tls_auth_xfr_only;
 
 	/* proxy protocol port list */
-	struct proxy_protocol_port_list* proxy_protocol_port;
+	struct port_list* proxy_protocol_port;
+	/* Allowed proxy senders (the outer IP addr), it allows all if empty */
+	struct acl_options* allow_proxy;
+	/* udp-padding-port list */
+	struct port_list* udp_padding_port;
 
 	/** remote control section. enable toggle. */
 	int control_enable;
@@ -463,9 +467,9 @@ struct tls_auth_options {
 	char* client_key_pw;
 };
 
-/* proxy protocol port option list */
-struct proxy_protocol_port_list {
-	struct proxy_protocol_port_list* next;
+/* port option list */
+struct port_list {
+	struct port_list* next;
 	int port;
 };
 
@@ -599,6 +603,7 @@ int acl_addr_matches_proxy(struct acl_op
 int acl_tls_hostname_matches(SSL* ssl, const char* acl_cert_cn);
 #endif
 int acl_key_matches(struct acl_options* acl, struct query* q);
+int acl_tls_auth_name_matches(struct acl_options* acl, struct query* q);
 int acl_addr_match_mask(uint32_t* a, uint32_t* b, uint32_t* mask, size_t sz);
 int acl_addr_match_range_v6(uint32_t* minval, uint32_t* x, uint32_t* maxval, size_t sz);
 int acl_addr_match_range_v4(uint32_t* minval, uint32_t* x, uint32_t* maxval, size_t sz);
@@ -606,6 +611,9 @@ int acl_addr_match_range_v4(uint32_t* mi
 /* check acl list for blocks on address, return 0 if none, -1 if blocked. */
 int acl_check_incoming_block_proxy(struct acl_options* acl, struct query* q,
 	struct acl_options** reason);
+/* check acl list, proxy addr, if match return 1, -1 if no matches. */
+int acl_check_incoming_proxy(struct acl_options* acl, struct query* q,
+	struct acl_options** reason);
 
 /* returns true if acls are both from the same host */
 int acl_same_host(struct acl_options* a, struct acl_options* b);
@@ -666,8 +674,7 @@ void warn_if_directory(const char* filet
  * names. */
 void resolve_interface_names(struct nsd_options* options);
 
-/* See if the sockaddr port number is listed in the proxy protocol ports. */
-int sockaddr_uses_proxy_protocol_port(struct nsd_options* options,
-	struct sockaddr* addr);
+/* See if the sockaddr port number is listed in the ports. */
+int sockaddr_uses_port(struct sockaddr* addr, struct port_list* ports);
 
 #endif /* OPTIONS_H */
Index: query.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/query.c,v
diff -u -p -r1.46 query.c
--- query.c	14 Jul 2026 16:20:05 -0000	1.46
+++ query.c	20 Sep 2026 09:47:57 -0000
@@ -249,7 +249,9 @@ query_reset(query_type *q, size_t maxlen
 	region_free_all(q->region);
 	q->remote_addrlen = (socklen_t)sizeof(q->remote_addr);
 	q->client_addrlen = (socklen_t)sizeof(q->client_addr);
-	q->is_proxied = 0;
+	q->may_pad = 0;
+	if(!is_tcp)
+		q->is_proxied = 0;
 	q->maxlen = maxlen;
 	q->reserved_space = 0;
 	buffer_clear(q->packet);
@@ -1031,7 +1033,8 @@ answer_nodata(struct query *query, answe
 	answer_soa(query, answer);
 
 #ifdef NSEC3
-	if (query->edns.dnssec_ok && query->zone->nsec3_param) {
+	if (query->edns.dnssec_ok && query->zone->nsec3_param &&
+		zone_is_secure(query->zone)) {
 		nsec3_answer_nodata(query, answer, original);
 	} else
 #endif
@@ -1142,6 +1145,9 @@ answer_domain(struct nsd* nsd, struct qu
 			zone_type* origzone = q->zone;
 			++q->cname_count;
 
+			if (q->cname_count >= MAX_CNAME_CHAIN) {
+				return;
+			}
 			answer_lookup_zone(nsd, q, answer, closest_match->number,
 					     closest_match == closest_encloser,
 					     closest_match, closest_encloser,
@@ -1236,6 +1242,7 @@ answer_authoritative(struct nsd   *nsd,
 			return;
 		}
 		DEBUG(DEBUG_QUERY,2, (LOG_INFO, "->result is %s", dname_to_string(newname, NULL)));
+
 		/* follow the DNAME */
 		(void)namedb_lookup(nsd->db, newname, &closest_match, &closest_encloser);
 		/* synthesize CNAME record */
@@ -1252,6 +1259,9 @@ answer_authoritative(struct nsd   *nsd,
 			 * of type CNAME */
 			return;
 		}
+		if (q->cname_count >= MAX_CNAME_CHAIN) {
+			return;
+		}
 
 		answer_lookup_zone(nsd, q, answer, newnum,
 			closest_match == closest_encloser,
@@ -1776,6 +1786,16 @@ query_process(query_type *q, nsd_type *n
 		cookie_verify(q, nsd, now_p);
 
 	query_prepare_response(q);
+	if(q->reserved_space + QHEADERSZ + (size_t)q->qname->name_size +
+		2 /* qtype */ + 2 /* qclass */ > q->maxlen) {
+		/* Clear out some space, and return error, it does not fit. */
+		q->edns.status = EDNS_NOT_PRESENT;
+		q->tsig.status = TSIG_NOT_PRESENT;
+		if(q->tcp)
+			return query_error(q, NSD_RC_SERVFAIL);
+		TC_SET(q->packet);
+		return query_error(q, NSD_RC_OK);
+	}
 
 	if (q->qclass != CLASS_IN && q->qclass != CLASS_ANY) {
 		if (q->qclass == CLASS_CH) {
@@ -1839,7 +1859,21 @@ query_add_optional(query_type *q, nsd_ty
 			                           +  sizeof(uint8_t)
 			                           +  sizeof(uint8_t)
 			                           +  sizeof(uint32_t);
-
+		if(q->edns.padding) {
+			size_t cur_sz = buffer_position(q->packet) + 2 + q->edns.opt_reserved_space;
+			size_t padded_sz = (((cur_sz - 1) / PADDING_BLOCK_SZ) + 1) * PADDING_BLOCK_SZ;
+			size_t to_padd = padded_sz - cur_sz;
+			/* Need 4 bytes for option code and length */
+			q->edns.padding = to_padd >= 4 ? to_padd
+			                : to_padd >  0 ? (PADDING_BLOCK_SZ + to_padd) 
+					: 0; /* Multiple of PADDING_BLOCK_SZ,
+			                      * so no outgoing padding option */
+			if(!buffer_available(q->packet, 2+q->edns.opt_reserved_space+q->edns.padding) || cur_sz + q->edns.padding > 65535)
+				q->edns.padding = 0;
+			if(q->edns.padding) {
+				q->edns.opt_reserved_space += q->edns.padding;
+			}
+		}
 		if(q->edns.opt_reserved_space == 0 || !buffer_available(
 			q->packet, 2+q->edns.opt_reserved_space)) {
 			/* fill with NULLs */
@@ -1894,6 +1928,12 @@ query_add_optional(query_type *q, nsd_ty
 					buffer_write(q->packet,
 							q->edns.ede_text,
 							q->edns.ede_text_len);
+			}
+			if(q->edns.padding) {
+				assert(q->edns.padding >= 4);
+				buffer_write_u16(q->packet, PADDING_CODE);
+				buffer_write_u16(q->packet, q->edns.padding - 4);
+				buffer_fill(q->packet, 0, q->edns.padding - 4);
 			}
 		}
 		ARCOUNT_SET(q->packet, ARCOUNT(q->packet) + 1);
Index: query.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/query.h,v
diff -u -p -r1.10 query.h
--- query.h	6 Sep 2025 17:41:37 -0000	1.10
+++ query.h	20 Sep 2026 09:47:57 -0000
@@ -47,6 +47,9 @@ struct query {
 
 	/* if set, the request came through a proxy */
 	int is_proxied;
+	/* if set, the response may be padded */
+	int may_pad;
+
 	/* the client address
 	 * the same as remote_addr if not proxied */
 #ifdef INET6
Index: rdata.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/rdata.c,v
diff -u -p -r1.22 rdata.c
--- rdata.c	14 Jul 2026 16:20:05 -0000	1.22
+++ rdata.c	20 Sep 2026 09:47:57 -0000
@@ -105,6 +105,14 @@ static int print_svcparam_dohpath(struct
 static int print_svcparam_tls_supported_groups(struct buffer *output,
 	uint16_t svcparamkey, const uint8_t* data, uint16_t datalen);
 
+/* Print svcparam docpath */
+static int print_svcparam_docpath(struct buffer *output,
+	uint16_t svcparamkey, const uint8_t* data, uint16_t datalen);
+
+/* Print svcparam oots */
+static int print_svcparam_oots(struct buffer *output,
+	uint16_t svcparamkey, const uint8_t* data, uint16_t datalen);
+
 static const nsd_svcparam_descriptor_type svcparams[] = {
 	{ SVCB_KEY_MANDATORY, "mandatory", print_svcparam_mandatory },
 	{ SVCB_KEY_ALPN, "alpn", print_svcparam_alpn },
@@ -118,6 +126,9 @@ static const nsd_svcparam_descriptor_typ
 	{ SVCB_KEY_OHTTP, "ohttp", print_svcparam_no_value },
 	{ SVCB_KEY_TLS_SUPPORTED_GROUPS, "tls-supported-groups",
 		print_svcparam_tls_supported_groups },
+	{ SVCB_KEY_DOCPATH, "docpath", print_svcparam_docpath},
+	{ SVCB_KEY_PVD, "pvd", print_svcparam_no_value },
+	{ SVCB_KEY_OOTS, "oots", print_svcparam_oots},
 };
 
 /*
@@ -683,6 +694,7 @@ svcparam_must_have_value(uint16_t svcpar
 	case SVCB_KEY_MANDATORY:
 	case SVCB_KEY_DOHPATH:
 	case SVCB_KEY_TLS_SUPPORTED_GROUPS:
+	case SVCB_KEY_OOTS:
 		return 1;
 	default:
 		break;
@@ -697,6 +709,7 @@ svcparam_must_not_have_value(uint16_t sv
 	switch (svcparamkey) {
 	case SVCB_KEY_NO_DEFAULT_ALPN:
 	case SVCB_KEY_OHTTP:
+	case SVCB_KEY_PVD:
 		return 1;
 	default:
 		break;
@@ -951,6 +964,52 @@ print_svcparam_tls_supported_groups(stru
 	return 1;
 }
 
+static int
+print_svcparam_docpath(struct buffer *output, uint16_t svcparamkey,
+	const uint8_t* data, uint16_t datalen)
+{
+	if(datalen > 0)
+		return print_svcparam_alpn(output, svcparamkey, data, datalen);
+	buffer_print_svcparamkey(output, svcparamkey);
+	return 1;
+}
+
+static int
+print_svcparam_oots(struct buffer *output, uint16_t svcparamkey,
+		const uint8_t* data, uint16_t datalen)
+{
+	assert(datalen > 0); /* Guaranteed by svcparam_print */
+
+	buffer_print_svcparamkey(output, svcparamkey);
+	buffer_printf(output, "=\"");
+	while(((size_t)(*data)) + 2 <= (size_t)datalen) {
+		size_t transport_len = *data;
+		uint8_t percentage = data[transport_len + 1];
+		size_t i;
+
+		if(!transport_len || percentage > 100)
+			return 0;
+
+		for(i=0; i < transport_len; i++) {
+			char ch = data[i + 1];
+			if(!isgraph(ch)
+			|| ch == '"' || ch == '\\' || ch == ',' || ch == ':')
+				return 0;
+
+			buffer_write_u8(output, ch);
+		}
+		buffer_printf(output, ":%d", percentage);
+		data += transport_len + 2;
+		datalen -= transport_len + 2;
+		if(datalen)
+			buffer_write_u8(output, ',');
+	}
+	if(datalen)
+		return 0;
+	buffer_printf(output, "\"");
+	return 1;
+}
+
 /*
  * Print svcparam.
  * @param output: the string is output here.
@@ -3407,6 +3466,50 @@ print_dsync_rdata(struct buffer *output,
 		rrtype_to_string(read_uint16(rr->rdata)), rr->rdata[2],
 		read_uint16(rr->rdata+3));
 	if(!print_name_literal(output, rr->rdlength, rr->rdata, &length))
+		return 0;
+	if(rr->rdlength != length)
+		return 0;
+	return 1;
+}
+
+int32_t
+read_hhit_rdata(struct domain_table *domains, uint16_t rdlength,
+	struct buffer *packet, struct rr **rr)
+{
+	/* A CBOR blob has at least 1 byte */
+	if (rdlength < 1)
+		return MALFORMED;
+	return read_rdata(domains, rdlength, packet, rr);
+}
+
+int
+print_hhit_rdata(struct buffer *output, const struct rr *rr)
+{
+	uint16_t length = 0;
+
+	if (!print_base64(output, rr->rdlength, rr->rdata, &length))
+		return 0;
+	if(rr->rdlength != length)
+		return 0;
+	return 1;
+}
+
+int32_t
+read_brid_rdata(struct domain_table *domains, uint16_t rdlength,
+	struct buffer *packet, struct rr **rr)
+{
+	/* A CBOR blob has at least 1 byte */
+	if (rdlength < 1)
+		return MALFORMED;
+	return read_rdata(domains, rdlength, packet, rr);
+}
+
+int
+print_brid_rdata(struct buffer *output, const struct rr *rr)
+{
+	uint16_t length = 0;
+
+	if (!print_base64(output, rr->rdlength, rr->rdata, &length))
 		return 0;
 	if(rr->rdlength != length)
 		return 0;
Index: rdata.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/rdata.h,v
diff -u -p -r1.4 rdata.h
--- rdata.h	21 Mar 2026 21:36:36 -0000	1.4
+++ rdata.h	20 Sep 2026 09:47:57 -0000
@@ -421,6 +421,20 @@ int32_t read_dsync_rdata(struct domain_t
 /* Print rdata for type DSYNC. */
 int print_dsync_rdata(struct buffer *output, const struct rr *rr);
 
+/* Read rdata for type HHIT. */
+int32_t read_hhit_rdata(struct domain_table *domains, uint16_t rdlength,
+	struct buffer *packet, struct rr **rr);
+
+/* Print rdata for type HHIT. */
+int print_hhit_rdata(struct buffer *output, const struct rr *rr);
+
+/* Read rdata for type BRID. */
+int32_t read_brid_rdata(struct domain_table *domains, uint16_t rdlength,
+	struct buffer *packet, struct rr **rr);
+
+/* Print rdata for type BRID. */
+int print_brid_rdata(struct buffer *output, const struct rr *rr);
+
 /* Read rdata for type NID. */
 int32_t read_nid_rdata(struct domain_table *domains, uint16_t rdlength,
 	struct buffer *packet, struct rr **rr);
Index: remote.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/remote.c,v
diff -u -p -r1.31 remote.c
--- remote.c	14 Jul 2026 16:20:05 -0000	1.31
+++ remote.c	20 Sep 2026 09:47:57 -0000
@@ -982,6 +982,10 @@ force_transfer_zone(xfrd_zone_type* zone
 		xfrd_tcp_release(xfrd->tcp_set, zone);
 	else if(zone->zone_handler.ev_fd != -1)
 		xfrd_udp_release(zone);
+	if(zone->udp_waiting)
+		udp_zone_waiting_list_remove(zone);
+	if(zone->tcp_waiting)
+		tcp_zone_waiting_list_remove(zone);
 	/* pretend we not longer have it and force any
 	 * zone to be downloaded (even same serial, w AXFR) */
 	zone->soa_disk_acquired = 0;
@@ -1044,8 +1048,10 @@ print_zonestatus(RES* ssl, xfrd_state_ty
 	}
 	if(zone_is_catalog_consumer(zo)) {
 		uint32_t serial = 0;
-		zone_type* zone = namedb_find_zone(xfrd->nsd->db,
-				(const dname_type*)zo->node.key);
+		zone_type* zone = xfrd->nsd->db
+			? namedb_find_zone(xfrd->nsd->db,
+				(const dname_type*)zo->node.key)
+			: NULL;
 		struct xfrd_catalog_consumer_zone* consumer_zone =
 			(struct xfrd_catalog_consumer_zone*)
 			rbtree_search( xfrd->catalog_consumer_zones
@@ -1882,6 +1888,18 @@ repat_interrupt_zones(xfrd_state_type* x
 				xfrd_set_refresh_now(xz);
 			} else if(xz->zone_handler.ev_fd != -1) {
 				xfrd_udp_release(xz);
+				xfrd_set_refresh_now(xz);
+			}
+			if(xz->udp_waiting) {
+				log_msg(LOG_INFO, "udp_waiting for %s",
+					xz->apex_str);
+				udp_zone_waiting_list_remove(xz);
+				xfrd_set_refresh_now(xz);
+			}
+			if(xz->tcp_waiting) {
+				log_msg(LOG_INFO, "tcp_waiting for %s",
+					xz->apex_str);
+				tcp_zone_waiting_list_remove(xz);
 				xfrd_set_refresh_now(xz);
 			}
 			xz->master = 0;
Index: server.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/server.c,v
diff -u -p -r1.58 server.c
--- server.c	14 Jul 2026 16:20:05 -0000	1.58
+++ server.c	20 Sep 2026 09:47:57 -0000
@@ -158,6 +158,8 @@ struct udp_handler_data
 	struct event       event;
 	/* if set, PROXYv2 is expected on this connection */
 	int pp2_enabled;
+	/* if set, padding is allowed on this connection */
+	int may_pad;
 };
 
 struct tcp_accept_handler_data {
@@ -3337,11 +3339,14 @@ add_udp_handler(
 	data->nsd = nsd;
 	data->socket = sock;
 
-	if(nsd->options->proxy_protocol_port &&
-		sockaddr_uses_proxy_protocol_port(nsd->options,
-		(struct sockaddr *)&sock->addr.ai_addr)) {
+	if(sockaddr_uses_port((struct sockaddr *)&sock->addr.ai_addr,
+				nsd->options->proxy_protocol_port)) {
 		data->pp2_enabled = 1;
 	}
+	if(sockaddr_uses_port((struct sockaddr *)&sock->addr.ai_addr,
+				nsd->options->udp_padding_port)) {
+		data->may_pad = 1;
+	}
 
 	memset(handler, 0, sizeof(*handler));
 	event_set(handler, sock->s, EV_PERSIST|EV_READ, handle_udp, data);
@@ -3362,9 +3367,8 @@ add_tcp_handler(
 	data->nsd = nsd;
 	data->socket = sock;
 
-	if(nsd->options->proxy_protocol_port &&
-		sockaddr_uses_proxy_protocol_port(nsd->options,
-		(struct sockaddr *)&sock->addr.ai_addr)) {
+	if(sockaddr_uses_port((struct sockaddr *)&sock->addr.ai_addr,
+				nsd->options->proxy_protocol_port)) {
 		data->pp2_enabled = 1;
 	}
 
@@ -4060,6 +4064,35 @@ port_is_zero(
 #endif
 }
 
+/* Check if proxy is allowed */
+static int
+pp2_is_allowed(struct query* q)
+{
+	if(nsd.options->allow_proxy) {
+		struct acl_options* why = NULL;
+		if(acl_check_incoming_proxy(nsd.options->allow_proxy, q,
+			&why) == -1) {
+			if(verbosity >= 2) {
+				char proxy[128];
+				addr2str(&q->remote_addr, proxy, sizeof(proxy));
+				VERBOSITY(2, (LOG_INFO, "proxy-protocol: %s is not "
+					"in allow-proxy list", proxy));
+			}
+			return 0;
+		}
+#ifndef NDEBUG
+		/* It was allowed from acl 'why'. */
+		if(why) {
+			char proxy[128];
+			addr2str(&q->remote_addr, proxy, sizeof(proxy));
+			DEBUG(DEBUG_QUERY,1, (LOG_INFO, "proxy %s passed acl %s",
+				proxy, why->ip_address_spec));
+		}
+#endif /* NDEBUG */
+	}
+	return 1;
+}
+
 /* Parses the PROXYv2 header from buf and updates the struct.
  * Returns 1 on success, 0 on failure. */
 static int
@@ -4179,6 +4212,7 @@ handle_udp(int fd, short event, void* ar
 		queries[i]->remote_addrlen = msgs[i].msg_hdr.msg_namelen;
 		queries[i]->client_addrlen = (socklen_t)sizeof(queries[i]->client_addr);
 		queries[i]->is_proxied = 0;
+		queries[i]->may_pad = data->may_pad;
 		q = queries[i];
 		if (received == -1) {
 			log_msg(LOG_ERR, "recvmmsg %d failed %s", i, strerror(
@@ -4207,10 +4241,15 @@ handle_udp(int fd, short event, void* ar
 
 		buffer_skip(q->packet, received);
 		buffer_flip(q->packet);
-		if(data->pp2_enabled && !consume_pp2_header(q->packet, q, 0)) {
-			VERBOSITY(2, (LOG_ERR, "proxy-protocol: could not "
-				"consume PROXYv2 header"));
-			goto swap_drop;
+		if(data->pp2_enabled) {
+			if(!pp2_is_allowed(q))
+				goto swap_drop;
+			if(!consume_pp2_header(q->packet, q, 0)) {
+				VERBOSITY(6, (LOG_ERR, "proxy-protocol: could not "
+					"consume PROXYv2 header"));
+				query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
+				goto swap_drop;
+			}
 		}
 		if(!q->is_proxied) {
 			q->client_addrlen = q->remote_addrlen;
@@ -4570,6 +4609,10 @@ handle_tcp_reading(int fd, short event, 
 			return;
 		}
 		buffer_flip(data->query->packet);
+		if(!pp2_is_allowed(data->query)) {
+			cleanup_tcp_handler(data);
+			return;
+		}
 		if(!consume_pp2_header(data->query->packet, data->query, 1)) {
 			VERBOSITY(6, (LOG_ERR, "proxy-protocol: could not consume PROXYv2 header"));
 
@@ -4819,7 +4862,8 @@ handle_tcp_writing(int fd, short event, 
 		}
 
 #ifdef HAVE_WRITEV
-		sent -= sizeof(n_tcplen);
+		/* The number of bytes transmitted for the message content. */
+		sent = data->bytes_transmitted - sizeof(n_tcplen);
 		/* handle potential 'packet done' code */
 		goto packet_could_be_done;
 #endif
@@ -5184,6 +5228,10 @@ handle_tls_reading(int fd, short event, 
 			return;
 		}
 		buffer_flip(data->query->packet);
+		if(!pp2_is_allowed(data->query)) {
+			cleanup_tcp_handler(data);
+			return;
+		}
 		if(!consume_pp2_header(data->query->packet, data->query, 1)) {
 			VERBOSITY(6, (LOG_ERR, "proxy-protocol: could not consume PROXYv2 header"));
 			cleanup_tcp_handler(data);
Index: xfrd-tcp.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/xfrd-tcp.c,v
diff -u -p -r1.33 xfrd-tcp.c
--- xfrd-tcp.c	14 Jul 2026 16:20:05 -0000	1.33
+++ xfrd-tcp.c	20 Sep 2026 09:47:57 -0000
@@ -656,6 +656,23 @@ pipeline_find(struct xfrd_tcp_set* set, 
 	return r;
 }
 
+void
+tcp_zone_waiting_list_remove(xfrd_zone_type* z)
+{
+	if(z->tcp_waiting) {
+		/* delete from tcp waiting list */
+		if(z->tcp_waiting_prev)
+			z->tcp_waiting_prev->tcp_waiting_next =
+				z->tcp_waiting_next;
+		else xfrd->tcp_set->tcp_waiting_first = z->tcp_waiting_next;
+		if(z->tcp_waiting_next)
+			z->tcp_waiting_next->tcp_waiting_prev =
+				z->tcp_waiting_prev;
+		else xfrd->tcp_set->tcp_waiting_last = z->tcp_waiting_prev;
+		z->tcp_waiting = 0;
+	}
+}
+
 /* remove zone from tcp waiting list */
 static void
 tcp_zone_waiting_list_popfirst(struct xfrd_tcp_set* set, xfrd_zone_type* zone)
@@ -1100,6 +1117,8 @@ xfrd_tcp_setup_write_packet(struct xfrd_
 	struct xfrd_tcp* tcp = tp->tcp_w;
 	assert(zone->tcp_conn != -1);
 	assert(zone->tcp_waiting == 0);
+	/* make sure we have a master to query the xfr request to */
+	assert(zone->master);
 	/* start AXFR or IXFR for the zone */
 	if(zone->soa_disk_acquired == 0 || zone->master->use_axfr_only ||
 		zone->master->ixfr_disabled ||
@@ -1252,8 +1271,8 @@ int conn_write(struct xfrd_tcp* tcp)
 		}
 
 		tcp->total_bytes += sent;
-		if(sent > (ssize_t)sizeof(tcp->msglen))
-			buffer_skip(tcp->packet, sent-sizeof(tcp->msglen));
+		if(tcp->total_bytes > (ssize_t)sizeof(tcp->msglen))
+			buffer_skip(tcp->packet, tcp->total_bytes-sizeof(tcp->msglen));
 		if(tcp->total_bytes < sizeof(tcp->msglen)) {
 			/* incomplete write, resume later */
 			return 0;
Index: xfrd-tcp.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/xfrd-tcp.h,v
diff -u -p -r1.6 xfrd-tcp.h
--- xfrd-tcp.h	6 Sep 2025 17:41:37 -0000	1.6
+++ xfrd-tcp.h	20 Sep 2026 09:47:57 -0000
@@ -187,6 +187,8 @@ struct xfrd_tcp* xfrd_tcp_create(struct 
 void xfrd_tcp_obtain(struct xfrd_tcp_set* set, struct xfrd_zone* zone);
 /* release tcp connection for a zone (starts waiting) */
 void xfrd_tcp_release(struct xfrd_tcp_set* set, struct xfrd_zone* zone);
+/* remove zone from tcp waiting list */
+void tcp_zone_waiting_list_remove(struct xfrd_zone* z);
 /* release tcp pipe entirely (does not stop the zones inside it) */
 void xfrd_tcp_pipe_release(struct xfrd_tcp_set* set,
 	struct xfrd_tcp_pipeline* tp, int conn);
Index: xfrd.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/xfrd.c,v
diff -u -p -r1.37 xfrd.c
--- xfrd.c	14 Jul 2026 16:20:05 -0000	1.37
+++ xfrd.c	20 Sep 2026 09:47:57 -0000
@@ -703,6 +703,7 @@ xfrd_process_soa_info_task(struct task_l
 	zone_type* dbzone = NULL;
 	xfrd_xfr_type* xfr;
 	xfrd_xfr_type* prev_xfr;
+	int xfr_was_ixfr = 0;
 	enum soainfo_hint hint;
 #ifndef NDEBUG
 	time_t before;
@@ -825,6 +826,10 @@ xfrd_process_soa_info_task(struct task_l
 		dbzone = namedb_find_or_create_zone( xfrd->nsd->db, task->zname
 		                                   , consumer_zone->options);
 	}
+	if(zone->latest_xfr) {
+		xfr_was_ixfr = (zone->latest_xfr->query_type == TYPE_IXFR);
+	}
+
 	/* soainfo_gone and soainfo_bad are straightforward, delete all updates
 	   that were transfered, i.e. acquired != 0. soainfo_ok is more
 	   complicated as it is possible that there are subsequent corrupt or
@@ -913,6 +918,28 @@ xfrd_process_soa_info_task(struct task_l
 			break;
 		/* fall through */
 	case soainfo_gone:
+		if(hint == soainfo_gone) {
+			/* "rollback" on-disk soa information */
+			zone->soa_disk_acquired = zone->soa_nsd_acquired;
+			zone->soa_disk = zone->soa_nsd;
+		}
+		if(hint == soainfo_gone && !soa_ptr) {
+			if(xfr_was_ixfr) {
+				/* Attempt without IXFR, maybe AXFR works. */
+				xfrd_disable_ixfr(zone);
+				xfrd_set_zone_state(zone, xfrd_zone_refreshing);
+				xfrd_set_refresh_now(zone);
+				break;
+			}
+			/* The zone transfer update failed to apply.
+			 * Okay to fallback from IXFR to AXFR, but after failed
+			 * AXFR, wait for retry instead of immediate fetch */
+			VERBOSITY(2, (LOG_INFO, "xfrd: zone %s transfer "
+				"failed to apply, waiting for retry",
+				zone->apex_str));
+			xfrd_set_timer_retry(zone);
+			break;
+		}
 		xfrd_handle_incoming_soa(zone, soa_ptr, acquired);
 		break;
 	}
@@ -1019,24 +1046,8 @@ xfrd_deactivate_zone(xfrd_zone_type* z)
 }
 
 void
-xfrd_del_slave_zone(xfrd_state_type* xfrd, const dname_type* dname)
+udp_zone_waiting_list_remove(xfrd_zone_type* z)
 {
-	xfrd_zone_type* z = (xfrd_zone_type*)rbtree_delete(xfrd->zones, dname);
-	if(!z) return;
-	
-	/* io */
-	if(z->tcp_waiting) {
-		/* delete from tcp waiting list */
-		if(z->tcp_waiting_prev)
-			z->tcp_waiting_prev->tcp_waiting_next =
-				z->tcp_waiting_next;
-		else xfrd->tcp_set->tcp_waiting_first = z->tcp_waiting_next;
-		if(z->tcp_waiting_next)
-			z->tcp_waiting_next->tcp_waiting_prev =
-				z->tcp_waiting_prev;
-		else xfrd->tcp_set->tcp_waiting_last = z->tcp_waiting_prev;
-		z->tcp_waiting = 0;
-	}
 	if(z->udp_waiting) {
 		/* delete from udp waiting list */
 		if(z->udp_waiting_prev)
@@ -1049,6 +1060,23 @@ xfrd_del_slave_zone(xfrd_state_type* xfr
 		else	xfrd->udp_waiting_last = z->udp_waiting_prev;
 		z->udp_waiting = 0;
 	}
+}
+
+void
+xfrd_del_slave_zone(xfrd_state_type* xfrd, const dname_type* dname)
+{
+	xfrd_zone_type* z = (xfrd_zone_type*)rbtree_delete(xfrd->zones, dname);
+	if(!z) return;
+
+	/* io */
+	if(z->tcp_waiting) {
+		/* delete from tcp waiting list */
+		tcp_zone_waiting_list_remove(z);
+	}
+	if(z->udp_waiting) {
+		/* delete from udp waiting list */
+		udp_zone_waiting_list_remove(z);
+	}
 	xfrd_deactivate_zone(z);
 	if(z->tcp_conn != -1) {
 		xfrd_tcp_release(xfrd->tcp_set, z);
@@ -1251,6 +1279,16 @@ xfrd_make_request(xfrd_zone_type* zone)
 		if(zone->round_num >= XFRD_MAX_ROUNDS) {
 			/* tried all servers that many times, wait */
 			zone->round_num = -1;
+			/* Discard NOTIFY serial hint. After one round of
+			 * searching for it, at the upstream primaries, the
+			 * notify hint need no longer be used. The retry
+			 * timer is used, for one, earlier re-attempt. This
+			 * is useful if the upstream is in-progress of loading
+			 * the zone information. After the brief wait it may
+			 * have completed that task. And this may catch the
+			 * case where it notify is sent before the load
+			 * activity has completed at the primary. */
+			zone->soa_notified_acquired = 0;
 			xfrd_set_timer_retry(zone);
 			DEBUG(DEBUG_XFRD,1, (LOG_INFO,
 				"xfrd zone %s makereq wait_retry, rd %d mr %d nx %d",
@@ -2674,6 +2712,7 @@ xfrd_handle_received_xfr_packet(xfrd_zon
 			zone->latest_xfr->xfrfilenumber))
 	{
 		zone->latest_xfr->sent = xfrd->nsd->mytask + 1;
+		xfrd->num_xfrs_in_reload++;
 	}
 	/* reset msg seq nr, so if that is nonnull we know xfr file exists */
 	zone->latest_xfr->msg_seq_nr = 0;
@@ -2994,6 +3033,8 @@ xfrd_prepare_zones_for_reload(void)
 					xfr->msg_old_serial,
 					xfr->msg_new_serial,
 					xfr->xfrfilenumber);
+				if(send)
+					xfrd->num_xfrs_in_reload++;
 				if(send && !reload) {
 					reload = 1;
 					xfrd_set_reload_timeout();
Index: xfrd.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/xfrd.h,v
diff -u -p -r1.16 xfrd.h
--- xfrd.h	14 Jul 2026 16:20:05 -0000	1.16
+++ xfrd.h	20 Sep 2026 09:47:57 -0000
@@ -85,6 +85,8 @@ struct xfrd_state {
 	uint8_t reload_failed;
 	uint8_t can_send_reload;
 	pid_t reload_pid;
+	int num_reload_failed_repeat;
+	int num_xfrs_in_reload;
 	/* timeout for lost sigchild and reaping children */
 	struct event child_timer;
 	int child_timer_added;
@@ -389,6 +391,8 @@ void xfrd_set_timer(xfrd_zone_type* zone
 void xfrd_set_refresh_now(xfrd_zone_type* zone);
 /* unset the timer - no more timeouts, for when zone is queued */
 void xfrd_unset_timer(xfrd_zone_type* zone);
+/* remove the zone from the udp waiting list */
+void udp_zone_waiting_list_remove(xfrd_zone_type* z);
 /* remove the 'refresh now', remove it from the activated list */
 void xfrd_deactivate_zone(xfrd_zone_type* z);
 
Index: dnstap/dnstap.c
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/dnstap/dnstap.c,v
diff -u -p -r1.7 dnstap.c
--- dnstap/dnstap.c	3 Sep 2025 18:46:48 -0000	1.7
+++ dnstap/dnstap.c	20 Sep 2026 09:47:57 -0000
@@ -353,27 +353,37 @@ dt_tls_writer_open(void* obj)
 	}
 	if(connect(dtw->fd, (struct sockaddr*)&addr, addrlen) < 0) {
 		log_msg(LOG_ERR, "dnstap: connect failed: %s", strerror(errno));
+		close(dtw->fd);
+		dtw->fd = -1;
 		return fstrm_res_failure;
 	}
-	dtw->connected = 1;
 
 	/* setup SSL */
 	dtw->ssl = SSL_new(dtw->ctx);
 	if(!dtw->ssl) {
 		log_msg(LOG_ERR, "dnstap: SSL_new failed");
+		close(dtw->fd);
+		dtw->fd = -1;
 		return fstrm_res_failure;
 	}
 	SSL_set_connect_state(dtw->ssl);
 	(void)SSL_set_mode(dtw->ssl, SSL_MODE_AUTO_RETRY);
 	if(!SSL_set_fd(dtw->ssl, dtw->fd)) {
 		log_msg(LOG_ERR, "dnstap: SSL_set_fd failed");
+res_failure:
+		if(dtw->ssl)
+			SSL_shutdown(dtw->ssl);
+		SSL_free(dtw->ssl);
+		dtw->ssl = NULL;
+		close(dtw->fd);
+		dtw->fd = -1;
 		return fstrm_res_failure;
 	}
 	if(dtw->tls_server_name && dtw->tls_server_name[0]) {
 		if(!SSL_set1_host(dtw->ssl, dtw->tls_server_name)) {
 			log_msg(LOG_ERR, "dnstap: TLS setting of hostname %s failed to %s",
 				dtw->tls_server_name, dtw->ip);
-			return fstrm_res_failure;
+			goto res_failure;
 		}
 	}
 
@@ -387,14 +397,14 @@ dt_tls_writer_open(void* obj)
 		if(r != SSL_ERROR_WANT_READ && r != SSL_ERROR_WANT_WRITE) {
 			if(r == SSL_ERROR_ZERO_RETURN) {
 				log_msg(LOG_ERR, "dnstap: EOF on SSL_do_handshake");
-				return fstrm_res_failure;
+				goto res_failure;
 			}
 			if(r == SSL_ERROR_SYSCALL) {
 				log_msg(LOG_ERR, "dnstap: SSL_do_handshake failed: %s", strerror(errno));
-				return fstrm_res_failure;
+				goto res_failure;
 			}
 			log_crypto_err("dnstap: SSL_do_handshake failed");
-			return fstrm_res_failure;
+			goto res_failure;
 		}
 		/* wants to be called again */
 	}
@@ -402,7 +412,7 @@ dt_tls_writer_open(void* obj)
 	/* check authenticity of server */
 	if(SSL_get_verify_result(dtw->ssl) != X509_V_OK) {
 		log_crypto_err("SSL verification failed");
-		return fstrm_res_failure;
+		goto res_failure;
 	}
 #ifdef HAVE_SSL_GET1_PEER_CERTIFICATE
 	x = SSL_get1_peer_certificate(dtw->ssl);
@@ -411,9 +421,10 @@ dt_tls_writer_open(void* obj)
 #endif
 	if(!x) {
 		log_crypto_err("Server presented no peer certificate");
-		return fstrm_res_failure;
+		goto res_failure;
 	}
 	X509_free(x);
+	dtw->connected = 1;
 
 	return fstrm_res_success;
 }
Index: doc/ChangeLog
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/doc/ChangeLog,v
diff -u -p -r1.23 ChangeLog
--- doc/ChangeLog	14 Jul 2026 16:20:05 -0000	1.23
+++ doc/ChangeLog	20 Sep 2026 09:47:57 -0000
@@ -1,8 +1,142 @@
+28 August 2026: Willem
+	- Fix client certificate checking by linking the tls-auth-name
+	  requirement on an access control list item, to the other requirements
+	  (IP address and/or TSIG key) on the same list item.
+	  Thanks to voguemerry for the report
+
+24 August 2026: Willem
+	- Merge #502: An `udp-padding-port` configuration file option to enable
+	  responding to the padding option on UDP, but on specific ports only.
+	  This is convenient for setups where NSD is behind a frontend handling
+	  the DNS over TLS.
+	- `--enable-multiple-catalog-zones` option to configure to enable
+	  experimental support for multiple catalog consumer zones.
+
+19 August 2026: Willem
+	- Continue with 4.15.2 for development
+
+17 August 2026: Willem
+	- Fix for CVE-2026-18664: IP range access control restrictions are
+	  bypassed for some unintended IP.
+	  Thanks to Qifan Zhang, Palo Alto Networks for the report
+	  https://www.nlnetlabs.nl/downloads/nsd/CVE-2026-18664.txt
+	- Fix for CVE-2026-18916: Any remote client can denial TCP service by
+	  throttling the TCP receive window (down to 1).
+	  Thanks to Qifan Zhang, Palo Alto Networks for the report
+	  https://www.nlnetlabs.nl/downloads/nsd/CVE-2026-18916.txt
+	- Fix for CVE-2026-19401: Any remote client can denial UDP service by
+	  sending a specifically crafted query with multiple DNS Cookie options.
+	  Thanks to Qifan Zhang, Palo Alto Networks for the report
+	  https://www.nlnetlabs.nl/downloads/nsd/CVE-2026-19401.txt
+	- Fix for CVE-2026-19538: Anyone with access to the proxy protocol port
+	  over TCP or TLS can bypass BLOCKED access control items.
+	  Thanks to Qifan Zhang, Palo Alto Networks for the report
+	  https://www.nlnetlabs.nl/downloads/nsd/CVE-2026-19538.txt
+
+14 August 2026: Willem
+	- Fix to ignore NSEC3 records with malformed owner name. In depth fix
+	  to clear key for NSEC3 tree collisions. And a fix to limit the
+	  reenactment of failed reloads, that ordinarily marks transfers as
+	  corrupt, so that for crashes it waits with that zone for retry. This
+	  allows other zones to update in the meantime.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix nsd-control reconfig so that change of a secondary zone while it
+	  is on the udp or tcp waiting list does not crash after that.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to reset the length of the incoming packet buffer after a failed
+	  PROXYv2 header on UDP. It can cause an assertion failure on the next
+	  packet.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix XoT transfer authentication to not fall back to the common name
+	  if a DNS name is present in the certificate. This fixes the RFC6125
+	  conformance.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix dnstap over TLS certificate check for reconnection. Also clean up
+	  file descriptor and SSL state on connection failures.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+
+3 August 2026: Willem
+	- An `allow-proxy` configuration file option to specify allow access
+	  control list for the PROXYv2 protocol port.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the suggestion.
+	- Fix to prevent a configured primary from hampering loading its own
+	  zone by providing a transfer with the OPT RR in the answer section
+	  and the UDP Payload size (i.e. class) of the OPT RR set to something
+	  else than 1 (IN).
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+
+29 July 2026: Wouter
+	- Fix that when DNAME is limited due to the maximum CNAME chain
+	  length, it copies the final DNAME with its CNAME into the
+	  packet. Also fix unit test for limit on CNAME chain length.
+
+16 July 2026: Willem
+	- Merge #500: Support for the "oots" SVCB Service Parameter Key
+
+15 July 2026: Willem
+	- Merge #498: Support for the HHIT and BRID RR types
+	- Merge #499: Support for the "docpath" and "pvd"
+	  SVCB Service Parameter Keys
+
+14 July 2026: Willem
+	- Fix that IXFR queries with overly large SOA records do not cause an
+	  assertion failure in the server child process. The malformed SOA
+	  record is rejected with FORMERR.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix that notify ahead of the zone serial does not cause perpetual
+	  retransfer. After one round, and after another retry, it gives up,
+	  and forgets the notified serial number. This allows the primary a
+	  moment to retrieve the new zone version.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix that AXFR out for a zone with an oversize record does not loop.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix that IXFR out for a zone with an oversize record does not loop.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix serve childs crashing when queried for a very long CNAME chain
+	  on ASAN builds. The maximum CNAME chain in responses is set to 18.
+	  The value can be configured with the --with-max-cname-chain=number
+	  option to configure.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to have failed zone transfers retry after a time, instead of in a
+	  loop, for transfers with failed contents.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix that an increase verbosity level is needed to warn about unfound
+	  RRs to be deleted (from IXFR). Verbosity 2 for unfound RRsets and
+	  unfound RDATA. Verbosity 3 to log which RR in the existing set does
+	  not match.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to ignore NSEC3s with wrong non-base32hex chars in owner label.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix Y2038 bug, where the server process would fail with undefined
+	  behaviour, if it was built with address sanitizer undefined checks
+	  enabled, for the timestamp in EDNS cookies.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+	- Fix to not let `nsd-control zonestatus` crash the xfrd processes
+	  when configured with multiple catalog consumers.
+	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+
+13 July 2026: Wouter
+	- Merge #496 from maertsen: Fix allow-query syntax in catalog
+	  documentation.
+
+8 July 2026: Willem
+	- Merge #494: RFC 7830 EDNS0 padding option support with DoT
+	  With padding suggestions from RFC 8467 for responses
+	  Thanks kdrenard for the suggestion
+
+7 July 2026: Willem
+	- Merge the NSD 4.15.0 release into master.
+	- Continue with 4.15.1 for development
+
+7 July 2026: Wouter
+	- (part of 4.15.1):
+	- Merge #495 from ruuda: Include inttypes.h from metrics.c.
+
 29 June 2026: Jannik
 	- Merge #483 from ruuda: Improve Prometheus metrics: Move zonestats from
 	  metric name to label
 
-26 June 2026: Willem
+2g June 2026: Willem
 	- Fix nsd-control assoc_tsig, if that interrupts a zone transfer in
 	  progress, to not crash. It restarts the transfer from the primary.
 	  Thanks to Qifan Zhang, Palo Alto Networks, for the report.
Index: doc/README
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/doc/README,v
diff -u -p -r1.13 README
--- doc/README	14 Jul 2026 16:20:05 -0000	1.13
+++ doc/README	20 Sep 2026 09:47:57 -0000
@@ -21,7 +21,7 @@
 
 1.0 Introduction
 
-This is NSD Name Server Daemon (NSD) version 4.15.0.
+This is NSD Name Server Daemon (NSD) version 4.15.2.
 
 The NLnet Labs Name Server Daemon (NSD) is an authoritative RFC compliant 
 DNS nameserver. It was first conceived to allow for more genetic 
@@ -57,7 +57,7 @@ and uses a simple configuration file 'ns
 
 1.2 Quick build and install
 
-Step 1: Unpack the source with gtar -xzvf nsd-4.15.0.tar.gz
+Step 1: Unpack the source with gtar -xzvf nsd-4.15.2.tar.gz
 
 Step 2: Create user nsd or any other unprivileged user of your
         choice. In case of later make sure to use
@@ -111,9 +111,9 @@ Step 11: If desired add 'nsd-control wri
 Use your favorite combination of tar and gnu zip to unpack the source,
 for example
 
-$ gtar -xzvf nsd-4.15.0.tar.gz
+$ gtar -xzvf nsd-4.15.2.tar.gz
 
-will unpack the source into the ./nsd-4.15.0 directory...
+will unpack the source into the ./nsd-4.15.2 directory...
 
 
 2.2 Configuring NSD
Index: simdzone/include/zone.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/simdzone/include/zone.h,v
diff -u -p -r1.1.1.2 zone.h
--- simdzone/include/zone.h	6 Sep 2025 17:38:34 -0000	1.1.1.2
+++ simdzone/include/zone.h	20 Sep 2026 09:47:57 -0000
@@ -185,8 +185,12 @@ extern "C" {
 #define ZONE_TYPE_SVCB (64u)
 /** Service binding @rfc{9460} */
 #define ZONE_TYPE_HTTPS (65u)
-/** Endpoint discovery for delegation synchronization @draft{ietf, dnsop-generalized-notify} */
+/** Endpoint discovery for delegation synchronization @rfc{9859]} */
 #define ZONE_TYPE_DSYNC (66u)
+/** Hierarchical Host Identity Tag @rfc{9886} */
+#define ZONE_TYPE_HHIT (67u)
+/** UAS Broadcast Remote Identification @rfc{9886} */
+#define ZONE_TYPE_BRID (68u)
 /** Sender Policy Framework @rfc{7208} */
 #define ZONE_TYPE_SPF (99u)
 /** Node Identifier @rfc{6742} */
@@ -255,6 +259,12 @@ extern "C" {
 #define ZONE_SVC_PARAM_KEY_OHTTP (8u)
 /** Supported groups in TLS @draft{ietf, tls-key-share-prediction} */
 #define ZONE_SVC_PARAM_KEY_TLS_SUPPORTED_GROUPS (9u)
+/** DNS over CoAP resource path @rfc{9953} */
+#define ZONE_SVC_PARAM_KEY_DOCPATH (10u)
+/** PvD configuration is available at the well-known path @draft{ietf, intarea-proxy-config} */
+#define ZONE_SVC_PARAM_KEY_PVD (11u)
+/** Per-transport operator confidence in serving the nameserver's query load over that transport, as a percentage @draft{johani, dnsop-svcb-oots} */
+#define ZONE_SVC_PARAM_KEY_OOTS (12u)
 /** Reserved ("invalid key") @rfc{9460} */
 #define ZONE_SVC_PARAM_KEY_INVALID_KEY (65535u)
 /** @} */
@@ -366,7 +376,7 @@ struct zone_file {
   /** vector of tokens generated by the scanner guaranteed to be large
       enough to hold every token for a single read + terminators */
   struct { const char **head, **tail, *tape[ZONE_TAPE_SIZE + 2]; } fields;
-  struct { const char **head, **tail, *tape[ZONE_TAPE_SIZE + 1]; } delimiters;
+  struct { const char **head, **tail, *tape[ZONE_TAPE_SIZE + 2]; } delimiters;
   struct { uint16_t *head, *tail, tape[ZONE_TAPE_SIZE + 1]; } newlines;
 };
 
Index: simdzone/src/fallback/scanner.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/simdzone/src/fallback/scanner.h,v
diff -u -p -r1.1.1.1 scanner.h
--- simdzone/src/fallback/scanner.h	3 Sep 2025 18:44:23 -0000	1.1.1.1
+++ simdzone/src/fallback/scanner.h	20 Sep 2026 09:47:57 -0000
@@ -144,7 +144,7 @@ static really_inline int32_t reindex(par
   if (left >= ZONE_BLOCK_SIZE) {
     const char *data_limit = parser->file->buffer.data +
                             (parser->file->buffer.length - ZONE_BLOCK_SIZE);
-    while (data <= data_limit && ((uintptr_t)tape_limit - (uintptr_t)tape) >= ZONE_BLOCK_SIZE) {
+    while (data <= data_limit && tape+ZONE_BLOCK_SIZE <= tape_limit) {
       scan(parser, data, data + ZONE_BLOCK_SIZE);
       parser->file->buffer.index += ZONE_BLOCK_SIZE;
       data += ZONE_BLOCK_SIZE;
@@ -156,11 +156,10 @@ static really_inline int32_t reindex(par
   }
 
   // only scan partial blocks after reading all data
-  if (parser->file->end_of_file) {
-    assert(left < ZONE_BLOCK_SIZE);
+  if (parser->file->end_of_file && left < ZONE_BLOCK_SIZE) {
     if (!left) {
       parser->file->end_of_file = NO_MORE_DATA;
-    } else if (((uintptr_t)tape_limit - (uintptr_t)tape) >= left) {
+    } else if(tape+left <= tape_limit) {
       scan(parser, data, data + left);
       parser->file->end_of_file = NO_MORE_DATA;
       parser->file->buffer.index += left;
Index: simdzone/src/generic/scanner.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/simdzone/src/generic/scanner.h,v
diff -u -p -r1.1.1.1 scanner.h
--- simdzone/src/generic/scanner.h	3 Sep 2025 18:44:23 -0000	1.1.1.1
+++ simdzone/src/generic/scanner.h	20 Sep 2026 09:47:57 -0000
@@ -300,7 +300,7 @@ static really_inline int32_t reindex(par
   if (left >= ZONE_BLOCK_SIZE) {
     const char *data_limit = parser->file->buffer.data +
                             (parser->file->buffer.length - ZONE_BLOCK_SIZE);
-    while (data <= data_limit && ((uintptr_t)tape_limit - (uintptr_t)tape) >= ZONE_BLOCK_SIZE) {
+   while (data <= data_limit && tape+ZONE_BLOCK_SIZE <= tape_limit) {
       simd_loadu_8x64(&block.input, (const uint8_t *)data);
       scan(parser, &block);
       write_indexes(parser, &block, 0);
@@ -314,11 +314,10 @@ static really_inline int32_t reindex(par
   }
 
   // only scan partial blocks after reading all data
-  if (parser->file->end_of_file) {
-    assert(left < ZONE_BLOCK_SIZE);
+  if(parser->file->end_of_file && left < ZONE_BLOCK_SIZE) {
     if (!left) {
       parser->file->end_of_file = NO_MORE_DATA;
-    } else if (((uintptr_t)tape_limit - (uintptr_t)tape) >= left) {
+    } else if (tape+left <= tape_limit) {
       // input is required to be padded, but may contain garbage
       uint8_t buffer[ZONE_BLOCK_SIZE] = { 0 };
       memcpy(buffer, data, left);
Index: simdzone/src/generic/svcb.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/simdzone/src/generic/svcb.h,v
diff -u -p -r1.1.1.2 svcb.h
--- simdzone/src/generic/svcb.h	14 Jul 2026 16:19:06 -0000	1.1.1.2
+++ simdzone/src/generic/svcb.h	20 Sep 2026 09:47:57 -0000
@@ -420,6 +420,89 @@ static int32_t parse_tls_supported_group
 }
 
 nonnull_all
+static int32_t parse_docpath(
+  parser_t *parser,
+  const type_info_t *type,
+  const rdata_info_t *field,
+  uint16_t key,
+  const svc_param_info_t *param,
+  rdata_t *rdata,
+  const token_t *token)
+{
+  return parse_alpn(parser, type, field, key, param, rdata, token);
+}
+
+nonnull_all
+static int32_t parse_oots(
+  parser_t *parser,
+  const type_info_t *type,
+  const rdata_info_t *field,
+  uint16_t key,
+  const svc_param_info_t *param,
+  rdata_t *rdata,
+  const token_t *token)
+{
+  const char *t = token->data, *te = token->data + token->length;
+  const uint8_t *rdata_start = rdata->octets;
+
+  (void)field;
+  (void)key;
+  (void)param;
+
+  while (t < te) {
+    const char *transport = t;
+    const uint8_t *transport_out = rdata->octets;
+    const char *colon = memchr(t, ':', (size_t)(te - t));
+
+    if (!colon)
+      SYNTAX_ERROR(parser, "No colon found in oots DNS transport in %s", NAME(type));
+
+    if(colon == t)
+      SYNTAX_ERROR(parser, "DNS transport name must have at least 1 character in %s", NAME(type));
+
+    if (rdata->octets + (colon - t) + 2 > rdata->limit)
+      SYNTAX_ERROR(parser, "No space for oots DNS transport in %s", NAME(type));
+
+    if (colon - t > 255)
+      SYNTAX_ERROR(parser, "DNS transport name too large in %s", NAME(type));
+
+    *rdata->octets++ = (uint8_t)(colon - t);
+    memcpy(rdata->octets, transport, (size_t)(colon - t));
+    rdata->octets += (colon - t);
+
+    t = colon + 1;
+    uint64_t number = 0;
+    for (;; t++) {
+      const uint64_t digit = (uint8_t)*t - '0';
+      if (digit > 9)
+        break;
+      number = number * 10 + digit;
+    }
+    if(t == colon +1)
+      SYNTAX_ERROR(parser, "Oots percentage missing in %s", NAME(type));
+    if (number > 100)
+      SYNTAX_ERROR(parser, "Invalid oots percentage in %s", NAME(type));
+
+    *rdata->octets++ = (uint8_t)number;
+
+    const uint8_t *g;
+    for (g = rdata_start; g < transport_out; g += ((size_t)(*g)) + 2) {
+      if (memcmp(g, transport_out, ((size_t)(*transport_out)) + 1) == 0)
+        SEMANTIC_ERROR(parser, "Duplicate DNS transport in oots in %s", NAME(type));
+    }
+    if (*t != ',')
+      break;
+    else
+      t++;
+  }
+
+  if (t != te || rdata->octets > rdata->limit)
+    SYNTAX_ERROR(parser, "Invalid oots in %s", NAME(type));
+  return 0;
+}
+
+
+nonnull_all
 static int32_t parse_mandatory_lax(
   parser_t *parser,
   const type_info_t *type,
@@ -481,6 +564,12 @@ static const svc_param_info_t svc_params
   // draft-ietf-tls-key-share-prediction-01 section 3.1
   SVC_PARAM("tls-supported-groups", 9u, MANDATORY_VALUE,
             parse_tls_supported_groups, parse_tls_supported_groups),
+  // RFC 9953 section 5:
+  SVC_PARAM("docpath", 10u, OPTIONAL_VALUE, parse_docpath, parse_docpath),
+  // draft-ietf-intarea-proxy-config-13 section 2.1:
+  SVC_PARAM("pvd", 11u, NO_VALUE, parse_unknown, parse_unknown),
+  // draft-johani-dnsop-svcb-oots
+  SVC_PARAM("oots", 12u, MANDATORY_VALUE, parse_oots, parse_oots),
 };
 
 static const svc_param_info_t unknown_svc_param =
@@ -546,6 +635,12 @@ static really_inline size_t scan_svc_par
     return (void)(*param = &svc_params[(*key = ZONE_SVC_PARAM_KEY_OHTTP)]), 5;
   else if (memcmp(data, "tls-supported-groups", 20) == 0)
     return (void)(*param = &svc_params[(*key = ZONE_SVC_PARAM_KEY_TLS_SUPPORTED_GROUPS)]), 20;
+  else if (memcmp(data, "docpath", 7) == 0)
+    return (void)(*param = &svc_params[(*key = ZONE_SVC_PARAM_KEY_DOCPATH)]), 7;
+  else if (memcmp(data, "pvd", 3) == 0)
+    return (void)(*param = &svc_params[(*key = ZONE_SVC_PARAM_KEY_PVD)]), 3;
+  else if (memcmp(data, "oots", 4) == 0)
+    return (void)(*param = &svc_params[(*key = ZONE_SVC_PARAM_KEY_OOTS)]), 4;
   else if (memcmp(data, "key", 3) == 0)
     return scan_unknown_svc_param_key(data, key, param);
   else
Index: simdzone/src/generic/type.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/simdzone/src/generic/type.h,v
diff -u -p -r1.1.1.2 type.h
--- simdzone/src/generic/type.h	6 Sep 2025 17:38:34 -0000	1.1.1.2
+++ simdzone/src/generic/type.h	20 Sep 2026 09:47:57 -0000
@@ -18,38 +18,38 @@ static const struct {
   const mnemonic_t *mnemonic;
   int32_t code;
 } types_and_classes[256] = {
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(34),   V(0),
-    V(0),   V(0),  T(30),   V(0),   V(0),  T(57),   V(0),  T(16),
-    V(0),   V(0),  T(56),  T(14),  T(12),   V(0),   V(0),  T(13),
-   T(61),   V(0), T(105),   V(0),   V(0),   V(0),  T(32), T(258),
-    V(0), T(107),  T(47),   V(0),   V(0),   V(0),  T(17),   V(0),
-  T(257),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),
-   T(65),   V(0),   V(0),  T(18),   V(0),   T(1),   V(0), T(263),
-    V(0),   V(0),   V(0),   V(0),  T(51),   V(0),   V(0), T(106),
-    T(3),   V(0),   V(0),  T(31),   V(0),   V(0),   V(0),   V(0),
-    V(0),  T(50),  T(44), T(104),  T(10),   V(0),   V(0),   V(0),
-    V(0),   V(0),  T(55),   V(0),  T(28),   V(0),   V(0),   V(0),
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(39),
-   T(35),   V(0),   V(0),   T(5),  T(29), T(262),   V(0),   V(0),
-  T(109),   V(0), T(264),   V(0),   V(0),   V(0),   V(0),   V(0),
-    V(0),  T(21),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),
-   T(37),   C(1),  T(58),   V(0),   V(0),   V(0),   V(0),   V(0),
-    V(0),   V(0),   V(0),   C(3),   V(0),  T(52),  T(11),  T(20),
-    V(0), T(261),   V(0),   V(0),   V(0),  T(48),   V(0),   V(0),
-    V(0),  T(25),   C(2),  T(43),   V(0),   V(0),   C(4),  T(60),
-    V(0),   V(0),   T(7),   T(2),   V(0),   V(0),  T(46),  T(22),
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(64),
-    V(0), T(260),   V(0),   V(0),   V(0),   V(0),  T(38),   V(0),
-    V(0), T(259),  T(59),   V(0),   V(0),   V(0),  T(42),  T(36),
-    T(8),  T(15),   V(0),  T(26),  T(27),   T(6),   V(0),  T(99),
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(53),
-    T(9),  T(63),  T(33),   V(0), T(271), T(270),   V(0),  T(40),
-    V(0),   V(0),  T(24),  T(19),   V(0),   V(0),   V(0),   V(0),
-    V(0),   V(0),   V(0), T(108),   V(0),   V(0),   V(0),  T(62),
-    V(0),   V(0),   V(0),   V(0),   V(0),  T(66),   T(4),   V(0),
-    V(0),   V(0), T(256),   V(0),  T(49),   V(0),   V(0),   V(0),
-    V(0),   V(0),  T(45),   V(0),   V(0),  T(23),   V(0),   V(0),
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0)
+    V(0),   V(0),  T(58),  T(66),   V(0),   V(0),  T(65),   V(0),
+    V(0),  T(53),   V(0),   V(0),  T(67), T(108),   V(0),   V(0),
+    V(0),   T(4),   V(0),   V(0),   V(0),  T(49),   V(0),   V(0),
+    V(0),   V(0),   V(0),   V(0),   V(0),  T(31),   V(0),   V(0),
+    V(0),   V(0),   V(0),  T(28), T(104),   V(0), T(257),   V(0),
+    V(0),   V(0),  T(27),   V(0),   V(0),   V(0),   V(0),  T(48),
+    V(0),   V(0),   V(0),   V(0),   V(0),   T(1),   V(0),  T(47),
+    V(0),   V(0),  T(34),   V(0), T(263),   V(0), T(106),   V(0),
+    V(0),   V(0),   V(0),   V(0),   V(0),  T(35),   V(0),  T(25),
+    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(56),
+   T(14), T(260), T(107),   V(0),  T(13),  T(68),   V(0),  T(17),
+    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(50),   V(0),
+    V(0), T(109),  T(52), T(258),   V(0),   V(0),  T(45),   V(0),
+  T(264),   V(0),   T(3),   V(0),   V(0),  T(19),   V(0),   V(0),
+    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),
+   T(61),   V(0),   V(0),  T(42),   V(0),   V(0),   V(0),  T(40),
+    V(0),   V(0),  T(24),  T(60),   V(0),   V(0),  T(55),   V(0),
+    V(0),  T(12),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),
+    V(0),   V(0),  T(11),   V(0),   V(0),   V(0),   V(0),   V(0),
+    V(0),   V(0),  T(37),   V(0),  T(46),  T(23),   V(0), T(262),
+   T(64),   V(0), T(261),   V(0),  T(29),  T(21),   V(0),   V(0),
+    V(0),   C(1),   V(0),   V(0),   V(0),  T(10),  T(57),   C(3),
+  T(271),  T(33),   V(0),   V(0),   V(0),   V(0),   V(0), T(259),
+   T(18),   V(0),  T(59),  T(30),   V(0),   V(0),   V(0),   V(0),
+   T(16),   V(0),   V(0),   T(7),   T(6),   C(2),  T(43),  T(20),
+    V(0),   C(4),   V(0),   V(0),   V(0),   V(0),   T(2),  T(38),
+   T(22),  T(51),   V(0),   V(0),   V(0),   V(0),   V(0),  T(99),
+    V(0), T(256),   V(0),   V(0),   V(0), T(105),   V(0),   V(0),
+   T(44),  T(39),   V(0),   V(0),   T(8),   V(0),   T(5),   V(0),
+    V(0),   V(0),   V(0),   V(0),   V(0),  T(36),  T(32),  T(15),
+    V(0),  T(26),   V(0),   V(0),   V(0), T(270),   V(0),   V(0),
+    V(0),   V(0),   T(9),   V(0),  T(63),   V(0),  T(62),   V(0)
 };
 
 #undef V
@@ -110,7 +110,7 @@ static really_inline uint8_t hash(uint64
   prefix = le64toh(prefix);
   uint32_t value = (uint32_t)((prefix >> 32) ^ prefix);
   // magic value is generated using hash.c, rerun when adding types
-  return (uint8_t)((value * 3537259401ull) >> 32);
+  return (uint8_t)((value * 3547541308ull) >> 32);
 }
 
 nonnull_all
Index: simdzone/src/generic/types.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/simdzone/src/generic/types.h,v
diff -u -p -r1.1.1.3 types.h
--- simdzone/src/generic/types.h	21 Mar 2026 21:34:33 -0000	1.1.1.3
+++ simdzone/src/generic/types.h	20 Sep 2026 09:47:57 -0000
@@ -2340,6 +2340,55 @@ static int32_t parse_dsync_rdata(
 }
 
 nonnull_all
+static int32_t check_hhit_rr(
+  parser_t *parser, const type_info_t *type, const rdata_t *rdata)
+{
+  // FIXME: It is a CBOR blob with some internal structure, but ignoring the
+  //        internal structure, a minimally sized CBOR blob is 1 byte
+  if ((uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets < 1)
+    SYNTAX_ERROR(parser, "Invalid %s", NAME(type));
+  return accept_rr(parser, type, rdata);
+}
+
+nonnull_all
+static int32_t parse_hhit_rdata(
+  parser_t *parser, const type_info_t *type, rdata_t *rdata, token_t *token)
+{
+  int32_t code;
+  const rdata_info_t *fields = type->rdata.fields;
+
+  if ((code = parse_base64_sequence(parser, type, &fields[0], rdata, token)) < 0)
+    return code;
+
+  return check_hhit_rr(parser, type, rdata);
+}
+
+nonnull_all
+static int32_t check_brid_rr(
+  parser_t *parser, const type_info_t *type, const rdata_t *rdata)
+{
+  // FIXME: It is a CBOR blob with some internal structure, but ignoring the
+  //        internal structure, a minimally sized CBOR blob is 1 byte
+  if ((uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets < 1)
+    SYNTAX_ERROR(parser, "Invalid %s", NAME(type));
+  return accept_rr(parser, type, rdata);
+}
+
+nonnull_all
+static int32_t parse_brid_rdata(
+  parser_t *parser, const type_info_t *type, rdata_t *rdata, token_t *token)
+{
+  int32_t code;
+  const rdata_info_t *fields = type->rdata.fields;
+
+  if ((code = parse_base64_sequence(parser, type, &fields[0], rdata, token)) < 0)
+    return code;
+
+  return check_brid_rr(parser, type, rdata);
+}
+
+
+nonnull_all
 static int32_t check_nid_rr(
   parser_t *parser, const type_info_t *type, const rdata_t *rdata)
 {
@@ -3281,6 +3330,14 @@ static const rdata_info_t dsync_rdata_fi
   FIELD("target")
 };
 
+static const rdata_info_t hhit_rdata_fields[] = {
+  FIELD("cbor blob"),
+};
+
+static const rdata_info_t brid_rdata_fields[] = {
+  FIELD("cbor blob"),
+};
+
 static const rdata_info_t spf_rdata_fields[] = {
   FIELD("text")
 };
@@ -3526,8 +3583,10 @@ static const type_info_t types[] = {
                 check_https_rr, parse_https_rdata),
   TYPE("DSYNC", ZONE_TYPE_DSYNC, ZONE_CLASS_ANY, FIELDS(dsync_rdata_fields),
                 check_dsync_rr, parse_dsync_rdata),
-  UNKNOWN_TYPE(67),
-  UNKNOWN_TYPE(68),
+  TYPE("HHIT", ZONE_TYPE_HHIT, ZONE_CLASS_ANY, FIELDS(hhit_rdata_fields),
+                check_hhit_rr, parse_hhit_rdata),
+  TYPE("BRID", ZONE_TYPE_BRID, ZONE_CLASS_ANY, FIELDS(brid_rdata_fields),
+                check_brid_rr, parse_brid_rdata),
   UNKNOWN_TYPE(69),
   UNKNOWN_TYPE(70),
   UNKNOWN_TYPE(71),
Index: simdzone/src/westmere/type.h
===================================================================
RCS file: /cvs/src/usr.sbin/nsd/simdzone/src/westmere/type.h,v
diff -u -p -r1.1.1.2 type.h
--- simdzone/src/westmere/type.h	6 Sep 2025 17:38:34 -0000	1.1.1.2
+++ simdzone/src/westmere/type.h	20 Sep 2026 09:47:57 -0000
@@ -18,38 +18,38 @@ static const struct {
   const mnemonic_t *mnemonic;
   int32_t code;
 } types_and_classes[256] = {
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(34),   V(0),
-    V(0),   V(0),  T(30),   V(0),   V(0),  T(57),   V(0),  T(16),
-    V(0),   V(0),  T(56),  T(14),  T(12),   V(0),   V(0),  T(13),
-   T(61),   V(0), T(105),   V(0),   V(0),   V(0),  T(32), T(258),
-    V(0), T(107),  T(47),   V(0),   V(0),   V(0),  T(17),   V(0),
-  T(257),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),
-   T(65),   V(0),   V(0),  T(18),   V(0),   T(1),   V(0), T(263),
-    V(0),   V(0),   V(0),   V(0),  T(51),   V(0),   V(0), T(106),
-    T(3),   V(0),   V(0),  T(31),   V(0),   V(0),   V(0),   V(0),
-    V(0),  T(50),  T(44), T(104),  T(10),   V(0),   V(0),   V(0),
-    V(0),   V(0),  T(55),   V(0),  T(28),   V(0),   V(0),   V(0),
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(39),
-   T(35),   V(0),   V(0),   T(5),  T(29), T(262),   V(0),   V(0),
-  T(109),   V(0), T(264),   V(0),   V(0),   V(0),   V(0),   V(0),
-    V(0),  T(21),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),
-   T(37),   C(1),  T(58),   V(0),   V(0),   V(0),   V(0),   V(0),
-    V(0),   V(0),   V(0),   C(3),   V(0),  T(52),  T(11),  T(20),
-    V(0), T(261),   V(0),   V(0),   V(0),  T(48),   V(0),   V(0),
-    V(0),  T(25),   C(2),  T(43),   V(0),   V(0),   C(4),  T(60),
-    V(0),   V(0),   T(7),   T(2),   V(0),   V(0),  T(46),  T(22),
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(64),
-    V(0), T(260),   V(0),   V(0),   V(0),   V(0),  T(38),   V(0),
-    V(0), T(259),  T(59),   V(0),   V(0),   V(0),  T(42),  T(36),
-    T(8),  T(15),   V(0),  T(26),  T(27),   T(6),   V(0),  T(99),
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(53),
-    T(9),  T(63),  T(33),   V(0), T(271), T(270),   V(0),  T(40),
-    V(0),   V(0),  T(24),  T(19),   V(0),   V(0),   V(0),   V(0),
-    V(0),   V(0),   V(0), T(108),   V(0),   V(0),   V(0),  T(62),
-    V(0),   V(0),   V(0),   V(0),   V(0),  T(66),   T(4),   V(0),
-    V(0),   V(0), T(256),   V(0),  T(49),   V(0),   V(0),   V(0),
-    V(0),   V(0),  T(45),   V(0),   V(0),  T(23),   V(0),   V(0),
-    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0)
+    V(0),   V(0),  T(58),  T(66),   V(0),   V(0),  T(65),   V(0),
+    V(0),  T(53),   V(0),   V(0),  T(67), T(108),   V(0),   V(0),
+    V(0),   T(4),   V(0),   V(0),   V(0),  T(49),   V(0),   V(0),
+    V(0),   V(0),   V(0),   V(0),   V(0),  T(31),   V(0),   V(0),
+    V(0),   V(0),   V(0),  T(28), T(104),   V(0), T(257),   V(0),
+    V(0),   V(0),  T(27),   V(0),   V(0),   V(0),   V(0),  T(48),
+    V(0),   V(0),   V(0),   V(0),   V(0),   T(1),   V(0),  T(47),
+    V(0),   V(0),  T(34),   V(0), T(263),   V(0), T(106),   V(0),
+    V(0),   V(0),   V(0),   V(0),   V(0),  T(35),   V(0),  T(25),
+    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(56),
+   T(14), T(260), T(107),   V(0),  T(13),  T(68),   V(0),  T(17),
+    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),  T(50),   V(0),
+    V(0), T(109),  T(52), T(258),   V(0),   V(0),  T(45),   V(0),
+  T(264),   V(0),   T(3),   V(0),   V(0),  T(19),   V(0),   V(0),
+    V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),
+   T(61),   V(0),   V(0),  T(42),   V(0),   V(0),   V(0),  T(40),
+    V(0),   V(0),  T(24),  T(60),   V(0),   V(0),  T(55),   V(0),
+    V(0),  T(12),   V(0),   V(0),   V(0),   V(0),   V(0),   V(0),
+    V(0),   V(0),  T(11),   V(0),   V(0),   V(0),   V(0),   V(0),
+    V(0),   V(0),  T(37),   V(0),  T(46),  T(23),   V(0), T(262),
+   T(64),   V(0), T(261),   V(0),  T(29),  T(21),   V(0),   V(0),
+    V(0),   C(1),   V(0),   V(0),   V(0),  T(10),  T(57),   C(3),
+  T(271),  T(33),   V(0),   V(0),   V(0),   V(0),   V(0), T(259),
+   T(18),   V(0),  T(59),  T(30),   V(0),   V(0),   V(0),   V(0),
+   T(16),   V(0),   V(0),   T(7),   T(6),   C(2),  T(43),  T(20),
+    V(0),   C(4),   V(0),   V(0),   V(0),   V(0),   T(2),  T(38),
+   T(22),  T(51),   V(0),   V(0),   V(0),   V(0),   V(0),  T(99),
+    V(0), T(256),   V(0),   V(0),   V(0), T(105),   V(0),   V(0),
+   T(44),  T(39),   V(0),   V(0),   T(8),   V(0),   T(5),   V(0),
+    V(0),   V(0),   V(0),   V(0),   V(0),  T(36),  T(32),  T(15),
+    V(0),  T(26),   V(0),   V(0),   V(0), T(270),   V(0),   V(0),
+    V(0),   V(0),   T(9),   V(0),  T(63),   V(0),  T(62),   V(0)
 };
 
 #undef V
@@ -102,7 +102,7 @@ static really_inline uint8_t hash(uint64
 {
   uint32_t value = (uint32_t)((prefix >> 32) ^ prefix);
   // magic value is generated using hash.c, rerun when adding types
-  return (uint8_t)((value * 3537259401ull) >> 32);
+  return (uint8_t)((value * 3547541308ull) >> 32);
 }
 
 nonnull_all