plugin/cache: stop setting AA on answers served from cache (#8419)

* plugin/cache: stop setting AA on answers served from cache

toMsg hardcoded m1.Authoritative = true, so a reply rebuilt from a cache entry claimed authority the answer that populated it never had.

The hardcoding was a workaround for legacy stub resolvers that dropped non-authoritative answers, but it only ever ran on the cache hit path: the same query still returned AA=0 on every miss and after every TTL expiry, so those clients were never actually protected.

Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>

* plugin/cache: pin the AA=1 side of the cache round trip

Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>

* plugin/cache: assert AA=0 on verified stale refresh and prove the cache hit

Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>

* plugin/cache: count backend calls in TestCachePreservesAA

Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>

---------

Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>
This commit is contained in:
Baltasar Blanco
2026-08-23 21:54:41 -03:00
committed by GitHub
parent 4b26cced32
commit 234f5fd378
3 changed files with 95 additions and 15 deletions

View File

@@ -558,9 +558,8 @@ func TestCacheInsertion(t *testing.T) {
// TODO: If we incorporate these individual checks into the
// test.Header function, we can eliminate them from here.
// Cache entries are always Authoritative.
if resp.Authoritative != true {
t.Error("Expected Authoritative Answer bit to be true, but was false")
if resp.Authoritative != tc.out.Authoritative {
t.Errorf("Expected Authoritative Answer bit to be %t, but got %t", tc.out.Authoritative, resp.Authoritative)
}
if resp.AuthenticatedData != tc.out.AuthenticatedData {
t.Errorf("Expected Authenticated Data bit to be %t, but got %t", tc.out.AuthenticatedData, resp.AuthenticatedData)
@@ -980,8 +979,8 @@ func TestServeFromStaleCacheFetchVerifyTimeoutFastUpstream(t *testing.T) {
if got := rec.Msg.Answer[0].Header().Ttl; got != 200 {
t.Errorf("expected fresh TTL=200, got %d", got)
}
if !rec.Msg.Authoritative {
t.Error("expected cached fresh response to preserve authoritative cache reply shaping")
if rec.Msg.Authoritative {
t.Error("expected AA=0: the freshly verified answer came from a non-authoritative backend")
}
}
@@ -1116,6 +1115,22 @@ func BackendHandler() plugin.Handler {
})
}
func authoritativeBackend(calls *int) plugin.Handler {
return plugin.HandlerFunc(func(_ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
*calls++
m := new(dns.Msg)
m.SetReply(r)
m.Response = true
m.Authoritative = true
owner := m.Question[0].Name
m.Answer = []dns.RR{test.A(owner + " 303 IN A 127.0.0.53")}
w.WriteMsg(m)
return dns.RcodeSuccess, nil
})
}
func nxDomainBackend(ttl int) plugin.Handler {
return plugin.HandlerFunc(func(_ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
m := new(dns.Msg)