diff --git a/Makefile.in b/Makefile.in index 22fb75c12..eb9fce66a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -866,7 +866,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 \ @@ -962,7 +962,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 \ @@ -1278,7 +1278,8 @@ daemon.lo daemon.o: $(srcdir)/daemon/daemon.c config.h $(srcdir)/daemon/daemon.h $(srcdir)/util/edns.h $(srcdir)/services/listen_dnsport.h $(srcdir)/services/cache/rrset.h \ $(srcdir)/services/cache/infra.h $(srcdir)/util/rtt.h $(srcdir)/services/localzone.h \ $(srcdir)/services/authzone.h $(srcdir)/services/mesh.h $(srcdir)/services/rpz.h $(srcdir)/respip/respip.h \ - $(srcdir)/util/random.h $(srcdir)/util/tube.h $(srcdir)/util/net_help.h $(srcdir)/sldns/keyraw.h + $(srcdir)/util/random.h $(srcdir)/util/tube.h $(srcdir)/util/net_help.h $(srcdir)/sldns/keyraw.h \ + $(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h remote.lo remote.o: $(srcdir)/daemon/remote.c config.h $(srcdir)/daemon/remote.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 $(srcdir)/util/netevent.h \ @@ -1296,7 +1297,7 @@ 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)/sldns/wire2str.h $(srcdir)/util/locks.h $(srcdir)/util/ub_event.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 \ @@ -1484,7 +1485,8 @@ context.lo context.o: $(srcdir)/libunbound/context.c config.h $(srcdir)/libunbou $(srcdir)/util/storage/slabhash.h $(srcdir)/services/cache/infra.h $(srcdir)/util/rtt.h \ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \ $(srcdir)/services/authzone.h $(srcdir)/services/mesh.h $(srcdir)/services/rpz.h $(srcdir)/daemon/stats.h \ - $(srcdir)/util/timehist.h $(srcdir)/respip/respip.h $(srcdir)/util/edns.h + $(srcdir)/util/timehist.h $(srcdir)/respip/respip.h $(srcdir)/util/edns.h \ + $(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h libunbound.lo libunbound.o: $(srcdir)/libunbound/libunbound.c $(srcdir)/libunbound/unbound.h \ $(srcdir)/libunbound/unbound-event.h config.h $(srcdir)/libunbound/context.h $(srcdir)/util/locks.h \ $(srcdir)/util/log.h $(srcdir)/util/alloc.h $(srcdir)/util/rbtree.h $(srcdir)/services/modstack.h \ @@ -1496,7 +1498,8 @@ libunbound.lo libunbound.o: $(srcdir)/libunbound/libunbound.c $(srcdir)/libunbou $(srcdir)/sldns/sbuffer.h $(srcdir)/services/cache/infra.h $(srcdir)/util/rtt.h $(srcdir)/util/netevent.h \ $(srcdir)/dnscrypt/dnscrypt.h $(srcdir)/services/cache/rrset.h \ $(srcdir)/util/storage/slabhash.h $(srcdir)/services/authzone.h $(srcdir)/services/mesh.h \ - $(srcdir)/services/rpz.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h $(srcdir)/respip/respip.h + $(srcdir)/services/rpz.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h $(srcdir)/respip/respip.h \ + $(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h libworker.lo libworker.o: $(srcdir)/libunbound/libworker.c config.h $(srcdir)/libunbound/libworker.h \ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \ $(srcdir)/libunbound/context.h $(srcdir)/util/alloc.h $(srcdir)/util/rbtree.h $(srcdir)/services/modstack.h \ @@ -1510,8 +1513,7 @@ libworker.lo libworker.o: $(srcdir)/libunbound/libworker.c config.h $(srcdir)/li $(srcdir)/services/cache/rrset.h $(srcdir)/util/storage/slabhash.h $(srcdir)/services/outbound_list.h \ $(srcdir)/util/fptr_wlist.h $(srcdir)/util/tube.h $(srcdir)/util/regional.h $(srcdir)/util/random.h \ $(srcdir)/util/storage/lookup3.h $(srcdir)/util/net_help.h $(srcdir)/util/data/dname.h \ - $(srcdir)/util/data/msgencode.h $(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h \ - $(srcdir)/sldns/str2wire.h + $(srcdir)/util/data/msgencode.h $(srcdir)/sldns/str2wire.h unbound-host.lo unbound-host.o: $(srcdir)/smallapp/unbound-host.c config.h $(srcdir)/libunbound/unbound.h \ $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/wire2str.h asynclook.lo asynclook.o: $(srcdir)/testcode/asynclook.c config.h $(srcdir)/libunbound/unbound.h \ diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c index b912be8ed..aa8b2645a 100644 --- a/cachedb/cachedb.c +++ b/cachedb/cachedb.c @@ -666,6 +666,7 @@ cachedb_extcache_store(struct module_qstate* qstate, struct cachedb_env* ie) static int cachedb_intcache_lookup(struct module_qstate* qstate, struct cachedb_env* cde) { + uint8_t dpname_storage[LDNS_MAX_DOMAINLEN+1]; uint8_t* dpname=NULL; size_t dpnamelen=0; struct dns_msg* msg; @@ -674,7 +675,7 @@ cachedb_intcache_lookup(struct module_qstate* qstate, struct cachedb_env* cde) return 0; } if(iter_stub_fwd_no_cache(qstate, &qstate->qinfo, - &dpname, &dpnamelen)) + &dpname, &dpnamelen, dpname_storage, sizeof(dpname_storage))) return 0; /* no cache for these queries */ msg = dns_cache_lookup(qstate->env, qstate->qinfo.qname, qstate->qinfo.qname_len, qstate->qinfo.qtype, diff --git a/config.h.in b/config.h.in index bc39544c4..f04baa7e9 100644 --- a/config.h.in +++ b/config.h.in @@ -581,6 +581,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 062b5ba02..e8b6910d2 100755 --- a/configure +++ b/configure @@ -16007,6 +16007,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 e85fd2eb6..7b8ad44cc 100644 --- a/configure.ac +++ b/configure.ac @@ -471,6 +471,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/cachedump.c b/daemon/cachedump.c index 61ee1d291..c7523497d 100644 --- a/daemon/cachedump.c +++ b/daemon/cachedump.c @@ -850,15 +850,20 @@ int print_deleg_lookup(RES* ssl, struct worker* worker, uint8_t* nm, if(!ssl_printf(ssl, "The following name servers are used for lookup " "of %s\n", b)) return 0; - + + lock_rw_rdlock(&worker->env.fwds->lock); dp = forwards_lookup(worker->env.fwds, nm, qinfo.qclass); if(dp) { - if(!ssl_printf(ssl, "forwarding request:\n")) + if(!ssl_printf(ssl, "forwarding request:\n")) { + lock_rw_unlock(&worker->env.fwds->lock); return 0; + } print_dp_main(ssl, dp, NULL); print_dp_details(ssl, worker, dp); + lock_rw_unlock(&worker->env.fwds->lock); return 1; } + lock_rw_unlock(&worker->env.fwds->lock); while(1) { dp = dns_cache_find_delegation(&worker->env, nm, nmlen, @@ -892,22 +897,29 @@ int print_deleg_lookup(RES* ssl, struct worker* worker, uint8_t* nm, return 0; continue; } - } + } + lock_rw_rdlock(&worker->env.hints->lock); stub = hints_lookup_stub(worker->env.hints, nm, qinfo.qclass, dp); if(stub) { if(stub->noprime) { if(!ssl_printf(ssl, "The noprime stub servers " - "are used:\n")) + "are used:\n")) { + lock_rw_unlock(&worker->env.hints->lock); return 0; + } } else { if(!ssl_printf(ssl, "The stub is primed " - "with servers:\n")) + "with servers:\n")) { + lock_rw_unlock(&worker->env.hints->lock); return 0; + } } print_dp_main(ssl, stub->dp, NULL); print_dp_details(ssl, worker, stub->dp); + lock_rw_unlock(&worker->env.hints->lock); } else { + lock_rw_unlock(&worker->env.hints->lock); print_dp_main(ssl, dp, msg); print_dp_details(ssl, worker, dp); } diff --git a/daemon/daemon.c b/daemon/daemon.c index 4870089a7..bb01f3206 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -91,6 +91,8 @@ #include "util/net_help.h" #include "sldns/keyraw.h" #include "respip/respip.h" +#include "iterator/iter_fwd.h" +#include "iterator/iter_hints.h" #include #ifdef HAVE_SYSTEMD @@ -714,6 +716,12 @@ daemon_fork(struct daemon* daemon) fatal_exit("Could not create local zones: out of memory"); if(!local_zones_apply_cfg(daemon->local_zones, daemon->cfg)) fatal_exit("Could not set up local zones"); + if(!(daemon->env->fwds = forwards_create()) || + !forwards_apply_cfg(daemon->env->fwds, daemon->cfg)) + fatal_exit("Could not set forward zones"); + if(!(daemon->env->hints = hints_create()) || + !hints_apply_cfg(daemon->env->hints, daemon->cfg)) + fatal_exit("Could not set root or stub hints"); /* process raw response-ip configuration data */ if(!(daemon->respip_set = respip_set_create())) @@ -830,6 +838,10 @@ daemon_cleanup(struct daemon* daemon) slabhash_clear(daemon->env->msg_cache); } daemon->old_num = daemon->num; /* save the current num */ + forwards_delete(daemon->env->fwds); + daemon->env->fwds = NULL; + hints_delete(daemon->env->hints); + daemon->env->hints = NULL; local_zones_delete(daemon->local_zones); daemon->local_zones = NULL; respip_set_delete(daemon->respip_set); @@ -840,6 +852,10 @@ daemon_cleanup(struct daemon* daemon) auth_zones_cleanup(daemon->env->auth_zones); /* key cache is cleared by module desetup 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); @@ -890,6 +906,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 57665446d..f76ced23a 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -58,6 +58,8 @@ struct ub_randstate; struct daemon_remote; struct respip_set; struct shm_main_info; +struct fast_reload_thread; +struct fast_reload_printq; #include "dnstap/dnstap_config.h" #ifdef USE_DNSTAP @@ -146,6 +148,14 @@ struct daemon { #endif /** reuse existing cache on reload if other conditions allow it. */ int reuse_cache; + /** 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; + /** config file name */ + char* cfgfile; }; /** diff --git a/daemon/remote.c b/daemon/remote.c index cbce1198b..75135c155 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" @@ -98,6 +102,9 @@ #ifdef HAVE_NETDB_H #include #endif +#ifdef HAVE_POLL_H +#include +#endif /* just for portability */ #ifdef SQ @@ -107,6 +114,19 @@ /** what to put on statistics lines between var and value, ": " or "=" */ #define SQ "=" +/** 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) { @@ -504,6 +524,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) { @@ -652,6 +677,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) @@ -1992,12 +2076,20 @@ static int print_root_fwds(RES* ssl, struct iter_forwards* fwds, uint8_t* root) { struct delegpt* dp; + lock_rw_rdlock(&fwds->lock); dp = forwards_lookup(fwds, root, LDNS_RR_CLASS_IN); - if(!dp) + if(!dp) { + lock_rw_unlock(&fwds->lock); return ssl_printf(ssl, "off (using root hints)\n"); + } /* if dp is returned it must be the root */ log_assert(query_dname_compare(dp->name, root)==0); - return ssl_print_name_dp(ssl, NULL, root, LDNS_RR_CLASS_IN, dp); + if(!ssl_print_name_dp(ssl, NULL, root, LDNS_RR_CLASS_IN, dp)) { + lock_rw_unlock(&fwds->lock); + return 0; + } + lock_rw_unlock(&fwds->lock); + return 1; } /** parse args into delegpt */ @@ -2082,15 +2174,20 @@ do_forward(RES* ssl, struct worker* worker, char* args) /* delete all the existing queries first */ mesh_delete_all(worker->env.mesh); if(strcmp(args, "off") == 0) { + lock_rw_wrlock(&fwd->lock); forwards_delete_zone(fwd, LDNS_RR_CLASS_IN, root); + lock_rw_unlock(&fwd->lock); } else { struct delegpt* dp; if(!(dp = parse_delegpt(ssl, args, root))) return; + lock_rw_wrlock(&fwd->lock); if(!forwards_add_zone(fwd, LDNS_RR_CLASS_IN, dp)) { + lock_rw_unlock(&fwd->lock); (void)ssl_printf(ssl, "error out of memory\n"); return; } + lock_rw_unlock(&fwd->lock); } send_ok(ssl); } @@ -2153,9 +2250,11 @@ do_forward_add(RES* ssl, struct worker* worker, char* args) return; if(tls) dp->ssl_upstream = 1; + lock_rw_wrlock(&fwd->lock); if(insecure && worker->env.anchors) { if(!anchors_add_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, nm)) { + lock_rw_unlock(&fwd->lock); (void)ssl_printf(ssl, "error out of memory\n"); delegpt_free_mlc(dp); free(nm); @@ -2163,10 +2262,12 @@ do_forward_add(RES* ssl, struct worker* worker, char* args) } } if(!forwards_add_zone(fwd, LDNS_RR_CLASS_IN, dp)) { + lock_rw_unlock(&fwd->lock); (void)ssl_printf(ssl, "error out of memory\n"); free(nm); return; } + lock_rw_unlock(&fwd->lock); free(nm); send_ok(ssl); } @@ -2180,10 +2281,12 @@ do_forward_remove(RES* ssl, struct worker* worker, char* args) uint8_t* nm = NULL; if(!parse_fs_args(ssl, args, &nm, NULL, &insecure, NULL, NULL)) return; + lock_rw_wrlock(&fwd->lock); if(insecure && worker->env.anchors) anchors_delete_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, nm); forwards_delete_zone(fwd, LDNS_RR_CLASS_IN, nm); + lock_rw_unlock(&fwd->lock); free(nm); send_ok(ssl); } @@ -2200,9 +2303,13 @@ do_stub_add(RES* ssl, struct worker* worker, char* args) return; if(tls) dp->ssl_upstream = 1; + lock_rw_wrlock(&fwd->lock); + lock_rw_wrlock(&worker->env.hints->lock); if(insecure && worker->env.anchors) { if(!anchors_add_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, nm)) { + lock_rw_unlock(&fwd->lock); + lock_rw_unlock(&worker->env.hints->lock); (void)ssl_printf(ssl, "error out of memory\n"); delegpt_free_mlc(dp); free(nm); @@ -2213,6 +2320,8 @@ do_stub_add(RES* ssl, struct worker* worker, char* args) if(insecure && worker->env.anchors) anchors_delete_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, nm); + lock_rw_unlock(&fwd->lock); + lock_rw_unlock(&worker->env.hints->lock); (void)ssl_printf(ssl, "error out of memory\n"); delegpt_free_mlc(dp); free(nm); @@ -2224,9 +2333,13 @@ do_stub_add(RES* ssl, struct worker* worker, char* args) if(insecure && worker->env.anchors) anchors_delete_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, nm); + lock_rw_unlock(&fwd->lock); + lock_rw_unlock(&worker->env.hints->lock); free(nm); return; } + lock_rw_unlock(&fwd->lock); + lock_rw_unlock(&worker->env.hints->lock); free(nm); send_ok(ssl); } @@ -2240,11 +2353,15 @@ do_stub_remove(RES* ssl, struct worker* worker, char* args) uint8_t* nm = NULL; if(!parse_fs_args(ssl, args, &nm, NULL, &insecure, NULL, NULL)) return; + lock_rw_wrlock(&fwd->lock); + lock_rw_wrlock(&worker->env.hints->lock); if(insecure && worker->env.anchors) anchors_delete_insecure(worker->env.anchors, LDNS_RR_CLASS_IN, nm); forwards_delete_stub_hole(fwd, LDNS_RR_CLASS_IN, nm); hints_delete_stub(worker->env.hints, LDNS_RR_CLASS_IN, nm); + lock_rw_unlock(&fwd->lock); + lock_rw_unlock(&worker->env.hints->lock); free(nm); send_ok(ssl); } @@ -2673,6 +2790,7 @@ do_list_forwards(RES* ssl, struct worker* worker) struct iter_forward_zone* z; struct trust_anchor* a; int insecure; + lock_rw_rdlock(&fwds->lock); RBTREE_FOR(z, struct iter_forward_zone*, fwds->tree) { if(!z->dp) continue; /* skip empty marker for stub */ @@ -2687,9 +2805,12 @@ do_list_forwards(RES* ssl, struct worker* worker) } if(!ssl_print_name_dp(ssl, (insecure?"forward +i":"forward"), - z->name, z->dclass, z->dp)) + z->name, z->dclass, z->dp)) { + lock_rw_unlock(&fwds->lock); return; + } } + lock_rw_unlock(&fwds->lock); } /** do the list_stubs command */ @@ -2700,6 +2821,7 @@ do_list_stubs(RES* ssl, struct worker* worker) struct trust_anchor* a; int insecure; char str[32]; + lock_rw_rdlock(&worker->env.hints->lock); RBTREE_FOR(z, struct iter_hints_stub*, &worker->env.hints->tree) { /* see if it is insecure */ @@ -2715,9 +2837,12 @@ do_list_stubs(RES* ssl, struct worker* worker) snprintf(str, sizeof(str), "stub %sprime%s", (z->noprime?"no":""), (insecure?" +i":"")); if(!ssl_print_name_dp(ssl, str, z->node.name, - z->node.dclass, z->dp)) + z->node.dclass, z->dp)) { + lock_rw_unlock(&worker->env.hints->lock); return; + } } + lock_rw_unlock(&worker->env.hints->lock); } /** do the list_auth_zones command */ @@ -3012,7 +3137,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); @@ -3026,6 +3151,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; @@ -3077,26 +3205,6 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd, } else if(cmdcmp(p, "auth_zone_transfer", 18)) { do_auth_zone_transfer(ssl, worker, skipwhite(p+18)); return; - } else if(cmdcmp(p, "stub_add", 8)) { - /* must always distribute this cmd */ - if(rc) distribute_cmd(rc, ssl, cmd); - do_stub_add(ssl, worker, skipwhite(p+8)); - return; - } else if(cmdcmp(p, "stub_remove", 11)) { - /* must always distribute this cmd */ - if(rc) distribute_cmd(rc, ssl, cmd); - do_stub_remove(ssl, worker, skipwhite(p+11)); - return; - } else if(cmdcmp(p, "forward_add", 11)) { - /* must always distribute this cmd */ - if(rc) distribute_cmd(rc, ssl, cmd); - do_forward_add(ssl, worker, skipwhite(p+11)); - return; - } else if(cmdcmp(p, "forward_remove", 14)) { - /* must always distribute this cmd */ - if(rc) distribute_cmd(rc, ssl, cmd); - do_forward_remove(ssl, worker, skipwhite(p+14)); - return; } else if(cmdcmp(p, "insecure_add", 12)) { /* must always distribute this cmd */ if(rc) distribute_cmd(rc, ssl, cmd); @@ -3107,11 +3215,6 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd, if(rc) distribute_cmd(rc, ssl, cmd); do_insecure_remove(ssl, worker, skipwhite(p+15)); return; - } else if(cmdcmp(p, "forward", 7)) { - /* must always distribute this cmd */ - if(rc) distribute_cmd(rc, ssl, cmd); - do_forward(ssl, worker, skipwhite(p+7)); - return; } else if(cmdcmp(p, "flush_stats", 11)) { /* must always distribute this cmd */ if(rc) distribute_cmd(rc, ssl, cmd); @@ -3153,6 +3256,16 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd, do_data_add(ssl, worker->daemon->local_zones, skipwhite(p+10)); } else if(cmdcmp(p, "local_datas", 11)) { do_datas_add(ssl, worker->daemon->local_zones); + } else if(cmdcmp(p, "forward_add", 11)) { + do_forward_add(ssl, worker, skipwhite(p+11)); + } else if(cmdcmp(p, "forward_remove", 14)) { + do_forward_remove(ssl, worker, skipwhite(p+14)); + } else if(cmdcmp(p, "forward", 7)) { + do_forward(ssl, worker, skipwhite(p+7)); + } else if(cmdcmp(p, "stub_add", 8)) { + do_stub_add(ssl, worker, skipwhite(p+8)); + } else if(cmdcmp(p, "stub_remove", 11)) { + do_stub_remove(ssl, worker, skipwhite(p+11)); } else if(cmdcmp(p, "view_local_zone_remove", 22)) { do_view_zone_remove(ssl, worker, skipwhite(p+22)); } else if(cmdcmp(p, "view_local_zone", 15)) { @@ -3207,7 +3320,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); } @@ -3271,7 +3384,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 */ @@ -3363,3 +3476,2559 @@ 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 forwards */ + struct iter_forwards* fwds; + /** construct for stubs */ + struct iter_hints* hints; + /** 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, clear construct information, deletes items */ +static void +fr_construct_clear(struct fast_reload_construct* ct) +{ + if(!ct) + return; + forwards_delete(ct->fwds); + hints_delete(ct->hints); + views_delete(ct->views); + /* 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 += forwards_get_mem(ct->fwds); + mem += hints_get_mem(ct->hints); + 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, 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) +{ + 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->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())) + 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->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; +} + +#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(var) oldcfg->var = cfg->var; atomic_store(&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(verbosity); + COPY_VAR(stat_interval); + COPY_VAR(stat_cumulative); + COPY_VAR(stat_extended); + COPY_VAR(stat_inhibit_zero); + COPY_VAR(num_threads); + COPY_VAR(port); + COPY_VAR(do_ip4); + COPY_VAR(do_ip6); + COPY_VAR(do_nat64); + COPY_VAR(prefer_ip4); + COPY_VAR(prefer_ip6); + COPY_VAR(do_udp); + COPY_VAR(do_tcp); + COPY_VAR(max_reuse_tcp_queries); + COPY_VAR(tcp_reuse_timeout); + COPY_VAR(tcp_auth_query_timeout); + COPY_VAR(tcp_upstream); + COPY_VAR(udp_upstream_without_downstream); + COPY_VAR(tcp_mss); + COPY_VAR(outgoing_tcp_mss); + COPY_VAR(tcp_idle_timeout); + COPY_VAR(do_tcp_keepalive); + COPY_VAR(tcp_keepalive_timeout); + COPY_VAR(sock_queue_timeout); + COPY_VAR(proxy_protocol_port); + COPY_VAR(ssl_service_key); + COPY_VAR(ssl_service_pem); + COPY_VAR(ssl_port); + COPY_VAR(ssl_upstream); + COPY_VAR(tls_cert_bundle); + COPY_VAR(tls_win_cert); + COPY_VAR(tls_additional_port); + /* The first is used to walk throught the list but last is + * only used during config read. */ + COPY_VAR(tls_session_ticket_keys.first); + COPY_VAR(tls_session_ticket_keys.last); + COPY_VAR(tls_ciphers); + COPY_VAR(tls_ciphersuites); + COPY_VAR(tls_use_sni); + COPY_VAR(https_port); + COPY_VAR(http_endpoint); + COPY_VAR(http_max_streams); + COPY_VAR(http_query_buffer_size); + COPY_VAR(http_response_buffer_size); + COPY_VAR(http_nodelay); + COPY_VAR(http_notls_downstream); + COPY_VAR(outgoing_num_ports); + COPY_VAR(outgoing_num_tcp); + COPY_VAR(incoming_num_tcp); + COPY_VAR(outgoing_avail_ports); + COPY_VAR(edns_buffer_size); + COPY_VAR(stream_wait_size); + COPY_VAR(msg_buffer_size); + COPY_VAR(msg_cache_size); + COPY_VAR(msg_cache_slabs); + COPY_VAR(num_queries_per_thread); + COPY_VAR(jostle_time); + COPY_VAR(rrset_cache_size); + COPY_VAR(rrset_cache_slabs); + COPY_VAR(host_ttl); + COPY_VAR(infra_cache_slabs); + COPY_VAR(infra_cache_numhosts); + COPY_VAR(infra_cache_min_rtt); + COPY_VAR(infra_cache_max_rtt); + COPY_VAR(infra_keep_probing); + COPY_VAR(delay_close); + COPY_VAR(udp_connect); + COPY_VAR(target_fetch_policy); + COPY_VAR(fast_server_permil); + COPY_VAR(fast_server_num); + COPY_VAR(if_automatic); + COPY_VAR(if_automatic_ports); + COPY_VAR(so_rcvbuf); + COPY_VAR(so_sndbuf); + COPY_VAR(so_reuseport); + COPY_VAR(ip_transparent); + COPY_VAR(ip_freebind); + COPY_VAR(ip_dscp); + /* Not copied because the length and items could then not match. + num_ifs, ifs, num_out_ifs, out_ifs + */ + COPY_VAR(root_hints); + COPY_VAR(stubs); + COPY_VAR(forwards); + COPY_VAR(auths); + COPY_VAR(views); + COPY_VAR(donotqueryaddrs); +#ifdef CLIENT_SUBNET + COPY_VAR(client_subnet); + COPY_VAR(client_subnet_zone); + COPY_VAR(client_subnet_opcode); + COPY_VAR(client_subnet_always_forward); + COPY_VAR(max_client_subnet_ipv4); + COPY_VAR(max_client_subnet_ipv6); + COPY_VAR(min_client_subnet_ipv4); + COPY_VAR(min_client_subnet_ipv6); + COPY_VAR(max_ecs_tree_size_ipv4); + COPY_VAR(max_ecs_tree_size_ipv6); +#endif + COPY_VAR(acls); + COPY_VAR(donotquery_localhost); + COPY_VAR(tcp_connection_limits); + COPY_VAR(harden_short_bufsize); + COPY_VAR(harden_large_queries); + COPY_VAR(harden_glue); + COPY_VAR(harden_dnssec_stripped); + COPY_VAR(harden_below_nxdomain); + COPY_VAR(harden_referral_path); + COPY_VAR(harden_algo_downgrade); + COPY_VAR(harden_unknown_additional); + COPY_VAR(use_caps_bits_for_id); + COPY_VAR(caps_whitelist); + COPY_VAR(private_address); + COPY_VAR(private_domain); + COPY_VAR(unwanted_threshold); + COPY_VAR(max_ttl); + COPY_VAR(min_ttl); + COPY_VAR(max_negative_ttl); + COPY_VAR(prefetch); + COPY_VAR(prefetch_key); + COPY_VAR(deny_any); + COPY_VAR(chrootdir); + COPY_VAR(username); + COPY_VAR(directory); + COPY_VAR(logfile); + COPY_VAR(pidfile); + COPY_VAR(use_syslog); + COPY_VAR(log_time_ascii); + COPY_VAR(log_queries); + COPY_VAR(log_replies); + COPY_VAR(log_tag_queryreply); + COPY_VAR(log_local_actions); + COPY_VAR(log_servfail); + COPY_VAR(log_identity); + COPY_VAR(log_destaddr); + COPY_VAR(hide_identity); + COPY_VAR(hide_version); + COPY_VAR(hide_trustanchor); + COPY_VAR(hide_http_user_agent); + COPY_VAR(identity); + COPY_VAR(version); + COPY_VAR(http_user_agent); + COPY_VAR(nsid_cfg_str); + /* Not copied because the length and items could then not match. + nsid; + nsid_len; + */ + COPY_VAR(module_conf); + COPY_VAR(trust_anchor_file_list); + COPY_VAR(trust_anchor_list); + COPY_VAR(auto_trust_anchor_file_list); + COPY_VAR(trusted_keys_file_list); + COPY_VAR(domain_insecure); + COPY_VAR(trust_anchor_signaling); + COPY_VAR(root_key_sentinel); + COPY_VAR(val_date_override); + COPY_VAR(val_sig_skew_min); + COPY_VAR(val_sig_skew_max); + COPY_VAR(val_max_restart); + COPY_VAR(bogus_ttl); + COPY_VAR(val_clean_additional); + COPY_VAR(val_log_level); + COPY_VAR(val_log_squelch); + COPY_VAR(val_permissive_mode); + COPY_VAR(aggressive_nsec); + COPY_VAR(ignore_cd); + COPY_VAR(disable_edns_do); + COPY_VAR(serve_expired); + COPY_VAR(serve_expired_ttl); + COPY_VAR(serve_expired_ttl_reset); + COPY_VAR(serve_expired_reply_ttl); + COPY_VAR(serve_expired_client_timeout); + COPY_VAR(ede_serve_expired); + COPY_VAR(serve_original_ttl); + COPY_VAR(val_nsec3_key_iterations); + COPY_VAR(zonemd_permissive_mode); + COPY_VAR(add_holddown); + COPY_VAR(del_holddown); + COPY_VAR(keep_missing); + COPY_VAR(permit_small_holddown); + COPY_VAR(key_cache_size); + COPY_VAR(key_cache_slabs); + COPY_VAR(neg_cache_size); + COPY_VAR(local_zones); + COPY_VAR(local_zones_nodefault); +#ifdef USE_IPSET + COPY_VAR(local_zones_ipset); +#endif + COPY_VAR(local_zones_disable_default); + COPY_VAR(local_data); + COPY_VAR(local_zone_overrides); + COPY_VAR(unblock_lan_zones); + COPY_VAR(insecure_lan_zones); + /* These reference tags + COPY_VAR(local_zone_tags); + COPY_VAR(acl_tags); + COPY_VAR(acl_tag_actions); + COPY_VAR(acl_tag_datas); + */ + COPY_VAR(acl_view); + COPY_VAR(interface_actions); + /* These reference tags + COPY_VAR(interface_tags); + COPY_VAR(interface_tag_actions); + COPY_VAR(interface_tag_datas); + */ + COPY_VAR(interface_view); + /* This references tags + COPY_VAR(respip_tags); + */ + COPY_VAR(respip_actions); + COPY_VAR(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(remote_control_enable); + /* The first is used to walk throught the list but last is + * only used during config read. */ + COPY_VAR(control_ifs.first); + COPY_VAR(control_ifs.last); + COPY_VAR(control_use_cert); + COPY_VAR(control_port); + COPY_VAR(server_key_file); + COPY_VAR(server_cert_file); + COPY_VAR(control_key_file); + COPY_VAR(control_cert_file); + COPY_VAR(python_script); + COPY_VAR(dynlib_file); + COPY_VAR(use_systemd); + COPY_VAR(do_daemonize); + COPY_VAR(minimal_responses); + COPY_VAR(rrset_roundrobin); + COPY_VAR(unknown_server_time_limit); + COPY_VAR(max_udp_size); + COPY_VAR(dns64_prefix); + COPY_VAR(dns64_synthall); + COPY_VAR(dns64_ignore_aaaa); + COPY_VAR(nat64_prefix); + COPY_VAR(dnstap); + COPY_VAR(dnstap_bidirectional); + COPY_VAR(dnstap_socket_path); + COPY_VAR(dnstap_ip); + COPY_VAR(dnstap_tls); + COPY_VAR(dnstap_tls_server_name); + COPY_VAR(dnstap_tls_cert_bundle); + COPY_VAR(dnstap_tls_client_key_file); + COPY_VAR(dnstap_tls_client_cert_file); + COPY_VAR(dnstap_send_identity); + COPY_VAR(dnstap_send_version); + COPY_VAR(dnstap_identity); + COPY_VAR(dnstap_version); + COPY_VAR(dnstap_log_resolver_query_messages); + COPY_VAR(dnstap_log_resolver_response_messages); + COPY_VAR(dnstap_log_client_query_messages); + COPY_VAR(dnstap_log_client_response_messages); + COPY_VAR(dnstap_log_forwarder_query_messages); + COPY_VAR(dnstap_log_forwarder_response_messages); + COPY_VAR(disable_dnssec_lame_check); + COPY_VAR(ip_ratelimit); + COPY_VAR(ip_ratelimit_cookie); + COPY_VAR(ip_ratelimit_slabs); + COPY_VAR(ip_ratelimit_size); + COPY_VAR(ip_ratelimit_factor); + COPY_VAR(ip_ratelimit_backoff); + COPY_VAR(ratelimit); + COPY_VAR(ratelimit_slabs); + COPY_VAR(ratelimit_size); + COPY_VAR(ratelimit_for_domain); + COPY_VAR(ratelimit_below_domain); + COPY_VAR(ratelimit_factor); + COPY_VAR(ratelimit_backoff); + COPY_VAR(outbound_msg_retry); + COPY_VAR(max_sent_count); + COPY_VAR(max_query_restarts); + COPY_VAR(qname_minimisation); + COPY_VAR(qname_minimisation_strict); + COPY_VAR(shm_enable); + COPY_VAR(shm_key); + COPY_VAR(edns_client_strings); + COPY_VAR(edns_client_string_opcode); + COPY_VAR(dnscrypt); + COPY_VAR(dnscrypt_port); + COPY_VAR(dnscrypt_provider); + COPY_VAR(dnscrypt_secret_key); + COPY_VAR(dnscrypt_provider_cert); + COPY_VAR(dnscrypt_provider_cert_rotated); + COPY_VAR(dnscrypt_shared_secret_cache_size); + COPY_VAR(dnscrypt_shared_secret_cache_slabs); + COPY_VAR(dnscrypt_nonce_cache_size); + COPY_VAR(dnscrypt_nonce_cache_slabs); + COPY_VAR(pad_responses); + COPY_VAR(pad_responses_block_size); + COPY_VAR(pad_queries); + COPY_VAR(pad_queries_block_size); +#ifdef USE_IPSECMOD + COPY_VAR(ipsecmod_enabled); + COPY_VAR(ipsecmod_whitelist); + COPY_VAR(ipsecmod_hook); + COPY_VAR(ipsecmod_ignore_bogus); + COPY_VAR(ipsecmod_max_ttl); + COPY_VAR(ipsecmod_strict); +#endif +#ifdef USE_CACHEDB + COPY_VAR(cachedb_backend); + COPY_VAR(cachedb_secret); + COPY_VAR(cachedb_no_store); +#ifdef USE_REDIS + COPY_VAR(redis_server_host); + COPY_VAR(redis_server_port); + COPY_VAR(redis_server_path); + COPY_VAR(redis_server_password); + COPY_VAR(redis_timeout); + COPY_VAR(redis_expire_records); + COPY_VAR(redis_logical_db); +#endif +#endif + COPY_VAR(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(ipset_name_v4); + COPY_VAR(ipset_name_v6); +#endif + COPY_VAR(ede); +} +#endif /* ATOMIC_POINTER_LOCK_FREE */ + +/** 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(&daemon->views->lock); + lock_rw_wrlock(&ct->fwds->lock); + lock_rw_wrlock(&ct->hints->lock); + lock_rw_wrlock(&env->fwds->lock); + lock_rw_wrlock(&env->hints->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(daemon->views, ct->views); + + /* Set globals with new config. */ + config_apply(env->cfg); + + lock_rw_unlock(&ct->views->lock); + lock_rw_unlock(&daemon->views->lock); + lock_rw_unlock(&env->fwds->lock); + lock_rw_unlock(&env->hints->lock); + lock_rw_unlock(&ct->fwds->lock); + lock_rw_unlock(&ct->hints->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); + } + 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_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.\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)); + + /* Delete old. */ + 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. */ + fr_send_notification(fr, + fast_reload_notification_reload_nopause_poll); + fr_poll_for_ack(fr); + } + 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, &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], 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], 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 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); + } + 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], &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); + } +} + + +/** 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); + } + + if(fr->worker->daemon->fast_reload_drop_mesh) { + verbose(VERB_ALGO, "worker: drop mesh queries after reload"); + mesh_delete_all(fr->worker->env.mesh); + } + 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); + + /* 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(printq->client_cp->ev->ev, 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(printq->client_cp->ev->ev, 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..4c4f442f4 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,112 @@ 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 */ + 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 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]; +}; + /** * Create new remote control state for the daemon. * @param cfg: config file with key file settings. @@ -203,4 +311,35 @@ 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); + #endif /* DAEMON_REMOTE_H */ diff --git a/daemon/unbound.c b/daemon/unbound.c index d6c371571..f911d2abc 100644 --- a/daemon/unbound.c +++ b/daemon/unbound.c @@ -677,6 +677,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 0d240db14..45222c867 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -369,6 +369,82 @@ 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], + &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); + } + 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) @@ -404,6 +480,14 @@ 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"); + worker_send_reload_ack(worker); + break; default: log_err("bad command %d", (int)cmd); break; @@ -2261,18 +2345,6 @@ worker_init(struct worker* worker, struct config_file *cfg, worker_delete(worker); return 0; } - if(!(worker->env.fwds = forwards_create()) || - !forwards_apply_cfg(worker->env.fwds, cfg)) { - log_err("Could not set forward zones"); - worker_delete(worker); - return 0; - } - if(!(worker->env.hints = hints_create()) || - !hints_apply_cfg(worker->env.hints, cfg)) { - log_err("Could not set root or stub hints"); - worker_delete(worker); - return 0; - } /* one probe timer per process -- if we have 5011 anchors */ if(autr_get_num_anchors(worker->env.anchors) > 0 #ifndef THREADS_DISABLED @@ -2345,8 +2417,6 @@ worker_delete(struct worker* worker) outside_network_quit_prepare(worker->back); mesh_delete(worker->env.mesh); sldns_buffer_free(worker->env.scratch_buffer); - forwards_delete(worker->env.fwds); - hints_delete(worker->env.hints); listen_delete(worker->front); outside_network_delete(worker->back); comm_signal_delete(worker->comsig); 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/unbound-dnstap-socket.c b/dnstap/unbound-dnstap-socket.c index 04fda74b8..0e2378837 100644 --- a/dnstap/unbound-dnstap-socket.c +++ b/dnstap/unbound-dnstap-socket.c @@ -1572,3 +1572,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 642b4c946..1940f1211 100644 --- a/doc/unbound-control.8.in +++ b/doc/unbound-control.8.in @@ -60,6 +60,47 @@ 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 and stubs. +.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 +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. +.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/edns-subnet/subnetmod.c b/edns-subnet/subnetmod.c index 22e3ef17e..e442fd62f 100644 --- a/edns-subnet/subnetmod.c +++ b/edns-subnet/subnetmod.c @@ -152,7 +152,7 @@ int ecs_whitelist_check(struct query_info* qinfo, /* Cache by default, might be disabled after parsing EDNS option * received from nameserver. */ - if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL)) { + if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL, NULL, 0)) { qstate->no_cache_store = 0; } diff --git a/iterator/iter_fwd.c b/iterator/iter_fwd.c index c4b241129..7954f865c 100644 --- a/iterator/iter_fwd.c +++ b/iterator/iter_fwd.c @@ -71,6 +71,7 @@ forwards_create(void) sizeof(struct iter_forwards)); if(!fwd) return NULL; + lock_rw_init(&fwd->lock); return fwd; } @@ -100,6 +101,7 @@ forwards_delete(struct iter_forwards* fwd) { if(!fwd) return; + lock_rw_destroy(&fwd->lock); fwd_del_tree(fwd); free(fwd); } @@ -332,17 +334,27 @@ make_stub_holes(struct iter_forwards* fwd, struct config_file* cfg) int forwards_apply_cfg(struct iter_forwards* fwd, struct config_file* cfg) { + if(fwd->tree) { + lock_unprotect(&fwd->lock, fwd->tree); + } fwd_del_tree(fwd); fwd->tree = rbtree_create(fwd_cmp); if(!fwd->tree) return 0; + lock_protect(&fwd->lock, fwd->tree, sizeof(*fwd->tree)); + lock_rw_wrlock(&fwd->lock); /* read forward zones */ - if(!read_forwards(fwd, cfg)) + if(!read_forwards(fwd, cfg)) { + lock_rw_unlock(&fwd->lock); return 0; - if(!make_stub_holes(fwd, cfg)) + } + if(!make_stub_holes(fwd, cfg)) { + lock_rw_unlock(&fwd->lock); return 0; + } fwd_init_parents(fwd); + lock_rw_unlock(&fwd->lock); return 1; } @@ -458,10 +470,12 @@ forwards_get_mem(struct iter_forwards* fwd) size_t s; if(!fwd) return 0; + lock_rw_rdlock(&fwd->lock); s = sizeof(*fwd) + sizeof(*fwd->tree); RBTREE_FOR(p, struct iter_forward_zone*, fwd->tree) { s += sizeof(*p) + p->namelen + delegpt_get_mem(p->dp); } + lock_rw_unlock(&fwd->lock); return s; } @@ -504,6 +518,8 @@ forwards_delete_zone(struct iter_forwards* fwd, uint16_t c, uint8_t* nm) int forwards_add_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm) { + if(fwd_zone_find(fwd, c, nm) != NULL) + return 1; /* already a stub zone there */ if(!fwd_add_stub_hole(fwd, c, nm)) { return 0; } @@ -523,3 +539,19 @@ forwards_delete_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm) fwd_zone_free(z); fwd_init_parents(fwd); } + +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 e90b74c16..e51165ede 100644 --- a/iterator/iter_fwd.h +++ b/iterator/iter_fwd.h @@ -43,6 +43,7 @@ #ifndef ITERATOR_ITER_FWD_H #define ITERATOR_ITER_FWD_H #include "util/rbtree.h" +#include "util/locks.h" struct config_file; struct delegpt; @@ -50,6 +51,11 @@ struct delegpt; * Iterator forward zones structure */ struct iter_forwards { + /** lock on the forwards tree. + * When grabbing both this lock and the anchors.lock, this lock + * is grabbed first. When grabbing both this lock and the hints.lock + * this lock is grabbed first. */ + lock_rw_type lock; /** * Zones are stored in this tree. Sort order is specially chosen. * first sorted on qclass. Then on dname in nsec-like order, so that @@ -106,6 +112,8 @@ int forwards_apply_cfg(struct iter_forwards* fwd, struct config_file* cfg); /** * Find forward zone exactly by name + * The return value is contents of the forwards structure, caller should + * lock and unlock a readlock on the forwards structure. * @param fwd: forward storage. * @param qname: The qname of the query. * @param qclass: The qclass of the query. @@ -118,6 +126,8 @@ struct delegpt* forwards_find(struct iter_forwards* fwd, uint8_t* qname, * Find forward zone information * For this qname/qclass find forward zone information, returns delegation * point with server names and addresses, or NULL if no forwarding is needed. + * The return value is contents of the forwards structure, caller should + * lock and unlock a readlock on the forwards structure. * * @param fwd: forward storage. * @param qname: The qname of the query. @@ -147,6 +157,7 @@ int forwards_next_root(struct iter_forwards* fwd, uint16_t* qclass); /** * Get memory in use by forward storage + * Locks and unlocks the structure. * @param fwd: forward storage. * @return bytes in use */ @@ -196,4 +207,13 @@ int forwards_add_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm); void forwards_delete_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm); +/** + * 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 4f86f3676..5a95b78cd 100644 --- a/iterator/iter_hints.c +++ b/iterator/iter_hints.c @@ -57,6 +57,8 @@ hints_create(void) sizeof(struct iter_hints)); if(!hints) return NULL; + lock_rw_init(&hints->lock); + lock_protect(&hints->lock, &hints->tree, sizeof(hints->tree)); return hints; } @@ -83,6 +85,7 @@ hints_delete(struct iter_hints* hints) { if(!hints) return; + lock_rw_destroy(&hints->lock); hints_del_tree(hints); free(hints); } @@ -438,29 +441,39 @@ read_root_hints_list(struct iter_hints* hints, struct config_file* cfg) int hints_apply_cfg(struct iter_hints* hints, struct config_file* cfg) { + lock_rw_wrlock(&hints->lock); hints_del_tree(hints); name_tree_init(&hints->tree); - + /* read root hints */ - if(!read_root_hints_list(hints, cfg)) + if(!read_root_hints_list(hints, cfg)) { + lock_rw_unlock(&hints->lock); return 0; + } /* read stub hints */ - if(!read_stubs(hints, cfg)) + if(!read_stubs(hints, cfg)) { + lock_rw_unlock(&hints->lock); return 0; + } /* use fallback compiletime root hints */ if(!hints_lookup_root(hints, LDNS_RR_CLASS_IN)) { struct delegpt* dp = compile_time_root_prime(cfg->do_ip4, cfg->do_ip6); verbose(VERB_ALGO, "no config, using builtin root hints."); - if(!dp) + if(!dp) { + lock_rw_unlock(&hints->lock); return 0; - if(!hints_insert(hints, LDNS_RR_CLASS_IN, dp, 0)) + } + if(!hints_insert(hints, LDNS_RR_CLASS_IN, dp, 0)) { + lock_rw_unlock(&hints->lock); return 0; + } } name_tree_init_parents(&hints->tree); + lock_rw_unlock(&hints->lock); return 1; } @@ -524,10 +537,12 @@ hints_get_mem(struct iter_hints* hints) size_t s; struct iter_hints_stub* p; if(!hints) return 0; + lock_rw_rdlock(&hints->lock); s = sizeof(*hints); RBTREE_FOR(p, struct iter_hints_stub*, &hints->tree) { s += sizeof(*p) + delegpt_get_mem(p->dp); } + lock_rw_unlock(&hints->lock); return s; } @@ -560,3 +575,14 @@ hints_delete_stub(struct iter_hints* hints, uint16_t c, uint8_t* nm) hints_stub_free(z); name_tree_init_parents(&hints->tree); } + +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 06b4b9667..6f6b6dc68 100644 --- a/iterator/iter_hints.h +++ b/iterator/iter_hints.h @@ -43,6 +43,7 @@ #ifndef ITERATOR_ITER_HINTS_H #define ITERATOR_ITER_HINTS_H #include "util/storage/dnstree.h" +#include "util/locks.h" struct iter_env; struct config_file; struct delegpt; @@ -51,6 +52,10 @@ struct delegpt; * Iterator hints structure */ struct iter_hints { + /** lock on the forwards tree. + * When grabbing both this lock and the anchors.lock, this lock + * is grabbed first. */ + lock_rw_type lock; /** * Hints are stored in this tree. Sort order is specially chosen. * first sorted on qclass. Then on dname in nsec-like order, so that @@ -96,6 +101,8 @@ int hints_apply_cfg(struct iter_hints* hints, struct config_file* cfg); /** * Find root hints for the given class. + * The return value is contents of the hints structure, caller should + * lock and unlock a readlock on the hints structure. * @param hints: hint storage. * @param qclass: class for which root hints are requested. host order. * @return: NULL if no hints, or a ptr to stored hints. @@ -118,6 +125,8 @@ int hints_next_root(struct iter_hints* hints, uint16_t* qclass); * Given a qname/qclass combination, and the delegation point from the cache * for this qname/qclass, determine if this combination indicates that a * stub hint exists and must be primed. + * The return value is contents of the hints structure, caller should + * lock and unlock a readlock on the hints structure. * * @param hints: hint storage. * @param qname: The qname that generated the delegation point. @@ -131,6 +140,7 @@ struct iter_hints_stub* hints_lookup_stub(struct iter_hints* hints, /** * Get memory in use by hints + * Locks and unlocks the structure. * @param hints: hint storage. * @return bytes in use */ @@ -158,4 +168,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); +/** + * 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 10a8ec3eb..79a6e3cb0 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -1284,8 +1284,15 @@ iter_get_next_root(struct iter_hints* hints, struct iter_forwards* fwd, uint16_t* c) { uint16_t c1 = *c, c2 = *c; - int r1 = hints_next_root(hints, &c1); - int r2 = forwards_next_root(fwd, &c2); + int r1, r2; + + lock_rw_rdlock(&fwd->lock); + lock_rw_rdlock(&hints->lock); + r1 = hints_next_root(hints, &c1); + r2 = forwards_next_root(fwd, &c2); + lock_rw_unlock(&fwd->lock); + lock_rw_unlock(&hints->lock); + if(!r1 && !r2) /* got none, end of list */ return 0; else if(!r1) /* got one, return that */ @@ -1450,12 +1457,15 @@ int iter_dp_cangodown(struct query_info* qinfo, struct delegpt* dp) int iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, - uint8_t** retdpname, size_t* retdpnamelen) + uint8_t** retdpname, size_t* retdpnamelen, uint8_t* dpname_storage, + size_t dpname_storage_len) { struct iter_hints_stub *stub; struct delegpt *dp; /* Check for stub. */ + lock_rw_rdlock(&qstate->env->fwds->lock); + lock_rw_rdlock(&qstate->env->hints->lock); stub = hints_lookup_stub(qstate->env->hints, qinf->qname, qinf->qclass, NULL); dp = forwards_lookup(qstate->env->fwds, qinf->qname, qinf->qclass); @@ -1472,7 +1482,9 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, /* check stub */ if (stub != NULL && stub->dp != NULL) { - if(stub->dp->no_cache) { + int stub_no_cache = stub->dp->no_cache; + lock_rw_unlock(&qstate->env->fwds->lock); + if(stub_no_cache) { char qname[255+1]; char dpname[255+1]; dname_str(qinf->qname, qname); @@ -1480,15 +1492,27 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, verbose(VERB_ALGO, "stub for %s %s has no_cache", qname, dpname); } if(retdpname) { - *retdpname = stub->dp->name; + if(stub->dp->namelen > dpname_storage_len) { + verbose(VERB_ALGO, "no cache stub dpname too long"); + lock_rw_unlock(&qstate->env->hints->lock); + *retdpname = NULL; + *retdpnamelen = 0; + return stub_no_cache; + } + memmove(dpname_storage, stub->dp->name, + stub->dp->namelen); + *retdpname = dpname_storage; *retdpnamelen = stub->dp->namelen; } - return (stub->dp->no_cache); + lock_rw_unlock(&qstate->env->hints->lock); + return stub_no_cache; } /* Check for forward. */ if (dp) { - if(dp->no_cache) { + int dp_no_cache = dp->no_cache; + lock_rw_unlock(&qstate->env->hints->lock); + if(dp_no_cache) { char qname[255+1]; char dpname[255+1]; dname_str(qinf->qname, qname); @@ -1496,11 +1520,22 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, verbose(VERB_ALGO, "forward for %s %s has no_cache", qname, dpname); } if(retdpname) { - *retdpname = dp->name; + if(dp->namelen > dpname_storage_len) { + verbose(VERB_ALGO, "no cache dpname too long"); + lock_rw_unlock(&qstate->env->fwds->lock); + *retdpname = NULL; + *retdpnamelen = 0; + return dp_no_cache; + } + memmove(dpname_storage, dp->name, dp->namelen); + *retdpname = dpname_storage; *retdpnamelen = dp->namelen; } - return (dp->no_cache); + lock_rw_unlock(&qstate->env->fwds->lock); + return dp_no_cache; } + lock_rw_unlock(&qstate->env->fwds->lock); + lock_rw_unlock(&qstate->env->hints->lock); if(retdpname) { *retdpname = NULL; *retdpnamelen = 0; diff --git a/iterator/iter_utils.h b/iterator/iter_utils.h index fa860fa68..4024629e6 100644 --- a/iterator/iter_utils.h +++ b/iterator/iter_utils.h @@ -407,10 +407,14 @@ int iter_dp_cangodown(struct query_info* qinfo, struct delegpt* dp); * Used for NXDOMAIN checks, above that it is an nxdomain from a * different server and zone. You can pass NULL to not get it. * @param retdpnamelen: returns the length of the dpname. + * @param dpname_storage: this is where the dpname buf is stored, if any. + * So that caller can manage the buffer. + * @param dpname_storage_len: size of dpname_storage buffer. * @return true if no_cache is set in stub or fwd. */ int iter_stub_fwd_no_cache(struct module_qstate *qstate, - struct query_info *qinf, uint8_t** retdpname, size_t* retdpnamelen); + struct query_info *qinf, uint8_t** retdpname, size_t* retdpnamelen, + uint8_t* dpname_storage, size_t dpname_storage_len); /** * Set support for IP4 and IP6 depending on outgoing interfaces diff --git a/iterator/iterator.c b/iterator/iterator.c index 6ec8af401..fbc0cec32 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -679,7 +679,8 @@ errinf_reply(struct module_qstate* qstate, struct iter_qstate* iq) /** see if last resort is possible - does config allow queries to parent */ static int can_have_last_resort(struct module_env* env, uint8_t* nm, size_t nmlen, - uint16_t qclass, struct delegpt** retdp) + uint16_t qclass, int* have_dp, struct delegpt** retdp, + struct regional* region) { struct delegpt* fwddp; struct iter_hints_stub* stub; @@ -687,21 +688,29 @@ can_have_last_resort(struct module_env* env, uint8_t* nm, size_t nmlen, /* do not process a last resort (the parent side) if a stub * or forward is configured, because we do not want to go 'above' * the configured servers */ + lock_rw_rdlock(&env->hints->lock); if(!dname_is_root(nm) && (stub = (struct iter_hints_stub*) name_tree_find(&env->hints->tree, nm, nmlen, labs, qclass)) && /* has_parent side is turned off for stub_first, where we * are allowed to go to the parent */ stub->dp->has_parent_side_NS) { - if(retdp) *retdp = stub->dp; + if(retdp) *retdp = delegpt_copy(stub->dp, region); + lock_rw_unlock(&env->hints->lock); + if(have_dp) *have_dp = 1; return 0; } + lock_rw_unlock(&env->hints->lock); + lock_rw_rdlock(&env->fwds->lock); if((fwddp = forwards_find(env->fwds, nm, qclass)) && /* has_parent_side is turned off for forward_first, where * we are allowed to go to the parent */ fwddp->has_parent_side_NS) { - if(retdp) *retdp = fwddp; + if(retdp) *retdp = delegpt_copy(fwddp, region); + lock_rw_unlock(&env->fwds->lock); + if(have_dp) *have_dp = 1; return 0; } + lock_rw_unlock(&env->fwds->lock); return 1; } @@ -880,8 +889,10 @@ prime_root(struct module_qstate* qstate, struct iter_qstate* iq, int id, verbose(VERB_DETAIL, "priming . %s NS", sldns_lookup_by_id(sldns_rr_classes, (int)qclass)? sldns_lookup_by_id(sldns_rr_classes, (int)qclass)->name:"??"); + lock_rw_rdlock(&qstate->env->hints->lock); dp = hints_lookup_root(qstate->env->hints, qclass); if(!dp) { + lock_rw_unlock(&qstate->env->hints->lock); verbose(VERB_ALGO, "Cannot prime due to lack of hints"); return 0; } @@ -890,6 +901,7 @@ prime_root(struct module_qstate* qstate, struct iter_qstate* iq, int id, if(!generate_sub_request((uint8_t*)"\000", 1, LDNS_RR_TYPE_NS, qclass, qstate, id, iq, QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0, 0)) { + lock_rw_unlock(&qstate->env->hints->lock); verbose(VERB_ALGO, "could not prime root"); return 0; } @@ -900,6 +912,7 @@ prime_root(struct module_qstate* qstate, struct iter_qstate* iq, int id, * copy dp, it is now part of the root prime query. * dp was part of in the fixed hints structure. */ subiq->dp = delegpt_copy(dp, subq->region); + lock_rw_unlock(&qstate->env->hints->lock); if(!subiq->dp) { log_err("out of memory priming root, copydp"); fptr_ok(fptr_whitelist_modenv_kill_sub( @@ -911,6 +924,8 @@ prime_root(struct module_qstate* qstate, struct iter_qstate* iq, int id, subiq->num_target_queries = 0; subiq->dnssec_expected = iter_indicates_dnssec( qstate->env, subiq->dp, NULL, subq->qinfo.qclass); + } else { + lock_rw_unlock(&qstate->env->hints->lock); } /* this module stops, our submodule starts, and does the query. */ @@ -943,16 +958,21 @@ prime_stub(struct module_qstate* qstate, struct iter_qstate* iq, int id, struct module_qstate* subq; if(!qname) return 0; + lock_rw_rdlock(&qstate->env->hints->lock); stub = hints_lookup_stub(qstate->env->hints, qname, qclass, iq->dp); /* The stub (if there is one) does not need priming. */ - if(!stub) + if(!stub) { + lock_rw_unlock(&qstate->env->hints->lock); return 0; + } stub_dp = stub->dp; /* if we have an auth_zone dp, and stub is equal, don't prime stub * yet, unless we want to fallback and avoid the auth_zone */ if(!iq->auth_zone_avoid && iq->dp && iq->dp->auth_dp && - query_dname_compare(iq->dp->name, stub_dp->name) == 0) + query_dname_compare(iq->dp->name, stub_dp->name) == 0) { + lock_rw_unlock(&qstate->env->hints->lock); return 0; + } /* is it a noprime stub (always use) */ if(stub->noprime) { @@ -961,13 +981,14 @@ prime_stub(struct module_qstate* qstate, struct iter_qstate* iq, int id, /* copy the dp out of the fixed hints structure, so that * it can be changed when servicing this query */ iq->dp = delegpt_copy(stub_dp, qstate->region); + lock_rw_unlock(&qstate->env->hints->lock); if(!iq->dp) { log_err("out of memory priming stub"); errinf(qstate, "malloc failure, priming stub"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); return 1; /* return 1 to make module stop, with error */ } - log_nametypeclass(VERB_DETAIL, "use stub", stub_dp->name, + log_nametypeclass(VERB_DETAIL, "use stub", iq->dp->name, LDNS_RR_TYPE_NS, qclass); return r; } @@ -981,6 +1002,7 @@ prime_stub(struct module_qstate* qstate, struct iter_qstate* iq, int id, if(!generate_sub_request(stub_dp->name, stub_dp->namelen, LDNS_RR_TYPE_NS, qclass, qstate, id, iq, QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0, 0)) { + lock_rw_unlock(&qstate->env->hints->lock); verbose(VERB_ALGO, "could not prime stub"); errinf(qstate, "could not generate lookup for stub prime"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); @@ -993,6 +1015,7 @@ prime_stub(struct module_qstate* qstate, struct iter_qstate* iq, int id, /* Set the initial delegation point to the hint. */ /* make copy to avoid use of stub dp by different qs/threads */ subiq->dp = delegpt_copy(stub_dp, subq->region); + lock_rw_unlock(&qstate->env->hints->lock); if(!subiq->dp) { log_err("out of memory priming stub, copydp"); fptr_ok(fptr_whitelist_modenv_kill_sub( @@ -1009,6 +1032,8 @@ prime_stub(struct module_qstate* qstate, struct iter_qstate* iq, int id, subiq->wait_priming_stub = 1; subiq->dnssec_expected = iter_indicates_dnssec( qstate->env, subiq->dp, NULL, subq->qinfo.qclass); + } else { + lock_rw_unlock(&qstate->env->hints->lock); } /* this module stops, our submodule starts, and does the query. */ @@ -1181,7 +1206,7 @@ generate_ns_check(struct module_qstate* qstate, struct iter_qstate* iq, int id) if(iq->depth == ie->max_dependency_depth) return; if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, - iq->qchase.qclass, NULL)) + iq->qchase.qclass, NULL, NULL, NULL)) return; /* is this query the same as the nscheck? */ if(qstate->qinfo.qtype == LDNS_RR_TYPE_NS && @@ -1302,12 +1327,16 @@ forward_request(struct module_qstate* qstate, struct iter_qstate* iq) if( (iq->qchase.qtype == LDNS_RR_TYPE_DS || iq->refetch_glue) && !dname_is_root(iq->qchase.qname)) dname_remove_label(&delname, &delnamelen); + lock_rw_rdlock(&qstate->env->fwds->lock); dp = forwards_lookup(qstate->env->fwds, delname, iq->qchase.qclass); - if(!dp) + if(!dp) { + lock_rw_unlock(&qstate->env->fwds->lock); return 0; + } /* send recursion desired to forward addr */ iq->chase_flags |= BIT_RD; iq->dp = delegpt_copy(dp, qstate->region); + lock_rw_unlock(&qstate->env->fwds->lock); /* iq->dp checked by caller */ verbose(VERB_ALGO, "forwarding request"); return 1; @@ -1335,6 +1364,7 @@ static int processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id) { + uint8_t dpname_storage[LDNS_MAX_DOMAINLEN+1]; uint8_t* delname, *dpname=NULL; size_t delnamelen, dpnamelen=0; struct dns_msg* msg = NULL; @@ -1381,7 +1411,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, if (iq->refetch_glue && iq->dp && !can_have_last_resort(qstate->env, iq->dp->name, - iq->dp->namelen, iq->qchase.qclass, NULL)) { + iq->dp->namelen, iq->qchase.qclass, NULL, NULL, NULL)) { iq->refetch_glue = 0; } @@ -1442,7 +1472,8 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, } } - if (iter_stub_fwd_no_cache(qstate, &iq->qchase, &dpname, &dpnamelen)) { + if (iter_stub_fwd_no_cache(qstate, &iq->qchase, &dpname, &dpnamelen, + dpname_storage, sizeof(dpname_storage))) { /* Asked to not query cache. */ verbose(VERB_ALGO, "no-cache set, going to the network"); qstate->no_cache_lookup = 1; @@ -1573,7 +1604,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, } if(iq->qchase.qtype == LDNS_RR_TYPE_DS || iq->refetch_glue || (iq->qchase.qtype == LDNS_RR_TYPE_NS && qstate->prefetch_leeway - && can_have_last_resort(qstate->env, delname, delnamelen, iq->qchase.qclass, NULL))) { + && can_have_last_resort(qstate->env, delname, delnamelen, iq->qchase.qclass, NULL, NULL, NULL))) { /* remove first label from delname, root goes to hints, * but only to fetch glue, not for qtype=DS. */ /* also when prefetching an NS record, fetch it again from @@ -1615,19 +1646,24 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, break; /* got noprime-stub-zone, continue */ else if(r) return 0; /* stub prime request made */ + lock_rw_rdlock(&qstate->env->fwds->lock); if(forwards_lookup_root(qstate->env->fwds, iq->qchase.qclass)) { + lock_rw_unlock(&qstate->env->fwds->lock); /* forward zone root, no root prime needed */ /* fill in some dp - safety belt */ + lock_rw_rdlock(&qstate->env->hints->lock); iq->dp = hints_lookup_root(qstate->env->hints, iq->qchase.qclass); if(!iq->dp) { + lock_rw_unlock(&qstate->env->hints->lock); log_err("internal error: no hints dp"); errinf(qstate, "no hints for this class"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } iq->dp = delegpt_copy(iq->dp, qstate->region); + lock_rw_unlock(&qstate->env->hints->lock); if(!iq->dp) { log_err("out of memory in safety belt"); errinf(qstate, "malloc failure, in safety belt"); @@ -1636,6 +1672,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, } return next_state(iq, INIT_REQUEST_2_STATE); } + lock_rw_unlock(&qstate->env->fwds->lock); /* Note that the result of this will set a new * DelegationPoint based on the result of priming. */ if(!prime_root(qstate, iq, id, iq->qchase.qclass)) @@ -1667,15 +1704,13 @@ 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)) { - struct delegpt* retdp = NULL; - if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, &retdp)) { - if(retdp) { + 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) { verbose(VERB_QUERY, "cache has stub " "or fwd but no addresses, " "fallback to config"); - iq->dp = delegpt_copy(retdp, - qstate->region); - if(!iq->dp) { + if(have_dp && !iq->dp) { log_err("out of memory in " "stub/fwd fallback"); errinf(qstate, "malloc failure, for fallback to config"); @@ -1697,16 +1732,19 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, /* use safety belt */ verbose(VERB_QUERY, "Cache has root NS but " "no addresses. Fallback to the safety belt."); + lock_rw_rdlock(&qstate->env->hints->lock); iq->dp = hints_lookup_root(qstate->env->hints, iq->qchase.qclass); /* note deleg_msg is from previous lookup, * but RD is on, so it is not used */ if(!iq->dp) { + lock_rw_unlock(&qstate->env->hints->lock); log_err("internal error: no hints dp"); return error_response(qstate, id, LDNS_RCODE_REFUSED); } iq->dp = delegpt_copy(iq->dp, qstate->region); + lock_rw_unlock(&qstate->env->hints->lock); if(!iq->dp) { log_err("out of memory in safety belt"); errinf(qstate, "malloc failure, in safety belt, for root"); @@ -1769,6 +1807,7 @@ processInitRequest2(struct module_qstate* qstate, struct iter_qstate* iq, } /* Do not send queries above stub, do not set delname to dp if * this is above stub without stub-first. */ + lock_rw_rdlock(&qstate->env->hints->lock); stub = hints_lookup_stub( qstate->env->hints, iq->qchase.qname, iq->qchase.qclass, iq->dp); @@ -1777,6 +1816,7 @@ processInitRequest2(struct module_qstate* qstate, struct iter_qstate* iq, delname = iq->dp->name; delnamelen = iq->dp->namelen; } + lock_rw_unlock(&qstate->env->hints->lock); } if(iq->qchase.qtype == LDNS_RR_TYPE_DS || iq->refetch_glue) { if(!dname_is_root(delname)) @@ -2080,7 +2120,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, log_assert(iq->dp); if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, - iq->qchase.qclass, NULL)) { + iq->qchase.qclass, NULL, NULL, NULL)) { /* fail -- no more targets, no more hope of targets, no hope * of a response. */ errinf(qstate, "all the configured stub or forward servers failed,"); @@ -2090,8 +2130,9 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(!iq->dp->has_parent_side_NS && dname_is_root(iq->dp->name)) { - struct delegpt* p = hints_lookup_root(qstate->env->hints, - iq->qchase.qclass); + struct delegpt* p; + lock_rw_rdlock(&qstate->env->hints->lock); + p = hints_lookup_root(qstate->env->hints, iq->qchase.qclass); if(p) { struct delegpt_addr* a; iq->chase_flags &= ~BIT_RD; /* go to authorities */ @@ -2106,6 +2147,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, a->lame, a->tls_auth_name, -1, NULL); } } + lock_rw_unlock(&qstate->env->hints->lock); iq->dp->has_parent_side_NS = 1; } else if(!iq->dp->has_parent_side_NS) { if(!iter_lookup_parent_NS_from_cache(qstate->env, iq->dp, @@ -2182,7 +2224,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, if( ((ie->supports_ipv6 && !ns->done_pside6) || ((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4)) && !can_have_last_resort(qstate->env, ns->name, ns->namelen, - iq->qchase.qclass, NULL)) { + 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); diff --git a/iterator/iterator.h b/iterator/iterator.h index e253f3f7e..f748d47f6 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; diff --git a/libunbound/context.c b/libunbound/context.c index f7c0a2cd5..a319f59cd 100644 --- a/libunbound/context.c +++ b/libunbound/context.c @@ -53,6 +53,8 @@ #include "util/storage/slabhash.h" #include "util/edns.h" #include "sldns/sbuffer.h" +#include "iterator/iter_fwd.h" +#include "iterator/iter_hints.h" int context_finalize(struct ub_ctx* ctx) @@ -85,6 +87,12 @@ context_finalize(struct ub_ctx* ctx) if(!auth_zones_apply_cfg(ctx->env->auth_zones, cfg, 1, &is_rpz, ctx->env, &ctx->mods)) return UB_INITFAIL; + if(!(ctx->env->fwds = forwards_create()) || + !forwards_apply_cfg(ctx->env->fwds, cfg)) + return UB_INITFAIL; + if(!(ctx->env->hints = hints_create()) || + !hints_apply_cfg(ctx->env->hints, cfg)) + return UB_INITFAIL; if(!edns_strings_apply_cfg(ctx->env->edns_strings, cfg)) return UB_INITFAIL; if(!slabhash_is_size(ctx->env->msg_cache, cfg->msg_cache_size, diff --git a/libunbound/libunbound.c b/libunbound/libunbound.c index 80a82bb47..e7636d641 100644 --- a/libunbound/libunbound.c +++ b/libunbound/libunbound.c @@ -66,6 +66,8 @@ #include "services/authzone.h" #include "services/listen_dnsport.h" #include "sldns/sbuffer.h" +#include "iterator/iter_fwd.h" +#include "iterator/iter_hints.h" #ifdef HAVE_PTHREAD #include #endif @@ -379,6 +381,8 @@ ub_ctx_delete(struct ub_ctx* ctx) config_delete(ctx->env->cfg); edns_known_options_delete(ctx->env); edns_strings_delete(ctx->env->edns_strings); + forwards_delete(ctx->env->fwds); + hints_delete(ctx->env->hints); auth_zones_delete(ctx->env->auth_zones); free(ctx->env); } diff --git a/libunbound/libworker.c b/libunbound/libworker.c index 0e1c40393..788067852 100644 --- a/libunbound/libworker.c +++ b/libunbound/libworker.c @@ -70,8 +70,6 @@ #include "util/data/msgreply.h" #include "util/data/msgencode.h" #include "util/tube.h" -#include "iterator/iter_fwd.h" -#include "iterator/iter_hints.h" #include "sldns/sbuffer.h" #include "sldns/str2wire.h" #ifdef USE_DNSTAP @@ -100,8 +98,6 @@ libworker_delete_env(struct libworker* w) !w->is_bg || w->is_bg_thread); sldns_buffer_free(w->env->scratch_buffer); regional_destroy(w->env->scratch); - forwards_delete(w->env->fwds); - hints_delete(w->env->hints); ub_randfree(w->env->rnd); free(w->env); } @@ -159,30 +155,19 @@ libworker_setup(struct ub_ctx* ctx, int is_bg, struct ub_event_base* eb) } w->env->scratch = regional_create_custom(cfg->msg_buffer_size); w->env->scratch_buffer = sldns_buffer_new(cfg->msg_buffer_size); - w->env->fwds = forwards_create(); - if(w->env->fwds && !forwards_apply_cfg(w->env->fwds, cfg)) { - forwards_delete(w->env->fwds); - w->env->fwds = NULL; - } - w->env->hints = hints_create(); - if(w->env->hints && !hints_apply_cfg(w->env->hints, cfg)) { - hints_delete(w->env->hints); - w->env->hints = NULL; - } #ifdef HAVE_SSL w->sslctx = connect_sslctx_create(NULL, NULL, cfg->tls_cert_bundle, cfg->tls_win_cert); if(!w->sslctx) { /* to make the setup fail after unlock */ - hints_delete(w->env->hints); - w->env->hints = NULL; + sldns_buffer_free(w->env->scratch_buffer); + w->env->scratch_buffer = NULL; } #endif if(!w->is_bg || w->is_bg_thread) { lock_basic_unlock(&ctx->cfglock); } - if(!w->env->scratch || !w->env->scratch_buffer || !w->env->fwds || - !w->env->hints) { + if(!w->env->scratch || !w->env->scratch_buffer) { libworker_delete(w); return NULL; } @@ -1072,3 +1057,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/pythonmod/interface.i b/pythonmod/interface.i index d9839fc38..5bc7e8492 100644 --- a/pythonmod/interface.i +++ b/pythonmod/interface.i @@ -1455,10 +1455,14 @@ struct delegpt* find_delegation(struct module_qstate* qstate, char *nm, size_t n dname_str((uint8_t*)nm, b); continue; } + lock_rw_rdlock(&qstate->env->hints->lock); stub = hints_lookup_stub(qstate->env->hints, qinfo.qname, qinfo.qclass, dp); if (stub) { - return stub->dp; + struct delegpt* stubdp = delegpt_copy(stub->dp, region); + lock_rw_unlock(&qstate->env->hints->lock); + return stubdp; } else { + lock_rw_unlock(&qstate->env->hints->lock); return dp; } } diff --git a/respip/respip.c b/respip/respip.c index 942e082b9..d698954b6 100644 --- a/respip/respip.c +++ b/respip/respip.c @@ -1326,3 +1326,12 @@ 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; +} diff --git a/respip/respip.h b/respip/respip.h index e4ab5cc9c..f43052e22 100644 --- a/respip/respip.h +++ b/respip/respip.h @@ -302,4 +302,9 @@ 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); + #endif /* RESPIP_RESPIP_H */ diff --git a/services/localzone.c b/services/localzone.c index 51056c8ff..ec1123054 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -2203,3 +2203,26 @@ 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; +} diff --git a/services/localzone.h b/services/localzone.h index 4456893ee..7a51ec59d 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -641,4 +641,9 @@ 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); + #endif /* SERVICES_LOCALZONE_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..990212496 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 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 57b0787db..3cc2f1c3f 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/fake_event.c b/testcode/fake_event.c index 13970c377..45911fe29 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -1906,4 +1906,24 @@ void http2_stream_add_meshstate(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 ec627cc8d..83a2fddac 100644 --- a/testcode/testbound.c +++ b/testcode/testbound.c @@ -615,3 +615,9 @@ void listen_desetup_locks(void) { /* nothing */ } + +void fast_reload_printq_list_delete( + struct fast_reload_printq* ATTR_UNUSED(list)) +{ + /* nothing */ +} 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..cc148053d --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf @@ -0,0 +1,74 @@ +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 + +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 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..98f43c0ef --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf2 @@ -0,0 +1,74 @@ +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 + +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 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..ac43a7e51 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns1 @@ -0,0 +1,251 @@ +; 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 + +; 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..01c5bf448 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns2 @@ -0,0 +1,251 @@ +; 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 + +; 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..c44b8c27c --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post @@ -0,0 +1,21 @@ +# #-- 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 +. ../common.sh +kill_pid $NS1_PID +kill_pid $NS2_PID +kill_pid $UNBOUND_PID +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 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..2fb705e74 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.pre @@ -0,0 +1,51 @@ +# #-- 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 + +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..b1212c96b --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.test @@ -0,0 +1,109 @@ +# #-- 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 + +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 + +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 07a1a3811..6e282c8d0 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -2743,3 +2743,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 faed1071e..5496ad533 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -1375,4 +1375,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/fptr_wlist.c b/util/fptr_wlist.c index a792a3429..8adc1c3bd 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 8a9da3f93..8e7aca635 100644 --- a/util/module.h +++ b/util/module.h @@ -511,10 +511,10 @@ struct module_env { /** auth zones */ struct auth_zones* auth_zones; /** Mapping of forwarding zones to targets. - * iterator forwarder information. per-thread, created by worker */ + * iterator forwarder information. */ struct iter_forwards* fwds; /** - * iterator forwarder information. per-thread, created by worker. + * iterator stub information. * The hints -- these aren't stored in the cache because they don't * expire. The hints are always used to "prime" the cache. Note * that both root hints and stub zone "hints" are stored in this