diff --git a/doc/man/knot.conf.5in b/doc/man/knot.conf.5in index de3207214..cfeee272b 100644 --- a/doc/man/knot.conf.5in +++ b/doc/man/knot.conf.5in @@ -394,8 +394,8 @@ Definition of remote servers for zone transfers or notifications. .ft C remote: \- id: STR - address: ADDR[@INT] - via: ADDR[@INT] + address: ADDR[@INT] ... + via: ADDR[@INT] ... key: key_id .ft P .fi @@ -406,16 +406,17 @@ remote: A remote identifier. .SS address .sp -A destination IP address of the remote server. Optional destination port -specification (default is 53) can be appended to the address using \fB@\fP -separator. +An ordered list of destination IP addresses which are used for communication +with the remote server. The addresses are tried in sequence unless the +operation is successful. Optional destination port (default is 53) +can be appended to the address using \fB@\fP separator. .sp Default: empty .SS via .sp -A source IP address which is used to communicate with the remote server. -Optional source port specification can be appended to the address using -\fB@\fP separator. +An ordered list of source IP addresses. The first address with the same family +as the destination address is used. Optional source port (default is random) +can be appended to the address using \fB@\fP separator. .sp Default: empty .SS key @@ -459,6 +460,7 @@ zone: file: STR storage: STR master: remote_id ... + ddns\-master: remote_id notify: remote_id ... acl: acl_id ... semantic\-checks: BOOL @@ -507,6 +509,12 @@ Default: \fB${localstatedir}/lib/knot\fP (configured with \fB\-\-with\-storage=p An ordered list of \fI\%references\fP to zone master servers. .sp Default: empty +.SS ddns\-master +.sp +A \fI\%references\fP to zone primary master server. +If not specified, the first \fI\%master\fP server is used. +.sp +Default: empty .SS notify .sp An ordered list of \fI\%references\fP to remotes to which notify diff --git a/doc/reference.rst b/doc/reference.rst index b72f06709..f5fee1207 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -466,8 +466,8 @@ Definition of remote servers for zone transfers or notifications. remote: - id: STR - address: ADDR[@INT] - via: ADDR[@INT] + address: ADDR[@INT] ... + via: ADDR[@INT] ... key: key_id .. _remote_id: @@ -482,9 +482,10 @@ A remote identifier. address ------- -A destination IP address of the remote server. Optional destination port -specification (default is 53) can be appended to the address using ``@`` -separator. +An ordered list of destination IP addresses which are used for communication +with the remote server. The addresses are tried in sequence unless the +operation is successful. Optional destination port (default is 53) +can be appended to the address using ``@`` separator. Default: empty @@ -493,9 +494,9 @@ Default: empty via --- -A source IP address which is used to communicate with the remote server. -Optional source port specification can be appended to the address using -``@`` separator. +An ordered list of source IP addresses. The first address with the same family +as the destination address is used. Optional source port (default is random) +can be appended to the address using ``@`` separator. Default: empty @@ -547,6 +548,7 @@ Definition of zones served by the server. file: STR storage: STR master: remote_id ... + ddns-master: remote_id notify: remote_id ... acl: acl_id ... semantic-checks: BOOL @@ -609,6 +611,16 @@ An ordered list of :ref:`references` to zone master servers. Default: empty +.. _zone_ddns-master: + +ddns-master +----------- + +A :ref:`references` to zone primary master server. +If not specified, the first :ref:`master` server is used. + +Default: empty + .. _zone_notify: notify diff --git a/src/knot/conf/scheme.c b/src/knot/conf/scheme.c index 64d050078..cbb7d076d 100644 --- a/src/knot/conf/scheme.c +++ b/src/knot/conf/scheme.c @@ -136,6 +136,7 @@ static const yp_item_t desc_remote[] = { { C_FILE, YP_TSTR, YP_VNONE }, \ { C_STORAGE, YP_TSTR, YP_VSTR = { STORAGE_DIR } }, \ { C_MASTER, YP_TREF, YP_VREF = { C_RMT }, YP_FMULTI, { check_ref } }, \ + { C_DDNS_MASTER, YP_TREF, YP_VREF = { C_RMT }, YP_FNONE, { check_ref } }, \ { C_NOTIFY, YP_TREF, YP_VREF = { C_RMT }, YP_FMULTI, { check_ref } }, \ { C_ACL, YP_TREF, YP_VREF = { C_ACL }, YP_FMULTI, { check_ref } }, \ { C_SEM_CHECKS, YP_TBOOL, YP_VNONE }, \ diff --git a/src/knot/conf/scheme.h b/src/knot/conf/scheme.h index 6bf9a72fe..5bb25b32f 100644 --- a/src/knot/conf/scheme.h +++ b/src/knot/conf/scheme.h @@ -36,6 +36,7 @@ #define C_BG_WORKERS "\x12""background-workers" #define C_COMMENT "\x07""comment" #define C_CTL "\x07""control" +#define C_DDNS_MASTER "\x0B""ddns-master" #define C_DENY "\x04""deny" #define C_DISABLE_ANY "\x0B""disable-any" #define C_DNSSEC_SIGNING "\x0E""dnssec-signing" diff --git a/src/knot/nameserver/axfr.c b/src/knot/nameserver/axfr.c index 8e2e0bd09..7037044df 100644 --- a/src/knot/nameserver/axfr.c +++ b/src/knot/nameserver/axfr.c @@ -385,7 +385,7 @@ int axfr_answer_process(knot_pkt_t *pkt, struct answer_data *adata) if (rcode != KNOT_RCODE_NOERROR) { lookup_table_t *lut = lookup_by_id(knot_rcode_names, rcode); if (lut != NULL) { - AXFRIN_LOG(LOG_ERR, "server responded with %s", lut->name); + AXFRIN_LOG(LOG_WARNING, "server responded with %s", lut->name); } return KNOT_STATE_FAIL; } @@ -397,7 +397,7 @@ int axfr_answer_process(knot_pkt_t *pkt, struct answer_data *adata) int ret = axfr_answer_init(adata); if (ret != KNOT_EOK) { - AXFRIN_LOG(LOG_ERR, "failed (%s)", knot_strerror(ret)); + AXFRIN_LOG(LOG_WARNING, "failed (%s)", knot_strerror(ret)); return KNOT_STATE_FAIL; } } else { diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c index 4cbc1eecf..9b5ae5ec9 100644 --- a/src/knot/nameserver/internet.c +++ b/src/knot/nameserver/internet.c @@ -888,12 +888,27 @@ static int process_soa_answer(knot_pkt_t *pkt, struct answer_data *data) if (serial_compare(our_serial, their_serial) >= 0) { ANSWER_LOG(LOG_INFO, data, "refresh, outgoing", "zone is up-to-date"); zone_events_cancel(zone, ZONE_EVENT_EXPIRE); + + /* Clear preferred master. */ + pthread_mutex_lock(&zone->preferred_lock); + free(zone->preferred_master); + zone->preferred_master = NULL; + pthread_mutex_unlock(&zone->preferred_lock); + return KNOT_STATE_DONE; /* Our zone is up to date. */ } /* Our zone is outdated, schedule zone transfer. */ ANSWER_LOG(LOG_INFO, data, "refresh, outgoing", "master has newer serial %u -> %u", our_serial, their_serial); + + /* Update preferred master. */ + pthread_mutex_lock(&zone->preferred_lock); + free(zone->preferred_master); + zone->preferred_master = malloc(sizeof(struct sockaddr_storage)); + *zone->preferred_master = *data->param->remote; + pthread_mutex_unlock(&zone->preferred_lock); + zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW); return KNOT_STATE_DONE; } diff --git a/src/knot/nameserver/notify.c b/src/knot/nameserver/notify.c index 0bbb490f6..91d190c30 100644 --- a/src/knot/nameserver/notify.c +++ b/src/knot/nameserver/notify.c @@ -98,6 +98,14 @@ int notify_process_query(knot_pkt_t *pkt, struct query_data *qdata) /* Incoming NOTIFY expires REFRESH timer and renews EXPIRE timer. */ zone_t *zone = (zone_t *)qdata->zone; + + /* Update preferred master. */ + pthread_mutex_lock(&zone->preferred_lock); + free(zone->preferred_master); + zone->preferred_master = malloc(sizeof(struct sockaddr_storage)); + *zone->preferred_master = *qdata->param->remote; + pthread_mutex_unlock(&zone->preferred_lock); + zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW); int ret = zone_events_write_persistent(zone); if (ret != KNOT_EOK) { diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c index 9045c38fb..a87fc14d7 100644 --- a/src/knot/nameserver/update.c +++ b/src/knot/nameserver/update.c @@ -341,13 +341,6 @@ static int process_requests(zone_t *zone, list_t *requests) static int forward_request(zone_t *zone, struct knot_request *request) { - /* Create requestor instance. */ - struct knot_requestor re; - knot_requestor_init(&re, NULL); - - /* Fetch primary master. */ - const conf_remote_t master = zone_master(zone); - /* Copy request and assign new ID. */ knot_pkt_t *query = knot_pkt_new(NULL, request->query->max_size, NULL); int ret = knot_pkt_copy(query, request->query); @@ -359,29 +352,56 @@ static int forward_request(zone_t *zone, struct knot_request *request) knot_wire_set_id(query->wire, dnssec_random_uint16_t()); knot_tsig_append(query->wire, &query->size, query->max_size, query->tsig_rr); - /* Create a request. */ - const struct sockaddr *dst = (const struct sockaddr *)&master.addr; - const struct sockaddr *src = (const struct sockaddr *)&master.via; - struct knot_request *req = knot_request_make(re.mm, dst, src, query, 0); - if (req == NULL) { - knot_pkt_free(&query); - return KNOT_ENOMEM; + /* Read the ddns master or the first master. */ + conf_val_t remote = conf_zone_get(conf(), C_DDNS_MASTER, zone->name); + if (remote.code != KNOT_EOK) { + remote = conf_zone_get(conf(), C_MASTER, zone->name); } - /* Prepare packet capture layer. */ - struct capture_param param; - param.sink = request->resp; - knot_requestor_overlay(&re, LAYER_CAPTURE, ¶m); + /* Get the number of remote addresses. */ + conf_val_t addr = conf_id_get(conf(), C_RMT, C_ADDR, &remote); + size_t addr_count = conf_val_count(&addr); - /* Enqueue and execute request. */ - ret = knot_requestor_enqueue(&re, req); - if (ret == KNOT_EOK) { + /* Try all remote addresses to forward the request to. */ + for (size_t i = 0; i < addr_count; i++) { + conf_remote_t master = conf_remote(conf(), &remote, i); + + /* Create requestor instance. */ + struct knot_requestor re; + knot_requestor_init(&re, NULL); + + /* Prepare packet capture layer. */ + struct capture_param param; + param.sink = request->resp; + knot_requestor_overlay(&re, LAYER_CAPTURE, ¶m); + + /* Create a request. */ + const struct sockaddr *dst = (const struct sockaddr *)&master.addr; + const struct sockaddr *src = (const struct sockaddr *)&master.via; + struct knot_request *req = knot_request_make(re.mm, dst, src, query, 0); + if (req == NULL) { + knot_pkt_free(&query); + return KNOT_ENOMEM; + } + + /* Enqueue the request. */ + ret = knot_requestor_enqueue(&re, req); + if (ret != KNOT_EOK) { + knot_requestor_clear(&re); + continue; + } + + /* Execute the request. */ conf_val_t val = conf_get(conf(), C_SRV, C_TCP_REPLY_TIMEOUT); struct timeval tv = { conf_int(&val), 0 }; ret = knot_requestor_exec(&re, &tv); - } + if (ret == KNOT_EOK) { + knot_requestor_clear(&re); + break; + } - knot_requestor_clear(&re); + knot_requestor_clear(&re); + } /* Restore message ID and TSIG. */ knot_wire_set_id(request->resp->wire, knot_wire_get_id(request->query->wire)); diff --git a/src/knot/zone/events/handlers.c b/src/knot/zone/events/handlers.c index 9b4c06f3f..b92bf1c6e 100644 --- a/src/knot/zone/events/handlers.c +++ b/src/knot/zone/events/handlers.c @@ -174,7 +174,7 @@ static int zone_query_transfer(zone_t *zone, const conf_remote_t *master, uint16 } /* Log connection errors. */ - ZONE_XFER_LOG(LOG_ERR, pkt_type, "failed (%s)", knot_strerror(ret)); + ZONE_XFER_LOG(LOG_WARNING, pkt_type, "failed (%s)", knot_strerror(ret)); } return ret; @@ -315,6 +315,20 @@ fail: return result; } +static int try_refresh(zone_t *zone, const conf_remote_t *master, void *ctx) +{ + assert(zone); + assert(master); + + int ret = zone_query_execute(zone, KNOT_QUERY_NORMAL, master); + if (ret != KNOT_EOK) { + ZONE_QUERY_LOG(LOG_WARNING, zone, master, "refresh, outgoing", + "failed (%s)", knot_strerror(ret)); + } + + return ret; +} + int event_refresh(zone_t *zone) { assert(zone); @@ -330,15 +344,11 @@ int event_refresh(zone_t *zone) return KNOT_EOK; } - const conf_remote_t master = zone_master(zone); - int ret = zone_query_execute(zone, KNOT_QUERY_NORMAL, &master); + int ret = zone_master_try(zone, try_refresh, NULL); const knot_rdataset_t *soa = zone_soa(zone); if (ret != KNOT_EOK) { - /* Log connection errors. */ - ZONE_QUERY_LOG(LOG_WARNING, zone, &master, "SOA query, outgoing", - "failed (%s)", knot_strerror(ret)); - /* Rotate masters if current failed. */ - zone_master_rotate(zone); + log_zone_error(zone->name, "refresh, failed (%s)", + knot_strerror(ret)); /* Schedule next retry. */ zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_retry(soa)); start_expire_timer(zone, soa); @@ -350,6 +360,21 @@ int event_refresh(zone_t *zone) return zone_events_write_persistent(zone); } +struct transfer_data { + uint16_t pkt_type; +}; + +static int try_xfer(zone_t *zone, const conf_remote_t *master, void *_data) +{ + assert(zone); + assert(master); + assert(_data); + + struct transfer_data *data = _data; + + return zone_query_transfer(zone, master, data->pkt_type); +} + int event_xfer(zone_t *zone) { assert(zone); @@ -359,20 +384,29 @@ int event_xfer(zone_t *zone) return KNOT_EOK; } + struct transfer_data data = { 0 }; + /* Determine transfer type. */ - bool is_boostrap = zone_contents_is_empty(zone->contents); - uint16_t pkt_type = KNOT_QUERY_IXFR; - if (is_boostrap || zone->flags & ZONE_FORCE_AXFR) { - pkt_type = KNOT_QUERY_AXFR; + bool is_bootstrap = zone_contents_is_empty(zone->contents); + if (is_bootstrap || zone->flags & ZONE_FORCE_AXFR) { + data.pkt_type = KNOT_QUERY_AXFR; + } else { + data.pkt_type = KNOT_QUERY_IXFR; } - /* Execute zone transfer and reschedule timers. */ - const conf_remote_t master = zone_master(zone); - int ret = zone_query_transfer(zone, &master, pkt_type); + /* Execute zone transfer. */ + int ret = zone_master_try(zone, try_xfer, &data); + + /* Clear preferred master. */ + pthread_mutex_lock(&zone->preferred_lock); + free(zone->preferred_master); + zone->preferred_master = NULL; + pthread_mutex_unlock(&zone->preferred_lock); - /* Handle failure during transfer. */ if (ret != KNOT_EOK) { - if (is_boostrap) { + log_zone_error(zone->name, "transfer, failed (%s)", + knot_strerror(ret)); + if (is_bootstrap) { zone->bootstrap_retry = bootstrap_next(zone->bootstrap_retry); zone_events_schedule(zone, ZONE_EVENT_XFER, zone->bootstrap_retry); } else { @@ -404,7 +438,7 @@ int event_xfer(zone_t *zone) zone->flags &= ~ZONE_FORCE_AXFR; /* Trim extra heap. */ - if (!is_boostrap) { + if (!is_bootstrap) { mem_trim(); } @@ -485,22 +519,27 @@ int event_notify(zone_t *zone) } /* Walk through configured remotes and send messages. */ - conf_val_t val = conf_zone_get(conf(), C_NOTIFY, zone->name); - while (val.code == KNOT_EOK) { - conf_remote_t remote = conf_remote(conf(), &val); + conf_val_t notify = conf_zone_get(conf(), C_NOTIFY, zone->name); + while (notify.code == KNOT_EOK) { + conf_val_t addr = conf_id_get(conf(), C_RMT, C_ADDR, ¬ify); + size_t addr_count = conf_val_count(&addr); - int ret = zone_query_execute(zone, KNOT_QUERY_NOTIFY, &remote); - if (ret == KNOT_EOK) { - ZONE_QUERY_LOG(LOG_INFO, zone, &remote, - "NOTIFY, outgoing", "serial %u", - zone_contents_serial(zone->contents)); - } else { - ZONE_QUERY_LOG(LOG_WARNING, zone, &remote, - "NOTIFY, outgoing", "failed (%s)", - knot_strerror(ret)); + for (int i = 0; i < addr_count; i++) { + conf_remote_t slave = conf_remote(conf(), ¬ify, i); + int ret = zone_query_execute(zone, KNOT_QUERY_NOTIFY, &slave); + if (ret == KNOT_EOK) { + ZONE_QUERY_LOG(LOG_INFO, zone, &slave, + "NOTIFY, outgoing", "serial %u", + zone_contents_serial(zone->contents)); + break; + } else { + ZONE_QUERY_LOG(LOG_WARNING, zone, &slave, + "NOTIFY, outgoing", "failed (%s)", + knot_strerror(ret)); + } } - conf_val_next(&val); + conf_val_next(¬ify); } return KNOT_EOK; diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c index 0a11720c9..8c0e8c18d 100644 --- a/src/knot/zone/zone.c +++ b/src/knot/zone/zone.c @@ -31,6 +31,7 @@ #include "knot/zone/zone.h" #include "knot/zone/zonefile.h" #include "knot/zone/contents.h" +#include "knot/updates/acl.h" #include "knot/updates/apply.h" #include "libknot/processing/requestor.h" #include "knot/nameserver/process_query.h" @@ -76,6 +77,9 @@ zone_t* zone_new(const knot_dname_t *name) // Journal lock pthread_mutex_init(&zone->journal_lock, NULL); + // Preferred master lock + pthread_mutex_init(&zone->preferred_lock, NULL); + // Initialize events zone_events_init(zone); @@ -98,6 +102,10 @@ void zone_free(zone_t **zone_ptr) pthread_mutex_destroy(&zone->ddns_lock); pthread_mutex_destroy(&zone->journal_lock); + /* Free preferred master. */ + pthread_mutex_destroy(&zone->preferred_lock); + free(zone->preferred_master); + /* Free zone contents. */ zone_contents_deep_free(&zone->contents); @@ -188,29 +196,88 @@ bool zone_is_slave(const zone_t *zone) return conf_val_count(&val) > 0 ? true : false; } -conf_remote_t zone_master(const zone_t *zone) +/*! + * \brief Get preferred zone master while checking its existence. + */ +int static preferred_master(zone_t *zone, conf_remote_t *master) { - conf_val_t val = conf_zone_get(conf(), C_MASTER, zone->name); + pthread_mutex_lock(&zone->preferred_lock); - /* Seek the current master if possible. */ - if (zone->master_index < conf_val_count(&val)) { - for (size_t index = 0; index < zone->master_index; index++) { - conf_val_next(&val); + if (zone->preferred_master == NULL) { + pthread_mutex_unlock(&zone->preferred_lock); + return KNOT_EINVAL; + } + + conf_val_t masters = conf_zone_get(conf(), C_MASTER, zone->name); + while (masters.code == KNOT_EOK) { + conf_val_t addr = conf_id_get(conf(), C_RMT, C_ADDR, &masters); + size_t addr_count = conf_val_count(&addr); + + for (size_t i = 0; i < addr_count; i++) { + conf_remote_t remote = conf_remote(conf(), &masters, i); + if (netblock_match(&remote.addr, zone->preferred_master, -1)) { + *master = remote; + pthread_mutex_unlock(&zone->preferred_lock); + return KNOT_EOK; + } + } + + conf_val_next(&masters); + } + + pthread_mutex_unlock(&zone->preferred_lock); + + return KNOT_ENOENT; +} + +int zone_master_try(zone_t *zone, zone_master_cb callback, void *callback_data) +{ + if (zone == NULL) { + return KNOT_EINVAL; + } + + /* Try the preferred server. */ + + conf_remote_t preferred = { { AF_UNSPEC } }; + if (preferred_master(zone, &preferred) == KNOT_EOK) { + int ret = callback(zone, &preferred, callback_data); + if (ret == KNOT_EOK) { + return ret; } } - return conf_remote(conf(), &val); -} + /* Try all the other servers. */ -void zone_master_rotate(zone_t *zone) -{ - conf_val_t val = conf_zone_get(conf(), C_MASTER, zone->name); + bool success = false; - if (zone->master_index + 2 <= conf_val_count(&val)) { - zone->master_index += 1; - } else { - zone->master_index = 0; + conf_val_t masters = conf_zone_get(conf(), C_MASTER, zone->name); + while (masters.code == KNOT_EOK) { + conf_val_t addr = conf_id_get(conf(), C_RMT, C_ADDR, &masters); + size_t addr_count = conf_val_count(&addr); + + for (size_t i = 0; i < addr_count; i++) { + conf_remote_t master = conf_remote(conf(), &masters, i); + if (preferred.addr.ss_family != AF_UNSPEC && + netblock_match(&master.addr, &preferred.addr, -1)) { + preferred.addr.ss_family = AF_UNSPEC; + continue; + } + int ret = callback(zone, &master, callback_data); + if (ret == KNOT_EOK) { + success = true; + break; + } + } + + if (!success) { + log_zone_warning(zone->name, "refresh, remote '%s' " + "not available", conf_str(&masters)); + } + + conf_val_next(&masters); } + + return success ? KNOT_EOK : KNOT_ENOMASTER; } int zone_flush_journal(zone_t *zone) diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h index b50056b45..0ca6a6b43 100644 --- a/src/knot/zone/zone.h +++ b/src/knot/zone/zone.h @@ -67,12 +67,14 @@ typedef struct zone /*! \brief Zone events. */ zone_events_t events; /*!< Zone events timers. */ - uint32_t bootstrap_retry; /*!< AXFR/IN bootstrap retry. */ time_t zonefile_mtime; + uint32_t bootstrap_retry; /*!< AXFR/IN bootstrap retry. */ uint32_t zonefile_serial; - /*! \brief Config master list index of the current master server. */ - size_t master_index; + /*! \brief Preferred master lock. */ + pthread_mutex_t preferred_lock; + /*! \brief Preferred master for remote operation. */ + struct sockaddr_storage *preferred_master; /*! \brief Query modules. */ list_t query_modules; @@ -109,17 +111,23 @@ int zone_change_store(zone_t *zone, changeset_t *change); /*! * \brief Atomically switch the content of the zone. */ -zone_contents_t *zone_switch_contents(zone_t *zone, - zone_contents_t *new_contents); +zone_contents_t *zone_switch_contents(zone_t *zone, zone_contents_t *new_contents); /*! \brief Checks if the zone is slave. */ bool zone_is_slave(const zone_t *zone); -/*! \brief Return the current zone master. */ -conf_remote_t zone_master(const zone_t *zone); +typedef int (*zone_master_cb)(zone_t *zone, const conf_remote_t *remote, void *data); -/*! \brief Set the next zone master as a current. */ -void zone_master_rotate(zone_t *zone); +/*! + * \brief Perform an action with a first working master server. + * + * The function iterates over available masters. For each master, the callback + * function is called. If the callback function succeeds (\ref KNOT_EOK is + * returned), the iteration is terminated. + * + * \return Error code from the last callback. + */ +int zone_master_try(zone_t *zone, zone_master_cb callback, void *callback_data); /*! \brief Synchronize zone file with journal. */ int zone_flush_journal(zone_t *zone); diff --git a/src/libknot/errcode.c b/src/libknot/errcode.c index d6927c246..cbe4bb518 100644 --- a/src/libknot/errcode.c +++ b/src/libknot/errcode.c @@ -65,6 +65,7 @@ static const struct error errors[] = { { KNOT_EZONENOENT, "zone file not found" }, { KNOT_ENOZONE, "no such zone found" }, { KNOT_ENONODE, "no such node in zone found" }, + { KNOT_ENOMASTER, "no active master" }, { KNOT_EDNAMEPTR, "domain name pointer larger than allowed" }, { KNOT_EPAYLOAD, "invalid EDNS payload size" }, { KNOT_EPREREQ, "UPDATE prerequisity not met" }, diff --git a/src/libknot/internal/errcode.h b/src/libknot/internal/errcode.h index 60b007dcd..3b3fc8dc9 100644 --- a/src/libknot/internal/errcode.h +++ b/src/libknot/internal/errcode.h @@ -69,6 +69,7 @@ enum knot_error { KNOT_EZONENOENT, KNOT_ENOZONE, KNOT_ENONODE, + KNOT_ENOMASTER, KNOT_EDNAMEPTR, KNOT_EPAYLOAD, KNOT_EPREREQ, diff --git a/tests-extra/tests/axfr/failover/test.py b/tests-extra/tests/axfr/failover/test.py new file mode 100644 index 000000000..1d9bf43a6 --- /dev/null +++ b/tests-extra/tests/axfr/failover/test.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +""" +Multi-master failover tests. +""" + +from dnstest.test import Test + +t = Test() + +# testing zone +zone = t.zone_rnd(1, dnssec=False, records=1)[0] +zone.update_soa(serial=1, refresh=600, retry=600, expire=3600) + +# +---------+       +---------+ +# | master1 <-------+ master2 | +# +----^----+       +----^----+ +#      |                 |     +#      |   +---------+   |     +#      +---+  slave  +---+     +#          +---------+    + +master1 = t.server("knot") +master2 = t.server("bind") +slave = t.server("knot") + +# flush zones immediatelly +for server in [master1, master2, slave]: + slave.zonefile_sync = "0" + +t.link([zone], master1, master2) +t.link([zone], master1, slave) +t.link([zone], master2, slave) + +t.start() + +# zone boostrap +for server in [master1, master2, slave]: + server.zone_wait(zone) + +# transfer with fully working topology +master1.zones[zone.name].zfile.update_soa(serial=10) +master1.reload() +for server in [master1, master2, slave]: + server.zone_wait(zone, serial=9) + +# stop slave, update masters +slave.stop() +master1.zones[zone.name].zfile.update_soa(serial=20) +master1.reload() +for server in [master1, master2]: + server.zone_wait(zone, serial=19) + +# failover to second master +master1.stop() +slave.start() +slave.zone_wait(zone, serial=19) +master1.start() + +# stop slave, update masters +slave.stop() +master1.zones[zone.name].zfile.update_soa(serial=30) +master1.reload() +for server in [master1, master2]: + server.zone_wait(zone, serial=29) + +# failover after notify +master1.stop() +master2.stop() +slave.start() +slave.zone_wait(zone, serial=19) +master2.start() +slave.zone_wait(zone, serial=29) + +t.end()