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

Bug#698658: bind9 with fix for 698641



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

Reply to: