mirror of
https://gitlab.nic.cz/knot/knot-dns.git
synced 2026-05-28 04:02:31 -04:00
Implemented outgoing XFR freeze/thaw
This commit is contained in:
parent
5b3edbacbf
commit
a8bcce0112
14 changed files with 136 additions and 20 deletions
|
|
@ -184,6 +184,12 @@ will be held up until the zone is thawed. (#)
|
|||
\fBzone\-thaw\fP [\fIzone\fP\&...]
|
||||
Trigger dismissal of zone freeze. (#)
|
||||
.TP
|
||||
\fBzone\-xfr\-freeze\fP [\fIzone\fP\&...]
|
||||
Temporarily disable outgoing AXFR/IXFR for the zone(s). (#)
|
||||
.TP
|
||||
\fBzone\-xfr\-thaw\fP [\fIzone\fP\&...]
|
||||
Dismiss outgoing XFR freeze. (#)
|
||||
.TP
|
||||
\fBzone\-read\fP \fIzone\fP [\fIowner\fP [\fItype\fP]]
|
||||
Get zone data that are currently being presented.
|
||||
.TP
|
||||
|
|
|
|||
|
|
@ -161,6 +161,12 @@ Actions
|
|||
**zone-thaw** [*zone*...]
|
||||
Trigger dismissal of zone freeze. (#)
|
||||
|
||||
**zone-xfr-freeze** [*zone*...]
|
||||
Temporarily disable outgoing AXFR/IXFR for the zone(s). (#)
|
||||
|
||||
**zone-xfr-thaw** [*zone*...]
|
||||
Dismiss outgoing XFR freeze. (#)
|
||||
|
||||
**zone-read** *zone* [*owner* [*type*]]
|
||||
Get zone data that are currently being presented.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
|
||||
/* Copyright (C) 2022 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
|
||||
|
|
@ -635,6 +635,24 @@ static int zone_thaw(zone_t *zone, _unused_ ctl_args_t *args)
|
|||
return schedule_trigger(zone, args, ZONE_EVENT_UTHAW, false);
|
||||
}
|
||||
|
||||
static int zone_xfr_freeze(zone_t *zone, _unused_ ctl_args_t *args)
|
||||
{
|
||||
zone_set_flag(zone, ZONE_XFR_FROZEN);
|
||||
|
||||
log_zone_info(zone->name, "outgoing XFR frozen");
|
||||
|
||||
return KNOT_EOK;
|
||||
}
|
||||
|
||||
static int zone_xfr_thaw(zone_t *zone, _unused_ ctl_args_t *args)
|
||||
{
|
||||
zone_unset_flag(zone, ZONE_XFR_FROZEN);
|
||||
|
||||
log_zone_info(zone->name, "outgoing XFR unfrozen");
|
||||
|
||||
return KNOT_EOK;
|
||||
}
|
||||
|
||||
static int zone_txn_begin(zone_t *zone, _unused_ ctl_args_t *args)
|
||||
{
|
||||
if (zone->control_update != NULL) {
|
||||
|
|
@ -1567,6 +1585,10 @@ static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd)
|
|||
return zones_apply(args, zone_freeze);
|
||||
case CTL_ZONE_THAW:
|
||||
return zones_apply(args, zone_thaw);
|
||||
case CTL_ZONE_XFR_FREEZE:
|
||||
return zones_apply(args, zone_xfr_freeze);
|
||||
case CTL_ZONE_XFR_THAW:
|
||||
return zones_apply(args, zone_xfr_thaw);
|
||||
case CTL_ZONE_READ:
|
||||
return zones_apply(args, zone_read);
|
||||
case CTL_ZONE_BEGIN:
|
||||
|
|
@ -2009,6 +2031,8 @@ static const desc_t cmd_table[] = {
|
|||
[CTL_ZONE_KSK_SBM] = { "zone-ksk-submitted", ctl_zone },
|
||||
[CTL_ZONE_FREEZE] = { "zone-freeze", ctl_zone },
|
||||
[CTL_ZONE_THAW] = { "zone-thaw", ctl_zone },
|
||||
[CTL_ZONE_XFR_FREEZE] = { "zone-xfr-freeze", ctl_zone },
|
||||
[CTL_ZONE_XFR_THAW] = { "zone-xfr-thaw", ctl_zone },
|
||||
|
||||
[CTL_ZONE_READ] = { "zone-read", ctl_zone },
|
||||
[CTL_ZONE_BEGIN] = { "zone-begin", ctl_zone },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
|
||||
/* Copyright (C) 2022 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
|
||||
|
|
@ -74,6 +74,8 @@ typedef enum {
|
|||
CTL_ZONE_KSK_SBM,
|
||||
CTL_ZONE_FREEZE,
|
||||
CTL_ZONE_THAW,
|
||||
CTL_ZONE_XFR_FREEZE,
|
||||
CTL_ZONE_XFR_THAW,
|
||||
|
||||
CTL_ZONE_READ,
|
||||
CTL_ZONE_BEGIN,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
false, fmt)
|
||||
|
||||
static void init_qdata_from_request(knotd_qdata_t *qdata,
|
||||
const zone_t *zone,
|
||||
zone_t *zone,
|
||||
knot_request_t *req,
|
||||
knotd_qdata_params_t *params,
|
||||
knotd_qdata_extra_t *extra)
|
||||
|
|
@ -312,7 +312,7 @@ static void forward_requests(conf_t *conf, zone_t *zone, list_t *requests)
|
|||
}
|
||||
}
|
||||
|
||||
static void send_update_response(conf_t *conf, const zone_t *zone, knot_request_t *req)
|
||||
static void send_update_response(conf_t *conf, zone_t *zone, knot_request_t *req)
|
||||
{
|
||||
if (req->resp) {
|
||||
if (!zone_is_slave(conf, zone)) {
|
||||
|
|
@ -343,7 +343,7 @@ static void free_request(knot_request_t *req)
|
|||
free(req);
|
||||
}
|
||||
|
||||
static void send_update_responses(conf_t *conf, const zone_t *zone, list_t *updates)
|
||||
static void send_update_responses(conf_t *conf, zone_t *zone, list_t *updates)
|
||||
{
|
||||
ptrnode_t *node, *nxt;
|
||||
WALK_LIST_DELSAFE(node, nxt, *updates) {
|
||||
|
|
|
|||
|
|
@ -129,6 +129,12 @@ static int axfr_query_init(knotd_qdata_t *qdata)
|
|||
}
|
||||
}
|
||||
|
||||
if (zone_get_flag(qdata->extra->zone, ZONE_XFR_FROZEN, false)) {
|
||||
qdata->rcode = KNOT_RCODE_REFUSED;
|
||||
qdata->rcode_ede = KNOT_EDNS_EDE_NOT_READY;
|
||||
return KNOT_EAGAIN;
|
||||
}
|
||||
|
||||
/* Create transfer processing context. */
|
||||
knot_mm_t *mm = qdata->mm;
|
||||
struct axfr_proc *axfr = mm_alloc(mm, sizeof(struct axfr_proc));
|
||||
|
|
@ -186,6 +192,9 @@ int axfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata)
|
|||
case KNOT_EMALF: /* Malformed query. */
|
||||
AXFROUT_LOG(LOG_DEBUG, qdata, "malformed query");
|
||||
return KNOT_STATE_FAIL;
|
||||
case KNOT_EAGAIN: /* Outgoing AXFR temporarily disabled. */
|
||||
AXFROUT_LOG(LOG_INFO, qdata, "outgoing AXFR frozen");
|
||||
return KNOT_STATE_FAIL;
|
||||
default:
|
||||
AXFROUT_LOG(LOG_ERR, qdata, "failed to start (%s)",
|
||||
knot_strerror(ret));
|
||||
|
|
|
|||
|
|
@ -173,6 +173,12 @@ static int ixfr_answer_init(knotd_qdata_t *qdata, uint32_t *serial_from)
|
|||
}
|
||||
}
|
||||
|
||||
if (zone_get_flag(qdata->extra->zone, ZONE_XFR_FROZEN, false)) {
|
||||
qdata->rcode = KNOT_RCODE_REFUSED;
|
||||
qdata->rcode_ede = KNOT_EDNS_EDE_NOT_READY;
|
||||
return KNOT_EAGAIN;
|
||||
}
|
||||
|
||||
const knot_pktsection_t *authority = knot_pkt_section(qdata->query, KNOT_AUTHORITY);
|
||||
const knot_rrset_t *their_soa = knot_pkt_rr(authority, 0);
|
||||
*serial_from = knot_soa_serial(their_soa->rrs.rdata);
|
||||
|
|
@ -278,6 +284,9 @@ int ixfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata)
|
|||
case KNOT_EMALF: /* Malformed query. */
|
||||
IXFROUT_LOG(LOG_DEBUG, qdata, "malformed query");
|
||||
return KNOT_STATE_FAIL;
|
||||
case KNOT_EAGAIN: /* Outgoing IXFR temporarily disabled. */
|
||||
IXFROUT_LOG(LOG_INFO, qdata, "outgoing IXFR frozen");
|
||||
return KNOT_STATE_FAIL;
|
||||
default: /* Server errors. */
|
||||
IXFROUT_LOG(LOG_ERR, qdata, "failed to start (%s)",
|
||||
knot_strerror(ret));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
|
||||
/* Copyright (C) 2022 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
|
||||
|
|
@ -182,12 +182,12 @@ static int query_chaos(knot_pkt_t *pkt, knot_layer_t *ctx)
|
|||
}
|
||||
|
||||
/*! \brief Find zone for given question. */
|
||||
static const zone_t *answer_zone_find(const knot_pkt_t *query, knot_zonedb_t *zonedb)
|
||||
static zone_t *answer_zone_find(const knot_pkt_t *query, knot_zonedb_t *zonedb)
|
||||
{
|
||||
uint16_t qtype = knot_pkt_qtype(query);
|
||||
uint16_t qclass = knot_pkt_qclass(query);
|
||||
const knot_dname_t *qname = knot_pkt_qname(query);
|
||||
const zone_t *zone = NULL;
|
||||
zone_t *zone = NULL;
|
||||
|
||||
// search for zone only for IN and ANY classes
|
||||
if (qclass != KNOT_CLASS_IN && qclass != KNOT_CLASS_ANY) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
|
||||
/* Copyright (C) 2022 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,7 +26,7 @@ const knot_layer_api_t *process_query_layer(void);
|
|||
|
||||
/*! \brief Query processing intermediate data. */
|
||||
typedef struct knotd_qdata_extra {
|
||||
const zone_t *zone; /*!< Zone from which is answered. */
|
||||
zone_t *zone; /*!< Zone from which is answered. */
|
||||
const zone_contents_t *contents; /*!< Zone contents from which is answered. */
|
||||
list_t wildcards; /*!< Visited wildcards. */
|
||||
list_t rrsigs; /*!< Section RRSIGs. */
|
||||
|
|
|
|||
|
|
@ -410,14 +410,14 @@ void zone_clear_preferred_master(zone_t *zone)
|
|||
pthread_mutex_unlock(&zone->preferred_lock);
|
||||
}
|
||||
|
||||
void zone_set_flag(zone_t *zone, zone_flag_t flag)
|
||||
static void set_flag(zone_t *zone, zone_flag_t flag, bool remove)
|
||||
{
|
||||
if (zone == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&zone->preferred_lock); // this mutex seems OK to be reused for this
|
||||
zone->flags |= flag;
|
||||
zone->flags = remove ? (zone->flags & ~flag) : (zone->flags | flag);
|
||||
pthread_mutex_unlock(&zone->preferred_lock);
|
||||
|
||||
if (flag & ZONE_IS_CATALOG) {
|
||||
|
|
@ -425,6 +425,16 @@ void zone_set_flag(zone_t *zone, zone_flag_t flag)
|
|||
}
|
||||
}
|
||||
|
||||
void zone_set_flag(zone_t *zone, zone_flag_t flag)
|
||||
{
|
||||
return set_flag(zone, flag, false);
|
||||
}
|
||||
|
||||
void zone_unset_flag(zone_t *zone, zone_flag_t flag)
|
||||
{
|
||||
return set_flag(zone, flag, true);
|
||||
}
|
||||
|
||||
zone_flag_t zone_get_flag(zone_t *zone, zone_flag_t flag, bool clear)
|
||||
{
|
||||
if (zone == NULL) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ typedef enum {
|
|||
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_XFR_FROZEN = 1 << 7, /*!< Outgoing AXFR/IXFR temporarily disabled. */
|
||||
} zone_flag_t;
|
||||
|
||||
/*!
|
||||
|
|
@ -180,6 +181,9 @@ void zone_clear_preferred_master(zone_t *zone);
|
|||
/*! \brief Sets a zone flag. */
|
||||
void zone_set_flag(zone_t *zone, zone_flag_t flag);
|
||||
|
||||
/*! \brief Unsets a zone flag. */
|
||||
void zone_unset_flag(zone_t *zone, zone_flag_t flag);
|
||||
|
||||
/*! \brief Returns if a flag is set (and optionally clears it). */
|
||||
zone_flag_t zone_get_flag(zone_t *zone, zone_flag_t flag, bool clear);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
|
||||
/* Copyright (C) 2022 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
|
||||
|
|
@ -54,6 +54,8 @@
|
|||
#define CMD_ZONE_KSK_SBM "zone-ksk-submitted"
|
||||
#define CMD_ZONE_FREEZE "zone-freeze"
|
||||
#define CMD_ZONE_THAW "zone-thaw"
|
||||
#define CMD_ZONE_XFR_FREEZE "zone-xfr-freeze"
|
||||
#define CMD_ZONE_XFR_THAW "zone-xfr-thaw"
|
||||
|
||||
#define CMD_ZONE_READ "zone-read"
|
||||
#define CMD_ZONE_BEGIN "zone-begin"
|
||||
|
|
@ -380,6 +382,8 @@ static void format_block(ctl_cmd_t cmd, bool failed, bool empty)
|
|||
case CTL_ZONE_KSK_SBM:
|
||||
case CTL_ZONE_FREEZE:
|
||||
case CTL_ZONE_THAW:
|
||||
case CTL_ZONE_XFR_FREEZE:
|
||||
case CTL_ZONE_XFR_THAW:
|
||||
case CTL_ZONE_BEGIN:
|
||||
case CTL_ZONE_COMMIT:
|
||||
case CTL_ZONE_ABORT:
|
||||
|
|
@ -1186,6 +1190,8 @@ const cmd_desc_t cmd_table[] = {
|
|||
{ CMD_ZONE_KSK_SBM, cmd_zone_ctl, CTL_ZONE_KSK_SBM, CMD_FREQ_ZONE | CMD_FOPT_ZONE },
|
||||
{ CMD_ZONE_FREEZE, cmd_zone_ctl, CTL_ZONE_FREEZE, CMD_FOPT_ZONE },
|
||||
{ CMD_ZONE_THAW, cmd_zone_ctl, CTL_ZONE_THAW, CMD_FOPT_ZONE },
|
||||
{ CMD_ZONE_XFR_FREEZE, cmd_zone_ctl, CTL_ZONE_XFR_FREEZE, CMD_FOPT_ZONE },
|
||||
{ CMD_ZONE_XFR_THAW, cmd_zone_ctl, CTL_ZONE_XFR_THAW, CMD_FOPT_ZONE },
|
||||
|
||||
{ CMD_ZONE_READ, cmd_zone_node_ctl, CTL_ZONE_READ, CMD_FREQ_ZONE },
|
||||
{ CMD_ZONE_BEGIN, cmd_zone_ctl, CTL_ZONE_BEGIN, CMD_FREQ_ZONE | CMD_FOPT_ZONE },
|
||||
|
|
@ -1236,6 +1242,8 @@ static const cmd_help_t cmd_help_table[] = {
|
|||
{ CMD_ZONE_KSK_SBM, " <zone>...", "When KSK submission, confirm parent's DS presence. (#)" },
|
||||
{ CMD_ZONE_FREEZE, "[<zone>...]", "Temporarily postpone automatic zone-changing events. (#)" },
|
||||
{ CMD_ZONE_THAW, "[<zone>...]", "Dismiss zone freeze. (#)" },
|
||||
{ CMD_ZONE_XFR_FREEZE, "[<zone>...]", "Temporarily disable outgoing AXFR/IXFR. (#)" },
|
||||
{ CMD_ZONE_XFR_THAW, "[<zone>...]", "Dismiss outgoing XFR freeze. (#)" },
|
||||
{ "", "", "" },
|
||||
{ CMD_ZONE_READ, "<zone> [<owner> [<type>]]", "Get zone data that are currently being presented." },
|
||||
{ CMD_ZONE_BEGIN, "<zone>...", "Begin a zone transaction." },
|
||||
|
|
|
|||
38
tests-extra/tests/zone/xfr_freeze/test.py
Normal file
38
tests-extra/tests/zone/xfr_freeze/test.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
'''Test of IXFR freeze.'''
|
||||
|
||||
from dnstest.test import Test
|
||||
|
||||
t = Test()
|
||||
|
||||
master = t.server("knot")
|
||||
slave = t.server("knot")
|
||||
zone = t.zone_rnd(1, records=50)
|
||||
|
||||
t.link(zone, master, slave)
|
||||
|
||||
t.start()
|
||||
|
||||
serial_init = master.zone_wait(zone)
|
||||
slave.zone_wait(zone)
|
||||
|
||||
master.ctl("zone-xfr-freeze", wait=True)
|
||||
master.random_ddns(zone, allow_empty=False)
|
||||
t.sleep(4)
|
||||
|
||||
resp = master.dig(zone[0].name, "AXFR", tries=1)
|
||||
resp.check_xfr(rcode="REFUSED")
|
||||
|
||||
resp = slave.dig(zone[0].name, "SOA")
|
||||
serial = resp.soa_serial()
|
||||
if serial != serial_init:
|
||||
set_err("SOA serial mismatch")
|
||||
detail_log("SOA serial mismatch %d != %d" % (serial, serial_init))
|
||||
|
||||
master.ctl("zone-xfr-thaw", wait=True)
|
||||
master.ctl("zone-notify")
|
||||
slave.zone_wait(zone, serial_init)
|
||||
t.xfr_diff(master, slave, zone)
|
||||
|
||||
t.end()
|
||||
|
|
@ -287,19 +287,19 @@ class ZoneFile(object):
|
|||
changes = 0
|
||||
with open(self.path, 'r') as file:
|
||||
for fline in file:
|
||||
line = fline.split(None, 3)
|
||||
line = fline.split(None, 4)
|
||||
if len(line) < 3:
|
||||
continue
|
||||
if line[0][0] not in [";", "@"]:
|
||||
dname = line[0]
|
||||
ttl = 0
|
||||
if line[1].isnumeric():
|
||||
ttl = line[1]
|
||||
rtype = line[2]
|
||||
rdata = ' '.join(line[3:])
|
||||
else:
|
||||
ttl = 0
|
||||
rtype = line[1]
|
||||
rdata = ' '.join(line[2:])
|
||||
del line[1]
|
||||
if line[1] == "IN":
|
||||
del line[1]
|
||||
rtype = line[1]
|
||||
rdata = ' '.join(line[2:])
|
||||
if rtype not in ["SOA", "RRSIG", "DNSKEY", "DS", "CDS", "CDNSKEY", "NSEC", "NSEC3", "NSEC3PARAM"]:
|
||||
try:
|
||||
if random.randint(1, 20) in [4, 5]:
|
||||
|
|
|
|||
Loading…
Reference in a new issue