* - fast-reload review, respip set can be null from a view.
* - fast-reload review, typos.
* - fast-reload review, keep clang static analyzer happy.
* - fast-reload review, don't forget to copy tag_actions.
* - fast-reload review, less indentation.
* - fast-reload review, don't leak respip_actions when reloading.
* - fast-reload review, protect NULL pointer dereference in get_mem
functions.
* - fast-reload review, add fast_reload_most_options.tdir to test most
options with high verbosity when fast reloading.
* - fast-reload review, don't skip new line on long error printouts.
* - fast-reload review, typo.
* - fast-reload review, use new_z for consistency.
* - fast-reload review, nit for unlock ordering to make eye comparison
with the lock counterpart easier.
* - fast-reload review, in case of error the sockets are already closed.
* - fast-reload review, identation.
* - fast-reload review, add static keywords.
* - fast-reload review, update unbound-control usage text.
* - fast-reload review, updates to the man page.
* fast reload. */
if(!fr_output_printf(fr, "The config changes items that are "
"not compatible with fast_reload, perhaps do reload "
- "or restart: %s\n", changed_str))
+ "or restart: %s", changed_str) ||
+ !fr_output_printf(fr, "\n"))
return 0;
fr_send_notification(fr, fast_reload_notification_printout);
return 0;
* to be able to update the variables. */
if(!fr_output_printf(fr, "The config changes items that need "
"the fast_reload +p option, for nopause, "
- "disabled to be reloaded: %s\n", changed_str))
+ "disabled to be reloaded: %s", changed_str) ||
+ !fr_output_printf(fr, "\n"))
return 0;
fr_send_notification(fr, fast_reload_notification_printout);
return 0;
p1 = p1->next;
p2 = p2->next;
}
- if(!p2 && !p2)
+ if(!p1 && !p2)
return 1;
return 0;
}
struct auth_xfer* old_xfr, *new_xfr;
lock_rw_rdlock(&new_z->lock);
lock_rw_rdlock(&old_z->lock);
- new_xfr = auth_xfer_find(ct->auth_zones, old_z->name,
- old_z->namelen, old_z->dclass);
+ new_xfr = auth_xfer_find(ct->auth_zones, new_z->name,
+ new_z->namelen, new_z->dclass);
old_xfr = auth_xfer_find(env->auth_zones, old_z->name,
old_z->namelen, old_z->dclass);
if(new_xfr) {
lock_rw_unlock(&env->respip_set->lock);
lock_rw_unlock(&ct->local_zones->lock);
lock_rw_unlock(&daemon->local_zones->lock);
- lock_rw_unlock(&env->auth_zones->lock);
lock_rw_unlock(&ct->auth_zones->lock);
- lock_rw_unlock(&env->auth_zones->rpz_lock);
+ lock_rw_unlock(&env->auth_zones->lock);
lock_rw_unlock(&ct->auth_zones->rpz_lock);
- lock_rw_unlock(&env->fwds->lock);
+ lock_rw_unlock(&env->auth_zones->rpz_lock);
lock_rw_unlock(&ct->fwds->lock);
- lock_rw_unlock(&env->hints->lock);
+ lock_rw_unlock(&env->fwds->lock);
lock_rw_unlock(&ct->hints->lock);
+ lock_rw_unlock(&env->hints->lock);
if(ct->anchors) {
lock_basic_unlock(&ct->anchors->lock);
lock_basic_unlock(&env->anchors->lock);
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;
}
if(!outevent)
continue;
+ /* keep static analyzer happy; send(-1,..) */
+ log_assert(fr->commpair[0] >= 0);
ret = send(fr->commpair[0], ((char*)&cmd)+bcount,
sizeof(cmd)-bcount, 0);
if(ret == -1) {
if(ret == -1) {
if(
#ifndef USE_WINSOCK
- errno == EINTR || errno == EAGAIN
+ errno == EINTR || errno == EAGAIN
# ifdef EWOULDBLOCK
- || errno == EWOULDBLOCK
+ || errno == EWOULDBLOCK
# endif
#else
- WSAGetLastError() == WSAEINTR ||
- WSAGetLastError() == WSAEINPROGRESS ||
- WSAGetLastError() == WSAEWOULDBLOCK
+ WSAGetLastError() == WSAEINTR ||
+ WSAGetLastError() == WSAEINPROGRESS ||
+ WSAGetLastError() == WSAEWOULDBLOCK
#endif
- )
+ )
continue; /* Try again */
log_err("worker reload ack: recv failed: %s",
sock_strerror(errno));
}
/** Pick up the worker mesh changes, after fast reload. */
-void
+static void
fr_worker_pickup_mesh(struct worker* worker)
{
struct mesh_area* mesh = worker->env.mesh;
* They are only incremented when an accept is performed on a tcp comm point.
* @param front: listening comm ports of the worker.
*/
-void
+static void
tcl_remove_old(struct listen_dnsport* front)
{
struct listen_list* l;
{
enum fast_reload_notification status;
ssize_t ret;
+ /* keep static analyzer happy; recv(-1,..) */
+ log_assert(fr->commpair[0] >= 0);
ret = recv(fr->commpair[0],
((char*)&fr->service_read_cmd)+fr->service_read_cmd_count,
sizeof(fr->service_read_cmd)-fr->service_read_cmd_count, 0);
/**
* Send fast-reload acknowledgement to the mainthread in one byte.
- * This signals that this works has received the previous command.
+ * This signals that this worker has received the previous command.
* The worker is waiting if that is after a reload_stop command.
* Or the worker has briefly processed the event itself, and in doing so
* released data pointers to old config, after a reload_poll command.
dnstap-send-identity, dnstap-send-version, dnstap-identity, and
dnstap-version can be loaded when '+p' is not used.
.IP
-The '+v' option makes the output verbose. With '+vv' it is more verbose.
-That includes the time it took to do the reload. And with more verbose output
-the amount of memory that was allocated temporarily to perform the reload,
-this amount of memory can be big if the config has large contents. In the
-timing output the 'reload' time is the time during which the server was paused.
+The '+v' option makes the output verbose which includes the time it took to do
+the reload.
+With '+vv' it is more verbose which includes the amount of memory that was
+allocated temporarily to perform the reload; this amount of memory can be big
+if the config has large contents.
+In the timing output the 'reload' time is the time during which the server was
+paused.
.IP
The '+p' option makes the reload not pause threads, they keep running.
Locks are acquired, but items are updated in sequence, so it is possible
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
+then the '+d' option is implicitly turned on with a warning printout, and
+queries are dropped.
+This is to stop references to the old tag information, by the old
queries. If the number of tags is increased in the newly loaded config, by
-adding tags at the end, then the '+d' option is not needed.
+adding tags at the end, then the implicit '+d' option is not needed.
.IP
For response ip, that is actions associated with IP addresses, and perhaps
intersected with access control tag and action information, those settings
socklen_t addrlen, int net, int create, const char* ipstr)
{
struct resp_addr* node;
+ log_assert(set);
node = (struct resp_addr*)addr_tree_find(&set->ip_tree, addr, addrlen, net);
if(!node && create) {
node = regional_alloc_zero(set->region, sizeof(*node));
respip_sockaddr_delete(struct respip_set* set, struct resp_addr* node)
{
struct resp_addr* prev;
+ log_assert(set);
prev = (struct resp_addr*)rbtree_previous((struct rbnode_type*)node);
lock_rw_destroy(&node->lock);
(void)rbtree_delete(&set->ip_tree, node);
struct sockaddr_storage addr;
int net;
socklen_t addrlen;
+ log_assert(set);
if(!netblockstrtoaddr(ipstr, 0, &addr, &addrlen, &net)) {
log_err("cannot parse netblock: '%s'", ipstr);
const uint8_t* taglist, size_t taglen)
{
struct resp_addr* node;
+ log_assert(set);
if(!(node=respip_find_or_create(set, ipstr, 1)))
return 0;
{
struct resp_addr* node;
enum respip_action action;
+ log_assert(set);
if(!(node=respip_find_or_create(set, ipstr, 1)))
return 0;
respip_data_cfg(struct respip_set* set, const char* ipstr, const char* rrstr)
{
struct resp_addr* node;
+ log_assert(set);
node=respip_find_or_create(set, ipstr, 0);
if(!node || node->action == respip_none) {
struct config_strbytelist* p;
struct config_str2list* pa;
struct config_str2list* pd;
+ log_assert(set);
set->tagname = tagname;
set->num_tags = num_tags;
struct resp_addr* ra;
struct sockaddr_storage ss;
socklen_t addrlen;
+ log_assert(rs);
lock_rw_rdlock(&rs->lock);
for(i=0; i<rep->an_numrrsets; i++) {
size_t respip_set_get_mem(struct respip_set* set)
{
- size_t m = sizeof(*set);
+ size_t m;
+ if(!set) return 0;
+ m = sizeof(*set);
lock_rw_rdlock(&set->lock);
m += regional_get_mem(set->region);
lock_rw_unlock(&set->lock);
size_t auth_zones_get_mem(struct auth_zones* zones)
{
- size_t m = sizeof(*zones);
+ size_t m;
+ if(!zones) return 0;
+ m = sizeof(*zones);
lock_rw_rdlock(&zones->rpz_lock);
lock_rw_rdlock(&zones->lock);
m += az_ztree_get_mem(zones);
size_t local_zones_get_mem(struct local_zones* zones)
{
struct local_zone* z;
- size_t m = sizeof(*zones);
+ size_t m;
+ if(!zones) return 0;
+ m = sizeof(*zones);
lock_rw_rdlock(&zones->lock);
RBTREE_FOR(z, struct local_zone*, &zones->ztree) {
m += local_zone_get_mem(z);
cinfo->taglen);
if(!client_info->taglist)
return NULL;
+ client_info->tag_actions = regional_alloc_init(region, cinfo->tag_actions,
+ cinfo->tag_actions_size);
+ if(!client_info->tag_actions)
+ return NULL;
client_info->tag_datas = regional_alloc_zero(region,
sizeof(struct config_strlist*)*cinfo->tag_datas_size);
if(!client_info->tag_datas)
{
struct mesh_state* s = NULL;
s = mesh_area_find(mesh, NULL, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0);
- if(s) {
- if(mesh_state_del_cb(s, cb, cb_arg)) {
- /* It was in the list and removed. */
- log_assert(mesh->num_reply_addrs > 0);
- mesh->num_reply_addrs--;
- if(!s->reply_list && !s->cb_list) {
- /* was a reply state, not anymore */
- log_assert(mesh->num_reply_states > 0);
- mesh->num_reply_states--;
- }
- if(!s->reply_list && !s->cb_list &&
- s->super_set.count == 0)
- mesh->num_detached_states++;
- }
+ if(!s) return;
+ if(!mesh_state_del_cb(s, cb, cb_arg)) return;
+
+ /* It was in the list and removed. */
+ log_assert(mesh->num_reply_addrs > 0);
+ mesh->num_reply_addrs--;
+ if(!s->reply_list && !s->cb_list) {
+ /* was a reply state, not anymore */
+ log_assert(mesh->num_reply_states > 0);
+ mesh->num_reply_states--;
+ }
+ if(!s->reply_list && !s->cb_list &&
+ s->super_set.count == 0) {
+ mesh->num_detached_states++;
}
}
size_t views_get_mem(struct views* vs)
{
struct view* v;
- size_t m = sizeof(struct views);
+ size_t m;
+ if(!vs) return 0;
+ m = sizeof(struct views);
lock_rw_rdlock(&vs->lock);
RBTREE_FOR(v, struct view*, &vs->vtree) {
m += view_get_mem(v);
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(" server processing, keeps cache, and changes\n");
+ printf(" most options; check unbound-control(8).\n");
+ printf(" +d drops running queries to keep consistency\n");
+ printf(" on changed options while reloading.\n");
+ printf(" +p does not pause threads for even faster\n");
+ printf(" reload but less options are supported\n");
+ printf(" ; check unbound-control(8).\n");
+ printf(" +v verbose output, it will include duration needed.\n");
+ printf(" +vv more verbose output, it will include memory needed.\n");
printf(" stats print statistics\n");
printf(" stats_noreset peek at statistics\n");
#ifdef HAVE_SHMGET
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
+echo "NS2_PORT=$NS2_PORT" >> .tpkg.var.test
# make config files
CONTROL_PATH=/tmp
--- /dev/null
+$ORIGIN auth.nlnetlabs.nl.
+$TTL 60
+@ IN SOA a b 1 2 3 4 5
--- /dev/null
+# Try to define values for options that don't have "default" options that would
+# trigger fast-reload functionality.
+server:
+ verbosity: 4
+ num-threads: 4
+ interface: 127.0.0.1
+ interface: lo
+ port: @PORT@
+ interface-action: lo allow
+ use-syslog: no
+ directory: ""
+ pidfile: "unbound.pid"
+ chroot: ""
+ username: ""
+ do-not-query-localhost: no
+
+ module-config: "respip validator iterator"
+
+ outgoing-interface: 127.0.0.1
+ outgoing-port-avoid: "3200-3208"
+
+ define-tag: "tag1 tag2 tag3"
+
+ do-nat64: yes
+ nat64-prefix: 64:ff9b::0/96
+ dns64-prefix: 64:ff9b::0/96
+ dns64-ignore-aaaa: "ignore-aaaa.nlnetlabs.nl"
+
+ edns-tcp-keepalive: yes
+
+ response-ip: 192.0.2.0 always_refuse
+ access-control: 127.0.0.0/8 allow
+ access-control: ::1 allow
+ access-control-tag: 192.0.2.0/24 "tag2 tag3"
+ interface-tag: lo "tag2 tag3"
+ access-control-tag-action: 192.0.2.0/24 tag3 always_refuse
+ interface-tag-action: lo tag3 always_refuse
+ access-control-tag-data: 192.0.2.0/24 tag2 "A 127.0.0.1"
+ interface-tag-data: lo tag2 "A 127.0.0.1"
+ access-control-view: 192.0.2.0/24 viewname
+ interface-view: lo viewname
+
+ nsid: "ascii_something"
+
+ http-user-agent: "httpuseragent"
+
+ caps-exempt: "nlnetlabs.nl"
+
+ private-address: 10.0.0.0/8
+ private-address: 172.16.0.0/12
+ private-address: 192.168.0.0/16
+ private-address: 169.254.0.0/16
+ private-address: fd00::/8
+ private-address: fe80::/10
+ private-address: ::ffff:0:0/96
+
+ private-domain: "nlnetlabs.nl"
+
+ unwanted-reply-threshold: 10000000
+
+ do-not-query-address: 1.1.1.1
+ do-not-query-address: 8.8.8.8
+ do-not-query-address: 9.9.9.9
+
+ do-not-query-localhost: no
+
+ trust-anchor: "jelte.nlnetlabs.nl. DS 42860 5 1 14D739EB566D2B1A5E216A0BA4D17FA9B038BE4A"
+
+ domain-insecure: "nlnetlabs.nl"
+
+ serve-expired: yes
+ serve-expired-client-timeout: 1800
+
+ val-log-level: 2
+
+ local-zone: refuse.nlnetlabs.nl. refuse
+ local-zone: override.nlnetlabs.nl. deny
+ local-zone: tag.nlnetlabs.nl. transparent
+ local-data: "data.nlnetlabs.nl. TXT localdata"
+ local-data-ptr: "192.0.2.3 reverse.nlnetlabs.nl."
+ local-zone-tag: "tag.nlnetlabs.nl" "tag2 tag3"
+ local-zone-override: "override.nlnetlabs.nl" 192.0.2.0/24 refuse
+
+
+ ratelimit: 100
+ ratelimit-below-domain: ratelimit.nlnetlabs.nl 1000
+ ip-ratelimit: 100
+
+ tcp-connection-limit: 192.0.2.0/24 12
+
+ answer-cookie: yes
+ cookie-secret: "000102030405060708090a0b0c0d0e0f"
+
+ ede: yes
+ ede-serve-expired: yes
+
+remote-control:
+ control-enable: yes
+ control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
+ control-use-cert: no
+
+stub-zone:
+ name: "stub.nlnetlabs.nl"
+ stub-addr: 192.0.2.68
+ stub-prime: no
+ stub-first: no
+ stub-tcp-upstream: no
+ stub-tls-upstream: no
+ stub-no-cache: no
+
+forward-zone:
+ name: "forward.nlnetlabs.nl"
+ forward-addr: 192.0.2.68
+ forward-first: no
+ forward-tcp-upstream: no
+ forward-tls-upstream: no
+ forward-no-cache: no
+
+auth-zone:
+ name: "auth.nlnetlabs.nl"
+ for-downstream: yes
+ for-upstream: yes
+ zonemd-check: no
+ zonemd-reject-absence: no
+ zonefile: "auth.nlnetlabs.nl.zone"
+
+view:
+ name: "viewname"
+ local-zone: "view.nlnetlabs.nl" redirect
+ local-data: "view.nlnetlabs.nl A 192.0.2.3"
+ local-data-ptr: "192.0.2.3 view.nlnetlabs.nl"
+ view-first: no
+
+rpz:
+ name: "rpz.nlnetlabs.nl"
+ zonefile: "rpz.nlnetlabs.nl.zone"
+ rpz-action-override: cname
+ rpz-cname-override: www.example.org
+ rpz-log: yes
+ rpz-log-name: "example policy"
+ rpz-signal-nxdomain-ra: no
+ for-downstream: no
+ tags: "tag3"
--- /dev/null
+BaseName: fast_reload_most_options
+Version: 1.0
+Description: Test fast reload on high verbosity with most options.
+CreationDate: Fri 28 Feb 2025 15:55:15 CET
+Maintainer: Yorgos Thessalonikefs
+Category:
+Component:
+CmdDepends:
+Depends:
+Help:
+Pre: fast_reload_most_options.pre
+Post: fast_reload_most_options.post
+Test: fast_reload_most_options.test
+AuxFiles:
+Passed:
+Failure:
--- /dev/null
+# #-- fast_reload_most_options.post --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# source the test var file when it's there
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+#
+# do your teardown here
+. ../common.sh
+kill_pid $UNBOUND_PID
+rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
+cat unbound.log
--- /dev/null
+# #-- fast_reload_most_options.pre--#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+# if no threads; exit
+if grep -e "define HAVE_PTHREAD 1" -e "define HAVE_SOLARIS_THREADS 1" -e "define HAVE_WINDOWS_THREADS 1" $PRE/config.h; then
+ echo "have threads"
+else
+ skip_test "no threads"
+fi
+
+get_random_port 1
+UNBOUND_PORT=$RND_PORT
+echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
+
+# make config file
+CONTROL_PATH=/tmp
+CONTROL_PID=$$
+sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < fast_reload_most_options.conf > ub.conf
+# start unbound in the background
+PRE="../.."
+$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
+UNBOUND_PID=$!
+echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
+echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
+echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
+
+cat .tpkg.var.test
+wait_unbound_up unbound.log
--- /dev/null
+# #-- fast_reload_most_options.test --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+
+echo "> unbound-control status"
+$PRE/unbound-control -c ub.conf status
+if test $? -ne 0; then
+ echo "wrong exit value."
+ exit 1
+else
+ echo "exit value: OK"
+fi
+
+for i in {1..10}
+do
+
+ echo "> unbound-control fast_reload +vvdp ($i)"
+ $PRE/unbound-control -c ub.conf fast_reload +vvdp 2>&1 | tee output
+ if test $? -ne 0; then
+ echo "wrong exit value."
+ exit 1
+ else
+ echo "exit value: OK"
+ fi
+ wait_logfile unbound.log "start fast reload thread" 60
+ wait_logfile unbound.log "stop fast reload thread" 60
+ wait_logfile unbound.log "joined with fastreload thread" 60
+
+ if grep "ok" output; then
+ echo "OK"
+ else
+ echo "output not correct"
+ exit 1
+ fi
+done
+
+exit 0
--- /dev/null
+$ORIGIN rpz.nlnetlabs.nl.
+$TTL 60
+@ IN SOA a b 1 2 3 4 5
+nxdomain.nlnetlabs.nl IN CNAME .
+rpzdata.nlnetlabs.nl IN A 0.0.0.0
config_del_strarray(cfg->tagname, cfg->num_tags);
config_del_strbytelist(cfg->local_zone_tags);
config_del_strbytelist(cfg->respip_tags);
+ config_deldblstrlist(cfg->respip_actions);
config_deldblstrlist(cfg->acl_view);
config_del_strbytelist(cfg->acl_tags);
config_deltrplstrlist(cfg->acl_tag_actions);
{
struct trust_anchor *ta;
struct ta_key *k;
- size_t s = sizeof(*anchors);
- if(!anchors)
- return 0;
+ size_t s;
+ if(!anchors) return 0;
+ s = sizeof(*anchors);
lock_basic_lock(&anchors->lock);
RBTREE_FOR(ta, struct trust_anchor*, anchors->tree) {
lock_basic_lock(&ta->lock);