mirror of
https://github.com/coredns/coredns.git
synced 2026-08-27 07:07:05 -04:00
* 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>
90 lines
3.0 KiB
Go
90 lines
3.0 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
|
"github.com/coredns/coredns/plugin/test"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
func TestNewItemPreservesMonotonicClock(t *testing.T) {
|
|
now := time.Now()
|
|
if reflect.DeepEqual(now, now.Round(0)) {
|
|
t.Fatal("time.Now did not include a monotonic clock reading")
|
|
}
|
|
i := newItem(new(dns.Msg), now, time.Minute)
|
|
|
|
// DeepEqual compares the complete time representation, including its
|
|
// monotonic clock reading. Time.Equal intentionally ignores that detail.
|
|
if !reflect.DeepEqual(i.stored, now) {
|
|
t.Fatalf("stored time = %v; want original time %v", i.stored, now)
|
|
}
|
|
}
|
|
|
|
// TestCacheDoesNotSynthesizeAA guards issue #6185.
|
|
//
|
|
// A cached answer that came from a non-authoritative upstream must not gain the
|
|
// AA bit when it is served from the cache. Historically toMsg set AA
|
|
// unconditionally, so the same query answered AA=0 on a miss and AA=1 on a hit.
|
|
// Both directions are pinned here.
|
|
func TestCacheDoesNotSynthesizeAA(t *testing.T) {
|
|
c := New()
|
|
c.Next = BackendHandler() // replies with Authoritative unset
|
|
|
|
req := new(dns.Msg)
|
|
req.SetQuestion("example.org.", dns.TypeA)
|
|
|
|
// Miss: the answer is passed through, AA must stay 0.
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, req)
|
|
if rec.Msg.Authoritative {
|
|
t.Fatalf("cache miss: expected AA=0 from a non-authoritative backend, got AA=1")
|
|
}
|
|
|
|
// Hit: the same answer is rebuilt from the cached item, AA must still be 0.
|
|
rec = dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, req)
|
|
if rec.Msg.Authoritative {
|
|
t.Errorf("cache hit: expected AA=0, the cached answer was not authoritative, got AA=1")
|
|
}
|
|
}
|
|
|
|
// TestCachePreservesAA is the AA=1 counterpart of
|
|
// TestCacheDoesNotSynthesizeAA: an answer that *was* authoritative must still
|
|
// be authoritative when it is replayed from cache. Without it the AA=0 test
|
|
// alone would also pass if toMsg hardcoded AA to 0, silently breaking answers
|
|
// from file, hosts and secondary.
|
|
func TestCachePreservesAA(t *testing.T) {
|
|
c := New()
|
|
calls := 0
|
|
c.Next = authoritativeBackend(&calls)
|
|
|
|
req := new(dns.Msg)
|
|
req.SetQuestion("example.org.", dns.TypeA)
|
|
|
|
// Miss: the authoritative answer is passed through.
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, req)
|
|
if !rec.Msg.Authoritative {
|
|
t.Fatalf("cache miss: expected AA=1 from an authoritative backend, got AA=0")
|
|
}
|
|
|
|
// Hit: rebuilt from the cached item, AA must survive the round trip.
|
|
rec = dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, req)
|
|
if !rec.Msg.Authoritative {
|
|
t.Errorf("cache hit: expected AA=1, the cached answer was authoritative, got AA=0")
|
|
}
|
|
|
|
// A second backend call would mean the "hit" was really a second miss,
|
|
// which satisfies the AA=1 assertion without ever touching the cache.
|
|
if calls != 1 {
|
|
t.Errorf("expected exactly one backend call after miss and hit, got %d", calls)
|
|
}
|
|
}
|