git: 26ba9518ffec - stable/13 - unbound: Vendor import 1.17.1

From: Cy Schubert <cy_at_FreeBSD.org>
Date: Tue, 14 Feb 2023 18:33:46 UTC
The branch stable/13 has been updated by cy:

URL: https://cgit.FreeBSD.org/src/commit/?id=26ba9518ffeccd0b64d2f610e731989193844814

commit 26ba9518ffeccd0b64d2f610e731989193844814
Author:     Cy Schubert <cy@FreeBSD.org>
AuthorDate: 2023-01-15 05:39:31 +0000
Commit:     Cy Schubert <cy@FreeBSD.org>
CommitDate: 2023-02-14 18:32:39 +0000

    unbound: Vendor import 1.17.1
    
    Release notes at
        https://www.nlnetlabs.nl/news/2023/Jan/12/unbound-1.17.1-released/.
    
    Merge commit '7699e1386a16236002b26107ffd2dcbde375e197' into main
    
    (cherry picked from commit 1838dec31895fd4752fa8631322ab93be0705a66)
---
 contrib/unbound/Makefile.in                        |   4 +-
 contrib/unbound/README.md                          |   1 +
 contrib/unbound/cachedb/cachedb.c                  |  21 ++-
 contrib/unbound/configure                          |  25 +--
 contrib/unbound/configure.ac                       |   5 +-
 contrib/unbound/contrib/unbound.service.in         |   5 +-
 contrib/unbound/daemon/cachedump.c                 |   9 +-
 contrib/unbound/daemon/daemon.c                    |  91 ++++++++-
 contrib/unbound/daemon/daemon.h                    |   6 +
 contrib/unbound/daemon/remote.c                    |  16 +-
 contrib/unbound/daemon/worker.c                    |  21 ++-
 contrib/unbound/daemon/worker.h                    |   4 +-
 contrib/unbound/doc/Changelog                      |  86 +++++++++
 contrib/unbound/doc/README                         |   2 +-
 contrib/unbound/doc/example.conf.in                |  18 +-
 contrib/unbound/doc/libunbound.3.in                |   4 +-
 contrib/unbound/doc/unbound-anchor.8.in            |   2 +-
 contrib/unbound/doc/unbound-checkconf.8.in         |   2 +-
 contrib/unbound/doc/unbound-control.8.in           |  10 +-
 contrib/unbound/doc/unbound-host.1.in              |   2 +-
 contrib/unbound/doc/unbound.8.in                   |   4 +-
 contrib/unbound/doc/unbound.conf.5.in              |  36 +++-
 contrib/unbound/edns-subnet/subnetmod.c            |  11 ++
 contrib/unbound/iterator/iter_utils.c              |   2 +
 contrib/unbound/iterator/iterator.c                |   7 +-
 contrib/unbound/iterator/iterator.h                |  10 +-
 contrib/unbound/libunbound/context.c               |   1 +
 contrib/unbound/libunbound/context.h               |   6 +
 contrib/unbound/libunbound/libunbound.c            |  39 +++-
 contrib/unbound/libunbound/libworker.c             |   1 +
 contrib/unbound/libunbound/unbound-event.h         |   3 +-
 contrib/unbound/services/authzone.c                |   1 +
 contrib/unbound/services/cache/dns.c               |   8 +
 contrib/unbound/sldns/rrdef.h                      |   4 +-
 contrib/unbound/smallapp/unbound-control.c         |  12 +-
 .../09-unbound-control.tdir/conf.bad_credentials   |   5 +
 .../conf.spoofed_credentials                       |   5 +
 .../unbound/testdata/cachedb_servfail_cname.crpl   | 181 ++++++++++++++++++
 .../testdata/serve_expired_cached_servfail.rpl     | 130 +++++++++++++
 .../unbound/testdata/subnet_cached_servfail.crpl   | 167 +++++++++++++++++
 contrib/unbound/util/config_file.c                 |   9 +
 contrib/unbound/util/config_file.h                 |   7 +
 contrib/unbound/util/configlexer.lex               |   3 +
 contrib/unbound/util/configparser.y                |  34 +++-
 contrib/unbound/util/iana_ports.inc                |   1 +
 contrib/unbound/util/netevent.c                    |  30 ++-
 contrib/unbound/util/tube.c                        |  53 ++++--
 usr.sbin/unbound/config.h                          | 204 +++++++++++++++++----
 48 files changed, 1169 insertions(+), 139 deletions(-)

