From 549cf0f3e65e31e8a5d99ff819f38b8a80ea93be Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Thu, 26 May 2022 14:43:23 -0700 Subject: [PATCH] "rndc fetchlimit" now also lists rate-limited domains "rndc fetchlimit" now also prints a list of domain names that are currently rate-limited by "fetches-per-zone". The "fetchlimit" system test has been updated to use this feature to check that domain limits are applied correctly. --- bin/named/server.c | 17 ++++++- bin/rndc/rndc.rst | 6 ++- bin/tests/system/fetchlimit/tests.sh | 5 ++- doc/man/rndc.8in | 6 ++- lib/dns/include/dns/resolver.h | 5 +++ lib/dns/resolver.c | 66 ++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 7 deletions(-) diff --git a/bin/named/server.c b/bin/named/server.c index b109683711..fadab6ece2 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -16622,7 +16622,7 @@ named_server_fetchlimit(named_server_t *server, isc_lex_t *lex, for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; view = ISC_LIST_NEXT(view, link)) { - char tbuf[BUFSIZ]; + char tbuf[100]; unsigned int used; uint32_t val; int s; @@ -16658,6 +16658,21 @@ named_server_fetchlimit(named_server_t *server, isc_lex_t *lex, if (used == isc_buffer_usedlength(*text)) { putstr(text, "\n None."); } + + putstr(text, "\nRate limited servers, view "); + putstr(text, view->name); + val = dns_resolver_getfetchesperzone(view->resolver); + s = snprintf(tbuf, sizeof(tbuf), + " (fetches-per-zone %u):", val); + if (s < 0 || (unsigned)s > sizeof(tbuf)) { + return (ISC_R_NOSPACE); + } + putstr(text, tbuf); + used = isc_buffer_usedlength(*text); + CHECK(dns_resolver_dumpquota(view->resolver, text)); + if (used == isc_buffer_usedlength(*text)) { + putstr(text, "\n None."); + } } cleanup: diff --git a/bin/rndc/rndc.rst b/bin/rndc/rndc.rst index 2236d1d911..c075bf163a 100644 --- a/bin/rndc/rndc.rst +++ b/bin/rndc/rndc.rst @@ -206,8 +206,10 @@ Currently supported commands are: .. option:: fetchlimit [view] - This command dumps a list of servers which are currently being - rate-limited as a result of ``fetches-per-server`` settings. + This command dumps a list of servers that are currently being + rate-limited as a result of ``fetches-per-server`` settings, and + a list of domain names that are currently being rate-limited as + a result of ``fetches-per-zone`` settings. .. option:: flush diff --git a/bin/tests/system/fetchlimit/tests.sh b/bin/tests/system/fetchlimit/tests.sh index a96caafa9d..c0da8d2d7e 100644 --- a/bin/tests/system/fetchlimit/tests.sh +++ b/bin/tests/system/fetchlimit/tests.sh @@ -128,9 +128,10 @@ for try in 1 2 3 4 5; do success=$((success+1)) grep "status: SERVFAIL" dig.out.ns3.$try > /dev/null 2>&1 && \ fail=$(($fail+1)) - stat 30 50 || ret=1 + stat 40 40 || ret=1 + allowed=$($RNDCCMD fetchlimit | awk '/lamesub/ { print $6 }') + [ "${allowed:-0}" -eq 40 ] || ret=1 [ $ret -eq 1 ] && break - $RNDCCMD recursing 2>&1 | sed 's/^/ns3 /' | cat_i sleep 1 done echo_i "$success successful valid queries, $fail SERVFAIL" diff --git a/doc/man/rndc.8in b/doc/man/rndc.8in index c21fab7a61..a8814bfd71 100644 --- a/doc/man/rndc.8in +++ b/doc/man/rndc.8in @@ -227,8 +227,10 @@ Manual.) .INDENT 0.0 .TP .B fetchlimit [view] -This command dumps a list of servers which are currently being -rate\-limited as a result of \fBfetches\-per\-server\fP settings. +This command dumps a list of servers that are currently being +rate\-limited as a result of \fBfetches\-per\-server\fP settings, and +a list of domain names that are currently being rate\-limited as +a result of \fBfetches\-per\-zone\fP settings. .UNINDENT .INDENT 0.0 .TP diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index d1edc8e446..a9c2ce953f 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -534,6 +534,9 @@ dns_resolver_setclientsperquery(dns_resolver_t *resolver, uint32_t min, void dns_resolver_setfetchesperzone(dns_resolver_t *resolver, uint32_t clients); +uint32_t +dns_resolver_getfetchesperzone(dns_resolver_t *resolver); + void dns_resolver_getclientsperquery(dns_resolver_t *resolver, uint32_t *cur, uint32_t *min, uint32_t *max); @@ -703,6 +706,8 @@ dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which); void dns_resolver_dumpfetches(dns_resolver_t *resolver, isc_statsformat_t format, FILE *fp); +isc_result_t +dns_resolver_dumpquota(dns_resolver_t *res, isc_buffer_t **buf); #ifdef ENABLE_AFL /*% diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index ba258a82fe..984765e19f 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -10075,6 +10075,7 @@ destroy(dns_resolver_t *res) { } isc_mem_put(res->mctx, res->tasks, res->ntasks * sizeof(res->tasks[0])); + RWLOCK(&res->hash_lock, isc_rwlocktype_write); isc_ht_iter_create(res->buckets, &it); for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS; result = isc_ht_iter_delcurrent_next(it)) @@ -10088,8 +10089,10 @@ destroy(dns_resolver_t *res) { } isc_ht_iter_destroy(&it); isc_ht_destroy(&res->buckets); + RWUNLOCK(&res->hash_lock, isc_rwlocktype_write); isc_rwlock_destroy(&res->hash_lock); + RWLOCK(&res->zonehash_lock, isc_rwlocktype_write); isc_ht_iter_create(res->zonebuckets, &it); for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS; result = isc_ht_iter_delcurrent_next(it)) @@ -10109,6 +10112,7 @@ destroy(dns_resolver_t *res) { } isc_ht_iter_destroy(&it); isc_ht_destroy(&res->zonebuckets); + RWUNLOCK(&res->zonehash_lock, isc_rwlocktype_write); isc_rwlock_destroy(&res->zonehash_lock); if (res->dispatches4 != NULL) { @@ -11352,6 +11356,13 @@ dns_resolver_setfetchesperzone(dns_resolver_t *resolver, uint32_t clients) { atomic_store_release(&resolver->zspill, clients); } +uint32_t +dns_resolver_getfetchesperzone(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (atomic_load_relaxed(&resolver->zspill)); +} + bool dns_resolver_getzeronosoattl(dns_resolver_t *resolver) { REQUIRE(VALID_RESOLVER(resolver)); @@ -11486,6 +11497,61 @@ dns_resolver_dumpfetches(dns_resolver_t *res, isc_statsformat_t format, isc_ht_iter_destroy(&it); } +isc_result_t +dns_resolver_dumpquota(dns_resolver_t *res, isc_buffer_t **buf) { + isc_result_t result; + isc_ht_iter_t *it = NULL; + uint_fast32_t spill; + + REQUIRE(VALID_RESOLVER(res)); + + spill = atomic_load_acquire(&res->zspill); + if (spill == 0) { + return (ISC_R_SUCCESS); + } + + RWLOCK(&res->zonehash_lock, isc_rwlocktype_read); + isc_ht_iter_create(res->zonebuckets, &it); + for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS; + result = isc_ht_iter_next(it)) + { + zonebucket_t *bucket = NULL; + + isc_ht_iter_current(it, (void **)&bucket); + LOCK(&bucket->lock); + for (fctxcount_t *fc = ISC_LIST_HEAD(bucket->list); fc != NULL; + fc = ISC_LIST_NEXT(fc, link)) + { + char nb[DNS_NAME_FORMATSIZE], text[BUFSIZ]; + + if (fc->count < spill) { + continue; + } + + dns_name_format(fc->domain, nb, sizeof(nb)); + snprintf(text, sizeof(text), + "\n- %s: %u active (allowed %u spilled %u)", + nb, fc->count, fc->allowed, fc->dropped); + + result = isc_buffer_reserve(buf, strlen(text)); + if (result != ISC_R_SUCCESS) { + UNLOCK(&bucket->lock); + goto cleanup; + } + isc_buffer_putstr(*buf, text); + } + UNLOCK(&bucket->lock); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +cleanup: + RWUNLOCK(&res->zonehash_lock, isc_rwlocktype_read); + isc_ht_iter_destroy(&it); + return (result); +} + void dns_resolver_setquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which, isc_result_t resp) {