Download raw body.
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
unbound 1.25.2