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