[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]
This commit is contained in:
Evan Hunt 2013-04-25 14:40:32 -07:00
parent 330f98fe3b
commit a6d43d18b1
13 changed files with 365 additions and 323 deletions

View file

@ -1,3 +1,7 @@
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'.

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
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");
/*
* 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;
CHECK_RRL_RATE(all_per_second, 0, DNS_RRL_MAX_RATE,
"all-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(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", "");
}

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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"; };

View file

@ -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

View file

@ -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,61 +10091,95 @@ 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.
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>
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).
All empty (NODATA) responses for a valid domain,
regardless of query type, are identical.
Responses in the NODATA class are limited by
<command>nodata-per-second</command>
(default <command>responses-per-second</command>).
Requests for any and all undefined subdomains of a given
valid domain result in NXDOMAIN errors, and are identical
regardless of query type.
They are limited by <command>nxdomain-per-second</command>
(default <command>responses-per-second</command>).
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.
can be relaxed or turned off (set to 0)
on servers that expect many legitimate
NXDOMAIN responses, such as from anti-spam blacklists.
Referrals or delegations to the server of a given
domain are identical and are limited by
<command>referrals-per-second</command>
(default <command>responses-per-second</command>).
</para>
<para>
Responses generated from local wildcards are counted and limited
as if they were for the parent domain name.
This controls flooding using random.wild.example.com.
</para>
<para>
All requests that result in DNS errors other
than NXDOMAIN, such as SERVFAIL and FORMERR, are identical
regardless of requested name (qname) or record type (qtype).
This controls attacks using invalid requests or distant,
broken authoritative servers.
By default the limit on errors is the same as the
@ -10152,33 +10188,6 @@ ns.domain.com.rpz-nsdname CNAME .
<command>errors-per-second</command>.
</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).
</para>
<para>
Responses generated from local wildcards are counted and limited
as if they were for the parent domain name.
This prevents flooding by requesting random.wild.example.com.
For similar reasons, NXDOMAIN responses are counted and rate
limited by the valid domain name nearest to the
query name with an SOA record.
</para>
<para>
Many attacks using DNS involve UDP requests with forged source
addresses.
@ -10188,14 +10197,15 @@ ns.domain.com.rpz-nsdname CNAME .
There is a mechanism that can answer some legitimate
requests from a client whose address is being forged in a flood.
Setting <command>slip</command> to 2 (its default) causes every
other UDP request to be answered with a small response
claiming that the response would have been truncated.
The small size and relative infrequency of the response make
it unattractive for abuse.
<command>Slip</command> must be between 0 and 10.
A value of 0 does not "slip"
or sends no rate limiting truncated responses.
Some error responses includinge REFUSED and SERVFAIL
other UDP request to be answered with a small truncated (TC=1)
response.
The small size and reduced frequency, and so lack of
amplification, of "slipped" responses make them unattractive
for reflection DoS attacks.
<command>slip</command> must be between 0 and 10.
A value of 0 does not "slip";
no truncated responses are sent due to rate limiting.
Some error responses including REFUSED and SERVFAIL
cannot be replaced with truncated responses and are instead
leaked at the <command>slip</command> rate.
</para>
@ -10225,8 +10235,8 @@ ns.domain.com.rpz-nsdname CNAME .
<command>rate-limit</command> statements in <command>view</command>
statements instead of the global <command>option</command>
statement.
A <command>rate-limit</command> statement in a view replaces
instead of being merged with a <command>rate-limit</command>
A <command>rate-limit</command> statement in a view replaces,
rather than supplementing, a <command>rate-limit</command>
statement among the main options.
DNS clients within a view can be exempted from rate limits
with the <command>exempt-clients</command> clause.

View file

@ -71,7 +71,8 @@ typedef struct dns_rrl_hash dns_rrl_hash_t;
typedef enum {
DNS_RRL_RTYPE_FREE = 0,
DNS_RRL_RTYPE_QUERY,
DNS_RRL_RTYPE_DELEGATION,
DNS_RRL_RTYPE_REFERRAL,
DNS_RRL_RTYPE_NODATA,
DNS_RRL_RTYPE_NXDOMAIN,
DNS_RRL_RTYPE_ERROR,
DNS_RRL_RTYPE_ALL,
@ -190,6 +191,13 @@ struct dns_rrl_qname_buf {
dns_fixedname_t qname;
};
typedef struct dns_rrl_rate dns_rrl_rate_t;
struct dns_rrl_rate {
int r;
int scaled;
const char *str;
};
/*
* Per-view query rate limit parameters and a pointer to database.
*/
@ -199,12 +207,14 @@ struct dns_rrl {
isc_mem_t *mctx;
isc_boolean_t log_only;
int responses_per_second;
int errors_per_second;
int nxdomains_per_second;
int all_per_second;
dns_rrl_rate_t responses_per_second;
dns_rrl_rate_t referrals_per_second;
dns_rrl_rate_t nodata_per_second;
dns_rrl_rate_t nxdomains_per_second;
dns_rrl_rate_t errors_per_second;
dns_rrl_rate_t all_per_second;
dns_rrl_rate_t slip;
int window;
int slip;
double qps_scale;
int max_entries;
@ -215,11 +225,6 @@ struct dns_rrl {
int qps_responses;
isc_stdtime_t qps_time;
double qps;
int scaled_responses_per_second;
int scaled_errors_per_second;
int scaled_nxdomains_per_second;
int scaled_all_per_second;
int scaled_slip;
unsigned int probes;
unsigned int searches;

View file

@ -167,11 +167,11 @@ set_age(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_stdtime_t now) {
if (ts >= DNS_RRL_MAX_TS) {
ts_gen = (ts_gen + 1) % DNS_RRL_TS_BASES;
for (e_old = ISC_LIST_TAIL(rrl->lru), i = 0;
e_old != NULL && e_old->ts_gen == ts_gen;
e_old != NULL && (e_old->ts_gen == ts_gen ||
!ISC_LINK_LINKED(e_old, hlink));
e_old = ISC_LIST_PREV(e_old, lru), ++i)
{
if (e_old->ts_valid)
e_old->ts_valid = ISC_FALSE;
e_old->ts_valid = ISC_FALSE;
}
if (i != 0)
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
@ -403,9 +403,16 @@ make_key(const dns_rrl_t *rrl, dns_rrl_key_t *key,
memset(key, 0, sizeof(*key));
key->s.rtype = rtype;
if (rtype == DNS_RRL_RTYPE_QUERY || rtype == DNS_RRL_RTYPE_DELEGATION) {
key->s.qclass = qclass & 0xff;
if (rtype == DNS_RRL_RTYPE_QUERY) {
key->s.qtype = qtype;
key->s.qclass = qclass & 0xff;
} else if (rtype == DNS_RRL_RTYPE_REFERRAL ||
rtype == DNS_RRL_RTYPE_NODATA) {
/*
* Because there is no qtype in the empty answer sections of
* referral and NODATA responses, count them as the same.
*/
key->s.qclass = qclass & 0xff;
}
if (qname != NULL && qname->labels != 0) {
@ -440,33 +447,40 @@ make_key(const dns_rrl_t *rrl, dns_rrl_key_t *key,
}
}
static inline int
response_balance(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) {
int balance, rate = 0;
balance = e->responses;
if (balance < 0)
switch (e->key.s.rtype) {
case DNS_RRL_RTYPE_QUERY:
case DNS_RRL_RTYPE_DELEGATION:
rate = rrl->scaled_responses_per_second;
break;
case DNS_RRL_RTYPE_NXDOMAIN:
rate = rrl->scaled_nxdomains_per_second;
break;
case DNS_RRL_RTYPE_ERROR:
rate = rrl->scaled_errors_per_second;
break;
case DNS_RRL_RTYPE_ALL:
rate = rrl->scaled_all_per_second;
break;
case DNS_RRL_RTYPE_TCP:
rate = 1;
break;
default:
INSIST(0);
static inline dns_rrl_rate_t *
get_rate(dns_rrl_t *rrl, dns_rrl_rtype_t rtype) {
switch (rtype) {
case DNS_RRL_RTYPE_QUERY:
return (&rrl->responses_per_second);
case DNS_RRL_RTYPE_REFERRAL:
return (&rrl->referrals_per_second);
case DNS_RRL_RTYPE_NODATA:
return (&rrl->nodata_per_second);
case DNS_RRL_RTYPE_NXDOMAIN:
return (&rrl->nxdomains_per_second);
case DNS_RRL_RTYPE_ERROR:
return (&rrl->errors_per_second);
case DNS_RRL_RTYPE_ALL:
return (&rrl->all_per_second);
default:
INSIST(0);
}
balance += age * rate;
return (NULL);
}
static int
response_balance(dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) {
dns_rrl_rate_t *ratep;
int balance, rate;
if (e->key.s.rtype == DNS_RRL_RTYPE_TCP) {
rate = 1;
} else {
ratep = get_rate(rrl, e->key.s.rtype);
rate = ratep->scaled;
}
balance = e->responses + age * rate;
if (balance > rate)
balance = rate;
return (balance);
@ -551,7 +565,7 @@ get_entry(dns_rrl_t *rrl, const isc_sockaddr_t *client_addr,
e = NULL;
break;
}
if (!e->logged && response_balance(rrl, e, age) >= 0)
if (!e->logged && response_balance(rrl, e, age) > 0)
break;
}
if (e == NULL) {
@ -598,35 +612,16 @@ debit_rrl_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, double qps, double scale,
const isc_sockaddr_t *client_addr, isc_stdtime_t now,
char *log_buf, unsigned int log_buf_len)
{
int rate, new_rate, *ratep, slip, new_slip, age, log_secs, min;
const char *rate_str = NULL;
int rate, new_rate, slip, new_slip, age, log_secs, min;
dns_rrl_rate_t *ratep;
dns_rrl_entry_t const *credit_e;
/*
* Pick the rate counter.
* Optionally adjust the rate by the estimated query/second rate.
*/
switch (e->key.s.rtype) {
case DNS_RRL_RTYPE_QUERY:
case DNS_RRL_RTYPE_DELEGATION:
rate = rrl->responses_per_second;
ratep = &rrl->scaled_responses_per_second;
break;
case DNS_RRL_RTYPE_NXDOMAIN:
rate = rrl->nxdomains_per_second;
ratep = &rrl->scaled_nxdomains_per_second;
break;
case DNS_RRL_RTYPE_ERROR:
rate = rrl->errors_per_second;
ratep = &rrl->scaled_errors_per_second;
break;
case DNS_RRL_RTYPE_ALL:
rate = rrl->all_per_second;
ratep = &rrl->scaled_all_per_second;
break;
default:
INSIST(0);
}
ratep = get_rate(rrl, e->key.s.rtype);
rate = ratep->r;
if (rate == 0)
return (DNS_RRL_RESULT_OK);
@ -648,36 +643,16 @@ debit_rrl_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, double qps, double scale,
new_rate = (int) (rate * scale);
if (new_rate < 1)
new_rate = 1;
if (*ratep != new_rate) {
if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) {
switch (e->key.s.rtype) {
case DNS_RRL_RTYPE_QUERY:
case DNS_RRL_RTYPE_DELEGATION:
rate_str = "responses-per-second";
break;
case DNS_RRL_RTYPE_NXDOMAIN:
rate_str = "nxdomains-per-second";
break;
case DNS_RRL_RTYPE_ERROR:
rate_str = "errors-per-second";
break;
case DNS_RRL_RTYPE_ALL:
rate_str = "all-per-second";
break;
case DNS_RRL_RTYPE_FREE:
case DNS_RRL_RTYPE_TCP:
INSIST(0);
}
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
DNS_LOGMODULE_REQUEST,
DNS_RRL_LOG_DEBUG1,
"%d qps scaled %s by %.2f"
" from %d to %d",
(int)qps, rate_str, scale,
rate, new_rate);
}
if (ratep->scaled != new_rate) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
DNS_LOGMODULE_REQUEST,
DNS_RRL_LOG_DEBUG1,
"%d qps scaled %s by %.2f"
" from %d to %d",
(int)qps, ratep->str, scale,
rate, new_rate);
rate = new_rate;
*ratep = rate;
ratep->scaled = rate;
}
}
@ -736,22 +711,21 @@ debit_rrl_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, double qps, double scale,
/*
* Drop this response unless it should slip or leak.
*/
slip = rrl->slip;
slip = rrl->slip.r;
if (slip > 2 && scale < 1.0) {
new_slip = (int) (slip * scale);
if (new_slip < 2)
new_slip = 2;
if (rrl->scaled_slip != new_slip) {
if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1))
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
DNS_LOGMODULE_REQUEST,
DNS_RRL_LOG_DEBUG1,
"%d qps scaled slip"
" by %.2f from %d to %d",
(int)qps, scale,
slip, new_slip);
if (rrl->slip.scaled != new_slip) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
DNS_LOGMODULE_REQUEST,
DNS_RRL_LOG_DEBUG1,
"%d qps scaled slip"
" by %.2f from %d to %d",
(int)qps, scale,
slip, new_slip);
slip = new_slip;
rrl->scaled_slip = slip;
rrl->slip.scaled = slip;
}
}
if (slip != 0 && e->key.s.rtype != DNS_RRL_RTYPE_ALL) {
@ -853,34 +827,36 @@ make_log_buf(dns_rrl_t *rrl, dns_rrl_entry_t *e,
switch (e->key.s.rtype) {
case DNS_RRL_RTYPE_QUERY:
ADD_LOG_CSTR(&lb, "response");
break;
case DNS_RRL_RTYPE_DELEGATION:
ADD_LOG_CSTR(&lb, "referral");
case DNS_RRL_RTYPE_REFERRAL:
ADD_LOG_CSTR(&lb, "referral ");
break;
case DNS_RRL_RTYPE_NODATA:
ADD_LOG_CSTR(&lb, "NODATA ");
break;
case DNS_RRL_RTYPE_NXDOMAIN:
ADD_LOG_CSTR(&lb, "NXDOMAIN response");
ADD_LOG_CSTR(&lb, "NXDOMAIN ");
break;
case DNS_RRL_RTYPE_ERROR:
if (resp_result == ISC_R_SUCCESS) {
ADD_LOG_CSTR(&lb, "error response");
ADD_LOG_CSTR(&lb, "error ");
} else {
rstr = isc_result_totext(resp_result);
add_log_str(&lb, rstr, strlen(rstr));
ADD_LOG_CSTR(&lb, " response");
ADD_LOG_CSTR(&lb, " error ");
}
break;
case DNS_RRL_RTYPE_ALL:
ADD_LOG_CSTR(&lb, "all response");
ADD_LOG_CSTR(&lb, "all ");
break;
default:
INSIST(0);
}
if (plural)
ADD_LOG_CSTR(&lb, "s to ");
ADD_LOG_CSTR(&lb, "responses to ");
else
ADD_LOG_CSTR(&lb, " to ");
ADD_LOG_CSTR(&lb, "response to ");
memset(&cidr, 0, sizeof(cidr));
if (e->key.s.ipv6) {
@ -899,7 +875,8 @@ make_log_buf(dns_rrl_t *rrl, dns_rrl_entry_t *e,
add_log_str(&lb, strbuf, strlen(strbuf));
if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY ||
e->key.s.rtype == DNS_RRL_RTYPE_DELEGATION ||
e->key.s.rtype == DNS_RRL_RTYPE_REFERRAL ||
e->key.s.rtype == DNS_RRL_RTYPE_NODATA ||
e->key.s.rtype == DNS_RRL_RTYPE_NXDOMAIN) {
qbuf = get_qname(rrl, e);
if (save_qname && qbuf == NULL &&
@ -947,8 +924,10 @@ make_log_buf(dns_rrl_t *rrl, dns_rrl_entry_t *e,
if (e->key.s.rtype != DNS_RRL_RTYPE_NXDOMAIN) {
ADD_LOG_CSTR(&lb, " ");
(void)dns_rdataclass_totext(e->key.s.qclass, &lb);
ADD_LOG_CSTR(&lb, " ");
(void)dns_rdatatype_totext(e->key.s.qtype, &lb);
if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY) {
ADD_LOG_CSTR(&lb, " ");
(void)dns_rdatatype_totext(e->key.s.qtype, &lb);
}
}
snprintf(strbuf, sizeof(strbuf), " (%08x)",
e->key.s.qname_hash);
@ -1117,14 +1096,23 @@ dns_rrl(dns_view_t *view,
* Find the right kind of entry, creating it if necessary.
* If that is impossible, then nothing more can be done
*/
if (resp_result == ISC_R_SUCCESS)
switch (resp_result) {
case ISC_R_SUCCESS:
rtype = DNS_RRL_RTYPE_QUERY;
else if (resp_result == DNS_R_DELEGATION)
rtype = DNS_RRL_RTYPE_DELEGATION;
else if (resp_result == DNS_R_NXDOMAIN)
break;
case DNS_R_DELEGATION:
rtype = DNS_RRL_RTYPE_REFERRAL;
break;
case DNS_R_NXRRSET:
rtype = DNS_RRL_RTYPE_NODATA;
break;
case DNS_R_NXDOMAIN:
rtype = DNS_RRL_RTYPE_NXDOMAIN;
else
break;
default:
rtype = DNS_RRL_RTYPE_ERROR;
break;
}
e = get_entry(rrl, client_addr, qclass, qtype, qname, rtype,
now, ISC_TRUE, log_buf, log_buf_len);
if (e == NULL) {
@ -1148,7 +1136,7 @@ dns_rrl(dns_view_t *view,
rrl_result = debit_rrl_entry(rrl, e, qps, scale, client_addr, now,
log_buf, log_buf_len);
if (rrl->all_per_second != 0) {
if (rrl->all_per_second.r != 0) {
/*
* We must debit the all-per-second token bucket if we have
* an all-per-second limit for the IP address.

View file

@ -1324,16 +1324,17 @@ static cfg_type_t cfg_type_rpz = {
*/
static cfg_clausedef_t rrl_clauses[] = {
{ "responses-per-second", &cfg_type_uint32, 0 },
{ "errors-per-second", &cfg_type_uint32, 0 },
{ "referrals-per-second", &cfg_type_uint32, 0 },
{ "nodata-per-second", &cfg_type_uint32, 0 },
{ "nxdomains-per-second", &cfg_type_uint32, 0 },
{ "responses-per-second", &cfg_type_uint32, 0 },
{ "errors-per-second", &cfg_type_uint32, 0 },
{ "all-per-second", &cfg_type_uint32, 0 },
{ "slip", &cfg_type_uint32, 0 },
{ "window", &cfg_type_uint32, 0 },
{ "log-only", &cfg_type_boolean, 0 },
{ "qps-scale", &cfg_type_uint32, 0 },
{ "IPv4-prefix-length", &cfg_type_uint32, 0 },
{ "IPv6-prefix-length", &cfg_type_uint32, 0 },
{ "ipv4-prefix-length", &cfg_type_uint32, 0 },
{ "ipv6-prefix-length", &cfg_type_uint32, 0 },
{ "exempt-clients", &cfg_type_bracketed_aml, 0 },
{ "max-table-size", &cfg_type_uint32, 0 },
{ "min-table-size", &cfg_type_uint32, 0 },