From 7781f25078c491a9650dec555bdc86cb0ed49861 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: Fri, 9 Jan 2009 22:24:37 +0000 Subject: [PATCH] 2526. [func] New named option "attach-cache" that allows multiple views to share a single cache to save memory and improve lookup efficiency. [RT 18905] --- CHANGES | 4 + bin/named/include/named/server.h | 3 +- bin/named/include/named/types.h | 4 +- bin/named/server.c | 484 ++++++++++++++++++++++++------- bin/named/statschannel.c | 25 +- doc/arm/Bv9ARM-book.xml | 99 ++++++- lib/dns/cache.c | 68 ++++- lib/dns/include/dns/cache.h | 37 ++- lib/dns/include/dns/view.h | 30 +- lib/dns/view.c | 29 +- lib/isccfg/namedconf.c | 3 +- 11 files changed, 664 insertions(+), 122 deletions(-) diff --git a/CHANGES b/CHANGES index a25b61936b..9ea4631e5c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +2526. [func] New named option "attach-cache" that allows multiple + views to share a single cache to save memory and + improve lookup efficiency. [RT 18905] + 2525. [func] New logging category "query-errors" to provide detailed internal information about query failures, especially about server failures. [RT #19027] diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index d3a75fd933..d1838c95b3 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.93 2008/04/03 05:55:51 marka Exp $ */ +/* $Id: server.h,v 1.94 2009/01/09 22:24:36 jinmei Exp $ */ #ifndef NAMED_SERVER_H #define NAMED_SERVER_H 1 @@ -91,6 +91,7 @@ struct ns_server { isc_boolean_t flushonshutdown; isc_boolean_t log_queries; /*%< For BIND 8 compatibility */ + ns_cachelist_t cachelist; /*%< Possibly shared caches */ dns_stats_t * nsstats; /*%< Server statistics */ dns_stats_t * rcvquerystats; /*% Incoming query statistics */ dns_stats_t * opcodestats; /*%< Incoming message statistics */ diff --git a/bin/named/include/named/types.h b/bin/named/include/named/types.h index eb255201e7..8c335900fc 100644 --- a/bin/named/include/named/types.h +++ b/bin/named/include/named/types.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: types.h,v 1.29 2008/01/17 23:46:59 tbox Exp $ */ +/* $Id: types.h,v 1.30 2009/01/09 22:24:36 jinmei Exp $ */ #ifndef NAMED_TYPES_H #define NAMED_TYPES_H 1 @@ -24,6 +24,8 @@ #include +typedef struct ns_cache ns_cache_t; +typedef ISC_LIST(ns_cache_t) ns_cachelist_t; typedef struct ns_client ns_client_t; typedef struct ns_clientmgr ns_clientmgr_t; typedef struct ns_query ns_query_t; diff --git a/bin/named/server.c b/bin/named/server.c index 124c4c783f..098e21ec75 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: server.c,v 1.522 2008/12/25 02:02:39 jinmei Exp $ */ +/* $Id: server.c,v 1.523 2009/01/09 22:24:36 jinmei Exp $ */ /*! \file */ @@ -142,6 +142,14 @@ fatal(msg, result); \ } while (0) \ +/*% + * Maximum ADB size for views that share a cache. Use this limit to suppress + * the total of memory footprint, which should be the main reason for sharing + * a cache. Only effective when a finite max-cache-size is specified. + * This is currently defined to be 8MB. + */ +#define MAX_ADB_SIZE_FOR_CACHESHARE 8388608 + struct ns_dispatch { isc_sockaddr_t addr; unsigned int dispatchgen; @@ -149,6 +157,14 @@ struct ns_dispatch { ISC_LINK(struct ns_dispatch) link; }; +struct ns_cache { + dns_cache_t *cache; + dns_view_t *primaryview; + isc_boolean_t needflush; + isc_boolean_t adbsizeadjusted; + ISC_LINK(ns_cache_t) link; +}; + struct dumpcontext { isc_mem_t *mctx; isc_boolean_t dumpcache; @@ -973,6 +989,63 @@ setquerystats(dns_zone_t *zone, isc_mem_t *mctx, isc_boolean_t on) { return (ISC_R_SUCCESS); } +static ns_cache_t * +cachelist_find(ns_cachelist_t *cachelist, const char *cachename) { + ns_cache_t *nsc; + + for (nsc = ISC_LIST_HEAD(*cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (strcmp(dns_cache_getname(nsc->cache), cachename) == 0) + return (nsc); + } + + return (NULL); +} + +static isc_boolean_t +cache_reusable(dns_view_t *originview, dns_view_t *view, + isc_boolean_t new_zero_no_soattl) +{ + if (originview->checknames != view->checknames || + dns_resolver_getzeronosoattl(originview->resolver) != + new_zero_no_soattl || + originview->acceptexpired != view->acceptexpired || + originview->enablevalidation != view->enablevalidation || + originview->maxcachettl != view->maxcachettl || + originview->maxncachettl != view->maxncachettl) { + return (ISC_FALSE); + } + + return (ISC_TRUE); +} + +static isc_boolean_t +cache_sharable(dns_view_t *originview, dns_view_t *view, + isc_boolean_t new_zero_no_soattl, + unsigned int new_cleaning_interval, + isc_uint32_t new_max_cache_size) +{ + /* + * If the cache cannot even reused for the same view, it cannot be + * shared with other views. + */ + if (!cache_reusable(originview, view, new_zero_no_soattl)) + return (ISC_FALSE); + + /* + * Check other cache related parameters that must be consistent among + * the sharing views. + */ + if (dns_cache_getcleaninginterval(originview->cache) != + new_cleaning_interval || + dns_cache_getcachesize(originview->cache) != new_max_cache_size) { + return (ISC_FALSE); + } + + return (ISC_TRUE); +} + /* * Configure 'view' according to 'vconfig', taking defaults from 'config' * where values are missing in 'vconfig'. @@ -982,8 +1055,9 @@ setquerystats(dns_zone_t *zone, isc_mem_t *mctx, isc_boolean_t on) { */ static isc_result_t configure_view(dns_view_t *view, const cfg_obj_t *config, - const cfg_obj_t *vconfig, isc_mem_t *mctx, - cfg_aclconfctx_t *actx, isc_boolean_t need_hints) + const cfg_obj_t *vconfig, ns_cachelist_t *cachelist, + isc_mem_t *mctx, cfg_aclconfctx_t *actx, + isc_boolean_t need_hints) { const cfg_obj_t *maps[4]; const cfg_obj_t *cfgmaps[3]; @@ -1005,6 +1079,7 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, dns_cache_t *cache = NULL; isc_result_t result; isc_uint32_t max_adb_size; + unsigned int cleaning_interval; isc_uint32_t max_cache_size; isc_uint32_t max_acache_size; isc_uint32_t lame_ttl; @@ -1014,8 +1089,10 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, dns_dispatch_t *dispatch4 = NULL; dns_dispatch_t *dispatch6 = NULL; isc_boolean_t reused_cache = ISC_FALSE; + isc_boolean_t shared_cache = ISC_FALSE; int i; const char *str; + const char *cachename = NULL; dns_order_t *order = NULL; isc_uint32_t udpsize; unsigned int resopts = 0; @@ -1029,6 +1106,8 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *disablelist = NULL; dns_stats_t *resstats = NULL; dns_stats_t *resquerystats = NULL; + ns_cache_t *nsc; + isc_boolean_t zero_no_soattl; REQUIRE(DNS_VIEW_VALID(view)); @@ -1170,59 +1249,13 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, #endif /* - * Configure the view's cache. Try to reuse an existing - * cache if possible, otherwise create a new cache. - * Note that the ADB is not preserved in either case. - * When a matching view is found, the associated statistics are - * also retrieved and reused. - * - * XXX Determining when it is safe to reuse a cache is - * tricky. When the view's configuration changes, the cached - * data may become invalid because it reflects our old - * view of the world. As more view attributes become - * configurable, we will have to add code here to check - * whether they have changed in ways that could - * invalidate the cache. + * Obtain configuration parameters that affect the decision of whether + * we can reuse/share an existing cache. */ - result = dns_viewlist_find(&ns_g_server->viewlist, - view->name, view->rdclass, - &pview); - if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) - goto cleanup; - if (pview != NULL) { - INSIST(pview->cache != NULL); - isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, - NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(3), - "reusing existing cache"); - reused_cache = ISC_TRUE; - dns_cache_attach(pview->cache, &cache); - dns_view_getresstats(pview, &resstats); - dns_view_getresquerystats(pview, &resquerystats); - dns_view_detach(&pview); - } else { - CHECK(isc_mem_create(0, 0, &cmctx)); - CHECK(dns_cache_create(cmctx, ns_g_taskmgr, ns_g_timermgr, - view->rdclass, "rbt", 0, NULL, &cache)); - isc_mem_setname(cmctx, "cache", NULL); - } - dns_view_setcache(view, cache); - - /* - * cache-file cannot be inherited if views are present, but this - * should be caught by the configuration checking stage. - */ - obj = NULL; - result = ns_config_get(maps, "cache-file", &obj); - if (result == ISC_R_SUCCESS && strcmp(view->name, "_bind") != 0) { - CHECK(dns_cache_setfilename(cache, cfg_obj_asstring(obj))); - if (!reused_cache) - CHECK(dns_cache_load(cache)); - } - obj = NULL; result = ns_config_get(maps, "cleaning-interval", &obj); INSIST(result == ISC_R_SUCCESS); - dns_cache_setcleaninginterval(cache, cfg_obj_asuint32(obj) * 60); + cleaning_interval = cfg_obj_asuint32(obj) * 60; obj = NULL; result = ns_config_get(maps, "max-cache-size", &obj); @@ -1244,13 +1277,8 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, } max_cache_size = (isc_uint32_t)value; } - dns_cache_setcachesize(cache, max_cache_size); - dns_cache_detach(&cache); - - /* - * Check-names. - */ + /* Check-names. */ obj = NULL; result = ns_checknames_get(maps, "response", &obj); INSIST(result == ISC_R_SUCCESS); @@ -1268,6 +1296,159 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, } else INSIST(0); + obj = NULL; + result = ns_config_get(maps, "zero-no-soa-ttl-cache", &obj); + INSIST(result == ISC_R_SUCCESS); + zero_no_soattl = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "dnssec-accept-expired", &obj); + INSIST(result == ISC_R_SUCCESS); + view->acceptexpired = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "dnssec-validation", &obj); + INSIST(result == ISC_R_SUCCESS); + view->enablevalidation = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "max-cache-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->maxcachettl = cfg_obj_asuint32(obj); + + obj = NULL; + result = ns_config_get(maps, "max-ncache-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->maxncachettl = cfg_obj_asuint32(obj); + if (view->maxncachettl > 7 * 24 * 3600) + view->maxncachettl = 7 * 24 * 3600; + + /* + * Configure the view's cache. + * + * First, check to see if there are any attach-cache options. If yes, + * attempt to lookup an existing cache at attach it to the view. If + * there is not one, then try to reuse an existing cache if possible; + * otherwise create a new cache. + * + * Note that the ADB is not preserved or shared in either case. + * + * When a matching view is found, the associated statistics are also + * retrieved and reused. + * + * XXX Determining when it is safe to reuse or share a cache is tricky. + * When the view's configuration changes, the cached data may become + * invalid because it reflects our old view of the world. We check + * some of the configuration parameters that could invalidate the cache + * or otherwise make it unsharable, but there are other configuration + * options that should be checked. For example, if a view uses a + * forwarder, changes in the forwarder configuration may invalidate + * the cache. At the moment, it's the administrator's responsibility to + * ensure these configuration options don't invalidate reusing/sharing. + */ + obj = NULL; + result = ns_config_get(maps, "attach-cache", &obj); + if (result == ISC_R_SUCCESS) + cachename = cfg_obj_asstring(obj); + else + cachename = view->name; + cache = NULL; + nsc = cachelist_find(cachelist, cachename); + if (nsc != NULL) { + if (!cache_sharable(nsc->primaryview, view, zero_no_soattl, + cleaning_interval, max_cache_size)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "views %s and %s can't share the cache " + "due to configuration parameter mismatch", + nsc->primaryview->name, view->name); + result = ISC_R_FAILURE; + goto cleanup; + } + dns_cache_attach(nsc->cache, &cache); + shared_cache = ISC_TRUE; + } else { + if (strcmp(cachename, view->name) == 0) { + result = dns_viewlist_find(&ns_g_server->viewlist, + cachename, view->rdclass, + &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + goto cleanup; + if (pview != NULL) { + if (cache_reusable(pview, view, + zero_no_soattl)) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_DEBUG(1), + "cache cannot be reused " + "for view %s due to " + "configuration parameter " + "mismatch", view->name); + } else { + INSIST(pview->cache != NULL); + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_DEBUG(3), + "reusing existing cache"); + reused_cache = ISC_TRUE; + dns_cache_attach(pview->cache, &cache); + } + dns_view_getresstats(pview, &resstats); + dns_view_getresquerystats(pview, + &resquerystats); + dns_view_detach(&pview); + } + } + if (cache == NULL) { + /* + * Create a cache with the desired name. This normally + * equals the view name, but may also be a forward + * reference to a view that share the cache with this + * view but is not yet configured. If it is not the + * view name but not a forward reference either, then it + * is simply a named cache that is not shared. + */ + CHECK(isc_mem_create(0, 0, &cmctx)); + isc_mem_setname(cmctx, "cache", NULL); + CHECK(dns_cache_create2(cmctx, ns_g_taskmgr, + ns_g_timermgr, view->rdclass, + cachename, "rbt", 0, NULL, + &cache)); + } + nsc = isc_mem_get(mctx, sizeof(*nsc)); + if (nsc == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + nsc->cache = NULL; + dns_cache_attach(cache, &nsc->cache); + nsc->primaryview = view; + nsc->needflush = ISC_FALSE; + nsc->adbsizeadjusted = ISC_FALSE; + ISC_LINK_INIT(nsc, link); + ISC_LIST_APPEND(*cachelist, nsc, link); + } + dns_view_setcache2(view, cache, shared_cache); + + /* + * cache-file cannot be inherited if views are present, but this + * should be caught by the configuration checking stage. + */ + obj = NULL; + result = ns_config_get(maps, "cache-file", &obj); + if (result == ISC_R_SUCCESS && strcmp(view->name, "_bind") != 0) { + CHECK(dns_cache_setfilename(cache, cfg_obj_asstring(obj))); + if (!reused_cache && !shared_cache) + CHECK(dns_cache_load(cache)); + } + + dns_cache_setcleaninginterval(cache, cleaning_interval); + dns_cache_setcachesize(cache, max_cache_size); + + dns_cache_detach(&cache); + /* * Resolver. * @@ -1301,13 +1482,23 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, dns_view_setresquerystats(view, resquerystats); /* - * Set the ADB cache size to 1/8th of the max-cache-size. + * Set the ADB cache size to 1/8th of the max-cache-size or + * MAX_ADB_SIZE_FOR_CACHESHARE when the cache is shared. */ max_adb_size = 0; if (max_cache_size != 0) { max_adb_size = max_cache_size / 8; if (max_adb_size == 0) max_adb_size = 1; /* Force minimum. */ + if (view != nsc->primaryview && + max_adb_size > MAX_ADB_SIZE_FOR_CACHESHARE) { + max_adb_size = MAX_ADB_SIZE_FOR_CACHESHARE; + if (!nsc->adbsizeadjusted) { + dns_adb_setadbsize(nsc->primaryview->adb, + MAX_ADB_SIZE_FOR_CACHESHARE); + nsc->adbsizeadjusted = ISC_TRUE; + } + } } dns_adb_setadbsize(view->adb, max_adb_size); @@ -1322,10 +1513,8 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, lame_ttl = 1800; dns_resolver_setlamettl(view->resolver, lame_ttl); - obj = NULL; - result = ns_config_get(maps, "zero-no-soa-ttl-cache", &obj); - INSIST(result == ISC_R_SUCCESS); - dns_resolver_setzeronosoattl(view->resolver, cfg_obj_asboolean(obj)); + /* Specify whether to use 0-TTL for negative response for SOA query */ + dns_resolver_setzeronosoattl(view->resolver, zero_no_soattl); /* * Set the resolver's EDNS UDP size. @@ -1661,16 +1850,6 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, INSIST(result == ISC_R_SUCCESS); view->enablednssec = cfg_obj_asboolean(obj); - obj = NULL; - result = ns_config_get(maps, "dnssec-accept-expired", &obj); - INSIST(result == ISC_R_SUCCESS); - view->acceptexpired = cfg_obj_asboolean(obj); - - obj = NULL; - result = ns_config_get(maps, "dnssec-validation", &obj); - INSIST(result == ISC_R_SUCCESS); - view->enablevalidation = cfg_obj_asboolean(obj); - obj = NULL; result = ns_config_get(maps, "dnssec-lookaside", &obj); if (result == ISC_R_SUCCESS) { @@ -1725,18 +1904,6 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, if (result == ISC_R_SUCCESS) CHECK(mustbesecure(obj, view->resolver)); - obj = NULL; - result = ns_config_get(maps, "max-cache-ttl", &obj); - INSIST(result == ISC_R_SUCCESS); - view->maxcachettl = cfg_obj_asuint32(obj); - - obj = NULL; - result = ns_config_get(maps, "max-ncache-ttl", &obj); - INSIST(result == ISC_R_SUCCESS); - view->maxncachettl = cfg_obj_asuint32(obj); - if (view->maxncachettl > 7 * 24 * 3600) - view->maxncachettl = 7 * 24 * 3600; - obj = NULL; result = ns_config_get(maps, "preferred-glue", &obj); if (result == ISC_R_SUCCESS) { @@ -2893,10 +3060,13 @@ load_configuration(const char *filename, ns_server_t *server, isc_uint32_t interface_interval; isc_uint32_t reserved; isc_uint32_t udpsize; + ns_cachelist_t cachelist, tmpcachelist; unsigned int maxsocks; + ns_cache_t *nsc; cfg_aclconfctx_init(&aclconfctx); ISC_LIST_INIT(viewlist); + ISC_LIST_INIT(cachelist); /* Ensure exclusive access to configuration data. */ result = isc_task_beginexclusive(server->task); @@ -3283,7 +3453,7 @@ load_configuration(const char *filename, ns_server_t *server, CHECK(create_view(vconfig, &viewlist, &view)); INSIST(view != NULL); - CHECK(configure_view(view, config, vconfig, + CHECK(configure_view(view, config, vconfig, &cachelist, ns_g_mctx, &aclconfctx, ISC_TRUE)); dns_view_freeze(view); dns_view_detach(&view); @@ -3301,7 +3471,7 @@ load_configuration(const char *filename, ns_server_t *server, * In either case, we need to configure and freeze it. */ CHECK(create_view(NULL, &viewlist, &view)); - CHECK(configure_view(view, config, NULL, ns_g_mctx, + CHECK(configure_view(view, config, NULL, &cachelist, ns_g_mctx, &aclconfctx, ISC_TRUE)); dns_view_freeze(view); dns_view_detach(&view); @@ -3320,8 +3490,8 @@ load_configuration(const char *filename, ns_server_t *server, { const cfg_obj_t *vconfig = cfg_listelt_value(element); CHECK(create_view(vconfig, &viewlist, &view)); - CHECK(configure_view(view, config, vconfig, ns_g_mctx, - &aclconfctx, ISC_FALSE)); + CHECK(configure_view(view, config, vconfig, &cachelist, + ns_g_mctx, &aclconfctx, ISC_FALSE)); dns_view_freeze(view); dns_view_detach(&view); view = NULL; @@ -3334,6 +3504,13 @@ load_configuration(const char *filename, ns_server_t *server, server->viewlist = viewlist; viewlist = tmpviewlist; + /* + * Swap our new cache list with the production one. + */ + tmpcachelist = server->cachelist; + server->cachelist = cachelist; + cachelist = tmpcachelist; + /* * Load the TKEY information from the configuration. */ @@ -3625,6 +3802,13 @@ load_configuration(const char *filename, ns_server_t *server, dns_view_detach(&view); } + /* Same cleanup for cache list. */ + while ((nsc = ISC_LIST_HEAD(cachelist)) != NULL) { + ISC_LIST_UNLINK(cachelist, nsc, link); + dns_cache_detach(&nsc->cache); + isc_mem_put(server->mctx, nsc, sizeof(*nsc)); + } + /* * Adjust the listening interfaces in accordance with the source * addresses specified in views and zones. @@ -3770,6 +3954,7 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { dns_view_t *view, *view_next; ns_server_t *server = (ns_server_t *)event->ev_arg; isc_boolean_t flush = server->flushonshutdown; + ns_cache_t *nsc; UNUSED(task); INSIST(task == server->task); @@ -3799,6 +3984,12 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { dns_view_detach(&view); } + while ((nsc = ISC_LIST_HEAD(server->cachelist)) != NULL) { + ISC_LIST_UNLINK(server->cachelist, nsc, link); + dns_cache_detach(&nsc->cache); + isc_mem_put(server->mctx, nsc, sizeof(*nsc)); + } + isc_timer_detach(&server->interface_timer); isc_timer_detach(&server->heartbeat_timer); isc_timer_detach(&server->pps_timer); @@ -3953,6 +4144,8 @@ ns_server_create(isc_mem_t *mctx, ns_server_t **serverp) { ISC_LIST_INIT(server->statschannels); + ISC_LIST_INIT(server->cachelist); + server->magic = NS_SERVER_MAGIC; *serverp = server; } @@ -3991,6 +4184,7 @@ ns_server_destroy(ns_server_t **serverp) { isc_event_free(&server->reload_event); INSIST(ISC_LIST_EMPTY(server->viewlist)); + INSIST(ISC_LIST_EMPTY(server->cachelist)); dns_aclenv_destroy(&server->aclenv); @@ -4652,15 +4846,23 @@ dumpdone(void *arg, isc_result_t result) { nextview: fprintf(dctx->fp, ";\n; Start view %s\n;\n", dctx->view->view->name); resume: - if (dctx->zone == NULL && dctx->cache == NULL && dctx->dumpcache) { + if (dctx->dumpcache && dns_view_iscacheshared(dctx->view->view)) { + fprintf(dctx->fp, + ";\n; Cache of view '%s' is shared as '%s'\n", + dctx->view->view->name, + dns_cache_getname(dctx->view->view->cache)); + } else if (dctx->zone == NULL && dctx->cache == NULL && + dctx->dumpcache) + { style = &dns_master_style_cache; /* start cache dump */ if (dctx->view->view->cachedb != NULL) dns_db_attach(dctx->view->view->cachedb, &dctx->cache); if (dctx->cache != NULL) { - - fprintf(dctx->fp, ";\n; Cache dump of view '%s'\n;\n", - dctx->view->view->name); + fprintf(dctx->fp, + ";\n; Cache dump of view '%s' (cache %s)\n;\n", + dctx->view->view->name, + dns_cache_getname(dctx->view->view->cache)); result = dns_master_dumptostreaminc(dctx->mctx, dctx->cache, NULL, style, dctx->fp, @@ -4937,6 +5139,7 @@ ns_server_flushcache(ns_server_t *server, char *args) { isc_boolean_t flushed; isc_boolean_t found; isc_result_t result; + ns_cache_t *nsc; /* Skip the command name. */ ptr = next_token(&args, " \t"); @@ -4950,22 +5153,96 @@ ns_server_flushcache(ns_server_t *server, char *args) { RUNTIME_CHECK(result == ISC_R_SUCCESS); flushed = ISC_TRUE; found = ISC_FALSE; - for (view = ISC_LIST_HEAD(server->viewlist); - view != NULL; - view = ISC_LIST_NEXT(view, link)) - { - if (viewname != NULL && strcasecmp(viewname, view->name) != 0) - continue; + + /* + * Flushing a cache is tricky when caches are shared by multiple views. + * We first identify which caches should be flushed in the local cache + * list, flush these caches, and then update other views that refer to + * the flushed cache DB. + */ + if (viewname != NULL) { + /* + * Mark caches that need to be flushed. This is an O(#view^2) + * operation in the very worst case, but should be normally + * much more lightweight because only a few (most typically just + * one) views will match. + */ + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (strcasecmp(viewname, view->name) != 0) + continue; + found = ISC_TRUE; + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (nsc->cache == view->cache) + break; + } + INSIST(nsc != NULL); + nsc->needflush = ISC_TRUE; + } + } else found = ISC_TRUE; - result = dns_view_flushcache(view); + + /* Perform flush */ + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (viewname != NULL && !nsc->needflush) + continue; + nsc->needflush = ISC_TRUE; + result = dns_view_flushcache2(nsc->primaryview, ISC_FALSE); if (result != ISC_R_SUCCESS) { flushed = ISC_FALSE; isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, "flushing cache in view '%s' failed: %s", - view->name, isc_result_totext(result)); + nsc->primaryview->name, + isc_result_totext(result)); } } + + /* + * Fix up views that share a flushed cache: let the views update the + * cache DB they're referring to. This could also be an expensive + * operation, but should typically be marginal: the inner loop is only + * necessary for views that share a cache, and if there are many such + * views the number of shared cache should normally be small. + * A worst case is that we have n views and n/2 caches, each shared by + * two views. Then this will be a O(n^2/4) operation. + */ + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (!dns_view_iscacheshared(view)) + continue; + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (!nsc->needflush || nsc->cache != view->cache) + continue; + result = dns_view_flushcache2(view, ISC_TRUE); + if (result != ISC_R_SUCCESS) { + flushed = ISC_FALSE; + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "fixing cache in view '%s' " + "failed: %s", view->name, + isc_result_totext(result)); + } + } + } + + /* Cleanup the cache list. */ + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + nsc->needflush = ISC_FALSE; + } + if (flushed && found) { if (viewname != NULL) isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, @@ -5034,6 +5311,11 @@ ns_server_flushname(ns_server_t *server, char *args) { if (viewname != NULL && strcasecmp(viewname, view->name) != 0) continue; found = ISC_TRUE; + /* + * It's a little inefficient to try flushing name for all views + * if some of the views share a single cache. But since the + * operation is lightweight we prefer simplicity here. + */ result = dns_view_flushname(view, name); if (result != ISC_R_SUCCESS) { flushed = ISC_FALSE; diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index a5ca1bbb92..73cf3b7b7c 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: statschannel.c,v 1.15 2008/12/18 02:23:27 marka Exp $ */ +/* $Id: statschannel.c,v 1.16 2009/01/09 22:24:36 jinmei Exp $ */ /*! \file */ @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -635,7 +636,7 @@ generatexml(ns_server_t *server, int *buflen, xmlChar **buf) { TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "bind")); TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "statistics")); TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "version", - ISC_XMLCHAR "2.0")); + ISC_XMLCHAR "2.1")); /* Set common fields for statistics dump */ dumparg.type = statsformat_xml; @@ -674,11 +675,15 @@ generatexml(ns_server_t *server, int *buflen, xmlChar **buf) { cachestats = dns_db_getrrsetstats(view->cachedb); if (cachestats != NULL) { - xmlTextWriterStartElement(writer, - ISC_XMLCHAR "cache"); + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "cache")); + TRY0(xmlTextWriterWriteAttribute(writer, + ISC_XMLCHAR "name", + ISC_XMLCHAR + dns_cache_getname(view->cache))); dns_rdatasetstats_dump(cachestats, rdatasetstats_dump, &dumparg, 0); - xmlTextWriterEndElement(writer); /* cache */ + TRY0(xmlTextWriterEndElement(writer)); /* cache */ } xmlTextWriterEndElement(writer); /* view */ @@ -1220,7 +1225,15 @@ ns_stats_dump(ns_server_t *server, FILE *fp) { if (strcmp(view->name, "_default") == 0) fprintf(fp, "[View: default]\n"); else - fprintf(fp, "[View: %s]\n", view->name); + fprintf(fp, "[View: %s (Cache: %s)]\n", view->name, + dns_cache_getname(view->cache)); + if (dns_view_iscacheshared(view)) { + /* + * Avoid dumping redundant statistics when the cache is + * shared. + */ + continue; + } dns_rdatasetstats_dump(cachestats, rdatasetstats_dump, &dumparg, 0); } diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 0123d86c9a..cd1db7ac82 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -18,7 +18,7 @@ - PERFORMANCE OF THIS SOFTWARE. --> - + BIND 9 Administrator Reference Manual @@ -4706,6 +4706,7 @@ category notify { null; }; options { + attach-cache cache_name; version version_string; hostname hostname_string; server-id server_id_string; @@ -4888,6 +4889,102 @@ category notify { null; }; + + attach-cache + + + Allows multiple views to share a single cache + database. + Each view has its own cache database by default, but + if multiple views have the same operational policy + for name resolution and caching, those views can + share a single cache to save memory and possibly + improve resolution efficiency by using this option. + + + + The attach-cache option + may also be specified in view + statements, in which case it overrides the + global attach-cache option. + + + + The cache_name specifies + the cache to be shared. + When the named server configures + views which are supposed to share a cache, it + creates a cache with the specified name for the + first view of these sharing views. + The rest of the views will simply refer to the + already created cache. + + + + One common configuration to share a cache would be to + allow all views to share a single cache. + This can be done by specifying + the attach-cache as a global + option with an arbitrary name. + + + + Another possible operation is to allow a subset of + all views to share a cache while the others to + retain their own caches. + For example, if there are three views A, B, and C, + and only A and B should share a cache, specify the + attach-cache option as a view A (or + B)'s option, referring to the other view name: + + + + view "A" { + // this view has its own cache + ... + }; + view "B" { + // this view refers to A's cache + attach-cache "A"; + }; + view "C" { + // this view has its own cache + ... + }; + + + + Views that share a cache must have the same policy + on configurable parameters that may affect caching. + The current implementation requires the following + configurable options be consistent among these + views: + check-names, + cleaning-interval, + dnssec-accept-expired, + dnssec-validation, + max-cache-ttl, + max-ncache-ttl, + max-cache-size, and + zero-no-soa-ttl. + + + + Note that there may be other parameters that may + cause confusion if they are inconsistent for + different views that share a single cache. + For example, if these views define different sets of + forwarders that can return different answers for the + same question, sharing the answer does not make + sense or could even be harmful. + It is administrator's responsibility to ensure + configuration differences in different views do + not cause disruption with a shared cache. + + + + + directory diff --git a/lib/dns/cache.c b/lib/dns/cache.c index 1b8c388b0e..f4907d1add 100644 --- a/lib/dns/cache.c +++ b/lib/dns/cache.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: cache.c,v 1.80 2008/09/24 02:46:22 marka Exp $ */ +/* $Id: cache.c,v 1.81 2009/01/09 22:24:36 jinmei Exp $ */ /*! \file */ @@ -122,6 +122,7 @@ struct dns_cache { isc_mutex_t lock; isc_mutex_t filelock; isc_mem_t *mctx; + char *name; /* Locked by 'lock'. */ int references; @@ -132,6 +133,7 @@ struct dns_cache { char *db_type; int db_argc; char **db_argv; + isc_uint32_t size; /* Locked by 'filelock'. */ char *filename; @@ -170,6 +172,16 @@ dns_cache_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, dns_rdataclass_t rdclass, const char *db_type, unsigned int db_argc, char **db_argv, dns_cache_t **cachep) +{ + return (dns_cache_create2(mctx, taskmgr, timermgr, rdclass, "", + db_type, db_argc, db_argv, cachep)); +} + +isc_result_t +dns_cache_create2(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_rdataclass_t rdclass, + const char *cachename, const char *db_type, + unsigned int db_argc, char **db_argv, dns_cache_t **cachep) { isc_result_t result; dns_cache_t *cache; @@ -178,6 +190,7 @@ dns_cache_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, REQUIRE(cachep != NULL); REQUIRE(*cachep == NULL); REQUIRE(mctx != NULL); + REQUIRE(cachename != NULL); cache = isc_mem_get(mctx, sizeof(*cache)); if (cache == NULL) @@ -186,6 +199,15 @@ dns_cache_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, cache->mctx = NULL; isc_mem_attach(mctx, &cache->mctx); + cache->name = NULL; + if (cachename != NULL) { + cache->name = isc_mem_strdup(mctx, cachename); + if (cache->name == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_mem; + } + } + result = isc_mutex_init(&cache->lock); if (result != ISC_R_SUCCESS) goto cleanup_mem; @@ -266,6 +288,8 @@ dns_cache_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, cleanup_lock: DESTROYLOCK(&cache->lock); cleanup_mem: + if (cache->name != NULL) + isc_mem_free(mctx, cache->name); isc_mem_put(mctx, cache, sizeof(*cache)); isc_mem_detach(&mctx); return (result); @@ -314,6 +338,9 @@ cache_free(dns_cache_t *cache) { if (cache->db_type != NULL) isc_mem_free(cache->mctx, cache->db_type); + if (cache->name != NULL) + isc_mem_free(cache->mctx, cache->name); + DESTROYLOCK(&cache->lock); DESTROYLOCK(&cache->filelock); cache->magic = 0; @@ -484,6 +511,26 @@ dns_cache_setcleaninginterval(dns_cache_t *cache, unsigned int t) { UNLOCK(&cache->lock); } +unsigned int +dns_cache_getcleaninginterval(dns_cache_t *cache) { + unsigned int t; + + REQUIRE(VALID_CACHE(cache)); + + LOCK(&cache->lock); + t = cache->cleaner.cleaning_interval; + UNLOCK(&cache->lock); + + return (t); +} + +const char * +dns_cache_getname(dns_cache_t *cache) { + REQUIRE(VALID_CACHE(cache)); + + return (cache->name); +} + /* * Initialize the cache cleaner object at *cleaner. * Space for the object must be allocated by the caller. @@ -510,6 +557,7 @@ cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr, cleaner->cleaning_timer = NULL; cleaner->resched_event = NULL; cleaner->overmem_event = NULL; + cleaner->cleaning_interval = 0; /* Initially turned off. */ result = dns_db_createiterator(cleaner->cache->db, ISC_FALSE, &cleaner->iterator); @@ -538,7 +586,6 @@ cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr, goto cleanup; } - cleaner->cleaning_interval = 0; /* Initially turned off. */ result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, cleaner->task, cleaning_timer_action, cleaner, @@ -940,6 +987,10 @@ dns_cache_setcachesize(dns_cache_t *cache, isc_uint32_t size) { if (size != 0 && size < DNS_CACHE_MINSIZE) size = DNS_CACHE_MINSIZE; + LOCK(&cache->lock); + cache->size = size; + UNLOCK(&cache->lock); + hiwater = size - (size >> 3); /* Approximately 7/8ths. */ lowater = size - (size >> 2); /* Approximately 3/4ths. */ @@ -963,6 +1014,19 @@ dns_cache_setcachesize(dns_cache_t *cache, isc_uint32_t size) { isc_mem_setwater(cache->mctx, water, cache, hiwater, lowater); } +isc_uint32_t +dns_cache_getcachesize(dns_cache_t *cache) { + isc_uint32_t size; + + REQUIRE(VALID_CACHE(cache)); + + LOCK(&cache->lock); + size = cache->size; + UNLOCK(&cache->lock); + + return (size); +} + /* * The cleaner task is shutting down; do the necessary cleanup. */ diff --git a/lib/dns/include/dns/cache.h b/lib/dns/include/dns/cache.h index 7b372357d7..34ec6f3372 100644 --- a/lib/dns/include/dns/cache.h +++ b/lib/dns/include/dns/cache.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: cache.h,v 1.26 2007/06/19 23:47:16 tbox Exp $ */ +/* $Id: cache.h,v 1.27 2009/01/09 22:24:37 jinmei Exp $ */ #ifndef DNS_CACHE_H #define DNS_CACHE_H 1 @@ -65,8 +65,15 @@ dns_cache_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, dns_rdataclass_t rdclass, const char *db_type, unsigned int db_argc, char **db_argv, dns_cache_t **cachep); +isc_result_t +dns_cache_create2(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_rdataclass_t rdclass, + const char *cachename, const char *db_type, + unsigned int db_argc, char **db_argv, dns_cache_t **cachep); /*%< - * Create a new DNS cache. + * Create a new DNS cache. dns_cache_create2() will create a named cache. + * dns_cache_create() is a backward compatible version that internally specifies + * an empty name. * * Requires: * @@ -76,6 +83,8 @@ dns_cache_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, * manager, or both are NULL. If NULL, no periodic cleaning of the * cache will take place. * + *\li 'cachename' is a valid string. This must not be NULL. + * *\li 'cachep' is a valid pointer, and *cachep == NULL * * Ensures: @@ -217,12 +226,36 @@ dns_cache_setcleaninginterval(dns_cache_t *cache, unsigned int interval); * Set the periodic cache cleaning interval to 'interval' seconds. */ +unsigned int +dns_cache_getcleaninginterval(dns_cache_t *cache); +/*%< + * Get the periodic cache cleaning interval to 'interval' seconds. + */ + +isc_uint32_t +dns_cache_getcachesize(dns_cache_t *cache); +/*%< + * Get the maximum cache size. + */ + +const char * +dns_cache_getname(dns_cache_t *cache); +/*%< + * Get the cache name. + */ + void dns_cache_setcachesize(dns_cache_t *cache, isc_uint32_t size); /*%< * Set the maximum cache size. 0 means unlimited. */ +isc_uint32_t +dns_cache_getcachesize(dns_cache_t *cache); +/*%< + * Get the maximum cache size. + */ + isc_result_t dns_cache_flush(dns_cache_t *cache); /*%< diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index feae030acd..7dc79de1fe 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.113 2009/01/05 23:47:53 tbox Exp $ */ +/* $Id: view.h,v 1.114 2009/01/09 22:24:37 jinmei Exp $ */ #ifndef DNS_VIEW_H #define DNS_VIEW_H 1 @@ -102,6 +102,7 @@ struct dns_view { isc_event_t reqevent; dns_stats_t * resstats; dns_stats_t * resquerystats; + isc_boolean_t cacheshared; /* Configurable data. */ dns_tsig_keyring_t * statickeys; @@ -308,8 +309,12 @@ dns_view_createresolver(dns_view_t *view, void dns_view_setcache(dns_view_t *view, dns_cache_t *cache); +void +dns_view_setcache2(dns_view_t *view, dns_cache_t *cache, isc_boolean_t shared); /*%< - * Set the view's cache database. + * Set the view's cache database. If 'shared' is true, this means the cache + * is created by another view and is shared with that view. dns_view_setcache() + * is a backward compatible version equivalent to setcache2(..., ISC_FALSE). * * Requires: * @@ -726,8 +731,14 @@ dns_view_dumpdbtostream(dns_view_t *view, FILE *fp); isc_result_t dns_view_flushcache(dns_view_t *view); +isc_result_t +dns_view_flushcache2(dns_view_t *view, isc_boolean_t fixuponly); /*%< - * Flush the view's cache (and ADB). + * Flush the view's cache (and ADB). If 'fixuponly' is true, it only updates + * the internal reference to the cache DB with omitting actual flush operation. + * 'fixuponly' is intended to be used for a view that shares a cache with + * a different view. dns_view_flushcache() is a backward compatible version + * that always sets fixuponly to false. * * Requires: * 'view' is valid. @@ -876,4 +887,17 @@ dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp); *\li 'statsp' != NULL && '*statsp' != NULL */ +isc_boolean_t +dns_view_iscacheshared(dns_view_t *view); +/*%< + * Check if the view shares the cache created by another view. + * + * Requires: + * \li 'view' is valid. + * + * Returns: + *\li #ISC_TRUE if the cache is shared. + *\li #ISC_FALSE othewise. + */ + #endif /* DNS_VIEW_H */ diff --git a/lib/dns/view.c b/lib/dns/view.c index ba5a811fae..2b9e851cae 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: view.c,v 1.150 2008/06/17 03:14:20 marka Exp $ */ +/* $Id: view.c,v 1.151 2009/01/09 22:24:36 jinmei Exp $ */ /*! \file */ @@ -154,6 +154,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, view->rootexclude = NULL; view->resstats = NULL; view->resquerystats = NULL; + view->cacheshared = ISC_FALSE; /* * Initialize configuration data with default values. @@ -626,9 +627,15 @@ dns_view_createresolver(dns_view_t *view, void dns_view_setcache(dns_view_t *view, dns_cache_t *cache) { + dns_view_setcache2(view, cache, ISC_FALSE); +} + +void +dns_view_setcache2(dns_view_t *view, dns_cache_t *cache, isc_boolean_t shared) { REQUIRE(DNS_VIEW_VALID(view)); REQUIRE(!view->frozen); + view->cacheshared = shared; if (view->cache != NULL) { if (view->acache != NULL) dns_acache_putdb(view->acache, view->cachedb); @@ -643,6 +650,13 @@ dns_view_setcache(dns_view_t *view, dns_cache_t *cache) { dns_acache_setdb(view->acache, view->cachedb); } +isc_boolean_t +dns_view_iscacheshared(dns_view_t *view) { + REQUIRE(DNS_VIEW_VALID(view)); + + return (view->cacheshared); +} + void dns_view_sethints(dns_view_t *view, dns_db_t *hints) { REQUIRE(DNS_VIEW_VALID(view)); @@ -1278,15 +1292,22 @@ dns_view_dumpdbtostream(dns_view_t *view, FILE *fp) { isc_result_t dns_view_flushcache(dns_view_t *view) { + return (dns_view_flushcache2(view, ISC_FALSE)); +} + +isc_result_t +dns_view_flushcache2(dns_view_t *view, isc_boolean_t fixuponly) { isc_result_t result; REQUIRE(DNS_VIEW_VALID(view)); if (view->cachedb == NULL) return (ISC_R_SUCCESS); - result = dns_cache_flush(view->cache); - if (result != ISC_R_SUCCESS) - return (result); + if (!fixuponly) { + result = dns_cache_flush(view->cache); + if (result != ISC_R_SUCCESS) + return (result); + } if (view->acache != NULL) dns_acache_putdb(view->acache, view->cachedb); dns_db_detach(&view->cachedb); diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 0610489464..3c954c977c 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: namedconf.c,v 1.92 2008/09/27 23:35:31 jinmei Exp $ */ +/* $Id: namedconf.c,v 1.93 2009/01/09 22:24:37 jinmei Exp $ */ /*! \file */ @@ -797,6 +797,7 @@ view_clauses[] = { { "allow-recursion-on", &cfg_type_bracketed_aml, 0 }, { "allow-v6-synthesis", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_OBSOLETE }, + { "attach-cache", &cfg_type_astring, 0 }, { "auth-nxdomain", &cfg_type_boolean, CFG_CLAUSEFLAG_NEWDEFAULT }, { "cache-file", &cfg_type_qstring, 0 }, { "check-names", &cfg_type_checknames, CFG_CLAUSEFLAG_MULTI },