servfail-ttl 1;\n\
# sortlist <none>\n\
stale-answer-enable false;\n\
+ stale-refresh-time 30; /* 30 seconds */\n\
stale-answer-ttl 1; /* 1 second */\n\
stale-cache-enable false;\n\
synth-from-dnssec no;\n\
size_t max_adb_size;
uint32_t lame_ttl, fail_ttl;
uint32_t max_stale_ttl = 0;
+ uint32_t stale_refresh_time = 0;
dns_tsig_keyring_t *ring = NULL;
dns_view_t *pview = NULL; /* Production view */
isc_mem_t *cmctx = NULL, *hmctx = NULL;
view->staleanswersok = dns_stale_answer_conf;
}
+ obj = NULL;
+ result = named_config_get(maps, "stale-refresh-time", &obj);
+ INSIST(result == ISC_R_SUCCESS);
+ stale_refresh_time = cfg_obj_asduration(obj);
+
/*
* Configure the view's cache.
*
dns_cache_setcachesize(cache, max_cache_size);
dns_cache_setservestalettl(cache, max_stale_ttl);
+ dns_cache_setservestalerefresh(cache, stale_refresh_time);
dns_cache_detach(&cache);
NULL, /* getsize */
NULL, /* setservestalettl */
NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
NULL, /* setgluecachestats */
NULL /* adjusthashsize */
};
return (result == ISC_R_SUCCESS ? ttl : 0);
}
+void
+dns_cache_setservestalerefresh(dns_cache_t *cache, uint32_t interval) {
+ REQUIRE(VALID_CACHE(cache));
+
+ (void)dns_db_setservestalerefresh(cache->db, interval);
+}
+
/*
* The cleaner task is shutting down; do the necessary cleanup.
*/
return (ISC_R_NOTIMPLEMENTED);
}
+isc_result_t
+dns_db_setservestalerefresh(dns_db_t *db, uint32_t interval) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->setservestalerefresh != NULL) {
+ return ((db->methods->setservestalerefresh)(db, interval));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+dns_db_getservestalerefresh(dns_db_t *db, uint32_t *interval) {
+ REQUIRE(DNS_DB_VALID(db));
+ REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
+
+ if (db->methods->getservestalerefresh != NULL) {
+ return ((db->methods->getservestalerefresh)(db, interval));
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
isc_result_t
dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats) {
REQUIRE(dns_db_iszone(db));
NULL, /* getsize */
NULL, /* setservestalettl */
NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
NULL, /* setgluecachestats */
NULL /* adjusthashsize */
};
*\li 'cache' to be valid.
*/
+void
+dns_cache_setservestalerefresh(dns_cache_t *cache, uint32_t interval);
+/*%<
+ * Sets the length of time to wait before attempting to refresh a rrset
+ * if a previous attempt in doing so has failed.
+ * During this time window if stale rrset are available in cache they
+ * will be directly returned to client.
+ *
+ * Requires:
+ *\li 'cache' to be valid.
+ */
+
isc_result_t
dns_cache_flush(dns_cache_t *cache);
/*%<
uint64_t *records, uint64_t *bytes);
isc_result_t (*setservestalettl)(dns_db_t *db, dns_ttl_t ttl);
isc_result_t (*getservestalettl)(dns_db_t *db, dns_ttl_t *ttl);
+ isc_result_t (*setservestalerefresh)(dns_db_t *db, uint32_t interval);
+ isc_result_t (*getservestalerefresh)(dns_db_t *db, uint32_t *interval);
isc_result_t (*setgluecachestats)(dns_db_t *db, isc_stats_t *stats);
isc_result_t (*adjusthashsize)(dns_db_t *db, size_t size);
} dns_dbmethods_t;
#define DNS_DBFIND_ADDITIONALOK 0x0100
#define DNS_DBFIND_NOZONECUT 0x0200
#define DNS_DBFIND_STALEOK 0x0400
+#define DNS_DBFIND_STALEENABLED 0x0800
/*@}*/
/*@{*/
* \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
*/
+isc_result_t
+dns_db_setservestalerefresh(dns_db_t *db, uint32_t interval);
+/*%<
+ * Sets the length of time to wait before attempting to refresh a rrset
+ * if a previous attempt in doing so has failed.
+ * During this time window if stale rrset are available in cache they
+ * will be directly returned to client.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'interval' is number of seconds before attempting to refresh data.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
+isc_result_t
+dns_db_getservestalerefresh(dns_db_t *db, uint32_t *interval);
+/*%<
+ * Gets the length of time in which stale answers are directly returned from
+ * cache before attempting to refresh them, in case a previous attempt in
+ * doing so has failed.
+ *
+ * Requires:
+ * \li 'db' is a valid cache database.
+ * \li 'interval' is number of seconds before attempting to refresh data.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
+ */
+
isc_result_t
dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats);
/*%<
rbtdb_rdatatype_t type;
atomic_uint_least16_t attributes;
dns_trust_t trust;
+ isc_stdtime_t last_refresh_fail_ts;
struct noqname *noqname;
struct noqname *closest;
unsigned int is_mmapped : 1;
*/
dns_ttl_t serve_stale_ttl;
+ /*
+ * The time after a failed lookup, where stale answers from cache
+ * may be used directly in a DNS response without attempting a
+ * new iterative lookup.
+ */
+ uint32_t serve_stale_refresh;
+
/*
* This is a linked list used to implement the LRU cache. There will
* be node_lock_count linked lists here. Nodes in bucket 1 will be
stale > search->now) {
mark_header_stale(search->rbtdb, header);
*header_prev = header;
+ /*
+ * If DNS_DBFIND_STALEOK is set then it means we failed
+ * to resolve the name during recursion, in this case we
+ * mark the time in which the refresh failed.
+ */
+ if ((search->options & DNS_DBFIND_STALEOK) != 0) {
+ header->last_refresh_fail_ts = search->now;
+ } else if ((search->options &
+ DNS_DBFIND_STALEENABLED) != 0 &&
+ search->now <
+ (header->last_refresh_fail_ts +
+ search->rbtdb->serve_stale_refresh))
+ {
+ /*
+ * If we are within interval between last
+ * refresh failure time + 'stale-refresh-time',
+ * then don't skip this stale entry but use it
+ * instead.
+ */
+ return (false);
+ }
return ((search->options & DNS_DBFIND_STALEOK) == 0);
}
return (ISC_R_SUCCESS);
}
+static isc_result_t
+setservestalerefresh(dns_db_t *db, uint32_t interval) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ /* currently no bounds checking. 0 means disable. */
+ rbtdb->serve_stale_refresh = interval;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+getservestalerefresh(dns_db_t *db, uint32_t *interval) {
+ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
+
+ REQUIRE(VALID_RBTDB(rbtdb));
+ REQUIRE(IS_CACHE(rbtdb));
+
+ *interval = rbtdb->serve_stale_refresh;
+ return (ISC_R_SUCCESS);
+}
+
static dns_dbmethods_t zone_methods = { attach,
detach,
beginload,
getsize,
NULL, /* setservestalettl */
NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
setgluecachestats,
adjusthashsize };
NULL, /* getsize */
setservestalettl,
getservestalettl,
+ setservestalerefresh,
+ getservestalerefresh,
NULL,
adjusthashsize };
NULL, /* getsize */
NULL, /* setservestalettl */
NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
NULL, /* setgluecachestats */
NULL /* adjusthashsize */
};
NULL, /* getsize */
NULL, /* setservestalettl */
NULL, /* getservestalettl */
+ NULL, /* setservestalerefresh */
+ NULL, /* getservestalerefresh */
NULL, /* setgluecachestats */
NULL /* adjusthashsize */
};
dns_cache_flushnode
dns_cache_getcachesize
dns_cache_getname
+dns_cache_getservestalerefresh
dns_cache_getservestalettl
dns_cache_getstats
dns_cache_load
@END LIBXML2
dns_cache_setcachesize
dns_cache_setfilename
+dns_cache_setservestalerefresh
dns_cache_setservestalettl
dns_cache_updatestats
dns_catz_add_zone
dns_db_getoriginnode
dns_db_getrrsetstats
dns_db_getservestalettl
+dns_db_getservestalerefresh
dns_db_getsigningtime
dns_db_getsize
dns_db_getsoaserial
dns_db_setcachestats
dns_db_setgluecachestats
dns_db_setservestalettl
+dns_db_setservestalerefresh
dns_db_setsigningtime
dns_db_settask
dns_db_subtractrdataset
{ "stale-answer-enable", &cfg_type_boolean, 0 },
{ "stale-answer-ttl", &cfg_type_duration, 0 },
{ "stale-cache-enable", &cfg_type_boolean, 0 },
+ { "stale-refresh-time", &cfg_type_duration, 0 },
{ "suppress-initial-notify", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI },
{ "synth-from-dnssec", &cfg_type_boolean, 0 },
{ "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_ANCIENT },
dns_clientinfo_t ci;
dns_name_t *rpzqname = NULL;
unsigned int dboptions;
+ dns_ttl_t stale_ttl = 0;
+ dns_ttl_t stale_refresh = 0;
+ bool dbfind_stale = false;
CCTRACE(ISC_LOG_DEBUG(3), "query_lookup");
dboptions |= DNS_DBFIND_COVERINGNSEC;
}
+ dns_db_getservestalerefresh(qctx->client->view->cachedb,
+ &stale_refresh);
+ dns_db_getservestalettl(qctx->client->view->cachedb, &stale_ttl);
+ if (stale_refresh > 0) {
+ if (qctx->client->view->staleanswersok == dns_stale_answer_yes)
+ {
+ dboptions |= DNS_DBFIND_STALEENABLED;
+ } else if (qctx->client->view->staleanswersok ==
+ dns_stale_answer_conf) {
+ if (qctx->client->view->staleanswersenable &&
+ stale_ttl > 0) {
+ dboptions |= DNS_DBFIND_STALEENABLED;
+ }
+ }
+ }
+
result = dns_db_findext(qctx->db, rpzqname, qctx->version, qctx->type,
dboptions, qctx->client->now, &qctx->node,
qctx->fname, &cm, &ci, qctx->rdataset,
dns_cache_updatestats(qctx->view->cache, result);
}
- if ((qctx->client->query.dboptions & DNS_DBFIND_STALEOK) != 0) {
+ /*
+ * If DNS_DBFIND_STALEOK is set this means we are dealing with a
+ * lookup following a failed lookup and it is okay to serve a stale
+ * answer. This will start a time window in rbtdb, tracking the last
+ * time the RRset lookup failed.
+ *
+ * A stale answer may also be served if this is a normal lookup,
+ * the view has enabled serve-stale (DNS_DBFIND_STALE_ENABLED is set),
+ * and the request is within the stale-refresh-time window. If this
+ * is the case we have to make sure that the lookup found a stale
+ * answer, otherwise "fresh" answers are also treated as stale.
+ */
+ dbfind_stale = ((dboptions & DNS_DBFIND_STALEOK) != 0);
+ if (dbfind_stale != 0 ||
+ (((dboptions & DNS_DBFIND_STALEENABLED) != 0) &&
+ STALE(qctx->rdataset)))
+ {
char namebuf[DNS_NAME_FORMATSIZE];
bool success;
+ inc_stats(qctx->client, ns_statscounter_trystale);
+
qctx->client->query.dboptions &= ~DNS_DBFIND_STALEOK;
if (dns_rdataset_isassociated(qctx->rdataset) &&
dns_rdataset_count(qctx->rdataset) > 0 &&
dns_name_format(qctx->client->query.qname, namebuf,
sizeof(namebuf));
- isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
- NS_LOGMODULE_QUERY, ISC_LOG_INFO,
- "%s resolver failure, stale answer %s", namebuf,
- success ? "used" : "unavailable");
+ if (dbfind_stale) {
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "%s resolver failure, stale answer %s",
+ namebuf,
+ success ? "used" : "unavailable");
+ } else {
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "%s query within stale refresh time, "
+ "stale answer %s",
+ namebuf,
+ success ? "used" : "unavailable");
+ }
if (!success) {
QUERY_ERROR(qctx, DNS_R_SERVFAIL);
if (staleanswersok) {
qctx->client->query.dboptions |= DNS_DBFIND_STALEOK;
- inc_stats(qctx->client, ns_statscounter_trystale);
if (qctx->client->query.fetch != NULL) {
dns_resolver_destroyfetch(&qctx->client->query.fetch);
}