From efbf80be5834e922b00784a00f27636c0f8e4034 Mon Sep 17 00:00:00 2001 From: Dominik Derigs Date: Sat, 26 Nov 2022 21:18:34 +0000 Subject: Make max staleness of stale cache entries configurable and default to one day. --- man/dnsmasq.8 | 5 +++-- src/cache.c | 16 ++++++++++++---- src/config.h | 1 + src/dnsmasq.h | 6 +++--- src/option.c | 22 ++++++++++++++++++++-- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 53bccb8..2495ed1 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -828,11 +828,12 @@ name on successive queries, for load-balancing. This turns off that behaviour, so that the records are always returned in the order that they are received from upstream. .TP -.B --use-stale-cache +.B --use-stale-cache[=] When set, if a DNS name exists in the cache, but its time-to-live has expired, dnsmasq will return the data anyway. (It attempts to refresh the data with an upstream query after returning the stale data.) This can improve speed and reliability. It comes at the expense of sometimes returning out-of-date data and less efficient cache utilisation, since old data cannot be flushed when its TTL expires, so the cache becomes -strictly least-recently-used. +mostly least-recently-used. To mitigate issues caused by massively outdated DNS replies, the maximum overaging of cached records can be specified in seconds +(defaulting to not serve anything older than one day). Setting the TTL excess time to zero will serve stale cache data regardless how long it has expired. .TP .B \-0, --dns-forward-max= Set the maximum number of concurrent DNS queries. The default value is diff --git a/src/cache.c b/src/cache.c index 119cf9f..b3c38c0 100644 --- a/src/cache.c +++ b/src/cache.c @@ -380,9 +380,17 @@ static int is_outdated_cname_pointer(struct crec *crecp) static int is_expired(time_t now, struct crec *crecp) { - /* Don't dump expired entries if we're using them, cache becomes strictly LRU in that case. - Never use expired DS or DNSKEY entries. */ - if (option_bool(OPT_STALE_CACHE) && !(crecp->flags & (F_DS | F_DNSKEY))) + /* Don't dump expired entries if they are within the accepted timeout range. + The cache becomes approx. LRU. Never use expired DS or DNSKEY entries. + Possible values for daemon->cache_max_expiry: + -1 == serve cached content regardless how long ago it expired + 0 == the option is disabled, expired content isn't served + == serve cached content only if it expire less than seconds + ago (where n is a positive integer) */ + if (daemon->cache_max_expiry != 0 && + (daemon->cache_max_expiry == -1 || + difftime(now, crecp->ttd) < daemon->cache_max_expiry) && + !(crecp->flags & (F_DS | F_DNSKEY))) return 0; if (crecp->flags & F_IMMORTAL) @@ -1762,7 +1770,7 @@ void dump_cache(time_t now) daemon->cachesize, daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED], daemon->metrics[METRIC_DNS_CACHE_INSERTED]); my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"), daemon->metrics[METRIC_DNS_QUERIES_FORWARDED], daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]); - if (option_bool(OPT_STALE_CACHE)) + if (daemon->cache_max_expiry != 0) my_syslog(LOG_INFO, _("queries answered from stale cache %u"), daemon->metrics[METRIC_DNS_STALE_ANSWERED]); #ifdef HAVE_AUTH my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]); diff --git a/src/config.h b/src/config.h index df1d985..1e7b30f 100644 --- a/src/config.h +++ b/src/config.h @@ -60,6 +60,7 @@ #define LOOP_TEST_DOMAIN "test" /* domain for loop testing, "test" is reserved by RFC 2606 and won't therefore clash */ #define LOOP_TEST_TYPE T_TXT #define DEFAULT_FAST_RETRY 1000 /* ms, default delay before fast retry */ +#define STALE_CACHE_EXPIRY 86400 /* 1 day in secs, default maximum expiry time for stale cache data */ /* compile-time options: uncomment below to enable or do eg. make COPTS=-DHAVE_BROKEN_RTC diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 90dc986..aaa6d62 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -280,9 +280,8 @@ struct event_desc { #define OPT_FILTER_AAAA 68 #define OPT_STRIP_ECS 69 #define OPT_STRIP_MAC 70 -#define OPT_STALE_CACHE 71 -#define OPT_NORR 72 -#define OPT_LAST 73 +#define OPT_NORR 71 +#define OPT_LAST 72 #define OPTION_BITS (sizeof(unsigned int)*8) #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) ) @@ -1201,6 +1200,7 @@ extern struct daemon { unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry; u32 metrics[__METRIC_MAX]; int fast_retry_time, fast_retry_timeout; + int cache_max_expiry; #ifdef HAVE_DNSSEC struct ds_config *ds; char *timestamp_file; diff --git a/src/option.c b/src/option.c index 6a63268..8e61a6b 100644 --- a/src/option.c +++ b/src/option.c @@ -373,7 +373,7 @@ static const struct myoption opts[] = { "quiet-tftp", 0, 0, LOPT_QUIET_TFTP }, { "port-limit", 1, 0, LOPT_RANDPORT_LIM }, { "fast-dns-retry", 2, 0, LOPT_FAST_RETRY }, - { "use-stale-cache", 0, 0 , LOPT_STALE_CACHE }, + { "use-stale-cache", 2, 0 , LOPT_STALE_CACHE }, { NULL, 0, 0, 0 } }; @@ -431,7 +431,7 @@ static struct { { 'M', ARG_DUP, "", gettext_noop("Specify BOOTP options to DHCP server."), NULL }, { 'n', OPT_NO_POLL, NULL, gettext_noop("Do NOT poll %s file, reload only on SIGHUP."), RESOLVFILE }, { 'N', OPT_NO_NEG, NULL, gettext_noop("Do NOT cache failed search results."), NULL }, - { LOPT_STALE_CACHE, OPT_STALE_CACHE, NULL, gettext_noop("Use expired cache data for faster reply."), NULL }, + { LOPT_STALE_CACHE, ARG_ONE, "[=]", gettext_noop("Use expired cache data for faster reply."), NULL }, { 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE }, { 'O', ARG_DUP, "", gettext_noop("Specify options to be sent to DHCP clients."), NULL }, { LOPT_FORCE, ARG_DUP, "", gettext_noop("DHCP option sent even if the client does not request it."), NULL}, @@ -5151,6 +5151,24 @@ err: break; } + case LOPT_STALE_CACHE: + { + int max_expiry = STALE_CACHE_EXPIRY; + if (arg) + { + /* Don't accept negative TTLs here, they'd have the counter-intuitive + side-effect of evicting cache records before they expire */ + if (!atoi_check(arg, &max_expiry) || max_expiry < 0) + ret_err(gen_err); + /* Store "serve expired forever" as -1 internally, the option isn't + active for daemon->cache_max_expiry == 0 */ + if (max_expiry == 0) + max_expiry = -1; + } + daemon->cache_max_expiry = max_expiry; + break; + } + #ifdef HAVE_DNSSEC case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */ daemon->timestamp_file = opt_string_alloc(arg); -- cgit v1.2.1