summaryrefslogtreecommitdiffstats
path: root/src/knot/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/common')
-rw-r--r--src/knot/common/evsched.c268
-rw-r--r--src/knot/common/evsched.h154
-rw-r--r--src/knot/common/fdset.c336
-rw-r--r--src/knot/common/fdset.h382
-rw-r--r--src/knot/common/log.c491
-rw-r--r--src/knot/common/log.h187
-rw-r--r--src/knot/common/process.c194
-rw-r--r--src/knot/common/process.h60
-rw-r--r--src/knot/common/stats.c309
-rw-r--r--src/knot/common/stats.h53
-rw-r--r--src/knot/common/systemd.c168
-rw-r--r--src/knot/common/systemd.h105
-rw-r--r--src/knot/common/unreachable.c148
-rw-r--r--src/knot/common/unreachable.h87
14 files changed, 2942 insertions, 0 deletions
diff --git a/src/knot/common/evsched.c b/src/knot/common/evsched.c
new file mode 100644
index 0000000..0d65c6a
--- /dev/null
+++ b/src/knot/common/evsched.c
@@ -0,0 +1,268 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "libknot/libknot.h"
+#include "knot/server/dthreads.h"
+#include "knot/common/evsched.h"
+
+/*! \brief Some implementations of timercmp >= are broken, this is for compat.*/
+static inline int timercmp_ge(struct timeval *a, struct timeval *b) {
+ return !timercmp(a, b, <);
+}
+
+static int compare_event_heap_nodes(void *e1, void *e2)
+{
+ if (timercmp(&((event_t *)e1)->tv, &((event_t *)e2)->tv, <)) return -1;
+ if (timercmp(&((event_t *)e1)->tv, &((event_t *)e2)->tv, >)) return 1;
+ return 0;
+}
+
+/*!
+ * \brief Get time T (now) + dt milliseconds.
+ */
+static struct timeval timeval_in(uint32_t dt)
+{
+ struct timeval tv = { 0 };
+ gettimeofday(&tv, NULL);
+
+ /* Add number of seconds. */
+ tv.tv_sec += dt / 1000;
+
+ /* Add the number of microseconds. */
+ tv.tv_usec += (dt % 1000) * 1000;
+
+ /* Check for overflow. */
+ while (tv.tv_usec > 999999) {
+ tv.tv_sec += 1;
+ tv.tv_usec -= 1 * 1000 * 1000;
+ }
+
+ return tv;
+}
+
+/*! \brief Event scheduler loop. */
+static int evsched_run(dthread_t *thread)
+{
+ evsched_t *sched = (evsched_t*)thread->data;
+ if (sched == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Run event loop. */
+ pthread_mutex_lock(&sched->heap_lock);
+ while (!dt_is_cancelled(thread)) {
+ if (!!EMPTY_HEAP(&sched->heap) || sched->paused) {
+ pthread_cond_wait(&sched->notify, &sched->heap_lock);
+ continue;
+ }
+
+ /* Get current time. */
+ struct timeval dt;
+ gettimeofday(&dt, 0);
+
+ /* Get next event. */
+ event_t *ev = *((event_t**)HHEAD(&sched->heap));
+ assert(ev != NULL);
+
+ if (timercmp_ge(&dt, &ev->tv)) {
+ heap_delmin(&sched->heap);
+ ev->cb(ev);
+ } else {
+ /* Wait for next event or interrupt. Unlock calendar. */
+ struct timespec ts;
+ ts.tv_sec = ev->tv.tv_sec;
+ ts.tv_nsec = ev->tv.tv_usec * 1000L;
+ pthread_cond_timedwait(&sched->notify, &sched->heap_lock, &ts);
+ }
+ }
+ pthread_mutex_unlock(&sched->heap_lock);
+
+ return KNOT_EOK;
+}
+
+int evsched_init(evsched_t *sched, void *ctx)
+{
+ memset(sched, 0, sizeof(evsched_t));
+ sched->ctx = ctx;
+
+ /* Initialize event calendar. */
+ pthread_mutex_init(&sched->heap_lock, 0);
+ pthread_cond_init(&sched->notify, 0);
+ heap_init(&sched->heap, compare_event_heap_nodes, 0);
+
+ sched->thread = dt_create(1, evsched_run, NULL, sched);
+
+ if (sched->thread == NULL) {
+ evsched_deinit(sched);
+ return KNOT_ENOMEM;
+ }
+
+ return KNOT_EOK;
+}
+
+void evsched_deinit(evsched_t *sched)
+{
+ if (sched == NULL) {
+ return;
+ }
+
+ /* Deinitialize event calendar. */
+ pthread_mutex_destroy(&sched->heap_lock);
+ pthread_cond_destroy(&sched->notify);
+
+ while (!EMPTY_HEAP(&sched->heap)) {
+ event_t *e = (event_t *)*HHEAD(&sched->heap);
+ heap_delmin(&sched->heap);
+ evsched_event_free(e);
+ }
+
+ heap_deinit(&sched->heap);
+
+ if (sched->thread != NULL) {
+ dt_delete(&sched->thread);
+ }
+
+ /* Clear the structure. */
+ memset(sched, 0, sizeof(evsched_t));
+}
+
+event_t *evsched_event_create(evsched_t *sched, event_cb_t cb, void *data)
+{
+ /* Create event. */
+ if (sched == NULL) {
+ return NULL;
+ }
+
+ /* Allocate. */
+ event_t *e = malloc(sizeof(event_t));
+ if (e == NULL) {
+ return NULL;
+ }
+
+ /* Initialize. */
+ memset(e, 0, sizeof(event_t));
+ e->sched = sched;
+ e->cb = cb;
+ e->data = data;
+ e->hpos.pos = 0;
+
+ return e;
+}
+
+void evsched_event_free(event_t *ev)
+{
+ if (ev == NULL) {
+ return;
+ }
+
+ free(ev);
+}
+
+int evsched_schedule(event_t *ev, uint32_t dt)
+{
+ if (ev == NULL || ev->sched == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct timeval new_time = timeval_in(dt);
+
+ evsched_t *sched = ev->sched;
+
+ /* Lock calendar. */
+ pthread_mutex_lock(&sched->heap_lock);
+
+ ev->tv = new_time;
+
+ /* Make sure it's not already enqueued. */
+ int found = heap_find(&sched->heap, (heap_val_t *)ev);
+ if (found > 0) {
+ /* "Replacing" with itself -- just repositioning it. */
+ heap_replace(&sched->heap, found, (heap_val_t *)ev);
+ } else {
+ heap_insert(&sched->heap, (heap_val_t *)ev);
+ }
+
+ /* Unlock calendar. */
+ pthread_cond_signal(&sched->notify);
+ pthread_mutex_unlock(&sched->heap_lock);
+
+ return KNOT_EOK;
+}
+
+int evsched_cancel(event_t *ev)
+{
+ if (ev == NULL || ev->sched == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ evsched_t *sched = ev->sched;
+
+ /* Lock calendar. */
+ pthread_mutex_lock(&sched->heap_lock);
+
+ int found = heap_find(&sched->heap, (heap_val_t *)ev);
+ if (found > 0) {
+ heap_delete(&sched->heap, found);
+ pthread_cond_signal(&sched->notify);
+ }
+
+ /* Unlock calendar. */
+ pthread_mutex_unlock(&sched->heap_lock);
+
+ /* Reset event timer. */
+ memset(&ev->tv, 0, sizeof(struct timeval));
+
+ return KNOT_EOK;
+}
+
+void evsched_start(evsched_t *sched)
+{
+ dt_start(sched->thread);
+}
+
+void evsched_stop(evsched_t *sched)
+{
+ pthread_mutex_lock(&sched->heap_lock);
+ dt_stop(sched->thread);
+ pthread_cond_signal(&sched->notify);
+ pthread_mutex_unlock(&sched->heap_lock);
+}
+
+void evsched_join(evsched_t *sched)
+{
+ dt_join(sched->thread);
+}
+
+void evsched_pause(evsched_t *sched)
+{
+ pthread_mutex_lock(&sched->heap_lock);
+ sched->paused = true;
+ pthread_mutex_unlock(&sched->heap_lock);
+}
+
+void evsched_resume(evsched_t *sched)
+{
+ pthread_mutex_lock(&sched->heap_lock);
+ sched->paused = false;
+ pthread_cond_signal(&sched->notify);
+ pthread_mutex_unlock(&sched->heap_lock);
+}
diff --git a/src/knot/common/evsched.h b/src/knot/common/evsched.h
new file mode 100644
index 0000000..762c3f8
--- /dev/null
+++ b/src/knot/common/evsched.h
@@ -0,0 +1,154 @@
+/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \brief Event scheduler.
+ */
+
+#pragma once
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/time.h>
+
+#include "knot/server/dthreads.h"
+#include "contrib/ucw/heap.h"
+
+/* Forward decls. */
+struct evsched;
+struct event;
+
+/*!
+ * \brief Event callback.
+ *
+ * Pointer to whole event structure is passed to the callback.
+ * Callback should return 0 on success and negative integer on error.
+ *
+ * Example callback:
+ * \code
+ * void print_callback(event_t *t) {
+ * printf("Callback: %s\n", t->data);
+ * }
+ * \endcode
+ */
+typedef void (*event_cb_t)(struct event *);
+
+/*!
+ * \brief Event structure.
+ */
+typedef struct event {
+ struct heap_val hpos;
+ struct timeval tv; /*!< Event scheduled time. */
+ void *data; /*!< Usable data ptr. */
+ event_cb_t cb; /*!< Event callback. */
+ struct evsched *sched; /*!< Scheduler for this event. */
+} event_t;
+
+/*!
+ * \brief Event scheduler structure.
+ */
+typedef struct evsched {
+ volatile bool paused; /*!< Temporarily stop processing events. */
+ pthread_mutex_t heap_lock; /*!< Event heap locking. */
+ pthread_cond_t notify; /*!< Event heap notification. */
+ struct heap heap; /*!< Event heap. */
+ void *ctx; /*!< Scheduler context. */
+ dt_unit_t *thread;
+} evsched_t;
+
+/*!
+ * \brief Initialize event scheduler instance.
+ *
+ * \retval New instance on success.
+ * \retval NULL on error.
+ */
+int evsched_init(evsched_t *sched, void *ctx);
+
+/*!
+ * \brief Deinitialize and free event scheduler instance.
+ *
+ * \param sched Pointer to event scheduler instance.
+ */
+void evsched_deinit(evsched_t *sched);
+
+/*!
+ * \brief Create a callback event.
+ *
+ * \note Scheduler takes ownership of scheduled events. Created, but unscheduled
+ * events are in the ownership of the caller.
+ *
+ * \param sched Pointer to event scheduler instance.
+ * \param cb Callback handler.
+ * \param data Data for callback.
+ *
+ * \retval New instance on success.
+ * \retval NULL on error.
+ */
+event_t *evsched_event_create(evsched_t *sched, event_cb_t cb, void *data);
+
+/*!
+ * \brief Dispose event instance.
+ *
+ * \param ev Event instance.
+ */
+void evsched_event_free(event_t *ev);
+
+/*!
+ * \brief Schedule an event.
+ *
+ * \note This function checks if the event was already scheduled, if it was
+ * then it replaces this timer with the newer value.
+ * Running events are not canceled or waited for.
+ *
+ * \param ev Prepared event.
+ * \param dt Time difference in milliseconds from now (dt is relative).
+ *
+ * \retval KNOT_EOK on success.
+ * \retval KNOT_EINVAL
+ */
+int evsched_schedule(event_t *ev, uint32_t dt);
+
+/*!
+ * \brief Cancel a scheduled event.
+ *
+ * \warning May block until current running event is finished (as it cannot
+ * interrupt running event).
+ *
+ * \warning Never cancel event in it's callback. As it never finishes,
+ * it deadlocks.
+ *
+ * \param ev Scheduled event.
+ *
+ * \retval KNOT_EOK
+ * \retval KNOT_EINVAL
+ */
+int evsched_cancel(event_t *ev);
+
+/*! \brief Start event processing threads. */
+void evsched_start(evsched_t *sched);
+
+/*! \brief Stop event processing threads. */
+void evsched_stop(evsched_t *sched);
+
+/*! \brief Join event processing threads. */
+void evsched_join(evsched_t *sched);
+
+/*! \brief Temporarily stop processing events. */
+void evsched_pause(evsched_t *sched);
+
+/*! \brief Resume processing events. */
+void evsched_resume(evsched_t *sched);
diff --git a/src/knot/common/fdset.c b/src/knot/common/fdset.c
new file mode 100644
index 0000000..a4b37d9
--- /dev/null
+++ b/src/knot/common/fdset.c
@@ -0,0 +1,336 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "knot/common/fdset.h"
+#include "contrib/time.h"
+#include "contrib/macros.h"
+
+#define MEM_RESIZE(p, n) { \
+ void *tmp = NULL; \
+ if ((tmp = realloc((p), (n) * sizeof(*p))) == NULL) { \
+ return KNOT_ENOMEM; \
+ } \
+ (p) = tmp; \
+}
+
+static int fdset_resize(fdset_t *set, const unsigned size)
+{
+ assert(set);
+
+ MEM_RESIZE(set->ctx, size);
+ MEM_RESIZE(set->timeout, size);
+#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
+ MEM_RESIZE(set->ev, size);
+#else
+ MEM_RESIZE(set->pfd, size);
+#endif
+ set->size = size;
+ return KNOT_EOK;
+}
+
+int fdset_init(fdset_t *set, const unsigned size)
+{
+ if (set == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ memset(set, 0, sizeof(*set));
+
+#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
+#ifdef HAVE_EPOLL
+ set->pfd = epoll_create1(0);
+#elif HAVE_KQUEUE
+ set->pfd = kqueue();
+#endif
+ if (set->pfd < 0) {
+ return knot_map_errno();
+ }
+#endif
+ int ret = fdset_resize(set, size);
+#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
+ if (ret != KNOT_EOK) {
+ close(set->pfd);
+ }
+#endif
+ return ret;
+}
+
+void fdset_clear(fdset_t *set)
+{
+ if (set == NULL) {
+ return;
+ }
+
+ free(set->ctx);
+ free(set->timeout);
+#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
+ free(set->ev);
+ free(set->recv_ev);
+ close(set->pfd);
+#else
+ free(set->pfd);
+#endif
+ memset(set, 0, sizeof(*set));
+}
+
+int fdset_add(fdset_t *set, const int fd, const fdset_event_t events, void *ctx)
+{
+ if (set == NULL || fd < 0) {
+ return KNOT_EINVAL;
+ }
+
+ if (set->n == set->size &&
+ fdset_resize(set, set->size + FDSET_RESIZE_STEP) != KNOT_EOK) {
+ return KNOT_ENOMEM;
+ }
+
+ const int idx = set->n++;
+ set->ctx[idx] = ctx;
+ set->timeout[idx] = 0;
+#ifdef HAVE_EPOLL
+ set->ev[idx].data.fd = fd;
+ set->ev[idx].events = events;
+ struct epoll_event ev = {
+ .data.u64 = idx,
+ .events = events
+ };
+ if (epoll_ctl(set->pfd, EPOLL_CTL_ADD, fd, &ev) != 0) {
+ return knot_map_errno();
+ }
+#elif HAVE_KQUEUE
+ EV_SET(&set->ev[idx], fd, events, EV_ADD, 0, 0, (void *)(intptr_t)idx);
+ if (kevent(set->pfd, &set->ev[idx], 1, NULL, 0, NULL) < 0) {
+ return knot_map_errno();
+ }
+#else
+ set->pfd[idx].fd = fd;
+ set->pfd[idx].events = events;
+ set->pfd[idx].revents = 0;
+#endif
+
+ return idx;
+}
+
+int fdset_remove(fdset_t *set, const unsigned idx)
+{
+ if (set == NULL || idx >= set->n) {
+ return KNOT_EINVAL;
+ }
+
+ const int fd = fdset_get_fd(set, idx);
+#ifdef HAVE_EPOLL
+ /* This is necessary as DDNS duplicates file descriptors! */
+ if (epoll_ctl(set->pfd, EPOLL_CTL_DEL, fd, NULL) != 0) {
+ close(fd);
+ return knot_map_errno();
+ }
+#elif HAVE_KQUEUE
+ /* Return delete flag back to original filter number. */
+#if defined(__NetBSD__)
+ if ((signed short)set->ev[idx].filter < 0)
+#else
+ if (set->ev[idx].filter >= 0)
+#endif
+ {
+ set->ev[idx].filter = ~set->ev[idx].filter;
+ }
+ set->ev[idx].flags = EV_DELETE;
+ if (kevent(set->pfd, &set->ev[idx], 1, NULL, 0, NULL) < 0) {
+ close(fd);
+ return knot_map_errno();
+ }
+#endif
+ close(fd);
+
+ const unsigned last = --set->n;
+ /* Nothing else if it is the last one. Move last -> i if some remain. */
+ if (idx < last) {
+ set->ctx[idx] = set->ctx[last];
+ set->timeout[idx] = set->timeout[last];
+#if defined(HAVE_EPOLL) || defined (HAVE_KQUEUE)
+ set->ev[idx] = set->ev[last];
+#ifdef HAVE_EPOLL
+ struct epoll_event ev = {
+ .data.u64 = idx,
+ .events = set->ev[idx].events
+ };
+ if (epoll_ctl(set->pfd, EPOLL_CTL_MOD, set->ev[last].data.fd, &ev) != 0) {
+ return knot_map_errno();
+ }
+#elif HAVE_KQUEUE
+ EV_SET(&set->ev[idx], set->ev[last].ident, set->ev[last].filter,
+ EV_ADD, 0, 0, (void *)(intptr_t)idx);
+ if (kevent(set->pfd, &set->ev[idx], 1, NULL, 0, NULL) < 0) {
+ return knot_map_errno();
+ }
+#endif
+#else
+ set->pfd[idx] = set->pfd[last];
+#endif
+ }
+
+ return KNOT_EOK;
+}
+
+int fdset_poll(fdset_t *set, fdset_it_t *it, const unsigned offset, const int timeout_ms)
+{
+ if (it == NULL) {
+ return KNOT_EINVAL;
+ }
+ it->unprocessed = 0;
+
+ if (set == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ it->set = set;
+ it->idx = offset;
+#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
+ if (set->recv_size != set->size) {
+ MEM_RESIZE(set->recv_ev, set->size);
+ set->recv_size = set->size;
+ }
+ it->ptr = set->recv_ev;
+ it->dirty = 0;
+#ifdef HAVE_EPOLL
+ if (set->n == 0) {
+ return 0;
+ }
+ if ((it->unprocessed = epoll_wait(set->pfd, set->recv_ev, set->recv_size,
+ timeout_ms)) == -1) {
+ return knot_map_errno();
+ }
+#ifndef NDEBUG
+ /* In specific circumstances with valgrind, it sometimes happens that
+ * `set->n < it->unprocessed`. */
+ if (it->unprocessed > 0 && unlikely(it->unprocessed > set->n)) {
+ assert(it->unprocessed == 232);
+ it->unprocessed = 0;
+ }
+#endif
+#elif HAVE_KQUEUE
+ struct timespec timeout = {
+ .tv_sec = timeout_ms / 1000,
+ .tv_nsec = (timeout_ms % 1000) * 1000000
+ };
+ if ((it->unprocessed = kevent(set->pfd, NULL, 0, set->recv_ev, set->recv_size,
+ (timeout_ms >= 0) ? &timeout : NULL)) == -1) {
+ return knot_map_errno();
+ }
+#endif
+ /*
+ * NOTE: Can't skip offset without bunch of syscalls!
+ * Because of that it waits for `ctx->n` (every socket). Offset is set when TCP
+ * throttling is ON. Sometimes it can return with sockets where none of them is
+ * connected socket, but it should not be common.
+ */
+ while (it->unprocessed > 0 && fdset_it_get_idx(it) < it->idx) {
+ it->ptr++;
+ it->unprocessed--;
+ }
+ return it->unprocessed;
+#else
+ it->unprocessed = poll(&set->pfd[offset], set->n - offset, timeout_ms);
+#ifndef NDEBUG
+ /* In specific circumstances with valgrind, it sometimes happens that
+ * `set->n < it->unprocessed`. */
+ if (it->unprocessed > 0 && unlikely(it->unprocessed > set->n - offset)) {
+ assert(it->unprocessed == 7);
+ it->unprocessed = 0;
+ }
+#endif
+ while (it->unprocessed > 0 && set->pfd[it->idx].revents == 0) {
+ it->idx++;
+ }
+ return it->unprocessed;
+#endif
+}
+
+void fdset_it_commit(fdset_it_t *it)
+{
+ if (it == NULL) {
+ return;
+ }
+#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
+ /* NOTE: reverse iteration to avoid as much "remove last" operations
+ * as possible. I'm not sure about performance improvement. It
+ * will skip some syscalls at begin of iteration, but what
+ * performance increase do we get is a question.
+ */
+ fdset_t *set = it->set;
+ for (int i = set->n - 1; it->dirty > 0 && i >= 0; --i) {
+#ifdef HAVE_EPOLL
+ if (set->ev[i].events == FDSET_REMOVE_FLAG)
+#else
+#if defined(__NetBSD__)
+ if ((signed short)set->ev[i].filter < 0)
+#else
+ if (set->ev[i].filter >= 0)
+#endif
+#endif
+ {
+ (void)fdset_remove(set, i);
+ it->dirty--;
+ }
+ }
+ assert(it->dirty == 0);
+#endif
+}
+
+int fdset_set_watchdog(fdset_t *set, const unsigned idx, const int interval)
+{
+ if (set == NULL || idx >= set->n) {
+ return KNOT_EINVAL;
+ }
+
+ /* Lift watchdog if interval is negative. */
+ if (interval < 0) {
+ set->timeout[idx] = 0;
+ return KNOT_EOK;
+ }
+
+ /* Update clock. */
+ const struct timespec now = time_now();
+ set->timeout[idx] = now.tv_sec + interval; /* Only seconds precision. */
+
+ return KNOT_EOK;
+}
+
+void fdset_sweep(fdset_t *set, const fdset_sweep_cb_t cb, void *data)
+{
+ if (set == NULL || cb == NULL) {
+ return;
+ }
+
+ /* Get time threshold. */
+ const struct timespec now = time_now();
+ unsigned idx = 0;
+ while (idx < set->n) {
+ /* Check sweep state, remove if requested. */
+ if (set->timeout[idx] > 0 && set->timeout[idx] <= now.tv_sec) {
+ const int fd = fdset_get_fd(set, idx);
+ if (cb(set, fd, data) == FDSET_SWEEP) {
+ (void)fdset_remove(set, idx);
+ continue;
+ }
+ }
+ ++idx;
+ }
+}
diff --git a/src/knot/common/fdset.h b/src/knot/common/fdset.h
new file mode 100644
index 0000000..95a5c61
--- /dev/null
+++ b/src/knot/common/fdset.h
@@ -0,0 +1,382 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \brief I/O multiplexing with context and timeouts for each fd.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <time.h>
+
+#ifdef HAVE_EPOLL
+#include <sys/epoll.h>
+#elif HAVE_KQUEUE
+#include <sys/event.h>
+#else
+#include <poll.h>
+#endif
+
+#include "libknot/errcode.h"
+
+#define FDSET_RESIZE_STEP 256
+#ifdef HAVE_EPOLL
+#define FDSET_REMOVE_FLAG ~0U
+#endif
+
+/*! \brief Set of file descriptors with associated context and timeouts. */
+typedef struct {
+ unsigned n; /*!< Active fds. */
+ unsigned size; /*!< Array size (allocated). */
+ void **ctx; /*!< Context for each fd. */
+ time_t *timeout; /*!< Timeout for each fd (seconds precision). */
+#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
+#ifdef HAVE_EPOLL
+ struct epoll_event *ev; /*!< Epoll event storage for each fd. */
+ struct epoll_event *recv_ev; /*!< Array for polled events. */
+#elif HAVE_KQUEUE
+ struct kevent *ev; /*!< Kqueue event storage for each fd. */
+ struct kevent *recv_ev; /*!< Array for polled events. */
+#endif
+ unsigned recv_size; /*!< Size of array for polled events. */
+ int pfd; /*!< File descriptor of kernel polling structure (epoll or kqueue). */
+#else
+ struct pollfd *pfd; /*!< Poll state for each fd. */
+#endif
+} fdset_t;
+
+/*! \brief State of iterator over received events */
+typedef struct {
+ fdset_t *set; /*!< Source fdset_t. */
+ unsigned idx; /*!< Event index offset. */
+ int unprocessed; /*!< Unprocessed events left. */
+#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
+#ifdef HAVE_EPOLL
+ struct epoll_event *ptr; /*!< Pointer on processed event. */
+#elif HAVE_KQUEUE
+ struct kevent *ptr; /*!< Pointer on processed event. */
+#endif
+ unsigned dirty; /*!< Number of fd to be removed on commit. */
+#endif
+} fdset_it_t;
+
+typedef enum {
+#ifdef HAVE_EPOLL
+ FDSET_POLLIN = EPOLLIN,
+ FDSET_POLLOUT = EPOLLOUT,
+#elif HAVE_KQUEUE
+ FDSET_POLLIN = EVFILT_READ,
+ FDSET_POLLOUT = EVFILT_WRITE,
+#else
+ FDSET_POLLIN = POLLIN,
+ FDSET_POLLOUT = POLLOUT,
+#endif
+} fdset_event_t;
+
+/*! \brief Mark-and-sweep state. */
+typedef enum {
+ FDSET_KEEP,
+ FDSET_SWEEP
+} fdset_sweep_state_t;
+
+/*! \brief Sweep callback (set, index, data) */
+typedef fdset_sweep_state_t (*fdset_sweep_cb_t)(fdset_t *, int, void *);
+
+/*!
+ * \brief Initialize fdset to given size.
+ *
+ * \param set Target set.
+ * \param size Initial set size.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int fdset_init(fdset_t *set, const unsigned size);
+
+/*!
+ * \brief Clear whole context of the fdset.
+ *
+ * \param set Target set.
+ */
+void fdset_clear(fdset_t *set);
+
+/*!
+ * \brief Add file descriptor to watched set.
+ *
+ * \param set Target set.
+ * \param fd Added file descriptor.
+ * \param events Mask of watched events.
+ * \param ctx Context (optional).
+ *
+ * \retval ret >= 0 is index of the added fd.
+ * \retval ret < 0 on error.
+ */
+int fdset_add(fdset_t *set, const int fd, const fdset_event_t events, void *ctx);
+
+/*!
+ * \brief Remove and close file descriptor from watched set.
+ *
+ * \param set Target set.
+ * \param idx Index of the removed fd.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int fdset_remove(fdset_t *set, const unsigned idx);
+
+/*!
+ * \brief Wait for receive events.
+ *
+ * Skip events based on offset and set iterator on first event.
+ *
+ * \param set Target set.
+ * \param it Event iterator storage.
+ * \param offset Index of first event.
+ * \param timeout_ms Timeout of operation in milliseconds (use -1 for unlimited).
+ *
+ * \retval ret >= 0 represents number of events received.
+ * \retval ret < 0 on error.
+ */
+int fdset_poll(fdset_t *set, fdset_it_t *it, const unsigned offset, const int timeout_ms);
+
+/*!
+ * \brief Set file descriptor watchdog interval.
+ *
+ * Set time (interval from now) after which the associated file descriptor
+ * should be sweeped (see fdset_sweep). Good example is setting a grace period
+ * of N seconds between socket activity. If socket is not active within
+ * <now, now + interval>, it is sweeped and closed.
+ *
+ * \param set Target set.
+ * \param idx Index of the file descriptor.
+ * \param interval Allowed interval without activity (seconds).
+ * -1 disables watchdog timer.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int fdset_set_watchdog(fdset_t *set, const unsigned idx, const int interval);
+
+/*!
+ * \brief Sweep file descriptors with exceeding inactivity period.
+ *
+ * \param set Target set.
+ * \param cb Callback for sweeped descriptors.
+ * \param data Pointer to extra data.
+ */
+void fdset_sweep(fdset_t *set, const fdset_sweep_cb_t cb, void *data);
+
+/*!
+ * \brief Returns file descriptor based on index.
+ *
+ * \param set Target set.
+ * \param idx Index of the file descriptor.
+ *
+ * \retval ret >= 0 for file descriptor.
+ * \retval ret < 0 on errors.
+ */
+inline static int fdset_get_fd(const fdset_t *set, const unsigned idx)
+{
+ assert(set && idx < set->n);
+
+#ifdef HAVE_EPOLL
+ return set->ev[idx].data.fd;
+#elif HAVE_KQUEUE
+ return set->ev[idx].ident;
+#else
+ return set->pfd[idx].fd;
+#endif
+}
+
+/*!
+ * \brief Returns number of file descriptors stored in set.
+ *
+ * \param set Target set.
+ *
+ * \retval Number of descriptors stored
+ */
+inline static unsigned fdset_get_length(const fdset_t *set)
+{
+ assert(set);
+
+ return set->n;
+}
+
+/*!
+ * \brief Get index of event in set referenced by iterator.
+ *
+ * \param it Target iterator.
+ *
+ * \retval Index of event.
+ */
+inline static unsigned fdset_it_get_idx(const fdset_it_t *it)
+{
+ assert(it);
+
+#ifdef HAVE_EPOLL
+ return it->ptr->data.u64;
+#elif HAVE_KQUEUE
+ return (unsigned)(intptr_t)it->ptr->udata;
+#else
+ return it->idx;
+#endif
+}
+
+/*!
+ * \brief Get file descriptor of event referenced by iterator.
+ *
+ * \param it Target iterator.
+ *
+ * \retval ret >= 0 for file descriptor.
+ * \retval ret < 0 on errors.
+ */
+inline static int fdset_it_get_fd(const fdset_it_t *it)
+{
+ assert(it);
+
+#ifdef HAVE_EPOLL
+ return it->set->ev[fdset_it_get_idx(it)].data.fd;
+#elif HAVE_KQUEUE
+ return it->ptr->ident;
+#else
+ return it->set->pfd[it->idx].fd;
+#endif
+}
+
+/*!
+ * \brief Move iterator on next received event.
+ *
+ * \param it Target iterator.
+ */
+inline static void fdset_it_next(fdset_it_t *it)
+{
+ assert(it);
+
+#if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
+ do {
+ it->ptr++;
+ it->unprocessed--;
+ } while (it->unprocessed > 0 && fdset_it_get_idx(it) < it->idx);
+#else
+ if (--it->unprocessed > 0) {
+ while (it->set->pfd[++it->idx].revents == 0); /* nop */
+ }
+#endif
+}
+
+/*!
+ * \brief Remove file descriptor referenced by iterator from watched set.
+ *
+ * \param it Target iterator.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+inline static void fdset_it_remove(fdset_it_t *it)
+{
+ assert(it);
+
+#ifdef HAVE_EPOLL
+ const int idx = fdset_it_get_idx(it);
+ it->set->ev[idx].events = FDSET_REMOVE_FLAG;
+ it->dirty++;
+#elif HAVE_KQUEUE
+ const int idx = fdset_it_get_idx(it);
+ /* Bitwise negated filter marks event for delete. */
+ /* Filters become: */
+ /* [FreeBSD] */
+ /* EVFILT_READ (-1) -> 0 */
+ /* EVFILT_WRITE (-2) -> 1 */
+ /* [NetBSD] */
+ /* EVFILT_READ (0) -> -1 */
+ /* EVFILT_WRITE (1) -> -2 */
+ /* If not marked for delete then mark for delete. */
+#if defined(__NetBSD__)
+ if ((signed short)it->set->ev[idx].filter >= 0)
+#else
+ if (it->set->ev[idx].filter < 0)
+#endif
+ {
+ it->set->ev[idx].filter = ~it->set->ev[idx].filter;
+ }
+ it->dirty++;
+#else
+ (void)fdset_remove(it->set, fdset_it_get_idx(it));
+ /* Iterator should return on last valid already processed element. */
+ /* On `next` call (in for-loop) will point on first unprocessed. */
+ it->idx--;
+#endif
+}
+
+/*!
+ * \brief Commit changes made in fdset using iterator.
+ *
+ * \param it Target iterator.
+ */
+void fdset_it_commit(fdset_it_t *it);
+
+/*!
+ * \brief Decide if there is more received events.
+ *
+ * \param it Target iterator.
+ *
+ * \retval Logical flag representing 'done' state.
+ */
+inline static bool fdset_it_is_done(const fdset_it_t *it)
+{
+ assert(it);
+
+ return it->unprocessed <= 0;
+}
+
+/*!
+ * \brief Decide if event referenced by iterator is POLLIN event.
+ *
+ * \param it Target iterator.
+ *
+ * \retval Logical flag represents 'POLLIN' event received.
+ */
+inline static bool fdset_it_is_pollin(const fdset_it_t *it)
+{
+ assert(it);
+
+#ifdef HAVE_EPOLL
+ return it->ptr->events & EPOLLIN;
+#elif HAVE_KQUEUE
+ return it->ptr->filter == EVFILT_READ;
+#else
+ return it->set->pfd[it->idx].revents & POLLIN;
+#endif
+}
+
+/*!
+ * \brief Decide if event referenced by iterator is error event.
+ *
+ * \param it Target iterator.
+ *
+ * \retval Logical flag represents error event received.
+ */
+inline static bool fdset_it_is_error(const fdset_it_t *it)
+{
+ assert(it);
+
+#ifdef HAVE_EPOLL
+ return it->ptr->events & (EPOLLERR | EPOLLHUP);
+#elif HAVE_KQUEUE
+ return it->ptr->flags & EV_ERROR;
+#else
+ return it->set->pfd[it->idx].revents & (POLLERR | POLLHUP | POLLNVAL);
+#endif
+}
diff --git a/src/knot/common/log.c b/src/knot/common/log.c
new file mode 100644
index 0000000..8bbdc51
--- /dev/null
+++ b/src/knot/common/log.c
@@ -0,0 +1,491 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <time.h>
+#include <urcu.h>
+
+#ifdef ENABLE_SYSTEMD
+#define SD_JOURNAL_SUPPRESS_LOCATION 1
+#include <systemd/sd-journal.h>
+#include <systemd/sd-daemon.h>
+#endif
+
+#include "knot/common/log.h"
+#include "libknot/libknot.h"
+#include "contrib/ucw/lists.h"
+
+/*! Single log message buffer length (one line). */
+#define LOG_BUFLEN 512
+#define NULL_ZONE_STR "?"
+
+#ifdef ENABLE_SYSTEMD
+int use_journal = 0;
+#endif
+
+/*! Log context. */
+typedef struct {
+ size_t target_count; /*!< Log target count. */
+ int *target; /*!< Log targets. */
+ size_t file_count; /*!< Open files count. */
+ FILE **file; /*!< Open files. */
+ log_flag_t flags; /*!< Formatting flags. */
+} log_t;
+
+/*! Log singleton. */
+log_t *s_log = NULL;
+
+static bool log_isopen(void)
+{
+ return s_log != NULL;
+}
+
+static void sink_free(log_t *log)
+{
+ if (log == NULL) {
+ return;
+ }
+
+ // Close open log files.
+ for (int i = 0; i < log->file_count; ++i) {
+ fclose(log->file[i]);
+ }
+ free(log->target);
+ free(log->file);
+ free(log);
+}
+
+/*!
+ * \brief Create logging targets respecting their canonical order.
+ *
+ * Facilities ordering: Syslog, Stderr, Stdout, File0...
+ */
+static log_t *sink_setup(size_t file_count)
+{
+ log_t *log = malloc(sizeof(*log));
+ if (log == NULL) {
+ return NULL;
+ }
+ memset(log, 0, sizeof(*log));
+
+ // Reserve space for targets.
+ log->target_count = LOG_TARGET_FILE + file_count;
+ log->target = malloc(LOG_SOURCE_ANY * sizeof(int) * log->target_count);
+ if (!log->target) {
+ free(log);
+ return NULL;
+ }
+ memset(log->target, 0, LOG_SOURCE_ANY * sizeof(int) * log->target_count);
+
+ // Reserve space for log files.
+ if (file_count > 0) {
+ log->file = malloc(sizeof(FILE *) * file_count);
+ if (!log->file) {
+ free(log->target);
+ free(log);
+ return NULL;
+ }
+ memset(log->file, 0, sizeof(FILE *) * file_count);
+ }
+
+ return log;
+}
+
+static void sink_publish(log_t *log)
+{
+ log_t **current_log = &s_log;
+ log_t *old_log = rcu_xchg_pointer(current_log, log);
+ synchronize_rcu();
+ sink_free(old_log);
+}
+
+static int *src_levels(log_t *log, log_target_t target, log_source_t src)
+{
+ assert(src < LOG_SOURCE_ANY);
+ return &log->target[LOG_SOURCE_ANY * target + src];
+}
+
+static void sink_levels_set(log_t *log, log_target_t target, log_source_t src, int levels)
+{
+ // Assign levels to the specified source.
+ if (src != LOG_SOURCE_ANY) {
+ *src_levels(log, target, src) = levels;
+ } else {
+ // ANY ~ set levels to all sources.
+ for (int i = 0; i < LOG_SOURCE_ANY; ++i) {
+ *src_levels(log, target, i) = levels;
+ }
+ }
+}
+
+static void sink_levels_add(log_t *log, log_target_t target, log_source_t src, int levels)
+{
+ // Add levels to the specified source.
+ if (src != LOG_SOURCE_ANY) {
+ *src_levels(log, target, src) |= levels;
+ } else {
+ // ANY ~ add levels to all sources.
+ for (int i = 0; i < LOG_SOURCE_ANY; ++i) {
+ *src_levels(log, target, i) |= levels;
+ }
+ }
+}
+
+void log_init(void)
+{
+ // Setup initial state.
+ int emask = LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING);
+ int imask = LOG_MASK(LOG_NOTICE) | LOG_MASK(LOG_INFO);
+
+ // Publish base log sink.
+ log_t *log = sink_setup(0);
+ if (log == NULL) {
+ fprintf(stderr, "Failed to setup logging\n");
+ return;
+ }
+
+#ifdef ENABLE_SYSTEMD
+ // Should only use the journal if system was booted with systemd.
+ use_journal = sd_booted();
+#endif
+
+ sink_levels_set(log, LOG_TARGET_SYSLOG, LOG_SOURCE_ANY, emask);
+ sink_levels_set(log, LOG_TARGET_STDERR, LOG_SOURCE_ANY, emask);
+ sink_levels_set(log, LOG_TARGET_STDOUT, LOG_SOURCE_ANY, imask);
+ sink_publish(log);
+
+ setlogmask(LOG_UPTO(LOG_DEBUG));
+ openlog(PACKAGE_NAME, LOG_PID, LOG_DAEMON);
+}
+
+void log_close(void)
+{
+ sink_publish(NULL);
+
+ fflush(stdout);
+ fflush(stderr);
+
+ closelog();
+}
+
+void log_flag_set(log_flag_t flag)
+{
+ if (log_isopen()) {
+ s_log->flags |= flag;
+ }
+}
+
+void log_levels_set(log_target_t target, log_source_t src, int levels)
+{
+ if (log_isopen()) {
+ sink_levels_set(s_log, target, src, levels);
+ }
+}
+
+void log_levels_add(log_target_t target, log_source_t src, int levels)
+{
+ if (log_isopen()) {
+ sink_levels_add(s_log, target, src, levels);
+ }
+}
+
+static void emit_log_msg(int level, log_source_t src, const char *zone,
+ size_t zone_len, const char *msg, const char *param)
+{
+ log_t *log = s_log;
+
+ // Syslog target.
+ if (*src_levels(log, LOG_TARGET_SYSLOG, src) & LOG_MASK(level)) {
+#ifdef ENABLE_SYSTEMD
+ if (use_journal) {
+ char *zone_fmt = zone ? "ZONE=%.*s." : NULL;
+ sd_journal_send("PRIORITY=%d", level,
+ "MESSAGE=%s", msg,
+ zone_fmt, zone_len, zone,
+ param, NULL);
+ } else
+#endif
+ {
+ syslog(level, "%s", msg);
+ }
+ }
+
+ // Prefix date and time.
+ char tstr[LOG_BUFLEN] = { 0 };
+ if (!(s_log->flags & LOG_FLAG_NOTIMESTAMP)) {
+ struct tm lt;
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ time_t sec = tv.tv_sec;
+ if (localtime_r(&sec, &lt) != NULL) {
+ strftime(tstr, sizeof(tstr), KNOT_LOG_TIME_FORMAT " ", &lt);
+ }
+ }
+
+ // Other log targets.
+ for (int i = LOG_TARGET_STDERR; i < LOG_TARGET_FILE + log->file_count; ++i) {
+ if (*src_levels(log, i, src) & LOG_MASK(level)) {
+ FILE *stream;
+ switch (i) {
+ case LOG_TARGET_STDERR: stream = stderr; break;
+ case LOG_TARGET_STDOUT: stream = stdout; break;
+ default: stream = log->file[i - LOG_TARGET_FILE]; break;
+ }
+
+ // Print the message.
+ fprintf(stream, "%s%s\n", tstr, msg);
+ if (stream == stdout) {
+ fflush(stream);
+ }
+ }
+ }
+}
+
+static const char *level_prefix(int level)
+{
+ switch (level) {
+ case LOG_DEBUG: return "debug";
+ case LOG_INFO: return "info";
+ case LOG_NOTICE: return "notice";
+ case LOG_WARNING: return "warning";
+ case LOG_ERR: return "error";
+ case LOG_CRIT: return "critical";
+ default: return NULL;
+ };
+}
+
+static int log_msg_add(char **write, size_t *capacity, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ int written = vsnprintf(*write, *capacity, fmt, args);
+ va_end(args);
+
+ if (written < 0 || written >= *capacity) {
+ return KNOT_ESPACE;
+ }
+
+ *write += written;
+ *capacity -= written;
+
+ return KNOT_EOK;
+}
+
+static void log_msg_text(int level, log_source_t src, const char *zone,
+ const char *fmt, va_list args, const char *param)
+{
+ if (!log_isopen() || src == LOG_SOURCE_ANY) {
+ return;
+ }
+
+ // Buffer for log message.
+ char buff[LOG_BUFLEN];
+ char *write = buff;
+ size_t capacity = sizeof(buff);
+
+ rcu_read_lock();
+
+ // Prefix error level.
+ if (level != LOG_INFO || !(s_log->flags & LOG_FLAG_NOINFO)) {
+ const char *prefix = level_prefix(level);
+ int ret = log_msg_add(&write, &capacity, "%s: ", prefix);
+ if (ret != KNOT_EOK) {
+ rcu_read_unlock();
+ return;
+ }
+ }
+
+ // Prefix zone name.
+ size_t zone_len = 0;
+ if (zone != NULL) {
+ zone_len = strlen(zone);
+ if (zone_len > 0 && zone[zone_len - 1] == '.') {
+ zone_len--;
+ }
+
+ int ret = log_msg_add(&write, &capacity, "[%.*s.] ", (int)zone_len, zone);
+ if (ret != KNOT_EOK) {
+ rcu_read_unlock();
+ return;
+ }
+ }
+
+ // Compile log message.
+ int ret = vsnprintf(write, capacity, fmt, args);
+ if (ret >= 0) {
+ // Send to logging targets.
+ emit_log_msg(level, src, zone, zone_len, buff, param);
+ }
+
+ rcu_read_unlock();
+}
+
+void log_fmt(int priority, log_source_t src, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ log_msg_text(priority, src, NULL, fmt, args, NULL);
+ va_end(args);
+}
+
+void log_fmt_zone(int priority, log_source_t src, const knot_dname_t *zone,
+ const char *param, const char *fmt, ...)
+{
+ knot_dname_txt_storage_t buff;
+ char *zone_str = knot_dname_to_str(buff, zone, sizeof(buff));
+ if (zone_str == NULL) {
+ zone_str = NULL_ZONE_STR;
+ }
+
+ va_list args;
+ va_start(args, fmt);
+ log_msg_text(priority, src, zone_str, fmt, args, param);
+ va_end(args);
+}
+
+void log_fmt_zone_str(int priority, log_source_t src, const char *zone,
+ const char *fmt, ...)
+{
+ if (zone == NULL) {
+ zone = NULL_ZONE_STR;
+ }
+
+ va_list args;
+ va_start(args, fmt);
+ log_msg_text(priority, src, zone, fmt, args, NULL);
+ va_end(args);
+}
+
+int log_update_privileges(int uid, int gid)
+{
+ if (!log_isopen()) {
+ return KNOT_EOK;
+ }
+
+ for (int i = 0; i < s_log->file_count; ++i) {
+ if (fchown(fileno(s_log->file[i]), uid, gid) < 0) {
+ log_error("failed to change log file owner");
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static log_target_t get_logtype(const char *logname)
+{
+ assert(logname);
+
+ if (strcasecmp(logname, "syslog") == 0) {
+ return LOG_TARGET_SYSLOG;
+ } else if (strcasecmp(logname, "stderr") == 0) {
+ return LOG_TARGET_STDERR;
+ } else if (strcasecmp(logname, "stdout") == 0) {
+ return LOG_TARGET_STDOUT;
+ } else {
+ return LOG_TARGET_FILE;
+ }
+}
+
+static int log_open_file(log_t *log, const char *filename)
+{
+ assert(LOG_TARGET_FILE + log->file_count < log->target_count);
+
+ // Open the file.
+ log->file[log->file_count] = fopen(filename, "a");
+ if (log->file[log->file_count] == NULL) {
+ return knot_map_errno();
+ }
+
+ // Disable buffering.
+ setvbuf(log->file[log->file_count], NULL, _IONBF, 0);
+
+ return LOG_TARGET_FILE + log->file_count++;
+}
+
+void log_reconfigure(conf_t *conf)
+{
+ // Use defaults if no 'log' section is configured.
+ if (conf_id_count(conf, C_LOG) == 0) {
+ log_close();
+ log_init();
+ return;
+ }
+
+ // Find maximum log target id.
+ unsigned files = 0;
+ for (conf_iter_t iter = conf_iter(conf, C_LOG); iter.code == KNOT_EOK;
+ conf_iter_next(conf, &iter)) {
+ conf_val_t id = conf_iter_id(conf, &iter);
+ if (get_logtype(conf_str(&id)) == LOG_TARGET_FILE) {
+ ++files;
+ }
+ }
+
+ // Initialize logsystem.
+ log_t *log = sink_setup(files);
+ if (log == NULL) {
+ fprintf(stderr, "Failed to setup logging\n");
+ return;
+ }
+
+ // Setup logs.
+ for (conf_iter_t iter = conf_iter(conf, C_LOG); iter.code == KNOT_EOK;
+ conf_iter_next(conf, &iter)) {
+ conf_val_t id = conf_iter_id(conf, &iter);
+ const char *logname = conf_str(&id);
+
+ // Get target.
+ int target = get_logtype(logname);
+ if (target == LOG_TARGET_FILE) {
+ target = log_open_file(log, logname);
+ if (target < 0) {
+ log_error("failed to open log, file '%s' (%s)",
+ logname, knot_strerror(target));
+ continue;
+ }
+ }
+
+ conf_val_t levels_val;
+ unsigned levels;
+
+ // Set SERVER logging.
+ levels_val = conf_id_get(conf, C_LOG, C_SERVER, &id);
+ levels = conf_opt(&levels_val);
+ sink_levels_add(log, target, LOG_SOURCE_SERVER, levels);
+
+ // Set CONTROL logging.
+ levels_val = conf_id_get(conf, C_LOG, C_CTL, &id);
+ levels = conf_opt(&levels_val);
+ sink_levels_add(log, target, LOG_SOURCE_CONTROL, levels);
+
+ // Set ZONE logging.
+ levels_val = conf_id_get(conf, C_LOG, C_ZONE, &id);
+ levels = conf_opt(&levels_val);
+ sink_levels_add(log, target, LOG_SOURCE_ZONE, levels);
+
+ // Set ANY logging.
+ levels_val = conf_id_get(conf, C_LOG, C_ANY, &id);
+ levels = conf_opt(&levels_val);
+ sink_levels_add(log, target, LOG_SOURCE_ANY, levels);
+ }
+
+ sink_publish(log);
+}
diff --git a/src/knot/common/log.h b/src/knot/common/log.h
new file mode 100644
index 0000000..49a8375
--- /dev/null
+++ b/src/knot/common/log.h
@@ -0,0 +1,187 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \brief Logging facility.
+ *
+ * Supported log levels/priorities:
+ * LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, and LOG_DEBUG.
+ *
+ * \see syslog.h
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <syslog.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "libknot/dname.h"
+#include "knot/conf/conf.h"
+
+/*! \brief Format for timestamps in log files. */
+#define KNOT_LOG_TIME_FORMAT "%Y-%m-%dT%H:%M:%S%z"
+
+/*! \brief Logging targets. */
+typedef enum {
+ LOG_TARGET_SYSLOG = 0, /*!< System log. */
+ LOG_TARGET_STDERR = 1, /*!< Standard error stream. */
+ LOG_TARGET_STDOUT = 2, /*!< Standard output stream. */
+ LOG_TARGET_FILE = 3 /*!< Generic logging to a file (unbuffered). */
+} log_target_t;
+
+/*! \brief Logging sources. */
+typedef enum {
+ LOG_SOURCE_SERVER = 0, /*!< Server module. */
+ LOG_SOURCE_CONTROL = 1, /*!< Server control module. */
+ LOG_SOURCE_ZONE = 2, /*!< Zone manipulation module. */
+ LOG_SOURCE_ANY = 3 /*!< Any module. */
+} log_source_t;
+
+/*! \brief Logging format flags. */
+typedef enum {
+ LOG_FLAG_NOTIMESTAMP = 1 << 0, /*!< Don't print timestamp prefix. */
+ LOG_FLAG_NOINFO = 1 << 1 /*!< Don't print info level prefix. */
+} log_flag_t;
+
+/*!
+ * \brief Setup logging subsystem.
+ */
+void log_init(void);
+
+/*!
+ * \brief Close and deinitialize log.
+ */
+void log_close(void);
+
+/*!
+ * \brief Set logging format flag.
+ */
+void log_flag_set(log_flag_t flag);
+
+/*!
+ * \brief Set log levels for given target.
+ *
+ * \param target Logging target index (LOG_TARGET_SYSLOG...).
+ * \param src Logging source (LOG_SOURCE_SERVER...LOG_SOURCE_ANY).
+ * \param levels Bitmask of specified log levels.
+ */
+void log_levels_set(log_target_t target, log_source_t src, int levels);
+
+/*!
+ * \brief Add log levels to a given target.
+ *
+ * New levels are added on top of existing, the resulting levels set is
+ * "old_levels OR new_levels".
+ *
+ * \param target Logging target index (LOG_TARGET_SYSLOG...).
+ * \param src Logging source (LOG_SOURCE_SERVER...LOG_SOURCE_ANY).
+ * \param levels Bitmask of specified log levels.
+ */
+void log_levels_add(log_target_t target, log_source_t src, int levels);
+
+/*!
+ * \brief Log message into server category.
+ *
+ * Function follows printf() format.
+ *
+ * \note LOG_SOURCE_ANY is not a valid value for the src parameter.
+ *
+ * \param priority Message priority.
+ * \param src Message source (LOG_SOURCE_SERVER...LOG_SOURCE_ZONE).
+ * \param fmt Content of the logged message.
+ */
+void log_fmt(int priority, log_source_t src, const char *fmt, ...)
+__attribute__((format(printf, 3, 4)));
+
+/*!
+ * \brief Log message into zone category.
+ *
+ * \see log_fmt
+ *
+ * \param priority Message priority.
+ * \param src Message source (LOG_SOURCE_SERVER...LOG_SOURCE_ZONE).
+ * \param zone Zone name in wire format.
+ * \param param Optional key-value parameter for structured logging.
+ * \param fmt Content of the logged message.
+ */
+void log_fmt_zone(int priority, log_source_t src, const knot_dname_t *zone,
+ const char *param, const char *fmt, ...)
+__attribute__((format(printf, 5, 6)));
+
+/*!
+ * \brief Log message into zone category.
+ *
+ * \see log_fmt
+ *
+ * \param zone Zone name as an ASCII string.
+ * \param priority Message priority.
+ * \param src Message source (LOG_SOURCE_SERVER...LOG_SOURCE_ZONE).
+ * \param fmt Content of the logged message.
+ */
+void log_fmt_zone_str(int priority, log_source_t src, const char *zone, const char *fmt, ...)
+__attribute__((format(printf, 4, 5)));
+
+/*!
+ * \brief Convenient logging macros.
+ */
+#define log_fatal(msg, ...) log_fmt(LOG_CRIT, LOG_SOURCE_SERVER, msg, ##__VA_ARGS__)
+#define log_error(msg, ...) log_fmt(LOG_ERR, LOG_SOURCE_SERVER, msg, ##__VA_ARGS__)
+#define log_warning(msg, ...) log_fmt(LOG_WARNING, LOG_SOURCE_SERVER, msg, ##__VA_ARGS__)
+#define log_notice(msg, ...) log_fmt(LOG_NOTICE, LOG_SOURCE_SERVER, msg, ##__VA_ARGS__)
+#define log_info(msg, ...) log_fmt(LOG_INFO, LOG_SOURCE_SERVER, msg, ##__VA_ARGS__)
+#define log_debug(msg, ...) log_fmt(LOG_DEBUG, LOG_SOURCE_SERVER, msg, ##__VA_ARGS__)
+
+#define log_ctl_fatal(msg, ...) log_fmt(LOG_CRIT, LOG_SOURCE_CONTROL, msg, ##__VA_ARGS__)
+#define log_ctl_error(msg, ...) log_fmt(LOG_ERR, LOG_SOURCE_CONTROL, msg, ##__VA_ARGS__)
+#define log_ctl_warning(msg, ...) log_fmt(LOG_WARNING, LOG_SOURCE_CONTROL, msg, ##__VA_ARGS__)
+#define log_ctl_notice(msg, ...) log_fmt(LOG_NOTICE, LOG_SOURCE_CONTROL, msg, ##__VA_ARGS__)
+#define log_ctl_info(msg, ...) log_fmt(LOG_INFO, LOG_SOURCE_CONTROL, msg, ##__VA_ARGS__)
+#define log_ctl_debug(msg, ...) log_fmt(LOG_DEBUG, LOG_SOURCE_CONTROL, msg, ##__VA_ARGS__)
+
+#define log_ctl_zone_str_error(zone, msg, ...) log_fmt_zone_str(LOG_ERR, LOG_SOURCE_CONTROL, zone, msg, ##__VA_ARGS__)
+#define log_ctl_zone_str_info(zone, msg, ...) log_fmt_zone_str(LOG_INFO, LOG_SOURCE_CONTROL, zone, msg, ##__VA_ARGS__)
+#define log_ctl_zone_str_debug(zone, msg, ...) log_fmt_zone_str(LOG_DEBUG, LOG_SOURCE_CONTROL, zone, msg, ##__VA_ARGS__)
+
+#define log_zone_fatal(zone, msg, ...) log_fmt_zone(LOG_CRIT, LOG_SOURCE_ZONE, zone, NULL, msg, ##__VA_ARGS__)
+#define log_zone_error(zone, msg, ...) log_fmt_zone(LOG_ERR, LOG_SOURCE_ZONE, zone, NULL, msg, ##__VA_ARGS__)
+#define log_zone_warning(zone, msg, ...) log_fmt_zone(LOG_WARNING, LOG_SOURCE_ZONE, zone, NULL, msg, ##__VA_ARGS__)
+#define log_zone_notice(zone, msg, ...) log_fmt_zone(LOG_NOTICE, LOG_SOURCE_ZONE, zone, NULL, msg, ##__VA_ARGS__)
+#define log_zone_info(zone, msg, ...) log_fmt_zone(LOG_INFO, LOG_SOURCE_ZONE, zone, NULL, msg, ##__VA_ARGS__)
+#define log_zone_debug(zone, msg, ...) log_fmt_zone(LOG_DEBUG, LOG_SOURCE_ZONE, zone, NULL, msg, ##__VA_ARGS__)
+
+#define log_zone_str_fatal(zone, msg, ...) log_fmt_zone_str(LOG_CRIT, LOG_SOURCE_ZONE, zone, msg, ##__VA_ARGS__)
+#define log_zone_str_error(zone, msg, ...) log_fmt_zone_str(LOG_ERR, LOG_SOURCE_ZONE, zone, msg, ##__VA_ARGS__)
+#define log_zone_str_warning(zone, msg, ...) log_fmt_zone_str(LOG_WARNING, LOG_SOURCE_ZONE, zone, msg, ##__VA_ARGS__)
+#define log_zone_str_notice(zone, msg, ...) log_fmt_zone_str(LOG_NOTICE, LOG_SOURCE_ZONE, zone, msg, ##__VA_ARGS__)
+#define log_zone_str_info(zone, msg, ...) log_fmt_zone_str(LOG_INFO, LOG_SOURCE_ZONE, zone, msg, ##__VA_ARGS__)
+#define log_zone_str_debug(zone, msg, ...) log_fmt_zone_str(LOG_DEBUG, LOG_SOURCE_ZONE, zone, msg, ##__VA_ARGS__)
+
+/*!
+ * \brief Update open files ownership.
+ *
+ * \param uid New owner id.
+ * \param gid New group id.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int log_update_privileges(int uid, int gid);
+
+/*!
+ * \brief Setup logging facilities from config.
+ */
+void log_reconfigure(conf_t *conf);
diff --git a/src/knot/common/process.c b/src/knot/common/process.c
new file mode 100644
index 0000000..bdec1d4
--- /dev/null
+++ b/src/knot/common/process.c
@@ -0,0 +1,194 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "knot/common/log.h"
+#include "knot/common/process.h"
+#include "knot/conf/conf.h"
+#include "libknot/errcode.h"
+
+static char* pid_filename(void)
+{
+ conf_val_t val = conf_get(conf(), C_SRV, C_RUNDIR);
+ char *rundir = conf_abs_path(&val, NULL);
+ val = conf_get(conf(), C_SRV, C_PIDFILE);
+ char *pidfile = conf_abs_path(&val, rundir);
+ free(rundir);
+
+ return pidfile;
+}
+
+static pid_t pid_read(const char *filename)
+{
+ if (filename == NULL) {
+ return 0;
+ }
+
+ size_t len = 0;
+ char buf[64] = { 0 };
+
+ FILE *fp = fopen(filename, "r");
+ if (fp == NULL) {
+ return 0;
+ }
+
+ /* Read the content of the file. */
+ len = fread(buf, 1, sizeof(buf) - 1, fp);
+ fclose(fp);
+ if (len < 1) {
+ return 0;
+ }
+
+ /* Convert pid. */
+ errno = 0;
+ char *end = 0;
+ unsigned long pid = strtoul(buf, &end, 10);
+ if (end == buf || *end != '\0'|| errno != 0) {
+ return 0;
+ }
+
+ return (pid_t)pid;
+}
+
+static int pid_write(const char *filename, pid_t pid)
+{
+ if (filename == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Convert. */
+ char buf[64];
+ int len = 0;
+ len = snprintf(buf, sizeof(buf), "%lu", (unsigned long)pid);
+ if (len < 0 || len >= sizeof(buf)) {
+ return KNOT_ENOMEM;
+ }
+
+ /* Create file. */
+ int ret = KNOT_EOK;
+ int fd = open(filename, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP);
+ if (fd >= 0) {
+ if (write(fd, buf, len) != len) {
+ ret = knot_map_errno();
+ }
+ close(fd);
+ } else {
+ ret = knot_map_errno();
+ }
+
+ return ret;
+}
+
+unsigned long pid_check_and_create(void)
+{
+ struct stat st;
+ char *pidfile = pid_filename();
+ pid_t pid = pid_read(pidfile);
+
+ /* Check PID for existence and liveness. */
+ if (pid > 0 && pid_running(pid)) {
+ log_fatal("server PID found, already running");
+ free(pidfile);
+ return 0;
+ } else if (stat(pidfile, &st) == 0) {
+ assert(pidfile);
+ log_warning("removing stale PID file '%s'", pidfile);
+ pid_cleanup();
+ }
+
+ /* Get current PID. */
+ pid = getpid();
+
+ /* Create a PID file. */
+ int ret = pid_write(pidfile, pid);
+ if (ret != KNOT_EOK) {
+ log_fatal("failed to create a PID file '%s' (%s)", pidfile,
+ knot_strerror(ret));
+ free(pidfile);
+ return 0;
+ }
+ free(pidfile);
+
+ return (unsigned long)pid;
+}
+
+void pid_cleanup(void)
+{
+ char *pidfile = pid_filename();
+ if (pidfile != NULL) {
+ (void)unlink(pidfile);
+ free(pidfile);
+ }
+}
+
+bool pid_running(pid_t pid)
+{
+ return kill(pid, 0) == 0;
+}
+
+int proc_update_privileges(int uid, int gid)
+{
+#ifdef HAVE_SETGROUPS
+ /* Drop supplementary groups. */
+ if ((uid_t)uid != getuid() || (gid_t)gid != getgid()) {
+ if (setgroups(0, NULL) < 0) {
+ log_warning("failed to drop supplementary groups for "
+ "UID %d (%s)", getuid(), strerror(errno));
+ }
+# ifdef HAVE_INITGROUPS
+ struct passwd *pw;
+ if ((pw = getpwuid(uid)) == NULL) {
+ log_warning("failed to get passwd entry for UID %d (%s)",
+ uid, strerror(errno));
+ } else {
+ if (initgroups(pw->pw_name, gid) < 0) {
+ log_warning("failed to set supplementary groups "
+ "for UID %d (%s)", uid, strerror(errno));
+ }
+ }
+# endif /* HAVE_INITGROUPS */
+ }
+#endif /* HAVE_SETGROUPS */
+
+ /* Watch uid/gid. */
+ if ((gid_t)gid != getgid()) {
+ log_info("changing GID to %d", gid);
+ if (setregid(gid, gid) < 0) {
+ log_error("failed to change GID to %d", gid);
+ return KNOT_ERROR;
+ }
+ }
+ if ((uid_t)uid != getuid()) {
+ log_info("changing UID to %d", uid);
+ if (setreuid(uid, uid) < 0) {
+ log_error("failed to change UID to %d", uid);
+ return KNOT_ERROR;
+ }
+ }
+
+ return KNOT_EOK;
+}
diff --git a/src/knot/common/process.h b/src/knot/common/process.h
new file mode 100644
index 0000000..14ca34e
--- /dev/null
+++ b/src/knot/common/process.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \brief Functions for POSIX process handling.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <unistd.h>
+
+/*!
+ * \brief Check if PID file exists and create it if possible.
+ *
+ * \retval 0 if failed.
+ * \retval Current PID.
+ */
+unsigned long pid_check_and_create(void);
+
+/*!
+ * \brief Remove PID file.
+ *
+ * \warning PID file content won't be checked.
+ */
+void pid_cleanup(void);
+
+/*!
+ * \brief Return true if the PID is running.
+ *
+ * \param pid Process ID.
+ *
+ * \retval 1 if running.
+ * \retval 0 if not running (or error).
+ */
+bool pid_running(pid_t pid);
+
+/*!
+ * \brief Update process privileges to new UID/GID.
+ *
+ * \param uid New user ID.
+ * \param gid New group ID.
+ *
+ * \retval KNOT_EOK on success.
+ * \retval KNOT_ERROR if UID or GID change failed.
+ */
+int proc_update_privileges(int uid, int gid);
diff --git a/src/knot/common/stats.c b/src/knot/common/stats.c
new file mode 100644
index 0000000..2b8cb09
--- /dev/null
+++ b/src/knot/common/stats.c
@@ -0,0 +1,309 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <urcu.h>
+
+#include "contrib/files.h"
+#include "knot/common/stats.h"
+#include "knot/common/log.h"
+#include "knot/nameserver/query_module.h"
+
+struct {
+ bool active_dumper;
+ pthread_t dumper;
+ uint32_t timer;
+ server_t *server;
+} stats = { 0 };
+
+typedef struct {
+ FILE *fd;
+ const list_t *query_modules;
+ const knot_dname_t *zone;
+ bool zone_emitted;
+} dump_ctx_t;
+
+#define DUMP_STR(fd, level, name, ...) do { \
+ fprintf(fd, "%-.*s"name": %s\n", level, " ", ##__VA_ARGS__); \
+ } while (0)
+#define DUMP_CTR(fd, level, name, ...) do { \
+ fprintf(fd, "%-.*s"name": %"PRIu64"\n", level, " ", ##__VA_ARGS__); \
+ } while (0)
+
+uint64_t server_zone_count(server_t *server)
+{
+ return knot_zonedb_size(server->zone_db);
+}
+
+const stats_item_t server_stats[] = {
+ { "zone-count", server_zone_count },
+ { 0 }
+};
+
+uint64_t stats_get_counter(uint64_t **stats_vals, uint32_t offset, unsigned threads)
+{
+ uint64_t res = 0;
+ for (unsigned i = 0; i < threads; i++) {
+ res += ATOMIC_GET(stats_vals[i][offset]);
+ }
+ return res;
+}
+
+static void dump_counters(FILE *fd, int level, mod_ctr_t *ctr, uint64_t **stats_vals, unsigned threads)
+{
+ for (uint32_t j = 0; j < ctr->count; j++) {
+ uint64_t counter = stats_get_counter(stats_vals, ctr->offset + j, threads);
+
+ // Skip empty counters.
+ if (counter == 0) {
+ continue;
+ }
+
+ if (ctr->idx_to_str != NULL) {
+ char *str = ctr->idx_to_str(j, ctr->count);
+ if (str != NULL) {
+ DUMP_CTR(fd, level, "%s", str, counter);
+ free(str);
+ }
+ } else {
+ DUMP_CTR(fd, level, "%u", j, counter);
+ }
+ }
+}
+
+static void dump_modules(dump_ctx_t *ctx)
+{
+ int level = 0;
+ knotd_mod_t *mod;
+ WALK_LIST(mod, *ctx->query_modules) {
+ // Skip modules without statistics.
+ if (mod->stats_count == 0) {
+ continue;
+ }
+
+ // Dump zone name.
+ if (ctx->zone != NULL) {
+ // Prevent from zone section override.
+ if (!ctx->zone_emitted) {
+ DUMP_STR(ctx->fd, 0, "zone", "");
+ ctx->zone_emitted = true;
+ }
+ level = 1;
+
+ knot_dname_txt_storage_t name;
+ if (knot_dname_to_str(name, ctx->zone, sizeof(name)) == NULL) {
+ return;
+ }
+ DUMP_STR(ctx->fd, level++, "\"%s\"", name, "");
+ } else {
+ level = 0;
+ }
+
+ unsigned threads = knotd_mod_threads(mod);
+
+ // Dump module counters.
+ DUMP_STR(ctx->fd, level, "%s", mod->id->name + 1, "");
+ for (int i = 0; i < mod->stats_count; i++) {
+ mod_ctr_t *ctr = mod->stats_info + i;
+ if (ctr->name == NULL) {
+ // Empty counter.
+ continue;
+ }
+ if (ctr->count == 1) {
+ // Simple counter.
+ uint64_t counter = stats_get_counter(mod->stats_vals,
+ ctr->offset, threads);
+ DUMP_CTR(ctx->fd, level + 1, "%s", ctr->name, counter);
+ } else {
+ // Array of counters.
+ DUMP_STR(ctx->fd, level + 1, "%s", ctr->name, "");
+ dump_counters(ctx->fd, level + 2, ctr, mod->stats_vals, threads);
+ }
+ }
+ }
+}
+
+static void zone_stats_dump(zone_t *zone, dump_ctx_t *ctx)
+{
+ if (EMPTY_LIST(zone->query_modules)) {
+ return;
+ }
+
+ ctx->query_modules = &zone->query_modules;
+ ctx->zone = zone->name;
+
+ dump_modules(ctx);
+}
+
+static void dump_to_file(FILE *fd, server_t *server)
+{
+ char date[64] = "";
+
+ // Get formatted current time string.
+ struct tm tm;
+ time_t now = time(NULL);
+ localtime_r(&now, &tm);
+ strftime(date, sizeof(date), KNOT_LOG_TIME_FORMAT, &tm);
+
+ // Get the server identity.
+ conf_val_t val = conf_get(conf(), C_SRV, C_IDENT);
+ const char *ident = conf_str(&val);
+ if (ident == NULL || ident[0] == '\0') {
+ ident = conf()->hostname;
+ }
+
+ // Dump record header.
+ fprintf(fd,
+ "---\n"
+ "time: %s\n"
+ "identity: %s\n",
+ date, ident);
+
+ // Dump server statistics.
+ DUMP_STR(fd, 0, "server", "");
+ for (const stats_item_t *item = server_stats; item->name != NULL; item++) {
+ DUMP_CTR(fd, 1, "%s", item->name, item->val(server));
+ }
+
+ dump_ctx_t ctx = {
+ .fd = fd,
+ .query_modules = conf()->query_modules,
+ };
+
+ // Dump global statistics.
+ dump_modules(&ctx);
+
+ // Dump zone statistics.
+ knot_zonedb_foreach(server->zone_db, zone_stats_dump, &ctx);
+}
+
+static void dump_stats(server_t *server)
+{
+ conf_t *pconf = conf();
+ conf_val_t val = conf_get(pconf, C_SRV, C_RUNDIR);
+ char *rundir = conf_abs_path(&val, NULL);
+ val = conf_get(pconf, C_STATS, C_FILE);
+ char *file_name = conf_abs_path(&val, rundir);
+ free(rundir);
+
+ val = conf_get(pconf, C_STATS, C_APPEND);
+ bool append = conf_bool(&val);
+
+ // Open or create output file.
+ FILE *fd = NULL;
+ char *tmp_name = NULL;
+ if (append) {
+ fd = fopen(file_name, "a");
+ if (fd == NULL) {
+ log_error("stats, failed to append file '%s' (%s)",
+ file_name, knot_strerror(knot_map_errno()));
+ free(file_name);
+ return;
+ }
+ } else {
+ int ret = open_tmp_file(file_name, &tmp_name, &fd,
+ S_IRUSR | S_IWUSR | S_IRGRP);
+ if (ret != KNOT_EOK) {
+ log_error("stats, failed to open file '%s' (%s)",
+ file_name, knot_strerror(ret));
+ free(file_name);
+ return;
+ }
+ }
+ assert(fd);
+
+ // Dump stats into the file.
+ dump_to_file(fd, server);
+
+ fflush(fd);
+ fclose(fd);
+
+ // Switch the file contents.
+ if (!append) {
+ int ret = rename(tmp_name, file_name);
+ if (ret != 0) {
+ log_error("stats, failed to access file '%s' (%s)",
+ file_name, knot_strerror(knot_map_errno()));
+ unlink(tmp_name);
+ }
+ free(tmp_name);
+ }
+
+ log_debug("stats, dumped into file '%s'", file_name);
+ free(file_name);
+}
+
+static void *dumper(void *data)
+{
+ rcu_register_thread();
+ while (true) {
+ assert(stats.timer > 0);
+ sleep(stats.timer);
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+ rcu_read_lock();
+ dump_stats(stats.server);
+ rcu_read_unlock();
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ }
+ rcu_unregister_thread();
+ return NULL;
+}
+
+void stats_reconfigure(conf_t *conf, server_t *server)
+{
+ if (conf == NULL || server == NULL) {
+ return;
+ }
+
+ // Update server context.
+ stats.server = server;
+
+ conf_val_t val = conf_get(conf, C_STATS, C_TIMER);
+ stats.timer = conf_int(&val);
+ if (stats.timer > 0) {
+ // Check if dumping is already running.
+ if (stats.active_dumper) {
+ return;
+ }
+
+ int ret = pthread_create(&stats.dumper, NULL, dumper, NULL);
+ if (ret != 0) {
+ log_error("stats, failed to launch periodic dumping (%s)",
+ knot_strerror(knot_map_errno_code(ret)));
+ } else {
+ stats.active_dumper = true;
+ }
+ // Stop current dumping.
+ } else if (stats.active_dumper) {
+ pthread_cancel(stats.dumper);
+ pthread_join(stats.dumper, NULL);
+ stats.active_dumper = false;
+ }
+}
+
+void stats_deinit(void)
+{
+ if (stats.active_dumper) {
+ pthread_cancel(stats.dumper);
+ pthread_join(stats.dumper, NULL);
+ }
+
+ memset(&stats, 0, sizeof(stats));
+}
diff --git a/src/knot/common/stats.h b/src/knot/common/stats.h
new file mode 100644
index 0000000..bd6df6d
--- /dev/null
+++ b/src/knot/common/stats.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \brief Server statistics general API.
+ */
+
+#pragma once
+
+#include "knot/server/server.h"
+
+typedef uint64_t (*stats_val_f)(server_t *server);
+
+/*!
+ * \brief Statistics metrics item.
+ */
+typedef struct {
+ const char *name; /*!< Metrics name. */
+ stats_val_f val; /*!< Metrics value getter. */
+} stats_item_t;
+
+/*!
+ * \brief Basic server metrics.
+ */
+extern const stats_item_t server_stats[];
+
+/*!
+ * \brief Read out value of single counter summed across threads.
+ */
+uint64_t stats_get_counter(uint64_t **stats_vals, uint32_t offset, unsigned threads);
+
+/*!
+ * \brief Reconfigures the statistics facility.
+ */
+void stats_reconfigure(conf_t *conf, server_t *server);
+
+/*!
+ * \brief Deinitializes the statistics facility.
+ */
+void stats_deinit(void);
diff --git a/src/knot/common/systemd.c b/src/knot/common/systemd.c
new file mode 100644
index 0000000..13c83e6
--- /dev/null
+++ b/src/knot/common/systemd.c
@@ -0,0 +1,168 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "knot/common/systemd.h"
+#include "contrib/strtonum.h"
+
+#ifdef ENABLE_SYSTEMD
+#include <systemd/sd-daemon.h>
+
+#define ZONE_LOAD_TIMEOUT_DEFAULT 60
+
+static int zone_load_timeout_s;
+
+static int systemd_zone_load_timeout(void)
+{
+ const char *timeout = getenv("KNOT_ZONE_LOAD_TIMEOUT_SEC");
+
+ int out;
+ if (timeout != NULL && timeout[0] != '\0' &&
+ str_to_int(timeout, &out, 0, 24 * 3600) == KNOT_EOK) {
+ return out;
+ } else {
+ return ZONE_LOAD_TIMEOUT_DEFAULT;
+ }
+}
+#endif
+
+#ifdef ENABLE_DBUS
+#include <systemd/sd-bus.h>
+
+static sd_bus *_dbus = NULL;
+#endif
+
+void systemd_zone_load_timeout_notify(void)
+{
+#ifdef ENABLE_SYSTEMD
+ if (zone_load_timeout_s == 0) {
+ zone_load_timeout_s = systemd_zone_load_timeout();
+ }
+ sd_notifyf(0, "EXTEND_TIMEOUT_USEC=%d000000", zone_load_timeout_s);
+#endif
+}
+
+void systemd_tasks_status_notify(int tasks)
+{
+#ifdef ENABLE_SYSTEMD
+ if (tasks > 0) {
+ sd_notifyf(0, "STATUS=Waiting for %d tasks to finish...", tasks);
+ } else {
+ sd_notify(0, "STATUS=");
+ }
+#endif
+}
+
+void systemd_ready_notify(void)
+{
+#ifdef ENABLE_SYSTEMD
+ sd_notify(0, "READY=1\nSTATUS=");
+#endif
+}
+
+void systemd_reloading_notify(void)
+{
+#ifdef ENABLE_SYSTEMD
+ sd_notify(0, "RELOADING=1\nSTATUS=");
+#endif
+}
+
+void systemd_stopping_notify(void)
+{
+#ifdef ENABLE_SYSTEMD
+ sd_notify(0, "STOPPING=1\nSTATUS=");
+#endif
+}
+
+int systemd_dbus_open(void)
+{
+#ifdef ENABLE_DBUS
+ if (_dbus != NULL) {
+ return KNOT_EOK;
+ }
+
+ int ret = sd_bus_open_system(&_dbus);
+ if (ret < 0) {
+ return ret;
+ }
+
+ /* Take a well-known service name so that clients can find us. */
+ ret = sd_bus_request_name(_dbus, KNOT_DBUS_NAME, 0);
+ if (ret < 0) {
+ systemd_dbus_close();
+ return ret;
+ }
+
+ return KNOT_EOK;
+#else
+ return KNOT_ENOTSUP;
+#endif
+}
+
+void systemd_dbus_close(void)
+{
+#ifdef ENABLE_DBUS
+ _dbus = sd_bus_unref(_dbus);
+#endif
+}
+
+#define emit_event(event, ...) \
+ sd_bus_emit_signal(_dbus, KNOT_DBUS_PATH, KNOT_DBUS_NAME".events", \
+ event, __VA_ARGS__)
+
+void systemd_emit_running(bool up)
+{
+#ifdef ENABLE_DBUS
+ emit_event(up ? KNOT_BUS_EVENT_STARTED : KNOT_BUS_EVENT_STOPPED, "");
+#endif
+}
+
+void systemd_emit_zone_updated(const knot_dname_t *zone_name, uint32_t serial)
+{
+#ifdef ENABLE_DBUS
+ knot_dname_txt_storage_t buff;
+ char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff));
+ if (zone_str != NULL) {
+ emit_event(KNOT_BUS_EVENT_ZONE_UPD, "su", zone_str, serial);
+ }
+#endif
+}
+
+void systemd_emit_zone_submission(const knot_dname_t *zone_name, uint16_t keytag,
+ const char *keyid)
+{
+#ifdef ENABLE_DBUS
+ knot_dname_txt_storage_t buff;
+ char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff));
+ if (zone_str != NULL) {
+ emit_event(KNOT_BUS_EVENT_ZONE_KSK_SUBM, "sqs", zone_str, keytag, keyid);
+ }
+#endif
+}
+
+void systemd_emit_zone_invalid(const knot_dname_t *zone_name)
+{
+#ifdef ENABLE_DBUS
+ knot_dname_txt_storage_t buff;
+ char *zone_str = knot_dname_to_str(buff, zone_name, sizeof(buff));
+ if (zone_str != NULL) {
+ emit_event(KNOT_BUS_EVENT_ZONE_INVALID, "s", zone_str);
+ }
+#endif
+}
diff --git a/src/knot/common/systemd.h b/src/knot/common/systemd.h
new file mode 100644
index 0000000..1cefd9c
--- /dev/null
+++ b/src/knot/common/systemd.h
@@ -0,0 +1,105 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \brief Systemd API wrappers.
+ */
+
+#pragma once
+
+#include "libknot/libknot.h"
+
+#define KNOT_DBUS_NAME "cz.nic.knotd"
+#define KNOT_DBUS_PATH "/cz/nic/knotd"
+
+#define KNOT_BUS_EVENT_STARTED "started"
+#define KNOT_BUS_EVENT_STOPPED "stopped"
+#define KNOT_BUS_EVENT_ZONE_UPD "zone_updated"
+#define KNOT_BUS_EVENT_ZONE_KSK_SUBM "zone_ksk_submission"
+#define KNOT_BUS_EVENT_ZONE_INVALID "zone_dnssec_invalid"
+
+/*!
+ * \brief Notify systemd about zone loading start.
+ */
+void systemd_zone_load_timeout_notify(void);
+
+/*!
+ * \brief Update systemd service status with information about number
+ * of scheduled tasks.
+ *
+ * \param tasks Number of tasks to be done.
+ */
+void systemd_tasks_status_notify(int tasks);
+
+/*!
+ * \brief Notify systemd about service is ready.
+ */
+void systemd_ready_notify(void);
+
+/*!
+ * \brief Notify systemd about service is reloading.
+ */
+void systemd_reloading_notify(void);
+
+/*!
+ * \brief Notify systemd about service is stopping.
+ */
+void systemd_stopping_notify(void);
+
+/*!
+ * \brief Creates unique D-Bus sender reference (common for whole process).
+ *
+ * \retval KNOT_EOK on successful create of reference.
+ * \retval Negative value on error.
+ */
+int systemd_dbus_open(void);
+
+/*!
+ * \brief Closes D-Bus.
+ */
+void systemd_dbus_close(void);
+
+/*!
+ * \brief Emit event signal for started daemon.
+ *
+ * \param up Indication if the server has been started.
+ */
+void systemd_emit_running(bool up);
+
+/*!
+ * \brief Emit event signal for updated zones.
+ *
+ * \param zone_name Zone name.
+ * \param serial Current zone SOA serial.
+ */
+void systemd_emit_zone_updated(const knot_dname_t *zone_name, uint32_t serial);
+
+/*!
+ * \brief Emit event signal for KSK submission.
+ *
+ * \param zone_name Zone name.
+ * \param keytag Keytag of the ready key.
+ * \param keyid KASP id of the ready key.
+ */
+void systemd_emit_zone_submission(const knot_dname_t *zone_name, uint16_t keytag,
+ const char *keyid);
+
+/*!
+ * \brief Emit event signal for failed DNSSEC validation.
+ *
+ * \param zone_name Zone name.
+ */
+void systemd_emit_zone_invalid(const knot_dname_t *zone_name);
diff --git a/src/knot/common/unreachable.c b/src/knot/common/unreachable.c
new file mode 100644
index 0000000..e137f3d
--- /dev/null
+++ b/src/knot/common/unreachable.c
@@ -0,0 +1,148 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "unreachable.h"
+
+knot_unreachables_t *global_unreachables = NULL;
+
+static uint32_t get_timestamp(void)
+{
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ uint64_t res = (uint64_t)t.tv_sec * 1000;
+ res += (uint64_t)t.tv_nsec / 1000000;
+ return res & 0xffffffff; // overflow does not matter since we are working with differences
+}
+
+knot_unreachables_t *knot_unreachables_init(uint32_t ttl_ms)
+{
+ knot_unreachables_t *res = calloc(1, sizeof(*res));
+ if (res != NULL) {
+ pthread_mutex_init(&res->mutex, NULL);
+ res->ttl_ms = ttl_ms;
+ init_list(&res->urs);
+ }
+ return res;
+}
+
+uint32_t knot_unreachables_ttl(knot_unreachables_t *urs, uint32_t new_ttl_ms)
+{
+ if (urs == NULL) {
+ return 0;
+ }
+
+ pthread_mutex_lock(&urs->mutex);
+
+ uint32_t prev = urs->ttl_ms;
+ urs->ttl_ms = new_ttl_ms;
+
+ pthread_mutex_unlock(&urs->mutex);
+
+ return prev;
+}
+
+void knot_unreachables_deinit(knot_unreachables_t **urs)
+{
+ if (urs != NULL && *urs != NULL) {
+ knot_unreachable_t *ur, *nxt;
+ WALK_LIST_DELSAFE(ur, nxt, (*urs)->urs) {
+ rem_node((node_t *)ur);
+ free(ur);
+ }
+ pthread_mutex_destroy(&(*urs)->mutex);
+ free(*urs);
+ *urs = NULL;
+ }
+}
+
+static bool clear_old(knot_unreachable_t *ur, uint32_t now, uint32_t ttl_ms)
+{
+ if (ur->time_ms != 0 && now - ur->time_ms > ttl_ms) {
+ rem_node((node_t *)ur);
+ free(ur);
+ return true;
+ }
+ return false;
+}
+
+// also clears up (some) expired unreachables
+static knot_unreachable_t *get_ur(knot_unreachables_t *urs,
+ const struct sockaddr_storage *addr,
+ const struct sockaddr_storage *via)
+{
+ assert(urs != NULL);
+
+ uint32_t now = get_timestamp();
+ knot_unreachable_t *ur, *nxt;
+ WALK_LIST_DELSAFE(ur, nxt, urs->urs) {
+ if (clear_old(ur, now, urs->ttl_ms)) {
+ continue;
+ }
+
+ if (sockaddr_cmp(&ur->addr, addr, false) == 0 &&
+ sockaddr_cmp(&ur->via, via, true) == 0) {
+ return ur;
+ }
+ }
+
+ return NULL;
+}
+
+bool knot_unreachable_is(knot_unreachables_t *urs,
+ const struct sockaddr_storage *addr,
+ const struct sockaddr_storage *via)
+{
+ if (urs == NULL) {
+ return false;
+ }
+ assert(addr);
+ assert(via);
+
+ pthread_mutex_lock(&urs->mutex);
+
+ bool res = (get_ur(urs, addr, via) != NULL);
+
+ pthread_mutex_unlock(&urs->mutex);
+
+ return res;
+}
+
+void knot_unreachable_add(knot_unreachables_t *urs,
+ const struct sockaddr_storage *addr,
+ const struct sockaddr_storage *via)
+{
+ if (urs == NULL) {
+ return;
+ }
+ assert(addr);
+ assert(via);
+
+ pthread_mutex_lock(&urs->mutex);
+
+ knot_unreachable_t *ur = malloc(sizeof(*ur));
+ if (ur != NULL) {
+ memcpy(&ur->addr, addr, sizeof(ur->addr));
+ memcpy(&ur->via, via, sizeof(ur->via));
+ ur->time_ms = get_timestamp();
+ add_head(&urs->urs, (node_t *)ur);
+ }
+
+ pthread_mutex_unlock(&urs->mutex);
+}
diff --git a/src/knot/common/unreachable.h b/src/knot/common/unreachable.h
new file mode 100644
index 0000000..40094f9
--- /dev/null
+++ b/src/knot/common/unreachable.h
@@ -0,0 +1,87 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "contrib/sockaddr.h"
+#include "contrib/ucw/lists.h"
+
+typedef struct {
+ node_t n;
+ struct sockaddr_storage addr;
+ struct sockaddr_storage via;
+ uint32_t time_ms;
+} knot_unreachable_t;
+
+typedef struct {
+ pthread_mutex_t mutex;
+ uint32_t ttl_ms;
+ list_t urs;
+} knot_unreachables_t;
+
+extern knot_unreachables_t *global_unreachables;
+
+/*!
+ * \brief Allocate Unreachables structure.
+ *
+ * \param ttl TTL for unreachable in milliseconds.
+ *
+ * \return Allocated structure, or NULL.
+ */
+knot_unreachables_t *knot_unreachables_init(uint32_t ttl_ms);
+
+/*!
+ * \brief Free Unreachables structure.
+ */
+void knot_unreachables_deinit(knot_unreachables_t **urs);
+
+/*!
+ * \brief Get and/or set the TTL.
+ *
+ * \param urs Unreachables structure.
+ * \param new_ttl_ms New TTL value in milliseconds.
+ *
+ * \return Previous value of TTL.
+ */
+uint32_t knot_unreachables_ttl(knot_unreachables_t *urs, uint32_t new_ttl_ms);
+
+/*!
+ * \brief Determine if given address is unreachable.
+ *
+ * \param urs Unreachables structure.
+ * \param addr Address and port in question.
+ * \param via Local outgoing address.
+ *
+ * \return True iff unreachable within TTL.
+ */
+bool knot_unreachable_is(knot_unreachables_t *urs,
+ const struct sockaddr_storage *addr,
+ const struct sockaddr_storage *via);
+
+/*!
+ * \brief Add an unreachable into Unreachables structure.
+ *
+ * \param urs Unreachables structure.
+ * \param addr Address and port being unreachable.
+ * \param via Local outgoing address.
+ */
+void knot_unreachable_add(knot_unreachables_t *urs,
+ const struct sockaddr_storage *addr,
+ const struct sockaddr_storage *via);