]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
Fast Reload Option (#1042)
authorWouter Wijngaards <wcawijngaards@users.noreply.github.com>
Mon, 31 Mar 2025 13:25:24 +0000 (15:25 +0200)
committerGitHub <noreply@github.com>
Mon, 31 Mar 2025 13:25:24 +0000 (15:25 +0200)
* - fast-reload, add unbound-control fast_reload

* - fast-reload, make a thread to service the unbound-control command.

* - fast-reload, communication sockets for information transfer.

* - fast-reload, fix compile for unbound-dnstap-socket.

* - fast-reload, set nonblocking communication to keep the server thread
  responding to DNS requests.

* - fast-reload, poll routine to test for readiness, timeout fails connection.

* - fast-reload, detect loop in sock_poll_timeout routine.

* - fast-reload, send done and exited notification.

* - fast-reload, defines for constants in ipc.

* - fast-reload, ipc socket recv and send resists partial reads and writes and
  can continue byte by byte. Also it can continue after an interrupt.

* - fast-reload, send exit command to thread when done.

* - fast-reload, output strings for client on string list.

* - fast-reload, add newline to terminal output.

* - fast-reload, send client string to remote client.

* - fast-reload, better debug output.

* - fast-reload, print queue structure, for output to the remote client.

* - fast-reload, move print items to print queue from fast_reload_thread struct.

* - fast-reload, keep list of pending print queue items in daemon struct.

* - fast-reload, comment explains in_list for printq to print remainder.

* - fast-reload, unit test testdata/fast_reload_thread.tdir that tests the
  thread output.

* - fast-reload, fix test link for fast_reload_printq_list_delete function.

* - fast-reload, reread config file from disk.

* - fast-reload, unshare forwards, making the structure locked, with an rwlock.

* - fast-reload, for nonthreaded, the unbound-control commands forward,
  forward_add and forward_delete should be distributed to other processes,
  but when threaded, they should not be distributed to other threads because
  the structure is not thread specific any more.

* - fast-reload, unshared stub hints, making the structure locked, with an rwlock.

* - fast-reload, helpful comments for hints lookup function return value.

* - fast-reload, fix bug in fast reload printout, the strlist appendlist routine,
  and printout time statistics after the reload is done.

* - fast-reload, keep track of reloadtime and deletestime and print them.

* - fast-reload, keep track of constructtime and print it.

* - fast-reload, construct new items.

* - fast-reload, better comment.

* - fast-reload, reload the config and swap trees for forwards and stub hints.

* - fast-reload, in forwards_swap_tree set protection of trees with locks.

* - fast-reload, in hints_swap_tree also swap the node count of the trees.

* - fast-reload, reload ipc to stop and start threads.

* - fast-reload, unused forward declarations removed.

* - fast-reload, unit test that fast reload works with forwards and stubs.

* - fast-reload, fix clang analyzer warnings.

* - fast-reload, small documentation entry in unbound-control -h output.

* - fast-reload, printout memory use by fast reload, in bytes.

* - fast-reload, compile without threads.

* - fast-reload, document fast_reload in man page.

* - fast-reload, print ok when done successfully.

* - fast-reload, option for fast-reload commandline, +v verbosity option,
  with timing and memory use output.

* - fast-reload, option for fast-reload commandline, +p does not pause threads.

* - fast-reload, option for fast-reload commandline, +d drops mesh queries.

* - fast-reload, fix to poll every thread with nopause to make certain that
  resources are not held by the threads and can be deleted.

* - fast-reload, fix to use atomic store for config variables with nopause.

* - fast-reload, reload views.

* - fast-reload, when tag defines are different, it drops the queries.

* - fast-reload, fix tag define check.

* - fast-reload, document that tag change causes drop of queries.

* - fast-reload, fix space in documentation man page.

* - fast-reload, copy respip client information to query state, put views tree
  in module env for lookup.

* - fast-reload, nicer respip view comparison.

* - fast-reload, respip global set is in module env.

* - fast-reload, document that respip_client_info acl info is copied.

* - fast-reload, reload the respip_set.

* - fast-reload, document no pause and pick up of use_response_ip boolean.

* - fast-reload, fix test compile.

* - fast-reload, reload local zones.

* Update locking management for iter_fwd and iter_hints methods. (#1054)

fast reload, move most of the locking management to iter_fwd and
iter_hints methods. The caller still has the ability to handle its
own locking, if desired, for atomic operations on sets of different
structs.

Co-authored-by: Wouter Wijngaards <wcawijngaards@users.noreply.github.com>
* - fast-reload, reload access-control.

* - fast-reload, reload access control interface, such as interface-action.

* - fast-reload, reload tcp-connection-limit.

* - fast-reload, improve comments on acl_list and tcl_list swap tree.

* - fast-reload, fixup references to old tcp connection limits in open tcp
  connections.

* - fast-reload, fixup to clean tcp connection also for different linked order.

* - fast-reload, if no tcp connection limits existed, no need to remove
  references for that.

* - fast-reload, document more options that work and do not work.

* - fast-reload, reload auth_zone and rpz data.

* - fast-reload, fix auth_zones_get_mem.

* - fast-reload, fix compilation of testbound for the new comm_timer_get_mem
  reference in remote control.

* - fast-reload, change use_rpz with reload.

* - fast-reload, list changes in auth zones and stop zonemd callbacks for
  deleted auth zones.

* - fast-reload, note xtree is not swapped, and why it is not swapped.

* - fast-reload, for added auth zones, pick up zone transfer and zonemd tasks.

* - fast-reload, unlock xfr when done with transfer pick up.

* - fast-reload, unlock z when picking up the xfr for it during transfer task
  pick up.

* - fast-reload, pick up task changes for added, deleted and modified auth zones.

* - fast-reload, remove xfr of auth zone deletion without tasks.

* - fast-reload, pick up zone transfer config.

* - fast-reload, the main worker thread picks up the transfer tasks and also
  performs setup of the xfer struct.

* - fast-reload, keep writelock on newzone when auth zone changes.

* - fast-reload, change cachedb_enabled setting.

* - fast-reload, pick up edns-strings config.

* - fast-reload, note that settings are not updated.

* - fast-reload, pick up dnstap config.

* - fast-reload, dnstap options that need to be loaded without +p.

* - fast-reload, fix auth zone reload

* - fast-reload, remove debug for auth zone test.

* - fast-reload, fix auth zone reload with zone transfer.

* - fast-reload, fix auth zone reload lock order.

* - fast-reload, remove debug from fast reload test.

* - fast-reload, remove unused function.

* - fast-reload, fix the worker trust anchor probe timer lock acquisition in
  the probe answer callback routine for trust anchor probes.

* - fast-reload, reload trust anchors.

* - fast-reload, fix trust anchor reload lock on autr global data and test
  for trust anchor reload.

* - fast-reload, adjust cache sizes.

* - fast-reload, reload cache sizes when changed.

* - fast-reload, reload validator env changes.

* - fast-reload, reload mesh changes.

* - fast-reload, check for incompatible changes.

* - fast-reload, improve error text for incompatible change.

* - fast-reload, fix check config option compatibility.

* - fast-reload, improve error text for nopause change.

* - fast-reload, fix spelling of incompatible options.

* - fast-reload, reload target-fetch-policy, outbound-msg-retry, max-sent-count
  and max-query-restarts.

* - fast-reload, check nopause config change for target-fetch-policy.

* - fast-reload, reload do-not-query-address, private-address and capt-exempt.

* - fast-reload, check nopause config change for do-not-query-address,
  private-address and capt-exempt.

* - fast-reload, check fast reload not possible due to interface and
  outgoing-interface changes.

* - fast-reload, reload nat64 settings.

* - fast-reload, reload settings stored in the infra structure.

* - fast-reload, fix modstack lookup and remove outgoing-range check.

* - fast-reload, more explanation for config parse failure.

* - fast-reload, reload worker outside network changes.

* - fast-reload, detect incompatible changes in network settings.

* fast-reload, commit test files.

* - fast-reload, fix warnings for call types in windows compile.

* - fast-reload, fix warnings and comm_point_internal for tcp wouldblock calls.

* - fast-reload, extend lock checks for repeat thread ids.

* - fast-reload, additional test cases, cache change and tag changes.

* - fast-reload, fix documentation for auth_zone_verify_zonemd_with_key.

* - fast-reload, fix copy_cfg type casts and memory leak on config parse failure.

* - fast-reload, fix use of WSAPoll.

* Review comments for the fast reload feature (#1259)

* - fast-reload review, respip set can be null from a view.

* - fast-reload review, typos.

* - fast-reload review, keep clang static analyzer happy.

* - fast-reload review, don't forget to copy tag_actions.

* - fast-reload review, less indentation.

* - fast-reload review, don't leak respip_actions when reloading.

* - fast-reload review, protect NULL pointer dereference in get_mem
  functions.

* - fast-reload review, add fast_reload_most_options.tdir to test most
  options with high verbosity when fast reloading.

* - fast-reload review, don't skip new line on long error printouts.

* - fast-reload review, typo.

* - fast-reload review, use new_z for consistency.

* - fast-reload review, nit for unlock ordering to make eye comparison
  with the lock counterpart easier.

* - fast-reload review, in case of error the sockets are already closed.

* - fast-reload review, identation.

* - fast-reload review, add static keywords.

* - fast-reload review, update unbound-control usage text.

* - fast-reload review, updates to the man page.

* - fast-reload, the fast-reload command is experimental.

* - fast-reload, fix compile of doqclient for fast reload functions.

* Changelog comment for #1042
- Merge #1042: Fast Reload. The unbound-control fast_reload is added.
  It reads changed config in a thread, then only briefly pauses the
  service threads, that keep running. DNS service is only interrupted
  briefly, less than a second.

---------

Co-authored-by: Yorgos Thessalonikefs <yorgos@nlnetlabs.nl>
90 files changed:
Makefile.in
config.h.in
configure
configure.ac
daemon/acl_list.c
daemon/acl_list.h
daemon/daemon.c
daemon/daemon.h
daemon/remote.c
daemon/remote.h
daemon/unbound.c
daemon/worker.c
daemon/worker.h
dnstap/dnstap.c
dnstap/dnstap.h
dnstap/unbound-dnstap-socket.c
doc/Changelog
doc/unbound-control.8.in
iterator/iter_fwd.c
iterator/iter_fwd.h
iterator/iter_hints.c
iterator/iter_hints.h
iterator/iter_utils.c
iterator/iter_utils.h
iterator/iterator.c
iterator/iterator.h
libunbound/libworker.c
respip/respip.c
respip/respip.h
services/authzone.c
services/authzone.h
services/cache/infra.c
services/cache/infra.h
services/localzone.c
services/localzone.h
services/mesh.c
services/mesh.h
services/rpz.c
services/rpz.h
services/view.c
services/view.h
smallapp/unbound-control.c
smallapp/worker_cb.c
testcode/checklocks.c
testcode/doqclient.c
testcode/fake_event.c
testcode/testbound.c
testdata/fast_reload_fwd.tdir/auth1.zone [new file with mode: 0644]
testdata/fast_reload_fwd.tdir/auth2.zone [new file with mode: 0644]
testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf [new file with mode: 0644]
testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf2 [new file with mode: 0644]
testdata/fast_reload_fwd.tdir/fast_reload_fwd.dsc [new file with mode: 0644]
testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns1 [new file with mode: 0644]
testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns2 [new file with mode: 0644]
testdata/fast_reload_fwd.tdir/fast_reload_fwd.post [new file with mode: 0644]
testdata/fast_reload_fwd.tdir/fast_reload_fwd.pre [new file with mode: 0644]
testdata/fast_reload_fwd.tdir/fast_reload_fwd.test [new file with mode: 0644]
testdata/fast_reload_most_options.tdir/auth.nlnetlabs.nl.zone [new file with mode: 0644]
testdata/fast_reload_most_options.tdir/fast_reload_most_options.conf [new file with mode: 0644]
testdata/fast_reload_most_options.tdir/fast_reload_most_options.dsc [new file with mode: 0644]
testdata/fast_reload_most_options.tdir/fast_reload_most_options.post [new file with mode: 0644]
testdata/fast_reload_most_options.tdir/fast_reload_most_options.pre [new file with mode: 0644]
testdata/fast_reload_most_options.tdir/fast_reload_most_options.test [new file with mode: 0644]
testdata/fast_reload_most_options.tdir/rpz.nlnetlabs.nl.zone [new file with mode: 0644]
testdata/fast_reload_thread.tdir/fast_reload_thread.conf [new file with mode: 0644]
testdata/fast_reload_thread.tdir/fast_reload_thread.dsc [new file with mode: 0644]
testdata/fast_reload_thread.tdir/fast_reload_thread.post [new file with mode: 0644]
testdata/fast_reload_thread.tdir/fast_reload_thread.pre [new file with mode: 0644]
testdata/fast_reload_thread.tdir/fast_reload_thread.test [new file with mode: 0644]
util/config_file.c
util/config_file.h
util/edns.c
util/edns.h
util/fptr_wlist.c
util/module.h
util/netevent.c
util/netevent.h
util/storage/lruhash.c
util/storage/lruhash.h
util/storage/slabhash.c
util/storage/slabhash.h
util/tcp_conn_limit.c
util/tcp_conn_limit.h
validator/autotrust.c
validator/val_anchor.c
validator/val_anchor.h
validator/val_neg.c
validator/val_neg.h
validator/validator.c
validator/validator.h

index 9262cefd430a42c83fc570e5f47e128247397dc4..0cdff8ac15756401fb2fdd072d13c5a7e1919c07 100644 (file)
@@ -881,7 +881,7 @@ view.lo view.o: $(srcdir)/services/view.c config.h $(srcdir)/services/view.h $(s
  $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/services/localzone.h $(srcdir)/util/storage/dnstree.h \
  $(srcdir)/util/module.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/data/msgreply.h \
  $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h \
- $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/sbuffer.h $(srcdir)/util/config_file.h
+ $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/sbuffer.h $(srcdir)/util/config_file.h $(srcdir)/respip/respip.h
 rpz.lo rpz.o: $(srcdir)/services/rpz.c config.h $(srcdir)/services/rpz.h $(srcdir)/services/localzone.h \
  $(srcdir)/util/rbtree.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/storage/dnstree.h \
  $(srcdir)/util/module.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/data/msgreply.h \
@@ -977,7 +977,7 @@ fptr_wlist.lo fptr_wlist.o: $(srcdir)/util/fptr_wlist.c config.h $(srcdir)/util/
  $(srcdir)/validator/val_nsec3.h $(srcdir)/validator/val_sigcrypt.h $(srcdir)/validator/val_kentry.h \
  $(srcdir)/validator/val_neg.h $(srcdir)/validator/autotrust.h $(srcdir)/libunbound/libworker.h \
  $(srcdir)/libunbound/context.h $(srcdir)/util/alloc.h $(srcdir)/libunbound/unbound-event.h \
- $(srcdir)/libunbound/worker.h
+ $(srcdir)/libunbound/worker.h $(srcdir)/daemon/remote.h
 locks.lo locks.o: $(srcdir)/util/locks.c config.h $(srcdir)/util/locks.h $(srcdir)/util/log.h
 log.lo log.o: $(srcdir)/util/log.c config.h $(srcdir)/util/log.h $(srcdir)/util/locks.h $(srcdir)/sldns/sbuffer.h
 mini_event.lo mini_event.o: $(srcdir)/util/mini_event.c config.h $(srcdir)/util/mini_event.h $(srcdir)/util/rbtree.h \
@@ -1313,7 +1313,10 @@ remote.lo remote.o: $(srcdir)/daemon/remote.c config.h $(srcdir)/daemon/remote.h
  $(srcdir)/validator/val_anchor.h $(srcdir)/iterator/iterator.h $(srcdir)/services/outbound_list.h \
  $(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h $(srcdir)/iterator/iter_delegpt.h \
  $(srcdir)/services/outside_network.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/parseutil.h \
- $(srcdir)/sldns/wire2str.h $(srcdir)/util/edns.h
+ $(srcdir)/sldns/wire2str.h $(srcdir)/util/edns.h \
+ $(srcdir)/util/locks.h $(srcdir)/util/ub_event.h \
+ $(srcdir)/util/tcp_conn_limit.h $(srcdir)/util/edns.h $(srcdir)/validator/val_neg.h \
+ $(srcdir)/iterator/iter_utils.h $(srcdir)/iterator/iter_donotq.h $(srcdir)/iterator/iter_priv.h
 stats.lo stats.o: $(srcdir)/daemon/stats.c config.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h \
  $(srcdir)/libunbound/unbound.h $(srcdir)/daemon/worker.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \
  $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \
index dc03e82dddbaa21e13fff68f8908ec94f986698b..246f06c2ffd1fbd0f6aa3e0578e3a6ea41768f01 100644 (file)
 /* Define to 1 if you have the <stdarg.h> header file. */
 #undef HAVE_STDARG_H
 
+/* Define to 1 if you have the <stdatomic.h> header file. */
+#undef HAVE_STDATOMIC_H
+
 /* Define to 1 if you have the <stdbool.h> header file. */
 #undef HAVE_STDBOOL_H
 
index 2f49f0bdaf308f0ac403a1490a8fb2e12f7b9521..e2276279a2e9949564d65ee8ddb4b0a2e473d7f0 100755 (executable)
--- a/configure
+++ b/configure
@@ -16100,6 +16100,14 @@ then :
 
 fi
 
+ac_fn_c_check_header_compile "$LINENO" "stdatomic.h" "ac_cv_header_stdatomic_h" "$ac_includes_default
+"
+if test "x$ac_cv_header_stdatomic_h" = xyes
+then :
+  printf "%s\n" "#define HAVE_STDATOMIC_H 1" >>confdefs.h
+
+fi
+
 
 # check for types.
 # Using own tests for int64* because autoconf builtin only give 32bit.
index 07d211e6f3cdcd784c737608f3c959521ad6b195..98b8241cfff3776cb4acc3924aca331122d1dc29 100644 (file)
@@ -524,6 +524,7 @@ AC_CHECK_HEADERS([netioapi.h],,, [AC_INCLUDES_DEFAULT
 
 # Check for Linux timestamping headers
 AC_CHECK_HEADERS([linux/net_tstamp.h],,, [AC_INCLUDES_DEFAULT])
+AC_CHECK_HEADERS([stdatomic.h],,, [AC_INCLUDES_DEFAULT])
 
 # check for types.  
 # Using own tests for int64* because autoconf builtin only give 32bit.
index 362de0a2c6f886c4e6ca937b54659f966472c813..24cd9b3991426b803ae85902b3333314e8ddfdfc 100644 (file)
@@ -816,3 +816,14 @@ log_acl_action(const char* action, struct sockaddr_storage* addr,
                        (int)port);
        }
 }
+
+void acl_list_swap_tree(struct acl_list* acl, struct acl_list* data)
+{
+       /* swap tree and region */
+       rbtree_type oldtree = acl->tree;
+       struct regional* oldregion = acl->region;
+       acl->tree = data->tree;
+       acl->region = data->region;
+       data->tree = oldtree;
+       data->region = oldregion;
+}
index 6e6e5e2e952a47cdf8d7dc792b7fb5eb707c7bc6..416169ee924bcccb1a9667c3726c61d9360827a7 100644 (file)
@@ -201,4 +201,12 @@ const char* acl_access_to_str(enum acl_access acl);
 void log_acl_action(const char* action, struct sockaddr_storage* addr,
        socklen_t addrlen, enum acl_access acl, struct acl_addr* acladdr);
 
+/**
+ * Swap internal tree with preallocated entries.
+ * @param acl: the acl structure.
+ * @param data: the data structure used to take elements from. This contains
+ *     the old elements on return.
+ */
+void acl_list_swap_tree(struct acl_list* acl, struct acl_list* data);
+
 #endif /* DAEMON_ACL_LIST_H */
index 76607fe52f8a56e8b84592ee30ccbd759c832032..f882bb9ad62ed1f4eb8ded38fa34115c89455c49 100644 (file)
@@ -323,8 +323,7 @@ daemon_init(void)
        return daemon;  
 }
 
-static int setup_acl_for_ports(struct acl_list* list,
-       struct listen_port* port_list)
+int setup_acl_for_ports(struct acl_list* list, struct listen_port* port_list)
 {
        struct acl_addr* acl_node;
        for(; port_list; port_list=port_list->next) {
@@ -717,16 +716,16 @@ daemon_fork(struct daemon* daemon)
 #endif
 
        log_assert(daemon);
-       if(!(daemon->views = views_create()))
+       if(!(daemon->env->views = views_create()))
                fatal_exit("Could not create views: out of memory");
        /* create individual views and their localzone/data trees */
-       if(!views_apply_cfg(daemon->views, daemon->cfg))
+       if(!views_apply_cfg(daemon->env->views, daemon->cfg))
                fatal_exit("Could not set up views");
 
-       if(!acl_list_apply_cfg(daemon->acl, daemon->cfg, daemon->views))
+       if(!acl_list_apply_cfg(daemon->acl, daemon->cfg, daemon->env->views))
                fatal_exit("Could not setup access control list");
        if(!acl_interface_apply_cfg(daemon->acl_interface, daemon->cfg,
-               daemon->views))
+               daemon->env->views))
                fatal_exit("Could not setup interface control list");
        if(!tcl_list_apply_cfg(daemon->tcl, daemon->cfg))
                fatal_exit("Could not setup TCP connection limits");
@@ -762,15 +761,15 @@ daemon_fork(struct daemon* daemon)
                fatal_exit("Could not set root or stub hints");
 
        /* process raw response-ip configuration data */
-       if(!(daemon->respip_set = respip_set_create()))
+       if(!(daemon->env->respip_set = respip_set_create()))
                fatal_exit("Could not create response IP set");
-       if(!respip_global_apply_cfg(daemon->respip_set, daemon->cfg))
+       if(!respip_global_apply_cfg(daemon->env->respip_set, daemon->cfg))
                fatal_exit("Could not set up response IP set");
-       if(!respip_views_apply_cfg(daemon->views, daemon->cfg,
+       if(!respip_views_apply_cfg(daemon->env->views, daemon->cfg,
                &have_view_respip_cfg))
                fatal_exit("Could not set up per-view response IP sets");
-       daemon->use_response_ip = !respip_set_is_empty(daemon->respip_set) ||
-               have_view_respip_cfg;
+       daemon->use_response_ip = !respip_set_is_empty(
+               daemon->env->respip_set) || have_view_respip_cfg;
 
        /* setup modules */
        daemon_setup_modules(daemon);
@@ -886,14 +885,18 @@ daemon_cleanup(struct daemon* daemon)
        daemon->env->hints = NULL;
        local_zones_delete(daemon->local_zones);
        daemon->local_zones = NULL;
-       respip_set_delete(daemon->respip_set);
-       daemon->respip_set = NULL;
-       views_delete(daemon->views);
-       daemon->views = NULL;
+       respip_set_delete(daemon->env->respip_set);
+       daemon->env->respip_set = NULL;
+       views_delete(daemon->env->views);
+       daemon->env->views = NULL;
        if(daemon->env->auth_zones)
                auth_zones_cleanup(daemon->env->auth_zones);
        /* key cache is cleared by module deinit during next daemon_fork() */
        daemon_remote_clear(daemon->rc);
+       if(daemon->fast_reload_thread)
+               fast_reload_thread_stop(daemon->fast_reload_thread);
+       if(daemon->fast_reload_printq_list)
+               fast_reload_printq_list_delete(daemon->fast_reload_printq_list);
        for(i=0; i<daemon->num; i++)
                worker_delete(daemon->workers[i]);
        free(daemon->workers);
@@ -951,6 +954,7 @@ daemon_delete(struct daemon* daemon)
        listen_desetup_locks();
        free(daemon->chroot);
        free(daemon->pidfile);
+       free(daemon->cfgfile);
        free(daemon->env);
 #ifdef HAVE_SSL
        listen_sslctx_delete_ticket_keys();
index 54ab97b2d31b7d242f423010a36c783b924ccb45..2295761ab7e30316c3779e7f606aa260824c3d35 100644 (file)
@@ -60,6 +60,8 @@ struct respip_set;
 struct shm_main_info;
 struct doq_table;
 struct cookie_secrets;
+struct fast_reload_thread;
+struct fast_reload_printq;
 
 #include "dnstap/dnstap_config.h"
 #ifdef USE_DNSTAP
@@ -137,15 +139,11 @@ struct daemon {
        struct timeval time_last_stat;
        /** time when daemon started */
        struct timeval time_boot;
-       /** views structure containing view tree */
-       struct views* views;
 #ifdef USE_DNSTAP
        /** the dnstap environment master value, copied and changed by threads*/
        struct dt_env* dtenv;
 #endif
        struct shm_main_info* shm_info;
-       /** response-ip set with associated actions and tags. */
-       struct respip_set* respip_set;
        /** some response-ip tags or actions are configured if true */
        int use_response_ip;
        /** some RPZ policies are configured */
@@ -160,6 +158,17 @@ struct daemon {
        int reuse_cache;
        /** the EDNS cookie secrets from the cookie-secret-file */
        struct cookie_secrets* cookie_secrets;
+       /** the fast reload thread, or NULL */
+       struct fast_reload_thread* fast_reload_thread;
+       /** the fast reload printq list */
+       struct fast_reload_printq* fast_reload_printq_list;
+       /** the fast reload option to drop mesh queries, true if so. */
+       int fast_reload_drop_mesh;
+       /** for fast reload, if the tcl, tcp connection limits, has
+        * changes for workers */
+       int fast_reload_tcl_has_changes;
+       /** config file name */
+       char* cfgfile;
 };
 
 /**
@@ -212,4 +221,12 @@ void daemon_delete(struct daemon* daemon);
  */
 void daemon_apply_cfg(struct daemon* daemon, struct config_file* cfg);
 
+/**
+ * Setup acl list to have entries for the port list.
+ * @param list: the acl interface
+ * @param port_list: list of open ports, or none.
+ * @return false on failure
+ */
+int setup_acl_for_ports(struct acl_list* list, struct listen_port* port_list);
+
 #endif /* DAEMON_H */
index 3d683afd1ba33a416f9476493d0be0784eae2d8f..b4b8abe25d574218cefb24c789bcf14618bb9309 100644 (file)
@@ -52,6 +52,9 @@
 #ifdef HAVE_OPENSSL_BN_H
 #include <openssl/bn.h>
 #endif
+#ifdef HAVE_STDATOMIC_H
+#include <stdatomic.h>
+#endif
 
 #include <ctype.h>
 #include "daemon/remote.h"
@@ -63,6 +66,7 @@
 #include "util/config_file.h"
 #include "util/net_help.h"
 #include "util/module.h"
+#include "util/ub_event.h"
 #include "services/listen_dnsport.h"
 #include "services/cache/rrset.h"
 #include "services/cache/infra.h"
 #include "validator/val_kcache.h"
 #include "validator/val_kentry.h"
 #include "validator/val_anchor.h"
+#include "validator/val_neg.h"
 #include "iterator/iterator.h"
 #include "iterator/iter_fwd.h"
 #include "iterator/iter_hints.h"
 #include "iterator/iter_delegpt.h"
+#include "iterator/iter_utils.h"
+#include "iterator/iter_donotq.h"
+#include "iterator/iter_priv.h"
 #include "services/outbound_list.h"
 #include "services/outside_network.h"
 #include "sldns/str2wire.h"
@@ -88,6 +96,7 @@
 #include "sldns/wire2str.h"
 #include "sldns/sbuffer.h"
 #include "util/timeval_func.h"
+#include "util/tcp_conn_limit.h"
 #include "util/edns.h"
 #ifdef USE_CACHEDB
 #include "cachedb/cachedb.h"
 #ifdef HAVE_NETDB_H
 #include <netdb.h>
 #endif
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif
 
 /* just for portability */
 #ifdef SQ
 /** Acceptable lengths of str lines */
 #define MAX_CMD_STRLINE 1024
 #define MAX_STDIN_STRLINE 2048
+/** What number of loop iterations is too much for ipc retries */
+#define IPC_LOOP_MAX 200
+/** Timeout in msec for ipc socket poll. */
+#define IPC_NOTIFICATION_WAIT 200
+
+static void fr_printq_delete(struct fast_reload_printq* printq);
+static void fr_main_perform_printout(struct fast_reload_thread* fr);
+static int fr_printq_empty(struct fast_reload_printq* printq);
+static void fr_printq_list_insert(struct fast_reload_printq* printq,
+       struct daemon* daemon);
+static void fr_printq_remove(struct fast_reload_printq* printq);
+static void fr_check_cmd_from_thread(struct fast_reload_thread* fr);
 
 static int
 remote_setup_ctx(struct daemon_remote* rc, struct config_file* cfg)
@@ -512,6 +536,11 @@ state_list_remove_elem(struct rc_state** list, struct comm_point* c)
 static void
 clean_point(struct daemon_remote* rc, struct rc_state* s)
 {
+       if(!s->rc) {
+               /* the state has been picked up and moved away */
+               free(s);
+               return;
+       }
        state_list_remove_elem(&rc->busy_list, s->c);
        rc->active --;
        if(s->ssl) {
@@ -679,6 +708,65 @@ do_reload(RES* ssl, struct worker* worker, int reuse_cache)
        send_ok(ssl);
 }
 
+#ifndef THREADS_DISABLED
+/** parse fast reload command options. */
+static int
+fr_parse_options(RES* ssl, char* arg, int* fr_verb, int* fr_nopause,
+       int* fr_drop_mesh)
+{
+       char* argp = arg;
+       while(*argp=='+') {
+               argp++;
+               while(*argp!=0 && *argp!=' ' && *argp!='\t') {
+                       if(*argp == 'v') {
+                               (*fr_verb)++;
+                       } else if(*argp == 'p') {
+                               (*fr_nopause) = 1;
+                       } else if(*argp == 'd') {
+                               (*fr_drop_mesh) = 1;
+                       } else {
+                               if(!ssl_printf(ssl,
+                                       "error: unknown option '+%c'\n",
+                                       *argp))
+                                       return 0;
+                               return 0;
+                       }
+                       argp++;
+               }
+               argp = skipwhite(argp);
+       }
+       if(*argp!=0) {
+               if(!ssl_printf(ssl, "error: unknown option '%s'\n", argp))
+                       return 0;
+               return 0;
+       }
+       return 1;
+}
+#endif /* !THREADS_DISABLED */
+
+/** do the fast_reload command */
+static void
+do_fast_reload(RES* ssl, struct worker* worker, struct rc_state* s, char* arg)
+{
+#ifdef THREADS_DISABLED
+       if(!ssl_printf(ssl, "error: no threads for fast_reload, compiled without threads.\n"))
+               return;
+       (void)worker;
+       (void)s;
+       (void)arg;
+#else
+       int fr_verb = 0, fr_nopause = 0, fr_drop_mesh = 0;
+       if(!fr_parse_options(ssl, arg, &fr_verb, &fr_nopause, &fr_drop_mesh))
+               return;
+       if(fr_verb >= 1) {
+               if(!ssl_printf(ssl, "start fast_reload\n"))
+                       return;
+       }
+       fast_reload_thread_start(ssl, worker, s, fr_verb, fr_nopause,
+               fr_drop_mesh);
+#endif
+}
+
 /** do the verbosity command */
 static void
 do_verbosity(RES* ssl, char* str)
@@ -1473,8 +1561,7 @@ do_view_zone_add(RES* ssl, struct worker* worker, char* arg)
        struct view* v;
        if(!find_arg2(ssl, arg, &arg2))
                return;
-       v = views_find_view(worker->daemon->views,
-               arg, 1 /* get write lock*/);
+       v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
        if(!v) {
                ssl_printf(ssl,"no view with name: %s\n", arg);
                return;
@@ -1506,8 +1593,7 @@ do_view_zone_remove(RES* ssl, struct worker* worker, char* arg)
        struct view* v;
        if(!find_arg2(ssl, arg, &arg2))
                return;
-       v = views_find_view(worker->daemon->views,
-               arg, 1 /* get write lock*/);
+       v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
        if(!v) {
                ssl_printf(ssl,"no view with name: %s\n", arg);
                return;
@@ -1529,8 +1615,7 @@ do_view_data_add(RES* ssl, struct worker* worker, char* arg)
        struct view* v;
        if(!find_arg2(ssl, arg, &arg2))
                return;
-       v = views_find_view(worker->daemon->views,
-               arg, 1 /* get write lock*/);
+       v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
        if(!v) {
                ssl_printf(ssl,"no view with name: %s\n", arg);
                return;
@@ -1555,8 +1640,7 @@ do_view_datas_add(struct daemon_remote* rc, RES* ssl, struct worker* worker,
        char buf[MAX_CMD_STRLINE + MAX_STDIN_STRLINE] = "view_local_data ";
        size_t cmd_len;
        int num = 0, line = 0;
-       v = views_find_view(worker->daemon->views,
-               arg, 1 /* get write lock*/);
+       v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
        if(!v) {
                ssl_printf(ssl,"no view with name: %s\n", arg);
                return;
@@ -1597,8 +1681,7 @@ do_view_data_remove(RES* ssl, struct worker* worker, char* arg)
        struct view* v;
        if(!find_arg2(ssl, arg, &arg2))
                return;
-       v = views_find_view(worker->daemon->views,
-               arg, 1 /* get write lock*/);
+       v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
        if(!v) {
                ssl_printf(ssl,"no view with name: %s\n", arg);
                return;
@@ -1621,8 +1704,7 @@ do_view_datas_remove(struct daemon_remote* rc, RES* ssl, struct worker* worker,
        char buf[MAX_CMD_STRLINE + MAX_STDIN_STRLINE] = "view_local_data_remove ";
        int num = 0;
        size_t cmd_len;
-       v = views_find_view(worker->daemon->views,
-               arg, 1 /* get write lock*/);
+       v = views_find_view(worker->env.views, arg, 1 /* get write lock*/);
        if(!v) {
                ssl_printf(ssl,"no view with name: %s\n", arg);
                return;
@@ -3045,7 +3127,7 @@ do_list_local_data(RES* ssl, struct worker* worker, struct local_zones* zones)
 static void
 do_view_list_local_zones(RES* ssl, struct worker* worker, char* arg)
 {
-       struct view* v = views_find_view(worker->daemon->views,
+       struct view* v = views_find_view(worker->env.views,
                arg, 0 /* get read lock*/);
        if(!v) {
                ssl_printf(ssl,"no view with name: %s\n", arg);
@@ -3061,7 +3143,7 @@ do_view_list_local_zones(RES* ssl, struct worker* worker, char* arg)
 static void
 do_view_list_local_data(RES* ssl, struct worker* worker, char* arg)
 {
-       struct view* v = views_find_view(worker->daemon->views,
+       struct view* v = views_find_view(worker->env.views,
                arg, 0 /* get read lock*/);
        if(!v) {
                ssl_printf(ssl,"no view with name: %s\n", arg);
@@ -3427,7 +3509,7 @@ cmdcmp(char* p, const char* cmd, size_t len)
 
 /** execute a remote control command */
 static void
-execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
+execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd,
        struct worker* worker)
 {
        char* p = skipwhite(cmd);
@@ -3441,6 +3523,9 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
        } else if(cmdcmp(p, "reload", 6)) {
                do_reload(ssl, worker, 0);
                return;
+       } else if(cmdcmp(p, "fast_reload", 11)) {
+               do_fast_reload(ssl, worker, s, skipwhite(p+11));
+               return;
        } else if(cmdcmp(p, "stats_noreset", 13)) {
                do_stats(ssl, worker, 0);
                return;
@@ -3639,7 +3724,7 @@ daemon_remote_exec(struct worker* worker)
                return;
        }
        verbose(VERB_ALGO, "remote exec distributed: %s", (char*)msg);
-       execute_cmd(NULL, NULL, (char*)msg, worker);
+       execute_cmd(NULL, NULL, NULL, (char*)msg, worker);
        free(msg);
 }
 
@@ -3703,7 +3788,7 @@ handle_req(struct daemon_remote* rc, struct rc_state* s, RES* res)
        verbose(VERB_DETAIL, "control cmd: %s", buf);
 
        /* figure out what to do */
-       execute_cmd(rc, res, buf, rc->worker);
+       execute_cmd(rc, s, res, buf, rc->worker);
 }
 
 /** handle SSL_do_handshake changes to the file descriptor to wait for later */
@@ -3795,3 +3880,3969 @@ int remote_control_callback(struct comm_point* c, void* arg, int err,
        clean_point(rc, s);
        return 0;
 }
+
+/**
+ * This routine polls a socket for readiness.
+ * @param fd: file descriptor, -1 uses no fd for a timer only.
+ * @param timeout: time in msec to wait. 0 means nonblocking test,
+ *     -1 waits blocking for events.
+ * @param pollin: check for input event.
+ * @param pollout: check for output event.
+ * @param event: output variable, set to true if the event happens.
+ *     It is false if there was an error or timeout.
+ * @return false is system call failure, also logged.
+ */
+static int
+sock_poll_timeout(int fd, int timeout, int pollin, int pollout, int* event)
+{
+       int loopcount = 0;
+       /* Loop if the system call returns an errno to do so, like EINTR. */
+       while(1) {
+               struct pollfd p, *fds;
+               int nfds, ret;
+               if(++loopcount > IPC_LOOP_MAX) {
+                       log_err("sock_poll_timeout: loop");
+                       if(event)
+                               *event = 0;
+                       return 0;
+               }
+               if(fd == -1) {
+                       fds = NULL;
+                       nfds = 0;
+               } else {
+                       fds = &p;
+                       nfds = 1;
+                       memset(&p, 0, sizeof(p));
+                       p.fd = fd;
+                       p.events = POLLERR
+#ifndef USE_WINSOCK
+                               | POLLHUP
+#endif
+                               ;
+                       if(pollin)
+                               p.events |= POLLIN;
+                       if(pollout)
+                               p.events |= POLLOUT;
+               }
+#ifndef USE_WINSOCK
+               ret = poll(fds, nfds, timeout);
+#else
+               if(fds == NULL) {
+                       Sleep(timeout);
+                       ret = 0;
+               } else {
+                       ret = WSAPoll(fds, nfds, timeout);
+               }
+#endif
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again. */
+                       log_err("poll: %s", sock_strerror(errno));
+                       if(event)
+                               *event = 0;
+                       return 0;
+               } else if(ret == 0) {
+                       /* Timeout */
+                       if(event)
+                               *event = 0;
+                       return 1;
+               }
+               break;
+       }
+       if(event)
+               *event = 1;
+       return 1;
+}
+
+/** fast reload convert fast reload notification status to string */
+static const char*
+fr_notification_to_string(enum fast_reload_notification status)
+{
+       switch(status) {
+       case fast_reload_notification_none:
+               return "none";
+       case fast_reload_notification_done:
+               return "done";
+       case fast_reload_notification_done_error:
+               return "done_error";
+       case fast_reload_notification_exit:
+               return "exit";
+       case fast_reload_notification_exited:
+               return "exited";
+       case fast_reload_notification_printout:
+               return "printout";
+       case fast_reload_notification_reload_stop:
+               return "reload_stop";
+       case fast_reload_notification_reload_ack:
+               return "reload_ack";
+       case fast_reload_notification_reload_nopause_poll:
+               return "reload_nopause_poll";
+       case fast_reload_notification_reload_start:
+               return "reload_start";
+       default:
+               break;
+       }
+       return "unknown";
+}
+
+#ifndef THREADS_DISABLED
+/** fast reload, poll for notification incoming. True if quit */
+static int
+fr_poll_for_quit(struct fast_reload_thread* fr)
+{
+       int inevent, loopexit = 0, bcount = 0;
+       uint32_t cmd;
+       ssize_t ret;
+
+       if(fr->need_to_quit)
+               return 1;
+       /* Is there data? */
+       if(!sock_poll_timeout(fr->commpair[1], 0, 1, 0, &inevent)) {
+               log_err("fr_poll_for_quit: poll failed");
+               return 0;
+       }
+       if(!inevent)
+               return 0;
+
+       /* Read the data */
+       while(1) {
+               if(++loopexit > IPC_LOOP_MAX) {
+                       log_err("fr_poll_for_quit: recv loops %s",
+                               sock_strerror(errno));
+                       return 0;
+               }
+               ret = recv(fr->commpair[1], ((char*)&cmd)+bcount,
+                       sizeof(cmd)-bcount, 0);
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again. */
+                       log_err("fr_poll_for_quit: recv: %s",
+                               sock_strerror(errno));
+                       return 0;
+               } else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+                       bcount += ret;
+                       if((size_t)bcount < sizeof(cmd))
+                               continue;
+               }
+               break;
+       }
+       if(cmd == fast_reload_notification_exit) {
+               fr->need_to_quit = 1;
+               verbose(VERB_ALGO, "fast reload: exit notification received");
+               return 1;
+       }
+       log_err("fr_poll_for_quit: unknown notification status received: %d %s",
+               cmd, fr_notification_to_string(cmd));
+       return 0;
+}
+
+/** fast reload thread. Send notification from the fast reload thread */
+static void
+fr_send_notification(struct fast_reload_thread* fr,
+       enum fast_reload_notification status)
+{
+       int outevent, loopexit = 0, bcount = 0;
+       uint32_t cmd;
+       ssize_t ret;
+       verbose(VERB_ALGO, "fast reload: send notification %s",
+               fr_notification_to_string(status));
+       /* Make a blocking attempt to send. But meanwhile stay responsive,
+        * once in a while for quit commands. In case the server has to quit. */
+       /* see if there is incoming quit signals */
+       if(fr_poll_for_quit(fr))
+               return;
+       cmd = status;
+       while(1) {
+               if(++loopexit > IPC_LOOP_MAX) {
+                       log_err("fast reload: could not send notification");
+                       return;
+               }
+               /* wait for socket to become writable */
+               if(!sock_poll_timeout(fr->commpair[1], IPC_NOTIFICATION_WAIT,
+                       0, 1, &outevent)) {
+                       log_err("fast reload: poll failed");
+                       return;
+               }
+               if(fr_poll_for_quit(fr))
+                       return;
+               if(!outevent)
+                       continue;
+               ret = send(fr->commpair[1], ((char*)&cmd)+bcount,
+                       sizeof(cmd)-bcount, 0);
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again. */
+                       log_err("fast reload send notification: send: %s",
+                               sock_strerror(errno));
+                       return;
+               } else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+                       bcount += ret;
+                       if((size_t)bcount < sizeof(cmd))
+                               continue;
+               }
+               break;
+       }
+}
+
+/** fast reload thread queue up text string for output */
+static int
+fr_output_text(struct fast_reload_thread* fr, const char* msg)
+{
+       char* item = strdup(msg);
+       if(!item) {
+               log_err("fast reload output text: strdup out of memory");
+               return 0;
+       }
+       lock_basic_lock(&fr->fr_output_lock);
+       if(!cfg_strlist_append(fr->fr_output, item)) {
+               lock_basic_unlock(&fr->fr_output_lock);
+               /* The item is freed by cfg_strlist_append on failure. */
+               log_err("fast reload output text: append out of memory");
+               return 0;
+       }
+       lock_basic_unlock(&fr->fr_output_lock);
+       return 1;
+}
+
+/** fast reload thread output vmsg function */
+static int
+fr_output_vmsg(struct fast_reload_thread* fr, const char* format, va_list args)
+{
+       char msg[1024];
+       vsnprintf(msg, sizeof(msg), format, args);
+       return fr_output_text(fr, msg);
+}
+
+/** fast reload thread printout function, with printf arguments */
+static int fr_output_printf(struct fast_reload_thread* fr,
+       const char* format, ...) ATTR_FORMAT(printf, 2, 3);
+
+/** fast reload thread printout function, prints to list and signals
+ * the remote control thread to move that to get written to the socket
+ * of the remote control connection. */
+static int
+fr_output_printf(struct fast_reload_thread* fr, const char* format, ...)
+{
+       va_list args;
+       int ret;
+       va_start(args, format);
+       ret = fr_output_vmsg(fr, format, args);
+       va_end(args);
+       return ret;
+}
+
+/** fast reload thread, init time counters */
+static void
+fr_init_time(struct timeval* time_start, struct timeval* time_read,
+       struct timeval* time_construct, struct timeval* time_reload,
+       struct timeval* time_end)
+{
+       memset(time_start, 0, sizeof(*time_start));
+       memset(time_read, 0, sizeof(*time_read));
+       memset(time_construct, 0, sizeof(*time_construct));
+       memset(time_reload, 0, sizeof(*time_reload));
+       memset(time_end, 0, sizeof(*time_end));
+       if(gettimeofday(time_start, NULL) < 0)
+               log_err("gettimeofday: %s", strerror(errno));
+}
+
+/**
+ * Structure with constructed elements for use during fast reload.
+ * At the start it contains the tree items for the new config.
+ * After the tree items are swapped into the server, the old elements
+ * are kept in here. They can then be deleted.
+ */
+struct fast_reload_construct {
+       /** construct for views */
+       struct views* views;
+       /** construct for auth zones */
+       struct auth_zones* auth_zones;
+       /** construct for forwards */
+       struct iter_forwards* fwds;
+       /** construct for stubs */
+       struct iter_hints* hints;
+       /** construct for respip_set */
+       struct respip_set* respip_set;
+       /** construct for access control */
+       struct acl_list* acl;
+       /** construct for access control interface */
+       struct acl_list* acl_interface;
+       /** construct for tcp connection limit */
+       struct tcl_list* tcl;
+       /** construct for local zones */
+       struct local_zones* local_zones;
+       /** if there is response ip configuration in use */
+       int use_response_ip;
+       /** if there is an rpz zone */
+       int use_rpz;
+       /** construct for edns strings */
+       struct edns_strings* edns_strings;
+       /** construct for trust anchors */
+       struct val_anchors* anchors;
+       /** construct for nsec3 key size */
+       size_t* nsec3_keysize;
+       /** construct for nsec3 max iter */
+       size_t* nsec3_maxiter;
+       /** construct for nsec3 keyiter count */
+       int nsec3_keyiter_count;
+       /** construct for target fetch policy */
+       int* target_fetch_policy;
+       /** construct for max dependency depth */
+       int max_dependency_depth;
+       /** construct for donotquery addresses */
+       struct iter_donotq* donotq;
+       /** construct for private addresses and domains */
+       struct iter_priv* priv;
+       /** construct whitelist for capsforid names */
+       struct rbtree_type* caps_white;
+       /** construct for nat64 */
+       struct iter_nat64 nat64;
+       /** construct for wait_limits_netblock */
+       struct rbtree_type wait_limits_netblock;
+       /** construct for wait_limits_cookie_netblock */
+       struct rbtree_type wait_limits_cookie_netblock;
+       /** construct for domain limits */
+       struct rbtree_type domain_limits;
+       /** storage for the old configuration elements. The outer struct
+        * is allocated with malloc here, the items are from config. */
+       struct config_file* oldcfg;
+};
+
+/** fast reload thread, read config */
+static int
+fr_read_config(struct fast_reload_thread* fr, struct config_file** newcfg)
+{
+       /* Create new config structure. */
+       *newcfg = config_create();
+       if(!*newcfg) {
+               if(!fr_output_printf(fr, "config_create failed: out of memory\n"))
+                       return 0;
+               fr_send_notification(fr, fast_reload_notification_printout);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       /* Read new config from file */
+       if(!config_read(*newcfg, fr->worker->daemon->cfgfile,
+               fr->worker->daemon->chroot)) {
+               config_delete(*newcfg);
+               if(!fr_output_printf(fr, "config_read %s failed: %s\n",
+                       fr->worker->daemon->cfgfile, strerror(errno)))
+                       return 0;
+               fr_send_notification(fr, fast_reload_notification_printout);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+       if(fr->fr_verb >= 1) {
+               if(!fr_output_printf(fr, "done read config file %s\n",
+                       fr->worker->daemon->cfgfile))
+                       return 0;
+               fr_send_notification(fr, fast_reload_notification_printout);
+       }
+
+       return 1;
+}
+
+/** Check if two taglists are equal. */
+static int
+taglist_equal(char** tagname_a, int num_tags_a, char** tagname_b,
+       int num_tags_b)
+{
+       int i;
+       if(num_tags_a != num_tags_b)
+               return 0;
+       for(i=0; i<num_tags_a; i++) {
+               if(strcmp(tagname_a[i], tagname_b[i]) != 0)
+                       return 0;
+       }
+       return 1;
+}
+
+/** Check the change from a to b is only new entries at the end. */
+static int
+taglist_change_at_end(char** tagname_a, int num_tags_a, char** tagname_b,
+       int num_tags_b)
+{
+       if(num_tags_a < 0 || num_tags_b < 0)
+               return 0;
+       if(num_tags_a >= num_tags_b)
+               return 0;
+       /* So, b is longer than a. Check if the initial start of the two
+        * taglists is the same. */
+       if(!taglist_equal(tagname_a, num_tags_a, tagname_b, num_tags_a))
+               return 0;
+       return 1;
+}
+
+/** fast reload thread, check tag defines. */
+static int
+fr_check_tag_defines(struct fast_reload_thread* fr, struct config_file* newcfg)
+{
+       /* The tags are kept in a bitlist for items. Some of them are stored
+        * in query info. If the tags change, then the old values are
+        * inaccurate. The solution is to then flush the query list.
+        * Unless the change only involves adding new tags at the end, that
+        * needs no changes. */
+       if(!taglist_equal(fr->worker->daemon->cfg->tagname,
+                       fr->worker->daemon->cfg->num_tags, newcfg->tagname,
+                       newcfg->num_tags) &&
+               !taglist_change_at_end(fr->worker->daemon->cfg->tagname,
+                       fr->worker->daemon->cfg->num_tags, newcfg->tagname,
+                       newcfg->num_tags)) {
+               /* The tags have changed too much, the define-tag config. */
+               if(fr->fr_drop_mesh)
+                       return 1; /* already dropping queries */
+               fr->fr_drop_mesh = 1;
+               fr->worker->daemon->fast_reload_drop_mesh = fr->fr_drop_mesh;
+               if(!fr_output_printf(fr, "tags have changed, with "
+                       "'define-tag', and the queries have to be dropped "
+                       "for consistency, setting '+d'\n"))
+                       return 0;
+               fr_send_notification(fr, fast_reload_notification_printout);
+       }
+       return 1;
+}
+
+/** fast reload thread, check if config item has changed, if not add to
+ * the explanatory string. */
+static void
+fr_check_changed_cfg(int cmp, const char* desc, char* str, size_t len)
+{
+       if(cmp) {
+               size_t slen = strlen(str);
+               size_t desclen = strlen(desc);
+               if(slen == 0) {
+                       snprintf(str, len, "%s", desc);
+                       return;
+               }
+               if(len - slen < desclen+2)
+                       return; /* It does not fit */
+               snprintf(str+slen, len-slen, " %s", desc);
+       }
+}
+
+/** fast reload thread, check if config string has changed, checks NULLs. */
+static void
+fr_check_changed_cfg_str(char* cmp1, char* cmp2, const char* desc, char* str,
+       size_t len)
+{
+       if((!cmp1 && cmp2) ||
+               (cmp1 && !cmp2) ||
+               (cmp1 && cmp2 && strcmp(cmp1, cmp2) != 0)) {
+               fr_check_changed_cfg(1, desc, str, len);
+       }
+}
+
+/** fast reload thread, check if config strlist has changed. */
+static void
+fr_check_changed_cfg_strlist(struct config_strlist* cmp1,
+       struct config_strlist* cmp2, const char* desc, char* str, size_t len)
+{
+       struct config_strlist* p1 = cmp1, *p2 = cmp2;
+       while(p1 && p2) {
+               if((!p1->str && p2->str) ||
+                       (p1->str && !p2->str) ||
+                       (p1->str && p2->str && strcmp(p1->str, p2->str) != 0)) {
+                       /* The strlist is different. */
+                       fr_check_changed_cfg(1, desc, str, len);
+                       return;
+               }
+               p1 = p1->next;
+               p2 = p2->next;
+       }
+       if((!p1 && p2) || (p1 && !p2)) {
+               fr_check_changed_cfg(1, desc, str, len);
+       }
+}
+
+/** fast reload thread, check if config str2list has changed. */
+static void
+fr_check_changed_cfg_str2list(struct config_str2list* cmp1,
+       struct config_str2list* cmp2, const char* desc, char* str, size_t len)
+{
+       struct config_str2list* p1 = cmp1, *p2 = cmp2;
+       while(p1 && p2) {
+               if((!p1->str && p2->str) ||
+                       (p1->str && !p2->str) ||
+                       (p1->str && p2->str && strcmp(p1->str, p2->str) != 0)) {
+                       /* The str2list is different. */
+                       fr_check_changed_cfg(1, desc, str, len);
+                       return;
+               }
+               if((!p1->str2 && p2->str2) ||
+                       (p1->str2 && !p2->str2) ||
+                       (p1->str2 && p2->str2 &&
+                       strcmp(p1->str2, p2->str2) != 0)) {
+                       /* The str2list is different. */
+                       fr_check_changed_cfg(1, desc, str, len);
+                       return;
+               }
+               p1 = p1->next;
+               p2 = p2->next;
+       }
+       if((!p1 && p2) || (p1 && !p2)) {
+               fr_check_changed_cfg(1, desc, str, len);
+       }
+}
+
+/** fast reload thread, check compatible config items */
+static int
+fr_check_compat_cfg(struct fast_reload_thread* fr, struct config_file* newcfg)
+{
+       int i;
+       char changed_str[1024];
+       struct config_file* cfg = fr->worker->env.cfg;
+       changed_str[0]=0;
+
+       /* Find incompatible options, and if so, print an error. */
+       fr_check_changed_cfg(cfg->num_threads != newcfg->num_threads,
+               "num-threads", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->do_ip4 != newcfg->do_ip4,
+               "do-ip4", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->do_ip6 != newcfg->do_ip6,
+               "do-ip6", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->do_udp != newcfg->do_udp,
+               "do-udp", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->do_tcp != newcfg->do_tcp,
+               "do-tcp", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->port != newcfg->port,
+               "port", changed_str, sizeof(changed_str));
+       /* But cfg->outgoing_num_ports has been changed at startup,
+        * possibly to reduce it, so do not check it here. */
+       fr_check_changed_cfg(cfg->outgoing_num_tcp != newcfg->outgoing_num_tcp,
+               "outgoing-num-tcp", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->incoming_num_tcp != newcfg->incoming_num_tcp,
+               "incoming-num-tcp", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->num_out_ifs != newcfg->num_out_ifs,
+               "outgoing-interface", changed_str, sizeof(changed_str));
+       if(cfg->num_out_ifs == newcfg->num_out_ifs) {
+               for(i=0; i<cfg->num_out_ifs; i++)
+                       fr_check_changed_cfg(strcmp(cfg->out_ifs[i],
+                               newcfg->out_ifs[i]) != 0, "outgoing-interface",
+                               changed_str, sizeof(changed_str));
+       }
+       fr_check_changed_cfg(cfg->num_ifs != newcfg->num_ifs,
+               "interface", changed_str, sizeof(changed_str));
+       if(cfg->num_ifs == newcfg->num_ifs) {
+               for(i=0; i<cfg->num_ifs; i++)
+                       fr_check_changed_cfg(strcmp(cfg->ifs[i],
+                               newcfg->ifs[i]) != 0, "interface",
+                               changed_str, sizeof(changed_str));
+       }
+       fr_check_changed_cfg(cfg->if_automatic != newcfg->if_automatic,
+               "interface-automatic", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->so_rcvbuf != newcfg->so_rcvbuf,
+               "so-rcvbuf", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->so_sndbuf != newcfg->so_sndbuf,
+               "so-sndbuf", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->so_reuseport != newcfg->so_reuseport,
+               "so-reuseport", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->ip_transparent != newcfg->ip_transparent,
+               "ip-transparent", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->ip_freebind != newcfg->ip_freebind,
+               "ip-freebind", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->udp_connect != newcfg->udp_connect,
+               "udp-connect", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->msg_buffer_size != newcfg->msg_buffer_size,
+               "msg-buffer-size", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->do_tcp_keepalive != newcfg->do_tcp_keepalive,
+               "edns-tcp-keepalive", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->tcp_keepalive_timeout != newcfg->tcp_keepalive_timeout,
+               "edns-tcp-keepalive-timeout", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->tcp_idle_timeout != newcfg->tcp_idle_timeout,
+               "tcp-idle-timeout", changed_str, sizeof(changed_str));
+       /* Not changed, only if DoH is used, it is then stored in commpoints,
+        * as well as used from cfg. */
+       fr_check_changed_cfg(
+               cfg->harden_large_queries != newcfg->harden_large_queries,
+               "harden-large-queries", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->http_max_streams != newcfg->http_max_streams,
+               "http-max-streams", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str(cfg->http_endpoint, newcfg->http_endpoint,
+               "http-endpoint", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->http_notls_downstream != newcfg->http_notls_downstream,
+               "http_notls_downstream", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->https_port != newcfg->https_port,
+               "https-port", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->ssl_port != newcfg->ssl_port,
+               "tls-port", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str(cfg->ssl_service_key, newcfg->ssl_service_key,
+               "tls-service-key", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str(cfg->ssl_service_pem, newcfg->ssl_service_pem,
+               "tls-service-pem", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str(cfg->tls_cert_bundle, newcfg->tls_cert_bundle,
+               "tls-cert-bundle", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_strlist(cfg->proxy_protocol_port,
+               newcfg->proxy_protocol_port, "proxy-protocol-port",
+               changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_strlist(cfg->tls_additional_port,
+               newcfg->tls_additional_port, "tls-additional-port",
+               changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str(cfg->if_automatic_ports,
+               newcfg->if_automatic_ports, "interface-automatic-ports",
+               changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->udp_upstream_without_downstream !=
+               newcfg->udp_upstream_without_downstream,
+               "udp-upstream-without-downstream", changed_str,
+               sizeof(changed_str));
+
+       if(changed_str[0] != 0) {
+               /* The new config changes some items that do not work with
+                * fast reload. */
+               if(!fr_output_printf(fr, "The config changes items that are "
+                       "not compatible with fast_reload, perhaps do reload "
+                       "or restart: %s", changed_str) ||
+                       !fr_output_printf(fr, "\n"))
+                       return 0;
+               fr_send_notification(fr, fast_reload_notification_printout);
+               return 0;
+       }
+       return 1;
+}
+
+/** fast reload thread, check nopause config items */
+static int
+fr_check_nopause_cfg(struct fast_reload_thread* fr, struct config_file* newcfg)
+{
+       char changed_str[1024];
+       struct config_file* cfg = fr->worker->env.cfg;
+       if(!fr->fr_nopause)
+               return 1; /* The nopause is not enabled, so no problem. */
+       changed_str[0]=0;
+
+       /* Check for iter_env. */
+       fr_check_changed_cfg(
+               cfg->outbound_msg_retry != newcfg->outbound_msg_retry,
+               "outbound-msg-retry", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->max_sent_count != newcfg->max_sent_count,
+               "max-sent-count", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->max_query_restarts != newcfg->max_query_restarts,
+               "max-query-restarts", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(strcmp(cfg->target_fetch_policy,
+               newcfg->target_fetch_policy) != 0,
+               "target-fetch-policy", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->donotquery_localhost != newcfg->donotquery_localhost,
+               "do-not-query-localhost", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_strlist(cfg->donotqueryaddrs,
+               newcfg->donotqueryaddrs, "do-not-query-localhost",
+               changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_strlist(cfg->private_address,
+               newcfg->private_address, "private-address",
+               changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_strlist(cfg->private_domain,
+               newcfg->private_domain, "private-domain",
+               changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_strlist(cfg->caps_whitelist,
+               newcfg->caps_whitelist, "caps-exempt",
+               changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->do_nat64 != newcfg->do_nat64,
+               "do-nat64", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str(cfg->nat64_prefix, newcfg->nat64_prefix,
+               "nat64-prefix", changed_str, sizeof(changed_str));
+
+       /* Check for val_env. */
+       fr_check_changed_cfg(cfg->bogus_ttl != newcfg->bogus_ttl,
+               "val-bogus-ttl", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->val_date_override != newcfg->val_date_override,
+               "val-date-override", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->val_sig_skew_min != newcfg->val_sig_skew_min,
+               "val-sig-skew-min", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->val_sig_skew_max != newcfg->val_sig_skew_max,
+               "val-sig-skew-max", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(cfg->val_max_restart != newcfg->val_max_restart,
+               "val-max-restart", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(strcmp(cfg->val_nsec3_key_iterations,
+               newcfg->val_nsec3_key_iterations) != 0,
+               "val-nsec3-keysize-iterations", changed_str,
+               sizeof(changed_str));
+
+       /* Check for infra. */
+       fr_check_changed_cfg(cfg->host_ttl != newcfg->host_ttl,
+               "infra-host-ttl", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->infra_keep_probing != newcfg->infra_keep_probing,
+               "infra-keep-probing", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->ratelimit != newcfg->ratelimit,
+               "ratelimit", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->ip_ratelimit != newcfg->ip_ratelimit,
+               "ip-ratelimit", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->ip_ratelimit_cookie != newcfg->ip_ratelimit_cookie,
+               "ip-ratelimit-cookie", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str2list(cfg->wait_limit_netblock,
+               newcfg->wait_limit_netblock, "wait-limit-netblock",
+               changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str2list(cfg->wait_limit_cookie_netblock,
+               newcfg->wait_limit_cookie_netblock,
+               "wait-limit-cookie-netblock", changed_str,
+               sizeof(changed_str));
+       fr_check_changed_cfg_str2list(cfg->ratelimit_below_domain,
+               newcfg->ratelimit_below_domain, "ratelimit-below-domain",
+               changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str2list(cfg->ratelimit_for_domain,
+               newcfg->ratelimit_for_domain, "ratelimit-for-domain",
+               changed_str, sizeof(changed_str));
+
+       /* Check for dnstap. */
+       fr_check_changed_cfg(
+               cfg->dnstap_send_identity != newcfg->dnstap_send_identity,
+               "dnstap-send-identity", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg(
+               cfg->dnstap_send_version != newcfg->dnstap_send_version,
+               "dnstap-send-version", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str(cfg->dnstap_identity, newcfg->dnstap_identity,
+               "dnstap-identity", changed_str, sizeof(changed_str));
+       fr_check_changed_cfg_str(cfg->dnstap_version, newcfg->dnstap_version,
+               "dnstap-version", changed_str, sizeof(changed_str));
+
+       if(changed_str[0] != 0) {
+               /* The new config changes some items that need a pause,
+                * to be able to update the variables. */
+               if(!fr_output_printf(fr, "The config changes items that need "
+                       "the fast_reload +p option, for nopause, "
+                       "disabled to be reloaded: %s", changed_str) ||
+                       !fr_output_printf(fr, "\n"))
+                       return 0;
+               fr_send_notification(fr, fast_reload_notification_printout);
+               return 0;
+       }
+       return 1;
+}
+
+/** fast reload thread, clear construct information, deletes items */
+static void
+fr_construct_clear(struct fast_reload_construct* ct)
+{
+       if(!ct)
+               return;
+       auth_zones_delete(ct->auth_zones);
+       forwards_delete(ct->fwds);
+       hints_delete(ct->hints);
+       respip_set_delete(ct->respip_set);
+       local_zones_delete(ct->local_zones);
+       acl_list_delete(ct->acl);
+       acl_list_delete(ct->acl_interface);
+       tcl_list_delete(ct->tcl);
+       edns_strings_delete(ct->edns_strings);
+       anchors_delete(ct->anchors);
+       views_delete(ct->views);
+       free(ct->nsec3_keysize);
+       free(ct->nsec3_maxiter);
+       free(ct->target_fetch_policy);
+       donotq_delete(ct->donotq);
+       priv_delete(ct->priv);
+       caps_white_delete(ct->caps_white);
+       wait_limits_free(&ct->wait_limits_netblock);
+       wait_limits_free(&ct->wait_limits_cookie_netblock);
+       domain_limits_free(&ct->domain_limits);
+       /* Delete the log identity here so that the global value is not
+        * reset by config_delete. */
+       if(ct->oldcfg && ct->oldcfg->log_identity) {
+               free(ct->oldcfg->log_identity);
+               ct->oldcfg->log_identity = NULL;
+       }
+       config_delete(ct->oldcfg);
+}
+
+/** get memory for strlist */
+static size_t
+getmem_config_strlist(struct config_strlist* p)
+{
+       size_t m = 0;
+       struct config_strlist* s;
+       for(s = p; s; s = s->next)
+               m += sizeof(*s) + getmem_str(s->str);
+       return m;
+}
+
+/** get memory for str2list */
+static size_t
+getmem_config_str2list(struct config_str2list* p)
+{
+       size_t m = 0;
+       struct config_str2list* s;
+       for(s = p; s; s = s->next)
+               m += sizeof(*s) + getmem_str(s->str) + getmem_str(s->str2);
+       return m;
+}
+
+/** get memory for str3list */
+static size_t
+getmem_config_str3list(struct config_str3list* p)
+{
+       size_t m = 0;
+       struct config_str3list* s;
+       for(s = p; s; s = s->next)
+               m += sizeof(*s) + getmem_str(s->str) + getmem_str(s->str2)
+                       + getmem_str(s->str3);
+       return m;
+}
+
+/** get memory for strbytelist */
+static size_t
+getmem_config_strbytelist(struct config_strbytelist* p)
+{
+       size_t m = 0;
+       struct config_strbytelist* s;
+       for(s = p; s; s = s->next)
+               m += sizeof(*s) + getmem_str(s->str) + (s->str2?s->str2len:0);
+       return m;
+}
+
+/** get memory used by ifs array */
+static size_t
+getmem_ifs(int numifs, char** ifs)
+{
+       size_t m = 0;
+       int i;
+       m += numifs * sizeof(char*);
+       for(i=0; i<numifs; i++)
+               m += getmem_str(ifs[i]);
+       return m;
+}
+
+/** get memory for config_stub */
+static size_t
+getmem_config_stub(struct config_stub* p)
+{
+       size_t m = 0;
+       struct config_stub* s;
+       for(s = p; s; s = s->next)
+               m += sizeof(*s) + getmem_str(s->name)
+                       + getmem_config_strlist(s->hosts)
+                       + getmem_config_strlist(s->addrs);
+       return m;
+}
+
+/** get memory for config_auth */
+static size_t
+getmem_config_auth(struct config_auth* p)
+{
+       size_t m = 0;
+       struct config_auth* s;
+       for(s = p; s; s = s->next)
+               m += sizeof(*s) + getmem_str(s->name)
+                       + getmem_config_strlist(s->masters)
+                       + getmem_config_strlist(s->urls)
+                       + getmem_config_strlist(s->allow_notify)
+                       + getmem_str(s->zonefile)
+                       + s->rpz_taglistlen
+                       + getmem_str(s->rpz_action_override)
+                       + getmem_str(s->rpz_log_name)
+                       + getmem_str(s->rpz_cname);
+       return m;
+}
+
+/** get memory for config_view */
+static size_t
+getmem_config_view(struct config_view* p)
+{
+       size_t m = 0;
+       struct config_view* s;
+       for(s = p; s; s = s->next)
+               m += sizeof(*s) + getmem_str(s->name)
+                       + getmem_config_str2list(s->local_zones)
+                       + getmem_config_strlist(s->local_data)
+                       + getmem_config_strlist(s->local_zones_nodefault)
+#ifdef USE_IPSET
+                       + getmem_config_strlist(s->local_zones_ipset)
+#endif
+                       + getmem_config_str2list(s->respip_actions)
+                       + getmem_config_str2list(s->respip_data);
+
+       return m;
+}
+
+/** get memory used by config_file item, estimate */
+static size_t
+config_file_getmem(struct config_file* cfg)
+{
+       size_t m = 0;
+       m += sizeof(*cfg);
+       m += getmem_config_strlist(cfg->proxy_protocol_port);
+       m += getmem_str(cfg->ssl_service_key);
+       m += getmem_str(cfg->ssl_service_pem);
+       m += getmem_str(cfg->tls_cert_bundle);
+       m += getmem_config_strlist(cfg->tls_additional_port);
+       m += getmem_config_strlist(cfg->tls_session_ticket_keys.first);
+       m += getmem_str(cfg->tls_ciphers);
+       m += getmem_str(cfg->tls_ciphersuites);
+       m += getmem_str(cfg->http_endpoint);
+       m += (cfg->outgoing_avail_ports?65536*sizeof(int):0);
+       m += getmem_str(cfg->target_fetch_policy);
+       m += getmem_str(cfg->if_automatic_ports);
+       m += getmem_ifs(cfg->num_ifs, cfg->ifs);
+       m += getmem_ifs(cfg->num_out_ifs, cfg->out_ifs);
+       m += getmem_config_strlist(cfg->root_hints);
+       m += getmem_config_stub(cfg->stubs);
+       m += getmem_config_stub(cfg->forwards);
+       m += getmem_config_auth(cfg->auths);
+       m += getmem_config_view(cfg->views);
+       m += getmem_config_strlist(cfg->donotqueryaddrs);
+#ifdef CLIENT_SUBNET
+       m += getmem_config_strlist(cfg->client_subnet);
+       m += getmem_config_strlist(cfg->client_subnet_zone);
+#endif
+       m += getmem_config_str2list(cfg->acls);
+       m += getmem_config_str2list(cfg->tcp_connection_limits);
+       m += getmem_config_strlist(cfg->caps_whitelist);
+       m += getmem_config_strlist(cfg->private_address);
+       m += getmem_config_strlist(cfg->private_domain);
+       m += getmem_str(cfg->chrootdir);
+       m += getmem_str(cfg->username);
+       m += getmem_str(cfg->directory);
+       m += getmem_str(cfg->logfile);
+       m += getmem_str(cfg->pidfile);
+       m += getmem_str(cfg->log_identity);
+       m += getmem_str(cfg->identity);
+       m += getmem_str(cfg->version);
+       m += getmem_str(cfg->http_user_agent);
+       m += getmem_str(cfg->nsid_cfg_str);
+       m += (cfg->nsid?cfg->nsid_len:0);
+       m += getmem_str(cfg->module_conf);
+       m += getmem_config_strlist(cfg->trust_anchor_file_list);
+       m += getmem_config_strlist(cfg->trust_anchor_list);
+       m += getmem_config_strlist(cfg->auto_trust_anchor_file_list);
+       m += getmem_config_strlist(cfg->trusted_keys_file_list);
+       m += getmem_config_strlist(cfg->domain_insecure);
+       m += getmem_str(cfg->val_nsec3_key_iterations);
+       m += getmem_config_str2list(cfg->local_zones);
+       m += getmem_config_strlist(cfg->local_zones_nodefault);
+#ifdef USE_IPSET
+       m += getmem_config_strlist(cfg->local_zones_ipset);
+#endif
+       m += getmem_config_strlist(cfg->local_data);
+       m += getmem_config_str3list(cfg->local_zone_overrides);
+       m += getmem_config_strbytelist(cfg->local_zone_tags);
+       m += getmem_config_strbytelist(cfg->acl_tags);
+       m += getmem_config_str3list(cfg->acl_tag_actions);
+       m += getmem_config_str3list(cfg->acl_tag_datas);
+       m += getmem_config_str2list(cfg->acl_view);
+       m += getmem_config_str2list(cfg->interface_actions);
+       m += getmem_config_strbytelist(cfg->interface_tags);
+       m += getmem_config_str3list(cfg->interface_tag_actions);
+       m += getmem_config_str3list(cfg->interface_tag_datas);
+       m += getmem_config_str2list(cfg->interface_view);
+       m += getmem_config_strbytelist(cfg->respip_tags);
+       m += getmem_config_str2list(cfg->respip_actions);
+       m += getmem_config_str2list(cfg->respip_data);
+       m += getmem_ifs(cfg->num_tags, cfg->tagname);
+       m += getmem_config_strlist(cfg->control_ifs.first);
+       m += getmem_str(cfg->server_key_file);
+       m += getmem_str(cfg->server_cert_file);
+       m += getmem_str(cfg->control_key_file);
+       m += getmem_str(cfg->control_cert_file);
+       m += getmem_config_strlist(cfg->python_script);
+       m += getmem_config_strlist(cfg->dynlib_file);
+       m += getmem_str(cfg->dns64_prefix);
+       m += getmem_config_strlist(cfg->dns64_ignore_aaaa);
+       m += getmem_str(cfg->nat64_prefix);
+       m += getmem_str(cfg->dnstap_socket_path);
+       m += getmem_str(cfg->dnstap_ip);
+       m += getmem_str(cfg->dnstap_tls_server_name);
+       m += getmem_str(cfg->dnstap_tls_cert_bundle);
+       m += getmem_str(cfg->dnstap_tls_client_key_file);
+       m += getmem_str(cfg->dnstap_tls_client_cert_file);
+       m += getmem_str(cfg->dnstap_identity);
+       m += getmem_str(cfg->dnstap_version);
+       m += getmem_config_str2list(cfg->ratelimit_for_domain);
+       m += getmem_config_str2list(cfg->ratelimit_below_domain);
+       m += getmem_config_str2list(cfg->edns_client_strings);
+       m += getmem_str(cfg->dnscrypt_provider);
+       m += getmem_config_strlist(cfg->dnscrypt_secret_key);
+       m += getmem_config_strlist(cfg->dnscrypt_provider_cert);
+       m += getmem_config_strlist(cfg->dnscrypt_provider_cert_rotated);
+#ifdef USE_IPSECMOD
+       m += getmem_config_strlist(cfg->ipsecmod_whitelist);
+       m += getmem_str(cfg->ipsecmod_hook);
+#endif
+#ifdef USE_CACHEDB
+       m += getmem_str(cfg->cachedb_backend);
+       m += getmem_str(cfg->cachedb_secret);
+#ifdef USE_REDIS
+       m += getmem_str(cfg->redis_server_host);
+       m += getmem_str(cfg->redis_server_path);
+       m += getmem_str(cfg->redis_server_password);
+#endif
+#endif
+#ifdef USE_IPSET
+       m += getmem_str(cfg->ipset_name_v4);
+       m += getmem_str(cfg->ipset_name_v6);
+#endif
+       return m;
+}
+
+/** fast reload thread, print memory used by construct of items. */
+static int
+fr_printmem(struct fast_reload_thread* fr,
+       struct config_file* newcfg, struct fast_reload_construct* ct)
+{
+       size_t mem = 0;
+       if(fr_poll_for_quit(fr))
+               return 1;
+       mem += views_get_mem(ct->views);
+       mem += respip_set_get_mem(ct->respip_set);
+       mem += auth_zones_get_mem(ct->auth_zones);
+       mem += forwards_get_mem(ct->fwds);
+       mem += hints_get_mem(ct->hints);
+       mem += local_zones_get_mem(ct->local_zones);
+       mem += acl_list_get_mem(ct->acl);
+       mem += acl_list_get_mem(ct->acl_interface);
+       mem += tcl_list_get_mem(ct->tcl);
+       mem += edns_strings_get_mem(ct->edns_strings);
+       mem += anchors_get_mem(ct->anchors);
+       mem += sizeof(*ct->oldcfg);
+       mem += config_file_getmem(newcfg);
+
+       if(!fr_output_printf(fr, "memory use %d bytes\n", (int)mem))
+               return 0;
+       fr_send_notification(fr, fast_reload_notification_printout);
+
+       return 1;
+}
+
+/** fast reload thread, setup the acl_interface for the ports that
+ * the server has. */
+static int
+ct_acl_interface_setup_ports(struct acl_list* acl_interface,
+       struct daemon* daemon)
+{
+       /* clean acl_interface */
+       acl_interface_init(acl_interface);
+       if(!setup_acl_for_ports(acl_interface, daemon->ports[0]))
+               return 0;
+       if(daemon->reuseport) {
+               size_t i;
+               for(i=1; i<daemon->num_ports; i++) {
+                       if(!setup_acl_for_ports(acl_interface,
+                               daemon->ports[i]))
+                               return 0;
+               }
+       }
+       return 1;
+}
+
+/** fast reload, add new change to list of auth zones */
+static int
+fr_add_auth_zone_change(struct fast_reload_thread* fr, struct auth_zone* old_z,
+       struct auth_zone* new_z, int is_deleted, int is_added, int is_changed)
+{
+       struct fast_reload_auth_change* item;
+       item = calloc(1, sizeof(*item));
+       if(!item) {
+               log_err("malloc failure in add auth zone change");
+               return 0;
+       }
+       item->old_z = old_z;
+       item->new_z = new_z;
+       item->is_deleted = is_deleted;
+       item->is_added = is_added;
+       item->is_changed = is_changed;
+
+       item->next = fr->auth_zone_change_list;
+       fr->auth_zone_change_list = item;
+       return 1;
+}
+
+/** See if auth master is equal */
+static int
+xfr_auth_master_equal(struct auth_master* m1, struct auth_master* m2)
+{
+       if(!m1 && !m2)
+               return 1;
+       if(!m1 || !m2)
+               return 0;
+
+       if((m1->host && !m2->host) || (!m1->host && m2->host))
+               return 0;
+       if(m1->host && m2->host && strcmp(m1->host, m2->host) != 0)
+               return 0;
+
+       if((m1->file && !m2->file) || (!m1->file && m2->file))
+               return 0;
+       if(m1->file && m2->file && strcmp(m1->file, m2->file) != 0)
+               return 0;
+
+       if((m1->http && !m2->http) || (!m1->http && m2->http))
+               return 0;
+       if((m1->ixfr && !m2->ixfr) || (!m1->ixfr && m2->ixfr))
+               return 0;
+       if((m1->allow_notify && !m2->allow_notify) || (!m1->allow_notify && m2->allow_notify))
+               return 0;
+       if((m1->ssl && !m2->ssl) || (!m1->ssl && m2->ssl))
+               return 0;
+       if(m1->port != m2->port)
+               return 0;
+       return 1;
+}
+
+/** See if list of auth masters is equal */
+static int
+xfr_masterlist_equal(struct auth_master* list1, struct auth_master* list2)
+{
+       struct auth_master* p1 = list1, *p2 = list2;
+       while(p1 && p2) {
+               if(!xfr_auth_master_equal(p1, p2))
+                       return 0;
+               p1 = p1->next;
+               p2 = p2->next;
+       }
+       if(!p1 && !p2)
+               return 1;
+       return 0;
+}
+
+/** See if the list of masters has changed. */
+static int
+xfr_masters_equal(struct auth_xfer* xfr1, struct auth_xfer* xfr2)
+{
+       if(xfr1 == NULL && xfr2 == NULL)
+               return 1;
+       if(xfr1 == NULL && xfr2 != NULL)
+               return 0;
+       if(xfr1 != NULL && xfr2 == NULL)
+               return 0;
+       if(xfr_masterlist_equal(xfr1->task_probe->masters,
+               xfr2->task_probe->masters) &&
+               xfr_masterlist_equal(xfr1->task_transfer->masters,
+               xfr2->task_transfer->masters))
+               return 1;
+       return 0;
+}
+
+/** Check what has changed in auth zones, like added and deleted zones */
+static int
+auth_zones_check_changes(struct fast_reload_thread* fr,
+       struct fast_reload_construct* ct)
+{
+       /* Check every zone in turn. */
+       struct auth_zone* new_z, *old_z;
+       struct module_env* env = &fr->worker->env;
+
+       fr->old_auth_zones = ct->auth_zones;
+       /* Nobody is using the new ct version yet.
+        * Also the ct lock is picked up before the env lock for auth_zones. */
+       lock_rw_rdlock(&ct->auth_zones->lock);
+
+       /* Find deleted zones by looping over the current list and looking
+        * up in the new tree. */
+       lock_rw_rdlock(&env->auth_zones->lock);
+       RBTREE_FOR(old_z, struct auth_zone*, &env->auth_zones->ztree) {
+               new_z = auth_zone_find(ct->auth_zones, old_z->name,
+                       old_z->namelen, old_z->dclass);
+               if(!new_z) {
+                       /* The zone has been removed. */
+                       if(!fr_add_auth_zone_change(fr, old_z, NULL, 1, 0,
+                               0)) {
+                               lock_rw_unlock(&env->auth_zones->lock);
+                               lock_rw_unlock(&ct->auth_zones->lock);
+                               return 0;
+                       }
+               }
+       }
+       lock_rw_unlock(&env->auth_zones->lock);
+
+       /* Find added zones by looping over new list and lookup in current. */
+       RBTREE_FOR(new_z, struct auth_zone*, &ct->auth_zones->ztree) {
+               lock_rw_rdlock(&env->auth_zones->lock);
+               old_z = auth_zone_find(env->auth_zones, new_z->name,
+                       new_z->namelen, new_z->dclass);
+               if(!old_z) {
+                       /* The zone has been added. */
+                       lock_rw_unlock(&env->auth_zones->lock);
+                       if(!fr_add_auth_zone_change(fr, NULL, new_z, 0, 1,
+                               0)) {
+                               lock_rw_unlock(&ct->auth_zones->lock);
+                               return 0;
+                       }
+               } else {
+                       uint32_t old_serial = 0, new_serial = 0;
+                       int have_old = 0, have_new = 0;
+                       struct auth_xfer* old_xfr, *new_xfr;
+                       lock_rw_rdlock(&new_z->lock);
+                       lock_rw_rdlock(&old_z->lock);
+                       new_xfr = auth_xfer_find(ct->auth_zones, new_z->name,
+                               new_z->namelen, new_z->dclass);
+                       old_xfr = auth_xfer_find(env->auth_zones, old_z->name,
+                               old_z->namelen, old_z->dclass);
+                       if(new_xfr) {
+                               lock_basic_lock(&new_xfr->lock);
+                       }
+                       if(old_xfr) {
+                               lock_basic_lock(&old_xfr->lock);
+                       }
+                       lock_rw_unlock(&env->auth_zones->lock);
+
+                       /* Change in the auth zone can be detected. */
+                       /* A change in serial number means that auth_xfer
+                        * has to be updated. */
+                       have_old = (auth_zone_get_serial(old_z,
+                               &old_serial)!=0);
+                       have_new = (auth_zone_get_serial(new_z,
+                               &new_serial)!=0);
+                       if(have_old != have_new || old_serial != new_serial
+                               || !xfr_masters_equal(old_xfr, new_xfr)) {
+                               /* The zone has been changed. */
+                               if(!fr_add_auth_zone_change(fr, old_z, new_z,
+                                       0, 0, 1)) {
+                                       lock_rw_unlock(&old_z->lock);
+                                       lock_rw_unlock(&new_z->lock);
+                                       lock_rw_unlock(&ct->auth_zones->lock);
+                                       if(new_xfr) {
+                                               lock_basic_unlock(&new_xfr->lock);
+                                       }
+                                       if(old_xfr) {
+                                               lock_basic_unlock(&old_xfr->lock);
+                                       }
+                                       return 0;
+                               }
+                       }
+
+                       if(new_xfr) {
+                               lock_basic_unlock(&new_xfr->lock);
+                       }
+                       if(old_xfr) {
+                               lock_basic_unlock(&old_xfr->lock);
+                       }
+                       lock_rw_unlock(&old_z->lock);
+                       lock_rw_unlock(&new_z->lock);
+               }
+       }
+
+       lock_rw_unlock(&ct->auth_zones->lock);
+       return 1;
+}
+
+/** fast reload thread, construct from config the new items */
+static int
+fr_construct_from_config(struct fast_reload_thread* fr,
+       struct config_file* newcfg, struct fast_reload_construct* ct)
+{
+       int have_view_respip_cfg = 0;
+
+       if(!(ct->views = views_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!views_apply_cfg(ct->views, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->acl = acl_list_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!acl_list_apply_cfg(ct->acl, newcfg, ct->views)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->acl_interface = acl_list_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!ct_acl_interface_setup_ports(ct->acl_interface,
+               fr->worker->daemon)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!acl_interface_apply_cfg(ct->acl_interface, newcfg, ct->views)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->tcl = tcl_list_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!tcl_list_apply_cfg(ct->tcl, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr->worker->daemon->tcl->tree.count != 0)
+               fr->worker->daemon->fast_reload_tcl_has_changes = 1;
+       else    fr->worker->daemon->fast_reload_tcl_has_changes = 0;
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->auth_zones = auth_zones_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!auth_zones_apply_cfg(ct->auth_zones, newcfg, 1, &ct->use_rpz,
+               fr->worker->daemon->env, &fr->worker->daemon->mods)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!auth_zones_check_changes(fr, ct)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->fwds = forwards_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!forwards_apply_cfg(ct->fwds, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->hints = hints_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!hints_apply_cfg(ct->hints, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->local_zones = local_zones_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!local_zones_apply_cfg(ct->local_zones, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->respip_set = respip_set_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!respip_global_apply_cfg(ct->respip_set, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+       if(!respip_views_apply_cfg(ct->views, newcfg, &have_view_respip_cfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       ct->use_response_ip = !respip_set_is_empty(ct->respip_set) ||
+               have_view_respip_cfg;
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->edns_strings = edns_strings_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!edns_strings_apply_cfg(ct->edns_strings, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(fr->worker->env.anchors) {
+               /* There are trust anchors already, so create it for reload. */
+               if(!(ct->anchors = anchors_create())) {
+                       fr_construct_clear(ct);
+                       return 0;
+               }
+               if(!anchors_apply_cfg(ct->anchors, newcfg)) {
+                       fr_construct_clear(ct);
+                       return 0;
+               }
+               if(fr_poll_for_quit(fr))
+                       return 1;
+       }
+
+       if(!val_env_parse_key_iter(newcfg->val_nsec3_key_iterations,
+               &ct->nsec3_keysize, &ct->nsec3_maxiter,
+               &ct->nsec3_keyiter_count)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!read_fetch_policy(&ct->target_fetch_policy,
+               &ct->max_dependency_depth, newcfg->target_fetch_policy)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!(ct->donotq = donotq_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!donotq_apply_cfg(ct->donotq, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!(ct->priv = priv_create())) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!priv_apply_cfg(ct->priv, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(newcfg->caps_whitelist) {
+               if(!(ct->caps_white = caps_white_create())) {
+                       fr_construct_clear(ct);
+                       return 0;
+               }
+               if(!caps_white_apply_cfg(ct->caps_white, newcfg)) {
+                       fr_construct_clear(ct);
+                       return 0;
+               }
+       }
+       if(!nat64_apply_cfg(&ct->nat64, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!setup_wait_limits(&ct->wait_limits_netblock,
+               &ct->wait_limits_cookie_netblock, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(!setup_domain_limits(&ct->domain_limits, newcfg)) {
+               fr_construct_clear(ct);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr))
+               return 1;
+
+       if(!(ct->oldcfg = (struct config_file*)calloc(1,
+               sizeof(*ct->oldcfg)))) {
+               fr_construct_clear(ct);
+               log_err("out of memory");
+               return 0;
+       }
+       if(fr->fr_verb >= 2) {
+               if(!fr_printmem(fr, newcfg, ct))
+                       return 0;
+       }
+       return 1;
+}
+
+/** fast reload thread, finish timers */
+static int
+fr_finish_time(struct fast_reload_thread* fr, struct timeval* time_start,
+       struct timeval* time_read, struct timeval* time_construct,
+       struct timeval* time_reload, struct timeval* time_end)
+{
+       struct timeval total, readtime, constructtime, reloadtime, deletetime;
+       if(gettimeofday(time_end, NULL) < 0)
+               log_err("gettimeofday: %s", strerror(errno));
+
+       timeval_subtract(&total, time_end, time_start);
+       timeval_subtract(&readtime, time_read, time_start);
+       timeval_subtract(&constructtime, time_construct, time_read);
+       timeval_subtract(&reloadtime, time_reload, time_construct);
+       timeval_subtract(&deletetime, time_end, time_reload);
+       if(!fr_output_printf(fr, "read disk  %3d.%6.6ds\n",
+               (int)readtime.tv_sec, (int)readtime.tv_usec))
+               return 0;
+       if(!fr_output_printf(fr, "construct  %3d.%6.6ds\n",
+               (int)constructtime.tv_sec, (int)constructtime.tv_usec))
+               return 0;
+       if(!fr_output_printf(fr, "reload     %3d.%6.6ds\n",
+               (int)reloadtime.tv_sec, (int)reloadtime.tv_usec))
+               return 0;
+       if(!fr_output_printf(fr, "deletes    %3d.%6.6ds\n",
+               (int)deletetime.tv_sec, (int)deletetime.tv_usec))
+               return 0;
+       if(!fr_output_printf(fr, "total time %3d.%6.6ds\n", (int)total.tv_sec,
+               (int)total.tv_usec))
+               return 0;
+       fr_send_notification(fr, fast_reload_notification_printout);
+       return 1;
+}
+
+/** Swap auth zone information */
+static void
+auth_zones_swap(struct auth_zones* az, struct auth_zones* data)
+{
+       rbtree_type oldztree = az->ztree;
+       int old_have_downstream = az->have_downstream;
+       struct auth_zone* old_rpz_first = az->rpz_first;
+
+       az->ztree = data->ztree;
+       data->ztree = oldztree;
+
+       az->have_downstream = data->have_downstream;
+       data->have_downstream = old_have_downstream;
+
+       /* Leave num_query_up and num_query_down, the statistics can
+        * remain counted. */
+
+       az->rpz_first = data->rpz_first;
+       data->rpz_first = old_rpz_first;
+
+       /* The xtree is not swapped. This contains the auth_xfer elements
+        * that contain tasks in progress, like zone transfers.
+        * The unchanged zones can keep their tasks in the tree, and thus
+        * the xfer elements can continue to be their callbacks. */
+}
+
+#ifdef ATOMIC_POINTER_LOCK_FREE
+/** Fast reload thread, if atomics are available, copy the config items
+ * one by one with atomic store operations. */
+static void
+fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg,
+       struct config_file* newcfg)
+{
+#define COPY_VAR_int(var) oldcfg->var = cfg->var; atomic_store((_Atomic int*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_ptr(var) oldcfg->var = cfg->var; atomic_store((void* _Atomic*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_unsigned_int(var) oldcfg->var = cfg->var; atomic_store((_Atomic unsigned*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_size_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic size_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_uint8_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic uint8_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_uint16_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic uint16_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_uint32_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic uint32_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+#define COPY_VAR_int32_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic int32_t*)&cfg->var, newcfg->var); newcfg->var = 0;
+       /* If config file items are missing from this list, they are
+        * not updated by fast-reload +p. */
+       /* For missing items, the oldcfg item is not updated, still NULL,
+        * and the cfg stays the same. The newcfg item is untouched.
+        * The newcfg item is then deleted later. */
+       /* Items that need synchronisation are omitted from the list.
+        * Use fast-reload without +p to update them together. */
+       COPY_VAR_int(verbosity);
+       COPY_VAR_int(stat_interval);
+       COPY_VAR_int(stat_cumulative);
+       COPY_VAR_int(stat_extended);
+       COPY_VAR_int(stat_inhibit_zero);
+       COPY_VAR_int(num_threads);
+       COPY_VAR_int(port);
+       COPY_VAR_int(do_ip4);
+       COPY_VAR_int(do_ip6);
+       COPY_VAR_int(do_nat64);
+       COPY_VAR_int(prefer_ip4);
+       COPY_VAR_int(prefer_ip6);
+       COPY_VAR_int(do_udp);
+       COPY_VAR_int(do_tcp);
+       COPY_VAR_size_t(max_reuse_tcp_queries);
+       COPY_VAR_int(tcp_reuse_timeout);
+       COPY_VAR_int(tcp_auth_query_timeout);
+       COPY_VAR_int(tcp_upstream);
+       COPY_VAR_int(udp_upstream_without_downstream);
+       COPY_VAR_int(tcp_mss);
+       COPY_VAR_int(outgoing_tcp_mss);
+       COPY_VAR_int(tcp_idle_timeout);
+       COPY_VAR_int(do_tcp_keepalive);
+       COPY_VAR_int(tcp_keepalive_timeout);
+       COPY_VAR_int(sock_queue_timeout);
+       COPY_VAR_ptr(proxy_protocol_port);
+       COPY_VAR_ptr(ssl_service_key);
+       COPY_VAR_ptr(ssl_service_pem);
+       COPY_VAR_int(ssl_port);
+       COPY_VAR_int(ssl_upstream);
+       COPY_VAR_ptr(tls_cert_bundle);
+       COPY_VAR_int(tls_win_cert);
+       COPY_VAR_ptr(tls_additional_port);
+       /* The first is used to walk throught the list but last is
+        * only used during config read. */
+       COPY_VAR_ptr(tls_session_ticket_keys.first);
+       COPY_VAR_ptr(tls_session_ticket_keys.last);
+       COPY_VAR_ptr(tls_ciphers);
+       COPY_VAR_ptr(tls_ciphersuites);
+       COPY_VAR_int(tls_use_sni);
+       COPY_VAR_int(https_port);
+       COPY_VAR_ptr(http_endpoint);
+       COPY_VAR_uint32_t(http_max_streams);
+       COPY_VAR_size_t(http_query_buffer_size);
+       COPY_VAR_size_t(http_response_buffer_size);
+       COPY_VAR_int(http_nodelay);
+       COPY_VAR_int(http_notls_downstream);
+       COPY_VAR_int(outgoing_num_ports);
+       COPY_VAR_size_t(outgoing_num_tcp);
+       COPY_VAR_size_t(incoming_num_tcp);
+       COPY_VAR_ptr(outgoing_avail_ports);
+       COPY_VAR_size_t(edns_buffer_size);
+       COPY_VAR_size_t(stream_wait_size);
+       COPY_VAR_size_t(msg_buffer_size);
+       COPY_VAR_size_t(msg_cache_size);
+       COPY_VAR_size_t(msg_cache_slabs);
+       COPY_VAR_size_t(num_queries_per_thread);
+       COPY_VAR_size_t(jostle_time);
+       COPY_VAR_size_t(rrset_cache_size);
+       COPY_VAR_size_t(rrset_cache_slabs);
+       COPY_VAR_int(host_ttl);
+       COPY_VAR_size_t(infra_cache_slabs);
+       COPY_VAR_size_t(infra_cache_numhosts);
+       COPY_VAR_int(infra_cache_min_rtt);
+       COPY_VAR_int(infra_cache_max_rtt);
+       COPY_VAR_int(infra_keep_probing);
+       COPY_VAR_int(delay_close);
+       COPY_VAR_int(udp_connect);
+       COPY_VAR_ptr(target_fetch_policy);
+       COPY_VAR_int(fast_server_permil);
+       COPY_VAR_size_t(fast_server_num);
+       COPY_VAR_int(if_automatic);
+       COPY_VAR_ptr(if_automatic_ports);
+       COPY_VAR_size_t(so_rcvbuf);
+       COPY_VAR_size_t(so_sndbuf);
+       COPY_VAR_int(so_reuseport);
+       COPY_VAR_int(ip_transparent);
+       COPY_VAR_int(ip_freebind);
+       COPY_VAR_int(ip_dscp);
+       /* Not copied because the length and items could then not match.
+          num_ifs, ifs, num_out_ifs, out_ifs
+       */
+       COPY_VAR_ptr(root_hints);
+       COPY_VAR_ptr(stubs);
+       COPY_VAR_ptr(forwards);
+       COPY_VAR_ptr(auths);
+       COPY_VAR_ptr(views);
+       COPY_VAR_ptr(donotqueryaddrs);
+#ifdef CLIENT_SUBNET
+       COPY_VAR_ptr(client_subnet);
+       COPY_VAR_ptr(client_subnet_zone);
+       COPY_VAR_uint16_t(client_subnet_opcode);
+       COPY_VAR_int(client_subnet_always_forward);
+       COPY_VAR_uint8_t(max_client_subnet_ipv4);
+       COPY_VAR_uint8_t(max_client_subnet_ipv6);
+       COPY_VAR_uint8_t(min_client_subnet_ipv4);
+       COPY_VAR_uint8_t(min_client_subnet_ipv6);
+       COPY_VAR_uint32_t(max_ecs_tree_size_ipv4);
+       COPY_VAR_uint32_t(max_ecs_tree_size_ipv6);
+#endif
+       COPY_VAR_ptr(acls);
+       COPY_VAR_int(donotquery_localhost);
+       COPY_VAR_ptr(tcp_connection_limits);
+       COPY_VAR_int(harden_short_bufsize);
+       COPY_VAR_int(harden_large_queries);
+       COPY_VAR_int(harden_glue);
+       COPY_VAR_int(harden_dnssec_stripped);
+       COPY_VAR_int(harden_below_nxdomain);
+       COPY_VAR_int(harden_referral_path);
+       COPY_VAR_int(harden_algo_downgrade);
+       COPY_VAR_int(harden_unknown_additional);
+       COPY_VAR_int(use_caps_bits_for_id);
+       COPY_VAR_ptr(caps_whitelist);
+       COPY_VAR_ptr(private_address);
+       COPY_VAR_ptr(private_domain);
+       COPY_VAR_size_t(unwanted_threshold);
+       COPY_VAR_int(max_ttl);
+       COPY_VAR_int(min_ttl);
+       COPY_VAR_int(max_negative_ttl);
+       COPY_VAR_int(min_negative_ttl);
+       COPY_VAR_int(prefetch);
+       COPY_VAR_int(prefetch_key);
+       COPY_VAR_int(deny_any);
+       COPY_VAR_ptr(chrootdir);
+       COPY_VAR_ptr(username);
+       COPY_VAR_ptr(directory);
+       COPY_VAR_ptr(logfile);
+       COPY_VAR_ptr(pidfile);
+       COPY_VAR_int(use_syslog);
+       COPY_VAR_int(log_time_ascii);
+       COPY_VAR_int(log_queries);
+       COPY_VAR_int(log_replies);
+       COPY_VAR_int(log_tag_queryreply);
+       COPY_VAR_int(log_local_actions);
+       COPY_VAR_int(log_servfail);
+       COPY_VAR_ptr(log_identity);
+       COPY_VAR_int(log_destaddr);
+       COPY_VAR_int(hide_identity);
+       COPY_VAR_int(hide_version);
+       COPY_VAR_int(hide_trustanchor);
+       COPY_VAR_int(hide_http_user_agent);
+       COPY_VAR_ptr(identity);
+       COPY_VAR_ptr(version);
+       COPY_VAR_ptr(http_user_agent);
+       COPY_VAR_ptr(nsid_cfg_str);
+       /* Not copied because the length and items could then not match.
+       nsid;
+       nsid_len;
+       */
+       COPY_VAR_ptr(module_conf);
+       COPY_VAR_ptr(trust_anchor_file_list);
+       COPY_VAR_ptr(trust_anchor_list);
+       COPY_VAR_ptr(auto_trust_anchor_file_list);
+       COPY_VAR_ptr(trusted_keys_file_list);
+       COPY_VAR_ptr(domain_insecure);
+       COPY_VAR_int(trust_anchor_signaling);
+       COPY_VAR_int(root_key_sentinel);
+       COPY_VAR_int32_t(val_date_override);
+       COPY_VAR_int32_t(val_sig_skew_min);
+       COPY_VAR_int32_t(val_sig_skew_max);
+       COPY_VAR_int32_t(val_max_restart);
+       COPY_VAR_int(bogus_ttl);
+       COPY_VAR_int(val_clean_additional);
+       COPY_VAR_int(val_log_level);
+       COPY_VAR_int(val_log_squelch);
+       COPY_VAR_int(val_permissive_mode);
+       COPY_VAR_int(aggressive_nsec);
+       COPY_VAR_int(ignore_cd);
+       COPY_VAR_int(disable_edns_do);
+       COPY_VAR_int(serve_expired);
+       COPY_VAR_int(serve_expired_ttl);
+       COPY_VAR_int(serve_expired_ttl_reset);
+       COPY_VAR_int(serve_expired_reply_ttl);
+       COPY_VAR_int(serve_expired_client_timeout);
+       COPY_VAR_int(ede_serve_expired);
+       COPY_VAR_int(serve_original_ttl);
+       COPY_VAR_ptr(val_nsec3_key_iterations);
+       COPY_VAR_int(zonemd_permissive_mode);
+       COPY_VAR_unsigned_int(add_holddown);
+       COPY_VAR_unsigned_int(del_holddown);
+       COPY_VAR_unsigned_int(keep_missing);
+       COPY_VAR_int(permit_small_holddown);
+       COPY_VAR_size_t(key_cache_size);
+       COPY_VAR_size_t(key_cache_slabs);
+       COPY_VAR_size_t(neg_cache_size);
+       COPY_VAR_ptr(local_zones);
+       COPY_VAR_ptr(local_zones_nodefault);
+#ifdef USE_IPSET
+       COPY_VAR_ptr(local_zones_ipset);
+#endif
+       COPY_VAR_int(local_zones_disable_default);
+       COPY_VAR_ptr(local_data);
+       COPY_VAR_ptr(local_zone_overrides);
+       COPY_VAR_int(unblock_lan_zones);
+       COPY_VAR_int(insecure_lan_zones);
+       /* These reference tags
+       COPY_VAR_ptr(local_zone_tags);
+       COPY_VAR_ptr(acl_tags);
+       COPY_VAR_ptr(acl_tag_actions);
+       COPY_VAR_ptr(acl_tag_datas);
+       */
+       COPY_VAR_ptr(acl_view);
+       COPY_VAR_ptr(interface_actions);
+       /* These reference tags
+       COPY_VAR_ptr(interface_tags);
+       COPY_VAR_ptr(interface_tag_actions);
+       COPY_VAR_ptr(interface_tag_datas);
+       */
+       COPY_VAR_ptr(interface_view);
+       /* This references tags
+       COPY_VAR_ptr(respip_tags);
+       */
+       COPY_VAR_ptr(respip_actions);
+       COPY_VAR_ptr(respip_data);
+       /* Not copied because the length and items could then not match.
+        * also the respip module keeps a pointer to the array in its state.
+          tagname, num_tags
+       */
+       COPY_VAR_int(remote_control_enable);
+       /* The first is used to walk throught the list but last is
+        * only used during config read. */
+       COPY_VAR_ptr(control_ifs.first);
+       COPY_VAR_ptr(control_ifs.last);
+       COPY_VAR_int(control_use_cert);
+       COPY_VAR_int(control_port);
+       COPY_VAR_ptr(server_key_file);
+       COPY_VAR_ptr(server_cert_file);
+       COPY_VAR_ptr(control_key_file);
+       COPY_VAR_ptr(control_cert_file);
+       COPY_VAR_ptr(python_script);
+       COPY_VAR_ptr(dynlib_file);
+       COPY_VAR_int(use_systemd);
+       COPY_VAR_int(do_daemonize);
+       COPY_VAR_int(minimal_responses);
+       COPY_VAR_int(rrset_roundrobin);
+       COPY_VAR_int(unknown_server_time_limit);
+       COPY_VAR_int(discard_timeout);
+       COPY_VAR_int(wait_limit);
+       COPY_VAR_int(wait_limit_cookie);
+       COPY_VAR_ptr(wait_limit_netblock);
+       COPY_VAR_ptr(wait_limit_cookie_netblock);
+       COPY_VAR_size_t(max_udp_size);
+       COPY_VAR_ptr(dns64_prefix);
+       COPY_VAR_int(dns64_synthall);
+       COPY_VAR_ptr(dns64_ignore_aaaa);
+       COPY_VAR_ptr(nat64_prefix);
+       COPY_VAR_int(dnstap);
+       COPY_VAR_int(dnstap_bidirectional);
+       COPY_VAR_ptr(dnstap_socket_path);
+       COPY_VAR_ptr(dnstap_ip);
+       COPY_VAR_int(dnstap_tls);
+       COPY_VAR_ptr(dnstap_tls_server_name);
+       COPY_VAR_ptr(dnstap_tls_cert_bundle);
+       COPY_VAR_ptr(dnstap_tls_client_key_file);
+       COPY_VAR_ptr(dnstap_tls_client_cert_file);
+       COPY_VAR_int(dnstap_send_identity);
+       COPY_VAR_int(dnstap_send_version);
+       COPY_VAR_ptr(dnstap_identity);
+       COPY_VAR_ptr(dnstap_version);
+       COPY_VAR_int(dnstap_sample_rate);
+       COPY_VAR_int(dnstap_log_resolver_query_messages);
+       COPY_VAR_int(dnstap_log_resolver_response_messages);
+       COPY_VAR_int(dnstap_log_client_query_messages);
+       COPY_VAR_int(dnstap_log_client_response_messages);
+       COPY_VAR_int(dnstap_log_forwarder_query_messages);
+       COPY_VAR_int(dnstap_log_forwarder_response_messages);
+       COPY_VAR_int(disable_dnssec_lame_check);
+       COPY_VAR_int(ip_ratelimit);
+       COPY_VAR_int(ip_ratelimit_cookie);
+       COPY_VAR_size_t(ip_ratelimit_slabs);
+       COPY_VAR_size_t(ip_ratelimit_size);
+       COPY_VAR_int(ip_ratelimit_factor);
+       COPY_VAR_int(ip_ratelimit_backoff);
+       COPY_VAR_int(ratelimit);
+       COPY_VAR_size_t(ratelimit_slabs);
+       COPY_VAR_size_t(ratelimit_size);
+       COPY_VAR_ptr(ratelimit_for_domain);
+       COPY_VAR_ptr(ratelimit_below_domain);
+       COPY_VAR_int(ratelimit_factor);
+       COPY_VAR_int(ratelimit_backoff);
+       COPY_VAR_int(outbound_msg_retry);
+       COPY_VAR_int(max_sent_count);
+       COPY_VAR_int(max_query_restarts);
+       COPY_VAR_int(qname_minimisation);
+       COPY_VAR_int(qname_minimisation_strict);
+       COPY_VAR_int(shm_enable);
+       COPY_VAR_int(shm_key);
+       COPY_VAR_ptr(edns_client_strings);
+       COPY_VAR_uint16_t(edns_client_string_opcode);
+       COPY_VAR_int(dnscrypt);
+       COPY_VAR_int(dnscrypt_port);
+       COPY_VAR_ptr(dnscrypt_provider);
+       COPY_VAR_ptr(dnscrypt_secret_key);
+       COPY_VAR_ptr(dnscrypt_provider_cert);
+       COPY_VAR_ptr(dnscrypt_provider_cert_rotated);
+       COPY_VAR_size_t(dnscrypt_shared_secret_cache_size);
+       COPY_VAR_size_t(dnscrypt_shared_secret_cache_slabs);
+       COPY_VAR_size_t(dnscrypt_nonce_cache_size);
+       COPY_VAR_size_t(dnscrypt_nonce_cache_slabs);
+       COPY_VAR_int(pad_responses);
+       COPY_VAR_size_t(pad_responses_block_size);
+       COPY_VAR_int(pad_queries);
+       COPY_VAR_size_t(pad_queries_block_size);
+#ifdef USE_IPSECMOD
+       COPY_VAR_int(ipsecmod_enabled);
+       COPY_VAR_ptr(ipsecmod_whitelist);
+       COPY_VAR_ptr(ipsecmod_hook);
+       COPY_VAR_int(ipsecmod_ignore_bogus);
+       COPY_VAR_int(ipsecmod_max_ttl);
+       COPY_VAR_int(ipsecmod_strict);
+#endif
+#ifdef USE_CACHEDB
+       COPY_VAR_ptr(cachedb_backend);
+       COPY_VAR_ptr(cachedb_secret);
+       COPY_VAR_int(cachedb_no_store);
+       COPY_VAR_int(cachedb_check_when_serve_expired);
+#ifdef USE_REDIS
+       COPY_VAR_ptr(redis_server_host);
+       COPY_VAR_int(redis_server_port);
+       COPY_VAR_ptr(redis_server_path);
+       COPY_VAR_ptr(redis_server_password);
+       COPY_VAR_int(redis_timeout);
+       COPY_VAR_int(redis_expire_records);
+       COPY_VAR_int(redis_logical_db);
+#endif
+#endif
+       COPY_VAR_int(do_answer_cookie);
+       /* Not copied because the length and content could then not match.
+          cookie_secret[40], cookie_secret_len
+       */
+#ifdef USE_IPSET
+       COPY_VAR_ptr(ipset_name_v4);
+       COPY_VAR_ptr(ipset_name_v6);
+#endif
+       COPY_VAR_int(ede);
+}
+#endif /* ATOMIC_POINTER_LOCK_FREE */
+
+/** fast reload thread, adjust the cache sizes */
+static void
+fr_adjust_cache(struct module_env* env, struct config_file* oldcfg)
+{
+       if(env->cfg->msg_cache_size != oldcfg->msg_cache_size)
+               slabhash_adjust_size(env->msg_cache, env->cfg->msg_cache_size);
+       if(env->cfg->rrset_cache_size != oldcfg->rrset_cache_size)
+               slabhash_adjust_size(&env->rrset_cache->table,
+                       env->cfg->rrset_cache_size);
+       if(env->key_cache &&
+               env->cfg->key_cache_size != oldcfg->key_cache_size)
+               slabhash_adjust_size(env->key_cache->slab,
+                       env->cfg->key_cache_size);
+       if(env->cfg->infra_cache_numhosts != oldcfg->infra_cache_numhosts) {
+               size_t inframem = env->cfg->infra_cache_numhosts *
+                       (sizeof(struct infra_key) + sizeof(struct infra_data)
+                       + INFRA_BYTES_NAME);
+               slabhash_adjust_size(env->infra_cache->hosts, inframem);
+       }
+       if(env->cfg->ratelimit_size != oldcfg->ratelimit_size) {
+               slabhash_adjust_size(env->infra_cache->domain_rates,
+                       env->cfg->ratelimit_size);
+               slabhash_adjust_size(env->infra_cache->client_ip_rates,
+                       env->cfg->ratelimit_size);
+       }
+       if(env->neg_cache &&
+               env->cfg->neg_cache_size != oldcfg->neg_cache_size) {
+               val_neg_adjust_size(env->neg_cache, env->cfg->neg_cache_size);
+       }
+}
+
+/** fast reload thread, adjust the iterator env */
+static void
+fr_adjust_iter_env(struct module_env* env, struct fast_reload_construct* ct)
+{
+       int m;
+       struct iter_env* iter_env = NULL;
+       /* There is no comparison here to see if no options changed and thus
+        * no swap is needed, the trees with addresses and domains can be
+        * large and that would take too long. Instead the trees are
+        * swapped in. */
+
+       /* Because the iterator env is not locked, the update cannot happen
+        * when fr nopause is used. Without it the fast reload pauses the
+        * other threads, so they are not currently using the structure. */
+       m = modstack_find(env->modstack, "iterator");
+       if(m != -1) iter_env = (struct iter_env*)env->modinfo[m];
+       if(iter_env) {
+               /* Swap the data so that the delete happens afterwards. */
+               int* oldtargetfetchpolicy = iter_env->target_fetch_policy;
+               int oldmaxdependencydepth = iter_env->max_dependency_depth;
+               struct iter_donotq* olddonotq = iter_env->donotq;
+               struct iter_priv* oldpriv = iter_env->priv;
+               struct rbtree_type* oldcapswhite = iter_env->caps_white;
+               struct iter_nat64 oldnat64 = iter_env->nat64;
+
+               iter_env->target_fetch_policy = ct->target_fetch_policy;
+               iter_env->max_dependency_depth = ct->max_dependency_depth;
+               iter_env->donotq = ct->donotq;
+               iter_env->priv = ct->priv;
+               iter_env->caps_white = ct->caps_white;
+               iter_env->nat64 = ct->nat64;
+               iter_env->outbound_msg_retry = env->cfg->outbound_msg_retry;
+               iter_env->max_sent_count = env->cfg->max_sent_count;
+               iter_env->max_query_restarts = env->cfg->max_query_restarts;
+
+               ct->target_fetch_policy = oldtargetfetchpolicy;
+               ct->max_dependency_depth = oldmaxdependencydepth;
+               ct->donotq = olddonotq;
+               ct->priv = oldpriv;
+               ct->caps_white = oldcapswhite;
+               ct->nat64 = oldnat64;
+       }
+}
+
+/** fast reload thread, adjust the validator env */
+static void
+fr_adjust_val_env(struct module_env* env, struct fast_reload_construct* ct,
+       struct config_file* oldcfg)
+{
+       int m;
+       struct val_env* val_env = NULL;
+       if(env->cfg->bogus_ttl == oldcfg->bogus_ttl &&
+               env->cfg->val_date_override == oldcfg->val_date_override &&
+               env->cfg->val_sig_skew_min == oldcfg->val_sig_skew_min &&
+               env->cfg->val_sig_skew_max == oldcfg->val_sig_skew_max &&
+               env->cfg->val_max_restart == oldcfg->val_max_restart &&
+               strcmp(env->cfg->val_nsec3_key_iterations,
+               oldcfg->val_nsec3_key_iterations) == 0)
+               return; /* no changes */
+
+       /* Because the validator env is not locked, the update cannot happen
+        * when fr nopause is used. Without it the fast reload pauses the
+        * other threads, so they are not currently using the structure. */
+       m = modstack_find(env->modstack, "validator");
+       if(m != -1) val_env = (struct val_env*)env->modinfo[m];
+       if(val_env) {
+               /* Swap the arrays so that the delete happens afterwards. */
+               size_t* oldkeysize = val_env->nsec3_keysize;
+               size_t* oldmaxiter = val_env->nsec3_maxiter;
+               val_env->nsec3_keysize = NULL;
+               val_env->nsec3_maxiter = NULL;
+               val_env_apply_cfg(val_env, env->cfg, ct->nsec3_keysize,
+                       ct->nsec3_maxiter, ct->nsec3_keyiter_count);
+               ct->nsec3_keysize = oldkeysize;
+               ct->nsec3_maxiter = oldmaxiter;
+               if(env->neg_cache) {
+                       lock_basic_lock(&env->neg_cache->lock);
+                       env->neg_cache->nsec3_max_iter = val_env->
+                               nsec3_maxiter[val_env->nsec3_keyiter_count-1];
+                       lock_basic_unlock(&env->neg_cache->lock);
+               }
+       }
+}
+
+/** fast reload thread, adjust the infra cache parameters */
+static void
+fr_adjust_infra(struct module_env* env, struct fast_reload_construct* ct)
+{
+       struct infra_cache* infra = env->infra_cache;
+       struct config_file* cfg = env->cfg;
+       struct rbtree_type oldwaitlim = infra->wait_limits_netblock;
+       struct rbtree_type oldwaitlimcookie =
+               infra->wait_limits_cookie_netblock;
+       struct rbtree_type olddomainlim = infra->domain_limits;
+
+       /* The size of the infra cache and ip rates is changed
+        * in fr_adjust_cache. */
+       infra->host_ttl = cfg->host_ttl;
+       infra->infra_keep_probing = cfg->infra_keep_probing;
+       infra_dp_ratelimit = cfg->ratelimit;
+       infra_ip_ratelimit = cfg->ip_ratelimit;
+       infra_ip_ratelimit_cookie = cfg->ip_ratelimit_cookie;
+       infra->wait_limits_netblock = ct->wait_limits_netblock;
+       infra->wait_limits_cookie_netblock = ct->wait_limits_cookie_netblock;
+       infra->domain_limits = ct->domain_limits;
+
+       ct->wait_limits_netblock = oldwaitlim;
+       ct->wait_limits_cookie_netblock = oldwaitlimcookie;
+       ct->domain_limits = olddomainlim;
+}
+
+/** fast reload thread, reload config with putting the new config items
+ * in place and swapping out the old items. */
+static int
+fr_reload_config(struct fast_reload_thread* fr, struct config_file* newcfg,
+       struct fast_reload_construct* ct)
+{
+       struct daemon* daemon = fr->worker->daemon;
+       struct module_env* env = daemon->env;
+
+       /* These are constructed in the fr_construct_from_config routine. */
+       log_assert(ct->oldcfg);
+       log_assert(ct->fwds);
+       log_assert(ct->hints);
+
+       /* Grab big locks to satisfy lock conditions. */
+       lock_rw_wrlock(&ct->views->lock);
+       lock_rw_wrlock(&env->views->lock);
+       lock_rw_wrlock(&ct->respip_set->lock);
+       lock_rw_wrlock(&env->respip_set->lock);
+       lock_rw_wrlock(&ct->local_zones->lock);
+       lock_rw_wrlock(&daemon->local_zones->lock);
+       lock_rw_wrlock(&ct->auth_zones->rpz_lock);
+       lock_rw_wrlock(&env->auth_zones->rpz_lock);
+       lock_rw_wrlock(&ct->auth_zones->lock);
+       lock_rw_wrlock(&env->auth_zones->lock);
+       lock_rw_wrlock(&ct->fwds->lock);
+       lock_rw_wrlock(&env->fwds->lock);
+       lock_rw_wrlock(&ct->hints->lock);
+       lock_rw_wrlock(&env->hints->lock);
+       if(ct->anchors) {
+               lock_basic_lock(&ct->anchors->lock);
+               lock_basic_lock(&env->anchors->lock);
+       }
+
+#ifdef ATOMIC_POINTER_LOCK_FREE
+       if(fr->fr_nopause) {
+               fr_atomic_copy_cfg(ct->oldcfg, env->cfg, newcfg);
+       } else {
+#endif
+               /* Store old config elements. */
+               *ct->oldcfg = *env->cfg;
+               /* Insert new config elements. */
+               *env->cfg = *newcfg;
+#ifdef ATOMIC_POINTER_LOCK_FREE
+       }
+#endif
+
+       if(env->cfg->log_identity || ct->oldcfg->log_identity) {
+               /* pick up new log_identity string to use for log output. */
+               log_ident_set_or_default(env->cfg->log_identity);
+       }
+       /* the newcfg elements are in env->cfg, so should not be freed here. */
+#ifdef ATOMIC_POINTER_LOCK_FREE
+       /* if used, the routine that copies the config has zeroed items. */
+       if(!fr->fr_nopause)
+#endif
+               memset(newcfg, 0, sizeof(*newcfg));
+
+       /* Quickly swap the tree roots themselves with the already allocated
+        * elements. This is a quick swap operation on the pointer.
+        * The other threads are stopped and locks are held, so that a
+        * consistent view of the configuration, before, and after, exists
+        * towards the state machine for query resolution. */
+       forwards_swap_tree(env->fwds, ct->fwds);
+       hints_swap_tree(env->hints, ct->hints);
+       views_swap_tree(env->views, ct->views);
+       acl_list_swap_tree(daemon->acl, ct->acl);
+       acl_list_swap_tree(daemon->acl_interface, ct->acl_interface);
+       tcl_list_swap_tree(daemon->tcl, ct->tcl);
+       local_zones_swap_tree(daemon->local_zones, ct->local_zones);
+       respip_set_swap_tree(env->respip_set, ct->respip_set);
+       daemon->use_response_ip = ct->use_response_ip;
+       daemon->use_rpz = ct->use_rpz;
+       auth_zones_swap(env->auth_zones, ct->auth_zones);
+       edns_strings_swap_tree(env->edns_strings, ct->edns_strings);
+       anchors_swap_tree(env->anchors, ct->anchors);
+#ifdef USE_CACHEDB
+       daemon->env->cachedb_enabled = cachedb_is_enabled(&daemon->mods,
+               daemon->env);
+#endif
+#ifdef USE_DNSTAP
+       if(env->cfg->dnstap) {
+               if(!fr->fr_nopause)
+                       dt_apply_cfg(daemon->dtenv, env->cfg);
+               else dt_apply_logcfg(daemon->dtenv, env->cfg);
+       }
+#endif
+       fr_adjust_cache(env, ct->oldcfg);
+       if(!fr->fr_nopause) {
+               fr_adjust_iter_env(env, ct);
+               fr_adjust_val_env(env, ct, ct->oldcfg);
+               fr_adjust_infra(env, ct);
+       }
+
+       /* Set globals with new config. */
+       config_apply(env->cfg);
+
+       lock_rw_unlock(&ct->views->lock);
+       lock_rw_unlock(&env->views->lock);
+       lock_rw_unlock(&ct->respip_set->lock);
+       lock_rw_unlock(&env->respip_set->lock);
+       lock_rw_unlock(&ct->local_zones->lock);
+       lock_rw_unlock(&daemon->local_zones->lock);
+       lock_rw_unlock(&ct->auth_zones->lock);
+       lock_rw_unlock(&env->auth_zones->lock);
+       lock_rw_unlock(&ct->auth_zones->rpz_lock);
+       lock_rw_unlock(&env->auth_zones->rpz_lock);
+       lock_rw_unlock(&ct->fwds->lock);
+       lock_rw_unlock(&env->fwds->lock);
+       lock_rw_unlock(&ct->hints->lock);
+       lock_rw_unlock(&env->hints->lock);
+       if(ct->anchors) {
+               lock_basic_unlock(&ct->anchors->lock);
+               lock_basic_unlock(&env->anchors->lock);
+       }
+
+       return 1;
+}
+
+/** fast reload, poll for ack incoming. */
+static void
+fr_poll_for_ack(struct fast_reload_thread* fr)
+{
+       int loopexit = 0, bcount = 0;
+       uint32_t cmd;
+       ssize_t ret;
+
+       if(fr->need_to_quit)
+               return;
+       /* Is there data? */
+       if(!sock_poll_timeout(fr->commpair[1], -1, 1, 0, NULL)) {
+               log_err("fr_poll_for_ack: poll failed");
+               return;
+       }
+
+       /* Read the data */
+       while(1) {
+               if(++loopexit > IPC_LOOP_MAX) {
+                       log_err("fr_poll_for_ack: recv loops %s",
+                               sock_strerror(errno));
+                       return;
+               }
+               ret = recv(fr->commpair[1], ((char*)&cmd)+bcount,
+                       sizeof(cmd)-bcount, 0);
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again. */
+                       log_err("fr_poll_for_ack: recv: %s",
+                               sock_strerror(errno));
+                       return;
+               } else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+                       bcount += ret;
+                       if((size_t)bcount < sizeof(cmd))
+                               continue;
+               }
+               break;
+       }
+       if(cmd == fast_reload_notification_exit) {
+               fr->need_to_quit = 1;
+               verbose(VERB_ALGO, "fast reload wait for ack: "
+                       "exit notification received");
+               return;
+       }
+       if(cmd != fast_reload_notification_reload_ack) {
+               verbose(VERB_ALGO, "fast reload wait for ack: "
+                       "wrong notification %d", (int)cmd);
+       }
+}
+
+/** fast reload thread, reload ipc communication to stop and start threads. */
+static int
+fr_reload_ipc(struct fast_reload_thread* fr, struct config_file* newcfg,
+       struct fast_reload_construct* ct)
+{
+       int result = 1;
+       if(!fr->fr_nopause) {
+               fr_send_notification(fr, fast_reload_notification_reload_stop);
+               fr_poll_for_ack(fr);
+       }
+       if(!fr_reload_config(fr, newcfg, ct)) {
+               result = 0;
+       }
+       if(!fr->fr_nopause) {
+               fr_send_notification(fr, fast_reload_notification_reload_start);
+               fr_poll_for_ack(fr);
+       }
+       return result;
+}
+
+/** fast reload thread, load config */
+static int
+fr_load_config(struct fast_reload_thread* fr, struct timeval* time_read,
+       struct timeval* time_construct, struct timeval* time_reload)
+{
+       struct fast_reload_construct ct;
+       struct config_file* newcfg = NULL;
+       memset(&ct, 0, sizeof(ct));
+
+       /* Read file. */
+       if(!fr_read_config(fr, &newcfg))
+               return 0;
+       if(gettimeofday(time_read, NULL) < 0)
+               log_err("gettimeofday: %s", strerror(errno));
+       if(fr_poll_for_quit(fr)) {
+               config_delete(newcfg);
+               return 1;
+       }
+
+       /* Check if the config can be loaded */
+       if(!fr_check_tag_defines(fr, newcfg)) {
+               config_delete(newcfg);
+               return 0;
+       }
+       if(!fr_check_compat_cfg(fr, newcfg)) {
+               config_delete(newcfg);
+               return 0;
+       }
+       if(!fr_check_nopause_cfg(fr, newcfg)) {
+               config_delete(newcfg);
+               return 0;
+       }
+       if(fr_poll_for_quit(fr)) {
+               config_delete(newcfg);
+               return 1;
+       }
+
+       /* Construct items. */
+       if(!fr_construct_from_config(fr, newcfg, &ct)) {
+               config_delete(newcfg);
+               if(!fr_output_printf(fr, "Could not construct from the "
+                       "config, check for errors with unbound-checkconf, or "
+                       "out of memory. The parse errors are printed in "
+                       "the log.\n"))
+                       return 0;
+               fr_send_notification(fr, fast_reload_notification_printout);
+               return 0;
+       }
+       if(gettimeofday(time_construct, NULL) < 0)
+               log_err("gettimeofday: %s", strerror(errno));
+       if(fr_poll_for_quit(fr)) {
+               config_delete(newcfg);
+               fr_construct_clear(&ct);
+               return 1;
+       }
+
+       /* Reload server. */
+       if(!fr_reload_ipc(fr, newcfg, &ct)) {
+               config_delete(newcfg);
+               fr_construct_clear(&ct);
+               if(!fr_output_printf(fr, "error: reload failed\n"))
+                       return 0;
+               fr_send_notification(fr, fast_reload_notification_printout);
+               return 0;
+       }
+       if(gettimeofday(time_reload, NULL) < 0)
+               log_err("gettimeofday: %s", strerror(errno));
+
+       if(fr_poll_for_quit(fr)) {
+               config_delete(newcfg);
+               fr_construct_clear(&ct);
+               return 1;
+       }
+       if(fr->fr_nopause) {
+               /* Poll every thread, with a no-work poll item over the
+                * command pipe. This makes the worker thread surely move
+                * to deal with that event, and thus the thread is no longer
+                * holding, eg. a string item from the old config struct.
+                * And then the old config struct can safely be deleted.
+                * Only needed when nopause is used, because without that
+                * the worker threads are already waiting on a command pipe
+                * item. This nopause command pipe item does not take work,
+                * it returns immediately, so it does not delay the workers.
+                * They can be polled one at a time. But its processing causes
+                * the worker to have released data items from old config.
+                * This also makes sure the threads are not holding locks on
+                * individual items in the local_zones, views, respip_set. */
+               fr_send_notification(fr,
+                       fast_reload_notification_reload_nopause_poll);
+               fr_poll_for_ack(fr);
+       }
+
+       /* Delete old. */
+       config_delete(newcfg);
+       fr_construct_clear(&ct);
+       return 1;
+}
+
+/** fast reload thread. the thread main function */
+static void* fast_reload_thread_main(void* arg)
+{
+       struct fast_reload_thread* fast_reload_thread = (struct fast_reload_thread*)arg;
+       struct timeval time_start, time_read, time_construct, time_reload,
+               time_end;
+       log_thread_set(&fast_reload_thread->threadnum);
+
+       verbose(VERB_ALGO, "start fast reload thread");
+       if(fast_reload_thread->fr_verb >= 1) {
+               fr_init_time(&time_start, &time_read, &time_construct,
+                       &time_reload, &time_end);
+               if(fr_poll_for_quit(fast_reload_thread))
+                       goto done;
+       }
+
+       /* print output to the client */
+       if(fast_reload_thread->fr_verb >= 1) {
+               if(!fr_output_printf(fast_reload_thread, "thread started\n"))
+                       goto done_error;
+               fr_send_notification(fast_reload_thread,
+                       fast_reload_notification_printout);
+               if(fr_poll_for_quit(fast_reload_thread))
+                       goto done;
+       }
+
+       if(!fr_load_config(fast_reload_thread, &time_read, &time_construct,
+               &time_reload))
+               goto done_error;
+       if(fr_poll_for_quit(fast_reload_thread))
+               goto done;
+
+       if(fast_reload_thread->fr_verb >= 1) {
+               if(!fr_finish_time(fast_reload_thread, &time_start, &time_read,
+                       &time_construct, &time_reload, &time_end))
+                       goto done_error;
+               if(fr_poll_for_quit(fast_reload_thread))
+                       goto done;
+       }
+
+       if(!fr_output_printf(fast_reload_thread, "ok\n"))
+               goto done_error;
+       fr_send_notification(fast_reload_thread,
+               fast_reload_notification_printout);
+       verbose(VERB_ALGO, "stop fast reload thread");
+       /* If this is not an exit due to quit earlier, send regular done. */
+       if(!fast_reload_thread->need_to_quit)
+               fr_send_notification(fast_reload_thread,
+                       fast_reload_notification_done);
+       /* If during the fast_reload_notification_done send,
+        * fast_reload_notification_exit was received, ack it. If the
+        * thread is exiting due to quit received earlier, also ack it.*/
+done:
+       if(fast_reload_thread->need_to_quit)
+               fr_send_notification(fast_reload_thread,
+                       fast_reload_notification_exited);
+       return NULL;
+done_error:
+       verbose(VERB_ALGO, "stop fast reload thread with done_error");
+       fr_send_notification(fast_reload_thread,
+               fast_reload_notification_done_error);
+       return NULL;
+}
+#endif /* !THREADS_DISABLED */
+
+/** create a socketpair for bidirectional communication, false on failure */
+static int
+create_socketpair(int* pair, struct ub_randstate* rand)
+{
+#ifndef USE_WINSOCK
+       if(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
+               log_err("socketpair: %s", strerror(errno));
+               return 0;
+       }
+       (void)rand;
+#else
+       struct sockaddr_in addr, baddr, accaddr, connaddr;
+       socklen_t baddrlen, accaddrlen, connaddrlen;
+       uint8_t localhost[] = {127, 0, 0, 1};
+       uint8_t nonce[16], recvnonce[16];
+       size_t i;
+       int lst, pollin_event, bcount, loopcount;
+       int connect_poll_timeout = 200; /* msec to wait for connection */
+       ssize_t ret;
+       pair[0] = -1;
+       pair[1] = -1;
+       for(i=0; i<sizeof(nonce); i++) {
+               nonce[i] = ub_random_max(rand, 256);
+       }
+       lst = socket(AF_INET, SOCK_STREAM, 0);
+       if(lst == -1) {
+               log_err("create_socketpair: socket: %s", sock_strerror(errno));
+               return 0;
+       }
+       memset(&addr, 0, sizeof(addr));
+       addr.sin_family = AF_INET;
+       addr.sin_port = 0;
+       memcpy(&addr.sin_addr, localhost, 4);
+       if(bind(lst, (struct sockaddr*)&addr, (socklen_t)sizeof(addr))
+               == -1) {
+               log_err("create socketpair: bind: %s", sock_strerror(errno));
+               sock_close(lst);
+               return 0;
+       }
+       if(listen(lst, 12) == -1) {
+               log_err("create socketpair: listen: %s", sock_strerror(errno));
+               sock_close(lst);
+               return 0;
+       }
+
+       pair[1] = socket(AF_INET, SOCK_STREAM, 0);
+       if(pair[1] == -1) {
+               log_err("create socketpair: socket: %s", sock_strerror(errno));
+               sock_close(lst);
+               return 0;
+       }
+       baddrlen = (socklen_t)sizeof(baddr);
+       if(getsockname(lst, (struct sockaddr*)&baddr, &baddrlen) == -1) {
+               log_err("create socketpair: getsockname: %s",
+                       sock_strerror(errno));
+               sock_close(lst);
+               sock_close(pair[1]);
+               pair[1] = -1;
+               return 0;
+       }
+       if(baddrlen > (socklen_t)sizeof(baddr)) {
+               log_err("create socketpair: getsockname returned addr too big");
+               sock_close(lst);
+               sock_close(pair[1]);
+               pair[1] = -1;
+               return 0;
+       }
+       /* the socket is blocking */
+       if(connect(pair[1], (struct sockaddr*)&baddr, baddrlen) == -1) {
+               log_err("create socketpair: connect: %s",
+                       sock_strerror(errno));
+               sock_close(lst);
+               sock_close(pair[1]);
+               pair[1] = -1;
+               return 0;
+       }
+       if(!sock_poll_timeout(lst, connect_poll_timeout, 1, 0, &pollin_event)) {
+               log_err("create socketpair: poll for accept failed: %s",
+                       sock_strerror(errno));
+               sock_close(lst);
+               sock_close(pair[1]);
+               pair[1] = -1;
+               return 0;
+       }
+       if(!pollin_event) {
+               log_err("create socketpair: poll timeout for accept");
+               sock_close(lst);
+               sock_close(pair[1]);
+               pair[1] = -1;
+               return 0;
+       }
+       accaddrlen = (socklen_t)sizeof(accaddr);
+       pair[0] = accept(lst, (struct sockaddr*)&accaddr, &accaddrlen);
+       if(pair[0] == -1) {
+               log_err("create socketpair: accept: %s", sock_strerror(errno));
+               sock_close(lst);
+               sock_close(pair[1]);
+               pair[1] = -1;
+               return 0;
+       }
+       if(accaddrlen > (socklen_t)sizeof(accaddr)) {
+               log_err("create socketpair: accept returned addr too big");
+               sock_close(lst);
+               sock_close(pair[0]);
+               sock_close(pair[1]);
+               pair[0] = -1;
+               pair[1] = -1;
+               return 0;
+       }
+       if(accaddr.sin_family != AF_INET ||
+          memcmp(localhost, &accaddr.sin_addr, 4) != 0) {
+               log_err("create socketpair: accept from wrong address");
+               sock_close(lst);
+               sock_close(pair[0]);
+               sock_close(pair[1]);
+               pair[0] = -1;
+               pair[1] = -1;
+               return 0;
+       }
+       connaddrlen = (socklen_t)sizeof(connaddr);
+       if(getsockname(pair[1], (struct sockaddr*)&connaddr, &connaddrlen)
+               == -1) {
+               log_err("create socketpair: getsockname connectedaddr: %s",
+                       sock_strerror(errno));
+               sock_close(lst);
+               sock_close(pair[0]);
+               sock_close(pair[1]);
+               pair[0] = -1;
+               pair[1] = -1;
+               return 0;
+       }
+       if(connaddrlen > (socklen_t)sizeof(connaddr)) {
+               log_err("create socketpair: getsockname connectedaddr returned addr too big");
+               sock_close(lst);
+               sock_close(pair[0]);
+               sock_close(pair[1]);
+               pair[0] = -1;
+               pair[1] = -1;
+               return 0;
+       }
+       if(connaddr.sin_family != AF_INET ||
+          memcmp(localhost, &connaddr.sin_addr, 4) != 0) {
+               log_err("create socketpair: getsockname connectedaddr returned wrong address");
+               sock_close(lst);
+               sock_close(pair[0]);
+               sock_close(pair[1]);
+               pair[0] = -1;
+               pair[1] = -1;
+               return 0;
+       }
+       if(accaddr.sin_port != connaddr.sin_port) {
+               log_err("create socketpair: accept from wrong port");
+               sock_close(lst);
+               sock_close(pair[0]);
+               sock_close(pair[1]);
+               pair[0] = -1;
+               pair[1] = -1;
+               return 0;
+       }
+       sock_close(lst);
+
+       loopcount = 0;
+       bcount = 0;
+       while(1) {
+               if(++loopcount > IPC_LOOP_MAX) {
+                       log_err("create socketpair: send failed due to loop");
+                       sock_close(pair[0]);
+                       sock_close(pair[1]);
+                       pair[0] = -1;
+                       pair[1] = -1;
+                       return 0;
+               }
+               ret = send(pair[1], (void*)(nonce+bcount),
+                       sizeof(nonce)-bcount, 0);
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again. */
+                       log_err("create socketpair: send: %s", sock_strerror(errno));
+                       sock_close(pair[0]);
+                       sock_close(pair[1]);
+                       pair[0] = -1;
+                       pair[1] = -1;
+                       return 0;
+               } else if(ret+(ssize_t)bcount != sizeof(nonce)) {
+                       bcount += ret;
+                       if((size_t)bcount < sizeof(nonce))
+                               continue;
+               }
+               break;
+       }
+
+       if(!sock_poll_timeout(pair[0], connect_poll_timeout, 1, 0, &pollin_event)) {
+               log_err("create socketpair: poll failed: %s",
+                       sock_strerror(errno));
+               sock_close(pair[0]);
+               sock_close(pair[1]);
+               pair[0] = -1;
+               pair[1] = -1;
+               return 0;
+       }
+       if(!pollin_event) {
+               log_err("create socketpair: poll timeout for recv");
+               sock_close(pair[0]);
+               sock_close(pair[1]);
+               pair[0] = -1;
+               pair[1] = -1;
+               return 0;
+       }
+
+       loopcount = 0;
+       bcount = 0;
+       while(1) {
+               if(++loopcount > IPC_LOOP_MAX) {
+                       log_err("create socketpair: recv failed due to loop");
+                       sock_close(pair[0]);
+                       sock_close(pair[1]);
+                       pair[0] = -1;
+                       pair[1] = -1;
+                       return 0;
+               }
+               ret = recv(pair[0], (void*)(recvnonce+bcount),
+                       sizeof(nonce)-bcount, 0);
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again. */
+                       log_err("create socketpair: recv: %s", sock_strerror(errno));
+                       sock_close(pair[0]);
+                       sock_close(pair[1]);
+                       pair[0] = -1;
+                       pair[1] = -1;
+                       return 0;
+               } else if(ret == 0) {
+                       log_err("create socketpair: stream closed");
+                       sock_close(pair[0]);
+                       sock_close(pair[1]);
+                       pair[0] = -1;
+                       pair[1] = -1;
+                       return 0;
+               } else if(ret+(ssize_t)bcount != sizeof(nonce)) {
+                       bcount += ret;
+                       if((size_t)bcount < sizeof(nonce))
+                               continue;
+               }
+               break;
+       }
+
+       if(memcmp(nonce, recvnonce, sizeof(nonce)) != 0) {
+               log_err("create socketpair: recv wrong nonce");
+               sock_close(pair[0]);
+               sock_close(pair[1]);
+               pair[0] = -1;
+               pair[1] = -1;
+               return 0;
+       }
+#endif
+       return 1;
+}
+
+/** fast reload thread. setup the thread info */
+static int
+fast_reload_thread_setup(struct worker* worker, int fr_verb, int fr_nopause,
+       int fr_drop_mesh)
+{
+       struct fast_reload_thread* fr;
+       int numworkers = worker->daemon->num;
+       worker->daemon->fast_reload_thread = (struct fast_reload_thread*)
+               calloc(1, sizeof(*worker->daemon->fast_reload_thread));
+       if(!worker->daemon->fast_reload_thread)
+               return 0;
+       fr = worker->daemon->fast_reload_thread;
+       fr->fr_verb = fr_verb;
+       fr->fr_nopause = fr_nopause;
+       fr->fr_drop_mesh = fr_drop_mesh;
+       worker->daemon->fast_reload_drop_mesh = fr->fr_drop_mesh;
+       /* The thread id printed in logs, numworker+1 is the dnstap thread.
+        * This is numworkers+2. */
+       fr->threadnum = numworkers+2;
+       fr->commpair[0] = -1;
+       fr->commpair[1] = -1;
+       fr->commreload[0] = -1;
+       fr->commreload[1] = -1;
+       if(!create_socketpair(fr->commpair, worker->daemon->rand)) {
+               free(fr);
+               worker->daemon->fast_reload_thread = NULL;
+               return 0;
+       }
+       fr->worker = worker;
+       fr->fr_output = (struct config_strlist_head*)calloc(1,
+               sizeof(*fr->fr_output));
+       if(!fr->fr_output) {
+               sock_close(fr->commpair[0]);
+               sock_close(fr->commpair[1]);
+               free(fr);
+               worker->daemon->fast_reload_thread = NULL;
+               return 0;
+       }
+       if(!create_socketpair(fr->commreload, worker->daemon->rand)) {
+               sock_close(fr->commpair[0]);
+               sock_close(fr->commpair[1]);
+               free(fr->fr_output);
+               free(fr);
+               worker->daemon->fast_reload_thread = NULL;
+               return 0;
+       }
+       lock_basic_init(&fr->fr_output_lock);
+       lock_protect(&fr->fr_output_lock, fr->fr_output,
+               sizeof(*fr->fr_output));
+       return 1;
+}
+
+/** fast reload, delete auth zone change list */
+static void
+fr_auth_change_list_delete(
+       struct fast_reload_auth_change* auth_zone_change_list)
+{
+       struct fast_reload_auth_change* item, *next;
+       item = auth_zone_change_list;
+       while(item) {
+               next = item->next;
+               free(item);
+               item = next;
+       }
+}
+
+/** fast reload thread. desetup and delete the thread info. */
+static void
+fast_reload_thread_desetup(struct fast_reload_thread* fast_reload_thread)
+{
+       if(!fast_reload_thread)
+               return;
+       if(fast_reload_thread->service_event &&
+               fast_reload_thread->service_event_is_added) {
+               ub_event_del(fast_reload_thread->service_event);
+               fast_reload_thread->service_event_is_added = 0;
+       }
+       if(fast_reload_thread->service_event)
+               ub_event_free(fast_reload_thread->service_event);
+       sock_close(fast_reload_thread->commpair[0]);
+       sock_close(fast_reload_thread->commpair[1]);
+       sock_close(fast_reload_thread->commreload[0]);
+       sock_close(fast_reload_thread->commreload[1]);
+       if(fast_reload_thread->printq) {
+               fr_main_perform_printout(fast_reload_thread);
+               /* If it is empty now, there is nothing to print on fd. */
+               if(fr_printq_empty(fast_reload_thread->printq)) {
+                       fr_printq_delete(fast_reload_thread->printq);
+               } else {
+                       /* Keep the printq around to printout the remaining
+                        * text to the remote client. Until it is done, it
+                        * sits on a list, that is in the daemon struct.
+                        * The event can then spool the remaining text to the
+                        * remote client and eventually delete itself from the
+                        * callback. */
+                       fr_printq_list_insert(fast_reload_thread->printq,
+                               fast_reload_thread->worker->daemon);
+                       fast_reload_thread->printq = NULL;
+               }
+       }
+       lock_basic_destroy(&fast_reload_thread->fr_output_lock);
+       if(fast_reload_thread->fr_output) {
+               config_delstrlist(fast_reload_thread->fr_output->first);
+               free(fast_reload_thread->fr_output);
+       }
+       fr_auth_change_list_delete(fast_reload_thread->auth_zone_change_list);
+
+       free(fast_reload_thread);
+}
+
+/**
+ * Fast reload thread, send a command to the thread. Blocking on timeout.
+ * It handles received input from the thread, if any is received.
+ */
+static void
+fr_send_cmd_to(struct fast_reload_thread* fr,
+       enum fast_reload_notification status, int check_cmds, int blocking)
+{
+       int outevent, loopexit = 0, bcount = 0;
+       uint32_t cmd;
+       ssize_t ret;
+       verbose(VERB_ALGO, "send notification to fast reload thread: %s",
+               fr_notification_to_string(status));
+       cmd = status;
+       while(1) {
+               if(++loopexit > IPC_LOOP_MAX) {
+                       log_err("send notification to fast reload: could not send notification: loop");
+                       return;
+               }
+               if(check_cmds)
+                       fr_check_cmd_from_thread(fr);
+               /* wait for socket to become writable */
+               if(!sock_poll_timeout(fr->commpair[0],
+                       (blocking?-1:IPC_NOTIFICATION_WAIT),
+                       0, 1, &outevent)) {
+                       log_err("send notification to fast reload: poll failed");
+                       return;
+               }
+               if(!outevent)
+                       continue;
+               /* keep static analyzer happy; send(-1,..) */
+               log_assert(fr->commpair[0] >= 0);
+               ret = send(fr->commpair[0], ((char*)&cmd)+bcount,
+                       sizeof(cmd)-bcount, 0);
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again. */
+                       log_err("send notification to fast reload: send: %s",
+                               sock_strerror(errno));
+                       return;
+               } else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+                       bcount += ret;
+                       if((size_t)bcount < sizeof(cmd))
+                               continue;
+               }
+               break;
+       }
+}
+
+/** Fast reload, the main thread handles that the fast reload thread has
+ * exited. */
+static void
+fr_main_perform_done(struct fast_reload_thread* fr)
+{
+       struct worker* worker = fr->worker;
+       verbose(VERB_ALGO, "join with fastreload thread");
+       ub_thread_join(fr->tid);
+       verbose(VERB_ALGO, "joined with fastreload thread");
+       fast_reload_thread_desetup(fr);
+       worker->daemon->fast_reload_thread = NULL;
+}
+
+/** Append strlist after strlist */
+static void
+cfg_strlist_append_listhead(struct config_strlist_head* list,
+       struct config_strlist_head* more)
+{
+       if(!more->first)
+               return;
+       if(list->last)
+               list->last->next = more->first;
+       else
+               list->first = more->first;
+       list->last = more->last;
+}
+
+/** Fast reload, the remote control thread handles that the fast reload thread
+ * has output to be printed, on the linked list that is locked. */
+static void
+fr_main_perform_printout(struct fast_reload_thread* fr)
+{
+       struct config_strlist_head out;
+
+       /* Fetch the list of items to be printed */
+       lock_basic_lock(&fr->fr_output_lock);
+       out.first = fr->fr_output->first;
+       out.last = fr->fr_output->last;
+       fr->fr_output->first = NULL;
+       fr->fr_output->last = NULL;
+       lock_basic_unlock(&fr->fr_output_lock);
+
+       if(!fr->printq || !fr->printq->client_cp) {
+               /* There is no output socket, delete it. */
+               config_delstrlist(out.first);
+               return;
+       }
+
+       /* Put them on the output list, not locked because the list
+        * producer and consumer are both owned by the remote control thread,
+        * it moves the items to the list for printing in the event callback
+        * for the client_cp. */
+       cfg_strlist_append_listhead(fr->printq->to_print, &out);
+
+       /* Set the client_cp to output if not already */
+       if(!fr->printq->client_cp->event_added)
+               comm_point_listen_for_rw(fr->printq->client_cp, 0, 1);
+}
+
+/** fast reload, receive ack from workers that they are waiting, run
+ * by the mainthr after sending them reload_stop. */
+static void
+fr_read_ack_from_workers(struct fast_reload_thread* fr)
+{
+       struct daemon* daemon = fr->worker->daemon;
+       /* Every worker sends one byte, wait for num-1 bytes. */
+       int count=0, total=daemon->num-1;
+       while(count < total) {
+               uint8_t r;
+               ssize_t ret;
+               ret = recv(fr->commreload[0], (void*)&r, 1, 0);
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again */
+                       log_err("worker reload ack: recv failed: %s",
+                               sock_strerror(errno));
+                       return;
+               }
+               count++;
+               verbose(VERB_ALGO, "worker reload ack from (uint8_t)%d",
+                       (int)r);
+       }
+}
+
+/** fast reload, poll for reload_start in mainthr waiting on a notification
+ * from the fast reload thread. */
+static void
+fr_poll_for_reload_start(struct fast_reload_thread* fr)
+{
+       int loopexit = 0, bcount = 0;
+       uint32_t cmd;
+       ssize_t ret;
+
+       /* Is there data? */
+       if(!sock_poll_timeout(fr->commpair[0], -1, 1, 0, NULL)) {
+               log_err("fr_poll_for_reload_start: poll failed");
+               return;
+       }
+
+       /* Read the data */
+       while(1) {
+               if(++loopexit > IPC_LOOP_MAX) {
+                       log_err("fr_poll_for_reload_start: recv loops %s",
+                               sock_strerror(errno));
+                       return;
+               }
+               ret = recv(fr->commpair[0], ((char*)&cmd)+bcount,
+                       sizeof(cmd)-bcount, 0);
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again. */
+                       log_err("fr_poll_for_reload_start: recv: %s",
+                               sock_strerror(errno));
+                       return;
+               } else if(ret+(ssize_t)bcount != sizeof(cmd)) {
+                       bcount += ret;
+                       if((size_t)bcount < sizeof(cmd))
+                               continue;
+               }
+               break;
+       }
+       if(cmd != fast_reload_notification_reload_start) {
+               verbose(VERB_ALGO, "fast reload wait for ack: "
+                       "wrong notification %d", (int)cmd);
+       }
+}
+
+/** Pick up the worker mesh changes, after fast reload. */
+static void
+fr_worker_pickup_mesh(struct worker* worker)
+{
+       struct mesh_area* mesh = worker->env.mesh;
+       struct config_file* cfg = worker->env.cfg;
+       mesh->use_response_ip = worker->daemon->use_response_ip;
+       mesh->use_rpz = worker->daemon->use_rpz;
+       mesh->max_reply_states = cfg->num_queries_per_thread;
+       mesh->max_forever_states = (mesh->max_reply_states+1)/2;
+#ifndef S_SPLINT_S
+       mesh->jostle_max.tv_sec = (time_t)(cfg->jostle_time / 1000);
+       mesh->jostle_max.tv_usec = (time_t)((cfg->jostle_time % 1000)*1000);
+#endif
+}
+
+/**
+ * Remove the old tcl_addr entries from the open connections.
+ * They are only incremented when an accept is performed on a tcp comm point.
+ * @param front: listening comm ports of the worker.
+ */
+static void
+tcl_remove_old(struct listen_dnsport* front)
+{
+       struct listen_list* l;
+       l = front->cps;
+       while(l) {
+               if(l->com->type == comm_tcp_accept) {
+                       int i;
+                       for(i=0; i<l->com->max_tcp_count; i++) {
+                               if(l->com->tcp_handlers[i]->tcl_addr) {
+                                       /* Because the increment of the
+                                        * connection limit was in the old
+                                        * tcl list, the new list does not
+                                        * need a decrement. With NULL it is
+                                        * not decremented when the connection
+                                        * is done, and also there is no
+                                        * reference to the old connection
+                                        * limit structure. */
+                                       l->com->tcp_handlers[i]->tcl_addr =
+                                               NULL;
+                               }
+                       }
+               }
+               l = l->next;
+       }
+}
+
+/** Stop zonemd lookup */
+static void
+auth_zone_zonemd_stop_lookup(struct auth_zone* z, struct mesh_area* mesh)
+{
+       struct query_info qinfo;
+       uint16_t qflags = BIT_RD;
+       qinfo.qname_len = z->namelen;
+       qinfo.qname = z->name;
+       qinfo.qclass = z->dclass;
+       qinfo.qtype = z->zonemd_callback_qtype;
+       qinfo.local_alias = NULL;
+
+       mesh_remove_callback(mesh, &qinfo, qflags,
+               &auth_zonemd_dnskey_lookup_callback, z);
+}
+
+/** Pick up the auth zone locks. */
+static void
+fr_pickup_auth_locks(struct worker* worker, struct auth_zone* namez,
+       struct auth_zone* old_z, struct auth_zone* new_z,
+       struct auth_xfer** xfr, struct auth_xfer** loadxfr)
+{
+       uint8_t nm[LDNS_MAX_DOMAINLEN+1];
+       size_t nmlen;
+       uint16_t dclass;
+
+       log_assert(namez->namelen <= sizeof(nm));
+       lock_rw_rdlock(&namez->lock);
+       nmlen = namez->namelen;
+       dclass = namez->dclass;
+       memmove(nm, namez->name, nmlen);
+       lock_rw_unlock(&namez->lock);
+
+       lock_rw_wrlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+       lock_rw_wrlock(&worker->env.auth_zones->lock);
+       if(new_z) {
+               lock_rw_wrlock(&new_z->lock);
+       }
+       if(old_z) {
+               lock_rw_wrlock(&old_z->lock);
+       }
+       if(loadxfr)
+               *loadxfr = auth_xfer_find(worker->daemon->fast_reload_thread->
+                       old_auth_zones, nm, nmlen, dclass);
+       if(xfr)
+               *xfr = auth_xfer_find(worker->env.auth_zones, nm, nmlen,
+                       dclass);
+       if(loadxfr && *loadxfr) {
+               lock_basic_lock(&(*loadxfr)->lock);
+       }
+       if(xfr && *xfr) {
+               lock_basic_lock(&(*xfr)->lock);
+       }
+}
+
+/** Fast reload, worker picks up deleted auth zone */
+static void
+fr_worker_auth_del(struct worker* worker, struct fast_reload_auth_change* item,
+       int for_change)
+{
+       int released = 0; /* Did this routine release callbacks. */
+       struct auth_xfer* xfr = NULL;
+
+       lock_rw_wrlock(&item->old_z->lock);
+       if(item->old_z->zonemd_callback_env &&
+          item->old_z->zonemd_callback_env->worker == worker){
+               /* This worker was performing a zonemd lookup,
+                * stop the lookup and remove that entry. */
+               auth_zone_zonemd_stop_lookup(item->old_z, worker->env.mesh);
+               item->old_z->zonemd_callback_env = NULL;
+       }
+       lock_rw_unlock(&item->old_z->lock);
+
+       fr_pickup_auth_locks(worker, item->old_z, item->old_z, NULL, &xfr,
+               NULL);
+       lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+       lock_rw_unlock(&worker->env.auth_zones->lock);
+       lock_rw_unlock(&item->old_z->lock);
+       if(xfr) {
+               /* Release callbacks on the xfr, if this worker holds them. */
+               if(xfr->task_nextprobe->worker == worker ||
+                       xfr->task_probe->worker == worker ||
+                       xfr->task_transfer->worker == worker) {
+                       released = 1;
+                       xfr_disown_tasks(xfr, worker);
+               }
+               lock_basic_unlock(&xfr->lock);
+       }
+
+       if(!for_change && (released || worker->thread_num == 0)) {
+               /* See if the xfr item can be deleted. */
+               xfr = NULL;
+               fr_pickup_auth_locks(worker, item->old_z, item->old_z, NULL,
+                       &xfr, NULL);
+               lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+               lock_rw_unlock(&item->old_z->lock);
+               if(xfr && xfr->task_nextprobe->worker == NULL &&
+                       xfr->task_probe->worker == NULL &&
+                       xfr->task_transfer->worker == NULL) {
+                       (void)rbtree_delete(&worker->env.auth_zones->xtree,
+                               &xfr->node);
+                       lock_rw_unlock(&worker->env.auth_zones->lock);
+                       lock_basic_unlock(&xfr->lock);
+                       auth_xfer_delete(xfr);
+               } else {
+                       lock_rw_unlock(&worker->env.auth_zones->lock);
+                       if(xfr) {
+                               lock_basic_unlock(&xfr->lock);
+                       }
+               }
+       }
+}
+
+/** Fast reload, auth xfer config is picked up */
+static void
+auth_xfr_pickup_config(struct auth_xfer* loadxfr, struct auth_xfer* xfr)
+{
+       struct auth_master *probe_masters, *transfer_masters;
+       log_assert(loadxfr->namelen == xfr->namelen);
+       log_assert(loadxfr->namelabs == xfr->namelabs);
+       log_assert(loadxfr->dclass == xfr->dclass);
+
+       /* The lists can be swapped in, the other xfr struct will be deleted
+        * afterwards. */
+       probe_masters = xfr->task_probe->masters;
+       transfer_masters = xfr->task_transfer->masters;
+       xfr->task_probe->masters = loadxfr->task_probe->masters;
+       xfr->task_transfer->masters = loadxfr->task_transfer->masters;
+       loadxfr->task_probe->masters = probe_masters;
+       loadxfr->task_transfer->masters = transfer_masters;
+}
+
+/** Fast reload, worker picks up added auth zone */
+static void
+fr_worker_auth_add(struct worker* worker, struct fast_reload_auth_change* item,
+       int for_change)
+{
+       struct auth_xfer* xfr = NULL, *loadxfr = NULL;
+
+       /* Start zone transfers and lookups. */
+       fr_pickup_auth_locks(worker, item->new_z, NULL, item->new_z, &xfr,
+               &loadxfr);
+       if(xfr == NULL && item->new_z->zone_is_slave) {
+               /* The xfr item needs to be created. The auth zones lock
+                * is held to make this possible. */
+               xfr = auth_xfer_create(worker->env.auth_zones, item->new_z);
+               auth_xfr_pickup_config(loadxfr, xfr);
+               /* Serial information is copied into the xfr struct. */
+               if(!xfr_find_soa(item->new_z, xfr)) {
+                       xfr->serial = 0;
+               }
+       } else if(for_change && xfr) {
+               if(!xfr_find_soa(item->new_z, xfr)) {
+                       xfr->serial = 0;
+               }
+       }
+       lock_rw_unlock(&item->new_z->lock);
+       lock_rw_unlock(&worker->env.auth_zones->lock);
+       lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+       if(loadxfr) {
+               lock_basic_unlock(&loadxfr->lock);
+       }
+       if(xfr) {
+               auth_xfer_pickup_initial_zone(xfr, &worker->env);
+               if(for_change) {
+                       xfr->task_probe->only_lookup = 0;
+               }
+               lock_basic_unlock(&xfr->lock);
+       }
+
+       /* Perform ZONEMD verification lookups. */
+       lock_rw_wrlock(&item->new_z->lock);
+       /* holding only the new_z lock */
+       auth_zone_verify_zonemd(item->new_z, &worker->env,
+               &worker->env.mesh->mods, NULL, 0, 1);
+       lock_rw_unlock(&item->new_z->lock);
+}
+
+/** Fast reload, worker picks up changed auth zone */
+static void
+fr_worker_auth_cha(struct worker* worker, struct fast_reload_auth_change* item)
+{
+       int todelete = 0;
+       struct auth_xfer* loadxfr = NULL, *xfr = NULL;
+       /* Since the zone has been changed, by rereading it from zone file,
+        * existing transfers and probes are likely for the old version.
+        * Stop them, and start new ones if needed. */
+       fr_worker_auth_del(worker, item, 1);
+
+       if(worker->thread_num != 0)
+               return;
+
+       /* The old callbacks are stopped, tasks have been disowned. The
+        * new config contents can be picked up. SOA information is picked
+        * up in the auth_add routine, as it has the new_z ready. */
+
+       fr_pickup_auth_locks(worker, item->new_z, item->old_z, item->new_z,
+               &xfr, &loadxfr);
+
+       /* The xfr is not there any more if the zone is not set to have
+        * zone transfers. Or the xfr needs to be created if it is set to
+        * have zone transfers. */
+       if(loadxfr && xfr) {
+               /* Copy the config from loadxfr to the xfr in current use. */
+               auth_xfr_pickup_config(loadxfr, xfr);
+       } else if(!loadxfr && xfr) {
+               /* Delete the xfr. */
+               (void)rbtree_delete(&worker->env.auth_zones->xtree,
+                       &xfr->node);
+               todelete = 1;
+               item->new_z->zone_is_slave = 0;
+       } else if(loadxfr && !xfr) {
+               /* Create the xfr. */
+               xfr = auth_xfer_create(worker->env.auth_zones, item->new_z);
+               auth_xfr_pickup_config(loadxfr, xfr);
+               item->new_z->zone_is_slave = 1;
+       }
+       lock_rw_unlock(&item->new_z->lock);
+       lock_rw_unlock(&item->old_z->lock);
+       lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock);
+       lock_rw_unlock(&worker->env.auth_zones->lock);
+       if(loadxfr) {
+               lock_basic_unlock(&loadxfr->lock);
+       }
+       if(xfr) {
+               lock_basic_unlock(&xfr->lock);
+       }
+       if(todelete) {
+               auth_xfer_delete(xfr);
+       }
+
+       fr_worker_auth_add(worker, item, 1);
+}
+
+/** Fast reload, the worker picks up changes in auth zones. */
+static void
+fr_worker_pickup_auth_changes(struct worker* worker,
+       struct fast_reload_auth_change* auth_zone_change_list)
+{
+       struct fast_reload_auth_change* item;
+       for(item = auth_zone_change_list; item; item = item->next) {
+               if(item->is_deleted) {
+                       fr_worker_auth_del(worker, item, 0);
+               }
+               if(item->is_added) {
+                       if(worker->thread_num == 0) {
+                               fr_worker_auth_add(worker, item, 0);
+                       }
+               }
+               if(item->is_changed) {
+                       fr_worker_auth_cha(worker, item);
+               }
+       }
+}
+
+/** Fast reload, the worker picks up changes in outside_network. */
+static void
+fr_worker_pickup_outside_network(struct worker* worker)
+{
+       struct outside_network* outnet = worker->back;
+       struct config_file* cfg = worker->env.cfg;
+       outnet->use_caps_for_id = cfg->use_caps_bits_for_id;
+       outnet->unwanted_threshold = cfg->unwanted_threshold;
+       outnet->tls_use_sni = cfg->tls_use_sni;
+       outnet->tcp_mss = cfg->outgoing_tcp_mss;
+       outnet->ip_dscp = cfg->ip_dscp;
+       outnet->max_reuse_tcp_queries = cfg->max_reuse_tcp_queries;
+       outnet->tcp_reuse_timeout = cfg->tcp_reuse_timeout;
+       outnet->tcp_auth_query_timeout = cfg->tcp_auth_query_timeout;
+       outnet->delayclose = cfg->delay_close;
+       if(outnet->delayclose) {
+#ifndef S_SPLINT_S
+               outnet->delay_tv.tv_sec = cfg->delay_close/1000;
+               outnet->delay_tv.tv_usec = (cfg->delay_close%1000)*1000;
+#endif
+       }
+}
+
+void
+fast_reload_worker_pickup_changes(struct worker* worker)
+{
+       /* The pickup of changes is called when the fast reload has
+        * a syncronized moment, and all the threads are paused and the
+        * reload has been applied. Then the worker can pick up the new
+        * changes and store them in worker-specific structs.
+        * The pickup is also called when there is no pause, and then
+        * it is called after the reload has completed, and the worker
+        * get a signal to release old information, it can then pick
+        * up the new information. But in the mean time, the reload has
+        * swapped in trees, and the worker has been running with the
+        * older information for some time. */
+       fr_worker_pickup_mesh(worker);
+
+       /* If the tcp connection limit has changed, the open connections
+        * need to remove their reference for the old tcp limits counters. */
+       if(worker->daemon->fast_reload_tcl_has_changes)
+               tcl_remove_old(worker->front);
+
+       /* If there are zonemd lookups, but the zone was deleted, the
+        * lookups should be cancelled. */
+       fr_worker_pickup_auth_changes(worker,
+               worker->daemon->fast_reload_thread->auth_zone_change_list);
+#ifdef USE_CACHEDB
+       worker->env.cachedb_enabled = worker->daemon->env->cachedb_enabled;
+#endif
+       fr_worker_pickup_outside_network(worker);
+}
+
+/** fast reload thread, handle reload_stop notification, send reload stop
+ * to other threads over IPC and collect their ack. When that is done,
+ * ack to the caller, the fast reload thread, and wait for it to send start. */
+static void
+fr_main_perform_reload_stop(struct fast_reload_thread* fr)
+{
+       struct daemon* daemon = fr->worker->daemon;
+       int i;
+
+       /* Send reload_stop to other threads. */
+       for(i=0; i<daemon->num; i++) {
+               if(i == fr->worker->thread_num)
+                       continue; /* Do not send to ourselves. */
+               worker_send_cmd(daemon->workers[i], worker_cmd_reload_stop);
+       }
+
+       /* Wait for the other threads to ack. */
+       fr_read_ack_from_workers(fr);
+
+       /* Send ack to fast reload thread. */
+       fr_send_cmd_to(fr, fast_reload_notification_reload_ack, 0, 1);
+
+       /* Wait for reload_start from fast reload thread to resume. */
+       fr_poll_for_reload_start(fr);
+
+       /* Send reload_start to other threads */
+       for(i=0; i<daemon->num; i++) {
+               if(i == fr->worker->thread_num)
+                       continue; /* Do not send to ourselves. */
+               worker_send_cmd(daemon->workers[i], worker_cmd_reload_start);
+       }
+
+       /* Pick up changes for this worker. */
+       if(fr->worker->daemon->fast_reload_drop_mesh) {
+               verbose(VERB_ALGO, "worker: drop mesh queries after reload");
+               mesh_delete_all(fr->worker->env.mesh);
+       }
+       fast_reload_worker_pickup_changes(fr->worker);
+
+       /* Wait for the other threads to ack. */
+       fr_read_ack_from_workers(fr);
+
+       /* Send ack to fast reload thread. */
+       fr_send_cmd_to(fr, fast_reload_notification_reload_ack, 0, 1);
+
+       verbose(VERB_ALGO, "worker resume after reload");
+}
+
+/** Fast reload, the main thread performs the nopause poll. It polls every
+ * other worker thread briefly over the command pipe ipc. The command takes
+ * no time for the worker, it can return immediately. After that it sends
+ * an acknowledgement to the fastreload thread. */
+static void
+fr_main_perform_reload_nopause_poll(struct fast_reload_thread* fr)
+{
+       struct daemon* daemon = fr->worker->daemon;
+       int i;
+
+       /* Send the reload_poll to other threads. They can respond
+        * one at a time. */
+       for(i=0; i<daemon->num; i++) {
+               if(i == fr->worker->thread_num)
+                       continue; /* Do not send to ourselves. */
+               worker_send_cmd(daemon->workers[i], worker_cmd_reload_poll);
+       }
+
+       /* Wait for the other threads to ack. */
+       fr_read_ack_from_workers(fr);
+       fast_reload_worker_pickup_changes(fr->worker);
+
+       /* Send ack to fast reload thread. */
+       fr_send_cmd_to(fr, fast_reload_notification_reload_ack, 0, 1);
+}
+
+/** Fast reload, perform the command received from the fast reload thread */
+static void
+fr_main_perform_cmd(struct fast_reload_thread* fr,
+       enum fast_reload_notification status)
+{
+       verbose(VERB_ALGO, "main perform fast reload status: %s",
+               fr_notification_to_string(status));
+       if(status == fast_reload_notification_printout) {
+               fr_main_perform_printout(fr);
+       } else if(status == fast_reload_notification_done ||
+               status == fast_reload_notification_done_error ||
+               status == fast_reload_notification_exited) {
+               fr_main_perform_done(fr);
+       } else if(status == fast_reload_notification_reload_stop) {
+               fr_main_perform_reload_stop(fr);
+       } else if(status == fast_reload_notification_reload_nopause_poll) {
+               fr_main_perform_reload_nopause_poll(fr);
+       } else {
+               log_err("main received unknown status from fast reload: %d %s",
+                       (int)status, fr_notification_to_string(status));
+       }
+}
+
+/** Fast reload, handle command from fast reload to the main thread. */
+static void
+fr_main_handle_cmd(struct fast_reload_thread* fr)
+{
+       enum fast_reload_notification status;
+       ssize_t ret;
+       /* keep static analyzer happy; recv(-1,..) */
+       log_assert(fr->commpair[0] >= 0);
+       ret = recv(fr->commpair[0],
+               ((char*)&fr->service_read_cmd)+fr->service_read_cmd_count,
+               sizeof(fr->service_read_cmd)-fr->service_read_cmd_count, 0);
+       if(ret == -1) {
+               if(
+#ifndef USE_WINSOCK
+                       errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                       || errno == EWOULDBLOCK
+#  endif
+#else
+                       WSAGetLastError() == WSAEINTR ||
+                       WSAGetLastError() == WSAEINPROGRESS ||
+                       WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                       )
+                       return; /* Continue later. */
+               log_err("read cmd from fast reload thread, recv: %s",
+                       sock_strerror(errno));
+               return;
+       } else if(ret == 0) {
+               verbose(VERB_ALGO, "closed connection from fast reload thread");
+               fr->service_read_cmd_count = 0;
+               /* handle this like an error */
+               fr->service_read_cmd = fast_reload_notification_done_error;
+       } else if(ret + (ssize_t)fr->service_read_cmd_count <
+               (ssize_t)sizeof(fr->service_read_cmd)) {
+               fr->service_read_cmd_count += ret;
+               /* Continue later. */
+               return;
+       }
+       status = fr->service_read_cmd;
+       fr->service_read_cmd = 0;
+       fr->service_read_cmd_count = 0;
+       fr_main_perform_cmd(fr, status);
+}
+
+/** Fast reload, poll for and handle cmd from fast reload thread. */
+static void
+fr_check_cmd_from_thread(struct fast_reload_thread* fr)
+{
+       int inevent = 0;
+       struct worker* worker = fr->worker;
+       /* Stop in case the thread has exited, or there is no read event. */
+       while(worker->daemon->fast_reload_thread) {
+               if(!sock_poll_timeout(fr->commpair[0], 0, 1, 0, &inevent)) {
+                       log_err("check for cmd from fast reload thread: "
+                               "poll failed");
+                       return;
+               }
+               if(!inevent)
+                       return;
+               fr_main_handle_cmd(fr);
+       }
+}
+
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits),
+       void* arg)
+{
+       struct fast_reload_thread* fast_reload_thread =
+               (struct fast_reload_thread*)arg;
+       struct worker* worker = fast_reload_thread->worker;
+
+       /* Read and handle the command */
+       fr_main_handle_cmd(fast_reload_thread);
+       if(worker->daemon->fast_reload_thread != NULL) {
+               /* If not exited, see if there are more pending statuses
+                * from the fast reload thread. */
+               fr_check_cmd_from_thread(fast_reload_thread);
+       }
+}
+
+#ifdef HAVE_SSL
+/** fast reload, send client item over SSL. Returns number of bytes
+ * printed, 0 on wait later, or -1 on failure. */
+static int
+fr_client_send_item_ssl(struct fast_reload_printq* printq)
+{
+       int r;
+       ERR_clear_error();
+       r = SSL_write(printq->remote.ssl,
+               printq->client_item+printq->client_byte_count,
+               printq->client_len - printq->client_byte_count);
+       if(r <= 0) {
+               int want = SSL_get_error(printq->remote.ssl, r);
+               if(want == SSL_ERROR_ZERO_RETURN) {
+                       log_err("fast_reload print to remote client: "
+                               "SSL_write says connection closed.");
+                       return -1;
+               } else if(want == SSL_ERROR_WANT_READ) {
+                       /* wait for read condition */
+                       printq->client_cp->ssl_shake_state = comm_ssl_shake_hs_read;
+                       comm_point_listen_for_rw(printq->client_cp, 1, 0);
+                       return 0;
+               } else if(want == SSL_ERROR_WANT_WRITE) {
+#ifdef USE_WINSOCK
+                       ub_winsock_tcp_wouldblock(comm_point_internal(printq->client_cp), UB_EV_WRITE);
+#endif
+                       return 0; /* write more later */
+               } else if(want == SSL_ERROR_SYSCALL) {
+#ifdef EPIPE
+                       if(errno == EPIPE && verbosity < 2) {
+                               /* silence 'broken pipe' */
+                               return -1;
+                       }
+#endif
+                       if(errno != 0)
+                               log_err("fast_reload print to remote client: "
+                                       "SSL_write syscall: %s",
+                                       sock_strerror(errno));
+                       return -1;
+               }
+               log_crypto_err_io("fast_reload print to remote client: "
+                       "could not SSL_write", want);
+               return -1;
+       }
+       return r;
+}
+#endif /* HAVE_SSL */
+
+/** fast reload, send client item for fd, returns bytes sent, or 0 for wait
+ * later, or -1 on failure. */
+static int
+fr_client_send_item_fd(struct fast_reload_printq* printq)
+{
+       int r;
+       r = (int)send(printq->remote.fd,
+               printq->client_item+printq->client_byte_count,
+               printq->client_len - printq->client_byte_count, 0);
+       if(r == -1) {
+               if(
+#ifndef USE_WINSOCK
+                       errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                       || errno == EWOULDBLOCK
+#  endif
+#else
+                       WSAGetLastError() == WSAEINTR ||
+                       WSAGetLastError() == WSAEINPROGRESS ||
+                       WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                       ) {
+#ifdef USE_WINSOCK
+                       ub_winsock_tcp_wouldblock(comm_point_internal(printq->client_cp), UB_EV_WRITE);
+#endif
+                       return 0; /* Try again. */
+               }
+               log_err("fast_reload print to remote client: send failed: %s",
+                       sock_strerror(errno));
+               return -1;
+       }
+       return r;
+}
+
+/** fast reload, send current client item. false on failure or wait later. */
+static int
+fr_client_send_item(struct fast_reload_printq* printq)
+{
+       int r;
+#ifdef HAVE_SSL
+       if(printq->remote.ssl) {
+               r = fr_client_send_item_ssl(printq);
+       } else {
+#endif
+               r = fr_client_send_item_fd(printq);
+#ifdef HAVE_SSL
+       }
+#endif
+       if(r == 0) {
+               /* Wait for later. */
+               return 0;
+       } else if(r == -1) {
+               /* It failed, close comm point and stop sending. */
+               fr_printq_remove(printq);
+               return 0;
+       }
+       printq->client_byte_count += r;
+       if(printq->client_byte_count < printq->client_len)
+               return 0; /* Print more later. */
+       return 1;
+}
+
+/** fast reload, pick up the next item to print */
+static void
+fr_client_pickup_next_item(struct fast_reload_printq* printq)
+{
+       struct config_strlist* item;
+       /* Pop first off the list. */
+       if(!printq->to_print->first) {
+               printq->client_item = NULL;
+               printq->client_len = 0;
+               printq->client_byte_count = 0;
+               return;
+       }
+       item = printq->to_print->first;
+       if(item->next) {
+               printq->to_print->first = item->next;
+       } else {
+               printq->to_print->first = NULL;
+               printq->to_print->last = NULL;
+       }
+       item->next = NULL;
+       printq->client_len = 0;
+       printq->client_byte_count = 0;
+       printq->client_item = item->str;
+       item->str = NULL;
+       free(item);
+       /* The len is the number of bytes to print out, and thus excludes
+        * the terminator zero. */
+       if(printq->client_item)
+               printq->client_len = (int)strlen(printq->client_item);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c), void* arg,
+       int err, struct comm_reply* ATTR_UNUSED(rep))
+{
+       struct fast_reload_printq* printq = (struct fast_reload_printq*)arg;
+       if(!printq->client_cp) {
+               fr_printq_remove(printq);
+               return 0; /* the output is closed and deleted */
+       }
+       if(err != NETEVENT_NOERROR) {
+               verbose(VERB_ALGO, "fast reload client: error, close it");
+               fr_printq_remove(printq);
+               return 0;
+       }
+#ifdef HAVE_SSL
+       if(printq->client_cp->ssl_shake_state == comm_ssl_shake_hs_read) {
+               /* read condition satisfied back to writing */
+               comm_point_listen_for_rw(printq->client_cp, 0, 1);
+               printq->client_cp->ssl_shake_state = comm_ssl_shake_none;
+       }
+#endif /* HAVE_SSL */
+
+       /* Pickup an item if there are none */
+       if(!printq->client_item) {
+               fr_client_pickup_next_item(printq);
+       }
+       if(!printq->client_item) {
+               if(printq->in_list) {
+                       /* Nothing more to print, it can be removed. */
+                       fr_printq_remove(printq);
+                       return 0;
+               }
+               /* Done with printing for now. */
+               comm_point_stop_listening(printq->client_cp);
+               return 0;
+       }
+
+       /* Try to print out a number of items, if they can print in full. */
+       while(printq->client_item) {
+               /* Send current item, if any. */
+               if(printq->client_item && printq->client_len != 0 &&
+                       printq->client_byte_count < printq->client_len) {
+                       if(!fr_client_send_item(printq))
+                               return 0;
+               }
+
+               /* The current item is done. */
+               if(printq->client_item) {
+                       free(printq->client_item);
+                       printq->client_item = NULL;
+                       printq->client_len = 0;
+                       printq->client_byte_count = 0;
+               }
+               if(!printq->to_print->first) {
+                       if(printq->in_list) {
+                               /* Nothing more to print, it can be removed. */
+                               fr_printq_remove(printq);
+                               return 0;
+                       }
+                       /* Done with printing for now. */
+                       comm_point_stop_listening(printq->client_cp);
+                       return 0;
+               }
+               fr_client_pickup_next_item(printq);
+       }
+
+       return 0;
+}
+
+#ifndef THREADS_DISABLED
+/** fast reload printq create */
+static struct fast_reload_printq*
+fr_printq_create(struct comm_point* c, struct worker* worker)
+{
+       struct fast_reload_printq* printq = calloc(1, sizeof(*printq));
+       if(!printq)
+               return NULL;
+       printq->to_print = calloc(1, sizeof(*printq->to_print));
+       if(!printq->to_print) {
+               free(printq);
+               return NULL;
+       }
+       printq->worker = worker;
+       printq->client_cp = c;
+       printq->client_cp->callback = fast_reload_client_callback;
+       printq->client_cp->cb_arg = printq;
+       return printq;
+}
+#endif /* !THREADS_DISABLED */
+
+/** fast reload printq delete */
+static void
+fr_printq_delete(struct fast_reload_printq* printq)
+{
+       if(!printq)
+               return;
+#ifdef HAVE_SSL
+       if(printq->remote.ssl) {
+               SSL_shutdown(printq->remote.ssl);
+               SSL_free(printq->remote.ssl);
+       }
+#endif
+       comm_point_delete(printq->client_cp);
+       if(printq->to_print) {
+               config_delstrlist(printq->to_print->first);
+               free(printq->to_print);
+       }
+       free(printq);
+}
+
+/** fast reload printq, returns true if the list is empty and no item */
+static int
+fr_printq_empty(struct fast_reload_printq* printq)
+{
+       if(printq->to_print->first == NULL && printq->client_item == NULL)
+               return 1;
+       return 0;
+}
+
+/** fast reload printq, insert onto list */
+static void
+fr_printq_list_insert(struct fast_reload_printq* printq, struct daemon* daemon)
+{
+       if(printq->in_list)
+               return;
+       printq->next = daemon->fast_reload_printq_list;
+       if(printq->next)
+               printq->next->prev = printq;
+       printq->prev = NULL;
+       printq->in_list = 1;
+       daemon->fast_reload_printq_list = printq;
+}
+
+/** fast reload printq delete list */
+void
+fast_reload_printq_list_delete(struct fast_reload_printq* list)
+{
+       struct fast_reload_printq* printq = list, *next;
+       while(printq) {
+               next = printq->next;
+               fr_printq_delete(printq);
+               printq = next;
+       }
+}
+
+/** fast reload printq remove the item from the printq list */
+static void
+fr_printq_list_remove(struct fast_reload_printq* printq)
+{
+       struct daemon* daemon = printq->worker->daemon;
+       if(printq->prev == NULL)
+               daemon->fast_reload_printq_list = printq->next;
+       else    printq->prev->next = printq->next;
+       if(printq->next)
+               printq->next->prev = printq->prev;
+       printq->in_list = 0;
+}
+
+/** fast reload printq, remove the printq when no longer needed,
+ * like the stream is closed. */
+static void
+fr_printq_remove(struct fast_reload_printq* printq)
+{
+       if(!printq)
+               return;
+       if(printq->worker->daemon->fast_reload_thread &&
+               printq->worker->daemon->fast_reload_thread->printq == printq)
+               printq->worker->daemon->fast_reload_thread->printq = NULL;
+       if(printq->in_list)
+               fr_printq_list_remove(printq);
+       fr_printq_delete(printq);
+}
+
+/** fast reload thread, send stop command to the thread, from the main thread.
+ */
+static void
+fr_send_stop(struct fast_reload_thread* fr)
+{
+       fr_send_cmd_to(fr, fast_reload_notification_exit, 1, 0);
+}
+
+void
+fast_reload_thread_start(RES* ssl, struct worker* worker, struct rc_state* s,
+       int fr_verb, int fr_nopause, int fr_drop_mesh)
+{
+       if(worker->daemon->fast_reload_thread) {
+               log_err("fast reload thread already running");
+               return;
+       }
+       if(!fast_reload_thread_setup(worker, fr_verb, fr_nopause,
+               fr_drop_mesh)) {
+               if(!ssl_printf(ssl, "error could not setup thread\n"))
+                       return;
+               return;
+       }
+       worker->daemon->fast_reload_thread->started = 1;
+
+#ifndef THREADS_DISABLED
+       /* Setup command listener in remote servicing thread */
+       /* The listener has to be nonblocking, so the the remote servicing
+        * thread can continue to service DNS queries, the fast reload
+        * thread is going to read the config from disk and apply it. */
+       /* The commpair[1] element can stay blocking, it is used by the
+        * fast reload thread to communicate back. The thread needs to wait
+        * at these times, when it has to check briefly it can use poll. */
+       fd_set_nonblock(worker->daemon->fast_reload_thread->commpair[0]);
+       worker->daemon->fast_reload_thread->service_event = ub_event_new(
+               comm_base_internal(worker->base),
+               worker->daemon->fast_reload_thread->commpair[0],
+               UB_EV_READ | UB_EV_PERSIST, fast_reload_service_cb,
+               worker->daemon->fast_reload_thread);
+       if(!worker->daemon->fast_reload_thread->service_event) {
+               fast_reload_thread_desetup(worker->daemon->fast_reload_thread);
+               if(!ssl_printf(ssl, "error out of memory\n"))
+                       return;
+               return;
+       }
+       if(ub_event_add(worker->daemon->fast_reload_thread->service_event,
+               NULL) != 0) {
+               fast_reload_thread_desetup(worker->daemon->fast_reload_thread);
+               if(!ssl_printf(ssl, "error out of memory adding service event\n"))
+                       return;
+               return;
+       }
+       worker->daemon->fast_reload_thread->service_event_is_added = 1;
+
+       /* Setup the comm point to the remote control client as an event
+        * on the remote servicing thread, which it already is.
+        * It needs a new callback to service it. */
+       log_assert(s);
+       state_list_remove_elem(&s->rc->busy_list, s->c);
+       s->rc->active --;
+       /* Set the comm point file descriptor to nonblocking. So that
+        * printout to the remote control client does not block the
+        * server thread from servicing DNS queries. */
+       fd_set_nonblock(s->c->fd);
+       worker->daemon->fast_reload_thread->printq = fr_printq_create(s->c,
+               worker);
+       if(!worker->daemon->fast_reload_thread->printq) {
+               fast_reload_thread_desetup(worker->daemon->fast_reload_thread);
+               if(!ssl_printf(ssl, "error out of memory create printq\n"))
+                       return;
+               return;
+       }
+       worker->daemon->fast_reload_thread->printq->remote = *ssl;
+       s->rc = NULL; /* move away the rc state */
+       /* Nothing to print right now, so no need to have it active. */
+       comm_point_stop_listening(worker->daemon->fast_reload_thread->printq->client_cp);
+
+       /* Start fast reload thread */
+       ub_thread_create(&worker->daemon->fast_reload_thread->tid,
+               fast_reload_thread_main, worker->daemon->fast_reload_thread);
+#else
+       (void)s;
+#endif
+}
+
+void
+fast_reload_thread_stop(struct fast_reload_thread* fast_reload_thread)
+{
+       struct worker* worker = fast_reload_thread->worker;
+       if(!fast_reload_thread)
+               return;
+       fr_send_stop(fast_reload_thread);
+       if(worker->daemon->fast_reload_thread != NULL) {
+               /* If it did not exit yet, join with the thread now. It is
+                * going to exit because the exit command is sent to it. */
+               fr_main_perform_done(fast_reload_thread);
+       }
+}
index 4902803f5e42bef5a6a87dc43d86eaa13757adc2..064d7b7fccced0a75fe221de6fbae5f0b8e0e479 100644 (file)
@@ -48,6 +48,7 @@
 #ifdef HAVE_OPENSSL_SSL_H
 #include <openssl/ssl.h>
 #endif
+#include "util/locks.h"
 struct config_file;
 struct listen_list;
 struct listen_port;
@@ -55,6 +56,7 @@ struct worker;
 struct comm_reply;
 struct comm_point;
 struct daemon_remote;
+struct config_strlist_head;
 
 /** number of milliseconds timeout on incoming remote control handshake */
 #define REMOTE_CONTROL_TCP_TIMEOUT 120000
@@ -118,6 +120,137 @@ struct remote_stream {
 };
 typedef struct remote_stream RES;
 
+/**
+ * Notification status. This is exchanged between the fast reload thread
+ * and the server thread, over the commpair sockets.
+ */
+enum fast_reload_notification {
+       /** nothing, not used */
+       fast_reload_notification_none = 0,
+       /** the fast reload thread is done */
+       fast_reload_notification_done = 1,
+       /** the fast reload thread is done but with an error, it failed */
+       fast_reload_notification_done_error = 2,
+       /** the fast reload thread is told to exit by the server thread.
+        * Sent on server quit while the reload is running. */
+       fast_reload_notification_exit = 3,
+       /** the fast reload thread has exited, after being told to exit */
+       fast_reload_notification_exited = 4,
+       /** the fast reload thread has information to print out */
+       fast_reload_notification_printout = 5,
+       /** stop as part of the reload the thread and other threads */
+       fast_reload_notification_reload_stop = 6,
+       /** ack the stop as part of the reload, and also ack start */
+       fast_reload_notification_reload_ack = 7,
+       /** resume from stop as part of the reload */
+       fast_reload_notification_reload_start = 8,
+       /** the fast reload thread wants the mainthread to poll workers,
+        * after the reload, sent when nopause is used */
+       fast_reload_notification_reload_nopause_poll = 9
+};
+
+/**
+ * Fast reload printout queue. Contains a list of strings, that need to be
+ * printed over the file descriptor.
+ */
+struct fast_reload_printq {
+       /** if this item is in a list, the previous and next */
+       struct fast_reload_printq *prev, *next;
+       /** if this item is in a list, it is true. */
+       int in_list;
+       /** list of strings to printout */
+       struct config_strlist_head* to_print;
+       /** the current item to print. It is malloced. NULL if none. */
+       char* client_item;
+       /** The length, strlen, of the client_item, that has to be sent. */
+       int client_len;
+       /** The number of bytes sent of client_item. */
+       int client_byte_count;
+       /** the comm point for the client connection, the remote control
+        * client. */
+       struct comm_point* client_cp;
+       /** the remote control connection to print output to. */
+       struct remote_stream remote;
+       /** the worker that the event is added in */
+       struct worker* worker;
+};
+
+/**
+ * Fast reload auth zone change. Keeps track if an auth zone was removed,
+ * added or changed. This is needed because workers can have events for
+ * dealing with auth zones, like transfers, and those have to be removed
+ * too, not just the auth zone structure from the tree. */
+struct fast_reload_auth_change {
+       /** next in the list of auth zone changes. */
+       struct fast_reload_auth_change* next;
+       /** the zone in the old config */
+       struct auth_zone* old_z;
+       /** the zone in the new config */
+       struct auth_zone* new_z;
+       /** if the zone was deleted */
+       int is_deleted;
+       /** if the zone was added */
+       int is_added;
+       /** if the zone has been changed */
+       int is_changed;
+};
+
+/**
+ * Fast reload thread structure
+ */
+struct fast_reload_thread {
+       /** the thread number for the dtio thread,
+        * must be first to cast thread arg to int* in checklock code. */
+       int threadnum;
+       /** communication socket pair, that sends commands */
+       int commpair[2];
+       /** thread id, of the io thread */
+       ub_thread_type tid;
+       /** if the io processing has started */
+       int started;
+       /** if the thread has to quit */
+       int need_to_quit;
+       /** verbosity of the fast_reload command, the number of +v options */
+       int fr_verb;
+       /** option to not pause threads during reload */
+       int fr_nopause;
+       /** option to drop mesh queries */
+       int fr_drop_mesh;
+
+       /** the event that listens on the remote service worker to the
+        * commpair, it receives content from the fast reload thread. */
+       void* service_event;
+       /** if the event that listens on the remote service worker has
+        * been added to the comm base. */
+       int service_event_is_added;
+       /** the service event can read a cmd, nonblocking, so it can
+        * save the partial read cmd here */
+       uint32_t service_read_cmd;
+       /** the number of bytes in service_read_cmd */
+       int service_read_cmd_count;
+       /** the worker that the service_event is added in */
+       struct worker* worker;
+
+       /** the printout of output to the remote client. */
+       struct fast_reload_printq *printq;
+
+       /** lock on fr_output, to stop race when both remote control thread
+        * and fast reload thread use fr_output list. */
+       lock_basic_type fr_output_lock;
+       /** list of strings, that the fast reload thread produces that have
+        * to be printed. The remote control thread can pick them up with
+        * the lock. */
+       struct config_strlist_head* fr_output;
+
+       /** communication socket pair, to respond to the reload request */
+       int commreload[2];
+
+       /** the list of auth zone changes. */
+       struct fast_reload_auth_change* auth_zone_change_list;
+       /** the old tree of auth zones, to lookup. */
+       struct auth_zones* old_auth_zones;
+};
+
 /**
  * Create new remote control state for the daemon.
  * @param cfg: config file with key file settings.
@@ -203,4 +336,38 @@ int ssl_printf(RES* ssl, const char* format, ...)
 int ssl_read_line(RES* ssl, char* buf, size_t max);
 #endif /* HAVE_SSL */
 
+/**
+ * Start fast reload thread
+ * @param ssl: the RES connection to print to.
+ * @param worker: the remote servicing worker.
+ * @param s: the rc_state that is servicing the remote control connection to
+ *     the remote control client. It needs to be moved away to stay connected
+ *     while the fast reload is running.
+ * @param fr_verb: verbosity to print output at. 0 is nothing, 1 is some
+ *     and 2 is more detail.
+ * @param fr_nopause: option to not pause threads during reload.
+ * @param fr_drop_mesh: option to drop mesh queries.
+ */
+void fast_reload_thread_start(RES* ssl, struct worker* worker,
+       struct rc_state* s, int fr_verb, int fr_nopause, int fr_drop_mesh);
+
+/**
+ * Stop fast reload thread
+ * @param fast_reload_thread: the thread struct.
+ */
+void fast_reload_thread_stop(struct fast_reload_thread* fast_reload_thread);
+
+/** fast reload thread commands to remote service thread event callback */
+void fast_reload_service_cb(int fd, short bits, void* arg);
+
+/** fast reload callback for the remote control client connection */
+int fast_reload_client_callback(struct comm_point* c, void* arg, int err,
+       struct comm_reply* rep);
+
+/** fast reload printq delete list */
+void fast_reload_printq_list_delete(struct fast_reload_printq* list);
+
+/** Pick up per worker changes after a fast reload. */
+void fast_reload_worker_pickup_changes(struct worker* worker);
+
 #endif /* DAEMON_REMOTE_H */
index 8de7eb0a590f6dcc462059ffd772cb0f393415a5..8e59bb25a72531d28b5254d76e7bcf338243f8cb 100644 (file)
@@ -710,6 +710,9 @@ perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode,
         * it would succeed on SIGHUP as well */
        if(!cfg->use_syslog)
                log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
+       daemon->cfgfile = strdup(*cfgfile);
+       if(!daemon->cfgfile)
+               fatal_exit("out of memory in daemon cfgfile strdup");
 }
 
 /**
index ba5118a74fd692f6b1fa6982d40ed2bf0d7a7805..54ba2d76cd9369abaaa61dab12d7af8b53ef2b55 100644 (file)
@@ -371,6 +371,84 @@ worker_check_request(sldns_buffer* pkt, struct worker* worker,
        return;
 }
 
+/**
+ * Send fast-reload acknowledgement to the mainthread in one byte.
+ * This signals that this worker has received the previous command.
+ * The worker is waiting if that is after a reload_stop command.
+ * Or the worker has briefly processed the event itself, and in doing so
+ * released data pointers to old config, after a reload_poll command.
+ */
+static void
+worker_send_reload_ack(struct worker* worker)
+{
+       /* If this is clipped to 8 bits because thread_num>255, then that
+        * is not a problem, the receiver counts the number of bytes received.
+        * The number is informative only. */
+       uint8_t c = (uint8_t)worker->thread_num;
+       ssize_t ret;
+       while(1) {
+               ret = send(worker->daemon->fast_reload_thread->commreload[1],
+                       (void*)&c, 1, 0);
+               if(ret == -1) {
+                       if(
+#ifndef USE_WINSOCK
+                               errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                               || errno == EWOULDBLOCK
+#  endif
+#else
+                               WSAGetLastError() == WSAEINTR ||
+                               WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK
+#endif
+                               )
+                               continue; /* Try again. */
+                       log_err("worker reload ack reply: send failed: %s",
+                               sock_strerror(errno));
+                       break;
+               }
+               break;
+       }
+}
+
+/** stop and wait to resume the worker */
+static void
+worker_stop_and_wait(struct worker* worker)
+{
+       uint8_t* buf = NULL;
+       uint32_t len = 0, cmd;
+       worker_send_reload_ack(worker);
+       /* wait for reload */
+       if(!tube_read_msg(worker->cmd, &buf, &len, 0)) {
+               log_err("worker reload read reply failed");
+               return;
+       }
+       if(len != sizeof(uint32_t)) {
+               log_err("worker reload reply, bad control msg length %d",
+                       (int)len);
+               free(buf);
+               return;
+       }
+       cmd = sldns_read_uint32(buf);
+       free(buf);
+       if(cmd == worker_cmd_quit) {
+               /* quit anyway */
+               verbose(VERB_ALGO, "reload reply, control cmd quit");
+               comm_base_exit(worker->base);
+               return;
+       }
+       if(cmd != worker_cmd_reload_start) {
+               log_err("worker reload reply, wrong reply command");
+       }
+       if(worker->daemon->fast_reload_drop_mesh) {
+               verbose(VERB_ALGO, "worker: drop mesh queries after reload");
+               mesh_delete_all(worker->env.mesh);
+       }
+       fast_reload_worker_pickup_changes(worker);
+       worker_send_reload_ack(worker);
+       verbose(VERB_ALGO, "worker resume after reload");
+}
+
 void
 worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg,
        size_t len, int error, void* arg)
@@ -406,6 +484,15 @@ worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg,
                verbose(VERB_ALGO, "got control cmd remote");
                daemon_remote_exec(worker);
                break;
+       case worker_cmd_reload_stop:
+               verbose(VERB_ALGO, "got control cmd reload_stop");
+               worker_stop_and_wait(worker);
+               break;
+       case worker_cmd_reload_poll:
+               verbose(VERB_ALGO, "got control cmd reload_poll");
+               fast_reload_worker_pickup_changes(worker);
+               worker_send_reload_ack(worker);
+               break;
        default:
                log_err("bad command %d", (int)cmd);
                break;
@@ -600,7 +687,8 @@ apply_respip_action(struct worker* worker, const struct query_info* qinfo,
                return 1;
 
        if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, &actinfo,
-               alias_rrset, 0, worker->scratchpad, az, NULL))
+               alias_rrset, 0, worker->scratchpad, az, NULL,
+               worker->env.views, worker->env.respip_set))
                return 0;
 
        /* xxx_deny actions mean dropping the reply, unless the original reply
@@ -761,7 +849,8 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo,
        } else if(partial_rep &&
                !respip_merge_cname(partial_rep, qinfo, rep, cinfo,
                must_validate, &encode_rep, worker->scratchpad,
-               worker->env.auth_zones)) {
+               worker->env.auth_zones, worker->env.views,
+               worker->env.respip_set)) {
                goto bail_out;
        }
        if(encode_rep != rep) {
@@ -1813,7 +1902,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
                cinfo_tmp.tag_datas = acladdr->tag_datas;
                cinfo_tmp.tag_datas_size = acladdr->tag_datas_size;
                cinfo_tmp.view = acladdr->view;
-               cinfo_tmp.respip_set = worker->daemon->respip_set;
+               cinfo_tmp.view_name = NULL;
                cinfo = &cinfo_tmp;
        }
 
index ab2fc728d274b32d76f0cf67b3b58cc3b9330fc9..b7bb52fd715ba3c42910d7dcf7a6cec467545fcd 100644 (file)
@@ -72,7 +72,13 @@ enum worker_commands {
        /** obtain statistics without statsclear */
        worker_cmd_stats_noreset,
        /** execute remote control command */
-       worker_cmd_remote
+       worker_cmd_remote,
+       /** for fast-reload, perform stop */
+       worker_cmd_reload_stop,
+       /** for fast-reload, start again */
+       worker_cmd_reload_start,
+       /** for fast-reload, poll to make sure worker has released data */
+       worker_cmd_reload_poll
 };
 
 /**
index cff308f9316475ac142fabab3d3c5b2141dbbc89..071fd0895fe608ffe500da1a9ab1b9f67eb87c86 100644 (file)
@@ -192,8 +192,11 @@ static void
 dt_apply_identity(struct dt_env *env, struct config_file *cfg)
 {
        char buf[MAXHOSTNAMELEN+1];
-       if (!cfg->dnstap_send_identity)
+       if (!cfg->dnstap_send_identity) {
+               free(env->identity);
+               env->identity = NULL;
                return;
+       }
        free(env->identity);
        if (cfg->dnstap_identity == NULL || cfg->dnstap_identity[0] == 0) {
                if (gethostname(buf, MAXHOSTNAMELEN) == 0) {
@@ -215,8 +218,11 @@ dt_apply_identity(struct dt_env *env, struct config_file *cfg)
 static void
 dt_apply_version(struct dt_env *env, struct config_file *cfg)
 {
-       if (!cfg->dnstap_send_version)
+       if (!cfg->dnstap_send_version) {
+               free(env->version);
+               env->version = NULL;
                return;
+       }
        free(env->version);
        if (cfg->dnstap_version == NULL || cfg->dnstap_version[0] == 0)
                env->version = strdup(PACKAGE_STRING);
@@ -230,13 +236,8 @@ dt_apply_version(struct dt_env *env, struct config_file *cfg)
 }
 
 void
-dt_apply_cfg(struct dt_env *env, struct config_file *cfg)
+dt_apply_logcfg(struct dt_env *env, struct config_file *cfg)
 {
-       if (!cfg->dnstap)
-               return;
-
-       dt_apply_identity(env, cfg);
-       dt_apply_version(env, cfg);
        if ((env->log_resolver_query_messages = (unsigned int)
             cfg->dnstap_log_resolver_query_messages))
        {
@@ -275,6 +276,17 @@ dt_apply_cfg(struct dt_env *env, struct config_file *cfg)
        lock_basic_unlock(&env->sample_lock);
 }
 
+void
+dt_apply_cfg(struct dt_env *env, struct config_file *cfg)
+{
+       if (!cfg->dnstap)
+               return;
+
+       dt_apply_identity(env, cfg);
+       dt_apply_version(env, cfg);
+       dt_apply_logcfg(env, cfg);
+}
+
 int
 dt_init(struct dt_env *env, struct comm_base* base)
 {
index 21c033697da126f15a281c3c3338e63220490a0d..4390a9cf15910224dc0a730d231739e402dfc2b6 100644 (file)
@@ -106,6 +106,13 @@ dt_create(struct config_file* cfg);
 void
 dt_apply_cfg(struct dt_env *env, struct config_file *cfg);
 
+/**
+ * Apply config settings for log enable for message types.
+ * @param env: dnstap environment object.
+ * @param cfg: new config settings.
+ */
+void dt_apply_logcfg(struct dt_env *env, struct config_file *cfg);
+
 /**
  * Initialize per-worker state in dnstap environment object.
  * @param env: dnstap environment object to initialize, created with dt_create().
index cfa0c8f95bcbc8adbdae7a644608a506adad3d23..a01627de965021d5358170d009a8ba8c6356c5b4 100644 (file)
@@ -1787,6 +1787,20 @@ void remote_get_opt_ssl(char* ATTR_UNUSED(str), void* ATTR_UNUSED(arg))
         log_assert(0);
 }
 
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
+       void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+       log_assert(0);
+       return 0;
+}
+
 #ifdef HAVE_NGTCP2
 void doq_client_event_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
        void* ATTR_UNUSED(arg))
index 8a5726fe3a4e91eb1406bcfc2096cc83a5f740e4..1bebdb7d0bb9c8e007521229cb4be5650797b9b5 100644 (file)
@@ -1,5 +1,9 @@
 31 March 2025: Wouter
        - iana portlist update.
+       - Merge #1042: Fast Reload. The unbound-control fast_reload is added.
+         It reads changed config in a thread, then only briefly pauses the
+         service threads, that keep running. DNS service is only interrupted
+         briefly, less than a second.
 
 27 March 2025: Wouter
        - Fix unit test dname log printout typecast.
index 467374d33729ac287f0adbd1d29f7056f2c74703..22a1214144922318eddc4d703b03fda1c75f85e0 100644 (file)
@@ -60,6 +60,93 @@ Reload the server but try to keep the RRset and message cache if
 That means the caches sizes and the number of threads must not change between
 reloads.
 .TP
+.B fast_reload \fR[\fI+dpv\fR]
+Reload the server, but keep downtime to a minimum, so that user queries
+keep seeing service. This needs the code compiled with threads. The config
+is loaded in a thread, and prepared, then it briefly pauses the existing
+server and updates config options. The intent is that the pause does not
+impact the service of user queries. The cache is kept. Also user queries
+worked on are kept and continue, but with the new config options.
+This command is experimental at this time.
+Not all options are changed, but it changes like forwards, stubs and
+local zones. Also access-control and interface-action and similar options,
+also tcp-connection-limits, views. It can reload some define-tag changes.
+It does not work with interface, outgoing-interface changes, also not with
+remote-control, outgoing-port-permit, outgoing-port-avoid, msg-buffer-size,
+slabs options and statistics-interval changes.
+.IP
+The fast reload also works on the options: insecure-lan-zones, domain-insecure,
+trust-anchor-file, trust-anchor, trusted-key-file, auto-trust-anchor-file,
+auth-zone and its options, rpz and its options, edns-strings, respip_set,
+view and its options, access-control options, tcp-connection-limit,
+log-identity, infra-cache-numhosts, msg-cache-size, rrset-cache-size,
+key-cache-size, ratelimit-size, neg-cache-size, num-queries-per-thread,
+jostle-timeout, use-caps-for-id, unwanted-reply-threshold, tls-use-sni,
+outgoing-tcp-mss, ip-dscp, max-reuse-tcp-queries, tcp-reuse-timeout,
+tcp-auth-query-timeout, delay-close.
+.IP
+For dnstap, the options can be changed: dnstap-log-resolver-query-messages,
+dnstap-log-resolver-response-messages, dnstap-log-client-query-messages,
+dnstap-log-client-response-messages, dnstap-log-forwarder-query-messages
+and dnstap-log-forwarder-response-messages. It does not work with
+these options: dnstap-enable, dnstap-bidirectional, dnstap-socket-path,
+dnstap-ip, dnstap-tls, dnstap-tls-server-name, dnstap-tls-cert-bundle,
+dnstap-tls-client-key-file and dnstap-tls-client-cert-file. The options
+dnstap-send-identity, dnstap-send-version, dnstap-identity, and
+dnstap-version can be loaded when '+p' is not used.
+.IP
+The '+v' option makes the output verbose which includes the time it took to do
+the reload.
+With '+vv' it is more verbose which includes the amount of memory that was
+allocated temporarily to perform the reload; this amount of memory can be big
+if the config has large contents.
+In the timing output the 'reload' time is the time during which the server was
+paused.
+.IP
+The '+p' option makes the reload not pause threads, they keep running.
+Locks are acquired, but items are updated in sequence, so it is possible
+for threads to see an inconsistent state with some options from the old
+and some options from the new config, such as cache TTL parameters from the
+old config and forwards from the new config. The stubs and forwards are
+updated at the same time, so that they are viewed consistently, either old
+or new values together. The option makes the reload time take eg. 3
+microseconds instead of 0.3 milliseconds during which the worker threads are
+interrupted. So, the interruption is much shorter, at the expense of some
+inconsistency. After the reload itself, every worker thread is briefly
+contacted to make them release resources, this makes the delete timing
+a little longer, and takes up time from the remote control servicing
+worker thread.
+.IP
+With the nopause option, the reload does not work to reload some options,
+that fast reload works on without the nopause option: val-bogus-ttl,
+val-date-override, val-sig-key-min, val-sig-skew-max, val-max-restart,
+val-nsec3-keysize-iterations, target-fetch-policy, outbound-msg-retry,
+max-sent-count, max-query-restarts, do-not-query-address,
+do-not-query-localhost, private-address, private-domain, caps-exempt,
+nat64-prefix, do-nat64, infra-host-ttl, infra-keep-probing, ratelimit,
+ip-ratelimit, ip-ratelimit-cookie, wait-limit-netblock,
+wait-limit-cookie-netblock, ratelimit-below-domain, ratelimit-for-domain.
+.IP
+The '+d' option makes the reload drop queries that the worker threads are
+working on. This is like flush_requestlist. Without it the queries are kept
+so that users keep getting answers for those queries that are currently
+processed. The drop makes it so that queries during the life time of the
+query processing see only old, or only new config options.
+.IP
+When there are changes to the config tags, from \fBdefine\-tag\fR config,
+then the '+d' option is implicitly turned on with a warning printout, and
+queries are dropped.
+This is to stop references to the old tag information, by the old
+queries. If the number of tags is increased in the newly loaded config, by
+adding tags at the end, then the implicit '+d' option is not needed.
+.IP
+For response ip, that is actions associated with IP addresses, and perhaps
+intersected with access control tag and action information, those settings
+are stored with a query when it comes in based on its source IP address.
+The old information is kept with the query until the queries are done.
+This is gone when those queries are resolved and finished, or it is possible
+to flush the requestlist with '+d'.
+.TP
 .B verbosity \fInumber
 Change verbosity value for logging. Same values as \fBverbosity\fR keyword in
 \fIunbound.conf\fR(5).  This new setting lasts until the server is issued
index 048d674019d5110b22ae8849dcf9561ab9983619..5c104a0a321775b22d702a39787719ad15120b73 100644 (file)
@@ -624,3 +624,19 @@ forwards_delete_stub_hole(struct iter_forwards* fwd, uint16_t c,
        fwd_init_parents(fwd);
        if(!nolock) { lock_rw_unlock(&fwd->lock); }
 }
+
+void
+forwards_swap_tree(struct iter_forwards* fwd, struct iter_forwards* data)
+{
+       rbtree_type* oldtree = fwd->tree;
+       if(oldtree) {
+               lock_unprotect(&fwd->lock, oldtree);
+       }
+       if(data->tree) {
+               lock_unprotect(&data->lock, data->tree);
+       }
+       fwd->tree = data->tree;
+       data->tree = oldtree;
+       lock_protect(&fwd->lock, fwd->tree, sizeof(*fwd->tree));
+       lock_protect(&data->lock, data->tree, sizeof(*data->tree));
+}
index 4527d899c793ae883a84ae4963e0dd9fd72b2e44..095cd96df852bc24fb0a92a4d259318b91aeb9f7 100644 (file)
@@ -234,4 +234,13 @@ int forwards_add_stub_hole(struct iter_forwards* fwd, uint16_t c,
 void forwards_delete_stub_hole(struct iter_forwards* fwd, uint16_t c,
        uint8_t* nm, int nolock);
 
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param fwd: the forward data structure.
+ * @param data: the data structure used to take elements from. This contains
+ *     the old elements on return.
+ */
+void forwards_swap_tree(struct iter_forwards* fwd, struct iter_forwards* data);
+
 #endif /* ITERATOR_ITER_FWD_H */
index fb9d10413f0bc09b9f1bd38ccd6147555a730706..9faf155ababe22ec8c24a3076144f607860e21b8 100644 (file)
@@ -611,3 +611,14 @@ hints_delete_stub(struct iter_hints* hints, uint16_t c, uint8_t* nm,
        name_tree_init_parents(&hints->tree);
        if(!nolock) { lock_rw_unlock(&hints->lock); }
 }
+
+void
+hints_swap_tree(struct iter_hints* hints, struct iter_hints* data)
+{
+       rbnode_type* oldroot = hints->tree.root;
+       size_t oldcount = hints->tree.count;
+       hints->tree.root = data->tree.root;
+       hints->tree.count = data->tree.count;
+       data->tree.root = oldroot;
+       data->tree.count = oldcount;
+}
index 26de323c9e985ba40e7c2e56d8f945463808f51a..87434b5ac2e69ee05b9f6eebec5ce73c93b04a3e 100644 (file)
@@ -198,4 +198,13 @@ int hints_add_stub(struct iter_hints* hints, uint16_t c, struct delegpt* dp,
 void hints_delete_stub(struct iter_hints* hints, uint16_t c,
        uint8_t* nm, int nolock);
 
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param hints: the hints data structure.
+ * @param data: the data structure used to take elements from. This contains
+ *     the old elements on return.
+ */
+void hints_swap_tree(struct iter_hints* hints, struct iter_hints* data);
+
 #endif /* ITERATOR_ITER_HINTS_H */
index 5acd7a1566c05d1b2a2a120cab5f6adab896ca33..1da21896cb2089e0d731c65df9c1efa4f3e295be 100644 (file)
 static const char DEFAULT_NAT64_PREFIX[] = "64:ff9b::/96";
 
 /** fillup fetch policy array */
-static void
-fetch_fill(struct iter_env* ie, const char* str)
+static int
+fetch_fill(int* target_fetch_policy, int max_dependency_depth, const char* str)
 {
        char* s = (char*)str, *e;
        int i;
-       for(i=0; i<ie->max_dependency_depth+1; i++) {
-               ie->target_fetch_policy[i] = strtol(s, &e, 10);
-               if(s == e)
-                       fatal_exit("cannot parse fetch policy number %s", s);
+       for(i=0; i<max_dependency_depth+1; i++) {
+               target_fetch_policy[i] = strtol(s, &e, 10);
+               if(s == e) {
+                       log_err("cannot parse fetch policy number %s", s);
+                       return 0;
+               }
                s = e;
        }
+       return 1;
 }
 
 /** Read config string that represents the target fetch policy */
-static int
-read_fetch_policy(struct iter_env* ie, const char* str)
+int
+read_fetch_policy(int** target_fetch_policy, int* max_dependency_depth,
+       const char* str)
 {
        int count = cfg_count_numbers(str);
        if(count < 1) {
                log_err("Cannot parse target fetch policy: \"%s\"", str);
                return 0;
        }
-       ie->max_dependency_depth = count - 1;
-       ie->target_fetch_policy = (int*)calloc(
-               (size_t)ie->max_dependency_depth+1, sizeof(int));
-       if(!ie->target_fetch_policy) {
+       *max_dependency_depth = count - 1;
+       *target_fetch_policy = (int*)calloc(
+               (size_t)(*max_dependency_depth)+1, sizeof(int));
+       if(!*target_fetch_policy) {
                log_err("alloc fetch policy: out of memory");
                return 0;
        }
-       fetch_fill(ie, str);
+       if(!fetch_fill(*target_fetch_policy, *max_dependency_depth, str))
+               return 0;
        return 1;
 }
 
-/** apply config caps whitelist items to name tree */
-static int
+struct rbtree_type*
+caps_white_create(void)
+{
+       struct rbtree_type* caps_white = rbtree_create(name_tree_compare);
+       if(!caps_white)
+               log_err("out of memory");
+       return caps_white;
+}
+
+/** delete caps_whitelist element */
+static void
+caps_free(struct rbnode_type* n, void* ATTR_UNUSED(d))
+{
+       if(n) {
+               free(((struct name_tree_node*)n)->name);
+               free(n);
+       }
+}
+
+void
+caps_white_delete(struct rbtree_type* caps_white)
+{
+       if(!caps_white)
+               return;
+       traverse_postorder(caps_white, caps_free, NULL);
+       free(caps_white);
+}
+
+int
 caps_white_apply_cfg(rbtree_type* ntree, struct config_file* cfg)
 {
        struct config_strlist* p;
@@ -145,12 +177,41 @@ caps_white_apply_cfg(rbtree_type* ntree, struct config_file* cfg)
 }
 
 int
-iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
+nat64_apply_cfg(struct iter_nat64* nat64, struct config_file* cfg)
 {
        const char *nat64_prefix;
+
+       nat64_prefix = cfg->nat64_prefix;
+       if(!nat64_prefix)
+               nat64_prefix = cfg->dns64_prefix;
+       if(!nat64_prefix)
+               nat64_prefix = DEFAULT_NAT64_PREFIX;
+       if(!netblockstrtoaddr(nat64_prefix, 0, &nat64->nat64_prefix_addr,
+               &nat64->nat64_prefix_addrlen, &nat64->nat64_prefix_net)) {
+               log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix);
+               return 0;
+       }
+       if(!addr_is_ip6(&nat64->nat64_prefix_addr,
+               nat64->nat64_prefix_addrlen)) {
+               log_err("nat64-prefix is not IPv6: %s", cfg->nat64_prefix);
+               return 0;
+       }
+       if(!prefixnet_is_nat64(nat64->nat64_prefix_net)) {
+               log_err("nat64-prefix length it not 32, 40, 48, 56, 64 or 96: %s",
+                       nat64_prefix);
+               return 0;
+       }
+       nat64->use_nat64 = cfg->do_nat64;
+       return 1;
+}
+
+int
+iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
+{
        int i;
        /* target fetch policy */
-       if(!read_fetch_policy(iter_env, cfg->target_fetch_policy))
+       if(!read_fetch_policy(&iter_env->target_fetch_policy,
+               &iter_env->max_dependency_depth, cfg->target_fetch_policy))
                return 0;
        for(i=0; i<iter_env->max_dependency_depth+1; i++)
                verbose(VERB_QUERY, "target fetch policy for level %d is %d",
@@ -170,7 +231,7 @@ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
        }
        if(cfg->caps_whitelist) {
                if(!iter_env->caps_white)
-                       iter_env->caps_white = rbtree_create(name_tree_compare);
+                       iter_env->caps_white = caps_white_create();
                if(!iter_env->caps_white || !caps_white_apply_cfg(
                        iter_env->caps_white, cfg)) {
                        log_err("Could not set capsforid whitelist");
@@ -179,31 +240,13 @@ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
 
        }
 
-       nat64_prefix = cfg->nat64_prefix;
-       if(!nat64_prefix)
-               nat64_prefix = cfg->dns64_prefix;
-       if(!nat64_prefix)
-               nat64_prefix = DEFAULT_NAT64_PREFIX;
-       if(!netblockstrtoaddr(nat64_prefix, 0, &iter_env->nat64_prefix_addr,
-               &iter_env->nat64_prefix_addrlen,
-               &iter_env->nat64_prefix_net)) {
-               log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix);
-               return 0;
-       }
-       if(!addr_is_ip6(&iter_env->nat64_prefix_addr,
-               iter_env->nat64_prefix_addrlen)) {
-               log_err("nat64-prefix is not IPv6: %s", cfg->nat64_prefix);
-               return 0;
-       }
-       if(!prefixnet_is_nat64(iter_env->nat64_prefix_net)) {
-               log_err("nat64-prefix length it not 32, 40, 48, 56, 64 or 96: %s",
-                       nat64_prefix);
+       if(!nat64_apply_cfg(&iter_env->nat64, cfg)) {
+               log_err("Could not setup nat64");
                return 0;
        }
 
        iter_env->supports_ipv6 = cfg->do_ip6;
        iter_env->supports_ipv4 = cfg->do_ip4;
-       iter_env->use_nat64 = cfg->do_nat64;
        iter_env->outbound_msg_retry = cfg->outbound_msg_retry;
        iter_env->max_sent_count = cfg->max_sent_count;
        iter_env->max_query_restarts = cfg->max_query_restarts;
@@ -270,7 +313,7 @@ iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env,
        if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr, a->addrlen)) {
                return -1; /* there is no ip6 available */
        }
-       if(!iter_env->supports_ipv4 && !iter_env->use_nat64 &&
+       if(!iter_env->supports_ipv4 && !iter_env->nat64.use_nat64 &&
           !addr_is_ip6(&a->addr, a->addrlen)) {
                return -1; /* there is no ip4 available */
        }
index 30b3dbe5f0bfe938947bd6ba2c92b0ae83656552..b17b091e6639a87d393ff4118e59d9081f4c529d 100644 (file)
@@ -61,6 +61,7 @@ struct sock_list;
 struct ub_packed_rrset_key;
 struct module_stack;
 struct outside_network;
+struct iter_nat64;
 
 /* max number of lookups in the cache for target nameserver names.
  * This stops, for large delegations, N*N lookups in the cache. */
@@ -430,6 +431,43 @@ int iter_stub_fwd_no_cache(struct module_qstate *qstate,
 void iterator_set_ip46_support(struct module_stack* mods,
        struct module_env* env, struct outside_network* outnet);
 
+/**
+ * Read config string that represents the target fetch policy.
+ * @param target_fetch_policy: alloced on return.
+ * @param max_dependency_depth: set on return.
+ * @param str: the config string
+ * @return false on failure.
+ */
+int read_fetch_policy(int** target_fetch_policy, int* max_dependency_depth,
+       const char* str);
+
+/**
+ * Create caps exempt data structure.
+ * @return NULL on failure.
+ */
+struct rbtree_type* caps_white_create(void);
+
+/**
+ * Delete caps exempt data structure.
+ * @param caps_white: caps exempt tree.
+ */
+void caps_white_delete(struct rbtree_type* caps_white);
+
+/**
+ * Apply config caps whitelist items to name tree
+ * @param ntree: caps exempt tree.
+ * @param cfg: config with options.
+ */
+int caps_white_apply_cfg(struct rbtree_type* ntree, struct config_file* cfg);
+
+/**
+ * Apply config for nat64
+ * @param nat64: the nat64 state.
+ * @param cfg: config with options.
+ * @return false on failure.
+ */
+int nat64_apply_cfg(struct iter_nat64* nat64, struct config_file* cfg);
+
 /**
  * Limit NSEC and NSEC3 TTL in response, RFC9077
  * @param msg: dns message, the SOA record ttl is used to restrict ttls
index a0ddb296b09a88d81f94ec24ab8ed3091225465e..8c0703e9e44f5fd817b6cdefcee2ef5369765343 100644 (file)
@@ -107,16 +107,6 @@ iter_init(struct module_env* env, int id)
        return 1;
 }
 
-/** delete caps_whitelist element */
-static void
-caps_free(struct rbnode_type* n, void* ATTR_UNUSED(d))
-{
-       if(n) {
-               free(((struct name_tree_node*)n)->name);
-               free(n);
-       }
-}
-
 void 
 iter_deinit(struct module_env* env, int id)
 {
@@ -128,10 +118,7 @@ iter_deinit(struct module_env* env, int id)
        free(iter_env->target_fetch_policy);
        priv_delete(iter_env->priv);
        donotq_delete(iter_env->donotq);
-       if(iter_env->caps_white) {
-               traverse_postorder(iter_env->caps_white, caps_free, NULL);
-               free(iter_env->caps_white);
-       }
+       caps_white_delete(iter_env->caps_white);
        free(iter_env);
        env->modinfo[id] = NULL;
 }
@@ -260,7 +247,7 @@ error_supers(struct module_qstate* qstate, int id, struct module_qstate* super)
                                log_err("out of memory adding missing");
                }
                delegpt_mark_neg(dpns, qstate->qinfo.qtype);
-               if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
+               if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->nat64.use_nat64)) &&
                        (dpns->got6 == 2 || !ie->supports_ipv6)) {
                        dpns->resolved = 1; /* mark as failed */
                        target_count_increase_nx(super_iq, 1);
@@ -1725,7 +1712,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
                 */
                if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags,
                        iq->dp, ie->supports_ipv4, ie->supports_ipv6,
-                       ie->use_nat64)) {
+                       ie->nat64.use_nat64)) {
                        int have_dp = 0;
                        if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, &have_dp, &iq->dp, qstate->region)) {
                                if(have_dp) {
@@ -2089,7 +2076,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq,
                        if(mesh_jostle_exceeded(qstate->env->mesh)) {
                                /* If no ip4 query is possible, that makes
                                 * this ns resolved. */
-                               if(!((ie->supports_ipv4 || ie->use_nat64) &&
+                               if(!((ie->supports_ipv4 || ie->nat64.use_nat64) &&
                                        ((ns->lame && !ns->done_pside4) ||
                                        (!ns->lame && !ns->got4)))) {
                                        ns->resolved = 1;
@@ -2098,7 +2085,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq,
                        }
                }
                /* Send the A request. */
-               if((ie->supports_ipv4 || ie->use_nat64) &&
+               if((ie->supports_ipv4 || ie->nat64.use_nat64) &&
                        ((ns->lame && !ns->done_pside4) ||
                        (!ns->lame && !ns->got4))) {
                        if(!generate_target_query(qstate, iq, id, 
@@ -2270,14 +2257,14 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq,
                /* if this nameserver is at a delegation point, but that
                 * delegation point is a stub and we cannot go higher, skip*/
                if( ((ie->supports_ipv6 && !ns->done_pside6) ||
-                   ((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4)) &&
+                   ((ie->supports_ipv4 || ie->nat64.use_nat64) && !ns->done_pside4)) &&
                    !can_have_last_resort(qstate->env, ns->name, ns->namelen,
                        iq->qchase.qclass, NULL, NULL, NULL)) {
                        log_nametypeclass(VERB_ALGO, "cannot pside lookup ns "
                                "because it is also a stub/forward,",
                                ns->name, LDNS_RR_TYPE_NS, iq->qchase.qclass);
                        if(ie->supports_ipv6) ns->done_pside6 = 1;
-                       if(ie->supports_ipv4 || ie->use_nat64) ns->done_pside4 = 1;
+                       if(ie->supports_ipv4 || ie->nat64.use_nat64) ns->done_pside4 = 1;
                        continue;
                }
                /* query for parent-side A and AAAA for nameservers */
@@ -2302,7 +2289,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq,
                                return 0;
                        }
                }
-               if((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4) {
+               if((ie->supports_ipv4 || ie->nat64.use_nat64) && !ns->done_pside4) {
                        /* Send the A request. */
                        if(!generate_parentside_target_query(qstate, iq, id, 
                                ns->name, ns->namelen, 
@@ -2571,7 +2558,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
        }
        if(!ie->supports_ipv6)
                delegpt_no_ipv6(iq->dp);
-       if(!ie->supports_ipv4 && !ie->use_nat64)
+       if(!ie->supports_ipv4 && !ie->nat64.use_nat64)
                delegpt_no_ipv4(iq->dp);
        delegpt_log(VERB_ALGO, iq->dp);
 
@@ -3070,9 +3057,9 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
        real_addr = target->addr;
        real_addrlen = target->addrlen;
 
-       if(ie->use_nat64 && target->addr.ss_family == AF_INET) {
-               addr_to_nat64(&target->addr, &ie->nat64_prefix_addr,
-                       ie->nat64_prefix_addrlen, ie->nat64_prefix_net,
+       if(ie->nat64.use_nat64 && target->addr.ss_family == AF_INET) {
+               addr_to_nat64(&target->addr, &ie->nat64.nat64_prefix_addr,
+                       ie->nat64.nat64_prefix_addrlen, ie->nat64.nat64_prefix_net,
                        &real_addr, &real_addrlen);
                log_name_addr(VERB_QUERY, "applied NAT64:",
                        iq->dp->name, &real_addr, real_addrlen);
@@ -3882,7 +3869,7 @@ processTargetResponse(struct module_qstate* qstate, int id,
        } else {
                verbose(VERB_ALGO, "iterator TargetResponse failed");
                delegpt_mark_neg(dpns, qstate->qinfo.qtype);
-               if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
+               if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->nat64.use_nat64)) &&
                        (dpns->got6 == 2 || !ie->supports_ipv6)) {
                        dpns->resolved = 1; /* fail the target */
                        /* do not count cached answers */
index 46701f6eee75c5bcbc5cf4976161fea87e37244f..ae4b4e45170a615ce9f3bf9d7c09bdd489ec6978 100644 (file)
@@ -46,8 +46,6 @@
 #include "util/data/msgreply.h"
 #include "util/module.h"
 struct delegpt;
-struct iter_hints;
-struct iter_forwards;
 struct iter_donotq;
 struct iter_prep_list;
 struct iter_priv;
@@ -108,15 +106,9 @@ extern int BLACKLIST_PENALTY;
 #define EMPTY_NODATA_RETRY_COUNT 2
 
 /**
- * Global state for the iterator.
+ * Iterator global state for nat64.
  */
-struct iter_env {
-       /** A flag to indicate whether or not we have an IPv6 route */
-       int supports_ipv6;
-
-       /** A flag to indicate whether or not we have an IPv4 route */
-       int supports_ipv4;
-
+struct iter_nat64 {
        /** A flag to locally apply NAT64 to make IPv4 addrs into IPv6 */
        int use_nat64;
 
@@ -128,6 +120,20 @@ struct iter_env {
 
        /** CIDR mask length of NAT64 prefix */
        int nat64_prefix_net;
+};
+
+/**
+ * Global state for the iterator.
+ */
+struct iter_env {
+       /** A flag to indicate whether or not we have an IPv6 route */
+       int supports_ipv6;
+
+       /** A flag to indicate whether or not we have an IPv4 route */
+       int supports_ipv4;
+
+       /** State for nat64 */
+       struct iter_nat64 nat64;
 
        /** A set of inetaddrs that should never be queried. */
        struct iter_donotq* donotq;
index 6b2bf7a3c53b482fa77574406dfd76900b0411e5..f0496452b521a6f7ad02b01c97657c1ef1d4d8b7 100644 (file)
@@ -1059,6 +1059,20 @@ void dtio_mainfdcallback(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
 }
 #endif
 
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
+       void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+       log_assert(0);
+       return 0;
+}
+
 #ifdef HAVE_NGTCP2
 void doq_client_event_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
        void* ATTR_UNUSED(arg))
index c0ecba1601871fe04036d5ba84f4dd47618ce0ce..353a0fd783e5df331ab1e2f34a193ad9b42f14f3 100644 (file)
@@ -105,6 +105,7 @@ respip_sockaddr_find_or_create(struct respip_set* set, struct sockaddr_storage*
                socklen_t addrlen, int net, int create, const char* ipstr)
 {
        struct resp_addr* node;
+       log_assert(set);
        node = (struct resp_addr*)addr_tree_find(&set->ip_tree, addr, addrlen, net);
        if(!node && create) {
                node = regional_alloc_zero(set->region, sizeof(*node));
@@ -128,6 +129,7 @@ void
 respip_sockaddr_delete(struct respip_set* set, struct resp_addr* node)
 {
        struct resp_addr* prev;
+       log_assert(set);
        prev = (struct resp_addr*)rbtree_previous((struct rbnode_type*)node);   
        lock_rw_destroy(&node->lock);
        (void)rbtree_delete(&set->ip_tree, node);
@@ -146,6 +148,7 @@ respip_find_or_create(struct respip_set* set, const char* ipstr, int create)
        struct sockaddr_storage addr;
        int net;
        socklen_t addrlen;
+       log_assert(set);
 
        if(!netblockstrtoaddr(ipstr, 0, &addr, &addrlen, &net)) {
                log_err("cannot parse netblock: '%s'", ipstr);
@@ -160,6 +163,7 @@ respip_tag_cfg(struct respip_set* set, const char* ipstr,
        const uint8_t* taglist, size_t taglen)
 {
        struct resp_addr* node;
+       log_assert(set);
 
        if(!(node=respip_find_or_create(set, ipstr, 1)))
                return 0;
@@ -183,6 +187,7 @@ respip_action_cfg(struct respip_set* set, const char* ipstr,
 {
        struct resp_addr* node;
        enum respip_action action;
+       log_assert(set);
 
        if(!(node=respip_find_or_create(set, ipstr, 1)))
                return 0;
@@ -325,6 +330,7 @@ static int
 respip_data_cfg(struct respip_set* set, const char* ipstr, const char* rrstr)
 {
        struct resp_addr* node;
+       log_assert(set);
 
        node=respip_find_or_create(set, ipstr, 0);
        if(!node || node->action == respip_none) {
@@ -344,6 +350,7 @@ respip_set_apply_cfg(struct respip_set* set, char* const* tagname, int num_tags,
        struct config_strbytelist* p;
        struct config_str2list* pa;
        struct config_str2list* pd;
+       log_assert(set);
 
        set->tagname = tagname;
        set->num_tags = num_tags;
@@ -609,6 +616,7 @@ respip_addr_lookup(const struct reply_info *rep, struct respip_set* rs,
        struct resp_addr* ra;
        struct sockaddr_storage ss;
        socklen_t addrlen;
+       log_assert(rs);
 
        lock_rw_rdlock(&rs->lock);
        for(i=0; i<rep->an_numrrsets; i++) {
@@ -867,7 +875,8 @@ respip_rewrite_reply(const struct query_info* qinfo,
        const struct respip_client_info* cinfo, const struct reply_info* rep,
        struct reply_info** new_repp, struct respip_action_info* actinfo,
        struct ub_packed_rrset_key** alias_rrset, int search_only,
-       struct regional* region, struct auth_zones* az, int* rpz_passthru)
+       struct regional* region, struct auth_zones* az, int* rpz_passthru,
+       struct views* views, struct respip_set* ipset)
 {
        const uint8_t* ctaglist;
        size_t ctaglen;
@@ -876,7 +885,6 @@ respip_rewrite_reply(const struct query_info* qinfo,
        struct config_strlist** tag_datas;
        size_t tag_datas_size;
        struct view* view = NULL;
-       struct respip_set* ipset = NULL;
        size_t rrset_id = 0, rr_id = 0;
        enum respip_action action = respip_none;
        int tag = -1;
@@ -899,8 +907,20 @@ respip_rewrite_reply(const struct query_info* qinfo,
        tag_actions_size = cinfo->tag_actions_size;
        tag_datas = cinfo->tag_datas;
        tag_datas_size = cinfo->tag_datas_size;
-       view = cinfo->view;
-       ipset = cinfo->respip_set;
+       if(cinfo->view) {
+               view = cinfo->view;
+               lock_rw_rdlock(&view->lock);
+       } else if(cinfo->view_name) {
+               view = views_find_view(views, cinfo->view_name, 0);
+               if(!view) {
+                       /* If the view no longer exists, the rewrite can not
+                        * be processed further. */
+                       verbose(VERB_ALGO, "respip: failed because view %s no "
+                               "longer exists", cinfo->view_name);
+                       return 0;
+               }
+               /* The view is rdlocked by views_find_view. */
+       }
 
        log_assert(ipset);
 
@@ -915,7 +935,6 @@ respip_rewrite_reply(const struct query_info* qinfo,
          * Note also that we assume 'view' is valid in this function, which
          * should be safe (see unbound bug #1191) */
        if(view) {
-               lock_rw_rdlock(&view->lock);
                if(view->respip_set) {
                        if((raddr = respip_addr_lookup(rep,
                                view->respip_set, &rrset_id, &rr_id))) {
@@ -1101,7 +1120,8 @@ respip_operate(struct module_qstate* qstate, enum module_ev event, int id,
                                qstate->client_info, qstate->return_msg->rep,
                                &new_rep, &actinfo, &alias_rrset, 0,
                                qstate->region, qstate->env->auth_zones,
-                               &qstate->rpz_passthru)) {
+                               &qstate->rpz_passthru, qstate->env->views,
+                               qstate->env->respip_set)) {
                                goto servfail;
                        }
                        if(actinfo.action != respip_none) {
@@ -1149,7 +1169,8 @@ respip_merge_cname(struct reply_info* base_rep,
        const struct query_info* qinfo, const struct reply_info* tgt_rep,
        const struct respip_client_info* cinfo, int must_validate,
        struct reply_info** new_repp, struct regional* region,
-       struct auth_zones* az)
+       struct auth_zones* az, struct views* views,
+       struct respip_set* respip_set)
 {
        struct reply_info* new_rep;
        struct reply_info* tmp_rep = NULL; /* just a placeholder */
@@ -1176,7 +1197,7 @@ respip_merge_cname(struct reply_info* base_rep,
 
        /* see if the target reply would be subject to a response-ip action. */
        if(!respip_rewrite_reply(qinfo, cinfo, tgt_rep, &tmp_rep, &actinfo,
-               &alias_rrset, 1, region, az, NULL))
+               &alias_rrset, 1, region, az, NULL, views, respip_set))
                return 0;
        if(actinfo.action != respip_none) {
                log_info("CNAME target of redirect response-ip action would "
@@ -1229,7 +1250,8 @@ respip_inform_super(struct module_qstate* qstate, int id,
        if(!respip_merge_cname(super->return_msg->rep, &qstate->qinfo,
                qstate->return_msg->rep, super->client_info,
                super->env->need_to_validate, &new_rep, super->region,
-               qstate->env->auth_zones))
+               qstate->env->auth_zones, qstate->env->views,
+               qstate->env->respip_set))
                goto fail;
        super->return_msg->rep = new_rep;
        return;
@@ -1326,3 +1348,35 @@ respip_inform_print(struct respip_action_info* respip_actinfo, uint8_t* qname,
                (actionstr) ? actionstr : "inform", srcip, port);
        log_nametypeclass(NO_VERBOSE, txt, qname, qtype, qclass);
 }
+
+size_t respip_set_get_mem(struct respip_set* set)
+{
+       size_t m;
+       if(!set) return 0;
+       m = sizeof(*set);
+       lock_rw_rdlock(&set->lock);
+       m += regional_get_mem(set->region);
+       lock_rw_unlock(&set->lock);
+       return m;
+}
+
+void
+respip_set_swap_tree(struct respip_set* respip_set,
+       struct respip_set* data)
+{
+       rbnode_type* oldroot = respip_set->ip_tree.root;
+       size_t oldcount = respip_set->ip_tree.count;
+       struct regional* oldregion = respip_set->region;
+       char* const* oldtagname = respip_set->tagname;
+       int oldnum_tags = respip_set->num_tags;
+       respip_set->ip_tree.root = data->ip_tree.root;
+       respip_set->ip_tree.count = data->ip_tree.count;
+       respip_set->region = data->region;
+       respip_set->tagname = data->tagname;
+       respip_set->num_tags = data->num_tags;
+       data->ip_tree.root = oldroot;
+       data->ip_tree.count = oldcount;
+       data->region = oldregion;
+       data->tagname = oldtagname;
+       data->num_tags = oldnum_tags;
+}
index e4ab5cc9cce376dd84ef95062cdff5a0e540ffa5..6469854c53cb35986998a060096638bd69f7da3c 100644 (file)
@@ -23,7 +23,8 @@
 struct respip_set {
        struct regional* region;
        struct rbtree_type ip_tree;
-       lock_rw_type lock;      /* lock on the respip tree */
+       lock_rw_type lock;      /* lock on the respip tree. It is ordered
+               after views and before hints, stubs and local zones. */
        char* const* tagname;   /* shallow copy of tag names, for logging */
        int num_tags;           /* number of tagname entries */
 };
@@ -59,7 +60,6 @@ struct respip_addr_info;
  * This is essentially a subset of acl_addr (except for respip_set) but
  * defined as a separate structure to avoid dependency on the daemon-specific
  * structure.
- * respip_set is supposed to refer to the response-ip set for the global view.
  */
 struct respip_client_info {
        uint8_t* taglist;
@@ -68,8 +68,12 @@ struct respip_client_info {
        size_t tag_actions_size;
        struct config_strlist** tag_datas;
        size_t tag_datas_size;
+       /** The view for the action, during cache callback that is by
+        * pointer. */
        struct view* view;
-       struct respip_set* respip_set;
+       /** If from module query state, the view pointer is NULL, but the
+        * name is stored in reference to the view. */
+       char* view_name;
 };
 
 /**
@@ -149,13 +153,16 @@ int respip_views_apply_cfg(struct views* vs, struct config_file* cfg,
  *   on error.
  * @param region: allocator to build *new_repp.
  * @param az: auth zones containing RPZ information.
+ * @param views: views tree to lookup view used.
+ * @param respip_set: the respip set for the global view.
  * @return 1 on success, 0 on error.
  */
 int respip_merge_cname(struct reply_info* base_rep,
        const struct query_info* qinfo, const struct reply_info* tgt_rep,
        const struct respip_client_info* cinfo, int must_validate,
        struct reply_info** new_repp, struct regional* region,
-       struct auth_zones* az);
+       struct auth_zones* az, struct views* views,
+       struct respip_set* respip_set);
 
 /**
  * See if any IP-based action should apply to any IP address of AAAA/A answer
@@ -178,6 +185,8 @@ int respip_merge_cname(struct reply_info* base_rep,
  * @param region: allocator to build *new_repp.
  * @param rpz_passthru: keeps track of query state can have passthru that
  *   stops further rpz processing. Or NULL for cached answer processing.
+ * @param views: views tree to lookup view used.
+ * @param ipset: the respip set for the global view.
  * @return 1 on success, 0 on error.
  */
 int respip_rewrite_reply(const struct query_info* qinfo,
@@ -186,7 +195,7 @@ int respip_rewrite_reply(const struct query_info* qinfo,
        struct respip_action_info* actinfo,
        struct ub_packed_rrset_key** alias_rrset,
        int search_only, struct regional* region, struct auth_zones* az,
-       int* rpz_passthru);
+       int* rpz_passthru, struct views* views, struct respip_set* ipset);
 
 /**
  * Get the response-ip function block.
@@ -302,4 +311,18 @@ respip_sockaddr_delete(struct respip_set* set, struct resp_addr* node);
 
 struct ub_packed_rrset_key*
 respip_copy_rrset(const struct ub_packed_rrset_key* key, struct regional* region);
+
+/** Get memory usage of respip set tree. The routine locks and unlocks the
+ * set for reading. */
+size_t respip_set_get_mem(struct respip_set* set);
+
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param respip_set: response ip tree
+ * @param data: preallocated information.
+ */
+void respip_set_swap_tree(struct respip_set* respip_set,
+       struct respip_set* data);
+
 #endif /* RESPIP_RESPIP_H */
index 90fd0235edd48d94915dd616afefe444607519bc..3c3dc9ad05d9fab4c829343cf7f2aee890ebd40d 100644 (file)
@@ -2317,9 +2317,6 @@ auth_free_masters(struct auth_master* list)
        }
 }
 
-/** delete auth xfer structure
- * @param xfr: delete this xfer and its tasks.
- */
 void
 auth_xfer_delete(struct auth_xfer* xfr)
 {
@@ -7006,6 +7003,18 @@ xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env,
        comm_timer_set(xfr->task_nextprobe->timer, &tv);
 }
 
+void auth_xfer_pickup_initial_zone(struct auth_xfer* x, struct module_env* env)
+{
+       /* set lease_time, because we now have timestamp in env,
+        * (not earlier during startup and apply_cfg), and this
+        * notes the start time when the data was acquired */
+       if(x->have_zone)
+               x->lease_time = *env->now;
+       if(x->task_nextprobe && x->task_nextprobe->worker == NULL) {
+               xfr_set_timeout(x, env, 0, 1);
+       }
+}
+
 /** initial pick up of worker timeouts, ties events to worker event loop */
 void
 auth_xfer_pickup_initial(struct auth_zones* az, struct module_env* env)
@@ -7014,14 +7023,7 @@ auth_xfer_pickup_initial(struct auth_zones* az, struct module_env* env)
        lock_rw_wrlock(&az->lock);
        RBTREE_FOR(x, struct auth_xfer*, &az->xtree) {
                lock_basic_lock(&x->lock);
-               /* set lease_time, because we now have timestamp in env,
-                * (not earlier during startup and apply_cfg), and this
-                * notes the start time when the data was acquired */
-               if(x->have_zone)
-                       x->lease_time = *env->now;
-               if(x->task_nextprobe && x->task_nextprobe->worker == NULL) {
-                       xfr_set_timeout(x, env, 0, 1);
-               }
+               auth_xfer_pickup_initial_zone(x, env);
                lock_basic_unlock(&x->lock);
        }
        lock_rw_unlock(&az->lock);
@@ -8580,3 +8582,161 @@ void auth_zones_pickup_zonemd_verify(struct auth_zones* az,
        }
        lock_rw_unlock(&az->lock);
 }
+
+/** Get memory usage of auth rrset */
+static size_t
+auth_rrset_get_mem(struct auth_rrset* rrset)
+{
+       size_t m = sizeof(*rrset) + packed_rrset_sizeof(rrset->data);
+       return m;
+}
+
+/** Get memory usage of auth data */
+static size_t
+auth_data_get_mem(struct auth_data* node)
+{
+       size_t m = sizeof(*node) + node->namelen;
+       struct auth_rrset* rrset;
+       for(rrset = node->rrsets; rrset; rrset = rrset->next) {
+               m += auth_rrset_get_mem(rrset);
+       }
+       return m;
+}
+
+/** Get memory usage of auth zone */
+static size_t
+auth_zone_get_mem(struct auth_zone* z)
+{
+       size_t m = sizeof(*z) + z->namelen;
+       struct auth_data* node;
+       if(z->zonefile)
+               m += strlen(z->zonefile)+1;
+       RBTREE_FOR(node, struct auth_data*, &z->data) {
+               m += auth_data_get_mem(node);
+       }
+       if(z->rpz)
+               m += rpz_get_mem(z->rpz);
+       return m;
+}
+
+/** Get memory usage of list of auth addr */
+static size_t
+auth_addrs_get_mem(struct auth_addr* list)
+{
+       size_t m = 0;
+       struct auth_addr* a;
+       for(a = list; a; a = a->next) {
+               m += sizeof(*a);
+       }
+       return m;
+}
+
+/** Get memory usage of list of primaries for auth xfer */
+static size_t
+auth_primaries_get_mem(struct auth_master* list)
+{
+       size_t m = 0;
+       struct auth_master* n;
+       for(n = list; n; n = n->next) {
+               m += sizeof(*n);
+               m += auth_addrs_get_mem(n->list);
+               if(n->host)
+                       m += strlen(n->host)+1;
+               if(n->file)
+                       m += strlen(n->file)+1;
+       }
+       return m;
+}
+
+/** Get memory usage or list of auth chunks */
+static size_t
+auth_chunks_get_mem(struct auth_chunk* list)
+{
+       size_t m = 0;
+       struct auth_chunk* chunk;
+       for(chunk = list; chunk; chunk = chunk->next) {
+               m += sizeof(*chunk) + chunk->len;
+       }
+       return m;
+}
+
+/** Get memory usage of auth xfer */
+static size_t
+auth_xfer_get_mem(struct auth_xfer* xfr)
+{
+       size_t m = sizeof(*xfr) + xfr->namelen;
+
+       /* auth_nextprobe */
+       m += comm_timer_get_mem(xfr->task_nextprobe->timer);
+
+       /* auth_probe */
+       m += auth_primaries_get_mem(xfr->task_probe->masters);
+       m += comm_point_get_mem(xfr->task_probe->cp);
+       m += comm_timer_get_mem(xfr->task_probe->timer);
+
+       /* auth_transfer */
+       m += auth_chunks_get_mem(xfr->task_transfer->chunks_first);
+       m += auth_primaries_get_mem(xfr->task_transfer->masters);
+       m += comm_point_get_mem(xfr->task_transfer->cp);
+       m += comm_timer_get_mem(xfr->task_transfer->timer);
+
+       /* allow_notify_list */
+       m += auth_primaries_get_mem(xfr->allow_notify_list);
+
+       return m;
+}
+
+/** Get memory usage of auth zones ztree */
+static size_t
+az_ztree_get_mem(struct auth_zones* az)
+{
+       size_t m = 0;
+       struct auth_zone* z;
+       RBTREE_FOR(z, struct auth_zone*, &az->ztree) {
+               lock_rw_rdlock(&z->lock);
+               m += auth_zone_get_mem(z);
+               lock_rw_unlock(&z->lock);
+       }
+       return m;
+}
+
+/** Get memory usage of auth zones xtree */
+static size_t
+az_xtree_get_mem(struct auth_zones* az)
+{
+       size_t m = 0;
+       struct auth_xfer* xfr;
+       RBTREE_FOR(xfr, struct auth_xfer*, &az->xtree) {
+               lock_basic_lock(&xfr->lock);
+               m += auth_xfer_get_mem(xfr);
+               lock_basic_unlock(&xfr->lock);
+       }
+       return m;
+}
+
+size_t auth_zones_get_mem(struct auth_zones* zones)
+{
+       size_t m;
+       if(!zones) return 0;
+       m = sizeof(*zones);
+       lock_rw_rdlock(&zones->rpz_lock);
+       lock_rw_rdlock(&zones->lock);
+       m += az_ztree_get_mem(zones);
+       m += az_xtree_get_mem(zones);
+       lock_rw_unlock(&zones->lock);
+       lock_rw_unlock(&zones->rpz_lock);
+       return m;
+}
+
+void xfr_disown_tasks(struct auth_xfer* xfr, struct worker* worker)
+{
+       if(xfr->task_nextprobe->worker == worker) {
+               xfr_nextprobe_disown(xfr);
+       }
+       if(xfr->task_probe->worker == worker) {
+               xfr_probe_disown(xfr);
+       }
+       if(xfr->task_transfer->worker == worker) {
+               xfr_transfer_disown(xfr);
+       }
+}
index 3994a4eadb313c5f0b1315092cf3303d687b6d06..722781a063a8c9bd7da4fad711a5163b6ab3ad84 100644 (file)
@@ -70,7 +70,8 @@ struct auth_chunk;
  * Authoritative zones, shared.
  */
 struct auth_zones {
-       /** lock on the authzone trees */
+       /** lock on the authzone trees. It is locked after views, respip,
+        * local_zones and before fwds and stubs. */
        lock_rw_type lock;
        /** rbtree of struct auth_zone */
        rbtree_type ztree;
@@ -207,7 +208,9 @@ struct auth_xfer {
         * one of the tasks. 
         * Once it has the task assigned to it, the worker can access the
         * other elements of the task structure without a lock, because that
-        * is necessary for the eventloop and callbacks from that. */
+        * is necessary for the eventloop and callbacks from that.
+        * The auth_zone->lock is locked before this lock.
+        */
        lock_basic_type lock;
 
        /** zone name, in uncompressed wireformat */
@@ -783,4 +786,33 @@ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode,
 void auth_zones_pickup_zonemd_verify(struct auth_zones* az,
        struct module_env* env);
 
+/** Get memory usage for auth zones. The routine locks and unlocks
+ * for reading. */
+size_t auth_zones_get_mem(struct auth_zones* zones);
+
+/**
+ * Initial pick up of the auth zone nextprobe timeout and that turns
+ * into further zone transfer work, if any. Also sets the lease time.
+ * @param x: xfer structure, locked by caller.
+ * @param env: environment of the worker that picks up the task.
+ */
+void auth_xfer_pickup_initial_zone(struct auth_xfer* x,
+       struct module_env* env);
+
+/**
+ * Delete auth xfer structure
+ * @param xfr: delete this xfer and its tasks.
+ */
+void auth_xfer_delete(struct auth_xfer* xfr);
+
+/**
+ * Disown tasks from the xfr that belong to this worker.
+ * Only tasks for the worker in question, the comm point and timer
+ * delete functions need to run in the thread of that worker to be
+ * able to delete the callback from the event base.
+ * @param xfr: xfr structure
+ * @param worker: the worker for which to stop tasks.
+ */
+void xfr_disown_tasks(struct auth_xfer* xfr, struct worker* worker);
+
 #endif /* SERVICES_AUTHZONE_H */
index 3833c1a0927a3feb4377cb5246930b31dcc73be3..9c3e4de4382e289d5bb34b21e6b8b26ab6414909 100644 (file)
@@ -161,7 +161,7 @@ rate_deldatafunc(void* d, void* ATTR_UNUSED(arg))
 
 /** find or create element in domainlimit tree */
 static struct domain_limit_data* domain_limit_findcreate(
-       struct infra_cache* infra, char* name)
+       struct rbtree_type* domain_limits, char* name)
 {
        uint8_t* nm;
        int labs;
@@ -177,8 +177,8 @@ static struct domain_limit_data* domain_limit_findcreate(
        labs = dname_count_labels(nm);
 
        /* can we find it? */
-       d = (struct domain_limit_data*)name_tree_find(&infra->domain_limits,
-               nm, nmlen, labs, LDNS_RR_CLASS_IN);
+       d = (struct domain_limit_data*)name_tree_find(domain_limits, nm,
+               nmlen, labs, LDNS_RR_CLASS_IN);
        if(d) {
                free(nm);
                return d;
@@ -197,8 +197,8 @@ static struct domain_limit_data* domain_limit_findcreate(
        d->node.dclass = LDNS_RR_CLASS_IN;
        d->lim = -1;
        d->below = -1;
-       if(!name_tree_insert(&infra->domain_limits, &d->node, nm, nmlen,
-               labs, LDNS_RR_CLASS_IN)) {
+       if(!name_tree_insert(domain_limits, &d->node, nm, nmlen, labs,
+               LDNS_RR_CLASS_IN)) {
                log_err("duplicate element in domainlimit tree");
                free(nm);
                free(d);
@@ -208,19 +208,19 @@ static struct domain_limit_data* domain_limit_findcreate(
 }
 
 /** insert rate limit configuration into lookup tree */
-static int infra_ratelimit_cfg_insert(struct infra_cache* infra,
+static int infra_ratelimit_cfg_insert(struct rbtree_type* domain_limits,
        struct config_file* cfg)
 {
        struct config_str2list* p;
        struct domain_limit_data* d;
        for(p = cfg->ratelimit_for_domain; p; p = p->next) {
-               d = domain_limit_findcreate(infra, p->str);
+               d = domain_limit_findcreate(domain_limits, p->str);
                if(!d)
                        return 0;
                d->lim = atoi(p->str2);
        }
        for(p = cfg->ratelimit_below_domain; p; p = p->next) {
-               d = domain_limit_findcreate(infra, p->str);
+               d = domain_limit_findcreate(domain_limits, p->str);
                if(!d)
                        return 0;
                d->below = atoi(p->str2);
@@ -228,24 +228,21 @@ static int infra_ratelimit_cfg_insert(struct infra_cache* infra,
        return 1;
 }
 
-/** setup domain limits tree (0 on failure) */
-static int
-setup_domain_limits(struct infra_cache* infra, struct config_file* cfg)
+int
+setup_domain_limits(struct rbtree_type* domain_limits, struct config_file* cfg)
 {
-       name_tree_init(&infra->domain_limits);
-       if(!infra_ratelimit_cfg_insert(infra, cfg)) {
+       name_tree_init(domain_limits);
+       if(!infra_ratelimit_cfg_insert(domain_limits, cfg)) {
                return 0;
        }
-       name_tree_init_parents(&infra->domain_limits);
+       name_tree_init_parents(domain_limits);
        return 1;
 }
 
 /** find or create element in wait limit netblock tree */
 static struct wait_limit_netblock_info*
-wait_limit_netblock_findcreate(struct infra_cache* infra, char* str,
-       int cookie)
+wait_limit_netblock_findcreate(struct rbtree_type* tree, char* str)
 {
-       rbtree_type* tree;
        struct sockaddr_storage addr;
        int net;
        socklen_t addrlen;
@@ -257,10 +254,6 @@ wait_limit_netblock_findcreate(struct infra_cache* infra, char* str,
        }
 
        /* can we find it? */
-       if(cookie)
-               tree = &infra->wait_limits_cookie_netblock;
-       else
-               tree = &infra->wait_limits_netblock;
        d = (struct wait_limit_netblock_info*)addr_tree_find(tree, &addr,
                addrlen, net);
        if(d)
@@ -282,19 +275,21 @@ wait_limit_netblock_findcreate(struct infra_cache* infra, char* str,
 
 /** insert wait limit information into lookup tree */
 static int
-infra_wait_limit_netblock_insert(struct infra_cache* infra,
-       struct config_file* cfg)
+infra_wait_limit_netblock_insert(rbtree_type* wait_limits_netblock,
+        rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg)
 {
        struct config_str2list* p;
        struct wait_limit_netblock_info* d;
        for(p = cfg->wait_limit_netblock; p; p = p->next) {
-               d = wait_limit_netblock_findcreate(infra, p->str, 0);
+               d = wait_limit_netblock_findcreate(wait_limits_netblock,
+                       p->str);
                if(!d)
                        return 0;
                d->limit = atoi(p->str2);
        }
        for(p = cfg->wait_limit_cookie_netblock; p; p = p->next) {
-               d = wait_limit_netblock_findcreate(infra, p->str, 1);
+               d = wait_limit_netblock_findcreate(wait_limits_cookie_netblock,
+                       p->str);
                if(!d)
                        return 0;
                d->limit = atoi(p->str2);
@@ -302,16 +297,17 @@ infra_wait_limit_netblock_insert(struct infra_cache* infra,
        return 1;
 }
 
-/** setup wait limits tree (0 on failure) */
-static int
-setup_wait_limits(struct infra_cache* infra, struct config_file* cfg)
+int
+setup_wait_limits(rbtree_type* wait_limits_netblock,
+       rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg)
 {
-       addr_tree_init(&infra->wait_limits_netblock);
-       addr_tree_init(&infra->wait_limits_cookie_netblock);
-       if(!infra_wait_limit_netblock_insert(infra, cfg))
+       addr_tree_init(wait_limits_netblock);
+       addr_tree_init(wait_limits_cookie_netblock);
+       if(!infra_wait_limit_netblock_insert(wait_limits_netblock,
+               wait_limits_cookie_netblock, cfg))
                return 0;
-       addr_tree_init_parents(&infra->wait_limits_netblock);
-       addr_tree_init_parents(&infra->wait_limits_cookie_netblock);
+       addr_tree_init_parents(wait_limits_netblock);
+       addr_tree_init_parents(wait_limits_cookie_netblock);
        return 1;
 }
 
@@ -344,11 +340,12 @@ infra_create(struct config_file* cfg)
                return NULL;
        }
        /* insert config data into ratelimits */
-       if(!setup_domain_limits(infra, cfg)) {
+       if(!setup_domain_limits(&infra->domain_limits, cfg)) {
                infra_delete(infra);
                return NULL;
        }
-       if(!setup_wait_limits(infra, cfg)) {
+       if(!setup_wait_limits(&infra->wait_limits_netblock,
+               &infra->wait_limits_cookie_netblock, cfg)) {
                infra_delete(infra);
                return NULL;
        }
@@ -373,12 +370,29 @@ static void domain_limit_free(rbnode_type* n, void* ATTR_UNUSED(arg))
        }
 }
 
+void
+domain_limits_free(struct rbtree_type* domain_limits)
+{
+       if(!domain_limits)
+               return;
+       traverse_postorder(domain_limits, domain_limit_free, NULL);
+}
+
 /** delete wait_limit_netblock_info entries */
 static void wait_limit_netblock_del(rbnode_type* n, void* ATTR_UNUSED(arg))
 {
        free(n);
 }
 
+void
+wait_limits_free(struct rbtree_type* wait_limits_tree)
+{
+       if(!wait_limits_tree)
+               return;
+       traverse_postorder(wait_limits_tree, wait_limit_netblock_del,
+               NULL);
+}
+
 void 
 infra_delete(struct infra_cache* infra)
 {
@@ -386,12 +400,10 @@ infra_delete(struct infra_cache* infra)
                return;
        slabhash_delete(infra->hosts);
        slabhash_delete(infra->domain_rates);
-       traverse_postorder(&infra->domain_limits, domain_limit_free, NULL);
+       domain_limits_free(&infra->domain_limits);
        slabhash_delete(infra->client_ip_rates);
-       traverse_postorder(&infra->wait_limits_netblock,
-               wait_limit_netblock_del, NULL);
-       traverse_postorder(&infra->wait_limits_cookie_netblock,
-               wait_limit_netblock_del, NULL);
+       wait_limits_free(&infra->wait_limits_netblock);
+       wait_limits_free(&infra->wait_limits_cookie_netblock);
        free(infra);
 }
 
@@ -422,7 +434,7 @@ infra_adjust(struct infra_cache* infra, struct config_file* cfg)
                /* reapply domain limits */
                traverse_postorder(&infra->domain_limits, domain_limit_free,
                        NULL);
-               if(!setup_domain_limits(infra, cfg)) {
+               if(!setup_domain_limits(&infra->domain_limits, cfg)) {
                        infra_delete(infra);
                        return NULL;
                }
index 752a141a8dbf940f958b2f9aba43b637c1650dda..903048cb286ea6717a69766476561d9656d1be42 100644 (file)
@@ -515,6 +515,21 @@ void infra_wait_limit_inc(struct infra_cache* infra, struct comm_reply* rep,
 void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep,
        struct config_file* cfg);
 
+/** setup wait limits tree (0 on failure) */
+int setup_wait_limits(struct rbtree_type* wait_limits_netblock,
+       struct rbtree_type* wait_limits_cookie_netblock,
+       struct config_file* cfg);
+
+/** Free the wait limits and wait cookie limits tree. */
+void wait_limits_free(struct rbtree_type* wait_limits_tree);
+
+/** setup domain limits tree (0 on failure) */
+int setup_domain_limits(struct rbtree_type* domain_limits,
+       struct config_file* cfg);
+
+/** Free the domain limits tree. */
+void domain_limits_free(struct rbtree_type* domain_limits);
+
 /** exported for unit test */
 int still_useful_timeout();
 
index 4ff30fd266301e945a2c59e7471624c3977cf16c..9ea98c250907a03f2fc3e025e4febc62b1cfe9ab 100644 (file)
@@ -2220,3 +2220,35 @@ void local_zones_del_data(struct local_zones* zones,
 
        lock_rw_unlock(&z->lock);
 }
+
+/** Get memory usage for local_zone */
+static size_t
+local_zone_get_mem(struct local_zone* z)
+{
+       size_t m = sizeof(*z);
+       lock_rw_rdlock(&z->lock);
+       m += z->namelen + z->taglen + regional_get_mem(z->region);
+       lock_rw_unlock(&z->lock);
+       return m;
+}
+
+size_t local_zones_get_mem(struct local_zones* zones)
+{
+       struct local_zone* z;
+       size_t m;
+       if(!zones) return 0;
+       m = sizeof(*zones);
+       lock_rw_rdlock(&zones->lock);
+       RBTREE_FOR(z, struct local_zone*, &zones->ztree) {
+               m += local_zone_get_mem(z);
+       }
+       lock_rw_unlock(&zones->lock);
+       return m;
+}
+
+void local_zones_swap_tree(struct local_zones* zones, struct local_zones* data)
+{
+       rbtree_type oldtree = zones->ztree;
+       zones->ztree = data->ztree;
+       data->ztree = oldtree;
+}
index 6f0f28b12422b4d40aaa315a7c6fa790aea73abd..66102fd98f7e72de4bb7ff3978c6a8f8e51d5a68 100644 (file)
@@ -642,6 +642,20 @@ local_zone_enter_rr(struct local_zone* z, uint8_t* nm, size_t nmlen,
 struct local_data* 
 local_zone_find_data(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs);
 
+/** Get memory usage for local_zones tree. The routine locks and unlocks
+ * the tree for reading. */
+size_t local_zones_get_mem(struct local_zones* zones);
+
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param zones: the local zones structure.
+ * @param data: the data structure used to take elements from. This contains
+ *     the old elements on return.
+ */
+void local_zones_swap_tree(struct local_zones* zones,
+       struct local_zones* data);
+
 /** Enter a new zone; returns with WRlock
  *  Made public for unit testing
  *  @param zones: the local zones tree
index 2289277eaca548910d6d2bd210e300102b719a64..e41f1c92b6b84412dc058827f523ef43d879e6d4 100644 (file)
 #include <netdb.h>
 #endif
 
+/** Compare two views by name */
+static int
+view_name_compare(const char* v_a, const char* v_b)
+{
+       if(v_a == NULL && v_b == NULL)
+               return 0;
+       /* The NULL name is smaller than if the name is set. */
+       if(v_a == NULL)
+               return -1;
+       if(v_b == NULL)
+               return 1;
+       return strcmp(v_a, v_b);
+}
+
 /**
  * Compare two response-ip client info entries for the purpose of mesh state
  * compare.  It returns 0 if ci_a and ci_b are considered equal; otherwise
@@ -132,12 +146,14 @@ client_info_compare(const struct respip_client_info* ci_a,
        }
        if(ci_a->tag_datas != ci_b->tag_datas)
                return ci_a->tag_datas < ci_b->tag_datas ? -1 : 1;
-       if(ci_a->view != ci_b->view)
-               return ci_a->view < ci_b->view ? -1 : 1;
-       /* For the unbound daemon these should be non-NULL and identical,
-        * but we check that just in case. */
-       if(ci_a->respip_set != ci_b->respip_set)
-               return ci_a->respip_set < ci_b->respip_set ? -1 : 1;
+       if(ci_a->view || ci_a->view_name || ci_b->view || ci_b->view_name) {
+               /* Compare the views by name. */
+               cmp = view_name_compare(
+                       (ci_a->view?ci_a->view->name:ci_a->view_name),
+                       (ci_b->view?ci_b->view->name:ci_b->view_name));
+               if(cmp != 0)
+                       return cmp;
+       }
        return 0;
 }
 
@@ -870,6 +886,72 @@ void mesh_report_reply(struct mesh_area* mesh, struct outbound_entry* e,
        mesh_run(mesh, e->qstate->mesh_info, event, e);
 }
 
+/** copy strlist to region */
+static struct config_strlist*
+cfg_region_strlist_copy(struct regional* region, struct config_strlist* list)
+{
+       struct config_strlist* result = NULL, *last = NULL, *s = list;
+       while(s) {
+               struct config_strlist* n = regional_alloc_zero(region,
+                       sizeof(*n));
+               if(!n)
+                       return NULL;
+               n->str = regional_strdup(region, s->str);
+               if(!n->str)
+                       return NULL;
+               if(last)
+                       last->next = n;
+               else    result = n;
+               last = n;
+               s = s->next;
+       }
+       return result;
+}
+
+/** Copy the client info to the query region. */
+static struct respip_client_info*
+mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo)
+{
+       size_t i;
+       struct respip_client_info* client_info;
+       client_info = regional_alloc_init(region, cinfo, sizeof(*cinfo));
+       if(!client_info)
+               return NULL;
+       /* Copy the client_info so that if the configuration changes,
+        * then the data stays valid. */
+       client_info->taglist = regional_alloc_init(region, cinfo->taglist,
+               cinfo->taglen);
+       if(!client_info->taglist)
+               return NULL;
+       client_info->tag_actions = regional_alloc_init(region, cinfo->tag_actions,
+               cinfo->tag_actions_size);
+       if(!client_info->tag_actions)
+               return NULL;
+       client_info->tag_datas = regional_alloc_zero(region,
+               sizeof(struct config_strlist*)*cinfo->tag_datas_size);
+       if(!client_info->tag_datas)
+               return NULL;
+       for(i=0; i<cinfo->tag_datas_size; i++) {
+               if(cinfo->tag_datas[i]) {
+                       client_info->tag_datas[i] = cfg_region_strlist_copy(
+                               region, cinfo->tag_datas[i]);
+                       if(!client_info->tag_datas[i])
+                               return NULL;
+               }
+       }
+       if(cinfo->view) {
+               /* Do not copy the view pointer but store a name instead.
+                * The name is looked up later when done, this means that
+                * the view tree can be changed, by reloads. */
+               client_info->view = NULL;
+               client_info->view_name = regional_strdup(region,
+                       cinfo->view->name);
+               if(!client_info->view_name)
+                       return NULL;
+       }
+       return client_info;
+}
+
 struct mesh_state*
 mesh_state_create(struct module_env* env, struct query_info* qinfo,
        struct respip_client_info* cinfo, uint16_t qflags, int prime,
@@ -910,8 +992,7 @@ mesh_state_create(struct module_env* env, struct query_info* qinfo,
                return NULL;
        }
        if(cinfo) {
-               mstate->s.client_info = regional_alloc_init(region, cinfo,
-                       sizeof(*cinfo));
+               mstate->s.client_info = mesh_copy_client_info(region, cinfo);
                if(!mstate->s.client_info) {
                        alloc_reg_release(env->alloc, region);
                        return NULL;
@@ -1686,6 +1767,25 @@ struct mesh_state* mesh_area_find(struct mesh_area* mesh,
        return result;
 }
 
+/** remove mesh state callback */
+int mesh_state_del_cb(struct mesh_state* s, mesh_cb_func_type cb, void* cb_arg)
+{
+       struct mesh_cb* r, *prev = NULL;
+       r = s->cb_list;
+       while(r) {
+               if(r->cb == cb && r->cb_arg == cb_arg) {
+                       /* Delete this entry. */
+                       /* It was allocated in the s.region, so no free. */
+                       if(prev) prev->next = r->next;
+                       else s->cb_list = r->next;
+                       return 1;
+               }
+               prev = r;
+               r = r->next;
+       }
+       return 0;
+}
+
 int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns,
         sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg,
        uint16_t qid, uint16_t qflags)
@@ -2151,7 +2251,8 @@ apply_respip_action(struct module_qstate* qstate,
                return 1;
 
        if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, actinfo,
-               alias_rrset, 0, qstate->region, az, NULL))
+               alias_rrset, 0, qstate->region, az, NULL, qstate->env->views,
+               qstate->env->respip_set))
                return 0;
 
        /* xxx_deny actions mean dropping the reply, unless the original reply
@@ -2226,7 +2327,8 @@ mesh_serve_expired_callback(void* arg)
                } else if(partial_rep &&
                        !respip_merge_cname(partial_rep, &qstate->qinfo, msg->rep,
                        qstate->client_info, must_validate, &encode_rep, qstate->region,
-                       qstate->env->auth_zones)) {
+                       qstate->env->auth_zones, qstate->env->views,
+                       qstate->env->respip_set)) {
                        return;
                }
                if(!encode_rep || alias_rrset) {
@@ -2380,3 +2482,25 @@ int mesh_jostle_exceeded(struct mesh_area* mesh)
                return 0;
        return 1;
 }
+
+void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo,
+       uint16_t qflags, mesh_cb_func_type cb, void* cb_arg)
+{
+       struct mesh_state* s = NULL;
+       s = mesh_area_find(mesh, NULL, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0);
+       if(!s) return;
+       if(!mesh_state_del_cb(s, cb, cb_arg)) return;
+
+       /* It was in the list and removed. */
+       log_assert(mesh->num_reply_addrs > 0);
+       mesh->num_reply_addrs--;
+       if(!s->reply_list && !s->cb_list) {
+               /* was a reply state, not anymore */
+               log_assert(mesh->num_reply_states > 0);
+               mesh->num_reply_states--;
+       }
+       if(!s->reply_list && !s->cb_list &&
+               s->super_set.count == 0) {
+               mesh->num_detached_states++;
+       }
+}
index 0906ed9cf8dd965e6907104e31a627d631c3c1d2..0b01d4ef816e0ad884aab2299e81f178f9db0e9a 100644 (file)
@@ -706,4 +706,17 @@ int mesh_jostle_exceeded(struct mesh_area* mesh);
  */
 void mesh_respond_serve_expired(struct mesh_state* mstate);
 
+/**
+ * Remove callback from mesh. Removes the callback from the state.
+ * The state itself is left to run. Searches for the pointer values.
+ *
+ * @param mesh: the mesh.
+ * @param qinfo: query from client.
+ * @param qflags: flags from client query.
+ * @param cb: callback function.
+ * @param cb_arg: callback user arg.
+ */
+void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo,
+       uint16_t qflags, mesh_cb_func_type cb, void* cb_arg);
+
 #endif /* SERVICES_MESH_H */
index 6658e89b77b2b4dc7c699162d466593087a5591b..df39e75b0596a12e8f489921cd9f53d7ce807e94 100644 (file)
@@ -2792,3 +2792,31 @@ void rpz_disable(struct rpz* r)
         return;
     r->disabled = 1;
 }
+
+/** Get memory usage for clientip_synthesized_rrset. Ignores memory usage
+ * of locks. */
+static size_t
+rpz_clientip_synthesized_set_get_mem(struct clientip_synthesized_rrset* set)
+{
+       size_t m = sizeof(*set);
+       lock_rw_rdlock(&set->lock);
+       m += regional_get_mem(set->region);
+       lock_rw_unlock(&set->lock);
+       return m;
+}
+
+size_t rpz_get_mem(struct rpz* r)
+{
+       size_t m = sizeof(*r);
+       if(r->taglist)
+               m += r->taglistlen;
+       if(r->log_name)
+               m += strlen(r->log_name) + 1;
+       m += regional_get_mem(r->region);
+       m += local_zones_get_mem(r->local_zones);
+       m += local_zones_get_mem(r->nsdname_zones);
+       m += respip_set_get_mem(r->respip_set);
+       m += rpz_clientip_synthesized_set_get_mem(r->client_set);
+       m += rpz_clientip_synthesized_set_get_mem(r->ns_set);
+       return m;
+}
index 7f409087f7726498b89be0d7118544206d636d4a..6b5f17d1e2c1f09dd9327a892d8a3b43c3d2ccf9 100644 (file)
@@ -269,4 +269,11 @@ void rpz_enable(struct rpz* r);
  */
 void rpz_disable(struct rpz* r);
 
+/**
+ * Get memory usage of rpz. Caller must manage locks.
+ * @param r: RPZ struct.
+ * @return memory usage.
+ */
+size_t rpz_get_mem(struct rpz* r);
+
 #endif /* SERVICES_RPZ_H */
index 72f3643184ee4275375e7a28b19160dfa37a671c..44b07f3ed1b41dff3559836a029dfee580256723 100644 (file)
@@ -43,6 +43,7 @@
 #include "services/view.h"
 #include "services/localzone.h"
 #include "util/config_file.h"
+#include "respip/respip.h"
 
 int 
 view_cmp(const void* v1, const void* v2)
@@ -66,11 +67,6 @@ views_create(void)
        return v;
 }
 
-/* \noop (ignore this comment for doxygen)
- * This prototype is defined in in respip.h, but we want to avoid
- * unnecessary dependencies */
-void respip_set_delete(struct respip_set *set);
-
 void 
 view_delete(struct view* v)
 {
@@ -247,3 +243,38 @@ void views_print(struct views* v)
        /* TODO implement print */
        (void)v;
 }
+
+size_t views_get_mem(struct views* vs)
+{
+       struct view* v;
+       size_t m;
+       if(!vs) return 0;
+       m = sizeof(struct views);
+       lock_rw_rdlock(&vs->lock);
+       RBTREE_FOR(v, struct view*, &vs->vtree) {
+               m += view_get_mem(v);
+       }
+       lock_rw_unlock(&vs->lock);
+       return m;
+}
+
+size_t view_get_mem(struct view* v)
+{
+       size_t m = sizeof(*v);
+       lock_rw_rdlock(&v->lock);
+       m += getmem_str(v->name);
+       m += local_zones_get_mem(v->local_zones);
+       m += respip_set_get_mem(v->respip_set);
+       lock_rw_unlock(&v->lock);
+       return m;
+}
+
+void views_swap_tree(struct views* vs, struct views* data)
+{
+       rbnode_type* oldroot = vs->vtree.root;
+       size_t oldcount = vs->vtree.count;
+       vs->vtree.root = data->vtree.root;
+       vs->vtree.count = data->vtree.count;
+       data->vtree.root = oldroot;
+       data->vtree.count = oldcount;
+}
index 12f7a64e7171c3be58d8b2dfaa95649e43ebd8d6..0ff39ed6e75d82cd40319e5671b3fbffc9188954 100644 (file)
@@ -54,7 +54,8 @@ struct respip_set;
  * Views storage, shared.
  */
 struct views {
-       /** lock on the view tree */
+       /** lock on the view tree. When locking order, the views lock
+        * is before the forwards,hints,anchors,localzones lock. */
        lock_rw_type lock;
        /** rbtree of struct view */
        rbtree_type vtree;
@@ -135,4 +136,27 @@ void views_print(struct views* v);
  */
 struct view* views_find_view(struct views* vs, const char* name, int write);
 
+/**
+ * Calculate memory usage of views.
+ * @param vs: the views tree. The routine locks and unlocks the structure
+ *     for reading.
+ * @return memory in bytes.
+ */
+size_t views_get_mem(struct views* vs);
+
+/**
+ * Calculate memory usage of view.
+ * @param v: the view. The routine locks and unlocks the structure for reading.
+ * @return memory in bytes.
+ */
+size_t view_get_mem(struct view* v);
+
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * the locks.
+ * @param vs: views tree
+ * @param data: preallocated information.
+ */
+void views_swap_tree(struct views* vs, struct views* data);
+
 #endif /* SERVICES_VIEW_H */
index f274b9dbf17cd6503cb04ec43d7249678fdbb68a..dcbe6603028edc1040cfc899758f11ff081991c5 100644 (file)
@@ -109,6 +109,16 @@ usage(void)
        printf("                                That means the caches sizes and\n");
        printf("                                the number of threads must not\n");
        printf("                                change between reloads.\n");
+       printf("  fast_reload [+dpv]            reloads the server but only briefly stops\n");
+       printf("                                server processing, keeps cache, and changes\n");
+       printf("                                most options; check unbound-control(8).\n");
+       printf("                +d              drops running queries to keep consistency\n");
+       printf("                                on changed options while reloading.\n");
+       printf("                +p              does not pause threads for even faster\n");
+       printf("                                reload but less options are supported\n");
+       printf("                                ; check unbound-control(8).\n");
+       printf("                +v              verbose output, it will include duration needed.\n");
+       printf("                +vv             more verbose output, it will include memory needed.\n");
        printf("  stats                         print statistics\n");
        printf("  stats_noreset                 peek at statistics\n");
 #ifdef HAVE_SHMGET
index 1d71a0945154fe9f2ebc7ad6d7201cbcd3a0d8ef..92ebe386d2697d52806d89534660f676fc8f2c00 100644 (file)
@@ -256,6 +256,20 @@ void dtio_mainfdcallback(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
 }
 #endif
 
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
+       void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+       log_assert(0);
+       return 0;
+}
+
 #ifdef HAVE_NGTCP2
 void doq_client_event_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
        void* ATTR_UNUSED(arg))
index fdc1b8af16dea0535d482a85f795a188cd3c6049..93bbf70f4af210f1939bf4276c024d721e9f187b 100644 (file)
@@ -64,6 +64,9 @@ static int key_deleted = 0;
 static ub_thread_key_type thr_debug_key;
 /** the list of threads, so all threads can be examined. NULL if unused. */
 static struct thr_check* thread_infos[THRDEBUG_MAX_THREADS];
+/** stored maximum lock number for threads, when a thread is restarted the
+ * number is kept track of, because the new locks get new id numbers. */
+static int thread_lockcount[THRDEBUG_MAX_THREADS];
 /** do we check locking order */
 int check_locking_order = 1;
 /** the pid of this runset, reasonably unique. */
@@ -698,10 +701,20 @@ open_lockorder(struct thr_check* thr)
        char buf[24];
        time_t t;
        snprintf(buf, sizeof(buf), "%s.%d", output_name, thr->num);
-       thr->order_info = fopen(buf, "w");
-       if(!thr->order_info)
-               fatal_exit("could not open %s: %s", buf, strerror(errno));
-       thr->locks_created = 0;
+       thr->locks_created = thread_lockcount[thr->num];
+       if(thr->locks_created == 0) {
+               thr->order_info = fopen(buf, "w");
+               if(!thr->order_info)
+                       fatal_exit("could not open %s: %s", buf, strerror(errno));
+       } else {
+               /* There is already a file to append on with the previous
+                * thread information. */
+               thr->order_info = fopen(buf, "a");
+               if(!thr->order_info)
+                       fatal_exit("could not open for append %s: %s", buf, strerror(errno));
+               return;
+       }
+
        t = time(NULL);
        /* write: <time_stamp> <runpid> <thread_num> */
        if(fwrite(&t, sizeof(t), 1, thr->order_info) != 1 ||
@@ -728,6 +741,7 @@ static void* checklock_main(void* arg)
        if(check_locking_order)
                open_lockorder(thr);
        ret = thr->func(thr->arg);
+       thread_lockcount[thr->num] = thr->locks_created;
        thread_infos[thr->num] = NULL;
        if(check_locking_order)
                fclose(thr->order_info);
index 1a2fd418359bc4235d72be357279ebf5fe20fc9f..e6f63a761f357e0b55bc8da27e620efe6e33093c 100644 (file)
@@ -2699,3 +2699,17 @@ void dtio_mainfdcallback(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
        log_assert(0);
 }
 #endif
+
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
+       void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+       log_assert(0);
+       return 0;
+}
index 48843f1db80237aae4c5471760eefe42055c58d6..f7f3210790ebddd1cf81bea984062b7dcaaf3847 100644 (file)
@@ -1484,6 +1484,11 @@ size_t comm_point_get_mem(struct comm_point* ATTR_UNUSED(c))
        return 0;
 }
 
+size_t comm_timer_get_mem(struct comm_timer* ATTR_UNUSED(timer))
+{
+       return 0;
+}
+
 size_t serviced_get_mem(struct serviced_query* ATTR_UNUSED(c))
 {
        return 0;
@@ -1994,4 +1999,24 @@ void http2_stream_remove_mesh_state(struct http2_stream* ATTR_UNUSED(h2_stream))
 {
 }
 
+void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(event),
+       void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void fast_reload_thread_stop(
+       struct fast_reload_thread* ATTR_UNUSED(fast_reload_thread))
+{
+       /* nothing */
+}
+
+int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c),
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+       log_assert(0);
+       return 0;
+}
+
 /*********** End of Dummy routines ***********/
index f9ea86e699ca1a03a54263e2730821f9a556e125..6da4ceaf2ebf0bf902f68b6949dd20a1d1bb0009 100644 (file)
@@ -601,6 +601,17 @@ void listen_desetup_locks(void)
        /* nothing */
 }
 
+void fast_reload_printq_list_delete(
+       struct fast_reload_printq* ATTR_UNUSED(list))
+{
+       /* nothing */
+}
+
+void fast_reload_worker_pickup_changes(struct worker* ATTR_UNUSED(worker))
+{
+       /* nothing */
+}
+
 #ifdef HAVE_NGTCP2
 void* quic_sslctx_create(char* ATTR_UNUSED(key), char* ATTR_UNUSED(pem),
        char* ATTR_UNUSED(verifypem))
diff --git a/testdata/fast_reload_fwd.tdir/auth1.zone b/testdata/fast_reload_fwd.tdir/auth1.zone
new file mode 100644 (file)
index 0000000..b6b551a
--- /dev/null
@@ -0,0 +1,2 @@
+@ SOA ns root 1 3600 300 7200 3600
+www A 1.2.3.4
diff --git a/testdata/fast_reload_fwd.tdir/auth2.zone b/testdata/fast_reload_fwd.tdir/auth2.zone
new file mode 100644 (file)
index 0000000..fc59810
--- /dev/null
@@ -0,0 +1,2 @@
+@ SOA ns root 1 3600 300 7200 3600
+www A 1.2.3.5
diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf
new file mode 100644 (file)
index 0000000..dca7634
--- /dev/null
@@ -0,0 +1,107 @@
+server:
+       verbosity: 4
+       num-threads: 1
+       interface: 127.0.0.1
+       port: @PORT@
+       use-syslog: no
+       directory: ""
+       pidfile: "unbound.pid"
+       chroot: ""
+       username: ""
+       do-not-query-localhost: no
+       trust-anchor: "ta1.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af"
+       trust-anchor: "ta2.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af"
+       trust-anchor: "ta3.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af"
+       domain-insecure: "insec1.ta1.example.com"
+       domain-insecure: "insec2.ta1.example.com"
+       domain-insecure: "insec3.ta1.example.com"
+
+forward-zone:
+       name: "."
+       forward-addr: "127.0.0.1@12345"
+
+remote-control:
+       control-enable: yes
+       control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
+       control-use-cert: no
+
+forward-zone:
+       name: "example1.org"
+       forward-addr: "127.0.0.1@@NS1_PORT@"
+
+forward-zone:
+       name: "example2.org"
+       forward-addr: "127.0.0.1@@NS1_PORT@"
+
+forward-zone:
+       name: "example3.org"
+       forward-addr: "127.0.0.1@@NS1_PORT@"
+
+forward-zone:
+       name: "example4.org"
+       forward-addr: "127.0.0.1@@NS2_PORT@"
+
+forward-zone:
+       name: "example5.org"
+       forward-addr: "127.0.0.1@@NS2_PORT@"
+
+forward-zone:
+       name: "example6.org"
+       forward-addr: "127.0.0.1@@NS2_PORT@"
+
+stub-zone:
+       name: "stub1.org"
+       stub-addr: "127.0.0.1@@NS1_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub2.org"
+       stub-addr: "127.0.0.1@@NS1_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub3.org"
+       stub-addr: "127.0.0.1@@NS1_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub4.org"
+       stub-addr: "127.0.0.1@@NS2_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub5.org"
+       stub-addr: "127.0.0.1@@NS2_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub6.org"
+       stub-addr: "127.0.0.1@@NS2_PORT@"
+       stub-prime: no
+
+auth-zone:
+       name: "auth1.org"
+       zonefile: "auth1.zone"
+
+auth-zone:
+       name: "auth2.org"
+       zonefile: "auth1.zone"
+
+auth-zone:
+       name: "auth3.org"
+       zonefile: "auth1.zone"
+
+auth-zone:
+       name: "auth5.org"
+       zonefile: "auth5.zone"
+       primary: 127.0.0.1@@NS1_PORT@
+
+auth-zone:
+       name: "auth6.org"
+       zonefile: "auth6.zone"
+       primary: 127.0.0.1@@NS1_PORT@
+
+auth-zone:
+       name: "auth7.org"
+       zonefile: "auth7.zone"
+       primary: 127.0.0.1@@NS1_PORT@
diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf2 b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf2
new file mode 100644 (file)
index 0000000..dbe6e4f
--- /dev/null
@@ -0,0 +1,108 @@
+server:
+       verbosity: 4
+       num-threads: 1
+       interface: 127.0.0.1
+       port: @PORT@
+       use-syslog: no
+       directory: ""
+       pidfile: "unbound.pid"
+       chroot: ""
+       username: ""
+       do-not-query-localhost: no
+       trust-anchor: "ta1.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af"
+       trust-anchor: "ta3.example.com DS 55567 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af"
+       trust-anchor: "ta4.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af"
+       domain-insecure: "insec1.ta1.example.com"
+       domain-insecure: "insec3.ta1.example.com"
+       domain-insecure: "insec4.ta1.example.com"
+
+forward-zone:
+       name: "."
+       # No addresses makes the server return SERVFAIL for deleted zones.
+       #forward-addr: "127.0.0.1@12345"
+
+remote-control:
+       control-enable: yes
+       control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
+       control-use-cert: no
+
+forward-zone:
+       name: "example1.org"
+       forward-addr: "127.0.0.1@@NS2_PORT@"
+
+forward-zone:
+       name: "example2.org"
+       forward-addr: "127.0.0.1@@NS1_PORT@"
+
+forward-zone:
+       name: "example3.org"
+       forward-addr: "127.0.0.1@@NS2_PORT@"
+
+forward-zone:
+       name: "example4.org"
+       forward-addr: "127.0.0.1@@NS1_PORT@"
+
+forward-zone:
+       name: "example5.org"
+       forward-addr: "127.0.0.1@@NS2_PORT@"
+
+forward-zone:
+       name: "example6.org"
+       forward-addr: "127.0.0.1@@NS1_PORT@"
+
+stub-zone:
+       name: "stub1.org"
+       stub-addr: "127.0.0.1@@NS2_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub2.org"
+       stub-addr: "127.0.0.1@@NS1_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub3.org"
+       stub-addr: "127.0.0.1@@NS2_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub4.org"
+       stub-addr: "127.0.0.1@@NS1_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub5.org"
+       stub-addr: "127.0.0.1@@NS2_PORT@"
+       stub-prime: no
+
+stub-zone:
+       name: "stub6.org"
+       stub-addr: "127.0.0.1@@NS1_PORT@"
+       stub-prime: no
+
+auth-zone:
+       name: "auth1.org"
+       zonefile: "auth1.zone"
+
+auth-zone:
+       name: "auth3.org"
+       zonefile: "auth2.zone"
+
+auth-zone:
+       name: "auth4.org"
+       zonefile: "auth2.zone"
+
+auth-zone:
+       name: "auth5.org"
+       zonefile: "auth5.zone"
+       primary: 127.0.0.1@@NS1_PORT@
+
+auth-zone:
+       name: "auth7.org"
+       zonefile: "auth7.zone"
+       primary: 127.0.0.1@@NS2_PORT@
+
+auth-zone:
+       name: "auth8.org"
+       zonefile: "auth8.zone"
+       primary: 127.0.0.1@@NS1_PORT@
diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.dsc b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.dsc
new file mode 100644 (file)
index 0000000..422cdee
--- /dev/null
@@ -0,0 +1,16 @@
+BaseName: fast_reload_fwd
+Version: 1.0
+Description: Test fast reload change of forwards and stubs.
+CreationDate: Thu Jan 22 11:55:55 CET 2024
+Maintainer: dr. W.C.A. Wijngaards
+Category:
+Component:
+CmdDepends:
+Depends:
+Help:
+Pre: fast_reload_fwd.pre
+Post: fast_reload_fwd.post
+Test: fast_reload_fwd.test
+AuxFiles:
+Passed:
+Failure:
diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns1 b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns1
new file mode 100644 (file)
index 0000000..d964441
--- /dev/null
@@ -0,0 +1,339 @@
+; match A records and return a reply indicating it is this server.
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example1.org. IN A
+SECTION ANSWER
+www.example1.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example2.org. IN A
+SECTION ANSWER
+www.example2.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example3.org. IN A
+SECTION ANSWER
+www.example3.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example4.org. IN A
+SECTION ANSWER
+www.example4.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example5.org. IN A
+SECTION ANSWER
+www.example5.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example6.org. IN A
+SECTION ANSWER
+www.example6.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example1.org. IN A
+SECTION ANSWER
+www2.example1.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example2.org. IN A
+SECTION ANSWER
+www2.example2.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example3.org. IN A
+SECTION ANSWER
+www2.example3.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example4.org. IN A
+SECTION ANSWER
+www2.example4.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example5.org. IN A
+SECTION ANSWER
+www2.example5.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example6.org. IN A
+SECTION ANSWER
+www2.example6.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub1.org. IN A
+SECTION ANSWER
+www.stub1.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub2.org. IN A
+SECTION ANSWER
+www.stub2.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub3.org. IN A
+SECTION ANSWER
+www.stub3.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub4.org. IN A
+SECTION ANSWER
+www.stub4.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub5.org. IN A
+SECTION ANSWER
+www.stub5.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub6.org. IN A
+SECTION ANSWER
+www.stub6.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub1.org. IN A
+SECTION ANSWER
+www2.stub1.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub2.org. IN A
+SECTION ANSWER
+www2.stub2.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub3.org. IN A
+SECTION ANSWER
+www2.stub3.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub4.org. IN A
+SECTION ANSWER
+www2.stub4.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub5.org. IN A
+SECTION ANSWER
+www2.stub5.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub6.org. IN A
+SECTION ANSWER
+www2.stub6.org. IN A 1.2.3.1
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth5.org. IN SOA
+SECTION ANSWER
+auth5.org. SOA ns root 1 3600 300 7200 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth5.org. IN AXFR
+SECTION ANSWER
+auth5.org. SOA ns root 1 3600 300 7200 3600
+www.auth5.org. A 1.2.3.4
+auth5.org. SOA ns root 1 3600 300 7200 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth6.org. IN SOA
+SECTION ANSWER
+auth6.org. SOA ns root 1 3600 300 7200 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth6.org. IN AXFR
+SECTION ANSWER
+auth6.org. SOA ns root 1 3600 300 7200 3600
+www.auth6.org. A 1.2.3.4
+auth6.org. SOA ns root 1 3600 300 7200 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth7.org. IN SOA
+SECTION ANSWER
+auth7.org. SOA ns root 1 3600 300 7200 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth7.org. IN AXFR
+SECTION ANSWER
+auth7.org. SOA ns root 1 3600 300 7200 3600
+www.auth7.org. A 1.2.3.4
+auth7.org. SOA ns root 1 3600 300 7200 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth8.org. IN SOA
+SECTION ANSWER
+auth8.org. SOA ns root 1 3600 300 7200 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth8.org. IN AXFR
+SECTION ANSWER
+auth8.org. SOA ns root 1 3600 300 7200 3600
+www.auth8.org. A 1.2.3.4
+auth8.org. SOA ns root 1 3600 300 7200 3600
+ENTRY_END
+
+; match anything and return a reply
+ENTRY_BEGIN
+MATCH opcode
+ADJUST copy_id copy_query
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.org. IN SOA
+SECTION AUTHORITY
+example.org. IN SOA ns1.example.org. hostmaster.example.org. 1 3600 900 86400 3600
+ENTRY_END
diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns2 b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns2
new file mode 100644 (file)
index 0000000..8e7eb60
--- /dev/null
@@ -0,0 +1,285 @@
+; match A records and return a reply indicating it is this server.
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example1.org. IN A
+SECTION ANSWER
+www.example1.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example2.org. IN A
+SECTION ANSWER
+www.example2.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example3.org. IN A
+SECTION ANSWER
+www.example3.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example4.org. IN A
+SECTION ANSWER
+www.example4.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example5.org. IN A
+SECTION ANSWER
+www.example5.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example6.org. IN A
+SECTION ANSWER
+www.example6.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example1.org. IN A
+SECTION ANSWER
+www2.example1.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example2.org. IN A
+SECTION ANSWER
+www2.example2.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example3.org. IN A
+SECTION ANSWER
+www2.example3.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example4.org. IN A
+SECTION ANSWER
+www2.example4.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example5.org. IN A
+SECTION ANSWER
+www2.example5.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.example6.org. IN A
+SECTION ANSWER
+www2.example6.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub1.org. IN A
+SECTION ANSWER
+www.stub1.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub2.org. IN A
+SECTION ANSWER
+www.stub2.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub3.org. IN A
+SECTION ANSWER
+www.stub3.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub4.org. IN A
+SECTION ANSWER
+www.stub4.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub5.org. IN A
+SECTION ANSWER
+www.stub5.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.stub6.org. IN A
+SECTION ANSWER
+www.stub6.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub1.org. IN A
+SECTION ANSWER
+www2.stub1.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub2.org. IN A
+SECTION ANSWER
+www2.stub2.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub3.org. IN A
+SECTION ANSWER
+www2.stub3.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub4.org. IN A
+SECTION ANSWER
+www2.stub4.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub5.org. IN A
+SECTION ANSWER
+www2.stub5.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www2.stub6.org. IN A
+SECTION ANSWER
+www2.stub6.org. IN A 1.2.3.2
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth7.org. IN SOA
+SECTION ANSWER
+auth7.org. SOA ns root 2 3600 300 7200 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth7.org. IN AXFR
+SECTION ANSWER
+auth7.org. SOA ns root 2 3600 300 7200 3600
+www.auth7.org. A 1.2.3.5
+auth7.org. SOA ns root 2 3600 300 7200 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+auth7.org. IN IXFR
+SECTION ANSWER
+auth7.org. SOA ns root 2 3600 300 7200 3600
+www.auth7.org. A 1.2.3.5
+auth7.org. SOA ns root 2 3600 300 7200 3600
+ENTRY_END
+
+; match anything and return a reply
+ENTRY_BEGIN
+MATCH opcode
+ADJUST copy_id copy_query
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.org. IN SOA
+SECTION AUTHORITY
+example.org. IN SOA ns1.example.org. hostmaster.example.org. 1 3600 900 86400 3600
+ENTRY_END
diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post
new file mode 100644 (file)
index 0000000..e7e644b
--- /dev/null
@@ -0,0 +1,25 @@
+# #-- fast_reload_fwd.post --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# source the test var file when it's there
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+#
+# do your teardown here
+PRE="../.."
+. ../common.sh
+kill_pid $NS1_PID
+kill_pid $NS2_PID
+if test -f unbound.pid; then
+       kill_pid $UNBOUND_PID
+fi
+rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
+echo
+echo "> ns1.log"
+cat ns1.log
+echo
+echo "> ns2.log"
+cat ns2.log
+echo
+echo "> unbound.log"
+cat unbound.log
+exit 0
diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.pre b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.pre
new file mode 100644 (file)
index 0000000..42e680d
--- /dev/null
@@ -0,0 +1,56 @@
+# #-- fast_reload_fwd.pre--#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+# if no threads; exit
+if grep -e "define HAVE_PTHREAD 1" -e "define HAVE_SOLARIS_THREADS 1" -e "define HAVE_WINDOWS_THREADS 1" $PRE/config.h; then
+       echo "have threads"
+else
+       skip_test "no threads"
+fi
+if grep -e "define ENABLE_LOCK_CHECKS 1" $PRE/config.h; then
+       get_make
+       echo "> (cd $PRE ; $MAKE lock-verify)"
+       (cd $PRE ; $MAKE lock-verify)
+fi
+
+get_random_port 3
+UNBOUND_PORT=$RND_PORT
+NS1_PORT=$(($RND_PORT + 1))
+NS2_PORT=$(($RND_PORT + 2))
+echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
+echo "NS1_PORT=$NS1_PORT" >> .tpkg.var.test
+echo "NS2_PORT=$NS2_PORT" >> .tpkg.var.test
+
+# make config files
+CONTROL_PATH=/tmp
+CONTROL_PID=$$
+sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@NS1_PORT\@/'$NS1_PORT'/' -e 's/@NS2_PORT\@/'$NS2_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < fast_reload_fwd.conf > ub.conf
+sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@NS1_PORT\@/'$NS1_PORT'/' -e 's/@NS2_PORT\@/'$NS2_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < fast_reload_fwd.conf2 > ub.conf2
+
+# start forwarders
+get_ldns_testns
+$LDNS_TESTNS -p $NS1_PORT fast_reload_fwd.ns1 >ns1.log 2>&1 &
+NS1_PID=$!
+echo "NS1_PID=$NS1_PID" >> .tpkg.var.test
+
+$LDNS_TESTNS -p $NS2_PORT fast_reload_fwd.ns2 >ns2.log 2>&1 &
+NS2_PID=$!
+echo "NS2_PID=$NS2_PID" >> .tpkg.var.test
+
+# start unbound in the background
+PRE="../.."
+$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
+UNBOUND_PID=$!
+echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
+echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
+echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
+
+cat .tpkg.var.test
+wait_ldns_testns_up ns1.log
+wait_ldns_testns_up ns2.log
+wait_unbound_up unbound.log
diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.test b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.test
new file mode 100644 (file)
index 0000000..9248593
--- /dev/null
@@ -0,0 +1,320 @@
+# #-- fast_reload_fwd.test --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+
+echo "> unbound-control status"
+$PRE/unbound-control -c ub.conf status
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+else
+       echo "exit value: OK"
+fi
+
+# test that the forwards and stubs point to the right upstream.
+for x in example1.org example2.org example3.org stub1.org stub2.org stub3.org; do
+       echo ""
+       echo "dig www.$x [upstream is NS1]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile
+       if grep "1.2.3.1" outfile; then
+               echo "response OK"
+       else
+               echo "www.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+for x in example4.org example5.org example6.org stub4.org stub5.org stub6.org; do
+       echo ""
+       echo "dig www.$x [upstream is NS2]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile
+       if grep "1.2.3.2" outfile; then
+               echo "response OK"
+       else
+               echo "www.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+for x in auth1.org auth2.org auth3.org auth5.org auth6.org auth7.org; do
+       echo ""
+       echo "dig www.$x [auth is 1.2.3.4]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile
+       if grep "1.2.3.4" outfile; then
+               echo "response OK"
+       else
+               echo "www.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+echo ""
+echo "> list_insecure"
+$PRE/unbound-control -c ub.conf list_insecure 2>&1 | tee output
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+fi
+if grep "insec1.ta1.example.com" output >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+if grep "insec2.ta1.example.com" output >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+if grep "insec3.ta1.example.com" output >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+echo ""
+echo "> trustanchor.unbound"
+dig @127.0.0.1 -p $UNBOUND_PORT trustanchor.unbound CH TXT 2>&1 | tee outfile
+if grep "ta1.example.com. 55566" outfile >/dev/null; then :; else
+       echo "wrong output ta1"
+       exit 1
+fi
+if grep "ta2.example.com. 55566" outfile >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+if grep "ta3.example.com. 55566" outfile >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+
+echo ""
+echo "> replace config file ub.conf"
+mv ub.conf ub.conf.orig
+mv ub.conf2 ub.conf
+echo ""
+echo "> unbound-control fast_reload"
+$PRE/unbound-control -c ub.conf fast_reload +vv 2>&1 | tee output
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+else
+       echo "exit value: OK"
+fi
+
+# for the previous digs to www.x the cached value should remain the same
+# but for new lookups, to www2.x the new upstream should be used.
+for x in example1.org example2.org example3.org stub1.org stub2.org stub3.org; do
+       echo ""
+       echo "dig www.$x [upstream is NS1]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile
+       if grep "1.2.3.1" outfile; then
+               echo "response OK"
+       else
+               echo "www.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+for x in example4.org example5.org example6.org stub4.org stub5.org stub6.org; do
+       echo ""
+       echo "dig www.$x [upstream is NS2]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile
+       if grep "1.2.3.2" outfile; then
+               echo "response OK"
+       else
+               echo "www.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+# new lookups for www2 go to the upstream.
+for x in example2.org example4.org example6.org stub2.org stub4.org stub6.org; do
+       echo ""
+       echo "dig www2.$x [upstream is NS1]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www2.$x A 2>&1 | tee outfile
+       if grep "1.2.3.1" outfile; then
+               echo "response OK"
+       else
+               echo "www2.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+for x in example1.org example3.org example5.org stub1.org stub3.org stub5.org; do
+       echo ""
+       echo "dig www2.$x [upstream is NS2]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www2.$x A 2>&1 | tee outfile
+       if grep "1.2.3.2" outfile; then
+               echo "response OK"
+       else
+               echo "www2.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+# auth is unchanged, or at ns1.
+for x in auth1.org auth5.org auth8.org; do
+       echo ""
+       echo "dig www.$x [auth is 1.2.3.4]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile
+       if grep "1.2.3.4" outfile; then
+               echo "response OK"
+       else
+               echo "www.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+# deleted auth
+for x in auth2.org auth6.org; do
+       echo ""
+       echo "dig www.$x [auth is deleted]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile
+       if grep "SERVFAIL" outfile; then
+               echo "response OK"
+       else
+               echo "www.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+# changed and added auth
+for x in auth3.org auth4.org auth7.org; do
+       echo ""
+       echo "dig www.$x [auth is 1.2.3.5]"
+       dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile
+       if grep "1.2.3.5" outfile; then
+               echo "response OK"
+       else
+               echo "www.$x got the wrong answer"
+               exit 1
+       fi
+done
+
+echo ""
+echo "> list_insecure"
+$PRE/unbound-control -c ub.conf list_insecure 2>&1 | tee output
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+fi
+if grep "insec1.ta1.example.com" output >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+if grep "insec2.ta1.example.com" output >/dev/null; then
+       echo "wrong output"
+       exit 1
+fi
+if grep "insec3.ta1.example.com" output >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+if grep "insec4.ta1.example.com" output >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+echo ""
+echo "> trustanchor.unbound"
+dig @127.0.0.1 -p $UNBOUND_PORT trustanchor.unbound CH TXT 2>&1 | tee outfile
+if grep "ta1.example.com. 55566" outfile >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+if grep "ta2.example.com. 55566" outfile >/dev/null; then
+       echo "wrong output"
+       exit 1
+fi
+if grep "ta3.example.com. 55566" outfile >/dev/null; then
+       echo "wrong output"
+       exit 1
+fi
+if grep "ta3.example.com. 55567" outfile >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+if grep "ta4.example.com. 55566" outfile >/dev/null; then :; else
+       echo "wrong output"
+       exit 1
+fi
+
+echo ""
+echo "> test change: add tag1 tag2"
+cp ub.conf ub.conf.orig2
+echo "server:" >> ub.conf
+echo ' define-tag: "tag1 tag2"' >> ub.conf
+echo "> unbound-control fast_reload"
+$PRE/unbound-control -c ub.conf fast_reload +vv 2>&1 | tee output
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+else
+       echo "exit value: OK"
+fi
+
+echo ""
+echo "> test change: change to tag2 tag3"
+cp ub.conf.orig2 ub.conf
+echo "server:" >> ub.conf
+echo ' define-tag: "tag2 tag3"' >> ub.conf
+echo "> unbound-control fast_reload"
+$PRE/unbound-control -c ub.conf fast_reload +vv 2>&1 | tee output
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+else
+       echo "exit value: OK"
+fi
+if grep "tags have changed" output; then
+       echo "output OK"
+else
+       echo "wrong output"
+       exit 1
+fi
+
+echo ""
+echo "> test change: change cache size"
+cp ub.conf.orig2 ub.conf
+echo "server:" >> ub.conf
+echo " msg-cache-size: 10m" >> ub.conf
+echo " rrset-cache-size: 5m" >> ub.conf
+echo "> unbound-control fast_reload"
+$PRE/unbound-control -c ub.conf fast_reload +vv 2>&1 | tee output
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+else
+       echo "exit value: OK"
+fi
+
+echo ""
+echo "> test change: change nothing, +p too"
+$PRE/unbound-control -c ub.conf fast_reload +vv +p 2>&1 | tee output
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+else
+       echo "exit value: OK"
+fi
+
+echo ""
+echo "> stop unbound"
+kill_pid $UNBOUND_PID
+if test -f unbound.pid; then sleep 1; fi
+if test -f unbound.pid; then sleep 1; fi
+if test -f unbound.pid; then sleep 1; fi
+if test -f unbound.pid; then echo "unbound.pid still there"; fi
+# check the locks.
+function locktest() {
+       if test -x $PRE/lock-verify -a -f ublocktrace.0; then
+               $PRE/lock-verify ublocktrace.*
+               if test $? -ne 0; then
+                       echo "lock-verify error"
+                       exit 1
+               fi
+       fi
+}
+locktest
+
+exit 0
diff --git a/testdata/fast_reload_most_options.tdir/auth.nlnetlabs.nl.zone b/testdata/fast_reload_most_options.tdir/auth.nlnetlabs.nl.zone
new file mode 100644 (file)
index 0000000..55b8d34
--- /dev/null
@@ -0,0 +1,3 @@
+$ORIGIN auth.nlnetlabs.nl.
+$TTL 60
+@ IN SOA a b 1 2 3 4 5
diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.conf b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.conf
new file mode 100644 (file)
index 0000000..eda3d67
--- /dev/null
@@ -0,0 +1,143 @@
+# Try to define values for options that don't have "default" options that would
+# trigger fast-reload functionality.
+server:
+       verbosity: 4
+       num-threads: 4
+       interface: 127.0.0.1
+       interface: lo
+       port: @PORT@
+       interface-action: lo allow
+       use-syslog: no
+       directory: ""
+       pidfile: "unbound.pid"
+       chroot: ""
+       username: ""
+       do-not-query-localhost: no
+
+       module-config: "respip validator iterator"
+
+       outgoing-interface: 127.0.0.1
+       outgoing-port-avoid: "3200-3208"
+
+       define-tag: "tag1 tag2 tag3"
+
+       do-nat64: yes
+       nat64-prefix: 64:ff9b::0/96
+       dns64-prefix: 64:ff9b::0/96
+       dns64-ignore-aaaa: "ignore-aaaa.nlnetlabs.nl"
+
+       edns-tcp-keepalive: yes
+
+       response-ip: 192.0.2.0 always_refuse
+       access-control: 127.0.0.0/8 allow
+       access-control: ::1 allow
+       access-control-tag: 192.0.2.0/24 "tag2 tag3"
+       interface-tag: lo "tag2 tag3"
+       access-control-tag-action: 192.0.2.0/24 tag3 always_refuse
+       interface-tag-action: lo tag3 always_refuse
+       access-control-tag-data: 192.0.2.0/24 tag2 "A 127.0.0.1"
+       interface-tag-data: lo tag2 "A 127.0.0.1"
+       access-control-view: 192.0.2.0/24 viewname
+       interface-view: lo viewname
+
+       nsid: "ascii_something"
+
+       http-user-agent: "httpuseragent"
+
+       caps-exempt: "nlnetlabs.nl"
+
+       private-address: 10.0.0.0/8
+       private-address: 172.16.0.0/12
+       private-address: 192.168.0.0/16
+       private-address: 169.254.0.0/16
+       private-address: fd00::/8
+       private-address: fe80::/10
+       private-address: ::ffff:0:0/96
+
+       private-domain: "nlnetlabs.nl"
+
+       unwanted-reply-threshold: 10000000
+
+       do-not-query-address: 1.1.1.1
+       do-not-query-address: 8.8.8.8
+       do-not-query-address: 9.9.9.9
+
+       do-not-query-localhost: no
+
+       trust-anchor: "jelte.nlnetlabs.nl. DS 42860 5 1 14D739EB566D2B1A5E216A0BA4D17FA9B038BE4A"
+
+       domain-insecure: "nlnetlabs.nl"
+
+       serve-expired: yes
+       serve-expired-client-timeout: 1800
+
+       val-log-level: 2
+
+       local-zone: refuse.nlnetlabs.nl. refuse
+       local-zone: override.nlnetlabs.nl. deny
+       local-zone: tag.nlnetlabs.nl. transparent
+       local-data: "data.nlnetlabs.nl. TXT localdata"
+       local-data-ptr: "192.0.2.3 reverse.nlnetlabs.nl."
+       local-zone-tag: "tag.nlnetlabs.nl" "tag2 tag3"
+       local-zone-override: "override.nlnetlabs.nl" 192.0.2.0/24 refuse
+
+
+       ratelimit: 100
+       ratelimit-below-domain: ratelimit.nlnetlabs.nl 1000
+       ip-ratelimit: 100
+
+       tcp-connection-limit: 192.0.2.0/24 12
+
+       answer-cookie: yes
+       cookie-secret:  "000102030405060708090a0b0c0d0e0f"
+
+       ede: yes
+       ede-serve-expired: yes
+
+remote-control:
+       control-enable: yes
+       control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
+       control-use-cert: no
+
+stub-zone:
+       name: "stub.nlnetlabs.nl"
+       stub-addr: 192.0.2.68
+       stub-prime: no
+       stub-first: no
+       stub-tcp-upstream: no
+       stub-tls-upstream: no
+       stub-no-cache: no
+
+forward-zone:
+       name: "forward.nlnetlabs.nl"
+       forward-addr: 192.0.2.68
+       forward-first: no
+       forward-tcp-upstream: no
+       forward-tls-upstream: no
+       forward-no-cache: no
+
+auth-zone:
+       name: "auth.nlnetlabs.nl"
+       for-downstream: yes
+       for-upstream: yes
+       zonemd-check: no
+       zonemd-reject-absence: no
+       zonefile: "auth.nlnetlabs.nl.zone"
+
+view:
+       name: "viewname"
+       local-zone: "view.nlnetlabs.nl" redirect
+       local-data: "view.nlnetlabs.nl A 192.0.2.3"
+       local-data-ptr: "192.0.2.3 view.nlnetlabs.nl"
+       view-first: no
+
+rpz:
+       name: "rpz.nlnetlabs.nl"
+       zonefile: "rpz.nlnetlabs.nl.zone"
+       rpz-action-override: cname
+       rpz-cname-override: www.example.org
+       rpz-log: yes
+       rpz-log-name: "example policy"
+       rpz-signal-nxdomain-ra: no
+       for-downstream: no
+       tags: "tag3"
diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.dsc b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.dsc
new file mode 100644 (file)
index 0000000..e0e8e9f
--- /dev/null
@@ -0,0 +1,16 @@
+BaseName: fast_reload_most_options
+Version: 1.0
+Description: Test fast reload on high verbosity with most options.
+CreationDate: Fri 28 Feb 2025 15:55:15 CET
+Maintainer: Yorgos Thessalonikefs
+Category:
+Component:
+CmdDepends:
+Depends:
+Help:
+Pre: fast_reload_most_options.pre
+Post: fast_reload_most_options.post
+Test: fast_reload_most_options.test
+AuxFiles:
+Passed:
+Failure:
diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.post b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.post
new file mode 100644 (file)
index 0000000..7fd25e3
--- /dev/null
@@ -0,0 +1,11 @@
+# #-- fast_reload_most_options.post --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# source the test var file when it's there
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+#
+# do your teardown here
+. ../common.sh
+kill_pid $UNBOUND_PID
+rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
+cat unbound.log
diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.pre b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.pre
new file mode 100644 (file)
index 0000000..47e3642
--- /dev/null
@@ -0,0 +1,33 @@
+# #-- fast_reload_most_options.pre--#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+# if no threads; exit
+if grep -e "define HAVE_PTHREAD 1" -e "define HAVE_SOLARIS_THREADS 1" -e "define HAVE_WINDOWS_THREADS 1" $PRE/config.h; then
+       echo "have threads"
+else
+       skip_test "no threads"
+fi
+
+get_random_port 1
+UNBOUND_PORT=$RND_PORT
+echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
+
+# make config file
+CONTROL_PATH=/tmp
+CONTROL_PID=$$
+sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < fast_reload_most_options.conf > ub.conf
+# start unbound in the background
+PRE="../.."
+$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
+UNBOUND_PID=$!
+echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
+echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
+echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
+
+cat .tpkg.var.test
+wait_unbound_up unbound.log
diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.test b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.test
new file mode 100644 (file)
index 0000000..2079998
--- /dev/null
@@ -0,0 +1,42 @@
+# #-- fast_reload_most_options.test --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+
+echo "> unbound-control status"
+$PRE/unbound-control -c ub.conf status
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+else
+       echo "exit value: OK"
+fi
+
+for i in {1..10}
+do
+
+       echo "> unbound-control fast_reload +vvdp ($i)"
+       $PRE/unbound-control -c ub.conf fast_reload +vvdp 2>&1 | tee output
+       if test $? -ne 0; then
+               echo "wrong exit value."
+               exit 1
+       else
+               echo "exit value: OK"
+       fi
+       wait_logfile unbound.log "start fast reload thread" 60
+       wait_logfile unbound.log "stop fast reload thread" 60
+       wait_logfile unbound.log "joined with fastreload thread" 60
+
+       if grep "ok" output; then
+               echo "OK"
+       else
+               echo "output not correct"
+               exit 1
+       fi
+done
+
+exit 0
diff --git a/testdata/fast_reload_most_options.tdir/rpz.nlnetlabs.nl.zone b/testdata/fast_reload_most_options.tdir/rpz.nlnetlabs.nl.zone
new file mode 100644 (file)
index 0000000..71b3715
--- /dev/null
@@ -0,0 +1,5 @@
+$ORIGIN rpz.nlnetlabs.nl.
+$TTL 60
+@ IN SOA a b 1 2 3 4 5
+nxdomain.nlnetlabs.nl IN CNAME .
+rpzdata.nlnetlabs.nl IN A 0.0.0.0
diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.conf b/testdata/fast_reload_thread.tdir/fast_reload_thread.conf
new file mode 100644 (file)
index 0000000..719f4a0
--- /dev/null
@@ -0,0 +1,20 @@
+server:
+       verbosity: 4
+       num-threads: 1
+       interface: 127.0.0.1
+       port: @PORT@
+       use-syslog: no
+       directory: ""
+       pidfile: "unbound.pid"
+       chroot: ""
+       username: ""
+       do-not-query-localhost: no
+
+forward-zone:
+       name: "."
+       forward-addr: "127.0.0.1@12345"
+
+remote-control:
+       control-enable: yes
+       control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
+       control-use-cert: no
diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.dsc b/testdata/fast_reload_thread.tdir/fast_reload_thread.dsc
new file mode 100644 (file)
index 0000000..ec3437b
--- /dev/null
@@ -0,0 +1,16 @@
+BaseName: fast_reload_thread
+Version: 1.0
+Description: Test fast reload thread output.
+CreationDate: Thu Jan 4 09:25:55 CET 2024
+Maintainer: dr. W.C.A. Wijngaards
+Category: 
+Component:
+CmdDepends: 
+Depends: 
+Help:
+Pre: fast_reload_thread.pre
+Post: fast_reload_thread.post
+Test: fast_reload_thread.test
+AuxFiles: 
+Passed:
+Failure:
diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.post b/testdata/fast_reload_thread.tdir/fast_reload_thread.post
new file mode 100644 (file)
index 0000000..569a17f
--- /dev/null
@@ -0,0 +1,11 @@
+# #-- fast_reload_thread.post --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# source the test var file when it's there
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+#
+# do your teardown here
+. ../common.sh
+kill_pid $UNBOUND_PID
+rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
+cat unbound.log
diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.pre b/testdata/fast_reload_thread.tdir/fast_reload_thread.pre
new file mode 100644 (file)
index 0000000..5521742
--- /dev/null
@@ -0,0 +1,34 @@
+# #-- fast_reload_thread.pre--#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+# if no threads; exit
+if grep -e "define HAVE_PTHREAD 1" -e "define HAVE_SOLARIS_THREADS 1" -e "define HAVE_WINDOWS_THREADS 1" $PRE/config.h; then
+       echo "have threads"
+else
+       skip_test "no threads"
+fi
+
+get_random_port 1
+UNBOUND_PORT=$RND_PORT
+echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
+
+# make config file
+CONTROL_PATH=/tmp
+CONTROL_PID=$$
+sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < fast_reload_thread.conf > ub.conf
+# start unbound in the background
+PRE="../.."
+$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
+UNBOUND_PID=$!
+echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
+echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
+echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
+
+cat .tpkg.var.test
+wait_unbound_up unbound.log
+
diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.test b/testdata/fast_reload_thread.tdir/fast_reload_thread.test
new file mode 100644 (file)
index 0000000..d2ef258
--- /dev/null
@@ -0,0 +1,38 @@
+# #-- fast_reload_thread.test --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+
+echo "> unbound-control status"
+$PRE/unbound-control -c ub.conf status
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+else
+       echo "exit value: OK"
+fi
+
+echo "> unbound-control fast_reload"
+$PRE/unbound-control -c ub.conf fast_reload 2>&1 | tee output
+if test $? -ne 0; then
+       echo "wrong exit value."
+       exit 1
+else
+       echo "exit value: OK"
+fi
+wait_logfile unbound.log "start fast reload thread" 60
+wait_logfile unbound.log "stop fast reload thread" 60
+wait_logfile unbound.log "joined with fastreload thread" 60
+
+if grep "ok" output; then
+       echo "OK"
+else
+       echo "output not correct"
+       exit 1
+fi
+
+exit 0
index f6e25a1ea37b9f8210e689c5c962b8dc6ab3d280..f763cea321630ce57930187744000dea92a5a6f6 100644 (file)
@@ -1731,6 +1731,7 @@ config_delete(struct config_file* cfg)
        config_del_strarray(cfg->tagname, cfg->num_tags);
        config_del_strbytelist(cfg->local_zone_tags);
        config_del_strbytelist(cfg->respip_tags);
+       config_deldblstrlist(cfg->respip_actions);
        config_deldblstrlist(cfg->acl_view);
        config_del_strbytelist(cfg->acl_tags);
        config_deltrplstrlist(cfg->acl_tag_actions);
@@ -2834,6 +2835,13 @@ if_is_dnscrypt(const char* ifname, int default_port, int dnscrypt_port)
 #endif
 }
 
+size_t
+getmem_str(char* str)
+{
+       if(!str) return 0;
+       return strlen(str)+1;
+}
+
 int
 if_is_quic(const char* ifname, int default_port, int quic_port)
 {
index 71e9cad2edcc743e341ff2046a96ab3c0259b4c3..a1dec45b0dc25f2544484cb1b25e8bd1a25c7738 100644 (file)
@@ -1454,4 +1454,7 @@ int cfg_has_quic(struct config_file* cfg);
 #define LINUX_IP_LOCAL_PORT_RANGE_PATH "/proc/sys/net/ipv4/ip_local_port_range"
 #endif
 
+/** get memory for string */
+size_t getmem_str(char* str);
+
 #endif /* UTIL_CONFIG_FILE_H */
index ee95a69122093ec6fe4cb692ca8b22079f45c021..40b7c94471fe9c045b8a065a01eb53900d3a84e7 100644 (file)
@@ -131,6 +131,29 @@ edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr,
        return (struct edns_string_addr*)addr_tree_lookup(tree, addr, addrlen);
 }
 
+size_t
+edns_strings_get_mem(struct edns_strings* edns_strings)
+{
+       if(!edns_strings) return 0;
+       return regional_get_mem(edns_strings->region) + sizeof(*edns_strings);
+}
+
+void
+edns_strings_swap_tree(struct edns_strings* edns_strings,
+       struct edns_strings* data)
+{
+       rbtree_type tree = edns_strings->client_strings;
+       uint16_t opcode = edns_strings->client_string_opcode;
+       struct regional* region = edns_strings->region;
+
+       edns_strings->client_strings = data->client_strings;
+       edns_strings->client_string_opcode = data->client_string_opcode;
+       edns_strings->region = data->region;
+       data->client_strings = tree;
+       data->client_string_opcode = opcode;
+       data->region = region;
+}
+
 uint8_t*
 edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret, int v4,
        uint8_t* hash)
index 47ccb1ad2cd3280b8bb3c94d8db7f4a742550bc0..5a991fc57bbc7188a01878c5c8dafa584ea806a5 100644 (file)
@@ -141,6 +141,22 @@ struct edns_string_addr*
 edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr,
        socklen_t addrlen);
 
+/**
+ * Get memory usage of edns strings.
+ * @param edns_strings: the edns strings
+ * @return memory usage
+ */
+size_t edns_strings_get_mem(struct edns_strings* edns_strings);
+
+/**
+ * Swap internal tree with preallocated entries.
+ * @param edns_strings: the edns strings structure.
+ * @param data: the data structure used to take elements from. This contains
+ *     the old elements on return.
+ */
+void edns_strings_swap_tree(struct edns_strings* edns_strings,
+       struct edns_strings* data);
+
 /**
  * Compute the interoperable DNS cookie (RFC9018) hash.
  * @param in: buffer input for the hash generation. It needs to be:
index e94ec5bbce850ec1debb7b242d2e429e747cbf96..c6f3ca24aeed029ae69a773da0c9af2f75830bd9 100644 (file)
@@ -74,6 +74,7 @@
 #include "libunbound/worker.h"
 #include "util/tube.h"
 #include "util/config_file.h"
+#include "daemon/remote.h"
 #ifdef UB_ON_WINDOWS
 #include "winrc/win_svc.h"
 #endif
@@ -121,6 +122,7 @@ fptr_whitelist_comm_point_raw(comm_point_callback_type *fptr)
        else if(fptr == &tube_handle_write) return 1;
        else if(fptr == &remote_accept_callback) return 1;
        else if(fptr == &remote_control_callback) return 1;
+       else if(fptr == &fast_reload_client_callback) return 1;
        return 0;
 }
 
@@ -188,6 +190,7 @@ fptr_whitelist_event(void (*fptr)(int, short, void *))
 #ifdef HAVE_NGTCP2
        else if(fptr == &comm_point_doq_callback) return 1;
 #endif
+       else if(fptr == &fast_reload_service_cb) return 1;
 #ifdef USE_DNSTAP
        else if(fptr == &dtio_output_cb) return 1;
        else if(fptr == &dtio_cmd_cb) return 1;
index abad3c8dd11459b954d35b20c66591ac2e7b3252..edce4a523759ee13085decd8a2a65887acc3cba6 100644 (file)
@@ -177,6 +177,7 @@ struct val_anchors;
 struct val_neg_cache;
 struct iter_forwards;
 struct iter_hints;
+struct views;
 struct respip_set;
 struct respip_client_info;
 struct respip_addr_info;
@@ -524,6 +525,10 @@ struct module_env {
         * data structure. 
         */
        struct iter_hints* hints;
+       /** views structure containing view tree */
+       struct views* views;
+       /** response-ip set with associated actions and tags. */
+       struct respip_set* respip_set;
        /** module specific data. indexed by module id. */
        void* modinfo[MAX_MODULE];
 
index 5e4095061a9e5aaeb43c58cc6d881807d32ee099..da8430e417e833a8d965f1236d34278163a46554 100644 (file)
@@ -314,6 +314,11 @@ struct ub_event_base* comm_base_internal(struct comm_base* b)
        return b->eb->base;
 }
 
+struct ub_event* comm_point_internal(struct comm_point* c)
+{
+       return c->ev->ev;
+}
+
 /** see if errno for udp has to be logged or not uses globals */
 static int
 udp_send_errno_needs_log(struct sockaddr* addr, socklen_t addrlen)
@@ -6917,8 +6922,9 @@ comm_timer_is_set(struct comm_timer* timer)
 }
 
 size_t
-comm_timer_get_mem(struct comm_timer* ATTR_UNUSED(timer))
+comm_timer_get_mem(struct comm_timer* timer)
 {
+       if(!timer) return 0;
        return sizeof(struct internal_timer);
 }
 
index a9deeeceac5134f0f5347041f045b9cfe5a0c743..96de0032cef601fd573475c4b3c95c09b56f0018 100644 (file)
@@ -547,6 +547,14 @@ void comm_base_set_slow_accept_handlers(struct comm_base* b,
  */
 struct ub_event_base* comm_base_internal(struct comm_base* b);
 
+/**
+ * Access internal event structure. It is for use with
+ * ub_winsock_tcp_wouldblock on windows.
+ * @param c: comm point.
+ * @return event.
+ */
+struct ub_event* comm_point_internal(struct comm_point* c);
+
 /**
  * Create an UDP comm point. Calls malloc.
  * setups the structure with the parameters you provide.
index aba9fcc1d83bc43951bed5e23878438e8b6df7b0..028b199aa6140200ec5a3a44e0b41e34e9d2e6b7 100644 (file)
@@ -562,6 +562,36 @@ lruhash_update_space_used(struct lruhash* table, void* cb_arg, int diff_size)
        }
 }
 
+void lruhash_update_space_max(struct lruhash* table, void* cb_arg, size_t max)
+{
+       struct lruhash_entry *reclaimlist = NULL;
+
+       fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
+       fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
+       fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
+       fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc));
+
+       if(cb_arg == NULL) cb_arg = table->cb_arg;
+
+       /* update space max */
+       lock_quick_lock(&table->lock);
+       table->space_max = max;
+
+       if(table->space_used > table->space_max)
+               reclaim_space(table, &reclaimlist);
+
+       lock_quick_unlock(&table->lock);
+
+       /* finish reclaim if any (outside of critical region) */
+       while(reclaimlist) {
+               struct lruhash_entry* n = reclaimlist->overflow_next;
+               void* d = reclaimlist->data;
+               (*table->delkeyfunc)(reclaimlist->key, cb_arg);
+               (*table->deldatafunc)(d, cb_arg);
+               reclaimlist = n;
+       }
+}
+
 void 
 lruhash_traverse(struct lruhash* h, int wr, 
        void (*func)(struct lruhash_entry*, void*), void* arg)
index 5ab488beb50853942ee768a4b509f04b5803fb8d..667eba59ce49759c954e3dfa95a923a55c3658cc 100644 (file)
@@ -314,6 +314,16 @@ void lruhash_setmarkdel(struct lruhash* table, lruhash_markdelfunc_type md);
 void lruhash_update_space_used(struct lruhash* table, void* cb_override,
        int diff_size);
 
+/**
+ * Update the max space for the hashtable.
+ *
+ * @param table: hash table.
+ * @param cb_override: if not NULL overrides the cb_arg for deletefunc.
+ * @param max: the new max.
+ */
+void lruhash_update_space_max(struct lruhash* table, void* cb_override,
+       size_t max);
+
 /************************* getdns functions ************************/
 /*** these are used by getdns only and not by unbound. ***/
 
index 62396e16a82dd5aaa5a23492c247f11d5b898fcb..b2bee0838d30c3ed0c570b3e4997f50e1e2ca0aa 100644 (file)
@@ -267,3 +267,12 @@ void get_slabhash_stats(struct slabhash* sh, long long* num, long long* collisio
        if (collisions != NULL)
                *collisions = max_collisions;
 }
+
+void slabhash_adjust_size(struct slabhash* sl, size_t max)
+{
+       size_t space_max = max / sl->size;
+       size_t i;
+       for(i=0; i<sl->size; i++) {
+               lruhash_update_space_max(sl->array[i], NULL, space_max);
+       }
+}
index 089847d9359bd8318a0c8a9bfb0ca3b457adf2cc..d6d94a940e05e3acad2f5e39d85055da832f59d8 100644 (file)
@@ -221,6 +221,13 @@ size_t count_slabhash_entries(struct slabhash* table);
 void get_slabhash_stats(struct slabhash* table,
        long long* entries_count, long long* max_collisions);
 
+/**
+ * Adjust size of slabhash memory max
+ * @param table: slabbed hash table
+ * @param max: new max memory
+ */
+void slabhash_adjust_size(struct slabhash* table, size_t max);
+
 /* --- test representation --- */
 /** test structure contains test key */
 struct slabhash_testkey {
index d7d86a5407b587f7a4d73b1c74abf0363598edfe..284d89076d57b62e4d846ae21926b7c7759c07bd 100644 (file)
@@ -192,3 +192,14 @@ tcl_list_get_mem(struct tcl_list* tcl)
        if(!tcl) return 0;
        return sizeof(*tcl) + regional_get_mem(tcl->region);
 }
+
+void tcl_list_swap_tree(struct tcl_list* tcl, struct tcl_list* data)
+{
+       /* swap tree and region */
+       rbtree_type oldtree = tcl->tree;
+       struct regional* oldregion = tcl->region;
+       tcl->tree = data->tree;
+       tcl->region = data->region;
+       data->tree = oldtree;
+       data->region = oldregion;
+}
index 4fb71a328036fef8b7adb2f479376f152f167755..52108942c7c4cb117e7197b3d75cf08687a03722 100644 (file)
@@ -127,4 +127,13 @@ tcl_addr_lookup(struct tcl_list* tcl, struct sockaddr_storage* addr,
  */
 size_t tcl_list_get_mem(struct tcl_list* tcl);
 
+/**
+ * Swap internal tree with preallocated entries. Caller should manage
+ * tcl_addr item locks.
+ * @param tcl: the tcp connection list structure.
+ * @param data: the data structure used to take elements from. This contains
+ *     the old elements on return.
+ */
+void tcl_list_swap_tree(struct tcl_list* tcl, struct tcl_list* data);
+
 #endif /* DAEMON_TCP_CONN_LIMIT_H */
index e28d728fd5a0799f4b4aa20f2b77ecd982d986ed..a0f61885f5dd7a2fbd065c3510139f80b435c305 100644 (file)
@@ -2035,25 +2035,40 @@ wait_probe_time(struct val_anchors* anchors)
        return 0;
 }
 
-/** reset worker timer */
+/** reset worker timer, at the time from wait_probe_time. */
 static void
-reset_worker_timer(struct module_env* env)
+reset_worker_timer_at(struct module_env* env, time_t next)
 {
        struct timeval tv;
 #ifndef S_SPLINT_S
-       time_t next = (time_t)wait_probe_time(env->anchors);
        /* in case this is libunbound, no timer */
        if(!env->probe_timer)
                return;
        if(next > *env->now)
                tv.tv_sec = (time_t)(next - *env->now);
        else    tv.tv_sec = 0;
+#else
+       (void)next;
 #endif
        tv.tv_usec = 0;
        comm_timer_set(env->probe_timer, &tv);
        verbose(VERB_ALGO, "scheduled next probe in " ARG_LL "d sec", (long long)tv.tv_sec);
 }
 
+/** reset worker timer. This routine manages the locks on acquiring the
+ * next time for the timer. */
+static void
+reset_worker_timer(struct module_env* env)
+{
+       time_t next;
+       if(!env->anchors)
+               return;
+       lock_basic_lock(&env->anchors->lock);
+       next = wait_probe_time(env->anchors);
+       lock_basic_unlock(&env->anchors->lock);
+       reset_worker_timer_at(env, next);
+}
+
 /** set next probe for trust anchor */
 static int
 set_next_probe(struct module_env* env, struct trust_anchor* tp,
@@ -2092,7 +2107,7 @@ set_next_probe(struct module_env* env, struct trust_anchor* tp,
        verbose(VERB_ALGO, "next probe set in %d seconds", 
                (int)tp->autr->next_probe_time - (int)*env->now);
        if(mold != mnew) {
-               reset_worker_timer(env);
+               reset_worker_timer_at(env, mnew);
        }
        return 1;
 }
@@ -2147,7 +2162,7 @@ autr_tp_remove(struct module_env* env, struct trust_anchor* tp,
                autr_point_delete(del_tp);
        }
        if(mold != mnew) {
-               reset_worker_timer(env);
+               reset_worker_timer_at(env, mnew);
        }
 }
 
index ab41fa48402ae86050bf80bd3450494c6a284ecc..daa04504e5956d79f4bce941074c2f29b9f43f29 100644 (file)
@@ -1173,17 +1173,53 @@ anchors_lookup(struct val_anchors* anchors,
        return result;
 }
 
+/** Get memory usage of assembled key rrset */
+static size_t
+assembled_rrset_get_mem(struct ub_packed_rrset_key* pkey)
+{
+       size_t s;
+       if(!pkey)
+               return 0;
+       s = sizeof(*pkey) + pkey->rk.dname_len;
+       if(pkey->entry.data) {
+               struct packed_rrset_data* pd = (struct packed_rrset_data*)
+                       pkey->entry.data;
+               s += sizeof(*pd) + pd->count * (sizeof(size_t)+sizeof(time_t)+
+                       sizeof(uint8_t*));
+       }
+       return s;
+}
+
 size_t 
 anchors_get_mem(struct val_anchors* anchors)
 {
        struct trust_anchor *ta;
-       size_t s = sizeof(*anchors);
-       if(!anchors)
-               return 0;
+       struct ta_key *k;
+       size_t s;
+       if(!anchors) return 0;
+       s = sizeof(*anchors);
+       lock_basic_lock(&anchors->lock);
        RBTREE_FOR(ta, struct trust_anchor*, anchors->tree) {
+               lock_basic_lock(&ta->lock);
                s += sizeof(*ta) + ta->namelen;
                /* keys and so on */
+               for(k = ta->keylist; k; k = k->next) {
+                       s += sizeof(*k) + k->len;
+               }
+               s += assembled_rrset_get_mem(ta->ds_rrset);
+               s += assembled_rrset_get_mem(ta->dnskey_rrset);
+               if(ta->autr) {
+                       struct autr_ta* p;
+                       s += sizeof(*ta->autr);
+                       if(ta->autr->file)
+                               s += strlen(ta->autr->file);
+                       for(p = ta->autr->keys; p; p=p->next) {
+                               s += sizeof(*p) + p->rr_len;
+                       }
+               }
+               lock_basic_unlock(&ta->lock);
        }
+       lock_basic_unlock(&anchors->lock);
        return s;
 }
 
@@ -1346,3 +1382,22 @@ anchors_find_any_noninsecure(struct val_anchors* anchors)
        lock_basic_unlock(&anchors->lock);
        return NULL;
 }
+
+void
+anchors_swap_tree(struct val_anchors* anchors, struct val_anchors* data)
+{
+       rbtree_type* oldtree;
+       rbtree_type oldprobe;
+
+       if(!anchors || !data)
+               return; /* If anchors is NULL, there is no validation. */
+
+       oldtree = anchors->tree;
+       oldprobe = anchors->autr->probe;
+
+       anchors->tree = data->tree;
+       anchors->autr->probe = data->autr->probe;
+
+       data->tree = oldtree;
+       data->autr->probe = oldprobe;
+}
index 02e7e17b5210c31a5c702cb58cc94bddc4150022..3fcf70eec9206d1b5643111c1393cd356c86ad44 100644 (file)
@@ -58,7 +58,7 @@ struct sldns_buffer;
  * on a trust anchor and look it up again to delete it.
  */
 struct val_anchors {
-       /** lock on trees */
+       /** lock on trees. It is locked in order after stubs. */
        lock_basic_type lock;
        /**
         * Anchors are store in this tree. Sort order is chosen, so that
@@ -248,4 +248,12 @@ int anchor_has_keytag(struct val_anchors* anchors, uint8_t* name, int namelabs,
  */
 struct trust_anchor* anchors_find_any_noninsecure(struct val_anchors* anchors);
 
+/**
+ * Swap internal tree with preallocated entries.
+ * @param anchors: anchor storage.
+ * @param data: the data structure used to take elements from. This contains
+ *     the old elements on return.
+ */
+void anchors_swap_tree(struct val_anchors* anchors, struct val_anchors* data);
+
 #endif /* VALIDATOR_VAL_ANCHOR_H */
index b5b678fdea68258979a2f9cd0948610f6ba9988f..bc3a83aeb4c92c400a67a87950066d7aeb3a1fff 100644 (file)
@@ -1554,3 +1554,12 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo,
        lock_basic_unlock(&neg->lock);
        return msg;
 }
+
+void
+val_neg_adjust_size(struct val_neg_cache* neg, size_t max)
+{
+       lock_basic_lock(&neg->lock);
+       neg->max = max;
+       neg_make_space(neg, 0);
+       lock_basic_unlock(&neg->lock);
+}
index 5643ca3314a74f9265c80c0ff9dcbebcedd8e3a1..27617dee5a1ccdda984378fa4d344e1a282744c4 100644 (file)
@@ -299,4 +299,11 @@ struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg,
  */
 void val_neg_zone_take_inuse(struct val_neg_zone* zone);
 
+/**
+ * Adjust the size of the negative cache.
+ * @param neg: negative cache
+ * @param max: new size for max mem.
+ */
+void val_neg_adjust_size(struct val_neg_cache* neg, size_t max);
+
 #endif /* VALIDATOR_VAL_NEG_H */
index 7bfc467371117473d0a58a5f0081bede9034687a..a0550b484eaeb69db5a0e1a45068f5b67f099af3 100644 (file)
@@ -91,50 +91,98 @@ update_reason_bogus(struct reply_info* rep, sldns_ede_code reason_bogus)
 
 /** fill up nsec3 key iterations config entry */
 static int
-fill_nsec3_iter(struct val_env* ve, char* s, int c)
+fill_nsec3_iter(size_t** keysize, size_t** maxiter, char* s, int c)
 {
        char* e;
        int i;
-       free(ve->nsec3_keysize);
-       free(ve->nsec3_maxiter);
-       ve->nsec3_keysize = (size_t*)calloc((size_t)c, sizeof(size_t));
-       ve->nsec3_maxiter = (size_t*)calloc((size_t)c, sizeof(size_t));
-       if(!ve->nsec3_keysize || !ve->nsec3_maxiter) {
+       *keysize = (size_t*)calloc((size_t)c, sizeof(size_t));
+       *maxiter = (size_t*)calloc((size_t)c, sizeof(size_t));
+       if(!*keysize || !*maxiter) {
+               free(*keysize);
+               *keysize = NULL;
+               free(*maxiter);
+               *maxiter = NULL;
                log_err("out of memory");
                return 0;
        }
        for(i=0; i<c; i++) {
-               ve->nsec3_keysize[i] = (size_t)strtol(s, &e, 10);
+               (*keysize)[i] = (size_t)strtol(s, &e, 10);
                if(s == e) {
                        log_err("cannot parse: %s", s);
+                       free(*keysize);
+                       *keysize = NULL;
+                       free(*maxiter);
+                       *maxiter = NULL;
                        return 0;
                }
                s = e;
-               ve->nsec3_maxiter[i] = (size_t)strtol(s, &e, 10);
+               (*maxiter)[i] = (size_t)strtol(s, &e, 10);
                if(s == e) {
                        log_err("cannot parse: %s", s);
+                       free(*keysize);
+                       *keysize = NULL;
+                       free(*maxiter);
+                       *maxiter = NULL;
                        return 0;
                }
                s = e;
-               if(i>0 && ve->nsec3_keysize[i-1] >= ve->nsec3_keysize[i]) {
+               if(i>0 && (*keysize)[i-1] >= (*keysize)[i]) {
                        log_err("nsec3 key iterations not ascending: %d %d",
-                               (int)ve->nsec3_keysize[i-1], 
-                               (int)ve->nsec3_keysize[i]);
+                               (int)(*keysize)[i-1], (int)(*keysize)[i]);
+                       free(*keysize);
+                       *keysize = NULL;
+                       free(*maxiter);
+                       *maxiter = NULL;
                        return 0;
                }
                verbose(VERB_ALGO, "validator nsec3cfg keysz %d mxiter %d",
-                       (int)ve->nsec3_keysize[i], (int)ve->nsec3_maxiter[i]);
+                       (int)(*keysize)[i], (int)(*maxiter)[i]);
        }
        return 1;
 }
 
+int
+val_env_parse_key_iter(char* val_nsec3_key_iterations, size_t** keysize,
+       size_t** maxiter, int* keyiter_count)
+{
+       int c;
+       c = cfg_count_numbers(val_nsec3_key_iterations);
+       if(c < 1 || (c&1)) {
+               log_err("validator: unparsable or odd nsec3 key "
+                       "iterations: %s", val_nsec3_key_iterations);
+               return 0;
+       }
+       *keyiter_count = c/2;
+       if(!fill_nsec3_iter(keysize, maxiter, val_nsec3_key_iterations, c/2)) {
+               log_err("validator: cannot apply nsec3 key iterations");
+               return 0;
+       }
+       return 1;
+}
+
+void
+val_env_apply_cfg(struct val_env* val_env, struct config_file* cfg,
+       size_t* keysize, size_t* maxiter, int keyiter_count)
+{
+       free(val_env->nsec3_keysize);
+       free(val_env->nsec3_maxiter);
+       val_env->nsec3_keysize = keysize;
+       val_env->nsec3_maxiter = maxiter;
+       val_env->nsec3_keyiter_count = keyiter_count;
+       val_env->bogus_ttl = (uint32_t)cfg->bogus_ttl;
+       val_env->date_override = cfg->val_date_override;
+       val_env->skew_min = cfg->val_sig_skew_min;
+       val_env->skew_max = cfg->val_sig_skew_max;
+       val_env->max_restart = cfg->val_max_restart;
+}
+
 /** apply config settings to validator */
 static int
 val_apply_cfg(struct module_env* env, struct val_env* val_env, 
        struct config_file* cfg)
 {
-       int c;
-       val_env->bogus_ttl = (uint32_t)cfg->bogus_ttl;
+       size_t* keysize=NULL, *maxiter=NULL;
+       int keyiter_count = 0;
        if(!env->anchors)
                env->anchors = anchors_create();
        if(!env->anchors) {
@@ -154,21 +202,11 @@ val_apply_cfg(struct module_env* env, struct val_env* val_env,
                log_err("validator: error in trustanchors config");
                return 0;
        }
-       val_env->date_override = cfg->val_date_override;
-       val_env->skew_min = cfg->val_sig_skew_min;
-       val_env->skew_max = cfg->val_sig_skew_max;
-       val_env->max_restart = cfg->val_max_restart;
-       c = cfg_count_numbers(cfg->val_nsec3_key_iterations);
-       if(c < 1 || (c&1)) {
-               log_err("validator: unparsable or odd nsec3 key "
-                       "iterations: %s", cfg->val_nsec3_key_iterations);
-               return 0;
-       }
-       val_env->nsec3_keyiter_count = c/2;
-       if(!fill_nsec3_iter(val_env, cfg->val_nsec3_key_iterations, c/2)) {
-               log_err("validator: cannot apply nsec3 key iterations");
+       if(!val_env_parse_key_iter(cfg->val_nsec3_key_iterations,
+               &keysize, &maxiter, &keyiter_count)) {
                return 0;
        }
+       val_env_apply_cfg(val_env, cfg, keysize, maxiter, keyiter_count);
        if (env->neg_cache)
                val_env->neg_cache = env->neg_cache;
        if(!val_env->neg_cache)
index c07f9d59d90d51510dd8628278f5289ea6589c43..e04fad57277060b38841e7bb8c056956d62e07b4 100644 (file)
@@ -52,6 +52,7 @@ struct key_entry_key;
 struct val_neg_cache;
 struct config_strlist;
 struct comm_timer;
+struct config_file;
 
 /**
  * This is the TTL to use when a trust anchor fails to prime. A trust anchor
@@ -280,4 +281,26 @@ size_t val_get_mem(struct module_env* env, int id);
 /** Timer callback for msg signatures continue timer */
 void validate_suspend_timer_cb(void* arg);
 
+/**
+ * Parse the val_nsec3_key_iterations string.
+ * @param val_nsec3_key_iterations: the string with nsec3 iterations config.
+ * @param keysize: returns malloced key size array on success.
+ * @param maxiter: returns malloced max iterations array on success.
+ * @param keyiter_count: returns size of keysize and maxiter arrays.
+ * @return false if it does not parse correctly.
+ */
+int val_env_parse_key_iter(char* val_nsec3_key_iterations, size_t** keysize,
+       size_t** maxiter, int* keyiter_count);
+
+/**
+ * Apply config to validator env
+ * @param val_env: validator env.
+ * @param cfg: config
+ * @param keysize: nsec3 key size array.
+ * @param maxiter: nsec3 max iterations array.
+ * @param keyiter_count: size of keysize and maxiter arrays.
+ */
+void val_env_apply_cfg(struct val_env* val_env, struct config_file* cfg,
+       size_t* keysize, size_t* maxiter, int keyiter_count);
+
 #endif /* VALIDATOR_VALIDATOR_H */