From 3900b8cbd74c2575397d072b63a8a7cdc1f11135 Mon Sep 17 00:00:00 2001 From: Libor Peltan Date: Mon, 16 Jun 2025 17:46:16 +0200 Subject: [PATCH] external-validation: implemented configurable timeout --- doc/reference.rst | 11 +++++ src/contrib/semaphore.c | 49 ++++++++++++++++++++ src/contrib/semaphore.h | 11 +++++ src/knot/conf/schema.c | 1 + src/knot/updates/zone-update.c | 3 +- tests-extra/tests/zone/external_vldt/test.py | 22 +++++++-- tests-extra/tools/dnstest/server.py | 1 + 7 files changed, 94 insertions(+), 4 deletions(-) diff --git a/doc/reference.rst b/doc/reference.rst index 2efbcf25a..608b29bd3 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -2559,6 +2559,7 @@ External zone validation configuration. external: - id: STR + timeout: TIME dump-new-zone: STR dump-removals: STR dump-additions: STR @@ -2570,6 +2571,16 @@ id An external section identifier. +.. _external_timeout: + +timeout +------- + +If the validation is not confirmed within this time interval in seconds, +it is considered failed. + +*Default:* ``300`` + .. _external_dump-new-zone: dump-new-zone diff --git a/src/contrib/semaphore.c b/src/contrib/semaphore.c index c7c893a2d..443d046c7 100644 --- a/src/contrib/semaphore.c +++ b/src/contrib/semaphore.c @@ -6,8 +6,11 @@ #include "semaphore.h" #include +#include #include #include +#include +#include #if defined(__APPLE__) #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -66,6 +69,52 @@ void knot_sem_wait(knot_sem_t *sem) } } +static void timespec_now_shift(struct timespec *ts, unsigned long shift_ms) +{ + clock_gettime(CLOCK_REALTIME, ts); + uint64_t nsec = ts->tv_nsec + shift_ms * 1000000LU; + ts->tv_sec += nsec / 1000000000LU; + ts->tv_nsec = nsec % 1000000000LU; +} + +static bool timespec_past(const struct timespec *ts) +{ + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + return ts->tv_sec == now.tv_sec ? ts->tv_nsec <= now.tv_nsec : ts->tv_sec < now.tv_sec; +} + +bool knot_sem_timedwait(knot_sem_t *sem, unsigned long ms) +{ + assert(sem != NULL); + if (ms == 0) { + knot_sem_wait(sem); + return true; + } + + struct timespec end; + timespec_now_shift(&end, ms); + if (sem->status == SEM_STATUS_POSIX) { + int semret; + do { + semret = sem_timedwait(&sem->semaphore, &end); + } while (semret != 0 && errno != ETIMEDOUT); // repeat wait as it might be interrupted by a signal + return (semret == 0); + } else { + pthread_mutex_lock(&sem->status_lock->mutex); + while (sem->status <= 0 && !timespec_past(&end)) { + pthread_cond_timedwait(&sem->status_lock->cond, &sem->status_lock->mutex, &end); + } + if (sem->status <= 0) { + pthread_mutex_unlock(&sem->status_lock->mutex); + return false; + } + sem->status--; + pthread_mutex_unlock(&sem->status_lock->mutex); + return true; + } +} + void knot_sem_wait_post(knot_sem_t *sem) { assert((sem != NULL) && (sem->status != SEM_STATUS_POSIX)); diff --git a/src/contrib/semaphore.h b/src/contrib/semaphore.h index 080a6d743..4c7c40446 100644 --- a/src/contrib/semaphore.h +++ b/src/contrib/semaphore.h @@ -7,6 +7,7 @@ #include #include +#include typedef struct { pthread_mutex_t mutex; @@ -42,6 +43,16 @@ void knot_sem_reset(knot_sem_t *sem, int value); */ void knot_sem_wait(knot_sem_t *sem); +/*! + * \brief Lock the semaphore (decrement), block until it's non-negative but only for specified timeout. + * + * \param sem Semapthore. + * \param ms Timeout in milliseconds or 0 for infinity (same as knot_sem_wait). + * + * \return True if semaphore acquired, false if timeout passed. + */ +bool knot_sem_timedwait(knot_sem_t *sem, unsigned long ms); + /*! * \brief Block until the semaphore could decrement, but keep the value unchanged. * \note This can be only used with nonposix semaphore. diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c index 222ae11f5..7d5e52502 100644 --- a/src/knot/conf/schema.c +++ b/src/knot/conf/schema.c @@ -457,6 +457,7 @@ static const yp_item_t desc_policy[] = { static const yp_item_t desc_external[] = { { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF }, + { C_TIMEOUT, YP_TINT, YP_VINT = { 0, UINT32_MAX, 300, YP_STIME } }, { C_DUMP_NEW, YP_TSTR, YP_VSTR = { "" } }, { C_DUMP_REM, YP_TSTR, YP_VSTR = { "" } }, { C_DUMP_ADD, YP_TSTR, YP_VSTR = { "" } }, diff --git a/src/knot/updates/zone-update.c b/src/knot/updates/zone-update.c index 1f30babd2..5479932b4 100644 --- a/src/knot/updates/zone-update.c +++ b/src/knot/updates/zone-update.c @@ -1005,7 +1005,8 @@ int zone_update_external(conf_t *conf, zone_update_t *update, conf_val_t *ev_id) dbus_emit_external_verify(update->zone->name); } - knot_sem_wait(&update->external); + val = conf_id_get(conf, C_EXTERNAL, C_TIMEOUT, ev_id); + knot_sem_timedwait(&update->external, conf_int(&val) * 1000); pthread_mutex_lock(&update->zone->cu_lock); update->zone->control_update = NULL; diff --git a/tests-extra/tests/zone/external_vldt/test.py b/tests-extra/tests/zone/external_vldt/test.py index 350ad83e5..fb31fa388 100644 --- a/tests-extra/tests/zone/external_vldt/test.py +++ b/tests-extra/tests/zone/external_vldt/test.py @@ -40,7 +40,10 @@ ZONE = zone[0].name LOG = "for external validation" slave.async_start = True -slave.zones[ZONE].external = { "new": dump_file(slave, "new"), "rem": dump_file(slave, "diff"), "add": dump_file(slave, "diff") } +slave.zones[ZONE].external = { "timeout": "10", + "new": dump_file(slave, "new"), + "rem": dump_file(slave, "diff"), + "add": dump_file(slave, "diff") } def check_diff_types(types): check_zf_types(slave.zones[ZONE].external["add"], types) @@ -83,20 +86,33 @@ resp = ctl.receive_block() t.sleep(2) resp = slave.dig(ZONE, "SOA") resp.check_soa_serial(serial - 1) +ctl.close() + +up = master.update(zone) +up.add("snail", 3600, "AAAA", "1::1") +up.send() +serial = master.zone_wait(zone, serial) +t.sleep(2) +log_count_expect(slave, LOG, 3) +t.sleep(int(slave.zones[ZONE].external["timeout"])) +resp = slave.dig(ZONE, "SOA") +resp.check_soa_serial(serial - 2) up = master.update(zone) up.add("shark", 3600, "AAAA", "1::1") up.send() serial = master.zone_wait(zone, serial) +ctl.connect(os.path.join(slave.dir, sockname)) t.sleep(2) -log_count_expect(slave, LOG, 3) +log_count_expect(slave, LOG, 4) ctl.send_block(cmd="zone-diff", zone=ZONE) resp = ctl.receive_block() isset("AAAA" in resp[ZONE]["horse."+ZONE], "ZONE-DIFF 2") isset("AAAA" in resp[ZONE]["shark."+ZONE], "ZONE-DIFF 3") +isset("AAAA" in resp[ZONE]["snail."+ZONE], "ZONE-DIFF 3.5") isset("AAAA" in resp[ZONE]["tiger."+ZONE], "ZONE-DIFF 4") -check_diff_types(["SOA", "SOA", "AAAA", "AAAA", "AAAA"]) +check_diff_types(["SOA", "SOA", "AAAA", "AAAA", "AAAA", "AAAA"]) ctl.send_block(cmd="zone-commit", zone=ZONE) resp = ctl.receive_block() t.sleep(2) diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py index 2d3197bf0..30a78211f 100644 --- a/tests-extra/tools/dnstest/server.py +++ b/tests-extra/tools/dnstest/server.py @@ -1799,6 +1799,7 @@ class Knot(Server): s.begin("external") have_external = True s.id_item("id", z.name) + self._str(s, "timeout", z.external["timeout"]) self._str(s, "dump-new-zone", z.external["new"]) self._str(s, "dump-removals", z.external["rem"]) self._str(s, "dump-additions", z.external["add"])