mirror of
https://github.com/coredns/coredns.git
synced 2026-10-16 23:35:43 -04:00
plugin/cache: cache SERVFAIL when the resolver fails without a response (#8629)
* plugin/cache: cache SERVFAIL when the resolver fails without a response The forward plugin returns (SERVFAIL, err) on an upstream timeout without calling WriteMsg, so the response never reaches the cache ResponseWriter and is not stored. Every client retry then hits the upstream again and pays the full forward timeout, while a SERVFAIL sent by the upstream is cached normally. Store a SERVFAIL generated from the failure, bounded by the existing servfail TTL, so identical queries are answered from the cache while it is valid. A servfail TTL of 0 keeps the previous behaviour. Signed-off-by: Ahmad Mehrabi <mehrabi@post.com> * plugin/cache: do not cache SERVFAIL caused by a canceled request forward also returns SERVFAIL together with an error when the request context is canceled. DoH propagates the HTTP request context, so caching that failure would make every subsequent request with the same cache key fail until the servfail TTL expires, even though the upstream is healthy. Only insert the synthetic SERVFAIL when the request context is still live, and add a regression test that cancels the first request and then verifies that a subsequent healthy request reaches the backend. Signed-off-by: Ahmad Mehrabi <mehrabi@post.com> --------- Signed-off-by: Ahmad Mehrabi <mehrabi@post.com>
This commit is contained in:
13
plugin/cache/handler.go
vendored
13
plugin/cache/handler.go
vendored
@@ -45,7 +45,18 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
|
||||
refreshState := authenticatedRefreshState(state)
|
||||
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: refreshState, server: server, do: do, ad: ad, cd: cd,
|
||||
nexcept: c.nexcept, pexcept: c.pexcept, wildcardFunc: wildcardFunc(ctx)}
|
||||
return c.doRefresh(ctx, refreshState, crr)
|
||||
ret, err := c.doRefresh(ctx, refreshState, crr)
|
||||
// The next plugin can fail without writing a response, e.g. forward when
|
||||
// the upstream times out. In that case the failure never reaches
|
||||
// ResponseWriter.WriteMsg, so it is not cached and every client retry
|
||||
// hits the upstream again. Store a SERVFAIL bounded by the servfail TTL
|
||||
// so identical queries are answered from the cache while it is valid.
|
||||
// A canceled request context (e.g. a canceled DoH request) is not an
|
||||
// upstream failure, so it must not poison the shared failure cache.
|
||||
if err != nil && ret == dns.RcodeServerFailure && ctx.Err() == nil && crr.lastResponse == nil && c.failttl > 0 {
|
||||
crr.cacheServerFailure(refreshState.Req)
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
ttl := i.ttl(now)
|
||||
stale := ttl <= 0
|
||||
|
||||
Reference in New Issue
Block a user