+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'.
}
/*
* 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;
}
/* 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) {
* 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;
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;
}
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, \
} \
} 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;
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;
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)
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;
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", "");
}
# 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
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 {
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
; 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
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"; };
}
+# 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' \
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
}
+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
# 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
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
# 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
# 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
<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>
<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>
</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).
+ 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
+ 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 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.
+ 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
+ <command>responses-per-second</command> value,
+ but it can be set separately with
+ <command>errors-per-second</command>.
</para>
<para>
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>
<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.
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,
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.
*/
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;
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;
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,
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) {
}
}
-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);
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) {
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);
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;
}
}
/*
* 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) {
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) {
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 &&
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);
* 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) {
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.
*/
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 },