{ NULL }
};
+static const yp_item_t desc_external[] = {
+ { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
+ { NULL }
+};
+
#define ZONE_ITEMS(FLAGS) \
{ C_STORAGE, YP_TSTR, YP_VSTR = { STORAGE_DIR }, FLAGS }, \
{ C_FILE, YP_TSTR, YP_VNONE, FLAGS }, \
{ C_IXFR_FROM_AXFR, YP_TBOOL, YP_VNONE }, \
{ C_ZONE_MAX_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, SSIZE_MAX, YP_SSIZE }, FLAGS }, \
{ C_ADJUST_THR, YP_TINT, YP_VINT = { 1, UINT16_MAX, 1 } }, \
+ { C_EXTERNAL_VLDT, YP_TREF, YP_VREF = { C_EXTERNAL }, FLAGS, { check_ref_dflt } }, \
{ C_DNSSEC_SIGNING, YP_TBOOL, YP_VNONE, FLAGS }, \
{ C_DNSSEC_VALIDATION, YP_TBOOL, YP_VNONE, FLAGS }, \
{ C_DNSSEC_POLICY, YP_TREF, YP_VREF = { C_POLICY }, FLAGS, { check_ref_dflt } }, \
{ C_SBM, YP_TGRP, YP_VGRP = { desc_submission }, YP_FMULTI },
{ C_DNSKEY_SYNC, YP_TGRP, YP_VGRP = { desc_dnskey_sync }, YP_FMULTI, { check_dnskey_sync } },
{ C_POLICY, YP_TGRP, YP_VGRP = { desc_policy }, YP_FMULTI, { check_policy } },
+ { C_EXTERNAL, YP_TGRP, YP_VGRP = { desc_external }, YP_FMULTI, { check_external } },
{ C_TPL, YP_TGRP, YP_VGRP = { desc_template }, YP_FMULTI, { check_template } },
{ C_ZONE, YP_TGRP, YP_VGRP = { desc_zone }, YP_FMULTI | CONF_IO_FZONE, { check_zone } },
{ C_INCL, YP_TSTR, YP_VNONE, CONF_IO_FDIFF_ZONES | CONF_IO_FRLD_ALL, { include_file } },
#define C_ECS "\x12""edns-client-subnet"
#define C_EXPIRE_MAX_INTERVAL "\x13""expire-max-interval"
#define C_EXPIRE_MIN_INTERVAL "\x13""expire-min-interval"
+#define C_EXTERNAL "\x08""external"
+#define C_EXTERNAL_VLDT "\x13""external-validation"
#define C_FILE "\x04""file"
#define C_GLOBAL_MODULE "\x0D""global-module"
#define C_ID "\x02""id"
return KNOT_EOK;
}
+int check_external(
+ knotd_conf_check_args_t *args)
+{
+ return KNOT_EOK;
+}
+
int check_key(
knotd_conf_check_args_t *args)
{
knotd_conf_check_args_t *args
);
+int check_external(
+ knotd_conf_check_args_t *args
+);
+
int check_key(
knotd_conf_check_args_t *args
);
return KNOT_TXN_ENOTEXISTS;
}
+ if (zone->control_update->flags & UPDATE_WFEV) {
+ zone->control_update->flags |= UPDATE_EVOK;
+ knot_sem_post(&zone->control_update->external);
+ return KNOT_EOK;
+ }
+
int ret = zone_update_semcheck(conf(), zone->control_update);
if (ret != KNOT_EOK) {
return ret; // Recoverable error.
return KNOT_TXN_ENOTEXISTS;
}
+ if (zone->control_update->flags & UPDATE_WFEV) {
+ knot_sem_post(&zone->control_update->external);
+ pthread_mutex_unlock(&zone->cu_lock);
+ return KNOT_EOK;
+ }
+
zone_control_clear(zone);
pthread_mutex_unlock(&zone->cu_lock);
static void check_zone_txn(zone_t *zone, const knot_dname_t **exists)
{
- if (zone->control_update != NULL) {
+ if (zone->control_update != NULL && !(zone->control_update->flags & UPDATE_WFEV)) {
*exists = zone->name;
}
}
synchronize_rcu();
pthread_mutex_lock(&zone->cu_lock);
+ assert(zone->control_update == NULL || !(zone->control_update->flags & UPDATE_WFEV));
zone_control_clear(zone);
pthread_mutex_unlock(&zone->cu_lock);
ret = zone_update_commit(data->conf, &up);
if (ret != KNOT_EOK) {
zone_update_clear(&up);
+ if (ret == KNOT_EEXTERNAL) {
+ data->fallback_axfr = false;
+ data->fallback->remote = false;
+ }
IXFRIN_LOG(LOG_WARNING, data,
"failed to store changes (%s)", knot_strerror(ret));
return ret;
return KNOT_EOK;
}
+static void zonedb_shutdown(server_t *server)
+{
+ if (server->zone_db != NULL) {
+ knot_zonedb_foreach(server->zone_db, zone_shutdown);
+ }
+}
+
void server_wait(server_t *server)
{
if (server == NULL) {
/* Stop scheduler. */
evsched_stop(&server->sched);
+ /* Shut down zones. */
+ zonedb_shutdown(server);
/* Interrupt background workers. */
worker_pool_stop(server->workers);
/* Suspend adding events to worker pool queue, wait for queued events. */
log_debug("suspending zone events");
evsched_pause(&server->sched);
+ zonedb_shutdown(server);
worker_pool_wait(server->workers);
log_debug("suspended zone events");
return ret;
}
+int zone_update_external(conf_t *conf, zone_update_t *update)
+{
+ (void)conf;
+ pthread_mutex_lock(&update->zone->cu_lock);
+
+ if (update->zone->control_update != NULL) {
+ assert(update->zone->control_update == update);
+ assert(!(update->flags & UPDATE_WFEV));
+ pthread_mutex_unlock(&update->zone->cu_lock);
+ return KNOT_EOK; // real control update never waits for external validation
+ }
+
+ if (zone_get_flag(update->zone, ZONE_SHUT_DOWN, false)) {
+ pthread_mutex_unlock(&update->zone->cu_lock);
+ return KNOT_EEXTERNAL;
+ }
+
+ update->zone->control_update = update;
+ update->flags |= UPDATE_WFEV;
+ knot_sem_init(&update->external, 0);
+ pthread_mutex_unlock(&update->zone->cu_lock);
+
+ log_zone_notice(update->zone->name, "waiting for external validation");
+
+ knot_sem_wait(&update->external);
+
+ pthread_mutex_lock(&update->zone->cu_lock);
+ update->zone->control_update = NULL;
+ pthread_mutex_unlock(&update->zone->cu_lock);
+
+ knot_sem_post(&update->external);
+ knot_sem_destroy(&update->external);
+
+ return (update->flags & UPDATE_EVOK) ? KNOT_EOK : KNOT_EEXTERNAL;
+}
+
int zone_update_commit(conf_t *conf, zone_update_t *update)
{
if (conf == NULL || update == NULL) {
}
}
+ val = conf_zone_get(conf, C_EXTERNAL_VLDT, update->zone->name);
+ if (val.code == KNOT_EOK) {
+ ret = zone_update_external(conf, update, &val);
+ if (ret != KNOT_EOK) {
+ discard_adds_tree(update);
+ return ret;
+ }
+ }
+
ret = update_catalog(conf, update);
if (ret != KNOT_EOK) {
log_zone_error(update->zone->name, "failed to process catalog zone (%s)", knot_strerror(ret));
changeset_t extra_ch; /*!< Extra changeset to store just diff btwn zonefile and result. */
apply_ctx_t *a_ctx; /*!< Context for applying changesets. */
uint32_t flags; /*!< Zone update flags. */
+ knot_sem_t external; /*!< Lock for external validation. */
dnssec_validation_hint_t validation_hint;
} zone_update_t;
UPDATE_CHANGED_NSEC = 1 << 7, /*!< This incremental update affects NSEC or NSEC3 nodes in zone. */
UPDATE_NO_CHSET = 1 << 8, /*!< Avoid using changeset and serialize to journal from diff of bi-nodes. */
UPDATE_SIGNED_FULL = 1 << 9, /*!< Full (non-incremental) zone sign took place during this update. */
+ UPDATE_WFEV = 1 << 10, /*!< Update waiting for external validation. */
+ UPDATE_EVOK = 1 << 11, /*!< External validation accepted the update. */
} zone_update_flags_t;
/*!
*/
int zone_update_verify_digest(conf_t *conf, zone_update_t *update);
+/*!
+ * \brief Wait for external validation.
+ *
+ * \param conf Configuration.
+ * \param update Zone update.
+ *
+ * \retval KNOT_EEXTERNAL External validation failed.
+ * \retval KNOT_EOK External validation succeeded.
+ * \return KNOT_E*
+ */
+int zone_update_external(conf_t *conf, zone_update_t *update);
+
/*!
* \brief Commits all changes to the zone, signs it, saves changes to journal.
*
zone->control_update = NULL;
}
+void zone_shutdown(zone_t *zone)
+{
+ pthread_mutex_lock(&zone->cu_lock);
+ if (zone->control_update != NULL && (zone->control_update->flags & UPDATE_WFEV)) {
+ knot_sem_post(&zone->control_update->external);
+ }
+ zone_set_flag(zone, ZONE_SHUT_DOWN);
+ pthread_mutex_unlock(&zone->cu_lock);
+}
+
void zone_free(zone_t **zone_ptr)
{
if (zone_ptr == NULL || *zone_ptr == NULL) {
knot_sem_destroy(&zone->cow_lock);
/* Control update. */
+ assert(zone->control_update == NULL || !(zone->control_update->flags & UPDATE_WFEV));
zone_control_clear(zone);
free(zone->catalog_gen);
ZONE_XFR_FROZEN = 1 << 7, /*!< Outgoing AXFR/IXFR temporarily disabled. */
ZONE_USER_FLUSH = 1 << 8, /*!< User-triggered flush. */
ZONE_LAST_SIGN_OK = 1 << 9, /*!< Last full-sign event finished OK. */
+ ZONE_SHUT_DOWN = 1 << 10, /*!< Zone events are shutting down. */
} zone_flag_t;
/*!
*/
zone_t* zone_new(const knot_dname_t *name);
+/*!
+ * \brief Declare that zone is shutting down.
+ *
+ * \param zone Zone to be shut down.
+ */
+void zone_shutdown(zone_t *zone);
+
/*!
* \brief Deallocates the zone structure.
*
it = knot_zonedb_iter_begin(db_new);
while (!knot_zonedb_iter_finished(it)) {
zone_t *z = knot_zonedb_iter_val(it);
+ zone_unset_flag(z, ZONE_SHUT_DOWN);
+
conf_val_t val = conf_zone_get(conf, C_REVERSE_GEN, z->name);
while (val.code == KNOT_EOK) {
const knot_dname_t *forw_name = conf_dname(&val);
KNOT_EBACKUPDATA,
KNOT_ECPUCOMPAT,
KNOT_EMODINVAL,
+ KNOT_EEXTERNAL,
KNOT_GENERAL_ERROR = -900,
{ KNOT_EBACKUPDATA, "requested data not in backup" },
{ KNOT_ECPUCOMPAT, "incompatible CPU architecture" },
{ KNOT_EMODINVAL, "invalid module" },
+ { KNOT_EEXTERNAL, "external validation failed" },
{ KNOT_GENERAL_ERROR, "unknown general error" },
--- /dev/null
+#!/usr/bin/env python3
+
+"""
+Test of external zone validation.
+"""
+
+from dnstest.utils import *
+from dnstest.test import Test
+import random
+
+t = Test()
+
+master = t.server("knot")
+slave = t.server("knot")
+zone = t.zone_rnd(1, records=40)
+t.link(zone, master, slave)
+
+def log_count_expect(server, pattern, expct):
+ fnd = server.log_search_count(pattern)
+ if fnd != expct:
+ detail_log("LOG SEARCH COUNT '%s' found %d expected %d" % (pattern, fnd, expct))
+ set_err("LOG SEARCH COUNT %d != %d" % (fnd, expct))
+
+ZONE = zone[0].name
+LOG = "for external validation"
+
+slave.async_start = True
+slave.zones[ZONE].external = True # TODO this will be a list or dict once 'external' secation has any fields
+
+master.dnssec(zone[0]).enable = random.choice([False, True])
+
+t.start()
+serial = master.zone_wait(zone)
+
+t.sleep(2)
+log_count_expect(slave, LOG, 1)
+resp = slave.dig(ZONE, "SOA")
+resp.check(rcode="SERVFAIL")
+resp.check_count(0, "SOA")
+
+slave.ctl("zone-commit " + ZONE)
+t.sleep(2)
+resp = slave.dig(ZONE, "SOA")
+resp.check_soa_serial(serial)
+
+master.random_ddns(zone, allow_empty=False)
+serial = master.zone_wait(zone, serial)
+
+t.sleep(2)
+log_count_expect(slave, LOG, 2)
+slave.ctl("zone-abort " + ZONE)
+t.sleep(2)
+resp = slave.dig(ZONE, "SOA")
+resp.check_soa_serial(serial - 1)
+
+master.random_ddns(zone, allow_empty=False)
+serial = master.zone_wait(zone, serial)
+
+t.sleep(2)
+log_count_expect(slave, LOG, 3)
+slave.ctl("zone-commit " + ZONE)
+t.sleep(2)
+resp = slave.dig(ZONE, "SOA")
+resp.check_soa_serial(serial)
+
+slave.ctl("zone-freeze " + ZONE)
+master.random_ddns(zone, allow_empty=False)
+serial = master.zone_wait(zone, serial)
+
+slave.zonemd_generate = "zonemd-sha512"
+slave.gen_confile()
+slave.ctl("zone-thaw " + ZONE)
+t.sleep(1)
+slave.reload()
+
+master.random_ddns(zone, allow_empty=False)
+serial = master.zone_wait(zone, serial)
+
+t.sleep(2)
+log_count_expect(slave, LOG, 5)
+slave.ctl("zone-commit " + ZONE)
+t.sleep(2)
+resp = slave.dig(ZONE, "SOA")
+resp.check_soa_serial(serial)
+
+master.random_ddns(zone, allow_empty=False)
+serial = master.zone_wait(zone, serial)
+
+t.sleep(2)
+log_count_expect(slave, LOG, 6)
+slave.stop()
+t.sleep(2)
+log_count_expect(slave, "shutting down", 1)
+
+t.end()
self.journal_content = journal_content # journal contents
self.modules = []
self.reverse_from = None
+ self.external = None
self.dnssec = ZoneDnssec()
self.catalog_role = ZoneCatalogRole.NONE
self.catalog_gen_name = None # Generated catalog name for this member
self.zone_size_limit = None
self.serial_policy = None
self.auto_acl = None
+ self.async_start = None
self.provide_ixfr = None
self.master_pin_tol = None
self.quic_log = None
self._str(s, "remote-pool-limit", str(random.randint(0,6)))
self._str(s, "remote-retry-delay", str(random.choice([0, 1, 5])))
self._bool(s, "automatic-acl", self.auto_acl)
+ self._bool(s, "async-start", self.async_start)
if self.cert_key_file:
s.item_str("key-file", self.cert_key_file[0])
s.item_str("cert-file", self.cert_key_file[1])
if have_dnskeysync:
s.end()
+ have_external = False
+ for zone in sorted(self.zones):
+ z = self.zones[zone]
+ if not z.external:
+ continue
+ if not have_external:
+ s.begin("external")
+ have_external = True
+ s.id_item("id", z.name)
+ if have_external:
+ s.end()
+
have_keystore = False
for zone in sorted(self.zones):
z = self.zones[zone]
self._str(s, "expire-min-interval", z.expire_min)
self._str(s, "expire-max-interval", z.expire_max)
+ if z.external:
+ s.item("external-validation", z.name)
+
if self.zonefile_load is not None:
s.item_str("zonefile-load", self.zonefile_load)
elif z.ixfr: