diff --git a/bin/named/server.c b/bin/named/server.c index fd12287d33..894d9b1b0b 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -10763,13 +10763,24 @@ isc_result_t named_server_retransfercommand(named_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { isc_result_t result; + const char *arg = NULL; dns_zone_t *zone = NULL; dns_zone_t *raw = NULL; dns_zonetype_t type; + bool force = false; REQUIRE(text != NULL); - result = zone_from_args(server, lex, NULL, &zone, NULL, text, true); + /* Skip the command name. */ + (void)next_token(lex, text); + + arg = next_token(lex, text); + if (arg != NULL && (strcmp(arg, "-force") == 0)) { + force = true; + arg = next_token(lex, text); + } + + result = zone_from_args(server, lex, arg, &zone, NULL, text, false); if (result != ISC_R_SUCCESS) { return (result); } @@ -10788,7 +10799,10 @@ named_server_retransfercommand(named_server_t *server, isc_lex_t *lex, (type == dns_zone_redirect && dns_zone_getredirecttype(zone) == dns_zone_secondary)) { - dns_zone_forcereload(zone); + if (force) { + dns_zone_stopxfr(zone); + } + dns_zone_forcexfr(zone); } else { (void)putstr(text, "retransfer: inappropriate zone type: "); (void)putstr(text, dns_zonetype_name(type)); diff --git a/bin/rndc/rndc.rst b/bin/rndc/rndc.rst index da9f0710e4..9df484cf3d 100644 --- a/bin/rndc/rndc.rst +++ b/bin/rndc/rndc.rst @@ -444,14 +444,16 @@ Currently supported commands are: .. program:: rndc -.. option:: retransfer zone [class [view]] +.. option:: retransfer [-force] zone [class [view]] This command retransfers the given secondary zone from the primary server. If the zone is configured to use ``inline-signing``, the signed version of the zone is discarded; after the retransfer of the unsigned version is complete, the signed version is regenerated - with new signatures. + with new signatures. With the optional ``-force`` argument provided + if there is an ongoing zone transfer it will be aborted before a new zone + transfer is scheduled. .. option:: scan diff --git a/bin/tests/system/xfer/ns1/axfr-rndc-retransfer-force.db b/bin/tests/system/xfer/ns1/axfr-rndc-retransfer-force.db new file mode 100644 index 0000000000..252925f8dc --- /dev/null +++ b/bin/tests/system/xfer/ns1/axfr-rndc-retransfer-force.db @@ -0,0 +1,15 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 3600 +@ IN SOA . . 0 0 0 0 0 +@ IN NS . +$GENERATE 1-5000 host$ TXT data-$ diff --git a/bin/tests/system/xfer/ns1/named1.conf.in b/bin/tests/system/xfer/ns1/named1.conf.in index 1f010ff888..d3f597dfda 100644 --- a/bin/tests/system/xfer/ns1/named1.conf.in +++ b/bin/tests/system/xfer/ns1/named1.conf.in @@ -47,6 +47,7 @@ zone "edns-expire" { file "edns-expire.db"; }; + zone "axfr-max-transfer-time" { type primary; file "axfr-max-transfer-time.db"; @@ -57,6 +58,11 @@ zone "axfr-max-idle-time" { file "axfr-max-idle-time.db"; }; +zone "axfr-rndc-retransfer-force" { + type primary; + file "axfr-rndc-retransfer-force.db"; +}; + zone "axfr-too-big" { type primary; file "axfr-too-big.db"; diff --git a/bin/tests/system/xfer/ns1/named2.conf.in b/bin/tests/system/xfer/ns1/named2.conf.in index 3803b49056..3c7f2fae20 100644 --- a/bin/tests/system/xfer/ns1/named2.conf.in +++ b/bin/tests/system/xfer/ns1/named2.conf.in @@ -40,3 +40,8 @@ zone "axfr-max-transfer-time" { type primary; file "axfr-max-transfer-time.db"; }; + +zone "axfr-rndc-retransfer-force" { + type primary; + file "axfr-rndc-retransfer-force.db"; +}; diff --git a/bin/tests/system/xfer/ns6/named.conf.in b/bin/tests/system/xfer/ns6/named.conf.in index 8fd14bd7b1..e98fc3b588 100644 --- a/bin/tests/system/xfer/ns6/named.conf.in +++ b/bin/tests/system/xfer/ns6/named.conf.in @@ -71,6 +71,12 @@ zone "axfr-max-idle-time" { file "axfr-max-idle-time.bk"; }; +zone "axfr-rndc-retransfer-force" { + type secondary; + primaries { 10.53.0.1; }; + file "axfr-rndc-retransfer-force.bk"; +}; + zone "axfr-too-big" { type secondary; max-records 30; diff --git a/bin/tests/system/xfer/tests.sh b/bin/tests/system/xfer/tests.sh index 950268def4..feeb3a679f 100755 --- a/bin/tests/system/xfer/tests.sh +++ b/bin/tests/system/xfer/tests.sh @@ -646,27 +646,50 @@ wait_for_message() ( grep -F "$1" wait_for_message.$n >/dev/null ) -nextpart ns6/named.run >/dev/null - -n=$((n + 1)) -echo_i "test max-transfer-time-in with 1 second timeout ($n)" +# Restart ns1 with -T transferslowly stop_server ns1 copy_setports ns1/named2.conf.in ns1/named.conf start_server --noclean --restart --port ${PORT} ns1 -- "-D xfer-ns1 $NS_PARAMS -T transferinsecs -T transferslowly" sleep 1 -$RNDCCMD 10.53.0.6 retransfer axfr-max-transfer-time 2>&1 | sed 's/^/ns6 /' | cat_i + +nextpart ns6/named.run >/dev/null + +n=$((n + 1)) +echo_i "test rndc retransfer -force ($n)" tmp=0 -retry_quiet 10 wait_for_message "maximum transfer time exceeded: timed out" || tmp=1 +$RNDCCMD 10.53.0.6 retransfer axfr-rndc-retransfer-force 2>&1 | sed 's/^/ns6 /' | cat_i +# Wait for at least one message +msg="'axfr-rndc-retransfer-force/IN' from 10.53.0.1#${PORT}: received" +retry_quiet 5 wait_for_message "$msg" || tmp=1 +# Issue a retransfer-force command which should cancel the ongoing transfer and start a new one +$RNDCCMD 10.53.0.6 retransfer -force axfr-rndc-retransfer-force 2>&1 | sed 's/^/ns6 /' | cat_i +msg="'axfr-rndc-retransfer-force/IN' from 10.53.0.1#${PORT}: Transfer status: operation canceled" +retry_quiet 5 wait_for_message "$msg" || tmp=1 +# Wait for the new transfer to complete successfully +msg="'axfr-rndc-retransfer-force/IN' from 10.53.0.1#${PORT}: Transfer status: success" +retry_quiet 30 wait_for_message "$msg" || tmp=1 +if test $tmp != 0; then echo_i "failed"; fi status=$((status + tmp)) nextpart ns6/named.run >/dev/null n=$((n + 1)) -echo_i "test max-transfer-idle-in with 50 seconds timeout ($n)" +echo_i "test max-transfer-time-in with 1 second timeout ($n)" +$RNDCCMD 10.53.0.6 retransfer axfr-max-transfer-time 2>&1 | sed 's/^/ns6 /' | cat_i +tmp=0 +retry_quiet 10 wait_for_message "maximum transfer time exceeded: timed out" || tmp=1 +status=$((status + tmp)) + +# Restart ns1 with -T transferstuck stop_server ns1 copy_setports ns1/named3.conf.in ns1/named.conf start_server --noclean --restart --port ${PORT} ns1 -- "-D xfer-ns1 $NS_PARAMS -T transferinsecs -T transferstuck" sleep 1 + +nextpart ns6/named.run >/dev/null + +n=$((n + 1)) +echo_i "test max-transfer-idle-in with 50 seconds timeout ($n)" start=$(date +%s) $RNDCCMD 10.53.0.6 retransfer axfr-max-idle-time 2>&1 | sed 's/^/ns6 /' | cat_i tmp=0 diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 623edf162b..a394ddd72e 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -1998,9 +1998,18 @@ dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr, */ void -dns_zone_forcereload(dns_zone_t *zone); +dns_zone_stopxfr(dns_zone_t *zone); /*%< - * Force a reload of specified zone. + * If 'zone' has an ongoing active transfer, stop it. + * + * Requires: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_forcexfr(dns_zone_t *zone); +/*%< + * Force a zone transfer of the specified zone. * * Requires: *\li 'zone' to be a valid zone. diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index 02488ebaed..523b59a5a6 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -1048,11 +1049,26 @@ dns_xfrin_gettsigkeyname(const dns_xfrin_t *xfr) { return (dst_key_name(xfr->tsigkey->key)); } +static void +xfrin_shutdown(void *arg) { + dns_xfrin_t *xfr = arg; + + REQUIRE(VALID_XFRIN(xfr)); + + xfrin_fail(xfr, ISC_R_CANCELED, "shut down"); + dns_xfrin_detach(&xfr); +} + void dns_xfrin_shutdown(dns_xfrin_t *xfr) { REQUIRE(VALID_XFRIN(xfr)); - xfrin_fail(xfr, ISC_R_CANCELED, "shut down"); + if (xfr->loop != isc_loop()) { + dns_xfrin_ref(xfr); + isc_async_run(xfr->loop, xfrin_shutdown, xfr); + } else { + xfrin_fail(xfr, ISC_R_CANCELED, "shut down"); + } } #if DNS_XFRIN_TRACE @@ -1110,7 +1126,10 @@ xfrin_fail(dns_xfrin_t *xfr, isc_result_t result, const char *msg) { { xfrin_log(xfr, ISC_LOG_ERROR, "%s: %s", msg, isc_result_totext(result)); - if (atomic_load(&xfr->is_ixfr)) { + if (atomic_load(&xfr->is_ixfr) && + result != ISC_R_CANCELED && + result != ISC_R_SHUTTINGDOWN) + { /* * Pass special result code to force AXFR retry */ diff --git a/lib/dns/zone.c b/lib/dns/zone.c index ce803abf0b..0bf4b6b865 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -17795,6 +17795,21 @@ again: inc_stats(zone, dns_zonestatscounter_xfrfail); break; + case ISC_R_CANCELED: + /* + * A new "retransfer" command with a "-force" argument could + * have canceled the current transfer in which case we should + * make sure to try again from the beginning. + */ + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER)) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH); + again = true; + } + FALLTHROUGH; + case ISC_R_SHUTTINGDOWN: + dns_remote_reset(&zone->primaries, true); + break; + default: next_primary: /* @@ -19458,7 +19473,29 @@ dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, } void -dns_zone_forcereload(dns_zone_t *zone) { +dns_zone_stopxfr(dns_zone_t *zone) { + dns_xfrin_t *xfr = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + + RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_read); + LOCK_ZONE(zone); + if (zone->statelist == &zone->zmgr->xfrin_in_progress && + zone->xfr != NULL) + { + dns_xfrin_attach(zone->xfr, &xfr); + } + UNLOCK_ZONE(zone); + RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_read); + + if (xfr != NULL) { + dns_xfrin_shutdown(xfr); + dns_xfrin_detach(&xfr); + } +} + +void +dns_zone_forcexfr(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); if (zone->type == dns_zone_primary ||