Index | Thread | Search

From:
Stuart Henderson <stu@spacehopper.org>
Subject:
unbound 1.25.2
To:
tech <tech@openbsd.org>
Date:
Thu, 23 Jul 2026 16:29:50 +0100

Download raw body.

Thread
  • Stuart Henderson:

    unbound 1.25.2

fixes listed at
https://nlnetlabs.nl/projects/unbound/download/#unbound-1-25-2

quite a few relate to things we don't enable (QUIC, DNSCrypt) but
some are more generally applicable.

hooray, same autoconf version so much less churn than usual!

Index: daemon/daemon.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/daemon.c,v
diff -u -p -r1.29 daemon.c
--- daemon/daemon.c	26 May 2026 11:14:11 -0000	1.29
+++ daemon/daemon.c	23 Jul 2026 15:23:53 -0000
@@ -79,6 +79,7 @@
 #include "util/tcp_conn_limit.h"
 #include "util/edns.h"
 #include "services/listen_dnsport.h"
+#include "services/outside_network.h"
 #include "services/cache/rrset.h"
 #include "services/cache/infra.h"
 #include "services/localzone.h"
@@ -813,6 +814,10 @@ daemon_create_workers(struct daemon* dae
 		fatal_exit("out of memory during daemon init");
 	numport = daemon_get_shufport(daemon, shufport);
 	verbose(VERB_ALGO, "total of %d outgoing ports available", numport);
+	if(!(daemon->shared_ports = shared_ports_create(daemon->cfg->out_ifs,
+		daemon->cfg->num_out_ifs, daemon->cfg->do_ip4,
+		daemon->cfg->do_ip6, shufport, numport)))
+		fatal_exit("could not setup shared ports: out of memory");
 
 #ifdef HAVE_NGTCP2
 	if (cfg_has_quic(daemon->cfg)) {
@@ -843,10 +848,7 @@ daemon_create_workers(struct daemon* dae
 #endif
 	}
 	for(i=0; i<daemon->num; i++) {
-		if(!(daemon->workers[i] = worker_create(daemon, i,
-			shufport+numport*i/daemon->num, 
-			numport*(i+1)/daemon->num - numport*i/daemon->num)))
-			/* the above is not ports/numthr, due to rounding */
+		if(!(daemon->workers[i] = worker_create(daemon, i)))
 			fatal_exit("could not create worker");
 	}
 	/* create per-worker alloc caches if not reusing existing ones. */
@@ -1204,6 +1206,8 @@ daemon_cleanup(struct daemon* daemon)
 	if(!daemon->reuse_cache || daemon->need_to_exit)
 		daemon_clear_allocs(daemon);
 	daemon->num = 0;
+	shared_ports_delete(daemon->shared_ports);
+	daemon->shared_ports = NULL;
 #ifdef USE_DNSTAP
 	dt_delete(daemon->dtenv);
 	daemon->dtenv = NULL;
Index: daemon/daemon.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/daemon.h,v
diff -u -p -r1.12 daemon.h
--- daemon/daemon.h	26 May 2026 11:14:11 -0000	1.12
+++ daemon/daemon.h	23 Jul 2026 15:23:53 -0000
@@ -62,6 +62,7 @@ struct doq_table;
 struct cookie_secrets;
 struct fast_reload_thread;
 struct fast_reload_printq;
+struct shared_ports;
 
 #include "dnstap/dnstap_config.h"
 #ifdef USE_DNSTAP
@@ -97,6 +98,8 @@ struct daemon {
 	int rc_port;
 	/** listening ports for remote control */
 	struct listen_port* rc_ports;
+	/** the shared ports structure, with random ports numbers. */
+	struct shared_ports* shared_ports;
 	/** remote control connections management (for first worker) */
 	struct daemon_remote* rc;
 	/** ssl context for listening to dnstcp over ssl */
Index: daemon/remote.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/remote.c,v
diff -u -p -r1.45 remote.c
--- daemon/remote.c	26 May 2026 11:14:11 -0000	1.45
+++ daemon/remote.c	23 Jul 2026 15:23:53 -0000
@@ -1658,6 +1658,14 @@ do_view_data_add(RES* ssl, struct worker
 			ssl_printf(ssl,"error out of memory\n");
 			return;
 		}
+		if(!v->isfirst) {
+			/* Global local-zone is not used for this view,
+			 * therefore add defaults to this view-specific
+			 * local-zone. */
+			struct config_file lz_cfg;
+			memset(&lz_cfg, 0, sizeof(lz_cfg));
+			local_zone_enter_defaults(v->local_zones, &lz_cfg);
+		}
 	}
 	do_data_add(ssl, v->local_zones, arg2);
 	lock_rw_unlock(&v->lock);
@@ -1682,6 +1690,14 @@ do_view_datas_add(struct daemon_remote* 
 			lock_rw_unlock(&v->lock);
 			ssl_printf(ssl,"error out of memory\n");
 			return;
+		}
+		if(!v->isfirst) {
+			/* Global local-zone is not used for this view,
+			 * therefore add defaults to this view-specific
+			 * local-zone. */
+			struct config_file lz_cfg;
+			memset(&lz_cfg, 0, sizeof(lz_cfg));
+			local_zone_enter_defaults(v->local_zones, &lz_cfg);
 		}
 	}
 	/* put the view name in the command buf */
Index: daemon/worker.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/worker.c,v
diff -u -p -r1.45 worker.c
--- daemon/worker.c	26 May 2026 11:14:11 -0000	1.45
+++ daemon/worker.c	23 Jul 2026 15:23:53 -0000
@@ -1550,6 +1550,7 @@ worker_handle_request(struct comm_point*
 				return 0;
 			}
 			query_error(c->buffer, LDNS_RCODE_FORMERR, 0);
+			sldns_buffer_copy(c->dnscrypt_buffer, c->buffer);
 			return 1;
 		}
 		dname_str(qinfo.qname, buf);
@@ -1568,6 +1569,7 @@ worker_handle_request(struct comm_point*
 			query_error(c->buffer, LDNS_RCODE_SERVFAIL,
 				qinfo.qname_len);
 			worker->stats.num_query_dnscrypt_cleartext++;
+			sldns_buffer_copy(c->dnscrypt_buffer, c->buffer);
 			return 1;
 		}
 		worker->stats.num_query_dnscrypt_cert++;
@@ -1828,7 +1830,13 @@ worker_handle_request(struct comm_point*
 		server_stats_insquery(&worker->stats, c, qinfo.qtype,
 			qinfo.qclass, &edns, repinfo);
 	if(c->type != comm_udp)
+#ifdef USE_DNSCRYPT
+		edns.udp_size = (c->dnscrypt && repinfo->is_dnscrypted)
+			? sldns_buffer_capacity(c->buffer) - DNSCRYPT_REPLY_HEADER_SIZE
+			: 65535;
+#else
 		edns.udp_size = 65535; /* max size for TCP replies */
+#endif
 	if(qinfo.qclass == LDNS_RR_CLASS_CH && answer_chaos(worker, &qinfo,
 		&edns, repinfo, c->buffer)) {
 		regional_free_all(worker->scratchpad);
@@ -2112,7 +2120,7 @@ send_reply_rc:
 		}
 	}
 #ifdef USE_DNSCRYPT
-	if(!dnsc_handle_uncurved_request(repinfo)) {
+	if(!dnsc_handle_uncurved_request(repinfo, c->buffer)) {
 		return 0;
 	}
 #endif
@@ -2225,23 +2233,16 @@ void worker_probe_timer_cb(void* arg)
 }
 
 struct worker*
-worker_create(struct daemon* daemon, int id, int* ports, int n)
+worker_create(struct daemon* daemon, int id)
 {
 	unsigned int seed;
 	struct worker* worker = (struct worker*)calloc(1,
 		sizeof(struct worker));
 	if(!worker)
 		return NULL;
-	worker->numports = n;
-	worker->ports = (int*)memdup(ports, sizeof(int)*n);
-	if(!worker->ports) {
-		free(worker);
-		return NULL;
-	}
 	worker->daemon = daemon;
 	worker->thread_num = id;
 	if(!(worker->cmd = tube_create())) {
-		free(worker->ports);
 		free(worker);
 		return NULL;
 	}
@@ -2249,7 +2250,6 @@ worker_create(struct daemon* daemon, int
 	if(!(worker->rndstate = ub_initstate(daemon->rand))) {
 		log_err("could not init random numbers.");
 		tube_delete(worker->cmd);
-		free(worker->ports);
 		free(worker);
 		return NULL;
 	}
@@ -2348,14 +2348,14 @@ worker_init(struct worker* worker, struc
 		cfg->out_ifs, cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6,
 		cfg->do_tcp?cfg->outgoing_num_tcp:0, cfg->ip_dscp,
 		worker->daemon->env->infra_cache, worker->rndstate,
-		cfg->use_caps_bits_for_id, worker->ports, worker->numports,
+		cfg->use_caps_bits_for_id,
 		cfg->unwanted_threshold, cfg->outgoing_tcp_mss,
 		&worker_alloc_cleanup, worker,
 		cfg->do_udp || cfg->udp_upstream_without_downstream,
 		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);
+		cfg->tcp_auth_query_timeout, worker->daemon->shared_ports);
 	if(!worker->back) {
 		log_err("could not create outgoing sockets");
 		worker_delete(worker);
@@ -2506,7 +2506,6 @@ worker_delete(struct worker* worker)
 	tube_delete(worker->cmd);
 	comm_timer_delete(worker->stat_timer);
 	comm_timer_delete(worker->env.probe_timer);
-	free(worker->ports);
 	if(worker->thread_num == 0) {
 #ifdef UB_ON_WINDOWS
 		wsvc_desetup_worker(worker);
@@ -2634,6 +2633,11 @@ void libworker_bg_done_cb(void* ATTR_UNU
 void libworker_event_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode),
 	sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s),
 	char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited))
+{
+	log_assert(0);
+}
+
+void libworker_alloc_cleanup(void* ATTR_UNUSED(arg))
 {
 	log_assert(0);
 }
Index: daemon/worker.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/daemon/worker.h,v
diff -u -p -r1.10 worker.h
--- daemon/worker.h	31 Aug 2025 21:41:09 -0000	1.10
+++ daemon/worker.h	23 Jul 2026 15:23:53 -0000
@@ -104,10 +104,6 @@ struct worker {
 	struct listen_dnsport* front;
 	/** the backside outside network interface to the auth servers */
 	struct outside_network* back;
-	/** ports to be used by this worker. */
-	int* ports;
-	/** number of ports for this worker */
-	int numports;
 	/** the signal handler */
 	struct comm_signal* comsig;
 	/** commpoint to listen to commands. */
@@ -146,11 +142,9 @@ struct worker {
  * with backpointers only. Use worker_init on it later.
  * @param daemon: the daemon that this worker thread is part of.
  * @param id: the thread number from 0.. numthreads-1.
- * @param ports: the ports it is allowed to use, array.
- * @param n: the number of ports.
  * @return: the new worker or NULL on alloc failure.
  */
-struct worker* worker_create(struct daemon* daemon, int id, int* ports, int n);
+struct worker* worker_create(struct daemon* daemon, int id);
 
 /**
  * Initialize worker.
Index: dnscrypt/dnscrypt.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/dnscrypt/dnscrypt.c,v
diff -u -p -r1.9 dnscrypt.c
--- dnscrypt/dnscrypt.c	26 May 2026 11:14:11 -0000	1.9
+++ dnscrypt/dnscrypt.c	23 Jul 2026 15:23:53 -0000
@@ -474,10 +474,18 @@ dnscrypt_server_curve(const dnsccert *ce
     uint8_t *const buf = sldns_buffer_begin(buffer);
     size_t len = sldns_buffer_limit(buffer);
 
+    if(len + DNSCRYPT_REPLY_HEADER_SIZE > sldns_buffer_capacity(buffer))
+	return -1;
+    sldns_buffer_clear(buffer);
+
     if(udp){
         if (max_len > max_reply_size)
             max_len = max_reply_size;
     }
+    if(max_len > sldns_buffer_capacity(buffer))
+	max_len = sldns_buffer_capacity(buffer);
+    if(max_len > 65535)
+	    max_len = 65535;
 
 
     memcpy(nonce, client_nonce, crypto_box_HALF_NONCEBYTES);
@@ -520,6 +528,7 @@ dnscrypt_server_curve(const dnsccert *ce
                           DNSCRYPT_MAGIC_HEADER_LEN,
                           nonce,
                           crypto_box_NONCEBYTES);
+    sldns_buffer_flip(buffer);
     sldns_buffer_set_limit(buffer, len + DNSCRYPT_REPLY_HEADER_SIZE);
     return 0;
 }
@@ -663,6 +672,8 @@ dnsc_find_cert(struct dnsc_env* dnscenv,
 	}
 	dnscrypt_header = (struct dnscrypt_query_header *)sldns_buffer_begin(buffer);
 	for (i = 0U; i < dnscenv->signed_certs_count; i++) {
+		if(!certs[i].keypair)
+			continue;
 		if (memcmp(certs[i].magic_query, dnscrypt_header->magic_query,
                    DNSCRYPT_MAGIC_HEADER_LEN) == 0) {
 			return &certs[i];
@@ -804,6 +815,7 @@ dnsc_parse_keys(struct dnsc_env *env, st
 		sizeof *env->keypairs);
 	env->certs = sodium_allocarray(env->signed_certs_count,
 		sizeof *env->certs);
+	memset(env->certs, 0, env->signed_certs_count * sizeof(*env->certs));
 
 	cert_id = 0U;
 	keypair_id = 0U;
@@ -912,12 +924,13 @@ dnsc_handle_curved_request(struct dnsc_e
 }
 
 int
-dnsc_handle_uncurved_request(struct comm_reply *repinfo)
+dnsc_handle_uncurved_request(struct comm_reply *repinfo,
+	struct sldns_buffer* buffer)
 {
     if(!repinfo->c->dnscrypt) {
         return 1;
     }
-    sldns_buffer_copy(repinfo->c->dnscrypt_buffer, repinfo->c->buffer);
+    sldns_buffer_copy(repinfo->c->dnscrypt_buffer, buffer);
     if(!repinfo->is_dnscrypted) {
         return 1;
     }
@@ -963,11 +976,18 @@ dnsc_create(void)
 int
 dnsc_apply_cfg(struct dnsc_env *env, struct config_file *cfg)
 {
+    int nkeys;
     if(dnsc_parse_certs(env, cfg) <= 0) {
         fatal_exit("dnsc_apply_cfg: no cert file loaded");
     }
-    if(dnsc_parse_keys(env, cfg) <= 0) {
+    nkeys = dnsc_parse_keys(env, cfg);
+    if(nkeys <= 0) {
         fatal_exit("dnsc_apply_cfg: no key file loaded");
+    }
+    if((size_t)nkeys < env->signed_certs_count) {
+	fatal_exit("dnsc_apply_cfg: %u dnscrypt-provider-cert file(s) have no "
+		"matching dnscrypt-secret-key",
+		(unsigned)(env->signed_certs_count - (size_t)nkeys));
     }
     randombytes_buf(env->hash_key, sizeof env->hash_key);
     env->provider_name = cfg->dnscrypt_provider;
Index: dnscrypt/dnscrypt.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/dnscrypt/dnscrypt.h,v
diff -u -p -r1.4 dnscrypt.h
--- dnscrypt/dnscrypt.h	23 Feb 2022 12:04:05 -0000	1.4
+++ dnscrypt/dnscrypt.h	23 Jul 2026 15:23:53 -0000
@@ -128,7 +128,8 @@ int dnsc_handle_curved_request(struct dn
  * \return 0 in case of failure.
  */
 
-int dnsc_handle_uncurved_request(struct comm_reply *repinfo);
+int dnsc_handle_uncurved_request(struct comm_reply *repinfo,
+	struct sldns_buffer* buffer);
 
 /**
  * Computes the size of the shared secret cache entry.
Index: dnstap/unbound-dnstap-socket.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/dnstap/unbound-dnstap-socket.c,v
diff -u -p -r1.5 unbound-dnstap-socket.c
--- dnstap/unbound-dnstap-socket.c	26 May 2026 11:14:11 -0000	1.5
+++ dnstap/unbound-dnstap-socket.c	23 Jul 2026 15:23:53 -0000
@@ -1735,6 +1735,11 @@ void libworker_event_done_cb(void* ATTR_
 	log_assert(0);
 }
 
+void libworker_alloc_cleanup(void* ATTR_UNUSED(arg))
+{
+	log_assert(0);
+}
+
 int context_query_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b))
 {
 	log_assert(0);
Index: doc/README
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/README,v
diff -u -p -r1.46 README
--- doc/README	26 May 2026 11:14:11 -0000	1.46
+++ doc/README	23 Jul 2026 15:23:53 -0000
@@ -1,4 +1,4 @@
-README for Unbound 1.25.1
+README for Unbound 1.25.2
 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.51 example.conf.in
--- doc/example.conf.in	26 May 2026 11:14:11 -0000	1.51
+++ doc/example.conf.in	23 Jul 2026 15:23:53 -0000
@@ -1,7 +1,7 @@
 #
 # Example configuration file.
 #
-# See unbound.conf(5) man page, version 1.25.1.
+# See unbound.conf(5) man page, version 1.25.2.
 #
 # this is a comment.
 
Index: doc/libunbound.3.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/libunbound.3.in,v
diff -u -p -r1.49 libunbound.3.in
--- doc/libunbound.3.in	26 May 2026 11:14:11 -0000	1.49
+++ doc/libunbound.3.in	23 Jul 2026 15:23:53 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "LIBUNBOUND" "3" "May 20, 2026" "1.25.1" "Unbound"
+.TH "LIBUNBOUND" "3" "Jul 22, 2026" "1.25.2" "Unbound"
 .SH NAME
-libunbound \- Unbound DNS validating resolver 1.25.1 functions.
+libunbound \- Unbound DNS validating resolver 1.25.2 functions.
 .SH SYNOPSIS
 .sp
 \fB#include <unbound.h>\fP
Index: doc/unbound-anchor.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-anchor.8.in,v
diff -u -p -r1.48 unbound-anchor.8.in
--- doc/unbound-anchor.8.in	26 May 2026 11:14:11 -0000	1.48
+++ doc/unbound-anchor.8.in	23 Jul 2026 15:23:53 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND-ANCHOR" "8" "May 20, 2026" "1.25.1" "Unbound"
+.TH "UNBOUND-ANCHOR" "8" "Jul 22, 2026" "1.25.2" "Unbound"
 .SH NAME
-unbound-anchor \- Unbound 1.25.1 anchor utility.
+unbound-anchor \- Unbound 1.25.2 anchor utility.
 .SH SYNOPSIS
 .sp
 \fBunbound\-anchor\fP [\fBopts\fP]
Index: doc/unbound-checkconf.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-checkconf.8.in,v
diff -u -p -r1.48 unbound-checkconf.8.in
--- doc/unbound-checkconf.8.in	26 May 2026 11:14:11 -0000	1.48
+++ doc/unbound-checkconf.8.in	23 Jul 2026 15:23:53 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND-CHECKCONF" "8" "May 20, 2026" "1.25.1" "Unbound"
+.TH "UNBOUND-CHECKCONF" "8" "Jul 22, 2026" "1.25.2" "Unbound"
 .SH NAME
-unbound-checkconf \- Check Unbound 1.25.1 configuration file for errors.
+unbound-checkconf \- Check Unbound 1.25.2 configuration file for errors.
 .SH SYNOPSIS
 .sp
 \fBunbound\-checkconf\fP [\fB\-hf\fP] [\fB\-o option\fP] [cfgfile]
Index: doc/unbound-control.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-control.8.in,v
diff -u -p -r1.50 unbound-control.8.in
--- doc/unbound-control.8.in	26 May 2026 11:14:11 -0000	1.50
+++ doc/unbound-control.8.in	23 Jul 2026 15:23:53 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND-CONTROL" "8" "May 20, 2026" "1.25.1" "Unbound"
+.TH "UNBOUND-CONTROL" "8" "Jul 22, 2026" "1.25.2" "Unbound"
 .SH NAME
-unbound-control \- Unbound 1.25.1 remote server control utility.
+unbound-control \- Unbound 1.25.2 remote server control utility.
 .SH SYNOPSIS
 .sp
 \fBunbound\-control\fP [\fB\-hq\fP] [\fB\-c cfgfile\fP] [\fB\-s server\fP] command
Index: doc/unbound-host.1.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-host.1.in,v
diff -u -p -r1.50 unbound-host.1.in
--- doc/unbound-host.1.in	26 May 2026 11:14:11 -0000	1.50
+++ doc/unbound-host.1.in	23 Jul 2026 15:23:53 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND-HOST" "1" "May 20, 2026" "1.25.1" "Unbound"
+.TH "UNBOUND-HOST" "1" "Jul 22, 2026" "1.25.2" "Unbound"
 .SH NAME
-unbound-host \- Unbound 1.25.1 DNS lookup utility.
+unbound-host \- Unbound 1.25.2 DNS lookup utility.
 .SH SYNOPSIS
 .sp
 \fBunbound\-host\fP [\fB\-C configfile\fP] [\fB\-vdhr46D\fP] [\fB\-c class\fP]
Index: doc/unbound.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.8.in,v
diff -u -p -r1.51 unbound.8.in
--- doc/unbound.8.in	26 May 2026 11:14:11 -0000	1.51
+++ doc/unbound.8.in	23 Jul 2026 15:23:53 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND" "8" "May 20, 2026" "1.25.1" "Unbound"
+.TH "UNBOUND" "8" "Jul 22, 2026" "1.25.2" "Unbound"
 .SH NAME
-unbound \- Unbound DNS validating resolver 1.25.1.
+unbound \- Unbound DNS validating resolver 1.25.2.
 .SH SYNOPSIS
 .sp
 \fBunbound\fP [\fB\-hdpVv\fP] [\fB\-c <cfgfile>\fP]
Index: doc/unbound.conf.5.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.conf.5.in,v
diff -u -p -r1.56 unbound.conf.5.in
--- doc/unbound.conf.5.in	26 May 2026 11:14:11 -0000	1.56
+++ doc/unbound.conf.5.in	23 Jul 2026 15:23:53 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND.CONF" "5" "May 20, 2026" "1.25.1" "Unbound"
+.TH "UNBOUND.CONF" "5" "Jul 22, 2026" "1.25.2" "Unbound"
 .SH NAME
-unbound.conf \- Unbound 1.25.1 configuration file.
+unbound.conf \- Unbound 1.25.2 configuration file.
 .SH SYNOPSIS
 .sp
 \fBunbound.conf\fP
Index: doc/unbound.conf.rst
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.conf.rst,v
diff -u -p -r1.5 unbound.conf.rst
--- doc/unbound.conf.rst	26 May 2026 11:14:11 -0000	1.5
+++ doc/unbound.conf.rst	23 Jul 2026 15:23:53 -0000
@@ -2055,6 +2055,13 @@ These options are part of the ``server:`
     flushing away any poison.
     A value of 10 million is suggested.
 
+    It is useful to add 0.0.0.0/8 and '::' to the
+    :ref:`do-not-query-address<unbound.conf.do-not-query-address>` list.
+    Otherwise they may be answered, from localhost, and the different source
+    makes an unwanted reply that unnecessarily ticks up.
+    The :ref:`do-not-query-localhost<unbound.conf.do-not-query-localhost>`
+    option includes them, the zero subnets, when it is enabled.
+
     Default: 0 (disabled)
 
 
Index: iterator/iter_donotq.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_donotq.c,v
diff -u -p -r1.1.1.2 iter_donotq.c
--- iterator/iter_donotq.c	16 Mar 2014 11:38:24 -0000	1.1.1.2
+++ iterator/iter_donotq.c	23 Jul 2026 15:23:53 -0000
@@ -132,6 +132,18 @@ donotq_apply_cfg(struct iter_donotq* dq,
 		if(cfg->do_ip6) {
 			if(!donotq_str_cfg(dq, "::1"))
 				return 0;
+			if(!donotq_str_cfg(dq, "::ffff:127.0.0.0/104"))
+				return 0;
+		}
+		/* RFC 1122 3.2.1.3 / RFC 6890 / RFC 4291 2.5.2: not valid as
+		 * destination; on Linux these route to the local host. */
+		if(!donotq_str_cfg(dq, "0.0.0.0/8"))
+			return 0;
+		if(cfg->do_ip6) {
+			if(!donotq_str_cfg(dq, "::"))
+				return 0;
+			if(!donotq_str_cfg(dq, "::ffff:0:0/96"))
+				return 0;
 		}
 	}
 	addr_tree_init_parents(&dq->tree);
Index: iterator/iterator.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/iterator/iterator.c,v
diff -u -p -r1.42 iterator.c
--- iterator/iterator.c	26 May 2026 11:14:11 -0000	1.42
+++ iterator/iterator.c	23 Jul 2026 15:23:53 -0000
@@ -81,7 +81,8 @@ 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);
+static void target_count_increase_nx(struct module_qstate* qstate,
+	struct iter_qstate* iq, int num);
 
 int 
 iter_init(struct module_env* env, int id)
@@ -250,7 +251,7 @@ error_supers(struct module_qstate* qstat
 		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);
+			target_count_increase_nx(super, super_iq, 1);
 		}
 	}
 	if(qstate->qinfo.qtype == LDNS_RR_TYPE_NS) {
@@ -734,7 +735,7 @@ is_caps_whitelisted(struct iter_env* ie,
  * created for the parent query.
  */
 static void
-target_count_create(struct iter_qstate* iq)
+target_count_create(struct module_qstate* qstate, struct iter_qstate* iq)
 {
 	if(!iq->target_count) {
 		iq->target_count = (int*)calloc(TARGET_COUNT_MAX, sizeof(int));
@@ -742,33 +743,57 @@ target_count_create(struct iter_qstate* 
 		if(iq->target_count) {
 			iq->target_count[TARGET_COUNT_REF] = 1;
 			iq->nxns_dp = (uint8_t**)calloc(1, sizeof(uint8_t*));
+			/* continue global quota from where it was. */
+			if(qstate->global_quota_reached >
+				iq->target_count[TARGET_COUNT_GLOBAL_QUOTA])
+				iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] =
+					qstate->global_quota_reached;
 		}
 	}
 }
 
 static void
-target_count_increase(struct iter_qstate* iq, int num)
+target_count_store(struct module_qstate* qstate, struct iter_qstate* iq)
 {
-	target_count_create(iq);
+	if(iq->target_count) {
+		/* By storing the global quota counter, it stays
+		 * there to be picked up if the module is restarted,
+		 * eg. due to a validator retry, and then the
+		 * target_count_create routine picks it up. */
+		if(iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] >
+			qstate->global_quota_reached)
+			qstate->global_quota_reached =
+			  iq->target_count[TARGET_COUNT_GLOBAL_QUOTA];
+	}
+}
+
+static void
+target_count_increase(struct module_qstate* qstate,
+	struct iter_qstate* iq, int num)
+{
+	target_count_create(qstate, iq);
 	if(iq->target_count)
 		iq->target_count[TARGET_COUNT_QUERIES] += num;
 	iq->dp_target_count++;
 }
 
 static void
-target_count_increase_nx(struct iter_qstate* iq, int num)
+target_count_increase_nx(struct module_qstate* qstate,
+	struct iter_qstate* iq, int num)
 {
-	target_count_create(iq);
+	target_count_create(qstate, iq);
 	if(iq->target_count)
 		iq->target_count[TARGET_COUNT_NX] += num;
 }
 
 static void
-target_count_increase_global_quota(struct iter_qstate* iq, int num)
+target_count_increase_global_quota(struct module_qstate* qstate,
+	struct iter_qstate* iq, int num)
 {
-	target_count_create(iq);
+	target_count_create(qstate, iq);
 	if(iq->target_count)
 		iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] += num;
+	target_count_store(qstate, iq);
 }
 
 /**
@@ -861,7 +886,7 @@ generate_sub_request(uint8_t* qname, siz
 		subiq = (struct iter_qstate*)subq->minfo[id];
 		memset(subiq, 0, sizeof(*subiq));
 		subiq->num_target_queries = 0;
-		target_count_create(iq);
+		target_count_create(qstate, iq);
 		subiq->target_count = iq->target_count;
 		if(iq->target_count) {
 			iq->target_count[TARGET_COUNT_REF] ++; /* extra reference */
@@ -2234,7 +2259,7 @@ processLastResort(struct module_qstate* 
 			return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL);
 		}
 		iq->num_target_queries += qs;
-		target_count_increase(iq, qs);
+		target_count_increase(qstate, iq, qs);
 		if(qs != 0) {
 			qstate->ext_state[id] = module_wait_subquery;
 			return 0; /* and wait for them */
@@ -2290,7 +2315,7 @@ processLastResort(struct module_qstate* 
 				 * lookups at a time. */
 				verbose(VERB_ALGO, "try parent-side glue lookup");
 				iq->num_target_queries += query_count;
-				target_count_increase(iq, query_count);
+				target_count_increase(qstate, iq, query_count);
 				qstate->ext_state[id] = module_wait_subquery;
 				return 0;
 			}
@@ -2310,7 +2335,7 @@ processLastResort(struct module_qstate* 
 		if(query_count != 0) { /* suspend to await results */
 			verbose(VERB_ALGO, "try parent-side glue lookup");
 			iq->num_target_queries += query_count;
-			target_count_increase(iq, query_count);
+			target_count_increase(qstate, iq, query_count);
 			qstate->ext_state[id] = module_wait_subquery;
 			return 0;
 		}
@@ -2788,7 +2813,7 @@ processQueryTargets(struct module_qstate
 			return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL);
 		}
 		iq->num_target_queries += extra;
-		target_count_increase(iq, extra);
+		target_count_increase(qstate, iq, extra);
 		if(iq->num_target_queries > 0) {
 			/* wait to get all targets, we want to try em */
 			verbose(VERB_ALGO, "wait for all targets for fallback");
@@ -2839,7 +2864,7 @@ processQueryTargets(struct module_qstate
 		/* errors ignored, these targets are not strictly necessary for
 		 * this result, we do not have to reply with SERVFAIL */
 		iq->num_target_queries += extra;
-		target_count_increase(iq, extra);
+		target_count_increase(qstate, iq, extra);
 	}
 
 	/* Add the current set of unused targets to our queue. */
@@ -2962,7 +2987,7 @@ processQueryTargets(struct module_qstate
 						LDNS_RCODE_SERVFAIL);
 				}
 				iq->num_target_queries += qs;
-				target_count_increase(iq, qs);
+				target_count_increase(qstate, iq, qs);
 			}
 			/* Since a target query might have been made, we 
 			 * need to check again. */
@@ -3022,7 +3047,7 @@ processQueryTargets(struct module_qstate
 		* this result, we do not have to reply with SERVFAIL */
 		if(extra > 0) {
 			iq->num_target_queries += extra;
-			target_count_increase(iq, extra);
+			target_count_increase(qstate, iq, extra);
 			check_waiting_queries(iq, qstate, id);
 			/* undo qname minimise step because we'll get back here
 			 * to do it again */
@@ -3035,7 +3060,7 @@ processQueryTargets(struct module_qstate
 		}
 	}
 
-	target_count_increase_global_quota(iq, 1);
+	target_count_increase_global_quota(qstate, iq, 1);
 	if(iq->target_count && iq->target_count[TARGET_COUNT_GLOBAL_QUOTA]
 		> MAX_GLOBAL_QUOTA) {
 		char s[LDNS_MAX_DOMAINLEN];
@@ -3879,7 +3904,7 @@ processTargetResponse(struct module_qsta
 			/* no new addresses, increase the nxns counter, like
 			 * this could be a list of wildcards with no new
 			 * addresses */
-			target_count_increase_nx(foriq, 1);
+			target_count_increase_nx(qstate, foriq, 1);
 		}
 		verbose(VERB_ALGO, "added target response");
 		delegpt_log(VERB_ALGO, foriq->dp);
@@ -3891,7 +3916,7 @@ processTargetResponse(struct module_qsta
 			dpns->resolved = 1; /* fail the target */
 			/* do not count cached answers */
 			if(qstate->reply_origin && qstate->reply_origin->len != 0) {
-				target_count_increase_nx(foriq, 1);
+				target_count_increase_nx(qstate, foriq, 1);
 			}
 		}
 	}
@@ -4116,6 +4141,7 @@ processFinished(struct module_qstate* qs
 			iter_store_parentside_neg(qstate->env, &qstate->qinfo,
 				iq->deleg_msg?iq->deleg_msg->rep:
 				(iq->response?iq->response->rep:NULL));
+	target_count_store(qstate, iq);
 	if(!iq->response) {
 		verbose(VERB_ALGO, "No response is set, servfail");
 		errinf(qstate, "(no response found at query finish)");
@@ -4531,6 +4557,7 @@ iter_clear(struct module_qstate* qstate,
 	iq = (struct iter_qstate*)qstate->minfo[id];
 	if(iq) {
 		outbound_list_clear(&iq->outlist);
+		target_count_store(qstate, iq);
 		if(iq->target_count && --iq->target_count[TARGET_COUNT_REF] == 0) {
 			free(iq->target_count);
 			if(*iq->nxns_dp) free(*iq->nxns_dp);
Index: libunbound/libworker.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/libunbound/libworker.c,v
diff -u -p -r1.36 libworker.c
--- libunbound/libworker.c	26 Sep 2025 07:32:37 -0000	1.36
+++ libunbound/libworker.c	23 Jul 2026 15:23:53 -0000
@@ -105,6 +105,7 @@ libworker_delete_env(struct libworker* w
 	SSL_CTX_free(w->sslctx);
 #endif
 	outside_network_delete(w->back);
+	shared_ports_delete(w->shared_ports);
 }
 
 /** delete libworker struct */
@@ -219,17 +220,25 @@ libworker_setup(struct ub_ctx* ctx, int 
 		libworker_delete(w);
 		return NULL;
 	}
+	if(!(w->shared_ports = shared_ports_create(cfg->out_ifs,
+		cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6, ports, numports))) {
+		if(!w->is_bg || w->is_bg_thread) {
+			lock_basic_unlock(&ctx->cfglock);
+		}
+		libworker_delete(w);
+		return NULL;
+	}
 	w->back = outside_network_create(w->base, cfg->msg_buffer_size,
 		(size_t)cfg->outgoing_num_ports, cfg->out_ifs,
 		cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6, 
 		cfg->do_tcp?cfg->outgoing_num_tcp:0, cfg->ip_dscp,
 		w->env->infra_cache, w->env->rnd, cfg->use_caps_bits_for_id,
-		ports, numports, cfg->unwanted_threshold,
+		cfg->unwanted_threshold,
 		cfg->outgoing_tcp_mss, &libworker_alloc_cleanup, w,
 		cfg->do_udp || cfg->udp_upstream_without_downstream, w->sslctx,
 		cfg->delay_close, cfg->tls_use_sni, NULL, cfg->udp_connect,
 		cfg->max_reuse_tcp_queries, cfg->tcp_reuse_timeout,
-		cfg->tcp_auth_query_timeout);
+		cfg->tcp_auth_query_timeout, w->shared_ports);
 	w->env->outnet = w->back;
 	if(!w->is_bg || w->is_bg_thread) {
 		lock_basic_unlock(&ctx->cfglock);
Index: libunbound/libworker.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/libunbound/libworker.h,v
diff -u -p -r1.4 libworker.h
--- libunbound/libworker.h	17 Sep 2018 09:46:12 -0000	1.4
+++ libunbound/libworker.h	23 Jul 2026 15:23:53 -0000
@@ -60,6 +60,7 @@ struct tube;
 struct sldns_buffer;
 struct ub_event_base;
 struct query_info;
+struct shared_ports;
 
 /** 
  * The library-worker status structure
@@ -84,6 +85,8 @@ struct libworker {
 	struct comm_base* base;
 	/** the backside outside network interface to the auth servers */
 	struct outside_network* back;
+	/** shared ports structure */
+	struct shared_ports* shared_ports;
 	/** random() table for this worker. */
 	struct ub_randstate* rndstate;
 	/** sslcontext for SSL wrapped DNS over TCP queries */
Index: respip/respip.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/respip/respip.c,v
diff -u -p -r1.18 respip.c
--- respip/respip.c	26 May 2026 11:14:11 -0000	1.18
+++ respip/respip.c	23 Jul 2026 15:23:53 -0000
@@ -1114,7 +1114,13 @@ respip_operate(struct module_qstate* qst
 		if((qstate->qinfo.qtype == LDNS_RR_TYPE_A ||
 			qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA ||
 			qstate->qinfo.qtype == LDNS_RR_TYPE_ANY) &&
-			qstate->return_msg && qstate->return_msg->rep) {
+			qstate->return_msg && qstate->return_msg->rep &&
+			!(qstate->env->need_to_validate &&
+			  (!(qstate->query_flags & BIT_CD)
+			    || qstate->env->cfg->ignore_cd) &&
+			  (qstate->return_msg->rep->security <= sec_status_bogus
+			    || qstate->return_msg->rep->security ==
+			    sec_status_secure_sentinel_fail))) {
 			struct reply_info* new_rep = qstate->return_msg->rep;
 			struct ub_packed_rrset_key* alias_rrset = NULL;
 			struct respip_action_info actinfo = {0, 0, 0, 0, NULL, 0, NULL};
Index: services/authzone.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/authzone.c,v
diff -u -p -r1.32 authzone.c
--- services/authzone.c	26 May 2026 11:14:11 -0000	1.32
+++ services/authzone.c	23 Jul 2026 15:23:53 -0000
@@ -5745,8 +5745,7 @@ xfr_master_add_addrs(struct auth_master*
 
 /** callback for task_transfer lookup of host name, of A or AAAA */
 void auth_xfer_transfer_lookup_callback(void* arg, int rcode, sldns_buffer* buf,
-	enum sec_status ATTR_UNUSED(sec), char* ATTR_UNUSED(why_bogus),
-	int ATTR_UNUSED(was_ratelimited))
+	enum sec_status sec, char* why_bogus, int ATTR_UNUSED(was_ratelimited))
 {
 	struct auth_xfer* xfr = (struct auth_xfer*)arg;
 	struct module_env* env;
@@ -5759,7 +5758,16 @@ void auth_xfer_transfer_lookup_callback(
 	}
 
 	/* process result */
-	if(rcode == LDNS_RCODE_NOERROR) {
+	if(sec == sec_status_bogus || sec == sec_status_secure_sentinel_fail) {
+		if(verbosity >= VERB_OPS) {
+			char zname[LDNS_MAX_DOMAINLEN];
+			dname_str(xfr->name, zname);
+			verbose(VERB_OPS, "auth zone %s: primary %s address lookup is DNSSEC bogus: %s",
+				zname, xfr->task_transfer->lookup_target->host,
+				(why_bogus?why_bogus:""));
+		}
+		/* fall through to next-lookup / next-master */
+	} else if(rcode == LDNS_RCODE_NOERROR) {
 		uint16_t wanted_qtype = LDNS_RR_TYPE_A;
 		struct regional* temp = env->scratch;
 		struct query_info rq;
@@ -6830,8 +6838,7 @@ xfr_probe_send_or_end(struct auth_xfer* 
 
 /** callback for task_probe lookup of host name, of A or AAAA */
 void auth_xfer_probe_lookup_callback(void* arg, int rcode, sldns_buffer* buf,
-	enum sec_status ATTR_UNUSED(sec), char* ATTR_UNUSED(why_bogus),
-	int ATTR_UNUSED(was_ratelimited))
+	enum sec_status sec, char* why_bogus, int ATTR_UNUSED(was_ratelimited))
 {
 	struct auth_xfer* xfr = (struct auth_xfer*)arg;
 	struct module_env* env;
@@ -6844,7 +6851,16 @@ void auth_xfer_probe_lookup_callback(voi
 	}
 
 	/* process result */
-	if(rcode == LDNS_RCODE_NOERROR) {
+	if(sec == sec_status_bogus || sec == sec_status_secure_sentinel_fail) {
+		if(verbosity >= VERB_OPS) {
+			char zname[LDNS_MAX_DOMAINLEN];
+			dname_str(xfr->name, zname);
+			verbose(VERB_OPS, "auth zone %s: primary %s address probe lookup is DNSSEC bogus: %s",
+				zname, xfr->task_transfer->lookup_target->host,
+				(why_bogus?why_bogus:""));
+		}
+		/* fall through to next-lookup / next-master */
+	} else if(rcode == LDNS_RCODE_NOERROR) {
 		uint16_t wanted_qtype = LDNS_RR_TYPE_A;
 		struct regional* temp = env->scratch;
 		struct query_info rq;
Index: services/listen_dnsport.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/listen_dnsport.c,v
diff -u -p -r1.41 listen_dnsport.c
--- services/listen_dnsport.c	26 May 2026 11:14:11 -0000	1.41
+++ services/listen_dnsport.c	23 Jul 2026 15:23:53 -0000
@@ -42,7 +42,6 @@
 #ifdef HAVE_SYS_TYPES_H
 #  include <sys/types.h>
 #endif
-#include <sys/time.h>
 #include <limits.h>
 #ifdef USE_TCP_FASTOPEN
 #include <netinet/tcp.h>
@@ -2167,7 +2166,8 @@ void tcp_req_info_clear(struct tcp_req_i
 	open = req->open_req_list;
 	while(open) {
 		nopen = open->next;
-		mesh_state_remove_reply(open->mesh, open->mesh_state, req->cp);
+		mesh_state_remove_reply(open->mesh, open->mesh_state, req->cp,
+			NULL);
 		free(open);
 		open = nopen;
 	}
@@ -3399,14 +3399,13 @@ doq_table_delete(struct doq_table* table
 }
 
 struct doq_timer*
-doq_timer_find_time(struct doq_table* table, struct timeval* tv)
+doq_timer_find_time(struct doq_table* table, ngtcp2_tstamp ts)
 {
 	struct doq_timer key;
 	struct rbnode_type* node;
 	log_assert(table != NULL);
 	memset(&key, 0, sizeof(key));
-	key.time.tv_sec = tv->tv_sec;
-	key.time.tv_usec = tv->tv_usec;
+	key.time_mono = ts;
 	node = rbtree_search(table->timer_tree, &key);
 	if(node)
 		return (struct doq_timer*)node->key;
@@ -3454,7 +3453,7 @@ doq_timer_list_remove(struct doq_table* 
 	if(!timer->timer_in_list)
 		return;
 	/* The item in the rbtree has the list start and end. */
-	rb_timer = doq_timer_find_time(table, &timer->time);
+	rb_timer = doq_timer_find_time(table, timer->time_mono);
 	if(rb_timer) {
 		if(timer->setlist_prev)
 			timer->setlist_prev->setlist_next = timer->setlist_next;
@@ -3500,7 +3499,8 @@ doq_timer_unset(struct doq_table* table,
 }
 
 void doq_timer_set(struct doq_table* table, struct doq_timer* timer,
-	struct doq_server_socket* worker_doq_socket, struct timeval* tv)
+	struct doq_server_socket* worker_doq_socket, struct timeval* tv,
+	ngtcp2_tstamp ts)
 {
 	struct doq_timer* rb_timer;
 	if(verbosity >= VERB_ALGO && timer->conn) {
@@ -3514,14 +3514,14 @@ void doq_timer_set(struct doq_table* tab
 			(int)rel.tv_sec, (int)rel.tv_usec);
 	}
 	if(timer->timer_in_tree || timer->timer_in_list) {
-		if(timer->time.tv_sec == tv->tv_sec &&
-			timer->time.tv_usec == tv->tv_usec)
+		if(timer->time_mono == ts)
 			return; /* already set on that time */
 		doq_timer_unset(table, timer);
 	}
-	timer->time.tv_sec = tv->tv_sec;
-	timer->time.tv_usec = tv->tv_usec;
-	rb_timer = doq_timer_find_time(table, tv);
+	timer->time_real.tv_sec = tv->tv_sec;
+	timer->time_real.tv_usec = tv->tv_usec;
+	timer->time_mono = ts;
+	rb_timer = doq_timer_find_time(table, ts);
 	if(rb_timer) {
 		/* There is a timeout already with this value. Timer is
 		 * added to the setlist. */
@@ -3597,15 +3597,29 @@ doq_conn_create(struct comm_point* c, st
 	return conn;
 }
 
+/** The arguments for doq stream tree del. */
+struct doq_stream_tree_del_args {
+	/** The doq table. */
+	struct doq_table* table;
+	/** The doq connection for the stream. */
+	struct doq_conn* conn;
+};
+
 /** delete stream tree node */
 static void
 stream_tree_del(rbnode_type* node, void* arg)
 {
-	struct doq_table* table = (struct doq_table*)arg;
+	struct doq_stream_tree_del_args* args = (struct doq_stream_tree_del_args*)arg;
+	struct doq_table* table = args->table;
 	struct doq_stream* stream;
 	if(!node)
 		return;
 	stream = (struct doq_stream*)node;
+	if(stream->mesh_state) {
+		mesh_state_remove_reply(stream->mesh, stream->mesh_state,
+			args->conn->doq_socket->cp, stream);
+		stream->mesh_state = NULL;
+	}
 	if(stream->in)
 		doq_table_quic_size_subtract(table, stream->inlen);
 	if(stream->out)
@@ -3627,7 +3641,11 @@ doq_conn_delete(struct doq_conn* conn, s
 	 * because the ngtcp2 conn is deleted. */
 	SSL_set_app_data(conn->ssl, NULL);
 	if(conn->stream_tree.count != 0) {
-		traverse_postorder(&conn->stream_tree, stream_tree_del, table);
+		struct doq_stream_tree_del_args args;
+		memset(&args, 0, sizeof(args));
+		args.table = table;
+		args.conn = conn;
+		traverse_postorder(&conn->stream_tree, stream_tree_del, &args);
 	}
 	free(conn->key.dcid);
 	SSL_free(conn->ssl);
@@ -3700,13 +3718,9 @@ int doq_timer_cmp(const void* key1, cons
 {
 	struct doq_timer* e = (struct doq_timer*)key1;
 	struct doq_timer* f = (struct doq_timer*)key2;
-	if(e->time.tv_sec < f->time.tv_sec)
-		return -1;
-	if(e->time.tv_sec > f->time.tv_sec)
-		return 1;
-	if(e->time.tv_usec < f->time.tv_usec)
+	if(e->time_mono < f->time_mono)
 		return -1;
-	if(e->time.tv_usec > f->time.tv_usec)
+	if(e->time_mono > f->time_mono)
 		return 1;
 	return 0;
 }
@@ -3940,6 +3954,11 @@ doq_stream_close(struct doq_conn* conn, 
 	if(stream->is_closed)
 		return 1;
 	stream->is_closed = 1;
+	if(stream->mesh_state) {
+		mesh_state_remove_reply(stream->mesh, stream->mesh_state,
+			conn->doq_socket->cp, stream);
+		stream->mesh_state = NULL;
+	}
 	doq_stream_off_write_list(conn, stream);
 	if(send_shutdown) {
 		verbose(VERB_ALGO, "doq: shutdown stream_id %d with app_error_code %d",
@@ -3969,7 +3988,8 @@ doq_stream_close(struct doq_conn* conn, 
 
 /** doq stream pick up answer data from buffer */
 static int
-doq_stream_pickup_answer(struct doq_stream* stream, struct sldns_buffer* buf)
+doq_stream_pickup_answer(struct doq_conn* conn, struct doq_stream* stream,
+	struct sldns_buffer* buf)
 {
 	stream->is_answer_available = 1;
 	if(stream->out) {
@@ -3979,6 +3999,11 @@ doq_stream_pickup_answer(struct doq_stre
 	}
 	stream->nwrite = 0;
 	stream->outlen = sldns_buffer_limit(buf);
+	if(!doq_table_quic_size_available(conn->doq_socket->table,
+		conn->doq_socket->cfg, stream->outlen)) {
+		verbose(VERB_ALGO, "doq stream: no space for reply length");
+		return 0;
+	}
 	/* For quic the output bytes have to stay allocated and available,
 	 * for potential resends, until the remote end has acknowledged them.
 	 * This includes the tcplen start uint16_t, in outlen_wire. */
@@ -4005,24 +4030,56 @@ doq_stream_send_reply(struct doq_conn* c
 	if(stream->out)
 		doq_table_quic_size_subtract(conn->doq_socket->table,
 			stream->outlen);
-	if(!doq_stream_pickup_answer(stream, buf))
+	if(!doq_stream_pickup_answer(conn, stream, buf))
 		return 0;
 	doq_table_quic_size_add(conn->doq_socket->table, stream->outlen);
 	doq_stream_on_write_list(conn, stream);
 	doq_conn_write_enable(conn);
 	return 1;
 }
+#endif /* HAVE_NGTCP2 */
+
+void
+doq_stream_add_meshstate(struct doq_stream* stream,
+	struct mesh_area* mesh, struct mesh_state* m)
+{
+#ifdef HAVE_NGTCP2
+	stream->mesh = mesh;
+	stream->mesh_state = m;
+#else
+	(void)stream; (void)mesh; (void)m;
+#endif
+}
 
+void
+doq_stream_remove_mesh_state(struct doq_stream* stream)
+{
+#ifdef HAVE_NGTCP2
+	if(!stream)
+		return;
+	stream->mesh_state = NULL;
+#else
+	(void)stream;
+#endif
+}
+
+#ifdef HAVE_NGTCP2
 /** doq stream data length has completed, allocations can be done. False on
  * allocation failure. */
 static int
-doq_stream_datalen_complete(struct doq_stream* stream, struct doq_table* table)
+doq_stream_datalen_complete(struct doq_conn* conn, struct doq_stream* stream,
+	struct doq_table* table)
 {
 	if(stream->inlen > 1024*1024) {
 		log_err("doq stream in length too large %d",
 			(int)stream->inlen);
 		return 0;
 	}
+	if(!doq_table_quic_size_available(table, conn->doq_socket->cfg,
+		stream->inlen)) {
+		verbose(VERB_ALGO, "doq stream: no space for query length");
+		return 0;
+	}
 	stream->in = calloc(1, stream->inlen);
 	if(!stream->in) {
 		log_err("doq could not read stream, calloc failed: "
@@ -4067,6 +4124,7 @@ doq_stream_data_complete(struct doq_conn
 		return 0;
 	}
 	c->repinfo.doq_streamid = stream->stream_id;
+	c->repinfo.doq_stream = stream;
 	conn->doq_socket->current_conn = conn;
 	fptr_ok(fptr_whitelist_comm_point(c->callback));
 	if( (*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, &c->repinfo)) {
@@ -4083,8 +4141,9 @@ doq_stream_data_complete(struct doq_conn
 
 /** doq receive data for a stream, more bytes of the incoming data */
 static int
-doq_stream_recv_data(struct doq_stream* stream, const uint8_t* data,
-	size_t datalen, int* recv_done, struct doq_table* table)
+doq_stream_recv_data(struct doq_conn* conn, struct doq_stream* stream,
+	const uint8_t* data, size_t datalen, int* recv_done,
+	struct doq_table* table)
 {
 	int got_data = 0;
 	/* read the tcplength uint16_t at the start */
@@ -4105,7 +4164,7 @@ doq_stream_recv_data(struct doq_stream* 
 		if(stream->nread == 2) {
 			/* the initial length value is completed */
 			stream->inlen = ntohs(tcplen);
-			if(!doq_stream_datalen_complete(stream, table))
+			if(!doq_stream_datalen_complete(conn, stream, table))
 				return 0;
 		} else {
 			/* store for later */
@@ -4254,12 +4313,11 @@ doq_submit_new_token(struct doq_conn* co
 	ngtcp2_ssize tokenlen;
 	int ret;
 	const ngtcp2_path* path = ngtcp2_conn_get_path(conn->conn);
-	ngtcp2_tstamp ts = doq_get_timestamp_nanosec();
 
 	tokenlen = ngtcp2_crypto_generate_regular_token(token,
 		conn->doq_socket->static_secret,
 		conn->doq_socket->static_secret_len, path->remote.addr,
-		path->remote.addrlen, ts);
+		path->remote.addrlen, doq_get_timestamp_nanosec());
 	if(tokenlen < 0) {
 		log_err("doq ngtcp2_crypto_generate_regular_token failed");
 		return 1;
@@ -4322,8 +4380,7 @@ doq_stream_open_cb(ngtcp2_conn* ATTR_UNU
 		verbose(VERB_ALGO, "doq: stream with this id already exists");
 		return 0;
 	}
-	if(stream_id != 0 && stream_id != 4 && /* allow one stream on a new connection */
-		!doq_table_quic_size_available(doq_conn->doq_socket->table,
+	if(!doq_table_quic_size_available(doq_conn->doq_socket->table,
 		doq_conn->doq_socket->cfg, sizeof(*stream)
 		+ 100 /* estimated query in */
 		+ 512 /* estimated response out */
@@ -4381,8 +4438,8 @@ doq_recv_stream_data_cb(ngtcp2_conn* ATT
 		return 0;
 	}
 	if(datalen != 0) {
-		if(!doq_stream_recv_data(stream, data, datalen, &recv_done,
-			doq_conn->doq_socket->table))
+		if(!doq_stream_recv_data(doq_conn, stream, data, datalen,
+			&recv_done, doq_conn->doq_socket->table))
 			return NGTCP2_ERR_CALLBACK_FAILURE;
 	}
 	if((flags&NGTCP2_STREAM_DATA_FLAG_FIN)!=0) {
@@ -4451,6 +4508,29 @@ doq_stream_reset_cb(ngtcp2_conn* ATTR_UN
 	return 0;
 }
 
+/** ngtcp2 extend_max_stream_data function */
+int doq_extend_max_stream_data_cb(ngtcp2_conn* ATTR_UNUSED(conn),
+	int64_t stream_id, uint64_t max_data, void* user_data,
+	void* ATTR_UNUSED(stream_user_data))
+{
+	struct doq_conn* doq_conn = (struct doq_conn*)user_data;
+	struct doq_stream* stream;
+	verbose(VERB_ALGO, "doq extend_max_stream_data stream id %d "
+		"max_data %d ", (int)stream_id, (int)max_data);
+	if(max_data == 0)
+		return 0;
+	stream = doq_stream_find(doq_conn, stream_id);
+	if(!stream) {
+		verbose(VERB_ALGO, "doq: unknown stream %d", (int)stream_id);
+		return 0;
+	}
+	if(!stream->is_answer_available)
+		return 0;
+	doq_stream_on_write_list(doq_conn, stream);
+	doq_conn_write_enable(doq_conn);
+	return 0;
+}
+
 /** ngtcp2 acked_stream_data_offset callback function */
 static int
 doq_acked_stream_data_offset_cb(ngtcp2_conn* ATTR_UNUSED(conn),
@@ -4825,6 +4905,7 @@ doq_conn_setup(struct doq_conn* conn, ui
 	callbacks.stream_open = doq_stream_open_cb;
 	callbacks.stream_close = doq_stream_close_cb;
 	callbacks.stream_reset = doq_stream_reset_cb;
+	callbacks.extend_max_stream_data = doq_extend_max_stream_data_cb;
 	callbacks.acked_stream_data_offset = doq_acked_stream_data_offset_cb;
 	callbacks.recv_stream_data = doq_recv_stream_data_cb;
 
@@ -5101,23 +5182,30 @@ doq_conn_clear_conids(struct doq_conn* c
 
 ngtcp2_tstamp doq_get_timestamp_nanosec(void)
 {
-#ifdef CLOCK_REALTIME
 	struct timespec tp;
 	memset(&tp, 0, sizeof(tp));
-	/* Get a nanosecond time, that can be compared with the event base. */
-	if(clock_gettime(CLOCK_REALTIME, &tp) == -1) {
-		log_err("clock_gettime failed: %s", strerror(errno));
+#ifdef CLOCK_BOOTTIME
+	if(clock_gettime(CLOCK_BOOTTIME, &tp) == -1) {
+#endif
+		if(clock_gettime(CLOCK_MONOTONIC, &tp) == -1) {
+			log_err("clock_gettime failed: %s", strerror(errno));
+		}
+#ifdef CLOCK_BOOTTIME
 	}
+#endif
 	return ((uint64_t)tp.tv_sec)*((uint64_t)1000000000) +
 		((uint64_t)tp.tv_nsec);
-#else
+}
+
+static struct timeval doq_get_timevalue(void)
+{
 	struct timeval tv;
+	memset(&tv, 0, sizeof(tv));
 	if(gettimeofday(&tv, NULL) < 0) {
 		log_err("gettimeofday failed: %s", strerror(errno));
+		memset(&tv, 0, sizeof(tv));
 	}
-	return ((uint64_t)tv.tv_sec)*((uint64_t)1000000000) +
-		((uint64_t)tv.tv_usec)*((uint64_t)1000);
-#endif /* CLOCK_REALTIME */
+	return tv;
 }
 
 /** doq start the closing period for the connection. */
@@ -5240,18 +5328,17 @@ doq_conn_recv(struct comm_point* c, stru
 	int* err_drop)
 {
 	int ret;
-	ngtcp2_tstamp ts;
 	struct ngtcp2_path path;
 	memset(&path, 0, sizeof(path));
 	path.remote.addr = (struct sockaddr*)&paddr->addr;
 	path.remote.addrlen = paddr->addrlen;
 	path.local.addr = (struct sockaddr*)&paddr->localaddr;
 	path.local.addrlen = paddr->localaddrlen;
-	ts = doq_get_timestamp_nanosec();
 
 	ret = ngtcp2_conn_read_pkt(conn->conn, &path, pi,
 		sldns_buffer_begin(c->doq_socket->pkt_buf),
-		sldns_buffer_limit(c->doq_socket->pkt_buf), ts);
+		sldns_buffer_limit(c->doq_socket->pkt_buf),
+		doq_get_timestamp_nanosec());
 	if(ret != 0) {
 		if(err_retry)
 			*err_retry = 0;
@@ -5339,7 +5426,6 @@ doq_conn_write_streams(struct comm_point
 {
 	struct doq_stream* stream = conn->stream_write_first;
 	ngtcp2_path_storage ps;
-	ngtcp2_tstamp ts = doq_get_timestamp_nanosec();
 	size_t num_packets = 0, max_packets = 65535;
 	ngtcp2_path_storage_zero(&ps);
 
@@ -5392,7 +5478,8 @@ doq_conn_write_streams(struct comm_point
 		ret = ngtcp2_conn_writev_stream(conn->conn, &ps.path, &pi,
 			sldns_buffer_begin(c->doq_socket->pkt_buf),
 			sldns_buffer_remaining(c->doq_socket->pkt_buf),
-			&ndatalen, flags, stream_id, datav, datav_count, ts);
+			&ndatalen, flags, stream_id, datav, datav_count,
+			doq_get_timestamp_nanosec());
 		if(ret < 0) {
 			if(ret == NGTCP2_ERR_WRITE_MORE) {
 				verbose(VERB_ALGO, "doq: write more, ndatalen %d", (int)ndatalen);
@@ -5407,26 +5494,20 @@ doq_conn_write_streams(struct comm_point
 				continue;
 			} else if(ret == NGTCP2_ERR_STREAM_DATA_BLOCKED) {
 				verbose(VERB_ALGO, "doq: ngtcp2_conn_writev_stream returned NGTCP2_ERR_STREAM_DATA_BLOCKED");
-#ifdef HAVE_NGTCP2_CCERR_DEFAULT
-				ngtcp2_ccerr_set_application_error(
-					&conn->ccerr, -1, NULL, 0);
-#else
-				ngtcp2_connection_close_error_set_application_error(&conn->last_error, -1, NULL, 0);
-#endif
-				if(err_drop)
-					*err_drop = 0;
-				if(!doq_conn_close_error(c, conn)) {
-					if(err_drop)
-						*err_drop = 1;
+				if(stream) {
+					doq_stream_off_write_list(conn, stream);
+					stream = stream->write_next;
+					continue;
+				} else {
+					break;
 				}
-				return 0;
 			} else if(ret == NGTCP2_ERR_STREAM_SHUT_WR) {
 				verbose(VERB_ALGO, "doq: ngtcp2_conn_writev_stream returned NGTCP2_ERR_STREAM_SHUT_WR");
 #ifdef HAVE_NGTCP2_CCERR_DEFAULT
 				ngtcp2_ccerr_set_application_error(
-					&conn->ccerr, -1, NULL, 0);
+					&conn->ccerr, DOQ_APP_ERROR_CODE, NULL, 0);
 #else
-				ngtcp2_connection_close_error_set_application_error(&conn->last_error, -1, NULL, 0);
+				ngtcp2_connection_close_error_set_application_error(&conn->last_error, DOQ_APP_ERROR_CODE, NULL, 0);
 #endif
 				if(err_drop)
 					*err_drop = 0;
@@ -5464,7 +5545,8 @@ doq_conn_write_streams(struct comm_point
 		if(ret == 0) {
 			/* congestion limited */
 			doq_conn_write_disable(conn);
-			ngtcp2_conn_update_pkt_tx_time(conn->conn, ts);
+			ngtcp2_conn_update_pkt_tx_time(conn->conn,
+				doq_get_timestamp_nanosec());
 			return 1;
 		}
 		sldns_buffer_set_position(c->doq_socket->pkt_buf, ret);
@@ -5478,7 +5560,7 @@ doq_conn_write_streams(struct comm_point
 		if(stream)
 			stream = stream->write_next;
 	}
-	ngtcp2_conn_update_pkt_tx_time(conn->conn, ts);
+	ngtcp2_conn_update_pkt_tx_time(conn->conn, doq_get_timestamp_nanosec());
 	return 1;
 }
 
@@ -5555,32 +5637,35 @@ doq_table_pop_first(struct doq_table* ta
 }
 
 int
-doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv)
+doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv, ngtcp2_tstamp* ts)
 {
-	ngtcp2_tstamp expiry = ngtcp2_conn_get_expiry(conn->conn);
-	ngtcp2_tstamp now = doq_get_timestamp_nanosec();
+	ngtcp2_tstamp doq_expiry = ngtcp2_conn_get_expiry(conn->conn);
+	ngtcp2_tstamp doq_now = doq_get_timestamp_nanosec();
 	ngtcp2_tstamp t;
+	struct timeval now = doq_get_timevalue();
 
-	if(expiry <= now) {
+	if(doq_expiry <= doq_now || doq_expiry == UINT64_MAX) {
+		/* UINT64_MAX means there is no next expiry. */
 		/* The timer has already expired, add with zero timeout.
 		 * This should call the callback straight away. Calling it
 		 * from the event callbacks is cleaner than calling it here,
 		 * because then it is always called with the same locks and
 		 * so on. This routine only has the conn.lock. */
-		t = now;
+		t = doq_now;
+		memcpy(tv, &now, sizeof(*tv));
 	} else {
-		t = expiry;
+		t = doq_expiry;
+		memset(tv, 0, sizeof(*tv));
+		tv->tv_sec = (doq_expiry - doq_now) / NGTCP2_SECONDS;
+		tv->tv_usec = ((doq_expiry - doq_now) / NGTCP2_MICROSECONDS)%1000000;
+		timeval_add(tv, &now);
 	}
 
-	/* convert to timeval */
-	memset(tv, 0, sizeof(*tv));
-	tv->tv_sec = t / NGTCP2_SECONDS;
-	tv->tv_usec = (t / NGTCP2_MICROSECONDS)%1000000;
+	*ts = t;
 
 	/* If we already have a timer, is it the right value? */
 	if(conn->timer.timer_in_tree || conn->timer.timer_in_list) {
-		if(conn->timer.time.tv_sec == tv->tv_sec &&
-			conn->timer.time.tv_usec == tv->tv_usec)
+		if(conn->timer.time_mono == *ts)
 			return 0;
 	}
 	return 1;
@@ -5601,13 +5686,12 @@ doq_conn_log_line(struct doq_conn* conn,
 int
 doq_conn_handle_timeout(struct doq_conn* conn)
 {
-	ngtcp2_tstamp now = doq_get_timestamp_nanosec();
 	int rv;
 
 	if(verbosity >= VERB_ALGO)
 		doq_conn_log_line(conn, "timeout");
 
-	rv = ngtcp2_conn_handle_expiry(conn->conn, now);
+	rv = ngtcp2_conn_handle_expiry(conn->conn, doq_get_timestamp_nanosec());
 	if(rv != 0) {
 		verbose(VERB_ALGO, "ngtcp2_conn_handle_expiry failed: %s",
 			ngtcp2_strerror(rv));
Index: services/listen_dnsport.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/listen_dnsport.h,v
diff -u -p -r1.23 listen_dnsport.h
--- services/listen_dnsport.h	26 Sep 2025 07:32:37 -0000	1.23
+++ services/listen_dnsport.h	23 Jul 2026 15:23:53 -0000
@@ -61,6 +61,8 @@ struct config_file;
 struct addrinfo;
 struct sldns_buffer;
 struct tcl_list;
+struct mesh_area;
+struct mesh_state;
 
 /**
  * Listening for queries structure.
@@ -538,8 +540,11 @@ void doq_table_delete(struct doq_table* 
 struct doq_timer {
 	/** The rbnode in the tree sorted by timeout value. Key this struct. */
 	struct rbnode_type node;
+	/** The timeout value. Monotonic value used with ngtcp2.
+	 *  This time value is used for the tree operations. */
+	ngtcp2_tstamp time_mono;
 	/** The timeout value. Absolute time value. */
-	struct timeval time;
+	struct timeval time_real;
 	/** If the timer is in the time tree, with the node. */
 	int timer_in_tree;
 	/** If there are more timers with the exact same timeout value,
@@ -689,6 +694,11 @@ struct doq_stream {
 	uint8_t* out;
 	/** if the stream is on the write list */
 	uint8_t on_write_list;
+	/** The mesh area and mesh state, set when this stream's query was
+	 * dispatched into the mesh; used to detach the reply on stream close */
+	struct mesh_area* mesh;
+	/** the mesh state for the query, is nonNULL when there is one. */
+	struct mesh_state* mesh_state;
 	/** the prev and next on the write list, if on the list */
 	struct doq_stream* write_prev, *write_next;
 };
@@ -791,7 +801,16 @@ int doq_stream_close(struct doq_conn* co
 /** send reply for a connection */
 int doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream,
 	struct sldns_buffer* buf);
+#endif /* HAVE_NGTCP2 */
 
+/** add mesh state to doq stream */
+void doq_stream_add_meshstate(struct doq_stream* stream,
+	struct mesh_area* mesh, struct mesh_state* m);
+
+/** remove mesh state from doq stream */
+void doq_stream_remove_mesh_state(struct doq_stream* stream);
+
+#ifdef HAVE_NGTCP2
 /** the connection has write interest, wants to write packets */
 void doq_conn_write_enable(struct doq_conn* conn);
 
@@ -813,10 +832,12 @@ struct doq_conn* doq_table_pop_first(str
  * doq check if the timer for the conn needs to be changed.
  * @param conn: connection, caller must hold lock on it.
  * @param tv: time value, absolute time, returned.
+ * @param ts: time stamp, absolute time, returned.
  * @return true if timer needs to be set to tv, false if no change is needed
  * 	to the timer. The timer is already set to the right time in that case.
  */
-int doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv);
+int doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv,
+	ngtcp2_tstamp* ts);
 
 /** doq remove timer from tree */
 void doq_timer_tree_remove(struct doq_table* table, struct doq_timer* timer);
@@ -829,11 +850,12 @@ void doq_timer_unset(struct doq_table* t
 
 /** doq set the timer and add it. */
 void doq_timer_set(struct doq_table* table, struct doq_timer* timer,
-	struct doq_server_socket* worker_doq_socket, struct timeval* tv);
+	struct doq_server_socket* worker_doq_socket, struct timeval* tv,
+	ngtcp2_tstamp ts);
 
 /** doq find a timeout in the timer tree */
 struct doq_timer* doq_timer_find_time(struct doq_table* table,
-	struct timeval* tv);
+	ngtcp2_tstamp ts);
 
 /** doq handle timeout for a connection. Pass conn locked. Returns false for
  * deletion. */
@@ -851,6 +873,9 @@ int doq_table_quic_size_available(struct
 
 /** doq get the quic size value */
 size_t doq_table_quic_size_get(struct doq_table* table);
+
+/** get a timestamp in nanoseconds */
+ngtcp2_tstamp doq_get_timestamp_nanosec(void);
 #endif /* HAVE_NGTCP2 */
 
 char* set_ip_dscp(int socket, int addrfamily, int ds);
@@ -866,8 +891,4 @@ void doq_client_event_cb(int fd, short e
 /** timer event callback for testcode/doqclient */
 void doq_client_timer_cb(int fd, short event, void* arg);
 
-#ifdef HAVE_NGTCP2
-/** get a timestamp in nanoseconds */
-ngtcp2_tstamp doq_get_timestamp_nanosec(void);
-#endif
 #endif /* LISTEN_DNSPORT_H */
Index: services/localzone.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/localzone.h,v
diff -u -p -r1.15 localzone.h
--- services/localzone.h	26 May 2026 11:14:11 -0000	1.15
+++ services/localzone.h	23 Jul 2026 15:23:53 -0000
@@ -573,7 +573,7 @@ enum respip_action {
 	respip_always_nxdomain = local_zone_always_nxdomain,
         /** answer with nodata response */
 	respip_always_nodata = local_zone_always_nodata,
-        /** answer with nodata response */
+        /** drop query */
 	respip_always_deny = local_zone_always_deny,
 	/** RPZ: truncate answer in order to force switch to tcp */
 	respip_truncate = local_zone_truncate,
Index: services/mesh.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/mesh.c,v
diff -u -p -r1.34 mesh.c
--- services/mesh.c	26 May 2026 11:14:11 -0000	1.34
+++ services/mesh.c	23 Jul 2026 15:23:53 -0000
@@ -467,6 +467,8 @@ void mesh_new_client(struct mesh_area* m
 				"incoming query.");
 			if(rep->c->use_h2)
 				http2_stream_remove_mesh_state(rep->c->h2_stream);
+			else if(rep->c->type == comm_doq && rep->doq_stream)
+				doq_stream_remove_mesh_state(rep->doq_stream);
 			comm_point_drop_reply(rep);
 			mesh->stats_dropped++;
 			return;
@@ -480,6 +482,8 @@ void mesh_new_client(struct mesh_area* m
 				"dropping incoming query.");
 			if(rep->c->use_h2)
 				http2_stream_remove_mesh_state(rep->c->h2_stream);
+			else if(rep->c->type == comm_doq && rep->doq_stream)
+				doq_stream_remove_mesh_state(rep->doq_stream);
 			comm_point_drop_reply(rep);
 			mesh->num_queries_replyaddr_limit++;
 			return;
@@ -552,6 +556,8 @@ void mesh_new_client(struct mesh_area* m
 	}
 	if(rep->c->use_h2) {
 		http2_stream_add_meshstate(rep->c->h2_stream, mesh, s);
+	} else if(rep->c->type == comm_doq && rep->doq_stream) {
+		doq_stream_add_meshstate(rep->doq_stream, mesh, s);
 	}
 	/* add serve expired timer if required and not already there */
 	if(timeout && !mesh_serve_expired_init(s, timeout)) {
@@ -605,6 +611,8 @@ servfail_mem:
 		qinfo, qid, qflags, edns);
 	if(rep->c->use_h2)
 		http2_stream_remove_mesh_state(rep->c->h2_stream);
+	else if(rep->c->type == comm_doq && rep->doq_stream)
+		doq_stream_remove_mesh_state(rep->doq_stream);
 	comm_point_send_reply(rep);
 	if(added)
 		mesh_state_delete(&s->s);
@@ -925,8 +933,7 @@ cfg_region_strlist_copy(struct regional*
 	return result;
 }
 
-/** Copy the client info to the query region. */
-static struct respip_client_info*
+struct respip_client_info*
 mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo)
 {
 	size_t i;
@@ -971,6 +978,11 @@ mesh_copy_client_info(struct regional* r
 			cinfo->view->name);
 		if(!client_info->view_name)
 			return NULL;
+	} else if(cinfo->view_name) {
+		client_info->view_name = regional_strdup(region,
+			cinfo->view_name);
+		if(!client_info->view_name)
+			return NULL;
 	}
 	return client_info;
 }
@@ -1484,6 +1496,10 @@ mesh_send_reply(struct mesh_state* m, in
 		 * for HTTP/2 stream to refer to mesh state, in case
 		 * connection gets cleanup before HTTP/2 stream close. */
 		r->h2_stream->mesh_state = NULL;
+#ifdef HAVE_NGTCP2
+	} else if(r->query_reply.doq_stream) {
+		r->query_reply.doq_stream->mesh_state = NULL;
+#endif
 	}
 	/* send the reply */
 	/* We don't reuse the encoded answer if:
@@ -1638,9 +1654,9 @@ static void dns_error_reporting(struct m
 	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) {
+	agent_domain_len = dname_valid(agent_domain, opt->opt_len);
+	if(agent_domain_len < 3) {
 		/* The agent domain needs to be a valid dname that is not the
 		 * root; from RFC9567. */
 		return;
@@ -1777,6 +1793,8 @@ void mesh_query_done(struct mesh_state* 
 				mstate->reply_list = NULL;
 				if(r->query_reply.c->use_h2)
 					http2_stream_remove_mesh_state(r->h2_stream);
+				else if(r->query_reply.doq_stream)
+					doq_stream_remove_mesh_state(r->query_reply.doq_stream);
 				comm_point_drop_reply(&r->query_reply);
 				mstate->reply_list = reply_list;
 				log_assert(mstate->s.env->mesh->num_reply_addrs > 0);
@@ -1814,6 +1832,8 @@ void mesh_query_done(struct mesh_state* 
 			mstate->reply_list = NULL;
 			if(r->query_reply.c->use_h2) {
 				http2_stream_remove_mesh_state(r->h2_stream);
+			} else if(r->query_reply.doq_stream) {
+				doq_stream_remove_mesh_state(r->query_reply.doq_stream);
 			}
 			comm_point_drop_reply(&r->query_reply);
 			mstate->reply_list = reply_list;
@@ -2009,6 +2029,8 @@ int mesh_state_add_reply(struct mesh_sta
 	if(rep->c->use_h2)
 		r->h2_stream = rep->c->h2_stream;
 	else	r->h2_stream = NULL;
+	if(rep->c->type != comm_doq)
+		r->query_reply.doq_stream = NULL;
 
 	/* Data related to local alias stored in 'qinfo' (if any) is ephemeral
 	 * and can be different for different original queries (even if the
@@ -2366,7 +2388,7 @@ void mesh_list_remove(struct mesh_state*
 }
 
 void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
-	struct comm_point* cp)
+	struct comm_point* cp, struct doq_stream* doq_stream)
 {
 	struct mesh_reply* n, *prev = NULL;
 	n = m->reply_list;
@@ -2374,7 +2396,8 @@ void mesh_state_remove_reply(struct mesh
 	 * there is no accounting twice */
 	if(!n) return; /* nothing to remove, also no accounting needed */
 	while(n) {
-		if(n->query_reply.c == cp) {
+		if(n->query_reply.c == cp
+			&& (!doq_stream || n->query_reply.doq_stream == doq_stream)) {
 			/* unlink it */
 			if(prev) prev->next = n->next;
 			else m->reply_list = n->next;
@@ -2387,6 +2410,10 @@ void mesh_state_remove_reply(struct mesh
 			 * share the same comm_point); make sure the streams
 			 * don't point back. */
 			if(n->h2_stream) n->h2_stream->mesh_state = NULL;
+#ifdef HAVE_NGTCP2
+			if(n->query_reply.doq_stream)
+				n->query_reply.doq_stream->mesh_state = NULL;
+#endif
 
 			/* prev = prev; */
 			n = n->next;
@@ -2427,9 +2454,10 @@ apply_respip_action(struct module_qstate
 
 	/* xxx_deny actions mean dropping the reply, unless the original reply
 	 * was redirected to response-ip data. */
-	if((actinfo->action == respip_deny ||
+	if(actinfo->action == respip_always_deny ||
+		((actinfo->action == respip_deny ||
 		actinfo->action == respip_inform_deny) &&
-		*encode_repp == rep)
+		*encode_repp == rep))
 		*encode_repp = NULL;
 
 	return 1;
@@ -2494,12 +2522,15 @@ mesh_serve_expired_callback(void* arg)
 			qstate->client_info, &actinfo, msg->rep, &alias_rrset, &encode_rep,
 			qstate->env->auth_zones)) {
 			return;
-		} else if(partial_rep &&
-			!respip_merge_cname(partial_rep, &qstate->qinfo, msg->rep,
+		} else if(partial_rep) {
+			if(!respip_merge_cname(partial_rep, &qstate->qinfo, msg->rep,
 			qstate->client_info, must_validate, &encode_rep, qstate->region,
 			qstate->env->auth_zones, qstate->env->views,
 			qstate->env->respip_set)) {
-			return;
+				return;
+			}
+			/* merge succeeded; final reply, no further alias pass */
+			partial_rep = NULL;
 		}
 		if(!encode_rep || alias_rrset) {
 			if(!encode_rep) {
@@ -2510,6 +2541,7 @@ mesh_serve_expired_callback(void* arg)
 				partial_rep = encode_rep;
 			}
 		}
+		msg->rep = encode_rep;
 		/* We've found a partial reply ending with an
 		* alias.  Replace the lookup qinfo for the
 		* alias target and lookup the cache again to
@@ -2536,9 +2568,10 @@ mesh_serve_expired_callback(void* arg)
 		log_dns_msg("Serve expired lookup", &qstate->qinfo, msg->rep);
 
 	for(r = mstate->reply_list; r; r = r->next) {
-		struct timeval old;
-		timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time);
-		if(mstate->s.env->cfg->discard_timeout != 0 &&
+		if(mesh_is_udp(r)) {
+		    struct timeval old;
+		    timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time);
+		    if(mstate->s.env->cfg->discard_timeout != 0 &&
 			((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 >
 			mstate->s.env->cfg->discard_timeout) {
 			/* Drop the reply, it is too old */
@@ -2554,10 +2587,15 @@ mesh_serve_expired_callback(void* arg)
 			mstate->reply_list = NULL;
 			if(r->query_reply.c->use_h2)
 				http2_stream_remove_mesh_state(r->h2_stream);
+			else if(r->query_reply.doq_stream)
+				doq_stream_remove_mesh_state(r->query_reply.doq_stream);
 			comm_point_drop_reply(&r->query_reply);
 			mstate->reply_list = reply_list;
+			log_assert(mstate->s.env->mesh->num_reply_addrs > 0);
+			mstate->s.env->mesh->num_reply_addrs--;
 			mstate->s.env->mesh->num_queries_discard_timeout++;
 			continue;
+		    }
 		}
 
 		i++;
Index: services/mesh.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/mesh.h,v
diff -u -p -r1.16 mesh.h
--- services/mesh.h	26 May 2026 11:14:11 -0000	1.16
+++ services/mesh.h	23 Jul 2026 15:23:53 -0000
@@ -683,9 +683,11 @@ void mesh_list_remove(struct mesh_state*
  * @param mesh: to update the counters.
  * @param m: the mesh state.
  * @param cp: the comm_point to remove from the list.
+ * @param doq_stream: if not NULL, it specifies the doq_stream to match
+ *	for the delete.
  */
 void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
-	struct comm_point* cp);
+	struct comm_point* cp, struct doq_stream* doq_stream);
 
 /** Callback for when the serve expired client timer has run out.  Tries to
  * find an expired answer in the cache and reply that to the client.
@@ -735,5 +737,9 @@ void mesh_respond_serve_expired(struct m
  */
 void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo,
 	uint16_t qflags, mesh_cb_func_type cb, void* cb_arg);
+
+/** Copy the client info to the query region. */
+struct respip_client_info* mesh_copy_client_info(struct regional* region,
+	struct respip_client_info* cinfo);
 
 #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.34 outside_network.c
--- services/outside_network.c	26 May 2026 11:14:11 -0000	1.34
+++ services/outside_network.c	23 Jul 2026 15:23:53 -0000
@@ -208,6 +208,7 @@ static void
 waiting_tcp_delete(struct waiting_tcp* w)
 {
 	if(!w) return;
+	free(w->tls_auth_name);
 	if(w->timer)
 		comm_timer_delete(w->timer);
 	free(w);
@@ -1480,7 +1481,7 @@ portcomm_loweruse(struct outside_network
 	pif = pc->pif;
 	log_assert(pif->inuse > 0);
 #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
-	pif->avail_ports[pif->avail_total - pif->inuse] = pc->number;
+	shared_ports_return_port(outnet->shared_ports, pif->shpif, pc->number);
 #endif
 	pif->inuse--;
 	pif->out[pc->index] = pif->out[pif->inuse];
@@ -1694,19 +1695,19 @@ create_pending_tcp(struct outside_networ
 }
 
 /** setup an outgoing interface, ready address */
-static int setup_if(struct port_if* pif, const char* addrstr, 
-	int* avail, int numavail, size_t numfd)
+static int setup_if(struct port_if* pif, const char* addrstr, size_t numfd,
+	struct shared_ports* shp)
 {
-#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
-	pif->avail_total = numavail;
-	pif->avail_ports = (int*)memdup(avail, (size_t)numavail*sizeof(int));
-	if(!pif->avail_ports)
-		return 0;
-#endif
 	if(!ipstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen) &&
 	   !netblockstrtoaddr(addrstr, UNBOUND_DNS_PORT,
 			      &pif->addr, &pif->addrlen, &pif->pfxlen))
 		return 0;
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+	pif->shpif = shared_ports_find_if(shp, &pif->addr, pif->addrlen,
+		pif->pfxlen);
+#else
+	(void)shp;
+#endif
 	pif->maxout = (int)numfd;
 	pif->inuse = 0;
 	pif->out = (struct port_comm**)calloc(numfd, 
@@ -1720,12 +1721,12 @@ struct outside_network* 
 outside_network_create(struct comm_base *base, size_t bufsize, 
 	size_t num_ports, char** ifs, int num_ifs, int do_ip4, 
 	int do_ip6, size_t num_tcp, int dscp, struct infra_cache* infra,
-	struct ub_randstate* rnd, int use_caps_for_id, int* availports, 
-	int numavailports, size_t unwanted_threshold, int tcp_mss,
+	struct ub_randstate* rnd, int use_caps_for_id,
+	size_t unwanted_threshold, int tcp_mss,
 	void (*unwanted_action)(void*), void* unwanted_param, int do_udp,
 	void* sslctx, int delayclose, int tls_use_sni, struct dt_env* dtenv,
 	int udp_connect, int max_reuse_tcp_queries, int tcp_reuse_timeout,
-	int tcp_auth_query_timeout)
+	int tcp_auth_query_timeout, struct shared_ports* shared_ports)
 {
 	struct outside_network* outnet = (struct outside_network*)
 		calloc(1, sizeof(struct outside_network));
@@ -1760,6 +1761,7 @@ outside_network_create(struct comm_base 
 	outnet->do_udp = do_udp;
 	outnet->tcp_mss = tcp_mss;
 	outnet->ip_dscp = dscp;
+	outnet->shared_ports = shared_ports;
 #ifndef S_SPLINT_S
 	if(delayclose) {
 		outnet->delayclose = 1;
@@ -1770,7 +1772,7 @@ outside_network_create(struct comm_base 
 	if(udp_connect) {
 		outnet->udp_connect = 1;
 	}
-	if(numavailports == 0 || num_ports == 0) {
+	if(num_ports == 0) {
 		log_err("no outgoing ports available");
 		outside_network_delete(outnet);
 		return NULL;
@@ -1831,13 +1833,13 @@ outside_network_create(struct comm_base 
 	/* allocate interfaces */
 	if(num_ifs == 0) {
 		if(do_ip4 && !setup_if(&outnet->ip4_ifs[0], "0.0.0.0", 
-			availports, numavailports, num_ports)) {
+			num_ports, outnet->shared_ports)) {
 			log_err("malloc failed");
 			outside_network_delete(outnet);
 			return NULL;
 		}
 		if(do_ip6 && !setup_if(&outnet->ip6_ifs[0], "::", 
-			availports, numavailports, num_ports)) {
+			num_ports, outnet->shared_ports)) {
 			log_err("malloc failed");
 			outside_network_delete(outnet);
 			return NULL;
@@ -1848,7 +1850,7 @@ outside_network_create(struct comm_base 
 		for(i=0; i<num_ifs; i++) {
 			if(str_is_ip6(ifs[i]) && do_ip6) {
 				if(!setup_if(&outnet->ip6_ifs[done_6], ifs[i],
-					availports, numavailports, num_ports)){
+					num_ports, outnet->shared_ports)){
 					log_err("malloc failed");
 					outside_network_delete(outnet);
 					return NULL;
@@ -1857,7 +1859,7 @@ outside_network_create(struct comm_base 
 			}
 			if(!str_is_ip6(ifs[i]) && do_ip4) {
 				if(!setup_if(&outnet->ip4_ifs[done_4], ifs[i],
-					availports, numavailports, num_ports)){
+					num_ports, outnet->shared_ports)){
 					log_err("malloc failed");
 					outside_network_delete(outnet);
 					return NULL;
@@ -1935,9 +1937,6 @@ outside_network_delete(struct outside_ne
 				comm_point_delete(pc->cp);
 				free(pc);
 			}
-#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
-			free(outnet->ip4_ifs[i].avail_ports);
-#endif
 			free(outnet->ip4_ifs[i].out);
 		}
 		free(outnet->ip4_ifs);
@@ -1951,9 +1950,6 @@ outside_network_delete(struct outside_ne
 				comm_point_delete(pc->cp);
 				free(pc);
 			}
-#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
-			free(outnet->ip6_ifs[i].avail_ports);
-#endif
 			free(outnet->ip6_ifs[i].out);
 		}
 		free(outnet->ip6_ifs);
@@ -2163,7 +2159,10 @@ static int
 select_ifport(struct outside_network* outnet, struct pending* pend,
 	int num_if, struct port_if* ifs)
 {
-	int my_if, my_port, fd, portno, inuse, tries=0;
+	int my_if, fd, portno, inuse, tries=0;
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+	int reused;
+#endif
 	struct port_if* pif;
 	/* randomly select interface and port */
 	if(num_if == 0) {
@@ -2177,37 +2176,35 @@ select_ifport(struct outside_network* ou
 		my_if = ub_random_max(outnet->rnd, num_if);
 		pif = &ifs[my_if];
 #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
-		if(outnet->udp_connect) {
-			/* if we connect() we cannot reuse fds for a port */
-			if(pif->inuse >= pif->avail_total) {
-				tries++;
-				if(tries < MAX_PORT_RETRY)
-					continue;
-				log_err("failed to find an open port, drop msg");
-				return 0;
-			}
-			my_port = pif->inuse + ub_random_max(outnet->rnd,
-				pif->avail_total - pif->inuse);
-		} else  {
-			my_port = ub_random_max(outnet->rnd, pif->avail_total);
-			if(my_port < pif->inuse) {
-				/* port already open */
-				pend->pc = pif->out[my_port];
-				verbose(VERB_ALGO, "using UDP if=%d port=%d",
-					my_if, pend->pc->number);
-				break;
-			}
+		if(!shared_ports_fetch_random(outnet->shared_ports,
+			pif->shpif, outnet->rnd, outnet->udp_connect,
+			pif->inuse, &portno, &reused)) {
+			tries++;
+			if(tries < MAX_PORT_RETRY)
+				continue;
+			log_err("failed to find an open port, drop msg");
+			return 0;
+		}
+		if(reused) {
+			/* port already open */
+			log_assert(portno < pif->inuse);
+			pend->pc = pif->out[portno];
+			verbose(VERB_ALGO, "using UDP if=%d port=%d",
+				my_if, pend->pc->number);
+			break;
 		}
-		/* try to open new port, if fails, loop to try again */
-		log_assert(pif->inuse < pif->maxout);
-		portno = pif->avail_ports[my_port - pif->inuse];
 #else
-		my_port = portno = 0;
+		portno = 0;
 #endif
+		/* try to open new port, if fails, loop to try again */
 		fd = udp_sockport(&pif->addr, pif->addrlen, pif->pfxlen,
 			portno, &inuse, outnet->rnd, outnet->ip_dscp);
 		if(fd == -1 && !inuse) {
 			/* nonrecoverable error making socket */
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+			shared_ports_return_port(outnet->shared_ports,
+				pif->shpif, portno);
+#endif
 			return 0;
 		}
 		if(fd != -1) {
@@ -2224,6 +2221,11 @@ select_ifport(struct outside_network* ou
 							pend->addrlen);
 					}
 					sock_close(fd);
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+					shared_ports_return_port(
+						outnet->shared_ports,
+						pif->shpif, portno);
+#endif
 					return 0;
 				}
 			}
@@ -2241,14 +2243,14 @@ select_ifport(struct outside_network* ou
 
 			/* grab port in interface */
 			pif->out[pif->inuse] = pend->pc;
-#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
-			pif->avail_ports[my_port - pif->inuse] =
-				pif->avail_ports[pif->avail_total-pif->inuse-1];
-#endif
 			pif->inuse++;
 			break;
 		}
 		/* failed, already in use */
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+		shared_ports_return_port(outnet->shared_ports, pif->shpif,
+			portno);
+#endif
 		verbose(VERB_QUERY, "port %d in use, trying another", portno);
 		tries++;
 		if(tries == MAX_PORT_RETRY) {
@@ -2540,7 +2542,16 @@ pending_tcp_query(struct serviced_query*
 	w->cb = callback;
 	w->cb_arg = callback_arg;
 	w->ssl_upstream = sq->ssl_upstream;
-	w->tls_auth_name = sq->tls_auth_name;
+	if(sq->tls_auth_name) {
+		w->tls_auth_name = strdup(sq->tls_auth_name);
+		if(!w->tls_auth_name) {
+			comm_timer_delete(w->timer);
+			free(w);
+			return NULL;
+		}
+	} else {
+		w->tls_auth_name = NULL;
+	}
 	w->timeout = timeout;
 	w->id_node.key = NULL;
 	w->write_wait_prev = NULL;
@@ -3630,13 +3641,16 @@ fd_for_dest(struct outside_network* outn
 {
 	struct sockaddr_storage* addr;
 	socklen_t addrlen;
-	int i, try, pnum, dscp;
+	int i, try, dscp;
 	struct port_if* pif;
 
 	/* create fd */
 	dscp = outnet->ip_dscp;
 	for(try = 0; try<1000; try++) {
 		int port = 0;
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+		int reused = 0;
+#endif
 		int freebind = 0;
 		int noproto = 0;
 		int inuse = 0;
@@ -3665,16 +3679,18 @@ fd_for_dest(struct outside_network* outn
 		addr = &pif->addr;
 		addrlen = pif->addrlen;
 #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
-		pnum = ub_random_max(outnet->rnd, pif->avail_total);
-		if(pnum < pif->inuse) {
-			/* port already open */
-			port = pif->out[pnum]->number;
-		} else {
-			/* unused ports in start part of array */
-			port = pif->avail_ports[pnum - pif->inuse];
+		if(!shared_ports_fetch_random(outnet->shared_ports,
+			pif->shpif, outnet->rnd, 0, pif->inuse,
+			&port, &reused)) {
+			/* try again, perhaps another interface. */
+			continue;
+		}
+		if(reused) {
+			log_assert(port < pif->inuse);
+			port = pif->out[port]->number;
 		}
 #else
-		pnum = port = 0;
+		port = 0;
 #endif
 		if(addr_is_ip6(to_addr, to_addrlen)) {
 			struct sockaddr_in6 sa = *(struct sockaddr_in6*)addr;
@@ -3689,6 +3705,14 @@ fd_for_dest(struct outside_network* outn
 				(struct sockaddr*)addr, addrlen, 1, &inuse, &noproto,
 				0, 0, 0, NULL, 0, freebind, 0, dscp);
 		}
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+		if(!reused) {
+			/* Return the port to the pool, since the caller does
+			 * not keep track of it, also have done fd, and bind. */
+			shared_ports_return_port(outnet->shared_ports,
+				pif->shpif, port);
+		}
+#endif
 		if(fd != -1) {
 			return fd;
 		}
@@ -3919,11 +3943,7 @@ if_get_mem(struct port_if* pif)
 {
 	size_t s;
 	int i;
-	s = sizeof(*pif) +
-#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
-	    sizeof(int)*pif->avail_total +
-#endif
-		sizeof(struct port_comm*)*pif->maxout;
+	s = sizeof(*pif) + sizeof(struct port_comm*)*pif->maxout;
 	for(i=0; i<pif->inuse; i++)
 		s += sizeof(*pif->out[i]) + 
 			comm_point_get_mem(pif->out[i]->cp);
@@ -4011,3 +4031,237 @@ serviced_get_mem(struct serviced_query* 
 	return s;
 }
 
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+/** Setup shared port interface */
+static int shared_ports_setup_if(struct shared_ports_if* shpif, char* str,
+	int* availports, int numavailports)
+{
+	shpif->avail_ports = (int*)memdup(availports,
+		(size_t)numavailports*sizeof(int));
+	if(!shpif->avail_ports)
+		return 0;
+	shpif->avail_total = numavailports;
+	shpif->inuse = 0;
+	shpif->pfxlen = 0;
+	if(!ipstrtoaddr(str, UNBOUND_DNS_PORT, &shpif->addr, &shpif->addrlen) &&
+	   !netblockstrtoaddr(str, UNBOUND_DNS_PORT, &shpif->addr,
+	   &shpif->addrlen, &shpif->pfxlen))
+		return 0;
+	return 1;
+}
+#endif
+
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+/** Allocate shared ports interfaces */
+static int shared_ports_alloc_ifs(struct shared_ports* shp, char** ifs,
+	int num_ifs, int do_ip4, int do_ip6, int* availports,
+	int numavailports)
+{
+#ifndef INET6
+	do_ip6 = 0;
+#endif
+	calc_num46(ifs, num_ifs, do_ip4, do_ip6,
+		&shp->num_ip4, &shp->num_ip6);
+	if(shp->num_ip4 != 0) {
+		if(!(shp->ip4_ifs = (struct shared_ports_if*)calloc(
+			(size_t)shp->num_ip4,
+			sizeof(struct shared_ports_if))))
+			return 0;
+	}
+	if(shp->num_ip6 != 0) {
+		if(!(shp->ip6_ifs = (struct shared_ports_if*)calloc(
+			(size_t)shp->num_ip6,
+			sizeof(struct shared_ports_if))))
+			return 0;
+	}
+	if(num_ifs == 0) {
+		if(do_ip4 && !shared_ports_setup_if(&shp->ip4_ifs[0],
+			"0.0.0.0", availports, numavailports))
+			return 0;
+		if(do_ip6 && !shared_ports_setup_if(&shp->ip6_ifs[0],
+			"::", availports, numavailports))
+			return 0;
+	} else {
+		size_t done_4 = 0, done_6 = 0;
+		int i;
+		for(i=0; i<num_ifs; i++) {
+			if(str_is_ip6(ifs[i]) && do_ip6) {
+				if(!shared_ports_setup_if(&shp->ip6_ifs[done_6],
+					ifs[i], availports, numavailports))
+					return 0;
+				done_6++;
+			}
+			if(!str_is_ip6(ifs[i]) && do_ip4) {
+				if(!shared_ports_setup_if(&shp->ip4_ifs[done_4],
+					ifs[i], availports, numavailports))
+					return 0;
+				done_4++;
+			}
+		}
+	}
+	return 1;
+}
+#endif
+
+struct shared_ports* shared_ports_create(char** ifs, int num_ifs, int do_ip4,
+	int do_ip6, int* availports, int numavailports)
+{
+	struct shared_ports* shp = calloc(1, sizeof(*shp));
+	if(!shp) {
+		log_err("malloc failed");
+		return NULL;
+	}
+	lock_basic_init(&shp->lock);
+	lock_protect(&shp->lock, shp, sizeof(*shp));
+
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+	/* Allocate interfaces */
+	if(!shared_ports_alloc_ifs(shp, ifs, num_ifs, do_ip4, do_ip6,
+		availports, numavailports)) {
+		log_err("malloc failed");
+		shared_ports_delete(shp);
+		return NULL;
+	}
+#else
+	(void)ifs; (void)num_ifs; (void)do_ip4; (void)do_ip6;
+	(void)availports; (void)numavailports;
+#endif
+	return shp;
+}
+
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+/** Delete shared ports interface structure */
+static void shared_ports_if_delete(struct shared_ports_if* shpif)
+{
+	if(!shpif)
+		return;
+	free(shpif->avail_ports);
+}
+#endif
+
+void shared_ports_delete(struct shared_ports* shp)
+{
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+	int i;
+#endif
+	if(!shp)
+		return;
+	lock_basic_destroy(&shp->lock);
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+	for(i=0; i<shp->num_ip4; i++) {
+		shared_ports_if_delete(&shp->ip4_ifs[i]);
+	}
+	free(shp->ip4_ifs);
+	for(i=0; i<shp->num_ip6; i++) {
+		shared_ports_if_delete(&shp->ip6_ifs[i]);
+	}
+	free(shp->ip6_ifs);
+#endif
+	free(shp);
+}
+
+struct shared_ports_if* shared_ports_find_if(struct shared_ports* shp,
+	struct sockaddr_storage* addr, socklen_t addrlen, int pfxlen)
+{
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+	struct shared_ports_if* ret, *ifs = NULL;
+	int i, num_ifs = 0;
+	lock_basic_lock(&shp->lock);
+	if(addr_is_ip6(addr, addrlen)) {
+		ifs = shp->ip6_ifs;
+		num_ifs = shp->num_ip6;
+	} else {
+		ifs = shp->ip4_ifs;
+		num_ifs = shp->num_ip4;
+	}
+	for(i=0; i<num_ifs; i++) {
+		if(sockaddr_cmp(addr, addrlen, &ifs[i].addr,
+			ifs[i].addrlen) == 0
+			&& pfxlen == ifs[i].pfxlen) {
+			ret = &ifs[i];
+			lock_basic_unlock(&shp->lock);
+			return ret;
+		}
+	}
+	lock_basic_unlock(&shp->lock);
+	return NULL;
+#else
+	(void)shp; (void)addr; (void)addrlen; (void)pfxlen;
+	return NULL;
+#endif
+}
+
+int shared_ports_fetch_random(struct shared_ports* shp,
+	struct shared_ports_if* shpif, struct ub_randstate* rnd,
+	int udp_connect, int reusenum, int* port, int* reused)
+{
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+	int portno = 0, my_port = 0;
+	if(!shpif)
+		return 0;
+	lock_basic_lock(&shp->lock);
+	if(udp_connect) {
+		/* if we connect() we cannot reuse fds for a port. */
+		if(shpif->inuse >= shpif->avail_total) {
+			lock_basic_unlock(&shp->lock);
+			return 0;
+		}
+		my_port = ub_random_max(rnd,
+			shpif->avail_total - shpif->inuse);
+	} else {
+		/* select from free ports and open ports on this thread. */
+		if(shpif->inuse >= shpif->avail_total) {
+			lock_basic_unlock(&shp->lock);
+			if(reusenum == 0) {
+				return 0;
+			}
+			my_port = ub_random_max(rnd, reusenum);
+			*port = my_port;
+			*reused = 1;
+			return 1;
+		}
+		my_port = ub_random_max(rnd, shpif->avail_total - shpif->inuse
+			+ reusenum);
+		if(my_port < reusenum) {
+			/* port already open */
+			lock_basic_unlock(&shp->lock);
+			*port = my_port;
+			*reused = 1;
+			return 1;
+		}
+		my_port -= reusenum;
+	}
+	log_assert(shpif->inuse < shpif->avail_total);
+	log_assert(my_port >= 0 && my_port < shpif->avail_total);
+	portno = shpif->avail_ports[my_port];
+	shpif->avail_ports[my_port] =
+		shpif->avail_ports[shpif->avail_total-shpif->inuse-1];
+	shpif->inuse++;
+	lock_basic_unlock(&shp->lock);
+	*port = portno;
+	*reused = 0;
+	return 1;
+#else
+	(void)shp; (void)shpif; (void)rnd; (void)udp_connect;
+	(void)reusenum;
+	*port = 0;
+	*reused = 0;
+	return 1;
+#endif
+}
+
+void shared_ports_return_port(struct shared_ports* shp,
+	struct shared_ports_if* shpif, int port)
+{
+#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
+	if(!shpif)
+		return;
+	lock_basic_lock(&shp->lock);
+	log_assert(shpif->inuse > 0);
+	shpif->avail_ports[shpif->avail_total - shpif->inuse] = port;
+	shpif->inuse--;
+	lock_basic_unlock(&shp->lock);
+#else
+	(void)shp; (void)shpif; (void)port;
+#endif
+}
Index: services/outside_network.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/outside_network.h,v
diff -u -p -r1.18 outside_network.h
--- services/outside_network.h	26 May 2026 11:14:11 -0000	1.18
+++ services/outside_network.h	23 Jul 2026 15:23:53 -0000
@@ -70,6 +70,8 @@ struct module_env;
 struct module_qstate;
 struct query_info;
 struct config_file;
+struct shared_ports;
+struct shared_ports_if;
 
 /**
  * Send queries to outside servers and wait for answers from servers.
@@ -119,6 +121,9 @@ struct outside_network {
 	int udp_connect;
 	/** number of udp packets sent. */
 	size_t num_udp_outgoing;
+	/** the shared ports structure, with random ports numbers.
+	 * This is a reference to the member in the daemon structure. */
+	struct shared_ports* shared_ports;
 
 	/** array of outgoing IP4 interfaces */
 	struct port_if* ip4_ifs;
@@ -211,11 +216,8 @@ struct port_if {
 	int pfxlen;
 
 #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION
-	/** the available ports array. These are unused.
-	 * Only the first total-inuse part is filled. */
-	int* avail_ports;
-	/** the total number of available ports (size of the array) */
-	int avail_total;
+	/** the shared port numbers for this interface. */
+	struct shared_ports_if* shpif;
 #endif
 
 	/** array of the commpoints currently in use. 
@@ -246,6 +248,42 @@ struct port_comm {
 };
 
 /**
+ * Shared ports, the list of ports shared across threads
+ */
+struct shared_ports {
+	/** mutex on the ports */
+	lock_basic_type lock;
+	/** array of IP4 interfaces */
+	struct shared_ports_if* ip4_ifs;
+	/** number of outgoing IP4 interfaces */
+	int num_ip4;
+	/** array of IP6 interfaces */
+	struct shared_ports_if* ip6_ifs;
+	/** number of outgoing IP6 interfaces */
+	int num_ip6;
+};
+
+/**
+ * Shared ports for an interface.
+ */
+struct shared_ports_if {
+	/** address ready to allocate new socket (except port no). */
+	struct sockaddr_storage addr;
+	/** length of addr field */
+	socklen_t addrlen;
+	/** if a netblock, the prefix */
+	int pfxlen;
+
+	/** the available ports array. These are unused.
+	 * Only the first total-inuse part is filled. */
+	int* avail_ports;
+	/** the total number of available ports (size of the array) */
+	int avail_total;
+	/** the number in use. */
+	int inuse;
+};
+
+/**
  * Reuse TCP connection, still open can be used again.
  */
 struct reuse_tcp {
@@ -419,7 +457,7 @@ struct waiting_tcp {
 	void* cb_arg;
 	/** if it uses ssl upstream */
 	int ssl_upstream;
-	/** ref to the tls_auth_name from the serviced_query */
+	/** owned copy of the tls_auth_name (malloced) */
 	char* tls_auth_name;
 	/** the packet was involved in an error, to stop looping errors */
 	int error_count;
@@ -551,8 +589,6 @@ struct serviced_query {
  * @param infra: pointer to infra cached used for serviced queries.
  * @param rnd: stored to create random numbers for serviced queries.
  * @param use_caps_for_id: enable to use 0x20 bits to encode id randomness.
- * @param availports: array of available ports. 
- * @param numavailports: number of available ports in array.
  * @param unwanted_threshold: when to take defensive action.
  * @param unwanted_action: the action to take.
  * @param unwanted_param: user parameter to action.
@@ -567,17 +603,18 @@ struct serviced_query {
  * @param max_reuse_tcp_queries: max number of queries on a reuse connection.
  * @param tcp_reuse_timeout: timeout for REUSE entries in milliseconds.
  * @param tcp_auth_query_timeout: timeout in milliseconds for TCP queries to auth servers.
+ * @param shared_ports: the shared_ports structure.
  * @return: the new structure (with no pending answers) or NULL on error.
  */
 struct outside_network* outside_network_create(struct comm_base* base,
 	size_t bufsize, size_t num_ports, char** ifs, int num_ifs,
 	int do_ip4, int do_ip6, size_t num_tcp, int dscp, struct infra_cache* infra, 
-	struct ub_randstate* rnd, int use_caps_for_id, int* availports, 
-	int numavailports, size_t unwanted_threshold, int tcp_mss,
+	struct ub_randstate* rnd, int use_caps_for_id,
+	size_t unwanted_threshold, int tcp_mss,
 	void (*unwanted_action)(void*), void* unwanted_param, int do_udp,
 	void* sslctx, int delayclose, int tls_use_sni, struct dt_env *dtenv,
 	int udp_connect, int max_reuse_tcp_queries, int tcp_reuse_timeout,
-	int tcp_auth_query_timeout);
+	int tcp_auth_query_timeout, struct shared_ports* shared_ports);
 
 /**
  * Delete outside_network structure.
@@ -818,6 +855,54 @@ struct comm_point* outnet_comm_point_for
 
 /** connect tcp connection to addr, 0 on failure */
 int outnet_tcp_connect(int s, struct sockaddr_storage* addr, socklen_t addrlen);
+
+/**
+ * Create new shared ports structure.
+ * @param ifs: interface names (or NULL for default interface).
+ *    These interfaces must be able to access all authoritative servers.
+ * @param num_ifs: number of names in array ifs.
+ * @param do_ip4: service IP4.
+ * @param do_ip6: service IP6.
+ * @param availports: array of available ports.
+ * @param numavailports: number of available ports in array.
+ * @return new, or NULL on failure.
+ */
+struct shared_ports* shared_ports_create(char** ifs, int num_ifs, int do_ip4,
+	int do_ip6, int* availports, int numavailports);
+
+/**
+ * Delete shared ports structure.
+ * @param shp: shared ports structure.
+ */
+void shared_ports_delete(struct shared_ports* shp);
+
+/** Find interface in shared ports. */
+struct shared_ports_if* shared_ports_find_if(struct shared_ports* shp,
+	struct sockaddr_storage* addr, socklen_t addrlen, int pfxlen);
+
+/**
+ * Get a shared port from the list of random ports.
+ * @param shp: shared ports structure.
+ * @param shpif: the shared ports interface.
+ * @param rnd: used to make random numbers.
+ * @param udp_connect: set to true if no reuse is possible.
+ * @param reusenum: number of ports that can be reused (already open).
+ * @param port: the port number is returned.
+ * @param reused: if the port numer is reused, returned.
+ * @return false on failure. That can mean no more free ports to use.
+ */
+int shared_ports_fetch_random(struct shared_ports* shp,
+	struct shared_ports_if* shpif, struct ub_randstate* rnd,
+	int udp_connect, int reusenum, int* port, int* reused);
+
+/**
+ * Return a shared port to the list of random ports.
+ * @param shp: shared ports structure.
+ * @param shpif: the shared ports interface.
+ * @param port: port number to return to be used again.
+ */
+void shared_ports_return_port(struct shared_ports* shp,
+	struct shared_ports_if* shpif, int port);
 
 /** callback for incoming udp answers from the network */
 int outnet_udp_cb(struct comm_point* c, void* arg, int error,
Index: services/cache/dns.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/cache/dns.c,v
diff -u -p -r1.28 dns.c
--- services/cache/dns.c	26 May 2026 11:14:11 -0000	1.28
+++ services/cache/dns.c	23 Jul 2026 15:23:53 -0000
@@ -277,6 +277,8 @@ find_closest_of_type(struct module_env* 
 
 		/* snip off front label */
 		lablen = *qname;
+		if(lablen == 0)
+			break;
 		qname += lablen + 1;
 		qnamelen -= lablen + 1;
 	}
@@ -1065,7 +1067,7 @@ dns_cache_lookup(struct module_env* env,
 	if(env->cfg->harden_below_nxdomain) {
 		while(!dname_is_root(k.qname)) {
 			if(dpname && dpnamelen
-				&& !dname_subdomain_c(k.qname, dpname))
+				&& !dname_strict_subdomain_c(k.qname, dpname))
 				break; /* no synth nxdomain above the stub */
 			dname_remove_label(&k.qname, &k.qname_len);
 			h = query_info_hash(&k, flags);
Index: services/cache/rrset.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/cache/rrset.c,v
diff -u -p -r1.11 rrset.c
--- services/cache/rrset.c	26 May 2026 11:14:11 -0000	1.11
+++ services/cache/rrset.c	23 Jul 2026 15:23:53 -0000
@@ -50,6 +50,7 @@
 #include "util/regional.h"
 #include "util/alloc.h"
 #include "util/net_help.h"
+#include "validator/val_utils.h"
 
 void
 rrset_markdel(void* key)
@@ -126,7 +127,8 @@ rrset_cache_touch(struct rrset_cache* r,
 
 /** see if rrset needs to be updated in the cache */
 static int
-need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns)
+need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns,
+	int a_aaaa)
 {
 	struct packed_rrset_data* newd = (struct packed_rrset_data*)nd;
 	struct packed_rrset_data* cached = (struct packed_rrset_data*)cd;
@@ -151,9 +153,13 @@ need_to_update_rrset(void* nd, void* cd,
 			return 0;
 		/* ghost-domain: never let an NS overwrite extend lifetime
 		 * past the entry it replaces, regardless of trust. */
-		if(ns && !TTL_IS_EXPIRED(cached->ttl, timenow) &&
+		/* Also for A/AAAA and it is glue. */
+		if((ns ||
+			(a_aaaa && cached->trust==rrset_trust_add_noAA))
+			&& !TTL_IS_EXPIRED(cached->ttl, timenow) &&
 			newd->ttl > cached->ttl) {
 			size_t i;
+			if(a_aaaa) newd->trust=rrset_trust_add_noAA;
 			newd->ttl = cached->ttl;
 			for(i=0; i<(newd->count+newd->rrsig_count); i++)
 				if(newd->rr_ttl[i] > newd->ttl)
@@ -223,7 +229,8 @@ rrset_cache_update(struct rrset_cache* r
 		equal = rrsetdata_equal((struct packed_rrset_data*)k->entry.
 			data, (struct packed_rrset_data*)e->data);
 		if(!need_to_update_rrset(k->entry.data, e->data, timenow,
-			equal, (rrset_type==LDNS_RR_TYPE_NS))) {
+			equal, (rrset_type==LDNS_RR_TYPE_NS),
+			(rrset_type==LDNS_RR_TYPE_A || rrset_type==LDNS_RR_TYPE_AAAA))) {
 			/* cache is superior, return that value */
 			lock_rw_unlock(&e->lock);
 			ub_packed_rrset_parsedelete(k, alloc);
@@ -255,12 +262,43 @@ rrset_cache_update(struct rrset_cache* r
 	return 0;
 }
 
+/** See if the name is a within signer authority */
+static int
+dname_subdomain_rrsig_signers(uint8_t* dname,
+	struct ub_packed_rrset_key* rrset)
+{
+	struct packed_rrset_data* d = (struct packed_rrset_data*)
+		rrset->entry.data;
+	size_t i;
+	if(!d || !d->rrsig_count)
+		return 0;
+	for(i=0; i<d->rrsig_count; i++) {
+		uint8_t* sname = NULL;
+		size_t slen = 0;
+		rrsig_get_signer(d->rr_data[d->count+i], d->rr_len[d->count+i],
+			&sname, &slen);
+		if(!sname || !slen)
+			return 0; /* malformed */
+		if(!dname_subdomain_c(dname, sname))
+			return 0; /* not a subdomain */
+	}
+	return 1;
+}
+
 void rrset_cache_update_wildcard(struct rrset_cache* rrset_cache, 
 	struct ub_packed_rrset_key* rrset, uint8_t* ce, size_t ce_len,
 	struct alloc_cache* alloc, time_t timenow)
 {
 	struct rrset_ref ref;
 	uint8_t wc_dname[LDNS_MAX_DOMAINLEN+3];
+
+	/* See if the RRSIG signer name allows this wildcard,
+	 * the new rrset should fall within the zone of the RRSIG signer(s). */
+	if(!dname_subdomain_rrsig_signers(ce, rrset)) {
+		verbose(VERB_ALGO, "wildcard canonical parent outside signer authority");
+		return;
+	}
+
 	rrset = packed_rrset_copy_alloc(rrset, alloc, timenow);
 	if(!rrset) {
 		log_err("malloc failure in rrset_cache_update_wildcard");
Index: smallapp/worker_cb.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/smallapp/worker_cb.c,v
diff -u -p -r1.14 worker_cb.c
--- smallapp/worker_cb.c	31 Aug 2025 21:41:10 -0000	1.14
+++ smallapp/worker_cb.c	23 Jul 2026 15:23:53 -0000
@@ -128,6 +128,12 @@ worker_alloc_cleanup(void* ATTR_UNUSED(a
 	log_assert(0);
 }
 
+void
+libworker_alloc_cleanup(void* ATTR_UNUSED(arg))
+{
+	log_assert(0);
+}
+
 struct outbound_entry* libworker_send_query(
 	struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags),
 	int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec),
Index: testcode/doqclient.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/doqclient.c,v
diff -u -p -r1.1.1.4 doqclient.c
--- testcode/doqclient.c	26 May 2026 11:10:51 -0000	1.1.1.4
+++ testcode/doqclient.c	23 Jul 2026 15:23:53 -0000
@@ -1519,9 +1519,9 @@ doq_client_send_pkt(struct doq_client_da
 		}
 		log_err("doq sendmsg: %s", strerror(errno));
 #ifdef HAVE_NGTCP2_CCERR_DEFAULT
-		ngtcp2_ccerr_set_application_error(&data->ccerr, -1, NULL, 0);
+		ngtcp2_ccerr_set_application_error(&data->ccerr, 1, NULL, 0);
 #else
-		ngtcp2_connection_close_error_set_application_error(&data->last_error, -1, NULL, 0);
+		ngtcp2_connection_close_error_set_application_error(&data->last_error, 1, NULL, 0);
 #endif
 		return 0;
 	}
@@ -2667,6 +2667,11 @@ void libworker_bg_done_cb(void* ATTR_UNU
 void libworker_event_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), 
 	struct sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(s),
 	char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited))
+{
+	log_assert(0);
+}
+
+void libworker_alloc_cleanup(void* ATTR_UNUSED(arg))
 {
 	log_assert(0);
 }
Index: testcode/fake_event.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/fake_event.c,v
diff -u -p -r1.1.1.19 fake_event.c
--- testcode/fake_event.c	26 Sep 2025 07:30:48 -0000	1.1.1.19
+++ testcode/fake_event.c	23 Jul 2026 15:23:53 -0000
@@ -1126,15 +1126,16 @@ outside_network_create(struct comm_base*
 	int ATTR_UNUSED(dscp),
 	struct infra_cache* infra,
 	struct ub_randstate* ATTR_UNUSED(rnd),
-	int ATTR_UNUSED(use_caps_for_id), int* ATTR_UNUSED(availports),
-	int ATTR_UNUSED(numavailports), size_t ATTR_UNUSED(unwanted_threshold),
+	int ATTR_UNUSED(use_caps_for_id),
+	size_t ATTR_UNUSED(unwanted_threshold),
 	int ATTR_UNUSED(outgoing_tcp_mss),
 	void (*unwanted_action)(void*), void* ATTR_UNUSED(unwanted_param),
 	int ATTR_UNUSED(do_udp), void* ATTR_UNUSED(sslctx),
 	int ATTR_UNUSED(delayclose), int ATTR_UNUSED(tls_use_sni),
 	struct dt_env* ATTR_UNUSED(dtenv), int ATTR_UNUSED(udp_connect),
 	int ATTR_UNUSED(max_reuse_tcp_queries), int ATTR_UNUSED(tcp_reuse_timeout),
-	int ATTR_UNUSED(tcp_auth_query_timeout))
+	int ATTR_UNUSED(tcp_auth_query_timeout),
+	struct shared_ports* ATTR_UNUSED(shared_ports))
 {
 	struct replay_runtime* runtime = (struct replay_runtime*)base;
 	struct outside_network* outnet =  calloc(1,
@@ -1980,6 +1981,20 @@ int outnet_tcp_connect(int ATTR_UNUSED(s
 	return 0;
 }
 
+struct shared_ports* shared_ports_create(char** ATTR_UNUSED(ifs),
+	int ATTR_UNUSED(num_ifs), int ATTR_UNUSED(do_ip4),
+        int ATTR_UNUSED(do_ip6), int* ATTR_UNUSED(availports),
+	int ATTR_UNUSED(numavailports))
+{
+	return calloc(1, sizeof(struct shared_ports));
+}
+
+void shared_ports_delete(struct shared_ports* shp)
+{
+	if(!shp) return;
+	free(shp);
+}
+
 int tcp_req_info_add_meshstate(struct tcp_req_info* ATTR_UNUSED(req),
         struct mesh_area* ATTR_UNUSED(mesh), struct mesh_state* ATTR_UNUSED(m))
 {
@@ -2018,6 +2033,15 @@ void http2_stream_add_meshstate(struct h
 }
 
 void http2_stream_remove_mesh_state(struct http2_stream* ATTR_UNUSED(h2_stream))
+{
+}
+
+void doq_stream_add_meshstate(struct doq_stream* ATTR_UNUSED(stream),
+	struct mesh_area* ATTR_UNUSED(mesh), struct mesh_state* ATTR_UNUSED(m))
+{
+}
+
+void doq_stream_remove_mesh_state(struct doq_stream* ATTR_UNUSED(stream))
 {
 }
 
Index: testcode/unitmain.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unitmain.c,v
diff -u -p -r1.1.1.14 unitmain.c
--- testcode/unitmain.c	26 May 2026 11:10:51 -0000	1.1.1.14
+++ testcode/unitmain.c	23 Jul 2026 15:23:53 -0000
@@ -1282,6 +1282,61 @@ static void localzone_test(void)
 	localzone_parents_test();
 }
 
+#include "services/mesh.h"
+/** mesh unit tests */
+static void mesh_test(void)
+{
+	struct regional* r2, *r3;
+	struct respip_client_info* c1, *c2, *c3;
+	unit_show_func("services/mesh.c", "mesh_copy_client_info");
+	r2 = regional_create();
+	r3 = regional_create();
+	if(!r2 || !r3) fatal_exit("out of memory");
+
+	c1 = calloc(1, sizeof(*c1));
+	if(!c1) fatal_exit("out of memory");
+	c1->view = calloc(1, sizeof(*c1->view));
+	if(!c1->view) fatal_exit("out of memory");
+	c1->view->name = strdup("view1");
+	if(!c1->view->name) fatal_exit("out of memory");
+
+	c2 = mesh_copy_client_info(r2, c1);
+	if(!c2) fatal_exit("out of memory");
+	c3 = mesh_copy_client_info(r3, c2);
+	if(!c3) fatal_exit("out of memory");
+
+	unit_assert(strcmp(c1->view->name, c2->view_name) == 0);
+	unit_assert(strcmp(c1->view->name, c3->view_name) == 0);
+
+	/* make sure that the c3 view_name is in the r3 region. */
+	unit_assert(r3->next == NULL);  /* only the first chunk present atm */
+	if(strlen(c3->view_name) >= r3->large_object_size) {
+		char* a = r3->large_list;
+		int found = 0;
+		while(a) {
+			if(strcmp(c3->view_name,
+				a + /* ALIGNEMENT */ sizeof(uint64_t)) == 0) {
+				found = 1;
+				break;
+			}
+			a = *(char**)a;
+		}
+		unit_assert(found == 1);
+	} else {
+		/* The allocation is expected in the r3 region first chunk */
+		unit_assert((uint8_t*)c3->view_name < ((uint8_t*)r3)+r3->first_size);
+	}
+
+	regional_destroy(r2);
+	/* ASAN should complain for the freed access below */
+	unit_assert(strcmp(c1->view->name, c3->view_name) == 0);
+
+	regional_destroy(r3);
+	free(c1->view->name);
+	free(c1->view);
+	free(c1);
+}
+
 void unit_show_func(const char* file, const char* func)
 {
 	printf("test %s:%s\n", file, func);
@@ -1356,6 +1411,7 @@ main(int argc, char* argv[])
 	msgparse_test();
 	edns_ede_answer_encode_test();
 	localzone_test();
+	mesh_test();
 #ifdef CLIENT_SUBNET
 	ecs_test();
 #endif /* CLIENT_SUBNET */
Index: testcode/unittcpreuse.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unittcpreuse.c,v
diff -u -p -r1.1.1.2 unittcpreuse.c
--- testcode/unittcpreuse.c	20 Oct 2022 08:25:17 -0000	1.1.1.2
+++ testcode/unittcpreuse.c	23 Jul 2026 15:23:53 -0000
@@ -41,6 +41,7 @@
 #include "config.h"
 #include "testcode/unitmain.h"
 #include "util/log.h"
+#include "util/net_help.h"
 #include "util/random.h"
 #include "services/outside_network.h"
 
@@ -479,6 +480,278 @@ static void reuse_write_wait_test(void)
 	check_reuse_write_wait_removal(1, &reuse, store, 0, 1);
 }
 
+static void shared_port_test_ifs(void)
+{
+	struct shared_ports* shp;
+	struct shared_ports_if* shpif;
+	char* ifs[] = {"1.2.3.4", "1.2.3.5", "::1:2", "::1:3"};
+	int availports[] = {1, 2, 3, 4};
+	struct sockaddr_storage addr;
+	socklen_t addrlen;
+
+	shp = shared_ports_create(ifs, 4, 1, 1, availports, 4);
+	unit_assert(shp);
+
+	if(!ipstrtoaddr("1.2.3.4", UNBOUND_DNS_PORT, &addr, &addrlen))
+		log_err("could not parse");
+	shpif = shared_ports_find_if(shp, &addr, addrlen, 0);
+	unit_assert(shpif);
+
+	if(!ipstrtoaddr("1.2.3.5", UNBOUND_DNS_PORT, &addr, &addrlen))
+		log_err("could not parse");
+	shpif = shared_ports_find_if(shp, &addr, addrlen, 0);
+	unit_assert(shpif);
+
+	if(!ipstrtoaddr("::1:2", UNBOUND_DNS_PORT, &addr, &addrlen))
+		log_err("could not parse");
+	shpif = shared_ports_find_if(shp, &addr, addrlen, 0);
+	unit_assert(shpif);
+
+	if(!ipstrtoaddr("::1:3", UNBOUND_DNS_PORT, &addr, &addrlen))
+		log_err("could not parse");
+	shpif = shared_ports_find_if(shp, &addr, addrlen, 0);
+	unit_assert(shpif);
+
+	shared_ports_delete(shp);
+}
+
+/** See if a port is on the shared_ports ports list */
+static int
+pif_list_contains(struct shared_ports_if* shpif, int item)
+{
+	int i;
+	unit_assert(shpif->inuse >= 0 && shpif->inuse <= shpif->avail_total);
+	for(i=0; i< shpif->avail_total - shpif->inuse; i++) {
+		if(shpif->avail_ports[i] == item)
+			return 1;
+	}
+	return 0;
+}
+
+/** See if a number of ports are on the shared_ports list */
+static int
+pif_list_contains_items(struct shared_ports_if* shpif, int item1,
+	int item2, int item3, int item4)
+{
+	if(item1 != -1 && !pif_list_contains(shpif, item1))
+		return 0;
+	if(item2 != -1 && !pif_list_contains(shpif, item2))
+		return 0;
+	if(item3 != -1 && !pif_list_contains(shpif, item3))
+		return 0;
+	if(item4 != -1 && !pif_list_contains(shpif, item4))
+		return 0;
+	return 1;
+}
+
+static void shared_port_test_port(void)
+{
+	struct shared_ports* shp;
+	struct shared_ports_if* shpif;
+	char* ifs[] = {"1.2.3.4", "1.2.3.5"};
+	int availports[] = {1, 2, 3, 4};
+	struct sockaddr_storage addr;
+	socklen_t addrlen;
+	int p1, p2, p3, reused;
+	struct ub_randstate* rnd;
+
+	rnd = ub_initstate(NULL);
+	unit_assert(rnd);
+
+	shp = shared_ports_create(ifs, 2, 1, 1, availports, 4);
+	unit_assert(shp);
+
+	if(!ipstrtoaddr("1.2.3.4", UNBOUND_DNS_PORT, &addr, &addrlen))
+		log_err("could not parse");
+	shpif = shared_ports_find_if(shp, &addr, addrlen, 0);
+	unit_assert(shpif);
+
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 0);
+	unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4));
+
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p1, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p1 != 0);
+	unit_assert(!pif_list_contains(shpif, p1));
+	if(p1 != 1) unit_assert(pif_list_contains(shpif, 1));
+	if(p1 != 2) unit_assert(pif_list_contains(shpif, 2));
+	if(p1 != 3) unit_assert(pif_list_contains(shpif, 3));
+	if(p1 != 4) unit_assert(pif_list_contains(shpif, 4));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 1);
+
+	shared_ports_return_port(shp, shpif, p1);
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 0);
+	unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4));
+
+	/* pick up two items */
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p1, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p1 != 0);
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p2, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p2 != 0);
+	unit_assert(!pif_list_contains(shpif, p1));
+	unit_assert(!pif_list_contains(shpif, p2));
+	if(p1 != 1 && p2 != 1) unit_assert(pif_list_contains(shpif, 1));
+	if(p1 != 2 && p2 != 2) unit_assert(pif_list_contains(shpif, 2));
+	if(p1 != 3 && p2 != 3) unit_assert(pif_list_contains(shpif, 3));
+	if(p1 != 4 && p2 != 4) unit_assert(pif_list_contains(shpif, 4));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 2);
+
+	shared_ports_return_port(shp, shpif, p1);
+	unit_assert(pif_list_contains(shpif, p1));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 1);
+
+	shared_ports_return_port(shp, shpif, p2);
+	unit_assert(pif_list_contains(shpif, p2));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 0);
+	unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4));
+
+	/* pick up three items */
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p1, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p1 != 0);
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p2, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p2 != 0);
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p3, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p3 != 0);
+	unit_assert(!pif_list_contains(shpif, p1));
+	unit_assert(!pif_list_contains(shpif, p2));
+	unit_assert(!pif_list_contains(shpif, p3));
+	if(p1 != 1 && p2 != 1 && p3 != 1)
+		unit_assert(pif_list_contains(shpif, 1));
+	if(p1 != 2 && p2 != 2 && p3 != 2)
+		unit_assert(pif_list_contains(shpif, 2));
+	if(p1 != 3 && p2 != 3 && p3 != 3)
+		unit_assert(pif_list_contains(shpif, 3));
+	if(p1 != 4 && p2 != 4 && p3 != 4)
+		unit_assert(pif_list_contains(shpif, 4));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 3);
+
+	shared_ports_return_port(shp, shpif, p1);
+	unit_assert(pif_list_contains(shpif, p1));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 2);
+
+	shared_ports_return_port(shp, shpif, p2);
+	unit_assert(pif_list_contains(shpif, p2));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 1);
+
+	shared_ports_return_port(shp, shpif, p3);
+	unit_assert(pif_list_contains(shpif, p3));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 0);
+	unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4));
+
+	/* pick up all four items */
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p1, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p1 != 0);
+
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p1, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p1 != 0);
+
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p1, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p1 != 0);
+
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0, 0, &p1, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 0);
+	unit_assert(p1 != 0);
+	unit_assert(!pif_list_contains(shpif, 1));
+	unit_assert(!pif_list_contains(shpif, 2));
+	unit_assert(!pif_list_contains(shpif, 3));
+	unit_assert(!pif_list_contains(shpif, 4));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 4);
+
+	/* more fetches fail, it is fully inuse. */
+	unit_assert(!shared_ports_fetch_random(shp, shpif, rnd, 0, 0, &p2,
+		&reused));
+	unit_assert(!shared_ports_fetch_random(shp, shpif, rnd, 0, 0, &p3,
+		&reused));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 4);
+
+	/* reuse is then always the case */
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0 /* can reuse */, 4 /* reusenum */, &p1, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 1);
+	unit_assert(p1 >= 0 && p1 < 4 /* reusenum */);
+
+	if(!shared_ports_fetch_random(shp, shpif, rnd,
+		0 /* can reuse */, 4 /* reusenum */, &p1, &reused)) {
+		unit_assert(0); /* should succeed */
+	}
+	unit_assert(reused == 1);
+	unit_assert(p1 >= 0 && p1 < 4 /* reusenum */);
+
+	/* return all the ports */
+	shared_ports_return_port(shp, shpif, 1);
+	unit_assert(pif_list_contains(shpif, 1));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 3);
+	shared_ports_return_port(shp, shpif, 2);
+	unit_assert(pif_list_contains(shpif, 2));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 2);
+	shared_ports_return_port(shp, shpif, 3);
+	unit_assert(pif_list_contains(shpif, 3));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 1);
+	shared_ports_return_port(shp, shpif, 4);
+	unit_assert(pif_list_contains(shpif, 4));
+	unit_assert(shpif->avail_total == 4);
+	unit_assert(shpif->inuse == 0);
+	unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4));
+
+	shared_ports_delete(shp);
+	ub_randfree(rnd);
+}
+
 void tcpreuse_test(void)
 {
     unit_show_feature("tcp_reuse");
@@ -486,4 +759,7 @@ void tcpreuse_test(void)
     tcp_reuse_tree_list_test();
     waiting_tcp_list_test();
     reuse_write_wait_test();
+    unit_show_feature("shared_ports");
+    shared_port_test_ifs();
+    shared_port_test_port();
 }
Index: util/fptr_wlist.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/fptr_wlist.c,v
diff -u -p -r1.30 fptr_wlist.c
--- util/fptr_wlist.c	26 May 2026 11:14:11 -0000	1.30
+++ util/fptr_wlist.c	23 Jul 2026 15:23:53 -0000
@@ -610,6 +610,7 @@ int 
 fptr_whitelist_alloc_cleanup(void (*fptr)(void*))
 {
 	if(fptr == &worker_alloc_cleanup) return 1;
+	else if(fptr == &libworker_alloc_cleanup) return 1;
 	return 0;
 }
 
Index: util/module.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/module.h,v
diff -u -p -r1.22 module.h
--- util/module.h	26 May 2026 11:14:11 -0000	1.22
+++ util/module.h	23 Jul 2026 15:23:53 -0000
@@ -721,6 +721,12 @@ struct module_qstate {
 
 	/** whether the reply should be dropped */
 	int is_drop;
+	/** the global quota that was reached, by one of the modules.
+	 * So that continued counting can go on from that point. */
+	int global_quota_reached;
+	/** the global quota that a query started with, it is a subquery,
+	 * so that calling mesh states can see the increase. */
+	int global_quota_started;
 };
 
 /** 
Index: util/netevent.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/netevent.c,v
diff -u -p -r1.43 netevent.c
--- util/netevent.c	26 May 2026 11:14:11 -0000	1.43
+++ util/netevent.c	23 Jul 2026 15:23:53 -0000
@@ -1827,7 +1827,6 @@ doq_send_retry(struct comm_point* c, str
 	char host[256], port[32];
 	struct ngtcp2_cid scid;
 	uint8_t token[NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN];
-	ngtcp2_tstamp ts;
 	ngtcp2_ssize tokenlen, ret;
 
 	if(!doq_print_addr_port(&paddr->addr, paddr->addrlen, host,
@@ -1841,12 +1840,10 @@ doq_send_retry(struct comm_point* c, str
 	scid.datalen = c->doq_socket->sv_scidlen;
 	doq_cid_randfill(&scid, scid.datalen, c->doq_socket->rnd);
 
-	ts = doq_get_timestamp_nanosec();
-
 	tokenlen = ngtcp2_crypto_generate_retry_token(token,
 		c->doq_socket->static_secret, c->doq_socket->static_secret_len,
 		hd->version, (void*)&paddr->addr, paddr->addrlen, &scid,
-		&hd->dcid, ts);
+		&hd->dcid, doq_get_timestamp_nanosec());
 	if(tokenlen < 0) {
 		log_err("ngtcp2_crypto_generate_retry_token failed: %s",
 			ngtcp2_strerror(tokenlen));
@@ -1895,13 +1892,11 @@ doq_verify_retry_token(struct comm_point
 	struct ngtcp2_cid* ocid, struct ngtcp2_pkt_hd* hd)
 {
 	char host[256], port[32];
-	ngtcp2_tstamp ts;
 	if(!doq_print_addr_port(&paddr->addr, paddr->addrlen, host,
 		sizeof(host), port, sizeof(port))) {
 		log_err("doq_verify_retry_token failed");
 		return 0;
 	}
-	ts = doq_get_timestamp_nanosec();
 	verbose(VERB_ALGO, "doq: verifying retry token from %s %s", host,
 		port);
 	if(ngtcp2_crypto_verify_retry_token(ocid,
@@ -1913,7 +1908,7 @@ doq_verify_retry_token(struct comm_point
 		c->doq_socket->static_secret,
 		c->doq_socket->static_secret_len, hd->version,
 		(void*)&paddr->addr, paddr->addrlen, &hd->dcid,
-		10*NGTCP2_SECONDS, ts) != 0) {
+		10*NGTCP2_SECONDS, doq_get_timestamp_nanosec()) != 0) {
 		verbose(VERB_ALGO, "doq: could not verify retry token "
 			"from %s %s", host, port);
 		return 0;
@@ -1928,13 +1923,11 @@ doq_verify_token(struct comm_point* c, s
 	struct ngtcp2_pkt_hd* hd)
 {
 	char host[256], port[32];
-	ngtcp2_tstamp ts;
 	if(!doq_print_addr_port(&paddr->addr, paddr->addrlen, host,
 		sizeof(host), port, sizeof(port))) {
 		log_err("doq_verify_token failed");
 		return 0;
 	}
-	ts = doq_get_timestamp_nanosec();
 	verbose(VERB_ALGO, "doq: verifying token from %s %s", host, port);
 	if(ngtcp2_crypto_verify_regular_token(
 #ifdef HAVE_STRUCT_NGTCP2_PKT_HD_TOKENLEN
@@ -1944,7 +1937,7 @@ doq_verify_token(struct comm_point* c, s
 #endif
 		c->doq_socket->static_secret, c->doq_socket->static_secret_len,
 		(void*)&paddr->addr, paddr->addrlen, 3600*NGTCP2_SECONDS,
-		ts) != 0) {
+		doq_get_timestamp_nanosec()) != 0) {
 		verbose(VERB_ALGO, "doq: could not verify token from %s %s",
 			host, port);
 		return 0;
@@ -2171,6 +2164,7 @@ doq_pickup_timer(struct comm_point* c)
 {
 	struct doq_timer* t;
 	struct timeval tv;
+	ngtcp2_tstamp ts = 0;
 	int have_time = 0;
 	memset(&tv, 0, sizeof(tv));
 
@@ -2180,27 +2174,24 @@ doq_pickup_timer(struct comm_point* c)
 			t->worker_doq_socket == c->doq_socket) {
 			/* pick up this element */
 			t->worker_doq_socket = c->doq_socket;
+			memcpy(&tv, &t->time_real, sizeof(tv));
+			ts = t->time_mono;
 			have_time = 1;
-			memcpy(&tv, &t->time, sizeof(tv));
 			break;
 		}
 	}
 	lock_rw_unlock(&c->doq_socket->table->lock);
-
+	c->doq_socket->marked_time = ts;
 	if(have_time) {
 		struct timeval rel;
 		timeval_subtract(&rel, &tv, c->doq_socket->now_tv);
 		comm_timer_set(c->doq_socket->timer, &rel);
-		memcpy(&c->doq_socket->marked_time, &tv,
-			sizeof(c->doq_socket->marked_time));
 		verbose(VERB_ALGO, "doq pickup timer at %d.%6.6d in %d.%6.6d",
 			(int)tv.tv_sec, (int)tv.tv_usec, (int)rel.tv_sec,
 			(int)rel.tv_usec);
 	} else {
 		if(comm_timer_is_set(c->doq_socket->timer))
 			comm_timer_disable(c->doq_socket->timer);
-		memset(&c->doq_socket->marked_time, 0,
-			sizeof(c->doq_socket->marked_time));
 		verbose(VERB_ALGO, "doq timer disabled");
 	}
 }
@@ -2213,13 +2204,14 @@ doq_done_setup_timer_and_write(struct co
 	uint8_t cid[NGTCP2_MAX_CIDLEN];
 	rbnode_type* node;
 	struct timeval new_tv;
+	ngtcp2_tstamp new_ts;
 	int write_change = 0, timer_change = 0;
 
 	/* No longer in callbacks, so the pointer to doq_socket is back
 	 * to NULL. */
 	conn->doq_socket = NULL;
 
-	if(doq_conn_check_timer(conn, &new_tv))
+	if(doq_conn_check_timer(conn, &new_tv, &new_ts))
 		timer_change = 1;
 	if( (conn->write_interest && !conn->on_write_list) ||
 		(!conn->write_interest && conn->on_write_list))
@@ -2265,7 +2257,7 @@ doq_done_setup_timer_and_write(struct co
 	}
 	if(timer_change) {
 		doq_timer_set(c->doq_socket->table, &conn->timer,
-			c->doq_socket, &new_tv);
+			c->doq_socket, &new_tv, new_ts);
 	}
 	lock_rw_unlock(&c->doq_socket->table->lock);
 	lock_basic_unlock(&conn->lock);
@@ -2429,7 +2421,7 @@ doq_write_blocked_pkt(struct comm_point*
 	return 1;
 }
 
-/** doq find a timer that timeouted and return the conn, locked. */
+/** doq find a timer that timed out and return the conn, locked. */
 static struct doq_conn*
 doq_timer_timeout_conn(struct doq_server_socket* doq_socket)
 {
@@ -2442,7 +2434,7 @@ doq_timer_timeout_conn(struct doq_server
 		conn = t->conn;
 
 		/* If now < timer then no further timeouts in tree. */
-		if(timeval_smaller(doq_socket->now_tv, &t->time)) {
+		if(timeval_smaller(doq_socket->now_tv, &t->time_real)) {
 			lock_rw_unlock(&doq_socket->table->lock);
 			return NULL;
 		}
@@ -2465,11 +2457,11 @@ doq_timer_erase_marker(struct doq_server
 {
 	struct doq_timer* t;
 	lock_rw_wrlock(&doq_socket->table->lock);
-	t = doq_timer_find_time(doq_socket->table, &doq_socket->marked_time);
+	t = doq_timer_find_time(doq_socket->table, doq_socket->marked_time);
 	if(t && t->worker_doq_socket == doq_socket)
 		t->worker_doq_socket = NULL;
 	lock_rw_unlock(&doq_socket->table->lock);
-	memset(&doq_socket->marked_time, 0, sizeof(doq_socket->marked_time));
+	doq_socket->marked_time = 0;
 }
 
 void
@@ -2776,7 +2768,7 @@ doq_server_socket_create(struct doq_tabl
 		free(doq_socket);
 		return NULL;
 	}
-	memset(&doq_socket->marked_time, 0, sizeof(doq_socket->marked_time));
+	doq_socket->marked_time = 0;
 	comm_base_timept(base, &doq_socket->now_tt, &doq_socket->now_tv);
 	doq_socket->cfg = cfg;
 	return doq_socket;
@@ -3174,7 +3166,7 @@ static void http2_stream_delete(struct h
 {
 	if(h2_stream->mesh_state) {
 		mesh_state_remove_reply(h2_stream->mesh, h2_stream->mesh_state,
-			h2_session->c);
+			h2_session->c, NULL);
 		h2_stream->mesh_state = NULL;
 	}
 	http2_req_stream_clear(h2_stream);
@@ -6685,7 +6677,9 @@ comm_point_send_reply(struct comm_reply 
 	log_assert(repinfo && repinfo->c);
 #ifdef USE_DNSCRYPT
 	buffer = repinfo->c->dnscrypt_buffer;
-	if(!dnsc_handle_uncurved_request(repinfo)) {
+	if(!dnsc_handle_uncurved_request(repinfo,
+		repinfo->c->tcp_req_info?
+		repinfo->c->tcp_req_info->spool_buffer:repinfo->c->buffer)) {
 		return;
 	}
 #else
Index: util/netevent.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/netevent.h,v
diff -u -p -r1.26 netevent.h
--- util/netevent.h	26 Sep 2025 07:32:37 -0000	1.26
+++ util/netevent.h	23 Jul 2026 15:23:53 -0000
@@ -187,6 +187,8 @@ struct comm_reply {
 	/** port number for doq */
 	int doq_srcport;
 #endif /* HAVE_NGTCP2 */
+	/** The doq stream to register mesh states to. */
+	struct doq_stream* doq_stream;
 };
 
 /**
@@ -1093,8 +1095,10 @@ struct doq_server_socket {
 	struct doq_pkt_addr* blocked_paddr;
 	/** timer for this worker on this comm_point to wait on. */
 	struct comm_timer* timer;
+#ifdef HAVE_NGTCP2
 	/** the timer that is marked by the doq_socket as waited on. */
-	struct timeval marked_time;
+	ngtcp2_tstamp marked_time;
+#endif
 	/** the current time for use by time functions, time_t. */
 	time_t* now_tt;
 	/** the current time for use by time functions, timeval. */
Index: util/data/msgparse.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/data/msgparse.c,v
diff -u -p -r1.14 msgparse.c
--- util/data/msgparse.c	26 May 2026 11:14:11 -0000	1.14
+++ util/data/msgparse.c	23 Jul 2026 15:23:53 -0000
@@ -687,6 +687,9 @@ calc_size(sldns_buffer* pkt, uint16_t ty
 			}
 			rdf++;
 		}
+		/* rdata ended before all _dname_count names were seen */
+		if(count != 0)
+			return 0; /* the rdata is too short. */
 	}
 	/* remaining rdata */
 	rr->size += pkt_len;
@@ -1068,13 +1071,13 @@ parse_edns_options_from_query(uint8_t* r
 			 * purposes. It will be overwritten if (re)creation
 			 * is needed.
 			 */
-			if(repinfo->remote_addr.ss_family == AF_INET) {
+			if(repinfo->client_addr.ss_family == AF_INET) {
 				memcpy(server_cookie + 16,
-					&((struct sockaddr_in*)&repinfo->remote_addr)->sin_addr, 4);
+					&((struct sockaddr_in*)&repinfo->client_addr)->sin_addr, 4);
 			} else {
 				cookie_is_v4 = 0;
 				memcpy(server_cookie + 16,
-					&((struct sockaddr_in6*)&repinfo->remote_addr)->sin6_addr, 16);
+					&((struct sockaddr_in6*)&repinfo->client_addr)->sin6_addr, 16);
 			}
 
 			if(cfg->cookie_secret_file &&
Index: util/data/packed_rrset.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/data/packed_rrset.c,v
diff -u -p -r1.8 packed_rrset.c
--- util/data/packed_rrset.c	26 May 2026 11:14:11 -0000	1.8
+++ util/data/packed_rrset.c	23 Jul 2026 15:23:53 -0000
@@ -198,6 +198,7 @@ get_cname_target(struct ub_packed_rrset_
 {
 	struct packed_rrset_data* d;
 	size_t len;
+	if(!rrset) return;
 	if(ntohs(rrset->rk.type) != LDNS_RR_TYPE_CNAME && 
 		ntohs(rrset->rk.type) != LDNS_RR_TYPE_DNAME)
 		return;
Index: validator/val_sigcrypt.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/val_sigcrypt.c,v
diff -u -p -r1.17 val_sigcrypt.c
--- validator/val_sigcrypt.c	26 May 2026 11:14:11 -0000	1.17
+++ validator/val_sigcrypt.c	23 Jul 2026 15:23:53 -0000
@@ -1094,6 +1094,7 @@ canonicalize_rdata(sldns_buffer* buf, st
 	size_t len)
 {
 	uint8_t* datstart = sldns_buffer_current(buf)-len+2;
+	size_t firstlen;
 	switch(ntohs(rrset->rk.type)) {
 		case LDNS_RR_TYPE_NXT: 
 		case LDNS_RR_TYPE_NS:
@@ -1113,8 +1114,9 @@ canonicalize_rdata(sldns_buffer* buf, st
 		case LDNS_RR_TYPE_SOA:
 			/* two names after another */
 			query_dname_tolower(datstart);
-			query_dname_tolower(datstart + 
-				dname_valid(datstart, len-2));
+			firstlen = dname_valid(datstart, len-2);
+			if(firstlen && firstlen < len-2)
+				query_dname_tolower(datstart + firstlen);
 			return;
 		case LDNS_RR_TYPE_RT:
 		case LDNS_RR_TYPE_AFSDB:
@@ -1141,8 +1143,9 @@ canonicalize_rdata(sldns_buffer* buf, st
 				return;
 			datstart += 2;
 			query_dname_tolower(datstart);
-			query_dname_tolower(datstart + 
-				dname_valid(datstart, len-2-2));
+			firstlen = dname_valid(datstart, len-2-2);
+			if(firstlen && firstlen < len-2-2)
+				query_dname_tolower(datstart + firstlen);
 			return;
 		case LDNS_RR_TYPE_NAPTR:
 			if(len < 2+4)
@@ -1662,6 +1665,13 @@ dnskey_verify_rrset_sig(struct regional*
 	if((int)sig[2+3] > dname_signame_label_count(rrset->rk.dname)) {
 		verbose(VERB_QUERY, "verify: labelcount out of range");
 		*reason = "signature labelcount out of range";
+		if(reason_bogus)
+			*reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+		return sec_status_bogus;
+	}
+	if((int)sig[2+3] < dname_signame_label_count(signer)) {
+		verbose(VERB_QUERY, "verify: RRSIG label count too low for signer");
+		*reason = "signature labelcount lower than signature signer";
 		if(reason_bogus)
 			*reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
 		return sec_status_bogus;
Index: validator/val_utils.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/val_utils.c,v
diff -u -p -r1.18 val_utils.c
--- validator/val_utils.c	26 May 2026 11:14:11 -0000	1.18
+++ validator/val_utils.c	23 Jul 2026 15:23:53 -0000
@@ -157,7 +157,7 @@ val_classify_response(uint16_t query_fla
 }
 
 /** Get signer name from RRSIG */
-static void
+void
 rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname, size_t* slen)
 {
 	/* RRSIG rdata is not allowed to be compressed, it is stored
@@ -439,10 +439,15 @@ val_verify_rrset(struct module_env* env,
 	 * only improves security status 
 	 * and bogus is set only once, even if we rechecked the status */
 	if(sec > d->security) {
+		int wc_expanded = 0;
 		d->security = sec;
-		if(sec == sec_status_secure)
+		if(sec == sec_status_secure) {
+			uint8_t* wc = NULL;
+			size_t wclen = 0;
 			d->trust = rrset_trust_validated;
-		else if(sec == sec_status_bogus) {
+			if(val_rrset_wildcard(rrset, &wc, &wclen) && wc)
+				wc_expanded = 1;
+		} else if(sec == sec_status_bogus) {
 			size_t i;
 			/* update ttl for rrset to fixed value. */
 			d->ttl = ve->bogus_ttl;
@@ -455,7 +460,11 @@ val_verify_rrset(struct module_env* env,
 			lock_basic_unlock(&ve->bogus_lock);
 		}
 		/* if status updated - store in cache for reuse */
-		rrset_update_sec_status(env->rrset_cache, rrset, *env->now);
+		/* For a wildcard rrset, that is secure, do not store this
+		 * into the cache, because it changes proofs around the
+		 * item. */
+		if(!wc_expanded)
+			rrset_update_sec_status(env->rrset_cache, rrset, *env->now);
 	}
 
 	return sec;
Index: validator/val_utils.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/val_utils.h,v
diff -u -p -r1.10 val_utils.h
--- validator/val_utils.h	26 May 2026 11:14:11 -0000	1.10
+++ validator/val_utils.h	23 Jul 2026 15:23:53 -0000
@@ -438,4 +438,8 @@ struct dns_msg* val_find_DS(struct modul
 int derive_cname_from_dname(struct ub_packed_rrset_key* cname,
 	struct ub_packed_rrset_key* dname, uint8_t* out, size_t outlen);
 
+/** Get signer name from RRSIG, sname is NULL if malformed. */
+void rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname,
+	size_t* slen);
+
 #endif /* VALIDATOR_VAL_UTILS_H */
Index: validator/validator.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/validator.c,v
diff -u -p -r1.29 validator.c
--- validator/validator.c	26 May 2026 11:14:11 -0000	1.29
+++ validator/validator.c	23 Jul 2026 15:23:53 -0000
@@ -517,6 +517,14 @@ generate_request(struct module_qstate* q
 		/* add our blacklist to the query blacklist */
 		sock_list_merge(&(*newq)->blacklist, (*newq)->region,
 			vq->chain_blacklist);
+		/* start its global quota counter where this one is. */
+		if(qstate->global_quota_reached >
+			(*newq)->global_quota_reached) {
+			(*newq)->global_quota_started =
+				qstate->global_quota_reached;
+			(*newq)->global_quota_reached =
+				qstate->global_quota_reached;
+		}
 	}
 	qstate->ext_state[id] = module_wait_subquery;
 	return 1;
@@ -1043,7 +1051,14 @@ validate_positive_response(struct module
 	uint8_t* wc = NULL;
 	size_t wl;
 	int wc_cached = 0;
+	int wc_to_cache = 0;
+	uint8_t* cache_wc = NULL;
+	size_t cache_wl = 0;
+	struct ub_packed_rrset_key* cache_s = NULL;
 	int wc_NSEC_ok = 0;
+	/* This is used to update the RRset cache, with the combination
+	 * of the dname expansion and this wildcard, for security status. */
+	struct ub_packed_rrset_key* wc_rrset = NULL;
 	int nsec3s_seen = 0;
 	size_t i;
 	struct ub_packed_rrset_key* s;
@@ -1062,14 +1077,20 @@ validate_positive_response(struct module
 				ntohs(s->rk.type), ntohs(s->rk.rrset_class));
 			chase_reply->security = sec_status_bogus;
 			update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+			if(wc_rrset)
+				((struct packed_rrset_data*)wc_rrset->
+				entry.data)->security = sec_status_bogus;
 			return;
 		}
 		if(wc && !wc_cached && env->cfg->aggressive_nsec) {
-			rrset_cache_update_wildcard(env->rrset_cache, s, wc, wl,
-				env->alloc, *env->now);
+			/* Postpone cache adjust until proof has succeeded. */
+			wc_to_cache = 1;
+			cache_wc = wc;
+			cache_wl = wl;
+			cache_s = s;
 			wc_cached = 1;
 		}
-
+		if(wc) wc_rrset = s;
 	}
 
 	/* validate the AUTHORITY section as well - this will generally be 
@@ -1126,8 +1147,15 @@ validate_positive_response(struct module
 			"did not exist");
 		chase_reply->security = sec_status_bogus;
 		update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+		if(wc_rrset)
+			((struct packed_rrset_data*)wc_rrset->
+			entry.data)->security = sec_status_bogus;
 		return;
 	}
+	if(wc_to_cache) {
+		rrset_cache_update_wildcard(env->rrset_cache, cache_s,
+			cache_wc, cache_wl, env->alloc, *env->now);
+	}
 
 	verbose(VERB_ALGO, "Successfully validated positive response");
 	chase_reply->security = sec_status_secure;
@@ -1527,6 +1555,16 @@ validate_any_response(struct module_env*
 			"did not exist");
 		chase_reply->security = sec_status_bogus;
 		update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+		/* Make the expanded name and wildcard RRSIG rrsets bogus */
+		for(i=0; i<chase_reply->an_numrrsets; i++) {
+			uint8_t* cwc = NULL;
+			size_t cwl = 0;
+			s = chase_reply->rrsets[i];
+			if(val_rrset_wildcard(s, &cwc, &cwl) && cwc) {
+				((struct packed_rrset_data*)s->
+				entry.data)->security = sec_status_bogus;
+			}
+		}
 		return;
 	}
 
@@ -1564,6 +1602,9 @@ validate_cname_response(struct module_en
 	uint8_t* wc = NULL;
 	size_t wl;
 	int wc_NSEC_ok = 0;
+	/* This is used to update the RRset cache, with the combination
+	 * of the dname expansion and this wildcard, for security status. */
+	struct ub_packed_rrset_key* wc_rrset = NULL;
 	int nsec3s_seen = 0;
 	size_t i;
 	struct ub_packed_rrset_key* s;
@@ -1584,6 +1625,7 @@ validate_cname_response(struct module_en
 			update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
 			return;
 		}
+		if(wc) wc_rrset = s;
 		
 		/* Refuse wildcarded DNAMEs rfc 4597. 
 		 * Do not follow a wildcarded DNAME because 
@@ -1595,6 +1637,9 @@ validate_cname_response(struct module_en
 				ntohs(s->rk.type), ntohs(s->rk.rrset_class));
 			chase_reply->security = sec_status_bogus;
 			update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+			if(wc_rrset)
+				((struct packed_rrset_data*)wc_rrset->
+				entry.data)->security = sec_status_bogus;
 			return;
 		}
 
@@ -1659,6 +1704,9 @@ validate_cname_response(struct module_en
 			"did not exist");
 		chase_reply->security = sec_status_bogus;
 		update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+		if(wc_rrset)
+			((struct packed_rrset_data*)wc_rrset->
+			entry.data)->security = sec_status_bogus;
 		return;
 	}
 
@@ -3535,6 +3583,11 @@ val_inform_super(struct module_qstate* q
 	if(!vq) {
 		verbose(VERB_ALGO, "super: has no validator state");
 		return;
+	}
+	/* Pick up the global quota limit from the subquery. */
+	if(qstate->global_quota_reached > qstate->global_quota_started) {
+		super->global_quota_reached += qstate->global_quota_reached -
+			qstate->global_quota_started;
 	}
 	if(vq->wait_prime_ta) {
 		vq->wait_prime_ta = 0;
Index: config.guess
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/config.guess,v
diff -u -p -r1.18 config.guess
--- config.guess	31 Aug 2025 21:41:09 -0000	1.18
+++ config.guess	23 Jul 2026 15:23:53 -0000
@@ -1,10 +1,10 @@
 #! /bin/sh
 # Attempt to guess a canonical system name.
-#   Copyright 1992-2025 Free Software Foundation, Inc.
+#   Copyright 1992-2026 Free Software Foundation, Inc.
 
 # shellcheck disable=SC2006,SC2268 # see below for rationale
 
-timestamp='2025-07-10'
+timestamp='2026-05-17'
 
 # 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-2025 Free Software Foundation, Inc.
+Copyright 1992-2026 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."
@@ -150,7 +150,7 @@ UNAME_SYSTEM=`(uname -s) 2>/dev/null` ||
 UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
 
 case $UNAME_SYSTEM in
-Linux|GNU|GNU/*)
+Ironclad|Linux|GNU|GNU/*)
 	LIBC=unknown
 
 	set_cc_for_build
@@ -167,6 +167,8 @@ Linux|GNU|GNU/*)
 	LIBC=gnu
 	#elif defined(__LLVM_LIBC__)
 	LIBC=llvm
+	#elif defined(__mlibc__)
+	LIBC=mlibc
 	#else
 	#include <stdarg.h>
 	/* First heuristic to detect musl libc.  */
@@ -1186,6 +1188,9 @@ EOF
     sparc:Linux:*:* | sparc64:Linux:*:*)
 	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
 	;;
+    sw_64:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     tile*:Linux:*:*)
 	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
 	;;
@@ -1598,10 +1603,10 @@ EOF
 	GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE
 	;;
     x86_64:[Ii]ronclad:*:*|i?86:[Ii]ronclad:*:*)
-	GUESS=$UNAME_MACHINE-pc-ironclad-mlibc
+	GUESS=$UNAME_MACHINE-pc-ironclad-$LIBC
 	;;
     *:[Ii]ronclad:*:*)
-	GUESS=$UNAME_MACHINE-unknown-ironclad-mlibc
+	GUESS=$UNAME_MACHINE-unknown-ironclad-$LIBC
 	;;
 esac
 
Index: config.sub
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/config.sub,v
diff -u -p -r1.17 config.sub
--- config.sub	31 Aug 2025 21:41:09 -0000	1.17
+++ config.sub	23 Jul 2026 15:23:53 -0000
@@ -1,10 +1,10 @@
 #! /bin/sh
 # Configuration validation subroutine script.
-#   Copyright 1992-2025 Free Software Foundation, Inc.
+#   Copyright 1992-2026 Free Software Foundation, Inc.
 
 # shellcheck disable=SC2006,SC2268,SC2162 # see below for rationale
 
-timestamp='2025-07-10'
+timestamp='2026-05-17'
 
 # 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-2025 Free Software Foundation, Inc.
+Copyright 1992-2026 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."
@@ -1432,6 +1432,7 @@ case $cpu-$vendor in
 			| sparcv9v \
 			| spu \
 			| sv1 \
+			| sw_64 \
 			| sx* \
 			| tahoe \
 			| thumbv7* \
@@ -1525,7 +1526,7 @@ EOF
 		;;
 	ironclad*)
 		kernel=ironclad
-		os=`echo "$basic_os" | sed -e 's|ironclad|mlibc|'`
+		os=`echo "$basic_os" | sed -e 's|ironclad|gnu|'`
 		;;
 	linux*)
 		kernel=linux
@@ -2220,7 +2221,7 @@ case $kernel-$os-$obj in
 		;;
 	uclinux-uclibc*- | uclinux-gnu*- )
 		;;
-	ironclad-mlibc*-)
+	ironclad-gnu*- | ironclad-mlibc*- )
 		;;
 	managarm-mlibc*- | managarm-kernel*- )
 		;;
Index: configure
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/configure,v
diff -u -p -r1.62 configure
--- configure	26 May 2026 11:14:10 -0000	1.62
+++ configure	23 Jul 2026 15:23:53 -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.25.1.
+# Generated by GNU Autoconf 2.71 for unbound 1.25.2.
 #
 # 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.25.1'
-PACKAGE_STRING='unbound 1.25.1'
+PACKAGE_VERSION='1.25.2'
+PACKAGE_STRING='unbound 1.25.2'
 PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues'
 PACKAGE_URL=''
 
@@ -1516,7 +1516,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.25.1 to adapt to many kinds of systems.
+\`configure' configures unbound 1.25.2 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1582,7 +1582,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of unbound 1.25.1:";;
+     short | recursive ) echo "Configuration of unbound 1.25.2:";;
    esac
   cat <<\_ACEOF
 
@@ -1835,7 +1835,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-unbound configure 1.25.1
+unbound configure 1.25.2
 generated by GNU Autoconf 2.71
 
 Copyright (C) 2021 Free Software Foundation, Inc.
@@ -2492,7 +2492,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.25.1, which was
+It was created by unbound $as_me 1.25.2, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   $ $0$ac_configure_args_raw
@@ -3256,11 +3256,11 @@ UNBOUND_VERSION_MAJOR=1
 
 UNBOUND_VERSION_MINOR=25
 
-UNBOUND_VERSION_MICRO=1
+UNBOUND_VERSION_MICRO=2
 
 
 LIBUNBOUND_CURRENT=9
-LIBUNBOUND_REVISION=37
+LIBUNBOUND_REVISION=38
 LIBUNBOUND_AGE=1
 # 1.0.0 had 0:12:0
 # 1.0.1 had 0:13:0
@@ -3365,6 +3365,7 @@ LIBUNBOUND_AGE=1
 # 1.24.2 had 9:35:1
 # 1.25.0 had 9:36:1
 # 1.25.1 had 9:37:1
+# 1.25.2 had 9:38:1
 
 #   Current  -- the number of the binary API that we're implementing
 #   Revision -- which iteration of the implementation of the binary
@@ -23677,6 +23678,29 @@ printf "%s\n" "no" >&6; }
 fi
 rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
 
+    ac_fn_check_decl "$LINENO" "CLOCK_MONOTONIC
+        " "ac_cv_have_decl_CLOCK_MONOTONIC_________" "$ac_includes_default
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# ifdef HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+" "$ac_c_undeclared_builtin_options" "CFLAGS"
+if test "x$ac_cv_have_decl_CLOCK_MONOTONIC_________" = xyes
+then :
+
+
+else $as_nop
+  as_fn_error $? "ngtcp2 for QUIC needs at least CLOCK_MONOTONIC on the system" "$LINENO" 5
+
+fi
+
 fi
 
 # set static linking for uninstalled libraries if requested
@@ -26307,7 +26331,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 1
 
 
 
-version=1.25.1
+version=1.25.2
 
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5
 printf %s "checking for build time... " >&6; }
@@ -26837,7 +26861,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.25.1, which was
+This file was extended by unbound $as_me 1.25.2, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -26905,7 +26929,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.25.1
+unbound config.status 1.25.2
 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.62 configure.ac
--- configure.ac	26 May 2026 11:14:11 -0000	1.62
+++ configure.ac	23 Jul 2026 15:23:53 -0000
@@ -12,14 +12,14 @@ sinclude(dnscrypt/dnscrypt.m4)
 # must be numbers. ac_defun because of later processing
 m4_define([VERSION_MAJOR],[1])
 m4_define([VERSION_MINOR],[25])
-m4_define([VERSION_MICRO],[1])
+m4_define([VERSION_MICRO],[2])
 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=37
+LIBUNBOUND_REVISION=38
 LIBUNBOUND_AGE=1
 # 1.0.0 had 0:12:0
 # 1.0.1 had 0:13:0
@@ -124,6 +124,7 @@ LIBUNBOUND_AGE=1
 # 1.24.2 had 9:35:1
 # 1.25.0 had 9:36:1
 # 1.25.1 had 9:37:1
+# 1.25.2 had 9:38:1
 
 #   Current  -- the number of the binary API that we're implementing
 #   Revision -- which iteration of the implementation of the binary
@@ -1734,6 +1735,22 @@ if test x_$withval = x_yes -o x_$withval
     ],[
 	AC_MSG_RESULT(no)
     ])
+
+    AC_CHECK_DECL([CLOCK_MONOTONIC]
+        , []
+        , [AC_MSG_ERROR([ngtcp2 for QUIC needs at least CLOCK_MONOTONIC on the system])]
+        , [AC_INCLUDES_DEFAULT
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# ifdef HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+        ])
 
 fi