From: =?utf-8?b?T25kxZllaiBTdXLDvQ==?= <ondrej@isc.org>
Date: Fri, 1 Mar 2024 08:26:07 +0100
Subject: Add a limit to the number of RRs in RRSets

Previously, the number of RRs in the RRSets were internally unlimited.
As the data structure that holds the RRs is just a linked list, and
there are places where we just walk through all of the RRs, adding an
RRSet with huge number of RRs inside would slow down processing of said
RRSets.

Add a configurable limit to cap the number of the RRs in a single RRSet.
This is enforced at the database (rbtdb, qpzone, qpcache) level and
configured with new max-records-per-type configuration option that can
be configured globally, per-view and per-zone.

(cherry picked from commit 3fbd21f69a1bcbd26c4c00920e7b0a419e8762fc)
---
 bin/named/config.c                 |  1 +
 bin/named/server.c                 |  9 +++++
 bin/named/zoneconf.c               |  8 +++++
 bin/tests/system/dyndb/driver/db.c |  3 +-
 doc/arm/reference.rst              | 12 +++++++
 doc/misc/master.zoneopt            |  2 ++
 doc/misc/mirror.zoneopt            |  2 ++
 doc/misc/options                   | 44 ++++++++++++++---------
 doc/misc/options.active            | 44 ++++++++++++++---------
 doc/misc/redirect.zoneopt          |  2 ++
 doc/misc/slave.zoneopt             |  2 ++
 doc/misc/static-stub.zoneopt       |  2 ++
 doc/misc/stub.zoneopt              |  2 ++
 lib/dns/cache.c                    | 13 +++++++
 lib/dns/db.c                       |  9 +++++
 lib/dns/dnsrps.c                   |  3 +-
 lib/dns/ecdb.c                     |  8 +++--
 lib/dns/include/dns/cache.h        |  6 ++++
 lib/dns/include/dns/db.h           |  8 +++++
 lib/dns/include/dns/rdataslab.h    |  6 ++--
 lib/dns/include/dns/view.h         |  7 ++++
 lib/dns/include/dns/zone.h         | 26 ++++++++++++++
 lib/dns/rbtdb.c                    | 45 +++++++++++++++--------
 lib/dns/rdataslab.c                | 10 +++---
 lib/dns/sdb.c                      |  3 +-
 lib/dns/sdlz.c                     |  3 +-
 lib/dns/view.c                     | 11 ++++++
 lib/dns/xfrin.c                    | 24 +++----------
 lib/dns/zone.c                     | 74 ++++++++++++++++++++++++++++----------
 lib/isccfg/namedconf.c             |  3 ++
 30 files changed, 296 insertions(+), 96 deletions(-)

diff --git a/bin/named/config.c b/bin/named/config.c
index 522798a..72dec1a 100644
--- a/bin/named/config.c
+++ b/bin/named/config.c
@@ -226,6 +226,7 @@ options {\n\
 	ixfr-from-differences false;\n\
 	max-journal-size default;\n\
 	max-records 0;\n\
+	max-records-per-type 100;\n\
 	max-refresh-time 2419200; /* 4 weeks */\n\
 	max-retry-time 1209600; /* 2 weeks */\n\
 	max-transfer-idle-in 60;\n\
diff --git a/bin/named/server.c b/bin/named/server.c
index 098e21d..f550814 100644
--- a/bin/named/server.c
+++ b/bin/named/server.c
@@ -5452,6 +5452,15 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
 	dns_resolver_setclientsperquery(view->resolver, cfg_obj_asuint32(obj),
 					max_clients_per_query);
 
+	/*
+	 * This is used for the cache and also as a default value
+	 * for zone databases.
+	 */
+	obj = NULL;
+	result = named_config_get(maps, "max-records-per-type", &obj);
+	INSIST(result == ISC_R_SUCCESS);
+	dns_view_setmaxrrperset(view, cfg_obj_asuint32(obj));
+
 	obj = NULL;
 	result = named_config_get(maps, "max-recursion-depth", &obj);
 	INSIST(result == ISC_R_SUCCESS);
diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c
index 2e614aa..81f6879 100644
--- a/bin/named/zoneconf.c
+++ b/bin/named/zoneconf.c
@@ -1072,6 +1072,14 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
 		dns_zone_setmaxrecords(zone, 0);
 	}
 
