From: Evan Hunt Date: Thu, 25 Apr 2013 21:40:32 +0000 (-0700) Subject: [master] fixed several RRL issues X-Git-Tag: v9.10.0a1~404 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=a6d43d18b1f6164fd144b2fa25ea57f5566b3bf9;p=thirdparty%2Fbind9.git [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] --- diff --git a/CHANGES b/CHANGES index 515e37e03f4..605183c34ff 100644 --- a/CHANGES +++ b/CHANGES @@ -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'. diff --git a/bin/named/client.c b/bin/named/client.c index 67403d07a48..1276dcbdc0e 100644 --- a/bin/named/client.c +++ b/bin/named/client.c @@ -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; } diff --git a/bin/named/query.c b/bin/named/query.c index b9d4891f9d0..c6c58581dc7 100644 --- a/bin/named/query.c +++ b/bin/named/query.c @@ -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; } diff --git a/bin/named/server.c b/bin/named/server.c index fbf7db05556..f22d09ee040 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -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", ""); } diff --git a/bin/tests/system/rrl/clean.sh b/bin/tests/system/rrl/clean.sh index 7b9a2ec0591..e42c43868e3 100644 --- a/bin/tests/system/rrl/clean.sh +++ b/bin/tests/system/rrl/clean.sh @@ -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 diff --git a/bin/tests/system/rrl/ns2/named.conf b/bin/tests/system/rrl/ns2/named.conf index 9f2bb865db0..7c00577703b 100644 --- a/bin/tests/system/rrl/ns2/named.conf +++ b/bin/tests/system/rrl/ns2/named.conf @@ -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 { diff --git a/bin/tests/system/rrl/ns2/tld2.db b/bin/tests/system/rrl/ns2/tld2.db index 8b965814ad2..0d9c8d07a86 100644 --- a/bin/tests/system/rrl/ns2/tld2.db +++ b/bin/tests/system/rrl/ns2/tld2.db @@ -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 diff --git a/bin/tests/system/rrl/ns3/named.conf b/bin/tests/system/rrl/ns3/named.conf index fb3622ac00f..75d5a808c8c 100644 --- a/bin/tests/system/rrl/ns3/named.conf +++ b/bin/tests/system/rrl/ns3/named.conf @@ -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"; }; diff --git a/bin/tests/system/rrl/tests.sh b/bin/tests/system/rrl/tests.sh index 5797889529b..c5a050b450b 100644 --- a/bin/tests/system/rrl/tests.sh +++ b/bin/tests/system/rrl/tests.sh @@ -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 diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index dcfcfb22b74..c89a27c2c3c 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -5449,7 +5449,7 @@ badresp:1,adberr:0,findfail:0,valfail:0] filter-aaaa-on-v4 ( yes_or_no | break-dnssec ); filter-aaaa-on-v6 ( yes_or_no | break-dnssec ); filter-aaaa { address_match_list }; - dns64 IPv6-prefix { + dns64 ipv6-prefix { clients { address_match_list }; mapped { address_match_list }; exclude { address_match_list }; @@ -5487,14 +5487,16 @@ badresp:1,adberr:0,findfail:0,valfail:0] deny-answer-aliases { namelist } except-from { namelist } ; rate-limit { responses-per-second number ; - errors-per-second number ; + referrals-per-second number ; + nodata-per-second number ; nxdomains-per-second number ; + errors-per-second number ; all-per-second number ; window number ; log-only yes_or_no ; qps-scale number ; - IPv4-prefix-length number ; - IPv6-prefix-length number ; + ipv4-prefix-length number ; + ipv6-prefix-length number ; slip number ; exempt-clients { address_match_list } ; max-table-size number ; @@ -10089,94 +10091,101 @@ ns.domain.com.rpz-nsdname CNAME . - Rate Limiting + Response Rate Limiting - Excessive essentially identical UDP responses - can be discarded by configuring a + Excessive almost identical UDP responses + can be controlled by configuring a rate-limit clause in an - options 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. + options or view 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. - Rate limiting works by setting - responses-per-second - 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. - Responses-per-second 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 window 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 window + 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. - The notion of "identical responses" - and "single DNS client" cannot be simplistic. - All responses to a CIDR block with prefix - length specified with IPv4-prefix-length - (default 24) or IPv6-prefix-length - (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 - responses-per-second value, - but it can be set separately with - nxdomains-per-second. - 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 - responses-per-second value, - but it can be set separately with - errors-per-second. + 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 ipv4-prefix-length (default 24) + and ipv6-prefix-length (default 56). - Rate limiting uses a "credit" or "token bucket" scheme. - Each identical response has a conceptual account - that is given responses-per-second, - errors-per-second, and - nxdomains-per-second 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 window - times the per-second limit. - A DNS client that sends requests that are not - answered can be penalized for up to window - 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 responses-per-second + (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 + nodata-per-second + (default responses-per-second). + 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 nxdomain-per-second + (default responses-per-second). + 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 + referrals-per-second + (default responses-per-second). 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. + + + + 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 + responses-per-second value, + but it can be set separately with + errors-per-second. @@ -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 slip 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. - Slip 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. + slip 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 slip rate. @@ -10225,8 +10235,8 @@ ns.domain.com.rpz-nsdname CNAME . rate-limit statements in view statements instead of the global option statement. - A rate-limit statement in a view replaces - instead of being merged with a rate-limit + A rate-limit statement in a view replaces, + rather than supplementing, a rate-limit statement among the main options. DNS clients within a view can be exempted from rate limits with the exempt-clients clause. diff --git a/lib/dns/include/dns/rrl.h b/lib/dns/include/dns/rrl.h index 4cabfa84c95..ef6b72b4280 100644 --- a/lib/dns/include/dns/rrl.h +++ b/lib/dns/include/dns/rrl.h @@ -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; diff --git a/lib/dns/rrl.c b/lib/dns/rrl.c index 063e0474720..3a6abfa4621 100644 --- a/lib/dns/rrl.c +++ b/lib/dns/rrl.c @@ -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. diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 1df333b3845..c65727f341d 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -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 },