diff options
Diffstat (limited to 'src/knot/catalog')
-rw-r--r-- | src/knot/catalog/catalog_db.c | 347 | ||||
-rw-r--r-- | src/knot/catalog/catalog_db.h | 187 | ||||
-rw-r--r-- | src/knot/catalog/catalog_update.c | 407 | ||||
-rw-r--r-- | src/knot/catalog/catalog_update.h | 171 | ||||
-rw-r--r-- | src/knot/catalog/generate.c | 346 | ||||
-rw-r--r-- | src/knot/catalog/generate.h | 56 | ||||
-rw-r--r-- | src/knot/catalog/interpret.c | 257 | ||||
-rw-r--r-- | src/knot/catalog/interpret.h | 52 |
8 files changed, 1823 insertions, 0 deletions
diff --git a/src/knot/catalog/catalog_db.c b/src/knot/catalog/catalog_db.c new file mode 100644 index 0000000..b483f4d --- /dev/null +++ b/src/knot/catalog/catalog_db.c @@ -0,0 +1,347 @@ +/* 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 <sys/stat.h> +#include <urcu.h> + +#include "contrib/files.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); +} + +static void ensure_cat_version(knot_lmdb_txn_t *ro_txn, knot_lmdb_txn_t *rw_txn) +{ + MDB_val key = { 8, "\x01version" }; + if (knot_lmdb_find(ro_txn, &key, KNOT_LMDB_EXACT)) { + if (strncmp(CATALOG_VERSION, ro_txn->cur_val.mv_data, + ro_txn->cur_val.mv_size) != 0) { + log_warning("catalog version mismatch"); + } + } else if (rw_txn != NULL) { + MDB_val val = { strlen(CATALOG_VERSION), CATALOG_VERSION }; + knot_lmdb_insert(rw_txn, &key, &val); + } +} + +// 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) { + ensure_cat_version(cat->ro_txn, cat->rw_txn); + } +} + +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); + } +} + +void 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); +} + +int catalog_add(catalog_t *cat, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catzone, + const char *group) +{ + 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("BBNS", 0, bail, owner, group); + + 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, const char **group) +{ + uint8_t zero, shift; + *group = ""; // backward compatibility with Knot 3.0 + knot_lmdb_unmake_key(val->mv_data, val->mv_size, "BBNS", &zero, &shift, + owner, group); + *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, + const char **group, 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, group); + *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, const char **group, void **tofree) +{ + const knot_dname_t *unused; + return find_threadsafe(cat, member, &unused, catz, group, tofree); +} + +bool catalog_has_member(catalog_t *cat, const knot_dname_t *member) +{ + const knot_dname_t *catz; + const char *group; + void *tofree = NULL; + int ret = catalog_get_catz(cat, member, &catz, &group, &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; + const char *found_group; + void *tofree = NULL; + int ret = find_threadsafe(cat, member, &found_owner, &found_catz, &found_group, &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; + const char *gr = NULL; + knot_lmdb_unmake_key(key->mv_data, key->mv_size, "BN", &zero, &mem); + unmake_val(val, &ow, &cz, &gr); + if (mem == NULL || ow == NULL || cz == NULL) { + return KNOT_EMALF; + } + return iter_ctx->cb(mem, ow, cz, gr, 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; + const char *grunused; + unmake_val(&txn->cur_val, &unused, &txn_cat, &grunused); + 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) == KNOT_ENODB) { + return KNOT_EOK; + } + int ret = knot_lmdb_open(from); + if (ret == KNOT_EOK) { + ret = make_path(to->path, S_IRWXU | S_IRWXG); + 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); + } + } + ensure_cat_version(&txn_w, &txn_w); + 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; +} diff --git a/src/knot/catalog/catalog_db.h b/src/knot/catalog/catalog_db.h new file mode 100644 index 0000000..d0abd3b --- /dev/null +++ b/src/knot/catalog/catalog_db.h @@ -0,0 +1,187 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#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" +#define CATALOG_GROUP_LABEL "\x05""group" +#define CATALOG_GROUP_MAXLEN 255 + +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 synchronize_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. + */ +void 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. + * \param group Configuration group of 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, + const char *group); + +/*! + * \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 database. + * \param member Member to search for. + * \param catz Out: name of catalog zone it resides in. + * \param group Out: configuration group the member 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, const char **group, 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, const char *group, 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 *cat_only, bool read_rw_txn); diff --git a/src/knot/catalog/catalog_update.c b/src/knot/catalog/catalog_update.c new file mode 100644 index 0000000..50f38cb --- /dev/null +++ b/src/knot/catalog/catalog_update.c @@ -0,0 +1,407 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "knot/catalog/catalog_update.h" +#include "knot/common/log.h" +#include "knot/conf/base.h" +#include "knot/server/server.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->new_group); + free(val); +} + +static int freecb(trie_val_t *tval, _unused_ void *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, catalog_upd_type_t type) +{ + assert(bail <= (int)knot_dname_size(owner)); + size_t member_size = knot_dname_size(member); + + 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; + } + val->type = type; + val->new_group = NULL; + if (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->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)) { + log_zone_error(val->member, "duplicate addition/removal of the member node, ignoring"); + return KNOT_EOK; + } + 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; +} + +static int upd_val_set_prop(catalog_upd_val_t *val, const knot_dname_t *check_ow, + const knot_dname_t *check_catz, const char *group, + size_t group_len) +{ + if (check_catz != NULL) { + if (val->type == CAT_UPD_REM || + !knot_dname_is_equal(check_ow, val->add_owner) || // TODO consider removing those checks. Are they worth the performance? + !knot_dname_is_equal(check_catz, val->add_catz)) { + return KNOT_EOK; // ignore invalid property set + } + } + if (val->new_group != NULL) { + free(val->new_group); + } + val->new_group = strndup(group, group_len); + return val->new_group == NULL ? KNOT_ENOMEM : 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, + catalog_upd_type_t type, const char *group, + size_t group_len, catalog_t *check_rem) +{ + 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 ((type == CAT_UPD_REM || type == CAT_UPD_PROP) && check_rem != NULL && + !catalog_contains_exact(check_rem, member, owner, catzone)) { + if (found == NULL) { + // we need to perform this check immediately because + // garbage removal would block legitimate removal + return KNOT_EOK; + } + if (type == CAT_UPD_REM) { + catalog_upd_val_t *val = *found; + catalog_upd_val_free(val); + trie_del(u->upd, lf + 1, lf[0], NULL); + return KNOT_EOK; + } + } + + if (found != NULL) { + catalog_upd_val_t *val = *found; + assert(knot_dname_is_equal(val->member, member)); + if (type == CAT_UPD_PROP) { + return upd_val_set_prop(val, owner, catzone, group, group_len); + } else { + return upd_val_update(val, bail, owner, type == CAT_UPD_REM); + } + } + + catalog_upd_val_t *val = upd_val_new(member, bail, owner, type); + if (val == NULL) { + return KNOT_ENOMEM; + } + if (group_len > 0) { + int ret = upd_val_set_prop(val, NULL, NULL, group, group_len); + if (ret != KNOT_EOK) { + catalog_upd_val_free(val); + return ret; + } + } + 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 || val->type == CAT_UPD_PROP) { + 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; +} + +typedef struct { + conf_t *conf; + catalog_update_t *cup; +} rem_conflict_ctx_t; + +static int rem_conf_conflict(const knot_dname_t *mem, const knot_dname_t *ow, + const knot_dname_t *cz, _unused_ const char *gr, void *ctx) +{ + rem_conflict_ctx_t *rcctx = ctx; + + if (conf_rawid_exists(rcctx->conf, C_ZONE, mem, knot_dname_size(mem))) { + return catalog_update_add(rcctx->cup, mem, ow, cz, CAT_UPD_REM, NULL, 0, NULL); + } + return KNOT_EOK; +} + +void catalog_update_finalize(catalog_update_t *u, catalog_t *cat, conf_t *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, conf, cat)) { + val->type = (val->type == CAT_UPD_ADD ? CAT_UPD_INVALID : CAT_UPD_REM); + } + catalog_it_next(it); + } + catalog_it_free(it); + + // This checks if the configuration file has not changed in the way + // it conflicts with existing member zone and let config take precedence. + if (cat->ro_txn != NULL) { + rem_conflict_ctx_t rcctx = { conf, u }; + (void)catalog_apply(cat, NULL, rem_conf_conflict, &rcctx, 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: + case CAT_UPD_PROP: + ret = catalog_add(cat, val->member, val->add_owner, val->add_catz, + val->new_group == NULL ? "" : val->new_group); + 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, _unused_ const char *group, 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, CAT_UPD_REM, NULL, 0, NULL); + } else { + return KNOT_EOK; + } +} + +int catalog_update_del_all(catalog_update_t *u, catalog_t *cat, const knot_dname_t *zone, ssize_t *upd_count) +{ + pthread_mutex_lock(&u->mutex); + del_all_ctx_t ctx = { zone, u }; + *upd_count -= trie_weight(u->upd); + int ret = catalog_apply(cat, NULL, del_all_cb, &ctx, false); + *upd_count += trie_weight(u->upd); + pthread_mutex_unlock(&u->mutex); + return ret; +} + +int catalog_zone_purge(server_t *server, conf_t *conf, const knot_dname_t *zone) +{ + assert(server); + assert(zone); + + if (server->catalog.ro_txn == NULL) { + return KNOT_EOK; // no catalog at all + } + + if (conf != NULL) { + conf_val_t role = conf_zone_get(conf, C_CATALOG_ROLE, zone); + if (conf_opt(&role) != CATALOG_ROLE_INTERPRET) { + return KNOT_EOK; + } + } + + ssize_t members = 0; + int ret = catalog_update_del_all(&server->catalog_upd, &server->catalog, zone, &members); + if (ret == KNOT_EOK && members > 0) { + log_zone_info(zone, "catalog zone purged, %zd member zones deconfigured", members); + server->catalog_upd_signal = true; + if (kill(getpid(), SIGUSR1) != 0) { + ret = knot_map_errno(); + } + } + return ret; +} diff --git a/src/knot/catalog/catalog_update.h b/src/knot/catalog/catalog_update.h new file mode 100644 index 0000000..3726372 --- /dev/null +++ b/src/knot/catalog/catalog_update.h @@ -0,0 +1,171 @@ +/* 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" +#include "knot/conf/conf.h" + +struct server; // "knot/server/server.h" causes preprocessor problems when included. + +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_PROP, // ONLY change of properties of existing member + CAT_UPD_MAX, // number of options in the 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 + + char *new_group; // the desired configuration group for the member +} catalog_upd_val_t; + +typedef struct { + trie_t *upd; // tree of catalog_upd_val_t, that gonna be changed in catalog + int error; // error occurred 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 type CAT_UPD_REM, CAT_UPD_ADD, CAT_UPD_PROP. + * \param group Optional: member group property value. + * \param group_len Length of 'group' string (if not NULL). + * \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, + catalog_upd_type_t type, const char *group, + size_t group_len, 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 Catalog update for conflicts with conf or other catalogs. + * + * \param u Catalog update to be aligned in-place. + * \param cat Catalog DB to check against. + * \param conf Relevant configuration. + */ +void catalog_update_finalize(catalog_update_t *u, catalog_t *cat, conf_t *conf); + +/*! + * \brief Put changes from Catalog Update into persistent Catalog database. + * + * \param u Catalog update to be committed. + * \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. + * \param upd_count Output: number of resulting updates to catalog database. + * + * \return KNOT_E* + */ +int catalog_update_del_all(catalog_update_t *u, catalog_t *cat, const knot_dname_t *zone, ssize_t *upd_count); + +/*! + * \brief Destroy all members of specified catalog zone. + * + * \param server Server with catalog DB. + * \param conf Optional: check conf to skip if zone not catalog. + * \param zone Catalog zone name. + * + * \return KNOT_E* + */ +int catalog_zone_purge(struct server *server, conf_t *conf, const knot_dname_t *zone); diff --git a/src/knot/catalog/generate.c b/src/knot/catalog/generate.c new file mode 100644 index 0000000..e05442b --- /dev/null +++ b/src/knot/catalog/generate.c @@ -0,0 +1,346 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <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), false); + 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 same_group(zone_t *old_z, zone_t *new_z) +{ + if (old_z->catalog_group == NULL || new_z->catalog_group == NULL) { + return (old_z->catalog_group == new_z->catalog_group); + } else { + return (strcmp(old_z->catalog_group, new_z->catalog_group) == 0); + } +} + +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, CAT_UPD_REM, NULL, 0, NULL); + free(owner); + if (ret != KNOT_EOK) { + catz->cat_members->error = ret; + } else { + zone_events_schedule_now(catz, ZONE_EVENT_LOAD); + } + } + } + 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; + if (cg == NULL) { + knot_zonedb_iter_next(it); + continue; + } + zone_t *catz = knot_zonedb_find(db_new, cg); + zone_t *old = knot_zonedb_find(db_old, zone->name); + knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member); + size_t cgroup_size = zone->catalog_group == NULL ? 0 : strlen(zone->catalog_group); + if (catz == NULL) { + log_zone_warning(zone->name, "member zone belongs to non-existing catalog zone"); + } else if (catz->contents == NULL || old == NULL) { + assert(catz->cat_members != NULL); + 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, CAT_UPD_ADD, zone->catalog_group, + cgroup_size, NULL); + if (ret != KNOT_EOK) { + catz->cat_members->error = ret; + } else { + zone_events_schedule_now(catz, ZONE_EVENT_LOAD); + } + } else if (!same_group(zone, old)) { + int ret = catalog_update_add(catz->cat_members, zone->name, owner, + cg, CAT_UPD_PROP, zone->catalog_group, + cgroup_size, NULL); + if (ret != KNOT_EOK) { + catz->cat_members->error = ret; + } else { + zone_events_schedule_now(catz, ZONE_EVENT_LOAD); + } + } + free(owner); + 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); +} + +#define def_txt_owner(ptr_owner) \ + knot_dname_storage_t txt_owner = "\x05""group"; \ + size_t _ptr_ow_len = knot_dname_size(ptr_owner); \ + size_t _ptr_ow_ind = strlen((const char *)txt_owner); \ + if (_ptr_ow_ind + _ptr_ow_len > sizeof(txt_owner)) { \ + return KNOT_ERANGE; \ + } \ + memcpy(txt_owner + _ptr_ow_ind, (ptr_owner), _ptr_ow_len); + +static int add_group_txt(const knot_dname_t *ptr_owner, const char *group, + zone_contents_t *conts, zone_update_t *up) +{ + assert((conts == NULL) != (up == NULL)); + size_t group_len; + if (group == NULL || (group_len = strlen(group)) < 1) { + return KNOT_EOK; + } + assert(group_len <= 255); + + def_txt_owner(ptr_owner); + + uint8_t data[256] = { group_len }; + memcpy(data + 1, group, group_len); + + knot_rrset_t txt; + knot_rrset_init(&txt, txt_owner, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, 0); + uint8_t txt_rd[256] = { 0 }; + txt.rrs.rdata = (knot_rdata_t *)txt_rd; + txt.rrs.count = 1; + set_rdata(&txt, data, 1 + group_len ); + + int ret; + if (conts != NULL) { + zone_node_t *unused = NULL; + ret = zone_contents_add_rr(conts, &txt, &unused); + } else { + ret = zone_update_add(up, &txt); + } + + return ret; +} + +static int rem_group_txt(const knot_dname_t *ptr_owner, zone_update_t *up) +{ + def_txt_owner(ptr_owner); + + int ret = zone_update_remove_rrset(up, txt_owner, KNOT_RRTYPE_TXT); + if (ret == KNOT_ENOENT || ret == KNOT_ENONODE) { + ret = KNOT_EOK; + } + + return ret; +} + +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 || + add_group_txt(val->add_owner, val->new_group, c, NULL) != 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) { + catalog_it_next(it); + continue; + } + + if (val->type == CAT_UPD_PROP && knot_dname_is_equal(zu->zone->name, val->add_catz)) { + ret = rem_group_txt(val->add_owner, zu); + if (ret == KNOT_EOK) { + ret = add_group_txt(val->add_owner, val->new_group, NULL, zu); + } + catalog_it_next(it); + continue; + } + + set_rdata(&ptr, val->member, knot_dname_size(val->member)); + if (val->type == CAT_UPD_REM && knot_dname_is_equal(zu->zone->name, val->rem_catz)) { + ptr.owner = val->rem_owner; + ret = zone_update_remove(zu, &ptr); + if (ret == KNOT_EOK) { + ret = rem_group_txt(val->rem_owner, zu); + } + } + if (val->type == CAT_UPD_ADD && knot_dname_is_equal(zu->zone->name, val->add_catz)) { + ptr.owner = val->add_owner; + ret = zone_update_add(zu, &ptr); + if (ret == KNOT_EOK) { + ret = add_group_txt(val->add_owner, val->new_group, NULL, zu); + } + } + 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 0000000..721c1ef --- /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 0000000..e7a5cf0 --- /dev/null +++ b/src/knot/catalog/interpret.c @@ -0,0 +1,257 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <pthread.h> +#include <stdio.h> + +#include "knot/catalog/interpret.h" +#include "knot/journal/serialization.h" + +struct cat_upd_ctx; +typedef int (*cat_interpret_cb_t)(zone_node_t *node, struct cat_upd_ctx *ctx); + +typedef struct cat_upd_ctx { + catalog_update_t *u; + const zone_contents_t *complete_conts; + int apex_labels; + bool remove; + bool zone_diff; + catalog_t *check; + cat_interpret_cb_t member_cb; + cat_interpret_cb_t property_cb; +} cat_upd_ctx_t; + +static bool label_eq(const knot_dname_t *a, const char *_b) +{ + const knot_dname_t *b = (const knot_dname_t *)_b; + return a[0] == b[0] && memcmp(a + 1, b + 1, a[0]) == 0; +} + +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 interpret_node(zone_node_t *node, void * _ctx) +{ + cat_upd_ctx_t *ctx = _ctx; + + int labels_diff = knot_dname_labels(node->owner, NULL) - ctx->apex_labels + - 1 /* "zones" label */ - 1 /* unique-N label */; + assert(labels_diff >= 0); + + switch (labels_diff) { + case 0: + return ctx->member_cb(node, ctx); + case 1: + return ctx->property_cb(node, ctx); + default: + return KNOT_EOK; + } +} + +static int interpret_zone(zone_diff_t *zdiff, cat_upd_ctx_t *ctx) +{ + knot_dname_storage_t sub; + if (knot_dname_store(sub, (uint8_t *)CATALOG_ZONES_LABEL) == 0 || + catalog_dname_append(sub, zdiff->apex->owner) == 0) { + return KNOT_EINVAL; + } + + if (zone_tree_get(&zdiff->nodes, sub) == NULL) { + return KNOT_EOK; + } + + return zone_tree_sub_apply(&zdiff->nodes, sub, true, interpret_node, ctx); +} + +static const knot_dname_t *property_get_member(const zone_node_t *prop_node, + const zone_contents_t *complete_conts, + const knot_dname_t **owner) +{ + assert(prop_node != NULL); + knot_rdataset_t *ptr = node_rdataset(prop_node->parent, KNOT_RRTYPE_PTR); + if (ptr == NULL) { + // fallback: search in provided complete zone contents + const knot_dname_t *memb_name = knot_wire_next_label(prop_node->owner, NULL); + const zone_node_t *memb_node = zone_contents_find_node(complete_conts, memb_name); + ptr = node_rdataset(memb_node, KNOT_RRTYPE_PTR); + if (memb_node != NULL) { + *owner = memb_node->owner; + } + } else { + *owner = prop_node->parent->owner; + } + if (*owner == NULL || ptr == NULL || ptr->count != 1) { + return NULL; + } + return knot_ptr_name(ptr->rdata); +} + +static int cat_update_add_memb(zone_node_t *node, cat_upd_ctx_t *ctx) +{ + const knot_rdataset_t *ptr = node_rdataset(node, KNOT_RRTYPE_PTR); + if (ptr == NULL) { + return KNOT_EOK; + } else if (ptr->count != 1) { + return KNOT_ERROR; + } + + const knot_rdataset_t *counter_ptr = node_rdataset(binode_counterpart(node), KNOT_RRTYPE_PTR); + if (knot_rdataset_subset(ptr, counter_ptr)) { + 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->complete_conts->apex->owner, + ctx->remove ? CAT_UPD_REM : CAT_UPD_ADD, + NULL, 0, ctx->check); + rdata = knot_rdataset_next(rdata); + } + return ret; +} + +static int cat_update_add_grp(zone_node_t *node, cat_upd_ctx_t *ctx) +{ + if (!label_eq(node->owner, CATALOG_GROUP_LABEL)) { + return KNOT_EOK; + } + + const knot_dname_t *owner = NULL; + const knot_dname_t *member = property_get_member(node, ctx->complete_conts, &owner); + if (member == NULL) { + return KNOT_EOK; // just ignore property w/o member + } + + const knot_rdataset_t *txt = node_rdataset(node, KNOT_RRTYPE_TXT); + if (txt == NULL) { + return KNOT_EOK; + } else if (txt->count != 1) { + return KNOT_ERROR; + } + + const knot_rdataset_t *counter_txt = node_rdataset(binode_counterpart(node), KNOT_RRTYPE_TXT); + if (knot_rdataset_subset(txt, counter_txt)) { + return KNOT_EOK; + } + + const char *newgr = ""; + size_t grlen = 0; + if (!ctx->remove) { + assert(txt->count == 1); + // TXT rdata consists of one or more 1-byte prefixed strings. + if (txt->rdata->len != txt->rdata->data[0] + 1) { + return KNOT_EMALF; + } + newgr = (const char *)txt->rdata->data + 1; + grlen = txt->rdata->data[0]; + assert(grlen <= CATALOG_GROUP_MAXLEN); + } + + return catalog_update_add(ctx->u, member, owner, ctx->complete_conts->apex->owner, + CAT_UPD_PROP, newgr, grlen, ctx->check); +} + +int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone, + const zone_diff_t *zone_diff, + const struct zone_contents *complete_contents, + bool remove, catalog_t *check, ssize_t *upd_count) +{ + int ret = KNOT_EOK; + zone_diff_t zdiff; + assert(zone == NULL || zone_diff == NULL); + if (zone != NULL) { + zone_diff_from_zone(&zdiff, zone); + } else { + zdiff = *zone_diff; + } + cat_upd_ctx_t ctx = { u, complete_contents, knot_dname_labels(zdiff.apex->owner, NULL), + remove, zone_diff != NULL, check, cat_update_add_memb, cat_update_add_grp }; + + pthread_mutex_lock(&u->mutex); + *upd_count -= trie_weight(u->upd); + if (zone_diff != NULL) { + zone_diff_reverse(&zdiff); + ctx.remove = true; + ret = interpret_zone(&zdiff, &ctx); + zone_diff_reverse(&zdiff); + ctx.remove = false; + ctx.check = NULL; + } + if (ret == KNOT_EOK) { + ret = interpret_zone(&zdiff, &ctx); + } + *upd_count += trie_weight(u->upd); + pthread_mutex_unlock(&u->mutex); + return ret; +} + +static int rr_count(const zone_node_t *node, uint16_t type) +{ + const knot_rdataset_t *rd = node_rdataset(node, type); + return rd == NULL ? 0 : rd->count; +} + +static int member_verify(zone_node_t *node, cat_upd_ctx_t *ctx) +{ + return rr_count(node, KNOT_RRTYPE_PTR) > 1 ? KNOT_EISRECORD : KNOT_EOK; +} + +static int prop_verify(zone_node_t *node, cat_upd_ctx_t *ctx) +{ + if (label_eq(node->owner, CATALOG_GROUP_LABEL) && + rr_count(node, KNOT_RRTYPE_TXT) > 1) { + return KNOT_EISRECORD; + } + + return KNOT_EOK; +} + +int catalog_zone_verify(const struct zone_contents *zone) +{ + cat_upd_ctx_t ctx = { NULL, zone, knot_dname_labels(zone->apex->owner, NULL), + false, false, NULL, member_verify, prop_verify }; + + if (!check_zone_version(zone)) { + return KNOT_EZONEINVAL; + } + + zone_diff_t zdiff; + zone_diff_from_zone(&zdiff, zone); + + return interpret_zone(&zdiff, &ctx); +} diff --git a/src/knot/catalog/interpret.h b/src/knot/catalog/interpret.h new file mode 100644 index 0000000..20928b7 --- /dev/null +++ b/src/knot/catalog/interpret.h @@ -0,0 +1,52 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "knot/catalog/catalog_update.h" + +struct zone_contents; +struct zone_diff; + +/*! + * \brief Validate if given zone is valid catalog. + * + * \param zone Catalog zone in question. + * + * \retval KNOT_EZONEINVAL Invalid version record. + * \retval KNOT_EISRECORD Some of single-record RRSets has multiple RRs. + * \return KNOT_EOK All OK. + */ +int catalog_zone_verify(const struct zone_contents *zone); + +/*! + * \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 zone_diff Zone diff to interpret for removals and additions. + * \param complete_contents Complete zone contents (zone might be from a changeset). + * \param remove Add removals of found member zones. + * \param check Optional: existing catalog database to be checked for existence + * of such record (useful for removals). + * \param upd_count Output: number of resulting updates to catalog database. + * + * \return KNOT_E* + */ +int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone, + const struct zone_diff *zone_diff, + const struct zone_contents *complete_contents, + bool remove, catalog_t *check, ssize_t *upd_count); |