From: Daniel Salzman Date: Tue, 28 Jan 2025 13:34:34 +0000 (+0100) Subject: knot: prepare for integration of the Redis backend X-Git-Tag: v3.5.0~11^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=17d7b25302d5353cba43354b7ed7814a4ea37fb3;p=thirdparty%2Fknot-dns.git knot: prepare for integration of the Redis backend --- diff --git a/Knot.files b/Knot.files index 247ef7dc8d..ced2c8aabf 100644 --- a/Knot.files +++ b/Knot.files @@ -186,6 +186,8 @@ src/knot/common/evsched.c 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 diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc index 0286d08e01..b5ecae7123 100644 --- a/src/knot/Makefile.inc +++ b/src/knot/Makefile.inc @@ -1,11 +1,13 @@ 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) @@ -238,6 +240,12 @@ libknotd_la_SOURCES += \ 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 diff --git a/src/knot/common/hiredis.c b/src/knot/common/hiredis.c new file mode 100644 index 0000000000..ca72df5ec6 --- /dev/null +++ b/src/knot/common/hiredis.c @@ -0,0 +1,255 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. and contributors + * SPDX-License-Identifier: GPL-2.0-or-later + * For more information, see + */ + +#include "knot/common/hiredis.h" + +#include "contrib/sockaddr.h" +#include "knot/common/log.h" +#include "libknot/errcode.h" + +#ifdef ENABLE_REDIS_TLS +#include +#include + +#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; +} diff --git a/src/knot/common/hiredis.h b/src/knot/common/hiredis.h new file mode 100644 index 0000000000..9064ed7dfc --- /dev/null +++ b/src/knot/common/hiredis.h @@ -0,0 +1,20 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. and contributors + * SPDX-License-Identifier: GPL-2.0-or-later + * For more information, see + */ + +/*! + * \brief Extension of Hiredis to support GnuTLS backend. + */ + +#pragma once + +#include + +#include "knot/conf/conf.h" + +redisContext *rdb_connect(conf_t *conf); + +void rdb_disconnect(redisContext* rdb); + +bool rdb_compatible(redisContext *rdb); diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c index dad7897cd1..97355d6fdf 100644 --- a/src/knot/conf/conf.c +++ b/src/knot/conf/conf.c @@ -1507,3 +1507,22 @@ int conf_xdp_iface( 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; +} diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h index 1de23c6f79..b496bb1fa3 100644 --- a/src/knot/conf/conf.h +++ b/src/knot/conf/conf.h @@ -991,3 +991,21 @@ int conf_xdp_iface( 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 +); diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c index bb3cf2e499..bc8b3ae7c1 100644 --- a/src/knot/conf/schema.c +++ b/src/knot/conf/schema.c @@ -299,21 +299,25 @@ static const yp_item_t desc_stats[] = { }; 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 } }; @@ -467,6 +471,8 @@ static const yp_item_t desc_external[] = { #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 } }, \ diff --git a/src/knot/conf/schema.h b/src/knot/conf/schema.h index 7b48c3a2e9..f9b6c7b276 100644 --- a/src/knot/conf/schema.h +++ b/src/knot/conf/schema.h @@ -191,6 +191,12 @@ #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" diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c index b937cd45b8..62a926efd7 100644 --- a/src/knot/conf/tools.c +++ b/src/knot/conf/tools.c @@ -1213,6 +1213,29 @@ int check_catalog_tpl( 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) diff --git a/src/knot/conf/tools.h b/src/knot/conf/tools.h index f48ed5bbc5..42be2e509b 100644 --- a/src/knot/conf/tools.h +++ b/src/knot/conf/tools.h @@ -163,6 +163,14 @@ int check_catalog_tpl( 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 ); diff --git a/src/knot/server/server.c b/src/knot/server/server.c index d80a0b0ab7..f6d73f8e47 100644 --- a/src/knot/server/server.c +++ b/src/knot/server/server.c @@ -9,6 +9,7 @@ #include #include +#include #include // OpenBSD #include // TCP_FASTOPEN #include @@ -831,16 +832,161 @@ static int configure_sockets(conf_t *conf, server_t *s) 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; } @@ -851,8 +997,22 @@ int server_init(server_t *server, int bg_workers) 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; @@ -909,6 +1069,9 @@ void server_deinit(server_t *server) /* 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); @@ -1023,6 +1186,9 @@ int server_start(server_t *server, bool answering) /* Start workers. */ worker_pool_start(server->workers); + /* Start zone DB event loop. */ + dt_start(server->rdb_events); + /* Start evsched handler. */ evsched_start(&server->sched); @@ -1399,6 +1565,14 @@ void server_stop(server_t *server) 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. */ diff --git a/src/knot/server/server.h b/src/knot/server/server.h index b37f2b1268..5faf010210 100644 --- a/src/knot/server/server.h +++ b/src/knot/server/server.h @@ -108,6 +108,10 @@ typedef struct server { /*! \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; diff --git a/src/knot/zone/zone-load.c b/src/knot/zone/zone-load.c index b4d7ef91b6..b4e1491195 100644 --- a/src/knot/zone/zone-load.c +++ b/src/knot/zone/zone-load.c @@ -28,28 +28,25 @@ int zone_load_contents(conf_t *conf, const knot_dname_t *zone_name, 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; diff --git a/src/knot/zone/zonefile.c b/src/knot/zone/zonefile.c index 25431c3891..65f3b09f65 100644 --- a/src/knot/zone/zonefile.c +++ b/src/knot/zone/zonefile.c @@ -4,10 +4,8 @@ */ #include -#include #include #include -#include #include #include #include @@ -17,8 +15,8 @@ #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" @@ -31,8 +29,8 @@ 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", @@ -84,18 +82,18 @@ int zcreator_step(zone_contents_t *contents, const knot_rrset_t *rr, zone_skip_t 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; } @@ -105,19 +103,19 @@ static void process_data(zs_scanner_t *scanner) 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); } @@ -139,24 +137,17 @@ static void error_origin(zs_scanner_t *s) } 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"; @@ -175,19 +166,16 @@ int zonefile_open(zloader_t *loader, const char *source, const knot_dname_t *ori 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; @@ -195,101 +183,91 @@ int zonefile_open(zloader_t *loader, const char *source, const knot_dname_t *ori 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; } @@ -359,13 +337,12 @@ int zonefile_write(const char *path, zone_contents_t *zone, zone_skip_t *skip) 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, diff --git a/src/knot/zone/zonefile.h b/src/knot/zone/zonefile.h index e1991c8b92..d3eabdca16 100644 --- a/src/knot/zone/zonefile.h +++ b/src/knot/zone/zonefile.h @@ -5,33 +5,20 @@ #pragma once -#include -#include - #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, @@ -44,14 +31,17 @@ 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. diff --git a/src/utils/knotc/commands.c b/src/utils/knotc/commands.c index 1797536a10..73e01063e2 100644 --- a/src/utils/knotc/commands.c +++ b/src/utils/knotc/commands.c @@ -562,7 +562,7 @@ static int zone_check(const knot_dname_t *dname, void *data) 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; diff --git a/src/utils/kzonecheck/zone_check.c b/src/utils/kzonecheck/zone_check.c index 68d06827dc..6a6c59e104 100644 --- a/src/utils/kzonecheck/zone_check.c +++ b/src/utils/kzonecheck/zone_check.c @@ -61,8 +61,9 @@ int zone_check(const char *zone_file, const knot_dname_t *zone_name, bool zonemd .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; @@ -77,17 +78,16 @@ int zone_check(const char *zone_file, const knot_dname_t *zone_name, bool zonemd 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; diff --git a/tests-fuzz/Makefile.am b/tests-fuzz/Makefile.am index 489153dbf9..20ff9b5f35 100644 --- a/tests-fuzz/Makefile.am +++ b/tests-fuzz/Makefile.am @@ -45,7 +45,8 @@ knotd_stdio_LDADD = \ $(liburcu_LIBS) \ $(lmdb_LIBS) \ $(systemd_LIBS) \ - $(libdbus_LIBS) + $(libdbus_LIBS) \ + $(hiredis_LIBS) BUILT_SOURCES = knotd_wrap/main.c CLEANFILES = knotd_wrap/main.c diff --git a/tests/Makefile.am b/tests/Makefile.am index ac88c46721..258ef902a5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,7 +15,8 @@ LDADD += \ $(top_builddir)/src/libknotd.la \ $(liburcu_LIBS) \ $(systemd_LIBS) \ - $(libdbus_LIBS) + $(libdbus_LIBS) \ + $(hiredis_LIBS) endif HAVE_DAEMON LDADD += \ diff --git a/tests/knot/test_digest.c b/tests/knot/test_digest.c index e9c5abc07b..14e6b4c2b5 100644 --- a/tests/knot/test_digest.c +++ b/tests/knot/test_digest.c @@ -3,56 +3,35 @@ * For more information, see */ -#include "knot/zone/digest.h" - +#include #include #include -#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; @@ -65,13 +44,12 @@ static zone_contents_t *str2contents(const char *zone_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;