From 91ea1562030d1efd58e1e035fcfde3d8962a1f70 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Mon, 10 Jun 2024 16:48:26 +0000 Subject: [PATCH 1/4] Implement the min-transfer-rate-in configuration option This new option sets a minimum amount of transfer rate for an incoming zone transfer that will abort a transfer, which for some network related reasons run very slowly. --- bin/named/config.c | 1 + bin/named/zoneconf.c | 27 ++++++++++++++++++++++++++ doc/misc/mirror.zoneopt | 1 + doc/misc/options | 2 ++ doc/misc/secondary.zoneopt | 1 + doc/misc/stub.zoneopt | 1 + lib/dns/include/dns/zone.h | 30 +++++++++++++++++++++++++++++ lib/dns/xfrin.c | 39 ++++++++++++++++++++++++++++++++++++++ lib/dns/zone.c | 24 +++++++++++++++++++++++ lib/isccfg/namedconf.c | 16 ++++++++++++++++ 10 files changed, 142 insertions(+) diff --git a/bin/named/config.c b/bin/named/config.c index 23df890193..e3aaecdd3e 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -232,6 +232,7 @@ options {\n\ max-transfer-time-out 120;\n\ min-refresh-time 300;\n\ min-retry-time 500;\n\ + min-transfer-rate-in 10240 5;\n\ multi-master no;\n\ notify yes;\n\ notify-delay 5;\n\ diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 00be0fe2a6..43a4943b8d 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1874,6 +1874,33 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, } dns_zone_setoption(mayberaw, DNS_ZONEOPT_MULTIMASTER, multi); + obj = NULL; + result = named_config_get(maps, "min-transfer-rate-in", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + uint32_t traffic_bytes = + cfg_obj_asuint32(cfg_tuple_get(obj, "traffic_bytes")); + uint32_t time_minutes = + cfg_obj_asuint32(cfg_tuple_get(obj, "time_minutes")); + if (traffic_bytes == 0) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "zone '%s': 'min-transfer-rate-in' bytes" + "value can not be '0'", + zname); + CHECK(ISC_R_FAILURE); + } + /* Max. 28 days (in minutes). */ + const unsigned int time_minutes_max = 28 * 24 * 60; + if (time_minutes < 1 || time_minutes > time_minutes_max) { + cfg_obj_log(obj, ISC_LOG_ERROR, + "zone '%s': 'min-transfer-rate-in' minutes" + "value is out of range (1..%u)", + zname, time_minutes_max); + CHECK(ISC_R_FAILURE); + } + dns_zone_setminxfrratein(mayberaw, traffic_bytes, + transferinsecs ? time_minutes + : time_minutes * 60); + obj = NULL; result = named_config_get(maps, "max-transfer-time-in", &obj); INSIST(result == ISC_R_SUCCESS && obj != NULL); diff --git a/doc/misc/mirror.zoneopt b/doc/misc/mirror.zoneopt index 99f1212643..b1c5c08ea7 100644 --- a/doc/misc/mirror.zoneopt +++ b/doc/misc/mirror.zoneopt @@ -26,6 +26,7 @@ zone [ ] { max-types-per-name ; min-refresh-time ; min-retry-time ; + min-transfer-rate-in ; multi-master ; notify ( explicit | master-only | primary-only | ); notify-delay ; diff --git a/doc/misc/options b/doc/misc/options index baa4e3696f..f49c0a8800 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -202,6 +202,7 @@ options { min-ncache-ttl ; min-refresh-time ; min-retry-time ; + min-transfer-rate-in ; minimal-any ; minimal-responses ( no-auth | no-auth-recursive | ); multi-master ; @@ -484,6 +485,7 @@ view [ ] { min-ncache-ttl ; min-refresh-time ; min-retry-time ; + min-transfer-rate-in ; minimal-any ; minimal-responses ( no-auth | no-auth-recursive | ); multi-master ; diff --git a/doc/misc/secondary.zoneopt b/doc/misc/secondary.zoneopt index e5bbb1816c..6fbe1fbaf5 100644 --- a/doc/misc/secondary.zoneopt +++ b/doc/misc/secondary.zoneopt @@ -38,6 +38,7 @@ zone [ ] { max-types-per-name ; min-refresh-time ; min-retry-time ; + min-transfer-rate-in ; multi-master ; notify ( explicit | master-only | primary-only | ); notify-delay ; diff --git a/doc/misc/stub.zoneopt b/doc/misc/stub.zoneopt index 4781f4d720..97b9ba0578 100644 --- a/doc/misc/stub.zoneopt +++ b/doc/misc/stub.zoneopt @@ -18,6 +18,7 @@ zone [ ] { max-types-per-name ; min-refresh-time ; min-retry-time ; + min-transfer-rate-in ; multi-master ; primaries [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; transfer-source ( | * ); diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 4524142525..63b39eb1b8 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -1233,6 +1233,36 @@ dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from, *\li DNS_R_SUCCESS */ +void +dns_zone_setminxfrratein(dns_zone_t *zone, uint32_t bytes, uint32_t seconds); +/*%< + * Set the minumum traffic rate (in bytes per seconds) that a zone transfer in + * (AXFR/IXFR) of this zone will use before being aborted. + * + * Requires: + * \li 'zone' to be valid initialised zone. + */ + +uint32_t +dns_zone_getminxfrratebytesin(dns_zone_t *zone); +/*%< + * Returns the 'bytes' portion of the minimum traffic rate for the transfer in + * for this zone. + * + * Requires: + *\li 'zone' to be valid initialised zone. + */ + +uint32_t +dns_zone_getminxfrratesecondsin(dns_zone_t *zone); +/*%< + * Returns the 'seconds' portion of the minimum traffic rate for the transfer in + * for this zone. + * + * Requires: + *\li 'zone' to be valid initialised zone. + */ + void dns_zone_setmaxxfrin(dns_zone_t *zone, uint32_t maxxfrin); /*%< diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index cefb35f634..c6167900c4 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -159,6 +159,7 @@ struct dns_xfrin { unsigned int maxrecords; /*%< The maximum number of * records set for the zone */ + uint64_t nbytes_saved; /*%< For enforcing the minimum transfer rate */ dns_tsigkey_t *tsigkey; /*%< Key used to create TSIG */ isc_buffer_t *lasttsig; /*%< The last TSIG */ @@ -192,6 +193,7 @@ struct dns_xfrin { isc_loop_t *loop; + isc_timer_t *min_rate_timer; isc_timer_t *max_time_timer; isc_timer_t *max_idle_timer; @@ -269,6 +271,8 @@ xfrin_timedout(void *); static void xfrin_idledout(void *); static void +xfrin_minratecheck(void *); +static void xfrin_fail(dns_xfrin_t *xfr, isc_result_t result, const char *msg); static isc_result_t render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf); @@ -963,6 +967,24 @@ xfrin_idledout(void *xfr) { xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum idle time exceeded"); } +static void +xfrin_minratecheck(void *arg) { + dns_xfrin_t *xfr = arg; + + REQUIRE(VALID_XFRIN(xfr)); + + const uint64_t nbytes = atomic_load_relaxed(&xfr->nbytes); + const uint64_t min = dns_zone_getminxfrratebytesin(xfr->zone); + + if (nbytes - xfr->nbytes_saved < min) { + isc_timer_stop(xfr->min_rate_timer); + xfrin_fail(xfr, ISC_R_TIMEDOUT, + "minimum transfer rate reached"); + } else { + xfr->nbytes_saved = nbytes; + } +} + isc_time_t dns_xfrin_getstarttime(dns_xfrin_t *xfr) { REQUIRE(VALID_XFRIN(xfr)); @@ -1326,6 +1348,15 @@ xfrin_start(dns_xfrin_t *xfr) { isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0); isc_timer_start(xfr->max_idle_timer, isc_timertype_once, &interval); + /* Set the minimum transfer rate checking timer */ + if (xfr->min_rate_timer == NULL) { + isc_timer_create(dns_zone_getloop(xfr->zone), + xfrin_minratecheck, xfr, &xfr->min_rate_timer); + } + isc_interval_set(&interval, dns_zone_getminxfrratesecondsin(xfr->zone), + 0); + isc_timer_start(xfr->min_rate_timer, isc_timertype_ticker, &interval); + /* * The connect has to be the last thing that is called before returning, * as it can end synchronously and destroy the xfr object. @@ -1606,6 +1637,8 @@ xfrin_send_request(dns_xfrin_t *xfr) { atomic_store_relaxed(&xfr->nbytes, 0); atomic_store_relaxed(&xfr->start, isc_time_now()); + xfr->nbytes_saved = 0; + msg->id = xfr->id; if (xfr->tsigctx != NULL) { dst_context_destroy(&xfr->tsigctx); @@ -1724,6 +1757,10 @@ xfrin_end(dns_xfrin_t *xfr, isc_result_t result) { isc_timer_stop(xfr->max_idle_timer); isc_timer_destroy(&xfr->max_idle_timer); } + if (xfr->min_rate_timer != NULL) { + isc_timer_stop(xfr->min_rate_timer); + isc_timer_destroy(&xfr->min_rate_timer); + } if (xfr->shutdown_result == ISC_R_UNSET) { xfr->shutdown_result = result; @@ -2017,6 +2054,7 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) { case XFRST_AXFR_END: case XFRST_IXFR_END: /* We are at the end, cancel the timers and IO */ + isc_timer_stop(xfr->min_rate_timer); isc_timer_stop(xfr->max_idle_timer); isc_timer_stop(xfr->max_time_timer); xfrin_cancelio(xfr); @@ -2182,6 +2220,7 @@ xfrin_destroy(dns_xfrin_t *xfr) { INSIST(xfr->max_time_timer == NULL); INSIST(xfr->max_idle_timer == NULL); + INSIST(xfr->min_rate_timer == NULL); isc_loop_detach(&xfr->loop); diff --git a/lib/dns/zone.c b/lib/dns/zone.c index ac41eb3601..9c3eafb95d 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -361,6 +361,8 @@ struct dns_zone { dns_request_t *request; dns_loadctx_t *loadctx; dns_dumpctx_t *dumpctx; + uint32_t minxfrratebytesin; + uint32_t minxfrratesecondsin; uint32_t maxxfrin; uint32_t maxxfrout; uint32_t idlein; @@ -16154,6 +16156,28 @@ message_count(dns_message_t *msg, dns_section_t section, dns_rdatatype_t type) { return count; } +void +dns_zone_setminxfrratein(dns_zone_t *zone, uint32_t bytes, uint32_t seconds) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->minxfrratebytesin = bytes; + zone->minxfrratesecondsin = seconds; +} + +uint32_t +dns_zone_getminxfrratebytesin(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return zone->minxfrratebytesin; +} + +uint32_t +dns_zone_getminxfrratesecondsin(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return zone->minxfrratesecondsin; +} + void dns_zone_setmaxxfrin(dns_zone_t *zone, uint32_t maxxfrin) { REQUIRE(DNS_ZONE_VALID(zone)); diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index e6bef96e3c..6319d0df8f 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2211,6 +2211,20 @@ static cfg_clausedef_t dnssecpolicy_clauses[] = { { NULL, NULL, 0 } }; +/* + * For min-transfer-rate-in. + */ +static cfg_tuplefielddef_t min_transfer_rate_fields[] = { + { "traffic_bytes", &cfg_type_uint32, 0 }, + { "time_minutes", &cfg_type_uint32, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_min_transfer_rate_in = { + "min-transfer-rate-in", cfg_parse_tuple, cfg_print_tuple, + cfg_doc_tuple, &cfg_rep_tuple, min_transfer_rate_fields +}; + /*% * Clauses that can be found in a 'zone' statement, * with defaults in the 'view' or 'options' statement. @@ -2304,6 +2318,8 @@ static cfg_clausedef_t zone_clauses[] = { CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB }, { "max-retry-time", &cfg_type_uint32, CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB }, + { "min-transfer-rate-in", &cfg_type_min_transfer_rate_in, + CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB }, { "max-transfer-idle-in", &cfg_type_uint32, CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB }, { "max-transfer-idle-out", &cfg_type_uint32, From f6dfff01ab453e91b1f563f13660b4e07efa632e Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Mon, 10 Jun 2024 16:49:56 +0000 Subject: [PATCH 2/4] Document the min-transfer-rate-in configuration option Add a new section in ARM describing min-transfer-rate-in. --- doc/arm/reference.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 27e789dd42..d541d705d6 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -3324,6 +3324,16 @@ options apply to zone transfers. global :any:`also-notify` list are not sent NOTIFY messages for that zone. The default is the empty list (no global notification list). +.. namedconf:statement:: min-transfer-rate-in + :tags: transfer + :short: Specifies the minimum traffic rate below which inbound zone transfers are terminated. + + Inbound zone transfers running slower than the given amount of bytes in the + given amount of minutes are terminated. This option takes two non-zero integer values. + A check is performed periodically every time the configured time interval + passes. The default value is ``10240 5``, i.e. 10240 bytes in 5 minutes. + The maximum time value is 28 days (40320 minutes). + .. namedconf:statement:: max-transfer-time-in :tags: transfer :short: Specifies the number of minutes after which inbound zone transfers are terminated. @@ -7068,6 +7078,9 @@ Zone Options :any:`max-records` See the description of :any:`max-records` in :ref:`server_resource_limits`. +:any:`min-transfer-rate-in` + See the description of :any:`min-transfer-rate-in` in :ref:`zone_transfers`. + :any:`max-transfer-time-in` See the description of :any:`max-transfer-time-in` in :ref:`zone_transfers`. From b9c6aa24f8c0c52d33fee11da28ec7316d6f4ed2 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Mon, 10 Jun 2024 16:50:37 +0000 Subject: [PATCH 3/4] Test the new min-transfer-rate-in configuration option Add a new big zone, run a zone transfer in slow mode, and check whether the zone transfer gets canceled because 100000 bytes are not transferred in 5 seconds (as it's running in slow mode). --- .../system/xfer/ns1/axfr-min-transfer-rate.db | 15 +++++++++++++++ bin/tests/system/xfer/ns1/named1.conf.in | 4 ++++ bin/tests/system/xfer/ns1/named2.conf.in | 5 +++++ bin/tests/system/xfer/ns6/named.conf.in | 8 ++++++++ bin/tests/system/xfer/tests.sh | 12 ++++++++++++ bin/tests/system/xfer/tests_sh_xfer.py | 1 + 6 files changed, 45 insertions(+) create mode 100644 bin/tests/system/xfer/ns1/axfr-min-transfer-rate.db diff --git a/bin/tests/system/xfer/ns1/axfr-min-transfer-rate.db b/bin/tests/system/xfer/ns1/axfr-min-transfer-rate.db new file mode 100644 index 0000000000..252925f8dc --- /dev/null +++ b/bin/tests/system/xfer/ns1/axfr-min-transfer-rate.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 990c911580..709187eff6 100644 --- a/bin/tests/system/xfer/ns1/named1.conf.in +++ b/bin/tests/system/xfer/ns1/named1.conf.in @@ -47,6 +47,10 @@ zone "edns-expire" { file "edns-expire.db"; }; +zone "axfr-min-transfer-rate" { + type primary; + file "axfr-min-transfer-rate.db"; +}; zone "axfr-max-transfer-time" { type primary; diff --git a/bin/tests/system/xfer/ns1/named2.conf.in b/bin/tests/system/xfer/ns1/named2.conf.in index 3c7f2fae20..aec68f3d1a 100644 --- a/bin/tests/system/xfer/ns1/named2.conf.in +++ b/bin/tests/system/xfer/ns1/named2.conf.in @@ -36,6 +36,11 @@ zone "." { file "root.db"; }; +zone "axfr-min-transfer-rate" { + type primary; + file "axfr-min-transfer-rate.db"; +}; + zone "axfr-max-transfer-time" { type primary; file "axfr-max-transfer-time.db"; diff --git a/bin/tests/system/xfer/ns6/named.conf.in b/bin/tests/system/xfer/ns6/named.conf.in index d6edf8a8a4..6a51f6e82d 100644 --- a/bin/tests/system/xfer/ns6/named.conf.in +++ b/bin/tests/system/xfer/ns6/named.conf.in @@ -32,6 +32,7 @@ options { ixfr-from-differences primary; check-integrity no; tcp-idle-timeout 600; + min-transfer-rate-in 10240 300; # this is tested as seconds, when used with '-T transferinsecs' (i.e. convert the default '10240 5' back so that it doesn't interfere with other tests) }; zone "." { @@ -57,6 +58,13 @@ zone "edns-expire" { file "edns-expire.bk"; }; +zone "axfr-min-transfer-rate" { + type secondary; + min-transfer-rate-in 100000 5; # this is tested as seconds, when used with '-T transferinsecs' (i.e. 100000 bytes in 5 seconds) + primaries { 10.53.0.1; }; + file "axfr-min-transfer-rate.bk"; +}; + zone "axfr-max-transfer-time" { type secondary; max-transfer-time-in 1; # this is tested as seconds, when used with '-T transferinsecs' diff --git a/bin/tests/system/xfer/tests.sh b/bin/tests/system/xfer/tests.sh index a8bca3dc3a..8098da23d6 100755 --- a/bin/tests/system/xfer/tests.sh +++ b/bin/tests/system/xfer/tests.sh @@ -709,11 +709,22 @@ status=$((status + tmp)) nextpart ns6/named.run >/dev/null +n=$((n + 1)) +echo_i "test min-transfer-rate-in with 5 seconds timeout ($n)" +$RNDCCMD 10.53.0.6 retransfer axfr-min-transfer-rate 2>&1 | sed 's/^/ns6 /' | cat_i +tmp=0 +retry_quiet 10 wait_for_message "minimum transfer rate reached: timed out" || 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-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 +if test $tmp != 0; then echo_i "failed"; fi status=$((status + tmp)) # Restart ns1 with -T transferstuck @@ -730,6 +741,7 @@ start=$(date +%s) $RNDCCMD 10.53.0.6 retransfer axfr-max-idle-time 2>&1 | sed 's/^/ns6 /' | cat_i tmp=0 retry_quiet 60 wait_for_message "maximum idle time exceeded: timed out" || tmp=1 +if test $tmp != 0; then echo_i "failed"; fi if [ $tmp -eq 0 ]; then now=$(date +%s) diff=$((now - start)) diff --git a/bin/tests/system/xfer/tests_sh_xfer.py b/bin/tests/system/xfer/tests_sh_xfer.py index 50efbca298..0b82e0bf12 100644 --- a/bin/tests/system/xfer/tests_sh_xfer.py +++ b/bin/tests/system/xfer/tests_sh_xfer.py @@ -41,6 +41,7 @@ pytestmark = pytest.mark.extra_artifacts( "ns4/root.db", "ns6/axfr-max-idle-time.bk", "ns6/axfr-max-transfer-time.bk", + "ns6/axfr-min-transfer-rate.bk", "ns6/axfr-rndc-retransfer-force.bk", "ns6/edns-expire.bk", "ns6/ixfr-too-big.bk", From c701b590e447301e7eca52ba3644fe60b633fa18 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Wed, 27 Nov 2024 10:34:40 +0000 Subject: [PATCH 4/4] Expose the incoming transfers' rates in the statistics channel Expose the average transfer rate (in bytes-per-second) during the last full 'min-transfer-rate-in ' minutes interval. If no such interval has passed yet, then the overall average rate is reported instead. --- bin/named/bind9.xsl | 2 ++ bin/named/statschannel.c | 13 +++++++++++-- doc/arm/reference.rst | 6 ++++++ lib/dns/include/dns/xfrin.h | 7 +++++-- lib/dns/xfrin.c | 29 +++++++++++++++++++++++++++-- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/bin/named/bind9.xsl b/bin/named/bind9.xsl index ac15b7c934..ae1820de0d 100644 --- a/bin/named/bind9.xsl +++ b/bin/named/bind9.xsl @@ -931,6 +931,7 @@ Messages Received Records Received Bytes Received + Transfer Rate (B/s) @@ -959,6 +960,7 @@ + diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index 887f21a1ed..8b1a1cd91c 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -1488,6 +1488,7 @@ xfrin_xmlrender(dns_zone_t *zone, void *arg) { unsigned int nmsg = 0; unsigned int nrecs = 0; uint64_t nbytes = 0; + uint64_t rate = 0; statlevel = dns_zone_getstatlevel(zone); if (statlevel == dns_zonestat_none) { @@ -1701,7 +1702,7 @@ xfrin_xmlrender(dns_zone_t *zone, void *arg) { TRY0(xmlTextWriterEndElement(writer)); if (is_running) { - dns_xfrin_getstats(xfr, &nmsg, &nrecs, &nbytes); + dns_xfrin_getstats(xfr, &nmsg, &nrecs, &nbytes, &rate); } TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "nmsg")); TRY0(xmlTextWriterWriteFormatString(writer, "%u", nmsg)); @@ -1712,6 +1713,9 @@ xfrin_xmlrender(dns_zone_t *zone, void *arg) { TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "nbytes")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64, nbytes)); TRY0(xmlTextWriterEndElement(writer)); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "rate")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64, rate)); + TRY0(xmlTextWriterEndElement(writer)); TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "ixfr")); if (is_running && is_first_data_received) { @@ -2559,6 +2563,7 @@ xfrin_jsonrender(dns_zone_t *zone, void *arg) { unsigned int nmsg = 0; unsigned int nrecs = 0; uint64_t nbytes = 0; + uint64_t rate = 0; statlevel = dns_zone_getstatlevel(zone); if (statlevel == dns_zonestat_none) { @@ -2756,7 +2761,7 @@ xfrin_jsonrender(dns_zone_t *zone, void *arg) { } if (is_running) { - dns_xfrin_getstats(xfr, &nmsg, &nrecs, &nbytes); + dns_xfrin_getstats(xfr, &nmsg, &nrecs, &nbytes, &rate); } json_object_object_add(xfrinobj, "nmsg", json_object_new_int64((int64_t)nmsg)); @@ -2766,6 +2771,10 @@ xfrin_jsonrender(dns_zone_t *zone, void *arg) { xfrinobj, "nbytes", json_object_new_int64(nbytes > INT64_MAX ? INT64_MAX : (int64_t)nbytes)); + json_object_object_add(xfrinobj, "rate", + json_object_new_int64(rate > INT64_MAX + ? INT64_MAX + : (int64_t)rate)); if (is_running && is_first_data_received) { json_object_object_add( diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index d541d705d6..1af2777594 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -7835,6 +7835,12 @@ Incoming Zone Transfers 64-bit unsigned Integer. This is the number of usable bytes of DNS data. It does not include transport overhead. + ``Transfer Rate (B/s)`` (``rate``) + 64 bit unsigned Integer. This is the average zone transfer rate in + bytes-per-second during the latest full interval that is configured by the + :any:`min-transfer-rate-in` configuration option. If no such interval + has passed yet, then the overall average rate is reported instead. + .. note:: Depending on the current state of the transfer, some of the values may be empty or set to ``-`` (meaning "not available"). diff --git a/lib/dns/include/dns/xfrin.h b/lib/dns/include/dns/xfrin.h index 52c74ec91a..161737abfa 100644 --- a/lib/dns/include/dns/xfrin.h +++ b/lib/dns/include/dns/xfrin.h @@ -140,10 +140,13 @@ dns_xfrin_getendserial(dns_xfrin_t *xfr); void dns_xfrin_getstats(dns_xfrin_t *xfr, unsigned int *nmsgp, unsigned int *nrecsp, - uint64_t *nbytesp); + uint64_t *nbytesp, uint64_t *ratep); /*%< * Get various statistics values of the xfrin object: number of the received - * messages, number of the received records, number of the received bytes. + * messages, number of the received records, number of the received bytes, + * and the average transfer rate (in bytes-per-second) during the last full + * 'min-transfer-rate-in ' minutes interval. If no such + * interval has passed yet, then the overall average rate is reported instead. * * Requires: *\li 'xfr' is a valid dns_xfrin_t. diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index c6167900c4..a352d93353 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -154,6 +154,7 @@ struct dns_xfrin { atomic_uint nrecs; /*%< Number of records recvd */ atomic_uint_fast64_t nbytes; /*%< Number of bytes received */ _Atomic(isc_time_t) start; /*%< Start time of the transfer */ + atomic_uint_fast64_t rate_bytes_per_second; _Atomic(dns_transport_type_t) soa_transport_type; atomic_uint_fast32_t end_serial; @@ -975,13 +976,21 @@ xfrin_minratecheck(void *arg) { const uint64_t nbytes = atomic_load_relaxed(&xfr->nbytes); const uint64_t min = dns_zone_getminxfrratebytesin(xfr->zone); + uint64_t rate = nbytes - xfr->nbytes_saved; - if (nbytes - xfr->nbytes_saved < min) { + if (rate < min) { isc_timer_stop(xfr->min_rate_timer); xfrin_fail(xfr, ISC_R_TIMEDOUT, "minimum transfer rate reached"); } else { xfr->nbytes_saved = nbytes; + + /* + * Calculate and store for the statistics channel the transfer + * rate in bytes-per-second for the latest interval. + */ + rate /= dns_zone_getminxfrratesecondsin(xfr->zone); + atomic_store_relaxed(&xfr->rate_bytes_per_second, rate); } } @@ -1046,13 +1055,29 @@ dns_xfrin_getendserial(dns_xfrin_t *xfr) { void dns_xfrin_getstats(dns_xfrin_t *xfr, unsigned int *nmsgp, unsigned int *nrecsp, - uint64_t *nbytesp) { + uint64_t *nbytesp, uint64_t *ratep) { REQUIRE(VALID_XFRIN(xfr)); REQUIRE(nmsgp != NULL && nrecsp != NULL && nbytesp != NULL); + uint64_t rate = atomic_load_relaxed(&xfr->rate_bytes_per_second); + if (rate == 0) { + /* + * Likely the first 'min-transfer-rate-in ' + * minutes interval hasn't passed yet. Calculate the overall + * average transfer rate instead. + */ + isc_time_t now = isc_time_now(); + isc_time_t start = atomic_load_relaxed(&xfr->start); + uint64_t sec = isc_time_microdiff(&now, &start) / US_PER_SEC; + if (sec > 0) { + rate = atomic_load_relaxed(&xfr->nbytes) / sec; + } + } + SET_IF_NOT_NULL(nmsgp, atomic_load_relaxed(&xfr->nmsg)); SET_IF_NOT_NULL(nrecsp, atomic_load_relaxed(&xfr->nrecs)); SET_IF_NOT_NULL(nbytesp, atomic_load_relaxed(&xfr->nbytes)); + SET_IF_NOT_NULL(ratep, rate); } const isc_sockaddr_t *