summaryrefslogtreecommitdiffstats
path: root/src/knot/zone/catalog.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/zone/catalog.c')
-rw-r--r--src/knot/zone/catalog.c628
1 files changed, 628 insertions, 0 deletions
diff --git a/src/knot/zone/catalog.c b/src/knot/zone/catalog.c
new file mode 100644
index 0000000..c9010a9
--- /dev/null
+++ b/src/knot/zone/catalog.c
@@ -0,0 +1,628 @@
+/* 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 "knot/common/log.h"
+#include "knot/conf/conf.h"
+#include "knot/zone/contents.h"
+
+#define CATALOG_VERSION "1.0"
+#define CATALOG_ZONE_VERSION "2" // must be just one char long
+
+const MDB_val catalog_iter_prefix = { 1, "" };
+
+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->add = trie_create(NULL);
+ if (u->add == NULL) {
+ return KNOT_ENOMEM;
+ }
+ u->rem = trie_create(NULL);
+ if (u->rem == NULL) {
+ trie_free(u->add);
+ return KNOT_ENOMEM;
+ }
+ pthread_mutex_init(&u->mutex, 0);
+ return KNOT_EOK;
+}
+
+static int freecb(trie_val_t *tval, void *unused)
+{
+ (void)unused;
+ free(*(void **)tval);
+ return 0;
+}
+
+void catalog_update_clear(catalog_update_t *u)
+{
+ trie_apply(u->add, freecb, NULL);
+ trie_clear(u->add);
+ trie_apply(u->rem, freecb, NULL);
+ trie_clear(u->rem);
+}
+
+void catalog_update_deinit(catalog_update_t *u)
+{
+ pthread_mutex_destroy(&u->mutex);
+ trie_free(u->add);
+ trie_free(u->rem);
+}
+
+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);
+
+ trie_t *toadd = remove ? u->rem : u->add;
+ trie_t *check = remove ? u->add : u->rem;
+
+ bool just_reconf = false;
+
+ trie_val_t *found = trie_get_try(check, lf + 1, lf[0]);
+ if (found != NULL) {
+ catalog_upd_val_t *counter = *found;
+ assert(knot_dname_is_equal(counter->member, member));
+ if (knot_dname_is_equal(counter->owner, owner)) {
+ assert(knot_dname_is_equal(counter->catzone, catzone));
+ trie_del(check, lf + 1, lf[0], NULL);
+ free(counter);
+ return KNOT_EOK;
+ } else {
+ counter->just_reconf = true;
+ just_reconf = true;
+ }
+ }
+
+ size_t member_size = knot_dname_size(member);
+ size_t owner_size = knot_dname_size(owner);
+
+ catalog_upd_val_t *val = malloc(sizeof(*val) + member_size + owner_size);
+ if (val == NULL) {
+ return KNOT_ENOMEM;
+ }
+ trie_val_t *added = trie_get_ins(toadd, lf + 1, lf[0]);
+ if (added == NULL) {
+ free(val);
+ return KNOT_ENOMEM;
+ }
+ if (*added != NULL) { // rewriting existing val
+ free(*added);
+ }
+ 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->just_reconf = just_reconf;
+ *added = val;
+ return KNOT_EOK;
+}
+
+catalog_upd_val_t *catalog_update_get(catalog_update_t *u, const knot_dname_t *member, bool remove)
+{
+ knot_dname_storage_t lf_storage;
+ uint8_t *lf = knot_dname_lf(member, lf_storage);
+
+ trie_val_t *found = trie_get_try(remove ? u->rem : u->add, 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;
+}
+
+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;
+ }
+
+ size_t zone_size = knot_dname_size(zone->apex->owner);
+ knot_dname_t sub[zone_size + 6];
+ memcpy(sub, "\x05""zones", 6);
+ memcpy(sub + 6, zone->apex->owner, zone_size);
+
+ 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;
+}
+
+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 *pre, const knot_dname_t *a, const knot_dname_t *b,
+ const knot_dname_t *c, const char *suff)
+{
+ printf("%s", pre);
+ print_dname(a);
+ print_dname(b);
+ print_dname(c);
+ printf("%s\n", suff);
+}
+
+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)
+{
+ ssize_t plus = 0, minus = 0;
+
+ printf(";; <catalog zone> <record owner> <record zone>\n");
+
+ if (u != NULL) {
+ catalog_it_t *it = catalog_it_begin(u, true);
+ while (!catalog_it_finished(it)) {
+ catalog_upd_val_t *val = catalog_it_val(it);
+ print_dname3("- ", val->member, val->owner, val->catzone, "");
+ minus++;
+ catalog_it_next(it);
+ }
+ catalog_it_free(it);
+
+ it = catalog_it_begin(u, false);
+ while (!catalog_it_finished(it)) {
+ catalog_upd_val_t *val = catalog_it_val(it);
+ print_dname3("+ ", val->member, val->owner, val->catzone,
+ val->just_reconf ? "JR" : "");
+ plus++;
+ catalog_it_next(it);
+ }
+ catalog_it_free(it);
+ }
+
+ printf("Total changes: -%zd +%zd\n", minus, plus);
+}