diff --git a/CHANGES b/CHANGES index 515e37e03f..605183c34f 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 67403d07a4..1276dcbdc0 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 b9d4891f9d..c6c58581dc 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 fbf7db0555..f22d09ee04 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; + 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", ""); } diff --git a/bin/tests/system/rrl/clean.sh b/bin/tests/system/rrl/clean.sh index 7b9a2ec059..e42c43868e 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 9f2bb865db..7c00577703 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 8b965814ad..0d9c8d07a8 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 fb3622ac00..75d5a808c8 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 5797889529..c5a050b450 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 dcfcfb22b7..c89a27c2c3 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,61 +10091,95 @@ 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. + 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). + + + + 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 - 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. + 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 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 @@ -10152,33 +10188,6 @@ ns.domain.com.rpz-nsdname CNAME . errors-per-second. - - 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). - - - - 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. - - 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 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 4cabfa84c9..ef6b72b428 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 063e047472..3a6abfa462 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 1df333b384..c65727f341 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 },