Add a limit to the number of RR types for single name

Previously, the number of RR types for a single owner name was limited
only by the maximum number of the types (64k).  As the data structure
that holds the RR types for the database node is just a linked list, and
there are places where we just walk through the whole list (again and
again), adding a large number of RR types for a single owner named with
would slow down processing of such name (database node).

Add a configurable limit to cap the number of the RR types for a single
owner.  This is enforced at the database (rbtdb, qpzone, qpcache) level
and configured with new max-types-per-name configuration option that
can be configured globally, per-view and per-zone.

(cherry picked from commit 00d16211d6368b99f070c1182d8c76b3798ca1db)
This commit is contained in:
Ondřej Surý 2024-05-25 11:46:56 +02:00 committed by Nicki Křížek
parent 197b08009d
commit 39d3e2a8ec
No known key found for this signature in database
GPG key ID: 01623B9B652A20A7
25 changed files with 163 additions and 6 deletions

View file

@ -236,6 +236,7 @@ options {\n\
max-records-per-type 100;\n\
max-refresh-time 2419200; /* 4 weeks */\n\
max-retry-time 1209600; /* 2 weeks */\n\
max-types-per-name 100;\n\
max-transfer-idle-in 60;\n\
max-transfer-idle-out 60;\n\
max-transfer-time-in 120;\n\

View file

@ -5565,6 +5565,15 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
INSIST(result == ISC_R_SUCCESS);
dns_view_setmaxrrperset(view, cfg_obj_asuint32(obj));
/*
* This is used for the cache and also as a default value
* for zone databases.
*/
obj = NULL;
result = named_config_get(maps, "max-types-per-name", &obj);
INSIST(result == ISC_R_SUCCESS);
dns_view_setmaxtypepername(view, cfg_obj_asuint32(obj));
obj = NULL;
result = named_config_get(maps, "max-recursion-depth", &obj);
INSIST(result == ISC_R_SUCCESS);

View file

@ -1091,6 +1091,14 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
dns_zone_setmaxrrperset(zone, 0);
}
obj = NULL;
result = named_config_get(maps, "max-types-per-name", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
dns_zone_setmaxtypepername(mayberaw, cfg_obj_asuint32(obj));
if (zone != mayberaw) {
dns_zone_setmaxtypepername(zone, 0);
}
if (raw != NULL && filename != NULL) {
#define SIGNED ".signed"
size_t signedlen = strlen(filename) + sizeof(SIGNED);

View file

@ -612,7 +612,8 @@ static dns_dbmethods_t sampledb_methods = {
NULL, /* setservestalerefresh */
NULL, /* getservestalerefresh */
NULL, /* setgluecachestats */
NULL /* setmaxrrperset */
NULL, /* setmaxrrperset */
NULL /* setmaxtypepername */
};
/* Auxiliary driver functions. */

View file

@ -3781,6 +3781,21 @@ system.
a failure. If set to 0, there is no cap on RRset size. The default is
100.
.. namedconf:statement:: max-types-per-name
:tags: server
:short: Sets the maximum number of RR types that can be stored for an owner name
This sets the maximum number of resource record types that can be stored
for a single owner name in a database. When configured in :namedconf:ref:`options`
or :namedconf:ref:`view`, it controls the cache database, and also sets
the default value for zone databases, which can be overridden by setting
it at the :namedconf:ref:`zone` level
If set to a positive value, any attempt to cache or to add to a zone an owner
name with more than the specified number of resource record types will result
in a failure. If set to 0, there is no cap on RR types number. The default is
100.
.. namedconf:statement:: recursive-clients
:tags: query
:short: Specifies the maximum number of concurrent recursive queries the server can perform.

View file

@ -25,6 +25,7 @@ zone <string> [ <class> ] {
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>;

View file

@ -192,6 +192,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>;
@ -482,6 +483,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>;

View file

@ -41,6 +41,7 @@ zone <string> [ <class> ] {
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>;

View file

@ -8,6 +8,7 @@ zone <string> [ <class> ] {
masterfile-style ( full | relative );
max-records <integer>;
max-records-per-type <integer>;
max-types-per-name <integer>;
max-zone-ttl ( unlimited | <duration> );
primaries [ port <integer> ] { ( <remote-servers> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
zone-statistics ( full | terse | none | <boolean> );

View file

@ -37,6 +37,7 @@ zone <string> [ <class> ] {
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>;

View file

@ -6,6 +6,7 @@ zone <string> [ <class> ] {
forwarders [ port <integer> ] { ( <ipv4_address> | <ipv6_address> ) [ port <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> );

View file

@ -17,6 +17,7 @@ zone <string> [ <class> ] {
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>;

View file

@ -147,6 +147,7 @@ struct dns_cache {
dns_ttl_t serve_stale_refresh;
isc_stats_t *stats;
uint32_t maxrrperset;
uint32_t maxtypepername;
};
/***
@ -212,6 +213,7 @@ cache_create_db(dns_cache_t *cache, dns_db_t **dbp, isc_mem_t **tmctxp,
dns_db_setservestalettl(db, cache->serve_stale_ttl);
dns_db_setservestalerefresh(db, cache->serve_stale_refresh);
dns_db_setmaxrrperset(db, cache->maxrrperset);
dns_db_setmaxtypepername(db, cache->maxtypepername);
if (cache->taskmgr == NULL) {
*dbp = db;
@ -1251,6 +1253,16 @@ dns_cache_setmaxrrperset(dns_cache_t *cache, uint32_t value) {
}
}
void
dns_cache_setmaxtypepername(dns_cache_t *cache, uint32_t value) {
REQUIRE(VALID_CACHE(cache));
cache->maxtypepername = value;
if (cache->db != NULL) {
dns_db_setmaxtypepername(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

View file

@ -1130,3 +1130,12 @@ dns_db_setmaxrrperset(dns_db_t *db, uint32_t value) {
(db->methods->setmaxrrperset)(db, value);
}
}
void
dns_db_setmaxtypepername(dns_db_t *db, uint32_t value) {
REQUIRE(DNS_DB_VALID(db));
if (db->methods->setmaxtypepername != NULL) {
(db->methods->setmaxtypepername)(db, value);
}
}

View file

@ -975,7 +975,8 @@ static dns_dbmethods_t rpsdb_db_methods = {
NULL, /* setservestalerefresh */
NULL, /* getservestalerefresh */
NULL, /* setgluecachestats */
NULL /* setmaxrrperset */
NULL, /* setmaxrrperset */
NULL /* setmaxtypepername */
};
static dns_rdatasetmethods_t rpsdb_rdataset_methods = {

View file

@ -286,6 +286,12 @@ dns_cache_setmaxrrperset(dns_cache_t *cache, uint32_t value);
* Set the maximum resource records per RRSet that can be cached.
*/
void
dns_cache_setmaxtypepername(dns_cache_t *cache, uint32_t value);
/*%<
* Set the maximum resource record types per owner name that can be cached.
*/
#ifdef HAVE_LIBXML2
int
dns_cache_renderxml(dns_cache_t *cache, void *writer0);

View file

@ -186,6 +186,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);
void (*setmaxrrperset)(dns_db_t *db, uint32_t value);
void (*setmaxtypepername)(dns_db_t *db, uint32_t value);
} dns_dbmethods_t;
typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx,
@ -1767,4 +1768,14 @@ dns_db_setmaxrrperset(dns_db_t *db, uint32_t value);
* is nonzero, then any subsequent attempt to add an rdataset with
* more than 'value' RRs will return ISC_R_NOSPACE.
*/
void
dns_db_setmaxtypepername(dns_db_t *db, uint32_t value);
/*%<
* Set the maximum permissible number of RR types per owner name.
*
* If 'value' is nonzero, then any subsequent attempt to add an rdataset with a
* RR type that would exceed the number of already stored RR types will return
* ISC_R_NOSPACE.
*/
ISC_LANG_ENDDECLS

View file

@ -192,6 +192,7 @@ struct dns_view {
uint32_t fail_ttl;
dns_badcache_t *failcache;
uint32_t maxrrperset;
uint32_t maxtypepername;
/*
* Configurable data for server use only,
@ -1420,4 +1421,10 @@ dns_view_setmaxrrperset(dns_view_t *view, uint32_t value);
* Set the maximum resource records per RRSet that can be cached.
*/
void
dns_view_setmaxtypepername(dns_view_t *view, uint32_t value);
/*%<
* Set the maximum resource record types per owner name that can be cached.
*/
ISC_LANG_ENDDECLS

View file

@ -376,6 +376,19 @@ dns_zone_setmaxrrperset(dns_zone_t *zone, uint32_t maxrrperset);
*\li void
*/
void
dns_zone_setmaxtypepername(dns_zone_t *zone, uint32_t maxtypepername);
/*%<
* Sets the maximum number of resource record types per owner name
* 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);
/*%<

View file

@ -463,6 +463,7 @@ struct dns_rbtdb {
rbtdb_serial_t least_serial;
rbtdb_serial_t next_serial;
uint32_t maxrrperset;
uint32_t maxtypepername;
rbtdb_version_t *current_version;
rbtdb_version_t *future_version;
rbtdb_versionlist_t open_versions;
@ -6260,6 +6261,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename,
rbtdb_rdatatype_t negtype, sigtype;
dns_trust_t trust;
int idx;
uint32_t ntypes = 0;
/*
* Add an rdatasetheader_t to a node.
@ -6324,6 +6326,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename,
set_ttl(rbtdb, topheader, 0);
mark_header_ancient(rbtdb, topheader);
}
ntypes = 0; /* Always add the negative entry */
goto find_header;
}
/*
@ -6347,9 +6350,11 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename,
* check for an extant non-ancient NODATA ncache
* entry which covers the same type as the RRSIG.
*/
ntypes = 0;
for (topheader = rbtnode->data; topheader != NULL;
topheader = topheader->next)
{
++ntypes;
if ((topheader->type ==
RBTDB_RDATATYPE_NCACHEANY) ||
(newheader->type == sigtype &&
@ -6394,9 +6399,11 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename,
}
}
ntypes = 0;
for (topheader = rbtnode->data; topheader != NULL;
topheader = topheader->next)
{
++ntypes;
if (prio_type(topheader->type)) {
prioheader = topheader;
}
@ -6766,6 +6773,14 @@ find_header:
/*
* No rdatasets of the given type exist at the node.
*/
if (rbtdb->maxtypepername > 0 &&
ntypes >= rbtdb->maxtypepername)
{
free_rdataset(rbtdb, rbtdb->common.mctx,
newheader);
return (DNS_R_TOOMANYRECORDS);
}
newheader->down = NULL;
if (prio_type(newheader->type)) {
@ -8127,6 +8142,15 @@ setmaxrrperset(dns_db_t *db, uint32_t maxrrperset) {
rbtdb->maxrrperset = maxrrperset;
}
static void
setmaxtypepername(dns_db_t *db, uint32_t maxtypepername) {
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
REQUIRE(VALID_RBTDB(rbtdb));
rbtdb->maxtypepername = maxtypepername;
}
static dns_stats_t *
getrrsetstats(dns_db_t *db) {
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
@ -8249,7 +8273,8 @@ static dns_dbmethods_t zone_methods = { attach,
NULL, /* setservestalerefresh */
NULL, /* getservestalerefresh */
setgluecachestats,
setmaxrrperset };
setmaxrrperset,
setmaxtypepername };
static dns_dbmethods_t cache_methods = { attach,
detach,
@ -8300,7 +8325,8 @@ static dns_dbmethods_t cache_methods = { attach,
setservestalerefresh,
getservestalerefresh,
NULL,
setmaxrrperset };
setmaxrrperset,
setmaxtypepername };
isc_result_t
dns_rbtdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,

View file

@ -1319,7 +1319,8 @@ static dns_dbmethods_t sdb_methods = {
NULL, /* setservestalerefresh */
NULL, /* getservestalerefresh */
NULL, /* setgluecachestats */
NULL /* setmaxrrperset */
NULL, /* setmaxrrperset */
NULL /* setmaxtypepername */
};
static isc_result_t

View file

@ -1292,7 +1292,8 @@ static dns_dbmethods_t sdlzdb_methods = {
NULL, /* setservestalerefresh */
NULL, /* getservestalerefresh */
NULL, /* setgluecachestats */
NULL /* setmaxrrperset */
NULL, /* setmaxrrperset */
NULL /* setmaxtypepername */
};
/*

View file

@ -894,6 +894,7 @@ dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared) {
INSIST(DNS_DB_VALID(view->cachedb));
dns_cache_setmaxrrperset(view->cache, view->maxrrperset);
dns_cache_setmaxtypepername(view->cache, view->maxtypepername);
}
bool
@ -2770,3 +2771,12 @@ dns_view_setmaxrrperset(dns_view_t *view, uint32_t value) {
dns_cache_setmaxrrperset(view->cache, value);
}
}
void
dns_view_setmaxtypepername(dns_view_t *view, uint32_t value) {
REQUIRE(DNS_VIEW_VALID(view));
view->maxtypepername = value;
if (view->cache != NULL) {
dns_cache_setmaxtypepername(view->cache, value);
}
}

View file

@ -310,6 +310,7 @@ struct dns_zone {
uint32_t maxrecords;
uint32_t maxrrperset;
uint32_t maxtypepername;
isc_sockaddr_t *primaries;
dns_name_t **primarykeynames;
@ -12309,6 +12310,16 @@ dns_zone_setmaxrrperset(dns_zone_t *zone, uint32_t val) {
}
}
void
dns_zone_setmaxtypepername(dns_zone_t *zone, uint32_t val) {
REQUIRE(DNS_ZONE_VALID(zone));
zone->maxtypepername = val;
if (zone->db != NULL) {
dns_db_setmaxtypepername(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,
@ -14798,6 +14809,8 @@ ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) {
}
dns_db_settask(stub->db, zone->task, zone->task);
dns_db_setmaxrrperset(stub->db, zone->maxrrperset);
dns_db_setmaxtypepername(stub->db,
zone->maxtypepername);
}
result = dns_db_newversion(stub->db, &stub->version);
@ -17903,6 +17916,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_db_setmaxtypepername(zone->db, zone->maxtypepername);
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY);
return (ISC_R_SUCCESS);
@ -24329,6 +24343,7 @@ dns_zone_makedb(dns_zone_t *zone, dns_db_t **dbp) {
dns_db_settask(db, zone->task, zone->task);
dns_db_setmaxrrperset(db, zone->maxrrperset);
dns_db_setmaxtypepername(db, zone->maxtypepername);
*dbp = db;

View file

@ -2303,6 +2303,9 @@ static cfg_clausedef_t zone_clauses[] = {
{ "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-types-per-name", &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,