diff --git a/contrib/unbound/Makefile.in b/contrib/unbound/Makefile.in
index e7c76c2588aa..bc021aa1eb00 100644
--- a/contrib/unbound/Makefile.in
+++ b/contrib/unbound/Makefile.in
@@ -616,7 +616,7 @@ install-all:	all $(PYTHONMOD_INSTALL) $(PYUNBOUND_INSTALL) $(UNBOUND_EVENT_INSTA
 	$(INSTALL) -c -m 644 doc/unbound.conf.5 $(DESTDIR)$(mandir)/man5
 	$(INSTALL) -c -m 644 doc/unbound-host.1 $(DESTDIR)$(mandir)/man1
 	$(INSTALL) -c -m 755 unbound-control-setup $(DESTDIR)$(sbindir)/unbound-control-setup
-	if test ! -e $(DESTDIR)$(configfile); then $(INSTALL) -d `dirname $(DESTDIR)$(configfile)`; $(INSTALL) -c -m 644 doc/example.conf $(DESTDIR)$(configfile); fi
+	if test ! -e "$(DESTDIR)$(configfile)"; then $(INSTALL) -d `dirname "$(DESTDIR)$(configfile)"`; $(INSTALL) -c -m 644 doc/example.conf "$(DESTDIR)$(configfile)"; fi
 
 pythonmod-uninstall:
 	rm -f -- $(DESTDIR)$(PYTHON_SITE_PKG)/unboundmodule.py
@@ -645,7 +645,7 @@ uninstall:	$(PYTHONMOD_UNINSTALL) $(PYUNBOUND_UNINSTALL) $(UNBOUND_EVENT_UNINSTA
 	rm -f -- $(DESTDIR)$(includedir)/unbound.h
 	$(LIBTOOL) --mode=uninstall rm -f $(DESTDIR)$(libdir)/libunbound.la
 	@echo
-	@echo "You still need to remove "`dirname $(DESTDIR)$(configfile)`" , $(DESTDIR)$(configfile) by hand"
+	@echo "You still need to remove "`dirname "$(DESTDIR)$(configfile)"`" , $(DESTDIR)$(configfile) by hand"
 
 iana_update:
 	curl -o port-numbers.tmp https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml --compressed
diff --git a/contrib/unbound/README.md b/contrib/unbound/README.md
index d1bbcf2b7797..c3d9bc2492ef 100644
--- a/contrib/unbound/README.md
+++ b/contrib/unbound/README.md
@@ -4,6 +4,7 @@
 [![Packaging status](https://repology.org/badge/tiny-repos/unbound.svg)](https://repology.org/project/unbound/versions)
 [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/unbound.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:unbound)
 [![Documentation Status](https://readthedocs.org/projects/unbound/badge/?version=latest)](https://unbound.readthedocs.io/en/latest/?badge=latest)
+[![Mastodon Follow](https://img.shields.io/mastodon/follow/109262826617293067?domain=https%3A%2F%2Ffosstodon.org&style=social)](https://fosstodon.org/@nlnetlabs)
 
 Unbound is a validating, recursive, caching DNS resolver. It is designed to be
 fast and lean and incorporates modern features based on open standards. If you
diff --git a/contrib/unbound/cachedb/cachedb.c b/contrib/unbound/cachedb/cachedb.c
index b07743d85259..245daa986967 100644
--- a/contrib/unbound/cachedb/cachedb.c
+++ b/contrib/unbound/cachedb/cachedb.c
@@ -390,6 +390,15 @@ prep_data(struct module_qstate* qstate, struct sldns_buffer* buf)
 
 	if(!qstate->return_msg || !qstate->return_msg->rep)
 		return 0;
+	/* do not store failures like SERVFAIL in the cachedb, this avoids
+	 * overwriting expired, valid, content with broken content. */
+	if(FLAGS_GET_RCODE(qstate->return_msg->rep->flags) !=
+		LDNS_RCODE_NOERROR &&
+	   FLAGS_GET_RCODE(qstate->return_msg->rep->flags) !=
+		LDNS_RCODE_NXDOMAIN &&
+	   FLAGS_GET_RCODE(qstate->return_msg->rep->flags) !=
+		LDNS_RCODE_YXDOMAIN)
+		return 0;
 	/* We don't store the reply if its TTL is 0 unless serve-expired is
 	 * enabled.  Such a reply won't be reusable and simply be a waste for
 	 * the backend.  It's also compatible with the default behavior of
@@ -542,10 +551,16 @@ parse_data(struct module_qstate* qstate, struct sldns_buffer* buf)
 		verbose(VERB_ALGO, "cachedb msg expired");
 		/* If serve-expired is enabled, we still use an expired message
 		 * setting the TTL to 0. */
-		if(qstate->env->cfg->serve_expired)
-			adjust = -1;
-		else
+		if(!qstate->env->cfg->serve_expired ||
+			(FLAGS_GET_RCODE(qstate->return_msg->rep->flags)
+			!= LDNS_RCODE_NOERROR &&
+			FLAGS_GET_RCODE(qstate->return_msg->rep->flags)
+			!= LDNS_RCODE_NXDOMAIN &&
+			FLAGS_GET_RCODE(qstate->return_msg->rep->flags)
+			!= LDNS_RCODE_YXDOMAIN))
 			return 0; /* message expired */
+		else
+			adjust = -1;
 	}
 	verbose(VERB_ALGO, "cachedb msg adjusted down by %d", (int)adjust);
 	adjust_msg_ttl(qstate->return_msg, adjust);
diff --git a/contrib/unbound/configure b/contrib/unbound/configure
index a2837d18553b..5823e49f2f80 100755
--- a/contrib/unbound/configure
+++ b/contrib/unbound/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for unbound 1.17.0.
+# Generated by GNU Autoconf 2.69 for unbound 1.17.1.
 #
 # Report bugs to <unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues>.
 #
@@ -591,8 +591,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='unbound'
 PACKAGE_TARNAME='unbound'
-PACKAGE_VERSION='1.17.0'
-PACKAGE_STRING='unbound 1.17.0'
+PACKAGE_VERSION='1.17.1'
+PACKAGE_STRING='unbound 1.17.1'
 PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues'
 PACKAGE_URL=''
 
@@ -1477,7 +1477,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.17.0 to adapt to many kinds of systems.
+\`configure' configures unbound 1.17.1 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1543,7 +1543,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of unbound 1.17.0:";;
+     short | recursive ) echo "Configuration of unbound 1.17.1:";;
    esac
   cat <<\_ACEOF
 
@@ -1785,7 +1785,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-unbound configure 1.17.0
+unbound configure 1.17.1
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2494,7 +2494,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.17.0, which was
+It was created by unbound $as_me 1.17.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2846,11 +2846,11 @@ UNBOUND_VERSION_MAJOR=1
 
 UNBOUND_VERSION_MINOR=17
 
-UNBOUND_VERSION_MICRO=0
+UNBOUND_VERSION_MICRO=1
 
 
 LIBUNBOUND_CURRENT=9
-LIBUNBOUND_REVISION=20
+LIBUNBOUND_REVISION=21
 LIBUNBOUND_AGE=1
 # 1.0.0 had 0:12:0
 # 1.0.1 had 0:13:0
@@ -2938,6 +2938,7 @@ LIBUNBOUND_AGE=1
 # 1.16.2 had 9:18:1
 # 1.16.3 had 9:19:1
 # 1.17.0 had 9:20:1
+# 1.17.1 had 9:21:1
 
 #   Current  -- the number of the binary API that we're implementing
 #   Revision -- which iteration of the implementation of the binary
@@ -22085,7 +22086,7 @@ _ACEOF
 
 
 
-version=1.17.0
+version=1.17.1
 
 date=`date +'%b %e, %Y'`
 
@@ -22604,7 +22605,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # 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.17.0, which was
+This file was extended by unbound $as_me 1.17.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -22670,7 +22671,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-unbound config.status 1.17.0
+unbound config.status 1.17.1
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff --git a/contrib/unbound/configure.ac b/contrib/unbound/configure.ac
index 57cc7e604b1e..2c7583310f20 100644
--- a/contrib/unbound/configure.ac
+++ b/contrib/unbound/configure.ac
@@ -11,14 +11,14 @@ sinclude(dnscrypt/dnscrypt.m4)
 # must be numbers. ac_defun because of later processing
 m4_define([VERSION_MAJOR],[1])
 m4_define([VERSION_MINOR],[17])
-m4_define([VERSION_MICRO],[0])
+m4_define([VERSION_MICRO],[1])
 AC_INIT([unbound],m4_defn([VERSION_MAJOR]).m4_defn([VERSION_MINOR]).m4_defn([VERSION_MICRO]),[unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues],[unbound])
 AC_SUBST(UNBOUND_VERSION_MAJOR, [VERSION_MAJOR])
 AC_SUBST(UNBOUND_VERSION_MINOR, [VERSION_MINOR])
 AC_SUBST(UNBOUND_VERSION_MICRO, [VERSION_MICRO])
 
 LIBUNBOUND_CURRENT=9
-LIBUNBOUND_REVISION=20
+LIBUNBOUND_REVISION=21
 LIBUNBOUND_AGE=1
 # 1.0.0 had 0:12:0
 # 1.0.1 had 0:13:0
@@ -106,6 +106,7 @@ LIBUNBOUND_AGE=1
 # 1.16.2 had 9:18:1
 # 1.16.3 had 9:19:1
 # 1.17.0 had 9:20:1
+# 1.17.1 had 9:21:1
 
 #   Current  -- the number of the binary API that we're implementing
 #   Revision -- which iteration of the implementation of the binary
diff --git a/contrib/unbound/contrib/unbound.service.in b/contrib/unbound/contrib/unbound.service.in
index ada5fac9c224..5a05c525170f 100644
--- a/contrib/unbound/contrib/unbound.service.in
+++ b/contrib/unbound/contrib/unbound.service.in
@@ -42,9 +42,8 @@
 [Unit]
 Description=Validating, recursive, and caching DNS resolver
 Documentation=man:unbound(8)
-After=network-online.target
-Before=nss-lookup.target
-Wants=network-online.target nss-lookup.target
+After=network.target
+Before=network-online.target nss-lookup.target
 
 [Install]
 WantedBy=multi-user.target
diff --git a/contrib/unbound/daemon/cachedump.c b/contrib/unbound/daemon/cachedump.c
index baf8008ea80f..943eb63f326c 100644
--- a/contrib/unbound/daemon/cachedump.c
+++ b/contrib/unbound/daemon/cachedump.c
@@ -387,7 +387,7 @@ move_into_cache(struct ub_packed_rrset_key* k,
 	struct rrset_ref ref;
 	uint8_t* p;
 
-	ak = alloc_special_obtain(&worker->alloc);
+	ak = alloc_special_obtain(worker->alloc);
 	if(!ak) {
 		log_warn("error out of memory");
 		return 0;
@@ -398,7 +398,7 @@ move_into_cache(struct ub_packed_rrset_key* k,
 	ak->rk.dname = (uint8_t*)memdup(k->rk.dname, k->rk.dname_len);
 	if(!ak->rk.dname) {
 		log_warn("error out of memory");
-		ub_packed_rrset_parsedelete(ak, &worker->alloc);
+		ub_packed_rrset_parsedelete(ak, worker->alloc);
 		return 0;
 	}
 	s = sizeof(*ad) + (sizeof(size_t) + sizeof(uint8_t*) + 
@@ -408,7 +408,7 @@ move_into_cache(struct ub_packed_rrset_key* k,
 	ad = (struct packed_rrset_data*)malloc(s);
 	if(!ad) {
 		log_warn("error out of memory");
-		ub_packed_rrset_parsedelete(ak, &worker->alloc);
+		ub_packed_rrset_parsedelete(ak, worker->alloc);
 		return 0;
 	}
 	p = (uint8_t*)ad;
@@ -431,7 +431,8 @@ move_into_cache(struct ub_packed_rrset_key* k,
 	ref.key = ak;
 	ref.id = ak->id;
 	(void)rrset_cache_update(worker->env.rrset_cache, &ref,
-		&worker->alloc, *worker->env.now);
+		worker->alloc, *worker->env.now);
+
 	return 1;
 }
 
diff --git a/contrib/unbound/daemon/daemon.c b/contrib/unbound/daemon/daemon.c
index 71091133a487..193608d40e05 100644
--- a/contrib/unbound/daemon/daemon.c
+++ b/contrib/unbound/daemon/daemon.c
@@ -488,6 +488,27 @@ static int daemon_get_shufport(struct daemon* daemon, int* shufport)
 	return avail;
 }
 
+/**
+ * Clear and delete per-worker alloc caches, and free memory maintained in
+ * superalloc.
+ * The rrset and message caches must be empty at the time of call.
+ * @param daemon: the daemon that maintains the alloc caches to be cleared.
+ */
+static void
+daemon_clear_allocs(struct daemon* daemon)
+{
+	int i;
+
+	for(i=0; i<daemon->num; i++) {
+		alloc_clear(daemon->worker_allocs[i]);
+		free(daemon->worker_allocs[i]);
+	}
+	free(daemon->worker_allocs);
+	daemon->worker_allocs = NULL;
+
+	alloc_clear_special(&daemon->superalloc);
+}
+
 /**
  * Allocate empty worker structures. With backptr and thread-number,
  * from 0..numthread initialised. Used as user arguments to new threads.
@@ -540,6 +561,21 @@ daemon_create_workers(struct daemon* daemon)
 			/* the above is not ports/numthr, due to rounding */
 			fatal_exit("could not create worker");
 	}
+	/* create per-worker alloc caches if not reusing existing ones. */
+	if(!daemon->worker_allocs) {
+		daemon->worker_allocs = (struct alloc_cache**)calloc(
+			(size_t)daemon->num, sizeof(struct alloc_cache*));
+		if(!daemon->worker_allocs)
+			fatal_exit("could not allocate worker allocs");
+		for(i=0; i<daemon->num; i++) {
+			struct alloc_cache* alloc = calloc(1,
+				sizeof(struct alloc_cache));
+			if (!alloc)
+				fatal_exit("could not allocate worker alloc");
+			alloc_init(alloc, &daemon->superalloc, i);
+			daemon->worker_allocs[i] = alloc;
+		}
+	}
 	free(shufport);
 }
 
@@ -771,6 +807,7 @@ daemon_fork(struct daemon* daemon)
 	/* Shutdown SHM */
 	shm_main_shutdown(daemon);
 
+	daemon->reuse_cache = daemon->workers[0]->reuse_cache;
 	daemon->need_to_exit = daemon->workers[0]->need_to_exit;
 }
 
@@ -785,9 +822,16 @@ daemon_cleanup(struct daemon* daemon)
 	log_thread_set(NULL);
 	/* clean up caches because
 	 * a) RRset IDs will be recycled after a reload, causing collisions
-	 * b) validation config can change, thus rrset, msg, keycache clear */
-	slabhash_clear(&daemon->env->rrset_cache->table);
-	slabhash_clear(daemon->env->msg_cache);
+	 * b) validation config can change, thus rrset, msg, keycache clear
+	 *
+	 * If we are trying to keep the cache as long as possible, we should
+	 * defer the cleanup until we know whether the new configuration allows
+	 * the reuse.  (If we're exiting, cleanup should be done here). */
+	if(!daemon->reuse_cache || daemon->need_to_exit) {
+		slabhash_clear(&daemon->env->rrset_cache->table);
+		slabhash_clear(daemon->env->msg_cache);
+	}
+	daemon->old_num = daemon->num; /* save the current num */
 	local_zones_delete(daemon->local_zones);
 	daemon->local_zones = NULL;
 	respip_set_delete(daemon->respip_set);
@@ -802,8 +846,13 @@ daemon_cleanup(struct daemon* daemon)
 		worker_delete(daemon->workers[i]);
 	free(daemon->workers);
 	daemon->workers = NULL;
+	/* Unless we're trying to keep the cache, worker alloc_caches should be
+	 * cleared and freed here. We do this after deleting workers to
+	 * guarantee that the alloc caches are valid throughout the lifetime
+	 * of workers. */
+	if(!daemon->reuse_cache || daemon->need_to_exit)
+		daemon_clear_allocs(daemon);
 	daemon->num = 0;
-	alloc_clear_special(&daemon->superalloc);
 #ifdef USE_DNSTAP
 	dt_delete(daemon->dtenv);
 	daemon->dtenv = NULL;
@@ -900,8 +949,42 @@ daemon_delete(struct daemon* daemon)
 
 void daemon_apply_cfg(struct daemon* daemon, struct config_file* cfg)
 {
+	int new_num = cfg->num_threads?cfg->num_threads:1;
+
         daemon->cfg = cfg;
 	config_apply(cfg);
+
+	/* If this is a reload and we deferred the decision on whether to
+	 * reuse the alloc, RRset, and message caches, then check to see if
+	 * it's safe to keep the caches:
+	 * - changing the number of threads is obviously incompatible with
+	 *   keeping the per-thread alloc caches. It also means we have to
+	 *   clear RRset and message caches. (note that 'new_num' may be
+	 *   adjusted in daemon_create_workers, but for our purpose we can
+	 *   simply compare it with 'old_num'; if they are equal here,
+	 *   'new_num' won't be adjusted to a different value than 'old_num').
+	 * - changing RRset cache size effectively clears any remaining cache
+	 *   entries. We could keep their keys in alloc caches, but it would
+	 *   be more consistent with the sense of the change to clear allocs
+	 *   and free memory. To do so we also have to clear message cache.
+	 * - only changing message cache size does not necessarily affect
+	 *   RRset or alloc cache. But almost all new subsequent queries will
+	 *   require recursive resolution anyway, so it doesn't help much to
+	 *   just keep RRset and alloc caches. For simplicity we clear/free
+	 *   the other two, too. */
+	if(daemon->worker_allocs &&
+		(new_num != daemon->old_num ||
+		 !slabhash_is_size(daemon->env->msg_cache, cfg->msg_cache_size,
+			cfg->msg_cache_slabs) ||
+		 !slabhash_is_size(&daemon->env->rrset_cache->table,
+			cfg->rrset_cache_size, cfg->rrset_cache_slabs)))
+	{
+		log_warn("cannot reuse caches due to critical config change");
+		slabhash_clear(&daemon->env->rrset_cache->table);
+		slabhash_clear(daemon->env->msg_cache);
+		daemon_clear_allocs(daemon);
+	}
+
 	if(!slabhash_is_size(daemon->env->msg_cache, cfg->msg_cache_size,
 	   	cfg->msg_cache_slabs)) {
 		slabhash_delete(daemon->env->msg_cache);
diff --git a/contrib/unbound/daemon/daemon.h b/contrib/unbound/daemon/daemon.h
index 58713e9ce466..57665446d41b 100644
--- a/contrib/unbound/daemon/daemon.h
+++ b/contrib/unbound/daemon/daemon.h
@@ -99,8 +99,12 @@ struct daemon {
 	void* listen_sslctx, *connect_sslctx;
 	/** num threads allocated */
 	int num;
+	/** num threads allocated in the previous config or 0 at first */
+	int old_num;
 	/** the worker entries */
 	struct worker** workers;
+	/** per-worker allocation cache */
+	struct alloc_cache **worker_allocs;
 	/** do we need to exit unbound (or is it only a reload?) */
 	int need_to_exit;
 	/** master random table ; used for port div between threads on reload*/
@@ -140,6 +144,8 @@ struct daemon {
 	/** the dnscrypt environment */
 	struct dnsc_env* dnscenv;
 #endif
+	/** reuse existing cache on reload if other conditions allow it. */
+	int reuse_cache;
 };
 
 /**
diff --git a/contrib/unbound/daemon/remote.c b/contrib/unbound/daemon/remote.c
index 7d4a414002ac..7c5a036f343d 100644
--- a/contrib/unbound/daemon/remote.c
+++ b/contrib/unbound/daemon/remote.c
@@ -105,8 +105,6 @@
 
 /** what to put on statistics lines between var and value, ": " or "=" */
 #define SQ "="
-/** if true, inhibits a lot of =0 lines from the stats output */
-static const int inhibit_zero = 1;
 
 /** subtract timers and the values do not overflow or become negative */
 static void
@@ -684,8 +682,9 @@ do_stop(RES* ssl, struct worker* worker)
 
 /** do the reload command */
 static void
-do_reload(RES* ssl, struct worker* worker)
+do_reload(RES* ssl, struct worker* worker, int reuse_cache)
 {
+	worker->reuse_cache = reuse_cache;
 	worker->need_to_exit = 0;
 	comm_base_exit(worker->base);
 	send_ok(ssl);
@@ -920,7 +919,7 @@ print_hist(RES* ssl, struct ub_stats_info* s)
 
 /** print extended stats */
 static int
-print_ext(RES* ssl, struct ub_stats_info* s)
+print_ext(RES* ssl, struct ub_stats_info* s, int inhibit_zero)
 {
 	int i;
 	char nm[32];
@@ -1129,7 +1128,7 @@ do_stats(RES* ssl, struct worker* worker, int reset)
 			return;
 		if(!print_hist(ssl, &total))
 			return;
-		if(!print_ext(ssl, &total))
+		if(!print_ext(ssl, &total, daemon->cfg->stat_inhibit_zero))
 			return;
 	}
 }
@@ -1963,6 +1962,8 @@ do_flush_name(RES* ssl, struct worker* w, char* arg)
 	do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_PTR, LDNS_RR_CLASS_IN);
 	do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_SRV, LDNS_RR_CLASS_IN);
 	do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_NAPTR, LDNS_RR_CLASS_IN);
+	do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_SVCB, LDNS_RR_CLASS_IN);
+	do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_HTTPS, LDNS_RR_CLASS_IN);
 	
 	free(nm);
 	send_ok(ssl);
@@ -3029,8 +3030,11 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
 	if(cmdcmp(p, "stop", 4)) {
 		do_stop(ssl, worker);
 		return;
+	} else if(cmdcmp(p, "reload_keep_cache", 17)) {
+		do_reload(ssl, worker, 1);
+		return;
 	} else if(cmdcmp(p, "reload", 6)) {
-		do_reload(ssl, worker);
+		do_reload(ssl, worker, 0);
 		return;
 	} else if(cmdcmp(p, "stats_noreset", 13)) {
 		do_stats(ssl, worker, 0);
diff --git a/contrib/unbound/daemon/worker.c b/contrib/unbound/daemon/worker.c
index caefad621409..99dcf9940004 100644
--- a/contrib/unbound/daemon/worker.c
+++ b/contrib/unbound/daemon/worker.c
@@ -133,7 +133,7 @@ worker_mem_report(struct worker* ATTR_UNUSED(worker),
 	rrset = slabhash_get_mem(&worker->env.rrset_cache->table);
 	infra = infra_get_mem(worker->env.infra_cache);
 	mesh = mesh_get_mem(worker->env.mesh);
-	ac = alloc_get_mem(&worker->alloc);
+	ac = alloc_get_mem(worker->alloc);
 	superac = alloc_get_mem(&worker->daemon->superalloc);
 	anch = anchors_get_mem(worker->env.anchors);
 	iter = 0;
@@ -623,6 +623,14 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo,
 				if(worker->env.cfg->serve_expired_ttl &&
 					rep->serve_expired_ttl < timenow)
 					return 0;
+				/* Ignore expired failure answers */
+				if(FLAGS_GET_RCODE(rep->flags) !=
+					LDNS_RCODE_NOERROR &&
+					FLAGS_GET_RCODE(rep->flags) !=
+					LDNS_RCODE_NXDOMAIN &&
+					FLAGS_GET_RCODE(rep->flags) !=
+					LDNS_RCODE_YXDOMAIN)
+					return 0;
 				if(!rrset_array_lock(rep->ref, rep->rrset_count, 0))
 					return 0;
 				*is_expired_answer = 1;
@@ -730,8 +738,6 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo,
 				goto bail_out;
 		}
 	} else {
-		/* We don't check the global ede as this is a warning, not
-		 * an error */
 		if (*is_expired_answer == 1 &&
 			worker->env.cfg->ede_serve_expired && worker->env.cfg->ede) {
 			EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
@@ -2059,15 +2065,14 @@ worker_init(struct worker* worker, struct config_file *cfg,
 	}
 
 	server_stats_init(&worker->stats, cfg);
-	alloc_init(&worker->alloc, &worker->daemon->superalloc, 
-		worker->thread_num);
-	alloc_set_id_cleanup(&worker->alloc, &worker_alloc_cleanup, worker);
+	worker->alloc = worker->daemon->worker_allocs[worker->thread_num];
+	alloc_set_id_cleanup(worker->alloc, &worker_alloc_cleanup, worker);
 	worker->env = *worker->daemon->env;
 	comm_base_timept(worker->base, &worker->env.now, &worker->env.now_tv);
 	worker->env.worker = worker;
 	worker->env.worker_base = worker->base;
 	worker->env.send_query = &worker_send_query;
-	worker->env.alloc = &worker->alloc;
+	worker->env.alloc = worker->alloc;
 	worker->env.outnet = worker->back;
 	worker->env.rnd = worker->rndstate;
 	/* If case prefetch is triggered, the corresponding mesh will clear
@@ -2211,7 +2216,7 @@ worker_delete(struct worker* worker)
 #endif /* USE_DNSTAP */
 	comm_base_delete(worker->base);
 	ub_randfree(worker->rndstate);
-	alloc_clear(&worker->alloc);
+	/* don't touch worker->alloc, as it's maintained in daemon */
 	regional_destroy(worker->env.scratch);
 	regional_destroy(worker->scratchpad);
 	free(worker);
diff --git a/contrib/unbound/daemon/worker.h b/contrib/unbound/daemon/worker.h
index 3fb52abd9d87..ab2fc728d274 100644
--- a/contrib/unbound/daemon/worker.h
+++ b/contrib/unbound/daemon/worker.h
@@ -118,7 +118,7 @@ struct worker {
 	/** do we need to restart or quit (on signal) */
 	int need_to_exit;
 	/** allocation cache for this thread */
-	struct alloc_cache alloc;
+	struct alloc_cache *alloc;
 	/** per thread statistics */
 	struct ub_server_stats stats;
 	/** thread scratch regional */
@@ -131,6 +131,8 @@ struct worker {
 	/** dnstap environment, changed for this thread */
 	struct dt_env dtenv;
 #endif
+	/** reuse existing cache on reload if other conditions allow it. */
+	int reuse_cache;
 };
 
 /**
diff --git a/contrib/unbound/doc/Changelog b/contrib/unbound/doc/Changelog
index 727d1543ea4e..899026352434 100644
--- a/contrib/unbound/doc/Changelog
+++ b/contrib/unbound/doc/Changelog
@@ -1,7 +1,93 @@
+5 January 2023: Wouter
+	- Tag for 1.17.1 release.
+
+2 January 2023: Wouter
+	- Fix windows compile for libunbound subprocess reap comm point closes.
+	- Update github workflows to use checkout v3.
+
+14 December 2022: George
+	- Merge #569 from JINMEI Tatuya: add keep-cache option to
+	  'unbound-control reload' to keep caches.
+
+13 December 2022: George
+	- Expose 'statistics-inhibit-zero' as a configuration option; the
+	  default value retains Unbound's behavior.
+	- Expose 'max-sent-count' as a configuration option; the
+	  default value retains Unbound's behavior.
+	- Merge #461 from Christian Allred: Add max-query-restarts option.
+	  Exposes an internal configuration but the default value retains
+	  Unbound's behavior.
+
+13 December 2022: Wouter
+	- Merge #808: Wrap Makefile script's directory variables in quotes.
+	- Fix to wrap Makefile scripts directory in quotes for uninstall.
+
+1 December 2022: Wouter
+	- Fix #773: When used with systemd-networkd, unbound does not start
+	  until systemd-networkd-wait-online.service times out.
+
+30 November 2022: George
+	- Add SVCB and HTTPS to the types removed by 'unbound-control flush'.
+	- Clear documentation for interactivity between the subnet module and
+	  the serve-expired and prefetch configuration options.
+
+30 November 2022: Wouter
+	- Fix #782: Segmentation fault in stats.c:404.
+
+28 November 2022: Wouter
+	- Fix for the ignore of tcp events for closed comm points, preserve
+	  the use after free protection features.
+
+23 November 2022: Philip
+	- Merge #720 from jonathangray: fix use after free when
+	  WSACreateEvent() fails.
+
+22 November 2022: George
+	- Ignore expired error responses.
+
+11 November 2022: Wouter
+	- Fix #779: [doc] Missing documention in ub_resolve_event() for
+	  callback parameter was_ratelimited.
+
+9 November 2022: George
+	- Complementary fix for distutils.sysconfig deprecation in Python 3.10
+	  to commit 62c5039ab9da42713e006e840b7578e01d66e7f2.
+
+8 November 2022: Wouter
+	- Fix to ignore tcp events for closed comm points.
+	- Fix to make sure to not read again after a tcp comm point is closed.
+	- Fix #775: libunbound: subprocess reap causes parent process reap
+	  to hang.
+	- iana portlist update.
+
+21 October 2022: George
+	- Merge #767 from jonathangray: consistently use IPv4/IPv6 in
+	  unbound.conf.5.
+
+21 October 2022: Wouter
+	- Fix that cachedb does not store failures in the external cache.
+
+18 October 2022: George
+	- Clarify the use of MAX_SENT_COUNT in the iterator code.
+
+17 October 2022: Wouter
+	- testcode/dohclient sets log identity to its name.
+
+14 October 2022: Wouter
+	- Merge #768 from fobser: Arithmetic on a pointer to void is a GNU
+	  extension.
+	- In unit test, print python script name list correctly.
+
+13 October 2022: Wouter
+	- Tag for 1.17.0 release. The code repository continues with 1.17.1.
+
 11 October 2022: George
 	- Fix PROXYv2 header read for TCP connections when no proxied addresses
 	  are provided.
 
+7 October 2022: Wouter
+	- Tag for 1.17.0rc1 release.
+
 7 October 2022: George
 	- Fix to stop possible loops in the tcp reuse code (write_wait list
 	  and tcp_wait list). Based on analysis and patch from Prad Seniappan
diff --git a/contrib/unbound/doc/README b/contrib/unbound/doc/README
index 88444a5e3c99..faab92bcb077 100644
--- a/contrib/unbound/doc/README
+++ b/contrib/unbound/doc/README
@@ -1,4 +1,4 @@
-README for Unbound 1.17.0
+README for Unbound 1.17.1
 Copyright 2007 NLnet Labs
 http://unbound.net
 
diff --git a/contrib/unbound/doc/example.conf.in b/contrib/unbound/doc/example.conf.in
index df0b2a1fb2c0..8cf3d868285e 100644
--- a/contrib/unbound/doc/example.conf.in
+++ b/contrib/unbound/doc/example.conf.in
@@ -1,7 +1,7 @@
 #
 # Example configuration file.
 #
-# See unbound.conf(5) man page, version 1.17.0.
+# See unbound.conf(5) man page, version 1.17.1.
 #
 # this is a comment.
 
@@ -35,9 +35,14 @@ server:
 	# statistics-cumulative: no
 
 	# enable extended statistics (query types, answer codes, status)
-	# printed from unbound-control. default off, because of speed.
+	# printed from unbound-control. Default off, because of speed.
 	# extended-statistics: no
 
+	# Inhibits selected extended statistics (qtype, qclass, qopcode, rcode,
+	# rpz-actions) from printing if their value is 0.
+	# Default on.
+	# statistics-inhibit-zero: yes
+
 	# number of threads to create. 1 disables threading.
 	# num-threads: 1
 
@@ -173,6 +178,15 @@ server:
 	# a throwaway response (also timeouts) is received.
 	# outbound-msg-retry: 5
 
+	# Hard limit on the number of outgoing queries Unbound will make while
+	# resolving a name, making sure large NS sets do not loop.
+	# It resets on query restarts (e.g., CNAME) and referrals.
+	# max-sent-count: 32
+
+	# Hard limit on the number of times Unbound is allowed to restart a
+	# query upon encountering a CNAME record.
+	# max-query-restarts: 11
+
 	# msec for waiting for an unknown server to reply.  Increase if you
 	# are behind a slow satellite link, to eg. 1128.
 	# unknown-server-time-limit: 376
diff --git a/contrib/unbound/doc/libunbound.3.in b/contrib/unbound/doc/libunbound.3.in
index b87289e0d764..19a213e1aa6f 100644
--- a/contrib/unbound/doc/libunbound.3.in
+++ b/contrib/unbound/doc/libunbound.3.in
@@ -1,4 +1,4 @@
-.TH "libunbound" "3" "Oct 13, 2022" "NLnet Labs" "unbound 1.17.0"
+.TH "libunbound" "3" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
 .\"
 .\" libunbound.3 -- unbound library functions manual
 .\"
@@ -44,7 +44,7 @@
 .B ub_ctx_zone_remove,
 .B ub_ctx_data_add,
 .B ub_ctx_data_remove
-\- Unbound DNS validating resolver 1.17.0 functions.
+\- Unbound DNS validating resolver 1.17.1 functions.
 .SH "SYNOPSIS"
 .B #include <unbound.h>
 .LP
diff --git a/contrib/unbound/doc/unbound-anchor.8.in b/contrib/unbound/doc/unbound-anchor.8.in
index dc61b72dadb4..9bba2522a19c 100644
--- a/contrib/unbound/doc/unbound-anchor.8.in
+++ b/contrib/unbound/doc/unbound-anchor.8.in
@@ -1,4 +1,4 @@
-.TH "unbound-anchor" "8" "Oct 13, 2022" "NLnet Labs" "unbound 1.17.0"
+.TH "unbound-anchor" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
 .\"
 .\" unbound-anchor.8 -- unbound anchor maintenance utility manual
 .\"
diff --git a/contrib/unbound/doc/unbound-checkconf.8.in b/contrib/unbound/doc/unbound-checkconf.8.in
index ba6c334c7c78..128f1cebd94b 100644
--- a/contrib/unbound/doc/unbound-checkconf.8.in
+++ b/contrib/unbound/doc/unbound-checkconf.8.in
@@ -1,4 +1,4 @@
-.TH "unbound-checkconf" "8" "Oct 13, 2022" "NLnet Labs" "unbound 1.17.0"
+.TH "unbound-checkconf" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
 .\"
 .\" unbound-checkconf.8 -- unbound configuration checker manual
 .\"
diff --git a/contrib/unbound/doc/unbound-control.8.in b/contrib/unbound/doc/unbound-control.8.in
index 3841b9737f4e..10be612fe20e 100644
--- a/contrib/unbound/doc/unbound-control.8.in
+++ b/contrib/unbound/doc/unbound-control.8.in
@@ -1,4 +1,4 @@
-.TH "unbound-control" "8" "Oct 13, 2022" "NLnet Labs" "unbound 1.17.0"
+.TH "unbound-control" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
 .\"
 .\" unbound-control.8 -- unbound remote control manual
 .\"
@@ -54,6 +54,12 @@ Stop the server. The server daemon exits.
 .B reload
 Reload the server. This flushes the cache and reads the config file fresh.
 .TP
+.B reload_keep_cache
+Reload the server but try to keep the RRset and message cache if
+(re)configuration allows for it.
+That means the caches sizes and the number of threads must not change between
+reloads.
+.TP
 .B verbosity \fInumber
 Change verbosity value for logging. Same values as \fBverbosity\fR keyword in
 \fIunbound.conf\fR(5).  This new setting lasts until the server is issued
@@ -130,7 +136,7 @@ name specified.
 .TP
 .B flush \fIname
 Remove the name from the cache. Removes the types
-A, AAAA, NS, SOA, CNAME, DNAME, MX, PTR, SRV and NAPTR.
+A, AAAA, NS, SOA, CNAME, DNAME, MX, PTR, SRV, NAPTR, SVCB and HTTPS.
 Because that is fast to do. Other record types can be removed using
 .B flush_type
 or
diff --git a/contrib/unbound/doc/unbound-host.1.in b/contrib/unbound/doc/unbound-host.1.in
index 8371084c575b..0af5777f0492 100644
--- a/contrib/unbound/doc/unbound-host.1.in
+++ b/contrib/unbound/doc/unbound-host.1.in
@@ -1,4 +1,4 @@
-.TH "unbound\-host" "1" "Oct 13, 2022" "NLnet Labs" "unbound 1.17.0"
+.TH "unbound\-host" "1" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
 .\"
 .\" unbound-host.1 -- unbound DNS lookup utility
 .\"
diff --git a/contrib/unbound/doc/unbound.8.in b/contrib/unbound/doc/unbound.8.in
index ac61b0f7a10c..498690805c85 100644
--- a/contrib/unbound/doc/unbound.8.in
+++ b/contrib/unbound/doc/unbound.8.in
@@ -1,4 +1,4 @@
-.TH "unbound" "8" "Oct 13, 2022" "NLnet Labs" "unbound 1.17.0"
+.TH "unbound" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
 .\"
 .\" unbound.8 -- unbound manual
 .\"
@@ -9,7 +9,7 @@
 .\"
 .SH "NAME"
 .B unbound
-\- Unbound DNS validating resolver 1.17.0.
+\- Unbound DNS validating resolver 1.17.1.
 .SH "SYNOPSIS"
 .B unbound
 .RB [ \-h ]
diff --git a/contrib/unbound/doc/unbound.conf.5.in b/contrib/unbound/doc/unbound.conf.5.in
index 6c021b900d89..3844d52551c6 100644
--- a/contrib/unbound/doc/unbound.conf.5.in
+++ b/contrib/unbound/doc/unbound.conf.5.in
@@ -1,4 +1,4 @@
-.TH "unbound.conf" "5" "Oct 13, 2022" "NLnet Labs" "unbound 1.17.0"
+.TH "unbound.conf" "5" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
 .\"
 .\" unbound.conf.5 -- unbound.conf manual
 .\"
@@ -112,6 +112,14 @@ If enabled, extended statistics are printed from \fIunbound\-control\fR(8).
 Default is off, because keeping track of more statistics takes time.  The
 counters are listed in \fIunbound\-control\fR(8).
 .TP
+.B statistics\-inhibit\-zero: \fI<yes or no>
+If enabled, selected extended statistics with a value of 0 are inhibited from
+printing with \fIunbound\-control\fR(8).
+These are query types, query classes, query opcodes, answer rcodes
+(except NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMPL, REFUSED) and
+RPZ actions.
+Default is on.
+.TP
 .B num\-threads: \fI<number>
 The number of threads to create to serve clients. Use 1 for no threading.
 .TP
@@ -349,7 +357,7 @@ ip\-transparent option is also available.
 The value of the Differentiated Services Codepoint (DSCP) in the
 differentiated services field (DS) of the outgoing IP packet headers.
 The field replaces the outdated IPv4 Type-Of-Service field and the
-IPV6 traffic class field.
+IPv6 traffic class field.
 .TP
 .B rrset\-cache\-size: \fI<number>
 Number of bytes size of the RRset cache. Default is 4 megabytes.
@@ -416,7 +424,7 @@ Enable or disable whether ip4 queries are answered or issued. Default is yes.
 Enable or disable whether ip6 queries are answered or issued. Default is yes.
 If disabled, queries are not answered on IPv6, and queries are not sent on
 IPv6 to the internet nameservers.  With this option you can disable the
-ipv6 transport for sending DNS traffic, it does not impact the contents of
+IPv6 transport for sending DNS traffic, it does not impact the contents of
 the DNS traffic, which may have ip4 and ip6 addresses in it.
 .TP
 .B prefer\-ip4: \fI<yes or no>
@@ -1671,7 +1679,7 @@ This specifies the action data for \fIresponse-ip\fR with action being
 to redirect as specified by "\fIresource record string\fR".  "Resource
 record string" is similar to that of \fIaccess-control-tag-action\fR,
 but it must be of either AAAA, A or CNAME types.
-If the IP-netblock is an IPv6/IPV4 prefix, the record
+If the IP-netblock is an IPv6/IPv4 prefix, the record
 must be AAAA/A respectively, unless it is a CNAME (which can be used
 for both versions of IP netblocks).  If it is CNAME there must not be
 more than one \fIresponse-ip-data\fR for the same IP-netblock.
@@ -1820,6 +1828,21 @@ If a forward/stub zone is used, this is the number of retries per nameserver in
 the zone.
 Default is 5.
 .TP 5
+.B max\-sent\-count: \fI<number>
+Hard limit on the number of outgoing queries Unbound will make while resolving
+a name, making sure large NS sets do not loop.
+Results in SERVFAIL when reached.
+It resets on query restarts (e.g., CNAME) and referrals.
+Default is 32.
+.TP 5
+.B max\-query\-restarts: \fI<number>
+Hard limit on the number of times Unbound is allowed to restart a query upon
+encountering a CNAME record.
+Results in SERVFAIL when reached.
+Changing this value needs caution as it can allow long CNAME chains to be
+accepted, where Unbound needs to verify (resolve) each link individually.
+Default is 11.
+.TP 5
 .B fast\-server\-permil: \fI<number>
 Specify how many times out of 1000 to pick from the set of fastest servers.
 0 turns the feature off.  A value of 900 would pick from the fastest
@@ -1853,7 +1876,7 @@ errors. Default is "no".
 When the \fBval-log-level\fR option is also set to \fB2\fR, responses with
 Extended DNS Errors concerning DNSSEC failures that are not served from cache,
 will also contain a descriptive text message about the reason for the failure.
-.TP
+.TP 5
*** 1646 LINES SKIPPED ***