diff --git a/bind.spec b/bind.spec index fb805cb..e67ba44 100644 --- a/bind.spec +++ b/bind.spec @@ -22,7 +22,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv Name: bind License: ISC Version: 9.9.2 -Release: 4.%{PATCHVER}%{?dist} +Release: 5.%{PATCHVER}%{?dist} Epoch: 32 Url: http://www.isc.org/products/BIND/ Buildroot:%{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -72,6 +72,7 @@ Patch125:bind99-buildfix.patch Patch127:bind99-forward.patch Patch130:bind-9.9.1-P2-multlib-conflict.patch Patch132:bind99-stat.patch +Patch133:rl-9.9.2-P1.patch # SDB patches Patch11: bind-9.3.2b2-sdbsrc.patch @@ -267,6 +268,7 @@ popd %patch125 -p1 -b .buildfix %patch127 -p1 -b .forward %patch130 -p1 -b .multlib-conflict +%patch133 -p0 -b .rl %if %{SDB} %patch101 -p1 -b .old-api @@ -771,6 +773,9 @@ rm -rf ${RPM_BUILD_ROOT} %endif %changelog +* Mon Feb 18 2013 Adam Tkac 32:9.9.2-5.P1 +- include rate limiting patch + * Tue Jan 29 2013 Tomas Hozza 32:9.9.2-4.P1 - Corrected IP addresses in named.ca (#901741) - mount/umount /var/named in setup-named-chroot.sh as the last one (#904666) diff --git a/rl-9.9.2-P1.patch b/rl-9.9.2-P1.patch new file mode 100644 index 0000000..a41aef5 --- /dev/null +++ b/rl-9.9.2-P1.patch @@ -0,0 +1,3252 @@ +diff -r -u bin/named/client.c-orig bin/named/client.c +--- bin/named/client.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/named/client.c 2004-01-01 00:00:00.000000000 +0000 +@@ -994,6 +994,11 @@ + } + if (result != ISC_R_SUCCESS) + goto done; ++ /* ++ * Stop after the question if TC was set for rate limiting. ++ */ ++ if ((client->message->flags & DNS_MESSAGEFLAG_TC) != 0) ++ goto renderend; + result = dns_message_rendersection(client->message, + DNS_SECTION_ANSWER, + DNS_MESSAGERENDER_PARTIAL | +@@ -1134,6 +1139,49 @@ + #endif + + /* ++ * Try to rate limit error responses. ++ */ ++ if (client->view != NULL && client->view->rrl != NULL) { ++ isc_boolean_t wouldlog; ++ char log_buf[DNS_RRL_LOG_BUF_LEN]; ++ dns_rrl_result_t rrl_result; ++ ++ INSIST(rcode != dns_rcode_noerror && ++ rcode != dns_rcode_nxdomain); ++ wouldlog = (ns_g_server->log_queries && ++ isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP)); ++ rrl_result = dns_rrl(client->view, &client->peeraddr, ++ TCP_CLIENT(client), ++ dns_rdataclass_in, dns_rdatatype_none, ++ NULL, result, client->now, ++ wouldlog, log_buf, sizeof(log_buf)); ++ if (rrl_result != DNS_RRL_RESULT_OK) { ++ /* ++ * Log dropped errors in the query category ++ * so that they are not lost in silence. ++ * Starts of rate-limited bursts are logged in ++ * NS_LOGCATEGORY_RRL. ++ */ ++ if (wouldlog) { ++ ns_client_log(client, NS_LOGCATEGORY_QUERIES, ++ NS_LOGMODULE_CLIENT, ++ DNS_RRL_LOG_DROP, ++ "%s", log_buf); ++ } ++ /* ++ * Some error responses cannot be 'slipped', ++ * so don't try. ++ * This will counted with dropped queries in the ++ * QryDropped counter. ++ */ ++ if (!client->view->rrl->log_only) { ++ ns_client_next(client, DNS_R_DROP); ++ return; ++ } ++ } ++ } ++ ++ /* + * Message may be an in-progress reply that we had trouble + * with, in which case QR will be set. We need to clear QR before + * calling dns_message_reply() to avoid triggering an assertion. +diff -r -u bin/named/config.c-orig bin/named/config.c +--- bin/named/config.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/named/config.c 2004-01-01 00:00:00.000000000 +0000 +@@ -227,6 +227,13 @@ + notify no;\n\ + allow-new-zones no;\n\ + \n\ ++ # Prevent use of this zone in DNS amplified reflection DoS attacks\n\ ++ rate-limit {\n\ ++ responses-per-second 3;\n\ ++ slip 0;\n\ ++ min-table-size 10;\n\ ++ };\n\ ++\n\ + zone \"version.bind\" chaos {\n\ + type master;\n\ + database \"_builtin version\";\n\ +diff -r -u bin/named/include/named/query.h-orig bin/named/include/named/query.h +--- bin/named/include/named/query.h-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/named/include/named/query.h 2004-01-01 00:00:00.000000000 +0000 +@@ -85,6 +85,7 @@ + #define NS_QUERYATTR_CACHEACLOK 0x2000 + #define NS_QUERYATTR_DNS64 0x4000 + #define NS_QUERYATTR_DNS64EXCLUDE 0x8000 ++#define NS_QUERYATTR_RRL_CHECKED 0x10000 + + + isc_result_t +diff -r -u bin/named/include/named/server.h-orig bin/named/include/named/server.h +--- bin/named/include/named/server.h-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/named/include/named/server.h 2004-01-01 00:00:00.000000000 +0000 +@@ -165,7 +165,10 @@ + dns_nsstatscounter_updatefail = 34, + dns_nsstatscounter_updatebadprereq = 35, + +- dns_nsstatscounter_max = 36 ++ dns_nsstatscounter_ratedropped = 36, ++ dns_nsstatscounter_rateslipped = 37, ++ ++ dns_nsstatscounter_max = 38 + }; + + void +diff -r -u bin/named/query.c-orig bin/named/query.c +--- bin/named/query.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/named/query.c 2004-01-01 00:00:00.000000000 +0000 +@@ -5748,6 +5748,105 @@ + resume: + CTRACE("query_find: resume"); + ++ /* ++ * Rate limit these responses to this client. ++ */ ++ if (client->view->rrl != NULL && ++ fname != NULL && dns_name_isabsolute(fname) && ++ (client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0) { ++ dns_rdataset_t nc_rdataset; ++ isc_boolean_t wouldlog; ++ char log_buf[DNS_RRL_LOG_BUF_LEN]; ++ isc_result_t nc_result; ++ dns_rrl_result_t rrl_result; ++ ++ client->query.attributes |= NS_QUERYATTR_RRL_CHECKED; ++ ++ wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP); ++ tname = fname; ++ if (result == DNS_R_NXDOMAIN) { ++ /* ++ * Use the database origin name to rate limit NXDOMAIN ++ */ ++ if (db != NULL) ++ tname = dns_db_origin(db); ++ rrl_result = result; ++ } else if (result == DNS_R_NCACHENXDOMAIN && ++ rdataset != NULL && ++ dns_rdataset_isassociated(rdataset) && ++ (rdataset->attributes & ++ DNS_RDATASETATTR_NEGATIVE) != 0) { ++ /* ++ * Try to use owner name in the negative cache SOA. ++ */ ++ dns_fixedname_init(&fixed); ++ dns_rdataset_init(&nc_rdataset); ++ for (nc_result = dns_rdataset_first(rdataset); ++ nc_result == ISC_R_SUCCESS; ++ nc_result = dns_rdataset_next(rdataset)) { ++ dns_ncache_current(rdataset, ++ dns_fixedname_name(&fixed), ++ &nc_rdataset); ++ if (nc_rdataset.type == dns_rdatatype_soa) { ++ dns_rdataset_disassociate(&nc_rdataset); ++ tname = dns_fixedname_name(&fixed); ++ break; ++ } ++ dns_rdataset_disassociate(&nc_rdataset); ++ } ++ rrl_result = DNS_R_NXDOMAIN; ++ } else if (result == DNS_R_DELEGATION) { ++ rrl_result = result; ++ } else { ++ rrl_result = ISC_R_SUCCESS; ++ } ++ rrl_result = dns_rrl(client->view, &client->peeraddr, ++ ISC_TF((client->attributes ++ & NS_CLIENTATTR_TCP) != 0), ++ client->message->rdclass, qtype, tname, ++ rrl_result, client->now, ++ wouldlog, log_buf, sizeof(log_buf)); ++ if (rrl_result != DNS_RRL_RESULT_OK) { ++ /* ++ * Log dropped or slipped responses in the query ++ * category so that requests are not silently lost. ++ * Starts of rate-limited bursts are logged in ++ * DNS_LOGCATEGORY_RRL. ++ * ++ * Dropped responses are counted with dropped queries ++ * in QryDropped while slipped responses are counted ++ * with other truncated responses in RespTruncated. ++ */ ++ if (wouldlog && ns_g_server->log_queries) { ++ ns_client_log(client, NS_LOGCATEGORY_QUERIES, ++ NS_LOGMODULE_CLIENT, ++ DNS_RRL_LOG_DROP, ++ "%s", log_buf); ++ } ++ if (!client->view->rrl->log_only) { ++ if (rrl_result == DNS_RRL_RESULT_DROP) { ++ /* ++ * These will also be counted in ++ * dns_nsstatscounter_dropped ++ */ ++ inc_stats(client, ++ dns_nsstatscounter_ratedropped); ++ QUERY_ERROR(DNS_R_DROP); ++ } else { ++ /* ++ * These will also be counted in ++ * dns_nsstatscounter_truncatedresp ++ */ ++ inc_stats(client, ++ dns_nsstatscounter_rateslipped); ++ client->message->flags |= ++ DNS_MESSAGEFLAG_TC; ++ } ++ goto cleanup; ++ } ++ } ++ } ++ + if (!ISC_LIST_EMPTY(client->view->rpz_zones) && + (RECURSIONOK(client) || !client->view->rpz_recursive_only) && + rpz_ck_dnssec(client, result, rdataset, sigrdataset) && +@@ -7170,12 +7269,14 @@ + } + + if (eresult != ISC_R_SUCCESS && +- (!PARTIALANSWER(client) || WANTRECURSION(client))) { ++ (!PARTIALANSWER(client) || WANTRECURSION(client) ++ || eresult == DNS_R_DROP)) { + if (eresult == DNS_R_DUPLICATE || eresult == DNS_R_DROP) { + /* + * This was a duplicate query that we are +- * recursing on. Don't send a response now. +- * The original query will still cause a response. ++ * recursing on or the result of rate limiting. ++ * Don't send a response now for a duplicate query, ++ * because the original will still cause a response. + */ + query_next(client, eresult); + } else { +diff -r -u bin/named/server.c-orig bin/named/server.c +--- bin/named/server.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/named/server.c 2004-01-01 00:00:00.000000000 +0000 +@@ -1561,6 +1561,199 @@ + return (result); + } + ++#define CHECK_RRL(obj, cond, pat, val1, val2) \ ++ do { \ ++ if (!(cond)) { \ ++ cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, \ ++ pat, val1, val2); \ ++ result = ISC_R_RANGE; \ ++ goto cleanup; \ ++ } \ ++ } 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; ++ dns_rrl_t *rrl; ++ isc_result_t result; ++ int min_entries, i, j; ++ ++ /* ++ * Most DNS servers have few clients, but intentinally open ++ * recursive and authoritative servers often have many. ++ * So start with a small number of entries unless told otherwise ++ * to reduce cold-start costs. ++ */ ++ min_entries = 500; ++ obj = NULL; ++ result = cfg_map_get(map, "min-table-size", &obj); ++ if (result == ISC_R_SUCCESS) { ++ min_entries = cfg_obj_asuint32(obj); ++ if (min_entries < 1) ++ min_entries = 1; ++ } ++ result = dns_rrl_init(&rrl, view, min_entries); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ ++ i = ISC_MAX(20000, min_entries); ++ obj = NULL; ++ 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, ++ "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; ++ ++ 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, ++ "window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW); ++ } ++ rrl->window = i; ++ ++ i = 0; ++ obj = NULL; ++ 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, ""); ++ } ++ rrl->qps_scale = i; ++ rrl->qps = 1.0; ++ ++ i = 24; ++ obj = NULL; ++ 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, ""); ++ } ++ rrl->ipv4_prefixlen = i; ++ if (i == 32) ++ rrl->ipv4_mask = 0xffffffff; ++ else ++ rrl->ipv4_mask = htonl(0xffffffff << (32-i)); ++ ++ i = 56; ++ obj = NULL; ++ 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", ++ i, DNS_RRL_MAX_PREFIX); ++ } ++ rrl->ipv6_prefixlen = i; ++ for (j = 0; j < 4; ++j) { ++ if (i <= 0) { ++ rrl->ipv6_mask[j] = 0; ++ } else if (i < 32) { ++ rrl->ipv6_mask[j] = htonl(0xffffffff << (32-i)); ++ } else { ++ rrl->ipv6_mask[j] = 0xffffffff; ++ } ++ i -= 32; ++ } ++ ++ obj = NULL; ++ result = cfg_map_get(map, "exempt-clients", &obj); ++ if (result == ISC_R_SUCCESS) { ++ 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, ++ "invalid %s%s", "address match list", ""); ++ } ++ ++ obj = NULL; ++ result = cfg_map_get(map, "log-only", &obj); ++ if (result == ISC_R_SUCCESS && cfg_obj_asboolean(obj)) ++ rrl->log_only = ISC_TRUE; ++ else ++ rrl->log_only = ISC_FALSE; ++ ++ return (ISC_R_SUCCESS); ++ ++ cleanup: ++ dns_rrl_view_destroy(view); ++ return (result); ++} ++ + /* + * Configure 'view' according to 'vconfig', taking defaults from 'config' + * where values are missing in 'vconfig'. +@@ -2925,6 +3118,14 @@ + } + } + ++ obj = NULL; ++ result = ns_config_get(maps, "rate-limit", &obj); ++ if (result == ISC_R_SUCCESS) { ++ result = configure_rrl(view, config, obj); ++ if (result != ISC_R_SUCCESS) ++ goto cleanup; ++ } ++ + result = ISC_R_SUCCESS; + + cleanup: +diff -r -u bin/named/statschannel.c-orig bin/named/statschannel.c +--- bin/named/statschannel.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/named/statschannel.c 2004-01-01 00:00:00.000000000 +0000 +@@ -202,6 +202,10 @@ + SET_NSSTATDESC(updatebadprereq, + "updates rejected due to prerequisite failure", + "UpdateBadPrereq"); ++ SET_NSSTATDESC(ratedropped, "responses dropped for rate limits", ++ "RateDropped"); ++ SET_NSSTATDESC(rateslipped, "responses truncated for rate limits", ++ "RateSlipped"); + INSIST(i == dns_nsstatscounter_max); + + /* Initialize resolver statistics */ +diff -r -u bin/tests/system/README-orig bin/tests/system/README +--- bin/tests/system/README-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/README 2004-01-01 00:00:00.000000000 +0000 +@@ -17,6 +17,7 @@ + nsupdate/ Dynamic update and IXFR tests + resolver/ Regression tests for resolver bugs that have been fixed + (not a complete resolver test suite) ++ rrl/ query rate limiting + rpz/ Tests of response policy zone (RPZ) rewriting + stub/ Tests of stub zone functionality + unknown/ Unknown type and class tests +diff -r -u bin/tests/system/conf.sh.in-orig bin/tests/system/conf.sh.in +--- bin/tests/system/conf.sh.in-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/conf.sh.in 2004-01-01 00:00:00.000000000 +0000 +@@ -58,7 +58,7 @@ + @CHECKDS@ checknames checkzone database dlv dlvauto dlz dlzexternal + dname dns64 dnssec ecdsa forward glue gost ixfr inline limits + logfileconfig lwresd masterfile masterformat metadata notify +- nsupdate pending pkcs11 redirect resolver rndc rpz rrsetorder ++ nsupdate pending pkcs11 redirect resolver rndc rpz rrl rrsetorder + rsabigexponent sortlist smartsign staticstub stub tkey tsig + tsiggss unknown upforwd verify views xfer xferquota zonechecks" + +diff -r -u bin/tests/system/rrl/.gitignore-orig bin/tests/system/rrl/.gitignore +--- bin/tests/system/rrl/.gitignore-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/.gitignore 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1 @@ ++flood +diff -r -u bin/tests/system/rrl/clean.sh-orig bin/tests/system/rrl/clean.sh +--- bin/tests/system/rrl/clean.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/clean.sh 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,21 @@ ++# Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++ ++ ++# Clean up after rrl tests. ++ ++rm -f dig.out* ++rm -f */named.memstats */named.run */named.stats */log */session.key ++rm -f ns3/bl*.db */*.jnl */*.core */*.pid +diff -r -u bin/tests/system/rrl/ns1/named.conf-orig bin/tests/system/rrl/ns1/named.conf +--- bin/tests/system/rrl/ns1/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/ns1/named.conf 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,32 @@ ++/* ++ * Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++ * PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++ ++controls { /* empty */ }; ++ ++options { ++ query-source address 10.53.0.1; ++ notify-source 10.53.0.1; ++ transfer-source 10.53.0.1; ++ port 5300; ++ session-keyfile "session.key"; ++ pid-file "named.pid"; ++ listen-on { 10.53.0.1; }; ++ listen-on-v6 { none; }; ++ notify no; ++}; ++ ++zone "." {type master; file "root.db";}; +diff -r -u bin/tests/system/rrl/ns1/root.db-orig bin/tests/system/rrl/ns1/root.db +--- bin/tests/system/rrl/ns1/root.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/ns1/root.db 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,31 @@ ++; Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++; ++; Permission to use, copy, modify, and/or distribute this software for any ++; purpose with or without fee is hereby granted, provided that the above ++; copyright notice and this permission notice appear in all copies. ++; ++; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++; PERFORMANCE OF THIS SOFTWARE. ++ ++ ++$TTL 120 ++@ SOA ns. hostmaster.ns. ( 1 3600 1200 604800 60 ) ++@ NS ns. ++ns. A 10.53.0.1 ++. A 10.53.0.1 ++ ++; limit responses from here ++tld2. NS ns.tld2. ++ns.tld2. A 10.53.0.2 ++ ++; limit recursion to here ++tld3. NS ns.tld3. ++ns.tld3. A 10.53.0.3 ++ ++; generate SERVFAIL ++tld4. NS ns.tld3. +diff -r -u bin/tests/system/rrl/ns2/hints-orig bin/tests/system/rrl/ns2/hints +--- bin/tests/system/rrl/ns2/hints-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/ns2/hints 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,18 @@ ++; Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++; ++; Permission to use, copy, modify, and/or distribute this software for any ++; purpose with or without fee is hereby granted, provided that the above ++; copyright notice and this permission notice appear in all copies. ++; ++; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++; PERFORMANCE OF THIS SOFTWARE. ++ ++ ++ ++. 0 NS ns1. ++ns1. 0 A 10.53.0.1 +diff -r -u bin/tests/system/rrl/ns2/named.conf-orig bin/tests/system/rrl/ns2/named.conf +--- bin/tests/system/rrl/ns2/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/ns2/named.conf 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,72 @@ ++/* ++ * Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++ * PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++ ++controls { /* empty */ }; ++ ++options { ++ query-source address 10.53.0.2; ++ notify-source 10.53.0.2; ++ transfer-source 10.53.0.2; ++ port 5300; ++ session-keyfile "session.key"; ++ pid-file "named.pid"; ++ statistics-file "named.stats"; ++ listen-on { 10.53.0.2; }; ++ listen-on-v6 { none; }; ++ notify no; ++ ++ 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; ++ }; ++}; ++ ++key rndc_key { ++ secret "1234abcd8765"; ++ algorithm hmac-md5; ++}; ++controls { ++ inet 10.53.0.2 port 9953 allow { any; } keys { rndc_key; }; ++}; ++ ++/* ++ * These log settings have no effect unless "-g" is removed from ../../start.pl ++ */ ++logging { ++ channel debug { ++ file "log-debug"; ++ print-category yes; print-severity yes; severity debug 10; ++ }; ++ channel queries { ++ file "log-queries"; ++ print-category yes; print-severity yes; severity info; ++ }; ++ category rate-limit { debug; queries; }; ++ category queries { debug; queries; }; ++}; ++ ++zone "." { type hint; file "hints"; }; ++ ++zone "tld2."{ type master; file "tld2.db"; }; +diff -r -u bin/tests/system/rrl/ns2/tld2.db-orig bin/tests/system/rrl/ns2/tld2.db +--- bin/tests/system/rrl/ns2/tld2.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/ns2/tld2.db 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,42 @@ ++; Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++; ++; Permission to use, copy, modify, and/or distribute this software for any ++; purpose with or without fee is hereby granted, provided that the above ++; copyright notice and this permission notice appear in all copies. ++; ++; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++; PERFORMANCE OF THIS SOFTWARE. ++ ++ ++ ++; rate limit response from this zone ++ ++$TTL 120 ++@ SOA tld2. hostmaster.ns.tld2. ( 1 3600 1200 604800 60 ) ++ NS ns ++ NS . ++ns A 10.53.0.2 ++ ++a1 A 192.168.2.1 ++ ++*.a2 A 192.168.2.2 ++ ++; a3 is in tld3 ++ ++; a4 does not exist to give NXDOMAIN ++ ++; a5 for TCP requests ++a5 A 192.168.2.5 ++ ++; a6 for whitelisted clients ++a6 A 192.168.2.6 ++ ++; a7 for SERVFAIL ++ ++; a8 for all-per-second limit ++$GENERATE 101-180 all$.a8 A 192.168.2.8 +diff -r -u bin/tests/system/rrl/ns3/hints-orig bin/tests/system/rrl/ns3/hints +--- bin/tests/system/rrl/ns3/hints-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/ns3/hints 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,18 @@ ++; Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++; ++; Permission to use, copy, modify, and/or distribute this software for any ++; purpose with or without fee is hereby granted, provided that the above ++; copyright notice and this permission notice appear in all copies. ++; ++; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++; PERFORMANCE OF THIS SOFTWARE. ++ ++ ++ ++. 0 NS ns1. ++ns1. 0 A 10.53.0.1 +diff -r -u bin/tests/system/rrl/ns3/named.conf-orig bin/tests/system/rrl/ns3/named.conf +--- bin/tests/system/rrl/ns3/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/ns3/named.conf 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,34 @@ ++/* ++ * Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++ * PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++ ++controls { /* empty */ }; ++ ++options { ++ query-source address 10.53.0.3; ++ notify-source 10.53.0.3; ++ transfer-source 10.53.0.3; ++ port 5300; ++ session-keyfile "session.key"; ++ pid-file "named.pid"; ++ listen-on { 10.53.0.3; }; ++ listen-on-v6 { none; }; ++ notify no; ++}; ++ ++zone "." { type hint; file "hints"; }; ++ ++zone "tld3."{ type master; file "tld3.db"; }; +diff -r -u bin/tests/system/rrl/ns3/tld3.db-orig bin/tests/system/rrl/ns3/tld3.db +--- bin/tests/system/rrl/ns3/tld3.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/ns3/tld3.db 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,25 @@ ++; Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++; ++; Permission to use, copy, modify, and/or distribute this software for any ++; purpose with or without fee is hereby granted, provided that the above ++; copyright notice and this permission notice appear in all copies. ++; ++; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++; PERFORMANCE OF THIS SOFTWARE. ++ ++ ++ ++; rate limit response from this zone ++ ++$TTL 120 ++@ SOA tld3. hostmaster.ns.tld3. ( 1 3600 1200 604800 60 ) ++ NS ns ++ NS . ++ns A 10.53.0.3 ++ ++*.a3 A 192.168.3.3 +diff -r -u bin/tests/system/rrl/setup.sh-orig bin/tests/system/rrl/setup.sh +--- bin/tests/system/rrl/setup.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/setup.sh 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,21 @@ ++#!/bin/sh ++# ++# Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++ ++SYSTEMTESTTOP=.. ++. $SYSTEMTESTTOP/conf.sh ++. ./clean.sh ++ +diff -r -u bin/tests/system/rrl/tests.sh-orig bin/tests/system/rrl/tests.sh +--- bin/tests/system/rrl/tests.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rrl/tests.sh 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,224 @@ ++# Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++# ++# Permission to use, copy, modify, and/or distribute this software for any ++# purpose with or without fee is hereby granted, provided that the above ++# copyright notice and this permission notice appear in all copies. ++# ++# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++# PERFORMANCE OF THIS SOFTWARE. ++ ++ ++# test response rate limiting ++ ++SYSTEMTESTTOP=.. ++. $SYSTEMTESTTOP/conf.sh ++ ++#set -x ++#set -o noclobber ++ ++ns1=10.53.0.1 # root, defining the others ++ns2=10.53.0.2 # test server ++ns3=10.53.0.3 # secondary test server ++ns7=10.53.0.7 # whitelisted client ++ ++USAGE="$0: [-x]" ++while getopts "x" c; do ++ case $c in ++ x) set -x;; ++ *) echo "$USAGE" 1>&2; exit 1;; ++ esac ++done ++shift `expr $OPTIND - 1 || true` ++if test "$#" -ne 0; then ++ echo "$USAGE" 1>&2 ++ exit 1 ++fi ++# really quit on control-C ++trap 'exit 1' 1 2 15 ++ ++ ++ret=0 ++setret () { ++ ret=1 ++ echo "$*" ++} ++ ++ ++# Wait until soon after the start of a second to make results consistent. ++# The start of a second credits a rate limit. ++# This would be far easier in C or by assuming a modern version of perl. ++sec_start () { ++ START=`date` ++ while true; do ++ NOW=`date` ++ if test "$START" != "$NOW"; then ++ return ++ fi ++ $PERL -e 'select(undef, undef, undef, 0.05)' || true ++ done ++} ++ ++ ++# $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" ++ #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' \ ++ -e 's/;; flags.* tc .*/TC/p' \ ++ -e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p' \ ++ -e 's/;; .* status: SERVFAIL.*/SERVFAIL/p' \ ++ -e 's/;; connection timed out.*/drop/p' \ ++ -e 's/;; communications error to.*/drop/p' \ ++ | tr -d '\n'` ++ mv "$OFILE=TEMP" "$OFILE=$RESULT" ++ touch -t $START "$OFILE=$RESULT" ++} ++ ++ ++# $1=number of tests $2=target domain $3=dig options ++CNT=1 ++burst () { ++ BURST_LIMIT=$1; shift ++ BURST_DOM_BASE="$1"; shift ++ while test "$BURST_LIMIT" -ge 1; do ++ if test $CNT -lt 10; then ++ CNT="00$CNT" ++ else ++ if test $CNT -lt 100; then ++ CNT="0$CNT" ++ fi ++ fi ++ eval BURST_DOM="$BURST_DOM_BASE" ++ FILE="dig.out-$BURST_DOM-$CNT" ++ digcmd $FILE $BURST_DOM $* & ++ CNT=`expr $CNT + 1` ++ BURST_LIMIT=`expr "$BURST_LIMIT" - 1` ++ done ++} ++ ++ ++# $1=domain $2=IP address $3=# of IP addresses $4=TC $5=drop ++# $6=NXDOMAIN $7=SERVFAIL or other errors ++ck_result() { ++ BAD= ++ wait ++ ADDRS=`ls dig.out-$1-*=$2 2>/dev/null | wc -l | tr -d ' '` ++ TC=`ls dig.out-$1-*=TC 2>/dev/null | wc -l | tr -d ' '` ++ DROP=`ls dig.out-$1-*=drop 2>/dev/null | wc -l | tr -d ' '` ++ 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" ++ BAD=yes ++ fi ++ if test $TC -ne "$4"; then ++ setret "I:$TC instead of $4 truncation responses for $1" ++ BAD=yes ++ fi ++ if test $DROP -ne "$5"; then ++ setret "I:$DROP instead of $5 dropped responses for $1" ++ BAD=yes ++ fi ++ if test $NXDOMAIN -ne "$6"; then ++ setret "I:$NXDOMAIN instead of $6 NXDOMAIN responses for $1" ++ BAD=yes ++ fi ++ if test $SERVFAIL -ne "$7"; then ++ setret "I:$SERVFAIL instead of $7 error responses for $1" ++ BAD=yes ++ fi ++ if test -z "$BAD"; then ++ rm -f dig.out-$1-* ++ fi ++} ++ ++ ++######### ++sec_start ++ ++# 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 ++burst 30 'x$CNT.a2.tld2' ++ ++# IP TC drop NXDOMAIN SERVFAIL ++# check for 24 results ++# including the 1 second delay ++ck_result a1.tld2 192.168.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.168.2.2 2 10 18 0 0 ++ ++ ++######### ++sec_start ++ ++burst 1 'y$CNT.a3.tld3'; wait; burst 20 'y$CNT.a3.tld3' ++burst 20 'z$CNT.a4.tld2' ++ ++# Recursion. ++# The first answer is counted separately because it is counted against ++# the rate limit on recursing to the server for a3.tld3. The remaining 20 ++# are counted as local responses from the cache. ++ck_result 'y*.a3.tld3' 192.168.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 ++ ++ ++######### ++sec_start ++ ++burst 20 a5.tld2 +tcp ++burst 20 a6.tld2 -b $ns7 ++burst 20 a7.tld4 ++ ++# TCP responses are not rate limited ++ck_result a5.tld2 192.168.2.5 20 0 0 0 0 ++ ++# whitelisted client is not rate limited ++ck_result a6.tld2 192.168.2.6 20 0 0 0 0 ++ ++# Errors such as SERVFAIL are rate limited. The numbers are confusing, because ++# other rate limiting can be triggered before the SERVFAIL limit is reached. ++ck_result a7.tld4 192.168.2.1 0 6 12 0 2 ++ ++ ++######### ++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.168.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 ++ ++ ++echo "I:exit status: $ret" ++exit $ret +diff -r -u doc/arm/Bv9ARM-book.xml-orig doc/arm/Bv9ARM-book.xml +--- doc/arm/Bv9ARM-book.xml-orig 2004-01-01 00:00:00.000000000 +0000 ++++ doc/arm/Bv9ARM-book.xml 2004-01-01 00:00:00.000000000 +0000 +@@ -4803,6 +4803,34 @@ + + + ++ ++ ++ rate-limit ++ ++ ++ ++ The start, periodic, and final notices of the ++ rate limiting of a stream of responses are logged at ++ info severity in this category. ++ These messages include a hash value of the domain name ++ of the response and the name itself, ++ except when there is insufficient memory to record ++ the name for the final notice ++ The final notice is normally delayed until about one ++ minute after rate limit stops. ++ A lack of memory can hurry the final notice, ++ in which case it starts with an asterisk (*). ++ Various internal events are logged at debug 1 level ++ and higher. ++ ++ ++ Rate limiting of individual requests ++ is logged in the queries category ++ and can be controlled with the ++ querylog option. ++ ++ ++ + + + +@@ -5334,6 +5362,21 @@ + resolver-query-timeout number ; + deny-answer-addresses { address_match_list } except-from { namelist } ; + deny-answer-aliases { namelist } except-from { namelist } ; ++ rate-limit { ++ responses-per-second number ; ++ errors-per-second number ; ++ nxdomains-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 ; ++ slip number ; ++ exempt-clients { address_match_list } ; ++ max-table-size number ; ++ min-table-size number ; ++ } ; + response-policy { zone_name + policy given | disabled | passthru | nxdomain | nodata | cname domain + recursive-only yes_or_no max-policy-ttl number ; +@@ -9737,6 +9780,215 @@ + 48.zz.2.2001.rpz-nsip CNAME . + + ++ ++ ++ Rate Limiting ++ ++ Excessive essentially identical UDP responses ++ can be discarded 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. ++ ++ ++ ++ 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. ++ ++ ++ ++ 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. ++ ++ ++ ++ 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. ++ ++ ++ ++ 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. ++ Rate limiting prevents the use of BIND 9 to flood a network ++ with responses to requests with forged source addresses, ++ but could let a third party block responses to legitimate requests. ++ 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 ++ cannot be replaced with truncated responses and are instead ++ leaked at the slip rate. ++ ++ ++ ++ When the approximate query per second rate exceeds ++ the qps-scale value, ++ then the responses-per-second, ++ errors-per-second, ++ nxdomains-per-second and ++ all-per-second values are reduced by the ++ ratio of the current rate to the qps-scale value. ++ This feature can tighten defenses during attacks. ++ For example, with ++ qps-scale 250; responses-per-second 20; and ++ a total query rate of 1000 queries/second for all queries from ++ all DNS clients including via TCP, ++ then the effective responses/second limit changes to ++ (250/1000)*20 or 5. ++ Responses sent via TCP are not limited ++ but are counted to compute the query per second rate. ++ ++ ++ ++ Communities of DNS clients can be given their own parameters or no ++ rate limiting by putting ++ 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 ++ statement among the main options. ++ DNS clients within a view can be exempted from rate limits ++ with the exempt-clients clause. ++ ++ ++ ++ UDP responses of all kinds can be limited with the ++ all-per-second phrase. ++ This rate limiting is unlike the rate limiting provided by ++ responses-per-second, ++ errors-per-second, and ++ nxdomains-per-second on a DNS server ++ which are often invisible to the victim of a DNS reflection attack. ++ Unless the forged requests of the attack are the same as the ++ legitimate requests of the victim, the victim's requests are ++ not affected. ++ Responses affected by an all-per-second limit ++ are always dropped; the slip value has no ++ effect. ++ An all-per-second limit should be ++ at least 4 times as large as the other limits, ++ because single DNS clients often send bursts of legitimate ++ requests. ++ For example, the receipt of a single mail message can prompt ++ requests from an SMTP server for NS, PTR, A, and AAAA records ++ as the incoming SMTP/TCP/IP connection is considered. ++ The SMTP server can need additional NS, A, AAAA, MX, TXT, and SPF ++ records as it considers the STMP Mail From ++ command. ++ Web browsers often repeatedly resolve the same names that ++ are repeated in HTML <IMG> tags in a page. ++ All-per-second is similar to the ++ rate limiting offered by firewalls but often inferior. ++ Attacks that justify ignoring the ++ contents of DNS responses are likely to be attacks on the ++ DNS server itself. ++ They usually should be discarded before the DNS server ++ spends resources make TCP connections or parsing DNS requesets, ++ but that rate limiting must be done before the ++ DNS server sees the requests. ++ ++ ++ ++ The maximum size of the table used to track requests and ++ rate limit responses is set with max-table-size. ++ Each entry in the table is between 40 and 80 bytes. ++ The table needs approximately as many entries as the number ++ of requests received per second. ++ The default is 20,000. ++ To reduce the cold start of growing the table, ++ min-table-size (default 500) ++ can set the minimum table size. ++ Enable rate-limit category logging to monitor ++ expansions of the table and inform ++ choices for the initial and maximum table size. ++ ++ ++ ++ Use log-only yes to test rate limiting parameters ++ without actually dropping any requests. ++ ++ ++ ++ Responses dropped by rate limits are included in the ++ RateDropped and QryDropped ++ statistics. ++ Responses that truncated by rate limits are included in ++ RateSlipped and RespTruncated. ++ + + + +@@ -14385,6 +14637,32 @@ + + + ++ ++ ++ RateDropped ++ ++ ++ ++ ++ ++ ++ Responses dropped by rate limits. ++ ++ ++ ++ ++ ++ RateSlipped ++ ++ ++ ++ ++ ++ ++ Responses truncated by rate limits. ++ ++ ++ + + + +diff -r -u lib/dns/Makefile.in-orig lib/dns/Makefile.in +--- lib/dns/Makefile.in-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/Makefile.in 2004-01-01 00:00:00.000000000 +0000 +@@ -66,8 +66,8 @@ + portlist.@O@ private.@O@ \ + rbt.@O@ rbtdb.@O@ rbtdb64.@O@ rcode.@O@ rdata.@O@ \ + rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \ +- request.@O@ resolver.@O@ result.@O@ rootns.@O@ rpz.@O@ \ +- rriterator.@O@ sdb.@O@ \ ++ request.@O@ resolver.@O@ result.@O@ rootns.@O@ \ ++ rpz.@O@ rrl.@O@ rriterator.@O@ sdb.@O@ \ + sdlz.@O@ soa.@O@ ssu.@O@ ssu_external.@O@ \ + stats.@O@ tcpmsg.@O@ time.@O@ timer.@O@ tkey.@O@ \ + tsec.@O@ tsig.@O@ ttl.@O@ update.@O@ validator.@O@ \ +@@ -93,7 +93,7 @@ + name.c ncache.c nsec.c nsec3.c order.c peer.c portlist.c \ + rbt.c rbtdb.c rbtdb64.c rcode.c rdata.c rdatalist.c \ + rdataset.c rdatasetiter.c rdataslab.c request.c \ +- resolver.c result.c rootns.c rpz.c rriterator.c \ ++ resolver.c result.c rootns.c rpz.c rrl.c rriterator.c \ + sdb.c sdlz.c soa.c ssu.c ssu_external.c \ + stats.c tcpmsg.c time.c timer.c tkey.c \ + tsec.c tsig.c ttl.c update.c validator.c \ +diff -r -u lib/dns/include/dns/log.h-orig lib/dns/include/dns/log.h +--- lib/dns/include/dns/log.h-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/include/dns/log.h 2004-01-01 00:00:00.000000000 +0000 +@@ -43,6 +43,7 @@ + #define DNS_LOGCATEGORY_DELEGATION_ONLY (&dns_categories[10]) + #define DNS_LOGCATEGORY_EDNS_DISABLED (&dns_categories[11]) + #define DNS_LOGCATEGORY_RPZ (&dns_categories[12]) ++#define DNS_LOGCATEGORY_RRL (&dns_categories[13]) + + /* Backwards compatibility. */ + #define DNS_LOGCATEGORY_GENERAL ISC_LOGCATEGORY_GENERAL +diff -r -u lib/dns/include/dns/rrl.h-orig lib/dns/include/dns/rrl.h +--- lib/dns/include/dns/rrl.h-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/include/dns/rrl.h 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,273 @@ ++/* ++ * Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++ * PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++ ++#ifndef DNS_RRL_H ++#define DNS_RRL_H 1 ++ ++/* ++ * Rate limit DNS responses. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++ ++ISC_LANG_BEGINDECLS ++ ++ ++/* ++ * Memory allocation or other failures. ++ */ ++#define DNS_RRL_LOG_FAIL ISC_LOG_WARNING ++/* ++ * dropped or slipped responses. ++ */ ++#define DNS_RRL_LOG_DROP ISC_LOG_INFO ++/* ++ * Major events in dropping or slipping. ++ */ ++#define DNS_RRL_LOG_DEBUG1 ISC_LOG_DEBUG(3) ++/* ++ * Limit computations. ++ */ ++#define DNS_RRL_LOG_DEBUG2 ISC_LOG_DEBUG(4) ++/* ++ * Even less interesting. ++ */ ++#define DNS_RRL_LOG_DEBUG3 ISC_LOG_DEBUG(9) ++ ++ ++#define DNS_RRL_LOG_ERR_LEN 64 ++#define DNS_RRL_LOG_BUF_LEN (sizeof("would continue limiting") + \ ++ DNS_RRL_LOG_ERR_LEN + \ ++ sizeof(" responses to ") + \ ++ ISC_NETADDR_FORMATSIZE + \ ++ sizeof("/128 for IN ") + \ ++ DNS_RDATATYPE_FORMATSIZE + \ ++ DNS_NAME_FORMATSIZE) ++ ++ ++typedef struct dns_rrl_hash dns_rrl_hash_t; ++ ++/* ++ * Response types. ++ */ ++typedef enum { ++ DNS_RRL_RTYPE_FREE = 0, ++ DNS_RRL_RTYPE_QUERY, ++ DNS_RRL_RTYPE_DELEGATION, ++ DNS_RRL_RTYPE_NXDOMAIN, ++ DNS_RRL_RTYPE_ERROR, ++ DNS_RRL_RTYPE_ALL, ++ DNS_RRL_RTYPE_TCP, ++} dns_rrl_rtype_t; ++ ++/* ++ * A rate limit bucket key. ++ * This should be small to limit the total size of the database. ++ * The hash of the qname should be wide enough to make the probability ++ * of collisions among requests from a single IP address block less than 50%. ++ * We need a 32-bit hash value for 10000 qps (e.g. random qnames forged ++ * by attacker) to collide with legitimate qnames from the target with ++ * probability at most 1%. ++ */ ++#define DNS_RRL_MAX_PREFIX 64 ++typedef union dns_rrl_key dns_rrl_key_t; ++union dns_rrl_key { ++ struct { ++ isc_uint32_t ip[DNS_RRL_MAX_PREFIX/32]; ++ isc_uint32_t qname_hash; ++ dns_rdatatype_t qtype; ++ isc_uint8_t qclass; ++ dns_rrl_rtype_t rtype :3; ++ isc_boolean_t ipv6 :1; ++ } s; ++ isc_uint16_t w[1]; ++}; ++ ++/* ++ * A rate-limit entry. ++ * This should be small to limit the total size of the table of entries. ++ */ ++typedef struct dns_rrl_entry dns_rrl_entry_t; ++typedef ISC_LIST(dns_rrl_entry_t) dns_rrl_bin_t; ++struct dns_rrl_entry { ++ ISC_LINK(dns_rrl_entry_t) lru; ++ ISC_LINK(dns_rrl_entry_t) hlink; ++ dns_rrl_key_t key; ++# define DNS_RRL_RESPONSE_BITS 24 ++ signed int responses :DNS_RRL_RESPONSE_BITS; ++# define DNS_RRL_QNAMES_BITS 8 ++ unsigned int log_qname :DNS_RRL_QNAMES_BITS; ++ ++# define DNS_RRL_TS_GEN_BITS 2 ++ unsigned int ts_gen :DNS_RRL_TS_GEN_BITS; ++ isc_boolean_t ts_valid :1; ++# define DNS_RRL_HASH_GEN_BITS 1 ++ unsigned int hash_gen :DNS_RRL_HASH_GEN_BITS; ++ isc_boolean_t logged :1; ++# define DNS_RRL_LOG_BITS 11 ++ unsigned int log_secs :DNS_RRL_LOG_BITS; ++ ++# define DNS_RRL_TS_BITS 12 ++ unsigned int ts :DNS_RRL_TS_BITS; ++ ++# define DNS_RRL_MAX_SLIP 10 ++ unsigned int slip_cnt :4; ++}; ++ ++#define DNS_RRL_MAX_TIME_TRAVEL 5 ++#define DNS_RRL_FOREVER (1<= DNS_RRL_MAX_TS ++#error "DNS_RRL_MAX_WINDOW is too large" ++#endif ++#define DNS_RRL_MAX_RATE 1000 ++#if DNS_RRL_MAX_RATE >= (DNS_RRL_MAX_RESPONSES / DNS_RRL_MAX_WINDOW) ++#error "DNS_RRL_MAX_rate is too large" ++#endif ++ ++#if (1<= DNS_RRL_FOREVER ++#error DNS_RRL_LOG_BITS is too big ++#endif ++#define DNS_RRL_MAX_LOG_SECS 1800 ++#if DNS_RRL_MAX_LOG_SECS >= (1<= (1< + #include ++#include + #include + #include + #include +@@ -142,6 +143,7 @@ + dns_rbt_t * answeracl_exclude; + dns_rbt_t * denyanswernames; + dns_rbt_t * answernames_exclude; ++ dns_rrl_t * rrl; + isc_boolean_t provideixfr; + isc_boolean_t requestnsid; + dns_ttl_t maxcachettl; +diff -r -u lib/dns/log.c-orig lib/dns/log.c +--- lib/dns/log.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/log.c 2004-01-01 00:00:00.000000000 +0000 +@@ -45,6 +45,7 @@ + { "delegation-only", 0 }, + { "edns-disabled", 0 }, + { "rpz", 0 }, ++ { "rate-limit", 0 }, + { NULL, 0 } + }; + +diff -r -u lib/dns/rrl.c-orig lib/dns/rrl.c +--- lib/dns/rrl.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/rrl.c 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,1321 @@ ++/* ++ * Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH ++ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, ++ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ++ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE ++ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ++ * PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++ ++/*! \file */ ++ ++/* ++ * Rate limit DNS responses. ++ */ ++ ++/* #define ISC_LIST_CHECKINIT */ ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++static void ++log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_boolean_t early, ++ char *log_buf, unsigned int log_buf_len); ++ ++ ++/* ++ * Get a modulus for a hash function that is tolerably likely to be ++ * relatively prime to most inputs. Of course, we get a prime for for initial ++ * values not larger than the square of the last prime. We often get a prime ++ * after that. ++ * This works well in practice for hash tables up to at least 100 ++ * times the square of the last prime and better than a multiplicative hash. ++ */ ++static int ++hash_divisor(unsigned int initial) { ++ static isc_uint16_t primes[] = { ++ 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, ++ 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, ++#if 0 ++ 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, ++ 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, ++ 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, ++ 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, ++ 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, ++ 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, ++ 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, ++ 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, ++ 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, ++ 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, ++ 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, ++ 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,1009, ++#endif ++ }; ++ int divisions, tries; ++ unsigned int result; ++ isc_uint16_t *pp, p; ++ ++ result = initial; ++ ++ if (primes[sizeof(primes)/sizeof(primes[0])-1] >= result) { ++ pp = primes; ++ while (*pp < result) ++ ++pp; ++ return (*pp); ++ } ++ ++ if ((result & 1) == 0) ++ ++result; ++ ++ divisions = 0; ++ tries = 1; ++ pp = primes; ++ do { ++ p = *pp++; ++ ++divisions; ++ if ((result % p) == 0) { ++ ++tries; ++ result += 2; ++ pp = primes; ++ } ++ } while (pp < &primes[sizeof(primes)/sizeof(primes[0])]); ++ ++ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3, ++ "%d hash_divisor() divisions in %d tries" ++ " to get %d from %d", ++ divisions, tries, result, initial); ++ ++ return (result); ++} ++ ++/* ++ * Convert a timestamp to a number of seconds in the past. ++ */ ++static inline int ++delta_rrl_time(isc_stdtime_t ts, isc_stdtime_t now) { ++ int delta; ++ ++ delta = now - ts; ++ if (delta >= 0) ++ return (delta); ++ ++ /* ++ * The timestamp is in the future. That future might result from ++ * re-ordered requests, because we use timestamps on requests ++ * instead of consulting a clock. Timestamps in the distant future are ++ * assumed to result from clock changes. When the clock changes to ++ * the past, make existing timestamps appear to be in the past. ++ */ ++ if (delta < -DNS_RRL_MAX_TIME_TRAVEL) ++ return (DNS_RRL_FOREVER); ++ return (0); ++} ++ ++static inline int ++get_age(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, isc_stdtime_t now) { ++ if (!e->ts_valid) ++ return (DNS_RRL_FOREVER); ++ return (delta_rrl_time(e->ts + rrl->ts_bases[e->ts_gen], now)); ++} ++ ++static inline void ++set_age(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_stdtime_t now) { ++ dns_rrl_entry_t *e_old; ++ unsigned int ts_gen; ++ int i, ts; ++ ++ ts_gen = rrl->ts_gen; ++ ts = now - rrl->ts_bases[ts_gen]; ++ if (ts < 0) { ++ if (ts < -DNS_RRL_MAX_TIME_TRAVEL) ++ ts = DNS_RRL_FOREVER; ++ else ++ ts = 0; ++ } ++ ++ /* ++ * Make a new timestamp base if the current base is too old. ++ * All entries older than DNS_RRL_MAX_WINDOW seconds are ancient, ++ * useless history. Their timestamps can be treated as if they are ++ * all the same. ++ * We only do arithmetic on more recent timestamps, so bases for ++ * older timestamps can be recycled provided the old timestamps are ++ * marked as ancient history. ++ * This loop is almost always very short because most entries are ++ * recycled after one second and any entries that need to be marked ++ * are older than (DNS_RRL_TS_BASES)*DNS_RRL_MAX_TS seconds. ++ */ ++ 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 = ISC_LIST_PREV(e_old, lru), ++i) { ++ if (e_old->ts_valid) ++ e_old->ts_valid = ISC_FALSE; ++ } ++ if (i != 0) ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, ++ "rrl new time base scanned %d entries" ++ " at %d for %d %d %d %d", ++ i, now, rrl->ts_bases[ts_gen], ++ rrl->ts_bases[(ts_gen+1)%DNS_RRL_TS_BASES], ++ rrl->ts_bases[(ts_gen+2)%DNS_RRL_TS_BASES], ++ rrl->ts_bases[(ts_gen+3)%DNS_RRL_TS_BASES]); ++ rrl->ts_gen = ts_gen; ++ rrl->ts_bases[ts_gen] = now; ++ ts = 0; ++ } ++ ++ e->ts_gen = ts_gen; ++ e->ts = ts; ++ e->ts_valid = ISC_TRUE; ++} ++ ++static isc_result_t ++expand_entries(dns_rrl_t *rrl, int new) { ++ unsigned int bsize; ++ dns_rrl_block_t *b; ++ dns_rrl_entry_t *e; ++ double rate; ++ int i; ++ ++ if (rrl->num_entries+new >= rrl->max_entries && rrl->max_entries != 0) { ++ if (rrl->num_entries >= rrl->max_entries) ++ return (ISC_R_SUCCESS); ++ new = rrl->max_entries - rrl->num_entries; ++ if (new <= 0) ++ return (ISC_R_NOMEMORY); ++ } ++ ++ /* ++ * Log expansions so that the user can tune max-table-size ++ * and min-table-size. ++ */ ++ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && ++ rrl->hash != NULL) { ++ rate = rrl->probes; ++ if (rrl->searches != 0) ++ rate /= rrl->searches; ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, ++ "increase from %d to %d RRL entries with" ++ " %d bins; average search length %.1f", ++ rrl->num_entries, rrl->num_entries+new, ++ rrl->hash->length, rate); ++ } ++ ++ bsize = sizeof(dns_rrl_block_t) + (new-1)*sizeof(dns_rrl_entry_t); ++ b = isc_mem_get(rrl->mctx, bsize); ++ if (b == NULL) { ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL, ++ "isc_mem_get(%d) failed for RRL entries", ++ bsize); ++ return (ISC_R_NOMEMORY); ++ } ++ memset(b, 0, bsize); ++ b->size = bsize; ++ ++ e = b->entries; ++ for (i = 0; i < new; ++i, ++e) { ++ ISC_LINK_INIT(e, hlink); ++ ISC_LIST_INITANDAPPEND(rrl->lru, e, lru); ++ } ++ rrl->num_entries += new; ++ ISC_LIST_INITANDAPPEND(rrl->blocks, b, link); ++ ++ return (ISC_R_SUCCESS); ++} ++ ++static inline dns_rrl_bin_t * ++get_bin(dns_rrl_hash_t *hash, unsigned int hval) { ++ return (&hash->bins[hval % hash->length]); ++} ++ ++static void ++free_old_hash(dns_rrl_t *rrl) { ++ dns_rrl_hash_t *old_hash; ++ dns_rrl_bin_t *old_bin; ++ dns_rrl_entry_t *e, *e_next; ++ ++ old_hash = rrl->old_hash; ++ for (old_bin = &old_hash->bins[0]; ++ old_bin < &old_hash->bins[old_hash->length]; ++ ++old_bin) { ++ for (e = ISC_LIST_HEAD(*old_bin); e != NULL; e = e_next) { ++ e_next = ISC_LIST_NEXT(e, hlink); ++ ISC_LINK_INIT(e, hlink); ++ } ++ } ++ ++ isc_mem_put(rrl->mctx, old_hash, ++ sizeof(*old_hash) ++ + (old_hash->length-1)*sizeof(old_hash->bins[0])); ++ rrl->old_hash = NULL; ++} ++ ++static isc_result_t ++expand_rrl_hash(dns_rrl_t *rrl, isc_stdtime_t now) { ++ dns_rrl_hash_t *hash; ++ int old_bins, new_bins, hsize; ++ double rate; ++ ++ if (rrl->old_hash != NULL) ++ free_old_hash(rrl); ++ ++ /* ++ * Most searches fail and so go to the end of the chain. ++ * Use a small hash table load factor. ++ */ ++ old_bins = (rrl->hash == NULL) ? 0 : rrl->hash->length; ++ new_bins = old_bins/8 + old_bins; ++ if (new_bins < rrl->num_entries) ++ new_bins = rrl->num_entries; ++ new_bins = hash_divisor(new_bins); ++ ++ hsize = sizeof(dns_rrl_hash_t) + (new_bins-1)*sizeof(hash->bins[0]); ++ hash = isc_mem_get(rrl->mctx, hsize); ++ if (hash == NULL) { ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL, ++ "isc_mem_get(%d) failed for" ++ " RRL hash table", ++ hsize); ++ return (ISC_R_NOMEMORY); ++ } ++ memset(hash, 0, hsize); ++ hash->length = new_bins; ++ rrl->hash_gen ^= 1; ++ hash->gen = rrl->hash_gen; ++ ++ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && old_bins != 0) { ++ rate = rrl->probes; ++ if (rrl->searches != 0) ++ rate /= rrl->searches; ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, ++ "increase from %d to %d RRL bins for" ++ " %d entries; average search length %.1f", ++ old_bins, new_bins, rrl->num_entries, rate); ++ } ++ ++ rrl->old_hash = rrl->hash; ++ if (rrl->old_hash != NULL) ++ rrl->old_hash->check_time = now; ++ rrl->hash = hash; ++ ++ return (ISC_R_SUCCESS); ++} ++ ++static void ++ref_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, int probes, isc_stdtime_t now) { ++ /* ++ * Make the entry most recently used. ++ */ ++ if (ISC_LIST_HEAD(rrl->lru) != e) { ++ if (e == rrl->last_logged) ++ rrl->last_logged = ISC_LIST_PREV(e, lru); ++ ISC_LIST_UNLINK(rrl->lru, e, lru); ++ ISC_LIST_PREPEND(rrl->lru, e, lru); ++ } ++ ++ /* ++ * Expand the hash table if it is time and necessary. ++ * This will leave the newly referenced entry in a chain in the ++ * old hash table. It will migrate to the new hash table the next ++ * time it is used or be cut loose when the old hash table is destroyed. ++ */ ++ rrl->probes += probes; ++ ++rrl->searches; ++ if (rrl->searches > 100 && ++ delta_rrl_time(rrl->hash->check_time, now) > 1) { ++ if (rrl->probes/rrl->searches > 2) ++ expand_rrl_hash(rrl, now); ++ rrl->hash->check_time = now; ++ rrl->probes = 0; ++ rrl->searches = 0; ++ } ++} ++ ++static inline isc_boolean_t ++key_cmp(const dns_rrl_key_t *a, const dns_rrl_key_t *b) { ++ if (memcmp(a, b, sizeof(dns_rrl_key_t)) == 0) ++ return (ISC_TRUE); ++ return (ISC_FALSE); ++} ++ ++static inline isc_uint32_t ++hash_key(const dns_rrl_key_t *key) { ++ isc_uint32_t hval; ++ int i; ++ ++ hval = key->w[0]; ++ for (i = sizeof(*key)/sizeof(key->w[0]) - 1; i >= 0; --i) { ++ hval = key->w[i] + (hval<<1); ++ } ++ return (hval); ++} ++ ++/* ++ * Construct the hash table key. ++ * Use a hash of the DNS query name to save space in the database. ++ * Collisions result in legitimate rate limiting responses for one ++ * query name also limiting responses for other names to the ++ * same client. This is rare and benign enough given the large ++ * space costs compared to keeping the entire name in the database ++ * entry or the time costs of dynamic allocation. ++ */ ++static void ++make_key(const dns_rrl_t *rrl, dns_rrl_key_t *key, ++ const isc_sockaddr_t *client_addr, ++ dns_rdatatype_t qtype, dns_name_t *qname, dns_rdataclass_t qclass, ++ dns_rrl_rtype_t rtype) ++{ ++ dns_name_t base; ++ dns_offsets_t base_offsets; ++ int labels, i; ++ ++ memset(key, 0, sizeof(*key)); ++ ++ key->s.rtype = rtype; ++ if (rtype == DNS_RRL_RTYPE_QUERY || ++ rtype == DNS_RRL_RTYPE_DELEGATION) { ++ key->s.qclass = qclass; ++ key->s.qtype = qtype; ++ } ++ ++ if (qname != NULL && qname->labels != 0) { ++ /* ++ * Ignore the first label of wildcards. ++ */ ++ if ((qname->attributes & DNS_NAMEATTR_WILDCARD) != 0 && ++ (labels = dns_name_countlabels(qname)) > 1) { ++ dns_name_init(&base, base_offsets); ++ dns_name_getlabelsequence(qname, 1, labels-1, &base); ++ key->s.qname_hash = dns_name_hashbylabel(&base, ++ ISC_FALSE); ++ } else { ++ key->s.qname_hash = dns_name_hashbylabel(qname, ++ ISC_FALSE); ++ } ++ } ++ ++ switch (client_addr->type.sa.sa_family) { ++ case AF_INET: ++ key->s.ip[0] = (client_addr->type.sin.sin_addr.s_addr & ++ rrl->ipv4_mask); ++ break; ++ case AF_INET6: ++ key->s.ipv6 = ISC_TRUE; ++ memcpy(key->s.ip, &client_addr->type.sin6.sin6_addr, ++ sizeof(key->s.ip)); ++ for (i = 0; i < DNS_RRL_MAX_PREFIX/32; ++i) ++ key->s.ip[i] &= rrl->ipv6_mask[i]; ++ break; ++ } ++} ++ ++static inline int ++response_balance(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) { ++ int balance, rate; ++ ++ 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); ++ } ++ balance += age * rate; ++ if (balance > rate) ++ balance = rate; ++ return (balance); ++} ++ ++/* ++ * Search for an entry for a response and optionally create it. ++ */ ++static dns_rrl_entry_t * ++get_entry(dns_rrl_t *rrl, const isc_sockaddr_t *client_addr, ++ dns_rdataclass_t qclass, dns_rdatatype_t qtype, dns_name_t *qname, ++ dns_rrl_rtype_t rtype, isc_stdtime_t now, isc_boolean_t create, ++ char *log_buf, unsigned int log_buf_len) ++{ ++ dns_rrl_key_t key; ++ isc_uint32_t hval; ++ dns_rrl_entry_t *e; ++ dns_rrl_hash_t *hash; ++ dns_rrl_bin_t *new_bin, *old_bin; ++ int probes, age; ++ ++ make_key(rrl, &key, client_addr, qtype, qname, qclass, rtype); ++ hval = hash_key(&key); ++ ++ /* ++ * Look for the entry in the current hash table. ++ */ ++ new_bin = get_bin(rrl->hash, hval); ++ probes = 1; ++ e = ISC_LIST_HEAD(*new_bin); ++ while (e != NULL) { ++ if (key_cmp(&e->key, &key)) { ++ ref_entry(rrl, e, probes, now); ++ return (e); ++ } ++ ++probes; ++ e = ISC_LIST_NEXT(e, hlink); ++ } ++ ++ /* ++ * Look in the old hash table. ++ */ ++ if (rrl->old_hash != NULL) { ++ old_bin = get_bin(rrl->old_hash, hval); ++ e = ISC_LIST_HEAD(*old_bin); ++ while (e != NULL) { ++ if (key_cmp(&e->key, &key)) { ++ ISC_LIST_UNLINK(*old_bin, e, hlink); ++ ISC_LIST_PREPEND(*new_bin, e, hlink); ++ e->hash_gen = rrl->hash_gen; ++ ref_entry(rrl, e, probes, now); ++ return (e); ++ } ++ e = ISC_LIST_NEXT(e, hlink); ++ } ++ ++ /* ++ * Discard prevous hash table when all of its entries are old. ++ */ ++ age = delta_rrl_time(rrl->old_hash->check_time, now); ++ if (age > rrl->window) ++ free_old_hash(rrl); ++ } ++ ++ if (!create) ++ return (NULL); ++ ++ /* ++ * The entry does not exist, so create it by finding a free entry. ++ * Keep currently penalized and logged entries. ++ * Try to make more entries if none are idle. ++ * Steal the oldest entry if we cannot create more. ++ */ ++ for (e = ISC_LIST_TAIL(rrl->lru); e != NULL; e = ISC_LIST_PREV(e, lru)) { ++ if (!ISC_LINK_LINKED(e, hlink)) ++ break; ++ age = get_age(rrl, e, now); ++ if (age <= 1) { ++ e = NULL; ++ break; ++ } ++ if (!e->logged && response_balance(rrl, e, age) >= 0) ++ break; ++ } ++ if (e == NULL) { ++ expand_entries(rrl, ISC_MIN((rrl->num_entries+1)/2, 1000)); ++ e = ISC_LIST_TAIL(rrl->lru); ++ } ++ if (e->logged) ++ log_end(rrl, e, ISC_TRUE, log_buf, log_buf_len); ++ if (ISC_LINK_LINKED(e, hlink)) { ++ if (e->hash_gen == rrl->hash_gen) ++ hash = rrl->hash; ++ else ++ hash = rrl->old_hash; ++ old_bin = get_bin(hash, hash_key(&e->key)); ++ ISC_LIST_UNLINK(*old_bin, e, hlink); ++ } ++ ISC_LIST_PREPEND(*new_bin, e, hlink); ++ e->hash_gen = rrl->hash_gen; ++ e->key = key; ++ e->ts_valid = ISC_FALSE; ++ ref_entry(rrl, e, probes, now); ++ return (e); ++} ++ ++static void ++debit_log(const dns_rrl_entry_t *e, int age, const char *action) { ++ char buf[sizeof("age=12345678")]; ++ const char *age_str; ++ ++ if (age == DNS_RRL_FOREVER) { ++ age_str = ""; ++ } else { ++ snprintf(buf, sizeof(buf), "age=%d", age); ++ age_str = buf; ++ } ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3, ++ "rrl %08x %6s responses=%-3d %s", ++ hash_key(&e->key), age_str, e->responses, action); ++} ++ ++static inline dns_rrl_result_t ++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; ++ 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); ++ } ++ if (rate == 0) ++ return (DNS_RRL_RESULT_OK); ++ ++ if (scale < 1.0) { ++ /* ++ * The limit for clients that have used TCP is not scaled. ++ */ ++ credit_e = get_entry(rrl, client_addr, ++ 0, dns_rdatatype_none, NULL, ++ DNS_RRL_RTYPE_TCP, now, ISC_FALSE, ++ log_buf, log_buf_len); ++ if (credit_e != NULL) { ++ age = get_age(rrl, e, now); ++ if (age < rrl->window) ++ scale = 1.0; ++ } ++ } ++ if (scale < 1.0) { ++ new_rate = 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; ++ } ++ 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); ++ } ++ rate = new_rate; ++ *ratep = rate; ++ } ++ } ++ ++ min = -rrl->window * rate; ++ ++ /* ++ * Treat time jumps into the recent past as no time. ++ * Treat entries older than the window as if they were just created ++ * Credit other entries. ++ */ ++ age = get_age(rrl, e, now); ++ if (age > 0) { ++ /* ++ * Credit tokens earned during elapsed time. ++ */ ++ if (age > rrl->window) { ++ e->responses = rate; ++ e->slip_cnt = 0; ++ } else { ++ e->responses += rate*age; ++ if (e->responses > rate) { ++ e->responses = rate; ++ e->slip_cnt = 0; ++ } ++ } ++ /* ++ * Find the seconds since last log message without overflowing ++ * small counter. This counter is reset when an entry is ++ * created. It is not necessarily reset when some requests ++ * are answered provided other requests continue to be dropped ++ * or slipped. This can happen when the request rate is just ++ * at the limit. ++ */ ++ if (e->logged) { ++ log_secs = e->log_secs; ++ log_secs += age; ++ if (log_secs > DNS_RRL_MAX_LOG_SECS || log_secs < 0) ++ log_secs = DNS_RRL_MAX_LOG_SECS; ++ e->log_secs = log_secs; ++ } ++ } ++ set_age(rrl, e, now); ++ ++ /* ++ * Debit the entry for this response. ++ */ ++ if (--e->responses >= 0) { ++ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) ++ debit_log(e, age, ""); ++ return (DNS_RRL_RESULT_OK); ++ } ++ ++ if (e->responses < min) ++ e->responses = min; ++ ++ /* ++ * Drop this response unless it should slip or leak. ++ */ ++ slip = rrl->slip; ++ if (slip > 2 && scale < 1.0) { ++ new_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); ++ slip = new_slip; ++ rrl->scaled_slip = slip; ++ } ++ } ++ if (slip != 0 && e->key.s.rtype != DNS_RRL_RTYPE_ALL) { ++ if (e->slip_cnt++ == 0) { ++ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) ++ debit_log(e, age, "slip"); ++ return (DNS_RRL_RESULT_SLIP); ++ } else if (e->slip_cnt >= slip) { ++ e->slip_cnt = 0; ++ } ++ } ++ ++ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) ++ debit_log(e, age, "drop"); ++ return (DNS_RRL_RESULT_DROP); ++} ++ ++static inline dns_rrl_qname_buf_t * ++get_qname(dns_rrl_t *rrl, const dns_rrl_entry_t *e) { ++ dns_rrl_qname_buf_t *qbuf; ++ ++ qbuf = rrl->qnames[e->log_qname]; ++ if (qbuf == NULL || qbuf->e != e) ++ return (NULL); ++ return (qbuf); ++} ++ ++static inline void ++free_qname(dns_rrl_t *rrl, dns_rrl_entry_t *e) { ++ dns_rrl_qname_buf_t *qbuf; ++ ++ qbuf = get_qname(rrl, e); ++ if (qbuf != NULL) { ++ qbuf->e = NULL; ++ ISC_LIST_APPEND(rrl->qname_free, qbuf, link); ++ } ++} ++ ++static void ++add_log_str(isc_buffer_t *lb, const char *str, unsigned int str_len) ++{ ++ isc_region_t region; ++ ++ isc_buffer_availableregion(lb, ®ion); ++ if (str_len >= region.length) { ++ if (region.length <= 0) ++ return; ++ str_len = region.length; ++ } ++ memcpy(region.base, str, str_len); ++ isc_buffer_add(lb, str_len); ++} ++ ++#define ADD_LOG_CSTR(eb, s) add_log_str(eb, s, sizeof(s)-1) ++ ++/* ++ * Build strings for the logs ++ */ ++static void ++make_log_buf(dns_rrl_t *rrl, dns_rrl_entry_t *e, ++ const char *str1, const char *str2, isc_boolean_t plural, ++ dns_name_t *qname, isc_boolean_t save_qname, ++ dns_rrl_result_t rrl_result, isc_result_t resp_result, ++ char *log_buf, unsigned int log_buf_len) ++{ ++ isc_buffer_t lb; ++ dns_rrl_qname_buf_t *qbuf; ++ isc_netaddr_t cidr; ++ char strbuf[ISC_MAX(sizeof("/123"), sizeof(" (12345678)"))]; ++ const char *rstr; ++ isc_result_t msg_result; ++ ++ if (log_buf_len <= 1) { ++ if (log_buf_len == 1) ++ log_buf[0] = '\0'; ++ return; ++ } ++ isc_buffer_init(&lb, log_buf, log_buf_len-1); ++ ++ if (str1 != NULL) ++ add_log_str(&lb, str1, strlen(str1)); ++ if (str2 != NULL) ++ add_log_str(&lb, str2, strlen(str2)); ++ ++ switch (rrl_result) { ++ case DNS_RRL_RESULT_OK: ++ break; ++ case DNS_RRL_RESULT_DROP: ++ ADD_LOG_CSTR(&lb, "drop "); ++ break; ++ case DNS_RRL_RESULT_SLIP: ++ ADD_LOG_CSTR(&lb, "slip "); ++ break; ++ default: ++ INSIST(0); ++ break; ++ } ++ ++ 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"); ++ break; ++ case DNS_RRL_RTYPE_NXDOMAIN: ++ ADD_LOG_CSTR(&lb, "NXDOMAIN response"); ++ break; ++ case DNS_RRL_RTYPE_ERROR: ++ if (resp_result == ISC_R_SUCCESS) { ++ ADD_LOG_CSTR(&lb, "error response"); ++ } else { ++ rstr = isc_result_totext(resp_result); ++ ADD_LOG_CSTR(&lb, " response"); ++ } ++ break; ++ case DNS_RRL_RTYPE_ALL: ++ ADD_LOG_CSTR(&lb, "all response"); ++ break; ++ default: ++ INSIST(0); ++ } ++ ++ if (plural) ++ ADD_LOG_CSTR(&lb, "s to "); ++ else ++ ADD_LOG_CSTR(&lb, " to "); ++ ++ memset(&cidr, 0, sizeof(cidr)); ++ if (e->key.s.ipv6) { ++ snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv6_prefixlen); ++ cidr.family = AF_INET6; ++ memset(&cidr.type.in6, 0, sizeof(cidr.type.in6)); ++ memcpy(&cidr.type.in6, e->key.s.ip, sizeof(e->key.s.ip)); ++ } else { ++ snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv4_prefixlen); ++ cidr.family = AF_INET; ++ cidr.type.in.s_addr = e->key.s.ip[0]; ++ } ++ msg_result = isc_netaddr_totext(&cidr, &lb); ++ if (msg_result != ISC_R_SUCCESS) ++ ADD_LOG_CSTR(&lb, "?"); ++ 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_NXDOMAIN) { ++ qbuf = get_qname(rrl, e); ++ if (save_qname && qbuf == NULL && ++ qname != NULL && dns_name_isabsolute(qname)) { ++ /* ++ * Capture the qname for the "stop limiting" message. ++ */ ++ qbuf = ISC_LIST_TAIL(rrl->qname_free); ++ if (qbuf != NULL) { ++ ISC_LIST_UNLINK(rrl->qname_free, qbuf, link); ++ } else if (rrl->num_qnames < DNS_RRL_QNAMES) { ++ qbuf = isc_mem_get(rrl->mctx, sizeof(*qbuf)); ++ if (qbuf != NULL) { ++ memset(qbuf, 0, sizeof(*qbuf)); ++ qbuf->index = rrl->num_qnames; ++ rrl->qnames[rrl->num_qnames++] = qbuf; ++ } else { ++ isc_log_write(dns_lctx, ++ DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, ++ DNS_RRL_LOG_FAIL, ++ "isc_mem_get(%d)" ++ " failed for RRL qname", ++ (int)sizeof(*qbuf)); ++ } ++ } ++ if (qbuf != NULL) { ++ e->log_qname = qbuf->index; ++ qbuf->e = e; ++ dns_fixedname_init(&qbuf->qname); ++ dns_name_copy(qname, ++ dns_fixedname_name(&qbuf->qname), ++ NULL); ++ } ++ } ++ if (qbuf != NULL) ++ qname = dns_fixedname_name(&qbuf->qname); ++ if (qname != NULL) { ++ ADD_LOG_CSTR(&lb, " for "); ++ dns_name_totext(qname, ISC_TRUE, &lb); ++ } else { ++ ADD_LOG_CSTR(&lb, " for (?)"); ++ } ++ if (e->key.s.rtype != DNS_RRL_RTYPE_NXDOMAIN) { ++ ADD_LOG_CSTR(&lb, " "); ++ dns_rdataclass_totext(e->key.s.qclass, &lb); ++ ADD_LOG_CSTR(&lb, " "); ++ dns_rdatatype_totext(e->key.s.qtype, &lb); ++ } ++ snprintf(strbuf, sizeof(strbuf), " (%08x)", ++ e->key.s.qname_hash); ++ add_log_str(&lb, strbuf, strlen(strbuf)); ++ } ++ ++ /* ++ * We saved room for '\0'. ++ */ ++ log_buf[isc_buffer_usedlength(&lb)] = '\0'; ++} ++ ++static void ++log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_boolean_t early, ++ char *log_buf, unsigned int log_buf_len) ++{ ++ if (e->logged) { ++ make_log_buf(rrl, e, ++ early ? "*" : NULL, ++ rrl->log_only ? "would stop limiting " ++ : "stop limiting ", ++ ISC_TRUE, NULL, ISC_FALSE, ++ DNS_RRL_RESULT_OK, ISC_R_SUCCESS, ++ log_buf, log_buf_len); ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, ++ "%s", log_buf); ++ free_qname(rrl, e); ++ e->logged = ISC_FALSE; ++ --rrl->num_logged; ++ } ++} ++ ++/* ++ * Log messages for streams that have stopped being rate limited. ++ */ ++static void ++log_stops(dns_rrl_t *rrl, isc_stdtime_t now, int limit, ++ char *log_buf, unsigned int log_buf_len) ++{ ++ dns_rrl_entry_t *e; ++ int age; ++ ++ for (e = rrl->last_logged; e != NULL; e = ISC_LIST_PREV(e, lru)) { ++ if (!e->logged) ++ continue; ++ if (now != 0) { ++ age = get_age(rrl, e, now); ++ if (age < DNS_RRL_STOP_LOG_SECS || ++ response_balance(rrl, e, age) < 0) ++ break; ++ } ++ ++ log_end(rrl, e, now == 0, log_buf, log_buf_len); ++ if (rrl->num_logged <= 0) ++ break; ++ ++ /* ++ * Too many messages could stall real work. ++ */ ++ if (--limit < 0) { ++ rrl->last_logged = ISC_LIST_PREV(e, lru); ++ return; ++ } ++ } ++ if (e == NULL) { ++ INSIST(rrl->num_logged == 0); ++ rrl->log_stops_time = now; ++ } ++ rrl->last_logged = e; ++} ++ ++/* ++ * Main rate limit interface. ++ */ ++dns_rrl_result_t ++dns_rrl(dns_view_t *view, ++ const isc_sockaddr_t *client_addr, isc_boolean_t is_tcp, ++ dns_rdataclass_t qclass, dns_rdatatype_t qtype, ++ dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now, ++ isc_boolean_t wouldlog, char *log_buf, unsigned int log_buf_len) ++{ ++ dns_rrl_t *rrl; ++ dns_rrl_rtype_t rtype; ++ dns_rrl_entry_t *e; ++ isc_netaddr_t netclient; ++ int secs; ++ double qps, scale; ++ int exempt_match; ++ isc_result_t result; ++ dns_rrl_result_t rrl_result; ++ ++ INSIST(log_buf != NULL && log_buf_len > 0); ++ ++ rrl = view->rrl; ++ if (rrl->exempt != NULL) { ++ isc_netaddr_fromsockaddr(&netclient, client_addr); ++ result = dns_acl_match(&netclient, NULL, rrl->exempt, ++ &view->aclenv, &exempt_match, NULL); ++ if (result == ISC_R_SUCCESS && exempt_match > 0) ++ return (DNS_RRL_RESULT_OK); ++ } ++ ++ LOCK(&rrl->lock); ++ ++ /* ++ * Estimate total query per second rate when scaling by qps. ++ */ ++ if (rrl->qps_scale == 0) { ++ qps = 0.0; ++ scale = 1.0; ++ } else { ++ ++rrl->qps_responses; ++ secs = delta_rrl_time(rrl->qps_time, now); ++ if (secs <= 0) { ++ qps = rrl->qps; ++ } else { ++ qps = (1.0*rrl->qps_responses) / secs; ++ if (secs >= rrl->window) { ++ if (isc_log_wouldlog(dns_lctx, ++ DNS_RRL_LOG_DEBUG3)) ++ isc_log_write(dns_lctx, ++ DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, ++ DNS_RRL_LOG_DEBUG3, ++ "%d responses/%d seconds" ++ " = %d qps", ++ rrl->qps_responses, secs, ++ (int)qps); ++ rrl->qps = qps; ++ rrl->qps_responses = 0; ++ rrl->qps_time = now; ++ } else if (qps < rrl->qps) { ++ qps = rrl->qps; ++ } ++ } ++ scale = rrl->qps_scale / qps; ++ } ++ ++ /* ++ * Do maintenance once per second. ++ */ ++ if (rrl->num_logged > 0 && rrl->log_stops_time != now) ++ log_stops(rrl, now, 8, log_buf, log_buf_len); ++ ++ /* ++ * Notice TCP responses when scaling limits by qps. ++ * Do not try to rate limit TCP responses. ++ */ ++ if (is_tcp) { ++ if (scale < 1.0) { ++ e = get_entry(rrl, client_addr, ++ 0, dns_rdatatype_none, NULL, ++ DNS_RRL_RTYPE_TCP, now, ISC_TRUE, ++ log_buf, log_buf_len); ++ if (e != NULL) { ++ e->responses = -(rrl->window+1); ++ set_age(rrl, e, now); ++ } ++ } ++ UNLOCK(&rrl->lock); ++ return (ISC_R_SUCCESS); ++ } ++ ++ /* ++ * 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) ++ rtype = DNS_RRL_RTYPE_QUERY; ++ else if (resp_result == DNS_R_DELEGATION) ++ rtype = DNS_RRL_RTYPE_DELEGATION; ++ else if (resp_result == DNS_R_NXDOMAIN) ++ rtype = DNS_RRL_RTYPE_NXDOMAIN; ++ else ++ rtype = DNS_RRL_RTYPE_ERROR; ++ e = get_entry(rrl, client_addr, qclass, qtype, qname, rtype, ++ now, ISC_TRUE, log_buf, log_buf_len); ++ if (e == NULL) { ++ UNLOCK(&rrl->lock); ++ return (DNS_RRL_RESULT_OK); ++ } ++ ++ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) { ++ /* ++ * Do not worry about speed or releasing the lock. ++ * This message appears before messages from debit_rrl_entry(). ++ */ ++ make_log_buf(rrl, e, "consider limiting ", NULL, ISC_FALSE, ++ qname, ISC_FALSE, DNS_RRL_RESULT_OK, resp_result, ++ log_buf, log_buf_len); ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, ++ "%s", log_buf); ++ } ++ ++ rrl_result = debit_rrl_entry(rrl, e, qps, scale, client_addr, now, ++ log_buf, log_buf_len); ++ ++ if (rrl->all_per_second != 0) { ++ /* ++ * We must debit the all-per-second token bucket if we have ++ * an all-per-second limit for the IP address. ++ * The all-per-second limit determines the log message ++ * when both limits are hit. ++ * The response limiting must continue if the ++ * all-per-second limiting lapses. ++ */ ++ dns_rrl_entry_t *e_all; ++ dns_rrl_result_t rrl_all_result; ++ ++ e_all = get_entry(rrl, client_addr, ++ 0, dns_rdatatype_none, NULL, ++ DNS_RRL_RTYPE_ALL, now, ISC_TRUE, ++ log_buf, log_buf_len); ++ if (e_all == NULL) { ++ UNLOCK(&rrl->lock); ++ return (DNS_RRL_RESULT_OK); ++ } ++ rrl_all_result = debit_rrl_entry(rrl, e_all, qps, scale, ++ client_addr, now, ++ log_buf, log_buf_len); ++ if (rrl_all_result != DNS_RRL_RESULT_OK) { ++ int level; ++ ++ e = e_all; ++ rrl_result = rrl_all_result; ++ if (rrl_result == DNS_RRL_RESULT_OK) ++ level = DNS_RRL_LOG_DEBUG2; ++ else ++ level = DNS_RRL_LOG_DEBUG1; ++ if (isc_log_wouldlog(dns_lctx, level)) { ++ make_log_buf(rrl, e, ++ "prefer all-per-second limiting ", ++ NULL, ISC_TRUE, qname, ISC_FALSE, ++ DNS_RRL_RESULT_OK, resp_result, ++ log_buf, log_buf_len); ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, level, ++ "%s", log_buf); ++ } ++ } ++ } ++ ++ if (rrl_result == DNS_RRL_RESULT_OK) { ++ UNLOCK(&rrl->lock); ++ return (DNS_RRL_RESULT_OK); ++ } ++ ++ /* ++ * Log occassionally in the rate-limit category. ++ */ ++ if ((!e->logged || e->log_secs >= DNS_RRL_MAX_LOG_SECS) && ++ isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP)) { ++ make_log_buf(rrl, e, rrl->log_only ? "would " : NULL, ++ e->logged ? "continue limiting " : "limit ", ++ ISC_TRUE, qname, ISC_TRUE, ++ DNS_RRL_RESULT_OK, resp_result, ++ log_buf, log_buf_len); ++ if (!e->logged) { ++ e->logged = ISC_TRUE; ++ if (++rrl->num_logged <= 1) ++ rrl->last_logged = e; ++ } ++ e->log_secs = 0; ++ /* ++ * Avoid holding the lock. ++ */ ++ if (!wouldlog) { ++ UNLOCK(&rrl->lock); ++ e = NULL; ++ } ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, ++ "%s", log_buf); ++ } ++ ++ /* ++ * Make a log message for the caller. ++ */ ++ if (wouldlog) ++ make_log_buf(rrl, e, rrl->log_only ? "would " : NULL, ++ NULL, ISC_FALSE, qname, ISC_FALSE, ++ rrl_result, resp_result, log_buf, log_buf_len); ++ ++ if (e != NULL) { ++ /* ++ * Do not save the qname unless we might needed it for ++ * the ending log message. ++ */ ++ if (!e->logged) ++ free_qname(rrl, e); ++ UNLOCK(&rrl->lock); ++ } ++ return (rrl_result); ++} ++ ++void ++dns_rrl_view_destroy(dns_view_t *view) { ++ dns_rrl_t *rrl; ++ dns_rrl_block_t *b; ++ dns_rrl_hash_t *h; ++ char log_buf[DNS_RRL_LOG_BUF_LEN]; ++ int i; ++ ++ rrl = view->rrl; ++ if (rrl == NULL) ++ return; ++ view->rrl = NULL; ++ ++ /* ++ * Assume the caller takes care of locking the view and anything else. ++ */ ++ ++ if (rrl->num_logged > 0) ++ log_stops(rrl, 0, ISC_INT32_MAX, log_buf, sizeof(log_buf)); ++ ++ for (i = 0; i < DNS_RRL_QNAMES; ++i) { ++ if (rrl->qnames[i] == NULL) ++ break; ++ isc_mem_put(rrl->mctx, rrl->qnames[i], sizeof(*rrl->qnames[i])); ++ } ++ ++ if (rrl->exempt != NULL) ++ dns_acl_detach(&rrl->exempt); ++ ++ DESTROYLOCK(&rrl->lock); ++ ++ while (!ISC_LIST_EMPTY(rrl->blocks)) { ++ b = ISC_LIST_HEAD(rrl->blocks); ++ ISC_LIST_UNLINK(rrl->blocks, b, link); ++ isc_mem_put(rrl->mctx, b, b->size); ++ } ++ ++ h = rrl->hash; ++ if (h != NULL) ++ isc_mem_put(rrl->mctx, h, ++ sizeof(*h)+(h->length-1)*sizeof(h->bins[0])); ++ ++ h = rrl->old_hash; ++ if (h != NULL) ++ isc_mem_put(rrl->mctx, h, ++ sizeof(*h)+(h->length-1)*sizeof(h->bins[0])); ++ ++ isc_mem_put(rrl->mctx, rrl, sizeof(*rrl)); ++} ++ ++isc_result_t ++dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries) { ++ dns_rrl_t *rrl; ++ isc_result_t result; ++ ++ *rrlp = NULL; ++ ++ rrl = isc_mem_get(view->mctx, sizeof(*rrl)); ++ if (rrl == NULL) ++ return (ISC_R_NOMEMORY); ++ memset(rrl, 0, sizeof(*rrl)); ++ rrl->mctx = view->mctx; ++ result = isc_mutex_init(&rrl->lock); ++ if (result != ISC_R_SUCCESS) { ++ isc_mem_put(view->mctx, rrl, sizeof(*rrl)); ++ return (result); ++ } ++ isc_stdtime_get(&rrl->ts_bases[0]); ++ ++ view->rrl = rrl; ++ ++ result = expand_entries(rrl, min_entries); ++ if (result != ISC_R_SUCCESS) { ++ dns_rrl_view_destroy(view); ++ return (result); ++ } ++ result = expand_rrl_hash(rrl, 0); ++ if (result != ISC_R_SUCCESS) { ++ dns_rrl_view_destroy(view); ++ return (result); ++ } ++ ++ *rrlp = rrl; ++ return (ISC_R_SUCCESS); ++} +diff -r -u lib/dns/view.c-orig lib/dns/view.c +--- lib/dns/view.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/view.c 2004-01-01 00:00:00.000000000 +0000 +@@ -48,6 +48,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -181,6 +182,7 @@ + view->answeracl_exclude = NULL; + view->denyanswernames = NULL; + view->answernames_exclude = NULL; ++ view->rrl = NULL; + view->provideixfr = ISC_TRUE; + view->maxcachettl = 7 * 24 * 3600; + view->maxncachettl = 3 * 3600; +@@ -331,9 +333,11 @@ + dns_acache_detach(&view->acache); + } + dns_rpz_view_destroy(view); ++ dns_rrl_view_destroy(view); + #else + INSIST(view->acache == NULL); + INSIST(ISC_LIST_EMPTY(view->rpz_zones)); ++ INSIST(view->rrl == NULL); + #endif + if (view->requestmgr != NULL) + dns_requestmgr_detach(&view->requestmgr); +diff -r -u lib/dns/win32/libdns.def-orig lib/dns/win32/libdns.def +--- lib/dns/win32/libdns.def-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/win32/libdns.def 2004-01-01 00:00:00.000000000 +0000 +@@ -654,6 +654,9 @@ + dns_rriterator_next + dns_rriterator_nextrrset + dns_rriterator_pause ++dns_rrl ++dns_rrl_init ++dns_rrl_view_destroy + dns_sdb_putnamedrr + dns_sdb_putrdata + dns_sdb_putrr +diff -r -u lib/dns/win32/libdns.dsp-orig lib/dns/win32/libdns.dsp +--- lib/dns/win32/libdns.dsp-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/win32/libdns.dsp 2004-01-01 00:00:00.000000000 +0000 +@@ -346,6 +346,10 @@ + # End Source File + # Begin Source File + ++SOURCE=..\include\dns\rrl.h ++# End Source File ++# Begin Source File ++ + SOURCE=..\include\dns\rriterator.h + # End Source File + # Begin Source File +@@ -650,6 +654,10 @@ + # End Source File + # Begin Source File + ++SOURCE=..\rrl.c ++# End Source File ++# Begin Source File ++ + SOURCE=..\rriterator.c + # End Source File + # Begin Source File +diff -r -u lib/dns/win32/libdns.mak-orig lib/dns/win32/libdns.mak +--- lib/dns/win32/libdns.mak-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/win32/libdns.mak 2004-01-01 00:00:00.000000000 +0000 +@@ -184,6 +184,7 @@ + -@erase "$(INTDIR)\result.obj" + -@erase "$(INTDIR)\rootns.obj" + -@erase "$(INTDIR)\rpz.obj" ++ -@erase "$(INTDIR)\rrl.obj" + -@erase "$(INTDIR)\sdb.obj" + -@erase "$(INTDIR)\sdlz.obj" + -@erase "$(INTDIR)\soa.obj" +@@ -309,6 +310,7 @@ + "$(INTDIR)\result.obj" \ + "$(INTDIR)\rootns.obj" \ + "$(INTDIR)\rpz.obj" \ ++ "$(INTDIR)\rrl.obj" \ + "$(INTDIR)\rriterator.obj" \ + "$(INTDIR)\sdb.obj" \ + "$(INTDIR)\sdlz.obj" \ +@@ -505,6 +507,8 @@ + -@erase "$(INTDIR)\rootns.sbr" + -@erase "$(INTDIR)\rpz.obj" + -@erase "$(INTDIR)\rpz.sbr" ++ -@erase "$(INTDIR)\rrl.obj" ++ -@erase "$(INTDIR)\rrl.sbr" + -@erase "$(INTDIR)\rriterator.obj" + -@erase "$(INTDIR)\rriterator.sbr" + -@erase "$(INTDIR)\sdb.obj" +@@ -651,6 +655,7 @@ + "$(INTDIR)\result.sbr" \ + "$(INTDIR)\rootns.sbr" \ + "$(INTDIR)\rpz.sbr" \ ++ "$(INTDIR)\rrl.sbr" \ + "$(INTDIR)\rriterator.sbr" \ + "$(INTDIR)\sdb.sbr" \ + "$(INTDIR)\sdlz.sbr" \ +@@ -748,6 +753,7 @@ + "$(INTDIR)\result.obj" \ + "$(INTDIR)\rootns.obj" \ + "$(INTDIR)\rpz.obj" \ ++ "$(INTDIR)\rrl.obj" \ + "$(INTDIR)\rriterator.obj" \ + "$(INTDIR)\sdb.obj" \ + "$(INTDIR)\sdlz.obj" \ +@@ -1726,6 +1732,24 @@ + + !ENDIF + ++SOURCE=..\rrl.c ++ ++!IF "$(CFG)" == "libdns - Win32 Release" ++ ++ ++"$(INTDIR)\rrl.obj" : $(SOURCE) "$(INTDIR)" ++ $(CPP) $(CPP_PROJ) $(SOURCE) ++ ++ ++!ELSEIF "$(CFG)" == "libdns - Win32 Debug" ++ ++ ++"$(INTDIR)\rrl.obj" "$(INTDIR)\rrl.sbr" : $(SOURCE) "$(INTDIR)" ++ $(CPP) $(CPP_PROJ) $(SOURCE) ++ ++ ++!ENDIF ++ + SOURCE=..\rriterator.c + + !IF "$(CFG)" == "libdns - Win32 Release" +diff -r -u lib/isccfg/namedconf.c-orig lib/isccfg/namedconf.c +--- lib/isccfg/namedconf.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/isccfg/namedconf.c 2004-01-01 00:00:00.000000000 +0000 +@@ -1244,6 +1244,39 @@ + }; + + ++/* ++ * rate-limit ++ */ ++static cfg_clausedef_t rrl_clauses[] = { ++ { "responses-per-second", &cfg_type_uint32, 0 }, ++ { "errors-per-second", &cfg_type_uint32, 0 }, ++ { "nxdomains-per-second", &cfg_type_uint32, 0 }, ++ { "responses-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 }, ++ { "exempt-clients", &cfg_type_bracketed_aml, 0 }, ++ { "max-table-size", &cfg_type_uint32, 0 }, ++ { "min-table-size", &cfg_type_uint32, 0 }, ++ { NULL, NULL, 0 } ++}; ++ ++static cfg_clausedef_t *rrl_clausesets[] = { ++ rrl_clauses, ++ NULL ++}; ++ ++static cfg_type_t cfg_type_rrl = { ++ "rate-limit", cfg_parse_map, cfg_print_map, cfg_doc_map, ++ &cfg_rep_map, rrl_clausesets ++}; ++ ++ ++ + /*% + * dnssec-lookaside + */ +@@ -1397,6 +1430,7 @@ + CFG_CLAUSEFLAG_NOTCONFIGURED }, + #endif + { "response-policy", &cfg_type_rpz, 0 }, ++ { "rate-limit", &cfg_type_rrl, 0 }, + { NULL, NULL, 0 } + }; + +diff -r -u version-orig version +--- version-orig 2004-01-01 00:00:00.000000000 +0000 ++++ version 2004-01-01 00:00:00.000000000 +0000 +@@ -5,6 +5,6 @@ + # + MAJORVER=9 + MINORVER=9 +-PATCHVER=2 ++PATCHVER=2-rl.028.23 + RELEASETYPE=-P + RELEASEVER=1