Implemented outgoing XFR freeze/thaw

This commit is contained in:
Libor Peltan 2020-08-17 17:43:39 +02:00 committed by Daniel Salzman
parent 5b3edbacbf
commit a8bcce0112
14 changed files with 136 additions and 20 deletions

View file

@ -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

View file

@ -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.

View file

@ -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 },

View file

@ -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,

View file

@ -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) {

View file

@ -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));

View file

@ -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));

View file

@ -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) {

View file

@ -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. */

View file

@ -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) {

View file

@ -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);

View file

@ -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." },

View 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()

View file

@ -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]: