From d0eb2cc33c5db3366a16b1cb0abcca6ec7c8ee3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tatuya=20JINMEI=20=E7=A5=9E=E6=98=8E=E9=81=94=E5=93=89?= Date: Tue, 21 Dec 2004 10:45:20 +0000 Subject: [PATCH] 1526. [func] Implemented "additional section caching (or acache)", an internal cache framework for additional section content to improve response performance. Several configuration options were provided to control the behavior. --- CHANGES | 6 +- bin/named/config.c | 5 +- bin/named/include/named/server.h | 5 +- bin/named/query.c | 672 ++++++++++++- bin/named/server.c | 53 +- doc/arm/Bv9ARM-book.xml | 107 +- lib/dns/Makefile.in | 6 +- lib/dns/acache.c | 1575 ++++++++++++++++++++++++++++++ lib/dns/db.c | 50 +- lib/dns/include/dns/acache.h | 440 +++++++++ lib/dns/include/dns/db.h | 84 +- lib/dns/include/dns/events.h | 6 +- lib/dns/include/dns/log.h | 3 +- lib/dns/include/dns/rdataset.h | 128 ++- lib/dns/include/dns/types.h | 4 +- lib/dns/include/dns/view.h | 3 +- lib/dns/include/dns/zone.h | 15 +- lib/dns/log.c | 3 +- lib/dns/ncache.c | 5 +- lib/dns/rbtdb.c | 533 +++++++++- lib/dns/rdatalist.c | 7 +- lib/dns/rdataset.c | 83 +- lib/dns/rdataslab.c | 5 +- lib/dns/sdb.c | 13 +- lib/dns/view.c | 8 +- lib/dns/zone.c | 73 +- lib/isccfg/namedconf.c | 5 +- 27 files changed, 3802 insertions(+), 95 deletions(-) create mode 100644 lib/dns/acache.c create mode 100644 lib/dns/include/dns/acache.h diff --git a/CHANGES b/CHANGES index 1d2f9e1ae0..446d4a87f8 100644 --- a/CHANGES +++ b/CHANGES @@ -770,7 +770,11 @@ 1527. [cleanup] Reduce the number of gettimeofday() calls without losing necessary timer granularity. -1526. [placeholder] +1526. [func] Implemented "additional section caching (or acache)", + an internal cache framework for additional section + content to improve response performance. Several + configuration options were provided to control the + behavior. 1525. [bug] dns_cache_create() could trigger a REQUIRE failure in isc_mem_put() during error cleanup. diff --git a/bin/named/config.c b/bin/named/config.c index 10739e777b..1965758be0 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: config.c,v 1.51 2004/10/05 02:47:50 marka Exp $ */ +/* $Id: config.c,v 1.52 2004/12/21 10:45:15 jinmei Exp $ */ #include @@ -125,6 +125,9 @@ options {\n\ check-names master fail;\n\ check-names slave warn;\n\ check-names response ignore;\n\ + use-additional-cache true;\n\ + acache-cleaning-interval 60;\n\ + max-acache-size 0;\n\ dnssec-enable no; /* Make yes for 9.4. */ \n\ " diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index 2aafc9cccc..e50f9ab354 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: server.h,v 1.75 2004/10/11 05:30:19 marka Exp $ */ +/* $Id: server.h,v 1.76 2004/12/21 10:45:15 jinmei Exp $ */ #ifndef NAMED_SERVER_H #define NAMED_SERVER_H 1 @@ -94,7 +94,8 @@ struct ns_server { ns_controls_t * controls; /* Control channels */ unsigned int dispatchgen; ns_dispatchlist_t dispatches; - + + dns_acache_t *acache; }; #define NS_SERVER_MAGIC ISC_MAGIC('S','V','E','R') diff --git a/bin/named/query.c b/bin/named/query.c index 907fac3556..62f9ffacea 100644 --- a/bin/named/query.c +++ b/bin/named/query.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: query.c,v 1.261 2004/06/30 14:16:06 marka Exp $ */ +/* $Id: query.c,v 1.262 2004/12/21 10:45:15 jinmei Exp $ */ #include @@ -92,6 +92,11 @@ #define DNS_GETDB_NOLOG 0x02U #define DNS_GETDB_PARTIAL 0x04U +typedef struct client_additionalctx { + ns_client_t *client; + dns_rdataset_t *rdataset; +} client_additionalctx_t; + static void query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype); @@ -538,37 +543,18 @@ query_findversion(ns_client_t *client, dns_db_t *db, } static inline isc_result_t -query_getzonedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, - unsigned int options, dns_zone_t **zonep, dns_db_t **dbp, - dns_dbversion_t **versionp) +query_validatezonedb(ns_client_t *client, dns_name_t *name, + dns_rdatatype_t qtype, unsigned int options, + dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t **versionp) { isc_result_t result; isc_boolean_t check_acl, new_zone; dns_acl_t *queryacl; ns_dbversion_t *dbversion; - unsigned int ztoptions; - dns_zone_t *zone = NULL; - dns_db_t *db = NULL; - isc_boolean_t partial = ISC_FALSE; - REQUIRE(zonep != NULL && *zonep == NULL); - REQUIRE(dbp != NULL && *dbp == NULL); - - /* - * Find a zone database to answer the query. - */ - ztoptions = ((options & DNS_GETDB_NOEXACT) != 0) ? - DNS_ZTFIND_NOEXACT : 0; - - result = dns_zt_find(client->view->zonetable, name, ztoptions, NULL, - &zone); - if (result == DNS_R_PARTIALMATCH) - partial = ISC_TRUE; - if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) - result = dns_zone_getdb(zone, &db); - - if (result != ISC_R_SUCCESS) - goto fail; + REQUIRE(zone != NULL); + REQUIRE(db != NULL); /* * This limits our searching to the zone where the first name @@ -689,17 +675,63 @@ query_getzonedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, */ dbversion->queryok = ISC_TRUE; + /* Transfer ownership, if necessary. */ + if (versionp != NULL) + *versionp = dbversion->version; + + return (ISC_R_SUCCESS); + + refuse: + return (DNS_R_REFUSED); + + fail: + return (result); +} + +static inline isc_result_t +query_getzonedb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype, + unsigned int options, dns_zone_t **zonep, dns_db_t **dbp, + dns_dbversion_t **versionp) +{ + isc_result_t result; + unsigned int ztoptions; + dns_zone_t *zone = NULL; + dns_db_t *db = NULL; + isc_boolean_t partial = ISC_FALSE; + + REQUIRE(zonep != NULL && *zonep == NULL); + REQUIRE(dbp != NULL && *dbp == NULL); + + /* + * Find a zone database to answer the query. + */ + ztoptions = ((options & DNS_GETDB_NOEXACT) != 0) ? + DNS_ZTFIND_NOEXACT : 0; + + result = dns_zt_find(client->view->zonetable, name, ztoptions, NULL, + &zone); + if (result == DNS_R_PARTIALMATCH) + partial = ISC_TRUE; + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) + result = dns_zone_getdb(zone, &db); + + if (result != ISC_R_SUCCESS) + goto fail; + + result = query_validatezonedb(client, name, qtype, options, zone, db, + versionp); + + if (result != ISC_R_SUCCESS) + goto fail; + /* Transfer ownership. */ *zonep = zone; *dbp = db; - *versionp = dbversion->version; if (partial && (options & DNS_GETDB_PARTIAL) != 0) return (DNS_R_PARTIALMATCH); return (ISC_R_SUCCESS); - refuse: - result = DNS_R_REFUSED; fail: if (zone != NULL) dns_zone_detach(&zone); @@ -1237,11 +1269,516 @@ query_addadditional(void *arg, dns_name_t *name, dns_rdatatype_t qtype) { return (eresult); } +static inline void +query_discardcache(ns_client_t *client, dns_rdataset_t *rdataset_base, + dns_rdatasetadditional_t additionaltype, + dns_rdatatype_t type, dns_zone_t **zonep, dns_db_t **dbp, + dns_dbversion_t **versionp, dns_dbnode_t **nodep, + dns_name_t *fname) +{ + dns_rdataset_t *rdataset; + + while ((rdataset = ISC_LIST_HEAD(fname->list)) != NULL) { + ISC_LIST_UNLINK(fname->list, rdataset, link); + query_putrdataset(client, &rdataset); + } + if (*versionp != NULL) + dns_db_closeversion(*dbp, versionp, ISC_FALSE); + if (*nodep != NULL) + dns_db_detachnode(*dbp, nodep); + if (*dbp != NULL) + dns_db_detach(dbp); + if (*zonep != NULL) + dns_zone_detach(zonep); + (void)dns_rdataset_putadditional(client->view->acache, rdataset_base, + additionaltype, type); +} + +static inline isc_result_t +query_iscachevalid(dns_zone_t *zone, dns_db_t *db, dns_db_t *db0, + dns_dbversion_t *version) +{ + isc_result_t result = ISC_R_SUCCESS; + dns_dbversion_t *version_current = NULL; + dns_db_t *db_current = db0; + + if (db_current == NULL) { + result = dns_zone_getdb(zone, &db_current); + if (result != ISC_R_SUCCESS) + return (result); + } + dns_db_currentversion(db_current, &version_current); + if (db_current != db || version_current != version) { + result = ISC_R_FAILURE; + goto cleanup; + } + + cleanup: + dns_db_closeversion(db_current, &version_current, ISC_FALSE); + if (db0 == NULL && db_current != NULL) + dns_db_detach(&db_current); + + return (result); +} + +static isc_result_t +query_addadditional2(void *arg, dns_name_t *name, dns_rdatatype_t qtype) { + client_additionalctx_t *additionalctx = arg; + dns_rdataset_t *rdataset_base; + ns_client_t *client; + isc_result_t result, eresult; + dns_dbnode_t *node, *cnode; + dns_db_t *db, *cdb; + dns_name_t *fname, *mname0, cfname; + dns_rdataset_t *rdataset, *sigrdataset; + dns_rdataset_t *crdataset, *crdataset_next; + isc_buffer_t *dbuf; + isc_buffer_t b; + dns_dbversion_t *version, *cversion; + isc_boolean_t added_something, need_addname, needadditionalcache; + isc_boolean_t need_sigrrset; + dns_zone_t *zone; + dns_rdatatype_t type; + dns_rdatasetadditional_t additionaltype; + + if (qtype != dns_rdatatype_a) { + /* + * This function is optimized for "address" types. For other + * types, use a generic routine. + * XXX: ideally, this function should be generic enough. + */ + return (query_addadditional(additionalctx->client, + name, qtype)); + } + + /* + * Initialization. + */ + rdataset_base = additionalctx->rdataset; + client = additionalctx->client; + REQUIRE(NS_CLIENT_VALID(client)); + eresult = ISC_R_SUCCESS; + fname = NULL; + rdataset = NULL; + sigrdataset = NULL; + db = NULL; + cdb = NULL; + version = NULL; + cversion = NULL; + node = NULL; + cnode = NULL; + added_something = ISC_FALSE; + need_addname = ISC_FALSE; + zone = NULL; + needadditionalcache = ISC_FALSE; + additionaltype = dns_rdatasetadditional_fromauth; + dns_name_init(&cfname, NULL); + + CTRACE("query_addadditional2"); + + /* + * We treat type A additional section processing as if it + * were "any address type" additional section processing. + * To avoid multiple lookups, we do an 'any' database + * lookup and iterate over the node. + * XXXJT: this approach can cause a suboptimal result when the cache + * DB only has partial address types and the glue DB has remaining + * ones. + */ + type = dns_rdatatype_any; + + /* + * Get some resources. + */ + dbuf = query_getnamebuf(client); + if (dbuf == NULL) + goto cleanup; + fname = query_newname(client, dbuf, &b); + if (fname == NULL) + goto cleanup; + dns_name_setbuffer(&cfname, &b); /* share the buffer */ + + /* Check additional cache */ + result = dns_rdataset_getadditional(rdataset_base, additionaltype, + type, client->view->acache, &zone, + &cdb, &cversion, &cnode, &cfname, + client->message, client->now); + if (result != ISC_R_SUCCESS) + goto findauthdb; + if (zone == NULL) { + CTRACE("query_addadditional2: auth zone not found"); + goto try_cache; + } + + /* Is the cached DB up-to-date? */ + result = query_iscachevalid(zone, cdb, NULL, cversion); + if (result != ISC_R_SUCCESS) { + CTRACE("query_addadditional2: old auth additional cache"); + query_discardcache(client, rdataset_base, additionaltype, + type, &zone, &cdb, &cversion, &cnode, + &cfname); + goto findauthdb; + } + + if (cnode == NULL) { + /* + * We have a negative cache. We don't have to check the zone + * ACL, since the result (not using this zone) would be same + * regardless of the result. + */ + CTRACE("query_addadditional2: negative auth additional cache"); + dns_db_closeversion(cdb, &cversion, ISC_FALSE); + dns_db_detach(&cdb); + dns_zone_detach(&zone); + goto try_cache; + } + + result = query_validatezonedb(client, name, qtype, DNS_GETDB_NOLOG, + zone, cdb, NULL); + if (result != ISC_R_SUCCESS) { + query_discardcache(client, rdataset_base, additionaltype, + type, &zone, &cdb, &cversion, &cnode, + &cfname); + goto try_cache; + } + + /* We've got an active cache. */ + CTRACE("query_addadditional2: auth additional cache"); + dns_db_closeversion(cdb, &cversion, ISC_FALSE); + db = cdb; + node = cnode; + dns_name_clone(&cfname, fname); + query_keepname(client, fname, dbuf); + goto foundcache; + + /* + * Look for a zone database that might contain authoritative + * additional data. + */ + findauthdb: + result = query_getzonedb(client, name, qtype, DNS_GETDB_NOLOG, + &zone, &db, &version); + if (result != ISC_R_SUCCESS) { + /* Cache the negative result */ + (void)dns_rdataset_setadditional(rdataset_base, additionaltype, + type, client->view->acache, + NULL, NULL, NULL, NULL, + NULL); + goto try_cache; + } + + CTRACE("query_addadditional2: db_find"); + + /* + * Since we are looking for authoritative data, we do not set + * the GLUEOK flag. Glue will be looked for later, but not + * necessarily in the same database. + */ + node = NULL; + result = dns_db_find(db, name, version, type, client->query.dboptions, + client->now, &node, fname, NULL, NULL); + if (result == ISC_R_SUCCESS) + goto found; + + /* Cache the negative result */ + (void)dns_rdataset_setadditional(rdataset_base, additionaltype, + type, client->view->acache, zone, db, + version, NULL, fname); + + if (node != NULL) + dns_db_detachnode(db, &node); + version = NULL; + dns_db_detach(&db); + + /* + * No authoritative data was found. The cache is our next best bet. + */ + + try_cache: + additionaltype = dns_rdatasetadditional_fromcache; + result = query_getcachedb(client, name, qtype, &db, DNS_GETDB_NOLOG); + if (result != ISC_R_SUCCESS) + /* + * Most likely the client isn't allowed to query the cache. + */ + goto try_glue; + + result = dns_db_find(db, name, version, type, client->query.dboptions, + client->now, &node, fname, NULL, NULL); + if (result == ISC_R_SUCCESS) + goto found; + + if (node != NULL) + dns_db_detachnode(db, &node); + dns_db_detach(&db); + + try_glue: + /* + * No cached data was found. Glue is our last chance. + * RFC1035 sayeth: + * + * NS records cause both the usual additional section + * processing to locate a type A record, and, when used + * in a referral, a special search of the zone in which + * they reside for glue information. + * + * This is the "special search". Note that we must search + * the zone where the NS record resides, not the zone it + * points to, and that we only do the search in the delegation + * case (identified by client->query.gluedb being set). + */ + if (client->query.gluedb == NULL) + goto cleanup; + + /* + * Don't poision caches using the bailiwick protection model. + */ + if (!dns_name_issubdomain(name, dns_db_origin(client->query.gluedb))) + goto cleanup; + + /* Check additional cache */ + additionaltype = dns_rdatasetadditional_fromglue; + result = dns_rdataset_getadditional(rdataset_base, additionaltype, + type, client->view->acache, NULL, + &cdb, &cversion, &cnode, &cfname, + client->message, client->now); + if (result != ISC_R_SUCCESS) + goto findglue; + + result = query_iscachevalid(zone, cdb, client->query.gluedb, cversion); + if (result != ISC_R_SUCCESS) { + CTRACE("query_addadditional2: old glue additional cache"); + query_discardcache(client, rdataset_base, additionaltype, + type, &zone, &cdb, &cversion, &cnode, + &cfname); + goto findglue; + } + + if (cnode == NULL) { + /* We have a negative cache. */ + CTRACE("query_addadditional2: negative glue additional cache"); + dns_db_closeversion(cdb, &cversion, ISC_FALSE); + dns_db_detach(&cdb); + goto cleanup; + } + + /* Cache hit. */ + CTRACE("query_addadditional2: glue additional cache"); + dns_db_closeversion(cdb, &cversion, ISC_FALSE); + db = cdb; + node = cnode; + dns_name_clone(&cfname, fname); + query_keepname(client, fname, dbuf); + goto foundcache; + + findglue: + dns_db_attach(client->query.gluedb, &db); + result = dns_db_find(db, name, version, type, + client->query.dboptions | DNS_DBFIND_GLUEOK, + client->now, &node, fname, NULL, NULL); + if (!(result == ISC_R_SUCCESS || + result == DNS_R_ZONECUT || + result == DNS_R_GLUE)) { + /* cache the negative result */ + (void)dns_rdataset_setadditional(rdataset_base, additionaltype, + type, client->view->acache, + NULL, db, version, NULL, + fname); + goto cleanup; + } + + found: + /* + * We have found a DB node to iterate over from a DB. + * We are going to look for address RRsets (i.e., A and AAAA) in the DB + * node we've just found. We'll then store the complete information + * in the additional data cache. + */ + dns_name_clone(fname, &cfname); + query_keepname(client, fname, dbuf); + needadditionalcache = ISC_TRUE; + + rdataset = query_newrdataset(client); + if (rdataset == NULL) + goto cleanup; + if (WANTDNSSEC(client)) { + sigrdataset = query_newrdataset(client); + if (sigrdataset == NULL) + goto cleanup; + } + + /* + * Find A RRset with sig RRset. Even if we don't find a sig RRset + * for a client using DNSSEC, we'll continue the process to make a + * complete list to be cached. However, we need to cancel the + * caching when something unexpected happens, in order to avoid + * caching incomplete information. + */ + result = dns_db_findrdataset(db, node, version, dns_rdatatype_a, 0, + client->now, rdataset, sigrdataset); + if (result == DNS_R_NCACHENXDOMAIN) + goto setcache; + if (result == DNS_R_NCACHENXRRSET) { + dns_rdataset_disassociate(rdataset); + /* + * Negative cache entries don't have sigrdatasets. + */ + INSIST(sigrdataset == NULL || + ! dns_rdataset_isassociated(sigrdataset)); + } + if (result == ISC_R_SUCCESS) { + /* Remember the result as a cache */ + ISC_LIST_APPEND(cfname.list, rdataset, link); + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) { + ISC_LIST_APPEND(cfname.list, sigrdataset, + link); + sigrdataset = + query_newrdataset(client); + if (sigrdataset == NULL) + needadditionalcache = ISC_FALSE; + } + rdataset = query_newrdataset(client); + if (rdataset == NULL) { + /* do not cache incomplete information */ + goto foundcache; + } + } + + /* Find AAAA RRset with sig RRset */ + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_aaaa, 0, + client->now, rdataset, + sigrdataset); + /* The NXDOMAIN case should be covered above */ + INSIST(result != DNS_R_NCACHENXDOMAIN); + if (result == DNS_R_NCACHENXRRSET) { + dns_rdataset_disassociate(rdataset); + /* + * Negative cache entries don't have sigrdatasets. + */ + INSIST(sigrdataset == NULL || + ! dns_rdataset_isassociated(sigrdataset)); + } + if (result == ISC_R_SUCCESS) { + ISC_LIST_APPEND(cfname.list, rdataset, link); + rdataset = NULL; + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) { + ISC_LIST_APPEND(cfname.list, sigrdataset, + link); + sigrdataset = NULL; + } + } + + setcache: + /* + * Set the new result in the cache if required. We do not support + * caching additional data from a cache DB. + */ + if (needadditionalcache == ISC_TRUE && + (additionaltype == dns_rdatasetadditional_fromauth || + additionaltype == dns_rdatasetadditional_fromglue)) { + (void)dns_rdataset_setadditional(rdataset_base, additionaltype, + type, client->view->acache, + zone, db, version, node, + &cfname); + } + + foundcache: + need_sigrrset = ISC_FALSE; + mname0 = NULL; + for (crdataset = ISC_LIST_HEAD(cfname.list); + crdataset != NULL; + crdataset = crdataset_next) { + dns_name_t *mname; + + crdataset_next = ISC_LIST_NEXT(crdataset, link); + + mname = NULL; + if (crdataset->type == dns_rdatatype_a || + crdataset->type == dns_rdatatype_aaaa) { + if (!query_isduplicate(client, fname, crdataset->type, + &mname)) { + if (mname != NULL) { + /* + * A different type of this name is + * already stored in the additional + * section. We'll reuse the name. + * Note that this should happen at most + * once. Otherwise, fname->link could + * leak below. + */ + INSIST(mname0 == NULL); + + query_releasename(client, &fname); + fname = mname; + mname0 = mname; + } else + need_addname = ISC_TRUE; + ISC_LIST_UNLINK(cfname.list, crdataset, link); + ISC_LIST_APPEND(fname->list, crdataset, link); + added_something = ISC_TRUE; + need_sigrrset = ISC_TRUE; + } else + need_sigrrset = ISC_FALSE; + } else if (crdataset->type == dns_rdatatype_sig && + need_sigrrset && WANTDNSSEC(client)) { + ISC_LIST_UNLINK(cfname.list, crdataset, link); + ISC_LIST_APPEND(fname->list, crdataset, link); + added_something = ISC_TRUE; /* just in case */ + need_sigrrset = ISC_FALSE; + } + } + + CTRACE("query_addadditional2: addname"); + + /* + * If we haven't added anything, then we're done. + */ + if (!added_something) + goto cleanup; + + /* + * We may have added our rdatasets to an existing name, if so, then + * need_addname will be ISC_FALSE. Whether we used an existing name + * or a new one, we must set fname to NULL to prevent cleanup. + */ + if (need_addname) + dns_message_addname(client->message, fname, + DNS_SECTION_ADDITIONAL); + fname = NULL; + + cleanup: + CTRACE("query_addadditional2: cleanup"); + + if (rdataset != NULL) + query_putrdataset(client, &rdataset); + if (sigrdataset != NULL) + query_putrdataset(client, &sigrdataset); + while ((crdataset = ISC_LIST_HEAD(cfname.list)) != NULL) { + ISC_LIST_UNLINK(cfname.list, crdataset, link); + query_putrdataset(client, &crdataset); + } + if (fname != NULL) + query_releasename(client, &fname); + if (node != NULL) + dns_db_detachnode(db, &node); + if (db != NULL) + dns_db_detach(&db); + if (zone != NULL) + dns_zone_detach(&zone); + + CTRACE("query_addadditional2: done"); + return (eresult); +} + static inline void query_addrdataset(ns_client_t *client, dns_name_t *fname, dns_rdataset_t *rdataset) { dns_rdatatype_t type = rdataset->type; + client_additionalctx_t additionalctx; /* * Add 'rdataset' and any pertinent additional data to @@ -1264,8 +1801,10 @@ query_addrdataset(ns_client_t *client, dns_name_t *fname, * * We don't care if dns_rdataset_additionaldata() fails. */ - (void)dns_rdataset_additionaldata(rdataset, - query_addadditional, client); + additionalctx.client = client; + additionalctx.rdataset = rdataset; + (void)dns_rdataset_additionaldata(rdataset, query_addadditional2, + &additionalctx); /* * RFC 2535 section 3.5 says that when NS, SOA, A, or AAAA records * are retrieved, any KEY RRs for the owner name should be added @@ -1362,11 +1901,12 @@ query_addrrset(ns_client_t *client, dns_name_t **namep, } static inline isc_result_t -query_addsoa(ns_client_t *client, dns_db_t *db, isc_boolean_t zero_ttl) { - dns_name_t *name, *fname; +query_addsoa(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version, + isc_boolean_t zero_ttl) +{ + dns_name_t *name; dns_dbnode_t *node; isc_result_t result, eresult; - dns_fixedname_t foundname; dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; dns_rdataset_t **sigrdatasetp = NULL; @@ -1378,8 +1918,6 @@ query_addsoa(ns_client_t *client, dns_db_t *db, isc_boolean_t zero_ttl) { name = NULL; rdataset = NULL; node = NULL; - dns_fixedname_init(&foundname); - fname = dns_fixedname_name(&foundname); /* * Get resources and make 'name' be the database origin. @@ -1405,9 +1943,26 @@ query_addsoa(ns_client_t *client, dns_db_t *db, isc_boolean_t zero_ttl) { /* * Find the SOA. */ - result = dns_db_find(db, name, NULL, dns_rdatatype_soa, - client->query.dboptions, 0, &node, - fname, rdataset, sigrdataset); + result = dns_db_getsoanode(db, &node); + if (result == ISC_R_SUCCESS) { + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_soa, + 0, client->now, rdataset, + sigrdataset); + } else { + dns_fixedname_t foundname; + dns_name_t *fname; + + dns_fixedname_init(&foundname); + fname = dns_fixedname_name(&foundname); + + result = dns_db_find(db, name, version, dns_rdatatype_soa, + client->query.dboptions, 0, &node, + fname, rdataset, sigrdataset); + + if (result == ISC_R_SUCCESS) + (void)dns_db_setsoanode(db, node); + } if (result != ISC_R_SUCCESS) { /* * This is bad. We tried to get the SOA RR at the zone top @@ -1463,7 +2018,7 @@ query_addsoa(ns_client_t *client, dns_db_t *db, isc_boolean_t zero_ttl) { } static inline isc_result_t -query_addns(ns_client_t *client, dns_db_t *db) { +query_addns(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version) { dns_name_t *name, *fname; dns_dbnode_t *node; isc_result_t result, eresult; @@ -1510,13 +2065,25 @@ query_addns(ns_client_t *client, dns_db_t *db) { /* * Find the NS rdataset. */ - CTRACE("query_addns: calling dns_db_find"); - result = dns_db_find(db, name, NULL, dns_rdatatype_ns, - client->query.dboptions, 0, &node, - fname, rdataset, sigrdataset); - CTRACE("query_addns: dns_db_find complete"); + result = dns_db_getnsnode(db, &node); + if (result == ISC_R_SUCCESS) { + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_ns, + 0, client->now, rdataset, + sigrdataset); + } else { + CTRACE("query_addns: calling dns_db_find"); + result = dns_db_find(db, name, NULL, dns_rdatatype_ns, + client->query.dboptions, 0, &node, + fname, rdataset, sigrdataset); + CTRACE("query_addns: dns_db_find complete"); + + if (result == ISC_R_SUCCESS) + (void)dns_db_setnsnode(db, node); + } if (result != ISC_R_SUCCESS) { - CTRACE("query_addns: dns_db_find failed"); + CTRACE("query_addns: " + "dns_db_findrdataset or dns_db_find failed"); /* * This is bad. We tried to get the NS rdataset at the zone * top and it didn't work! @@ -2514,7 +3081,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) if (is_zone) authoritative = ISC_TRUE; - + if (event == NULL && client->query.restarts == 0) { if (is_zone) { dns_zone_attach(zone, &client->query.authzone); @@ -2838,7 +3405,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) /* * Add SOA. */ - result = query_addsoa(client, db, ISC_FALSE); + result = query_addsoa(client, db, version, ISC_FALSE); if (result != ISC_R_SUCCESS) { QUERY_ERROR(result); goto cleanup; @@ -2879,9 +3446,9 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) * resolver and not have it cached. */ if (qtype == dns_rdatatype_soa) - result = query_addsoa(client, db, ISC_TRUE); + result = query_addsoa(client, db, version, ISC_TRUE); else - result = query_addsoa(client, db, ISC_FALSE); + result = query_addsoa(client, db, version, ISC_FALSE); if (result != ISC_R_SUCCESS) { QUERY_ERROR(result); goto cleanup; @@ -3205,7 +3772,8 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) /* * Add SOA. */ - result = query_addsoa(client, db, ISC_FALSE); + result = query_addsoa(client, db, version, + ISC_FALSE); if (result == ISC_R_SUCCESS) result = ISC_R_NOMORE; } else { @@ -3257,7 +3825,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) qtype == dns_rdatatype_any) && dns_name_equal(client->query.qname, dns_db_origin(db)))) - (void)query_addns(client, db); + (void)query_addns(client, db, version); } else if (qtype != dns_rdatatype_ns) { if (fname != NULL) query_releasename(client, &fname); diff --git a/bin/named/server.c b/bin/named/server.c index 513b2e446f..4b22013393 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: server.c,v 1.433 2004/11/10 22:14:28 marka Exp $ */ +/* $Id: server.c,v 1.434 2004/12/21 10:45:15 jinmei Exp $ */ #include @@ -41,6 +41,7 @@ #include +#include #include #include #include @@ -733,6 +734,7 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, isc_result_t result; isc_uint32_t max_adb_size; isc_uint32_t max_cache_size; + isc_uint32_t max_acache_size; isc_uint32_t lame_ttl; dns_tsig_keyring_t *ring; dns_view_t *pview = NULL; /* Production view */ @@ -776,6 +778,51 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, CHECKM(ns_config_getport(config, &port), "port"); dns_view_setdstport(view, port); + /* + * Create additional cache for this view and zones under the view + * unless explicitly disabled. + */ + obj = NULL; + ns_config_get(maps, "use-additional-cache", &obj); + if (obj == NULL || cfg_obj_asboolean(obj)) { + cmctx = NULL; + CHECK(isc_mem_create(0, 0, &cmctx)); + CHECK(dns_acache_create(&view->acache, cmctx, ns_g_taskmgr, + ns_g_timermgr)); + isc_mem_detach(&cmctx); + } + if (view->acache != NULL) { + obj = NULL; + result = ns_config_get(maps, "acache-cleaning-interval", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_acache_setcleaninginterval(view->acache, + cfg_obj_asuint32(obj) * 60); + + obj = NULL; + result = ns_config_get(maps, "max-acache-size", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isstring(obj)) { + str = cfg_obj_asstring(obj); + INSIST(strcasecmp(str, "unlimited") == 0); + max_acache_size = ISC_UINT32_MAX; + } else { + isc_resourcevalue_t value; + + value = cfg_obj_asuint64(obj); + if (value > ISC_UINT32_MAX) { + cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, + "'max-acache-size " + "%" ISC_PRINT_QUADFORMAT + "d' is too large", + value); + result = ISC_R_RANGE; + goto cleanup; + } + max_acache_size = (isc_uint32_t)value; + } + dns_acache_setcachesize(view->acache, max_acache_size); + } + /* * Configure the zones. */ @@ -1737,6 +1784,8 @@ configure_zone(cfg_obj_t *config, cfg_obj_t *zconfig, cfg_obj_t *vconfig, * new view. */ dns_zone_setview(zone, view); + if (view->acache != NULL) + dns_zone_setacache(zone, view->acache); } else { /* * We cannot reuse an existing zone, we have @@ -1745,6 +1794,8 @@ configure_zone(cfg_obj_t *config, cfg_obj_t *zconfig, cfg_obj_t *vconfig, CHECK(dns_zone_create(&zone, mctx)); CHECK(dns_zone_setorigin(zone, origin)); dns_zone_setview(zone, view); + if (view->acache != NULL) + dns_zone_setacache(zone, view->acache); CHECK(dns_zonemgr_managezone(ns_g_server->zonemgr, zone)); } diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 8ecb232287..04882f40bf 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -2,7 +2,7 @@ - + BIND 9 Administrator Reference Manual @@ -392,7 +392,12 @@ signed zones, serving many thousands of queries per second. cache and zones loaded off disk. The max-cache-size option can be used to limit the amount of memory used by the cache, at the expense of reducing cache hit rates and causing more DNS -traffic. It is still good practice to have enough memory to load +traffic. +Additionally, if additional section caching +() is enabled, +the max-acache-size can be used to limit the amount +of memory used by the mechanism. +It is still good practice to have enough memory to load all zone and cache data into memory — unfortunately, the best way to determine this for a given installation is to watch the name server in operation. After a few weeks the server process should reach @@ -2800,8 +2805,11 @@ statement in the named.conf file: edns-udp-size number; root-delegation-only exclude { namelist } ; querylog yes_or_no ; -}; disable-algorithms domain { algorithm; algorithm; }; + use-additional-cache yes_or_no ; + acache-cleaning-interval number; + max-acache-size size_spec ; +}; @@ -4346,6 +4354,99 @@ to be incremented, and may additionally cause the + +Additional Section Caching + + +The additional section cache, also called acache, +is an internal cache to improve the response performance of BIND 9. +When the additional section caching is enabled, BIND 9 will +cache internal short-cut to the additional section content for each +answer RR. +Note that acache is an internal caching mechanism of BIND 9, and is +not relevant to the DNS caching server function. + + + +The additional section caching does not make any difference on the +response content (except the RRsets ordering of the additional +section, see below), but can improve the response performance significantly. +It is particularly effective when BIND 9 acts as an authoritative server +for a zone that has many delegations with many glue RRs. + + + +In order to achieve the maximum performance improvement by acache, +it is recommended to set additional-from-cache +to no, since the current implementation of acache +does not make a short-cut of additional section information from a DNS +cache data. + + + +One obvious disadvantage of acache is that it requires much more +memory for the internal cached data. +Thus, if the response performance does not matter and memory +consumption is much more severe, the acache mechanism can be +disabled by setting use-additional-cache to +no. +It is also possible to specify the upper limit of memory consumption +for acache by max-acache-size. + + + +The additional section caching also has a minor effect on the RRset +ordering in the additional section. +Without acache, the "cyclic" order is effective for the additional +section as well as the answer and authority sections. +However, the additional section caching fixes the ordering when it +first caches an RRset for the additional section, and the same +ordering will be kept in succeeding responses, regardless of the +configuration for rrset-order. +This should be minor, though, since an RRset in the additional section +typically only contains a small number of RRs (and in many cases it +only contains a single RR), in which case the +ordering does not matter much. + + + +The following is a summary of options related to acache. + + + + +use-additional-cache + +If yes, the additional section caching is enabled. +The default value is yes. + + + +acache-cleaning-interval + +The server will remove stale cache entries, based on an LRU based +algorithm, every acache-cleaning-interval minutes. +The default is 60 minutes. +If set to 0, no periodic cleaning will occur. + + + +max-acache-size + +The maximum amount of memory to use for the server's acache, in bytes. +When the amount of data in the acache reaches this limit, the server +will cause more aggressive cleaning so that the limit is not exceeded. +In a server with multiple views, the limit applies separately to the +acache of each view. +The default is unlimited, meaning that +entries are purged from acache only at the periodic cleaning time. + + + + + + + diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index b4304d80ec..248f874649 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -13,7 +13,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: Makefile.in,v 1.148 2004/12/09 01:40:59 marka Exp $ +# $Id: Makefile.in,v 1.149 2004/12/21 10:45:16 jinmei Exp $ srcdir = @srcdir@ VPATH = @srcdir@ @@ -49,7 +49,7 @@ DSTOBJS = dst_api.@O@ dst_lib.@O@ dst_parse.@O@ dst_result.@O@ \ opensslrsa_link.@O@ # Alphabetically -DNSOBJS = acl.@O@ adb.@O@ byaddr.@O@ \ +DNSOBJS = acache.@O@ acl.@O@ adb.@O@ byaddr.@O@ \ cache.@O@ callbacks.@O@ compress.@O@ \ db.@O@ dbiterator.@O@ dbtable.@O@ diff.@O@ dispatch.@O@ \ dnssec.@O@ ds.@O@ forward.@O@ journal.@O@ keytable.@O@ \ @@ -73,7 +73,7 @@ DSTSRCS = dst_api.c dst_lib.c dst_parse.c \ openssl_link.c openssldh_link.c \ openssldsa_link.c opensslrsa_link.c -DNSSRCS = acl.c adb.c byaddr.c \ +DNSSRCS = acache.c acl.c adb.c byaddr.c \ cache.c callbacks.c compress.c \ db.c dbiterator.c dbtable.c diff.c dispatch.c \ dnssec.c ds.c forward.c journal.c keytable.c \ diff --git a/lib/dns/acache.c b/lib/dns/acache.c new file mode 100644 index 0000000000..a59d326f64 --- /dev/null +++ b/lib/dns/acache.c @@ -0,0 +1,1575 @@ +/* + * Copyright (C) 2003 Internet Software Consortium. + * + * Permission to use, copy, modify, and 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 INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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: acache.c,v 1.2 2004/12/21 10:45:16 jinmei Exp $ */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ACACHE_MAGIC ISC_MAGIC('A', 'C', 'H', 'E') +#define DNS_ACACHE_VALID(acache) ISC_MAGIC_VALID(acache, ACACHE_MAGIC) + +#define ACACHEENTRY_MAGIC ISC_MAGIC('A', 'C', 'E', 'T') +#define DNS_ACACHEENTRY_VALID(entry) ISC_MAGIC_VALID(entry, ACACHEENTRY_MAGIC) + +#define DBBUCKETS 67 + +#if 0 +#define ATRACE(m) isc_log_write(dns_lctx, \ + DNS_LOGCATEGORY_DATABASE, \ + DNS_LOGMODULE_ACACHE, \ + ISC_LOG_DEBUG(3), \ + "acache %p: %s", acache, (m)) +#define AATRACE(a,m) isc_log_write(dns_lctx, \ + DNS_LOGCATEGORY_DATABASE, \ + DNS_LOGMODULE_ACACHE, \ + ISC_LOG_DEBUG(3), \ + "acache %p: %s", (a), (m)) +#else +#define ATRACE(m) +#define AATRACE(a, m) +#endif + +/* + * The following variables control incremental cleaning. + * MINSIZE is how many bytes is the floor for dns_acache_setcachesize(). + * CLEANERINCREMENT is how many entries are examined in one pass. + * (XXX simply derived from definitions in cache.c There may be better + * constants here.) + */ +#define DNS_ACACHE_MINSIZE 2097152 /* Bytes. 2097152 = 2 MB */ +#define DNS_ACACHE_CLEANERINCREMENT 1000 /* Number of entries. */ + +/* Locked by acache lock */ +typedef struct dbentry { + ISC_LINK(struct dbentry) link; + + dns_db_t *db; + ISC_LIST(dns_acacheentry_t) originlist; + ISC_LIST(dns_acacheentry_t) referlist; +} dbentry_t; + +typedef ISC_LIST(dbentry_t) dbentrylist_t; + +typedef struct acache_cleaner acache_cleaner_t; + +typedef enum { + cleaner_s_idle, /* Waiting for cleaning-interval to expire. */ + cleaner_s_busy, /* Currently cleaning. */ + cleaner_s_done /* Freed enough memory after being overmem. */ +} cleaner_state_t; + +/* + * Convenience macros for comprehensive assertion checking. + */ +#define CLEANER_IDLE(c) ((c)->state == cleaner_s_idle && \ + (c)->resched_event != NULL) +#define CLEANER_BUSY(c) ((c)->state == cleaner_s_busy && \ + (c)->resched_event == NULL) + +struct acache_cleaner { + isc_mutex_t lock; + /* + * Locks overmem_event, overmem. (See cache.c) + */ + + dns_acache_t *acache; + unsigned int cleaning_interval; /* The cleaning-interval + from named.conf, + in seconds. */ + + isc_timer_t *cleaning_timer; + isc_event_t *resched_event; /* Sent by cleaner task to + itself to reschedule */ + isc_event_t *overmem_event; + + dns_acacheentry_t *current_entry; /* The bookmark entry to + restart the cleaning. + Locked by acache lock. */ + int increment; /* Number of entries to + clean in one increment */ + + unsigned long ncleaned; /* Number of entries cleaned + up (for logging purposes) */ + cleaner_state_t state; /* Idle/Busy/Done. */ + isc_boolean_t overmem; /* The acache is in an overmem + state. */ +}; + +/* + * The actual acache object. + */ + +struct dns_acache { + unsigned int magic; + + isc_mem_t *mctx; + isc_refcount_t refs; + + isc_mutex_t lock; + + int live_cleaners; + acache_cleaner_t cleaner; + ISC_LIST(dns_acacheentry_t) entries; + unsigned int dbentries; + dbentrylist_t dbbucket[DBBUCKETS]; + + isc_boolean_t shutting_down; + + isc_task_t *task; + isc_event_t cevent; + isc_boolean_t cevent_sent; +}; + +struct dns_acacheentry { + unsigned int magic; + + isc_mutex_t lock; + isc_refcount_t references; + + dns_acache_t *acache; + + /* Data for Management of cache entries */ + ISC_LINK(dns_acacheentry_t) link; + ISC_LINK(dns_acacheentry_t) olink; + ISC_LINK(dns_acacheentry_t) rlink; + + dns_db_t *origdb; /* reference to the DB + holding this entry */ + + /* Cache data */ + dns_zone_t *zone; /* zone this entry + belongs to */ + dns_db_t *db; /* DB this entry belongs to */ + dns_dbversion_t *version; /* the version of the DB */ + dns_dbnode_t *node; /* node this entry + belongs to */ + dns_name_t *foundname; /* corresponding DNS name + and rdataset */ + + /* Callback function and its argument */ + void (*callback)(dns_acacheentry_t *, void **); + void *cbarg; + + /* Timestamp of the last time this entry is referred to */ + isc_stdtime_t lastused; +}; + +/* + * Internal functions (and prototypes). + */ +static inline isc_boolean_t check_noentry(dns_acache_t *acache); +static void destroy(dns_acache_t *acache); +static void shutdown_entries(dns_acache_t *acache); +static void shutdown_buckets(dns_acache_t *acache); +static void destroy_entry(dns_acacheentry_t *ent); +static inline void unlink_dbentries(dns_acache_t *acache, + dns_acacheentry_t *ent); +static inline isc_result_t finddbent(dns_acache_t *acache, + dns_db_t *db, dbentry_t **dbentryp); +static inline void clear_entry(dns_acache_t *acache, dns_acacheentry_t *entry); +static isc_result_t acache_cleaner_init(dns_acache_t *acache, + isc_timermgr_t *timermgr, + acache_cleaner_t *cleaner); +static void acache_cleaning_timer_action(isc_task_t *task, isc_event_t *event); +static void acache_incremental_cleaning_action(isc_task_t *task, + isc_event_t *event); +static void acache_overmem_cleaning_action(isc_task_t *task, + isc_event_t *event); +static void acache_cleaner_shutdown_action(isc_task_t *task, + isc_event_t *event); + +/* + * The acache must be locked before calling. + */ +static inline isc_boolean_t +check_noentry(dns_acache_t *acache) { + if (ISC_LIST_EMPTY(acache->entries) && acache->dbentries == 0) { + return (ISC_TRUE); + } + + return (ISC_FALSE); +} + +/* + * The acache must be locked before calling. + */ +static void +shutdown_entries(dns_acache_t *acache) { + dns_acacheentry_t *entry, *entry_next; + + REQUIRE(DNS_ACACHE_VALID(acache)); + INSIST(acache->shutting_down); + + /* + * Release the dependency of all entries, and detach them. + */ + for (entry = ISC_LIST_HEAD(acache->entries); + entry != NULL; + entry = entry_next) { + entry_next = ISC_LIST_NEXT(entry, link); + + LOCK(&entry->lock); + + /* + * If the cleaner holds this entry, it will be unlinked and + * freed in the cleaner later. + */ + if (acache->cleaner.current_entry != entry) + ISC_LIST_UNLINK(acache->entries, entry, link); + unlink_dbentries(acache, entry); + if (entry->callback != NULL) { + (entry->callback)(entry, &entry->cbarg); + entry->callback = NULL; + } + + UNLOCK(&entry->lock); + + if (acache->cleaner.current_entry != entry) + dns_acache_detachentry(&entry); + } +} + +/* + * The acache must be locked before calling. + */ +static void +shutdown_buckets(dns_acache_t *acache) { + int i; + dbentry_t *dbent; + + REQUIRE(DNS_ACACHE_VALID(acache)); + INSIST(acache->shutting_down); + + for (i = 0; i < DBBUCKETS; i++) { + while ((dbent = ISC_LIST_HEAD(acache->dbbucket[i])) != NULL) { + INSIST(ISC_LIST_EMPTY(dbent->originlist) && + ISC_LIST_EMPTY(dbent->referlist)); + ISC_LIST_UNLINK(acache->dbbucket[i], dbent, link); + + dns_db_detach(&dbent->db); + + isc_mem_put(acache->mctx, dbent, sizeof(*dbent)); + + acache->dbentries--; + } + } + + INSIST(acache->dbentries == 0); +} + +static void +shutdown_task(isc_task_t *task, isc_event_t *ev) { + dns_acache_t *acache; + + UNUSED(task); + + acache = ev->ev_arg; + INSIST(DNS_ACACHE_VALID(acache)); + + isc_event_free(&ev); + + LOCK(&acache->lock); + + shutdown_entries(acache); + shutdown_buckets(acache); + + UNLOCK(&acache->lock); + + dns_acache_detach(&acache); +} + +/* The acache and the entry must be locked before calling. */ +static inline void +unlink_dbentries(dns_acache_t *acache, dns_acacheentry_t *ent) { + isc_result_t result; + dbentry_t *dbent; + + if (ISC_LINK_LINKED(ent, olink)) { + INSIST(ent->origdb != NULL); + dbent = NULL; + result = finddbent(acache, ent->origdb, &dbent); + INSIST(result == ISC_R_SUCCESS); + + ISC_LIST_UNLINK(dbent->originlist, ent, olink); + } + if (ISC_LINK_LINKED(ent, rlink)) { + INSIST(ent->db != NULL); + dbent = NULL; + result = finddbent(acache, ent->db, &dbent); + INSIST(result == ISC_R_SUCCESS); + + ISC_LIST_UNLINK(dbent->referlist, ent, rlink); + } +} + +/* There must not be a reference to this entry. */ +static void +destroy_entry(dns_acacheentry_t *entry) { + dns_acache_t *acache; + + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + + acache = entry->acache; + REQUIRE(DNS_ACACHE_VALID(acache)); + + /* + * Since there is no reference to this entry, it is safe to call + * clear_entry() here. + */ + clear_entry(acache, entry); + + isc_mem_put(acache->mctx, entry, sizeof(*entry)); + + dns_acache_detach(&acache); +} + +static void +destroy(dns_acache_t *acache) { + isc_mem_t *mctx; + + REQUIRE(DNS_ACACHE_VALID(acache)); + + ATRACE("destroy"); + + isc_mem_setwater(acache->mctx, NULL, NULL, 0, 0); + + if (acache->cleaner.overmem_event != NULL) + isc_event_free(&acache->cleaner.overmem_event); + + if (acache->cleaner.resched_event != NULL) + isc_event_free(&acache->cleaner.resched_event); + + if (acache->task != NULL) + isc_task_detach(&acache->task); + + DESTROYLOCK(&acache->cleaner.lock); + + DESTROYLOCK(&acache->lock); + acache->magic = 0; + mctx = acache->mctx; + + isc_mem_putanddetach(&acache->mctx, acache, sizeof(*acache)); +} + +static inline isc_result_t +finddbent(dns_acache_t *acache, dns_db_t *db, dbentry_t **dbentryp) { + int bucket; + dbentry_t *dbentry; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(db != NULL); + REQUIRE(dbentryp != NULL && *dbentryp == NULL); + + /* + * The caller must be holding the acache lock. + */ + + bucket = isc_hash_calc((const unsigned char *)&db, + sizeof(db), ISC_TRUE) % DBBUCKETS; + + for (dbentry = ISC_LIST_HEAD(acache->dbbucket[bucket]); + dbentry != NULL; + dbentry = ISC_LIST_NEXT(dbentry, link)) { + if (dbentry->db == db) + break; + } + + *dbentryp = dbentry; + + if (dbentry == NULL) + return (ISC_R_NOTFOUND); + else + return (ISC_R_SUCCESS); +} + +static inline void +clear_entry(dns_acache_t *acache, dns_acacheentry_t *entry) { + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + + /* + * The caller must be holing the entry lock. + */ + + if (entry->foundname) { + dns_rdataset_t *rdataset, *rdataset_next; + + for (rdataset = ISC_LIST_HEAD(entry->foundname->list); + rdataset != NULL; + rdataset = rdataset_next) { + rdataset_next = ISC_LIST_NEXT(rdataset, link); + ISC_LIST_UNLINK(entry->foundname->list, + rdataset, link); + dns_rdataset_disassociate(rdataset); + isc_mem_put(acache->mctx, rdataset, sizeof(*rdataset)); + } + if (dns_name_dynamic(entry->foundname)) + dns_name_free(entry->foundname, acache->mctx); + isc_mem_put(acache->mctx, entry->foundname, + sizeof(*entry->foundname)); + entry->foundname = NULL; + } + + if (entry->node != NULL) { + INSIST(entry->db != NULL); + dns_db_detachnode(entry->db, &entry->node); + } + if (entry->version != NULL) { + INSIST(entry->db != NULL); + dns_db_closeversion(entry->db, &entry->version, ISC_FALSE); + } + if (entry->db != NULL) + dns_db_detach(&entry->db); + if (entry->zone != NULL) + dns_zone_detach(&entry->zone); + + if (entry->origdb != NULL) + dns_db_detach(&entry->origdb); +} + +static isc_result_t +acache_cleaner_init(dns_acache_t *acache, isc_timermgr_t *timermgr, + acache_cleaner_t *cleaner) +{ + int result; + + ATRACE("acache cleaner init"); + + result = isc_mutex_init(&cleaner->lock); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed: %s", + dns_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto fail; + } + + cleaner->increment = DNS_ACACHE_CLEANERINCREMENT; + cleaner->state = cleaner_s_idle; + cleaner->acache = acache; + cleaner->overmem = ISC_FALSE; + + cleaner->cleaning_timer = NULL; + cleaner->resched_event = NULL; + cleaner->overmem_event = NULL; + cleaner->current_entry = NULL; + + if (timermgr != NULL) { + cleaner->acache->live_cleaners++; + + result = isc_task_onshutdown(acache->task, + acache_cleaner_shutdown_action, + acache); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "acache cleaner: " + "isc_task_onshutdown() failed: %s", + dns_result_totext(result)); + goto cleanup; + } + + cleaner->cleaning_interval = 0; /* Initially turned off. */ + result = isc_timer_create(timermgr, isc_timertype_inactive, + NULL, NULL, + acache->task, + acache_cleaning_timer_action, + cleaner, &cleaner->cleaning_timer); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_timer_create() failed: %s", + dns_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + cleaner->resched_event = + isc_event_allocate(acache->mctx, cleaner, + DNS_EVENT_ACACHECLEAN, + acache_incremental_cleaning_action, + cleaner, sizeof(isc_event_t)); + if (cleaner->resched_event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + cleaner->overmem_event = + isc_event_allocate(acache->mctx, cleaner, + DNS_EVENT_ACACHEOVERMEM, + acache_overmem_cleaning_action, + cleaner, sizeof(isc_event_t)); + if (cleaner->overmem_event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } + + return (ISC_R_SUCCESS); + + cleanup: + if (cleaner->overmem_event != NULL) + isc_event_free(&cleaner->overmem_event); + if (cleaner->resched_event != NULL) + isc_event_free(&cleaner->resched_event); + if (cleaner->cleaning_timer != NULL) + isc_timer_detach(&cleaner->cleaning_timer); + cleaner->acache->live_cleaners--; + DESTROYLOCK(&cleaner->lock); + fail: + return (result); +} + +static void +begin_cleaning(acache_cleaner_t *cleaner) { + dns_acacheentry_t *head; + dns_acache_t *acache = cleaner->acache; + + /* + * This function does not have to lock the cleaner, since critical + * parameters (except current_entry, which is locked by acache lock,) + * are only used in a single task context. + */ + + REQUIRE(CLEANER_IDLE(cleaner)); + INSIST(DNS_ACACHE_VALID(acache)); + INSIST(cleaner->current_entry == NULL); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, ISC_LOG_DEBUG(1), + "begin acache cleaning, mem inuse %lu", + (unsigned long)isc_mem_inuse(cleaner->acache->mctx)); + + LOCK(&acache->lock); + + head = ISC_LIST_HEAD(acache->entries); + if (head != NULL) + dns_acache_attachentry(head, &cleaner->current_entry); + + UNLOCK(&acache->lock); + + if (cleaner->current_entry != NULL) { + cleaner->ncleaned = 0; + cleaner->state = cleaner_s_busy; + isc_task_send(acache->task, &cleaner->resched_event); + } + + return; +} + +static void +end_cleaning(acache_cleaner_t *cleaner, isc_event_t *event) { + dns_acache_t *acache = cleaner->acache; + + REQUIRE(CLEANER_BUSY(cleaner)); + REQUIRE(event != NULL); + REQUIRE(DNS_ACACHEENTRY_VALID(cleaner->current_entry)); + + /* No need to lock the cleaner (see begin_cleaning()). */ + + LOCK(&acache->lock); + + /* + * Even if the cleaner has the last reference to the entry, which means + * the entry has been unused, it may still be linked if unlinking the + * entry has been delayed due to the reference. + */ + if (isc_refcount_current(&cleaner->current_entry->references) == 1) { + INSIST(cleaner->current_entry->callback == NULL); + + if (ISC_LINK_LINKED(cleaner->current_entry, link)) { + ISC_LIST_UNLINK(acache->entries, + cleaner->current_entry, link); + } + } + dns_acache_detachentry(&cleaner->current_entry); + + UNLOCK(&acache->lock); + + dns_acache_setcleaninginterval(cleaner->acache, + cleaner->cleaning_interval); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE, + ISC_LOG_DEBUG(1), "end acache cleaning, " + "%lu entries cleaned, mem inuse %lu", + cleaner->ncleaned, + (unsigned long)isc_mem_inuse(cleaner->acache->mctx)); + + if (cleaner->overmem) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, ISC_LOG_NOTICE, + "acache is still in overmem state " + "after cleaning"); + } + + cleaner->ncleaned = 0; + cleaner->state = cleaner_s_idle; + cleaner->resched_event = event; +} + +/* + * This is run once for every acache-cleaning-interval as defined + * in named.conf. + */ +static void +acache_cleaning_timer_action(isc_task_t *task, isc_event_t *event) { + acache_cleaner_t *cleaner = event->ev_arg; + + UNUSED(task); + + INSIST(event->ev_type == ISC_TIMEREVENT_TICK); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE, + ISC_LOG_DEBUG(1), "acache cleaning timer fired, " + "cleaner state = %d", cleaner->state); + + if (cleaner->state == cleaner_s_idle) + begin_cleaning(cleaner); + + isc_event_free(&event); +} + +/* The caller must hold entry lock. */ +static inline isc_boolean_t +entry_stale(acache_cleaner_t *cleaner, dns_acacheentry_t *entry, + isc_stdtime_t now) +{ + unsigned int interval = cleaner->cleaning_interval; + + /* + * If the callback has been canceled, we definitely do not need the + * entry. + */ + if (entry->callback == NULL) + return (ISC_TRUE); + + if (entry->lastused + interval < now) + return (ISC_TRUE); + + /* + * If the acache is in an overmem state, probabilistically decide if + * the entry should be purged, based on the time passed from its last + * use and the cleaning interval. + */ + if (cleaner->overmem) { + unsigned int passed = now - entry->lastused; /* <= interval */ + isc_uint32_t val, r; + + isc_random_get(&val); + r = val % interval; + + if (r < passed) + return (ISC_TRUE); + } + + return (ISC_FALSE); +} + +/* + * Do incremental cleaning. + */ +static void +acache_incremental_cleaning_action(isc_task_t *task, isc_event_t *event) { + acache_cleaner_t *cleaner = event->ev_arg; + dns_acache_t *acache = cleaner->acache; + dns_acacheentry_t *entry, *next = NULL; + int n_entries; + isc_stdtime_t now; + + INSIST(DNS_ACACHE_VALID(acache)); + INSIST(task == acache->task); + INSIST(event->ev_type == DNS_EVENT_ACACHECLEAN); + + if (cleaner->state == cleaner_s_done) { + cleaner->state = cleaner_s_busy; + end_cleaning(cleaner, event); + return; + } + + INSIST(CLEANER_BUSY(cleaner)); + + n_entries = cleaner->increment; + + isc_stdtime_get(&now); + + LOCK(&acache->lock); + + entry = cleaner->current_entry; + + while (n_entries-- > 0) { + isc_boolean_t is_stale = ISC_FALSE; + + INSIST(entry != NULL); + + next = ISC_LIST_NEXT(entry, link); + + LOCK(&entry->lock); + + is_stale = entry_stale(cleaner, entry, now); + if (is_stale) { + ISC_LIST_UNLINK(acache->entries, entry, link); + unlink_dbentries(acache, entry); + if (entry->callback != NULL) + (entry->callback)(entry, &entry->cbarg); + entry->callback = NULL; + + cleaner->ncleaned++; + } + + UNLOCK(&entry->lock); + + if (is_stale) + dns_acache_detachentry(&entry); + + if (next == NULL) { + UNLOCK(&acache->lock); + end_cleaning(cleaner, event); + return; + } + + entry = next; + } + + /* + * We have successfully performed a cleaning increment but have + * not gone through the entire cache. Remember the entry that will + * be the starting point in the next clean-up, and reschedule another + * batch. If it fails, just try to continue anyway. + */ + INSIST(next != NULL && next != cleaner->current_entry); + dns_acache_detachentry(&cleaner->current_entry); + dns_acache_attachentry(next, &cleaner->current_entry); + + UNLOCK(&acache->lock); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE, + ISC_LOG_DEBUG(1), "acache cleaner: checked %d entries, " + "mem inuse %lu, sleeping", cleaner->increment, + (unsigned long)isc_mem_inuse(cleaner->acache->mctx)); + + isc_task_send(task, &event); + INSIST(CLEANER_BUSY(cleaner)); + + return; +} + +/* + * This is called when the acache either surpasses its upper limit + * or shrinks beyond its lower limit. + */ +static void +acache_overmem_cleaning_action(isc_task_t *task, isc_event_t *event) { + acache_cleaner_t *cleaner = event->ev_arg; + isc_boolean_t want_cleaning = ISC_FALSE; + + UNUSED(task); + + INSIST(event->ev_type == DNS_EVENT_ACACHEOVERMEM); + INSIST(cleaner->overmem_event == NULL); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE, + ISC_LOG_DEBUG(1), "overmem_cleaning_action called, " + "overmem = %d, state = %d", cleaner->overmem, + cleaner->state); + + LOCK(&cleaner->lock); + + if (cleaner->overmem) { + if (cleaner->state == cleaner_s_idle) + want_cleaning = ISC_TRUE; + } else { + if (cleaner->state == cleaner_s_busy) + /* + * end_cleaning() can't be called here because + * then both cleaner->overmem_event and + * cleaner->resched_event will point to this + * event. Set the state to done, and then + * when the acache_incremental_cleaning_action() event + * is posted, it will handle the end_cleaning. + */ + cleaner->state = cleaner_s_done; + } + + cleaner->overmem_event = event; + + UNLOCK(&cleaner->lock); + + if (want_cleaning) + begin_cleaning(cleaner); +} + +static void +water(void *arg, int mark) { + dns_acache_t *acache = arg; + isc_boolean_t overmem = ISC_TF(mark == ISC_MEM_HIWATER); + + REQUIRE(DNS_ACACHE_VALID(acache)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, ISC_LOG_DEBUG(1), + "acache memory reaches %s watermark, mem inuse %lu", + overmem ? "high" : "low", + (unsigned long)isc_mem_inuse(acache->mctx)); + + LOCK(&acache->cleaner.lock); + + acache->cleaner.overmem = overmem; + + if (acache->cleaner.overmem_event != NULL) + isc_task_send(acache->task, &acache->cleaner.overmem_event); + + UNLOCK(&acache->cleaner.lock); +} + +/* + * The cleaner task is shutting down; do the necessary cleanup. + */ +static void +acache_cleaner_shutdown_action(isc_task_t *task, isc_event_t *event) { + dns_acache_t *acache = event->ev_arg; + isc_boolean_t should_free = ISC_FALSE; + + INSIST(task == acache->task); + INSIST(event->ev_type == ISC_TASKEVENT_SHUTDOWN); + INSIST(DNS_ACACHE_VALID(acache)); + + ATRACE("acache cleaner shutdown"); + + if (CLEANER_BUSY(&acache->cleaner)) + end_cleaning(&acache->cleaner, event); + else + isc_event_free(&event); + + LOCK(&acache->lock); + + acache->live_cleaners--; + INSIST(acache->live_cleaners == 0); + + if (isc_refcount_current(&acache->refs) == 0) { + INSIST(check_noentry(acache) == ISC_TRUE); + should_free = ISC_TRUE; + } + + /* + * By detaching the timer in the context of its task, + * we are guaranteed that there will be no further timer + * events. + */ + if (acache->cleaner.cleaning_timer != NULL) + isc_timer_detach(&acache->cleaner.cleaning_timer); + + /* Make sure we don't reschedule anymore. */ + (void)isc_task_purge(task, NULL, DNS_EVENT_ACACHECLEAN, NULL); + + UNLOCK(&acache->lock); + + if (should_free) + destroy(acache); +} + +/* + * Public functions. + */ + +isc_result_t +dns_acache_create(dns_acache_t **acachep, isc_mem_t *mctx, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr) +{ + int i; + isc_result_t result; + dns_acache_t *acache; + + REQUIRE(acachep != NULL && *acachep == NULL); + REQUIRE(mctx != NULL); + REQUIRE(taskmgr != NULL); + + acache = isc_mem_get(mctx, sizeof(*acache)); + if (acache == NULL) + return (ISC_R_NOMEMORY); + + ATRACE("create"); + + isc_refcount_init(&acache->refs, 1); + + result = isc_mutex_init(&acache->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, acache, sizeof(*acache)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed: %s", + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + + acache->mctx = NULL; + isc_mem_attach(mctx, &acache->mctx); + ISC_LIST_INIT(acache->entries); + + acache->shutting_down = ISC_FALSE; + + acache->task = NULL; + result = isc_task_create(taskmgr, 1, &acache->task); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_task_create() failed(): %s", + dns_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + isc_task_setname(acache->task, "acachetask", acache); + ISC_EVENT_INIT(&acache->cevent, sizeof(acache->cevent), 0, NULL, + DNS_EVENT_ACACHECONTROL, shutdown_task, NULL, + NULL, NULL, NULL); + acache->cevent_sent = ISC_FALSE; + + acache->dbentries = 0; + for (i = 0; i < DBBUCKETS; i++) + ISC_LIST_INIT(acache->dbbucket[i]); + + acache->live_cleaners = 0; + result = acache_cleaner_init(acache, timermgr, &acache->cleaner); + if (result != ISC_R_SUCCESS) + goto cleanup; + + acache->magic = ACACHE_MAGIC; + + *acachep = acache; + return (ISC_R_SUCCESS); + + cleanup: + if (acache->task != NULL) + isc_task_detach(&acache->task); + DESTROYLOCK(&acache->lock); + isc_mem_put(mctx, acache, sizeof(*acache)); + isc_mem_detach(&mctx); + + return (result); +} + +void +dns_acache_attach(dns_acache_t *source, dns_acache_t **targetp) { + REQUIRE(DNS_ACACHE_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + AATRACE(source, "attach"); + + isc_refcount_increment(&source->refs, NULL); + + *targetp = source; +} + +void +dns_acache_detach(dns_acache_t **acachep) { + dns_acache_t *acache; + unsigned int refs; + isc_boolean_t should_free = ISC_FALSE; + + REQUIRE(acachep != NULL && DNS_ACACHE_VALID(*acachep)); + acache = *acachep; + + ATRACE("detach"); + + isc_refcount_decrement(&acache->refs, &refs); + if (refs == 0) { + INSIST(check_noentry(acache) == ISC_TRUE); + should_free = ISC_TRUE; + } + + *acachep = NULL; + + /* + * If we're exiting and the cleaner task exists, let it free the cache. + */ + if (should_free && acache->live_cleaners > 0) { + isc_task_shutdown(acache->task); + should_free = ISC_FALSE; + } + + if (should_free) + destroy(acache); +} + +void +dns_acache_shutdown(dns_acache_t *acache) { + REQUIRE(DNS_ACACHE_VALID(acache)); + + LOCK(&acache->lock); + + ATRACE("shutdown"); + + if (!acache->shutting_down) { + isc_event_t *event; + dns_acache_t *acache_evarg = NULL; + + INSIST(!acache->cevent_sent); + + acache->shutting_down = ISC_TRUE; + + isc_mem_setwater(acache->mctx, NULL, NULL, 0, 0); + + /* + * Self attach the object in order to prevent it from being + * destroyed while waiting for the event. + */ + dns_acache_attach(acache, &acache_evarg); + event = &acache->cevent; + event->ev_arg = acache_evarg; + isc_task_send(acache->task, &event); + acache->cevent_sent = ISC_TRUE; + } + + UNLOCK(&acache->lock); +} + +isc_result_t +dns_acache_setdb(dns_acache_t *acache, dns_db_t *db) { + int bucket; + dbentry_t *dbentry; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(db != NULL); + + ATRACE("setdb"); + + LOCK(&acache->lock); + + dbentry = NULL; + result = finddbent(acache, db, &dbentry); + if (result == ISC_R_SUCCESS) { + result = ISC_R_EXISTS; + goto end; + } + result = ISC_R_SUCCESS; + + dbentry = isc_mem_get(acache->mctx, sizeof(*dbentry)); + if (dbentry == NULL) { + result = ISC_R_NOMEMORY; + goto end; + } + + ISC_LINK_INIT(dbentry, link); + ISC_LIST_INIT(dbentry->originlist); + ISC_LIST_INIT(dbentry->referlist); + + dbentry->db = NULL; + dns_db_attach(db, &dbentry->db); + + bucket = isc_hash_calc((const unsigned char *)&db, + sizeof(db), ISC_TRUE) % DBBUCKETS; + + ISC_LIST_APPEND(acache->dbbucket[bucket], dbentry, link); + + acache->dbentries++; + + end: + UNLOCK(&acache->lock); + + return (result); +} + +isc_result_t +dns_acache_putdb(dns_acache_t *acache, dns_db_t *db) { + int bucket; + isc_result_t result; + dbentry_t *dbentry; + dns_acacheentry_t *entry; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(db != NULL); + + ATRACE("putdb"); + + LOCK(&acache->lock); + + dbentry = NULL; + result = finddbent(acache, db, &dbentry); + if (result != ISC_R_SUCCESS) { + /* + * The entry may have not been created due to memory shortage. + */ + UNLOCK(&acache->lock); + return (ISC_R_NOTFOUND); + } + + /* + * Release corresponding cache entries: for each entry, release all + * links the entry has, and then callback to the entry holder (if any). + * If no other external references exist (this can happen if the + * original holder has canceled callback,) destroy it here. + */ + while ((entry = ISC_LIST_HEAD(dbentry->originlist)) != NULL) { + LOCK(&entry->lock); + + /* + * Releasing olink first would avoid finddbent() in + * unlink_dbentries(). + */ + ISC_LIST_UNLINK(dbentry->originlist, entry, olink); + if (acache->cleaner.current_entry == NULL || + acache->cleaner.current_entry != entry) { + ISC_LIST_UNLINK(acache->entries, entry, link); + } + unlink_dbentries(acache, entry); + + if (entry->callback != NULL) + (entry->callback)(entry, &entry->cbarg); + entry->callback = NULL; + + UNLOCK(&entry->lock); + + if (acache->cleaner.current_entry == NULL || + acache->cleaner.current_entry != entry) { + dns_acache_detachentry(&entry); + } + } + while ((entry = ISC_LIST_HEAD(dbentry->referlist)) != NULL) { + LOCK(&entry->lock); + + ISC_LIST_UNLINK(dbentry->referlist, entry, rlink); + if (acache->cleaner.current_entry == NULL || + acache->cleaner.current_entry != entry) { + ISC_LIST_UNLINK(acache->entries, entry, link); + } + unlink_dbentries(acache, entry); + + if (entry->callback != NULL) + (entry->callback)(entry, &entry->cbarg); + entry->callback = NULL; + + UNLOCK(&entry->lock); + + if (acache->cleaner.current_entry == NULL || + acache->cleaner.current_entry != entry) { + dns_acache_detachentry(&entry); + } + } + + INSIST(ISC_LIST_EMPTY(dbentry->originlist) && + ISC_LIST_EMPTY(dbentry->referlist)); + + bucket = isc_hash_calc((const unsigned char *)&db, + sizeof(db), ISC_TRUE) % DBBUCKETS; + ISC_LIST_UNLINK(acache->dbbucket[bucket], dbentry, link); + dns_db_detach(&dbentry->db); + + isc_mem_put(acache->mctx, dbentry, sizeof(*dbentry)); + + acache->dbentries--; + + UNLOCK(&acache->lock); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_acache_createentry(dns_acache_t *acache, dns_db_t *origdb, + void (*callback)(dns_acacheentry_t *, void **), + void *cbarg, dns_acacheentry_t **entryp) +{ + dns_acacheentry_t *newentry; + isc_result_t result; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(entryp != NULL && *entryp == NULL); + REQUIRE(origdb != NULL); + + newentry = isc_mem_get(acache->mctx, sizeof(*newentry)); + if (newentry == NULL) + return (ISC_R_NOMEMORY); + + result = isc_mutex_init(&newentry->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(acache->mctx, newentry, sizeof(*newentry)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed: %s", + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + }; + /* + * We need two counters on creation: one for the caller, and one for + * the cache object. + */ + isc_refcount_init(&newentry->references, 2); + + ISC_LINK_INIT(newentry, link); + ISC_LINK_INIT(newentry, olink); + ISC_LINK_INIT(newentry, rlink); + + newentry->acache = NULL; + dns_acache_attach(acache, &newentry->acache); + + newentry->zone = NULL; + newentry->db = NULL; + newentry->version = NULL; + newentry->node = NULL; + newentry->foundname = NULL; + + newentry->callback = callback; + newentry->cbarg = cbarg; + newentry->origdb = NULL; + dns_db_attach(origdb, &newentry->origdb); + + isc_stdtime_get(&newentry->lastused); + + newentry->magic = ACACHEENTRY_MAGIC; + + *entryp = newentry; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_acache_getentry(dns_acacheentry_t *entry, dns_zone_t **zonep, + dns_db_t **dbp, dns_dbversion_t **versionp, + dns_dbnode_t **nodep, dns_name_t *fname, + dns_message_t *msg, isc_stdtime_t now) +{ + isc_result_t result = ISC_R_SUCCESS; + dns_rdataset_t *erdataset; + + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + REQUIRE(zonep == NULL || *zonep == NULL); + REQUIRE(dbp != NULL && *dbp == NULL); + REQUIRE(versionp != NULL && *versionp == NULL); + REQUIRE(nodep != NULL && *nodep == NULL); + REQUIRE(fname != NULL); + REQUIRE(msg != NULL); + + LOCK(&entry->lock); + + entry->lastused = now; + + if (entry->zone != NULL && zonep != NULL) + dns_zone_attach(entry->zone, zonep); + + if (entry->db == NULL) { + *dbp = NULL; + *versionp = NULL; + } else { + dns_db_attach(entry->db, dbp); + dns_db_attachversion(entry->db, entry->version, versionp); + } + if (entry->node == NULL) + *nodep = NULL; + else { + dns_db_attachnode(entry->db, entry->node, nodep); + + INSIST(entry->foundname != NULL); + dns_name_copy(entry->foundname, fname, NULL); + for (erdataset = ISC_LIST_HEAD(entry->foundname->list); + erdataset != NULL; + erdataset = ISC_LIST_NEXT(erdataset, link)) { + dns_rdataset_t *ardataset; + + ardataset = NULL; + result = dns_message_gettemprdataset(msg, &ardataset); + if (result != ISC_R_SUCCESS) { + UNLOCK(&entry->lock); + goto fail; + } + + /* + * XXXJT: if we simply clone the rdataset, we'll get + * lost wrt cyclic ordering. We'll need an additional + * trick to get the latest counter from the original + * header. + */ + dns_rdataset_init(ardataset); + dns_rdataset_clone(erdataset, ardataset); + ISC_LIST_APPEND(fname->list, ardataset, link); + } + } + + UNLOCK(&entry->lock); + + return (result); + + fail: + while ((erdataset = ISC_LIST_HEAD(fname->list)) != NULL) { + ISC_LIST_UNLINK(fname->list, erdataset, link); + dns_rdataset_disassociate(erdataset); + dns_message_puttemprdataset(msg, &erdataset); + } + if (*nodep != NULL) + dns_db_detachnode(*dbp, nodep); + if (*versionp != NULL) + dns_db_closeversion(*dbp, versionp, ISC_FALSE); + if (*dbp != NULL) + dns_db_detach(dbp); + if (zonep != NULL && *zonep != NULL) + dns_zone_detach(zonep); + + return (result); +} + +isc_result_t +dns_acache_setentry(dns_acache_t *acache, dns_acacheentry_t *entry, + dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + dns_dbnode_t *node, dns_name_t *fname) +{ + isc_result_t result; + dbentry_t *odbent; + dbentry_t *rdbent = NULL; + isc_boolean_t close_version = ISC_FALSE; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + + LOCK(&acache->lock); /* XXX: need to lock it here for ordering */ + LOCK(&entry->lock); + + /* Set zone */ + if (zone != NULL) + dns_zone_attach(zone, &entry->zone); + /* Set DB */ + if (db != NULL) + dns_db_attach(db, &entry->db); + /* + * Set DB version. If the version is not given by the caller, + * which is the case for glue or cache DBs, use the current version. + */ + if (version == NULL) { + if (db != NULL) { + dns_db_currentversion(db, &version); + close_version = ISC_TRUE; + } + } + if (version != NULL) { + INSIST(db != NULL); + dns_db_attachversion(db, version, &entry->version); + } + if (close_version) + dns_db_closeversion(db, &version, ISC_FALSE); + /* Set DB node. */ + if (node != NULL) { + INSIST(db != NULL); + dns_db_attachnode(db, node, &entry->node); + } + + /* + * Set list of the corresponding rdatasets, if given. + * To minimize the overhead and memory consumption, we'll do this for + * positive cache only, in which case the DB node is non NULL. + * We do not want to cache incomplete information, so give up the + * entire entry when a memory shortage happen during the process. + */ + if (node != NULL) { + dns_rdataset_t *ardataset, *crdataset; + + entry->foundname = isc_mem_get(acache->mctx, + sizeof(*entry->foundname)); + + if (entry->foundname == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + dns_name_init(entry->foundname, NULL); + result = dns_name_dup(fname, acache->mctx, + entry->foundname); + if (result != ISC_R_SUCCESS) + goto fail; + + for (ardataset = ISC_LIST_HEAD(fname->list); + ardataset != NULL; + ardataset = ISC_LIST_NEXT(ardataset, link)) { + crdataset = isc_mem_get(acache->mctx, + sizeof(*crdataset)); + if (crdataset == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + + dns_rdataset_init(crdataset); + dns_rdataset_clone(ardataset, crdataset); + ISC_LIST_APPEND(entry->foundname->list, crdataset, + link); + } + } + + odbent = NULL; + result = finddbent(acache, entry->origdb, &odbent); + if (result != ISC_R_SUCCESS) + goto fail; + if (db != NULL) { + rdbent = NULL; + result = finddbent(acache, db, &rdbent); + if (result != ISC_R_SUCCESS) + goto fail; + } + + ISC_LIST_APPEND(acache->entries, entry, link); + ISC_LIST_APPEND(odbent->originlist, entry, olink); + if (rdbent != NULL) + ISC_LIST_APPEND(rdbent->referlist, entry, rlink); + + UNLOCK(&entry->lock); + UNLOCK(&acache->lock); + + return (ISC_R_SUCCESS); + + fail: + clear_entry(acache, entry); + + UNLOCK(&entry->lock); + UNLOCK(&acache->lock); + + return (result); +} + +void +dns_acache_cancelentry(dns_acacheentry_t *entry) { + dns_acache_t *acache = entry->acache; + + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + INSIST(DNS_ACACHE_VALID(acache)); + + LOCK(&acache->lock); + LOCK(&entry->lock); + + /* + * Release dependencies stored in this entry as much as possible. + * The main link cannot be released, since the acache object has + * a reference to this entry; the empty entry will be released in + * the next cleaning action. + */ + unlink_dbentries(acache, entry); + clear_entry(entry->acache, entry); + + entry->callback = NULL; + entry->cbarg = NULL; + + UNLOCK(&entry->lock); + UNLOCK(&acache->lock); +} + +void +dns_acache_attachentry(dns_acacheentry_t *source, + dns_acacheentry_t **targetp) +{ + REQUIRE(DNS_ACACHEENTRY_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references, NULL); + + *targetp = source; +} + +void +dns_acache_detachentry(dns_acacheentry_t **entryp) { + dns_acacheentry_t *entry; + unsigned int refs; + + REQUIRE(entryp != NULL && DNS_ACACHEENTRY_VALID(*entryp)); + entry = *entryp; + + isc_refcount_decrement(&entry->references, &refs); + + /* + * If there are no references to the entry, the entry must have been + * unlinked and can be destroyed safely. + */ + if (refs == 0) { + INSIST(!ISC_LINK_LINKED(entry, link)); + destroy_entry(entry); + } + + *entryp = NULL; +} + +void +dns_acache_setcleaninginterval(dns_acache_t *acache, unsigned int t) { + isc_interval_t interval; + isc_result_t result; + + REQUIRE(DNS_ACACHE_VALID(acache)); + + ATRACE("dns_acache_setcleaninginterval"); + + LOCK(&acache->lock); + + /* + * It may be the case that the acache has already shut down. + * If so, it has no timer. (Not sure if this can really happen.) + */ + if (acache->cleaner.cleaning_timer == NULL) + goto unlock; + + acache->cleaner.cleaning_interval = t; + + if (t == 0) { + result = isc_timer_reset(acache->cleaner.cleaning_timer, + isc_timertype_inactive, + NULL, NULL, ISC_TRUE); + } else { + isc_interval_set(&interval, acache->cleaner.cleaning_interval, + 0); + result = isc_timer_reset(acache->cleaner.cleaning_timer, + isc_timertype_ticker, + NULL, &interval, ISC_FALSE); + } + if (result != ISC_R_SUCCESS) + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, ISC_LOG_WARNING, + "could not set acache cleaning interval: %s", + isc_result_totext(result)); + + unlock: + UNLOCK(&acache->lock); +} + +/* + * This function was derived from cache.c:dns_cache_setcachesize(). See the + * function for more details about the logic. + */ +void +dns_acache_setcachesize(dns_acache_t *acache, isc_uint32_t size) { + isc_uint32_t lowater; + isc_uint32_t hiwater; + + REQUIRE(DNS_ACACHE_VALID(acache)); + + if (size != 0 && size < DNS_ACACHE_MINSIZE) + size = DNS_ACACHE_MINSIZE; + + hiwater = size - (size >> 3); + lowater = size - (size >> 2); + + if (size == 0 || hiwater == 0 || lowater == 0) + isc_mem_setwater(acache->mctx, water, acache, 0, 0); + else + isc_mem_setwater(acache->mctx, water, acache, + hiwater, lowater); +} diff --git a/lib/dns/db.c b/lib/dns/db.c index c8033469af..a539ebdee4 100644 --- a/lib/dns/db.c +++ b/lib/dns/db.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: db.c,v 1.74 2004/03/05 05:09:18 marka Exp $ */ +/* $Id: db.c,v 1.75 2004/12/21 10:45:16 jinmei Exp $ */ /*** *** Imports @@ -791,3 +791,51 @@ dns_db_unregister(dns_dbimplementation_t **dbimp) { isc_mem_detach(&mctx); RWUNLOCK(&implock, isc_rwlocktype_write); } + +isc_result_t +dns_db_getsoanode(dns_db_t *db, dns_dbnode_t **nodep) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db) == ISC_TRUE); + REQUIRE(nodep != NULL && *nodep == NULL); + + if (db->methods->getsoanode != NULL) + return ((db->methods->getsoanode)(db, nodep)); + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_db_setsoanode(dns_db_t *db, dns_dbnode_t *node) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db) == ISC_TRUE); + REQUIRE(node != NULL); + + if (db->methods->setsoanode != NULL) + return ((db->methods->setsoanode)(db, node)); + + return (ISC_R_FAILURE); +} + +isc_result_t +dns_db_getnsnode(dns_db_t *db, dns_dbnode_t **nodep) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db) == ISC_TRUE); + REQUIRE(nodep != NULL && *nodep == NULL); + + if (db->methods->getnsnode != NULL) + return ((db->methods->getnsnode)(db, nodep)); + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_db_setnsnode(dns_db_t *db, dns_dbnode_t *node) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db) == ISC_TRUE); + REQUIRE(node != NULL); + + if (db->methods->setnsnode != NULL) + return ((db->methods->setnsnode)(db, node)); + + return (ISC_R_FAILURE); +} diff --git a/lib/dns/include/dns/acache.h b/lib/dns/include/dns/acache.h new file mode 100644 index 0000000000..2969f77cbc --- /dev/null +++ b/lib/dns/include/dns/acache.h @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2003 Internet Software Consortium. + * + * Permission to use, copy, modify, and 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 INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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: acache.h,v 1.2 2004/12/21 10:45:18 jinmei Exp $ */ + +#ifndef DNS_ACACHE_H +#define DNS_ACACHE_H 1 + +/***** + ***** Module Info + *****/ + +/* + * Acache + * + * The Additional Cache Object + * + * This module manages internal caching entries that correspond to + * the additional section data of a DNS DB node (an RRset header, more + * accurately). An additional cache entry is expected to be (somehow) + * attached to a particular RR in a particular DB node, and contains a set + * of information of an additional data for the DB node. + * + * An additional cache object is intended to be created as a per-view + * object, and manages all cache entries within the view. + * + * The intended usage of the additional caching is to provide a short cut + * to additional glue RRs of an NS RR. For each NS RR, it is often + * necessary to look for glue RRs to make a proper response. Once the + * glue RRs are known, the additional caching allows the client to + * associate the information to the original NS RR so that further + * expensive lookups can be avoided for the NS RR. + * + * Each additional cache entry contains information to identify a + * particular DB node and (optionally) an associated RRset. The + * information consists of its zone, database, the version of the + * database, database node, and RRset. + * + * A "negative" information can also be cached. For example, if a glue + * RR does not exist as an authoritative data in the same zone as that + * of the NS RR, this fact can be cached by specifying a NULL pointer + * for the database, version, and node. (See the description for + * dns_acache_getentry() below for more details.) + * + * Since each member stored in an additional cache entry holds a reference + * to a corresponding object, a stale cache entry may cause unnecessary + * memory consumption. For instance, when a zone is reloaded, additional + * cache entries that have a reference to the zone (and its DB and/or + * DB nodes) can delay the cleanup of the referred objects. In order to + * minimize such a bad effect, this module provides several cleanup + * mechanisms. + * + * The first one is a shutdown procedure called when the associated view + * is shut down. In this case, dns_acache_shutdown() will be called and + * all cache entries will be purged. This mechanism will help the + * situation when the configuration is reloaded or the main server is + * stopped. + * + * Per-DB cleanup mechanism is also provided. Each additional cache entry + * is associated with related DB, which is expected to have been + * registered when the DB was created by dns_acache_setdb(). If a + * particular DB is going to be destroyed, the primary holder of the DB, + * a typical example of which is a zone, will call dns_acache_putdb(). + * Then this module will clean-up all cache entries associated with the + * DB. This mechanism is effective when a secondary zone DB is going to + * be stale after a zone transfer. + * + * Finally, this module supports for periodic clean-up of stale entries. + * Each cache entry has a timestamp field, which is updated every time + * the entry is referred. A periodically invoked cleaner checks the + * timestamp of each entry, and purge entries that have not been referred + * for a certain period. The cleaner interval can be specified by + * dns_acache_setcleaninginterval(). If the periodic clean-up is not + * enough, it is also possible to specify the upper limit of entries + * in terms of the memory consumption. If the maximum value is + * specified, the cleaner is invoked when the memory consumption reaches + * the high watermark inferred from the maximum value. In this case, + * the cleaner will use more aggressive algorithm to decide the "victim" + * entries. The maximum value can be specified by + * dns_acache_setcachesize(). + * + * When a cache entry is going to be purged within this module, the + * callback function specified at the creation time will be called. + * The callback function is expected to release all internal resources + * related to the entry, which will typically be specific to DB + * implementation, and to call dns_acache_detachentry(). The callback + * mechanism is very important, since the holder of an additional cache + * entry may not be able to initiate the clean-up of the entry, due to + * the reference ordering. For example, as long as an additional cache + * entry has a reference to a DB object, the DB cannot be freed, in which + * a DB node may have a reference to the cache entry. + * + * Credits: + * The basic idea of this kind of short-cut for frequently used + * information is similar to the "pre-compiled answer" approach adopted + * in nsd by NLnet LABS with RIPE NCC. Our work here is an independent + * effort, but the success of nsd encouraged us to pursue this path. + * + * The design and implementation of the periodic memory management and + * the upper limitation of memory consumption was derived from the cache + * DB implementation of BIND9. + * + * MP: + * There are two main locks in this module. One is for each entry, and + * the other is for the additional cache object. + * + * Reliability: + * The callback function for a cache entry is called with holding the + * entry lock. Thus, it implicitly assumes the callback function does not + * call a function that can require the lock. Typically, the only + * function that can be called from the callback function safely is + * dns_acache_detachentry(). The breakage of this implicit assumption + * may cause a deadlock. + * + * Resources: + * In a 32-bit architecture (such as i386), the following additional + * memory is required comparing to the case that disables this module. + * - 76 bytes for each additional cache entry + * - if the entry has a DNS name and associated RRset, + * * 44 bytes + size of the name (1-255 bytes) + * * 52 bytes x number_of_RRs + * - 28 bytes for each DB related to this module + * + * Using the additional cache also requires extra memory consumption in + * the DB implementation. In the current implementation for rbtdb, we + * need: + * - two additional pointers for each DB node (8 bytes for a 32-bit + * architecture + * - for each RR associated to an RR in a DB node, we also need + * a pointer and management objects to support the additional cache + * function. These are allocated on-demand. The total size is + * 32 bytes for a 32-bit architecture. + * + * Security: + * Since this module does not handle any low-level data directly, + * no security issue specific to this module is anticipated. + * + * Standards: + * None. + */ + +/*** + *** Imports + ***/ + +#include +#include +#include +#include + +#include + +/*** + *** Functions + ***/ +ISC_LANG_BEGINDECLS + +isc_result_t +dns_acache_create(dns_acache_t **acachep, isc_mem_t *mctx, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr); +/* + * Create a new DNS additional cache object. + * + * Requires: + * + * 'mctx' is a valid memory context + * + * 'taskmgr' is a valid task manager + * + * 'timermgr' is a valid timer or NULL. If NULL, no periodic cleaning of + * the cache will take place. + * + * 'acachep' is a valid pointer, and *acachep == NULL + * + * Ensures: + * + * '*acachep' is attached to the newly created cache + * + * Returns: + * + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + * ISC_R_UNEXPECTED + */ + +void +dns_acache_attach(dns_acache_t *source, dns_acache_t **targetp); +/* + * Attach *targetp to cache. + * + * Requires: + * + * 'acache' is a valid additional cache. + * + * 'targetp' points to a NULL dns_acache_t *. + * + * Ensures: + * + * *targetp is attached to the 'source' additional cache. + */ + +void +dns_acache_detach(dns_acache_t **acachep); +/* + * Detach *acachep from its cache. + * + * Requires: + * + * '*acachep' points to a valid additional cache. + * + * Ensures: + * + * *acachep is NULL. + * + * If '*acachep' is the last reference to the cache and the additional + * cache does not have an outstanding task, all resources used by the + * cache will be freed. + */ + +void +dns_acache_setcleaninginterval(dns_acache_t *acache, unsigned int t); +/* + * Set the periodic cleaning interval of an additional cache to 'interval' + * seconds. + */ + +void +dns_acache_setcachesize(dns_acache_t *acache, isc_uint32_t size); +/* + * Set the maximum additional cache size. 0 means unlimited. + */ + +isc_result_t +dns_acache_setdb(dns_acache_t *acache, dns_db_t *db); +/* + * Set 'db' in 'acache' when the db can be referred from acache, in order + * to provide a hint for resolving the back reference. + * + * Requires: + * 'acache' is a valid acache pointer. + * 'db' is a valid DNS DB pointer. + * + * Ensures: + * 'acache' will have a reference to 'db'. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_EXISTS (which means the specified 'db' is already set) + * ISC_R_NOMEMORY + */ + +isc_result_t +dns_acache_putdb(dns_acache_t *acache, dns_db_t *db); +/* + * Release 'db' from 'acache' if it has been set by dns_acache_setdb(). + * + * Requires: + * 'acache' is a valid acache pointer. + * 'db' is a valid DNS DB pointer. + * + * Ensures: + * 'acache' will release the reference to 'db'. Additionally, the content + * of each cache entry that is related to the 'db' will be released via + * the callback function. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOTFOUND (which means the specified 'db' is not set in 'acache') + * ISC_R_NOMEMORY + */ + +void +dns_acache_shutdown(dns_acache_t *acache); +/* + * Shutdown 'acache'. + * + * Requires: + * + * '*acache' is a valid additional cache. + */ + +isc_result_t +dns_acache_createentry(dns_acache_t *acache, dns_db_t *origdb, + void (*callback)(dns_acacheentry_t *, void **), + void *cbarg, dns_acacheentry_t **entryp); +/* + * Create an additional cache entry. A new entry is created and attached to + * the given additional cache object. A callback function is also associated + * with the created entry, which will be called when the cache entry is purged + * for some reason. + * + * Requires: + * + * 'acache' is a valid additional cache. + * 'entryp' is a valid pointer, and *entryp == NULL + * 'origdb' is a valid DNS DB pointer. + * 'callback' and 'cbarg' can be NULL. In this case, however, the entry + * is meaningless (and will be cleaned-up in the next periodical + * cleaning). + * + * Ensures: + * '*entryp' will point to a new additional cache entry. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + */ + +isc_result_t +dns_acache_getentry(dns_acacheentry_t *entry, dns_zone_t **zonep, + dns_db_t **dbp, dns_dbversion_t **versionp, + dns_dbnode_t **nodep, dns_name_t *fname, + dns_message_t *msg, isc_stdtime_t now); +/* + * Get content from a particular additional cache entry. + * + * Requires: + * + * 'entry' is a valid additional cache entry. + * 'zonep' is a NULL pointer or '*zonep' == NULL (this is the only + * optional parameter.) + * 'dbp' is a valid pointer, and '*dbp' == NULL + * 'versionp' is a valid pointer, and '*versionp' == NULL + * 'nodep' is a valid pointer, and '*nodep' == NULL + * 'fname' is a valid DNS name. + * 'msg' is a valid DNS message. + * + * Ensures: + * Several possible cases can happen according to the content. + * 1. For a positive cache entry, + * '*zonep' will point to the corresponding zone (if zonep is a valid + * pointer), + * '*dbp' will point to a DB for the zone, + * '*versionp' will point to its version, and + * '*nodep' will point to the corresponding DB node. + * 'fname' will have the DNS name of the DB node and contain a list of + * rdataset for the node (which can be an empty list). + * + * 2. For a negative cache entry that means no corresponding zone exists, + * '*zonep' == NULL (if zonep is a valid pointer) + * '*dbp', '*versionp', and '*nodep' will be NULL. + * + * 3. For a negative cache entry that means no corresponding DB node + * exists, '*zonep' will point to the corresponding zone (if zonep is a + * valid pointer), + * '*dbp' will point to a corresponding DB for zone, + * '*versionp' will point to its version. + * '*nodep' will be kept as NULL. + * 'fname' will not change. + * + * On failure, no new references will be created. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + */ + +isc_result_t +dns_acache_setentry(dns_acache_t *acache, dns_acacheentry_t *entry, + dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + dns_dbnode_t *node, dns_name_t *fname); +/* + * Set content to a particular additional cache entry. + * + * Requires: + * 'acache' is a valid additional cache. + * 'entry' is a valid additional cache entry. + * All the others pointers are NULL or a valid pointer of the + * corresponding type. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + * ISC_R_NOTFOUND + */ + +void +dns_acache_cancelentry(dns_acacheentry_t *entry); +/* + * Cancel the use of the cache entry 'entry'. This function is supposed to + * be called when the node that holds the entry finds the content is not + * correct any more. This function will try to release as much dependency as + * possible, and will be ready to be cleaned-up. The registered callback + * function will be canceled and will never called. + * + * Requires: + * 'entry' is a valid additional cache entry. + */ + +void +dns_acache_attachentry(dns_acacheentry_t *source, dns_acacheentry_t **targetp); +/* + * Attach *targetp to the cache entry 'source'. + * + * Requires: + * + * 'source' is a valid additional cache entry. + * + * 'targetp' points to a NULL dns_acacheentry_t *. + * + * Ensures: + * + * *targetp is attached to 'source'. + */ + +void +dns_acache_detachentry(dns_acacheentry_t **entryp); +/* + * Detach *entryp from its cache. + * + * Requires: + * + * '*entryp' points to a valid additional cache entry. + * + * Ensures: + * + * *entryp is NULL. + * + * If '*entryp' is the last reference to the entry, + * cache does not have an outstanding task, all resources used by the + * entry (including the entry object itself) will be freed. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_ACACHE_H */ diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index b4c7261da5..c94dc4f930 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: db.h,v 1.77 2004/05/14 04:45:57 marka Exp $ */ +/* $Id: db.h,v 1.78 2004/12/21 10:45:18 jinmei Exp $ */ #ifndef DNS_DB_H #define DNS_DB_H 1 @@ -145,6 +145,10 @@ typedef struct dns_dbmethods { isc_boolean_t (*ispersistent)(dns_db_t *db); void (*overmem)(dns_db_t *db, isc_boolean_t overmem); void (*settask)(dns_db_t *db, isc_task_t *); + isc_result_t (*getsoanode)(dns_db_t *db, dns_dbnode_t **nodep); + isc_result_t (*setsoanode)(dns_db_t *db, dns_dbnode_t *nodep); + isc_result_t (*getnsnode)(dns_db_t *db, dns_dbnode_t **nodep); + isc_result_t (*setnsnode)(dns_db_t *db, dns_dbnode_t *nodep); } dns_dbmethods_t; typedef isc_result_t @@ -1266,6 +1270,84 @@ dns_db_unregister(dns_dbimplementation_t **dbimp); * Any memory allocated in *dbimp will be freed. */ +isc_result_t +dns_db_getsoanode(dns_db_t *db, dns_dbnode_t **nodep); +/* + * Get a cached SOA DB node corresponding to the DB's zone. + * + * Requires: + * + * 'db' is a valid zone database. + * 'nodep' != NULL && '*nodep' == NULL + * + * Ensures: + * On sucess, '*nodep' will point to a DB node for the SOA RR of 'db.' + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOTFOUND - an SOA RR node has not been cached in 'db' or SOA RR + * caching is not supported for 'db' + */ + +isc_result_t +dns_db_setsoanode(dns_db_t *db, dns_dbnode_t *node); +/* + * Set an SOA DB node as cache corresponding to the DB's zone. If there is + * already a node set in the DB, it will be detached and replaced with the + * new one. + * + * Requires: + * + * 'db' is a valid zone database. + * 'node' is a valid DB node. + * + * Ensures: + * On sucess, '*nodep' will point to a DB node for the SOA RR of 'db.' + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_FAILURE - SOA RR caching is not supported for 'db' + */ + +isc_result_t +dns_db_getnsnode(dns_db_t *db, dns_dbnode_t **nodep); +/* + * Get a cached NS DB node corresponding to the DB's zone. + * + * Requires: + * + * 'db' is a valid zone database. + * 'nodep' != NULL && '*nodep' == NULL + * + * Ensures: + * On sucess, '*nodep' will point to a DB node for the NS RR of 'db.' + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOTFOUND - an NS RR node has not been cached in 'db' or NS RR + * caching is not supported for 'db' + */ + +isc_result_t +dns_db_setnsnode(dns_db_t *db, dns_dbnode_t *node); +/* + * Set an NS DB node as cache corresponding to the DB's zone. If there is + * already a node set in the DB, it will be detached and replaced with the + * new one. + * + * Requires: + * + * 'db' is a valid zone database. + * 'node' is a valid DB node. + * + * Ensures: + * On sucess, '*nodep' will point to a DB node for the NS RR of 'db.' + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_FAILURE - NS RR caching is not supported for 'db' + */ + ISC_LANG_ENDDECLS #endif /* DNS_DB_H */ diff --git a/lib/dns/include/dns/events.h b/lib/dns/include/dns/events.h index 0c56291485..9f8ffd13cc 100644 --- a/lib/dns/include/dns/events.h +++ b/lib/dns/include/dns/events.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: events.h,v 1.42 2004/03/05 05:09:42 marka Exp $ */ +/* $Id: events.h,v 1.43 2004/12/21 10:45:19 jinmei Exp $ */ #ifndef DNS_EVENTS_H #define DNS_EVENTS_H 1 @@ -63,6 +63,10 @@ #define DNS_EVENT_DUMPQUANTUM (ISC_EVENTCLASS_DNS + 34) #define DNS_EVENT_IMPORTRECVDONE (ISC_EVENTCLASS_DNS + 35) #define DNS_EVENT_FREESTORAGE (ISC_EVENTCLASS_DNS + 36) +#define DNS_EVENT_VIEWACACHESHUTDOWN (ISC_EVENTCLASS_DNS + 37) +#define DNS_EVENT_ACACHECONTROL (ISC_EVENTCLASS_DNS + 38) +#define DNS_EVENT_ACACHECLEAN (ISC_EVENTCLASS_DNS + 39) +#define DNS_EVENT_ACACHEOVERMEM (ISC_EVENTCLASS_DNS + 40) #define DNS_EVENT_FIRSTEVENT (ISC_EVENTCLASS_DNS + 0) #define DNS_EVENT_LASTEVENT (ISC_EVENTCLASS_DNS + 65535) diff --git a/lib/dns/include/dns/log.h b/lib/dns/include/dns/log.h index dc1fa8c88f..514d31aa1e 100644 --- a/lib/dns/include/dns/log.h +++ b/lib/dns/include/dns/log.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: log.h,v 1.33 2004/03/05 05:09:43 marka Exp $ */ +/* $Id: log.h,v 1.34 2004/12/21 10:45:19 jinmei Exp $ */ /* Principal Authors: DCL */ @@ -69,6 +69,7 @@ LIBDNS_EXTERNAL_DATA extern isc_logmodule_t dns_modules[]; #define DNS_LOGMODULE_SDB (&dns_modules[22]) #define DNS_LOGMODULE_DIFF (&dns_modules[23]) #define DNS_LOGMODULE_HINTS (&dns_modules[24]) +#define DNS_LOGMODULE_ACACHE (&dns_modules[25]) ISC_LANG_BEGINDECLS diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h index ee61b9b549..a286600bad 100644 --- a/lib/dns/include/dns/rdataset.h +++ b/lib/dns/include/dns/rdataset.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: rdataset.h,v 1.51 2004/03/05 05:09:45 marka Exp $ */ +/* $Id: rdataset.h,v 1.52 2004/12/21 10:45:19 jinmei Exp $ */ #ifndef DNS_RDATASET_H #define DNS_RDATASET_H 1 @@ -54,11 +54,18 @@ #include #include +#include #include ISC_LANG_BEGINDECLS +typedef enum { + dns_rdatasetadditional_fromauth, + dns_rdatasetadditional_fromcache, + dns_rdatasetadditional_fromglue +} dns_rdatasetadditional_t; + typedef struct dns_rdatasetmethods { void (*disassociate)(dns_rdataset_t *rdataset); isc_result_t (*first)(dns_rdataset_t *rdataset); @@ -74,6 +81,30 @@ typedef struct dns_rdatasetmethods { dns_name_t *name, dns_rdataset_t *nsec, dns_rdataset_t *nsecsig); + isc_result_t (*getadditional)(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t **zonep, + dns_db_t **dbp, + dns_dbversion_t **versionp, + dns_dbnode_t **nodep, + dns_name_t *fname, + dns_message_t *msg, + isc_stdtime_t now); + isc_result_t (*setadditional)(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t *zone, + dns_db_t *db, + dns_dbversion_t *version, + dns_dbnode_t *node, + dns_name_t *fname); + isc_result_t (*putadditional)(dns_acache_t *acache, + dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype); } dns_rdatasetmethods_t; #define DNS_RDATASET_MAGIC ISC_MAGIC('D','N','S','R') @@ -409,7 +440,6 @@ dns_rdataset_towirepartial(dns_rdataset_t *rdataset, * written. */ - isc_result_t dns_rdataset_additionaldata(dns_rdataset_t *rdataset, dns_additionaldatafunc_t add, void *arg); @@ -463,6 +493,100 @@ dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name); * 'name' to be valid and have NSEC and RRSIG(NSEC) rdatasets. */ +isc_result_t +dns_rdataset_getadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t **zonep, + dns_db_t **dbp, + dns_dbversion_t **versionp, + dns_dbnode_t **nodep, + dns_name_t *fname, + dns_message_t *msg, + isc_stdtime_t now); +/* + * Get cached additional information from the DB node for a particular + * 'rdataset.' 'type' is one of dns_rdatasetadditional_fromauth, + * dns_rdatasetadditional_fromcache, and dns_rdatasetadditional_fromglue, + * which specifies the origin of the information. 'qtype' is intended to + * be used for specifying a particular rdata type in the cached information. + * + * Requires: + * 'rdataset' is a valid rdataset. + * 'acache' can be NULL, in which case this function will simply return + * ISC_R_FAILURE. + * For the other pointers, see dns_acache_getentry(). + * + * Ensures: + * See dns_acache_getentry(). + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_FAILURE - additional information caching is not supported. + * ISC_R_NOTFOUND - the corresponding DB node has not cached additional + * information for 'rdataset.' + * + * Any error that dns_acache_getentry() can return. + */ + +isc_result_t +dns_rdataset_setadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t *zone, + dns_db_t *db, + dns_dbversion_t *version, + dns_dbnode_t *node, + dns_name_t *fname); +/* + * Set cached additional information to the DB node for a particular + * 'rdataset.' See dns_rdataset_getadditional for the semantics of 'type' + * and 'qtype'. + * + * Requires: + * 'rdataset' is a valid rdataset. + * 'acache' can be NULL, in which case this function will simply return + * ISC_R_FAILURE. + * For the other pointers, see dns_acache_setentry(). + * + * Ensures: + * See dns_acache_setentry(). + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_FAILURE - additional information caching is not supported. + * ISC_R_NOMEMORY + * + * Any error that dns_acache_setentry() can return. + */ + +isc_result_t +dns_rdataset_putadditional(dns_acache_t *acache, + dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype); +/* + * Discard cached additional information stored in the DB node for a particular + * 'rdataset.' See dns_rdataset_getadditional for the semantics of 'type' + * and 'qtype'. + * + * Requires: + * 'rdataset' is a valid rdataset. + * 'acache' can be NULL, in which case this function will simply return + * ISC_R_FAILURE. + * + * Ensures: + * See dns_acache_cancelentry(). + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_FAILURE - additional information caching is not supported. + * ISC_R_NOTFOUND - the corresponding DB node has not cached additional + * information for 'rdataset.' + */ + ISC_LANG_ENDDECLS #endif /* DNS_RDATASET_H */ diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 42e8314252..625380b417 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: types.h,v 1.110 2004/03/30 02:13:44 marka Exp $ */ +/* $Id: types.h,v 1.111 2004/12/21 10:45:19 jinmei Exp $ */ #ifndef DNS_TYPES_H #define DNS_TYPES_H 1 @@ -30,6 +30,8 @@ #include +typedef struct dns_acache dns_acache_t; +typedef struct dns_acacheentry dns_acacheentry_t; typedef struct dns_acl dns_acl_t; typedef struct dns_aclelement dns_aclelement_t; typedef struct dns_aclenv dns_aclenv_t; diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index e46085b617..4570cfc70b 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: view.h,v 1.91 2004/03/10 02:19:56 marka Exp $ */ +/* $Id: view.h,v 1.92 2004/12/21 10:45:19 jinmei Exp $ */ #ifndef DNS_VIEW_H #define DNS_VIEW_H 1 @@ -86,6 +86,7 @@ struct dns_view { dns_resolver_t * resolver; dns_adb_t * adb; dns_requestmgr_t * requestmgr; + dns_acache_t * acache; dns_cache_t * cache; dns_db_t * cachedb; dns_db_t * hints; diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 2316c259b1..28337a68f8 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zone.h,v 1.127 2004/10/26 02:01:19 marka Exp $ */ +/* $Id: zone.h,v 1.128 2004/12/21 10:45:19 jinmei Exp $ */ #ifndef DNS_ZONE_H #define DNS_ZONE_H 1 @@ -1432,6 +1432,19 @@ dns_zone_checknames(dns_zone_t *zone, dns_name_t *name, dns_rdata_t *rdata); * DNS_R_BADNAME failed rdata checks. */ +void +dns_zone_setacache(dns_zone_t *zone, dns_acache_t *acache); +/* + * Associate the zone with an additional cache. + * + * Require: + * 'zone' to be a valid zone. + * 'acache' to be a non NULL pointer. + * + * Ensures: + * 'zone' will have a reference to 'acache' + */ + ISC_LANG_ENDDECLS #endif /* DNS_ZONE_H */ diff --git a/lib/dns/log.c b/lib/dns/log.c index a37d05aef6..0ce419f18c 100644 --- a/lib/dns/log.c +++ b/lib/dns/log.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: log.c,v 1.36 2004/03/05 05:09:20 marka Exp $ */ +/* $Id: log.c,v 1.37 2004/12/21 10:45:16 jinmei Exp $ */ /* Principal Authors: DCL */ @@ -74,6 +74,7 @@ LIBDNS_EXTERNAL_DATA isc_logmodule_t dns_modules[] = { { "dns/sdb", 0 }, { "dns/diff", 0 }, { "dns/hints", 0 }, + { "dns/acache", 0 }, { NULL, 0 } }; diff --git a/lib/dns/ncache.c b/lib/dns/ncache.c index 5e1c87fe2e..5bec301b95 100644 --- a/lib/dns/ncache.c +++ b/lib/dns/ncache.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: ncache.c,v 1.36 2004/03/05 05:09:21 marka Exp $ */ +/* $Id: ncache.c,v 1.37 2004/12/21 10:45:17 jinmei Exp $ */ #include @@ -473,6 +473,9 @@ static dns_rdatasetmethods_t rdataset_methods = { rdataset_clone, rdataset_count, NULL, + NULL, + NULL, + NULL, NULL }; diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index 93aaa593e9..30959ddaa5 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: rbtdb.c,v 1.200 2004/05/23 06:59:20 marka Exp $ */ +/* $Id: rbtdb.c,v 1.201 2004/12/21 10:45:17 jinmei Exp $ */ /* * Principal Author: Bob Halley @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -46,6 +47,8 @@ #include #include #include +#include +#include #include #ifdef DNS_RBTDB_VERSION64 @@ -103,6 +106,8 @@ struct noqname { void * nsecsig; }; +typedef struct acachectl acachectl_t; + typedef struct rdatasetheader { /* * Locked by the owning node's lock. @@ -140,6 +145,9 @@ typedef struct rdatasetheader { * should not be so crucial, no lock is set for the counter for * performance reasons. */ + + acachectl_t *additional_auth; + acachectl_t *additional_glue; } rdatasetheader_t; #define RDATASET_ATTR_NONEXISTENT 0x0001 @@ -148,6 +156,19 @@ typedef struct rdatasetheader { #define RDATASET_ATTR_RETAIN 0x0008 #define RDATASET_ATTR_NXDOMAIN 0x0010 +typedef struct acache_cbarg { + dns_rdatasetadditional_t type; + unsigned int count; + dns_db_t *db; + dns_dbnode_t *node; + rdatasetheader_t *header; +} acache_cbarg_t; + +struct acachectl { + dns_acacheentry_t *entry; + acache_cbarg_t *cbarg; +}; + /* * XXX * When the cache will pre-expire data (due to memory low or other @@ -219,6 +240,8 @@ typedef struct { rbtdb_versionlist_t open_versions; isc_boolean_t overmem; isc_task_t * task; + dns_dbnode_t *soanode; + dns_dbnode_t *nsnode; /* Locked by tree_lock. */ dns_rbt_t * tree; isc_boolean_t secure; @@ -264,6 +287,30 @@ static isc_result_t rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, dns_rdataset_t *nsec, dns_rdataset_t *nsecsig); +static isc_result_t rdataset_getadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t **zonep, + dns_db_t **dbp, + dns_dbversion_t **versionp, + dns_dbnode_t **nodep, + dns_name_t *fname, + dns_message_t *msg, + isc_stdtime_t now); +static isc_result_t rdataset_setadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t *zone, + dns_db_t *db, + dns_dbversion_t *version, + dns_dbnode_t *node, + dns_name_t *fname); +static isc_result_t rdataset_putadditional(dns_acache_t *acache, + dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype); static dns_rdatasetmethods_t rdataset_methods = { rdataset_disassociate, @@ -273,7 +320,10 @@ static dns_rdatasetmethods_t rdataset_methods = { rdataset_clone, rdataset_count, NULL, - rdataset_getnoqname + rdataset_getnoqname, + rdataset_getadditional, + rdataset_setadditional, + rdataset_putadditional }; static void rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp); @@ -468,6 +518,11 @@ maybe_free_rbtdb(dns_rbtdb_t *rbtdb) { /* XXX check for open versions here */ + if (rbtdb->soanode != NULL) + dns_db_detachnode((dns_db_t *)rbtdb, &rbtdb->soanode); + if (rbtdb->nsnode != NULL) + dns_db_detachnode((dns_db_t *)rbtdb, &rbtdb->nsnode); + /* * Even though there are no external direct references, there still * may be nodes in use. @@ -631,6 +686,35 @@ add_changed(dns_rbtdb_t *rbtdb, rbtdb_version_t *version, return (changed); } +static void +free_acachearray(isc_mem_t *mctx, rdatasetheader_t *header, + acachectl_t *array) +{ + unsigned int count; + unsigned int i; + unsigned char *raw; + + /* + * The caller must be holding the corresponding node lock. + */ + + if (array == NULL) + return; + + raw = (unsigned char *)header + sizeof(*header); + count = raw[0] * 256 + raw[1]; + + /* + * Sanity check: since an additional cache entry has a reference to + * the original DB node (in the callback arg), there should be no + * acache entries when the node can be freed. + */ + for (i = 0; i < count; i++) + INSIST(array[i].entry == NULL && array[i].cbarg == NULL); + + isc_mem_put(mctx, array, count * sizeof(acachectl_t)); +} + static inline void free_noqname(isc_mem_t *mctx, struct noqname **noqname) { @@ -652,7 +736,10 @@ free_rdataset(isc_mem_t *mctx, rdatasetheader_t *rdataset) { if (rdataset->noqname != NULL) free_noqname(mctx, &rdataset->noqname); - + + free_acachearray(mctx, rdataset, rdataset->additional_auth); + free_acachearray(mctx, rdataset, rdataset->additional_glue); + if ((rdataset->attributes & RDATASET_ATTR_NONEXISTENT) != 0) size = sizeof(*rdataset); else @@ -4265,6 +4352,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, newheader->noqname = NULL; newheader->count = 0; newheader->trust = rdataset->trust; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; if (rbtversion != NULL) { newheader->serial = rbtversion->serial; now = 0; @@ -4338,6 +4427,8 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, newheader->trust = 0; newheader->noqname = NULL; newheader->count = 0; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; LOCK(&rbtdb->node_locks[rbtnode->locknum].lock); @@ -4390,6 +4481,13 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, * header, not newheader. */ newheader->serial = rbtversion->serial; + /* + * XXXJT: dns_rdataslab_subtract() copied the pointers + * to additional info. We need to clear these fields + * to avoid having duplicated references. + */ + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; } else if (result == DNS_R_NXRRSET) { /* * This subtraction would remove all of the rdata; @@ -4409,6 +4507,8 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, newheader->serial = rbtversion->serial; newheader->noqname = NULL; newheader->count = 0; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; } else { free_rdataset(rbtdb->common.mctx, newheader); goto unlock; @@ -4474,6 +4574,8 @@ deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, newheader->attributes = RDATASET_ATTR_NONEXISTENT; newheader->trust = 0; newheader->noqname = NULL; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; if (rbtversion != NULL) newheader->serial = rbtversion->serial; else @@ -4556,6 +4658,8 @@ loading_addrdataset(void *arg, dns_name_t *name, dns_rdataset_t *rdataset) { newheader->serial = 1; newheader->noqname = NULL; newheader->count = 0; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; result = add(rbtdb, node, rbtdb->current_version, newheader, DNS_DBADD_MERGE, ISC_TRUE, NULL, 0); @@ -4755,6 +4859,74 @@ ispersistent(dns_db_t *db) { return (ISC_FALSE); } +static isc_result_t +getsoanode(dns_db_t *db, dns_dbnode_t **nodep) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(nodep != NULL && *nodep == NULL); + + LOCK(&rbtdb->lock); + if (rbtdb->soanode != NULL) { + attachnode(db, rbtdb->soanode, nodep); + } else + result = ISC_R_NOTFOUND; + UNLOCK(&rbtdb->lock); + + return (result); +} + +static isc_result_t +setsoanode(dns_db_t *db, dns_dbnode_t *node) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(node != NULL); + + LOCK(&rbtdb->lock); + if (rbtdb->soanode != NULL) + detachnode(db, &rbtdb->soanode); + attachnode(db, node, &rbtdb->soanode); + UNLOCK(&rbtdb->lock); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +getnsnode(dns_db_t *db, dns_dbnode_t **nodep) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(nodep != NULL && *nodep == NULL); + + LOCK(&rbtdb->lock); + if (rbtdb->nsnode != NULL) { + attachnode(db, rbtdb->nsnode, nodep); + } else + result = ISC_R_NOTFOUND; + UNLOCK(&rbtdb->lock); + + return (result); +} + +static isc_result_t +setnsnode(dns_db_t *db, dns_dbnode_t *node) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(node != NULL); + + LOCK(&rbtdb->lock); + if (rbtdb->nsnode != NULL) + detachnode(db, &rbtdb->nsnode); + attachnode(db, node, &rbtdb->nsnode); + UNLOCK(&rbtdb->lock); + + return (ISC_R_SUCCESS); +} + static dns_dbmethods_t zone_methods = { attach, detach, @@ -4782,7 +4954,11 @@ static dns_dbmethods_t zone_methods = { nodecount, ispersistent, overmem, - settask + settask, + getsoanode, + setsoanode, + getnsnode, + setnsnode }; static dns_dbmethods_t cache_methods = { @@ -4812,7 +4988,11 @@ static dns_dbmethods_t cache_methods = { nodecount, ispersistent, overmem, - settask + settask, + getsoanode, + setsoanode, + getnsnode, + setnsnode }; isc_result_t @@ -5704,3 +5884,346 @@ dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) { return (dns_name_copy(origin, name, NULL)); } + +/* + * Additional cache routines. + */ +static isc_result_t +rdataset_getadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, dns_acache_t *acache, + dns_zone_t **zonep, dns_db_t **dbp, + dns_dbversion_t **versionp, dns_dbnode_t **nodep, + dns_name_t *fname, dns_message_t *msg, + isc_stdtime_t now) +{ + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + unsigned char *raw = rdataset->private3; + unsigned int current_count = rdataset->privateuint4; + unsigned int count; + rdatasetheader_t *header; + isc_mutex_t *nodelock; + unsigned int total_count; + acachectl_t *acarray; + dns_acacheentry_t *entry; + isc_result_t result; + + UNUSED(qtype); /* we do not use this value at least for now */ + UNUSED(acache); + + header = (struct rdatasetheader *)(raw - sizeof(*header)); + + total_count = rdataset_count(rdataset); + INSIST(total_count > current_count); + count = total_count - current_count - 1; + + acarray = NULL; + + nodelock = &rbtdb->node_locks[rbtnode->locknum].lock; + LOCK(nodelock); + + switch (type) { + case dns_rdatasetadditional_fromauth: + acarray = header->additional_auth; + break; + case dns_rdatasetadditional_fromcache: + acarray = NULL; + break; + case dns_rdatasetadditional_fromglue: + acarray = header->additional_glue; + break; + default: + INSIST(0); + } + + if (acarray == NULL) { + UNLOCK(nodelock); + return (ISC_R_NOTFOUND); + } + + if (acarray[count].entry == NULL) { + UNLOCK(nodelock); + return (ISC_R_NOTFOUND); + } + + entry = NULL; + dns_acache_attachentry(acarray[count].entry, &entry); + + UNLOCK(nodelock); + + result = dns_acache_getentry(entry, zonep, dbp, versionp, + nodep, fname, msg, now); + + dns_acache_detachentry(&entry); + + return (result); +} + +static void +acache_callback(dns_acacheentry_t *entry, void **arg) { + dns_rbtdb_t *rbtdb; + dns_rbtnode_t *rbtnode; + isc_mutex_t *nodelock; + acachectl_t *acarray = NULL; + acache_cbarg_t *cbarg; + unsigned int count; + + REQUIRE(arg != NULL); + cbarg = *arg; + + /* + * The caller must hold the entry lock. + */ + + rbtdb = (dns_rbtdb_t *)cbarg->db; + rbtnode = (dns_rbtnode_t *)cbarg->node; + + nodelock = &rbtdb->node_locks[rbtnode->locknum].lock; + LOCK(nodelock); + + switch (cbarg->type) { + case dns_rdatasetadditional_fromauth: + acarray = cbarg->header->additional_auth; + break; + case dns_rdatasetadditional_fromglue: + acarray = cbarg->header->additional_glue; + break; + default: + INSIST(0); + } + + count = cbarg->count; + if (acarray[count].entry == entry) + acarray[count].entry = NULL; + INSIST(acarray[count].cbarg != NULL); + isc_mem_put(rbtdb->common.mctx, acarray[count].cbarg, + sizeof(acache_cbarg_t)); + acarray[count].cbarg = NULL; + + dns_acache_detachentry(&entry); + + UNLOCK(nodelock); + + dns_db_detachnode((dns_db_t *)rbtdb, (dns_dbnode_t **)&rbtnode); + dns_db_detach((dns_db_t **)&rbtdb); + + *arg = NULL; +} + +static void +acache_cancelentry(isc_mem_t *mctx, dns_acacheentry_t *entry, + acache_cbarg_t **cbargp) +{ + acache_cbarg_t *cbarg; + + REQUIRE(mctx != NULL); + REQUIRE(entry != NULL); + REQUIRE(cbargp != NULL && *cbargp != NULL); + + cbarg = *cbargp; + + dns_acache_cancelentry(entry); + dns_db_detachnode(cbarg->db, &cbarg->node); + dns_db_detach(&cbarg->db); + + isc_mem_put(mctx, cbarg, sizeof(acache_cbarg_t)); + + *cbargp = NULL; +} + +static isc_result_t +rdataset_setadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, dns_acache_t *acache, + dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *version, dns_dbnode_t *node, + dns_name_t *fname) +{ + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + unsigned char *raw = rdataset->private3; + unsigned int current_count = rdataset->privateuint4; + rdatasetheader_t *header; + unsigned int total_count, count; + isc_mutex_t *nodelock; + isc_mem_t *mctx = rbtdb->common.mctx; + isc_result_t result; + acachectl_t *acarray; + dns_acacheentry_t *newentry, *oldentry = NULL; + acache_cbarg_t *newcbarg, *oldcbarg = NULL; + + UNUSED(qtype); + + if (type == dns_rdatasetadditional_fromcache) + return (ISC_R_SUCCESS); + + header = (struct rdatasetheader *)(raw - sizeof(*header)); + + total_count = rdataset_count(rdataset); + INSIST(total_count > current_count); + count = total_count - current_count - 1; /* should be private data */ + + newcbarg = isc_mem_get(mctx, sizeof(*newcbarg)); + if (newcbarg == NULL) + return (ISC_R_NOMEMORY); + newcbarg->type = type; + newcbarg->count = count; + newcbarg->header = header; + newcbarg->db = NULL; + dns_db_attach((dns_db_t *)rbtdb, &newcbarg->db); + newcbarg->node = NULL; + dns_db_attachnode((dns_db_t *)rbtdb, (dns_dbnode_t *)rbtnode, + &newcbarg->node); + newentry = NULL; + result = dns_acache_createentry(acache, (dns_db_t *)rbtdb, + acache_callback, newcbarg, &newentry); + if (result != ISC_R_SUCCESS) + goto fail; + /* Set cache data in the new entry. */ + result = dns_acache_setentry(acache, newentry, zone, db, + version, node, fname); + if (result != ISC_R_SUCCESS) + goto fail; + + nodelock = &rbtdb->node_locks[rbtnode->locknum].lock; + LOCK(nodelock); + + acarray = NULL; + switch (type) { + case dns_rdatasetadditional_fromauth: + acarray = header->additional_auth; + break; + case dns_rdatasetadditional_fromglue: + acarray = header->additional_glue; + break; + default: + INSIST(0); + } + + if (acarray == NULL) { + unsigned int i; + + acarray = isc_mem_get(mctx, total_count * + sizeof(acachectl_t)); + + if (acarray == NULL) { + UNLOCK(nodelock); + goto fail; + } + + for (i = 0; i < total_count; i++) { + acarray[i].entry = NULL; + acarray[i].cbarg = NULL; + } + } + switch (type) { + case dns_rdatasetadditional_fromauth: + header->additional_auth = acarray; + break; + case dns_rdatasetadditional_fromglue: + header->additional_glue = acarray; + break; + default: + INSIST(0); + } + + if (acarray[count].entry != NULL) { + /* + * Swap the entry. Delay cleaning-up the old entry since + * it would require a node lock. + */ + oldentry = acarray[count].entry; + INSIST(acarray[count].cbarg != NULL); + oldcbarg = acarray[count].cbarg; + } + acarray[count].entry = newentry; + acarray[count].cbarg = newcbarg; + + UNLOCK(nodelock); + + if (oldentry != NULL) { + if (oldcbarg != NULL) + acache_cancelentry(mctx, oldentry, &oldcbarg); + dns_acache_detachentry(&oldentry); + } + + return (ISC_R_SUCCESS); + + fail: + if (newentry != NULL) { + if (newcbarg != NULL) + acache_cancelentry(mctx, newentry, &newcbarg); + dns_acache_detachentry(&newentry); + } + + return (result); +} + +static isc_result_t +rdataset_putadditional(dns_acache_t *acache, dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, dns_rdatatype_t qtype) +{ + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + unsigned char *raw = rdataset->private3; + unsigned int current_count = rdataset->privateuint4; + rdatasetheader_t *header; + isc_mutex_t *nodelock; + unsigned int total_count, count; + acachectl_t *acarray; + dns_acacheentry_t *entry; + acache_cbarg_t *cbarg; + + UNUSED(qtype); /* we do not use this value at least for now */ + UNUSED(acache); + + if (type == dns_rdatasetadditional_fromcache) + return (ISC_R_SUCCESS); + + header = (struct rdatasetheader *)(raw - sizeof(*header)); + + total_count = rdataset_count(rdataset); + INSIST(total_count > current_count); + count = total_count - current_count - 1; + + acarray = NULL; + entry = NULL; + + nodelock = &rbtdb->node_locks[rbtnode->locknum].lock; + LOCK(nodelock); + + switch (type) { + case dns_rdatasetadditional_fromauth: + acarray = header->additional_auth; + break; + case dns_rdatasetadditional_fromglue: + acarray = header->additional_glue; + break; + default: + INSIST(0); + } + + if (acarray == NULL) { + UNLOCK(nodelock); + return (ISC_R_NOTFOUND); + } + + entry = acarray[count].entry; + if (entry == NULL) { + UNLOCK(nodelock); + return (ISC_R_NOTFOUND); + } + + acarray[count].entry = NULL; + cbarg = acarray[count].cbarg; + acarray[count].cbarg = NULL; + + UNLOCK(nodelock); + + if (entry != NULL) { + if(cbarg != NULL) + acache_cancelentry(rbtdb->common.mctx, entry, &cbarg); + dns_acache_detachentry(&entry); + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c index 2ceda74fca..a181f5ccbf 100644 --- a/lib/dns/rdatalist.c +++ b/lib/dns/rdatalist.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: rdatalist.c,v 1.28 2004/03/05 05:09:23 marka Exp $ */ +/* $Id: rdatalist.c,v 1.29 2004/12/21 10:45:17 jinmei Exp $ */ #include @@ -38,7 +38,10 @@ static dns_rdatasetmethods_t methods = { isc__rdatalist_clone, isc__rdatalist_count, isc__rdatalist_addnoqname, - isc__rdatalist_getnoqname + isc__rdatalist_getnoqname, + NULL, + NULL, + NULL }; void diff --git a/lib/dns/rdataset.c b/lib/dns/rdataset.c index 8f3f1f0dcf..f65b4bd825 100644 --- a/lib/dns/rdataset.c +++ b/lib/dns/rdataset.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: rdataset.c,v 1.72 2004/03/05 05:09:23 marka Exp $ */ +/* $Id: rdataset.c,v 1.73 2004/12/21 10:45:17 jinmei Exp $ */ #include @@ -174,6 +174,9 @@ static dns_rdatasetmethods_t question_methods = { question_clone, question_count, NULL, + NULL, + NULL, + NULL, NULL }; @@ -624,3 +627,81 @@ dns_rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, return (ISC_R_NOTIMPLEMENTED); return((rdataset->methods->getnoqname)(rdataset, name, nsec, nsecsig)); } + +/* + * Additional cache stuff + */ +isc_result_t +dns_rdataset_getadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t **zonep, + dns_db_t **dbp, + dns_dbversion_t **versionp, + dns_dbnode_t **nodep, + dns_name_t *fname, + dns_message_t *msg, + isc_stdtime_t now) +{ + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + REQUIRE(zonep == NULL || *zonep == NULL); + REQUIRE(dbp != NULL && *dbp == NULL); + REQUIRE(versionp != NULL && *versionp == NULL); + REQUIRE(nodep != NULL && *nodep == NULL); + REQUIRE(fname != NULL); + REQUIRE(msg != NULL); + + if (acache != NULL && rdataset->methods->getadditional != NULL) { + return ((rdataset->methods->getadditional)(rdataset, type, + qtype, acache, + zonep, dbp, + versionp, nodep, + fname, msg, now)); + } + + return (ISC_R_FAILURE); +} + +isc_result_t +dns_rdataset_setadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t *zone, + dns_db_t *db, + dns_dbversion_t *version, + dns_dbnode_t *node, + dns_name_t *fname) +{ + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (acache != NULL && rdataset->methods->setadditional != NULL) { + return ((rdataset->methods->setadditional)(rdataset, type, + qtype, acache, zone, + db, version, + node, fname)); + } + + return (ISC_R_FAILURE); +} + +isc_result_t +dns_rdataset_putadditional(dns_acache_t *acache, + dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype) +{ + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (acache != NULL && rdataset->methods->putadditional != NULL) { + return ((rdataset->methods->putadditional)(acache, rdataset, + type, qtype)); + } + + return (ISC_R_FAILURE); +} + diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c index 2e1b5f823e..228cf1fe72 100644 --- a/lib/dns/rdataslab.c +++ b/lib/dns/rdataslab.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: rdataslab.c,v 1.35 2004/03/05 05:09:23 marka Exp $ */ +/* $Id: rdataslab.c,v 1.36 2004/12/21 10:45:17 jinmei Exp $ */ #include @@ -241,6 +241,9 @@ static dns_rdatasetmethods_t rdataset_methods = { rdataset_clone, rdataset_count, NULL, + NULL, + NULL, + NULL, NULL }; diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c index 486874788f..ebf113ae19 100644 --- a/lib/dns/sdb.c +++ b/lib/dns/sdb.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: sdb.c,v 1.46 2004/07/22 03:58:07 marka Exp $ */ +/* $Id: sdb.c,v 1.47 2004/12/21 10:45:18 jinmei Exp $ */ #include @@ -1234,7 +1234,11 @@ static dns_dbmethods_t sdb_methods = { nodecount, ispersistent, overmem, - settask + settask, + NULL, + NULL, + NULL, + NULL }; static isc_result_t @@ -1361,7 +1365,10 @@ static dns_rdatasetmethods_t methods = { rdataset_clone, isc__rdatalist_count, isc__rdatalist_addnoqname, - isc__rdatalist_getnoqname + isc__rdatalist_getnoqname, + NULL, + NULL, + NULL }; static void diff --git a/lib/dns/view.c b/lib/dns/view.c index b3848c1cc3..ba90d117c8 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: view.c,v 1.126 2004/03/10 02:19:56 marka Exp $ */ +/* $Id: view.c,v 1.127 2004/12/21 10:45:18 jinmei Exp $ */ #include @@ -24,6 +24,7 @@ #include /* Required for HP/UX (and others?) */ #include +#include #include #include #include @@ -120,6 +121,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, goto cleanup_trustedkeys; } + view->acache = NULL; view->cache = NULL; view->cachedb = NULL; view->hints = NULL; @@ -253,6 +255,8 @@ destroy(dns_view_t *view) { dns_adb_detach(&view->adb); if (view->resolver != NULL) dns_resolver_detach(&view->resolver); + if (view->acache != NULL) + dns_acache_detach(&view->acache); if (view->requestmgr != NULL) dns_requestmgr_detach(&view->requestmgr); if (view->task != NULL) @@ -365,6 +369,8 @@ view_flushanddetach(dns_view_t **viewp, isc_boolean_t flush) { dns_adb_shutdown(view->adb); if (!REQSHUTDOWN(view)) dns_requestmgr_shutdown(view->requestmgr); + if (view->acache != NULL) + dns_acache_shutdown(view->acache); if (view->flush) dns_zt_flushanddetach(&view->zonetable); else diff --git a/lib/dns/zone.c b/lib/dns/zone.c index dac17f6fb1..063c1a64cd 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zone.c,v 1.425 2004/11/23 05:23:46 marka Exp $ */ +/* $Id: zone.c,v 1.426 2004/12/21 10:45:18 jinmei Exp $ */ #include @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -206,6 +207,7 @@ struct dns_zone { dns_ssutable_t *ssutable; isc_uint32_t sigvalidityinterval; dns_view_t *view; + dns_acache_t *acache; /* * Zones in certain states such as "waiting for zone transfer" * or "zone transfer in progress" are kept on per-state linked lists @@ -384,6 +386,8 @@ static void zone_iattach(dns_zone_t *source, dns_zone_t **target); static void zone_idetach(dns_zone_t **zonep); static isc_result_t zone_replacedb(dns_zone_t *zone, dns_db_t *db, isc_boolean_t dump); +static inline void zone_attachdb(dns_zone_t *zone, dns_db_t *db); +static inline void zone_detachdb(dns_zone_t *zone); static isc_result_t default_journal(dns_zone_t *zone); static void zone_xfrdone(dns_zone_t *zone, isc_result_t result); static isc_result_t zone_postload(dns_zone_t *zone, dns_db_t *db, @@ -572,6 +576,7 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { zone->ssutable = NULL; zone->sigvalidityinterval = 30 * 24 * 3600; zone->view = NULL; + zone->acache = NULL; ISC_LINK_INIT(zone, statelink); zone->statelist = NULL; zone->counters = NULL; @@ -636,7 +641,9 @@ zone_free(dns_zone_t *zone) { if (zone->counters != NULL) dns_stats_freecounters(zone->mctx, &zone->counters); if (zone->db != NULL) - dns_db_detach(&zone->db); + zone_detachdb(zone); + if (zone->acache != NULL) + dns_acache_detach(&zone->acache); zone_freedbargs(zone); RUNTIME_CHECK(dns_zone_setmasterswithkeys(zone, NULL, NULL, 0) == ISC_R_SUCCESS); @@ -821,6 +828,33 @@ dns_zone_setorigin(dns_zone_t *zone, dns_name_t *origin) { return (result); } +void +dns_zone_setacache(dns_zone_t *zone, dns_acache_t *acache) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(acache != NULL); + + LOCK_ZONE(zone); + if (zone->acache != NULL) + dns_acache_detach(&zone->acache); + dns_acache_attach(acache, &zone->acache); + if (zone->db != NULL) { + isc_result_t result; + + /* + * If the zone reuses an existing DB, the DB needs to be + * set in the acache explicitly. We can safely ignore the + * case where the DB is already set. If other error happens, + * the acache will not work effectively. + */ + result = dns_acache_setdb(acache, zone->db); + if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_acache_setdb() failed: %s", + isc_result_totext(result)); + } + } + UNLOCK_ZONE(zone); +} static isc_result_t dns_zone_setstring(dns_zone_t *zone, char **field, const char *value) { @@ -1423,7 +1457,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, if (result != ISC_R_SUCCESS) goto cleanup; } else { - dns_db_attach(db, &zone->db); + zone_attachdb(zone, db); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED|DNS_ZONEFLG_NEEDNOTIFY); } @@ -2641,7 +2675,7 @@ zone_unload(dns_zone_t *zone) { REQUIRE(LOCKED_ZONE(zone)); - dns_db_detach(&zone->db); + zone_detachdb(zone); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADED); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP); } @@ -3400,7 +3434,7 @@ stub_callback(isc_task_t *task, isc_event_t *event) { dns_db_closeversion(stub->db, &stub->version, ISC_TRUE); LOCK_ZONE(zone); if (zone->db == NULL) - dns_db_attach(stub->db, &zone->db); + zone_attachdb(zone, stub->db); UNLOCK_ZONE(zone); dns_db_detach(&stub->db); @@ -5427,8 +5461,8 @@ zone_replacedb(dns_zone_t *zone, dns_db_t *db, isc_boolean_t dump) { "replacing zone database"); if (zone->db != NULL) - dns_db_detach(&zone->db); - dns_db_attach(db, &zone->db); + zone_detachdb(zone); + zone_attachdb(zone, db); dns_db_settask(zone->db, zone->task); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED|DNS_ZONEFLG_NEEDNOTIFY); return (ISC_R_SUCCESS); @@ -5438,6 +5472,31 @@ zone_replacedb(dns_zone_t *zone, dns_db_t *db, isc_boolean_t dump) { return (result); } +static inline void +zone_attachdb(dns_zone_t *zone, dns_db_t *db) { + REQUIRE(zone->db == NULL && db != NULL); + + dns_db_attach(db, &zone->db); + if (zone->acache != NULL) { + isc_result_t result; + result = dns_acache_setdb(zone->acache, db); + if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_acache_setdb() failed: %s", + isc_result_totext(result)); + } + } +} + +static inline void +zone_detachdb(dns_zone_t *zone) { + REQUIRE(zone->db != NULL); + + if (zone->acache != NULL) + (void)dns_acache_putdb(zone->acache, zone->db); + dns_db_detach(&zone->db); +} + static void zone_xfrdone(dns_zone_t *zone, isc_result_t result) { isc_time_t now; diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index a93179a2d9..90176929dd 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: namedconf.c,v 1.41 2004/11/11 01:08:24 marka Exp $ */ +/* $Id: namedconf.c,v 1.42 2004/12/21 10:45:20 jinmei Exp $ */ #include @@ -733,6 +733,9 @@ view_clauses[] = { { "dnssec-must-be-secure", &cfg_type_mustbesecure, CFG_CLAUSEFLAG_MULTI }, { "ixfr-from-differences", &cfg_type_ixfrdifftype, 0 }, + { "use-additional-cache", &cfg_type_boolean, 0 }, + { "acache-cleaning-interval", &cfg_type_uint32, 0 }, + { "max-acache-size", &cfg_type_sizenodefault, 0 }, { NULL, NULL, 0 } };