diff --git a/Makefile.in b/Makefile.in index 672435e01..788ef7b8c 100644 --- a/Makefile.in +++ b/Makefile.in @@ -867,7 +867,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 \ @@ -963,7 +963,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 \ @@ -1298,7 +1298,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 \ diff --git a/config.h.in b/config.h.in index 099206025..52fff0bd8 100644 --- a/config.h.in +++ b/config.h.in @@ -593,6 +593,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STDARG_H +/* Define to 1 if you have the header file. */ +#undef HAVE_STDATOMIC_H + /* Define to 1 if you have the header file. */ #undef HAVE_STDBOOL_H diff --git a/configure b/configure index ef250d6c6..a3d68927b 100755 --- a/configure +++ b/configure @@ -16090,6 +16090,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. diff --git a/configure.ac b/configure.ac index fdded4f50..b21e164a2 100644 --- a/configure.ac +++ b/configure.ac @@ -521,6 +521,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. diff --git a/daemon/acl_list.c b/daemon/acl_list.c index 83cfd7ddf..c6e33e5fa 100644 --- a/daemon/acl_list.c +++ b/daemon/acl_list.c @@ -823,3 +823,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; +} diff --git a/daemon/acl_list.h b/daemon/acl_list.h index 9da43bef3..f0ca106f1 100644 --- a/daemon/acl_list.h +++ b/daemon/acl_list.h @@ -202,4 +202,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 */ diff --git a/daemon/daemon.c b/daemon/daemon.c index 72b4a43be..fff00c26b 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -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) { @@ -711,16 +710,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"); @@ -756,15 +755,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); @@ -880,14 +879,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; inum; i++) worker_delete(daemon->workers[i]); free(daemon->workers); @@ -941,6 +944,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(); diff --git a/daemon/daemon.h b/daemon/daemon.h index 5c3a114cc..1e5a4a9bd 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -59,6 +59,8 @@ struct daemon_remote; struct respip_set; struct shm_main_info; struct cookie_secrets; +struct fast_reload_thread; +struct fast_reload_printq; #include "dnstap/dnstap_config.h" #ifdef USE_DNSTAP @@ -130,15 +132,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 */ @@ -151,6 +149,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; }; /** @@ -203,4 +212,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 */ diff --git a/daemon/remote.c b/daemon/remote.c index 855b1f963..45a7ada97 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -52,6 +52,9 @@ #ifdef HAVE_OPENSSL_BN_H #include #endif +#ifdef HAVE_STDATOMIC_H +#include +#endif #include #include "daemon/remote.h" @@ -63,6 +66,7 @@ #include "util/config_file.h" #include "util/net_help.h" #include "util/module.h" +#include "util/ub_event.h" #include "services/listen_dnsport.h" #include "services/cache/rrset.h" #include "services/cache/infra.h" @@ -77,10 +81,14 @@ #include "validator/val_kcache.h" #include "validator/val_kentry.h" #include "validator/val_anchor.h" +#include "validator/val_neg.h" #include "iterator/iterator.h" #include "iterator/iter_fwd.h" #include "iterator/iter_hints.h" #include "iterator/iter_delegpt.h" +#include "iterator/iter_utils.h" +#include "iterator/iter_donotq.h" +#include "iterator/iter_priv.h" #include "services/outbound_list.h" #include "services/outside_network.h" #include "sldns/str2wire.h" @@ -88,6 +96,7 @@ #include "sldns/wire2str.h" #include "sldns/sbuffer.h" #include "util/timeval_func.h" +#include "util/tcp_conn_limit.h" #include "util/edns.h" #ifdef USE_CACHEDB #include "cachedb/cachedb.h" @@ -102,6 +111,9 @@ #ifdef HAVE_NETDB_H #include #endif +#ifdef HAVE_POLL_H +#include +#endif /* just for portability */ #ifdef SQ @@ -114,6 +126,18 @@ /** Acceptable lengths of str lines */ #define MAX_CMD_STRLINE 1024 #define MAX_STDIN_STRLINE 2048 +/** What number of loop iterations is too much for ipc retries */ +#define IPC_LOOP_MAX 200 +/** Timeout in msec for ipc socket poll. */ +#define IPC_NOTIFICATION_WAIT 200 + +static void fr_printq_delete(struct fast_reload_printq* printq); +static void fr_main_perform_printout(struct fast_reload_thread* fr); +static int fr_printq_empty(struct fast_reload_printq* printq); +static void fr_printq_list_insert(struct fast_reload_printq* printq, + struct daemon* daemon); +static void fr_printq_remove(struct fast_reload_printq* printq); +static void fr_check_cmd_from_thread(struct fast_reload_thread* fr); static int remote_setup_ctx(struct daemon_remote* rc, struct config_file* cfg) @@ -512,6 +536,11 @@ state_list_remove_elem(struct rc_state** 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) @@ -1461,8 +1549,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; @@ -1494,8 +1581,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; @@ -1517,8 +1603,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; @@ -1543,8 +1628,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; @@ -1585,8 +1669,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; @@ -1609,8 +1692,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; @@ -3027,7 +3109,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); @@ -3043,7 +3125,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); @@ -3409,7 +3491,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); @@ -3423,6 +3505,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; @@ -3621,7 +3706,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); } @@ -3685,7 +3770,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 */ @@ -3777,3 +3862,3956 @@ 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 | POLLHUP; + if(pollin) + p.events |= POLLIN; + if(pollout) + p.events |= POLLOUT; + } +#ifndef USE_WINSOCK + ret = poll(fds, nfds, timeout); +#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_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; inum_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; inum_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\n", changed_str)) + 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\n", changed_str)) + 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; inext) + 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; inum_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(!p2 && !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, old_z->name, + old_z->namelen, old_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(&env->auth_zones->lock); + lock_rw_unlock(&ct->auth_zones->lock); + lock_rw_unlock(&env->auth_zones->rpz_lock); + lock_rw_unlock(&ct->auth_zones->rpz_lock); + lock_rw_unlock(&env->fwds->lock); + lock_rw_unlock(&ct->fwds->lock); + lock_rw_unlock(&env->hints->lock); + lock_rw_unlock(&ct->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 (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]); + sock_close(fr->commreload[0]); + sock_close(fr->commreload[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; + 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. */ +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. + */ +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; icom->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; inum; 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; inum; 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; inum; 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; + 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); + } +} diff --git a/daemon/remote.h b/daemon/remote.h index 4902803f5..064d7b7fc 100644 --- a/daemon/remote.h +++ b/daemon/remote.h @@ -48,6 +48,7 @@ #ifdef HAVE_OPENSSL_SSL_H #include #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 */ diff --git a/daemon/unbound.c b/daemon/unbound.c index 306fe6caf..ebeb75450 100644 --- a/daemon/unbound.c +++ b/daemon/unbound.c @@ -681,6 +681,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"); } /** diff --git a/daemon/worker.c b/daemon/worker.c index 5e6b2a656..de74cc049 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -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 works 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 @@ -765,7 +853,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) { @@ -1817,7 +1906,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; } diff --git a/daemon/worker.h b/daemon/worker.h index ab2fc728d..b7bb52fd7 100644 --- a/daemon/worker.h +++ b/daemon/worker.h @@ -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 }; /** diff --git a/dnstap/dnstap.c b/dnstap/dnstap.c index cff308f93..071fd0895 100644 --- a/dnstap/dnstap.c +++ b/dnstap/dnstap.c @@ -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) { diff --git a/dnstap/dnstap.h b/dnstap/dnstap.h index 21c033697..4390a9cf1 100644 --- a/dnstap/dnstap.h +++ b/dnstap/dnstap.h @@ -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(). diff --git a/dnstap/unbound-dnstap-socket.c b/dnstap/unbound-dnstap-socket.c index f203aa7d7..1f3f87ddb 100644 --- a/dnstap/unbound-dnstap-socket.c +++ b/dnstap/unbound-dnstap-socket.c @@ -1783,3 +1783,17 @@ 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; +} diff --git a/doc/unbound-control.8.in b/doc/unbound-control.8.in index 17073f938..60cbe96a7 100644 --- a/doc/unbound-control.8.in +++ b/doc/unbound-control.8.in @@ -60,6 +60,89 @@ 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. +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. With '+vv' it is more verbose. +That includes the time it took to do the reload. And with more verbose output +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 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 '+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 diff --git a/iterator/iter_fwd.c b/iterator/iter_fwd.c index b9d42553a..6bf98861a 100644 --- a/iterator/iter_fwd.c +++ b/iterator/iter_fwd.c @@ -590,3 +590,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)); +} diff --git a/iterator/iter_fwd.h b/iterator/iter_fwd.h index 4527d899c..095cd96df 100644 --- a/iterator/iter_fwd.h +++ b/iterator/iter_fwd.h @@ -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 */ diff --git a/iterator/iter_hints.c b/iterator/iter_hints.c index 8b168271c..b8a02f4d8 100644 --- a/iterator/iter_hints.c +++ b/iterator/iter_hints.c @@ -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; +} diff --git a/iterator/iter_hints.h b/iterator/iter_hints.h index 26de323c9..87434b5ac 100644 --- a/iterator/iter_hints.h +++ b/iterator/iter_hints.h @@ -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 */ diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index 1b4f5f6eb..179b02635 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -77,41 +77,73 @@ static const char DEFAULT_NAT64_PREFIX[] = "64:ff9b::/96"; /** fillup fetch policy array */ -static void -fetch_fill(struct iter_env* ie, const char* str) +static int +fetch_fill(int* target_fetch_policy, int max_dependency_depth, const char* str) { char* s = (char*)str, *e; int i; - for(i=0; imax_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; imax_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; imax_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 */ } diff --git a/iterator/iter_utils.h b/iterator/iter_utils.h index 4024629e6..a3f33a482 100644 --- a/iterator/iter_utils.h +++ b/iterator/iter_utils.h @@ -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. */ @@ -428,4 +429,41 @@ 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); + #endif /* ITERATOR_ITER_UTILS_H */ diff --git a/iterator/iterator.c b/iterator/iterator.c index 228f5dfae..a5fc73dc6 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -103,16 +103,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) { @@ -124,10 +114,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; } @@ -256,7 +243,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); @@ -1710,7 +1697,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) { @@ -2074,7 +2061,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; @@ -2083,7 +2070,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, @@ -2246,14 +2233,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 */ @@ -2278,7 +2265,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, @@ -2547,7 +2534,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); @@ -3048,9 +3035,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); @@ -3849,7 +3836,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 */ diff --git a/iterator/iterator.h b/iterator/iterator.h index 70b11df7e..097a26733 100644 --- a/iterator/iterator.h +++ b/iterator/iterator.h @@ -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; diff --git a/libunbound/libworker.c b/libunbound/libworker.c index 94b644a49..9539f817e 100644 --- a/libunbound/libworker.c +++ b/libunbound/libworker.c @@ -1058,3 +1058,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; +} diff --git a/respip/respip.c b/respip/respip.c index db48f176e..c13bc6ac9 100644 --- a/respip/respip.c +++ b/respip/respip.c @@ -867,7 +867,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 +877,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 +899,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 +927,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 +1112,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 +1161,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 +1189,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 +1242,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 +1340,33 @@ 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 = 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; +} diff --git a/respip/respip.h b/respip/respip.h index e4ab5cc9c..6469854c5 100644 --- a/respip/respip.h +++ b/respip/respip.h @@ -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 */ diff --git a/services/authzone.c b/services/authzone.c index 580a681f5..ad9351d9e 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -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) { @@ -6987,6 +6984,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) @@ -6995,14 +7004,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); @@ -8561,3 +8563,159 @@ 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 = 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); + } +} diff --git a/services/authzone.h b/services/authzone.h index 07614ed82..bac8bdf7c 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -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; @@ -211,7 +212,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 */ @@ -787,4 +790,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 */ diff --git a/services/cache/infra.c b/services/cache/infra.c index 66b17c121..5b055a71c 100644 --- a/services/cache/infra.c +++ b/services/cache/infra.c @@ -165,7 +165,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; @@ -181,8 +181,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; @@ -201,8 +201,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); @@ -212,19 +212,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); @@ -232,24 +232,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; @@ -261,10 +258,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) @@ -286,19 +279,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); @@ -306,16 +301,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; } @@ -348,11 +344,12 @@ infra_create(struct config_file* cfg) return NULL; } /* insert config data into ratelimits */ - if(!setup_domain_limits(infra, cfg)) { + if(!setup_domain_limits(&infra->domain_limits, cfg)) { infra_delete(infra); return NULL; } - if(!setup_wait_limits(infra, cfg)) { + if(!setup_wait_limits(&infra->wait_limits_netblock, + &infra->wait_limits_cookie_netblock, cfg)) { infra_delete(infra); return NULL; } @@ -377,12 +374,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) { @@ -390,12 +404,10 @@ infra_delete(struct infra_cache* infra) return; slabhash_delete(infra->hosts); slabhash_delete(infra->domain_rates); - traverse_postorder(&infra->domain_limits, domain_limit_free, NULL); + domain_limits_free(&infra->domain_limits); slabhash_delete(infra->client_ip_rates); - traverse_postorder(&infra->wait_limits_netblock, - wait_limit_netblock_del, NULL); - traverse_postorder(&infra->wait_limits_cookie_netblock, - wait_limit_netblock_del, NULL); + wait_limits_free(&infra->wait_limits_netblock); + wait_limits_free(&infra->wait_limits_cookie_netblock); free(infra); } @@ -426,7 +438,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; } diff --git a/services/cache/infra.h b/services/cache/infra.h index 1a88bbb94..b93919e94 100644 --- a/services/cache/infra.h +++ b/services/cache/infra.h @@ -502,4 +502,19 @@ 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); + #endif /* SERVICES_CACHE_INFRA_H */ diff --git a/services/localzone.c b/services/localzone.c index d21e0c48a..e5411eebb 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -2210,3 +2210,33 @@ 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 = 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; +} diff --git a/services/localzone.h b/services/localzone.h index 6f0f28b12..66102fd98 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -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 diff --git a/services/mesh.c b/services/mesh.c index 522118844..658ce632c 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -77,6 +77,20 @@ #include #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; } @@ -866,6 +882,68 @@ 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_datas = regional_alloc_zero(region, + sizeof(struct config_strlist*)*cinfo->tag_datas_size); + if(!client_info->tag_datas) + return NULL; + for(i=0; itag_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, @@ -906,8 +984,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; @@ -1680,6 +1757,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) @@ -2141,7 +2237,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 @@ -2209,7 +2306,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) { @@ -2361,3 +2459,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) { + if(mesh_state_del_cb(s, cb, cb_arg)) { + /* 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++; + } + } +} diff --git a/services/mesh.h b/services/mesh.h index 5bd53e065..f0f043940 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -696,4 +696,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 */ diff --git a/services/rpz.c b/services/rpz.c index d8999a8a5..52b0c2881 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -2772,3 +2772,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; +} diff --git a/services/rpz.h b/services/rpz.h index 7f409087f..6b5f17d1e 100644 --- a/services/rpz.h +++ b/services/rpz.h @@ -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 */ diff --git a/services/view.c b/services/view.c index 72f364318..4ecd52d49 100644 --- a/services/view.c +++ b/services/view.c @@ -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,36 @@ 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 = 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; +} diff --git a/services/view.h b/services/view.h index 12f7a64e7..0ff39ed6e 100644 --- a/services/view.h +++ b/services/view.h @@ -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 */ diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c index 21e7eb82d..3513b04e7 100644 --- a/smallapp/unbound-control.c +++ b/smallapp/unbound-control.c @@ -109,6 +109,10 @@ 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 only\n"); + printf(" changes some options, like forwards\n"); + printf(" and stubs.\n"); printf(" stats print statistics\n"); printf(" stats_noreset peek at statistics\n"); #ifdef HAVE_SHMGET diff --git a/smallapp/worker_cb.c b/smallapp/worker_cb.c index c68981735..5987e7622 100644 --- a/smallapp/worker_cb.c +++ b/smallapp/worker_cb.c @@ -255,3 +255,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; +} diff --git a/testcode/checklocks.c b/testcode/checklocks.c index d1c877467..ac3e70ad1 100644 --- a/testcode/checklocks.c +++ b/testcode/checklocks.c @@ -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. */ @@ -682,10 +685,20 @@ open_lockorder(struct thr_check* thr) char buf[24]; time_t t; snprintf(buf, sizeof(buf), "ublocktrace.%d", 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: */ if(fwrite(&t, sizeof(t), 1, thr->order_info) != 1 || @@ -712,6 +725,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); diff --git a/testcode/fake_event.c b/testcode/fake_event.c index a517fa5f3..4710d4bd3 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -1479,6 +1479,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; @@ -1988,4 +1993,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 ***********/ diff --git a/testcode/testbound.c b/testcode/testbound.c index 123fe0d4e..d5807e8b7 100644 --- a/testcode/testbound.c +++ b/testcode/testbound.c @@ -600,3 +600,14 @@ 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 */ +} diff --git a/testdata/fast_reload_fwd.tdir/auth1.zone b/testdata/fast_reload_fwd.tdir/auth1.zone new file mode 100644 index 000000000..b6b551a42 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/auth1.zone @@ -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 index 000000000..fc59810c9 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/auth2.zone @@ -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 index 000000000..dca76342f --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf @@ -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 index 000000000..dbe6e4ffa --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf2 @@ -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 index 000000000..422cdee46 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.dsc @@ -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 index 000000000..d9644414b --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns1 @@ -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 index 000000000..8e7eb60c8 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns2 @@ -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 index 000000000..e7e644b7a --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post @@ -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 index 000000000..67d02ca66 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.pre @@ -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=$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 index 000000000..9248593c7 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.test @@ -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_thread.tdir/fast_reload_thread.conf b/testdata/fast_reload_thread.tdir/fast_reload_thread.conf new file mode 100644 index 000000000..719f4a00e --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.conf @@ -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 index 000000000..ec3437b69 --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.dsc @@ -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 index 000000000..569a17f85 --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.post @@ -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 index 000000000..5521742fa --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.pre @@ -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 index 000000000..d2ef25880 --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.test @@ -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 diff --git a/util/config_file.c b/util/config_file.c index 9a93befd3..0587b7062 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -2795,3 +2795,10 @@ if_is_dnscrypt(const char* ifname, const char* port, int dnscrypt_port) return 0; #endif } + +size_t +getmem_str(char* str) +{ + if(!str) return 0; + return strlen(str)+1; +} diff --git a/util/config_file.h b/util/config_file.h index 23aacc67a..79e09a720 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -1396,4 +1396,7 @@ int if_is_dnscrypt(const char* ifname, const char* port, int dnscrypt_port); #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 */ diff --git a/util/edns.c b/util/edns.c index ee95a6912..40b7c9447 100644 --- a/util/edns.c +++ b/util/edns.c @@ -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) diff --git a/util/edns.h b/util/edns.h index 47ccb1ad2..5a991fc57 100644 --- a/util/edns.h +++ b/util/edns.h @@ -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: diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index 705dc1bbe..2021b4ca5 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -73,6 +73,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 @@ -120,6 +121,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; } @@ -181,6 +183,7 @@ fptr_whitelist_event(void (*fptr)(int, short, void *)) else if(fptr == &tube_handle_signal) return 1; else if(fptr == &comm_base_handle_slow_accept) return 1; else if(fptr == &comm_point_http_handle_callback) return 1; + 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; diff --git a/util/module.h b/util/module.h index 5bdb622a2..6befc9316 100644 --- a/util/module.h +++ b/util/module.h @@ -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; @@ -522,6 +523,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]; diff --git a/util/netevent.c b/util/netevent.c index 9d5131da9..de49dce4c 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -306,6 +306,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) @@ -5039,8 +5044,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); } diff --git a/util/netevent.h b/util/netevent.h index 6f43ce56c..5579a74cd 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -517,6 +517,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. diff --git a/util/storage/lruhash.c b/util/storage/lruhash.c index aba9fcc1d..028b199aa 100644 --- a/util/storage/lruhash.c +++ b/util/storage/lruhash.c @@ -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) diff --git a/util/storage/lruhash.h b/util/storage/lruhash.h index 5ab488beb..667eba59c 100644 --- a/util/storage/lruhash.h +++ b/util/storage/lruhash.h @@ -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. ***/ diff --git a/util/storage/slabhash.c b/util/storage/slabhash.c index 62396e16a..b2bee0838 100644 --- a/util/storage/slabhash.c +++ b/util/storage/slabhash.c @@ -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; isize; i++) { + lruhash_update_space_max(sl->array[i], NULL, space_max); + } +} diff --git a/util/storage/slabhash.h b/util/storage/slabhash.h index 089847d93..d6d94a940 100644 --- a/util/storage/slabhash.h +++ b/util/storage/slabhash.h @@ -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 { diff --git a/util/tcp_conn_limit.c b/util/tcp_conn_limit.c index d7d86a540..284d89076 100644 --- a/util/tcp_conn_limit.c +++ b/util/tcp_conn_limit.c @@ -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; +} diff --git a/util/tcp_conn_limit.h b/util/tcp_conn_limit.h index 4fb71a328..52108942c 100644 --- a/util/tcp_conn_limit.h +++ b/util/tcp_conn_limit.h @@ -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 */ diff --git a/validator/autotrust.c b/validator/autotrust.c index 36cdf3e0a..99e8eb0a8 100644 --- a/validator/autotrust.c +++ b/validator/autotrust.c @@ -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); } } diff --git a/validator/val_anchor.c b/validator/val_anchor.c index 8466a8923..0c9d0c4fd 100644 --- a/validator/val_anchor.c +++ b/validator/val_anchor.c @@ -1170,17 +1170,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; + struct ta_key *k; size_t s = sizeof(*anchors); if(!anchors) return 0; + 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; } @@ -1343,3 +1379,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; +} diff --git a/validator/val_anchor.h b/validator/val_anchor.h index 02e7e17b5..3fcf70eec 100644 --- a/validator/val_anchor.h +++ b/validator/val_anchor.h @@ -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 */ diff --git a/validator/val_neg.c b/validator/val_neg.c index 52bc68387..feab475f9 100644 --- a/validator/val_neg.c +++ b/validator/val_neg.c @@ -1552,3 +1552,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); +} diff --git a/validator/val_neg.h b/validator/val_neg.h index 5643ca331..27617dee5 100644 --- a/validator/val_neg.h +++ b/validator/val_neg.h @@ -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 */ diff --git a/validator/validator.c b/validator/validator.c index e6d19a2c9..171135a7f 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -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(sizeof(size_t), (size_t)c); - ve->nsec3_maxiter = (size_t*)calloc(sizeof(size_t), (size_t)c); - if(!ve->nsec3_keysize || !ve->nsec3_maxiter) { + *keysize = (size_t*)calloc(sizeof(size_t), (size_t)c); + *maxiter = (size_t*)calloc(sizeof(size_t), (size_t)c); + if(!*keysize || !*maxiter) { + free(*keysize); + *keysize = NULL; + free(*maxiter); + *maxiter = NULL; log_err("out of memory"); return 0; } for(i=0; insec3_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) diff --git a/validator/validator.h b/validator/validator.h index 72f44b16e..33b0c1f9a 100644 --- a/validator/validator.h +++ b/validator/validator.h @@ -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 */