diff --git a/CHANGES b/CHANGES index f2789a4e73..ba3506a109 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +5919. [func] The "rndc fetchlimit" command lists name servers + and domain names that are being rate-limited by + "fetches-per-server" or "fetches-per-zone" limits. + [GL #665] + 5918. [test] Convert system tests to use a default HMAC algorithm where the test is not HMAC specific. [GL #3433] diff --git a/bin/named/control.c b/bin/named/control.c index 29c848b070..0b742407fe 100644 --- a/bin/named/control.c +++ b/bin/named/control.c @@ -218,6 +218,8 @@ named_control_docommand(isccc_sexpr_t *message, bool readonly, result = ISC_R_SUCCESS; } else if (command_compare(command, NAMED_COMMAND_DUMPSTATS)) { result = named_server_dumpstats(named_g_server); + } else if (command_compare(command, NAMED_COMMAND_FETCHLIMIT)) { + result = named_server_fetchlimit(named_g_server, lex, text); } else if (command_compare(command, NAMED_COMMAND_FLUSH)) { result = named_server_flushcache(named_g_server, lex); } else if (command_compare(command, NAMED_COMMAND_FLUSHNAME)) { diff --git a/bin/named/include/named/control.h b/bin/named/include/named/control.h index 1ea4c490fb..425b50696f 100644 --- a/bin/named/include/named/control.h +++ b/bin/named/include/named/control.h @@ -70,6 +70,7 @@ #define NAMED_COMMAND_DNSTAP "dnstap" #define NAMED_COMMAND_TCPTIMEOUTS "tcp-timeouts" #define NAMED_COMMAND_SERVESTALE "serve-stale" +#define NAMED_COMMAND_FETCHLIMIT "fetchlimit" isc_result_t named_controls_create(named_server_t *server, named_controls_t **ctrlsp); diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index d1c97cd853..ce45b440c1 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -401,3 +401,10 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text); isc_result_t named_server_servestale(named_server_t *server, isc_lex_t *lex, isc_buffer_t **text); + +/*% + * Report fetch-limited ADB server addresses. + */ +isc_result_t +named_server_fetchlimit(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); diff --git a/bin/named/server.c b/bin/named/server.c index dcb7a708f4..fadab6ece2 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -16600,3 +16600,85 @@ cleanup: return (result); } + +isc_result_t +named_server_fetchlimit(named_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) { + isc_result_t result = ISC_R_SUCCESS; + dns_view_t *view = NULL; + char *ptr = NULL, *viewname = NULL; + bool first = true; + + REQUIRE(text != NULL); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Look for the view name. */ + viewname = next_token(lex, text); + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + char tbuf[100]; + unsigned int used; + uint32_t val; + int s; + + if (view->rdclass != dns_rdataclass_in) { + continue; + } + + if (viewname != NULL && strcasecmp(view->name, viewname) != 0) { + continue; + } + + if (view->adb == NULL) { + continue; + } + + if (!first) { + putstr(text, "\n"); + } + putstr(text, "Rate limited servers, view "); + putstr(text, view->name); + + dns_adb_getquota(view->adb, &val, NULL, NULL, NULL, NULL); + s = snprintf(tbuf, sizeof(tbuf), + " (fetches-per-server %u):", val); + if (s < 0 || (unsigned)s > sizeof(tbuf)) { + return (ISC_R_NOSPACE); + } + first = false; + putstr(text, tbuf); + used = isc_buffer_usedlength(*text); + CHECK(dns_adb_dumpquota(view->adb, text)); + 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: + if (isc_buffer_usedlength(*text) > 0) { + (void)putnull(text); + } + + return (result); +} diff --git a/bin/rndc/rndc.rst b/bin/rndc/rndc.rst index 6717e242d1..c075bf163a 100644 --- a/bin/rndc/rndc.rst +++ b/bin/rndc/rndc.rst @@ -204,6 +204,13 @@ Currently supported commands are: (See the ``dump-file`` option in the BIND 9 Administrator Reference Manual.) +.. option:: fetchlimit [view] + + 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 This command flushes the server's cache. diff --git a/bin/tests/system/fetchlimit/tests.sh b/bin/tests/system/fetchlimit/tests.sh index c10bee6677..c0da8d2d7e 100644 --- a/bin/tests/system/fetchlimit/tests.sh +++ b/bin/tests/system/fetchlimit/tests.sh @@ -58,12 +58,11 @@ if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status+ret)) echo_i "dumping ADB data" -$RNDCCMD dumpdb -adb -info=`grep '10.53.0.4' ns3/named_dump.db | sed 's/.*\(atr [.0-9]*\).*\(quota [0-9]*\).*/\1 \2/'` +info=$($RNDCCMD fetchlimit | grep 10.53.0.4 | sed 's/.*quota .*(\([0-9]*\).*atr \([.0-9]*\).*/\2 \1/') echo_i $info set -- $info -quota=$5 -[ ${5:-200} -lt 200 ] || ret=1 +quota=$2 +[ ${quota:-200} -lt 200 ] || ret=1 echo_i "checking servfail statistics" ret=0 @@ -92,12 +91,11 @@ for try in 1 2 3 4 5; do done echo_i "dumping ADB data" -$RNDCCMD dumpdb -adb -info=`grep '10.53.0.4' ns3/named_dump.db | sed 's/.*\(atr [.0-9]*\).*\(quota [0-9]*\).*/\1 \2/'` +info=$($RNDCCMD fetchlimit | grep 10.53.0.4 | sed 's/.*quota .*(\([0-9]*\).*atr \([.0-9]*\).*/\2 \1/') echo_i $info set -- $info -[ ${5:-${quota}} -lt $quota ] || ret=1 -quota=$5 +[ ${2:-${quota}} -lt $quota ] || ret=1 +quota=$2 for try in 1 2 3 4 5 6 7 8 9 10; do burst c $try @@ -107,12 +105,11 @@ for try in 1 2 3 4 5 6 7 8 9 10; do done echo_i "dumping ADB data" -$RNDCCMD dumpdb -adb -info=`grep '10.53.0.4' ns3/named_dump.db | sed 's/.*\(atr [.0-9]*\).*\(quota [0-9]*\).*/\1 \2/'` +info=$($RNDCCMD fetchlimit | grep 10.53.0.4 | sed 's/.*quota .*(\([0-9]*\).*atr \([.0-9]*\).*/\2 \1/') echo_i $info set -- $info -[ ${5:-${quota}} -gt $quota ] || ret=1 -quota=$5 +[ ${2:-${quota}} -gt $quota ] || ret=1 +quota=$2 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status+ret)) @@ -131,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 4023b82ee2..a8814bfd71 100644 --- a/doc/man/rndc.8in +++ b/doc/man/rndc.8in @@ -226,6 +226,14 @@ Manual.) .UNINDENT .INDENT 0.0 .TP +.B fetchlimit [view] +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 .B flush This command flushes the server\(aqs cache. .UNINDENT diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index 1a48dae0a5..29682896e0 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -25,7 +25,10 @@ Known Issues New Features ~~~~~~~~~~~~ -- None. +- The new ``rndc fetchlimit`` command prints a list of name server + addresses that are currently rate-limited due to ``fetches-per-server`` + and domain names that are rate limited due to ``fetches-per-zone``. + :gl:`#665` Removed Features ~~~~~~~~~~~~~~~~ diff --git a/lib/dns/adb.c b/lib/dns/adb.c index 92fd72418f..b97f3e6390 100644 --- a/lib/dns/adb.c +++ b/lib/dns/adb.c @@ -2880,8 +2880,8 @@ dump_entry(FILE *f, dns_adb_t *adb, dns_adbentry_t *entry, bool debug, } } -void -dns_adb_dumpfind(dns_adbfind_t *find, FILE *f) { +static void +dumpfind(dns_adbfind_t *find, FILE *f) { char tmp[512]; const char *tmpp = NULL; dns_adbaddrinfo_t *ai = NULL; @@ -2971,11 +2971,72 @@ print_find_list(FILE *f, dns_adbname_t *name) { find = ISC_LIST_HEAD(name->finds); while (find != NULL) { - dns_adb_dumpfind(find, f); + dumpfind(find, f); find = ISC_LIST_NEXT(find, plink); } } +static isc_result_t +putstr(isc_buffer_t **b, const char *str) { + isc_result_t result; + + result = isc_buffer_reserve(b, strlen(str)); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_putstr(*b, str); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_adb_dumpquota(dns_adb_t *adb, isc_buffer_t **buf) { + isc_result_t result; + isc_ht_iter_t *it = NULL; + + REQUIRE(DNS_ADB_VALID(adb)); + + RWLOCK(&adb->entries_lock, isc_rwlocktype_read); + isc_ht_iter_create(adb->entrybuckets, &it); + for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS; + result = isc_ht_iter_next(it)) + { + dns_adbentrybucket_t *ebucket = NULL; + dns_adbentry_t *entry = NULL; + + isc_ht_iter_current(it, (void **)&ebucket); + LOCK(&ebucket->lock); + for (entry = ISC_LIST_HEAD(ebucket->entries); entry != NULL; + entry = ISC_LIST_NEXT(entry, plink)) + { + char addrbuf[ISC_NETADDR_FORMATSIZE]; + char text[BUFSIZ]; + isc_netaddr_t netaddr; + + if (entry->atr == 0.0 && entry->quota == adb->quota) { + continue; + } + + isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr); + isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf)); + + snprintf(text, sizeof(text), + "\n- quota %s (%" PRIuFAST32 "/%d) atr %0.2f", + addrbuf, atomic_load_relaxed(&entry->quota), + adb->quota, entry->atr); + putstr(buf, text); + } + UNLOCK(&ebucket->lock); + } + RWUNLOCK(&adb->entries_lock, isc_rwlocktype_read); + isc_ht_iter_destroy(&it); + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + return (result); +} + static isc_result_t dbfind_name(dns_adbname_t *adbname, isc_stdtime_t now, dns_rdatatype_t rdtype) { isc_result_t result; @@ -3965,6 +4026,32 @@ dns_adb_setquota(dns_adb_t *adb, uint32_t quota, uint32_t freq, double low, adb->atr_discount = discount; } +void +dns_adb_getquota(dns_adb_t *adb, uint32_t *quotap, uint32_t *freqp, + double *lowp, double *highp, double *discountp) { + REQUIRE(DNS_ADB_VALID(adb)); + + if (quotap != NULL) { + *quotap = adb->quota; + } + + if (freqp != NULL) { + *freqp = adb->atr_freq; + } + + if (lowp != NULL) { + *lowp = adb->atr_low; + } + + if (highp != NULL) { + *highp = adb->atr_high; + } + + if (discountp != NULL) { + *discountp = adb->atr_discount; + } +} + bool dns_adbentry_overquota(dns_adbentry_t *entry) { uint_fast32_t quota, active; diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h index 05b9c91b29..1daa6b8e08 100644 --- a/lib/dns/include/dns/adb.h +++ b/lib/dns/include/dns/adb.h @@ -454,29 +454,15 @@ dns_adb_destroyfind(dns_adbfind_t **find); void dns_adb_dump(dns_adb_t *adb, FILE *f); /*%< - * This function is only used for debugging. It will dump as much of the - * state of the running system as possible. + * Used by "rndc dumpdb": Dump the state of the running ADB. * * Requires: * - *\li adb be valid. + *\li adb is valid. * *\li f != NULL, and is a file open for writing. */ -void -dns_adb_dumpfind(dns_adbfind_t *find, FILE *f); -/*%< - * This function is only used for debugging. Dump the data associated - * with a find. - * - * Requires: - * - *\li find is valid. - * - * \li f != NULL, and is a file open for writing. - */ - isc_result_t dns_adb_marklame(dns_adb_t *adb, dns_adbaddrinfo_t *addr, const dns_name_t *qname, dns_rdatatype_t type, @@ -762,6 +748,20 @@ dns_adb_setquota(dns_adb_t *adb, uint32_t quota, uint32_t freq, double low, *\li 'adb' is valid. */ +void +dns_adb_getquota(dns_adb_t *adb, uint32_t *quotap, uint32_t *freqp, + double *lowp, double *highp, double *discountp); +/*%< + * Get the quota values set by dns_adb_setquota(). + * If any of the 'quotap', 'freqp', 'lowp', 'highp', and + * 'discountp' parameters are non-NULL, then the memory they + * point to will be updated to hold the corresponding quota + * or parameter value. + * + * Requires: + *\li 'adb' is valid. + */ + bool dns_adbentry_overquota(dns_adbentry_t *entry); /*%< @@ -796,4 +796,16 @@ dns_adb_getstats(dns_adb_t *adb); * Requires: * \li 'adb' is valid. */ + +isc_result_t +dns_adb_dumpquota(dns_adb_t *adb, isc_buffer_t **buf); +/*% + * Dump the addresses, current quota values, and current ATR values + * for all servers that are currently being fetchlimited. Servers + * for which the quota is still equal to the default and the ATR + * is zero are not printed. + * + * Requires: + * \li 'adb' is valid. + */ ISC_LANG_ENDDECLS 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/include/dns/view.h b/lib/dns/include/dns/view.h index 5d78999941..7a9a48c269 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -868,28 +868,6 @@ dns_view_dialup(dns_view_t *view); * Perform dialup-time maintenance on the zones of 'view'. */ -isc_result_t -dns_view_dumpdbtostream(dns_view_t *view, FILE *fp); -/*%< - * Dump the current state of the view 'view' to the stream 'fp' - * for purposes of analysis or debugging. - * - * Currently the dumped state includes the view's cache; in the future - * it may also include other state such as the address database. - * It will not not include authoritative data since it is voluminous and - * easily obtainable by other means. - * - * Requires: - * - *\li 'view' is valid. - * - *\li 'fp' refers to a file open for writing. - * - * Returns: - * \li ISC_R_SUCCESS The cache was successfully dumped. - * \li others An error occurred (see dns_master_dump) - */ - isc_result_t dns_view_flushcache(dns_view_t *view, bool fixuponly); /*%< 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) { diff --git a/lib/dns/view.c b/lib/dns/view.c index 4200eeb8c5..3a00a0a46c 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -1472,25 +1472,6 @@ dns_view_checksig(dns_view_t *view, isc_buffer_t *source, dns_message_t *msg) { view->dynamickeys)); } -isc_result_t -dns_view_dumpdbtostream(dns_view_t *view, FILE *fp) { - isc_result_t result; - - REQUIRE(DNS_VIEW_VALID(view)); - - (void)fprintf(fp, ";\n; Cache dump of view '%s'\n;\n", view->name); - result = dns_master_dumptostream(view->mctx, view->cachedb, NULL, - &dns_master_style_cache, - dns_masterformat_text, NULL, fp); - if (result != ISC_R_SUCCESS) { - return (result); - } - dns_adb_dump(view->adb, fp); - dns_resolver_printbadcache(view->resolver, fp); - dns_badcache_print(view->failcache, "SERVFAIL cache", fp); - return (ISC_R_SUCCESS); -} - isc_result_t dns_view_flushcache(dns_view_t *view, bool fixuponly) { isc_result_t result;