






















Hi Simon,Pi-hole enables use-stale-cache by default to reduce DNS latency for clients - they get an instant stale answer while dnsmasq refreshes in the background.
When this feature is combined with DNSSEC validation and a caching upstream resolver like unbound (Pi-hole + local unbound is a very popular combination), use-stale-cache creates a problematic interaction with RRSIG timestamp checking:
1. Unbound caches a response with its RRSIGs at time T and validates them (RRSIGs are valid at that point) 2. Later, dnsmasq queries unbound; unbound returns the cached response whose RRSIG has since expired 3. validate_rrset() finds sig_expiration < curtime, skips all signatures, and returns BOGUS with EDE "signature expired" (7) 4. Every concurrent query for that domain fails until unbound refreshes its cache
Popular resolvers like unbound enable serve-expired with no time limit by default, so RRSIGs can be days past their expiration timestamp by the time dnsmasq sees them. In various user submitted logs (no telemetry, they have to opt-in sending them so we help them solving issues easier) we often saw bursts of dozens of BOGUS results across many DNSSEC-signed domains (slack.com, startpage.com, paypal.com, computer-bild.de, ...) with the same EDE code 7, all clearing up after a few seconds once the upstream cache refreshes. During the failure window the domains are completely unreachable for clients.
The core tension is that use-stale-cache deliberately trades freshness for availability on the data plane (is_expired() in cache.c serves stale records past their TTL), but the RRSIG timestamp check in validate_rrset() enforces strict freshness on the validation plane. When a caching upstream sits between dnsmasq and the authoritative servers, these two policies conflict - enforcing strict RRSIG freshness on top of deliberately stale data is contradictory.
I have been able to confirm this issue myself easily with such a local unbound setup (serve-expired with no time limit). The key is that you have to visit a website that is DNSSEC protected but which you don't visit usually. Then visit it once (saves it in unbound's cache), do other things for two days, then visit it again. You will then observe what I described here. My own observations showed RRSIGs expiration ages range from ~25,000s (~7h) to ~40,000s (~11h). I first considered us using our stale timeout setting in dnsmasq but reality proved that this was insufficient as the external resolvers may give us RRSIGs which are much older.
The attached patch resolves this intermittent error by skipping the RRSIG expiration timestamp check when use-stale-cache is enabled (cache_max_expiry != 0). The actual cryptographic signature check still runs in full - only the timestamp gate is relaxed. When stale caching is disabled (the default), behaviour is completely unchanged. Hence, this patch should make the dnsmasq DNSSEC implementation only more consistent, but not weaker.
Cheers, Dominik
From 26c83adc4815e46d64b982105d74968dd9796d2d Mon Sep 17 00:00:00 2001 From: Dominik <[email protected]> Date: Mon, 6 Apr 2026 11:07:18 +0200 Subject: [PATCH] Allow expired RRSIGs when stale caching is enabled. When dnsmasq forwards to a caching validating resolver (e.g. unbound with serve-expired), the upstream may return responses from its cache whose RRSIGs have expired since being cached - the upstream validated them when they were fresh, but by the time dnsmasq re-validates, the signatures have passed their expiration timestamp. This causes intermittent DNSSEC BOGUS failures (EDE: "signature expired") that clear up once the upstream refreshes its cache. Pi-hole enables use-stale-cache by default to reduce DNS latency for clients - they get an instant stale answer while dnsmasq refreshes in the background. Combined with DNSSEC validation and a caching upstream like unbound, this creates a window where every query for a DNSSEC- signed domain fails BOGUS until the upstream cache refreshes. Popular resolvers like unbound enable serve-expired with no time limit by default, so RRSIGs can be days past expiration by the time dnsmasq sees them. When use-stale-cache is enabled, skip the RRSIG expiration timestamp check entirely. The signature still receives full cryptographic verification - only the timestamp gate is relaxed. This is consistent with the existing stale cache design: is_expired() in cache.c already serves stale data records past their TTL, so enforcing strict RRSIG freshness on top of deliberately stale data is contradictory. When stale caching is disabled (cache_max_expiry == 0), behaviour is completely unchanged. Signed-off-by: Dominik <[email protected]> --- src/dnssec.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index b6e5502..c575713 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -514,7 +514,17 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in failflags &= ~DNSSEC_FAIL_NYV; if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT) - continue; + { + /* When stale caching is enabled, skip the RRSIG expiration check. + A caching forwarder (e.g. unbound with serve-expired) may return + responses whose RRSIGs expired long ago but were valid when + originally cached upstream. The signature still gets full + cryptographic verification - only the timestamp gate is relaxed. */ + if (daemon->cache_max_expiry != 0) + failflags &= ~DNSSEC_FAIL_EXP; + else + continue; + } else failflags &= ~DNSSEC_FAIL_EXP; } -- 2.43.0
_______________________________________________ Dnsmasq-discuss mailing list [email protected] https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。