src/knot/common/evsched.h
src/knot/common/fdset.c
src/knot/common/fdset.h
+src/knot/common/hiredis.c
+src/knot/common/hiredis.h
src/knot/common/log.c
src/knot/common/log.h
src/knot/common/process.c
libknotd_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(libkqueue_CFLAGS) \
$(liburcu_CFLAGS) $(lmdb_CFLAGS) $(systemd_CFLAGS) \
- $(libdbus_CFLAGS) $(gnutls_CFLAGS) -DKNOTD_MOD_STATIC
+ $(libdbus_CFLAGS) $(gnutls_CFLAGS) $(libhiredis_CFLAGS) \
+ -DKNOTD_MOD_STATIC
libknotd_la_LDFLAGS = $(AM_LDFLAGS) -export-symbols-regex '^knotd_'
libknotd_la_LIBADD = $(dlopen_LIBS) $(libkqueue_LIBS) $(pthread_LIBS)
libknotd_LIBS = libknotd.la libknot.la libdnssec.la libzscanner.la \
$(libcontrib_LIBS) $(liburcu_LIBS) $(lmdb_LIBS) \
- $(systemd_LIBS) $(libdbus_LIBS) $(gnutls_LIBS)
+ $(systemd_LIBS) $(libdbus_LIBS) $(gnutls_LIBS) \
+ $(hiredis_LIBS)
if EMBEDDED_LIBNGTCP2
libknotd_la_LIBADD += $(libembngtcp2_LIBS)
knot/server/quic-handler.h
endif ENABLE_QUIC
+if ENABLE_REDIS
+libknotd_la_SOURCES += \
+ knot/common/hiredis.c \
+ knot/common/hiredis.h
+endif ENABLE_REDIS
+
if HAVE_DAEMON
noinst_LTLIBRARIES += libknotd.la
pkgconfig_DATA += knotd.pc
--- /dev/null
+/* Copyright (C) CZ.NIC, z.s.p.o. and contributors
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * For more information, see <https://www.knot-dns.cz/>
+ */
+
+#include "knot/common/hiredis.h"
+
+#include "contrib/sockaddr.h"
+#include "knot/common/log.h"
+#include "libknot/errcode.h"
+
+#ifdef ENABLE_REDIS_TLS
+#include <hiredis/alloc.h>
+#include <hiredis/sds.h>
+
+#include "libknot/quic/tls.h"
+#include "libknot/quic/tls_common.h"
+
+typedef struct {
+ struct knot_creds *local_creds;
+ struct knot_tls_ctx *tls;
+ struct knot_tls_conn *conn;
+} redis_tls_ctx_t;
+
+static void knot_redis_tls_close(redisContext *ctx);
+static void knot_redis_tls_free(void *privctx);
+static ssize_t knot_redis_tls_read(struct redisContext *ctx, char *buff, size_t size);
+static ssize_t knot_redis_tls_write(struct redisContext *ctx);
+
+redisContextFuncs redisContextGnuTLSFuncs = {
+ .close = knot_redis_tls_close,
+ .free_privctx = knot_redis_tls_free,
+ .read = knot_redis_tls_read,
+ .write = knot_redis_tls_write
+};
+
+static void ctx_deinit(redis_tls_ctx_t *ctx)
+{
+ if (ctx != NULL) {
+ if (ctx->tls != NULL) {
+ knot_creds_free(ctx->tls->creds);
+ knot_tls_ctx_free(ctx->tls);
+ }
+ knot_creds_free(ctx->local_creds);
+ hi_free(ctx);
+ }
+}
+
+static void knot_redis_tls_close(redisContext *ctx)
+{
+ redis_tls_ctx_t *tls_ctx = ctx->privctx;
+ if (ctx && ctx->fd != REDIS_INVALID_FD) {
+ knot_tls_conn_del(tls_ctx->conn);
+ close(ctx->fd);
+ ctx->fd = REDIS_INVALID_FD;
+ }
+}
+
+static void knot_redis_tls_free(void *privctx)
+{
+ ctx_deinit((redis_tls_ctx_t *)privctx);
+}
+
+static ssize_t knot_redis_tls_read(struct redisContext *ctx, char *buff, size_t size)
+{
+ redis_tls_ctx_t *tls_ctx = ctx->privctx;
+
+ int ret = knot_tls_recv(tls_ctx->conn, buff, size);
+ if (ret >= 0) {
+ return ret;
+ } else if (ret == KNOT_EBADCERT ||
+ ret == KNOT_NET_ERECV ||
+ ret == KNOT_NET_ECONNECT ||
+ ret == KNOT_NET_EHSHAKE ||
+ ret == KNOT_ETIMEOUT
+ ) {
+ return -1;
+ }
+ return 0;
+}
+
+static ssize_t knot_redis_tls_write(struct redisContext *ctx)
+{
+ redis_tls_ctx_t *tls_ctx = ctx->privctx;
+
+ int ret = knot_tls_send(tls_ctx->conn, ctx->obuf, sdslen(ctx->obuf));
+ if (ret >= 0) {
+ return ret;
+ } else if (ret == KNOT_EBADCERT ||
+ ret == KNOT_NET_ESEND ||
+ ret == KNOT_NET_ECONNECT ||
+ ret == KNOT_NET_EHSHAKE ||
+ ret == KNOT_ETIMEOUT
+ ) {
+ return -1;
+ }
+ return 0;
+}
+
+static int hiredis_attach_gnutls(redisContext *ctx, struct knot_creds *local_creds,
+ struct knot_creds *creds)
+{
+ redis_tls_ctx_t *privctx = hi_calloc(1, sizeof(redis_tls_ctx_t));
+ if (ctx == NULL) {
+ return KNOT_ENOMEM;
+ }
+ privctx->local_creds = local_creds;
+
+ privctx->tls = knot_tls_ctx_new(creds, 5000, 2000, KNOT_TLS_CLIENT);
+ if (privctx->tls == NULL) {
+ ctx_deinit(privctx);
+ return KNOT_EINVAL;
+ }
+
+ privctx->conn = knot_tls_conn_new(privctx->tls, ctx->fd);
+ if (privctx->conn == NULL) {
+ ctx_deinit(privctx);
+ return KNOT_ECONN;
+ }
+
+ if (knot_tls_handshake(privctx->conn, true) != KNOT_EOK) {
+ return KNOT_ECONN;
+ }
+
+ ctx->funcs = &redisContextGnuTLSFuncs;
+ ctx->privctx = privctx;
+
+ return KNOT_EOK;
+}
+#endif // ENABLE_REDIS_TLS
+
+redisContext *rdb_connect(conf_t *conf)
+{
+ conf_val_t db_listen = conf_db_param(conf, C_ZONE_DB_LISTEN);
+ struct sockaddr_storage addr = conf_addr(&db_listen, NULL);
+
+ int port = sockaddr_port(&addr);
+ sockaddr_port_set(&addr, 0);
+
+ char addr_str[SOCKADDR_STRLEN];
+ if (sockaddr_tostr(addr_str, sizeof(addr_str), &addr) <= 0) {
+ return NULL;
+ }
+
+ const struct timeval timeout = { 10, 0 };
+
+ redisContext *rdb;
+ if (addr.ss_family == AF_UNIX) {
+ rdb = redisConnectUnixWithTimeout(addr_str, timeout);
+ } else {
+ rdb = redisConnectWithTimeout(addr_str, port, timeout);
+ }
+ if (rdb == NULL) {
+ log_error("rdb, failed to connect");
+ } else if (rdb->err) {
+ log_error("rdb, failed to connect (%s)", rdb->errstr);
+ return NULL;
+ }
+
+#ifdef ENABLE_REDIS_TLS
+ if (conf_get_bool(conf, C_DB, C_ZONE_DB_TLS)) {
+ struct knot_creds *local_creds = NULL;
+ char *cert_file = conf_tls(conf, C_CERT_FILE);
+ if (cert_file != NULL) {
+ char *key_file = conf_tls(conf, C_KEY_FILE);
+ conf_val_t cafiles_val = conf_get(conf, C_SERVER, C_CA_FILE);
+ size_t nfiles = conf_val_count(&cafiles_val);
+ const char *ca_files[nfiles + 1];
+ bool system_ca = false;
+
+ memset(ca_files, 0, sizeof(ca_files));
+ for (size_t i = 0; cafiles_val.code == KNOT_EOK; conf_val_next(&cafiles_val)) {
+ const char *file = conf_str(&cafiles_val);
+ if (*file == '\0') {
+ system_ca = true;
+ } else {
+ ca_files[i++] = file;
+ }
+ }
+
+ int ret = knot_creds_init(&local_creds, key_file, cert_file,
+ ca_files, system_ca, 0, 0);
+ free(key_file);
+ free(cert_file);
+ if (ret != KNOT_EOK) {
+ redisFree(rdb);
+ return NULL;
+ }
+ }
+
+ const char *hostnames[KNOT_TLS_MAX_PINS] = { 0 };
+ conf_val_t val = conf_db_param(conf, C_ZONE_DB_CERT_HOSTNAME);
+ for (size_t i = 0; val.code == KNOT_EOK; i++) {
+ hostnames[i] = conf_str(&val);
+ conf_val_next(&val);
+ }
+
+ const uint8_t *pins[KNOT_TLS_MAX_PINS] = { 0 };
+ val = conf_db_param(conf, C_ZONE_DB_CERT_KEY);
+ for (size_t i = 0; val.code == KNOT_EOK; i++) {
+ size_t len;
+ pins[i] = (uint8_t *)conf_bin(&val, &len);
+ conf_val_next(&val);
+ }
+
+ struct knot_creds *creds = knot_creds_init_peer(local_creds, hostnames, pins);
+ if (creds == NULL) {
+ knot_creds_free(local_creds);
+ redisFree(rdb);
+ return NULL;
+ }
+
+ int ret = hiredis_attach_gnutls(rdb, local_creds, creds);
+ if (ret != KNOT_EOK) {
+ knot_creds_free(local_creds);
+ knot_creds_free(creds);
+ redisFree(rdb);
+ return NULL;
+ }
+ }
+#endif // ENABLE_REDIS_TLS
+
+ return rdb;
+}
+
+void rdb_disconnect(redisContext* rdb)
+{
+ if (rdb != NULL) {
+ // TODO: is anything more needed for TLS case?
+ redisFree(rdb);
+ }
+}
+
+bool rdb_compatible(redisContext *rdb)
+{
+ if (rdb == NULL) {
+ return false;
+ }
+
+#ifdef ENDIANITY_LITTLE
+ #define ENDIAN 1
+#else
+ #define ENDIAN 0
+#endif
+
+ const char *lua = "local n=1; local s=string.dump(function() return n end); " \
+ "local e=string.byte(s,7); if e==0 then return 0 else return 1 end";
+
+ redisReply *reply = redisCommand(rdb, "EVAL %s 0", lua);
+ bool res = (reply != NULL &&
+ reply->type == REDIS_REPLY_INTEGER &&
+ reply->integer == ENDIAN);
+ freeReplyObject(reply);
+ return res;
+}
--- /dev/null
+/* Copyright (C) CZ.NIC, z.s.p.o. and contributors
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * For more information, see <https://www.knot-dns.cz/>
+ */
+
+/*!
+ * \brief Extension of Hiredis to support GnuTLS backend.
+ */
+
+#pragma once
+
+#include <hiredis/hiredis.h>
+
+#include "knot/conf/conf.h"
+
+redisContext *rdb_connect(conf_t *conf);
+
+void rdb_disconnect(redisContext* rdb);
+
+bool rdb_compatible(redisContext *rdb);
return KNOT_EOK;
#endif
}
+
+bool conf_zone_rdb_enabled(
+ conf_t *conf,
+ const knot_dname_t *zone,
+ bool input,
+ uint8_t *instance)
+{
+ if (instance == NULL) {
+ return false;
+ }
+
+ conf_val_t val = conf_zone_get(conf, input ? C_ZONE_DB_IN : C_ZONE_DB_OUT, zone);
+ int64_t num = conf_int(&val);
+ if (num > 0) {
+ *instance = num;
+ return true;
+ }
+ return false;
+}
struct sockaddr_storage *addr,
conf_xdp_iface_t *iface
);
+
+/*!
+ * Checks if zone database backend is enabled for the zone.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] zone Zone name.
+ * \param[in] input Input backend indication; Output backend otherwise.
+ * \param[out] instance Configured instance number.
+ *
+ * \retval true if enabled.
+ * \retval false if disabled.
+ */
+bool conf_zone_rdb_enabled(
+ conf_t *conf,
+ const knot_dname_t *zone,
+ bool input,
+ uint8_t *instance
+);
};
static const yp_item_t desc_database[] = {
- { C_STORAGE, YP_TSTR, YP_VSTR = { STORAGE_DIR } },
- { C_JOURNAL_DB, YP_TSTR, YP_VSTR = { "journal" } },
- { C_JOURNAL_DB_MODE, YP_TOPT, YP_VOPT = { journal_modes, JOURNAL_MODE_ROBUST } },
- { C_JOURNAL_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(TERA(100)),
- VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } },
- { C_KASP_DB, YP_TSTR, YP_VSTR = { "keys" } },
- { C_KASP_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)),
- MEGA(500), YP_SSIZE } },
- { C_TIMER_DB, YP_TSTR, YP_VSTR = { "timers" } },
- { C_TIMER_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(GIGA(100)),
- MEGA(100), YP_SSIZE } },
- { C_CATALOG_DB, YP_TSTR, YP_VSTR = { "catalog" } },
- { C_CATALOG_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)),
- VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } },
- { C_COMMENT, YP_TSTR, YP_VNONE },
+ { C_STORAGE, YP_TSTR, YP_VSTR = { STORAGE_DIR } },
+ { C_JOURNAL_DB, YP_TSTR, YP_VSTR = { "journal" } },
+ { C_JOURNAL_DB_MODE, YP_TOPT, YP_VOPT = { journal_modes, JOURNAL_MODE_ROBUST } },
+ { C_JOURNAL_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(TERA(100)),
+ VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } },
+ { C_KASP_DB, YP_TSTR, YP_VSTR = { "keys" } },
+ { C_KASP_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)),
+ MEGA(500), YP_SSIZE } },
+ { C_TIMER_DB, YP_TSTR, YP_VSTR = { "timers" } },
+ { C_TIMER_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(GIGA(100)),
+ MEGA(100), YP_SSIZE } },
+ { C_CATALOG_DB, YP_TSTR, YP_VSTR = { "catalog" } },
+ { C_CATALOG_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)),
+ VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } },
+ { C_ZONE_DB_LISTEN, YP_TADDR, YP_VADDR = { 6379 }, YP_FNONE, { check_rdb, check_listen } },
+ { C_ZONE_DB_TLS, YP_TBOOL, YP_VNONE },
+ { C_ZONE_DB_CERT_KEY, YP_TB64, YP_VNONE, YP_FMULTI, { check_cert_pin } },
+ { C_ZONE_DB_CERT_HOSTNAME, YP_TSTR, YP_VNONE, YP_FMULTI },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
{ NULL }
};
#define ZONE_ITEMS(FLAGS) \
{ C_STORAGE, YP_TSTR, YP_VSTR = { STORAGE_DIR }, FLAGS }, \
{ C_FILE, YP_TSTR, YP_VNONE, FLAGS }, \
+ { C_ZONE_DB_IN, YP_TINT, YP_VINT = { -1, 8, -1 }, FLAGS, { check_db_instance } }, \
+ { C_ZONE_DB_OUT, YP_TINT, YP_VINT = { -1, 8, -1 }, FLAGS, { check_db_instance } }, \
{ C_MASTER, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI | CONF_REF_EMPTY, \
{ check_ref } }, \
{ C_DDNS_MASTER, YP_TREF, YP_VREF = { C_RMT }, YP_FNONE, { check_ref_empty } }, \
#define C_ZONEFILE_SYNC "\x0D""zonefile-sync"
#define C_ZONEMD_GENERATE "\x0F""zonemd-generate"
#define C_ZONEMD_VERIFY "\x0D""zonemd-verify"
+#define C_ZONE_DB_CERT_HOSTNAME "\x15""zone-db-cert-hostname"
+#define C_ZONE_DB_CERT_KEY "\x10""zone-db-cert-key"
+#define C_ZONE_DB_IN "\x0D""zone-db-input"
+#define C_ZONE_DB_LISTEN "\x0E""zone-db-listen"
+#define C_ZONE_DB_OUT "\x0E""zone-db-output"
+#define C_ZONE_DB_TLS "\x0B""zone-db-tls"
#define C_ZONE_MAX_SIZE "\x0D""zone-max-size"
#define C_ZONE_MAX_TTL "\x0C""zone-max-ttl"
#define C_ZSK_LIFETIME "\x0C""zsk-lifetime"
return check_zone_or_tpl(args);
}
+int check_rdb(
+ knotd_conf_check_args_t *args)
+{
+#ifndef ENABLE_REDIS
+ args->err_str = "Zone database support not available";
+ return KNOT_ENOTSUP;
+#else
+ return KNOT_EOK;
+#endif
+}
+
+int check_db_instance(
+ knotd_conf_check_args_t *args)
+{
+ int64_t instance = yp_int(args->data);
+ if (instance == 0) {
+ args->err_str = "instance number must be 1-8";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
static int glob_error(
const char *epath,
int eerrno)
knotd_conf_check_args_t *args
);
+int check_rdb(
+ knotd_conf_check_args_t *args
+);
+
+int check_db_instance(
+ knotd_conf_check_args_t *args
+);
+
int include_file(
knotd_conf_check_args_t *args
);
#include <assert.h>
#include <gnutls/x509.h>
+#include <string.h>
#include <sys/types.h> // OpenBSD
#include <netinet/tcp.h> // TCP_FASTOPEN
#include <sys/resource.h>
return KNOT_EOK;
}
+#ifdef ENABLE_REDIS
+#include "contrib/openbsd/strlcpy.h"
+#include "contrib/strtonum.h"
+#include "redis/knot.h"
+#include "knot/common/hiredis.h"
+
+#define RDB_TIMESTAMP_SIZE 42 // 2x uint64_t as string (2x20) + dash separator + zero byte.
+
+static void rdb_process_event(redisReply *reply, knot_zonedb_t *zone_db,
+ char since[static RDB_TIMESTAMP_SIZE])
+{
+ redisReply *timestamp = reply->element[0];
+ redisReply *data = reply->element[1];
+ if (data->type != REDIS_REPLY_ARRAY ||
+ data->elements % 2 != 0 ||
+ timestamp->len > RDB_TIMESTAMP_SIZE) {
+ goto failed;
+ }
+ strlcpy(since, timestamp->str, RDB_TIMESTAMP_SIZE);
+
+ uint8_t type = 0;
+ uint8_t instance = 0;
+ uint32_t serial = 0;
+ const knot_dname_t *origin = NULL;
+
+ for (int idx = 0; idx < data->elements; ++idx) {
+ redisReply *key = data->element[idx++];
+ redisReply *val = data->element[idx];
+ if (key->type != REDIS_REPLY_STRING) {
+ goto failed;
+ }
+ if (strcmp(key->str, RDB_EVENT_ARG_EVENT) == 0) {
+ if (str_to_u8(val->str, &type) != KNOT_EOK) {
+ goto failed;
+ }
+ } else if (strcmp(key->str, RDB_EVENT_ARG_ORIGIN) == 0) {
+ origin = (const knot_dname_t *)val->str;
+ } else if (strcmp(key->str, RDB_EVENT_ARG_INSTANCE) == 0) {
+ if (str_to_u8(val->str, &instance) != KNOT_EOK) {
+ goto failed;
+ }
+ } else if (strcmp(key->str, RDB_EVENT_ARG_SERIAL) == 0) {
+ if (str_to_u32(val->str, &serial) != KNOT_EOK) {
+ goto failed;
+ }
+ }
+ }
+
+ if (type == 0 || origin == NULL || instance == 0) {
+ goto failed;
+ }
+
+ uint8_t db_instance = 0;
+ bool db_enabled = conf_zone_rdb_enabled(conf(), origin, true, &db_instance);
+ if (!db_enabled || db_instance != instance) {
+ return;
+ }
+ zone_t *zone = knot_zonedb_find(zone_db, origin);
+ if (zone == NULL) {
+ return;
+ }
+
+ switch (type) {
+ case RDB_EVENT_ZONE:
+ case RDB_EVENT_UPD:
+ zone_events_schedule_now(zone, ZONE_EVENT_LOAD);
+ break;
+ default:
+ break;
+ }
+
+ return;
+failed:
+ log_error("rdb, invalid event parameters");
+}
+
+static void rdb_process_events(redisReply *reply, knot_zonedb_t *zone_db,
+ char since[static RDB_TIMESTAMP_SIZE])
+{
+ if (reply->type != REDIS_REPLY_ARRAY) {
+ log_error("rdb, unexpected response");
+ return;
+ }
+
+ for (int idx = 0; idx < reply->elements; ++idx) {
+ if (reply->element[idx]->type != REDIS_REPLY_ARRAY) {
+ log_error("rdb, unexpected response");
+ continue;
+ }
+ redisReply *events = reply->element[idx]->element[1];
+ for (int event_idx = 0; event_idx < events->elements; ++event_idx) {
+ rdb_process_event(events->element[event_idx], zone_db, since);
+ }
+ }
+}
+
+static int rdb_listener_run(struct dthread *thread)
+{
+ server_t *s = thread->data;
+
+ s->rdb_ctx = NULL;
+ char since[RDB_TIMESTAMP_SIZE] = "$";
+
+ while (thread->state & ThreadActive) {
+ if (s->rdb_ctx == NULL) {
+ s->rdb_ctx = rdb_connect(conf());
+ if (s->rdb_ctx == NULL) {
+ log_error("rdb, failed to connect");
+ sleep(2);
+ continue;
+ } else if (!rdb_compatible(s->rdb_ctx)) {
+ log_error("rdb, incompatible CPU endianness");
+ break;
+ }
+ }
+
+ redisReply *reply = redisCommand(s->rdb_ctx, "XREAD BLOCK %d STREAMS %b %s",
+ 10000, RDB_EVENT_KEY, strlen(RDB_EVENT_KEY), since);
+ if (reply == NULL) {
+ if (thread->state & ThreadDead) {
+ break;
+ }
+ if (s->rdb_ctx->err != REDIS_OK) {
+ log_error("rdb, failed to read events (%s)", s->rdb_ctx->errstr);
+ }
+ sleep(2);
+ continue;
+ }
+ if (reply->type == REDIS_REPLY_NIL) {
+ freeReplyObject(reply);
+ continue;
+ }
+
+ knot_zonedb_t *zone_db = s->zone_db;
+ if (zone_db != NULL) {
+ rdb_process_events(reply, zone_db, since);
+ }
+ freeReplyObject(reply);
+ }
+
+ rdb_disconnect(s->rdb_ctx);
+ s->rdb_ctx = NULL;
+
+ return KNOT_EOK;
+}
+#endif // ENABLE_REDIS
+
int server_init(server_t *server, int bg_workers)
{
if (server == NULL) {
return KNOT_EINVAL;
}
- /* Clear the structure. */
memset(server, 0, sizeof(server_t));
- /* Initialize event scheduler. */
if (evsched_init(&server->sched, server) != KNOT_EOK) {
return KNOT_ENOMEM;
}
return KNOT_ENOMEM;
}
+#ifdef ENABLE_REDIS
+ conf_val_t rdb = conf_db_param(conf(), C_ZONE_DB_LISTEN);
+ if (rdb.code == KNOT_EOK) {
+ server->rdb_events = dt_create(1, rdb_listener_run, NULL, server);
+ if (server->rdb_events == NULL) {
+ worker_pool_destroy(server->workers);
+ evsched_deinit(&server->sched);
+ return KNOT_ENOMEM;
+ }
+ }
+#endif // ENABLE_REDIS
+
int ret = catalog_update_init(&server->catalog_upd);
if (ret != KNOT_EOK) {
+ dt_stop(server->rdb_events);
+ dt_delete(&server->rdb_events);
worker_pool_destroy(server->workers);
evsched_deinit(&server->sched);
return ret;
/* Free threads and event handlers. */
worker_pool_destroy(server->workers);
+ /* Free optional zone DB event thread. */
+ dt_delete(&server->rdb_events);
+
/* Free zone database. */
knot_zonedb_deep_free(&server->zone_db, true);
/* Start workers. */
worker_pool_start(server->workers);
+ /* Start zone DB event loop. */
+ dt_start(server->rdb_events);
+
/* Start evsched handler. */
evsched_start(&server->sched);
log_info("stopping server");
systemd_stopping_notify();
+#ifdef ENABLE_REDIS
+ /* Interrupt and stop XREAD BLOCK loop. */
+ if (server->rdb_ctx != NULL) {
+ redisSetTimeout(server->rdb_ctx, (struct timeval){ 0, 1 });
+ }
+ dt_stop(server->rdb_events);
+#endif // ENABLE_REDIS
+
/* Stop scheduler. */
evsched_stop(&server->sched);
/* Mark the server is shutting down. */
/*! \brief Background jobs. */
worker_pool_t *workers;
+ /*! \brief Zone DB event loop context. */
+ dt_unit_t *rdb_events;
+ struct redisContext *rdb_ctx;
+
/*! \brief Event scheduler. */
evsched_t sched;
return ret;
}
+ zloader_t loader;
+ sem_handler_t handler = {
+ .cb = err_handler_logger
+ };
+
char *zonefile = conf_zonefile(conf, zone_name);
conf_val_t val = conf_zone_get(conf, C_DEFAULT_TTL, zone_name);
uint32_t dflt_ttl = conf_int(&val);
- zloader_t zl;
- ret = zonefile_open(&zl, zonefile, zone_name, dflt_ttl,
- semcheck_mode, time(NULL));
+ ret = zonefile_open(&loader, zonefile, zone_name, dflt_ttl,
+ semcheck_mode, &handler, time(NULL), &skip);
free(zonefile);
if (ret != KNOT_EOK) {
zone_skip_free(&skip);
return ret;
}
- sem_handler_t handler = {
- .cb = err_handler_logger
- };
-
- zl.err_handler = &handler;
- zl.creator->skip = &skip;
-
- *contents = zonefile_load(&zl, 0);
- zonefile_close(&zl);
+ *contents = zonefile_load(&loader, 0);
+ zonefile_close(&loader);
zone_skip_free(&skip);
if (*contents == NULL) {
return KNOT_ERROR;
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <libgen.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "libknot/libknot.h"
#include "contrib/files.h"
+#include "contrib/time.h"
#include "knot/common/log.h"
-#include "knot/dnssec/zone-nsec.h"
#include "knot/zone/semantic-check.h"
#include "knot/zone/adjust.h"
#include "knot/zone/contents.h"
static void process_error(zs_scanner_t *s)
{
- zcreator_t *zc = s->process.data;
- const knot_dname_t *zname = zc->z->apex->owner;
+ zloader_t *loader = s->process.data;
+ const knot_dname_t *zname = loader->contents->apex->owner;
ERROR(zname, "%s in zone, file '%s', line %"PRIu64" (%s)",
s->error.fatal ? "fatal error" : "error",
return KNOT_EOK;
}
-/*! \brief Creates RR from parser input, passes it to handling function. */
static void process_data(zs_scanner_t *scanner)
{
- zcreator_t *zc = scanner->process.data;
- if (zc->ret != KNOT_EOK) {
+ zloader_t *zl = scanner->process.data;
+
+ if (zl->ret != KNOT_EOK) {
scanner->state = ZS_STATE_STOP;
return;
}
knot_dname_t *owner = knot_dname_copy(scanner->r_owner, NULL);
if (owner == NULL) {
- zc->ret = KNOT_ENOMEM;
+ zl->ret = KNOT_ENOMEM;
return;
}
int ret = knot_rrset_add_rdata(&rr, scanner->r_data, scanner->r_data_length, NULL);
if (ret != KNOT_EOK) {
knot_rrset_clear(&rr, NULL);
- zc->ret = ret;
+ zl->ret = ret;
return;
}
- /* Convert RDATA dnames to lowercase before adding to zone. */
ret = knot_rrset_rr_to_canonical(&rr);
if (ret != KNOT_EOK) {
knot_rrset_clear(&rr, NULL);
- zc->ret = ret;
+ zl->ret = ret;
return;
}
- zc->ret = zcreator_step(zc->z, &rr, zc->skip);
+ zl->ret = zcreator_step(zl->contents, &rr, zl->skip);
+
knot_rrset_clear(&rr, NULL);
}
}
int zonefile_open(zloader_t *loader, const char *source, const knot_dname_t *origin,
- uint32_t dflt_ttl, semcheck_optional_t semantic_checks, time_t time)
+ uint32_t dflt_ttl, semcheck_optional_t sem_checks,
+ sem_handler_t *sem_err_handler, time_t time, zone_skip_t *skip)
{
if (loader == NULL || source == NULL) {
return KNOT_EINVAL;
}
- memset(loader, 0, sizeof(zloader_t));
-
if (access(source, F_OK | R_OK) != 0) {
return knot_map_errno();
}
- zcreator_t *zc = malloc(sizeof(zcreator_t));
- if (zc == NULL) {
- return KNOT_ENOMEM;
- }
- memset(zc, 0, sizeof(zcreator_t));
-
uint8_t origin_buf[1 + KNOT_DNAME_MAXLEN];
if (origin == NULL) { // Origin autodetection based on SOA owner and source.
const char *ext = ".zone";
zs_set_processing(&s, check_origin, error_origin, &origin_buf) != 0) {
free(origin_str);
zs_deinit(&s);
- free(zc);
return KNOT_EFILE;
}
free(origin_str);
if (zs_parse_all(&s) != 0 && s.error.fatal) {
zs_deinit(&s);
- free(zc);
return KNOT_EPARSEFAIL;
}
zs_deinit(&s);
if (origin_buf[0] == 0) {
- free(zc);
return KNOT_ESOAINVAL;
}
origin = origin_buf + 1;
knot_dname_txt_storage_t origin_str;
if (knot_dname_to_str(origin_str, origin, sizeof(origin_str)) == NULL) {
- free(zc);
return KNOT_EINVAL;
}
if (zs_init(&loader->scanner, origin_str, KNOT_CLASS_IN, dflt_ttl) != 0 ||
zs_set_input_file(&loader->scanner, source) != 0 ||
- zs_set_processing(&loader->scanner, process_data, process_error, zc) != 0) {
+ zs_set_processing(&loader->scanner, process_data, process_error, loader) != 0) {
zs_deinit(&loader->scanner);
- free(zc);
return KNOT_EFILE;
}
- zc->z = zone_contents_new(origin, true);
- if (zc->z == NULL) {
+ loader->contents = zone_contents_new(origin, true);
+ if (loader->contents == NULL) {
zs_deinit(&loader->scanner);
- free(zc);
return KNOT_ENOMEM;
}
loader->source = strdup(source);
- loader->creator = zc;
- loader->semantic_checks = semantic_checks;
+ loader->sem_checks = sem_checks;
+ loader->err_handler = sem_err_handler;
+ loader->skip = skip;
loader->time = time;
+ loader->ret = KNOT_EOK;
return KNOT_EOK;
}
zone_contents_t *zonefile_load(zloader_t *loader, uint16_t threads)
{
- if (!loader) {
+ if (loader == NULL) {
return NULL;
}
- zcreator_t *zc = loader->creator;
- const knot_dname_t *zname = zc->z->apex->owner;
+ const knot_dname_t *zname = loader->contents->apex->owner;
- assert(zc);
int ret = zs_parse_all(&loader->scanner);
if (ret != 0 && loader->scanner.error.counter == 0) {
- ERROR(zname, "failed to load zone, file '%s' (%s)",
+ ERROR(zname, "failed to load, file '%s' (%s)",
loader->source, zs_strerror(loader->scanner.error.code));
goto fail;
- }
-
- if (zc->ret != KNOT_EOK) {
- ERROR(zname, "failed to load zone, file '%s' (%s)",
- loader->source, knot_strerror(zc->ret));
+ } else if (loader->ret != KNOT_EOK) {
+ ERROR(zname, "failed to load, file '%s' (%s)",
+ loader->source, knot_strerror(loader->ret));
goto fail;
- }
-
- if (loader->scanner.error.counter > 0) {
- ERROR(zname, "failed to load zone, file '%s', %"PRIu64" errors",
+ } else if (loader->scanner.error.counter > 0) {
+ ERROR(zname, "failed to load, file '%s', %"PRIu64" errors",
loader->source, loader->scanner.error.counter);
goto fail;
}
- knot_rdataset_t *soa = node_rdataset(zc->z->apex, KNOT_RRTYPE_SOA);
+ knot_rdataset_t *soa = node_rdataset(loader->contents->apex, KNOT_RRTYPE_SOA);
if (soa == NULL || soa->count != 1) {
sem_error_t code = (soa == NULL) ? SEM_ERR_SOA_NONE : SEM_ERR_SOA_MULTIPLE;
loader->err_handler->error = true;
- loader->err_handler->cb(loader->err_handler, zc->z, NULL, code, NULL);
+ loader->err_handler->cb(loader->err_handler, loader->contents, NULL, code, NULL);
goto fail;
}
- ret = zone_adjust_contents(zc->z, adjust_cb_flags_and_nsec3, adjust_cb_nsec3_flags,
- true, true, 1, NULL);
+ ret = zone_adjust_contents(loader->contents, adjust_cb_flags_and_nsec3,
+ adjust_cb_nsec3_flags, true, true, 1, NULL);
if (ret != KNOT_EOK) {
- ERROR(zname, "failed to finalize zone contents (%s)",
- knot_strerror(ret));
+ ERROR(zname, "failed to finalize zone contents (%s)", knot_strerror(ret));
goto fail;
}
- ret = sem_checks_process(zc->z, loader->semantic_checks,
+ ret = sem_checks_process(loader->contents, loader->sem_checks,
loader->err_handler, loader->time, threads);
if (ret != KNOT_EOK) {
- ERROR(zname, "failed to load zone, file '%s' (%s)",
- loader->source, knot_strerror(ret));
+ ERROR(zname, "failed to check zone (%s)", knot_strerror(ret));
goto fail;
}
/* The contents will now change possibly messing up NSEC3 tree, it will
be adjusted again at zone_update_commit. */
- ret = zone_adjust_contents(zc->z, unadjust_cb_point_to_nsec3, NULL,
- false, false, 1, NULL);
+ ret = zone_adjust_contents(loader->contents, unadjust_cb_point_to_nsec3,
+ NULL, false, false, 1, NULL);
if (ret != KNOT_EOK) {
- ERROR(zname, "failed to finalize zone contents (%s)",
- knot_strerror(ret));
+ ERROR(zname, "failed to finalize zone contents (%s)", knot_strerror(ret));
goto fail;
}
- return zc->z;
-
+ return loader->contents;
fail:
- zone_contents_deep_free(zc->z);
+ zone_contents_deep_free(loader->contents);
+
return NULL;
}
void zonefile_close(zloader_t *loader)
{
- if (!loader) {
+ if (loader == NULL) {
return;
}
zs_deinit(&loader->scanner);
free(loader->source);
- free(loader->creator);
}
void err_handler_logger(sem_handler_t *handler, const zone_contents_t *zone,
#pragma once
-#include <stdbool.h>
-#include <stdio.h>
-
#include "knot/zone/skip.h"
#include "knot/zone/zone.h"
#include "knot/zone/semantic-check.h"
#include "libzscanner/scanner.h"
-/*!
- * \brief Zone creator structure.
- */
-typedef struct zcreator {
- zone_contents_t *z; /*!< Created zone. */
- zone_skip_t *skip; /*!< Skip configured types. */
- int ret; /*!< Return value. */
-} zcreator_t;
-
-/*!
- * \brief Zone loader structure.
- */
typedef struct {
- char *source; /*!< Zone source file. */
- semcheck_optional_t semantic_checks; /*!< Do semantic checks. */
- sem_handler_t *err_handler; /*!< Semantic checks error handler. */
- zcreator_t *creator; /*!< Loader context. */
- zs_scanner_t scanner; /*!< Zone scanner. */
- time_t time; /*!< time for zone check. */
+ char *source; /*!< Zone source file. */
+ zs_scanner_t scanner; /*!< Zone scanner. */
+ zone_contents_t *contents; /*!< Created zone. */
+ semcheck_optional_t sem_checks; /*!< Do semantic checks. */
+ sem_handler_t *err_handler; /*!< Semantic checks error handler. */
+ zone_skip_t *skip; /*!< Skip configured types. */
+ time_t time; /*!< Time for zone check. */
+ int ret; /*!< Callback return value. */
} zloader_t;
void err_handler_logger(sem_handler_t *handler, const zone_contents_t *zone,
* \param source Source file name.
* \param origin Zone origin.
* \param dflt_ttl Default TTL.
- * \param semantic_checks Perform semantic checks.
+ * \param sem_checks Perform semantic checks.
+ * \param sem_err_handler Semantic checks error handler.
* \param time Time for semantic check.
+ * \param skip RRTypes to be skipped.
*
* \retval Initialized loader on success.
* \retval NULL on error.
*/
int zonefile_open(zloader_t *loader, const char *source, const knot_dname_t *origin,
- uint32_t dflt_ttl, semcheck_optional_t semantic_checks, time_t time);
+ uint32_t dflt_ttl, semcheck_optional_t sem_checks,
+ sem_handler_t *sem_err_handler, time_t time, zone_skip_t *skip);
/*!
* \brief Loads zone from a zone file.
if (ret != KNOT_EOK && ret != KNOT_ESEMCHECK) {
knot_dname_txt_storage_t name;
(void)knot_dname_to_str(name, dname, sizeof(name));
- log_error("[%s] failed to check zone file (%s)", name, knot_strerror(ret));
+ log_error("[%s] failed to check zone (%s)", name, knot_strerror(ret));
}
return ret;
.handler = { .cb = err_callback },
};
- zloader_t zl;
- int ret = zonefile_open(&zl, zone_file, zone_name, dflt_ttl, optional, time);
+ zloader_t loader;
+ int ret = zonefile_open(&loader, zone_file, zone_name, dflt_ttl, optional,
+ (sem_handler_t *)&stats, time, NULL);
switch (ret) {
case KNOT_EOK:
break;
ERR2("failed to run semantic checks (%s)", knot_strerror(ret));
return ret;
}
- zl.err_handler = (sem_handler_t *)&stats;
if (zone_name == NULL) {
knot_dname_txt_storage_t origin;
- if (knot_dname_to_str(origin, zl.scanner.zone_origin, sizeof(origin)) != NULL) {
+ if (knot_dname_to_str(origin, loader.scanner.zone_origin, sizeof(origin)) != NULL) {
log_debug("detected zone origin %s", origin);
}
}
- zone_contents_t *contents = zonefile_load(&zl, threads);
- zonefile_close(&zl);
+ zone_contents_t *contents = zonefile_load(&loader, threads);
+ zonefile_close(&loader);
if (contents == NULL && !stats.handler.error) {
ERR2("failed to run semantic checks");
return KNOT_ERROR;
$(liburcu_LIBS) \
$(lmdb_LIBS) \
$(systemd_LIBS) \
- $(libdbus_LIBS)
+ $(libdbus_LIBS) \
+ $(hiredis_LIBS)
BUILT_SOURCES = knotd_wrap/main.c
CLEANFILES = knotd_wrap/main.c
$(top_builddir)/src/libknotd.la \
$(liburcu_LIBS) \
$(systemd_LIBS) \
- $(libdbus_LIBS)
+ $(libdbus_LIBS) \
+ $(hiredis_LIBS)
endif HAVE_DAEMON
LDADD += \
* For more information, see <https://www.knot-dns.cz/>
*/
-#include "knot/zone/digest.h"
-
+#include <stdio.h>
#include <string.h>
#include <tap/basic.h>
-#include "knot/zone/zonefile.h"
+#include "knot/zone/digest.h"
#include "libzscanner/scanner.h"
-// copy-pasted from knot/zone/zonefile.c
static void process_data(zs_scanner_t *scanner)
{
- zcreator_t *zc = scanner->process.data;
- if (zc->ret != KNOT_EOK) {
- scanner->state = ZS_STATE_STOP;
- return;
- }
+ zone_contents_t *cont = scanner->process.data;
knot_dname_t *owner = knot_dname_copy(scanner->r_owner, NULL);
- if (owner == NULL) {
- zc->ret = KNOT_ENOMEM;
- return;
- }
+ assert(owner != NULL);
knot_rrset_t rr;
knot_rrset_init(&rr, owner, scanner->r_type, scanner->r_class, scanner->r_ttl);
- int ret = knot_rrset_add_rdata(&rr, scanner->r_data, scanner->r_data_length, NULL);
- if (ret != KNOT_EOK) {
- knot_rrset_clear(&rr, NULL);
- zc->ret = ret;
- return;
+ if (knot_rrset_add_rdata(&rr, scanner->r_data, scanner->r_data_length, NULL) == KNOT_EOK &&
+ knot_rrset_rr_to_canonical(&rr) == KNOT_EOK) {
+ zone_node_t *node = NULL;
+ int ret = zone_contents_add_rr(cont, &rr, &node);
+ if (ret != KNOT_EOK && ret != KNOT_EOUTOFZONE) {
+ scanner->error.code = ZS_STATE_ERROR;
+ }
}
- ret = knot_rrset_rr_to_canonical(&rr);
- if (ret != KNOT_EOK) {
- knot_rrset_clear(&rr, NULL);
- zc->ret = ret;
- return;
- }
-
- zc->ret = zcreator_step(zc->z, &rr, zc->skip);
knot_rrset_clear(&rr, NULL);
}
-static void process_error(zs_scanner_t *s)
-{
- (void)s;
- assert(0);
-}
-
static zone_contents_t *str2contents(const char *zone_str)
{
knot_dname_txt_storage_t origin_str;
assert(cont != NULL);
knot_dname_free(origin, NULL);
- zcreator_t zc = { cont, NULL, KNOT_EOK };
-
zs_scanner_t sc;
ok(zs_init(&sc, origin_str, KNOT_CLASS_IN, 3600) == 0 &&
zs_set_input_string(&sc, zone_str, strlen(zone_str)) == 0 &&
- zs_set_processing(&sc, process_data, process_error, &zc) == 0 &&
- zs_parse_all(&sc) == 0, "zscanner initialization");
+ zs_set_processing(&sc, process_data, NULL, cont) == 0 &&
+ zs_parse_all(&sc) == 0 &&
+ sc.error.code == ZS_OK, "zscanner processing");
zs_deinit(&sc);
return cont;