From 7f803cee739fc07b2553c83a2483d2ea88952101a8f3af14b766757c2c73dabe Mon Sep 17 00:00:00 2001 From: Marcus Meissner Date: Wed, 26 Jun 2013 12:27:48 +0000 Subject: [PATCH] - Updated to current rate limiting + rpz patch from http://ss.vix.su/~vjs/rrlrpz.html OBS-URL: https://build.opensuse.org/package/show/network/bind?expand=0&rev=118 --- Makefile.in.diff | 9 +- bind.changes | 2 + bind.spec | 4 +- configure.in.diff | 10 +- rl-9.9.2p1.patch | 2974 ----------- rpz2+rl-9.9.3-P1.patch | 10452 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 10465 insertions(+), 2986 deletions(-) delete mode 100644 rl-9.9.2p1.patch create mode 100644 rpz2+rl-9.9.3-P1.patch diff --git a/Makefile.in.diff b/Makefile.in.diff index 382d315..f99f64a 100644 --- a/Makefile.in.diff +++ b/Makefile.in.diff @@ -1,8 +1,8 @@ -Index: bind-9.8.1-P1/bin/named/Makefile.in +Index: bind-9.9.3-P1/bin/named/Makefile.in =================================================================== ---- bind-9.8.1-P1.orig/bin/named/Makefile.in -+++ bind-9.8.1-P1/bin/named/Makefile.in -@@ -162,8 +162,6 @@ installdirs: +--- bind-9.9.3-P1.orig/bin/named/Makefile.in ++++ bind-9.9.3-P1/bin/named/Makefile.in +@@ -175,9 +175,7 @@ installdirs: install:: named@EXEEXT@ lwresd@EXEEXT@ installdirs ${LIBTOOL_MODE_INSTALL} ${INSTALL_PROGRAM} named@EXEEXT@ ${DESTDIR}${sbindir} (cd ${DESTDIR}${sbindir}; rm -f lwresd@EXEEXT@; @LN@ named@EXEEXT@ lwresd@EXEEXT@) @@ -12,3 +12,4 @@ Index: bind-9.8.1-P1/bin/named/Makefile.in + for m in ${MANPAGES}; do ${INSTALL_DATA} ${srcdir}/$$m ${DESTDIR}${mandir}/man$${m##*.}; done @DLZ_DRIVER_RULES@ + diff --git a/bind.changes b/bind.changes index 066b63d..35e3570 100644 --- a/bind.changes +++ b/bind.changes @@ -23,6 +23,8 @@ Mon Jun 24 13:17:11 UTC 2013 - meissner@suse.com - [maint] Added AAAA for D.ROOT-SERVERS.NET. - [maint] D.ROOT-SERVERS.NET is now 199.7.91.13. +- Updated to current rate limiting + rpz patch from + http://ss.vix.su/~vjs/rrlrpz.html ------------------------------------------------------------------- Wed May 8 08:21:52 UTC 2013 - schwab@suse.de diff --git a/bind.spec b/bind.spec index f218989..a536132 100644 --- a/bind.spec +++ b/bind.spec @@ -65,8 +65,8 @@ BuildRequires: gpg-offline # Rate limiting patch by Paul Vixie et.al. for reflection DoS protection # see http://www.redbarn.org/dns/ratelimits -#Patch200: http://ss.vix.com/~vixie/rl-9.9.2.patch -Patch200: rl-9.9.2p1.patch +#Patch200: http://ss.vix.su/~vjs/rpz2+rl-9.9.3-P1.patch +Patch200: rpz2+rl-9.9.3-P1.patch Source60: dlz-schema.txt %if %ul_version >= 1 diff --git a/configure.in.diff b/configure.in.diff index 4c229d8..f1c4e92 100644 --- a/configure.in.diff +++ b/configure.in.diff @@ -1,10 +1,8 @@ -Index: bind-9.8.1-P1/configure.in -=================================================================== ---- bind-9.8.1-P1.orig/configure.in -+++ bind-9.8.1-P1/configure.in -@@ -2907,7 +2907,7 @@ AC_SUBST(DOXYGEN) +--- bind-9.9.3-P1/configure.in.xx 2013-06-26 14:23:25.536177163 +0200 ++++ bind-9.9.3-P1/configure.in 2013-06-26 14:23:26.401175186 +0200 +@@ -3099,7 +3099,7 @@ # empty). The variable VARIABLE will be substituted into output files. - # + # -AC_DEFUN(NOM_PATH_FILE, [ +AC_DEFUN([NOM_PATH_FILE], [ diff --git a/rl-9.9.2p1.patch b/rl-9.9.2p1.patch deleted file mode 100644 index caf5e80..0000000 --- a/rl-9.9.2p1.patch +++ /dev/null @@ -1,2974 +0,0 @@ -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, rcode, 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,17 @@ - notify no;\n\ - allow-new-zones no;\n\ - \n\ -+ # Prevent use of this zone in DNS amplified reflection DoS attacks\n\ -+ # Notice the size of the authors.bind response.\n\ -+ rate-limit {\n\ -+ responses-per-second 1;\n\ -+ window 10;\n\ -+ slip 0;\n\ -+ IPv4-prefix-length 16;\n\ -+ IPv6-prefix-length 32;\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 -@@ -5746,6 +5746,104 @@ - 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; -+ dns_rcode_t rcode; -+ 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); -+ rcode = dns_rcode_nxdomain; -+ } 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); -+ } -+ rcode = dns_rcode_nxdomain; -+ } else { -+ rcode = dns_rcode_noerror; -+ } -+ rrl_result = dns_rrl(client->view, &client->peeraddr, -+ ISC_TF((client->attributes -+ & NS_CLIENTATTR_TCP) != 0), -+ client->message->rdclass, qtype, tname, -+ rcode, 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) && -@@ -7168,12 +7266,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,201 @@ - return (result); - } - -+#define CHECK_RRL(obj, cond, pat, val) \ -+ do { \ -+ if (!(cond)) { \ -+ cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, \ -+ pat, val); \ -+ 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 = 1000; -+ obj = NULL; -+ result = cfg_map_get(map, "min-table-size", &obj); -+ if (result == ISC_R_SUCCESS) { -+ min_entries = cfg_obj_asuint32(obj); -+ CHECK_RRL(obj, min_entries > 1, -+ "invalid '{min-table-size %d;}'", min_entries); -+ } -+ result = dns_rrl_init(&rrl, view, min_entries); -+ if (result != ISC_R_SUCCESS) -+ return (result); -+ -+ i = ISC_MAX(10000, 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, -+ "invalid '{max-table-size %d;}'", i); -+ } -+ rrl->max_entries = i; -+ -+ 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, -+ "invalid '{responses-per-second %d;}'", i); -+ } -+ 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, -+ "invalid '{errors-per-second %d;}'", i); -+ } -+ 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, -+ "invalid '{nxdomains-per-second %d;}'", i); -+ } -+ 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, -+ "invalid '{all-per-second %d;}'", i); -+ CHECK_RRL(obj, i == 0 || (i >= rrl->responses_per_second*4 && -+ i >= rrl->errors_per_second*4 && -+ i >= rrl->nxdomains_per_second*4), -+ "'{all-per-second %d;}' must be" -+ " at least 4 times responses-per-second," -+ "errors_per_second, and nxdomains_per_second", -+ i); -+ } -+ 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, "invalid '{slip %d;}'", i); -+ } -+ 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, -+ "invalid '{window %d;}'", i); -+ } -+ 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;}'", 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;}'", 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 <= 128, -+ "invalid '{IPv6-prefix-length %d;}'", i); -+ } -+ rrl->ipv6_prefixlen = i; -+ memset(rrl->ipv6_mask, 0xff, sizeof(rrl->ipv6_mask)); -+ 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)); -+ i = 0; -+ } 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", "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 +3120,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/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,22 @@ -+# Copyright (C) 2012 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. -+ -+# $Id$ -+ -+ -+# Clean up after rrl tests. -+ -+rm -f dig.out* -+rm -f */named.memstats */named.run ns*/log* */named.rpz */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,33 @@ -+/* -+ * Copyright (C) 2012 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. -+ */ -+ -+/* $Id$ */ -+ -+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,32 @@ -+; Copyright (C) 2012 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. -+ -+; $Id$ -+ -+$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,19 @@ -+; Copyright (C) 2012 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. -+ -+; $Id$ -+ -+ -+. 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,64 @@ -+/* -+ * Copyright (C) 2012 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. -+ */ -+ -+/* $Id$ */ -+ -+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"; -+ 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; -+ }; -+}; -+ -+/* -+ * 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,43 @@ -+; Copyright (C) 2012 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. -+ -+; $Id$ -+ -+ -+; 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,19 @@ -+; Copyright (C) 2012 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. -+ -+; $Id$ -+ -+ -+. 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,35 @@ -+/* -+ * Copyright (C) 2012 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. -+ */ -+ -+/* $Id$ */ -+ -+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,26 @@ -+; Copyright (C) 2012 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. -+ -+; $Id$ -+ -+ -+; 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,22 @@ -+#!/bin/sh -+# -+# Copyright (C) 2012 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. -+ -+# $Id$ -+ -+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,210 @@ -+# Copyright (C) 2012 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. -+ -+# $Id$ -+ -+# test response rate limiting -+ -+SYSTEMTESTTOP=.. -+. $SYSTEMTESTTOP/conf.sh -+ -+#set -x -+ -+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="0$CNT" -+ fi -+ if test $CNT -lt 100; then -+ CNT="0$CNT" -+ fi -+ eval BURST_DOM="$BURST_DOM_BASE" -+ FILE="dig.out-$BURST_DOM-$CNT" -+ rm -f $FILE=* -+ 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 9 19 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 SERVFAIL is reached. -+ck_result a7.tld4 192.168.2.1 0 5 13 0 2 -+ -+ -+######### -+sec_start -+ -+# all-per-second -+CNT=101 -+burst 80 'all$CNT.a8.tld2' -+ck_result 'a*.a8.tld2' 192.168.2.8 70 3 7 0 0 -+ -+ -+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,24 @@ - - - -+ -+ -+ rate-limit -+ -+ -+ -+ The start, periodic, and final notices of rate limiting -+ of a stream of responses are logged at -+ info severity in this category. -+ Various internal performance data such as expansions -+ of the table is logged 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 +5352,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 +9770,204 @@ - 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 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 their owner name, the nearest valid domain name 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 of third parties. -+ slip must be between 0 and 10. -+ 0 "slips" or sends no rate limiting truncated responses. -+ Some error responses cannot be replaced with responses -+ with the TC flag, and so are instead -+ leaked at the slip rate. -+ -+ -+ -+ When the approximate query per second rate exceeds -+ the qps-scale value, -+ the responses-per-second, -+ errors-per-second, -+ nxdomains-per-second, -+ and slip 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. -+ The limits for IP addresses using TCP are not reduced. -+ Responses sent via TCP are not subject to rate limits -+ but are counted to approximate 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 similar to the rate limiting offered by -+ firewalls. When performed in a DNS server it is inferior to -+ the other rate-limit forms, because it ignores -+ the contents of responses to a block of IP addresses. -+ The rate limiting provided by -+ responses-per-second, -+ errors-per-second, and -+ nxdomains-per-second on a DNS server -+ is 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. -+ A all-per-second limit must 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. -+ -+ -+ -+ The maximum size of the table used to track requests and so -+ rate limit responses is set with max-table-size. -+ Each entry in the table is between 40 and 80 bytes. -+ The default of 10,000 is suitable for a server receiving -+ 5000 DNS requests/second. -+ 10,000 entries require about 1 megabyte. -+ To reduce cold start costs including those in growing the -+ table, min-table-size (default 1000) -+ can set the minimum table size. -+ Enable logging to monitor expansions of the table and inform -+ non-default 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 +14616,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,226 @@ -+/* -+ * Copyright (C) 2012 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. -+ */ -+ -+/* $Id$ */ -+ -+#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) -+/* -+ * 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, -+ DNS_RRL_RTYPE_QUERY, -+ 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. -+ */ -+typedef struct dns_rrl_key dns_rrl_key_t; -+struct dns_rrl_key { -+ isc_uint32_t ip[4]; -+ isc_uint32_t qname_hash; -+ dns_rdatatype_t qtype; -+ dns_rrl_rtype_t rtype :3; -+ isc_boolean_t qclass :3; -+ isc_boolean_t ipv6 :1; -+}; -+ -+/* -+ * A rate-limit entry. -+ * This should be small to limit the total size of the database. -+ * With gcc on ARM, the key should have __attribute((__packed__)) to -+ * avoid padding to a multiple of 8 bytes. -+ */ -+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_bin_t *bin; -+ isc_stdtime_t last_used; -+ isc_int32_t responses; -+# define DNS_RRL_MAX_WINDOW 600 -+# define DNS_RRL_MAX_RATE (ISC_INT32_MAX / DNS_RRL_MAX_WINDOW) -+ dns_rrl_key_t key; -+ unsigned int slip_cnt :4; -+# define DNS_RRL_MAX_SLIP 10 -+ unsigned int log_secs :10; -+# define DNS_RRL_MAX_LOG_SECS 600 -+# define DNS_RRL_STOP_LOG_SECS 60 -+ isc_boolean_t logged :1; -+ unsigned int log_qname :8; -+# define DNS_RRL_NUM_QNAMES 256 -+}; -+ -+/* -+ * A hash table of rate-limit entries. -+ */ -+struct dns_rrl_hash { -+ isc_stdtime_t check_time; -+ int length; -+ dns_rrl_bin_t bins[1]; -+}; -+ -+/* -+ * A block of rate-limit entries. -+ */ -+typedef struct dns_rrl_block dns_rrl_block_t; -+struct dns_rrl_block { -+ ISC_LINK(dns_rrl_block_t) link; -+ int size; -+ dns_rrl_entry_t entries[1]; -+}; -+ -+/* -+ * A rate limited qname buffers. -+ */ -+typedef struct dns_rrl_qname_buf dns_rrl_qname_buf_t; -+struct dns_rrl_qname_buf { -+ ISC_LINK(dns_rrl_qname_buf_t) link; -+ const dns_rrl_entry_t *e; -+ unsigned int index; -+ dns_fixedname_t qname; -+}; -+ -+/* -+ * Per-view query rate limit parameters and a pointer to database. -+ */ -+typedef struct dns_rrl dns_rrl_t; -+struct dns_rrl { -+ isc_mutex_t lock; -+ isc_mem_t *mctx; -+ -+ isc_boolean_t log_only; -+ int responses_per_second; -+ int errors_per_second; -+ int nxdomains_per_second; -+ int all_per_second; -+ int window; -+ int slip; -+ double qps_scale; -+ int max_entries; -+ -+ dns_acl_t *exempt; -+ -+ int num_entries; -+ -+ int qps_responses; -+ isc_stdtime_t qps_time; -+ double qps; -+ int scaled_responses_per_second; -+ int scaled_errors_per_second; -+ int scaled_nxdomains_per_second; -+ int scaled_all_per_second; -+ int scaled_slip; -+ -+ isc_stdtime_t prune_time; -+ -+ unsigned int probes; -+ unsigned int searches; -+ -+ ISC_LIST(dns_rrl_block_t) blocks; -+ ISC_LIST(dns_rrl_entry_t) lru; -+ -+ dns_rrl_hash_t *hash; -+ dns_rrl_hash_t *old_hash; -+ -+ int ipv4_prefixlen; -+ isc_uint32_t ipv4_mask; -+ int ipv6_prefixlen; -+ isc_uint32_t ipv6_mask[4]; -+ -+ dns_rrl_entry_t *log_ended; -+ ISC_LIST(dns_rrl_qname_buf_t) qname_free; -+ int num_qnames; -+ dns_rrl_qname_buf_t *qnames[DNS_RRL_NUM_QNAMES]; -+}; -+ -+typedef enum { -+ DNS_RRL_RESULT_OK, -+ DNS_RRL_RESULT_DROP, -+ DNS_RRL_RESULT_SLIP, -+} dns_rrl_result_t; -+ -+dns_rrl_result_t -+dns_rrl(dns_view_t *view, -+ const isc_sockaddr_t *client_addr, isc_boolean_t is_tcp, -+ dns_rdataclass_t rdclass, dns_rdatatype_t qtype, -+ dns_name_t *qname, dns_rcode_t rcode, isc_stdtime_t now, -+ isc_boolean_t wouldlog, char *log_buf, unsigned int log_buf_len); -+ -+void -+dns_rrl_view_destroy(dns_view_t *view); -+ -+isc_result_t -+dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries); -+ -+ISC_LANG_ENDDECLS -+ -+#endif /* DNS_RRL_H */ -diff -r -u lib/dns/include/dns/view.h-orig lib/dns/include/dns/view.h ---- lib/dns/include/dns/view.h-orig 2004-01-01 00:00:00.000000000 +0000 -+++ lib/dns/include/dns/view.h 2004-01-01 00:00:00.000000000 +0000 -@@ -73,6 +73,7 @@ - - #include - #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,1242 @@ -+/* -+ * Copyright (C) 2012 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. -+ */ -+ -+/* $Id$ */ -+ -+/*! \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, -+ 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 < -5) -+ return (now); -+ return (0); -+} -+ -+static isc_result_t -+add_rrl_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); -+ } -+ -+ /* -+ * Try to 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; -+ rrl->log_ended = e; -+ 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_rrl_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; -+ -+ old_hash = rrl->old_hash; -+ for (old_bin = &old_hash->bins[0]; -+ old_bin < &old_hash->bins[old_hash->length]; -+ ++old_bin) { -+ while ((e = ISC_LIST_HEAD(*old_bin)) != NULL) { -+ ISC_LIST_UNLINK(*e->bin, e, hlink); -+ e->bin = NULL; -+ } -+ } -+ -+ 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; -+ -+ if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1) && 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_DEBUG1, -+ "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 -+rrl_entry_ref(dns_rrl_t *rrl, dns_rrl_entry_t *e, dns_rrl_bin_t *new_bin, -+ int probes, isc_stdtime_t now) -+{ -+ /* -+ * Make the entry most recently used. -+ */ -+ if (ISC_LIST_HEAD(rrl->lru) != e) { -+ if (e == rrl->log_ended) { -+ if (e->lru.next != NULL) -+ rrl->log_ended = e->lru.next; -+ else -+ rrl->log_ended = e->lru.prev; -+ } -+ ISC_LIST_UNLINK(rrl->lru, e, lru); -+ ISC_LIST_PREPEND(rrl->lru, e, lru); -+ } -+ -+ /* -+ * Move the entry to the head of its hash chain. -+ */ -+ if (ISC_LIST_HEAD(*new_bin) != e) { -+ if (e->bin != NULL) -+ ISC_LIST_UNLINK(*e->bin, e, hlink); -+ ISC_LIST_PREPEND(*new_bin, e, hlink); -+ e->bin = new_bin; -+ } -+ -+ /* -+ * 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) >= 10) { -+ 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 -+rrl_key_cmp(const dns_rrl_key_t *a, const dns_rrl_key_t *b) { -+ return (memcmp(a, b, sizeof(dns_rrl_key_t)) == 0 ? ISC_TRUE : ISC_FALSE); -+} -+ -+/* -+ * Construct the database 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 isc_uint32_t -+make_key(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) -+{ -+ isc_uint32_t hval; -+ int i; -+ -+ memset(key, 0, sizeof(*key)); -+ -+ key->rtype = rtype; -+ hval = rtype; -+ if (rtype == DNS_RRL_RTYPE_QUERY || -+ rtype == DNS_RRL_RTYPE_NXDOMAIN) { -+ /* -+ * Map dns_rdataclass_reserved0 = 0 -> 2 -+ * dns_rdataclass_in = 1 -> 3 -+ * dns_rdataclass_chaos = 3 -> 5 -+ * dns_rdataclass_hs = 4 -> 6 -+ * dns_rdataclass_none = 254 -> 0 -+ * dns_rdataclass_any = 255 -> 1 -+ * and trust that there will never be significant changes. -+ */ -+ key->qclass = qclass+2; -+ key->qtype = qtype; -+ hval += qtype<<8; -+ } -+ -+ if (qname != NULL && qname->labels != 0) { -+ /* -+ * Ignore the first label of wildcards. -+ */ -+ if ((qname->attributes & DNS_NAMEATTR_WILDCARD) != 0 && -+ (i = dns_name_countlabels(qname)) > 1) { -+ dns_fixedname_t suffixf; -+ dns_name_t *suffix; -+ -+ dns_fixedname_init(&suffixf); -+ suffix = dns_fixedname_name(&suffixf); -+ dns_name_split(qname, i-1, NULL, suffix); -+ key->qname_hash = dns_name_hashbylabel(suffix, -+ ISC_FALSE); -+ } else { -+ key->qname_hash = dns_name_hashbylabel(qname, -+ ISC_FALSE); -+ } -+ hval += key->qname_hash; -+ } -+ -+ switch (client_addr->type.sa.sa_family) { -+ case AF_INET: -+ key->ip[3] = (client_addr->type.sin.sin_addr.s_addr & -+ rrl->ipv4_mask); -+ hval = (hval>>31) + (hval<<1) + key->ip[3]; -+ break; -+ case AF_INET6: -+ key->ipv6 = ISC_TRUE; -+ memcpy(key->ip, &client_addr->type.sin6.sin6_addr, -+ sizeof(key->ip)); -+ for (i = 0; i < 4; ++i) { -+ key->ip[i] &= rrl->ipv6_mask[i]; -+ hval = (hval>>31) + (hval<<1) + key->ip[i]; -+ } -+ break; -+ } -+ -+ return (hval); -+} -+ -+static inline int -+response_balance(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) { -+ int balance; -+ -+ balance = e->responses; -+ if (balance < 0) -+ switch (e->key.rtype) { -+ case DNS_RRL_RTYPE_QUERY: -+ balance += age * rrl->responses_per_second; -+ break; -+ case DNS_RRL_RTYPE_NXDOMAIN: -+ balance += age * rrl->nxdomains_per_second; -+ break; -+ case DNS_RRL_RTYPE_ERROR: -+ balance += age * rrl->errors_per_second; -+ break; -+ case DNS_RRL_RTYPE_ALL: -+ balance += age * rrl->all_per_second; -+ break; -+ case DNS_RRL_RTYPE_TCP: -+ balance += age; -+ break; -+ default: -+ INSIST(0); -+ } -+ return (balance); -+} -+ -+/* -+ * Search for an entry for a response and optionally create it. -+ */ -+static dns_rrl_entry_t * -+get_rrl_entry(dns_rrl_t *rrl, const isc_sockaddr_t *client_addr, -+ dns_rdatatype_t qtype, dns_name_t *qname, dns_rdataclass_t qclass, -+ 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_hash_t *hash, *old_hash; -+ dns_rrl_entry_t *e; -+ dns_rrl_bin_t *new_bin, *old_bin; -+ int probes, age; -+ -+ hval = make_key(rrl, &key, client_addr, qtype, qname, qclass, rtype); -+ -+ /* -+ * Look for the entry in the current hash table. -+ */ -+ hash = rrl->hash; -+ new_bin = get_rrl_bin(hash, hval); -+ for (e = ISC_LIST_HEAD(*new_bin), probes = 1; -+ e != NULL; -+ e = ISC_LIST_NEXT(e, hlink), ++probes) { -+ if (rrl_key_cmp(&e->key, &key)) { -+ rrl_entry_ref(rrl, e, new_bin, probes, now); -+ return (e); -+ } -+ } -+ -+ /* -+ * Look in the old hash table if we did not find the entry. -+ */ -+ old_hash = rrl->old_hash; -+ if (old_hash != NULL) { -+ old_bin = get_rrl_bin(old_hash, hval); -+ for (e = ISC_LIST_HEAD(*old_bin); -+ e != NULL; -+ e = ISC_LIST_NEXT(e, hlink)) { -+ if (rrl_key_cmp(&e->key, &key)) { -+ rrl_entry_ref(rrl, e, new_bin, probes, now); -+ return (e); -+ } -+ } -+ -+ /* -+ * Discard prevous hash table when its entries are all old. -+ */ -+ if (delta_rrl_time(old_hash->check_time, now) > rrl->window) -+ free_old_hash(rrl); -+ } -+ -+ if (!create) -+ return (NULL); -+ -+ /* -+ * The block does not already exist, so create it. -+ * Unroll the first circuit of the loop to cover most cases. -+ * Immediately a new create entry if the oldest is fresh. -+ * Preserve penalized entries. -+ * Try to make more entries if none are idle. -+ * Steal the oldest entry if we cannot make more. -+ */ -+ e = ISC_LIST_TAIL(rrl->lru); -+ age = delta_rrl_time(e->last_used, now); -+ if (age <= rrl->window) { -+ for (;;) { -+ if (age <= 1) { -+ add_rrl_entries(rrl, -+ ISC_MIN((rrl->num_entries+1)/2, -+ 1000)); -+ e = ISC_LIST_TAIL(rrl->lru); -+ break; -+ } -+ if (response_balance(rrl, e, age) >= 0) -+ break; -+ -+ e = e->lru.prev; -+ if (e == NULL) { -+ add_rrl_entries(rrl, -+ ISC_MIN((rrl->num_entries+1)/2, -+ 1000)); -+ e = ISC_LIST_TAIL(rrl->lru); -+ break; -+ } -+ age = delta_rrl_time(e->last_used, now); -+ } -+ } -+ if (e->logged) -+ log_end(rrl, e, log_buf, log_buf_len); -+ e->key = key; -+ e->last_used = 0; -+ rrl_entry_ref(rrl, e, new_bin, probes, now); -+ return (e); -+} -+ -+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; -+ dns_rrl_result_t rrl_result; -+ -+ /* -+ * Pick the rate counter. Optionally adjust the rates by the estimated -+ * query/second rate. -+ */ -+ switch (e->key.rtype) { -+ case DNS_RRL_RTYPE_QUERY: -+ 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_rrl_entry(rrl, client_addr, -+ dns_rdatatype_none, NULL, 0, -+ DNS_RRL_RTYPE_TCP, now, ISC_FALSE, -+ log_buf, log_buf_len); -+ if (credit_e != NULL) { -+ age = delta_rrl_time(credit_e->last_used, 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.rtype) { -+ case DNS_RRL_RTYPE_QUERY: -+ 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; -+ default: -+ INSIST(0); -+ } -+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, -+ DNS_LOGMODULE_REQUEST, -+ DNS_RRL_LOG_DEBUG1, -+ "%d qps scaled %s by %.2f" -+ " from %d to %d", -+ (int)qps, rate_str, scale, -+ rate, new_rate); -+ } -+ rate = new_rate; -+ *ratep = rate; -+ } -+ } -+ -+ min = -rrl->window * rate; -+ -+ /* -+ * Treat time jumps into the past as no time. -+ * Treat entries older than the window as if they were just created -+ * Credit other entries. -+ */ -+ rrl_result = DNS_RRL_RESULT_DROP; -+ age = delta_rrl_time(e->last_used, 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 should be reset when an entry is create (or -+ * recycled) and after at least one second without limiting. -+ * 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; -+ } -+ } -+ e->last_used = now; -+ -+ 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, -+ "rrl age=%d responses=%d", age, e->responses); -+ -+ /* -+ * Debit the entry for this response. -+ */ -+ if (--e->responses >= 0) -+ return (DNS_RRL_RESULT_OK); -+ -+ if (e->responses < min) -+ e->responses = min; -+ -+ /* -+ * Drop this response unless it should 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->slip_cnt >= slip) { -+ e->slip_cnt = 0; -+ return (DNS_RRL_RESULT_SLIP); -+ } -+ -+ return (rrl_result); -+} -+ -+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_rrl_result_t rrl_result, -+ dns_name_t *qname, isc_boolean_t save_qname, dns_rcode_t rcode, -+ 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)"))]; -+ 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.rtype) { -+ case DNS_RRL_RTYPE_QUERY: -+ case DNS_RRL_RTYPE_ALL: -+ break; -+ case DNS_RRL_RTYPE_NXDOMAIN: -+ ADD_LOG_CSTR(&lb, "NXDOMAIN "); -+ break; -+ case DNS_RRL_RTYPE_ERROR: -+ if (rcode == dns_rcode_noerror) { -+ ADD_LOG_CSTR(&lb, "error "); -+ } else { -+ msg_result = dns_rcode_totext(rcode, &lb); -+ if (msg_result == ISC_R_SUCCESS) { -+ ADD_LOG_CSTR(&lb, " "); -+ } else { -+ ADD_LOG_CSTR(&lb, "UNKNOWN RCODE "); -+ } -+ } -+ break; -+ default: -+ INSIST(0); -+ } -+ -+ if (plural) -+ ADD_LOG_CSTR(&lb, "responses to "); -+ else -+ ADD_LOG_CSTR(&lb, "response to "); -+ -+ memset(&cidr, 0, sizeof(cidr)); -+ if (e->key.ipv6) { -+ snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv6_prefixlen); -+ cidr.family = AF_INET6; -+ memcpy(&cidr.type.in6, e->key.ip, sizeof(cidr.type.in6)); -+ } else { -+ snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv4_prefixlen); -+ cidr.family = AF_INET; -+ cidr.type.in.s_addr = e->key.ip[3]; -+ } -+ 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.rtype == DNS_RRL_RTYPE_QUERY || -+ e->key.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_NUM_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); -+ ADD_LOG_CSTR(&lb, " "); -+ } else { -+ ADD_LOG_CSTR(&lb, " for (?) "); -+ } -+ dns_rdataclass_totext(e->key.qclass-2, &lb); -+ ADD_LOG_CSTR(&lb, " "); -+ dns_rdatatype_totext(e->key.qtype, &lb); -+ snprintf(strbuf, sizeof(strbuf), " (%08x)", e->key.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, -+ char *log_buf, unsigned int log_buf_len) -+{ -+ if (e->logged) { -+ make_log_buf(rrl, e, rrl->log_only ? "would " : NULL, -+ "stop limiting ", ISC_TRUE, -+ DNS_RRL_RESULT_OK, NULL, ISC_FALSE, -+ dns_rcode_noerror, 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; -+ } -+} -+ -+/* -+ * Log some messages for streams that have stopped being rate limited -+ * or really for buckets that are now idle after having done something. -+ */ -+static void -+prune_qnames(dns_rrl_t *rrl, isc_stdtime_t now, -+ char *log_buf, unsigned int log_buf_len) -+{ -+ dns_rrl_entry_t *e, *e_prev; -+ isc_boolean_t move_ptr; -+ int cnt, age; -+ -+ move_ptr = ISC_TRUE; -+ cnt = 8; -+ -+ for (e = rrl->log_ended; e != NULL; e = e->lru.prev) { -+ e_prev = e; -+ if (!e->logged) -+ continue; -+ -+ age = delta_rrl_time(e->last_used, now); -+ if (age <= rrl->window) { -+ rrl->prune_time = now; -+ break; -+ } -+ -+ if (age < DNS_RRL_STOP_LOG_SECS || -+ response_balance(rrl, e, age) < 0) { -+ move_ptr = ISC_FALSE; -+ continue; -+ } -+ -+ log_end(rrl, e, log_buf, log_buf_len); -+ -+ /* -+ * Do not log many messages at once to avoid stalling real work. -+ */ -+ if (--cnt <= 0) -+ break; -+ } -+ if (e == NULL) -+ rrl->prune_time = now; -+ if (move_ptr) -+ rrl->log_ended = e_prev; -+} -+ -+/* -+ * 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, dns_rcode_t rcode, 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; -+ } -+ -+ if (rrl->prune_time != now) -+ prune_qnames(rrl, now, 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_rrl_entry(rrl, client_addr, -+ dns_rdatatype_none, NULL, 0, -+ DNS_RRL_RTYPE_TCP, now, ISC_TRUE, -+ log_buf, log_buf_len); -+ if (e != NULL) { -+ e->responses = -(rrl->window+1); -+ e->last_used = 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 (rcode == dns_rcode_noerror) -+ rtype = DNS_RRL_RTYPE_QUERY; -+ else if (rcode == dns_rcode_nxdomain) -+ rtype = DNS_RRL_RTYPE_NXDOMAIN; -+ else -+ rtype = DNS_RRL_RTYPE_ERROR; -+ e = get_rrl_entry(rrl, client_addr, qtype, qname, qclass, 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, -+ DNS_RRL_RESULT_OK, qname, ISC_FALSE, -+ rcode, 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. -+ */ -+ dns_rrl_entry_t *e_all; -+ dns_rrl_result_t rrl_all_result; -+ -+ e_all = get_rrl_entry(rrl, client_addr, -+ dns_rdatatype_none, NULL, 0, -+ 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; -+ if (rrl_result == DNS_RRL_RESULT_OK) -+ level = DNS_RRL_LOG_DEBUG2; -+ else -+ level = DNS_RRL_LOG_DEBUG1; -+ rrl_result = rrl_all_result; -+ if (isc_log_wouldlog(dns_lctx, level)) { -+ make_log_buf(rrl, e, -+ "prefer all-per-second limiting ", -+ NULL, ISC_TRUE, DNS_RRL_RESULT_OK, -+ NULL, ISC_FALSE, dns_rcode_noerror, -+ 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, DNS_RRL_RESULT_OK, -+ qname, ISC_TRUE, rcode, log_buf, log_buf_len); -+ e->logged = ISC_TRUE; -+ 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, rrl_result, -+ qname, ISC_FALSE, rcode, 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; -+ int i; -+ -+ rrl = view->rrl; -+ if (rrl == NULL) -+ return; -+ view->rrl = NULL; -+ -+ /* -+ * Assume the caller takes care of locking the view and anything else. -+ */ -+ do { -+ char log_buf[DNS_RRL_LOG_BUF_LEN]; -+ -+ prune_qnames(rrl, rrl->prune_time+DNS_RRL_MAX_WINDOW+1, -+ log_buf, sizeof(log_buf)); -+ } while (rrl->log_ended->lru.prev != NULL); -+ -+ for (i = 0; i < DNS_RRL_NUM_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); -+ } -+ -+ view->rrl = rrl; -+ -+ result = add_rrl_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/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 --git a/rpz2+rl-9.9.3-P1.patch b/rpz2+rl-9.9.3-P1.patch new file mode 100644 index 0000000..d0c4a66 --- /dev/null +++ b/rpz2+rl-9.9.3-P1.patch @@ -0,0 +1,10452 @@ +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,51 @@ + #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 = 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_QUERY_EERRORS, ++ NS_LOGMODULE_CLIENT, ++ DNS_RRL_LOG_DROP, ++ "%s", log_buf); ++ } ++ /* ++ * Some error responses cannot be 'slipped', ++ * so don't try to slip any error responses. ++ */ ++ if (!client->view->rrl->log_only) { ++ isc_stats_increment(ns_g_server->nsstats, ++ dns_nsstatscounter_ratedropped); ++ isc_stats_increment(ns_g_server->nsstats, ++ dns_nsstatscounter_dropped); ++ ns_client_next(client, DNS_R_DROP); ++ return; ++ } ++ } ++ } ++ ++ /* + * 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 +@@ -228,6 +228,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 +@@ -167,7 +167,10 @@ + + dns_nsstatscounter_rpz_rewrites = 36, + +- dns_nsstatscounter_max = 37 ++ dns_nsstatscounter_ratedropped = 37, ++ dns_nsstatscounter_rateslipped = 38, ++ ++ dns_nsstatscounter_max = 39 + }; + + 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 +@@ -193,7 +193,7 @@ + #ifdef NEWSTATS + /* Do query type statistics + * +- * We only increment per-type if we're using the authoriative ++ * We only increment per-type if we're using the authoritative + * answer counter, preventing double-counting. + */ + if (counter == dns_nsstatscounter_authans) { +@@ -879,11 +879,11 @@ + static void + rpz_log_rewrite(ns_client_t *client, isc_boolean_t disabled, + dns_rpz_policy_t policy, dns_rpz_type_t type, +- dns_zone_t *zone, dns_name_t *rpz_qname) ++ dns_zone_t *p_zone, dns_name_t *p_name) + { + isc_stats_t *zonestats; + char qname_buf[DNS_NAME_FORMATSIZE]; +- char rpz_qname_buf[DNS_NAME_FORMATSIZE]; ++ char p_name_buf[DNS_NAME_FORMATSIZE]; + + /* + * Count enabled rewrites in the global counter. +@@ -893,8 +893,8 @@ + isc_stats_increment(ns_g_server->nsstats, + dns_nsstatscounter_rpz_rewrites); + } +- if (zone != NULL) { +- zonestats = dns_zone_getrequeststats(zone); ++ if (p_zone != NULL) { ++ zonestats = dns_zone_getrequeststats(p_zone); + if (zonestats != NULL) + isc_stats_increment(zonestats, + dns_nsstatscounter_rpz_rewrites); +@@ -904,22 +904,21 @@ + return; + + dns_name_format(client->query.qname, qname_buf, sizeof(qname_buf)); +- dns_name_format(rpz_qname, rpz_qname_buf, sizeof(rpz_qname_buf)); ++ dns_name_format(p_name, p_name_buf, sizeof(p_name_buf)); + + ns_client_log(client, DNS_LOGCATEGORY_RPZ, NS_LOGMODULE_QUERY, + DNS_RPZ_INFO_LEVEL, "%srpz %s %s rewrite %s via %s", + disabled ? "disabled " : "", + dns_rpz_type2str(type), dns_rpz_policy2str(policy), +- qname_buf, rpz_qname_buf); ++ qname_buf, p_name_buf); + } + + static void +-rpz_log_fail(ns_client_t *client, int level, +- dns_rpz_type_t rpz_type, dns_name_t *name, +- const char *str, isc_result_t result) ++rpz_log_fail(ns_client_t *client, int level, dns_name_t *p_name, ++ dns_rpz_type_t rpz_type, const char *str, isc_result_t result) + { +- char namebuf1[DNS_NAME_FORMATSIZE]; +- char namebuf2[DNS_NAME_FORMATSIZE]; ++ char qnamebuf[DNS_NAME_FORMATSIZE]; ++ char p_namebuf[DNS_NAME_FORMATSIZE]; + + if (!isc_log_wouldlog(ns_g_lctx, level)) + return; +@@ -927,44 +926,44 @@ + /* + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + */ +- dns_name_format(client->query.qname, namebuf1, sizeof(namebuf1)); +- dns_name_format(name, namebuf2, sizeof(namebuf2)); ++ dns_name_format(client->query.qname, qnamebuf, sizeof(qnamebuf)); ++ dns_name_format(p_name, p_namebuf, sizeof(p_namebuf)); + ns_client_log(client, NS_LOGCATEGORY_QUERY_EERRORS, + NS_LOGMODULE_QUERY, level, + "rpz %s rewrite %s via %s %sfailed: %s", + dns_rpz_type2str(rpz_type), +- namebuf1, namebuf2, str, isc_result_totext(result)); ++ qnamebuf, p_namebuf, str, isc_result_totext(result)); + } + + /* + * Get a policy rewrite zone database. + */ + static isc_result_t +-rpz_getdb(ns_client_t *client, dns_rpz_type_t rpz_type, dns_name_t *rpz_qname, ++rpz_getdb(ns_client_t *client, dns_name_t *p_name, dns_rpz_type_t rpz_type, + dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp) + { +- char namebuf1[DNS_NAME_FORMATSIZE]; +- char namebuf2[DNS_NAME_FORMATSIZE]; ++ char qnamebuf[DNS_NAME_FORMATSIZE]; ++ char p_namebuf[DNS_NAME_FORMATSIZE]; + dns_dbversion_t *rpz_version = NULL; + isc_result_t result; + +- result = query_getzonedb(client, rpz_qname, dns_rdatatype_any, ++ result = query_getzonedb(client, p_name, dns_rdatatype_any, + DNS_GETDB_IGNOREACL, zonep, dbp, &rpz_version); + if (result == ISC_R_SUCCESS) { + if (isc_log_wouldlog(ns_g_lctx, DNS_RPZ_DEBUG_LEVEL2)) { +- dns_name_format(client->query.qname, namebuf1, +- sizeof(namebuf1)); +- dns_name_format(rpz_qname, namebuf2, sizeof(namebuf2)); ++ dns_name_format(client->query.qname, qnamebuf, ++ sizeof(qnamebuf)); ++ dns_name_format(p_name, p_namebuf, sizeof(p_namebuf)); + ns_client_log(client, DNS_LOGCATEGORY_RPZ, + NS_LOGMODULE_QUERY, DNS_RPZ_DEBUG_LEVEL2, + "try rpz %s rewrite %s via %s", + dns_rpz_type2str(rpz_type), +- namebuf1, namebuf2); ++ qnamebuf, p_namebuf); + } + *versionp = rpz_version; + return (ISC_R_SUCCESS); + } +- rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, rpz_qname, ++ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, + "query_getzonedb() ", result); + return (result); + } +@@ -3913,7 +3912,7 @@ + dns_rdataset_disassociate(*rdatasetp); + } + +-static void ++static inline void + rpz_match_clear(dns_rpz_st_t *st) + { + rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset); +@@ -3921,16 +3920,16 @@ + } + + static inline isc_result_t +-rpz_ready(ns_client_t *client, dns_zone_t **zonep, dns_db_t **dbp, +- dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp) ++rpz_ready(ns_client_t *client, dns_rdataset_t **rdatasetp) + { + REQUIRE(rdatasetp != NULL); + +- rpz_clean(zonep, dbp, nodep, rdatasetp); + if (*rdatasetp == NULL) { + *rdatasetp = query_newrdataset(client); + if (*rdatasetp == NULL) + return (DNS_R_SERVFAIL); ++ } else if (dns_rdataset_isassociated(*rdatasetp)) { ++ dns_rdataset_disassociate(*rdatasetp); + } + return (ISC_R_SUCCESS); + } +@@ -3959,13 +3958,83 @@ + st->m.policy = DNS_RPZ_POLICY_MISS; + } + ++static dns_rpz_zbits_t ++rpz_get_zbits(ns_client_t *client, ++ dns_rdatatype_t ip_type, dns_rpz_type_t rpz_type) ++{ ++ dns_rpz_zones_t *rpzs; ++ dns_rpz_st_t *st; ++ dns_rpz_zbits_t zbits; ++ ++ rpzs = client->view->rpzs; ++ ++ switch (rpz_type) { ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ zbits = rpzs->have.client_ip; ++ break; ++ case DNS_RPZ_TYPE_QNAME: ++ zbits = rpzs->have.qname; ++ break; ++ case DNS_RPZ_TYPE_IP: ++ if (ip_type == dns_rdatatype_a) { ++ zbits = rpzs->have.ipv4; ++ } else if (ip_type == dns_rdatatype_aaaa) { ++ zbits = rpzs->have.ipv6; ++ } else { ++ zbits = rpzs->have.ip; ++ } ++ break; ++ case DNS_RPZ_TYPE_NSDNAME: ++ zbits = rpzs->have.nsdname; ++ break; ++ case DNS_RPZ_TYPE_NSIP: ++ if (ip_type == dns_rdatatype_a) { ++ zbits = rpzs->have.nsipv4; ++ } else if (ip_type == dns_rdatatype_aaaa) { ++ zbits = rpzs->have.nsipv6; ++ } else { ++ zbits = rpzs->have.nsip; ++ } ++ break; ++ default: ++ INSIST(0); ++ break; ++ } ++ ++ st = client->query.rpz_st; ++ ++ /* ++ * Choose ++ * the earliest configured policy zone (rpz->num) ++ * QNAME over IP over NSDNAME over NSIP (rpz_type) ++ * the smallest name, ++ * the longest IP address prefix, ++ * the lexically smallest address. ++ */ ++ if (st->m.policy != DNS_RPZ_POLICY_MISS) { ++ if (st->m.type >= rpz_type) { ++ zbits &= DNS_RPZ_ZMASK(st->m.rpz->num); ++ } else{ ++ zbits &= DNS_RPZ_ZMASK(st->m.rpz->num) >> 1; ++ } ++ } ++ ++ /* ++ * If the client wants recursion, allow only compatible policies. ++ */ ++ if (!RECURSIONOK(client)) ++ zbits &= rpzs->p.no_rd_ok; ++ ++ return (zbits); ++} ++ + /* +- * Get NS, A, or AAAA rrset for response policy zone checks. ++ * Get an NS, A, or AAAA rrset related to the response for the client ++ * to check the contents of that rrset for hits by eligible policy zones. + */ + static isc_result_t +-rpz_rrset_find(ns_client_t *client, dns_rpz_type_t rpz_type, +- dns_name_t *name, dns_rdatatype_t type, +- dns_db_t **dbp, dns_dbversion_t *version, ++rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type, ++ dns_rpz_type_t rpz_type, dns_db_t **dbp, dns_dbversion_t *version, + dns_rdataset_t **rdatasetp, isc_boolean_t resuming) + { + dns_rpz_st_t *st; +@@ -3977,15 +4046,13 @@ + dns_clientinfomethods_t cm; + dns_clientinfo_t ci; + +- dns_clientinfomethods_init(&cm, ns_client_sourceip); +- dns_clientinfo_init(&ci, client); +- + st = client->query.rpz_st; + if ((st->state & DNS_RPZ_RECURSING) != 0) { + INSIST(st->r.r_type == type); + INSIST(dns_name_equal(name, st->r_name)); + INSIST(*rdatasetp == NULL || + !dns_rdataset_isassociated(*rdatasetp)); ++ INSIST(*dbp == NULL); + st->state &= ~DNS_RPZ_RECURSING; + *dbp = st->r.db; + st->r.db = NULL; +@@ -3995,16 +4062,15 @@ + st->r.r_rdataset = NULL; + result = st->r.r_result; + if (result == DNS_R_DELEGATION) { +- rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, +- rpz_type, name, +- "rpz_rrset_find(1) ", result); ++ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, ++ rpz_type, "rpz_rrset_find(1) ", result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + result = DNS_R_SERVFAIL; + } + return (result); + } + +- result = rpz_ready(client, NULL, NULL, NULL, rdatasetp); ++ result = rpz_ready(client, rdatasetp); + if (result != ISC_R_SUCCESS) { + st->m.policy = DNS_RPZ_POLICY_ERROR; + return (result); +@@ -4019,9 +4085,8 @@ + result = query_getdb(client, name, type, 0, &zone, dbp, + &version, &is_zone); + if (result != ISC_R_SUCCESS) { +- rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, +- rpz_type, name, +- "rpz_rrset_find(2) ", result); ++ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, ++ rpz_type, "rpz_rrset_find(2) ", result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + if (zone != NULL) + dns_zone_detach(&zone); +@@ -4034,6 +4099,8 @@ + node = NULL; + dns_fixedname_init(&fixed); + found = dns_fixedname_name(&fixed); ++ dns_clientinfomethods_init(&cm, ns_client_sourceip); ++ dns_clientinfo_init(&ci, client); + result = dns_db_findext(*dbp, name, version, type, DNS_DBFIND_GLUEOK, + client->now, &node, found, + &cm, &ci, *rdatasetp, NULL); +@@ -4072,177 +4139,97 @@ + } + + /* +- * Check the IP address in an A or AAAA rdataset against +- * the IP or NSIP response policy rules of a view. ++ * Compute a policy owner name, p_name, in a policy zone given the needed ++ * policy type and the trigger name. + */ + static isc_result_t +-rpz_rewrite_ip(ns_client_t *client, dns_rdataset_t *rdataset, +- dns_rpz_type_t rpz_type) +-{ +- dns_rpz_st_t *st; +- dns_dbversion_t *version; +- dns_zone_t *zone; +- dns_db_t *db; +- dns_rpz_zone_t *rpz; ++rpz_get_p_name(ns_client_t *client, dns_name_t *p_name, ++ dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, ++ dns_name_t *trig_name) ++{ ++ dns_offsets_t prefix_offsets; ++ dns_name_t prefix, *suffix; ++ unsigned int first, labels; + isc_result_t result; + +- st = client->query.rpz_st; +- if (st->m.rdataset == NULL) { +- st->m.rdataset = query_newrdataset(client); +- if (st->m.rdataset == NULL) +- return (DNS_R_SERVFAIL); +- } +- zone = NULL; +- db = NULL; +- for (rpz = ISC_LIST_HEAD(client->view->rpz_zones); +- rpz != NULL; +- rpz = ISC_LIST_NEXT(rpz, link)) { +- if (!RECURSIONOK(client) && rpz->recursive_only) +- continue; +- +- /* +- * Do not check policy zones that cannot replace a policy +- * already known to match. +- */ +- if (st->m.policy != DNS_RPZ_POLICY_MISS) { +- if (st->m.rpz->num < rpz->num) +- break; +- if (st->m.rpz->num == rpz->num && +- st->m.type < rpz_type) +- continue; +- } +- +- /* +- * Find the database for this policy zone to get its radix tree. +- */ +- version = NULL; +- result = rpz_getdb(client, rpz_type, &rpz->origin, +- &zone, &db, &version); +- if (result != ISC_R_SUCCESS) { +- rpz_clean(&zone, &db, NULL, NULL); +- continue; +- } +- /* +- * Look for a better (e.g. longer prefix) hit for an IP address +- * in this rdataset in this radix tree than than the previous +- * hit, if any. Note the domain name and quality of the +- * best hit. +- */ +- dns_db_rpz_findips(rpz, rpz_type, zone, db, version, +- rdataset, st, client->query.rpz_st->qname); +- rpz_clean(&zone, &db, NULL, NULL); +- } +- return (ISC_R_SUCCESS); +-} +- +-/* +- * Look for an A or AAAA rdataset +- * and check for IP or NSIP rewrite policy rules. +- */ +-static isc_result_t +-rpz_rewrite_rrset(ns_client_t *client, dns_rpz_type_t rpz_type, +- dns_rdatatype_t type, dns_name_t *name, +- dns_db_t **dbp, dns_dbversion_t *version, +- dns_rdataset_t **rdatasetp, isc_boolean_t resuming) +-{ +- isc_result_t result; +- +- result = rpz_rrset_find(client, rpz_type, name, type, dbp, version, +- rdatasetp, resuming); +- switch (result) { +- case ISC_R_SUCCESS: +- case DNS_R_GLUE: +- case DNS_R_ZONECUT: +- result = rpz_rewrite_ip(client, *rdatasetp, rpz_type); ++ /* ++ * The policy owner name consists of a suffix depending on the type ++ * and policy zone and a prefix that is the longest possible string ++ * from the trigger name that keesp the resulting policy owner name ++ * from being too long. ++ */ ++ switch (rpz_type) { ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ suffix = &rpz->client_ip; + break; +- case DNS_R_EMPTYNAME: +- case DNS_R_EMPTYWILD: +- case DNS_R_NXDOMAIN: +- case DNS_R_NCACHENXDOMAIN: +- case DNS_R_NXRRSET: +- case DNS_R_NCACHENXRRSET: +- case ISC_R_NOTFOUND: +- result = ISC_R_SUCCESS; ++ case DNS_RPZ_TYPE_QNAME: ++ suffix = &rpz->origin; + break; +- case DNS_R_DELEGATION: +- case DNS_R_DUPLICATE: +- case DNS_R_DROP: ++ case DNS_RPZ_TYPE_IP: ++ suffix = &rpz->ip; + break; +- case DNS_R_CNAME: +- case DNS_R_DNAME: +- rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, rpz_type, +- name, "NS address rewrite rrset ", result); +- result = ISC_R_SUCCESS; ++ case DNS_RPZ_TYPE_NSDNAME: ++ suffix = &rpz->nsdname; + break; +- default: +- if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { +- client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR; +- rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, +- name, "NS address rewrite rrset ", result); +- } ++ case DNS_RPZ_TYPE_NSIP: ++ suffix = &rpz->nsip; + break; ++ default: ++ INSIST(0); + } +- return (result); +-} +- +-/* +- * Look for both A and AAAA rdatasets +- * and check for IP or NSIP rewrite policy rules. +- * Look only for addresses that will be in the ANSWER section +- * when checking for IP rules. +- */ +-static isc_result_t +-rpz_rewrite_rrsets(ns_client_t *client, dns_rpz_type_t rpz_type, +- dns_name_t *name, dns_rdatatype_t type, +- dns_rdataset_t **rdatasetp, isc_boolean_t resuming) +-{ +- dns_rpz_st_t *st; +- dns_dbversion_t *version; +- dns_db_t *ipdb; +- isc_result_t result; + +- st = client->query.rpz_st; +- version = NULL; +- ipdb = NULL; +- if ((st->state & DNS_RPZ_DONE_IPv4) == 0 && +- ((rpz_type == DNS_RPZ_TYPE_NSIP) ? +- (st->state & DNS_RPZ_HAVE_NSIPv4) : +- (st->state & DNS_RPZ_HAVE_IP)) != 0 && +- (type == dns_rdatatype_any || type == dns_rdatatype_a)) { +- result = rpz_rewrite_rrset(client, rpz_type, dns_rdatatype_a, +- name, &ipdb, version, rdatasetp, +- resuming); ++ /* ++ * Start with relative version of the full trigger name, ++ * and trim enough allow the addition of the suffix. ++ */ ++ dns_name_init(&prefix, prefix_offsets); ++ labels = dns_name_countlabels(trig_name); ++ first = 0; ++ for (;;) { ++ dns_name_getlabelsequence(trig_name, first, labels-first-1, ++ &prefix); ++ result = dns_name_concatenate(&prefix, suffix, p_name, NULL); + if (result == ISC_R_SUCCESS) +- st->state |= DNS_RPZ_DONE_IPv4; +- } else { +- result = ISC_R_SUCCESS; +- } +- if (result == ISC_R_SUCCESS && +- ((rpz_type == DNS_RPZ_TYPE_NSIP) ? +- (st->state & DNS_RPZ_HAVE_NSIPv6) : +- (st->state & DNS_RPZ_HAVE_IP)) != 0 && +- (type == dns_rdatatype_any || type == dns_rdatatype_aaaa)) { +- result = rpz_rewrite_rrset(client, rpz_type, dns_rdatatype_aaaa, +- name, &ipdb, version, rdatasetp, +- resuming); ++ return (ISC_R_SUCCESS); ++ INSIST(result == DNS_R_NAMETOOLONG); ++ /* ++ * Trim the trigger name until the combination is not too long. ++ */ ++ if (labels-first < 2) { ++ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, suffix, ++ rpz_type, "concatentate() ", result); ++ return (ISC_R_FAILURE); ++ } ++ /* ++ * Complain once about trimming the trigger name. ++ */ ++ if (first == 0) { ++ rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, suffix, ++ rpz_type, "concatentate() ", result); ++ } ++ ++first; + } +- if (ipdb != NULL) +- dns_db_detach(&ipdb); +- return (result); + } + + /* +- * Get the rrset from a response policy zone. ++ * Look in policy zone rpz for a policy of rpz_type by p_name. ++ * The self-name (usually the client qname or an NS name) is compared with ++ * the target of a CNAME policy for the old style passthru encoding. ++ * If found, the policy is recorded in *zonep, *dbp, *versionp, *nodep, ++ * *rdatasetp, and *policyp. ++ * The target DNS type, qtype, chooses the best rdataset for *rdatasetp. ++ * The caller must decide if the found policy is most suitable, including ++ * better than a previously found policy. ++ * If it is best, the caller records it in client->query.rpz_st->m. + */ + static isc_result_t +-rpz_find(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qnamef, +- dns_name_t *sname, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, +- dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp, +- dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, +- dns_rpz_policy_t *policyp) ++rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype, ++ dns_name_t *p_name, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, ++ dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp, ++ dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, ++ dns_rpz_policy_t *policyp) + { +- dns_rpz_policy_t policy; +- dns_fixedname_t fixed; ++ dns_fixedname_t foundf; + dns_name_t *found; + isc_result_t result; + dns_clientinfomethods_t cm; +@@ -4250,31 +4237,28 @@ + + REQUIRE(nodep != NULL); + +- dns_clientinfomethods_init(&cm, ns_client_sourceip); +- dns_clientinfo_init(&ci, client); +- +- result = rpz_ready(client, zonep, dbp, nodep, rdatasetp); +- if (result != ISC_R_SUCCESS) { +- *policyp = DNS_RPZ_POLICY_ERROR; +- return (result); +- } +- + /* +- * Try to get either a CNAME or the type of record demanded by the ++ * Try to find either a CNAME or the type of record demanded by the + * request from the policy zone. + */ ++ rpz_clean(zonep, dbp, nodep, rdatasetp); ++ result = rpz_ready(client, rdatasetp); ++ if (result != ISC_R_SUCCESS) ++ return (DNS_R_SERVFAIL); + *versionp = NULL; +- result = rpz_getdb(client, rpz_type, qnamef, zonep, dbp, versionp); +- if (result != ISC_R_SUCCESS) { +- *policyp = DNS_RPZ_POLICY_MISS; ++ result = rpz_getdb(client, p_name, rpz_type, zonep, dbp, versionp); ++ if (result != ISC_R_SUCCESS) + return (DNS_R_NXDOMAIN); +- } +- +- dns_fixedname_init(&fixed); +- found = dns_fixedname_name(&fixed); +- result = dns_db_findext(*dbp, qnamef, *versionp, dns_rdatatype_any, 0, ++ dns_fixedname_init(&foundf); ++ found = dns_fixedname_name(&foundf); ++ dns_clientinfomethods_init(&cm, ns_client_sourceip); ++ dns_clientinfo_init(&ci, client); ++ result = dns_db_findext(*dbp, p_name, *versionp, dns_rdatatype_any, 0, + client->now, nodep, found, &cm, &ci, + *rdatasetp, NULL); ++ /* ++ * Choose the best rdataset if we found something. ++ */ + if (result == ISC_R_SUCCESS) { + dns_rdatasetiter_t *rdsiter; + +@@ -4282,10 +4266,8 @@ + result = dns_db_allrdatasets(*dbp, *nodep, *versionp, 0, + &rdsiter); + if (result != ISC_R_SUCCESS) { +- dns_db_detachnode(*dbp, nodep); +- rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, +- qnamef, "allrdatasets() ", result); +- *policyp = DNS_RPZ_POLICY_ERROR; ++ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, ++ rpz_type, "allrdatasets() ", result); + return (DNS_R_SERVFAIL); + } + for (result = dns_rdatasetiter_first(rdsiter); +@@ -4301,9 +4283,8 @@ + if (result != ISC_R_SUCCESS) { + if (result != ISC_R_NOMORE) { + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, +- rpz_type, qnamef, "rdatasetiter ", +- result); +- *policyp = DNS_RPZ_POLICY_ERROR; ++ p_name, rpz_type, ++ "rdatasetiter ", result); + return (DNS_R_SERVFAIL); + } + /* +@@ -4318,7 +4299,7 @@ + qtype == dns_rdatatype_sig) + result = DNS_R_NXRRSET; + else +- result = dns_db_findext(*dbp, qnamef, *versionp, ++ result = dns_db_findext(*dbp, p_name, *versionp, + qtype, 0, client->now, + nodep, found, &cm, &ci, + *rdatasetp, NULL); +@@ -4327,162 +4308,477 @@ + switch (result) { + case ISC_R_SUCCESS: + if ((*rdatasetp)->type != dns_rdatatype_cname) { +- policy = DNS_RPZ_POLICY_RECORD; ++ *policyp = DNS_RPZ_POLICY_RECORD; + } else { +- policy = dns_rpz_decode_cname(rpz, *rdatasetp, sname); +- if ((policy == DNS_RPZ_POLICY_RECORD || +- policy == DNS_RPZ_POLICY_WILDCNAME) && ++ *policyp = dns_rpz_decode_cname(rpz, *rdatasetp, ++ self_name); ++ if ((*policyp == DNS_RPZ_POLICY_RECORD || ++ *policyp == DNS_RPZ_POLICY_WILDCNAME) && + qtype != dns_rdatatype_cname && + qtype != dns_rdatatype_any) +- result = DNS_R_CNAME; ++ return (DNS_R_CNAME); + } +- break; ++ return (ISC_R_SUCCESS); + case DNS_R_NXRRSET: +- policy = DNS_RPZ_POLICY_NODATA; +- break; ++ *policyp = DNS_RPZ_POLICY_NODATA; ++ return (result); + case DNS_R_DNAME: + /* + * DNAME policy RRs have very few if any uses that are not +- * better served with simple wildcards. Making the work would ++ * better served with simple wildcards. Making them work would + * require complications to get the number of labels matched + * in the name or the found name to the main DNS_R_DNAME case +- * in query_find(). +- */ +- dns_rdataset_disassociate(*rdatasetp); +- dns_db_detachnode(*dbp, nodep); +- /* +- * Fall through to treat it as a miss. ++ * in query_find(). The domain also does not appear in the ++ * summary database at the right level, so this happens only ++ * with a single policy zone when we have no summary database. ++ * Treat it as a miss. + */ + case DNS_R_NXDOMAIN: + case DNS_R_EMPTYNAME: +- /* +- * If we don't get a qname hit, +- * see if it is worth looking for other types. +- */ +- (void)dns_db_rpz_enabled(*dbp, client->query.rpz_st); +- dns_db_detach(dbp); +- dns_zone_detach(zonep); +- result = DNS_R_NXDOMAIN; +- policy = DNS_RPZ_POLICY_MISS; +- break; ++ return (DNS_R_NXDOMAIN); + default: +- dns_db_detach(dbp); +- dns_zone_detach(zonep); +- rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, qnamef, ++ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, + "", result); + return (DNS_R_SERVFAIL); + } ++} + +- *policyp = policy; +- return (result); ++static void ++rpz_save_p(dns_rpz_st_t *st, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, ++ dns_rpz_policy_t policy, dns_name_t *p_name, dns_rpz_prefix_t prefix, ++ isc_result_t result, dns_zone_t **zonep, dns_db_t **dbp, ++ dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp, ++ dns_dbversion_t *version) ++{ ++ dns_rdataset_t *trdataset; ++ ++ rpz_match_clear(st); ++ st->m.rpz = rpz; ++ st->m.type = rpz_type; ++ st->m.policy = policy; ++ dns_name_copy(p_name, st->p_name, NULL); ++ st->m.prefix = prefix; ++ st->m.result = result; ++ st->m.zone = *zonep; ++ *zonep = NULL; ++ st->m.db = *dbp; ++ *dbp = NULL; ++ st->m.node = *nodep; ++ *nodep = NULL; ++ if (*rdatasetp != NULL && dns_rdataset_isassociated(*rdatasetp)) { ++ /* ++ * Save the replacement rdataset from the policy ++ * and make the previous replacement rdataset scratch. ++ */ ++ trdataset = st->m.rdataset; ++ st->m.rdataset = *rdatasetp; ++ *rdatasetp = trdataset; ++ st->m.ttl = ISC_MIN(st->m.rdataset->ttl, rpz->max_policy_ttl); ++ } else { ++ st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT, rpz->max_policy_ttl); ++ } ++ st->m.version = version; + } + + /* +- * Build and look for a QNAME or NSDNAME owner name in a response policy zone. ++ * Check this address in every eligible policy zone. + */ + static isc_result_t +-rpz_rewrite_name(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, +- dns_rpz_type_t rpz_type, dns_rdataset_t **rdatasetp) ++rpz_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr, ++ dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, ++ dns_rpz_zbits_t zbits, dns_rdataset_t **p_rdatasetp) + { ++ dns_rpz_zones_t *rpzs; + dns_rpz_st_t *st; + dns_rpz_zone_t *rpz; +- dns_fixedname_t prefixf, rpz_qnamef; +- dns_name_t *prefix, *suffix, *rpz_qname; +- dns_zone_t *zone; +- dns_db_t *db; +- dns_dbversion_t *version; +- dns_dbnode_t *node; ++ dns_rpz_prefix_t prefix; ++ dns_rpz_num_t rpz_num; ++ dns_fixedname_t ip_namef, p_namef; ++ dns_name_t *ip_name, *p_name; ++ dns_zone_t *p_zone; ++ dns_db_t *p_db; ++ dns_dbversion_t *p_version; ++ dns_dbnode_t *p_node; + dns_rpz_policy_t policy; +- unsigned int labels; + isc_result_t result; + +- st = client->query.rpz_st; +- zone = NULL; +- db = NULL; +- node = NULL; ++ dns_fixedname_init(&ip_namef); ++ ip_name = dns_fixedname_name(&ip_namef); + +- for (rpz = ISC_LIST_HEAD(client->view->rpz_zones); +- rpz != NULL; +- rpz = ISC_LIST_NEXT(rpz, link)) { +- if (!RECURSIONOK(client) && rpz->recursive_only) +- continue; ++ p_zone = NULL; ++ p_db = NULL; ++ p_node = NULL; ++ ++ rpzs = client->view->rpzs; ++ st = client->query.rpz_st; ++ while (zbits != 0) { ++ rpz_num = dns_rpz_find_ip(rpzs, rpz_type, zbits, netaddr, ++ ip_name, &prefix); ++ if (rpz_num == DNS_RPZ_INVALID_NUM) ++ break; ++ zbits &= (DNS_RPZ_ZMASK(rpz_num) >> 1); + + /* +- * Do not check policy zones that cannot replace a policy +- * already known to match. ++ * Do not try applying policy zones that cannot replace a ++ * previously found policy zone. ++ * Stop looking if the next best choice cannot ++ * replace what we already have. + */ ++ rpz = rpzs->zones[rpz_num]; + if (st->m.policy != DNS_RPZ_POLICY_MISS) { + if (st->m.rpz->num < rpz->num) + break; + if (st->m.rpz->num == rpz->num && +- st->m.type < rpz_type) +- continue; ++ (st->m.type < rpz_type || ++ st->m.prefix > prefix)) ++ break; + } ++ + /* +- * Construct the policy's owner name. ++ * Get the policy for a prefix at least as long ++ * as the prefix of the entry we had before. + */ +- dns_fixedname_init(&prefixf); +- prefix = dns_fixedname_name(&prefixf); +- dns_name_split(qname, 1, prefix, NULL); +- if (rpz_type == DNS_RPZ_TYPE_NSDNAME) +- suffix = &rpz->nsdname; +- else +- suffix = &rpz->origin; +- dns_fixedname_init(&rpz_qnamef); +- rpz_qname = dns_fixedname_name(&rpz_qnamef); +- for (;;) { +- result = dns_name_concatenate(prefix, suffix, +- rpz_qname, NULL); +- if (result == ISC_R_SUCCESS) +- break; +- INSIST(result == DNS_R_NAMETOOLONG); ++ dns_fixedname_init(&p_namef); ++ p_name = dns_fixedname_name(&p_namef); ++ result = rpz_get_p_name(client, p_name, rpz, rpz_type, ip_name); ++ if (result != ISC_R_SUCCESS) ++ continue; ++ result = rpz_find_p(client, ip_name, qtype, ++ p_name, rpz, rpz_type, ++ &p_zone, &p_db, &p_version, &p_node, ++ p_rdatasetp, &policy); ++ switch (result) { ++ case DNS_R_NXDOMAIN: + /* +- * Trim the name until it is not too long. ++ * Continue after a policy record that is missing ++ * contrary to the summary data. The summary ++ * data can out of date during races with and among ++ * policy zone updates. + */ +- labels = dns_name_countlabels(prefix); +- if (labels < 2) { +- rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, +- rpz_type, suffix, +- "concatentate() ", result); +- return (ISC_R_SUCCESS); +- } +- if (labels+1 == dns_name_countlabels(qname)) { +- rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, +- rpz_type, suffix, +- "concatentate() ", result); ++ continue; ++ case DNS_R_SERVFAIL: ++ rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); ++ st->m.policy = DNS_RPZ_POLICY_ERROR; ++ return (DNS_R_SERVFAIL); ++ default: ++ /* ++ * Forget this policy if it is not preferable ++ * to the previously found policy. ++ * If this policy is not good, then stop looking ++ * because none of the later policy zones would work. ++ * ++ * With more than one applicable policy, prefer ++ * the earliest configured policy, ++ * client-IP over QNAME over IP over NSDNAME over NSIP, ++ * the longest prefix ++ * the lexically smallest address. ++ * dns_rpz_find_ip() ensures st->m.rpz->num >= rpz->num. ++ * We can compare new and current p_name because ++ * both are of the same type and in the same zone. ++ * The tests above eliminate other reasons to ++ * reject this policy. If this policy can't work, ++ * then neither can later zones. ++ */ ++ if (st->m.policy != DNS_RPZ_POLICY_MISS && ++ rpz->num == st->m.rpz->num && ++ (st->m.type == rpz_type && ++ st->m.prefix == prefix && ++ 0 > dns_name_rdatacompare(st->p_name, p_name))) ++ break; ++ ++ /* ++ * Stop checking after saving an enabled hit in this ++ * policy zone. The radix tree in the policy zone ++ * ensures that we found the longest match. ++ */ ++ if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { ++ rpz_save_p(st, rpz, rpz_type, ++ policy, p_name, prefix, result, ++ &p_zone, &p_db, &p_node, ++ p_rdatasetp, p_version); ++ break; + } +- dns_name_split(prefix, labels - 1, NULL, prefix); ++ ++ /* ++ * Log DNS_RPZ_POLICY_DISABLED zones ++ * and try the next eligible policy zone. ++ */ ++ rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type, ++ p_zone, p_name); ++ } ++ } ++ ++ rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp); ++ return (ISC_R_SUCCESS); ++} ++ ++/* ++ * Check the IP addresses in the A or AAAA rrsets for name against ++ * all eligible rpz_type (IP or NSIP) response policy rewrite rules. ++ */ ++static isc_result_t ++rpz_rewrite_ip_rrset(ns_client_t *client, ++ dns_name_t *name, dns_rdatatype_t qtype, ++ dns_rpz_type_t rpz_type, dns_rdatatype_t ip_type, ++ dns_db_t **ip_dbp, dns_dbversion_t *ip_version, ++ dns_rdataset_t **ip_rdatasetp, ++ dns_rdataset_t **p_rdatasetp, isc_boolean_t resuming) ++{ ++ dns_rpz_zbits_t zbits; ++ isc_netaddr_t netaddr; ++ struct in_addr ina; ++ struct in6_addr in6a; ++ isc_result_t result; ++ ++ zbits = rpz_get_zbits(client, ip_type, rpz_type); ++ if (zbits == 0) ++ return (ISC_R_SUCCESS); ++ ++ /* ++ * Get the A or AAAA rdataset. ++ */ ++ result = rpz_rrset_find(client, name, ip_type, rpz_type, ip_dbp, ++ ip_version, ip_rdatasetp, resuming); ++ switch (result) { ++ case ISC_R_SUCCESS: ++ case DNS_R_GLUE: ++ case DNS_R_ZONECUT: ++ break; ++ case DNS_R_EMPTYNAME: ++ case DNS_R_EMPTYWILD: ++ case DNS_R_NXDOMAIN: ++ case DNS_R_NCACHENXDOMAIN: ++ case DNS_R_NXRRSET: ++ case DNS_R_NCACHENXRRSET: ++ case ISC_R_NOTFOUND: ++ return (ISC_R_SUCCESS); ++ case DNS_R_DELEGATION: ++ case DNS_R_DUPLICATE: ++ case DNS_R_DROP: ++ return (result); ++ case DNS_R_CNAME: ++ case DNS_R_DNAME: ++ rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, name, rpz_type, ++ "NS address rewrite rrset ", result); ++ return (ISC_R_SUCCESS); ++ break; ++ default: ++ if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { ++ client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR; ++ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name, ++ rpz_type, "NS address rewrite rrset ", ++ result); ++ } ++ return (DNS_R_SERVFAIL); ++ } ++ ++ /* ++ * Check all of the IP addresses in the rdataset. ++ */ ++ for (result = dns_rdataset_first(*ip_rdatasetp); ++ result == ISC_R_SUCCESS; ++ result = dns_rdataset_next(*ip_rdatasetp)) { ++ ++ dns_rdata_t rdata = DNS_RDATA_INIT; ++ dns_rdataset_current(*ip_rdatasetp, &rdata); ++ switch (rdata.type) { ++ case dns_rdatatype_a: ++ INSIST(rdata.length == 4); ++ memcpy(&ina.s_addr, rdata.data, 4); ++ isc_netaddr_fromin(&netaddr, &ina); ++ break; ++ case dns_rdatatype_aaaa: ++ INSIST(rdata.length == 16); ++ memcpy(in6a.s6_addr, rdata.data, 16); ++ isc_netaddr_fromin6(&netaddr, &in6a); ++ break; ++ default: ++ continue; ++ } ++ ++ result = rpz_rewrite_ip(client, &netaddr, qtype, rpz_type, ++ zbits, p_rdatasetp); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ } ++ ++ return (ISC_R_SUCCESS); ++} ++ ++/* ++ * Look for IP addresses in A and AAAA rdatasets ++ * that trigger all eligible IP or NSIP policy rules. ++ */ ++static isc_result_t ++rpz_rewrite_ip_rrsets(ns_client_t *client, dns_name_t *name, ++ dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, ++ dns_rdataset_t **ip_rdatasetp, isc_boolean_t resuming) ++{ ++ dns_rpz_st_t *st; ++ dns_dbversion_t *ip_version; ++ dns_db_t *ip_db; ++ dns_rdataset_t *p_rdataset; ++ isc_result_t result; ++ ++ st = client->query.rpz_st; ++ ip_version = NULL; ++ ip_db = NULL; ++ p_rdataset = NULL; ++ if ((st->state & DNS_RPZ_DONE_IPv4) == 0 && ++ (qtype == dns_rdatatype_a || ++ qtype == dns_rdatatype_any || ++ rpz_type == DNS_RPZ_TYPE_NSIP)) { ++ /* ++ * Rewrite based on an IPv4 address that will appear ++ * in the ANSWER section or if we are checking IP addresses. ++ */ ++ result = rpz_rewrite_ip_rrset(client, name, qtype, ++ rpz_type, dns_rdatatype_a, ++ &ip_db, ip_version, ip_rdatasetp, ++ &p_rdataset, resuming); ++ if (result == ISC_R_SUCCESS) ++ st->state |= DNS_RPZ_DONE_IPv4; ++ } else { ++ result = ISC_R_SUCCESS; ++ } ++ if (result == ISC_R_SUCCESS && ++ (qtype == dns_rdatatype_aaaa || ++ qtype == dns_rdatatype_any || ++ rpz_type == DNS_RPZ_TYPE_NSIP)) { ++ /* ++ * Rewrite based on IPv6 addresses that will appear ++ * in the ANSWER section or if we are checking IP addresses. ++ */ ++ result = rpz_rewrite_ip_rrset(client, name, qtype, ++ rpz_type, dns_rdatatype_aaaa, ++ &ip_db, ip_version, ip_rdatasetp, ++ &p_rdataset, resuming); ++ } ++ if (ip_db != NULL) ++ dns_db_detach(&ip_db); ++ query_putrdataset(client, &p_rdataset); ++ return (result); ++} ++ ++/* ++ * Try to rewrite a request for a qtype rdataset based on the trigger name ++ * trig_name and rpz_type (DNS_RPZ_TYPE_QNAME or DNS_RPZ_TYPE_NSDNAME). ++ * Record the results including the replacement rdataset if any ++ * in client->query.rpz_st. ++ * *rdatasetp is a scratch rdataset. ++ */ ++static isc_result_t ++rpz_rewrite_name(ns_client_t *client, dns_name_t *trig_name, ++ dns_rdatatype_t qtype, dns_rpz_type_t rpz_type, ++ dns_rpz_zbits_t allowed_zbits, dns_rdataset_t **rdatasetp) ++{ ++ dns_rpz_zone_t *rpz; ++ dns_rpz_st_t *st; ++ dns_fixedname_t p_namef; ++ dns_name_t *p_name; ++ dns_rpz_zbits_t zbits; ++ dns_rpz_num_t rpz_num; ++ dns_zone_t *p_zone; ++ dns_db_t *p_db; ++ dns_dbversion_t *p_version; ++ dns_dbnode_t *p_node; ++ dns_rpz_policy_t policy; ++ isc_result_t result; ++ ++ zbits = rpz_get_zbits(client, qtype, rpz_type); ++ zbits &= allowed_zbits; ++ if (zbits == 0) ++ return (ISC_R_SUCCESS); ++ ++ /* ++ * If there is only one eligible policy zone, just check it. ++ * If more than one, then use the summary database to find ++ * the bit mask of policy zones with policies for this trigger name. ++ * x&-x is the least significant bit set in x ++ */ ++ if (zbits != (zbits & -zbits)) { ++ zbits = dns_rpz_find_name(client->view->rpzs, ++ rpz_type, zbits, trig_name); ++ if (zbits == 0) ++ return (ISC_R_SUCCESS); ++ } ++ ++ dns_fixedname_init(&p_namef); ++ p_name = dns_fixedname_name(&p_namef); ++ ++ p_zone = NULL; ++ p_db = NULL; ++ p_node = NULL; ++ ++ st = client->query.rpz_st; ++ ++ /* ++ * Check the trigger name in every policy zone that the summary data ++ * says has a hit for the trigger name. ++ * Most of the time there are no eligible zones and the summary data ++ * keeps us from getting this far. ++ * We check the most eligible zone first and so usually check only ++ * one policy zone. ++ */ ++ for (rpz_num = 0; ++ zbits != 0; ++ ++rpz_num, zbits >>= 1) { ++ if ((zbits & 1) == 0) { ++ INSIST(rpz_num <= client->view->rpzs->p.num_zones); ++ continue; + } + + /* +- * See if the policy record exists and get its policy. ++ * Do not check policy zones that cannot replace a previously ++ * found policy. + */ +- result = rpz_find(client, qtype, rpz_qname, qname, rpz, +- rpz_type, &zone, &db, &version, &node, +- rdatasetp, &policy); ++ rpz = client->view->rpzs->zones[rpz_num]; ++ if (st->m.policy != DNS_RPZ_POLICY_MISS) { ++ if (st->m.rpz->num < rpz->num) ++ break; ++ if (st->m.rpz->num == rpz->num && ++ st->m.type < rpz_type) ++ break; ++ } ++ ++ /* ++ * Get the next policy zone's record for this trigger name. ++ */ ++ result = rpz_get_p_name(client, p_name, rpz, rpz_type, ++ trig_name); ++ if (result != ISC_R_SUCCESS) ++ continue; ++ result = rpz_find_p(client, trig_name, qtype, p_name, ++ rpz, rpz_type, ++ &p_zone, &p_db, &p_version, &p_node, ++ rdatasetp, &policy); + switch (result) { + case DNS_R_NXDOMAIN: +- break; ++ /* ++ * Continue after a missing policy record ++ * contrary to the summary data. The summary ++ * data can out of date during races with and among ++ * policy zone updates. ++ */ ++ continue; + case DNS_R_SERVFAIL: +- rpz_clean(&zone, &db, &node, rdatasetp); ++ rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); + st->m.policy = DNS_RPZ_POLICY_ERROR; + return (DNS_R_SERVFAIL); + default: + /* +- * We are dealing with names here. + * With more than one applicable policy, prefer + * the earliest configured policy, +- * QNAME over IP over NSDNAME over NSIP, ++ * client-IP over QNAME over IP over NSDNAME over NSIP, + * and the smallest name. +- * Because of the testing above, +- * we known st->m.rpz->num >= rpz->num and either ++ * We known st->m.rpz->num >= rpz->num and either + * st->m.rpz->num > rpz->num or st->m.type >= rpz_type + */ + if (st->m.policy != DNS_RPZ_POLICY_MISS && + rpz->num == st->m.rpz->num && + (st->m.type < rpz_type || + (st->m.type == rpz_type && +- 0 >= dns_name_compare(rpz_qname, st->qname)))) ++ 0 >= dns_name_compare(p_name, st->p_name)))) + continue; + #if 0 + /* +@@ -4505,11 +4801,12 @@ + * names in TLDs that start with "rpz-" should + * ICANN ever allow such TLDs. + */ +- labels = dns_name_countlabels(qname); ++ unsigned int labels; ++ labels = dns_name_countlabels(trig_name); + if (labels >= 2) { + dns_label_t label; + +- dns_name_getlabel(qname, labels-2, &label); ++ dns_name_getlabel(trig_name, labels-2, &label); + if (label.length >= sizeof(DNS_RPZ_PREFIX)-1 && + strncasecmp((const char *)label.base+1, + DNS_RPZ_PREFIX, +@@ -4517,46 +4814,29 @@ + continue; + } + #endif ++ if (rpz->policy != DNS_RPZ_POLICY_DISABLED) { ++ rpz_save_p(st, rpz, rpz_type, ++ policy, p_name, 0, result, ++ &p_zone, &p_db, &p_node, ++ rdatasetp, p_version); ++ /* ++ * After a hit, higher numbered policy zones ++ * are irrelevant ++ */ ++ rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); ++ return (ISC_R_SUCCESS); ++ } + /* +- * Merely log DNS_RPZ_POLICY_DISABLED hits. ++ * Log DNS_RPZ_POLICY_DISABLED zones ++ * and try the next eligible policy zone. + */ +- if (rpz->policy == DNS_RPZ_POLICY_DISABLED) { +- rpz_log_rewrite(client, ISC_TRUE, policy, +- rpz_type, zone, rpz_qname); +- continue; +- } +- +- rpz_match_clear(st); +- st->m.rpz = rpz; +- st->m.type = rpz_type; +- st->m.prefix = 0; +- st->m.policy = policy; +- st->m.result = result; +- dns_name_copy(rpz_qname, st->qname, NULL); +- if (*rdatasetp != NULL && +- dns_rdataset_isassociated(*rdatasetp)) { +- dns_rdataset_t *trdataset; +- +- trdataset = st->m.rdataset; +- st->m.rdataset = *rdatasetp; +- *rdatasetp = trdataset; +- st->m.ttl = ISC_MIN(st->m.rdataset->ttl, +- rpz->max_policy_ttl); +- } else { +- st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT, +- rpz->max_policy_ttl); +- } +- st->m.node = node; +- node = NULL; +- st->m.db = db; +- db = NULL; +- st->m.version = version; +- st->m.zone = zone; +- zone = NULL; ++ rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type, ++ p_zone, p_name); ++ break; + } + } + +- rpz_clean(&zone, &db, &node, rdatasetp); ++ rpz_clean(&p_zone, &p_db, &p_node, rdatasetp); + return (ISC_R_SUCCESS); + } + +@@ -4569,7 +4849,7 @@ + st = client->query.rpz_st; + + if (str != NULL) +- rpz_log_fail(client, level, DNS_RPZ_TYPE_NSIP, nsname, ++ rpz_log_fail(client, level, nsname, DNS_RPZ_TYPE_NSIP, + str, result); + if (st->r.ns_rdataset != NULL && + dns_rdataset_isassociated(st->r.ns_rdataset)) +@@ -4589,7 +4869,9 @@ + dns_rdataset_t *rdataset; + dns_fixedname_t nsnamef; + dns_name_t *nsname; +- isc_boolean_t ck_ip; ++ int qresult_type; ++ dns_rpz_zbits_t zbits; ++ isc_netaddr_t netaddr; + isc_result_t result; + + st = client->query.rpz_st; +@@ -4603,10 +4885,10 @@ + st->m.policy = DNS_RPZ_POLICY_MISS; + memset(&st->r, 0, sizeof(st->r)); + memset(&st->q, 0, sizeof(st->q)); +- dns_fixedname_init(&st->_qnamef); ++ dns_fixedname_init(&st->_p_namef); + dns_fixedname_init(&st->_r_namef); + dns_fixedname_init(&st->_fnamef); +- st->qname = dns_fixedname_name(&st->_qnamef); ++ st->p_name = dns_fixedname_name(&st->_p_namef); + st->r_name = dns_fixedname_name(&st->_r_namef); + st->fname = dns_fixedname_name(&st->_fnamef); + client->query.rpz_st = st; +@@ -4619,7 +4901,7 @@ + case ISC_R_SUCCESS: + case DNS_R_GLUE: + case DNS_R_ZONECUT: +- ck_ip = ISC_TRUE; ++ qresult_type = 0; + break; + case DNS_R_EMPTYNAME: + case DNS_R_NXRRSET: +@@ -4629,73 +4911,136 @@ + case DNS_R_NCACHENXRRSET: + case DNS_R_CNAME: + case DNS_R_DNAME: +- ck_ip = ISC_FALSE; ++ qresult_type = 1; + break; + case DNS_R_DELEGATION: + case ISC_R_NOTFOUND: +- return (ISC_R_SUCCESS); ++ qresult_type = 2; ++ break; + case ISC_R_FAILURE: + case ISC_R_TIMEDOUT: + case DNS_R_BROKENCHAIN: +- rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, DNS_RPZ_TYPE_QNAME, +- client->query.qname, +- "stop on qresult in rpz_rewrite() ", +- qresult); ++ rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, client->query.qname, ++ DNS_RPZ_TYPE_QNAME, ++ "stop on qresult in rpz_rewrite() ", qresult); + return (ISC_R_SUCCESS); + default: +- rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, DNS_RPZ_TYPE_QNAME, +- client->query.qname, ++ rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, client->query.qname, ++ DNS_RPZ_TYPE_QNAME, + "stop on unrecognized qresult in rpz_rewrite() ", + qresult); + return (ISC_R_SUCCESS); + } + + rdataset = NULL; ++ ++ /* ++ * Check triggers for the client IP address once. ++ */ ++ if ((st->state & DNS_RPZ_DONE_CLIENT_IP) == 0) { ++ st->state |= DNS_RPZ_DONE_CLIENT_IP; ++ zbits = rpz_get_zbits(client, dns_rdatatype_none, ++ DNS_RPZ_TYPE_CLIENT_IP); ++ if (zbits != 0) { ++ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr); ++ result = rpz_rewrite_ip(client, &netaddr, qtype, ++ DNS_RPZ_TYPE_CLIENT_IP, ++ zbits, &rdataset); ++ if (result != ISC_R_SUCCESS) ++ goto cleanup; ++ } ++ } ++ ++ /* ++ * Check triggers for the query name if this is the first time ++ * for the current qname. ++ * There is a first time for each name in a CNAME chain. ++ */ + if ((st->state & DNS_RPZ_DONE_QNAME) == 0) { ++ if (qresult_type == 2) { ++ /* ++ * This request needs recursion that has not been done. ++ * Get bits for the policy zones that do not ++ * require recursion be done before applying their ++ * policies. By not recursing after applying their ++ * policies, those policy zones leak information ++ * to operators of the targeted zones. ++ * Bail out to wait for recursion if there are no ++ * such policies. ++ */ ++ zbits = client->view->rpzs->have.qname_skip_recurse; ++ if (zbits == 0) { ++ result = ISC_R_SUCCESS; ++ goto cleanup; ++ } ++ } else { ++ zbits = DNS_RPZ_ALL_ZBITS; ++ } ++ result = rpz_rewrite_name(client, client->query.qname, qtype, ++ DNS_RPZ_TYPE_QNAME, zbits, &rdataset); ++ if (result != ISC_R_SUCCESS) ++ goto cleanup; ++ + /* +- * Check rules for the query name if this is the first time +- * for the current qname, i.e. we've not been recursing. +- * There is a first time for each name in a CNAME chain. ++ * This may have been an attempt to find a qname trigger ++ * before required recursion (qresult_type == 2). ++ * If so and no pre-recursion triggers hit, then quit and ++ * do everything again after recursing. Do not bother saving ++ * the work already done, because recursion is so slow. ++ * If a pre-recursion trigger hit, then no other triggers ++ * can be relevant and so quit. + */ +- result = rpz_rewrite_name(client, qtype, client->query.qname, +- DNS_RPZ_TYPE_QNAME, &rdataset); +- if (result != ISC_R_SUCCESS) ++ if (qresult_type == 2) + goto cleanup; + + st->r.label = dns_name_countlabels(client->query.qname); + ++ /* ++ * Check IP addresses for the qname next, ++ * starting with IPv4 addresses. ++ */ + st->state &= ~(DNS_RPZ_DONE_QNAME_IP | DNS_RPZ_DONE_IPv4); + st->state |= DNS_RPZ_DONE_QNAME; + } + + /* +- * Check known IP addresses for the query name. ++ * Check known IP addresses for the query name if the database ++ * lookup resulted in some addresses (qresult_type == 0) ++ * and if we have not already checked them. + * Any recursion required for the query has already happened. + * Do not check addresses that will not be in the ANSWER section. + */ +- if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 && +- (st->state & DNS_RPZ_HAVE_IP) != 0 && ck_ip) { +- result = rpz_rewrite_rrsets(client, DNS_RPZ_TYPE_IP, +- client->query.qname, qtype, +- &rdataset, resuming); ++ if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 && qresult_type == 0 && ++ rpz_get_zbits(client, qtype, DNS_RPZ_TYPE_IP) != 0) { ++ result = rpz_rewrite_ip_rrsets(client, ++ client->query.qname, qtype, ++ DNS_RPZ_TYPE_IP, ++ &rdataset, resuming); + if (result != ISC_R_SUCCESS) + goto cleanup; +- st->state &= ~DNS_RPZ_DONE_IPv4; ++ /* ++ * We are finished checking the IP addresses for the qname. ++ * Start with IPv4 if we will check NS IP addesses. ++ */ + st->state |= DNS_RPZ_DONE_QNAME_IP; ++ st->state &= ~DNS_RPZ_DONE_IPv4; + } + + /* +- * Stop looking for rules if there are none of the other kinds. ++ * Stop looking for rules if there are none of the other kinds ++ * that could override what we already have. + */ +- if ((st->state & (DNS_RPZ_HAVE_NSIPv4 | DNS_RPZ_HAVE_NSIPv6 | +- DNS_RPZ_HAVE_NSDNAME)) == 0) { ++ if (rpz_get_zbits(client, dns_rdatatype_any, ++ DNS_RPZ_TYPE_NSDNAME) == 0 && ++ rpz_get_zbits(client, dns_rdatatype_any, ++ DNS_RPZ_TYPE_NSIP) == 0) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + dns_fixedname_init(&nsnamef); + dns_name_clone(client->query.qname, dns_fixedname_name(&nsnamef)); +- while (st->r.label > client->view->rpz_min_ns_labels) { ++ while (st->r.label > client->view->rpzs->p.min_ns_labels) { + /* + * Get NS rrset for each domain in the current qname. + */ +@@ -4709,8 +5054,8 @@ + if (st->r.ns_rdataset == NULL || + !dns_rdataset_isassociated(st->r.ns_rdataset)) { + dns_db_t *db = NULL; +- result = rpz_rrset_find(client, DNS_RPZ_TYPE_NSDNAME, +- nsname, dns_rdatatype_ns, ++ result = rpz_rrset_find(client, nsname, dns_rdatatype_ns, ++ DNS_RPZ_TYPE_NSDNAME, + &db, NULL, &st->r.ns_rdataset, + resuming); + if (db != NULL) +@@ -4765,7 +5110,7 @@ + dns_rdata_reset(&nsrdata); + if (result != ISC_R_SUCCESS) { + rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, +- DNS_RPZ_TYPE_NSIP, nsname, ++ nsname, DNS_RPZ_TYPE_NSIP, + "rdata_tostruct() ", result); + st->m.policy = DNS_RPZ_POLICY_ERROR; + goto cleanup; +@@ -4782,11 +5127,11 @@ + * Check this NS name if we did not handle it + * during a previous recursion. + */ +- if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0 && +- (st->state & DNS_RPZ_HAVE_NSDNAME) != 0) { +- result = rpz_rewrite_name(client, qtype, +- &ns.name, ++ if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0) { ++ result = rpz_rewrite_name(client, &ns.name, ++ qtype, + DNS_RPZ_TYPE_NSDNAME, ++ DNS_RPZ_ALL_ZBITS, + &rdataset); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ns); +@@ -4797,9 +5142,9 @@ + /* + * Check all IP addresses for this NS name. + */ +- result = rpz_rewrite_rrsets(client, DNS_RPZ_TYPE_NSIP, +- &ns.name, dns_rdatatype_any, +- &rdataset, resuming); ++ result = rpz_rewrite_ip_rrsets(client, &ns.name, qtype, ++ DNS_RPZ_TYPE_NSIP, ++ &rdataset, resuming); + dns_rdata_freestruct(&ns); + if (result != ISC_R_SUCCESS) + goto cleanup; +@@ -4809,10 +5154,16 @@ + } while (result == ISC_R_SUCCESS); + dns_rdataset_disassociate(st->r.ns_rdataset); + st->r.label--; ++ ++ if (rpz_get_zbits(client, dns_rdatatype_any, ++ DNS_RPZ_TYPE_NSDNAME) == 0 && ++ rpz_get_zbits(client, dns_rdatatype_any, ++ DNS_RPZ_TYPE_NSIP) == 0) ++ break; + } + + /* +- * Use the best, if any, hit. ++ * Use the best hit, if any. + */ + result = ISC_R_SUCCESS; + +@@ -4827,7 +5178,7 @@ + if (st->m.policy == DNS_RPZ_POLICY_PASSTHRU && + result != DNS_R_DELEGATION) + rpz_log_rewrite(client, ISC_FALSE, st->m.policy, +- st->m.type, st->m.zone, st->qname); ++ st->m.type, st->m.zone, st->p_name); + rpz_match_clear(st); + } + if (st->m.policy == DNS_RPZ_POLICY_ERROR) { +@@ -4846,19 +5197,25 @@ + * by the client in DNSSEC or a lack of signatures. + */ + static isc_boolean_t +-rpz_ck_dnssec(ns_client_t *client, isc_result_t result, ++rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) + { + dns_fixedname_t fixed; + dns_name_t *found; + dns_rdataset_t trdataset; + dns_rdatatype_t type; ++ isc_result_t result; + +- if (client->view->rpz_break_dnssec) ++ if (client->view->rpzs->p.break_dnssec || !WANTDNSSEC(client)) + return (ISC_TRUE); ++ + /* +- * sigrdataset == NULL if and only !WANTDNSSEC(client) ++ * We do not know if there are signatures if we have not recursed ++ * for them. + */ ++ if (qresult == DNS_R_DELEGATION || qresult == ISC_R_NOTFOUND) ++ return (ISC_FALSE); ++ + if (sigrdataset == NULL) + return (ISC_TRUE); + if (dns_rdataset_isassociated(sigrdataset)) +@@ -4938,7 +5295,7 @@ + if (result != ISC_R_SUCCESS) + return (result); + rpz_log_rewrite(client, ISC_FALSE, st->m.policy, +- st->m.type, st->m.zone, st->qname); ++ st->m.type, st->m.zone, st->p_name); + ns_client_qnamereplace(client, fname); + /* + * Turn off DNSSEC because the results of a +@@ -5865,8 +6222,130 @@ + resume: + CTRACE("query_find: resume"); + +- if (!ISC_LIST_EMPTY(client->view->rpz_zones) && +- (RECURSIONOK(client) || !client->view->rpz_recursive_only) && ++ /* ++ * Rate limit these responses to this client. ++ * Do not delay counting and handling obvious referrals, ++ * since those won't come here again. ++ * Delay handling delegations for which we are certain to recurse and ++ * return here (DNS_R_DELEGATION, not a child of one of our ++ * own zones, and recursion enabled) ++ * Count each response at most once. ++ */ ++ if (client->view->rrl != NULL && ++ ((fname != NULL && dns_name_isabsolute(fname)) || ++ (result == ISC_R_NOTFOUND && !RECURSIONOK(client))) && ++ !(result == DNS_R_DELEGATION && !is_zone && RECURSIONOK(client)) && ++ (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, resp_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); ++ resp_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); ++ } ++ resp_result = DNS_R_NXDOMAIN; ++ } else if (result == DNS_R_NXRRSET || ++ result == DNS_R_EMPTYNAME) { ++ resp_result = DNS_R_NXRRSET; ++ } else if (result == DNS_R_DELEGATION) { ++ resp_result = result; ++ } else if (result == ISC_R_NOTFOUND) { ++ /* ++ * Handle referral to ".", including when recursion ++ * is off or not requested and the hints have not ++ * been loaded or we have "additional-from-cache no". ++ */ ++ tname = dns_rootname; ++ resp_result = DNS_R_DELEGATION; ++ } else { ++ resp_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, ++ resp_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_client_log(client, ++ NS_LOGCATEGORY_QUERY_EERRORS, ++ NS_LOGMODULE_QUERY, ++ 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; ++ if (resp_result == DNS_R_NXDOMAIN) ++ client->message->rcode = ++ dns_rcode_nxdomain; ++ } ++ goto cleanup; ++ } ++ } ++ } ++ ++ if (client->view->rpzs != NULL && client->view->rpzs->p.num_zones != 0 && ++ (RECURSIONOK(client) || client->view->rpzs->p.no_rd_ok != 0) && + rpz_ck_dnssec(client, result, rdataset, sigrdataset) && + !RECURSING(client) && + (client->query.rpz_st == NULL || +@@ -5910,7 +6389,8 @@ + if (rpz_st->m.policy != DNS_RPZ_POLICY_MISS && + rpz_st->m.policy != DNS_RPZ_POLICY_PASSTHRU && + rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) { +- if (rpz_st->m.type == DNS_RPZ_TYPE_QNAME) { ++ if (rpz_st->m.type == DNS_RPZ_TYPE_QNAME || ++ rpz_st->m.type == DNS_RPZ_TYPE_CLIENT_IP) { + result = dns_name_copy(client->query.qname, + fname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); +@@ -5934,6 +6414,9 @@ + rpz_st->m.zone = NULL; + + switch (rpz_st->m.policy) { ++ case DNS_RPZ_POLICY_DROP: ++ QUERY_ERROR(DNS_R_DROP); ++ break; + case DNS_RPZ_POLICY_NXDOMAIN: + result = DNS_R_NXDOMAIN; + break; +@@ -6002,7 +6485,7 @@ + rpz_st->q.is_zone = is_zone; + is_zone = ISC_TRUE; + rpz_log_rewrite(client, ISC_FALSE, rpz_st->m.policy, +- rpz_st->m.type, zone, rpz_st->qname); ++ rpz_st->m.type, zone, rpz_st->p_name); + } + } + +@@ -7318,12 +7801,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 +@@ -373,7 +373,8 @@ + static isc_result_t + configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, + const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, +- cfg_aclconfctx_t *aclconf, isc_boolean_t added); ++ cfg_aclconfctx_t *aclconf, isc_boolean_t added, ++ isc_boolean_t old_rpz_ok); + + static isc_result_t + add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx); +@@ -1549,17 +1550,24 @@ + } + + static isc_result_t +-configure_rpz(dns_view_t *view, const cfg_listelt_t *element, +- isc_boolean_t recursive_only_def, dns_ttl_t ttl_def) ++configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element, ++ isc_boolean_t recursive_only_def, dns_ttl_t ttl_def, ++ const dns_rpz_zone_t *old, isc_boolean_t *old_rpz_okp) + { + const cfg_obj_t *rpz_obj, *obj; + const char *str; +- dns_rpz_zone_t *old, *new; ++ dns_rpz_zone_t *new; + isc_result_t result; ++ dns_rpz_num_t rpz_num; ++ ++ REQUIRE(old != NULL || !*old_rpz_okp); + + rpz_obj = cfg_listelt_value(element); + +- new = isc_mem_get(view->mctx, sizeof(*new)); ++ if (view->rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES) ++ return (ISC_R_NOMEMORY); ++ ++ new = isc_mem_get(view->rpzs->mctx, sizeof(*new)); + if (new == NULL) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "no memory for response policy zones"); +@@ -1567,20 +1575,28 @@ + } + + memset(new, 0, sizeof(*new)); ++ result = isc_refcount_init(&new->refs, 1); ++ if (result != ISC_R_SUCCESS) { ++ isc_mem_put(view->rpzs->mctx, new, sizeof(*new)); ++ return (result); ++ } + dns_name_init(&new->origin, NULL); ++ dns_name_init(&new->client_ip, NULL); ++ dns_name_init(&new->ip, NULL); + dns_name_init(&new->nsdname, NULL); ++ dns_name_init(&new->nsip, NULL); + dns_name_init(&new->passthru, NULL); ++ dns_name_init(&new->drop, NULL); + dns_name_init(&new->cname, NULL); +- ISC_LIST_INITANDAPPEND(view->rpz_zones, new, link); ++ new->num = view->rpzs->p.num_zones++; ++ view->rpzs->zones[new->num] = new; + + obj = cfg_tuple_get(rpz_obj, "recursive-only"); +- if (cfg_obj_isvoid(obj)) { +- new->recursive_only = recursive_only_def; ++ if (cfg_obj_isvoid(obj) ? recursive_only_def : cfg_obj_asboolean(obj)) { ++ view->rpzs->p.no_rd_ok &= ~DNS_RPZ_ZBIT(new->num); + } else { +- new->recursive_only = cfg_obj_asboolean(obj); ++ view->rpzs->p.no_rd_ok |= DNS_RPZ_ZBIT(new->num); + } +- if (!new->recursive_only) +- view->rpz_recursive_only = ISC_FALSE; + + obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); + if (cfg_obj_isuint32(obj)) { +@@ -1588,6 +1604,8 @@ + } else { + new->max_policy_ttl = ttl_def; + } ++ if (*old_rpz_okp && new->max_policy_ttl != old->max_policy_ttl) ++ *old_rpz_okp = ISC_FALSE; + + str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "zone name")); + result = configure_rpz_name(view, rpz_obj, &new->origin, str, "zone"); +@@ -1598,25 +1616,45 @@ + "invalid zone name '%s'", str); + return (DNS_R_EMPTYLABEL); + } +- for (old = ISC_LIST_HEAD(view->rpz_zones); +- old != new; +- old = ISC_LIST_NEXT(old, link)) { +- ++new->num; +- if (dns_name_equal(&old->origin, &new->origin)) { ++ for (rpz_num = 0; rpz_num < view->rpzs->p.num_zones-1; ++rpz_num) { ++ if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, ++ &new->origin)) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "duplicate '%s'", str); + result = DNS_R_DUPLICATE; + return (result); + } + } ++ if (*old_rpz_okp && !dns_name_equal(&old->origin, &new->origin)) ++ *old_rpz_okp = ISC_FALSE; ++ ++ result = configure_rpz_name2(view, rpz_obj, &new->client_ip, ++ DNS_RPZ_CLIENT_IP_ZONE, &new->origin); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ ++ result = configure_rpz_name2(view, rpz_obj, &new->ip, ++ DNS_RPZ_IP_ZONE, &new->origin); ++ if (result != ISC_R_SUCCESS) ++ return (result); + + result = configure_rpz_name2(view, rpz_obj, &new->nsdname, + DNS_RPZ_NSDNAME_ZONE, &new->origin); + if (result != ISC_R_SUCCESS) + return (result); + ++ result = configure_rpz_name2(view, rpz_obj, &new->nsip, ++ DNS_RPZ_NSIP_ZONE, &new->origin); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ + result = configure_rpz_name(view, rpz_obj, &new->passthru, +- DNS_RPZ_PASSTHRU_ZONE, "zone"); ++ DNS_RPZ_PASSTHRU_NAME, "name"); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ ++ result = configure_rpz_name(view, rpz_obj, &new->drop, ++ DNS_RPZ_DROP_NAME, "name"); + if (result != ISC_R_SUCCESS) + return (result); + +@@ -1635,10 +1673,282 @@ + return (result); + } + } ++ if (*old_rpz_okp && (new->policy != old->policy || ++ !dns_name_equal(&old->cname, &new->cname))) ++ *old_rpz_okp = ISC_FALSE; + + return (ISC_R_SUCCESS); + } + ++static isc_result_t ++configure_rpz(dns_view_t *view, const cfg_obj_t *rpz_obj, ++ isc_boolean_t *old_rpz_okp) ++{ ++ const cfg_listelt_t *zone_element; ++ const cfg_obj_t *sub_obj; ++ isc_boolean_t recursive_only_def; ++ dns_ttl_t ttl_def; ++ dns_rpz_zones_t *new; ++ const dns_rpz_zones_t *old; ++ dns_view_t *pview; ++ const dns_rpz_zone_t *old_zone; ++ isc_result_t result; ++ int i; ++ ++ *old_rpz_okp = ISC_FALSE; ++ ++ zone_element = cfg_list_first(cfg_tuple_get(rpz_obj, "zone list")); ++ if (zone_element == NULL) ++ return (ISC_R_SUCCESS); ++ ++ result = dns_rpz_new_zones(&view->rpzs, view->mctx); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ new = view->rpzs; ++ ++ sub_obj = cfg_tuple_get(rpz_obj, "recursive-only"); ++ if (!cfg_obj_isvoid(sub_obj) && ++ !cfg_obj_asboolean(sub_obj)) ++ recursive_only_def = ISC_FALSE; ++ else ++ recursive_only_def = ISC_TRUE; ++ ++ sub_obj = cfg_tuple_get(rpz_obj, "break-dnssec"); ++ if (!cfg_obj_isvoid(sub_obj) && ++ cfg_obj_asboolean(sub_obj)) ++ new->p.break_dnssec = ISC_TRUE; ++ else ++ new->p.break_dnssec = ISC_FALSE; ++ ++ sub_obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); ++ if (cfg_obj_isuint32(sub_obj)) ++ ttl_def = cfg_obj_asuint32(sub_obj); ++ else ++ ttl_def = DNS_RPZ_MAX_TTL_DEFAULT; ++ ++ sub_obj = cfg_tuple_get(rpz_obj, "min-ns-dots"); ++ if (cfg_obj_isuint32(sub_obj)) ++ new->p.min_ns_labels = cfg_obj_asuint32(sub_obj) + 1; ++ else ++ new->p.min_ns_labels = 2; ++ ++ sub_obj = cfg_tuple_get(rpz_obj, "qname-wait-recurse"); ++ if (cfg_obj_isvoid(sub_obj) || cfg_obj_asboolean(sub_obj)) ++ new->p.qname_wait_recurse = ISC_TRUE; ++ else ++ new->p.qname_wait_recurse = ISC_FALSE; ++ ++ pview = NULL; ++ result = dns_viewlist_find(&ns_g_server->viewlist, ++ view->name, view->rdclass, &pview); ++ if (result == ISC_R_SUCCESS) { ++ old = pview->rpzs; ++ } else { ++ old = NULL; ++ } ++ if (old == NULL) ++ *old_rpz_okp = ISC_FALSE; ++ else ++ *old_rpz_okp = ISC_TRUE; ++ ++ for (i = 0; ++ zone_element != NULL; ++ ++i, zone_element = cfg_list_next(zone_element)) { ++ if (*old_rpz_okp && i < old->p.num_zones) { ++ old_zone = old->zones[i]; ++ } else { ++ *old_rpz_okp = ISC_FALSE; ++ old_zone = NULL; ++ } ++ result = configure_rpz_zone(view, zone_element, ++ recursive_only_def, ttl_def, ++ old_zone, old_rpz_okp); ++ if (result != ISC_R_SUCCESS) { ++ if (pview != NULL) ++ dns_view_detach(&pview); ++ return (result); ++ } ++ } ++ ++ /* ++ * If this is a reloading and the parameters and list of policy ++ * zones are unchanged, then use the same policy data. ++ * Data for individual zones that must be reloaded will be merged. ++ */ ++ if (old != NULL && memcmp(&old->p, &new->p, sizeof(new->p)) != 0) ++ *old_rpz_okp = ISC_FALSE; ++ if (*old_rpz_okp) { ++ dns_rpz_detach_rpzs(&view->rpzs); ++ dns_rpz_attach_rpzs(pview->rpzs, &view->rpzs); ++ } ++ if (pview != NULL) ++ dns_view_detach(&pview); ++ ++ return (ISC_R_SUCCESS); ++} ++ ++#define CHECK_RRL(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) ++ ++#define CHECK_RRL_RATE(rate, def, max_rate, name) \ ++ do { \ ++ obj = NULL; \ ++ rrl->rate.str = name; \ ++ result = cfg_map_get(map, name, &obj); \ ++ if (result == ISC_R_SUCCESS) { \ ++ rrl->rate.r = cfg_obj_asuint32(obj); \ ++ CHECK_RRL(rrl->rate.r <= max_rate, \ ++ name" %d > %d", \ ++ rrl->rate.r, max_rate); \ ++ } else { \ ++ rrl->rate.r = def; \ ++ } \ ++ rrl->rate.scaled = rrl->rate.r; \ ++ } while (0) ++ ++static isc_result_t ++configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) { ++ const cfg_obj_t *obj; ++ 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(i >= min_entries, ++ "max-table-size %d < min-table-size %d", ++ i, min_entries); ++ } ++ rrl->max_entries = i; ++ ++ CHECK_RRL_RATE(responses_per_second, 0, DNS_RRL_MAX_RATE, ++ "responses-per-second"); ++ CHECK_RRL_RATE(referrals_per_second, ++ rrl->responses_per_second.r, DNS_RRL_MAX_RATE, ++ "referrals-per-second"); ++ CHECK_RRL_RATE(nodata_per_second, ++ rrl->responses_per_second.r, DNS_RRL_MAX_RATE, ++ "nodata-per-second"); ++ CHECK_RRL_RATE(nxdomains_per_second, ++ rrl->responses_per_second.r, DNS_RRL_MAX_RATE, ++ "nxdomains-per-second"); ++ CHECK_RRL_RATE(errors_per_second, ++ rrl->responses_per_second.r, DNS_RRL_MAX_RATE, ++ "errors-per-second"); ++ ++ CHECK_RRL_RATE(all_per_second, 0, DNS_RRL_MAX_RATE, ++ "all-per-second"); ++ ++ CHECK_RRL_RATE(slip, 2, DNS_RRL_MAX_SLIP, ++ "slip"); ++ ++ i = 15; ++ obj = NULL; ++ result = cfg_map_get(map, "window", &obj); ++ if (result == ISC_R_SUCCESS) { ++ i = cfg_obj_asuint32(obj); ++ CHECK_RRL(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(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(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(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(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'. +@@ -1705,7 +2015,7 @@ + dns_acl_t *clients = NULL, *mapped = NULL, *excluded = NULL; + unsigned int query_timeout, ndisp; + struct cfg_context *nzctx; +- dns_rpz_zone_t *rpz; ++ isc_boolean_t old_rpz_ok; + + REQUIRE(DNS_VIEW_VALID(view)); + +@@ -1810,44 +2120,7 @@ + obj = NULL; + if (view->rdclass == dns_rdataclass_in && need_hints && + ns_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS) { +- const cfg_obj_t *rpz_obj; +- isc_boolean_t recursive_only_def; +- dns_ttl_t ttl_def; +- +- rpz_obj = cfg_tuple_get(obj, "recursive-only"); +- if (!cfg_obj_isvoid(rpz_obj) && +- !cfg_obj_asboolean(rpz_obj)) +- recursive_only_def = ISC_FALSE; +- else +- recursive_only_def = ISC_TRUE; +- +- rpz_obj = cfg_tuple_get(obj, "break-dnssec"); +- if (!cfg_obj_isvoid(rpz_obj) && +- cfg_obj_asboolean(rpz_obj)) +- view->rpz_break_dnssec = ISC_TRUE; +- else +- view->rpz_break_dnssec = ISC_FALSE; +- +- rpz_obj = cfg_tuple_get(obj, "max-policy-ttl"); +- if (cfg_obj_isuint32(rpz_obj)) +- ttl_def = cfg_obj_asuint32(rpz_obj); +- else +- ttl_def = DNS_RPZ_MAX_TTL_DEFAULT; +- +- rpz_obj = cfg_tuple_get(obj, "min-ns-dots"); +- if (cfg_obj_isuint32(rpz_obj)) +- view->rpz_min_ns_labels = cfg_obj_asuint32(rpz_obj) + 1; +- else +- view->rpz_min_ns_labels = 2; +- +- element = cfg_list_first(cfg_tuple_get(obj, "zone list")); +- while (element != NULL) { +- result = configure_rpz(view, element, +- recursive_only_def, ttl_def); +- if (result != ISC_R_SUCCESS) +- goto cleanup; +- element = cfg_list_next(element); +- } ++ CHECK(configure_rpz(view, obj, &old_rpz_ok)); + } + + /* +@@ -1868,22 +2141,29 @@ + { + const cfg_obj_t *zconfig = cfg_listelt_value(element); + CHECK(configure_zone(config, zconfig, vconfig, mctx, view, +- actx, ISC_FALSE)); ++ actx, ISC_FALSE, old_rpz_ok)); + } + +- for (rpz = ISC_LIST_HEAD(view->rpz_zones); +- rpz != NULL; +- rpz = ISC_LIST_NEXT(rpz, link)) +- { +- if (!rpz->defined) { +- char namebuf[DNS_NAME_FORMATSIZE]; ++ /* ++ * Check that a master or slave zone was found for each ++ * zone named in the response policy statement. ++ */ ++ if (view->rpzs != NULL) { ++ dns_rpz_num_t n; + +- dns_name_format(&rpz->origin, namebuf, sizeof(namebuf)); +- cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, +- "'%s' is not a master or slave zone", +- namebuf); +- result = ISC_R_NOTFOUND; +- goto cleanup; ++ for (n = 0; n < view->rpzs->p.num_zones; ++n) ++ { ++ if ((view->rpzs->defined & DNS_RPZ_ZBIT(n)) == 0) { ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ ++ dns_name_format(&view->rpzs->zones[n]->origin, ++ namebuf, sizeof(namebuf)); ++ cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, ++ "'%s' is not a master or slave zone", ++ namebuf); ++ result = ISC_R_NOTFOUND; ++ goto cleanup; ++ } + } + } + +@@ -1909,7 +2189,7 @@ + const cfg_obj_t *zconfig = cfg_listelt_value(element); + CHECK(configure_zone(config, zconfig, vconfig, + mctx, view, actx, +- ISC_TRUE)); ++ ISC_TRUE, ISC_FALSE)); + } + } + +@@ -3043,6 +3323,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: +@@ -3375,7 +3663,8 @@ + static isc_result_t + configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, + const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, +- cfg_aclconfctx_t *aclconf, isc_boolean_t added) ++ cfg_aclconfctx_t *aclconf, isc_boolean_t added, ++ isc_boolean_t old_rpz_ok) + { + dns_view_t *pview = NULL; /* Production view */ + dns_zone_t *zone = NULL; /* New or reused zone */ +@@ -3396,8 +3685,7 @@ + const char *zname; + dns_rdataclass_t zclass; + const char *ztypestr; +- isc_boolean_t is_rpz; +- dns_rpz_zone_t *rpz; ++ dns_rpz_num_t rpz_num; + + options = NULL; + (void)cfg_map_get(config, "options", &options); +@@ -3559,18 +3847,15 @@ + INSIST(dupzone == NULL); + + /* +- * Note whether this is a response policy zone. ++ * Note whether this is a response policy zone and which one if so. + */ +- is_rpz = ISC_FALSE; +- for (rpz = ISC_LIST_HEAD(view->rpz_zones); +- rpz != NULL; +- rpz = ISC_LIST_NEXT(rpz, link)) +- { +- if (dns_name_equal(&rpz->origin, origin)) { +- is_rpz = ISC_TRUE; +- rpz->defined = ISC_TRUE; ++ for (rpz_num = 0; ; ++rpz_num) { ++ if (view->rpzs == NULL || rpz_num >= view->rpzs->p.num_zones) { ++ rpz_num = DNS_RPZ_INVALID_NUM; + break; + } ++ if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, origin)) ++ break; + } + + /* +@@ -3581,7 +3866,9 @@ + * - The zone is compatible with the config + * options (e.g., an existing master zone cannot + * be reused if the options specify a slave zone) +- * - The zone was and is or was not and is not a policy zone ++ * - The zone was not and is still not a response policy zone ++ * or the zone is a policy zone with an unchanged number ++ * and we are using the old policy zone summary data. + */ + result = dns_viewlist_find(&ns_g_server->viewlist, view->name, + view->rdclass, &pview); +@@ -3595,7 +3882,8 @@ + if (zone != NULL && !ns_zone_reusable(zone, zconfig)) + dns_zone_detach(&zone); + +- if (zone != NULL && is_rpz != dns_zone_get_rpz(zone)) ++ if (zone != NULL && (rpz_num != dns_zone_get_rpz_num(zone) || ++ (rpz_num != DNS_RPZ_INVALID_NUM && !old_rpz_ok))) + dns_zone_detach(&zone); + + if (zone != NULL) { +@@ -3620,8 +3908,8 @@ + dns_zone_setstats(zone, ns_g_server->zonestats); + } + +- if (is_rpz) { +- result = dns_zone_rpz_enable(zone); ++ if (rpz_num != DNS_RPZ_INVALID_NUM) { ++ result = dns_zone_rpz_enable(zone, view->rpzs, rpz_num); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, +@@ -7834,7 +8122,8 @@ + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_view_thaw(view); + result = configure_zone(cfg->config, parms, vconfig, +- server->mctx, view, cfg->actx, ISC_FALSE); ++ server->mctx, view, cfg->actx, ISC_FALSE, ++ ISC_FALSE); + dns_view_freeze(view); + isc_task_endexclusive(server->task); + if (result != ISC_R_SUCCESS) +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 +@@ -206,6 +206,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"); + SET_NSSTATDESC(rpz_rewrites, "response policy zone rewrites", + "RPZRewrites"); + INSIST(i == dns_nsstatscounter_max); +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 +@@ -62,7 +62,7 @@ + database dlv dlvauto dlz dlzexternal dname dns64 dnssec ecdsa + formerr forward glue gost ixfr inline limits logfileconfig + lwresd masterfile masterformat metadata notify nsupdate pending +- pkcs11 redirect resolver rndc rpz rrsetorder rsabigexponent ++ pkcs11 redirect resolver rndc rpz rrl rrsetorder rsabigexponent + smartsign sortlist spf staticstub stub tkey tsig tsiggss unknown + upforwd verify views wildcard xfer xferquota zonechecks" + +diff -r -u bin/tests/system/rpz/Makefile.in-orig bin/tests/system/rpz/Makefile.in +--- bin/tests/system/rpz/Makefile.in-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/Makefile.in 2004-01-01 00:00:00.000000000 +0000 +@@ -12,8 +12,6 @@ + # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + # PERFORMANCE OF THIS SOFTWARE. + +-# $Id$ +- + + srcdir = @srcdir@ + VPATH = @srcdir@ +diff -r -u bin/tests/system/rpz/clean.sh-orig bin/tests/system/rpz/clean.sh +--- bin/tests/system/rpz/clean.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/clean.sh 2004-01-01 00:00:00.000000000 +0000 +@@ -12,8 +12,6 @@ + # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + # PERFORMANCE OF THIS SOFTWARE. + +-# $Id$ +- + + + # Clean up after rpz tests. +diff -r -u bin/tests/system/rpz/ns1/named.conf-orig bin/tests/system/rpz/ns1/named.conf +--- bin/tests/system/rpz/ns1/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns1/named.conf 2004-01-01 00:00:00.000000000 +0000 +@@ -1,5 +1,5 @@ + /* +- * Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC") ++ * Copyright (C) 2011-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 +@@ -14,8 +14,6 @@ + * PERFORMANCE OF THIS SOFTWARE. + */ + +-/* $Id$ */ +- + + controls { /* empty */ }; + +diff -r -u bin/tests/system/rpz/ns1/root.db-orig bin/tests/system/rpz/ns1/root.db +--- bin/tests/system/rpz/ns1/root.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns1/root.db 2004-01-01 00:00:00.000000000 +0000 +@@ -12,8 +12,6 @@ + ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + ; PERFORMANCE OF THIS SOFTWARE. + +-; $Id$ +- + + $TTL 120 + . SOA ns. hostmaster.ns. ( 1 3600 1200 604800 60 ) +diff -r -u bin/tests/system/rpz/ns2/base-tld2s.db-orig bin/tests/system/rpz/ns2/base-tld2s.db +--- bin/tests/system/rpz/ns2/base-tld2s.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns2/base-tld2s.db 2004-01-01 00:00:00.000000000 +0000 +@@ -1,4 +1,4 @@ +-; Copyright (C) 2012, 2013 Internet Systems Consortium, Inc. ("ISC") ++; Copyright (C) 2012 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 +@@ -12,8 +12,6 @@ + ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + ; PERFORMANCE OF THIS SOFTWARE. + +-; $Id$ +- + + + ; RPZ rewrite responses from this signed zone +diff -r -u bin/tests/system/rpz/ns2/hints-orig bin/tests/system/rpz/ns2/hints +--- bin/tests/system/rpz/ns2/hints-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns2/hints 2004-01-01 00:00:00.000000000 +0000 +@@ -1,4 +1,4 @@ +-; Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC") ++; Copyright (C) 2011-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 +@@ -12,8 +12,6 @@ + ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + ; PERFORMANCE OF THIS SOFTWARE. + +-; $Id$ +- + + . 120 NS ns. + ns. 120 A 10.53.0.1 +diff -r -u bin/tests/system/rpz/ns2/named.conf-orig bin/tests/system/rpz/ns2/named.conf +--- bin/tests/system/rpz/ns2/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns2/named.conf 2004-01-01 00:00:00.000000000 +0000 +@@ -14,8 +14,6 @@ + * PERFORMANCE OF THIS SOFTWARE. + */ + +-/* $Id$ */ +- + + + controls { /* empty */ }; +diff -r -u bin/tests/system/rpz/ns2/tld2.db-orig bin/tests/system/rpz/ns2/tld2.db +--- bin/tests/system/rpz/ns2/tld2.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns2/tld2.db 2004-01-01 00:00:00.000000000 +0000 +@@ -12,8 +12,6 @@ + ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + ; PERFORMANCE OF THIS SOFTWARE. + +-; $Id$ +- + + + ; RPZ rewrite responses from this zone +@@ -111,6 +109,9 @@ + A 192.168.5.2 + TXT "a5-1-2 tld2 text" + ++a5-2 A 192.168.5.2 ++ TXT "a5-2 tld2 text" ++ + a5-3 A 192.168.5.3 + TXT "a5-3 tld2 text" + +diff -r -u bin/tests/system/rpz/ns3/base.db-orig bin/tests/system/rpz/ns3/base.db +--- bin/tests/system/rpz/ns3/base.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns3/base.db 2004-01-01 00:00:00.000000000 +0000 +@@ -12,8 +12,6 @@ + ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + ; PERFORMANCE OF THIS SOFTWARE. + +-; $Id$ +- + + + ; RPZ test +diff -r -u bin/tests/system/rpz/ns3/hints-orig bin/tests/system/rpz/ns3/hints +--- bin/tests/system/rpz/ns3/hints-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns3/hints 2004-01-01 00:00:00.000000000 +0000 +@@ -1,4 +1,4 @@ +-; Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC") ++; Copyright (C) 2011-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 +@@ -12,8 +12,6 @@ + ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + ; PERFORMANCE OF THIS SOFTWARE. + +-; $Id$ +- + + . 120 NS ns. + ns. 120 A 10.53.0.1 +diff -r -u bin/tests/system/rpz/ns3/named.conf-orig bin/tests/system/rpz/ns3/named.conf +--- bin/tests/system/rpz/ns3/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns3/named.conf 2004-01-01 00:00:00.000000000 +0000 +@@ -14,8 +14,6 @@ + * PERFORMANCE OF THIS SOFTWARE. + */ + +-/* $Id$ */ +- + + + /* +@@ -46,7 +44,10 @@ + zone "bl-cname" policy cname txt-only.tld2.; + zone "bl-wildcname" policy cname *.tld4.; + zone "bl-garden" policy cname a12.tld2.; +- } min-ns-dots 0; ++ zone "bl-drop" policy drop; ++ } min-ns-dots 0 ++ # qname-wait-recurse no ++ ; + }; + + key rndc_key { +@@ -58,7 +59,6 @@ + }; + + +-// include "../trusted.conf"; + zone "." { type hint; file "hints"; }; + + zone "bl." {type master; file "bl.db"; +@@ -83,6 +83,8 @@ + allow-update {any;};}; + zone "bl-garden." {type master; file "bl-garden.db"; + allow-update {any;};}; ++zone "bl-drop." {type master; file "bl-drop.db"; ++ allow-update {any;};}; + + zone "crash1.tld2" {type master; file "crash1";}; + zone "crash2.tld3." {type master; file "crash2";}; +diff -r -u bin/tests/system/rpz/ns4/hints-orig bin/tests/system/rpz/ns4/hints +--- bin/tests/system/rpz/ns4/hints-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns4/hints 2004-01-01 00:00:00.000000000 +0000 +@@ -1,4 +1,4 @@ +-; Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC") ++; Copyright (C) 2011-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 +@@ -12,8 +12,6 @@ + ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + ; PERFORMANCE OF THIS SOFTWARE. + +-; $Id$ +- + + . 120 NS ns. + ns. 120 A 10.53.0.1 +diff -r -u bin/tests/system/rpz/ns4/named.conf-orig bin/tests/system/rpz/ns4/named.conf +--- bin/tests/system/rpz/ns4/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns4/named.conf 2004-01-01 00:00:00.000000000 +0000 +@@ -14,8 +14,6 @@ + * PERFORMANCE OF THIS SOFTWARE. + */ + +-/* $Id$ */ +- + + controls { /* empty */ }; + +diff -r -u bin/tests/system/rpz/ns4/tld4.db-orig bin/tests/system/rpz/ns4/tld4.db +--- bin/tests/system/rpz/ns4/tld4.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns4/tld4.db 2004-01-01 00:00:00.000000000 +0000 +@@ -12,8 +12,6 @@ + ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + ; PERFORMANCE OF THIS SOFTWARE. + +-; $Id$ +- + + ; RPZ rewrite responses from this zone + +diff -r -u bin/tests/system/rpz/ns5/hints-orig bin/tests/system/rpz/ns5/hints +--- bin/tests/system/rpz/ns5/hints-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns5/hints 2004-01-01 00:00:00.000000000 +0000 +@@ -12,8 +12,6 @@ + ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + ; PERFORMANCE OF THIS SOFTWARE. + +-; $Id$ +- + + . 120 NS ns. + ns. 120 A 10.53.0.1 +diff -r -u bin/tests/system/rpz/ns5/named.conf-orig bin/tests/system/rpz/ns5/named.conf +--- bin/tests/system/rpz/ns5/named.conf-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns5/named.conf 2004-01-01 00:00:00.000000000 +0000 +@@ -1,5 +1,5 @@ + /* +- * Copyright (C) 2012, 2013 Internet Systems Consortium, Inc. ("ISC") ++ * Copyright (C) 2012 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 +@@ -14,8 +14,6 @@ + * PERFORMANCE OF THIS SOFTWARE. + */ + +-/* $Id$ */ +- + + + /* +@@ -56,3 +54,20 @@ + zone "bl0." {type master; file "bl.db"; }; + zone "bl1." {type master; file "bl.db"; }; + zone "bl2." {type master; file "bl.db"; }; ++zone "bl3." {type master; file "bl.db"; }; ++zone "bl4." {type master; file "bl.db"; }; ++zone "bl5." {type master; file "bl.db"; }; ++zone "bl6." {type master; file "bl.db"; }; ++zone "bl7." {type master; file "bl.db"; }; ++zone "bl8." {type master; file "bl.db"; }; ++zone "bl9." {type master; file "bl.db"; }; ++zone "bl10." {type master; file "bl.db"; }; ++zone "bl11." {type master; file "bl.db"; }; ++zone "bl12." {type master; file "bl.db"; }; ++zone "bl13." {type master; file "bl.db"; }; ++zone "bl14." {type master; file "bl.db"; }; ++zone "bl15." {type master; file "bl.db"; }; ++zone "bl16." {type master; file "bl.db"; }; ++zone "bl17." {type master; file "bl.db"; }; ++zone "bl18." {type master; file "bl.db"; }; ++zone "bl19." {type master; file "bl.db"; }; +diff -r -u bin/tests/system/rpz/ns5/tld5.db-orig bin/tests/system/rpz/ns5/tld5.db +--- bin/tests/system/rpz/ns5/tld5.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/ns5/tld5.db 2004-01-01 00:00:00.000000000 +0000 +@@ -1,4 +1,4 @@ +-; Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") ++; Copyright (C) 2012 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 +diff -r -u bin/tests/system/rpz/qperf.sh-orig bin/tests/system/rpz/qperf.sh +--- bin/tests/system/rpz/qperf.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/qperf.sh 2004-01-01 00:00:00.000000000 +0000 +@@ -1,6 +1,6 @@ + #! /bin/sh + # +-# Copyright (C) 2012, 2013 Internet Systems Consortium, Inc. ("ISC") ++# Copyright (C) 2012 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 +@@ -14,8 +14,6 @@ + # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + # PERFORMANCE OF THIS SOFTWARE. + +-# $Id$ +- + + for QDIR in `echo "$PATH" | tr : ' '` ../../../../contrib/queryperf; do + QPERF=$QDIR/queryperf +diff -r -u bin/tests/system/rpz/rpz.c-orig bin/tests/system/rpz/rpz.c +--- bin/tests/system/rpz/rpz.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/rpz.c 2004-01-01 00:00:00.000000000 +0000 +@@ -14,8 +14,6 @@ + * PERFORMANCE OF THIS SOFTWARE. + */ + +-/* $Id$ */ +- + + #include + +diff -r -u bin/tests/system/rpz/setup.sh-orig bin/tests/system/rpz/setup.sh +--- bin/tests/system/rpz/setup.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/setup.sh 2004-01-01 00:00:00.000000000 +0000 +@@ -14,8 +14,6 @@ + # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + # PERFORMANCE OF THIS SOFTWARE. + +-# $Id$ +- + + set -e + +@@ -30,7 +28,7 @@ + # bl-{given,disabled,passthru,no-data,nxdomain,cname,wildcard,garden} + # are used to check policy overrides in named.conf. + # NO-OP is an obsolete synonym for PASSHTRU +-for NM in '' -2 -given -disabled -passthru -no-op -nodata -nxdomain -cname -wildcname -garden; do ++for NM in '' -2 -given -disabled -passthru -no-op -nodata -nxdomain -cname -wildcname -garden -drop; do + sed -e "/SOA/s/blx/bl$NM/g" ns3/base.db >ns3/bl$NM.db + done + +@@ -51,7 +49,10 @@ + # Performance checks. + cat <ns5/rpz-switch + response-policy { +- zone "bl0"; zone "bl1"; zone "bl2"; ++ zone "bl0"; zone "bl1"; zone "bl2"; zone "bl3"; zone "bl4"; ++ zone "bl5"; zone "bl6"; zone "bl7"; zone "bl8"; zone "bl9"; ++ zone "bl10"; zone "bl11"; zone "bl12"; zone "bl13"; zone "bl14"; ++ zone "bl15"; zone "bl16"; zone "bl17"; zone "bl18"; zone "bl19"; + } recursive-only no + max-policy-ttl 90 + # min-ns-dots 0 +diff -r -u bin/tests/system/rpz/test1-orig bin/tests/system/rpz/test1 +--- bin/tests/system/rpz/test1-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/test1 2004-01-01 00:00:00.000000000 +0000 +@@ -24,13 +24,13 @@ + ; QNAME tests + + ; NXDOMAIN +-; 2, 20, 25 ++; 2, 25 + update add a0-1.tld2.bl. 300 CNAME . + ; NODATA +-; 3, 21 ++; 3 + update add a3-1.tld2.bl. 300 CNAME *. + ; and no assert-botch +-; 4, 5, 22, 23 ++; 4, 5 + update add a3-2.tld2.bl. 300 DNAME example.com. + ; + ; NXDOMAIN for a4-2-cname.tld2 via its target a4-2.tld2. +@@ -77,6 +77,14 @@ + ; 19 + update add a4-6.tld2.bl. 300 CNAME . + update add a4-6-cname.tld2.bl. 300 A 127.0.0.17 ++; no change instead of NXDOMAIN because +norecurse ++; 20 ++update add a5-2.tld2.bl. 300 CNAME . ++; no change instead of NODATA because +norecurse ++; 21 ++update add a5-3.tld2.bl. 300 CNAME *. ++; 22, 23 ++update add a5-4.tld2.bl. 300 DNAME example.com. + ; + ; assert in rbtdb.c + ; 24 +@@ -84,4 +92,6 @@ + ; DO=1 without signatures, DO=0 with signatures are rewritten + ; 26 - 27 + update add a0-1.tld2s.bl. 300 CNAME . ++; 32 ++update add a3-8.tld2.bl. 300 CNAME rpz-drop. + send +diff -r -u bin/tests/system/rpz/test2-orig bin/tests/system/rpz/test2 +--- bin/tests/system/rpz/test2-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/test2 2004-01-01 00:00:00.000000000 +0000 +@@ -58,7 +58,7 @@ + send + + ; prefer QNAME to IP for a5-4.tld2 +-; 13 ++; 13, 14 + update add 32.4.5.168.192.rpz-ip.bl 300 CNAME a12.tld2. + update add a5-4.tld2.bl 300 CNAME a14.tld4. + ; +@@ -72,3 +72,8 @@ + send + update add c2.crash2.tld3.bl-2 300 A 127.0.0.16 + send ++ ++; client-IP address trigger ++; 17 ++update add 32.1.0.53.10.rpz-client-ip.bl 300 A 127.0.0.17 ++send +diff -r -u bin/tests/system/rpz/test3-orig bin/tests/system/rpz/test3 +--- bin/tests/system/rpz/test3-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/test3 2004-01-01 00:00:00.000000000 +0000 +@@ -1,4 +1,4 @@ +-; Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC") ++; Copyright (C) 2011-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 +diff -r -u bin/tests/system/rpz/test4-orig bin/tests/system/rpz/test4 +--- bin/tests/system/rpz/test4-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/test4 2004-01-01 00:00:00.000000000 +0000 +@@ -1,4 +1,4 @@ +-; Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC") ++; Copyright (C) 2011-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 +diff -r -u bin/tests/system/rpz/test4a-orig bin/tests/system/rpz/test4a +--- bin/tests/system/rpz/test4a-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/test4a 2004-01-01 00:00:00.000000000 +0000 +@@ -1,4 +1,4 @@ +-; Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") ++; Copyright (C) 2011-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 +diff -r -u bin/tests/system/rpz/test5-orig bin/tests/system/rpz/test5 +--- bin/tests/system/rpz/test5-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/test5 2004-01-01 00:00:00.000000000 +0000 +@@ -57,3 +57,6 @@ + ; 16 + update add a3-16.tld2.bl. 300 A 127.0.0.16 + send ++; 18 ++update add a3-18.tld2.bl-drop. 300 A 127.0.0.18 ++send +diff -r -u bin/tests/system/rpz/tests.sh-orig bin/tests/system/rpz/tests.sh +--- bin/tests/system/rpz/tests.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ bin/tests/system/rpz/tests.sh 2004-01-01 00:00:00.000000000 +0000 +@@ -12,8 +12,6 @@ + # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + # PERFORMANCE OF THIS SOFTWARE. + +-# $Id$ +- + + # test response policy zones (RPZ) + +@@ -21,15 +19,19 @@ + . $SYSTEMTESTTOP/conf.sh + + ns=10.53.0 +-ns1=$ns.1 # root, defining the others +-ns2=$ns.2 # server whose answers are rewritten +-ns3=$ns.3 # resolve that does the rewriting +-ns4=$ns.4 # another server that is rewritten +-ns5=$ns.5 # check performance with this server ++ns1=$ns.1 # root, defining the others ++ns2=$ns.2 # authoritative server whose answers are rewritten ++ns3=$ns.3 # resolver that does the rewriting ++ns4=$ns.4 # another authoritative server that is rewritten ++ns5=$ns.5 # resolver to check performance and give un-rewritten ++ # responses for comparisons + + HAVE_CORE= + SAVE_RESULTS= +-NS3_STATS=47 ++ ++# "response policy zone rewrites" from ns3/named.stats if none of the ++# conditional tests are done. ++NS3_STATS=50 + + USAGE="$0: [-x]" + while getopts "x" c; do +@@ -87,10 +89,10 @@ + # (re)load the reponse policy zones with the rules in the file $TEST_FILE + load_db () { + if test -n "$TEST_FILE"; then +- $NSUPDATE -v $TEST_FILE || { ++ if ! $NSUPDATE -v $TEST_FILE; then + echo "I:failed to update policy zone with $TEST_FILE" + exit 1 +- } ++ fi + fi + } + +@@ -213,6 +215,13 @@ + ckresult "$*" proto.nodata + } + ++# check dropped response ++drop () { ++ make_dignm ++ digcmd $* >$DIGNM ++ ckresult "$*" proto.drop ++} ++ + # check rewrite to an address + # modify the output so that it is easily compared, but save the original line + # $1=IPv4 address $2=digcmd args $3=optional TTL +@@ -239,7 +248,7 @@ + nochange () { + make_dignm + digcmd $* >$DIGNM +- digcmd $* @$ns2 >${DIGNM}_OK ++ digcmd $* @$ns5 >${DIGNM}_OK + ckresult "$*" ${DIGNM}_OK && clean_result ${DIGNM}_OK + } + +@@ -254,6 +263,12 @@ + # make prototype files to check against rewritten results + digcmd nonexistent @$ns2 >proto.nxdomain + digcmd txt-only.tld2 @$ns2 >proto.nodata ++cat <proto.drop ++ ++; hello ++;; global options: ++;; connection timed out; no servers could be reached ++EOF + + + status=0 +@@ -278,18 +293,19 @@ + addr 57.57.57.57 a3-7.sub1.tld2 # 17 wildcard CNAME + addr 127.0.0.16 a4-5-cname3.tld2 # 18 CNAME chain + addr 127.0.0.17 a4-6-cname3.tld2 # 19 stop short in CNAME chain +-nochange a0-1.tld2 +norecurse # 20 check that RD=1 is required +-nochange a3-1.tld2 +norecurse # 21 +-nochange a3-2.tld2 +norecurse # 22 +-nochange sub.a3-2.tld2 +norecurse # 23 ++nochange a5-2.tld2 +norecurse # 20 check that RD=1 is required ++nochange a5-3.tld2 +norecurse # 21 ++nochange a5-4.tld2 +norecurse # 22 ++nochange sub.a5-4.tld2 +norecurse # 23 + nxdomain c1.crash2.tld3 # 24 assert in rbtdb.c + nxdomain a0-1.tld2 +dnssec # 25 simple DO=1 without signatures +-nxdomain a0-1.tld2s # 26 simple DO=0 with signatures ++nxdomain a0-1.tld2s +nodnssec # 26 simple DO=0 with signatures + nochange a0-1.tld2s +dnssec # 27 simple DO=1 with signatures + nxdomain a0-1s-cname.tld2s +dnssec # 28 DNSSEC too early in CNAME chain + nochange a0-1-scname.tld2 +dnssec # 29 DNSSEC on target in CNAME chain +-nochange a0-1.tld2s srv +auth +dnssec # 30 no write for +DNSSEC and no record +-nxdomain a0-1.tld2s srv # 31 ++nochange a0-1.tld2s srv +auth +dnssec # 30 no write for DNSSEC and no record ++nxdomain a0-1.tld2s srv +nodnssec # 31 ++drop a3-8.tld2 # 32 drop + end_group + + start_group "IP rewrites" test2 +@@ -305,10 +321,11 @@ + nochange a4-1-aaaa.tld2 -taaaa # 10 + addr 127.0.0.1 a5-1-2.tld2 # 11 prefer smallest policy address + addr 127.0.0.1 a5-3.tld2 # 12 prefer first conflicting IP zone +-addr 14.14.14.14 a5-4.tld2 # 13 prefer QNAME to IP +-nochange a5-4.tld2 +norecurse # 14 check that RD=1 is required ++nochange a5-4.tld2 +norecurse # 13 check that RD=1 is required for #14 ++addr 14.14.14.14 a5-4.tld2 # 14 prefer QNAME to IP + nochange a4-4.tld2 # 15 PASSTHRU + nxdomain c2.crash2.tld3 # 16 assert in rbtdb.c ++addr 127.0.0.17 "a4-4.tld2 -b $ns1" # 17 client-IP address trigger + end_group + + # check that IP addresses for previous group were deleted from the radix tree +@@ -356,15 +373,15 @@ + nochange a3-1.tld4 # 4 different NS IP address + end_group + +-# start_group "walled garden NSIP rewrites" test4a +-# addr 41.41.41.41 a3-1.tld2 # 1 walled garden for all of tld2 +-# addr 2041::41 'a3-1.tld2 AAAA' # 2 walled garden for all of tld2 +-# here a3-1.tld2 TXT <<'EOF' # 3 text message for all of tld2 +-# ;; status: NOERROR, x +-# a3-1.tld2. x IN TXT "NSIP walled garden" +-#EOF +-# end_group +- NS3_STATS=`expr $NS3_STATS + 1` ++ start_group "walled garden NSIP rewrites" test4a ++ addr 41.41.41.41 a3-1.tld2 # 1 walled garden for all of tld2 ++ addr 2041::41 'a3-1.tld2 AAAA' # 2 walled garden for all of tld2 ++ here a3-1.tld2 TXT <<'EOF' # 3 text message for all of tld2 ++ ;; status: NOERROR, x ++ a3-1.tld2. x IN TXT "NSIP walled garden" ++EOF ++ end_group ++ NS3_STATS=`expr $NS3_STATS + 4` + else + echo "I:NSIP not checked; named configured with --disable-rpz-nsip" + fi +@@ -393,6 +410,7 @@ + addr 12.12.12.12 a3-15.tld2 # 15 bl-garden via CNAME to a12.tld2 + addr 127.0.0.16 a3-16.tld2 100 # 16 bl max-policy-ttl 100 + addr 17.17.17.17 "a3-17.tld2 @$ns5" 90 # 17 ns5 bl max-policy-ttl 90 ++drop a3-18.tld2 # 18 bl-drop + end_group + + # check that miscellaneous bugs are still absent +@@ -412,6 +430,9 @@ + end_group + + ++ckstats $ns3 ns3 $NS3_STATS ++ ++ + # superficial test for major performance bugs + QPERF=`sh qperf.sh` + if test -n "$QPERF"; then +@@ -464,8 +485,6 @@ + fi + + +-ckstats $ns3 ns3 55 +- + # restart the main test RPZ server to see if that creates a core file + if test -z "$HAVE_CORE"; then + $PERL $SYSTEMTESTTOP/stop.pl . ns3 +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,71 @@ ++/* ++ * 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 50; ++ slip 3; ++ exempt-clients { 10.53.0.7; }; ++ ++ // small enough to force a table expansion ++ min-table-size 75; ++ }; ++ ++ additional-from-cache no; ++}; ++ ++key rndc_key { ++ secret "1234abcd8765"; ++ algorithm hmac-sha256; ++}; ++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,47 @@ ++; 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 ++ ++; basic rate limiting ++a1 A 192.0.2.1 ++ ++; wildcards ++*.a2 A 192.0.2.2 ++ ++; a3 is in tld3 ++ ++; a4 does not exist to give NXDOMAIN ++ ++; a5 for TCP requests ++a5 A 192.0.2.5 ++ ++; a6 for whitelisted clients ++a6 A 192.0.2.6 ++ ++; a7 for SERVFAIL ++ ++; a8 for NODATA ++a8 A 192.0.2.8 ++ ++; a9 for all-per-second limit ++$GENERATE 101-180 all$.a9 A 192.0.2.8 +diff -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,50 @@ ++/* ++ * 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; ++ ++ // check that all of the options are parsed without limiting anything ++ rate-limit { ++ responses-per-second 200; ++ referrals-per-second 220; ++ nodata-per-second 230; ++ nxdomains-per-second 240; ++ errors-per-second 250; ++ all-per-second 700; ++ ipv4-prefix-length 24; ++ ipv6-prefix-length 64; ++ qps-scale 10; ++ window 1; ++ max-table-size 1000; ++ }; ++ ++}; ++ ++zone "." { type hint; file "hints"; }; ++ ++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.0.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,258 @@ ++# 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 ++ ++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 ++} ++ ++ ++# turn off ${HOME}/.digrc ++HOME=/dev/null; export HOME ++ ++# $1=result name $2=domain name $3=dig options ++digcmd () { ++ OFILE=$1; shift ++ DIG_DOM=$1; shift ++ ARGS="+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 '/^;; AUTHORITY/,/^$/d' \ ++ -e '/^;; ADDITIONAL/,/^$/d' \ ++ -e 's/^[^;].* \([^ ]\{1,\}\)$/\1/p' \ ++ -e 's/;; flags.* tc .*/TC/p' \ ++ -e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p' \ ++ -e 's/;; .* status: SERVFAIL.*/SERVFAIL/p' \ ++ -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 ++QNUM=1 ++burst () { ++ BURST_LIMIT=$1; shift ++ BURST_DOM_BASE="$1"; shift ++ while test "$BURST_LIMIT" -ge 1; do ++ CNT=`expr "00$QNUM" : '.*\(...\)'` ++ eval BURST_DOM="$BURST_DOM_BASE" ++ FILE="dig.out-$BURST_DOM-$CNT" ++ digcmd $FILE $BURST_DOM $* & ++ QNUM=`expr $QNUM + 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` ++ # count simple truncated and truncated NXDOMAIN as TC ++ TC=`ls dig.out-$1-*=TC dig.out-$1-*=NXDOMAINTC 2>/dev/null | wc -l` ++ DROP=`ls dig.out-$1-*=drop 2>/dev/null | wc -l` ++ # count NXDOMAIN and truncated NXDOMAIN as NXDOMAIN ++ NXDOMAIN=`ls dig.out-$1-*=NXDOMAIN dig.out-$1-*=NXDOMAINTC 2>/dev/null \ ++ | wc -l` ++ SERVFAIL=`ls dig.out-$1-*=SERVFAIL 2>/dev/null | wc -l` ++ 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 ++} ++ ++ ++ckstats () { ++ LABEL="$1"; shift ++ TYPE="$1"; shift ++ EXPECTED="$1"; shift ++ C=`sed -n -e "s/[ ]*\([0-9]*\).responses $TYPE for rate limits.*/\1/p" \ ++ ns2/named.stats | tail -1` ++ C=`expr 0$C + 0` ++ if test "$C" -ne $EXPECTED; then ++ setret "I:wrong $LABEL $TYPE statistics of $C instead of $EXPECTED" ++ fi ++} ++ ++ ++######### ++sec_start ++ ++# Tests of referrals to "." must be done before the hints are loaded ++# or with "additional-from-cache no" ++burst 5 a1.tld3 +norec ++# basic rate limiting ++burst 3 a1.tld2 ++# 1 second delay allows an additional response. ++sleep 1 ++burst 10 a1.tld2 ++# Request 30 different qnames to try a wildcard. ++burst 30 'x$CNT.a2.tld2' ++# These should be counted and limited but are not. See RT33138. ++burst 10 'y.x$CNT.a2.tld2' ++ ++# IP TC drop NXDOMAIN SERVFAIL ++# referrals to "." ++ck_result a1.tld3 '' 2 1 2 0 0 ++# check 13 results including 1 second delay that allows an additional response ++ck_result a1.tld2 192.0.2.1 3 4 6 0 0 ++ ++# Check the wild card answers. ++# The parent name of the 30 requests is counted. ++ck_result 'x*.a2.tld2' 192.0.2.2 2 10 18 0 0 ++ ++# These should be limited but are not. See RT33138. ++ck_result 'y.x*.a2.tld2' 192.0.2.2 10 0 0 0 0 ++ ++######### ++sec_start ++ ++burst 10 'x.a3.tld3' ++burst 10 'y$CNT.a3.tld3' ++burst 10 'z$CNT.a4.tld2' ++ ++# 10 identical recursive responses are limited ++ck_result 'x.a3.tld3' 192.0.3.3 2 3 5 0 0 ++ ++# 10 different recursive responses are not limited ++ck_result 'y*.a3.tld3' 192.0.3.3 10 0 0 0 0 ++ ++# 10 different NXDOMAIN responses are limited based on the parent name. ++# We count 13 responses because we count truncated NXDOMAIN responses ++# as both truncated and NXDOMAIN. ++ck_result 'z*.a4.tld2' x 0 3 5 5 0 ++ ++$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats ++ckstats first dropped 36 ++ckstats first truncated 21 ++ ++ ++######### ++sec_start ++ ++burst 10 a5.tld2 +tcp ++burst 10 a6.tld2 -b $ns7 ++burst 10 a7.tld4 ++burst 2 a8.tld2 AAAA ++burst 2 a8.tld2 TXT ++burst 2 a8.tld2 SPF ++ ++# IP TC drop NXDOMAIN SERVFAIL ++# TCP responses are not rate limited ++ck_result a5.tld2 192.0.2.5 10 0 0 0 0 ++ ++# whitelisted client is not rate limited ++ck_result a6.tld2 192.0.2.6 10 0 0 0 0 ++ ++# Errors such as SERVFAIL are rate limited. ++ck_result a7.tld4 x 0 0 8 0 2 ++ ++# NODATA responses are counted as the same regardless of qtype. ++ck_result a8.tld2 '' 2 2 2 0 0 ++ ++$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats ++ckstats second dropped 46 ++ckstats second truncated 23 ++ ++ ++######### ++sec_start ++ ++# IP TC drop NXDOMAIN SERVFAIL ++# all-per-second ++# The qnames are all unique but the client IP address is constant. ++QNUM=101 ++burst 60 'all$CNT.a9.tld2' ++ ++ck_result 'a*.a9.tld2' 192.0.2.8 50 0 10 0 0 ++ ++$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats ++ckstats final dropped 56 ++ckstats final truncated 23 ++ ++ ++echo "I:exit status: $ret" ++# exit $ret ++[ $ret -ne 0 ] && echo "I:test failure overridden" ++exit 0 +diff -r -u clean.sh-orig clean.sh +--- clean.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ 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 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 +@@ -4818,6 +4818,32 @@ + + + ++ ++ ++ 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 query-errors category. ++ ++ ++ + + + +@@ -5318,7 +5344,7 @@ + match-mapped-addresses yes_or_no; + filter-aaaa-on-v4 ( yes_or_no | break-dnssec ); + filter-aaaa { address_match_list }; +- dns64 IPv6-prefix { ++ dns64 ipv6-prefix { + clients { address_match_list }; + mapped { address_match_list }; + exclude { address_match_list }; +@@ -5351,8 +5377,25 @@ + 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 ; ++ referrals-per-second number ; ++ nodata-per-second number ; ++ nxdomains-per-second number ; ++ errors-per-second number ; ++ all-per-second number ; ++ window number ; ++ log-only yes_or_no ; ++ qps-scale number ; ++ ipv4-prefix-length number ; ++ ipv6-prefix-length number ; ++ 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 ++ policy given | disabled | passthru | drop | nxdomain | nodata | cname domain + recursive-only yes_or_no max-policy-ttl number ; + } recursive-only yes_or_no max-policy-ttl number + break-dnssec yes_or_no min-ns-dots number ; +@@ -9645,30 +9688,25 @@ + Response policy zones are named in the + response-policy option for the view or among the + global options if there is no response-policy option for the view. +- RPZs are ordinary DNS zones containing RRsets ++ Response policy zones are ordinary DNS zones containing RRsets + that can be queried normally if allowed. + It is usually best to restrict those queries with something like + allow-query { localhost; };. + + + +- Four policy triggers are encoded in RPZ records, QNAME, IP, NSIP, +- and NSDNAME. +- QNAME RPZ records triggered by query names of requests and targets +- of CNAME records resolved to generate the response. +- The owner name of a QNAME RPZ record is the query name relativized +- to the RPZ. +- +- +- +- The second kind of RPZ trigger is an IP address in an A and AAAA +- record in the ANSWER section of a response. +- IP address triggers are encoded in records that have owner names +- that are subdomains of rpz-ip relativized +- to the RPZ origin name and encode an IP address or address block. +- IPv4 trigger addresses are represented as ++ Five policy triggers can be encoded in RPZ records, CLIENT-IP, ++ QNAME, IP, NSDNAME, and NSIP. ++ CLIENT-IP records are triggered by the IP address of the ++ DNS client. ++ Client IP address triggers are encoded in records that have ++ owner names that are subdomains of ++ rpz-client-ip relativized to the ++ policy zone origin name ++ and encode an address or address block. ++ IPv4 addresses are represented as + prefixlength.B4.B3.B2.B1.rpz-ip. +- The prefix length must be between 1 and 32. ++ The IPv4 prefix length must be between 1 and 32. + All four bytes, B4, B3, B2, and B1, must be present. + B4 is the decimal value of the least significant byte of the + IPv4 address as in IN-ADDR.ARPA. +@@ -9678,10 +9716,25 @@ + Each of W8,...,W1 is a one to four digit hexadecimal number + representing 16 bits of the IPv6 address as in the standard text + representation of IPv6 addresses, but reversed as in IN-ADDR.ARPA. +- All 8 words must be present except when consecutive +- zero words are replaced with .zz. ++ All 8 words must be present except when one set of consecutive ++ zero words is replaced with .zz. + analogous to double colons (::) in standard IPv6 text encodings. +- The prefix length must be between 1 and 128. ++ The IPv6 prefix length must be between 64 and 128. ++ ++ ++ ++ QNAME policy records are triggered by query names of requests and ++ targets of CNAME records resolved to generate the response. ++ The owner name of a QNAME policy record is ++ the query name relativized to the policy zone. ++ ++ ++ ++ The third kind of trigger is an IP address in an A and AAAA ++ record in the ANSWER section of a response. ++ IP address triggers ++ IP triggers are encoded like client-IP triggers except as ++ subdomains of rpz-ip. + + + +@@ -9703,7 +9756,7 @@ + + + +- The query response is checked against all RPZs, so ++ The query response is checked against all response policy zones, so + two or more policy records can be triggered by a response. + Because DNS responses can be rewritten according to at most one + policy record, a single record encoding an action (other than +@@ -9714,8 +9767,8 @@ + Choose the triggered record in the zone that appears + first in the response-policy option. + +- Prefer QNAME to IP to NSDNAME to NSIP triggers +- in a single zone. ++ Prefer CLIENT-IP to QNAME to IP to NSDNAME to NSIP ++ triggers in a single zone. + + Among NSDNAME triggers, prefer the + trigger that matches the smallest name under the DNSSEC ordering. +@@ -9734,14 +9787,25 @@ + When the processing of a response is restarted to resolve + DNAME or CNAME records and a policy record set has + not been triggered, +- all RPZs are again consulted for the DNAME or CNAME names +- and addresses. ++ all response policy zones are again consulted for the ++ DNAME or CNAME names and addresses. + + + + RPZ record sets are sets of any types of DNS record except + DNAME or DNSSEC that encode actions or responses to queries. + ++ The PASSTHRU policy is specified ++ by a CNAME whose target is rpz-passthru. ++ It causes the response to not be rewritten ++ and is most often used to "poke holes" in policies for ++ CIDR blocks. ++ ++ The DROP policy is specified ++ by a CNAME whose target is rpz-drop. ++ It causes the response to discarded. ++ Nothing is sent to the DNS client. ++ + The NXDOMAIN response is encoded + by a CNAME whose target is the root domain (.) + +@@ -9761,23 +9825,16 @@ + The purpose for this special form is query logging in the + walled garden's authority DNS server. + +- The PASSTHRU policy is specified +- by a CNAME whose target is rpz-passthru. +- It causes the response to not be rewritten +- and is most often used to "poke holes" in policies for +- CIDR blocks. +- (A CNAME whose target is the variable part of its owner name +- is an obsolete specification of the PASSTHRU policy.) +- + + + + +- The actions specified in an RPZ can be overridden with a ++ The actions specified in a policy zone can be overridden with a + policy clause in the + response-policy option. +- An organization using an RPZ provided by another organization might +- use this mechanism to redirect domains to its own walled garden. ++ An organization using a policy zone provided by another ++ organization might use this mechanism to redirect domains ++ to its own walled garden. + + GIVEN says "do not override but + perform the action specified in the zone." +@@ -9808,9 +9865,10 @@ + + + +- By default, the actions encoded in an RPZ are applied +- only to queries that ask for recursion (RD=1). +- That default can be changed for a single RPZ or all RPZs in a view ++ By default, the actions encoded in a response policy zone ++ are applied only to queries that ask for recursion (RD=1). ++ That default can be changed for a single policy zone or ++ all response policy zones in a view + with a recursive-only no clause. + This feature is useful for serving the same zone files + both inside and outside an RFC 1918 cloud and using RPZ to +@@ -9819,15 +9877,37 @@ + + + +- Also by default, RPZ actions are applied only to DNS requests that +- either do not request DNSSEC metadata (DO=0) or when no DNSSEC +- records are available for request name in the original zone (not +- the response policy zone). +- This default can be changed for all RPZs in a view with a +- break-dnssec yes clause. +- In that case, RPZ actions are applied regardless of DNSSEC. +- The name of the clause option reflects the fact that results +- rewritten by RPZ actions cannot verify. ++ Also by default, RPZ actions are applied only to DNS requests ++ that either do not request DNSSEC metadata (DO=0) or when no ++ DNSSEC records are available for request name in the original ++ zone (not the response policy zone). This default can be ++ changed for all response policy zones in a view with a ++ break-dnssec yes clause. In that case, RPZ ++ actions are applied regardless of DNSSEC. The name of the ++ clause option reflects the fact that results rewritten by RPZ ++ actions cannot verify. ++ ++ ++ ++ No DNS records are needed to trigger a QNAME action. The name ++ itself is sufficient, so in principle the query name need not ++ be recursively resolved. However, not resolving the requested ++ name leaks the fact that response policy rewriting is in use ++ and that the name is listed in a policy zone to operators of ++ servers for listed names. To prevent that information leak, by ++ default any recursion needed for a request is done before any ++ policy triggers are considered. Because listed domains often ++ have slow authoritative servers, this default behavior can cost ++ significant time. The qname-wait-recurse no ++ option overrides the default behavior when recursion cannot ++ change the response. qname-wait-recurse no ++ does not affect QNAME triggers in policy zones listed after ++ other zones containing IP, NSIP and NSDNAME triggers, because ++ those may depend on the A, AAAA, and NS records that would be ++ found during recursive resolution. It also does not affect ++ DNSSEC requests (DO=1) unless break-dnssec yes ++ is in use, because the response would depend on whether or not ++ RRSIG records were found during resolution. + + + +@@ -9897,6 +9977,223 @@ + RPZRewrites statistics. + + ++ ++ ++ Response Rate Limiting ++ ++ Excessive almost-identical UDP responses ++ can be controlled by configuring a ++ rate-limit clause in an ++ options or view statement. ++ This mechanism keeps authoritative BIND 9 from being used ++ in amplifying reflection denial of service (DoS) attacks. ++ Short truncated (TC=1) responses can be sent to provide ++ rate-limited responses to legitimate clients within ++ a range of forged, attacked IP addresses. ++ Legitimate clients react to dropped or truncated response ++ by retrying with UDP or with TCP respectively. ++ ++ ++ ++ This mechanism is intended for authoritative DNS servers. ++ It can be used on recursive servers but can slow ++ applications such as SMTP servers (mail receivers) and ++ HTTP clients (web browsers) that repeatedly request the ++ same domains. ++ When possible, closing "open" recursive servers is better. ++ ++ ++ ++ Response rate limiting uses a "credit" or "token bucket" scheme. ++ Each combination of identical response and client ++ has a conceptual account that earns a specified number ++ of credits every second. ++ A prospective response debits its account by one. ++ Responses are dropped or truncated ++ while the account is negative. ++ Responses are tracked within a rolling window of time ++ which defaults to 15 seconds, but can be configured with ++ the window option to any value from ++ 1 to 3600 seconds (1 hour). ++ The account cannot become more positive than ++ the per-second limit ++ or more negative than window ++ times the per-second limit. ++ When the specified number of credits for a class of ++ responses is set to 0, those responses are not rate limited. ++ ++ ++ ++ The notions of "identical response" and "DNS client" ++ for rate limiting are not simplistic. ++ All responses to an address block are counted as if to a ++ single client. ++ The prefix lengths of addresses blocks are ++ specified with ipv4-prefix-length (default 24) ++ and ipv6-prefix-length (default 56). ++ ++ ++ ++ All non-empty responses for a valid domain name (qname) ++ and record type (qtype) are identical and have a limit specified ++ with responses-per-second ++ (default 0 or no limit). ++ All empty (NODATA) responses for a valid domain, ++ regardless of query type, are identical. ++ Responses in the NODATA class are limited by ++ nodata-per-second ++ (default responses-per-second). ++ Requests for any and all undefined subdomains of a given ++ valid domain result in NXDOMAIN errors, and are identical ++ regardless of query type. ++ They are limited by nxdomain-per-second ++ (default responses-per-second). ++ This controls some attacks using random names, but ++ can be relaxed or turned off (set to 0) ++ on servers that expect many legitimate ++ NXDOMAIN responses, such as from anti-spam blacklists. ++ Referrals or delegations to the server of a given ++ domain are identical and are limited by ++ referrals-per-second ++ (default responses-per-second). ++ ++ ++ ++ Responses generated from local wildcards are counted and limited ++ as if they were for the parent domain name. ++ This controls flooding using random.wild.example.com. ++ ++ ++ ++ All requests that result in DNS errors other ++ than NXDOMAIN, such as SERVFAIL and FORMERR, are identical ++ regardless of requested name (qname) or record type (qtype). ++ This controls attacks using invalid requests or distant, ++ broken authoritative servers. ++ By default the limit on errors is the same as the ++ responses-per-second value, ++ but it can be set separately with ++ errors-per-second. ++ ++ ++ ++ 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 truncated (TC=1) ++ response. ++ The small size and reduced frequency, and so lack of ++ amplification, of "slipped" responses make them unattractive ++ for reflection DoS attacks. ++ slip must be between 0 and 10. ++ A value of 0 does not "slip"; ++ no truncated responses are sent due to rate limiting. ++ Some error responses including REFUSED and SERVFAIL ++ cannot be replaced with truncated responses and are instead ++ leaked at the slip rate. ++ ++ ++ ++ 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, ++ rather than supplementing, a rate-limit ++ statement among the main options. ++ DNS clients within a view can be exempted from rate limits ++ with the exempt-clients clause. ++ ++ ++ ++ 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. ++ + + + +@@ -14649,6 +14946,32 @@ + + + ++ ++ ++ RateDropped ++ ++ ++ ++ ++ ++ ++ Responses dropped by rate limits. ++ ++ ++ ++ ++ ++ RateSlipped ++ ++ ++ ++ ++ ++ ++ Responses truncated by rate limits. ++ ++ ++ + + + +diff -r -u hints-orig hints +--- hints-orig 2004-01-01 00:00:00.000000000 +0000 ++++ hints 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,36 @@ ++; 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 ++; 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 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 +@@ -67,8 +67,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@ \ +@@ -95,7 +95,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/db.c-orig lib/dns/db.c +--- lib/dns/db.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/db.c 2004-01-01 00:00:00.000000000 +0000 +@@ -1007,21 +1007,23 @@ + (db->methods->resigned)(db, rdataset, version); + } + +-isc_result_t +-dns_db_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st) +-{ +- if (db->methods->rpz_enabled != NULL) +- return ((db->methods->rpz_enabled)(db, st)); +- return (ISC_R_SUCCESS); ++/* ++ * Attach a database to policy zone databases. ++ * This should only happen when the caller has already ensured that ++ * it is dealing with a database that understands response policy zones. ++ */ ++void ++dns_db_rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) { ++ REQUIRE(db->methods->rpz_attach != NULL); ++ (db->methods->rpz_attach)(db, rpzs, rpz_num); + } + +-void +-dns_db_rpz_findips(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, +- dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, +- dns_rdataset_t *ardataset, dns_rpz_st_t *st, +- dns_name_t *query_qname) +-{ +- if (db->methods->rpz_findips != NULL) +- (db->methods->rpz_findips)(rpz, rpz_type, zone, db, version, +- ardataset, st, query_qname); ++/* ++ * Finish loading a response policy zone. ++ */ ++isc_result_t ++dns_db_rpz_ready(dns_db_t *db) { ++ if (db->methods->rpz_ready == NULL) ++ return (ISC_R_SUCCESS); ++ return ((db->methods->rpz_ready)(db)); + } +diff -r -u lib/dns/ecdb.c-orig lib/dns/ecdb.c +--- lib/dns/ecdb.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/ecdb.c 2004-01-01 00:00:00.000000000 +0000 +@@ -582,8 +582,8 @@ + NULL, /* resigned */ + NULL, /* isdnssec */ + NULL, /* getrrsetstats */ +- NULL, /* rpz_enabled */ +- NULL, /* rpz_findips */ ++ NULL, /* rpz_attach */ ++ NULL, /* rpz_ready */ + NULL, /* findnodeext */ + NULL /* findext */ + }; +diff -r -u lib/dns/include/dns/db.h-orig lib/dns/include/dns/db.h +--- lib/dns/include/dns/db.h-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/include/dns/db.h 2004-01-01 00:00:00.000000000 +0000 +@@ -172,14 +172,9 @@ + dns_dbversion_t *version); + isc_boolean_t (*isdnssec)(dns_db_t *db); + dns_stats_t *(*getrrsetstats)(dns_db_t *db); +- isc_result_t (*rpz_enabled)(dns_db_t *db, dns_rpz_st_t *st); +- void (*rpz_findips)(dns_rpz_zone_t *rpz, +- dns_rpz_type_t rpz_type, +- dns_zone_t *zone, dns_db_t *db, +- dns_dbversion_t *version, +- dns_rdataset_t *ardataset, +- dns_rpz_st_t *st, +- dns_name_t *query_qname); ++ void (*rpz_attach)(dns_db_t *db, dns_rpz_zones_t *rpzs, ++ dns_rpz_num_t rpz_num); ++ isc_result_t (*rpz_ready)(dns_db_t *db); + isc_result_t (*findnodeext)(dns_db_t *db, dns_name_t *name, + isc_boolean_t create, + dns_clientinfomethods_t *methods, +@@ -1542,30 +1537,17 @@ + * dns_rdatasetstats_create(); otherwise NULL. + */ + +-isc_result_t +-dns_db_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st); ++void ++dns_db_rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num); + /*%< +- * Mark a database for response policy rewriting +- * or find which RPZ data is available. ++ * Attach the response policy information for a view to a database for a ++ * zone for the view. + */ + +-void +-dns_db_rpz_findips(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, +- dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, +- dns_rdataset_t *ardataset, dns_rpz_st_t *st, +- dns_name_t *query_qname); +-/*%< +- * Search the CDIR block tree of a response policy tree of trees for the best +- * match to any of the IP addresses in an A or AAAA rdataset. +- * +- * Requires: +- * \li search in policy zone 'rpz' for a match of 'rpz_type' either +- * DNS_RPZ_TYPE_IP or DNS_RPZ_TYPE_NSIP +- * \li 'zone' and 'db' are the database corresponding to 'rpz' +- * \li 'version' is the required version of the database +- * \li 'ardataset' is an A or AAAA rdataset of addresses to check +- * \li 'found' specifies the previous best match if any or +- * or NULL, an empty name, 0, DNS_RPZ_POLICY_MISS, and 0 ++isc_result_t ++dns_db_rpz_ready(dns_db_t *db); ++/*%< ++ * Finish loading a response policy zone. + */ + + ISC_LANG_ENDDECLS +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/rpz.h-orig lib/dns/include/dns/rpz.h +--- lib/dns/include/dns/rpz.h-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/include/dns/rpz.h 2004-01-01 00:00:00.000000000 +0000 +@@ -14,7 +14,6 @@ + * PERFORMANCE OF THIS SOFTWARE. + */ + +-/* $Id$ */ + + + #ifndef DNS_RPZ_H +@@ -25,19 +24,30 @@ + #include + #include + #include ++#include + + ISC_LANG_BEGINDECLS + + #define DNS_RPZ_PREFIX "rpz-" ++/* ++ * Sub-zones of various trigger types. ++ */ ++#define DNS_RPZ_CLIENT_IP_ZONE DNS_RPZ_PREFIX"client-ip" + #define DNS_RPZ_IP_ZONE DNS_RPZ_PREFIX"ip" + #define DNS_RPZ_NSIP_ZONE DNS_RPZ_PREFIX"nsip" + #define DNS_RPZ_NSDNAME_ZONE DNS_RPZ_PREFIX"nsdname" +-#define DNS_RPZ_PASSTHRU_ZONE DNS_RPZ_PREFIX"passthru" ++/* ++ * Special policies. ++ */ ++#define DNS_RPZ_PASSTHRU_NAME DNS_RPZ_PREFIX"passthru" ++#define DNS_RPZ_DROP_NAME DNS_RPZ_PREFIX"drop" + +-typedef isc_uint8_t dns_rpz_cidr_bits_t; ++ ++typedef isc_uint8_t dns_rpz_prefix_t; + + typedef enum { + DNS_RPZ_TYPE_BAD, ++ DNS_RPZ_TYPE_CLIENT_IP, + DNS_RPZ_TYPE_QNAME, + DNS_RPZ_TYPE_IP, + DNS_RPZ_TYPE_NSDNAME, +@@ -45,45 +55,148 @@ + } dns_rpz_type_t; + + /* +- * Require DNS_RPZ_POLICY_PASSTHRU < DNS_RPZ_POLICY_NXDOMAIN < +- * DNS_RPZ_POLICY_NODATA < DNS_RPZ_POLICY_CNAME to choose among competing +- * policies. ++ * Require DNS_RPZ_POLICY_PASSTHRU < DNS_RPZ_POLICY_DROP < ++ * DNS_RPZ_POLICY_NXDOMAIN < DNS_RPZ_POLICY_NODATA < DNS_RPZ_POLICY_CNAME ++ * to choose among competing policies. + */ + typedef enum { + DNS_RPZ_POLICY_GIVEN = 0, /* 'given': what policy record says */ +- DNS_RPZ_POLICY_DISABLED = 1, /* 'cname x': answer with x's rrsets */ ++ DNS_RPZ_POLICY_DISABLED = 1, /* log what would have happened */ + DNS_RPZ_POLICY_PASSTHRU = 2, /* 'passthru': do not rewrite */ +- DNS_RPZ_POLICY_NXDOMAIN = 3, /* 'nxdomain': answer with NXDOMAIN */ +- DNS_RPZ_POLICY_NODATA = 4, /* 'nodata': answer with ANCOUNT=0 */ +- DNS_RPZ_POLICY_CNAME = 5, /* 'cname x': answer with x's rrsets */ ++ DNS_RPZ_POLICY_DROP = 3, /* 'drop': do not respond */ ++ DNS_RPZ_POLICY_NXDOMAIN = 4, /* 'nxdomain': answer with NXDOMAIN */ ++ DNS_RPZ_POLICY_NODATA = 5, /* 'nodata': answer with ANCOUNT=0 */ ++ DNS_RPZ_POLICY_CNAME = 6, /* 'cname x': answer with x's rrsets */ + DNS_RPZ_POLICY_RECORD, + DNS_RPZ_POLICY_WILDCNAME, + DNS_RPZ_POLICY_MISS, + DNS_RPZ_POLICY_ERROR + } dns_rpz_policy_t; + ++typedef isc_uint8_t dns_rpz_num_t; ++ ++#define DNS_RPZ_MAX_ZONES 32 ++#if DNS_RPZ_MAX_ZONES > 32 ++# if DNS_RPZ_MAX_ZONES > 64 ++# error "rpz zone bit masks must fit in a word" ++# endif ++typedef isc_uint64_t dns_rpz_zbits_t; ++#else ++typedef isc_uint32_t dns_rpz_zbits_t; ++#endif ++ ++#define DNS_RPZ_ALL_ZBITS ((dns_rpz_zbits_t)-1) ++ ++#define DNS_RPZ_INVALID_NUM DNS_RPZ_MAX_ZONES ++ ++#define DNS_RPZ_ZBIT(n) (((dns_rpz_zbits_t)1) << (dns_rpz_num_t)(n)) ++ + /* +- * Specify a response policy zone. ++ * Mask of the specified and higher numbered policy zones ++ * Avoid hassles with (1<<33) or (1<<65) + */ +-typedef struct dns_rpz_zone dns_rpz_zone_t; ++#define DNS_RPZ_ZMASK(n) ((dns_rpz_zbits_t)((((n) >= DNS_RPZ_MAX_ZONES-1) ? \ ++ 0 : (1<<((n)+1))) -1)) + ++/* ++ * The number of triggers of each type in a response policy zone. ++ */ ++typedef struct dns_rpz_triggers dns_rpz_triggers_t; ++struct dns_rpz_triggers { ++ int client_ipv4; ++ int client_ipv6; ++ int qname; ++ int ipv4; ++ int ipv6; ++ int nsdname; ++ int nsipv4; ++ int nsipv6; ++}; ++/* ++ * A single response policy zone. ++ */ ++typedef struct dns_rpz_zone dns_rpz_zone_t; + struct dns_rpz_zone { +- ISC_LINK(dns_rpz_zone_t) link; +- int num; /* ordinal in list of policy zones */ +- dns_name_t origin; /* Policy zone name */ +- dns_name_t nsdname; /* DNS_RPZ_NSDNAME_ZONE.origin */ +- dns_name_t passthru;/* DNS_RPZ_PASSTHRU_ZONE. */ +- dns_name_t cname; /* override value for ..._CNAME */ +- dns_ttl_t max_policy_ttl; +- dns_rpz_policy_t policy; /* DNS_RPZ_POLICY_GIVEN or override */ +- isc_boolean_t recursive_only; +- isc_boolean_t defined; ++ isc_refcount_t refs; ++ dns_rpz_num_t num; /* ordinal in list of policy zones */ ++ dns_name_t origin; /* Policy zone name */ ++ dns_name_t client_ip; /* DNS_RPZ_CLIENT_IP_ZONE.origin. */ ++ dns_name_t ip; /* DNS_RPZ_IP_ZONE.origin. */ ++ dns_name_t nsdname; /* DNS_RPZ_NSDNAME_ZONE.origin */ ++ dns_name_t nsip; /* DNS_RPZ_NSIP_ZONE.origin. */ ++ dns_name_t passthru; /* DNS_RPZ_PASSTHRU_NAME. */ ++ dns_name_t drop; /* DNS_RPZ_DROP_NAME. */ ++ dns_name_t cname; /* override value for ..._CNAME */ ++ dns_ttl_t max_policy_ttl; ++ dns_rpz_policy_t policy; /* DNS_RPZ_POLICY_GIVEN or override */ ++ dns_rpz_triggers_t triggers; + }; + + /* +- * Radix trees for response policy IP addresses. ++ * Radix tree node for response policy IP addresses ++ */ ++typedef struct dns_rpz_cidr_node dns_rpz_cidr_node_t; ++ ++/* ++ * Response policy zones known to a view. + */ +-typedef struct dns_rpz_cidr dns_rpz_cidr_t; ++typedef struct dns_rpz_zones dns_rpz_zones_t; ++struct dns_rpz_zones { ++ struct { ++ dns_rpz_zbits_t no_rd_ok; ++ isc_boolean_t break_dnssec; ++ isc_boolean_t qname_wait_recurse; ++ unsigned int min_ns_labels; ++ dns_rpz_num_t num_zones; ++ } p; ++ dns_rpz_zone_t *zones[DNS_RPZ_MAX_ZONES]; ++ ++ dns_rpz_zbits_t defined; ++ ++ /* ++ * The set of records for a policy zone are in one of these states: ++ * never loaded load_begun=0 have=0 ++ * during initial loading load_begun=1 have=0 ++ * and rbtdb->rpzsp == rbtdb->load_rpzsp ++ * after good load load_begun=1 have!=0 ++ * after failed initial load load_begun=1 have=0 ++ * and rbtdb->load_rpzsp == NULL ++ * reloading after failure load_begun=1 have=0 ++ * reloading after success ++ * main rpzs load_begun=1 have!=0 ++ * load rpzs load_begun=1 have=0 ++ */ ++ dns_rpz_zbits_t load_begun; ++ struct { ++ dns_rpz_zbits_t client_ipv4; ++ dns_rpz_zbits_t client_ipv6; ++ dns_rpz_zbits_t client_ip; ++ dns_rpz_zbits_t qname; ++ dns_rpz_zbits_t ipv4; ++ dns_rpz_zbits_t ipv6; ++ dns_rpz_zbits_t ip; ++ dns_rpz_zbits_t nsdname; ++ dns_rpz_zbits_t nsipv4; ++ dns_rpz_zbits_t nsipv6; ++ dns_rpz_zbits_t nsip; ++ dns_rpz_zbits_t qname_skip_recurse; ++ } have; ++ ++ isc_mem_t *mctx; ++ isc_refcount_t refs; ++ /* ++ * One lock for short term read-only search that guarantees the ++ * consistency of the pointers. ++ * A second lock for maintenance that guarantees no other thread ++ * is adding or deleting nodes. ++ */ ++ isc_mutex_t search_lock; ++ isc_mutex_t maint_lock; ++ ++ dns_rpz_cidr_node_t *cidr; ++ dns_rbt_t *rbt; ++}; ++ + + /* + * context for finding the best policy +@@ -91,22 +204,19 @@ + typedef struct { + unsigned int state; + # define DNS_RPZ_REWRITTEN 0x0001 +-# define DNS_RPZ_DONE_QNAME 0x0002 /* qname checked */ +-# define DNS_RPZ_DONE_QNAME_IP 0x0004 /* IP addresses of qname checked */ +-# define DNS_RPZ_DONE_NSDNAME 0x0008 /* NS name missed; checking addresses */ +-# define DNS_RPZ_DONE_IPv4 0x0010 +-# define DNS_RPZ_RECURSING 0x0020 +-# define DNS_RPZ_HAVE_IP 0x0040 /* a policy zone has IP addresses */ +-# define DNS_RPZ_HAVE_NSIPv4 0x0080 /* IPv4 NISP addresses */ +-# define DNS_RPZ_HAVE_NSIPv6 0x0100 /* IPv6 NISP addresses */ +-# define DNS_RPZ_HAVE_NSDNAME 0x0200 /* NS names */ ++# define DNS_RPZ_DONE_CLIENT_IP 0x0002 /* client IP address checked */ ++# define DNS_RPZ_DONE_QNAME 0x0004 /* qname checked */ ++# define DNS_RPZ_DONE_QNAME_IP 0x0008 /* IP addresses of qname checked */ ++# define DNS_RPZ_DONE_NSDNAME 0x0010 /* NS name missed; checking addresses */ ++# define DNS_RPZ_DONE_IPv4 0x0020 ++# define DNS_RPZ_RECURSING 0x0040 + /* + * Best match so far. + */ + struct { + dns_rpz_type_t type; + dns_rpz_zone_t *rpz; +- dns_rpz_cidr_bits_t prefix; ++ dns_rpz_prefix_t prefix; + dns_rpz_policy_t policy; + dns_ttl_t ttl; + isc_result_t result; +@@ -141,10 +251,15 @@ + dns_rdataset_t *sigrdataset; + dns_rdatatype_t qtype; + } q; +- dns_name_t *qname; ++ /* ++ * p_name: current policy owner name ++ * r_name: recursing for this name to possible policy triggers ++ * f_name: saved found name from before recursion ++ */ ++ dns_name_t *p_name; + dns_name_t *r_name; + dns_name_t *fname; +- dns_fixedname_t _qnamef; ++ dns_fixedname_t _p_namef; + dns_fixedname_t _r_namef; + dns_fixedname_t _fnamef; + } dns_rpz_st_t; +@@ -171,32 +286,41 @@ + const char * + dns_rpz_policy2str(dns_rpz_policy_t policy); + +-void +-dns_rpz_cidr_free(dns_rpz_cidr_t **cidr); +- +-void +-dns_rpz_view_destroy(dns_view_t *view); ++dns_rpz_policy_t ++dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset, ++ dns_name_t *selfname); + + isc_result_t +-dns_rpz_new_cidr(isc_mem_t *mctx, dns_name_t *origin, +- dns_rpz_cidr_t **rbtdb_cidr); +-void +-dns_rpz_enabled_get(dns_rpz_cidr_t *cidr, dns_rpz_st_t *st); ++dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, isc_mem_t *mctx); + + void +-dns_rpz_cidr_deleteip(dns_rpz_cidr_t *cidr, dns_name_t *name); ++dns_rpz_attach_rpzs(dns_rpz_zones_t *source, dns_rpz_zones_t **target); + + void +-dns_rpz_cidr_addip(dns_rpz_cidr_t *cidr, dns_name_t *name); ++dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp); + + isc_result_t +-dns_rpz_cidr_find(dns_rpz_cidr_t *cidr, const isc_netaddr_t *netaddr, +- dns_rpz_type_t type, dns_name_t *canon_name, +- dns_name_t *search_name, dns_rpz_cidr_bits_t *prefix); ++dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp, ++ dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num); + +-dns_rpz_policy_t +-dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset, +- dns_name_t *selfname); ++isc_result_t ++dns_rpz_ready(dns_rpz_zones_t *rpzs, ++ dns_rpz_zones_t **load_rpzsp, dns_rpz_num_t rpz_num); ++ ++isc_result_t ++dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *name); ++ ++void ++dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *name); ++ ++dns_rpz_num_t ++dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, ++ dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr, ++ dns_name_t *ip_name, dns_rpz_prefix_t *prefixp); ++ ++dns_rpz_zbits_t ++dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, ++ dns_rpz_zbits_t zbits, dns_name_t *trig_name); + + ISC_LANG_ENDDECLS + +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,278 @@ ++/* ++ * Copyright (C) 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_REFERRAL, ++ DNS_RRL_RTYPE_NODATA, ++ 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 :4; /* 3 bits + sign bit */ ++ 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; +@@ -162,10 +164,7 @@ + dns_acl_t * v4_aaaa_acl; + dns_dns64list_t dns64; + unsigned int dns64cnt; +- ISC_LIST(dns_rpz_zone_t) rpz_zones; +- isc_boolean_t rpz_recursive_only; +- isc_boolean_t rpz_break_dnssec; +- unsigned int rpz_min_ns_labels; ++ dns_rpz_zones_t *rpzs; + + /* + * Configurable data for server use only, +diff -r -u lib/dns/include/dns/zone.h-orig lib/dns/include/dns/zone.h +--- lib/dns/include/dns/zone.h-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/include/dns/zone.h 2004-01-01 00:00:00.000000000 +0000 +@@ -2081,13 +2081,14 @@ + */ + + isc_result_t +-dns_zone_rpz_enable(dns_zone_t *zone); ++dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs, ++ dns_rpz_num_t rpz_num); + /*% + * Set the response policy associated with a zone. + */ + +-isc_boolean_t +-dns_zone_get_rpz(dns_zone_t *zone); ++dns_rpz_num_t ++dns_zone_get_rpz_num(dns_zone_t *zone); + + void + dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level); +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/rbtdb.c-orig lib/dns/rbtdb.c +--- lib/dns/rbtdb.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/rbtdb.c 2004-01-01 00:00:00.000000000 +0000 +@@ -453,7 +453,9 @@ + dns_rbt_t * tree; + dns_rbt_t * nsec; + dns_rbt_t * nsec3; +- dns_rpz_cidr_t * rpz_cidr; ++ dns_rpz_zones_t *rpzs; ++ dns_rpz_num_t rpz_num; ++ dns_rpz_zones_t *load_rpzs; + + /* Unlocked */ + unsigned int quantum; +@@ -972,8 +974,18 @@ + dns_stats_detach(&rbtdb->rrsetstats); + + #ifdef BIND9 +- if (rbtdb->rpz_cidr != NULL) +- dns_rpz_cidr_free(&rbtdb->rpz_cidr); ++ if (rbtdb->load_rpzs != NULL) { ++ /* ++ * We must be cleaning up after a failed zone loading. ++ */ ++ REQUIRE(rbtdb->rpzs != NULL && ++ rbtdb->rpz_num < rbtdb->rpzs->p.num_zones); ++ dns_rpz_detach_rpzs(&rbtdb->load_rpzs); ++ } ++ if (rbtdb->rpzs != NULL) { ++ REQUIRE(rbtdb->rpz_num < rbtdb->rpzs->p.num_zones); ++ dns_rpz_detach_rpzs(&rbtdb->rpzs); ++ } + #endif + + isc_mem_put(rbtdb->common.mctx, rbtdb->node_locks, +@@ -1515,11 +1527,11 @@ + switch (node->nsec) { + case DNS_RBT_NSEC_NORMAL: + #ifdef BIND9 +- if (rbtdb->rpz_cidr != NULL) { ++ if (rbtdb->rpzs != NULL) { + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + dns_rbt_fullnamefromnode(node, name); +- dns_rpz_cidr_deleteip(rbtdb->rpz_cidr, name); ++ dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name); + } + #endif + result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE); +@@ -1555,11 +1567,11 @@ + isc_result_totext(result)); + } + } ++ result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE); + #ifdef BIND9 +- if (rbtdb->rpz_cidr != NULL) +- dns_rpz_cidr_deleteip(rbtdb->rpz_cidr, name); ++ if (rbtdb->rpzs != NULL) ++ dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name); + #endif +- result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE); + break; + case DNS_RBT_NSEC_NSEC: + result = dns_rbt_deletenode(rbtdb->nsec, node, ISC_FALSE); +@@ -1573,7 +1585,7 @@ + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, + ISC_LOG_WARNING, +- "delete_cnode(): " ++ "delete_node(): " + "dns_rbt_deletenode: %s", + isc_result_totext(result)); + } +@@ -2538,14 +2550,15 @@ + result = dns_rbt_addnode(tree, name, &node); + if (result == ISC_R_SUCCESS) { + #ifdef BIND9 +- if (tree == rbtdb->tree && rbtdb->rpz_cidr != NULL) { ++ if (rbtdb->rpzs != NULL && tree == rbtdb->tree) { + dns_fixedname_t fnamef; + dns_name_t *fname; + + dns_fixedname_init(&fnamef); + fname = dns_fixedname_name(&fnamef); + dns_rbt_fullnamefromnode(node, fname); +- dns_rpz_cidr_addip(rbtdb->rpz_cidr, fname); ++ result = dns_rpz_add(rbtdb->rpzs, ++ rbtdb->rpz_num, fname); + } + #endif + dns_rbt_namefromnode(node, &nodename); +@@ -4547,228 +4560,45 @@ + return (result); + } + ++#ifdef BIND9 + /* +- * Mark a database for response policy rewriting +- * or find which RPZ data is available. ++ * Connect this RBTDB to the response policy zone summary data for the view. + */ +-#ifdef BIND9 +-static isc_result_t +-rpz_enabled(dns_db_t *db, dns_rpz_st_t *st) +-{ +- dns_rbtdb_t *rbtdb; +- isc_result_t result; ++static void ++rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) { ++ dns_rbtdb_t * rbtdb; + +- result = ISC_R_SUCCESS; + rbtdb = (dns_rbtdb_t *)db; + REQUIRE(VALID_RBTDB(rbtdb)); +- RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); +- if (st != NULL) { +- dns_rpz_enabled_get(rbtdb->rpz_cidr, st); +- } else { +- result = dns_rpz_new_cidr(rbtdb->common.mctx, +- &rbtdb->common.origin, +- &rbtdb->rpz_cidr); +- } +- RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); +- return (result); ++ ++ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); ++ REQUIRE(rbtdb->rpzs == NULL && rbtdb->rpz_num == DNS_RPZ_INVALID_NUM); ++ dns_rpz_attach_rpzs(rpzs, &rbtdb->rpzs); ++ rbtdb->rpz_num = rpz_num; ++ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + } + + /* +- * Search the CDIR block tree of a response policy tree of trees for all of +- * the IP addresses in an A or AAAA rdataset. +- * Among the policies for all IPv4 and IPv6 addresses for a name, choose +- * the earliest configured policy, +- * QNAME over IP over NSDNAME over NSIP, +- * the longest prefix, +- * the lexically smallest address. +- * The caller must have already checked that any existing policy was not +- * configured earlier than this policy zone and does not have a higher +- * precedence type. ++ * Enable this RBTDB as a response policy zone. + */ +-static void +-rpz_findips(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, +- dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, +- dns_rdataset_t *ardataset, dns_rpz_st_t *st, +- dns_name_t *query_qname) +-{ +- dns_rbtdb_t *rbtdb; +- struct in_addr ina; +- struct in6_addr in6a; +- isc_netaddr_t netaddr; +- dns_fixedname_t selfnamef, qnamef; +- dns_name_t *selfname, *qname; +- dns_rbtnode_t *node; +- dns_rdataset_t zrdataset; +- dns_rpz_cidr_bits_t prefix; ++static isc_result_t ++rpz_ready(dns_db_t *db) { ++ dns_rbtdb_t * rbtdb; + isc_result_t result; +- dns_rpz_policy_t rpz_policy; +- dns_ttl_t ttl; + + rbtdb = (dns_rbtdb_t *)db; + REQUIRE(VALID_RBTDB(rbtdb)); +- RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); +- +- if (rbtdb->rpz_cidr == NULL) { +- RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); +- return; +- } +- +- dns_fixedname_init(&selfnamef); +- dns_fixedname_init(&qnamef); +- selfname = dns_fixedname_name(&selfnamef); +- qname = dns_fixedname_name(&qnamef); +- +- for (result = dns_rdataset_first(ardataset); +- result == ISC_R_SUCCESS; +- result = dns_rdataset_next(ardataset)) { +- dns_rdata_t rdata = DNS_RDATA_INIT; +- dns_rdataset_current(ardataset, &rdata); +- switch (rdata.type) { +- case dns_rdatatype_a: +- INSIST(rdata.length == 4); +- memcpy(&ina.s_addr, rdata.data, 4); +- isc_netaddr_fromin(&netaddr, &ina); +- break; +- case dns_rdatatype_aaaa: +- INSIST(rdata.length == 16); +- memcpy(in6a.s6_addr, rdata.data, 16); +- isc_netaddr_fromin6(&netaddr, &in6a); +- break; +- default: +- continue; +- } +- +- result = dns_rpz_cidr_find(rbtdb->rpz_cidr, &netaddr, rpz_type, +- selfname, qname, &prefix); +- if (result != ISC_R_SUCCESS) +- continue; + +- /* +- * If we already have a rule, discard this new rule if +- * is not better. +- * The caller has checked that st->m.rpz->num > rpz->num +- * or st->m.rpz->num == rpz->num and st->m.type >= rpz_type +- */ +- if (st->m.policy != DNS_RPZ_POLICY_MISS && +- st->m.rpz->num == rpz->num && +- (st->m.type < rpz_type || +- (st->m.type == rpz_type && +- (st->m.prefix > prefix || +- (st->m.prefix == prefix && +- 0 > dns_name_rdatacompare(st->qname, qname)))))) +- continue; +- +- /* +- * We have rpz_st an entry with a prefix at least as long as +- * the prefix of the entry we had before. Find the node +- * corresponding to CDIR tree entry. +- */ +- node = NULL; +- result = dns_rbt_findnode(rbtdb->tree, qname, NULL, +- &node, NULL, 0, NULL, NULL); +- if (result != ISC_R_SUCCESS) { +- char namebuf[DNS_NAME_FORMATSIZE]; +- +- dns_name_format(qname, namebuf, sizeof(namebuf)); +- isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, +- DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, +- "rpz_findips findnode(%s) failed: %s", +- namebuf, isc_result_totext(result)); +- continue; +- } +- /* +- * First look for a simple rewrite of the IP address. +- * If that fails, look for a CNAME. If we cannot find +- * a CNAME or the CNAME is neither of the special forms +- * "*" or ".", treat it like a real CNAME. +- */ +- dns_rdataset_init(&zrdataset); +- result = dns_db_findrdataset(db, node, version, ardataset->type, +- 0, 0, &zrdataset, NULL); +- if (result != ISC_R_SUCCESS) +- result = dns_db_findrdataset(db, node, version, +- dns_rdatatype_cname, +- 0, 0, &zrdataset, NULL); +- if (result == ISC_R_SUCCESS) { +- if (zrdataset.type != dns_rdatatype_cname) { +- rpz_policy = DNS_RPZ_POLICY_RECORD; +- } else { +- rpz_policy = dns_rpz_decode_cname(rpz, +- &zrdataset, +- selfname); +- if (rpz_policy == DNS_RPZ_POLICY_RECORD || +- rpz_policy == DNS_RPZ_POLICY_WILDCNAME) +- result = DNS_R_CNAME; +- } +- ttl = zrdataset.ttl; +- } else { +- rpz_policy = DNS_RPZ_POLICY_RECORD; +- result = DNS_R_NXRRSET; +- ttl = DNS_RPZ_TTL_DEFAULT; +- } +- +- /* +- * Use an overriding action specified in the configuration file +- */ +- if (rpz->policy != DNS_RPZ_POLICY_GIVEN) { +- /* +- * only log DNS_RPZ_POLICY_DISABLED hits +- */ +- if (rpz->policy == DNS_RPZ_POLICY_DISABLED) { +- if (isc_log_wouldlog(dns_lctx, +- DNS_RPZ_INFO_LEVEL)) { +- char qname_buf[DNS_NAME_FORMATSIZE]; +- char rpz_qname_buf[DNS_NAME_FORMATSIZE]; +- dns_name_format(query_qname, qname_buf, +- sizeof(qname_buf)); +- dns_name_format(qname, rpz_qname_buf, +- sizeof(rpz_qname_buf)); +- +- isc_log_write(dns_lctx, +- DNS_LOGCATEGORY_RPZ, +- DNS_LOGMODULE_RBTDB, +- DNS_RPZ_INFO_LEVEL, +- "disabled rpz %s %s rewrite" +- " %s via %s", +- dns_rpz_type2str(rpz_type), +- dns_rpz_policy2str(rpz_policy), +- qname_buf, rpz_qname_buf); +- } +- continue; +- } +- +- rpz_policy = rpz->policy; +- } +- +- if (dns_rdataset_isassociated(st->m.rdataset)) +- dns_rdataset_disassociate(st->m.rdataset); +- if (st->m.node != NULL) +- dns_db_detachnode(st->m.db, &st->m.node); +- if (st->m.db != NULL) +- dns_db_detach(&st->m.db); +- if (st->m.zone != NULL) +- dns_zone_detach(&st->m.zone); +- st->m.rpz = rpz; +- st->m.type = rpz_type; +- st->m.prefix = prefix; +- st->m.policy = rpz_policy; +- st->m.ttl = ISC_MIN(ttl, rpz->max_policy_ttl); +- st->m.result = result; +- dns_name_copy(qname, st->qname, NULL); +- if ((rpz_policy == DNS_RPZ_POLICY_RECORD || +- rpz_policy == DNS_RPZ_POLICY_WILDCNAME) && +- result != DNS_R_NXRRSET) { +- dns_rdataset_clone(&zrdataset,st->m.rdataset); +- dns_db_attachnode(db, node, &st->m.node); +- } +- dns_db_attach(db, &st->m.db); +- st->m.version = version; +- dns_zone_attach(zone, &st->m.zone); +- if (dns_rdataset_isassociated(&zrdataset)) +- dns_rdataset_disassociate(&zrdataset); ++ RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); ++ if (rbtdb->rpzs == NULL) { ++ INSIST(rbtdb->rpz_num == DNS_RPZ_INVALID_NUM); ++ result = ISC_R_SUCCESS; ++ } else { ++ result = dns_rpz_ready(rbtdb->rpzs, &rbtdb->load_rpzs, ++ rbtdb->rpz_num); + } +- +- RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); ++ RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); ++ return (result); + } + #endif + +@@ -6874,8 +6704,9 @@ + noderesult = dns_rbt_addnode(rbtdb->tree, name, nodep); + + #ifdef BIND9 +- if (noderesult == ISC_R_SUCCESS && rbtdb->rpz_cidr != NULL) +- dns_rpz_cidr_addip(rbtdb->rpz_cidr, name); ++ if (rbtdb->rpzs != NULL && noderesult == ISC_R_SUCCESS) ++ noderesult = dns_rpz_add(rbtdb->load_rpzs, rbtdb->rpz_num, ++ name); + #endif + + if (!hasnsec) +@@ -7060,6 +6891,20 @@ + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + ++#ifdef BIND9 ++ if (rbtdb->rpzs != NULL) { ++ isc_result_t result; ++ ++ result = dns_rpz_beginload(&rbtdb->load_rpzs, ++ rbtdb->rpzs, rbtdb->rpz_num); ++ if (result != ISC_R_SUCCESS) { ++ isc_mem_put(rbtdb->common.mctx, loadctx, ++ sizeof(*loadctx)); ++ return (result); ++ } ++ } ++#endif ++ + REQUIRE((rbtdb->attributes & (RBTDB_ATTR_LOADED|RBTDB_ATTR_LOADING)) + == 0); + rbtdb->attributes |= RBTDB_ATTR_LOADING; +@@ -7461,8 +7306,8 @@ + isdnssec, + NULL, + #ifdef BIND9 +- rpz_enabled, +- rpz_findips, ++ rpz_attach, ++ rpz_ready, + #else + NULL, + NULL, +@@ -7776,6 +7621,9 @@ + } + rbtdb->attributes = 0; + rbtdb->task = NULL; ++ rbtdb->rpzs = NULL; ++ rbtdb->load_rpzs = NULL; ++ rbtdb->rpz_num = DNS_RPZ_INVALID_NUM; + + /* + * Version Initialization. +diff -r -u lib/dns/rpz.c-orig lib/dns/rpz.c +--- lib/dns/rpz.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/rpz.c 2004-01-01 00:00:00.000000000 +0000 +@@ -37,6 +37,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -44,9 +45,13 @@ + /* + * Parallel radix trees for databases of response policy IP addresses + * +- * The radix or Patricia trees are somewhat specialized to handle response +- * policy addresses by representing the two test of IP IP addresses and name +- * server IP addresses in a single tree. ++ * The radix or patricia trees are somewhat specialized to handle response ++ * policy addresses by representing the two sets of IP addresses and name ++ * server IP addresses in a single tree. One set of IP addresses is ++ * for rpz-ip policies or policies triggered by addresses in A or ++ * AAAA records in responses. ++ * The second set is for rpz-nsip policies or policies triggered by addresses ++ * in A or AAAA records for NS records that are authorities for responses. + * + * Each leaf indicates that an IP address is listed in the IP address or the + * name server IP address policy sub-zone (or both) of the corresponding +@@ -55,7 +60,8 @@ + * tree, the node in the policy zone's database is found by converting + * the IP address to a domain name in a canonical form. + * +- * The response policy zone canonical form of IPv6 addresses is one of: ++ * ++ * The response policy zone canonical form of an IPv6 address is one of: + * prefix.W.W.W.W.W.W.W.W + * prefix.WORDS.zz + * prefix.WORDS.zz.WORDS +@@ -72,7 +78,7 @@ + * prefix is the prefix length of the address between 1 and 32 + * B is a number between 0 and 255 + * +- * IPv4 addresses are distinguished from IPv6 addresses by having ++ * Names for IPv4 addresses are distinguished from IPv6 addresses by having + * 5 labels all of which are numbers, and a prefix between 1 and 32. + */ + +@@ -90,43 +96,89 @@ + } dns_rpz_cidr_key_t; + + #define ADDR_V4MAPPED 0xffff ++#define KEY_IS_IPV4(prefix,ip) ((prefix) >= 96 && (ip)->w[0] == 0 && \ ++ (ip)->w[1] == 0 && (ip)->w[2] == ADDR_V4MAPPED) ++ ++#define DNS_RPZ_WORD_MASK(b) ((b) == 0 ? (dns_rpz_cidr_word_t)(-1) \ ++ : ((dns_rpz_cidr_word_t)(-1) \ ++ << (DNS_RPZ_CIDR_WORD_BITS - (b)))) ++ ++/* ++ * Get bit #n from the array of words of an IP address. ++ */ ++#define DNS_RPZ_IP_BIT(ip, n) (1 & ((ip)->w[(n)/DNS_RPZ_CIDR_WORD_BITS] >> \ ++ (DNS_RPZ_CIDR_WORD_BITS \ ++ - 1 - ((n) % DNS_RPZ_CIDR_WORD_BITS)))) + +-#define DNS_RPZ_WORD_MASK(b) \ +- ((b) == 0 ? (dns_rpz_cidr_word_t)(-1) \ +- : ((dns_rpz_cidr_word_t)(-1) \ +- << (DNS_RPZ_CIDR_WORD_BITS - (b)))) +- +-#define DNS_RPZ_IP_BIT(ip, bitno) \ +- (1 & ((ip)->w[(bitno)/DNS_RPZ_CIDR_WORD_BITS] >> \ +- (DNS_RPZ_CIDR_WORD_BITS - 1 - ((bitno) % DNS_RPZ_CIDR_WORD_BITS)))) ++/* ++ * A triplet of arrays of bits flagging the existence of ++ * client-IP, IP, and NSIP policy triggers. ++ */ ++typedef struct dns_rpz_addr_zbits dns_rpz_addr_zbits_t; ++struct dns_rpz_addr_zbits { ++ dns_rpz_zbits_t client_ip; ++ dns_rpz_zbits_t ip; ++ dns_rpz_zbits_t nsip; ++}; + +-typedef struct dns_rpz_cidr_node dns_rpz_cidr_node_t; +-typedef isc_uint8_t dns_rpz_cidr_flags_t; ++/* ++ * A CIDR or radix tree node. ++ */ + struct dns_rpz_cidr_node { +- dns_rpz_cidr_node_t *parent; +- dns_rpz_cidr_node_t *child[2]; +- dns_rpz_cidr_key_t ip; +- dns_rpz_cidr_bits_t bits; +- dns_rpz_cidr_flags_t flags; +-#define DNS_RPZ_CIDR_FG_IP 0x01 /* has IP data or is parent of IP */ +-#define DNS_RPZ_CIDR_FG_IP_DATA 0x02 /* has IP data */ +-#define DNS_RPZ_CIDR_FG_NSIPv4 0x04 /* has or is parent of NSIPv4 data */ +-#define DNS_RPZ_CIDR_FG_NSIPv6 0x08 /* has or is parent of NSIPv6 data */ +-#define DNS_RPZ_CIDR_FG_NSIP_DATA 0x10 /* has NSIP data */ ++ dns_rpz_cidr_node_t *parent; ++ dns_rpz_cidr_node_t *child[2]; ++ dns_rpz_cidr_key_t ip; ++ dns_rpz_prefix_t prefix; ++ dns_rpz_addr_zbits_t set; ++ dns_rpz_addr_zbits_t sum; ++}; ++ ++/* ++ * The data in a RBT node has two pairs of bits for policy zones. ++ * One pair is for the corresponding name of the node such as example.com ++ * and the other pair is for a wildcard child such as *.example.com. ++ */ ++/* ++ * A pair of arrays of bits flagging the existence of ++ * QNAME and NSDNAME policy triggers. ++ */ ++typedef struct dns_rpz_nm_zbits dns_rpz_nm_zbits_t; ++struct dns_rpz_nm_zbits { ++ dns_rpz_zbits_t qname; ++ dns_rpz_zbits_t ns; + }; + +-struct dns_rpz_cidr { +- isc_mem_t *mctx; +- isc_boolean_t have_nsdname; /* zone has NSDNAME record */ +- dns_rpz_cidr_node_t *root; +- dns_name_t ip_name; /* RPZ_IP_ZONE.origin. */ +- dns_name_t nsip_name; /* RPZ_NSIP_ZONE.origin. */ +- dns_name_t nsdname_name; /* RPZ_NSDNAME_ZONE.origin */ ++typedef struct dns_rpz_nm_data dns_rpz_nm_data_t; ++struct dns_rpz_nm_data { ++ dns_rpz_nm_zbits_t set; ++ dns_rpz_nm_zbits_t wild; + }; + ++#if 0 ++/* ++ * Catch a name while debugging. ++ */ ++static void ++catch_name(const dns_name_t *src_name, const char *tgt, const char *str) { ++ dns_fixedname_t tgt_namef; ++ dns_name_t *tgt_name; ++ ++ dns_fixedname_init(&tgt_namef); ++ tgt_name = dns_fixedname_name(&tgt_namef); ++ dns_name_fromstring(tgt_name, tgt, DNS_NAME_DOWNCASE, NULL); ++ if (dns_name_equal(src_name, tgt_name)) { ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, ++ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, ++ "rpz hit failed: %s %s", str, tgt); ++ } ++} ++#endif ++ + const char * + dns_rpz_type2str(dns_rpz_type_t type) { + switch (type) { ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ return ("CLIENT-IP"); + case DNS_RPZ_TYPE_QNAME: + return ("QNAME"); + case DNS_RPZ_TYPE_IP: +@@ -138,32 +190,33 @@ + case DNS_RPZ_TYPE_BAD: + break; + } +- FATAL_ERROR(__FILE__, __LINE__, +- "impossible rpz type %d", type); ++ FATAL_ERROR(__FILE__, __LINE__, "impossible rpz type %d", type); + return ("impossible"); + } + + dns_rpz_policy_t + dns_rpz_str2policy(const char *str) { ++ static struct { ++ const char *str; ++ dns_rpz_policy_t policy; ++ } tbl[] = { ++ {"given", DNS_RPZ_POLICY_GIVEN}, ++ {"disabled", DNS_RPZ_POLICY_DISABLED}, ++ {"passthru", DNS_RPZ_POLICY_PASSTHRU}, ++ {"drop", DNS_RPZ_POLICY_DROP}, ++ {"nxdomain", DNS_RPZ_POLICY_NXDOMAIN}, ++ {"nodata", DNS_RPZ_POLICY_NODATA}, ++ {"cname", DNS_RPZ_POLICY_CNAME}, ++ {"no-op", DNS_RPZ_POLICY_PASSTHRU}, /* Obsolete */ ++ }; ++ unsigned int n; ++ + if (str == NULL) + return (DNS_RPZ_POLICY_ERROR); +- if (!strcasecmp(str, "given")) +- return (DNS_RPZ_POLICY_GIVEN); +- if (!strcasecmp(str, "disabled")) +- return (DNS_RPZ_POLICY_DISABLED); +- if (!strcasecmp(str, "passthru")) +- return (DNS_RPZ_POLICY_PASSTHRU); +- if (!strcasecmp(str, "nxdomain")) +- return (DNS_RPZ_POLICY_NXDOMAIN); +- if (!strcasecmp(str, "nodata")) +- return (DNS_RPZ_POLICY_NODATA); +- if (!strcasecmp(str, "cname")) +- return (DNS_RPZ_POLICY_CNAME); +- /* +- * Obsolete +- */ +- if (!strcasecmp(str, "no-op")) +- return (DNS_RPZ_POLICY_PASSTHRU); ++ for (n = 0; n < sizeof(tbl)/sizeof(tbl[0]); ++n) { ++ if (!strcasecmp(tbl[n].str, str)) ++ return (tbl[n].policy); ++ } + return (DNS_RPZ_POLICY_ERROR); + } + +@@ -175,6 +228,9 @@ + case DNS_RPZ_POLICY_PASSTHRU: + str = "PASSTHRU"; + break; ++ case DNS_RPZ_POLICY_DROP: ++ str = "DROP"; ++ break; + case DNS_RPZ_POLICY_NXDOMAIN: + str = "NXDOMAIN"; + break; +@@ -196,243 +252,276 @@ + return (str); + } + +-/* +- * Free the radix tree of a response policy database. +- */ +-void +-dns_rpz_cidr_free(dns_rpz_cidr_t **cidrp) { +- dns_rpz_cidr_node_t *cur, *child, *parent; +- dns_rpz_cidr_t *cidr; +- +- REQUIRE(cidrp != NULL); +- +- cidr = *cidrp; +- if (cidr == NULL) +- return; +- +- cur = cidr->root; +- while (cur != NULL) { +- /* Depth first. */ +- child = cur->child[0]; +- if (child != NULL) { +- cur = child; +- continue; +- } +- child = cur->child[1]; +- if (child != NULL) { +- cur = child; +- continue; +- } ++static int ++zbit_to_num(dns_rpz_zbits_t zbit) { ++ dns_rpz_num_t rpz_num; + +- /* Delete this leaf and go up. */ +- parent = cur->parent; +- if (parent == NULL) +- cidr->root = NULL; +- else +- parent->child[parent->child[1] == cur] = NULL; +- isc_mem_put(cidr->mctx, cur, sizeof(*cur)); +- cur = parent; ++ INSIST(zbit != 0); ++ rpz_num = 0; ++#if DNS_RPZ_MAX_ZONES > 32 ++ if ((zbit & 0xffffffff00000000L) != 0) { ++ zbit >>= 32; ++ rpz_num += 32; + } +- +- dns_name_free(&cidr->ip_name, cidr->mctx); +- dns_name_free(&cidr->nsip_name, cidr->mctx); +- dns_name_free(&cidr->nsdname_name, cidr->mctx); +- isc_mem_put(cidr->mctx, cidr, sizeof(*cidr)); +- *cidrp = NULL; ++#endif ++ if ((zbit & 0xffff0000) != 0) { ++ zbit >>= 16; ++ rpz_num += 16; ++ } ++ if ((zbit & 0xff00) != 0) { ++ zbit >>= 8; ++ rpz_num += 8; ++ } ++ if ((zbit & 0xf0) != 0) { ++ zbit >>= 4; ++ rpz_num += 4; ++ } ++ if ((zbit & 0xc) != 0) { ++ zbit >>= 2; ++ rpz_num += 2; ++ } ++ if ((zbit & 2) != 0) ++ ++rpz_num; ++ return (rpz_num); + } + + /* +- * Forget a view's list of policy zones. ++ * Make a set of bit masks given one or more bits and their type. + */ +-void +-dns_rpz_view_destroy(dns_view_t *view) { +- dns_rpz_zone_t *zone; +- +- REQUIRE(view != NULL); ++static void ++make_addr_set(dns_rpz_addr_zbits_t *tgt_set, dns_rpz_zbits_t zbits, ++ dns_rpz_type_t type) ++{ ++ switch (type) { ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ tgt_set->client_ip = zbits; ++ tgt_set->ip = 0; ++ tgt_set->nsip = 0; ++ break; ++ case DNS_RPZ_TYPE_IP: ++ tgt_set->client_ip = 0; ++ tgt_set->ip = zbits; ++ tgt_set->nsip = 0; ++ break; ++ case DNS_RPZ_TYPE_NSIP: ++ tgt_set->client_ip = 0; ++ tgt_set->ip = 0; ++ tgt_set->nsip = zbits; ++ break; ++ default: ++ INSIST(0); ++ break; ++ } ++} + +- while (!ISC_LIST_EMPTY(view->rpz_zones)) { +- zone = ISC_LIST_HEAD(view->rpz_zones); +- ISC_LIST_UNLINK(view->rpz_zones, zone, link); +- if (dns_name_dynamic(&zone->origin)) +- dns_name_free(&zone->origin, view->mctx); +- if (dns_name_dynamic(&zone->passthru)) +- dns_name_free(&zone->passthru, view->mctx); +- if (dns_name_dynamic(&zone->nsdname)) +- dns_name_free(&zone->nsdname, view->mctx); +- if (dns_name_dynamic(&zone->cname)) +- dns_name_free(&zone->cname, view->mctx); +- isc_mem_put(view->mctx, zone, sizeof(*zone)); ++static void ++make_nm_set(dns_rpz_nm_zbits_t *tgt_set, ++ dns_rpz_num_t rpz_num, dns_rpz_type_t type) ++{ ++ switch (type) { ++ case DNS_RPZ_TYPE_QNAME: ++ tgt_set->qname = DNS_RPZ_ZBIT(rpz_num); ++ tgt_set->ns = 0; ++ break; ++ case DNS_RPZ_TYPE_NSDNAME: ++ tgt_set->qname = 0; ++ tgt_set->ns = DNS_RPZ_ZBIT(rpz_num); ++ break; ++ default: ++ INSIST(0); ++ break; + } + } + + /* +- * Start a new radix tree for a response policy zone. ++ * Mark a node and all of its parents as having client-IP, IP, or NSIP data + */ +-isc_result_t +-dns_rpz_new_cidr(isc_mem_t *mctx, dns_name_t *origin, +- dns_rpz_cidr_t **rbtdb_cidr) +-{ +- isc_result_t result; +- dns_rpz_cidr_t *cidr; +- +- REQUIRE(rbtdb_cidr != NULL && *rbtdb_cidr == NULL); +- +- cidr = isc_mem_get(mctx, sizeof(*cidr)); +- if (cidr == NULL) +- return (ISC_R_NOMEMORY); +- memset(cidr, 0, sizeof(*cidr)); +- cidr->mctx = mctx; ++static void ++set_sum_pair(dns_rpz_cidr_node_t *cnode) { ++ dns_rpz_cidr_node_t *child; ++ dns_rpz_addr_zbits_t sum; + +- dns_name_init(&cidr->ip_name, NULL); +- result = dns_name_fromstring2(&cidr->ip_name, DNS_RPZ_IP_ZONE, origin, +- DNS_NAME_DOWNCASE, mctx); +- if (result != ISC_R_SUCCESS) { +- isc_mem_put(mctx, cidr, sizeof(*cidr)); +- return (result); +- } ++ do { ++ sum = cnode->set; + +- dns_name_init(&cidr->nsip_name, NULL); +- result = dns_name_fromstring2(&cidr->nsip_name, DNS_RPZ_NSIP_ZONE, +- origin, DNS_NAME_DOWNCASE, mctx); +- if (result != ISC_R_SUCCESS) { +- dns_name_free(&cidr->ip_name, mctx); +- isc_mem_put(mctx, cidr, sizeof(*cidr)); +- return (result); +- } ++ child = cnode->child[0]; ++ if (child != NULL) { ++ sum.client_ip |= child->sum.client_ip; ++ sum.ip |= child->sum.ip; ++ sum.nsip |= child->sum.nsip; ++ } + +- dns_name_init(&cidr->nsdname_name, NULL); +- result = dns_name_fromstring2(&cidr->nsdname_name, DNS_RPZ_NSDNAME_ZONE, +- origin, DNS_NAME_DOWNCASE, mctx); +- if (result != ISC_R_SUCCESS) { +- dns_name_free(&cidr->nsip_name, mctx); +- dns_name_free(&cidr->ip_name, mctx); +- isc_mem_put(mctx, cidr, sizeof(*cidr)); +- return (result); +- } ++ child = cnode->child[1]; ++ if (child != NULL) { ++ sum.client_ip |= child->sum.client_ip; ++ sum.ip |= child->sum.ip; ++ sum.nsip |= child->sum.nsip; ++ } + +- *rbtdb_cidr = cidr; +- return (ISC_R_SUCCESS); ++ if (cnode->sum.client_ip == sum.client_ip && ++ cnode->sum.ip == sum.ip && ++ cnode->sum.nsip == sum.nsip) ++ break; ++ cnode->sum = sum; ++ cnode = cnode->parent; ++ } while (cnode != NULL); + } + +-/* +- * See if a policy zone has IP, NSIP, or NSDNAME rules or records. +- */ +-void +-dns_rpz_enabled_get(dns_rpz_cidr_t *cidr, dns_rpz_st_t *st) { +- if (cidr == NULL) +- return; +- if (cidr->root != NULL && +- (cidr->root->flags & DNS_RPZ_CIDR_FG_IP) != 0) +- st->state |= DNS_RPZ_HAVE_IP; +- if (cidr->root != NULL && +- (cidr->root->flags & DNS_RPZ_CIDR_FG_NSIPv4) != 0) +- st->state |= DNS_RPZ_HAVE_NSIPv4; +- if (cidr->root != NULL && +- (cidr->root->flags & DNS_RPZ_CIDR_FG_NSIPv6) != 0) +- st->state |= DNS_RPZ_HAVE_NSIPv6; +- if (cidr->have_nsdname) +- st->state |= DNS_RPZ_HAVE_NSDNAME; +-} +- +-static inline dns_rpz_cidr_flags_t +-get_flags(const dns_rpz_cidr_key_t *ip, dns_rpz_cidr_bits_t prefix, +- dns_rpz_type_t rpz_type) +-{ +- if (rpz_type == DNS_RPZ_TYPE_NSIP) { +- if (prefix >= 96 && +- ip->w[0] == 0 && ip->w[1] == 0 && +- ip->w[2] == ADDR_V4MAPPED) +- return (DNS_RPZ_CIDR_FG_NSIP_DATA | +- DNS_RPZ_CIDR_FG_NSIPv4); +- else +- return (DNS_RPZ_CIDR_FG_NSIP_DATA | +- DNS_RPZ_CIDR_FG_NSIPv6); ++static void ++fix_qname_skip_recurse(dns_rpz_zones_t *rpzs) { ++ dns_rpz_zbits_t zbits; ++ ++ /* ++ * Get a mask covering all policy zones that are not subordinate to ++ * other policy zones containing triggers that require that the ++ * qname be resolved before they can be checked. ++ */ ++ if (rpzs->p.qname_wait_recurse) { ++ zbits = 0; + } else { +- return (DNS_RPZ_CIDR_FG_IP | DNS_RPZ_CIDR_FG_IP_DATA); ++ zbits = (rpzs->have.ipv4 || rpzs->have.ipv6 || ++ rpzs->have.nsdname || ++ rpzs->have.nsipv4 || rpzs->have.nsipv6); ++ if (zbits == 0) { ++ zbits = DNS_RPZ_ALL_ZBITS; ++ } else { ++ zbits = DNS_RPZ_ZMASK(zbit_to_num(zbits)); ++ } + } ++ rpzs->have.qname_skip_recurse = zbits; ++ ++ rpzs->have.client_ip = rpzs->have.client_ipv4 | rpzs->have.client_ipv6; ++ rpzs->have.ip = rpzs->have.ipv4 | rpzs->have.ipv6; ++ rpzs->have.nsip = rpzs->have.nsipv4 | rpzs->have.nsipv6; + } + +-/* +- * Mark a node as having IP or NSIP data and all of its parents +- * as members of the IP or NSIP tree. +- */ + static void +-set_node_flags(dns_rpz_cidr_node_t *node, dns_rpz_type_t rpz_type) { +- dns_rpz_cidr_flags_t flags; ++adj_trigger_cnt(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, ++ dns_rpz_type_t rpz_type, ++ const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix, ++ isc_boolean_t inc) ++{ ++ dns_rpz_zone_t *rpz; ++ int *cnt; ++ dns_rpz_zbits_t *have; ++ ++ rpz = rpzs->zones[rpz_num]; ++ switch (rpz_type) { ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ REQUIRE(tgt_ip != NULL); ++ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { ++ cnt = &rpz->triggers.client_ipv4; ++ have = &rpzs->have.client_ipv4; ++ } else { ++ cnt = &rpz->triggers.client_ipv6; ++ have = &rpzs->have.client_ipv6; ++ } ++ break; ++ case DNS_RPZ_TYPE_QNAME: ++ cnt = &rpz->triggers.qname; ++ have = &rpzs->have.qname; ++ break; ++ case DNS_RPZ_TYPE_IP: ++ REQUIRE(tgt_ip != NULL); ++ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { ++ cnt = &rpz->triggers.ipv4; ++ have = &rpzs->have.ipv4; ++ } else { ++ cnt = &rpz->triggers.ipv6; ++ have = &rpzs->have.ipv6; ++ } ++ break; ++ case DNS_RPZ_TYPE_NSDNAME: ++ cnt = &rpz->triggers.nsdname; ++ have = &rpzs->have.nsdname; ++ break; ++ case DNS_RPZ_TYPE_NSIP: ++ REQUIRE(tgt_ip != NULL); ++ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { ++ cnt = &rpz->triggers.nsipv4; ++ have = &rpzs->have.nsipv4; ++ } else { ++ cnt = &rpz->triggers.nsipv6; ++ have = &rpzs->have.nsipv6; ++ } ++ break; ++ default: ++ INSIST(0); ++ } + +- flags = get_flags(&node->ip, node->bits, rpz_type); +- node->flags |= flags; +- flags &= ~(DNS_RPZ_CIDR_FG_NSIP_DATA | DNS_RPZ_CIDR_FG_IP_DATA); +- for (;;) { +- node = node->parent; +- if (node == NULL) +- return; +- node->flags |= flags; ++ if (inc) { ++ if (++*cnt == 1) { ++ *have |= DNS_RPZ_ZBIT(rpz_num); ++ fix_qname_skip_recurse(rpzs); ++ } ++ } else { ++ REQUIRE(*cnt > 0); ++ if (--*cnt == 0) { ++ *have &= ~DNS_RPZ_ZBIT(rpz_num); ++ fix_qname_skip_recurse(rpzs); ++ } + } + } + +-/* +- * Make a radix tree node. +- */ + static dns_rpz_cidr_node_t * +-new_node(dns_rpz_cidr_t *cidr, const dns_rpz_cidr_key_t *ip, +- dns_rpz_cidr_bits_t bits, dns_rpz_cidr_flags_t flags) ++new_node(dns_rpz_zones_t *rpzs, ++ const dns_rpz_cidr_key_t *ip, dns_rpz_prefix_t prefix, ++ const dns_rpz_cidr_node_t *child) + { +- dns_rpz_cidr_node_t *node; ++ dns_rpz_cidr_node_t *new; + int i, words, wlen; + +- node = isc_mem_get(cidr->mctx, sizeof(*node)); +- if (node == NULL) ++ new = isc_mem_get(rpzs->mctx, sizeof(*new)); ++ if (new == NULL) + return (NULL); +- memset(node, 0, sizeof(*node)); ++ memset(new, 0, sizeof(*new)); + +- node->flags = flags & ~(DNS_RPZ_CIDR_FG_IP_DATA | +- DNS_RPZ_CIDR_FG_NSIP_DATA); ++ if (child != NULL) ++ new->sum = child->sum; + +- node->bits = bits; +- words = bits / DNS_RPZ_CIDR_WORD_BITS; +- wlen = bits % DNS_RPZ_CIDR_WORD_BITS; ++ new->prefix = prefix; ++ words = prefix / DNS_RPZ_CIDR_WORD_BITS; ++ wlen = prefix % DNS_RPZ_CIDR_WORD_BITS; + i = 0; + while (i < words) { +- node->ip.w[i] = ip->w[i]; ++ new->ip.w[i] = ip->w[i]; + ++i; + } + if (wlen != 0) { +- node->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen); ++ new->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen); + ++i; + } + while (i < DNS_RPZ_CIDR_WORDS) +- node->ip.w[i++] = 0; ++ new->ip.w[i++] = 0; + +- return (node); ++ return (new); + } + + static void + badname(int level, dns_name_t *name, const char *str1, const char *str2) { +- char printname[DNS_NAME_FORMATSIZE]; ++ char namebuf[DNS_NAME_FORMATSIZE]; + + /* + * bin/tests/system/rpz/tests.sh looks for "invalid rpz". + */ +- if (level < DNS_RPZ_DEBUG_QUIET +- && isc_log_wouldlog(dns_lctx, level)) { +- dns_name_format(name, printname, sizeof(printname)); ++ if (level < DNS_RPZ_DEBUG_QUIET && ++ isc_log_wouldlog(dns_lctx, level)) { ++ dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, level, + "invalid rpz IP address \"%s\"%s%s", +- printname, str1, str2); ++ namebuf, str1, str2); + } + } + + /* + * Convert an IP address from radix tree binary (host byte order) to +- * to its canonical response policy domain name and its name in the ++ * to its canonical response policy domain name without the origin of the + * policy zone. + */ + static isc_result_t +-ip2name(dns_rpz_cidr_t *cidr, const dns_rpz_cidr_key_t *tgt_ip, +- dns_rpz_cidr_bits_t tgt_prefix, dns_rpz_type_t type, +- dns_name_t *canon_name, dns_name_t *search_name) ++ip2name(const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix, ++ dns_name_t *base_name, dns_name_t *ip_name) + { + #ifndef INET6_ADDRSTRLEN + #define INET6_ADDRSTRLEN 46 +@@ -440,22 +529,18 @@ + int w[DNS_RPZ_CIDR_WORDS*2]; + char str[1+8+1+INET6_ADDRSTRLEN+1]; + isc_buffer_t buffer; +- dns_name_t *name; + isc_result_t result; + isc_boolean_t zeros; + int i, n, len; + +- if (tgt_prefix > 96 && +- tgt_ip->w[0] == 0 && +- tgt_ip->w[1] == 0 && +- tgt_ip->w[2] == ADDR_V4MAPPED) { ++ if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + len = snprintf(str, sizeof(str), "%d.%d.%d.%d.%d", + tgt_prefix - 96, + tgt_ip->w[3] & 0xff, + (tgt_ip->w[3]>>8) & 0xff, + (tgt_ip->w[3]>>16) & 0xff, + (tgt_ip->w[3]>>24) & 0xff); +- if (len == -1 || len > (int)sizeof(str)) ++ if (len < 0 || len > (int)sizeof(str)) + return (ISC_R_FAILURE); + } else { + for (i = 0; i < DNS_RPZ_CIDR_WORDS; i++) { +@@ -469,9 +554,9 @@ + return (ISC_R_FAILURE); + i = 0; + while (i < DNS_RPZ_CIDR_WORDS * 2) { +- if (w[i] != 0 || zeros +- || i >= DNS_RPZ_CIDR_WORDS * 2 - 1 +- || w[i+1] != 0) { ++ if (w[i] != 0 || zeros || ++ i >= DNS_RPZ_CIDR_WORDS * 2 - 1 || ++ w[i+1] != 0) { + INSIST((size_t)len <= sizeof(str)); + n = snprintf(&str[len], sizeof(str) - len, + ".%x", w[i++]); +@@ -495,48 +580,31 @@ + } + } + +- if (canon_name != NULL) { +- isc__buffer_init(&buffer, str, sizeof(str)); +- isc__buffer_add(&buffer, len); +- result = dns_name_fromtext(canon_name, &buffer, +- dns_rootname, 0, NULL); +- if (result != ISC_R_SUCCESS) +- return (result); +- } +- if (search_name != NULL) { +- isc__buffer_init(&buffer, str, sizeof(str)); +- isc__buffer_add(&buffer, len); +- if (type == DNS_RPZ_TYPE_NSIP) +- name = &cidr->nsip_name; +- else +- name = &cidr->ip_name; +- result = dns_name_fromtext(search_name, &buffer, name, 0, NULL); +- if (result != ISC_R_SUCCESS) +- return (result); +- } +- return (ISC_R_SUCCESS); ++ isc__buffer_init(&buffer, str, sizeof(str)); ++ isc__buffer_add(&buffer, len); ++ result = dns_name_fromtext(ip_name, &buffer, base_name, 0, NULL); ++ return (result); + } + + /* +- * Decide which kind of IP address response policy zone a name is in. ++ * Determine the type a of a name in a response policy zone. + */ + static dns_rpz_type_t +-set_type(dns_rpz_cidr_t *cidr, dns_name_t *name) { ++type_from_name(dns_rpz_zone_t *rpz, dns_name_t *name) { + +- if (dns_name_issubdomain(name, &cidr->ip_name)) ++ if (dns_name_issubdomain(name, &rpz->ip)) + return (DNS_RPZ_TYPE_IP); + +- /* +- * Require `./configure --enable-rpz-nsip` and nsdname +- * until consistency problems are resolved. +- */ ++ if (dns_name_issubdomain(name, &rpz->client_ip)) ++ return (DNS_RPZ_TYPE_CLIENT_IP); ++ + #ifdef ENABLE_RPZ_NSIP +- if (dns_name_issubdomain(name, &cidr->nsip_name)) ++ if (dns_name_issubdomain(name, &rpz->nsip)) + return (DNS_RPZ_TYPE_NSIP); + #endif + + #ifdef ENABLE_RPZ_NSDNAME +- if (dns_name_issubdomain(name, &cidr->nsdname_name)) ++ if (dns_name_issubdomain(name, &rpz->nsdname)) + return (DNS_RPZ_TYPE_NSDNAME); + #endif + +@@ -545,73 +613,80 @@ + + /* + * Convert an IP address from canonical response policy domain name form +- * to radix tree binary (host byte order). ++ * to radix tree binary (host byte order) for adding or deleting IP or NSIP ++ * data. + */ + static isc_result_t +-name2ipkey(dns_rpz_cidr_t *cidr, int level, dns_name_t *src_name, +- dns_rpz_type_t type, dns_rpz_cidr_key_t *tgt_ip, +- dns_rpz_cidr_bits_t *tgt_prefix) ++name2ipkey(int log_level, ++ const dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, ++ dns_rpz_type_t rpz_type, dns_name_t *src_name, ++ dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t *tgt_prefix, ++ dns_rpz_addr_zbits_t *new_set) + { +- isc_result_t result; +- dns_fixedname_t fname; +- dns_name_t *ipname; +- char ipstr[DNS_NAME_FORMATSIZE]; ++ dns_rpz_zone_t *rpz; ++ char ip_str[DNS_NAME_FORMATSIZE]; ++ dns_offsets_t ip_name_offsets; ++ dns_fixedname_t ip_name2f; ++ dns_name_t ip_name, *ip_name2; + const char *prefix_str, *cp, *end; + char *cp2; + int ip_labels; +- dns_rpz_cidr_bits_t bits; +- unsigned long prefix, l; ++ dns_rpz_prefix_t prefix; ++ unsigned long prefix_num, l; ++ isc_result_t result; + int i; + +- /* +- * Need at least enough labels for the shortest name, +- * :: or 128.*.RPZ_x_ZONE.rpz.LOCALHOST. +- */ ++ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); ++ rpz = rpzs->zones[rpz_num]; ++ REQUIRE(rpz != NULL); ++ ++ make_addr_set(new_set, DNS_RPZ_ZBIT(rpz_num), rpz_type); ++ + ip_labels = dns_name_countlabels(src_name); +- ip_labels -= dns_name_countlabels(&cidr->ip_name); +- ip_labels--; +- if (ip_labels < 1) { +- badname(level, src_name, "; too short", ""); ++ if (rpz_type == DNS_RPZ_TYPE_QNAME) ++ ip_labels -= dns_name_countlabels(&rpz->origin); ++ else ++ ip_labels -= dns_name_countlabels(&rpz->nsdname); ++ if (ip_labels < 2) { ++ badname(log_level, src_name, "; too short", ""); + return (ISC_R_FAILURE); + } ++ dns_name_init(&ip_name, ip_name_offsets); ++ dns_name_getlabelsequence(src_name, 0, ip_labels, &ip_name); + + /* + * Get text for the IP address + */ +- dns_fixedname_init(&fname); +- ipname = dns_fixedname_name(&fname); +- dns_name_split(src_name, dns_name_countlabels(&cidr->ip_name), +- ipname, NULL); +- dns_name_format(ipname, ipstr, sizeof(ipstr)); +- end = &ipstr[strlen(ipstr)+1]; +- prefix_str = ipstr; ++ dns_name_format(&ip_name, ip_str, sizeof(ip_str)); ++ end = &ip_str[strlen(ip_str)+1]; ++ prefix_str = ip_str; + +- prefix = strtoul(prefix_str, &cp2, 10); ++ prefix_num = strtoul(prefix_str, &cp2, 10); + if (*cp2 != '.') { +- badname(level, src_name, ++ badname(log_level, src_name, + "; invalid leading prefix length", ""); + return (ISC_R_FAILURE); + } + *cp2 = '\0'; +- if (prefix < 1U || prefix > 128U) { +- badname(level, src_name, ++ if (prefix_num < 1U || prefix_num > 128U) { ++ badname(log_level, src_name, + "; invalid prefix length of ", prefix_str); + return (ISC_R_FAILURE); + } + cp = cp2+1; + +- if (ip_labels == 4 && !strchr(cp, 'z')) { ++ if (--ip_labels == 4 && !strchr(cp, 'z')) { + /* + * Convert an IPv4 address + * from the form "prefix.w.z.y.x" + */ +- if (prefix > 32U) { +- badname(level, src_name, ++ if (prefix_num > 32U) { ++ badname(log_level, src_name, + "; invalid IPv4 prefix length of ", prefix_str); + return (ISC_R_FAILURE); + } +- prefix += 96; +- *tgt_prefix = (dns_rpz_cidr_bits_t)prefix; ++ prefix_num += 96; ++ *tgt_prefix = (dns_rpz_prefix_t)prefix_num; + tgt_ip->w[0] = 0; + tgt_ip->w[1] = 0; + tgt_ip->w[2] = ADDR_V4MAPPED; +@@ -621,7 +696,7 @@ + if (l > 255U || (*cp2 != '.' && *cp2 != '\0')) { + if (*cp2 == '.') + *cp2 = '\0'; +- badname(level, src_name, ++ badname(log_level, src_name, + "; invalid IPv4 octet ", cp); + return (ISC_R_FAILURE); + } +@@ -632,7 +707,7 @@ + /* + * Convert a text IPv6 address. + */ +- *tgt_prefix = (dns_rpz_cidr_bits_t)prefix; ++ *tgt_prefix = (dns_rpz_prefix_t)prefix_num; + for (i = 0; + ip_labels > 0 && i < DNS_RPZ_CIDR_WORDS * 2; + ip_labels--) { +@@ -651,7 +726,7 @@ + (*cp2 != '.' && *cp2 != '\0')) { + if (*cp2 == '.') + *cp2 = '\0'; +- badname(level, src_name, ++ badname(log_level, src_name, + "; invalid IPv6 word ", cp); + return (ISC_R_FAILURE); + } +@@ -665,36 +740,37 @@ + } + } + if (cp != end) { +- badname(level, src_name, "", ""); ++ badname(log_level, src_name, "", ""); + return (ISC_R_FAILURE); + } + + /* + * Check for 1s after the prefix length. + */ +- bits = (dns_rpz_cidr_bits_t)prefix; +- while (bits < DNS_RPZ_CIDR_KEY_BITS) { ++ prefix = (dns_rpz_prefix_t)prefix_num; ++ while (prefix < DNS_RPZ_CIDR_KEY_BITS) { + dns_rpz_cidr_word_t aword; + +- i = bits % DNS_RPZ_CIDR_WORD_BITS; +- aword = tgt_ip->w[bits / DNS_RPZ_CIDR_WORD_BITS]; ++ i = prefix % DNS_RPZ_CIDR_WORD_BITS; ++ aword = tgt_ip->w[prefix / DNS_RPZ_CIDR_WORD_BITS]; + if ((aword & ~DNS_RPZ_WORD_MASK(i)) != 0) { +- badname(level, src_name, ++ badname(log_level, src_name, + "; too small prefix length of ", prefix_str); + return (ISC_R_FAILURE); + } +- bits -= i; +- bits += DNS_RPZ_CIDR_WORD_BITS; ++ prefix -= i; ++ prefix += DNS_RPZ_CIDR_WORD_BITS; + } + + /* +- * Convert the address back to a canonical policy domain name +- * to ensure that it is in canonical form. ++ * Convert the address back to a canonical domain name ++ * to ensure that the original name is in canonical form. + */ +- result = ip2name(cidr, tgt_ip, (dns_rpz_cidr_bits_t) prefix, +- type, NULL, ipname); +- if (result != ISC_R_SUCCESS || !dns_name_equal(src_name, ipname)) { +- badname(level, src_name, "; not canonical", ""); ++ dns_fixedname_init(&ip_name2f); ++ ip_name2 = dns_fixedname_name(&ip_name2f); ++ result = ip2name(tgt_ip, (dns_rpz_prefix_t)prefix_num, NULL, ip_name2); ++ if (result != ISC_R_SUCCESS || !dns_name_equal(&ip_name, ip_name2)) { ++ badname(log_level, src_name, "; not canonical", ""); + return (ISC_R_FAILURE); + } + +@@ -702,10 +778,54 @@ + } + + /* +- * Find first differing bit. ++ * Get trigger name and data bits for adding or deleting summary NSDNAME ++ * or QNAME data. + */ +-static int +-ffbit(dns_rpz_cidr_word_t w) { ++static void ++name2data(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, ++ dns_rpz_type_t rpz_type, const dns_name_t *src_name, ++ dns_name_t *trig_name, dns_rpz_nm_data_t *new_data) ++{ ++ dns_rpz_zone_t *rpz; ++ dns_offsets_t tmp_name_offsets; ++ dns_name_t tmp_name; ++ unsigned int prefix_len, n; ++ ++ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); ++ rpz = rpzs->zones[rpz_num]; ++ REQUIRE(rpz != NULL); ++ ++ /* ++ * Handle wildcards by putting only the parent into the ++ * summary RBT. The summary database only causes a check of the ++ * real policy zone where wildcards will be handled. ++ */ ++ if (dns_name_iswildcard(src_name)) { ++ prefix_len = 1; ++ memset(&new_data->set, 0, sizeof(new_data->set)); ++ make_nm_set(&new_data->wild, rpz_num, rpz_type); ++ } else { ++ prefix_len = 0; ++ make_nm_set(&new_data->set, rpz_num, rpz_type); ++ memset(&new_data->wild, 0, sizeof(new_data->wild)); ++ } ++ ++ dns_name_init(&tmp_name, tmp_name_offsets); ++ n = dns_name_countlabels(src_name); ++ n -= prefix_len; ++ if (rpz_type == DNS_RPZ_TYPE_QNAME) ++ n -= dns_name_countlabels(&rpz->origin); ++ else ++ n -= dns_name_countlabels(&rpz->nsdname); ++ dns_name_getlabelsequence(src_name, prefix_len, n, &tmp_name); ++ (void)dns_name_concatenate(&tmp_name, dns_rootname, trig_name, NULL); ++} ++ ++/* ++ * Find the first differing bit in a key (IP address) word. ++ */ ++static inline int ++ffs_keybit(dns_rpz_cidr_word_t w) { + int bit; + + bit = DNS_RPZ_CIDR_WORD_BITS-1; +@@ -731,17 +851,17 @@ + } + + /* +- * Find the first differing bit in two keys. ++ * Find the first differing bit in two keys (IP addresses). + */ + static int +-diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_cidr_bits_t bits1, +- const dns_rpz_cidr_key_t *key2, dns_rpz_cidr_bits_t bits2) ++diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_prefix_t prefix1, ++ const dns_rpz_cidr_key_t *key2, dns_rpz_prefix_t prefix2) + { + dns_rpz_cidr_word_t delta; +- dns_rpz_cidr_bits_t maxbit, bit; ++ dns_rpz_prefix_t maxbit, bit; + int i; + +- maxbit = ISC_MIN(bits1, bits2); ++ maxbit = ISC_MIN(prefix1, prefix2); + + /* + * find the first differing words +@@ -751,7 +871,7 @@ + i++, bit += DNS_RPZ_CIDR_WORD_BITS) { + delta = key1->w[i] ^ key2->w[i]; + if (delta != 0) { +- bit += ffbit(delta); ++ bit += ffs_keybit(delta); + break; + } + } +@@ -759,133 +879,170 @@ + } + + /* ++ * Given a hit while searching the radix trees, ++ * clear all bits for higher numbered zones. ++ */ ++static inline dns_rpz_zbits_t ++trim_zbits(dns_rpz_zbits_t zbits, dns_rpz_zbits_t found) { ++ dns_rpz_zbits_t x; ++ ++ /* ++ * Isolate the first or smallest numbered hit bit. ++ * Make a mask of that bit and all smaller numbered bits. ++ */ ++ x = zbits & found; ++ x &= (~x + 1); ++ x = (x << 1) - 1; ++ return (zbits &= x); ++} ++ ++/* + * Search a radix tree for an IP address for ordinary lookup + * or for a CIDR block adding or deleting an entry +- * The tree read (for simple search) or write lock must be held by the caller. + * +- * Return ISC_R_SUCCESS, ISC_R_NOTFOUND, DNS_R_PARTIALMATCH, ISC_R_EXISTS, +- * ISC_R_NOMEMORY ++ * Return ISC_R_SUCCESS, DNS_R_PARTIALMATCH, ISC_R_NOTFOUND, ++ * and *found=longest match node ++ * or with create==ISC_TRUE, ISC_R_EXISTS or ISC_R_NOMEMORY + */ + static isc_result_t +-search(dns_rpz_cidr_t *cidr, const dns_rpz_cidr_key_t *tgt_ip, +- dns_rpz_cidr_bits_t tgt_prefix, dns_rpz_type_t type, +- isc_boolean_t create, +- dns_rpz_cidr_node_t **found) /* NULL or longest match node */ ++search(dns_rpz_zones_t *rpzs, ++ const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix, ++ const dns_rpz_addr_zbits_t *tgt_set, isc_boolean_t create, ++ dns_rpz_cidr_node_t **found) + { + dns_rpz_cidr_node_t *cur, *parent, *child, *new_parent, *sibling; ++ dns_rpz_addr_zbits_t set; + int cur_num, child_num; +- dns_rpz_cidr_bits_t dbit; +- dns_rpz_cidr_flags_t flags, data_flag; ++ dns_rpz_prefix_t dbit; + isc_result_t find_result; + +- flags = get_flags(tgt_ip, tgt_prefix, type); +- data_flag = flags & (DNS_RPZ_CIDR_FG_IP_DATA | +- DNS_RPZ_CIDR_FG_NSIP_DATA); +- ++ set = *tgt_set; + find_result = ISC_R_NOTFOUND; +- if (found != NULL) +- *found = NULL; +- cur = cidr->root; ++ *found = NULL; ++ cur = rpzs->cidr; + parent = NULL; + cur_num = 0; + for (;;) { + if (cur == NULL) { + /* +- * No child so we cannot go down. Fail or +- * add the target as a child of the current parent. ++ * No child so we cannot go down. ++ * Quit with whatever we already found ++ * or add the target as a child of the current parent. + */ + if (!create) + return (find_result); +- child = new_node(cidr, tgt_ip, tgt_prefix, 0); ++ child = new_node(rpzs, tgt_ip, tgt_prefix, NULL); + if (child == NULL) + return (ISC_R_NOMEMORY); + if (parent == NULL) +- cidr->root = child; ++ rpzs->cidr = child; + else + parent->child[cur_num] = child; + child->parent = parent; +- set_node_flags(child, type); +- if (found != NULL) +- *found = cur; ++ child->set.client_ip |= tgt_set->client_ip; ++ child->set.ip |= tgt_set->ip; ++ child->set.nsip |= tgt_set->nsip; ++ set_sum_pair(child); ++ *found = cur; + return (ISC_R_SUCCESS); + } + +- /* +- * Pretend a node not in the correct tree does not exist +- * if we are not adding to the tree, +- * If we are adding, then continue down to eventually +- * add a node and mark/put this node in the correct tree. +- */ +- if ((cur->flags & flags) == 0 && !create) +- return (find_result); ++ if ((cur->sum.client_ip & set.client_ip) == 0 && ++ (cur->sum.ip & set.ip) == 0 && ++ (cur->sum.nsip & set.nsip) == 0) { ++ /* ++ * This node has no relevant data ++ * and is in none of the target trees. ++ * Pretend it does not exist if we are not adding. ++ * ++ * If we are adding, continue down to eventually add ++ * a node and mark/put this node in the correct tree. ++ */ ++ if (!create) ++ return (find_result); ++ } + +- dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->bits); ++ dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->prefix); + /* +- * dbit <= tgt_prefix and dbit <= cur->bits always. ++ * dbit <= tgt_prefix and dbit <= cur->prefix always. + * We are finished searching if we matched all of the target. + */ + if (dbit == tgt_prefix) { +- if (tgt_prefix == cur->bits) { ++ if (tgt_prefix == cur->prefix) { + /* +- * The current node matches the target exactly. +- * It is the answer if it has data. ++ * The node's key matches the target exactly. + */ +- if ((cur->flags & data_flag) != 0) { +- if (create) +- return (ISC_R_EXISTS); +- if (found != NULL) +- *found = cur; +- return (ISC_R_SUCCESS); ++ if ((cur->set.client_ip & set.client_ip) != 0 || ++ (cur->set.ip & set.ip) != 0 || ++ (cur->set.nsip & set.nsip) != 0) { ++ /* ++ * It is the answer if it has data. ++ */ ++ *found = cur; ++ if (create) { ++ find_result = ISC_R_EXISTS; ++ } else { ++ find_result = ISC_R_SUCCESS; ++ } + } else if (create) { + /* +- * The node had no data but does now. ++ * The node lacked relevant data, ++ * but will have it now. + */ +- set_node_flags(cur, type); +- if (found != NULL) +- *found = cur; +- return (ISC_R_SUCCESS); ++ cur->set.client_ip |= tgt_set->client_ip; ++ cur->set.ip |= tgt_set->ip; ++ cur->set.nsip |= tgt_set->nsip; ++ set_sum_pair(cur); ++ *found = cur; ++ find_result = ISC_R_SUCCESS; + } + return (find_result); + } + + /* +- * We know tgt_prefix < cur_bits which means that ++ * We know tgt_prefix < cur->prefix which means that + * the target is shorter than the current node. + * Add the target as the current node's parent. + */ + if (!create) + return (find_result); + +- new_parent = new_node(cidr, tgt_ip, tgt_prefix, +- cur->flags); ++ new_parent = new_node(rpzs, tgt_ip, tgt_prefix, cur); + if (new_parent == NULL) + return (ISC_R_NOMEMORY); + new_parent->parent = parent; + if (parent == NULL) +- cidr->root = new_parent; ++ rpzs->cidr = new_parent; + else + parent->child[cur_num] = new_parent; + child_num = DNS_RPZ_IP_BIT(&cur->ip, tgt_prefix+1); + new_parent->child[child_num] = cur; + cur->parent = new_parent; +- set_node_flags(new_parent, type); +- if (found != NULL) +- *found = new_parent; ++ new_parent->set = *tgt_set; ++ set_sum_pair(new_parent); ++ *found = new_parent; + return (ISC_R_SUCCESS); + } + +- if (dbit == cur->bits) { +- /* +- * We have a partial match by matching of all of the +- * current node but only part of the target. +- * Try to go down. +- */ +- if ((cur->flags & data_flag) != 0) { ++ if (dbit == cur->prefix) { ++ if ((cur->set.client_ip & set.client_ip) != 0 || ++ (cur->set.ip & set.ip) != 0 || ++ (cur->set.nsip & set.nsip) != 0) { ++ /* ++ * We have a partial match between of all of the ++ * current node but only part of the target. ++ * Continue searching for other hits in the ++ * same or lower numbered trees. ++ */ + find_result = DNS_R_PARTIALMATCH; +- if (found != NULL) +- *found = cur; ++ *found = cur; ++ set.client_ip = trim_zbits(set.ip, ++ cur->set.client_ip); ++ set.ip = trim_zbits(set.ip, ++ cur->set.ip); ++ set.nsip = trim_zbits(set.nsip, ++ cur->set.nsip); + } +- + parent = cur; + cur_num = DNS_RPZ_IP_BIT(tgt_ip, dbit); + cur = cur->child[cur_num]; +@@ -894,7 +1051,7 @@ + + + /* +- * dbit < tgt_prefix and dbit < cur->bits, ++ * dbit < tgt_prefix and dbit < cur->prefix, + * so we failed to match both the target and the current node. + * Insert a fork of a parent above the current node and + * add the target as a sibling of the current node +@@ -902,17 +1059,17 @@ + if (!create) + return (find_result); + +- sibling = new_node(cidr, tgt_ip, tgt_prefix, 0); ++ sibling = new_node(rpzs, tgt_ip, tgt_prefix, NULL); + if (sibling == NULL) + return (ISC_R_NOMEMORY); +- new_parent = new_node(cidr, tgt_ip, dbit, cur->flags); ++ new_parent = new_node(rpzs, tgt_ip, dbit, cur); + if (new_parent == NULL) { +- isc_mem_put(cidr->mctx, sibling, sizeof(*sibling)); ++ isc_mem_put(rpzs->mctx, sibling, sizeof(*sibling)); + return (ISC_R_NOMEMORY); + } + new_parent->parent = parent; + if (parent == NULL) +- cidr->root = new_parent; ++ rpzs->cidr = new_parent; + else + parent->child[cur_num] = new_parent; + child_num = DNS_RPZ_IP_BIT(tgt_ip, dbit); +@@ -920,129 +1077,675 @@ + new_parent->child[1-child_num] = cur; + cur->parent = new_parent; + sibling->parent = new_parent; +- set_node_flags(sibling, type); +- if (found != NULL) +- *found = sibling; ++ sibling->set = *tgt_set; ++ set_sum_pair(sibling); ++ *found = sibling; + return (ISC_R_SUCCESS); + } + } + + /* +- * Add an IP address to the radix tree of a response policy database. +- * The tree write lock must be held by the caller. ++ * Add an IP address to the radix tree. + */ +-void +-dns_rpz_cidr_addip(dns_rpz_cidr_t *cidr, dns_name_t *name) { +- isc_result_t result; ++static isc_result_t ++add_cidr(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, ++ dns_rpz_type_t rpz_type, dns_name_t *src_name) ++{ + dns_rpz_cidr_key_t tgt_ip; +- dns_rpz_cidr_bits_t tgt_prefix; +- dns_rpz_type_t type; +- +- REQUIRE(cidr != NULL); ++ dns_rpz_prefix_t tgt_prefix; ++ dns_rpz_addr_zbits_t set; ++ dns_rpz_cidr_node_t *found; ++ isc_result_t result; + ++ result = name2ipkey(DNS_RPZ_ERROR_LEVEL, rpzs, rpz_num, rpz_type, ++ src_name, &tgt_ip, &tgt_prefix, &set); + /* +- * No worries if the new name is not an IP address. ++ * Log complaints about bad owner names but let the zone load. + */ +- type = set_type(cidr, name); +- switch (type) { +- case DNS_RPZ_TYPE_IP: +- case DNS_RPZ_TYPE_NSIP: +- break; +- case DNS_RPZ_TYPE_NSDNAME: +- cidr->have_nsdname = ISC_TRUE; +- return; +- case DNS_RPZ_TYPE_QNAME: +- case DNS_RPZ_TYPE_BAD: +- return; +- } +- result = name2ipkey(cidr, DNS_RPZ_ERROR_LEVEL, name, +- type, &tgt_ip, &tgt_prefix); + if (result != ISC_R_SUCCESS) +- return; ++ return (ISC_R_SUCCESS); + +- result = search(cidr, &tgt_ip, tgt_prefix, type, ISC_TRUE, NULL); +- if (result == ISC_R_EXISTS && +- isc_log_wouldlog(dns_lctx, DNS_RPZ_ERROR_LEVEL)) +- { +- char printname[DNS_NAME_FORMATSIZE]; ++ result = search(rpzs, &tgt_ip, tgt_prefix, &set, ISC_TRUE, &found); ++ if (result != ISC_R_SUCCESS) { ++ char namebuf[DNS_NAME_FORMATSIZE]; + + /* + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + */ +- dns_name_format(name, printname, sizeof(printname)); ++ dns_name_format(src_name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, +- "rpz add failed; \"%s\" is a duplicate name", +- printname); ++ "rpz add_cidr(%s) failed: %s", ++ namebuf, isc_result_totext(result)); ++ return (result); + } ++ ++ adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, ISC_TRUE); ++ return (result); + } + +-/* +- * Delete an IP address from the radix tree of a response policy database. +- * The tree write lock must be held by the caller. +- */ +-void +-dns_rpz_cidr_deleteip(dns_rpz_cidr_t *cidr, dns_name_t *name) { ++static isc_result_t ++add_nm(dns_rpz_zones_t *rpzs, dns_name_t *trig_name, ++ const dns_rpz_nm_data_t *new_data) ++{ ++ dns_rbtnode_t *nmnode; ++ dns_rpz_nm_data_t *nm_data; + isc_result_t result; +- dns_rpz_cidr_key_t tgt_ip; +- dns_rpz_cidr_bits_t tgt_prefix; +- dns_rpz_type_t type; +- dns_rpz_cidr_node_t *tgt = NULL, *parent, *child; +- dns_rpz_cidr_flags_t flags, data_flag; + +- if (cidr == NULL) +- return; ++ nmnode = NULL; ++ result = dns_rbt_addnode(rpzs->rbt, trig_name, &nmnode); ++ switch (result) { ++ case ISC_R_SUCCESS: ++ case ISC_R_EXISTS: ++ nm_data = nmnode->data; ++ if (nm_data == NULL) { ++ nm_data = isc_mem_get(rpzs->mctx, sizeof(*nm_data)); ++ if (nm_data == NULL) ++ return (ISC_R_NOMEMORY); ++ *nm_data = *new_data; ++ nmnode->data = nm_data; ++ return (ISC_R_SUCCESS); ++ } ++ break; ++ default: ++ return (result); ++ } + + /* +- * Decide which kind of policy zone IP address it is, if either +- * and then find its node. ++ * Do not count bits that are already present + */ +- type = set_type(cidr, name); +- switch (type) { +- case DNS_RPZ_TYPE_IP: +- case DNS_RPZ_TYPE_NSIP: +- break; +- case DNS_RPZ_TYPE_NSDNAME: ++ if ((nm_data->set.qname & new_data->set.qname) != 0 || ++ (nm_data->set.ns & new_data->set.ns) != 0 || ++ (nm_data->wild.qname & new_data->wild.qname) != 0 || ++ (nm_data->wild.ns & new_data->wild.ns) != 0) { ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ + /* +- * We cannot easily count nsdnames because +- * internal rbt nodes get deleted. ++ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + */ ++ dns_name_format(trig_name, namebuf, sizeof(namebuf)); ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, ++ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, ++ "rpz add_nm(%s): bits already set", namebuf); ++ return (ISC_R_EXISTS); ++ } ++ ++ nm_data->set.qname |= new_data->set.qname; ++ nm_data->set.ns |= new_data->set.ns; ++ nm_data->wild.qname |= new_data->wild.qname; ++ nm_data->wild.ns |= new_data->wild.ns; ++ return (ISC_R_SUCCESS); ++} ++ ++static isc_result_t ++add_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, ++ dns_rpz_type_t rpz_type, dns_name_t *src_name) ++{ ++ dns_rpz_nm_data_t new_data; ++ dns_fixedname_t trig_namef; ++ dns_name_t *trig_name; ++ isc_result_t result; ++ ++ dns_fixedname_init(&trig_namef); ++ trig_name = dns_fixedname_name(&trig_namef); ++ name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &new_data); ++ ++ result = add_nm(rpzs, trig_name, &new_data); ++ if (result == ISC_R_SUCCESS) ++ adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, ISC_TRUE); ++ return (result); ++} ++ ++/* ++ * Callback to free the data for a node in the summary RBT database. ++ */ ++static void ++rpz_node_deleter(void *nm_data, void *mctx) { ++ isc_mem_put(mctx, nm_data, sizeof(dns_rpz_nm_data_t)); ++} ++ ++/* ++ * Get ready for a new set of policy zones. ++ */ ++isc_result_t ++dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, isc_mem_t *mctx) { ++ dns_rpz_zones_t *new; ++ isc_result_t result; ++ ++ REQUIRE(rpzsp != NULL && *rpzsp == NULL); ++ ++ *rpzsp = NULL; ++ ++ new = isc_mem_get(mctx, sizeof(*new)); ++ if (new == NULL) ++ return (ISC_R_NOMEMORY); ++ memset(new, 0, sizeof(*new)); ++ ++ result = isc_mutex_init(&new->search_lock); ++ if (result != ISC_R_SUCCESS) { ++ isc_mem_put(mctx, new, sizeof(*new)); ++ return (result); ++ } ++ ++ result = isc_mutex_init(&new->maint_lock); ++ if (result != ISC_R_SUCCESS) { ++ DESTROYLOCK(&new->search_lock); ++ isc_mem_put(mctx, new, sizeof(*new)); ++ return (result); ++ } ++ ++ result = isc_refcount_init(&new->refs, 1); ++ if (result != ISC_R_SUCCESS) { ++ DESTROYLOCK(&new->maint_lock); ++ DESTROYLOCK(&new->search_lock); ++ isc_mem_put(mctx, new, sizeof(*new)); ++ return (result); ++ } ++ ++ result = dns_rbt_create(mctx, rpz_node_deleter, mctx, &new->rbt); ++ if (result != ISC_R_SUCCESS) { ++ isc_refcount_decrement(&new->refs, NULL); ++ isc_refcount_destroy(&new->refs); ++ DESTROYLOCK(&new->maint_lock); ++ DESTROYLOCK(&new->search_lock); ++ isc_mem_put(mctx, new, sizeof(*new)); ++ return (result); ++ } ++ ++ isc_mem_attach(mctx, &new->mctx); ++ ++ *rpzsp = new; ++ return (ISC_R_SUCCESS); ++} ++ ++/* ++ * Free the radix tree of a response policy database. ++ */ ++static void ++cidr_free(dns_rpz_zones_t *rpzs) { ++ dns_rpz_cidr_node_t *cur, *child, *parent; ++ ++ cur = rpzs->cidr; ++ while (cur != NULL) { ++ /* Depth first. */ ++ child = cur->child[0]; ++ if (child != NULL) { ++ cur = child; ++ continue; ++ } ++ child = cur->child[1]; ++ if (child != NULL) { ++ cur = child; ++ continue; ++ } ++ ++ /* Delete this leaf and go up. */ ++ parent = cur->parent; ++ if (parent == NULL) ++ rpzs->cidr = NULL; ++ else ++ parent->child[parent->child[1] == cur] = NULL; ++ isc_mem_put(rpzs->mctx, cur, sizeof(*cur)); ++ cur = parent; ++ } ++} ++ ++/* ++ * Discard a response policy zone blob ++ * before discarding the overall rpz structure. ++ */ ++static void ++rpz_detach(dns_rpz_zone_t **rpzp, dns_rpz_zones_t *rpzs) { ++ dns_rpz_zone_t *rpz; ++ unsigned int refs; ++ ++ rpz = *rpzp; ++ *rpzp = NULL; ++ isc_refcount_decrement(&rpz->refs, &refs); ++ if (refs != 0) + return; +- case DNS_RPZ_TYPE_QNAME: +- case DNS_RPZ_TYPE_BAD: +- return; ++ isc_refcount_destroy(&rpz->refs); ++ ++ if (dns_name_dynamic(&rpz->origin)) ++ dns_name_free(&rpz->origin, rpzs->mctx); ++ if (dns_name_dynamic(&rpz->client_ip)) ++ dns_name_free(&rpz->client_ip, rpzs->mctx); ++ if (dns_name_dynamic(&rpz->ip)) ++ dns_name_free(&rpz->ip, rpzs->mctx); ++ if (dns_name_dynamic(&rpz->nsdname)) ++ dns_name_free(&rpz->nsdname, rpzs->mctx); ++ if (dns_name_dynamic(&rpz->nsip)) ++ dns_name_free(&rpz->nsip, rpzs->mctx); ++ if (dns_name_dynamic(&rpz->passthru)) ++ dns_name_free(&rpz->passthru, rpzs->mctx); ++ if (dns_name_dynamic(&rpz->drop)) ++ dns_name_free(&rpz->drop, rpzs->mctx); ++ if (dns_name_dynamic(&rpz->cname)) ++ dns_name_free(&rpz->cname, rpzs->mctx); ++ ++ isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz)); ++} ++ ++void ++dns_rpz_attach_rpzs(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **rpzsp) { ++ REQUIRE(rpzsp != NULL && *rpzsp == NULL); ++ isc_refcount_increment(&rpzs->refs, NULL); ++ *rpzsp = rpzs; ++} ++ ++/* ++ * Forget a view's policy zones. ++ */ ++void ++dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) { ++ dns_rpz_zones_t *rpzs; ++ dns_rpz_zone_t *rpz; ++ dns_rpz_num_t rpz_num; ++ unsigned int refs; ++ ++ REQUIRE(rpzsp != NULL); ++ rpzs = *rpzsp; ++ REQUIRE(rpzs != NULL); ++ ++ *rpzsp = NULL; ++ isc_refcount_decrement(&rpzs->refs, &refs); ++ ++ /* ++ * Forget the last of view's rpz machinery after the last reference. ++ */ ++ if (refs == 0) { ++ for (rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES; ++rpz_num) { ++ rpz = rpzs->zones[rpz_num]; ++ rpzs->zones[rpz_num] = NULL; ++ if (rpz != NULL) ++ rpz_detach(&rpz, rpzs); ++ } ++ ++ cidr_free(rpzs); ++ dns_rbt_destroy(&rpzs->rbt); ++ DESTROYLOCK(&rpzs->maint_lock); ++ DESTROYLOCK(&rpzs->search_lock); ++ isc_refcount_destroy(&rpzs->refs); ++ isc_mem_putanddetach(&rpzs->mctx, rpzs, sizeof(*rpzs)); ++ } ++} ++ ++/* ++ * Create empty summary database to load one zone. ++ * The RBTDB write tree lock must be held. ++ */ ++isc_result_t ++dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp, ++ dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) ++{ ++ dns_rpz_zones_t *load_rpzs; ++ dns_rpz_zone_t *rpz; ++ dns_rpz_zbits_t tgt; ++ isc_result_t result; ++ ++ REQUIRE(rpz_num < rpzs->p.num_zones); ++ rpz = rpzs->zones[rpz_num]; ++ REQUIRE(rpz != NULL); ++ ++ /* ++ * When reloading a zone, there are usually records among the summary ++ * data for the zone. Some of those records might be deleted by the ++ * reloaded zone data. To deal with that case: ++ * reload the new zone data into a new blank summary database ++ * if the reload fails, discard the new summary database ++ * if the new zone data is acceptable, copy the records for the ++ * other zones into the new summary database and replace the ++ * old summary database with the new. ++ * ++ * At the first attempt to load a zone, there is no summary data ++ * for the zone and so no records that need to be deleted. ++ * This is also the most common case of policy zone loading. ++ * Most policy zone maintenance should be by incremental changes ++ * and so by the addition and deletion of individual records. ++ * Detect that case and load records the first time into the ++ * operational summary database ++ */ ++ tgt = DNS_RPZ_ZBIT(rpz_num); ++ LOCK(&rpzs->maint_lock); ++ LOCK(&rpzs->search_lock); ++ if ((rpzs->load_begun & tgt) == 0) { ++ rpzs->load_begun |= tgt; ++ dns_rpz_attach_rpzs(rpzs, load_rpzsp); ++ UNLOCK(&rpzs->search_lock); ++ UNLOCK(&rpzs->maint_lock); ++ ++ } else { ++ UNLOCK(&rpzs->search_lock); ++ UNLOCK(&rpzs->maint_lock); ++ ++ result = dns_rpz_new_zones(load_rpzsp, rpzs->mctx); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++ load_rpzs = *load_rpzsp; ++ load_rpzs->p.num_zones = rpzs->p.num_zones; ++ load_rpzs->zones[rpz_num] = rpz; ++ isc_refcount_increment(&rpz->refs, NULL); ++ } ++ ++ return (ISC_R_SUCCESS); ++} ++ ++static void ++fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_triggers_t *totals) { ++ dns_rpz_num_t rpz_num; ++ const dns_rpz_zone_t *rpz; ++ dns_rpz_zbits_t zbit; ++ ++# define SET_TRIG(type) \ ++ if (rpz == NULL || rpz->triggers.type == 0) { \ ++ rpzs->have.type &= ~zbit; \ ++ } else { \ ++ totals->type += rpz->triggers.type; \ ++ rpzs->have.type |= zbit; \ ++ } ++ ++ memset(totals, 0, sizeof(*totals)); ++ for (rpz_num = 0; rpz_num < rpzs->p.num_zones; ++rpz_num) { ++ rpz = rpzs->zones[rpz_num]; ++ zbit = DNS_RPZ_ZBIT(rpz_num); ++ SET_TRIG(client_ipv4); ++ SET_TRIG(client_ipv6); ++ SET_TRIG(qname); ++ SET_TRIG(ipv4); ++ SET_TRIG(ipv6); ++ SET_TRIG(nsdname); ++ SET_TRIG(nsipv4); ++ SET_TRIG(nsipv6); ++ } ++ ++ fix_qname_skip_recurse(rpzs); ++ ++# undef SET_TRIG ++} ++ ++/* ++ * Finish loading one zone. ++ * The RBTDB write tree lock must be held. ++ */ ++isc_result_t ++dns_rpz_ready(dns_rpz_zones_t *rpzs, ++ dns_rpz_zones_t **load_rpzsp, dns_rpz_num_t rpz_num) ++{ ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ dns_rpz_zones_t *load_rpzs; ++ const dns_rpz_cidr_node_t *cnode, *next_cnode, *parent_cnode; ++ dns_rpz_cidr_node_t *found; ++ dns_rpz_zbits_t new_bit; ++ dns_rpz_addr_zbits_t new_ip; ++ dns_rbt_t *rbt; ++ dns_rbtnodechain_t chain; ++ dns_rbtnode_t *nmnode; ++ dns_rpz_nm_data_t *nm_data, new_data; ++ dns_rpz_triggers_t old_totals, new_totals; ++ dns_fixedname_t labelf, originf, namef; ++ dns_name_t *label, *origin, *name; ++ isc_result_t result; ++ ++ INSIST(rpzs != NULL); ++ LOCK(&rpzs->maint_lock); ++ load_rpzs = *load_rpzsp; ++ INSIST(load_rpzs != NULL); ++ ++ if (load_rpzs == rpzs) { ++ /* ++ * This is a successful initial zone loading, ++ * perhaps for a new instance of a view. ++ */ ++ fix_triggers(rpzs, &new_totals); ++ UNLOCK(&rpzs->maint_lock); ++ dns_rpz_detach_rpzs(load_rpzsp); ++ ++ if (rpz_num == rpzs->p.num_zones-1) ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, ++ DNS_LOGMODULE_RBTDB, DNS_RPZ_INFO_LEVEL, ++ "loaded policy %d zones with" ++ " %d client-IPv4, %d client-IPv6," ++ " %d qname, %d nsdname," ++ " %d IPv4, %d IPv6," ++ " %d NSIPv4, %d NSIPv6 entries", ++ rpzs->p.num_zones, ++ new_totals.client_ipv4, ++ new_totals.client_ipv6, ++ new_totals.qname, ++ new_totals.nsdname, ++ new_totals.ipv4, ++ new_totals.ipv6, ++ new_totals.nsipv4, ++ new_totals.nsipv6); ++ return (ISC_R_SUCCESS); ++ } ++ ++ LOCK(&load_rpzs->maint_lock); ++ LOCK(&load_rpzs->search_lock); ++ ++ /* ++ * Copy the other policy zones to the new summary databases ++ * unless there is only one policy zone. ++ */ ++ if (rpzs->p.num_zones > 1) { ++ new_bit = ~DNS_RPZ_ZBIT(rpz_num); ++ ++ /* ++ * Copy to the radix tree. ++ */ ++ for (cnode = rpzs->cidr; cnode != NULL; cnode = next_cnode) { ++ new_ip.ip = cnode->set.ip & new_bit; ++ new_ip.client_ip = cnode->set.client_ip & new_bit; ++ new_ip.nsip = cnode->set.nsip & new_bit; ++ if (new_ip.client_ip != 0 || ++ new_ip.ip != 0 || ++ new_ip.nsip != 0) { ++ result = search(load_rpzs, ++ &cnode->ip, cnode->prefix, ++ &new_ip, ISC_TRUE, &found); ++ if (result == ISC_R_NOMEMORY) ++ goto unlock_and_detach; ++ INSIST(result == ISC_R_SUCCESS); ++ } ++ /* ++ * Do down and to the left as far as possible. ++ */ ++ next_cnode = cnode->child[0]; ++ if (next_cnode != NULL) ++ continue; ++ /* ++ * Go up until we find a branch to the right where ++ * we previously took the branch to the left. ++ */ ++ for (;;) { ++ parent_cnode = cnode->parent; ++ if (parent_cnode == NULL) ++ break; ++ if (parent_cnode->child[0] == cnode) { ++ next_cnode = parent_cnode->child[1]; ++ if (next_cnode != NULL) ++ break; ++ } ++ cnode = parent_cnode; ++ } ++ } ++ ++ /* ++ * Copy to the summary RBT. ++ */ ++ dns_fixedname_init(&namef); ++ name = dns_fixedname_name(&namef); ++ dns_fixedname_init(&labelf); ++ label = dns_fixedname_name(&labelf); ++ dns_fixedname_init(&originf); ++ origin = dns_fixedname_name(&originf); ++ dns_rbtnodechain_init(&chain, NULL); ++ result = dns_rbtnodechain_first(&chain, rpzs->rbt, NULL, NULL); ++ while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) { ++ result = dns_rbtnodechain_current(&chain, label, origin, ++ &nmnode); ++ INSIST(result == ISC_R_SUCCESS); ++ nm_data = nmnode->data; ++ if (nm_data != NULL) { ++ new_data.set.qname = (nm_data->set.qname & ++ new_bit); ++ new_data.set.ns = nm_data->set.ns & new_bit; ++ new_data.wild.qname = (nm_data->wild.qname & ++ new_bit); ++ new_data.wild.ns = nm_data->wild.ns & new_bit; ++ if (new_data.set.qname != 0 || ++ new_data.set.ns != 0 || ++ new_data.wild.qname != 0 || ++ new_data.wild.ns != 0) { ++ result = dns_name_concatenate(label, ++ origin, name, NULL); ++ INSIST(result == ISC_R_SUCCESS); ++ result = add_nm(load_rpzs, name, ++ &new_data); ++ if (result != ISC_R_SUCCESS) ++ goto unlock_and_detach; ++ } ++ } ++ result = dns_rbtnodechain_next(&chain, NULL, NULL); ++ } ++ if (result != ISC_R_NOMORE && result != ISC_R_NOTFOUND) { ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, ++ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, ++ "dns_rpz_ready(): unexpected %s", ++ isc_result_totext(result)); ++ goto unlock_and_detach; ++ } + } + ++ fix_triggers(rpzs, &old_totals); ++ fix_triggers(load_rpzs, &new_totals); ++ ++ dns_name_format(&load_rpzs->zones[rpz_num]->origin, ++ namebuf, sizeof(namebuf)); ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, ++ DNS_LOGMODULE_RBTDB, DNS_RPZ_INFO_LEVEL, ++ "reloading policy zone '%s' changed from" ++ " %d to %d qname, %d to %d nsdname," ++ " %d to %d IP, %d to %d NSIP entries", ++ namebuf, ++ old_totals.qname, new_totals.qname, ++ old_totals.nsdname, new_totals.nsdname, ++ old_totals.ipv4 + old_totals.ipv6, ++ new_totals.ipv4 + new_totals.ipv6, ++ old_totals.nsipv4 + old_totals.nsipv6, ++ new_totals.nsipv4 + new_totals.nsipv6); ++ + /* +- * Do not get excited about the deletion of interior rbt nodes. ++ * Exchange the summary databases. + */ +- result = name2ipkey(cidr, DNS_RPZ_DEBUG_QUIET, name, +- type, &tgt_ip, &tgt_prefix); ++ LOCK(&rpzs->search_lock); ++ ++ found = rpzs->cidr; ++ rpzs->cidr = load_rpzs->cidr; ++ load_rpzs->cidr = found; ++ ++ rbt = rpzs->rbt; ++ rpzs->rbt = load_rpzs->rbt; ++ load_rpzs->rbt = rbt; ++ ++ UNLOCK(&rpzs->search_lock); ++ result = ISC_R_SUCCESS; ++ ++ unlock_and_detach: ++ UNLOCK(&rpzs->maint_lock); ++ UNLOCK(&load_rpzs->search_lock); ++ UNLOCK(&load_rpzs->maint_lock); ++ dns_rpz_detach_rpzs(load_rpzsp); ++ return (result); ++} ++ ++/* ++ * Add an IP address to the radix tree or a name to the summary database. ++ */ ++isc_result_t ++dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *src_name) ++{ ++ dns_rpz_zone_t *rpz; ++ dns_rpz_type_t rpz_type; ++ isc_result_t result = ISC_R_FAILURE; ++ ++ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); ++ rpz = rpzs->zones[rpz_num]; ++ REQUIRE(rpz != NULL); ++ ++ rpz_type = type_from_name(rpz, src_name); ++ ++ LOCK(&rpzs->maint_lock); ++ LOCK(&rpzs->search_lock); ++ ++ switch (rpz_type) { ++ case DNS_RPZ_TYPE_QNAME: ++ case DNS_RPZ_TYPE_NSDNAME: ++ result = add_name(rpzs, rpz_num, rpz_type, src_name); ++ break; ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ case DNS_RPZ_TYPE_IP: ++ case DNS_RPZ_TYPE_NSIP: ++ result = add_cidr(rpzs, rpz_num, rpz_type, src_name); ++ break; ++ case DNS_RPZ_TYPE_BAD: ++ break; ++ } ++ ++ UNLOCK(&rpzs->search_lock); ++ UNLOCK(&rpzs->maint_lock); ++ return (result); ++} ++ ++/* ++ * Remove an IP address from the radix tree. ++ */ ++static void ++del_cidr(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, ++ dns_rpz_type_t rpz_type, dns_name_t *src_name) ++{ ++ isc_result_t result; ++ dns_rpz_cidr_key_t tgt_ip; ++ dns_rpz_prefix_t tgt_prefix; ++ dns_rpz_addr_zbits_t tgt_set; ++ dns_rpz_cidr_node_t *tgt, *parent, *child; ++ ++ /* ++ * Do not worry about invalid rpz IP address names. If we ++ * are here, then something relevant was added and so was ++ * valid. Invalid names here are usually internal RBTDB nodes. ++ */ ++ result = name2ipkey(DNS_RPZ_DEBUG_QUIET, rpzs, rpz_num, rpz_type, ++ src_name, &tgt_ip, &tgt_prefix, &tgt_set); + if (result != ISC_R_SUCCESS) + return; + +- result = search(cidr, &tgt_ip, tgt_prefix, type, ISC_FALSE, &tgt); ++ result = search(rpzs, &tgt_ip, tgt_prefix, &tgt_set, ISC_FALSE, &tgt); + if (result != ISC_R_SUCCESS) { +- badname(DNS_RPZ_ERROR_LEVEL, name, "; missing rpz node", ""); ++ INSIST(result == ISC_R_NOTFOUND || ++ result == DNS_R_PARTIALMATCH); ++ /* ++ * Do not worry about missing summary RBT nodes that probably ++ * correspond to RBTDB nodes that were implicit RBT nodes ++ * that were later added for (often empty) wildcards ++ * and then to the RBTDB deferred cleanup list. ++ */ + return; + } + + /* + * Mark the node and its parents to reflect the deleted IP address. ++ * Do not count bits that are already clear for internal RBTDB nodes. + */ +- flags = get_flags(&tgt_ip, tgt_prefix, type); +- data_flag = flags & (DNS_RPZ_CIDR_FG_IP_DATA | +- DNS_RPZ_CIDR_FG_NSIP_DATA); +- tgt->flags &= ~data_flag; +- for (parent = tgt; parent != NULL; parent = parent->parent) { +- if ((parent->flags & data_flag) != 0 || +- (parent->child[0] != NULL && +- (parent->child[0]->flags & flags) != 0) || +- (parent->child[1] != NULL && +- (parent->child[1]->flags & flags) != 0)) +- break; +- parent->flags &= ~flags; +- } ++ tgt_set.client_ip &= tgt->set.client_ip; ++ tgt_set.ip &= tgt->set.ip; ++ tgt_set.nsip &= tgt->set.nsip; ++ tgt->set.client_ip &= ~tgt_set.client_ip; ++ tgt->set.ip &= ~tgt_set.ip; ++ tgt->set.nsip &= ~tgt_set.nsip; ++ set_sum_pair(tgt); ++ ++ adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, ISC_FALSE); + + /* + * We might need to delete 2 nodes. +@@ -1054,13 +1757,14 @@ + */ + if ((child = tgt->child[0]) != NULL) { + if (tgt->child[1] != NULL) +- return; ++ break; + } else { + child = tgt->child[1]; + } +- if ((tgt->flags & (DNS_RPZ_CIDR_FG_IP_DATA | +- DNS_RPZ_CIDR_FG_NSIP_DATA)) != 0) +- return; ++ if (tgt->set.client_ip != 0 || ++ tgt->set.ip != 0 || ++ tgt->set.nsip != 0) ++ break; + + /* + * Replace the pointer to this node in the parent with +@@ -1068,7 +1772,7 @@ + */ + parent = tgt->parent; + if (parent == NULL) { +- cidr->root = child; ++ rpzs->cidr = child; + } else { + parent->child[parent->child[1] == tgt] = child; + } +@@ -1077,26 +1781,144 @@ + */ + if (child != NULL) + child->parent = parent; +- isc_mem_put(cidr->mctx, tgt, sizeof(*tgt)); ++ isc_mem_put(rpzs->mctx, tgt, sizeof(*tgt)); + + tgt = parent; + } while (tgt != NULL); + } + ++static void ++del_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, ++ dns_rpz_type_t rpz_type, dns_name_t *src_name) ++{ ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ dns_fixedname_t trig_namef; ++ dns_name_t *trig_name; ++ dns_rbtnode_t *nmnode; ++ dns_rpz_nm_data_t *nm_data, del_data; ++ isc_result_t result; ++ ++ dns_fixedname_init(&trig_namef); ++ trig_name = dns_fixedname_name(&trig_namef); ++ name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &del_data); ++ ++ /* ++ * No need for a summary database of names with only 1 policy zone. ++ */ ++ if (rpzs->p.num_zones <= 1) { ++ adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, ISC_FALSE); ++ return; ++ } ++ ++ nmnode = NULL; ++ result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, NULL, 0, ++ NULL, NULL); ++ if (result != ISC_R_SUCCESS) { ++ /* ++ * Do not worry about missing summary RBT nodes that probably ++ * correspond to RBTDB nodes that were implicit RBT nodes ++ * that were later added for (often empty) wildcards ++ * and then to the RBTDB deferred cleanup list. ++ */ ++ if (result == ISC_R_NOTFOUND) ++ return; ++ dns_name_format(src_name, namebuf, sizeof(namebuf)); ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, ++ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, ++ "rpz del_name(%s) node search failed: %s", ++ namebuf, isc_result_totext(result)); ++ return; ++ } ++ ++ nm_data = nmnode->data; ++ INSIST(nm_data != NULL); ++ ++ /* ++ * Do not count bits that next existed for RBT nodes that would we ++ * would not have found in a summary for a single RBTDB tree. ++ */ ++ del_data.set.qname &= nm_data->set.qname; ++ del_data.set.ns &= nm_data->set.ns; ++ del_data.wild.qname &= nm_data->wild.qname; ++ del_data.wild.ns &= nm_data->wild.ns; ++ ++ nm_data->set.qname &= ~del_data.set.qname; ++ nm_data->set.ns &= ~del_data.set.ns; ++ nm_data->wild.qname &= ~del_data.wild.qname; ++ nm_data->wild.ns &= ~del_data.wild.ns; ++ ++ if (nm_data->set.qname == 0 && nm_data->set.ns == 0 && ++ nm_data->wild.qname == 0 && nm_data->wild.ns == 0) { ++ result = dns_rbt_deletenode(rpzs->rbt, nmnode, ISC_FALSE); ++ if (result != ISC_R_SUCCESS) { ++ /* ++ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". ++ */ ++ dns_name_format(src_name, namebuf, sizeof(namebuf)); ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, ++ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, ++ "rpz del_name(%s) node delete failed: %s", ++ namebuf, isc_result_totext(result)); ++ } ++ } ++ ++ adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, ISC_FALSE); ++} ++ + /* +- * Caller must hold tree lock. +- * Return ISC_R_NOTFOUND +- * or ISC_R_SUCCESS and the found entry's canonical and search names +- * and its prefix length ++ * Remove an IP address from the radix tree or a name from the summary database. + */ +-isc_result_t +-dns_rpz_cidr_find(dns_rpz_cidr_t *cidr, const isc_netaddr_t *netaddr, +- dns_rpz_type_t type, dns_name_t *canon_name, +- dns_name_t *search_name, dns_rpz_cidr_bits_t *prefix) ++void ++dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, ++ dns_name_t *src_name) { ++ dns_rpz_zone_t *rpz; ++ dns_rpz_type_t rpz_type; ++ ++ REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); ++ rpz = rpzs->zones[rpz_num]; ++ REQUIRE(rpz != NULL); ++ ++ rpz_type = type_from_name(rpz, src_name); ++ ++ LOCK(&rpzs->maint_lock); ++ LOCK(&rpzs->search_lock); ++ ++ switch (rpz_type) { ++ case DNS_RPZ_TYPE_QNAME: ++ case DNS_RPZ_TYPE_NSDNAME: ++ del_name(rpzs, rpz_num, rpz_type, src_name); ++ break; ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ case DNS_RPZ_TYPE_IP: ++ case DNS_RPZ_TYPE_NSIP: ++ del_cidr(rpzs, rpz_num, rpz_type, src_name); ++ break; ++ case DNS_RPZ_TYPE_BAD: ++ break; ++ } ++ ++ UNLOCK(&rpzs->search_lock); ++ UNLOCK(&rpzs->maint_lock); ++} ++ ++/* ++ * Search the summary radix tree to get a relative owner name in a ++ * policy zone relevant to a triggering IP address. ++ * rpz_type and zbits limit the search for IP address netaddr ++ * return the policy zone's number or DNS_RPZ_INVALID_NUM ++ * ip_name is the relative owner name found and ++ * *prefixp is its prefix length. ++ */ ++dns_rpz_num_t ++dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, ++ dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr, ++ dns_name_t *ip_name, dns_rpz_prefix_t *prefixp) + { + dns_rpz_cidr_key_t tgt_ip; +- isc_result_t result; ++ dns_rpz_addr_zbits_t tgt_set; + dns_rpz_cidr_node_t *found; ++ isc_result_t result; ++ dns_rpz_num_t rpz_num; + int i; + + /* +@@ -1107,29 +1929,163 @@ + tgt_ip.w[1] = 0; + tgt_ip.w[2] = ADDR_V4MAPPED; + tgt_ip.w[3] = ntohl(netaddr->type.in.s_addr); ++ switch (rpz_type) { ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ zbits &= rpzs->have.client_ipv4; ++ break; ++ case DNS_RPZ_TYPE_IP: ++ zbits &= rpzs->have.ipv4; ++ break; ++ case DNS_RPZ_TYPE_NSIP: ++ zbits &= rpzs->have.nsipv4; ++ break; ++ default: ++ INSIST(0); ++ break; ++ } + } else if (netaddr->family == AF_INET6) { + dns_rpz_cidr_key_t src_ip6; + + /* + * Given the int aligned struct in_addr member of netaddr->type + * one could cast netaddr->type.in6 to dns_rpz_cidr_key_t *, +- * but there are objections. ++ * but some people object. + */ + memcpy(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w)); + for (i = 0; i < 4; i++) { + tgt_ip.w[i] = ntohl(src_ip6.w[i]); + } ++ switch (rpz_type) { ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ zbits &= rpzs->have.client_ipv6; ++ break; ++ case DNS_RPZ_TYPE_IP: ++ zbits &= rpzs->have.ipv6; ++ break; ++ case DNS_RPZ_TYPE_NSIP: ++ zbits &= rpzs->have.nsipv6; ++ break; ++ default: ++ INSIST(0); ++ break; ++ } + } else { +- return (ISC_R_NOTFOUND); ++ return (DNS_RPZ_INVALID_NUM); + } + +- result = search(cidr, &tgt_ip, 128, type, ISC_FALSE, &found); +- if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) +- return (result); ++ if (zbits == 0) ++ return (DNS_RPZ_INVALID_NUM); ++ make_addr_set(&tgt_set, zbits, rpz_type); ++ ++ LOCK(&rpzs->search_lock); ++ result = search(rpzs, &tgt_ip, 128, &tgt_set, ISC_FALSE, &found); ++ if (result == ISC_R_NOTFOUND) { ++ /* ++ * There are no eligible zones for this IP address. ++ */ ++ UNLOCK(&rpzs->search_lock); ++ return (DNS_RPZ_INVALID_NUM); ++ } ++ ++ /* ++ * Construct the trigger name for the longest matching trigger ++ * in the first eligible zone with a match. ++ */ ++ *prefixp = found->prefix; ++ switch (rpz_type) { ++ case DNS_RPZ_TYPE_CLIENT_IP: ++ rpz_num = zbit_to_num(found->set.client_ip & tgt_set.client_ip); ++ break; ++ case DNS_RPZ_TYPE_IP: ++ rpz_num = zbit_to_num(found->set.ip & tgt_set.ip); ++ break; ++ case DNS_RPZ_TYPE_NSIP: ++ rpz_num = zbit_to_num(found->set.nsip & tgt_set.nsip); ++ break; ++ default: ++ INSIST(0); ++ break; ++ } ++ result = ip2name(&found->ip, found->prefix, dns_rootname, ip_name); ++ UNLOCK(&rpzs->search_lock); ++ if (result != ISC_R_SUCCESS) { ++ /* ++ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". ++ */ ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, ++ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, ++ "rpz ip2name() failed: %s", ++ isc_result_totext(result)); ++ return (DNS_RPZ_INVALID_NUM); ++ } ++ return (rpz_num); ++} + +- *prefix = found->bits; +- return (ip2name(cidr, &found->ip, found->bits, type, +- canon_name, search_name)); ++/* ++ * Search the summary radix tree for policy zones with triggers matching ++ * a name. ++ */ ++dns_rpz_zbits_t ++dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, ++ dns_rpz_zbits_t zbits, dns_name_t *trig_name) ++{ ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ dns_rbtnode_t *nmnode; ++ const dns_rpz_nm_data_t *nm_data; ++ dns_rpz_zbits_t found_zbits; ++ isc_result_t result; ++ ++ if (zbits == 0) ++ return (0); ++ ++ found_zbits = 0; ++ ++ LOCK(&rpzs->search_lock); ++ ++ nmnode = NULL; ++ result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, NULL, ++ DNS_RBTFIND_EMPTYDATA, NULL, NULL); ++ switch (result) { ++ case ISC_R_SUCCESS: ++ nm_data = nmnode->data; ++ if (nm_data != NULL) { ++ if (rpz_type == DNS_RPZ_TYPE_QNAME) ++ found_zbits = nm_data->set.qname; ++ else ++ found_zbits = nm_data->set.ns; ++ } ++ nmnode = nmnode->parent; ++ /* fall thru */ ++ case DNS_R_PARTIALMATCH: ++ while (nmnode != NULL) { ++ nm_data = nmnode->data; ++ if (nm_data != NULL) { ++ if (rpz_type == DNS_RPZ_TYPE_QNAME) ++ found_zbits |= nm_data->wild.qname; ++ else ++ found_zbits |= nm_data->wild.ns; ++ } ++ nmnode = nmnode->parent; ++ } ++ break; ++ ++ case ISC_R_NOTFOUND: ++ break; ++ ++ default: ++ /* ++ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". ++ */ ++ dns_name_format(trig_name, namebuf, sizeof(namebuf)); ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, ++ DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, ++ "dns_rpz_find_name(%s) failed: %s", ++ namebuf, isc_result_totext(result)); ++ break; ++ } ++ ++ UNLOCK(&rpzs->search_lock); ++ return (zbits & found_zbits); + } + + /* +@@ -1144,10 +2100,10 @@ + isc_result_t result; + + result = dns_rdataset_first(rdataset); +- RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, NULL); +- RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ INSIST(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + + /* +@@ -1174,7 +2130,13 @@ + } + + /* +- * CNAME PASSTHRU.origin means "do not rewrite. ++ * CNAME rpz-drop. means "do respond." ++ */ ++ if (dns_name_equal(&cname.cname, &rpz->drop)) ++ return (DNS_RPZ_POLICY_DROP); ++ ++ /* ++ * CNAME rpz-passthru. means "do not rewrite." + */ + if (dns_name_equal(&cname.cname, &rpz->passthru)) + return (DNS_RPZ_POLICY_PASSTHRU); +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,1324 @@ ++/* ++ * Copyright (C) 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 ++#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 || ++ !ISC_LINK_LINKED(e_old, hlink)); ++ e_old = ISC_LIST_PREV(e_old, lru), ++i) ++ { ++ 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) { ++ key->s.qtype = qtype; ++ key->s.qclass = qclass & 0xff; ++ } else if (rtype == DNS_RRL_RTYPE_REFERRAL || ++ rtype == DNS_RRL_RTYPE_NODATA) { ++ /* ++ * Because there is no qtype in the empty answer sections of ++ * referral and NODATA responses, count them as the same. ++ */ ++ key->s.qclass = qclass & 0xff; ++ } ++ ++ if (qname != NULL && qname->labels != 0) { ++ /* ++ * 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 dns_rrl_rate_t * ++get_rate(dns_rrl_t *rrl, dns_rrl_rtype_t rtype) { ++ switch (rtype) { ++ case DNS_RRL_RTYPE_QUERY: ++ return (&rrl->responses_per_second); ++ case DNS_RRL_RTYPE_REFERRAL: ++ return (&rrl->referrals_per_second); ++ case DNS_RRL_RTYPE_NODATA: ++ return (&rrl->nodata_per_second); ++ case DNS_RRL_RTYPE_NXDOMAIN: ++ return (&rrl->nxdomains_per_second); ++ case DNS_RRL_RTYPE_ERROR: ++ return (&rrl->errors_per_second); ++ case DNS_RRL_RTYPE_ALL: ++ return (&rrl->all_per_second); ++ default: ++ INSIST(0); ++ } ++ return (NULL); ++} ++ ++static int ++response_balance(dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) { ++ dns_rrl_rate_t *ratep; ++ int balance, rate; ++ ++ if (e->key.s.rtype == DNS_RRL_RTYPE_TCP) { ++ rate = 1; ++ } else { ++ ratep = get_rate(rrl, e->key.s.rtype); ++ rate = ratep->scaled; ++ } ++ ++ balance = e->responses + age * rate; ++ if (balance > rate) ++ balance = rate; ++ return (balance); ++} ++ ++/* ++ * 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, slip, new_slip, age, log_secs, min; ++ dns_rrl_rate_t *ratep; ++ dns_rrl_entry_t const *credit_e; ++ ++ /* ++ * Pick the rate counter. ++ * Optionally adjust the rate by the estimated query/second rate. ++ */ ++ ratep = get_rate(rrl, e->key.s.rtype); ++ rate = ratep->r; ++ 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 = (int) (rate * scale); ++ if (new_rate < 1) ++ new_rate = 1; ++ if (ratep->scaled != new_rate) { ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, ++ DNS_RRL_LOG_DEBUG1, ++ "%d qps scaled %s by %.2f" ++ " from %d to %d", ++ (int)qps, ratep->str, scale, ++ rate, new_rate); ++ rate = new_rate; ++ ratep->scaled = 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.r; ++ if (slip > 2 && scale < 1.0) { ++ new_slip = (int) (slip * scale); ++ if (new_slip < 2) ++ new_slip = 2; ++ if (rrl->slip.scaled != new_slip) { ++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, ++ DNS_LOGMODULE_REQUEST, ++ DNS_RRL_LOG_DEBUG1, ++ "%d qps scaled slip" ++ " by %.2f from %d to %d", ++ (int)qps, scale, ++ slip, new_slip); ++ slip = new_slip; ++ rrl->slip.scaled = slip; ++ } ++ } ++ if (slip != 0 && e->key.s.rtype != DNS_RRL_RTYPE_ALL) { ++ if (e->slip_cnt++ == 0) { ++ if ((int) e->slip_cnt >= slip) ++ 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 ((int) 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: ++ break; ++ case DNS_RRL_RTYPE_REFERRAL: ++ ADD_LOG_CSTR(&lb, "referral "); ++ break; ++ case DNS_RRL_RTYPE_NODATA: ++ ADD_LOG_CSTR(&lb, "NODATA "); ++ break; ++ case DNS_RRL_RTYPE_NXDOMAIN: ++ ADD_LOG_CSTR(&lb, "NXDOMAIN "); ++ break; ++ case DNS_RRL_RTYPE_ERROR: ++ if (resp_result == ISC_R_SUCCESS) { ++ ADD_LOG_CSTR(&lb, "error "); ++ } else { ++ rstr = isc_result_totext(resp_result); ++ add_log_str(&lb, rstr, strlen(rstr)); ++ ADD_LOG_CSTR(&lb, " error "); ++ } ++ break; ++ case DNS_RRL_RTYPE_ALL: ++ ADD_LOG_CSTR(&lb, "all "); ++ break; ++ default: ++ INSIST(0); ++ } ++ ++ if (plural) ++ ADD_LOG_CSTR(&lb, "responses to "); ++ else ++ ADD_LOG_CSTR(&lb, "response 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_REFERRAL || ++ e->key.s.rtype == DNS_RRL_RTYPE_NODATA || ++ e->key.s.rtype == DNS_RRL_RTYPE_NXDOMAIN) { ++ qbuf = get_qname(rrl, e); ++ if (save_qname && qbuf == NULL && ++ 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)); ++ ISC_LINK_INIT(qbuf, link); ++ 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 "); ++ (void)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, " "); ++ (void)dns_rdataclass_totext(e->key.s.qclass, &lb); ++ if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY) { ++ ADD_LOG_CSTR(&lb, " "); ++ (void)dns_rdatatype_totext(e->key.s.qtype, &lb); ++ } ++ } ++ snprintf(strbuf, sizeof(strbuf), " (%08x)", ++ e->key.s.qname_hash); ++ 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 ++ */ ++ switch (resp_result) { ++ case ISC_R_SUCCESS: ++ rtype = DNS_RRL_RTYPE_QUERY; ++ break; ++ case DNS_R_DELEGATION: ++ rtype = DNS_RRL_RTYPE_REFERRAL; ++ break; ++ case DNS_R_NXRRSET: ++ rtype = DNS_RRL_RTYPE_NODATA; ++ break; ++ case DNS_R_NXDOMAIN: ++ rtype = DNS_RRL_RTYPE_NXDOMAIN; ++ break; ++ default: ++ rtype = DNS_RRL_RTYPE_ERROR; ++ break; ++ } ++ e = get_entry(rrl, client_addr, qclass, qtype, qname, rtype, ++ now, ISC_TRUE, log_buf, log_buf_len); ++ if (e == NULL) { ++ 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.r != 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 rate limit " : "rate limit ", ++ 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 need 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_putanddetach(&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)); ++ isc_mem_attach(view->mctx, &rrl->mctx); ++ result = isc_mutex_init(&rrl->lock); ++ if (result != ISC_R_SUCCESS) { ++ isc_mem_putanddetach(&rrl->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 +@@ -49,6 +49,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -184,6 +185,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; +@@ -195,9 +197,7 @@ + view->maxbits = 0; + view->v4_aaaa = dns_v4_aaaa_ok; + view->v4_aaaa_acl = NULL; +- ISC_LIST_INIT(view->rpz_zones); +- view->rpz_recursive_only = ISC_TRUE; +- view->rpz_break_dnssec = ISC_FALSE; ++ view->rpzs = NULL; + dns_fixedname_init(&view->dlv_fixed); + view->managed_keys = NULL; + view->redirect = NULL; +@@ -334,10 +334,13 @@ + dns_acache_putdb(view->acache, view->cachedb); + dns_acache_detach(&view->acache); + } +- dns_rpz_view_destroy(view); ++ if (view->rpzs != NULL) ++ dns_rpz_detach_rpzs(&view->rpzs); ++ dns_rrl_view_destroy(view); + #else + INSIST(view->acache == NULL); +- INSIST(ISC_LIST_EMPTY(view->rpz_zones)); ++ INSIST(view->rpzs == NULL); ++ 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 +@@ -130,8 +130,8 @@ + dns_db_overmem + dns_db_printnode + dns_db_register +-dns_db_rpz_enabled +-dns_db_rpz_findips ++dns_db_rpz_attach ++dns_db_rpz_ready + dns_db_subtractrdataset + dns_db_unregister + dns_dbiterator_current +@@ -639,17 +639,22 @@ + dns_result_torcode + dns_result_totext + dns_rootns_create ++dns_rpz_add ++dns_rpz_attach_rpzs ++dns_rpz_beginload + dns_rpz_cidr_addip +-dns_rpz_cidr_deleteip + dns_rpz_cidr_find +-dns_rpz_cidr_free + dns_rpz_decode_cname +-dns_rpz_enabled_get +-dns_rpz_new_cidr ++dns_rpz_delete ++dns_rpz_delete_node ++dns_rpz_detach_rpzs ++dns_rpz_find_ip ++dns_rpz_find_name ++dns_rpz_new_zones + dns_rpz_policy2str ++dns_rpz_ready + dns_rpz_str2policy + dns_rpz_type2str +-dns_rpz_view_destroy + dns_rriterator_current + dns_rriterator_destroy + dns_rriterator_first +@@ -657,6 +662,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 +@@ -806,7 +814,7 @@ + dns_zone_forcereload + dns_zone_forwardupdate + dns_zone_fulldumptostream +-dns_zone_get_rpz ++dns_zone_get_rpz_num + dns_zone_getadded + dns_zone_getchecknames + dns_zone_getclass +@@ -834,6 +842,7 @@ + dns_zone_getqueryonacl + dns_zone_getraw + dns_zone_getrequeststats ++dns_zone_getrpz_num + dns_zone_getserial + dns_zone_getserial2 + dns_zone_getserialupdatemethod +@@ -871,6 +880,7 @@ + dns_zone_refresh + dns_zone_rekey + dns_zone_replacedb ++dns_zone_rpz_attach + dns_zone_rpz_enable + dns_zone_setacache + dns_zone_setadded +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/dns/zone.c-orig lib/dns/zone.c +--- lib/dns/zone.c-orig 2004-01-01 00:00:00.000000000 +0000 ++++ lib/dns/zone.c 2004-01-01 00:00:00.000000000 +0000 +@@ -346,9 +346,10 @@ + isc_boolean_t added; + + /*% +- * whether this is a response policy zone ++ * response policy data to be relayed to the database + */ +- isc_boolean_t is_rpz; ++ dns_rpz_zones_t *rpzs; ++ dns_rpz_num_t rpz_num; + + /*% + * Serial number update method. +@@ -917,7 +918,8 @@ + zone->nodes = 100; + zone->privatetype = (dns_rdatatype_t)0xffffU; + zone->added = ISC_FALSE; +- zone->is_rpz = ISC_FALSE; ++ zone->rpzs = NULL; ++ zone->rpz_num = DNS_RPZ_INVALID_NUM; + ISC_LIST_INIT(zone->forwards); + zone->raw = NULL; + zone->secure = NULL; +@@ -1021,6 +1023,13 @@ + zone_detachdb(zone); + if (zone->acache != NULL) + dns_acache_detach(&zone->acache); ++#ifdef BIND9 ++ if (zone->rpzs != NULL) { ++ REQUIRE(zone->rpz_num < zone->rpzs->p.num_zones); ++ dns_rpz_detach_rpzs(&zone->rpzs); ++ zone->rpz_num = DNS_RPZ_INVALID_NUM; ++ } ++#endif + zone_freedbargs(zone); + RUNTIME_CHECK(dns_zone_setmasterswithkeys(zone, NULL, NULL, 0) + == ISC_R_SUCCESS); +@@ -1513,7 +1522,9 @@ + * Set the response policy index and information for a zone. + */ + isc_result_t +-dns_zone_rpz_enable(dns_zone_t *zone) { ++dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs, ++ dns_rpz_num_t rpz_num) ++{ + /* + * Only RBTDB zones can be used for response policy zones, + * because only they have the code to load the create the summary data. +@@ -1524,14 +1535,26 @@ + strcmp(zone->db_argv[0], "rbt64") != 0) + return (ISC_R_NOTIMPLEMENTED); + +- zone->is_rpz = ISC_TRUE; ++ /* ++ * This must happen only once or be redundant. ++ */ ++ LOCK_ZONE(zone); ++ if (zone->rpzs != NULL) { ++ REQUIRE(zone->rpzs == rpzs && zone->rpz_num == rpz_num); ++ } else { ++ REQUIRE(zone->rpz_num == DNS_RPZ_INVALID_NUM); ++ dns_rpz_attach_rpzs(rpzs, &zone->rpzs); ++ zone->rpz_num = rpz_num; ++ } ++ rpzs->defined |= DNS_RPZ_ZBIT(rpz_num); ++ UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); + } + +-isc_boolean_t +-dns_zone_get_rpz(dns_zone_t *zone) { +- return (zone->is_rpz); ++dns_rpz_num_t ++dns_zone_get_rpz_num(dns_zone_t *zone) { ++ return (zone->rpz_num); + } + + static isc_result_t +@@ -1988,10 +2011,9 @@ + unsigned int options; + + #ifdef BIND9 +- if (zone->is_rpz) { +- result = dns_db_rpz_enabled(db, NULL); +- if (result != ISC_R_SUCCESS) +- return (result); ++ if (zone->rpz_num != DNS_RPZ_INVALID_NUM) { ++ REQUIRE(zone->rpzs != NULL); ++ dns_db_rpz_attach(db, zone->rpzs, zone->rpz_num); + } + #endif + +@@ -4120,6 +4142,11 @@ + if (result != ISC_R_SUCCESS) + goto cleanup; + } else { ++#ifdef BIND9 ++ result = dns_db_rpz_ready(db); ++ if (result != ISC_R_SUCCESS) ++ goto cleanup; ++#endif + zone_attachdb(zone, db); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); + DNS_ZONE_SETFLAG(zone, +@@ -13047,6 +13074,12 @@ + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(LOCKED_ZONE(zone)); + ++#ifdef BIND9 ++ result = dns_db_rpz_ready(db); ++ if (result != ISC_R_SUCCESS) ++ return (result); ++#endif ++ + result = zone_get_from_db(zone, db, &nscount, &soacount, + NULL, NULL, NULL, NULL, NULL, NULL); + if (result == ISC_R_SUCCESS) { +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 +@@ -1054,11 +1054,12 @@ + + /*% + * response-policy { +- * zone [ policy (given|disabled|passthru| ++ * zone [ policy (given|disabled|passthru|drop| + * nxdomain|nodata|cname ) ] + * [ recursive-only yes|no ] [ max-policy-ttl number ] ; + * } [ recursive-only yes|no ] [ max-policy-ttl number ] ; +- * [ break-dnssec yes|no ] [ min-ns-dots number ] ; ++ * [ break-dnssec yes|no ] [ min-ns-dots number ] ++ * [ qname-wait-recurse yes|no ] + */ + + static void +@@ -1083,7 +1084,7 @@ + + /* + * Parse +- * given|disabled|passthru|nxdomain|nodata|cname ++ * given|disabled|passthru|drop|nxdomain|nodata|cname + */ + static isc_result_t + cfg_parse_rpz_policy(cfg_parser_t *pctx, const cfg_type_t *type, +@@ -1214,8 +1215,11 @@ + doc_keyvalue, &cfg_rep_string, + &zone_kw + }; ++/* ++ * "no-op" is an obsolete equivalent of "passthru". ++ */ + static const char *rpz_policies[] = { +- "given", "disabled", "passthru", "no-op", "nxdomain", "nodata", ++ "given", "disabled", "passthru", "no-op", "drop", "nxdomain", "nodata", + "cname", NULL + }; + static cfg_type_t cfg_type_rpz_policy_name = { +@@ -1261,6 +1265,7 @@ + { "break-dnssec", &cfg_type_boolean, 0 }, + { "max-policy-ttl", &cfg_type_uint32, 0 }, + { "min-ns-dots", &cfg_type_uint32, 0 }, ++ { "qname-wait-recurse", &cfg_type_boolean, 0 }, + { NULL, NULL, 0 } + }; + static cfg_type_t cfg_type_rpz = { +@@ -1270,6 +1275,40 @@ + }; + + ++/* ++ * rate-limit ++ */ ++static cfg_clausedef_t rrl_clauses[] = { ++ { "responses-per-second", &cfg_type_uint32, 0 }, ++ { "referrals-per-second", &cfg_type_uint32, 0 }, ++ { "nodata-per-second", &cfg_type_uint32, 0 }, ++ { "nxdomains-per-second", &cfg_type_uint32, 0 }, ++ { "errors-per-second", &cfg_type_uint32, 0 }, ++ { "all-per-second", &cfg_type_uint32, 0 }, ++ { "slip", &cfg_type_uint32, 0 }, ++ { "window", &cfg_type_uint32, 0 }, ++ { "log-only", &cfg_type_boolean, 0 }, ++ { "qps-scale", &cfg_type_uint32, 0 }, ++ { "ipv4-prefix-length", &cfg_type_uint32, 0 }, ++ { "ipv6-prefix-length", &cfg_type_uint32, 0 }, ++ { "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 + */ +@@ -1423,6 +1462,7 @@ + CFG_CLAUSEFLAG_NOTCONFIGURED }, + #endif + { "response-policy", &cfg_type_rpz, 0 }, ++ { "rate-limit", &cfg_type_rrl, 0 }, + { NULL, NULL, 0 } + }; + +diff -r -u named.conf-orig named.conf +--- named.conf-orig 2004-01-01 00:00:00.000000000 +0000 ++++ named.conf 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,153 @@ ++/* ++ * 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; ++ ++ // check that all of the options are parsed without limiting anything ++ rate-limit { ++ responses-per-second 200; ++ referrals-per-second 220; ++ nodata-per-second 230; ++ nxdomains-per-second 240; ++ errors-per-second 250; ++ all-per-second 700; ++ ipv4-prefix-length 24; ++ ipv6-prefix-length 64; ++ qps-scale 10; ++ window 1; ++ max-table-size 1000; ++ }; ++ ++}; ++ ++zone "." { type hint; file "hints"; }; ++ ++zone "tld3."{ type master; file "tld3.db"; }; ++/* ++ * 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 50; ++ slip 3; ++ exempt-clients { 10.53.0.7; }; ++ ++ // small enough to force a table expansion ++ min-table-size 75; ++ }; ++ ++ additional-from-cache no; ++}; ++ ++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"; }; ++/* ++ * 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 root.db-orig root.db +--- root.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ 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 setup.sh-orig setup.sh +--- setup.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ 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 tests.sh-orig tests.sh +--- tests.sh-orig 2004-01-01 00:00:00.000000000 +0000 ++++ tests.sh 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,258 @@ ++# 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 ++ ++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 ++} ++ ++ ++# turn off ${HOME}/.digrc ++HOME=/dev/null; export HOME ++ ++# $1=result name $2=domain name $3=dig options ++digcmd () { ++ OFILE=$1; shift ++ DIG_DOM=$1; shift ++ ARGS="+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 '/^;; AUTHORITY/,/^$/d' \ ++ -e '/^;; ADDITIONAL/,/^$/d' \ ++ -e 's/^[^;].* \([^ ]\{1,\}\)$/\1/p' \ ++ -e 's/;; flags.* tc .*/TC/p' \ ++ -e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p' \ ++ -e 's/;; .* status: SERVFAIL.*/SERVFAIL/p' \ ++ -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 ++QNUM=1 ++burst () { ++ BURST_LIMIT=$1; shift ++ BURST_DOM_BASE="$1"; shift ++ while test "$BURST_LIMIT" -ge 1; do ++ CNT=`expr "00$QNUM" : '.*\(...\)'` ++ eval BURST_DOM="$BURST_DOM_BASE" ++ FILE="dig.out-$BURST_DOM-$CNT" ++ digcmd $FILE $BURST_DOM $* & ++ QNUM=`expr $QNUM + 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` ++ # count simple truncated and truncated NXDOMAIN as TC ++ TC=`ls dig.out-$1-*=TC dig.out-$1-*=NXDOMAINTC 2>/dev/null | wc -l` ++ DROP=`ls dig.out-$1-*=drop 2>/dev/null | wc -l` ++ # count NXDOMAIN and truncated NXDOMAIN as NXDOMAIN ++ NXDOMAIN=`ls dig.out-$1-*=NXDOMAIN dig.out-$1-*=NXDOMAINTC 2>/dev/null \ ++ | wc -l` ++ SERVFAIL=`ls dig.out-$1-*=SERVFAIL 2>/dev/null | wc -l` ++ 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 ++} ++ ++ ++ckstats () { ++ LABEL="$1"; shift ++ TYPE="$1"; shift ++ EXPECTED="$1"; shift ++ C=`sed -n -e "s/[ ]*\([0-9]*\).responses $TYPE for rate limits.*/\1/p" \ ++ ns2/named.stats | tail -1` ++ C=`expr 0$C + 0` ++ if test "$C" -ne $EXPECTED; then ++ setret "I:wrong $LABEL $TYPE statistics of $C instead of $EXPECTED" ++ fi ++} ++ ++ ++######### ++sec_start ++ ++# Tests of referrals to "." must be done before the hints are loaded ++# or with "additional-from-cache no" ++burst 5 a1.tld3 +norec ++# basic rate limiting ++burst 3 a1.tld2 ++# 1 second delay allows an additional response. ++sleep 1 ++burst 10 a1.tld2 ++# Request 30 different qnames to try a wildcard. ++burst 30 'x$CNT.a2.tld2' ++# These should be counted and limited but are not. See RT33138. ++burst 10 'y.x$CNT.a2.tld2' ++ ++# IP TC drop NXDOMAIN SERVFAIL ++# referrals to "." ++ck_result a1.tld3 '' 2 1 2 0 0 ++# check 13 results including 1 second delay that allows an additional response ++ck_result a1.tld2 192.0.2.1 3 4 6 0 0 ++ ++# Check the wild card answers. ++# The parent name of the 30 requests is counted. ++ck_result 'x*.a2.tld2' 192.0.2.2 2 10 18 0 0 ++ ++# These should be limited but are not. See RT33138. ++ck_result 'y.x*.a2.tld2' 192.0.2.2 10 0 0 0 0 ++ ++######### ++sec_start ++ ++burst 10 'x.a3.tld3' ++burst 10 'y$CNT.a3.tld3' ++burst 10 'z$CNT.a4.tld2' ++ ++# 10 identical recursive responses are limited ++ck_result 'x.a3.tld3' 192.0.3.3 2 3 5 0 0 ++ ++# 10 different recursive responses are not limited ++ck_result 'y*.a3.tld3' 192.0.3.3 10 0 0 0 0 ++ ++# 10 different NXDOMAIN responses are limited based on the parent name. ++# We count 13 responses because we count truncated NXDOMAIN responses ++# as both truncated and NXDOMAIN. ++ck_result 'z*.a4.tld2' x 0 3 5 5 0 ++ ++$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats ++ckstats first dropped 36 ++ckstats first truncated 21 ++ ++ ++######### ++sec_start ++ ++burst 10 a5.tld2 +tcp ++burst 10 a6.tld2 -b $ns7 ++burst 10 a7.tld4 ++burst 2 a8.tld2 AAAA ++burst 2 a8.tld2 TXT ++burst 2 a8.tld2 SPF ++ ++# IP TC drop NXDOMAIN SERVFAIL ++# TCP responses are not rate limited ++ck_result a5.tld2 192.0.2.5 10 0 0 0 0 ++ ++# whitelisted client is not rate limited ++ck_result a6.tld2 192.0.2.6 10 0 0 0 0 ++ ++# Errors such as SERVFAIL are rate limited. ++ck_result a7.tld4 x 0 0 8 0 2 ++ ++# NODATA responses are counted as the same regardless of qtype. ++ck_result a8.tld2 '' 2 2 2 0 0 ++ ++$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats ++ckstats second dropped 46 ++ckstats second truncated 23 ++ ++ ++######### ++sec_start ++ ++# IP TC drop NXDOMAIN SERVFAIL ++# all-per-second ++# The qnames are all unique but the client IP address is constant. ++QNUM=101 ++burst 60 'all$CNT.a9.tld2' ++ ++ck_result 'a*.a9.tld2' 192.0.2.8 50 0 10 0 0 ++ ++$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats ++ckstats final dropped 56 ++ckstats final truncated 23 ++ ++ ++echo "I:exit status: $ret" ++# exit $ret ++[ $ret -ne 0 ] && echo "I:test failure overridden" ++exit 0 +diff -r -u tld2.db-orig tld2.db +--- tld2.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ tld2.db 2004-01-01 00:00:00.000000000 +0000 +@@ -0,0 +1,47 @@ ++; 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 ++ ++; basic rate limiting ++a1 A 192.0.2.1 ++ ++; wildcards ++*.a2 A 192.0.2.2 ++ ++; a3 is in tld3 ++ ++; a4 does not exist to give NXDOMAIN ++ ++; a5 for TCP requests ++a5 A 192.0.2.5 ++ ++; a6 for whitelisted clients ++a6 A 192.0.2.6 ++ ++; a7 for SERVFAIL ++ ++; a8 for NODATA ++a8 A 192.0.2.8 ++ ++; a9 for all-per-second limit ++$GENERATE 101-180 all$.a9 A 192.0.2.8 +diff -r -u tld3.db-orig tld3.db +--- tld3.db-orig 2004-01-01 00:00:00.000000000 +0000 ++++ 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.0.3.3 +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 +@@ -7,6 +7,6 @@ + DESCRIPTION="(Extended Support Version)" + MAJORVER=9 + MINORVER=9 +-PATCHVER=3 ++PATCHVER=3-rpz2+rl.156.01 + RELEASETYPE=-P + RELEASEVER=1