feat: background refresh on stale cache hit (RFC 8767 revalidation)

When a cached entry is expired but within the 1-hour stale window,
serve it immediately with TTL=1 AND spawn a background re-resolve.
The next query gets a fresh entry instead of another stale serve.

Without this, stale entries were served repeatedly for up to an hour
with no refresh — effectively ignoring TTL.
This commit is contained in:
Razvan Dimescu
2026-04-12 19:42:56 +03:00
parent 043a7e1ba5
commit 571ce2f013
4 changed files with 64 additions and 11 deletions

View File

@@ -132,18 +132,19 @@ impl DnsCache {
/// Read-only lookup — expired entries are left in place (cleaned up on insert).
pub fn lookup(&self, domain: &str, qtype: QueryType) -> Option<DnsPacket> {
self.lookup_with_status(domain, qtype).map(|(pkt, _)| pkt)
self.lookup_with_status(domain, qtype)
.map(|(pkt, _, _)| pkt)
}
pub fn lookup_with_status(
&self,
domain: &str,
qtype: QueryType,
) -> Option<(DnsPacket, DnssecStatus)> {
let (wire, status, _stale) = self.lookup_wire(domain, qtype, 0)?;
) -> Option<(DnsPacket, DnssecStatus, bool)> {
let (wire, status, stale) = self.lookup_wire(domain, qtype, 0)?;
let mut buf = BytePacketBuffer::from_bytes(&wire);
let pkt = DnsPacket::from_buffer(&mut buf).ok()?;
Some((pkt, status))
Some((pkt, status, stale))
}
pub fn insert(&mut self, domain: &str, qtype: QueryType, packet: &DnsPacket) {