+	obj = NULL;
+	result = named_config_get(maps, "max-records-per-type", &obj);
+	INSIST(result == ISC_R_SUCCESS && obj != NULL);
+	dns_zone_setmaxrrperset(mayberaw, cfg_obj_asuint32(obj));
+	if (zone != mayberaw) {
+		dns_zone_setmaxrrperset(zone, 0);
+	}
+
 	if (raw != NULL && filename != NULL) {
 #define SIGNED ".signed"
 		size_t signedlen = strlen(filename) + sizeof(SIGNED);
diff --git a/bin/tests/system/dyndb/driver/db.c b/bin/tests/system/dyndb/driver/db.c
index 5f01185..727268f 100644
--- a/bin/tests/system/dyndb/driver/db.c
+++ b/bin/tests/system/dyndb/driver/db.c
@@ -622,7 +622,8 @@ static dns_dbmethods_t sampledb_methods = {
 	NULL, /* setservestalerefresh */
 	NULL, /* getservestalerefresh */
 	NULL, /* setgluecachestats */
-	NULL  /* adjusthashsize */
+	NULL, /* adjusthashsize */
+	NULL  /* setmaxrrperset */
 };
 
 /* Auxiliary driver functions. */
diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst
index f982e0a..fbd8edb 100644
--- a/doc/arm/reference.rst
+++ b/doc/arm/reference.rst
@@ -2945,6 +2945,18 @@ system.
    This sets the maximum number of records permitted in a zone. The default is
    zero, which means the maximum is unlimited.
 
+``max-records-per-type``
+   This sets the maximum number of resource records that can be stored
+   in an RRset in a database. When configured in ``options``
+   or ``view``, it controls the cache database; it also sets
+   the default value for zone databases, which can be overridden by setting
+   it at the ``zone`` level.
+
+   If set to a positive value, any attempt to cache or to add to a zone
+   an RRset with more than the specified number of records will result in
+   a failure.  If set to 0, there is no cap on RRset size.  The default is
+   100.
+
 ``recursive-clients``
    This sets the maximum number (a "hard quota") of simultaneous recursive lookups
    the server performs on behalf of clients. The default is
diff --git a/doc/misc/master.zoneopt b/doc/misc/master.zoneopt
index 953e3a2..b2fc2c8 100644
--- a/doc/misc/master.zoneopt
+++ b/doc/misc/master.zoneopt
@@ -38,8 +38,10 @@ zone <string> [ <class> ] {
 	max-ixfr-ratio ( unlimited | <percentage> );
 	max-journal-size ( default | unlimited | <sizeval> );
 	max-records <integer>;
+	max-records-per-type <integer>;
 	max-transfer-idle-out <integer>;
 	max-transfer-time-out <integer>;
+	max-types-per-name <integer>;
 	max-zone-ttl ( unlimited | <duration> );
 	notify ( explicit | master-only | primary-only | <boolean> );
 	notify-delay <integer>;
diff --git a/doc/misc/mirror.zoneopt b/doc/misc/mirror.zoneopt
index 3d45a3d..701edce 100644
--- a/doc/misc/mirror.zoneopt
+++ b/doc/misc/mirror.zoneopt
@@ -19,12 +19,14 @@ zone <string> [ <class> ] {
 	max-ixfr-ratio ( unlimited | <percentage> );
 	max-journal-size ( default | unlimited | <sizeval> );
 	max-records <integer>;
+	max-records-per-type <integer>;
 	max-refresh-time <integer>;
 	max-retry-time <integer>;
 	max-transfer-idle-in <integer>;
 	max-transfer-idle-out <integer>;
 	max-transfer-time-in <integer>;
 	max-transfer-time-out <integer>;
+	max-types-per-name <integer>;
 	min-refresh-time <integer>;
 	min-retry-time <integer>;
 	multi-master <boolean>;
diff --git a/doc/misc/options b/doc/misc/options
index 0dbcf10..93eb156 100644
--- a/doc/misc/options
+++ b/doc/misc/options
@@ -170,13 +170,16 @@ options {
         dnssec-secure-to-insecure <boolean>;
         dnssec-update-mode ( maintain | no-resign );
         dnssec-validation ( yes | no | auto );
-        dnstap { ( all | auth | client | forwarder | resolver | update ) [
-            ( query | response ) ]; ... };
-        dnstap-identity ( <quoted_string> | none | hostname );
-        dnstap-output ( file | unix ) <quoted_string> [ size ( unlimited |
-            <size> ) ] [ versions ( unlimited | <integer> ) ] [ suffix (
-            increment | timestamp ) ];
-        dnstap-version ( <quoted_string> | none );
+        dnstap { ( all | auth | client | forwarder |
+            resolver | update ) [ ( query | response ) ];
+            ... }; // not configured
+        dnstap-identity ( <quoted_string> | none |
+            hostname ); // not configured
+        dnstap-output ( file | unix ) <quoted_string> [
+            size ( unlimited | <size> ) ] [ versions (
+            unlimited | <integer> ) ] [ suffix ( increment
+            | timestamp ) ]; // not configured
+        dnstap-version ( <quoted_string> | none ); // not configured
         dscp <integer>; // deprecated
         dual-stack-servers [ port <integer> ] { ( <quoted_string> [ port
             <integer> ] [ dscp <integer> ] | <ipv4_address> [ port
@@ -200,13 +203,13 @@ options {
         forward ( first | only );
         forwarders [ port <integer> ] [ dscp <integer> ] { ( <ipv4_address>
             | <ipv6_address> ) [ port <integer> ] [ dscp <integer> ]; ... };
-        fstrm-set-buffer-hint <integer>;
-        fstrm-set-flush-timeout <integer>;
-        fstrm-set-input-queue-size <integer>;
-        fstrm-set-output-notify-threshold <integer>;
-        fstrm-set-output-queue-model ( mpsc | spsc );
-        fstrm-set-output-queue-size <integer>;
-        fstrm-set-reopen-interval <duration>;
+        fstrm-set-buffer-hint <integer>; // not configured
+        fstrm-set-flush-timeout <integer>; // not configured
+        fstrm-set-input-queue-size <integer>; // not configured
+        fstrm-set-output-notify-threshold <integer>; // not configured
+        fstrm-set-output-queue-model ( mpsc | spsc ); // not configured
+        fstrm-set-output-queue-size <integer>; // not configured
+        fstrm-set-reopen-interval <duration>; // not configured
         geoip-directory ( <quoted_string> | none );
         geoip-use-ecs <boolean>; // obsolete
         glue-cache <boolean>;
@@ -243,6 +246,7 @@ options {
         max-journal-size ( default | unlimited | <sizeval> );
         max-ncache-ttl <duration>;
         max-records <integer>;
+        max-records-per-type <integer>;
         max-recursion-depth <integer>;
         max-recursion-queries <integer>;
         max-refresh-time <integer>;
@@ -253,6 +257,7 @@ options {
         max-transfer-idle-out <integer>;
         max-transfer-time-in <integer>;
         max-transfer-time-out <integer>;
+        max-types-per-name <integer>;
         max-udp-size <integer>;
         max-zone-ttl ( unlimited | <duration> );
         memstatistics <boolean>;
@@ -570,8 +575,9 @@ view <string> [ <class> ] {
         dnssec-secure-to-insecure <boolean>;
         dnssec-update-mode ( maintain | no-resign );
         dnssec-validation ( yes | no | auto );
-        dnstap { ( all | auth | client | forwarder | resolver | update ) [
-            ( query | response ) ]; ... };
+        dnstap { ( all | auth | client | forwarder |
+            resolver | update ) [ ( query | response ) ];
+            ... }; // not configured
         dual-stack-servers [ port <integer> ] { ( <quoted_string> [ port
             <integer> ] [ dscp <integer> ] | <ipv4_address> [ port
             <integer> ] [ dscp <integer> ] | <ipv6_address> [ port
@@ -623,6 +629,7 @@ view <string> [ <class> ] {
         max-journal-size ( default | unlimited | <sizeval> );
         max-ncache-ttl <duration>;
         max-records <integer>;
+        max-records-per-type <integer>;
         max-recursion-depth <integer>;
         max-recursion-queries <integer>;
         max-refresh-time <integer>;
@@ -632,6 +639,7 @@ view <string> [ <class> ] {
         max-transfer-idle-out <integer>;
         max-transfer-time-in <integer>;
         max-transfer-time-out <integer>;
+        max-types-per-name <integer>;
         max-udp-size <integer>;
         max-zone-ttl ( unlimited | <duration> );
         message-compression <boolean>;
@@ -855,12 +863,14 @@ view <string> [ <class> ] {
                 max-ixfr-ratio ( unlimited | <percentage> );
                 max-journal-size ( default | unlimited | <sizeval> );
                 max-records <integer>;
+                max-records-per-type <integer>;
                 max-refresh-time <integer>;
                 max-retry-time <integer>;
                 max-transfer-idle-in <integer>;
                 max-transfer-idle-out <integer>;
                 max-transfer-time-in <integer>;
                 max-transfer-time-out <integer>;
+                max-types-per-name <integer>;
                 max-zone-ttl ( unlimited | <duration> );
                 min-refresh-time <integer>;
                 min-retry-time <integer>;
@@ -972,12 +982,14 @@ zone <string> [ <class> ] {
         max-ixfr-ratio ( unlimited | <percentage> );
         max-journal-size ( default | unlimited | <sizeval> );
         max-records <integer>;
+        max-records-per-type <integer>;
         max-refresh-time <integer>;
         max-retry-time <integer>;
         max-transfer-idle-in <integer>;
         max-transfer-idle-out <integer>;
         max-transfer-time-in <integer>;
         max-transfer-time-out <integer>;
+        max-types-per-name <integer>;
         max-zone-ttl ( unlimited | <duration> );
         min-refresh-time <integer>;
         min-retry-time <integer>;
diff --git a/doc/misc/options.active b/doc/misc/options.active
index eb75a86..1b748d9 100644
--- a/doc/misc/options.active
+++ b/doc/misc/options.active
@@ -156,13 +156,16 @@ options {
         dnssec-secure-to-insecure <boolean>;
         dnssec-update-mode ( maintain | no-resign );
         dnssec-validation ( yes | no | auto );
-        dnstap { ( all | auth | client | forwarder | resolver | update ) [
-            ( query | response ) ]; ... };
-        dnstap-identity ( <quoted_string> | none | hostname );
-        dnstap-output ( file | unix ) <quoted_string> [ size ( unlimited |
-            <size> ) ] [ versions ( unlimited | <integer> ) ] [ suffix (
-            increment | timestamp ) ];
-        dnstap-version ( <quoted_string> | none );
+        dnstap { ( all | auth | client | forwarder |
+            resolver | update ) [ ( query | response ) ];
+            ... }; // not configured
+        dnstap-identity ( <quoted_string> | none |
+            hostname ); // not configured
+        dnstap-output ( file | unix ) <quoted_string> [
+            size ( unlimited | <size> ) ] [ versions (
+            unlimited | <integer> ) ] [ suffix ( increment
+            | timestamp ) ]; // not configured
+        dnstap-version ( <quoted_string> | none ); // not configured
         dscp <integer>; // deprecated
         dual-stack-servers [ port <integer> ] { ( <quoted_string> [ port
             <integer> ] [ dscp <integer> ] | <ipv4_address> [ port
@@ -181,13 +184,13 @@ options {
         forward ( first | only );
         forwarders [ port <integer> ] [ dscp <integer> ] { ( <ipv4_address>
             | <ipv6_address> ) [ port <integer> ] [ dscp <integer> ]; ... };
-        fstrm-set-buffer-hint <integer>;
-        fstrm-set-flush-timeout <integer>;
-        fstrm-set-input-queue-size <integer>;
-        fstrm-set-output-notify-threshold <integer>;
-        fstrm-set-output-queue-model ( mpsc | spsc );
-        fstrm-set-output-queue-size <integer>;
-        fstrm-set-reopen-interval <duration>;
+        fstrm-set-buffer-hint <integer>; // not configured
+        fstrm-set-flush-timeout <integer>; // not configured
+        fstrm-set-input-queue-size <integer>; // not configured
+        fstrm-set-output-notify-threshold <integer>; // not configured
+        fstrm-set-output-queue-model ( mpsc | spsc ); // not configured
+        fstrm-set-output-queue-size <integer>; // not configured
+        fstrm-set-reopen-interval <duration>; // not configured
         geoip-directory ( <quoted_string> | none );
         glue-cache <boolean>;
         heartbeat-interval <integer>;
@@ -217,6 +220,7 @@ options {
         max-journal-size ( default | unlimited | <sizeval> );
         max-ncache-ttl <duration>;
         max-records <integer>;
+        max-records-per-type <integer>;
         max-recursion-depth <integer>;
         max-recursion-queries <integer>;
         max-refresh-time <integer>;
@@ -227,6 +231,7 @@ options {
         max-transfer-idle-out <integer>;
         max-transfer-time-in <integer>;
         max-transfer-time-out <integer>;
+        max-types-per-name <integer>;
         max-udp-size <integer>;
         max-zone-ttl ( unlimited | <duration> );
         memstatistics <boolean>;
@@ -514,8 +519,9 @@ view <string> [ <class> ] {
         dnssec-secure-to-insecure <boolean>;
         dnssec-update-mode ( maintain | no-resign );
         dnssec-validation ( yes | no | auto );
-        dnstap { ( all | auth | client | forwarder | resolver | update ) [
-            ( query | response ) ]; ... };
+        dnstap { ( all | auth | client | forwarder |
+            resolver | update ) [ ( query | response ) ];
+            ... }; // not configured
         dual-stack-servers [ port <integer> ] { ( <quoted_string> [ port
             <integer> ] [ dscp <integer> ] | <ipv4_address> [ port
             <integer> ] [ dscp <integer> ] | <ipv6_address> [ port
@@ -560,6 +566,7 @@ view <string> [ <class> ] {
         max-journal-size ( default | unlimited | <sizeval> );
         max-ncache-ttl <duration>;
         max-records <integer>;
+        max-records-per-type <integer>;
         max-recursion-depth <integer>;
         max-recursion-queries <integer>;
         max-refresh-time <integer>;
@@ -569,6 +576,7 @@ view <string> [ <class> ] {
         max-transfer-idle-out <integer>;
         max-transfer-time-in <integer>;
         max-transfer-time-out <integer>;
+        max-types-per-name <integer>;
         max-udp-size <integer>;
         max-zone-ttl ( unlimited | <duration> );
         message-compression <boolean>;
@@ -775,12 +783,14 @@ view <string> [ <class> ] {
                 max-ixfr-ratio ( unlimited | <percentage> );
                 max-journal-size ( default | unlimited | <sizeval> );
                 max-records <integer>;
+                max-records-per-type <integer>;
                 max-refresh-time <integer>;
                 max-retry-time <integer>;
                 max-transfer-idle-in <integer>;
                 max-transfer-idle-out <integer>;
                 max-transfer-time-in <integer>;
                 max-transfer-time-out <integer>;
+                max-types-per-name <integer>;
                 max-zone-ttl ( unlimited | <duration> );
                 min-refresh-time <integer>;
                 min-retry-time <integer>;
@@ -885,12 +895,14 @@ zone <string> [ <class> ] {
         max-ixfr-ratio ( unlimited | <percentage> );
         max-journal-size ( default | unlimited | <sizeval> );
         max-records <integer>;
+        max-records-per-type <integer>;
         max-refresh-time <integer>;
         max-retry-time <integer>;
         max-transfer-idle-in <integer>;
         max-transfer-idle-out <integer>;
         max-transfer-time-in <integer>;
         max-transfer-time-out <integer>;
+        max-types-per-name <integer>;
         max-zone-ttl ( unlimited | <duration> );
         min-refresh-time <integer>;
         min-retry-time <integer>;
diff --git a/doc/misc/redirect.zoneopt b/doc/misc/redirect.zoneopt
index 6a5ef66..c4ab59d 100644
--- a/doc/misc/redirect.zoneopt
+++ b/doc/misc/redirect.zoneopt
@@ -8,6 +8,8 @@ zone <string> [ <class> ] {
 	masterfile-style ( full | relative );
 	masters [ port <integer> ] [ dscp <integer> ] { ( <remote-servers> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ]; ... };
 	max-records <integer>;
+	max-records-per-type <integer>;
+	max-types-per-name <integer>;
 	max-zone-ttl ( unlimited | <duration> );
 	primaries [ port <integer> ] [ dscp <integer> ] { ( <remote-servers> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ]; ... };
 	zone-statistics ( full | terse | none | <boolean> );
diff --git a/doc/misc/slave.zoneopt b/doc/misc/slave.zoneopt
index c46202d..52ce7cf 100644
--- a/doc/misc/slave.zoneopt
+++ b/doc/misc/slave.zoneopt
@@ -31,12 +31,14 @@ zone <string> [ <class> ] {
 	max-ixfr-ratio ( unlimited | <percentage> );
 	max-journal-size ( default | unlimited | <sizeval> );
 	max-records <integer>;
+	max-records-per-type <integer>;
 	max-refresh-time <integer>;
 	max-retry-time <integer>;
 	max-transfer-idle-in <integer>;
 	max-transfer-idle-out <integer>;
 	max-transfer-time-in <integer>;
 	max-transfer-time-out <integer>;
+	max-types-per-name <integer>;
 	min-refresh-time <integer>;
 	min-retry-time <integer>;
 	multi-master <boolean>;
diff --git a/doc/misc/static-stub.zoneopt b/doc/misc/static-stub.zoneopt
index f89d462..102b980 100644
--- a/doc/misc/static-stub.zoneopt
+++ b/doc/misc/static-stub.zoneopt
@@ -5,6 +5,8 @@ zone <string> [ <class> ] {
 	forward ( first | only );
 	forwarders [ port <integer> ] [ dscp <integer> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ dscp <integer> ]; ... };
 	max-records <integer>;
+	max-records-per-type <integer>;
+	max-types-per-name <integer>;
 	server-addresses { ( <ipv4_address> | <ipv6_address> ); ... };
 	server-names { <string>; ... };
 	zone-statistics ( full | terse | none | <boolean> );
diff --git a/doc/misc/stub.zoneopt b/doc/misc/stub.zoneopt
index 2db604d..ca4bf1c 100644
--- a/doc/misc/stub.zoneopt
+++ b/doc/misc/stub.zoneopt
@@ -13,10 +13,12 @@ zone <string> [ <class> ] {
 	masterfile-style ( full | relative );
 	masters [ port <integer> ] [ dscp <integer> ] { ( <remote-servers> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ]; ... };
 	max-records <integer>;
+	max-records-per-type <integer>;
 	max-refresh-time <integer>;
 	max-retry-time <integer>;
 	max-transfer-idle-in <integer>;
 	max-transfer-time-in <integer>;
+	max-types-per-name <integer>;
 	min-refresh-time <integer>;
 	min-retry-time <integer>;
 	multi-master <boolean>;
diff --git a/lib/dns/cache.c b/lib/dns/cache.c
index b5d2b09..8cb442b 100644
--- a/lib/dns/cache.c
+++ b/lib/dns/cache.c
@@ -151,6 +151,8 @@ struct dns_cache {
 	/* Locked by 'filelock'. */
 	char *filename;
 	/* Access to the on-disk cache file is also locked by 'filelock'. */
+
+	uint32_t maxrrperset;
 };
 
 /***
@@ -184,6 +186,7 @@ cache_create_db(dns_cache_t *cache, dns_db_t **db) {
 	}
 
 	dns_db_setservestalettl(*db, cache->serve_stale_ttl);
+	dns_db_setmaxrrperset(*db, cache->maxrrperset);
 
 	if (cache->taskmgr == NULL) {
 		return (ISC_R_SUCCESS);
@@ -1316,6 +1319,16 @@ dns_cache_updatestats(dns_cache_t *cache, isc_result_t result) {
 	}
 }
 
+void
+dns_cache_setmaxrrperset(dns_cache_t *cache, uint32_t value) {
+	REQUIRE(VALID_CACHE(cache));
+
+	cache->maxrrperset = value;
+	if (cache->db != NULL) {
+		dns_db_setmaxrrperset(cache->db, value);
+	}
+}
+
 /*
  * XXX: Much of the following code has been copied in from statschannel.c.
  * We should refactor this into a generic function in stats.c that can be
diff --git a/lib/dns/db.c b/lib/dns/db.c
index f90add1..a575d62 100644
--- a/lib/dns/db.c
+++ b/lib/dns/db.c
@@ -1137,3 +1137,12 @@ dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats) {
 
 	return (ISC_R_NOTIMPLEMENTED);
 }
+
+void
+dns_db_setmaxrrperset(dns_db_t *db, uint32_t value) {
+	REQUIRE(DNS_DB_VALID(db));
+
+	if (db->methods->setmaxrrperset != NULL) {
+		(db->methods->setmaxrrperset)(db, value);
+	}
+}
diff --git a/lib/dns/dnsrps.c b/lib/dns/dnsrps.c
index 8c04337..f4d9bae 100644
--- a/lib/dns/dnsrps.c
+++ b/lib/dns/dnsrps.c
@@ -975,7 +975,8 @@ static dns_dbmethods_t rpsdb_db_methods = {
 	NULL, /* setservestalerefresh */
 	NULL, /* getservestalerefresh */
 	NULL, /* setgluecachestats */
-	NULL  /* adjusthashsize */
+	NULL, /* adjusthashsize */
+	NULL  /* setmaxrrperset */
 };
 
 static dns_rdatasetmethods_t rpsdb_rdataset_methods = {
diff --git a/lib/dns/ecdb.c b/lib/dns/ecdb.c
index abbb8d9..42a1248 100644
--- a/lib/dns/ecdb.c
+++ b/lib/dns/ecdb.c
@@ -428,7 +428,7 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
 	}
 
 	result = dns_rdataslab_fromrdataset(rdataset, mctx, &r,
-					    sizeof(rdatasetheader_t));
+					    sizeof(rdatasetheader_t), 0);
 	if (result != ISC_R_SUCCESS) {
 		goto unlock;
 	}
@@ -560,7 +560,11 @@ static dns_dbmethods_t ecdb_methods = {
 	NULL, /* getsize */
 	NULL, /* setservestalettl */
 	NULL, /* getservestalettl */
-	NULL  /* setgluecachestats */
+	NULL, /* setservestalerefresh */
+	NULL, /* getservestalerefresh */
+	NULL, /* setgluecachestats */
+	NULL, /* adjusthashsize */
+	NULL  /* setmaxrrperset */
 };
 
 static isc_result_t
diff --git a/lib/dns/include/dns/cache.h b/lib/dns/include/dns/cache.h
index 02dd1ac..34539eb 100644
--- a/lib/dns/include/dns/cache.h
+++ b/lib/dns/include/dns/cache.h
@@ -334,6 +334,12 @@ dns_cache_updatestats(dns_cache_t *cache, isc_result_t result);
  * Update cache statistics based on result code in 'result'
  */
 
+void
+dns_cache_setmaxrrperset(dns_cache_t *cache, uint32_t value);
+/*%<
+ * Set the maximum resource records per RRSet that can be cached.
+ */
+
 #ifdef HAVE_LIBXML2
 int
 dns_cache_renderxml(dns_cache_t *cache, void *writer0);
diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h
index ae27639..e0d17f2 100644
--- a/lib/dns/include/dns/db.h
+++ b/lib/dns/include/dns/db.h
@@ -184,6 +184,7 @@ typedef struct dns_dbmethods {
 	isc_result_t (*getservestalerefresh)(dns_db_t *db, uint32_t *interval);
 	isc_result_t (*setgluecachestats)(dns_db_t *db, isc_stats_t *stats);
 	isc_result_t (*adjusthashsize)(dns_db_t *db, size_t size);
+	void (*setmaxrrperset)(dns_db_t *db, uint32_t value);
 } dns_dbmethods_t;
 
 typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t	    *mctx,
@@ -1796,6 +1797,13 @@ dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats);
  *	dns_rdatasetstats_create(); otherwise NULL.
  */
 
+void
+dns_db_setmaxrrperset(dns_db_t *db, uint32_t value);
+/*%<
+ * Set the maximum permissible number of RRs per RRset. If 'value'
+ * is nonzero, then any subsequent attempt to add an rdataset with
+ * more than 'value' RRs will return ISC_R_NOSPACE.
+ */
 ISC_LANG_ENDDECLS
 
 #endif /* DNS_DB_H */
diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h
index 5a1c30f..89c8fc2 100644
--- a/lib/dns/include/dns/rdataslab.h
+++ b/lib/dns/include/dns/rdataslab.h
@@ -67,7 +67,8 @@ ISC_LANG_BEGINDECLS
 
 isc_result_t
 dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
-			   isc_region_t *region, unsigned int reservelen);
+			   isc_region_t *region, unsigned int reservelen,
+			   uint32_t limit);
 /*%<
  * Slabify a rdataset.  The slab area will be allocated and returned
  * in 'region'.
@@ -123,7 +124,8 @@ isc_result_t
 dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab,
 		    unsigned int reservelen, isc_mem_t *mctx,
 		    dns_rdataclass_t rdclass, dns_rdatatype_t type,
-		    unsigned int flags, unsigned char **tslabp);
+		    unsigned int flags, uint32_t maxrrperset,
+		    unsigned char **tslabp);
 /*%<
  * Merge 'oslab' and 'nslab'.
  */
diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h
index d6e7f10..b7cf663 100644
--- a/lib/dns/include/dns/view.h
+++ b/lib/dns/include/dns/view.h
@@ -188,6 +188,7 @@ struct dns_view {
 	dns_dlzdblist_t	  dlz_unsearched;
 	uint32_t	  fail_ttl;
 	dns_badcache_t	 *failcache;
+	uint32_t	  maxrrperset;
 
 	/*
 	 * Configurable data for server use only,
@@ -1354,6 +1355,12 @@ dns_view_staleanswerenabled(dns_view_t *view);
  *\li	'view' to be valid.
  */
 
+void
+dns_view_setmaxrrperset(dns_view_t *view, uint32_t value);
+/*%<
+ * Set the maximum resource records per RRSet that can be cached.
+ */
+
 ISC_LANG_ENDDECLS
 
 #endif /* DNS_VIEW_H */
diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h
index 4bdc936..a98b28f 100644
--- a/lib/dns/include/dns/zone.h
+++ b/lib/dns/include/dns/zone.h
@@ -164,6 +164,19 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx);
  *\li	#ISC_R_UNEXPECTED
  */
 
+isc_result_t
+dns_zone_makedb(dns_zone_t *zone, dns_db_t **dbp);
+/*%<
+ *        Creates a new empty database for the 'zone'.
+ *
+ * Requires:
+ *\li        'zone' to be a valid zone.
+ *\li        'dbp' to point to NULL pointer.
+ *
+ * Returns:
+ *\li        dns_db_create() error codes.
+ */
+
 void
 dns_zone_setclass(dns_zone_t *zone, dns_rdataclass_t rdclass);
 /*%<
@@ -332,6 +345,19 @@ dns_zone_getmaxrecords(dns_zone_t *zone);
  *\li	uint32_t maxrecords.
  */
 
+void
+dns_zone_setmaxrrperset(dns_zone_t *zone, uint32_t maxrrperset);
+/*%<
+ * 	Sets the maximum number of records per rrset permitted in a zone.
+ *	0 implies unlimited.
+ *
+ * Requires:
+ *\li	'zone' to be valid initialised zone.
+ *
+ * Returns:
+ *\li	void
+ */
+
 void
 dns_zone_setmaxttl(dns_zone_t *zone, uint32_t maxttl);
 /*%<
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
index 9670671..06228c9 100644
--- a/lib/dns/rbtdb.c
+++ b/lib/dns/rbtdb.c
@@ -496,6 +496,7 @@ struct dns_rbtdb {
 	rbtdb_serial_t current_serial;
 	rbtdb_serial_t least_serial;
 	rbtdb_serial_t next_serial;
+	uint32_t maxrrperset;
 	rbtdb_version_t *current_version;
 	rbtdb_version_t *future_version;
 	rbtdb_versionlist_t open_versions;
@@ -6538,7 +6539,7 @@ find_header:
 					rbtdb->common.mctx,
 					rbtdb->common.rdclass,
 					(dns_rdatatype_t)header->type, flags,
-					&merged);
+					rbtdb->maxrrperset, &merged);
 			}
 			if (result == ISC_R_SUCCESS) {
 				/*
@@ -6896,7 +6897,7 @@ delegating_type(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
 
 static isc_result_t
 addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
-	   dns_rdataset_t *rdataset) {
+	   uint32_t maxrrperset, dns_rdataset_t *rdataset) {
 	struct noqname *noqname;
 	isc_mem_t *mctx = rbtdb->common.mctx;
 	dns_name_t name;
@@ -6917,12 +6918,12 @@ addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
 	noqname->negsig = NULL;
 	noqname->type = neg.type;
 	dns_name_dup(&name, mctx, &noqname->name);
-	result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0);
+	result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0, maxrrperset);
 	if (result != ISC_R_SUCCESS) {
 		goto cleanup;
 	}
 	noqname->neg = r.base;
-	result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0);
+	result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0, maxrrperset);
 	if (result != ISC_R_SUCCESS) {
 		goto cleanup;
 	}
@@ -6941,7 +6942,7 @@ cleanup:
 
 static isc_result_t
 addclosest(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
-	   dns_rdataset_t *rdataset) {
+	   uint32_t maxrrperset, dns_rdataset_t *rdataset) {
 	struct noqname *closest;
 	isc_mem_t *mctx = rbtdb->common.mctx;
 	dns_name_t name;
@@ -6962,12 +6963,12 @@ addclosest(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
 	closest->negsig = NULL;
 	closest->type = neg.type;
 	dns_name_dup(&name, mctx, &closest->name);
-	result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0);
+	result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0, maxrrperset);
 	if (result != ISC_R_SUCCESS) {
 		goto cleanup;
 	}
 	closest->neg = r.base;
-	result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0);
+	result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0, maxrrperset);
 	if (result != ISC_R_SUCCESS) {
 		goto cleanup;
 	}
@@ -7048,7 +7049,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
 	}
 
 	result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
-					    &region, sizeof(rdatasetheader_t));
+					    &region, sizeof(rdatasetheader_t),
+					    rbtdb->maxrrperset);
 	if (result != ISC_R_SUCCESS) {
 		return (result);
 	}
@@ -7106,7 +7108,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
 			RDATASET_ATTR_SET(newheader, RDATASET_ATTR_OPTOUT);
 		}
 		if ((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0) {
-			result = addnoqname(rbtdb, newheader, rdataset);
+			result = addnoqname(rbtdb, newheader,
+					    rbtdb->maxrrperset, rdataset);
 			if (result != ISC_R_SUCCESS) {
 				free_rdataset(rbtdb, rbtdb->common.mctx,
 					      newheader);
@@ -7114,7 +7117,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
 			}
 		}
 		if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0) {
-			result = addclosest(rbtdb, newheader, rdataset);
+			result = addclosest(rbtdb, newheader,
+					    rbtdb->maxrrperset, rdataset);
 			if (result != ISC_R_SUCCESS) {
 				free_rdataset(rbtdb, rbtdb->common.mctx,
 					      newheader);
@@ -7268,7 +7272,8 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
 	nodefullname(db, node, nodename);
 
 	result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
-					    &region, sizeof(rdatasetheader_t));
+					    &region, sizeof(rdatasetheader_t),
+					    0);
 	if (result != ISC_R_SUCCESS) {
 		return (result);
 	}
@@ -7672,7 +7677,8 @@ loading_addrdataset(void *arg, const dns_name_t *name,
 	}
 
 	result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
-					    &region, sizeof(rdatasetheader_t));
+					    &region, sizeof(rdatasetheader_t),
+					    rbtdb->maxrrperset);
 	if (result != ISC_R_SUCCESS) {
 		return (result);
 	}
@@ -8612,6 +8618,15 @@ setgluecachestats(dns_db_t *db, isc_stats_t *stats) {
 	return (ISC_R_SUCCESS);
 }
 
+static void
+setmaxrrperset(dns_db_t *db, uint32_t maxrrperset) {
+	dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+	REQUIRE(VALID_RBTDB(rbtdb));
+
+	rbtdb->maxrrperset = maxrrperset;
+}
+
 static dns_stats_t *
 getrrsetstats(dns_db_t *db) {
 	dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
@@ -8735,7 +8750,8 @@ static dns_dbmethods_t zone_methods = { attach,
 					NULL, /* setservestalerefresh */
 					NULL, /* getservestalerefresh */
 					setgluecachestats,
-					adjusthashsize };
+					adjusthashsize,
+					setmaxrrperset };
 
 static dns_dbmethods_t cache_methods = { attach,
 					 detach,
@@ -8787,7 +8803,8 @@ static dns_dbmethods_t cache_methods = { attach,
 					 setservestalerefresh,
 					 getservestalerefresh,
 					 NULL,
-					 adjusthashsize };
+					 adjusthashsize,
+					 setmaxrrperset };
 
 isc_result_t
 dns_rbtdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c
index d74e84c..bf4e045 100644
--- a/lib/dns/rdataslab.c
+++ b/lib/dns/rdataslab.c
@@ -118,7 +118,8 @@ fillin_offsets(unsigned char *offsetbase, unsigned int *offsettable,
 
 isc_result_t
 dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
-			   isc_region_t *region, unsigned int reservelen) {
+			   isc_region_t *region, unsigned int reservelen,
+			   uint32_t maxrrperset) {
 	/*
 	 * Use &removed as a sentinel pointer for duplicate
 	 * rdata as rdata.data == NULL is valid.
@@ -160,7 +161,7 @@ dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
 		return (ISC_R_SUCCESS);
 	}
 
-	if (nitems > DNS_RDATASET_MAX_RECORDS) {
+	if (maxrrperset > 0 && nitems > maxrrperset) {
 		return (DNS_R_TOOMANYRECORDS);
 	}
 
@@ -492,7 +493,8 @@ isc_result_t
 dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab,
 		    unsigned int reservelen, isc_mem_t *mctx,
 		    dns_rdataclass_t rdclass, dns_rdatatype_t type,
-		    unsigned int flags, unsigned char **tslabp) {
+		    unsigned int flags, uint32_t maxrrperset,
+		    unsigned char **tslabp) {
 	unsigned char *ocurrent, *ostart, *ncurrent, *tstart, *tcurrent, *data;
 	unsigned int ocount, ncount, count, olength, tlength, tcount, length;
 	dns_rdata_t ordata = DNS_RDATA_INIT;
@@ -532,7 +534,7 @@ dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab,
 #endif /* if DNS_RDATASET_FIXED */
 	INSIST(ocount > 0 && ncount > 0);
 
-	if (ocount + ncount > DNS_RDATASET_MAX_RECORDS) {
+	if (maxrrperset > 0 && ocount + ncount > maxrrperset) {
 		return (DNS_R_TOOMANYRECORDS);
 	}
 
diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c
index 7822d6e..282c327 100644
--- a/lib/dns/sdb.c
+++ b/lib/dns/sdb.c
@@ -1319,7 +1319,8 @@ static dns_dbmethods_t sdb_methods = {
 	NULL, /* setservestalerefresh */
 	NULL, /* getservestalerefresh */
 	NULL, /* setgluecachestats */
-	NULL  /* adjusthashsize */
+	NULL, /* adjusthashsize */
+	NULL  /* setmaxrrperset */
 };
 
 static isc_result_t
diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c
index 106e24b..299c7d8 100644
--- a/lib/dns/sdlz.c
+++ b/lib/dns/sdlz.c
@@ -1292,7 +1292,8 @@ static dns_dbmethods_t sdlzdb_methods = {
 	NULL, /* setservestalerefresh */
 	NULL, /* getservestalerefresh */
 	NULL, /* setgluecachestats */
-	NULL  /* adjusthashsize */
+	NULL, /* adjusthashsize */
+	NULL  /* setmaxrrperset */
 };
 
 /*
diff --git a/lib/dns/view.c b/lib/dns/view.c
index a67dd73..9f5daf7 100644
--- a/lib/dns/view.c
+++ b/lib/dns/view.c
@@ -871,6 +871,8 @@ dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared) {
 	dns_cache_attach(cache, &view->cache);
 	dns_cache_attachdb(cache, &view->cachedb);
 	INSIST(DNS_DB_VALID(view->cachedb));
+
+	dns_cache_setmaxrrperset(view->cache, view->maxrrperset);
 }
 
 bool
@@ -2640,3 +2642,12 @@ dns_view_staleanswerenabled(dns_view_t *view) {
 
 	return (result);
 }
+
+void
+dns_view_setmaxrrperset(dns_view_t *view, uint32_t value) {
+	REQUIRE(DNS_VIEW_VALID(view));
+	view->maxrrperset = value;
+	if (view->cache != NULL) {
+		dns_cache_setmaxrrperset(view->cache, value);
+	}
+}
diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c
index a54d0d8..9d72b82 100644
--- a/lib/dns/xfrin.c
+++ b/lib/dns/xfrin.c
@@ -205,8 +205,6 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_task_t *task,
 static isc_result_t
 axfr_init(dns_xfrin_ctx_t *xfr);
 static isc_result_t
-axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp);
-static isc_result_t
 axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
 	     dns_ttl_t ttl, dns_rdata_t *rdata);
 static isc_result_t
@@ -279,7 +277,11 @@ axfr_init(dns_xfrin_ctx_t *xfr) {
 		dns_db_detach(&xfr->db);
 	}
 
-	CHECK(axfr_makedb(xfr, &xfr->db));
+	CHECK(dns_zone_makedb(xfr->zone, &xfr->db));
+
+	dns_zone_rpz_enable_db(xfr->zone, xfr->db);
+	dns_zone_catz_enable_db(xfr->zone, xfr->db);
+
 	dns_rdatacallbacks_init(&xfr->axfr);
 	CHECK(dns_db_beginload(xfr->db, &xfr->axfr));
 	result = ISC_R_SUCCESS;
@@ -287,22 +289,6 @@ failure:
 	return (result);
 }
 
-static isc_result_t
-axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp) {
-	isc_result_t result;
-
-	result = dns_db_create(xfr->mctx, /* XXX */
-			       "rbt",	  /* XXX guess */
-			       &xfr->name, dns_dbtype_zone, xfr->rdclass, 0,
-			       NULL, /* XXX guess */
-			       dbp);
-	if (result == ISC_R_SUCCESS) {
-		dns_zone_rpz_enable_db(xfr->zone, *dbp);
-		dns_zone_catz_enable_db(xfr->zone, *dbp);
-	}
-	return (result);
-}
-
 static isc_result_t
 axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
 	     dns_ttl_t ttl, dns_rdata_t *rdata) {
diff --git a/lib/dns/zone.c b/lib/dns/zone.c
index b457095..5cdb8ea 100644
--- a/lib/dns/zone.c
+++ b/lib/dns/zone.c
@@ -307,6 +307,7 @@ struct dns_zone {
 	uint32_t minretry;
 
 	uint32_t maxrecords;
+	uint32_t maxrrperset;
 
 	isc_sockaddr_t *masters;
 	isc_dscp_t *masterdscps;
@@ -2304,31 +2305,13 @@ zone_load(dns_zone_t *zone, unsigned int flags, bool locked) {
 	dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1),
 		      "starting load");
 
-	result = dns_db_create(zone->mctx, zone->db_argv[0], &zone->origin,
-			       (zone->type == dns_zone_stub) ? dns_dbtype_stub
-							     : dns_dbtype_zone,
-			       zone->rdclass, zone->db_argc - 1,
-			       zone->db_argv + 1, &db);
-
+	result = dns_zone_makedb(zone, &db);
 	if (result != ISC_R_SUCCESS) {
 		dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR,
 			      "loading zone: creating database: %s",
 			      isc_result_totext(result));
 		goto cleanup;
 	}
-	dns_db_settask(db, zone->task, zone->task);
-
-	if (zone->type == dns_zone_primary ||
-	    zone->type == dns_zone_secondary || zone->type == dns_zone_mirror)
-	{
-		result = dns_db_setgluecachestats(db, zone->gluecachestats);
-		if (result == ISC_R_NOTIMPLEMENTED) {
-			result = ISC_R_SUCCESS;
-		}
-		if (result != ISC_R_SUCCESS) {
-			goto cleanup;
-		}
-	}
 
 	if (!dns_db_ispersistent(db)) {
 		if (zone->masterfile != NULL) {
@@ -12313,6 +12296,16 @@ dns_zone_setmaxrecords(dns_zone_t *zone, uint32_t val) {
 	zone->maxrecords = val;
 }
 
+void
+dns_zone_setmaxrrperset(dns_zone_t *zone, uint32_t val) {
+	REQUIRE(DNS_ZONE_VALID(zone));
+
+	zone->maxrrperset = val;
+	if (zone->db != NULL) {
+		dns_db_setmaxrrperset(zone->db, val);
+	}
+}
+
 static bool
 notify_isqueued(dns_zone_t *zone, unsigned int flags, dns_name_t *name,
 		isc_sockaddr_t *addr, dns_tsigkey_t *key) {
@@ -14726,6 +14719,7 @@ ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) {
 				goto cleanup;
 			}
 			dns_db_settask(stub->db, zone->task, zone->task);
+			dns_db_setmaxrrperset(stub->db, zone->maxrrperset);
 		}
 
 		result = dns_db_newversion(stub->db, &stub->version);
@@ -17468,6 +17462,7 @@ zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump) {
 	}
 	zone_attachdb(zone, db);
 	dns_db_settask(zone->db, zone->task, zone->task);
+	dns_db_setmaxrrperset(zone->db, zone->maxrrperset);
 	DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY);
 	return (ISC_R_SUCCESS);
 
@@ -23615,3 +23610,44 @@ zone_nsecttl(dns_zone_t *zone) {
 
 	return (ISC_MIN(zone->minimum, zone->soattl));
 }
+
+isc_result_t
+dns_zone_makedb(dns_zone_t *zone, dns_db_t **dbp) {
+	REQUIRE(DNS_ZONE_VALID(zone));
+	REQUIRE(dbp != NULL && *dbp == NULL);
+
+	dns_db_t *db = NULL;
+
+	isc_result_t result = dns_db_create(
+		zone->mctx, zone->db_argv[0], &zone->origin,
+		(zone->type == dns_zone_stub) ? dns_dbtype_stub
+					      : dns_dbtype_zone,
+		zone->rdclass, zone->db_argc - 1, zone->db_argv + 1, &db);
+	if (result != ISC_R_SUCCESS) {
+		return (result);
+	}
+
+	switch (zone->type) {
+	case dns_zone_primary:
+	case dns_zone_secondary:
+	case dns_zone_mirror:
+		result = dns_db_setgluecachestats(db, zone->gluecachestats);
+		if (result == ISC_R_NOTIMPLEMENTED) {
+			result = ISC_R_SUCCESS;
+		}
+		if (result != ISC_R_SUCCESS) {
+			dns_db_detach(&db);
+			return (result);
+		}
+		break;
+	default:
+		break;
+	}
+
+	dns_db_settask(db, zone->task, zone->task);
+	dns_db_setmaxrrperset(db, zone->maxrrperset);
+
+	*dbp = db;
+
+	return (ISC_R_SUCCESS);
+}
diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c
index 6e63d86..ffe22b3 100644
--- a/lib/isccfg/namedconf.c
+++ b/lib/isccfg/namedconf.c
@@ -2241,6 +2241,9 @@ static cfg_clausedef_t zone_clauses[] = {
 	{ "max-records", &cfg_type_uint32,
 	  CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
 		  CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT },
+	{ "max-records-per-type", &cfg_type_uint32,
+	  CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+		  CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT },
 	{ "max-refresh-time", &cfg_type_uint32,
 	  CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
 	{ "max-retry-time", &cfg_type_uint32,
