]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
Implemented outgoing XFR freeze/thaw
authorLibor Peltan <libor.peltan@nic.cz>
Mon, 17 Aug 2020 15:43:39 +0000 (17:43 +0200)
committerDaniel Salzman <daniel.salzman@nic.cz>
Mon, 31 Jan 2022 11:56:15 +0000 (12:56 +0100)
14 files changed:
doc/man/knotc.8in
doc/man_knotc.rst
src/knot/ctl/commands.c
src/knot/ctl/commands.h
src/knot/events/handlers/update.c
src/knot/nameserver/axfr.c
src/knot/nameserver/ixfr.c
src/knot/nameserver/process_query.c
src/knot/nameserver/process_query.h
src/knot/zone/zone.c
src/knot/zone/zone.h
src/utils/knotc/commands.c
tests-extra/tests/zone/xfr_freeze/test.py [new file with mode: 0644]
tests-extra/tools/dnstest/zonefile.py

index 3fcc05699a2f2014370e8fee344dd16fa355cf1f..140a1eba0e2e2493eac81a5717d71f63c1cff1dc 100644 (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
index ddccc56978165d6eeb0297077878d8a9bfd2c9e4..f840f065d1d36149e2a7606634e29cdcd7a3120b 100644 (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.
 
index e27acf178cbf28c459fa6d755e8b8d7f85610630..f312a253f4d6b9cfa2693bd23dbe578f8c844217 100644 (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 },
index fa78cf24f97c5c9879da181287ca0e5390cc0bac..b1ba4e12b16d6f5db5e520cc509d12c985f8f881 100644 (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,
index 8f60f17fcd93a2be1c1c06ada2d3f889bb22c402..b998eb57d321a3cb127d434a5333c96fed1f4e2c 100644 (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) {
index 7fd69eb3dad7a1ba4c1366d7f467c7d737ac996b..5f46a4c0c6349d2ee14a2dc796e78c4a68c3a5f2 100644 (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));
index cc89a290792970652e23e85491f3366b7ecfdbff..98c01b0ebc50434ac6b6faa4df0aec46526ce0df 100644 (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));
index 87d7cc1873753522d33129b7379d23a659e8bfa4..325c7b18d1066c602282d9513244fdad8a5ef0cb 100644 (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) {
index c80f1228ac4517d09da8d8c50bc9aada0d49c70e..bd7d42af0c0de4be1f15d3cfedb90b3e30afa99d 100644 (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. */
index 8d412613012ce3c6053c04f896107f188b486844..f58424f125bf900c7fe7931709fded3d97367bd8 100644 (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) {
index 6623c43848456f2c290cb8f291515bb9d3dbc9a5..0f8ca5424342cf83f9af47ec623db889a5ac5c44 100644 (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);
 
index f0ddd97fdaa21aa7cbb99ceda811e290dfa265e4..90b114586e2d4b76c7160e0e2444bc01dea8a6c2 100644 (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." },
diff --git a/tests-extra/tests/zone/xfr_freeze/test.py b/tests-extra/tests/zone/xfr_freeze/test.py
new file mode 100644 (file)
index 0000000..a27a333
--- /dev/null
@@ -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()
index 76586ce1e8034b5d9cc58530e3bc79aef8ee6986..39dc48879236efa9c0cad073764a9145cbedc04e 100644 (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]: