From 230326d8de7f7ce9836feaf803b0df6fa69ecf4c Mon Sep 17 00:00:00 2001 From: Libor Peltan <libor.peltan@nic.cz> Date: Tue, 23 Feb 2021 20:38:26 +0100 Subject: [PATCH] catalog: complete refactoring with little fixes/enh --- Knot.files | 10 +- src/knot/Makefile.inc | 10 +- src/knot/catalog/catalog_db.c | 374 ++++++++ src/knot/catalog/catalog_db.h | 191 ++++ src/knot/catalog/catalog_update.c | 335 +++++++ src/knot/catalog/catalog_update.h | 151 +++ src/knot/catalog/generate.c | 245 +++++ src/knot/catalog/generate.h | 56 ++ src/knot/catalog/interpret.c | 92 ++ src/knot/catalog/interpret.h | 36 + src/knot/conf/conf.c | 8 +- src/knot/events/handlers/load.c | 3 +- src/knot/server/server.h | 4 +- src/knot/updates/zone-update.c | 2 +- src/knot/zone/backup.c | 2 +- src/knot/zone/catalog.c | 884 ------------------ src/knot/zone/catalog.h | 369 -------- src/knot/zone/zone.h | 4 +- src/knot/zone/zonedb-load.c | 174 +--- src/knot/zone/zonedb.c | 6 +- src/utils/kcatalogprint/main.c | 2 +- .../basic}/data/catalog1.zone | 0 .../basic}/data/cataloged1.zone | 0 .../basic}/data/cataloged2.zone | 0 .../{zone/catalog => catalog/basic}/test.py | 0 .../generate}/test.py | 0 tests-extra/tests/catalog/many_zones/test.py | 68 ++ tests-extra/tools/dnstest/server.py | 1 + 28 files changed, 1628 insertions(+), 1399 deletions(-) create mode 100644 src/knot/catalog/catalog_db.c create mode 100644 src/knot/catalog/catalog_db.h create mode 100644 src/knot/catalog/catalog_update.c create mode 100644 src/knot/catalog/catalog_update.h create mode 100644 src/knot/catalog/generate.c create mode 100644 src/knot/catalog/generate.h create mode 100644 src/knot/catalog/interpret.c create mode 100644 src/knot/catalog/interpret.h delete mode 100644 src/knot/zone/catalog.c delete mode 100644 src/knot/zone/catalog.h rename tests-extra/tests/{zone/catalog => catalog/basic}/data/catalog1.zone (100%) rename tests-extra/tests/{zone/catalog => catalog/basic}/data/cataloged1.zone (100%) rename tests-extra/tests/{zone/catalog => catalog/basic}/data/cataloged2.zone (100%) rename tests-extra/tests/{zone/catalog => catalog/basic}/test.py (100%) rename tests-extra/tests/{zone/catalog_generate => catalog/generate}/test.py (100%) create mode 100644 tests-extra/tests/catalog/many_zones/test.py diff --git a/Knot.files b/Knot.files index 7157f4c36a..61b6366ec5 100644 --- a/Knot.files +++ b/Knot.files @@ -100,6 +100,14 @@ src/contrib/url-parser/url_parser.h src/contrib/vpool/vpool.c src/contrib/vpool/vpool.h src/contrib/wire_ctx.h +src/knot/catalog/catalog_db.c +src/knot/catalog/catalog_db.h +src/knot/catalog/catalog_update.c +src/knot/catalog/catalog_update.h +src/knot/catalog/generate.c +src/knot/catalog/generate.h +src/knot/catalog/interpret.c +src/knot/catalog/interpret.h src/knot/common/evsched.c src/knot/common/evsched.h src/knot/common/fdset.c @@ -268,8 +276,6 @@ src/knot/zone/adjust.c src/knot/zone/adjust.h src/knot/zone/backup.c src/knot/zone/backup.h -src/knot/zone/catalog.c -src/knot/zone/catalog.h src/knot/zone/contents.c src/knot/zone/contents.h src/knot/zone/measure.c diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc index 7d39438ff4..88b964fa58 100644 --- a/src/knot/Makefile.inc +++ b/src/knot/Makefile.inc @@ -9,6 +9,14 @@ include_libknotd_HEADERS = \ knot/include/module.h libknotd_la_SOURCES = \ + knot/catalog/catalog_db.c \ + knot/catalog/catalog_db.h \ + knot/catalog/catalog_update.c \ + knot/catalog/catalog_update.h \ + knot/catalog/generate.c \ + knot/catalog/generate.h \ + knot/catalog/interpret.c \ + knot/catalog/interpret.h \ knot/conf/base.c \ knot/conf/base.h \ knot/conf/conf.c \ @@ -159,8 +167,6 @@ libknotd_la_SOURCES = \ knot/zone/adjust.h \ knot/zone/backup.c \ knot/zone/backup.h \ - knot/zone/catalog.c \ - knot/zone/catalog.h \ knot/zone/contents.c \ knot/zone/contents.h \ knot/zone/measure.h \ diff --git a/src/knot/catalog/catalog_db.c b/src/knot/catalog/catalog_db.c new file mode 100644 index 0000000000..28628bb7a2 --- /dev/null +++ b/src/knot/catalog/catalog_db.c @@ -0,0 +1,374 @@ +/* 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 <stdio.h> +#include <string.h> +#include <urcu.h> + +#include "knot/catalog/catalog_db.h" +#include "knot/common/log.h" + +static const MDB_val catalog_iter_prefix = { 1, "" }; + +size_t catalog_dname_append(knot_dname_storage_t storage, const knot_dname_t *name) +{ + size_t old_len = knot_dname_size(storage); + size_t name_len = knot_dname_size(name); + size_t new_len = old_len - 1 + name_len; + if (old_len == 0 || name_len == 0 || new_len > KNOT_DNAME_MAXLEN) { + return 0; + } + memcpy(storage + old_len - 1, name, name_len); + return new_len; +} + +int catalog_bailiwick_shift(const knot_dname_t *subname, const knot_dname_t *name) +{ + const knot_dname_t *res = subname; + while (!knot_dname_is_equal(res, name)) { + if (*res == '\0') { + return -1; + } + res = knot_wire_next_label(res, NULL); + } + return res - subname; +} + +void catalog_init(catalog_t *cat, const char *path, size_t mapsize) +{ + knot_lmdb_init(&cat->db, path, mapsize, MDB_NOTLS, NULL); +} + +// does NOT check for catalog zone version by RFC, this is Knot-specific in the cat LMDB ! +static void check_cat_version(catalog_t *cat) +{ + if (cat->ro_txn->ret == KNOT_EOK) { + MDB_val key = { 8, "\x01version" }; + if (knot_lmdb_find(cat->ro_txn, &key, KNOT_LMDB_EXACT)) { + if (strncmp(CATALOG_VERSION, cat->ro_txn->cur_val.mv_data, + cat->ro_txn->cur_val.mv_size) != 0) { + log_warning("unmatching catalog version"); + } + } else if (cat->rw_txn != NULL) { + MDB_val val = { strlen(CATALOG_VERSION), CATALOG_VERSION }; + knot_lmdb_insert(cat->rw_txn, &key, &val); + } + } +} + +int catalog_open(catalog_t *cat) +{ + if (!knot_lmdb_is_open(&cat->db)) { + int ret = knot_lmdb_open(&cat->db); + if (ret != KNOT_EOK) { + return ret; + } + } + if (cat->ro_txn == NULL) { + knot_lmdb_txn_t *ro_txn = calloc(1, sizeof(*ro_txn)); + if (ro_txn == NULL) { + return KNOT_ENOMEM; + } + knot_lmdb_begin(&cat->db, ro_txn, false); + cat->ro_txn = ro_txn; + } + check_cat_version(cat); + return cat->ro_txn->ret; +} + +int catalog_begin(catalog_t *cat) +{ + int ret = catalog_open(cat); + if (ret != KNOT_EOK) { + return ret; + } + knot_lmdb_txn_t *rw_txn = calloc(1, sizeof(*rw_txn)); + if (rw_txn == NULL) { + return KNOT_ENOMEM; + } + knot_lmdb_begin(&cat->db, rw_txn, true); + if (rw_txn->ret != KNOT_EOK) { + ret = rw_txn->ret; + free(rw_txn); + return ret; + } + assert(cat->rw_txn == NULL); // LMDB prevents two existing RW txns at a time + cat->rw_txn = rw_txn; + check_cat_version(cat); + return cat->rw_txn->ret; +} + +int catalog_commit(catalog_t *cat) +{ + knot_lmdb_txn_t *rw_txn = rcu_xchg_pointer(&cat->rw_txn, NULL); + knot_lmdb_commit(rw_txn); + int ret = rw_txn->ret; + free(rw_txn); + if (ret != KNOT_EOK) { + return ret; + } + + // now refresh RO txn + knot_lmdb_txn_t *ro_txn = calloc(1, sizeof(*ro_txn)); + if (ro_txn == NULL) { + return KNOT_ENOMEM; + } + knot_lmdb_begin(&cat->db, ro_txn, false); + cat->old_ro_txn = rcu_xchg_pointer(&cat->ro_txn, ro_txn); + + return KNOT_EOK; +} + +void catalog_abort(catalog_t *cat) +{ + knot_lmdb_txn_t *rw_txn = rcu_xchg_pointer(&cat->rw_txn, NULL); + if (rw_txn != NULL) { + knot_lmdb_abort(rw_txn); + free(rw_txn); + } +} + +void catalog_commit_cleanup(catalog_t *cat) +{ + knot_lmdb_txn_t *old_ro_txn = rcu_xchg_pointer(&cat->old_ro_txn, NULL); + if (old_ro_txn != NULL) { + knot_lmdb_abort(old_ro_txn); + free(old_ro_txn); + } +} + +int catalog_deinit(catalog_t *cat) +{ + assert(cat->rw_txn == NULL); + if (cat->ro_txn != NULL) { + knot_lmdb_abort(cat->ro_txn); + free(cat->ro_txn); + } + if (cat->old_ro_txn != NULL) { + knot_lmdb_abort(cat->old_ro_txn); + free(cat->old_ro_txn); + } + knot_lmdb_deinit(&cat->db); + return KNOT_EOK; +} + +int catalog_add(catalog_t *cat, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catzone) +{ + if (cat->rw_txn == NULL) { + return KNOT_EINVAL; + } + int bail = catalog_bailiwick_shift(owner, catzone); + if (bail < 0) { + return KNOT_EOUTOFZONE; + } + assert(bail >= 0 && bail < 256); + MDB_val key = knot_lmdb_make_key("BN", 0, member); // 0 for future purposes + MDB_val val = knot_lmdb_make_key("BBN", 0, bail, owner); + + knot_lmdb_insert(cat->rw_txn, &key, &val); + free(key.mv_data); + free(val.mv_data); + return cat->rw_txn->ret; +} + +int catalog_del(catalog_t *cat, const knot_dname_t *member) +{ + if (cat->rw_txn == NULL) { + return KNOT_EINVAL; + } + MDB_val key = knot_lmdb_make_key("BN", 0, member); + knot_lmdb_del_prefix(cat->rw_txn, &key); // deletes one record + free(key.mv_data); + return cat->rw_txn->ret; +} + +static void unmake_val(MDB_val *val, const knot_dname_t **owner, const knot_dname_t **catz) +{ + uint8_t zero, shift; + knot_lmdb_unmake_key(val->mv_data, val->mv_size, "BBN", &zero, &shift, owner); + *catz = *owner + shift; +} + +static int find_threadsafe(catalog_t *cat, const knot_dname_t *member, + const knot_dname_t **owner, const knot_dname_t **catz, + void **tofree) +{ + *tofree = NULL; + if (cat->ro_txn == NULL) { + return KNOT_ENOENT; + } + + MDB_val key = knot_lmdb_make_key("BN", 0, member), val = { 0 }; + + int ret = knot_lmdb_find_threadsafe(cat->ro_txn, &key, &val, KNOT_LMDB_EXACT); + if (ret == KNOT_EOK) { + unmake_val(&val, owner, catz); + *tofree = val.mv_data; + } + free(key.mv_data); + return ret; +} + +int catalog_get_catz(catalog_t *cat, const knot_dname_t *member, + const knot_dname_t **catz, void **tofree) +{ + const knot_dname_t *unused; + return find_threadsafe(cat, member, &unused, catz, tofree); +} + +bool catalog_has_member(catalog_t *cat, const knot_dname_t *member) +{ + const knot_dname_t *catz; + void *tofree = NULL; + int ret = catalog_get_catz(cat, member, &catz, &tofree); + free(tofree); + return (ret == KNOT_EOK); +} + +bool catalog_contains_exact(catalog_t *cat, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catz) +{ + const knot_dname_t *found_owner, *found_catz; + void *tofree = NULL; + int ret = find_threadsafe(cat, member, &found_owner, &found_catz, &tofree); + if (ret == KNOT_EOK && (!knot_dname_is_equal(owner, found_owner) || + !knot_dname_is_equal(catz, found_catz))) { + ret = KNOT_ENOENT; + } + free(tofree); + return (ret == KNOT_EOK); +} + +typedef struct { + catalog_apply_cb_t cb; + void *ctx; +} catalog_apply_ctx_t; + +static int catalog_apply_cb(MDB_val *key, MDB_val *val, void *ctx) +{ + catalog_apply_ctx_t *iter_ctx = ctx; + uint8_t zero; + const knot_dname_t *mem = NULL, *ow = NULL, *cz = NULL; + knot_lmdb_unmake_key(key->mv_data, key->mv_size, "BN", &zero, &mem); + unmake_val(val, &ow, &cz); + if (mem == NULL || ow == NULL || cz == NULL) { + return KNOT_EMALF; + } + return iter_ctx->cb(mem, ow, cz, iter_ctx->ctx); +} + +int catalog_apply(catalog_t *cat, const knot_dname_t *for_member, + catalog_apply_cb_t cb, void *ctx, bool rw) +{ + MDB_val prefix = knot_lmdb_make_key(for_member == NULL ? "B" : "BN", 0, for_member); + catalog_apply_ctx_t iter_ctx = { cb, ctx }; + knot_lmdb_txn_t *use_txn = rw ? cat->rw_txn : cat->ro_txn; + int ret = knot_lmdb_apply_threadsafe(use_txn, &prefix, true, catalog_apply_cb, &iter_ctx); + free(prefix.mv_data); + return ret; +} + +static bool same_catalog(knot_lmdb_txn_t *txn, const knot_dname_t *catalog) +{ + if (catalog == NULL) { + return true; + } + const knot_dname_t *txn_cat = NULL, *unused; + unmake_val(&txn->cur_val, &unused, &txn_cat); + return knot_dname_is_equal(txn_cat, catalog); +} + +int catalog_copy(knot_lmdb_db_t *from, knot_lmdb_db_t *to, + const knot_dname_t *cat_only, bool read_rw_txn) +{ + if (!knot_lmdb_exists(from)) { + return KNOT_EOK; + } + int ret = knot_lmdb_open(from); + if (ret == KNOT_EOK) { + ret = knot_lmdb_open(to); + } + if (ret != KNOT_EOK) { + return ret; + } + knot_lmdb_txn_t txn_r = { 0 }, txn_w = { 0 }; + knot_lmdb_begin(from, &txn_r, read_rw_txn); // using RW txn not to conflict with still-open RO txn + knot_lmdb_begin(to, &txn_w, true); + knot_lmdb_foreach(&txn_w, (MDB_val *)&catalog_iter_prefix) { + if (same_catalog(&txn_w, cat_only)) { + knot_lmdb_del_cur(&txn_w); + } + } + knot_lmdb_foreach(&txn_r, (MDB_val *)&catalog_iter_prefix) { + if (same_catalog(&txn_r, cat_only)) { + knot_lmdb_insert(&txn_w, &txn_r.cur_key, &txn_r.cur_val); + } + } + if (txn_r.ret != KNOT_EOK) { + knot_lmdb_abort(&txn_r); + knot_lmdb_abort(&txn_w); + return txn_r.ret; + } + knot_lmdb_commit(&txn_r); + knot_lmdb_commit(&txn_w); + return txn_w.ret; +} + +static void print_dname(const knot_dname_t *d) +{ + knot_dname_txt_storage_t tmp; + knot_dname_to_str(tmp, d, sizeof(tmp)); + printf("%s ", tmp); +} + +static void print_dname3(const char *prefix, const knot_dname_t *a, + const knot_dname_t *b,const knot_dname_t *c) +{ + printf("%s", prefix); + print_dname(a); + print_dname(b); + print_dname(c); + printf("\n"); +} + +static int catalog_print_cb(const knot_dname_t *mem, const knot_dname_t *ow, + const knot_dname_t *cz, void *ctx) +{ + print_dname3("", mem, ow, cz); + (*(ssize_t *)ctx)++; + return KNOT_EOK; +} + +void catalog_print(catalog_t *cat) +{ + ssize_t total = 0; + + printf(";; <catalog zone> <record owner> <record zone>\n"); + + if (cat != NULL) { + int ret = catalog_open(cat); + if (ret == KNOT_EOK) { + ret = catalog_apply(cat, NULL, catalog_print_cb, &total, false); + } + if (ret != KNOT_EOK) { + printf("Catalog print failed (%s)\n", knot_strerror(ret)); + return; + } + } + + printf("Total records: %zd\n", total); +} diff --git a/src/knot/catalog/catalog_db.h b/src/knot/catalog/catalog_db.h new file mode 100644 index 0000000000..e0c2184d22 --- /dev/null +++ b/src/knot/catalog/catalog_db.h @@ -0,0 +1,191 @@ +/* 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 "knot/journal/knot_lmdb.h" +#include "libknot/libknot.h" + +#define CATALOG_VERSION "1.0" +#define CATALOG_ZONE_VERSION "2" // must be just one char long +#define CATALOG_ZONES_LABEL "\x05""zones" + +typedef struct catalog { + knot_lmdb_db_t db; + knot_lmdb_txn_t *ro_txn; // persistent RO transaction + knot_lmdb_txn_t *rw_txn; // temporary RW transaction + + // private + knot_lmdb_txn_t *old_ro_txn; +} catalog_t; + +/*! + * \brief Append a prefix dname to a dname in a storage. + * + * \return New dname length. + */ +size_t catalog_dname_append(knot_dname_storage_t storage, const knot_dname_t *name); + +/*! + * \brief Return the number of bytes that subname has more than name. + * + * \return -1 if subname is not subname of name + */ +int catalog_bailiwick_shift(const knot_dname_t *subname, const knot_dname_t *name); + +/*! + * \brief Initialize catalog structure. + * + * \param cat Catalog structure. + * \param path Path to LMDB for catalog. + * \param mapsize Mapsize of the LMDB. + */ +void catalog_init(catalog_t *cat, const char *path, size_t mapsize); + +/*! + * \brief Open the catalog LMDB, create it if not exists. + * + * \param cat Catlog to be opened. + * + * \return KNOT_E* + */ +int catalog_open(catalog_t *cat); + +/*! + * \brief Start a temporary RW transaction in the catalog. + * + * \param cat Catalog in question. + * + * \return KNOT_E* + */ +int catalog_begin(catalog_t *cat); + +/*! + * \brief End using the temporary RW txn, refresh the persistent RO txn. + * + * \param cat Catalog in question. + * + * \return KNOT_E* + */ +int catalog_commit(catalog_t *cat); + +/*! + * \brief Abort temporary RW txn. + */ +void catalog_abort(catalog_t *cat); + +/*! + * \brief Free up old txns. + * + * \note This must be called after catalog_commit() with a delay of synchronnize_rcu(). + * + * \param cat Catalog. + */ +void catalog_commit_cleanup(catalog_t *cat); + +/*! + * \brief Close the catalog and de-init the structure. + * + * \param cat Catalog to be closed. + * + * \return KNOT_E* + */ +int catalog_deinit(catalog_t *cat); + +/*! + * \brief Add a member zone to the catalog database. + * + * \param cat Catalog to be augmented. + * \param member Member zone name. + * \param owner Owner of the PTR record in catalog zone, respective to the member zone. + * \param catzone Name of the catalog zone whose it's the member. + * + * \return KNOT_E* + */ +int catalog_add(catalog_t *cat, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catzone); + +/*! + * \brief Delete a member zone from the catalog database. + * + * \param cat Catalog to be removed from. + * \param member Member zone to be removed. + * + * \return KNOT_E* + */ +int catalog_del(catalog_t *cat, const knot_dname_t *member); + +/*! + * \brief Find catz name of the catalog owning this member. + * + * \note This function may be called in multithreaded operation. + * + * \param cat Catalog datatase. + * \param member Member to search for. + * \param catz Out: name of catalog zone it resides in. + * \param tofree Out: a pointer that has to be freed later. + * + * \return KNOT_E* + */ +int catalog_get_catz(catalog_t *cat, const knot_dname_t *member, + const knot_dname_t **catz, void **tofree); + +/*! + * \brief Check if this member exists in any catalog zone. + */ +bool catalog_has_member(catalog_t *cat, const knot_dname_t *member); + +/*! + * \brief Check if exactly this record (member, owner, catz) is in catalog DB. + */ +bool catalog_contains_exact(catalog_t *cat, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catz); + +typedef int (*catalog_apply_cb_t)(const knot_dname_t *member, const knot_dname_t *owner, + const knot_dname_t *catz, void *ctx); +/*! + * \brief Iterate through catalog database, applying callback. + * + * \param cat Catalog to be iterated. + * \param for_member (Optional) Iterate only on records for this member name. + * \param cb Callback to be called. + * \param ctx Context for this callback. + * \param rw Use read-write transaction. + * + * \return KNOT_E* + */ +int catalog_apply(catalog_t *cat, const knot_dname_t *for_member, + catalog_apply_cb_t cb, void *ctx, bool rw); + +/*! + * \brief Copy records from one catalog database to other. + * + * \param from Catalog DB to copy from. + * \param to Catalog DB to copy to. + * \param cat_only Optional: copy only records for this catalog zone. + * \param read_rw_txn Use RW txn for read operations. + * + * \return KNOT_E* + */ +int catalog_copy(knot_lmdb_db_t *from, knot_lmdb_db_t *to, + const knot_dname_t *zone_only, bool read_rw_txn); + +/*! + * \brief Print to stdout whole contents of catalog database (for human). + * + * \param cat Catalog database to be printed. + */ +void catalog_print(catalog_t *cat); diff --git a/src/knot/catalog/catalog_update.c b/src/knot/catalog/catalog_update.c new file mode 100644 index 0000000000..5e46ddbb64 --- /dev/null +++ b/src/knot/catalog/catalog_update.c @@ -0,0 +1,335 @@ +/* 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 <stdio.h> +#include <string.h> + +#include "knot/catalog/catalog_update.h" +#include "knot/common/log.h" +#include "knot/conf/base.h" +#include "contrib/macros.h" + +int catalog_update_init(catalog_update_t *u) +{ + u->upd = trie_create(NULL); + if (u->upd == NULL) { + return KNOT_ENOMEM; + } + pthread_mutex_init(&u->mutex, 0); + u->error = KNOT_EOK; + return KNOT_EOK; +} + +catalog_update_t *catalog_update_new(void) +{ + catalog_update_t *u = calloc(1, sizeof(*u)); + if (u != NULL) { + int ret = catalog_update_init(u); + if (ret != KNOT_EOK) { + free(u); + u = NULL; + } + } + return u; +} + +static void catalog_upd_val_free(catalog_upd_val_t *val) +{ + free(val->add_owner); + free(val->rem_owner); + free(val); +} + +static int freecb(trie_val_t *tval, void *unused) +{ + UNUSED(unused); + catalog_upd_val_t *val = *tval; + if (val != NULL) { + catalog_upd_val_free(val); + } + return 0; +} + +void catalog_update_clear(catalog_update_t *u) +{ + trie_apply(u->upd, freecb, NULL); + trie_clear(u->upd); + u->error = KNOT_EOK; +} + +void catalog_update_deinit(catalog_update_t *u) +{ + pthread_mutex_destroy(&u->mutex); + trie_free(u->upd); +} + +void catalog_update_free(catalog_update_t *u) +{ + if (u != NULL) { + catalog_update_deinit(u); + free(u); + } +} + +static catalog_upd_val_t *upd_val_new(const knot_dname_t *member, int bail, + const knot_dname_t *owner, bool rem) +{ + size_t member_size = knot_dname_size(member); + size_t owner_size = knot_dname_size(owner); + assert(bail <= (int)owner_size); + + catalog_upd_val_t *val = malloc(sizeof(*val) + member_size); + if (val == NULL) { + return NULL; + } + val->member = (knot_dname_t *)(val + 1); + memcpy(val->member, member, member_size); + knot_dname_t *owner_cpy = knot_dname_copy(owner, NULL); + if (owner_cpy == NULL) { + free(val); + return NULL; + } + if (rem) { + val->type = CAT_UPD_REM; + val->add_owner = NULL; + val->add_catz = NULL; + val->rem_owner = owner_cpy; + val->rem_catz = owner_cpy + bail; + } else { + val->type = CAT_UPD_ADD; + val->add_owner = owner_cpy; + val->add_catz = owner_cpy + bail; + val->rem_owner = NULL; + val->rem_catz = NULL; + } + return val; +} + +static const knot_dname_t *get_uniq(const knot_dname_t *ptr_owner, + const knot_dname_t *catz) +{ + int labels = knot_dname_labels(ptr_owner, NULL); + labels -= knot_dname_labels(catz, NULL); + assert(labels >= 2); + return ptr_owner + knot_dname_prefixlen(ptr_owner, labels - 2, NULL); +} + +static bool same_uniq(const knot_dname_t *owner1, const knot_dname_t *catz1, + const knot_dname_t *owner2, const knot_dname_t *catz2) +{ + const knot_dname_t *uniq1 = get_uniq(owner1, catz1), *uniq2 = get_uniq(owner2, catz2); + if (*uniq1 != *uniq2) { + return false; + } + return memcmp(uniq1 + 1, uniq2 + 1, *uniq1) == 0; +} + +static int upd_val_update(catalog_upd_val_t *val, int bail, + const knot_dname_t *owner, bool rem) +{ + if ((rem && val->type != CAT_UPD_ADD) || + (!rem && val->type != CAT_UPD_REM)) { + return KNOT_EEXIST; + } + knot_dname_t *owner_cpy = knot_dname_copy(owner, NULL); + if (owner_cpy == NULL) { + return KNOT_ENOMEM; + } + if (rem) { + val->rem_owner = owner_cpy; + val->rem_catz = owner_cpy + bail; + } else { + val->add_owner = owner_cpy; + val->add_catz = owner_cpy + bail; + } + if (same_uniq(val->rem_owner, val->rem_catz, val->add_owner, val->add_catz)) { + val->type = CAT_UPD_MINOR; + } else { + val->type = CAT_UPD_UNIQ; + } + return KNOT_EOK; +} + +int catalog_update_add(catalog_update_t *u, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catzone, + bool remove, catalog_t *check_rem) +{ + if (remove && check_rem != NULL && + !catalog_contains_exact(check_rem, member, owner, catzone)) { + return KNOT_EOK; + // we need to perform this check immediately because + // garbage removal would block legitimate removal + } + + int bail = catalog_bailiwick_shift(owner, catzone); + if (bail < 0) { + return KNOT_EOUTOFZONE; + } + assert(bail >= 0 && bail <= KNOT_DNAME_MAXLEN); + + knot_dname_storage_t lf_storage; + uint8_t *lf = knot_dname_lf(member, lf_storage); + + trie_val_t *found = trie_get_try(u->upd, lf + 1, lf[0]); + if (found != NULL) { + catalog_upd_val_t *val = *found; + assert(knot_dname_is_equal(val->member, member)); + return upd_val_update(val, bail, owner, remove); + } + + catalog_upd_val_t *val = upd_val_new(member, bail, owner, remove); + if (val == NULL) { + return KNOT_ENOMEM; + } + trie_val_t *added = trie_get_ins(u->upd, lf + 1, lf[0]); + if (added == NULL) { + catalog_upd_val_free(val); + return KNOT_ENOMEM; + } + assert(*added == NULL); + *added = val; + return KNOT_EOK; +} + +catalog_upd_val_t *catalog_update_get(catalog_update_t *u, const knot_dname_t *member) +{ + knot_dname_storage_t lf_storage; + uint8_t *lf = knot_dname_lf(member, lf_storage); + + trie_val_t *found = trie_get_try(u->upd, lf + 1, lf[0]); + return found == NULL ? NULL : *(catalog_upd_val_t **)found; +} + +static bool check_member(catalog_upd_val_t *val, conf_t *conf, catalog_t *cat) +{ + if (val->type == CAT_UPD_REM || val->type == CAT_UPD_INVALID) { + return true; + } + if (!conf_rawid_exists(conf, C_ZONE, val->add_catz, knot_dname_size(val->add_catz))) { + knot_dname_txt_storage_t cat_str; + (void)knot_dname_to_str(cat_str, val->add_catz, sizeof(cat_str)); + log_zone_error(val->member, "catalog template zone '%s' not configured, ignoring", cat_str); + return false; + } + if (conf_rawid_exists(conf, C_ZONE, val->member, knot_dname_size(val->member))) { + log_zone_error(val->member, "member zone already configured, ignoring"); + return false; + } + if (val->type == CAT_UPD_ADD && catalog_has_member(cat, val->member)) { + log_zone_error(val->member, "member zone already configured by catalog, ignoring"); + return false; + } + return true; +} + +static int rem_conf_conflict(const knot_dname_t *mem, const knot_dname_t *ow, + const knot_dname_t *cz, void *ctx) +{ + if (conf_rawid_exists(conf(), C_ZONE, mem, knot_dname_size(mem))) { + return catalog_update_add(ctx, mem, ow, cz, true, NULL); + } + return KNOT_EOK; +} + +void catalog_update_finalize(catalog_update_t *u, catalog_t *cat) +{ + conf_t *cnf = conf(); + + catalog_it_t *it = catalog_it_begin(u); + while (!catalog_it_finished(it)) { + catalog_upd_val_t *val = catalog_it_val(it); + if (!check_member(val, cnf, cat)) { + val->type = (val->type == CAT_UPD_ADD ? CAT_UPD_INVALID : CAT_UPD_REM); + } + catalog_it_next(it); + } + catalog_it_free(it); + + // TODO this takes time. Is it really useful? + // it checks if the configuration file has not + // changed in the way to create confict with + // existing member zone and let config take precendence + if (cat->ro_txn != NULL) { + (void)catalog_apply(cat, NULL, rem_conf_conflict, u, false); + } +} + +int catalog_update_commit(catalog_update_t *u, catalog_t *cat) +{ + catalog_it_t *it = catalog_it_begin(u); + if (catalog_it_finished(it)) { + catalog_it_free(it); + return KNOT_EOK; + } + int ret = catalog_begin(cat); + while (!catalog_it_finished(it) && ret == KNOT_EOK) { + catalog_upd_val_t *val = catalog_it_val(it); + switch (val->type) { + case CAT_UPD_ADD: + case CAT_UPD_MINOR: // catalog_add will simply update/overwrite existing data + case CAT_UPD_UNIQ: + ret = catalog_add(cat, val->member, val->add_owner, val->add_catz); + break; + case CAT_UPD_REM: + ret = catalog_del(cat, val->member); + break; + case CAT_UPD_INVALID: + break; // no action + default: + assert(0); + ret = KNOT_ERROR; + } + catalog_it_next(it); + } + catalog_it_free(it); + if (ret == KNOT_EOK) { + ret = catalog_commit(cat); + } else { + catalog_abort(cat); + } + return ret; +} + +typedef struct { + const knot_dname_t *zone; + catalog_update_t *u; +} del_all_ctx_t; + +static int del_all_cb(const knot_dname_t *member, const knot_dname_t *owner, + const knot_dname_t *catz, void *dactx) +{ + del_all_ctx_t *ctx = dactx; + if (knot_dname_is_equal(catz, ctx->zone)) { + // TODO possible speedup by indexing which member zones belong to a catalog zone + return catalog_update_add(ctx->u, member, owner, catz, true, NULL); + } else { + return KNOT_EOK; + } +} + +int catalog_update_del_all(catalog_update_t *u, catalog_t *cat, const knot_dname_t *zone) +{ + int ret = catalog_open(cat); + if (ret != KNOT_EOK) { + return ret; + } + + pthread_mutex_lock(&u->mutex); + del_all_ctx_t ctx = { zone, u }; + ret = catalog_apply(cat, NULL, del_all_cb, &ctx, false); + pthread_mutex_unlock(&u->mutex); + return ret; +} diff --git a/src/knot/catalog/catalog_update.h b/src/knot/catalog/catalog_update.h new file mode 100644 index 0000000000..0835a74965 --- /dev/null +++ b/src/knot/catalog/catalog_update.h @@ -0,0 +1,151 @@ +/* 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 "contrib/qp-trie/trie.h" +#include "knot/catalog/catalog_db.h" + +typedef enum { + CAT_UPD_INVALID, // invalid value + CAT_UPD_ADD, // member addition + CAT_UPD_REM, // member removal + CAT_UPD_MINOR, // owner or catzone change, uniqID preserved + CAT_UPD_UNIQ, // uniqID change + CAT_UPD_MAX, // number of options in ths enum +} catalog_upd_type_t; + +typedef struct catalog_upd_val { + knot_dname_t *member; // name of catalog member zone + catalog_upd_type_t type; // what kind of update this is + + knot_dname_t *rem_owner; // owner of PTR record being removed + knot_dname_t *rem_catz; // catalog zone the member being removed from + knot_dname_t *add_owner; // owner of PTR record being added + knot_dname_t *add_catz; // catalog zone the member being added to +} catalog_upd_val_t; + +typedef struct { + trie_t *upd; // tree of catalog_upd_val_t, that gonna be changed in catalog + int error; // error occured during generating of upd + pthread_mutex_t mutex; // lock for accessing this struct +} catalog_update_t; + +/*! + * \brief Initialize catalog update structure. + * + * \param u Catalog update to be initialized. + * + * \return KNOT_EOK, KNOT_ENOMEM + */ +int catalog_update_init(catalog_update_t *u); +catalog_update_t *catalog_update_new(void); + +/*! + * \brief Clear contents of catalog update structure. + * + * \param u Catalog update structure to be cleared. + */ +void catalog_update_clear(catalog_update_t *u); + +/*! + * \brief Free catalog update structure. + * + * \param u Catalog update structure. + */ +void catalog_update_deinit(catalog_update_t *u); +void catalog_update_free(catalog_update_t *u); + +/*! + * \brief Add a new record to catalog update structure. + * + * \param u Catalog update. + * \param member Member zone name to be added. + * \param owner Owner of respective PTR record. + * \param catzone Catalog zone holding the member. + * \param remove Add a removal of such record. + * \param check_rem Check catalog DB for existing record to be removed. + * + * \return KNOT_E* + */ +int catalog_update_add(catalog_update_t *u, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catzone, + bool remove, catalog_t *check_rem); + +/*! + * \brief Read catalog update record for given member zone. + * + * \param u Catalog update. + * \param member Member zone name. + * \param remove Search in remove section. + * + * \return Found update record for given member zone; or NULL. + */ +catalog_upd_val_t *catalog_update_get(catalog_update_t *u, const knot_dname_t *member); + +/*! + * \brief Catalog update iteration. + */ +typedef trie_it_t catalog_it_t; + +inline static catalog_it_t *catalog_it_begin(catalog_update_t *u) +{ + return trie_it_begin(u->upd); +} + +inline static catalog_upd_val_t *catalog_it_val(catalog_it_t *it) +{ + return *(catalog_upd_val_t **)trie_it_val(it); +} + +inline static bool catalog_it_finished(catalog_it_t *it) +{ + return it == NULL || trie_it_finished(it); +} + +#define catalog_it_next trie_it_next +#define catalog_it_free trie_it_free + +/*! + * \brief CHeck and align Catalog update to avoid conflicts with conf or other catalogs. + * + * \param u Catalog update to be aligned in-place. + * \param cat Catalog DB to check against. + * + * \note Also accesses conf(). + */ +void catalog_update_finalize(catalog_update_t *u, catalog_t *cat); + +/*! + * \brief Put changes from Catalog Update into persistent Catalog database. + * + * \param u Catalog update to be commited. + * \param cat Catalog to be updated. + * + * \return KNOT_E* + */ +int catalog_update_commit(catalog_update_t *u, catalog_t *cat); + +/*! + * \brief Add to catalog update removals of all member zones of a single catalog zone. + * + * \param u Catalog update to be updated. + * \param cat Catalog database to be iterated. + * \param zone Name of catalog zone whose members gonna be removed. + * + * \return KNOT_E* + */ +int catalog_update_del_all(catalog_update_t *u, catalog_t *cat, const knot_dname_t *zone); diff --git a/src/knot/catalog/generate.c b/src/knot/catalog/generate.c new file mode 100644 index 0000000000..05230e3ef6 --- /dev/null +++ b/src/knot/catalog/generate.c @@ -0,0 +1,245 @@ +/* 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 <string.h> + +#include "knot/catalog/generate.h" +#include "knot/common/log.h" +#include "knot/updates/zone-update.h" +#include "knot/zone/zonedb.h" +#include "contrib/openbsd/siphash.h" +#include "contrib/wire_ctx.h" + +static knot_dname_t *catalog_member_owner(const knot_dname_t *member, + const knot_dname_t *catzone, + time_t member_time) +{ + SIPHASH_CTX hash; + SIPHASH_KEY shkey = { 0 }; // only used for hashing -> zero key + SipHash24_Init(&hash, &shkey); + SipHash24_Update(&hash, member, knot_dname_size(member)); + uint64_t u64time = htobe64(member_time); + SipHash24_Update(&hash, &u64time, sizeof(u64time)); + uint64_t hashres = SipHash24_End(&hash); + + char *hexhash = bin_to_hex((uint8_t *)&hashres, sizeof(hashres)); + if (hexhash == NULL) { + return NULL; + } + size_t hexlen = strlen(hexhash); + assert(hexlen == 16); + size_t zoneslen = knot_dname_size((uint8_t *)CATALOG_ZONES_LABEL); + assert(hexlen <= KNOT_DNAME_MAXLABELLEN && zoneslen <= KNOT_DNAME_MAXLABELLEN); + size_t catzlen = knot_dname_size(catzone); + + size_t outlen = hexlen + zoneslen + catzlen; + knot_dname_t *out; + if (outlen > KNOT_DNAME_MAXLEN || (out = malloc(outlen)) == NULL) { + free(hexhash); + return NULL; + } + + wire_ctx_t wire = wire_ctx_init(out, outlen); + wire_ctx_write_u8(&wire, hexlen); + wire_ctx_write(&wire, hexhash, hexlen); + wire_ctx_write(&wire, CATALOG_ZONES_LABEL, zoneslen); + wire_ctx_skip(&wire, -1); + wire_ctx_write(&wire, catzone, catzlen); + assert(wire.error == KNOT_EOK); + + free(hexhash); + return out; +} + +void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old) +{ + // general comment: catz->contents!=NULL means incremental update of catalog + + if (db_old != NULL) { + knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_old); + while (!knot_zonedb_iter_finished(it)) { + zone_t *zone = knot_zonedb_iter_val(it); + knot_dname_t *cg = zone->catalog_gen; + if (cg != NULL && knot_zonedb_find(db_new, zone->name) == NULL) { + zone_t *catz = knot_zonedb_find(db_new, cg); + if (catz != NULL && catz->contents != NULL) { + assert(catz->cat_members != NULL); // if this failed to allocate, catz wasn't added to zonedb + knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member); + if (owner == NULL) { + catz->cat_members->error = KNOT_ENOENT; + knot_zonedb_iter_next(it); + continue; + } + int ret = catalog_update_add(catz->cat_members, zone->name, owner, cg, true, NULL); + free(owner); + if (ret != KNOT_EOK) { + catz->cat_members->error = ret; + } + } + } + knot_zonedb_iter_next(it); + } + knot_zonedb_iter_free(it); + } + + knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_new); + while (!knot_zonedb_iter_finished(it)) { + zone_t *zone = knot_zonedb_iter_val(it); + knot_dname_t *cg = zone->catalog_gen; + zone_t *catz = cg != NULL ? knot_zonedb_find(db_new, cg) : NULL; + if (cg != NULL && catz == NULL) { + log_zone_warning(zone->name, "member zone belongs to non-existing catalog zone"); + continue; + } + if (cg != NULL && (catz->contents == NULL || knot_zonedb_find(db_old, zone->name) == NULL)) { + assert(catz->cat_members != NULL); + knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member); + if (owner == NULL) { + catz->cat_members->error = KNOT_ENOENT; + knot_zonedb_iter_next(it); + continue; + } + int ret = catalog_update_add(catz->cat_members, zone->name, owner, cg, false, NULL); + free(owner); + if (ret != KNOT_EOK) { + catz->cat_members->error = ret; + } + } + knot_zonedb_iter_next(it); + } + knot_zonedb_iter_free(it); +} + +static void set_rdata(knot_rrset_t *rrset, uint8_t *data, uint16_t len) +{ + knot_rdata_init(rrset->rrs.rdata, len, data); + rrset->rrs.size = knot_rdata_size(len); +} + +struct zone_contents *catalog_update_to_zone(catalog_update_t *u, const knot_dname_t *catzone, + uint32_t soa_serial) +{ + if (u->error != KNOT_EOK) { + return NULL; + } + zone_contents_t *c = zone_contents_new(catzone, true); + if (c == NULL) { + return c; + } + + zone_node_t *unused = NULL; + uint8_t invalid[9] = "\x07""invalid"; + uint8_t version[9] = "\x07""version"; + uint8_t cat_version[2] = "\x01" CATALOG_ZONE_VERSION; + + // prepare common rrset with one rdata item + uint8_t rdata[256] = { 0 }; + knot_rrset_t rrset; + knot_rrset_init(&rrset, (knot_dname_t *)catzone, KNOT_RRTYPE_SOA, KNOT_CLASS_IN, 0); + rrset.rrs.rdata = (knot_rdata_t *)rdata; + rrset.rrs.count = 1; + + // set catalog zone's SOA + uint8_t data[250]; + assert(sizeof(knot_rdata_t) + sizeof(data) <= sizeof(rdata)); + wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); + wire_ctx_write(&wire, invalid, sizeof(invalid)); + wire_ctx_write(&wire, invalid, sizeof(invalid)); + wire_ctx_write_u32(&wire, soa_serial); + wire_ctx_write_u32(&wire, CATALOG_SOA_REFRESH); + wire_ctx_write_u32(&wire, CATALOG_SOA_RETRY); + wire_ctx_write_u32(&wire, CATALOG_SOA_EXPIRE); + wire_ctx_write_u32(&wire, 0); + set_rdata(&rrset, data, wire_ctx_offset(&wire)); + if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { + goto fail; + } + + // set catalog zone's NS + unused = NULL; + rrset.type = KNOT_RRTYPE_NS; + set_rdata(&rrset, invalid, sizeof(invalid)); + if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { + goto fail; + } + + // set catalog zone's version TXT + unused = NULL; + knot_dname_storage_t owner; + if (knot_dname_store(owner, version) == 0 || catalog_dname_append(owner, catzone) == 0) { + goto fail; + } + rrset.owner = owner; + rrset.type = KNOT_RRTYPE_TXT; + set_rdata(&rrset, cat_version, sizeof(cat_version)); + if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { + goto fail; + } + + // insert member zone PTR records + rrset.type = KNOT_RRTYPE_PTR; + catalog_it_t *it = catalog_it_begin(u); + while (!catalog_it_finished(it)) { + catalog_upd_val_t *val = catalog_it_val(it); + if (val->add_owner == NULL) { + continue; + } + rrset.owner = val->add_owner; + set_rdata(&rrset, val->member, knot_dname_size(val->member)); + unused = NULL; + if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { + catalog_it_free(it); + goto fail; + } + catalog_it_next(it); + } + catalog_it_free(it); + + return c; +fail: + zone_contents_deep_free(c); + return NULL; +} + +int catalog_update_to_update(catalog_update_t *u, struct zone_update *zu) +{ + knot_rrset_t ptr; + knot_rrset_init(&ptr, NULL, KNOT_RRTYPE_PTR, KNOT_CLASS_IN, 0); + uint8_t tmp[KNOT_DNAME_MAXLEN + sizeof(knot_rdata_t)]; + ptr.rrs.rdata = (knot_rdata_t *)tmp; + ptr.rrs.count = 1; + + int ret = u->error; + catalog_it_t *it = catalog_it_begin(u); + while (!catalog_it_finished(it) && ret == KNOT_EOK) { + catalog_upd_val_t *val = catalog_it_val(it); + if (val->type == CAT_UPD_INVALID) { + continue; + } + set_rdata(&ptr, val->member, knot_dname_size(val->member)); + if (val->type != CAT_UPD_ADD && knot_dname_is_equal(zu->zone->name, val->rem_catz)) { + ptr.owner = val->rem_owner; + ret = zone_update_remove(zu, &ptr); + } + if (val->type != CAT_UPD_REM && knot_dname_is_equal(zu->zone->name, val->add_catz)) { + ptr.owner = val->add_owner; + ret = zone_update_add(zu, &ptr); + } + catalog_it_next(it); + } + catalog_it_free(it); + return ret; +} diff --git a/src/knot/catalog/generate.h b/src/knot/catalog/generate.h new file mode 100644 index 0000000000..721c1efa03 --- /dev/null +++ b/src/knot/catalog/generate.h @@ -0,0 +1,56 @@ +/* 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 "knot/catalog/catalog_update.h" + +#define CATALOG_SOA_REFRESH 3600 +#define CATALOG_SOA_RETRY 600 +#define CATALOG_SOA_EXPIRE (INT32_MAX - 1) + +struct knot_zonedb; + +/*! + * \brief Compare old and new zonedb, create incremental catalog upd in each catz->cat_members + */ +void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old); + +struct zone_contents; + +/*! + * \brief Generate catalog zone contents from (full) catalog update. + * + * \param u Catalog update to read. + * \param catzone Catalog zone name. + * \param soa_serial SOA serial of the generated zone. + * + * \return Catalog zone contents, or NULL if ENOMEM. + */ +struct zone_contents *catalog_update_to_zone(catalog_update_t *u, const knot_dname_t *catzone, + uint32_t soa_serial); + +struct zone_update; + +/*! + * \brief Incrementally update catalog zone from catalog update. + * + * \param u Catalog update to read. + * \param zu Zone update to be updated. + * + * \return KNOT_E* + */ +int catalog_update_to_update(catalog_update_t *u, struct zone_update *zu); diff --git a/src/knot/catalog/interpret.c b/src/knot/catalog/interpret.c new file mode 100644 index 0000000000..fa45bbe214 --- /dev/null +++ b/src/knot/catalog/interpret.c @@ -0,0 +1,92 @@ +/* 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 <pthread.h> + +#include "knot/catalog/interpret.h" +#include "knot/zone/contents.h" + +typedef struct { + catalog_update_t *u; + const knot_dname_t *apex; + bool remove; + catalog_t *check; +} cat_upd_ctx_t; + +static bool check_zone_version(const zone_contents_t *zone) +{ + size_t zone_size = knot_dname_size(zone->apex->owner); + knot_dname_t sub[zone_size + 8]; + memcpy(sub, "\x07""version", 8); + memcpy(sub + 8, zone->apex->owner, zone_size); + + const zone_node_t *ver_node = zone_contents_find_node(zone, sub); + knot_rdataset_t *ver_rr = node_rdataset(ver_node, KNOT_RRTYPE_TXT); + if (ver_rr == NULL) { + return false; + } + + knot_rdata_t *rdata = ver_rr->rdata; + for (int i = 0; i < ver_rr->count; i++) { + if (rdata->len == 2 && rdata->data[1] == CATALOG_ZONE_VERSION[0]) { + return true; + } + rdata = knot_rdataset_next(rdata); + } + return false; +} + +static int cat_update_add_node(zone_node_t *node, void *data) +{ + cat_upd_ctx_t *ctx = data; + const knot_rdataset_t *ptr = node_rdataset(node, KNOT_RRTYPE_PTR); + if (ptr == NULL || ptr->count == 0) { + return KNOT_EOK; + } + knot_rdata_t *rdata = ptr->rdata; + int ret = KNOT_EOK; + for (int i = 0; ret == KNOT_EOK && i < ptr->count; i++) { + const knot_dname_t *member = knot_ptr_name(rdata); + ret = catalog_update_add(ctx->u, member, node->owner, ctx->apex, + ctx->remove, ctx->check); + rdata = knot_rdataset_next(rdata); + } + return ret; +} + +int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone, + bool remove, bool check_ver, catalog_t *check) +{ + if (check_ver && !check_zone_version(zone)) { + return KNOT_EZONEINVAL; + } + + knot_dname_storage_t sub; + if (knot_dname_store(sub, (uint8_t *)CATALOG_ZONES_LABEL) == 0 || + catalog_dname_append(sub, zone->apex->owner) == 0) { + return KNOT_EINVAL; + } + + if (zone_contents_find_node(zone, sub) == NULL) { + return KNOT_EOK; + } + + cat_upd_ctx_t ctx = { u, zone->apex->owner, remove, check }; + pthread_mutex_lock(&u->mutex); + int ret = zone_tree_sub_apply(zone->nodes, sub, false, cat_update_add_node, &ctx); + pthread_mutex_unlock(&u->mutex); + return ret; +} diff --git a/src/knot/catalog/interpret.h b/src/knot/catalog/interpret.h new file mode 100644 index 0000000000..851c6cf85e --- /dev/null +++ b/src/knot/catalog/interpret.h @@ -0,0 +1,36 @@ +/* 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 "knot/catalog/catalog_update.h" + +struct zone_contents; + +/*! + * \brief Iterate over PTR records in given zone contents and add members to catalog update. + * + * \param u Catalog update to be updated. + * \param zone Zone contents to be searched for member PTR records. + * \param remove Add removals of found member zones. + * \param check_ver Do check catalog zone version record first. + * \param check Optional: existing catalog database to be checked for existence + * of such record (useful for removals). + * + * \return KNOT_E* + */ +int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone, + bool remove, bool check_ver, catalog_t *check); diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c index 8e200f8697..c9007bb7d0 100644 --- a/src/knot/conf/conf.c +++ b/src/knot/conf/conf.c @@ -23,9 +23,9 @@ #include "knot/conf/base.h" #include "knot/conf/confdb.h" +#include "knot/catalog/catalog_db.h" #include "knot/common/log.h" #include "knot/server/dthreads.h" -#include "knot/zone/catalog.h" #include "libknot/libknot.h" #include "libknot/yparser/yptrafo.h" #include "contrib/macros.h" @@ -218,11 +218,13 @@ conf_val_t conf_zone_get_txn( // Check if this is a catalog member zone. if (conf->catalog != NULL) { - knot_dname_storage_t catalog; - int ret = catalog_get_zone_threadsafe(conf->catalog, dname, catalog); + void *tofree = NULL; + const knot_dname_t *catalog; + int ret = catalog_get_catz(conf->catalog, dname, &catalog, &tofree); if (ret == KNOT_EOK) { conf_db_get(conf, txn, C_ZONE, C_CATALOG_TPL, catalog, knot_dname_size(catalog), &val); + free(tofree); if (val.code != KNOT_EOK) { CONF_LOG_ZONE(LOG_ERR, catalog, "catalog zone has no catalog template (%s)", diff --git a/src/knot/events/handlers/load.c b/src/knot/events/handlers/load.c index 2dcff11bed..3d37a4fb5c 100644 --- a/src/knot/events/handlers/load.c +++ b/src/knot/events/handlers/load.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* 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 @@ -16,6 +16,7 @@ #include <assert.h> +#include "knot/catalog/generate.h" #include "knot/common/log.h" #include "knot/conf/conf.h" #include "knot/dnssec/key-events.h" diff --git a/src/knot/server/server.h b/src/knot/server/server.h index 56dd3cd383..0c14e67e1e 100644 --- a/src/knot/server/server.h +++ b/src/knot/server/server.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* 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 @@ -17,13 +17,13 @@ #pragma once #include "knot/conf/conf.h" +#include "knot/catalog/catalog_update.h" #include "knot/common/evsched.h" #include "knot/common/fdset.h" #include "knot/journal/knot_lmdb.h" #include "knot/server/dthreads.h" #include "knot/worker/pool.h" #include "knot/zone/backup.h" -#include "knot/zone/catalog.h" #include "knot/zone/zonedb.h" struct server; diff --git a/src/knot/updates/zone-update.c b/src/knot/updates/zone-update.c index 5cee6e4bb2..e1b6a2c5f4 100644 --- a/src/knot/updates/zone-update.c +++ b/src/knot/updates/zone-update.c @@ -14,12 +14,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +#include "knot/catalog/interpret.h" #include "knot/common/log.h" #include "knot/dnssec/zone-events.h" #include "knot/updates/zone-update.h" #include "knot/zone/adds_tree.h" #include "knot/zone/adjust.h" -#include "knot/zone/catalog.h" #include "knot/zone/serial.h" #include "knot/zone/zone-diff.h" #include "knot/zone/zonefile.h" diff --git a/src/knot/zone/backup.c b/src/knot/zone/backup.c index 1e85f52e6f..ef34547607 100644 --- a/src/knot/zone/backup.c +++ b/src/knot/zone/backup.c @@ -25,11 +25,11 @@ #include "contrib/files.h" #include "knot/zone/backup.h" +#include "knot/catalog/catalog_db.h" #include "knot/common/log.h" #include "knot/dnssec/kasp/kasp_zone.h" #include "knot/dnssec/kasp/keystore.h" #include "knot/journal/journal_metadata.h" -#include "knot/zone/catalog.h" #include "libdnssec/error.h" #include "contrib/files.h" #include "contrib/string.h" diff --git a/src/knot/zone/catalog.c b/src/knot/zone/catalog.c deleted file mode 100644 index 1b3589e1f8..0000000000 --- a/src/knot/zone/catalog.c +++ /dev/null @@ -1,884 +0,0 @@ -/* 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/>. - */ - -#include "knot/zone/catalog.h" - -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <urcu.h> - -#include "contrib/openbsd/siphash.h" -#include "contrib/string.h" -#include "contrib/wire_ctx.h" - -#include "knot/common/log.h" -#include "knot/conf/conf.h" -#include "knot/updates/zone-update.h" - -#define CATALOG_VERSION "1.0" -#define CATALOG_ZONE_VERSION "2" // must be just one char long -#define CATALOG_ZONES_LABEL "\x05""zones" -#define CATALOG_SOA_REFRESH 3600 -#define CATALOG_SOA_RETRY 600 -#define CATALOG_SOA_EXPIRE (INT32_MAX - 1) - -const MDB_val catalog_iter_prefix = { 1, "" }; - -knot_dname_t *catalog_member_owner(const knot_dname_t *member, - const knot_dname_t *catzone, - time_t member_time) -{ - SIPHASH_CTX hash; - SIPHASH_KEY shkey = { 0 }; // only used for hashing -> zero key - SipHash24_Init(&hash, &shkey); - SipHash24_Update(&hash, member, knot_dname_size(member)); - uint64_t u64time = htobe64(member_time); - SipHash24_Update(&hash, &u64time, sizeof(u64time)); - uint64_t hashres = SipHash24_End(&hash); - - char *hexhash = bin_to_hex((uint8_t *)&hashres, sizeof(hashres)); - if (hexhash == NULL) { - return NULL; - } - size_t hexlen = strlen(hexhash); - assert(hexlen == 16); - size_t zoneslen = knot_dname_size((uint8_t *)CATALOG_ZONES_LABEL); - assert(hexlen <= KNOT_DNAME_MAXLABELLEN && zoneslen <= KNOT_DNAME_MAXLABELLEN); - size_t catzlen = knot_dname_size(catzone); - - size_t outlen = hexlen + zoneslen + catzlen; - knot_dname_t *out; - if (outlen > KNOT_DNAME_MAXLEN || (out = malloc(outlen)) == NULL) { - free(hexhash); - return NULL; - } - - wire_ctx_t wire = wire_ctx_init(out, outlen); - wire_ctx_write_u8(&wire, hexlen); - wire_ctx_write(&wire, hexhash, hexlen); - wire_ctx_write(&wire, CATALOG_ZONES_LABEL, zoneslen); - wire_ctx_skip(&wire, -1); - wire_ctx_write(&wire, catzone, catzlen); - assert(wire.error == KNOT_EOK); - - free(hexhash); - return out; -} - -static bool check_zone_version(const zone_contents_t *zone) -{ - size_t zone_size = knot_dname_size(zone->apex->owner); - knot_dname_t sub[zone_size + 8]; - memcpy(sub, "\x07""version", 8); - memcpy(sub + 8, zone->apex->owner, zone_size); - - const zone_node_t *ver_node = zone_contents_find_node(zone, sub); - knot_rdataset_t *ver_rr = node_rdataset(ver_node, KNOT_RRTYPE_TXT); - if (ver_rr == NULL) { - return false; - } - - knot_rdata_t *rd = ver_rr->rdata; - for (int i = 0; i < ver_rr->count; i++) { - if (rd->len == 2 && rd->data[1] == CATALOG_ZONE_VERSION[0]) { - return true; - } - rd = knot_rdataset_next(rd); - } - return false; -} - -void catalog_init(catalog_t *cat, const char *path, size_t mapsize) -{ - knot_lmdb_init(&cat->db, path, mapsize, MDB_NOTLS, NULL); -} - -// does NOT check for catalog zone version by RFC, this is Knot-specific in the cat LMDB ! -static void check_cat_version(catalog_t *cat) -{ - if (cat->ro_txn->ret == KNOT_EOK) { - MDB_val key = { 8, "\x01version" }; - if (knot_lmdb_find(cat->ro_txn, &key, KNOT_LMDB_EXACT)) { - if (strncmp(CATALOG_VERSION, cat->ro_txn->cur_val.mv_data, - cat->ro_txn->cur_val.mv_size) != 0) { - log_warning("unmatching catalog version"); - } - } else if (cat->rw_txn != NULL) { - MDB_val val = { strlen(CATALOG_VERSION), CATALOG_VERSION }; - knot_lmdb_insert(cat->rw_txn, &key, &val); - } - } -} - -int catalog_open(catalog_t *cat) -{ - if (!knot_lmdb_is_open(&cat->db)) { - int ret = knot_lmdb_open(&cat->db); - if (ret != KNOT_EOK) { - return ret; - } - } - if (cat->ro_txn == NULL) { - knot_lmdb_txn_t *ro_txn = calloc(1, sizeof(*ro_txn)); - if (ro_txn == NULL) { - return KNOT_ENOMEM; - } - knot_lmdb_begin(&cat->db, ro_txn, false); - cat->ro_txn = ro_txn; - } - check_cat_version(cat); - return cat->ro_txn->ret; -} - -int catalog_begin(catalog_t *cat) -{ - int ret = catalog_open(cat); - if (ret != KNOT_EOK) { - return ret; - } - knot_lmdb_txn_t *rw_txn = calloc(1, sizeof(*rw_txn)); - if (rw_txn == NULL) { - return KNOT_ENOMEM; - } - knot_lmdb_begin(&cat->db, rw_txn, true); - if (rw_txn->ret != KNOT_EOK) { - ret = rw_txn->ret; - free(rw_txn); - return ret; - } - assert(cat->rw_txn == NULL); // LMDB prevents two existing RW txns at a time - cat->rw_txn = rw_txn; - check_cat_version(cat); - return cat->rw_txn->ret; -} - -int catalog_commit(catalog_t *cat) -{ - knot_lmdb_txn_t *rw_txn = rcu_xchg_pointer(&cat->rw_txn, NULL); - knot_lmdb_commit(rw_txn); - int ret = rw_txn->ret; - free(rw_txn); - if (ret != KNOT_EOK) { - return ret; - } - - // now refresh RO txn - knot_lmdb_txn_t *ro_txn = calloc(1, sizeof(*ro_txn)); - if (ro_txn == NULL) { - return KNOT_ENOMEM; - } - knot_lmdb_begin(&cat->db, ro_txn, false); - cat->old_ro_txn = rcu_xchg_pointer(&cat->ro_txn, ro_txn); - - return KNOT_EOK; -} - -void catalog_commit_cleanup(catalog_t *cat) -{ - knot_lmdb_txn_t *old_ro_txn = rcu_xchg_pointer(&cat->old_ro_txn, NULL); - if (old_ro_txn != NULL) { - knot_lmdb_abort(old_ro_txn); - free(old_ro_txn); - } -} - -int catalog_deinit(catalog_t *cat) -{ - assert(cat->rw_txn == NULL); - if (cat->ro_txn != NULL) { - knot_lmdb_abort(cat->ro_txn); - free(cat->ro_txn); - } - if (cat->old_ro_txn != NULL) { - knot_lmdb_abort(cat->old_ro_txn); - free(cat->old_ro_txn); - } - knot_lmdb_deinit(&cat->db); - return KNOT_EOK; -} - -static int bailiwick_shift(const knot_dname_t *subname, const knot_dname_t *name) -{ - const knot_dname_t *res = subname; - while (!knot_dname_is_equal(res, name)) { - if (*res == '\0') { - return -1; - } - res = knot_wire_next_label(res, NULL); - } - return res - subname; -} - -int catalog_add(catalog_t *cat, const knot_dname_t *member, - const knot_dname_t *owner, const knot_dname_t *catzone) -{ - if (cat->rw_txn == NULL) { - return KNOT_EINVAL; - } - int bail = bailiwick_shift(owner, catzone); - if (bail < 0) { - return KNOT_EOUTOFZONE; - } - assert(bail >= 0 && bail < 256); - MDB_val key = knot_lmdb_make_key("BN", 0, member); // 0 for future purposes - MDB_val val = knot_lmdb_make_key("BBN", 0, bail, owner); - - knot_lmdb_insert(cat->rw_txn, &key, &val); - free(key.mv_data); - free(val.mv_data); - return cat->rw_txn->ret; -} - -int catalog_del(catalog_t *cat, const knot_dname_t *member) -{ - if (cat->rw_txn == NULL) { - return KNOT_EINVAL; - } - MDB_val key = knot_lmdb_make_key("BN", 0, member); - knot_lmdb_del_prefix(cat->rw_txn, &key); // deletes one record - free(key.mv_data); - return cat->rw_txn->ret; -} - -void catalog_curval(catalog_t *cat, const knot_dname_t **member, - const knot_dname_t **owner, const knot_dname_t **catzone) -{ - uint8_t zero, shift; - if (member != NULL) { - knot_lmdb_unmake_key(cat->ro_txn->cur_key.mv_data, cat->ro_txn->cur_key.mv_size, - "BN", &zero, member); - } - const knot_dname_t *ow; - knot_lmdb_unmake_curval(cat->ro_txn, "BBN", &zero, &shift, &ow); - if (owner != NULL) { - *owner = ow; - } - if (catzone != NULL) { - *catzone = ow + shift; - } -} - -static void catalog_curval2(MDB_val *key, MDB_val *val, const knot_dname_t **member, - const knot_dname_t **owner, const knot_dname_t **catzone) -{ - uint8_t zero, shift; - if (member != NULL) { - knot_lmdb_unmake_key(key->mv_data, key->mv_size, - "BN", &zero, member); - } - const knot_dname_t *ow; - knot_lmdb_unmake_key(val->mv_data, val->mv_size, "BBN", &zero, &shift, &ow); - if (owner != NULL) { - *owner = ow; - } - if (catzone != NULL) { - *catzone = ow + shift; - } -} - -int catalog_get_zone(catalog_t *cat, const knot_dname_t *member, - const knot_dname_t **catzone) -{ - if (cat->ro_txn == NULL) { - return KNOT_ENOENT; - } - - MDB_val key = knot_lmdb_make_key("BN", 0, member); - if (knot_lmdb_find(cat->ro_txn, &key, KNOT_LMDB_EXACT)) { - catalog_curval(cat, NULL, NULL, catzone); - free(key.mv_data); - return KNOT_EOK; - } - free(key.mv_data); - return MIN(cat->ro_txn->ret, KNOT_ENOENT); -} - -int catalog_get_zone_threadsafe(catalog_t *cat, const knot_dname_t *member, - knot_dname_storage_t catzone) -{ - if (cat->ro_txn == NULL) { - return KNOT_ENOENT; - } - - MDB_val key = knot_lmdb_make_key("BN", 0, member), val = { 0 }; - - int ret = knot_lmdb_find_threadsafe(cat->ro_txn, &key, &val, KNOT_LMDB_EXACT); - if (ret == KNOT_EOK) { - uint8_t zero, shift; - const knot_dname_t *ow = NULL; - knot_lmdb_unmake_key(val.mv_data, val.mv_size, "BBN", &zero, &shift, &ow); - if (knot_dname_store(catzone, ow + shift) == 0) { - ret = KNOT_EINVAL; - } - free(val.mv_data); - } - free(key.mv_data); - return ret; -} - -typedef struct { - const knot_dname_t *member; - const knot_dname_t *owner; - const knot_dname_t *catzone; - catalog_find_res_t ret; -} find_ctx_t; - -static int find_cb(MDB_val *key, MDB_val *val, void *fictx) -{ - const knot_dname_t *mem, *ow, *cz; - catalog_curval2(key, val, &mem, &ow, &cz); - find_ctx_t *ctx = fictx; - assert(knot_dname_is_equal(mem, ctx->member)); - if (!knot_dname_is_equal(cz, ctx->catzone)) { - ctx->ret = MEMBER_ZONE; - } else if (!knot_dname_is_equal(ow, ctx->owner)) { - ctx->ret = MEMBER_OWNER; - } else { - ctx->ret = MEMBER_EXACT; - } - return KNOT_EOK; -} - -catalog_find_res_t catalog_find(catalog_t *cat, const knot_dname_t *member, - const knot_dname_t *owner, const knot_dname_t *catzone) -{ - MDB_val key = knot_lmdb_make_key("BN", 0, member); - find_ctx_t ctx = { member, owner, catzone, MEMBER_NONE }; - int ret = knot_lmdb_apply_threadsafe(cat->ro_txn, &key, false, find_cb, &ctx); - free(key.mv_data); - switch (ret) { - case KNOT_EOK: - return ctx.ret; - case KNOT_ENOENT: - return MEMBER_NONE; - default: - return MEMBER_ERROR; - } -} - -inline static bool same_catalog(knot_lmdb_txn_t *txn, const knot_dname_t *catalog) -{ - if (catalog == NULL) { - return true; - } - const knot_dname_t *txn_cat = NULL; - catalog_curval2(&txn->cur_key, &txn->cur_val, NULL, NULL, &txn_cat); - return knot_dname_is_equal(txn_cat, catalog); -} - -int catalog_copy(knot_lmdb_db_t *from, knot_lmdb_db_t *to, - const knot_dname_t *zone_only, bool read_rw_txn) -{ - if (!knot_lmdb_exists(from)) { - return KNOT_EOK; - } - int ret = knot_lmdb_open(from); - if (ret == KNOT_EOK) { - ret = knot_lmdb_open(to); - } - if (ret != KNOT_EOK) { - return ret; - } - knot_lmdb_txn_t txn_r = { 0 }, txn_w = { 0 }; - knot_lmdb_begin(from, &txn_r, read_rw_txn); // using RW txn not to conflict with still-open RO txn - knot_lmdb_begin(to, &txn_w, true); - knot_lmdb_foreach(&txn_w, (MDB_val *)&catalog_iter_prefix) { - if (same_catalog(&txn_w, zone_only)) { - knot_lmdb_del_cur(&txn_w); - } - } - knot_lmdb_foreach(&txn_r, (MDB_val *)&catalog_iter_prefix) { - if (same_catalog(&txn_r, zone_only)) { - knot_lmdb_insert(&txn_w, &txn_r.cur_key, &txn_r.cur_val); - } - } - if (txn_r.ret != KNOT_EOK) { - knot_lmdb_abort(&txn_r); - knot_lmdb_abort(&txn_w); - return txn_r.ret; - } - knot_lmdb_commit(&txn_r); - knot_lmdb_commit(&txn_w); - return txn_w.ret; -} - -int catalog_update_init(catalog_update_t *u) -{ - u->upd = trie_create(NULL); - if (u->upd == NULL) { - return KNOT_ENOMEM; - } - pthread_mutex_init(&u->mutex, 0); - u->error = KNOT_EOK; - return KNOT_EOK; -} - -catalog_update_t *catalog_update_new() -{ - catalog_update_t *u = calloc(1, sizeof(*u)); - if (u != NULL) { - int ret = catalog_update_init(u); - if (ret != KNOT_EOK) { - free(u); - u = NULL; - } - } - return u; -} - -static int freecb(trie_val_t *tval, void *unused) -{ - catalog_upd_val_t *val = *tval; - if (val != NULL) { - freecb((void **)&val->counter, unused); - free(val); - } - return 0; -} - -void catalog_update_clear(catalog_update_t *u) -{ - trie_apply(u->upd, freecb, NULL); - trie_clear(u->upd); - u->error = KNOT_EOK; -} - -void catalog_update_deinit(catalog_update_t *u) -{ - pthread_mutex_destroy(&u->mutex); - trie_free(u->upd); -} - -void catalog_update_free(catalog_update_t *u) -{ - if (u != NULL) { - catalog_update_deinit(u); - free(u); - } -} - -static const knot_dname_t *get_uniq(const knot_dname_t *ptr_owner, - const knot_dname_t *catz) -{ - int labels = knot_dname_labels(ptr_owner, NULL); - labels -= knot_dname_labels(catz, NULL); - assert(labels >= 2); - return ptr_owner + knot_dname_prefixlen(ptr_owner, labels - 2, NULL); -} - -static bool same_uniq(const knot_dname_t *owner1, const knot_dname_t *catz1, - const knot_dname_t *owner2, const knot_dname_t *catz2) -{ - const knot_dname_t *uniq1 = get_uniq(owner1, catz1), *uniq2 = get_uniq(owner2, catz2); - if (*uniq1 != *uniq2) { - return false; - } - return memcmp(uniq1 + 1, uniq2 + 1, *uniq1) == 0; -} - -static catalog_upd_val_t *new_upd_val(const knot_dname_t *member, - const knot_dname_t *owner, - size_t bail, catalog_upd_type_t type, - catalog_upd_val_t *counter) -{ - size_t member_size = knot_dname_size(member); - size_t owner_size = knot_dname_size(owner); - assert(bail <= owner_size); - - catalog_upd_val_t *val = malloc(sizeof(*val) + member_size + owner_size); - if (val == NULL) { - return NULL; - } - val->member = (knot_dname_t *)(val + 1); - val->owner = val->member + member_size; - val->catzone = val->owner + bail; - memcpy(val->member, member, member_size); - memcpy(val->owner, owner, owner_size); - val->type = type; - val->counter = counter; - return val; -} - -int catalog_update_add(catalog_update_t *u, const knot_dname_t *member, - const knot_dname_t *owner, const knot_dname_t *catzone, - bool remove) -{ - int bail = bailiwick_shift(owner, catzone); - if (bail < 0) { - return KNOT_EOUTOFZONE; - } - assert(bail >= 0 && bail < 256); - - knot_dname_storage_t lf_storage; - uint8_t *lf = knot_dname_lf(member, lf_storage); - - catalog_upd_type_t type = remove ? MEMB_UPD_REM : MEMB_UPD_ADD; - catalog_upd_val_t *counter = NULL; - - trie_val_t *found = trie_get_try(u->upd, lf + 1, lf[0]); - if (found != NULL) { - counter = *found; - assert(knot_dname_is_equal(counter->member, member)); - switch (counter->type) { - case MEMB_UPD_ADD: - case MEMB_UPD_REM: - assert(counter->counter == NULL); - if (counter->type == type) { - return KNOT_ESEMCHECK; - } - if (knot_dname_is_equal(counter->owner, owner)) { // exact cancelout - assert(knot_dname_is_equal(counter->catzone, catzone)); - trie_del(u->upd, lf + 1, lf[0], NULL); - free(counter); - return KNOT_EOK; - } - bool suniq = same_uniq(owner, catzone, counter->owner, counter->catzone); - if (type == MEMB_UPD_REM) { - counter->type = suniq ? MEMB_UPD_MINOR : MEMB_UPD_UNIQ; - counter->counter = new_upd_val(member, owner, bail, type, NULL); - return counter->counter != NULL ? KNOT_EOK : KNOT_ENOMEM; - } - type = suniq ? MEMB_UPD_MINOR : MEMB_UPD_UNIQ; - *found = NULL; // counter will be attached to new val - break; - default: - return KNOT_ESEMCHECK; - } - } - - catalog_upd_val_t *val = new_upd_val(member, owner, bail, type, counter); - if (val == NULL) { - return KNOT_ENOMEM; - } - trie_val_t *added = trie_get_ins(u->upd, lf + 1, lf[0]); - if (added == NULL) { - free(val); - return KNOT_ENOMEM; - } - assert(*added == NULL); - *added = val; - return KNOT_EOK; -} - -catalog_upd_val_t *catalog_update_get(catalog_update_t *u, const knot_dname_t *member) -{ - knot_dname_storage_t lf_storage; - uint8_t *lf = knot_dname_lf(member, lf_storage); - - trie_val_t *found = trie_get_try(u->upd, lf + 1, lf[0]); - return found == NULL ? NULL : *(catalog_upd_val_t **)found; -} - -typedef struct { - catalog_update_t *u; - const knot_dname_t *apex; - bool remove; - catalog_t *check; -} cat_upd_ctx_t; - -static int cat_update_add_node(zone_node_t *node, void *data) -{ - cat_upd_ctx_t *ctx = data; - const knot_rdataset_t *ptr = node_rdataset(node, KNOT_RRTYPE_PTR); - if (ptr == NULL || ptr->count == 0) { - return KNOT_EOK; - } - knot_rdata_t *rdata = ptr->rdata; - int ret = KNOT_EOK; - for (int i = 0; ret == KNOT_EOK && i < ptr->count; i++) { - const knot_dname_t *member = knot_ptr_name(rdata); - if (ctx->check != NULL && ctx->remove && - catalog_find(ctx->check, member, node->owner, ctx->apex) != MEMBER_EXACT) { - rdata = knot_rdataset_next(rdata); - continue; - } - ret = catalog_update_add(ctx->u, member, node->owner, ctx->apex, ctx->remove); - rdata = knot_rdataset_next(rdata); - } - return ret; -} - -static size_t dname_append(knot_dname_storage_t storage, const knot_dname_t *name) -{ - size_t old_len = knot_dname_size(storage); - size_t name_len = knot_dname_size(name); - size_t new_len = old_len - 1 + name_len; - if (old_len == 0 || name_len == 0 || new_len > KNOT_DNAME_MAXLEN) { - return 0; - } - memcpy(storage + old_len - 1, name, name_len); - return new_len; -} - -int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone, - bool remove, bool check_ver, catalog_t *check) -{ - if (check_ver && !check_zone_version(zone)) { - return KNOT_EZONEINVAL; - } - - knot_dname_storage_t sub; - if (knot_dname_store(sub, (uint8_t *)CATALOG_ZONES_LABEL) == 0 || - dname_append(sub, zone->apex->owner ) == 0) { - return KNOT_EINVAL; - } - - if (zone_contents_find_node(zone, sub) == NULL) { - return KNOT_EOK; - } - - cat_upd_ctx_t ctx = { u, zone->apex->owner, remove, check }; - pthread_mutex_lock(&u->mutex); - int ret = zone_tree_sub_apply(zone->nodes, sub, false, cat_update_add_node, &ctx); - pthread_mutex_unlock(&u->mutex); - return ret; -} - -static void set_rdata(knot_rrset_t *rrset, uint8_t *data, uint16_t len) -{ - knot_rdata_init(rrset->rrs.rdata, len, data); - rrset->rrs.size = knot_rdata_size(len); -} - -struct zone_contents *catalog_update_to_zone(catalog_update_t *u, const knot_dname_t *catzone, - uint32_t soa_serial) -{ - if (u->error != KNOT_EOK) { - return NULL; - } - zone_contents_t *c = zone_contents_new(catzone, true); - if (c == NULL) { - return c; - } - - zone_node_t *unused = NULL; - uint8_t invalid[9] = "\x07""invalid"; - uint8_t version[9] = "\x07""version"; - uint8_t cat_version[2] = "\x01" CATALOG_ZONE_VERSION; - - // prepare common rrset with one rdata item - uint8_t rdata[256] = { 0 }; - knot_rrset_t rrset; - knot_rrset_init(&rrset, (knot_dname_t *)catzone, KNOT_RRTYPE_SOA, KNOT_CLASS_IN, 0); - rrset.rrs.rdata = (knot_rdata_t *)rdata; - rrset.rrs.count = 1; - - // set catalog zone's SOA - uint8_t data[250]; - assert(sizeof(knot_rdata_t) + sizeof(data) <= sizeof(rdata)); - wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); - wire_ctx_write(&wire, invalid, sizeof(invalid)); - wire_ctx_write(&wire, invalid, sizeof(invalid)); - wire_ctx_write_u32(&wire, soa_serial); - wire_ctx_write_u32(&wire, CATALOG_SOA_REFRESH); - wire_ctx_write_u32(&wire, CATALOG_SOA_RETRY); - wire_ctx_write_u32(&wire, CATALOG_SOA_EXPIRE); - wire_ctx_write_u32(&wire, 0); - set_rdata(&rrset, data, wire_ctx_offset(&wire)); - if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { - goto fail; - } - - // set catalog zone's NS - unused = NULL; - rrset.type = KNOT_RRTYPE_NS; - set_rdata(&rrset, invalid, sizeof(invalid)); - if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { - goto fail; - } - - // set catalog zone's version TXT - unused = NULL; - knot_dname_storage_t owner; - if (knot_dname_store(owner, version) == 0 || dname_append(owner, catzone) == 0) { - goto fail; - } - rrset.owner = owner; - rrset.type = KNOT_RRTYPE_TXT; - set_rdata(&rrset, cat_version, sizeof(cat_version)); - if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { - goto fail; - } - - // insert member zone PTR records - rrset.type = KNOT_RRTYPE_PTR; - catalog_it_t *it = catalog_it_begin(u); - while (!catalog_it_finished(it)) { - catalog_upd_val_t *val = catalog_it_val(it); - rrset.owner = val->owner; - set_rdata(&rrset, val->member, knot_dname_size(val->member)); - unused = NULL; - if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { - catalog_it_free(it); - goto fail; - } - catalog_it_next(it); - } - catalog_it_free(it); - - return c; - -fail: - zone_contents_deep_free(c); - return NULL; -} - -int catalog_update_to_update(catalog_update_t *u, struct zone_update *zu) -{ - knot_rrset_t ptr; - knot_rrset_init(&ptr, NULL, KNOT_RRTYPE_PTR, KNOT_CLASS_IN, 0); - uint8_t tmp[KNOT_DNAME_MAXLEN + sizeof(knot_rdata_t)]; - ptr.rrs.rdata = (knot_rdata_t *)tmp; - ptr.rrs.count = 1; - - int ret = u->error; - catalog_it_t *it = catalog_it_begin(u); - while (!catalog_it_finished(it) && ret == KNOT_EOK) { - catalog_upd_val_t *val = catalog_it_val(it); - bool same_cat = knot_dname_is_equal(zu->zone->name, val->catzone); - ptr.owner = val->owner; - set_rdata(&ptr, val->member, knot_dname_size(val->member)); - switch (val->type) { - case MEMB_UPD_ADD: - if (same_cat) { - ret = zone_update_add(zu, &ptr); - } - break; - case MEMB_UPD_REM: - if (same_cat) { - ret = zone_update_remove(zu, &ptr); - } - break; - case MEMB_UPD_MINOR: - case MEMB_UPD_UNIQ: - if (val->counter == NULL) { - ret = KNOT_ERROR; // some previous error - } else if (same_cat) { - ret = zone_update_add(zu, &ptr); - } - if (ret == KNOT_EOK && - knot_dname_is_equal(zu->zone->name, val->counter->catzone)) { - ptr.owner = val->counter->owner; - ret = zone_update_remove(zu, &ptr); - } - break; - default: - ret = KNOT_EINVAL; - } - catalog_it_next(it); - } - catalog_it_free(it); - return ret; -} - -typedef struct { - const knot_dname_t *zone; - catalog_update_t *u; -} del_all_ctx_t; - -static int del_all_cb(MDB_val *key, MDB_val *val, void *dactx) -{ - const knot_dname_t *mem, *ow, *cz; - catalog_curval2(key, val, &mem, &ow, &cz); - del_all_ctx_t *ctx = dactx; - if (knot_dname_is_equal(cz, ctx->zone)) { - // TODO possible speedup by indexing which member zones belong to a catalog zone - return catalog_update_add(ctx->u, mem, ow, cz, true); - } else { - return KNOT_EOK; - } -} - -int catalog_update_del_all(catalog_update_t *u, catalog_t *cat, const knot_dname_t *zone) -{ - int ret = catalog_open(cat); - if (ret != KNOT_EOK) { - return ret; - } - - pthread_mutex_lock(&u->mutex); - del_all_ctx_t ctx = { zone, u }; - ret = knot_lmdb_apply_threadsafe(cat->ro_txn, &catalog_iter_prefix, true, del_all_cb, &ctx); - pthread_mutex_unlock(&u->mutex); - return ret; -} - -static void print_dname(const knot_dname_t *d) -{ - knot_dname_txt_storage_t tmp; - knot_dname_to_str(tmp, d, sizeof(tmp)); - printf("%s ", tmp); -} - -static void print_dname3(const char *prefix, const knot_dname_t *a, const knot_dname_t *b, - const knot_dname_t *c) -{ - printf("%s", prefix); - print_dname(a); - print_dname(b); - print_dname(c); -} - -void catalog_print(catalog_t *cat) -{ - ssize_t total = 0; - - printf(";; <catalog zone> <record owner> <record zone>\n"); - - if (cat != NULL) { - int ret = catalog_open(cat); - if (ret != KNOT_EOK) { - printf("Catalog print failed (%s)\n", knot_strerror(ret)); - return; - } - - catalog_foreach(cat) { - const knot_dname_t *mem, *ow, *cz; - catalog_curval(cat, &mem, &ow, &cz); - print_dname3("", mem, ow, cz); - total++; - } - } - - printf("Total zones: %zd\n", total); -} - -void catalog_update_print(catalog_update_t *u) -{ - const static char* sign[MEMB_UPD_MAX] = { "! ", "+ ", "- ", "* ", "# " }; - ssize_t counts[MEMB_UPD_MAX] = { 0 }; - - printf(";; <catalog zone> <record owner> <record zone>\n"); - - if (u != NULL) { - catalog_it_t *it = catalog_it_begin(u); - while (!catalog_it_finished(it)) { - catalog_upd_val_t *val = catalog_it_val(it); - print_dname3(sign[val->type], val->member, val->owner, val->catzone); - counts[val->type]++; - catalog_it_next(it); - } - catalog_it_free(it); - } - - printf("Total changes:"); - for (int i = 1; i < MEMB_UPD_MAX; i++) { - printf(" %s%zd", sign[i], counts[i]); - } - printf("\n"); -} diff --git a/src/knot/zone/catalog.h b/src/knot/zone/catalog.h deleted file mode 100644 index 22db6d5362..0000000000 --- a/src/knot/zone/catalog.h +++ /dev/null @@ -1,369 +0,0 @@ -/* 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/>. - */ - -#pragma once - -#include <pthread.h> - -#include "libknot/libknot.h" -#include "contrib/qp-trie/trie.h" -#include "knot/journal/knot_lmdb.h" - -typedef struct catalog { - knot_lmdb_db_t db; - knot_lmdb_txn_t *ro_txn; // persistent RO transaction - knot_lmdb_txn_t *rw_txn; // temporary RW transaction - - // private - knot_lmdb_txn_t *old_ro_txn; -} catalog_t; - -typedef enum { - MEMBER_NONE, // this member zone is not in any catalog - MEMBER_EXACT, // this member zone precisely matches lookup - MEMBER_ZONE, // this member zone is in different catalog - MEMBER_OWNER, // this member zone is in same catalog with diferent owner - MEMBER_ERROR, // find error code in cat->txn.ret -} catalog_find_res_t; - -typedef struct { - trie_t *upd; // tree of catalog_upd_val_t, that gonna be changed in catalog - int error; // error occured during generating of upd - pthread_mutex_t mutex; // lock for accessing this struct -} catalog_update_t; - -typedef enum { - MEMB_UPD_INVALID, // invalid value - MEMB_UPD_ADD, // member addition - MEMB_UPD_REM, // member removal - MEMB_UPD_MINOR, // owner or catzone change, uniqID preserved - MEMB_UPD_UNIQ, // uniqID change - MEMB_UPD_MAX, // number of options in ths enum -} catalog_upd_type_t; - -typedef struct catalog_upd_val { - knot_dname_t *member; // name of catalog member zone - knot_dname_t *owner; // the owner of PTR record defining the member zone - knot_dname_t *catzone; // the catalog zone the PTR is in - catalog_upd_type_t type; // what kind of update this is - struct catalog_upd_val *counter; // original owner/catzone before this update -} catalog_upd_val_t; - -extern const MDB_val catalog_iter_prefix; - -/*! - * \brief Generate owner name for catalog PTR record. - * - * \param member Name of the member zone respective to the PTR record. - * \param catzone Catalog zone name to contain the PTR. - * \param member_time Timestamp of member zone addition. - * - * \return Owner name or NULL on error (e.g. ENOMEM, too long result...). - * - * \note Don't forget to free the return value later. - */ -knot_dname_t *catalog_member_owner(const knot_dname_t *member, - const knot_dname_t *catzone, - time_t member_time); - -/*! - * \brief Initialize catalog structure. - * - * \param cat Catalog structure. - * \param path Path to LMDB for catalog. - * \param mapsize Mapsize of the LMDB. - */ -void catalog_init(catalog_t *cat, const char *path, size_t mapsize); - -/*! - * \brief Open the catalog LMDB, create it if not exists. - * - * \param cat Catlog to be opened. - * - * \return KNOT_E* - */ -int catalog_open(catalog_t *cat); - -/*! - * \brief Start a temporary RW transaction in the catalog. - * - * \param cat Catalog in question. - * - * \return KNOT_E* - */ -int catalog_begin(catalog_t *cat); - -/*! - * \brief End using the temporary RW txn, refresh the persistent RO txn. - * - * \param cat Catalog in question. - * - * \return KNOT_E* - */ -int catalog_commit(catalog_t *cat); - -/*! - * \brief Free up old txns. - * - * \note This must be called after catalog_commit() with a delay of synchronnize_rcu(). - * - * \param cat Catalog. - */ -void catalog_commit_cleanup(catalog_t *cat); - -/*! - * \brief Close the catalog and de-init the structure. - * - * \param cat Catalog to be closed. - * - * \return KNOT_E* - */ -int catalog_deinit(catalog_t *cat); - -/*! - * \brief Add a member zone to the catalog database. - * - * \param cat Catalog to be augmented. - * \param member Member zone name. - * \param owner Owner of the PTR record in catalog zone, respective to the member zone. - * \param catzone Name of the catalog zone whose it's the member. - * - * \return KNOT_E* - */ -int catalog_add(catalog_t *cat, const knot_dname_t *member, - const knot_dname_t *owner, const knot_dname_t *catzone); - -inline static int catalog_add2(catalog_t *cat, const catalog_upd_val_t *val) -{ - return catalog_add(cat, val->member, val->owner, val->catzone); -} - -/*! - * \brief Delete a member zone from the catalog database. - * - * \param cat Catalog to be removed from. - * \param member Member zone to be removed. - * - * \return KNOT_E* - */ -int catalog_del(catalog_t *cat, const knot_dname_t *member); - -inline static int catalog_del2(catalog_t *cat, const catalog_upd_val_t *val) -{ - assert(val->type != MEMB_UPD_ADD); - return catalog_del(cat, val->member); -} - -#define catalog_foreach(cat) knot_lmdb_foreach((cat)->ro_txn, (MDB_val *)&catalog_iter_prefix) - -/*! - * \brief Deserialize a value in catalog database. - * - * \param cat Catalog with cat->txn->cur_val to be deserialized. - * \param member Output: member zone. - * \param owner Output: PTR owner. - * \param catzone Output: catalog zone. - */ -void catalog_curval(catalog_t *cat, const knot_dname_t **member, - const knot_dname_t **owner, const knot_dname_t **catzone); - -/*! - * \brief Get the catalog zone for known member zone. - * - * \param cat Catalog database. - * \param member Member zone name. - * \param catzone Catalog zone holding the member zone. - * - * \return KNOT_E* - */ -int catalog_get_zone(catalog_t *cat, const knot_dname_t *member, - const knot_dname_t **catzone); - -/*! - * \brief Get the catalog zone for known member zone. - * - * \note This function is safe for multithreaded operation over shared LMDB transaction. - * - * \param cat Catalog database. - * \param member Member zone name. - * \param catzone Catalog zone holding the member zone. - * - * \return KNOT_E* - */ -int catalog_get_zone_threadsafe(catalog_t *cat, const knot_dname_t *member, - knot_dname_storage_t catzone); - -/*! - * \brief Find specific member record in catalog database. - * - * \param cat Catalog database. - * \param member Member zone to be searched for. - * \param owner Owner to be searched/verified. - * \param catzone Catalog zone to be searched/verified. - * - * \return see catalog_find_res_t - */ -catalog_find_res_t catalog_find(catalog_t *cat, const knot_dname_t *member, - const knot_dname_t *owner, const knot_dname_t *catzone); - -/*! - * \brief Copy records from one catalog database to other. - * - * \param from Catalog DB to copy from. - * \param to Catalog db to copy to. - * \param zone_only Optional: copy only records for this catalog zone. - * \param read_rw_txn Use RW txn for read operations. - * - * \return KNOT_E* - */ -int catalog_copy(knot_lmdb_db_t *from, knot_lmdb_db_t *to, - const knot_dname_t *zone_only, bool read_rw_txn); - -/*! - * \brief Initialize catalog update structure. - * - * \param u Catalog update to be initialized. - * - * \return KNOT_EOK, KNOT_ENOMEM - */ -int catalog_update_init(catalog_update_t *u); -catalog_update_t *catalog_update_new(void); - -/*! - * \brief Clear contents of catalog update structure. - * - * \param u Catalog update structure to be cleared. - */ -void catalog_update_clear(catalog_update_t *u); - -/*! - * \brief Free catalog update structure. - * - * \param u Catalog update structure. - */ -void catalog_update_deinit(catalog_update_t *u); -void catalog_update_free(catalog_update_t *u); - -/*! - * \brief Add a new record to catalog update structure. - * - * \param u Catalog update. - * \param member Member zone name to be added. - * \param owner Owner of respective PTR record. - * \param catzone Catalog zone holding the member. - * \param remove Add a removal of such record. - * - * \return KNOT_E* - */ -int catalog_update_add(catalog_update_t *u, const knot_dname_t *member, - const knot_dname_t *owner, const knot_dname_t *catzone, - bool remove); - -/*! - * \brief Read catalog update record for given member zone. - * - * \param u Catalog update. - * \param member Member zone name. - * \param remove Search in remove section. - * - * \return Found update record for given member zone; or NULL. - */ -catalog_upd_val_t *catalog_update_get(catalog_update_t *u, const knot_dname_t *member); - -struct zone_contents; - -/*! - * \brief Iterate over PTR records in given zone contents and add members to catalog update. - * - * \param u Catalog update to be updated. - * \param zone Zone contents to be searched for member PTR records. - * \param remove Add removals of found member zones. - * \param check_ver Do check catalog zone version record first. - * \param check Optional: existing catalog database to be checked for existence of such record (useful for removals). - * - * \return KNOT_E* - */ -int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone, - bool remove, bool check_ver, catalog_t *check); - -/*! - * \brief Generate catalog zone contents from (full) catalog update. - * - * \param u Catalog update to read. - * \param catzone Catalog zone name. - * \param soa_serial SOA serial of the generated zone. - * - * \return Catalog zone contents, or NULL if ENOMEM. - */ -struct zone_contents *catalog_update_to_zone(catalog_update_t *u, const knot_dname_t *catzone, - uint32_t soa_serial); - -struct zone_update; - -/*! - * \brief Incrementally update catalog zone from catalog update. - * - * \param u Catalog update to read. - * \param zu Zone update to be updated. - * - * \return KNOT_E* - */ -int catalog_update_to_update(catalog_update_t *u, struct zone_update *zu); - -/*! - * \brief Add to catalog update removals of all member zones of a single catalog zone. - * - * \param u Catalog updat to be updated. - * \param cat Catalog database to be iterated. - * \param zone Name of catalog zone whose members gonna be removed. - * - * \return KNOT_E* - */ -int catalog_update_del_all(catalog_update_t *u, catalog_t *cat, const knot_dname_t *zone); - -typedef trie_it_t catalog_it_t; - -inline static catalog_it_t *catalog_it_begin(catalog_update_t *u) -{ - return trie_it_begin(u->upd); -} - -inline static catalog_upd_val_t *catalog_it_val(catalog_it_t *it) -{ - return *(catalog_upd_val_t **)trie_it_val(it); -} - -inline static bool catalog_it_finished(catalog_it_t *it) -{ - return it == NULL || trie_it_finished(it); -} - -#define catalog_it_next trie_it_next -#define catalog_it_free trie_it_free - -/*! - * \brief Print to stdout whole contents of catalog database (for human). - * - * \param cat Catalog database to be printed. - */ -void catalog_print(catalog_t *cat); - -/*! - * \brief Print to stdout whole contents of catalog update (for human). - * - * \param u Catalog update to be printed. - */ -void catalog_update_print(catalog_update_t *u); diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h index 0ae65a504b..84eb706d69 100644 --- a/src/knot/zone/zone.h +++ b/src/knot/zone/zone.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* 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 @@ -17,12 +17,12 @@ #pragma once #include "contrib/semaphore.h" +#include "knot/catalog/catalog_update.h" #include "knot/conf/conf.h" #include "knot/conf/confio.h" #include "knot/journal/journal_basic.h" #include "knot/events/events.h" #include "knot/updates/changesets.h" -#include "knot/zone/catalog.h" #include "knot/zone/contents.h" #include "knot/zone/timers.h" #include "libknot/dname.h" diff --git a/src/knot/zone/zonedb-load.c b/src/knot/zone/zonedb-load.c index 6e85213274..865e505de7 100644 --- a/src/knot/zone/zonedb-load.c +++ b/src/knot/zone/zonedb-load.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* 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 @@ -18,11 +18,11 @@ #include <unistd.h> #include <urcu.h> +#include "knot/catalog/generate.h" #include "knot/common/log.h" #include "knot/conf/module.h" #include "knot/events/replan.h" #include "knot/journal/journal_metadata.h" -#include "knot/zone/catalog.h" #include "knot/zone/timers.h" #include "knot/zone/zone-load.h" #include "knot/zone/zone.h" @@ -76,65 +76,6 @@ static zone_t *create_zone_from(const knot_dname_t *name, server_t *server) return zone; } -static void catalogs_generate(knot_zonedb_t *db_new, knot_zonedb_t *db_old) -{ - // general comment: catz->contents!=NULL means incremental update of catalog - - if (db_old != NULL) { - knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_old); - while (!knot_zonedb_iter_finished(it)) { - zone_t *zone = knot_zonedb_iter_val(it); - knot_dname_t *cg = zone->catalog_gen; - if (cg != NULL && knot_zonedb_find(db_new, zone->name) == NULL) { - zone_t *catz = knot_zonedb_find(db_new, cg); - if (catz != NULL && catz->contents != NULL) { - assert(catz->cat_members != NULL); // if this failed to allocate, catz wasn't added to zonedb - knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member); - if (owner == NULL) { - catz->cat_members->error = KNOT_ENOENT; - knot_zonedb_iter_next(it); - continue; - } - int ret = catalog_update_add(catz->cat_members, zone->name, owner, cg, true); - free(owner); - if (ret != KNOT_EOK) { - catz->cat_members->error = ret; - } - } - } - knot_zonedb_iter_next(it); - } - knot_zonedb_iter_free(it); - } - - knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_new); - while (!knot_zonedb_iter_finished(it)) { - zone_t *zone = knot_zonedb_iter_val(it); - knot_dname_t *cg = zone->catalog_gen; - zone_t *catz = cg != NULL ? knot_zonedb_find(db_new, cg) : NULL; - if (cg != NULL && catz == NULL) { - log_zone_warning(zone->name, "member zone belongs to non-existing catalog zone"); - continue; - } - if (cg != NULL && (catz->contents == NULL || knot_zonedb_find(db_old, zone->name) == NULL)) { - assert(catz->cat_members != NULL); - knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member); - if (owner == NULL) { - catz->cat_members->error = KNOT_ENOENT; - knot_zonedb_iter_next(it); - continue; - } - int ret = catalog_update_add(catz->cat_members, zone->name, owner, cg, false); - free(owner); - if (ret != KNOT_EOK) { - catz->cat_members->error = ret; - } - } - knot_zonedb_iter_next(it); - } - knot_zonedb_iter_free(it); -} - static zone_t *create_zone_reload(conf_t *conf, const knot_dname_t *name, server_t *server, zone_t *old_zone) { @@ -301,7 +242,7 @@ static bool check_open_catalog(catalog_t *cat) if (knot_lmdb_exists(&cat->db)) { int ret = catalog_open(cat); if (ret != KNOT_EOK) { - log_error("failed to open existing zone catalog"); + log_error("failed to open persistent zone catalog"); } else { return true; } @@ -319,13 +260,13 @@ static zone_t *reuse_member_zone(zone_t *zone, server_t *server, conf_t *conf, catalog_upd_val_t *upd = catalog_update_get(&server->catalog_upd, zone->name); if (upd != NULL) { switch (upd->type) { - case MEMB_UPD_UNIQ: + case CAT_UPD_UNIQ: zone_purge(conf, zone, server); knot_sem_wait(&zone->cow_lock); ptrlist_add(expired_contents, zone_expire(zone), NULL); knot_sem_post(&zone->cow_lock); break; - case MEMB_UPD_REM: + case CAT_UPD_REM: return NULL; // zone to be removed default: break; @@ -347,7 +288,7 @@ static zone_t *reuse_member_zone(zone_t *zone, server_t *server, conf_t *conf, static zone_t *reuse_cold_zone(const knot_dname_t *zname, server_t *server, conf_t *conf) { catalog_upd_val_t *upd = catalog_update_get(&server->catalog_upd, zname); - if (upd != NULL && upd->type == MEMB_UPD_REM) { + if (upd != NULL && upd->type == CAT_UPD_REM) { return NULL; // zone will be removed immediately } @@ -362,10 +303,30 @@ static zone_t *reuse_cold_zone(const knot_dname_t *zname, server_t *server, conf return zone; } +typedef struct { + knot_zonedb_t *zonedb; + server_t *server; + conf_t *conf; +} reuse_cold_zone_ctx_t; + +static int reuse_cold_zone_cb(const knot_dname_t *member, const knot_dname_t *owner, + const knot_dname_t *catz, void *ctx) +{ + UNUSED(owner); + UNUSED(catz); + reuse_cold_zone_ctx_t *rcz = ctx; + + zone_t *zone = reuse_cold_zone(member, rcz->server, rcz->conf); + if (zone == NULL) { + return KNOT_ENOMEM; + } + return knot_zonedb_insert(rcz->zonedb, zone); +} + static zone_t *add_member_zone(catalog_upd_val_t *val, knot_zonedb_t *check, server_t *server, conf_t *conf) { - if (val->type != MEMB_UPD_ADD) { + if (val->type != CAT_UPD_ADD) { return NULL; } @@ -377,7 +338,6 @@ static zone_t *add_member_zone(catalog_upd_val_t *val, knot_zonedb_t *check, zone_t *zone = create_zone(conf, val->member, server, NULL); if (zone == NULL) { log_zone_error(val->member, "zone cannot be created"); - catalog_del2(conf->catalog, val); } else { zone_set_flag(zone, ZONE_IS_CAT_MEMBER); conf_activate_modules(conf, server, zone->name, &zone->query_modules, @@ -456,60 +416,29 @@ static knot_zonedb_t *create_zonedb(conf_t *conf, server_t *server, list_t *expi } knot_zonedb_iter_free(it); } else if (check_open_catalog(&server->catalog)) { - catalog_foreach(&server->catalog) { - const knot_dname_t *member = NULL, *catzone = NULL; - catalog_curval(&server->catalog, &member, NULL, &catzone); - - if (!conf_rawid_exists(conf, C_ZONE, catzone, knot_dname_size(catzone))) { - knot_dname_txt_storage_t cat_str; - (void)knot_dname_to_str(cat_str, catzone, sizeof(cat_str)); - log_zone_error(member, "catalog template zone '%s' not configured, ignoring", cat_str); - continue; - } else if (conf_rawid_exists(conf, C_ZONE, member, knot_dname_size(member))) { - log_zone_error(member, "non-catalog zone already configured, ignoring"); - continue; - } - - zone_t *zone = reuse_cold_zone(member, server, conf); - if (zone != NULL) { - knot_zonedb_insert(db_new, zone); - } + reuse_cold_zone_ctx_t rcz = { db_new, server, conf }; + int ret = catalog_apply(&server->catalog, NULL, reuse_cold_zone_cb, &rcz, false); + if (ret != KNOT_EOK) { + log_error("catalog, failed to reload member zones (%s)", knot_strerror(ret)); } } catalog_commit_cleanup(&server->catalog); - catalog_it_t *it = catalog_it_begin(&server->catalog_upd); - int catret = 1; - if (!catalog_it_finished(it)) { - catret = catalog_begin(&server->catalog); - } - while (!catalog_it_finished(it) && catret == KNOT_EOK) { - catalog_upd_val_t *val = catalog_it_val(it); - if (val->type == MEMB_UPD_UNIQ || val->type == MEMB_UPD_MINOR || - knot_zonedb_find(db_new, val->member) == NULL) { - // ^ warning for existing zone later in add_member_zone() - catret = catalog_add2(&server->catalog, val); - } - catalog_it_next(it); - } - catalog_it_free(it); - if (catret == KNOT_EOK) { - catret = catalog_commit(&server->catalog); - } - - it = catalog_it_begin(&server->catalog_upd); - while (!catalog_it_finished(it) && catret == KNOT_EOK) { - catalog_upd_val_t *val = catalog_it_val(it); - zone_t *zone = add_member_zone(val, db_new, server, conf); - if (zone != NULL) { - knot_zonedb_insert(db_new, zone); + int ret = catalog_update_commit(&server->catalog_upd, &server->catalog); + if (ret == KNOT_EOK) { + catalog_it_t *it = catalog_it_begin(&server->catalog_upd); + while (!catalog_it_finished(it)) { + catalog_upd_val_t *val = catalog_it_val(it); + zone_t *zone = add_member_zone(val, db_new, server, conf); + if (zone != NULL) { + knot_zonedb_insert(db_new, zone); + } + catalog_it_next(it); } - catalog_it_next(it); - } - catalog_it_free(it); - if (catret < 0) { - log_error("failed to process zone catalog (%s)", knot_strerror(catret)); + catalog_it_free(it); + } else { + log_error("catalog, failed to apply changes (%s)", knot_strerror(ret)); } return db_new; @@ -566,14 +495,9 @@ static void remove_old_zonedb(conf_t *conf, knot_zonedb_t *db_old, catalog_only: ; /* Remove deleted cataloged zones from conf. */ catalog_it_t *cat_it = catalog_it_begin(&server->catalog_upd); - int catret = 1; - if (!catalog_it_finished(cat_it)) { - catret = catalog_begin(&server->catalog); - } while (!catalog_it_finished(cat_it)) { catalog_upd_val_t *upd = catalog_it_val(cat_it); - if (upd->type == MEMB_UPD_REM) { - catalog_del(&server->catalog, upd->member); + if (upd->type == CAT_UPD_REM) { zone_t *zone = knot_zonedb_find(db_old, upd->member); if (zone != NULL) { zone_purge(conf, zone, server); @@ -582,12 +506,6 @@ catalog_only: catalog_it_next(cat_it); } catalog_it_free(cat_it); - if (catret == KNOT_EOK) { - catret = catalog_commit(&server->catalog); - } - if (catret < 0) { - log_error("failed to process zone catalog (%s)", knot_strerror(catret)); - } /* Clear catalog changes. No need to use mutex as this is done from main * thread while all zone events are paused. */ @@ -609,6 +527,8 @@ void zonedb_reload(conf_t *conf, server_t *server) list_t contents_tofree; init_list(&contents_tofree); + catalog_update_finalize(&server->catalog_upd, &server->catalog); + /* Insert all required zones to the new zone DB. */ knot_zonedb_t *db_new = create_zonedb(conf, server, &contents_tofree); if (db_new == NULL) { diff --git a/src/knot/zone/zonedb.c b/src/knot/zone/zonedb.c index b6c751381b..98cade558d 100644 --- a/src/knot/zone/zonedb.c +++ b/src/knot/zone/zonedb.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* 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 @@ -26,11 +26,9 @@ /*! \brief Discard zone in zone database. */ static void discard_zone(zone_t *zone, bool abort_txn) { - const knot_dname_t *unused = NULL; - // Don't flush if removed zone (no previous configuration available). if (conf_rawid_exists(conf(), C_ZONE, zone->name, knot_dname_size(zone->name)) || - catalog_get_zone(conf()->catalog, zone->name, &unused) == KNOT_EOK) { + catalog_has_member(conf()->catalog, zone->name)) { uint32_t journal_serial, zone_serial = zone_contents_serial(zone->contents); bool exists; diff --git a/src/utils/kcatalogprint/main.c b/src/utils/kcatalogprint/main.c index 76f4d61724..b46813c4a0 100644 --- a/src/utils/kcatalogprint/main.c +++ b/src/utils/kcatalogprint/main.c @@ -18,7 +18,7 @@ #include <stdlib.h> #include <string.h> -#include "knot/zone/catalog.h" +#include "knot/catalog/catalog_db.h" #include "utils/common/params.h" #define PROGRAM_NAME "kcatalogprint" diff --git a/tests-extra/tests/zone/catalog/data/catalog1.zone b/tests-extra/tests/catalog/basic/data/catalog1.zone similarity index 100% rename from tests-extra/tests/zone/catalog/data/catalog1.zone rename to tests-extra/tests/catalog/basic/data/catalog1.zone diff --git a/tests-extra/tests/zone/catalog/data/cataloged1.zone b/tests-extra/tests/catalog/basic/data/cataloged1.zone similarity index 100% rename from tests-extra/tests/zone/catalog/data/cataloged1.zone rename to tests-extra/tests/catalog/basic/data/cataloged1.zone diff --git a/tests-extra/tests/zone/catalog/data/cataloged2.zone b/tests-extra/tests/catalog/basic/data/cataloged2.zone similarity index 100% rename from tests-extra/tests/zone/catalog/data/cataloged2.zone rename to tests-extra/tests/catalog/basic/data/cataloged2.zone diff --git a/tests-extra/tests/zone/catalog/test.py b/tests-extra/tests/catalog/basic/test.py similarity index 100% rename from tests-extra/tests/zone/catalog/test.py rename to tests-extra/tests/catalog/basic/test.py diff --git a/tests-extra/tests/zone/catalog_generate/test.py b/tests-extra/tests/catalog/generate/test.py similarity index 100% rename from tests-extra/tests/zone/catalog_generate/test.py rename to tests-extra/tests/catalog/generate/test.py diff --git a/tests-extra/tests/catalog/many_zones/test.py b/tests-extra/tests/catalog/many_zones/test.py new file mode 100644 index 0000000000..69fa5dcecc --- /dev/null +++ b/tests-extra/tests/catalog/many_zones/test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +'''Test of handling large catalog with changes.''' + +from dnstest.test import Test +from dnstest.utils import set_err, detail_log +import os +import random +import time + +UPDATES = 5 +ADD_ZONES = 11 +REM_ZONES = 7 +DNSSEC = True + +t = Test(stress=False) + +master = t.server("knot") +slave = t.server("knot") + +catz = t.zone("example.") + +t.link(catz, master, slave) + +cz = master.zones[catz[0].name] +cz.catalog_gen_link(cz) # empty catz with "generate" role + +slave.zones[catz[0].name].catalog = True +slave.dnssec(catz[0]).enable = DNSSEC +slave.dnssec(catz[0]).alg = "ECDSAP256SHA256" +slave.zones[catz[0].name].journal_content = "all" +slave.journal_db_size = 200 * 1024 * 1024 + +t.start() + +slave.zone_wait(catz, udp=False, tsig=True) + +for i in range(UPDATES): + zone_add = t.zone_rnd(ADD_ZONES, records=5, dnssec=False) + t.link(zone_add, master) + for z in zone_add: + master.zones[z.name].catalog_gen_link(master.zones[catz[0].name]) + master.gen_confile() + master.reload() + slave.zones_wait(zone_add) + + zone_rem = [] + REM_PERCENT = REM_ZONES * 100 / len(master.zones) + 1 + for z in master.zones: + if z != catz[0].name and random.random() * 100 < REM_PERCENT: + zone_rem.append(z) + serial_bef_rem = slave.zone_wait(catz, udp=False, tsig=True) + for z in zone_rem: + master.zones.pop(z) + master.gen_confile() + master.reload() + slave.zone_wait(catz, serial_bef_rem, udp=False, tsig=True) + t.sleep(5) + for z in zone_rem: + resp = slave.dig(z, "SOA") + if resp.count("SOA") > 0: + # allowed: REFUSED (zone not exists) + # NXDOMAIN (in bailiwick of another existing zone) + # NODATA (ditto) + # not allowed: NOERROR+data (zone exists with this name) + resp.check(rcode="REFUSED") + +t.end() diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py index 674bf475c3..1b858825db 100644 --- a/tests-extra/tools/dnstest/server.py +++ b/tests-extra/tools/dnstest/server.py @@ -1404,6 +1404,7 @@ class Knot(Server): s.id_item("id", "catemplate") s.item_str("file", self.dir + "/master/%s.zone") s.item_str("zonefile-load", "difference") + s.item_str("journal-content", z.journal_content) # this is weird but for the sake of testing, the cataloged zones inherit dnssec policy from catalog zone if z.dnssec.enable: -- GitLab