From: Libor Peltan Date: Wed, 20 Aug 2025 14:02:53 +0000 (+0200) Subject: XFR/master-selection: try all masters if more than one sent NOTIFY X-Git-Tag: v3.5.0~19^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=338d535eb0fcd2454b91105455f2e0f48d7687d8;p=thirdparty%2Fknot-dns.git XFR/master-selection: try all masters if more than one sent NOTIFY --- diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c index ba11aa1134..0084dccab7 100644 --- a/src/knot/zone/zone.c +++ b/src/knot/zone/zone.c @@ -517,10 +517,16 @@ void zone_set_preferred_master(zone_t *zone, const struct sockaddr_storage *addr pthread_mutex_lock(&zone->preferred_lock); if (zone->preferred_master == NULL) { - zone->preferred_master = malloc(sizeof(*zone->preferred_master)); + zone->preferred_master = calloc(1, sizeof(*zone->preferred_master)); assert(zone->preferred_master != NULL); } - memcpy(zone->preferred_master, addr, sockaddr_len(addr)); + + if (!sockaddr_net_match(zone->preferred_master, addr, -1)) { + if (zone->preferred_master->ss_family != AF_UNSPEC) { + zone->flags |= ZONE_PREF_MASTER_2X; // not zone_set_flag() as the mutex is already locked + } + memcpy(zone->preferred_master, addr, sockaddr_len(addr)); + } pthread_mutex_unlock(&zone->preferred_lock); } @@ -533,6 +539,7 @@ void zone_clear_preferred_master(zone_t *zone) pthread_mutex_lock(&zone->preferred_lock); free(zone->preferred_master); zone->preferred_master = NULL; + zone->flags &= ~ZONE_PREF_MASTER_2X; pthread_mutex_unlock(&zone->preferred_lock); } @@ -698,6 +705,7 @@ int zone_master_try(conf_t *conf, zone_t *zone, zone_master_cb callback, const char *last_id = NULL, *preferred_id = NULL; conf_val_t last = { 0 }, preferred = { 0 }; int idx = 0, last_idx = -1, preferred_idx = -1; + bool preferred_2x = false; conf_val_t masters = conf_zone_get(conf, C_MASTER, zone->name); conf_mix_iter_t iter; @@ -714,6 +722,7 @@ int zone_master_try(conf_t *conf, zone_t *zone, zone_master_cb callback, preferred_id = conf_str(iter.id); preferred = *iter.id; preferred_idx = idx; + preferred_2x = (zone->flags & ZONE_PREF_MASTER_2X); } if (pin_tolerance > 0 && sockaddr_net_match(&remote.addr, (struct sockaddr_storage *)&zone->timers.last_master, -1)) { @@ -738,7 +747,7 @@ int zone_master_try(conf_t *conf, zone_t *zone, zone_master_cb callback, }; ret = try_remote(conf, zone, callback, callback_data, err_str, preferred_id, &preferred, &fallback, "notifier "); - if (ret == KNOT_EOK || !fallback.remote) { + if ((ret == KNOT_EOK && !preferred_2x) || !fallback.remote) { return ret; // Success or local error. } } diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h index ef861f2a39..561264e671 100644 --- a/src/knot/zone/zone.h +++ b/src/knot/zone/zone.h @@ -39,6 +39,7 @@ typedef enum { ZONE_XFR_FROZEN = 1 << 7, /*!< Outgoing AXFR/IXFR temporarily disabled. */ ZONE_USER_FLUSH = 1 << 8, /*!< User-triggered flush. */ ZONE_LAST_SIGN_OK = 1 << 9, /*!< Last full-sign event finished OK. */ + ZONE_PREF_MASTER_2X = 1 << 10, /*!< Preferred master has been overwritten at least once. */ } zone_flag_t; /*! diff --git a/tests-extra/tests/notify/multi-master2/test.py b/tests-extra/tests/notify/multi-master2/test.py new file mode 100644 index 0000000000..475132c439 --- /dev/null +++ b/tests-extra/tests/notify/multi-master2/test.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +'''Test of not inhibiting received NOTIFY by another NOTIFY from outdated master.''' + +from dnstest.test import Test + +t = Test(address=4) + +master1 = t.server("knot", address="127.0.0.11", via=True) +master2 = t.server("knot", address="127.0.0.12", via=True) +slave = t.server("knot") + +zone = t.zone_rnd(1, records=300) +ZONE = zone[0].name + +t.link(zone, master1, slave) +t.link(zone, master2, slave) + +t.start() + +serial = slave.zone_wait(zone) + +# TEST 1: both masters send NOTIFY, but only the first one has higher serial than slave + +slave.ctl("zone-freeze", wait=True) +slave.ctl("zone-flush", wait=True) + +up = master1.update(zone) +up.add(ZONE, 3600, "SOA", "dns1 hostmaster %d 10800 3600 1209600 7200" % (serial + 2)) +up.add("add1", 3600, "TXT", master1.name) +up.send() +t.sleep(2) + +up = master2.update(zone) +up.add("add1", 3600, "TXT", master2.name) +up.send() +t.sleep(2) + +slave.zones[ZONE].zfile.update_soa() +slave.ctl("zone-reload", wait=True) +serial = slave.zone_wait(zone, serial) +slave.ctl("zone-thaw") + +# in case of failure: slave only tries master2 as the last one sending NOTIFY +serial = slave.zone_wait(zone, serial) +resp = slave.dig("add1." + ZONE, "TXT") +resp.check(rcode="NOERROR", rdata=master1.name, nordata=master2.name) + +# TEST 2: master1 doesn't send NOTIFY, only master2 is attempted + +slave.ctl("zone-freeze", wait=True) +slave.ctl("zone-flush", wait=True) + +master1.disable_notify = True +master1.gen_confile() +master1.reload() + +up = master2.update(zone) +up.add(ZONE, 3600, "SOA", "dns1 hostmaster %d 10800 3600 1209600 7200" % (serial + 1)) +up.add("add2", 3600, "TXT", master2.name) +up.send() +t.sleep(2) + +up = master1.update(zone) +up.add(ZONE, 3600, "SOA", "dns1 hostmaster %d 10800 3600 1209600 7200" % (serial + 2)) +up.add("add2", 3600, "TXT", master1.name) +up.send() +t.sleep(2) + +slave.zones[ZONE].zfile.update_soa() +slave.ctl("zone-reload", wait=True) +serial = slave.zone_wait(zone, serial) +slave.ctl("zone-thaw") + +t.sleep(5) + +# in case of failure: slave also tries master1 as the last one with greatest (greater) serial +slave.zone_wait(zone, serial, equal=True, greater=False) +resp = slave.dig("add2." + ZONE, "TXT") +resp.check(rcode="NXDOMAIN") + +t.end()