Index | Thread | Search

From:
Stuart Henderson <stu@spacehopper.org>
Subject:
unbound 1.23.1
To:
tech <tech@openbsd.org>
Date:
Sat, 30 Aug 2025 13:03:41 +0100

Download raw body.

Thread
  • Stuart Henderson:

    unbound 1.23.1

this takes us to the current upstream release (and fixes some whitespace
issues in the diff between our version and upstream).

lots of churn for fast_reload and quite a few things that don't affect
us (we can't build with doh, dnstap, etc in base, and we don't currently
build with client subnet support enabled so the CVE fix is not relevant)
but there are some small fixes and improvements that seem worth having.

test reports and/or oks welcome.

Index: doc/Changelog
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/Changelog,v
diff -u -p -r1.54 Changelog
--- doc/Changelog	21 Feb 2025 13:20:40 -0000	1.54
+++ doc/Changelog	30 Aug 2025 11:52:23 -0000
@@ -1,9 +1,273 @@
+11 April 2025: Yorgos
+	- Merge #1265: Fix WSAPoll.
+
+10 April 2025: Wouter
+	- Fix for print of connection type in log-replies for dot and doh.
+
+9 April 2025: Wouter
+	- Fix to detect if atomic_store links in configure.
+	- Fix #1264: unbound 1.22.0 leaks memory when doing DoH.
+
+8 April 2025: Wouter
+	- Tag for 1.23.0rc1.
+	- Fix fast_reload to print chroot with config file name.
+
+7 April 2025: Yorgos
+	- Merge #902: DNS Error Reporting (RFC 9567). Introduces new
+	  configuration option 'dns-error-reporting' and new statistics for
+	  'num.dns_error_reports'.
+
+4 April 2025: Wouter
+	- Fix mesh_copy_client_info to omit null contents from copy.
+	- Fix comment name in the rpz nsdname test.
+	- Fix nettle compile for warnings and ticket keys.
+	- Fix redis_replica test for unused option defaults and log printout.
+	- Fix test to speed up common.sh script kill_pid.
+	- Fix to update common.sh for speed of kill_pid.
+
+4 April 2025: Yorgos
+	- Merge #1019: Redis read-only replica support.
+	  Introduces new 'redis-replica-*' options for the Redis cache backend.
+
+3 April 2025: Wouter
+	- Fix #1263: Exempt loopback addresses from wait-limit.
+	- Fix wait-limit-netblock and wait-limit-cookie-netblock config parse
+	  to allow two arguments.
+	- Fix ub_event and include dnstap and win_svc headers.
+	- Fix test for stat_values for wait limit defaults for localhost.
+	- Fix parameter unused warning in net_help.c.
+
+2 April 2025: Yorgos
+	- Merge #1262 from markyang92, fix build with
+	  'gcc-15 -Wbuiltin-declaration-mismatch' error in compat/malloc.c.
+	- For #1262, ifdef is no longer needed.
+
+2 April 2025: Wouter
+	- Fix unbound-control test so it counts the new flush_negative output,
+	  also answers the _ta probe from testns and prints command output
+	  and skip a thread specific test when no threads are available.
+	- Fix that ub_event has the facility to deal with callbacks for
+	  fast reload, doq, windows-stop and dnstap.
+	- Fix fast reload test to check if pid exists before acting on it.
+
+1 April 2025: Wouter
+	- Fix escape more characters when printing an RR type with an unquoted
+	  string.
+	- Enable the auth_tls.tdir and auth_tls_failcert.tdir tests.
+
+31 March 2025: Wouter
+	- iana portlist update.
+	- Merge #1042: Fast Reload. The unbound-control fast_reload is added.
+	  It reads changed config in a thread, then only briefly pauses the
+	  service threads, that keep running. DNS service is only interrupted
+	  briefly, less than a second.
+	- Skip the unit tests for auth_tls.tdir and auth_tls_failcert.tdir.
+
+27 March 2025: Wouter
+	- Fix unit test dname log printout typecast.
+	- Fix for ci test, expat is installed on the osx image.
+
+26 March 2025: Yorgos
+	- Fix #1255: Multiple pinnings to vulnerable copies of libexpat.
+	- For #1255, for ios use an older expat version that does not require
+	  C++11 language features.
+	- For #1255, for ios disable building tests that require C++11.
+	- For #1255, for ios try the latest expat version again.
+
+24 March 2025: Wouter
+	- Fix #1254: `send failed: Socket is not connected` and
+	  `remote address is 0.0.0.0 port 53`.
+
+21 March 2025: Wouter
+	- Fix #1253: Cache entries fail to be removed from Redis cachedb
+	  backend with unbound-control flush* +c.
+	- Fix for #1253: Fix for redis cachedb backend to expect an integer
+	  reply for the EXPIRE command.
+
+20 March 2025: Wouter
+	- Fix print of RR type NSAP-PTR, it is an unquoted string.
+
+18 March 2025: Wouter
+	- Fix #1251: WSAPoll first argument cannot be NULL.
+	- Fix for windows compile create ssl contexts.
+
+17 March 2025: Wouter
+	- Fix representation of types GPOS and RESINFO, add rdf type for
+	  unquoted str.
+
+16 March 2025: Yorgos
+	- Fix 'unbound-control flush_negative' when reporting removed data;
+	  reported by David 'eqvinox' Lamparter.
+
+28 February 2025: Wouter
+	- Merge #1238: Prefer SOURCE_DATE_EPOCH over actual time.
+	  Add --help output description for the SOURCE_DATE_EPOCH variable.
+
+25 February 2025: Wouter
+	- Merge #1243: Do not shadow tm on line 236.
+
+24 February 2025: Yorgos
+	- Fix hash calculation for cachedb to ignore case. Previously, cached
+	  records there were only relevant for same case queries (if not
+	  already in Unbound's internal cache).
+
+19 February 2025: Yorgos
+	- Fix static analysis report about unhandled EOF on error conditions
+	  when reading anchor key files.
+	- Merge #1241: Fix infra-keep-probing for low infra-cache-max-rtt
+	  values.
+
+17 February 2025: Yorgos
+	- Consider reconfigurations when calculating the still_useful_timeout
+	  for servers in the infrastructure cache.
+
+30 January 2025: Wouter
+	- Fix #986: Resolving sas.com with dnssec-validation fails though
+	  signed delegations seem to be (mostly) correct.
+
+29 January 2025: Yorgos
+	- Make the default value of module-config "validator iterator"
+	  regardless of compilation options. --enable-subnet would implicitly
+	  change the value to enable the subnetcache module by default in the
+	  past.
+
+24 January 2025: Yorgos
+	- Merge #1220 from Petr Menšík, Add unbound members group access to
+	  control key.
+
+21 January 2025: Yorgos
+	- Use the same interface listening port discovery code for all needed
+	  protocols.
+	- Port to string only when needed before getaddrinfo().
+	- Do not open unencrypted channels next to encrypted ones on the same
+	  port.
+	- Merge #1224 from Theo Buehler: Do not use DSA API unless USE_DSA is
+	  set.
+
+21 January 2025: Wouter
+	- Fix compile of interface check code when dnscrypt or quic is
+	  disabled.
+	- Fix encoding of RR type ATMA.
+	- Fix to check length in ATMA string to wire.
+	- Merge #1229: check before use daemon->shm_info.
+
+20 January 2025: Yorgos
+	- Merge #1222: Unique DoT and DoH SSL contexts to allow for different
+	  ALPN.
+	- Create the quic SSL listening context only when needed.
+
+15 January 2025: Yorgos
+	- Merge #1221: Consider auth zones when checking for forwarders.
+
+14 January 2025: Yorgos
+	- Add resolver.arpa and service.arpa to the default locally served
+	  zones.
+
+13 January 2025: Yorgos
+	- Fix #1213: Misleading error message on default access control causing
+	  refuse.
+
+10 January 2025: Yorgos
+	- Merge #1214: Use TCP_NODELAY on TLS sockets to speed up the TLS
+	  handshake.
+
+31 December 2024: Yorgos
+	- Merge #1174: Serve expired cache update fixes. Fixes a regression bug
+	  with serve-expired that appeared in 1.22.0 and would not allow the
+	  iterator to update the cache with not-yet-validated entries resulting
+	  in increased outgoing traffic.
+
+20 December 2024: Yorgos
+	- For #1207: [FR] Support for RESINFO RRType 261 (RFC9606), add
+	  LDNS_RR_TYPE_RESINFO similar to LDNS_RR_TYPE_TXT.
+
+13 December 2024: Yorgos
+	- Merge #1204: ci: set persist-credentials: false for actions/checkout
+	  per zizmor suggestion.
+
+3 December 2024: Yorgos
+	- Merge #1189: Fix the dname_str method to cause conversion errors
+	  when the domain name length is 255.
+	- Merge #1197: dname_str() fixes.
+	- For #1175, the default value of serve-expired-ttl is set to 86400
+	  (1 day) as suggested by RFC8767.
+	- Merge #1198: Fix log-servfail with serve expired and no useful cache
+	  contents.
+	- Safeguard alias loop while looking in the cache for expired answers.
+	- Merge #1187: Create the SSL_CTX for QUIC before chroot and privilege
+	  drop.
+	- Fix typo in log_servfail.tdir test.
+
+22 November 2024: Yorgos
+	- Fix #1175: serve-expired does not adhere to secure-by-default
+	  principle. The default value of serve-expired-client-timeout
+	  is set to 1800 as suggested by RFC8767.
+	- For #1175, update serve-expired tests.
+
+20 November 2024: Yorgos
+	- Fix comparison to help static analyzer.
+
+19 November 2024: Yorgos
+	- Merge #1169 from Sergey Kacheev, fix: lock-free counters for
+	  auth_zone up/down queries.
+
+15 November 2024: Wouter
+	- Fix #1183: the data being used is released in method
+	  nsec3_hash_test_entry.
+	- Fix for #1183: release nsec3 hashes per test file.
+
+8 November 2024: Yorgos
+	- More descriptive text for 'harden-algo-downgrade'.
+	- Complete fix for max-global-quota to 200.
+
+6 November 2024: Yorgos
+	- Increase the default of max-global-quota to 200 from 128 after
+	  operational feedback. Still keeping the possible amplification
+	  factor (CAMP related issues) in the hundreds.
+
+5 November 2024: Wouter
+	- Fix for the serve expired DNSSEC information fix, it would not allow
+	  current delegation information be updated in cache. The fix allows
+	  current delegation and validation recursion information to be
+	  updated, but as a consequence no longer has certain expired
+	  information around for later dnssec valid expired responses.
+	- Fix to log redis timeout error string on failure.
+
+5 November 2024: Yorgos
+	- Fix SETEX check during Redis (re)initialization.
+
+4 November 2024: Wouter
+	- Fix redis that during a reload it does not fail if the redis
+	  server does not connect or does not respond. It still logs the
+	  errors and if the server is up checks expiration features.
+	- Merge #1167: Makefile.in: fix occasional parallel build failures
+	  around bison rule.
+
+1 November 2024: Yorgos
+	- Merge #1159: Stats for discard-timeout and wait-limit.
+	- Add test case for #1159.
+	- Some clean up for stat_values.test.
+	- Merge #1170 from Melroy van den Berg, Fix chroot manpage
+	  description.
+	- Merge #1157 from Liang Zhu, Fix heap corruption when calling
+	  ub_ctx_delete in Windows.
+
+25 October 2024: Yorgos
+	- Fix #1163: Typos in unbound.conf documentation.
+
+17 October 2024: Wouter
+	- Tag for 1.22.0 release. This did not contain the 1154 fix
+	  from 16 oct. The code repository continues with
+	  version 1.22.1 in development.
+
 16 October 2024: Yorgos
 	- Fix for dnsoverquic and dnstap to use the correct dnstap
 	  environment.
 
 16 October 2024: Wouter
 	- Fix for dnstap with dnscrypt and dnstap without dnsoverquic.
+	- Fix #1154: Tag Incorrectly Applying for Other Interfaces
+	  Using the Same IP. This fix is not for 1.22.0.
 
 14 October 2024: Wouter
 	- Fix to display warning if quic-port is set but dnsoverquic is not
@@ -23,6 +287,7 @@
 	- Fix add reallocarray to alloc stats unit test, and disable
 	  override of strdup in unbound-host, and the result of config
 	  get option is freed properly.
+	- Tag for 1.22.0rc1.
 
 9 October 2024: Wouter
 	- Merge #871: DNS over QUIC. This adds `quic-port: 853` and
Index: Makefile.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/Makefile.in,v
diff -u -p -r1.46 Makefile.in
--- Makefile.in	21 Feb 2025 13:20:39 -0000	1.46
+++ Makefile.in	30 Aug 2025 11:52:22 -0000
@@ -179,11 +179,11 @@ testcode/unitlruhash.c testcode/unitmain
 testcode/unitneg.c testcode/unitregional.c testcode/unitslabhash.c \
 testcode/unitverify.c testcode/readhex.c testcode/testpkts.c testcode/unitldns.c \
 testcode/unitecs.c testcode/unitauth.c testcode/unitzonemd.c \
-testcode/unittcpreuse.c testcode/unitdoq.c
+testcode/unittcpreuse.c testcode/unitdoq.c testcode/unitinfra.c
 UNITTEST_OBJ=unitanchor.lo unitdname.lo unitlruhash.lo unitmain.lo \
 unitmsgparse.lo unitneg.lo unitregional.lo unitslabhash.lo unitverify.lo \
 readhex.lo testpkts.lo unitldns.lo unitecs.lo unitauth.lo unitzonemd.lo \
-unittcpreuse.lo unitdoq.lo
+unittcpreuse.lo unitdoq.lo unitinfra.lo
 UNITTEST_OBJ_LINK=$(UNITTEST_OBJ) worker_cb.lo $(COMMON_OBJ) $(SLDNS_OBJ) \
 $(COMPAT_OBJ)
 DAEMON_SRC=daemon/acl_list.c daemon/cachedump.c daemon/daemon.c \
@@ -509,10 +509,15 @@ util/configlexer.c:  $(srcdir)/util/conf
 	fi
 	@if test ! -f $@; then echo "No $@ : need flex and bison to compile from source repository"; exit 1; fi
 
-util/configparser.c util/configparser.h:  $(srcdir)/util/configparser.y
+# Builds both util/configparser.c and util/configparser.h.
+# To avoid double-building we split one target out.
+util/configparser.c:  $(srcdir)/util/configparser.y
 	@-if test ! -d util; then mkdir -p util; fi
 	$(YACC) -d -o util/configparser.c $(srcdir)/util/configparser.y
 
+util/configparser.h: util/configparser.c
+	touch $@
+
 clean:
 	rm -f *.o *.d *.lo *~ tags
 	rm -f unbound$(EXEEXT) unbound-checkconf$(EXEEXT) unbound-host$(EXEEXT) unbound-control$(EXEEXT) unbound-anchor$(EXEEXT) unbound-control-setup libunbound.la unbound.h
@@ -584,7 +589,7 @@ unbound-event-install:
 	$(LIBTOOL) --mode=install cp $(srcdir)/libunbound/unbound-event.h $(DESTDIR)$(includedir)/unbound-event.h
 
 install:
-	# avoid mistakes made by people who forget "make -f Makefile.bsd-wrapper"
+	# you probably wanted "make -f Makefile.bsd-wrapper"
 
 install-lib:	lib $(UNBOUND_EVENT_INSTALL)
 	$(INSTALL) -m 755 -d $(DESTDIR)$(libdir)
@@ -877,7 +882,7 @@ view.lo view.o: $(srcdir)/services/view.
  $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/services/localzone.h $(srcdir)/util/storage/dnstree.h \
  $(srcdir)/util/module.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/data/msgreply.h \
  $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h \
- $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/sbuffer.h $(srcdir)/util/config_file.h
+ $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/sbuffer.h $(srcdir)/util/config_file.h $(srcdir)/respip/respip.h
 rpz.lo rpz.o: $(srcdir)/services/rpz.c config.h $(srcdir)/services/rpz.h $(srcdir)/services/localzone.h \
  $(srcdir)/util/rbtree.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/storage/dnstree.h \
  $(srcdir)/util/module.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/data/msgreply.h \
@@ -973,7 +978,7 @@ fptr_wlist.lo fptr_wlist.o: $(srcdir)/ut
  $(srcdir)/validator/val_nsec3.h $(srcdir)/validator/val_sigcrypt.h $(srcdir)/validator/val_kentry.h \
  $(srcdir)/validator/val_neg.h $(srcdir)/validator/autotrust.h $(srcdir)/libunbound/libworker.h \
  $(srcdir)/libunbound/context.h $(srcdir)/util/alloc.h $(srcdir)/libunbound/unbound-event.h \
- $(srcdir)/libunbound/worker.h
+ $(srcdir)/libunbound/worker.h $(srcdir)/daemon/remote.h
 locks.lo locks.o: $(srcdir)/util/locks.c config.h $(srcdir)/util/locks.h $(srcdir)/util/log.h
 log.lo log.o: $(srcdir)/util/log.c config.h $(srcdir)/util/log.h $(srcdir)/util/locks.h $(srcdir)/sldns/sbuffer.h
 mini_event.lo mini_event.o: $(srcdir)/util/mini_event.c config.h $(srcdir)/util/mini_event.h $(srcdir)/util/rbtree.h \
@@ -1060,7 +1065,7 @@ tube.lo tube.o: $(srcdir)/util/tube.c co
  $(srcdir)/libunbound/unbound.h $(srcdir)/respip/respip.h $(srcdir)/util/ub_event.h
 ub_event.lo ub_event.o: $(srcdir)/util/ub_event.c config.h $(srcdir)/util/ub_event.h $(srcdir)/util/log.h \
  $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h  \
- $(srcdir)/util/tube.h $(srcdir)/util/mini_event.h $(srcdir)/util/rbtree.h
+ $(srcdir)/util/tube.h $(srcdir)/util/mini_event.h $(srcdir)/util/rbtree.h $(srcdir)/daemon/remote.h
 ub_event_pluggable.lo ub_event_pluggable.o: $(srcdir)/util/ub_event_pluggable.c config.h $(srcdir)/util/ub_event.h \
  $(srcdir)/libunbound/unbound-event.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
   $(srcdir)/util/log.h $(srcdir)/util/fptr_wlist.h \
@@ -1257,6 +1262,7 @@ unitzonemd.lo unitzonemd.o: $(srcdir)/te
  $(srcdir)/validator/val_anchor.h
 unittcpreuse.lo unittcpreuse.o: $(srcdir)/testcode/unittcpreuse.c config.h $(srcdir)/services/outside_network.h \
 $(srcdir)/util/random.h
+unitinfra.lo unitinfra.o: $(srcdir)/testcode/unitinfra.c config.h $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/iterator/iterator.h
 acl_list.lo acl_list.o: $(srcdir)/daemon/acl_list.c config.h $(srcdir)/daemon/acl_list.h \
  $(srcdir)/util/storage/dnstree.h $(srcdir)/util/rbtree.h $(srcdir)/services/view.h $(srcdir)/util/locks.h \
  $(srcdir)/util/log.h $(srcdir)/util/regional.h $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h \
@@ -1308,7 +1314,10 @@ remote.lo remote.o: $(srcdir)/daemon/rem
  $(srcdir)/validator/val_anchor.h $(srcdir)/iterator/iterator.h $(srcdir)/services/outbound_list.h \
  $(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h $(srcdir)/iterator/iter_delegpt.h \
  $(srcdir)/services/outside_network.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/parseutil.h \
- $(srcdir)/sldns/wire2str.h $(srcdir)/util/edns.h
+ $(srcdir)/sldns/wire2str.h $(srcdir)/util/edns.h \
+ $(srcdir)/util/locks.h $(srcdir)/util/ub_event.h \
+ $(srcdir)/util/tcp_conn_limit.h $(srcdir)/util/edns.h $(srcdir)/validator/val_neg.h \
+ $(srcdir)/iterator/iter_utils.h $(srcdir)/iterator/iter_donotq.h $(srcdir)/iterator/iter_priv.h
 stats.lo stats.o: $(srcdir)/daemon/stats.c config.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h \
  $(srcdir)/libunbound/unbound.h $(srcdir)/daemon/worker.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \
  $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \
Index: ac_pkg_swig.m4
===================================================================
RCS file: ac_pkg_swig.m4
diff -N ac_pkg_swig.m4
--- ac_pkg_swig.m4	12 Aug 2017 11:22:45 -0000	1.2
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,133 +0,0 @@
-# ===========================================================================
-#              http://autoconf-archive.cryp.to/ac_pkg_swig.html
-# ===========================================================================
-#
-# SYNOPSIS
-#
-#   AC_PROG_SWIG([major.minor.micro])
-#
-# DESCRIPTION
-#
-#   This macro searches for a SWIG installation on your system. If found you
-#   should call SWIG via $(SWIG). You can use the optional first argument to
-#   check if the version of the available SWIG is greater than or equal to
-#   the value of the argument. It should have the format: N[.N[.N]] (N is a
-#   number between 0 and 999. Only the first N is mandatory.)
-#
-#   If the version argument is given (e.g. 1.3.17), AC_PROG_SWIG checks that
-#   the swig package is this version number or higher.
-#
-#   In configure.in, use as:
-#
-#     AC_PROG_SWIG(1.3.17)
-#     SWIG_ENABLE_CXX
-#     SWIG_MULTI_MODULE_SUPPORT
-#     SWIG_PYTHON
-#
-# LAST MODIFICATION
-#
-#   2008-04-12
-#
-# COPYLEFT
-#
-#   Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de>
-#   Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca>
-#   Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net>
-#   Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za>
-#
-#   This program is free software; you can redistribute it and/or modify it
-#   under the terms of the GNU General Public License as published by the
-#   Free Software Foundation; either version 2 of the License, or (at your
-#   option) any later version.
-#
-#   This program is distributed in the hope that it will be useful, but
-#   WITHOUT ANY WARRANTY; without even the implied warranty of
-#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
-#   Public License for more details.
-#
-#   You should have received a copy of the GNU General Public License along
-#   with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-#   As a special exception, the respective Autoconf Macro's copyright owner
-#   gives unlimited permission to copy, distribute and modify the configure
-#   scripts that are the output of Autoconf when processing the Macro. You
-#   need not follow the terms of the GNU General Public License when using
-#   or distributing such scripts, even though portions of the text of the
-#   Macro appear in them. The GNU General Public License (GPL) does govern
-#   all other use of the material that constitutes the Autoconf Macro.
-#
-#   This special exception to the GPL applies to versions of the Autoconf
-#   Macro released by the Autoconf Macro Archive. When you make and
-#   distribute a modified version of the Autoconf Macro, you may extend this
-#   special exception to the GPL to apply to your modified version as well.
-
-AC_DEFUN([AC_PROG_SWIG],[
-        AC_PATH_PROG([SWIG],[swig])
-        if test -z "$SWIG" ; then
-                AC_MSG_WARN([cannot find 'swig' program. You should look at http://www.swig.org])
-                SWIG='echo "Error: SWIG is not installed. You should look at http://www.swig.org" ; false'
-        elif test -n "$1" ; then
-                AC_MSG_CHECKING([for SWIG version])
-                [swig_version=`$SWIG -version 2>&1 | grep 'SWIG Version' | sed 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/g'`]
-                AC_MSG_RESULT([$swig_version])
-                if test -n "$swig_version" ; then
-                        # Calculate the required version number components
-                        [required=$1]
-                        [required_major=`echo $required | sed 's/[^0-9].*//'`]
-                        if test -z "$required_major" ; then
-                                [required_major=0]
-                        fi
-                        [required=`echo $required | sed 's/[0-9]*[^0-9]//'`]
-                        [required_minor=`echo $required | sed 's/[^0-9].*//'`]
-                        if test -z "$required_minor" ; then
-                                [required_minor=0]
-                        fi
-                        [required=`echo $required | sed 's/[0-9]*[^0-9]//'`]
-                        [required_patch=`echo $required | sed 's/[^0-9].*//'`]
-                        if test -z "$required_patch" ; then
-                                [required_patch=0]
-                        fi
-                        # Calculate the available version number components
-                        [available=$swig_version]
-                        [available_major=`echo $available | sed 's/[^0-9].*//'`]
-                        if test -z "$available_major" ; then
-                                [available_major=0]
-                        fi
-                        [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
-                        [available_minor=`echo $available | sed 's/[^0-9].*//'`]
-                        if test -z "$available_minor" ; then
-                                [available_minor=0]
-                        fi
-                        [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
-                        [available_patch=`echo $available | sed 's/[^0-9].*//'`]
-                        if test -z "$available_patch" ; then
-                                [available_patch=0]
-                        fi
-			[badversion=0]
-			if test $available_major -lt $required_major ; then
-				[badversion=1]
-			fi
-                        if test $available_major -eq $required_major \
-                                -a $available_minor -lt $required_minor ; then
-				[badversion=1]
-			fi
-                        if test $available_major -eq $required_major \
-                                -a $available_minor -eq $required_minor \
-                                -a $available_patch -lt $required_patch ; then
-				[badversion=1]
-			fi
-			if test $badversion -eq 1 ; then
-                                AC_MSG_WARN([SWIG version >= $1 is required.  You have $swig_version.  You should look at http://www.swig.org])
-                                SWIG='echo "Error: SWIG version >= $1 is required.  You have '"$swig_version"'.  You should look at http://www.swig.org" ; false'
-                        else
-                                AC_MSG_NOTICE([SWIG executable is '$SWIG'])
-                                SWIG_LIB=`$SWIG -swiglib`
-                                AC_MSG_NOTICE([SWIG library directory is '$SWIG_LIB'])
-                        fi
-                else
-                        AC_MSG_WARN([cannot determine SWIG version])
-                        SWIG='echo "Error: Cannot determine SWIG version.  You should look at http://www.swig.org" ; false'
-                fi
-        fi
-        AC_SUBST([SWIG_LIB])
-])
Index: ax_build_date_epoch.m4
===================================================================
RCS file: ax_build_date_epoch.m4
diff -N ax_build_date_epoch.m4
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ax_build_date_epoch.m4	30 Aug 2025 11:52:23 -0000
@@ -0,0 +1,70 @@
+# ===========================================================================
+#   https://www.gnu.org/software/autoconf-archive/ax_build_date_epoch.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_BUILD_DATE_EPOCH(VARIABLE[, FORMAT[, ACTION-IF-FAIL]])
+#
+# DESCRIPTION
+#
+#   Sets VARIABLE to a string representing the current time.  It is
+#   formatted according to FORMAT if specified, otherwise it is formatted as
+#   the number of seconds (excluding leap seconds) since the UNIX epoch (01
+#   Jan 1970 00:00:00 UTC).
+#
+#   If the SOURCE_DATE_EPOCH environment variable is set, it uses the value
+#   of that variable instead of the current time.  See
+#   https://reproducible-builds.org/specs/source-date-epoch).  If
+#   SOURCE_DATE_EPOCH is set but cannot be properly interpreted as a UNIX
+#   timestamp, then execute ACTION-IF-FAIL if specified, otherwise error.
+#
+#   VARIABLE is AC_SUBST-ed.
+#
+# LICENSE
+#
+#   Copyright (c) 2016 Eric Bavier <bavier@member.fsf.org>
+#
+#   This program is free software: you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation, either version 3 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 2
+
+AC_DEFUN([AX_BUILD_DATE_EPOCH],
+[dnl
+AC_MSG_CHECKING([for build time])
+ax_date_fmt="m4_default($2,%s)"
+AS_IF([test x"$SOURCE_DATE_EPOCH" = x],
+ [$1=`date "+$ax_date_fmt"`],
+ [ax_build_date=`date -u -d "@$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null \
+                 || date -u -r "$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null`
+  AS_IF([test x"$ax_build_date" = x],
+   [m4_ifval([$3],
+      [$3],
+      [AC_MSG_ERROR([malformed SOURCE_DATE_EPOCH])])],
+   [$1=$ax_build_date])])
+AC_MSG_RESULT([$$1])
+])dnl AX_BUILD_DATE_EPOCH
Index: config.guess
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/config.guess,v
diff -u -p -r1.17 config.guess
--- config.guess	4 Sep 2024 09:36:40 -0000	1.17
+++ config.guess	30 Aug 2025 11:52:23 -0000
@@ -1,10 +1,10 @@
 #! /bin/sh
 # Attempt to guess a canonical system name.
-#   Copyright 1992-2024 Free Software Foundation, Inc.
+#   Copyright 1992-2025 Free Software Foundation, Inc.
 
 # shellcheck disable=SC2006,SC2268 # see below for rationale
 
-timestamp='2024-07-27'
+timestamp='2025-07-10'
 
 # This file is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -60,7 +60,7 @@ version="\
 GNU config.guess ($timestamp)
 
 Originally written by Per Bothner.
-Copyright 1992-2024 Free Software Foundation, Inc.
+Copyright 1992-2025 Free Software Foundation, Inc.
 
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
@@ -1597,8 +1597,11 @@ EOF
     *:Unleashed:*:*)
 	GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE
 	;;
-    *:Ironclad:*:*)
-	GUESS=$UNAME_MACHINE-unknown-ironclad
+    x86_64:[Ii]ronclad:*:*|i?86:[Ii]ronclad:*:*)
+	GUESS=$UNAME_MACHINE-pc-ironclad-mlibc
+	;;
+    *:[Ii]ronclad:*:*)
+	GUESS=$UNAME_MACHINE-unknown-ironclad-mlibc
 	;;
 esac
 
@@ -1808,8 +1811,8 @@ fi
 exit 1
 
 # Local variables:
-# eval: (add-hook 'before-save-hook 'time-stamp)
+# eval: (add-hook 'before-save-hook 'time-stamp nil t)
 # time-stamp-start: "timestamp='"
-# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-format: "%Y-%02m-%02d"
 # time-stamp-end: "'"
 # End:
Index: config.h.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/config.h.in,v
diff -u -p -r1.33 config.h.in
--- config.h.in	21 Feb 2025 13:20:39 -0000	1.33
+++ config.h.in	30 Aug 2025 11:52:23 -0000
@@ -378,6 +378,9 @@
 /* Define if we have LibreSSL */
 #undef HAVE_LIBRESSL
 
+/* If we have atomic_store */
+#undef HAVE_LINK_ATOMIC_STORE
+
 /* Define to 1 if you have the <linux/net_tstamp.h> header file. */
 #undef HAVE_LINUX_NET_TSTAMP_H
 
@@ -662,6 +665,9 @@
 
 /* Define to 1 if you have the <stdarg.h> header file. */
 #undef HAVE_STDARG_H
+
+/* Define to 1 if you have the <stdatomic.h> header file. */
+#undef HAVE_STDATOMIC_H
 
 /* Define to 1 if you have the <stdbool.h> header file. */
 #undef HAVE_STDBOOL_H
Index: config.sub
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/config.sub,v
diff -u -p -r1.16 config.sub
--- config.sub	4 Sep 2024 09:36:40 -0000	1.16
+++ config.sub	30 Aug 2025 11:52:23 -0000
@@ -1,10 +1,10 @@
 #! /bin/sh
 # Configuration validation subroutine script.
-#   Copyright 1992-2024 Free Software Foundation, Inc.
+#   Copyright 1992-2025 Free Software Foundation, Inc.
 
 # shellcheck disable=SC2006,SC2268,SC2162 # see below for rationale
 
-timestamp='2024-05-27'
+timestamp='2025-07-10'
 
 # This file is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -76,7 +76,7 @@ Report bugs and patches to <config-patch
 version="\
 GNU config.sub ($timestamp)
 
-Copyright 1992-2024 Free Software Foundation, Inc.
+Copyright 1992-2025 Free Software Foundation, Inc.
 
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
@@ -145,6 +145,7 @@ case $1 in
 			| kfreebsd*-gnu* \
 			| knetbsd*-gnu* \
 			| kopensolaris*-gnu* \
+			| ironclad-* \
 			| linux-* \
 			| managarm-* \
 			| netbsd*-eabi* \
@@ -242,7 +243,6 @@ case $1 in
 					| rombug \
 					| semi \
 					| sequent* \
-					| siemens \
 					| sgi* \
 					| siemens \
 					| sim \
@@ -261,7 +261,7 @@ case $1 in
 						basic_machine=$field1-$field2
 						basic_os=
 						;;
-					zephyr*)
+					tock* | zephyr*)
 						basic_machine=$field1-unknown
 						basic_os=$field2
 						;;
@@ -1194,7 +1194,7 @@ case $cpu-$vendor in
 	xscale-* | xscalee[bl]-*)
 		cpu=`echo "$cpu" | sed 's/^xscale/arm/'`
 		;;
-	arm64-* | aarch64le-*)
+	arm64-* | aarch64le-* | arm64_32-*)
 		cpu=aarch64
 		;;
 
@@ -1321,6 +1321,7 @@ case $cpu-$vendor in
 			| i960 \
 			| ia16 \
 			| ia64 \
+			| intelgt \
 			| ip2k \
 			| iq2000 \
 			| javascript \
@@ -1522,6 +1523,10 @@ EOF
 		kernel=nto
 		os=`echo "$basic_os" | sed -e 's|nto|qnx|'`
 		;;
+	ironclad*)
+		kernel=ironclad
+		os=`echo "$basic_os" | sed -e 's|ironclad|mlibc|'`
+		;;
 	linux*)
 		kernel=linux
 		os=`echo "$basic_os" | sed -e 's|linux|gnu|'`
@@ -1976,6 +1981,7 @@ case $os in
 	| atheos* \
 	| auroraux* \
 	| aux* \
+	| banan_os* \
 	| beos* \
 	| bitrig* \
 	| bme* \
@@ -2022,7 +2028,6 @@ case $os in
 	| ios* \
 	| iris* \
 	| irix* \
-	| ironclad* \
 	| isc* \
 	| its* \
 	| l4re* \
@@ -2118,6 +2123,7 @@ case $os in
 	| sysv* \
 	| tenex* \
 	| tirtos* \
+	| tock* \
 	| toppers* \
 	| tops10* \
 	| tops20* \
@@ -2214,6 +2220,8 @@ case $kernel-$os-$obj in
 		;;
 	uclinux-uclibc*- | uclinux-gnu*- )
 		;;
+	ironclad-mlibc*-)
+		;;
 	managarm-mlibc*- | managarm-kernel*- )
 		;;
 	windows*-msvc*-)
@@ -2249,6 +2257,8 @@ case $kernel-$os-$obj in
 		;;
 	*-eabi*- | *-gnueabi*-)
 		;;
+	ios*-simulator- | tvos*-simulator- | watchos*-simulator- )
+		;;
 	none--*)
 		# None (no kernel, i.e. freestanding / bare metal),
 		# can be paired with an machine code file format
@@ -2347,8 +2357,8 @@ echo "$cpu-$vendor${kernel:+-$kernel}${o
 exit
 
 # Local variables:
-# eval: (add-hook 'before-save-hook 'time-stamp)
+# eval: (add-hook 'before-save-hook 'time-stamp nil t)
 # time-stamp-start: "timestamp='"
-# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-format: "%Y-%02m-%02d"
 # time-stamp-end: "'"
 # End:
Index: configure
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/configure,v
diff -u -p -r1.57 configure
--- configure	21 Feb 2025 13:20:39 -0000	1.57
+++ configure	30 Aug 2025 11:52:23 -0000
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.71 for unbound 1.22.0.
+# Generated by GNU Autoconf 2.71 for unbound 1.23.1.
 #
 # Report bugs to <unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues>.
 #
@@ -622,8 +622,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='unbound'
 PACKAGE_TARNAME='unbound'
-PACKAGE_VERSION='1.22.0'
-PACKAGE_STRING='unbound 1.22.0'
+PACKAGE_VERSION='1.23.1'
+PACKAGE_STRING='unbound 1.23.1'
 PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues'
 PACKAGE_URL=''
 
@@ -712,6 +712,7 @@ SSLLIB
 HAVE_SSL
 PC_CRYPTO_DEPENDENCY
 CONFIG_DATE
+SOURCE_DATE_EPOCH
 GCC_DOCKER_LINTFLAGS
 NETBSD_LINTFLAGS
 PYUNBOUND_UNINSTALL
@@ -959,6 +960,7 @@ SYSTEMD_LIBS
 SYSTEMD_DAEMON_CFLAGS
 SYSTEMD_DAEMON_LIBS
 PYTHON_VERSION
+SOURCE_DATE_EPOCH
 PROTOBUFC_CFLAGS
 PROTOBUFC_LIBS'
 
@@ -1509,7 +1511,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 unbound 1.22.0 to adapt to many kinds of systems.
+\`configure' configures unbound 1.23.1 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1575,7 +1577,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of unbound 1.22.0:";;
+     short | recursive ) echo "Configuration of unbound 1.23.1:";;
    esac
   cat <<\_ACEOF
 
@@ -1752,6 +1754,10 @@ Some influential environment variables:
               The installed Python version to use, for example '2.3'. This
               string will be appended to the Python interpreter canonical
               name.
+  SOURCE_DATE_EPOCH
+              If it is set, it uses the value of that variable instead of the
+              current time as the build timestamp. The format is a unix
+              timestamp. This enables reproducible build output.
   PROTOBUFC_CFLAGS
               C compiler flags for PROTOBUFC, overriding pkg-config
   PROTOBUFC_LIBS
@@ -1824,7 +1830,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-unbound configure 1.22.0
+unbound configure 1.23.1
 generated by GNU Autoconf 2.71
 
 Copyright (C) 2021 Free Software Foundation, Inc.
@@ -2481,7 +2487,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 unbound $as_me 1.22.0, which was
+It was created by unbound $as_me 1.23.1, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   $ $0$ac_configure_args_raw
@@ -3243,13 +3249,13 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 UNBOUND_VERSION_MAJOR=1
 
-UNBOUND_VERSION_MINOR=22
+UNBOUND_VERSION_MINOR=23
 
-UNBOUND_VERSION_MICRO=0
+UNBOUND_VERSION_MICRO=1
 
 
 LIBUNBOUND_CURRENT=9
-LIBUNBOUND_REVISION=30
+LIBUNBOUND_REVISION=32
 LIBUNBOUND_AGE=1
 # 1.0.0 had 0:12:0
 # 1.0.1 had 0:13:0
@@ -3347,6 +3353,8 @@ LIBUNBOUND_AGE=1
 # 1.21.0 had 9:28:1
 # 1.21.1 had 9:29:1
 # 1.22.0 had 9:30:1
+# 1.23.0 had 9:31:1
+# 1.23.1 had 9:32:1
 
 #   Current  -- the number of the binary API that we're implementing
 #   Revision -- which iteration of the implementation of the binary
@@ -16093,6 +16101,14 @@ then :
 
 fi
 
+ac_fn_c_check_header_compile "$LINENO" "stdatomic.h" "ac_cv_header_stdatomic_h" "$ac_includes_default
+"
+if test "x$ac_cv_header_stdatomic_h" = xyes
+then :
+  printf "%s\n" "#define HAVE_STDATOMIC_H 1" >>confdefs.h
+
+fi
+
 
 # check for types.
 # Using own tests for int64* because autoconf builtin only give 32bit.
@@ -19893,7 +19909,26 @@ if test "`uname`" = "Linux"; then
 	GCC_DOCKER_LINTFLAGS='-syntax'
 
 fi
-CONFIG_DATE=`date +%Y%m%d`
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5
+printf %s "checking for build time... " >&6; }
+ax_date_fmt="%Y%m%d"
+if test x"$SOURCE_DATE_EPOCH" = x
+then :
+  CONFIG_DATE=`date "+$ax_date_fmt"`
+else $as_nop
+  ax_build_date=`date -u -d "@$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null \
+                 || date -u -r "$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null`
+  if test x"$ax_build_date" = x
+then :
+  as_fn_error $? "malformed SOURCE_DATE_EPOCH" "$LINENO" 5
+else $as_nop
+  CONFIG_DATE=$ax_build_date
+fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CONFIG_DATE" >&5
+printf "%s\n" "$CONFIG_DATE" >&6; }
+
+
 
 
 # Checks for libraries.
@@ -21340,7 +21375,7 @@ fi
 else $as_nop
 
 		  # without EVP_PKEY_fromdata, older openssl, check for support
-	      ac_fn_c_check_func "$LINENO" "ECDSA_sign" "ac_cv_func_ECDSA_sign"
+		  ac_fn_c_check_func "$LINENO" "ECDSA_sign" "ac_cv_func_ECDSA_sign"
 if test "x$ac_cv_func_ECDSA_sign" = xyes
 then :
 
@@ -21348,7 +21383,7 @@ else $as_nop
   as_fn_error $? "OpenSSL does not support ECDSA: please upgrade or rerun with --disable-ecdsa" "$LINENO" 5
 fi
 
-	      ac_fn_c_check_func "$LINENO" "SHA384_Init" "ac_cv_func_SHA384_Init"
+		  ac_fn_c_check_func "$LINENO" "SHA384_Init" "ac_cv_func_SHA384_Init"
 if test "x$ac_cv_func_SHA384_Init" = xyes
 then :
 
@@ -23496,6 +23531,48 @@ if echo $host_os | grep darwin8 > /dev/n
 printf "%s\n" "#define DARWIN_BROKEN_SETREUID 1" >>confdefs.h
 
 fi
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for atomic_store" >&5
+printf %s "checking for atomic_store... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$ac_includes_default
+#ifdef HAVE_STDATOMIC_H
+#include <stdatomic.h>
+#endif
+
+int
+main (void)
+{
+
+	int newvar = 5, var = 0;
+	atomic_store((_Atomic int*)&var, newvar);
+	newvar = 0;
+	/* condition to use the variables. */
+	if(var == newvar) return 1;
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+
+printf "%s\n" "#define HAVE_LINK_ATOMIC_STORE 1" >>confdefs.h
+
+
+else $as_nop
+
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+
 ac_fn_check_decl "$LINENO" "inet_pton" "ac_cv_have_decl_inet_pton" "
 $ac_includes_default
 #ifdef HAVE_NETINET_IN_H
@@ -24296,36 +24373,36 @@ fi
 	echo "$PROTOBUFC_PKG_ERRORS" >&5
 
 
-                                # pkg-config failed; try falling back to known values
-                                # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
-                                if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
-                                    CFLAGS="$CFLAGS -I/usr/include/google"
+                            # pkg-config failed; try falling back to known values
+                            # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
+                            if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
+                                CFLAGS="$CFLAGS -I/usr/include/google"
+                            else
+                                if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
+                                    CFLAGS="$CFLAGS -I/usr/local/include/google"
+                                    LDFLAGS="$LDFLAGS -L/usr/local/lib"
                                 else
-                                    if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
-                                        CFLAGS="$CFLAGS -I/usr/local/include/google"
-                                        LDFLAGS="$LDFLAGS -L/usr/local/lib"
-                                    else
-                                        as_fn_error $? "The protobuf-c package was not found with pkg-config. Please install protobuf-c!" "$LINENO" 5
-                                    fi
+                                    as_fn_error $? "The protobuf-c package was not found with pkg-config. Please install protobuf-c!" "$LINENO" 5
                                 fi
+                            fi
 
 
 elif test $pkg_failed = untried; then
      	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
 printf "%s\n" "no" >&6; }
 
-                                # pkg-config failed; try falling back to known values
-                                # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
-                                if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
-                                    CFLAGS="$CFLAGS -I/usr/include/google"
+                            # pkg-config failed; try falling back to known values
+                            # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
+                            if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
+                                CFLAGS="$CFLAGS -I/usr/include/google"
+                            else
+                                if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
+                                    CFLAGS="$CFLAGS -I/usr/local/include/google"
+                                    LDFLAGS="$LDFLAGS -L/usr/local/lib"
                                 else
-                                    if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
-                                        CFLAGS="$CFLAGS -I/usr/local/include/google"
-                                        LDFLAGS="$LDFLAGS -L/usr/local/lib"
-                                    else
-                                        as_fn_error $? "The protobuf-c package was not found with pkg-config. Please install protobuf-c!" "$LINENO" 5
-                                    fi
+                                    as_fn_error $? "The protobuf-c package was not found with pkg-config. Please install protobuf-c!" "$LINENO" 5
                                 fi
+                            fi
 
 
 else
@@ -24334,20 +24411,20 @@ else
         { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 printf "%s\n" "yes" >&6; }
 
-                                CFLAGS="$CFLAGS $PROTOBUFC_CFLAGS"
-                                LIBS="$LIBS $PROTOBUFC_LIBS"
+                            CFLAGS="$CFLAGS $PROTOBUFC_CFLAGS"
+                            LIBS="$LIBS $PROTOBUFC_LIBS"
 
 fi
                 else
-                        # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
-                        if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
-                            CFLAGS="$CFLAGS -I/usr/include/google"
-                        else
-                            if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
-                                CFLAGS="$CFLAGS -I/usr/local/include/google"
-                                LDFLAGS="$LDFLAGS -L/usr/local/lib"
-                            fi
+                    # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
+                    if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
+                        CFLAGS="$CFLAGS -I/usr/include/google"
+                    else
+                        if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
+                            CFLAGS="$CFLAGS -I/usr/local/include/google"
+                            LDFLAGS="$LDFLAGS -L/usr/local/lib"
                         fi
+                    fi
                 fi
 
 
@@ -24761,7 +24838,7 @@ then :
 
 else $as_nop
 
-		# mnl
+		    # mnl
 
 # Check whether --with-libmnl was given.
 if test ${with_libmnl+y}
@@ -24771,13 +24848,13 @@ else $as_nop
    withval="yes"
 fi
 
-		found_libmnl="no"
-		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libmnl" >&5
+		    found_libmnl="no"
+		    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libmnl" >&5
 printf %s "checking for libmnl... " >&6; }
-		if test x_$withval = x_ -o x_$withval = x_yes; then
+		    if test x_$withval = x_ -o x_$withval = x_yes; then
 			withval="/usr/local /opt/local /usr/lib /usr/pkg /usr/sfw /usr"
-		fi
-		for dir in $withval ; do
+		    fi
+		    for dir in $withval ; do
 			if test -f "$dir/include/libmnl/libmnl.h" -o -f "$dir/include/libmnl/libmnl/libmnl.h"; then
 				found_libmnl="yes"
 								extralibmnl=""
@@ -24795,10 +24872,10 @@ printf "%s\n" "found in $dir" >&6; }
 				LIBS="$LIBS -lmnl"
 				break;
 			fi
-		done
-		if test x_$found_libmnl != x_yes; then
-			as_fn_error $? "Could not find libmnl, libmnl.h" "$LINENO" 5
-		fi
+		    done
+		    if test x_$found_libmnl != x_yes; then
+			  as_fn_error $? "Could not find libmnl, libmnl.h" "$LINENO" 5
+		    fi
 
 fi
 
@@ -24997,9 +25074,27 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 1
 
 
 
-version=1.22.0
+version=1.23.1
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5
+printf %s "checking for build time... " >&6; }
+ax_date_fmt="%b %e, %Y"
+if test x"$SOURCE_DATE_EPOCH" = x
+then :
+  date=`date "+$ax_date_fmt"`
+else $as_nop
+  ax_build_date=`date -u -d "@$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null \
+                 || date -u -r "$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null`
+  if test x"$ax_build_date" = x
+then :
+  as_fn_error $? "malformed SOURCE_DATE_EPOCH" "$LINENO" 5
+else $as_nop
+  date=$ax_build_date
+fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $date" >&5
+printf "%s\n" "$date" >&6; }
 
-date=`date +'%b %e, %Y'`
 
 
 ac_config_files="$ac_config_files Makefile doc/example.conf doc/libunbound.3 doc/unbound.8 doc/unbound-anchor.8 doc/unbound-checkconf.8 doc/unbound.conf.5 doc/unbound-control.8 doc/unbound-host.1 smallapp/unbound-control-setup.sh dnstap/dnstap_config.h dnscrypt/dnscrypt_config.h"
@@ -25509,7 +25604,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 unbound $as_me 1.22.0, which was
+This file was extended by unbound $as_me 1.23.1, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -25577,7 +25672,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="\\
-unbound config.status 1.22.0
+unbound config.status 1.23.1
 configured by $0, generated by GNU Autoconf 2.71,
   with options \\"\$ac_cs_config\\"
 
Index: configure.ac
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/configure.ac,v
diff -u -p -r1.57 configure.ac
--- configure.ac	21 Feb 2025 13:20:39 -0000	1.57
+++ configure.ac	30 Aug 2025 11:52:23 -0000
@@ -2,6 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 AC_PREREQ([2.56])
 sinclude(acx_nlnetlabs.m4)
+sinclude(ax_build_date_epoch.m4)
 sinclude(ax_pthread.m4)
 sinclude(acx_python.m4)
 sinclude(ax_pkg_swig.m4)
@@ -10,15 +11,15 @@ sinclude(dnscrypt/dnscrypt.m4)
 
 # must be numbers. ac_defun because of later processing
 m4_define([VERSION_MAJOR],[1])
-m4_define([VERSION_MINOR],[22])
-m4_define([VERSION_MICRO],[0])
+m4_define([VERSION_MINOR],[23])
+m4_define([VERSION_MICRO],[1])
 AC_INIT([unbound],m4_defn([VERSION_MAJOR]).m4_defn([VERSION_MINOR]).m4_defn([VERSION_MICRO]),[unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues],[unbound])
 AC_SUBST(UNBOUND_VERSION_MAJOR, [VERSION_MAJOR])
 AC_SUBST(UNBOUND_VERSION_MINOR, [VERSION_MINOR])
 AC_SUBST(UNBOUND_VERSION_MICRO, [VERSION_MICRO])
 
 LIBUNBOUND_CURRENT=9
-LIBUNBOUND_REVISION=30
+LIBUNBOUND_REVISION=32
 LIBUNBOUND_AGE=1
 # 1.0.0 had 0:12:0
 # 1.0.1 had 0:13:0
@@ -116,6 +117,8 @@ LIBUNBOUND_AGE=1
 # 1.21.0 had 9:28:1
 # 1.21.1 had 9:29:1
 # 1.22.0 had 9:30:1
+# 1.23.0 had 9:31:1
+# 1.23.1 had 9:32:1
 
 #   Current  -- the number of the binary API that we're implementing
 #   Revision -- which iteration of the implementation of the binary
@@ -522,6 +525,7 @@ AC_CHECK_HEADERS([netioapi.h],,, [AC_INC
 
 # Check for Linux timestamping headers
 AC_CHECK_HEADERS([linux/net_tstamp.h],,, [AC_INCLUDES_DEFAULT])
+AC_CHECK_HEADERS([stdatomic.h],,, [AC_INCLUDES_DEFAULT])
 
 # check for types.  
 # Using own tests for int64* because autoconf builtin only give 32bit.
@@ -907,7 +911,8 @@ if test "`uname`" = "Linux"; then
 	GCC_DOCKER_LINTFLAGS='-syntax'
 	AC_SUBST(GCC_DOCKER_LINTFLAGS)
 fi
-CONFIG_DATE=`date +%Y%m%d`
+AX_BUILD_DATE_EPOCH(CONFIG_DATE, [%Y%m%d])
+AC_ARG_VAR(SOURCE_DATE_EPOCH, [If it is set, it uses the value of that variable instead of the current time as the build timestamp. The format is a unix timestamp. This enables reproducible build output.])
 AC_SUBST(CONFIG_DATE)
 
 # Checks for libraries.
@@ -1236,8 +1241,8 @@ case "$enable_ecdsa" in
 		  ])
 		], [
 		  # without EVP_PKEY_fromdata, older openssl, check for support
-	      AC_CHECK_FUNC(ECDSA_sign, [], [AC_MSG_ERROR([OpenSSL does not support ECDSA: please upgrade or rerun with --disable-ecdsa])])
-	      AC_CHECK_FUNC(SHA384_Init, [], [AC_MSG_ERROR([OpenSSL does not support SHA384: please upgrade or rerun with --disable-ecdsa])])
+		  AC_CHECK_FUNC(ECDSA_sign, [], [AC_MSG_ERROR([OpenSSL does not support ECDSA: please upgrade or rerun with --disable-ecdsa])])
+		  AC_CHECK_FUNC(SHA384_Init, [], [AC_MSG_ERROR([OpenSSL does not support SHA384: please upgrade or rerun with --disable-ecdsa])])
 		])
 	      AC_CHECK_DECLS([NID_X9_62_prime256v1, NID_secp384r1], [], [AC_MSG_ERROR([OpenSSL does not support the ECDSA curves: please upgrade or rerun with --disable-ecdsa])], [AC_INCLUDES_DEFAULT
 #include <openssl/evp.h>
@@ -1814,6 +1819,25 @@ AC_CHECK_FUNCS([setresgid],,[AC_CHECK_FU
 if echo $host_os | grep darwin8 > /dev/null; then
 	AC_DEFINE(DARWIN_BROKEN_SETREUID, 1, [Define this if on macOSX10.4-darwin8 and setreuid and setregid do not work])
 fi
+
+AC_MSG_CHECKING([for atomic_store])
+AC_LINK_IFELSE([AC_LANG_PROGRAM(AC_INCLUDES_DEFAULT [[
+#ifdef HAVE_STDATOMIC_H
+#include <stdatomic.h>
+#endif
+]], [[
+	int newvar = 5, var = 0;
+	atomic_store((_Atomic int*)&var, newvar);
+	newvar = 0;
+	/* condition to use the variables. */
+	if(var == newvar) return 1;
+]])], [
+	AC_MSG_RESULT(yes)
+	AC_DEFINE(HAVE_LINK_ATOMIC_STORE, 1, [If we have atomic_store])
+], [
+	AC_MSG_RESULT([no])
+])
+
 AC_CHECK_DECLS([inet_pton,inet_ntop], [], [], [
 AC_INCLUDES_DEFAULT
 #ifdef HAVE_NETINET_IN_H
@@ -2040,15 +2064,15 @@ case "$enable_ipset" in
 
 		# BSD's pf
 		AC_CHECK_HEADERS([net/pfvar.h], [], [
-		# mnl
-		AC_ARG_WITH(libmnl, AS_HELP_STRING([--with-libmnl=path],[specify explicit path for libmnl.]),
+		    # mnl
+		    AC_ARG_WITH(libmnl, AS_HELP_STRING([--with-libmnl=path],[specify explicit path for libmnl.]),
 			[ ],[ withval="yes" ])
-		found_libmnl="no"
-		AC_MSG_CHECKING(for libmnl)
-		if test x_$withval = x_ -o x_$withval = x_yes; then
+		    found_libmnl="no"
+		    AC_MSG_CHECKING(for libmnl)
+		    if test x_$withval = x_ -o x_$withval = x_yes; then
 			withval="/usr/local /opt/local /usr/lib /usr/pkg /usr/sfw /usr"
-		fi
-		for dir in $withval ; do
+		    fi
+		    for dir in $withval ; do
 			if test -f "$dir/include/libmnl/libmnl.h" -o -f "$dir/include/libmnl/libmnl/libmnl.h"; then
 				found_libmnl="yes"
 				dnl assume /usr is in default path.
@@ -2066,10 +2090,10 @@ case "$enable_ipset" in
 				LIBS="$LIBS -lmnl"
 				break;
 			fi
-		done
-		if test x_$found_libmnl != x_yes; then
-			AC_MSG_ERROR([Could not find libmnl, libmnl.h])
-		fi
+		    done
+		    if test x_$found_libmnl != x_yes; then
+			  AC_MSG_ERROR([Could not find libmnl, libmnl.h])
+		    fi
 		], [
 		  #include <netinet/in.h>
 		  #include <net/if.h>
@@ -2434,7 +2458,8 @@ char *unbound_stat_strdup_log(const char
 dnl if we build from source tree, the man pages need @date@ and @version@
 dnl if this is a distro tarball, that was already done by makedist.sh
 AC_SUBST(version, [VERSION_MAJOR.VERSION_MINOR.VERSION_MICRO])
-AC_SUBST(date, [`date +'%b %e, %Y'`])
+AX_BUILD_DATE_EPOCH(date, [[%b %e, %Y]])
+AC_SUBST(date)
 
 AC_CONFIG_FILES([Makefile doc/example.conf doc/libunbound.3 doc/unbound.8 doc/unbound-anchor.8 doc/unbound-checkconf.8 doc/unbound.conf.5 doc/unbound-control.8 doc/unbound-host.1 smallapp/unbound-control-setup.sh dnstap/dnstap_config.h dnscrypt/dnscrypt_config.h])
 AC_CONFIG_HEADERS([config.h])
Index: cachedb/cachedb.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/cachedb/cachedb.c,v
diff -u -p -r1.21 cachedb.c
--- cachedb/cachedb.c	21 Feb 2025 13:20:40 -0000	1.21
+++ cachedb/cachedb.c	30 Aug 2025 11:52:23 -0000
@@ -47,6 +47,7 @@
 #include "util/regional.h"
 #include "util/net_help.h"
 #include "util/config_file.h"
+#include "util/data/dname.h"
 #include "util/data/msgreply.h"
 #include "util/data/msgencode.h"
 #include "services/cache/dns.h"
@@ -341,6 +342,7 @@ calc_hash(struct query_info* qinfo, stru
 	/* copy the hash info into the clear buffer */
 	if(clen + qinfo->qname_len < sizeof(clear)) {
 		memmove(clear+clen, qinfo->qname, qinfo->qname_len);
+		query_dname_tolower(clear+clen);
 		clen += qinfo->qname_len;
 	}
 	if(clen + 4 < sizeof(clear)) {
@@ -755,7 +757,8 @@ cachedb_intcache_store(struct module_qst
 	}
 	(void)dns_cache_store(qstate->env, &qstate->qinfo,
 		qstate->return_msg->rep, 0, qstate->prefetch_leeway, 0,
-		qstate->region, store_flags, qstate->qstarttime);
+		qstate->region, store_flags, qstate->qstarttime,
+		qstate->is_valrec);
 	if(serve_expired && msg_expired) {
 		if(qstate->env->cfg->serve_expired_client_timeout) {
 			/* No expired response from the query state, the
Index: cachedb/redis.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/cachedb/redis.c,v
diff -u -p -r1.1.1.5 redis.c
--- cachedb/redis.c	21 Feb 2025 13:17:14 -0000	1.1.1.5
+++ cachedb/redis.c	30 Aug 2025 11:52:23 -0000
@@ -52,19 +52,38 @@
 #include "hiredis/hiredis.h"
 
 struct redis_moddata {
-	redisContext** ctxs;	/* thread-specific redis contexts */
-	int numctxs;		/* number of ctx entries */
-	const char* server_host; /* server's IP address or host name */
-	int server_port;	 /* server's TCP port */
-	const char* server_path; /* server's unix path, or "", NULL if unused */
-	const char* server_password; /* server's AUTH password, or "", NULL if unused */
-	struct timeval command_timeout;	 /* timeout for commands */
-	struct timeval connect_timeout;	 /* timeout for connect */
-	int logical_db;		/* the redis logical database to use */
+	/* thread-specific redis contexts */
+	redisContext** ctxs;
+	redisContext** replica_ctxs;
+	/* number of ctx entries */
+	int numctxs;
+	/* server's IP address or host name */
+	const char* server_host;
+	const char* replica_server_host;
+	/* server's TCP port */
+	int server_port;
+	int replica_server_port;
+	/* server's unix path, or "", NULL if unused */
+	const char* server_path;
+	const char* replica_server_path;
+	/* server's AUTH password, or "", NULL if unused */
+	const char* server_password;
+	const char* replica_server_password;
+	/* timeout for commands */
+	struct timeval command_timeout;
+	struct timeval replica_command_timeout;
+	/* timeout for connection setup */
+	struct timeval connect_timeout;
+	struct timeval replica_connect_timeout;
+	/* the redis logical database to use */
+	int logical_db;
+	int replica_logical_db;
+	/* if the SET with EX command is supported */
+	int set_with_ex_available;
 };
 
 static redisReply* redis_command(struct module_env*, struct cachedb_env*,
-	const char*, const uint8_t*, size_t);
+	const char*, const uint8_t*, size_t, int);
 
 static void
 moddata_clean(struct redis_moddata** moddata) {
@@ -78,21 +97,30 @@ moddata_clean(struct redis_moddata** mod
 		}
 		free((*moddata)->ctxs);
 	}
+	if((*moddata)->replica_ctxs) {
+		int i;
+		for(i = 0; i < (*moddata)->numctxs; i++) {
+			if((*moddata)->replica_ctxs[i])
+				redisFree((*moddata)->replica_ctxs[i]);
+		}
+		free((*moddata)->replica_ctxs);
+	}
 	free(*moddata);
 	*moddata = NULL;
 }
 
 static redisContext*
-redis_connect(const struct redis_moddata* moddata)
+redis_connect(const char* host, int port, const char* path,
+	const char* password, int logical_db,
+	const struct timeval connect_timeout,
+	const struct timeval command_timeout)
 {
 	redisContext* ctx;
 
-	if(moddata->server_path && moddata->server_path[0]!=0) {
-		ctx = redisConnectUnixWithTimeout(moddata->server_path,
-			moddata->connect_timeout);
+	if(path && path[0]!=0) {
+		ctx = redisConnectUnixWithTimeout(path, connect_timeout);
 	} else {
-		ctx = redisConnectWithTimeout(moddata->server_host,
-			moddata->server_port, moddata->connect_timeout);
+		ctx = redisConnectWithTimeout(host, port, connect_timeout);
 	}
 	if(!ctx || ctx->err) {
 		const char *errstr = "out of memory";
@@ -101,13 +129,13 @@ redis_connect(const struct redis_moddata
 		log_err("failed to connect to redis server: %s", errstr);
 		goto fail;
 	}
-	if(redisSetTimeout(ctx, moddata->command_timeout) != REDIS_OK) {
-		log_err("failed to set redis timeout");
+	if(redisSetTimeout(ctx, command_timeout) != REDIS_OK) {
+		log_err("failed to set redis timeout, %s", ctx->errstr);
 		goto fail;
 	}
-	if(moddata->server_password && moddata->server_password[0]!=0) {
+	if(password && password[0]!=0) {
 		redisReply* rep;
-		rep = redisCommand(ctx, "AUTH %s", moddata->server_password);
+		rep = redisCommand(ctx, "AUTH %s", password);
 		if(!rep || rep->type == REDIS_REPLY_ERROR) {
 			log_err("failed to authenticate with password");
 			freeReplyObject(rep);
@@ -115,18 +143,25 @@ redis_connect(const struct redis_moddata
 		}
 		freeReplyObject(rep);
 	}
-	if(moddata->logical_db > 0) {
+	if(logical_db > 0) {
 		redisReply* rep;
-		rep = redisCommand(ctx, "SELECT %d", moddata->logical_db);
+		rep = redisCommand(ctx, "SELECT %d", logical_db);
 		if(!rep || rep->type == REDIS_REPLY_ERROR) {
 			log_err("failed to set logical database (%d)",
-				moddata->logical_db);
+				logical_db);
 			freeReplyObject(rep);
 			goto fail;
 		}
 		freeReplyObject(rep);
 	}
-	verbose(VERB_OPS, "Connection to Redis established");
+	if(verbosity >= VERB_OPS) {
+		char port_str[6+1];
+		port_str[0] = ' ';
+		(void)snprintf(port_str+1, sizeof(port_str)-1, "%d", port);
+		verbose(VERB_OPS, "Connection to Redis established (%s%s)",
+			path&&path[0]!=0?path:host,
+			path&&path[0]!=0?"":port_str);
+	}
 	return ctx;
 
 fail:
@@ -135,6 +170,14 @@ fail:
 	return NULL;
 }
 
+static void
+set_timeout(struct timeval* timeout, int value, int explicit_value)
+{
+	int v = explicit_value != 0 ? explicit_value : value;
+	timeout->tv_sec = v / 1000;
+	timeout->tv_usec = (v % 1000) * 1000;
+}
+
 static int
 redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
 {
@@ -149,57 +192,98 @@ redis_init(struct module_env* env, struc
 		goto fail;
 	}
 	moddata->numctxs = env->cfg->num_threads;
+	/* note: server_host and similar string configuration options are
+	 * shallow references to configured strings; we don't have to free them
+	 * in this module. */
+	moddata->server_host = env->cfg->redis_server_host;
+	moddata->replica_server_host = env->cfg->redis_replica_server_host;
+
+	moddata->server_port = env->cfg->redis_server_port;
+	moddata->replica_server_port = env->cfg->redis_replica_server_port;
+
+	moddata->server_path = env->cfg->redis_server_path;
+	moddata->replica_server_path = env->cfg->redis_replica_server_path;
+
+	moddata->server_password = env->cfg->redis_server_password;
+	moddata->replica_server_password = env->cfg->redis_replica_server_password;
+
+	set_timeout(&moddata->command_timeout,
+		env->cfg->redis_timeout,
+		env->cfg->redis_command_timeout);
+	set_timeout(&moddata->replica_command_timeout,
+		env->cfg->redis_replica_timeout,
+		env->cfg->redis_replica_command_timeout);
+	set_timeout(&moddata->connect_timeout,
+		env->cfg->redis_timeout,
+		env->cfg->redis_connect_timeout);
+	set_timeout(&moddata->replica_connect_timeout,
+		env->cfg->redis_replica_timeout,
+		env->cfg->redis_replica_connect_timeout);
+
+	moddata->logical_db = env->cfg->redis_logical_db;
+	moddata->replica_logical_db = env->cfg->redis_replica_logical_db;
+
 	moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
 	if(!moddata->ctxs) {
 		log_err("out of memory");
 		goto fail;
 	}
-	/* note: server_host is a shallow reference to configured string.
-	 * we don't have to free it in this module. */
-	moddata->server_host = env->cfg->redis_server_host;
-	moddata->server_port = env->cfg->redis_server_port;
-	moddata->server_path = env->cfg->redis_server_path;
-	moddata->server_password = env->cfg->redis_server_password;
-	moddata->command_timeout.tv_sec = env->cfg->redis_timeout / 1000;
-	moddata->command_timeout.tv_usec =
-		(env->cfg->redis_timeout % 1000) * 1000;
-	moddata->connect_timeout.tv_sec = env->cfg->redis_timeout / 1000;
-	moddata->connect_timeout.tv_usec =
-		(env->cfg->redis_timeout % 1000) * 1000;
-	if(env->cfg->redis_command_timeout != 0) {
-		moddata->command_timeout.tv_sec =
-			env->cfg->redis_command_timeout / 1000;
-		moddata->command_timeout.tv_usec =
-			(env->cfg->redis_command_timeout % 1000) * 1000;
-	}
-	if(env->cfg->redis_connect_timeout != 0) {
-		moddata->connect_timeout.tv_sec =
-			env->cfg->redis_connect_timeout / 1000;
-		moddata->connect_timeout.tv_usec =
-			(env->cfg->redis_connect_timeout % 1000) * 1000;
+	if((moddata->replica_server_host && moddata->replica_server_host[0]!=0)
+		|| (moddata->replica_server_path && moddata->replica_server_path[0]!=0)) {
+		/* There is a replica configured, allocate ctxs */
+		moddata->replica_ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
+		if(!moddata->replica_ctxs) {
+			log_err("out of memory");
+			goto fail;
+		}
 	}
-	moddata->logical_db = env->cfg->redis_logical_db;
 	for(i = 0; i < moddata->numctxs; i++) {
-		redisContext* ctx = redis_connect(moddata);
+		redisContext* ctx = redis_connect(
+			moddata->server_host,
+			moddata->server_port,
+			moddata->server_path,
+			moddata->server_password,
+			moddata->logical_db,
+			moddata->connect_timeout,
+			moddata->command_timeout);
 		if(!ctx) {
-			log_err("redis_init: failed to init redis");
-			goto fail;
+			log_err("redis_init: failed to init redis "
+				"(for thread %d)", i);
+			/* And continue, the context can be established
+			 * later, just like after a disconnect. */
 		}
 		moddata->ctxs[i] = ctx;
 	}
+	if(moddata->replica_ctxs) {
+		for(i = 0; i < moddata->numctxs; i++) {
+			redisContext* ctx = redis_connect(
+				moddata->replica_server_host,
+				moddata->replica_server_port,
+				moddata->replica_server_path,
+				moddata->replica_server_password,
+				moddata->replica_logical_db,
+				moddata->replica_connect_timeout,
+				moddata->replica_command_timeout);
+			if(!ctx) {
+				log_err("redis_init: failed to init redis "
+					"replica (for thread %d)", i);
+				/* And continue, the context can be established
+				* later, just like after a disconnect. */
+			}
+			moddata->replica_ctxs[i] = ctx;
+		}
+	}
 	cachedb_env->backend_data = moddata;
-	if(env->cfg->redis_expire_records) {
+	if(env->cfg->redis_expire_records &&
+		moddata->ctxs[env->alloc->thread_num] != NULL) {
 		redisReply* rep = NULL;
 		int redis_reply_type = 0;
-		/** check if setex command is supported */
+		/** check if set with ex command is supported */
 		rep = redis_command(env, cachedb_env,
-			"SETEX __UNBOUND_REDIS_CHECK__ 1 none", NULL, 0);
+			"SET __UNBOUND_REDIS_CHECK__ none EX 1", NULL, 0, 1);
 		if(!rep) {
 			/** init failed, no response from redis server*/
-			log_err("redis_init: failed to init redis, the "
-				"redis-expire-records option requires the SETEX command "
-				"(redis >= 2.0.0)");
-			goto fail;
+			goto set_with_ex_fail;
 		}
 		redis_reply_type = rep->type;
 		freeReplyObject(rep);
@@ -207,15 +291,18 @@ redis_init(struct module_env* env, struc
 		case REDIS_REPLY_STATUS:
 			break;
 		default:
-			/** init failed, setex command not supported */
-			log_err("redis_init: failed to init redis, the "
-				"redis-expire-records option requires the SETEX command "
-				"(redis >= 2.0.0)");
-			goto fail;
+			/** init failed, set_with_ex command not supported */
+			goto set_with_ex_fail;
 		}
+		moddata->set_with_ex_available = 1;
 	}
 	return 1;
 
+set_with_ex_fail:
+	log_err("redis_init: failure during redis_init, the "
+		"redis-expire-records option requires the SET with EX command "
+		"(redis >= 2.6.2)");
+	return 1;
 fail:
 	moddata_clean(&moddata);
 	return 0;
@@ -246,9 +333,9 @@ redis_deinit(struct module_env* env, str
  */
 static redisReply*
 redis_command(struct module_env* env, struct cachedb_env* cachedb_env,
-	const char* command, const uint8_t* data, size_t data_len)
+	const char* command, const uint8_t* data, size_t data_len, int write)
 {
-	redisContext* ctx;
+	redisContext* ctx, **ctx_selector;
 	redisReply* rep;
 	struct redis_moddata* d = (struct redis_moddata*)
 		cachedb_env->backend_data;
@@ -259,17 +346,38 @@ redis_command(struct module_env* env, st
 	 * assumption throughout the unbound architecture, so we simply assert
 	 * it. */
 	log_assert(env->alloc->thread_num < d->numctxs);
-	ctx = d->ctxs[env->alloc->thread_num];
+
+	ctx_selector = !write && d->replica_ctxs
+		?d->replica_ctxs
+		:d->ctxs;
+	ctx = ctx_selector[env->alloc->thread_num];
 
 	/* If we've not established a connection to the server or we've closed
 	 * it on a failure, try to re-establish a new one.   Failures will be
 	 * logged in redis_connect(). */
 	if(!ctx) {
-		ctx = redis_connect(d);
-		d->ctxs[env->alloc->thread_num] = ctx;
+		if(!write && d->replica_ctxs) {
+			ctx = redis_connect(
+				d->replica_server_host,
+				d->replica_server_port,
+				d->replica_server_path,
+				d->replica_server_password,
+				d->replica_logical_db,
+				d->replica_connect_timeout,
+				d->replica_command_timeout);
+		} else {
+			ctx = redis_connect(
+				d->server_host,
+				d->server_port,
+				d->server_path,
+				d->server_password,
+				d->logical_db,
+				d->connect_timeout,
+				d->command_timeout);
+		}
+		ctx_selector[env->alloc->thread_num] = ctx;
 	}
-	if(!ctx)
-		return NULL;
+	if(!ctx) return NULL;
 
 	/* Send the command and get a reply, synchronously. */
 	rep = (redisReply*)redisCommand(ctx, command, data, data_len);
@@ -279,7 +387,7 @@ redis_command(struct module_env* env, st
 		log_err("redis_command: failed to receive a reply, "
 			"closing connection: %s", ctx->errstr);
 		redisFree(ctx);
-		d->ctxs[env->alloc->thread_num] = NULL;
+		ctx_selector[env->alloc->thread_num] = NULL;
 		return NULL;
 	}
 
@@ -309,7 +417,7 @@ redis_lookup(struct module_env* env, str
 		return 0;
 	}
 
-	rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0);
+	rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0, 0);
 	if(!rep)
 		return 0;
 	switch(rep->type) {
@@ -346,11 +454,16 @@ redis_store(struct module_env* env, stru
 {
 	redisReply* rep;
 	int n;
-	int set_ttl = (env->cfg->redis_expire_records &&
+	struct redis_moddata* moddata = (struct redis_moddata*)
+		cachedb_env->backend_data;
+	int set_ttl = (moddata->set_with_ex_available &&
+		env->cfg->redis_expire_records &&
 		(!env->cfg->serve_expired || env->cfg->serve_expired_ttl > 0));
 	/* Supported commands:
 	 * - "SET " + key + " %b"
-	 * - "SETEX " + key + " " + ttl + " %b"
+	 * - "SET " + key + " %b EX " + ttl
+	 *   older redis 2.0.0 was "SETEX " + key + " " + ttl + " %b"
+	 * - "EXPIRE " + key + " 0"
 	 */
 	char cmdbuf[6+(CACHEDB_HASHSIZE/8)*2+11+3+1];
 
@@ -358,14 +471,22 @@ redis_store(struct module_env* env, stru
 		verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len);
 		/* build command to set to a binary safe string */
 		n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key);
+	} else if(ttl == 0) {
+		/* use the EXPIRE command, SET with EX 0 is an invalid time. */
+		/* Replies with REDIS_REPLY_INTEGER of 1. */
+		verbose(VERB_ALGO, "redis_store expire %s (%d bytes)",
+			key, (int)data_len);
+		n = snprintf(cmdbuf, sizeof(cmdbuf), "EXPIRE %s 0", key);
+		data = NULL;
+		data_len = 0;
 	} else {
 		/* add expired ttl time to redis ttl to avoid premature eviction of key */
 		ttl += env->cfg->serve_expired_ttl;
 		verbose(VERB_ALGO, "redis_store %s (%d bytes) with ttl %u",
-			key, (int)data_len, (uint32_t)ttl);
+			key, (int)data_len, (unsigned)(uint32_t)ttl);
 		/* build command to set to a binary safe string */
-		n = snprintf(cmdbuf, sizeof(cmdbuf), "SETEX %s %u %%b", key,
-			(uint32_t)ttl);
+		n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b EX %u", key,
+			(unsigned)(uint32_t)ttl);
 	}
 
 
@@ -374,11 +495,12 @@ redis_store(struct module_env* env, stru
 		return;
 	}
 
-	rep = redis_command(env, cachedb_env, cmdbuf, data, data_len);
+	rep = redis_command(env, cachedb_env, cmdbuf, data, data_len, 1);
 	if(rep) {
 		verbose(VERB_ALGO, "redis_store set completed");
 		if(rep->type != REDIS_REPLY_STATUS &&
-			rep->type != REDIS_REPLY_ERROR) {
+			rep->type != REDIS_REPLY_ERROR &&
+			rep->type != REDIS_REPLY_INTEGER) {
 			log_err("redis_store: unexpected type of reply (%d)",
 				rep->type);
 		}
Index: daemon/acl_list.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/acl_list.c,v
diff -u -p -r1.7 acl_list.c
--- daemon/acl_list.c	5 Sep 2023 11:12:10 -0000	1.7
+++ daemon/acl_list.c	30 Aug 2025 11:52:23 -0000
@@ -221,7 +221,9 @@ acl_interface_insert(struct acl_list* ac
 	struct sockaddr_storage* addr, socklen_t addrlen,
 	enum acl_access control)
 {
-	return acl_find_or_create(acl_interface, addr, addrlen, control);
+	struct acl_addr* node = acl_find_or_create(acl_interface, addr, addrlen, control);
+	node->is_interface = 1;
+	return node;
 }
 
 /** apply acl_tag string */
@@ -551,17 +553,6 @@ acl_list_apply_cfg(struct acl_list* acl,
 	return 1;
 }
 
-int
-acl_interface_compare(const void* k1, const void* k2)
-{
-	struct addr_tree_node* n1 = (struct addr_tree_node*)k1;
-	struct addr_tree_node* n2 = (struct addr_tree_node*)k2;
-	return sockaddr_cmp(&n1->addr, n1->addrlen, &n2->addr,
-		n2->addrlen);
-	/* We don't care about comparing node->net. All addresses in the
-	 * acl_interface tree have either 32 (ipv4) or 128 (ipv6). */
-}
-
 void
 acl_interface_init(struct acl_list* acl_interface)
 {
@@ -816,10 +807,23 @@ log_acl_action(const char* action, struc
 		addr_to_str(&acladdr->node.addr, acladdr->node.addrlen,
 			n, sizeof(n));
 		verbose(VERB_ALGO, "%s query from %s port %d because of "
-			"%s/%d %s", action, a, (int)port, n, acladdr->node.net,
+			"%s/%d %s%s", action, a, (int)port, n,
+			acladdr->node.net,
+			acladdr->is_interface?"(ACL on interface IP) ":"",
 			acl_access_to_str(acl));
 	} else {
 		verbose(VERB_ALGO, "%s query from %s port %d", action, a,
 			(int)port);
 	}
+}
+
+void acl_list_swap_tree(struct acl_list* acl, struct acl_list* data)
+{
+	/* swap tree and region */
+	rbtree_type oldtree = acl->tree;
+	struct regional* oldregion = acl->region;
+	acl->tree = data->tree;
+	acl->region = data->region;
+	data->tree = oldtree;
+	data->region = oldregion;
 }
Index: daemon/acl_list.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/acl_list.h,v
diff -u -p -r1.6 acl_list.h
--- daemon/acl_list.h	5 Sep 2023 11:12:10 -0000	1.6
+++ daemon/acl_list.h	30 Aug 2025 11:52:23 -0000
@@ -107,6 +107,8 @@ struct acl_addr {
 	struct config_strlist** tag_datas;
 	/** size of the tag_datas array */
 	size_t tag_datas_size;
+	/* If the acl node is for an interface */
+	int is_interface;
 	/* view element, NULL if none */
 	struct view* view;
 };
@@ -147,9 +149,6 @@ acl_interface_insert(struct acl_list* ac
 int acl_list_apply_cfg(struct acl_list* acl, struct config_file* cfg,
 	struct views* v);
 
-/** compare ACL interface "addr_tree" nodes (+port) */
-int acl_interface_compare(const void* k1, const void* k2);
-
 /**
  * Initialise (also clean) the acl_interface struct.
  * @param acl_interface: where to store.
@@ -201,5 +200,13 @@ const char* acl_access_to_str(enum acl_a
 /* log acl and addr for action */
 void log_acl_action(const char* action, struct sockaddr_storage* addr,
 	socklen_t addrlen, enum acl_access acl, struct acl_addr* acladdr);
+
+/**
+ * Swap internal tree with preallocated entries.
+ * @param acl: the acl structure.
+ * @param data: the data structure used to take elements from. This contains
+ * 	the old elements on return.
+ */
+void acl_list_swap_tree(struct acl_list* acl, struct acl_list* data);
 
 #endif /* DAEMON_ACL_LIST_H */
Index: daemon/cachedump.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/cachedump.c,v
diff -u -p -r1.11 cachedump.c
--- daemon/cachedump.c	13 Jun 2024 14:30:28 -0000	1.11
+++ daemon/cachedump.c	30 Aug 2025 11:52:23 -0000
@@ -692,7 +692,7 @@ load_msg(RES* ssl, sldns_buffer* buf, st
 		return 1; /* skip this one, not all references satisfied */
 
 	if(!dns_cache_store(&worker->env, &qinf, &rep, 0, 0, 0, NULL, flags,
-		*worker->env.now)) {
+		*worker->env.now, 1)) {
 		log_warn("error out of memory");
 		return 0;
 	}
@@ -836,7 +836,7 @@ int print_deleg_lookup(RES* ssl, struct 
 	struct delegpt* dp;
 	struct dns_msg* msg;
 	struct regional* region = worker->scratchpad;
-	char b[260];
+	char b[LDNS_MAX_DOMAINLEN];
 	struct query_info qinfo;
 	struct iter_hints_stub* stub;
 	int nolock = 0;
Index: daemon/daemon.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/daemon.c,v
diff -u -p -r1.27 daemon.c
--- daemon/daemon.c	21 Feb 2025 13:20:40 -0000	1.27
+++ daemon/daemon.c	30 Aug 2025 11:52:23 -0000
@@ -323,8 +323,7 @@ daemon_init(void)
 	return daemon;	
 }
 
-static int setup_acl_for_ports(struct acl_list* list,
-	struct listen_port* port_list)
+int setup_acl_for_ports(struct acl_list* list, struct listen_port* port_list)
 {
 	struct acl_addr* acl_node;
 	for(; port_list; port_list=port_list->next) {
@@ -717,16 +716,16 @@ daemon_fork(struct daemon* daemon)
 #endif
 
 	log_assert(daemon);
-	if(!(daemon->views = views_create()))
+	if(!(daemon->env->views = views_create()))
 		fatal_exit("Could not create views: out of memory");
 	/* create individual views and their localzone/data trees */
-	if(!views_apply_cfg(daemon->views, daemon->cfg))
+	if(!views_apply_cfg(daemon->env->views, daemon->cfg))
 		fatal_exit("Could not set up views");
 
-	if(!acl_list_apply_cfg(daemon->acl, daemon->cfg, daemon->views))
+	if(!acl_list_apply_cfg(daemon->acl, daemon->cfg, daemon->env->views))
 		fatal_exit("Could not setup access control list");
 	if(!acl_interface_apply_cfg(daemon->acl_interface, daemon->cfg,
-		daemon->views))
+		daemon->env->views))
 		fatal_exit("Could not setup interface control list");
 	if(!tcl_list_apply_cfg(daemon->tcl, daemon->cfg))
 		fatal_exit("Could not setup TCP connection limits");
@@ -762,15 +761,15 @@ daemon_fork(struct daemon* daemon)
 		fatal_exit("Could not set root or stub hints");
 
 	/* process raw response-ip configuration data */
-	if(!(daemon->respip_set = respip_set_create()))
+	if(!(daemon->env->respip_set = respip_set_create()))
 		fatal_exit("Could not create response IP set");
-	if(!respip_global_apply_cfg(daemon->respip_set, daemon->cfg))
+	if(!respip_global_apply_cfg(daemon->env->respip_set, daemon->cfg))
 		fatal_exit("Could not set up response IP set");
-	if(!respip_views_apply_cfg(daemon->views, daemon->cfg,
+	if(!respip_views_apply_cfg(daemon->env->views, daemon->cfg,
 		&have_view_respip_cfg))
 		fatal_exit("Could not set up per-view response IP sets");
-	daemon->use_response_ip = !respip_set_is_empty(daemon->respip_set) ||
-		have_view_respip_cfg;
+	daemon->use_response_ip = !respip_set_is_empty(
+		daemon->env->respip_set) || have_view_respip_cfg;
 
 	/* setup modules */
 	daemon_setup_modules(daemon);
@@ -886,14 +885,18 @@ daemon_cleanup(struct daemon* daemon)
 	daemon->env->hints = NULL;
 	local_zones_delete(daemon->local_zones);
 	daemon->local_zones = NULL;
-	respip_set_delete(daemon->respip_set);
-	daemon->respip_set = NULL;
-	views_delete(daemon->views);
-	daemon->views = NULL;
+	respip_set_delete(daemon->env->respip_set);
+	daemon->env->respip_set = NULL;
+	views_delete(daemon->env->views);
+	daemon->env->views = NULL;
 	if(daemon->env->auth_zones)
 		auth_zones_cleanup(daemon->env->auth_zones);
 	/* key cache is cleared by module deinit during next daemon_fork() */
 	daemon_remote_clear(daemon->rc);
+	if(daemon->fast_reload_thread)
+		fast_reload_thread_stop(daemon->fast_reload_thread);
+	if(daemon->fast_reload_printq_list)
+		fast_reload_printq_list_delete(daemon->fast_reload_printq_list);
 	for(i=0; i<daemon->num; i++)
 		worker_delete(daemon->workers[i]);
 	free(daemon->workers);
@@ -951,11 +954,16 @@ daemon_delete(struct daemon* daemon)
 	listen_desetup_locks();
 	free(daemon->chroot);
 	free(daemon->pidfile);
+	free(daemon->cfgfile);
 	free(daemon->env);
 #ifdef HAVE_SSL
 	listen_sslctx_delete_ticket_keys();
-	SSL_CTX_free((SSL_CTX*)daemon->listen_sslctx);
-	SSL_CTX_free((SSL_CTX*)daemon->connect_sslctx);
+	SSL_CTX_free((SSL_CTX*)daemon->listen_dot_sslctx);
+	SSL_CTX_free((SSL_CTX*)daemon->listen_doh_sslctx);
+	SSL_CTX_free((SSL_CTX*)daemon->connect_dot_sslctx);
+#endif
+#ifdef HAVE_NGTCP2
+	SSL_CTX_free((SSL_CTX*)daemon->listen_quic_sslctx);
 #endif
 	free(daemon);
 	/* lex cleanup */
Index: daemon/daemon.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/daemon.h,v
diff -u -p -r1.10 daemon.h
--- daemon/daemon.h	21 Feb 2025 13:20:40 -0000	1.10
+++ daemon/daemon.h	30 Aug 2025 11:52:23 -0000
@@ -60,6 +60,8 @@ struct respip_set;
 struct shm_main_info;
 struct doq_table;
 struct cookie_secrets;
+struct fast_reload_thread;
+struct fast_reload_printq;
 
 #include "dnstap/dnstap_config.h"
 #ifdef USE_DNSTAP
@@ -97,8 +99,14 @@ struct daemon {
 	struct listen_port* rc_ports;
 	/** remote control connections management (for first worker) */
 	struct daemon_remote* rc;
-	/** ssl context for listening to dnstcp over ssl, and connecting ssl */
-	void* listen_sslctx, *connect_sslctx;
+	/** ssl context for listening to dnstcp over ssl */
+	void* listen_dot_sslctx;
+	/** ssl context for connecting to dnstcp over ssl */
+	void* connect_dot_sslctx;
+	/** ssl context for listening to DoH */
+	void* listen_doh_sslctx;
+	/** ssl context for listening to quic */
+	void* listen_quic_sslctx;
 	/** num threads allocated */
 	int num;
 	/** num threads allocated in the previous config or 0 at first */
@@ -131,15 +139,11 @@ struct daemon {
 	struct timeval time_last_stat;
 	/** time when daemon started */
 	struct timeval time_boot;
-	/** views structure containing view tree */
-	struct views* views;
 #ifdef USE_DNSTAP
 	/** the dnstap environment master value, copied and changed by threads*/
 	struct dt_env* dtenv;
 #endif
 	struct shm_main_info* shm_info;
-	/** response-ip set with associated actions and tags. */
-	struct respip_set* respip_set;
 	/** some response-ip tags or actions are configured if true */
 	int use_response_ip;
 	/** some RPZ policies are configured */
@@ -154,6 +158,17 @@ struct daemon {
 	int reuse_cache;
 	/** the EDNS cookie secrets from the cookie-secret-file */
 	struct cookie_secrets* cookie_secrets;
+	/** the fast reload thread, or NULL */
+	struct fast_reload_thread* fast_reload_thread;
+	/** the fast reload printq list */
+	struct fast_reload_printq* fast_reload_printq_list;
+	/** the fast reload option to drop mesh queries, true if so. */
+	int fast_reload_drop_mesh;
+	/** for fast reload, if the tcl, tcp connection limits, has
+	 * changes for workers */
+	int fast_reload_tcl_has_changes;
+	/** config file name */
+	char* cfgfile;
 };
 
 /**
@@ -205,5 +220,13 @@ void daemon_delete(struct daemon* daemon
  * @param cfg: new config settings.
  */
 void daemon_apply_cfg(struct daemon* daemon, struct config_file* cfg);
+
+/**
+ * Setup acl list to have entries for the port list.
+ * @param list: the acl interface
+ * @param port_list: list of open ports, or none.
+ * @return false on failure
+ */
+int setup_acl_for_ports(struct acl_list* list, struct listen_port* port_list);
 
 #endif /* DAEMON_H */
Index: daemon/remote.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/remote.c,v
diff -u -p -r1.41 remote.c
--- daemon/remote.c	21 Feb 2025 13:20:40 -0000	1.41
+++ daemon/remote.c	30 Aug 2025 11:52:23 -0000
@@ -52,6 +52,9 @@
 #ifdef HAVE_OPENSSL_BN_H
 #include <openssl/bn.h>
 #endif
+#ifdef HAVE_STDATOMIC_H
+#include <stdatomic.h>
+#endif
 
 #include <ctype.h>
 #include "daemon/remote.h"
@@ -63,6 +66,7 @@
 #include "util/config_file.h"
 #include "util/net_help.h"
 #include "util/module.h"
+#include "util/ub_event.h"
 #include "services/listen_dnsport.h"
 #include "services/cache/rrset.h"
 #include "services/cache/infra.h"
@@ -77,10 +81,14 @@
 #include "validator/val_kcache.h"
 #include "validator/val_kentry.h"
 #include "validator/val_anchor.h"
+#include "validator/val_neg.h"
 #include "iterator/iterator.h"
 #include "iterator/iter_fwd.h"
 #include "iterator/iter_hints.h"
 #include "iterator/iter_delegpt.h"
+#include "iterator/iter_utils.h"
+#include "iterator/iter_donotq.h"
+#include "iterator/iter_priv.h"
 #include "services/outbound_list.h"
 #include "services/outside_network.h"
 #include "sldns/str2wire.h"
@@ -88,6 +96,7 @@
 #include "sldns/wire2str.h"
 #include "sldns/sbuffer.h"
 #include "util/timeval_func.h"
+#include "util/tcp_conn_limit.h"
 #include "util/edns.h"
 #ifdef USE_CACHEDB
 #include "cachedb/cachedb.h"
@@ -102,6 +111,9 @@
 #ifdef HAVE_NETDB_H
 #include <netdb.h>
 #endif
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif
 
 /* just for portability */
 #ifdef SQ
@@ -114,6 +126,18 @@
 /** Acceptable lengths of str lines */
 #define MAX_CMD_STRLINE 1024
 #define MAX_STDIN_STRLINE 2048
+/** What number of loop iterations is too much for ipc retries */
+#define IPC_LOOP_MAX 200
+/** Timeout in msec for ipc socket poll. */
+#define IPC_NOTIFICATION_WAIT 200
+
+static void fr_printq_delete(struct fast_reload_printq* printq);
+static void fr_main_perform_printout(struct fast_reload_thread* fr);
+static int fr_printq_empty(struct fast_reload_printq* printq);
+static void fr_printq_list_insert(struct fast_reload_printq* printq,
+	struct daemon* daemon);
+static void fr_printq_remove(struct fast_reload_printq* printq);
+static void fr_check_cmd_from_thread(struct fast_reload_thread* fr);
 
 static int
 remote_setup_ctx(struct daemon_remote* rc, struct config_file* cfg)
@@ -512,6 +536,11 @@ state_list_remove_elem(struct rc_state**
 static void
 clean_point(struct daemon_remote* rc, struct rc_state* s)
 {
+	if(!s->rc) {
+		/* the state has been picked up and moved away */
+		free(s);
+		return;
+	}
 	state_list_remove_elem(&rc->busy_list, s->c);
 	rc->active --;
 	if(s->ssl) {
@@ -679,6 +708,65 @@ do_reload(RES* ssl, struct worker* worke
 	send_ok(ssl);
 }
 
+#ifndef THREADS_DISABLED
+/** parse fast reload command options. */
+static int
+fr_parse_options(RES* ssl, char* arg, int* fr_verb, int* fr_nopause,
+	int* fr_drop_mesh)
+{
+	char* argp = arg;
+	while(*argp=='+') {
+		argp++;
+		while(*argp!=0 && *argp!=' ' && *argp!='\t') {
+			if(*argp == 'v') {
+				(*fr_verb)++;
+			} else if(*argp == 'p') {
+				(*fr_nopause) = 1;
+			} else if(*argp == 'd') {
+				(*fr_drop_mesh) = 1;
+			} else {
+				if(!ssl_printf(ssl,
+					"error: unknown option '+%c'\n",
+					*argp))
+					return 0;
+				return 0;
+			}
+			argp++;
+		}
+		argp = skipwhite(argp);
+	}
+	if(*argp!=0) {
+		if(!ssl_printf(ssl, "error: unknown option '%s'\n", argp))
+			return 0;
+		return 0;
+	}
+	return 1;
+}
+#endif /* !THREADS_DISABLED */
+
+/** do the fast_reload command */
+static void
+do_fast_reload(RES* ssl, struct worker* worker, struct rc_state* s, char* arg)
+{
+#ifdef THREADS_DISABLED
+	if(!ssl_printf(ssl, "error: no threads for fast_reload, compiled without threads.\n"))
+		return;
+	(void)worker;
+	(void)s;
+	(void)arg;
+#else
+	int fr_verb = 0, fr_nopause = 0, fr_drop_mesh = 0;
+	if(!fr_parse_options(ssl, arg, &fr_verb, &fr_nopause, &fr_drop_mesh))
+		return;
+	if(fr_verb >= 1) {
+		if(!ssl_printf(ssl, "start fast_reload\n"))
+			return;
+	}
+	fast_reload_thread_start(ssl, worker, s, fr_verb, fr_nopause,
+		fr_drop_mesh);
+#endif
+}
+
 /** do the verbosity command */
 static void
 do_verbosity(RES* ssl, char* str)
@@ -707,6 +795,10 @@ print_stats(RES* ssl, const char* nm, st
 		(unsigned long)s->svr.num_queries_cookie_client)) return 0;
 	if(!ssl_printf(ssl, "%s.num.queries_cookie_invalid"SQ"%lu\n", nm,
 		(unsigned long)s->svr.num_queries_cookie_invalid)) return 0;
+	if(!ssl_printf(ssl, "%s.num.queries_discard_timeout"SQ"%lu\n", nm,
+		(unsigned long)s->svr.num_queries_discard_timeout)) return 0;
+	if(!ssl_printf(ssl, "%s.num.queries_wait_limit"SQ"%lu\n", nm,
+		(unsigned long)s->svr.num_queries_wait_limit)) return 0;
 	if(!ssl_printf(ssl, "%s.num.cachehits"SQ"%lu\n", nm,
 		(unsigned long)(s->svr.num_queries
 			- s->svr.num_queries_missed_cache))) return 0;
@@ -732,6 +824,8 @@ print_stats(RES* ssl, const char* nm, st
 	if(!ssl_printf(ssl, "%s.num.dnscrypt.malformed"SQ"%lu\n", nm,
 		(unsigned long)s->svr.num_query_dnscrypt_crypted_malformed)) return 0;
 #endif
+	if(!ssl_printf(ssl, "%s.num.dns_error_reports"SQ"%lu\n", nm,
+		(unsigned long)s->svr.num_dns_error_reports)) return 0;
 	if(!ssl_printf(ssl, "%s.requestlist.avg"SQ"%g\n", nm,
 		(s->svr.num_queries_missed_cache+s->svr.num_queries_prefetch)?
 			(double)s->svr.sum_query_list_size/
@@ -1469,8 +1563,7 @@ do_view_zone_add(RES* ssl, struct worker
 	struct view* v;
 	if(!find_arg2(ssl, arg, &arg2))
 		return;
-	v = views_find_view(worker->daemon->views,
-		arg, 1 /* get write lock*/);
+	v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
 	if(!v) {
 		ssl_printf(ssl,"no view with name: %s\n", arg);
 		return;
@@ -1502,8 +1595,7 @@ do_view_zone_remove(RES* ssl, struct wor
 	struct view* v;
 	if(!find_arg2(ssl, arg, &arg2))
 		return;
-	v = views_find_view(worker->daemon->views,
-		arg, 1 /* get write lock*/);
+	v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
 	if(!v) {
 		ssl_printf(ssl,"no view with name: %s\n", arg);
 		return;
@@ -1525,8 +1617,7 @@ do_view_data_add(RES* ssl, struct worker
 	struct view* v;
 	if(!find_arg2(ssl, arg, &arg2))
 		return;
-	v = views_find_view(worker->daemon->views,
-		arg, 1 /* get write lock*/);
+	v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
 	if(!v) {
 		ssl_printf(ssl,"no view with name: %s\n", arg);
 		return;
@@ -1551,8 +1642,7 @@ do_view_datas_add(struct daemon_remote* 
 	char buf[MAX_CMD_STRLINE + MAX_STDIN_STRLINE] = "view_local_data ";
 	size_t cmd_len;
 	int num = 0, line = 0;
-	v = views_find_view(worker->daemon->views,
-		arg, 1 /* get write lock*/);
+	v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
 	if(!v) {
 		ssl_printf(ssl,"no view with name: %s\n", arg);
 		return;
@@ -1593,8 +1683,7 @@ do_view_data_remove(RES* ssl, struct wor
 	struct view* v;
 	if(!find_arg2(ssl, arg, &arg2))
 		return;
-	v = views_find_view(worker->daemon->views,
-		arg, 1 /* get write lock*/);
+	v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
 	if(!v) {
 		ssl_printf(ssl,"no view with name: %s\n", arg);
 		return;
@@ -1617,8 +1706,7 @@ do_view_datas_remove(struct daemon_remot
 	char buf[MAX_CMD_STRLINE + MAX_STDIN_STRLINE] = "view_local_data_remove ";
 	int num = 0;
 	size_t cmd_len;
-	v = views_find_view(worker->daemon->views,
-		arg, 1 /* get write lock*/);
+	v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
 	if(!v) {
 		ssl_printf(ssl,"no view with name: %s\n", arg);
 		return;
@@ -1946,7 +2034,7 @@ bogus_del_rrset(struct lruhash_entry* e,
 	/* entry is locked */
 	struct del_info* inf = (struct del_info*)arg;
 	struct packed_rrset_data* d = (struct packed_rrset_data*)e->data;
-	if(d->security == sec_status_bogus) {
+	if(d->security == sec_status_bogus && d->ttl > inf->expired) {
 		d->ttl = inf->expired;
 		inf->num_rrsets++;
 	}
@@ -1959,7 +2047,7 @@ bogus_del_msg(struct lruhash_entry* e, v
 	/* entry is locked */
 	struct del_info* inf = (struct del_info*)arg;
 	struct reply_info* d = (struct reply_info*)e->data;
-	if(d->security == sec_status_bogus) {
+	if(d->security == sec_status_bogus && d->ttl > inf->expired) {
 		d->ttl = inf->expired;
 		d->prefetch_ttl = inf->expired;
 		d->serve_expired_ttl = inf->expired;
@@ -1979,7 +2067,7 @@ bogus_del_kcache(struct lruhash_entry* e
 	/* entry is locked */
 	struct del_info* inf = (struct del_info*)arg;
 	struct key_entry_data* d = (struct key_entry_data*)e->data;
-	if(d->isbad) {
+	if(d->isbad && d->ttl > inf->expired) {
 		d->ttl = inf->expired;
 		inf->num_keys++;
 	}
@@ -2028,7 +2116,8 @@ negative_del_rrset(struct lruhash_entry*
 	/* delete the parentside negative cache rrsets,
 	 * these are nameserver rrsets that failed lookup, rdata empty */
 	if((k->rk.flags & PACKED_RRSET_PARENT_SIDE) && d->count == 1 &&
-		d->rrsig_count == 0 && d->rr_len[0] == 0) {
+		d->rrsig_count == 0 && d->rr_len[0] == 0 &&
+		d->ttl > inf->expired) {
 		d->ttl = inf->expired;
 		inf->num_rrsets++;
 	}
@@ -2043,7 +2132,8 @@ negative_del_msg(struct lruhash_entry* e
 	struct reply_info* d = (struct reply_info*)e->data;
 	/* rcode not NOERROR: NXDOMAIN, SERVFAIL, ..: an nxdomain or error
 	 * or NOERROR rcode with ANCOUNT==0: a NODATA answer */
-	if(FLAGS_GET_RCODE(d->flags) != 0 || d->an_numrrsets == 0) {
+	if((FLAGS_GET_RCODE(d->flags) != 0 || d->an_numrrsets == 0) &&
+		d->ttl > inf->expired) {
 		d->ttl = inf->expired;
 		d->prefetch_ttl = inf->expired;
 		d->serve_expired_ttl = inf->expired;
@@ -2065,7 +2155,7 @@ negative_del_kcache(struct lruhash_entry
 	struct key_entry_data* d = (struct key_entry_data*)e->data;
 	/* could be bad because of lookup failure on the DS, DNSKEY, which
 	 * was nxdomain or servfail, and thus a result of negative lookups */
-	if(d->isbad) {
+	if(d->isbad && d->ttl > inf->expired) {
 		d->ttl = inf->expired;
 		inf->num_keys++;
 	}
@@ -2137,7 +2227,7 @@ static int
 ssl_print_name_dp(RES* ssl, const char* str, uint8_t* nm, uint16_t dclass,
 	struct delegpt* dp)
 {
-	char buf[257];
+	char buf[LDNS_MAX_DOMAINLEN];
 	struct delegpt_ns* ns;
 	struct delegpt_addr* a;
 	int f = 0;
@@ -2505,7 +2595,7 @@ do_insecure_remove(RES* ssl, struct work
 static void
 do_insecure_list(RES* ssl, struct worker* worker)
 {
-	char buf[257];
+	char buf[LDNS_MAX_DOMAINLEN];
 	struct trust_anchor* a;
 	if(worker->env.anchors) {
 		RBTREE_FOR(a, struct trust_anchor*, worker->env.anchors->tree) {
@@ -2602,7 +2692,7 @@ get_mesh_status(struct mesh_area* mesh, 
 		}
 	} else if(s == module_wait_subquery) {
 		/* look in subs from mesh state to see what */
-		char nm[257];
+		char nm[LDNS_MAX_DOMAINLEN];
 		struct mesh_state_ref* sub;
 		snprintf(buf, len, "%s wants", modname);
 		l = strlen(buf);
@@ -2632,7 +2722,7 @@ do_dump_requestlist(RES* ssl, struct wor
 	struct mesh_area* mesh;
 	struct mesh_state* m;
 	int num = 0;
-	char buf[257];
+	char buf[LDNS_MAX_DOMAINLEN];
 	char timebuf[32];
 	char statbuf[10240];
 	if(!ssl_printf(ssl, "thread #%d\n", worker->thread_num))
@@ -2682,7 +2772,7 @@ dump_infra_host(struct lruhash_entry* e,
 	struct infra_key* k = (struct infra_key*)e->key;
 	struct infra_data* d = (struct infra_data*)e->data;
 	char ip_str[1024];
-	char name[257];
+	char name[LDNS_MAX_DOMAINLEN];
 	int port;
 	if(a->ssl_failed)
 		return;
@@ -2949,7 +3039,7 @@ static void
 do_list_auth_zones(RES* ssl, struct auth_zones* az)
 {
 	struct auth_zone* z;
-	char buf[257], buf2[256];
+	char buf[LDNS_MAX_DOMAINLEN], buf2[256];
 	lock_rw_rdlock(&az->lock);
 	RBTREE_FOR(z, struct auth_zone*, &az->ztree) {
 		lock_rw_rdlock(&z->lock);
@@ -2979,7 +3069,7 @@ static void
 do_list_local_zones(RES* ssl, struct local_zones* zones)
 {
 	struct local_zone* z;
-	char buf[257];
+	char buf[LDNS_MAX_DOMAINLEN];
 	lock_rw_rdlock(&zones->lock);
 	RBTREE_FOR(z, struct local_zone*, &zones->ztree) {
 		lock_rw_rdlock(&z->lock);
@@ -3039,7 +3129,7 @@ do_list_local_data(RES* ssl, struct work
 static void
 do_view_list_local_zones(RES* ssl, struct worker* worker, char* arg)
 {
-	struct view* v = views_find_view(worker->daemon->views,
+	struct view* v = views_find_view(worker->env.views,
 		arg, 0 /* get read lock*/);
 	if(!v) {
 		ssl_printf(ssl,"no view with name: %s\n", arg);
@@ -3055,7 +3145,7 @@ do_view_list_local_zones(RES* ssl, struc
 static void
 do_view_list_local_data(RES* ssl, struct worker* worker, char* arg)
 {
-	struct view* v = views_find_view(worker->daemon->views,
+	struct view* v = views_find_view(worker->env.views,
 		arg, 0 /* get read lock*/);
 	if(!v) {
 		ssl_printf(ssl,"no view with name: %s\n", arg);
@@ -3090,7 +3180,7 @@ rate_list(struct lruhash_entry* e, void*
 	struct ratelimit_list_arg* a = (struct ratelimit_list_arg*)arg;
 	struct rate_key* k = (struct rate_key*)e->key;
 	struct rate_data* d = (struct rate_data*)e->data;
-	char buf[257];
+	char buf[LDNS_MAX_DOMAINLEN];
 	int lim = infra_find_ratelimit(a->infra, k->name, k->namelen);
 	int max = infra_rate_max(d, a->now, a->backoff);
 	if(a->all == 0) {
@@ -3421,7 +3511,7 @@ cmdcmp(char* p, const char* cmd, size_t 
 
 /** execute a remote control command */
 static void
-execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
+execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd,
 	struct worker* worker)
 {
 	char* p = skipwhite(cmd);
@@ -3435,6 +3525,9 @@ execute_cmd(struct daemon_remote* rc, RE
 	} else if(cmdcmp(p, "reload", 6)) {
 		do_reload(ssl, worker, 0);
 		return;
+	} else if(cmdcmp(p, "fast_reload", 11)) {
+		do_fast_reload(ssl, worker, s, skipwhite(p+11));
+		return;
 	} else if(cmdcmp(p, "stats_noreset", 13)) {
 		do_stats(ssl, worker, 0);
 		return;
@@ -3633,7 +3726,7 @@ daemon_remote_exec(struct worker* worker
 		return;
 	}
 	verbose(VERB_ALGO, "remote exec distributed: %s", (char*)msg);
-	execute_cmd(NULL, NULL, (char*)msg, worker);
+	execute_cmd(NULL, NULL, NULL, (char*)msg, worker);
 	free(msg);
 }
 
@@ -3697,7 +3790,7 @@ handle_req(struct daemon_remote* rc, str
 	verbose(VERB_DETAIL, "control cmd: %s", buf);
 
 	/* figure out what to do */
-	execute_cmd(rc, res, buf, rc->worker);
+	execute_cmd(rc, s, res, buf, rc->worker);
 }
 
 /** handle SSL_do_handshake changes to the file descriptor to wait for later */
@@ -3788,4 +3881,4011 @@ int remote_control_callback(struct comm_
 	verbose(VERB_ALGO, "remote control operation completed");
 	clean_point(rc, s);
 	return 0;
+}
+
+/**
+ * This routine polls a socket for readiness.
+ * @param fd: file descriptor, -1 uses no fd for a timer only.
+ * @param timeout: time in msec to wait. 0 means nonblocking test,
+ * 	-1 waits blocking for events.
+ * @param pollin: check for input event.
+ * @param pollout: check for output event.
+ * @param event: output variable, set to true if the event happens.
+ * 	It is false if there was an error or timeout.
+ * @return false is system call failure, also logged.
+ */
+static int
+sock_poll_timeout(int fd, int timeout, int pollin, int pollout, int* event)
+{
+	int loopcount = 0;
+	/* Loop if the system call returns an errno to do so, like EINTR. */
+	log_assert(pollin || pollout);
+	while(1) {
+		struct pollfd p, *fds;
+		int nfds, ret;
+		if(++loopcount > IPC_LOOP_MAX) {
+			log_err("sock_poll_timeout: loop");
+			if(event)
+				*event = 0;
+			return 0;
+		}
+		if(fd == -1) {
+			fds = NULL;
+			nfds = 0;
+		} else {
+			fds = &p;
+			nfds = 1;
+			memset(&p, 0, sizeof(p));
+			p.fd = fd;
+#ifndef USE_WINSOCK
+			p.events = POLLERR
+				| POLLHUP
+				;
+#endif
+			if(pollin)
+				p.events |= POLLIN;
+			if(pollout)
+				p.events |= POLLOUT;
+		}
+#ifndef USE_WINSOCK
+		ret = poll(fds, nfds, timeout);
+#else
+		if(fds == NULL) {
+			Sleep(timeout);
+			ret = 0;
+		} else {
+			ret = WSAPoll(fds, nfds, timeout);
+		}
+#endif
+		if(ret == -1) {
+#ifndef USE_WINSOCK
+			if(
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+			) continue; /* Try again. */
+#endif
+			/* For WSAPoll we only get errors here:
+			 * o WSAENETDOWN
+			 * o WSAEFAULT
+			 * o WSAEINVAL
+			 * o WSAENOBUFS
+			 */
+			log_err("poll: %s", sock_strerror(errno));
+			if(event)
+				*event = 0;
+			return 0;
+		} else if(ret == 0) {
+			/* Timeout */
+			if(event)
+				*event = 0;
+			return 1;
+		}
+		break;
+	}
+	if(event)
+		*event = 1;
+	return 1;
+}
+
+/** fast reload convert fast reload notification status to string */
+static const char*
+fr_notification_to_string(enum fast_reload_notification status)
+{
+	switch(status) {
+	case fast_reload_notification_none:
+		return "none";
+	case fast_reload_notification_done:
+		return "done";
+	case fast_reload_notification_done_error:
+		return "done_error";
+	case fast_reload_notification_exit:
+		return "exit";
+	case fast_reload_notification_exited:
+		return "exited";
+	case fast_reload_notification_printout:
+		return "printout";
+	case fast_reload_notification_reload_stop:
+		return "reload_stop";
+	case fast_reload_notification_reload_ack:
+		return "reload_ack";
+	case fast_reload_notification_reload_nopause_poll:
+		return "reload_nopause_poll";
+	case fast_reload_notification_reload_start:
+		return "reload_start";
+	default:
+		break;
+	}
+	return "unknown";
+}
+
+#ifndef THREADS_DISABLED
+/** fast reload, poll for notification incoming. True if quit */
+static int
+fr_poll_for_quit(struct fast_reload_thread* fr)
+{
+	int inevent, loopexit = 0, bcount = 0;
+	uint32_t cmd;
+	ssize_t ret;
+
+	if(fr->need_to_quit)
+		return 1;
+	/* Is there data? */
+	if(!sock_poll_timeout(fr->commpair[1], 0, 1, 0, &inevent)) {
+		log_err("fr_poll_for_quit: poll failed");
+		return 0;
+	}
+	if(!inevent)
+		return 0;
+
+	/* Read the data */
+	while(1) {
+		if(++loopexit > IPC_LOOP_MAX) {
+			log_err("fr_poll_for_quit: recv loops %s",
+				sock_strerror(errno));
+			return 0;
+		}
+		ret = recv(fr->commpair[1], ((char*)&cmd)+bcount,
+			sizeof(cmd)-bcount, 0);
+		if(ret == -1) {
+			if(
+#ifndef USE_WINSOCK
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+#else
+				WSAGetLastError() == WSAEINTR ||
+				WSAGetLastError() == WSAEINPROGRESS ||
+				WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+				)
+				continue; /* Try again. */
+			log_err("fr_poll_for_quit: recv: %s",
+				sock_strerror(errno));
+			return 0;
+		} else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+			bcount += ret;
+			if((size_t)bcount < sizeof(cmd))
+				continue;
+		}
+		break;
+	}
+	if(cmd == fast_reload_notification_exit) {
+		fr->need_to_quit = 1;
+		verbose(VERB_ALGO, "fast reload: exit notification received");
+		return 1;
+	}
+	log_err("fr_poll_for_quit: unknown notification status received: %d %s",
+		cmd, fr_notification_to_string(cmd));
+	return 0;
+}
+
+/** fast reload thread. Send notification from the fast reload thread */
+static void
+fr_send_notification(struct fast_reload_thread* fr,
+	enum fast_reload_notification status)
+{
+	int outevent, loopexit = 0, bcount = 0;
+	uint32_t cmd;
+	ssize_t ret;
+	verbose(VERB_ALGO, "fast reload: send notification %s",
+		fr_notification_to_string(status));
+	/* Make a blocking attempt to send. But meanwhile stay responsive,
+	 * once in a while for quit commands. In case the server has to quit. */
+	/* see if there is incoming quit signals */
+	if(fr_poll_for_quit(fr))
+		return;
+	cmd = status;
+	while(1) {
+		if(++loopexit > IPC_LOOP_MAX) {
+			log_err("fast reload: could not send notification");
+			return;
+		}
+		/* wait for socket to become writable */
+		if(!sock_poll_timeout(fr->commpair[1], IPC_NOTIFICATION_WAIT,
+			0, 1, &outevent)) {
+			log_err("fast reload: poll failed");
+			return;
+		}
+		if(fr_poll_for_quit(fr))
+			return;
+		if(!outevent)
+			continue;
+		ret = send(fr->commpair[1], ((char*)&cmd)+bcount,
+			sizeof(cmd)-bcount, 0);
+		if(ret == -1) {
+			if(
+#ifndef USE_WINSOCK
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+#else
+				WSAGetLastError() == WSAEINTR ||
+				WSAGetLastError() == WSAEINPROGRESS ||
+				WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+				)
+				continue; /* Try again. */
+			log_err("fast reload send notification: send: %s",
+				sock_strerror(errno));
+			return;
+		} else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+			bcount += ret;
+			if((size_t)bcount < sizeof(cmd))
+				continue;
+		}
+		break;
+	}
+}
+
+/** fast reload thread queue up text string for output */
+static int
+fr_output_text(struct fast_reload_thread* fr, const char* msg)
+{
+	char* item = strdup(msg);
+	if(!item) {
+		log_err("fast reload output text: strdup out of memory");
+		return 0;
+	}
+	lock_basic_lock(&fr->fr_output_lock);
+	if(!cfg_strlist_append(fr->fr_output, item)) {
+		lock_basic_unlock(&fr->fr_output_lock);
+		/* The item is freed by cfg_strlist_append on failure. */
+		log_err("fast reload output text: append out of memory");
+		return 0;
+	}
+	lock_basic_unlock(&fr->fr_output_lock);
+	return 1;
+}
+
+/** fast reload thread output vmsg function */
+static int
+fr_output_vmsg(struct fast_reload_thread* fr, const char* format, va_list args)
+{
+	char msg[1024];
+	vsnprintf(msg, sizeof(msg), format, args);
+	return fr_output_text(fr, msg);
+}
+
+/** fast reload thread printout function, with printf arguments */
+static int fr_output_printf(struct fast_reload_thread* fr,
+	const char* format, ...) ATTR_FORMAT(printf, 2, 3);
+
+/** fast reload thread printout function, prints to list and signals
+ * the remote control thread to move that to get written to the socket
+ * of the remote control connection. */
+static int
+fr_output_printf(struct fast_reload_thread* fr, const char* format, ...)
+{
+	va_list args;
+	int ret;
+	va_start(args, format);
+	ret = fr_output_vmsg(fr, format, args);
+	va_end(args);
+	return ret;
+}
+
+/** fast reload thread, init time counters */
+static void
+fr_init_time(struct timeval* time_start, struct timeval* time_read,
+	struct timeval* time_construct, struct timeval* time_reload,
+	struct timeval* time_end)
+{
+	memset(time_start, 0, sizeof(*time_start));
+	memset(time_read, 0, sizeof(*time_read));
+	memset(time_construct, 0, sizeof(*time_construct));
+	memset(time_reload, 0, sizeof(*time_reload));
+	memset(time_end, 0, sizeof(*time_end));
+	if(gettimeofday(time_start, NULL) < 0)
+		log_err("gettimeofday: %s", strerror(errno));
+}
+
+/**
+ * Structure with constructed elements for use during fast reload.
+ * At the start it contains the tree items for the new config.
+ * After the tree items are swapped into the server, the old elements
+ * are kept in here. They can then be deleted.
+ */
+struct fast_reload_construct {
+	/** construct for views */
+	struct views* views;
+	/** construct for auth zones */
+	struct auth_zones* auth_zones;
+	/** construct for forwards */
+	struct iter_forwards* fwds;
+	/** construct for stubs */
+	struct iter_hints* hints;
+	/** construct for respip_set */
+	struct respip_set* respip_set;
+	/** construct for access control */
+	struct acl_list* acl;
+	/** construct for access control interface */
+	struct acl_list* acl_interface;
+	/** construct for tcp connection limit */
+	struct tcl_list* tcl;
+	/** construct for local zones */
+	struct local_zones* local_zones;
+	/** if there is response ip configuration in use */
+	int use_response_ip;
+	/** if there is an rpz zone */
+	int use_rpz;
+	/** construct for edns strings */
+	struct edns_strings* edns_strings;
+	/** construct for trust anchors */
+	struct val_anchors* anchors;
+	/** construct for nsec3 key size */
+	size_t* nsec3_keysize;
+	/** construct for nsec3 max iter */
+	size_t* nsec3_maxiter;
+	/** construct for nsec3 keyiter count */
+	int nsec3_keyiter_count;
+	/** construct for target fetch policy */
+	int* target_fetch_policy;
+	/** construct for max dependency depth */
+	int max_dependency_depth;
+	/** construct for donotquery addresses */
+	struct iter_donotq* donotq;
+	/** construct for private addresses and domains */
+	struct iter_priv* priv;
+	/** construct whitelist for capsforid names */
+	struct rbtree_type* caps_white;
+	/** construct for nat64 */
+	struct iter_nat64 nat64;
+	/** construct for wait_limits_netblock */
+	struct rbtree_type wait_limits_netblock;
+	/** construct for wait_limits_cookie_netblock */
+	struct rbtree_type wait_limits_cookie_netblock;
+	/** construct for domain limits */
+	struct rbtree_type domain_limits;
+	/** storage for the old configuration elements. The outer struct
+	 * is allocated with malloc here, the items are from config. */
+	struct config_file* oldcfg;
+};
+
+/** fast reload thread, read config */
+static int
+fr_read_config(struct fast_reload_thread* fr, struct config_file** newcfg)
+{
+	/* Create new config structure. */
+	*newcfg = config_create();
+	if(!*newcfg) {
+		if(!fr_output_printf(fr, "config_create failed: out of memory\n"))
+			return 0;
+		fr_send_notification(fr, fast_reload_notification_printout);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	/* Read new config from file */
+	if(!config_read(*newcfg, fr->worker->daemon->cfgfile,
+		fr->worker->daemon->chroot)) {
+		config_delete(*newcfg);
+		if(!fr_output_printf(fr, "config_read %s%s%s%s failed: %s\n",
+			(fr->worker->daemon->chroot?"<chroot:":""),
+			(fr->worker->daemon->chroot?fr->worker->daemon->chroot:""),
+			(fr->worker->daemon->chroot?"> ":""),
+			fr->worker->daemon->cfgfile, strerror(errno)))
+			return 0;
+		fr_send_notification(fr, fast_reload_notification_printout);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+	if(fr->fr_verb >= 1) {
+		if(!fr_output_printf(fr, "done read config file %s%s%s%s\n",
+			(fr->worker->daemon->chroot?"<chroot:":""),
+			(fr->worker->daemon->chroot?fr->worker->daemon->chroot:""),
+			(fr->worker->daemon->chroot?"> ":""),
+			fr->worker->daemon->cfgfile))
+			return 0;
+		fr_send_notification(fr, fast_reload_notification_printout);
+	}
+
+	return 1;
+}
+
+/** Check if two taglists are equal. */
+static int
+taglist_equal(char** tagname_a, int num_tags_a, char** tagname_b,
+	int num_tags_b)
+{
+	int i;
+	if(num_tags_a != num_tags_b)
+		return 0;
+	for(i=0; i<num_tags_a; i++) {
+		if(strcmp(tagname_a[i], tagname_b[i]) != 0)
+			return 0;
+	}
+	return 1;
+}
+
+/** Check the change from a to b is only new entries at the end. */
+static int
+taglist_change_at_end(char** tagname_a, int num_tags_a, char** tagname_b,
+	int num_tags_b)
+{
+	if(num_tags_a < 0 || num_tags_b < 0)
+		return 0;
+	if(num_tags_a >= num_tags_b)
+		return 0;
+	/* So, b is longer than a. Check if the initial start of the two
+	 * taglists is the same. */
+	if(!taglist_equal(tagname_a, num_tags_a, tagname_b, num_tags_a))
+		return 0;
+	return 1;
+}
+
+/** fast reload thread, check tag defines. */
+static int
+fr_check_tag_defines(struct fast_reload_thread* fr, struct config_file* newcfg)
+{
+	/* The tags are kept in a bitlist for items. Some of them are stored
+	 * in query info. If the tags change, then the old values are
+	 * inaccurate. The solution is to then flush the query list.
+	 * Unless the change only involves adding new tags at the end, that
+	 * needs no changes. */
+	if(!taglist_equal(fr->worker->daemon->cfg->tagname,
+			fr->worker->daemon->cfg->num_tags, newcfg->tagname,
+			newcfg->num_tags) &&
+		!taglist_change_at_end(fr->worker->daemon->cfg->tagname,
+			fr->worker->daemon->cfg->num_tags, newcfg->tagname,
+			newcfg->num_tags)) {
+		/* The tags have changed too much, the define-tag config. */
+		if(fr->fr_drop_mesh)
+			return 1; /* already dropping queries */
+		fr->fr_drop_mesh = 1;
+		fr->worker->daemon->fast_reload_drop_mesh = fr->fr_drop_mesh;
+		if(!fr_output_printf(fr, "tags have changed, with "
+			"'define-tag', and the queries have to be dropped "
+			"for consistency, setting '+d'\n"))
+			return 0;
+		fr_send_notification(fr, fast_reload_notification_printout);
+	}
+	return 1;
+}
+
+/** fast reload thread, check if config item has changed, if not add to
+ * the explanatory string. */
+static void
+fr_check_changed_cfg(int cmp, const char* desc, char* str, size_t len)
+{
+	if(cmp) {
+		size_t slen = strlen(str);
+		size_t desclen = strlen(desc);
+		if(slen == 0) {
+			snprintf(str, len, "%s", desc);
+			return;
+		}
+		if(len - slen < desclen+2)
+			return; /* It does not fit */
+		snprintf(str+slen, len-slen, " %s", desc);
+	}
+}
+
+/** fast reload thread, check if config string has changed, checks NULLs. */
+static void
+fr_check_changed_cfg_str(char* cmp1, char* cmp2, const char* desc, char* str,
+	size_t len)
+{
+	if((!cmp1 && cmp2) ||
+		(cmp1 && !cmp2) ||
+		(cmp1 && cmp2 && strcmp(cmp1, cmp2) != 0)) {
+		fr_check_changed_cfg(1, desc, str, len);
+	}
+}
+
+/** fast reload thread, check if config strlist has changed. */
+static void
+fr_check_changed_cfg_strlist(struct config_strlist* cmp1,
+	struct config_strlist* cmp2, const char* desc, char* str, size_t len)
+{
+	struct config_strlist* p1 = cmp1, *p2 = cmp2;
+	while(p1 && p2) {
+		if((!p1->str && p2->str) ||
+			(p1->str && !p2->str) ||
+			(p1->str && p2->str && strcmp(p1->str, p2->str) != 0)) {
+			/* The strlist is different. */
+			fr_check_changed_cfg(1, desc, str, len);
+			return;
+		}
+		p1 = p1->next;
+		p2 = p2->next;
+	}
+	if((!p1 && p2) || (p1 && !p2)) {
+		fr_check_changed_cfg(1, desc, str, len);
+	}
+}
+
+/** fast reload thread, check if config str2list has changed. */
+static void
+fr_check_changed_cfg_str2list(struct config_str2list* cmp1,
+	struct config_str2list* cmp2, const char* desc, char* str, size_t len)
+{
+	struct config_str2list* p1 = cmp1, *p2 = cmp2;
+	while(p1 && p2) {
+		if((!p1->str && p2->str) ||
+			(p1->str && !p2->str) ||
+			(p1->str && p2->str && strcmp(p1->str, p2->str) != 0)) {
+			/* The str2list is different. */
+			fr_check_changed_cfg(1, desc, str, len);
+			return;
+		}
+		if((!p1->str2 && p2->str2) ||
+			(p1->str2 && !p2->str2) ||
+			(p1->str2 && p2->str2 &&
+			strcmp(p1->str2, p2->str2) != 0)) {
+			/* The str2list is different. */
+			fr_check_changed_cfg(1, desc, str, len);
+			return;
+		}
+		p1 = p1->next;
+		p2 = p2->next;
+	}
+	if((!p1 && p2) || (p1 && !p2)) {
+		fr_check_changed_cfg(1, desc, str, len);
+	}
+}
+
+/** fast reload thread, check compatible config items */
+static int
+fr_check_compat_cfg(struct fast_reload_thread* fr, struct config_file* newcfg)
+{
+	int i;
+	char changed_str[1024];
+	struct config_file* cfg = fr->worker->env.cfg;
+	changed_str[0]=0;
+
+	/* Find incompatible options, and if so, print an error. */
+	fr_check_changed_cfg(cfg->num_threads != newcfg->num_threads,
+		"num-threads", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->do_ip4 != newcfg->do_ip4,
+		"do-ip4", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->do_ip6 != newcfg->do_ip6,
+		"do-ip6", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->do_udp != newcfg->do_udp,
+		"do-udp", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->do_tcp != newcfg->do_tcp,
+		"do-tcp", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->port != newcfg->port,
+		"port", changed_str, sizeof(changed_str));
+	/* But cfg->outgoing_num_ports has been changed at startup,
+	 * possibly to reduce it, so do not check it here. */
+	fr_check_changed_cfg(cfg->outgoing_num_tcp != newcfg->outgoing_num_tcp,
+		"outgoing-num-tcp", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->incoming_num_tcp != newcfg->incoming_num_tcp,
+		"incoming-num-tcp", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->num_out_ifs != newcfg->num_out_ifs,
+		"outgoing-interface", changed_str, sizeof(changed_str));
+	if(cfg->num_out_ifs == newcfg->num_out_ifs) {
+		for(i=0; i<cfg->num_out_ifs; i++)
+			fr_check_changed_cfg(strcmp(cfg->out_ifs[i],
+				newcfg->out_ifs[i]) != 0, "outgoing-interface",
+				changed_str, sizeof(changed_str));
+	}
+	fr_check_changed_cfg(cfg->num_ifs != newcfg->num_ifs,
+		"interface", changed_str, sizeof(changed_str));
+	if(cfg->num_ifs == newcfg->num_ifs) {
+		for(i=0; i<cfg->num_ifs; i++)
+			fr_check_changed_cfg(strcmp(cfg->ifs[i],
+				newcfg->ifs[i]) != 0, "interface",
+				changed_str, sizeof(changed_str));
+	}
+	fr_check_changed_cfg(cfg->if_automatic != newcfg->if_automatic,
+		"interface-automatic", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->so_rcvbuf != newcfg->so_rcvbuf,
+		"so-rcvbuf", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->so_sndbuf != newcfg->so_sndbuf,
+		"so-sndbuf", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->so_reuseport != newcfg->so_reuseport,
+		"so-reuseport", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->ip_transparent != newcfg->ip_transparent,
+		"ip-transparent", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->ip_freebind != newcfg->ip_freebind,
+		"ip-freebind", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->udp_connect != newcfg->udp_connect,
+		"udp-connect", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->msg_buffer_size != newcfg->msg_buffer_size,
+		"msg-buffer-size", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->do_tcp_keepalive != newcfg->do_tcp_keepalive,
+		"edns-tcp-keepalive", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->tcp_keepalive_timeout != newcfg->tcp_keepalive_timeout,
+		"edns-tcp-keepalive-timeout", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->tcp_idle_timeout != newcfg->tcp_idle_timeout,
+		"tcp-idle-timeout", changed_str, sizeof(changed_str));
+	/* Not changed, only if DoH is used, it is then stored in commpoints,
+	 * as well as used from cfg. */
+	fr_check_changed_cfg(
+		cfg->harden_large_queries != newcfg->harden_large_queries,
+		"harden-large-queries", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->http_max_streams != newcfg->http_max_streams,
+		"http-max-streams", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str(cfg->http_endpoint, newcfg->http_endpoint,
+		"http-endpoint", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->http_notls_downstream != newcfg->http_notls_downstream,
+		"http_notls_downstream", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->https_port != newcfg->https_port,
+		"https-port", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->ssl_port != newcfg->ssl_port,
+		"tls-port", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str(cfg->ssl_service_key, newcfg->ssl_service_key,
+		"tls-service-key", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str(cfg->ssl_service_pem, newcfg->ssl_service_pem,
+		"tls-service-pem", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str(cfg->tls_cert_bundle, newcfg->tls_cert_bundle,
+		"tls-cert-bundle", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_strlist(cfg->proxy_protocol_port,
+		newcfg->proxy_protocol_port, "proxy-protocol-port",
+		changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_strlist(cfg->tls_additional_port,
+		newcfg->tls_additional_port, "tls-additional-port",
+		changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str(cfg->if_automatic_ports,
+		newcfg->if_automatic_ports, "interface-automatic-ports",
+		changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->udp_upstream_without_downstream !=
+		newcfg->udp_upstream_without_downstream,
+		"udp-upstream-without-downstream", changed_str,
+		sizeof(changed_str));
+
+	if(changed_str[0] != 0) {
+		/* The new config changes some items that do not work with
+		 * fast reload. */
+		if(!fr_output_printf(fr, "The config changes items that are "
+			"not compatible with fast_reload, perhaps do reload "
+			"or restart: %s", changed_str) ||
+			!fr_output_printf(fr, "\n"))
+			return 0;
+		fr_send_notification(fr, fast_reload_notification_printout);
+		return 0;
+	}
+	return 1;
+}
+
+/** fast reload thread, check nopause config items */
+static int
+fr_check_nopause_cfg(struct fast_reload_thread* fr, struct config_file* newcfg)
+{
+	char changed_str[1024];
+	struct config_file* cfg = fr->worker->env.cfg;
+	if(!fr->fr_nopause)
+		return 1; /* The nopause is not enabled, so no problem. */
+	changed_str[0]=0;
+
+	/* Check for iter_env. */
+	fr_check_changed_cfg(
+		cfg->outbound_msg_retry != newcfg->outbound_msg_retry,
+		"outbound-msg-retry", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->max_sent_count != newcfg->max_sent_count,
+		"max-sent-count", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->max_query_restarts != newcfg->max_query_restarts,
+		"max-query-restarts", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(strcmp(cfg->target_fetch_policy,
+		newcfg->target_fetch_policy) != 0,
+		"target-fetch-policy", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->donotquery_localhost != newcfg->donotquery_localhost,
+		"do-not-query-localhost", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_strlist(cfg->donotqueryaddrs,
+		newcfg->donotqueryaddrs, "do-not-query-localhost",
+		changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_strlist(cfg->private_address,
+		newcfg->private_address, "private-address",
+		changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_strlist(cfg->private_domain,
+		newcfg->private_domain, "private-domain",
+		changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_strlist(cfg->caps_whitelist,
+		newcfg->caps_whitelist, "caps-exempt",
+		changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->do_nat64 != newcfg->do_nat64,
+		"do-nat64", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str(cfg->nat64_prefix, newcfg->nat64_prefix,
+		"nat64-prefix", changed_str, sizeof(changed_str));
+
+	/* Check for val_env. */
+	fr_check_changed_cfg(cfg->bogus_ttl != newcfg->bogus_ttl,
+		"val-bogus-ttl", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->val_date_override != newcfg->val_date_override,
+		"val-date-override", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->val_sig_skew_min != newcfg->val_sig_skew_min,
+		"val-sig-skew-min", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->val_sig_skew_max != newcfg->val_sig_skew_max,
+		"val-sig-skew-max", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(cfg->val_max_restart != newcfg->val_max_restart,
+		"val-max-restart", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(strcmp(cfg->val_nsec3_key_iterations,
+		newcfg->val_nsec3_key_iterations) != 0,
+		"val-nsec3-keysize-iterations", changed_str,
+		sizeof(changed_str));
+
+	/* Check for infra. */
+	fr_check_changed_cfg(cfg->host_ttl != newcfg->host_ttl,
+		"infra-host-ttl", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->infra_keep_probing != newcfg->infra_keep_probing,
+		"infra-keep-probing", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->ratelimit != newcfg->ratelimit,
+		"ratelimit", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->ip_ratelimit != newcfg->ip_ratelimit,
+		"ip-ratelimit", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->ip_ratelimit_cookie != newcfg->ip_ratelimit_cookie,
+		"ip-ratelimit-cookie", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str2list(cfg->wait_limit_netblock,
+		newcfg->wait_limit_netblock, "wait-limit-netblock",
+		changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str2list(cfg->wait_limit_cookie_netblock,
+		newcfg->wait_limit_cookie_netblock,
+		"wait-limit-cookie-netblock", changed_str,
+		sizeof(changed_str));
+	fr_check_changed_cfg_str2list(cfg->ratelimit_below_domain,
+		newcfg->ratelimit_below_domain, "ratelimit-below-domain",
+		changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str2list(cfg->ratelimit_for_domain,
+		newcfg->ratelimit_for_domain, "ratelimit-for-domain",
+		changed_str, sizeof(changed_str));
+
+	/* Check for dnstap. */
+	fr_check_changed_cfg(
+		cfg->dnstap_send_identity != newcfg->dnstap_send_identity,
+		"dnstap-send-identity", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg(
+		cfg->dnstap_send_version != newcfg->dnstap_send_version,
+		"dnstap-send-version", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str(cfg->dnstap_identity, newcfg->dnstap_identity,
+		"dnstap-identity", changed_str, sizeof(changed_str));
+	fr_check_changed_cfg_str(cfg->dnstap_version, newcfg->dnstap_version,
+		"dnstap-version", changed_str, sizeof(changed_str));
+
+	if(changed_str[0] != 0) {
+		/* The new config changes some items that need a pause,
+		 * to be able to update the variables. */
+		if(!fr_output_printf(fr, "The config changes items that need "
+			"the fast_reload +p option, for nopause, "
+			"disabled to be reloaded: %s", changed_str) ||
+			!fr_output_printf(fr, "\n"))
+			return 0;
+		fr_send_notification(fr, fast_reload_notification_printout);
+		return 0;
+	}
+	return 1;
+}
+
+/** fast reload thread, clear construct information, deletes items */
+static void
+fr_construct_clear(struct fast_reload_construct* ct)
+{
+	if(!ct)
+		return;
+	auth_zones_delete(ct->auth_zones);
+	forwards_delete(ct->fwds);
+	hints_delete(ct->hints);
+	respip_set_delete(ct->respip_set);
+	local_zones_delete(ct->local_zones);
+	acl_list_delete(ct->acl);
+	acl_list_delete(ct->acl_interface);
+	tcl_list_delete(ct->tcl);
+	edns_strings_delete(ct->edns_strings);
+	anchors_delete(ct->anchors);
+	views_delete(ct->views);
+	free(ct->nsec3_keysize);
+	free(ct->nsec3_maxiter);
+	free(ct->target_fetch_policy);
+	donotq_delete(ct->donotq);
+	priv_delete(ct->priv);
+	caps_white_delete(ct->caps_white);
+	wait_limits_free(&ct->wait_limits_netblock);
+	wait_limits_free(&ct->wait_limits_cookie_netblock);
+	domain_limits_free(&ct->domain_limits);
+	/* Delete the log identity here so that the global value is not
+	 * reset by config_delete. */
+	if(ct->oldcfg && ct->oldcfg->log_identity) {
+		free(ct->oldcfg->log_identity);
+		ct->oldcfg->log_identity = NULL;
+	}
+	config_delete(ct->oldcfg);
+}
+
+/** get memory for strlist */
+static size_t
+getmem_config_strlist(struct config_strlist* p)
+{
+	size_t m = 0;
+	struct config_strlist* s;
+	for(s = p; s; s = s->next)
+		m += sizeof(*s) + getmem_str(s->str);
+	return m;
+}
+
+/** get memory for str2list */
+static size_t
+getmem_config_str2list(struct config_str2list* p)
+{
+	size_t m = 0;
+	struct config_str2list* s;
+	for(s = p; s; s = s->next)
+		m += sizeof(*s) + getmem_str(s->str) + getmem_str(s->str2);
+	return m;
+}
+
+/** get memory for str3list */
+static size_t
+getmem_config_str3list(struct config_str3list* p)
+{
+	size_t m = 0;
+	struct config_str3list* s;
+	for(s = p; s; s = s->next)
+		m += sizeof(*s) + getmem_str(s->str) + getmem_str(s->str2)
+			+ getmem_str(s->str3);
+	return m;
+}
+
+/** get memory for strbytelist */
+static size_t
+getmem_config_strbytelist(struct config_strbytelist* p)
+{
+	size_t m = 0;
+	struct config_strbytelist* s;
+	for(s = p; s; s = s->next)
+		m += sizeof(*s) + getmem_str(s->str) + (s->str2?s->str2len:0);
+	return m;
+}
+
+/** get memory used by ifs array */
+static size_t
+getmem_ifs(int numifs, char** ifs)
+{
+	size_t m = 0;
+	int i;
+	m += numifs * sizeof(char*);
+	for(i=0; i<numifs; i++)
+		m += getmem_str(ifs[i]);
+	return m;
+}
+
+/** get memory for config_stub */
+static size_t
+getmem_config_stub(struct config_stub* p)
+{
+	size_t m = 0;
+	struct config_stub* s;
+	for(s = p; s; s = s->next)
+		m += sizeof(*s) + getmem_str(s->name)
+			+ getmem_config_strlist(s->hosts)
+			+ getmem_config_strlist(s->addrs);
+	return m;
+}
+
+/** get memory for config_auth */
+static size_t
+getmem_config_auth(struct config_auth* p)
+{
+	size_t m = 0;
+	struct config_auth* s;
+	for(s = p; s; s = s->next)
+		m += sizeof(*s) + getmem_str(s->name)
+			+ getmem_config_strlist(s->masters)
+			+ getmem_config_strlist(s->urls)
+			+ getmem_config_strlist(s->allow_notify)
+			+ getmem_str(s->zonefile)
+			+ s->rpz_taglistlen
+			+ getmem_str(s->rpz_action_override)
+			+ getmem_str(s->rpz_log_name)
+			+ getmem_str(s->rpz_cname);
+	return m;
+}
+
+/** get memory for config_view */
+static size_t
+getmem_config_view(struct config_view* p)
+{
+	size_t m = 0;
+	struct config_view* s;
+	for(s = p; s; s = s->next)
+		m += sizeof(*s) + getmem_str(s->name)
+			+ getmem_config_str2list(s->local_zones)
+			+ getmem_config_strlist(s->local_data)
+			+ getmem_config_strlist(s->local_zones_nodefault)
+#ifdef USE_IPSET
+			+ getmem_config_strlist(s->local_zones_ipset)
+#endif
+			+ getmem_config_str2list(s->respip_actions)
+			+ getmem_config_str2list(s->respip_data);
+
+	return m;
+}
+
+/** get memory used by config_file item, estimate */
+static size_t
+config_file_getmem(struct config_file* cfg)
+{
+	size_t m = 0;
+	m += sizeof(*cfg);
+	m += getmem_config_strlist(cfg->proxy_protocol_port);
+	m += getmem_str(cfg->ssl_service_key);
+	m += getmem_str(cfg->ssl_service_pem);
+	m += getmem_str(cfg->tls_cert_bundle);
+	m += getmem_config_strlist(cfg->tls_additional_port);
+	m += getmem_config_strlist(cfg->tls_session_ticket_keys.first);
+	m += getmem_str(cfg->tls_ciphers);
+	m += getmem_str(cfg->tls_ciphersuites);
+	m += getmem_str(cfg->http_endpoint);
+	m += (cfg->outgoing_avail_ports?65536*sizeof(int):0);
+	m += getmem_str(cfg->target_fetch_policy);
+	m += getmem_str(cfg->if_automatic_ports);
+	m += getmem_ifs(cfg->num_ifs, cfg->ifs);
+	m += getmem_ifs(cfg->num_out_ifs, cfg->out_ifs);
+	m += getmem_config_strlist(cfg->root_hints);
+	m += getmem_config_stub(cfg->stubs);
+	m += getmem_config_stub(cfg->forwards);
+	m += getmem_config_auth(cfg->auths);
+	m += getmem_config_view(cfg->views);
+	m += getmem_config_strlist(cfg->donotqueryaddrs);
+#ifdef CLIENT_SUBNET
+	m += getmem_config_strlist(cfg->client_subnet);
+	m += getmem_config_strlist(cfg->client_subnet_zone);
+#endif
+	m += getmem_config_str2list(cfg->acls);
+	m += getmem_config_str2list(cfg->tcp_connection_limits);
+	m += getmem_config_strlist(cfg->caps_whitelist);
+	m += getmem_config_strlist(cfg->private_address);
+	m += getmem_config_strlist(cfg->private_domain);
+	m += getmem_str(cfg->chrootdir);
+	m += getmem_str(cfg->username);
+	m += getmem_str(cfg->directory);
+	m += getmem_str(cfg->logfile);
+	m += getmem_str(cfg->pidfile);
+	m += getmem_str(cfg->log_identity);
+	m += getmem_str(cfg->identity);
+	m += getmem_str(cfg->version);
+	m += getmem_str(cfg->http_user_agent);
+	m += getmem_str(cfg->nsid_cfg_str);
+	m += (cfg->nsid?cfg->nsid_len:0);
+	m += getmem_str(cfg->module_conf);
+	m += getmem_config_strlist(cfg->trust_anchor_file_list);
+	m += getmem_config_strlist(cfg->trust_anchor_list);
+	m += getmem_config_strlist(cfg->auto_trust_anchor_file_list);
+	m += getmem_config_strlist(cfg->trusted_keys_file_list);
+	m += getmem_config_strlist(cfg->domain_insecure);
+	m += getmem_str(cfg->val_nsec3_key_iterations);
+	m += getmem_config_str2list(cfg->local_zones);
+	m += getmem_config_strlist(cfg->local_zones_nodefault);
+#ifdef USE_IPSET
+	m += getmem_config_strlist(cfg->local_zones_ipset);
+#endif
+	m += getmem_config_strlist(cfg->local_data);
+	m += getmem_config_str3list(cfg->local_zone_overrides);
+	m += getmem_config_strbytelist(cfg->local_zone_tags);
+	m += getmem_config_strbytelist(cfg->acl_tags);
+	m += getmem_config_str3list(cfg->acl_tag_actions);
+	m += getmem_config_str3list(cfg->acl_tag_datas);
+	m += getmem_config_str2list(cfg->acl_view);
+	m += getmem_config_str2list(cfg->interface_actions);
+	m += getmem_config_strbytelist(cfg->interface_tags);
+	m += getmem_config_str3list(cfg->interface_tag_actions);
+	m += getmem_config_str3list(cfg->interface_tag_datas);
+	m += getmem_config_str2list(cfg->interface_view);
+	m += getmem_config_strbytelist(cfg->respip_tags);
+	m += getmem_config_str2list(cfg->respip_actions);
+	m += getmem_config_str2list(cfg->respip_data);
+	m += getmem_ifs(cfg->num_tags, cfg->tagname);
+	m += getmem_config_strlist(cfg->control_ifs.first);
+	m += getmem_str(cfg->server_key_file);
+	m += getmem_str(cfg->server_cert_file);
+	m += getmem_str(cfg->control_key_file);
+	m += getmem_str(cfg->control_cert_file);
+	m += getmem_config_strlist(cfg->python_script);
+	m += getmem_config_strlist(cfg->dynlib_file);
+	m += getmem_str(cfg->dns64_prefix);
+	m += getmem_config_strlist(cfg->dns64_ignore_aaaa);
+	m += getmem_str(cfg->nat64_prefix);
+	m += getmem_str(cfg->dnstap_socket_path);
+	m += getmem_str(cfg->dnstap_ip);
+	m += getmem_str(cfg->dnstap_tls_server_name);
+	m += getmem_str(cfg->dnstap_tls_cert_bundle);
+	m += getmem_str(cfg->dnstap_tls_client_key_file);
+	m += getmem_str(cfg->dnstap_tls_client_cert_file);
+	m += getmem_str(cfg->dnstap_identity);
+	m += getmem_str(cfg->dnstap_version);
+	m += getmem_config_str2list(cfg->ratelimit_for_domain);
+	m += getmem_config_str2list(cfg->ratelimit_below_domain);
+	m += getmem_config_str2list(cfg->edns_client_strings);
+	m += getmem_str(cfg->dnscrypt_provider);
+	m += getmem_config_strlist(cfg->dnscrypt_secret_key);
+	m += getmem_config_strlist(cfg->dnscrypt_provider_cert);
+	m += getmem_config_strlist(cfg->dnscrypt_provider_cert_rotated);
+#ifdef USE_IPSECMOD
+	m += getmem_config_strlist(cfg->ipsecmod_whitelist);
+	m += getmem_str(cfg->ipsecmod_hook);
+#endif
+#ifdef USE_CACHEDB
+	m += getmem_str(cfg->cachedb_backend);
+	m += getmem_str(cfg->cachedb_secret);
+#ifdef USE_REDIS
+	m += getmem_str(cfg->redis_server_host);
+	m += getmem_str(cfg->redis_replica_server_host);
+	m += getmem_str(cfg->redis_server_path);
+	m += getmem_str(cfg->redis_replica_server_path);
+	m += getmem_str(cfg->redis_server_password);
+	m += getmem_str(cfg->redis_replica_server_password);
+#endif
+#endif
+#ifdef USE_IPSET
+	m += getmem_str(cfg->ipset_name_v4);
+	m += getmem_str(cfg->ipset_name_v6);
+#endif
+	return m;
+}
+
+/** fast reload thread, print memory used by construct of items. */
+static int
+fr_printmem(struct fast_reload_thread* fr,
+	struct config_file* newcfg, struct fast_reload_construct* ct)
+{
+	size_t mem = 0;
+	if(fr_poll_for_quit(fr))
+		return 1;
+	mem += views_get_mem(ct->views);
+	mem += respip_set_get_mem(ct->respip_set);
+	mem += auth_zones_get_mem(ct->auth_zones);
+	mem += forwards_get_mem(ct->fwds);
+	mem += hints_get_mem(ct->hints);
+	mem += local_zones_get_mem(ct->local_zones);
+	mem += acl_list_get_mem(ct->acl);
+	mem += acl_list_get_mem(ct->acl_interface);
+	mem += tcl_list_get_mem(ct->tcl);
+	mem += edns_strings_get_mem(ct->edns_strings);
+	mem += anchors_get_mem(ct->anchors);
+	mem += sizeof(*ct->oldcfg);
+	mem += config_file_getmem(newcfg);
+
+	if(!fr_output_printf(fr, "memory use %d bytes\n", (int)mem))
+		return 0;
+	fr_send_notification(fr, fast_reload_notification_printout);
+
+	return 1;
+}
+
+/** fast reload thread, setup the acl_interface for the ports that
+ * the server has. */
+static int
+ct_acl_interface_setup_ports(struct acl_list* acl_interface,
+	struct daemon* daemon)
+{
+	/* clean acl_interface */
+	acl_interface_init(acl_interface);
+	if(!setup_acl_for_ports(acl_interface, daemon->ports[0]))
+		return 0;
+	if(daemon->reuseport) {
+		size_t i;
+		for(i=1; i<daemon->num_ports; i++) {
+			if(!setup_acl_for_ports(acl_interface,
+				daemon->ports[i]))
+				return 0;
+		}
+	}
+	return 1;
+}
+
+/** fast reload, add new change to list of auth zones */
+static int
+fr_add_auth_zone_change(struct fast_reload_thread* fr, struct auth_zone* old_z,
+	struct auth_zone* new_z, int is_deleted, int is_added, int is_changed)
+{
+	struct fast_reload_auth_change* item;
+	item = calloc(1, sizeof(*item));
+	if(!item) {
+		log_err("malloc failure in add auth zone change");
+		return 0;
+	}
+	item->old_z = old_z;
+	item->new_z = new_z;
+	item->is_deleted = is_deleted;
+	item->is_added = is_added;
+	item->is_changed = is_changed;
+
+	item->next = fr->auth_zone_change_list;
+	fr->auth_zone_change_list = item;
+	return 1;
+}
+
+/** See if auth master is equal */
+static int
+xfr_auth_master_equal(struct auth_master* m1, struct auth_master* m2)
+{
+	if(!m1 && !m2)
+		return 1;
+	if(!m1 || !m2)
+		return 0;
+
+	if((m1->host && !m2->host) || (!m1->host && m2->host))
+		return 0;
+	if(m1->host && m2->host && strcmp(m1->host, m2->host) != 0)
+		return 0;
+
+	if((m1->file && !m2->file) || (!m1->file && m2->file))
+		return 0;
+	if(m1->file && m2->file && strcmp(m1->file, m2->file) != 0)
+		return 0;
+
+	if((m1->http && !m2->http) || (!m1->http && m2->http))
+		return 0;
+	if((m1->ixfr && !m2->ixfr) || (!m1->ixfr && m2->ixfr))
+		return 0;
+	if((m1->allow_notify && !m2->allow_notify) || (!m1->allow_notify && m2->allow_notify))
+		return 0;
+	if((m1->ssl && !m2->ssl) || (!m1->ssl && m2->ssl))
+		return 0;
+	if(m1->port != m2->port)
+		return 0;
+	return 1;
+}
+
+/** See if list of auth masters is equal */
+static int
+xfr_masterlist_equal(struct auth_master* list1, struct auth_master* list2)
+{
+	struct auth_master* p1 = list1, *p2 = list2;
+	while(p1 && p2) {
+		if(!xfr_auth_master_equal(p1, p2))
+			return 0;
+		p1 = p1->next;
+		p2 = p2->next;
+	}
+	if(!p1 && !p2)
+		return 1;
+	return 0;
+}
+
+/** See if the list of masters has changed. */
+static int
+xfr_masters_equal(struct auth_xfer* xfr1, struct auth_xfer* xfr2)
+{
+	if(xfr1 == NULL && xfr2 == NULL)
+		return 1;
+	if(xfr1 == NULL && xfr2 != NULL)
+		return 0;
+	if(xfr1 != NULL && xfr2 == NULL)
+		return 0;
+	if(xfr_masterlist_equal(xfr1->task_probe->masters,
+		xfr2->task_probe->masters) &&
+		xfr_masterlist_equal(xfr1->task_transfer->masters,
+		xfr2->task_transfer->masters))
+		return 1;
+	return 0;
+}
+
+/** Check what has changed in auth zones, like added and deleted zones */
+static int
+auth_zones_check_changes(struct fast_reload_thread* fr,
+	struct fast_reload_construct* ct)
+{
+	/* Check every zone in turn. */
+	struct auth_zone* new_z, *old_z;
+	struct module_env* env = &fr->worker->env;
+
+	fr->old_auth_zones = ct->auth_zones;
+	/* Nobody is using the new ct version yet.
+	 * Also the ct lock is picked up before the env lock for auth_zones. */
+	lock_rw_rdlock(&ct->auth_zones->lock);
+
+	/* Find deleted zones by looping over the current list and looking
+	 * up in the new tree. */
+	lock_rw_rdlock(&env->auth_zones->lock);
+	RBTREE_FOR(old_z, struct auth_zone*, &env->auth_zones->ztree) {
+		new_z = auth_zone_find(ct->auth_zones, old_z->name,
+			old_z->namelen, old_z->dclass);
+		if(!new_z) {
+			/* The zone has been removed. */
+			if(!fr_add_auth_zone_change(fr, old_z, NULL, 1, 0,
+				0)) {
+				lock_rw_unlock(&env->auth_zones->lock);
+				lock_rw_unlock(&ct->auth_zones->lock);
+				return 0;
+			}
+		}
+	}
+	lock_rw_unlock(&env->auth_zones->lock);
+
+	/* Find added zones by looping over new list and lookup in current. */
+	RBTREE_FOR(new_z, struct auth_zone*, &ct->auth_zones->ztree) {
+		lock_rw_rdlock(&env->auth_zones->lock);
+		old_z = auth_zone_find(env->auth_zones, new_z->name,
+			new_z->namelen, new_z->dclass);
+		if(!old_z) {
+			/* The zone has been added. */
+			lock_rw_unlock(&env->auth_zones->lock);
+			if(!fr_add_auth_zone_change(fr, NULL, new_z, 0, 1,
+				0)) {
+				lock_rw_unlock(&ct->auth_zones->lock);
+				return 0;
+			}
+		} else {
+			uint32_t old_serial = 0, new_serial = 0;
+			int have_old = 0, have_new = 0;
+			struct auth_xfer* old_xfr, *new_xfr;
+			lock_rw_rdlock(&new_z->lock);
+			lock_rw_rdlock(&old_z->lock);
+			new_xfr = auth_xfer_find(ct->auth_zones, new_z->name,
+				new_z->namelen, new_z->dclass);
+			old_xfr = auth_xfer_find(env->auth_zones, old_z->name,
+				old_z->namelen, old_z->dclass);
+			if(new_xfr) {
+				lock_basic_lock(&new_xfr->lock);
+			}
+			if(old_xfr) {
+				lock_basic_lock(&old_xfr->lock);
+			}
+			lock_rw_unlock(&env->auth_zones->lock);
+
+			/* Change in the auth zone can be detected. */
+			/* A change in serial number means that auth_xfer
+			 * has to be updated. */
+			have_old = (auth_zone_get_serial(old_z,
+				&old_serial)!=0);
+			have_new = (auth_zone_get_serial(new_z,
+				&new_serial)!=0);
+			if(have_old != have_new || old_serial != new_serial
+				|| !xfr_masters_equal(old_xfr, new_xfr)) {
+				/* The zone has been changed. */
+				if(!fr_add_auth_zone_change(fr, old_z, new_z,
+					0, 0, 1)) {
+					lock_rw_unlock(&old_z->lock);
+					lock_rw_unlock(&new_z->lock);
+					lock_rw_unlock(&ct->auth_zones->lock);
+					if(new_xfr) {
+						lock_basic_unlock(&new_xfr->lock);
+					}
+					if(old_xfr) {
+						lock_basic_unlock(&old_xfr->lock);
+					}
+					return 0;
+				}
+			}
+
+			if(new_xfr) {
+				lock_basic_unlock(&new_xfr->lock);
+			}
+			if(old_xfr) {
+				lock_basic_unlock(&old_xfr->lock);
+			}
+			lock_rw_unlock(&old_z->lock);
+			lock_rw_unlock(&new_z->lock);
+		}
+	}
+
+	lock_rw_unlock(&ct->auth_zones->lock);
+	return 1;
+}
+
+/** fast reload thread, construct from config the new items */
+static int
+fr_construct_from_config(struct fast_reload_thread* fr,
+	struct config_file* newcfg, struct fast_reload_construct* ct)
+{
+	int have_view_respip_cfg = 0;
+
+	if(!(ct->views = views_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!views_apply_cfg(ct->views, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->acl = acl_list_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!acl_list_apply_cfg(ct->acl, newcfg, ct->views)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->acl_interface = acl_list_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!ct_acl_interface_setup_ports(ct->acl_interface,
+		fr->worker->daemon)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!acl_interface_apply_cfg(ct->acl_interface, newcfg, ct->views)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->tcl = tcl_list_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!tcl_list_apply_cfg(ct->tcl, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr->worker->daemon->tcl->tree.count != 0)
+		fr->worker->daemon->fast_reload_tcl_has_changes = 1;
+	else	fr->worker->daemon->fast_reload_tcl_has_changes = 0;
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->auth_zones = auth_zones_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!auth_zones_apply_cfg(ct->auth_zones, newcfg, 1, &ct->use_rpz,
+		fr->worker->daemon->env, &fr->worker->daemon->mods)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!auth_zones_check_changes(fr, ct)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->fwds = forwards_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!forwards_apply_cfg(ct->fwds, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->hints = hints_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!hints_apply_cfg(ct->hints, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->local_zones = local_zones_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!local_zones_apply_cfg(ct->local_zones, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->respip_set = respip_set_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!respip_global_apply_cfg(ct->respip_set, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+	if(!respip_views_apply_cfg(ct->views, newcfg, &have_view_respip_cfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	ct->use_response_ip = !respip_set_is_empty(ct->respip_set) ||
+		have_view_respip_cfg;
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->edns_strings = edns_strings_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!edns_strings_apply_cfg(ct->edns_strings, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(fr->worker->env.anchors) {
+		/* There are trust anchors already, so create it for reload. */
+		if(!(ct->anchors = anchors_create())) {
+			fr_construct_clear(ct);
+			return 0;
+		}
+		if(!anchors_apply_cfg(ct->anchors, newcfg)) {
+			fr_construct_clear(ct);
+			return 0;
+		}
+		if(fr_poll_for_quit(fr))
+			return 1;
+	}
+
+	if(!val_env_parse_key_iter(newcfg->val_nsec3_key_iterations,
+		&ct->nsec3_keysize, &ct->nsec3_maxiter,
+		&ct->nsec3_keyiter_count)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!read_fetch_policy(&ct->target_fetch_policy,
+		&ct->max_dependency_depth, newcfg->target_fetch_policy)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!(ct->donotq = donotq_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!donotq_apply_cfg(ct->donotq, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!(ct->priv = priv_create())) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!priv_apply_cfg(ct->priv, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(newcfg->caps_whitelist) {
+		if(!(ct->caps_white = caps_white_create())) {
+			fr_construct_clear(ct);
+			return 0;
+		}
+		if(!caps_white_apply_cfg(ct->caps_white, newcfg)) {
+			fr_construct_clear(ct);
+			return 0;
+		}
+	}
+	if(!nat64_apply_cfg(&ct->nat64, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!setup_wait_limits(&ct->wait_limits_netblock,
+		&ct->wait_limits_cookie_netblock, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(!setup_domain_limits(&ct->domain_limits, newcfg)) {
+		fr_construct_clear(ct);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr))
+		return 1;
+
+	if(!(ct->oldcfg = (struct config_file*)calloc(1,
+		sizeof(*ct->oldcfg)))) {
+		fr_construct_clear(ct);
+		log_err("out of memory");
+		return 0;
+	}
+	if(fr->fr_verb >= 2) {
+		if(!fr_printmem(fr, newcfg, ct))
+			return 0;
+	}
+	return 1;
+}
+
+/** fast reload thread, finish timers */
+static int
+fr_finish_time(struct fast_reload_thread* fr, struct timeval* time_start,
+	struct timeval* time_read, struct timeval* time_construct,
+	struct timeval* time_reload, struct timeval* time_end)
+{
+	struct timeval total, readtime, constructtime, reloadtime, deletetime;
+	if(gettimeofday(time_end, NULL) < 0)
+		log_err("gettimeofday: %s", strerror(errno));
+
+	timeval_subtract(&total, time_end, time_start);
+	timeval_subtract(&readtime, time_read, time_start);
+	timeval_subtract(&constructtime, time_construct, time_read);
+	timeval_subtract(&reloadtime, time_reload, time_construct);
+	timeval_subtract(&deletetime, time_end, time_reload);
+	if(!fr_output_printf(fr, "read disk  %3d.%6.6ds\n",
+		(int)readtime.tv_sec, (int)readtime.tv_usec))
+		return 0;
+	if(!fr_output_printf(fr, "construct  %3d.%6.6ds\n",
+		(int)constructtime.tv_sec, (int)constructtime.tv_usec))
+		return 0;
+	if(!fr_output_printf(fr, "reload     %3d.%6.6ds\n",
+		(int)reloadtime.tv_sec, (int)reloadtime.tv_usec))
+		return 0;
+	if(!fr_output_printf(fr, "deletes    %3d.%6.6ds\n",
+		(int)deletetime.tv_sec, (int)deletetime.tv_usec))
+		return 0;
+	if(!fr_output_printf(fr, "total time %3d.%6.6ds\n", (int)total.tv_sec,
+		(int)total.tv_usec))
+		return 0;
+	fr_send_notification(fr, fast_reload_notification_printout);
+	return 1;
+}
+
+/** Swap auth zone information */
+static void
+auth_zones_swap(struct auth_zones* az, struct auth_zones* data)
+{
+	rbtree_type oldztree = az->ztree;
+	int old_have_downstream = az->have_downstream;
+	struct auth_zone* old_rpz_first = az->rpz_first;
+
+	az->ztree = data->ztree;
+	data->ztree = oldztree;
+
+	az->have_downstream = data->have_downstream;
+	data->have_downstream = old_have_downstream;
+
+	/* Leave num_query_up and num_query_down, the statistics can
+	 * remain counted. */
+
+	az->rpz_first = data->rpz_first;
+	data->rpz_first = old_rpz_first;
+
+	/* The xtree is not swapped. This contains the auth_xfer elements
+	 * that contain tasks in progress, like zone transfers.
+	 * The unchanged zones can keep their tasks in the tree, and thus
+	 * the xfer elements can continue to be their callbacks. */
+}
+
+#if defined(ATOMIC_POINTER_LOCK_FREE) && defined(HAVE_LINK_ATOMIC_STORE)
+/** Fast reload thread, if atomics are available, copy the config items
+ * one by one with atomic store operations. */
+static void
+fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg,
+	struct config_file* newcfg)
+{
+#define COPY_VAR_int(var) oldcfg->var = cfg->var; atomic_store((_Atomic int*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_ptr(var) oldcfg->var = cfg->var; atomic_store((void* _Atomic*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_unsigned_int(var) oldcfg->var = cfg->var; atomic_store((_Atomic unsigned*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_size_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic size_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_uint8_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic uint8_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_uint16_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic uint16_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_uint32_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic uint32_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_int32_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic int32_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+	/* If config file items are missing from this list, they are
+	 * not updated by fast-reload +p. */
+	/* For missing items, the oldcfg item is not updated, still NULL,
+	 * and the cfg stays the same. The newcfg item is untouched.
+	 * The newcfg item is then deleted later. */
+	/* Items that need synchronisation are omitted from the list.
+	 * Use fast-reload without +p to update them together. */
+	COPY_VAR_int(verbosity);
+	COPY_VAR_int(stat_interval);
+	COPY_VAR_int(stat_cumulative);
+	COPY_VAR_int(stat_extended);
+	COPY_VAR_int(stat_inhibit_zero);
+	COPY_VAR_int(num_threads);
+	COPY_VAR_int(port);
+	COPY_VAR_int(do_ip4);
+	COPY_VAR_int(do_ip6);
+	COPY_VAR_int(do_nat64);
+	COPY_VAR_int(prefer_ip4);
+	COPY_VAR_int(prefer_ip6);
+	COPY_VAR_int(do_udp);
+	COPY_VAR_int(do_tcp);
+	COPY_VAR_size_t(max_reuse_tcp_queries);
+	COPY_VAR_int(tcp_reuse_timeout);
+	COPY_VAR_int(tcp_auth_query_timeout);
+	COPY_VAR_int(tcp_upstream);
+	COPY_VAR_int(udp_upstream_without_downstream);
+	COPY_VAR_int(tcp_mss);
+	COPY_VAR_int(outgoing_tcp_mss);
+	COPY_VAR_int(tcp_idle_timeout);
+	COPY_VAR_int(do_tcp_keepalive);
+	COPY_VAR_int(tcp_keepalive_timeout);
+	COPY_VAR_int(sock_queue_timeout);
+	COPY_VAR_ptr(proxy_protocol_port);
+	COPY_VAR_ptr(ssl_service_key);
+	COPY_VAR_ptr(ssl_service_pem);
+	COPY_VAR_int(ssl_port);
+	COPY_VAR_int(ssl_upstream);
+	COPY_VAR_ptr(tls_cert_bundle);
+	COPY_VAR_int(tls_win_cert);
+	COPY_VAR_ptr(tls_additional_port);
+	/* The first is used to walk throught the list but last is
+	 * only used during config read. */
+	COPY_VAR_ptr(tls_session_ticket_keys.first);
+	COPY_VAR_ptr(tls_session_ticket_keys.last);
+	COPY_VAR_ptr(tls_ciphers);
+	COPY_VAR_ptr(tls_ciphersuites);
+	COPY_VAR_int(tls_use_sni);
+	COPY_VAR_int(https_port);
+	COPY_VAR_ptr(http_endpoint);
+	COPY_VAR_uint32_t(http_max_streams);
+	COPY_VAR_size_t(http_query_buffer_size);
+	COPY_VAR_size_t(http_response_buffer_size);
+	COPY_VAR_int(http_nodelay);
+	COPY_VAR_int(http_notls_downstream);
+	COPY_VAR_int(outgoing_num_ports);
+	COPY_VAR_size_t(outgoing_num_tcp);
+	COPY_VAR_size_t(incoming_num_tcp);
+	COPY_VAR_ptr(outgoing_avail_ports);
+	COPY_VAR_size_t(edns_buffer_size);
+	COPY_VAR_size_t(stream_wait_size);
+	COPY_VAR_size_t(msg_buffer_size);
+	COPY_VAR_size_t(msg_cache_size);
+	COPY_VAR_size_t(msg_cache_slabs);
+	COPY_VAR_size_t(num_queries_per_thread);
+	COPY_VAR_size_t(jostle_time);
+	COPY_VAR_size_t(rrset_cache_size);
+	COPY_VAR_size_t(rrset_cache_slabs);
+	COPY_VAR_int(host_ttl);
+	COPY_VAR_size_t(infra_cache_slabs);
+	COPY_VAR_size_t(infra_cache_numhosts);
+	COPY_VAR_int(infra_cache_min_rtt);
+	COPY_VAR_int(infra_cache_max_rtt);
+	COPY_VAR_int(infra_keep_probing);
+	COPY_VAR_int(delay_close);
+	COPY_VAR_int(udp_connect);
+	COPY_VAR_ptr(target_fetch_policy);
+	COPY_VAR_int(fast_server_permil);
+	COPY_VAR_size_t(fast_server_num);
+	COPY_VAR_int(if_automatic);
+	COPY_VAR_ptr(if_automatic_ports);
+	COPY_VAR_size_t(so_rcvbuf);
+	COPY_VAR_size_t(so_sndbuf);
+	COPY_VAR_int(so_reuseport);
+	COPY_VAR_int(ip_transparent);
+	COPY_VAR_int(ip_freebind);
+	COPY_VAR_int(ip_dscp);
+	/* Not copied because the length and items could then not match.
+	   num_ifs, ifs, num_out_ifs, out_ifs
+	*/
+	COPY_VAR_ptr(root_hints);
+	COPY_VAR_ptr(stubs);
+	COPY_VAR_ptr(forwards);
+	COPY_VAR_ptr(auths);
+	COPY_VAR_ptr(views);
+	COPY_VAR_ptr(donotqueryaddrs);
+#ifdef CLIENT_SUBNET
+	COPY_VAR_ptr(client_subnet);
+	COPY_VAR_ptr(client_subnet_zone);
+	COPY_VAR_uint16_t(client_subnet_opcode);
+	COPY_VAR_int(client_subnet_always_forward);
+	COPY_VAR_uint8_t(max_client_subnet_ipv4);
+	COPY_VAR_uint8_t(max_client_subnet_ipv6);
+	COPY_VAR_uint8_t(min_client_subnet_ipv4);
+	COPY_VAR_uint8_t(min_client_subnet_ipv6);
+	COPY_VAR_uint32_t(max_ecs_tree_size_ipv4);
+	COPY_VAR_uint32_t(max_ecs_tree_size_ipv6);
+#endif
+	COPY_VAR_ptr(acls);
+	COPY_VAR_int(donotquery_localhost);
+	COPY_VAR_ptr(tcp_connection_limits);
+	COPY_VAR_int(harden_short_bufsize);
+	COPY_VAR_int(harden_large_queries);
+	COPY_VAR_int(harden_glue);
+	COPY_VAR_int(harden_dnssec_stripped);
+	COPY_VAR_int(harden_below_nxdomain);
+	COPY_VAR_int(harden_referral_path);
+	COPY_VAR_int(harden_algo_downgrade);
+	COPY_VAR_int(harden_unknown_additional);
+	COPY_VAR_int(use_caps_bits_for_id);
+	COPY_VAR_ptr(caps_whitelist);
+	COPY_VAR_ptr(private_address);
+	COPY_VAR_ptr(private_domain);
+	COPY_VAR_size_t(unwanted_threshold);
+	COPY_VAR_int(max_ttl);
+	COPY_VAR_int(min_ttl);
+	COPY_VAR_int(max_negative_ttl);
+	COPY_VAR_int(min_negative_ttl);
+	COPY_VAR_int(prefetch);
+	COPY_VAR_int(prefetch_key);
+	COPY_VAR_int(deny_any);
+	COPY_VAR_ptr(chrootdir);
+	COPY_VAR_ptr(username);
+	COPY_VAR_ptr(directory);
+	COPY_VAR_ptr(logfile);
+	COPY_VAR_ptr(pidfile);
+	COPY_VAR_int(use_syslog);
+	COPY_VAR_int(log_time_ascii);
+	COPY_VAR_int(log_queries);
+	COPY_VAR_int(log_replies);
+	COPY_VAR_int(log_tag_queryreply);
+	COPY_VAR_int(log_local_actions);
+	COPY_VAR_int(log_servfail);
+	COPY_VAR_ptr(log_identity);
+	COPY_VAR_int(log_destaddr);
+	COPY_VAR_int(hide_identity);
+	COPY_VAR_int(hide_version);
+	COPY_VAR_int(hide_trustanchor);
+	COPY_VAR_int(hide_http_user_agent);
+	COPY_VAR_ptr(identity);
+	COPY_VAR_ptr(version);
+	COPY_VAR_ptr(http_user_agent);
+	COPY_VAR_ptr(nsid_cfg_str);
+	/* Not copied because the length and items could then not match.
+	nsid;
+	nsid_len;
+	*/
+	COPY_VAR_ptr(module_conf);
+	COPY_VAR_ptr(trust_anchor_file_list);
+	COPY_VAR_ptr(trust_anchor_list);
+	COPY_VAR_ptr(auto_trust_anchor_file_list);
+	COPY_VAR_ptr(trusted_keys_file_list);
+	COPY_VAR_ptr(domain_insecure);
+	COPY_VAR_int(trust_anchor_signaling);
+	COPY_VAR_int(root_key_sentinel);
+	COPY_VAR_int32_t(val_date_override);
+	COPY_VAR_int32_t(val_sig_skew_min);
+	COPY_VAR_int32_t(val_sig_skew_max);
+	COPY_VAR_int32_t(val_max_restart);
+	COPY_VAR_int(bogus_ttl);
+	COPY_VAR_int(val_clean_additional);
+	COPY_VAR_int(val_log_level);
+	COPY_VAR_int(val_log_squelch);
+	COPY_VAR_int(val_permissive_mode);
+	COPY_VAR_int(aggressive_nsec);
+	COPY_VAR_int(ignore_cd);
+	COPY_VAR_int(disable_edns_do);
+	COPY_VAR_int(serve_expired);
+	COPY_VAR_int(serve_expired_ttl);
+	COPY_VAR_int(serve_expired_ttl_reset);
+	COPY_VAR_int(serve_expired_reply_ttl);
+	COPY_VAR_int(serve_expired_client_timeout);
+	COPY_VAR_int(ede_serve_expired);
+	COPY_VAR_int(dns_error_reporting);
+	COPY_VAR_int(serve_original_ttl);
+	COPY_VAR_ptr(val_nsec3_key_iterations);
+	COPY_VAR_int(zonemd_permissive_mode);
+	COPY_VAR_unsigned_int(add_holddown);
+	COPY_VAR_unsigned_int(del_holddown);
+	COPY_VAR_unsigned_int(keep_missing);
+	COPY_VAR_int(permit_small_holddown);
+	COPY_VAR_size_t(key_cache_size);
+	COPY_VAR_size_t(key_cache_slabs);
+	COPY_VAR_size_t(neg_cache_size);
+	COPY_VAR_ptr(local_zones);
+	COPY_VAR_ptr(local_zones_nodefault);
+#ifdef USE_IPSET
+	COPY_VAR_ptr(local_zones_ipset);
+#endif
+	COPY_VAR_int(local_zones_disable_default);
+	COPY_VAR_ptr(local_data);
+	COPY_VAR_ptr(local_zone_overrides);
+	COPY_VAR_int(unblock_lan_zones);
+	COPY_VAR_int(insecure_lan_zones);
+	/* These reference tags
+	COPY_VAR_ptr(local_zone_tags);
+	COPY_VAR_ptr(acl_tags);
+	COPY_VAR_ptr(acl_tag_actions);
+	COPY_VAR_ptr(acl_tag_datas);
+	*/
+	COPY_VAR_ptr(acl_view);
+	COPY_VAR_ptr(interface_actions);
+	/* These reference tags
+	COPY_VAR_ptr(interface_tags);
+	COPY_VAR_ptr(interface_tag_actions);
+	COPY_VAR_ptr(interface_tag_datas);
+	*/
+	COPY_VAR_ptr(interface_view);
+	/* This references tags
+	COPY_VAR_ptr(respip_tags);
+	*/
+	COPY_VAR_ptr(respip_actions);
+	COPY_VAR_ptr(respip_data);
+	/* Not copied because the length and items could then not match.
+	 * also the respip module keeps a pointer to the array in its state.
+	   tagname, num_tags
+	*/
+	COPY_VAR_int(remote_control_enable);
+	/* The first is used to walk throught the list but last is
+	 * only used during config read. */
+	COPY_VAR_ptr(control_ifs.first);
+	COPY_VAR_ptr(control_ifs.last);
+	COPY_VAR_int(control_use_cert);
+	COPY_VAR_int(control_port);
+	COPY_VAR_ptr(server_key_file);
+	COPY_VAR_ptr(server_cert_file);
+	COPY_VAR_ptr(control_key_file);
+	COPY_VAR_ptr(control_cert_file);
+	COPY_VAR_ptr(python_script);
+	COPY_VAR_ptr(dynlib_file);
+	COPY_VAR_int(use_systemd);
+	COPY_VAR_int(do_daemonize);
+	COPY_VAR_int(minimal_responses);
+	COPY_VAR_int(rrset_roundrobin);
+	COPY_VAR_int(unknown_server_time_limit);
+	COPY_VAR_int(discard_timeout);
+	COPY_VAR_int(wait_limit);
+	COPY_VAR_int(wait_limit_cookie);
+	COPY_VAR_ptr(wait_limit_netblock);
+	COPY_VAR_ptr(wait_limit_cookie_netblock);
+	COPY_VAR_size_t(max_udp_size);
+	COPY_VAR_ptr(dns64_prefix);
+	COPY_VAR_int(dns64_synthall);
+	COPY_VAR_ptr(dns64_ignore_aaaa);
+	COPY_VAR_ptr(nat64_prefix);
+	COPY_VAR_int(dnstap);
+	COPY_VAR_int(dnstap_bidirectional);
+	COPY_VAR_ptr(dnstap_socket_path);
+	COPY_VAR_ptr(dnstap_ip);
+	COPY_VAR_int(dnstap_tls);
+	COPY_VAR_ptr(dnstap_tls_server_name);
+	COPY_VAR_ptr(dnstap_tls_cert_bundle);
+	COPY_VAR_ptr(dnstap_tls_client_key_file);
+	COPY_VAR_ptr(dnstap_tls_client_cert_file);
+	COPY_VAR_int(dnstap_send_identity);
+	COPY_VAR_int(dnstap_send_version);
+	COPY_VAR_ptr(dnstap_identity);
+	COPY_VAR_ptr(dnstap_version);
+	COPY_VAR_int(dnstap_sample_rate);
+	COPY_VAR_int(dnstap_log_resolver_query_messages);
+	COPY_VAR_int(dnstap_log_resolver_response_messages);
+	COPY_VAR_int(dnstap_log_client_query_messages);
+	COPY_VAR_int(dnstap_log_client_response_messages);
+	COPY_VAR_int(dnstap_log_forwarder_query_messages);
+	COPY_VAR_int(dnstap_log_forwarder_response_messages);
+	COPY_VAR_int(disable_dnssec_lame_check);
+	COPY_VAR_int(ip_ratelimit);
+	COPY_VAR_int(ip_ratelimit_cookie);
+	COPY_VAR_size_t(ip_ratelimit_slabs);
+	COPY_VAR_size_t(ip_ratelimit_size);
+	COPY_VAR_int(ip_ratelimit_factor);
+	COPY_VAR_int(ip_ratelimit_backoff);
+	COPY_VAR_int(ratelimit);
+	COPY_VAR_size_t(ratelimit_slabs);
+	COPY_VAR_size_t(ratelimit_size);
+	COPY_VAR_ptr(ratelimit_for_domain);
+	COPY_VAR_ptr(ratelimit_below_domain);
+	COPY_VAR_int(ratelimit_factor);
+	COPY_VAR_int(ratelimit_backoff);
+	COPY_VAR_int(outbound_msg_retry);
+	COPY_VAR_int(max_sent_count);
+	COPY_VAR_int(max_query_restarts);
+	COPY_VAR_int(qname_minimisation);
+	COPY_VAR_int(qname_minimisation_strict);
+	COPY_VAR_int(shm_enable);
+	COPY_VAR_int(shm_key);
+	COPY_VAR_ptr(edns_client_strings);
+	COPY_VAR_uint16_t(edns_client_string_opcode);
+	COPY_VAR_int(dnscrypt);
+	COPY_VAR_int(dnscrypt_port);
+	COPY_VAR_ptr(dnscrypt_provider);
+	COPY_VAR_ptr(dnscrypt_secret_key);
+	COPY_VAR_ptr(dnscrypt_provider_cert);
+	COPY_VAR_ptr(dnscrypt_provider_cert_rotated);
+	COPY_VAR_size_t(dnscrypt_shared_secret_cache_size);
+	COPY_VAR_size_t(dnscrypt_shared_secret_cache_slabs);
+	COPY_VAR_size_t(dnscrypt_nonce_cache_size);
+	COPY_VAR_size_t(dnscrypt_nonce_cache_slabs);
+	COPY_VAR_int(pad_responses);
+	COPY_VAR_size_t(pad_responses_block_size);
+	COPY_VAR_int(pad_queries);
+	COPY_VAR_size_t(pad_queries_block_size);
+#ifdef USE_IPSECMOD
+	COPY_VAR_int(ipsecmod_enabled);
+	COPY_VAR_ptr(ipsecmod_whitelist);
+	COPY_VAR_ptr(ipsecmod_hook);
+	COPY_VAR_int(ipsecmod_ignore_bogus);
+	COPY_VAR_int(ipsecmod_max_ttl);
+	COPY_VAR_int(ipsecmod_strict);
+#endif
+#ifdef USE_CACHEDB
+	COPY_VAR_ptr(cachedb_backend);
+	COPY_VAR_ptr(cachedb_secret);
+	COPY_VAR_int(cachedb_no_store);
+	COPY_VAR_int(cachedb_check_when_serve_expired);
+#ifdef USE_REDIS
+	COPY_VAR_ptr(redis_server_host);
+	COPY_VAR_ptr(redis_replica_server_host);
+	COPY_VAR_int(redis_server_port);
+	COPY_VAR_int(redis_replica_server_port);
+	COPY_VAR_ptr(redis_server_path);
+	COPY_VAR_ptr(redis_replica_server_path);
+	COPY_VAR_ptr(redis_server_password);
+	COPY_VAR_ptr(redis_replica_server_password);
+	COPY_VAR_int(redis_timeout);
+	COPY_VAR_int(redis_replica_timeout);
+	COPY_VAR_int(redis_command_timeout);
+	COPY_VAR_int(redis_replica_command_timeout);
+	COPY_VAR_int(redis_connect_timeout);
+	COPY_VAR_int(redis_replica_connect_timeout);
+	COPY_VAR_int(redis_expire_records);
+	COPY_VAR_int(redis_logical_db);
+	COPY_VAR_int(redis_replica_logical_db);
+#endif
+#endif
+	COPY_VAR_int(do_answer_cookie);
+	/* Not copied because the length and content could then not match.
+	   cookie_secret[40], cookie_secret_len
+	*/
+#ifdef USE_IPSET
+	COPY_VAR_ptr(ipset_name_v4);
+	COPY_VAR_ptr(ipset_name_v6);
+#endif
+	COPY_VAR_int(ede);
+}
+#endif /* ATOMIC_POINTER_LOCK_FREE && HAVE_LINK_ATOMIC_STORE */
+
+/** fast reload thread, adjust the cache sizes */
+static void
+fr_adjust_cache(struct module_env* env, struct config_file* oldcfg)
+{
+	if(env->cfg->msg_cache_size != oldcfg->msg_cache_size)
+		slabhash_adjust_size(env->msg_cache, env->cfg->msg_cache_size);
+	if(env->cfg->rrset_cache_size != oldcfg->rrset_cache_size)
+		slabhash_adjust_size(&env->rrset_cache->table,
+			env->cfg->rrset_cache_size);
+	if(env->key_cache &&
+		env->cfg->key_cache_size != oldcfg->key_cache_size)
+		slabhash_adjust_size(env->key_cache->slab,
+			env->cfg->key_cache_size);
+	if(env->cfg->infra_cache_numhosts != oldcfg->infra_cache_numhosts) {
+		size_t inframem = env->cfg->infra_cache_numhosts *
+			(sizeof(struct infra_key) + sizeof(struct infra_data)
+			+ INFRA_BYTES_NAME);
+		slabhash_adjust_size(env->infra_cache->hosts, inframem);
+	}
+	if(env->cfg->ratelimit_size != oldcfg->ratelimit_size) {
+		slabhash_adjust_size(env->infra_cache->domain_rates,
+			env->cfg->ratelimit_size);
+		slabhash_adjust_size(env->infra_cache->client_ip_rates,
+			env->cfg->ratelimit_size);
+	}
+	if(env->neg_cache &&
+		env->cfg->neg_cache_size != oldcfg->neg_cache_size) {
+		val_neg_adjust_size(env->neg_cache, env->cfg->neg_cache_size);
+	}
+}
+
+/** fast reload thread, adjust the iterator env */
+static void
+fr_adjust_iter_env(struct module_env* env, struct fast_reload_construct* ct)
+{
+	int m;
+	struct iter_env* iter_env = NULL;
+	/* There is no comparison here to see if no options changed and thus
+	 * no swap is needed, the trees with addresses and domains can be
+	 * large and that would take too long. Instead the trees are
+	 * swapped in. */
+
+	/* Because the iterator env is not locked, the update cannot happen
+	 * when fr nopause is used. Without it the fast reload pauses the
+	 * other threads, so they are not currently using the structure. */
+	m = modstack_find(env->modstack, "iterator");
+	if(m != -1) iter_env = (struct iter_env*)env->modinfo[m];
+	if(iter_env) {
+		/* Swap the data so that the delete happens afterwards. */
+		int* oldtargetfetchpolicy = iter_env->target_fetch_policy;
+		int oldmaxdependencydepth = iter_env->max_dependency_depth;
+		struct iter_donotq* olddonotq = iter_env->donotq;
+		struct iter_priv* oldpriv = iter_env->priv;
+		struct rbtree_type* oldcapswhite = iter_env->caps_white;
+		struct iter_nat64 oldnat64 = iter_env->nat64;
+
+		iter_env->target_fetch_policy = ct->target_fetch_policy;
+		iter_env->max_dependency_depth = ct->max_dependency_depth;
+		iter_env->donotq = ct->donotq;
+		iter_env->priv = ct->priv;
+		iter_env->caps_white = ct->caps_white;
+		iter_env->nat64 = ct->nat64;
+		iter_env->outbound_msg_retry = env->cfg->outbound_msg_retry;
+		iter_env->max_sent_count = env->cfg->max_sent_count;
+		iter_env->max_query_restarts = env->cfg->max_query_restarts;
+
+		ct->target_fetch_policy = oldtargetfetchpolicy;
+		ct->max_dependency_depth = oldmaxdependencydepth;
+		ct->donotq = olddonotq;
+		ct->priv = oldpriv;
+		ct->caps_white = oldcapswhite;
+		ct->nat64 = oldnat64;
+	}
+}
+
+/** fast reload thread, adjust the validator env */
+static void
+fr_adjust_val_env(struct module_env* env, struct fast_reload_construct* ct,
+	struct config_file* oldcfg)
+{
+	int m;
+	struct val_env* val_env = NULL;
+	if(env->cfg->bogus_ttl == oldcfg->bogus_ttl &&
+		env->cfg->val_date_override == oldcfg->val_date_override &&
+		env->cfg->val_sig_skew_min == oldcfg->val_sig_skew_min &&
+		env->cfg->val_sig_skew_max == oldcfg->val_sig_skew_max &&
+		env->cfg->val_max_restart == oldcfg->val_max_restart &&
+		strcmp(env->cfg->val_nsec3_key_iterations,
+		oldcfg->val_nsec3_key_iterations) == 0)
+		return; /* no changes */
+
+	/* Because the validator env is not locked, the update cannot happen
+	 * when fr nopause is used. Without it the fast reload pauses the
+	 * other threads, so they are not currently using the structure. */
+	m = modstack_find(env->modstack, "validator");
+	if(m != -1) val_env = (struct val_env*)env->modinfo[m];
+	if(val_env) {
+		/* Swap the arrays so that the delete happens afterwards. */
+		size_t* oldkeysize = val_env->nsec3_keysize;
+		size_t* oldmaxiter = val_env->nsec3_maxiter;
+		val_env->nsec3_keysize = NULL;
+		val_env->nsec3_maxiter = NULL;
+		val_env_apply_cfg(val_env, env->cfg, ct->nsec3_keysize,
+			ct->nsec3_maxiter, ct->nsec3_keyiter_count);
+		ct->nsec3_keysize = oldkeysize;
+		ct->nsec3_maxiter = oldmaxiter;
+		if(env->neg_cache) {
+			lock_basic_lock(&env->neg_cache->lock);
+			env->neg_cache->nsec3_max_iter = val_env->
+				nsec3_maxiter[val_env->nsec3_keyiter_count-1];
+			lock_basic_unlock(&env->neg_cache->lock);
+		}
+	}
+}
+
+/** fast reload thread, adjust the infra cache parameters */
+static void
+fr_adjust_infra(struct module_env* env, struct fast_reload_construct* ct)
+{
+	struct infra_cache* infra = env->infra_cache;
+	struct config_file* cfg = env->cfg;
+	struct rbtree_type oldwaitlim = infra->wait_limits_netblock;
+	struct rbtree_type oldwaitlimcookie =
+		infra->wait_limits_cookie_netblock;
+	struct rbtree_type olddomainlim = infra->domain_limits;
+
+	/* The size of the infra cache and ip rates is changed
+	 * in fr_adjust_cache. */
+	infra->host_ttl = cfg->host_ttl;
+	infra->infra_keep_probing = cfg->infra_keep_probing;
+	infra_dp_ratelimit = cfg->ratelimit;
+	infra_ip_ratelimit = cfg->ip_ratelimit;
+	infra_ip_ratelimit_cookie = cfg->ip_ratelimit_cookie;
+	infra->wait_limits_netblock = ct->wait_limits_netblock;
+	infra->wait_limits_cookie_netblock = ct->wait_limits_cookie_netblock;
+	infra->domain_limits = ct->domain_limits;
+
+	ct->wait_limits_netblock = oldwaitlim;
+	ct->wait_limits_cookie_netblock = oldwaitlimcookie;
+	ct->domain_limits = olddomainlim;
+}
+
+/** fast reload thread, reload config with putting the new config items
+ * in place and swapping out the old items. */
+static int
+fr_reload_config(struct fast_reload_thread* fr, struct config_file* newcfg,
+	struct fast_reload_construct* ct)
+{
+	struct daemon* daemon = fr->worker->daemon;
+	struct module_env* env = daemon->env;
+
+	/* These are constructed in the fr_construct_from_config routine. */
+	log_assert(ct->oldcfg);
+	log_assert(ct->fwds);
+	log_assert(ct->hints);
+
+	/* Grab big locks to satisfy lock conditions. */
+	lock_rw_wrlock(&ct->views->lock);
+	lock_rw_wrlock(&env->views->lock);
+	lock_rw_wrlock(&ct->respip_set->lock);
+	lock_rw_wrlock(&env->respip_set->lock);
+	lock_rw_wrlock(&ct->local_zones->lock);
+	lock_rw_wrlock(&daemon->local_zones->lock);
+	lock_rw_wrlock(&ct->auth_zones->rpz_lock);
+	lock_rw_wrlock(&env->auth_zones->rpz_lock);
+	lock_rw_wrlock(&ct->auth_zones->lock);
+	lock_rw_wrlock(&env->auth_zones->lock);
+	lock_rw_wrlock(&ct->fwds->lock);
+	lock_rw_wrlock(&env->fwds->lock);
+	lock_rw_wrlock(&ct->hints->lock);
+	lock_rw_wrlock(&env->hints->lock);
+	if(ct->anchors) {
+		lock_basic_lock(&ct->anchors->lock);
+		lock_basic_lock(&env->anchors->lock);
+	}
+
+#if defined(ATOMIC_POINTER_LOCK_FREE) && defined(HAVE_LINK_ATOMIC_STORE)
+	if(fr->fr_nopause) {
+		fr_atomic_copy_cfg(ct->oldcfg, env->cfg, newcfg);
+	} else {
+#endif
+		/* Store old config elements. */
+		*ct->oldcfg = *env->cfg;
+		/* Insert new config elements. */
+		*env->cfg = *newcfg;
+#if defined(ATOMIC_POINTER_LOCK_FREE) && defined(HAVE_LINK_ATOMIC_STORE)
+	}
+#endif
+
+	if(env->cfg->log_identity || ct->oldcfg->log_identity) {
+		/* pick up new log_identity string to use for log output. */
+		log_ident_set_or_default(env->cfg->log_identity);
+	}
+	/* the newcfg elements are in env->cfg, so should not be freed here. */
+#if defined(ATOMIC_POINTER_LOCK_FREE) && defined(HAVE_LINK_ATOMIC_STORE)
+	/* if used, the routine that copies the config has zeroed items. */
+	if(!fr->fr_nopause)
+#endif
+		memset(newcfg, 0, sizeof(*newcfg));
+
+	/* Quickly swap the tree roots themselves with the already allocated
+	 * elements. This is a quick swap operation on the pointer.
+	 * The other threads are stopped and locks are held, so that a
+	 * consistent view of the configuration, before, and after, exists
+	 * towards the state machine for query resolution. */
+	forwards_swap_tree(env->fwds, ct->fwds);
+	hints_swap_tree(env->hints, ct->hints);
+	views_swap_tree(env->views, ct->views);
+	acl_list_swap_tree(daemon->acl, ct->acl);
+	acl_list_swap_tree(daemon->acl_interface, ct->acl_interface);
+	tcl_list_swap_tree(daemon->tcl, ct->tcl);
+	local_zones_swap_tree(daemon->local_zones, ct->local_zones);
+	respip_set_swap_tree(env->respip_set, ct->respip_set);
+	daemon->use_response_ip = ct->use_response_ip;
+	daemon->use_rpz = ct->use_rpz;
+	auth_zones_swap(env->auth_zones, ct->auth_zones);
+	edns_strings_swap_tree(env->edns_strings, ct->edns_strings);
+	anchors_swap_tree(env->anchors, ct->anchors);
+#ifdef USE_CACHEDB
+	daemon->env->cachedb_enabled = cachedb_is_enabled(&daemon->mods,
+		daemon->env);
+#endif
+#ifdef USE_DNSTAP
+	if(env->cfg->dnstap) {
+		if(!fr->fr_nopause)
+			dt_apply_cfg(daemon->dtenv, env->cfg);
+		else dt_apply_logcfg(daemon->dtenv, env->cfg);
+	}
+#endif
+	fr_adjust_cache(env, ct->oldcfg);
+	if(!fr->fr_nopause) {
+		fr_adjust_iter_env(env, ct);
+		fr_adjust_val_env(env, ct, ct->oldcfg);
+		fr_adjust_infra(env, ct);
+	}
+
+	/* Set globals with new config. */
+	config_apply(env->cfg);
+
+	lock_rw_unlock(&ct->views->lock);
+	lock_rw_unlock(&env->views->lock);
+	lock_rw_unlock(&ct->respip_set->lock);
+	lock_rw_unlock(&env->respip_set->lock);
+	lock_rw_unlock(&ct->local_zones->lock);
+	lock_rw_unlock(&daemon->local_zones->lock);
+	lock_rw_unlock(&ct->auth_zones->lock);
+	lock_rw_unlock(&env->auth_zones->lock);
+	lock_rw_unlock(&ct->auth_zones->rpz_lock);
+	lock_rw_unlock(&env->auth_zones->rpz_lock);
+	lock_rw_unlock(&ct->fwds->lock);
+	lock_rw_unlock(&env->fwds->lock);
+	lock_rw_unlock(&ct->hints->lock);
+	lock_rw_unlock(&env->hints->lock);
+	if(ct->anchors) {
+		lock_basic_unlock(&ct->anchors->lock);
+		lock_basic_unlock(&env->anchors->lock);
+	}
+
+	return 1;
+}
+
+/** fast reload, poll for ack incoming. */
+static void
+fr_poll_for_ack(struct fast_reload_thread* fr)
+{
+	int loopexit = 0, bcount = 0;
+	uint32_t cmd;
+	ssize_t ret;
+
+	if(fr->need_to_quit)
+		return;
+	/* Is there data? */
+	if(!sock_poll_timeout(fr->commpair[1], -1, 1, 0, NULL)) {
+		log_err("fr_poll_for_ack: poll failed");
+		return;
+	}
+
+	/* Read the data */
+	while(1) {
+		if(++loopexit > IPC_LOOP_MAX) {
+			log_err("fr_poll_for_ack: recv loops %s",
+				sock_strerror(errno));
+			return;
+		}
+		ret = recv(fr->commpair[1], ((char*)&cmd)+bcount,
+			sizeof(cmd)-bcount, 0);
+		if(ret == -1) {
+			if(
+#ifndef USE_WINSOCK
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+#else
+				WSAGetLastError() == WSAEINTR ||
+				WSAGetLastError() == WSAEINPROGRESS ||
+				WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+				)
+				continue; /* Try again. */
+			log_err("fr_poll_for_ack: recv: %s",
+				sock_strerror(errno));
+			return;
+		} else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+			bcount += ret;
+			if((size_t)bcount < sizeof(cmd))
+				continue;
+		}
+		break;
+	}
+	if(cmd == fast_reload_notification_exit) {
+		fr->need_to_quit = 1;
+		verbose(VERB_ALGO, "fast reload wait for ack: "
+			"exit notification received");
+		return;
+	}
+	if(cmd != fast_reload_notification_reload_ack) {
+		verbose(VERB_ALGO, "fast reload wait for ack: "
+			"wrong notification %d", (int)cmd);
+	}
+}
+
+/** fast reload thread, reload ipc communication to stop and start threads. */
+static int
+fr_reload_ipc(struct fast_reload_thread* fr, struct config_file* newcfg,
+	struct fast_reload_construct* ct)
+{
+	int result = 1;
+	if(!fr->fr_nopause) {
+		fr_send_notification(fr, fast_reload_notification_reload_stop);
+		fr_poll_for_ack(fr);
+	}
+	if(!fr_reload_config(fr, newcfg, ct)) {
+		result = 0;
+	}
+	if(!fr->fr_nopause) {
+		fr_send_notification(fr, fast_reload_notification_reload_start);
+		fr_poll_for_ack(fr);
+	}
+	return result;
+}
+
+/** fast reload thread, load config */
+static int
+fr_load_config(struct fast_reload_thread* fr, struct timeval* time_read,
+	struct timeval* time_construct, struct timeval* time_reload)
+{
+	struct fast_reload_construct ct;
+	struct config_file* newcfg = NULL;
+	memset(&ct, 0, sizeof(ct));
+
+	/* Read file. */
+	if(!fr_read_config(fr, &newcfg))
+		return 0;
+	if(gettimeofday(time_read, NULL) < 0)
+		log_err("gettimeofday: %s", strerror(errno));
+	if(fr_poll_for_quit(fr)) {
+		config_delete(newcfg);
+		return 1;
+	}
+
+	/* Check if the config can be loaded */
+	if(!fr_check_tag_defines(fr, newcfg)) {
+		config_delete(newcfg);
+		return 0;
+	}
+	if(!fr_check_compat_cfg(fr, newcfg)) {
+		config_delete(newcfg);
+		return 0;
+	}
+	if(!fr_check_nopause_cfg(fr, newcfg)) {
+		config_delete(newcfg);
+		return 0;
+	}
+	if(fr_poll_for_quit(fr)) {
+		config_delete(newcfg);
+		return 1;
+	}
+
+	/* Construct items. */
+	if(!fr_construct_from_config(fr, newcfg, &ct)) {
+		config_delete(newcfg);
+		if(!fr_output_printf(fr, "Could not construct from the "
+			"config, check for errors with unbound-checkconf, or "
+			"out of memory. The parse errors are printed in "
+			"the log.\n"))
+			return 0;
+		fr_send_notification(fr, fast_reload_notification_printout);
+		return 0;
+	}
+	if(gettimeofday(time_construct, NULL) < 0)
+		log_err("gettimeofday: %s", strerror(errno));
+	if(fr_poll_for_quit(fr)) {
+		config_delete(newcfg);
+		fr_construct_clear(&ct);
+		return 1;
+	}
+
+	/* Reload server. */
+	if(!fr_reload_ipc(fr, newcfg, &ct)) {
+		config_delete(newcfg);
+		fr_construct_clear(&ct);
+		if(!fr_output_printf(fr, "error: reload failed\n"))
+			return 0;
+		fr_send_notification(fr, fast_reload_notification_printout);
+		return 0;
+	}
+	if(gettimeofday(time_reload, NULL) < 0)
+		log_err("gettimeofday: %s", strerror(errno));
+
+	if(fr_poll_for_quit(fr)) {
+		config_delete(newcfg);
+		fr_construct_clear(&ct);
+		return 1;
+	}
+	if(fr->fr_nopause) {
+		/* Poll every thread, with a no-work poll item over the
+		 * command pipe. This makes the worker thread surely move
+		 * to deal with that event, and thus the thread is no longer
+		 * holding, eg. a string item from the old config struct.
+		 * And then the old config struct can safely be deleted.
+		 * Only needed when nopause is used, because without that
+		 * the worker threads are already waiting on a command pipe
+		 * item. This nopause command pipe item does not take work,
+		 * it returns immediately, so it does not delay the workers.
+		 * They can be polled one at a time. But its processing causes
+		 * the worker to have released data items from old config.
+		 * This also makes sure the threads are not holding locks on
+		 * individual items in the local_zones, views, respip_set. */
+		fr_send_notification(fr,
+			fast_reload_notification_reload_nopause_poll);
+		fr_poll_for_ack(fr);
+	}
+
+	/* Delete old. */
+	config_delete(newcfg);
+	fr_construct_clear(&ct);
+	return 1;
+}
+
+/** fast reload thread. the thread main function */
+static void* fast_reload_thread_main(void* arg)
+{
+	struct fast_reload_thread* fast_reload_thread = (struct fast_reload_thread*)arg;
+	struct timeval time_start, time_read, time_construct, time_reload,
+		time_end;
+	log_thread_set(&fast_reload_thread->threadnum);
+
+	verbose(VERB_ALGO, "start fast reload thread");
+	if(fast_reload_thread->fr_verb >= 1) {
+		fr_init_time(&time_start, &time_read, &time_construct,
+			&time_reload, &time_end);
+		if(fr_poll_for_quit(fast_reload_thread))
+			goto done;
+	}
+
+	/* print output to the client */
+	if(fast_reload_thread->fr_verb >= 1) {
+		if(!fr_output_printf(fast_reload_thread, "thread started\n"))
+			goto done_error;
+		fr_send_notification(fast_reload_thread,
+			fast_reload_notification_printout);
+		if(fr_poll_for_quit(fast_reload_thread))
+			goto done;
+	}
+
+	if(!fr_load_config(fast_reload_thread, &time_read, &time_construct,
+		&time_reload))
+		goto done_error;
+	if(fr_poll_for_quit(fast_reload_thread))
+		goto done;
+
+	if(fast_reload_thread->fr_verb >= 1) {
+		if(!fr_finish_time(fast_reload_thread, &time_start, &time_read,
+			&time_construct, &time_reload, &time_end))
+			goto done_error;
+		if(fr_poll_for_quit(fast_reload_thread))
+			goto done;
+	}
+
+	if(!fr_output_printf(fast_reload_thread, "ok\n"))
+		goto done_error;
+	fr_send_notification(fast_reload_thread,
+		fast_reload_notification_printout);
+	verbose(VERB_ALGO, "stop fast reload thread");
+	/* If this is not an exit due to quit earlier, send regular done. */
+	if(!fast_reload_thread->need_to_quit)
+		fr_send_notification(fast_reload_thread,
+			fast_reload_notification_done);
+	/* If during the fast_reload_notification_done send,
+	 * fast_reload_notification_exit was received, ack it. If the
+	 * thread is exiting due to quit received earlier, also ack it.*/
+done:
+	if(fast_reload_thread->need_to_quit)
+		fr_send_notification(fast_reload_thread,
+			fast_reload_notification_exited);
+	return NULL;
+done_error:
+	verbose(VERB_ALGO, "stop fast reload thread with done_error");
+	fr_send_notification(fast_reload_thread,
+		fast_reload_notification_done_error);
+	return NULL;
+}
+#endif /* !THREADS_DISABLED */
+
+/** create a socketpair for bidirectional communication, false on failure */
+static int
+create_socketpair(int* pair, struct ub_randstate* rand)
+{
+#ifndef USE_WINSOCK
+	if(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
+		log_err("socketpair: %s", strerror(errno));
+		return 0;
+	}
+	(void)rand;
+#else
+	struct sockaddr_in addr, baddr, accaddr, connaddr;
+	socklen_t baddrlen, accaddrlen, connaddrlen;
+	uint8_t localhost[] = {127, 0, 0, 1};
+	uint8_t nonce[16], recvnonce[16];
+	size_t i;
+	int lst, pollin_event, bcount, loopcount;
+	int connect_poll_timeout = 200; /* msec to wait for connection */
+	ssize_t ret;
+	pair[0] = -1;
+	pair[1] = -1;
+	for(i=0; i<sizeof(nonce); i++) {
+		nonce[i] = ub_random_max(rand, 256);
+	}
+	lst = socket(AF_INET, SOCK_STREAM, 0);
+	if(lst == -1) {
+		log_err("create_socketpair: socket: %s", sock_strerror(errno));
+		return 0;
+	}
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = 0;
+	memcpy(&addr.sin_addr, localhost, 4);
+	if(bind(lst, (struct sockaddr*)&addr, (socklen_t)sizeof(addr))
+		== -1) {
+		log_err("create socketpair: bind: %s", sock_strerror(errno));
+		sock_close(lst);
+		return 0;
+	}
+	if(listen(lst, 12) == -1) {
+		log_err("create socketpair: listen: %s", sock_strerror(errno));
+		sock_close(lst);
+		return 0;
+	}
+
+	pair[1] = socket(AF_INET, SOCK_STREAM, 0);
+	if(pair[1] == -1) {
+		log_err("create socketpair: socket: %s", sock_strerror(errno));
+		sock_close(lst);
+		return 0;
+	}
+	baddrlen = (socklen_t)sizeof(baddr);
+	if(getsockname(lst, (struct sockaddr*)&baddr, &baddrlen) == -1) {
+		log_err("create socketpair: getsockname: %s",
+			sock_strerror(errno));
+		sock_close(lst);
+		sock_close(pair[1]);
+		pair[1] = -1;
+		return 0;
+	}
+	if(baddrlen > (socklen_t)sizeof(baddr)) {
+		log_err("create socketpair: getsockname returned addr too big");
+		sock_close(lst);
+		sock_close(pair[1]);
+		pair[1] = -1;
+		return 0;
+	}
+	/* the socket is blocking */
+	if(connect(pair[1], (struct sockaddr*)&baddr, baddrlen) == -1) {
+		log_err("create socketpair: connect: %s",
+			sock_strerror(errno));
+		sock_close(lst);
+		sock_close(pair[1]);
+		pair[1] = -1;
+		return 0;
+	}
+	if(!sock_poll_timeout(lst, connect_poll_timeout, 1, 0, &pollin_event)) {
+		log_err("create socketpair: poll for accept failed: %s",
+			sock_strerror(errno));
+		sock_close(lst);
+		sock_close(pair[1]);
+		pair[1] = -1;
+		return 0;
+	}
+	if(!pollin_event) {
+		log_err("create socketpair: poll timeout for accept");
+		sock_close(lst);
+		sock_close(pair[1]);
+		pair[1] = -1;
+		return 0;
+	}
+	accaddrlen = (socklen_t)sizeof(accaddr);
+	pair[0] = accept(lst, (struct sockaddr*)&accaddr, &accaddrlen);
+	if(pair[0] == -1) {
+		log_err("create socketpair: accept: %s", sock_strerror(errno));
+		sock_close(lst);
+		sock_close(pair[1]);
+		pair[1] = -1;
+		return 0;
+	}
+	if(accaddrlen > (socklen_t)sizeof(accaddr)) {
+		log_err("create socketpair: accept returned addr too big");
+		sock_close(lst);
+		sock_close(pair[0]);
+		sock_close(pair[1]);
+		pair[0] = -1;
+		pair[1] = -1;
+		return 0;
+	}
+	if(accaddr.sin_family != AF_INET ||
+	   memcmp(localhost, &accaddr.sin_addr, 4) != 0) {
+		log_err("create socketpair: accept from wrong address");
+		sock_close(lst);
+		sock_close(pair[0]);
+		sock_close(pair[1]);
+		pair[0] = -1;
+		pair[1] = -1;
+		return 0;
+	}
+	connaddrlen = (socklen_t)sizeof(connaddr);
+	if(getsockname(pair[1], (struct sockaddr*)&connaddr, &connaddrlen)
+		== -1) {
+		log_err("create socketpair: getsockname connectedaddr: %s",
+			sock_strerror(errno));
+		sock_close(lst);
+		sock_close(pair[0]);
+		sock_close(pair[1]);
+		pair[0] = -1;
+		pair[1] = -1;
+		return 0;
+	}
+	if(connaddrlen > (socklen_t)sizeof(connaddr)) {
+		log_err("create socketpair: getsockname connectedaddr returned addr too big");
+		sock_close(lst);
+		sock_close(pair[0]);
+		sock_close(pair[1]);
+		pair[0] = -1;
+		pair[1] = -1;
+		return 0;
+	}
+	if(connaddr.sin_family != AF_INET ||
+	   memcmp(localhost, &connaddr.sin_addr, 4) != 0) {
+		log_err("create socketpair: getsockname connectedaddr returned wrong address");
+		sock_close(lst);
+		sock_close(pair[0]);
+		sock_close(pair[1]);
+		pair[0] = -1;
+		pair[1] = -1;
+		return 0;
+	}
+	if(accaddr.sin_port != connaddr.sin_port) {
+		log_err("create socketpair: accept from wrong port");
+		sock_close(lst);
+		sock_close(pair[0]);
+		sock_close(pair[1]);
+		pair[0] = -1;
+		pair[1] = -1;
+		return 0;
+	}
+	sock_close(lst);
+
+	loopcount = 0;
+	bcount = 0;
+	while(1) {
+		if(++loopcount > IPC_LOOP_MAX) {
+			log_err("create socketpair: send failed due to loop");
+			sock_close(pair[0]);
+			sock_close(pair[1]);
+			pair[0] = -1;
+			pair[1] = -1;
+			return 0;
+		}
+		ret = send(pair[1], (void*)(nonce+bcount),
+			sizeof(nonce)-bcount, 0);
+		if(ret == -1) {
+			if(
+#ifndef USE_WINSOCK
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+#else
+				WSAGetLastError() == WSAEINTR ||
+				WSAGetLastError() == WSAEINPROGRESS ||
+				WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+				)
+				continue; /* Try again. */
+			log_err("create socketpair: send: %s", sock_strerror(errno));
+			sock_close(pair[0]);
+			sock_close(pair[1]);
+			pair[0] = -1;
+			pair[1] = -1;
+			return 0;
+		} else if(ret+(ssize_t)bcount != sizeof(nonce)) {
+			bcount += ret;
+			if((size_t)bcount < sizeof(nonce))
+				continue;
+		}
+		break;
+	}
+
+	if(!sock_poll_timeout(pair[0], connect_poll_timeout, 1, 0, &pollin_event)) {
+		log_err("create socketpair: poll failed: %s",
+			sock_strerror(errno));
+		sock_close(pair[0]);
+		sock_close(pair[1]);
+		pair[0] = -1;
+		pair[1] = -1;
+		return 0;
+	}
+	if(!pollin_event) {
+		log_err("create socketpair: poll timeout for recv");
+		sock_close(pair[0]);
+		sock_close(pair[1]);
+		pair[0] = -1;
+		pair[1] = -1;
+		return 0;
+	}
+
+	loopcount = 0;
+	bcount = 0;
+	while(1) {
+		if(++loopcount > IPC_LOOP_MAX) {
+			log_err("create socketpair: recv failed due to loop");
+			sock_close(pair[0]);
+			sock_close(pair[1]);
+			pair[0] = -1;
+			pair[1] = -1;
+			return 0;
+		}
+		ret = recv(pair[0], (void*)(recvnonce+bcount),
+			sizeof(nonce)-bcount, 0);
+		if(ret == -1) {
+			if(
+#ifndef USE_WINSOCK
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+#else
+				WSAGetLastError() == WSAEINTR ||
+				WSAGetLastError() == WSAEINPROGRESS ||
+				WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+				)
+				continue; /* Try again. */
+			log_err("create socketpair: recv: %s", sock_strerror(errno));
+			sock_close(pair[0]);
+			sock_close(pair[1]);
+			pair[0] = -1;
+			pair[1] = -1;
+			return 0;
+		} else if(ret == 0) {
+			log_err("create socketpair: stream closed");
+			sock_close(pair[0]);
+			sock_close(pair[1]);
+			pair[0] = -1;
+			pair[1] = -1;
+			return 0;
+		} else if(ret+(ssize_t)bcount != sizeof(nonce)) {
+			bcount += ret;
+			if((size_t)bcount < sizeof(nonce))
+				continue;
+		}
+		break;
+	}
+
+	if(memcmp(nonce, recvnonce, sizeof(nonce)) != 0) {
+		log_err("create socketpair: recv wrong nonce");
+		sock_close(pair[0]);
+		sock_close(pair[1]);
+		pair[0] = -1;
+		pair[1] = -1;
+		return 0;
+	}
+#endif
+	return 1;
+}
+
+/** fast reload thread. setup the thread info */
+static int
+fast_reload_thread_setup(struct worker* worker, int fr_verb, int fr_nopause,
+	int fr_drop_mesh)
+{
+	struct fast_reload_thread* fr;
+	int numworkers = worker->daemon->num;
+	worker->daemon->fast_reload_thread = (struct fast_reload_thread*)
+		calloc(1, sizeof(*worker->daemon->fast_reload_thread));
+	if(!worker->daemon->fast_reload_thread)
+		return 0;
+	fr = worker->daemon->fast_reload_thread;
+	fr->fr_verb = fr_verb;
+	fr->fr_nopause = fr_nopause;
+	fr->fr_drop_mesh = fr_drop_mesh;
+	worker->daemon->fast_reload_drop_mesh = fr->fr_drop_mesh;
+	/* The thread id printed in logs, numworker+1 is the dnstap thread.
+	 * This is numworkers+2. */
+	fr->threadnum = numworkers+2;
+	fr->commpair[0] = -1;
+	fr->commpair[1] = -1;
+	fr->commreload[0] = -1;
+	fr->commreload[1] = -1;
+	if(!create_socketpair(fr->commpair, worker->daemon->rand)) {
+		free(fr);
+		worker->daemon->fast_reload_thread = NULL;
+		return 0;
+	}
+	fr->worker = worker;
+	fr->fr_output = (struct config_strlist_head*)calloc(1,
+		sizeof(*fr->fr_output));
+	if(!fr->fr_output) {
+		sock_close(fr->commpair[0]);
+		sock_close(fr->commpair[1]);
+		free(fr);
+		worker->daemon->fast_reload_thread = NULL;
+		return 0;
+	}
+	if(!create_socketpair(fr->commreload, worker->daemon->rand)) {
+		sock_close(fr->commpair[0]);
+		sock_close(fr->commpair[1]);
+		free(fr->fr_output);
+		free(fr);
+		worker->daemon->fast_reload_thread = NULL;
+		return 0;
+	}
+	lock_basic_init(&fr->fr_output_lock);
+	lock_protect(&fr->fr_output_lock, fr->fr_output,
+		sizeof(*fr->fr_output));
+	return 1;
+}
+
+/** fast reload, delete auth zone change list */
+static void
+fr_auth_change_list_delete(
+	struct fast_reload_auth_change* auth_zone_change_list)
+{
+	struct fast_reload_auth_change* item, *next;
+	item = auth_zone_change_list;
+	while(item) {
+		next = item->next;
+		free(item);
+		item = next;
+	}
+}
+
+/** fast reload thread. desetup and delete the thread info. */
+static void
+fast_reload_thread_desetup(struct fast_reload_thread* fast_reload_thread)
+{
+	if(!fast_reload_thread)
+		return;
+	if(fast_reload_thread->service_event &&
+		fast_reload_thread->service_event_is_added) {
+		ub_event_del(fast_reload_thread->service_event);
+		fast_reload_thread->service_event_is_added = 0;
+	}
+	if(fast_reload_thread->service_event)
+		ub_event_free(fast_reload_thread->service_event);
+	sock_close(fast_reload_thread->commpair[0]);
+	sock_close(fast_reload_thread->commpair[1]);
+	sock_close(fast_reload_thread->commreload[0]);
+	sock_close(fast_reload_thread->commreload[1]);
+	if(fast_reload_thread->printq) {
+		fr_main_perform_printout(fast_reload_thread);
+		/* If it is empty now, there is nothing to print on fd. */
+		if(fr_printq_empty(fast_reload_thread->printq)) {
+			fr_printq_delete(fast_reload_thread->printq);
+		} else {
+			/* Keep the printq around to printout the remaining
+			 * text to the remote client. Until it is done, it
+			 * sits on a list, that is in the daemon struct.
+			 * The event can then spool the remaining text to the
+			 * remote client and eventually delete itself from the
+			 * callback. */
+			fr_printq_list_insert(fast_reload_thread->printq,
+				fast_reload_thread->worker->daemon);
+			fast_reload_thread->printq = NULL;
+		}
+	}
+	lock_basic_destroy(&fast_reload_thread->fr_output_lock);
+	if(fast_reload_thread->fr_output) {
+		config_delstrlist(fast_reload_thread->fr_output->first);
+		free(fast_reload_thread->fr_output);
+	}
+	fr_auth_change_list_delete(fast_reload_thread->auth_zone_change_list);
+
+	free(fast_reload_thread);
+}
+
+/**
+ * Fast reload thread, send a command to the thread. Blocking on timeout.
+ * It handles received input from the thread, if any is received.
+ */
+static void
+fr_send_cmd_to(struct fast_reload_thread* fr,
+	enum fast_reload_notification status, int check_cmds, int blocking)
+{
+	int outevent, loopexit = 0, bcount = 0;
+	uint32_t cmd;
+	ssize_t ret;
+	verbose(VERB_ALGO, "send notification to fast reload thread: %s",
+		fr_notification_to_string(status));
+	cmd = status;
+	while(1) {
+		if(++loopexit > IPC_LOOP_MAX) {
+			log_err("send notification to fast reload: could not send notification: loop");
+			return;
+		}
+		if(check_cmds)
+			fr_check_cmd_from_thread(fr);
+		/* wait for socket to become writable */
+		if(!sock_poll_timeout(fr->commpair[0],
+			(blocking?-1:IPC_NOTIFICATION_WAIT),
+			0, 1, &outevent)) {
+			log_err("send notification to fast reload: poll failed");
+			return;
+		}
+		if(!outevent)
+			continue;
+		/* keep static analyzer happy; send(-1,..) */
+		log_assert(fr->commpair[0] >= 0);
+		ret = send(fr->commpair[0], ((char*)&cmd)+bcount,
+			sizeof(cmd)-bcount, 0);
+		if(ret == -1) {
+			if(
+#ifndef USE_WINSOCK
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+#else
+				WSAGetLastError() == WSAEINTR ||
+				WSAGetLastError() == WSAEINPROGRESS ||
+				WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+				)
+				continue; /* Try again. */
+			log_err("send notification to fast reload: send: %s",
+				sock_strerror(errno));
+			return;
+		} else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+			bcount += ret;
+			if((size_t)bcount < sizeof(cmd))
+				continue;
+		}
+		break;
+	}
+}
+
+/** Fast reload, the main thread handles that the fast reload thread has
+ * exited. */
+static void
+fr_main_perform_done(struct fast_reload_thread* fr)
+{
+	struct worker* worker = fr->worker;
+	verbose(VERB_ALGO, "join with fastreload thread");
+	ub_thread_join(fr->tid);
+	verbose(VERB_ALGO, "joined with fastreload thread");
+	fast_reload_thread_desetup(fr);
+	worker->daemon->fast_reload_thread = NULL;
+}
+
+/** Append strlist after strlist */
+static void
+cfg_strlist_append_listhead(struct config_strlist_head* list,
+	struct config_strlist_head* more)
+{
+	if(!more->first)
+		return;
+	if(list->last)
+		list->last->next = more->first;
+	else
+		list->first = more->first;
+	list->last = more->last;
+}
+
+/** Fast reload, the remote control thread handles that the fast reload thread
+ * has output to be printed, on the linked list that is locked. */
+static void
+fr_main_perform_printout(struct fast_reload_thread* fr)
+{
+	struct config_strlist_head out;
+
+	/* Fetch the list of items to be printed */
+	lock_basic_lock(&fr->fr_output_lock);
+	out.first = fr->fr_output->first;
+	out.last = fr->fr_output->last;
+	fr->fr_output->first = NULL;
+	fr->fr_output->last = NULL;
+	lock_basic_unlock(&fr->fr_output_lock);
+
+	if(!fr->printq || !fr->printq->client_cp) {
+		/* There is no output socket, delete it. */
+		config_delstrlist(out.first);
+		return;
+	}
+
+	/* Put them on the output list, not locked because the list
+	 * producer and consumer are both owned by the remote control thread,
+	 * it moves the items to the list for printing in the event callback
+	 * for the client_cp. */
+	cfg_strlist_append_listhead(fr->printq->to_print, &out);
+
+	/* Set the client_cp to output if not already */
+	if(!fr->printq->client_cp->event_added)
+		comm_point_listen_for_rw(fr->printq->client_cp, 0, 1);
+}
+
+/** fast reload, receive ack from workers that they are waiting, run
+ * by the mainthr after sending them reload_stop. */
+static void
+fr_read_ack_from_workers(struct fast_reload_thread* fr)
+{
+	struct daemon* daemon = fr->worker->daemon;
+	/* Every worker sends one byte, wait for num-1 bytes. */
+	int count=0, total=daemon->num-1;
+	while(count < total) {
+		uint8_t r;
+		ssize_t ret;
+		ret = recv(fr->commreload[0], (void*)&r, 1, 0);
+		if(ret == -1) {
+			if(
+#ifndef USE_WINSOCK
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+#else
+				WSAGetLastError() == WSAEINTR ||
+				WSAGetLastError() == WSAEINPROGRESS ||
+				WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+				)
+				continue; /* Try again */
+			log_err("worker reload ack: recv failed: %s",
+				sock_strerror(errno));
+			return;
+		}
+		count++;
+		verbose(VERB_ALGO, "worker reload ack from (uint8_t)%d",
+			(int)r);
+	}
+}
+
+/** fast reload, poll for reload_start in mainthr waiting on a notification
+ * from the fast reload thread. */
+static void
+fr_poll_for_reload_start(struct fast_reload_thread* fr)
+{
+	int loopexit = 0, bcount = 0;
+	uint32_t cmd;
+	ssize_t ret;
+
+	/* Is there data? */
+	if(!sock_poll_timeout(fr->commpair[0], -1, 1, 0, NULL)) {
+		log_err("fr_poll_for_reload_start: poll failed");
+		return;
+	}
+
+	/* Read the data */
+	while(1) {
+		if(++loopexit > IPC_LOOP_MAX) {
+			log_err("fr_poll_for_reload_start: recv loops %s",
+				sock_strerror(errno));
+			return;
+		}
+		ret = recv(fr->commpair[0], ((char*)&cmd)+bcount,
+			sizeof(cmd)-bcount, 0);
+		if(ret == -1) {
+			if(
+#ifndef USE_WINSOCK
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+#else
+				WSAGetLastError() == WSAEINTR ||
+				WSAGetLastError() == WSAEINPROGRESS ||
+				WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+				)
+				continue; /* Try again. */
+			log_err("fr_poll_for_reload_start: recv: %s",
+				sock_strerror(errno));
+			return;
+		} else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+			bcount += ret;
+			if((size_t)bcount < sizeof(cmd))
+				continue;
+		}
+		break;
+	}
+	if(cmd != fast_reload_notification_reload_start) {
+		verbose(VERB_ALGO, "fast reload wait for ack: "
+			"wrong notification %d", (int)cmd);
+	}
+}
+
+/** Pick up the worker mesh changes, after fast reload. */
+static void
+fr_worker_pickup_mesh(struct worker* worker)
+{
+	struct mesh_area* mesh = worker->env.mesh;
+	struct config_file* cfg = worker->env.cfg;
+	mesh->use_response_ip = worker->daemon->use_response_ip;
+	mesh->use_rpz = worker->daemon->use_rpz;
+	mesh->max_reply_states = cfg->num_queries_per_thread;
+	mesh->max_forever_states = (mesh->max_reply_states+1)/2;
+#ifndef S_SPLINT_S
+	mesh->jostle_max.tv_sec = (time_t)(cfg->jostle_time / 1000);
+	mesh->jostle_max.tv_usec = (time_t)((cfg->jostle_time % 1000)*1000);
+#endif
+}
+
+/**
+ * Remove the old tcl_addr entries from the open connections.
+ * They are only incremented when an accept is performed on a tcp comm point.
+ * @param front: listening comm ports of the worker.
+ */
+static void
+tcl_remove_old(struct listen_dnsport* front)
+{
+	struct listen_list* l;
+	l = front->cps;
+	while(l) {
+		if(l->com->type == comm_tcp_accept) {
+			int i;
+			for(i=0; i<l->com->max_tcp_count; i++) {
+				if(l->com->tcp_handlers[i]->tcl_addr) {
+					/* Because the increment of the
+					 * connection limit was in the old
+					 * tcl list, the new list does not
+					 * need a decrement. With NULL it is
+					 * not decremented when the connection
+					 * is done, and also there is no
+					 * reference to the old connection
+					 * limit structure. */
+					l->com->tcp_handlers[i]->tcl_addr =
+						NULL;
+				}
+			}
+		}
+		l = l->next;
+	}
+}
+
+/** Stop zonemd lookup */
+static void
+auth_zone_zonemd_stop_lookup(struct auth_zone* z, struct mesh_area* mesh)
+{
+	struct query_info qinfo;
+	uint16_t qflags = BIT_RD;
+	qinfo.qname_len = z->namelen;
+	qinfo.qname = z->name;
+	qinfo.qclass = z->dclass;
+	qinfo.qtype = z->zonemd_callback_qtype;
+	qinfo.local_alias = NULL;
+
+	mesh_remove_callback(mesh, &qinfo, qflags,
+		&auth_zonemd_dnskey_lookup_callback, z);
+}
+
+/** Pick up the auth zone locks. */
+static void
+fr_pickup_auth_locks(struct worker* worker, struct auth_zone* namez,
+	struct auth_zone* old_z, struct auth_zone* new_z,
+	struct auth_xfer** xfr, struct auth_xfer** loadxfr)
+{
+	uint8_t nm[LDNS_MAX_DOMAINLEN+1];
+	size_t nmlen;
+	uint16_t dclass;
+
+	log_assert(namez->namelen <= sizeof(nm));
+	lock_rw_rdlock(&namez->lock);
+	nmlen = namez->namelen;
+	dclass = namez->dclass;
+	memmove(nm, namez->name, nmlen);
+	lock_rw_unlock(&namez->lock);
+
+	lock_rw_wrlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+	lock_rw_wrlock(&worker->env.auth_zones->lock);
+	if(new_z) {
+		lock_rw_wrlock(&new_z->lock);
+	}
+	if(old_z) {
+		lock_rw_wrlock(&old_z->lock);
+	}
+	if(loadxfr)
+		*loadxfr = auth_xfer_find(worker->daemon->fast_reload_thread->
+			old_auth_zones, nm, nmlen, dclass);
+	if(xfr)
+		*xfr = auth_xfer_find(worker->env.auth_zones, nm, nmlen,
+			dclass);
+	if(loadxfr && *loadxfr) {
+		lock_basic_lock(&(*loadxfr)->lock);
+	}
+	if(xfr && *xfr) {
+		lock_basic_lock(&(*xfr)->lock);
+	}
+}
+
+/** Fast reload, worker picks up deleted auth zone */
+static void
+fr_worker_auth_del(struct worker* worker, struct fast_reload_auth_change* item,
+	int for_change)
+{
+	int released = 0; /* Did this routine release callbacks. */
+	struct auth_xfer* xfr = NULL;
+
+	lock_rw_wrlock(&item->old_z->lock);
+	if(item->old_z->zonemd_callback_env &&
+	   item->old_z->zonemd_callback_env->worker == worker){
+		/* This worker was performing a zonemd lookup,
+		 * stop the lookup and remove that entry. */
+		auth_zone_zonemd_stop_lookup(item->old_z, worker->env.mesh);
+		item->old_z->zonemd_callback_env = NULL;
+	}
+	lock_rw_unlock(&item->old_z->lock);
+
+	fr_pickup_auth_locks(worker, item->old_z, item->old_z, NULL, &xfr,
+		NULL);
+	lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+	lock_rw_unlock(&worker->env.auth_zones->lock);
+	lock_rw_unlock(&item->old_z->lock);
+	if(xfr) {
+		/* Release callbacks on the xfr, if this worker holds them. */
+		if(xfr->task_nextprobe->worker == worker ||
+			xfr->task_probe->worker == worker ||
+			xfr->task_transfer->worker == worker) {
+			released = 1;
+			xfr_disown_tasks(xfr, worker);
+		}
+		lock_basic_unlock(&xfr->lock);
+	}
+
+	if(!for_change && (released || worker->thread_num == 0)) {
+		/* See if the xfr item can be deleted. */
+		xfr = NULL;
+		fr_pickup_auth_locks(worker, item->old_z, item->old_z, NULL,
+			&xfr, NULL);
+		lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+		lock_rw_unlock(&item->old_z->lock);
+		if(xfr && xfr->task_nextprobe->worker == NULL &&
+			xfr->task_probe->worker == NULL &&
+			xfr->task_transfer->worker == NULL) {
+			(void)rbtree_delete(&worker->env.auth_zones->xtree,
+				&xfr->node);
+			lock_rw_unlock(&worker->env.auth_zones->lock);
+			lock_basic_unlock(&xfr->lock);
+			auth_xfer_delete(xfr);
+		} else {
+			lock_rw_unlock(&worker->env.auth_zones->lock);
+			if(xfr) {
+				lock_basic_unlock(&xfr->lock);
+			}
+		}
+	}
+}
+
+/** Fast reload, auth xfer config is picked up */
+static void
+auth_xfr_pickup_config(struct auth_xfer* loadxfr, struct auth_xfer* xfr)
+{
+	struct auth_master *probe_masters, *transfer_masters;
+	log_assert(loadxfr->namelen == xfr->namelen);
+	log_assert(loadxfr->namelabs == xfr->namelabs);
+	log_assert(loadxfr->dclass == xfr->dclass);
+
+	/* The lists can be swapped in, the other xfr struct will be deleted
+	 * afterwards. */
+	probe_masters = xfr->task_probe->masters;
+	transfer_masters = xfr->task_transfer->masters;
+	xfr->task_probe->masters = loadxfr->task_probe->masters;
+	xfr->task_transfer->masters = loadxfr->task_transfer->masters;
+	loadxfr->task_probe->masters = probe_masters;
+	loadxfr->task_transfer->masters = transfer_masters;
+}
+
+/** Fast reload, worker picks up added auth zone */
+static void
+fr_worker_auth_add(struct worker* worker, struct fast_reload_auth_change* item,
+	int for_change)
+{
+	struct auth_xfer* xfr = NULL, *loadxfr = NULL;
+
+	/* Start zone transfers and lookups. */
+	fr_pickup_auth_locks(worker, item->new_z, NULL, item->new_z, &xfr,
+		&loadxfr);
+	if(xfr == NULL && item->new_z->zone_is_slave) {
+		/* The xfr item needs to be created. The auth zones lock
+		 * is held to make this possible. */
+		xfr = auth_xfer_create(worker->env.auth_zones, item->new_z);
+		auth_xfr_pickup_config(loadxfr, xfr);
+		/* Serial information is copied into the xfr struct. */
+		if(!xfr_find_soa(item->new_z, xfr)) {
+			xfr->serial = 0;
+		}
+	} else if(for_change && xfr) {
+		if(!xfr_find_soa(item->new_z, xfr)) {
+			xfr->serial = 0;
+		}
+	}
+	lock_rw_unlock(&item->new_z->lock);
+	lock_rw_unlock(&worker->env.auth_zones->lock);
+	lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+	if(loadxfr) {
+		lock_basic_unlock(&loadxfr->lock);
+	}
+	if(xfr) {
+		auth_xfer_pickup_initial_zone(xfr, &worker->env);
+		if(for_change) {
+			xfr->task_probe->only_lookup = 0;
+		}
+		lock_basic_unlock(&xfr->lock);
+	}
+
+	/* Perform ZONEMD verification lookups. */
+	lock_rw_wrlock(&item->new_z->lock);
+	/* holding only the new_z lock */
+	auth_zone_verify_zonemd(item->new_z, &worker->env,
+		&worker->env.mesh->mods, NULL, 0, 1);
+	lock_rw_unlock(&item->new_z->lock);
+}
+
+/** Fast reload, worker picks up changed auth zone */
+static void
+fr_worker_auth_cha(struct worker* worker, struct fast_reload_auth_change* item)
+{
+	int todelete = 0;
+	struct auth_xfer* loadxfr = NULL, *xfr = NULL;
+	/* Since the zone has been changed, by rereading it from zone file,
+	 * existing transfers and probes are likely for the old version.
+	 * Stop them, and start new ones if needed. */
+	fr_worker_auth_del(worker, item, 1);
+
+	if(worker->thread_num != 0)
+		return;
+
+	/* The old callbacks are stopped, tasks have been disowned. The
+	 * new config contents can be picked up. SOA information is picked
+	 * up in the auth_add routine, as it has the new_z ready. */
+
+	fr_pickup_auth_locks(worker, item->new_z, item->old_z, item->new_z,
+		&xfr, &loadxfr);
+
+	/* The xfr is not there any more if the zone is not set to have
+	 * zone transfers. Or the xfr needs to be created if it is set to
+	 * have zone transfers. */
+	if(loadxfr && xfr) {
+		/* Copy the config from loadxfr to the xfr in current use. */
+		auth_xfr_pickup_config(loadxfr, xfr);
+	} else if(!loadxfr && xfr) {
+		/* Delete the xfr. */
+		(void)rbtree_delete(&worker->env.auth_zones->xtree,
+			&xfr->node);
+		todelete = 1;
+		item->new_z->zone_is_slave = 0;
+	} else if(loadxfr && !xfr) {
+		/* Create the xfr. */
+		xfr = auth_xfer_create(worker->env.auth_zones, item->new_z);
+		auth_xfr_pickup_config(loadxfr, xfr);
+		item->new_z->zone_is_slave = 1;
+	}
+	lock_rw_unlock(&item->new_z->lock);
+	lock_rw_unlock(&item->old_z->lock);
+	lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+	lock_rw_unlock(&worker->env.auth_zones->lock);
+	if(loadxfr) {
+		lock_basic_unlock(&loadxfr->lock);
+	}
+	if(xfr) {
+		lock_basic_unlock(&xfr->lock);
+	}
+	if(todelete) {
+		auth_xfer_delete(xfr);
+	}
+
+	fr_worker_auth_add(worker, item, 1);
+}
+
+/** Fast reload, the worker picks up changes in auth zones. */
+static void
+fr_worker_pickup_auth_changes(struct worker* worker,
+	struct fast_reload_auth_change* auth_zone_change_list)
+{
+	struct fast_reload_auth_change* item;
+	for(item = auth_zone_change_list; item; item = item->next) {
+		if(item->is_deleted) {
+			fr_worker_auth_del(worker, item, 0);
+		}
+		if(item->is_added) {
+			if(worker->thread_num == 0) {
+				fr_worker_auth_add(worker, item, 0);
+			}
+		}
+		if(item->is_changed) {
+			fr_worker_auth_cha(worker, item);
+		}
+	}
+}
+
+/** Fast reload, the worker picks up changes in outside_network. */
+static void
+fr_worker_pickup_outside_network(struct worker* worker)
+{
+	struct outside_network* outnet = worker->back;
+	struct config_file* cfg = worker->env.cfg;
+	outnet->use_caps_for_id = cfg->use_caps_bits_for_id;
+	outnet->unwanted_threshold = cfg->unwanted_threshold;
+	outnet->tls_use_sni = cfg->tls_use_sni;
+	outnet->tcp_mss = cfg->outgoing_tcp_mss;
+	outnet->ip_dscp = cfg->ip_dscp;
+	outnet->max_reuse_tcp_queries = cfg->max_reuse_tcp_queries;
+	outnet->tcp_reuse_timeout = cfg->tcp_reuse_timeout;
+	outnet->tcp_auth_query_timeout = cfg->tcp_auth_query_timeout;
+	outnet->delayclose = cfg->delay_close;
+	if(outnet->delayclose) {
+#ifndef S_SPLINT_S
+		outnet->delay_tv.tv_sec = cfg->delay_close/1000;
+		outnet->delay_tv.tv_usec = (cfg->delay_close%1000)*1000;
+#endif
+	}
+}
+
+void
+fast_reload_worker_pickup_changes(struct worker* worker)
+{
+	/* The pickup of changes is called when the fast reload has
+	 * a syncronized moment, and all the threads are paused and the
+	 * reload has been applied. Then the worker can pick up the new
+	 * changes and store them in worker-specific structs.
+	 * The pickup is also called when there is no pause, and then
+	 * it is called after the reload has completed, and the worker
+	 * get a signal to release old information, it can then pick
+	 * up the new information. But in the mean time, the reload has
+	 * swapped in trees, and the worker has been running with the
+	 * older information for some time. */
+	fr_worker_pickup_mesh(worker);
+
+	/* If the tcp connection limit has changed, the open connections
+	 * need to remove their reference for the old tcp limits counters. */
+	if(worker->daemon->fast_reload_tcl_has_changes)
+		tcl_remove_old(worker->front);
+
+	/* If there are zonemd lookups, but the zone was deleted, the
+	 * lookups should be cancelled. */
+	fr_worker_pickup_auth_changes(worker,
+		worker->daemon->fast_reload_thread->auth_zone_change_list);
+#ifdef USE_CACHEDB
+	worker->env.cachedb_enabled = worker->daemon->env->cachedb_enabled;
+#endif
+	fr_worker_pickup_outside_network(worker);
+}
+
+/** fast reload thread, handle reload_stop notification, send reload stop
+ * to other threads over IPC and collect their ack. When that is done,
+ * ack to the caller, the fast reload thread, and wait for it to send start. */
+static void
+fr_main_perform_reload_stop(struct fast_reload_thread* fr)
+{
+	struct daemon* daemon = fr->worker->daemon;
+	int i;
+
+	/* Send reload_stop to other threads. */
+	for(i=0; i<daemon->num; i++) {
+		if(i == fr->worker->thread_num)
+			continue; /* Do not send to ourselves. */
+		worker_send_cmd(daemon->workers[i], worker_cmd_reload_stop);
+	}
+
+	/* Wait for the other threads to ack. */
+	fr_read_ack_from_workers(fr);
+
+	/* Send ack to fast reload thread. */
+	fr_send_cmd_to(fr, fast_reload_notification_reload_ack, 0, 1);
+
+	/* Wait for reload_start from fast reload thread to resume. */
+	fr_poll_for_reload_start(fr);
+
+	/* Send reload_start to other threads */
+	for(i=0; i<daemon->num; i++) {
+		if(i == fr->worker->thread_num)
+			continue; /* Do not send to ourselves. */
+		worker_send_cmd(daemon->workers[i], worker_cmd_reload_start);
+	}
+
+	/* Pick up changes for this worker. */
+	if(fr->worker->daemon->fast_reload_drop_mesh) {
+		verbose(VERB_ALGO, "worker: drop mesh queries after reload");
+		mesh_delete_all(fr->worker->env.mesh);
+	}
+	fast_reload_worker_pickup_changes(fr->worker);
+
+	/* Wait for the other threads to ack. */
+	fr_read_ack_from_workers(fr);
+
+	/* Send ack to fast reload thread. */
+	fr_send_cmd_to(fr, fast_reload_notification_reload_ack, 0, 1);
+
+	verbose(VERB_ALGO, "worker resume after reload");
+}
+
+/** Fast reload, the main thread performs the nopause poll. It polls every
+ * other worker thread briefly over the command pipe ipc. The command takes
+ * no time for the worker, it can return immediately. After that it sends
+ * an acknowledgement to the fastreload thread. */
+static void
+fr_main_perform_reload_nopause_poll(struct fast_reload_thread* fr)
+{
+	struct daemon* daemon = fr->worker->daemon;
+	int i;
+
+	/* Send the reload_poll to other threads. They can respond
+	 * one at a time. */
+	for(i=0; i<daemon->num; i++) {
+		if(i == fr->worker->thread_num)
+			continue; /* Do not send to ourselves. */
+		worker_send_cmd(daemon->workers[i], worker_cmd_reload_poll);
+	}
+
+	/* Wait for the other threads to ack. */
+	fr_read_ack_from_workers(fr);
+	fast_reload_worker_pickup_changes(fr->worker);
+
+	/* Send ack to fast reload thread. */
+	fr_send_cmd_to(fr, fast_reload_notification_reload_ack, 0, 1);
+}
+
+/** Fast reload, perform the command received from the fast reload thread */
+static void
+fr_main_perform_cmd(struct fast_reload_thread* fr,
+	enum fast_reload_notification status)
+{
+	verbose(VERB_ALGO, "main perform fast reload status: %s",
+		fr_notification_to_string(status));
+	if(status == fast_reload_notification_printout) {
+		fr_main_perform_printout(fr);
+	} else if(status == fast_reload_notification_done ||
+		status == fast_reload_notification_done_error ||
+		status == fast_reload_notification_exited) {
+		fr_main_perform_done(fr);
+	} else if(status == fast_reload_notification_reload_stop) {
+		fr_main_perform_reload_stop(fr);
+	} else if(status == fast_reload_notification_reload_nopause_poll) {
+		fr_main_perform_reload_nopause_poll(fr);
+	} else {
+		log_err("main received unknown status from fast reload: %d %s",
+			(int)status, fr_notification_to_string(status));
+	}
+}
+
+/** Fast reload, handle command from fast reload to the main thread. */
+static void
+fr_main_handle_cmd(struct fast_reload_thread* fr)
+{
+	enum fast_reload_notification status;
+	ssize_t ret;
+	/* keep static analyzer happy; recv(-1,..) */
+	log_assert(fr->commpair[0] >= 0);
+	ret = recv(fr->commpair[0],
+		((char*)&fr->service_read_cmd)+fr->service_read_cmd_count,
+		sizeof(fr->service_read_cmd)-fr->service_read_cmd_count, 0);
+	if(ret == -1) {
+		if(
+#ifndef USE_WINSOCK
+			errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+			|| errno == EWOULDBLOCK
+#  endif
+#else
+			WSAGetLastError() == WSAEINTR ||
+			WSAGetLastError() == WSAEINPROGRESS
+#endif
+			)
+			return; /* Continue later. */
+#ifdef USE_WINSOCK
+		if(WSAGetLastError() == WSAEWOULDBLOCK) {
+			ub_winsock_tcp_wouldblock(fr->service_event,
+				UB_EV_READ);
+			return; /* Continue later. */
+		}
+#endif
+		log_err("read cmd from fast reload thread, recv: %s",
+			sock_strerror(errno));
+		return;
+	} else if(ret == 0) {
+		verbose(VERB_ALGO, "closed connection from fast reload thread");
+		fr->service_read_cmd_count = 0;
+		/* handle this like an error */
+		fr->service_read_cmd = fast_reload_notification_done_error;
+	} else if(ret + (ssize_t)fr->service_read_cmd_count <
+		(ssize_t)sizeof(fr->service_read_cmd)) {
+		fr->service_read_cmd_count += ret;
+		/* Continue later. */
+		return;
+	}
+	status = fr->service_read_cmd;
+	fr->service_read_cmd = 0;
+	fr->service_read_cmd_count = 0;
+	fr_main_perform_cmd(fr, status);
+}
+
+/** Fast reload, poll for and handle cmd from fast reload thread. */
+static void
+fr_check_cmd_from_thread(struct fast_reload_thread* fr)
+{
+	int inevent = 0;
+	struct worker* worker = fr->worker;
+	/* Stop in case the thread has exited, or there is no read event. */
+	while(worker->daemon->fast_reload_thread) {
+		if(!sock_poll_timeout(fr->commpair[0], 0, 1, 0, &inevent)) {
+			log_err("check for cmd from fast reload thread: "
+				"poll failed");
+#ifdef USE_WINSOCK
+			if(worker->daemon->fast_reload_thread)
+				ub_winsock_tcp_wouldblock(worker->daemon->
+					fast_reload_thread->service_event,
+					UB_EV_READ);
+#endif
+			return;
+		}
+		if(!inevent) {
+#ifdef USE_WINSOCK
+			if(worker->daemon->fast_reload_thread)
+				ub_winsock_tcp_wouldblock(worker->daemon->
+					fast_reload_thread->service_event,
+					UB_EV_READ);
+#endif
+			return;
+		}
+		fr_main_handle_cmd(fr);
+	}
+}
+
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits),
+	void* arg)
+{
+	struct fast_reload_thread* fast_reload_thread =
+		(struct fast_reload_thread*)arg;
+	struct worker* worker = fast_reload_thread->worker;
+
+	/* Read and handle the command */
+	fr_main_handle_cmd(fast_reload_thread);
+	if(worker->daemon->fast_reload_thread != NULL) {
+		/* If not exited, see if there are more pending statuses
+		 * from the fast reload thread. */
+		fr_check_cmd_from_thread(fast_reload_thread);
+	}
+}
+
+#ifdef HAVE_SSL
+/** fast reload, send client item over SSL. Returns number of bytes
+ * printed, 0 on wait later, or -1 on failure. */
+static int
+fr_client_send_item_ssl(struct fast_reload_printq* printq)
+{
+	int r;
+	ERR_clear_error();
+	r = SSL_write(printq->remote.ssl,
+		printq->client_item+printq->client_byte_count,
+		printq->client_len - printq->client_byte_count);
+	if(r <= 0) {
+		int want = SSL_get_error(printq->remote.ssl, r);
+		if(want == SSL_ERROR_ZERO_RETURN) {
+			log_err("fast_reload print to remote client: "
+				"SSL_write says connection closed.");
+			return -1;
+		} else if(want == SSL_ERROR_WANT_READ) {
+			/* wait for read condition */
+			printq->client_cp->ssl_shake_state = comm_ssl_shake_hs_read;
+			comm_point_listen_for_rw(printq->client_cp, 1, 0);
+			return 0;
+		} else if(want == SSL_ERROR_WANT_WRITE) {
+#ifdef USE_WINSOCK
+			ub_winsock_tcp_wouldblock(comm_point_internal(printq->client_cp), UB_EV_WRITE);
+#endif
+			return 0; /* write more later */
+		} else if(want == SSL_ERROR_SYSCALL) {
+#ifdef EPIPE
+			if(errno == EPIPE && verbosity < 2) {
+				/* silence 'broken pipe' */
+				return -1;
+			}
+#endif
+			if(errno != 0)
+				log_err("fast_reload print to remote client: "
+					"SSL_write syscall: %s",
+					sock_strerror(errno));
+			return -1;
+		}
+		log_crypto_err_io("fast_reload print to remote client: "
+			"could not SSL_write", want);
+		return -1;
+	}
+	return r;
+}
+#endif /* HAVE_SSL */
+
+/** fast reload, send client item for fd, returns bytes sent, or 0 for wait
+ * later, or -1 on failure. */
+static int
+fr_client_send_item_fd(struct fast_reload_printq* printq)
+{
+	int r;
+	r = (int)send(printq->remote.fd,
+		printq->client_item+printq->client_byte_count,
+		printq->client_len - printq->client_byte_count, 0);
+	if(r == -1) {
+		if(
+#ifndef USE_WINSOCK
+			errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+			|| errno == EWOULDBLOCK
+#  endif
+#else
+			WSAGetLastError() == WSAEINTR ||
+			WSAGetLastError() == WSAEINPROGRESS ||
+			WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+			) {
+#ifdef USE_WINSOCK
+			ub_winsock_tcp_wouldblock(comm_point_internal(printq->client_cp), UB_EV_WRITE);
+#endif
+			return 0; /* Try again. */
+		}
+		log_err("fast_reload print to remote client: send failed: %s",
+			sock_strerror(errno));
+		return -1;
+	}
+	return r;
+}
+
+/** fast reload, send current client item. false on failure or wait later. */
+static int
+fr_client_send_item(struct fast_reload_printq* printq)
+{
+	int r;
+#ifdef HAVE_SSL
+	if(printq->remote.ssl) {
+		r = fr_client_send_item_ssl(printq);
+	} else {
+#endif
+		r = fr_client_send_item_fd(printq);
+#ifdef HAVE_SSL
+	}
+#endif
+	if(r == 0) {
+		/* Wait for later. */
+		return 0;
+	} else if(r == -1) {
+		/* It failed, close comm point and stop sending. */
+		fr_printq_remove(printq);
+		return 0;
+	}
+	printq->client_byte_count += r;
+	if(printq->client_byte_count < printq->client_len)
+		return 0; /* Print more later. */
+	return 1;
+}
+
+/** fast reload, pick up the next item to print */
+static void
+fr_client_pickup_next_item(struct fast_reload_printq* printq)
+{
+	struct config_strlist* item;
+	/* Pop first off the list. */
+	if(!printq->to_print->first) {
+		printq->client_item = NULL;
+		printq->client_len = 0;
+		printq->client_byte_count = 0;
+		return;
+	}
+	item = printq->to_print->first;
+	if(item->next) {
+		printq->to_print->first = item->next;
+	} else {
+		printq->to_print->first = NULL;
+		printq->to_print->last = NULL;
+	}
+	item->next = NULL;
+	printq->client_len = 0;
+	printq->client_byte_count = 0;
+	printq->client_item = item->str;
+	item->str = NULL;
+	free(item);
+	/* The len is the number of bytes to print out, and thus excludes
+	 * the terminator zero. */
+	if(printq->client_item)
+		printq->client_len = (int)strlen(printq->client_item);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c), void* arg,
+	int err, struct comm_reply* ATTR_UNUSED(rep))
+{
+	struct fast_reload_printq* printq = (struct fast_reload_printq*)arg;
+	if(!printq->client_cp) {
+		fr_printq_remove(printq);
+		return 0; /* the output is closed and deleted */
+	}
+	if(err != NETEVENT_NOERROR) {
+		verbose(VERB_ALGO, "fast reload client: error, close it");
+		fr_printq_remove(printq);
+		return 0;
+	}
+#ifdef HAVE_SSL
+	if(printq->client_cp->ssl_shake_state == comm_ssl_shake_hs_read) {
+		/* read condition satisfied back to writing */
+		comm_point_listen_for_rw(printq->client_cp, 0, 1);
+		printq->client_cp->ssl_shake_state = comm_ssl_shake_none;
+	}
+#endif /* HAVE_SSL */
+
+	/* Pickup an item if there are none */
+	if(!printq->client_item) {
+		fr_client_pickup_next_item(printq);
+	}
+	if(!printq->client_item) {
+		if(printq->in_list) {
+			/* Nothing more to print, it can be removed. */
+			fr_printq_remove(printq);
+			return 0;
+		}
+		/* Done with printing for now. */
+		comm_point_stop_listening(printq->client_cp);
+		return 0;
+	}
+
+	/* Try to print out a number of items, if they can print in full. */
+	while(printq->client_item) {
+		/* Send current item, if any. */
+		if(printq->client_item && printq->client_len != 0 &&
+			printq->client_byte_count < printq->client_len) {
+			if(!fr_client_send_item(printq))
+				return 0;
+		}
+
+		/* The current item is done. */
+		if(printq->client_item) {
+			free(printq->client_item);
+			printq->client_item = NULL;
+			printq->client_len = 0;
+			printq->client_byte_count = 0;
+		}
+		if(!printq->to_print->first) {
+			if(printq->in_list) {
+				/* Nothing more to print, it can be removed. */
+				fr_printq_remove(printq);
+				return 0;
+			}
+			/* Done with printing for now. */
+			comm_point_stop_listening(printq->client_cp);
+			return 0;
+		}
+		fr_client_pickup_next_item(printq);
+	}
+
+	return 0;
+}
+
+#ifndef THREADS_DISABLED
+/** fast reload printq create */
+static struct fast_reload_printq*
+fr_printq_create(struct comm_point* c, struct worker* worker)
+{
+	struct fast_reload_printq* printq = calloc(1, sizeof(*printq));
+	if(!printq)
+		return NULL;
+	printq->to_print = calloc(1, sizeof(*printq->to_print));
+	if(!printq->to_print) {
+		free(printq);
+		return NULL;
+	}
+	printq->worker = worker;
+	printq->client_cp = c;
+	printq->client_cp->callback = fast_reload_client_callback;
+	printq->client_cp->cb_arg = printq;
+	return printq;
+}
+#endif /* !THREADS_DISABLED */
+
+/** fast reload printq delete */
+static void
+fr_printq_delete(struct fast_reload_printq* printq)
+{
+	if(!printq)
+		return;
+#ifdef HAVE_SSL
+	if(printq->remote.ssl) {
+		SSL_shutdown(printq->remote.ssl);
+		SSL_free(printq->remote.ssl);
+	}
+#endif
+	comm_point_delete(printq->client_cp);
+	if(printq->to_print) {
+		config_delstrlist(printq->to_print->first);
+		free(printq->to_print);
+	}
+	free(printq);
+}
+
+/** fast reload printq, returns true if the list is empty and no item */
+static int
+fr_printq_empty(struct fast_reload_printq* printq)
+{
+	if(printq->to_print->first == NULL && printq->client_item == NULL)
+		return 1;
+	return 0;
+}
+
+/** fast reload printq, insert onto list */
+static void
+fr_printq_list_insert(struct fast_reload_printq* printq, struct daemon* daemon)
+{
+	if(printq->in_list)
+		return;
+	printq->next = daemon->fast_reload_printq_list;
+	if(printq->next)
+		printq->next->prev = printq;
+	printq->prev = NULL;
+	printq->in_list = 1;
+	daemon->fast_reload_printq_list = printq;
+}
+
+/** fast reload printq delete list */
+void
+fast_reload_printq_list_delete(struct fast_reload_printq* list)
+{
+	struct fast_reload_printq* printq = list, *next;
+	while(printq) {
+		next = printq->next;
+		fr_printq_delete(printq);
+		printq = next;
+	}
+}
+
+/** fast reload printq remove the item from the printq list */
+static void
+fr_printq_list_remove(struct fast_reload_printq* printq)
+{
+	struct daemon* daemon = printq->worker->daemon;
+	if(printq->prev == NULL)
+		daemon->fast_reload_printq_list = printq->next;
+	else	printq->prev->next = printq->next;
+	if(printq->next)
+		printq->next->prev = printq->prev;
+	printq->in_list = 0;
+}
+
+/** fast reload printq, remove the printq when no longer needed,
+ * like the stream is closed. */
+static void
+fr_printq_remove(struct fast_reload_printq* printq)
+{
+	if(!printq)
+		return;
+	if(printq->worker->daemon->fast_reload_thread &&
+		printq->worker->daemon->fast_reload_thread->printq == printq)
+		printq->worker->daemon->fast_reload_thread->printq = NULL;
+	if(printq->in_list)
+		fr_printq_list_remove(printq);
+	fr_printq_delete(printq);
+}
+
+/** fast reload thread, send stop command to the thread, from the main thread.
+ */
+static void
+fr_send_stop(struct fast_reload_thread* fr)
+{
+	fr_send_cmd_to(fr, fast_reload_notification_exit, 1, 0);
+}
+
+void
+fast_reload_thread_start(RES* ssl, struct worker* worker, struct rc_state* s,
+	int fr_verb, int fr_nopause, int fr_drop_mesh)
+{
+	if(worker->daemon->fast_reload_thread) {
+		log_err("fast reload thread already running");
+		return;
+	}
+	if(!fast_reload_thread_setup(worker, fr_verb, fr_nopause,
+		fr_drop_mesh)) {
+		if(!ssl_printf(ssl, "error could not setup thread\n"))
+			return;
+		return;
+	}
+	worker->daemon->fast_reload_thread->started = 1;
+
+#ifndef THREADS_DISABLED
+	/* Setup command listener in remote servicing thread */
+	/* The listener has to be nonblocking, so the the remote servicing
+	 * thread can continue to service DNS queries, the fast reload
+	 * thread is going to read the config from disk and apply it. */
+	/* The commpair[1] element can stay blocking, it is used by the
+	 * fast reload thread to communicate back. The thread needs to wait
+	 * at these times, when it has to check briefly it can use poll. */
+	fd_set_nonblock(worker->daemon->fast_reload_thread->commpair[0]);
+	worker->daemon->fast_reload_thread->service_event = ub_event_new(
+		comm_base_internal(worker->base),
+		worker->daemon->fast_reload_thread->commpair[0],
+		UB_EV_READ | UB_EV_PERSIST, fast_reload_service_cb,
+		worker->daemon->fast_reload_thread);
+	if(!worker->daemon->fast_reload_thread->service_event) {
+		fast_reload_thread_desetup(worker->daemon->fast_reload_thread);
+		if(!ssl_printf(ssl, "error out of memory\n"))
+			return;
+		return;
+	}
+	if(ub_event_add(worker->daemon->fast_reload_thread->service_event,
+		NULL) != 0) {
+		fast_reload_thread_desetup(worker->daemon->fast_reload_thread);
+		if(!ssl_printf(ssl, "error out of memory adding service event\n"))
+			return;
+		return;
+	}
+	worker->daemon->fast_reload_thread->service_event_is_added = 1;
+
+	/* Setup the comm point to the remote control client as an event
+	 * on the remote servicing thread, which it already is.
+	 * It needs a new callback to service it. */
+	log_assert(s);
+	state_list_remove_elem(&s->rc->busy_list, s->c);
+	s->rc->active --;
+	/* Set the comm point file descriptor to nonblocking. So that
+	 * printout to the remote control client does not block the
+	 * server thread from servicing DNS queries. */
+	fd_set_nonblock(s->c->fd);
+	worker->daemon->fast_reload_thread->printq = fr_printq_create(s->c,
+		worker);
+	if(!worker->daemon->fast_reload_thread->printq) {
+		fast_reload_thread_desetup(worker->daemon->fast_reload_thread);
+		if(!ssl_printf(ssl, "error out of memory create printq\n"))
+			return;
+		return;
+	}
+	worker->daemon->fast_reload_thread->printq->remote = *ssl;
+	s->rc = NULL; /* move away the rc state */
+	/* Nothing to print right now, so no need to have it active. */
+	comm_point_stop_listening(worker->daemon->fast_reload_thread->printq->client_cp);
+
+	/* Start fast reload thread */
+	ub_thread_create(&worker->daemon->fast_reload_thread->tid,
+		fast_reload_thread_main, worker->daemon->fast_reload_thread);
+#else
+	(void)s;
+#endif
+}
+
+void
+fast_reload_thread_stop(struct fast_reload_thread* fast_reload_thread)
+{
+	struct worker* worker = fast_reload_thread->worker;
+	if(!fast_reload_thread)
+		return;
+	fr_send_stop(fast_reload_thread);
+	if(worker->daemon->fast_reload_thread != NULL) {
+		/* If it did not exit yet, join with the thread now. It is
+		 * going to exit because the exit command is sent to it. */
+		fr_main_perform_done(fast_reload_thread);
+	}
 }
Index: daemon/remote.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/remote.h,v
diff -u -p -r1.9 remote.h
--- daemon/remote.h	5 Sep 2023 11:12:10 -0000	1.9
+++ daemon/remote.h	30 Aug 2025 11:52:23 -0000
@@ -48,6 +48,7 @@
 #ifdef HAVE_OPENSSL_SSL_H
 #include <openssl/ssl.h>
 #endif
+#include "util/locks.h"
 struct config_file;
 struct listen_list;
 struct listen_port;
@@ -55,6 +56,7 @@ struct worker;
 struct comm_reply;
 struct comm_point;
 struct daemon_remote;
+struct config_strlist_head;
 
 /** number of milliseconds timeout on incoming remote control handshake */
 #define REMOTE_CONTROL_TCP_TIMEOUT 120000
@@ -119,6 +121,137 @@ struct remote_stream {
 typedef struct remote_stream RES;
 
 /**
+ * Notification status. This is exchanged between the fast reload thread
+ * and the server thread, over the commpair sockets.
+ */
+enum fast_reload_notification {
+	/** nothing, not used */
+	fast_reload_notification_none = 0,
+	/** the fast reload thread is done */
+	fast_reload_notification_done = 1,
+	/** the fast reload thread is done but with an error, it failed */
+	fast_reload_notification_done_error = 2,
+	/** the fast reload thread is told to exit by the server thread.
+	 * Sent on server quit while the reload is running. */
+	fast_reload_notification_exit = 3,
+	/** the fast reload thread has exited, after being told to exit */
+	fast_reload_notification_exited = 4,
+	/** the fast reload thread has information to print out */
+	fast_reload_notification_printout = 5,
+	/** stop as part of the reload the thread and other threads */
+	fast_reload_notification_reload_stop = 6,
+	/** ack the stop as part of the reload, and also ack start */
+	fast_reload_notification_reload_ack = 7,
+	/** resume from stop as part of the reload */
+	fast_reload_notification_reload_start = 8,
+	/** the fast reload thread wants the mainthread to poll workers,
+	 * after the reload, sent when nopause is used */
+	fast_reload_notification_reload_nopause_poll = 9
+};
+
+/**
+ * Fast reload printout queue. Contains a list of strings, that need to be
+ * printed over the file descriptor.
+ */
+struct fast_reload_printq {
+	/** if this item is in a list, the previous and next */
+	struct fast_reload_printq *prev, *next;
+	/** if this item is in a list, it is true. */
+	int in_list;
+	/** list of strings to printout */
+	struct config_strlist_head* to_print;
+	/** the current item to print. It is malloced. NULL if none. */
+	char* client_item;
+	/** The length, strlen, of the client_item, that has to be sent. */
+	int client_len;
+	/** The number of bytes sent of client_item. */
+	int client_byte_count;
+	/** the comm point for the client connection, the remote control
+	 * client. */
+	struct comm_point* client_cp;
+	/** the remote control connection to print output to. */
+	struct remote_stream remote;
+	/** the worker that the event is added in */
+	struct worker* worker;
+};
+
+/**
+ * Fast reload auth zone change. Keeps track if an auth zone was removed,
+ * added or changed. This is needed because workers can have events for
+ * dealing with auth zones, like transfers, and those have to be removed
+ * too, not just the auth zone structure from the tree. */
+struct fast_reload_auth_change {
+	/** next in the list of auth zone changes. */
+	struct fast_reload_auth_change* next;
+	/** the zone in the old config */
+	struct auth_zone* old_z;
+	/** the zone in the new config */
+	struct auth_zone* new_z;
+	/** if the zone was deleted */
+	int is_deleted;
+	/** if the zone was added */
+	int is_added;
+	/** if the zone has been changed */
+	int is_changed;
+};
+
+/**
+ * Fast reload thread structure
+ */
+struct fast_reload_thread {
+	/** the thread number for the dtio thread,
+	 * must be first to cast thread arg to int* in checklock code. */
+	int threadnum;
+	/** communication socket pair, that sends commands */
+	int commpair[2];
+	/** thread id, of the io thread */
+	ub_thread_type tid;
+	/** if the io processing has started */
+	int started;
+	/** if the thread has to quit */
+	int need_to_quit;
+	/** verbosity of the fast_reload command, the number of +v options */
+	int fr_verb;
+	/** option to not pause threads during reload */
+	int fr_nopause;
+	/** option to drop mesh queries */
+	int fr_drop_mesh;
+
+	/** the event that listens on the remote service worker to the
+	 * commpair, it receives content from the fast reload thread. */
+	void* service_event;
+	/** if the event that listens on the remote service worker has
+	 * been added to the comm base. */
+	int service_event_is_added;
+	/** the service event can read a cmd, nonblocking, so it can
+	 * save the partial read cmd here */
+	uint32_t service_read_cmd;
+	/** the number of bytes in service_read_cmd */
+	int service_read_cmd_count;
+	/** the worker that the service_event is added in */
+	struct worker* worker;
+
+	/** the printout of output to the remote client. */
+	struct fast_reload_printq *printq;
+
+	/** lock on fr_output, to stop race when both remote control thread
+	 * and fast reload thread use fr_output list. */
+	lock_basic_type fr_output_lock;
+	/** list of strings, that the fast reload thread produces that have
+	 * to be printed. The remote control thread can pick them up with
+	 * the lock. */
+	struct config_strlist_head* fr_output;
+
+	/** communication socket pair, to respond to the reload request */
+	int commreload[2];
+
+	/** the list of auth zone changes. */
+	struct fast_reload_auth_change* auth_zone_change_list;
+	/** the old tree of auth zones, to lookup. */
+	struct auth_zones* old_auth_zones;
+};
+
+/**
  * Create new remote control state for the daemon.
  * @param cfg: config file with key file settings.
  * @return new state, or NULL on failure.
@@ -202,5 +335,39 @@ int ssl_printf(RES* ssl, const char* for
  */
 int ssl_read_line(RES* ssl, char* buf, size_t max);
 #endif /* HAVE_SSL */
+
+/**
+ * Start fast reload thread
+ * @param ssl: the RES connection to print to.
+ * @param worker: the remote servicing worker.
+ * @param s: the rc_state that is servicing the remote control connection to
+ *	the remote control client. It needs to be moved away to stay connected
+ *	while the fast reload is running.
+ * @param fr_verb: verbosity to print output at. 0 is nothing, 1 is some
+ *	and 2 is more detail.
+ * @param fr_nopause: option to not pause threads during reload.
+ * @param fr_drop_mesh: option to drop mesh queries.
+ */
+void fast_reload_thread_start(RES* ssl, struct worker* worker,
+	struct rc_state* s, int fr_verb, int fr_nopause, int fr_drop_mesh);
+
+/**
+ * Stop fast reload thread
+ * @param fast_reload_thread: the thread struct.
+ */
+void fast_reload_thread_stop(struct fast_reload_thread* fast_reload_thread);
+
+/** fast reload thread commands to remote service thread event callback */
+void fast_reload_service_cb(int fd, short bits, void* arg);
+
+/** fast reload callback for the remote control client connection */
+int fast_reload_client_callback(struct comm_point* c, void* arg, int err,
+	struct comm_reply* rep);
+
+/** fast reload printq delete list */
+void fast_reload_printq_list_delete(struct fast_reload_printq* list);
+
+/** Pick up per worker changes after a fast reload. */
+void fast_reload_worker_pickup_changes(struct worker* worker);
 
 #endif /* DAEMON_REMOTE_H */
Index: daemon/stats.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/stats.c,v
diff -u -p -r1.17 stats.c
--- daemon/stats.c	21 Feb 2025 13:20:40 -0000	1.17
+++ daemon/stats.c	30 Aug 2025 11:52:23 -0000
@@ -281,6 +281,12 @@ server_stats_compile(struct worker* work
 		s->svr.rpz_action[i] += (long long)worker->env.mesh->rpz_action[i];
 	timehist_export(worker->env.mesh->histogram, s->svr.hist,
 		NUM_BUCKETS_HIST);
+	s->svr.num_queries_discard_timeout +=
+		(long long)worker->env.mesh->num_queries_discard_timeout;
+	s->svr.num_queries_wait_limit +=
+		(long long)worker->env.mesh->num_queries_wait_limit;
+	s->svr.num_dns_error_reports +=
+		(long long)worker->env.mesh->num_dns_error_reports;
 	/* values from outside network */
 	s->svr.unwanted_replies = (long long)worker->back->unwanted_replies;
 	s->svr.qtcp_outgoing = (long long)worker->back->num_tcp_outgoing;
@@ -325,20 +331,8 @@ server_stats_compile(struct worker* work
 	s->svr.num_query_dnscrypt_replay = 0;
 #endif /* USE_DNSCRYPT */
 	if(worker->env.auth_zones) {
-		if(reset && !worker->env.cfg->stat_cumulative) {
-			lock_rw_wrlock(&worker->env.auth_zones->lock);
-		} else {
-			lock_rw_rdlock(&worker->env.auth_zones->lock);
-		}
-		s->svr.num_query_authzone_up = (long long)worker->env.
-			auth_zones->num_query_up;
-		s->svr.num_query_authzone_down = (long long)worker->env.
-			auth_zones->num_query_down;
-		if(reset && !worker->env.cfg->stat_cumulative) {
-			worker->env.auth_zones->num_query_up = 0;
-			worker->env.auth_zones->num_query_down = 0;
-		}
-		lock_rw_unlock(&worker->env.auth_zones->lock);
+		s->svr.num_query_authzone_up += (long long)worker->env.mesh->num_query_authzone_up;
+		s->svr.num_query_authzone_down += (long long)worker->env.mesh->num_query_authzone_down;
 	}
 	s->svr.mem_stream_wait =
 		(long long)tcp_req_info_get_stream_buffer_size();
@@ -451,9 +445,15 @@ void server_stats_add(struct ub_stats_in
 	total->svr.num_queries_cookie_valid += a->svr.num_queries_cookie_valid;
 	total->svr.num_queries_cookie_client += a->svr.num_queries_cookie_client;
 	total->svr.num_queries_cookie_invalid += a->svr.num_queries_cookie_invalid;
+	total->svr.num_queries_discard_timeout +=
+		a->svr.num_queries_discard_timeout;
+	total->svr.num_queries_wait_limit += a->svr.num_queries_wait_limit;
+	total->svr.num_dns_error_reports += a->svr.num_dns_error_reports;
 	total->svr.num_queries_missed_cache += a->svr.num_queries_missed_cache;
 	total->svr.num_queries_prefetch += a->svr.num_queries_prefetch;
 	total->svr.num_queries_timed_out += a->svr.num_queries_timed_out;
+	total->svr.num_query_authzone_up += a->svr.num_query_authzone_up;
+	total->svr.num_query_authzone_down += a->svr.num_query_authzone_down;
 	if (total->svr.max_query_time_us < a->svr.max_query_time_us)
 		total->svr.max_query_time_us = a->svr.max_query_time_us;
 	total->svr.sum_query_list_size += a->svr.sum_query_list_size;
@@ -461,9 +461,9 @@ void server_stats_add(struct ub_stats_in
 #ifdef USE_DNSCRYPT
 	total->svr.num_query_dnscrypt_crypted += a->svr.num_query_dnscrypt_crypted;
 	total->svr.num_query_dnscrypt_cert += a->svr.num_query_dnscrypt_cert;
-	total->svr.num_query_dnscrypt_cleartext += \
+	total->svr.num_query_dnscrypt_cleartext +=
 		a->svr.num_query_dnscrypt_cleartext;
-	total->svr.num_query_dnscrypt_crypted_malformed += \
+	total->svr.num_query_dnscrypt_crypted_malformed +=
 		a->svr.num_query_dnscrypt_crypted_malformed;
 #endif /* USE_DNSCRYPT */
 	/* the max size reached is upped to higher of both */
Index: daemon/unbound.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/unbound.c,v
diff -u -p -r1.33 unbound.c
--- daemon/unbound.c	4 Sep 2024 09:36:40 -0000	1.33
+++ daemon/unbound.c	30 Aug 2025 11:52:23 -0000
@@ -463,6 +463,64 @@ detach(void)
 #endif /* HAVE_DAEMON */
 }
 
+/* setup a listening ssl context, fatal_exit() on any failure */
+static void
+setup_listen_sslctx(void** ctx, int is_dot, int is_doh, struct config_file* cfg)
+{
+#ifdef HAVE_SSL
+	if(!(*ctx = listen_sslctx_create(
+		cfg->ssl_service_key, cfg->ssl_service_pem, NULL,
+		cfg->tls_ciphers, cfg->tls_ciphersuites,
+		(cfg->tls_session_ticket_keys.first &&
+		cfg->tls_session_ticket_keys.first->str[0] != 0),
+		is_dot, is_doh))) {
+		fatal_exit("could not set up listen SSL_CTX");
+	}
+#else /* HAVE_SSL */
+	(void)ctx;(void)is_dot;(void)is_doh;(void)cfg;
+#endif /* HAVE_SSL */
+}
+
+/* setups the needed ssl contexts, fatal_exit() on any failure */
+static void
+setup_sslctxs(struct daemon* daemon, struct config_file* cfg)
+{
+#ifdef HAVE_SSL
+	if(!(daemon->rc = daemon_remote_create(cfg)))
+		fatal_exit("could not set up remote-control");
+	if(cfg->ssl_service_key && cfg->ssl_service_key[0]) {
+		/* setup the session keys; the callback to use them will be
+		 * attached to each sslctx separately */
+		if(cfg->tls_session_ticket_keys.first &&
+			cfg->tls_session_ticket_keys.first->str[0] != 0) {
+			if(!listen_sslctx_setup_ticket_keys(
+				cfg->tls_session_ticket_keys.first)) {
+				fatal_exit("could not set session ticket SSL_CTX");
+			}
+		}
+		(void)setup_listen_sslctx(&daemon->listen_dot_sslctx, 1, 0, cfg);
+#ifdef HAVE_NGHTTP2_NGHTTP2_H
+		if(cfg_has_https(cfg)) {
+			(void)setup_listen_sslctx(&daemon->listen_doh_sslctx, 0, 1, cfg);
+		}
+#endif
+#ifdef HAVE_NGTCP2
+		if(cfg_has_quic(cfg)) {
+			if(!(daemon->listen_quic_sslctx = quic_sslctx_create(
+				cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) {
+				fatal_exit("could not set up quic SSL_CTX");
+			}
+		}
+#endif /* HAVE_NGTCP2 */
+	}
+	if(!(daemon->connect_dot_sslctx = connect_sslctx_create(NULL, NULL,
+		cfg->tls_cert_bundle, cfg->tls_win_cert)))
+		fatal_exit("could not set up connect SSL_CTX");
+#else /* HAVE_SSL */
+	(void)daemon;(void)cfg;
+#endif /* HAVE_SSL */
+}
+
 /** daemonize, drop user privileges and chroot if needed */
 static void
 perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode,
@@ -489,36 +547,7 @@ perform_setup(struct daemon* daemon, str
 #endif
 
 	/* read ssl keys while superuser and outside chroot */
-#ifdef HAVE_SSL
-	if(!(daemon->rc = daemon_remote_create(cfg)))
-		fatal_exit("could not set up remote-control");
-	if(cfg->ssl_service_key && cfg->ssl_service_key[0]) {
-		if(!(daemon->listen_sslctx = listen_sslctx_create(
-			cfg->ssl_service_key, cfg->ssl_service_pem, NULL)))
-			fatal_exit("could not set up listen SSL_CTX");
-		if(cfg->tls_ciphers && cfg->tls_ciphers[0]) {
-			if (!SSL_CTX_set_cipher_list(daemon->listen_sslctx, cfg->tls_ciphers)) {
-				fatal_exit("failed to set tls-cipher %s", cfg->tls_ciphers);
-			}
-		}
-#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
-		if(cfg->tls_ciphersuites && cfg->tls_ciphersuites[0]) {
-			if (!SSL_CTX_set_ciphersuites(daemon->listen_sslctx, cfg->tls_ciphersuites)) {
-				fatal_exit("failed to set tls-ciphersuites %s", cfg->tls_ciphersuites);
-			}
-		}
-#endif
-		if(cfg->tls_session_ticket_keys.first &&
-			cfg->tls_session_ticket_keys.first->str[0] != 0) {
-			if(!listen_sslctx_setup_ticket_keys(daemon->listen_sslctx, cfg->tls_session_ticket_keys.first)) {
-				fatal_exit("could not set session ticket SSL_CTX");
-			}
-		}
-	}
-	if(!(daemon->connect_sslctx = connect_sslctx_create(NULL, NULL,
-		cfg->tls_cert_bundle, cfg->tls_win_cert)))
-		fatal_exit("could not set up connect SSL_CTX");
-#endif
+	(void)setup_sslctxs(daemon, cfg);
 
 	/* init syslog (as root) if needed, before daemonize, otherwise
 	 * a fork error could not be printed since daemonize closed stderr.*/
@@ -681,6 +710,9 @@ perform_setup(struct daemon* daemon, str
 	 * it would succeed on SIGHUP as well */
 	if(!cfg->use_syslog)
 		log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
+	daemon->cfgfile = strdup(*cfgfile);
+	if(!daemon->cfgfile)
+		fatal_exit("out of memory in daemon cfgfile strdup");
 }
 
 /**
Index: daemon/worker.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/worker.c,v
diff -u -p -r1.42 worker.c
--- daemon/worker.c	21 Feb 2025 13:20:40 -0000	1.42
+++ daemon/worker.c	30 Aug 2025 11:52:23 -0000
@@ -371,6 +371,84 @@ worker_check_request(sldns_buffer* pkt, 
 	return;
 }
 
+/**
+ * Send fast-reload acknowledgement to the mainthread in one byte.
+ * This signals that this worker has received the previous command.
+ * The worker is waiting if that is after a reload_stop command.
+ * Or the worker has briefly processed the event itself, and in doing so
+ * released data pointers to old config, after a reload_poll command.
+ */
+static void
+worker_send_reload_ack(struct worker* worker)
+{
+	/* If this is clipped to 8 bits because thread_num>255, then that
+	 * is not a problem, the receiver counts the number of bytes received.
+	 * The number is informative only. */
+	uint8_t c = (uint8_t)worker->thread_num;
+	ssize_t ret;
+	while(1) {
+		ret = send(worker->daemon->fast_reload_thread->commreload[1],
+			(void*)&c, 1, 0);
+		if(ret == -1) {
+			if(
+#ifndef USE_WINSOCK
+				errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+				|| errno == EWOULDBLOCK
+#  endif
+#else
+				WSAGetLastError() == WSAEINTR ||
+				WSAGetLastError() == WSAEINPROGRESS ||
+				WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+				)
+				continue; /* Try again. */
+			log_err("worker reload ack reply: send failed: %s",
+				sock_strerror(errno));
+			break;
+		}
+		break;
+	}
+}
+
+/** stop and wait to resume the worker */
+static void
+worker_stop_and_wait(struct worker* worker)
+{
+	uint8_t* buf = NULL;
+	uint32_t len = 0, cmd;
+	worker_send_reload_ack(worker);
+	/* wait for reload */
+	if(!tube_read_msg(worker->cmd, &buf, &len, 0)) {
+		log_err("worker reload read reply failed");
+		return;
+	}
+	if(len != sizeof(uint32_t)) {
+		log_err("worker reload reply, bad control msg length %d",
+			(int)len);
+		free(buf);
+		return;
+	}
+	cmd = sldns_read_uint32(buf);
+	free(buf);
+	if(cmd == worker_cmd_quit) {
+		/* quit anyway */
+		verbose(VERB_ALGO, "reload reply, control cmd quit");
+		comm_base_exit(worker->base);
+		return;
+	}
+	if(cmd != worker_cmd_reload_start) {
+		log_err("worker reload reply, wrong reply command");
+	}
+	if(worker->daemon->fast_reload_drop_mesh) {
+		verbose(VERB_ALGO, "worker: drop mesh queries after reload");
+		mesh_delete_all(worker->env.mesh);
+	}
+	fast_reload_worker_pickup_changes(worker);
+	worker_send_reload_ack(worker);
+	verbose(VERB_ALGO, "worker resume after reload");
+}
+
 void
 worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg,
 	size_t len, int error, void* arg)
@@ -406,6 +484,15 @@ worker_handle_control_cmd(struct tube* A
 		verbose(VERB_ALGO, "got control cmd remote");
 		daemon_remote_exec(worker);
 		break;
+	case worker_cmd_reload_stop:
+		verbose(VERB_ALGO, "got control cmd reload_stop");
+		worker_stop_and_wait(worker);
+		break;
+	case worker_cmd_reload_poll:
+		verbose(VERB_ALGO, "got control cmd reload_poll");
+		fast_reload_worker_pickup_changes(worker);
+		worker_send_reload_ack(worker);
+		break;
 	default:
 		log_err("bad command %d", (int)cmd);
 		break;
@@ -600,7 +687,8 @@ apply_respip_action(struct worker* worke
 		return 1;
 
 	if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, &actinfo,
-		alias_rrset, 0, worker->scratchpad, az, NULL))
+		alias_rrset, 0, worker->scratchpad, az, NULL,
+		worker->env.views, worker->env.respip_set))
 		return 0;
 
 	/* xxx_deny actions mean dropping the reply, unless the original reply
@@ -761,7 +849,8 @@ answer_from_cache(struct worker* worker,
 	} else if(partial_rep &&
 		!respip_merge_cname(partial_rep, qinfo, rep, cinfo,
 		must_validate, &encode_rep, worker->scratchpad,
-		worker->env.auth_zones)) {
+		worker->env.auth_zones, worker->env.views,
+		worker->env.respip_set)) {
 		goto bail_out;
 	}
 	if(encode_rep != rep) {
@@ -1082,7 +1171,7 @@ answer_notify(struct worker* w, struct q
 
 	if(verbosity >= VERB_DETAIL) {
 		char buf[380];
-		char zname[255+1];
+		char zname[LDNS_MAX_DOMAINLEN];
 		char sr[25];
 		dname_str(qinfo->qname, zname);
 		sr[0]=0;
@@ -1413,7 +1502,7 @@ worker_handle_request(struct comm_point*
 		return 0;
 	}
 	if(c->dnscrypt && !repinfo->is_dnscrypted) {
-		char buf[LDNS_MAX_DOMAINLEN+1];
+		char buf[LDNS_MAX_DOMAINLEN];
 		/* Check if this is unencrypted and asking for certs */
 		worker_check_request(c->buffer, worker, &check_result);
 		if(check_result.value != 0) {
@@ -1813,7 +1902,7 @@ worker_handle_request(struct comm_point*
 		cinfo_tmp.tag_datas = acladdr->tag_datas;
 		cinfo_tmp.tag_datas_size = acladdr->tag_datas_size;
 		cinfo_tmp.view = acladdr->view;
-		cinfo_tmp.respip_set = worker->daemon->respip_set;
+		cinfo_tmp.view_name = NULL;
 		cinfo = &cinfo_tmp;
 	}
 
@@ -1845,10 +1934,10 @@ lookup_cache:
 				 * its qname must be that used for cache
 				 * lookup. */
 				if((worker->env.cfg->prefetch &&
-					*worker->env.now >= rep->prefetch_ttl) ||
+					rep->prefetch_ttl <= *worker->env.now) ||
 					(worker->env.cfg->serve_expired &&
-					*worker->env.now > rep->ttl)) {
-
+					rep->ttl < *worker->env.now  &&
+					!(*worker->env.now < rep->serve_expired_norec_ttl))) {
 					time_t leeway = rep->ttl - *worker->env.now;
 					if(rep->ttl < *worker->env.now)
 						leeway = 0;
@@ -1966,13 +2055,13 @@ send_reply_rc:
 				&repinfo->client_addr, repinfo->client_addrlen,
 				tv, 1, c->buffer,
 				(worker->env.cfg->log_destaddr?(void*)repinfo->c->socket->addr:NULL),
-				c->type);
+				c->type, c->ssl);
 		} else {
 			log_reply_info(NO_VERBOSE, &qinfo,
 				&repinfo->client_addr, repinfo->client_addrlen,
 				tv, 1, c->buffer,
 				(worker->env.cfg->log_destaddr?(void*)repinfo->c->socket->addr:NULL),
-				c->type);
+				c->type, c->ssl);
 		}
 	}
 #ifdef USE_DNSCRYPT
@@ -2173,10 +2262,11 @@ worker_init(struct worker* worker, struc
 			: cfg->tcp_idle_timeout,
 		cfg->harden_large_queries, cfg->http_max_streams,
 		cfg->http_endpoint, cfg->http_notls_downstream,
-		worker->daemon->tcl, worker->daemon->listen_sslctx,
+		worker->daemon->tcl, worker->daemon->listen_dot_sslctx,
+		worker->daemon->listen_doh_sslctx,
+		worker->daemon->listen_quic_sslctx,
 		dtenv, worker->daemon->doq_table, worker->env.rnd,
-		cfg->ssl_service_key, cfg->ssl_service_pem, cfg,
-		worker_handle_request, worker);
+		cfg, worker_handle_request, worker);
 	if(!worker->front) {
 		log_err("could not create listening sockets");
 		worker_delete(worker);
@@ -2191,7 +2281,7 @@ worker_init(struct worker* worker, struc
 		cfg->unwanted_threshold, cfg->outgoing_tcp_mss,
 		&worker_alloc_cleanup, worker,
 		cfg->do_udp || cfg->udp_upstream_without_downstream,
-		worker->daemon->connect_sslctx, cfg->delay_close,
+		worker->daemon->connect_dot_sslctx, cfg->delay_close,
 		cfg->tls_use_sni, dtenv, cfg->udp_connect,
 		cfg->max_reuse_tcp_queries, cfg->tcp_reuse_timeout,
 		cfg->tcp_auth_query_timeout);
Index: daemon/worker.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/worker.h,v
diff -u -p -r1.9 worker.h
--- daemon/worker.h	5 Sep 2023 11:12:10 -0000	1.9
+++ daemon/worker.h	30 Aug 2025 11:52:23 -0000
@@ -72,7 +72,13 @@ enum worker_commands {
 	/** obtain statistics without statsclear */
 	worker_cmd_stats_noreset,
 	/** execute remote control command */
-	worker_cmd_remote
+	worker_cmd_remote,
+	/** for fast-reload, perform stop */
+	worker_cmd_reload_stop,
+	/** for fast-reload, start again */
+	worker_cmd_reload_start,
+	/** for fast-reload, poll to make sure worker has released data */
+	worker_cmd_reload_poll
 };
 
 /**
Index: dns64/dns64.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/dns64/dns64.c,v
diff -u -p -r1.22 dns64.c
--- dns64/dns64.c	21 Feb 2025 13:20:40 -0000	1.22
+++ dns64/dns64.c	30 Aug 2025 11:52:23 -0000
@@ -658,7 +658,8 @@ handle_event_moddone(struct module_qstat
 		!dns_cache_store(
 			qstate->env, &qstate->qinfo, qstate->return_msg->rep,
 			0, qstate->prefetch_leeway, 0, NULL,
-			qstate->query_flags, qstate->qstarttime))
+			qstate->query_flags, qstate->qstarttime,
+			qstate->is_valrec))
 		log_err("out of memory");
 
 	/* do nothing */
@@ -1008,7 +1009,8 @@ dns64_inform_super(struct module_qstate*
 	/* Store the generated response in cache. */
 	if ( (!super_dq || !super_dq->started_no_cache_store) &&
 		!dns_cache_store(super->env, &super->qinfo, super->return_msg->rep,
-		0, super->prefetch_leeway, 0, NULL, super->query_flags, qstate->qstarttime))
+		0, super->prefetch_leeway, 0, NULL, super->query_flags,
+		qstate->qstarttime, qstate->is_valrec))
 		log_err("out of memory");
 }
 
Index: dnstap/dnstap.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/dnstap/dnstap.c,v
diff -u -p -r1.12 dnstap.c
--- dnstap/dnstap.c	4 Sep 2024 09:36:40 -0000	1.12
+++ dnstap/dnstap.c	30 Aug 2025 11:52:23 -0000
@@ -192,8 +192,11 @@ static void
 dt_apply_identity(struct dt_env *env, struct config_file *cfg)
 {
 	char buf[MAXHOSTNAMELEN+1];
-	if (!cfg->dnstap_send_identity)
+	if (!cfg->dnstap_send_identity) {
+		free(env->identity);
+		env->identity = NULL;
 		return;
+	}
 	free(env->identity);
 	if (cfg->dnstap_identity == NULL || cfg->dnstap_identity[0] == 0) {
 		if (gethostname(buf, MAXHOSTNAMELEN) == 0) {
@@ -215,8 +218,11 @@ dt_apply_identity(struct dt_env *env, st
 static void
 dt_apply_version(struct dt_env *env, struct config_file *cfg)
 {
-	if (!cfg->dnstap_send_version)
+	if (!cfg->dnstap_send_version) {
+		free(env->version);
+		env->version = NULL;
 		return;
+	}
 	free(env->version);
 	if (cfg->dnstap_version == NULL || cfg->dnstap_version[0] == 0)
 		env->version = strdup(PACKAGE_STRING);
@@ -230,13 +236,8 @@ dt_apply_version(struct dt_env *env, str
 }
 
 void
-dt_apply_cfg(struct dt_env *env, struct config_file *cfg)
+dt_apply_logcfg(struct dt_env *env, struct config_file *cfg)
 {
-	if (!cfg->dnstap)
-		return;
-
-	dt_apply_identity(env, cfg);
-	dt_apply_version(env, cfg);
 	if ((env->log_resolver_query_messages = (unsigned int)
 	     cfg->dnstap_log_resolver_query_messages))
 	{
@@ -273,6 +274,17 @@ dt_apply_cfg(struct dt_env *env, struct 
 		verbose(VERB_OPS, "dnstap SAMPLE_RATE enabled and set to \"%d\"", (int)env->sample_rate);
 	}
 	lock_basic_unlock(&env->sample_lock);
+}
+
+void
+dt_apply_cfg(struct dt_env *env, struct config_file *cfg)
+{
+	if (!cfg->dnstap)
+		return;
+
+	dt_apply_identity(env, cfg);
+	dt_apply_version(env, cfg);
+	dt_apply_logcfg(env, cfg);
 }
 
 int
Index: dnstap/dnstap.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/dnstap/dnstap.h,v
diff -u -p -r1.1.1.7 dnstap.h
--- dnstap/dnstap.h	4 Sep 2024 09:35:36 -0000	1.1.1.7
+++ dnstap/dnstap.h	30 Aug 2025 11:52:23 -0000
@@ -107,6 +107,13 @@ void
 dt_apply_cfg(struct dt_env *env, struct config_file *cfg);
 
 /**
+ * Apply config settings for log enable for message types.
+ * @param env: dnstap environment object.
+ * @param cfg: new config settings.
+ */
+void dt_apply_logcfg(struct dt_env *env, struct config_file *cfg);
+
+/**
  * Initialize per-worker state in dnstap environment object.
  * @param env: dnstap environment object to initialize, created with dt_create().
  * @param base: event base for wakeup timer.
Index: dnstap/dnstap.m4
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/dnstap/dnstap.m4,v
diff -u -p -r1.2 dnstap.m4
--- dnstap/dnstap.m4	4 Sep 2024 09:36:40 -0000	1.2
+++ dnstap/dnstap.m4	30 Aug 2025 11:52:23 -0000
@@ -35,36 +35,36 @@ AC_DEFUN([dt_DNSTAP],
             ],
             [
                 if test -n "$PKG_CONFIG"; then
-                        PKG_CHECK_MODULES([PROTOBUFC], [libprotobuf-c],
-                            [
-                                CFLAGS="$CFLAGS $PROTOBUFC_CFLAGS"
-                                LIBS="$LIBS $PROTOBUFC_LIBS"
-                            ],
-                            [
-                                # pkg-config failed; try falling back to known values
-                                # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
-                                if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
-                                    CFLAGS="$CFLAGS -I/usr/include/google"
+                    PKG_CHECK_MODULES([PROTOBUFC], [libprotobuf-c],
+                        [
+                            CFLAGS="$CFLAGS $PROTOBUFC_CFLAGS"
+                            LIBS="$LIBS $PROTOBUFC_LIBS"
+                        ],
+                        [
+                            # pkg-config failed; try falling back to known values
+                            # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
+                            if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
+                                CFLAGS="$CFLAGS -I/usr/include/google"
+                            else
+                                if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
+                                    CFLAGS="$CFLAGS -I/usr/local/include/google"
+                                    LDFLAGS="$LDFLAGS -L/usr/local/lib"
                                 else
-                                    if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
-                                        CFLAGS="$CFLAGS -I/usr/local/include/google"
-                                        LDFLAGS="$LDFLAGS -L/usr/local/lib"
-                                    else
-                                        AC_MSG_ERROR([The protobuf-c package was not found with pkg-config. Please install protobuf-c!])
-                                    fi
+                                    AC_MSG_ERROR([The protobuf-c package was not found with pkg-config. Please install protobuf-c!])
                                 fi
-                            ]
-                        )
-                else
-                        # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
-                        if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
-                            CFLAGS="$CFLAGS -I/usr/include/google"
-                        else
-                            if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
-                                CFLAGS="$CFLAGS -I/usr/local/include/google"
-                                LDFLAGS="$LDFLAGS -L/usr/local/lib"
                             fi
+                        ]
+                    )
+                else
+                    # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
+                    if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
+                        CFLAGS="$CFLAGS -I/usr/include/google"
+                    else
+                        if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
+                            CFLAGS="$CFLAGS -I/usr/local/include/google"
+                            LDFLAGS="$LDFLAGS -L/usr/local/lib"
                         fi
+                    fi
                 fi
             ]
         )
Index: dnstap/unbound-dnstap-socket.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/dnstap/unbound-dnstap-socket.c,v
diff -u -p -r1.3 unbound-dnstap-socket.c
--- dnstap/unbound-dnstap-socket.c	21 Feb 2025 13:20:40 -0000	1.3
+++ dnstap/unbound-dnstap-socket.c	30 Aug 2025 11:52:23 -0000
@@ -346,7 +346,8 @@ static struct tap_socket* tap_socket_new
 	s->fd = -1;
 	s->ev_cb = ev_cb;
 	s->data = data;
-	s->sslctx = listen_sslctx_create(server_key, server_cert, verifypem);
+	s->sslctx = listen_sslctx_create(server_key, server_cert, verifypem,
+		NULL, NULL, 0, 0, 0);
 	if(!s->sslctx) {
 		log_err("could not create ssl context");
 		free(s->ip);
@@ -1784,6 +1785,20 @@ int replay_var_compare(const void* ATTR_
 void remote_get_opt_ssl(char* ATTR_UNUSED(str), void* ATTR_UNUSED(arg))
 {
         log_assert(0);
+}
+
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
+	void* ATTR_UNUSED(arg))
+{
+	log_assert(0);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+	void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+	log_assert(0);
+	return 0;
 }
 
 #ifdef HAVE_NGTCP2
Index: doc/README
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/README,v
diff -u -p -r1.41 README
--- doc/README	21 Feb 2025 13:20:40 -0000	1.41
+++ doc/README	30 Aug 2025 11:52:23 -0000
@@ -1,4 +1,4 @@
-README for Unbound 1.22.0
+README for Unbound 1.23.1
 Copyright 2007 NLnet Labs
 http://unbound.net
 
Index: doc/example.conf.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/example.conf.in,v
diff -u -p -r1.46 example.conf.in
--- doc/example.conf.in	21 Feb 2025 13:20:40 -0000	1.46
+++ doc/example.conf.in	30 Aug 2025 11:52:23 -0000
@@ -1,7 +1,7 @@
 #
 # Example configuration file.
 #
-# See unbound.conf(5) man page, version 1.22.0.
+# See unbound.conf(5) man page, version 1.23.1.
 #
 # this is a comment.
 
@@ -194,7 +194,7 @@ server:
 	# iter-scrub-cname: 11
 
 	# Limit on upstream queries for an incoming query and its recursion.
-	# max-global-quota: 128
+	# max-global-quota: 200
 
 	# msec for waiting for an unknown server to reply.  Increase if you
 	# are behind a slow satellite link, to eg. 1128.
@@ -215,6 +215,12 @@ server:
 	# Apart from the default, the wait limit with cookie can be adjusted.
 	# wait-limit-cookie-netblock: 192.0.2.0/24 50000
 
+	# Defaults for loopback, it has no wait limit.
+	# wait-limit-netblock: 127.0.0.0/8 -1
+	# wait-limit-netblock: ::1/128 -1
+	# wait-limit-cookie-netblock: 127.0.0.0/8 -1
+	# wait-limit-cookie-netblock: ::1/128 -1
+
 	# the amount of memory to use for the RRset cache.
 	# plain value in bytes or you can append k, m or G. default is "4Mb".
 	# rrset-cache-size: 4m
@@ -556,8 +562,9 @@ server:
 	# harden-referral-path: no
 
 	# Harden against algorithm downgrade when multiple algorithms are
-	# advertised in the DS record.  If no, allows the weakest algorithm
-	# to validate the zone.
+	# advertised in the DS record.  If no, allows any algorithm
+	# to validate the zone which is the standard behavior for validators.
+	# Check the manpage for detailed information.
 	# harden-algo-downgrade: no
 
 	# Harden against unknown records in the authority section and the
@@ -730,12 +737,13 @@ server:
 	# disable-edns-do: no
 
 	# Serve expired responses from cache, with serve-expired-reply-ttl in
-	# the response, and then attempt to fetch the data afresh.
+	# the response. By default it first tries to refresh an expired answer.
+	# Can be configured with serve-expired-client-timeout.
 	# serve-expired: no
 	#
 	# Limit serving of expired responses to configured seconds after
 	# expiration. 0 disables the limit.
-	# serve-expired-ttl: 0
+	# serve-expired-ttl: 86400
 	#
 	# Set the TTL of expired records to the serve-expired-ttl value after a
 	# failed attempt to retrieve the record from upstream. This makes sure
@@ -748,10 +756,9 @@ server:
 	#
 	# Time in milliseconds before replying to the client with expired data.
 	# This essentially enables the serve-stale behavior as specified in
-	# RFC 8767 that first tries to resolve before
-	# immediately responding with expired data.  0 disables this behavior.
-	# A recommended value is 1800.
-	# serve-expired-client-timeout: 0
+	# RFC 8767 that first tries to resolve before immediately responding
+	# with expired data.  0 disables this behavior.
+	# serve-expired-client-timeout: 1800
 
 	# Return the original TTL as received from the upstream name server rather
 	# than the decrementing TTL as stored in the cache.  Enabling this feature
@@ -810,6 +817,8 @@ server:
 	# local-zone: "127.in-addr.arpa." nodefault
 	# local-zone: "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa." nodefault
 	# local-zone: "home.arpa." nodefault
+	# local-zone: "resolver.arpa." nodefault
+	# local-zone: "service.arpa." nodefault
 	# local-zone: "onion." nodefault
 	# local-zone: "test." nodefault
 	# local-zone: "invalid." nodefault
@@ -1077,6 +1086,11 @@ server:
 	# Note that the ede option above needs to be enabled for this to work.
 	# ede-serve-expired: no
 
+	# Enable DNS Error Reporting (RFC9567).
+	# qname-minimisation is advised to be turned on as well to increase
+	# privacy on the outgoing reports.
+	# dns-error-reporting: no
+
 	# Specific options for ipsecmod. Unbound needs to be configured with
 	# --enable-ipsecmod for these to take effect.
 	#
@@ -1304,9 +1318,9 @@ remote-control:
 #     # redis server's TCP port
 #     redis-server-port: 6379
 #     # if the server uses a unix socket, set its path, or "" when not used.
-#     # redis-server-path: "/var/lib/redis/redis-server.sock"
+#     redis-server-path: "/var/lib/redis/redis-server.sock"
 #     # if the server uses an AUTH password, specify here, or "" when not used.
-#     # redis-server-password: ""
+#     redis-server-password: ""
 #     # timeout (in ms) for communication with the redis server
 #     redis-timeout: 100
 #     # timeout (in ms) for commands, if 0, uses redis-timeout.
@@ -1317,6 +1331,22 @@ remote-control:
 #     redis-expire-records: no
 #     # redis logical database to use, 0 is the default database.
 #     redis-logical-db: 0
+#     # redis replica server's IP address or host name
+#     redis-replica-server-host: 127.0.0.1
+#     # redis replica server's TCP port
+#     redis-replica-server-port: 6379
+#     # if the replica server uses a unix socket, set its path, or "" when not used.
+#     redis-replica-server-path: "/var/lib/redis/redis-server.sock"
+#     # if the replica server uses an AUTH password, specify here, or "" when not used.
+#     redis-replica-server-password: ""
+#     # timeout (in ms) for communication with the redis replica server
+#     redis-replica-timeout: 100
+#     # timeout (in ms) for redis replica commands, if 0, uses redis-replica-timeout.
+#     redis-replica-command-timeout: 0
+#     # timeout (in ms) for redis replica connection set up, if 0, uses redis-replica-timeout.
+#     redis-replica-connect-timeout: 0
+#     # redis logical database to use for the replica server, 0 is the default database.
+#     redis-replica-logical-db: 0
 
 # IPSet
 # Add specify domain into set via ipset.
Index: doc/libunbound.3.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/libunbound.3.in,v
diff -u -p -r1.44 libunbound.3.in
--- doc/libunbound.3.in	21 Feb 2025 13:20:40 -0000	1.44
+++ doc/libunbound.3.in	30 Aug 2025 11:52:23 -0000
@@ -1,4 +1,4 @@
-.TH "libunbound" "3" "Oct 17, 2024" "NLnet Labs" "unbound 1.22.0"
+.TH "libunbound" "3" "Jul 16, 2025" "NLnet Labs" "unbound 1.23.1"
 .\"
 .\" libunbound.3 -- unbound library functions manual
 .\"
@@ -44,7 +44,7 @@
 .B ub_ctx_zone_remove,
 .B ub_ctx_data_add,
 .B ub_ctx_data_remove
-\- Unbound DNS validating resolver 1.22.0 functions.
+\- Unbound DNS validating resolver 1.23.1 functions.
 .SH "SYNOPSIS"
 .B #include <unbound.h>
 .LP
Index: doc/unbound-anchor.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-anchor.8.in,v
diff -u -p -r1.43 unbound-anchor.8.in
--- doc/unbound-anchor.8.in	21 Feb 2025 13:20:40 -0000	1.43
+++ doc/unbound-anchor.8.in	30 Aug 2025 11:52:23 -0000
@@ -1,4 +1,4 @@
-.TH "unbound-anchor" "8" "Oct 17, 2024" "NLnet Labs" "unbound 1.22.0"
+.TH "unbound-anchor" "8" "Jul 16, 2025" "NLnet Labs" "unbound 1.23.1"
 .\"
 .\" unbound-anchor.8 -- unbound anchor maintenance utility manual
 .\"
Index: doc/unbound-checkconf.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-checkconf.8.in,v
diff -u -p -r1.43 unbound-checkconf.8.in
--- doc/unbound-checkconf.8.in	21 Feb 2025 13:20:40 -0000	1.43
+++ doc/unbound-checkconf.8.in	30 Aug 2025 11:52:23 -0000
@@ -1,4 +1,4 @@
-.TH "unbound-checkconf" "8" "Oct 17, 2024" "NLnet Labs" "unbound 1.22.0"
+.TH "unbound-checkconf" "8" "Jul 16, 2025" "NLnet Labs" "unbound 1.23.1"
 .\"
 .\" unbound-checkconf.8 -- unbound configuration checker manual
 .\"
Index: doc/unbound-control.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-control.8.in,v
diff -u -p -r1.45 unbound-control.8.in
--- doc/unbound-control.8.in	21 Feb 2025 13:20:40 -0000	1.45
+++ doc/unbound-control.8.in	30 Aug 2025 11:52:23 -0000
@@ -1,4 +1,4 @@
-.TH "unbound-control" "8" "Oct 17, 2024" "NLnet Labs" "unbound 1.22.0"
+.TH "unbound-control" "8" "Jul 16, 2025" "NLnet Labs" "unbound 1.23.1"
 .\"
 .\" unbound-control.8 -- unbound remote control manual
 .\"
@@ -60,6 +60,155 @@ Reload the server but try to keep the RR
 That means the caches sizes and the number of threads must not change between
 reloads.
 .TP
+.B fast_reload \fR[\fI+dpv\fR]
+Reload the server, but keep downtime to a minimum, so that user queries
+keep seeing service. This needs the code compiled with threads. The config
+is loaded in a thread, and prepared, then it briefly pauses the existing
+server and updates config options. The intent is that the pause does not
+impact the service of user queries. The cache is kept. Also user queries
+worked on are kept and continue, but with the new config options.
+.IP
+This command is experimental at this time.
+.IP
+The amount of temporal memory needed during a fast_reload is twice the
+amount needed for configuration.
+This is because Unbound temporarily needs to store both current configuration
+values and new ones while trying to fast_reload.
+Zones loaded from disk (authority zones and RPZ zones) are included in such
+memory needs.
+.IP
+Options that can be changed are for
+forwards,
+stubs,
+views,
+authority zones,
+RPZ zones and
+local zones.
+.IP
+Also
+access-control and similar options,
+interface-action and similar options and
+tcp-connection-limit.
+It can reload some
+define-tag
+changes, more on that below.
+Further options include
+insecure-lan-zones,
+domain-insecure,
+trust-anchor-file,
+trust-anchor,
+trusted-keys-file,
+auto-trust-anchor-file,
+edns-client-string,
+ipset,
+log-identity,
+infra-cache-numhosts,
+msg-cache-size,
+rrset-cache-size,
+key-cache-size,
+ratelimit-size,
+neg-cache-size,
+num-queries-per-thread,
+jostle-timeout,
+use-caps-for-id,
+unwanted-reply-threshold,
+tls-use-sni,
+outgoing-tcp-mss,
+ip-dscp,
+max-reuse-tcp-queries,
+tcp-reuse-timeout,
+tcp-auth-query-timeout,
+delay-close.
+.IP
+It does not work with
+interface and
+outgoing-interface changes,
+also not with
+remote control,
+outgoing-port-permit,
+outgoing-port-avoid,
+msg-buffer-size,
+any **\*-slabs** options and
+statistics-interval changes.
+.IP
+For dnstap these options can be changed:
+dnstap-log-resolver-query-messages,
+dnstap-log-resolver-response-messages,
+dnstap-log-client-query-messages,
+dnstap-log-client-response-messages,
+dnstap-log-forwarder-query-messages and
+dnstap-log-forwarder-response-messages.
+.IP
+It does not work with these options:
+dnstap-enable,
+dnstap-bidirectional,
+dnstap-socket-path,
+dnstap-ip,
+dnstap-tls,
+dnstap-tls-server-name,
+dnstap-tls-cert-bundle,
+dnstap-tls-client-key-file and
+dnstap-tls-client-cert-file.
+.IP
+The options
+dnstap-send-identity,
+dnstap-send-version,
+dnstap-identity, and
+dnstap-version can be loaded
+when ``+p`` is not used.
+.IP
+The '+v' option makes the output verbose which includes the time it took to do
+the reload.
+With '+vv' it is more verbose which includes the amount of memory that was
+allocated temporarily to perform the reload; this amount of memory can be big
+if the config has large contents.
+In the timing output the 'reload' time is the time during which the server was
+paused.
+.IP
+The '+p' option makes the reload not pause threads, they keep running.
+Locks are acquired, but items are updated in sequence, so it is possible
+for threads to see an inconsistent state with some options from the old
+and some options from the new config, such as cache TTL parameters from the
+old config and forwards from the new config. The stubs and forwards are
+updated at the same time, so that they are viewed consistently, either old
+or new values together. The option makes the reload time take eg. 3
+microseconds instead of 0.3 milliseconds during which the worker threads are
+interrupted. So, the interruption is much shorter, at the expense of some
+inconsistency. After the reload itself, every worker thread is briefly
+contacted to make them release resources, this makes the delete timing
+a little longer, and takes up time from the remote control servicing
+worker thread.
+.IP
+With the nopause option, the reload does not work to reload some options,
+that fast reload works on without the nopause option: val-bogus-ttl,
+val-override-date, val-sig-skew-min, val-sig-skew-max, val-max-restart,
+val-nsec3-keysize-iterations, target-fetch-policy, outbound-msg-retry,
+max-sent-count, max-query-restarts, do-not-query-address,
+do-not-query-localhost, private-address, private-domain, caps-exempt,
+nat64-prefix, do-nat64, infra-host-ttl, infra-keep-probing, ratelimit,
+ip-ratelimit, ip-ratelimit-cookie, wait-limit-netblock,
+wait-limit-cookie-netblock, ratelimit-below-domain, ratelimit-for-domain.
+.IP
+The '+d' option makes the reload drop queries that the worker threads are
+working on. This is like flush_requestlist. Without it the queries are kept
+so that users keep getting answers for those queries that are currently
+processed. The drop makes it so that queries during the life time of the
+query processing see only old, or only new config options.
+.IP
+When there are changes to the config tags, from the \fBdefine\-tag\fR option,
+then the '+d' option is implicitly turned on with a warning printout, and
+queries are dropped.
+This is to stop references to the old tag information, by the old
+queries. If the number of tags is increased in the newly loaded config, by
+adding tags at the end, then the implicit '+d' option is not needed.
+.IP
+For response ip, that is actions associated with IP addresses, and perhaps
+intersected with access control tag and action information, those settings
+are stored with a query when it comes in based on its source IP address.
+The old information is kept with the query until the queries are done.
+This is gone when those queries are resolved and finished, or it is possible
+to flush the requestlist with '+d'.
+.TP
 .B verbosity \fInumber
 Change verbosity value for logging. Same values as \fBverbosity\fR keyword in
 \fIunbound.conf\fR(5).  This new setting lasts until the server is issued
@@ -422,6 +571,12 @@ number of queries with a client part onl
 .I threadX.num.queries_cookie_invalid
 number of queries with an invalid DNS Cookie by thread
 .TP
+.I threadX.num.queries_discard_timeout
+number of queries removed due to discard-timeout by thread
+.TP
+.I threadX.num.queries_wait_limit
+number of queries removed due to wait-limit by thread
+.TP
 .I threadX.num.cachehits
 number of queries that were successfully answered using a cache lookup
 .TP
@@ -441,6 +596,9 @@ request for certificates.
 .I threadX.num.dnscrypt.malformed
 number of request that were neither cleartext, not valid dnscrypt messages.
 .TP
+.I threadX.num.dns_error_reports
+number of DNS Error Reports generated by thread
+.TP
 .I threadX.num.prefetch
 number of cache prefetches performed.  This number is included in
 cachehits, as the original query had the unprefetched answer from cache,
@@ -511,6 +669,12 @@ summed over threads.
 .I total.num.queries_cookie_invalid
 summed over threads.
 .TP
+.I total.num.queries_discard_timeout
+summed over threads.
+.TP
+.I total.num.queries_wait_limit
+summed over threads.
+.TP
 .I total.num.cachehits
 summed over threads.
 .TP
@@ -527,6 +691,9 @@ summed over threads.
 summed over threads.
 .TP
 .I total.num.dnscrypt.malformed
+summed over threads.
+.TP
+.I total.num.dns_error_reports
 summed over threads.
 .TP
 .I total.num.prefetch
Index: doc/unbound-host.1.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-host.1.in,v
diff -u -p -r1.45 unbound-host.1.in
--- doc/unbound-host.1.in	21 Feb 2025 13:20:40 -0000	1.45
+++ doc/unbound-host.1.in	30 Aug 2025 11:52:23 -0000
@@ -1,4 +1,4 @@
-.TH "unbound\-host" "1" "Oct 17, 2024" "NLnet Labs" "unbound 1.22.0"
+.TH "unbound\-host" "1" "Jul 16, 2025" "NLnet Labs" "unbound 1.23.1"
 .\"
 .\" unbound-host.1 -- unbound DNS lookup utility
 .\"
Index: doc/unbound.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.8.in,v
diff -u -p -r1.46 unbound.8.in
--- doc/unbound.8.in	21 Feb 2025 13:20:40 -0000	1.46
+++ doc/unbound.8.in	30 Aug 2025 11:52:23 -0000
@@ -1,4 +1,4 @@
-.TH "unbound" "8" "Oct 17, 2024" "NLnet Labs" "unbound 1.22.0"
+.TH "unbound" "8" "Jul 16, 2025" "NLnet Labs" "unbound 1.23.1"
 .\"
 .\" unbound.8 -- unbound manual
 .\"
@@ -9,7 +9,7 @@
 .\"
 .SH "NAME"
 .B unbound
-\- Unbound DNS validating resolver 1.22.0.
+\- Unbound DNS validating resolver 1.23.1.
 .SH "SYNOPSIS"
 .B unbound
 .RB [ \-h ]
Index: doc/unbound.conf.5.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.conf.5.in,v
diff -u -p -r1.50 unbound.conf.5.in
--- doc/unbound.conf.5.in	21 Feb 2025 13:20:40 -0000	1.50
+++ doc/unbound.conf.5.in	30 Aug 2025 11:52:23 -0000
@@ -1,4 +1,4 @@
-.TH "unbound.conf" "5" "Oct 17, 2024" "NLnet Labs" "unbound 1.22.0"
+.TH "unbound.conf" "5" "Jul 16, 2025" "NLnet Labs" "unbound 1.23.1"
 .\"
 .\" unbound.conf.5 -- unbound.conf manual
 .\"
@@ -290,11 +290,15 @@ The wait limit for the netblock. If not 
 used. The most specific netblock is used to determine the limit. Useful for
 overriding the default for a specific, group or individual, server.
 The value -1 disables wait limits for the netblock.
+By default the loopback has a wait limit netblock of -1, it is not limited,
+because it is separated from the rest of network for spoofed packets.
+The loopback addresses 127.0.0.0/8 and ::1/128 are default at -1.
 .TP
 .B wait\-limit\-cookie\-netblock: \fI<netblock> <number>
 The wait limit for the netblock, when the query has a DNS cookie.
 If not given, the wait\-limit\-cookie value is used.
 The value -1 disables wait limits for the netblock.
+The loopback addresses 127.0.0.0/8 and ::1/128 are default at -1.
 .TP
 .B so\-rcvbuf: \fI<number>
 If not 0, then set the SO_RCVBUF socket option to get more buffer
@@ -680,7 +684,7 @@ and initial ACL (check if the proxy itse
 The proxied address (if any) will then be used as the true client address and
 will be used where applicable for logging, ACL, DNSTAP, RPZ and IP ratelimiting.
 PROXYv2 is supported for UDP and TCP/TLS listening interfaces.
-There is no support for PROXYv2 on a DoH or DNSCrypt listening interface.
+There is no support for PROXYv2 on a DoH, DoQ or DNSCrypt listening interface.
 Can list multiple, each on a new statement.
 .TP
 .B quic\-port: \fI<number>
@@ -866,9 +870,8 @@ outside of the chroot directory.
 Additionally, Unbound may need to access /dev/urandom (for entropy)
 from inside the chroot.
 .IP
-If given a chroot is done to the given directory. By default chroot is
-enabled and the default is "@UNBOUND_CHROOT_DIR@". If you give "" no
-chroot is performed.
+If given a chroot is done to the given directory. The chroot is by default 
+set to "@UNBOUND_CHROOT_DIR@". If you give "" no chroot is performed.
 .TP
 .B username: \fI<name>
 If given, after binding the port the user privileges are dropped. Default is
@@ -1011,11 +1014,11 @@ closer to that of BIND 9, while setting 
 rumoured to be closer to that of BIND 8.
 .TP
 .B harden\-short\-bufsize: \fI<yes or no>
-Very small EDNS buffer sizes from queries are ignored. Default is on, as
+Very small EDNS buffer sizes from queries are ignored. Default is yes, as
 described in the standard.
 .TP
 .B harden\-large\-queries: \fI<yes or no>
-Very large queries are ignored. Default is off, since it is legal protocol
+Very large queries are ignored. Default is no, since it is legal protocol
 wise to send these, and could be necessary for operation if TSIG or EDNS
 payload is very large.
 .TP
@@ -1062,10 +1065,23 @@ to increase the max depth that is checke
 .TP
 .B harden\-algo\-downgrade: \fI<yes or no>
 Harden against algorithm downgrade when multiple algorithms are
-advertised in the DS record.  If no, allows the weakest algorithm to
-validate the zone.  Default is no.  Zone signers must produce zones
-that allow this feature to work, but sometimes they do not, and turning
-this option off avoids that validation failure.
+advertised in the DS record.
+This works by first choosing only the strongest DS digest type as per RFC 4509
+(Unbound treats the highest algorithm as the strongest) and then
+expecting signatures from all the advertised signing algorithms from the chosen
+DS(es) to be present.
+If no, allows any one supported algorithm to validate the zone, even if other advertised algorithms are broken.
+Default is no.
+RFC 6840 mandates that zone signers must produce zones signed with all
+advertised algorithms, but sometimes they do not.
+RFC 6840 also clarifies that this requirement is not for validators and
+validators should accept any single valid path.
+It should thus be explicitly noted that this option violates RFC 6840 for
+DNSSEC validation and should only be used to perform a signature
+completeness test to support troubleshooting.
+Using this option may break DNSSEC resolution with non-RFC6840-conforming
+signers and/or in multi-signer configurations that don't send all the
+advertised signatures.
 .TP
 .B harden\-unknown\-additional: \fI<yes or no>
 Harden against unknown records in the authority section and additional
@@ -1086,7 +1102,7 @@ queries.  For domains that do not suppor
 because they keep sending different answers, like some load balancers.
 Can be given multiple times, for different domains.
 .TP
-.B caps\-whitelist: \fI<yes or no>
+.B caps\-whitelist: \fI<domain>
 Alternate syntax for \fBcaps\-exempt\fR.
 .TP
 .B qname\-minimisation: \fI<yes or no>
@@ -1203,9 +1219,6 @@ Adding \fIrespip\fR to the front will ca
 all queries.
 The default is "\fIvalidator iterator\fR".
 .IP
-When the server is built with
-EDNS client subnet support the default is "\fIsubnetcache validator
-iterator\fR".
 Most modules that need to be listed here have to be listed at the beginning
 of the line.  The subnetcachedb module has to be listed just before
 the iterator.
@@ -1345,15 +1358,17 @@ Default is no.
 .TP
 .B serve\-expired: \fI<yes or no>
 If enabled, Unbound attempts to serve old responses from cache with a
-TTL of \fBserve\-expired\-reply\-ttl\fR in the response without waiting for the
-actual resolution to finish.  The actual resolution answer ends up in the cache
-later on.  Default is "no".
+TTL of \fBserve\-expired\-reply\-ttl\fR in the response.
+By default the expired answer will be used after a resolution attempt errored
+out or is taking more than serve\-expired\-client\-timeout to resolve.
+Default is "no".
 .TP
 .B serve\-expired\-ttl: \fI<seconds>
-Limit serving of expired responses to configured seconds after expiration. 0
-disables the limit.  This option only applies when \fBserve\-expired\fR is
-enabled.  A suggested value per RFC 8767 is between
-86400 (1 day) and 259200 (3 days).  The default is 0.
+Limit serving of expired responses to configured seconds after expiration.
+0 disables the limit.
+This option only applies when \fBserve\-expired\fR is enabled.
+A suggested value per RFC 8767 is between 86400 (1 day) and 259200 (3 days).
+The default is 86400.
 .TP
 .B serve\-expired\-ttl\-reset: \fI<yes or no>
 Set the TTL of expired records to the \fBserve\-expired\-ttl\fR value after a
@@ -1367,12 +1382,14 @@ TTL value to use when replying with expi
 use 30 as the value (RFC 8767).  The default is 30.
 .TP
 .B serve\-expired\-client\-timeout: \fI<msec>
-Time in milliseconds before replying to the client with expired data.  This
-essentially enables the serve-stale behavior as specified in
+Time in milliseconds before replying to the client with expired data.
+This essentially enables the serve-stale behavior as specified in
 RFC 8767 that first tries to resolve before immediately
-responding with expired data.  A recommended value per
-RFC 8767 is 1800.  Setting this to 0 will disable this
-behavior.  Default is 0.
+responding with expired data.
+Setting this to 0 will disable this behavior and instead serve the expired
+record immediately from the cache before attempting to refresh it via
+resolution.
+Default is 1800.
 .TP
 .B serve\-original\-ttl: \fI<yes or no>
 If enabled, Unbound will always return the original TTL as received from
@@ -1577,6 +1594,7 @@ given zone.  Use \fInodefault\fR if you 
 use a subzone, use \fItransparent\fR.
 .P
 The default zones are localhost, reverse 127.0.0.1 and ::1, the home.arpa,
+the resolver.arpa, the service.arpa,
 the onion, test, invalid and the AS112 zones. The AS112 zones are reverse
 DNS zones for private use and reserved IP addresses for which the servers
 on the internet cannot provide correct answers. They are configured by
@@ -1632,6 +1650,24 @@ local\-data: "home.arpa. 10800 IN
     SOA localhost. nobody.invalid. 1 3600 1200 604800 10800"
 .fi
 .TP 10
+\h'5'\fIresolver.arpa (RFC 9462)\fR
+Default content:
+.nf
+local\-zone: "resolver.arpa." static
+local\-data: "resolver.arpa. 10800 IN NS localhost."
+local\-data: "resolver.arpa. 10800 IN
+    SOA localhost. nobody.invalid. 1 3600 1200 604800 10800"
+.fi
+.TP 10
+\h'5'\fIservice.arpa (draft-ietf-dnssd-srp-25)\fR
+Default content:
+.nf
+local\-zone: "service.arpa." static
+local\-data: "service.arpa. 10800 IN NS localhost."
+local\-data: "service.arpa. 10800 IN
+    SOA localhost. nobody.invalid. 1 3600 1200 604800 10800"
+.fi
+.TP 10
 \h'5'\fIonion (RFC 7686)\fR
 Default content:
 .nf
@@ -1953,7 +1989,7 @@ Default is 11.
 Limit on the number of upstream queries sent out for an incoming query and
 its subqueries from recursion. It is not reset during the resolution. When
 it is exceeded the query is failed and the lookup process stops.
-Default is 128.
+Default is 200.
 .TP 5
 .B fast\-server\-permil: \fI<number>
 Specify how many times out of 1000 to pick from the set of fastest servers.
@@ -2008,17 +2044,30 @@ be used.  Default is 65001.
 .TP 5
 .B ede: \fI<yes or no>
 If enabled, Unbound will respond with Extended DNS Error codes (RFC8914).
-These EDEs attach informative error messages to a response for various
-errors. Default is "no".
+These EDEs provide additional information with a response mainly for, but not
+limited to, DNS and DNSSEC errors.
 
 When the \fBval-log-level\fR option is also set to \fB2\fR, responses with
-Extended DNS Errors concerning DNSSEC failures that are not served from cache,
-will also contain a descriptive text message about the reason for the failure.
+Extended DNS Errors concerning DNSSEC failures will also contain a descriptive
+text message about the reason for the failure.
+Default is "no".
 .TP 5
 .B ede\-serve\-expired: \fI<yes or no>
 If enabled, Unbound will attach an Extended DNS Error (RFC8914) Code 3 - Stale
-Answer as EDNS0 option to the expired response. Note that this will not attach
-the EDE code without setting the global \fBede\fR option to "yes" as well.
+Answer as EDNS0 option to the expired response.
+The \fBede\fR option needs to be enabled as well for this to work.
+Default is "no".
+.TP 5
+.B dns\-error\-reporting: \fI<yes or no>
+If enabled, Unbound will send DNS Error Reports (RFC9567).
+The name servers need to express support by attaching the Report-Channel EDNS0
+option on their replies specifying the reporting agent for the zone.
+Any errors encountered during resolution that would result in Unbound
+generating an Extended DNS Error (RFC8914) will be reported to the zone's
+reporting agent.
+The \fBede\fR option does not need to be enabled for this to work.
+It is advised that the \fBqname\-minimisation\fR option is also enabled to
+increase privacy on the outgoing reports.
 Default is "no".
 .SS "Remote Control Options"
 In the
@@ -2431,8 +2480,8 @@ The dynamic library file to load. Repeat
 instance added to the \fBmodule\-config:\fR option.
 .SS "DNS64 Module Options"
 .LP
-The dns64 module must be configured in the \fBmodule\-config:\fR "dns64
-validator iterator" directive and be compiled into the daemon to be
+The dns64 module must be configured in the \fBmodule\-config:\fR directive
+e.g., "dns64 validator iterator" and be compiled into the daemon to be
 enabled.  These settings go in the \fBserver:\fR section.
 .TP
 .B dns64\-prefix: \fI<IPv6 prefix>\fR
@@ -2532,8 +2581,8 @@ in the dnscrypt nonce cache.  Close to t
 a fairly good setting.
 .SS "EDNS Client Subnet Module Options"
 .LP
-The ECS module must be configured in the \fBmodule\-config:\fR "subnetcache
-validator iterator" directive and be compiled into the daemon to be
+The ECS module must be configured in the \fBmodule\-config:\fR directive e.g.,
+"subnetcache validator iterator" and be compiled into the daemon to be
 enabled.  These settings go in the \fBserver:\fR section.
 .LP
 If the destination address is allowed in the configuration Unbound will add the
@@ -2554,6 +2603,15 @@ configuration file. On top of that, for 
 are allowed to be stored for each address family. Exceeding that number, older
 entries will be purged from cache.
 .LP
+Note that due to the nature of how EDNS Client Subnet works, by segregating the
+client IP space in order to try and have tailored responses for prefixes of
+unknown sizes, resolution and cache response performance are impacted as a
+result.
+Usage of the subnetcache module should only be enabled in installations that
+require such functionality where the resolver and the clients belong to
+different networks.
+An example of that is an open resolver installation.
+.LP
 This module does not interact with the \fBserve\-expired*\fR and
 \fBprefetch:\fR options.
 .TP
@@ -2604,8 +2662,8 @@ Specifies the maximum number of subnets 
 This number applies for each qname/qclass/qtype tuple. Defaults to 100.
 .SS "Opportunistic IPsec Support Module Options"
 .LP
-The IPsec module must be configured in the \fBmodule\-config:\fR "ipsecmod
-validator iterator" directive and be compiled into Unbound by using
+The IPsec module must be configured in the \fBmodule\-config:\fR directive
+e.g., "ipsecmod validator iterator" and be compiled into Unbound by using
 \fB\-\-enable\-ipsecmod\fR to be enabled.
 These settings go in the \fBserver:\fR section.
 .LP
@@ -2670,12 +2728,12 @@ Allow the ipsecmod functionality for the
 executed.  Can be given multiple times, for different domains.  If the option is
 not specified, all domains are treated as being allowed (default).
 .TP
-.B ipsecmod\-whitelist: \fI<yes or no>
+.B ipsecmod\-whitelist: \fI<domain>
 Alternate syntax for \fBipsecmod\-allow\fR.
 .SS "Cache DB Module Options"
 .LP
-The Cache DB module must be configured in the \fBmodule\-config:\fR
-"validator cachedb iterator" directive and be compiled into the daemon
+The Cache DB module must be configured in the \fBmodule\-config:\fR directive
+e.g., "validator cachedb iterator" and be compiled into the daemon
 with \fB\-\-enable\-cachedb\fR.
 If this module is enabled and configured, the specified backend database
 works as a second level cache:
@@ -2770,12 +2828,12 @@ The TCP port number of the Redis server.
 This option defaults to 6379.
 .TP
 .B redis-server-path: \fI<unix socket path>\fR
-The unix socket path to connect to the redis server. Off by default, and it
+The unix socket path to connect to the Redis server. Off by default, and it
 can be set to "" to turn this off. Unix sockets may have better throughput
 than the IP address option.
 .TP
 .B redis-server-password: \fI"<password>"\fR
-The Redis AUTH password to use for the redis server.
+The Redis AUTH password to use for the Redis server.
 Only relevant if Redis is configured for client password authorisation.
 Off by default, and it can be set to "" to turn this off.
 .TP
@@ -2787,12 +2845,14 @@ re-establish a new connection later.
 This option defaults to 100 milliseconds.
 .TP
 .B redis-command-timeout: \fI<msec>\fR
-The timeout to use for redis commands, in milliseconds. If 0, it uses the
-redis\-timeout value. The default is 0.
+The timeout to use for Redis commands, in milliseconds.
+If 0, it uses the \fBredis\-timeout\fR value.
+The default is 0.
 .TP
 .B redis-connect-timeout: \fI<msec>\fR
-The timeout to use for redis connection set up, in milliseconds. If 0, it
-uses the redis\-timeout value. The default is 0.
+The timeout to use for Redis connection set up, in milliseconds.
+If 0, it uses the \fBredis\-timeout\fR value.
+The default is 0.
 .TP
 .B redis-expire-records: \fI<yes or no>
 If Redis record expiration is enabled.  If yes, Unbound sets timeout for Redis
@@ -2811,6 +2871,52 @@ If unsure about using this option, Redis
 for multiple unrelated applications.
 The default database in Redis is 0 while other logical databases need to be
 explicitly SELECT'ed upon connecting.
+This option defaults to 0.
+.TP
+.B redis-replica-server-host: \fI<server address or name>\fR
+The IP (either v6 or v4) address or domain name of the Redis replica server.
+In general an IP address should be specified as otherwise Unbound will have to
+resolve the name of the server every time it establishes a connection
+to the server.
+This server is treated as a read-only replica server
+(https://redis.io/docs/management/replication/#read-only-replica).
+If specified, all Redis read commands will go to this replica server, while
+the write commands will go to the \fBredis-server-host\fR.
+This option defaults to "" (disabled).
+.TP
+.B redis-replica-server-port: \fI<port number>\fR
+The TCP port number of the Redis replica server.
+This option defaults to 6379.
+.TP
+.B redis-replica-server-path: \fI<unix socket path>\fR
+The unix socket path to connect to the Redis server. Off by default, and it
+can be set to "" to turn this off. Unix sockets may have better throughput
+than the IP address option.
+.TP
+.B redis-replica-server-password: \fI"<password>"\fR
+The Redis AUTH password to use for the Redis replica server.
+Only relevant if Redis is configured for client password authorisation.
+Off by default, and it can be set to "" to turn this off.
+.TP
+.B redis-replica-timeout: \fI<msec>\fR
+The period until when Unbound waits for a response from the Redis replica sever.
+If this timeout expires Unbound closes the connection, treats it as
+if the Redis replica server does not have the requested data, and will try to
+re-establish a new connection later.
+This option defaults to 100 milliseconds.
+.TP
+.B redis-replica-command-timeout: \fI<msec>\fR
+The timeout to use for Redis replica commands, in milliseconds.
+If 0, it uses the \fBredis\-replica\-timeout\fR value.
+The default is 0.
+.TP
+.B redis-replica-connect-timeout: \fI<msec>\fR
+The timeout to use for Redis replica connection set up, in milliseconds.
+If 0, it uses the \fBredis\-replica\-timeout\fR value.
+The default is 0.
+.TP
+.B redis-replica-logical-db: \fI<logical database index>
+Same as \fBredis-logical-db\fR but for the Redis replica server.
 This option defaults to 0.
 .SS DNSTAP Logging Options
 DNSTAP support, when compiled in by using \fB\-\-enable\-dnstap\fR, is enabled
Index: edns-subnet/subnetmod.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/edns-subnet/subnetmod.c,v
diff -u -p -r1.17 subnetmod.c
--- edns-subnet/subnetmod.c	4 Sep 2024 09:36:40 -0000	1.17
+++ edns-subnet/subnetmod.c	30 Aug 2025 11:52:23 -0000
@@ -51,6 +51,7 @@
 #include "services/cache/dns.h"
 #include "util/module.h"
 #include "util/regional.h"
+#include "util/fptr_wlist.h"
 #include "util/storage/slabhash.h"
 #include "util/config_file.h"
 #include "util/data/msgreply.h"
@@ -155,7 +156,8 @@ int ecs_whitelist_check(struct query_inf
 
 	/* Cache by default, might be disabled after parsing EDNS option
 	 * received from nameserver. */
-	if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL, NULL, 0)) {
+	if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL, NULL, 0)
+		&& sq->ecs_client_in.subnet_validdata) {
 		qstate->no_cache_store = 0;
 	}
 
@@ -522,6 +524,69 @@ common_prefix(uint8_t *a, uint8_t *b, ui
 	return !memcmp(a, b, n) && ((net % 8) == 0 || a[n] == b[n]);
 }
 
+/**
+ * Create sub request that looks up the query.
+ * @param qstate: query state
+ * @param sq: subnet qstate
+ * @return false on failure.
+ */
+static int
+generate_sub_request(struct module_qstate *qstate, struct subnet_qstate* sq)
+{
+	struct module_qstate* subq = NULL;
+	uint16_t qflags = 0; /* OPCODE QUERY, no flags */
+	int prime = 0;
+	int valrec = 0;
+	struct query_info qinf;
+	qinf.qname = qstate->qinfo.qname;
+	qinf.qname_len = qstate->qinfo.qname_len;
+	qinf.qtype = qstate->qinfo.qtype;
+	qinf.qclass = qstate->qinfo.qclass;
+	qinf.local_alias = NULL;
+
+	qflags |= BIT_RD;
+	if((qstate->query_flags & BIT_CD)!=0) {
+		qflags |= BIT_CD;
+		valrec = 1;
+	}
+
+	fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub));
+	if(!(*qstate->env->attach_sub)(qstate, &qinf, qflags, prime, valrec,
+		&subq)) {
+		return 0;
+	}
+	if(subq) {
+		/* It is possible to access the subquery module state. */
+		if(sq->ecs_client_in.subnet_source_mask == 0 &&
+			edns_opt_list_find(qstate->edns_opts_front_in,
+				qstate->env->cfg->client_subnet_opcode)) {
+			subq->no_cache_store = 1;
+		}
+	}
+	return 1;
+}
+
+/**
+ * Perform the query without subnet
+ * @param qstate: query state
+ * @param sq: subnet qstate
+ * @return module state
+ */
+static enum module_ext_state
+generate_lookup_without_subnet(struct module_qstate *qstate,
+	struct subnet_qstate* sq)
+{
+	verbose(VERB_ALGO, "subnetcache: make subquery to look up without subnet");
+	if(!generate_sub_request(qstate, sq)) {
+		verbose(VERB_ALGO, "Could not generate sub query");
+		qstate->return_rcode = LDNS_RCODE_FORMERR;
+		qstate->return_msg = NULL;
+		return module_finished;
+	}
+	sq->wait_subquery = 1;
+	return module_wait_subquery;
+}
+
 static enum module_ext_state
 eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
 {
@@ -557,14 +622,7 @@ eval_response(struct module_qstate *qsta
 		 * is still useful to put it in the edns subnet cache for
 		 * when a client explicitly asks for subnet specific answer. */
 		verbose(VERB_QUERY, "subnetcache: Authority indicates no support");
-		if(!sq->started_no_cache_store) {
-			lock_rw_wrlock(&sne->biglock);
-			update_cache(qstate, id);
-			lock_rw_unlock(&sne->biglock);
-		}
-		if (sq->subnet_downstream)
-			cp_edns_bad_response(c_out, c_in);
-		return module_finished;
+		return generate_lookup_without_subnet(qstate, sq);
 	}
 
 	/* Purposefully there was no sent subnet, and there is consequently
@@ -589,14 +647,14 @@ eval_response(struct module_qstate *qsta
 		!common_prefix(s_out->subnet_addr, s_in->subnet_addr, 
 			s_out->subnet_source_mask))
 	{
-		/* we can not accept, restart query without option */
+		/* we can not accept, perform query without option */
 		verbose(VERB_QUERY, "subnetcache: forged data");
 		s_out->subnet_validdata = 0;
 		(void)edns_opt_list_remove(&qstate->edns_opts_back_out,
 			qstate->env->cfg->client_subnet_opcode);
 		sq->subnet_sent = 0;
 		sq->subnet_sent_no_subnet = 0;
-		return module_restart_next;
+		return generate_lookup_without_subnet(qstate, sq);
 	}
 
 	lock_rw_wrlock(&sne->biglock);
@@ -795,6 +853,9 @@ ecs_edns_back_parsed(struct module_qstat
 	} else if(sq->subnet_sent_no_subnet) {
 		/* The answer can be stored as scope 0, not in global cache. */
 		qstate->no_cache_store = 1;
+	} else if(sq->subnet_sent) {
+		/* Need another query to be able to store in global cache. */
+		qstate->no_cache_store = 1;
 	}
 
 	return 1;
@@ -812,6 +873,32 @@ subnetmod_operate(struct module_qstate *
 		strmodulevent(event));
 	log_query_info(VERB_QUERY, "subnetcache operate: query", &qstate->qinfo);
 
+	if(sq && sq->wait_subquery_done) {
+		/* The subquery lookup returned. */
+		if(sq->ecs_client_in.subnet_source_mask == 0 &&
+			edns_opt_list_find(qstate->edns_opts_front_in,
+				qstate->env->cfg->client_subnet_opcode)) {
+			if(!sq->started_no_cache_store &&
+				qstate->return_msg) {
+				lock_rw_wrlock(&sne->biglock);
+				update_cache(qstate, id);
+				lock_rw_unlock(&sne->biglock);
+			}
+			if (sq->subnet_downstream)
+				cp_edns_bad_response(&sq->ecs_client_out,
+					&sq->ecs_client_in);
+			/* It is a scope zero lookup, append edns subnet
+			 * option to the querier. */
+			subnet_ecs_opt_list_append(&sq->ecs_client_out,
+				&qstate->edns_opts_front_out, qstate,
+				qstate->region);
+		}
+		sq->wait_subquery_done = 0;
+		qstate->ext_state[id] = module_finished;
+		qstate->no_cache_store = sq->started_no_cache_store;
+		qstate->no_cache_lookup = sq->started_no_cache_lookup;
+		return;
+	}
 	if((event == module_event_new || event == module_event_pass) &&
 		sq == NULL) {
 		struct edns_option* ecs_opt;
@@ -822,6 +909,8 @@ subnetmod_operate(struct module_qstate *
 		}
 
 		sq = (struct subnet_qstate*)qstate->minfo[id];
+		if(sq->wait_subquery)
+			return; /* Wait for that subquery to return */
 
 		if((ecs_opt = edns_opt_list_find(
 			qstate->edns_opts_front_in,
@@ -851,6 +940,14 @@ subnetmod_operate(struct module_qstate *
 			/* No clients are interested in result or we could not
 			 * parse it, we don't do client subnet */
 			sq->ecs_server_out.subnet_validdata = 0;
+			if(edns_opt_list_find(qstate->edns_opts_front_in,
+				qstate->env->cfg->client_subnet_opcode)) {
+				/* aggregated this deaggregated state */
+				qstate->ext_state[id] =
+					generate_lookup_without_subnet(
+					qstate, sq);
+				return;
+			}
 			verbose(VERB_ALGO, "subnetcache: pass to next module");
 			qstate->ext_state[id] = module_wait_module;
 			return;
@@ -891,6 +988,14 @@ subnetmod_operate(struct module_qstate *
 			}
 			lock_rw_unlock(&sne->biglock);
 		}
+		if(sq->ecs_client_in.subnet_source_mask == 0 &&
+			edns_opt_list_find(qstate->edns_opts_front_in,
+				qstate->env->cfg->client_subnet_opcode)) {
+			/* client asked for resolution without edns subnet */
+			qstate->ext_state[id] = generate_lookup_without_subnet(
+				qstate, sq);
+			return;
+		}
 		
 		sq->ecs_server_out.subnet_addr_fam =
 			sq->ecs_client_in.subnet_addr_fam;
@@ -927,6 +1032,8 @@ subnetmod_operate(struct module_qstate *
 		qstate->ext_state[id] = module_wait_module;
 		return;
 	}
+	if(sq && sq->wait_subquery)
+		return; /* Wait for that subquery to return */
 	/* Query handed back by next module, we have a 'final' answer */
 	if(sq && event == module_event_moddone) {
 		qstate->ext_state[id] = eval_response(qstate, id, sq);
@@ -975,10 +1082,27 @@ subnetmod_clear(struct module_qstate *AT
 }
 
 void
-subnetmod_inform_super(struct module_qstate *ATTR_UNUSED(qstate),
-	int ATTR_UNUSED(id), struct module_qstate *ATTR_UNUSED(super))
+subnetmod_inform_super(struct module_qstate *qstate, int id,
+	struct module_qstate *super)
 {
-	/* Not used */
+	struct subnet_qstate* super_sq =
+		(struct subnet_qstate*)super->minfo[id];
+	log_query_info(VERB_ALGO, "subnetcache inform_super: query",
+		&super->qinfo);
+	super_sq->wait_subquery = 0;
+	super_sq->wait_subquery_done = 1;
+	if(qstate->return_rcode != LDNS_RCODE_NOERROR ||
+		!qstate->return_msg) {
+		super->return_msg = NULL;
+		super->return_rcode = LDNS_RCODE_SERVFAIL;
+		return;
+	}
+	super->return_rcode = LDNS_RCODE_NOERROR;
+	super->return_msg = dns_copy_msg(qstate->return_msg, super->region);
+	if(!super->return_msg) {
+		log_err("subnetcache: copy response, out of memory");
+		super->return_rcode = LDNS_RCODE_SERVFAIL;
+	}
 }
 
 size_t
Index: edns-subnet/subnetmod.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/edns-subnet/subnetmod.h,v
diff -u -p -r1.8 subnetmod.h
--- edns-subnet/subnetmod.h	12 Apr 2024 15:45:24 -0000	1.8
+++ edns-subnet/subnetmod.h	30 Aug 2025 11:52:23 -0000
@@ -102,6 +102,10 @@ struct subnet_qstate {
 	int started_no_cache_store;
 	/** has the subnet module been started with no_cache_lookup? */
 	int started_no_cache_lookup;
+	/** Wait for subquery that has been started for nonsubnet lookup. */
+	int wait_subquery;
+	/** The subquery waited for is done. */
+	int wait_subquery_done;
 };
 
 void subnet_data_delete(void* d, void* ATTR_UNUSED(arg));
Index: ipsecmod/ipsecmod.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/ipsecmod/ipsecmod.c,v
diff -u -p -r1.7 ipsecmod.c
--- ipsecmod/ipsecmod.c	4 Sep 2024 09:36:40 -0000	1.7
+++ ipsecmod/ipsecmod.c	30 Aug 2025 11:52:23 -0000
@@ -456,7 +456,8 @@ ipsecmod_handle_query(struct module_qsta
 	/* Store A/AAAA in cache. */
 	if(!dns_cache_store(qstate->env, &qstate->qinfo,
 		qstate->return_msg->rep, 0, qstate->prefetch_leeway,
-		0, qstate->region, qstate->query_flags, qstate->qstarttime)) {
+		0, qstate->region, qstate->query_flags, qstate->qstarttime,
+		qstate->is_valrec)) {
 		log_err("ipsecmod: out of memory caching record");
 	}
 	qstate->ext_state[id] = module_finished;
Index: iterator/iter_delegpt.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_delegpt.c,v
diff -u -p -r1.8 iter_delegpt.c
--- iterator/iter_delegpt.c	5 Sep 2023 11:12:10 -0000	1.8
+++ iterator/iter_delegpt.c	30 Aug 2025 11:52:23 -0000
@@ -278,7 +278,7 @@ delegpt_count_addr(struct delegpt* dp, s
 
 void delegpt_log(enum verbosity_value v, struct delegpt* dp)
 {
-	char buf[LDNS_MAX_DOMAINLEN+1];
+	char buf[LDNS_MAX_DOMAINLEN];
 	struct delegpt_ns* ns;
 	struct delegpt_addr* a;
 	size_t missing=0, numns=0, numaddr=0, numres=0, numavail=0;
Index: iterator/iter_fwd.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_fwd.c,v
diff -u -p -r1.9 iter_fwd.c
--- iterator/iter_fwd.c	13 Jun 2024 14:30:28 -0000	1.9
+++ iterator/iter_fwd.c	30 Aug 2025 11:52:23 -0000
@@ -129,7 +129,7 @@ forwards_insert_data(struct iter_forward
 	node->namelabs = nmlabs;
 	node->dp = dp;
 	if(!rbtree_insert(fwd->tree, &node->node)) {
-		char buf[257];
+		char buf[LDNS_MAX_DOMAINLEN];
 		dname_str(nm, buf);
 		log_err("duplicate forward zone %s ignored.", buf);
 		delegpt_free_mlc(dp);
@@ -331,6 +331,30 @@ make_stub_holes(struct iter_forwards* fw
 	return 1;
 }
 
+/** make NULL entries for auths */
+static int
+make_auth_holes(struct iter_forwards* fwd, struct config_file* cfg)
+{
+	struct config_auth* a;
+	uint8_t* dname;
+	size_t dname_len;
+	for(a = cfg->auths; a; a = a->next) {
+		if(!a->name) continue;
+		dname = sldns_str2wire_dname(a->name, &dname_len);
+		if(!dname) {
+			log_err("cannot parse auth name '%s'", a->name);
+			return 0;
+		}
+		if(!fwd_add_stub_hole(fwd, LDNS_RR_CLASS_IN, dname)) {
+			free(dname);
+			log_err("out of memory");
+			return 0;
+		}
+		free(dname);
+	}
+	return 1;
+}
+
 int 
 forwards_apply_cfg(struct iter_forwards* fwd, struct config_file* cfg)
 {
@@ -353,6 +377,16 @@ forwards_apply_cfg(struct iter_forwards*
 		lock_rw_unlock(&fwd->lock);
 		return 0;
 	}
+	/* TODO: Now we punch holes for auth zones as well so that in
+	 *       iterator:forward_request() we see the configured
+	 *       delegation point, but code flow/naming is hard to follow.
+	 *       Consider having a single tree with configured
+	 *       delegation points for all categories
+	 *       (stubs, forwards, auths). */
+	if(!make_auth_holes(fwd, cfg)) {
+		lock_rw_unlock(&fwd->lock);
+		return 0;
+	}
 	fwd_init_parents(fwd);
 	lock_rw_unlock(&fwd->lock);
 	return 1;
@@ -589,4 +623,20 @@ forwards_delete_stub_hole(struct iter_fo
 	fwd_zone_free(z);
 	fwd_init_parents(fwd);
 	if(!nolock) { lock_rw_unlock(&fwd->lock); }
+}
+
+void
+forwards_swap_tree(struct iter_forwards* fwd, struct iter_forwards* data)
+{
+	rbtree_type* oldtree = fwd->tree;
+	if(oldtree) {
+		lock_unprotect(&fwd->lock, oldtree);
+	}
+	if(data->tree) {
+		lock_unprotect(&data->lock, data->tree);
+	}
+	fwd->tree = data->tree;
+	data->tree = oldtree;
+	lock_protect(&fwd->lock, fwd->tree, sizeof(*fwd->tree));
+	lock_protect(&data->lock, data->tree, sizeof(*data->tree));
 }
Index: iterator/iter_fwd.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_fwd.h,v
diff -u -p -r1.3 iter_fwd.h
--- iterator/iter_fwd.h	13 Jun 2024 14:30:28 -0000	1.3
+++ iterator/iter_fwd.h	30 Aug 2025 11:52:23 -0000
@@ -234,4 +234,13 @@ int forwards_add_stub_hole(struct iter_f
 void forwards_delete_stub_hole(struct iter_forwards* fwd, uint16_t c,
 	uint8_t* nm, int nolock);
 
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param fwd: the forward data structure.
+ * @param data: the data structure used to take elements from. This contains
+ * 	the old elements on return.
+ */
+void forwards_swap_tree(struct iter_forwards* fwd, struct iter_forwards* data);
+
 #endif /* ITERATOR_ITER_FWD_H */
Index: iterator/iter_hints.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_hints.c,v
diff -u -p -r1.18 iter_hints.c
--- iterator/iter_hints.c	13 Jun 2024 14:30:28 -0000	1.18
+++ iterator/iter_hints.c	30 Aug 2025 11:52:23 -0000
@@ -181,7 +181,7 @@ hints_insert(struct iter_hints* hints, u
 	node->noprime = (uint8_t)noprime;
 	if(!name_tree_insert(&hints->tree, &node->node, dp->name, dp->namelen,
 		dp->namelabs, c)) {
-		char buf[257];
+		char buf[LDNS_MAX_DOMAINLEN];
 		dname_str(dp->name, buf);
 		log_err("second hints for zone %s ignored.", buf);
 		delegpt_free_mlc(dp);
@@ -610,4 +610,15 @@ hints_delete_stub(struct iter_hints* hin
 	hints_stub_free(z);
 	name_tree_init_parents(&hints->tree);
 	if(!nolock) { lock_rw_unlock(&hints->lock); }
+}
+
+void
+hints_swap_tree(struct iter_hints* hints, struct iter_hints* data)
+{
+	rbnode_type* oldroot = hints->tree.root;
+	size_t oldcount = hints->tree.count;
+	hints->tree.root = data->tree.root;
+	hints->tree.count = data->tree.count;
+	data->tree.root = oldroot;
+	data->tree.count = oldcount;
 }
Index: iterator/iter_hints.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_hints.h,v
diff -u -p -r1.3 iter_hints.h
--- iterator/iter_hints.h	13 Jun 2024 14:30:28 -0000	1.3
+++ iterator/iter_hints.h	30 Aug 2025 11:52:23 -0000
@@ -198,4 +198,13 @@ int hints_add_stub(struct iter_hints* hi
 void hints_delete_stub(struct iter_hints* hints, uint16_t c,
 	uint8_t* nm, int nolock);
 
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param hints: the hints data structure.
+ * @param data: the data structure used to take elements from. This contains
+ * 	the old elements on return.
+ */
+void hints_swap_tree(struct iter_hints* hints, struct iter_hints* data);
+
 #endif /* ITERATOR_ITER_HINTS_H */
Index: iterator/iter_utils.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_utils.c,v
diff -u -p -r1.24 iter_utils.c
--- iterator/iter_utils.c	21 Feb 2025 13:20:40 -0000	1.24
+++ iterator/iter_utils.c	30 Aug 2025 11:52:23 -0000
@@ -77,41 +77,73 @@
 static const char DEFAULT_NAT64_PREFIX[] = "64:ff9b::/96";
 
 /** fillup fetch policy array */
-static void
-fetch_fill(struct iter_env* ie, const char* str)
+static int
+fetch_fill(int* target_fetch_policy, int max_dependency_depth, const char* str)
 {
 	char* s = (char*)str, *e;
 	int i;
-	for(i=0; i<ie->max_dependency_depth+1; i++) {
-		ie->target_fetch_policy[i] = strtol(s, &e, 10);
-		if(s == e)
-			fatal_exit("cannot parse fetch policy number %s", s);
+	for(i=0; i<max_dependency_depth+1; i++) {
+		target_fetch_policy[i] = strtol(s, &e, 10);
+		if(s == e) {
+			log_err("cannot parse fetch policy number %s", s);
+			return 0;
+		}
 		s = e;
 	}
+	return 1;
 }
 
 /** Read config string that represents the target fetch policy */
-static int
-read_fetch_policy(struct iter_env* ie, const char* str)
+int
+read_fetch_policy(int** target_fetch_policy, int* max_dependency_depth,
+	const char* str)
 {
 	int count = cfg_count_numbers(str);
 	if(count < 1) {
 		log_err("Cannot parse target fetch policy: \"%s\"", str);
 		return 0;
 	}
-	ie->max_dependency_depth = count - 1;
-	ie->target_fetch_policy = (int*)calloc(
-		(size_t)ie->max_dependency_depth+1, sizeof(int));
-	if(!ie->target_fetch_policy) {
+	*max_dependency_depth = count - 1;
+	*target_fetch_policy = (int*)calloc(
+		(size_t)(*max_dependency_depth)+1, sizeof(int));
+	if(!*target_fetch_policy) {
 		log_err("alloc fetch policy: out of memory");
 		return 0;
 	}
-	fetch_fill(ie, str);
+	if(!fetch_fill(*target_fetch_policy, *max_dependency_depth, str))
+		return 0;
 	return 1;
 }
 
-/** apply config caps whitelist items to name tree */
-static int
+struct rbtree_type*
+caps_white_create(void)
+{
+	struct rbtree_type* caps_white = rbtree_create(name_tree_compare);
+	if(!caps_white)
+		log_err("out of memory");
+	return caps_white;
+}
+
+/** delete caps_whitelist element */
+static void
+caps_free(struct rbnode_type* n, void* ATTR_UNUSED(d))
+{
+	if(n) {
+		free(((struct name_tree_node*)n)->name);
+		free(n);
+	}
+}
+
+void
+caps_white_delete(struct rbtree_type* caps_white)
+{
+	if(!caps_white)
+		return;
+	traverse_postorder(caps_white, caps_free, NULL);
+	free(caps_white);
+}
+
+int
 caps_white_apply_cfg(rbtree_type* ntree, struct config_file* cfg)
 {
 	struct config_strlist* p;
@@ -145,12 +177,41 @@ caps_white_apply_cfg(rbtree_type* ntree,
 }
 
 int
-iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
+nat64_apply_cfg(struct iter_nat64* nat64, struct config_file* cfg)
 {
 	const char *nat64_prefix;
+
+	nat64_prefix = cfg->nat64_prefix;
+	if(!nat64_prefix)
+		nat64_prefix = cfg->dns64_prefix;
+	if(!nat64_prefix)
+		nat64_prefix = DEFAULT_NAT64_PREFIX;
+	if(!netblockstrtoaddr(nat64_prefix, 0, &nat64->nat64_prefix_addr,
+		&nat64->nat64_prefix_addrlen, &nat64->nat64_prefix_net)) {
+		log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix);
+		return 0;
+	}
+	if(!addr_is_ip6(&nat64->nat64_prefix_addr,
+		nat64->nat64_prefix_addrlen)) {
+		log_err("nat64-prefix is not IPv6: %s", cfg->nat64_prefix);
+		return 0;
+	}
+	if(!prefixnet_is_nat64(nat64->nat64_prefix_net)) {
+		log_err("nat64-prefix length it not 32, 40, 48, 56, 64 or 96: %s",
+			nat64_prefix);
+		return 0;
+	}
+	nat64->use_nat64 = cfg->do_nat64;
+	return 1;
+}
+
+int
+iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
+{
 	int i;
 	/* target fetch policy */
-	if(!read_fetch_policy(iter_env, cfg->target_fetch_policy))
+	if(!read_fetch_policy(&iter_env->target_fetch_policy,
+		&iter_env->max_dependency_depth, cfg->target_fetch_policy))
 		return 0;
 	for(i=0; i<iter_env->max_dependency_depth+1; i++)
 		verbose(VERB_QUERY, "target fetch policy for level %d is %d",
@@ -170,7 +231,7 @@ iter_apply_cfg(struct iter_env* iter_env
 	}
 	if(cfg->caps_whitelist) {
 		if(!iter_env->caps_white)
-			iter_env->caps_white = rbtree_create(name_tree_compare);
+			iter_env->caps_white = caps_white_create();
 		if(!iter_env->caps_white || !caps_white_apply_cfg(
 			iter_env->caps_white, cfg)) {
 			log_err("Could not set capsforid whitelist");
@@ -179,31 +240,13 @@ iter_apply_cfg(struct iter_env* iter_env
 
 	}
 
-	nat64_prefix = cfg->nat64_prefix;
-	if(!nat64_prefix)
-		nat64_prefix = cfg->dns64_prefix;
-	if(!nat64_prefix)
-		nat64_prefix = DEFAULT_NAT64_PREFIX;
-	if(!netblockstrtoaddr(nat64_prefix, 0, &iter_env->nat64_prefix_addr,
-		&iter_env->nat64_prefix_addrlen,
-		&iter_env->nat64_prefix_net)) {
-		log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix);
-		return 0;
-	}
-	if(!addr_is_ip6(&iter_env->nat64_prefix_addr,
-		iter_env->nat64_prefix_addrlen)) {
-		log_err("nat64-prefix is not IPv6: %s", cfg->nat64_prefix);
-		return 0;
-	}
-	if(!prefixnet_is_nat64(iter_env->nat64_prefix_net)) {
-		log_err("nat64-prefix length it not 32, 40, 48, 56, 64 or 96: %s",
-			nat64_prefix);
+	if(!nat64_apply_cfg(&iter_env->nat64, cfg)) {
+		log_err("Could not setup nat64");
 		return 0;
 	}
 
 	iter_env->supports_ipv6 = cfg->do_ip6;
 	iter_env->supports_ipv4 = cfg->do_ip4;
-	iter_env->use_nat64 = cfg->do_nat64;
 	iter_env->outbound_msg_retry = cfg->outbound_msg_retry;
 	iter_env->max_sent_count = cfg->max_sent_count;
 	iter_env->max_query_restarts = cfg->max_query_restarts;
@@ -270,7 +313,7 @@ iter_filter_unsuitable(struct iter_env* 
 	if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr, a->addrlen)) {
 		return -1; /* there is no ip6 available */
 	}
-	if(!iter_env->supports_ipv4 && !iter_env->use_nat64 &&
+	if(!iter_env->supports_ipv4 && !iter_env->nat64.use_nat64 &&
 	   !addr_is_ip6(&a->addr, a->addrlen)) {
 		return -1; /* there is no ip4 available */
 	}
@@ -693,10 +736,11 @@ dns_copy_msg(struct dns_msg* from, struc
 void
 iter_dns_store(struct module_env* env, struct query_info* msgqinf,
 	struct reply_info* msgrep, int is_referral, time_t leeway, int pside,
-	struct regional* region, uint16_t flags, time_t qstarttime)
+	struct regional* region, uint16_t flags, time_t qstarttime,
+	int is_valrec)
 {
 	if(!dns_cache_store(env, msgqinf, msgrep, is_referral, leeway,
-		pside, region, flags, qstarttime))
+		pside, region, flags, qstarttime, is_valrec))
 		log_err("out of memory: cannot store data in cache");
 }
 
@@ -1488,14 +1532,15 @@ iter_stub_fwd_no_cache(struct module_qst
 
 	/* check stub */
 	if (stub != NULL && stub->dp != NULL) {
+		enum verbosity_value level = VERB_ALGO;
 		int stub_no_cache = stub->dp->no_cache;
 		lock_rw_unlock(&qstate->env->fwds->lock);
-		if(stub_no_cache) {
-			char qname[255+1];
-			char dpname[255+1];
+		if(verbosity >= level && stub_no_cache) {
+			char qname[LDNS_MAX_DOMAINLEN];
+			char dpname[LDNS_MAX_DOMAINLEN];
 			dname_str(qinf->qname, qname);
 			dname_str(stub->dp->name, dpname);
-			verbose(VERB_ALGO, "stub for %s %s has no_cache", qname, dpname);
+			verbose(level, "stub for %s %s has no_cache", qname, dpname);
 		}
 		if(retdpname) {
 			if(stub->dp->namelen > dpname_storage_len) {
@@ -1516,14 +1561,15 @@ iter_stub_fwd_no_cache(struct module_qst
 
 	/* Check for forward. */
 	if (dp) {
+		enum verbosity_value level = VERB_ALGO;
 		int dp_no_cache = dp->no_cache;
 		lock_rw_unlock(&qstate->env->hints->lock);
-		if(dp_no_cache) {
-			char qname[255+1];
-			char dpname[255+1];
+		if(verbosity >= level && dp_no_cache) {
+			char qname[LDNS_MAX_DOMAINLEN];
+			char dpname[LDNS_MAX_DOMAINLEN];
 			dname_str(qinf->qname, qname);
 			dname_str(dp->name, dpname);
-			verbose(VERB_ALGO, "forward for %s %s has no_cache", qname, dpname);
+			verbose(level, "forward for %s %s has no_cache", qname, dpname);
 		}
 		if(retdpname) {
 			if(dp->namelen > dpname_storage_len) {
@@ -1605,4 +1651,13 @@ limit_nsec_ttl(struct dns_msg* msg)
 			}
 		}
 	}
+}
+
+void
+iter_make_minimal(struct reply_info* rep)
+{
+	size_t rem = rep->ns_numrrsets + rep->ar_numrrsets;
+	rep->ns_numrrsets = 0;
+	rep->ar_numrrsets = 0;
+	rep->rrset_count -= rem;
 }
Index: iterator/iter_utils.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_utils.h,v
diff -u -p -r1.17 iter_utils.h
--- iterator/iter_utils.h	21 Feb 2025 13:20:40 -0000	1.17
+++ iterator/iter_utils.h	30 Aug 2025 11:52:23 -0000
@@ -61,6 +61,7 @@ struct sock_list;
 struct ub_packed_rrset_key;
 struct module_stack;
 struct outside_network;
+struct iter_nat64;
 
 /* max number of lookups in the cache for target nameserver names.
  * This stops, for large delegations, N*N lookups in the cache. */
@@ -142,6 +143,7 @@ struct dns_msg* dns_copy_msg(struct dns_
  * @param region: to copy modified (cache is better) rrs back to.
  * @param flags: with BIT_CD for dns64 AAAA translated queries.
  * @param qstarttime: time of query start.
+ * @param is_valrec: if the query is validation recursion and does not get
  * return void, because we are not interested in alloc errors,
  * 	the iterator and validator can operate on the results in their
  * 	scratch space (the qstate.region) and are not dependent on the cache.
@@ -150,7 +152,8 @@ struct dns_msg* dns_copy_msg(struct dns_
  */
 void iter_dns_store(struct module_env* env, struct query_info* qinf,
 	struct reply_info* rep, int is_referral, time_t leeway, int pside,
-	struct regional* region, uint16_t flags, time_t qstarttime);
+	struct regional* region, uint16_t flags, time_t qstarttime,
+	int is_valrec);
 
 /**
  * Select randomly with n/m probability.
@@ -429,10 +432,54 @@ void iterator_set_ip46_support(struct mo
 	struct module_env* env, struct outside_network* outnet);
 
 /**
+ * Read config string that represents the target fetch policy.
+ * @param target_fetch_policy: alloced on return.
+ * @param max_dependency_depth: set on return.
+ * @param str: the config string
+ * @return false on failure.
+ */
+int read_fetch_policy(int** target_fetch_policy, int* max_dependency_depth,
+	const char* str);
+
+/**
+ * Create caps exempt data structure.
+ * @return NULL on failure.
+ */
+struct rbtree_type* caps_white_create(void);
+
+/**
+ * Delete caps exempt data structure.
+ * @param caps_white: caps exempt tree.
+ */
+void caps_white_delete(struct rbtree_type* caps_white);
+
+/**
+ * Apply config caps whitelist items to name tree
+ * @param ntree: caps exempt tree.
+ * @param cfg: config with options.
+ */
+int caps_white_apply_cfg(struct rbtree_type* ntree, struct config_file* cfg);
+
+/**
+ * Apply config for nat64
+ * @param nat64: the nat64 state.
+ * @param cfg: config with options.
+ * @return false on failure.
+ */
+int nat64_apply_cfg(struct iter_nat64* nat64, struct config_file* cfg);
+
+/**
  * Limit NSEC and NSEC3 TTL in response, RFC9077
  * @param msg: dns message, the SOA record ttl is used to restrict ttls
  *	of NSEC and NSEC3 RRsets. If no SOA record, nothing happens.
  */
 void limit_nsec_ttl(struct dns_msg* msg);
+
+/**
+ * Make the response minimal. Removed authority and additional section,
+ * that works when there is an answer in the answer section.
+ * @param rep: reply to modify.
+ */
+void iter_make_minimal(struct reply_info* rep);
 
 #endif /* ITERATOR_ITER_UTILS_H */
Index: iterator/iterator.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iterator.c,v
diff -u -p -r1.39 iterator.c
--- iterator/iterator.c	21 Feb 2025 13:20:40 -0000	1.39
+++ iterator/iterator.c	30 Aug 2025 11:52:23 -0000
@@ -71,13 +71,15 @@
 #include "sldns/sbuffer.h"
 
 /* number of packets */
-int MAX_GLOBAL_QUOTA = 128;
+int MAX_GLOBAL_QUOTA = 200;
 /* in msec */
 int UNKNOWN_SERVER_NICENESS = 376;
 /* in msec */
 int USEFUL_SERVER_TOP_TIMEOUT = 120000;
 /* Equals USEFUL_SERVER_TOP_TIMEOUT*4 */
 int BLACKLIST_PENALTY = (120000*4);
+/** Timeout when only a single probe query per IP is allowed. */
+int PROBE_MAXRTO = PROBE_MAXRTO_DEFAULT; /* in msec */
 
 static void target_count_increase_nx(struct iter_qstate* iq, int num);
 
@@ -105,16 +107,6 @@ iter_init(struct module_env* env, int id
 	return 1;
 }
 
-/** delete caps_whitelist element */
-static void
-caps_free(struct rbnode_type* n, void* ATTR_UNUSED(d))
-{
-	if(n) {
-		free(((struct name_tree_node*)n)->name);
-		free(n);
-	}
-}
-
 void 
 iter_deinit(struct module_env* env, int id)
 {
@@ -126,10 +118,7 @@ iter_deinit(struct module_env* env, int 
 	free(iter_env->target_fetch_policy);
 	priv_delete(iter_env->priv);
 	donotq_delete(iter_env->donotq);
-	if(iter_env->caps_white) {
-		traverse_postorder(iter_env->caps_white, caps_free, NULL);
-		free(iter_env->caps_white);
-	}
+	caps_white_delete(iter_env->caps_white);
 	free(iter_env);
 	env->modinfo[id] = NULL;
 }
@@ -258,7 +247,7 @@ error_supers(struct module_qstate* qstat
 				log_err("out of memory adding missing");
 		}
 		delegpt_mark_neg(dpns, qstate->qinfo.qtype);
-		if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
+		if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->nat64.use_nat64)) &&
 			(dpns->got6 == 2 || !ie->supports_ipv6)) {
 			dpns->resolved = 1; /* mark as failed */
 			target_count_increase_nx(super_iq, 1);
@@ -368,7 +357,7 @@ error_response_cache(struct module_qstat
 	err.security = sec_status_indeterminate;
 	verbose(VERB_ALGO, "store error response in message cache");
 	iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0, 0, NULL,
-		qstate->query_flags, qstate->qstarttime);
+		qstate->query_flags, qstate->qstarttime, qstate->is_valrec);
 	return error_response(qstate, id, rcode);
 }
 
@@ -1092,7 +1081,7 @@ auth_zone_delegpt(struct module_qstate* 
 			/* cache is blacklisted and fallback, and we
 			 * already have an auth_zone dp */
 			if(verbosity>=VERB_ALGO) {
-				char buf[255+1];
+				char buf[LDNS_MAX_DOMAINLEN];
 				dname_str(z->name, buf);
 				verbose(VERB_ALGO, "auth_zone %s "
 				  "fallback because cache blacklisted",
@@ -1109,7 +1098,7 @@ auth_zone_delegpt(struct module_qstate* 
 				 * validation failure, and the zone allows
 				 * fallback to the internet, query there. */
 				if(verbosity>=VERB_ALGO) {
-					char buf[255+1];
+					char buf[LDNS_MAX_DOMAINLEN];
 					dname_str(z->name, buf);
 					verbose(VERB_ALGO, "auth_zone %s "
 					  "fallback because cache blacklisted",
@@ -1723,7 +1712,7 @@ processInitRequest(struct module_qstate*
 		 */
 		if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags,
 			iq->dp, ie->supports_ipv4, ie->supports_ipv6,
-			ie->use_nat64)) {
+			ie->nat64.use_nat64)) {
 			int have_dp = 0;
 			if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, &have_dp, &iq->dp, qstate->region)) {
 				if(have_dp) {
@@ -2033,7 +2022,7 @@ query_for_targets(struct module_qstate* 
 		return 1;
 	if(iq->depth > 0 && iq->target_count &&
 		iq->target_count[TARGET_COUNT_QUERIES] > MAX_TARGET_COUNT) {
-		char s[LDNS_MAX_DOMAINLEN+1];
+		char s[LDNS_MAX_DOMAINLEN];
 		dname_str(qstate->qinfo.qname, s);
 		verbose(VERB_QUERY, "request %s has exceeded the maximum "
 			"number of glue fetches %d", s,
@@ -2041,7 +2030,7 @@ query_for_targets(struct module_qstate* 
 		return 2;
 	}
 	if(iq->dp_target_count > MAX_DP_TARGET_COUNT) {
-		char s[LDNS_MAX_DOMAINLEN+1];
+		char s[LDNS_MAX_DOMAINLEN];
 		dname_str(qstate->qinfo.qname, s);
 		verbose(VERB_QUERY, "request %s has exceeded the maximum "
 			"number of glue fetches %d to a single delegation point",
@@ -2087,7 +2076,7 @@ query_for_targets(struct module_qstate* 
 			if(mesh_jostle_exceeded(qstate->env->mesh)) {
 				/* If no ip4 query is possible, that makes
 				 * this ns resolved. */
-				if(!((ie->supports_ipv4 || ie->use_nat64) &&
+				if(!((ie->supports_ipv4 || ie->nat64.use_nat64) &&
 					((ns->lame && !ns->done_pside4) ||
 					(!ns->lame && !ns->got4)))) {
 					ns->resolved = 1;
@@ -2096,7 +2085,7 @@ query_for_targets(struct module_qstate* 
 		}
 		}
 		/* Send the A request. */
-		if((ie->supports_ipv4 || ie->use_nat64) &&
+		if((ie->supports_ipv4 || ie->nat64.use_nat64) &&
 			((ns->lame && !ns->done_pside4) ||
 			(!ns->lame && !ns->got4))) {
 			if(!generate_target_query(qstate, iq, id, 
@@ -2252,7 +2241,7 @@ processLastResort(struct module_qstate* 
 	}
 	if(iq->depth > 0 && iq->target_count &&
 		iq->target_count[TARGET_COUNT_QUERIES] > MAX_TARGET_COUNT) {
-		char s[LDNS_MAX_DOMAINLEN+1];
+		char s[LDNS_MAX_DOMAINLEN];
 		dname_str(qstate->qinfo.qname, s);
 		verbose(VERB_QUERY, "request %s has exceeded the maximum "
 			"number of glue fetches %d", s,
@@ -2268,14 +2257,14 @@ processLastResort(struct module_qstate* 
 		/* if this nameserver is at a delegation point, but that
 		 * delegation point is a stub and we cannot go higher, skip*/
 		if( ((ie->supports_ipv6 && !ns->done_pside6) ||
-		    ((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4)) &&
+		    ((ie->supports_ipv4 || ie->nat64.use_nat64) && !ns->done_pside4)) &&
 		    !can_have_last_resort(qstate->env, ns->name, ns->namelen,
 			iq->qchase.qclass, NULL, NULL, NULL)) {
 			log_nametypeclass(VERB_ALGO, "cannot pside lookup ns "
 				"because it is also a stub/forward,",
 				ns->name, LDNS_RR_TYPE_NS, iq->qchase.qclass);
 			if(ie->supports_ipv6) ns->done_pside6 = 1;
-			if(ie->supports_ipv4 || ie->use_nat64) ns->done_pside4 = 1;
+			if(ie->supports_ipv4 || ie->nat64.use_nat64) ns->done_pside4 = 1;
 			continue;
 		}
 		/* query for parent-side A and AAAA for nameservers */
@@ -2300,7 +2289,7 @@ processLastResort(struct module_qstate* 
 				return 0;
 			}
 		}
-		if((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4) {
+		if((ie->supports_ipv4 || ie->nat64.use_nat64) && !ns->done_pside4) {
 			/* Send the A request. */
 			if(!generate_parentside_target_query(qstate, iq, id, 
 				ns->name, ns->namelen, 
@@ -2569,7 +2558,7 @@ processQueryTargets(struct module_qstate
 	}
 	if(!ie->supports_ipv6)
 		delegpt_no_ipv6(iq->dp);
-	if(!ie->supports_ipv4 && !ie->use_nat64)
+	if(!ie->supports_ipv4 && !ie->nat64.use_nat64)
 		delegpt_no_ipv4(iq->dp);
 	delegpt_log(VERB_ALGO, iq->dp);
 
@@ -2741,9 +2730,7 @@ processQueryTargets(struct module_qstate
 		if((iq->chase_flags&BIT_RD) && !(iq->response->rep->flags&BIT_AA)) {
 			verbose(VERB_ALGO, "forwarder, ignoring referral from auth zone");
 		} else {
-			lock_rw_wrlock(&qstate->env->auth_zones->lock);
-			qstate->env->auth_zones->num_query_up++;
-			lock_rw_unlock(&qstate->env->auth_zones->lock);
+			qstate->env->mesh->num_query_authzone_up++;
 			iq->num_current_queries++;
 			iq->chase_to_rd = 0;
 			iq->dnssec_lame_query = 0;
@@ -3046,7 +3033,7 @@ processQueryTargets(struct module_qstate
 	target_count_increase_global_quota(iq, 1);
 	if(iq->target_count && iq->target_count[TARGET_COUNT_GLOBAL_QUOTA]
 		> MAX_GLOBAL_QUOTA) {
-		char s[LDNS_MAX_DOMAINLEN+1];
+		char s[LDNS_MAX_DOMAINLEN];
 		dname_str(qstate->qinfo.qname, s);
 		verbose(VERB_QUERY, "request %s has exceeded the maximum "
 			"global quota on number of upstream queries %d", s,
@@ -3070,9 +3057,9 @@ processQueryTargets(struct module_qstate
 	real_addr = target->addr;
 	real_addrlen = target->addrlen;
 
-	if(ie->use_nat64 && target->addr.ss_family == AF_INET) {
-		addr_to_nat64(&target->addr, &ie->nat64_prefix_addr,
-			ie->nat64_prefix_addrlen, ie->nat64_prefix_net,
+	if(ie->nat64.use_nat64 && target->addr.ss_family == AF_INET) {
+		addr_to_nat64(&target->addr, &ie->nat64.nat64_prefix_addr,
+			ie->nat64.nat64_prefix_addrlen, ie->nat64.nat64_prefix_net,
 			&real_addr, &real_addrlen);
 		log_name_addr(VERB_QUERY, "applied NAT64:",
 			iq->dp->name, &real_addr, real_addrlen);
@@ -3296,6 +3283,16 @@ processQueryResponse(struct module_qstat
 			iq->num_target_queries = 0;
 			return processDSNSFind(qstate, iq, id);
 		}
+		if(iq->qchase.qtype == LDNS_RR_TYPE_DNSKEY && SERVE_EXPIRED
+			&& qstate->is_valrec &&
+			reply_find_answer_rrset(&iq->qchase, iq->response->rep) != NULL) {
+			/* clean out the authority section, if any, so it
+			 * does not overwrite dnssec valid data in the
+			 * validation recursion lookup. */
+			verbose(VERB_ALGO, "make DNSKEY minimal for serve "
+				"expired");
+			iter_make_minimal(iq->response->rep);
+		}
 		if(!qstate->no_cache_store)
 			iter_dns_store(qstate->env, &iq->response->qinfo,
 				iq->response->rep,
@@ -3303,7 +3300,7 @@ processQueryResponse(struct module_qstat
 				qstate->prefetch_leeway,
 				iq->dp&&iq->dp->has_parent_side_NS,
 				qstate->region, qstate->query_flags,
-				qstate->qstarttime);
+				qstate->qstarttime, qstate->is_valrec);
 		/* close down outstanding requests to be discarded */
 		outbound_list_clear(&iq->outlist);
 		iq->num_current_queries = 0;
@@ -3397,7 +3394,7 @@ processQueryResponse(struct module_qstat
 			/* no prefetch-leeway, since its not the answer */
 			iter_dns_store(qstate->env, &iq->response->qinfo,
 				iq->response->rep, 1, 0, 0, NULL, 0,
-				qstate->qstarttime);
+				qstate->qstarttime, qstate->is_valrec);
 			if(iq->store_parent_NS)
 				iter_store_parentside_NS(qstate->env, 
 					iq->response->rep);
@@ -3527,7 +3524,8 @@ processQueryResponse(struct module_qstat
 			iter_dns_store(qstate->env, &iq->response->qinfo,
 				iq->response->rep, 1, qstate->prefetch_leeway,
 				iq->dp&&iq->dp->has_parent_side_NS, NULL,
-				qstate->query_flags, qstate->qstarttime);
+				qstate->query_flags, qstate->qstarttime,
+				qstate->is_valrec);
 		/* set the current request's qname to the new value. */
 		iq->qchase.qname = sname;
 		iq->qchase.qname_len = snamelen;
@@ -3871,7 +3869,7 @@ processTargetResponse(struct module_qsta
 	} else {
 		verbose(VERB_ALGO, "iterator TargetResponse failed");
 		delegpt_mark_neg(dpns, qstate->qinfo.qtype);
-		if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
+		if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->nat64.use_nat64)) &&
 			(dpns->got6 == 2 || !ie->supports_ipv6)) {
 			dpns->resolved = 1; /* fail the target */
 			/* do not count cached answers */
@@ -4154,7 +4152,7 @@ processFinished(struct module_qstate* qs
 				iq->response->rep, 0, qstate->prefetch_leeway,
 				iq->dp&&iq->dp->has_parent_side_NS,
 				qstate->region, qstate->query_flags,
-				qstate->qstarttime);
+				qstate->qstarttime, qstate->is_valrec);
 		}
 	}
 	qstate->return_rcode = LDNS_RCODE_NOERROR;
@@ -4334,6 +4332,7 @@ process_response(struct module_qstate* q
 	}
 
 	/* Copy the edns options we may got from the back end */
+	qstate->edns_opts_back_in = NULL;
 	if(edns.opt_list_in) {
 		qstate->edns_opts_back_in = edns_opt_copy_region(edns.opt_list_in,
 			qstate->region);
Index: iterator/iterator.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iterator.h,v
diff -u -p -r1.23 iterator.h
--- iterator/iterator.h	21 Feb 2025 13:20:40 -0000	1.23
+++ iterator/iterator.h	30 Aug 2025 11:52:23 -0000
@@ -46,8 +46,6 @@
 #include "util/data/msgreply.h"
 #include "util/module.h"
 struct delegpt;
-struct iter_hints;
-struct iter_forwards;
 struct iter_donotq;
 struct iter_prep_list;
 struct iter_priv;
@@ -108,15 +106,9 @@ extern int BLACKLIST_PENALTY;
 #define EMPTY_NODATA_RETRY_COUNT 2
 
 /**
- * Global state for the iterator.
+ * Iterator global state for nat64.
  */
-struct iter_env {
-	/** A flag to indicate whether or not we have an IPv6 route */
-	int supports_ipv6;
-
-	/** A flag to indicate whether or not we have an IPv4 route */
-	int supports_ipv4;
-
+struct iter_nat64 {
 	/** A flag to locally apply NAT64 to make IPv4 addrs into IPv6 */
 	int use_nat64;
 
@@ -128,6 +120,20 @@ struct iter_env {
 
 	/** CIDR mask length of NAT64 prefix */
 	int nat64_prefix_net;
+};
+
+/**
+ * Global state for the iterator.
+ */
+struct iter_env {
+	/** A flag to indicate whether or not we have an IPv6 route */
+	int supports_ipv6;
+
+	/** A flag to indicate whether or not we have an IPv4 route */
+	int supports_ipv4;
+
+	/** State for nat64 */
+	struct iter_nat64 nat64;
 
 	/** A set of inetaddrs that should never be queried. */
 	struct iter_donotq* donotq;
Index: libunbound/libworker.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/libunbound/libworker.c,v
diff -u -p -r1.34 libworker.c
--- libunbound/libworker.c	21 Feb 2025 13:20:40 -0000	1.34
+++ libunbound/libworker.c	30 Aug 2025 11:52:23 -0000
@@ -423,7 +423,7 @@ int libworker_bg(struct ub_ctx* ctx)
 static int
 fill_canon(struct ub_result* res, uint8_t* s)
 {
-	char buf[255+2];
+	char buf[LDNS_MAX_DOMAINLEN];
 	dname_str(s, buf);
 	res->canonname = strdup(buf);
 	return res->canonname != 0;
@@ -1058,6 +1058,20 @@ void dtio_mainfdcallback(int ATTR_UNUSED
 	log_assert(0);
 }
 #endif
+
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
+	void* ATTR_UNUSED(arg))
+{
+	log_assert(0);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+	void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+	log_assert(0);
+	return 0;
+}
 
 #ifdef HAVE_NGTCP2
 void doq_client_event_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
Index: libunbound/unbound.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/libunbound/unbound.h,v
diff -u -p -r1.16 unbound.h
--- libunbound/unbound.h	21 Feb 2025 13:20:40 -0000	1.16
+++ libunbound/unbound.h	30 Aug 2025 11:52:23 -0000
@@ -849,6 +849,12 @@ struct ub_server_stats {
 	long long mem_quic;
 	/** number of queries over (DNS over) QUIC */
 	long long qquic;
+	/** number of queries removed due to discard-timeout */
+	long long num_queries_discard_timeout;
+	/** number of queries removed due to wait-limit */
+	long long num_queries_wait_limit;
+	/** number of dns error reports generated */
+	long long num_dns_error_reports;
 };
 
 /**
Index: respip/respip.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/respip/respip.c,v
diff -u -p -r1.16 respip.c
--- respip/respip.c	4 Sep 2024 09:36:40 -0000	1.16
+++ respip/respip.c	30 Aug 2025 11:52:23 -0000
@@ -105,6 +105,7 @@ respip_sockaddr_find_or_create(struct re
 		socklen_t addrlen, int net, int create, const char* ipstr)
 {
 	struct resp_addr* node;
+	log_assert(set);
 	node = (struct resp_addr*)addr_tree_find(&set->ip_tree, addr, addrlen, net);
 	if(!node && create) {
 		node = regional_alloc_zero(set->region, sizeof(*node));
@@ -128,6 +129,7 @@ void
 respip_sockaddr_delete(struct respip_set* set, struct resp_addr* node)
 {
 	struct resp_addr* prev;
+	log_assert(set);
 	prev = (struct resp_addr*)rbtree_previous((struct rbnode_type*)node);	
 	lock_rw_destroy(&node->lock);
 	(void)rbtree_delete(&set->ip_tree, node);
@@ -146,6 +148,7 @@ respip_find_or_create(struct respip_set*
 	struct sockaddr_storage addr;
 	int net;
 	socklen_t addrlen;
+	log_assert(set);
 
 	if(!netblockstrtoaddr(ipstr, 0, &addr, &addrlen, &net)) {
 		log_err("cannot parse netblock: '%s'", ipstr);
@@ -160,6 +163,7 @@ respip_tag_cfg(struct respip_set* set, c
 	const uint8_t* taglist, size_t taglen)
 {
 	struct resp_addr* node;
+	log_assert(set);
 
 	if(!(node=respip_find_or_create(set, ipstr, 1)))
 		return 0;
@@ -183,6 +187,7 @@ respip_action_cfg(struct respip_set* set
 {
 	struct resp_addr* node;
 	enum respip_action action;
+	log_assert(set);
 
 	if(!(node=respip_find_or_create(set, ipstr, 1)))
 		return 0;
@@ -325,6 +330,7 @@ static int
 respip_data_cfg(struct respip_set* set, const char* ipstr, const char* rrstr)
 {
 	struct resp_addr* node;
+	log_assert(set);
 
 	node=respip_find_or_create(set, ipstr, 0);
 	if(!node || node->action == respip_none) {
@@ -344,6 +350,7 @@ respip_set_apply_cfg(struct respip_set* 
 	struct config_strbytelist* p;
 	struct config_str2list* pa;
 	struct config_str2list* pd;
+	log_assert(set);
 
 	set->tagname = tagname;
 	set->num_tags = num_tags;
@@ -609,6 +616,7 @@ respip_addr_lookup(const struct reply_in
 	struct resp_addr* ra;
 	struct sockaddr_storage ss;
 	socklen_t addrlen;
+	log_assert(rs);
 
 	lock_rw_rdlock(&rs->lock);
 	for(i=0; i<rep->an_numrrsets; i++) {
@@ -867,7 +875,8 @@ respip_rewrite_reply(const struct query_
 	const struct respip_client_info* cinfo, const struct reply_info* rep,
 	struct reply_info** new_repp, struct respip_action_info* actinfo,
 	struct ub_packed_rrset_key** alias_rrset, int search_only,
-	struct regional* region, struct auth_zones* az, int* rpz_passthru)
+	struct regional* region, struct auth_zones* az, int* rpz_passthru,
+	struct views* views, struct respip_set* ipset)
 {
 	const uint8_t* ctaglist;
 	size_t ctaglen;
@@ -876,7 +885,6 @@ respip_rewrite_reply(const struct query_
 	struct config_strlist** tag_datas;
 	size_t tag_datas_size;
 	struct view* view = NULL;
-	struct respip_set* ipset = NULL;
 	size_t rrset_id = 0, rr_id = 0;
 	enum respip_action action = respip_none;
 	int tag = -1;
@@ -899,8 +907,20 @@ respip_rewrite_reply(const struct query_
 	tag_actions_size = cinfo->tag_actions_size;
 	tag_datas = cinfo->tag_datas;
 	tag_datas_size = cinfo->tag_datas_size;
-	view = cinfo->view;
-	ipset = cinfo->respip_set;
+	if(cinfo->view) {
+		view = cinfo->view;
+		lock_rw_rdlock(&view->lock);
+	} else if(cinfo->view_name) {
+		view = views_find_view(views, cinfo->view_name, 0);
+		if(!view) {
+			/* If the view no longer exists, the rewrite can not
+			 * be processed further. */
+			verbose(VERB_ALGO, "respip: failed because view %s no "
+				"longer exists", cinfo->view_name);
+			return 0;
+		}
+		/* The view is rdlocked by views_find_view. */
+	}
 
 	log_assert(ipset);
 
@@ -915,7 +935,6 @@ respip_rewrite_reply(const struct query_
 	  * Note also that we assume 'view' is valid in this function, which
 	  * should be safe (see unbound bug #1191) */
 	if(view) {
-		lock_rw_rdlock(&view->lock);
 		if(view->respip_set) {
 			if((raddr = respip_addr_lookup(rep,
 				view->respip_set, &rrset_id, &rr_id))) {
@@ -961,7 +980,7 @@ respip_rewrite_reply(const struct query_
 						struct sockaddr_storage ss;
 						socklen_t ss_len = 0;
 						char nm[256], ip[256];
-						char qn[255+1];
+						char qn[LDNS_MAX_DOMAINLEN];
 						if(!rdata2sockaddr(rep->rrsets[rrset_id]->entry.data, ntohs(rep->rrsets[rrset_id]->rk.type), rr_id, &ss, &ss_len))
 							snprintf(ip, sizeof(ip), "invalidRRdata");
 						else
@@ -1101,7 +1120,8 @@ respip_operate(struct module_qstate* qst
 				qstate->client_info, qstate->return_msg->rep,
 				&new_rep, &actinfo, &alias_rrset, 0,
 				qstate->region, qstate->env->auth_zones,
-				&qstate->rpz_passthru)) {
+				&qstate->rpz_passthru, qstate->env->views,
+				qstate->env->respip_set)) {
 				goto servfail;
 			}
 			if(actinfo.action != respip_none) {
@@ -1149,7 +1169,8 @@ respip_merge_cname(struct reply_info* ba
 	const struct query_info* qinfo, const struct reply_info* tgt_rep,
 	const struct respip_client_info* cinfo, int must_validate,
 	struct reply_info** new_repp, struct regional* region,
-	struct auth_zones* az)
+	struct auth_zones* az, struct views* views,
+	struct respip_set* respip_set)
 {
 	struct reply_info* new_rep;
 	struct reply_info* tmp_rep = NULL; /* just a placeholder */
@@ -1176,7 +1197,7 @@ respip_merge_cname(struct reply_info* ba
 
 	/* see if the target reply would be subject to a response-ip action. */
 	if(!respip_rewrite_reply(qinfo, cinfo, tgt_rep, &tmp_rep, &actinfo,
-		&alias_rrset, 1, region, az, NULL))
+		&alias_rrset, 1, region, az, NULL, views, respip_set))
 		return 0;
 	if(actinfo.action != respip_none) {
 		log_info("CNAME target of redirect response-ip action would "
@@ -1229,7 +1250,8 @@ respip_inform_super(struct module_qstate
 	if(!respip_merge_cname(super->return_msg->rep, &qstate->qinfo,
 		qstate->return_msg->rep, super->client_info,
 		super->env->need_to_validate, &new_rep, super->region,
-		qstate->env->auth_zones))
+		qstate->env->auth_zones, qstate->env->views,
+		qstate->env->respip_set))
 		goto fail;
 	super->return_msg->rep = new_rep;
 	return;
@@ -1325,4 +1347,36 @@ respip_inform_print(struct respip_action
 		"%s/%d %s %s@%u", respip, respip_addr->net,
 		(actionstr) ? actionstr : "inform", srcip, port);
 	log_nametypeclass(NO_VERBOSE, txt, qname, qtype, qclass);
+}
+
+size_t respip_set_get_mem(struct respip_set* set)
+{
+	size_t m;
+	if(!set) return 0;
+	m = sizeof(*set);
+	lock_rw_rdlock(&set->lock);
+	m += regional_get_mem(set->region);
+	lock_rw_unlock(&set->lock);
+	return m;
+}
+
+void
+respip_set_swap_tree(struct respip_set* respip_set,
+	struct respip_set* data)
+{
+	rbnode_type* oldroot = respip_set->ip_tree.root;
+	size_t oldcount = respip_set->ip_tree.count;
+	struct regional* oldregion = respip_set->region;
+	char* const* oldtagname = respip_set->tagname;
+	int oldnum_tags = respip_set->num_tags;
+	respip_set->ip_tree.root = data->ip_tree.root;
+	respip_set->ip_tree.count = data->ip_tree.count;
+	respip_set->region = data->region;
+	respip_set->tagname = data->tagname;
+	respip_set->num_tags = data->num_tags;
+	data->ip_tree.root = oldroot;
+	data->ip_tree.count = oldcount;
+	data->region = oldregion;
+	data->tagname = oldtagname;
+	data->num_tags = oldnum_tags;
 }
Index: respip/respip.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/respip/respip.h,v
diff -u -p -r1.5 respip.h
--- respip/respip.h	20 Oct 2022 08:26:14 -0000	1.5
+++ respip/respip.h	30 Aug 2025 11:52:23 -0000
@@ -23,7 +23,8 @@
 struct respip_set {
 	struct regional* region;
 	struct rbtree_type ip_tree;
-	lock_rw_type lock;	/* lock on the respip tree */
+	lock_rw_type lock;	/* lock on the respip tree. It is ordered
+		after views and before hints, stubs and local zones. */
 	char* const* tagname;	/* shallow copy of tag names, for logging */
 	int num_tags;		/* number of tagname entries */
 };
@@ -59,7 +60,6 @@ struct respip_addr_info;
  * This is essentially a subset of acl_addr (except for respip_set) but
  * defined as a separate structure to avoid dependency on the daemon-specific
  * structure.
- * respip_set is supposed to refer to the response-ip set for the global view.
  */
 struct respip_client_info {
 	uint8_t* taglist;
@@ -68,8 +68,12 @@ struct respip_client_info {
 	size_t tag_actions_size;
 	struct config_strlist** tag_datas;
 	size_t tag_datas_size;
+	/** The view for the action, during cache callback that is by
+	 * pointer. */
 	struct view* view;
-	struct respip_set* respip_set;
+	/** If from module query state, the view pointer is NULL, but the
+	 * name is stored in reference to the view. */
+	char* view_name;
 };
 
 /**
@@ -149,13 +153,16 @@ int respip_views_apply_cfg(struct views*
  *   on error.
  * @param region: allocator to build *new_repp.
  * @param az: auth zones containing RPZ information.
+ * @param views: views tree to lookup view used.
+ * @param respip_set: the respip set for the global view.
  * @return 1 on success, 0 on error.
  */
 int respip_merge_cname(struct reply_info* base_rep,
 	const struct query_info* qinfo, const struct reply_info* tgt_rep,
 	const struct respip_client_info* cinfo, int must_validate,
 	struct reply_info** new_repp, struct regional* region,
-	struct auth_zones* az);
+	struct auth_zones* az, struct views* views,
+	struct respip_set* respip_set);
 
 /**
  * See if any IP-based action should apply to any IP address of AAAA/A answer
@@ -178,6 +185,8 @@ int respip_merge_cname(struct reply_info
  * @param region: allocator to build *new_repp.
  * @param rpz_passthru: keeps track of query state can have passthru that
  *   stops further rpz processing. Or NULL for cached answer processing.
+ * @param views: views tree to lookup view used.
+ * @param ipset: the respip set for the global view.
  * @return 1 on success, 0 on error.
  */
 int respip_rewrite_reply(const struct query_info* qinfo,
@@ -186,7 +195,7 @@ int respip_rewrite_reply(const struct qu
 	struct respip_action_info* actinfo,
 	struct ub_packed_rrset_key** alias_rrset,
 	int search_only, struct regional* region, struct auth_zones* az,
-	int* rpz_passthru);
+	int* rpz_passthru, struct views* views, struct respip_set* ipset);
 
 /**
  * Get the response-ip function block.
@@ -302,4 +311,18 @@ respip_sockaddr_delete(struct respip_set
 
 struct ub_packed_rrset_key*
 respip_copy_rrset(const struct ub_packed_rrset_key* key, struct regional* region);
+
+/** Get memory usage of respip set tree. The routine locks and unlocks the
+ * set for reading. */
+size_t respip_set_get_mem(struct respip_set* set);
+
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param respip_set: response ip tree
+ * @param data: preallocated information.
+ */
+void respip_set_swap_tree(struct respip_set* respip_set,
+	struct respip_set* data);
+
 #endif	/* RESPIP_RESPIP_H */
Index: services/authzone.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/authzone.c,v
diff -u -p -r1.29 authzone.c
--- services/authzone.c	21 Feb 2025 13:20:40 -0000	1.29
+++ services/authzone.c	30 Aug 2025 11:52:23 -0000
@@ -1578,7 +1578,7 @@ auth_zone_read_zonefile(struct auth_zone
 		cfg->chrootdir, strlen(cfg->chrootdir)) == 0)
 		zfilename += strlen(cfg->chrootdir);
 	if(verbosity >= VERB_ALGO) {
-		char nm[255+1];
+		char nm[LDNS_MAX_DOMAINLEN];
 		dname_str(z->name, nm);
 		verbose(VERB_ALGO, "read zonefile %s for %s", zfilename, nm);
 	}
@@ -1942,7 +1942,7 @@ static int auth_zone_zonemd_check_hash(s
 					unsupported_reason = *reason;
 				/* continue to check for valid ZONEMD */
 				if(verbosity >= VERB_ALGO) {
-					char zstr[255+1];
+					char zstr[LDNS_MAX_DOMAINLEN];
 					dname_str(z->name, zstr);
 					verbose(VERB_ALGO, "auth-zone %s ZONEMD %d %d is unsupported: %s", zstr, (int)scheme, (int)hashalgo, *reason);
 				}
@@ -1950,7 +1950,7 @@ static int auth_zone_zonemd_check_hash(s
 				continue;
 			}
 			if(verbosity >= VERB_ALGO) {
-				char zstr[255+1];
+				char zstr[LDNS_MAX_DOMAINLEN];
 				dname_str(z->name, zstr);
 				if(!*reason)
 					verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr);
@@ -1973,7 +1973,7 @@ static int auth_zone_zonemd_check_hash(s
 	if(!*reason)
 		*reason = "no ZONEMD records found";
 	if(verbosity >= VERB_ALGO) {
-		char zstr[255+1];
+		char zstr[LDNS_MAX_DOMAINLEN];
 		dname_str(z->name, zstr);
 		verbose(VERB_ALGO, "auth-zone %s ZONEMD failed: %s", zstr, *reason);
 	}
@@ -2317,9 +2317,6 @@ auth_free_masters(struct auth_master* li
 	}
 }
 
-/** delete auth xfer structure
- * @param xfr: delete this xfer and its tasks.
- */
 void
 auth_xfer_delete(struct auth_xfer* xfr)
 {
@@ -3610,9 +3607,7 @@ int auth_zones_answer(struct auth_zones*
 			return 0;
 		}
 		lock_rw_unlock(&z->lock);
-		lock_rw_wrlock(&az->lock);
-		az->num_query_down++;
-		lock_rw_unlock(&az->lock);
+		env->mesh->num_query_authzone_down++;
 		auth_error_encode(qinfo, env, edns, repinfo, buf, temp,
 			LDNS_RCODE_SERVFAIL);
 		return 1;
@@ -3625,9 +3620,7 @@ int auth_zones_answer(struct auth_zones*
 		/* fallback to regular answering (recursive) */
 		return 0;
 	}
-	lock_rw_wrlock(&az->lock);
-	az->num_query_down++;
-	lock_rw_unlock(&az->lock);
+	env->mesh->num_query_authzone_down++;
 
 	/* encode answer */
 	if(!r)
@@ -4803,8 +4796,8 @@ log_rrlist_position(const char* label, s
 {
 	sldns_buffer pkt;
 	size_t dlen;
-	uint8_t buf[256];
-	char str[256];
+	uint8_t buf[LDNS_MAX_DOMAINLEN];
+	char str[LDNS_MAX_DOMAINLEN];
 	char typestr[32];
 	sldns_buffer_init_frm_data(&pkt, rr_chunk->data, rr_chunk->len);
 	sldns_buffer_set_position(&pkt, (size_t)(rr_dname -
@@ -5231,7 +5224,7 @@ xfr_write_after_update(struct auth_xfer*
 		cfg->chrootdir, strlen(cfg->chrootdir)) == 0)
 		zfilename += strlen(cfg->chrootdir);
 	if(verbosity >= VERB_ALGO) {
-		char nm[255+1];
+		char nm[LDNS_MAX_DOMAINLEN];
 		dname_str(z->name, nm);
 		verbose(VERB_ALGO, "write zonefile %s for %s", zfilename, nm);
 	}
@@ -5348,7 +5341,7 @@ xfr_process_chunk_list(struct auth_xfer*
 	/* holding z lock */
 	auth_zone_verify_zonemd(z, env, &env->mesh->mods, NULL, 0, 0);
 	if(z->zone_expired) {
-		char zname[256];
+		char zname[LDNS_MAX_DOMAINLEN];
 		/* ZONEMD must have failed */
 		/* reacquire locks, so we hold xfr lock on exit of routine,
 		 * and both xfr and z again after releasing xfr for potential
@@ -5380,7 +5373,7 @@ xfr_process_chunk_list(struct auth_xfer*
 	lock_rw_unlock(&z->lock);
 
 	if(verbosity >= VERB_QUERY && xfr->have_zone) {
-		char zname[256];
+		char zname[LDNS_MAX_DOMAINLEN];
 		dname_str(xfr->name, zname);
 		verbose(VERB_QUERY, "auth zone %s updated to serial %u", zname,
 			(unsigned)xfr->serial);
@@ -5442,7 +5435,7 @@ xfr_transfer_lookup_host(struct auth_xfe
 	qinfo.local_alias = NULL;
 	if(verbosity >= VERB_ALGO) {
 		char buf1[512];
-		char buf2[LDNS_MAX_DOMAINLEN+1];
+		char buf2[LDNS_MAX_DOMAINLEN];
 		dname_str(xfr->name, buf2);
 		snprintf(buf1, sizeof(buf1), "auth zone %s: master lookup"
 			" for task_transfer", buf2);
@@ -5498,7 +5491,7 @@ xfr_transfer_init_fetch(struct auth_xfer
 			/* the ones that are not in addr format are supposed
 			 * to be looked up.  The lookup has failed however,
 			 * so skip them */
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(xfr->name, zname);
 			log_err("%s: failed lookup, cannot transfer from master %s",
 				zname, master->host);
@@ -5537,7 +5530,7 @@ xfr_transfer_init_fetch(struct auth_xfer
 			&addr, addrlen, -1, master->ssl, master->host,
 			master->file, env->cfg);
 		if(!xfr->task_transfer->cp) {
-			char zname[255+1], as[256];
+			char zname[LDNS_MAX_DOMAINLEN], as[256];
 			dname_str(xfr->name, zname);
 			addr_port_to_str(&addr, addrlen, as, sizeof(as));
 			verbose(VERB_ALGO, "cannot create http cp "
@@ -5546,7 +5539,7 @@ xfr_transfer_init_fetch(struct auth_xfer
 		}
 		comm_timer_set(xfr->task_transfer->timer, &t);
 		if(verbosity >= VERB_ALGO) {
-			char zname[255+1], as[256];
+			char zname[LDNS_MAX_DOMAINLEN], as[256];
 			dname_str(xfr->name, zname);
 			addr_port_to_str(&addr, addrlen, as, sizeof(as));
 			verbose(VERB_ALGO, "auth zone %s transfer next HTTP fetch from %s started", zname, as);
@@ -5569,7 +5562,7 @@ xfr_transfer_init_fetch(struct auth_xfer
 		env->scratch_buffer, -1,
 		auth_name != NULL, auth_name);
 	if(!xfr->task_transfer->cp) {
-		char zname[255+1], as[256];
+		char zname[LDNS_MAX_DOMAINLEN], as[256];
  		dname_str(xfr->name, zname);
 		addr_port_to_str(&addr, addrlen, as, sizeof(as));
 		verbose(VERB_ALGO, "cannot create tcp cp connection for "
@@ -5578,7 +5571,7 @@ xfr_transfer_init_fetch(struct auth_xfer
 	}
 	comm_timer_set(xfr->task_transfer->timer, &t);
 	if(verbosity >= VERB_ALGO) {
-		char zname[255+1], as[256];
+		char zname[LDNS_MAX_DOMAINLEN], as[256];
  		dname_str(xfr->name, zname);
 		addr_port_to_str(&addr, addrlen, as, sizeof(as));
 		verbose(VERB_ALGO, "auth zone %s transfer next %s fetch from %s started", zname, 
@@ -5602,7 +5595,7 @@ xfr_transfer_nexttarget_or_end(struct au
 			 * and that calls the callback just like a full
 			 * lookup and lookup failures also call callback */
 			if(verbosity >= VERB_ALGO) {
-				char zname[255+1];
+				char zname[LDNS_MAX_DOMAINLEN];
 				dname_str(xfr->name, zname);
 				verbose(VERB_ALGO, "auth zone %s transfer next target lookup", zname);
 			}
@@ -5625,7 +5618,7 @@ xfr_transfer_nexttarget_or_end(struct au
 		xfr_transfer_nextmaster(xfr);
 	}
 	if(verbosity >= VERB_ALGO) {
-		char zname[255+1];
+		char zname[LDNS_MAX_DOMAINLEN];
 		dname_str(xfr->name, zname);
 		verbose(VERB_ALGO, "auth zone %s transfer failed, wait", zname);
 	}
@@ -5728,14 +5721,14 @@ void auth_xfer_transfer_lookup_callback(
 					lookup_target, answer, wanted_qtype);
 			} else {
 				if(verbosity >= VERB_ALGO) {
-					char zname[255+1];
+					char zname[LDNS_MAX_DOMAINLEN];
 					dname_str(xfr->name, zname);
 					verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup has nodata", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A"));
 				}
 			}
 		} else {
 			if(verbosity >= VERB_ALGO) {
-				char zname[255+1];
+				char zname[LDNS_MAX_DOMAINLEN];
 				dname_str(xfr->name, zname);
 				verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup has no answer", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A"));
 			}
@@ -5743,7 +5736,7 @@ void auth_xfer_transfer_lookup_callback(
 		regional_free_all(temp);
 	} else {
 		if(verbosity >= VERB_ALGO) {
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(xfr->name, zname);
 			verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup failed", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A"));
 		}
@@ -6385,7 +6378,7 @@ xfr_probe_send_probe(struct auth_xfer* x
 			/* the ones that are not in addr format are supposed
 			 * to be looked up.  The lookup has failed however,
 			 * so skip them */
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(xfr->name, zname);
 			log_err("%s: failed lookup, cannot probe to master %s",
 				zname, master->host);
@@ -6427,7 +6420,7 @@ xfr_probe_send_probe(struct auth_xfer* x
 		xfr->task_probe->cp = outnet_comm_point_for_udp(env->outnet,
 			auth_xfer_probe_udp_callback, xfr, &addr, addrlen);
 		if(!xfr->task_probe->cp) {
-			char zname[255+1], as[256];
+			char zname[LDNS_MAX_DOMAINLEN], as[256];
 			dname_str(xfr->name, zname);
 			addr_port_to_str(&addr, addrlen, as, sizeof(as));
 			verbose(VERB_ALGO, "cannot create udp cp for "
@@ -6447,7 +6440,7 @@ xfr_probe_send_probe(struct auth_xfer* x
 	/* send udp packet */
 	if(!comm_point_send_udp_msg(xfr->task_probe->cp, env->scratch_buffer,
 		(struct sockaddr*)&addr, addrlen, 0)) {
-		char zname[255+1], as[256];
+		char zname[LDNS_MAX_DOMAINLEN], as[256];
 		dname_str(xfr->name, zname);
 		addr_port_to_str(&addr, addrlen, as, sizeof(as));
 		verbose(VERB_ALGO, "failed to send soa probe for %s to %s",
@@ -6455,7 +6448,7 @@ xfr_probe_send_probe(struct auth_xfer* x
 		return 0;
 	}
 	if(verbosity >= VERB_ALGO) {
-		char zname[255+1], as[256];
+		char zname[LDNS_MAX_DOMAINLEN], as[256];
 		dname_str(xfr->name, zname);
 		addr_port_to_str(&addr, addrlen, as, sizeof(as));
 		verbose(VERB_ALGO, "auth zone %s soa probe sent to %s", zname,
@@ -6486,7 +6479,7 @@ auth_xfer_probe_timer_callback(void* arg
 	}
 
 	if(verbosity >= VERB_ALGO) {
-		char zname[255+1];
+		char zname[LDNS_MAX_DOMAINLEN];
 		dname_str(xfr->name, zname);
 		verbose(VERB_ALGO, "auth zone %s soa probe timeout", zname);
 	}
@@ -6534,7 +6527,7 @@ auth_xfer_probe_udp_callback(struct comm
 			&serial)) {
 			/* successful lookup */
 			if(verbosity >= VERB_ALGO) {
-				char buf[256];
+				char buf[LDNS_MAX_DOMAINLEN];
 				dname_str(xfr->name, buf);
 				verbose(VERB_ALGO, "auth zone %s: soa probe "
 					"serial is %u", buf, (unsigned)serial);
@@ -6573,14 +6566,14 @@ auth_xfer_probe_udp_callback(struct comm
 			}
 		} else {
 			if(verbosity >= VERB_ALGO) {
-				char buf[256];
+				char buf[LDNS_MAX_DOMAINLEN];
 				dname_str(xfr->name, buf);
 				verbose(VERB_ALGO, "auth zone %s: bad reply to soa probe", buf);
 			}
 		}
 	} else {
 		if(verbosity >= VERB_ALGO) {
-			char buf[256];
+			char buf[LDNS_MAX_DOMAINLEN];
 			dname_str(xfr->name, buf);
 			verbose(VERB_ALGO, "auth zone %s: soa probe failed", buf);
 		}
@@ -6637,7 +6630,7 @@ xfr_probe_lookup_host(struct auth_xfer* 
 	qinfo.local_alias = NULL;
 	if(verbosity >= VERB_ALGO) {
 		char buf1[512];
-		char buf2[LDNS_MAX_DOMAINLEN+1];
+		char buf2[LDNS_MAX_DOMAINLEN];
 		dname_str(xfr->name, buf2);
 		snprintf(buf1, sizeof(buf1), "auth zone %s: master lookup"
 			" for task_probe", buf2);
@@ -6683,7 +6676,7 @@ xfr_probe_send_or_end(struct auth_xfer* 
 			 * and that calls the callback just like a full
 			 * lookup and lookup failures also call callback */
 			if(verbosity >= VERB_ALGO) {
-				char zname[255+1];
+				char zname[LDNS_MAX_DOMAINLEN];
 				dname_str(xfr->name, zname);
 				verbose(VERB_ALGO, "auth zone %s probe next target lookup", zname);
 			}
@@ -6696,7 +6689,7 @@ xfr_probe_send_or_end(struct auth_xfer* 
 	 * allow_notify addrs */
 	probe_copy_masters_for_allow_notify(xfr);
 	if(verbosity >= VERB_ALGO) {
-		char zname[255+1];
+		char zname[LDNS_MAX_DOMAINLEN];
 		dname_str(xfr->name, zname);
 		verbose(VERB_ALGO, "auth zone %s probe: notify addrs updated", zname);
 	}
@@ -6704,7 +6697,7 @@ xfr_probe_send_or_end(struct auth_xfer* 
 		/* only wanted lookups for copy, stop probe and start wait */
 		xfr->task_probe->only_lookup = 0;
 		if(verbosity >= VERB_ALGO) {
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(xfr->name, zname);
 			verbose(VERB_ALGO, "auth zone %s probe: finished only_lookup", zname);
 		}
@@ -6730,7 +6723,7 @@ xfr_probe_send_or_end(struct auth_xfer* 
 	if(xfr->task_probe->have_new_lease) {
 		/* if zone not updated, start the wait timer again */
 		if(verbosity >= VERB_ALGO) {
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(xfr->name, zname);
 			verbose(VERB_ALGO, "auth_zone %s unchanged, new lease, wait", zname);
 		}
@@ -6741,7 +6734,7 @@ xfr_probe_send_or_end(struct auth_xfer* 
 			xfr_set_timeout(xfr, env, 0, 0);
 	} else {
 		if(verbosity >= VERB_ALGO) {
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(xfr->name, zname);
 			verbose(VERB_ALGO, "auth zone %s soa probe failed, wait to retry", zname);
 		}
@@ -6791,14 +6784,14 @@ void auth_xfer_probe_lookup_callback(voi
 					lookup_target, answer, wanted_qtype);
 			} else {
 				if(verbosity >= VERB_ALGO) {
-					char zname[255+1];
+					char zname[LDNS_MAX_DOMAINLEN];
 					dname_str(xfr->name, zname);
 					verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup has nodata", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A"));
 				}
 			}
 		} else {
 			if(verbosity >= VERB_ALGO) {
-				char zname[255+1];
+				char zname[LDNS_MAX_DOMAINLEN];
 				dname_str(xfr->name, zname);
 				verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup has no address", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A"));
 			}
@@ -6806,7 +6799,7 @@ void auth_xfer_probe_lookup_callback(voi
 		regional_free_all(temp);
 	} else {
 		if(verbosity >= VERB_ALGO) {
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(xfr->name, zname);
 			verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup failed", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A"));
 		}
@@ -6980,7 +6973,7 @@ xfr_set_timeout(struct auth_xfer* xfr, s
 		if(!xfr->task_nextprobe->timer) {
 			/* failed to malloc memory. likely zone transfer
 			 * also fails for that. skip the timeout */
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(xfr->name, zname);
 			log_err("cannot allocate timer, no refresh for %s",
 				zname);
@@ -7001,7 +6994,7 @@ xfr_set_timeout(struct auth_xfer* xfr, s
 			xfr->task_probe->only_lookup = 1;
 	}
 	if(verbosity >= VERB_ALGO) {
-		char zname[255+1];
+		char zname[LDNS_MAX_DOMAINLEN];
 		dname_str(xfr->name, zname);
 		verbose(VERB_ALGO, "auth zone %s timeout in %d seconds",
 			zname, (int)tv.tv_sec);
@@ -7010,6 +7003,18 @@ xfr_set_timeout(struct auth_xfer* xfr, s
 	comm_timer_set(xfr->task_nextprobe->timer, &tv);
 }
 
+void auth_xfer_pickup_initial_zone(struct auth_xfer* x, struct module_env* env)
+{
+	/* set lease_time, because we now have timestamp in env,
+	 * (not earlier during startup and apply_cfg), and this
+	 * notes the start time when the data was acquired */
+	if(x->have_zone)
+		x->lease_time = *env->now;
+	if(x->task_nextprobe && x->task_nextprobe->worker == NULL) {
+		xfr_set_timeout(x, env, 0, 1);
+	}
+}
+
 /** initial pick up of worker timeouts, ties events to worker event loop */
 void
 auth_xfer_pickup_initial(struct auth_zones* az, struct module_env* env)
@@ -7018,14 +7023,7 @@ auth_xfer_pickup_initial(struct auth_zon
 	lock_rw_wrlock(&az->lock);
 	RBTREE_FOR(x, struct auth_xfer*, &az->xtree) {
 		lock_basic_lock(&x->lock);
-		/* set lease_time, because we now have timestamp in env,
-		 * (not earlier during startup and apply_cfg), and this
-		 * notes the start time when the data was acquired */
-		if(x->have_zone)
-			x->lease_time = *env->now;
-		if(x->task_nextprobe && x->task_nextprobe->worker == NULL) {
-			xfr_set_timeout(x, env, 0, 1);
-		}
+		auth_xfer_pickup_initial_zone(x, env);
 		lock_basic_unlock(&x->lock);
 	}
 	lock_rw_unlock(&az->lock);
@@ -7788,7 +7786,7 @@ static void auth_zone_log(uint8_t* name,
 	va_list args;
 	va_start(args, format);
 	if(verbosity >= level) {
-		char str[255+1];
+		char str[LDNS_MAX_DOMAINLEN];
 		char msg[MAXSYSLOGMSGLEN];
 		dname_str(name, str);
 		vsnprintf(msg, sizeof(msg), format, args);
@@ -7990,7 +7988,7 @@ static int zonemd_check_dnssec_soazonemd
 static void auth_zone_zonemd_fail(struct auth_zone* z, struct module_env* env,
 	char* reason, char* why_bogus, char** result)
 {
-	char zstr[255+1];
+	char zstr[LDNS_MAX_DOMAINLEN];
 	/* if fail: log reason, and depending on config also take action
 	 * and drop the zone, eg. it is gone from memory, set zone_expired */
 	dname_str(z->name, zstr);
@@ -8436,7 +8434,7 @@ zonemd_lookup_dnskey(struct auth_zone* z
 	qinfo.local_alias = NULL;
 	if(verbosity >= VERB_ALGO) {
 		char buf1[512];
-		char buf2[LDNS_MAX_DOMAINLEN+1];
+		char buf2[LDNS_MAX_DOMAINLEN];
 		dname_str(z->name, buf2);
 		snprintf(buf1, sizeof(buf1), "auth zone %s: lookup %s "
 			"for zonemd verification", buf2,
@@ -8583,4 +8581,162 @@ void auth_zones_pickup_zonemd_verify(str
 			break;
 	}
 	lock_rw_unlock(&az->lock);
+}
+
+/** Get memory usage of auth rrset */
+static size_t
+auth_rrset_get_mem(struct auth_rrset* rrset)
+{
+	size_t m = sizeof(*rrset) + packed_rrset_sizeof(rrset->data);
+	return m;
+}
+
+/** Get memory usage of auth data */
+static size_t
+auth_data_get_mem(struct auth_data* node)
+{
+	size_t m = sizeof(*node) + node->namelen;
+	struct auth_rrset* rrset;
+	for(rrset = node->rrsets; rrset; rrset = rrset->next) {
+		m += auth_rrset_get_mem(rrset);
+	}
+	return m;
+}
+
+/** Get memory usage of auth zone */
+static size_t
+auth_zone_get_mem(struct auth_zone* z)
+{
+	size_t m = sizeof(*z) + z->namelen;
+	struct auth_data* node;
+	if(z->zonefile)
+		m += strlen(z->zonefile)+1;
+	RBTREE_FOR(node, struct auth_data*, &z->data) {
+		m += auth_data_get_mem(node);
+	}
+	if(z->rpz)
+		m += rpz_get_mem(z->rpz);
+	return m;
+}
+
+/** Get memory usage of list of auth addr */
+static size_t
+auth_addrs_get_mem(struct auth_addr* list)
+{
+	size_t m = 0;
+	struct auth_addr* a;
+	for(a = list; a; a = a->next) {
+		m += sizeof(*a);
+	}
+	return m;
+}
+
+/** Get memory usage of list of primaries for auth xfer */
+static size_t
+auth_primaries_get_mem(struct auth_master* list)
+{
+	size_t m = 0;
+	struct auth_master* n;
+	for(n = list; n; n = n->next) {
+		m += sizeof(*n);
+		m += auth_addrs_get_mem(n->list);
+		if(n->host)
+			m += strlen(n->host)+1;
+		if(n->file)
+			m += strlen(n->file)+1;
+	}
+	return m;
+}
+
+/** Get memory usage or list of auth chunks */
+static size_t
+auth_chunks_get_mem(struct auth_chunk* list)
+{
+	size_t m = 0;
+	struct auth_chunk* chunk;
+	for(chunk = list; chunk; chunk = chunk->next) {
+		m += sizeof(*chunk) + chunk->len;
+	}
+	return m;
+}
+
+/** Get memory usage of auth xfer */
+static size_t
+auth_xfer_get_mem(struct auth_xfer* xfr)
+{
+	size_t m = sizeof(*xfr) + xfr->namelen;
+
+	/* auth_nextprobe */
+	m += comm_timer_get_mem(xfr->task_nextprobe->timer);
+
+	/* auth_probe */
+	m += auth_primaries_get_mem(xfr->task_probe->masters);
+	m += comm_point_get_mem(xfr->task_probe->cp);
+	m += comm_timer_get_mem(xfr->task_probe->timer);
+
+	/* auth_transfer */
+	m += auth_chunks_get_mem(xfr->task_transfer->chunks_first);
+	m += auth_primaries_get_mem(xfr->task_transfer->masters);
+	m += comm_point_get_mem(xfr->task_transfer->cp);
+	m += comm_timer_get_mem(xfr->task_transfer->timer);
+
+	/* allow_notify_list */
+	m += auth_primaries_get_mem(xfr->allow_notify_list);
+
+	return m;
+}
+
+/** Get memory usage of auth zones ztree */
+static size_t
+az_ztree_get_mem(struct auth_zones* az)
+{
+	size_t m = 0;
+	struct auth_zone* z;
+	RBTREE_FOR(z, struct auth_zone*, &az->ztree) {
+		lock_rw_rdlock(&z->lock);
+		m += auth_zone_get_mem(z);
+		lock_rw_unlock(&z->lock);
+	}
+	return m;
+}
+
+/** Get memory usage of auth zones xtree */
+static size_t
+az_xtree_get_mem(struct auth_zones* az)
+{
+	size_t m = 0;
+	struct auth_xfer* xfr;
+	RBTREE_FOR(xfr, struct auth_xfer*, &az->xtree) {
+		lock_basic_lock(&xfr->lock);
+		m += auth_xfer_get_mem(xfr);
+		lock_basic_unlock(&xfr->lock);
+	}
+	return m;
+}
+
+size_t auth_zones_get_mem(struct auth_zones* zones)
+{
+	size_t m;
+	if(!zones) return 0;
+	m = sizeof(*zones);
+	lock_rw_rdlock(&zones->rpz_lock);
+	lock_rw_rdlock(&zones->lock);
+	m += az_ztree_get_mem(zones);
+	m += az_xtree_get_mem(zones);
+	lock_rw_unlock(&zones->lock);
+	lock_rw_unlock(&zones->rpz_lock);
+	return m;
+}
+
+void xfr_disown_tasks(struct auth_xfer* xfr, struct worker* worker)
+{
+	if(xfr->task_nextprobe->worker == worker) {
+		xfr_nextprobe_disown(xfr);
+	}
+	if(xfr->task_probe->worker == worker) {
+		xfr_probe_disown(xfr);
+	}
+	if(xfr->task_transfer->worker == worker) {
+		xfr_transfer_disown(xfr);
+	}
 }
Index: services/authzone.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/authzone.h,v
diff -u -p -r1.13 authzone.h
--- services/authzone.h	7 Jun 2022 15:42:53 -0000	1.13
+++ services/authzone.h	30 Aug 2025 11:52:23 -0000
@@ -70,7 +70,8 @@ struct auth_chunk;
  * Authoritative zones, shared.
  */
 struct auth_zones {
-	/** lock on the authzone trees */
+	/** lock on the authzone trees. It is locked after views, respip,
+	 * local_zones and before fwds and stubs. */
 	lock_rw_type lock;
 	/** rbtree of struct auth_zone */
 	rbtree_type ztree;
@@ -78,10 +79,6 @@ struct auth_zones {
 	rbtree_type xtree;
 	/** do we have downstream enabled */
 	int have_downstream;
-	/** number of queries upstream */
-	size_t num_query_up;
-	/** number of queries downstream */
-	size_t num_query_down;
 	/** first auth zone containing rpz item in linked list */
 	struct auth_zone* rpz_first;
 	/** rw lock for rpz linked list, needed when iterating or editing linked
@@ -211,7 +208,9 @@ struct auth_xfer {
 	 * one of the tasks. 
 	 * Once it has the task assigned to it, the worker can access the
 	 * other elements of the task structure without a lock, because that
-	 * is necessary for the eventloop and callbacks from that. */
+	 * is necessary for the eventloop and callbacks from that.
+	 * The auth_zone->lock is locked before this lock.
+	 */
 	lock_basic_type lock;
 
 	/** zone name, in uncompressed wireformat */
@@ -786,5 +785,34 @@ void auth_zonemd_dnskey_lookup_callback(
  */
 void auth_zones_pickup_zonemd_verify(struct auth_zones* az,
 	struct module_env* env);
+
+/** Get memory usage for auth zones. The routine locks and unlocks
+ * for reading. */
+size_t auth_zones_get_mem(struct auth_zones* zones);
+
+/**
+ * Initial pick up of the auth zone nextprobe timeout and that turns
+ * into further zone transfer work, if any. Also sets the lease time.
+ * @param x: xfer structure, locked by caller.
+ * @param env: environment of the worker that picks up the task.
+ */
+void auth_xfer_pickup_initial_zone(struct auth_xfer* x,
+	struct module_env* env);
+
+/**
+ * Delete auth xfer structure
+ * @param xfr: delete this xfer and its tasks.
+ */
+void auth_xfer_delete(struct auth_xfer* xfr);
+
+/**
+ * Disown tasks from the xfr that belong to this worker.
+ * Only tasks for the worker in question, the comm point and timer
+ * delete functions need to run in the thread of that worker to be
+ * able to delete the callback from the event base.
+ * @param xfr: xfr structure
+ * @param worker: the worker for which to stop tasks.
+ */
+void xfr_disown_tasks(struct auth_xfer* xfr, struct worker* worker);
 
 #endif /* SERVICES_AUTHZONE_H */
Index: services/listen_dnsport.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/listen_dnsport.c,v
diff -u -p -r1.38 listen_dnsport.c
--- services/listen_dnsport.c	21 Feb 2025 13:20:40 -0000	1.38
+++ services/listen_dnsport.c	30 Aug 2025 11:52:23 -0000
@@ -703,7 +703,10 @@ create_tcp_accept_sock(struct addrinfo *
 {
 	int s = -1;
 	char* err;
-#if defined(SO_REUSEADDR) || defined(SO_REUSEPORT) || defined(IPV6_V6ONLY) || defined(IP_TRANSPARENT) || defined(IP_BINDANY) || defined(IP_FREEBIND) || defined(SO_BINDANY)
+#if defined(SO_REUSEADDR) || defined(SO_REUSEPORT)		\
+	|| defined(IPV6_V6ONLY) || defined(IP_TRANSPARENT)	\
+	|| defined(IP_BINDANY) || defined(IP_FREEBIND)		\
+	|| defined(SO_BINDANY) || defined(TCP_NODELAY)
 	int on = 1;
 #endif
 #ifdef HAVE_SYSTEMD
@@ -1031,7 +1034,7 @@ err:
  * Create socket from getaddrinfo results
  */
 static int
-make_sock(int stype, const char* ifname, const char* port,
+make_sock(int stype, const char* ifname, int port,
 	struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd,
 	int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind,
 	int use_systemd, int dscp, struct unbound_socket* ub_sock,
@@ -1039,9 +1042,11 @@ make_sock(int stype, const char* ifname,
 {
 	struct addrinfo *res = NULL;
 	int r, s, inuse, noproto;
+	char portbuf[32];
+	snprintf(portbuf, sizeof(portbuf), "%d", port);
 	hints->ai_socktype = stype;
 	*noip6 = 0;
-	if((r=getaddrinfo(ifname, port, hints, &res)) != 0 || !res) {
+	if((r=getaddrinfo(ifname, portbuf, hints, &res)) != 0 || !res) {
 #ifdef USE_WINSOCK
 		if(r == EAI_NONAME && hints->ai_family == AF_INET6){
 			*noip6 = 1; /* 'Host not found' for IP6 on winXP */
@@ -1049,7 +1054,7 @@ make_sock(int stype, const char* ifname,
 		}
 #endif
 		log_err("node %s:%s getaddrinfo: %s %s",
-			ifname?ifname:"default", port, gai_strerror(r),
+			ifname?ifname:"default", portbuf, gai_strerror(r),
 #ifdef EAI_SYSTEM
 			(r==EAI_SYSTEM?(char*)strerror(errno):"")
 #else
@@ -1103,7 +1108,7 @@ make_sock(int stype, const char* ifname,
 
 /** make socket and first see if ifname contains port override info */
 static int
-make_sock_port(int stype, const char* ifname, const char* port,
+make_sock_port(int stype, const char* ifname, int port,
 	struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd,
 	int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind,
 	int use_systemd, int dscp, struct unbound_socket* ub_sock,
@@ -1112,23 +1117,22 @@ make_sock_port(int stype, const char* if
 	char* s = strchr(ifname, '@');
 	if(s) {
 		/* override port with ifspec@port */
-		char p[16];
+		int port;
 		char newif[128];
 		if((size_t)(s-ifname) >= sizeof(newif)) {
 			log_err("ifname too long: %s", ifname);
 			*noip6 = 0;
 			return -1;
 		}
-		if(strlen(s+1) >= sizeof(p)) {
-			log_err("portnumber too long: %s", ifname);
+		port = atoi(s+1);
+		if(port < 0 || 0 == port || port > 65535) {
+			log_err("invalid portnumber in interface: %s", ifname);
 			*noip6 = 0;
 			return -1;
 		}
 		(void)strlcpy(newif, ifname, sizeof(newif));
 		newif[s-ifname] = 0;
-		(void)strlcpy(p, s+1, sizeof(p));
-		p[strlen(s+1)]=0;
-		return make_sock(stype, newif, p, hints, v6only, noip6, rcv,
+		return make_sock(stype, newif, port, hints, v6only, noip6, rcv,
 			snd, reuseport, transparent, tcp_mss, nodelay, freebind,
 			use_systemd, dscp, ub_sock, additional);
 	}
@@ -1237,26 +1241,6 @@ set_recvpktinfo(int s, int family)
 	return 1;
 }
 
-/** see if interface is ssl, its port number == the ssl port number */
-static int
-if_is_ssl(const char* ifname, const char* port, int ssl_port,
-	struct config_strlist* tls_additional_port)
-{
-	struct config_strlist* s;
-	char* p = strchr(ifname, '@');
-	if(!p && atoi(port) == ssl_port)
-		return 1;
-	if(p && atoi(p+1) == ssl_port)
-		return 1;
-	for(s = tls_additional_port; s; s = s->next) {
-		if(p && atoi(p+1) == atoi(s->str))
-			return 1;
-		if(!p && atoi(port) == atoi(s->str))
-			return 1;
-	}
-	return 0;
-}
-
 /**
  * Helper for ports_open. Creates one interface (or NULL for default).
  * @param ifname: The interface ip address.
@@ -1265,7 +1249,7 @@ if_is_ssl(const char* ifname, const char
  * @param do_udp: if udp should be used.
  * @param do_tcp: if tcp should be used.
  * @param hints: for getaddrinfo. family and flags have to be set by caller.
- * @param port: Port number to use (as string).
+ * @param port: Port number to use.
  * @param list: list of open ports, appended to, changed to point to list head.
  * @param rcv: receive buffer size for UDP
  * @param snd: send buffer size for UDP
@@ -1291,7 +1275,7 @@ if_is_ssl(const char* ifname, const char
  */
 static int
 ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp,
-	struct addrinfo *hints, const char* port, struct listen_port** list,
+	struct addrinfo *hints, int port, struct listen_port** list,
 	size_t rcv, size_t snd, int ssl_port,
 	struct config_strlist* tls_additional_port, int https_port,
 	struct config_strlist* proxy_protocol_port,
@@ -1300,12 +1284,18 @@ ports_create_if(const char* ifname, int 
 	int quic_port, int http_notls_downstream, int sock_queue_timeout)
 {
 	int s, noip6=0;
+	int is_ssl = if_is_ssl(ifname, port, ssl_port, tls_additional_port);
 	int is_https = if_is_https(ifname, port, https_port);
 	int is_dnscrypt = if_is_dnscrypt(ifname, port, dnscrypt_port);
 	int is_pp2 = if_is_pp2(ifname, port, proxy_protocol_port);
-	int nodelay = is_https && http2_nodelay;
-	struct unbound_socket* ub_sock;
 	int is_doq = if_is_quic(ifname, port, quic_port);
+	/* Always set TCP_NODELAY on TLS connection as it speeds up the TLS
+	 * handshake. DoH had already such option so we respect it.
+	 * Otherwise the server waits before sending more handshake data for
+	 * the client ACK (Nagle's algorithm), which is delayed because the
+	 * client waits for more data before ACKing (delayed ACK). */
+	int nodelay = is_https?http2_nodelay:is_ssl; 
+	struct unbound_socket* ub_sock;
 	const char* add = NULL;
 
 	if(!do_udp && !do_tcp)
@@ -1324,6 +1314,12 @@ ports_create_if(const char* ifname, int 
 		}
 	}
 
+	/* Check if both UDP and TCP ports should be open.
+	 * In the case of encrypted channels, probably an unencrypted channel
+	 * at the same port is not desired. */
+	if((is_ssl || is_https) && !is_doq) do_udp = do_auto = 0;
+	if((is_doq) && !(is_https || is_ssl)) do_tcp = 0;
+
 	if(do_auto) {
 		ub_sock = calloc(1, sizeof(struct unbound_socket));
 		if(!ub_sock)
@@ -1369,13 +1365,11 @@ ports_create_if(const char* ifname, int 
 		} else if(is_doq) {
 			udp_port_type = listen_type_doq;
 			add = "doq";
-			if(((strchr(ifname, '@') &&
-				atoi(strchr(ifname, '@')+1) == 53) ||
-				(!strchr(ifname, '@') && atoi(port) == 53))) {
-				log_err("DNS over QUIC is not allowed on "
-					"port 53. Port 53 is for DNS "
-					"datagrams. Error for "
-					"interface '%s'.", ifname);
+			if(if_listens_on(ifname, port, 53, NULL)) {
+				log_err("DNS over QUIC is strictly not "
+					"allowed on port 53 as per RFC 9250. "
+					"Port 53 is for DNS datagrams. Error "
+					"for interface '%s'.", ifname);
 				free(ub_sock->addr);
 				free(ub_sock);
 				return 0;
@@ -1423,8 +1417,6 @@ ports_create_if(const char* ifname, int 
 		}
 	}
 	if(do_tcp) {
-		int is_ssl = if_is_ssl(ifname, port, ssl_port,
-			tls_additional_port);
 		enum listen_type port_type;
 		ub_sock = calloc(1, sizeof(struct unbound_socket));
 		if(!ub_sock)
@@ -1523,9 +1515,10 @@ listen_create(struct comm_base* base, st
 	size_t bufsize, int tcp_accept_count, int tcp_idle_timeout,
 	int harden_large_queries, uint32_t http_max_streams,
 	char* http_endpoint, int http_notls, struct tcl_list* tcp_conn_limit,
-	void* sslctx, struct dt_env* dtenv, struct doq_table* doq_table,
-	struct ub_randstate* rnd, const char* ssl_service_key,
-	const char* ssl_service_pem, struct config_file* cfg,
+	void* dot_sslctx, void* doh_sslctx, void* quic_sslctx,
+	struct dt_env* dtenv,
+	struct doq_table* doq_table,
+	struct ub_randstate* rnd,struct config_file* cfg,
 	comm_point_callback_type* cb, void *cb_arg)
 {
 	struct listen_dnsport* front = (struct listen_dnsport*)
@@ -1558,8 +1551,7 @@ listen_create(struct comm_base* base, st
 #endif
 			cp = comm_point_create_doq(base, ports->fd,
 				front->udp_buff, cb, cb_arg, ports->socket,
-				doq_table, rnd, ssl_service_key,
-				ssl_service_pem, cfg);
+				doq_table, rnd, quic_sslctx, cfg);
 		} else if(ports->ftype == listen_type_tcp ||
 				ports->ftype == listen_type_tcp_dnscrypt) {
 			cp = comm_point_create_tcp(base, ports->fd,
@@ -1578,7 +1570,7 @@ listen_create(struct comm_base* base, st
 				ports->ftype, ports->pp2_enabled, cb, cb_arg,
 				ports->socket);
 			if(ports->ftype == listen_type_http) {
-				if(!sslctx && !http_notls) {
+				if(!doh_sslctx && !http_notls) {
 					log_warn("HTTPS port configured, but "
 						"no TLS tls-service-key or "
 						"tls-service-pem set");
@@ -1620,10 +1612,15 @@ listen_create(struct comm_base* base, st
 			(ports->ftype == listen_type_udpancil) ||
 			(ports->ftype == listen_type_tcp_dnscrypt) ||
 			(ports->ftype == listen_type_udp_dnscrypt) ||
-			(ports->ftype == listen_type_udpancil_dnscrypt))
+			(ports->ftype == listen_type_udpancil_dnscrypt)) {
 			cp->ssl = NULL;
-		else
-			cp->ssl = sslctx;
+		} else if(ports->ftype == listen_type_doq) {
+			cp->ssl = quic_sslctx;
+		} else if(ports->ftype == listen_type_http) {
+			cp->ssl = doh_sslctx;
+		} else {
+			cp->ssl = dot_sslctx;
+		}
 		cp->dtenv = dtenv;
 		cp->do_not_close = 1;
 #ifdef USE_DNSCRYPT
@@ -1887,8 +1884,6 @@ listening_ports_open(struct config_file*
 	struct addrinfo hints;
 	int i, do_ip4, do_ip6;
 	int do_tcp, do_auto;
-	char portbuf[32];
-	snprintf(portbuf, sizeof(portbuf), "%d", cfg->port);
 	do_ip4 = cfg->do_ip4;
 	do_ip6 = cfg->do_ip6;
 	do_tcp = cfg->do_tcp;
@@ -1934,12 +1929,11 @@ listening_ports_open(struct config_file*
 					return NULL;
 				}
 				now = after;
-				snprintf(portbuf, sizeof(portbuf), "%d", extraport);
 				if(do_ip6) {
 					hints.ai_family = AF_INET6;
 					if(!ports_create_if("::0",
 						do_auto, cfg->do_udp, do_tcp,
-						&hints, portbuf, &list,
+						&hints, extraport, &list,
 						cfg->so_rcvbuf, cfg->so_sndbuf,
 						cfg->ssl_port, cfg->tls_additional_port,
 						cfg->https_port,
@@ -1958,7 +1952,7 @@ listening_ports_open(struct config_file*
 					hints.ai_family = AF_INET;
 					if(!ports_create_if("0.0.0.0",
 						do_auto, cfg->do_udp, do_tcp,
-						&hints, portbuf, &list,
+						&hints, extraport, &list,
 						cfg->so_rcvbuf, cfg->so_sndbuf,
 						cfg->ssl_port, cfg->tls_additional_port,
 						cfg->https_port,
@@ -1980,7 +1974,7 @@ listening_ports_open(struct config_file*
 			hints.ai_family = AF_INET6;
 			if(!ports_create_if(do_auto?"::0":"::1",
 				do_auto, cfg->do_udp, do_tcp,
-				&hints, portbuf, &list,
+				&hints, cfg->port, &list,
 				cfg->so_rcvbuf, cfg->so_sndbuf,
 				cfg->ssl_port, cfg->tls_additional_port,
 				cfg->https_port, cfg->proxy_protocol_port,
@@ -1998,7 +1992,7 @@ listening_ports_open(struct config_file*
 			hints.ai_family = AF_INET;
 			if(!ports_create_if(do_auto?"0.0.0.0":"127.0.0.1",
 				do_auto, cfg->do_udp, do_tcp,
-				&hints, portbuf, &list,
+				&hints, cfg->port, &list,
 				cfg->so_rcvbuf, cfg->so_sndbuf,
 				cfg->ssl_port, cfg->tls_additional_port,
 				cfg->https_port, cfg->proxy_protocol_port,
@@ -2018,7 +2012,7 @@ listening_ports_open(struct config_file*
 				continue;
 			hints.ai_family = AF_INET6;
 			if(!ports_create_if(ifs[i], 0, cfg->do_udp,
-				do_tcp, &hints, portbuf, &list,
+				do_tcp, &hints, cfg->port, &list,
 				cfg->so_rcvbuf, cfg->so_sndbuf,
 				cfg->ssl_port, cfg->tls_additional_port,
 				cfg->https_port, cfg->proxy_protocol_port,
@@ -2036,7 +2030,7 @@ listening_ports_open(struct config_file*
 				continue;
 			hints.ai_family = AF_INET;
 			if(!ports_create_if(ifs[i], 0, cfg->do_udp,
-				do_tcp, &hints, portbuf, &list,
+				do_tcp, &hints, cfg->port, &list,
 				cfg->so_rcvbuf, cfg->so_sndbuf,
 				cfg->ssl_port, cfg->tls_additional_port,
 				cfg->https_port, cfg->proxy_protocol_port,
@@ -4598,10 +4592,9 @@ doq_alpn_select_cb(SSL* ATTR_UNUSED(ssl)
 	return SSL_TLSEXT_ERR_ALERT_FATAL;
 }
 
-/** create new tls session for server doq connection */
-static SSL_CTX*
-doq_ctx_server_setup(struct doq_server_socket* doq_socket)
+void* quic_sslctx_create(char* key, char* pem, char* verifypem)
 {
+#ifdef HAVE_NGTCP2
 	char* sid_ctx = "unbound server";
 #ifndef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_SERVER_CONTEXT
 	SSL_QUIC_METHOD* quic_method;
@@ -4611,6 +4604,16 @@ doq_ctx_server_setup(struct doq_server_s
 		log_crypto_err("Could not SSL_CTX_new");
 		return NULL;
 	}
+	if(!key || key[0] == 0) {
+		log_err("doq: error, no tls-service-key file specified");
+		SSL_CTX_free(ctx);
+		return NULL;
+	}
+	if(!pem || pem[0] == 0) {
+		log_err("doq: error, no tls-service-pem file specified");
+		SSL_CTX_free(ctx);
+		return NULL;
+	}
 	SSL_CTX_set_options(ctx,
 		(SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
 		SSL_OP_SINGLE_ECDH_USE |
@@ -4623,43 +4626,37 @@ doq_ctx_server_setup(struct doq_server_s
 	SSL_CTX_set_alpn_select_cb(ctx, doq_alpn_select_cb, NULL);
 #endif
 	SSL_CTX_set_default_verify_paths(ctx);
-	if(!SSL_CTX_use_certificate_chain_file(ctx,
-		doq_socket->ssl_service_pem)) {
-		log_err("doq: error for cert file: %s",
-			doq_socket->ssl_service_pem);
+	if(!SSL_CTX_use_certificate_chain_file(ctx, pem)) {
+		log_err("doq: error for cert file: %s", pem);
 		log_crypto_err("doq: error in "
 			"SSL_CTX_use_certificate_chain_file");
 		SSL_CTX_free(ctx);
 		return NULL;
 	}
-	if(!SSL_CTX_use_PrivateKey_file(ctx, doq_socket->ssl_service_key,
-		SSL_FILETYPE_PEM)) {
-		log_err("doq: error for private key file: %s",
-			doq_socket->ssl_service_key);
+	if(!SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM)) {
+		log_err("doq: error for private key file: %s", key);
 		log_crypto_err("doq: error in SSL_CTX_use_PrivateKey_file");
 		SSL_CTX_free(ctx);
 		return NULL;
 	}
 	if(!SSL_CTX_check_private_key(ctx)) {
-		log_err("doq: error for key file: %s",
-			doq_socket->ssl_service_key);
+		log_err("doq: error for key file: %s", key);
 		log_crypto_err("doq: error in SSL_CTX_check_private_key");
 		SSL_CTX_free(ctx);
 		return NULL;
 	}
 	SSL_CTX_set_session_id_context(ctx, (void*)sid_ctx, strlen(sid_ctx));
-	if(doq_socket->ssl_verify_pem && doq_socket->ssl_verify_pem[0]) {
-		if(!SSL_CTX_load_verify_locations(ctx,
-			doq_socket->ssl_verify_pem, NULL)) {
+	if(verifypem && verifypem[0]) {
+		if(!SSL_CTX_load_verify_locations(ctx, verifypem, NULL)) {
 			log_err("doq: error for verify pem file: %s",
-				doq_socket->ssl_verify_pem);
+				verifypem);
 			log_crypto_err("doq: error in "
 				"SSL_CTX_load_verify_locations");
 			SSL_CTX_free(ctx);
 			return NULL;
 		}
 		SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(
-			doq_socket->ssl_verify_pem));
+			verifypem));
 		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|
 			SSL_VERIFY_CLIENT_ONCE|
 			SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
@@ -4672,7 +4669,7 @@ doq_ctx_server_setup(struct doq_server_s
 		SSL_CTX_free(ctx);
 		return NULL;
 	}
-#else
+#else /* HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_SERVER_CONTEXT */
 	/* The quic_method needs to remain valid during the SSL_CTX
 	 * lifetime, so we allocate it. It is freed with the
 	 * doq_server_socket. */
@@ -4690,6 +4687,10 @@ doq_ctx_server_setup(struct doq_server_s
 	SSL_CTX_set_quic_method(ctx, doq_socket->quic_method);
 #endif
 	return ctx;
+#else /* HAVE_NGTCP2 */
+	(void)key; (void)pem; (void)verifypem;
+	return NULL;
+#endif /* HAVE_NGTCP2 */
 }
 
 /** Get the ngtcp2_conn from ssl userdata of type ngtcp2_conn_ref */
@@ -4718,16 +4719,6 @@ doq_ssl_server_setup(SSL_CTX* ctx, struc
 	SSL_set_accept_state(ssl);
 	SSL_set_quic_early_data_enabled(ssl, 1);
 	return ssl;
-}
-
-/** setup the doq_socket server tls context */
-int
-doq_socket_setup_ctx(struct doq_server_socket* doq_socket)
-{
-	doq_socket->ctx = doq_ctx_server_setup(doq_socket);
-	if(!doq_socket->ctx)
-		return 0;
-	return 1;
 }
 
 int
Index: services/listen_dnsport.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/listen_dnsport.h,v
diff -u -p -r1.21 listen_dnsport.h
--- services/listen_dnsport.h	21 Feb 2025 13:20:40 -0000	1.21
+++ services/listen_dnsport.h	30 Aug 2025 11:52:23 -0000
@@ -194,12 +194,12 @@ int resolve_interface_names(char** ifs, 
  * @param http_endpoint: HTTP endpoint to service queries on
  * @param http_notls: no TLS for http downstream
  * @param tcp_conn_limit: TCP connection limit info.
- * @param sslctx: nonNULL if ssl context.
+ * @param dot_sslctx: nonNULL if dot ssl context.
+ * @param doh_sslctx: nonNULL if doh ssl context.
+ * @param quic_sslctx: nonNULL if quic ssl context.
  * @param dtenv: nonNULL if dnstap enabled.
  * @param doq_table: the doq connection table, with shared information.
  * @param rnd: random state.
- * @param ssl_service_key: the SSL service key file.
- * @param ssl_service_pem: the SSL service pem file.
  * @param cfg: config file struct.
  * @param cb: callback function when a request arrives. It is passed
  *	  the packet and user argument. Return true to send a reply.
@@ -211,9 +211,10 @@ listen_create(struct comm_base* base, st
 	size_t bufsize, int tcp_accept_count, int tcp_idle_timeout,
 	int harden_large_queries, uint32_t http_max_streams,
 	char* http_endpoint, int http_notls, struct tcl_list* tcp_conn_limit,
-	void* sslctx, struct dt_env* dtenv, struct doq_table* doq_table,
-	struct ub_randstate* rnd, const char* ssl_service_key,
-	const char* ssl_service_pem, struct config_file* cfg,
+	void* dot_sslctx, void* doh_sslctx, void* quic_sslctx,
+	struct dt_env* dtenv,
+	struct doq_table* doq_table,
+	struct ub_randstate* rnd,struct config_file* cfg,
 	comm_point_callback_type* cb, void *cb_arg);
 
 /**
@@ -512,6 +513,15 @@ struct doq_table {
 	size_t current_size;
 };
 
+/**
+ * create SSL context for QUIC
+ * @param key: private key file.
+ * @param pem: public key cert.
+ * @param verifypem: if nonNULL, verifylocation file.
+ * return SSL_CTX* or NULL on failure (logged).
+ */
+void* quic_sslctx_create(char* key, char* pem, char* verifypem);
+
 /** create doq table */
 struct doq_table* doq_table_create(struct config_file* cfg,
 	struct ub_randstate* rnd);
@@ -711,9 +721,6 @@ int doq_timer_cmp(const void* key1, cons
 
 /** compare function of doq_stream */
 int doq_stream_cmp(const void* key1, const void* key2);
-
-/** setup the doq_socket server tls context */
-int doq_socket_setup_ctx(struct doq_server_socket* doq_socket);
 
 /** setup the doq connection callbacks, and settings. */
 int doq_conn_setup(struct doq_conn* conn, uint8_t* scid, size_t scidlen,
Index: services/localzone.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/localzone.c,v
diff -u -p -r1.23 localzone.c
--- services/localzone.c	4 Sep 2024 09:36:40 -0000	1.23
+++ services/localzone.c	30 Aug 2025 11:52:23 -0000
@@ -223,7 +223,7 @@ lz_enter_zone_dname(struct local_zones* 
 	lock_rw_wrlock(&z->lock);
 	if(!rbtree_insert(&zones->ztree, &z->node)) {
 		struct local_zone* oldz;
-		char str[256];
+		char str[LDNS_MAX_DOMAINLEN];
 		dname_str(nm, str);
 		log_warn("duplicate local-zone %s", str);
 		lock_rw_unlock(&z->lock);
@@ -943,6 +943,16 @@ int local_zone_enter_defaults(struct loc
 		log_err("out of memory adding default zone");
 		return 0;
 	}
+	/* resolver.arpa. zone (RFC 9462) */
+	if(!add_empty_default(zones, cfg, "resolver.arpa.")) {
+		log_err("out of memory adding default zone");
+		return 0;
+	}
+	/* service.arpa. zone (draft-ietf-dnssd-srp-25) */
+	if(!add_empty_default(zones, cfg, "service.arpa.")) {
+		log_err("out of memory adding default zone");
+		return 0;
+	}
 	/* onion. zone (RFC 7686) */
 	if(!add_empty_default(zones, cfg, "onion.")) {
 		log_err("out of memory adding default zone");
@@ -1765,7 +1775,7 @@ lz_inform_print(struct local_zone* z, st
 	struct sockaddr_storage* addr, socklen_t addrlen)
 {
 	char ip[128], txt[512];
-	char zname[LDNS_MAX_DOMAINLEN+1];
+	char zname[LDNS_MAX_DOMAINLEN];
 	uint16_t port = ntohs(((struct sockaddr_in*)addr)->sin_port);
 	dname_str(z->name, zname);
 	addr_to_str(addr, addrlen, ip, sizeof(ip));
@@ -1875,7 +1885,7 @@ local_zones_answer(struct local_zones* z
 			return 0;
 		}
 		if(z && verbosity >= VERB_ALGO) {
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(z->name, zname);
 			verbose(VERB_ALGO, "using localzone %s %s from view %s", 
 				zname, local_zone_type2str(lzt), view->name);
@@ -1897,7 +1907,7 @@ local_zones_answer(struct local_zones* z
 			z->override_tree, &tag, tagname, num_tags);
 		lock_rw_unlock(&zones->lock);
 		if(z && verbosity >= VERB_ALGO) {
-			char zname[255+1];
+			char zname[LDNS_MAX_DOMAINLEN];
 			dname_str(z->name, zname);
 			verbose(VERB_ALGO, "using localzone %s %s", zname,
 				local_zone_type2str(lzt));
@@ -2209,4 +2219,36 @@ void local_zones_del_data(struct local_z
 	}
 
 	lock_rw_unlock(&z->lock);
+}
+
+/** Get memory usage for local_zone */
+static size_t
+local_zone_get_mem(struct local_zone* z)
+{
+	size_t m = sizeof(*z);
+	lock_rw_rdlock(&z->lock);
+	m += z->namelen + z->taglen + regional_get_mem(z->region);
+	lock_rw_unlock(&z->lock);
+	return m;
+}
+
+size_t local_zones_get_mem(struct local_zones* zones)
+{
+	struct local_zone* z;
+	size_t m;
+	if(!zones) return 0;
+	m = sizeof(*zones);
+	lock_rw_rdlock(&zones->lock);
+	RBTREE_FOR(z, struct local_zone*, &zones->ztree) {
+		m += local_zone_get_mem(z);
+	}
+	lock_rw_unlock(&zones->lock);
+	return m;
+}
+
+void local_zones_swap_tree(struct local_zones* zones, struct local_zones* data)
+{
+	rbtree_type oldtree = zones->ztree;
+	zones->ztree = data->ztree;
+	data->ztree = oldtree;
 }
Index: services/localzone.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/localzone.h,v
diff -u -p -r1.13 localzone.h
--- services/localzone.h	4 Sep 2024 09:36:40 -0000	1.13
+++ services/localzone.h	30 Aug 2025 11:52:23 -0000
@@ -642,6 +642,20 @@ local_zone_enter_rr(struct local_zone* z
 struct local_data* 
 local_zone_find_data(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs);
 
+/** Get memory usage for local_zones tree. The routine locks and unlocks
+ * the tree for reading. */
+size_t local_zones_get_mem(struct local_zones* zones);
+
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param zones: the local zones structure.
+ * @param data: the data structure used to take elements from. This contains
+ * 	the old elements on return.
+ */
+void local_zones_swap_tree(struct local_zones* zones,
+	struct local_zones* data);
+
 /** Enter a new zone; returns with WRlock
  *  Made public for unit testing
  *  @param zones: the local zones tree
Index: services/mesh.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/mesh.c,v
diff -u -p -r1.31 mesh.c
--- services/mesh.c	21 Feb 2025 13:20:40 -0000	1.31
+++ services/mesh.c	30 Aug 2025 11:52:23 -0000
@@ -77,6 +77,20 @@
 #include <netdb.h>
 #endif
 
+/** Compare two views by name */
+static int
+view_name_compare(const char* v_a, const char* v_b)
+{
+	if(v_a == NULL && v_b == NULL)
+		return 0;
+	/* The NULL name is smaller than if the name is set. */
+	if(v_a == NULL)
+		return -1;
+	if(v_b == NULL)
+		return 1;
+	return strcmp(v_a, v_b);
+}
+
 /**
  * Compare two response-ip client info entries for the purpose of mesh state
  * compare.  It returns 0 if ci_a and ci_b are considered equal; otherwise
@@ -132,12 +146,14 @@ client_info_compare(const struct respip_
 	}
 	if(ci_a->tag_datas != ci_b->tag_datas)
 		return ci_a->tag_datas < ci_b->tag_datas ? -1 : 1;
-	if(ci_a->view != ci_b->view)
-		return ci_a->view < ci_b->view ? -1 : 1;
-	/* For the unbound daemon these should be non-NULL and identical,
-	 * but we check that just in case. */
-	if(ci_a->respip_set != ci_b->respip_set)
-		return ci_a->respip_set < ci_b->respip_set ? -1 : 1;
+	if(ci_a->view || ci_a->view_name || ci_b->view || ci_b->view_name) {
+		/* Compare the views by name. */
+		cmp = view_name_compare(
+			(ci_a->view?ci_a->view->name:ci_a->view_name),
+			(ci_b->view?ci_b->view->name:ci_b->view_name));
+		if(cmp != 0)
+			return cmp;
+	}
 	return 0;
 }
 
@@ -214,6 +230,9 @@ mesh_create(struct module_stack* stack, 
 	mesh->stats_dropped = 0;
 	mesh->ans_expired = 0;
 	mesh->ans_cachedb = 0;
+	mesh->num_queries_discard_timeout = 0;
+	mesh->num_queries_wait_limit = 0;
+	mesh->num_dns_error_reports = 0;
 	mesh->max_reply_states = env->cfg->num_queries_per_thread;
 	mesh->max_forever_states = (mesh->max_reply_states+1)/2;
 #ifndef S_SPLINT_S
@@ -424,7 +443,7 @@ void mesh_new_client(struct mesh_area* m
 		verbose(VERB_ALGO, "Too many queries waiting from the IP. "
 			"dropping incoming query.");
 		comm_point_drop_reply(rep);
-		mesh->stats_dropped++;
+		mesh->num_queries_wait_limit++;
 		return;
 	}
 	if(!unique)
@@ -868,6 +887,78 @@ void mesh_report_reply(struct mesh_area*
 	mesh_run(mesh, e->qstate->mesh_info, event, e);
 }
 
+/** copy strlist to region */
+static struct config_strlist*
+cfg_region_strlist_copy(struct regional* region, struct config_strlist* list)
+{
+	struct config_strlist* result = NULL, *last = NULL, *s = list;
+	while(s) {
+		struct config_strlist* n = regional_alloc_zero(region,
+			sizeof(*n));
+		if(!n)
+			return NULL;
+		n->str = regional_strdup(region, s->str);
+		if(!n->str)
+			return NULL;
+		if(last)
+			last->next = n;
+		else	result = n;
+		last = n;
+		s = s->next;
+	}
+	return result;
+}
+
+/** Copy the client info to the query region. */
+static struct respip_client_info*
+mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo)
+{
+	size_t i;
+	struct respip_client_info* client_info;
+	client_info = regional_alloc_init(region, cinfo, sizeof(*cinfo));
+	if(!client_info)
+		return NULL;
+	/* Copy the client_info so that if the configuration changes,
+	 * then the data stays valid. */
+	if(cinfo->taglist) {
+		client_info->taglist = regional_alloc_init(region, cinfo->taglist,
+			cinfo->taglen);
+		if(!client_info->taglist)
+			return NULL;
+	}
+	if(cinfo->tag_actions) {
+		client_info->tag_actions = regional_alloc_init(region, cinfo->tag_actions,
+			cinfo->tag_actions_size);
+		if(!client_info->tag_actions)
+			return NULL;
+	}
+	if(cinfo->tag_datas) {
+		client_info->tag_datas = regional_alloc_zero(region,
+			sizeof(struct config_strlist*)*cinfo->tag_datas_size);
+		if(!client_info->tag_datas)
+			return NULL;
+		for(i=0; i<cinfo->tag_datas_size; i++) {
+			if(cinfo->tag_datas[i]) {
+				client_info->tag_datas[i] = cfg_region_strlist_copy(
+					region, cinfo->tag_datas[i]);
+				if(!client_info->tag_datas[i])
+					return NULL;
+			}
+		}
+	}
+	if(cinfo->view) {
+		/* Do not copy the view pointer but store a name instead.
+		 * The name is looked up later when done, this means that
+		 * the view tree can be changed, by reloads. */
+		client_info->view = NULL;
+		client_info->view_name = regional_strdup(region,
+			cinfo->view->name);
+		if(!client_info->view_name)
+			return NULL;
+	}
+	return client_info;
+}
+
 struct mesh_state*
 mesh_state_create(struct module_env* env, struct query_info* qinfo,
 	struct respip_client_info* cinfo, uint16_t qflags, int prime,
@@ -908,8 +999,7 @@ mesh_state_create(struct module_env* env
 		return NULL;
 	}
 	if(cinfo) {
-		mstate->s.client_info = regional_alloc_init(region, cinfo,
-			sizeof(*cinfo));
+		mstate->s.client_info = mesh_copy_client_info(region, cinfo);
 		if(!mstate->s.client_info) {
 			alloc_reg_release(env->alloc, region);
 			return NULL;
@@ -1489,8 +1579,119 @@ mesh_send_reply(struct mesh_state* m, in
 			&r->query_reply.client_addr,
 			r->query_reply.client_addrlen, duration, 0, r_buffer,
 			(m->s.env->cfg->log_destaddr?(void*)r->query_reply.c->socket->addr:NULL),
-			r->query_reply.c->type);
+			r->query_reply.c->type, r->query_reply.c->ssl);
+	}
+}
+
+/**
+ * Generate the DNS Error Report (RFC9567).
+ * If there is an EDE attached for this reply and there was a Report-Channel
+ * EDNS0 option from the upstream, fire up a report query.
+ * @param qstate: module qstate.
+ * @param rep: prepared reply to be sent.
+ */
+static void dns_error_reporting(struct module_qstate* qstate,
+	struct reply_info* rep)
+{
+	struct query_info qinfo;
+	struct mesh_state* sub;
+	struct module_qstate* newq;
+	uint8_t buf[LDNS_MAX_DOMAINLEN];
+	size_t count = 0;
+	int written;
+	size_t expected_length;
+	struct edns_option* opt;
+	sldns_ede_code reason_bogus = LDNS_EDE_NONE;
+	sldns_rr_type qtype = qstate->qinfo.qtype;
+	uint8_t* qname = qstate->qinfo.qname;
+	size_t qname_len = qstate->qinfo.qname_len-1; /* skip the trailing \0 */
+	uint8_t* agent_domain;
+	size_t agent_domain_len;
+
+	/* We need a valid reporting agent;
+	 * this is based on qstate->edns_opts_back_in that will probably have
+	 * the latest reporting agent we found while iterating */
+	opt = edns_opt_list_find(qstate->edns_opts_back_in,
+		LDNS_EDNS_REPORT_CHANNEL);
+	if(!opt) return;
+	agent_domain_len = opt->opt_len;
+	agent_domain = opt->opt_data;
+	if(dname_valid(agent_domain, agent_domain_len) < 3) {
+		/* The agent domain needs to be a valid dname that is not the
+		 * root; from RFC9567. */
+		return;
 	}
+
+	/* Get the EDE generated from the mesh state, these are mostly
+	 * validator errors. If other errors are produced in the future (e.g.,
+	 * RPZ) we would not want them to result in error reports. */
+	reason_bogus = errinf_to_reason_bogus(qstate);
+	if(rep && ((reason_bogus == LDNS_EDE_DNSSEC_BOGUS &&
+		rep->reason_bogus != LDNS_EDE_NONE) ||
+		reason_bogus == LDNS_EDE_NONE)) {
+		reason_bogus = rep->reason_bogus;
+	}
+	if(reason_bogus == LDNS_EDE_NONE ||
+		/* other, does not make sense without the text that comes
+		 * with it */
+		reason_bogus == LDNS_EDE_OTHER) return;
+
+	/* Synthesize the error report query in the format:
+	 * "_er.$qtype.$qname.$ede._er.$reporting-agent-domain" */
+	/* First check if the static length parts fit in the buffer.
+	 * That is everything except for qtype and ede that need to be
+	 * converted to decimal and checked further on. */
+	expected_length = 4/*_er*/+qname_len+4/*_er*/+agent_domain_len;
+	if(expected_length > LDNS_MAX_DOMAINLEN) goto skip;
+
+	memmove(buf+count, "\3_er", 4);
+	count += 4;
+
+	written = snprintf((char*)buf+count, LDNS_MAX_DOMAINLEN-count,
+		"X%d", qtype);
+	expected_length += written;
+	/* Skip on error, truncation or long expected length */
+	if(written < 0 || (size_t)written >= LDNS_MAX_DOMAINLEN-count ||
+		expected_length > LDNS_MAX_DOMAINLEN ) goto skip;
+	/* Put in the label length */
+	*(buf+count) = (char)(written - 1);
+	count += written;
+
+	memmove(buf+count, qname, qname_len);
+	count += qname_len;
+
+	written = snprintf((char*)buf+count, LDNS_MAX_DOMAINLEN-count,
+		"X%d", reason_bogus);
+	expected_length += written;
+	/* Skip on error, truncation or long expected length */
+	if(written < 0 || (size_t)written >= LDNS_MAX_DOMAINLEN-count ||
+		expected_length > LDNS_MAX_DOMAINLEN ) goto skip;
+	*(buf+count) = (char)(written - 1);
+	count += written;
+
+	memmove(buf+count, "\3_er", 4);
+	count += 4;
+
+	/* Copy the agent domain */
+	memmove(buf+count, agent_domain, agent_domain_len);
+	count += agent_domain_len;
+
+	qinfo.qname = buf;
+	qinfo.qname_len = count;
+	qinfo.qtype = LDNS_RR_TYPE_TXT;
+	qinfo.qclass = qstate->qinfo.qclass;
+	qinfo.local_alias = NULL;
+
+	log_query_info(VERB_ALGO, "DNS Error Reporting: generating report "
+		"query for", &qinfo);
+	if(mesh_add_sub(qstate, &qinfo, BIT_RD, 0, 0, &newq, &sub)) {
+		qstate->env->mesh->num_dns_error_reports++;
+	}
+	return;
+skip:
+	verbose(VERB_ALGO, "DNS Error Reporting: report query qname too long; "
+		"skip");
+	return;
 }
 
 void mesh_query_done(struct mesh_state* mstate)
@@ -1510,8 +1711,10 @@ void mesh_query_done(struct mesh_state* 
 	}
 	if(mstate->s.return_rcode == LDNS_RCODE_SERVFAIL ||
 		(rep && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_SERVFAIL)) {
-		/* we are SERVFAILing; check for expired answer here */
-		mesh_serve_expired_callback(mstate);
+		if(mstate->s.env->cfg->serve_expired) {
+			/* we are SERVFAILing; check for expired answer here */
+			mesh_respond_serve_expired(mstate);
+		}
 		if((mstate->reply_list || mstate->cb_list)
 		&& mstate->s.env->cfg->log_servfail
 		&& !mstate->s.env->cfg->val_log_squelch) {
@@ -1519,6 +1722,10 @@ void mesh_query_done(struct mesh_state* 
 			if(err) { log_err("%s", err); }
 		}
 	}
+
+	if(mstate->reply_list && mstate->s.env->cfg->dns_error_reporting)
+		dns_error_reporting(&mstate->s, rep);
+
 	for(r = mstate->reply_list; r; r = r->next) {
 		struct timeval old;
 		timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time);
@@ -1540,7 +1747,7 @@ void mesh_query_done(struct mesh_state* 
 				http2_stream_remove_mesh_state(r->h2_stream);
 			comm_point_drop_reply(&r->query_reply);
 			mstate->reply_list = reply_list;
-			mstate->s.env->mesh->stats_dropped++;
+			mstate->s.env->mesh->num_queries_discard_timeout++;
 			continue;
 		}
 
@@ -1682,6 +1889,25 @@ struct mesh_state* mesh_area_find(struct
 	return result;
 }
 
+/** remove mesh state callback */
+int mesh_state_del_cb(struct mesh_state* s, mesh_cb_func_type cb, void* cb_arg)
+{
+	struct mesh_cb* r, *prev = NULL;
+	r = s->cb_list;
+	while(r) {
+		if(r->cb == cb && r->cb_arg == cb_arg) {
+			/* Delete this entry. */
+			/* It was allocated in the s.region, so no free. */
+			if(prev) prev->next = r->next;
+			else s->cb_list = r->next;
+			return 1;
+		}
+		prev = r;
+		r = r->next;
+	}
+	return 0;
+}
+
 int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns,
         sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg,
 	uint16_t qid, uint16_t qflags)
@@ -2029,6 +2255,8 @@ mesh_stats_clear(struct mesh_area* mesh)
 {
 	if(!mesh)
 		return;
+	mesh->num_query_authzone_up = 0;
+	mesh->num_query_authzone_down = 0;
 	mesh->replies_sent = 0;
 	mesh->replies_sum_wait.tv_sec = 0;
 	mesh->replies_sum_wait.tv_usec = 0;
@@ -2042,6 +2270,9 @@ mesh_stats_clear(struct mesh_area* mesh)
 	memset(&mesh->ans_rcode[0], 0, sizeof(size_t)*UB_STATS_RCODE_NUM);
 	memset(&mesh->rpz_action[0], 0, sizeof(size_t)*UB_STATS_RPZ_ACTION_NUM);
 	mesh->ans_nodata = 0;
+	mesh->num_queries_discard_timeout = 0;
+	mesh->num_queries_wait_limit = 0;
+	mesh->num_dns_error_reports = 0;
 }
 
 size_t
@@ -2143,7 +2374,8 @@ apply_respip_action(struct module_qstate
 		return 1;
 
 	if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, actinfo,
-		alias_rrset, 0, qstate->region, az, NULL))
+		alias_rrset, 0, qstate->region, az, NULL, qstate->env->views,
+		qstate->env->respip_set))
 		return 0;
 
 	/* xxx_deny actions mean dropping the reply, unless the original reply
@@ -2177,7 +2409,7 @@ mesh_serve_expired_callback(void* arg)
 	struct timeval tv = {0, 0};
 	int must_validate = (!(qstate->query_flags&BIT_CD)
 		|| qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate;
-	int i = 0;
+	int i = 0, for_count;
 	int is_expired;
 	if(!qstate->serve_expired_data) return;
 	verbose(VERB_ALGO, "Serve expired: Trying to reply with expired data");
@@ -2190,15 +2422,21 @@ mesh_serve_expired_callback(void* arg)
 			"Serve expired: Not allowed to look into cache for stale");
 		return;
 	}
-	/* The following while is used instead of the `goto lookup_cache`
-	 * like in the worker. */
-	while(1) {
+	/* The following for is used instead of the `goto lookup_cache`
+	 * like in the worker. This loop should get max 2 passes if we need to
+	 * do any aliasing. */
+	for(for_count = 0; for_count < 2; for_count++) {
 		fptr_ok(fptr_whitelist_serve_expired_lookup(
 			qstate->serve_expired_data->get_cached_answer));
 		msg = (*qstate->serve_expired_data->get_cached_answer)(qstate,
 			lookup_qinfo, &is_expired);
-		if(!msg)
+		if(!msg || (FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR
+			&& FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NXDOMAIN
+			&& FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_YXDOMAIN)) {
+			/* We don't care for cached failure answers at this
+			 * stage. */
 			return;
+		}
 		/* Reset these in case we pass a second time from here. */
 		encode_rep = msg->rep;
 		memset(&actinfo, 0, sizeof(actinfo));
@@ -2212,7 +2450,8 @@ mesh_serve_expired_callback(void* arg)
 		} else if(partial_rep &&
 			!respip_merge_cname(partial_rep, &qstate->qinfo, msg->rep,
 			qstate->client_info, must_validate, &encode_rep, qstate->region,
-			qstate->env->auth_zones)) {
+			qstate->env->auth_zones, qstate->env->views,
+			qstate->env->respip_set)) {
 			return;
 		}
 		if(!encode_rep || alias_rrset) {
@@ -2270,7 +2509,7 @@ mesh_serve_expired_callback(void* arg)
 				http2_stream_remove_mesh_state(r->h2_stream);
 			comm_point_drop_reply(&r->query_reply);
 			mstate->reply_list = reply_list;
-			mstate->s.env->mesh->stats_dropped++;
+			mstate->s.env->mesh->num_queries_discard_timeout++;
 			continue;
 		}
 
@@ -2365,4 +2604,26 @@ int mesh_jostle_exceeded(struct mesh_are
 	if(mesh->all.count < mesh->max_reply_states)
 		return 0;
 	return 1;
+}
+
+void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo,
+	uint16_t qflags, mesh_cb_func_type cb, void* cb_arg)
+{
+	struct mesh_state* s = NULL;
+	s = mesh_area_find(mesh, NULL, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0);
+	if(!s) return;
+	if(!mesh_state_del_cb(s, cb, cb_arg)) return;
+
+	/* It was in the list and removed. */
+	log_assert(mesh->num_reply_addrs > 0);
+	mesh->num_reply_addrs--;
+	if(!s->reply_list && !s->cb_list) {
+		/* was a reply state, not anymore */
+		log_assert(mesh->num_reply_states > 0);
+		mesh->num_reply_states--;
+	}
+	if(!s->reply_list && !s->cb_list &&
+		s->super_set.count == 0) {
+		mesh->num_detached_states++;
+	}
 }
Index: services/mesh.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/mesh.h,v
diff -u -p -r1.13 mesh.h
--- services/mesh.h	21 Feb 2025 13:20:40 -0000	1.13
+++ services/mesh.h	30 Aug 2025 11:52:23 -0000
@@ -90,6 +90,11 @@ struct mesh_area {
 	/** rbtree of all current queries (mesh_state.node)*/
 	rbtree_type all;
 
+	/** number of queries for unbound's auth_zones, upstream query */
+	size_t num_query_authzone_up;
+	/** number of queries for unbound's auth_zones, downstream answers */
+	size_t num_query_authzone_down;
+
 	/** count of the total number of mesh_reply entries */
 	size_t num_reply_addrs;
 	/** count of the number of mesh_states that have mesh_replies 
@@ -132,6 +137,12 @@ struct mesh_area {
 	size_t ans_nodata;
 	/** (extended stats) type of applied RPZ action */
 	size_t rpz_action[UB_STATS_RPZ_ACTION_NUM];
+	/** stats, number of queries removed due to discard-timeout */
+	size_t num_queries_discard_timeout;
+	/** stats, number of queries removed due to wait-limit */
+	size_t num_queries_wait_limit;
+	/** stats, number of dns error reports generated */
+	size_t num_dns_error_reports;
 
 	/** backup of query if other operations recurse and need the
 	 * network buffers */
@@ -696,5 +707,18 @@ int mesh_jostle_exceeded(struct mesh_are
  * @param mstate: mesh state for query that has serve_expired_data.
  */
 void mesh_respond_serve_expired(struct mesh_state* mstate);
+
+/**
+ * Remove callback from mesh. Removes the callback from the state.
+ * The state itself is left to run. Searches for the pointer values.
+ *
+ * @param mesh: the mesh.
+ * @param qinfo: query from client.
+ * @param qflags: flags from client query.
+ * @param cb: callback function.
+ * @param cb_arg: callback user arg.
+ */
+void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo,
+	uint16_t qflags, mesh_cb_func_type cb, void* cb_arg);
 
 #endif /* SERVICES_MESH_H */
Index: services/outside_network.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/outside_network.c,v
diff -u -p -r1.31 outside_network.c
--- services/outside_network.c	4 Sep 2024 09:36:40 -0000	1.31
+++ services/outside_network.c	30 Aug 2025 11:52:23 -0000
@@ -262,12 +262,14 @@ pick_outgoing_tcp(struct pending_tcp* pe
 /** get TCP file descriptor for address, returns -1 on failure,
  * tcp_mss is 0 or maxseg size to set for TCP packets. */
 int
-outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen, int tcp_mss, int dscp)
+outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen,
+	int tcp_mss, int dscp, int nodelay)
 {
 	int s;
 	int af;
 	char* err;
-#if defined(SO_REUSEADDR) || defined(IP_BIND_ADDRESS_NO_PORT)
+#if defined(SO_REUSEADDR) || defined(IP_BIND_ADDRESS_NO_PORT)	\
+	|| defined(TCP_NODELAY)
 	int on = 1;
 #endif
 #ifdef INET6
@@ -320,6 +322,18 @@ outnet_get_tcp_fd(struct sockaddr_storag
 			" setsockopt(.. IP_BIND_ADDRESS_NO_PORT ..) failed");
 	}
 #endif /* IP_BIND_ADDRESS_NO_PORT */
+	if(nodelay) {
+#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
+		if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void*)&on,
+			(socklen_t)sizeof(on)) < 0) {
+			verbose(VERB_ALGO, "outgoing tcp:"
+				" setsockopt(.. TCP_NODELAY ..) failed");
+		}
+#else
+		verbose(VERB_ALGO, "outgoing tcp:"
+			" setsockopt(.. TCP_NODELAY ..) unsupported");
+#endif /* defined(IPPROTO_TCP) && defined(TCP_NODELAY) */
+	}
 	return s;
 }
 
@@ -649,7 +663,8 @@ outnet_tcp_take_into_use(struct waiting_
 	}
 
 	/* open socket */
-	s = outnet_get_tcp_fd(&w->addr, w->addrlen, w->outnet->tcp_mss, w->outnet->ip_dscp);
+	s = outnet_get_tcp_fd(&w->addr, w->addrlen, w->outnet->tcp_mss,
+		w->outnet->ip_dscp, w->ssl_upstream);
 
 	if(s == -1)
 		return 0;
@@ -1054,7 +1069,7 @@ reuse_move_writewait_away(struct outside
 		if(verbosity >= VERB_CLIENT && pend->query->pkt_len > 12+2+2 &&
 			LDNS_QDCOUNT(pend->query->pkt) > 0 &&
 			dname_valid(pend->query->pkt+12, pend->query->pkt_len-12)) {
-			char buf[LDNS_MAX_DOMAINLEN+1];
+			char buf[LDNS_MAX_DOMAINLEN];
 			dname_str(pend->query->pkt+12, buf);
 			verbose(VERB_CLIENT, "reuse_move_writewait_away current %s %d bytes were written",
 				buf, (int)pend->c->tcp_write_byte_count);
@@ -1079,7 +1094,7 @@ reuse_move_writewait_away(struct outside
 		if(verbosity >= VERB_CLIENT && w->pkt_len > 12+2+2 &&
 			LDNS_QDCOUNT(w->pkt) > 0 &&
 			dname_valid(w->pkt+12, w->pkt_len-12)) {
-			char buf[LDNS_MAX_DOMAINLEN+1];
+			char buf[LDNS_MAX_DOMAINLEN];
 			dname_str(w->pkt+12, buf);
 			verbose(VERB_CLIENT, "reuse_move_writewait_away item %s", buf);
 		}
@@ -2825,7 +2840,7 @@ serviced_perturb_qname(struct ub_randsta
 		lablen = *d++;
 	}
 	if(verbosity >= VERB_ALGO) {
-		char buf[LDNS_MAX_DOMAINLEN+1];
+		char buf[LDNS_MAX_DOMAINLEN];
 		dname_str(qbuf+10, buf);
 		verbose(VERB_ALGO, "qname perturbed to %s", buf);
 	}
@@ -3718,7 +3733,8 @@ outnet_comm_point_for_tcp(struct outside
 	sldns_buffer* query, int timeout, int ssl, char* host)
 {
 	struct comm_point* cp;
-	int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss, outnet->ip_dscp);
+	int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss,
+		outnet->ip_dscp, ssl);
 	if(fd == -1) {
 		return 0;
 	}
@@ -3793,7 +3809,8 @@ outnet_comm_point_for_http(struct outsid
 {
 	/* cp calls cb with err=NETEVENT_DONE when transfer is done */
 	struct comm_point* cp;
-	int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss, outnet->ip_dscp);
+	int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss,
+		outnet->ip_dscp, ssl);
 	if(fd == -1) {
 		return 0;
 	}
Index: services/outside_network.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/outside_network.h,v
diff -u -p -r1.16 outside_network.h
--- services/outside_network.h	20 Oct 2022 08:26:14 -0000	1.16
+++ services/outside_network.h	30 Aug 2025 11:52:23 -0000
@@ -743,9 +743,11 @@ void reuse_write_wait_remove(struct reus
 void reuse_write_wait_push_back(struct reuse_tcp* reuse, struct waiting_tcp* w);
 
 /** get TCP file descriptor for address, returns -1 on failure,
- * tcp_mss is 0 or maxseg size to set for TCP packets. */
+ * tcp_mss is 0 or maxseg size to set for TCP packets,
+ * nodelay (TCP_NODELAY) should be set for TLS connections to speed up the TLS
+ * handshake.*/
 int outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen,
-	int tcp_mss, int dscp);
+	int tcp_mss, int dscp, int nodelay);
 
 /**
  * Create udp commpoint suitable for sending packets to the destination.
Index: services/rpz.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/rpz.c,v
diff -u -p -r1.1.1.14 rpz.c
--- services/rpz.c	21 Feb 2025 13:17:08 -0000	1.1.1.14
+++ services/rpz.c	30 Aug 2025 11:52:23 -0000
@@ -666,7 +666,7 @@ rpz_insert_local_zones_trigger(struct lo
 	int newzone = 0;
 
 	if(a == RPZ_INVALID_ACTION) {
-		char str[255+1];
+		char str[LDNS_MAX_DOMAINLEN];
 		if(rrtype == LDNS_RR_TYPE_SOA || rrtype == LDNS_RR_TYPE_NS ||
 			rrtype == LDNS_RR_TYPE_DNAME ||
 			rrtype == LDNS_RR_TYPE_DNSKEY ||
@@ -739,7 +739,7 @@ rpz_insert_local_zones_trigger(struct lo
 static void
 rpz_log_dname(char const* msg, uint8_t* dname, size_t dname_len)
 {
-	char buf[LDNS_MAX_DOMAINLEN+1];
+	char buf[LDNS_MAX_DOMAINLEN];
 	(void)dname_len;
 	dname_str(dname, buf);
 	verbose(VERB_ALGO, "rpz: %s: <%s>", msg, buf);
@@ -1062,7 +1062,7 @@ rpz_insert_response_ip_trigger(struct rp
 
 	if(a == RPZ_INVALID_ACTION ||
 		rpz_action_to_respip_action(a) == respip_invalid) {
-		char str[255+1];
+		char str[LDNS_MAX_DOMAINLEN];
 		dname_str(dname, str);
 		verbose(VERB_ALGO, "rpz: respip trigger, %s skipping unsupported action: %s",
 			str, rpz_action_to_string(a));
@@ -1633,7 +1633,7 @@ log_rpz_apply(char* trigger, uint8_t* dn
 	struct comm_reply* repinfo, struct module_qstate* ms, char* log_name)
 {
 	char ip[128], txt[512], portstr[32];
-	char dnamestr[LDNS_MAX_DOMAINLEN+1];
+	char dnamestr[LDNS_MAX_DOMAINLEN];
 	uint16_t port = 0;
 	if(dname) {
 		dname_str(dname, dnamestr);
@@ -2427,7 +2427,8 @@ rpz_delegation_point_zone_lookup(struct 
 			match->dname = nameserver->name;
 			match->dname_len = nameserver->namelen;
 			if(verbosity >= VERB_ALGO) {
-				char nm[255+1], zn[255+1];
+				char nm[LDNS_MAX_DOMAINLEN];
+				char zn[LDNS_MAX_DOMAINLEN];
 				dname_str(match->dname, nm);
 				dname_str(z->name, zn);
 				if(strcmp(nm, zn) != 0)
@@ -2581,7 +2582,7 @@ struct dns_msg* rpz_callback_from_iterat
 	}
 
 	if(verbosity >= VERB_ALGO) {
-		char nm[255+1], zn[255+1];
+		char nm[LDNS_MAX_DOMAINLEN], zn[LDNS_MAX_DOMAINLEN];
 		dname_str(is->qchase.qname, nm);
 		dname_str(z->name, zn);
 		if(strcmp(zn, nm) != 0)
@@ -2758,7 +2759,7 @@ rpz_callback_from_worker_request(struct 
 	}
 
 	if(verbosity >= VERB_ALGO) {
-		char nm[255+1], zn[255+1];
+		char nm[LDNS_MAX_DOMAINLEN], zn[LDNS_MAX_DOMAINLEN];
 		dname_str(qinfo->qname, nm);
 		dname_str(z->name, zn);
 		if(strcmp(zn, nm) != 0)
@@ -2790,4 +2791,32 @@ void rpz_disable(struct rpz* r)
     if(!r)
         return;
     r->disabled = 1;
+}
+
+/** Get memory usage for clientip_synthesized_rrset. Ignores memory usage
+ * of locks. */
+static size_t
+rpz_clientip_synthesized_set_get_mem(struct clientip_synthesized_rrset* set)
+{
+	size_t m = sizeof(*set);
+	lock_rw_rdlock(&set->lock);
+	m += regional_get_mem(set->region);
+	lock_rw_unlock(&set->lock);
+	return m;
+}
+
+size_t rpz_get_mem(struct rpz* r)
+{
+	size_t m = sizeof(*r);
+	if(r->taglist)
+		m += r->taglistlen;
+	if(r->log_name)
+		m += strlen(r->log_name) + 1;
+	m += regional_get_mem(r->region);
+	m += local_zones_get_mem(r->local_zones);
+	m += local_zones_get_mem(r->nsdname_zones);
+	m += respip_set_get_mem(r->respip_set);
+	m += rpz_clientip_synthesized_set_get_mem(r->client_set);
+	m += rpz_clientip_synthesized_set_get_mem(r->ns_set);
+	return m;
 }
Index: services/rpz.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/rpz.h,v
diff -u -p -r1.1.1.7 rpz.h
--- services/rpz.h	13 Jun 2024 14:29:33 -0000	1.1.1.7
+++ services/rpz.h	30 Aug 2025 11:52:23 -0000
@@ -269,4 +269,11 @@ void rpz_enable(struct rpz* r);
  */
 void rpz_disable(struct rpz* r);
 
+/**
+ * Get memory usage of rpz. Caller must manage locks.
+ * @param r: RPZ struct.
+ * @return memory usage.
+ */
+size_t rpz_get_mem(struct rpz* r);
+
 #endif /* SERVICES_RPZ_H */
Index: services/view.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/view.c,v
diff -u -p -r1.4 view.c
--- services/view.c	20 Oct 2022 08:26:14 -0000	1.4
+++ services/view.c	30 Aug 2025 11:52:23 -0000
@@ -43,6 +43,7 @@
 #include "services/view.h"
 #include "services/localzone.h"
 #include "util/config_file.h"
+#include "respip/respip.h"
 
 int 
 view_cmp(const void* v1, const void* v2)
@@ -66,11 +67,6 @@ views_create(void)
 	return v;
 }
 
-/* \noop (ignore this comment for doxygen)
- * This prototype is defined in in respip.h, but we want to avoid
- * unnecessary dependencies */
-void respip_set_delete(struct respip_set *set);
-
 void 
 view_delete(struct view* v)
 {
@@ -246,4 +242,39 @@ void views_print(struct views* v)
 {
 	/* TODO implement print */
 	(void)v;
+}
+
+size_t views_get_mem(struct views* vs)
+{
+	struct view* v;
+	size_t m;
+	if(!vs) return 0;
+	m = sizeof(struct views);
+	lock_rw_rdlock(&vs->lock);
+	RBTREE_FOR(v, struct view*, &vs->vtree) {
+		m += view_get_mem(v);
+	}
+	lock_rw_unlock(&vs->lock);
+	return m;
+}
+
+size_t view_get_mem(struct view* v)
+{
+	size_t m = sizeof(*v);
+	lock_rw_rdlock(&v->lock);
+	m += getmem_str(v->name);
+	m += local_zones_get_mem(v->local_zones);
+	m += respip_set_get_mem(v->respip_set);
+	lock_rw_unlock(&v->lock);
+	return m;
+}
+
+void views_swap_tree(struct views* vs, struct views* data)
+{
+	rbnode_type* oldroot = vs->vtree.root;
+	size_t oldcount = vs->vtree.count;
+	vs->vtree.root = data->vtree.root;
+	vs->vtree.count = data->vtree.count;
+	data->vtree.root = oldroot;
+	data->vtree.count = oldcount;
 }
Index: services/view.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/view.h,v
diff -u -p -r1.4 view.h
--- services/view.h	13 Jun 2024 14:30:28 -0000	1.4
+++ services/view.h	30 Aug 2025 11:52:23 -0000
@@ -54,7 +54,8 @@ struct respip_set;
  * Views storage, shared.
  */
 struct views {
-	/** lock on the view tree */
+	/** lock on the view tree. When locking order, the views lock
+	 * is before the forwards,hints,anchors,localzones lock. */
 	lock_rw_type lock;
 	/** rbtree of struct view */
 	rbtree_type vtree;
@@ -134,5 +135,28 @@ void views_print(struct views* v);
  * @return: locked view or NULL. 
  */
 struct view* views_find_view(struct views* vs, const char* name, int write);
+
+/**
+ * Calculate memory usage of views.
+ * @param vs: the views tree. The routine locks and unlocks the structure
+ * 	for reading.
+ * @return memory in bytes.
+ */
+size_t views_get_mem(struct views* vs);
+
+/**
+ * Calculate memory usage of view.
+ * @param v: the view. The routine locks and unlocks the structure for reading.
+ * @return memory in bytes.
+ */
+size_t view_get_mem(struct view* v);
+
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param vs: views tree
+ * @param data: preallocated information.
+ */
+void views_swap_tree(struct views* vs, struct views* data);
 
 #endif /* SERVICES_VIEW_H */
Index: services/cache/dns.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/cache/dns.c,v
diff -u -p -r1.26 dns.c
--- services/cache/dns.c	21 Feb 2025 13:20:40 -0000	1.26
+++ services/cache/dns.c	30 Aug 2025 11:52:23 -0000
@@ -1056,8 +1056,9 @@ dns_cache_lookup(struct module_env* env,
 
 int
 dns_cache_store(struct module_env* env, struct query_info* msgqinf,
-        struct reply_info* msgrep, int is_referral, time_t leeway, int pside,
-	struct regional* region, uint32_t flags, time_t qstarttime)
+	struct reply_info* msgrep, int is_referral, time_t leeway, int pside,
+	struct regional* region, uint32_t flags, time_t qstarttime,
+	int is_valrec)
 {
 	struct reply_info* rep = NULL;
 	if(SERVE_EXPIRED) {
@@ -1065,7 +1066,7 @@ dns_cache_store(struct module_env* env, 
 		 * useful expired record exists. */
 		struct msgreply_entry* e = msg_cache_lookup(env,
 			msgqinf->qname, msgqinf->qname_len, msgqinf->qtype,
-			msgqinf->qclass, flags, 0, 0);
+			msgqinf->qclass, flags, 0, 1);
 		if(e) {
 			struct reply_info* cached = e->entry.data;
 			if(cached->ttl < *env->now
@@ -1079,7 +1080,43 @@ dns_cache_store(struct module_env* env, 
 				 * one and let the validator manage caching. */
 				&& cached->security != sec_status_bogus
 				&& (env->need_to_validate &&
-				msgrep->security == sec_status_unchecked)) {
+				msgrep->security == sec_status_unchecked)
+				/* Exceptions to that rule are:
+				 * o recursions that don't need validation but
+				 *   need to update the cache for coherence
+				 *   (delegation information while iterating,
+				 *   DNSKEY and DS lookups from validator)
+				 * o explicit RRSIG queries that are not
+				 *   validated. */
+				&& !is_valrec
+				&& msgqinf->qtype != LDNS_RR_TYPE_RRSIG) {
+				if((int)FLAGS_GET_RCODE(msgrep->flags) !=
+					LDNS_RCODE_NOERROR &&
+					(int)FLAGS_GET_RCODE(msgrep->flags) !=
+					LDNS_RCODE_NXDOMAIN) {
+					/* The current response has an
+					 * erroneous rcode. Adjust norec time
+					 * so that additional lookups are not
+					 * performed for some time. */
+					verbose(VERB_ALGO, "set "
+						"serve-expired-norec-ttl for "
+						"response in cache");
+					cached->serve_expired_norec_ttl =
+						NORR_TTL + *env->now;
+					if(env->cfg->serve_expired_ttl_reset &&
+					    cached->serve_expired_ttl
+					    < *env->now +
+					    env->cfg->serve_expired_ttl) {
+						/* Reset serve-expired-ttl for
+						 * valid response in cache. */
+						verbose(VERB_ALGO, "reset "
+							"serve-expired-ttl "
+							"for response in cache");
+						cached->serve_expired_ttl =
+						    *env->now +
+						    env->cfg->serve_expired_ttl;
+					}
+				}
 				verbose(VERB_ALGO, "a validated expired entry "
 					"could be overwritten, skip caching "
 					"the new message at this stage");
Index: services/cache/dns.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/cache/dns.h,v
diff -u -p -r1.12 dns.h
--- services/cache/dns.h	21 Feb 2025 13:20:40 -0000	1.12
+++ services/cache/dns.h	30 Aug 2025 11:52:23 -0000
@@ -90,11 +90,14 @@ struct dns_msg {
  *   (See DNSCACHE_STORE_xxx flags).
  * @param qstarttime: time when the query was started, and thus when the
  * 	delegations were looked up.
+ * @param is_valrec: if the query is validation recursion and does not get
+ *	dnssec validation itself.
  * @return 0 on alloc error (out of memory).
  */
 int dns_cache_store(struct module_env* env, struct query_info* qinf,
         struct reply_info* rep, int is_referral, time_t leeway, int pside,
-	struct regional* region, uint32_t flags, time_t qstarttime);
+	struct regional* region, uint32_t flags, time_t qstarttime,
+	int is_valrec);
 
 /**
  * Store message in the cache. Stores in message cache and rrset cache.
Index: services/cache/infra.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/cache/infra.c,v
diff -u -p -r1.17 infra.c
--- services/cache/infra.c	4 Sep 2024 09:36:40 -0000	1.17
+++ services/cache/infra.c	30 Aug 2025 11:52:23 -0000
@@ -52,24 +52,6 @@
 #include "util/config_file.h"
 #include "iterator/iterator.h"
 
-/** Timeout when only a single probe query per IP is allowed. */
-#define PROBE_MAXRTO 12000 /* in msec */
-
-/** number of timeouts for a type when the domain can be blocked ;
- * even if another type has completely rtt maxed it, the different type
- * can do this number of packets (until those all timeout too) */
-#define TIMEOUT_COUNT_MAX 3
-
-/** Minus 1000 because that is outside of the RTTBAND, so
- * blacklisted servers stay blacklisted if this is chosen.
- * If USEFUL_SERVER_TOP_TIMEOUT is below 1000 (configured via RTT_MAX_TIMEOUT,
- * infra-cache-max-rtt) change it to just above the RTT_BAND. */
-#define STILL_USEFUL_TIMEOUT (				\
-	USEFUL_SERVER_TOP_TIMEOUT < 1000 ||		\
-	USEFUL_SERVER_TOP_TIMEOUT - 1000 <= RTT_BAND	\
-		?RTT_BAND + 1				\
-		:USEFUL_SERVER_TOP_TIMEOUT - 1000)
-
 /** ratelimit value for delegation point */
 int infra_dp_ratelimit = 0;
 
@@ -82,6 +64,20 @@ int infra_ip_ratelimit = 0;
  *  For clients with a valid DNS Cookie. */
 int infra_ip_ratelimit_cookie = 0;
 
+/** Minus 1000 because that is outside of the RTTBAND, so
+ * blacklisted servers stay blacklisted if this is chosen.
+ * If USEFUL_SERVER_TOP_TIMEOUT is below 1000 (configured via RTT_MAX_TIMEOUT,
+ * infra-cache-max-rtt) change it to just above the RTT_BAND. */
+int
+still_useful_timeout()
+{
+	return
+	USEFUL_SERVER_TOP_TIMEOUT < 1000 ||
+	USEFUL_SERVER_TOP_TIMEOUT - 1000 <= RTT_BAND
+		?RTT_BAND + 1
+		:USEFUL_SERVER_TOP_TIMEOUT - 1000;
+}
+
 size_t 
 infra_sizefunc(void* k, void* ATTR_UNUSED(d))
 {
@@ -165,7 +161,7 @@ rate_deldatafunc(void* d, void* ATTR_UNU
 
 /** find or create element in domainlimit tree */
 static struct domain_limit_data* domain_limit_findcreate(
-	struct infra_cache* infra, char* name)
+	struct rbtree_type* domain_limits, char* name)
 {
 	uint8_t* nm;
 	int labs;
@@ -181,8 +177,8 @@ static struct domain_limit_data* domain_
 	labs = dname_count_labels(nm);
 
 	/* can we find it? */
-	d = (struct domain_limit_data*)name_tree_find(&infra->domain_limits,
-		nm, nmlen, labs, LDNS_RR_CLASS_IN);
+	d = (struct domain_limit_data*)name_tree_find(domain_limits, nm,
+		nmlen, labs, LDNS_RR_CLASS_IN);
 	if(d) {
 		free(nm);
 		return d;
@@ -201,8 +197,8 @@ static struct domain_limit_data* domain_
 	d->node.dclass = LDNS_RR_CLASS_IN;
 	d->lim = -1;
 	d->below = -1;
-	if(!name_tree_insert(&infra->domain_limits, &d->node, nm, nmlen,
-		labs, LDNS_RR_CLASS_IN)) {
+	if(!name_tree_insert(domain_limits, &d->node, nm, nmlen, labs,
+		LDNS_RR_CLASS_IN)) {
 		log_err("duplicate element in domainlimit tree");
 		free(nm);
 		free(d);
@@ -212,19 +208,19 @@ static struct domain_limit_data* domain_
 }
 
 /** insert rate limit configuration into lookup tree */
-static int infra_ratelimit_cfg_insert(struct infra_cache* infra,
+static int infra_ratelimit_cfg_insert(struct rbtree_type* domain_limits,
 	struct config_file* cfg)
 {
 	struct config_str2list* p;
 	struct domain_limit_data* d;
 	for(p = cfg->ratelimit_for_domain; p; p = p->next) {
-		d = domain_limit_findcreate(infra, p->str);
+		d = domain_limit_findcreate(domain_limits, p->str);
 		if(!d)
 			return 0;
 		d->lim = atoi(p->str2);
 	}
 	for(p = cfg->ratelimit_below_domain; p; p = p->next) {
-		d = domain_limit_findcreate(infra, p->str);
+		d = domain_limit_findcreate(domain_limits, p->str);
 		if(!d)
 			return 0;
 		d->below = atoi(p->str2);
@@ -232,24 +228,21 @@ static int infra_ratelimit_cfg_insert(st
 	return 1;
 }
 
-/** setup domain limits tree (0 on failure) */
-static int
-setup_domain_limits(struct infra_cache* infra, struct config_file* cfg)
+int
+setup_domain_limits(struct rbtree_type* domain_limits, struct config_file* cfg)
 {
-	name_tree_init(&infra->domain_limits);
-	if(!infra_ratelimit_cfg_insert(infra, cfg)) {
+	name_tree_init(domain_limits);
+	if(!infra_ratelimit_cfg_insert(domain_limits, cfg)) {
 		return 0;
 	}
-	name_tree_init_parents(&infra->domain_limits);
+	name_tree_init_parents(domain_limits);
 	return 1;
 }
 
 /** find or create element in wait limit netblock tree */
 static struct wait_limit_netblock_info*
-wait_limit_netblock_findcreate(struct infra_cache* infra, char* str,
-	int cookie)
+wait_limit_netblock_findcreate(struct rbtree_type* tree, char* str)
 {
-	rbtree_type* tree;
 	struct sockaddr_storage addr;
 	int net;
 	socklen_t addrlen;
@@ -261,10 +254,6 @@ wait_limit_netblock_findcreate(struct in
 	}
 
 	/* can we find it? */
-	if(cookie)
-		tree = &infra->wait_limits_cookie_netblock;
-	else
-		tree = &infra->wait_limits_netblock;
 	d = (struct wait_limit_netblock_info*)addr_tree_find(tree, &addr,
 		addrlen, net);
 	if(d)
@@ -286,19 +275,21 @@ wait_limit_netblock_findcreate(struct in
 
 /** insert wait limit information into lookup tree */
 static int
-infra_wait_limit_netblock_insert(struct infra_cache* infra,
-	struct config_file* cfg)
+infra_wait_limit_netblock_insert(rbtree_type* wait_limits_netblock,
+        rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg)
 {
 	struct config_str2list* p;
 	struct wait_limit_netblock_info* d;
 	for(p = cfg->wait_limit_netblock; p; p = p->next) {
-		d = wait_limit_netblock_findcreate(infra, p->str, 0);
+		d = wait_limit_netblock_findcreate(wait_limits_netblock,
+			p->str);
 		if(!d)
 			return 0;
 		d->limit = atoi(p->str2);
 	}
 	for(p = cfg->wait_limit_cookie_netblock; p; p = p->next) {
-		d = wait_limit_netblock_findcreate(infra, p->str, 1);
+		d = wait_limit_netblock_findcreate(wait_limits_cookie_netblock,
+			p->str);
 		if(!d)
 			return 0;
 		d->limit = atoi(p->str2);
@@ -306,16 +297,48 @@ infra_wait_limit_netblock_insert(struct 
 	return 1;
 }
 
-/** setup wait limits tree (0 on failure) */
+/** Add a default wait limit netblock */
 static int
-setup_wait_limits(struct infra_cache* infra, struct config_file* cfg)
+wait_limit_netblock_default(struct rbtree_type* tree, char* str, int limit)
+{
+	struct wait_limit_netblock_info* d;
+	d = wait_limit_netblock_findcreate(tree, str);
+	if(!d)
+		return 0;
+	d->limit = limit;
+	return 1;
+}
+
+int
+setup_wait_limits(rbtree_type* wait_limits_netblock,
+	rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg)
 {
-	addr_tree_init(&infra->wait_limits_netblock);
-	addr_tree_init(&infra->wait_limits_cookie_netblock);
-	if(!infra_wait_limit_netblock_insert(infra, cfg))
+	addr_tree_init(wait_limits_netblock);
+	addr_tree_init(wait_limits_cookie_netblock);
+
+	/* Insert defaults */
+	/* The loopback address is separated from the rest of the network. */
+	/* wait-limit-netblock: 127.0.0.0/8 -1 */
+	if(!wait_limit_netblock_default(wait_limits_netblock, "127.0.0.0/8",
+		-1))
+		return 0;
+	/* wait-limit-netblock: ::1/128 -1 */
+	if(!wait_limit_netblock_default(wait_limits_netblock, "::1/128", -1))
+		return 0;
+	/* wait-limit-cookie-netblock: 127.0.0.0/8 -1 */
+	if(!wait_limit_netblock_default(wait_limits_cookie_netblock,
+		"127.0.0.0/8", -1))
+		return 0;
+	/* wait-limit-cookie-netblock: ::1/128 -1 */
+	if(!wait_limit_netblock_default(wait_limits_cookie_netblock,
+		"::1/128", -1))
 		return 0;
-	addr_tree_init_parents(&infra->wait_limits_netblock);
-	addr_tree_init_parents(&infra->wait_limits_cookie_netblock);
+
+	if(!infra_wait_limit_netblock_insert(wait_limits_netblock,
+		wait_limits_cookie_netblock, cfg))
+		return 0;
+	addr_tree_init_parents(wait_limits_netblock);
+	addr_tree_init_parents(wait_limits_cookie_netblock);
 	return 1;
 }
 
@@ -348,11 +371,12 @@ infra_create(struct config_file* cfg)
 		return NULL;
 	}
 	/* insert config data into ratelimits */
-	if(!setup_domain_limits(infra, cfg)) {
+	if(!setup_domain_limits(&infra->domain_limits, cfg)) {
 		infra_delete(infra);
 		return NULL;
 	}
-	if(!setup_wait_limits(infra, cfg)) {
+	if(!setup_wait_limits(&infra->wait_limits_netblock,
+		&infra->wait_limits_cookie_netblock, cfg)) {
 		infra_delete(infra);
 		return NULL;
 	}
@@ -377,12 +401,29 @@ static void domain_limit_free(rbnode_typ
 	}
 }
 
+void
+domain_limits_free(struct rbtree_type* domain_limits)
+{
+	if(!domain_limits)
+		return;
+	traverse_postorder(domain_limits, domain_limit_free, NULL);
+}
+
 /** delete wait_limit_netblock_info entries */
 static void wait_limit_netblock_del(rbnode_type* n, void* ATTR_UNUSED(arg))
 {
 	free(n);
 }
 
+void
+wait_limits_free(struct rbtree_type* wait_limits_tree)
+{
+	if(!wait_limits_tree)
+		return;
+	traverse_postorder(wait_limits_tree, wait_limit_netblock_del,
+		NULL);
+}
+
 void 
 infra_delete(struct infra_cache* infra)
 {
@@ -390,12 +431,10 @@ infra_delete(struct infra_cache* infra)
 		return;
 	slabhash_delete(infra->hosts);
 	slabhash_delete(infra->domain_rates);
-	traverse_postorder(&infra->domain_limits, domain_limit_free, NULL);
+	domain_limits_free(&infra->domain_limits);
 	slabhash_delete(infra->client_ip_rates);
-	traverse_postorder(&infra->wait_limits_netblock,
-		wait_limit_netblock_del, NULL);
-	traverse_postorder(&infra->wait_limits_cookie_netblock,
-		wait_limit_netblock_del, NULL);
+	wait_limits_free(&infra->wait_limits_netblock);
+	wait_limits_free(&infra->wait_limits_cookie_netblock);
 	free(infra);
 }
 
@@ -426,7 +465,7 @@ infra_adjust(struct infra_cache* infra, 
 		/* reapply domain limits */
 		traverse_postorder(&infra->domain_limits, domain_limit_free,
 			NULL);
-		if(!setup_domain_limits(infra, cfg)) {
+		if(!setup_domain_limits(&infra->domain_limits, cfg)) {
 			infra_delete(infra);
 			return NULL;
 		}
@@ -668,7 +707,7 @@ infra_update_tcp_works(struct infra_cach
 	if(data->rtt.rto >= RTT_MAX_TIMEOUT)
 		/* do not disqualify this server altogether, it is better
 		 * than nothing */
-		data->rtt.rto = STILL_USEFUL_TIMEOUT;
+		data->rtt.rto = still_useful_timeout();
 	lock_rw_unlock(&e->lock);
 }
 
@@ -808,7 +847,7 @@ infra_get_lame_rtt(struct infra_cache* i
 		&& infra->infra_keep_probing) {
 		/* single probe, keep probing */
 		if(*rtt >= USEFUL_SERVER_TOP_TIMEOUT)
-			*rtt = STILL_USEFUL_TIMEOUT;
+			*rtt = still_useful_timeout();
 	} else if(host->rtt.rto >= PROBE_MAXRTO && timenow < host->probedelay
 		&& rtt_notimeout(&host->rtt)*4 <= host->rtt.rto) {
 		/* single probe for this domain, and we are not probing */
@@ -816,15 +855,15 @@ infra_get_lame_rtt(struct infra_cache* i
 		if(qtype == LDNS_RR_TYPE_A) {
 			if(host->timeout_A >= TIMEOUT_COUNT_MAX)
 				*rtt = USEFUL_SERVER_TOP_TIMEOUT;
-			else	*rtt = STILL_USEFUL_TIMEOUT;
+			else	*rtt = still_useful_timeout();
 		} else if(qtype == LDNS_RR_TYPE_AAAA) {
 			if(host->timeout_AAAA >= TIMEOUT_COUNT_MAX)
 				*rtt = USEFUL_SERVER_TOP_TIMEOUT;
-			else	*rtt = STILL_USEFUL_TIMEOUT;
+			else	*rtt = still_useful_timeout();
 		} else {
 			if(host->timeout_other >= TIMEOUT_COUNT_MAX)
 				*rtt = USEFUL_SERVER_TOP_TIMEOUT;
-			else	*rtt = STILL_USEFUL_TIMEOUT;
+			else	*rtt = still_useful_timeout();
 		}
 	}
 	/* expired entry */
@@ -832,7 +871,7 @@ infra_get_lame_rtt(struct infra_cache* i
 		/* see if this can be a re-probe of an unresponsive server */
 		if(host->rtt.rto >= USEFUL_SERVER_TOP_TIMEOUT) {
 			lock_rw_unlock(&e->lock);
-			*rtt = STILL_USEFUL_TIMEOUT;
+			*rtt = still_useful_timeout();
 			*lame = 0;
 			*dnsseclame = 0;
 			*reclame = 0;
@@ -1081,7 +1120,8 @@ int infra_ratelimit_inc(struct infra_cac
 		lock_rw_unlock(&entry->lock);
 
 		if(premax <= lim && max > lim) {
-			char buf[257], qnm[257], ts[12], cs[12], ip[128];
+			char buf[LDNS_MAX_DOMAINLEN], qnm[LDNS_MAX_DOMAINLEN];
+			char ts[12], cs[12], ip[128];
 			dname_str(name, buf);
 			dname_str(qinfo->qname, qnm);
 			sldns_wire2str_type_buf(qinfo->qtype, ts, sizeof(ts));
Index: services/cache/infra.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/cache/infra.h,v
diff -u -p -r1.10 infra.h
--- services/cache/infra.h	4 Sep 2024 09:36:40 -0000	1.10
+++ services/cache/infra.h	30 Aug 2025 11:52:23 -0000
@@ -52,6 +52,19 @@
 struct slabhash;
 struct config_file;
 
+/** number of timeouts for a type when the domain can be blocked ;
+ * even if another type has completely rtt maxed it, the different type
+ * can do this number of packets (until those all timeout too) */
+#define TIMEOUT_COUNT_MAX 3
+
+
+/** Timeout when only a single probe query per IP is allowed.
+ *  Any RTO above this number is considered a probe.
+ *  It is synchronized (caped) with USEFUL_SERVER_TOP_TIMEOUT so that probing
+ *  keeps working even if that configurable number drops below the default
+ *  12000 ms of probing. */
+extern int PROBE_MAXRTO;
+
 /**
  * Host information kept for every server, per zone.
  */
@@ -501,5 +514,23 @@ void infra_wait_limit_inc(struct infra_c
 /** Decrement number of waiting replies for IP */
 void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep,
 	struct config_file* cfg);
+
+/** setup wait limits tree (0 on failure) */
+int setup_wait_limits(struct rbtree_type* wait_limits_netblock,
+	struct rbtree_type* wait_limits_cookie_netblock,
+	struct config_file* cfg);
+
+/** Free the wait limits and wait cookie limits tree. */
+void wait_limits_free(struct rbtree_type* wait_limits_tree);
+
+/** setup domain limits tree (0 on failure) */
+int setup_domain_limits(struct rbtree_type* domain_limits,
+	struct config_file* cfg);
+
+/** Free the domain limits tree. */
+void domain_limits_free(struct rbtree_type* domain_limits);
+
+/** exported for unit test */
+int still_useful_timeout();
 
 #endif /* SERVICES_CACHE_INFRA_H */
Index: sldns/keyraw.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/sldns/keyraw.c,v
diff -u -p -r1.9 keyraw.c
--- sldns/keyraw.c	7 Feb 2025 07:36:19 -0000	1.9
+++ sldns/keyraw.c	30 Aug 2025 11:52:23 -0000
@@ -371,7 +371,7 @@ EVP_PKEY *sldns_key_dsa2pkey_raw(unsigne
 	return evp_key;
 #endif
 }
-#endif
+#endif /* USE_DSA */
 
 /* Retrieve params as BIGNUM from raw buffer, n is modulus, e is exponent */
 static int
Index: sldns/rrdef.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/sldns/rrdef.c,v
diff -u -p -r1.8 rrdef.c
--- sldns/rrdef.c	5 Sep 2023 11:12:10 -0000	1.8
+++ sldns/rrdef.c	30 Aug 2025 11:52:23 -0000
@@ -72,7 +72,7 @@ static const sldns_rdf_type type_nsap_wi
 	LDNS_RDF_TYPE_NSAP
 };
 static const sldns_rdf_type type_nsap_ptr_wireformat[] = {
-	LDNS_RDF_TYPE_STR
+	LDNS_RDF_TYPE_UNQUOTED
 };
 static const sldns_rdf_type type_sig_wireformat[] = {
 	LDNS_RDF_TYPE_TYPE, LDNS_RDF_TYPE_ALG, LDNS_RDF_TYPE_INT8, LDNS_RDF_TYPE_INT32,
@@ -86,7 +86,7 @@ static const sldns_rdf_type type_px_wire
 	LDNS_RDF_TYPE_INT16, LDNS_RDF_TYPE_DNAME, LDNS_RDF_TYPE_DNAME
 };
 static const sldns_rdf_type type_gpos_wireformat[] = {
-	LDNS_RDF_TYPE_STR, LDNS_RDF_TYPE_STR, LDNS_RDF_TYPE_STR
+	LDNS_RDF_TYPE_UNQUOTED, LDNS_RDF_TYPE_UNQUOTED, LDNS_RDF_TYPE_UNQUOTED
 };
 static const sldns_rdf_type type_aaaa_wireformat[] = { LDNS_RDF_TYPE_AAAA };
 static const sldns_rdf_type type_loc_wireformat[] = { LDNS_RDF_TYPE_LOC };
@@ -616,6 +616,12 @@ static sldns_rr_descriptor rdata_field_d
 #else
 {(enum sldns_enum_rr_type)0, "TYPE258", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
 #endif
+
+{(enum sldns_enum_rr_type)0, "TYPE259", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+{(enum sldns_enum_rr_type)0, "TYPE260", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+
+	/* 261 */
+	{LDNS_RR_TYPE_RESINFO,  "RESINFO", 1, 0, NULL, LDNS_RDF_TYPE_UNQUOTED, LDNS_RR_NO_COMPRESS, 0 },
 
 /* split in array, no longer contiguous */
 
Index: sldns/rrdef.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/sldns/rrdef.h,v
diff -u -p -r1.14 rrdef.h
--- sldns/rrdef.h	4 Sep 2024 09:36:40 -0000	1.14
+++ sldns/rrdef.h	30 Aug 2025 11:52:23 -0000
@@ -229,6 +229,8 @@ enum sldns_enum_rr_type
 	LDNS_RR_TYPE_CAA = 257, /* RFC 6844 */
 	LDNS_RR_TYPE_AVC = 258,
 
+	LDNS_RR_TYPE_RESINFO = 261, /* RFC 9606 */
+
 	/** DNSSEC Trust Authorities */
 	LDNS_RR_TYPE_TA = 32768,
 	/* RFC 4431, 5074, DNSSEC Lookaside Validation */
@@ -341,6 +343,9 @@ enum sldns_enum_rdf_type
         /** 8 * 8 bit hex numbers separated by dashes. For EUI64. */
         LDNS_RDF_TYPE_EUI64,
 
+	/** Character string without quotes. */
+	LDNS_RDF_TYPE_UNQUOTED,
+
         /** A non-zero sequence of US-ASCII letters and numbers in lower case.
          *  For CAA.
          */
@@ -438,6 +443,7 @@ enum sldns_enum_edns_option
 	LDNS_EDNS_PADDING = 12, /* RFC7830 */
 	LDNS_EDNS_EDE = 15, /* RFC8914 */
 	LDNS_EDNS_CLIENT_TAG = 16, /* draft-bellis-dnsop-edns-tags-01 */
+	LDNS_EDNS_REPORT_CHANNEL = 18, /* RFC9567 */
 	LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST = 65534
 };
 typedef enum sldns_enum_edns_option sldns_edns_option;
Index: sldns/str2wire.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/sldns/str2wire.c,v
diff -u -p -r1.17 str2wire.c
--- sldns/str2wire.c	12 Apr 2024 15:45:24 -0000	1.17
+++ sldns/str2wire.c	30 Aug 2025 11:52:23 -0000
@@ -365,7 +365,8 @@ static int
 sldns_rdf_type_maybe_quoted(sldns_rdf_type rdf_type)
 {
 	return  rdf_type == LDNS_RDF_TYPE_STR ||
-		rdf_type == LDNS_RDF_TYPE_LONG_STR;
+		rdf_type == LDNS_RDF_TYPE_LONG_STR ||
+		rdf_type == LDNS_RDF_TYPE_UNQUOTED;
 }
 
 /** see if rdata is quoted */
@@ -1719,6 +1720,8 @@ int sldns_str2wire_rdf_buf(const char* s
 		return sldns_str2wire_eui48_buf(str, rd, len);
 	case LDNS_RDF_TYPE_EUI64:
 		return sldns_str2wire_eui64_buf(str, rd, len);
+	case LDNS_RDF_TYPE_UNQUOTED:
+		return sldns_str2wire_unquoted_buf(str, rd, len);
 	case LDNS_RDF_TYPE_TAG:
 		return sldns_str2wire_tag_buf(str, rd, len);
 	case LDNS_RDF_TYPE_LONG_STR:
@@ -2554,12 +2557,42 @@ int sldns_str2wire_atma_buf(const char* 
 {
 	const char* s = str;
 	size_t slen = strlen(str);
-	size_t dlen = 0; /* number of hexdigits parsed */
+	size_t dlen = 0; /* number of hexdigits parsed for hex,
+		digits for E.164 */
 
-	/* just a hex string with optional dots? */
-	/* notimpl e.164 format */
 	if(slen > LDNS_MAX_RDFLEN*2)
 		return LDNS_WIREPARSE_ERR_LABEL_OVERFLOW;
+	if(*len < 1)
+		return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
+	if(*s == 0) {
+		/* empty string */
+		rd[0] = 0;
+		*len = 1;
+		return LDNS_WIREPARSE_ERR_OK;
+	}
+	if(s[0] == '+') {
+		rd[0] = 1; /* E.164 format */
+		/* digits '0'..'9', with skipped dots. */
+		s++;
+		while(*s) {
+			if(isspace((unsigned char)*s) || *s == '.') {
+				s++;
+				continue;
+			}
+			if(*s < '0' || *s > '9')
+				return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX, s-str);
+			if(*len < dlen + 2)
+				return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL,
+					s-str);
+			rd[dlen+1] = *s++;
+			dlen++;
+		}
+		*len = dlen+1;
+		return LDNS_WIREPARSE_ERR_OK;
+	}
+
+	rd[0] = 0; /* AESA format */
+	/* hex, with skipped dots. */
 	while(*s) {
 		if(isspace((unsigned char)*s) || *s == '.') {
 			s++;
@@ -2567,17 +2600,17 @@ int sldns_str2wire_atma_buf(const char* 
 		}
 		if(!isxdigit((unsigned char)*s))
 			return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX_HEX, s-str);
-		if(*len < dlen/2 + 1)
+		if(*len < dlen/2 + 2)
 			return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL,
 				s-str);
 		if((dlen&1)==0)
-			rd[dlen/2] = (uint8_t)sldns_hexdigit_to_int(*s++) * 16;
-		else	rd[dlen/2] += sldns_hexdigit_to_int(*s++);
+			rd[dlen/2 + 1] = (uint8_t)sldns_hexdigit_to_int(*s++) * 16;
+		else	rd[dlen/2 + 1] += sldns_hexdigit_to_int(*s++);
 		dlen++;
 	}
 	if((dlen&1)!=0)
 		return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX_HEX, s-str);
-	*len = dlen/2;
+	*len = dlen/2 + 1;
 	return LDNS_WIREPARSE_ERR_OK;
 }
 
@@ -2744,6 +2777,11 @@ int sldns_str2wire_eui64_buf(const char*
 	rd[7] = h;
 	*len = 8;
 	return LDNS_WIREPARSE_ERR_OK;
+}
+
+int sldns_str2wire_unquoted_buf(const char* str, uint8_t* rd, size_t* len)
+{
+	return sldns_str2wire_str_buf(str, rd, len);
 }
 
 int sldns_str2wire_tag_buf(const char* str, uint8_t* rd, size_t* len)
Index: sldns/str2wire.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/sldns/str2wire.h,v
diff -u -p -r1.6 str2wire.h
--- sldns/str2wire.h	5 Sep 2023 11:12:10 -0000	1.6
+++ sldns/str2wire.h	30 Aug 2025 11:52:23 -0000
@@ -552,6 +552,15 @@ int sldns_str2wire_eui48_buf(const char*
 int sldns_str2wire_eui64_buf(const char* str, uint8_t* rd, size_t* len);
 
 /**
+ * Convert rdf of type LDNS_RDF_TYPE_UNQUOTED from string to wireformat.
+ * @param str: the text to convert for this rdata element.
+ * @param rd: rdata buffer for the wireformat.
+ * @param len: length of rd buffer on input, used length on output.
+ * @return 0 on success, error on failure.
+ */
+int sldns_str2wire_unquoted_buf(const char* str, uint8_t* rd, size_t* len);
+
+/**
  * Convert rdf of type LDNS_RDF_TYPE_TAG from string to wireformat.
  * @param str: the text to convert for this rdata element.
  * @param rd: rdata buffer for the wireformat.
Index: sldns/wire2str.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/sldns/wire2str.c,v
diff -u -p -r1.17 wire2str.c
--- sldns/wire2str.c	4 Sep 2024 09:36:40 -0000	1.17
+++ sldns/wire2str.c	30 Aug 2025 11:52:23 -0000
@@ -1344,6 +1344,8 @@ int sldns_wire2str_rdf_scan(uint8_t** d,
 		return sldns_wire2str_eui48_scan(d, dlen, s, slen);
 	case LDNS_RDF_TYPE_EUI64:
 		return sldns_wire2str_eui64_scan(d, dlen, s, slen);
+	case LDNS_RDF_TYPE_UNQUOTED:
+		return sldns_wire2str_unquoted_scan(d, dlen, s, slen);
 	case LDNS_RDF_TYPE_TAG:
 		return sldns_wire2str_tag_scan(d, dlen, s, slen);
 	case LDNS_RDF_TYPE_LONG_STR:
@@ -1870,7 +1872,33 @@ int sldns_wire2str_nsap_scan(uint8_t** d
 
 int sldns_wire2str_atma_scan(uint8_t** d, size_t* dl, char** s, size_t* sl)
 {
-	return print_remainder_hex("", d, dl, s, sl);
+	uint8_t format;
+	int w = 0;
+	size_t i;
+
+	if(*dl < 1) return -1;
+	format = (*d)[0];
+	(*d)+=1;
+	(*dl)-=1;
+
+	if(format == 0) {
+		/* AESA format (ATM End System Address). */
+		return print_remainder_hex("", d, dl, s, sl);
+	} else if(format == 1) {
+		/* E.164 format. */
+		w += sldns_str_print(s, sl, "+");
+		for(i=0; i<*dl; i++) {
+			if((*d)[i] < '0' || (*d)[0] > '9')
+				return -1;
+			w += sldns_str_print(s, sl, "%c", (*d)[i]);
+		}
+		(*d) += *dl;
+		(*dl) = 0;
+	} else {
+		/* Unknown format. */
+		return -1;
+	}
+	return w;
 }
 
 /* internal scan routine that can modify arguments on failure */
@@ -2018,6 +2046,26 @@ int sldns_wire2str_eui64_scan(uint8_t** 
 		(*d)[6], (*d)[7]);
 	(*d)+=8;
 	(*dl)-=8;
+	return w;
+}
+
+int sldns_wire2str_unquoted_scan(uint8_t** d, size_t* dl, char** s, size_t* sl)
+{
+	int w = 0;
+	size_t i, len;
+	if(*dl < 1) return -1;
+	len = **d;
+	if(*dl < 1+len) return -1;
+	(*d)++;
+	(*dl)--;
+	for(i=0; i<len; i++) {
+		if(isspace((unsigned char)(*d)[i]) || (*d)[i] == '(' ||
+			(*d)[i] == ')' || (*d)[i] == '\'')
+			w += sldns_str_print(s, sl, "\\%c", (char)(*d)[i]);
+		else	w += str_char_print(s, sl, (*d)[i]);
+	}
+	(*d)+=len;
+	(*dl)-=len;
 	return w;
 }
 
Index: sldns/wire2str.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/sldns/wire2str.h,v
diff -u -p -r1.8 wire2str.h
--- sldns/wire2str.h	12 Apr 2024 15:45:24 -0000	1.8
+++ sldns/wire2str.h	30 Aug 2025 11:52:23 -0000
@@ -920,6 +920,19 @@ int sldns_wire2str_eui64_scan(uint8_t** 
 	size_t* str_len);
 
 /**
+ * Scan wireformat UNQUOTED field to string, with user buffers.
+ * It shifts the arguments to move along (see sldns_wire2str_pkt_scan).
+ * @param data: wireformat data.
+ * @param data_len: length of data buffer.
+ * @param str: string buffer.
+ * @param str_len: length of string buffer.
+ * @return number of characters (except null) needed to print.
+ * 	Can return -1 on failure.
+ */
+int sldns_wire2str_unquoted_scan(uint8_t** data, size_t* data_len, char** str,
+	size_t* str_len);
+
+/**
  * Scan wireformat TAG field to string, with user buffers.
  * It shifts the arguments to move along (see sldns_wire2str_pkt_scan).
  * @param data: wireformat data.
Index: smallapp/unbound-checkconf.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/smallapp/unbound-checkconf.c,v
diff -u -p -r1.24 unbound-checkconf.c
--- smallapp/unbound-checkconf.c	4 Sep 2024 09:36:40 -0000	1.24
+++ smallapp/unbound-checkconf.c	30 Aug 2025 11:52:23 -0000
@@ -342,8 +342,6 @@ interfacechecks(struct config_file* cfg)
 	int i, j, i2, j2;
 	char*** resif = NULL;
 	int* num_resif = NULL;
-	char portbuf[32];
-	snprintf(portbuf, sizeof(portbuf), "%d", cfg->port);
 
 	if(cfg->num_ifs != 0) {
 		resif = (char***)calloc(cfg->num_ifs, sizeof(char**));
@@ -366,14 +364,18 @@ interfacechecks(struct config_file* cfg)
 				cfg->ifs[i]);
 		}
 		/* check for port combinations that are not supported */
-		if(if_is_pp2(resif[i][0], portbuf, cfg->proxy_protocol_port)) {
-			if(if_is_dnscrypt(resif[i][0], portbuf,
+		if(if_is_pp2(resif[i][0], cfg->port, cfg->proxy_protocol_port)) {
+			if(if_is_dnscrypt(resif[i][0], cfg->port,
 				cfg->dnscrypt_port)) {
 				fatal_exit("PROXYv2 and DNSCrypt combination not "
 					"supported!");
-			} else if(if_is_https(resif[i][0], portbuf,
+			} else if(if_is_https(resif[i][0], cfg->port,
 				cfg->https_port)) {
 				fatal_exit("PROXYv2 and DoH combination not "
+					"supported!");
+			} else if(if_is_quic(resif[i][0], cfg->port,
+				cfg->quic_port)) {
+				fatal_exit("PROXYv2 and DoQ combination not "
 					"supported!");
 			}
 		}
Index: smallapp/unbound-control-setup.sh.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/smallapp/unbound-control-setup.sh.in,v
diff -u -p -r1.10 unbound-control-setup.sh.in
--- smallapp/unbound-control-setup.sh.in	21 Feb 2025 13:20:40 -0000	1.10
+++ smallapp/unbound-control-setup.sh.in	30 Aug 2025 11:52:23 -0000
@@ -204,7 +204,8 @@ fi
 # remove unused permissions
 chmod o-rw \
       "$SVR_BASE.pem" \
-      "$SVR_BASE.key" \
+      "$SVR_BASE.key"
+chmod g+r,o-rw \
       "$CTL_BASE.pem" \
       "$CTL_BASE.key"
 
Index: smallapp/unbound-control.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/smallapp/unbound-control.c,v
diff -u -p -r1.29 unbound-control.c
--- smallapp/unbound-control.c	21 Feb 2025 13:20:40 -0000	1.29
+++ smallapp/unbound-control.c	30 Aug 2025 11:52:23 -0000
@@ -109,6 +109,16 @@ usage(void)
 	printf("  				That means the caches sizes and\n");
 	printf("  				the number of threads must not\n");
 	printf("  				change between reloads.\n");
+	printf("  fast_reload [+dpv]		reloads the server but only briefly stops\n");
+	printf("  				server processing, keeps cache, and changes\n");
+	printf("  				most options; check unbound-control(8).\n");
+	printf("  		+d		drops running queries to keep consistency\n");
+	printf("  				on changed options while reloading.\n");
+	printf("  		+p		does not pause threads for even faster\n");
+	printf("  				reload but less options are supported\n");
+	printf("  				; check unbound-control(8).\n");
+	printf("  		+v		verbose output, it will include duration needed.\n");
+	printf("  		+vv		more verbose output, it will include memory needed.\n");
 	printf("  stats				print statistics\n");
 	printf("  stats_noreset			peek at statistics\n");
 #ifdef HAVE_SHMGET
@@ -222,6 +232,9 @@ static void pr_stats(const char* nm, str
 		s->svr.num_queries_cookie_client);
 	PR_UL_NM("num.queries_cookie_invalid",
 		s->svr.num_queries_cookie_invalid);
+	PR_UL_NM("num.queries_discard_timeout",
+		s->svr.num_queries_discard_timeout);
+	PR_UL_NM("num.queries_wait_limit", s->svr.num_queries_wait_limit);
 	PR_UL_NM("num.cachehits",
 		s->svr.num_queries - s->svr.num_queries_missed_cache);
 	PR_UL_NM("num.cachemiss", s->svr.num_queries_missed_cache);
@@ -231,12 +244,13 @@ static void pr_stats(const char* nm, str
 	PR_UL_NM("num.expired", s->svr.ans_expired);
 	PR_UL_NM("num.recursivereplies", s->mesh_replies_sent);
 #ifdef USE_DNSCRYPT
-    PR_UL_NM("num.dnscrypt.crypted", s->svr.num_query_dnscrypt_crypted);
-    PR_UL_NM("num.dnscrypt.cert", s->svr.num_query_dnscrypt_cert);
-    PR_UL_NM("num.dnscrypt.cleartext", s->svr.num_query_dnscrypt_cleartext);
-    PR_UL_NM("num.dnscrypt.malformed",
-             s->svr.num_query_dnscrypt_crypted_malformed);
+	PR_UL_NM("num.dnscrypt.crypted", s->svr.num_query_dnscrypt_crypted);
+	PR_UL_NM("num.dnscrypt.cert", s->svr.num_query_dnscrypt_cert);
+	PR_UL_NM("num.dnscrypt.cleartext", s->svr.num_query_dnscrypt_cleartext);
+	PR_UL_NM("num.dnscrypt.malformed",
+		s->svr.num_query_dnscrypt_crypted_malformed);
 #endif /* USE_DNSCRYPT */
+	PR_UL_NM("num.dns_error_reports", s->svr.num_dns_error_reports);
 	printf("%s.requestlist.avg"SQ"%g\n", nm,
 		(s->svr.num_queries_missed_cache+s->svr.num_queries_prefetch)?
 			(double)s->svr.sum_query_list_size/
Index: smallapp/worker_cb.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/smallapp/worker_cb.c,v
diff -u -p -r1.13 worker_cb.c
--- smallapp/worker_cb.c	21 Feb 2025 13:20:40 -0000	1.13
+++ smallapp/worker_cb.c	30 Aug 2025 11:52:23 -0000
@@ -256,6 +256,20 @@ void dtio_mainfdcallback(int ATTR_UNUSED
 }
 #endif
 
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
+	void* ATTR_UNUSED(arg))
+{
+	log_assert(0);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+	void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+	log_assert(0);
+	return 0;
+}
+
 #ifdef HAVE_NGTCP2
 void doq_client_event_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
 	void* ATTR_UNUSED(arg))
Index: testcode/checklocks.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/checklocks.c,v
diff -u -p -r1.1.1.4 checklocks.c
--- testcode/checklocks.c	21 Feb 2025 13:17:10 -0000	1.1.1.4
+++ testcode/checklocks.c	30 Aug 2025 11:52:23 -0000
@@ -64,6 +64,9 @@ static int key_deleted = 0;
 static ub_thread_key_type thr_debug_key;
 /** the list of threads, so all threads can be examined. NULL if unused. */
 static struct thr_check* thread_infos[THRDEBUG_MAX_THREADS];
+/** stored maximum lock number for threads, when a thread is restarted the
+ * number is kept track of, because the new locks get new id numbers. */
+static int thread_lockcount[THRDEBUG_MAX_THREADS];
 /** do we check locking order */
 int check_locking_order = 1;
 /** the pid of this runset, reasonably unique. */
@@ -698,10 +701,20 @@ open_lockorder(struct thr_check* thr)
 	char buf[24];
 	time_t t;
 	snprintf(buf, sizeof(buf), "%s.%d", output_name, thr->num);
-	thr->order_info = fopen(buf, "w");
-	if(!thr->order_info)
-		fatal_exit("could not open %s: %s", buf, strerror(errno));
-	thr->locks_created = 0;
+	thr->locks_created = thread_lockcount[thr->num];
+	if(thr->locks_created == 0) {
+		thr->order_info = fopen(buf, "w");
+		if(!thr->order_info)
+			fatal_exit("could not open %s: %s", buf, strerror(errno));
+	} else {
+		/* There is already a file to append on with the previous
+		 * thread information. */
+		thr->order_info = fopen(buf, "a");
+		if(!thr->order_info)
+			fatal_exit("could not open for append %s: %s", buf, strerror(errno));
+		return;
+	}
+
 	t = time(NULL);
 	/* write: <time_stamp> <runpid> <thread_num> */
 	if(fwrite(&t, sizeof(t), 1, thr->order_info) != 1 ||
@@ -728,6 +741,7 @@ static void* checklock_main(void* arg)
 	if(check_locking_order)
 		open_lockorder(thr);
 	ret = thr->func(thr->arg);
+	thread_lockcount[thr->num] = thr->locks_created;
 	thread_infos[thr->num] = NULL;
 	if(check_locking_order)
 		fclose(thr->order_info);
Index: testcode/do-tests.sh
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/do-tests.sh,v
diff -u -p -r1.1.1.4 do-tests.sh
--- testcode/do-tests.sh	20 Oct 2022 08:25:17 -0000	1.1.1.4
+++ testcode/do-tests.sh	30 Aug 2025 11:52:23 -0000
@@ -17,6 +17,7 @@ NEED_IPV6='fwd_ancil.tdir fwd_tcp_tc6.td
 NEED_NOMINGW='tcp_sigpipe.tdir 07-confroot.tdir 08-host-lib.tdir fwd_ancil.tdir'
 NEED_DNSCRYPT_PROXY='dnscrypt_queries.tdir dnscrypt_queries_chacha.tdir'
 NEED_UNSHARE='acl_interface.tdir proxy_protocol.tdir'
+NEED_REDIS_SERVER='redis_replica.tdir'
 
 # test if dig and ldns-testns are available.
 test_tool_avail "dig"
@@ -52,6 +53,7 @@ for test in `ls -d *.tdir`; do
 	skip_if_in_list $test "$NEED_WHOAMI" "whoami"
 	skip_if_in_list $test "$NEED_DNSCRYPT_PROXY" "dnscrypt-proxy"
 	skip_if_in_list $test "$NEED_UNSHARE" "unshare"
+	skip_if_in_list $test "$NEED_REDIS_SERVER" "redis-server"
 
 	if echo $NEED_IPV6 | grep $test >/dev/null; then
 		if test "$HAVE_IPV6" = no; then
Index: testcode/doqclient.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/doqclient.c,v
diff -u -p -r1.1.1.1 doqclient.c
--- testcode/doqclient.c	21 Feb 2025 13:17:10 -0000	1.1.1.1
+++ testcode/doqclient.c	30 Aug 2025 11:52:23 -0000
@@ -2699,3 +2699,17 @@ void dtio_mainfdcallback(int ATTR_UNUSED
 	log_assert(0);
 }
 #endif
+
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
+	void* ATTR_UNUSED(arg))
+{
+	log_assert(0);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+	void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+	log_assert(0);
+	return 0;
+}
Index: testcode/fake_event.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/fake_event.c,v
diff -u -p -r1.1.1.17 fake_event.c
--- testcode/fake_event.c	21 Feb 2025 13:17:11 -0000	1.1.1.17
+++ testcode/fake_event.c	30 Aug 2025 11:52:23 -0000
@@ -938,11 +938,11 @@ listen_create(struct comm_base* base, st
 	char* ATTR_UNUSED(http_endpoint),
 	int ATTR_UNUSED(http_notls),
 	struct tcl_list* ATTR_UNUSED(tcp_conn_limit),
-	void* ATTR_UNUSED(sslctx), struct dt_env* ATTR_UNUSED(dtenv),
+	void* ATTR_UNUSED(dot_sslctx), void* ATTR_UNUSED(doh_sslctx),
+	void* ATTR_UNUSED(quic_ssl),
+	struct dt_env* ATTR_UNUSED(dtenv),
 	struct doq_table* ATTR_UNUSED(table),
 	struct ub_randstate* ATTR_UNUSED(rnd),
-	const char* ATTR_UNUSED(ssl_service_key),
-	const char* ATTR_UNUSED(ssl_service_pem),
 	struct config_file* ATTR_UNUSED(cfg),
 	comm_point_callback_type* cb, void *cb_arg)
 {
@@ -1264,7 +1264,7 @@ struct serviced_query* outnet_serviced_q
 	struct replay_runtime* runtime = (struct replay_runtime*)outnet->base;
 	struct fake_pending* pend = (struct fake_pending*)calloc(1,
 		sizeof(struct fake_pending));
-	char z[256];
+	char z[LDNS_MAX_DOMAINLEN];
 	log_assert(pend);
 	log_nametypeclass(VERB_OPS, "pending serviced query",
 		qinfo->qname, qinfo->qtype, qinfo->qclass);
@@ -1484,6 +1484,11 @@ size_t comm_point_get_mem(struct comm_po
 	return 0;
 }
 
+size_t comm_timer_get_mem(struct comm_timer* ATTR_UNUSED(timer))
+{
+	return 0;
+}
+
 size_t serviced_get_mem(struct serviced_query* ATTR_UNUSED(c))
 {
 	return 0;
@@ -1939,7 +1944,8 @@ int comm_point_send_udp_msg(struct comm_
 }
 
 int outnet_get_tcp_fd(struct sockaddr_storage* ATTR_UNUSED(addr),
-	socklen_t ATTR_UNUSED(addrlen), int ATTR_UNUSED(tcp_mss), int ATTR_UNUSED(dscp))
+	socklen_t ATTR_UNUSED(addrlen), int ATTR_UNUSED(tcp_mss),
+	int ATTR_UNUSED(dscp), int ATTR_UNUSED(nodelay))
 {
 	log_assert(0);
 	return -1;
@@ -1991,6 +1997,26 @@ void http2_stream_add_meshstate(struct h
 
 void http2_stream_remove_mesh_state(struct http2_stream* ATTR_UNUSED(h2_stream))
 {
+}
+
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(event),
+	void* ATTR_UNUSED(arg))
+{
+	log_assert(0);
+}
+
+void fast_reload_thread_stop(
+	struct fast_reload_thread* ATTR_UNUSED(fast_reload_thread))
+{
+	/* nothing */
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+	void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+	log_assert(0);
+	return 0;
 }
 
 /*********** End of Dummy routines ***********/
Index: testcode/testbound.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/testbound.c,v
diff -u -p -r1.1.1.12 testbound.c
--- testcode/testbound.c	21 Feb 2025 13:17:11 -0000	1.1.1.12
+++ testcode/testbound.c	30 Aug 2025 11:52:23 -0000
@@ -601,7 +601,24 @@ void listen_desetup_locks(void)
 	/* nothing */
 }
 
+void fast_reload_printq_list_delete(
+	struct fast_reload_printq* ATTR_UNUSED(list))
+{
+	/* nothing */
+}
+
+void fast_reload_worker_pickup_changes(struct worker* ATTR_UNUSED(worker))
+{
+	/* nothing */
+}
+
 #ifdef HAVE_NGTCP2
+void* quic_sslctx_create(char* ATTR_UNUSED(key), char* ATTR_UNUSED(pem),
+	char* ATTR_UNUSED(verifypem))
+{
+    return NULL;
+}
+
 void comm_point_doq_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(event),
 	void* ATTR_UNUSED(arg))
 {
Index: testcode/unitdname.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unitdname.c,v
diff -u -p -r1.1.1.2 unitdname.c
--- testcode/unitdname.c	19 Mar 2020 17:46:45 -0000	1.1.1.2
+++ testcode/unitdname.c	30 Aug 2025 11:52:23 -0000
@@ -39,6 +39,7 @@
  */
 
 #include "config.h"
+#include <ctype.h>
 #include "util/log.h"
 #include "testcode/unitmain.h"
 #include "util/data/dname.h"
@@ -858,6 +859,151 @@ dname_setup_bufs(sldns_buffer* loopbuf, 
 	sldns_buffer_flip(boundbuf);
 }
 
+static void
+dname_test_str(sldns_buffer* buff)
+{
+	char result[LDNS_MAX_DOMAINLEN], expect[LDNS_MAX_DOMAINLEN], *e;
+	size_t i;
+	unit_show_func("util/data/dname.c", "dname_str");
+
+	/* root ; expected OK */
+	sldns_buffer_clear(buff);
+	sldns_buffer_write(buff, "\000", 1);
+	sldns_buffer_flip(buff);
+	unit_assert( sldns_buffer_limit(buff) == 1 );
+	unit_assert( pkt_dname_len(buff) == 1 );
+	dname_str(sldns_buffer_begin(buff), result);
+	unit_assert( strcmp( ".", result) == 0 );
+
+	/* LDNS_MAX_DOMAINLEN - 1 ; expected OK */
+	sldns_buffer_clear(buff);
+	sldns_buffer_write(buff,
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /*  64 up to here */
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /* 128 up to here */
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /* 192 up to here */
+		"\074abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567"     /* 253 up to here */
+		"\000"                                                                 /* 254 up to here */
+		, 254);
+	sldns_buffer_flip(buff);
+	unit_assert( sldns_buffer_limit(buff) == 254 );
+	unit_assert( pkt_dname_len(buff) == 254 );
+	dname_str(sldns_buffer_begin(buff), result);
+	unit_assert( strcmp(
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567"
+		".", result) == 0 );
+
+	/* LDNS_MAX_DOMAINLEN ; expected OK */
+	sldns_buffer_clear(buff);
+	sldns_buffer_write(buff,
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /*  64 up to here */
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /* 128 up to here */
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /* 192 up to here */
+		"\075abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"    /* 254 up to here */
+		"\000"                                                                 /* 255 up to here */
+		, 255);
+	sldns_buffer_flip(buff);
+	unit_assert( sldns_buffer_limit(buff) == 255 );
+	unit_assert( pkt_dname_len(buff) == 255 );
+	dname_str(sldns_buffer_begin(buff), result);
+	unit_assert( strcmp(
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"
+		".", result) == 0 );
+
+	/* LDNS_MAX_DOMAINLEN + 1 ; expected to fail, output uses '&' on the latest label */
+	sldns_buffer_clear(buff);
+	sldns_buffer_write(buff,
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /*  64 up to here */
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /* 128 up to here */
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /* 192 up to here */
+		"\076abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"   /* 255 up to here */
+		"\000"                                                                 /* 256 up to here */
+		, 256);
+	sldns_buffer_flip(buff);
+	unit_assert( sldns_buffer_limit(buff) == 256 );
+	unit_assert( pkt_dname_len(buff) == 0 );
+	dname_str(sldns_buffer_begin(buff), result);
+	unit_assert( strcmp(
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"&", result) == 0 );
+
+	/* LDNS_MAX_LABELLEN + 1 ; expected to fail, output uses '#' on the offending label */
+	sldns_buffer_clear(buff);
+	sldns_buffer_write(buff,
+		"\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"  /*  64 up to here */
+		"\100abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890a" /* 129 up to here */
+		"\000"                                                                 /* 130 up to here */
+		, 130);
+	sldns_buffer_flip(buff);
+	unit_assert( sldns_buffer_limit(buff) == 130 );
+	unit_assert( pkt_dname_len(buff) == 0 );
+	dname_str(sldns_buffer_begin(buff), result);
+	unit_assert( strcmp(
+		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890."
+		"#", result) == 0 );
+
+	/* LDNS_MAX_DOMAINLEN with single labels; expected OK */
+	sldns_buffer_clear(buff);
+	for(i=0; i<=252; i+=2)
+		sldns_buffer_write(buff, "\001a", 2);
+	sldns_buffer_write_u8(buff, 0);
+	sldns_buffer_flip(buff);
+	unit_assert( sldns_buffer_limit(buff) == 255 );
+	unit_assert( pkt_dname_len(buff) == 255 );
+	dname_str(sldns_buffer_begin(buff), result);
+	e = expect;
+	for(i=0; i<=252; i+=2) {
+		*e++ = 'a';
+		*e++ = '.';
+	}
+	*e = '\0';
+	unit_assert( strcmp(expect, result) == 0 );
+
+	/* LDNS_MAX_DOMAINLEN + 1 with single labels; expected to fail, output uses '&' on the latest label */
+	sldns_buffer_clear(buff);
+	for(i=0; i<=250; i+=2)
+		sldns_buffer_write(buff, "\001a", 2);
+	sldns_buffer_write(buff, "\002ab", 3);
+	sldns_buffer_write_u8(buff, 0);
+	sldns_buffer_flip(buff);
+	unit_assert( sldns_buffer_limit(buff) == 256 );
+	unit_assert( pkt_dname_len(buff) == 0 );
+	dname_str(sldns_buffer_begin(buff), result);
+	e = expect;
+	for(i=0; i<=250; i+=2) {
+		*e++ = 'a';
+		*e++ = '.';
+	}
+	*e++ = '&';
+	*e = '\0';
+	unit_assert( strcmp(expect, result) == 0 );
+
+	/* Only alphas, numericals and '-', '_' and '*' are allowed in the output */
+	for(i=1; i<=255; i++) {
+		if(isalnum(i) || i == '-' || i == '_' || i == '*')
+			continue;
+		sldns_buffer_clear(buff);
+		sldns_buffer_write_u8(buff, 1);
+		sldns_buffer_write_u8(buff, (uint8_t)i);
+		sldns_buffer_write_u8(buff, 0);
+		sldns_buffer_flip(buff);
+		unit_assert( sldns_buffer_limit(buff) == 3 );
+		unit_assert( pkt_dname_len(buff) == 3);
+		dname_str(sldns_buffer_begin(buff), result);
+		if(strcmp( "?.", result) != 0 ) {
+			log_err("ASCII value '0x%lX' allowed in string output", (unsigned long)i);
+			unit_assert(0);
+		}
+	}
+}
+
 void dname_test(void)
 {
 	sldns_buffer* loopbuf = sldns_buffer_new(14);
@@ -884,6 +1030,7 @@ void dname_test(void)
 	dname_test_topdomain();
 	dname_test_valid();
 	dname_test_has_label();
+	dname_test_str(buff);
 	sldns_buffer_free(buff);
 	sldns_buffer_free(loopbuf);
 	sldns_buffer_free(boundbuf);
Index: testcode/unitldns.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unitldns.c,v
diff -u -p -r1.1.1.5 unitldns.c
--- testcode/unitldns.c	7 Jun 2022 15:40:02 -0000	1.1.1.5
+++ testcode/unitldns.c	30 Aug 2025 11:52:23 -0000
@@ -172,10 +172,14 @@ rr_test_file(const char* input, const ch
 		if(txt_in[0] == 0 || txt_in[0] == '\n' || txt_in[0] == ';')
 			continue;
 		/* read check lines */
-		if(!fgets(wire_chk, (int)bufs, chf))
+		if(!fgets(wire_chk, (int)bufs, chf)) {
 			printf("%s too short\n", check);
-		if(!fgets(txt_chk, (int)bufs, chf))
+			unit_assert(0);
+		}
+		if(!fgets(txt_chk, (int)bufs, chf)) {
 			printf("%s too short\n", check);
+			unit_assert(0);
+		}
 		chlineno += 2;
 		if(vbmp) printf("%s:%d %s", check, chlineno-1, wire_chk);
 		if(vbmp) printf("%s:%d %s", check, chlineno, txt_chk);
Index: testcode/unitmain.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unitmain.c,v
diff -u -p -r1.1.1.11 unitmain.c
--- testcode/unitmain.c	21 Feb 2025 13:17:11 -0000	1.1.1.11
+++ testcode/unitmain.c	30 Aug 2025 11:52:23 -0000
@@ -433,103 +433,6 @@ rtt_test(void)
 	unit_assert(UB_STATS_BUCKET_NUM == NUM_BUCKETS_HIST);
 }
 
-#include "services/cache/infra.h"
-
-/* lookup and get key and data structs easily */
-static struct infra_data* infra_lookup_host(struct infra_cache* infra,
-	struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone,
-	size_t zonelen, int wr, time_t now, struct infra_key** k)
-{
-	struct infra_data* d;
-	struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen,
-		zone, zonelen, wr);
-	if(!e) return NULL;
-	d = (struct infra_data*)e->data;
-	if(d->ttl < now) {
-		lock_rw_unlock(&e->lock);
-		return NULL;
-	}
-	*k = (struct infra_key*)e->key;
-	return d;
-}
-
-/** test host cache */
-static void
-infra_test(void)
-{
-	struct sockaddr_storage one;
-	socklen_t onelen;
-	uint8_t* zone = (uint8_t*)"\007example\003com\000";
-	size_t zonelen = 13;
-	struct infra_cache* slab;
-	struct config_file* cfg = config_create();
-	time_t now = 0;
-	uint8_t edns_lame;
-	int vs, to;
-	struct infra_key* k;
-	struct infra_data* d;
-	int init = 376;
-
-	unit_show_feature("infra cache");
-	unit_assert(ipstrtoaddr("127.0.0.1", 53, &one, &onelen));
-
-	slab = infra_create(cfg);
-	unit_assert( infra_host(slab, &one, onelen, zone, zonelen, now,
-		&vs, &edns_lame, &to) );
-	unit_assert( vs == 0 && to == init && edns_lame == 0 );
-
-	unit_assert( infra_rtt_update(slab, &one, onelen, zone, zonelen, LDNS_RR_TYPE_A, -1, init, now) );
-	unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
-			now, &vs, &edns_lame, &to) );
-	unit_assert( vs == 0 && to == init*2 && edns_lame == 0 );
-
-	unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, -1, now) );
-	unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
-			now, &vs, &edns_lame, &to) );
-	unit_assert( vs == -1 && to == init*2  && edns_lame == 1);
-
-	now += cfg->host_ttl + 10;
-	unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
-			now, &vs, &edns_lame, &to) );
-	unit_assert( vs == 0 && to == init && edns_lame == 0 );
-	
-	unit_assert( infra_set_lame(slab, &one, onelen,
-		zone, zonelen,  now, 0, 0, LDNS_RR_TYPE_A) );
-	unit_assert( (d=infra_lookup_host(slab, &one, onelen, zone, zonelen, 0, now, &k)) );
-	unit_assert( d->ttl == now+cfg->host_ttl );
-	unit_assert( d->edns_version == 0 );
-	unit_assert(!d->isdnsseclame && !d->rec_lame && d->lame_type_A &&
-		!d->lame_other);
-	lock_rw_unlock(&k->entry.lock);
-
-	/* test merge of data */
-	unit_assert( infra_set_lame(slab, &one, onelen,
-		zone, zonelen,  now, 0, 0, LDNS_RR_TYPE_AAAA) );
-	unit_assert( (d=infra_lookup_host(slab, &one, onelen, zone, zonelen, 0, now, &k)) );
-	unit_assert(!d->isdnsseclame && !d->rec_lame && d->lame_type_A &&
-		d->lame_other);
-	lock_rw_unlock(&k->entry.lock);
-
-	/* test that noEDNS cannot overwrite known-yesEDNS */
-	now += cfg->host_ttl + 10;
-	unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
-			now, &vs, &edns_lame, &to) );
-	unit_assert( vs == 0 && to == init && edns_lame == 0 );
-
-	unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, 0, now) );
-	unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
-			now, &vs, &edns_lame, &to) );
-	unit_assert( vs == 0 && to == init && edns_lame == 1 );
-
-	unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, -1, now) );
-	unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
-			now, &vs, &edns_lame, &to) );
-	unit_assert( vs == 0 && to == init && edns_lame == 1 );
-
-	infra_delete(slab);
-	config_delete(cfg);
-}
-
 #include "util/edns.h"
 /* Complete version-invalid client cookie; needs a new one.
  * Based on edns_cookie_rfc9018_a2 */
Index: testcode/unitmain.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unitmain.h,v
diff -u -p -r1.1.1.3 unitmain.h
--- testcode/unitmain.h	21 Feb 2025 13:17:11 -0000	1.1.1.3
+++ testcode/unitmain.h	30 Aug 2025 11:52:23 -0000
@@ -86,5 +86,7 @@ void zonemd_test(void);
 void tcpreuse_test(void);
 /** unit test for doq functions */
 void doq_test(void);
+/** unit test for infra cache functions */
+void infra_test(void);
 
 #endif /* TESTCODE_UNITMAIN_H */
Index: testcode/unitneg.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unitneg.c,v
diff -u -p -r1.1.1.2 unitneg.c
--- testcode/unitneg.c	8 Oct 2018 16:02:24 -0000	1.1.1.2
+++ testcode/unitneg.c	30 Aug 2025 11:52:23 -0000
@@ -53,7 +53,7 @@ static int negverbose = 0;
 /** debug printout of neg cache */
 static void print_neg_cache(struct val_neg_cache* neg)
 {
-	char buf[1024];
+	char buf[LDNS_MAX_DOMAINLEN];
 	struct val_neg_zone* z;
 	struct val_neg_data* d;
 	printf("neg_cache print\n");
Index: testcode/unitverify.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unitverify.c,v
diff -u -p -r1.3 unitverify.c
--- testcode/unitverify.c	4 Sep 2024 09:36:41 -0000	1.3
+++ testcode/unitverify.c	30 Aug 2025 11:52:23 -0000
@@ -425,7 +425,7 @@ nsec3_hash_test_entry(struct entry* e, r
 {
 	struct query_info qinfo;
 	struct reply_info* rep = NULL;
-	struct ub_packed_rrset_key* answer, *nsec3;
+	struct ub_packed_rrset_key* answer, *nsec3, *nsec3_region;
 	struct nsec3_cached_hash* hash = NULL;
 	int ret;
 	uint8_t* qname;
@@ -443,7 +443,11 @@ nsec3_hash_test_entry(struct entry* e, r
 	/* check test is OK */
 	unit_assert(nsec3 && answer && qname);
 
-	ret = nsec3_hash_name(ct, region, buf, nsec3, 0, qname,
+	/* Copy the nsec3 to the region, so it can stay referenced by the
+	 * ct tree entry. The region is freed when the file is done. */
+	nsec3_region = packed_rrset_copy_region(nsec3, region, 0);
+
+	ret = nsec3_hash_name(ct, region, buf, nsec3_region, 0, qname,
 		qinfo.qname_len, &hash);
 	if(ret < 1) {
 		printf("Bad nsec3_hash_name retcode %d\n", ret);
Index: testcode/unitzonemd.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unitzonemd.c,v
diff -u -p -r1.1.1.4 unitzonemd.c
--- testcode/unitzonemd.c	21 Feb 2025 13:17:10 -0000	1.1.1.4
+++ testcode/unitzonemd.c	30 Aug 2025 11:52:23 -0000
@@ -111,7 +111,7 @@ static void zonemd_generate_test(const c
 		digestdup[i] = toupper((unsigned char)digestdup[i]);
 	}
 	if(verbosity >= VERB_ALGO) {
-		char zname[255+1];
+		char zname[LDNS_MAX_DOMAINLEN];
 		dname_str(z->name, zname);
 		printf("zonemd generated for %s in %s with "
 			"scheme=%d hashalgo=%d\n", zname, z->zonefile,
Index: util/config_file.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/config_file.c,v
diff -u -p -r1.37 config_file.c
--- util/config_file.c	21 Feb 2025 13:20:40 -0000	1.37
+++ util/config_file.c	30 Aug 2025 11:52:23 -0000
@@ -280,11 +280,10 @@ config_create(void)
 	cfg->ignore_cd = 0;
 	cfg->disable_edns_do = 0;
 	cfg->serve_expired = 0;
-	cfg->serve_expired_ttl = 0;
+	cfg->serve_expired_ttl = 86400;
 	cfg->serve_expired_ttl_reset = 0;
 	cfg->serve_expired_reply_ttl = 30;
-	cfg->serve_expired_client_timeout = 0;
-	cfg->ede_serve_expired = 0;
+	cfg->serve_expired_client_timeout = 1800;
 	cfg->serve_original_ttl = 0;
 	cfg->zonemd_permissive_mode = 0;
 	cfg->add_holddown = 30*24*3600;
@@ -329,11 +328,7 @@ config_create(void)
 	if(!(cfg->control_cert_file = strdup(RUN_DIR"/unbound_control.pem")))
 		goto error_exit;
 
-#ifdef CLIENT_SUBNET
-	if(!(cfg->module_conf = strdup("subnetcache validator iterator"))) goto error_exit;
-#else
 	if(!(cfg->module_conf = strdup("validator iterator"))) goto error_exit;
-#endif
 	if(!(cfg->val_nsec3_key_iterations =
 		strdup("1024 150 2048 150 4096 150"))) goto error_exit;
 #if defined(DNSTAP_SOCKET_PATH)
@@ -399,14 +394,22 @@ config_create(void)
 	cfg->cachedb_check_when_serve_expired = 1;
 #ifdef USE_REDIS
 	if(!(cfg->redis_server_host = strdup("127.0.0.1"))) goto error_exit;
+	if(!(cfg->redis_replica_server_host = strdup(""))) goto error_exit;
 	cfg->redis_server_path = NULL;
+	cfg->redis_replica_server_path = NULL;
 	cfg->redis_server_password = NULL;
+	cfg->redis_replica_server_password = NULL;
 	cfg->redis_timeout = 100;
+	cfg->redis_replica_timeout = 100;
 	cfg->redis_command_timeout = 0;
+	cfg->redis_replica_command_timeout = 0;
 	cfg->redis_connect_timeout = 0;
+	cfg->redis_replica_connect_timeout = 0;
 	cfg->redis_server_port = 6379;
+	cfg->redis_replica_server_port = 6379;
 	cfg->redis_expire_records = 0;
 	cfg->redis_logical_db = 0;
+	cfg->redis_replica_logical_db = 0;
 #endif  /* USE_REDIS */
 #endif  /* USE_CACHEDB */
 #ifdef USE_IPSET
@@ -414,9 +417,11 @@ config_create(void)
 	cfg->ipset_name_v6 = NULL;
 #endif
 	cfg->ede = 0;
+	cfg->ede_serve_expired = 0;
+	cfg->dns_error_reporting = 0;
 	cfg->iter_scrub_ns = 20;
 	cfg->iter_scrub_cname = 11;
-	cfg->max_global_quota = 128;
+	cfg->max_global_quota = 200;
 	return cfg;
 error_exit:
 	config_delete(cfg);
@@ -502,6 +507,25 @@ struct config_file* config_create_forlib
 #define S_STRLIST_APPEND(str, var) if(strcmp(opt, str)==0) \
 	{ return cfg_strlist_append(&cfg->var, strdup(val)); }
 
+/** Set PROBE_MAXRTO based on current RTT_MAX_TIMEOUT
+ *  (USEFUL_SERVER_TOP_TIMEOUT) configuration. */
+static int
+probe_maxrto(int useful_server_top_timeout) {
+	return
+	PROBE_MAXRTO > useful_server_top_timeout
+		?useful_server_top_timeout
+		:PROBE_MAXRTO_DEFAULT;
+}
+
+/** Apply the relevant changes that rely upon RTT_MAX_TIMEOUT */
+int config_apply_max_rtt(int max_rtt)
+{
+	USEFUL_SERVER_TOP_TIMEOUT = max_rtt;
+	BLACKLIST_PENALTY = max_rtt*4;
+	PROBE_MAXRTO = probe_maxrto(max_rtt);
+	return max_rtt;
+}
+
 int config_set_option(struct config_file* cfg, const char* opt,
 	const char* val)
 {
@@ -648,9 +672,7 @@ int config_set_option(struct config_file
 	}
 	else if(strcmp(opt, "infra-cache-max-rtt:") == 0) {
 		IS_NUMBER_OR_ZERO; cfg->infra_cache_max_rtt = atoi(val);
-		RTT_MAX_TIMEOUT=cfg->infra_cache_max_rtt;
-		USEFUL_SERVER_TOP_TIMEOUT = RTT_MAX_TIMEOUT;
-		BLACKLIST_PENALTY = USEFUL_SERVER_TOP_TIMEOUT*4;
+		RTT_MAX_TIMEOUT=config_apply_max_rtt(cfg->infra_cache_max_rtt);
 	}
 	else S_YNO("infra-keep-probing:", infra_keep_probing)
 	else S_NUMBER_OR_ZERO("infra-host-ttl:", host_ttl)
@@ -735,6 +757,7 @@ int config_set_option(struct config_file
 	else S_NUMBER_OR_ZERO("serve-expired-client-timeout:", serve_expired_client_timeout)
 	else S_YNO("ede:", ede)
 	else S_YNO("ede-serve-expired:", ede_serve_expired)
+	else S_YNO("dns-error-reporting:", dns_error_reporting)
 	else S_NUMBER_OR_ZERO("iter-scrub-ns:", iter_scrub_ns)
 	else S_NUMBER_OR_ZERO("iter-scrub-cname:", iter_scrub_cname)
 	else S_NUMBER_OR_ZERO("max-global-quota:", max_global_quota)
@@ -1210,6 +1233,7 @@ config_get_option(struct config_file* cf
 	else O_DEC(opt, "serve-expired-client-timeout", serve_expired_client_timeout)
 	else O_YNO(opt, "ede", ede)
 	else O_YNO(opt, "ede-serve-expired", ede_serve_expired)
+	else O_YNO(opt, "dns-error-reporting", dns_error_reporting)
 	else O_DEC(opt, "iter-scrub-ns", iter_scrub_ns)
 	else O_DEC(opt, "iter-scrub-cname", iter_scrub_cname)
 	else O_DEC(opt, "max-global-quota", max_global_quota)
@@ -1375,14 +1399,22 @@ config_get_option(struct config_file* cf
 	else O_YNO(opt, "cachedb-check-when-serve-expired", cachedb_check_when_serve_expired)
 #ifdef USE_REDIS
 	else O_STR(opt, "redis-server-host", redis_server_host)
+	else O_STR(opt, "redis-replica-server-host", redis_replica_server_host)
 	else O_DEC(opt, "redis-server-port", redis_server_port)
+	else O_DEC(opt, "redis-replica-server-port", redis_replica_server_port)
 	else O_STR(opt, "redis-server-path", redis_server_path)
+	else O_STR(opt, "redis-replica-server-path", redis_replica_server_path)
 	else O_STR(opt, "redis-server-password", redis_server_password)
+	else O_STR(opt, "redis-replica-server-password", redis_replica_server_password)
 	else O_DEC(opt, "redis-timeout", redis_timeout)
+	else O_DEC(opt, "redis-replica-timeout", redis_replica_timeout)
 	else O_DEC(opt, "redis-command-timeout", redis_command_timeout)
+	else O_DEC(opt, "redis-replica-command-timeout", redis_replica_command_timeout)
 	else O_DEC(opt, "redis-connect-timeout", redis_connect_timeout)
+	else O_DEC(opt, "redis-replica-connect-timeout", redis_replica_connect_timeout)
 	else O_YNO(opt, "redis-expire-records", redis_expire_records)
 	else O_DEC(opt, "redis-logical-db", redis_logical_db)
+	else O_DEC(opt, "redis-replica-logical-db", redis_replica_logical_db)
 #endif  /* USE_REDIS */
 #endif  /* USE_CACHEDB */
 #ifdef USE_IPSET
@@ -1718,6 +1750,7 @@ config_delete(struct config_file* cfg)
 	config_del_strarray(cfg->tagname, cfg->num_tags);
 	config_del_strbytelist(cfg->local_zone_tags);
 	config_del_strbytelist(cfg->respip_tags);
+	config_deldblstrlist(cfg->respip_actions);
 	config_deldblstrlist(cfg->acl_view);
 	config_del_strbytelist(cfg->acl_tags);
 	config_deltrplstrlist(cfg->acl_tag_actions);
@@ -1761,8 +1794,11 @@ config_delete(struct config_file* cfg)
 	free(cfg->cachedb_secret);
 #ifdef USE_REDIS
 	free(cfg->redis_server_host);
+	free(cfg->redis_replica_server_host);
 	free(cfg->redis_server_path);
+	free(cfg->redis_replica_server_path);
 	free(cfg->redis_server_password);
+	free(cfg->redis_replica_server_password);
 #endif  /* USE_REDIS */
 #endif  /* USE_CACHEDB */
 #ifdef USE_IPSET
@@ -2414,15 +2450,13 @@ config_apply(struct config_file* config)
 	MAX_NEG_TTL = (time_t)config->max_negative_ttl;
 	MIN_NEG_TTL = (time_t)config->min_negative_ttl;
 	RTT_MIN_TIMEOUT = config->infra_cache_min_rtt;
-	RTT_MAX_TIMEOUT = config->infra_cache_max_rtt;
+	RTT_MAX_TIMEOUT = config_apply_max_rtt(config->infra_cache_max_rtt);
 	EDNS_ADVERTISED_SIZE = (uint16_t)config->edns_buffer_size;
 	MINIMAL_RESPONSES = config->minimal_responses;
 	RRSET_ROUNDROBIN = config->rrset_roundrobin;
 	LOG_TAG_QUERYREPLY = config->log_tag_queryreply;
 	MAX_GLOBAL_QUOTA = config->max_global_quota;
 	UNKNOWN_SERVER_NICENESS = config->unknown_server_time_limit;
-	USEFUL_SERVER_TOP_TIMEOUT = RTT_MAX_TIMEOUT;
-	BLACKLIST_PENALTY = USEFUL_SERVER_TOP_TIMEOUT*4;
 	log_set_time_asc(config->log_time_ascii);
 	log_set_time_iso(config->log_time_iso);
 	autr_permit_small_holddown = config->permit_small_holddown;
@@ -2771,78 +2805,101 @@ int options_remote_is_address(struct con
 	return (cfg->control_ifs.first->str[0] != '/');
 }
 
-/** see if interface is https, its port number == the https port number */
 int
-if_is_https(const char* ifname, const char* port, int https_port)
+if_listens_on(const char* ifname, int default_port, int port,
+	struct config_strlist* additional_ports)
 {
+	struct config_strlist* s;
 	char* p = strchr(ifname, '@');
-	if(!p && atoi(port) == https_port)
-		return 1;
-	if(p && atoi(p+1) == https_port)
-		return 1;
+	int if_port;
+	if(p) if_port = atoi(p+1);
+	else  if_port = default_port;
+
+	if(port && if_port == port) return 1;
+
+	for(s = additional_ports; s; s = s->next) {
+		if(if_port == atoi(s->str)) return 1;
+	}
 	return 0;
 }
 
-/** see if config contains https turned on */
-int cfg_has_https(struct config_file* cfg)
+int
+if_is_ssl(const char* ifname, int default_port, int ssl_port,
+	struct config_strlist* tls_additional_port)
 {
-	int i;
-	char portbuf[32];
-	snprintf(portbuf, sizeof(portbuf), "%d", cfg->port);
-	for(i = 0; i<cfg->num_ifs; i++) {
-		if(if_is_https(cfg->ifs[i], portbuf, cfg->https_port))
-			return 1;
-	}
-	return 0;
+	return if_listens_on(ifname, default_port, ssl_port,
+		tls_additional_port);
 }
 
-/** see if interface is PROXYv2, its port number == the proxy port number */
 int
-if_is_pp2(const char* ifname, const char* port,
+if_is_pp2(const char* ifname, int default_port,
 	struct config_strlist* proxy_protocol_port)
 {
-	struct config_strlist* s;
-	char* p = strchr(ifname, '@');
-	for(s = proxy_protocol_port; s; s = s->next) {
-		if(p && atoi(p+1) == atoi(s->str))
-			return 1;
-		if(!p && atoi(port) == atoi(s->str))
-			return 1;
-	}
-	return 0;
+	return if_listens_on(ifname, default_port, 0, proxy_protocol_port);
 }
 
-/** see if interface is DNSCRYPT, its port number == the dnscrypt port number */
 int
-if_is_dnscrypt(const char* ifname, const char* port, int dnscrypt_port)
+if_is_https(const char* ifname, int default_port, int https_port)
+{
+	return if_listens_on(ifname, default_port, https_port, NULL);
+}
+
+int
+if_is_dnscrypt(const char* ifname, int default_port, int dnscrypt_port)
 {
 #ifdef USE_DNSCRYPT
-	return ((strchr(ifname, '@') &&
-		atoi(strchr(ifname, '@')+1) == dnscrypt_port) ||
-		(!strchr(ifname, '@') && atoi(port) == dnscrypt_port));
+	return if_listens_on(ifname, default_port, dnscrypt_port, NULL);
 #else
 	(void)ifname;
-	(void)port;
+	(void)default_port;
 	(void)dnscrypt_port;
 	return 0;
 #endif
 }
 
-/** see if interface is quic, its port number == the quic port number */
+size_t
+getmem_str(char* str)
+{
+	if(!str) return 0;
+	return strlen(str)+1;
+}
+
 int
-if_is_quic(const char* ifname, const char* port, int quic_port)
+if_is_quic(const char* ifname, int default_port, int quic_port)
 {
-#ifndef HAVE_NGTCP2
+#ifdef HAVE_NGTCP2
+	return if_listens_on(ifname, default_port, quic_port, NULL);
+#else
 	(void)ifname;
-	(void)port;
+	(void)default_port;
 	(void)quic_port;
 	return 0;
+#endif
+}
+
+int
+cfg_has_https(struct config_file* cfg)
+{
+	int i;
+	for(i = 0; i<cfg->num_ifs; i++) {
+		if(if_is_https(cfg->ifs[i], cfg->port, cfg->https_port))
+			return 1;
+	}
+	return 0;
+}
+
+int
+cfg_has_quic(struct config_file* cfg)
+{
+#ifdef HAVE_NGTCP2
+	int i;
+	for(i = 0; i<cfg->num_ifs; i++) {
+		if(if_is_quic(cfg->ifs[i], cfg->port, cfg->quic_port))
+			return 1;
+	}
+	return 0;
 #else
-	char* p = strchr(ifname, '@');
-	if(!p && atoi(port) == quic_port)
-		return 1;
-	if(p && atoi(p+1) == quic_port)
-		return 1;
+	(void)cfg;
 	return 0;
 #endif
 }
Index: util/config_file.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/config_file.h,v
diff -u -p -r1.35 config_file.h
--- util/config_file.h	21 Feb 2025 13:20:40 -0000	1.35
+++ util/config_file.h	30 Aug 2025 11:52:23 -0000
@@ -54,6 +54,9 @@ struct sock_list;
 struct ub_packed_rrset_key;
 struct regional;
 
+/** Default value for PROBE_MAXRTO */
+#define PROBE_MAXRTO_DEFAULT 12000
+
 /** List head for strlist processing, used for append operation. */
 struct config_strlist_head {
 	/** first in list of text items */
@@ -435,8 +438,6 @@ struct config_file {
 	/** serve expired entries only after trying to update the entries and this
 	 *  timeout (in milliseconds) is reached */
 	int serve_expired_client_timeout;
-	/** serve EDE code 3 - Stale Answer (RFC8914) for expired entries */
-	int ede_serve_expired;
 	/** serve original TTLs rather than decrementing ones */
 	int serve_original_ttl;
 	/** nsec3 maximum iterations per key size, string */
@@ -738,22 +739,30 @@ struct config_file {
 #ifdef USE_REDIS
 	/** redis server's IP address or host name */
 	char* redis_server_host;
+	char* redis_replica_server_host;
 	/** redis server's TCP port */
 	int redis_server_port;
+	int redis_replica_server_port;
 	/** redis server's unix path. Or "", NULL if unused */
 	char* redis_server_path;
+	char* redis_replica_server_path;
 	/** redis server's AUTH password. Or "", NULL if unused */
 	char* redis_server_password;
+	char* redis_replica_server_password;
 	/** timeout (in ms) for communication with the redis server */
 	int redis_timeout;
+	int redis_replica_timeout;
 	/** timeout (in ms) for redis commands */
 	int redis_command_timeout;
+	int redis_replica_command_timeout;
 	/** timeout (in ms) for redis connection set up */
 	int redis_connect_timeout;
+	int redis_replica_connect_timeout;
 	/** set timeout on redis records based on DNS response ttl */
 	int redis_expire_records;
 	/** set the redis logical database upon connection */
 	int redis_logical_db;
+	int redis_replica_logical_db;
 #endif
 #endif
 	/** Downstream DNS Cookies */
@@ -773,6 +782,10 @@ struct config_file {
 #endif
 	/** respond with Extended DNS Errors (RFC8914) */
 	int ede;
+	/** serve EDE code 3 - Stale Answer (RFC8914) for expired entries */
+	int ede_serve_expired;
+	/** send DNS Error Reports to upstream reporting agent (RFC9567) */
+	int dns_error_reporting;
 	/** limit on NS RRs in RRset for the iterator scrubber. */
 	size_t iter_scrub_ns;
 	/** limit on CNAME, DNAME RRs in answer for the iterator scrubber. */
@@ -976,6 +989,10 @@ void config_delete(struct config_file* c
  */
 void config_apply(struct config_file* config);
 
+/** Apply the relevant changes that rely upon RTT_MAX_TIMEOUT;
+ *  exported for unit test */
+int config_apply_max_rtt(int max_rtt);
+
 /**
  * Find username, sets cfg_uid and cfg_gid.
  * @param config: the config structure.
@@ -1395,8 +1412,39 @@ void w_config_adjust_directory(struct co
 /** debug option for unit tests. */
 extern int fake_dsa, fake_sha1;
 
-/** see if interface is https, its port number == the https port number */
-int if_is_https(const char* ifname, const char* port, int https_port);
+/** Return true if interface will listen to specific port(s).
+ * @param ifname: the interface as configured in the configuration file.
+ * @param default_port: the default port to use as the interface port if ifname
+ *	does not include a port via the '@' notation.
+ * @param port: port to check for, if 0 it will not be checked.
+ * @param additional_ports: additional configured ports, if any (nonNULL) to
+ *	be checked against.
+ * @return true if one of (port, additional_ports) matches the interface port.
+ */
+int if_listens_on(const char* ifname, int default_port, int port,
+	struct config_strlist* additional_ports);
+
+/** see if interface will listen on https;
+ *  its port number == the https port number */
+int if_is_https(const char* ifname, int default_port, int https_port);
+
+/** see if interface will listen on ssl;
+ *  its port number == the ssl port number or any of the additional ports */
+int if_is_ssl(const char* ifname, int default_port, int ssl_port,
+	struct config_strlist* tls_additional_port);
+
+/** see if interface will listen on PROXYv2;
+ *  its port number == any of the proxy ports number */
+int if_is_pp2(const char* ifname, int default_port,
+	struct config_strlist* proxy_protocol_port);
+
+/** see if interface will listen on DNSCRYPT;
+ *  its port number == the dnscrypt port number */
+int if_is_dnscrypt(const char* ifname, int default_port, int dnscrypt_port);
+
+/** see if interface will listen on quic;
+ *  its port number == the quic port number */
+int if_is_quic(const char* ifname, int default_port, int quic_port);
 
 /**
  * Return true if the config contains settings that enable https.
@@ -1405,18 +1453,18 @@ int if_is_https(const char* ifname, cons
  */
 int cfg_has_https(struct config_file* cfg);
 
-/** see if interface is PROXYv2, its port number == the proxy port number */
-int if_is_pp2(const char* ifname, const char* port,
-	struct config_strlist* proxy_protocol_port);
-
-/** see if interface is DNSCRYPT, its port number == the dnscrypt port number */
-int if_is_dnscrypt(const char* ifname, const char* port, int dnscrypt_port);
-
-/** see if interface is quic, its port number == the quic port number */
-int if_is_quic(const char* ifname, const char* port, int quic_port);
+/**
+ * Return true if the config contains settings that enable quic.
+ * @param cfg: config information.
+ * @return true if quic ports are used for server.
+ */
+int cfg_has_quic(struct config_file* cfg);
 
 #ifdef USE_LINUX_IP_LOCAL_PORT_RANGE
 #define LINUX_IP_LOCAL_PORT_RANGE_PATH "/proc/sys/net/ipv4/ip_local_port_range"
 #endif
+
+/** get memory for string */
+size_t getmem_str(char* str);
 
 #endif /* UTIL_CONFIG_FILE_H */
Index: util/configlexer.lex
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/configlexer.lex,v
diff -u -p -r1.32 configlexer.lex
--- util/configlexer.lex	21 Feb 2025 13:20:40 -0000	1.32
+++ util/configlexer.lex	30 Aug 2025 11:52:23 -0000
@@ -471,8 +471,8 @@ unknown-server-time-limit{COLON} { YDVAR
 discard-timeout{COLON}		{ YDVAR(1, VAR_DISCARD_TIMEOUT) }
 wait-limit{COLON}		{ YDVAR(1, VAR_WAIT_LIMIT) }
 wait-limit-cookie{COLON}	{ YDVAR(1, VAR_WAIT_LIMIT_COOKIE) }
-wait-limit-netblock{COLON}	{ YDVAR(1, VAR_WAIT_LIMIT_NETBLOCK) }
-wait-limit-cookie-netblock{COLON} { YDVAR(1, VAR_WAIT_LIMIT_COOKIE_NETBLOCK) }
+wait-limit-netblock{COLON}	{ YDVAR(2, VAR_WAIT_LIMIT_NETBLOCK) }
+wait-limit-cookie-netblock{COLON} { YDVAR(2, VAR_WAIT_LIMIT_COOKIE_NETBLOCK) }
 max-udp-size{COLON}		{ YDVAR(1, VAR_MAX_UDP_SIZE) }
 dns64-prefix{COLON}		{ YDVAR(1, VAR_DNS64_PREFIX) }
 dns64-synthall{COLON}		{ YDVAR(1, VAR_DNS64_SYNTHALL) }
@@ -572,15 +572,23 @@ backend{COLON}			{ YDVAR(1, VAR_CACHEDB_
 secret-seed{COLON}		{ YDVAR(1, VAR_CACHEDB_SECRETSEED) }
 cachedb-no-store{COLON}		{ YDVAR(1, VAR_CACHEDB_NO_STORE) }
 cachedb-check-when-serve-expired{COLON}		{ YDVAR(1, VAR_CACHEDB_CHECK_WHEN_SERVE_EXPIRED) }
-redis-server-host{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISHOST) }
-redis-server-port{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISPORT) }
-redis-server-path{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISPATH) }
-redis-server-password{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISPASSWORD) }
-redis-timeout{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISTIMEOUT) }
+redis-server-host{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISHOST) }
+redis-replica-server-host{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISREPLICAHOST) }
+redis-server-port{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISPORT) }
+redis-replica-server-port{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISREPLICAPORT) }
+redis-server-path{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISPATH) }
+redis-replica-server-path{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISREPLICAPATH) }
+redis-server-password{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISPASSWORD) }
+redis-replica-server-password{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISREPLICAPASSWORD) }
+redis-timeout{COLON}			{ YDVAR(1, VAR_CACHEDB_REDISTIMEOUT) }
+redis-replica-timeout{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISREPLICATIMEOUT) }
 redis-command-timeout{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISCOMMANDTIMEOUT) }
+redis-replica-command-timeout{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISREPLICACOMMANDTIMEOUT) }
 redis-connect-timeout{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISCONNECTTIMEOUT) }
+redis-replica-connect-timeout{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISREPLICACONNECTTIMEOUT) }
 redis-expire-records{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISEXPIRERECORDS) }
-redis-logical-db{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISLOGICALDB) }
+redis-logical-db{COLON}			{ YDVAR(1, VAR_CACHEDB_REDISLOGICALDB) }
+redis-replica-logical-db{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISREPLICALOGICALDB) }
 ipset{COLON}			{ YDVAR(0, VAR_IPSET) }
 name-v4{COLON}			{ YDVAR(1, VAR_IPSET_NAME_V4) }
 name-v6{COLON}			{ YDVAR(1, VAR_IPSET_NAME_V6) }
@@ -593,6 +601,7 @@ edns-client-string{COLON}	{ YDVAR(2, VAR
 edns-client-string-opcode{COLON} { YDVAR(1, VAR_EDNS_CLIENT_STRING_OPCODE) }
 nsid{COLON}			{ YDVAR(1, VAR_NSID ) }
 ede{COLON}			{ YDVAR(1, VAR_EDE ) }
+dns-error-reporting{COLON}	{ YDVAR(1, VAR_DNS_ERROR_REPORTING ) }
 proxy-protocol-port{COLON}	{ YDVAR(1, VAR_PROXY_PROTOCOL_PORT) }
 iter-scrub-ns{COLON}		{ YDVAR(1, VAR_ITER_SCRUB_NS) }
 iter-scrub-cname{COLON}		{ YDVAR(1, VAR_ITER_SCRUB_CNAME) }
Index: util/configparser.y
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/configparser.y,v
diff -u -p -r1.33 configparser.y
--- util/configparser.y	21 Feb 2025 13:20:40 -0000	1.33
+++ util/configparser.y	30 Aug 2025 11:52:23 -0000
@@ -179,10 +179,15 @@ extern struct config_parser_state* cfg_p
 %token VAR_IPSECMOD_ENABLED VAR_IPSECMOD_HOOK VAR_IPSECMOD_IGNORE_BOGUS
 %token VAR_IPSECMOD_MAX_TTL VAR_IPSECMOD_WHITELIST VAR_IPSECMOD_STRICT
 %token VAR_CACHEDB VAR_CACHEDB_BACKEND VAR_CACHEDB_SECRETSEED
-%token VAR_CACHEDB_REDISHOST VAR_CACHEDB_REDISPORT VAR_CACHEDB_REDISTIMEOUT
-%token VAR_CACHEDB_REDISEXPIRERECORDS VAR_CACHEDB_REDISPATH VAR_CACHEDB_REDISPASSWORD
-%token VAR_CACHEDB_REDISLOGICALDB
-%token VAR_CACHEDB_REDISCOMMANDTIMEOUT VAR_CACHEDB_REDISCONNECTTIMEOUT
+%token VAR_CACHEDB_REDISHOST VAR_CACHEDB_REDISREPLICAHOST
+%token VAR_CACHEDB_REDISPORT VAR_CACHEDB_REDISREPLICAPORT
+%token VAR_CACHEDB_REDISTIMEOUT VAR_CACHEDB_REDISREPLICATIMEOUT
+%token VAR_CACHEDB_REDISEXPIRERECORDS
+%token VAR_CACHEDB_REDISPATH VAR_CACHEDB_REDISREPLICAPATH
+%token VAR_CACHEDB_REDISPASSWORD VAR_CACHEDB_REDISREPLICAPASSWORD
+%token VAR_CACHEDB_REDISLOGICALDB VAR_CACHEDB_REDISREPLICALOGICALDB
+%token VAR_CACHEDB_REDISCOMMANDTIMEOUT VAR_CACHEDB_REDISREPLICACOMMANDTIMEOUT
+%token VAR_CACHEDB_REDISCONNECTTIMEOUT VAR_CACHEDB_REDISREPLICACONNECTTIMEOUT
 %token VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM VAR_FOR_UPSTREAM
 %token VAR_AUTH_ZONE VAR_ZONEFILE VAR_MASTER VAR_URL VAR_FOR_DOWNSTREAM
 %token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL
@@ -201,6 +206,7 @@ extern struct config_parser_state* cfg_p
 %token VAR_EDNS_CLIENT_STRING_OPCODE VAR_NSID
 %token VAR_ZONEMD_PERMISSIVE_MODE VAR_ZONEMD_CHECK VAR_ZONEMD_REJECT_ABSENCE
 %token VAR_RPZ_SIGNAL_NXDOMAIN_RA VAR_INTERFACE_AUTOMATIC_PORTS VAR_EDE
+%token VAR_DNS_ERROR_REPORTING
 %token VAR_INTERFACE_ACTION VAR_INTERFACE_VIEW VAR_INTERFACE_TAG
 %token VAR_INTERFACE_TAG_ACTION VAR_INTERFACE_TAG_DATA
 %token VAR_QUIC_PORT VAR_QUIC_SIZE
@@ -345,6 +351,7 @@ content_server: server_num_threads | ser
 	server_tcp_reuse_timeout | server_tcp_auth_query_timeout |
 	server_quic_port | server_quic_size |
 	server_interface_automatic_ports | server_ede |
+	server_dns_error_reporting |
 	server_proxy_protocol_port | server_statistics_inhibit_zero |
 	server_harden_unknown_additional | server_disable_edns_do |
 	server_log_destaddr | server_cookie_secret_file |
@@ -3068,6 +3075,15 @@ server_ede: VAR_EDE STRING_ARG
 		free($2);
 	}
 	;
+server_dns_error_reporting: VAR_DNS_ERROR_REPORTING STRING_ARG
+	{
+		OUTYY(("P(server_dns_error_reporting:%s)\n", $2));
+		if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+			yyerror("expected yes or no.");
+		else cfg_parser->cfg->dns_error_reporting = (strcmp($2, "yes")==0);
+		free($2);
+	}
+	;
 server_proxy_protocol_port: VAR_PROXY_PROTOCOL_PORT STRING_ARG
 	{
 		OUTYY(("P(server_proxy_protocol_port:%s)\n", $2));
@@ -3868,10 +3884,16 @@ cachedbstart: VAR_CACHEDB
 contents_cachedb: contents_cachedb content_cachedb
 	| ;
 content_cachedb: cachedb_backend_name | cachedb_secret_seed |
-	redis_server_host | redis_server_port | redis_timeout |
-	redis_expire_records | redis_server_path | redis_server_password |
-	cachedb_no_store | redis_logical_db | cachedb_check_when_serve_expired |
-	redis_command_timeout | redis_connect_timeout
+	redis_server_host | redis_replica_server_host |
+	redis_server_port | redis_replica_server_port |
+	redis_timeout | redis_replica_timeout |
+	redis_command_timeout | redis_replica_command_timeout |
+	redis_connect_timeout | redis_replica_connect_timeout |
+	redis_server_path | redis_replica_server_path |
+	redis_server_password | redis_replica_server_password |
+	redis_logical_db | redis_replica_logical_db |
+	cachedb_no_store | redis_expire_records |
+	cachedb_check_when_serve_expired
 	;
 cachedb_backend_name: VAR_CACHEDB_BACKEND STRING_ARG
 	{
@@ -3935,6 +3957,18 @@ redis_server_host: VAR_CACHEDB_REDISHOST
 	#endif
 	}
 	;
+redis_replica_server_host: VAR_CACHEDB_REDISREPLICAHOST STRING_ARG
+	{
+	#if defined(USE_CACHEDB) && defined(USE_REDIS)
+		OUTYY(("P(redis_replica_server_host:%s)\n", $2));
+		free(cfg_parser->cfg->redis_replica_server_host);
+		cfg_parser->cfg->redis_replica_server_host = $2;
+	#else
+		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
+		free($2);
+	#endif
+	}
+	;
 redis_server_port: VAR_CACHEDB_REDISPORT STRING_ARG
 	{
 	#if defined(USE_CACHEDB) && defined(USE_REDIS)
@@ -3950,6 +3984,21 @@ redis_server_port: VAR_CACHEDB_REDISPORT
 		free($2);
 	}
 	;
+redis_replica_server_port: VAR_CACHEDB_REDISREPLICAPORT STRING_ARG
+	{
+	#if defined(USE_CACHEDB) && defined(USE_REDIS)
+		int port;
+		OUTYY(("P(redis_replica_server_port:%s)\n", $2));
+		port = atoi($2);
+		if(port == 0 || port < 0 || port > 65535)
+			yyerror("valid redis server port number expected");
+		else cfg_parser->cfg->redis_replica_server_port = port;
+	#else
+		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
+	#endif
+		free($2);
+	}
+	;
 redis_server_path: VAR_CACHEDB_REDISPATH STRING_ARG
 	{
 	#if defined(USE_CACHEDB) && defined(USE_REDIS)
@@ -3962,6 +4011,18 @@ redis_server_path: VAR_CACHEDB_REDISPATH
 	#endif
 	}
 	;
+redis_replica_server_path: VAR_CACHEDB_REDISREPLICAPATH STRING_ARG
+	{
+	#if defined(USE_CACHEDB) && defined(USE_REDIS)
+		OUTYY(("P(redis_replica_server_path:%s)\n", $2));
+		free(cfg_parser->cfg->redis_replica_server_path);
+		cfg_parser->cfg->redis_replica_server_path = $2;
+	#else
+		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
+		free($2);
+	#endif
+	}
+	;
 redis_server_password: VAR_CACHEDB_REDISPASSWORD STRING_ARG
 	{
 	#if defined(USE_CACHEDB) && defined(USE_REDIS)
@@ -3974,6 +4035,18 @@ redis_server_password: VAR_CACHEDB_REDIS
 	#endif
 	}
 	;
+redis_replica_server_password: VAR_CACHEDB_REDISREPLICAPASSWORD STRING_ARG
+	{
+	#if defined(USE_CACHEDB) && defined(USE_REDIS)
+		OUTYY(("P(redis_replica_server_password:%s)\n", $2));
+		free(cfg_parser->cfg->redis_replica_server_password);
+		cfg_parser->cfg->redis_replica_server_password = $2;
+	#else
+		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
+		free($2);
+	#endif
+	}
+	;
 redis_timeout: VAR_CACHEDB_REDISTIMEOUT STRING_ARG
 	{
 	#if defined(USE_CACHEDB) && defined(USE_REDIS)
@@ -3987,6 +4060,19 @@ redis_timeout: VAR_CACHEDB_REDISTIMEOUT 
 		free($2);
 	}
 	;
+redis_replica_timeout: VAR_CACHEDB_REDISREPLICATIMEOUT STRING_ARG
+	{
+	#if defined(USE_CACHEDB) && defined(USE_REDIS)
+		OUTYY(("P(redis_replica_timeout:%s)\n", $2));
+		if(atoi($2) == 0)
+			yyerror("redis timeout value expected");
+		else cfg_parser->cfg->redis_replica_timeout = atoi($2);
+	#else
+		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
+	#endif
+		free($2);
+	}
+	;
 redis_command_timeout: VAR_CACHEDB_REDISCOMMANDTIMEOUT STRING_ARG
 	{
 	#if defined(USE_CACHEDB) && defined(USE_REDIS)
@@ -4000,6 +4086,19 @@ redis_command_timeout: VAR_CACHEDB_REDIS
 		free($2);
 	}
 	;
+redis_replica_command_timeout: VAR_CACHEDB_REDISREPLICACOMMANDTIMEOUT STRING_ARG
+	{
+	#if defined(USE_CACHEDB) && defined(USE_REDIS)
+		OUTYY(("P(redis_replica_command_timeout:%s)\n", $2));
+		if(atoi($2) == 0 && strcmp($2, "0") != 0)
+			yyerror("redis command timeout value expected");
+		else cfg_parser->cfg->redis_replica_command_timeout = atoi($2);
+	#else
+		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
+	#endif
+		free($2);
+	}
+	;
 redis_connect_timeout: VAR_CACHEDB_REDISCONNECTTIMEOUT STRING_ARG
 	{
 	#if defined(USE_CACHEDB) && defined(USE_REDIS)
@@ -4013,6 +4112,19 @@ redis_connect_timeout: VAR_CACHEDB_REDIS
 		free($2);
 	}
 	;
+redis_replica_connect_timeout: VAR_CACHEDB_REDISREPLICACONNECTTIMEOUT STRING_ARG
+	{
+	#if defined(USE_CACHEDB) && defined(USE_REDIS)
+		OUTYY(("P(redis_replica_connect_timeout:%s)\n", $2));
+		if(atoi($2) == 0 && strcmp($2, "0") != 0)
+			yyerror("redis connect timeout value expected");
+		else cfg_parser->cfg->redis_replica_connect_timeout = atoi($2);
+	#else
+		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
+	#endif
+		free($2);
+	}
+	;
 redis_expire_records: VAR_CACHEDB_REDISEXPIRERECORDS STRING_ARG
 	{
 	#if defined(USE_CACHEDB) && defined(USE_REDIS)
@@ -4035,6 +4147,21 @@ redis_logical_db: VAR_CACHEDB_REDISLOGIC
 		if((db == 0 && strcmp($2, "0") != 0) || db < 0)
 			yyerror("valid redis logical database index expected");
 		else cfg_parser->cfg->redis_logical_db = db;
+	#else
+		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
+	#endif
+		free($2);
+	}
+	;
+redis_replica_logical_db: VAR_CACHEDB_REDISREPLICALOGICALDB STRING_ARG
+	{
+	#if defined(USE_CACHEDB) && defined(USE_REDIS)
+		int db;
+		OUTYY(("P(redis_replica_logical_db:%s)\n", $2));
+		db = atoi($2);
+		if((db == 0 && strcmp($2, "0") != 0) || db < 0)
+			yyerror("valid redis logical database index expected");
+		else cfg_parser->cfg->redis_replica_logical_db = db;
 	#else
 		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
 	#endif
Index: util/edns.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/edns.c,v
diff -u -p -r1.1.1.8 edns.c
--- util/edns.c	4 Sep 2024 09:35:36 -0000	1.1.1.8
+++ util/edns.c	30 Aug 2025 11:52:23 -0000
@@ -131,6 +131,29 @@ edns_string_addr_lookup(rbtree_type* tre
 	return (struct edns_string_addr*)addr_tree_lookup(tree, addr, addrlen);
 }
 
+size_t
+edns_strings_get_mem(struct edns_strings* edns_strings)
+{
+	if(!edns_strings) return 0;
+	return regional_get_mem(edns_strings->region) + sizeof(*edns_strings);
+}
+
+void
+edns_strings_swap_tree(struct edns_strings* edns_strings,
+	struct edns_strings* data)
+{
+	rbtree_type tree = edns_strings->client_strings;
+	uint16_t opcode = edns_strings->client_string_opcode;
+	struct regional* region = edns_strings->region;
+
+	edns_strings->client_strings = data->client_strings;
+	edns_strings->client_string_opcode = data->client_string_opcode;
+	edns_strings->region = data->region;
+	data->client_strings = tree;
+	data->client_string_opcode = opcode;
+	data->region = region;
+}
+
 uint8_t*
 edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret, int v4,
 	uint8_t* hash)
Index: util/edns.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/edns.h,v
diff -u -p -r1.1.1.6 edns.h
--- util/edns.h	4 Sep 2024 09:35:36 -0000	1.1.1.6
+++ util/edns.h	30 Aug 2025 11:52:23 -0000
@@ -142,6 +142,22 @@ edns_string_addr_lookup(rbtree_type* tre
 	socklen_t addrlen);
 
 /**
+ * Get memory usage of edns strings.
+ * @param edns_strings: the edns strings
+ * @return memory usage
+ */
+size_t edns_strings_get_mem(struct edns_strings* edns_strings);
+
+/**
+ * Swap internal tree with preallocated entries.
+ * @param edns_strings: the edns strings structure.
+ * @param data: the data structure used to take elements from. This contains
+ * 	the old elements on return.
+ */
+void edns_strings_swap_tree(struct edns_strings* edns_strings,
+	struct edns_strings* data);
+
+/**
  * Compute the interoperable DNS cookie (RFC9018) hash.
  * @param in: buffer input for the hash generation. It needs to be:
  *	Client Cookie | Version | Reserved | Timestamp | Client-IP
Index: util/fptr_wlist.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/fptr_wlist.c,v
diff -u -p -r1.28 fptr_wlist.c
--- util/fptr_wlist.c	21 Feb 2025 13:20:40 -0000	1.28
+++ util/fptr_wlist.c	30 Aug 2025 11:52:23 -0000
@@ -74,6 +74,7 @@
 #include "libunbound/worker.h"
 #include "util/tube.h"
 #include "util/config_file.h"
+#include "daemon/remote.h"
 #ifdef UB_ON_WINDOWS
 #include "winrc/win_svc.h"
 #endif
@@ -121,6 +122,7 @@ fptr_whitelist_comm_point_raw(comm_point
 	else if(fptr == &tube_handle_write) return 1;
 	else if(fptr == &remote_accept_callback) return 1;
 	else if(fptr == &remote_control_callback) return 1;
+	else if(fptr == &fast_reload_client_callback) return 1;
 	return 0;
 }
 
@@ -188,6 +190,7 @@ fptr_whitelist_event(void (*fptr)(int, s
 #ifdef HAVE_NGTCP2
 	else if(fptr == &comm_point_doq_callback) return 1;
 #endif
+	else if(fptr == &fast_reload_service_cb) return 1;
 #ifdef USE_DNSTAP
 	else if(fptr == &dtio_output_cb) return 1;
 	else if(fptr == &dtio_cmd_cb) return 1;
Index: util/iana_ports.inc
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/iana_ports.inc,v
diff -u -p -r1.28 iana_ports.inc
--- util/iana_ports.inc	13 Apr 2024 12:24:57 -0000	1.28
+++ util/iana_ports.inc	30 Aug 2025 11:52:23 -0000
@@ -1,6 +1,4 @@
 1,
-2,
-3,
 5,
 7,
 9,
@@ -2426,6 +2424,7 @@
 2791,
 2792,
 2793,
+2794,
 2795,
 2796,
 2797,
@@ -4358,6 +4357,7 @@
 5911,
 5912,
 5913,
+5914,
 5963,
 5968,
 5969,
@@ -5477,6 +5477,7 @@
 44900,
 45000,
 45054,
+45185,
 45514,
 45678,
 45825,
Index: util/log.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/log.c,v
diff -u -p -r1.10 log.c
--- util/log.c	21 Feb 2025 13:20:40 -0000	1.10
+++ util/log.c	30 Aug 2025 11:52:23 -0000
@@ -281,7 +281,7 @@ log_vmsg(int pri, const char* type,
 	if(log_time_iso && log_time_asc) {
 		char tzbuf[16];
 		struct timeval tv;
-		struct tm tm, *tm_p;
+		struct tm *tm_p;
 		if(gettimeofday(&tv, NULL) < 0)
 			memset(&tv, 0, sizeof(tv));
 		now = (time_t)tv.tv_sec;
Index: util/module.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/module.c,v
diff -u -p -r1.10 module.c
--- util/module.c	13 Jun 2024 14:30:28 -0000	1.10
+++ util/module.c	30 Aug 2025 11:52:23 -0000
@@ -135,7 +135,7 @@ char* errinf_to_str_bogus(struct module_
 	char* p = buf;
 	size_t left = sizeof(buf);
 	struct errinf_strlist* s;
-	char dname[LDNS_MAX_DOMAINLEN+1];
+	char dname[LDNS_MAX_DOMAINLEN];
 	char t[16], c[16];
 	sldns_wire2str_type_buf(qstate->qinfo.qtype, t, sizeof(t));
 	sldns_wire2str_class_buf(qstate->qinfo.qclass, c, sizeof(c));
@@ -178,7 +178,7 @@ char* errinf_to_str_servfail(struct modu
 	char* p = buf;
 	size_t left = sizeof(buf);
 	struct errinf_strlist* s;
-	char dname[LDNS_MAX_DOMAINLEN+1];
+	char dname[LDNS_MAX_DOMAINLEN];
 	char t[16], c[16];
 	sldns_wire2str_type_buf(qstate->qinfo.qtype, t, sizeof(t));
 	sldns_wire2str_class_buf(qstate->qinfo.qclass, c, sizeof(c));
@@ -218,7 +218,7 @@ char* errinf_to_str_misc(struct module_q
 void errinf_rrset(struct module_qstate* qstate, struct ub_packed_rrset_key *rr)
 {
 	char buf[1024];
-	char dname[LDNS_MAX_DOMAINLEN+1];
+	char dname[LDNS_MAX_DOMAINLEN];
 	char t[16], c[16];
 	if((qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail) || !rr)
 		return;
@@ -232,7 +232,7 @@ void errinf_rrset(struct module_qstate* 
 void errinf_dname(struct module_qstate* qstate, const char* str, uint8_t* dname)
 {
 	char b[1024];
-	char buf[LDNS_MAX_DOMAINLEN+1];
+	char buf[LDNS_MAX_DOMAINLEN];
 	if((qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail) || !str || !dname)
 		return;
 	dname_str(dname, buf);
Index: util/module.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/module.h,v
diff -u -p -r1.20 module.h
--- util/module.h	21 Feb 2025 13:20:40 -0000	1.20
+++ util/module.h	30 Aug 2025 11:52:23 -0000
@@ -177,6 +177,7 @@ struct val_anchors;
 struct val_neg_cache;
 struct iter_forwards;
 struct iter_hints;
+struct views;
 struct respip_set;
 struct respip_client_info;
 struct respip_addr_info;
@@ -524,6 +525,10 @@ struct module_env {
 	 * data structure. 
 	 */
 	struct iter_hints* hints;
+	/** views structure containing view tree */
+	struct views* views;
+	/** response-ip set with associated actions and tags. */
+	struct respip_set* respip_set;
 	/** module specific data. indexed by module id. */
 	void* modinfo[MAX_MODULE];
 
Index: util/net_help.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/net_help.c,v
diff -u -p -r1.33 net_help.c
--- util/net_help.c	4 Sep 2024 09:36:41 -0000	1.33
+++ util/net_help.c	30 Aug 2025 11:52:23 -0000
@@ -92,11 +92,13 @@ int RRSET_ROUNDROBIN = 1;
 /** log tag queries with name instead of 'info' for filtering */
 int LOG_TAG_QUERYREPLY = 0;
 
+#ifdef HAVE_SSL
 static struct tls_session_ticket_key {
 	unsigned char *key_name;
 	unsigned char *aes_key;
 	unsigned char *hmac_key;
 } *ticket_keys;
+#endif /* HAVE_SSL */
 
 #ifdef HAVE_SSL
 /**
@@ -544,7 +546,7 @@ void
 log_nametypeclass(enum verbosity_value v, const char* str, uint8_t* name, 
 	uint16_t type, uint16_t dclass)
 {
-	char buf[LDNS_MAX_DOMAINLEN+1];
+	char buf[LDNS_MAX_DOMAINLEN];
 	char t[12], c[12];
 	const char *ts, *cs; 
 	if(verbosity < v)
@@ -575,7 +577,7 @@ log_nametypeclass(enum verbosity_value v
 void
 log_query_in(const char* str, uint8_t* name, uint16_t type, uint16_t dclass)
 {
-	char buf[LDNS_MAX_DOMAINLEN+1];
+	char buf[LDNS_MAX_DOMAINLEN];
 	char t[12], c[12];
 	const char *ts, *cs; 
 	dname_str(name, buf);
@@ -608,7 +610,7 @@ void log_name_addr(enum verbosity_value 
 {
 	uint16_t port;
 	const char* family = "unknown_family ";
-	char namebuf[LDNS_MAX_DOMAINLEN+1];
+	char namebuf[LDNS_MAX_DOMAINLEN];
 	char dest[100];
 	int af = (int)((struct sockaddr_in*)addr)->sin_family;
 	void* sinaddr = &((struct sockaddr_in*)addr)->sin_addr;
@@ -728,6 +730,52 @@ sockaddr_cmp_addr(struct sockaddr_storag
 }
 
 int
+sockaddr_cmp_scopeid(struct sockaddr_storage* addr1, socklen_t len1,
+	struct sockaddr_storage* addr2, socklen_t len2)
+{
+	struct sockaddr_in* p1_in = (struct sockaddr_in*)addr1;
+	struct sockaddr_in* p2_in = (struct sockaddr_in*)addr2;
+	struct sockaddr_in6* p1_in6 = (struct sockaddr_in6*)addr1;
+	struct sockaddr_in6* p2_in6 = (struct sockaddr_in6*)addr2;
+	if(len1 < len2)
+		return -1;
+	if(len1 > len2)
+		return 1;
+	log_assert(len1 == len2);
+	if( p1_in->sin_family < p2_in->sin_family)
+		return -1;
+	if( p1_in->sin_family > p2_in->sin_family)
+		return 1;
+	log_assert( p1_in->sin_family == p2_in->sin_family );
+	/* compare ip4 */
+	if( p1_in->sin_family == AF_INET ) {
+		/* just order it, ntohs not required */
+		if(p1_in->sin_port < p2_in->sin_port)
+			return -1;
+		if(p1_in->sin_port > p2_in->sin_port)
+			return 1;
+		log_assert(p1_in->sin_port == p2_in->sin_port);
+		return memcmp(&p1_in->sin_addr, &p2_in->sin_addr, INET_SIZE);
+	} else if (p1_in6->sin6_family == AF_INET6) {
+		/* just order it, ntohs not required */
+		if(p1_in6->sin6_port < p2_in6->sin6_port)
+			return -1;
+		if(p1_in6->sin6_port > p2_in6->sin6_port)
+			return 1;
+		if(p1_in6->sin6_scope_id < p2_in6->sin6_scope_id)
+			return -1;
+		if(p1_in6->sin6_scope_id > p2_in6->sin6_scope_id)
+			return 1;
+		log_assert(p1_in6->sin6_port == p2_in6->sin6_port);
+		return memcmp(&p1_in6->sin6_addr, &p2_in6->sin6_addr,
+			INET6_SIZE);
+	} else {
+		/* eek unknown type, perform this comparison for sanity. */
+		return memcmp(addr1, addr2, len1);
+	}
+}
+
+int
 addr_is_ip6(struct sockaddr_storage* addr, socklen_t len)
 {
 	if(len == (socklen_t)sizeof(struct sockaddr_in6) &&
@@ -1116,8 +1164,29 @@ log_cert(unsigned level, const char* str
 }
 #endif /* HAVE_SSL */
 
+#if defined(HAVE_SSL) && defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB)
+static int
+dot_alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out,
+	unsigned char* outlen, const unsigned char* in, unsigned int inlen,
+	void* ATTR_UNUSED(arg))
+{
+	static const unsigned char alpns[] = { 3, 'd', 'o', 't' };
+	unsigned char* tmp_out;
+	int ret;
+	ret = SSL_select_next_proto(&tmp_out, outlen, alpns, sizeof(alpns), in, inlen);
+	if(ret == OPENSSL_NPN_NO_OVERLAP) {
+		/* Client sent ALPN but no overlap. Should have been error,
+		 * but for privacy we continue without ALPN (e.g., if certain
+		 * ALPNs are blocked) */
+		return SSL_TLSEXT_ERR_NOACK;
+	}
+	*out = tmp_out;
+	return SSL_TLSEXT_ERR_OK;
+}
+#endif
+
 #if defined(HAVE_SSL) && defined(HAVE_NGHTTP2) && defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB)
-static int alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out,
+static int doh_alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out,
 	unsigned char* outlen, const unsigned char* in, unsigned int inlen,
 	void* ATTR_UNUSED(arg))
 {
@@ -1131,6 +1200,24 @@ static int alpn_select_cb(SSL* ATTR_UNUS
 }
 #endif
 
+#ifdef HAVE_SSL
+/* setup the callback for ticket keys */
+static int
+setup_ticket_keys_cb(void* sslctx)
+{
+#  ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
+	if(SSL_CTX_set_tlsext_ticket_key_evp_cb(sslctx, tls_session_ticket_key_cb) == 0) {
+		return 0;
+	}
+#  else
+	if(SSL_CTX_set_tlsext_ticket_key_cb(sslctx, tls_session_ticket_key_cb) == 0) {
+		return 0;
+	}
+#  endif
+	return 1;
+}
+#endif /* HAVE_SSL */
+
 int
 listen_sslctx_setup(void* ctxt)
 {
@@ -1202,9 +1289,6 @@ listen_sslctx_setup(void* ctxt)
 #ifdef HAVE_SSL_CTX_SET_SECURITY_LEVEL
 	SSL_CTX_set_security_level(ctx, 0);
 #endif
-#if defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB) && defined(HAVE_NGHTTP2)
-	SSL_CTX_set_alpn_select_cb(ctx, alpn_select_cb, NULL);
-#endif
 #else
 	(void)ctxt;
 #endif /* HAVE_SSL */
@@ -1239,7 +1323,10 @@ listen_sslctx_setup_2(void* ctxt)
 #endif /* HAVE_SSL */
 }
 
-void* listen_sslctx_create(char* key, char* pem, char* verifypem)
+void* listen_sslctx_create(const char* key, const char* pem,
+	const char* verifypem, const char* tls_ciphers,
+	const char* tls_ciphersuites, int set_ticket_keys_cb,
+	int is_dot, int is_doh)
 {
 #ifdef HAVE_SSL
 	SSL_CTX* ctx = SSL_CTX_new(SSLv23_server_method());
@@ -1290,11 +1377,52 @@ void* listen_sslctx_create(char* key, ch
 			verifypem));
 		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
 	}
+	if(tls_ciphers && tls_ciphers[0]) {
+		if (!SSL_CTX_set_cipher_list(ctx, tls_ciphers)) {
+			log_err("failed to set tls-cipher %s",
+				tls_ciphers);
+			log_crypto_err("Error in SSL_CTX_set_cipher_list");
+			SSL_CTX_free(ctx);
+			return NULL;
+		}
+	}
+#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
+	if(tls_ciphersuites && tls_ciphersuites[0]) {
+		if (!SSL_CTX_set_ciphersuites(ctx, tls_ciphersuites)) {
+			log_err("failed to set tls-ciphersuites %s",
+				tls_ciphersuites);
+			log_crypto_err("Error in SSL_CTX_set_ciphersuites");
+			SSL_CTX_free(ctx);
+			return NULL;
+		}
+	}
+#else
+	(void)tls_ciphersuites; /* variable unused. */
+#endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */
+	if(set_ticket_keys_cb) {
+		if(!setup_ticket_keys_cb(ctx)) {
+			log_crypto_err("no support for TLS session ticket");
+			SSL_CTX_free(ctx);
+			return NULL;
+		}
+	}
+	/* setup ALPN */
+#if defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB)
+	if(is_dot) {
+		SSL_CTX_set_alpn_select_cb(ctx, dot_alpn_select_cb, NULL);
+	} else if(is_doh) {
+#if defined(HAVE_NGHTTP2)
+		SSL_CTX_set_alpn_select_cb(ctx, doh_alpn_select_cb, NULL);
+#endif
+	}
+#endif /* HAVE_SSL_CTX_SET_ALPN_SELECT_CB */
 	return ctx;
 #else
 	(void)key; (void)pem; (void)verifypem;
+	(void)tls_ciphers; (void)tls_ciphersuites;
+	(void)set_ticket_keys_cb; (void)is_dot; (void)is_doh;
 	return NULL;
-#endif
+#endif /* HAVE_SSL */
 }
 
 #ifdef USE_WINSOCK
@@ -1654,7 +1782,7 @@ void ub_openssl_lock_delete(void)
 #endif /* OPENSSL_THREADS */
 }
 
-int listen_sslctx_setup_ticket_keys(void* sslctx, struct config_strlist* tls_session_ticket_keys) {
+int listen_sslctx_setup_ticket_keys(struct config_strlist* tls_session_ticket_keys) {
 #ifdef HAVE_SSL
 	size_t s = 1;
 	struct config_strlist* p;
@@ -1700,24 +1828,11 @@ int listen_sslctx_setup_ticket_keys(void
 	}
 	/* terminate array with NULL key name entry */
 	keys->key_name = NULL;
-#  ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
-	if(SSL_CTX_set_tlsext_ticket_key_evp_cb(sslctx, tls_session_ticket_key_cb) == 0) {
-		log_err("no support for TLS session ticket");
-		return 0;
-	}
-#  else
-	if(SSL_CTX_set_tlsext_ticket_key_cb(sslctx, tls_session_ticket_key_cb) == 0) {
-		log_err("no support for TLS session ticket");
-		return 0;
-	}
-#  endif
 	return 1;
 #else
-	(void)sslctx;
 	(void)tls_session_ticket_keys;
 	return 0;
 #endif
-
 }
 
 #ifdef HAVE_SSL
@@ -1828,6 +1943,7 @@ int tls_session_ticket_key_cb(SSL *ATTR_
 }
 #endif /* HAVE_SSL */
 
+#ifdef HAVE_SSL
 void
 listen_sslctx_delete_ticket_keys(void)
 {
@@ -1845,6 +1961,7 @@ listen_sslctx_delete_ticket_keys(void)
 	free(ticket_keys);
 	ticket_keys = NULL;
 }
+#endif /* HAVE_SSL */
 
 #  ifndef USE_WINSOCK
 char*
Index: util/net_help.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/net_help.h,v
diff -u -p -r1.15 net_help.h
--- util/net_help.h	4 Sep 2024 09:36:41 -0000	1.15
+++ util/net_help.h	30 Aug 2025 11:52:23 -0000
@@ -290,6 +290,18 @@ int sockaddr_cmp_addr(struct sockaddr_st
 	struct sockaddr_storage* addr2, socklen_t len2);
 
 /**
+ * Compare two sockaddrs. Imposes an ordering on the addresses.
+ * Compares address and port. It also compares scope_id for ip6.
+ * @param addr1: address 1.
+ * @param len1: lengths of addr1.
+ * @param addr2: address 2.
+ * @param len2: lengths of addr2.
+ * @return: 0 if addr1 == addr2. -1 if addr1 is smaller, +1 if larger.
+ */
+int sockaddr_cmp_scopeid(struct sockaddr_storage* addr1, socklen_t len1,
+	struct sockaddr_storage* addr2, socklen_t len2);
+
+/**
  * Checkout address family.
  * @param addr: the sockaddr to examine.
  * @param len: the length of addr.
@@ -476,14 +488,23 @@ int listen_sslctx_setup(void* ctxt);
  */
 void listen_sslctx_setup_2(void* ctxt);
 
-/** 
+/**
  * create SSL listen context
  * @param key: private key file.
  * @param pem: public key cert.
  * @param verifypem: if nonNULL, verifylocation file.
+ * @param tls_ciphers: if non empty string, tls ciphers to use.
+ * @param tls_ciphersuites: if non empty string, tls ciphersuites to use.
+ * @param set_ticket_keys_cb: if the callback for configured ticket keys needs
+ *	to be set.
+ * @param is_dot: if the TLS connection is for DoT to set the appropriate ALPN.
+ * @param is_doh: if the TLS connection is for DoH to set the appropriate ALPN.
  * return SSL_CTX* or NULL on failure (logged).
  */
-void* listen_sslctx_create(char* key, char* pem, char* verifypem);
+void* listen_sslctx_create(const char* key, const char* pem,
+	const char* verifypem, const char* tls_ciphers,
+	const char* tls_ciphersuites, int set_ticket_keys_cb,
+	int is_dot, int is_doh);
 
 /**
  * create SSL connect context
@@ -541,12 +562,10 @@ void ub_openssl_lock_delete(void);
 
 /**
  * setup TLS session ticket
- * @param sslctx: the SSL_CTX to use (from connect_sslctx_create())
  * @param tls_session_ticket_keys: TLS ticket secret filenames
  * @return false on failure (alloc failure).
  */
-int listen_sslctx_setup_ticket_keys(void* sslctx,
-	struct config_strlist* tls_session_ticket_keys);
+int listen_sslctx_setup_ticket_keys(struct config_strlist* tls_session_ticket_keys);
 
 /** Free memory used for TLS session ticket keys */
 void listen_sslctx_delete_ticket_keys(void);
Index: util/netevent.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/netevent.c,v
diff -u -p -r1.40 netevent.c
--- util/netevent.c	21 Feb 2025 13:20:40 -0000	1.40
+++ util/netevent.c	30 Aug 2025 11:52:23 -0000
@@ -314,6 +314,11 @@ struct ub_event_base* comm_base_internal
 	return b->eb->base;
 }
 
+struct ub_event* comm_point_internal(struct comm_point* c)
+{
+	return c->ev->ev;
+}
+
 /** see if errno for udp has to be logged or not uses globals */
 static int
 udp_send_errno_needs_log(struct sockaddr* addr, socklen_t addrlen)
@@ -369,6 +374,15 @@ udp_send_errno_needs_log(struct sockaddr
 		(struct sockaddr_storage*)addr, addrlen) &&
 		verbosity < VERB_DETAIL)
 		return 0;
+#  ifdef ENOTCONN
+	/* For 0.0.0.0, ::0 targets it can return that socket is not connected.
+	 * This can be ignored, and the address skipped. It remains
+	 * possible to send there for completeness in configuration. */
+	if(errno == ENOTCONN && addr_is_any(
+		(struct sockaddr_storage*)addr, addrlen) &&
+		verbosity < VERB_DETAIL)
+		return 0;
+#  endif
 	return 1;
 }
 
@@ -442,7 +456,11 @@ comm_point_send_udp_msg(struct comm_poin
 				int pret;
 				memset(&p, 0, sizeof(p));
 				p.fd = c->fd;
-				p.events = POLLOUT | POLLERR | POLLHUP;
+				p.events = POLLOUT
+#ifndef USE_WINSOCK
+					| POLLERR | POLLHUP
+#endif
+					;
 #  ifndef USE_WINSOCK
 				pret = poll(&p, 1, SEND_BLOCKED_WAIT_TIMEOUT);
 #  else
@@ -465,7 +483,7 @@ comm_point_send_udp_msg(struct comm_poin
 #  ifdef EWOULDBLOCK
 					errno != EWOULDBLOCK &&
 #  endif
-					errno != ENOBUFS
+					errno != ENOMEM && errno != ENOBUFS
 #else
 					WSAGetLastError() != WSAEINPROGRESS &&
 					WSAGetLastError() != WSAEINTR &&
@@ -478,15 +496,19 @@ comm_point_send_udp_msg(struct comm_poin
 					return 0;
 				} else if((pret < 0 &&
 #ifndef USE_WINSOCK
-					errno == ENOBUFS
+					( errno == ENOBUFS  /* Maybe some systems */
+					|| errno == ENOMEM  /* Linux */
+					|| errno == EAGAIN)  /* Macos, solaris, openbsd */
 #else
 					WSAGetLastError() == WSAENOBUFS
 #endif
 					) || (send_nobufs && retries > 0)) {
-					/* ENOBUFS, and poll returned without
+					/* ENOBUFS/ENOMEM/EAGAIN, and poll
+					 * returned without
 					 * a timeout. Or the retried send call
-					 * returned ENOBUFS. It is good to
-					 * wait a bit for the error to clear. */
+					 * returned ENOBUFS/ENOMEM/EAGAIN.
+					 * It is good to wait a bit for the
+					 * error to clear. */
 					/* The timeout is 20*(2^(retries+1)),
 					 * it increases exponentially, starting
 					 * at 40 msec. After 5 tries, 1240 msec
@@ -496,20 +518,18 @@ comm_point_send_udp_msg(struct comm_poin
 #ifndef USE_WINSOCK
 					pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
 #else
-					pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+					Sleep((SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+					pret = 0;
 #endif
-					if(pret < 0 &&
+					if(pret < 0
 #ifndef USE_WINSOCK
-						errno != EAGAIN && errno != EINTR &&
+						&& errno != EAGAIN && errno != EINTR &&
 #  ifdef EWOULDBLOCK
 						errno != EWOULDBLOCK &&
 #  endif
-						errno != ENOBUFS
+						errno != ENOMEM && errno != ENOBUFS
 #else
-						WSAGetLastError() != WSAEINPROGRESS &&
-						WSAGetLastError() != WSAEINTR &&
-						WSAGetLastError() != WSAENOBUFS &&
-						WSAGetLastError() != WSAEWOULDBLOCK
+						/* Sleep does not error */
 #endif
 					) {
 						log_err("poll udp out timer failed: %s",
@@ -751,7 +771,11 @@ comm_point_send_udp_msg_if(struct comm_p
 				int pret;
 				memset(&p, 0, sizeof(p));
 				p.fd = c->fd;
-				p.events = POLLOUT | POLLERR | POLLHUP;
+				p.events = POLLOUT
+#ifndef USE_WINSOCK
+					| POLLERR | POLLHUP
+#endif
+					;
 #  ifndef USE_WINSOCK
 				pret = poll(&p, 1, SEND_BLOCKED_WAIT_TIMEOUT);
 #  else
@@ -774,7 +798,7 @@ comm_point_send_udp_msg_if(struct comm_p
 #  ifdef EWOULDBLOCK
 					errno != EWOULDBLOCK &&
 #  endif
-					errno != ENOBUFS
+					errno != ENOMEM && errno != ENOBUFS
 #else
 					WSAGetLastError() != WSAEINPROGRESS &&
 					WSAGetLastError() != WSAEINTR &&
@@ -787,15 +811,19 @@ comm_point_send_udp_msg_if(struct comm_p
 					return 0;
 				} else if((pret < 0 &&
 #ifndef USE_WINSOCK
-					errno == ENOBUFS
+					( errno == ENOBUFS  /* Maybe some systems */
+					|| errno == ENOMEM  /* Linux */
+					|| errno == EAGAIN)  /* Macos, solaris, openbsd */
 #else
 					WSAGetLastError() == WSAENOBUFS
 #endif
 					) || (send_nobufs && retries > 0)) {
-					/* ENOBUFS, and poll returned without
+					/* ENOBUFS/ENOMEM/EAGAIN, and poll
+					 * returned without
 					 * a timeout. Or the retried send call
-					 * returned ENOBUFS. It is good to
-					 * wait a bit for the error to clear. */
+					 * returned ENOBUFS/ENOMEM/EAGAIN.
+					 * It is good to wait a bit for the
+					 * error to clear. */
 					/* The timeout is 20*(2^(retries+1)),
 					 * it increases exponentially, starting
 					 * at 40 msec. After 5 tries, 1240 msec
@@ -805,20 +833,18 @@ comm_point_send_udp_msg_if(struct comm_p
 #ifndef USE_WINSOCK
 					pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
 #else
-					pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+					Sleep((SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+					pret = 0;
 #endif
-					if(pret < 0 &&
+					if(pret < 0
 #ifndef USE_WINSOCK
-						errno != EAGAIN && errno != EINTR &&
+						&& errno != EAGAIN && errno != EINTR &&
 #  ifdef EWOULDBLOCK
 						errno != EWOULDBLOCK &&
 #  endif
-						errno != ENOBUFS
-#else
-						WSAGetLastError() != WSAEINPROGRESS &&
-						WSAGetLastError() != WSAEINTR &&
-						WSAGetLastError() != WSAENOBUFS &&
-						WSAGetLastError() != WSAEWOULDBLOCK
+						errno != ENOMEM && errno != ENOBUFS
+#else  /* USE_WINSOCK */
+						/* Sleep does not error */
 #endif
 					) {
 						log_err("poll udp out timer failed: %s",
@@ -2687,8 +2713,8 @@ comm_point_doq_callback(int fd, short ev
 /** create new doq server socket structure */
 static struct doq_server_socket*
 doq_server_socket_create(struct doq_table* table, struct ub_randstate* rnd,
-	const char* ssl_service_key, const char* ssl_service_pem,
-	struct comm_point* c, struct comm_base* base, struct config_file* cfg)
+	const void* quic_sslctx, struct comm_point* c, struct comm_base* base,
+	struct config_file* cfg)
 {
 	size_t doq_buffer_size = 4096; /* bytes buffer size, for one packet. */
 	struct doq_server_socket* doq_socket;
@@ -2699,69 +2725,29 @@ doq_server_socket_create(struct doq_tabl
 	doq_socket->table = table;
 	doq_socket->rnd = rnd;
 	doq_socket->validate_addr = 1;
-	if(ssl_service_key == NULL || ssl_service_key[0]==0) {
-		log_err("doq server socket create: no tls-service-key");
-		free(doq_socket);
-		return NULL;
-	}
-	if(ssl_service_pem == NULL || ssl_service_pem[0]==0) {
-		log_err("doq server socket create: no tls-service-pem");
-		free(doq_socket);
-		return NULL;
-	}
-	doq_socket->ssl_service_key = strdup(ssl_service_key);
-	if(!doq_socket->ssl_service_key) {
-		free(doq_socket);
-		return NULL;
-	}
-	doq_socket->ssl_service_pem = strdup(ssl_service_pem);
-	if(!doq_socket->ssl_service_pem) {
-		free(doq_socket->ssl_service_key);
-		free(doq_socket);
-		return NULL;
-	}
-	doq_socket->ssl_verify_pem = NULL;
 	/* the doq_socket has its own copy of the static secret, as
 	 * well as other config values, so that they do not need table.lock */
 	doq_socket->static_secret_len = table->static_secret_len;
 	doq_socket->static_secret = memdup(table->static_secret,
 		table->static_secret_len);
 	if(!doq_socket->static_secret) {
-		free(doq_socket->ssl_service_key);
-		free(doq_socket->ssl_service_pem);
-		free(doq_socket->ssl_verify_pem);
-		free(doq_socket);
-		return NULL;
-	}
-	if(!doq_socket_setup_ctx(doq_socket)) {
-		free(doq_socket->ssl_service_key);
-		free(doq_socket->ssl_service_pem);
-		free(doq_socket->ssl_verify_pem);
-		free(doq_socket->static_secret);
 		free(doq_socket);
 		return NULL;
 	}
+	doq_socket->ctx = (SSL_CTX*)quic_sslctx;
 	doq_socket->idle_timeout = table->idle_timeout;
 	doq_socket->sv_scidlen = table->sv_scidlen;
 	doq_socket->cp = c;
 	doq_socket->pkt_buf = sldns_buffer_new(doq_buffer_size);
 	if(!doq_socket->pkt_buf) {
-		free(doq_socket->ssl_service_key);
-		free(doq_socket->ssl_service_pem);
-		free(doq_socket->ssl_verify_pem);
 		free(doq_socket->static_secret);
-		SSL_CTX_free(doq_socket->ctx);
 		free(doq_socket);
 		return NULL;
 	}
 	doq_socket->blocked_pkt = sldns_buffer_new(
 		sldns_buffer_capacity(doq_socket->pkt_buf));
 	if(!doq_socket->pkt_buf) {
-		free(doq_socket->ssl_service_key);
-		free(doq_socket->ssl_service_pem);
-		free(doq_socket->ssl_verify_pem);
 		free(doq_socket->static_secret);
-		SSL_CTX_free(doq_socket->ctx);
 		sldns_buffer_free(doq_socket->pkt_buf);
 		free(doq_socket);
 		return NULL;
@@ -2769,11 +2755,7 @@ doq_server_socket_create(struct doq_tabl
 	doq_socket->blocked_paddr = calloc(1,
 		sizeof(*doq_socket->blocked_paddr));
 	if(!doq_socket->blocked_paddr) {
-		free(doq_socket->ssl_service_key);
-		free(doq_socket->ssl_service_pem);
-		free(doq_socket->ssl_verify_pem);
 		free(doq_socket->static_secret);
-		SSL_CTX_free(doq_socket->ctx);
 		sldns_buffer_free(doq_socket->pkt_buf);
 		sldns_buffer_free(doq_socket->blocked_pkt);
 		free(doq_socket);
@@ -2781,11 +2763,7 @@ doq_server_socket_create(struct doq_tabl
 	}
 	doq_socket->timer = comm_timer_create(base, doq_timer_cb, doq_socket);
 	if(!doq_socket->timer) {
-		free(doq_socket->ssl_service_key);
-		free(doq_socket->ssl_service_pem);
-		free(doq_socket->ssl_verify_pem);
 		free(doq_socket->static_secret);
-		SSL_CTX_free(doq_socket->ctx);
 		sldns_buffer_free(doq_socket->pkt_buf);
 		sldns_buffer_free(doq_socket->blocked_pkt);
 		free(doq_socket->blocked_paddr);
@@ -2805,13 +2783,9 @@ doq_server_socket_delete(struct doq_serv
 	if(!doq_socket)
 		return;
 	free(doq_socket->static_secret);
-	SSL_CTX_free(doq_socket->ctx);
 #ifndef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_SERVER_CONTEXT
 	free(doq_socket->quic_method);
 #endif
-	free(doq_socket->ssl_service_key);
-	free(doq_socket->ssl_service_pem);
-	free(doq_socket->ssl_verify_pem);
 	sldns_buffer_free(doq_socket->pkt_buf);
 	sldns_buffer_free(doq_socket->blocked_pkt);
 	free(doq_socket->blocked_paddr);
@@ -2845,6 +2819,7 @@ static int
 doq_lookup_conn_stream(struct comm_reply* repinfo, struct comm_point* c,
 	struct doq_conn** conn, struct doq_stream** stream)
 {
+	log_assert(c->doq_socket);
 	if(c->doq_socket->current_conn) {
 		*conn = c->doq_socket->current_conn;
 	} else {
@@ -3084,7 +3059,7 @@ int comm_point_perform_accept(struct com
 			if(verbosity >= 3)
 				log_err_addr("accept rejected",
 				"connection limit exceeded", addr, *addrlen);
-			close(new_fd);
+			sock_close(new_fd);
 			return -1;
 		}
 	}
@@ -3185,6 +3160,40 @@ static int http2_submit_settings(struct 
 }
 #endif /* HAVE_NGHTTP2 */
 
+#ifdef HAVE_NGHTTP2
+/** Delete http2 stream. After session delete or stream close callback */
+static void http2_stream_delete(struct http2_session* h2_session,
+	struct http2_stream* h2_stream)
+{
+	if(h2_stream->mesh_state) {
+		mesh_state_remove_reply(h2_stream->mesh, h2_stream->mesh_state,
+			h2_session->c);
+		h2_stream->mesh_state = NULL;
+	}
+	http2_req_stream_clear(h2_stream);
+	free(h2_stream);
+}
+#endif /* HAVE_NGHTTP2 */
+
+/** delete http2 session server. After closing connection. */
+static void http2_session_server_delete(struct http2_session* h2_session)
+{
+#ifdef HAVE_NGHTTP2
+	struct http2_stream* h2_stream, *next;
+	nghttp2_session_del(h2_session->session); /* NULL input is fine */
+	h2_session->session = NULL;
+	for(h2_stream = h2_session->first_stream; h2_stream;) {
+		next = h2_stream->next;
+		http2_stream_delete(h2_session, h2_stream);
+		h2_stream = next;
+	}
+	h2_session->first_stream = NULL;
+	h2_session->is_drop = 0;
+	h2_session->postpone_drop = 0;
+	h2_session->c->h2_stream = NULL;
+#endif
+	(void)h2_session;
+}
 
 void
 comm_point_tcp_accept_callback(int fd, short event, void* arg)
@@ -3223,6 +3232,8 @@ comm_point_tcp_accept_callback(int fd, s
 		if(!c_hdl->h2_session ||
 			!http2_submit_settings(c_hdl->h2_session)) {
 			log_warn("failed to submit http2 settings");
+			if(c_hdl->h2_session)
+				http2_session_server_delete(c_hdl->h2_session);
 			return;
 		}
 		if(!c->ssl) {
@@ -3240,14 +3251,23 @@ comm_point_tcp_accept_callback(int fd, s
 	}
 	if(!c_hdl->ev->ev) {
 		log_warn("could not ub_event_new, dropped tcp");
+#ifdef HAVE_NGHTTP2
+		if(c_hdl->type == comm_http && c_hdl->h2_session)
+			http2_session_server_delete(c_hdl->h2_session);
+#endif
 		return;
 	}
 	log_assert(fd != -1);
 	(void)fd;
 	new_fd = comm_point_perform_accept(c, &c_hdl->repinfo.remote_addr,
 		&c_hdl->repinfo.remote_addrlen);
-	if(new_fd == -1)
+	if(new_fd == -1) {
+#ifdef HAVE_NGHTTP2
+		if(c_hdl->type == comm_http && c_hdl->h2_session)
+			http2_session_server_delete(c_hdl->h2_session);
+#endif
 		return;
+	}
 	/* Copy remote_address to client_address.
 	 * Simplest way/time for streams to do that. */
 	c_hdl->repinfo.client_addrlen = c_hdl->repinfo.remote_addrlen;
@@ -5062,19 +5082,6 @@ struct http2_stream* http2_stream_create
 	h2_stream->stream_id = stream_id;
 	return h2_stream;
 }
-
-/** Delete http2 stream. After session delete or stream close callback */
-static void http2_stream_delete(struct http2_session* h2_session,
-	struct http2_stream* h2_stream)
-{
-	if(h2_stream->mesh_state) {
-		mesh_state_remove_reply(h2_stream->mesh, h2_stream->mesh_state,
-			h2_session->c);
-		h2_stream->mesh_state = NULL;
-	}
-	http2_req_stream_clear(h2_stream);
-	free(h2_stream);
-}
 #endif
 
 void http2_stream_add_meshstate(struct http2_stream* h2_stream,
@@ -5091,26 +5098,6 @@ void http2_stream_remove_mesh_state(stru
 	h2_stream->mesh_state = NULL;
 }
 
-/** delete http2 session server. After closing connection. */
-static void http2_session_server_delete(struct http2_session* h2_session)
-{
-#ifdef HAVE_NGHTTP2
-	struct http2_stream* h2_stream, *next;
-	nghttp2_session_del(h2_session->session); /* NULL input is fine */
-	h2_session->session = NULL;
-	for(h2_stream = h2_session->first_stream; h2_stream;) {
-		next = h2_stream->next;
-		http2_stream_delete(h2_session, h2_stream);
-		h2_stream = next;
-	}
-	h2_session->first_stream = NULL;
-	h2_session->is_drop = 0;
-	h2_session->postpone_drop = 0;
-	h2_session->c->h2_stream = NULL;
-#endif
-	(void)h2_session;
-}
-
 #ifdef HAVE_NGHTTP2
 void http2_session_add_stream(struct http2_session* h2_session,
 	struct http2_stream* h2_stream)
@@ -5861,8 +5848,8 @@ struct comm_point*
 comm_point_create_doq(struct comm_base *base, int fd, sldns_buffer* buffer,
 	comm_point_callback_type* callback, void* callback_arg,
 	struct unbound_socket* socket, struct doq_table* table,
-	struct ub_randstate* rnd, const char* ssl_service_key,
-	const char* ssl_service_pem, struct config_file* cfg)
+	struct ub_randstate* rnd, const void* quic_sslctx,
+	struct config_file* cfg)
 {
 #ifdef HAVE_NGTCP2
 	struct comm_point* c = (struct comm_point*)calloc(1,
@@ -5899,15 +5886,13 @@ comm_point_create_doq(struct comm_base *
 	c->dnscrypt = 0;
 	c->dnscrypt_buffer = NULL;
 #endif
-#ifdef HAVE_NGTCP2
-	c->doq_socket = doq_server_socket_create(table, rnd, ssl_service_key,
-		ssl_service_pem, c, base, cfg);
+	c->doq_socket = doq_server_socket_create(table, rnd, quic_sslctx, c,
+		base, cfg);
 	if(!c->doq_socket) {
 		log_err("could not create doq comm_point");
 		comm_point_delete(c);
 		return NULL;
 	}
-#endif
 	c->inuse = 0;
 	c->callback = callback;
 	c->cb_arg = callback_arg;
@@ -5939,8 +5924,7 @@ comm_point_create_doq(struct comm_base *
 	(void)socket;
 	(void)rnd;
 	(void)table;
-	(void)ssl_service_key;
-	(void)ssl_service_pem;
+	(void)quic_sslctx;
 	(void)cfg;
 	sock_close(fd);
 	return NULL;
@@ -6747,7 +6731,7 @@ comm_point_drop_reply(struct comm_reply*
 		reclaim_http_handler(repinfo->c);
 		return;
 #ifdef HAVE_NGTCP2
-	} else if(repinfo->c->type == comm_doq) {
+	} else if(repinfo->c->doq_socket) {
 		doq_socket_drop_reply(repinfo);
 		return;
 #endif
@@ -6952,8 +6936,9 @@ comm_timer_is_set(struct comm_timer* tim
 }
 
 size_t
-comm_timer_get_mem(struct comm_timer* ATTR_UNUSED(timer))
+comm_timer_get_mem(struct comm_timer* timer)
 {
+	if(!timer) return 0;
 	return sizeof(struct internal_timer);
 }
 
Index: util/netevent.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/netevent.h,v
diff -u -p -r1.24 netevent.h
--- util/netevent.h	21 Feb 2025 13:20:40 -0000	1.24
+++ util/netevent.h	30 Aug 2025 11:52:23 -0000
@@ -548,6 +548,14 @@ void comm_base_set_slow_accept_handlers(
 struct ub_event_base* comm_base_internal(struct comm_base* b);
 
 /**
+ * Access internal event structure. It is for use with
+ * ub_winsock_tcp_wouldblock on windows.
+ * @param c: comm point.
+ * @return event.
+ */
+struct ub_event* comm_point_internal(struct comm_point* c);
+
+/**
  * Create an UDP comm point. Calls malloc.
  * setups the structure with the parameters you provide.
  * @param base: in which base to alloc the commpoint.
@@ -593,8 +601,7 @@ struct comm_point* comm_point_create_udp
  * @param socket: and opened socket properties will be passed to your callback function.
  * @param table: the doq connection table for the host.
  * @param rnd: random generator to use.
- * @param ssl_service_key: the ssl service key file.
- * @param ssl_service_pem: the ssl service pem file.
+ * @param quic_sslctx: the quic ssl context.
  * @param cfg: config file struct.
  * @return: returns the allocated communication point. NULL on error.
  * Sets timeout to NULL. Turns off TCP options.
@@ -603,8 +610,8 @@ struct comm_point* comm_point_create_doq
 	int fd, struct sldns_buffer* buffer,
 	comm_point_callback_type* callback, void* callback_arg,
 	struct unbound_socket* socket, struct doq_table* table,
-	struct ub_randstate* rnd, const char* ssl_service_key,
-	const char* ssl_service_pem, struct config_file* cfg);
+	struct ub_randstate* rnd, const void* quic_sslctx,
+	struct config_file* cfg);
 
 /**
  * Create a TCP listener comm point. Calls malloc.
@@ -1045,12 +1052,6 @@ struct doq_server_socket {
 	struct ub_randstate* rnd;
 	/** if address validation is enabled */
 	uint8_t validate_addr;
-	/** the ssl service key file */
-	char* ssl_service_key;
-	/** the ssl service pem file */
-	char* ssl_service_pem;
-	/** the ssl verify pem file */
-	char* ssl_verify_pem;
 	/** the server scid length */
 	int sv_scidlen;
 	/** the idle timeout in nanoseconds */
Index: util/tcp_conn_limit.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/tcp_conn_limit.c,v
diff -u -p -r1.1.1.1 tcp_conn_limit.c
--- util/tcp_conn_limit.c	20 Sep 2018 23:14:40 -0000	1.1.1.1
+++ util/tcp_conn_limit.c	30 Aug 2025 11:52:23 -0000
@@ -192,3 +192,14 @@ tcl_list_get_mem(struct tcl_list* tcl)
 	if(!tcl) return 0;
 	return sizeof(*tcl) + regional_get_mem(tcl->region);
 }
+
+void tcl_list_swap_tree(struct tcl_list* tcl, struct tcl_list* data)
+{
+	/* swap tree and region */
+	rbtree_type oldtree = tcl->tree;
+	struct regional* oldregion = tcl->region;
+	tcl->tree = data->tree;
+	tcl->region = data->region;
+	data->tree = oldtree;
+	data->region = oldregion;
+}
Index: util/tcp_conn_limit.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/tcp_conn_limit.h,v
diff -u -p -r1.1.1.1 tcp_conn_limit.h
--- util/tcp_conn_limit.h	20 Sep 2018 23:14:41 -0000	1.1.1.1
+++ util/tcp_conn_limit.h	30 Aug 2025 11:52:23 -0000
@@ -127,4 +127,13 @@ tcl_addr_lookup(struct tcl_list* tcl, st
  */
 size_t tcl_list_get_mem(struct tcl_list* tcl);
 
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * tcl_addr item locks.
+ * @param tcl: the tcp connection list structure.
+ * @param data: the data structure used to take elements from. This contains
+ * 	the old elements on return.
+ */
+void tcl_list_swap_tree(struct tcl_list* tcl, struct tcl_list* data);
+
 #endif /* DAEMON_TCP_CONN_LIMIT_H */
Index: util/tube.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/tube.c,v
diff -u -p -r1.9 tube.c
--- util/tube.c	6 Sep 2023 09:08:05 -0000	1.9
+++ util/tube.c	30 Aug 2025 11:52:23 -0000
@@ -584,7 +584,10 @@ void tube_close_write(struct tube* ATTR_
 void tube_remove_bg_listen(struct tube* tube)
 {
 	verbose(VERB_ALGO, "tube remove_bg_listen");
-	ub_winsock_unregister_wsaevent(tube->ev_listen);
+	if (tube->ev_listen != NULL) {
+		ub_winsock_unregister_wsaevent(tube->ev_listen);
+		tube->ev_listen = NULL;
+	}
 }
 
 void tube_remove_bg_write(struct tube* tube)
Index: util/ub_event.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/ub_event.c,v
diff -u -p -r1.8 ub_event.c
--- util/ub_event.c	23 Feb 2022 12:04:06 -0000	1.8
+++ util/ub_event.c	30 Aug 2025 11:52:23 -0000
@@ -46,6 +46,13 @@
 #include "util/log.h"
 #include "util/netevent.h"
 #include "util/tube.h"
+#include "daemon/remote.h"
+#ifdef USE_DNSTAP
+#include "dnstap/dtstream.h"
+#endif
+#ifdef UB_ON_WINDOWS
+#include "winrc/win_svc.h"
+#endif
 
 /* We define libevent structures here to hide the libevent stuff. */
 
@@ -95,9 +102,29 @@ UB_EV_BITS_CB(comm_timer_callback)
 UB_EV_BITS_CB(comm_signal_callback)
 UB_EV_BITS_CB(comm_point_local_handle_callback)
 UB_EV_BITS_CB(comm_point_raw_handle_callback)
-UB_EV_BITS_CB(comm_point_http_handle_callback)
 UB_EV_BITS_CB(tube_handle_signal)
 UB_EV_BITS_CB(comm_base_handle_slow_accept)
+UB_EV_BITS_CB(comm_point_http_handle_callback)
+#ifdef HAVE_NGTCP2
+UB_EV_BITS_CB(comm_point_doq_callback)
+#endif
+UB_EV_BITS_CB(fast_reload_service_cb)
+#ifdef USE_DNSTAP
+UB_EV_BITS_CB(dtio_output_cb)
+UB_EV_BITS_CB(dtio_cmd_cb)
+UB_EV_BITS_CB(dtio_reconnect_timeout_cb)
+UB_EV_BITS_CB(dtio_stop_timer_cb)
+UB_EV_BITS_CB(dtio_stop_ev_cb)
+UB_EV_BITS_CB(dtio_tap_callback)
+UB_EV_BITS_CB(dtio_mainfdcallback)
+#endif
+#ifdef HAVE_NGTCP2
+UB_EV_BITS_CB(doq_client_event_cb)
+UB_EV_BITS_CB(doq_client_timer_cb)
+#endif
+#ifdef UB_ON_WINDOWS
+UB_EV_BITS_CB(worker_win_stop_cb)
+#endif
 
 static void (*NATIVE_BITS_CB(void (*cb)(int, short, void*)))(int, short, void*)
 {
@@ -123,6 +150,38 @@ static void (*NATIVE_BITS_CB(void (*cb)(
 		return my_tube_handle_signal;
 	else if(cb == comm_base_handle_slow_accept)
 		return my_comm_base_handle_slow_accept;
+#ifdef HAVE_NGTCP2
+	else if(cb == comm_point_doq_callback)
+		return my_comm_point_doq_callback;
+#endif
+	else if(cb == fast_reload_service_cb)
+		return my_fast_reload_service_cb;
+#ifdef USE_DNSTAP
+	else if(cb == dtio_output_cb)
+		return my_dtio_output_cb;
+	else if(cb == dtio_cmd_cb)
+		return my_dtio_cmd_cb;
+	else if(cb == dtio_reconnect_timeout_cb)
+		return my_dtio_reconnect_timeout_cb;
+	else if(cb == dtio_stop_timer_cb)
+		return my_dtio_stop_timer_cb;
+	else if(cb == dtio_stop_ev_cb)
+		return my_dtio_stop_ev_cb;
+	else if(cb == dtio_tap_callback)
+		return my_dtio_tap_callback;
+	else if(cb == dtio_mainfdcallback)
+		return my_dtio_mainfdcallback;
+#endif
+#ifdef HAVE_NGTCP2
+	else if(cb == doq_client_event_cb)
+		return my_doq_client_event_cb;
+	else if(cb == doq_client_timer_cb)
+		return my_doq_client_timer_cb;
+#endif
+#ifdef UB_ON_WINDOWS
+	else if(cb == worker_win_stop_cb)
+		return my_worker_win_stop_cb;
+#endif
 	else {
 		log_assert(0); /* this NULL callback pointer should not happen,
 			we should have the necessary routine listed above */
Index: util/data/dname.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/data/dname.c,v
diff -u -p -r1.7 dname.c
--- util/data/dname.c	19 May 2020 08:48:50 -0000	1.7
+++ util/data/dname.c	30 Aug 2025 11:52:23 -0000
@@ -644,24 +644,24 @@ void dname_str(uint8_t* dname, char* str
 	if(!dname || !*dname) {
 		*s++ = '.';
 		*s = 0;
-		return;
+		goto out;
 	}
 	lablen = *dname++;
 	while(lablen) {
 		if(lablen > LDNS_MAX_LABELLEN) {
 			*s++ = '#';
 			*s = 0;
-			return;
+			goto out;
 		}
 		len += lablen+1;
-		if(len >= LDNS_MAX_DOMAINLEN-1) {
+		if(len >= LDNS_MAX_DOMAINLEN) {
 			*s++ = '&';
 			*s = 0;
-			return;
+			goto out;
 		}
 		while(lablen--) {
-			if(isalnum((unsigned char)*dname) 
-				|| *dname == '-' || *dname == '_' 
+			if(isalnum((unsigned char)*dname)
+				|| *dname == '-' || *dname == '_'
 				|| *dname == '*')
 				*s++ = *(char*)dname++;
 			else	{
@@ -673,6 +673,10 @@ void dname_str(uint8_t* dname, char* str
 		lablen = *dname++;
 	}
 	*s = 0;
+
+out:
+	log_assert(s - str < LDNS_MAX_DOMAINLEN);
+	return;
 }
 
 int 
Index: util/data/dname.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/data/dname.h,v
diff -u -p -r1.6 dname.h
--- util/data/dname.h	4 Sep 2024 09:36:41 -0000	1.6
+++ util/data/dname.h	30 Aug 2025 11:52:23 -0000
@@ -242,11 +242,12 @@ void dname_print(FILE* out, struct sldns
 
 /** 
  * Debug helper. Print dname to given string buffer (string buffer must
- * be at least 255 chars + 1 for the 0, in printable form.
+ * be at least 255 chars, in printable form.
  * This may lose information (? for nonprintable characters, or & if
  * the name is too long, # for a bad label length).
+ * Upon return, the buffer will always have a terminating \0 value.
  * @param dname: uncompressed wireformat.
- * @param str: buffer of 255+1 length.
+ * @param str: buffer of at least 255 length.
  */
 void dname_str(uint8_t* dname, char* str);
 
Index: util/data/msgreply.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/data/msgreply.c,v
diff -u -p -r1.26 msgreply.c
--- util/data/msgreply.c	21 Feb 2025 13:20:40 -0000	1.26
+++ util/data/msgreply.c	30 Aug 2025 11:52:23 -0000
@@ -66,7 +66,7 @@ time_t MIN_NEG_TTL = 0;
 /** If we serve expired entries and prefetch them */
 int SERVE_EXPIRED = 0;
 /** Time to serve records after expiration */
-time_t SERVE_EXPIRED_TTL = 0;
+time_t SERVE_EXPIRED_TTL = 86400;
 /** Reset serve expired TTL after failed update attempt */
 time_t SERVE_EXPIRED_TTL_RESET = 0;
 /** TTL to use for expired records */
@@ -965,15 +965,11 @@ void
 log_reply_info(enum verbosity_value v, struct query_info *qinf,
 	struct sockaddr_storage *addr, socklen_t addrlen, struct timeval dur,
 	int cached, struct sldns_buffer *rmsg, struct sockaddr_storage* daddr,
-	enum comm_point_type tp)
+	enum comm_point_type tp, void* ssl)
 {
-	char qname_buf[LDNS_MAX_DOMAINLEN+1];
 	char clientip_buf[128];
 	char rcode_buf[16];
-	char type_buf[16];
-	char class_buf[16];
 	char dest_buf[160];
-	size_t pktlen;
 	uint16_t rcode = FLAGS_GET_RCODE(sldns_buffer_read_u16_at(rmsg, 2));
 
 	if(verbosity < v)
@@ -1004,9 +1000,9 @@ log_reply_info(enum verbosity_value v, s
 				(int)daddr->ss_family);
 		}
 		comm = "udp";
-		if(tp == comm_tcp) comm = "tcp";
-		else if(tp == comm_tcp_accept) comm = "tcp";
-		else if(tp == comm_http) comm = "dot";
+		if(tp == comm_tcp) comm = (ssl?"dot":"tcp");
+		else if(tp == comm_tcp_accept) comm = (ssl?"dot":"tcp");
+		else if(tp == comm_http) comm = "doh";
 		else if(tp == comm_local) comm = "unix";
 		else if(tp == comm_raw) comm = "raw";
 		snprintf(dest_buf, sizeof(dest_buf), " on %s %s %d",
@@ -1022,6 +1018,10 @@ log_reply_info(enum verbosity_value v, s
 		else	log_info("%s - - - %s - - -%s", clientip_buf,
 				rcode_buf, dest_buf);
 	} else {
+		char qname_buf[LDNS_MAX_DOMAINLEN];
+		char type_buf[16];
+		char class_buf[16];
+		size_t pktlen;
 		if(qinf->qname)
 			dname_str(qinf->qname, qname_buf);
 		else	snprintf(qname_buf, sizeof(qname_buf), "null");
Index: util/data/msgreply.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/data/msgreply.h,v
diff -u -p -r1.16 msgreply.h
--- util/data/msgreply.h	21 Feb 2025 13:20:40 -0000	1.16
+++ util/data/msgreply.h	30 Aug 2025 11:52:23 -0000
@@ -554,11 +554,13 @@ void log_dns_msg(const char* str, struct
  * @param rmsg: sldns buffer packet.
  * @param daddr: if not NULL, the destination address and port are logged.
  * @param tp: type of the comm point for logging destination connection type.
+ * @param ssl: the SSL pointer of the connection, to see if the connection
+ *	type is tcp or dot.
  */
 void log_reply_info(enum verbosity_value v, struct query_info *qinf,
 	struct sockaddr_storage *addr, socklen_t addrlen, struct timeval dur,
 	int cached, struct sldns_buffer *rmsg, struct sockaddr_storage* daddr,
-	enum comm_point_type tp);
+	enum comm_point_type tp, void* ssl);
 
 /**
  * Print string with neat domain name, type, class from query info.
Index: util/shm_side/shm_main.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/shm_side/shm_main.c,v
diff -u -p -r1.7 shm_main.c
--- util/shm_side/shm_main.c	23 Feb 2022 12:04:06 -0000	1.7
+++ util/shm_side/shm_main.c	30 Aug 2025 11:52:23 -0000
@@ -195,7 +195,7 @@ void shm_main_shutdown(struct daemon* da
 {
 #ifdef HAVE_SHMGET
 	/* web are OK, just disabled */
-	if(!daemon->cfg->shm_enable)
+	if(!daemon->cfg->shm_enable || !daemon->shm_info)
 		return;
 
 	verbose(VERB_DETAIL, "SHM shutdown - KEY [%d] - ID CTL [%d] ARR [%d] - PTR CTL [%p] ARR [%p]",
Index: util/storage/dnstree.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/storage/dnstree.c,v
diff -u -p -r1.4 dnstree.c
--- util/storage/dnstree.c	20 Oct 2022 08:26:14 -0000	1.4
+++ util/storage/dnstree.c	30 Aug 2025 11:52:23 -0000
@@ -75,7 +75,7 @@ int addr_tree_addrport_compare(const voi
 {
 	struct addr_tree_node* n1 = (struct addr_tree_node*)k1;
 	struct addr_tree_node* n2 = (struct addr_tree_node*)k2;
-	return sockaddr_cmp(&n1->addr, n1->addrlen, &n2->addr,
+	return sockaddr_cmp_scopeid(&n1->addr, n1->addrlen, &n2->addr,
 		n2->addrlen);
 }
 
Index: util/storage/lruhash.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/storage/lruhash.c,v
diff -u -p -r1.6 lruhash.c
--- util/storage/lruhash.c	13 Jun 2024 14:30:28 -0000	1.6
+++ util/storage/lruhash.c	30 Aug 2025 11:52:23 -0000
@@ -562,6 +562,36 @@ lruhash_update_space_used(struct lruhash
 	}
 }
 
+void lruhash_update_space_max(struct lruhash* table, void* cb_arg, size_t max)
+{
+	struct lruhash_entry *reclaimlist = NULL;
+
+	fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
+	fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
+	fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
+	fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc));
+
+	if(cb_arg == NULL) cb_arg = table->cb_arg;
+
+	/* update space max */
+	lock_quick_lock(&table->lock);
+	table->space_max = max;
+
+	if(table->space_used > table->space_max)
+		reclaim_space(table, &reclaimlist);
+
+	lock_quick_unlock(&table->lock);
+
+	/* finish reclaim if any (outside of critical region) */
+	while(reclaimlist) {
+		struct lruhash_entry* n = reclaimlist->overflow_next;
+		void* d = reclaimlist->data;
+		(*table->delkeyfunc)(reclaimlist->key, cb_arg);
+		(*table->deldatafunc)(d, cb_arg);
+		reclaimlist = n;
+	}
+}
+
 void 
 lruhash_traverse(struct lruhash* h, int wr, 
 	void (*func)(struct lruhash_entry*, void*), void* arg)
Index: util/storage/lruhash.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/storage/lruhash.h,v
diff -u -p -r1.5 lruhash.h
--- util/storage/lruhash.h	13 Jun 2024 14:30:28 -0000	1.5
+++ util/storage/lruhash.h	30 Aug 2025 11:52:23 -0000
@@ -314,6 +314,16 @@ void lruhash_setmarkdel(struct lruhash* 
 void lruhash_update_space_used(struct lruhash* table, void* cb_override,
 	int diff_size);
 
+/**
+ * Update the max space for the hashtable.
+ *
+ * @param table: hash table.
+ * @param cb_override: if not NULL overrides the cb_arg for deletefunc.
+ * @param max: the new max.
+ */
+void lruhash_update_space_max(struct lruhash* table, void* cb_override,
+	size_t max);
+
 /************************* getdns functions ************************/
 /*** these are used by getdns only and not by unbound. ***/
 
Index: util/storage/slabhash.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/storage/slabhash.c,v
diff -u -p -r1.5 slabhash.c
--- util/storage/slabhash.c	13 Jun 2024 14:30:28 -0000	1.5
+++ util/storage/slabhash.c	30 Aug 2025 11:52:23 -0000
@@ -267,3 +267,12 @@ void get_slabhash_stats(struct slabhash*
 	if (collisions != NULL)
 		*collisions = max_collisions;
 }
+
+void slabhash_adjust_size(struct slabhash* sl, size_t max)
+{
+	size_t space_max = max / sl->size;
+	size_t i;
+	for(i=0; i<sl->size; i++) {
+		lruhash_update_space_max(sl->array[i], NULL, space_max);
+	}
+}
Index: util/storage/slabhash.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/storage/slabhash.h,v
diff -u -p -r1.6 slabhash.h
--- util/storage/slabhash.h	13 Jun 2024 14:30:28 -0000	1.6
+++ util/storage/slabhash.h	30 Aug 2025 11:52:23 -0000
@@ -221,6 +221,13 @@ size_t count_slabhash_entries(struct sla
 void get_slabhash_stats(struct slabhash* table,
 	long long* entries_count, long long* max_collisions);
 
+/**
+ * Adjust size of slabhash memory max
+ * @param table: slabbed hash table
+ * @param max: new max memory
+ */
+void slabhash_adjust_size(struct slabhash* table, size_t max);
+
 /* --- test representation --- */
 /** test structure contains test key */
 struct slabhash_testkey {
Index: validator/autotrust.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/autotrust.c,v
diff -u -p -r1.19 autotrust.c
--- validator/autotrust.c	4 Sep 2024 09:36:41 -0000	1.19
+++ validator/autotrust.c	30 Aug 2025 11:52:23 -0000
@@ -353,7 +353,7 @@ autr_tp_create(struct val_anchors* ancho
 
 	lock_basic_lock(&anchors->lock);
 	if(!rbtree_insert(anchors->tree, &tp->node)) {
-		char buf[LDNS_MAX_DOMAINLEN+1];
+		char buf[LDNS_MAX_DOMAINLEN];
 		lock_basic_unlock(&anchors->lock);
 		dname_str(tp->name, buf);
 		log_err("trust anchor for '%s' presented twice", buf);
@@ -363,7 +363,7 @@ autr_tp_create(struct val_anchors* ancho
 		return NULL;
 	}
 	if(!rbtree_insert(&anchors->autr->probe, &tp->autr->pnode)) {
-		char buf[LDNS_MAX_DOMAINLEN+1];
+		char buf[LDNS_MAX_DOMAINLEN];
 		(void)rbtree_delete(anchors->tree, tp);
 		lock_basic_unlock(&anchors->lock);
 		dname_str(tp->name, buf);
@@ -2035,25 +2035,40 @@ wait_probe_time(struct val_anchors* anch
 	return 0;
 }
 
-/** reset worker timer */
+/** reset worker timer, at the time from wait_probe_time. */
 static void
-reset_worker_timer(struct module_env* env)
+reset_worker_timer_at(struct module_env* env, time_t next)
 {
 	struct timeval tv;
 #ifndef S_SPLINT_S
-	time_t next = (time_t)wait_probe_time(env->anchors);
 	/* in case this is libunbound, no timer */
 	if(!env->probe_timer)
 		return;
 	if(next > *env->now)
 		tv.tv_sec = (time_t)(next - *env->now);
 	else	tv.tv_sec = 0;
+#else
+	(void)next;
 #endif
 	tv.tv_usec = 0;
 	comm_timer_set(env->probe_timer, &tv);
 	verbose(VERB_ALGO, "scheduled next probe in " ARG_LL "d sec", (long long)tv.tv_sec);
 }
 
+/** reset worker timer. This routine manages the locks on acquiring the
+ * next time for the timer. */
+static void
+reset_worker_timer(struct module_env* env)
+{
+	time_t next;
+	if(!env->anchors)
+		return;
+	lock_basic_lock(&env->anchors->lock);
+	next = wait_probe_time(env->anchors);
+	lock_basic_unlock(&env->anchors->lock);
+	reset_worker_timer_at(env, next);
+}
+
 /** set next probe for trust anchor */
 static int
 set_next_probe(struct module_env* env, struct trust_anchor* tp,
@@ -2092,7 +2107,7 @@ set_next_probe(struct module_env* env, s
 	verbose(VERB_ALGO, "next probe set in %d seconds", 
 		(int)tp->autr->next_probe_time - (int)*env->now);
 	if(mold != mnew) {
-		reset_worker_timer(env);
+		reset_worker_timer_at(env, mnew);
 	}
 	return 1;
 }
@@ -2147,7 +2162,7 @@ autr_tp_remove(struct module_env* env, s
 		autr_point_delete(del_tp);
 	}
 	if(mold != mnew) {
-		reset_worker_timer(env);
+		reset_worker_timer_at(env, mnew);
 	}
 }
 
@@ -2288,7 +2303,9 @@ static void 
 autr_debug_print_tp(struct trust_anchor* tp)
 {
 	struct autr_ta* ta;
-	char buf[257];
+	/* Note: buf is also used for autr_ctime_r but that only needs a size
+	 *       of 26, so LDNS_MAX_DOMAINLEN is enough. */
+	char buf[LDNS_MAX_DOMAINLEN];
 	if(!tp->autr)
 		return;
 	dname_str(tp->name, buf);
Index: validator/val_anchor.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/val_anchor.c,v
diff -u -p -r1.10 val_anchor.c
--- validator/val_anchor.c	12 Apr 2024 15:45:24 -0000	1.10
+++ validator/val_anchor.c	30 Aug 2025 11:52:23 -0000
@@ -483,11 +483,10 @@ anchor_read_file(struct val_anchors* anc
 
 /** skip file to end of line */
 static void
-skip_to_eol(FILE* in)
+skip_to_eol(FILE* in, int *c)
 {
-	int c;
-	while((c = getc(in)) != EOF ) {
-		if(c == '\n')
+	while((*c = getc(in)) != EOF ) {
+		if(*c == '\n')
 			return;
 	}
 }
@@ -534,7 +533,8 @@ readkeyword_bindfile(FILE* in, sldns_buf
 	int numdone = 0;
 	while((c = getc(in)) != EOF ) {
 		if(comments && c == '#') {	/*   # blabla   */
-			skip_to_eol(in);
+			skip_to_eol(in, &c);
+			if(c == EOF) return 0;
 			(*line)++;
 			continue;
 		} else if(comments && c=='/' && numdone>0 && /* /_/ bla*/
@@ -542,7 +542,8 @@ readkeyword_bindfile(FILE* in, sldns_buf
 			sldns_buffer_position(buf)-1) == '/') {
 			sldns_buffer_skip(buf, -1);
 			numdone--;
-			skip_to_eol(in);
+			skip_to_eol(in, &c);
+			if(c == EOF) return 0;
 			(*line)++;
 			continue;
 		} else if(comments && c=='*' && numdone>0 && /* /_* bla *_/ */
@@ -559,6 +560,7 @@ readkeyword_bindfile(FILE* in, sldns_buf
 				if(c == '\n')
 					(*line)++;
 			}
+			if(c == EOF) return 0;
 			continue;
 		}
 		/* not a comment, complete the keyword */
@@ -593,6 +595,7 @@ readkeyword_bindfile(FILE* in, sldns_buf
 					break;
 				}
 			}
+			if(c == EOF) return 0;
 			return numdone;
 		}
 		if(is_bind_special(c))
@@ -1018,7 +1021,7 @@ anchors_assemble_rrsets(struct val_ancho
 				ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass);
 		}
 		if(nods == ta->numDS && nokey == ta->numDNSKEY) {
-			char b[257];
+			char b[LDNS_MAX_DOMAINLEN];
 			dname_str(ta->name, b);
 			log_warn("trust anchor %s has no supported algorithms,"
 				" the anchor is ignored (check if you need to"
@@ -1170,17 +1173,53 @@ anchors_lookup(struct val_anchors* ancho
 	return result;
 }
 
+/** Get memory usage of assembled key rrset */
+static size_t
+assembled_rrset_get_mem(struct ub_packed_rrset_key* pkey)
+{
+	size_t s;
+	if(!pkey)
+		return 0;
+	s = sizeof(*pkey) + pkey->rk.dname_len;
+	if(pkey->entry.data) {
+		struct packed_rrset_data* pd = (struct packed_rrset_data*)
+			pkey->entry.data;
+		s += sizeof(*pd) + pd->count * (sizeof(size_t)+sizeof(time_t)+
+			sizeof(uint8_t*));
+	}
+	return s;
+}
+
 size_t 
 anchors_get_mem(struct val_anchors* anchors)
 {
 	struct trust_anchor *ta;
-	size_t s = sizeof(*anchors);
-	if(!anchors)
-		return 0;
+	struct ta_key *k;
+	size_t s;
+	if(!anchors) return 0;
+	s = sizeof(*anchors);
+	lock_basic_lock(&anchors->lock);
 	RBTREE_FOR(ta, struct trust_anchor*, anchors->tree) {
+		lock_basic_lock(&ta->lock);
 		s += sizeof(*ta) + ta->namelen;
 		/* keys and so on */
+		for(k = ta->keylist; k; k = k->next) {
+			s += sizeof(*k) + k->len;
+		}
+		s += assembled_rrset_get_mem(ta->ds_rrset);
+		s += assembled_rrset_get_mem(ta->dnskey_rrset);
+		if(ta->autr) {
+			struct autr_ta* p;
+			s += sizeof(*ta->autr);
+			if(ta->autr->file)
+				s += strlen(ta->autr->file);
+			for(p = ta->autr->keys; p; p=p->next) {
+				s += sizeof(*p) + p->rr_len;
+			}
+		}
+		lock_basic_unlock(&ta->lock);
 	}
+	lock_basic_unlock(&anchors->lock);
 	return s;
 }
 
@@ -1342,4 +1381,23 @@ anchors_find_any_noninsecure(struct val_
 	}
 	lock_basic_unlock(&anchors->lock);
 	return NULL;
+}
+
+void
+anchors_swap_tree(struct val_anchors* anchors, struct val_anchors* data)
+{
+	rbtree_type* oldtree;
+	rbtree_type oldprobe;
+
+	if(!anchors || !data)
+		return; /* If anchors is NULL, there is no validation. */
+
+	oldtree = anchors->tree;
+	oldprobe = anchors->autr->probe;
+
+	anchors->tree = data->tree;
+	anchors->autr->probe = data->autr->probe;
+
+	data->tree = oldtree;
+	data->autr->probe = oldprobe;
 }
Index: validator/val_anchor.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/val_anchor.h,v
diff -u -p -r1.6 val_anchor.h
--- validator/val_anchor.h	12 Apr 2024 15:45:24 -0000	1.6
+++ validator/val_anchor.h	30 Aug 2025 11:52:23 -0000
@@ -58,7 +58,7 @@ struct sldns_buffer;
  * on a trust anchor and look it up again to delete it.
  */
 struct val_anchors {
-	/** lock on trees */
+	/** lock on trees. It is locked in order after stubs. */
 	lock_basic_type lock;
 	/**
 	 * Anchors are store in this tree. Sort order is chosen, so that
@@ -247,5 +247,13 @@ int anchor_has_keytag(struct val_anchors
  * @return trust anchor or NULL. It is locked.
  */
 struct trust_anchor* anchors_find_any_noninsecure(struct val_anchors* anchors);
+
+/**
+ * Swap internal tree with preallocated entries.
+ * @param anchors: anchor storage.
+ * @param data: the data structure used to take elements from. This contains
+ * 	the old elements on return.
+ */
+void anchors_swap_tree(struct val_anchors* anchors, struct val_anchors* data);
 
 #endif /* VALIDATOR_VAL_ANCHOR_H */
Index: validator/val_neg.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/val_neg.c,v
diff -u -p -r1.10 val_neg.c
--- validator/val_neg.c	21 Feb 2025 13:20:40 -0000	1.10
+++ validator/val_neg.c	30 Aug 2025 11:52:23 -0000
@@ -1554,3 +1554,12 @@ val_neg_getmsg(struct val_neg_cache* neg
 	lock_basic_unlock(&neg->lock);
 	return msg;
 }
+
+void
+val_neg_adjust_size(struct val_neg_cache* neg, size_t max)
+{
+	lock_basic_lock(&neg->lock);
+	neg->max = max;
+	neg_make_space(neg, 0);
+	lock_basic_unlock(&neg->lock);
+}
Index: validator/val_neg.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/val_neg.h,v
diff -u -p -r1.6 val_neg.h
--- validator/val_neg.h	28 Oct 2020 11:31:07 -0000	1.6
+++ validator/val_neg.h	30 Aug 2025 11:52:23 -0000
@@ -299,4 +299,11 @@ struct val_neg_zone* neg_create_zone(str
  */
 void val_neg_zone_take_inuse(struct val_neg_zone* zone);
 
+/**
+ * Adjust the size of the negative cache.
+ * @param neg: negative cache
+ * @param max: new size for max mem.
+ */
+void val_neg_adjust_size(struct val_neg_cache* neg, size_t max);
+
 #endif /* VALIDATOR_VAL_NEG_H */
Index: validator/validator.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/validator.c,v
diff -u -p -r1.26 validator.c
--- validator/validator.c	21 Feb 2025 13:20:40 -0000	1.26
+++ validator/validator.c	30 Aug 2025 11:52:23 -0000
@@ -91,50 +91,98 @@ update_reason_bogus(struct reply_info* r
 
 /** fill up nsec3 key iterations config entry */
 static int
-fill_nsec3_iter(struct val_env* ve, char* s, int c)
+fill_nsec3_iter(size_t** keysize, size_t** maxiter, char* s, int c)
 {
 	char* e;
 	int i;
-	free(ve->nsec3_keysize);
-	free(ve->nsec3_maxiter);
-	ve->nsec3_keysize = (size_t*)calloc((size_t)c, sizeof(size_t));
-	ve->nsec3_maxiter = (size_t*)calloc((size_t)c, sizeof(size_t));
-	if(!ve->nsec3_keysize || !ve->nsec3_maxiter) {
+	*keysize = (size_t*)calloc((size_t)c, sizeof(size_t));
+	*maxiter = (size_t*)calloc((size_t)c, sizeof(size_t));
+	if(!*keysize || !*maxiter) {
+		free(*keysize);
+		*keysize = NULL;
+		free(*maxiter);
+		*maxiter = NULL;
 		log_err("out of memory");
 		return 0;
 	}
 	for(i=0; i<c; i++) {
-		ve->nsec3_keysize[i] = (size_t)strtol(s, &e, 10);
+		(*keysize)[i] = (size_t)strtol(s, &e, 10);
 		if(s == e) {
 			log_err("cannot parse: %s", s);
+			free(*keysize);
+			*keysize = NULL;
+			free(*maxiter);
+			*maxiter = NULL;
 			return 0;
 		}
 		s = e;
-		ve->nsec3_maxiter[i] = (size_t)strtol(s, &e, 10);
+		(*maxiter)[i] = (size_t)strtol(s, &e, 10);
 		if(s == e) {
 			log_err("cannot parse: %s", s);
+			free(*keysize);
+			*keysize = NULL;
+			free(*maxiter);
+			*maxiter = NULL;
 			return 0;
 		}
 		s = e;
-		if(i>0 && ve->nsec3_keysize[i-1] >= ve->nsec3_keysize[i]) {
+		if(i>0 && (*keysize)[i-1] >= (*keysize)[i]) {
 			log_err("nsec3 key iterations not ascending: %d %d",
-				(int)ve->nsec3_keysize[i-1], 
-				(int)ve->nsec3_keysize[i]);
+				(int)(*keysize)[i-1], (int)(*keysize)[i]);
+			free(*keysize);
+			*keysize = NULL;
+			free(*maxiter);
+			*maxiter = NULL;
 			return 0;
 		}
 		verbose(VERB_ALGO, "validator nsec3cfg keysz %d mxiter %d",
-			(int)ve->nsec3_keysize[i], (int)ve->nsec3_maxiter[i]);
+			(int)(*keysize)[i], (int)(*maxiter)[i]);
 	}
 	return 1;
 }
 
+int
+val_env_parse_key_iter(char* val_nsec3_key_iterations, size_t** keysize,
+	size_t** maxiter, int* keyiter_count)
+{
+	int c;
+	c = cfg_count_numbers(val_nsec3_key_iterations);
+	if(c < 1 || (c&1)) {
+		log_err("validator: unparsable or odd nsec3 key "
+			"iterations: %s", val_nsec3_key_iterations);
+		return 0;
+	}
+	*keyiter_count = c/2;
+	if(!fill_nsec3_iter(keysize, maxiter, val_nsec3_key_iterations, c/2)) {
+		log_err("validator: cannot apply nsec3 key iterations");
+		return 0;
+	}
+	return 1;
+}
+
+void
+val_env_apply_cfg(struct val_env* val_env, struct config_file* cfg,
+	size_t* keysize, size_t* maxiter, int keyiter_count)
+{
+	free(val_env->nsec3_keysize);
+	free(val_env->nsec3_maxiter);
+	val_env->nsec3_keysize = keysize;
+	val_env->nsec3_maxiter = maxiter;
+	val_env->nsec3_keyiter_count = keyiter_count;
+	val_env->bogus_ttl = (uint32_t)cfg->bogus_ttl;
+	val_env->date_override = cfg->val_date_override;
+	val_env->skew_min = cfg->val_sig_skew_min;
+	val_env->skew_max = cfg->val_sig_skew_max;
+	val_env->max_restart = cfg->val_max_restart;
+}
+
 /** apply config settings to validator */
 static int
 val_apply_cfg(struct module_env* env, struct val_env* val_env, 
 	struct config_file* cfg)
 {
-	int c;
-	val_env->bogus_ttl = (uint32_t)cfg->bogus_ttl;
+	size_t* keysize=NULL, *maxiter=NULL;
+	int keyiter_count = 0;
 	if(!env->anchors)
 		env->anchors = anchors_create();
 	if(!env->anchors) {
@@ -154,21 +202,11 @@ val_apply_cfg(struct module_env* env, st
 		log_err("validator: error in trustanchors config");
 		return 0;
 	}
-	val_env->date_override = cfg->val_date_override;
-	val_env->skew_min = cfg->val_sig_skew_min;
-	val_env->skew_max = cfg->val_sig_skew_max;
-	val_env->max_restart = cfg->val_max_restart;
-	c = cfg_count_numbers(cfg->val_nsec3_key_iterations);
-	if(c < 1 || (c&1)) {
-		log_err("validator: unparsable or odd nsec3 key "
-			"iterations: %s", cfg->val_nsec3_key_iterations);
-		return 0;
-	}
-	val_env->nsec3_keyiter_count = c/2;
-	if(!fill_nsec3_iter(val_env, cfg->val_nsec3_key_iterations, c/2)) {
-		log_err("validator: cannot apply nsec3 key iterations");
+	if(!val_env_parse_key_iter(cfg->val_nsec3_key_iterations,
+		&keysize, &maxiter, &keyiter_count)) {
 		return 0;
 	}
+	val_env_apply_cfg(val_env, cfg, keysize, maxiter, keyiter_count);
 	if (env->neg_cache)
 		val_env->neg_cache = env->neg_cache;
 	if(!val_env->neg_cache)
@@ -210,7 +248,7 @@ val_init(struct module_env* env, int id)
 		struct trust_anchor* anchor = anchors_find_any_noninsecure(
 			env->anchors);
 		if(anchor) {
-			char b[LDNS_MAX_DOMAINLEN+2];
+			char b[LDNS_MAX_DOMAINLEN];
 			dname_str(anchor->name, b);
 			log_warn("validator: disable-edns-do is enabled, but there is a trust anchor for '%s'. Since DNSSEC could not work, the disable-edns-do setting is turned off. Continuing without it.", b);
 			lock_basic_unlock(&anchor->lock);
@@ -2563,7 +2601,7 @@ processFinished(struct module_qstate* qs
 			if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo,
 				vq->orig_msg->rep, 0, qstate->prefetch_leeway,
 				0, qstate->region, qstate->query_flags,
-				qstate->qstarttime)) {
+				qstate->qstarttime, qstate->is_valrec)) {
 				log_err("out of memory caching validator results");
 			}
 		}
@@ -2572,7 +2610,8 @@ processFinished(struct module_qstate* qs
 		/* and this does not get prefetched, so no leeway */
 		if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo,
 			vq->orig_msg->rep, 1, 0, 0, qstate->region,
-			qstate->query_flags, qstate->qstarttime)) {
+			qstate->query_flags, qstate->qstarttime,
+			qstate->is_valrec)) {
 			log_err("out of memory caching validator results");
 		}
 	}
Index: validator/validator.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/validator.h,v
diff -u -p -r1.10 validator.h
--- validator/validator.h	21 Feb 2025 13:20:40 -0000	1.10
+++ validator/validator.h	30 Aug 2025 11:52:23 -0000
@@ -52,6 +52,7 @@ struct key_entry_key;
 struct val_neg_cache;
 struct config_strlist;
 struct comm_timer;
+struct config_file;
 
 /**
  * This is the TTL to use when a trust anchor fails to prime. A trust anchor
@@ -279,5 +280,27 @@ size_t val_get_mem(struct module_env* en
 
 /** Timer callback for msg signatures continue timer */
 void validate_suspend_timer_cb(void* arg);
+
+/**
+ * Parse the val_nsec3_key_iterations string.
+ * @param val_nsec3_key_iterations: the string with nsec3 iterations config.
+ * @param keysize: returns malloced key size array on success.
+ * @param maxiter: returns malloced max iterations array on success.
+ * @param keyiter_count: returns size of keysize and maxiter arrays.
+ * @return false if it does not parse correctly.
+ */
+int val_env_parse_key_iter(char* val_nsec3_key_iterations, size_t** keysize,
+	size_t** maxiter, int* keyiter_count);
+
+/**
+ * Apply config to validator env
+ * @param val_env: validator env.
+ * @param cfg: config
+ * @param keysize: nsec3 key size array.
+ * @param maxiter: nsec3 max iterations array.
+ * @param keyiter_count: size of keysize and maxiter arrays.
+ */
+void val_env_apply_cfg(struct val_env* val_env, struct config_file* cfg,
+	size_t* keysize, size_t* maxiter, int keyiter_count);
 
 #endif /* VALIDATOR_VALIDATOR_H */