Commit a6d43d18 authored by Evan Hunt's avatar Evan Hunt

[master] fixed several RRL issues

3554.	[bug]		RRL failed to correctly rate-limit upward
			referrals and failed to count dropped error
			responses in the statistics. [RT #33225]
parent 330f98fe
3554. [bug] RRL failed to correctly rate-limit upward
referrals and failed to count dropped error
responses in the statistics. [RT #33225]
3553. [bug] Address suspected double free in acache. [RT #33252]
3552. [bug] Wrong getopt option string for 'nsupdate -r'.
......
......@@ -1257,11 +1257,13 @@ ns_client_error(ns_client_t *client, isc_result_t result) {
}
/*
* Some error responses cannot be 'slipped',
* so don't try.
* This will counted with dropped queries in the
* QryDropped counter.
* so don't try to slip any error responses.
*/
if (!client->view->rrl->log_only) {
isc_stats_increment(ns_g_server->nsstats,
dns_nsstatscounter_ratedropped);
isc_stats_increment(ns_g_server->nsstats,
dns_nsstatscounter_dropped);
ns_client_next(client, DNS_R_DROP);
return;
}
......
......@@ -191,7 +191,7 @@ inc_stats(ns_client_t *client, isc_statscounter_t counter) {
/* Do query type statistics
*
* We only increment per-type if we're using the authoriative
* We only increment per-type if we're using the authoritative
* answer counter, preventing double-counting.
*/
if (counter == dns_nsstatscounter_authans) {
......@@ -6246,7 +6246,8 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
* Rate limit these responses to this client.
*/
if (client->view->rrl != NULL &&
fname != NULL && dns_name_isabsolute(fname) &&
((fname != NULL && dns_name_isabsolute(fname)) ||
(result == ISC_R_NOTFOUND && !RECURSIONOK(client))) &&
(client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0) {
dns_rdataset_t nc_rdataset;
isc_boolean_t wouldlog;
......@@ -6289,8 +6290,19 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
dns_rdataset_disassociate(&nc_rdataset);
}
rrl_result = DNS_R_NXDOMAIN;
} else if (result == DNS_R_NXRRSET ||
result == DNS_R_EMPTYNAME) {
rrl_result = DNS_R_NXRRSET;
} else if (result == DNS_R_DELEGATION) {
rrl_result = result;
} else if (result == ISC_R_NOTFOUND) {
/*
* Handle referral to ".", including when recursion
* is off or not requested and the hints have not
* been loaded or we have "additional-from-cache no".
*/
tname = dns_rootname;
rrl_result = DNS_R_DELEGATION;
} else {
rrl_result = ISC_R_SUCCESS;
}
......
......@@ -1835,7 +1835,7 @@ configure_rpz(dns_view_t *view, const cfg_obj_t *rpz_obj,
return (ISC_R_SUCCESS);
}
#define CHECK_RRL(obj, cond, pat, val1, val2) \
#define CHECK_RRL(cond, pat, val1, val2) \
do { \
if (!(cond)) { \
cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, \
......@@ -1845,6 +1845,22 @@ configure_rpz(dns_view_t *view, const cfg_obj_t *rpz_obj,
} \
} while (0)
#define CHECK_RRL_RATE(rate, def, max_rate, name) \
do { \
obj = NULL; \
rrl->rate.str = name; \
result = cfg_map_get(map, name, &obj); \
if (result == ISC_R_SUCCESS) { \
rrl->rate.r = cfg_obj_asuint32(obj); \
CHECK_RRL(rrl->rate.r <= max_rate, \
name" %d > %d", \
rrl->rate.r, max_rate); \
} else { \
rrl->rate.r = def; \
} \
rrl->rate.scaled = rrl->rate.r; \
} while (0)
static isc_result_t
configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) {
const cfg_obj_t *obj;
......@@ -1875,86 +1891,39 @@ configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) {
result = cfg_map_get(map, "max-table-size", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= min_entries,
CHECK_RRL(i >= min_entries,
"max-table-size %d < min-table-size %d",
i, min_entries);
}
rrl->max_entries = i;
i = 0;
obj = NULL;
result = cfg_map_get(map, "responses-per-second", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
"responses-per-second %d > %d",
i, DNS_RRL_MAX_RATE);
}
rrl->responses_per_second = i;
rrl->scaled_responses_per_second = rrl->responses_per_second;
/*
* The default error rate is the response rate,
* and so off by default.
*/
i = rrl->responses_per_second;
obj = NULL;
result = cfg_map_get(map, "errors-per-second", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
"errors-per-second %d > %d",
i, DNS_RRL_MAX_RATE);
}
rrl->errors_per_second = i;
rrl->scaled_errors_per_second = rrl->errors_per_second;
/*
* The default NXDOMAIN rate is the response rate,
* and so off by default.
*/
i = rrl->responses_per_second;
obj = NULL;
result = cfg_map_get(map, "nxdomains-per-second", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
"nxdomains-per-second %d > %d",
i, DNS_RRL_MAX_RATE);
}
rrl->nxdomains_per_second = i;
rrl->scaled_nxdomains_per_second = rrl->nxdomains_per_second;
/*
* The all-per-second rate is off by default.
*/
i = 0;
obj = NULL;
result = cfg_map_get(map, "all-per-second", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE, "all-per-second %d > %d",
i, DNS_RRL_MAX_RATE);
}
rrl->all_per_second = i;
rrl->scaled_all_per_second = rrl->all_per_second;
i = 2;
obj = NULL;
result = cfg_map_get(map, "slip", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i <= DNS_RRL_MAX_SLIP,
"slip %d > %d", i, DNS_RRL_MAX_SLIP);
}
rrl->slip = i;
rrl->scaled_slip = rrl->slip;
CHECK_RRL_RATE(responses_per_second, 0, DNS_RRL_MAX_RATE,
"responses-per-second");
CHECK_RRL_RATE(referrals_per_second,
rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
"referrals-per-second");
CHECK_RRL_RATE(nodata_per_second,
rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
"nodata-per-second");
CHECK_RRL_RATE(nxdomains_per_second,
rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
"nxdomains-per-second");
CHECK_RRL_RATE(errors_per_second,
rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
"errors-per-second");
CHECK_RRL_RATE(all_per_second, 0, DNS_RRL_MAX_RATE,
"all-per-second");
CHECK_RRL_RATE(slip, 2, DNS_RRL_MAX_SLIP,
"slip");
i = 15;
obj = NULL;
result = cfg_map_get(map, "window", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= 1 && i <= DNS_RRL_MAX_WINDOW,
CHECK_RRL(i >= 1 && i <= DNS_RRL_MAX_WINDOW,
"window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW);
}
rrl->window = i;
......@@ -1964,18 +1933,18 @@ configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) {
result = cfg_map_get(map, "qps-scale", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= 1, "invalid 'qps-scale %d'%s", i, "");
CHECK_RRL(i >= 1, "invalid 'qps-scale %d'%s", i, "");
}
rrl->qps_scale = i;
rrl->qps = 1.0;
i = 24;
obj = NULL;
result = cfg_map_get(map, "IPv4-prefix-length", &obj);
result = cfg_map_get(map, "ipv4-prefix-length", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= 8 && i <= 32,
"invalid 'IPv4-prefix-length %d'%s", i, "");
CHECK_RRL(i >= 8 && i <= 32,
"invalid 'ipv4-prefix-length %d'%s", i, "");
}
rrl->ipv4_prefixlen = i;
if (i == 32)
......@@ -1985,11 +1954,11 @@ configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) {
i = 56;
obj = NULL;
result = cfg_map_get(map, "IPv6-prefix-length", &obj);
result = cfg_map_get(map, "ipv6-prefix-length", &obj);
if (result == ISC_R_SUCCESS) {
i = cfg_obj_asuint32(obj);
CHECK_RRL(obj, i >= 16 && i <= DNS_RRL_MAX_PREFIX,
"IPv6-prefix-length %d < 16 or > %d",
CHECK_RRL(i >= 16 && i <= DNS_RRL_MAX_PREFIX,
"ipv6-prefix-length %d < 16 or > %d",
i, DNS_RRL_MAX_PREFIX);
}
rrl->ipv6_prefixlen = i;
......@@ -2010,7 +1979,7 @@ configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) {
result = cfg_acl_fromconfig(obj, config, ns_g_lctx,
ns_g_aclconfctx, ns_g_mctx,
0, &rrl->exempt);
CHECK_RRL(obj, result == ISC_R_SUCCESS,
CHECK_RRL(result == ISC_R_SUCCESS,
"invalid %s%s", "address match list", "");
}
......
......@@ -17,5 +17,5 @@
# Clean up after rrl tests.
rm -f dig.out*
rm -f */named.memstats */named.run */named.stats */log */session.key
rm -f */named.memstats */named.run */named.stats */log-* */session.key
rm -f ns3/bl*.db */*.jnl */*.core */*.pid
......@@ -32,15 +32,14 @@ options {
rate-limit {
responses-per-second 2;
all-per-second 70;
IPv4-prefix-length 24;
IPv6-prefix-length 64;
slip 3;
/* qps-scale 2; */
exempt-clients { 10.53.0.7; };
window 1;
max-table-size 100;
min-table-size 2;
// small enough to force a table expansion
min-table-size 75;
};
additional-from-cache no;
};
key rndc_key {
......
......@@ -22,8 +22,10 @@ $TTL 120
NS .
ns A 10.53.0.2
; basic rate limiting
a1 A 192.0.2.1
; wildcards
*.a2 A 192.0.2.2
; a3 is in tld3
......@@ -38,5 +40,8 @@ a6 A 192.0.2.6
; a7 for SERVFAIL
; a8 for all-per-second limit
$GENERATE 101-180 all$.a8 A 192.0.2.8
; a8 for NODATA
a8 A 192.0.2.8
; a9 for all-per-second limit
$GENERATE 101-180 all$.a9 A 192.0.2.8
......@@ -27,6 +27,22 @@ options {
listen-on { 10.53.0.3; };
listen-on-v6 { none; };
notify no;
// check that all of the options are parsed without limiting anything
rate-limit {
responses-per-second 200;
referrals-per-second 220;
nodata-per-second 230;
nxdomains-per-second 240;
errors-per-second 250;
all-per-second 700;
ipv4-prefix-length 24;
ipv6-prefix-length 64;
qps-scale 10;
window 1;
max-table-size 1000;
};
};
zone "." { type hint; file "hints"; };
......
......@@ -64,15 +64,20 @@ sec_start () {
}
# turn off ${HOME}/.digrc
HOME=/dev/null; export HOME
# $1=result name $2=domain name $3=dig options
digcmd () {
OFILE=$1; shift
DIG_DOM=$1; shift
ARGS="+noadd +noauth +nosearch +time=1 +tries=1 +ignore $* -p 5300 $DIG_DOM @$ns2"
ARGS="+nosearch +time=1 +tries=1 +ignore -p 5300 $* $DIG_DOM @$ns2"
#echo I:dig $ARGS 1>&2
START=`date +%y%m%d%H%M.%S`
RESULT=`$DIG $ARGS 2>&1 | tee $OFILE=TEMP \
| sed -n -e 's/^[^;].* \([^ ]\{1,\}\)$/\1/p' \
| sed -n -e '/^;; AUTHORITY/,/^$/d' \
-e '/^;; ADDITIONAL/,/^$/d' \
-e 's/^[^;].* \([^ ]\{1,\}\)$/\1/p' \
-e 's/;; flags.* tc .*/TC/p' \
-e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p' \
-e 's/;; .* status: SERVFAIL.*/SERVFAIL/p' \
......@@ -117,7 +122,7 @@ ck_result() {
NXDOMAIN=`ls dig.out-$1-*=NXDOMAIN 2>/dev/null | wc -l | tr -d ' '`
SERVFAIL=`ls dig.out-$1-*=SERVFAIL 2>/dev/null | wc -l | tr -d ' '`
if test $ADDRS -ne "$3"; then
setret "I:$ADDRS instead of $3 $2 responses for $1"
setret "I:$ADDRS instead of $3 '$2' responses for $1"
BAD=yes
fi
if test $TC -ne "$4"; then
......@@ -142,26 +147,47 @@ ck_result() {
}
ckstats () {
LABEL="$1"; shift
TYPE="$1"; shift
EXPECTED="$1"; shift
CNT=`sed -n -e "s/[ ]*\([0-9]*\).responses $TYPE for rate limits.*/\1/p" \
ns2/named.stats | tail -1`
CNT=`expr 0$CNT + 0`
if test "$CNT" -ne $EXPECTED; then
setret "I:wrong $LABEL $TYPE statistics of $CNT instead of $EXPECTED"
fi
}
#########
sec_start
# Tests of referrals to "." must be done before the hints are loaded
# or with "additional-from-cache no"
burst 5 a1.tld3 +norec
# basic rate limiting
burst 3 a1.tld2
# 1 second delay allows an additional response.
sleep 1
burst 21 a1.tld2
# request 30 different qnames to try a wild card
# Request 30 different qnames to try a wildcard.
burst 30 'x$CNT.a2.tld2'
# These should be counted and limited but are not. See RT33138.
burst 10 'y.x$CNT.a2.tld2'
# IP TC drop NXDOMAIN SERVFAIL
# check for 24 results
# including the 1 second delay
# referrals to "."
ck_result a1.tld3 '' 2 1 2 0 0
# check 24 results including 1 second delay that allows an additional response
ck_result a1.tld2 192.0.2.1 3 7 14 0 0
# Check the wild card answers.
# The parent name of the 30 requests is counted.
ck_result 'x*.a2.tld2' 192.0.2.2 2 10 18 0 0
# These should be limited but are not. See RT33138.
ck_result 'y.x*.a2.tld2' 192.0.2.2 10 0 0 0 0
#########
sec_start
......@@ -178,6 +204,10 @@ ck_result 'y*.a3.tld3' 192.0.3.3 3 6 12 0 0
# NXDOMAIN responses are also limited based on the parent name.
ck_result 'z*.a4.tld2' x 0 6 12 2 0
$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
ckstats first dropped 58
ckstats first truncated 30
#########
sec_start
......@@ -185,6 +215,9 @@ sec_start
burst 20 a5.tld2 +tcp
burst 20 a6.tld2 -b $ns7
burst 20 a7.tld4
burst 2 a8.tld2 AAAA
burst 2 a8.tld2 TXT
burst 2 a8.tld2 SPF
# TCP responses are not rate limited
ck_result a5.tld2 192.0.2.5 20 0 0 0 0
......@@ -196,6 +229,13 @@ ck_result a6.tld2 192.0.2.6 20 0 0 0 0
# other rate limiting can be triggered before the SERVFAIL limit is reached.
ck_result a7.tld4 192.0.2.1 0 6 12 0 2
# NODATA responses are counted as the same regardless of qtype.
ck_result a8.tld2 '' 2 2 2 0 0
$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
ckstats second dropped 72
ckstats second truncated 38
#########
sec_start
......@@ -203,23 +243,14 @@ sec_start
# all-per-second
# The qnames are all unique but the client IP address is constant.
CNT=101
burst 80 'all$CNT.a8.tld2'
ck_result 'a*.a8.tld2' 192.0.2.8 70 0 10 0 0
burst 80 'all$CNT.a9.tld2'
ck_result 'a*.a9.tld2' 192.0.2.8 70 0 10 0 0
$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
ckstats () {
CNT=`sed -n -e "s/[ ]*\([0-9]*\).responses $1 for rate limits.*/\1/p" \
ns2/named.stats`
CNT=`expr 0$CNT + 0`
if test "$CNT" -ne $2; then
setret "I:wrong $1 statistics of $CNT instead of $2"
fi
}
ckstats dropped 77
ckstats truncated 35
ckstats final dropped 82
ckstats final truncated 38
echo "I:exit status: $ret"
# exit $ret
[ $ret -ne 0 ] && echo "I:test failure overridden"
exit 0
exit $ret
......@@ -5449,7 +5449,7 @@ badresp:1,adberr:0,findfail:0,valfail:0]
<optional> filter-aaaa-on-v4 ( <replaceable>yes_or_no</replaceable> | <replaceable>break-dnssec</replaceable> ); </optional>
<optional> filter-aaaa-on-v6 ( <replaceable>yes_or_no</replaceable> | <replaceable>break-dnssec</replaceable> ); </optional>
<optional> filter-aaaa { <replaceable>address_match_list</replaceable> }; </optional>
<optional> dns64 <replaceable>IPv6-prefix</replaceable> {
<optional> dns64 <replaceable>ipv6-prefix</replaceable> {
<optional> clients { <replaceable>address_match_list</replaceable> }; </optional>
<optional> mapped { <replaceable>address_match_list</replaceable> }; </optional>
<optional> exclude { <replaceable>address_match_list</replaceable> }; </optional>
......@@ -5487,14 +5487,16 @@ badresp:1,adberr:0,findfail:0,valfail:0]
<optional> deny-answer-aliases { <replaceable>namelist</replaceable> } <optional> except-from { <replaceable>namelist</replaceable> } </optional>;</optional>
<optional> rate-limit {
<optional> responses-per-second <replaceable>number</replaceable> ; </optional>
<optional> errors-per-second <replaceable>number</replaceable> ; </optional>
<optional> referrals-per-second <replaceable>number</replaceable> ; </optional>
<optional> nodata-per-second <replaceable>number</replaceable> ; </optional>
<optional> nxdomains-per-second <replaceable>number</replaceable> ; </optional>
<optional> errors-per-second <replaceable>number</replaceable> ; </optional>
<optional> all-per-second <replaceable>number</replaceable> ; </optional>
<optional> window <replaceable>number</replaceable> ; </optional>
<optional> log-only <replaceable>yes_or_no</replaceable> ; </optional>
<optional> qps-scale <replaceable>number</replaceable> ; </optional>
<optional> IPv4-prefix-length <replaceable>number</replaceable> ; </optional>
<optional> IPv6-prefix-length <replaceable>number</replaceable> ; </optional>
<optional> ipv4-prefix-length <replaceable>number</replaceable> ; </optional>
<optional> ipv6-prefix-length <replaceable>number</replaceable> ; </optional>
<optional> slip <replaceable>number</replaceable> ; </optional>
<optional> exempt-clients { <replaceable>address_match_list</replaceable> } ; </optional>
<optional> max-table-size <replaceable>number</replaceable> ; </optional>
......@@ -10089,94 +10091,101 @@ ns.domain.com.rpz-nsdname CNAME .
</sect3>
<sect3>
<title>Rate Limiting</title>
<title>Response Rate Limiting</title>
<para>
Excessive essentially identical UDP <emphasis>responses</emphasis>
can be discarded by configuring a
Excessive almost identical UDP <emphasis>responses</emphasis>
can be controlled by configuring a
<command>rate-limit</command> clause in an
<command>options</command> statement.
This mechanism keeps BIND 9 from being used
in amplifying reflection denial of service attacks
as well as partially protecting BIND 9 itself from
some denial of service attacks.
Very short truncated responses can be sent to provide
rate-limited responses to legitimate
clients within a range of attacked and forged IP addresses,
Legitimate clients react to truncated response by retrying
with TCP.
<command>options</command> or <command>view</command> statement.
This mechanism keeps authoritative BIND 9 from being used
in amplifying reflection denial of service (DoS) attacks.
Short truncated (TC=1) responses can be sent to provide
rate-limited responses to legitimate clients within
a range of forged, attacked IP addresses.
Legitimate clients react to dropped or truncated response
by retrying with UDP or with TCP respectively.
</para>
<para>
Rate limiting works by setting
<command>responses-per-second</command>
to a number of repetitions per second for responses for a given name
and record type to a DNS client.
This mechanism is intended for authoritative DNS servers.
It can be used on recursive servers but can slow
applications such as SMTP servers (mail receivers) and
HTTP clients (web browsers) that repeatedly request the
same domains.
When possible, closing "open" recursive servers is better.
</para>
<para>
<command>Responses-per-second</command> is a limit on
identical responses instead of a limit on all responses or
even all responses to a single client.
10 identical responses per second is a generous limit except perhaps
when many clients are using a single IP address via network
address translation (NAT).
The default limit of zero specifies an unbounded limit to turn off
rate-limiting in a view or to only rate-limit NXDOMAIN or other
errors.
Response rate limiting uses a "credit" or "token bucket" scheme.
Each combination of identical response and client
has a conceptual account that earns a specified number
of credits every second.
A prospective response debits its account by one.
Responses are dropped or truncated
while the account is negative.
Responses are tracked within a rolling window of time
which defaults to 15 seconds, but can be configured with
the <command>window</command> option to any value from
1 to 3600 seconds (1 hour).
The account cannot become more positive than
the per-second limit
or more negative than <command>window</command>
times the per-second limit.
When the specified number of credits for a class of
responses is set to 0, those responses are not rate limited.
</para>
<para>
The notion of "identical responses"
and "single DNS client" cannot be simplistic.
All responses to a CIDR block with prefix
length specified with <command>IPv4-prefix-length</command>
(default 24) or <command>IPv6-prefix-length</command>
(default 56) are assumed to come from a single DNS client.
Requests for a name that result in DNS NXDOMAIN
errors are considered identical.
This controls some attacks using random names, but
accommodates servers that expect many legitimate NXDOMAIN responses
such as anti-spam blacklists.
By default the limit on NXDOMAIN errors is the same as the
<command>responses-per-second</command> value,
but it can be set separately with
<command>nxdomains-per-second</command>.
All requests for all names or types that result in DNS errors
such as SERVFAIL and FORMERR (but not NXDOMAIN) are considered
identical.
This controls attacks using invalid requests or distant,
broken authoritative servers.
By default the limit on errors is the same as the
<command>responses-per-second</command> value,
but it can be set separately with
<command>errors-per-second</command>.
The notions of "identical response" and "DNS client"
for rate limiting are not simplistic.
All responses to an address block are counted as if to a
single client.
The prefix lengths of addresses blocks are
specified with <command>ipv4-prefix-length</command> (default 24)
and <command>ipv6-prefix-length</command> (default 56).
</para>
<para>
Rate limiting uses a "credit" or "token bucket" scheme.
Each identical response has a conceptual account
that is given <command>responses-per-second</command>,
<command>errors-per-second</command>, and
<command>nxdomains-per-second</command> credits every second.
A DNS request triggering some desired response debits
the account by one.
Responses are not sent while the account is negative.
The account cannot become more positive than
the per-second limit
or more negative than <command>window</command>
times the per-second limit.
A DNS client that sends requests that are not
answered can be penalized for up to <command>window</command>
seconds (default 15).
All non-empty responses for a valid domain name (qname)
and record type (qtype) are identical and have a limit specified
with <command>responses-per-second</command>
(default 0 or no limit).