mirror of
https://gitlab.nic.cz/knot/knot-dns.git
synced 2026-05-28 04:02:31 -04:00
knot: implemented serial-modulo
This commit is contained in:
parent
36fff51b20
commit
0c475eae4a
11 changed files with 183 additions and 5 deletions
|
|
@ -2106,6 +2106,7 @@ zone:
|
|||
zonemd\-verify: BOOL
|
||||
zonemd\-generate: none | zonemd\-sha384 | zonemd\-sha512 | remove
|
||||
serial\-policy: increment | unixtime | dateserial
|
||||
serial\-modulo: INT/INT
|
||||
reverse\-generate: DNAME
|
||||
refresh\-min\-interval: TIME
|
||||
refresh\-max\-interval: TIME
|
||||
|
|
@ -2512,6 +2513,29 @@ Generated catalog zones use \fBunixtime\fP only.
|
|||
.UNINDENT
|
||||
.sp
|
||||
\fIDefault:\fP \fBincrement\fP (\fBunixtime\fP for generated catalog zones)
|
||||
.SS serial\-modulo
|
||||
.sp
|
||||
Specifies that the zone serials shall be congruent by specified modulo.
|
||||
The option value must be a string in the format \fBR/M\fP, where \fBR < M <= 256\fP are
|
||||
positive integers. Whenever the zone serial is incremented, it is ensured
|
||||
that \fBserial % M == R\fP\&. This can be useful in the case of multiple inconsistent
|
||||
primaries, where distinct zone serial sequences prevent cross\-master\-IXFR
|
||||
by any secondary.
|
||||
.sp
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
In order to ensure the congruent policy, this option is only allowed
|
||||
with \fI\%DNSSEC signing enabled\fP and
|
||||
\fI\%zonefile\-load\fP to be either \fBdifference\-no\-serial\fP or \fBnone\fP\&.
|
||||
.sp
|
||||
Because the zone serial effectively always increments by \fBM\fP instead of
|
||||
\fB1\fP, it is not recommended to use \fBdateserial\fP \fI\%serial\-policy\fP
|
||||
or even \fBunixtime\fP in case of rapidly updated zone.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
\fIDefault:\fP \fB0/1\fP
|
||||
.SS reverse\-generate
|
||||
.sp
|
||||
This option triggers the automatic generation of reverse PTR records based on
|
||||
|
|
|
|||
|
|
@ -2298,6 +2298,7 @@ Definition of zones served by the server.
|
|||
zonemd-verify: BOOL
|
||||
zonemd-generate: none | zonemd-sha384 | zonemd-sha512 | remove
|
||||
serial-policy: increment | unixtime | dateserial
|
||||
serial-modulo: INT/INT
|
||||
reverse-generate: DNAME
|
||||
refresh-min-interval: TIME
|
||||
refresh-max-interval: TIME
|
||||
|
|
@ -2727,6 +2728,29 @@ Possible values:
|
|||
|
||||
*Default:* ``increment`` (``unixtime`` for generated catalog zones)
|
||||
|
||||
.. _zone_serial-modulo:
|
||||
|
||||
serial-modulo
|
||||
-------------
|
||||
|
||||
Specifies that the zone serials shall be congruent by specified modulo.
|
||||
The option value must be a string in the format ``R/M``, where ``R < M <= 256`` are
|
||||
positive integers. Whenever the zone serial is incremented, it is ensured
|
||||
that ``serial % M == R``. This can be useful in the case of multiple inconsistent
|
||||
primaries, where distinct zone serial sequences prevent cross-master-IXFR
|
||||
by any secondary.
|
||||
|
||||
.. NOTE::
|
||||
In order to ensure the congruent policy, this option is only allowed
|
||||
with :ref:`DNSSEC signing enabled<zone_dnssec-signing>` and
|
||||
:ref:`zone_zonefile-load` to be either ``difference-no-serial`` or ``none``.
|
||||
|
||||
Because the zone serial effectively always increments by ``M`` instead of
|
||||
``1``, it is not recommended to use ``dateserial`` :ref:`zone_serial-policy`
|
||||
or even ``unixtime`` in case of rapidly updated zone.
|
||||
|
||||
*Default:* ``0/1``
|
||||
|
||||
.. _zone_reverse-generate:
|
||||
|
||||
reverse-generate
|
||||
|
|
|
|||
|
|
@ -666,6 +666,16 @@ const char* conf_str(
|
|||
}
|
||||
}
|
||||
|
||||
int conf_tuple(
|
||||
conf_val_t *val,
|
||||
uint32_t *a,
|
||||
uint32_t *b)
|
||||
{
|
||||
const char *str = conf_str(val);
|
||||
int res = sscanf(str, "%"SCNu32"/%"SCNu32, a, b);
|
||||
return res == 2 ? KNOT_EOK : KNOT_EMALF;
|
||||
}
|
||||
|
||||
const knot_dname_t* conf_dname(
|
||||
conf_val_t *val)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -495,6 +495,21 @@ const char* conf_str(
|
|||
conf_val_t *val
|
||||
);
|
||||
|
||||
/*!
|
||||
* Gets the tuple value of a string item in format "##/##".
|
||||
*
|
||||
* \param[in] val Item value.
|
||||
* \param[out] a First numeric value.
|
||||
* \param[out] b Second numeric value.
|
||||
*
|
||||
* \return KNOT_EOK if OK, KNOT_EMALF if the tuple format not recognized.
|
||||
*/
|
||||
int conf_tuple(
|
||||
conf_val_t *val,
|
||||
uint32_t *a,
|
||||
uint32_t *b
|
||||
);
|
||||
|
||||
/*!
|
||||
* Gets the dname value of the item.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -469,6 +469,7 @@ static const yp_item_t desc_policy[] = {
|
|||
{ check_ref } }, \
|
||||
{ C_REVERSE_GEN, YP_TDNAME,YP_VNONE, FLAGS | CONF_IO_FRLD_ZONES }, \
|
||||
{ C_SERIAL_POLICY, YP_TOPT, YP_VOPT = { serial_policies, SERIAL_POLICY_INCREMENT } }, \
|
||||
{ C_SERIAL_MODULO, YP_TSTR, YP_VSTR = { "0/1" } }, \
|
||||
{ C_ZONEMD_GENERATE, YP_TOPT, YP_VOPT = { zone_digest, ZONE_DIGEST_NONE }, FLAGS }, \
|
||||
{ C_ZONEMD_VERIFY, YP_TBOOL, YP_VNONE, FLAGS }, \
|
||||
{ C_REFRESH_MIN_INTERVAL,YP_TINT, YP_VINT = { 2, UINT32_MAX, 2, YP_STIME } }, \
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@
|
|||
#define C_SBM "\x0A""submission"
|
||||
#define C_SECRET "\x06""secret"
|
||||
#define C_SEM_CHECKS "\x0F""semantic-checks"
|
||||
#define C_SERIAL_MODULO "\x0D""serial-modulo"
|
||||
#define C_SERIAL_POLICY "\x0D""serial-policy"
|
||||
#define C_SERVER "\x06""server"
|
||||
#define C_SIGNING_THREADS "\x0F""signing-threads"
|
||||
|
|
|
|||
|
|
@ -940,7 +940,8 @@ int check_zone(
|
|||
C_ZONEFILE_LOAD, yp_dname(args->id));
|
||||
conf_val_t journal = conf_zone_get_txn(args->extra->conf, args->extra->txn,
|
||||
C_JOURNAL_CONTENT, yp_dname(args->id));
|
||||
if (conf_opt(&zf_load) == ZONEFILE_LOAD_DIFSE) {
|
||||
int zf_load_val = conf_opt(&zf_load);
|
||||
if (zf_load_val == ZONEFILE_LOAD_DIFSE) {
|
||||
if (conf_opt(&journal) != JOURNAL_CONTENT_ALL) {
|
||||
args->err_str = "'zonefile-load: difference-no-serial' requires 'journal-content: all'";
|
||||
return KNOT_EINVAL;
|
||||
|
|
@ -965,6 +966,24 @@ int check_zone(
|
|||
}
|
||||
}
|
||||
|
||||
conf_val_t serial_modulo = conf_zone_get_txn(args->extra->conf, args->extra->txn,
|
||||
C_SERIAL_MODULO, yp_dname(args->id));
|
||||
uint32_t a, b;
|
||||
int ret = conf_tuple(&serial_modulo, &a, &b);
|
||||
if (ret != KNOT_EOK || a >= b) {
|
||||
args->err_str = "invalid format of 'serial-modulo'";
|
||||
return KNOT_EINVAL;
|
||||
} else if (b > 1) {
|
||||
if (!conf_bool(&signing)) {
|
||||
args->err_str = "'serial-modulo' is only possible with `dnssec-signing`";
|
||||
return KNOT_EINVAL;
|
||||
} else if (zf_load_val != ZONEFILE_LOAD_DIFSE && zf_load_val != ZONEFILE_LOAD_NONE) {
|
||||
args->err_str = "'serial-modulo' requires 'zonefile-load' either 'none'"
|
||||
" or 'difference-no-serial'";
|
||||
return KNOT_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
conf_val_t catalog_role = conf_zone_get_txn(args->extra->conf, args->extra->txn,
|
||||
C_CATALOG_ROLE, yp_dname(args->id));
|
||||
conf_val_t catalog_tpl = conf_zone_get_txn(args->extra->conf, args->extra->txn,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ static uint32_t serial_dateserial(uint32_t current)
|
|||
uint32_t serial_next(uint32_t current, conf_t *conf, const knot_dname_t *zone,
|
||||
unsigned policy, uint32_t must_increment)
|
||||
{
|
||||
uint32_t minimum;
|
||||
uint32_t minimum, result;
|
||||
|
||||
if (policy == SERIAL_POLICY_AUTO) {
|
||||
assert(conf);
|
||||
|
|
@ -74,10 +74,25 @@ uint32_t serial_next(uint32_t current, conf_t *conf, const knot_dname_t *zone,
|
|||
return 0;
|
||||
}
|
||||
if (serial_compare(minimum, current) != SERIAL_GREATER) {
|
||||
return current + must_increment;
|
||||
result = current + must_increment;
|
||||
} else {
|
||||
return minimum;
|
||||
result = minimum;
|
||||
}
|
||||
|
||||
if (conf != NULL) { // NULL conf SHOULD be only allowed in tests
|
||||
conf_val_t val = conf_zone_get(conf, C_SERIAL_MODULO, zone);
|
||||
uint32_t a, b;
|
||||
int ret = conf_tuple(&val, &a, &b);
|
||||
assert(ret == KNOT_EOK && a < b); // ensured by conf/tools.c
|
||||
if (b > 1) {
|
||||
uint32_t incr = ((a + b) - (result % b)) % b;
|
||||
assert(incr == 0 || result % b != a);
|
||||
result += incr;
|
||||
assert(result % b == a);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
serial_cmp_result_t kserial_cmp(kserial_t a, kserial_t b)
|
||||
|
|
|
|||
66
tests-extra/tests/zone/serial_modulo/test.py
Normal file
66
tests-extra/tests/zone/serial_modulo/test.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Test of serial modulo.
|
||||
"""
|
||||
|
||||
import random
|
||||
from dnstest.utils import *
|
||||
from dnstest.test import Test
|
||||
|
||||
SCENARIO = random.choice(["xfr-ddns", "ddns", "reload", "restart", "ddns-restart"])
|
||||
MODULO_A = 3
|
||||
MODULO_B = 7
|
||||
|
||||
def check_serial(serial, modulo_a, msg):
|
||||
if serial % MODULO_B != modulo_a:
|
||||
set_err("WRONG MODULO " + msg)
|
||||
detail_log("%s: serial %d modulo %d expected %d found %d" % \
|
||||
(msg, serial, MODULO_B, modulo_a, serial % MODULO_B))
|
||||
|
||||
def check_serials(serials, modulo_a, msg):
|
||||
for z in serials:
|
||||
check_serial(serials[z], modulo_a, msg)
|
||||
|
||||
t = Test()
|
||||
|
||||
master = t.server("knot")
|
||||
knot = t.server("knot")
|
||||
zones = t.zone("example.com.")
|
||||
if "xfr" in SCENARIO:
|
||||
source = master
|
||||
t.link(zones, master, knot)
|
||||
else:
|
||||
source = knot
|
||||
t.link(zones, knot)
|
||||
|
||||
for z in zones:
|
||||
knot.dnssec(z).enable = True
|
||||
knot.zones[z.name].serial_modulo = "%d/%d" % (MODULO_A, MODULO_B)
|
||||
|
||||
knot.zonefile_load = "difference-no-serial"
|
||||
knot.zones[z.name].journal_content = "all"
|
||||
|
||||
detail_log("SCENARIO " + SCENARIO)
|
||||
t.start()
|
||||
|
||||
serials = knot.zones_wait(zones)
|
||||
check_serials(serials, MODULO_A, "INIT")
|
||||
|
||||
if "ddns" in SCENARIO:
|
||||
for z in zones:
|
||||
source.random_ddns(z, allow_empty=False)
|
||||
else:
|
||||
for z in zones:
|
||||
source.zones[z.name].zfile.update_rnd()
|
||||
|
||||
if "start" in SCENARIO:
|
||||
source.stop()
|
||||
source.start()
|
||||
elif "reload" in SCENARIO:
|
||||
source.ctl("zone-reload")
|
||||
|
||||
serials = knot.zones_wait(zones, serials)
|
||||
check_serials(serials, MODULO_A, "DDNS")
|
||||
|
||||
t.end()
|
||||
|
|
@ -92,6 +92,7 @@ class Zone(object):
|
|||
self.zfile = zone_file
|
||||
self.masters = set()
|
||||
self.slaves = set()
|
||||
self.serial_modulo = None
|
||||
self.ddns = ddns
|
||||
self.ixfr = ixfr
|
||||
self.journal_content = journal_content # journal contents
|
||||
|
|
@ -1616,6 +1617,7 @@ class Knot(Server):
|
|||
self.config_xfr(z, s)
|
||||
|
||||
self._str(s, "serial-policy", self.serial_policy)
|
||||
self._str(s, "serial-modulo", z.serial_modulo)
|
||||
self._str(s, "ddns-master", self.ddns_master)
|
||||
|
||||
s.item_str("journal-content", z.journal_content)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
|
||||
/* Copyright (C) 2023 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
|
||||
|
|
@ -1034,6 +1034,7 @@ static const knot_lookup_t opts[] = {
|
|||
{ C_JOURNAL_CONTENT, YP_TOPT, YP_VOPT = { opts, 0 } }, \
|
||||
{ C_DNSSEC_SIGNING, YP_TBOOL, YP_VNONE }, \
|
||||
{ C_DNSSEC_VALIDATION, YP_TBOOL, YP_VNONE }, \
|
||||
{ C_SERIAL_MODULO, YP_TSTR, YP_VSTR = { "0/1" } }, \
|
||||
{ C_CATALOG_ROLE, YP_TOPT, YP_VOPT = { opts, 0 } }, \
|
||||
{ C_CATALOG_TPL, YP_TREF, YP_VREF = { C_RMT } }, \
|
||||
{ C_COMMENT, YP_TSTR, YP_VNONE },
|
||||
|
|
|
|||
Loading…
Reference in a new issue