]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- auth-load-thread, make and run auth load thread. auth-load-thread
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Fri, 26 Jun 2026 15:11:12 +0000 (17:11 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Fri, 26 Jun 2026 15:11:12 +0000 (17:11 +0200)
libunbound/authload.h [new file with mode: 0644]
services/authload.c
services/authload.h
services/authzone.c
services/authzone.h
util/fptr_wlist.c

diff --git a/libunbound/authload.h b/libunbound/authload.h
new file mode 100644 (file)
index 0000000..4b789dd
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * libunbound/authload.h - prototypes for auth load methods.
+ *
+ * Copyright (c) 2026, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This file declares the methods that must be implemented to use the
+ * auth load service.
+ */
+
+#ifndef LIBUNBOUND_AUTHLOAD_H
+#define LIBUNBOUND_AUTHLOAD_H
+
+/** The worker routine that services the auth load connection. */
+void worker_auth_load_service_cb(int fd, short bits, void* arg);
+
+#endif /* LIBUNBOUND_AUTHLOAD_H */
index 37d099fe20fcf37ec52965462f914a53f20035d0..fdf2494e152e1a4c80b4c8800e0516dfc27cbefa 100644 (file)
 
 #include "config.h"
 #include "services/authload.h"
+#include "daemon/worker.h"
+#include "daemon/daemon.h"
+#include "services/authzone.h"
+#include "libunbound/authload.h"
+#include "util/net_help.h"
+#include "util/log.h"
+#include "util/ub_event.h"
+
+/** delete chunks */
+static void
+auth_chunk_list_delete(struct auth_chunk* first)
+{
+       struct auth_chunk* c = first, *cn;
+       while(c) {
+               cn = c->next;
+               free(c->data);
+               free(c);
+               c = cn;
+       }
+}
+
+/** Delete auth load task item */
+static void
+auth_load_task_delete(struct auth_load_task* task)
+{
+       if(!task)
+               return;
+       free(task->name);
+       free(task->host);
+       free(task->file);
+       auth_chunk_list_delete(task->chunks_first);
+       free(task);
+}
+
+/** Create new auth load task item */
+static struct auth_load_task*
+auth_load_task_create(void)
+{
+       struct auth_load_task* task = (struct auth_load_task*)calloc(1,
+               sizeof(*task));
+       return task;
+}
+
+/** Pick up the work content of task transfer of auth xfr */
+static int
+auth_load_task_pickup_xfr(struct auth_load_task* task, struct auth_xfer* xfr)
+{
+       task->name = memdup(xfr->name, xfr->namelen);
+       if(!task->name)
+               return 0;
+       task->namelen = xfr->namelen;
+       task->dclass = xfr->dclass;
+       if(xfr->task_transfer->master && xfr->task_transfer->master->host) {
+               task->host = strdup(xfr->task_transfer->master->host);
+               if(!task->host)
+                       return 0;
+       }
+       if(xfr->task_transfer->master && xfr->task_transfer->master->file) {
+               task->file = strdup(xfr->task_transfer->master->file);
+               if(!task->file)
+                       return 0;
+       }
+       if(xfr->task_transfer->master)
+               task->on_http = xfr->task_transfer->master->http;
+       task->on_ixfr = xfr->task_transfer->on_ixfr;
+       task->on_ixfr_is_axfr = xfr->task_transfer->on_ixfr_is_axfr;
+       task->serial = xfr->serial;
+       if(xfr->task_transfer->chunks_first) {
+               task->chunks_first = xfr->task_transfer->chunks_first;
+               task->chunks_last = xfr->task_transfer->chunks_last;
+               task->chunks_total = xfr->task_transfer->chunks_total;
+               /* The task now has the chunks. Remove them from the
+                * xfr structure. */
+               xfr->task_transfer->chunks_first = 0;
+               xfr->task_transfer->chunks_last = 0;
+               xfr->task_transfer->chunks_total = 0;
+       }
+
+       if(task->on_http)
+               task->task_type = AUTH_LOAD_TASK_HTTPCHUNKS;
+       else    task->task_type = AUTH_LOAD_TASK_TRANSFER;
+       return 1;
+}
+
+/** Create xfr task */
+static struct auth_load_task*
+auth_load_task_create_xfr(struct auth_xfer* xfr, struct worker* worker)
+{
+       struct auth_load_task* task = auth_load_task_create();
+       if(!task) {
+               log_err("out of memory");
+               return 0;
+       }
+       task->worker = worker;
+       if(!auth_load_task_pickup_xfr(task, xfr)) {
+               log_err("out of memory");
+               auth_load_task_delete(task);
+               return 0;
+       }
+       return task;
+}
+
+/** See if there is a quit signal, true if so. */
+static int
+auth_load_thread_poll_for_quit(struct auth_load_thread* thr)
+{
+       (void)thr;
+       return 0;
+}
+
+/** Signal the worker connected to an auth load thread the status */
+static void
+auth_load_thread_signal_worker(struct auth_load_thread* thr, int status)
+{
+       int outevent, loopexit = 0;
+       ssize_t ret;
+       uint8_t to_send;
+       verbose(VERB_ALGO, "auth load thread: send status %d", 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(auth_load_thread_poll_for_quit(thr))
+               return;
+       to_send = (uint8_t)status;
+       while(1) {
+               if(++loopexit > 200) {
+                       log_err("auth load thread: could not send status");
+                       return;
+               }
+               /* wait for socket to become writable */
+               if(!sock_poll_timeout(thr->commpair[1],
+                       200, /* msec wait before check for quit, and loop to
+                               wait again. */
+                       0, 1, &outevent)) {
+                       log_err("auth load thread: poll failed");
+                       return;
+               }
+               if(auth_load_thread_poll_for_quit(thr))
+                       return;
+               if(!outevent)
+                       continue;
+               ret = send(thr->commpair[1], &to_send, 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("auth load thread signal worker: send: %s",
+                               sock_strerror(errno));
+                       return;
+               } else if(ret < 1) {
+                       continue;
+               }
+               break;
+       }
+}
+
+/** In the auth load thread, process the task */
+static int
+auth_load_thread_process(struct auth_load_thread* thr)
+{
+       (void)thr;
+       return 0;
+}
+
+/** The auth load thread. The thread main function. */
+static void*
+auth_load_thread_main(void* arg)
+{
+       struct auth_load_thread* thr = (struct auth_load_thread*)arg;
+       int s;
+       const char name[16] = "unbound/authld"; /* seems to be the safest size
+                                                    between different OSes */
+
+#if defined(HAVE_GETTID) && !defined(THREADS_DISABLED)
+       thr->thread_tid = gettid();
+       if(thr->thread_tid_log)
+               log_thread_set(&thr->thread_tid);
+       else
+#endif
+               log_thread_set(&thr->threadnum);
+
+       ub_thread_setname(ub_thread_self(), name);
+       (void)name; /* When setname is not defined, ignore the name variable. */
+
+       verbose(VERB_ALGO, "start auth load thread");
+       s = auth_load_thread_process(thr);
+       /* The result is sent to the worker, that reaps the thread. */
+       auth_load_thread_signal_worker(thr, s);
+       verbose(VERB_ALGO, "stop auth load thread");
+       return NULL;
+}
+
+/** Delete auth load thread structure */
+static void
+auth_load_thread_delete(struct auth_load_thread* thr)
+{
+       if(!thr)
+               return;
+       if(thr->service_event && thr->service_event_is_added) {
+               ub_event_del(thr->service_event);
+               thr->service_event_is_added = 0;
+       }
+       if(thr->service_event)
+               ub_event_free(thr->service_event);
+       if(thr->commpair[0] != -1)
+               sock_close(thr->commpair[0]);
+       if(thr->commpair[1] != -1)
+               sock_close(thr->commpair[1]);
+       free(thr);
+}
+
+/** Create auth load thread structure */
+static struct auth_load_thread*
+auth_load_thread_create(struct auth_load_task* task)
+{
+       int numworkers;
+       struct auth_load_thread* thr = (struct auth_load_thread*)calloc(1,
+               sizeof(*thr));
+       if(!thr)
+               return NULL;
+       numworkers = task->worker->daemon->num;
+       /* This number is printed into the logs */
+       thr->threadnum = numworkers+3;
+       thr->task = task;
+       thr->commpair[0] = -1;
+       thr->commpair[1] = -1;
+       if(!create_socketpair(thr->commpair, task->worker->daemon->rand)) {
+               auth_load_thread_delete(thr);
+               return NULL;
+       }
+#ifdef HAVE_GETTID
+        thr->thread_tid_log = task->worker->env.cfg->log_thread_id;
+#endif
+       return thr;
+}
+
+/** The worker routine that services the auth load connection. */
+void
+worker_auth_load_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits),
+       void* arg)
+{
+       struct auth_load_thread* thr = (struct auth_load_thread*)arg;
+       uint8_t recv_item;
+       ssize_t ret;
+       struct auth_xfer* xfr;
+
+       log_assert(thr->commpair[0] >= 0);
+       ret = recv(thr->commpair[0], &recv_item, 1, 0);
+       if(ret == -1) {
+               if(
+#ifndef USE_WINSOCK
+                       errno == EINTR || errno == EAGAIN
+#  ifdef EWOULDBLOCK
+                       || errno == EWOULDBLOCK
+#  endif
+#else
+                       WSAGetLastError() == WSAEINTR ||
+                       WSAGetLastError() == WSAEINPROGRESS
+#endif
+                       )
+                       return; /* Continue later. */
+#ifdef USE_WINSOCK
+               if(WSAGetLastError() == WSAEWOULDBLOCK) {
+                       ub_winsock_tcp_wouldblock(fr->service_event,
+                               UB_EV_READ);
+                       return; /* Continue later. */
+               }
+#endif
+               log_err("read status from auth load thread, recv: %s",
+                       sock_strerror(errno));
+               return;
+       } else if(ret == 0) {
+               verbose(VERB_ALGO, "closed connection from auth load thread");
+               /* handle this like an error */
+               recv_item = 0;
+       /* ret<1: No short read on 1 byte, to continue later on */
+       }
+
+       /* Deal with the result of auth load thread */
+       verbose(VERB_ALGO, "auth load status is %d", (int)recv_item);
+       verbose(VERB_ALGO, "join with auth load thread");
+       ub_thread_join(thr->tid);
+       verbose(VERB_ALGO, "joined with auth load thread");
+       lock_rw_rdlock(&thr->task->worker->env.auth_zones->lock);
+       xfr = auth_xfer_find(thr->task->worker->env.auth_zones,
+               thr->task->name, thr->task->namelen, thr->task->dclass);
+       if(!xfr) {
+               lock_rw_unlock(&thr->task->worker->env.auth_zones->lock);
+               verbose(VERB_ALGO, "auth load: xfr is gone");
+               auth_load_thread_delete(thr);
+               return;
+       }
+       lock_basic_lock(&xfr->lock);
+       lock_rw_unlock(&thr->task->worker->env.auth_zones->lock);
+       auth_load_thread_delete(thr);
+       xfr_process_load_end_transfer(xfr, recv_item);
+}
+
+/** Attach worker to the auth load thread. */
+static int
+auth_load_thread_attach(struct auth_load_thread* thr, struct worker* worker)
+{
+       /* Setup listener in worker, that connects via a pipe to the
+        * auth load thread.
+        * The listener has to be nonblocking, so the the remote servicing
+        * thread can continue to service DNS queries.
+        * The commpair[1] element can stay blocking, it is used by the
+        * auth load thread. The thread needs to wait at these times, when
+        * it has to check briefly it can use poll. */
+       fd_set_nonblock(thr->commpair[0]);
+       thr->service_event = ub_event_new(comm_base_internal(worker->base),
+               thr->commpair[0], UB_EV_READ | UB_EV_PERSIST,
+               worker_auth_load_service_cb, thr);
+       if(!thr->service_event) {
+               log_err("out of memory");
+               return 0;
+       }
+       if(ub_event_add(thr->service_event, NULL) != 0) {
+               log_err("out of memory");
+               return 0;
+       }
+       thr->service_event_is_added = 1;
+       return 1;
+}
+
+/** Create and start the auth load thread, with the task */
+static int
+auth_load_start_thread(struct auth_load_task* task)
+{
+       struct auth_load_thread* thr = auth_load_thread_create(task);
+       if(!thr) {
+               log_err("out of memory");
+               auth_load_task_delete(task);
+               return 0;
+       }
+       if(!auth_load_thread_attach(thr, task->worker)) {
+               log_err("out of memory");
+               auth_load_thread_delete(thr);
+               return 0;
+       }
+
+       /* Start auth load thread */
+       ub_thread_create(&thr->tid, auth_load_thread_main, thr);
+       return 1;
+}
 
 int auth_load_add_task_xfr(struct auth_xfer* xfr, struct worker* worker)
 {
+       struct auth_load_task* task;
+       int can_run = 0;
+
        /* Check auth load count */
+       can_run = 1;
 
        /* Create new thread */
+       task = auth_load_task_create_xfr(xfr, worker);
+       if(!task)
+               return 0;
+       if(can_run) {
+               if(!auth_load_start_thread(task))
+                       return 0;
+               return 1;
+       }
 
        /* Make wait item */
+       return 0;
 }
index d842f77af9d28b2d84782d1ee895e8c0f53bdfcc..47fb99cedb7c9baa5f8bf50fb207bd20cab6c980 100644 (file)
@@ -72,6 +72,12 @@ struct auth_load_thread {
        int commpair[2];
        /** if the thread has to quit */
        int need_to_quit;
+       /** the event that listens on the worker to commpair,
+        * it receives content from the auth load thread. */
+       void* service_event;
+       /** if the event that listens on the worker has
+        * been added to the comm base. */
+       int service_event_is_added;
 
        /** the worker that the auth load is connected to */
        struct worker* worker;
@@ -87,7 +93,7 @@ enum auth_load_task_type {
        AUTH_LOAD_TASK_TRANSFER,
        AUTH_LOAD_TASK_ZONEFILE_READ,
        AUTH_LOAD_TASK_ZONEFILE_WRITE,
-       AUTH_LOAD_TASK_CHUNKS
+       AUTH_LOAD_TASK_HTTPCHUNKS
 };
 
 /**
index f91fb461ee81fc6fe5cd5266bc80cff9a1850fbe..b5eb162b46b0ee82c86f7f8d298ccb46212737ad 100644 (file)
@@ -6336,6 +6336,20 @@ process_list_end_transfer(struct auth_xfer* xfr, struct module_env* env)
        xfr_transfer_nexttarget_or_end(xfr, env);
 }
 
+void xfr_process_load_end_transfer(struct auth_xfer* xfr, uint8_t status)
+{
+       if(status) {
+               /* it worked! */
+               /* we fetched the zone, move to wait task */
+               xfr_transfer_disown(xfr);
+
+               lock_basic_unlock(&xfr->lock);
+               return;
+       }
+       /* The transfer failed */
+       lock_basic_unlock(&xfr->lock);
+}
+
 /** callback for the task_transfer timer */
 void
 auth_xfer_transfer_timer_callback(void* arg)
index 97ab01fcbc1d8aab11cc59aa89f37a12717d820e..7f9c116068e1080bd7b82d8dac9644a1b9590fc9 100644 (file)
@@ -857,4 +857,7 @@ int chunkline_count_parens(struct sldns_buffer* buf, size_t start);
 /** Clear data in auth zone */
 void auth_zone_clear_data(struct auth_zone* z);
 
+/** Handle the end of an auth load task. */
+void xfr_process_load_end_transfer(struct auth_xfer* xfr, uint8_t status);
+
 #endif /* SERVICES_AUTHZONE_H */
index 18a2fc11bb30431c56fd2903c38b59018644c73d..da301c034d79e23175ba757ff78be34fe91ebf27 100644 (file)
@@ -72,6 +72,7 @@
 #include "libunbound/libworker.h"
 #include "libunbound/context.h"
 #include "libunbound/worker.h"
+#include "libunbound/authload.h"
 #include "util/tube.h"
 #include "util/config_file.h"
 #include "daemon/remote.h"
@@ -191,6 +192,7 @@ fptr_whitelist_event(void (*fptr)(int, short, void *))
        else if(fptr == &comm_point_doq_callback) return 1;
 #endif
        else if(fptr == &fast_reload_service_cb) return 1;
+       else if(fptr == &worker_auth_load_service_cb) return 1;
 #ifdef USE_DNSTAP
        else if(fptr == &dtio_output_cb) return 1;
        else if(fptr == &dtio_cmd_cb) return 1;