diff --git a/Knot.files b/Knot.files index 4003f6a4e738b303d36987b4a1374f252d2cb26e..5eb8dfb2f0107a67a79d2837e2a78bc31c483e14 100644 --- a/Knot.files +++ b/Knot.files @@ -265,6 +265,8 @@ src/knot/zone/adds_tree.c src/knot/zone/adds_tree.h src/knot/zone/adjust.c src/knot/zone/adjust.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/doc/configuration.rst b/doc/configuration.rst index 1d58aebfbe55643dbbd42b0f3eab5ca750de6c3e..4a9cbfce143c3992f8cd3c7e10a5d5a633899202 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -577,6 +577,65 @@ master's SOA serial in a special variable inside KASP DB and appropriately modifiying AXFR/IXFR queries/answers to keep the communication with master consistent while applying the changes with a different serial. +.. _catalog-zones: + +Catalog zones +============= + +Catalog zone is a concept when the list of zones configured is maintained +as contents of a special zone. This approach has the benefit of simple propagation +of the actual zone list to slave servers. Especially when the list is frequently +updated. + +Terminology first. *Catalog zone* is a meta-zone which shall not be a part +of the DNS tree, but it contains information about the set of member zones and +is transferable to slaves using common AXFR/IXFR techniques. +*Catalog-member zone* (or just *member zone*) is a zone based on +information from the catalog zone and not from configuration file/database. + +Catalog zone is handled almost in the same way as a regular zone. +It can be configured using all the standard options (but for example +DNSSEC signing would be useless), including master/slave configuration +and ACLs. Being a catalog zone is indicated by setting the option +:ref:`zone_catalog-template`. The difference is that standard DNS +queries to a catalog zone are answered with REFUSED as if such a zone +wouldn't exist, unless querying from an address with transfers enabled +by ACL. The name of the catalog zone is arbitrary. +It's possible to configure more catalog zones. + +.. WARNING:: + Don't choose name for a catalog zone below a name of any other + existing zones configured on the server as it would effectively "shadow" + part of your DNS subtree. + +Upon catalog zone (re)load or change, all the PTR records in the zone +are processed and member zones created, with zone names taken from the +PTR records' RData, and zone settings taken from the confguration +template specified by :ref:`zone_catalog-template`. Owner names of those PTR +records may be arbitrary, but when a member zone is de-cataloged and +re-cataloged again, the owner name of the relevant PTR record must +be changed. It's also recommended that all the PTR records have different +owner names (in other words, catalog zone RRSets consist of one RR each) +to prevent oversized RRSets (not AXFR-able) and to achieve interoperability. + +All records other than PTR are ignored. However, they remain in the catalog +zone and might be for example transfered to a slave, possibly interpreting +catalog zones differently. SOA still needs to be present in the catalog zone +and its serial handled appropriately. Apex NS record should be present +for the sake of interoperability. + +Catalog zone may be modified using any standard means (e.g. AXFR/IXFR, DDNS, +zone file reload). In the case of incremental change, only affected +member zones are reloaded. + +Any de-cataloged member zone is purged immediately, including its +zone file, journal, timers, and DNSSEC keys. The zone file is not +deleted if :ref:`zone_zonefile-sync` is set to *-1* for member zones. + +When setting up catalog zones, it might be useful to set +:ref:`database_catalog-db` and :ref:`database_catalog-db-max-size` +to non-default values. + .. _query-modules: Query modules diff --git a/doc/man/knot.conf.5in b/doc/man/knot.conf.5in index 01e916da2a5e14d9a84660d3c546ecd645b04ce3..610f7e5d2a963dee9dbfa0ab95498965bd090be2 100644 --- a/doc/man/knot.conf.5in +++ b/doc/man/knot.conf.5in @@ -667,6 +667,8 @@ database: kasp\-db\-max\-size: SIZE timer\-db: STR timer\-db\-max\-size: SIZE + catalog\-db: str + catalog\-db\-max\-size: SIZE .ft P .fi .UNINDENT @@ -759,6 +761,26 @@ This value also influences server\(aqs usage of virtual memory. .UNINDENT .sp \fIDefault:\fP 100 MiB +.SS catalog\-db +.sp +An explicit specification of the zone catalog database directory. +Only useful if catalog\-zones are enabled. +Non\-absolute path (i.e. not starting with \fB/\fP) is relative to +\fI\%storage\fP\&. +.sp +\fIDefault:\fP \fI\%storage\fP/catalog +.SS catalog\-db\-max\-size +.sp +The hard limit for the catalog database maximum size. +.sp +\fBNOTE:\fP +.INDENT 0.0 +.INDENT 3.5 +This value also influences server\(aqs usage of virtual memory. +.UNINDENT +.UNINDENT +.sp +\fIDefault:\fP 20 GiB (512 MiB for 32\-bit) .SH KEYSTORE SECTION .sp DNSSEC keystore configuration. @@ -1325,6 +1347,7 @@ zone: serial\-policy: increment | unixtime | dateserial refresh\-min\-interval: TIME refresh\-max\-interval: TIME + catalog\-template: template_id module: STR/STR ... .ft P .fi @@ -1591,6 +1614,11 @@ Forced minimum zone refresh interval to avoid flooding master. Forced maximum zone refresh interval. .sp \fIDefault:\fP not set +.SS catalog\-template +.sp +This zone is a catalog zone. For the catalog\-member zones, the specified configuration template will be applied. +.sp +\fIDefault:\fP not set .SS module .sp An ordered list of references to query modules in the form of \fImodule_name\fP or diff --git a/doc/reference.rst b/doc/reference.rst index 0148ca1364766a381e1b1cde24ea40ad3c158341..509686f1fd218e54f0d1d0f1fa1b37f78bc5e7fd 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -725,6 +725,8 @@ Configuration of databases for zone contents, DNSSEC metadata, or event timers. kasp-db-max-size: SIZE timer-db: STR timer-db-max-size: SIZE + catalog-db: str + catalog-db-max-size: SIZE .. _database_storage: @@ -831,6 +833,30 @@ The hard limit for the timer database maximum size. *Default:* 100 MiB +.. _database_catalog-db: + +catalog-db +---------- + +An explicit specification of the zone catalog database directory. +Only useful if :ref:`catalog-zones` are enabled. +Non-absolute path (i.e. not starting with ``/``) is relative to +:ref:`storage<database_storage>`. + +*Default:* :ref:`storage<database_storage>`/catalog + +.. _database_catalog-db-max-size: + +catalog-db-max-size +------------------- + +The hard limit for the catalog database maximum size. + +.. NOTE:: + This value also influences server's usage of virtual memory. + +*Default:* 20 GiB (512 MiB for 32-bit) + .. _Keystore section: Keystore section @@ -1451,6 +1477,7 @@ Definition of zones served by the server. serial-policy: increment | unixtime | dateserial refresh-min-interval: TIME refresh-max-interval: TIME + catalog-template: template_id module: STR/STR ... .. _zone_domain: @@ -1747,6 +1774,15 @@ Forced maximum zone refresh interval. *Default:* not set +.. _zone_catalog-template: + +catalog-template +---------------- + +This zone is a catalog zone. For the catalog-member zones, the specified configuration template will be applied. + +*Default:* not set + .. _zone_module: module diff --git a/src/contrib/ucw/lists.c b/src/contrib/ucw/lists.c index b3f254686f36a86e4cf13b253cdec5193644de91..84c6eaa839e946c85e6b9fbeed5491e3ecb6baec 100644 --- a/src/contrib/ucw/lists.c +++ b/src/contrib/ucw/lists.c @@ -2,7 +2,7 @@ * BIRD Library -- Linked Lists * * (c) 1998 Martin Mares <mj@ucw.cz> - * (c) 2015, 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + * (c) 2015, 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> * * Can be freely distributed and used under the terms of the GNU GPL. */ @@ -233,3 +233,12 @@ void ptrlist_deep_free(list_t *l, knot_mm_t *mm) } ptrlist_free(l, mm); } + +void ptrlist_free_custom(list_t *l, knot_mm_t *mm, ptrlist_free_cb free_cb) +{ + ptrnode_t *n; + WALK_LIST(n, *l) { + free_cb(n->d); + } + ptrlist_free(l, mm); +} diff --git a/src/contrib/ucw/lists.h b/src/contrib/ucw/lists.h index 922e152f406e15fbfd453cf4e67b13d5bf66dc81..305806ad33e965bc06af192aadb759f61ad8e7f9 100644 --- a/src/contrib/ucw/lists.h +++ b/src/contrib/ucw/lists.h @@ -2,7 +2,7 @@ * BIRD Library -- Linked Lists * * (c) 1998 Martin Mares <mj@ucw.cz> - * (c) 2015, 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + * (c) 2015, 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> * * Can be freely distributed and used under the terms of the GNU GPL. */ @@ -82,3 +82,5 @@ void ptrlist_free(list_t *, knot_mm_t *); void ptrlist_rem(ptrnode_t *node, knot_mm_t *mm); void ptrlist_deep_free(list_t *, knot_mm_t *); +typedef void (*ptrlist_free_cb)(void *); +void ptrlist_free_custom(list_t *l, knot_mm_t *mm, ptrlist_free_cb free_cb); diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc index bd9341569f9e59858873974759f7b74366040af4..4cf49eed611ac33c051234268c89fb8b0f7f72ac 100644 --- a/src/knot/Makefile.inc +++ b/src/knot/Makefile.inc @@ -156,6 +156,8 @@ libknotd_la_SOURCES = \ knot/zone/adds_tree.h \ knot/zone/adjust.c \ knot/zone/adjust.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/conf/base.c b/src/knot/conf/base.c index 98fdc454de2508c47e56c55cce912c75bfef2272..edb40c02cd26ae441058bd39bccac7f99fb4b37d 100644 --- a/src/knot/conf/base.c +++ b/src/knot/conf/base.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* 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 @@ -380,6 +380,8 @@ int conf_clone( out->hostname = strdup(s_conf->hostname); } + out->catalog = s_conf->catalog; + // Initialize cached values. init_cache(out, false); diff --git a/src/knot/conf/base.h b/src/knot/conf/base.h index e33ba15d0f4513396d4a2c51ab45b967357fa8c4..475da16255eaf681e4deb61c881ccb1f471996ce 100644 --- a/src/knot/conf/base.h +++ b/src/knot/conf/base.h @@ -73,6 +73,8 @@ typedef struct { dynarray_declare(mod, module_t *, DYNARRAY_VISIBILITY_PUBLIC, 16) dynarray_declare(old_schema, yp_item_t *, DYNARRAY_VISIBILITY_PUBLIC, 16) +struct knot_catalog; + /*! Configuration context. */ typedef struct { /*! Cloned configuration indicator. */ @@ -131,6 +133,8 @@ typedef struct { list_t *query_modules; /*! Default query modules plan. */ struct query_plan *query_plan; + /*! Zone catalog database. */ + struct knot_catalog *catalog; } conf_t; /*! diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c index caf6fab67db612fef8e61e38a418df880064f57c..1ee35bae635be5ce8fe8dcc270ef1f18b86e3e7f 100644 --- a/src/knot/conf/conf.c +++ b/src/knot/conf/conf.c @@ -24,6 +24,7 @@ #include "knot/conf/confdb.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" @@ -179,6 +180,7 @@ conf_val_t conf_zone_get_txn( CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)", &C_ZONE[1], &key1_name[1], knot_strerror(val.code)); // FALLTHROUGH + case KNOT_YP_EINVAL_ID: case KNOT_ENOENT: break; } @@ -190,18 +192,41 @@ conf_val_t conf_zone_get_txn( // Use the specified template. conf_val(&val); conf_db_get(conf, txn, C_TPL, key1_name, val.data, val.len, &val); - break; + goto got_template; default: CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)", &C_ZONE[1], &C_TPL[1], knot_strerror(val.code)); // FALLTHROUGH case KNOT_ENOENT: case KNOT_YP_EINVAL_ID: - // Use the default template. - conf_db_get(conf, txn, C_TPL, key1_name, CONF_DEFAULT_ID + 1, - CONF_DEFAULT_ID[0], &val); + break; + } + + // Check if this is a catalog member zone. + if (conf->catalog != NULL) { + knot_dname_t *catalog = NULL; + int ret = knot_cat_get_catzone_thrsafe(conf->catalog, dname, &catalog); + if (ret == KNOT_EOK) { + conf_db_get(conf, txn, C_ZONE, C_CATALOG_TPL, catalog, + knot_dname_size(catalog), &val); + if (val.code != KNOT_EOK) { + CONF_LOG_ZONE(LOG_ERR, catalog, + "catalog zone has no catalog template (%s)", + knot_strerror(val.code)); + return val; + } + knot_dname_free(catalog, NULL); + conf_val(&val); + conf_db_get(conf, txn, C_TPL, key1_name, val.data, val.len, &val); + goto got_template; + } } + // Use the default template. + conf_db_get(conf, txn, C_TPL, key1_name, CONF_DEFAULT_ID + 1, + CONF_DEFAULT_ID[0], &val); + +got_template: switch (val.code) { default: CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)", @@ -1079,6 +1104,15 @@ char* conf_zonefile_txn( return get_filename(conf, txn, zone, file); } +inline static bool legacy_db_fallback( + const yp_name_t *db_type) +{ + return (db_type[1] == C_JOURNAL_DB[1] || + db_type[1] == C_KASP_DB[1] || + db_type[1] == C_TIMER_DB[1]); + // warning comparing by first letter as every database has different! +} + char* conf_db_txn( conf_t *conf, knot_db_txn_t *txn, @@ -1090,7 +1124,7 @@ char* conf_db_txn( } conf_val_t db_val = conf_get_txn(conf, txn, C_DB, db_type); - if (db_val.code != KNOT_EOK) { + if (db_val.code != KNOT_EOK && legacy_db_fallback(db_type)) { db_val = conf_default_get_txn(conf, txn, db_type); } @@ -1108,7 +1142,7 @@ conf_val_t conf_db_param_txn( const yp_name_t *legacy_param) { conf_val_t val = conf_get_txn(conf, txn, C_DB, param); - if (val.code != KNOT_EOK) { + if (val.code != KNOT_EOK && legacy_param != NULL) { val = conf_default_get_txn(conf, txn, legacy_param); } diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c index 82751293ef8a4276ad6d2f99d5cfe21356d0b7b7..64c7807301f46a59b02f548a780f43dae3f43912 100644 --- a/src/knot/conf/schema.c +++ b/src/knot/conf/schema.c @@ -230,6 +230,9 @@ static const yp_item_t desc_database[] = { { C_TIMER_DB, YP_TSTR, YP_VSTR = { "timers" } }, { C_TIMER_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(GIGA(100)), MEGA(100), YP_SSIZE } }, + { C_CATALOG_DB, YP_TSTR, YP_VSTR = { "catalog" } }, + { C_CATALOG_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)), + VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } }, { NULL } }; @@ -357,6 +360,7 @@ static const yp_item_t desc_policy[] = { { C_REFRESH_MAX_INTERVAL,YP_TINT, YP_VINT = { 2, UINT32_MAX, UINT32_MAX, YP_STIME } }, \ { C_REFRESH_MIN_INTERVAL,YP_TINT, YP_VINT = { 2, UINT32_MAX, 2, YP_STIME } }, \ { C_ADJUST_THR, YP_TINT, YP_VINT = { 1, UINT16_MAX, 1 } }, \ + { C_CATALOG_TPL, YP_TREF, YP_VREF = { C_TPL }, FLAGS, { check_ref } }, \ { C_MODULE, YP_TDATA, YP_VDATA = { 0, NULL, mod_id_to_bin, mod_id_to_txt }, \ YP_FMULTI | FLAGS, { check_modref } }, \ { C_COMMENT, YP_TSTR, YP_VNONE }, \ diff --git a/src/knot/conf/schema.h b/src/knot/conf/schema.h index 2932401302e0a2893628ec9b5121084489b08771..426d479e7af21510f89f0c151e8d2c9fdc563540 100644 --- a/src/knot/conf/schema.h +++ b/src/knot/conf/schema.h @@ -31,6 +31,9 @@ #define C_BACKEND "\x07""backend" #define C_BG_WORKERS "\x12""background-workers" #define C_BLOCK_NOTIFY_XFR "\x1B""block-notify-after-transfer" +#define C_CATALOG_DB "\x0A""catalog-db" +#define C_CATALOG_DB_MAX_SIZE "\x13""catalog-db-max-size" +#define C_CATALOG_TPL "\x10""catalog-template" #define C_CDS_CDNSKEY "\x13""cds-cdnskey-publish" #define C_CHK_INTERVAL "\x0E""check-interval" #define C_COMMENT "\x07""comment" diff --git a/src/knot/journal/knot_lmdb.c b/src/knot/journal/knot_lmdb.c index 7b247105093df997cdbc91a5c5019de71a89a04b..9d547a5866b74d5a61221e2e722c25ab6256c641 100644 --- a/src/knot/journal/knot_lmdb.c +++ b/src/knot/journal/knot_lmdb.c @@ -335,6 +335,17 @@ static bool curget(knot_lmdb_txn_t *txn, MDB_cursor_op op) return (txn->ret == KNOT_EOK); } +static int mdb_val_clone(const MDB_val *orig, MDB_val *clone) +{ + clone->mv_data = malloc(orig->mv_size); + if (clone->mv_data == NULL) { + return KNOT_ENOMEM; + } + clone->mv_size = orig->mv_size; + memcpy(clone->mv_data, orig->mv_data, clone->mv_size); + return KNOT_EOK; +} + bool knot_lmdb_find(knot_lmdb_txn_t *txn, MDB_val *what, knot_lmdb_find_t how) { if (!txn_semcheck(txn) || !init_cursor(txn) || !txn_enomem(txn, what)) { @@ -365,6 +376,28 @@ bool knot_lmdb_find(knot_lmdb_txn_t *txn, MDB_val *what, knot_lmdb_find_t how) return succ; } +// this is not bulletproof thread-safe (in case of LMDB fail-teardown, but mostly OK +int knot_lmdb_find_threadsafe(knot_lmdb_txn_t *txn, MDB_val *key, MDB_val *val, knot_lmdb_find_t how) +{ + assert(how == KNOT_LMDB_EXACT); + if (key->mv_data == NULL) { + return KNOT_ENOMEM; + } + if (!txn->opened) { + return KNOT_EINVAL; + } + if (txn->ret != KNOT_EOK) { + return txn->ret; + } + MDB_val tmp = { 0 }; + int ret = mdb_get(txn->txn, txn->db->dbi, key, &tmp); + err_to_knot(&ret); + if (ret == KNOT_EOK) { + ret = mdb_val_clone(&tmp, val); + } + return ret; +} + bool knot_lmdb_first(knot_lmdb_txn_t *txn) { return txn_semcheck(txn) && init_cursor(txn) && curget(txn, MDB_FIRST); diff --git a/src/knot/journal/knot_lmdb.h b/src/knot/journal/knot_lmdb.h index f4934cff5afb9993ca52631d44b5c26009976eb1..80eaf45ecfd173d79f5f47547285d955ef112f4c 100644 --- a/src/knot/journal/knot_lmdb.h +++ b/src/knot/journal/knot_lmdb.h @@ -144,7 +144,7 @@ void knot_lmdb_deinit(knot_lmdb_db_t *db); /*! * \brief Return true if DB is open. */ -inline static bool knot_lmdb_is_open(knot_lmdb_db_t *db) { return db->env != NULL; } +inline static bool knot_lmdb_is_open(knot_lmdb_db_t *db) { return db != NULL && db->env != NULL; } /*! * \brief Start a DB transaction. @@ -186,6 +186,21 @@ void knot_lmdb_commit(knot_lmdb_txn_t *txn); */ bool knot_lmdb_find(knot_lmdb_txn_t *txn, MDB_val *what, knot_lmdb_find_t how); +/*! + * \brief Simple database lookup in case txn shared among threads. + * + * \param txn DB transaction share among threads. + * \param key Key to be searched for. + * \param val Output: database value. + * \param how Must be KNOT_LMDB_EXACT. + * + * \note Free val->mv_data afterwards! + * + * \retval KNOT_ENOENT no such key in DB. + * \return KNOT_E* + */ +int knot_lmdb_find_threadsafe(knot_lmdb_txn_t *txn, MDB_val *key, MDB_val *val, knot_lmdb_find_t how); + /*! * \brief Start iteration the whole DB from lexicographically first key. * diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c index 32618ea882ddf3e1e04bcf7a66d36acd88d54183..2c52cf89b9b26b6c7dab55bd128167de76803919 100644 --- a/src/knot/nameserver/process_query.c +++ b/src/knot/nameserver/process_query.c @@ -423,6 +423,14 @@ static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, knot_layer_t *ctx qdata->extra->contents = qdata->extra->zone->contents; } + if (query_type(query) == KNOTD_QUERY_TYPE_NORMAL && + qdata->extra->zone != NULL && (qdata->extra->zone->flags & ZONE_IS_CATALOG)) { + if (!process_query_acl_check(conf(), ACL_ACTION_TRANSFER, qdata)) { + qdata->extra->zone = NULL; + qdata->extra->contents = NULL; + } + } + return KNOT_EOK; } diff --git a/src/knot/server/server.c b/src/knot/server/server.c index 154b4354c92c1240d777cac7d95bdcec0c17d0fc..9131b3f3f7a3e0994fe9353897ad438e7258a5e9 100644 --- a/src/knot/server/server.c +++ b/src/knot/server/server.c @@ -520,6 +520,19 @@ int server_init(server_t *server, int bg_workers) return KNOT_ENOMEM; } + int ret = knot_cat_update_init(&server->catalog_upd); + if (ret != KNOT_EOK) { + worker_pool_destroy(server->workers); + evsched_deinit(&server->sched); + return ret; + } + + char *catalog_dir = conf_db(conf(), C_CATALOG_DB); + conf_val_t catalog_size = conf_db_param(conf(), C_CATALOG_DB_MAX_SIZE, NULL); + knot_catalog_init(&server->catalog, catalog_dir, conf_int(&catalog_size)); + free(catalog_dir); + conf()->catalog = &server->catalog; + char *journal_dir = conf_db(conf(), C_JOURNAL_DB); conf_val_t journal_size = conf_db_param(conf(), C_JOURNAL_DB_MAX_SIZE, C_MAX_JOURNAL_DB_SIZE); conf_val_t journal_mode = conf_db_param(conf(), C_JOURNAL_DB_MODE, C_JOURNAL_DB_MODE); @@ -552,6 +565,10 @@ void server_deinit(server_t *server) } } + /* Free catalog zone context. */ + knot_cat_update_deinit(&server->catalog_upd); + knot_catalog_deinit(&server->catalog); + /* Free remaining interfaces. */ server_deinit_iface_list(server->ifaces, server->n_ifaces); diff --git a/src/knot/server/server.h b/src/knot/server/server.h index b8e3c3e4bd2091c3e65e8c307ee443bebd112c31..bc7dc634d94c0910738eb842a66e6af479754bc2 100644 --- a/src/knot/server/server.h +++ b/src/knot/server/server.h @@ -22,6 +22,7 @@ #include "knot/journal/knot_lmdb.h" #include "knot/server/dthreads.h" #include "knot/worker/pool.h" +#include "knot/zone/catalog.h" #include "knot/zone/zonedb.h" struct server; @@ -78,11 +79,11 @@ typedef struct server { /*! \brief Server state tracking. */ volatile unsigned state; - /*! \brief Zone database. */ knot_zonedb_t *zone_db; knot_lmdb_db_t timerdb; knot_lmdb_db_t journaldb; knot_lmdb_db_t kaspdb; + knot_catalog_t catalog; /*! \brief I/O handlers. */ struct { @@ -99,6 +100,9 @@ typedef struct server { /*! \brief List of interfaces. */ iface_t *ifaces; size_t n_ifaces; + + /*! \brief Pending changes to catalog member zones. */ + knot_cat_update_t catalog_upd; } server_t; /*! diff --git a/src/knot/updates/zone-update.c b/src/knot/updates/zone-update.c index b5d5fec54252b687a319f9b90bce5a2eeabf7bec..03ac4ef42b251199ab7bdbd3a69b74a9fd6b56a9 100644 --- a/src/knot/updates/zone-update.c +++ b/src/knot/updates/zone-update.c @@ -19,11 +19,14 @@ #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 "contrib/trim.h" #include "contrib/ucw/lists.h" +#include <signal.h> +#include <unistd.h> #include <urcu.h> // Call mem_trim() whenever accumuled size of updated zones reaches this size. @@ -703,6 +706,35 @@ static int commit_full(conf_t *conf, zone_update_t *update) return KNOT_EOK; } +static int update_catalog(conf_t *conf, zone_update_t *update) +{ + conf_val_t val = conf_zone_get(conf, C_CATALOG_TPL, update->zone->name); + if (val.code != KNOT_EOK) { + return val.code == KNOT_ENOENT ? KNOT_EOK : val.code; + } + + update->zone->flags |= ZONE_IS_CATALOG; + + int ret = KNOT_EOK; + if ((update->flags & UPDATE_INCREMENTAL)) { + ret = knot_cat_update_from_zone(update->zone->catalog_upd, update->change.remove, true, update->zone->catalog); + if (ret == KNOT_EOK) { + ret = knot_cat_update_from_zone(update->zone->catalog_upd, update->change.add, false, NULL); + } + } else { + ret = knot_cat_update_del_all(update->zone->catalog_upd, update->zone->catalog, update->zone->name); + if (ret == KNOT_EOK) { + ret = knot_cat_update_from_zone(update->zone->catalog_upd, update->zone->contents, false, NULL); + } + } + + if (ret == KNOT_EOK) { + kill(getpid(), SIGUSR1); + } + + return ret; +} + typedef struct { pthread_mutex_t lock; size_t counter; @@ -842,6 +874,11 @@ int zone_update_commit(conf_t *conf, zone_update_t *update) zone_contents_t *old_contents; old_contents = zone_switch_contents(update->zone, update->new_cont); + ret = commit_catalog(conf, update); + if (ret != KNOT_EOK) { + log_zone_warning(update->zone->name, "catalog zone not fully populated (%s)", knot_strerror(ret)); + } + if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) { changeset_clear(&update->change); changeset_clear(&update->extra_ch); diff --git a/src/knot/zone/catalog.c b/src/knot/zone/catalog.c new file mode 100644 index 0000000000000000000000000000000000000000..c33733b566c445ad910d236c50d6a87649f03d43 --- /dev/null +++ b/src/knot/zone/catalog.c @@ -0,0 +1,416 @@ +/* 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 "knot/common/log.h" +#include "knot/conf/conf.h" +#include "knot/zone/contents.h" + +#define CATALOG_VERSION "1.0" + +const MDB_val knot_catalog_iter_prefix = { 1, "" }; + +void knot_catalog_init(knot_catalog_t *cat, const char *path, size_t mapsize) +{ + knot_lmdb_init(&cat->db, path, mapsize, 0, NULL); +} + +int knot_catalog_open(knot_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->txn.opened) { + knot_lmdb_begin(&cat->db, &cat->txn, !(cat->db.env_flags & MDB_RDONLY)); + } + if (cat->txn.ret == KNOT_EOK) { + MDB_val key = { 8, "\x01version" }; + if (knot_lmdb_find(&cat->txn, &key, KNOT_LMDB_EXACT)) { + if (strncmp(CATALOG_VERSION, cat->txn.cur_val.mv_data, + cat->txn.cur_val.mv_size) != 0) { + log_warning("unmatching catalog version"); + } + } else if (!(cat->db.env_flags & MDB_RDONLY)) { + MDB_val val = { strlen(CATALOG_VERSION), CATALOG_VERSION }; + knot_lmdb_insert(&cat->txn, &key, &val); + } + } + return cat->txn.ret; +} + +int knot_catalog_deinit(knot_catalog_t *cat) +{ + if (cat->txn.opened) { + knot_lmdb_commit(&cat->txn); + } + knot_lmdb_deinit(&cat->db); + return cat->txn.ret; +} + +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 knot_catalog_add(knot_catalog_t *cat, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catzone) +{ + int ret = knot_catalog_open(cat); + if (ret != KNOT_EOK) { + return ret; + } + 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->txn, &key, &val); + free(key.mv_data); + free(val.mv_data); + return cat->txn.ret; +} + +int knot_catalog_del(knot_catalog_t *cat, const knot_dname_t *member) +{ + MDB_val key = knot_lmdb_make_key("BN", 0, member); + knot_lmdb_del_prefix(&cat->txn, &key); // deletes one record + free(key.mv_data); + return cat->txn.ret; +} + +void knot_catalog_curval(knot_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->txn.cur_key.mv_data, cat->txn.cur_key.mv_size, + "BN", &zero, member); + } + const knot_dname_t *ow; + knot_lmdb_unmake_curval(&cat->txn, "BBN", &zero, &shift, &ow); + if (owner != NULL) { + *owner = ow; + } + if (catzone != NULL) { + *catzone = ow + shift; + } +} + +int knot_catalog_get_catzone(knot_catalog_t *cat, const knot_dname_t *member, + const knot_dname_t **catzone) +{ + if (!knot_lmdb_is_open(&cat->db)) { + return KNOT_ENOENT; + } + + MDB_val key = knot_lmdb_make_key("BN", 0, member); + if (knot_lmdb_find(&cat->txn, &key, KNOT_LMDB_EXACT)) { + knot_catalog_curval(cat, NULL, NULL, catzone); + free(key.mv_data); + return KNOT_EOK; + } + free(key.mv_data); + return MIN(cat->txn.ret, KNOT_ENOENT); +} + +int knot_cat_get_catzone_thrsafe(knot_catalog_t *cat, const knot_dname_t *member, + knot_dname_t **catzone) +{ + if (!knot_lmdb_is_open(&cat->db)) { + return KNOT_ENOENT; + } + + MDB_val key = knot_lmdb_make_key("BN", 0, member), val = { 0 }; + int ret = knot_lmdb_find_threadsafe(&cat->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); + *catzone = knot_dname_copy(ow + shift, NULL); + if (*catzone == NULL) { + ret = KNOT_ENOMEM; + } + free(val.mv_data); + } + free(key.mv_data); + return ret; +} + +knot_cat_find_res_t knot_catalog_find(knot_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); + int ret = MEMBER_NONE; + if (knot_lmdb_find(&cat->txn, &key, KNOT_LMDB_EXACT)) { + const knot_dname_t *ow, *cz; + knot_catalog_curval(cat, NULL, &ow, &cz); + if (!knot_dname_is_equal(cz, catzone)) { + ret = MEMBER_ZONE; + } else if (!knot_dname_is_equal(ow, owner)) { + ret = MEMBER_OWNER; + } else { + ret = MEMBER_EXACT; + } + } + if (cat->txn.ret != KNOT_EOK) { + ret = MEMBER_ERROR; + } + free(key.mv_data); + return ret; +} + +int knot_cat_update_init(knot_cat_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 knot_cat_update_clear(knot_cat_update_t *u) +{ + trie_apply(u->add, freecb, NULL); + trie_clear(u->add); + trie_apply(u->rem, freecb, NULL); + trie_clear(u->rem); +} + +void knot_cat_update_deinit(knot_cat_update_t *u) +{ + pthread_mutex_destroy(&u->mutex); + trie_free(u->add); + trie_free(u->rem); +} + +int knot_cat_update_add(knot_cat_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) { + knot_cat_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); + + knot_cat_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; +} + +knot_cat_upd_val_t *knot_cat_update_get(knot_cat_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 : *(knot_cat_upd_val_t **)found; +} + +typedef struct { + knot_cat_update_t *u; + const knot_dname_t *apex; + bool remove; + knot_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 && + knot_catalog_find(ctx->check, member, node->owner, ctx->apex) != MEMBER_EXACT) { + rdata = knot_rdataset_next(rdata); + continue; + } + ret = knot_cat_update_add(ctx->u, member, node->owner, ctx->apex, ctx->remove); + rdata = knot_rdataset_next(rdata); + } + return ret; +} + +int knot_cat_update_from_zone(knot_cat_update_t *u, struct zone_contents *zone, + bool remove, knot_catalog_t *check) +{ + cat_upd_ctx_t ctx = { u, zone->apex->owner, remove, check }; + pthread_mutex_lock(&u->mutex); + int ret = zone_contents_apply(zone, cat_update_add_node, &ctx); + pthread_mutex_unlock(&u->mutex); + return ret; +} + +int knot_cat_update_del_all(knot_cat_update_t *u, knot_catalog_t *cat, const knot_dname_t *zone) +{ + int ret = knot_catalog_open(cat); + if (ret != KNOT_EOK) { + return ret; + } + + pthread_mutex_lock(&u->mutex); + knot_catalog_foreach(cat) { // TODO possible speedup by indexing which member zones belong to a catalog zone + const knot_dname_t *mem, *ow, *cz; + knot_catalog_curval(cat, &mem, &ow, &cz); + if (knot_dname_is_equal(cz, zone)) { + ret = knot_cat_update_add(u, mem, ow, cz, true); + if (ret != KNOT_EOK) { + pthread_mutex_unlock(&u->mutex); + return ret; + } + } + } + pthread_mutex_unlock(&u->mutex); + return cat->txn.ret; +} + +static void print_dname(const knot_dname_t *d) +{ + char tmp[KNOT_DNAME_TXT_MAXLEN]; + 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 knot_cat_update_print(const char *intro, knot_catalog_t *cat, knot_cat_update_t *u) +{ + ssize_t cattot = 0, uplus = 0, uminus = 0; + + printf("Catalog (%s)\n", intro); + + if (cat != NULL) { + int ret = knot_catalog_open(cat); + if (ret != KNOT_EOK) { + printf("Catalog print failed (%s)\n", knot_strerror(ret)); + return; + } + + knot_catalog_foreach(cat) { + const knot_dname_t *mem, *ow, *cz; + knot_catalog_curval(cat, &mem, &ow, &cz); + print_dname3("*", mem, ow, cz, ""); + cattot++; + } + } + if (u != NULL) { + knot_cat_it_t *it = knot_cat_it_begin(u, true); + while (!knot_cat_it_finised(it)) { + knot_cat_upd_val_t *val = knot_cat_it_val(it); + print_dname3("-", val->member, val->owner, val->catzone, ""); + uminus++; + knot_cat_it_next(it); + } + knot_cat_it_free(it); + + it = knot_cat_it_begin(u, false); + while (!knot_cat_it_finised(it)) { + knot_cat_upd_val_t *val = knot_cat_it_val(it); + print_dname3("+", val->member, val->owner, val->catzone, val->just_reconf ? "JR" : ""); + uplus++; + knot_cat_it_next(it); + } + knot_cat_it_free(it); + } + printf("Catalog: *%zd -%zd +%zd\n", cattot, uminus, uplus); +} diff --git a/src/knot/zone/catalog.h b/src/knot/zone/catalog.h new file mode 100644 index 0000000000000000000000000000000000000000..54d2af63cbed3c8efcab58ccef6a033de943d7cd --- /dev/null +++ b/src/knot/zone/catalog.h @@ -0,0 +1,128 @@ +/* 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 knot_catalog { + knot_lmdb_db_t db; + knot_lmdb_txn_t txn; // RW transaction open all the time +} knot_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 +} knot_cat_find_res_t; + +typedef struct { + trie_t *rem; + trie_t *add; + pthread_mutex_t mutex; +} knot_cat_update_t; + +typedef struct { + knot_dname_t *member; + knot_dname_t *owner; + knot_dname_t *catzone; + bool just_reconf; +} knot_cat_upd_val_t; + +extern const MDB_val knot_catalog_iter_prefix; + +void knot_catalog_init(knot_catalog_t *cat, const char *path, size_t mapsize); + +int knot_catalog_open(knot_catalog_t *cat); + +int knot_catalog_deinit(knot_catalog_t *cat); + +int knot_catalog_add(knot_catalog_t *cat, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catzone); + +inline static int knot_catalog_add2(knot_catalog_t *cat, const knot_cat_upd_val_t *val) +{ + return knot_catalog_add(cat, val->member, val->owner, val->catzone); +} + +int knot_catalog_del(knot_catalog_t *cat, const knot_dname_t *member); + +inline static int knot_catalog_del2(knot_catalog_t *cat, const knot_cat_upd_val_t *val) +{ + assert(!val->just_reconf); // just re-add in this case + return knot_catalog_del(cat, val->member); +} + +#define knot_catalog_foreach(cat) knot_lmdb_foreach(&(cat)->txn, (MDB_val *)&knot_catalog_iter_prefix) + +void knot_catalog_curval(knot_catalog_t *cat, const knot_dname_t **member, + const knot_dname_t **owner, const knot_dname_t **catzone); + +int knot_catalog_get_catzone(knot_catalog_t *cat, const knot_dname_t *member, + const knot_dname_t **catzone); + +int knot_cat_get_catzone_thrsafe(knot_catalog_t *cat, const knot_dname_t *member, + knot_dname_t **catzone); + +knot_cat_find_res_t knot_catalog_find(knot_catalog_t *cat, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catzone); + +int knot_cat_update_init(knot_cat_update_t *u); + +void knot_cat_update_clear(knot_cat_update_t *u); + +void knot_cat_update_deinit(knot_cat_update_t *u); + +int knot_cat_update_add(knot_cat_update_t *u, const knot_dname_t *member, + const knot_dname_t *owner, const knot_dname_t *catzone, + bool remove); + +knot_cat_upd_val_t *knot_cat_update_get(knot_cat_update_t *u, const knot_dname_t *member, bool remove); + +struct zone_contents; + +int knot_cat_update_from_zone(knot_cat_update_t *u, struct zone_contents *zone, + bool remove, knot_catalog_t *check); + +int knot_cat_update_del_all(knot_cat_update_t *u, knot_catalog_t *cat, const knot_dname_t *zone); + +typedef trie_it_t knot_cat_it_t; + +inline static knot_cat_it_t *knot_cat_it_begin(knot_cat_update_t *u, bool remove) +{ + return trie_it_begin(remove ? u->rem : u->add); +} + +inline static knot_cat_upd_val_t *knot_cat_it_val(knot_cat_it_t *it) +{ + return *(knot_cat_upd_val_t **)trie_it_val(it); +} + +inline static bool knot_cat_it_finised(knot_cat_it_t *it) +{ + return it == NULL || trie_it_finished(it); +} + +#define knot_cat_it_next trie_it_next +#define knot_cat_it_free trie_it_free + +void knot_cat_update_print(const char *intro, knot_catalog_t *cat, knot_cat_update_t *u); diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h index bed37ec054fab7b92b3558a01e9d3788170faf50..3af58fa38b1c0485e9c1a75b0c36a2ebf09761ca 100644 --- a/src/knot/zone/zone.h +++ b/src/knot/zone/zone.h @@ -22,6 +22,7 @@ #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" @@ -38,6 +39,8 @@ typedef enum zone_flag_t { ZONE_FORCE_FLUSH = 1 << 2, /*!< Force zone flush. */ ZONE_FORCE_KSK_ROLL = 1 << 3, /*!< Force KSK/CSK rollover. */ ZONE_FORCE_ZSK_ROLL = 1 << 4, /*!< Force ZSK rollover. */ + ZONE_IS_CATALOG = 1 << 5, /*!< This is a catalog. */ + ZONE_IS_CAT_MEMBER = 1 << 6, /*!< This zone exists according to a catalog. */ } zone_flag_t; /*! @@ -82,6 +85,10 @@ typedef struct zone /*! \brief Ptr to journal DB (in struct server) */ knot_lmdb_db_t *kaspdb; + /*! \brief Ptr to catalog and ist changeset changes (in struct server) */ + knot_catalog_t *catalog; + knot_cat_update_t *catalog_upd; + /*! \brief Preferred master lock. */ pthread_mutex_t preferred_lock; /*! \brief Preferred master for remote operation. */ diff --git a/src/knot/zone/zonedb-load.c b/src/knot/zone/zonedb-load.c index e9922ae0f343e16f2784be708e0072cb06f6ac90..9ffb147a699dc16f4577f9b4a6202b561cb58fdd 100644 --- a/src/knot/zone/zonedb-load.c +++ b/src/knot/zone/zonedb-load.c @@ -15,11 +15,14 @@ */ #include <assert.h> +#include <unistd.h> #include <urcu.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" @@ -34,15 +37,22 @@ static bool zone_file_updated(conf_t *conf, const zone_t *old_zone, assert(conf); assert(zone_name); + if (old_zone == NULL) { + return false; + } + char *zonefile = conf_zonefile(conf, zone_name); struct timespec mtime; int ret = zonefile_exists(zonefile, &mtime); free(zonefile); - return (ret == KNOT_EOK && old_zone != NULL && - !(old_zone->zonefile.exists && - old_zone->zonefile.mtime.tv_sec == mtime.tv_sec && - old_zone->zonefile.mtime.tv_nsec == mtime.tv_nsec)); + if (ret == KNOT_EOK) { + return !(old_zone->zonefile.exists && + old_zone->zonefile.mtime.tv_sec == mtime.tv_sec && + old_zone->zonefile.mtime.tv_nsec == mtime.tv_nsec); + } else { + return old_zone->zonefile.exists; + } } static zone_t *create_zone_from(const knot_dname_t *name, server_t *server) @@ -54,6 +64,8 @@ static zone_t *create_zone_from(const knot_dname_t *name, server_t *server) zone->journaldb = &server->journaldb; zone->kaspdb = &server->kaspdb; + zone->catalog = &server->catalog; + zone->catalog_upd = &server->catalog_upd; int result = zone_events_setup(zone, server->workers, &server->sched); if (result != KNOT_EOK) { @@ -115,6 +127,7 @@ static zone_t *create_zone_reload(conf_t *conf, const knot_dname_t *name, } zone->contents = old_zone->contents; + zone->flags = (old_zone->flags & (ZONE_IS_CATALOG | ZONE_IS_CAT_MEMBER)); zone->timers = old_zone->timers; timers_sanitize(conf, zone); @@ -209,18 +222,134 @@ static void mark_changed_zones(knot_zonedb_t *zonedb, trie_t *changed) trie_it_free(it); } +static void zone_purge(conf_t *conf, zone_t *zone, server_t *server) +{ + (void)zone_timers_sweep(&server->timerdb, (sweep_cb)knot_dname_cmp, zone->name); + + conf_val_t sync = conf_zone_get(conf, C_ZONEFILE_SYNC, zone->name); + if (conf_int(&sync) > -1) { + char *zonefile = conf_zonefile(conf, zone->name); + (void)unlink(zonefile); + free(zonefile); + } + + (void)journal_scrape_with_md(zone_journal(zone), true); + if (knot_lmdb_open(zone->kaspdb) == KNOT_EOK) { + (void)kasp_db_delete_all(zone->kaspdb, zone->name); + } +} + +static zone_contents_t *zone_expire(zone_t *zone) +{ + zone->timers.next_refresh = time(NULL); + return zone_switch_contents(zone, NULL); +} + +static bool check_open_catalog(knot_catalog_t *cat) { + if (knot_lmdb_exists(&cat->db)) { + int ret = knot_catalog_open(cat); + if (ret != KNOT_EOK) { + log_error("failed to open existing zone catalog"); + } else { + return true; + } + } + return false; +} + +static zone_t *reuse_member_zone(zone_t *zone, server_t *server, conf_t *conf, + list_t *expired_contents) +{ + if (!(zone->flags & ZONE_IS_CAT_MEMBER)) { + return NULL; + } + + knot_cat_upd_val_t *upd = knot_cat_update_get(&server->catalog_upd, zone->name, true); + if (upd != NULL) { + if (upd->just_reconf) { + 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); + } else { + return NULL; // zone to be removed + } + } + + zone_t *newzone = create_zone(conf, zone->name, server, zone); + if (newzone == NULL) { + log_zone_error(zone->name, "zone cannot be created"); + } else { + assert(newzone->flags & ZONE_IS_CAT_MEMBER); + conf_activate_modules(conf, server, newzone->name, &newzone->query_modules, + &newzone->query_plan); + } + return newzone; +} + +// cold start of knot: add unchanged member zone to zonedb +static zone_t *reuse_cold_zone(const knot_dname_t *zname, server_t *server, conf_t *conf) +{ + knot_cat_upd_val_t *upd = knot_cat_update_get(&server->catalog_upd, zname, true); + if (upd != NULL && !upd->just_reconf) { + return NULL; // zone will be removed immediately + } + + zone_t *zone = create_zone(conf, zname, server, NULL); + if (zone == NULL) { + log_zone_error(zname, "zone cannot be created"); + } else { + zone->flags |= ZONE_IS_CAT_MEMBER; + conf_activate_modules(conf, server, zone->name, &zone->query_modules, + &zone->query_plan); + } + return zone; +} + +static zone_t *add_member_zone(knot_cat_upd_val_t *val, knot_zonedb_t *check, server_t *server, conf_t *conf) +{ + if (val->just_reconf) { + return NULL; + } + + if (knot_zonedb_find(check, val->member) != NULL) { + log_zone_warning(val->member, "zone already configured, skipping creation"); + return NULL; + } + + int ret = knot_catalog_add2(&server->catalog, val); + if (ret != KNOT_EOK) { + log_zone_error(val->member, "failed adding member zone to catalog (%s)", + knot_strerror(ret)); + return NULL; + } + + zone_t *zone = create_zone(conf, val->member, server, NULL); + if (zone == NULL) { + log_zone_error(val->member, "zone cannot be created"); + knot_catalog_del2(conf->catalog, val); + } else { + zone->flags |= ZONE_IS_CAT_MEMBER; + conf_activate_modules(conf, server, zone->name, &zone->query_modules, + &zone->query_plan); + log_zone_info(val->member, "zone added from catalog"); + } + return zone; +} + /*! * \brief Create new zone database. * * Zones that should be retained are just added from the old database to the * new. New zones are loaded. * - * \param conf New server configuration. - * \param server Server instance. + * \param conf New server configuration. + * \param server Server instance. + * \param expired_contents Out: ptrlist of zone_contents_t to be deep freed after sync RCU. * * \return New zone database. */ -static knot_zonedb_t *create_zonedb(conf_t *conf, server_t *server) +static knot_zonedb_t *create_zonedb(conf_t *conf, server_t *server, list_t *expired_contents) { assert(conf); assert(server); @@ -265,6 +394,38 @@ static knot_zonedb_t *create_zonedb(conf_t *conf, server_t *server) knot_zonedb_insert(db_new, zone); } + if (db_old != NULL) { + knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_old); + while (!knot_zonedb_iter_finished(it)) { + zone_t *newzone = reuse_member_zone(knot_zonedb_iter_val(it), + server, conf, expired_contents); + if (newzone != NULL) { + knot_zonedb_insert(db_new, newzone); + } + knot_zonedb_iter_next(it); + } + knot_zonedb_iter_free(it); + } else if (check_open_catalog(&server->catalog)) { + knot_catalog_foreach(&server->catalog) { + const knot_dname_t *member = NULL; + knot_catalog_curval(&server->catalog, &member, NULL, NULL); + zone_t *zone = reuse_cold_zone(member, server, conf); + if (zone != NULL) { + knot_zonedb_insert(db_new, zone); + } + } + } + + knot_cat_it_t *it = knot_cat_it_begin(&server->catalog_upd, false); + while (!knot_cat_it_finised(it)) { + zone_t *zone = add_member_zone(knot_cat_it_val(it), db_new, server, conf); + if (zone != NULL) { + knot_zonedb_insert(db_new, zone); + } + knot_cat_it_next(it); + } + knot_cat_it_free(it); + return db_new; } @@ -276,23 +437,23 @@ static knot_zonedb_t *create_zonedb(conf_t *conf, server_t *server) * * \param conf New server configuration. * \param db_old Old zone database to remove. - * \param db_new New zone database for comparison if full reload. + * \param server Server context. */ static void remove_old_zonedb(conf_t *conf, knot_zonedb_t *db_old, - knot_zonedb_t *db_new) + server_t *server) { - if (db_old == NULL) { - return; - } + knot_zonedb_t *db_new = server->zone_db; bool full = !(conf->io.flags & CONF_IO_FACTIVE) || (conf->io.flags & CONF_IO_FRLD_ZONES); - knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_old); + if (db_old == NULL) { + goto catalog_only; + } + 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); - if (full) { /* Check if reloaded (reused contents). */ if (knot_zonedb_find(db_new, zone->name)) { @@ -316,6 +477,25 @@ static void remove_old_zonedb(conf_t *conf, knot_zonedb_t *db_old, knot_zonedb_iter_free(it); +catalog_only: + ; /* Remove deleted cataloged zones from conf. */ + knot_cat_it_t *tit = knot_cat_it_begin(&server->catalog_upd, true); + while (!knot_cat_it_finised(tit)) { + knot_cat_upd_val_t *val = knot_cat_it_val(tit); + if (!val->just_reconf) { + knot_catalog_del(&server->catalog, val->member); + zone_t *zone = knot_zonedb_find(db_old, val->member); + if (zone != NULL) { + zone_purge(conf, zone, server); + } + } + knot_cat_it_next(tit); + } + knot_cat_it_free(tit); + + /* Clear catalog changes. No need to use mutex as this is done from main thread while all zone events are paused. */ + knot_cat_update_clear(&server->catalog_upd); + if (full) { knot_zonedb_deep_free(&db_old, false); } else { @@ -329,8 +509,11 @@ void zonedb_reload(conf_t *conf, server_t *server) return; } + list_t contents_tofree; + init_list(&contents_tofree); + /* Insert all required zones to the new zone DB. */ - knot_zonedb_t *db_new = create_zonedb(conf, server); + knot_zonedb_t *db_new = create_zonedb(conf, server, &contents_tofree); if (db_new == NULL) { log_error("failed to create new zone database"); return; @@ -343,6 +526,8 @@ void zonedb_reload(conf_t *conf, server_t *server) /* Wait for readers to finish reading old zone database. */ synchronize_rcu(); + ptrlist_free_custom(&contents_tofree, NULL, (ptrlist_free_cb)zone_contents_deep_free); + /* Remove old zone DB. */ - remove_old_zonedb(conf, db_old, db_new); + remove_old_zonedb(conf, db_old, server); } diff --git a/src/knot/zone/zonedb.c b/src/knot/zone/zonedb.c index 9d28fd0be33b60c610505a63f1a55172ac97a06b..af8050510d03ba99ad87b202d625667b514c937a 100644 --- a/src/knot/zone/zonedb.c +++ b/src/knot/zone/zonedb.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* 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 @@ -26,8 +26,11 @@ /*! \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))) { + if (conf_rawid_exists(conf(), C_ZONE, zone->name, knot_dname_size(zone->name)) || + knot_catalog_get_catzone(conf()->catalog, zone->name, &unused) == KNOT_EOK) { uint32_t journal_serial, zone_serial = zone_contents_serial(zone->contents); bool exists; diff --git a/src/libknot/rrtype/rdname.h b/src/libknot/rrtype/rdname.h index 6f4c09c9ab67e0729b994d69695682dea21e3096..e2b10c47fabce215df804cfb62149e0a54cb3e0c 100644 --- a/src/libknot/rrtype/rdname.h +++ b/src/libknot/rrtype/rdname.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* 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 @@ -48,6 +48,13 @@ const knot_dname_t *knot_ns_name(const knot_rdata_t *rdata) return rdata->data; } +static inline +const knot_dname_t *knot_ptr_name(const knot_rdata_t *rdata) +{ + assert(rdata); + return rdata->data; +} + static inline const knot_dname_t *knot_mx_name(const knot_rdata_t *rdata) { @@ -69,6 +76,8 @@ const knot_dname_t *knot_rdata_name(const knot_rdata_t *rdata, uint16_t type) switch (type) { case KNOT_RRTYPE_NS: return knot_ns_name(rdata); + case KNOT_RRTYPE_PTR: + return knot_ptr_name(rdata); case KNOT_RRTYPE_MX: return knot_mx_name(rdata); case KNOT_RRTYPE_SRV: diff --git a/src/utils/knotd/main.c b/src/utils/knotd/main.c index e2ad5e01052477a4845c917f32a4659b7cab522a..7dc2b8d054105e9aae74daf9172bb68b6143b50a 100644 --- a/src/utils/knotd/main.c +++ b/src/utils/knotd/main.c @@ -51,6 +51,7 @@ /* Signal flags. */ static volatile bool sig_req_stop = false; static volatile bool sig_req_reload = false; +static volatile bool sig_req_zones_reload = false; /* \brief Signal started state to the init system. */ static void init_signal_started(void) @@ -125,6 +126,7 @@ struct signal { /*! \brief Signals used by the server. */ static const struct signal SIGNALS[] = { { SIGHUP, true }, /* Reload server. */ + { SIGUSR1, true }, /* Reload zones. */ { SIGINT, true }, /* Terminate server. */ { SIGTERM, true }, /* Terminate server. */ { SIGALRM, false }, /* Internal thread synchronization. */ @@ -139,6 +141,9 @@ static void handle_signal(int signum) case SIGHUP: sig_req_reload = true; break; + case SIGUSR1: + sig_req_zones_reload = true; + break; case SIGINT: case SIGTERM: if (sig_req_stop) { @@ -263,6 +268,10 @@ static void event_loop(server_t *server, const char *socket) sig_req_reload = false; server_reload(server); } + if (sig_req_zones_reload) { + sig_req_zones_reload = false; + server_update_zones(conf(), server); + } // Update control timeout. knot_ctl_set_timeout(ctl, conf()->cache.ctl_timeout); diff --git a/tests-extra/tests/zone/catalog/data/catalog1.zone b/tests-extra/tests/zone/catalog/data/catalog1.zone new file mode 100644 index 0000000000000000000000000000000000000000..b72a57a33687a4a6ef049af60b4d264147968c6d --- /dev/null +++ b/tests-extra/tests/zone/catalog/data/catalog1.zone @@ -0,0 +1,7 @@ +$ORIGIN catalog1. +$TTL 0 + +@ SOA ns admin 1 25 25 80 600 + NS ns +ns AAAA ::0 +foo.bar PTR cataloged1. diff --git a/tests-extra/tests/zone/catalog/data/cataloged1.zone b/tests-extra/tests/zone/catalog/data/cataloged1.zone new file mode 100644 index 0000000000000000000000000000000000000000..67031c7144f1ece5eec8f06746c6f0871e9e9597 --- /dev/null +++ b/tests-extra/tests/zone/catalog/data/cataloged1.zone @@ -0,0 +1,6 @@ +$ORIGIN cataloged1. +$TTL 1200 + +@ SOA ns admin 10001 25 25 80 600 + NS ns +ns AAAA ::0 diff --git a/tests-extra/tests/zone/catalog/data/cataloged2.zone b/tests-extra/tests/zone/catalog/data/cataloged2.zone new file mode 100644 index 0000000000000000000000000000000000000000..29aea7fddfeff424a7bedea40c9f49cb25bdc87f --- /dev/null +++ b/tests-extra/tests/zone/catalog/data/cataloged2.zone @@ -0,0 +1,6 @@ +$ORIGIN cataloged2. +$TTL 1200 + +@ SOA ns admin 1 25 25 80 600 + NS ns +ns AAAA ::0 diff --git a/tests-extra/tests/zone/catalog/test.py b/tests-extra/tests/zone/catalog/test.py new file mode 100644 index 0000000000000000000000000000000000000000..5077f9fa349c331e6738ba4a1b16652097211e1a --- /dev/null +++ b/tests-extra/tests/zone/catalog/test.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 + +'''Test of Catalog zones.''' + +from dnstest.test import Test +from dnstest.utils import set_err, detail_log +import dnstest.params + +import glob +import shutil +from subprocess import DEVNULL, PIPE, Popen +import subprocess + +def check_keys(server, zone_name, expect_keys): + cmd = Popen([dnstest.params.keymgr_bin, "-d", server.dir + "/keys", zone_name, "list"], stdout=PIPE, stderr=PIPE, universal_newlines=True) + (stdout, stderr) = cmd.communicate() + lines = len(stdout.splitlines()) + if lines != expect_keys: + set_err("CHECK # of KEYS (%d != %d)" % (lines, expect_keys)) + +t = Test() + +master = t.server("knot") +slave = t.server("knot") + +# Zone setup +zone = t.zone("example.com.") + t.zone("catalog1.", storage=".") + +t.link(zone, master, slave, ixfr=True) + +master.zones["catalog1."].catalog = True +slave.zones["catalog1."].catalog = True + +slave.dnssec(zone[1]).enable = True + +for zf in glob.glob(t.data_dir + "/*.zone"): + shutil.copy(zf, master.dir + "/master") + +t.start() + +# Basic: master a slave configure cataloged zone. +t.sleep(5) +resp = master.dig("cataloged1.", "SOA") +resp.check(rcode="NOERROR") +resp = slave.dig("cataloged1.", "DNSKEY", dnssec=True) +resp.check(rcode="NOERROR") +resp.check_count(2, "DNSKEY") +resp.check_count(1, "RRSIG") + +# Udating a cataloged zone +subprocess.run(["sed", "-i", "s/10001/10002/;$s/$/\\nxyz A 1.2.3.4/", master.dir + "/master/cataloged1.zone"]) +master.ctl("zone-reload cataloged1.") +t.sleep(4) +resp = slave.dig("xyz.cataloged1.", "A", dnssec=True) +resp.check(rcode="NOERROR") +resp.check_count(1, "RRSIG") + +check_keys(slave, "cataloged1", 2) + +# Check adding cataloged zone. +up = master.update(zone[1]) +up.add("bar.catalog1.", 0, "PTR", "cataloged2.") +up.send("NOERROR") +t.sleep(6) +resp = master.dig("cataloged2.", "NS") +resp.check(rcode="NOERROR") +resp = slave.dig("cataloged2.", "DNSKEY", dnssec=True) +resp.check(rcode="NOERROR") +resp.check_count(2, "DNSKEY") +resp.check_count(1, "RRSIG") + +# Check that addition didn't delete prvious +resp = master.dig("cataloged1.", "SOA") +resp.check(rcode="NOERROR") +resp = slave.dig("cataloged1.", "SOA", dnssec=True) +resp.check(rcode="NOERROR") +resp.check_count(1, "RRSIG") + +# Check remove-adding tha same catalog record: shall not purge it +resp0 = slave.dig("cataloged2.", "DNSKEY") +resp0.check_count(2, "DNSKEY") +dnskey0 = resp0.resp.answer[0].to_rdataset()[0] +up = master.update(zone[1]) +up.delete("bar.catalog1.", "PTR", "cataloged2.") +up.add("bar.catalog1.", 0, "PTR", "cataloged2.") +up.send("NOERROR") +t.sleep(4) +resp1 = slave.dig("cataloged2.", "DNSKEY") +resp1.check_count(2, "DNSKEY") +match = 0 +if resp1.count("DNSKEY") > 0: + for dnskey1 in resp1.resp.answer[0].to_rdataset(): + if dnskey1.to_text() == dnskey0.to_text(): + match = match + 1 +if match < 1: + set_err("ZONE PURGED") + dnskey1 = dnskey0 +else: + dnskey1 = resp1.resp.answer[0].to_rdataset()[0] + +# Check remove-adding the zone: shall effectively purge it +up = master.update(zone[1]) +up.delete("bar.catalog1.", "PTR", "cataloged2.") +up.add("bar2.catalog1.", 0, "PTR", "cataloged2.") +up.send("NOERROR") +t.sleep(4) +shutil.copy(t.data_dir + "/cataloged2.zone", master.dir + "/master") # because the purge deletes even zonefile +master.ctl("zone-reload cataloged2.") +t.sleep(6) +resp2 = slave.dig("cataloged2.", "DNSKEY") +resp2.check_count(2, "DNSKEY") +if resp2.count("DNSKEY") > 0: + for dnskey2 in resp2.resp.answer[0].to_rdataset(): + if dnskey1.to_text() == dnskey2.to_text(): + set_err("ZONE NOT PURGED") + +# Check persistence after server restart +slave.stop() +slave.start() +t.sleep(8) +resp = master.dig("cataloged1.", "SOA") +resp.check(rcode="NOERROR") +resp = slave.dig("cataloged1.", "SOA", dnssec=True) +resp.check(rcode="NOERROR") +resp.check_count(1, "RRSIG") +resp = master.dig("cataloged2.", "SOA") +resp.check(rcode="NOERROR") +resp = slave.dig("cataloged2.", "SOA", dnssec=True) +resp.check(rcode="NOERROR") +resp.check_count(1, "RRSIG") + +# Check adding and removing duplicate +up = master.update(zone[1]) +up.add("bar3.catalog1.", 0, "PTR", "cataloged2.") +up.send("NOERROR") +t.sleep(6) +up = master.update(zone[1]) +up.delete("bar3.catalog1.", "PTR") +up.send("NOERROR") +t.sleep(6) +resp = master.dig("cataloged2.", "SOA") +resp.check(rcode="NOERROR") +resp = slave.dig("cataloged2.", "SOA", dnssec=True) +resp.check(rcode="NOERROR") +check_keys(slave, "cataloged2", 2) + +# Check removing cataloged zone +up = master.update(zone[1]) +up.delete("foo.bar.catalog1.", "PTR") +up.send("NOERROR") +t.sleep(6) +resp = master.dig("cataloged1.", "SOA") +resp.check(rcode="REFUSED") +resp = slave.dig("cataloged1.", "DNSKEY") +resp.check(rcode="REFUSED") +check_keys(slave, "cataloged1", 0) + +# Check inaccessibility of catalog zone +slave.ctl("conf-begin") +slave.ctl("conf-unset zone[catalog1.].acl") # remove transfer-related ACLs +slave.ctl("conf-commit") +t.sleep(3) +resp = slave.dig("abc.catalog1.", "A") +resp.check(rcode="REFUSED") + +t.end() diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py index 1114fa4ae6edc755f5c6932b8f3e8934856351a1..0b37f2c5799d61c03b9e67a33234f31a2c97a995 100644 --- a/tests-extra/tools/dnstest/server.py +++ b/tests-extra/tools/dnstest/server.py @@ -76,6 +76,7 @@ class Zone(object): self.journal_content = journal_content # journal contents self.modules = [] self.dnssec = ZoneDnssec() + self.catalog = None @property def name(self): @@ -1314,6 +1315,49 @@ class Knot(Server): s.item("global-module", "[%s]" % modules) if self.zone_size_limit: s.item("zone-max-size", self.zone_size_limit) + + have_catalog = None + for zone in self.zones: + z = self.zones[zone] + if z.catalog: + have_catalog = z + if have_catalog is not None: + s.id_item("id", "catemplate") + s.item_str("file", self.dir + "/master/%s.zone") + s.item_str("zonefile-load", "difference") + + # this is weird but for the sake of testing, the cataloged zones inherit dnssec policy from catalog zone + if z.dnssec.enable: + s.item_str("dnssec-signing", "on") + s.item_str("dnssec-policy", z.name) + + acl = "" + if z.masters: + masters = "" + for master in z.masters: + if masters: + masters += ", " + masters += master.name + if not master.disable_notify: + if acl: + acl += ", " + acl += "acl_%s" % master.name + s.item("master", "[%s]" % masters) + if z.slaves: + slaves = "" + for slave in z.slaves: + if slave.disable_notify: + continue + if slaves: + slaves += ", " + slaves += slave.name + if slaves: + s.item("notify", "[%s]" % slaves) + if acl: + acl += ", " + acl += "acl_local, acl_test" + s.item("acl", "[%s]" % acl) + s.end() s.begin("zone") @@ -1363,6 +1407,9 @@ class Knot(Server): s.item_str("dnssec-signing", "on") s.item_str("dnssec-policy", z.dnssec.shared_policy_with or z.name) + if z.catalog: + s.item_str("catalog-template", "catemplate") + if len(z.modules) > 0: modules = "" for module in z.modules: