File rpz2+rl-9.9.5.patch of Package bind

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
@@ -879,11 +879,11 @@
 static void
 rpz_log_rewrite(ns_client_t *client, isc_boolean_t disabled,
 		dns_rpz_policy_t policy, dns_rpz_type_t type,
-		dns_zone_t *zone, dns_name_t *rpz_qname)
+		dns_zone_t *p_zone, dns_name_t *p_name)
 {
 	isc_stats_t *zonestats;
 	char qname_buf[DNS_NAME_FORMATSIZE];
-	char rpz_qname_buf[DNS_NAME_FORMATSIZE];
+	char p_name_buf[DNS_NAME_FORMATSIZE];
 
 	/*
 	 * Count enabled rewrites in the global counter.
@@ -893,8 +893,8 @@
 		isc_stats_increment(ns_g_server->nsstats,
 				    dns_nsstatscounter_rpz_rewrites);
 	}
-	if (zone != NULL) {
-		zonestats = dns_zone_getrequeststats(zone);
+	if (p_zone != NULL) {
+		zonestats = dns_zone_getrequeststats(p_zone);
 		if (zonestats != NULL)
 			isc_stats_increment(zonestats,
 					    dns_nsstatscounter_rpz_rewrites);
@@ -904,68 +904,73 @@
 		return;
 
 	dns_name_format(client->query.qname, qname_buf, sizeof(qname_buf));
-	dns_name_format(rpz_qname, rpz_qname_buf, sizeof(rpz_qname_buf));
+	dns_name_format(p_name, p_name_buf, sizeof(p_name_buf));
 
 	ns_client_log(client, DNS_LOGCATEGORY_RPZ, NS_LOGMODULE_QUERY,
 		      DNS_RPZ_INFO_LEVEL, "%srpz %s %s rewrite %s via %s",
 		      disabled ? "disabled " : "",
 		      dns_rpz_type2str(type), dns_rpz_policy2str(policy),
-		      qname_buf, rpz_qname_buf);
+		      qname_buf, p_name_buf);
 }
 
 static void
-rpz_log_fail(ns_client_t *client, int level,
-	     dns_rpz_type_t rpz_type, dns_name_t *name,
-	     const char *str, isc_result_t result)
+rpz_log_fail(ns_client_t *client, int level, dns_name_t *p_name,
+	     dns_rpz_type_t rpz_type, const char *str, isc_result_t result)
 {
-	char namebuf1[DNS_NAME_FORMATSIZE];
-	char namebuf2[DNS_NAME_FORMATSIZE];
+	char qnamebuf[DNS_NAME_FORMATSIZE];
+	char p_namebuf[DNS_NAME_FORMATSIZE];
+	const char *failed;
 
 	if (!isc_log_wouldlog(ns_g_lctx, level))
 		return;
 
 	/*
-	 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+	 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed" for problems.
 	 */
-	dns_name_format(client->query.qname, namebuf1, sizeof(namebuf1));
-	dns_name_format(name, namebuf2, sizeof(namebuf2));
+	if (level <= DNS_RPZ_DEBUG_LEVEL1)
+		failed = "failed: ";
+	else
+		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",
+		      "rpz %s rewrite %s via %s%s%s%s",
 		      dns_rpz_type2str(rpz_type),
-		      namebuf1, namebuf2, str, isc_result_totext(result));
+		      qnamebuf, p_namebuf,
+		      str, failed, 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,
-		     "query_getzonedb() ", result);
+	rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type,
+		     " query_getzonedb()", result);
 	return (result);
 }
 
@@ -3913,7 +3918,7 @@
 		dns_rdataset_disassociate(*rdatasetp);
 }
 
-static void
+static inline void
 rpz_match_clear(dns_rpz_st_t *st)
 {
 	rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset);
@@ -3921,16 +3926,16 @@
 }
 
 static inline isc_result_t
-rpz_ready(ns_client_t *client, dns_zone_t **zonep, dns_db_t **dbp,
-	  dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp)
+rpz_ready(ns_client_t *client, dns_rdataset_t **rdatasetp)
 {
 	REQUIRE(rdatasetp != NULL);
 
-	rpz_clean(zonep, dbp, nodep, rdatasetp);
 	if (*rdatasetp == NULL) {
 		*rdatasetp = query_newrdataset(client);
 		if (*rdatasetp == NULL)
 			return (DNS_R_SERVFAIL);
+	} else if (dns_rdataset_isassociated(*rdatasetp)) {
+		dns_rdataset_disassociate(*rdatasetp);
 	}
 	return (ISC_R_SUCCESS);
 }
@@ -3959,13 +3964,83 @@
 	st->m.policy = DNS_RPZ_POLICY_MISS;
 }
 
+static dns_rpz_zbits_t
+rpz_get_zbits(ns_client_t *client,
+	      dns_rdatatype_t ip_type, dns_rpz_type_t rpz_type)
+{
+	dns_rpz_zones_t *rpzs;
+	dns_rpz_st_t *st;
+	dns_rpz_zbits_t zbits;
+
+	rpzs = client->view->rpzs;
+
+	switch (rpz_type) {
+	case DNS_RPZ_TYPE_CLIENT_IP:
+		zbits = rpzs->have.client_ip;
+		break;
+	case DNS_RPZ_TYPE_QNAME:
+		zbits = rpzs->have.qname;
+		break;
+	case DNS_RPZ_TYPE_IP:
+		if (ip_type == dns_rdatatype_a) {
+			zbits = rpzs->have.ipv4;
+		} else if (ip_type == dns_rdatatype_aaaa) {
+			zbits = rpzs->have.ipv6;
+		} else {
+			zbits = rpzs->have.ip;
+		}
+		break;
+	case DNS_RPZ_TYPE_NSDNAME:
+		zbits = rpzs->have.nsdname;
+		break;
+	case DNS_RPZ_TYPE_NSIP:
+		if (ip_type == dns_rdatatype_a) {
+			zbits = rpzs->have.nsipv4;
+		} else if (ip_type == dns_rdatatype_aaaa) {
+			zbits = rpzs->have.nsipv6;
+		} else {
+			zbits = rpzs->have.nsip;
+		}
+		break;
+	default:
+		INSIST(0);
+		break;
+	}
+
+	st = client->query.rpz_st;
+
+	/*
+	 * Choose
+	 *	the earliest configured policy zone (rpz->num)
+	 *	QNAME over IP over NSDNAME over NSIP (rpz_type)
+	 *	the smallest name,
+	 *	the longest IP address prefix,
+	 *	the lexically smallest address.
+	 */
+	if (st->m.policy != DNS_RPZ_POLICY_MISS) {
+		if (st->m.type >= rpz_type) {
+			zbits &= DNS_RPZ_ZMASK(st->m.rpz->num);
+		} else{
+			zbits &= DNS_RPZ_ZMASK(st->m.rpz->num) >> 1;
+		}
+	}
+
+	/*
+	 * If the client wants recursion, allow only compatible policies.
+	 */
+	if (!RECURSIONOK(client))
+		zbits &= rpzs->p.no_rd_ok;
+
+	return (zbits);
+}
+
 /*
- * Get NS, A, or AAAA rrset for response policy zone checks.
+ * Get an NS, A, or AAAA rrset related to the response for the client
+ * to check the contents of that rrset for hits by eligible policy zones.
  */
 static isc_result_t
-rpz_rrset_find(ns_client_t *client, dns_rpz_type_t rpz_type,
-	       dns_name_t *name, dns_rdatatype_t type,
-	       dns_db_t **dbp, dns_dbversion_t *version,
+rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type,
+	       dns_rpz_type_t rpz_type, dns_db_t **dbp, dns_dbversion_t *version,
 	       dns_rdataset_t **rdatasetp, isc_boolean_t resuming)
 {
 	dns_rpz_st_t *st;
@@ -3977,15 +4052,13 @@
 	dns_clientinfomethods_t cm;
 	dns_clientinfo_t ci;
 
-	dns_clientinfomethods_init(&cm, ns_client_sourceip);
-	dns_clientinfo_init(&ci, client);
-
 	st = client->query.rpz_st;
 	if ((st->state & DNS_RPZ_RECURSING) != 0) {
 		INSIST(st->r.r_type == type);
 		INSIST(dns_name_equal(name, st->r_name));
 		INSIST(*rdatasetp == NULL ||
 		       !dns_rdataset_isassociated(*rdatasetp));
+		INSIST(*dbp == NULL);
 		st->state &= ~DNS_RPZ_RECURSING;
 		*dbp = st->r.db;
 		st->r.db = NULL;
@@ -3995,16 +4068,15 @@
 		st->r.r_rdataset = NULL;
 		result = st->r.r_result;
 		if (result == DNS_R_DELEGATION) {
-			rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL,
-				     rpz_type, name,
-				     "rpz_rrset_find(1) ", result);
+			rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name,
+				     rpz_type, " rpz_rrset_find(1)", result);
 			st->m.policy = DNS_RPZ_POLICY_ERROR;
 			result = DNS_R_SERVFAIL;
 		}
 		return (result);
 	}
 
-	result = rpz_ready(client, NULL, NULL, NULL, rdatasetp);
+	result = rpz_ready(client, rdatasetp);
 	if (result != ISC_R_SUCCESS) {
 		st->m.policy = DNS_RPZ_POLICY_ERROR;
 		return (result);
@@ -4019,9 +4091,8 @@
 		result = query_getdb(client, name, type, 0, &zone, dbp,
 				     &version, &is_zone);
 		if (result != ISC_R_SUCCESS) {
-			rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL,
-				     rpz_type, name,
-				     "rpz_rrset_find(2) ", result);
+			rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name,
+				     rpz_type, " rpz_rrset_find(2)", result);
 			st->m.policy = DNS_RPZ_POLICY_ERROR;
 			if (zone != NULL)
 				dns_zone_detach(&zone);
@@ -4034,6 +4105,8 @@
 	node = NULL;
 	dns_fixedname_init(&fixed);
 	found = dns_fixedname_name(&fixed);
+	dns_clientinfomethods_init(&cm, ns_client_sourceip);
+	dns_clientinfo_init(&ci, client);
 	result = dns_db_findext(*dbp, name, version, type, DNS_DBFIND_GLUEOK,
 				client->now, &node, found,
 				&cm, &ci, *rdatasetp, NULL);
@@ -4072,177 +4145,97 @@
 }
 
 /*
- * Check the IP address in an A or AAAA rdataset against
- * the IP or NSIP response policy rules of a view.
+ * Compute a policy owner name, p_name, in a policy zone given the needed
+ *	policy type and the trigger name.
  */
 static isc_result_t
-rpz_rewrite_ip(ns_client_t *client, dns_rdataset_t *rdataset,
-	       dns_rpz_type_t rpz_type)
+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:
-	case DNS_R_GLUE:
-	case DNS_R_ZONECUT:
-		result = rpz_rewrite_ip(client, *rdatasetp, rpz_type);
+	/*
+	 * The policy owner name consists of a suffix depending on the type
+	 * and policy zone and a prefix that is the longest possible string
+	 * from the trigger name that keesp the resulting policy owner name
+	 * from being too long.
+	 */
+	switch (rpz_type) {
+	case DNS_RPZ_TYPE_CLIENT_IP:
+		suffix = &rpz->client_ip;
 		break;
-	case DNS_R_EMPTYNAME:
-	case DNS_R_EMPTYWILD:
-	case DNS_R_NXDOMAIN:
-	case DNS_R_NCACHENXDOMAIN:
-	case DNS_R_NXRRSET:
-	case DNS_R_NCACHENXRRSET:
-	case ISC_R_NOTFOUND:
-		result = ISC_R_SUCCESS;
+	case DNS_RPZ_TYPE_QNAME:
+		suffix = &rpz->origin;
 		break;
-	case DNS_R_DELEGATION:
-	case DNS_R_DUPLICATE:
-	case DNS_R_DROP:
+	case DNS_RPZ_TYPE_IP:
+		suffix = &rpz->ip;
 		break;
-	case DNS_R_CNAME:
-	case DNS_R_DNAME:
-		rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, rpz_type,
-			     name, "NS address rewrite rrset ", result);
-		result = ISC_R_SUCCESS;
+	case DNS_RPZ_TYPE_NSDNAME:
+		suffix = &rpz->nsdname;
 		break;
-	default:
-		if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) {
-			client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR;
-			rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type,
-				     name, "NS address rewrite rrset ", result);
-		}
+	case DNS_RPZ_TYPE_NSIP:
+		suffix = &rpz->nsip;
 		break;
+	default:
+		INSIST(0);
 	}
-	return (result);
-}
-
-/*
- * Look for both A and AAAA rdatasets
- * and check for IP or NSIP rewrite policy rules.
- * Look only for addresses that will be in the ANSWER section
- * when checking for IP rules.
- */
-static isc_result_t
-rpz_rewrite_rrsets(ns_client_t *client, dns_rpz_type_t rpz_type,
-		   dns_name_t *name, dns_rdatatype_t type,
-		   dns_rdataset_t **rdatasetp, isc_boolean_t resuming)
-{
-	dns_rpz_st_t *st;
-	dns_dbversion_t *version;
-	dns_db_t *ipdb;
-	isc_result_t result;
 
-	st = client->query.rpz_st;
-	version = NULL;
-	ipdb = NULL;
-	if ((st->state & DNS_RPZ_DONE_IPv4) == 0 &&
-	    ((rpz_type == DNS_RPZ_TYPE_NSIP) ?
-	     (st->state & DNS_RPZ_HAVE_NSIPv4) :
-	     (st->state & DNS_RPZ_HAVE_IP)) != 0 &&
-	    (type == dns_rdatatype_any || type == dns_rdatatype_a)) {
-		result = rpz_rewrite_rrset(client, rpz_type, dns_rdatatype_a,
-					   name, &ipdb, version, rdatasetp,
-					   resuming);
+	/*
+	 * Start with relative version of the full trigger name,
+	 * and trim enough allow the addition of the suffix.
+	 */
+	dns_name_init(&prefix, prefix_offsets);
+	labels = dns_name_countlabels(trig_name);
+	first = 0;
+	for (;;) {
+		dns_name_getlabelsequence(trig_name, first, labels-first-1,
+					  &prefix);
+		result = dns_name_concatenate(&prefix, suffix, p_name, NULL);
 		if (result == ISC_R_SUCCESS)
-			st->state |= DNS_RPZ_DONE_IPv4;
-	} else {
-		result = ISC_R_SUCCESS;
-	}
-	if (result == ISC_R_SUCCESS &&
-	    ((rpz_type == DNS_RPZ_TYPE_NSIP) ?
-	     (st->state & DNS_RPZ_HAVE_NSIPv6) :
-	     (st->state & DNS_RPZ_HAVE_IP)) != 0 &&
-	    (type == dns_rdatatype_any || type == dns_rdatatype_aaaa)) {
-		result = rpz_rewrite_rrset(client, rpz_type, dns_rdatatype_aaaa,
-					   name, &ipdb, version, rdatasetp,
-					   resuming);
+			return (ISC_R_SUCCESS);
+		INSIST(result == DNS_R_NAMETOOLONG);
+		/*
+		 * Trim the trigger name until the combination is not too long.
+		 */
+		if (labels-first < 2) {
+			rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, suffix,
+				     rpz_type, " concatentate()", result);
+			return (ISC_R_FAILURE);
+		}
+		/*
+		 * Complain once about trimming the trigger name.
+		 */
+		if (first == 0) {
+			rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, suffix,
+				     rpz_type, " concatentate()", result);
+		}
+		++first;
 	}
-	if (ipdb != NULL)
-		dns_db_detach(&ipdb);
-	return (result);
 }
 
 /*
- * Get the rrset from a response policy zone.
+ * Look in policy zone rpz for a policy of rpz_type by p_name.
+ * The self-name (usually the client qname or an NS name) is compared with
+ * the target of a CNAME policy for the old style passthru encoding.
+ * If found, the policy is recorded in *zonep, *dbp, *versionp, *nodep,
+ * *rdatasetp, and *policyp.
+ * The target DNS type, qtype, chooses the best rdataset for *rdatasetp.
+ * The caller must decide if the found policy is most suitable, including
+ * better than a previously found policy.
+ * If it is best, the caller records it in client->query.rpz_st->m.
  */
 static isc_result_t
-rpz_find(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qnamef,
-	 dns_name_t *sname, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
-	 dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp,
-	 dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp,
-	 dns_rpz_policy_t *policyp)
+rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype,
+	   dns_name_t *p_name, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+	   dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp,
+	   dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp,
+	   dns_rpz_policy_t *policyp)
 {
-	dns_rpz_policy_t policy;
-	dns_fixedname_t fixed;
+	dns_fixedname_t foundf;
 	dns_name_t *found;
 	isc_result_t result;
 	dns_clientinfomethods_t cm;
@@ -4250,31 +4243,28 @@
 
 	REQUIRE(nodep != NULL);
 
-	dns_clientinfomethods_init(&cm, ns_client_sourceip);
-	dns_clientinfo_init(&ci, client);
-
-	result = rpz_ready(client, zonep, dbp, nodep, rdatasetp);
-	if (result != ISC_R_SUCCESS) {
-		*policyp = DNS_RPZ_POLICY_ERROR;
-		return (result);
-	}
-
 	/*
-	 * Try to get either a CNAME or the type of record demanded by the
+	 * Try to find either a CNAME or the type of record demanded by the
 	 * request from the policy zone.
 	 */
+	rpz_clean(zonep, dbp, nodep, rdatasetp);
+	result = rpz_ready(client, rdatasetp);
+	if (result != ISC_R_SUCCESS)
+		return (DNS_R_SERVFAIL);
 	*versionp = NULL;
-	result = rpz_getdb(client, rpz_type, qnamef, zonep, dbp, versionp);
-	if (result != ISC_R_SUCCESS) {
-		*policyp = DNS_RPZ_POLICY_MISS;
+	result = rpz_getdb(client, p_name, rpz_type, zonep, dbp, versionp);
+	if (result != ISC_R_SUCCESS)
 		return (DNS_R_NXDOMAIN);
-	}
-
-	dns_fixedname_init(&fixed);
-	found = dns_fixedname_name(&fixed);
-	result = dns_db_findext(*dbp, qnamef, *versionp, dns_rdatatype_any, 0,
+	dns_fixedname_init(&foundf);
+	found = dns_fixedname_name(&foundf);
+	dns_clientinfomethods_init(&cm, ns_client_sourceip);
+	dns_clientinfo_init(&ci, client);
+	result = dns_db_findext(*dbp, p_name, *versionp, dns_rdatatype_any, 0,
 				client->now, nodep, found, &cm, &ci,
 				*rdatasetp, NULL);
+	/*
+	 * Choose the best rdataset if we found something.
+	 */
 	if (result == ISC_R_SUCCESS) {
 		dns_rdatasetiter_t *rdsiter;
 
@@ -4282,10 +4272,8 @@
 		result = dns_db_allrdatasets(*dbp, *nodep, *versionp, 0,
 					     &rdsiter);
 		if (result != ISC_R_SUCCESS) {
-			dns_db_detachnode(*dbp, nodep);
-			rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type,
-				     qnamef, "allrdatasets() ", result);
-			*policyp = DNS_RPZ_POLICY_ERROR;
+			rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name,
+				     rpz_type, " allrdatasets()", result);
 			return (DNS_R_SERVFAIL);
 		}
 		for (result = dns_rdatasetiter_first(rdsiter);
@@ -4301,9 +4289,8 @@
 		if (result != ISC_R_SUCCESS) {
 			if (result != ISC_R_NOMORE) {
 				rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL,
-					     rpz_type, qnamef, "rdatasetiter ",
-					     result);
-				*policyp = DNS_RPZ_POLICY_ERROR;
+					     p_name, rpz_type,
+					     " rdatasetiter", result);
 				return (DNS_R_SERVFAIL);
 			}
 			/*
@@ -4318,7 +4305,7 @@
 			    qtype == dns_rdatatype_sig)
 				result = DNS_R_NXRRSET;
 			else
-				result = dns_db_findext(*dbp, qnamef, *versionp,
+				result = dns_db_findext(*dbp, p_name, *versionp,
 							qtype, 0, client->now,
 							nodep, found, &cm, &ci,
 							*rdatasetp, NULL);
@@ -4327,162 +4314,476 @@
 	switch (result) {
 	case ISC_R_SUCCESS:
 		if ((*rdatasetp)->type != dns_rdatatype_cname) {
-			policy = DNS_RPZ_POLICY_RECORD;
+			*policyp = DNS_RPZ_POLICY_RECORD;
 		} else {
-			policy = dns_rpz_decode_cname(rpz, *rdatasetp, sname);
-			if ((policy == DNS_RPZ_POLICY_RECORD ||
-			     policy == DNS_RPZ_POLICY_WILDCNAME) &&
+			*policyp = dns_rpz_decode_cname(rpz, *rdatasetp,
+							self_name);
+			if ((*policyp == DNS_RPZ_POLICY_RECORD ||
+			     *policyp == DNS_RPZ_POLICY_WILDCNAME) &&
 			    qtype != dns_rdatatype_cname &&
 			    qtype != dns_rdatatype_any)
-				result = DNS_R_CNAME;
+				return (DNS_R_CNAME);
 		}
-		break;
+		return (ISC_R_SUCCESS);
 	case DNS_R_NXRRSET:
-		policy = DNS_RPZ_POLICY_NODATA;
-		break;
+		*policyp = DNS_RPZ_POLICY_NODATA;
+		return (result);
 	case DNS_R_DNAME:
 		/*
 		 * DNAME policy RRs have very few if any uses that are not
-		 * better served with simple wildcards.  Making the work would
+		 * better served with simple wildcards.  Making them work would
 		 * require complications to get the number of labels matched
 		 * in the name or the found name to the main DNS_R_DNAME case
-		 * in query_find().
-		 */
-		dns_rdataset_disassociate(*rdatasetp);
-		dns_db_detachnode(*dbp, nodep);
-		/*
-		 * Fall through to treat it as a miss.
+		 * in query_find().  The domain also does not appear in the
+		 * summary database at the right level, so this happens only
+		 * with a single policy zone when we have no summary database.
+		 * Treat it as a miss.
 		 */
 	case DNS_R_NXDOMAIN:
 	case DNS_R_EMPTYNAME:
-		/*
-		 * If we don't get a qname hit,
-		 * see if it is worth looking for other types.
-		 */
-		(void)dns_db_rpz_enabled(*dbp, client->query.rpz_st);
-		dns_db_detach(dbp);
-		dns_zone_detach(zonep);
-		result = DNS_R_NXDOMAIN;
-		policy = DNS_RPZ_POLICY_MISS;
-		break;
+		return (DNS_R_NXDOMAIN);
 	default:
-		dns_db_detach(dbp);
-		dns_zone_detach(zonep);
-		rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, rpz_type, qnamef,
+		rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type,
 			     "", result);
 		return (DNS_R_SERVFAIL);
 	}
+}
 
-	*policyp = policy;
-	return (result);
+static void
+rpz_save_p(dns_rpz_st_t *st, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+	   dns_rpz_policy_t policy, dns_name_t *p_name, dns_rpz_prefix_t prefix,
+	   isc_result_t result, dns_zone_t **zonep, dns_db_t **dbp,
+	   dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp,
+	   dns_dbversion_t *version)
+{
+	dns_rdataset_t *trdataset;
+
+	rpz_match_clear(st);
+	st->m.rpz = rpz;
+	st->m.type = rpz_type;
+	st->m.policy = policy;
+	dns_name_copy(p_name, st->p_name, NULL);
+	st->m.prefix = prefix;
+	st->m.result = result;
+	st->m.zone = *zonep;
+	*zonep = NULL;
+	st->m.db = *dbp;
+	*dbp = NULL;
+	st->m.node = *nodep;
+	*nodep = NULL;
+	if (*rdatasetp != NULL && dns_rdataset_isassociated(*rdatasetp)) {
+		/*
+		 * Save the replacement rdataset from the policy
+		 * and make the previous replacement rdataset scratch.
+		 */
+		trdataset = st->m.rdataset;
+		st->m.rdataset = *rdatasetp;
+		*rdatasetp = trdataset;
+		st->m.ttl = ISC_MIN(st->m.rdataset->ttl, rpz->max_policy_ttl);
+	} else {
+		st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT, rpz->max_policy_ttl);
+	}
+	st->m.version = version;
 }
 
 /*
- * Build and look for a QNAME or NSDNAME owner name in a response policy zone.
+ * Check this address in every eligible policy zone.
  */
 static isc_result_t
-rpz_rewrite_name(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
-		 dns_rpz_type_t rpz_type, dns_rdataset_t **rdatasetp)
+rpz_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr,
+	       dns_rdatatype_t qtype, dns_rpz_type_t rpz_type,
+	       dns_rpz_zbits_t zbits, dns_rdataset_t **p_rdatasetp)
 {
+	dns_rpz_zones_t *rpzs;
 	dns_rpz_st_t *st;
 	dns_rpz_zone_t *rpz;
-	dns_fixedname_t prefixf, rpz_qnamef;
-	dns_name_t *prefix, *suffix, *rpz_qname;
-	dns_zone_t *zone;
-	dns_db_t *db;
-	dns_dbversion_t *version;
-	dns_dbnode_t *node;
+	dns_rpz_prefix_t prefix;
+	dns_rpz_num_t rpz_num;
+	dns_fixedname_t ip_namef, p_namef;
+	dns_name_t *ip_name, *p_name;
+	dns_zone_t *p_zone;
+	dns_db_t *p_db;
+	dns_dbversion_t *p_version;
+	dns_dbnode_t *p_node;
 	dns_rpz_policy_t policy;
-	unsigned int labels;
 	isc_result_t result;
 
-	st = client->query.rpz_st;
-	zone = NULL;
-	db = NULL;
-	node = NULL;
+	dns_fixedname_init(&ip_namef);
+	ip_name = dns_fixedname_name(&ip_namef);
 
-	for (rpz = ISC_LIST_HEAD(client->view->rpz_zones);
-	     rpz != NULL;
-	     rpz = ISC_LIST_NEXT(rpz, link)) {
-		if (!RECURSIONOK(client) && rpz->recursive_only)
-			continue;
+	p_zone = NULL;
+	p_db = NULL;
+	p_node = NULL;
+
+	rpzs = client->view->rpzs;
+	st = client->query.rpz_st;
+	while (zbits != 0) {
+		rpz_num = dns_rpz_find_ip(rpzs, rpz_type, zbits, netaddr,
+					  ip_name, &prefix);
+		if (rpz_num == DNS_RPZ_INVALID_NUM)
+			break;
+		zbits &= (DNS_RPZ_ZMASK(rpz_num) >> 1);
 
 		/*
-		 * Do not check policy zones that cannot replace a policy
-		 * already known to match.
+		 * Do not try applying policy zones that cannot replace a
+		 * previously found policy zone.
+		 * Stop looking if the next best choice cannot
+		 * replace what we already have.
 		 */
+		rpz = rpzs->zones[rpz_num];
 		if (st->m.policy != DNS_RPZ_POLICY_MISS) {
 			if (st->m.rpz->num < rpz->num)
 				break;
 			if (st->m.rpz->num == rpz->num &&
-			    st->m.type < rpz_type)
-				continue;
+			    (st->m.type < rpz_type ||
+			     st->m.prefix > prefix))
+				break;
 		}
+
 		/*
-		 * Construct the policy's owner name.
+		 * Get the policy for a prefix at least as long
+		 * as the prefix of the entry we had before.
 		 */
-		dns_fixedname_init(&prefixf);
-		prefix = dns_fixedname_name(&prefixf);
-		dns_name_split(qname, 1, prefix, NULL);
-		if (rpz_type == DNS_RPZ_TYPE_NSDNAME)
-			suffix = &rpz->nsdname;
-		else
-			suffix = &rpz->origin;
-		dns_fixedname_init(&rpz_qnamef);
-		rpz_qname = dns_fixedname_name(&rpz_qnamef);
-		for (;;) {
-			result = dns_name_concatenate(prefix, suffix,
-						      rpz_qname, NULL);
-			if (result == ISC_R_SUCCESS)
-				break;
-			INSIST(result == DNS_R_NAMETOOLONG);
+		dns_fixedname_init(&p_namef);
+		p_name = dns_fixedname_name(&p_namef);
+		result = rpz_get_p_name(client, p_name, rpz, rpz_type, ip_name);
+		if (result != ISC_R_SUCCESS)
+			continue;
+		result = rpz_find_p(client, ip_name, qtype,
+				    p_name, rpz, rpz_type,
+				    &p_zone, &p_db, &p_version, &p_node,
+				    p_rdatasetp, &policy);
+		switch (result) {
+		case DNS_R_NXDOMAIN:
 			/*
-			 * Trim the name until it is not too long.
+			 * Continue after a policy record that is missing
+			 * contrary to the summary data.  The summary
+			 * data can out of date during races with and among
+			 * policy zone updates.
 			 */
-			labels = dns_name_countlabels(prefix);
-			if (labels < 2) {
-				rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL,
-					     rpz_type, suffix,
-					     "concatentate() ", result);
-				return (ISC_R_SUCCESS);
-			}
-			if (labels+1 == dns_name_countlabels(qname)) {
-				rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1,
-					     rpz_type, suffix,
-					     "concatentate() ", result);
+			continue;
+		case DNS_R_SERVFAIL:
+			rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp);
+			st->m.policy = DNS_RPZ_POLICY_ERROR;
+			return (DNS_R_SERVFAIL);
+		default:
+			/*
+			 * Forget this policy if it is not preferable
+			 * to the previously found policy.
+			 * If this policy is not good, then stop looking
+			 * because none of the later policy zones would work.
+			 *
+			 * With more than one applicable policy, prefer
+			 * the earliest configured policy,
+			 * client-IP over QNAME over IP over NSDNAME over NSIP,
+			 * the longest prefix
+			 * the lexically smallest address.
+			 * dns_rpz_find_ip() ensures st->m.rpz->num >= rpz->num.
+			 * We can compare new and current p_name because
+			 * both are of the same type and in the same zone.
+			 * The tests above eliminate other reasons to
+			 * reject this policy.  If this policy can't work,
+			 * then neither can later zones.
+			 */
+			if (st->m.policy != DNS_RPZ_POLICY_MISS &&
+			    rpz->num == st->m.rpz->num &&
+			     (st->m.type == rpz_type &&
+			      st->m.prefix == prefix &&
+			      0 > dns_name_rdatacompare(st->p_name, p_name)))
+				break;
+
+			/*
+			 * Stop checking after saving an enabled hit in this
+			 * policy zone.  The radix tree in the policy zone
+			 * ensures that we found the longest match.
+			 */
+			if (rpz->policy != DNS_RPZ_POLICY_DISABLED) {
+				rpz_save_p(st, rpz, rpz_type,
+					   policy, p_name, prefix, result,
+					   &p_zone, &p_db, &p_node,
+					   p_rdatasetp, p_version);
+				break;
 			}
-			dns_name_split(prefix, labels - 1, NULL, prefix);
+
+			/*
+			 * Log DNS_RPZ_POLICY_DISABLED zones
+			 * and try the next eligible policy zone.
+			 */
+			rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type,
+					p_zone, p_name);
+		}
+	}
+
+	rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp);
+	return (ISC_R_SUCCESS);
+}
+
+/*
+ * Check the IP addresses in the A or AAAA rrsets for name against
+ * all eligible rpz_type (IP or NSIP) response policy rewrite rules.
+ */
+static isc_result_t
+rpz_rewrite_ip_rrset(ns_client_t *client,
+		     dns_name_t *name, dns_rdatatype_t qtype,
+		     dns_rpz_type_t rpz_type, dns_rdatatype_t ip_type,
+		     dns_db_t **ip_dbp, dns_dbversion_t *ip_version,
+		     dns_rdataset_t **ip_rdatasetp,
+		     dns_rdataset_t **p_rdatasetp, isc_boolean_t resuming)
+{
+	dns_rpz_zbits_t zbits;
+	isc_netaddr_t netaddr;
+	struct in_addr ina;
+	struct in6_addr in6a;
+	isc_result_t result;
+
+	zbits = rpz_get_zbits(client, ip_type, rpz_type);
+	if (zbits == 0)
+		return (ISC_R_SUCCESS);
+
+	/*
+	 * Get the A or AAAA rdataset.
+	 */
+	result = rpz_rrset_find(client, name, ip_type, rpz_type, ip_dbp,
+				ip_version, ip_rdatasetp, resuming);
+	switch (result) {
+	case ISC_R_SUCCESS:
+	case DNS_R_GLUE:
+	case DNS_R_ZONECUT:
+		break;
+	case DNS_R_EMPTYNAME:
+	case DNS_R_EMPTYWILD:
+	case DNS_R_NXDOMAIN:
+	case DNS_R_NCACHENXDOMAIN:
+	case DNS_R_NXRRSET:
+	case DNS_R_NCACHENXRRSET:
+	case ISC_R_NOTFOUND:
+		return (ISC_R_SUCCESS);
+	case DNS_R_DELEGATION:
+	case DNS_R_DUPLICATE:
+	case DNS_R_DROP:
+		return (result);
+	case DNS_R_CNAME:
+	case DNS_R_DNAME:
+		rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, name, rpz_type,
+			     " NS address rewrite rrset", result);
+		return (ISC_R_SUCCESS);
+	default:
+		if (client->query.rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) {
+			client->query.rpz_st->m.policy = DNS_RPZ_POLICY_ERROR;
+			rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name,
+				     rpz_type, " NS address rewrite rrset",
+				     result);
+		}
+		return (DNS_R_SERVFAIL);
+	}
+
+	/*
+	 * Check all of the IP addresses in the rdataset.
+	 */
+	for (result = dns_rdataset_first(*ip_rdatasetp);
+	     result == ISC_R_SUCCESS;
+	     result = dns_rdataset_next(*ip_rdatasetp)) {
+
+		dns_rdata_t rdata = DNS_RDATA_INIT;
+		dns_rdataset_current(*ip_rdatasetp, &rdata);
+		switch (rdata.type) {
+		case dns_rdatatype_a:
+			INSIST(rdata.length == 4);
+			memcpy(&ina.s_addr, rdata.data, 4);
+			isc_netaddr_fromin(&netaddr, &ina);
+			break;
+		case dns_rdatatype_aaaa:
+			INSIST(rdata.length == 16);
+			memcpy(in6a.s6_addr, rdata.data, 16);
+			isc_netaddr_fromin6(&netaddr, &in6a);
+			break;
+		default:
+			continue;
+		}
+
+		result = rpz_rewrite_ip(client, &netaddr, qtype, rpz_type,
+					zbits, p_rdatasetp);
+		if (result != ISC_R_SUCCESS)
+			return (result);
+	}
+
+	return (ISC_R_SUCCESS);
+}
+
+/*
+ * Look for IP addresses in A and AAAA rdatasets
+ * that trigger all eligible IP or NSIP policy rules.
+ */
+static isc_result_t
+rpz_rewrite_ip_rrsets(ns_client_t *client, dns_name_t *name,
+		      dns_rdatatype_t qtype, dns_rpz_type_t rpz_type,
+		      dns_rdataset_t **ip_rdatasetp, isc_boolean_t resuming)
+{
+	dns_rpz_st_t *st;
+	dns_dbversion_t *ip_version;
+	dns_db_t *ip_db;
+	dns_rdataset_t *p_rdataset;
+	isc_result_t result;
+
+	st = client->query.rpz_st;
+	ip_version = NULL;
+	ip_db = NULL;
+	p_rdataset = NULL;
+	if ((st->state & DNS_RPZ_DONE_IPv4) == 0 &&
+	    (qtype == dns_rdatatype_a ||
+	     qtype == dns_rdatatype_any ||
+	     rpz_type == DNS_RPZ_TYPE_NSIP)) {
+		/*
+		 * Rewrite based on an IPv4 address that will appear
+		 * in the ANSWER section or if we are checking IP addresses.
+		 */
+		result = rpz_rewrite_ip_rrset(client, name, qtype,
+					      rpz_type, dns_rdatatype_a,
+					      &ip_db, ip_version, ip_rdatasetp,
+					      &p_rdataset, resuming);
+		if (result == ISC_R_SUCCESS)
+			st->state |= DNS_RPZ_DONE_IPv4;
+	} else {
+		result = ISC_R_SUCCESS;
+	}
+	if (result == ISC_R_SUCCESS &&
+	    (qtype == dns_rdatatype_aaaa ||
+	     qtype == dns_rdatatype_any ||
+	     rpz_type == DNS_RPZ_TYPE_NSIP)) {
+		/*
+		 * Rewrite based on IPv6 addresses that will appear
+		 * in the ANSWER section or if we are checking IP addresses.
+		 */
+		result = rpz_rewrite_ip_rrset(client, name,  qtype,
+					      rpz_type, dns_rdatatype_aaaa,
+					      &ip_db, ip_version, ip_rdatasetp,
+					      &p_rdataset, resuming);
+	}
+	if (ip_db != NULL)
+		dns_db_detach(&ip_db);
+	query_putrdataset(client, &p_rdataset);
+	return (result);
+}
+
+/*
+ * Try to rewrite a request for a qtype rdataset based on the trigger name
+ * trig_name and rpz_type (DNS_RPZ_TYPE_QNAME or DNS_RPZ_TYPE_NSDNAME).
+ * Record the results including the replacement rdataset if any
+ * in client->query.rpz_st.
+ * *rdatasetp is a scratch rdataset.
+ */
+static isc_result_t
+rpz_rewrite_name(ns_client_t *client, dns_name_t *trig_name,
+		 dns_rdatatype_t qtype, dns_rpz_type_t rpz_type,
+		 dns_rpz_zbits_t allowed_zbits, dns_rdataset_t **rdatasetp)
+{
+	dns_rpz_zone_t *rpz;
+	dns_rpz_st_t *st;
+	dns_fixedname_t p_namef;
+	dns_name_t *p_name;
+	dns_rpz_zbits_t zbits;
+	dns_rpz_num_t rpz_num;
+	dns_zone_t *p_zone;
+	dns_db_t *p_db;
+	dns_dbversion_t *p_version;
+	dns_dbnode_t *p_node;
+	dns_rpz_policy_t policy;
+	isc_result_t result;
+
+	zbits = rpz_get_zbits(client, qtype, rpz_type);
+	zbits &= allowed_zbits;
+	if (zbits == 0)
+		return (ISC_R_SUCCESS);
+
+	/*
+	 * If there is only one eligible policy zone, just check it.
+	 * If more than one, then use the summary database to find
+	 * the bit mask of policy zones with policies for this trigger name.
+	 *	x&-x is the least significant bit set in x
+	 */
+	if (zbits != (zbits & (~zbits + 1))) {
+		zbits = dns_rpz_find_name(client->view->rpzs,
+					  rpz_type, zbits, trig_name);
+		if (zbits == 0)
+			return (ISC_R_SUCCESS);
+	}
+
+	dns_fixedname_init(&p_namef);
+	p_name = dns_fixedname_name(&p_namef);
+
+	p_zone = NULL;
+	p_db = NULL;
+	p_node = NULL;
+
+	st = client->query.rpz_st;
+
+	/*
+	 * Check the trigger name in every policy zone that the summary data
+	 * says has a hit for the trigger name.
+	 * Most of the time there are no eligible zones and the summary data
+	 * keeps us from getting this far.
+	 * We check the most eligible zone first and so usually check only
+	 * one policy zone.
+	 */
+	for (rpz_num = 0;
+	     zbits != 0;
+	     ++rpz_num, zbits >>= 1) {
+		if ((zbits & 1) == 0) {
+			INSIST(rpz_num <= client->view->rpzs->p.num_zones);
+			continue;
 		}
 
 		/*
-		 * See if the policy record exists and get its policy.
+		 * Do not check policy zones that cannot replace a previously
+		 * found policy.
 		 */
-		result = rpz_find(client, qtype, rpz_qname, qname, rpz,
-				  rpz_type, &zone, &db, &version, &node,
-				  rdatasetp, &policy);
+		rpz = client->view->rpzs->zones[rpz_num];
+		if (st->m.policy != DNS_RPZ_POLICY_MISS) {
+			if (st->m.rpz->num < rpz->num)
+				break;
+			if (st->m.rpz->num == rpz->num &&
+			    st->m.type < rpz_type)
+				break;
+		}
+
+		/*
+		 * Get the next policy zone's record for this trigger name.
+		 */
+		result = rpz_get_p_name(client, p_name, rpz, rpz_type,
+					trig_name);
+		if (result != ISC_R_SUCCESS)
+			continue;
+		result = rpz_find_p(client, trig_name, qtype, p_name,
+				    rpz, rpz_type,
+				    &p_zone, &p_db, &p_version, &p_node,
+				    rdatasetp, &policy);
 		switch (result) {
 		case DNS_R_NXDOMAIN:
-			break;
+			/*
+			 * Continue after a missing policy record
+			 * contrary to the summary data.  The summary
+			 * data can out of date during races with and among
+			 * policy zone updates.
+			 */
+			continue;
 		case DNS_R_SERVFAIL:
-			rpz_clean(&zone, &db, &node, rdatasetp);
+			rpz_clean(&p_zone, &p_db, &p_node, rdatasetp);
 			st->m.policy = DNS_RPZ_POLICY_ERROR;
 			return (DNS_R_SERVFAIL);
 		default:
 			/*
-			 * We are dealing with names here.
 			 * With more than one applicable policy, prefer
 			 * the earliest configured policy,
-			 * QNAME over IP over NSDNAME over NSIP,
+			 * client-IP over QNAME over IP over NSDNAME over NSIP,
 			 * and the smallest name.
-			 * Because of the testing above,
-			 * we known st->m.rpz->num >= rpz->num  and either
+			 * We known st->m.rpz->num >= rpz->num  and either
 			 * st->m.rpz->num > rpz->num or st->m.type >= rpz_type
 			 */
 			if (st->m.policy != DNS_RPZ_POLICY_MISS &&
 			    rpz->num == st->m.rpz->num &&
 			    (st->m.type < rpz_type ||
 			     (st->m.type == rpz_type &&
-			      0 >= dns_name_compare(rpz_qname, st->qname))))
+			      0 >= dns_name_compare(p_name, st->p_name))))
 				continue;
 #if 0
 			/*
@@ -4505,11 +4806,12 @@
 			 * names in TLDs that start with "rpz-" should
 			 * ICANN ever allow such TLDs.
 			 */
-			labels = dns_name_countlabels(qname);
+			unsigned int labels;
+			labels = dns_name_countlabels(trig_name);
 			if (labels >= 2) {
 				dns_label_t label;
 
-				dns_name_getlabel(qname, labels-2, &label);
+				dns_name_getlabel(trig_name, labels-2, &label);
 				if (label.length >= sizeof(DNS_RPZ_PREFIX)-1 &&
 				    strncasecmp((const char *)label.base+1,
 						DNS_RPZ_PREFIX,
@@ -4517,46 +4819,29 @@
 					continue;
 			}
 #endif
+			if (rpz->policy != DNS_RPZ_POLICY_DISABLED) {
+				rpz_save_p(st, rpz, rpz_type,
+					   policy, p_name, 0, result,
+					   &p_zone, &p_db, &p_node,
+					   rdatasetp, p_version);
+				/*
+				 * After a hit, higher numbered policy zones
+				 * are irrelevant
+				 */
+				rpz_clean(&p_zone, &p_db, &p_node, rdatasetp);
+				return (ISC_R_SUCCESS);
+			}
 			/*
-			 * Merely log DNS_RPZ_POLICY_DISABLED hits.
+			 * Log DNS_RPZ_POLICY_DISABLED zones
+			 * and try the next eligible policy zone.
 			 */
-			if (rpz->policy == DNS_RPZ_POLICY_DISABLED) {
-				rpz_log_rewrite(client, ISC_TRUE, policy,
-						rpz_type, zone, rpz_qname);
-				continue;
-			}
-
-			rpz_match_clear(st);
-			st->m.rpz = rpz;
-			st->m.type = rpz_type;
-			st->m.prefix = 0;
-			st->m.policy = policy;
-			st->m.result = result;
-			dns_name_copy(rpz_qname, st->qname, NULL);
-			if (*rdatasetp != NULL &&
-			    dns_rdataset_isassociated(*rdatasetp)) {
-				dns_rdataset_t *trdataset;
-
-				trdataset = st->m.rdataset;
-				st->m.rdataset = *rdatasetp;
-				*rdatasetp = trdataset;
-				st->m.ttl = ISC_MIN(st->m.rdataset->ttl,
-						    rpz->max_policy_ttl);
-			} else {
-				st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT,
-						    rpz->max_policy_ttl);
-			}
-			st->m.node = node;
-			node = NULL;
-			st->m.db = db;
-			db = NULL;
-			st->m.version = version;
-			st->m.zone = zone;
-			zone = NULL;
+			rpz_log_rewrite(client, ISC_TRUE, policy, rpz_type,
+					p_zone, p_name);
+			break;
 		}
 	}
 
-	rpz_clean(&zone, &db, &node, rdatasetp);
+	rpz_clean(&p_zone, &p_db, &p_node, rdatasetp);
 	return (ISC_R_SUCCESS);
 }
 
@@ -4569,7 +4854,7 @@
 	st = client->query.rpz_st;
 
 	if (str != NULL)
-		rpz_log_fail(client, level, DNS_RPZ_TYPE_NSIP, nsname,
+		rpz_log_fail(client, level, nsname, DNS_RPZ_TYPE_NSIP,
 			     str, result);
 	if (st->r.ns_rdataset != NULL &&
 	    dns_rdataset_isassociated(st->r.ns_rdataset))
@@ -4589,7 +4874,8 @@
 	dns_rdataset_t *rdataset;
 	dns_fixedname_t nsnamef;
 	dns_name_t *nsname;
-	isc_boolean_t ck_ip;
+	int qresult_type;
+	dns_rpz_zbits_t zbits;
 	isc_result_t result;
 
 	st = client->query.rpz_st;
@@ -4604,10 +4890,10 @@
 		st->m.ttl = ~0;
 		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;
@@ -4620,7 +4906,7 @@
 	case ISC_R_SUCCESS:
 	case DNS_R_GLUE:
 	case DNS_R_ZONECUT:
-		ck_ip = ISC_TRUE;
+		qresult_type = 0;
 		break;
 	case DNS_R_EMPTYNAME:
 	case DNS_R_NXRRSET:
@@ -4630,73 +4916,155 @@
 	case DNS_R_NCACHENXRRSET:
 	case DNS_R_CNAME:
 	case DNS_R_DNAME:
-		ck_ip = ISC_FALSE;
+		qresult_type = 1;
 		break;
 	case DNS_R_DELEGATION:
 	case ISC_R_NOTFOUND:
-		return (ISC_R_SUCCESS);
+		/*
+		 * If recursion is on, do only tentative rewriting.
+		 * If recursion is off, this the normal and only time we
+		 * can rewrite.
+		 */
+		if (RECURSIONOK(client))
+			qresult_type = 2;
+		else
+			qresult_type = 1;
+		break;
 	case ISC_R_FAILURE:
 	case ISC_R_TIMEDOUT:
 	case DNS_R_BROKENCHAIN:
-		rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, DNS_RPZ_TYPE_QNAME,
-			     client->query.qname,
-			     "stop on qresult in rpz_rewrite() ",
-			     qresult);
+		rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, client->query.qname,
+			     DNS_RPZ_TYPE_QNAME,
+			     " stop on qresult in rpz_rewrite()", qresult);
 		return (ISC_R_SUCCESS);
 	default:
-		rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, DNS_RPZ_TYPE_QNAME,
-			     client->query.qname,
-			     "stop on unrecognized qresult in rpz_rewrite() ",
+		rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, client->query.qname,
+			     DNS_RPZ_TYPE_QNAME,
+			     " stop on unrecognized qresult in rpz_rewrite()",
 			     qresult);
 		return (ISC_R_SUCCESS);
 	}
 
 	rdataset = NULL;
-	if ((st->state & DNS_RPZ_DONE_QNAME) == 0) {
+
+	if ((st->state & (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) !=
+	    (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) {
+		isc_netaddr_t netaddr;
+		dns_rpz_zbits_t allowed;
+
+		if (qresult_type == 2) {
+			/*
+			 * This request needs recursion that has not been done.
+			 * Get bits for the policy zones that do not need
+			 * to wait for the results of recursion.
+			 */
+			allowed = client->view->rpzs->have.qname_skip_recurse;
+			if (allowed == 0)
+				return (ISC_R_SUCCESS);
+		} else {
+			allowed = DNS_RPZ_ALL_ZBITS;
+		}
+
 		/*
-		 * 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.
+		 * Check once for triggers for the client IP address.
 		 */
-		result = rpz_rewrite_name(client, qtype, client->query.qname,
-					  DNS_RPZ_TYPE_QNAME, &rdataset);
-		if (result != ISC_R_SUCCESS)
-			goto cleanup;
+		if ((st->state & DNS_RPZ_DONE_CLIENT_IP) == 0) {
+			zbits = rpz_get_zbits(client, dns_rdatatype_none,
+					      DNS_RPZ_TYPE_CLIENT_IP);
+			zbits &= allowed;
+			if (zbits != 0) {
+				isc_netaddr_fromsockaddr(&netaddr,
+							&client->peeraddr);
+				result = rpz_rewrite_ip(client, &netaddr, qtype,
+							DNS_RPZ_TYPE_CLIENT_IP,
+							zbits, &rdataset);
+				if (result != ISC_R_SUCCESS)
+					goto cleanup;
+			}
+		}
+
+		/*
+		 * Check triggers for the query name if this is the first time
+		 * for the current qname.
+		 * There is a first time for each name in a CNAME chain
+		 */
+		if ((st->state & DNS_RPZ_DONE_QNAME) == 0) {
+			result = rpz_rewrite_name(client, client->query.qname,
+						  qtype, DNS_RPZ_TYPE_QNAME,
+						  allowed, &rdataset);
+			if (result != ISC_R_SUCCESS)
+				goto cleanup;
+
+			/*
+			 * Check IPv4 addresses in A RRs next.
+			 * Reset to the start of the NS names.
+			 */
+			st->r.label = dns_name_countlabels(client->query.qname);
+			st->state &= ~(DNS_RPZ_DONE_QNAME_IP |
+				       DNS_RPZ_DONE_IPv4);
 
-		st->r.label = dns_name_countlabels(client->query.qname);
+		}
 
-		st->state &= ~(DNS_RPZ_DONE_QNAME_IP | DNS_RPZ_DONE_IPv4);
-		st->state |= DNS_RPZ_DONE_QNAME;
+		/*
+		 * Quit if this was an attempt to find a qname or
+		 * client-IP trigger before recursion.
+		 * We will be back if no pre-recursion triggers hit.
+		 * For example, consider 2 policy zones, both with qname and
+		 * IP address triggers.  If the qname misses the 1st zone,
+		 * then we cannot know whether a hit for the qname in the
+		 * 2nd zone matters until after recursing to get the A RRs and
+		 * testing them in the first zone.
+		 * Do not bother saving the work from this attempt,
+		 * because recusion is so slow.
+		 */
+		if (qresult_type == 2)
+			goto cleanup;
+
+		/*
+		 * DNS_RPZ_DONE_QNAME but not DNS_RPZ_DONE_CLIENT_IP
+		 * is reset at the end of dealing with each CNAME.
+		 */
+		st->state |= (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME);
 	}
 
 	/*
-	 * Check known IP addresses for the query name.
+	 * Check known IP addresses for the query name if the database
+	 * lookup resulted in some addresses (qresult_type == 0)
+	 * and if we have not already checked them.
 	 * Any recursion required for the query has already happened.
 	 * Do not check addresses that will not be in the ANSWER section.
 	 */
-	if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 &&
-	    (st->state & DNS_RPZ_HAVE_IP) != 0 && ck_ip) {
-		result = rpz_rewrite_rrsets(client, DNS_RPZ_TYPE_IP,
-					    client->query.qname, qtype,
-					    &rdataset, resuming);
+	if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 && qresult_type == 0 &&
+	    rpz_get_zbits(client, qtype, DNS_RPZ_TYPE_IP) != 0) {
+		result = rpz_rewrite_ip_rrsets(client,
+					       client->query.qname, qtype,
+					       DNS_RPZ_TYPE_IP,
+					       &rdataset, resuming);
 		if (result != ISC_R_SUCCESS)
 			goto cleanup;
-		st->state &= ~DNS_RPZ_DONE_IPv4;
+		/*
+		 * We are finished checking the IP addresses for the qname.
+		 * Start with IPv4 if we will check NS IP addesses.
+		 */
 		st->state |= DNS_RPZ_DONE_QNAME_IP;
+		st->state &= ~DNS_RPZ_DONE_IPv4;
 	}
 
 	/*
-	 * Stop looking for rules if there are none of the other kinds.
+	 * Stop looking for rules if there are none of the other kinds
+	 * that could override what we already have.
 	 */
-	if ((st->state & (DNS_RPZ_HAVE_NSIPv4 | DNS_RPZ_HAVE_NSIPv6 |
-			  DNS_RPZ_HAVE_NSDNAME)) == 0) {
+	if (rpz_get_zbits(client, dns_rdatatype_any,
+			  DNS_RPZ_TYPE_NSDNAME) == 0 &&
+	    rpz_get_zbits(client, dns_rdatatype_any,
+			  DNS_RPZ_TYPE_NSIP) == 0) {
 		result = ISC_R_SUCCESS;
 		goto cleanup;
 	}
 
 	dns_fixedname_init(&nsnamef);
 	dns_name_clone(client->query.qname, dns_fixedname_name(&nsnamef));
-	while (st->r.label > client->view->rpz_min_ns_labels) {
+	while (st->r.label > client->view->rpzs->p.min_ns_labels) {
 		/*
 		 * Get NS rrset for each domain in the current qname.
 		 */
@@ -4710,8 +5078,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)
@@ -4745,12 +5113,12 @@
 			case ISC_R_FAILURE:
 				rpz_rewrite_ns_skip(client, nsname, result,
 						DNS_RPZ_DEBUG_LEVEL3,
-						"NS db_find() ");
+						" NS db_find()");
 				continue;
 			default:
 				rpz_rewrite_ns_skip(client, nsname, result,
 						DNS_RPZ_INFO_LEVEL,
-						"unrecognized NS db_find() ");
+						" unrecognized NS db_find()");
 				continue;
 			}
 		}
@@ -4766,8 +5134,8 @@
 			dns_rdata_reset(&nsrdata);
 			if (result != ISC_R_SUCCESS) {
 				rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL,
-					     DNS_RPZ_TYPE_NSIP, nsname,
-					     "rdata_tostruct() ", result);
+					     nsname, DNS_RPZ_TYPE_NSIP,
+					     " rdata_tostruct()", result);
 				st->m.policy = DNS_RPZ_POLICY_ERROR;
 				goto cleanup;
 			}
@@ -4783,11 +5151,11 @@
 			 * Check this NS name if we did not handle it
 			 * during a previous recursion.
 			 */
-			if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0 &&
-			    (st->state & DNS_RPZ_HAVE_NSDNAME) != 0) {
-				result = rpz_rewrite_name(client, qtype,
-							&ns.name,
+			if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0) {
+				result = rpz_rewrite_name(client, &ns.name,
+							qtype,
 							DNS_RPZ_TYPE_NSDNAME,
+							DNS_RPZ_ALL_ZBITS,
 							&rdataset);
 				if (result != ISC_R_SUCCESS) {
 					dns_rdata_freestruct(&ns);
@@ -4798,9 +5166,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;
@@ -4810,10 +5178,16 @@
 		} while (result == ISC_R_SUCCESS);
 		dns_rdataset_disassociate(st->r.ns_rdataset);
 		st->r.label--;
+
+		if (rpz_get_zbits(client, dns_rdatatype_any,
+				  DNS_RPZ_TYPE_NSDNAME) == 0 &&
+		    rpz_get_zbits(client, dns_rdatatype_any,
+				  DNS_RPZ_TYPE_NSIP) == 0)
+			break;
 	}
 
 	/*
-	 * Use the best, if any, hit.
+	 * Use the best hit, if any.
 	 */
 	result = ISC_R_SUCCESS;
 
@@ -4828,7 +5202,7 @@
 		if (st->m.policy == DNS_RPZ_POLICY_PASSTHRU &&
 		    result != DNS_R_DELEGATION)
 			rpz_log_rewrite(client, ISC_FALSE, st->m.policy,
-					st->m.type, st->m.zone, st->qname);
+					st->m.type, st->m.zone, st->p_name);
 		rpz_match_clear(st);
 	}
 	if (st->m.policy == DNS_RPZ_POLICY_ERROR) {
@@ -4847,19 +5221,25 @@
  * by the client in DNSSEC or a lack of signatures.
  */
 static isc_boolean_t
-rpz_ck_dnssec(ns_client_t *client, isc_result_t result,
+rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult,
 	      dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset)
 {
 	dns_fixedname_t fixed;
 	dns_name_t *found;
 	dns_rdataset_t trdataset;
 	dns_rdatatype_t type;
+	isc_result_t result;
 
-	if (client->view->rpz_break_dnssec)
+	if (client->view->rpzs->p.break_dnssec || !WANTDNSSEC(client))
 		return (ISC_TRUE);
+
 	/*
-	 * sigrdataset == NULL if and only !WANTDNSSEC(client)
+	 * We do not know if there are signatures if we have not recursed
+	 * for them.
 	 */
+	if (qresult == DNS_R_DELEGATION || qresult == ISC_R_NOTFOUND)
+		return (ISC_FALSE);
+
 	if (sigrdataset == NULL)
 		return (ISC_TRUE);
 	if (dns_rdataset_isassociated(sigrdataset))
@@ -4939,7 +5319,7 @@
 	if (result != ISC_R_SUCCESS)
 		return (result);
 	rpz_log_rewrite(client, ISC_FALSE, st->m.policy,
-			st->m.type, st->m.zone, st->qname);
+			st->m.type, st->m.zone, st->p_name);
 	ns_client_qnamereplace(client, fname);
 	/*
 	 * Turn off DNSSEC because the results of a
@@ -5998,13 +6378,15 @@
 	}
 #endif /* USE_RRL */
 
-	if (!ISC_LIST_EMPTY(client->view->rpz_zones) &&
-	    (RECURSIONOK(client) || !client->view->rpz_recursive_only) &&
+	if (client->view->rpzs != NULL &&
+	    client->view->rpzs->p.num_zones != 0 &&
+	    (RECURSIONOK(client) || client->view->rpzs->p.no_rd_ok != 0) &&
 	    rpz_ck_dnssec(client, result, rdataset, sigrdataset) &&
 	    !RECURSING(client) &&
 	    (client->query.rpz_st == NULL ||
 	     (client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0) &&
-	    !dns_name_equal(client->query.qname, dns_rootname)) {
+	    !dns_name_equal(client->query.qname, dns_rootname))
+	{
 		isc_result_t rresult;
 
 		rresult = rpz_rewrite(client, qtype, result, resuming);
@@ -6042,12 +6424,17 @@
 			rpz_st->state |= DNS_RPZ_REWRITTEN;
 		if (rpz_st->m.policy != DNS_RPZ_POLICY_MISS &&
 		    rpz_st->m.policy != DNS_RPZ_POLICY_PASSTHRU &&
+		    (rpz_st->m.policy != DNS_RPZ_POLICY_TCP_ONLY ||
+		     (client->attributes & NS_CLIENTATTR_TCP) == 0) &&
 		    rpz_st->m.policy != DNS_RPZ_POLICY_ERROR) {
-			if (rpz_st->m.type == DNS_RPZ_TYPE_QNAME) {
-				result = dns_name_copy(client->query.qname,
-						       fname, NULL);
-				RUNTIME_CHECK(result == ISC_R_SUCCESS);
-			}
+			/* We got a hit and are going to answer with our
+			 * fiction. Ensure that we answer with the name
+			 * we looked up even if we were stopped short
+			 * in recursion or for a deferral.
+			 */
+			rresult = dns_name_copy(client->query.qname,
+						fname, NULL);
+			RUNTIME_CHECK(rresult == ISC_R_SUCCESS);
 			rpz_clean(&zone, &db, &node, NULL);
 			if (rpz_st->m.rdataset != NULL) {
 				query_putrdataset(client, &rdataset);
@@ -6067,6 +6454,27 @@
 			rpz_st->m.zone = NULL;
 
 			switch (rpz_st->m.policy) {
+			case DNS_RPZ_POLICY_TCP_ONLY:
+				client->message->flags |= DNS_MESSAGEFLAG_TC;
+				if (result == DNS_R_NXDOMAIN ||
+				    result == DNS_R_NCACHENXDOMAIN)
+					client->message->rcode =
+						    dns_rcode_nxdomain;
+				else
+					result = ISC_R_SUCCESS;
+				rpz_log_rewrite(client, ISC_FALSE,
+						rpz_st->m.policy,
+						rpz_st->m.type, zone,
+						rpz_st->p_name);
+				goto cleanup;
+			case DNS_RPZ_POLICY_DROP:
+				result = ISC_R_SUCCESS;
+				QUERY_ERROR(DNS_R_DROP);
+				rpz_log_rewrite(client, ISC_FALSE,
+						rpz_st->m.policy,
+						rpz_st->m.type, zone,
+						rpz_st->p_name);
+				goto cleanup;
 			case DNS_RPZ_POLICY_NXDOMAIN:
 				result = DNS_R_NXDOMAIN;
 				break;
@@ -6079,8 +6487,8 @@
 				    result != DNS_R_CNAME) {
 					/*
 					 * We will add all of the rdatasets of
-					 * the node by iterating, setting the
-					 * TTL then.
+					 * the node by iterating later,
+					 * and set the TTL then.
 					 */
 					if (dns_rdataset_isassociated(rdataset))
 					    dns_rdataset_disassociate(rdataset);
@@ -6135,7 +6543,7 @@
 			rpz_st->q.is_zone = is_zone;
 			is_zone = ISC_TRUE;
 			rpz_log_rewrite(client, ISC_FALSE, rpz_st->m.policy,
-					rpz_st->m.type, zone, rpz_st->qname);
+					rpz_st->m.type, zone, rpz_st->p_name);
 		}
 	}
 
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
@@ -375,7 +375,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);
@@ -1556,17 +1557,24 @@
 }
 
 static isc_result_t
-configure_rpz(dns_view_t *view, const cfg_listelt_t *element,
-	      isc_boolean_t recursive_only_def, dns_ttl_t ttl_def)
+configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element,
+		   isc_boolean_t recursive_only_def, dns_ttl_t ttl_def,
+		   const dns_rpz_zone_t *old, isc_boolean_t *old_rpz_okp)
 {
 	const cfg_obj_t *rpz_obj, *obj;
 	const char *str;
-	dns_rpz_zone_t *old, *new;
+	dns_rpz_zone_t *new;
 	isc_result_t result;
+	dns_rpz_num_t rpz_num;
+
+	REQUIRE(old != NULL || !*old_rpz_okp);
 
 	rpz_obj = cfg_listelt_value(element);
 
-	new = isc_mem_get(view->mctx, sizeof(*new));
+	if (view->rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES)
+		return (ISC_R_NOMEMORY);
+
+	new = isc_mem_get(view->rpzs->mctx, sizeof(*new));
 	if (new == NULL) {
 		cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
 			    "no memory for response policy zones");
@@ -1574,20 +1582,29 @@
 	}
 
 	memset(new, 0, sizeof(*new));
+	result = isc_refcount_init(&new->refs, 1);
+	if (result != ISC_R_SUCCESS) {
+		isc_mem_put(view->rpzs->mctx, new, sizeof(*new));
+		return (result);
+	}
 	dns_name_init(&new->origin, NULL);
+	dns_name_init(&new->client_ip, NULL);
+	dns_name_init(&new->ip, NULL);
 	dns_name_init(&new->nsdname, NULL);
+	dns_name_init(&new->nsip, NULL);
 	dns_name_init(&new->passthru, NULL);
+	dns_name_init(&new->drop, NULL);
+	dns_name_init(&new->tcp_only, NULL);
 	dns_name_init(&new->cname, NULL);
-	ISC_LIST_INITANDAPPEND(view->rpz_zones, new, link);
+	new->num = view->rpzs->p.num_zones++;
+	view->rpzs->zones[new->num] = new;
 
 	obj = cfg_tuple_get(rpz_obj, "recursive-only");
-	if (cfg_obj_isvoid(obj)) {
-		new->recursive_only = recursive_only_def;
+	if (cfg_obj_isvoid(obj) ? recursive_only_def : cfg_obj_asboolean(obj)) {
+		view->rpzs->p.no_rd_ok &= ~DNS_RPZ_ZBIT(new->num);
 	} else {
-		new->recursive_only = cfg_obj_asboolean(obj);
+		view->rpzs->p.no_rd_ok |= DNS_RPZ_ZBIT(new->num);
 	}
-	if (!new->recursive_only)
-		view->rpz_recursive_only = ISC_FALSE;
 
 	obj = cfg_tuple_get(rpz_obj, "max-policy-ttl");
 	if (cfg_obj_isuint32(obj)) {
@@ -1595,6 +1612,8 @@
 	} else {
 		new->max_policy_ttl = ttl_def;
 	}
+	if (*old_rpz_okp && new->max_policy_ttl != old->max_policy_ttl)
+		*old_rpz_okp = ISC_FALSE;
 
 	str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "zone name"));
 	result = configure_rpz_name(view, rpz_obj, &new->origin, str, "zone");
@@ -1605,25 +1624,50 @@
 			    "invalid zone name '%s'", str);
 		return (DNS_R_EMPTYLABEL);
 	}
-	for (old = ISC_LIST_HEAD(view->rpz_zones);
-	     old != new;
-	     old = ISC_LIST_NEXT(old, link)) {
-		++new->num;
-		if (dns_name_equal(&old->origin, &new->origin)) {
+	for (rpz_num = 0; rpz_num < view->rpzs->p.num_zones-1; ++rpz_num) {
+		if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin,
+				   &new->origin)) {
 			cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
 				    "duplicate '%s'", str);
 			result = DNS_R_DUPLICATE;
 			return (result);
 		}
 	}
+	if (*old_rpz_okp && !dns_name_equal(&old->origin, &new->origin))
+		*old_rpz_okp = ISC_FALSE;
+
+	result = configure_rpz_name2(view, rpz_obj, &new->client_ip,
+				     DNS_RPZ_CLIENT_IP_ZONE, &new->origin);
+	if (result != ISC_R_SUCCESS)
+		return (result);
+
+	result = configure_rpz_name2(view, rpz_obj, &new->ip,
+				     DNS_RPZ_IP_ZONE, &new->origin);
+	if (result != ISC_R_SUCCESS)
+		return (result);
 
 	result = configure_rpz_name2(view, rpz_obj, &new->nsdname,
 				     DNS_RPZ_NSDNAME_ZONE, &new->origin);
 	if (result != ISC_R_SUCCESS)
 		return (result);
 
+	result = configure_rpz_name2(view, rpz_obj, &new->nsip,
+				     DNS_RPZ_NSIP_ZONE, &new->origin);
+	if (result != ISC_R_SUCCESS)
+		return (result);
+
 	result = configure_rpz_name(view, rpz_obj, &new->passthru,
-				    DNS_RPZ_PASSTHRU_ZONE, "zone");
+				    DNS_RPZ_PASSTHRU_NAME, "name");
+	if (result != ISC_R_SUCCESS)
+		return (result);
+
+	result = configure_rpz_name(view, rpz_obj, &new->drop,
+				    DNS_RPZ_DROP_NAME, "name");
+	if (result != ISC_R_SUCCESS)
+		return (result);
+
+	result = configure_rpz_name(view, rpz_obj, &new->tcp_only,
+				    DNS_RPZ_TCP_ONLY_NAME, "name");
 	if (result != ISC_R_SUCCESS)
 		return (result);
 
@@ -1642,6 +1686,116 @@
 				return (result);
 		}
 	}
+	if (*old_rpz_okp && (new->policy != old->policy ||
+			     !dns_name_equal(&old->cname, &new->cname)))
+		*old_rpz_okp = ISC_FALSE;
+
+	return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+configure_rpz(dns_view_t *view, const cfg_obj_t *rpz_obj,
+	      isc_boolean_t *old_rpz_okp)
+{
+	const cfg_listelt_t *zone_element;
+	const cfg_obj_t *sub_obj;
+	isc_boolean_t recursive_only_def;
+	dns_ttl_t ttl_def;
+	dns_rpz_zones_t *new;
+	const dns_rpz_zones_t *old;
+	dns_view_t *pview;
+	const dns_rpz_zone_t *old_zone;
+	isc_result_t result;
+	int i;
+
+	*old_rpz_okp = ISC_FALSE;
+
+	zone_element = cfg_list_first(cfg_tuple_get(rpz_obj, "zone list"));
+	if (zone_element == NULL)
+		return (ISC_R_SUCCESS);
+
+	result = dns_rpz_new_zones(&view->rpzs, view->mctx);
+	if (result != ISC_R_SUCCESS)
+		return (result);
+	new = view->rpzs;
+
+	sub_obj = cfg_tuple_get(rpz_obj, "recursive-only");
+	if (!cfg_obj_isvoid(sub_obj) &&
+	    !cfg_obj_asboolean(sub_obj))
+		recursive_only_def = ISC_FALSE;
+	else
+		recursive_only_def = ISC_TRUE;
+
+	sub_obj = cfg_tuple_get(rpz_obj, "break-dnssec");
+	if (!cfg_obj_isvoid(sub_obj) &&
+	    cfg_obj_asboolean(sub_obj))
+		new->p.break_dnssec = ISC_TRUE;
+	else
+		new->p.break_dnssec = ISC_FALSE;
+
+	sub_obj = cfg_tuple_get(rpz_obj, "max-policy-ttl");
+	if (cfg_obj_isuint32(sub_obj))
+		ttl_def = cfg_obj_asuint32(sub_obj);
+	else
+		ttl_def = DNS_RPZ_MAX_TTL_DEFAULT;
+
+	sub_obj = cfg_tuple_get(rpz_obj, "min-ns-dots");
+	if (cfg_obj_isuint32(sub_obj))
+		new->p.min_ns_labels = cfg_obj_asuint32(sub_obj) + 1;
+	else
+		new->p.min_ns_labels = 2;
+
+	sub_obj = cfg_tuple_get(rpz_obj, "qname-wait-recurse");
+	if (cfg_obj_isvoid(sub_obj) || cfg_obj_asboolean(sub_obj))
+		new->p.qname_wait_recurse = ISC_TRUE;
+	else
+		new->p.qname_wait_recurse = ISC_FALSE;
+
+	pview = NULL;
+	result = dns_viewlist_find(&ns_g_server->viewlist,
+				   view->name, view->rdclass, &pview);
+	if (result == ISC_R_SUCCESS) {
+		old = pview->rpzs;
+	} else {
+		old = NULL;
+	}
+	if (old == NULL)
+		*old_rpz_okp = ISC_FALSE;
+	else
+		*old_rpz_okp = ISC_TRUE;
+
+	for (i = 0;
+	     zone_element != NULL;
+	     ++i, zone_element = cfg_list_next(zone_element)) {
+		if (*old_rpz_okp && i < old->p.num_zones) {
+			old_zone = old->zones[i];
+		} else {
+			*old_rpz_okp = ISC_FALSE;
+			old_zone = NULL;
+		}
+		result = configure_rpz_zone(view, zone_element,
+					    recursive_only_def, ttl_def,
+					    old_zone, old_rpz_okp);
+		if (result != ISC_R_SUCCESS) {
+			if (pview != NULL)
+				dns_view_detach(&pview);
+			return (result);
+		}
+	}
+
+	/*
+	 * If this is a reloading and the parameters and list of policy
+	 * zones are unchanged, then use the same policy data.
+	 * Data for individual zones that must be reloaded will be merged.
+	 */
+	if (old != NULL && memcmp(&old->p, &new->p, sizeof(new->p)) != 0)
+		*old_rpz_okp = ISC_FALSE;
+	if (*old_rpz_okp) {
+		dns_rpz_detach_rpzs(&view->rpzs);
+		dns_rpz_attach_rpzs(pview->rpzs, &view->rpzs);
+	}
+	if (pview != NULL)
+		dns_view_detach(&pview);
 
 	return (ISC_R_SUCCESS);
 }
@@ -2109,7 +2263,7 @@
 	dns_acl_t *clients = NULL, *mapped = NULL, *excluded = NULL;
 	unsigned int query_timeout, ndisp;
 	struct cfg_context *nzctx;
-	dns_rpz_zone_t *rpz;
+	isc_boolean_t old_rpz_ok = ISC_FALSE;
 
 	REQUIRE(DNS_VIEW_VALID(view));
 
@@ -2207,44 +2361,7 @@
 	obj = NULL;
 	if (view->rdclass == dns_rdataclass_in && need_hints &&
 	    ns_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS) {
-		const cfg_obj_t *rpz_obj;
-		isc_boolean_t recursive_only_def;
-		dns_ttl_t ttl_def;
-
-		rpz_obj = cfg_tuple_get(obj, "recursive-only");
-		if (!cfg_obj_isvoid(rpz_obj) &&
-		    !cfg_obj_asboolean(rpz_obj))
-			recursive_only_def = ISC_FALSE;
-		else
-			recursive_only_def = ISC_TRUE;
-
-		rpz_obj = cfg_tuple_get(obj, "break-dnssec");
-		if (!cfg_obj_isvoid(rpz_obj) &&
-		    cfg_obj_asboolean(rpz_obj))
-			view->rpz_break_dnssec = ISC_TRUE;
-		else
-			view->rpz_break_dnssec = ISC_FALSE;
-
-		rpz_obj = cfg_tuple_get(obj, "max-policy-ttl");
-		if (cfg_obj_isuint32(rpz_obj))
-			ttl_def = cfg_obj_asuint32(rpz_obj);
-		else
-			ttl_def = DNS_RPZ_MAX_TTL_DEFAULT;
-
-		rpz_obj = cfg_tuple_get(obj, "min-ns-dots");
-		if (cfg_obj_isuint32(rpz_obj))
-			view->rpz_min_ns_labels = cfg_obj_asuint32(rpz_obj) + 1;
-		else
-			view->rpz_min_ns_labels = 2;
-
-		element = cfg_list_first(cfg_tuple_get(obj, "zone list"));
-		while (element != NULL) {
-			result = configure_rpz(view, element,
-					       recursive_only_def, ttl_def);
-			if (result != ISC_R_SUCCESS)
-				goto cleanup;
-			element = cfg_list_next(element);
-		}
+		CHECK(configure_rpz(view, obj, &old_rpz_ok));
 	}
 
 	/*
@@ -2265,22 +2382,29 @@
 	{
 		const cfg_obj_t *zconfig = cfg_listelt_value(element);
 		CHECK(configure_zone(config, zconfig, vconfig, mctx, view,
-				     actx, ISC_FALSE));
+				     actx, ISC_FALSE, old_rpz_ok));
 	}
 
-	for (rpz = ISC_LIST_HEAD(view->rpz_zones);
-	     rpz != NULL;
-	     rpz = ISC_LIST_NEXT(rpz, link))
-	{
-		if (!rpz->defined) {
-			char namebuf[DNS_NAME_FORMATSIZE];
+	/*
+	 * Check that a master or slave zone was found for each
+	 * zone named in the response policy statement.
+	 */
+	if (view->rpzs != NULL) {
+		dns_rpz_num_t n;
 
-			dns_name_format(&rpz->origin, namebuf, sizeof(namebuf));
-			cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
-				    "'%s' is not a master or slave zone",
-				    namebuf);
-			result = ISC_R_NOTFOUND;
-			goto cleanup;
+		for (n = 0; n < view->rpzs->p.num_zones; ++n)
+		{
+			if ((view->rpzs->defined & DNS_RPZ_ZBIT(n)) == 0) {
+				char namebuf[DNS_NAME_FORMATSIZE];
+
+				dns_name_format(&view->rpzs->zones[n]->origin,
+						namebuf, sizeof(namebuf));
+				cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL,
+					    "'%s' is not a master or slave zone",
+					    namebuf);
+				result = ISC_R_NOTFOUND;
+				goto cleanup;
+			}
 		}
 	}
 
@@ -2306,7 +2430,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));
 		}
 	}
 
@@ -3750,7 +3874,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 */
@@ -3771,8 +3896,7 @@
 	const char *zname;
 	dns_rdataclass_t zclass;
 	const char *ztypestr;
-	isc_boolean_t is_rpz;
-	dns_rpz_zone_t *rpz;
+	dns_rpz_num_t rpz_num;
 
 	options = NULL;
 	(void)cfg_map_get(config, "options", &options);
@@ -3934,18 +4058,15 @@
 	INSIST(dupzone == NULL);
 
 	/*
-	 * Note whether this is a response policy zone.
+	 * Note whether this is a response policy zone and which one if so.
 	 */
-	is_rpz = ISC_FALSE;
-	for (rpz = ISC_LIST_HEAD(view->rpz_zones);
-	     rpz != NULL;
-	     rpz = ISC_LIST_NEXT(rpz, link))
-	{
-		if (dns_name_equal(&rpz->origin, origin)) {
-			is_rpz = ISC_TRUE;
-			rpz->defined = ISC_TRUE;
+	for (rpz_num = 0; ; ++rpz_num) {
+		if (view->rpzs == NULL || rpz_num >= view->rpzs->p.num_zones) {
+			rpz_num = DNS_RPZ_INVALID_NUM;
 			break;
 		}
+		if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, origin))
+			break;
 	}
 
 	/*
@@ -3956,7 +4077,9 @@
 	 *   - The zone is compatible with the config
 	 *     options (e.g., an existing master zone cannot
 	 *     be reused if the options specify a slave zone)
-	 *   - The zone was and is or was not and is not a policy zone
+	 *   - The zone was not and is still not a response policy zone
+	 *     or the zone is a policy zone with an unchanged number
+	 *     and we are using the old policy zone summary data.
 	 */
 	result = dns_viewlist_find(&ns_g_server->viewlist, view->name,
 				   view->rdclass, &pview);
@@ -3970,7 +4093,8 @@
 	if (zone != NULL && !ns_zone_reusable(zone, zconfig))
 		dns_zone_detach(&zone);
 
-	if (zone != NULL && is_rpz != dns_zone_get_rpz(zone))
+	if (zone != NULL && (rpz_num != dns_zone_get_rpz_num(zone) ||
+			     (rpz_num != DNS_RPZ_INVALID_NUM && !old_rpz_ok)))
 		dns_zone_detach(&zone);
 
 	if (zone != NULL) {
@@ -3995,8 +4119,8 @@
 		dns_zone_setstats(zone, ns_g_server->zonestats);
 	}
 
-	if (is_rpz) {
-		result = dns_zone_rpz_enable(zone);
+	if (rpz_num != DNS_RPZ_INVALID_NUM) {
+		result = dns_zone_rpz_enable(zone, view->rpzs, rpz_num);
 		if (result != ISC_R_SUCCESS) {
 			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
 				      NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
@@ -8286,7 +8410,8 @@
 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
 	dns_view_thaw(view);
 	result = configure_zone(cfg->config, parms, vconfig,
-				server->mctx, view, cfg->actx, ISC_FALSE);
+				server->mctx, view, cfg->actx, ISC_FALSE,
+				ISC_FALSE);
 	dns_view_freeze(view);
 	isc_task_endexclusive(server->task);
 	if (result != ISC_R_SUCCESS)
diff -r -u bin/tests/system/rpz/Makefile-orig bin/tests/system/rpz/Makefile
--- bin/tests/system/rpz/Makefile-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/Makefile	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,478 @@
+# 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.
+
+# $Id$
+
+
+srcdir =	.
+
+top_srcdir =	../../../..
+
+VERSION=9.10.0pre-alpha
+
+# Copyright (C) 2004, 2005, 2007, 2012  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 1999-2001  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: includes.in,v 1.21 2007/06/19 23:47:24 tbox Exp $
+
+# Search for machine-generated header files in the build tree,
+# and for normal headers in the source tree (${top_srcdir}).
+# We only need to look in OS-specific subdirectories for the
+# latter case, because there are no machine-generated OS-specific
+# headers.
+
+ISC_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/isc/include \
+	-I${top_srcdir}/lib/isc \
+	-I${top_srcdir}/lib/isc/include \
+	-I${top_srcdir}/lib/isc/unix/include \
+	-I${top_srcdir}/lib/isc/pthreads/include \
+	-I${top_srcdir}/lib/isc/x86_32/include
+
+ISCCC_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/isccc/include \
+       -I${top_srcdir}/lib/isccc/include
+
+ISCCFG_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/isccfg/include \
+       -I${top_srcdir}/lib/isccfg/include
+
+DNS_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/dns/include \
+	-I${top_srcdir}/lib/dns/include
+
+LWRES_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/lwres/include \
+	-I${top_srcdir}/lib/lwres/unix/include \
+	-I${top_srcdir}/lib/lwres/include
+
+BIND9_INCLUDES = -I/usr/home/vjs/isc/work/rpz3/lib/bind9/include \
+	-I${top_srcdir}/lib/bind9/include
+
+TEST_INCLUDES = \
+	-I${top_srcdir}/lib/tests/include
+
+CINCLUDES =
+
+CDEFINES =
+CWARNINGS =
+
+DNSLIBS =
+ISCLIBS =	.
+
+DNSDEPLIBS =
+ISCDEPLIBS =
+
+DEPLIBS =
+
+LIBS =		 -L/usr/local/lib -lxml2 -lz -L/usr/local/lib -liconv -lm
+
+TARGETS =	rpz
+
+RPZOBJS =	rpz.o
+
+SRCS =		rpz.c
+
+# Copyright (C) 2004-2009, 2011-2013  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 1998-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$
+
+###
+### Common Makefile rules for BIND 9.
+###
+
+###
+### Paths
+###
+### Note: paths that vary by Makefile MUST NOT be listed
+### here, or they won't get expanded correctly.
+
+prefix =	/usr
+exec_prefix =	${prefix}
+bindir =	${exec_prefix}/bin
+sbindir =	${exec_prefix}/sbin
+includedir =	${prefix}/include
+libdir =	${exec_prefix}/lib
+sysconfdir =	/etc/namedb
+localstatedir =	${prefix}/var
+mandir =	${datarootdir}/man
+datarootdir =   ${prefix}/share
+
+DESTDIR =
+
+
+
+top_builddir =	/usr/home/vjs/isc/work/rpz3
+
+###
+### All
+###
+### Makefile may define:
+###	TARGETS
+
+all: subdirs ${TARGETS} testdirs
+
+###
+### Subdirectories
+###
+### Makefile may define:
+###	SUBDIRS
+
+ALL_SUBDIRS = ${SUBDIRS} nulldir
+ALL_TESTDIRS = ${TESTDIRS} nulldir
+
+#
+# We use a single-colon rule so that additional dependencies of
+# subdirectories can be specified after the inclusion of this file.
+# The "depend" and "testdirs" targets are treated the same way.
+#
+subdirs:
+	@for i in ${ALL_SUBDIRS}; do \
+		if [ "$$i" != "nulldir" -a -d $$i ]; then \
+			echo "making all in `pwd`/$$i"; \
+			(cd $$i; ${MAKE} ${MAKEDEFS} DESTDIR="${DESTDIR}" all) || exit 1; \
+		fi; \
+	done
+
+#
+# Tests are built after the targets instead of before
+#
+testdirs:
+	@for i in ${ALL_TESTDIRS}; do \
+		if [ "$$i" != "nulldir" -a -d $$i ]; then \
+			echo "making all in `pwd`/$$i"; \
+			(cd $$i; ${MAKE} ${MAKEDEFS} DESTDIR="${DESTDIR}" all) || exit 1; \
+		fi; \
+	done
+
+install:: all
+
+install clean distclean maintainer-clean doc docclean man manclean::
+	@for i in ${ALL_SUBDIRS} ${ALL_TESTDIRS}; do \
+		if [ "$$i" != "nulldir" -a -d $$i ]; then \
+			echo "making $@ in `pwd`/$$i"; \
+			(cd $$i; ${MAKE} ${MAKEDEFS} DESTDIR="${DESTDIR}" $@) || exit 1; \
+		fi; \
+	done
+
+###
+### C Programs
+###
+### Makefile must define
+###	CC
+### Makefile may define
+###	CFLAGS
+###	LDFLAGS
+###	CINCLUDES
+###	CDEFINES
+###	CWARNINGS
+### User may define externally
+###     EXT_CFLAGS
+
+CC = 		gcc -pthread
+CFLAGS =	-g -I/usr/local/include/libxml2 -I/usr/local/include
+LDFLAGS =	
+STD_CINCLUDES =	
+STD_CDEFINES =	 -D_THREAD_SAFE
+STD_CWARNINGS =	 -W -Wall -Wmissing-prototypes -Wcast-qual -Wwrite-strings -Wformat -Wpointer-arith -fno-strict-aliasing
+
+BUILD_CC = gcc -pthread
+BUILD_CFLAGS = -g -I/usr/local/include/libxml2 -I/usr/local/include
+BUILD_CPPFLAGS =  
+BUILD_LDFLAGS = 
+BUILD_LIBS =  -L/usr/local/lib -lxml2 -lz -L/usr/local/lib -liconv -lm
+
+.SUFFIXES:
+.SUFFIXES: .c .o
+
+ALWAYS_INCLUDES = -I${top_builddir}
+ALWAYS_DEFINES = -D_REENTRANT
+ALWAYS_WARNINGS =
+
+ALL_CPPFLAGS = \
+	${ALWAYS_INCLUDES} ${CINCLUDES} ${STD_CINCLUDES} \
+	${ALWAYS_DEFINES} ${CDEFINES} ${STD_CDEFINES}
+
+ALL_CFLAGS = ${EXT_CFLAGS} ${ALL_CPPFLAGS} ${CFLAGS} \
+	${ALWAYS_WARNINGS} ${STD_CWARNINGS} ${CWARNINGS}
+
+.c.o:
+	${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c $<
+
+SHELL = /bin/sh
+LIBTOOL = 
+LIBTOOL_MODE_COMPILE = ${LIBTOOL} 
+LIBTOOL_MODE_INSTALL = ${LIBTOOL} 
+LIBTOOL_MODE_LINK = ${LIBTOOL} 
+PURIFY = 
+
+MKDEP = ${SHELL} ${top_builddir}/make/mkdep
+
+###
+### This is a template compound command to build an executable binary with
+### an internal symbol table.
+### This process is tricky.  We first link all objects including a tentative
+### empty symbol table, then get a tentative list of symbols from the resulting
+### binary ($@tmp0).  Next, we re-link all objects, but this time with the
+### symbol table just created ($tmp@1).  The set of symbols should be the same,
+### but the corresponding addresses would be changed due to the difference on
+### the size of symbol tables.  So we create the symbol table and re-create the
+### objects once again.  Finally, we check the symbol table embedded in the
+### final binaryis consistent with the binary itself; otherwise the process is
+### terminated.
+###
+### To minimize the overhead of creating symbol tables, the autoconf switch
+### --enable-symtable takes an argument so that the symbol table can be created
+### on a per application basis: unless the argument is set to "all", the symbol
+### table is created only when a shell (environment) variable "MAKE_SYMTABLE" is
+### set to a non-null value in the rule to build the executable binary.
+###
+### Each Makefile.in that uses this macro is expected to define "LIBS" and
+### "NOSYMLIBS"; the former includes libisc with an empty symbol table, and
+### the latter includes libisc without the definition of a symbol table.
+### The rule to make the executable binary will look like this
+### binary: ${OBJS}
+###     #export MAKE_SYMTABLE="yes"; \  <- enable if symtable is always needed
+###	export BASEOBJS="${OBJS}"; \
+###	${FINALBUILDCMD}
+###
+### Normally, ${LIBS} includes all necessary libraries to build the binary;
+### there are some exceptions however, where the rule lists some of the
+### necessary libraries explicitly in addition to (or instead of) ${LIBS},
+### like this:
+### binary: ${OBJS}
+###     cc -o $@ ${OBJS} ${OTHERLIB1} ${OTHERLIB2} ${lIBS}
+### in order to modify such a rule to use this compound command, a separate
+### variable "LIBS0" should be deinfed for the explicitly listed libraries,
+### while making sure ${LIBS} still includes libisc.  So the above rule would
+### be modified as follows:
+### binary: ${OBJS}
+###	export BASEOBJS="${OBJS}"; \
+###	export LIBS0="${OTHERLIB1} ${OTHERLIB2}"; \
+###     ${FINALBUILDCMD}
+### See bin/check/Makefile.in for a complete example of the use of LIBS0.
+###
+FINALBUILDCMD = if [ X"${MKSYMTBL_PROGRAM}" = X -o X"$${MAKE_SYMTABLE:-${ALWAYS_MAKE_SYMTABLE}}" = X ] ; then \
+		${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} ${LDFLAGS} \
+		-o $@ $${BASEOBJS} $${LIBS0} ${LIBS}; \
+	else \
+		rm -f $@tmp0; \
+		${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} ${LDFLAGS} \
+		-o $@tmp0 $${BASEOBJS} $${LIBS0} ${LIBS} || exit 1; \
+		rm -f $@-symtbl.c $@-symtbl.o; \
+		${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \
+		-o $@-symtbl.c $@tmp0 || exit 1; \
+		$(MAKE) $@-symtbl.o || exit 1; \
+		rm -f $@tmp1; \
+		${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} ${LDFLAGS} \
+		-o $@tmp1 $${BASEOBJS} $@-symtbl.o $${LIBS0} ${NOSYMLIBS} || exit 1; \
+		rm -f $@-symtbl.c $@-symtbl.o; \
+		${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \
+		-o $@-symtbl.c $@tmp1 || exit 1; \
+		$(MAKE) $@-symtbl.o || exit 1; \
+		${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} ${LDFLAGS} \
+		-o $@tmp2 $${BASEOBJS} $@-symtbl.o $${LIBS0} ${NOSYMLIBS}; \
+		${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \
+		-o $@-symtbl2.c $@tmp2; \
+		count=0; \
+		until diff $@-symtbl.c $@-symtbl2.c > /dev/null ; \
+		do \
+			count=`expr $$count + 1` ; \
+			test $$count = 42 && exit 1 ; \
+			rm -f $@-symtbl.c $@-symtbl.o; \
+			${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \
+			-o $@-symtbl.c $@tmp2 || exit 1; \
+			$(MAKE) $@-symtbl.o || exit 1; \
+			${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${ALL_CFLAGS} \
+			${LDFLAGS} -o $@tmp2 $${BASEOBJS} $@-symtbl.o \
+			$${LIBS0} ${NOSYMLIBS}; \
+			${MKSYMTBL_PROGRAM} ${top_srcdir}/util/mksymtbl.pl \
+			-o $@-symtbl2.c $@tmp2; \
+		done ; \
+		mv $@tmp2 $@; \
+		rm -f $@tmp0 $@tmp1 $@tmp2 $@-symtbl2.c; \
+	fi
+
+cleandir: distclean
+superclean: maintainer-clean
+
+clean distclean maintainer-clean::
+	rm -f *.o *.o *.lo *.la core *.core *-symtbl.c *tmp0 *tmp1 *tmp2
+	rm -rf .depend .libs
+
+distclean maintainer-clean::
+	rm -f Makefile
+
+depend:
+	@for i in ${ALL_SUBDIRS}; do \
+		if [ "$$i" != "nulldir" -a -d $$i ]; then \
+			echo "making depend in `pwd`/$$i"; \
+			(cd $$i; ${MAKE} ${MAKEDEFS} DESTDIR="${DESTDIR}" $@) || exit 1; \
+		fi; \
+	done
+	@if [ X"${srcdir}" != X. ] ; then \
+		if [ X"${SRCS}" != X -a X"${PSRCS}" != X ] ; then \
+			echo ${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \
+			${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \
+			echo ${MKDEP} -vpath ${srcdir} -ap ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \
+			${MKDEP} -vpath ${srcdir} -ap ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \
+			${DEPENDEXTRA} \
+		elif [ X"${SRCS}" != X ] ; then \
+			echo ${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \
+			${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \
+			${DEPENDEXTRA} \
+		elif [ X"${PSRCS}" != X ] ; then \
+			echo ${MKDEP} -vpath ${srcdir} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \
+			${MKDEP} -vpath ${srcdir} -p ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \
+			${DEPENDEXTRA} \
+		fi \
+	else \
+		if [ X"${SRCS}" != X -a X"${PSRCS}" != X ] ; then \
+			echo ${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \
+			${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \
+			echo ${MKDEP} -ap ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \
+			${MKDEP} -ap ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \
+			${DEPENDEXTRA} \
+		elif [ X"${SRCS}" != X ] ; then \
+			echo ${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \
+			${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${SRCS}; \
+			${DEPENDEXTRA} \
+		elif [ X"${PSRCS}" != X ] ; then \
+			echo ${MKDEP} ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \
+			${MKDEP} -p ${ALL_CPPFLAGS} ${ALL_CFLAGS} ${PSRCS}; \
+			${DEPENDEXTRA} \
+		fi \
+	fi
+
+FORCE:
+
+###
+### Libraries
+###
+
+AR =		/usr/local/bin/ar
+ARFLAGS =	cruv
+RANLIB =	ranlib
+
+###
+### Installation
+###
+
+INSTALL =		/usr/bin/install -c
+INSTALL_PROGRAM =	${INSTALL}
+LINK_PROGRAM =		ln -s
+INSTALL_SCRIPT =	${INSTALL}
+INSTALL_DATA =		${INSTALL} -m 644
+
+###
+### Programs used when generating documentation.  It's ok for these
+### not to exist when not generating documentation.
+###
+
+XSLTPROC =		xsltproc --novalid --xinclude --nonet
+PERL =			/usr/local/bin/perl5
+LATEX =			latex
+PDFLATEX =		pdflatex
+W3M =			w3m
+
+###
+### Script language program used to create internal symbol tables
+###
+MKSYMTBL_PROGRAM =	/usr/local/bin/perl5
+
+###
+### Switch to create internal symbol table selectively
+###
+ALWAYS_MAKE_SYMTABLE =	
+
+###
+### DocBook -> HTML
+### DocBook -> man page
+###
+
+.SUFFIXES: .docbook .html .1 .2 .3 .4 .5 .6 .7 .8
+
+.docbook.html:
+	${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-docbook-html.xsl $<
+
+.docbook.1:
+	${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $<
+
+.docbook.2:
+	${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $<
+
+.docbook.3:
+	${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $<
+
+.docbook.4:
+	${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $<
+
+.docbook.5:
+	${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $<
+
+.docbook.6:
+	${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $<
+
+.docbook.7:
+	${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $<
+
+.docbook.8:
+	${XSLTPROC} -o $@ ${top_srcdir}/doc/xsl/isc-manpage.xsl $<
+
+###
+### Python executable
+###
+.SUFFIXES: .py
+.py:
+	cp -f $< $@
+	chmod +x $@
+
+
+all: rpz
+
+rpz: ${RPZOBJS}
+	${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ ${RPZOBJS} ${LIBS}
+
+clean distclean::
+	rm -f ${TARGETS}
+
+# DO NOT DELETE THIS LINE -- mkdep uses it.
+# DO NOT PUT ANYTHING AFTER THIS LINE, IT WILL GO AWAY.
+
+rpz.o: rpz.c /usr/home/vjs/isc/work/rpz3/config.h /usr/include/stdlib.h \
+    /usr/include/sys/cdefs.h /usr/include/sys/_null.h \
+    /usr/include/sys/_types.h /usr/include/machine/_types.h \
+    /usr/include/stdio.h /usr/include/string.h /usr/include/strings.h
+
+# IF YOU PUT ANYTHING HERE IT WILL GO AWAY
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
@@ -19,7 +19,7 @@
 # Clean up after rpz tests.
 
 rm -f proto.* dsset-* random.data trusted.conf dig.out* nsupdate.tmp ns*/*tmp
-rm -f ns*/*.key ns*/*.private ns2/tld2s.db ns2/bl.tld2.db
+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.stats */session.key
 rm -f */*.jnl */*.core */*.pid
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
@@ -38,3 +38,6 @@
 ; performance test
 tld5.		NS	ns.tld5.
 ns.tld5.	A	10.53.0.5
+
+; generate SERVFAIL
+servfail	NS	ns.tld2.
diff -r -u bin/tests/system/rpz/ns2/bl.tld2.db-orig bin/tests/system/rpz/ns2/bl.tld2.db
--- bin/tests/system/rpz/ns2/bl.tld2.db-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/ns2/bl.tld2.db	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,27 @@
+; Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+; $Id$
+
+
+
+; master for slave RPZ zone
+
+$TTL	3600
+@		SOA	rpz.tld2.  hostmaster.ns.tld2. ( 1 3600 1200 604800 60 )
+		NS	ns
+ns		A	10.53.0.2
+		A	10.53.0.3
+
+32.1.7.168.192.rpz-ip	 CNAME	.
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
@@ -32,14 +32,6 @@
 	notify no;
 };
 
-key rndc_key {
-	secret "1234abcd8765";
-	algorithm hmac-sha256;
-};
-controls {
-	inet 10.53.0.2 port 9953 allow { any; } keys { rndc_key; };
-};
-
 include "../trusted.conf";
 zone "." { type hint; file "hints"; };
 
@@ -53,4 +45,4 @@
 
 zone "tld2s."		    {type master; file "tld2s.db";};
 
-zone "bl.tld2."		    {type master; file "bl.tld2.db"; notify yes; notify-delay 1;};
+zone "bl.tld2."		    {type master; file "bl.tld2.db";};
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
@@ -111,6 +111,9 @@
 		A	192.168.5.2
 		TXT	"a5-1-2 tld2 text"
 
+a5-2		A	192.168.5.2
+		TXT	"a5-2 tld2 text"
+
 a5-3		A	192.168.5.3
 		TXT	"a5-3 tld2 text"
 
diff -r -u bin/tests/system/rpz/ns3/base.db-orig bin/tests/system/rpz/ns3/base.db
--- bin/tests/system/rpz/ns3/base.db-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/ns3/base.db	2004-01-01 00:00:00.000000000 +0000
@@ -21,30 +21,7 @@
 ;   Its contents are also changed with nsupdate
 
 
-$TTL	120
+$TTL	300
 @	SOA	blx.  hostmaster.ns.blx. ( 1 3600 1200 604800 60 )
-	NS	ns
-ns	A	10.53.0.3
+	NS	ns.tld3.
 
-; Poke the radix tree a little.
-128.1111.2222.3333.4444.5555.6666.7777.8888.rpz-ip	CNAME	.
-128.1111.2222.3333.4444.5555.6666.zz.rpz-ip		CNAME	.
-128.1111.2222.3333.4444.5555.zz.8888.rpz-ip		CNAME	.
-128.1111.2222.3333.4444.zz.8888.rpz-ip			CNAME	.
-128.zz.3333.4444.0.0.8888.rpz-ip			CNAME	.
-128.zz.3333.4444.0.7777.8888.rpz-ip			CNAME	.
-128.zz.3333.4444.0.8777.8888.rpz-ip			CNAME	.
-127.zz.3333.4444.0.8777.8888.rpz-ip			CNAME	.
-
-
-; regression testing for some old crashes
-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/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
@@ -46,20 +46,24 @@
 	    zone "bl-cname"	policy cname txt-only.tld2.;
 	    zone "bl-wildcname"	policy cname *.tld4.;
 	    zone "bl-garden"	policy cname a12.tld2.;
+	    zone "bl-drop"	policy drop;
+	    zone "bl-tcp-only"	policy tcp-only;
 	    zone "bl.tld2";
-	} min-ns-dots 0;
+	}
+	min-ns-dots 0
+	qname-wait-recurse yes
+	;
 };
 
 key rndc_key {
 	secret "1234abcd8765";
-	algorithm hmac-md5;
+	algorithm hmac-sha256;
 };
 controls {
 	inet 10.53.0.3 port 9953 allow { any; } keys { rndc_key; };
 };
 
 
-// include "../trusted.conf";
 zone "." { type hint; file "hints"; };
 
 zone "bl."		{type master; file "bl.db";
@@ -84,9 +88,13 @@
 				allow-update {any;};};
 zone "bl-garden."	{type master; file "bl-garden.db";
 				allow-update {any;};};
+zone "bl-drop."		{type master; file "bl-drop.db";
+				allow-update {any;};};
+zone "bl-tcp-only."	{type master; file "bl-tcp-only.db";
+				allow-update {any;};};
 
 zone "bl.tld2."		{type slave; file "bl.tld2.db"; masters {10.53.0.2;};
-				request-ixfr no; masterfile-format text;};
+				masterfile-format text;};
 
 zone "crash1.tld2"	{type master; file "crash1";};
 zone "crash2.tld3."	{type master; file "crash2";};
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
@@ -1,3 +1,3 @@
 # run the performace test close to real life
 
--c named.conf -g
+-c named.conf -gd3
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
@@ -40,7 +40,7 @@
 
 key rndc_key {
 	secret "1234abcd8765";
-	algorithm hmac-md5;
+	algorithm hmac-sha256;
 };
 controls {
 	inet 10.53.0.5 port 9953 allow { any; } keys { rndc_key; };
@@ -56,3 +56,20 @@
 zone "bl0."		{type master; file "bl.db"; };
 zone "bl1."		{type master; file "bl.db"; };
 zone "bl2."		{type master; file "bl.db"; };
+zone "bl3."		{type master; file "bl.db"; };
+zone "bl4."		{type master; file "bl.db"; };
+zone "bl5."		{type master; file "bl.db"; };
+zone "bl6."		{type master; file "bl.db"; };
+zone "bl7."		{type master; file "bl.db"; };
+zone "bl8."		{type master; file "bl.db"; };
+zone "bl9."		{type master; file "bl.db"; };
+zone "bl10."		{type master; file "bl.db"; };
+zone "bl11."		{type master; file "bl.db"; };
+zone "bl12."		{type master; file "bl.db"; };
+zone "bl13."		{type master; file "bl.db"; };
+zone "bl14."		{type master; file "bl.db"; };
+zone "bl15."		{type master; file "bl.db"; };
+zone "bl16."		{type master; file "bl.db"; };
+zone "bl17."		{type master; file "bl.db"; };
+zone "bl18."		{type master; file "bl.db"; };
+zone "bl19."		{type master; file "bl.db"; };
diff -r -u bin/tests/system/rpz/ns5/tld5.db-orig bin/tests/system/rpz/ns5/tld5.db
--- bin/tests/system/rpz/ns5/tld5.db-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/ns5/tld5.db	2004-01-01 00:00:00.000000000 +0000
@@ -22,42 +22,10 @@
 	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.
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
@@ -26,11 +26,13 @@
 
 sh clean.sh
 
-# set up test policy zones.  bl-2 is used to check competing zones.
-#	bl-{given,disabled,passthru,no-data,nxdomain,cname,wildcard,garden}
-#	are used to check policy overrides in named.conf.
-#	NO-OP is an obsolete synonym for PASSHTRU
-for NM in '' -2 -given -disabled -passthru -no-op -nodata -nxdomain -cname -wildcname -garden; do
+# set up test policy zones.
+#   bl is the main test zone
+#   bl-2 is used to check competing zones.
+#   bl-{given,disabled,passthru,no-data,nxdomain,cname,wildcard,garden,
+#	    drop,tcp-only} are used to check policy overrides in named.conf.
+#   NO-OP is an obsolete synonym for PASSHTRU
+for NM in '' -2 -given -disabled -passthru -no-op -nodata -nxdomain -cname -wildcname -garden -drop -tcp-only; do
     sed -e "/SOA/s/blx/bl$NM/g" ns3/base.db >ns3/bl$NM.db
 done
 
@@ -48,18 +50,22 @@
 signzone ns2 tld2s. base-tld2s.db tld2s.db
 
 
-# Performance checks.
+# Performance and a few other checks.
 cat <<EOF >ns5/rpz-switch
 response-policy {
-	zone "bl0"; zone "bl1"; zone "bl2";
+	zone "bl0"; zone "bl1"; zone "bl2"; zone "bl3"; zone "bl4";
+	zone "bl5"; zone "bl6"; zone "bl7"; zone "bl8"; zone "bl9";
+	zone "bl10"; zone "bl11"; zone "bl12"; zone "bl13"; zone "bl14";
+	zone "bl15"; zone "bl16"; zone "bl17"; zone "bl18"; zone "bl19";
     } recursive-only no
-	max-policy-ttl 90
-	# min-ns-dots 0
-	break-dnssec yes;
+    max-policy-ttl 90
+    break-dnssec yes
+    qname-wait-recurse no
+    ;
 EOF
 
 cat <<EOF >ns5/example.db
-\$TTL	120
+\$TTL	300
 @	SOA	.  hostmaster.ns.example.tld5. ( 1 3600 1200 604800 60 )
 	NS	ns
 	NS	ns1
@@ -68,15 +74,16 @@
 EOF
 
 cat <<EOF >ns5/bl.db
-\$TTL	120
+\$TTL	300
 @		SOA	.  hostmaster.ns.blperf. ( 1 3600 1200 604800 60 )
-		NS	ns
-ns		A	10.53.0.5
+		NS	ns.tld5.
 
-; used only in failure for "recursive-only no" in #8 test5
-a3-5.tld2	CNAME	*.
+; for "qname-wait-recurse no" in #35 test1
+x.servfail	A	35.35.35.35
+; for "recursive-only no" in #8 test5
+a3-5.tld2	CNAME	.
 ; for "break-dnssec" in #9 & #10 test5
-a3-5.tld2s	CNAME	*.
+a3-5.tld2s	CNAME	.
 ; for "max-policy-ttl 90" in #17 test5
 a3-17.tld2	500 A	17.17.17.17
 
@@ -85,8 +92,7 @@
 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
+    # Do not build the full zones if we will not use them.
     $PERL -e 'for ($val = 1; $val <= 65535; ++$val) {
 	printf("host-%05d\tA    192.168.%d.%d\n", $val, $val/256, $val%256);
 	}' >>ns5/example.db
@@ -110,5 +116,3 @@
 	printf("host-%05d.example.tld5 A\n", $val);
 	$val = ($val * 9 + 32771) % 65536;
 	}' >ns5/requests
-
-cp ns2/bl.tld2.db.in ns2/bl.tld2.db
diff -r -u bin/tests/system/rpz/test1-orig bin/tests/system/rpz/test1
--- bin/tests/system/rpz/test1-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/test1	2004-01-01 00:00:00.000000000 +0000
@@ -24,13 +24,13 @@
 ; QNAME tests
 
 ; NXDOMAIN
-;	2, 20, 25
+;	2, 25
 update add  a0-1.tld2.bl.	300 CNAME .
 ; NODATA
-;	3, 21
+;	3
 update add  a3-1.tld2.bl.	300 CNAME *.
 ; and no assert-botch
-;	4, 5, 22, 23
+;	4, 5
 update add  a3-2.tld2.bl.	300 DNAME example.com.
 ;
 ; NXDOMAIN for a4-2-cname.tld2 via its target a4-2.tld2.
@@ -77,6 +77,14 @@
 ;	19
 update add  a4-6.tld2.bl.	300 CNAME .
 update add  a4-6-cname.tld2.bl.	300 A	127.0.0.17
+; no change instead of NXDOMAIN because +norecurse
+;	20
+update add  a5-2.tld2.bl.	300 CNAME .
+; no change instead of NODATA because +norecurse
+;	21
+update add  a5-3.tld2.bl.	300 CNAME *.
+;	22, 23
+update add  a5-4.tld2.bl.	300 DNAME example.com.
 ;
 ; assert in rbtdb.c
 ;	24
@@ -84,4 +92,10 @@
 ; DO=1 without signatures, DO=0 with signatures are rewritten
 ;	26 - 27
 update add  a0-1.tld2s.bl.	300 CNAME .
+;	32
+update add  a3-8.tld2.bl.	300 CNAME rpz-drop.
+;	33
+update add  a3-9.tld2.bl.	300 CNAME rpz-tcp-only.
+;	34 qname-wait-recurse yes
+update add  x.servfail.bl.	300 A	127.0.0.34
 send
diff -r -u bin/tests/system/rpz/test2-orig bin/tests/system/rpz/test2
--- bin/tests/system/rpz/test2-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/test2	2004-01-01 00:00:00.000000000 +0000
@@ -58,7 +58,7 @@
 send
 
 ; prefer QNAME to IP for a5-4.tld2
-;	13
+;	13, 14
 update add 32.4.5.168.192.rpz-ip.bl	300 CNAME a12.tld2.
 update add a5-4.tld2.bl			300 CNAME a14.tld4.
 ;
@@ -72,3 +72,8 @@
 send
 update add c2.crash2.tld3.bl-2		300 A	127.0.0.16
 send
+
+; client-IP address trigger
+;	17
+update add 32.1.0.53.10.rpz-client-ip.bl 300 A	127.0.0.17
+send
diff -r -u bin/tests/system/rpz/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
@@ -35,10 +35,8 @@
 ;	4
 update add  a3-4.tld2.bl-disabled.  300 A 127.0.0.4
 send
-;	5 - 8
+;	5 - 7
 update add  a3-5.tld2.bl-nodata.    300 A 127.0.0.5
-;	9 - 10
-update add  a3-5.tld2s.bl-nodata.   300 A 127.0.0.9
 send
 ;	11
 update add  a3-6.tld2.bl-nxdomain.  300 A 127.0.0.11
@@ -57,3 +55,9 @@
 ;	16
 update add  a3-16.tld2.bl.	    300 A 127.0.0.16
 send
+;	18
+update add  a3-18.tld2.bl-drop.	    300 A 127.0.0.18
+send
+;	19
+update add  a3-19.tld2.bl-tcp-only. 300 A 127.0.0.19
+send
diff -r -u bin/tests/system/rpz/test6-orig bin/tests/system/rpz/test6
--- bin/tests/system/rpz/test6-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/test6	2004-01-01 00:00:00.000000000 +0000
@@ -0,0 +1,40 @@
+; 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'
+
+server 10.53.0.3 5300
+
+; Poke the radix tree a little.
+update add  128.1111.2222.3333.4444.5555.6666.7777.8888.rpz-ip.bl. 300 CNAME .
+update add  128.1111.2222.3333.4444.5555.6666.zz.rpz-ip.bl.	300 CNAME   .
+update add  128.1111.2222.3333.4444.5555.zz.8888.rpz-ip.bl.	300 CNAME   .
+update add  128.1111.2222.3333.4444.zz.8888.rpz-ip.bl.		300 CNAME   .
+update add  128.zz.3333.4444.0.0.8888.rpz-ip.bl.		300 CNAME   .
+update add  128.zz.3333.4444.0.7777.8888.rpz-ip.bl.		300 CNAME   .
+update add  128.zz.3333.4444.0.8777.8888.rpz-ip.bl.		300 CNAME   .
+update add  127.zz.3333.4444.0.8777.8888.rpz-ip.bl.		300 CNAME   .
+;
+;
+; regression testing for some old crashes
+update add  redirect.bl.	300 A		127.0.0.1
+update add  *.redirect.bl.	300 A		127.0.0.1
+update add  *.credirect.bl.	300 CNAME	google.com.
+;
+send
diff -r -u bin/tests/system/rpz/tests.sh-orig bin/tests/system/rpz/tests.sh
--- bin/tests/system/rpz/tests.sh-orig	2004-01-01 00:00:00.000000000 +0000
+++ bin/tests/system/rpz/tests.sh	2004-01-01 00:00:00.000000000 +0000
@@ -21,15 +21,15 @@
 . $SYSTEMTESTTOP/conf.sh
 
 ns=10.53.0
-ns1=$ns.1			    # root, defining the others
-ns2=$ns.2			    # server whose answers are rewritten
-ns3=$ns.3			    # resolve that does the rewriting
-ns4=$ns.4			    # another server that is rewritten
-ns5=$ns.5			    # check performance with this server
+ns1=$ns.1		# root, defining the others
+ns2=$ns.2		# authoritative server whose records are rewritten
+ns3=$ns.3		# main rewriting resolver
+ns4=$ns.4		# another authoritative server that is rewritten
+ns5=$ns.5		# another rewriting resolver
 
 HAVE_CORE=
 SAVE_RESULTS=
-NS3_STATS=47
+
 
 USAGE="$0: [-x]"
 while getopts "x" c; do
@@ -57,13 +57,16 @@
 RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s"
 
 digcmd () {
+    if test "$1" = TCP; then
+	shift
+    fi
     # Default to +noauth and @$ns3
     # Also default to -bX where X is the @value so that OS X will choose
-    #      the right IP source address.
-    digcmd_args=`echo "+noadd +time=1 +tries=1 -p 5300 $*" |   \
-	   sed -e "/@/!s/.*/& @$ns3/"                          \
-	       -e '/-b/!s/@\([^ ]*\)/@\1 -b\1/'                \
-	       -e '/+n?o?auth/!s/.*/+noauth &/'`
+    #	    the right IP source address.
+    digcmd_args=`echo "+noadd +time=1 +tries=1 -p 5300 $*" |	\
+	    sed -e "/@/!s/.*/& @$ns3/"				\
+		-e '/-b/!s/@\([^ ]*\)/@\1 -b\1/'		\
+		-e '/+n?o?auth/!s/.*/+noauth &/'`
     #echo I:dig $digcmd_args 1>&2
     $DIG $digcmd_args
 }
@@ -89,10 +92,13 @@
 # (re)load the reponse policy zones with the rules in the file $TEST_FILE
 load_db () {
     if test -n "$TEST_FILE"; then
-	$NSUPDATE -v $TEST_FILE || {
+	if $NSUPDATE -v $TEST_FILE; then :
+	    $RNDCCMD $ns3 sync
+	else
 	    echo "I:failed to update policy zone with $TEST_FILE"
+	    $RNDCCMD $ns3 sync
 	    exit 1
-	}
+	fi
     fi
 }
 
@@ -135,16 +141,20 @@
     return 1
 }
 
-# check that statistics for $1 in $2 = $3
 ckstats () {
-    rm -f $2/named.stats
-    $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"
+    HOST=$1
+    LABEL="$2"
+    NSDIR="$3"
+    EXPECTED="$4"
+    $RNDCCMD $HOST stats
+    NEW_CNT=0`sed -n -e 's/[	 ]*\([0-9]*\).response policy.*/\1/p'  \
+		    $NSDIR/named.stats | tail -1`
+    eval "OLD_CNT=0\$${NSDIR}_CNT"
+    GOT=`expr $NEW_CNT - $OLD_CNT`
+    if test "$GOT" -ne "$EXPECTED"; then
+	setret "I:wrong $LABEL $NSDIR statistics of $GOT instead of $EXPECTED"
     fi
+    eval "${NSDIR}_CNT=$NEW_CNT"
 }
 
 # $1=message  $2=optional test file name
@@ -181,6 +191,12 @@
 ckresult () {
     #ckalive "$1" "I:server crashed by 'dig $1'" || return 1
     if $PERL $SYSTEMTESTTOP/digcomp.pl $DIGNM $2 >/dev/null; then
+	NEED_TCP=`echo "$1" | sed -n -e 's/[Tt][Cc][Pp].*/TCP/p'`
+	RESULT_TCP=`sed -n -e 's/.*Truncated, retrying in TCP.*/TCP/p' $DIGNM`
+	if test "$NEED_TCP" != "$RESULT_TCP"; then
+	    setret "I:'dig $1' wrong; no or unexpected truncation in $DIGNM"
+	    return 1
+	fi
 	clean_result ${DIGNM}*
 	return 0
     fi
@@ -237,12 +253,14 @@
     clean_result ${DIGNM}*
 }
 
-# check that a response is not rewritten
-# $1=target domain  $2=optional query type
+# Check that a response is not rewritten
+#   Use $ns1 instead of the authority for most test domains, $ns2 to prevent
+#   spurious differences for `dig +norecurse`
+# $1=optional "TCP"  remaining args for dig
 nochange () {
     make_dignm
     digcmd $* >$DIGNM
-    digcmd $* @$ns2 >${DIGNM}_OK
+    digcmd $* @$ns1 >${DIGNM}_OK
     ckresult "$*" ${DIGNM}_OK && clean_result ${DIGNM}_OK
 }
 
@@ -254,6 +272,20 @@
     ckresult "$*" ${DIGNM}_OK
 }
 
+# check dropped response
+DROPPED='^;; connection timed out; no servers could be reached'
+drop () {
+    make_dignm
+    digcmd $* >$DIGNM
+    if grep "$DROPPED" $DIGNM >/dev/null; then
+	clean_result ${DIGNM}*
+	return 0
+    fi
+    setret "I:'dig $1' wrong; response in $DIGNM"
+    return 1
+}
+
+
 # make prototype files to check against rewritten results
 digcmd nonexistent @$ns2 >proto.nxdomain
 digcmd txt-only.tld2 @$ns2 >proto.nodata
@@ -281,19 +313,27 @@
 addr 57.57.57.57  a3-7.sub1.tld2	# 17 wildcard CNAME
 addr 127.0.0.16	  a4-5-cname3.tld2	# 18 CNAME chain
 addr 127.0.0.17	  a4-6-cname3.tld2	# 19 stop short in CNAME chain
-nochange a0-1.tld2	    +norecurse	# 20 check that RD=1 is required
-nochange a3-1.tld2	    +norecurse	# 21
-nochange a3-2.tld2	    +norecurse	# 22
-nochange sub.a3-2.tld2	    +norecurse	# 23
+nochange a5-2.tld2	    +norecurse	# 20 check that RD=1 is required
+nochange a5-3.tld2	    +norecurse	# 21
+nochange a5-4.tld2	    +norecurse	# 22
+nochange sub.a5-4.tld2	    +norecurse	# 23
 nxdomain c1.crash2.tld3			# 24 assert in rbtdb.c
 nxdomain a0-1.tld2	    +dnssec	# 25 simple DO=1 without signatures
-nxdomain a0-1.tld2s			# 26 simple DO=0 with signatures
+nxdomain a0-1.tld2s	    +nodnssec	# 26 simple DO=0 with signatures
 nochange a0-1.tld2s	    +dnssec	# 27 simple DO=1 with signatures
 nxdomain a0-1s-cname.tld2s  +dnssec	# 28 DNSSEC too early in CNAME chain
 nochange a0-1-scname.tld2   +dnssec	# 29 DNSSEC on target in CNAME chain
-nochange a0-1.tld2s srv +auth +dnssec	# 30 no write for +DNSSEC and no record
-nxdomain a0-1.tld2s srv			# 31
+nochange a0-1.tld2s srv +auth +dnssec	# 30 no write for DNSSEC and no record
+nxdomain a0-1.tld2s srv	    +nodnssec	# 31
+drop a3-8.tld2 any			# 32 drop
+nochange tcp a3-9.tld2			# 33 tcp-only
+here x.servfail <<'EOF'			# 34 qname-wait-recurse yes
+    ;; status: SERVFAIL, x
+EOF
+addr 35.35.35.35 "x.servfail @$ns5"	# 35 qname-wait-recurse no
 end_group
+ckstats $ns3 test1 ns3 22
+ckstats $ns5 test1 ns5 1
 
 start_group "IP rewrites" test2
 nodata a3-1.tld2			# 1 NODATA
@@ -308,35 +348,14 @@
 nochange a4-1-aaaa.tld2 -taaaa		# 10
 addr 127.0.0.1	 a5-1-2.tld2		# 11 prefer smallest policy address
 addr 127.0.0.1	 a5-3.tld2		# 12 prefer first conflicting IP zone
-addr 14.14.14.14 a5-4.tld2		# 13 prefer QNAME to IP
-nochange a5-4.tld2	    +norecurse	# 14 check that RD=1 is required
+nochange a5-4.tld2	    +norecurse	# 13 check that RD=1 is required for #14
+addr 14.14.14.14 a5-4.tld2		# 14 prefer QNAME to IP
 nochange a4-4.tld2			# 15 PASSTHRU
 nxdomain c2.crash2.tld3			# 16 assert in rbtdb.c
-ckstats $ns3 ns3 29
-nxdomain a7-1.tld2			# 17 slave policy zone (RT34450)
-cp ns2/blv2.tld2.db.in ns2/bl.tld2.db
-$RNDCCMD 10.53.0.2 reload bl.tld2
-goodsoa="rpz.tld2. hostmaster.ns.tld2. 2 3600 1200 604800 60"
-for i in 0 1 2 3 4 5 6 7 8 9 10
-do
-	soa=`$DIG -p 5300 +short soa bl.tld2 @10.53.0.3 -b10.53.0.3`
-	test "$soa" = "$goodsoa" && break
-	sleep 1
-done
-nochange a7-1.tld2			# 18 PASSTHRU
-sleep 1	# ensure that a clock tick has occured so that the reload takes effect
-cp ns2/blv3.tld2.db.in ns2/bl.tld2.db
-goodsoa="rpz.tld2. hostmaster.ns.tld2. 3 3600 1200 604800 60"
-$RNDCCMD 10.53.0.2 reload bl.tld2
-for i in 0 1 2 3 4 5 6 7 8 9 10
-do
-	soa=`$DIG -p 5300 +short soa bl.tld2 @10.53.0.3 -b10.53.0.3`
-	test "$soa" = "$goodsoa" && break
-	sleep 1
-done
-nxdomain a7-1.tld2			# 19 slave policy zone (RT34450)
-ckstats $ns3 ns3 31
+addr 127.0.0.17 "a4-4.tld2 -b $ns1"	# 17 client-IP address trigger
+nxdomain a7-1.tld2			# 18 slave policy zone (RT34450)
 end_group
+ckstats $ns3 test2 ns3 11
 
 # check that IP addresses for previous group were deleted from the radix tree
 start_group "radix tree deletions"
@@ -352,6 +371,7 @@
 nochange a4-1-aaaa.tld2 -tAAAA
 nochange a5-1-2.tld2
 end_group
+ckstats $ns3 'radix tree deletions' ns3 0
 
 if ./rpz nsdname; then
     # these tests assume "min-ns-dots 0"
@@ -369,7 +389,7 @@
     addr 127.0.0.2 a3-1.subsub.sub3.tld2
     nxdomain xxx.crash1.tld2		# 12 dns_db_detachnode() crash
     end_group
-    NS3_STATS=`expr $NS3_STATS + 7`
+    ckstats $ns3 test3 ns3 7
 else
     echo "I:NSDNAME not checked; named configured with --disable-rpz-nsdname"
 fi
@@ -383,15 +403,15 @@
     nochange a3-1.tld4			# 4 different NS IP address
     end_group
 
-#    start_group "walled garden NSIP rewrites" test4a
-#    addr 41.41.41.41 a3-1.tld2		# 1 walled garden for all of tld2
-#    addr 2041::41   'a3-1.tld2 AAAA'	# 2 walled garden for all of tld2
-#    here a3-1.tld2 TXT <<'EOF'		# 3 text message for all of tld2
-#    ;; status: NOERROR, x
-#    a3-1.tld2.	    x	IN	TXT   "NSIP walled garden"
-#EOF
-#    end_group
-    NS3_STATS=`expr $NS3_STATS + 1`
+    start_group "walled garden NSIP rewrites" test4a
+    addr 41.41.41.41 a3-1.tld2		# 1 walled garden for all of tld2
+    addr 2041::41   'a3-1.tld2 AAAA'	# 2 walled garden for all of tld2
+    here a3-1.tld2 TXT <<'EOF'		# 3 text message for all of tld2
+    ;; status: NOERROR, x
+    a3-1.tld2.	    x	IN	TXT   "NSIP walled garden"
+EOF
+    end_group
+    ckstats $ns3 test4 ns3 4
 else
     echo "I:NSIP not checked; named configured with --disable-rpz-nsip"
 fi
@@ -403,12 +423,12 @@
 nochange a3-2.tld2			# 2 bl-passthru
 nochange a3-3.tld2			# 3 bl-no-op	obsolete for passthru
 nochange a3-4.tld2			# 4 bl-disabled
-nodata a3-5.tld2			# 5 bl-nodata
-nodata a3-5.tld2    +norecurse		# 6 bl-nodata	    recursive-only no
-nodata a3-5.tld2			# 7 bl-nodata
-nodata a3-5.tld2    +norecurse	@$ns5	# 8 bl-nodata	    recursive-only no
-nodata a3-5.tld2s		@$ns5	# 9 bl-nodata
-nodata a3-5.tld2s   +dnssec	@$ns5	# 10 bl-nodata	    break-dnssec
+nodata a3-5.tld2			# 5 bl-nodata	zone recursive-only no
+nodata a3-5.tld2    +norecurse		# 6 bl-nodata	zone recursive-only no
+nodata a3-5.tld2			# 7 bl-nodata		not needed
+nxdomain a3-5.tld2  +norecurse	@$ns5	# 8 bl-nodata	global recursive-only no
+nxdomain a3-5.tld2s		@$ns5	# 9 bl-nodata	global break-dnssec
+nxdomain a3-5.tld2s +dnssec	@$ns5	# 10 bl-nodata	global break-dnssec
 nxdomain a3-6.tld2			# 11 bl-nxdomain
 here a3-7.tld2 -tany <<'EOF'
     ;; status: NOERROR, x
@@ -420,10 +440,15 @@
 addr 12.12.12.12 a3-15.tld2		# 15 bl-garden	via CNAME to a12.tld2
 addr 127.0.0.16 a3-16.tld2	    100	# 16 bl		    max-policy-ttl 100
 addr 17.17.17.17 "a3-17.tld2 @$ns5" 90	# 17 ns5 bl	    max-policy-ttl 90
+drop a3-18.tld2 any			# 18 bl-drop
+nxdomain TCP a3-19.tld2			# 19 bl-tcp-only
 end_group
+ckstats $ns3 test5 ns3 12
+ckstats $ns5 test5 ns5 4
+
 
 # check that miscellaneous bugs are still absent
-start_group "crashes"
+start_group "crashes" test6
 for Q in RRSIG SIG ANY 'ANY +dnssec'; do
     nocrash a3-1.tld2 -t$Q
     nocrash a3-2.tld2 -t$Q
@@ -437,6 +462,8 @@
 # resolving foo.
 # nxdomain 32.3.2.1.127.rpz-ip
 end_group
+ckstats $ns3 bugs ns3 8
+
 
 
 # superficial test for major performance bugs
@@ -449,6 +476,7 @@
 	$QPERF -c -1 -l30 -d ns5/requests -s $ns5 -p 5300 >/dev/null
 	comment "before real test $1"
 	PFILE="ns5/$2.perf"
+	$RNDCCMD $ns5 notrace
 	$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 \
@@ -463,17 +491,17 @@
     }
 
     # get qps with rpz
-    perf 'with rpz' rpz 'NOERROR:2900 NXDOMAIN:100 '
+    perf 'with RPZ' rpz 'NOERROR:2900 NXDOMAIN:100 '
     RPZ=`trim rpz`
 
     # turn off rpz and measure qps again
-    echo "# rpz off" >ns5/rpz-switch
+    echo "# RPZ off" >ns5/rpz-switch
     RNDCCMD_OUT=`$RNDCCMD $ns5 reload`
-    perf 'without rpz' norpz 'NOERROR:3000 '
+    perf 'without RPZ' norpz 'NOERROR:3000 '
     NORPZ=`trim norpz`
 
     PERCENT=`expr \( "$RPZ" \* 100 + \( $NORPZ / 2 \) \) / $NORPZ`
-    echo "I:$RPZ qps with rpz is $PERCENT% of $NORPZ qps without rpz"
+    echo "I:$RPZ qps with RPZ is $PERCENT% of $NORPZ qps without RPZ"
 
     MIN_PERCENT=30
     if test "$PERCENT" -lt $MIN_PERCENT; then
@@ -484,13 +512,12 @@
 	setret "I:$RPZ qps with RPZ or $PERCENT% of $NORPZ qps without RPZ is too high"
     fi
 
-    ckstats $ns5 ns5 203
+    ckstats $ns5 performance ns5 200
 
 else
     echo "I:performance not checked; queryperf not available"
 fi
 
-ckstats $ns3 ns3 57
 
 # restart the main test RPZ server to see if that creates a core file
 if test -z "$HAVE_CORE"; then
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
@@ -4873,7 +4873,7 @@
 	<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> policy given | disabled | passthru | drop | 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> min-ns-dots <replaceable>number</replaceable> </optional> ; </optional>
@@ -9167,77 +9167,122 @@
 	    Response policy zones are named in the
 	    <command>response-policy</command> option for the view or among the
 	    global options if there is no response-policy option for the view.
-	    RPZs are ordinary DNS zones containing RRsets
+	    Response policy zones are ordinary DNS zones containing RRsets
 	    that can be queried normally if allowed.
 	    It is usually best to restrict those queries with something like
 	    <command>allow-query { localhost; };</command>.
 	  </para>
 
 	  <para>
-	    Four policy triggers are encoded in RPZ records, QNAME, IP, NSIP,
-	    and NSDNAME.
-	    QNAME RPZ records triggered by query names of requests and targets
-	    of CNAME records resolved to generate the response.
-	    The owner name of a QNAME RPZ record is the query name relativized
-	    to the RPZ.
-	  </para>
+	    Five policy triggers can be encoded in RPZ records.
+	    <variablelist>
+	      <varlistentry>
+		<term><command>RPZ-CLIENT-IP</command></term>
+		<listitem>
+		  <para>
+		    IP records are triggered by the IP address of the
+		    DNS client.
+		    Client IP address triggers are encoded in records that have
+		    owner names that are subdomains of
+		    <command>rpz-client-ip</command> relativized to the
+		    policy zone origin name
+		    and encode an address or address block.
+		    IPv4 addresses are represented as
+		    <userinput>prefixlength.B4.B3.B2.B1.rpz-ip</userinput>.
+		    The IPv4 prefix length must be between 1 and 32.
+		    All four bytes, B4, B3, B2, and B1, must be present.
+		    B4 is the decimal value of the least significant byte of the
+		    IPv4 address as in IN-ADDR.ARPA.
+		  </para>
 
-	  <para>
-	    The second kind of RPZ trigger is an IP address in an A and AAAA
-	    record in the ANSWER section of a response.
-	    IP address triggers are encoded in records that have owner names
-	    that are subdomains of <userinput>rpz-ip</userinput> relativized
-	    to the RPZ origin name and encode an IP address or address block.
-	    IPv4 trigger addresses are represented as
-	    <userinput>prefixlength.B4.B3.B2.B1.rpz-ip</userinput>.
-	    The prefix length must be between 1 and 32.
-	    All four bytes, B4, B3, B2, and B1, must be present.
-	    B4 is the decimal value of the least significant byte of the
-	    IPv4 address as in IN-ADDR.ARPA.
-	    IPv6 addresses are encoded in a format similar to the standard
-	    IPv6 text representation,
-	    <userinput>prefixlength.W8.W7.W6.W5.W4.W3.W2.W1.rpz-ip</userinput>.
-	    Each of W8,...,W1 is a one to four digit hexadecimal number
-	    representing 16 bits of the IPv6 address as in the standard text
-	    representation of IPv6 addresses, but reversed as in IN-ADDR.ARPA.
-	    All 8 words must be present except when consecutive
-	    zero words are replaced with <userinput>.zz.</userinput>
-	    analogous to double colons (::) in standard IPv6 text encodings.
-	    The prefix length must be between 1 and 128.
-	  </para>
+		  <para>
+		    IPv6 addresses are encoded in a format similar
+		    to the standard IPv6 text representation,
+		    <userinput>prefixlength.W8.W7.W6.W5.W4.W3.W2.W1.rpz-ip</userinput>.
+		    Each of W8,...,W1 is a one to four digit hexadecimal number
+		    representing 16 bits of the IPv6 address as in the standard 
+		    text representation of IPv6 addresses,
+		    but reversed as in IN-ADDR.ARPA.
+		    All 8 words must be present except when one set of consecutive
+		    zero words is replaced with <userinput>.zz.</userinput>
+		    analogous to double colons (::) in standard IPv6 text
+		    encodings.
+		    The IPv6 prefix length must be between 64 and 128.
+		  </para>
+		</listitem>
+	      </varlistentry>
 
-	  <para>
-	    NSDNAME triggers match names of authoritative servers
-	    for the query name, a parent of the query name, a CNAME for
-	    query name, or a parent of a CNAME.
-	    They are encoded as subdomains of
-	    <userinput>rpz-nsdomain</userinput> relativized
-	    to the RPZ origin name.
-	    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>
+	      <varlistentry>
+		<term><command>QNAME</command></term>
+		<listitem>
+		  <para>
+		    QNAME policy records are triggered by query names of
+		    requests and targets of CNAME records resolved to generate
+		    the response.
+		    The owner name of a QNAME policy record is
+		    the query name relativized to the policy zone.
+		  </para>
+		</listitem>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>RPZ-IP</command></term>
+		<listitem>
+		  <para>
+		    IP triggers are IP addresses in an
+		    A or AAAA record in the ANSWER section of a response.
+		    They are encoded like client-IP triggers except as
+		    subdomains of <command>rpz-ip</command>.
+		  </para>
+		</listitem>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>RPZ-NSDNAME</command></term>
+		<listitem>
+		  <para>
+		    NSDNAME triggers match names of authoritative servers
+		    for the query name, a parent of the query name, a CNAME for
+		    query name, or a parent of a CNAME.
+		    They are encoded as subdomains of
+		    <command>rpz-nsdname</command> relativized
+		    to the RPZ origin name.
+		    NSIP triggers match IP addresses in A and
+		    AAAA RRsets for domains that can be checked against NSDNAME
+		    policy records.
+		  </para>
+		</listitem>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>RPZ-NSIP</command></term>
+		<listitem>
+		  <para>
+		    NSIP triggers are encoded like IP triggers except as
+		    subdomains of <command>rpz-nsip</command>.
+		    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>
+		</listitem>
+	      </varlistentry>
+	    </variablelist>
 
 	  <para>
-	    The query response is checked against all RPZs, so
-	    two or more policy records can be triggered by a response.
-	    Because DNS responses can be rewritten according to at most one
+	    The query response is checked against all response policy zones,
+	    so two or more policy records can be triggered by a response.
+	    Because DNS responses are rewritten according to at most one
 	    policy record, a single record encoding an action (other than
 	    <command>DISABLED</command> actions) must be chosen.
-	    Triggers or the records that encode them are chosen in
-	    the following order:
+	    Triggers or the records that encode them are chosen for the
+	    rewriting in the following order:
 	    <itemizedlist>
 	      <listitem>Choose the triggered record in the zone that appears
-		first in the response-policy option.
+		first in the <command>response-policy</command> option.
 	      </listitem>
-	      <listitem>Prefer QNAME to IP to NSDNAME to NSIP triggers
-		in a single zone.
+	      <listitem>Prefer CLIENT-IP to QNAME to IP to NSDNAME to NSIP
+		triggers in a single zone.
 	      </listitem>
 	      <listitem>Among NSDNAME triggers, prefer the
 		trigger that matches the smallest name under the DNSSEC ordering.
@@ -9256,83 +9301,168 @@
 	    When the processing of a response is restarted to resolve
 	    DNAME or CNAME records and a policy record set has
 	    not been triggered,
-	    all RPZs are again consulted for the DNAME or CNAME names
-	    and addresses.
+	    all response policy zones are again consulted for the
+	    DNAME or CNAME names and addresses.
 	  </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>
-	      <listitem>The <command>NXDOMAIN</command> response is encoded
-		by a CNAME whose target is the root domain (.)
-	      </listitem>
-	      <listitem>A CNAME whose target is the wildcard top-level
-		domain (*.) specifies the <command>NODATA</command> action,
-		which rewrites the response to NODATA or ANCOUNT=1.
-	      </listitem>
-	      <listitem>The <command>Local Data</command> action is
-		represented by a set ordinary DNS records that are used
-		to answer queries.  Queries for record types not the
-		set are answered with NODATA.
-
-		A special form of local data is a CNAME whose target is a
-		wildcard such as *.example.com.
-		It is used as if were an ordinary CNAME after the astrisk (*)
-		has been replaced with the query name.
-		The purpose for this special form is query logging in the
-		walled garden's authority DNS server.
-	      </listitem>
-	      <listitem>The <command>PASSTHRU</command> policy is specified
-		by a CNAME whose target is <command>rpz-passthru.</command>
-		It causes the response to not be rewritten
-		and is most often used to "poke holes" in policies for
-		CIDR blocks.
-		(A CNAME whose target is the variable part of its owner name
-		is an obsolete specification of the PASSTHRU policy.)
-	      </listitem>
-	    </itemizedlist>
+	    RPZ record sets are any types of DNS record except
+	    DNAME or DNSSEC that encode actions or responses to
+	    individual queries.
+	    Any of the policies can be used with any of the triggers.
+	    For example, while the <command>TCP-only</command> policy is
+	    commonly used with <command>client-IP</command> triggers,
+	    it cn be used with any type of trigger to force the use of
+	    TCP for responses with owner names in a zone.
+	    <variablelist>
+	      <varlistentry>
+		<term><command>PASSTHRU</command></term>
+		<listitem>
+		  <para>
+		    The whitelist policy is specified
+		    by a CNAME whose target is <command>rpz-passthru</command>.
+		    It causes the response to not be rewritten
+		    and is most often used to "poke holes" in policies for
+		    CIDR blocks.
+		  </para>
+		</listitem>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>DROP</command></term>
+		<listitem>
+		  <para>
+		    The blacklist policy is specified
+		    by a CNAME whose target is <command>rpz-drop</command>.
+		    It causes the response to be discarded.
+		    Nothing is sent to the DNS client.
+		  </para>
+		</listitem>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>TCP-Only</command></term>
+		<listitem>
+		  <para>
+		    The "slip" policy is specified
+		    by a CNAME whose target is <command>rpz-tcp-only</command>.
+		    It changes UDP responses to short, truncated DNS responses
+		    that require the DNS client to try again with TCP.
+		    It is used to mitigate distributed DNS reflection attacks.
+		  </para>
+		</listitem>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>NXDOMAIN</command></term>
+		<listitem>
+		  <para>
+		    The domain undefined response is encoded
+		    by a CNAME whose target is the root domain (.)
+		  </para>
+		</listitem>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>NODATA</command></term>
+		<listitem>
+		  <para>
+		    The empty set of resource records is specified by
+		    CNAME whose target is the wildcard top-level
+		    domain (*.).
+		    It rewrites the response to NODATA or ANCOUNT=1.
+		  </listitem>
+		</para>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>Local Data</command></term>
+		<listitem>
+		  <para>
+		    A set of ordinary DNS records can be used to answer queries.
+		    Queries for record types not the set are answered with
+		    NODATA.
+		  </para>
+
+		  <para>
+		    A special form of local data is a CNAME whose target is a
+		    wildcard such as *.example.com.
+		    It is used as if were an ordinary CNAME after the astrisk (*)
+		    has been replaced with the query name.
+		    The purpose for this special form is query logging in the
+		    walled garden's authority DNS server.
+		  </para>
+		</listitem>
+	      </varlistentry>
+	    </variablelist>
 	  </para>
 
 	  <para>
-	    The actions specified in an RPZ can be overridden with a
-	    <command>policy</command> clause in the
+	    All of the actions specified in all of the individual records
+	    in a policy zone
+	    can be overridden with a <command>policy</command> clause in the
 	    <command>response-policy</command> option.
-	    An organization using an RPZ provided by another organization might
-	    use this mechanism to redirect domains to its own walled garden.
-	    <itemizedlist>
-	      <listitem><command>GIVEN</command> says "do not override but
-		perform the action specified in the zone."
-	      </listitem>
-	      <listitem><command>DISABLED</command> causes policy records to do
-		nothing but log what they might have done.
-		The response to the DNS query will be written according to
-		any triggered policy records that are not disabled.
-		Disabled policy zones should appear first,
-		because they will often not be logged
-		if a higher precedence trigger is found first.
-	      </listitem>
-	      <listitem><command>PASSTHRU</command> causes all policy records
-		to act as if they were CNAME records with targets the variable
-		part of their owner name.  They protect the response from
-		being changed.
-	      </listitem>
-	      <listitem><command>NXDOMAIN</command> causes all RPZ records
-		to specify NXDOMAIN policies.
-	      </listitem>
-	      <listitem><command>NODATA</command> overrides with the
-		NODATA policy
-	      </listitem>
-	      <listitem><command>CNAME domain</command> causes all RPZ
-		policy records to act as if they were "cname domain" records.
-	      </listitem>
-	    </itemizedlist>
+	    An organization using a policy zone provided by another
+	    organization might use this mechanism to redirect domains
+	    to its own walled garden.
+	    <variablelist>
+	      <varlistentry>
+		<term><command>GIVEN</command></term>
+		<listitem>
+		  <para>The placeholder policy says "do not override but
+		    perform the action specified in the zone."
+		  </para>
+		</listitem>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>DISABLED</command></term>
+		<listitem>
+		  <para>
+		    The testing override policy causes policy zone records to do
+		    nothing but log what they would have done if the
+		    policy zone were not disabled.
+		    The response to the DNS query will be written (or not)
+		    according to any triggered policy records that are not
+		    disabled.
+		    Disabled policy zones should appear first,
+		    because they will often not be logged
+		    if a higher precedence trigger is found first.
+		  </para>
+		</listitem>
+	      </varlistentry>
+
+	      <varlistentry>
+		<term><command>PASSTHRU</command></term>,
+		<term><command>DROP</command></term>,
+		<term><command>TCP-Only</command></term>,
+		<term><command>NXDOMAIN</command></term>,
+		and
+		<term><command>NODATA</command></term>
+		<listitem>
+		  <para>
+		    override with the corresponding per-record policy.
+		  </listitem>
+		</para>
+	      </varlistentry>
+
+	      <varlistentry>
+		  <term><command>CNAME domain</command></term>
+		  <listitem>
+		    <para>
+		      causes all RPZ policy records to act as if they were
+		      "cname domain" records.
+		    </para>
+		  </listitem>
+		</varlistentry>
+	    </variablelist>
 	  </para>
 
 	  <para>
-	    By default, the actions encoded in an RPZ are applied
-	    only to queries that ask for recursion (RD=1).
-	    That default can be changed for a single RPZ or all RPZs in a view
+	    By default, the actions encoded in a response policy zone
+	    are applied only to queries that ask for recursion (RD=1).
+	    That default can be changed for a single policy zone or
+	    all response policy zones in a view
 	    with a <command>recursive-only no</command> clause.
 	    This feature is useful for serving the same zone files
 	    both inside and outside an RFC 1918 cloud and using RPZ to
@@ -9341,15 +9471,43 @@
 	  </para>
 
 	  <para>
-	    Also by default, RPZ actions are applied only to DNS requests that
-	    either do not request DNSSEC metadata (DO=0) or when no DNSSEC
-	    records are available for request name in the original zone (not
-	    the response policy zone).
-	    This default can be changed for all RPZs in a view with a
-	    <command>break-dnssec yes</command> clause.
-	    In that case, RPZ actions are applied regardless of DNSSEC.
-	    The name of the clause option reflects the fact that results
-	    rewritten by RPZ actions cannot verify.
+            Also by default, RPZ actions are applied only to DNS requests
+            that either do not request DNSSEC metadata (DO=0) or when no
+            DNSSEC records are available for request name in the original
+            zone (not the response policy zone).  This default can be
+            changed for all response policy zones in a view with a
+            <command>break-dnssec yes</command> clause.  In that case, RPZ
+            actions are applied regardless of DNSSEC.  The name of the
+            clause option reflects the fact that results rewritten by RPZ
+            actions cannot verify.
+	  </para>
+
+	  <para>
+            No DNS records are needed for a QNAME or Client-IP trigger.
+	    The name or IP address itself is sufficient,
+	    so in principle the query name need not be recursively resolved.
+	    However, not resolving the requested
+            name can leak the fact that response policy rewriting is in use
+            and that the name is listed in a policy zone to operators of
+            servers for listed names.  To prevent that information leak, by
+            default any recursion needed for a request is done before any
+            policy triggers are considered.  Because listed domains often
+            have slow authoritative servers, this default behavior can cost
+            significant time.
+	    The <command>qname-wait-recurse no</command> option
+            overrides that default behavior when recursion cannot
+            change a non-error response.
+	    The option does not affect QNAME or client-IP triggers
+	    in policy zones listed
+	    after other zones containing IP, NSIP and NSDNAME triggers, because
+            those may depend on the A, AAAA, and NS records that would be
+            found during recursive resolution.  It also does not affect
+            DNSSEC requests (DO=1) unless <command>break-dnssec yes</command>
+            is in use, because the response would depend on whether or not
+            RRSIG records were found during resolution.
+	    The option can cause appear to rewrite error responses
+	    such as SERVFAIL when no recursion is done to discover problems
+	    at the authoritative server.
 	  </para>
 
 	  <para>
@@ -9377,26 +9535,38 @@
 
 ; QNAME policy records.  There are no periods (.) after the owner names.
 nxdomain.domain.com     CNAME   .               ; NXDOMAIN policy
+*.nxdomain.domain.com   CNAME   .               ; NXDOMAIN policy
 nodata.domain.com       CNAME   *.              ; NODATA policy
+*.nodata.domain.com     CNAME   *.              ; NODATA policy
 bad.domain.com          A       10.0.0.1        ; redirect to a walled garden
                         AAAA    2001:2::1
+bzone.domain.com        CNAME   garden.example.com.
 
 ; do not rewrite (PASSTHRU) OK.DOMAIN.COM
 ok.domain.com           CNAME   rpz-passthru.
 
-bzone.domain.com        CNAME   garden.example.com.
-
 ; redirect x.bzone.domain.com to x.bzone.domain.com.garden.example.com
 *.bzone.domain.com      CNAME   *.garden.example.com.
 
 
-; IP policy records that rewrite all answers for 127/8 except 127.0.0.1
+; IP policy records that rewrite all responses containing A records in 127/8
+;       except 127.0.0.1
 8.0.0.0.127.rpz-ip      CNAME   .
 32.1.0.0.127.rpz-ip     CNAME   rpz-passthru.
 
 ; NSDNAME and NSIP policy records
 ns.domain.com.rpz-nsdname   CNAME   .
 48.zz.2.2001.rpz-nsip       CNAME   .
+
+; blacklist and whitelist some DNS clients
+112.zz.2001.rpz-client-ip    CNAME   rpz-drop.
+8.0.0.0.127.rpz-client-ip    CNAME   rpz-drop.
+
+; force some DNS clients and responses in the example.com zone to TCP
+16.0.0.1.10.rpz-client-ip   CNAME   rpz-tcp-only.
+example.com                 CNAME   rpz-tcp-only.
+*.example.com               CNAME   rpz-tcp-only.
+
 </programlisting>
           <para>
             RPZ can affect server performance.
diff -r -u lib/dns/db.c-orig lib/dns/db.c
--- lib/dns/db.c-orig	2004-01-01 00:00:00.000000000 +0000
+++ lib/dns/db.c	2004-01-01 00:00:00.000000000 +0000
@@ -1007,21 +1007,23 @@
 		(db->methods->resigned)(db, rdataset, version);
 }
 
-isc_result_t
-dns_db_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st)
-{
-	if (db->methods->rpz_enabled != NULL)
-		return ((db->methods->rpz_enabled)(db, st));
-	return (ISC_R_SUCCESS);
+/*
+ * Attach a database to policy zone databases.
+ * This should only happen when the caller has already ensured that
+ * it is dealing with a database that understands response policy zones.
+ */
+void
+dns_db_rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
+	REQUIRE(db->methods->rpz_attach != NULL);
+	(db->methods->rpz_attach)(db, rpzs, rpz_num);
 }
 
-void
-dns_db_rpz_findips(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
-		   dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
-		   dns_rdataset_t *ardataset, dns_rpz_st_t *st,
-		   dns_name_t *query_qname)
-{
-	if (db->methods->rpz_findips != NULL)
-		(db->methods->rpz_findips)(rpz, rpz_type, zone, db, version,
-					   ardataset, st, query_qname);
+/*
+ * Finish loading a response policy zone.
+ */
+isc_result_t
+dns_db_rpz_ready(dns_db_t *db) {
+	if (db->methods->rpz_ready == NULL)
+		return (ISC_R_SUCCESS);
+	return ((db->methods->rpz_ready)(db));
 }
diff -r -u lib/dns/ecdb.c-orig lib/dns/ecdb.c
--- lib/dns/ecdb.c-orig	2004-01-01 00:00:00.000000000 +0000
+++ lib/dns/ecdb.c	2004-01-01 00:00:00.000000000 +0000
@@ -582,8 +582,8 @@
 	NULL,			/* resigned */
 	NULL,			/* isdnssec */
 	NULL,			/* getrrsetstats */
-	NULL,			/* rpz_enabled */
-	NULL,			/* rpz_findips */
+ 	NULL,			/* rpz_attach */
+ 	NULL,			/* rpz_ready */
 	NULL,			/* findnodeext */
 	NULL			/* findext */
 };
diff -r -u lib/dns/include/dns/db.h-orig lib/dns/include/dns/db.h
--- lib/dns/include/dns/db.h-orig	2004-01-01 00:00:00.000000000 +0000
+++ lib/dns/include/dns/db.h	2004-01-01 00:00:00.000000000 +0000
@@ -172,14 +172,9 @@
 					   dns_dbversion_t *version);
 	isc_boolean_t	(*isdnssec)(dns_db_t *db);
 	dns_stats_t	*(*getrrsetstats)(dns_db_t *db);
-	isc_result_t	(*rpz_enabled)(dns_db_t *db, dns_rpz_st_t *st);
-	void		(*rpz_findips)(dns_rpz_zone_t *rpz,
-				       dns_rpz_type_t rpz_type,
-				       dns_zone_t *zone, dns_db_t *db,
-				       dns_dbversion_t *version,
-				       dns_rdataset_t *ardataset,
-				       dns_rpz_st_t *st,
-				       dns_name_t *query_qname);
+	void		(*rpz_attach)(dns_db_t *db, dns_rpz_zones_t *rpzs,
+				      dns_rpz_num_t rpz_num);
+	isc_result_t	(*rpz_ready)(dns_db_t *db);
 	isc_result_t	(*findnodeext)(dns_db_t *db, dns_name_t *name,
 				     isc_boolean_t create,
 				     dns_clientinfomethods_t *methods,
@@ -1542,30 +1537,17 @@
  *	dns_rdatasetstats_create(); otherwise NULL.
  */
 
-isc_result_t
-dns_db_rpz_enabled(dns_db_t *db, dns_rpz_st_t *st);
+void
+dns_db_rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num);
 /*%<
- * Mark a database for response policy rewriting
- * or find which RPZ data is available.
+ * Attach the response policy information for a view to a database for a
+ * zone for the view.
  */
 
-void
-dns_db_rpz_findips(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
-		   dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
-		   dns_rdataset_t *ardataset, dns_rpz_st_t *st,
-		   dns_name_t *query_qname);
-/*%<
- * Search the CDIR block tree of a response policy tree of trees for the best
- * match to any of the IP addresses in an A or AAAA rdataset.
- *
- * Requires:
- * \li	search in policy zone 'rpz' for a match of 'rpz_type' either
- *	    DNS_RPZ_TYPE_IP or DNS_RPZ_TYPE_NSIP
- * \li	'zone' and 'db' are the database corresponding to 'rpz'
- * \li	'version' is the required version of the database
- * \li	'ardataset' is an A or AAAA rdataset of addresses to check
- * \li	'found' specifies the previous best match if any or
- *	    or NULL, an empty name, 0, DNS_RPZ_POLICY_MISS, and 0
+isc_result_t
+dns_db_rpz_ready(dns_db_t *db);
+/*%<
+ * Finish loading a response policy zone.
  */
 
 ISC_LANG_ENDDECLS
diff -r -u lib/dns/include/dns/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
@@ -25,19 +25,31 @@
 #include <dns/fixedname.h>
 #include <dns/rdata.h>
 #include <dns/types.h>
+#include <isc/refcount.h>
 
 ISC_LANG_BEGINDECLS
 
 #define DNS_RPZ_PREFIX		"rpz-"
+/*
+ * Sub-zones of various trigger types.
+ */
+#define DNS_RPZ_CLIENT_IP_ZONE	DNS_RPZ_PREFIX"client-ip"
 #define DNS_RPZ_IP_ZONE		DNS_RPZ_PREFIX"ip"
 #define DNS_RPZ_NSIP_ZONE	DNS_RPZ_PREFIX"nsip"
 #define DNS_RPZ_NSDNAME_ZONE	DNS_RPZ_PREFIX"nsdname"
-#define DNS_RPZ_PASSTHRU_ZONE	DNS_RPZ_PREFIX"passthru"
+/*
+ * Special policies.
+ */
+#define DNS_RPZ_PASSTHRU_NAME	DNS_RPZ_PREFIX"passthru"
+#define DNS_RPZ_DROP_NAME	DNS_RPZ_PREFIX"drop"
+#define DNS_RPZ_TCP_ONLY_NAME	DNS_RPZ_PREFIX"tcp-only"
 
-typedef isc_uint8_t		dns_rpz_cidr_bits_t;
+
+typedef isc_uint8_t		dns_rpz_prefix_t;
 
 typedef enum {
 	DNS_RPZ_TYPE_BAD,
+	DNS_RPZ_TYPE_CLIENT_IP,
 	DNS_RPZ_TYPE_QNAME,
 	DNS_RPZ_TYPE_IP,
 	DNS_RPZ_TYPE_NSDNAME,
@@ -45,45 +57,151 @@
 } dns_rpz_type_t;
 
 /*
- * Require DNS_RPZ_POLICY_PASSTHRU < DNS_RPZ_POLICY_NXDOMAIN <
- * DNS_RPZ_POLICY_NODATA < DNS_RPZ_POLICY_CNAME to choose among competing
- * policies.
+ * Require DNS_RPZ_POLICY_PASSTHRU < DNS_RPZ_POLICY_DROP
+ * < DNS_RPZ_POLICY_TCP_ONLY DNS_RPZ_POLICY_NXDOMAIN < DNS_RPZ_POLICY_NODATA
+ * < DNS_RPZ_POLICY_CNAME to choose among competing policies.
  */
 typedef enum {
 	DNS_RPZ_POLICY_GIVEN = 0,	/* 'given': what policy record says */
-	DNS_RPZ_POLICY_DISABLED = 1,	/* 'cname x': answer with x's rrsets */
+	DNS_RPZ_POLICY_DISABLED = 1,	/* log what would have happened */
 	DNS_RPZ_POLICY_PASSTHRU = 2,	/* 'passthru': do not rewrite */
-	DNS_RPZ_POLICY_NXDOMAIN = 3,	/* 'nxdomain': answer with NXDOMAIN */
-	DNS_RPZ_POLICY_NODATA = 4,	/* 'nodata': answer with ANCOUNT=0 */
-	DNS_RPZ_POLICY_CNAME = 5,	/* 'cname x': answer with x's rrsets */
+	DNS_RPZ_POLICY_DROP = 3,	/* 'drop': do not respond */
+	DNS_RPZ_POLICY_TCP_ONLY = 4,	/* 'tcp-only': answer UDP with TC=1 */
+	DNS_RPZ_POLICY_NXDOMAIN = 5,	/* 'nxdomain': answer with NXDOMAIN */
+	DNS_RPZ_POLICY_NODATA = 6,	/* 'nodata': answer with ANCOUNT=0 */
+	DNS_RPZ_POLICY_CNAME = 7,	/* 'cname x': answer with x's rrsets */
 	DNS_RPZ_POLICY_RECORD,
 	DNS_RPZ_POLICY_WILDCNAME,
 	DNS_RPZ_POLICY_MISS,
 	DNS_RPZ_POLICY_ERROR
 } dns_rpz_policy_t;
 
+typedef isc_uint8_t	    dns_rpz_num_t;
+
+#define DNS_RPZ_MAX_ZONES   32
+#if DNS_RPZ_MAX_ZONES > 32
+# if DNS_RPZ_MAX_ZONES > 64
+#  error "rpz zone bit masks must fit in a word"
+# endif
+typedef isc_uint64_t	    dns_rpz_zbits_t;
+#else
+typedef isc_uint32_t	    dns_rpz_zbits_t;
+#endif
+
+#define DNS_RPZ_ALL_ZBITS   ((dns_rpz_zbits_t)-1)
+
+#define DNS_RPZ_INVALID_NUM DNS_RPZ_MAX_ZONES
+
+#define DNS_RPZ_ZBIT(n)	    (((dns_rpz_zbits_t)1) << (dns_rpz_num_t)(n))
+
 /*
- * Specify a response policy zone.
+ * Mask of the specified and higher numbered policy zones
+ * Avoid hassles with (1<<33) or (1<<65)
  */
-typedef struct dns_rpz_zone dns_rpz_zone_t;
+#define DNS_RPZ_ZMASK(n)    ((dns_rpz_zbits_t)((((n) >= DNS_RPZ_MAX_ZONES-1) ? \
+						0 : (1<<((n)+1))) -1))
 
+/*
+ * The number of triggers of each type in a response policy zone.
+ */
+typedef struct dns_rpz_triggers dns_rpz_triggers_t;
+struct dns_rpz_triggers {
+	int		client_ipv4;
+	int		client_ipv6;
+	int		qname;
+	int		ipv4;
+	int		ipv6;
+	int		nsdname;
+	int		nsipv4;
+	int		nsipv6;
+};
+/*
+ * A single response policy zone.
+ */
+typedef struct dns_rpz_zone dns_rpz_zone_t;
 struct dns_rpz_zone {
-	ISC_LINK(dns_rpz_zone_t) link;
-	int			 num;	  /* ordinal in list of policy zones */
-	dns_name_t		 origin;  /* Policy zone name */
-	dns_name_t		 nsdname; /* DNS_RPZ_NSDNAME_ZONE.origin */
-	dns_name_t		 passthru;/* DNS_RPZ_PASSTHRU_ZONE. */
-	dns_name_t		 cname;	  /* override value for ..._CNAME */
-	dns_ttl_t		 max_policy_ttl;
-	dns_rpz_policy_t	 policy;  /* DNS_RPZ_POLICY_GIVEN or override */
-	isc_boolean_t		 recursive_only;
-	isc_boolean_t		 defined;
+	isc_refcount_t	refs;
+	dns_rpz_num_t	num;		/* ordinal in list of policy zones */
+	dns_name_t	origin;		/* Policy zone name */
+	dns_name_t	client_ip;	/* DNS_RPZ_CLIENT_IP_ZONE.origin. */
+	dns_name_t	ip;		/* DNS_RPZ_IP_ZONE.origin. */
+	dns_name_t	nsdname;	/* DNS_RPZ_NSDNAME_ZONE.origin */
+	dns_name_t	nsip;		/* DNS_RPZ_NSIP_ZONE.origin. */
+	dns_name_t	passthru;	/* DNS_RPZ_PASSTHRU_NAME. */
+	dns_name_t	drop;		/* DNS_RPZ_DROP_NAME. */
+	dns_name_t	tcp_only;	/* DNS_RPZ_TCP_ONLY_NAME. */
+	dns_name_t	cname;		/* override value for ..._CNAME */
+	dns_ttl_t	max_policy_ttl;
+	dns_rpz_policy_t policy;	/* DNS_RPZ_POLICY_GIVEN or override */
 };
 
 /*
- * Radix trees for response policy IP addresses.
+ * Radix tree node for response policy IP addresses
+ */
+typedef struct dns_rpz_cidr_node dns_rpz_cidr_node_t;
+
+/*
+ * Response policy zones known to a view.
  */
-typedef struct dns_rpz_cidr	dns_rpz_cidr_t;
+typedef struct dns_rpz_zones dns_rpz_zones_t;
+struct dns_rpz_zones {
+	struct {
+		dns_rpz_zbits_t	    no_rd_ok;
+		isc_boolean_t	    break_dnssec;
+		isc_boolean_t	    qname_wait_recurse;
+		unsigned int	    min_ns_labels;
+		dns_rpz_num_t	    num_zones;
+	} p;
+	dns_rpz_zone_t		*zones[DNS_RPZ_MAX_ZONES];
+	dns_rpz_triggers_t	triggers[DNS_RPZ_MAX_ZONES];
+
+	dns_rpz_zbits_t		defined;
+
+	/*
+	 * The set of records for a policy zone are in one of these states:
+	 *	never loaded		    load_begun=0  have=0
+	 *	during initial loading	    load_begun=1  have=0
+	 *				and rbtdb->rpzsp == rbtdb->load_rpzsp
+	 *	after good load		    load_begun=1  have!=0
+	 *	after failed initial load   load_begun=1  have=0
+	 *				and rbtdb->load_rpzsp == NULL
+	 *	reloading after failure	    load_begun=1  have=0
+	 *	reloading after success
+	 *		main rpzs	    load_begun=1  have!=0
+	 *		load rpzs	    load_begun=1  have=0
+	 */
+	dns_rpz_zbits_t		load_begun;
+	struct {
+		dns_rpz_zbits_t	    client_ipv4;
+		dns_rpz_zbits_t	    client_ipv6;
+		dns_rpz_zbits_t	    client_ip;
+		dns_rpz_zbits_t	    qname;
+		dns_rpz_zbits_t	    ipv4;
+		dns_rpz_zbits_t	    ipv6;
+		dns_rpz_zbits_t	    ip;
+		dns_rpz_zbits_t	    nsdname;
+		dns_rpz_zbits_t	    nsipv4;
+		dns_rpz_zbits_t	    nsipv6;
+		dns_rpz_zbits_t	    nsip;
+		dns_rpz_zbits_t	    qname_skip_recurse;
+	} have;
+	dns_rpz_triggers_t	total_triggers;
+
+	isc_mem_t		*mctx;
+	isc_refcount_t		refs;
+	/*
+	 * One lock for short term read-only search that guarantees the
+	 * consistency of the pointers.
+	 * A second lock for maintenance that guarantees no other thread
+	 * is adding or deleting nodes.
+	 */
+	isc_mutex_t		search_lock;
+	isc_mutex_t		maint_lock;
+
+	dns_rpz_cidr_node_t	*cidr;
+	dns_rbt_t		*rbt;
+};
+
 
 /*
  * context for finding the best policy
@@ -91,22 +209,19 @@
 typedef struct {
 	unsigned int		state;
 # define DNS_RPZ_REWRITTEN	0x0001
-# define DNS_RPZ_DONE_QNAME	0x0002	/* qname checked */
-# define DNS_RPZ_DONE_QNAME_IP	0x0004	/* IP addresses of qname checked */
-# define DNS_RPZ_DONE_NSDNAME	0x0008	/* NS name missed; checking addresses */
-# define DNS_RPZ_DONE_IPv4 	0x0010
-# define DNS_RPZ_RECURSING	0x0020
-# define DNS_RPZ_HAVE_IP 	0x0040	/* a policy zone has IP addresses */
-# define DNS_RPZ_HAVE_NSIPv4	0x0080	/*		  IPv4 NISP addresses */
-# define DNS_RPZ_HAVE_NSIPv6	0x0100	/*		  IPv6 NISP addresses */
-# define DNS_RPZ_HAVE_NSDNAME	0x0200	/*		  NS names */
+# define DNS_RPZ_DONE_CLIENT_IP	0x0002	/* client IP address checked */
+# define DNS_RPZ_DONE_QNAME	0x0004	/* qname checked */
+# define DNS_RPZ_DONE_QNAME_IP	0x0008	/* IP addresses of qname checked */
+# define DNS_RPZ_DONE_NSDNAME	0x0010	/* NS name missed; checking addresses */
+# define DNS_RPZ_DONE_IPv4	0x0020
+# define DNS_RPZ_RECURSING	0x0040
 	/*
 	 * Best match so far.
 	 */
 	struct {
 		dns_rpz_type_t		type;
 		dns_rpz_zone_t		*rpz;
-		dns_rpz_cidr_bits_t	prefix;
+		dns_rpz_prefix_t	prefix;
 		dns_rpz_policy_t	policy;
 		dns_ttl_t		ttl;
 		isc_result_t		result;
@@ -141,10 +256,15 @@
 		dns_rdataset_t		*sigrdataset;
 		dns_rdatatype_t		qtype;
 	} q;
-	dns_name_t		*qname;
+	/*
+	 * p_name: current policy owner name
+	 * r_name: recursing for this name to possible policy triggers
+	 * f_name: saved found name from before recursion
+	 */
+	dns_name_t		*p_name;
 	dns_name_t		*r_name;
 	dns_name_t		*fname;
-	dns_fixedname_t		_qnamef;
+	dns_fixedname_t		_p_namef;
 	dns_fixedname_t		_r_namef;
 	dns_fixedname_t		_fnamef;
 } dns_rpz_st_t;
@@ -171,32 +291,41 @@
 const char *
 dns_rpz_policy2str(dns_rpz_policy_t policy);
 
-void
-dns_rpz_cidr_free(dns_rpz_cidr_t **cidr);
-
-void
-dns_rpz_view_destroy(dns_view_t *view);
+dns_rpz_policy_t
+dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset,
+		     dns_name_t *selfname);
 
 isc_result_t
-dns_rpz_new_cidr(isc_mem_t *mctx, dns_name_t *origin,
-		 dns_rpz_cidr_t **rbtdb_cidr);
-void
-dns_rpz_enabled_get(dns_rpz_cidr_t *cidr, dns_rpz_st_t *st);
+dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, isc_mem_t *mctx);
 
 void
-dns_rpz_cidr_deleteip(dns_rpz_cidr_t *cidr, dns_name_t *name);
+dns_rpz_attach_rpzs(dns_rpz_zones_t *source, dns_rpz_zones_t **target);
 
 void
-dns_rpz_cidr_addip(dns_rpz_cidr_t *cidr, dns_name_t *name);
+dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp);
 
 isc_result_t
-dns_rpz_cidr_find(dns_rpz_cidr_t *cidr, const isc_netaddr_t *netaddr,
-		  dns_rpz_type_t type, dns_name_t *canon_name,
-		  dns_name_t *search_name, dns_rpz_cidr_bits_t *prefix);
+dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp,
+		  dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num);
 
-dns_rpz_policy_t
-dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset,
-		     dns_name_t *selfname);
+isc_result_t
+dns_rpz_ready(dns_rpz_zones_t *rpzs,
+	      dns_rpz_zones_t **load_rpzsp, dns_rpz_num_t rpz_num);
+
+isc_result_t
+dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *name);
+
+void
+dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *name);
+
+dns_rpz_num_t
+dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+		dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr,
+		dns_name_t *ip_name, dns_rpz_prefix_t *prefixp);
+
+dns_rpz_zbits_t
+dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+		  dns_rpz_zbits_t zbits, dns_name_t *trig_name);
 
 ISC_LANG_ENDDECLS
 
diff -r -u lib/dns/include/dns/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
@@ -164,10 +164,7 @@
 	dns_acl_t *			v4_aaaa_acl;
 	dns_dns64list_t 		dns64;
 	unsigned int 			dns64cnt;
-	ISC_LIST(dns_rpz_zone_t)	rpz_zones;
-	isc_boolean_t			rpz_recursive_only;
-	isc_boolean_t			rpz_break_dnssec;
-	unsigned int			rpz_min_ns_labels;
+	dns_rpz_zones_t			*rpzs;
 
 	/*
 	 * Configurable data for server use only,
diff -r -u lib/dns/include/dns/zone.h-orig lib/dns/include/dns/zone.h
--- lib/dns/include/dns/zone.h-orig	2004-01-01 00:00:00.000000000 +0000
+++ lib/dns/include/dns/zone.h	2004-01-01 00:00:00.000000000 +0000
@@ -2081,19 +2081,20 @@
  */
 
 isc_result_t
-dns_zone_rpz_enable(dns_zone_t *zone);
+dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs,
+		    dns_rpz_num_t rpz_num);
 /*%
  * Set the response policy associated with a zone.
  */
 
-isc_result_t
+void
 dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db);
 /*%
  * If a zone is a response policy zone, mark its new database.
  */
 
-isc_boolean_t
-dns_zone_get_rpz(dns_zone_t *zone);
+dns_rpz_num_t
+dns_zone_get_rpz_num(dns_zone_t *zone);
 
 void
 dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level);
diff -r -u lib/dns/rbtdb.c-orig lib/dns/rbtdb.c
--- lib/dns/rbtdb.c-orig	2004-01-01 00:00:00.000000000 +0000
+++ lib/dns/rbtdb.c	2004-01-01 00:00:00.000000000 +0000
@@ -453,7 +453,9 @@
 	dns_rbt_t *                     tree;
 	dns_rbt_t *			nsec;
 	dns_rbt_t *			nsec3;
-	dns_rpz_cidr_t *		rpz_cidr;
+	dns_rpz_zones_t			*rpzs;
+	dns_rpz_num_t			rpz_num;
+	dns_rpz_zones_t			*load_rpzs;
 
 	/* Unlocked */
 	unsigned int                    quantum;
@@ -972,8 +974,18 @@
 		dns_stats_detach(&rbtdb->rrsetstats);
 
 #ifdef BIND9
-	if (rbtdb->rpz_cidr != NULL)
-		dns_rpz_cidr_free(&rbtdb->rpz_cidr);
+	if (rbtdb->load_rpzs != NULL) {
+		/*
+		 * We must be cleaning up after a failed zone loading.
+		 */
+		REQUIRE(rbtdb->rpzs != NULL &&
+			rbtdb->rpz_num < rbtdb->rpzs->p.num_zones);
+		dns_rpz_detach_rpzs(&rbtdb->load_rpzs);
+	}
+	if (rbtdb->rpzs != NULL) {
+		REQUIRE(rbtdb->rpz_num < rbtdb->rpzs->p.num_zones);
+		dns_rpz_detach_rpzs(&rbtdb->rpzs);
+	}
 #endif
 
 	isc_mem_put(rbtdb->common.mctx, rbtdb->node_locks,
@@ -1515,11 +1527,11 @@
 	switch (node->nsec) {
 	case DNS_RBT_NSEC_NORMAL:
 #ifdef BIND9
-		if (rbtdb->rpz_cidr != NULL) {
+		if (rbtdb->rpzs != NULL) {
 			dns_fixedname_init(&fname);
 			name = dns_fixedname_name(&fname);
 			dns_rbt_fullnamefromnode(node, name);
-			dns_rpz_cidr_deleteip(rbtdb->rpz_cidr, name);
+			dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name);
 		}
 #endif
 		result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE);
@@ -1555,11 +1567,11 @@
 					      isc_result_totext(result));
 			}
 		}
+		result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE);
 #ifdef BIND9
-		if (rbtdb->rpz_cidr != NULL)
-			dns_rpz_cidr_deleteip(rbtdb->rpz_cidr, name);
+		if (rbtdb->rpzs != NULL)
+			dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name);
 #endif
-		result = dns_rbt_deletenode(rbtdb->tree, node, ISC_FALSE);
 		break;
 	case DNS_RBT_NSEC_NSEC:
 		result = dns_rbt_deletenode(rbtdb->nsec, node, ISC_FALSE);
@@ -1573,7 +1585,7 @@
 			      DNS_LOGCATEGORY_DATABASE,
 			      DNS_LOGMODULE_CACHE,
 			      ISC_LOG_WARNING,
-			      "delete_cnode(): "
+			      "delete_node(): "
 			      "dns_rbt_deletenode: %s",
 			      isc_result_totext(result));
 	}
@@ -2540,14 +2552,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);
@@ -4549,228 +4562,45 @@
 	return (result);
 }
 
+#ifdef BIND9
 /*
- * Mark a database for response policy rewriting
- * or find which RPZ data is available.
+ * Connect this RBTDB to the response policy zone summary data for the view.
  */
-#ifdef BIND9
-static isc_result_t
-rpz_enabled(dns_db_t *db, dns_rpz_st_t *st)
-{
-	dns_rbtdb_t *rbtdb;
-	isc_result_t result;
+static void
+rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
+	dns_rbtdb_t * rbtdb;
 
-	result = ISC_R_SUCCESS;
 	rbtdb = (dns_rbtdb_t *)db;
 	REQUIRE(VALID_RBTDB(rbtdb));
-	RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
-	if (st != NULL) {
-		dns_rpz_enabled_get(rbtdb->rpz_cidr, st);
-	} else {
-		result = dns_rpz_new_cidr(rbtdb->common.mctx,
-					  &rbtdb->common.origin,
-					  &rbtdb->rpz_cidr);
-	}
-	RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
-	return (result);
+
+	RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
+	REQUIRE(rbtdb->rpzs == NULL && rbtdb->rpz_num == DNS_RPZ_INVALID_NUM);
+	dns_rpz_attach_rpzs(rpzs, &rbtdb->rpzs);
+	rbtdb->rpz_num = rpz_num;
+	RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write);
 }
 
 /*
- * Search the CDIR block tree of a response policy tree of trees for all of
- * the IP addresses in an A or AAAA rdataset.
- * Among the policies for all IPv4 and IPv6 addresses for a name, choose
- *	the earliest configured policy,
- *	QNAME over IP over NSDNAME over NSIP,
- *	the longest prefix,
- *	the lexically smallest address.
- * The caller must have already checked that any existing policy was not
- * configured earlier than this policy zone and does not have a higher
- * precedence type.
+ * Enable this RBTDB as a response policy zone.
  */
-static void
-rpz_findips(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
-	    dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
-	    dns_rdataset_t *ardataset, dns_rpz_st_t *st,
-	    dns_name_t *query_qname)
-{
-	dns_rbtdb_t *rbtdb;
-	struct in_addr ina;
-	struct in6_addr in6a;
-	isc_netaddr_t netaddr;
-	dns_fixedname_t selfnamef, qnamef;
-	dns_name_t *selfname, *qname;
-	dns_rbtnode_t *node;
-	dns_rdataset_t zrdataset;
-	dns_rpz_cidr_bits_t prefix;
+static isc_result_t
+rpz_ready(dns_db_t *db) {
+	dns_rbtdb_t * rbtdb;
 	isc_result_t result;
-	dns_rpz_policy_t rpz_policy;
-	dns_ttl_t ttl;
 
 	rbtdb = (dns_rbtdb_t *)db;
 	REQUIRE(VALID_RBTDB(rbtdb));
-	RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
-
-	if (rbtdb->rpz_cidr == NULL) {
-		RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read);
-		return;
-	}
-
-	dns_fixedname_init(&selfnamef);
-	dns_fixedname_init(&qnamef);
-	selfname = dns_fixedname_name(&selfnamef);
-	qname = dns_fixedname_name(&qnamef);
-
-	for (result = dns_rdataset_first(ardataset);
-	     result == ISC_R_SUCCESS;
-	     result = dns_rdataset_next(ardataset)) {
-		dns_rdata_t rdata = DNS_RDATA_INIT;
-		dns_rdataset_current(ardataset, &rdata);
-		switch (rdata.type) {
-		case dns_rdatatype_a:
-			INSIST(rdata.length == 4);
-			memmove(&ina.s_addr, rdata.data, 4);
-			isc_netaddr_fromin(&netaddr, &ina);
-			break;
-		case dns_rdatatype_aaaa:
-			INSIST(rdata.length == 16);
-			memmove(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
 
@@ -6938,8 +6768,9 @@
 
  done:
 #ifdef BIND9
-	if (noderesult == ISC_R_SUCCESS && rbtdb->rpz_cidr != NULL)
-		dns_rpz_cidr_addip(rbtdb->rpz_cidr, name);
+	if (rbtdb->rpzs != NULL && noderesult == ISC_R_SUCCESS)
+		noderesult = dns_rpz_add(rbtdb->load_rpzs, rbtdb->rpz_num,
+					 name);
 #endif
 	if (noderesult == ISC_R_SUCCESS || noderesult == ISC_R_EXISTS)
 		*nodep = node;
@@ -7074,6 +6905,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;
@@ -7476,8 +7321,8 @@
 	isdnssec,
 	NULL,
 #ifdef BIND9
-	rpz_enabled,
-	rpz_findips,
+	rpz_attach,
+	rpz_ready,
 #else
 	NULL,
 	NULL,
@@ -7791,6 +7636,9 @@
 	}
 	rbtdb->attributes = 0;
 	rbtdb->task = NULL;
+	rbtdb->rpzs = NULL;
+	rbtdb->load_rpzs = NULL;
+	rbtdb->rpz_num = DNS_RPZ_INVALID_NUM;
 
 	/*
 	 * Version Initialization.
diff -r -u lib/dns/rpz.c-orig lib/dns/rpz.c
--- lib/dns/rpz.c-orig	2004-01-01 00:00:00.000000000 +0000
+++ lib/dns/rpz.c	2004-01-01 00:00:00.000000000 +0000
@@ -37,6 +37,7 @@
 #include <dns/rdataset.h>
 #include <dns/rdatastruct.h>
 #include <dns/result.h>
+#include <dns/rbt.h>
 #include <dns/rpz.h>
 #include <dns/view.h>
 
@@ -44,9 +45,13 @@
 /*
  * Parallel radix trees for databases of response policy IP addresses
  *
- * The radix or Patricia trees are somewhat specialized to handle response
- * policy addresses by representing the two test of IP IP addresses and name
- * server IP addresses in a single tree.
+ * The radix or patricia trees are somewhat specialized to handle response
+ * policy addresses by representing the two sets of IP addresses and name
+ * server IP addresses in a single tree.  One set of IP addresses is
+ * for rpz-ip policies or policies triggered by addresses in A or
+ * AAAA records in responses.
+ * The second set is for rpz-nsip policies or policies triggered by addresses
+ * in A or AAAA records for NS records that are authorities for responses.
  *
  * Each leaf indicates that an IP address is listed in the IP address or the
  * name server IP address policy sub-zone (or both) of the corresponding
@@ -55,7 +60,8 @@
  * tree, the node in the policy zone's database is found by converting
  * the IP address to a domain name in a canonical form.
  *
- * The response policy zone canonical form of IPv6 addresses is one of:
+ *
+ * The response policy zone canonical form of an IPv6 address is one of:
  *	prefix.W.W.W.W.W.W.W.W
  *	prefix.WORDS.zz
  *	prefix.WORDS.zz.WORDS
@@ -72,7 +78,7 @@
  *	prefix	is the prefix length of the address between 1 and 32
  *	B	is a number between 0 and 255
  *
- * IPv4 addresses are distinguished from IPv6 addresses by having
+ * Names for IPv4 addresses are distinguished from IPv6 addresses by having
  * 5 labels all of which are numbers, and a prefix between 1 and 32.
  */
 
@@ -90,43 +96,89 @@
 } dns_rpz_cidr_key_t;
 
 #define ADDR_V4MAPPED		0xffff
+#define KEY_IS_IPV4(prefix,ip) ((prefix) >= 96 && (ip)->w[0] == 0 &&	\
+				(ip)->w[1] == 0 && (ip)->w[2] == ADDR_V4MAPPED)
+
+#define DNS_RPZ_WORD_MASK(b) ((b) == 0 ? (dns_rpz_cidr_word_t)(-1)	\
+			      : ((dns_rpz_cidr_word_t)(-1)		\
+				 << (DNS_RPZ_CIDR_WORD_BITS - (b))))
+
+/*
+ * Get bit #n from the array of words of an IP address.
+ */
+#define DNS_RPZ_IP_BIT(ip, n) (1 & ((ip)->w[(n)/DNS_RPZ_CIDR_WORD_BITS] >>  \
+				    (DNS_RPZ_CIDR_WORD_BITS		    \
+				     - 1 - ((n) % DNS_RPZ_CIDR_WORD_BITS))))
 
-#define DNS_RPZ_WORD_MASK(b)				\
-	((b) == 0 ? (dns_rpz_cidr_word_t)(-1)		\
-		  : ((dns_rpz_cidr_word_t)(-1)		\
-		    << (DNS_RPZ_CIDR_WORD_BITS - (b))))
-
-#define DNS_RPZ_IP_BIT(ip, bitno) \
-	(1 & ((ip)->w[(bitno)/DNS_RPZ_CIDR_WORD_BITS] >> \
-	    (DNS_RPZ_CIDR_WORD_BITS - 1 - ((bitno) % DNS_RPZ_CIDR_WORD_BITS))))
+/*
+ * A triplet of arrays of bits flagging the existence of
+ * client-IP, IP, and NSIP policy triggers.
+ */
+typedef struct dns_rpz_addr_zbits dns_rpz_addr_zbits_t;
+struct dns_rpz_addr_zbits {
+	dns_rpz_zbits_t		client_ip;
+	dns_rpz_zbits_t		ip;
+	dns_rpz_zbits_t		nsip;
+};
 
-typedef struct dns_rpz_cidr_node	dns_rpz_cidr_node_t;
-typedef isc_uint8_t			dns_rpz_cidr_flags_t;
+/*
+ * A CIDR or radix tree node.
+ */
 struct dns_rpz_cidr_node {
-	dns_rpz_cidr_node_t		*parent;
-	dns_rpz_cidr_node_t		*child[2];
-	dns_rpz_cidr_key_t		ip;
-	dns_rpz_cidr_bits_t		bits;
-	dns_rpz_cidr_flags_t		flags;
-#define	DNS_RPZ_CIDR_FG_IP	 0x01	/* has IP data or is parent of IP */
-#define	DNS_RPZ_CIDR_FG_IP_DATA	 0x02	/* has IP data */
-#define	DNS_RPZ_CIDR_FG_NSIPv4	 0x04	/* has or is parent of NSIPv4 data */
-#define	DNS_RPZ_CIDR_FG_NSIPv6	 0x08	/* has or is parent of NSIPv6 data */
-#define	DNS_RPZ_CIDR_FG_NSIP_DATA 0x10	/* has NSIP data */
+	dns_rpz_cidr_node_t	*parent;
+	dns_rpz_cidr_node_t	*child[2];
+	dns_rpz_cidr_key_t	ip;
+	dns_rpz_prefix_t	prefix;
+	dns_rpz_addr_zbits_t	set;
+	dns_rpz_addr_zbits_t	sum;
+};
+
+/*
+ * The data in a RBT node has two pairs of bits for policy zones.
+ * One pair is for the corresponding name of the node such as example.com
+ * and the other pair is for a wildcard child such as *.example.com.
+ */
+/*
+ * A pair of arrays of bits flagging the existence of
+ * QNAME and NSDNAME policy triggers.
+ */
+typedef struct dns_rpz_nm_zbits dns_rpz_nm_zbits_t;
+struct dns_rpz_nm_zbits {
+	dns_rpz_zbits_t		qname;
+	dns_rpz_zbits_t		ns;
 };
 
-struct dns_rpz_cidr {
-	isc_mem_t		*mctx;
-	isc_boolean_t		have_nsdname;	/* zone has NSDNAME record */
-	dns_rpz_cidr_node_t	*root;
-	dns_name_t		ip_name;	/* RPZ_IP_ZONE.origin. */
-	dns_name_t		nsip_name;      /* RPZ_NSIP_ZONE.origin. */
-	dns_name_t		nsdname_name;	/* RPZ_NSDNAME_ZONE.origin */
+typedef struct dns_rpz_nm_data dns_rpz_nm_data_t;
+struct dns_rpz_nm_data {
+	dns_rpz_nm_zbits_t	set;
+	dns_rpz_nm_zbits_t	wild;
 };
 
+#if 0
+/*
+ * Catch a name while debugging.
+ */
+static void
+catch_name(const dns_name_t *src_name, const char *tgt, const char *str) {
+	dns_fixedname_t tgt_namef;
+	dns_name_t *tgt_name;
+
+	dns_fixedname_init(&tgt_namef);
+	tgt_name = dns_fixedname_name(&tgt_namef);
+	dns_name_fromstring(tgt_name, tgt, DNS_NAME_DOWNCASE, NULL);
+	if (dns_name_equal(src_name, tgt_name)) {
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+			      "rpz hit failed: %s %s", str, tgt);
+	}
+}
+#endif
+
 const char *
 dns_rpz_type2str(dns_rpz_type_t type) {
 	switch (type) {
+	case DNS_RPZ_TYPE_CLIENT_IP:
+		return ("CLIENT-IP");
 	case DNS_RPZ_TYPE_QNAME:
 		return ("QNAME");
 	case DNS_RPZ_TYPE_IP:
@@ -138,32 +190,34 @@
 	case DNS_RPZ_TYPE_BAD:
 		break;
 	}
-	FATAL_ERROR(__FILE__, __LINE__,
-		    "impossible rpz type %d", type);
+	FATAL_ERROR(__FILE__, __LINE__, "impossible rpz type %d", type);
 	return ("impossible");
 }
 
 dns_rpz_policy_t
 dns_rpz_str2policy(const char *str) {
+	static struct {
+		const char *str;
+		dns_rpz_policy_t policy;
+	} tbl[] = {
+		{"given",	DNS_RPZ_POLICY_GIVEN},
+		{"disabled",	DNS_RPZ_POLICY_DISABLED},
+		{"passthru",	DNS_RPZ_POLICY_PASSTHRU},
+		{"drop",	DNS_RPZ_POLICY_DROP},
+		{"tcp-only",	DNS_RPZ_POLICY_TCP_ONLY},
+		{"nxdomain",	DNS_RPZ_POLICY_NXDOMAIN},
+		{"nodata",	DNS_RPZ_POLICY_NODATA},
+		{"cname",	DNS_RPZ_POLICY_CNAME},
+		{"no-op",	DNS_RPZ_POLICY_PASSTHRU},   /* old passthru */
+	};
+	unsigned int n;
+
 	if (str == NULL)
 		return (DNS_RPZ_POLICY_ERROR);
-	if (!strcasecmp(str, "given"))
-		return (DNS_RPZ_POLICY_GIVEN);
-	if (!strcasecmp(str, "disabled"))
-		return (DNS_RPZ_POLICY_DISABLED);
-	if (!strcasecmp(str, "passthru"))
-		return (DNS_RPZ_POLICY_PASSTHRU);
-	if (!strcasecmp(str, "nxdomain"))
-		return (DNS_RPZ_POLICY_NXDOMAIN);
-	if (!strcasecmp(str, "nodata"))
-		return (DNS_RPZ_POLICY_NODATA);
-	if (!strcasecmp(str, "cname"))
-		return (DNS_RPZ_POLICY_CNAME);
-	/*
-	 * Obsolete
-	 */
-	if (!strcasecmp(str, "no-op"))
-		return (DNS_RPZ_POLICY_PASSTHRU);
+	for (n = 0; n < sizeof(tbl)/sizeof(tbl[0]); ++n) {
+		if (!strcasecmp(tbl[n].str, str))
+			return (tbl[n].policy);
+	}
 	return (DNS_RPZ_POLICY_ERROR);
 }
 
@@ -175,6 +229,12 @@
 	case DNS_RPZ_POLICY_PASSTHRU:
 		str = "PASSTHRU";
 		break;
+	case DNS_RPZ_POLICY_DROP:
+		str = "DROP";
+		break;
+	case DNS_RPZ_POLICY_TCP_ONLY:
+		str = "TCP-ONLY";
+		break;
 	case DNS_RPZ_POLICY_NXDOMAIN:
 		str = "NXDOMAIN";
 		break;
@@ -196,243 +256,274 @@
 	return (str);
 }
 
-/*
- * Free the radix tree of a response policy database.
- */
-void
-dns_rpz_cidr_free(dns_rpz_cidr_t **cidrp) {
-	dns_rpz_cidr_node_t *cur, *child, *parent;
-	dns_rpz_cidr_t *cidr;
-
-	REQUIRE(cidrp != NULL);
-
-	cidr = *cidrp;
-	if (cidr == NULL)
-		return;
-
-	cur = cidr->root;
-	while (cur != NULL) {
-		/* Depth first. */
-		child = cur->child[0];
-		if (child != NULL) {
-			cur = child;
-			continue;
-		}
-		child = cur->child[1];
-		if (child != NULL) {
-			cur = child;
-			continue;
-		}
+static int
+zbit_to_num(dns_rpz_zbits_t zbit) {
+	dns_rpz_num_t rpz_num;
 
-		/* Delete this leaf and go up. */
-		parent = cur->parent;
-		if (parent == NULL)
-			cidr->root = NULL;
-		else
-			parent->child[parent->child[1] == cur] = NULL;
-		isc_mem_put(cidr->mctx, cur, sizeof(*cur));
-		cur = parent;
+	INSIST(zbit != 0);
+	rpz_num = 0;
+#if DNS_RPZ_MAX_ZONES > 32
+	if ((zbit & 0xffffffff00000000L) != 0) {
+		zbit >>= 32;
+		rpz_num += 32;
 	}
-
-	dns_name_free(&cidr->ip_name, cidr->mctx);
-	dns_name_free(&cidr->nsip_name, cidr->mctx);
-	dns_name_free(&cidr->nsdname_name, cidr->mctx);
-	isc_mem_put(cidr->mctx, cidr, sizeof(*cidr));
-	*cidrp = NULL;
+#endif
+	if ((zbit & 0xffff0000) != 0) {
+		zbit >>= 16;
+		rpz_num += 16;
+	}
+	if ((zbit & 0xff00) != 0) {
+		zbit >>= 8;
+		rpz_num += 8;
+	}
+	if ((zbit & 0xf0) != 0) {
+		zbit >>= 4;
+		rpz_num += 4;
+	}
+	if ((zbit & 0xc) != 0) {
+		zbit >>= 2;
+		rpz_num += 2;
+	}
+	if ((zbit & 2) != 0)
+		++rpz_num;
+	return (rpz_num);
 }
 
 /*
- * Forget a view's list of policy zones.
+ * Make a set of bit masks given one or more bits and their type.
  */
-void
-dns_rpz_view_destroy(dns_view_t *view) {
-	dns_rpz_zone_t *zone;
-
-	REQUIRE(view != NULL);
+static void
+make_addr_set(dns_rpz_addr_zbits_t *tgt_set, dns_rpz_zbits_t zbits,
+	      dns_rpz_type_t type)
+{
+	switch (type) {
+	case DNS_RPZ_TYPE_CLIENT_IP:
+		tgt_set->client_ip = zbits;
+		tgt_set->ip = 0;
+		tgt_set->nsip = 0;
+		break;
+	case DNS_RPZ_TYPE_IP:
+		tgt_set->client_ip = 0;
+		tgt_set->ip = zbits;
+		tgt_set->nsip = 0;
+		break;
+	case DNS_RPZ_TYPE_NSIP:
+		tgt_set->client_ip = 0;
+		tgt_set->ip = 0;
+		tgt_set->nsip = zbits;
+		break;
+	default:
+		INSIST(0);
+		break;
+	}
+}
 
-	while (!ISC_LIST_EMPTY(view->rpz_zones)) {
-		zone = ISC_LIST_HEAD(view->rpz_zones);
-		ISC_LIST_UNLINK(view->rpz_zones, zone, link);
-		if (dns_name_dynamic(&zone->origin))
-			dns_name_free(&zone->origin, view->mctx);
-		if (dns_name_dynamic(&zone->passthru))
-			dns_name_free(&zone->passthru, view->mctx);
-		if (dns_name_dynamic(&zone->nsdname))
-			dns_name_free(&zone->nsdname, view->mctx);
-		if (dns_name_dynamic(&zone->cname))
-			dns_name_free(&zone->cname, view->mctx);
-		isc_mem_put(view->mctx, zone, sizeof(*zone));
+static void
+make_nm_set(dns_rpz_nm_zbits_t *tgt_set,
+	    dns_rpz_num_t rpz_num, dns_rpz_type_t type)
+{
+	switch (type) {
+	case DNS_RPZ_TYPE_QNAME:
+		tgt_set->qname = DNS_RPZ_ZBIT(rpz_num);
+		tgt_set->ns = 0;
+		break;
+	case DNS_RPZ_TYPE_NSDNAME:
+		tgt_set->qname = 0;
+		tgt_set->ns = DNS_RPZ_ZBIT(rpz_num);
+		break;
+	default:
+		INSIST(0);
+		break;
 	}
 }
 
 /*
- * Start a new radix tree for a response policy zone.
+ * Mark a node and all of its parents as having client-IP, IP, or NSIP data
  */
-isc_result_t
-dns_rpz_new_cidr(isc_mem_t *mctx, dns_name_t *origin,
-		 dns_rpz_cidr_t **rbtdb_cidr)
-{
-	isc_result_t result;
-	dns_rpz_cidr_t *cidr;
-
-	REQUIRE(rbtdb_cidr != NULL && *rbtdb_cidr == NULL);
-
-	cidr = isc_mem_get(mctx, sizeof(*cidr));
-	if (cidr == NULL)
-		return (ISC_R_NOMEMORY);
-	memset(cidr, 0, sizeof(*cidr));
-	cidr->mctx = mctx;
+static void
+set_sum_pair(dns_rpz_cidr_node_t *cnode) {
+	dns_rpz_cidr_node_t *child;
+	dns_rpz_addr_zbits_t sum;
 
-	dns_name_init(&cidr->ip_name, NULL);
-	result = dns_name_fromstring2(&cidr->ip_name, DNS_RPZ_IP_ZONE, origin,
-				      DNS_NAME_DOWNCASE, mctx);
-	if (result != ISC_R_SUCCESS) {
-		isc_mem_put(mctx, cidr, sizeof(*cidr));
-		return (result);
-	}
+	do {
+		sum = cnode->set;
 
-	dns_name_init(&cidr->nsip_name, NULL);
-	result = dns_name_fromstring2(&cidr->nsip_name, DNS_RPZ_NSIP_ZONE,
-				      origin, DNS_NAME_DOWNCASE, mctx);
-	if (result != ISC_R_SUCCESS) {
-		dns_name_free(&cidr->ip_name, mctx);
-		isc_mem_put(mctx, cidr, sizeof(*cidr));
-		return (result);
-	}
+		child = cnode->child[0];
+		if (child != NULL) {
+			sum.client_ip |= child->sum.client_ip;
+			sum.ip |= child->sum.ip;
+			sum.nsip |= child->sum.nsip;
+		}
 
-	dns_name_init(&cidr->nsdname_name, NULL);
-	result = dns_name_fromstring2(&cidr->nsdname_name, DNS_RPZ_NSDNAME_ZONE,
-				      origin, DNS_NAME_DOWNCASE, mctx);
-	if (result != ISC_R_SUCCESS) {
-		dns_name_free(&cidr->nsip_name, mctx);
-		dns_name_free(&cidr->ip_name, mctx);
-		isc_mem_put(mctx, cidr, sizeof(*cidr));
-		return (result);
-	}
+		child = cnode->child[1];
+		if (child != NULL) {
+			sum.client_ip |= child->sum.client_ip;
+			sum.ip |= child->sum.ip;
+			sum.nsip |= child->sum.nsip;
+		}
 
-	*rbtdb_cidr = cidr;
-	return (ISC_R_SUCCESS);
+		if (cnode->sum.client_ip == sum.client_ip &&
+		    cnode->sum.ip == sum.ip &&
+		    cnode->sum.nsip == sum.nsip)
+			break;
+		cnode->sum = sum;
+		cnode = cnode->parent;
+	} while (cnode != NULL);
 }
 
-/*
- * See if a policy zone has IP, NSIP, or NSDNAME rules or records.
- */
-void
-dns_rpz_enabled_get(dns_rpz_cidr_t *cidr, dns_rpz_st_t *st) {
-	if (cidr == NULL)
-		return;
-	if (cidr->root != NULL &&
-	    (cidr->root->flags & DNS_RPZ_CIDR_FG_IP) != 0)
-		st->state |= DNS_RPZ_HAVE_IP;
-	if (cidr->root != NULL &&
-	    (cidr->root->flags & DNS_RPZ_CIDR_FG_NSIPv4) != 0)
-		st->state |= DNS_RPZ_HAVE_NSIPv4;
-	if (cidr->root != NULL &&
-	    (cidr->root->flags & DNS_RPZ_CIDR_FG_NSIPv6) != 0)
-		st->state |= DNS_RPZ_HAVE_NSIPv6;
-	if (cidr->have_nsdname)
-		st->state |= DNS_RPZ_HAVE_NSDNAME;
-}
-
-static inline dns_rpz_cidr_flags_t
-get_flags(const dns_rpz_cidr_key_t *ip, dns_rpz_cidr_bits_t prefix,
-	dns_rpz_type_t rpz_type)
-{
-	if (rpz_type == DNS_RPZ_TYPE_NSIP) {
-		if (prefix >= 96 &&
-		    ip->w[0] == 0 && ip->w[1] == 0 &&
-		    ip->w[2] == ADDR_V4MAPPED)
-			return (DNS_RPZ_CIDR_FG_NSIP_DATA |
-				DNS_RPZ_CIDR_FG_NSIPv4);
-		else
-			return (DNS_RPZ_CIDR_FG_NSIP_DATA |
-				DNS_RPZ_CIDR_FG_NSIPv6);
+static void
+fix_qname_skip_recurse(dns_rpz_zones_t *rpzs) {
+	dns_rpz_zbits_t zbits;
+
+	/*
+	 * Get a mask covering all policy zones that are not subordinate to
+	 * other policy zones containing triggers that require that the
+	 * qname be resolved before they can be checked.
+	 */
+	if (rpzs->p.qname_wait_recurse) {
+		zbits = 0;
 	} else {
-		return (DNS_RPZ_CIDR_FG_IP | DNS_RPZ_CIDR_FG_IP_DATA);
+		zbits = (rpzs->have.ipv4 || rpzs->have.ipv6 ||
+			 rpzs->have.nsdname ||
+			 rpzs->have.nsipv4 || rpzs->have.nsipv6);
+		if (zbits == 0) {
+			zbits = DNS_RPZ_ALL_ZBITS;
+		} else {
+			zbits = DNS_RPZ_ZMASK(zbit_to_num(zbits));
+		}
 	}
+	rpzs->have.qname_skip_recurse = zbits;
+
+	rpzs->have.client_ip = rpzs->have.client_ipv4 | rpzs->have.client_ipv6;
+	rpzs->have.ip = rpzs->have.ipv4 | rpzs->have.ipv6;
+	rpzs->have.nsip = rpzs->have.nsipv4 | rpzs->have.nsipv6;
 }
 
-/*
- * Mark a node as having IP or NSIP data and all of its parents
- * as members of the IP or NSIP tree.
- */
 static void
-set_node_flags(dns_rpz_cidr_node_t *node, dns_rpz_type_t rpz_type) {
-	dns_rpz_cidr_flags_t flags;
+adj_trigger_cnt(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+		dns_rpz_type_t rpz_type,
+		const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix,
+		isc_boolean_t inc)
+{
+	int *cnt;
+	dns_rpz_zbits_t *have;
 
-	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;
+	switch (rpz_type) {
+	case DNS_RPZ_TYPE_CLIENT_IP:
+		REQUIRE(tgt_ip != NULL);
+		if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+			cnt = &rpzs->triggers[rpz_num].client_ipv4;
+			have = &rpzs->have.client_ipv4;
+		} else {
+			cnt = &rpzs->triggers[rpz_num].client_ipv6;
+			have = &rpzs->have.client_ipv6;
+		}
+		break;
+	case DNS_RPZ_TYPE_QNAME:
+		cnt = &rpzs->triggers[rpz_num].qname;
+		have = &rpzs->have.qname;
+		break;
+	case DNS_RPZ_TYPE_IP:
+		REQUIRE(tgt_ip != NULL);
+		if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+			cnt = &rpzs->triggers[rpz_num].ipv4;
+			have = &rpzs->have.ipv4;
+		} else {
+			cnt = &rpzs->triggers[rpz_num].ipv6;
+			have = &rpzs->have.ipv6;
+		}
+		break;
+	case DNS_RPZ_TYPE_NSDNAME:
+		cnt = &rpzs->triggers[rpz_num].nsdname;
+		have = &rpzs->have.nsdname;
+		break;
+	case DNS_RPZ_TYPE_NSIP:
+		REQUIRE(tgt_ip != NULL);
+		if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
+			cnt = &rpzs->triggers[rpz_num].nsipv4;
+			have = &rpzs->have.nsipv4;
+		} else {
+			cnt = &rpzs->triggers[rpz_num].nsipv6;
+			have = &rpzs->have.nsipv6;
+		}
+		break;
+	default:
+		INSIST(0);
+	}
+
+	if (inc) {
+		if (++*cnt == 1) {
+			*have |= DNS_RPZ_ZBIT(rpz_num);
+			fix_qname_skip_recurse(rpzs);
+		}
+	} else {
+		REQUIRE(*cnt > 0);
+		if (--*cnt == 0) {
+			*have &= ~DNS_RPZ_ZBIT(rpz_num);
+			fix_qname_skip_recurse(rpzs);
+		}
 	}
 }
 
-/*
- * Make a radix tree node.
- */
 static dns_rpz_cidr_node_t *
-new_node(dns_rpz_cidr_t *cidr, const dns_rpz_cidr_key_t *ip,
-	 dns_rpz_cidr_bits_t bits, dns_rpz_cidr_flags_t flags)
+new_node(dns_rpz_zones_t *rpzs,
+	 const dns_rpz_cidr_key_t *ip, dns_rpz_prefix_t prefix,
+	 const dns_rpz_cidr_node_t *child)
 {
-	dns_rpz_cidr_node_t *node;
+	dns_rpz_cidr_node_t *new;
 	int i, words, wlen;
 
-	node = isc_mem_get(cidr->mctx, sizeof(*node));
-	if (node == NULL)
+	new = isc_mem_get(rpzs->mctx, sizeof(*new));
+	if (new == NULL)
 		return (NULL);
-	memset(node, 0, sizeof(*node));
+	memset(new, 0, sizeof(*new));
 
-	node->flags = flags & ~(DNS_RPZ_CIDR_FG_IP_DATA |
-				DNS_RPZ_CIDR_FG_NSIP_DATA);
+	if (child != NULL)
+		new->sum = child->sum;
 
-	node->bits = bits;
-	words = bits / DNS_RPZ_CIDR_WORD_BITS;
-	wlen = bits % DNS_RPZ_CIDR_WORD_BITS;
+	new->prefix = prefix;
+	words = prefix / DNS_RPZ_CIDR_WORD_BITS;
+	wlen = prefix % DNS_RPZ_CIDR_WORD_BITS;
 	i = 0;
 	while (i < words) {
-		node->ip.w[i] = ip->w[i];
+		new->ip.w[i] = ip->w[i];
 		++i;
 	}
 	if (wlen != 0) {
-		node->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen);
+		new->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen);
 		++i;
 	}
 	while (i < DNS_RPZ_CIDR_WORDS)
-		node->ip.w[i++] = 0;
+		new->ip.w[i++] = 0;
 
-	return (node);
+	return (new);
 }
 
 static void
 badname(int level, dns_name_t *name, const char *str1, const char *str2) {
-	char printname[DNS_NAME_FORMATSIZE];
+	char namebuf[DNS_NAME_FORMATSIZE];
 
 	/*
 	 * bin/tests/system/rpz/tests.sh looks for "invalid rpz".
 	 */
-	if (level < DNS_RPZ_DEBUG_QUIET
-	    && isc_log_wouldlog(dns_lctx, level)) {
-		dns_name_format(name, printname, sizeof(printname));
+	if (level < DNS_RPZ_DEBUG_QUIET &&
+	    isc_log_wouldlog(dns_lctx, level)) {
+		dns_name_format(name, namebuf, sizeof(namebuf));
 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
 			      DNS_LOGMODULE_RBTDB, level,
 			      "invalid rpz IP address \"%s\"%s%s",
-			      printname, str1, str2);
+			      namebuf, str1, str2);
 	}
 }
 
 /*
  * Convert an IP address from radix tree binary (host byte order) to
- * to its canonical response policy domain name and its name in the
+ * to its canonical response policy domain name without the origin of the
  * policy zone.
  */
 static isc_result_t
-ip2name(dns_rpz_cidr_t *cidr, const dns_rpz_cidr_key_t *tgt_ip,
-	dns_rpz_cidr_bits_t tgt_prefix, dns_rpz_type_t type,
-	dns_name_t *canon_name, dns_name_t *search_name)
+ip2name(const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix,
+	dns_name_t *base_name, dns_name_t *ip_name)
 {
 #ifndef INET6_ADDRSTRLEN
 #define INET6_ADDRSTRLEN 46
@@ -440,22 +531,18 @@
 	int w[DNS_RPZ_CIDR_WORDS*2];
 	char str[1+8+1+INET6_ADDRSTRLEN+1];
 	isc_buffer_t buffer;
-	dns_name_t *name;
 	isc_result_t result;
 	isc_boolean_t zeros;
 	int i, n, len;
 
-	if (tgt_prefix > 96 &&
-	    tgt_ip->w[0] == 0 &&
-	    tgt_ip->w[1] == 0 &&
-	    tgt_ip->w[2] == ADDR_V4MAPPED) {
+	if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) {
 		len = snprintf(str, sizeof(str), "%d.%d.%d.%d.%d",
 			       tgt_prefix - 96,
 			       tgt_ip->w[3] & 0xff,
 			       (tgt_ip->w[3]>>8) & 0xff,
 			       (tgt_ip->w[3]>>16) & 0xff,
 			       (tgt_ip->w[3]>>24) & 0xff);
-		if (len == -1 || len > (int)sizeof(str))
+		if (len < 0 || len > (int)sizeof(str))
 			return (ISC_R_FAILURE);
 	} else {
 		for (i = 0; i < DNS_RPZ_CIDR_WORDS; i++) {
@@ -469,9 +556,9 @@
 			return (ISC_R_FAILURE);
 		i = 0;
 		while (i < DNS_RPZ_CIDR_WORDS * 2) {
-			if (w[i] != 0 || zeros
-			    || i >= DNS_RPZ_CIDR_WORDS * 2 - 1
-			    || w[i+1] != 0) {
+			if (w[i] != 0 || zeros ||
+			    i >= DNS_RPZ_CIDR_WORDS * 2 - 1 ||
+			    w[i+1] != 0) {
 				INSIST((size_t)len <= sizeof(str));
 				n = snprintf(&str[len], sizeof(str) - len,
 					     ".%x", w[i++]);
@@ -495,48 +582,31 @@
 		}
 	}
 
-	if (canon_name != NULL) {
-		isc__buffer_init(&buffer, str, sizeof(str));
-		isc__buffer_add(&buffer, len);
-		result = dns_name_fromtext(canon_name, &buffer,
-					   dns_rootname, 0, NULL);
-		if (result != ISC_R_SUCCESS)
-			return (result);
-	}
-	if (search_name != NULL) {
-		isc__buffer_init(&buffer, str, sizeof(str));
-		isc__buffer_add(&buffer, len);
-		if (type == DNS_RPZ_TYPE_NSIP)
-			name = &cidr->nsip_name;
-		else
-			name = &cidr->ip_name;
-		result = dns_name_fromtext(search_name, &buffer, name, 0, NULL);
-		if (result != ISC_R_SUCCESS)
-			return (result);
-	}
-	return (ISC_R_SUCCESS);
+	isc__buffer_init(&buffer, str, sizeof(str));
+	isc__buffer_add(&buffer, len);
+	result = dns_name_fromtext(ip_name, &buffer, base_name, 0, NULL);
+	return (result);
 }
 
 /*
- * Decide which kind of IP address response policy zone a name is in.
+ * Determine the type a of a name in a response policy zone.
  */
 static dns_rpz_type_t
-set_type(dns_rpz_cidr_t *cidr, dns_name_t *name) {
+type_from_name(dns_rpz_zone_t *rpz, dns_name_t *name) {
 
-	if (dns_name_issubdomain(name, &cidr->ip_name))
+	if (dns_name_issubdomain(name, &rpz->ip))
 		return (DNS_RPZ_TYPE_IP);
 
-	/*
-	 * Require `./configure --enable-rpz-nsip` and nsdname
-	 * until consistency problems are resolved.
-	 */
+	if (dns_name_issubdomain(name, &rpz->client_ip))
+		return (DNS_RPZ_TYPE_CLIENT_IP);
+
 #ifdef ENABLE_RPZ_NSIP
-	if (dns_name_issubdomain(name, &cidr->nsip_name))
+	if (dns_name_issubdomain(name, &rpz->nsip))
 		return (DNS_RPZ_TYPE_NSIP);
 #endif
 
 #ifdef ENABLE_RPZ_NSDNAME
-	if (dns_name_issubdomain(name, &cidr->nsdname_name))
+	if (dns_name_issubdomain(name, &rpz->nsdname))
 		return (DNS_RPZ_TYPE_NSDNAME);
 #endif
 
@@ -545,73 +615,80 @@
 
 /*
  * Convert an IP address from canonical response policy domain name form
- * to radix tree binary (host byte order).
+ * to radix tree binary (host byte order) for adding or deleting IP or NSIP
+ * data.
  */
 static isc_result_t
-name2ipkey(dns_rpz_cidr_t *cidr, int level, dns_name_t *src_name,
-	   dns_rpz_type_t type, dns_rpz_cidr_key_t *tgt_ip,
-	   dns_rpz_cidr_bits_t *tgt_prefix)
+name2ipkey(int log_level,
+	   const dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+	   dns_rpz_type_t rpz_type, dns_name_t *src_name,
+	   dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t *tgt_prefix,
+	   dns_rpz_addr_zbits_t *new_set)
 {
-	isc_result_t result;
-	dns_fixedname_t fname;
-	dns_name_t *ipname;
-	char ipstr[DNS_NAME_FORMATSIZE];
+	dns_rpz_zone_t *rpz;
+	char ip_str[DNS_NAME_FORMATSIZE];
+	dns_offsets_t ip_name_offsets;
+	dns_fixedname_t ip_name2f;
+	dns_name_t ip_name, *ip_name2;
 	const char *prefix_str, *cp, *end;
 	char *cp2;
 	int ip_labels;
-	dns_rpz_cidr_bits_t bits;
-	unsigned long prefix, l;
+	dns_rpz_prefix_t prefix;
+	unsigned long prefix_num, l;
+	isc_result_t result;
 	int i;
 
-	/*
-	 * Need at least enough labels for the shortest name,
-	 * :: or 128.*.RPZ_x_ZONE.rpz.LOCALHOST.
-	 */
+	REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+	rpz = rpzs->zones[rpz_num];
+	REQUIRE(rpz != NULL);
+
+	make_addr_set(new_set, DNS_RPZ_ZBIT(rpz_num), rpz_type);
+
 	ip_labels = dns_name_countlabels(src_name);
-	ip_labels -= dns_name_countlabels(&cidr->ip_name);
-	ip_labels--;
-	if (ip_labels < 1) {
-		badname(level, src_name, "; too short", "");
+	if (rpz_type == DNS_RPZ_TYPE_QNAME)
+		ip_labels -= dns_name_countlabels(&rpz->origin);
+	else
+		ip_labels -= dns_name_countlabels(&rpz->nsdname);
+	if (ip_labels < 2) {
+		badname(log_level, src_name, "; too short", "");
 		return (ISC_R_FAILURE);
 	}
+	dns_name_init(&ip_name, ip_name_offsets);
+	dns_name_getlabelsequence(src_name, 0, ip_labels, &ip_name);
 
 	/*
 	 * Get text for the IP address
 	 */
-	dns_fixedname_init(&fname);
-	ipname = dns_fixedname_name(&fname);
-	dns_name_split(src_name, dns_name_countlabels(&cidr->ip_name),
-		       ipname, NULL);
-	dns_name_format(ipname, ipstr, sizeof(ipstr));
-	end = &ipstr[strlen(ipstr)+1];
-	prefix_str = ipstr;
+	dns_name_format(&ip_name, ip_str, sizeof(ip_str));
+	end = &ip_str[strlen(ip_str)+1];
+	prefix_str = ip_str;
 
-	prefix = strtoul(prefix_str, &cp2, 10);
+	prefix_num = strtoul(prefix_str, &cp2, 10);
 	if (*cp2 != '.') {
-		badname(level, src_name,
+		badname(log_level, src_name,
 			"; invalid leading prefix length", "");
 		return (ISC_R_FAILURE);
 	}
 	*cp2 = '\0';
-	if (prefix < 1U || prefix > 128U) {
-		badname(level, src_name,
+	if (prefix_num < 1U || prefix_num > 128U) {
+		badname(log_level, src_name,
 			"; invalid prefix length of ", prefix_str);
 		return (ISC_R_FAILURE);
 	}
 	cp = cp2+1;
 
-	if (ip_labels == 4 && !strchr(cp, 'z')) {
+	if (--ip_labels == 4 && !strchr(cp, 'z')) {
 		/*
 		 * Convert an IPv4 address
 		 * from the form "prefix.w.z.y.x"
 		 */
-		if (prefix > 32U) {
-			badname(level, src_name,
+		if (prefix_num > 32U) {
+			badname(log_level, src_name,
 				"; invalid IPv4 prefix length of ", prefix_str);
 			return (ISC_R_FAILURE);
 		}
-		prefix += 96;
-		*tgt_prefix = (dns_rpz_cidr_bits_t)prefix;
+		prefix_num += 96;
+		*tgt_prefix = (dns_rpz_prefix_t)prefix_num;
 		tgt_ip->w[0] = 0;
 		tgt_ip->w[1] = 0;
 		tgt_ip->w[2] = ADDR_V4MAPPED;
@@ -621,7 +698,7 @@
 			if (l > 255U || (*cp2 != '.' && *cp2 != '\0')) {
 				if (*cp2 == '.')
 					*cp2 = '\0';
-				badname(level, src_name,
+				badname(log_level, src_name,
 					"; invalid IPv4 octet ", cp);
 				return (ISC_R_FAILURE);
 			}
@@ -632,7 +709,7 @@
 		/*
 		 * Convert a text IPv6 address.
 		 */
-		*tgt_prefix = (dns_rpz_cidr_bits_t)prefix;
+		*tgt_prefix = (dns_rpz_prefix_t)prefix_num;
 		for (i = 0;
 		     ip_labels > 0 && i < DNS_RPZ_CIDR_WORDS * 2;
 		     ip_labels--) {
@@ -651,7 +728,7 @@
 				    (*cp2 != '.' && *cp2 != '\0')) {
 					if (*cp2 == '.')
 					    *cp2 = '\0';
-					badname(level, src_name,
+					badname(log_level, src_name,
 						"; invalid IPv6 word ", cp);
 					return (ISC_R_FAILURE);
 				}
@@ -665,36 +742,37 @@
 		}
 	}
 	if (cp != end) {
-		badname(level, src_name, "", "");
+		badname(log_level, src_name, "", "");
 		return (ISC_R_FAILURE);
 	}
 
 	/*
 	 * Check for 1s after the prefix length.
 	 */
-	bits = (dns_rpz_cidr_bits_t)prefix;
-	while (bits < DNS_RPZ_CIDR_KEY_BITS) {
+	prefix = (dns_rpz_prefix_t)prefix_num;
+	while (prefix < DNS_RPZ_CIDR_KEY_BITS) {
 		dns_rpz_cidr_word_t aword;
 
-		i = bits % DNS_RPZ_CIDR_WORD_BITS;
-		aword = tgt_ip->w[bits / DNS_RPZ_CIDR_WORD_BITS];
+		i = prefix % DNS_RPZ_CIDR_WORD_BITS;
+		aword = tgt_ip->w[prefix / DNS_RPZ_CIDR_WORD_BITS];
 		if ((aword & ~DNS_RPZ_WORD_MASK(i)) != 0) {
-			badname(level, src_name,
+			badname(log_level, src_name,
 				"; too small prefix length of ", prefix_str);
 			return (ISC_R_FAILURE);
 		}
-		bits -= i;
-		bits += DNS_RPZ_CIDR_WORD_BITS;
+		prefix -= i;
+		prefix += DNS_RPZ_CIDR_WORD_BITS;
 	}
 
 	/*
-	 * Convert the address back to a canonical policy domain name
-	 * to ensure that it is in canonical form.
+	 * Convert the address back to a canonical domain name
+	 * to ensure that the original name is in canonical form.
 	 */
-	result = ip2name(cidr, tgt_ip, (dns_rpz_cidr_bits_t) prefix,
-			 type, NULL, ipname);
-	if (result != ISC_R_SUCCESS || !dns_name_equal(src_name, ipname)) {
-		badname(level, src_name, "; not canonical", "");
+	dns_fixedname_init(&ip_name2f);
+	ip_name2 = dns_fixedname_name(&ip_name2f);
+	result = ip2name(tgt_ip, (dns_rpz_prefix_t)prefix_num, NULL, ip_name2);
+	if (result != ISC_R_SUCCESS || !dns_name_equal(&ip_name, ip_name2)) {
+		badname(log_level, src_name, "; not canonical", "");
 		return (ISC_R_FAILURE);
 	}
 
@@ -702,10 +780,54 @@
 }
 
 /*
- * Find first differing bit.
+ * Get trigger name and data bits for adding or deleting summary NSDNAME
+ * or QNAME data.
  */
-static int
-ffbit(dns_rpz_cidr_word_t w) {
+static void
+name2data(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+	  dns_rpz_type_t rpz_type, const dns_name_t *src_name,
+	  dns_name_t *trig_name, dns_rpz_nm_data_t *new_data)
+{
+	dns_rpz_zone_t *rpz;
+	dns_offsets_t tmp_name_offsets;
+	dns_name_t tmp_name;
+	unsigned int prefix_len, n;
+
+	REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+	rpz = rpzs->zones[rpz_num];
+	REQUIRE(rpz != NULL);
+
+	/*
+	 * Handle wildcards by putting only the parent into the
+	 * summary RBT.  The summary database only causes a check of the
+	 * real policy zone where wildcards will be handled.
+	 */
+	if (dns_name_iswildcard(src_name)) {
+		prefix_len = 1;
+		memset(&new_data->set, 0, sizeof(new_data->set));
+		make_nm_set(&new_data->wild, rpz_num, rpz_type);
+	} else {
+		prefix_len = 0;
+		make_nm_set(&new_data->set, rpz_num, rpz_type);
+		memset(&new_data->wild, 0, sizeof(new_data->wild));
+	}
+
+	dns_name_init(&tmp_name, tmp_name_offsets);
+	n = dns_name_countlabels(src_name);
+	n -= prefix_len;
+	if (rpz_type == DNS_RPZ_TYPE_QNAME)
+		n -= dns_name_countlabels(&rpz->origin);
+	else
+		n -= dns_name_countlabels(&rpz->nsdname);
+	dns_name_getlabelsequence(src_name, prefix_len, n, &tmp_name);
+	(void)dns_name_concatenate(&tmp_name, dns_rootname, trig_name, NULL);
+}
+
+/*
+ * Find the first differing bit in a key (IP address) word.
+ */
+static inline int
+ffs_keybit(dns_rpz_cidr_word_t w) {
 	int bit;
 
 	bit = DNS_RPZ_CIDR_WORD_BITS-1;
@@ -731,17 +853,17 @@
 }
 
 /*
- * Find the first differing bit in two keys.
+ * Find the first differing bit in two keys (IP addresses).
  */
 static int
-diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_cidr_bits_t bits1,
-	  const dns_rpz_cidr_key_t *key2, dns_rpz_cidr_bits_t bits2)
+diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_prefix_t prefix1,
+	  const dns_rpz_cidr_key_t *key2, dns_rpz_prefix_t prefix2)
 {
 	dns_rpz_cidr_word_t delta;
-	dns_rpz_cidr_bits_t maxbit, bit;
+	dns_rpz_prefix_t maxbit, bit;
 	int i;
 
-	maxbit = ISC_MIN(bits1, bits2);
+	maxbit = ISC_MIN(prefix1, prefix2);
 
 	/*
 	 * find the first differing words
@@ -751,7 +873,7 @@
 	     i++, bit += DNS_RPZ_CIDR_WORD_BITS) {
 		delta = key1->w[i] ^ key2->w[i];
 		if (delta != 0) {
-			bit += ffbit(delta);
+			bit += ffs_keybit(delta);
 			break;
 		}
 	}
@@ -759,133 +881,170 @@
 }
 
 /*
+ * Given a hit while searching the radix trees,
+ * clear all bits for higher numbered zones.
+ */
+static inline dns_rpz_zbits_t
+trim_zbits(dns_rpz_zbits_t zbits, dns_rpz_zbits_t found) {
+	dns_rpz_zbits_t x;
+
+	/*
+	 * Isolate the first or smallest numbered hit bit.
+	 * Make a mask of that bit and all smaller numbered bits.
+	 */
+	x = zbits & found;
+	x &= (~x + 1);
+	x = (x << 1) - 1;
+	return (zbits &= x);
+}
+
+/*
  * Search a radix tree for an IP address for ordinary lookup
  *	or for a CIDR block adding or deleting an entry
- * The tree read (for simple search) or write lock must be held by the caller.
  *
- * Return ISC_R_SUCCESS, ISC_R_NOTFOUND, DNS_R_PARTIALMATCH, ISC_R_EXISTS,
- *	ISC_R_NOMEMORY
+ * Return ISC_R_SUCCESS, DNS_R_PARTIALMATCH, ISC_R_NOTFOUND,
+ *	    and *found=longest match node
+ *	or with create==ISC_TRUE, ISC_R_EXISTS or ISC_R_NOMEMORY
  */
 static isc_result_t
-search(dns_rpz_cidr_t *cidr, const dns_rpz_cidr_key_t *tgt_ip,
-       dns_rpz_cidr_bits_t tgt_prefix, dns_rpz_type_t type,
-       isc_boolean_t create,
-       dns_rpz_cidr_node_t **found)		/* NULL or longest match node */
+search(dns_rpz_zones_t *rpzs,
+       const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix,
+       const dns_rpz_addr_zbits_t *tgt_set, isc_boolean_t create,
+       dns_rpz_cidr_node_t **found)
 {
 	dns_rpz_cidr_node_t *cur, *parent, *child, *new_parent, *sibling;
+	dns_rpz_addr_zbits_t set;
 	int cur_num, child_num;
-	dns_rpz_cidr_bits_t dbit;
-	dns_rpz_cidr_flags_t flags, data_flag;
+	dns_rpz_prefix_t dbit;
 	isc_result_t find_result;
 
-	flags = get_flags(tgt_ip, tgt_prefix, type);
-	data_flag = flags & (DNS_RPZ_CIDR_FG_IP_DATA |
-			     DNS_RPZ_CIDR_FG_NSIP_DATA);
-
+	set = *tgt_set;
 	find_result = ISC_R_NOTFOUND;
-	if (found != NULL)
-		*found = NULL;
-	cur = cidr->root;
+	*found = NULL;
+	cur = rpzs->cidr;
 	parent = NULL;
 	cur_num = 0;
 	for (;;) {
 		if (cur == NULL) {
 			/*
-			 * No child so we cannot go down.  Fail or
-			 * add the target as a child of the current parent.
+			 * No child so we cannot go down.
+			 * Quit with whatever we already found
+			 * or add the target as a child of the current parent.
 			 */
 			if (!create)
 				return (find_result);
-			child = new_node(cidr, tgt_ip, tgt_prefix, 0);
+			child = new_node(rpzs, tgt_ip, tgt_prefix, NULL);
 			if (child == NULL)
 				return (ISC_R_NOMEMORY);
 			if (parent == NULL)
-				cidr->root = child;
+				rpzs->cidr = child;
 			else
 				parent->child[cur_num] = child;
 			child->parent = parent;
-			set_node_flags(child, type);
-			if (found != NULL)
-				*found = cur;
+			child->set.client_ip |= tgt_set->client_ip;
+			child->set.ip |= tgt_set->ip;
+			child->set.nsip |= tgt_set->nsip;
+			set_sum_pair(child);
+			*found = cur;
 			return (ISC_R_SUCCESS);
 		}
 
-		/*
-		 * Pretend a node not in the correct tree does not exist
-		 * if we are not adding to the tree,
-		 * If we are adding, then continue down to eventually
-		 * add a node and mark/put this node in the correct tree.
-		 */
-		if ((cur->flags & flags) == 0 && !create)
-			return (find_result);
+		if ((cur->sum.client_ip & set.client_ip) == 0 &&
+		    (cur->sum.ip & set.ip) == 0 &&
+		    (cur->sum.nsip & set.nsip) == 0) {
+			/*
+			 * This node has no relevant data
+			 * and is in none of the target trees.
+			 * Pretend it does not exist if we are not adding.
+			 *
+			 * If we are adding, continue down to eventually add
+			 * a node and mark/put this node in the correct tree.
+			 */
+			if (!create)
+				return (find_result);
+		}
 
-		dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->bits);
+		dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->prefix);
 		/*
-		 * dbit <= tgt_prefix and dbit <= cur->bits always.
+		 * dbit <= tgt_prefix and dbit <= cur->prefix always.
 		 * We are finished searching if we matched all of the target.
 		 */
 		if (dbit == tgt_prefix) {
-			if (tgt_prefix == cur->bits) {
+			if (tgt_prefix == cur->prefix) {
 				/*
-				 * The current node matches the target exactly.
-				 * It is the answer if it has data.
+				 * The node's key matches the target exactly.
 				 */
-				if ((cur->flags & data_flag) != 0) {
-					if (create)
-						return (ISC_R_EXISTS);
-					if (found != NULL)
-						*found = cur;
-					return (ISC_R_SUCCESS);
+				if ((cur->set.client_ip & set.client_ip) != 0 ||
+				    (cur->set.ip & set.ip) != 0 ||
+				    (cur->set.nsip & set.nsip) != 0) {
+					/*
+					 * It is the answer if it has data.
+					 */
+					*found = cur;
+					if (create) {
+					    find_result = ISC_R_EXISTS;
+					} else {
+					    find_result = ISC_R_SUCCESS;
+					}
 				} else if (create) {
 					/*
-					 * The node had no data but does now.
+					 * The node lacked relevant data,
+					 * but will have it now.
 					 */
-					set_node_flags(cur, type);
-					if (found != NULL)
-						*found = cur;
-					return (ISC_R_SUCCESS);
+					cur->set.client_ip |= tgt_set->client_ip;
+					cur->set.ip |= tgt_set->ip;
+					cur->set.nsip |= tgt_set->nsip;
+					set_sum_pair(cur);
+					*found = cur;
+					find_result = ISC_R_SUCCESS;
 				}
 				return (find_result);
 			}
 
 			/*
-			 * We know tgt_prefix < cur_bits which means that
+			 * We know tgt_prefix < cur->prefix which means that
 			 * the target is shorter than the current node.
 			 * Add the target as the current node's parent.
 			 */
 			if (!create)
 				return (find_result);
 
-			new_parent = new_node(cidr, tgt_ip, tgt_prefix,
-					      cur->flags);
+			new_parent = new_node(rpzs, tgt_ip, tgt_prefix, cur);
 			if (new_parent == NULL)
 				return (ISC_R_NOMEMORY);
 			new_parent->parent = parent;
 			if (parent == NULL)
-				cidr->root = new_parent;
+				rpzs->cidr = new_parent;
 			else
 				parent->child[cur_num] = new_parent;
 			child_num = DNS_RPZ_IP_BIT(&cur->ip, tgt_prefix+1);
 			new_parent->child[child_num] = cur;
 			cur->parent = new_parent;
-			set_node_flags(new_parent, type);
-			if (found != NULL)
-				*found = new_parent;
+			new_parent->set = *tgt_set;
+			set_sum_pair(new_parent);
+			*found = new_parent;
 			return (ISC_R_SUCCESS);
 		}
 
-		if (dbit == cur->bits) {
-			/*
-			 * We have a partial match by matching of all of the
-			 * current node but only part of the target.
-			 * Try to go down.
-			 */
-			if ((cur->flags & data_flag) != 0) {
+		if (dbit == cur->prefix) {
+			if ((cur->set.client_ip & set.client_ip) != 0 ||
+			    (cur->set.ip & set.ip) != 0 ||
+			    (cur->set.nsip & set.nsip) != 0) {
+				/*
+				 * We have a partial match between of all of the
+				 * current node but only part of the target.
+				 * Continue searching for other hits in the
+				 * same or lower numbered trees.
+				 */
 				find_result = DNS_R_PARTIALMATCH;
-				if (found != NULL)
-					*found = cur;
+				*found = cur;
+				set.client_ip = trim_zbits(set.ip,
+							cur->set.client_ip);
+				set.ip = trim_zbits(set.ip,
+						    cur->set.ip);
+				set.nsip = trim_zbits(set.nsip,
+						      cur->set.nsip);
 			}
-
 			parent = cur;
 			cur_num = DNS_RPZ_IP_BIT(tgt_ip, dbit);
 			cur = cur->child[cur_num];
@@ -894,7 +1053,7 @@
 
 
 		/*
-		 * dbit < tgt_prefix and dbit < cur->bits,
+		 * dbit < tgt_prefix and dbit < cur->prefix,
 		 * so we failed to match both the target and the current node.
 		 * Insert a fork of a parent above the current node and
 		 * add the target as a sibling of the current node
@@ -902,17 +1061,17 @@
 		if (!create)
 			return (find_result);
 
-		sibling = new_node(cidr, tgt_ip, tgt_prefix, 0);
+		sibling = new_node(rpzs, tgt_ip, tgt_prefix, NULL);
 		if (sibling == NULL)
 			return (ISC_R_NOMEMORY);
-		new_parent = new_node(cidr, tgt_ip, dbit, cur->flags);
+		new_parent = new_node(rpzs, tgt_ip, dbit, cur);
 		if (new_parent == NULL) {
-			isc_mem_put(cidr->mctx, sibling, sizeof(*sibling));
+			isc_mem_put(rpzs->mctx, sibling, sizeof(*sibling));
 			return (ISC_R_NOMEMORY);
 		}
 		new_parent->parent = parent;
 		if (parent == NULL)
-			cidr->root = new_parent;
+			rpzs->cidr = new_parent;
 		else
 			parent->child[cur_num] = new_parent;
 		child_num = DNS_RPZ_IP_BIT(tgt_ip, dbit);
@@ -920,129 +1079,670 @@
 		new_parent->child[1-child_num] = cur;
 		cur->parent = new_parent;
 		sibling->parent = new_parent;
-		set_node_flags(sibling, type);
-		if (found != NULL)
-			*found = sibling;
+		sibling->set = *tgt_set;
+		set_sum_pair(sibling);
+		*found = sibling;
 		return (ISC_R_SUCCESS);
 	}
 }
 
 /*
- * Add an IP address to the radix tree of a response policy database.
- *	The tree write lock must be held by the caller.
+ * Add an IP address to the radix tree.
  */
-void
-dns_rpz_cidr_addip(dns_rpz_cidr_t *cidr, dns_name_t *name) {
-	isc_result_t result;
+static isc_result_t
+add_cidr(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+	 dns_rpz_type_t rpz_type, dns_name_t *src_name)
+{
 	dns_rpz_cidr_key_t tgt_ip;
-	dns_rpz_cidr_bits_t tgt_prefix;
-	dns_rpz_type_t type;
-
-	REQUIRE(cidr != NULL);
+	dns_rpz_prefix_t tgt_prefix;
+	dns_rpz_addr_zbits_t set;
+	dns_rpz_cidr_node_t *found;
+	isc_result_t result;
 
+	result = name2ipkey(DNS_RPZ_ERROR_LEVEL, rpzs, rpz_num, rpz_type,
+			    src_name, &tgt_ip, &tgt_prefix, &set);
 	/*
-	 * No worries if the new name is not an IP address.
+	 * Log complaints about bad owner names but let the zone load.
 	 */
-	type = set_type(cidr, name);
-	switch (type) {
-	case DNS_RPZ_TYPE_IP:
-	case DNS_RPZ_TYPE_NSIP:
-		break;
-	case DNS_RPZ_TYPE_NSDNAME:
-		cidr->have_nsdname = ISC_TRUE;
-		return;
-	case DNS_RPZ_TYPE_QNAME:
-	case DNS_RPZ_TYPE_BAD:
-		return;
-	}
-	result = name2ipkey(cidr, DNS_RPZ_ERROR_LEVEL, name,
-			    type, &tgt_ip, &tgt_prefix);
 	if (result != ISC_R_SUCCESS)
-		return;
+		return (ISC_R_SUCCESS);
 
-	result = search(cidr, &tgt_ip, tgt_prefix, type, ISC_TRUE, NULL);
-	if (result == ISC_R_EXISTS &&
-	    isc_log_wouldlog(dns_lctx, DNS_RPZ_ERROR_LEVEL))
-	{
-		char printname[DNS_NAME_FORMATSIZE];
+	result = search(rpzs, &tgt_ip, tgt_prefix, &set, ISC_TRUE, &found);
+	if (result != ISC_R_SUCCESS) {
+		char namebuf[DNS_NAME_FORMATSIZE];
 
 		/*
 		 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
 		 */
-		dns_name_format(name, printname, sizeof(printname));
+		dns_name_format(src_name, namebuf, sizeof(namebuf));
 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
 			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
-			      "rpz add failed; \"%s\" is a duplicate name",
-			      printname);
+			      "rpz add_cidr(%s) failed: %s",
+			      namebuf, isc_result_totext(result));
+		return (result);
 	}
+
+	adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, ISC_TRUE);
+	return (result);
 }
 
-/*
- * Delete an IP address from the radix tree of a response policy database.
- *	The tree write lock must be held by the caller.
- */
-void
-dns_rpz_cidr_deleteip(dns_rpz_cidr_t *cidr, dns_name_t *name) {
+static isc_result_t
+add_nm(dns_rpz_zones_t *rpzs, dns_name_t *trig_name,
+	 const dns_rpz_nm_data_t *new_data)
+{
+	dns_rbtnode_t *nmnode;
+	dns_rpz_nm_data_t *nm_data;
 	isc_result_t result;
-	dns_rpz_cidr_key_t tgt_ip;
-	dns_rpz_cidr_bits_t tgt_prefix;
-	dns_rpz_type_t type;
-	dns_rpz_cidr_node_t *tgt = NULL, *parent, *child;
-	dns_rpz_cidr_flags_t flags, data_flag;
 
-	if (cidr == NULL)
-		return;
+	nmnode = NULL;
+	result = dns_rbt_addnode(rpzs->rbt, trig_name, &nmnode);
+	switch (result) {
+	case ISC_R_SUCCESS:
+	case ISC_R_EXISTS:
+		nm_data = nmnode->data;
+		if (nm_data == NULL) {
+			nm_data = isc_mem_get(rpzs->mctx, sizeof(*nm_data));
+			if (nm_data == NULL)
+				return (ISC_R_NOMEMORY);
+			*nm_data = *new_data;
+			nmnode->data = nm_data;
+			return (ISC_R_SUCCESS);
+		}
+		break;
+	default:
+		return (result);
+	}
 
 	/*
-	 * Decide which kind of policy zone IP address it is, if either
-	 * and then find its node.
+	 * Do not count bits that are already present
 	 */
-	type = set_type(cidr, name);
-	switch (type) {
-	case DNS_RPZ_TYPE_IP:
-	case DNS_RPZ_TYPE_NSIP:
-		break;
-	case DNS_RPZ_TYPE_NSDNAME:
+	if ((nm_data->set.qname & new_data->set.qname) != 0 ||
+	    (nm_data->set.ns & new_data->set.ns) != 0 ||
+	    (nm_data->wild.qname & new_data->wild.qname) != 0 ||
+	    (nm_data->wild.ns & new_data->wild.ns) != 0) {
+		char namebuf[DNS_NAME_FORMATSIZE];
+
 		/*
-		 * We cannot easily count nsdnames because
-		 * internal rbt nodes get deleted.
+		 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
 		 */
-		return;
+		dns_name_format(trig_name, namebuf, sizeof(namebuf));
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+			      "rpz add_nm(%s): bits already set", namebuf);
+		return (ISC_R_EXISTS);
+	}
+
+	nm_data->set.qname |= new_data->set.qname;
+	nm_data->set.ns |= new_data->set.ns;
+	nm_data->wild.qname |= new_data->wild.qname;
+	nm_data->wild.ns |= new_data->wild.ns;
+	return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+add_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+	 dns_rpz_type_t rpz_type, dns_name_t *src_name)
+{
+	dns_rpz_nm_data_t new_data;
+	dns_fixedname_t trig_namef;
+	dns_name_t *trig_name;
+	isc_result_t result;
+
+	dns_fixedname_init(&trig_namef);
+	trig_name = dns_fixedname_name(&trig_namef);
+	name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &new_data);
+
+	result = add_nm(rpzs, trig_name, &new_data);
+	if (result == ISC_R_SUCCESS)
+		adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, ISC_TRUE);
+	return (result);
+}
+
+/*
+ * Callback to free the data for a node in the summary RBT database.
+ */
+static void
+rpz_node_deleter(void *nm_data, void *mctx) {
+	isc_mem_put(mctx, nm_data, sizeof(dns_rpz_nm_data_t));
+}
+
+/*
+ * Get ready for a new set of policy zones.
+ */
+isc_result_t
+dns_rpz_new_zones(dns_rpz_zones_t **rpzsp, isc_mem_t *mctx) {
+	dns_rpz_zones_t *new;
+	isc_result_t result;
+
+	REQUIRE(rpzsp != NULL && *rpzsp == NULL);
+
+	*rpzsp = NULL;
+
+	new = isc_mem_get(mctx, sizeof(*new));
+	if (new == NULL)
+		return (ISC_R_NOMEMORY);
+	memset(new, 0, sizeof(*new));
+
+	result = isc_mutex_init(&new->search_lock);
+	if (result != ISC_R_SUCCESS) {
+		isc_mem_put(mctx, new, sizeof(*new));
+		return (result);
+	}
+
+	result = isc_mutex_init(&new->maint_lock);
+	if (result != ISC_R_SUCCESS) {
+		DESTROYLOCK(&new->search_lock);
+		isc_mem_put(mctx, new, sizeof(*new));
+		return (result);
+	}
+
+	result = isc_refcount_init(&new->refs, 1);
+	if (result != ISC_R_SUCCESS) {
+		DESTROYLOCK(&new->maint_lock);
+		DESTROYLOCK(&new->search_lock);
+		isc_mem_put(mctx, new, sizeof(*new));
+		return (result);
+	}
+
+	result = dns_rbt_create(mctx, rpz_node_deleter, mctx, &new->rbt);
+	if (result != ISC_R_SUCCESS) {
+		isc_refcount_decrement(&new->refs, NULL);
+		isc_refcount_destroy(&new->refs);
+		DESTROYLOCK(&new->maint_lock);
+		DESTROYLOCK(&new->search_lock);
+		isc_mem_put(mctx, new, sizeof(*new));
+		return (result);
+	}
+
+	isc_mem_attach(mctx, &new->mctx);
+
+	*rpzsp = new;
+	return (ISC_R_SUCCESS);
+}
+
+/*
+ * Free the radix tree of a response policy database.
+ */
+static void
+cidr_free(dns_rpz_zones_t *rpzs) {
+	dns_rpz_cidr_node_t *cur, *child, *parent;
+
+	cur = rpzs->cidr;
+	while (cur != NULL) {
+		/* Depth first. */
+		child = cur->child[0];
+		if (child != NULL) {
+			cur = child;
+			continue;
+		}
+		child = cur->child[1];
+		if (child != NULL) {
+			cur = child;
+			continue;
+		}
+
+		/* Delete this leaf and go up. */
+		parent = cur->parent;
+		if (parent == NULL)
+			rpzs->cidr = NULL;
+		else
+			parent->child[parent->child[1] == cur] = NULL;
+		isc_mem_put(rpzs->mctx, cur, sizeof(*cur));
+		cur = parent;
+	}
+}
+
+/*
+ * Discard a response policy zone blob
+ * before discarding the overall rpz structure.
+ */
+static void
+rpz_detach(dns_rpz_zone_t **rpzp, dns_rpz_zones_t *rpzs) {
+	dns_rpz_zone_t *rpz;
+	unsigned int refs;
+
+	rpz = *rpzp;
+	*rpzp = NULL;
+	isc_refcount_decrement(&rpz->refs, &refs);
+	if (refs != 0)
+		return;
+	isc_refcount_destroy(&rpz->refs);
+
+	if (dns_name_dynamic(&rpz->origin))
+		dns_name_free(&rpz->origin, rpzs->mctx);
+	if (dns_name_dynamic(&rpz->client_ip))
+		dns_name_free(&rpz->client_ip, rpzs->mctx);
+	if (dns_name_dynamic(&rpz->ip))
+		dns_name_free(&rpz->ip, rpzs->mctx);
+	if (dns_name_dynamic(&rpz->nsdname))
+		dns_name_free(&rpz->nsdname, rpzs->mctx);
+	if (dns_name_dynamic(&rpz->nsip))
+		dns_name_free(&rpz->nsip, rpzs->mctx);
+	if (dns_name_dynamic(&rpz->passthru))
+		dns_name_free(&rpz->passthru, rpzs->mctx);
+	if (dns_name_dynamic(&rpz->drop))
+		dns_name_free(&rpz->drop, rpzs->mctx);
+	if (dns_name_dynamic(&rpz->tcp_only))
+		dns_name_free(&rpz->tcp_only, rpzs->mctx);
+	if (dns_name_dynamic(&rpz->cname))
+		dns_name_free(&rpz->cname, rpzs->mctx);
+
+	isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz));
+}
+
+void
+dns_rpz_attach_rpzs(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **rpzsp) {
+	REQUIRE(rpzsp != NULL && *rpzsp == NULL);
+	isc_refcount_increment(&rpzs->refs, NULL);
+	*rpzsp = rpzs;
+}
+
+/*
+ * Forget a view's policy zones.
+ */
+void
+dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) {
+	dns_rpz_zones_t *rpzs;
+	dns_rpz_zone_t *rpz;
+	dns_rpz_num_t rpz_num;
+	unsigned int refs;
+
+	REQUIRE(rpzsp != NULL);
+	rpzs = *rpzsp;
+	REQUIRE(rpzs != NULL);
+
+	*rpzsp = NULL;
+	isc_refcount_decrement(&rpzs->refs, &refs);
+
+	/*
+	 * Forget the last of view's rpz machinery after the last reference.
+	 */
+	if (refs == 0) {
+		for (rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES; ++rpz_num) {
+			rpz = rpzs->zones[rpz_num];
+			rpzs->zones[rpz_num] = NULL;
+			if (rpz != NULL)
+				rpz_detach(&rpz, rpzs);
+		}
+
+		cidr_free(rpzs);
+		dns_rbt_destroy(&rpzs->rbt);
+		DESTROYLOCK(&rpzs->maint_lock);
+		DESTROYLOCK(&rpzs->search_lock);
+		isc_refcount_destroy(&rpzs->refs);
+		isc_mem_putanddetach(&rpzs->mctx, rpzs, sizeof(*rpzs));
+	}
+}
+
+/*
+ * Create empty summary database to load one zone.
+ * The RBTDB write tree lock must be held.
+ */
+isc_result_t
+dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp,
+		  dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num)
+{
+	dns_rpz_zones_t *load_rpzs;
+	dns_rpz_zone_t *rpz;
+	dns_rpz_zbits_t tgt;
+	isc_result_t result;
+
+	REQUIRE(rpz_num < rpzs->p.num_zones);
+	rpz = rpzs->zones[rpz_num];
+	REQUIRE(rpz != NULL);
+
+	/*
+	 * When reloading a zone, there are usually records among the summary
+	 * data for the zone.  Some of those records might be deleted by the
+	 * reloaded zone data.  To deal with that case:
+	 *    reload the new zone data into a new blank summary database
+	 *    if the reload fails, discard the new summary database
+	 *    if the new zone data is acceptable, copy the records for the
+	 *	other zones into the new summary database and replace the
+	 *	old summary database with the new.
+	 *
+	 * At the first attempt to load a zone, there is no summary data
+	 * for the zone and so no records that need to be deleted.
+	 * This is also the most common case of policy zone loading.
+	 * Most policy zone maintenance should be by incremental changes
+	 * and so by the addition and deletion of individual records.
+	 * Detect that case and load records the first time into the
+	 * operational summary database
+	 */
+	tgt = DNS_RPZ_ZBIT(rpz_num);
+	LOCK(&rpzs->maint_lock);
+	LOCK(&rpzs->search_lock);
+	if ((rpzs->load_begun & tgt) == 0) {
+		/*
+		 * There is no existing version of the target zone.
+		 */
+		rpzs->load_begun |= tgt;
+		dns_rpz_attach_rpzs(rpzs, load_rpzsp);
+		UNLOCK(&rpzs->search_lock);
+		UNLOCK(&rpzs->maint_lock);
+
+	} else {
+		UNLOCK(&rpzs->search_lock);
+		UNLOCK(&rpzs->maint_lock);
+
+		result = dns_rpz_new_zones(load_rpzsp, rpzs->mctx);
+		if (result != ISC_R_SUCCESS)
+			return (result);
+		load_rpzs = *load_rpzsp;
+		load_rpzs->p.num_zones = rpzs->p.num_zones;
+		load_rpzs->total_triggers = rpzs->total_triggers;
+		memcpy(load_rpzs->triggers, rpzs->triggers,
+		       sizeof(load_rpzs->triggers));
+		memset(&load_rpzs->triggers[rpz_num], 0,
+		       sizeof(load_rpzs->triggers[rpz_num]));
+		load_rpzs->zones[rpz_num] = rpz;
+		isc_refcount_increment(&rpz->refs, NULL);
+	}
+
+	return (ISC_R_SUCCESS);
+}
+
+static void
+fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) {
+	dns_rpz_num_t n;
+	const dns_rpz_zone_t *rpz;
+	dns_rpz_triggers_t old_totals;
+	dns_rpz_zbits_t zbit;
+	char namebuf[DNS_NAME_FORMATSIZE];
+
+#	define SET_TRIG(n, zbit, type)					\
+	if (rpzs->triggers[n].type == 0) {				\
+		rpzs->have.type &= ~zbit;				\
+	} else {							\
+		rpzs->total_triggers.type += rpzs->triggers[n].type;	\
+		rpzs->have.type |= zbit;				\
+	}
+
+	memcpy(&old_totals, &rpzs->total_triggers, sizeof(old_totals));
+	memset(&rpzs->total_triggers, 0, sizeof(rpzs->total_triggers));
+	for (n = 0; n < rpzs->p.num_zones; ++n) {
+		rpz = rpzs->zones[n];
+		zbit = DNS_RPZ_ZBIT(n);
+		SET_TRIG(n, zbit, client_ipv4);
+		SET_TRIG(n, zbit, client_ipv6);
+		SET_TRIG(n, zbit, qname);
+		SET_TRIG(n, zbit, ipv4);
+		SET_TRIG(n, zbit, ipv6);
+		SET_TRIG(n, zbit, nsdname);
+		SET_TRIG(n, zbit, nsipv4);
+		SET_TRIG(n, zbit, nsipv6);
+	}
+
+	fix_qname_skip_recurse(rpzs);
+
+	dns_name_format(&rpzs->zones[rpz_num]->origin,
+			namebuf, sizeof(namebuf));
+	isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+		      DNS_LOGMODULE_RBTDB, DNS_RPZ_INFO_LEVEL,
+		      "(re)loading policy zone '%s' changed from"
+		      " %d to %d qname, %d to %d nsdname,"
+		      " %d to %d IP, %d to %d NSIP entries",
+		      namebuf,
+		      old_totals.qname, rpzs->total_triggers.qname,
+		      old_totals.nsdname, rpzs->total_triggers.nsdname,
+		      old_totals.ipv4 + old_totals.ipv6,
+		      rpzs->total_triggers.ipv4 + rpzs->total_triggers.ipv6,
+		      old_totals.nsipv4 + old_totals.nsipv6,
+		      rpzs->total_triggers.nsipv4 + rpzs->total_triggers.nsipv6);
+
+#	undef SET_TRIG
+}
+
+/*
+ * Finish loading one zone.
+ * The RBTDB write tree lock must be held.
+ */
+isc_result_t
+dns_rpz_ready(dns_rpz_zones_t *rpzs,
+	      dns_rpz_zones_t **load_rpzsp, dns_rpz_num_t rpz_num)
+{
+	dns_rpz_zones_t *load_rpzs;
+	const dns_rpz_cidr_node_t *cnode, *next_cnode, *parent_cnode;
+	dns_rpz_cidr_node_t *found;
+	dns_rpz_zbits_t new_bit;
+	dns_rpz_addr_zbits_t new_ip;
+	dns_rbt_t *rbt;
+	dns_rbtnodechain_t chain;
+	dns_rbtnode_t *nmnode;
+	dns_rpz_nm_data_t *nm_data, new_data;
+	dns_fixedname_t labelf, originf, namef;
+	dns_name_t *label, *origin, *name;
+	isc_result_t result;
+
+	INSIST(rpzs != NULL);
+	LOCK(&rpzs->maint_lock);
+	load_rpzs = *load_rpzsp;
+	INSIST(load_rpzs != NULL);
+
+	if (load_rpzs == rpzs) {
+		/*
+		 * This is a successful initial zone loading,
+		 * perhaps for a new instance of a view.
+		 */
+		fix_triggers(rpzs, rpz_num);
+		UNLOCK(&rpzs->maint_lock);
+		dns_rpz_detach_rpzs(load_rpzsp);
+		return (ISC_R_SUCCESS);
+	}
+
+	LOCK(&load_rpzs->maint_lock);
+	LOCK(&load_rpzs->search_lock);
+
+	/*
+	 * Unless there is only one policy zone, copy the other policy zones
+	 * from the old policy structure to the new summary databases.
+	 */
+	if (rpzs->p.num_zones > 1) {
+		new_bit = ~DNS_RPZ_ZBIT(rpz_num);
+
+		/*
+		 * Copy to the radix tree.
+		 */
+		for (cnode = rpzs->cidr; cnode != NULL; cnode = next_cnode) {
+			new_ip.ip = cnode->set.ip & new_bit;
+			new_ip.client_ip = cnode->set.client_ip & new_bit;
+			new_ip.nsip = cnode->set.nsip & new_bit;
+			if (new_ip.client_ip != 0 ||
+			    new_ip.ip != 0 ||
+			    new_ip.nsip != 0) {
+				result = search(load_rpzs,
+						&cnode->ip, cnode->prefix,
+						&new_ip, ISC_TRUE, &found);
+				if (result == ISC_R_NOMEMORY)
+					goto unlock_and_detach;
+				INSIST(result == ISC_R_SUCCESS);
+			}
+			/*
+			 * Do down and to the left as far as possible.
+			 */
+			next_cnode = cnode->child[0];
+			if (next_cnode != NULL)
+				continue;
+			/*
+			 * Go up until we find a branch to the right where
+			 * we previously took the branch to the left.
+			 */
+			for (;;) {
+				parent_cnode = cnode->parent;
+				if (parent_cnode == NULL)
+					break;
+				if (parent_cnode->child[0] == cnode) {
+					next_cnode = parent_cnode->child[1];
+					if (next_cnode != NULL)
+					    break;
+				}
+				cnode = parent_cnode;
+			}
+		}
+
+		/*
+		 * Copy to the summary RBT.
+		 */
+		dns_fixedname_init(&namef);
+		name = dns_fixedname_name(&namef);
+		dns_fixedname_init(&labelf);
+		label = dns_fixedname_name(&labelf);
+		dns_fixedname_init(&originf);
+		origin = dns_fixedname_name(&originf);
+		dns_rbtnodechain_init(&chain, NULL);
+		result = dns_rbtnodechain_first(&chain, rpzs->rbt, NULL, NULL);
+		while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
+			result = dns_rbtnodechain_current(&chain, label, origin,
+							&nmnode);
+			INSIST(result == ISC_R_SUCCESS);
+			nm_data = nmnode->data;
+			if (nm_data != NULL) {
+				new_data.set.qname = (nm_data->set.qname &
+						      new_bit);
+				new_data.set.ns = nm_data->set.ns & new_bit;
+				new_data.wild.qname = (nm_data->wild.qname &
+						       new_bit);
+				new_data.wild.ns = nm_data->wild.ns & new_bit;
+				if (new_data.set.qname != 0 ||
+				    new_data.set.ns != 0 ||
+				    new_data.wild.qname != 0 ||
+				    new_data.wild.ns != 0) {
+					result = dns_name_concatenate(label,
+							origin, name, NULL);
+					INSIST(result == ISC_R_SUCCESS);
+					result = add_nm(load_rpzs, name,
+							&new_data);
+					if (result != ISC_R_SUCCESS)
+						goto unlock_and_detach;
+				}
+			}
+			result = dns_rbtnodechain_next(&chain, NULL, NULL);
+		}
+		if (result != ISC_R_NOMORE && result != ISC_R_NOTFOUND) {
+			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+				      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+				      "dns_rpz_ready(): unexpected %s",
+				      isc_result_totext(result));
+			goto unlock_and_detach;
+		}
+	}
+
+	fix_triggers(load_rpzs, rpz_num);
+
+	/*
+	 * 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;
+
+	rpzs->total_triggers = load_rpzs->total_triggers;
+
+	UNLOCK(&rpzs->search_lock);
+
+	result = ISC_R_SUCCESS;
+
+ unlock_and_detach:
+	UNLOCK(&rpzs->maint_lock);
+	UNLOCK(&load_rpzs->search_lock);
+	UNLOCK(&load_rpzs->maint_lock);
+	dns_rpz_detach_rpzs(load_rpzsp);
+	return (result);
+}
+
+/*
+ * Add an IP address to the radix tree or a name to the summary database.
+ */
+isc_result_t
+dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *src_name)
+{
+	dns_rpz_zone_t *rpz;
+	dns_rpz_type_t rpz_type;
+	isc_result_t result = ISC_R_FAILURE;
+
+	REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+	rpz = rpzs->zones[rpz_num];
+	REQUIRE(rpz != NULL);
+
+	rpz_type = type_from_name(rpz, src_name);
+
+	LOCK(&rpzs->maint_lock);
+	LOCK(&rpzs->search_lock);
+
+	switch (rpz_type) {
 	case DNS_RPZ_TYPE_QNAME:
+	case DNS_RPZ_TYPE_NSDNAME:
+		result = add_name(rpzs, rpz_num, rpz_type, src_name);
+		break;
+	case DNS_RPZ_TYPE_CLIENT_IP:
+	case DNS_RPZ_TYPE_IP:
+	case DNS_RPZ_TYPE_NSIP:
+		result = add_cidr(rpzs, rpz_num, rpz_type, src_name);
+		break;
 	case DNS_RPZ_TYPE_BAD:
-		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_addr_zbits_t tgt_set;
+	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, &tgt_set);
 	if (result != ISC_R_SUCCESS)
 		return;
 
-	result = search(cidr, &tgt_ip, tgt_prefix, type, ISC_FALSE, &tgt);
+	result = search(rpzs, &tgt_ip, tgt_prefix, &tgt_set, ISC_FALSE, &tgt);
 	if (result != ISC_R_SUCCESS) {
-		badname(DNS_RPZ_ERROR_LEVEL, name, "; missing rpz node", "");
+		INSIST(result == ISC_R_NOTFOUND ||
+		       result == DNS_R_PARTIALMATCH);
+		/*
+		 * Do not worry about missing summary RBT nodes that probably
+		 * correspond to RBTDB nodes that were implicit RBT nodes
+		 * that were later added for (often empty) wildcards
+		 * and then to the RBTDB deferred cleanup list.
+		 */
 		return;
 	}
 
 	/*
 	 * Mark the node and its parents to reflect the deleted IP address.
+	 * Do not count bits that are already clear for internal RBTDB nodes.
 	 */
-	flags = get_flags(&tgt_ip, tgt_prefix, type);
-	data_flag = flags & (DNS_RPZ_CIDR_FG_IP_DATA |
-			      DNS_RPZ_CIDR_FG_NSIP_DATA);
-	tgt->flags &= ~data_flag;
-	for (parent = tgt; parent != NULL; parent = parent->parent) {
-		if ((parent->flags & data_flag) != 0 ||
-		    (parent->child[0] != NULL &&
-		     (parent->child[0]->flags & flags) != 0) ||
-		    (parent->child[1] != NULL &&
-		     (parent->child[1]->flags & flags) != 0))
-			break;
-		parent->flags &= ~flags;
-	}
+	tgt_set.client_ip &= tgt->set.client_ip;
+	tgt_set.ip &= tgt->set.ip;
+	tgt_set.nsip &= tgt->set.nsip;
+	tgt->set.client_ip &= ~tgt_set.client_ip;
+	tgt->set.ip &= ~tgt_set.ip;
+	tgt->set.nsip &= ~tgt_set.nsip;
+	set_sum_pair(tgt);
+
+	adj_trigger_cnt(rpzs, rpz_num, rpz_type, &tgt_ip, tgt_prefix, ISC_FALSE);
 
 	/*
 	 * We might need to delete 2 nodes.
@@ -1054,13 +1754,14 @@
 		 */
 		if ((child = tgt->child[0]) != NULL) {
 			if (tgt->child[1] != NULL)
-				return;
+				break;
 		} else {
 			child = tgt->child[1];
 		}
-		if ((tgt->flags & (DNS_RPZ_CIDR_FG_IP_DATA |
-				 DNS_RPZ_CIDR_FG_NSIP_DATA)) != 0)
-			return;
+		if (tgt->set.client_ip != 0 ||
+		    tgt->set.ip != 0 ||
+		    tgt->set.nsip != 0)
+			break;
 
 		/*
 		 * Replace the pointer to this node in the parent with
@@ -1068,7 +1769,7 @@
 		 */
 		parent = tgt->parent;
 		if (parent == NULL) {
-			cidr->root = child;
+			rpzs->cidr = child;
 		} else {
 			parent->child[parent->child[1] == tgt] = child;
 		}
@@ -1077,26 +1778,144 @@
 		 */
 		if (child != NULL)
 			child->parent = parent;
-		isc_mem_put(cidr->mctx, tgt, sizeof(*tgt));
+		isc_mem_put(rpzs->mctx, tgt, sizeof(*tgt));
 
 		tgt = parent;
 	} while (tgt != NULL);
 }
 
+static void
+del_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+	 dns_rpz_type_t rpz_type, dns_name_t *src_name)
+{
+	char namebuf[DNS_NAME_FORMATSIZE];
+	dns_fixedname_t trig_namef;
+	dns_name_t *trig_name;
+	dns_rbtnode_t *nmnode;
+	dns_rpz_nm_data_t *nm_data, del_data;
+	isc_result_t result;
+
+	dns_fixedname_init(&trig_namef);
+	trig_name = dns_fixedname_name(&trig_namef);
+	name2data(rpzs, rpz_num, rpz_type, src_name, trig_name, &del_data);
+
+	/*
+	 * No need for a summary database of names with only 1 policy zone.
+	 */
+	if (rpzs->p.num_zones <= 1) {
+		adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, ISC_FALSE);
+		return;
+	}
+
+	nmnode = NULL;
+	result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, NULL, 0,
+				  NULL, NULL);
+	if (result != ISC_R_SUCCESS) {
+		/*
+		 * Do not worry about missing summary RBT nodes that probably
+		 * correspond to RBTDB nodes that were implicit RBT nodes
+		 * that were later added for (often empty) wildcards
+		 * and then to the RBTDB deferred cleanup list.
+		 */
+		if (result == ISC_R_NOTFOUND)
+			return;
+		dns_name_format(src_name, namebuf, sizeof(namebuf));
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+			      "rpz del_name(%s) node search failed: %s",
+			      namebuf, isc_result_totext(result));
+		return;
+	}
+
+	nm_data = nmnode->data;
+	INSIST(nm_data != NULL);
+
+	/*
+	 * Do not count bits that next existed for RBT nodes that would we
+	 * would not have found in a summary for a single RBTDB tree.
+	 */
+	del_data.set.qname &= nm_data->set.qname;
+	del_data.set.ns &= nm_data->set.ns;
+	del_data.wild.qname &= nm_data->wild.qname;
+	del_data.wild.ns &= nm_data->wild.ns;
+
+	nm_data->set.qname &= ~del_data.set.qname;
+	nm_data->set.ns &= ~del_data.set.ns;
+	nm_data->wild.qname &= ~del_data.wild.qname;
+	nm_data->wild.ns &= ~del_data.wild.ns;
+
+	if (nm_data->set.qname == 0 && nm_data->set.ns == 0 &&
+	    nm_data->wild.qname == 0 && nm_data->wild.ns == 0) {
+		result = dns_rbt_deletenode(rpzs->rbt, nmnode, ISC_FALSE);
+		if (result != ISC_R_SUCCESS) {
+			/*
+			 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+			 */
+			dns_name_format(src_name, namebuf, sizeof(namebuf));
+			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+				      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+				      "rpz del_name(%s) node delete failed: %s",
+				      namebuf, isc_result_totext(result));
+		}
+	}
+
+	adj_trigger_cnt(rpzs, rpz_num, rpz_type, NULL, 0, ISC_FALSE);
+}
+
 /*
- * Caller must hold tree lock.
- * Return  ISC_R_NOTFOUND
- *	or ISC_R_SUCCESS and the found entry's canonical and search names
- *	    and its prefix length
+ * Remove an IP address from the radix tree or a name from the summary database.
  */
-isc_result_t
-dns_rpz_cidr_find(dns_rpz_cidr_t *cidr, const isc_netaddr_t *netaddr,
-		  dns_rpz_type_t type, dns_name_t *canon_name,
-		  dns_name_t *search_name, dns_rpz_cidr_bits_t *prefix)
+void
+dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num,
+	       dns_name_t *src_name) {
+	dns_rpz_zone_t *rpz;
+	dns_rpz_type_t rpz_type;
+
+	REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones);
+	rpz = rpzs->zones[rpz_num];
+	REQUIRE(rpz != NULL);
+
+	rpz_type = type_from_name(rpz, src_name);
+
+	LOCK(&rpzs->maint_lock);
+	LOCK(&rpzs->search_lock);
+
+	switch (rpz_type) {
+	case DNS_RPZ_TYPE_QNAME:
+	case DNS_RPZ_TYPE_NSDNAME:
+		del_name(rpzs, rpz_num, rpz_type, src_name);
+		break;
+	case DNS_RPZ_TYPE_CLIENT_IP:
+	case DNS_RPZ_TYPE_IP:
+	case DNS_RPZ_TYPE_NSIP:
+		del_cidr(rpzs, rpz_num, rpz_type, src_name);
+		break;
+	case DNS_RPZ_TYPE_BAD:
+		break;
+	}
+
+	UNLOCK(&rpzs->search_lock);
+	UNLOCK(&rpzs->maint_lock);
+}
+
+/*
+ * Search the summary radix tree to get a relative owner name in a
+ * policy zone relevant to a triggering IP address.
+ *	rpz_type and zbits limit the search for IP address netaddr
+ *	return the policy zone's number or DNS_RPZ_INVALID_NUM
+ *	ip_name is the relative owner name found and
+ *	*prefixp is its prefix length.
+ */
+dns_rpz_num_t
+dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+		dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr,
+		dns_name_t *ip_name, dns_rpz_prefix_t *prefixp)
 {
 	dns_rpz_cidr_key_t tgt_ip;
-	isc_result_t result;
+	dns_rpz_addr_zbits_t tgt_set;
 	dns_rpz_cidr_node_t *found;
+	isc_result_t result;
+	dns_rpz_num_t rpz_num;
 	int i;
 
 	/*
@@ -1107,29 +1926,163 @@
 		tgt_ip.w[1] = 0;
 		tgt_ip.w[2] = ADDR_V4MAPPED;
 		tgt_ip.w[3] = ntohl(netaddr->type.in.s_addr);
+		switch (rpz_type) {
+		case DNS_RPZ_TYPE_CLIENT_IP:
+			zbits &= rpzs->have.client_ipv4;
+			break;
+		case DNS_RPZ_TYPE_IP:
+			zbits &= rpzs->have.ipv4;
+			break;
+		case DNS_RPZ_TYPE_NSIP:
+			zbits &= rpzs->have.nsipv4;
+			break;
+		default:
+			INSIST(0);
+			break;
+		}
 	} else if (netaddr->family == AF_INET6) {
 		dns_rpz_cidr_key_t src_ip6;
 
 		/*
 		 * Given the int aligned struct in_addr member of netaddr->type
 		 * one could cast netaddr->type.in6 to dns_rpz_cidr_key_t *,
-		 * but there are objections.
+		 * but some people object.
 		 */
-		memmove(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w));
+		memcpy(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w));
 		for (i = 0; i < 4; i++) {
 			tgt_ip.w[i] = ntohl(src_ip6.w[i]);
 		}
+		switch (rpz_type) {
+		case DNS_RPZ_TYPE_CLIENT_IP:
+			zbits &= rpzs->have.client_ipv6;
+			break;
+		case DNS_RPZ_TYPE_IP:
+			zbits &= rpzs->have.ipv6;
+			break;
+		case DNS_RPZ_TYPE_NSIP:
+			zbits &= rpzs->have.nsipv6;
+			break;
+		default:
+			INSIST(0);
+			break;
+		}
 	} else {
-		return (ISC_R_NOTFOUND);
+		return (DNS_RPZ_INVALID_NUM);
 	}
 
-	result = search(cidr, &tgt_ip, 128, type, ISC_FALSE, &found);
-	if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH)
-		return (result);
+	if (zbits == 0)
+		return (DNS_RPZ_INVALID_NUM);
+	make_addr_set(&tgt_set, zbits, rpz_type);
+
+	LOCK(&rpzs->search_lock);
+	result = search(rpzs, &tgt_ip, 128, &tgt_set, ISC_FALSE, &found);
+	if (result == ISC_R_NOTFOUND) {
+		/*
+		 * There are no eligible zones for this IP address.
+		 */
+		UNLOCK(&rpzs->search_lock);
+		return (DNS_RPZ_INVALID_NUM);
+	}
 
-	*prefix = found->bits;
-	return (ip2name(cidr, &found->ip, found->bits, type,
-			canon_name, search_name));
+	/*
+	 * Construct the trigger name for the longest matching trigger
+	 * in the first eligible zone with a match.
+	 */
+	*prefixp = found->prefix;
+	switch (rpz_type) {
+	case DNS_RPZ_TYPE_CLIENT_IP:
+		rpz_num = zbit_to_num(found->set.client_ip & tgt_set.client_ip);
+		break;
+	case DNS_RPZ_TYPE_IP:
+		rpz_num = zbit_to_num(found->set.ip & tgt_set.ip);
+		break;
+	case DNS_RPZ_TYPE_NSIP:
+		rpz_num = zbit_to_num(found->set.nsip & tgt_set.nsip);
+		break;
+	default:
+		INSIST(0);
+		break;
+	}
+	result = ip2name(&found->ip, found->prefix, dns_rootname, ip_name);
+	UNLOCK(&rpzs->search_lock);
+	if (result != ISC_R_SUCCESS) {
+		/*
+		 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+		 */
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+			      "rpz ip2name() failed: %s",
+			      isc_result_totext(result));
+		return (DNS_RPZ_INVALID_NUM);
+	}
+	return (rpz_num);
+}
+
+/*
+ * Search the summary radix tree for policy zones with triggers matching
+ * a name.
+ */
+dns_rpz_zbits_t
+dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type,
+		  dns_rpz_zbits_t zbits, dns_name_t *trig_name)
+{
+	char namebuf[DNS_NAME_FORMATSIZE];
+	dns_rbtnode_t *nmnode;
+	const dns_rpz_nm_data_t *nm_data;
+	dns_rpz_zbits_t found_zbits;
+	isc_result_t result;
+
+	if (zbits == 0)
+		return (0);
+
+	found_zbits = 0;
+
+	LOCK(&rpzs->search_lock);
+
+	nmnode = NULL;
+	result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, NULL,
+				  DNS_RBTFIND_EMPTYDATA, NULL, NULL);
+	switch (result) {
+	case ISC_R_SUCCESS:
+		nm_data = nmnode->data;
+		if (nm_data != NULL) {
+			if (rpz_type == DNS_RPZ_TYPE_QNAME)
+				found_zbits = nm_data->set.qname;
+			else
+				found_zbits = nm_data->set.ns;
+		}
+		nmnode = nmnode->parent;
+		/* fall thru */
+	case DNS_R_PARTIALMATCH:
+		while (nmnode != NULL) {
+			nm_data = nmnode->data;
+			if (nm_data != NULL) {
+				if (rpz_type == DNS_RPZ_TYPE_QNAME)
+					found_zbits |= nm_data->wild.qname;
+				else
+					found_zbits |= nm_data->wild.ns;
+			}
+			nmnode = nmnode->parent;
+		}
+		break;
+
+	case ISC_R_NOTFOUND:
+		break;
+
+	default:
+		/*
+		 * bin/tests/system/rpz/tests.sh looks for "rpz.*failed".
+		 */
+		dns_name_format(trig_name, namebuf, sizeof(namebuf));
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ,
+			      DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL,
+			      "dns_rpz_find_name(%s) failed: %s",
+			      namebuf, isc_result_totext(result));
+		break;
+	}
+
+	UNLOCK(&rpzs->search_lock);
+	return (zbits & found_zbits);
 }
 
 /*
@@ -1144,10 +2097,10 @@
 	isc_result_t result;
 
 	result = dns_rdataset_first(rdataset);
-	RUNTIME_CHECK(result == ISC_R_SUCCESS);
+	INSIST(result == ISC_R_SUCCESS);
 	dns_rdataset_current(rdataset, &rdata);
 	result = dns_rdata_tostruct(&rdata, &cname, NULL);
-	RUNTIME_CHECK(result == ISC_R_SUCCESS);
+	INSIST(result == ISC_R_SUCCESS);
 	dns_rdata_reset(&rdata);
 
 	/*
@@ -1174,7 +2127,19 @@
 	}
 
 	/*
-	 * CNAME PASSTHRU.origin means "do not rewrite.
+	 * CNAME rpz-tcp-only. means "send truncated UDP responses."
+	 */
+	if (dns_name_equal(&cname.cname, &rpz->tcp_only))
+		return (DNS_RPZ_POLICY_TCP_ONLY);
+
+	/*
+	 * CNAME rpz-drop. means "do not respond."
+	 */
+	if (dns_name_equal(&cname.cname, &rpz->drop))
+		return (DNS_RPZ_POLICY_DROP);
+
+	/*
+	 * CNAME rpz-passthru. means "do not rewrite."
 	 */
 	if (dns_name_equal(&cname.cname, &rpz->passthru))
 		return (DNS_RPZ_POLICY_PASSTHRU);
diff -r -u lib/dns/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
@@ -197,9 +197,7 @@
 	view->maxbits = 0;
 	view->v4_aaaa = dns_v4_aaaa_ok;
 	view->v4_aaaa_acl = NULL;
-	ISC_LIST_INIT(view->rpz_zones);
-	view->rpz_recursive_only = ISC_TRUE;
-	view->rpz_break_dnssec = ISC_FALSE;
+	view->rpzs = NULL;
 	dns_fixedname_init(&view->dlv_fixed);
 	view->managed_keys = NULL;
 	view->redirect = NULL;
@@ -336,7 +334,8 @@
 			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);
 #ifdef USE_RRL
 	dns_rrl_view_destroy(view);
 #else /* USE_RRL */
diff -r -u lib/dns/xfrin.c-orig lib/dns/xfrin.c
--- lib/dns/xfrin.c-orig	2004-01-01 00:00:00.000000000 +0000
+++ lib/dns/xfrin.c	2004-01-01 00:00:00.000000000 +0000
@@ -279,7 +279,7 @@
 			       0, NULL, /* XXX guess */
 			       dbp);
 	if (result == ISC_R_SUCCESS)
-		result = dns_zone_rpz_enable_db(xfr->zone, *dbp);
+		dns_zone_rpz_enable_db(xfr->zone, *dbp);
 	return (result);
 }
 
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
@@ -357,9 +357,10 @@
 	isc_boolean_t           added;
 
 	/*%
-	 * whether this is a response policy zone
+	 * response policy data to be relayed to the database
 	 */
-	isc_boolean_t           is_rpz;
+	dns_rpz_zones_t		*rpzs;
+	dns_rpz_num_t		rpz_num;
 
 	/*%
 	 * Serial number update method.
@@ -940,7 +941,8 @@
 	zone->nodes = 100;
 	zone->privatetype = (dns_rdatatype_t)0xffffU;
 	zone->added = ISC_FALSE;
-	zone->is_rpz = ISC_FALSE;
+	zone->rpzs = NULL;
+	zone->rpz_num = DNS_RPZ_INVALID_NUM;
 	ISC_LIST_INIT(zone->forwards);
 	zone->raw = NULL;
 	zone->secure = NULL;
@@ -1043,6 +1045,13 @@
 		zone_detachdb(zone);
 	if (zone->acache != NULL)
 		dns_acache_detach(&zone->acache);
+#ifdef BIND9
+	if (zone->rpzs != NULL) {
+		REQUIRE(zone->rpz_num < zone->rpzs->p.num_zones);
+		dns_rpz_detach_rpzs(&zone->rpzs);
+		zone->rpz_num = DNS_RPZ_INVALID_NUM;
+	}
+#endif
 	zone_freedbargs(zone);
 	RUNTIME_CHECK(dns_zone_setmasterswithkeys(zone, NULL, NULL, 0)
 		      == ISC_R_SUCCESS);
@@ -1535,7 +1544,9 @@
  * Set the response policy index and information for a zone.
  */
 isc_result_t
-dns_zone_rpz_enable(dns_zone_t *zone) {
+dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs,
+		    dns_rpz_num_t rpz_num)
+{
 	/*
 	 * Only RBTDB zones can be used for response policy zones,
 	 * because only they have the code to load the create the summary data.
@@ -1546,26 +1557,37 @@
 	    strcmp(zone->db_argv[0], "rbt64") != 0)
 		return (ISC_R_NOTIMPLEMENTED);
 
-	zone->is_rpz = ISC_TRUE;
+	/*
+	 * This must happen only once or be redundant.
+	 */
+	LOCK_ZONE(zone);
+	if (zone->rpzs != NULL) {
+		REQUIRE(zone->rpzs == rpzs && zone->rpz_num == rpz_num);
+	} else {
+		REQUIRE(zone->rpz_num == DNS_RPZ_INVALID_NUM);
+		dns_rpz_attach_rpzs(rpzs, &zone->rpzs);
+		zone->rpz_num = rpz_num;
+	}
+	rpzs->defined |= DNS_RPZ_ZBIT(rpz_num);
+	UNLOCK_ZONE(zone);
 
 	return (ISC_R_SUCCESS);
 }
 
-isc_boolean_t
-dns_zone_get_rpz(dns_zone_t *zone) {
-	return (zone->is_rpz);
+dns_rpz_num_t
+dns_zone_get_rpz_num(dns_zone_t *zone) {
+	return (zone->rpz_num);
 }
 
 /*
  * If a zone is a response policy zone, mark its new database.
  */
-isc_result_t
+void
 dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db) {
-#ifdef BIND9
-	if (zone->is_rpz)
-		return (dns_db_rpz_enabled(db, NULL));
-#endif
-	return (ISC_R_SUCCESS);
+	if (zone->rpz_num != DNS_RPZ_INVALID_NUM) {
+		REQUIRE(zone->rpzs != NULL);
+		dns_db_rpz_attach(db, zone->rpzs, zone->rpz_num);
+	}
 }
 
 static isc_result_t
@@ -2025,9 +2047,7 @@
 	isc_result_t tresult;
 	unsigned int options;
 
-	result = dns_zone_rpz_enable_db(zone, db);
-	if (result != ISC_R_SUCCESS)
-		return (result);
+	dns_zone_rpz_enable_db(zone, db);
 	options = get_master_options(zone);
 	if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MANYERRORS))
 		options |= DNS_MASTER_MANYERRORS;
@@ -4210,6 +4230,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,
@@ -13455,6 +13480,12 @@
 	if (inline_raw(zone))
 		REQUIRE(LOCKED_ZONE(zone->secure));
 
+#ifdef BIND9
+	result = dns_db_rpz_ready(db);
+	if (result != ISC_R_SUCCESS)
+		return (result);
+#endif
+
 	result = zone_get_from_db(zone, db, &nscount, &soacount,
 				  NULL, NULL, NULL, NULL, NULL, NULL);
 	if (result == ISC_R_SUCCESS) {
diff -r -u lib/isccfg/namedconf.c-orig lib/isccfg/namedconf.c
--- lib/isccfg/namedconf.c-orig	2004-01-01 00:00:00.000000000 +0000
+++ lib/isccfg/namedconf.c	2004-01-01 00:00:00.000000000 +0000
@@ -1054,11 +1054,12 @@
 
 /*%
  *  response-policy {
- *	zone <string> [ policy (given|disabled|passthru|
+ *	zone <string> [ policy (given|disabled|passthru|drop|tcp-only|
  *					nxdomain|nodata|cname <domain> ) ]
  *		      [ recursive-only yes|no ] [ max-policy-ttl number ] ;
  *  } [ recursive-only yes|no ] [ max-policy-ttl number ] ;
- *	 [ break-dnssec yes|no ] [ min-ns-dots number ] ;
+ *	 [ break-dnssec yes|no ] [ min-ns-dots number ]
+ *	 [ qname-wait-recurse yes|no ]
  */
 
 static void
@@ -1083,7 +1084,7 @@
 
 /*
  * Parse
- *	given|disabled|passthru|nxdomain|nodata|cname <domain>
+ *	given|disabled|passthru|drop|tcp-only|nxdomain|nodata|cname <domain>
  */
 static isc_result_t
 cfg_parse_rpz_policy(cfg_parser_t *pctx, const cfg_type_t *type,
@@ -1214,9 +1215,12 @@
 	doc_keyvalue, &cfg_rep_string,
 	&zone_kw
 };
+/*
+ * "no-op" is an obsolete equivalent of "passthru".
+ */
 static const char *rpz_policies[] = {
-	"given", "disabled", "passthru", "no-op", "nxdomain", "nodata",
-	"cname", NULL
+	"given", "disabled", "passthru", "no-op", "drop", "tcp-only",
+	"nxdomain", "nodata", "cname", NULL
 };
 static cfg_type_t cfg_type_rpz_policy_name = {
 	"policy name", cfg_parse_enum, cfg_print_ustring,
@@ -1261,6 +1265,7 @@
 	{ "break-dnssec", &cfg_type_boolean, 0 },
 	{ "max-policy-ttl", &cfg_type_uint32, 0 },
 	{ "min-ns-dots", &cfg_type_uint32, 0 },
+	{ "qname-wait-recurse", &cfg_type_boolean, 0 },
 	{ NULL, NULL, 0 }
 };
 static cfg_type_t cfg_type_rpz = {
diff -r -u version-orig version
--- version-orig	2004-01-01 00:00:00.000000000 +0000
+++ version	2004-01-01 00:00:00.000000000 +0000
@@ -7,7 +7,7 @@
 DESCRIPTION="(Extended Support Version)"
 MAJORVER=9
 MINORVER=9
-PATCHVER=5
+PATCHVER=5-rpz2+rl.14038.05
 RELEASETYPE=-P
 RELEASEVER=1
 EXTENSIONS=