[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#698658: marked as done (bind9 with fix for 698641)



Your message dated Fri, 15 Feb 2013 19:29:44 +0000
with message-id <1360956584.20472.33.camel@jacala.jungle.funky-badger.org>
and subject line Re: Bug#698658: bind9 with fix for 698641
has caused the Debian Bug report #698658,
regarding bind9 with fix for 698641
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
698658: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=698658
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org                                                                                                                       
Severity: normal                                                                                                                                  
User: release.debian.org@packages.debian.org                                                                                                      

I need to upload 1:9.8.4.dfsg.P1-3 with the rest of the fix for 697681.
In the meantime, a more invasive patch (attached) has been recommended
by DSA for inclusion in wheezey (see bug 698641).  Because of the size
of the diff, I would like to have some discussion with the release team
before I upload it to sid on its way to wheezy.

thoughts?
lamont
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
@@ -993,6 +993,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 |
@@ -1133,6 +1138,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
@@ -222,6 +222,13 @@
 	notify no;\n\
 	allow-new-zones no;\n\
 \n\
+	# Prevent use of this zone in DNS amplified reflection DoS attacks\n\
+	rate-limit {\n\
+		responses-per-second 3;\n\
+		slip 0;\n\
+		min-table-size 10;\n\
+	};\n\
+\n\
 	zone \"version.bind\" chaos {\n\
 		type master;\n\
 		database \"_builtin version\";\n\
diff -r -u bin/named/include/named/query.h-orig bin/named/include/named/query.h
--- bin/named/include/named/query.h-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/named/include/named/query.h	2004-01-01 00:00:00.000000000 +0000
@@ -85,6 +85,7 @@
 #define NS_QUERYATTR_CACHEACLOK		0x2000
 #define NS_QUERYATTR_DNS64		0x4000
 #define NS_QUERYATTR_DNS64EXCLUDE	0x8000
+#define NS_QUERYATTR_RRL_CHECKED	0x10000
 
 
 isc_result_t
diff -r -u bin/named/include/named/server.h-orig bin/named/include/named/server.h
--- bin/named/include/named/server.h-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/named/include/named/server.h	2004-01-01 00:00:00.000000000 +0000
@@ -165,7 +165,12 @@
 	dns_nsstatscounter_updatefail = 34,
 	dns_nsstatscounter_updatebadprereq = 35,
 
-	dns_nsstatscounter_max = 36
+	dns_nsstatscounter_rpz_rewrites = 36,
+
+	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
@@ -831,74 +831,93 @@
 }
 
 static void
-rpz_log_rewrite(ns_client_t *client, const char *disabled,
+rpz_log_rewrite(ns_client_t *client, isc_boolean_t disabled,
 		dns_rpz_policy_t policy, dns_rpz_type_t type,
-		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.
+	 * Count both enabled and disabled rewrites for each zone.
+	 */
+	if (!disabled && policy != DNS_RPZ_POLICY_PASSTHRU) {
+		isc_stats_increment(ns_g_server->nsstats,
+				    dns_nsstatscounter_rpz_rewrites);
+	}
+	if (p_zone != NULL) {
+		zonestats = dns_zone_getrequeststats(p_zone);
+		if (zonestats != NULL)
+			isc_stats_increment(zonestats,
+					    dns_nsstatscounter_rpz_rewrites);
+	}
 
 	if (!isc_log_wouldlog(ns_g_lctx, DNS_RPZ_INFO_LEVEL))
 		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 ? "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;
 
-	dns_name_format(client->query.qname, namebuf1, sizeof(namebuf1));
-	dns_name_format(name, namebuf2, sizeof(namebuf2));
+	/*
+	 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+	 */
+	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);
 }
@@ -3796,7 +3815,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);
@@ -3804,16 +3823,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);
 }
@@ -3842,13 +3861,84 @@
 	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_QNAME:
+		if (rpzs->num.qname == 0)
+			return (0);
+		break;
+	case DNS_RPZ_TYPE_NSDNAME:
+		if (rpzs->num.nsdname == 0)
+			return (0);
+		break;
+	case DNS_RPZ_TYPE_IP:
+		if (ip_type == dns_rdatatype_a) {
+			if (rpzs->num.ipv4 == 0)
+				return (0);
+		} else {
+			if (rpzs->num.ipv6 == 0)
+				return (0);
+		}
+		break;
+	case DNS_RPZ_TYPE_NSIP:
+		if (ip_type == dns_rdatatype_a) {
+			if (rpzs->num.nsipv4 == 0)
+				return (0);
+		} else {
+			if (rpzs->num.nsipv6 == 0)
+				return (0);
+		}
+		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 (rpzs->p.cnt == 0)
+			return (0);
+		zbits = DNS_RPZ_ZMASK(rpzs->p.cnt) >> 1;
+	} else if (st->m.type >= rpz_type) {
+		zbits = DNS_RPZ_ZMASK(st->m.rpz->num);
+	} else if (st->m.rpz->num == 0) {
+		return (0);
+	} 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;
@@ -3864,6 +3954,7 @@
 		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;
@@ -3873,16 +3964,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);
@@ -3897,9 +3987,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);
@@ -3949,199 +4038,118 @@
 }
 
 /*
- * 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)
+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_rpz_st_t *st;
-	dns_dbversion_t *version;
-	dns_zone_t *zone;
-	dns_db_t *db;
-	dns_rpz_zone_t *rpz;
+	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:
-		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_QNAME:
+		suffix = &rpz->origin;
 		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_IP:
+		suffix = &rpz->ip;
 		break;
-	case DNS_R_DELEGATION:
-	case DNS_R_DUPLICATE:
-	case DNS_R_DROP:
+	case DNS_RPZ_TYPE_NSDNAME:
+		suffix = &rpz->nsdname;
 		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_NSIP:
+		suffix = &rpz->nsip;
 		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);
-		}
-		break;
+		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;
 
-	result = rpz_ready(client, zonep, dbp, nodep, rdatasetp);
-	if (result != ISC_R_SUCCESS) {
-		*policyp = DNS_RPZ_POLICY_ERROR;
-		return (result);
-	}
+	REQUIRE(nodep != NULL);
 
 	/*
-	 * 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_find(*dbp, qnamef, *versionp, dns_rdatatype_any, 0,
+	dns_fixedname_init(&foundf);
+	found = dns_fixedname_name(&foundf);
+	result = dns_db_find(*dbp, p_name, *versionp, dns_rdatatype_any, 0,
 			     client->now, nodep, found, *rdatasetp, NULL);
+	/*
+	 * Choose the best rdataset if we found something.
+	 */
 	if (result == ISC_R_SUCCESS) {
 		dns_rdatasetiter_t *rdsiter;
 
@@ -4149,10 +4157,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);
@@ -4168,9 +4174,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);
 			}
 			/*
@@ -4185,7 +4190,7 @@
 			    qtype == dns_rdatatype_sig)
 				result = DNS_R_NXRRSET;
 			else
-				result = dns_db_find(*dbp, qnamef, *versionp,
+				result = dns_db_find(*dbp, p_name, *versionp,
 						     qtype, 0, client->now,
 						     nodep, found, *rdatasetp,
 						     NULL);
@@ -4194,198 +4199,526 @@
 	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:
+		*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(). So fall through to treat them as NODATA.
+		 * 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_NXRRSET:
-		policy = DNS_RPZ_POLICY_NODATA;
-		break;
 	case DNS_R_NXDOMAIN:
 	case DNS_R_EMPTYNAME:
+		return (DNS_R_NXDOMAIN);
+	default:
+		rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type,
+			     "", result);
+		return (DNS_R_SERVFAIL);
+	}
+}
+
+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 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;
+}
+
+/*
+ * Check this address in every eligible policy zone.
+ */
+static isc_result_t
+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_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;
+	isc_result_t result;
+
+	dns_fixedname_init(&ip_namef);
+	ip_name = dns_fixedname_name(&ip_namef);
+
+	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);
+
 		/*
-		 * If we don't get a qname hit,
-		 * see if it is worth looking for other types.
+		 * 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.
 		 */
-		dns_db_rpz_enabled(*dbp, client->query.rpz_st);
-		dns_db_detach(dbp);
-		dns_zone_detach(zonep);
-		policy = DNS_RPZ_POLICY_MISS;
+		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 ||
+			     st->m.prefix > prefix))
+				break;
+		}
+
+		/*
+		 * Get the policy for a prefix at least as long
+		 * as the prefix of the entry we had before.
+		 */
+		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:
+			/*
+			 * 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.
+			 */
+			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,
+			 * 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.
+			 */
+			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;
+			}
+
+			/*
+			 * 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;
-	default:
-		dns_db_detach(dbp);
-		dns_zone_detach(zonep);
-		rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, qnamef,
-			     "", result);
-		policy = DNS_RPZ_POLICY_ERROR;
-		result = DNS_R_SERVFAIL;
+	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);
 	}
 
-	*policyp = policy;
-	return (result);
+	/*
+	 * 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);
 }
 
 /*
- * Build and look for a QNAME or NSDNAME owner name in a response policy zone.
+ * Look for IP addresses in A and AAAA rdatasets
+ * that trigger all eligible IP or NSIP policy rules.
  */
 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_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_rdataset_t **rdatasetp)
+{
 	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_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;
-	unsigned int labels;
 	isc_result_t result;
 
+	zbits = rpz_get_zbits(client, qtype, rpz_type);
+	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;
-	zone = NULL;
-	db = NULL;
-	node = NULL;
 
-	for (rpz = ISC_LIST_HEAD(client->view->rpz_zones);
-	     rpz != NULL;
-	     rpz = ISC_LIST_NEXT(rpz, link)) {
-		if (!RECURSIONOK(client) && rpz->recursive_only)
+	/*
+	 * 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.cnt);
 			continue;
+		}
 
 		/*
-		 * Do not check policy zones that cannot replace a policy
-		 * already known to match.
+		 * Do not check policy zones that cannot replace a previously
+		 * found 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)
-				continue;
-		}
-		/*
-		 * Construct the policy's owner name.
-		 */
-		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);
-			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);
-			}
-			dns_name_split(prefix, labels - 1, NULL, prefix);
 		}
 
 		/*
-		 * See if the policy record exists and get its policy.
+		 * Get the next policy zone's record for this trigger name.
 		 */
-		result = rpz_find(client, qtype, rpz_qname, qname, rpz,
-				  rpz_type, &zone, &db, &version, &node,
-				  rdatasetp, &policy);
+		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:
-		case DNS_R_EMPTYNAME:
-			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,
 			 * 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
 			/*
-			 * Merely log DNS_RPZ_POLICY_DISABLED hits.
-			 */
-			if (rpz->policy == DNS_RPZ_POLICY_DISABLED) {
-				rpz_log_rewrite(client, "disabled ",
-						policy, rpz_type, rpz_qname);
-				continue;
+			 * This is not worth the CPU cycles, because
+			 * any data leaked by requesting 24.4.3.2.10.rpz-ip
+			 * (or whatever) is always available by publishing
+			 * "foo A 10.2.3.4" and then requesting foo.
+			 *
+			 * We have the less frequent case of a triggered
+			 * policy.  Check that we have not trigger on one
+			 * of the pretend RPZ TLDs.
+			 * This test would make it impossible to rewrite
+			 * names in TLDs that start with "rpz-" should
+			 * ICANN ever allow such TLDs.
+			 */
+			unsigned int labels;
+			labels = dns_name_countlabels(trig_name);
+			if (labels >= 2) {
+				dns_label_t 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,
+						sizeof(DNS_RPZ_PREFIX)-1) == 0)
+					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);
+#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);
 			}
-			st->m.node = node;
-			node = NULL;
-			st->m.db = db;
-			db = NULL;
-			st->m.version = version;
-			st->m.zone = zone;
-			zone = NULL;
+			/*
+			 * 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);
+			break;
 		}
 	}
 
-	rpz_clean(&zone, &db, &node, rdatasetp);
+	rpz_clean(&p_zone, &p_db, &p_node, rdatasetp);
 	return (ISC_R_SUCCESS);
 }
 
@@ -4398,7 +4731,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))
@@ -4414,6 +4747,7 @@
 rpz_rewrite(ns_client_t *client, dns_rdatatype_t qtype, isc_result_t qresult,
 	    isc_boolean_t resuming)
 {
+	dns_rpz_zones_t *rpzs;
 	dns_rpz_st_t *st;
 	dns_rdataset_t *rdataset;
 	dns_fixedname_t nsnamef;
@@ -4432,10 +4766,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;
@@ -4466,14 +4800,13 @@
 	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);
@@ -4482,11 +4815,11 @@
 	rdataset = NULL;
 	if ((st->state & DNS_RPZ_DONE_QNAME) == 0) {
 		/*
-		 * Check rules for the query name if this it the first time
+		 * 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.
 		 */
-		result = rpz_rewrite_name(client, qtype, client->query.qname,
+		result = rpz_rewrite_name(client, client->query.qname, qtype,
 					  DNS_RPZ_TYPE_QNAME, &rdataset);
 		if (result != ISC_R_SUCCESS)
 			goto cleanup;
@@ -4502,11 +4835,12 @@
 	 * 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);
+	rpzs = client->view->rpzs;
+	if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 &&	ck_ip &&
+	    (rpzs->num.ipv4 != 0 || rpzs->num.ipv6 != 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;
@@ -4516,15 +4850,15 @@
 	/*
 	 * Stop looking for rules if there are none of the other kinds.
 	 */
-	if ((st->state & (DNS_RPZ_HAVE_NSIPv4 | DNS_RPZ_HAVE_NSIPv6 |
-			  DNS_RPZ_HAVE_NSDNAME)) == 0) {
+	if (rpzs->num.nsdname == 0 &&
+	    rpzs->num.nsipv4 == 0 && rpzs->num.nsipv6 == 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 > 1) {
+	while (st->r.label > rpzs->p.min_ns_labels) {
 		/*
 		 * Get NS rrset for each domain in the current qname.
 		 */
@@ -4538,8 +4872,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)
@@ -4594,7 +4928,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;
@@ -4612,9 +4946,9 @@
 			 * 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,
+			    rpzs->num.nsdname != 0) {
+				result = rpz_rewrite_name(client,
+							&ns.name, qtype,
 							DNS_RPZ_TYPE_NSDNAME,
 							&rdataset);
 				if (result != ISC_R_SUCCESS) {
@@ -4626,9 +4960,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;
@@ -4655,8 +4989,8 @@
 	    st->m.policy == DNS_RPZ_POLICY_ERROR) {
 		if (st->m.policy == DNS_RPZ_POLICY_PASSTHRU &&
 		    result != DNS_R_DELEGATION)
-			rpz_log_rewrite(client, "", st->m.policy, st->m.type,
-					st->qname);
+			rpz_log_rewrite(client, ISC_FALSE, st->m.policy,
+					st->m.type, st->m.zone, st->p_name);
 		rpz_match_clear(st);
 	}
 	if (st->m.policy == DNS_RPZ_POLICY_ERROR) {
@@ -4671,7 +5005,7 @@
 }
 
 /*
- * See if response policy zone rewriting is allowed a lack of interest
+ * See if response policy zone rewriting is allowed by a lack of interest
  * by the client in DNSSEC or a lack of signatures.
  */
 static isc_boolean_t
@@ -4683,7 +5017,7 @@
 	dns_rdataset_t trdataset;
 	dns_rdatatype_t type;
 
-	if (client->view->rpz_break_dnssec)
+	if (client->view->rpzs->p.break_dnssec)
 		return (ISC_TRUE);
 	/*
 	 * sigrdataset == NULL if and only !WANTDNSSEC(client)
@@ -4766,7 +5100,8 @@
 				 fname, dns_trust_authanswer, st->m.ttl);
 	if (result != ISC_R_SUCCESS)
 		return (result);
-	rpz_log_rewrite(client, "", st->m.policy, st->m.type, st->qname);
+	rpz_log_rewrite(client, ISC_FALSE, st->m.policy,
+			st->m.type, st->m.zone, st->p_name);
 	ns_client_qnamereplace(client, fname);
 	/*
 	 * Turn off DNSSEC because the results of a
@@ -5569,8 +5904,106 @@
  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.
+	 */
+	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 (client->view->rpzs != NULL && client->view->rpzs->p.cnt != 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 ||
@@ -5704,8 +6137,8 @@
 						DNS_MESSAGEFLAG_AD);
 			query_putrdataset(client, &sigrdataset);
 			is_zone = ISC_TRUE;
-			rpz_log_rewrite(client, "", rpz_st->m.policy,
-					rpz_st->m.type, rpz_st->qname);
+			rpz_log_rewrite(client, ISC_FALSE, rpz_st->m.policy,
+					rpz_st->m.type, zone, rpz_st->p_name);
 		}
 	}
 
@@ -6983,12 +7416,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
@@ -293,7 +293,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);
@@ -1430,47 +1431,75 @@
 }
 
 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_name(dns_view_t *view, const cfg_obj_t *obj, dns_name_t *name,
+		   const char *str, const char *msg)
 {
-	const cfg_obj_t *rpz_obj, *policy_obj, *obj;
+	isc_result_t result;
+
+	result = dns_name_fromstring(name, str, DNS_NAME_DOWNCASE, view->mctx);
+	if (result != ISC_R_SUCCESS)
+		cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
+			    "invalid %s '%s'", msg, str);
+	return (result);
+}
+
+static isc_result_t
+configure_rpz_name2(dns_view_t *view, const cfg_obj_t *obj, dns_name_t *name,
+		    const char *str, const dns_name_t *origin)
+{
+	isc_result_t result;
+
+	result = dns_name_fromstring2(name, str, origin, DNS_NAME_DOWNCASE,
+				      view->mctx);
+	if (result != ISC_R_SUCCESS)
+		cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
+			    "invalid zone '%s'", str);
+	return (result);
+}
+
+static isc_result_t
+configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element,
+		   isc_boolean_t recursive_only_def, dns_ttl_t ttl_def)
+{
+	const cfg_obj_t *rpz_obj, *obj;
 	const char *str;
-	dns_rpz_zone_t *old, *new;
-	dns_zone_t *zone = NULL;
+	dns_rpz_zone_t *new;
 	isc_result_t result;
+	dns_rpz_num_t rpz_num;
 
-	new = isc_mem_get(view->mctx, sizeof(*new));
+	if (view->rpzs->p.cnt >= DNS_RPZ_MAX_ZONES)
+		return (ISC_R_NOMEMORY);
+
+	new = isc_mem_get(view->rpzs->mctx, sizeof(*new));
 	if (new == NULL) {
-		result = ISC_R_NOMEMORY;
-		goto cleanup;
+		cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
+			    "no memory for response policy zones");
+		return (ISC_R_NOMEMORY);
 	}
 
 	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->ip, NULL);
 	dns_name_init(&new->nsdname, NULL);
-	dns_name_init(&new->cname, NULL);
+	dns_name_init(&new->nsip, NULL);
 	dns_name_init(&new->passthru, NULL);
-	ISC_LIST_INITANDAPPEND(view->rpz_zones, new, link);
+	dns_name_init(&new->cname, NULL);
+	new->num = view->rpzs->p.cnt++;
+	view->rpzs->zones[new->num] = new;
 
 	rpz_obj = cfg_listelt_value(element);
-	policy_obj = cfg_tuple_get(rpz_obj, "policy");
-	if (cfg_obj_isvoid(policy_obj)) {
-		new->policy = DNS_RPZ_POLICY_GIVEN;
-	} else {
-		str = cfg_obj_asstring(cfg_tuple_get(policy_obj,
-						     "policy name"));
-		new->policy = dns_rpz_str2policy(str);
-		INSIST(new->policy != DNS_RPZ_POLICY_ERROR);
-	}
 
 	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)) {
@@ -1480,77 +1509,338 @@
 	}
 
 	str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "zone name"));
-	result = dns_name_fromstring(&new->origin, str, DNS_NAME_DOWNCASE,
-				     view->mctx);
-	if (result != ISC_R_SUCCESS) {
+	result = configure_rpz_name(view, rpz_obj, &new->origin, str, "zone");
+	if (result != ISC_R_SUCCESS)
+		return (result);
+	if (dns_name_equal(&new->origin, dns_rootname)) {
 		cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
-			    "invalid zone '%s'", str);
-		goto cleanup;
+			    "invalid zone name '%s'", str);
+		return (DNS_R_EMPTYLABEL);
+	}
+	for (rpz_num = 0; rpz_num < view->rpzs->p.cnt-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);
+		}
 	}
 
-	result = dns_name_fromstring2(&new->nsdname, DNS_RPZ_NSDNAME_ZONE,
-				      &new->origin, DNS_NAME_DOWNCASE,
-				      view->mctx);
-	if (result != ISC_R_SUCCESS) {
-		cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
-			    "invalid zone '%s'", str);
-		goto cleanup;
+	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");
+	if (result != ISC_R_SUCCESS)
+		return (result);
+
+	obj = cfg_tuple_get(rpz_obj, "policy");
+	if (cfg_obj_isvoid(obj)) {
+		new->policy = DNS_RPZ_POLICY_GIVEN;
+	} else {
+		str = cfg_obj_asstring(cfg_tuple_get(obj, "policy name"));
+		new->policy = dns_rpz_str2policy(str);
+		INSIST(new->policy != DNS_RPZ_POLICY_ERROR);
+		if (new->policy == DNS_RPZ_POLICY_CNAME) {
+			str = cfg_obj_asstring(cfg_tuple_get(obj, "cname"));
+			result = configure_rpz_name(view, rpz_obj, &new->cname,
+						    str, "cname");
+			if (result != ISC_R_SUCCESS)
+				return (result);
+		}
 	}
 
-	result = dns_name_fromstring(&new->passthru, DNS_RPZ_PASSTHRU_ZONE,
-				     DNS_NAME_DOWNCASE, view->mctx);
-	if (result != ISC_R_SUCCESS) {
-		cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
-			    "invalid zone '%s'", str);
-		goto cleanup;
+	return (ISC_R_SUCCESS);
+}
+
+#define CHECK_RRL(obj, cond, pat, val1, val2)				\
+	do {								\
+		if (!(cond)) {						\
+			cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR,	\
+				    pat, val1, val2);			\
+			result = ISC_R_RANGE;				\
+			goto cleanup;					\
+		    }							\
+	} while (0)
+
+static isc_result_t
+configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) {
+	const cfg_obj_t *obj;
+	dns_rrl_t *rrl;
+	isc_result_t result;
+ 	int min_entries, i, j;
+
+	/*
+	 * Most DNS servers have few clients, but intentinally open
+	 * recursive and authoritative servers often have many.
+	 * So start with a small number of entries unless told otherwise
+	 * to reduce cold-start costs.
+	 */
+	min_entries = 500;
+	obj = NULL;
+	result = cfg_map_get(map, "min-table-size", &obj);
+	if (result == ISC_R_SUCCESS) {
+		min_entries = cfg_obj_asuint32(obj);
+		if (min_entries < 1)
+			min_entries = 1;
 	}
+	result = dns_rrl_init(&rrl, view, min_entries);
+	if (result != ISC_R_SUCCESS)
+		return (result);
 
-	result = dns_view_findzone(view, &new->origin, &zone);
-	if (result != ISC_R_SUCCESS) {
-		cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
-			    "unknown zone '%s'", str);
-		goto cleanup;
+	i = ISC_MAX(20000, min_entries);
+	obj = NULL;
+	result = cfg_map_get(map, "max-table-size", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i >= min_entries,
+			  "max-table-size %d < min-table-size %d",
+			  i, min_entries);
 	}
-	if (dns_zone_gettype(zone) != dns_zone_master &&
-	    dns_zone_gettype(zone) != dns_zone_slave) {
-		cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
-			     "zone '%s' is neither master nor slave", str);
-		dns_zone_detach(&zone);
-		result = DNS_R_NOTMASTER;
-		goto cleanup;
+	rrl->max_entries = i;
+
+	i = 0;
+	obj = NULL;
+	result = cfg_map_get(map, "responses-per-second", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
+			  "responses-per-second %d > %d",
+			  i, DNS_RRL_MAX_RATE);
 	}
-	dns_zone_detach(&zone);
+	rrl->responses_per_second = i;
+	rrl->scaled_responses_per_second = rrl->responses_per_second;
 
-	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)) {
-			cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
-				    "duplicate '%s'", str);
-			result = DNS_R_DUPLICATE;
-			goto cleanup;
-		}
+	/*
+	 * The default error rate is the response rate,
+	 * and so off by default.
+	 */
+	i = rrl->responses_per_second;
+	obj = NULL;
+	result = cfg_map_get(map, "errors-per-second", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
+			  "errors-per-second %d > %d",
+			  i, DNS_RRL_MAX_RATE);
 	}
+	rrl->errors_per_second = i;
+	rrl->scaled_errors_per_second = rrl->errors_per_second;
+	/*
+	 * The default NXDOMAIN rate is the response rate,
+	 * and so off by default.
+	 */
+	i = rrl->responses_per_second;
+	obj = NULL;
+	result = cfg_map_get(map, "nxdomains-per-second", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
+			  "nxdomains-per-second %d > %d",
+			  i, DNS_RRL_MAX_RATE);
+	}
+	rrl->nxdomains_per_second = i;
+	rrl->scaled_nxdomains_per_second = rrl->nxdomains_per_second;
 
-	if (new->policy == DNS_RPZ_POLICY_CNAME) {
-		str = cfg_obj_asstring(cfg_tuple_get(policy_obj, "cname"));
-		result = dns_name_fromstring(&new->cname, str,
-					     DNS_NAME_DOWNCASE, view->mctx);
-		if (result != ISC_R_SUCCESS) {
-			cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
-				    "invalid cname '%s'", str);
-			goto cleanup;
+	/*
+	 * The all-per-second rate is off by default.
+	 */
+	i = 0;
+	obj = NULL;
+	result = cfg_map_get(map, "all-per-second", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
+			  "all-per-second %d > %d",
+			  i, DNS_RRL_MAX_RATE);
+		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 %d times responses-per-second,"
+			  "errors_per_second, and nxdomains_per_second",
+			  i, 4);
+	}
+	rrl->all_per_second = i;
+	rrl->scaled_all_per_second = rrl->all_per_second;
+
+	i = 2;
+	obj = NULL;
+	result = cfg_map_get(map, "slip", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_SLIP,
+			  "slip %d > %d", i, DNS_RRL_MAX_SLIP);
+	}
+	rrl->slip = i;
+	rrl->scaled_slip = rrl->slip;
+
+	i = 15;
+	obj = NULL;
+	result = cfg_map_get(map, "window", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i >= 1 && i <= DNS_RRL_MAX_WINDOW,
+			  "window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW);
+	}
+	rrl->window = i;
+
+	i = 0;
+	obj = NULL;
+	result = cfg_map_get(map, "qps-scale", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i >= 1, "invalid 'qps-scale %d'%s", i, "");
+	}
+	rrl->qps_scale = i;
+	rrl->qps = 1.0;
+
+	i = 24;
+	obj = NULL;
+	result = cfg_map_get(map, "IPv4-prefix-length", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i >= 8 && i <= 32,
+			  "invalid 'IPv4-prefix-length %d'%s", i, "");
+	}
+	rrl->ipv4_prefixlen = i;
+	if (i == 32)
+		rrl->ipv4_mask = 0xffffffff;
+	else
+		rrl->ipv4_mask = htonl(0xffffffff << (32-i));
+
+	i = 56;
+	obj = NULL;
+	result = cfg_map_get(map, "IPv6-prefix-length", &obj);
+	if (result == ISC_R_SUCCESS) {
+		i = cfg_obj_asuint32(obj);
+		CHECK_RRL(obj, i >= 16 && i <= DNS_RRL_MAX_PREFIX,
+			  "IPv6-prefix-length %d < 16 or > %d",
+			  i, DNS_RRL_MAX_PREFIX);
+	}
+	rrl->ipv6_prefixlen = i;
+	for (j = 0; j < 4; ++j) {
+		if (i <= 0) {
+			rrl->ipv6_mask[j] = 0;
+		} else if (i < 32) {
+			rrl->ipv6_mask[j] = htonl(0xffffffff << (32-i));
+		} else {
+			rrl->ipv6_mask[j] = 0xffffffff;
 		}
+		i -= 32;
+	}
+
+	obj = NULL;
+	result = cfg_map_get(map, "exempt-clients", &obj);
+	if (result == ISC_R_SUCCESS) {
+		result = cfg_acl_fromconfig(obj, config, ns_g_lctx,
+					    ns_g_aclconfctx, ns_g_mctx,
+					    0, &rrl->exempt);
+		CHECK_RRL(obj, result == ISC_R_SUCCESS,
+			  "invalid %s%s", "address match list", "");
 	}
 
+	obj = NULL;
+	result = cfg_map_get(map, "log-only", &obj);
+	if (result == ISC_R_SUCCESS && cfg_obj_asboolean(obj))
+		rrl->log_only = ISC_TRUE;
+	else
+		rrl->log_only = ISC_FALSE;
+
 	return (ISC_R_SUCCESS);
 
  cleanup:
-	dns_rpz_view_destroy(view);
+	dns_rrl_view_destroy(view);
 	return (result);
 }
 
+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 *rpzs;
+	dns_view_t *pview;
+	isc_result_t result;
+
+	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);
+	rpzs = 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))
+		rpzs->p.break_dnssec = ISC_TRUE;
+	else
+		rpzs->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))
+		rpzs->p.min_ns_labels = cfg_obj_asuint32(sub_obj) + 1;
+	else
+		rpzs->p.min_ns_labels = 2;
+
+	do {
+		result = configure_rpz_zone(view, zone_element,
+					    recursive_only_def, ttl_def);
+		if (result != ISC_R_SUCCESS)
+			return (result);
+		zone_element = cfg_list_next(zone_element);
+	} while (zone_element != NULL);
+
+	/*
+	 * 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.
+	 */
+	result = dns_viewlist_find(&ns_g_server->viewlist,
+				   view->name, view->rdclass, &pview);
+	if (result == ISC_R_SUCCESS) {
+		if (memcmp(&pview->rpzs->p, &rpzs->p, sizeof(rpzs->p)) == 0) {
+			*old_rpz_okp = ISC_TRUE;
+			dns_rpz_detach_rpzs(&view->rpzs);
+			dns_rpz_attach_rpzs(pview->rpzs, &view->rpzs);
+		}
+		dns_view_detach(&pview);
+	}
+
+	return (ISC_R_SUCCESS);
+}
+
 /*
  * Configure 'view' according to 'vconfig', taking defaults from 'config'
  * where values are missing in 'vconfig'.
@@ -1617,6 +1907,7 @@
 	dns_acl_t *clients = NULL, *mapped = NULL, *excluded = NULL;
 	unsigned int query_timeout;
 	struct cfg_context *nzctx;
+	isc_boolean_t old_rpz_ok;
 
 	REQUIRE(DNS_VIEW_VALID(view));
 
@@ -1715,6 +2006,17 @@
 	}
 
 	/*
+	 * Make the list of response policy zone names for a view that
+	 * is used for real lookups and so cares about hints.
+	 */
+	old_rpz_ok = ISC_FALSE;
+	obj = NULL;
+	if (view->rdclass == dns_rdataclass_in && need_hints &&
+	    ns_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS) {
+		CHECK(configure_rpz(view, obj, &old_rpz_ok));
+	}
+
+	/*
 	 * Configure the zones.
 	 */
 	zonelist = NULL;
@@ -1732,7 +2034,30 @@
 	{
 		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));
+	}
+
+	/*
+	 * 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;
+
+		for (n = 0; n < view->rpzs->p.cnt; ++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;
+			}
+		}
 	}
 
 	/*
@@ -1757,7 +2082,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));
 		}
 	}
 
@@ -2886,47 +3211,12 @@
 		}
 	}
 
-	/*
-	 * Make the list of response policy zone names for views that
-	 * are used for real lookups and so care about hints.
-	 */
 	obj = NULL;
-	if (view->rdclass == dns_rdataclass_in && need_hints &&
-	    ns_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS) {
-		const cfg_obj_t *recursive_only_obj;
-		const cfg_obj_t *break_dnssec_obj, *ttl_obj;
-		isc_boolean_t recursive_only_def;
-		dns_ttl_t ttl_def;
-
-		recursive_only_obj = cfg_tuple_get(obj, "recursive-only");
-		if (!cfg_obj_isvoid(recursive_only_obj) &&
-		    !cfg_obj_asboolean(recursive_only_obj))
-			recursive_only_def = ISC_FALSE;
-		else
-			recursive_only_def = ISC_TRUE;
-
-		break_dnssec_obj = cfg_tuple_get(obj, "break-dnssec");
-		if (!cfg_obj_isvoid(break_dnssec_obj) &&
-		    cfg_obj_asboolean(break_dnssec_obj))
-			view->rpz_break_dnssec = ISC_TRUE;
-		else
-			view->rpz_break_dnssec = ISC_FALSE;
-
-		ttl_obj = cfg_tuple_get(obj, "max-policy-ttl");
-		if (cfg_obj_isuint32(ttl_obj))
-			ttl_def = cfg_obj_asuint32(ttl_obj);
-		else
-			ttl_def = DNS_RPZ_MAX_TTL_DEFAULT;
-
-		for (element = cfg_list_first(cfg_tuple_get(obj, "zone list"));
-		     element != NULL;
-		     element = cfg_list_next(element)) {
-			result = configure_rpz(view, element,
-					       recursive_only_def, ttl_def);
-			if (result != ISC_R_SUCCESS)
-				goto cleanup;
-			dns_rpz_set_need(ISC_TRUE);
-		}
+	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;
@@ -3261,7 +3551,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 */
@@ -3280,6 +3571,7 @@
 	const char *zname;
 	dns_rdataclass_t zclass;
 	const char *ztypestr;
+	dns_rpz_num_t rpz_num;
 
 	options = NULL;
 	(void)cfg_map_get(config, "options", &options);
@@ -3409,6 +3701,18 @@
 	INSIST(dupzone == NULL);
 
 	/*
+	 * Note whether this is a response policy zone and which one if so.
+	 */
+	for (rpz_num = 0; ; ++rpz_num) {
+		if (view->rpzs == NULL || rpz_num >= view->rpzs->p.cnt) {
+			rpz_num = DNS_RPZ_INVALID_NUM;
+			break;
+		}
+		if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, origin))
+			break;
+	}
+
+	/*
 	 * See if we can reuse an existing zone.  This is
 	 * only possible if all of these are true:
 	 *   - The zone's view exists
@@ -3416,6 +3720,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 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 polic zone summary data.
 	 */
 	result = dns_viewlist_find(&ns_g_server->viewlist,
 				   view->name, view->rdclass,
@@ -3429,6 +3736,10 @@
 	if (zone != NULL && !ns_zone_reusable(zone, zconfig))
 		dns_zone_detach(&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) {
 		/*
 		 * We found a reusable zone.  Make it use the
@@ -3451,6 +3762,19 @@
 		dns_zone_setstats(zone, ns_g_server->zonestats);
 	}
 
+	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,
+				      "zone '%s': incompatible"
+				      " masterfile-format or database"
+				      " for a response policy zone",
+				      zname);
+			goto cleanup;
+		}
+	}
+
 	/*
 	 * If the zone contains a 'forwarders' statement, configure
 	 * selective forwarding.
@@ -7409,7 +7733,8 @@
 	isc_task_beginexclusive(server->task);
 	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
@@ -202,6 +202,12 @@
 	SET_NSSTATDESC(updatebadprereq,
 		       "updates rejected due to prerequisite failure",
 		       "UpdateBadPrereq");
+	SET_NSSTATDESC(rpz_rewrites, "response policy zone rewrites",
+		       "RPZRewrites");
+	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
@@ -56,7 +56,7 @@
 	 checknames checkzone database dlv dlvauto dlz dlzexternal
          dname dns64 dnssec ecdsa forward glue gost ixfr limits
 	 logfileconfig lwresd masterfile masterformat metadata notify
-	 nsupdate pending pkcs11 resolver rndc rpz rrsetorder sortlist
+	 nsupdate pending pkcs11 resolver rndc rpz rrl rrsetorder sortlist
 	 smartsign staticstub stub tkey tsig tsiggss unknown upforwd
 	 views 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
@@ -1,4 +1,4 @@
-# Copyright (C) 2011, 2012  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,7 +12,6 @@
 # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 
-# $Id: Makefile.in,v 1.3 2011-01-13 04:59:24 tbox Exp $
 
 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
@@ -1,4 +1,4 @@
-# Copyright (C) 2011, 2012  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,13 +12,12 @@
 # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
 
 
 # Clean up after rpz tests.
 
-rm -f proto.*  dsset-* random.data trusted.conf dig.out* nsupdate.tmp ns*/*tmp
+rm -f proto.* dsset-* random.data trusted.conf dig.out* nsupdate.tmp ns*/*tmp
 rm -f ns*/*.key ns*/*.private ns2/tld2s.db
 rm -f ns3/bl*.db ns*/*switch ns5/requests ns5/example.db ns5/bl.db ns5/*.perf
-rm -f */named.memstats */named.run */named.rpz */session.key
+rm -f */named.memstats */named.run */named.stats */session.key
 rm -f */*.jnl */*.core */*.pid
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, 2012  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,7 +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
@@ -1,4 +1,4 @@
-; Copyright (C) 2011, 2012  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,13 +12,11 @@
 ; 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.
+.		SOA	ns. hostmaster.ns. ( 1 3600 1200 604800 60 )
+		NS	ns.
 ns.		A	10.53.0.1
-.		A	10.53.0.1
 
 ; rewrite responses from this zone
 tld2.		NS	ns.tld2.
@@ -34,3 +32,7 @@
 ; rewrite responses from this zone
 tld4.		NS	ns.tld4.
 ns.tld4.	A	10.53.0.4
+
+; performance test
+tld5.		NS	ns.tld5.
+ns.tld5.	A	10.53.0.5
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
@@ -12,7 +12,6 @@
 ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 ; PERFORMANCE OF THIS SOFTWARE.
 
-; $Id: base-tld2s.db,v 1.1.2.1 2012/02/24 17:22:37 vjs Exp $
 
 
 ; 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, 2012  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$
 
-
-.	0	NS	ns1.
-ns1.	0	A	10.53.0.1
+.	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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2012  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,7 +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
@@ -1,4 +1,4 @@
-; Copyright (C) 2011, 2012  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,7 +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/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
@@ -1,4 +1,4 @@
-; Copyright (C) 2011, 2012  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,7 +12,6 @@
 ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 ; PERFORMANCE OF THIS SOFTWARE.
 
-; $Id$
 
 
 ; RPZ test
@@ -40,3 +39,10 @@
 redirect	A       127.0.0.1
 *.redirect	A       127.0.0.1
 *.credirect	CNAME   google.com.
+
+
+; names in the RPZ TLDs that some say should not be rewritten.
+; This is not a bug, because any data leaked by writing 24.4.3.2.10.rpz-ip
+; (or whatever) is available by publishing "foo A 10.2.3.4" and then
+; resolving foo.
+32.3.2.1.127.rpz-ip					CNAME	walled.invalid.
diff -r -u bin/tests/system/rpz/ns3/crash1-orig bin/tests/system/rpz/ns3/crash1
--- bin/tests/system/rpz/ns3/crash1-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/ns3/crash1	2004-01-01 00:00:00.000000000 +0000
@@ -1,4 +1,4 @@
-; Copyright (C) 2011  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,7 +12,6 @@
 ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 ; PERFORMANCE OF THIS SOFTWARE.
 
-; $Id$
 
 ; a bad zone that caused a crash related to dns_rdataset_disassociate()
 
diff -r -u bin/tests/system/rpz/ns3/crash2-orig bin/tests/system/rpz/ns3/crash2
--- bin/tests/system/rpz/ns3/crash2-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/ns3/crash2	2004-01-01 00:00:00.000000000 +0000
@@ -1,4 +1,4 @@
-; Copyright (C) 2011  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,7 +12,6 @@
 ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 ; PERFORMANCE OF THIS SOFTWARE.
 
-; $Id$
 
 ; a valid zone containing records that caused crashes
 
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, 2012  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$
 
-
-.	0	NS	ns1.
-ns1.	0	A	10.53.0.1
+.	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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2012  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,7 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id$ */
 
 
 /*
@@ -27,6 +26,7 @@
 	transfer-source 10.53.0.3;
 	port 5300;
 	pid-file "named.pid";
+	statistics-file	"named.stats";
 	session-keyfile "session.key";
 	listen-on { 10.53.0.3; };
 	listen-on-v6 { none; };
@@ -44,7 +44,7 @@
 	    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;
 };
 
 key rndc_key {
@@ -55,17 +55,6 @@
 	inet 10.53.0.3 port 9953 allow { any; } keys { rndc_key; };
 };
 
-logging {
-	# change "-c named.conf -d 99 -g" to "-c named.conf -d 99 -f"
-	#   in ../start.pl to check the rpz log category
-	channel rpz { severity debug 10;
-		print-category yes; print-time yes; print-severity yes;
-		file "named.rpz";};
-	category rpz { default_stderr; rpz; };
-	category queries { default_stderr; rpz; };
-	category query-errors { default_stderr; };
-};
-
 
 // include "../trusted.conf";
 zone "." { type hint; file "hints"; };
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, 2012  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,7 +12,6 @@
 ; 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
+.	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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2012  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,7 +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
@@ -1,4 +1,4 @@
-; Copyright (C) 2011, 2012  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,7 +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
@@ -1,4 +1,4 @@
-; Copyright (C) 2011, 2012  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$
 
-
-.	0	NS	ns1.
-ns1.	0	A	10.53.0.1
+.	120	NS	ns.
+ns.	120	A	10.53.0.1
diff -r -u bin/tests/system/rpz/ns5/named.args-orig bin/tests/system/rpz/ns5/named.args
--- bin/tests/system/rpz/ns5/named.args-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/ns5/named.args	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,3 @@
+# run the performace test close to real life
+
+-c named.conf -g
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
@@ -14,7 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id$ */
 
 
 /*
@@ -27,12 +26,13 @@
 	transfer-source 10.53.0.5;
 	port 5300;
 	pid-file "named.pid";
+	statistics-file "named.stats";
 	session-keyfile "session.key";
 	listen-on { 10.53.0.5; };
 	listen-on-v6 { none; };
 	notify no;
 
-	# Eventually turn rpz on.
+	# turn rpz on or off
 	include "rpz-switch";
 };
 
@@ -40,12 +40,34 @@
 	secret "1234abcd8765";
 	algorithm hmac-md5;
 };
-controls { inet 10.53.0.5 port 9953 allow { any; } keys { rndc_key; }; };
+controls {
+	inet 10.53.0.5 port 9953 allow { any; } keys { rndc_key; };
+};
 
 
 include "../trusted.conf";
 zone "."		{type hint; file "hints"; };
 
-zone "example.com."	{type master; file "example.db"; };
+zone "tld5."		{type master; file "tld5.db"; };
+zone "example.tld5."	{type master; file "example.db"; };
 
-zone "bl."		{type master; file "bl.db"; };
+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
@@ -0,0 +1,67 @@
+; 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.
+
+
+
+; RPZ preformance test
+
+$TTL	120
+@	SOA	.  hostmaster.ns.example.tld5. ( 1 3600 1200 604800 60 )
+	NS	ns
+	NS	ns1
+	NS	ns2
+	NS	ns3
+	NS	ns4
+	NS	ns5
+	NS	ns6
+	NS	ns7
+	NS	ns8
+	NS	ns9
+	NS	ns10
+	NS	ns11
+	NS	ns12
+	NS	ns13
+	NS	ns14
+	NS	ns15
+	NS	ns16
+	NS	ns17
+	NS	ns18
+	NS	ns19
+ns	A	10.53.0.5
+ns1	A	10.53.0.5
+ns2	A	10.53.0.5
+ns3	A	10.53.0.5
+ns4	A	10.53.0.5
+ns5	A	10.53.0.5
+ns6	A	10.53.0.5
+ns7	A	10.53.0.5
+ns8	A	10.53.0.5
+ns9	A	10.53.0.5
+ns10	A	10.53.0.5
+ns11	A	10.53.0.5
+ns12	A	10.53.0.5
+ns13	A	10.53.0.5
+ns14	A	10.53.0.5
+ns15	A	10.53.0.5
+ns16	A	10.53.0.5
+ns17	A	10.53.0.5
+ns18	A	10.53.0.5
+ns19	A	10.53.0.5
+
+
+$ORIGIN	example.tld5.
+example.tld5.	NS	ns
+		NS	ns1
+ns		A	10.53.0.5
+ns1		A	10.53.0.5
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
@@ -14,7 +14,6 @@
 # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 
-# $Id: qperf.sh,v 1.1.2.1 2011/10/15 23:03:37 vjs Exp $
 
 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2012  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,7 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id$ */
 
 #include <config.h>
 
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
@@ -1,6 +1,6 @@
 #! /bin/sh
 #
-# Copyright (C) 2011, 2012  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,7 +14,6 @@
 # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
 
 set -e
 
@@ -48,19 +47,25 @@
 
 
 # Performance checks.
-# First with rpz off.
 cat <<EOF >ns5/rpz-switch
-response-policy {zone "bl";}
-    recursive-only no
-    max-policy-ttl 90
-    break-dnssec yes;
+response-policy {
+	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
+	break-dnssec yes;
 EOF
 
 cat <<EOF >ns5/example.db
 \$TTL	120
-@	SOA	.  hostmaster.ns.example. ( 1 3600 1200 604800 60 )
+@	SOA	.  hostmaster.ns.example.tld5. ( 1 3600 1200 604800 60 )
 	NS	ns
+	NS	ns1
 ns	A	10.53.0.5
+ns1	A	10.53.0.5
 EOF
 
 cat <<EOF >ns5/bl.db
@@ -71,31 +76,26 @@
 
 ; used only in failure for "recursive-only no" in #8 test5
 a3-5.tld2	CNAME	*.
-; for "break-dnssec" in #9 test5
+; for "break-dnssec" in #9 & #10 test5
 a3-5.tld2s	CNAME	*.
-; for "max-policy-ttl 90" in test5
+; for "max-policy-ttl 90" in #17 test5
 a3-17.tld2	500 A	17.17.17.17
 
-; dummy NSDNAME policies to trigger lookups
-ns-1.example.com.rpz-nsdname	CNAME	.
-ns-2.example.com.rpz-nsdname	CNAME	.
-ns-3.example.com.rpz-nsdname	CNAME	.
-ns-4.example.com.rpz-nsdname	CNAME	.
-ns-5.example.com.rpz-nsdname	CNAME	.
+; dummy NSDNAME policy to trigger lookups
+ns1.x.rpz-nsdname	CNAME	.
 EOF
 
 if test -n "$QPERF"; then
     # do not build the full zones if we will not use them to avoid the long
     # time otherwise required to shut down the server
     $PERL -e 'for ($val = 1; $val <= 65535; ++$val) {
-	printf("host-%d-%d\tA    192.168.%d.%d\n",
-		$val/256, $val%256, $val/256, $val%256);
+	printf("host-%05d\tA    192.168.%d.%d\n", $val, $val/256, $val%256);
 	}' >>ns5/example.db
 
     echo >>ns5/bl.db
     echo "; rewrite some names" >>ns5/bl.db
     $PERL -e 'for ($val = 2; $val <= 65535; $val += 69) {
-	printf("host-%d.sub%d.example.com\tCNAME\t.\n", $val/256, $val%256);
+	printf("host-%05d.example.tld5\tCNAME\t.\n", $val);
 	}' >>ns5/bl.db
 
     echo >>ns5/bl.db
@@ -103,13 +103,11 @@
     $PERL -e 'for ($val = 3; $val <= 65535; $val += 69) {
 	printf("32.%d.%d.168.192.rpz-ip  \tCNAME\t.\n",
 		$val%256, $val/256);
-	printf("32.%d.%d.168.192.rpz-nsip\tCNAME\t.\n",
-		($val+1)%256, ($val+1)/256);
 	}' >>ns5/bl.db
 fi
 
 # some psuedo-random queryperf requests
-$PERL -e 'for ($cnt = $val = 1; $cnt <= 2000; ++$cnt) {
-	printf("host-%d.sub%d.example.com A\n", $val%256, $val/256);
-		$val = ($val * 9 + 32771) % 65536;
+$PERL -e 'for ($cnt = $val = 1; $cnt <= 3000; ++$cnt) {
+	printf("host-%05d.example.tld5 A\n", $val);
+	$val = ($val * 9 + 32771) % 65536;
 	}' >ns5/requests
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
@@ -1,4 +1,4 @@
-; Copyright (C) 2011  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,7 +12,6 @@
 ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 ; PERFORMANCE OF THIS SOFTWARE.
 
-; $Id$
 
 
 ; Use comment lines instead of blank lines to combine update requests into
@@ -31,7 +30,7 @@
 ;	3, 21
 update add  a3-1.tld2.bl.	300 CNAME *.
 ; and no assert-botch
-;	5, 22
+;	4, 5, 22, 23
 update add  a3-2.tld2.bl.	300 DNAME example.com.
 ;
 ; NXDOMAIN for a4-2-cname.tld2 via its target a4-2.tld2.
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
@@ -1,4 +1,4 @@
-; Copyright (C) 2011  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,7 +12,6 @@
 ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 ; PERFORMANCE OF THIS SOFTWARE.
 
-; $Id$
 
 
 ; Use comment lines instead of blank lines to combine update requests into
@@ -45,7 +44,7 @@
 ;	9
 update add  128.1.zz.3.2.2001.rpz-ip.bl	300 CNAME .
 ;
-; apply the policy with the lexically smallest address of 192.168.5.1
+; apply the policy with the lexically smaller trigger address of 192.168.5.1
 ; to an RRset of more than one A RR
 ;	11
 update add  32.1.5.168.192.rpz-ip.bl	300 A	127.0.0.1
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  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,7 +12,6 @@
 ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 ; PERFORMANCE OF THIS SOFTWARE.
 
-; $Id$
 
 
 ; Use comment lines instead of blank lines to combine update requests into
@@ -24,20 +23,24 @@
 
 server 10.53.0.3 5300
 
+;	3, 4, 5
 ; NXDOMAIN for *.sub1.tld2 by NSDNAME
 update add  *.sub1.tld2.rpz-nsdname.bl.	300 CNAME .
 ;
+;	6
 ; walled garden for *.sub2.tld2
 update add  *.sub2.tld2.rpz-nsdname.bl.	300 CNAME a12-cname.tld2.
 ;
+;	7, 8
 ; exempt a3-2.tld2 and anything in 192.168.0.0/24
 ;   also checks that IP policies are preferred over NSDNAME policies
 update add  a3-2.tld2.bl		300 CNAME a3-2.tld2.
 update add  24.0.0.168.192.rpz-ip.bl	300 CNAME 24.0.0.168.192.
 ;
+;	9
 ; prefer QNAME policy to NSDNAME policy
 update add  a4-1.tld2.bl.		300 A 12.12.12.12
-;
+;	10
 ; prefer policy for largest NS name
 update add  ns.sub3.tld2.rpz-nsdname.bl.	300 A	127.0.0.1
 update add  ns.subsub.sub3.tld2.rpz-nsdname.bl. 300 A	127.0.0.2
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  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,7 +12,6 @@
 ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 ; PERFORMANCE OF THIS SOFTWARE.
 
-; $Id$
 
 
 ; Use comment lines instead of blank lines to combine update requests into
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
@@ -0,0 +1,30 @@
+; 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
+; 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.
+
+
+
+; Use comment lines instead of blank lines to combine update requests into
+;	single requests
+; Separate update requests for distinct TLDs with blank lines or 'send'
+; End the file with a blank line or 'send'
+
+; walled-garden NSIP tests
+
+server 10.53.0.3 5300
+
+; rewrite all of tld2 based on its server IP address
+update add  32.2.0.53.10.rpz-nsip.bl.	300 A	    41.41.41.41
+update add  32.2.0.53.10.rpz-nsip.bl.	300 AAAA    2041::41
+update add  32.2.0.53.10.rpz-nsip.bl.	300 TXT	    "NSIP walled garden"
+send
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
@@ -1,4 +1,4 @@
-; Copyright (C) 2011  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,7 +12,6 @@
 ; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 ; PERFORMANCE OF THIS SOFTWARE.
 
-; $Id$
 
 
 ; Use comment lines instead of blank lines to combine update requests into
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
@@ -1,4 +1,4 @@
-# Copyright (C) 2011, 2012  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,7 +12,6 @@
 # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 
-# $Id: tests.sh,v 1.12 2012/01/07 23:46:53 tbox Exp $
 
 # test response policy zones (RPZ)
 
@@ -27,6 +26,7 @@
 ns5=$ns.5			    # check performance with this server
 
 HAVE_CORE=
+SAVE_RESULTS=
 
 USAGE="$0: [-x]"
 while getopts "x" c; do
@@ -43,11 +43,18 @@
 # really quit on control-C
 trap 'exit 1' 1 2 15
 
+TS='%H:%M:%S '
+TS=
+comment () {
+    if test -n "$TS"; then
+	date "+I:${TS}$*"
+    fi
+}
 
 RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s"
 
 digcmd () {
-    digcmd_args="+noadd +nosearch +time=1 +tries=1 -p 5300 $*"
+    digcmd_args="+noadd +time=1 +tries=1 -p 5300 $*"
     expr "$digcmd_args" : '.*@' >/dev/null || \
 	digcmd_args="$digcmd_args @$ns3"
     expr "$digcmd_args" : '.*+[no]*auth' >/dev/null || \
@@ -70,13 +77,14 @@
 
 setret () {
     ret=1
+    status=`expr $status + 1`
     echo "$*"
 }
 
 # (re)load the reponse policy zones with the rules in the file $TEST_FILE
 load_db () {
     if test -n "$TEST_FILE"; then
-	if $NSUPDATE -v $TEST_FILE; then : ; else
+	if ! $NSUPDATE -v $TEST_FILE; then
 	    echo "I:failed to update policy zone with $TEST_FILE"
 	    exit 1
 	fi
@@ -122,10 +130,21 @@
     return 1
 }
 
+# check that statistics for $1 in $2 = $3
+ckstats () {
+    $RNDCCMD $1 stats
+    CNT=`sed -n -e 's/[	 ]*\([0-9]*\).response policy.*/\1/p'  \
+		    $2/named.stats`
+    CNT=`expr 0$CNT + 0`
+    if test "$CNT" -ne $3; then
+	setret "I:wrong $2 statistics of $CNT instead of $3"
+    fi
+}
+
 # $1=message  $2=optional test file name
 start_group () {
     ret=0
-    test -n "$1" && echo "I:checking $1"
+    test -n "$1" && date "+I:${TS}checking $1"
     TEST_FILE=$2
     if test -n "$TEST_FILE"; then
 	GROUP_NM="-$TEST_FILE"
@@ -138,33 +157,25 @@
 
 end_group () {
     if test -n "$TEST_FILE"; then
+	# remove the previous set of test rules
 	sed -e 's/[	 ]add[	 ]/ delete /' $TEST_FILE | $NSUPDATE
 	TEST_FILE=
     fi
     ckalive $ns3 "I:failed; ns3 server crashed and restarted"
-    if test "$status" -eq 0; then
-	# look for complaints from rpz.c
-	EMSGS=`grep -l 'invalid rpz' */*.run`
-	if test -n "$EMSGS"; then
-	    setret "I:'invalid rpz' complaints in $EMSGS starting with:"
-	    grep 'invalid rpz' */*.run | sed -e '4,$d' -e 's/^/I:    /'
-	fi
-	# look for complaints from rpz.c and query.c
-	EMSGS=`grep -l 'rpz .*failed' */*.run`
-	if test -n "$EMSGS"; then
-	    setret "I:'rpz failed' complaints in $EMSGS starting with:"
-	    grep 'rpz .*failed' */*.run | sed -e '4,$d' -e 's/^/I:    /'
-	fi
-    fi
-    status=`expr $status + $ret`
     GROUP_NM=
 }
 
+clean_result () {
+    if test -z "$SAVE_RESULTS"; then
+	rm -f $*
+    fi
+}
+
 # $1=dig args $2=other dig output file
 ckresult () {
     #ckalive "$1" "I:server crashed by 'dig $1'" || return 1
     if $PERL $SYSTEMTESTTOP/digcomp.pl $DIGNM $2 >/dev/null; then
-	rm -f ${DIGNM}*
+	clean_result ${DIGNM}*
 	return 0
     fi
     setret "I:'dig $1' wrong; diff $DIGNM $2"
@@ -208,7 +219,7 @@
     digcmd $2 >$DIGNM
     #ckalive "$2" "I:server crashed by 'dig $2'" || return 1
     ADDR_ESC=`echo "$ADDR" | sed -e 's/\./\\\\./g'`
-    ADDR_TTL=`sed -n -e  "s/^[-.a-z0-9]\{1,\}	*\([0-9]*\)	IN	A\{1,4\}	${ADDR_ESC}\$/\1/p" $DIGNM`
+    ADDR_TTL=`sed -n -e "s/^[-.a-z0-9]\{1,\}	*\([0-9]*\)	IN	AA*	${ADDR_ESC}\$/\1/p" $DIGNM`
     if test -z "$ADDR_TTL"; then
 	setret "I:'dig $2' wrong; no address $ADDR record in $DIGNM"
 	return 1
@@ -217,7 +228,7 @@
 	setret "I:'dig $2' wrong; TTL=$ADDR_TTL instead of $3 in $DIGNM"
 	return 1
     fi
-    rm -f ${DIGNM}*
+    clean_result ${DIGNM}*
 }
 
 # check that a response is not rewritten
@@ -226,7 +237,7 @@
     make_dignm
     digcmd $* >$DIGNM
     digcmd $* @$ns2 >${DIGNM}_OK
-    ckresult "$*" ${DIGNM}_OK && rm -f ${DIGNM}_OK
+    ckresult "$*" ${DIGNM}_OK && clean_result ${DIGNM}_OK
 }
 
 # check against a 'here document'
@@ -248,8 +259,8 @@
 nochange .				# 1 do not crash or rewrite root
 nxdomain a0-1.tld2			# 2
 nodata a3-1.tld2			# 3
-nodata a3-2.tld2			# 4 no crash on DNAME
-nodata sub.a3-2.tld2
+nodata a3-2.tld2			# 4 nodata at DNAME itself
+nochange sub.a3-2.tld2			# 5 miss where DNAME might work
 nxdomain a4-2.tld2			# 6 rewrite based on CNAME target
 nxdomain a4-2-cname.tld2		# 7
 nodata a4-3-cname.tld2			# 8
@@ -313,8 +324,9 @@
 end_group
 
 if ./rpz nsdname; then
+    # these tests assume "min-ns-dots 0"
     start_group "NSDNAME rewrites" test3
-    nochange a3-1.tld2
+    nochange a3-1.tld2			# 1
     nochange a3-1.tld2	    +dnssec	# 2 this once caused problems
     nxdomain a3-1.sub1.tld2		# 3 NXDOMAIN *.sub1.tld2 by NSDNAME
     nxdomain a3-1.subsub.sub1.tld2
@@ -332,12 +344,22 @@
 fi
 
 if ./rpz nsip; then
+    # these tests assume "min-ns-dots 0"
     start_group "NSIP rewrites" test4
-    nxdomain a3-1.tld2			# 1 NXDOMAIN for all of tld2 by NSIP
+    nxdomain a3-1.tld2			# 1 NXDOMAIN for all of tld2
     nochange a3-2.tld2.			# 2 exempt rewrite by name
     nochange a0-1.tld2.			# 3 exempt rewrite by address block
     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
 else
     echo "I:NSIP not checked; named not configured with --enable-rpz-nsip"
 fi
@@ -377,6 +399,11 @@
     nocrash www.redirect -t$Q
     nocrash www.credirect -t$Q
 done
+
+# This is not a bug, because any data leaked by writing 24.4.3.2.10.rpz-ip
+# (or whatever) is available by publishing "foo A 10.2.3.4" and then
+# resolving foo.
+# nxdomain 32.3.2.1.127.rpz-ip
 end_group
 
 
@@ -384,55 +411,56 @@
 QPERF=`sh qperf.sh`
 if test -n "$QPERF"; then
     perf () {
-	echo "I:checking performance $1"
-	# don't measure the costs of -d99
-	$RNDCCMD $ns5 notrace >/dev/null
-	$QPERF -1 -l2 -d ns5/requests -s $ns5 -p 5300 >ns5/$2.perf
+	date "+I:${TS}checking performance $1"
+	# Dry run to prime everything
+	comment "before dry run $1"
+	$QPERF -c -1 -l30 -d ns5/requests -s $ns5 -p 5300 >/dev/null
+	comment "before real test $1"
+	PFILE="ns5/$2.perf"
+	$QPERF -c -1 -l30 -d ns5/requests -s $ns5 -p 5300 >$PFILE
+	comment "after test $1"
+	X=`sed -n -e 's/.*Returned *\([^ ]*:\) *\([0-9]*\) .*/\1\2/p' $PFILE \
+		| tr '\n' ' '`
+	if test "$X" != "$3"; then
+	    setret "I:wrong results '$X' in $PFILE"
+	fi
 	ckalive $ns5 "I:failed; server #5 crashed"
     }
     trim () {
 	sed -n -e 's/.*Queries per second: *\([0-9]*\).*/\1/p' ns5/$1.perf
     }
 
-    # Dry run to prime disk cache
-    #	Otherwise a first test of either flavor is 25% low
-    perf 'to prime disk cache' rpz
-
-    # get queries/second with rpz
-    perf 'with rpz' rpz
-
-    # turn off rpz and measure queries/second again
-    # Don't wait for a clean stop.  Clean stops of this server need seconds
-    # until the sockets are closed.  5 or 10 seconds after that, the
-    # server really stops and deletes named.pid.
-    echo "# rpz off" >ns5/rpz-switch
-    PID=`cat ns5/named.pid`
-    test -z "$PID" || kill -9 "$PID"
-    $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns5
-    perf 'without rpz' norpz
-
-    # Don't wait for a clean stop.  Clean stops of this server need seconds
-    # until the sockets are closed.  5 or 10 seconds after that, the
-    # server really stops and deletes named.pid.
-    echo "# rpz off" >ns5/rpz-switch
-    PID=`cat ns5/named.pid`
-    test -z "$PID" || kill -9 "$PID" && rm -f ns5/named.pid
+    # get qps with rpz
+    perf 'with rpz' rpz 'NOERROR:2900 NXDOMAIN:100 '
+    RPZ=`trim rpz`
 
+    # turn off rpz and measure qps again
+    echo "# rpz off" >ns5/rpz-switch
+    RNDCCMD_OUT=`$RNDCCMD $ns5 reload`
+    perf 'without rpz' norpz 'NOERROR:3000 '
     NORPZ=`trim norpz`
-    RPZ=`trim rpz`
-    echo "I:$RPZ qps with RPZ versus $NORPZ qps without"
 
-    # fail if RPZ costs more than 100%
-    NORPZ2=`expr "$NORPZ" / 2`
-    if test "$RPZ" -le "$NORPZ2"; then
-	echo "I:rpz $RPZ qps too far below non-RPZ $NORPZ qps"
-	status=`expr $status + 1`
+    PERCENT=`expr \( "$RPZ" \* 100 + \( $NORPZ / 2 \) \) / $NORPZ`
+    echo "I:$RPZ qps with rpz is $PERCENT% of $NORPZ qps without rpz"
+
+    MIN_PERCENT=30
+    if test "$PERCENT" -lt $MIN_PERCENT; then
+	setret "I:$RPZ qps with rpz or $PERCENT% is below $MIN_PERCENT% of $NORPZ qps"
+    fi
+
+    if test "$PERCENT" -ge 100; then
+	setret "I:$RPZ qps with RPZ or $PERCENT% of $NORPZ qps without RPZ is too high"
     fi
+
+    ckstats $ns5 ns5 203
+
 else
     echo "I:performance not checked; queryperf not available"
 fi
 
 
+ckstats $ns3 ns3 58
+
 # 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
@@ -441,6 +469,12 @@
     test -z "$HAVE_CORE" || setret "I:found $HAVE_CORE; memory leak?"
 fi
 
+# look for complaints from lib/dns/rpz.c and bin/name/query.c
+EMSGS=`egrep -l 'invalid rpz|rpz.*failed' ns*/named.run`
+if test -n "$EMSGS"; then
+    setret "I:error messages in $EMSGS starting with:"
+    egrep 'invalid rpz|rpz.*failed' ns*/named.run | sed -e '10,$d' -e 's/^/I:  /'
+fi
 
 echo "I:exit status: $status"
 exit $status
diff -r -u bin/tests/system/rrl/.gitignore-orig bin/tests/system/rrl/.gitignore
--- bin/tests/system/rrl/.gitignore-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/.gitignore	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1 @@
+flood
diff -r -u bin/tests/system/rrl/clean.sh-orig bin/tests/system/rrl/clean.sh
--- bin/tests/system/rrl/clean.sh-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/clean.sh	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,21 @@
+# Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+
+
+# Clean up after rrl tests.
+
+rm -f dig.out*
+rm -f  */named.memstats */named.run */named.stats */log */session.key
+rm -f ns3/bl*.db */*.jnl */*.core */*.pid
diff -r -u bin/tests/system/rrl/ns1/named.conf-orig bin/tests/system/rrl/ns1/named.conf
--- bin/tests/system/rrl/ns1/named.conf-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/ns1/named.conf	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+controls { /* empty */ };
+
+options {
+	query-source address 10.53.0.1;
+	notify-source 10.53.0.1;
+	transfer-source 10.53.0.1;
+	port 5300;
+	session-keyfile "session.key";
+	pid-file "named.pid";
+	listen-on { 10.53.0.1; };
+	listen-on-v6 { none; };
+	notify no;
+};
+
+zone "." {type master; file "root.db";};
diff -r -u bin/tests/system/rrl/ns1/root.db-orig bin/tests/system/rrl/ns1/root.db
--- bin/tests/system/rrl/ns1/root.db-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/ns1/root.db	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,31 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+$TTL	120
+@		SOA	ns. hostmaster.ns. ( 1 3600 1200 604800 60 )
+@		NS	ns.
+ns.		A	10.53.0.1
+.		A	10.53.0.1
+
+; limit responses from here
+tld2.		NS	ns.tld2.
+ns.tld2.	A	10.53.0.2
+
+; limit recursion to here
+tld3.		NS	ns.tld3.
+ns.tld3.	A	10.53.0.3
+
+; generate SERVFAIL
+tld4.		NS	ns.tld3.
diff -r -u bin/tests/system/rrl/ns2/hints-orig bin/tests/system/rrl/ns2/hints
--- bin/tests/system/rrl/ns2/hints-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/ns2/hints	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,18 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+
+.	0	NS	ns1.
+ns1.	0	A	10.53.0.1
diff -r -u bin/tests/system/rrl/ns2/named.conf-orig bin/tests/system/rrl/ns2/named.conf
--- bin/tests/system/rrl/ns2/named.conf-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/ns2/named.conf	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+controls { /* empty */ };
+
+options {
+	query-source address 10.53.0.2;
+	notify-source 10.53.0.2;
+	transfer-source 10.53.0.2;
+	port 5300;
+	session-keyfile "session.key";
+	pid-file "named.pid";
+	statistics-file	"named.stats";
+	listen-on { 10.53.0.2; };
+	listen-on-v6 { none; };
+	notify no;
+
+	rate-limit {
+	    responses-per-second 2;
+	    all-per-second 70;
+	    IPv4-prefix-length 24;
+	    IPv6-prefix-length 64;
+	    slip 3;
+	    /* qps-scale 2; */
+	    exempt-clients { 10.53.0.7; };
+	    window 1;
+	    max-table-size 100;
+	    min-table-size 2;
+	};
+};
+
+key rndc_key {
+	secret "1234abcd8765";
+	algorithm hmac-md5;
+};
+controls {
+	inet 10.53.0.2 port 9953 allow { any; } keys { rndc_key; };
+};
+
+/*
+ * These log settings have no effect unless "-g" is removed from ../../start.pl
+ */
+logging {
+	channel debug {
+	    file "log-debug";
+	    print-category yes; print-severity yes; severity debug 10;
+	};
+	channel queries {
+	    file "log-queries";
+	    print-category yes; print-severity yes; severity info;
+	};
+	category rate-limit { debug; queries; };
+	category queries { debug; queries; };
+};
+
+zone "." { type hint; file "hints"; };
+
+zone "tld2."{ type master; file "tld2.db"; };
diff -r -u bin/tests/system/rrl/ns2/tld2.db-orig bin/tests/system/rrl/ns2/tld2.db
--- bin/tests/system/rrl/ns2/tld2.db-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/ns2/tld2.db	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,42 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+
+; rate limit response from this zone
+
+$TTL	120
+@		SOA	tld2.  hostmaster.ns.tld2. ( 1 3600 1200 604800 60 )
+		NS	ns
+		NS	.
+ns		A	10.53.0.2
+
+a1		A	192.168.2.1
+
+*.a2		A	192.168.2.2
+
+; a3 is in tld3
+
+; a4 does not exist to give NXDOMAIN
+
+; a5 for TCP requests
+a5		A	192.168.2.5
+
+; a6 for whitelisted clients
+a6		A	192.168.2.6
+
+; a7 for SERVFAIL
+
+; a8 for all-per-second limit
+$GENERATE 101-180 all$.a8 A 192.168.2.8
diff -r -u bin/tests/system/rrl/ns3/hints-orig bin/tests/system/rrl/ns3/hints
--- bin/tests/system/rrl/ns3/hints-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/ns3/hints	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,18 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+
+.	0	NS	ns1.
+ns1.	0	A	10.53.0.1
diff -r -u bin/tests/system/rrl/ns3/named.conf-orig bin/tests/system/rrl/ns3/named.conf
--- bin/tests/system/rrl/ns3/named.conf-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/ns3/named.conf	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+controls { /* empty */ };
+
+options {
+	query-source address 10.53.0.3;
+	notify-source 10.53.0.3;
+	transfer-source 10.53.0.3;
+	port 5300;
+	session-keyfile "session.key";
+	pid-file "named.pid";
+	listen-on { 10.53.0.3; };
+	listen-on-v6 { none; };
+	notify no;
+};
+
+zone "." { type hint; file "hints"; };
+
+zone "tld3."{ type master; file "tld3.db"; };
diff -r -u bin/tests/system/rrl/ns3/tld3.db-orig bin/tests/system/rrl/ns3/tld3.db
--- bin/tests/system/rrl/ns3/tld3.db-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/ns3/tld3.db	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,25 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+
+; rate limit response from this zone
+
+$TTL	120
+@		SOA	tld3.  hostmaster.ns.tld3. ( 1 3600 1200 604800 60 )
+		NS	ns
+		NS	.
+ns		A	10.53.0.3
+
+*.a3		A	192.168.3.3
diff -r -u bin/tests/system/rrl/setup.sh-orig bin/tests/system/rrl/setup.sh
--- bin/tests/system/rrl/setup.sh-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/setup.sh	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+. ./clean.sh
+
diff -r -u bin/tests/system/rrl/tests.sh-orig bin/tests/system/rrl/tests.sh
--- bin/tests/system/rrl/tests.sh-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rrl/tests.sh	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,224 @@
+# Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+
+# test response rate limiting
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+#set -x
+#set -o noclobber
+
+ns1=10.53.0.1			    # root, defining the others
+ns2=10.53.0.2			    # test server
+ns3=10.53.0.3			    # secondary test server
+ns7=10.53.0.7			    # whitelisted client
+
+USAGE="$0: [-x]"
+while getopts "x" c; do
+    case $c in
+	x) set -x;;
+	*) echo "$USAGE" 1>&2; exit 1;;
+    esac
+done
+shift `expr $OPTIND - 1 || true`
+if test "$#" -ne 0; then
+    echo "$USAGE" 1>&2
+    exit 1
+fi
+# really quit on control-C
+trap 'exit 1' 1 2 15
+
+
+ret=0
+setret () {
+    ret=1
+    echo "$*"
+}
+
+
+# Wait until soon after the start of a second to make results consistent.
+#   The start of a second credits a rate limit.
+#   This would be far easier in C or by assuming a modern version of perl.
+sec_start () {
+    START=`date`
+    while true; do
+	NOW=`date`
+	if test "$START" != "$NOW"; then
+	    return
+	fi
+	$PERL -e 'select(undef, undef, undef, 0.05)' || true
+    done
+}
+
+
+#   $1=result name  $2=domain name  $3=dig options
+digcmd () {
+    OFILE=$1; shift
+    DIG_DOM=$1; shift
+    ARGS="+noadd +noauth +nosearch +time=1 +tries=1 +ignore $* -p 5300 $DIG_DOM @$ns2"
+    #echo I:dig $ARGS 1>&2
+    START=`date +%y%m%d%H%M.%S`
+    RESULT=`$DIG $ARGS 2>&1 | tee $OFILE=TEMP				\
+	    | sed -n -e  's/^[^;].*	\([^	 ]\{1,\}\)$/\1/p'	\
+		-e 's/;; flags.* tc .*/TC/p'				\
+		-e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p'		\
+		-e 's/;; .* status: SERVFAIL.*/SERVFAIL/p'		\
+		-e 's/;; connection timed out.*/drop/p'			\
+		-e 's/;; communications error to.*/drop/p'		\
+	    | tr -d '\n'`
+    mv "$OFILE=TEMP" "$OFILE=$RESULT"
+    touch -t $START "$OFILE=$RESULT"
+}
+
+
+#   $1=number of tests  $2=target domain  $3=dig options
+CNT=1
+burst () {
+    BURST_LIMIT=$1; shift
+    BURST_DOM_BASE="$1"; shift
+    while test "$BURST_LIMIT" -ge 1; do
+	if test $CNT -lt 10; then
+	    CNT="00$CNT"
+	else
+	    if test $CNT -lt 100; then
+		CNT="0$CNT"
+	    fi
+	fi
+	eval BURST_DOM="$BURST_DOM_BASE"
+	FILE="dig.out-$BURST_DOM-$CNT"
+	digcmd $FILE $BURST_DOM $* &
+	CNT=`expr $CNT + 1`
+	BURST_LIMIT=`expr "$BURST_LIMIT" - 1`
+    done
+}
+
+
+#   $1=domain  $2=IP address  $3=# of IP addresses  $4=TC  $5=drop
+#	$6=NXDOMAIN  $7=SERVFAIL or other errors
+ck_result() {
+    BAD=
+    wait
+    ADDRS=`ls dig.out-$1-*=$2		2>/dev/null	| wc -l | tr -d ' '`
+    TC=`ls dig.out-$1-*=TC		2>/dev/null	| wc -l | tr -d ' '`
+    DROP=`ls dig.out-$1-*=drop		2>/dev/null	| wc -l | tr -d ' '`
+    NXDOMAIN=`ls dig.out-$1-*=NXDOMAIN	2>/dev/null	| wc -l | tr -d ' '`
+    SERVFAIL=`ls dig.out-$1-*=SERVFAIL	2>/dev/null	| wc -l | tr -d ' '`
+    if test $ADDRS -ne "$3"; then
+	setret "I:$ADDRS instead of $3 $2 responses for $1"
+	BAD=yes
+    fi
+    if test $TC -ne "$4"; then
+	setret "I:$TC instead of $4 truncation responses for $1"
+	BAD=yes
+    fi
+    if test $DROP -ne "$5"; then
+	setret "I:$DROP instead of $5 dropped responses for $1"
+	BAD=yes
+    fi
+    if test $NXDOMAIN -ne "$6"; then
+	setret "I:$NXDOMAIN instead of $6 NXDOMAIN responses for $1"
+	BAD=yes
+    fi
+    if test $SERVFAIL -ne "$7"; then
+	setret "I:$SERVFAIL instead of $7 error responses for $1"
+	BAD=yes
+    fi
+    if test -z "$BAD"; then
+	rm -f dig.out-$1-*
+    fi
+}
+
+
+#########
+sec_start
+
+# basic rate limiting
+burst 3 a1.tld2
+# 1 second delay allows an additional response.
+sleep 1
+burst 21 a1.tld2
+# request 30 different qnames to try a wild card
+burst 30 'x$CNT.a2.tld2'
+
+#					IP      TC      drop  NXDOMAIN SERVFAIL
+# check for 24 results
+# including the 1 second delay
+ck_result   a1.tld2	192.168.2.1	3	7	14	0	0
+
+# Check the wild card answers.
+# The parent name of the 30 requests is counted.
+ck_result 'x*.a2.tld2'	192.168.2.2	2	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 the SERVFAIL limit is reached.
+ck_result a7.tld4	192.168.2.1	0	5	13	0	2
+
+
+#########
+sec_start
+
+# all-per-second
+#   The qnames are all unique but the client IP address is constant.
+CNT=101
+burst 80 'all$CNT.a8.tld2'
+ck_result 'a*.a8.tld2'	192.168.2.8	70	3	7	0	0
+
+
+$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
+ckstats () {
+    CNT=`sed -n -e "s/[	 ]*\([0-9]*\).responses $1 for rate limits.*/\1/p"  \
+		ns2/named.stats`
+    CNT=`expr 0$CNT + 0`
+    if test "$CNT" -ne $2; then
+	setret "I:wrong $1 statistics of $CNT instead of $2"
+    fi
+}
+ckstats dropped 76
+ckstats truncated 36
+
+
+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
@@ -4896,6 +4896,34 @@
 		    </para>
 		  </entry>
 		</row>
+                <row rowsep="0">
+                  <entry colname="1">
+                    <para><command>rate-limit</command></para>
+                  </entry>
+		  <entry colname="2">
+		    <para>
+		      The start, periodic, and final notices of the
+		      rate limiting of a stream of responses are logged at
+		      <command>info</command> 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.
+		    </para>
+		    <para>
+		      Rate limiting of individual requests
+		      is logged in the <command>queries</command> category
+		      and can be controlled with the
+		      <command>querylog</command> option.
+		    </para>
+		  </entry>
+		</row>
 	      </tbody>
 	    </tgroup>
 	  </informaltable>
@@ -5212,11 +5240,26 @@
     <optional> resolver-query-timeout <replaceable>number</replaceable> ; </optional>
     <optional> deny-answer-addresses { <replaceable>address_match_list</replaceable> } <optional> except-from { <replaceable>namelist</replaceable> } </optional>;</optional>
     <optional> deny-answer-aliases { <replaceable>namelist</replaceable> } <optional> except-from { <replaceable>namelist</replaceable> } </optional>;</optional>
+    <optional> rate-limit {
+	<optional> responses-per-second <replaceable>number</replaceable> ; </optional>
+	<optional> errors-per-second <replaceable>number</replaceable> ; </optional>
+	<optional> nxdomains-per-second <replaceable>number</replaceable> ; </optional>
+	<optional> all-per-second <replaceable>number</replaceable> ; </optional>
+	<optional> window <replaceable>number</replaceable> ; </optional>
+	<optional> log-only <replaceable>yes_or_no</replaceable> ; </optional>
+	<optional> qps-scale <replaceable>number</replaceable> ; </optional>
+	<optional> IPv4-prefix-length <replaceable>number</replaceable> ; </optional>
+	<optional> IPv6-prefix-length <replaceable>number</replaceable> ; </optional>
+	<optional> slip <replaceable>number</replaceable> ; </optional>
+	<optional> exempt-clients  { <replaceable>address_match_list</replaceable> } ; </optional>
+	<optional> max-table-size <replaceable>number</replaceable> ; </optional>
+	<optional> min-table-size <replaceable>number</replaceable> ; </optional>
+      } ; </optional>
     <optional> response-policy { <replaceable>zone_name</replaceable>
 	<optional> policy given | disabled | passthru | nxdomain | nodata | cname <replaceable>domain</replaceable> </optional>
 	<optional> recursive-only <replaceable>yes_or_no</replaceable> </optional> <optional> max-policy-ttl <replaceable>number</replaceable> </optional> ;
     } <optional> recursive-only <replaceable>yes_or_no</replaceable> </optional> <optional> max-policy-ttl <replaceable>number</replaceable> </optional>
-	<optional> break-dnssec <replaceable>yes_or_no</replaceable> </optional> ; </optional>
+	<optional> break-dnssec <replaceable>yes_or_no</replaceable> </optional> <optional> min-ns-dots <replaceable>number</replaceable> </optional> ; </optional>
 };
 </programlisting>
 
@@ -9328,14 +9371,26 @@
 	    They are encoded as subdomains of
 	    <userinput>rpz-nsdomain</userinput> relativized
 	    to the RPZ origin name.
-	  </para>
-
-	  <para>
 	    NSIP triggers match IP addresses in A and
 	    AAAA RRsets for domains that can be checked against NSDNAME
 	    policy records.
 	    NSIP triggers are encoded like IP triggers except as subdomains of
 	    <userinput>rpz-nsip</userinput>.
+	    NSDNAME and NSIP triggers are checked only for names with at
+	    least <command>min-ns-dots</command> dots.
+	    The default value of <command>min-ns-dots</command> is 1 to
+	    exclude top level domains.
+	  </para>
+
+	  <para>
+	    Authority verification issues and variations in authority data
+	    can cause inconsistent results for NSIP and NSDNAME policy records.
+	    Glue NS records often differ from authoritative NS records.
+	    So NSIP and NSDNAME policy records are available
+	    only when <acronym>BIND</acronym> is built with the
+	    <userinput>--enable-rpz-nsip</userinput> or
+	    <userinput>--enable-rpz-nsdname</userinput> options
+	    on the "configure" command line.
 	  </para>
 
 	  <para>
@@ -9375,17 +9430,6 @@
 	  </para>
 
 	  <para>
-	    Authority verification issues and variations in authority data
-	    can cause inconsistent results for NSIP and NSDNAME policy records.
-	    Glue NS records often differ from authoritative NS records.
-	    So they are available
-	    only when <acronym>BIND</acronym> is built with the
-	    <userinput>--enable-rpz-nsip</userinput> or
-	    <userinput>--enable-rpz-nsdname</userinput> options
-	    on the "configure" command line.
-	  </para>
-
-	  <para>
 	    RPZ record sets are sets of any types of DNS record except
 	    DNAME or DNSSEC that encode actions or responses to queries.
 	    <itemizedlist>
@@ -9523,7 +9567,232 @@
 ns.domain.com.rpz-nsdname   CNAME   .
 48.zz.2.2001.rpz-nsip       CNAME   .
 </programlisting>
+          <para>
+            RPZ can affect server performance.
+            Each configured response policy zone requires the server to
+            perform one to four additional database lookups before a
+            query can be answered.
+            For example, a DNS server with four policy zones, each with all
+            four kinds of response triggers, QNAME, IP, NSIP, and
+            NSDNAME, requires a total of 17 times as many database
+            lookups as a similar DNS server with no response policy zones.
+            A <acronym>BIND9</acronym> server with adequate memory and one
+            response policy zone with QNAME and IP triggers might achieve a
+            maximum queries-per-second rate about 20% lower.
+            A server with four response policy zones with QNAME and IP
+            triggers might have a maximum QPS rate about 50% lower.
+          </para>
+
+          <para>
+            Responses rewritten by RPZ are counted in the
+            <command>RPZRewrites</command> statistics.
+          </para>
         </sect3>
+
+	<sect3>
+	  <title>Rate Limiting</title>
+	  <para>
+	    Excessive essentially identical UDP <emphasis>responses</emphasis>
+	    can be discarded by configuring a
+	    <command>rate-limit</command> clause in an
+	    <command>options</command> 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.
+	  </para>
+
+	  <para>
+	    Rate limiting works by setting
+	    <command>responses-per-second</command>
+	    to a number of repetitions per second for responses for a given name
+	    and record type to a DNS client.
+	  </para>
+
+	  <para>
+	    <command>Responses-per-second</command> 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.
+	  </para>
+
+	  <para>
+	    The notion of "identical responses"
+	    and "single DNS client" cannot be simplistic.
+	    All responses to a CIDR block with prefix
+	    length specified with <command>IPv4-prefix-length</command>
+	    (default 24) or <command>IPv6-prefix-length</command>
+	    (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
+	    <command>responses-per-second</command> value,
+	    but it can be set separately with
+	    <command>nxdomains-per-second</command>.
+	    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
+	    <command>responses-per-second</command> value,
+	    but it can be set separately with
+	    <command>errors-per-second</command>.
+	  </para>
+
+	  <para>
+	    Rate limiting uses a "credit" or "token bucket" scheme.
+	    Each identical response has a conceptual account
+	    that is given <command>responses-per-second</command>,
+	    <command>errors-per-second</command>, and
+	    <command>nxdomains-per-second</command> 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 <command>window</command>
+	    times the per-second limit.
+	    A DNS client that sends requests that are not
+	    answered can be penalized for up to <command>window</command>
+	    seconds (default 15).
+	  </para>
+
+	  <para>
+	    Responses generated from local wildcards are counted and limited
+	    as if they were for the parent domain name.
+	    This prevents flooding by requesting random.wild.example.com.
+	    For similar reasons, NXDOMAIN responses are counted and rate
+	    limited by the valid domain name nearest to the
+	    query name with an SOA record.
+	  </para>
+
+	  <para>
+	    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 <command>slip</command> 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.
+	    <command>Slip</command> must be between 0 and 10.
+	    A value of 0 does not "slip"
+	    or sends no rate limiting truncated responses.
+	    Some error responses includinge REFUSED and SERVFAIL
+	    cannot be replaced with truncated responses and are instead
+	    leaked at the <command>slip</command> rate.
+	  </para>
+
+	  <para>
+	    When the approximate query per second rate exceeds
+	    the <command>qps-scale</command> value,
+	    the <command>responses-per-second</command>,
+	    <command>errors-per-second</command>,
+	    <command>nxdomains-per-second</command>,
+	    and <command>slip</command> values are reduced by the
+	    ratio of the current rate to the <command>qps-scale</command> value.
+	    This feature can tighten defenses during attacks.
+	    For example, with
+	    <command>qps-scale 250; responses-per-second 20;</command> 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.
+	  </para>
+
+	  <para>
+	    Communities of DNS clients can be given their own parameters or no
+	    rate limiting by putting
+	    <command>rate-limit</command> statements in <command>view</command>
+	    statements instead of the global <command>option</command>
+	    statement.
+	    A <command>rate-limit</command> statement in a view replaces
+	    instead of being merged with a <command>rate-limit</command>
+	    statement among the main options.
+	    DNS clients within a view can be exempted from rate limits
+	    with the <command>exempt-clients</command> clause.
+	  </para>
+
+	  <para>
+	    UDP responses of all kinds can be limited with the
+	    <command>all-per-second</command> phrase.
+	    The rate limiting provided by
+	    <command>responses-per-second</command>,
+	    <command>errors-per-second</command>, and
+	    <command>nxdomains-per-second</command> 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 <command>all-per-second</command> 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 <command>Mail From</command>
+	    command.
+	    This limiting 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.
+	    Attacks on the DNS server 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.
+	  </para>
+
+	  <para>
+	    The maximum size of the table used to track requests and
+	    rate limit responses is set with <command>max-table-size</command>.
+	    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,
+	    <command>min-table-size</command> (default 500)
+	    can set the minimum table size.
+	    Enable <command>rate-limit</command> category logging to monitor
+	    expansions of the table and inform
+	    choices for the initial and maximum table size.
+	  </para>
+
+	  <para>
+	    Use <command>log-only yes</command> to test rate limiting parameters
+	    without actually dropping any requests.
+	  </para>
+
+	  <para>
+	    Responses dropped by rate limits are included in the
+	    <command>RateDropped</command> and <command>QryDropped</command>
+	    statistics.
+	    Responses that truncated by rate limits are included in
+	    <command>RateSlipped</command> and <command>RespTruncated</command>.
+	</sect3>
       </sect2>
 
       <sect2 id="server_statement_grammar">
@@ -14250,6 +14519,45 @@
 		      </para>
 		    </entry>
 		  </row>
+		  <row rowsep="0">
+		    <entry colname="1">
+		      <para><command>RPZRewrites</command></para>
+		    </entry>
+		    <entry colname="2">
+		      <para><command></command></para>
+		    </entry>
+		    <entry colname="3">
+		      <para>
+			Response policy zone rewrites.
+		      </para>
+		    </entry>
+		  </row>
+		  <row rowsep="0">
+		    <entry colname="1">
+		      <para><command>RateDropped</command></para>
+		    </entry>
+		    <entry colname="2">
+		      <para><command></command></para>
+		    </entry>
+		    <entry colname="3">
+		      <para>
+			Responses dropped by rate limits.
+		      </para>
+		    </entry>
+		  </row>
+		  <row rowsep="0">
+		    <entry colname="1">
+		      <para><command>RateSlipped</command></para>
+		    </entry>
+		    <entry colname="2">
+		      <para><command></command></para>
+		    </entry>
+		    <entry colname="3">
+		      <para>
+			Responses truncated by rate limits.
+		      </para>
+		    </entry>
+		  </row>
 		</tbody>
               </tgroup>
             </informaltable>
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@ 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 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
@@ -945,20 +945,23 @@
 		(db->methods->resigned)(db, rdataset, version);
 }
 
+/*
+ * 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_enabled(dns_db_t *db, dns_rpz_st_t *st)
-{
-	if (db->methods->rpz_enabled != NULL)
-		(db->methods->rpz_enabled)(db, st);
+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
@@ -575,8 +575,8 @@
 	NULL,			/* resigned */
 	NULL,			/* isdnssec */
 	NULL,			/* getrrsetstats */
-	NULL,			/* rpz_enabled */
-	NULL			/* rpz_findips */
+ 	NULL,			/* rpz_attach */
+ 	NULL,			/* rpz_ready */
 };
 
 static isc_result_t
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
@@ -0,0 +1,1080 @@
+/*
+ * Copyright (C) 2004-2012  Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (C) 1999-2003  Internet Software Consortium.
+ *
+ * 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_VIEW_H
+#define DNS_VIEW_H 1
+
+/*****
+ ***** Module Info
+ *****/
+
+/*! \file dns/view.h
+ * \brief
+ * DNS View
+ *
+ * A "view" is a DNS namespace, together with an optional resolver and a
+ * forwarding policy.  A "DNS namespace" is a (possibly empty) set of
+ * authoritative zones together with an optional cache and optional
+ * "hints" information.
+ *
+ * Views start out "unfrozen".  In this state, core attributes like
+ * the cache, set of zones, and forwarding policy may be set.  While
+ * "unfrozen", the caller (e.g. nameserver configuration loading
+ * code), must ensure exclusive access to the view.  When the view is
+ * "frozen", the core attributes become immutable, and the view module
+ * will ensure synchronization.  Freezing allows the view's core attributes
+ * to be accessed without locking.
+ *
+ * MP:
+ *\li	Before the view is frozen, the caller must ensure synchronization.
+ *
+ *\li	After the view is frozen, the module guarantees appropriate
+ *	synchronization of any data structures it creates and manipulates.
+ *
+ * Reliability:
+ *\li	No anticipated impact.
+ *
+ * Resources:
+ *\li	TBS
+ *
+ * Security:
+ *\li	No anticipated impact.
+ *
+ * Standards:
+ *\li	None.
+ */
+
+#include <stdio.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/event.h>
+#include <isc/mutex.h>
+#include <isc/net.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/stdtime.h>
+
+#include <dns/acl.h>
+#include <dns/fixedname.h>
+#include <dns/rdatastruct.h>
+#include <dns/rpz.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+struct dns_view {
+	/* Unlocked. */
+	unsigned int			magic;
+	isc_mem_t *			mctx;
+	dns_rdataclass_t		rdclass;
+	char *				name;
+	dns_zt_t *			zonetable;
+	dns_dlzdb_t *			dlzdatabase;
+	dns_resolver_t *		resolver;
+	dns_adb_t *			adb;
+	dns_requestmgr_t *		requestmgr;
+	dns_acache_t *			acache;
+	dns_cache_t *			cache;
+	dns_db_t *			cachedb;
+	dns_db_t *			hints;
+
+	/*
+	 * security roots.
+	 * internal use only; access via * dns_view_getsecroots()
+	 */
+	dns_keytable_t *		secroots_priv;
+
+	isc_mutex_t			lock;
+	isc_boolean_t			frozen;
+	isc_task_t *			task;
+	isc_event_t			resevent;
+	isc_event_t			adbevent;
+	isc_event_t			reqevent;
+	isc_stats_t *			resstats;
+	dns_stats_t *			resquerystats;
+	isc_boolean_t			cacheshared;
+
+	/* Configurable data. */
+	dns_tsig_keyring_t *		statickeys;
+	dns_tsig_keyring_t *		dynamickeys;
+	dns_peerlist_t *		peers;
+	dns_order_t *			order;
+	dns_fwdtable_t *		fwdtable;
+	isc_boolean_t			recursion;
+	isc_boolean_t			auth_nxdomain;
+	isc_boolean_t			additionalfromcache;
+	isc_boolean_t			additionalfromauth;
+	isc_boolean_t			minimalresponses;
+	isc_boolean_t			enablednssec;
+	isc_boolean_t			enablevalidation;
+	isc_boolean_t			acceptexpired;
+	dns_transfer_format_t		transfer_format;
+	dns_acl_t *			cacheacl;
+	dns_acl_t *			cacheonacl;
+	dns_acl_t *			queryacl;
+	dns_acl_t *			queryonacl;
+	dns_acl_t *			recursionacl;
+	dns_acl_t *			recursiononacl;
+	dns_acl_t *			sortlist;
+	dns_acl_t *			notifyacl;
+	dns_acl_t *			transferacl;
+	dns_acl_t *			updateacl;
+	dns_acl_t *			upfwdacl;
+	dns_acl_t *			denyansweracl;
+	dns_rbt_t *			answeracl_exclude;
+	dns_rbt_t *			denyanswernames;
+	dns_rbt_t *			answernames_exclude;
+	isc_boolean_t			requestixfr;
+	isc_boolean_t			provideixfr;
+	isc_boolean_t			requestnsid;
+	dns_ttl_t			maxcachettl;
+	dns_ttl_t			maxncachettl;
+	in_port_t			dstport;
+	dns_aclenv_t			aclenv;
+	dns_rdatatype_t			preferred_glue;
+	isc_boolean_t			flush;
+	dns_namelist_t *		delonly;
+	isc_boolean_t			rootdelonly;
+	dns_namelist_t *		rootexclude;
+	isc_boolean_t			checknames;
+	dns_name_t *			dlv;
+	dns_fixedname_t			dlv_fixed;
+	isc_uint16_t			maxudp;
+	dns_v4_aaaa_t			v4_aaaa;
+	dns_acl_t *			v4_aaaa_acl;
+	dns_dns64list_t 		dns64;
+	unsigned int 			dns64cnt;
+	dns_rpz_zones_t			*rpzs;
+
+	/*
+	 * Configurable data for server use only,
+	 * locked by server configuration lock.
+	 */
+	dns_acl_t *			matchclients;
+	dns_acl_t *			matchdestinations;
+	isc_boolean_t			matchrecursiveonly;
+
+	/* Locked by themselves. */
+	isc_refcount_t			references;
+
+	/* Locked by lock. */
+	unsigned int			weakrefs;
+	unsigned int			attributes;
+	/* Under owner's locking control. */
+	ISC_LINK(struct dns_view)	link;
+	dns_viewlist_t *		viewlist;
+
+	dns_zone_t *			managed_keys;
+
+#ifdef BIND9
+	/* File in which to store configuration for newly added zones */
+	char *				new_zone_file;
+
+	void *				new_zone_config;
+	void				(*cfg_destroy)(void **);
+#endif
+};
+
+#define DNS_VIEW_MAGIC			ISC_MAGIC('V','i','e','w')
+#define DNS_VIEW_VALID(view)		ISC_MAGIC_VALID(view, DNS_VIEW_MAGIC)
+
+#define DNS_VIEWATTR_RESSHUTDOWN	0x01
+#define DNS_VIEWATTR_ADBSHUTDOWN	0x02
+#define DNS_VIEWATTR_REQSHUTDOWN	0x04
+
+isc_result_t
+dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
+		const char *name, dns_view_t **viewp);
+/*%<
+ * Create a view.
+ *
+ * Notes:
+ *
+ *\li	The newly created view has no cache, no resolver, and an empty
+ *	zone table.  The view is not frozen.
+ *
+ * Requires:
+ *
+ *\li	'mctx' is a valid memory context.
+ *
+ *\li	'rdclass' is a valid class.
+ *
+ *\li	'name' is a valid C string.
+ *
+ *\li	viewp != NULL && *viewp == NULL
+ *
+ * Returns:
+ *
+ *\li	#ISC_R_SUCCESS
+ *\li	#ISC_R_NOMEMORY
+ *
+ *\li	Other errors are possible.
+ */
+
+void
+dns_view_attach(dns_view_t *source, dns_view_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li	'source' is a valid, frozen view.
+ *
+ *\li	'targetp' points to a NULL dns_view_t *.
+ *
+ * Ensures:
+ *
+ *\li	*targetp is attached to source.
+ *
+ *\li	While *targetp is attached, the view will not shut down.
+ */
+
+void
+dns_view_detach(dns_view_t **viewp);
+/*%<
+ * Detach '*viewp' from its view.
+ *
+ * Requires:
+ *
+ *\li	'viewp' points to a valid dns_view_t *
+ *
+ * Ensures:
+ *
+ *\li	*viewp is NULL.
+ */
+
+void
+dns_view_flushanddetach(dns_view_t **viewp);
+/*%<
+ * Detach '*viewp' from its view.  If this was the last reference
+ * uncommitted changed in zones will be flushed to disk.
+ *
+ * Requires:
+ *
+ *\li	'viewp' points to a valid dns_view_t *
+ *
+ * Ensures:
+ *
+ *\li	*viewp is NULL.
+ */
+
+void
+dns_view_weakattach(dns_view_t *source, dns_view_t **targetp);
+/*%<
+ * Weakly attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li	'source' is a valid, frozen view.
+ *
+ *\li	'targetp' points to a NULL dns_view_t *.
+ *
+ * Ensures:
+ *
+ *\li	*targetp is attached to source.
+ *
+ * \li	While *targetp is attached, the view will not be freed.
+ */
+
+void
+dns_view_weakdetach(dns_view_t **targetp);
+/*%<
+ * Detach '*viewp' from its view.
+ *
+ * Requires:
+ *
+ *\li	'viewp' points to a valid dns_view_t *.
+ *
+ * Ensures:
+ *
+ *\li	*viewp is NULL.
+ */
+
+isc_result_t
+dns_view_createresolver(dns_view_t *view,
+			isc_taskmgr_t *taskmgr, unsigned int ntasks,
+			isc_socketmgr_t *socketmgr,
+			isc_timermgr_t *timermgr,
+			unsigned int options,
+			dns_dispatchmgr_t *dispatchmgr,
+			dns_dispatch_t *dispatchv4,
+			dns_dispatch_t *dispatchv6);
+/*%<
+ * Create a resolver and address database for the view.
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid, unfrozen view.
+ *
+ *\li	'view' does not have a resolver already.
+ *
+ *\li	The requirements of dns_resolver_create() apply to 'taskmgr',
+ *	'ntasks', 'socketmgr', 'timermgr', 'options', 'dispatchv4', and
+ *	'dispatchv6'.
+ *
+ * Returns:
+ *
+ *\li   	#ISC_R_SUCCESS
+ *
+ *\li	Any error that dns_resolver_create() can return.
+ */
+
+void
+dns_view_setcache(dns_view_t *view, dns_cache_t *cache);
+void
+dns_view_setcache2(dns_view_t *view, dns_cache_t *cache, isc_boolean_t shared);
+/*%<
+ * Set the view's cache database.  If 'shared' is true, this means the cache
+ * is created by another view and is shared with that view.  dns_view_setcache()
+ * is a backward compatible version equivalent to setcache2(..., ISC_FALSE).
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid, unfrozen view.
+ *
+ *\li	'cache' is a valid cache.
+ *
+ * Ensures:
+ *
+ * \li    	The cache of 'view' is 'cached.
+ *
+ *\li	If this is not the first call to dns_view_setcache() for this
+ *	view, then previously set cache is detached.
+ */
+
+void
+dns_view_sethints(dns_view_t *view, dns_db_t *hints);
+/*%<
+ * Set the view's hints database.
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid, unfrozen view, whose hints database has not been
+ *	set.
+ *
+ *\li	'hints' is a valid zone database.
+ *
+ * Ensures:
+ *
+ * \li    	The hints database of 'view' is 'hints'.
+ */
+
+void
+dns_view_setkeyring(dns_view_t *view, dns_tsig_keyring_t *ring);
+void
+dns_view_setdynamickeyring(dns_view_t *view, dns_tsig_keyring_t *ring);
+/*%<
+ * Set the view's static TSIG keys
+ *
+ * Requires:
+ *
+ *   \li   'view' is a valid, unfrozen view, whose static TSIG keyring has not
+ *	been set.
+ *
+ *\li      'ring' is a valid TSIG keyring
+ *
+ * Ensures:
+ *
+ *\li      The static TSIG keyring of 'view' is 'ring'.
+ */
+
+void
+dns_view_getdynamickeyring(dns_view_t *view, dns_tsig_keyring_t **ringp);
+/*%<
+ * Return the views dynamic keys.
+ *
+ *   \li  'view' is a valid, unfrozen view.
+ *   \li  'ringp' != NULL && ringp == NULL.
+ */
+
+void
+dns_view_setdstport(dns_view_t *view, in_port_t dstport);
+/*%<
+ * Set the view's destination port.  This is the port to
+ * which outgoing queries are sent.  The default is 53,
+ * the standard DNS port.
+ *
+ * Requires:
+ *
+ *\li      'view' is a valid view.
+ *
+ *\li      'dstport' is a valid TCP/UDP port number.
+ *
+ * Ensures:
+ *\li	External name servers will be assumed to be listening
+ *	on 'dstport'.  For servers whose address has already
+ *	obtained obtained at the time of the call, the view may
+ *	continue to use the previously set port until the address
+ *	times out from the view's address database.
+ */
+
+
+isc_result_t
+dns_view_addzone(dns_view_t *view, dns_zone_t *zone);
+/*%<
+ * Add zone 'zone' to 'view'.
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid, unfrozen view.
+ *
+ *\li	'zone' is a valid zone.
+ */
+
+void
+dns_view_freeze(dns_view_t *view);
+/*%<
+ * Freeze view.  No changes can be made to view configuration while frozen.
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid, unfrozen view.
+ *
+ * Ensures:
+ *
+ *\li	'view' is frozen.
+ */
+
+void
+dns_view_thaw(dns_view_t *view);
+/*%<
+ * Thaw view.  This allows zones to be added or removed at runtime.  This is
+ * NOT thread-safe; the caller MUST have run isc_task_exclusive() prior to
+ * thawing the view.
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid, frozen view.
+ *
+ * Ensures:
+ *
+ *\li	'view' is no longer frozen.
+ */
+isc_result_t
+dns_view_find(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
+	      isc_stdtime_t now, unsigned int options, isc_boolean_t use_hints,
+	      dns_db_t **dbp, dns_dbnode_t **nodep, dns_name_t *foundname,
+	      dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+isc_result_t
+dns_view_find2(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
+	       isc_stdtime_t now, unsigned int options,
+	       isc_boolean_t use_hints, isc_boolean_t use_static_stub,
+	       dns_db_t **dbp, dns_dbnode_t **nodep, dns_name_t *foundname,
+	       dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find an rdataset whose owner name is 'name', and whose type is
+ * 'type'.
+ * In general, this function first searches view's zone and cache DBs for the
+ * best match data against 'name'.  If nothing found there, and if 'use_hints'
+ * is ISC_TRUE, the view's hint DB (if configured) is searched.
+ * If the view is configured with a static-stub zone which gives the longest
+ * match for 'name' among the zones, however, the cache DB is not consulted
+ * unless 'use_static_stub' is ISC_FALSE (see below about this argument).
+ *
+ * dns_view_find() is a backward compatible version equivalent to
+ * dns_view_find2() with use_static_stub argument being ISC_FALSE.
+ *
+ * Notes:
+ *
+ *\li	See the description of dns_db_find() for information about 'options'.
+ *	If the caller sets #DNS_DBFIND_GLUEOK, it must ensure that 'name'
+ *	and 'type' are appropriate for glue retrieval.
+ *
+ *\li	If 'now' is zero, then the current time will be used.
+ *
+ *\li	If 'use_hints' is ISC_TRUE, and the view has a hints database, then
+ *	it will be searched last.  If the answer is found in the hints
+ *	database, the result code will be DNS_R_HINT.  If the name is found
+ *	in the hints database but not the type, the result code will be
+ *	#DNS_R_HINTNXRRSET.
+ *
+ *\li	If 'use_static_stub' is ISC_FALSE and the longest match zone for 'name'
+ *	is a static-stub zone, it's ignored and the cache and/or hints will be
+ *	searched.  In the majority of the cases this argument should be
+ *	ISC_FALSE.  The only known usage of this argument being ISC_TRUE is
+ *	if this search is for a "bailiwick" glue A or AAAA RRset that may
+ *	best match a static-stub zone.  Consider the following example:
+ *	this view is configured with a static-stub zone "example.com",
+ *	and an attempt of recursive resolution needs to send a query for the
+ *	zone.  In this case it's quite likely that the resolver is trying to
+ *	find A/AAAA RRs for the apex name "example.com".  And, to honor the
+ *	static-stub configuration it needs to return the glue RRs in the
+ *	static-stub zone even if that exact RRs coming from the authoritative
+ *	zone has been cached.
+ *	In other general cases, the requested data is better to be
+ *	authoritative, either locally configured or retrieved from an external
+ *	server, and the data in the static-stub zone should better be ignored.
+ *
+ *\li	'foundname' must meet the requirements of dns_db_find().
+ *
+ *\li	If 'sigrdataset' is not NULL, and there is a SIG rdataset which
+ *	covers 'type', then 'sigrdataset' will be bound to it.
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid, frozen view.
+ *
+ *\li	'name' is valid name.
+ *
+ *\li	'type' is a valid dns_rdatatype_t, and is not a meta query type
+ *	except dns_rdatatype_any.
+ *
+ *\li	dbp == NULL || *dbp == NULL
+ *
+ *\li	nodep == NULL || *nodep == NULL.  If nodep != NULL, dbp != NULL.
+ *
+ *\li	'foundname' is a valid name with a dedicated buffer or NULL.
+ *
+ *\li	'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li	'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *
+ *\li	In successful cases, 'rdataset', and possibly 'sigrdataset', are
+ *	bound to the found data.
+ *
+ *\li	If dbp != NULL, it points to the database containing the data.
+ *
+ *\li	If nodep != NULL, it points to the database node containing the data.
+ *
+ *\li	If foundname != NULL, it contains the full name of the found data.
+ *
+ * Returns:
+ *
+ *\li	Any result that dns_db_find() can return, with the exception of
+ *	#DNS_R_DELEGATION.
+ */
+
+isc_result_t
+dns_view_simplefind(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
+		    isc_stdtime_t now, unsigned int options,
+		    isc_boolean_t use_hints,
+		    dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find an rdataset whose owner name is 'name', and whose type is
+ * 'type'.
+ *
+ * Notes:
+ *
+ *\li	This routine is appropriate for simple, exact-match queries of the
+ *	view.  'name' must be a canonical name; there is no DNAME or CNAME
+ *	processing.
+ *
+ *\li	See the description of dns_db_find() for information about 'options'.
+ *	If the caller sets DNS_DBFIND_GLUEOK, it must ensure that 'name'
+ *	and 'type' are appropriate for glue retrieval.
+ *
+ *\li	If 'now' is zero, then the current time will be used.
+ *
+ *\li	If 'use_hints' is ISC_TRUE, and the view has a hints database, then
+ *	it will be searched last.  If the answer is found in the hints
+ *	database, the result code will be DNS_R_HINT.  If the name is found
+ *	in the hints database but not the type, the result code will be
+ *	DNS_R_HINTNXRRSET.
+ *
+ *\li	If 'sigrdataset' is not NULL, and there is a SIG rdataset which
+ *	covers 'type', then 'sigrdataset' will be bound to it.
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid, frozen view.
+ *
+ *\li	'name' is valid name.
+ *
+ *\li	'type' is a valid dns_rdatatype_t, and is not a meta query type
+ *	(e.g. dns_rdatatype_any), or dns_rdatatype_rrsig.
+ *
+ *\li	'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li	'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ * Ensures:
+ *
+ *\li	In successful cases, 'rdataset', and possibly 'sigrdataset', are
+ *	bound to the found data.
+ *
+ * Returns:
+ *
+ *\li	#ISC_R_SUCCESS			Success; result is desired type.
+ *\li	DNS_R_GLUE			Success; result is glue.
+ *\li	DNS_R_HINT			Success; result is a hint.
+ *\li	DNS_R_NCACHENXDOMAIN		Success; result is a ncache entry.
+ *\li	DNS_R_NCACHENXRRSET		Success; result is a ncache entry.
+ *\li	DNS_R_NXDOMAIN			The name does not exist.
+ *\li	DNS_R_NXRRSET			The rrset does not exist.
+ *\li	#ISC_R_NOTFOUND			No matching data found,
+ *					or an error occurred.
+ */
+
+/*% See dns_view_findzonecut2() */
+isc_result_t
+dns_view_findzonecut(dns_view_t *view, dns_name_t *name, dns_name_t *fname,
+		     isc_stdtime_t now, unsigned int options,
+		     isc_boolean_t use_hints,
+		     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+
+isc_result_t
+dns_view_findzonecut2(dns_view_t *view, dns_name_t *name, dns_name_t *fname,
+		      isc_stdtime_t now, unsigned int options,
+		      isc_boolean_t use_hints, isc_boolean_t use_cache,
+		      dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+/*%<
+ * Find the best known zonecut containing 'name'.
+ *
+ * This uses local authority, cache, and optionally hints data.
+ * No external queries are performed.
+ *
+ * Notes:
+ *
+ *\li	If 'now' is zero, then the current time will be used.
+ *
+ *\li	If 'use_hints' is ISC_TRUE, and the view has a hints database, then
+ *	it will be searched last.
+ *
+ *\li	If 'use_cache' is ISC_TRUE, and the view has a cache, then it will be
+ *	searched.
+ *
+ *\li	If 'sigrdataset' is not NULL, and there is a SIG rdataset which
+ *	covers 'type', then 'sigrdataset' will be bound to it.
+ *
+ *\li	If the DNS_DBFIND_NOEXACT option is set, then the zonecut returned
+ *	(if any) will be the deepest known ancestor of 'name'.
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid, frozen view.
+ *
+ *\li	'name' is valid name.
+ *
+ *\li	'rdataset' is a valid, disassociated rdataset.
+ *
+ *\li	'sigrdataset' is NULL, or is a valid, disassociated rdataset.
+ *
+ * Returns:
+ *
+ *\li	#ISC_R_SUCCESS				Success.
+ *
+ *\li	Many other results are possible.
+ */
+
+isc_result_t
+dns_viewlist_find(dns_viewlist_t *list, const char *name,
+		  dns_rdataclass_t rdclass, dns_view_t **viewp);
+/*%<
+ * Search for a view with name 'name' and class 'rdclass' in 'list'.
+ * If found, '*viewp' is (strongly) attached to it.
+ *
+ * Requires:
+ *
+ *\li	'viewp' points to a NULL dns_view_t *.
+ *
+ * Returns:
+ *
+ *\li	#ISC_R_SUCCESS		A matching view was found.
+ *\li	#ISC_R_NOTFOUND		No matching view was found.
+ */
+
+isc_result_t
+dns_viewlist_findzone(dns_viewlist_t *list, dns_name_t *name, isc_boolean_t allclasses,
+		      dns_rdataclass_t rdclass, dns_zone_t **zonep);
+
+/*%<
+ * Search zone with 'name' in view with 'rdclass' in viewlist 'list'
+ * If found, zone is returned in *zonep. If allclasses is set rdclass is ignored
+ *
+ * Returns:
+ *\li	#ISC_R_SUCCESS          A matching zone was found.
+ *\li	#ISC_R_NOTFOUND         No matching zone was found.
+ */
+
+isc_result_t
+dns_view_findzone(dns_view_t *view, dns_name_t *name, dns_zone_t **zonep);
+/*%<
+ * Search for the zone 'name' in the zone table of 'view'.
+ * If found, 'zonep' is (strongly) attached to it.  There
+ * are no partial matches.
+ *
+ * Requires:
+ *
+ *\li	'zonep' points to a NULL dns_zone_t *.
+ *
+ * Returns:
+ *\li	#ISC_R_SUCCESS		A matching zone was found.
+ *\li	#ISC_R_NOTFOUND		No matching zone was found.
+ *\li	others			An error occurred.
+ */
+
+isc_result_t
+dns_view_load(dns_view_t *view, isc_boolean_t stop);
+
+isc_result_t
+dns_view_loadnew(dns_view_t *view, isc_boolean_t stop);
+/*%<
+ * Load zones attached to this view.  dns_view_load() loads
+ * all zones whose master file has changed since the last
+ * load; dns_view_loadnew() loads only zones that have never
+ * been loaded.
+ *
+ * If 'stop' is ISC_TRUE, stop on the first error and return it.
+ * If 'stop' is ISC_FALSE, ignore errors.
+ *
+ * Requires:
+ *
+ *\li	'view' is valid.
+ */
+
+isc_result_t
+dns_view_gettsig(dns_view_t *view, dns_name_t *keyname,
+		 dns_tsigkey_t **keyp);
+/*%<
+ * Find the TSIG key configured in 'view' with name 'keyname',
+ * if any.
+ *
+ * Requires:
+ *\li	keyp points to a NULL dns_tsigkey_t *.
+ *
+ * Returns:
+ *\li	#ISC_R_SUCCESS	A key was found and '*keyp' now points to it.
+ *\li	#ISC_R_NOTFOUND	No key was found.
+ *\li	others		An error occurred.
+ */
+
+isc_result_t
+dns_view_getpeertsig(dns_view_t *view, isc_netaddr_t *peeraddr,
+		     dns_tsigkey_t **keyp);
+/*%<
+ * Find the TSIG key configured in 'view' for the server whose
+ * address is 'peeraddr', if any.
+ *
+ * Requires:
+ *	keyp points to a NULL dns_tsigkey_t *.
+ *
+ * Returns:
+ *\li	#ISC_R_SUCCESS	A key was found and '*keyp' now points to it.
+ *\li	#ISC_R_NOTFOUND	No key was found.
+ *\li	others		An error occurred.
+ */
+
+isc_result_t
+dns_view_checksig(dns_view_t *view, isc_buffer_t *source, dns_message_t *msg);
+/*%<
+ * Verifies the signature of a message.
+ *
+ * Requires:
+ *
+ *\li	'view' is a valid view.
+ *\li	'source' is a valid buffer containing the message
+ *\li	'msg' is a valid message
+ *
+ * Returns:
+ *\li	see dns_tsig_verify()
+ */
+
+void
+dns_view_dialup(dns_view_t *view);
+/*%<
+ * Perform dialup-time maintenance on the zones of 'view'.
+ */
+
+isc_result_t
+dns_view_dumpdbtostream(dns_view_t *view, FILE *fp);
+/*%<
+ * Dump the current state of the view 'view' to the stream 'fp'
+ * for purposes of analysis or debugging.
+ *
+ * Currently the dumped state includes the view's cache; in the future
+ * it may also include other state such as the address database.
+ * It will not not include authoritative data since it is voluminous and
+ * easily obtainable by other means.
+ *
+ * Requires:
+ *
+ *\li	'view' is valid.
+ *
+ *\li	'fp' refers to a file open for writing.
+ *
+ * Returns:
+ * \li	ISC_R_SUCCESS	The cache was successfully dumped.
+ * \li	others		An error occurred (see dns_master_dump)
+ */
+
+isc_result_t
+dns_view_flushcache(dns_view_t *view);
+isc_result_t
+dns_view_flushcache2(dns_view_t *view, isc_boolean_t fixuponly);
+/*%<
+ * Flush the view's cache (and ADB).  If 'fixuponly' is true, it only updates
+ * the internal reference to the cache DB with omitting actual flush operation.
+ * 'fixuponly' is intended to be used for a view that shares a cache with
+ * a different view.  dns_view_flushcache() is a backward compatible version
+ * that always sets fixuponly to false.
+ *
+ * Requires:
+ * 	'view' is valid.
+ *
+ * 	No other tasks are executing.
+ *
+ * Returns:
+ *\li	#ISC_R_SUCCESS
+ *\li	#ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_view_flushname(dns_view_t *view, dns_name_t *);
+/*%<
+ * Flush the given name from the view's cache (and ADB).
+ *
+ * Requires:
+ *\li	'view' is valid.
+ *\li	'name' is valid.
+ *
+ * Returns:
+ *\li	#ISC_R_SUCCESS
+ *	other returns are failures.
+ */
+
+isc_result_t
+dns_view_adddelegationonly(dns_view_t *view, dns_name_t *name);
+/*%<
+ * Add the given name to the delegation only table.
+ *
+ *
+ * Requires:
+ *\li	'view' is valid.
+ *\li	'name' is valid.
+ *
+ * Returns:
+ *\li	#ISC_R_SUCCESS
+ *\li	#ISC_R_NOMEMORY
+ */
+
+isc_result_t
+dns_view_excludedelegationonly(dns_view_t *view, dns_name_t *name);
+/*%<
+ * Add the given name to be excluded from the root-delegation-only.
+ *
+ *
+ * Requires:
+ *\li	'view' is valid.
+ *\li	'name' is valid.
+ *
+ * Returns:
+ *\li	#ISC_R_SUCCESS
+ *\li	#ISC_R_NOMEMORY
+ */
+
+isc_boolean_t
+dns_view_isdelegationonly(dns_view_t *view, dns_name_t *name);
+/*%<
+ * Check if 'name' is in the delegation only table or if
+ * rootdelonly is set that name is not being excluded.
+ *
+ * Requires:
+ *\li	'view' is valid.
+ *\li	'name' is valid.
+ *
+ * Returns:
+ *\li	#ISC_TRUE if the name is the table.
+ *\li	#ISC_FALSE otherwise.
+ */
+
+void
+dns_view_setrootdelonly(dns_view_t *view, isc_boolean_t value);
+/*%<
+ * Set the root delegation only flag.
+ *
+ * Requires:
+ *\li	'view' is valid.
+ */
+
+isc_boolean_t
+dns_view_getrootdelonly(dns_view_t *view);
+/*%<
+ * Get the root delegation only flag.
+ *
+ * Requires:
+ *\li	'view' is valid.
+ */
+
+isc_result_t
+dns_view_freezezones(dns_view_t *view, isc_boolean_t freeze);
+/*%<
+ * Freeze/thaw updates to master zones.
+ *
+ * Requires:
+ * \li	'view' is valid.
+ */
+
+void
+dns_view_setresstats(dns_view_t *view, isc_stats_t *stats);
+/*%<
+ * Set a general resolver statistics counter set 'stats' for 'view'.
+ *
+ * Requires:
+ * \li	'view' is valid and is not frozen.
+ *
+ *\li	stats is a valid statistics supporting resolver statistics counters
+ *	(see dns/stats.h).
+ */
+
+void
+dns_view_getresstats(dns_view_t *view, isc_stats_t **statsp);
+/*%<
+ * Get the general statistics counter set for 'view'.  If a statistics set is
+ * set '*statsp' will be attached to the set; otherwise, '*statsp' will be
+ * untouched.
+ *
+ * Requires:
+ * \li	'view' is valid and is not frozen.
+ *
+ *\li	'statsp' != NULL && '*statsp' != NULL
+ */
+
+void
+dns_view_setresquerystats(dns_view_t *view, dns_stats_t *stats);
+/*%<
+ * Set a statistics counter set of rdata type, 'stats', for 'view'.  Once the
+ * statistic set is installed, view's resolver will count outgoing queries
+ * per rdata type.
+ *
+ * Requires:
+ * \li	'view' is valid and is not frozen.
+ *
+ *\li	stats is a valid statistics created by dns_rdatatypestats_create().
+ */
+
+void
+dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp);
+/*%<
+ * Get the rdatatype statistics counter set for 'view'.  If a statistics set is
+ * set '*statsp' will be attached to the set; otherwise, '*statsp' will be
+ * untouched.
+ *
+ * Requires:
+ * \li	'view' is valid and is not frozen.
+ *
+ *\li	'statsp' != NULL && '*statsp' != NULL
+ */
+
+isc_boolean_t
+dns_view_iscacheshared(dns_view_t *view);
+/*%<
+ * Check if the view shares the cache created by another view.
+ *
+ * Requires:
+ * \li	'view' is valid.
+ *
+ * Returns:
+ *\li	#ISC_TRUE if the cache is shared.
+ *\li	#ISC_FALSE otherwise.
+ */
+
+isc_result_t
+dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx);
+/*%<
+ * Initialize security roots for the view.  (Note that secroots is
+ * NULL until this function is called, so any function using
+ * secroots must check its validity first.  One way to do this is
+ * use dns_view_getsecroots() and check its return value.)
+ *
+ * Requires:
+ * \li	'view' is valid.
+ * \li	'view->secroots' is NULL.
+ *
+ * Returns:
+ *\li	ISC_R_SUCCESS
+ *\li	Any other result indicates failure
+ */
+
+isc_result_t
+dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp);
+/*%<
+ * Get the security roots for this view.  Returns ISC_R_NOTFOUND if
+ * the security roots keytable has not been initialized for the view.
+ *
+ * '*ktp' is attached on success; the caller is responsible for
+ * detaching it with dns_keytable_detach().
+ *
+ * Requires:
+ * \li	'view' is valid.
+ * \li	'ktp' is not NULL and '*ktp' is NULL.
+ *
+ * Returns:
+ *\li	ISC_R_SUCCESS
+ *\li	ISC_R_NOTFOUND
+ */
+
+isc_result_t
+dns_view_issecuredomain(dns_view_t *view, dns_name_t *name,
+			 isc_boolean_t *secure_domain);
+/*%<
+ * Is 'name' at or beneath a trusted key?  Put answer in
+ * '*secure_domain'.
+ *
+ * Requires:
+ * \li	'view' is valid.
+ *
+ * Returns:
+ *\li	ISC_R_SUCCESS
+ *\li	Any other value indicates failure
+ */
+
+void
+dns_view_untrust(dns_view_t *view, dns_name_t *keyname,
+		 dns_rdata_dnskey_t *dnskey, isc_mem_t *mctx);
+/*%<
+ * Remove keys that match 'keyname' and 'dnskey' from the views trust
+ * anchors.
+ *
+ * Requires:
+ * \li	'view' is valid.
+ * \li	'keyname' is valid.
+ * \li	'mctx' is valid.
+ * \li	'dnskey' is valid.
+ */
+
+void
+dns_view_setnewzones(dns_view_t *view, isc_boolean_t allow, void *cfgctx,
+		     void (*cfg_destroy)(void **));
+/*%<
+ * Set whether or not to allow zones to be created or deleted at runtime.
+ *
+ * If 'allow' is ISC_TRUE, determines the filename into which new zone
+ * configuration will be written.  Preserves the configuration context
+ * (a pointer to which is passed in 'cfgctx') for use when parsing new
+ * zone configuration.  'cfg_destroy' points to a callback routine to
+ * destroy the configuration context when the view is destroyed.  (This
+ * roundabout method is used in order to avoid libdns having a dependency
+ * on libisccfg and libbind9.)
+ *
+ * If 'allow' is ISC_FALSE, removes any existing references to
+ * configuration context and frees any memory.
+ *
+ * Requires:
+ * \li 'view' is valid.
+ */
+
+void
+dns_view_restorekeyring(dns_view_t *view);
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_VIEW_H */
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
@@ -171,14 +171,9 @@
 					   dns_dbversion_t *version);
 	isc_boolean_t	(*isdnssec)(dns_db_t *db);
 	dns_stats_t	*(*getrrsetstats)(dns_db_t *db);
-	void		(*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);
 } dns_dbmethods_t;
 
 typedef isc_result_t
@@ -1501,29 +1496,16 @@
  */
 
 void
-dns_db_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st);
+dns_db_rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num);
 /*%<
- * See if a policy database has DNS_RPZ_TYPE_IP, DNS_RPZ_TYPE_NSIP, or
- * DNS_RPZ_TYPE_NSDNAME records.
+ * 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2012  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,7 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id$ */
 
 #ifndef DNS_RPZ_H
 #define DNS_RPZ_H 1
@@ -24,15 +23,17 @@
 #include <dns/fixedname.h>
 #include <dns/rdata.h>
 #include <dns/types.h>
+#include <isc/refcount.h>
 
 ISC_LANG_BEGINDECLS
 
-#define DNS_RPZ_IP_ZONE		"rpz-ip"
-#define DNS_RPZ_NSIP_ZONE	"rpz-nsip"
-#define DNS_RPZ_NSDNAME_ZONE	"rpz-nsdname"
-#define DNS_RPZ_PASSTHRU_ZONE	"rpz-passthru"
+#define DNS_RPZ_PREFIX		"rpz-"
+#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"
 
-typedef isc_uint8_t		dns_rpz_cidr_bits_t;
+typedef isc_uint8_t		dns_rpz_prefix_t;
 
 typedef enum {
 	DNS_RPZ_TYPE_BAD,
@@ -49,7 +50,7 @@
  */
 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 */
@@ -60,27 +61,109 @@
 	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_INVALID_NUM DNS_RPZ_MAX_ZONES
+
+#define DNS_RPZ_ZBIT(n)	    (((dns_rpz_zbits_t)1) << (dns_rpz_num_t)(n))
+
+/*
+ * Mask of the specified and higher numbered policy zones
+ * Avoid hassles with (1<<33) or (1<<65)
+ */
+#define DNS_RPZ_ZMASK(n)    ((dns_rpz_zbits_t)((((n) >= DNS_RPZ_MAX_ZONES-1) ? \
+						0 : (1<<((n)+1))) -1))
+
 /*
  * Specify a 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_refcount_t	refs;
+	dns_rpz_num_t	num;		/* ordinal in list of policy zones */
+	dns_name_t	origin;		/* Policy zone name */
+	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_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 */
 };
 
 /*
- * 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_num dns_rpz_zones_num_t;
+struct dns_rpz_zones_num {
+	int		nsdname;
+	int		qname;
+	int		ipv4;
+	int		ipv6;
+	int		nsipv4;
+	int		nsipv6;
+};
+
+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;
+		unsigned int	    min_ns_labels;
+		dns_rpz_num_t	    cnt;
+	} 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  valid=0
+	 *	during initial loading	    load_begun=1  valid=0
+	 *				and rbtdb->rpzsp == rbtdb->load_rpzsp
+	 *	after good load		    load_begun=1  valid=1
+	 *	after failed initial load   load_begun=1  valid=0
+	 *				and rbtdb->load_rpzsp == NULL
+	 *	reloading after failure	    load_begun=1  valid=0
+	 *	reloading after success
+	 *		main rpzs	    load_begun=1  valid=1
+	 *		load rpzs	    load_begun=1  valid=0
+	 */
+	dns_rpz_zbits_t		load_begun;
+	dns_rpz_zbits_t		valid;
+
+	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;
+
+	dns_rpz_zones_num_t	num;
+};
+
 
 /*
  * context for finding the best policy
@@ -91,19 +174,15 @@
 # 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_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 */
 	/*
 	 * 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;
@@ -138,10 +217,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;
@@ -168,38 +252,41 @@
 const char *
 dns_rpz_policy2str(dns_rpz_policy_t policy);
 
-void
-dns_rpz_set_need(isc_boolean_t need);
+dns_rpz_policy_t
+dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset,
+		     dns_name_t *selfname);
 
-isc_boolean_t
-dns_rpz_needed(void);
+isc_result_t
+dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, isc_mem_t *mctx);
 
 void
-dns_rpz_cidr_free(dns_rpz_cidr_t **cidr);
+dns_rpz_attach_rpzs(dns_rpz_zones_t *source, dns_rpz_zones_t **target);
 
 void
-dns_rpz_view_destroy(dns_view_t *view);
+dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp);
 
 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(dns_rpz_cidr_t *cidr, dns_rpz_st_t *st);
-
-void
-dns_rpz_cidr_deleteip(dns_rpz_cidr_t *cidr, dns_name_t *name);
+dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp,
+		  dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num);
 
-void
-dns_rpz_cidr_addip(dns_rpz_cidr_t *cidr, dns_name_t *name);
+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_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_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *name);
 
-dns_rpz_policy_t
-dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset,
-		     dns_name_t *selfname);
+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,260 @@
+/*
+ * Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+#ifndef DNS_RRL_H
+#define DNS_RRL_H 1
+
+/*
+ * Rate limit DNS responses.
+ */
+
+#include <isc/lang.h>
+
+#include <dns/fixedname.h>
+#include <dns/rdata.h>
+#include <dns/types.h>
+
+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_NXDOMAIN,
+	DNS_RRL_RTYPE_ERROR,
+	DNS_RRL_RTYPE_ALL,
+	DNS_RRL_RTYPE_TCP,
+} dns_rrl_rtype_t;
+
+/*
+ * A rate limit bucket key.
+ * This should be small to limit the total size of the database.
+ * The hash of the qname should be wide enough to make the probability
+ * of collisions among requests from a single IP address block less than 50%.
+ * We need a 32-bit hash value for 10000 qps (e.g. random qnames forged
+ * by attacker) to collide with legitimate qnames from the target with
+ * probability at most 1%.
+ */
+#define DNS_RRL_MAX_PREFIX  64
+typedef union dns_rrl_key dns_rrl_key_t;
+union dns_rrl_key {
+	struct {
+		isc_uint32_t	    ip[DNS_RRL_MAX_PREFIX/32];
+		isc_uint32_t	    qname_hash;
+		dns_rdatatype_t	    qtype;
+		isc_uint8_t	    qclass;
+		dns_rrl_rtype_t	    rtype   :3;
+		isc_boolean_t	    ipv6    :1;
+	} s;
+	isc_uint16_t	w[1];
+};
+
+/*
+ * A rate-limit entry.
+ * This should be small to limit the total size of the table of entries.
+ */
+typedef struct dns_rrl_entry dns_rrl_entry_t;
+typedef ISC_LIST(dns_rrl_entry_t) dns_rrl_bin_t;
+struct dns_rrl_entry {
+	ISC_LINK(dns_rrl_entry_t) lru;
+	ISC_LINK(dns_rrl_entry_t) hlink;
+	dns_rrl_key_t	key;
+# define DNS_RRL_RESPONSE_BITS	24
+	signed int	responses   :DNS_RRL_RESPONSE_BITS;
+	isc_boolean_t	logged	    :1;
+# 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;
+# define DNS_RRL_MAX_SLIP	10
+	unsigned int	slip_cnt    :4;
+# define DNS_RRL_TS_BITS	10
+	unsigned int	ts	    :DNS_RRL_TS_BITS;
+	unsigned int	log_secs    :DNS_RRL_TS_BITS;
+};
+
+#define DNS_RRL_MAX_TIME_TRAVEL	5
+#define DNS_RRL_FOREVER		(1<<DNS_RRL_RESPONSE_BITS)
+#define DNS_RRL_MAX_TS		((1<<DNS_RRL_TS_BITS) - 1)
+
+#define DNS_RRL_MAX_RESPONSES	((1<<(DNS_RRL_RESPONSE_BITS-1))-1)
+#define DNS_RRL_MAX_WINDOW	600
+#if DNS_RRL_MAX_WINDOW >= DNS_RRL_MAX_TS
+#error "DNS_RRL_MAX_WINDOW is too large"
+#endif
+#define DNS_RRL_MAX_RATE	(DNS_RRL_MAX_RESPONSES / DNS_RRL_MAX_WINDOW)
+
+#define DNS_RRL_MAX_LOG_SECS	900
+#define DNS_RRL_STOP_LOG_SECS	60
+#if DNS_RRL_MAX_LOG_SECS >= (1<<DNS_RRL_TS_BITS)
+#error "DNS_RRL_MAX_LOG_SECS is too large"
+#endif
+
+
+/*
+ * A hash table of rate-limit entries.
+ */
+struct dns_rrl_hash {
+	isc_stdtime_t	check_time;
+	unsigned int	gen	    :DNS_RRL_HASH_GEN_BITS;
+	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 buffer.
+ */
+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;
+
+	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;
+	unsigned int	hash_gen;
+
+	unsigned int	ts_gen;
+# define DNS_RRL_TS_BASES   (1<<DNS_RRL_TS_GEN_BITS)
+	isc_stdtime_t	ts_bases[DNS_RRL_TS_BASES];
+
+	int		ipv4_prefixlen;
+	isc_uint32_t	ipv4_mask;
+	int		ipv6_prefixlen;
+	isc_uint32_t	ipv6_mask[4];
+
+	isc_stdtime_t	log_stops_time;
+	dns_rrl_entry_t	*last_logged;
+	int		num_logged;
+	int		num_qnames;
+	ISC_LIST(dns_rrl_qname_buf_t) qname_free;
+# define DNS_RRL_QNAMES	    (1<<DNS_RRL_QNAMES_BITS)
+	dns_rrl_qname_buf_t *qnames[DNS_RRL_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 <dns/acl.h>
 #include <dns/fixedname.h>
+#include <dns/rrl.h>
 #include <dns/rdatastruct.h>
 #include <dns/rpz.h>
 #include <dns/types.h>
@@ -141,6 +142,7 @@
 	dns_rbt_t *			answeracl_exclude;
 	dns_rbt_t *			denyanswernames;
 	dns_rbt_t *			answernames_exclude;
+	dns_rrl_t *			rrl;
 	isc_boolean_t			requestixfr;
 	isc_boolean_t			provideixfr;
 	isc_boolean_t			requestnsid;
@@ -161,9 +163,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;
+	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
@@ -34,6 +34,7 @@
 
 #include <dns/masterdump.h>
 #include <dns/rdatastruct.h>
+#include <dns/rpz.h>
 #include <dns/types.h>
 
 typedef enum {
@@ -1898,6 +1899,16 @@
  * maintenance timer.
  */
 
+isc_result_t
+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.
+ */
+
+dns_rpz_num_t
+dns_zone_get_rpz_num(dns_zone_t *zone);
+
 ISC_LANG_ENDDECLS
 
 #endif /* DNS_ZONE_H */
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.cnt);
+		dns_rpz_detach_rpzs(&rbtdb->load_rpzs);
+	}
+	if (rbtdb->rpzs != NULL) {
+		REQUIRE(rbtdb->rpz_num < rbtdb->rpzs->p.cnt);
+		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);
@@ -1550,14 +1562,15 @@
 					      DNS_LOGCATEGORY_DATABASE,
 					      DNS_LOGMODULE_CACHE,
 					      ISC_LOG_WARNING,
-					      "delete_nsecnode(): "
+					      "delete_node(): "
 					      "dns_rbt_deletenode(nsecnode): %s",
 					      isc_result_totext(result));
 			}
 		}
 		result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE);
 #ifdef BIND9
-		dns_rpz_cidr_deleteip(rbtdb->rpz_cidr, name);
+		if (rbtdb->rpzs != NULL)
+			dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name);
 #endif
 		break;
 	case DNS_RBT_NSEC_NSEC:
@@ -1572,7 +1585,7 @@
 			      DNS_LOGCATEGORY_DATABASE,
 			      DNS_LOGMODULE_CACHE,
 			      ISC_LOG_WARNING,
-			      "delete_nsecnode(): "
+			      "delete_node(): "
 			      "dns_rbt_deletenode: %s",
 			      isc_result_totext(result));
 	}
@@ -2537,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);
@@ -4546,218 +4560,45 @@
 	return (result);
 }
 
+#ifdef BIND9
 /*
- * Mark a database for response policy rewriting.
+ * Connect this RBTDB to the response policy zone summary data for the view.
  */
-#ifdef BIND9
 static void
-get_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st)
-{
-	dns_rbtdb_t *rbtdb;
+rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
+	dns_rbtdb_t * rbtdb;
 
 	rbtdb = (dns_rbtdb_t *)db;
 	REQUIRE(VALID_RBTDB(rbtdb));
-	RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
-	dns_rpz_enabled(rbtdb->rpz_cidr, st);
-	RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
+
+	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
 
@@ -6860,8 +6701,9 @@
 	noderesult = dns_rbt_addnode(rbtdb->tree, name, nodep);
 
 #ifdef BIND9
-	if (noderesult == ISC_R_SUCCESS)
-		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)
@@ -7046,6 +6888,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;
@@ -7447,8 +7303,8 @@
 	isdnssec,
 	NULL,
 #ifdef BIND9
-	get_rpz_enabled,
-	rpz_findips
+	rpz_attach,
+	rpz_ready,
 #else
 	NULL,
 	NULL
@@ -7681,24 +7537,6 @@
 		return (result);
 	}
 
-#ifdef BIND9
-	/*
-	 * Get ready for response policy IP address searching if at least one
-	 * zone has been configured as a response policy zone and this
-	 * is not a cache zone.
-	 * It would be better to know that this database is for a policy
-	 * zone named for a view, but that would require knowledge from
-	 * above such as an argv[] set from data in the zone.
-	 */
-	if (type == dns_dbtype_zone && !dns_name_equal(origin, dns_rootname)) {
-		result = dns_rpz_new_cidr(mctx, origin, &rbtdb->rpz_cidr);
-		if (result != ISC_R_SUCCESS) {
-			free_rbtdb(rbtdb, ISC_FALSE, NULL);
-			return (result);
-		}
-	}
-#endif
-
 	/*
 	 * In order to set the node callback bit correctly in zone databases,
 	 * we need to know if the node has the origin name of the zone.
@@ -7776,6 +7614,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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2012  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,7 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id$ */
 
 /*! \file */
 
@@ -36,6 +35,7 @@
 #include <dns/rdataset.h>
 #include <dns/rdatastruct.h>
 #include <dns/result.h>
+#include <dns/rbt.h>
 #include <dns/rpz.h>
 #include <dns/view.h>
 
@@ -43,9 +43,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
@@ -54,7 +58,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
@@ -71,7 +76,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.
  */
 
@@ -89,41 +94,72 @@
 } 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 pair of arrays of bits flagging the existence of
+ * IP or QNAME (d) and NSIP or NSDNAME (ns) policy triggers.
+ */
+typedef struct dns_rpz_pair_zbits dns_rpz_pair_zbits_t;
+struct dns_rpz_pair_zbits {
+	dns_rpz_zbits_t	    d;
+	dns_rpz_zbits_t	    ns;
+};
 
-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_pair_zbits_t	pair;
+	dns_rpz_pair_zbits_t	sum;
 };
 
-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 */
+/*
+ * 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.
+ */
+typedef struct dns_rpz_nm_data dns_rpz_nm_data_t;
+struct dns_rpz_nm_data {
+	dns_rpz_pair_zbits_t	pair;
+	dns_rpz_pair_zbits_t	wild;
 };
 
-static isc_boolean_t		have_rpz_zones = ISC_FALSE;
+#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) {
@@ -139,8 +175,7 @@
 	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");
 }
 
@@ -191,266 +226,167 @@
 		break;
 	default:
 		str = "";
+		POST(str);
 		INSIST(0);
 	}
 	return (str);
 }
 
+static inline void
+make_pair(dns_rpz_pair_zbits_t *pair, dns_rpz_zbits_t zbits,
+	  dns_rpz_type_t type)
+{
+	switch (type) {
+	case DNS_RPZ_TYPE_QNAME:
+	case DNS_RPZ_TYPE_IP:
+		pair->d = zbits;
+		pair->ns = 0;
+		break;
+	case DNS_RPZ_TYPE_NSDNAME:
+	case DNS_RPZ_TYPE_NSIP:
+		pair->d = 0;
+		pair->ns = zbits;
+		break;
+	default:
+		INSIST(0);
+		break;
+	}
+}
+
 /*
- * Free the radix tree of a response policy database.
+ * Mark a node and all of its parents as having IP or NSIP data
  */
-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);
+static void
+set_sum_pair(dns_rpz_cidr_node_t *cnode) {
+	dns_rpz_cidr_node_t *child;
+	dns_rpz_pair_zbits_t sum;
 
-	cidr = *cidrp;
-	if (cidr == NULL)
-		return;
+	do {
+		sum = cnode->pair;
 
-	cur = cidr->root;
-	while (cur != NULL) {
-		/* Depth first. */
-		child = cur->child[0];
+		child = cnode->child[0];
 		if (child != NULL) {
-			cur = child;
-			continue;
+			sum.d |= child->sum.d;
+			sum.ns |= child->sum.ns;
 		}
-		child = cur->child[1];
+
+		child = cnode->child[1];
 		if (child != NULL) {
-			cur = child;
-			continue;
+			sum.d |= child->sum.d;
+			sum.ns |= child->sum.ns;
 		}
 
-		/* 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;
-	}
-
-	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;
+		if (cnode->sum.d == sum.d &&
+		    cnode->sum.ns == sum.ns)
+			break;
+		cnode->sum = sum;
+		cnode = cnode->parent;
+	} while (cnode != NULL);
 }
 
 /*
- * Forget a view's list of policy zones.
+ * classic population count
  */
-void
-dns_rpz_view_destroy(dns_view_t *view) {
-	dns_rpz_zone_t *zone;
-
-	REQUIRE(view != NULL);
+static int
+pcnt(dns_rpz_zbits_t zbits) {
+	int i;
 
-	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));
+	i = 0;
+	while (zbits != 0) {
+		zbits &= ~(zbits & -zbits);
+		++i;
 	}
+	return i;
 }
 
-/*
- * Note that we have at least one response policy zone.
- * It would be better for something to tell the rbtdb code that the
- * zone is in at least one view's list of policy zones.
- */
-void
-dns_rpz_set_need(isc_boolean_t need) {
-	have_rpz_zones = need;
-}
-
-isc_boolean_t
-dns_rpz_needed(void) {
-	return (have_rpz_zones);
-}
-
-/*
- * Start a new radix tree for a response policy zone.
- */
-isc_result_t
-dns_rpz_new_cidr(isc_mem_t *mctx, dns_name_t *origin,
-		 dns_rpz_cidr_t **rbtdb_cidr)
+static void
+num_cnode(dns_rpz_zones_t *rpzs,
+	  dns_rpz_prefix_t tgt_prefix, const dns_rpz_cidr_key_t *tgt_ip,
+	  const dns_rpz_pair_zbits_t *pair, int sign)
 {
-	isc_result_t result;
-	dns_rpz_cidr_t *cidr;
-
-	REQUIRE(rbtdb_cidr != NULL && *rbtdb_cidr == NULL);
-
-	/*
-	 * Only if there is at least one response policy zone.
-	 */
-	if (!have_rpz_zones)
-		return (ISC_R_SUCCESS);
-
-	cidr = isc_mem_get(mctx, sizeof(*cidr));
-	if (cidr == NULL)
-		return (ISC_R_NOMEMORY);
-	memset(cidr, 0, sizeof(*cidr));
-	cidr->mctx = mctx;
-
-	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);
-	}
-
-	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);
-	}
-
-	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);
-	}
+	int num_ip, num_nsip;
 
-	*rbtdb_cidr = cidr;
-	return (ISC_R_SUCCESS);
-}
+	num_ip = pcnt(pair->d) * sign;
+	num_nsip = pcnt(pair->ns) * sign;
 
-/*
- * See if a policy zone has IP, NSIP, or NSDNAME rules or records.
- */
-void
-dns_rpz_enabled(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);
+	if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+		rpzs->num.ipv4 += num_ip;
+		rpzs->num.nsipv4 += num_nsip;
+		REQUIRE(rpzs->num.ipv4 >= 0 && rpzs->num.nsipv4 >= 0);
 	} else {
-		return (DNS_RPZ_CIDR_FG_IP | DNS_RPZ_CIDR_FG_IP_DATA);
+		rpzs->num.ipv6 += num_ip;
+		rpzs->num.nsipv6 += num_nsip;
+		REQUIRE(rpzs->num.ipv6 >= 0 && rpzs->num.nsipv6 >= 0);
 	}
 }
 
-/*
- * 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;
+num_nmnode(dns_rpz_zones_t *rpzs, const dns_rpz_nm_data_t *data, int sign) {
 
-	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;
-	}
+	rpzs->num.qname += (pcnt(data->pair.d) + pcnt(data->wild.d)) * sign;
+	rpzs->num.nsdname += (pcnt(data->pair.ns) + pcnt(data->wild.ns)) * sign;
+	REQUIRE(rpzs->num.qname >= 0 && rpzs->num.nsdname >= 0);
 }
 
-/*
- * 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));
+		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
@@ -458,22 +394,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++) {
@@ -508,40 +440,24 @@
 				while (i < DNS_RPZ_CIDR_WORDS * 2 && w[i] == 0)
 					++i;
 			}
-			if (len > (int)sizeof(str))
+			if (len >= (int)sizeof(str))
 				return (ISC_R_FAILURE);
 		}
 	}
 
-	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);
 
 	/*
@@ -549,12 +465,12 @@
 	 * until consistency problems are resolved.
 	 */
 #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
 
@@ -563,73 +479,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_pair_zbits_t *new_pair)
 {
-	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.cnt);
+	rpz = rpzs->zones[rpz_num];
+	REQUIRE(rpz != NULL);
+
+	make_pair(new_pair, 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;
@@ -639,7 +562,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);
 			}
@@ -650,7 +573,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--) {
@@ -669,7 +592,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);
 				}
@@ -683,36 +606,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);
 	}
 
@@ -720,10 +644,55 @@
 }
 
 /*
- * 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)
+{
+	static dns_rpz_pair_zbits_t zero = {0, 0};
+	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.cnt);
+	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;
+		new_data->pair = zero;
+		make_pair(&new_data->wild, DNS_RPZ_ZBIT(rpz_num), rpz_type);
+	} else {
+		prefix_len = 0;
+		make_pair(&new_data->pair, DNS_RPZ_ZBIT(rpz_num), rpz_type);
+		new_data->wild = zero;
+	}
+
+	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;
@@ -749,17 +718,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
@@ -769,7 +738,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;
 		}
 	}
@@ -777,133 +746,194 @@
 }
 
 /*
+ * 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;
+	x = (x << 1) - 1;
+	return (zbits &= x);
+}
+
+static int
+zbit_to_num(dns_rpz_zbits_t zbit) {
+	dns_rpz_num_t rpz_num;
+
+	INSIST(zbit != 0);
+	rpz_num = 0;
+#if DNS_RPZ_MAX_ZONES > 32
+	if ((zbit & 0xffffffff00000000L) != 0) {
+		zbit >>= 32;
+		rpz_num += 32;
+	}
+#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);
+}
+
+/*
  * 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_pair_zbits_t *tgt_pair, isc_boolean_t create,
+       dns_rpz_cidr_node_t **found)
 {
 	dns_rpz_cidr_node_t *cur, *parent, *child, *new_parent, *sibling;
+	dns_rpz_pair_zbits_t pair;
 	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);
-
+	pair = *tgt_pair;
 	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->pair.d |= tgt_pair->d;
+			child->pair.ns |= tgt_pair->ns;
+			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.d & pair.d) == 0 &&
+		    (cur->sum.ns & pair.ns) == 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->pair.d & pair.d) != 0 ||
+				    (cur->pair.ns & pair.ns) != 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->pair.d |= tgt_pair->d;
+					cur->pair.ns |= tgt_pair->ns;
+					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->pair = *tgt_pair;
+			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->pair.d & pair.d) != 0 ||
+			    (cur->pair.ns & pair.ns) != 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;
+				pair.d = trim_zbits(pair.d, cur->pair.d);
+				pair.ns = trim_zbits(pair.ns, cur->pair.ns);
 			}
-
 			parent = cur;
 			cur_num = DNS_RPZ_IP_BIT(tgt_ip, dbit);
 			cur = cur->child[cur_num];
@@ -912,7 +942,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
@@ -920,17 +950,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);
@@ -938,127 +968,616 @@
 		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->pair = *tgt_pair;
+		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;
-
-	if (cidr == NULL)
-		return;
+	dns_rpz_prefix_t tgt_prefix;
+	dns_rpz_pair_zbits_t pair;
+	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, &pair);
 	/*
-	 * 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:
+	if (result != ISC_R_SUCCESS)
+		return (ISC_R_SUCCESS);
+
+	result = search(rpzs, &tgt_ip, tgt_prefix, &pair, 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(src_name, namebuf, sizeof(namebuf));
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+			      "rpz add_cidr(%s) failed: %s",
+			      namebuf, isc_result_totext(result));
+		return (result);
+	}
+
+	num_cnode(rpzs, tgt_prefix, &tgt_ip, &pair, 1);
+	return (result);
+}
+
+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;
+
+	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;
+			num_nmnode(rpzs, new_data, 1);
+			return (ISC_R_SUCCESS);
+		}
 		break;
-	case DNS_RPZ_TYPE_NSDNAME:
-		cidr->have_nsdname = ISC_TRUE;
-		return;
-	case DNS_RPZ_TYPE_QNAME:
-	case DNS_RPZ_TYPE_BAD:
-		return;
+	default:
+		return (result);
 	}
-	result = name2ipkey(cidr, DNS_RPZ_ERROR_LEVEL, name,
-			    type, &tgt_ip, &tgt_prefix);
-	if (result != ISC_R_SUCCESS)
-		return;
 
-	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];
+	/*
+	 * Do not count bits that are already present
+	 */
+	if ((nm_data->pair.d & new_data->pair.d) != 0 ||
+	    (nm_data->pair.ns & new_data->pair.ns) != 0 ||
+	    (nm_data->wild.d & new_data->wild.d) != 0 ||
+	    (nm_data->wild.ns & new_data->wild.ns) != 0) {
+		char namebuf[DNS_NAME_FORMATSIZE];
 
-		dns_name_format(name, printname, sizeof(printname));
+		/*
+		 * 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 failed; \"%s\" is a duplicate name",
-			      printname);
+			      "rpz add_nm(%s): bits already set", namebuf);
+		return (ISC_R_EXISTS);
 	}
+
+	nm_data->pair.d |= new_data->pair.d;
+	nm_data->pair.ns |= new_data->pair.ns;
+	nm_data->wild.d |= new_data->wild.d;
+	nm_data->wild.ns |= new_data->wild.ns;
+	num_nmnode(rpzs, new_data, 1);
+	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;
+
+	dns_fixedname_init(&trig_namef);
+	trig_name = dns_fixedname_name(&trig_namef);
+	name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &new_data);
+
+	return add_nm(rpzs, trig_name, &new_data);
 }
 
 /*
- * Delete an IP address from the radix tree of a response policy database.
- *	The tree write lock must be held by the caller.
+ * Callback to free the data for a node in the summary RBT database.
  */
-void
-dns_rpz_cidr_deleteip(dns_rpz_cidr_t *cidr, dns_name_t *name) {
+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;
-	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)
+	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;
+	isc_refcount_destroy(&rpz->refs);
+
+	if (dns_name_dynamic(&rpz->origin))
+		dns_name_free(&rpz->origin, 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->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);
 
 	/*
-	 * Decide which kind of policy zone IP address it is, if either
-	 * and then find its node.
+	 * Forget the last of view's rpz machinery after the last reference.
 	 */
-	type = set_type(cidr, name);
-	switch (type) {
-	case DNS_RPZ_TYPE_IP:
-	case DNS_RPZ_TYPE_NSIP:
-		break;
-	case DNS_RPZ_TYPE_NSDNAME:
+	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.cnt);
+	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.cnt = rpzs->p.cnt;
+		load_rpzs->zones[rpz_num] = rpz;
+		isc_refcount_increment(&rpz->refs, NULL);
+	}
+
+	return (ISC_R_SUCCESS);
+}
+
+static void
+load_unlock(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **load_rpzsp) {
+	UNLOCK(&rpzs->maint_lock);
+	UNLOCK(&(*load_rpzsp)->search_lock);
+	UNLOCK(&(*load_rpzsp)->maint_lock);
+	dns_rpz_detach_rpzs(load_rpzsp);
+}
+
+/*
+ * 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_pair_zbits_t load_pair, new_pair;
+	dns_rbt_t *rbt;
+	dns_rbtnodechain_t chain;
+	dns_rbtnode_t *nmnode;
+	dns_rpz_nm_data_t *nm_data, new_data;
+	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) {
 		/*
-		 * We cannot easily count nsdnames because
-		 * internal rbt nodes get deleted.
+		 * This must be a successful first zone loading.
 		 */
-		return;
+		rpzs->valid |= DNS_RPZ_ZBIT(rpz_num);
+		UNLOCK(&(*load_rpzsp)->maint_lock);
+		dns_rpz_detach_rpzs(load_rpzsp);
+		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.cnt > 1) {
+		load_pair.d = ~DNS_RPZ_ZBIT(rpz_num);
+		load_pair.ns = load_pair.d;
+		for (cnode = rpzs->cidr; cnode != NULL; cnode = next_cnode) {
+			new_pair.d = cnode->pair.d & load_pair.d;
+			new_pair.ns = cnode->pair.ns & load_pair.ns;
+			if (new_pair.d != 0 || new_pair.ns != 0) {
+				result = search(load_rpzs,
+						&cnode->ip, cnode->prefix,
+						&new_pair, ISC_TRUE,
+						&found);
+				if (result == ISC_R_NOMEMORY) {
+					load_unlock(rpzs, load_rpzsp);
+					return (result);
+				}
+				INSIST(result == ISC_R_SUCCESS);
+				num_cnode(load_rpzs, cnode->prefix, &cnode->ip,
+					  &new_pair, 1);
+			}
+			/*
+			 * 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 branck 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;
+			}
+		}
+
+		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.pair.d = (nm_data->pair.d &
+						   load_pair.d);
+				new_data.pair.ns = (nm_data->pair.ns &
+						   load_pair.ns);
+				new_data.wild.d = (nm_data->wild.d &
+						   load_pair.d);
+				new_data.wild.ns = (nm_data->wild.ns &
+						   load_pair.ns);
+				if (new_data.pair.d != 0 ||
+				    new_data.pair.ns != 0 ||
+				    new_data.wild.d != 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) {
+					    load_unlock(rpzs, load_rpzsp);
+					    return (result);
+					}
+				}
+			}
+			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));
+			load_unlock(rpzs, load_rpzsp);
+			return (result);
+		}
+	}
+
+	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,
+		      "loading zone '%s' changed from"
+		      " %d to %d qname, %d to %d nsdname,"
+		      " %d to %d IP, %d to %d NSIP entries",
+		      namebuf,
+		      rpzs->num.qname, load_rpzs->num.qname,
+		      rpzs->num.nsdname, load_rpzs->num.nsdname,
+		      rpzs->num.ipv4 + rpzs->num.ipv6,
+		      load_rpzs->num.ipv4 + load_rpzs->num.ipv6,
+		      rpzs->num.nsipv4 + rpzs->num.nsipv6,
+		      load_rpzs->num.nsipv4 + load_rpzs->num.nsipv6);
+
+	/*
+	 * Exchange the summary databases.
+	 */
+	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;
+
+	memcpy(&rpzs->num, &load_rpzs->num, sizeof(rpzs->num));
+	rpzs->valid |= DNS_RPZ_ZBIT(rpz_num);
+
+	UNLOCK(&rpzs->search_lock);
+
+	load_unlock(rpzs, load_rpzsp);
+	return (ISC_R_SUCCESS);
+}
+
+/*
+ * 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;
+
+	REQUIRE(rpzs != NULL && rpz_num < rpzs->p.cnt);
+	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_IP:
+	case DNS_RPZ_TYPE_NSIP:
+		result = add_cidr(rpzs, rpz_num, rpz_type, src_name);
+		break;
 	case DNS_RPZ_TYPE_BAD:
-		return;
+		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_pair_zbits_t pair;
+	dns_rpz_cidr_node_t *tgt, *parent, *child;
+
 	/*
-	 * Do not get excited about the deletion of interior rbt nodes.
+	 * 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(cidr, DNS_RPZ_DEBUG_QUIET, name,
-			    type, &tgt_ip, &tgt_prefix);
+	result = name2ipkey(DNS_RPZ_DEBUG_QUIET, rpzs, rpz_num, rpz_type,
+			    src_name, &tgt_ip, &tgt_prefix, &pair);
 	if (result != ISC_R_SUCCESS)
 		return;
 
-	result = search(cidr, &tgt_ip, tgt_prefix, type, ISC_FALSE, &tgt);
+	result = search(rpzs, &tgt_ip, tgt_prefix, &pair, 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;
-	}
+	pair.d &= tgt->pair.d;
+	pair.ns &= tgt->pair.ns;
+
+	tgt->pair.d &= ~pair.d;
+	tgt->pair.ns &= ~pair.ns;
+	set_sum_pair(tgt);
+	num_cnode(rpzs, tgt_prefix, &tgt_ip, &pair, -1);
 
 	/*
 	 * We might need to delete 2 nodes.
@@ -1070,13 +1589,12 @@
 		 */
 		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->pair.d + tgt->pair.ns != 0)
+			break;
 
 		/*
 		 * Replace the pointer to this node in the parent with
@@ -1084,7 +1602,7 @@
 		 */
 		parent = tgt->parent;
 		if (parent == NULL) {
-			cidr->root = child;
+			rpzs->cidr = child;
 		} else {
 			parent->child[parent->child[1] == tgt] = child;
 		}
@@ -1093,28 +1611,150 @@
 		 */
 		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.cnt <= 1) {
+		num_nmnode(rpzs, &del_data, -1);
+		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.pair.d &= nm_data->pair.d;
+	del_data.pair.ns &= nm_data->pair.ns;
+	del_data.wild.d &= nm_data->wild.d;
+	del_data.wild.ns &= nm_data->wild.ns;
+
+	nm_data->pair.d &= ~del_data.pair.d;
+	nm_data->pair.ns &= ~del_data.pair.ns;
+	nm_data->wild.d &= ~del_data.wild.d;
+	nm_data->wild.ns &= ~del_data.wild.ns;
+
+	if (nm_data->pair.d == 0 && nm_data->pair.ns == 0 &&
+	    nm_data->wild.d == 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));
+		}
+	}
+
+	num_nmnode(rpzs, &del_data, -1);
+}
+
 /*
- * 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.cnt);
+	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_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_pair_zbits_t pair;
 	dns_rpz_cidr_node_t *found;
+	isc_result_t result;
+	dns_rpz_num_t rpz_num;
 	int i;
 
+	zbits &= rpzs->valid;
+	if (zbits == 0)
+		return (DNS_RPZ_INVALID_NUM);
+	make_pair(&pair, zbits, rpz_type);
+
 	/*
 	 * Convert IP address to CIDR tree key.
 	 */
@@ -1129,23 +1769,119 @@
 		/*
 		 * 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]);
 		}
 	} 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);
+	LOCK(&rpzs->search_lock);
+	result = search(rpzs, &tgt_ip, 128, &pair, 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;
+	if (rpz_type == DNS_RPZ_TYPE_IP) {
+		INSIST((found->pair.d & pair.d) != 0);
+		rpz_num = zbit_to_num(found->pair.d & pair.d);
+	} else {
+		INSIST((found->pair.ns & pair.ns) != 0);
+		rpz_num = zbit_to_num(found->pair.ns & pair.ns);
+	}
+	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);
+}
+
+/*
+ * 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;
+
+	zbits &= rpzs->valid;
+	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->pair.d;
+			else
+				found_zbits = nm_data->pair.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.d;
+				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;
+	}
 
-	*prefix = found->bits;
-	return (ip2name(cidr, &found->ip, found->bits, type,
-			canon_name, search_name));
+	UNLOCK(&rpzs->search_lock);
+	return (zbits & found_zbits);
 }
 
 /*
@@ -1160,10 +1896,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);
 
 	/*
@@ -1190,7 +1926,7 @@
 	}
 
 	/*
-	 * CNAME PASSTHRU.origin means "do not rewrite.
+	 * CNAME 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,1308 @@
+/*
+ * Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+/*! \file */
+
+/*
+ * Rate limit DNS responses.
+ */
+
+/* #define ISC_LIST_CHECKINIT */
+
+#include <config.h>
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/netaddr.h>
+
+#include <dns/result.h>
+#include <dns/rcode.h>
+#include <dns/rdatatype.h>
+#include <dns/rdataclass.h>
+#include <dns/log.h>
+#include <dns/rrl.h>
+#include <dns/view.h>
+
+
+static void
+log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_boolean_t early,
+	char *log_buf, unsigned int log_buf_len);
+
+
+/*
+ * Get a modulus for a hash function that is tolerably likely to be
+ * relatively prime to most inputs.  Of course, we get a prime for for initial
+ * values not larger than the square of the last prime.  We often get a prime
+ * after that.
+ * This works well in practice for hash tables up to at least 100
+ * times the square of the last prime and better than a multiplicative hash.
+ */
+static int
+hash_divisor(unsigned int initial) {
+	static isc_uint16_t primes[] = {
+		  3,   5,   7,  11,  13,  17,  19,  23,  29,  31,  37,  41,
+		 43,  47,  53,  59,  61,  67,  71,  73,  79,  83,  89,  97,
+#if 0
+		101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157,
+		163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227,
+		229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283,
+		293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367,
+		373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439,
+		443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509,
+		521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599,
+		601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661,
+		673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751,
+		757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
+		839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919,
+		929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,1009,
+#endif
+	};
+	int divisions, tries;
+	unsigned int result;
+	isc_uint16_t *pp, p;
+
+	result = initial;
+
+	if (primes[sizeof(primes)/sizeof(primes[0])-1] >= result) {
+		pp = primes;
+		while (*pp < result)
+			++pp;
+		return (*pp);
+	}
+
+	if ((result & 1) == 0)
+		++result;
+
+	divisions = 0;
+	tries = 1;
+	pp = primes;
+	do {
+		p = *pp++;
+		++divisions;
+		if ((result % p) == 0) {
+			++tries;
+			result += 2;
+			pp = primes;
+		}
+	} while (pp < &primes[sizeof(primes)/sizeof(primes[0])]);
+
+	if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3,
+			      "%d hash_divisor() divisions in %d tries"
+			      " to get %d from %d",
+			      divisions, tries, result, initial);
+
+	return (result);
+}
+
+/*
+ * Convert a timestamp to a number of seconds in the past.
+ */
+static inline int
+delta_rrl_time(isc_stdtime_t ts, isc_stdtime_t now) {
+	int delta;
+
+	delta = now - ts;
+	if (delta >= 0)
+		return (delta);
+
+	/*
+	 * The timestamp is in the future.  That future might result from
+	 * re-ordered requests, because we use timestamps on requests
+	 * instead of consulting a clock.  Timestamps in the distant future are
+	 * assumed to result from clock changes.  When the clock changes to
+	 * the past, make existing timestamps appear to be in the past.
+	 */
+	if (delta < -DNS_RRL_MAX_TIME_TRAVEL)
+		return (DNS_RRL_FOREVER);
+	return (0);
+}
+
+static inline int
+get_age(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, isc_stdtime_t now) {
+	if (!e->ts_valid)
+		return (DNS_RRL_FOREVER);
+	return (delta_rrl_time(e->ts + rrl->ts_bases[e->ts_gen], now));
+}
+
+static inline void
+set_age(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_stdtime_t now) {
+	dns_rrl_entry_t *e_old;
+	unsigned int ts_gen;
+	int i, ts;
+
+	ts_gen = rrl->ts_gen;
+	ts = now - rrl->ts_bases[ts_gen];
+	if (ts < 0) {
+		if (ts < -DNS_RRL_MAX_TIME_TRAVEL)
+			ts = DNS_RRL_FOREVER;
+		else
+			ts = 0;
+	}
+
+	/*
+	 * Make a new timestamp base if the current base is too old.
+	 * All entries older than DNS_RRL_MAX_WINDOW seconds are ancient,
+	 * useless history.  Their timestamps can be treated as if they are
+	 * all the same.
+	 * We only do arithmetic on more recent timestamps, so bases for
+	 * older timestamps can be recycled provided the old timestamps are
+	 * marked as ancient history.
+	 * This loop is almost always very short because most entries are
+	 * recycled after one second and any entries that need to be marked
+	 * are older than (DNS_RRL_TS_BASES)*DNS_RRL_MAX_TS seconds.
+	 */
+	if (ts >= DNS_RRL_MAX_TS) {
+		ts_gen = (ts_gen+1) % DNS_RRL_TS_BASES;
+		for (e_old = ISC_LIST_TAIL(rrl->lru), i = 0;
+		     e_old != NULL && e_old->ts_gen == ts_gen;
+		     e_old = ISC_LIST_PREV(e_old, lru), ++i) {
+			if (e_old->ts_valid)
+				e_old->ts_valid = ISC_FALSE;
+		}
+		if (i != 0)
+			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+				      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1,
+				      "rrl new time base scanned %d entries"
+				      " at %d for %d %d %d %d",
+				      i, now, rrl->ts_bases[ts_gen],
+				      rrl->ts_bases[(ts_gen+1)%DNS_RRL_TS_BASES],
+				      rrl->ts_bases[(ts_gen+2)%DNS_RRL_TS_BASES],
+				      rrl->ts_bases[(ts_gen+3)%DNS_RRL_TS_BASES]);
+		rrl->ts_gen = ts_gen;
+		rrl->ts_bases[ts_gen] = now;
+		ts = 0;
+	}
+
+	e->ts_gen = ts_gen;
+	e->ts = ts;
+	e->ts_valid = ISC_TRUE;
+}
+
+static isc_result_t
+expand_entries(dns_rrl_t *rrl, int new) {
+	unsigned int bsize;
+	dns_rrl_block_t *b;
+	dns_rrl_entry_t *e;
+	double rate;
+	int i;
+
+	if (rrl->num_entries+new >= rrl->max_entries && rrl->max_entries != 0) {
+		if (rrl->num_entries >= rrl->max_entries)
+			return (ISC_R_SUCCESS);
+		new = rrl->max_entries - rrl->num_entries;
+		if (new <= 0)
+			return (ISC_R_NOMEMORY);
+	}
+
+	/*
+	 * Log expansions so that the user can tune max-table-size
+	 * and min-table-size.
+	 */
+	if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) &&
+	    rrl->hash != NULL) {
+		rate = rrl->probes;
+		if (rrl->searches != 0)
+			rate /= rrl->searches;
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
+			      "increase from %d to %d RRL entries with"
+			      " %d bins; average search length %.1f",
+			      rrl->num_entries, rrl->num_entries+new,
+			      rrl->hash->length, rate);
+	}
+
+	bsize = sizeof(dns_rrl_block_t) + (new-1)*sizeof(dns_rrl_entry_t);
+	b = isc_mem_get(rrl->mctx, bsize);
+	if (b == NULL) {
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL,
+			      "isc_mem_get(%d) failed for RRL entries",
+			      bsize);
+		return (ISC_R_NOMEMORY);
+	}
+	memset(b, 0, bsize);
+	b->size = bsize;
+
+	e = b->entries;
+	for (i = 0; i < new; ++i, ++e) {
+		ISC_LINK_INIT(e, hlink);
+		ISC_LIST_INITANDAPPEND(rrl->lru, e, lru);
+	}
+	rrl->num_entries += new;
+	ISC_LIST_INITANDAPPEND(rrl->blocks, b, link);
+
+	return (ISC_R_SUCCESS);
+}
+
+static inline dns_rrl_bin_t *
+get_bin(dns_rrl_hash_t *hash, unsigned int hval) {
+	return (&hash->bins[hval % hash->length]);
+}
+
+static void
+free_old_hash(dns_rrl_t *rrl) {
+	dns_rrl_hash_t *old_hash;
+	dns_rrl_bin_t *old_bin;
+	dns_rrl_entry_t *e, *e_next;
+
+	old_hash = rrl->old_hash;
+	for (old_bin = &old_hash->bins[0];
+	     old_bin < &old_hash->bins[old_hash->length];
+	     ++old_bin) {
+		for (e = ISC_LIST_HEAD(*old_bin); e != NULL; e = e_next) {
+			e_next = ISC_LIST_NEXT(e, hlink);
+			ISC_LINK_INIT(e, hlink);
+		}
+	}
+
+	isc_mem_put(rrl->mctx, old_hash,
+		    sizeof(*old_hash)
+		    + (old_hash->length-1)*sizeof(old_hash->bins[0]));
+	rrl->old_hash = NULL;
+}
+
+static isc_result_t
+expand_rrl_hash(dns_rrl_t *rrl, isc_stdtime_t now) {
+	dns_rrl_hash_t *hash;
+	int old_bins, new_bins, hsize;
+	double rate;
+
+	if (rrl->old_hash != NULL)
+		free_old_hash(rrl);
+
+	/*
+	 * Most searches fail and so go to the end of the chain.
+	 * Use a small hash table load factor.
+	 */
+	old_bins = (rrl->hash == NULL) ? 0 : rrl->hash->length;
+	new_bins = old_bins/8 + old_bins;
+	if (new_bins < rrl->num_entries)
+		new_bins = rrl->num_entries;
+	new_bins = hash_divisor(new_bins);
+
+	hsize = sizeof(dns_rrl_hash_t) + (new_bins-1)*sizeof(hash->bins[0]);
+	hash = isc_mem_get(rrl->mctx, hsize);
+	if (hash == NULL) {
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL,
+			      "isc_mem_get(%d) failed for"
+			      " RRL hash table",
+			      hsize);
+		return (ISC_R_NOMEMORY);
+	}
+	memset(hash, 0, hsize);
+	hash->length = new_bins;
+	rrl->hash_gen ^= 1;
+	hash->gen = rrl->hash_gen;
+
+	if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && old_bins != 0) {
+		rate = rrl->probes;
+		if (rrl->searches != 0)
+			rate /= rrl->searches;
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
+			      "increase from %d to %d RRL bins for"
+			      " %d entries; average search length %.1f",
+			      old_bins, new_bins, rrl->num_entries, rate);
+	}
+
+	rrl->old_hash = rrl->hash;
+	if (rrl->old_hash != NULL)
+		rrl->old_hash->check_time = now;
+	rrl->hash = hash;
+
+	return (ISC_R_SUCCESS);
+}
+
+static void
+ref_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, int probes, isc_stdtime_t now) {
+	/*
+	 * Make the entry most recently used.
+	 */
+	if (ISC_LIST_HEAD(rrl->lru) != e) {
+		if (e == rrl->last_logged)
+			rrl->last_logged = ISC_LIST_PREV(e, lru);
+		ISC_LIST_UNLINK(rrl->lru, e, lru);
+		ISC_LIST_PREPEND(rrl->lru, e, lru);
+	}
+
+	/*
+	 * Expand the hash table if it is time and necessary.
+	 * This will leave the newly referenced entry in a chain in the
+	 * old hash table.  It will migrate to the new hash table the next
+	 * time it is used or be cut loose when the old hash table is destroyed.
+	 */
+	rrl->probes += probes;
+	++rrl->searches;
+	if (rrl->searches > 100 &&
+	    delta_rrl_time(rrl->hash->check_time, now) > 1) {
+		if (rrl->probes/rrl->searches > 2)
+			expand_rrl_hash(rrl, now);
+		rrl->hash->check_time = now;
+		rrl->probes = 0;
+		rrl->searches = 0;
+	}
+}
+
+static inline isc_boolean_t
+key_cmp(const dns_rrl_key_t *a, const dns_rrl_key_t *b) {
+	if (memcmp(a, b, sizeof(dns_rrl_key_t)) == 0)
+		return (ISC_TRUE);
+	return (ISC_FALSE);
+}
+
+static inline isc_uint32_t
+hash_key(const dns_rrl_key_t *key) {
+	isc_uint32_t hval;
+	int i;
+
+	hval = key->w[0];
+	for (i = sizeof(*key)/sizeof(key->w[0]) - 1; i >= 0; --i) {
+		hval = key->w[i] + (hval<<1);
+	}
+	return (hval);
+}
+
+/*
+ * Construct the hash table key.
+ * Use a hash of the DNS query name to save space in the database.
+ * Collisions result in legitimate rate limiting responses for one
+ * query name also limiting responses for other names to the
+ * same client.  This is rare and benign enough given the large
+ * space costs compared to keeping the entire name in the database
+ * entry or the time costs of dynamic allocation.
+ */
+static void
+make_key(const dns_rrl_t *rrl, dns_rrl_key_t *key,
+	 const isc_sockaddr_t *client_addr,
+	 dns_rdatatype_t qtype, dns_name_t *qname, dns_rdataclass_t qclass,
+	 dns_rrl_rtype_t rtype)
+{
+	dns_name_t base;
+	dns_offsets_t base_offsets;
+	int labels, i;
+
+	memset(key, 0, sizeof(*key));
+
+	key->s.rtype = rtype;
+	if (rtype == DNS_RRL_RTYPE_QUERY ||
+	    rtype == DNS_RRL_RTYPE_NXDOMAIN) {
+		key->s.qclass = qclass;
+		key->s.qtype = qtype;
+	}
+
+	if (qname != NULL && qname->labels != 0) {
+		/*
+		 * Ignore the first label of wildcards.
+		 */
+		if ((qname->attributes & DNS_NAMEATTR_WILDCARD) != 0 &&
+		    (labels = dns_name_countlabels(qname)) > 1) {
+			dns_name_init(&base, base_offsets);
+			dns_name_getlabelsequence(qname, 1, labels-1, &base);
+			key->s.qname_hash = dns_name_hashbylabel(&base,
+							ISC_FALSE);
+		} else {
+			key->s.qname_hash = dns_name_hashbylabel(qname,
+							ISC_FALSE);
+		}
+	}
+
+	switch (client_addr->type.sa.sa_family) {
+	case AF_INET:
+		key->s.ip[0] = (client_addr->type.sin.sin_addr.s_addr &
+			      rrl->ipv4_mask);
+		break;
+	case AF_INET6:
+		key->s.ipv6 = ISC_TRUE;
+		memcpy(key->s.ip, &client_addr->type.sin6.sin6_addr,
+		       sizeof(key->s.ip));
+		for (i = 0; i < DNS_RRL_MAX_PREFIX/32; ++i)
+			key->s.ip[i] &= rrl->ipv6_mask[i];
+		break;
+	}
+}
+
+static inline int
+response_balance(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) {
+	int balance, rate;
+
+	balance = e->responses;
+	if (balance < 0)
+		switch (e->key.s.rtype) {
+		case DNS_RRL_RTYPE_QUERY:
+			rate = rrl->scaled_responses_per_second;
+			break;
+		case DNS_RRL_RTYPE_NXDOMAIN:
+			rate = rrl->scaled_nxdomains_per_second;
+			break;
+		case DNS_RRL_RTYPE_ERROR:
+			rate = rrl->scaled_errors_per_second;
+			break;
+		case DNS_RRL_RTYPE_ALL:
+			rate = rrl->scaled_all_per_second;
+			break;
+		case DNS_RRL_RTYPE_TCP:
+			rate = 1;
+			break;
+		default:
+			INSIST(0);
+	}
+	balance += age * rate;
+	if (balance > rate)
+		balance = rate;
+	return (balance);
+}
+
+/*
+ * Search for an entry for a response and optionally create it.
+ */
+static dns_rrl_entry_t *
+get_entry(dns_rrl_t *rrl, const isc_sockaddr_t *client_addr,
+	  dns_rdataclass_t qclass, dns_rdatatype_t qtype, dns_name_t *qname,
+	  dns_rrl_rtype_t rtype, isc_stdtime_t now, isc_boolean_t create,
+	  char *log_buf, unsigned int log_buf_len)
+{
+	dns_rrl_key_t key;
+	isc_uint32_t hval;
+	dns_rrl_entry_t *e;
+	dns_rrl_hash_t *hash;
+	dns_rrl_bin_t *new_bin, *old_bin;
+	int probes, age;
+
+	make_key(rrl, &key, client_addr, qtype, qname, qclass, rtype);
+	hval = hash_key(&key);
+
+	/*
+	 * Look for the entry in the current hash table.
+	 */
+	new_bin = get_bin(rrl->hash, hval);
+	probes = 1;
+	e = ISC_LIST_HEAD(*new_bin);
+	while (e != NULL) {
+		if (key_cmp(&e->key, &key)) {
+			ref_entry(rrl, e, probes, now);
+			return (e);
+		}
+		++probes;
+		e = ISC_LIST_NEXT(e, hlink);
+	}
+
+	/*
+	 * Look in the old hash table.
+	 */
+	if (rrl->old_hash != NULL) {
+		old_bin = get_bin(rrl->old_hash, hval);
+		e = ISC_LIST_HEAD(*old_bin);
+		while (e != NULL) {
+			if (key_cmp(&e->key, &key)) {
+				ISC_LIST_UNLINK(*old_bin, e, hlink);
+				ISC_LIST_PREPEND(*new_bin, e, hlink);
+				e->hash_gen = rrl->hash_gen;
+				ref_entry(rrl, e, probes, now);
+				return (e);
+			}
+		     e = ISC_LIST_NEXT(e, hlink);
+		}
+
+		/*
+		 * Discard prevous hash table when all of its entries are old.
+		 */
+		age = delta_rrl_time(rrl->old_hash->check_time, now);
+		if (age > rrl->window)
+			free_old_hash(rrl);
+	}
+
+	if (!create)
+		return (NULL);
+
+	/*
+	 * The entry does not exist, so create it by finding a free entry.
+	 * Keep currently penalized and logged entries.
+	 * Try to make more entries if none are idle.
+	 * Steal the oldest entry if we cannot create more.
+	 */
+	for (e = ISC_LIST_TAIL(rrl->lru); e != NULL; e = ISC_LIST_PREV(e, lru)) {
+		if (!ISC_LINK_LINKED(e, hlink))
+			break;
+		age = get_age(rrl, e, now);
+		if (age <= 1) {
+			e = NULL;
+			break;
+		}
+		if (!e->logged && response_balance(rrl, e, age) >= 0)
+			break;
+	}
+	if (e == NULL) {
+		expand_entries(rrl, ISC_MIN((rrl->num_entries+1)/2, 1000));
+		e = ISC_LIST_TAIL(rrl->lru);
+	}
+	if (e->logged)
+		log_end(rrl, e, ISC_TRUE, log_buf, log_buf_len);
+	if (ISC_LINK_LINKED(e, hlink)) {
+		if (e->hash_gen == rrl->hash_gen)
+			hash = rrl->hash;
+		else
+			hash = rrl->old_hash;
+		old_bin = get_bin(hash, hash_key(&e->key));
+		ISC_LIST_UNLINK(*old_bin, e, hlink);
+	}
+	ISC_LIST_PREPEND(*new_bin, e, hlink);
+	e->hash_gen = rrl->hash_gen;
+	e->key = key;
+	e->ts_valid = ISC_FALSE;
+	ref_entry(rrl, e, probes, now);
+	return (e);
+}
+
+static void
+debit_log(const dns_rrl_entry_t *e, int age, const char *action) {
+	char buf[sizeof("age=12345678")];
+	const char *age_str;
+
+	if (age == DNS_RRL_FOREVER) {
+		age_str = "";
+	} else {
+		snprintf(buf, sizeof(buf), "age=%d", age);
+		age_str = buf;
+	}
+	isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+		      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3,
+		      "rrl %08x %6s  responses=%-3d %s",
+		      hash_key(&e->key), age_str, e->responses, action);
+}
+
+static inline dns_rrl_result_t
+debit_rrl_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, double qps, double scale,
+		const isc_sockaddr_t *client_addr, isc_stdtime_t now,
+		char *log_buf, unsigned int log_buf_len)
+{
+	int rate, new_rate, *ratep, slip, new_slip, age, log_secs, min;
+	const char *rate_str;
+	dns_rrl_entry_t const *credit_e;
+
+	/*
+	 * Pick the rate counter.
+	 * Optionally adjust the rate by the estimated query/second rate.
+	 */
+	switch (e->key.s.rtype) {
+	case DNS_RRL_RTYPE_QUERY:
+		rate = rrl->responses_per_second;
+		ratep = &rrl->scaled_responses_per_second;
+		break;
+	case DNS_RRL_RTYPE_NXDOMAIN:
+		rate = rrl->nxdomains_per_second;
+		ratep = &rrl->scaled_nxdomains_per_second;
+		break;
+	case DNS_RRL_RTYPE_ERROR:
+		rate = rrl->errors_per_second;
+		ratep = &rrl->scaled_errors_per_second;
+		break;
+	case DNS_RRL_RTYPE_ALL:
+		rate = rrl->all_per_second;
+		ratep = &rrl->scaled_all_per_second;
+		break;
+	default:
+		INSIST(0);
+	}
+	if (rate == 0)
+		return (DNS_RRL_RESULT_OK);
+
+	if (scale < 1.0) {
+		/*
+		 * The limit for clients that have used TCP is not scaled.
+		 */
+		credit_e = get_entry(rrl, client_addr,
+				     0, dns_rdatatype_none, NULL,
+				     DNS_RRL_RTYPE_TCP, now, ISC_FALSE,
+				     log_buf, log_buf_len);
+		if (credit_e != NULL) {
+			age = get_age(rrl, e, now);
+			if (age < rrl->window)
+				scale = 1.0;
+		}
+	}
+	if (scale < 1.0) {
+		new_rate = rate * scale;
+		if (new_rate < 1)
+			new_rate = 1;
+		if (*ratep != new_rate) {
+			if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) {
+				switch (e->key.s.rtype) {
+				case DNS_RRL_RTYPE_QUERY:
+					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 recent past as no time.
+	 * Treat entries older than the window as if they were just created
+	 * Credit other entries.
+	 */
+	age = get_age(rrl, e, now);
+	if (age > 0) {
+		/*
+		 * Credit tokens earned during elapsed time.
+		 */
+		if (age > rrl->window) {
+			e->responses = rate;
+			e->slip_cnt = 0;
+		} else {
+			e->responses += rate*age;
+			if (e->responses > rate) {
+				e->responses = rate;
+				e->slip_cnt = 0;
+			}
+		}
+		/*
+		 * Find the seconds since last log message without overflowing
+		 * small counter.  This counter is reset when an entry is
+		 * created.  It is not necessarily reset when some requests
+		 * are answered provided other requests continue to be dropped
+		 * or slipped.  This can happen when the request rate is just
+		 * at the limit.
+		 */
+		if (e->logged) {
+			log_secs = e->log_secs;
+			log_secs += age;
+			if (log_secs > DNS_RRL_MAX_LOG_SECS || log_secs < 0)
+				log_secs = DNS_RRL_MAX_LOG_SECS;
+			e->log_secs = log_secs;
+		}
+	}
+	set_age(rrl, e, now);
+
+	/*
+	 * Debit the entry for this response.
+	 */
+	if (--e->responses >= 0) {
+		if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
+			debit_log(e, age, "");
+		return (DNS_RRL_RESULT_OK);
+	}
+
+	if (e->responses < min)
+		e->responses = min;
+
+	/*
+	 * Drop this response unless it should slip or leak.
+	 */
+	slip = rrl->slip;
+	if (slip > 2 && scale < 1.0) {
+		new_slip *= scale;
+		if (new_slip < 2)
+			new_slip = 2;
+		if (rrl->scaled_slip != new_slip) {
+			if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1))
+				isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+					      DNS_LOGMODULE_REQUEST,
+					      DNS_RRL_LOG_DEBUG1,
+					      "%d qps scaled slip"
+					      " by %.2f from %d to %d",
+					      (int)qps, scale,
+					      slip, new_slip);
+			slip = new_slip;
+			rrl->scaled_slip = slip;
+		}
+	}
+	if (slip != 0 && ++e->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);
+	}
+
+	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, &region);
+	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, 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.s.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.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_NXDOMAIN) {
+		qbuf = get_qname(rrl, e);
+		if (save_qname && qbuf == NULL &&
+		    qname != NULL && dns_name_isabsolute(qname)) {
+			/*
+			 * Capture the qname for the "stop limiting" message.
+			 */
+			qbuf = ISC_LIST_TAIL(rrl->qname_free);
+			if (qbuf != NULL) {
+				ISC_LIST_UNLINK(rrl->qname_free, qbuf, link);
+			} else if (rrl->num_qnames < DNS_RRL_QNAMES) {
+				qbuf = isc_mem_get(rrl->mctx, sizeof(*qbuf));
+				if (qbuf != NULL) {
+					memset(qbuf, 0, sizeof(*qbuf));
+					qbuf->index = rrl->num_qnames;
+					rrl->qnames[rrl->num_qnames++] = qbuf;
+				} else {
+					isc_log_write(dns_lctx,
+						      DNS_LOGCATEGORY_RRL,
+						      DNS_LOGMODULE_REQUEST,
+						      DNS_RRL_LOG_FAIL,
+						      "isc_mem_get(%d)"
+						      " failed for RRL qname",
+						      (int)sizeof(*qbuf));
+				}
+			}
+			if (qbuf != NULL) {
+				e->log_qname = qbuf->index;
+				qbuf->e = e;
+				dns_fixedname_init(&qbuf->qname);
+				dns_name_copy(qname,
+					      dns_fixedname_name(&qbuf->qname),
+					      NULL);
+			}
+		}
+		if (qbuf != NULL)
+			qname = dns_fixedname_name(&qbuf->qname);
+		if (qname != NULL) {
+			ADD_LOG_CSTR(&lb, " for ");
+			dns_name_totext(qname, ISC_TRUE, &lb);
+			ADD_LOG_CSTR(&lb, " ");
+		} else {
+			ADD_LOG_CSTR(&lb, " for (?) ");
+		}
+		dns_rdataclass_totext(e->key.s.qclass, &lb);
+		ADD_LOG_CSTR(&lb, " ");
+		dns_rdatatype_totext(e->key.s.qtype, &lb);
+		snprintf(strbuf, sizeof(strbuf), "  (%08x)",
+			 e->key.s.qname_hash);
+		add_log_str(&lb, strbuf, strlen(strbuf));
+	}
+
+	/*
+	 * We saved room for '\0'.
+	 */
+	log_buf[isc_buffer_usedlength(&lb)] = '\0';
+}
+
+static void
+log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_boolean_t early,
+	char *log_buf, unsigned int log_buf_len)
+{
+	if (e->logged) {
+		make_log_buf(rrl, e,
+			     early ? "*" : NULL,
+			     rrl->log_only ? "would stop limiting "
+					   : "stop limiting ",
+			     ISC_TRUE, NULL, ISC_FALSE,
+			     DNS_RRL_RESULT_OK, 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;
+		--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, 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;
+	}
+
+	/*
+	 * Do maintenance once per second.
+	 */
+	if (rrl->num_logged > 0 && rrl->log_stops_time != now)
+		log_stops(rrl, now, 8, log_buf, log_buf_len);
+
+	/*
+	 * Notice TCP responses when scaling limits by qps.
+	 * Do not try to rate limit TCP responses.
+	 */
+	if (is_tcp) {
+		if (scale < 1.0) {
+			e = get_entry(rrl, client_addr,
+				      0, dns_rdatatype_none, NULL,
+				      DNS_RRL_RTYPE_TCP, now, ISC_TRUE,
+				      log_buf, log_buf_len);
+			if (e != NULL) {
+				e->responses = -(rrl->window+1);
+				set_age(rrl, e, now);
+			}
+		}
+		UNLOCK(&rrl->lock);
+		return (ISC_R_SUCCESS);
+	}
+
+	/*
+	 * Find the right kind of entry, creating it if necessary.
+	 * If that is impossible, then nothing more can be done
+	 */
+	if (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_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, 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.
+		 * 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, rcode,
+					     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, rcode, log_buf, log_buf_len);
+		if (!e->logged) {
+			e->logged = ISC_TRUE;
+			if (++rrl->num_logged <= 1)
+				rrl->last_logged = e;
+		}
+		e->log_secs = 0;
+		/*
+		 * Avoid holding the lock.
+		 */
+		if (!wouldlog) {
+			UNLOCK(&rrl->lock);
+			e = NULL;
+		}
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
+			      "%s", log_buf);
+	}
+
+	/*
+	 * Make a log message for the caller.
+	 */
+	if (wouldlog)
+		make_log_buf(rrl, e, rrl->log_only ? "would " : NULL,
+			     NULL, ISC_FALSE, qname, ISC_FALSE,
+			     rrl_result, 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;
+	char log_buf[DNS_RRL_LOG_BUF_LEN];
+	int i;
+
+	rrl = view->rrl;
+	if (rrl == NULL)
+		return;
+	view->rrl = NULL;
+
+	/*
+	 * Assume the caller takes care of locking the view and anything else.
+	 */
+
+	if (rrl->num_logged > 0)
+		log_stops(rrl, 0, ISC_INT32_MAX, log_buf, sizeof(log_buf));
+
+	for (i = 0; i < DNS_RRL_QNAMES; ++i) {
+		if (rrl->qnames[i] == NULL)
+			break;
+		isc_mem_put(rrl->mctx, rrl->qnames[i], sizeof(*rrl->qnames[i]));
+	}
+
+	if (rrl->exempt != NULL)
+		dns_acl_detach(&rrl->exempt);
+
+	DESTROYLOCK(&rrl->lock);
+
+	while (!ISC_LIST_EMPTY(rrl->blocks)) {
+		b = ISC_LIST_HEAD(rrl->blocks);
+		ISC_LIST_UNLINK(rrl->blocks, b, link);
+		isc_mem_put(rrl->mctx, b, b->size);
+	}
+
+	h = rrl->hash;
+	if (h != NULL)
+		isc_mem_put(rrl->mctx, h,
+			    sizeof(*h)+(h->length-1)*sizeof(h->bins[0]));
+
+	h = rrl->old_hash;
+	if (h != NULL)
+		isc_mem_put(rrl->mctx, h,
+			    sizeof(*h)+(h->length-1)*sizeof(h->bins[0]));
+
+	isc_mem_put(rrl->mctx, rrl, sizeof(*rrl));
+}
+
+isc_result_t
+dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries) {
+	dns_rrl_t *rrl;
+	isc_result_t result;
+
+	*rrlp = NULL;
+
+	rrl = isc_mem_get(view->mctx, sizeof(*rrl));
+	if (rrl == NULL)
+		return (ISC_R_NOMEMORY);
+	memset(rrl, 0, sizeof(*rrl));
+	rrl->mctx = view->mctx;
+	result = isc_mutex_init(&rrl->lock);
+	if (result != ISC_R_SUCCESS) {
+		isc_mem_put(view->mctx, rrl, sizeof(*rrl));
+		return (result);
+	}
+	isc_stdtime_get(&rrl->ts_bases[0]);
+
+	view->rrl = rrl;
+
+	result = expand_entries(rrl, min_entries);
+	if (result != ISC_R_SUCCESS) {
+		dns_rrl_view_destroy(view);
+		return (result);
+	}
+	result = expand_rrl_hash(rrl, 0);
+	if (result != ISC_R_SUCCESS) {
+		dns_rrl_view_destroy(view);
+		return (result);
+	}
+
+	*rrlp = rrl;
+	return (ISC_R_SUCCESS);
+}
diff -r -u lib/dns/view.c-orig lib/dns/view.c
--- lib/dns/view.c-orig	2004-01-01 00:00:00.000000000 +0000
+++ lib/dns/view.c	2004-01-01 00:00:00.000000000 +0000
@@ -48,6 +48,7 @@
 #include <dns/masterdump.h>
 #include <dns/order.h>
 #include <dns/peer.h>
+#include <dns/rrl.h>
 #include <dns/rbt.h>
 #include <dns/rdataset.h>
 #include <dns/request.h>
@@ -181,6 +182,7 @@
 	view->answeracl_exclude = NULL;
 	view->denyanswernames = NULL;
 	view->answernames_exclude = NULL;
+	view->rrl = NULL;
 	view->requestixfr = ISC_TRUE;
 	view->provideixfr = ISC_TRUE;
 	view->maxcachettl = 7 * 24 * 3600;
@@ -192,9 +194,7 @@
 	view->maxudp = 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;
 #ifdef BIND9
@@ -329,10 +329,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
@@ -125,8 +125,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
@@ -615,19 +615,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
-dns_rpz_needed
-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_set_need
+dns_rpz_ready
 dns_rpz_str2policy
 dns_rpz_type2str
-dns_rpz_view_destroy
 dns_rriterator_current
 dns_rriterator_destroy
 dns_rriterator_first
@@ -635,6 +638,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
@@ -778,6 +784,7 @@
 dns_zone_forcereload
 dns_zone_forwardupdate
 dns_zone_fulldumptostream
+dns_zone_get_rpz_num
 dns_zone_getadded
 dns_zone_getchecknames
 dns_zone_getclass
@@ -803,6 +810,7 @@
 dns_zone_getprivatetype
 dns_zone_getqueryacl
 dns_zone_getrequeststats
+dns_zone_getrpz_num
 dns_zone_getserial
 dns_zone_getserial2
 dns_zone_getsigresigninginterval
@@ -835,6 +843,8 @@
 dns_zone_refresh
 dns_zone_rekey
 dns_zone_replacedb
+dns_zone_rpz_attach
+dns_zone_rpz_enable
 dns_zone_setacache
 dns_zone_setadded
 dns_zone_setalsonotify
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
@@ -342,6 +342,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
@@ -638,6 +642,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
@@ -183,6 +183,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"
@@ -306,6 +307,7 @@
 	"$(INTDIR)\result.obj" \
 	"$(INTDIR)\rootns.obj" \
 	"$(INTDIR)\rpz.obj" \
+	"$(INTDIR)\rrl.obj" \
 	"$(INTDIR)\rriterator.obj" \
 	"$(INTDIR)\sdb.obj" \
 	"$(INTDIR)\sdlz.obj" \
@@ -499,6 +501,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"
@@ -642,6 +646,7 @@
 	"$(INTDIR)\result.sbr" \
 	"$(INTDIR)\rootns.sbr" \
 	"$(INTDIR)\rpz.sbr" \
+	"$(INTDIR)\rrl.sbr" \
 	"$(INTDIR)\rriterator.sbr" \
 	"$(INTDIR)\sdb.sbr" \
 	"$(INTDIR)\sdlz.sbr" \
@@ -737,6 +742,7 @@
 	"$(INTDIR)\result.obj" \
 	"$(INTDIR)\rootns.obj" \
 	"$(INTDIR)\rpz.obj" \
+	"$(INTDIR)\rrl.obj" \
 	"$(INTDIR)\rriterator.obj" \
 	"$(INTDIR)\sdb.obj" \
 	"$(INTDIR)\sdlz.obj" \
@@ -1695,6 +1701,23 @@
 
 
 !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
 
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
@@ -335,9 +335,10 @@
 	isc_boolean_t           added;
 
 	/*%
-	 * whether a rpz radix was needed when last loaded
+	 * response policy data to be relayed to the database
 	 */
-	isc_boolean_t           rpz_zone;
+	dns_rpz_zones_t		*rpzs;
+	dns_rpz_num_t		rpz_num;
 
 	/*%
 	 * Outstanding forwarded UPDATE requests.
@@ -855,7 +856,8 @@
 	zone->nodes = 100;
 	zone->privatetype = (dns_rdatatype_t)0xffffU;
 	zone->added = ISC_FALSE;
-	zone->rpz_zone = ISC_FALSE;
+	zone->rpzs = NULL;
+	zone->rpz_num = DNS_RPZ_INVALID_NUM;
 	ISC_LIST_INIT(zone->forwards);
 
 	zone->magic = ZONE_MAGIC;
@@ -951,6 +953,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.cnt);
+		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);
@@ -1391,6 +1400,45 @@
 }
 
 
+/*
+ * Set the response policy index and information for a zone.
+ */
+isc_result_t
+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.
+	 * Only zones that are loaded instead of mmap()ed create the
+	 * summary data and so can be policy zones.
+	 */
+	if (strcmp(zone->db_argv[0], "rbt") != 0 &&
+	    strcmp(zone->db_argv[0], "rbt64") != 0)
+		return (ISC_R_NOTIMPLEMENTED);
+
+	/*
+	 * 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);
+}
+
+dns_rpz_num_t
+dns_zone_get_rpz_num(dns_zone_t *zone) {
+	return (zone->rpz_num);
+}
+
 static isc_result_t
 zone_load(dns_zone_t *zone, unsigned int flags) {
 	isc_result_t result;
@@ -1459,8 +1507,7 @@
 		 * "rndc reconfig", we are done.
 		 */
 		if (!isc_time_isepoch(&zone->loadtime) &&
-		    (flags & DNS_ZONELOADFLAG_NOSTAT) != 0 &&
-		    zone->rpz_zone == dns_rpz_needed()) {
+		    (flags & DNS_ZONELOADFLAG_NOSTAT) != 0) {
 			result = ISC_R_SUCCESS;
 			goto cleanup;
 		}
@@ -1469,8 +1516,7 @@
 		if (result == ISC_R_SUCCESS) {
 			if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) &&
 			    !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HASINCLUDE) &&
-			    isc_time_compare(&filetime, &zone->loadtime) <= 0 &&
-			    zone->rpz_zone == dns_rpz_needed()) {
+			    isc_time_compare(&filetime, &zone->loadtime) <= 0) {
 				dns_zone_log(zone, ISC_LOG_DEBUG(1),
 					     "skipping load: master file "
 					     "older than last load");
@@ -1478,7 +1524,6 @@
 				goto cleanup;
 			}
 			loadtime = filetime;
-			zone->rpz_zone = dns_rpz_needed();
 		}
 	}
 
@@ -1704,8 +1749,14 @@
 	isc_result_t tresult;
 	unsigned int options;
 
-	options = get_master_options(zone);
+#ifdef BIND9
+	if (zone->rpz_num != DNS_RPZ_INVALID_NUM) {
+		REQUIRE(zone->rpzs != NULL);
+		dns_db_rpz_attach(db, zone->rpzs, zone->rpz_num);
+	}
+#endif
 
+	options = get_master_options(zone);
 	if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MANYERRORS))
 		options |= DNS_MASTER_MANYERRORS;
 
@@ -3587,6 +3638,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,
@@ -11615,6 +11671,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
@@ -1018,10 +1018,9 @@
  *  response-policy {
  *	zone <string> [ policy (given|disabled|passthru|
  *					nxdomain|nodata|cname <domain> ) ]
- *		      [ recursive-only yes|no ]
- *		      [ max-policy-ttl number ] ;
- *  } [ recursive-only yes|no ] [ break-dnssec yes|no ]
- *	[ max-policy-ttl number ] ;
+ *		      [ recursive-only yes|no ] [ max-policy-ttl number ] ;
+ *  } [ recursive-only yes|no ] [ max-policy-ttl number ] ;
+ *	 [ break-dnssec yes|no ] [ min-ns-dots number ] ;
  */
 
 static void
@@ -1223,6 +1222,7 @@
 	{ "recursive-only", &cfg_type_boolean, 0 },
 	{ "break-dnssec", &cfg_type_boolean, 0 },
 	{ "max-policy-ttl", &cfg_type_uint32, 0 },
+	{ "min-ns-dots", &cfg_type_uint32, 0 },
 	{ NULL, NULL, 0 }
 };
 static cfg_type_t cfg_type_rpz = {
@@ -1232,6 +1232,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
  */
@@ -1385,6 +1418,7 @@
 	   CFG_CLAUSEFLAG_NOTCONFIGURED },
 #endif
 	{ "response-policy", &cfg_type_rpz, 0 },
+	{ "rate-limit", &cfg_type_rrl, 0 },
 	{ NULL, NULL, 0 }
 };
 
diff -r -u version-orig version
--- version-orig	2004-01-01 00:00:00.000000000 +0000
+++ version	2004-01-01 00:00:00.000000000 +0000
@@ -5,6 +5,6 @@
 #
 MAJORVER=9
 MINORVER=8
-PATCHVER=4
+PATCHVER=4-rpz2+rl005.12
 RELEASETYPE=-P
 RELEASEVER=1

--- End Message ---
--- Begin Message ---
On Thu, 2013-02-14 at 08:21 -0700, LaMont Jones wrote:
> On Mon, Jan 21, 2013 at 08:44:55PM +0000, Adam D. Barratt wrote:
[none of the quoted text - at least directly]

> 1:9.8.4.dfsg.P1-5 is now at 14 days in sid with no reported regressions
> relative to -4.
> 
> Please consider unblocking it.

Unblocked, with a couple of crossed fingers. :)

Regards,

Adam

--- End Message ---

Reply to: