mirror of
https://github.com/coredns/coredns.git
synced 2026-08-20 23:08:28 -04:00
196 lines
6.3 KiB
Go
196 lines
6.3 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
|
"github.com/coredns/coredns/plugin/test"
|
|
"github.com/coredns/coredns/request"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// TestCacheADBitNotPartitioned guards the fix for issue #6642.
|
|
//
|
|
// The cache key is hashed on qname, qtype, qclass, the DO bit and the CD bit, but
|
|
// deliberately NOT on the AD bit (AD must not partition the cache). This means
|
|
// a single cache entry serves both AD-requesting and non-AD-requesting clients,
|
|
// so the AD bit returned to a client must be derived per request from the
|
|
// authentication status of the cached answer, never frozen from whichever query
|
|
// happened to populate the entry first.
|
|
//
|
|
// Historically (reported on v1.11.1) the order mattered: a first query without
|
|
// the AD bit cached the answer with AD cleared, and a later query WITH the AD
|
|
// bit could no longer get AD=1 back. The reverse order worked. This test pins
|
|
// both directions so the asymmetry cannot regress.
|
|
func TestCacheADBitNotPartitioned(t *testing.T) {
|
|
// Forward order: +noad first (populates cache), then +ad on the same entry.
|
|
// The upstream answer is authenticated, so the +ad query must receive AD=1.
|
|
t.Run("noad_then_ad", func(t *testing.T) {
|
|
c := New()
|
|
h := &adBitHandler{}
|
|
c.Next = h
|
|
|
|
// First query: AD not requested, DO not set -> miss, populates cache.
|
|
noad := new(dns.Msg)
|
|
noad.SetQuestion("invent.example.org.", dns.TypeA)
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, noad)
|
|
if rec.Msg.AuthenticatedData {
|
|
t.Errorf("first query did not request AD, expected AuthenticatedData=false, got true")
|
|
}
|
|
if !h.requestedAD[0] {
|
|
t.Errorf("cache refresh should ask upstream for AD when populating an AD-shared entry")
|
|
}
|
|
if c.pcache.Len() != 1 {
|
|
t.Fatalf("expected first query to populate one cache entry, got %d", c.pcache.Len())
|
|
}
|
|
|
|
// Second query: AD requested, DO not set -> hit on the same key.
|
|
// Must reflect the authenticated answer with AD=1.
|
|
ad := new(dns.Msg)
|
|
ad.SetQuestion("invent.example.org.", dns.TypeA)
|
|
ad.AuthenticatedData = true
|
|
rec = dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, ad)
|
|
if h.calls != 1 {
|
|
t.Fatalf("expected second query to be served from cache, backend calls=%d", h.calls)
|
|
}
|
|
if !rec.Msg.AuthenticatedData {
|
|
t.Errorf("second query requested AD on an authenticated cached answer, expected AuthenticatedData=true, got false")
|
|
}
|
|
})
|
|
|
|
// Reverse order: +ad first, then +noad. This direction already worked; we
|
|
// pin it so a fix for the forward case never breaks it.
|
|
t.Run("ad_then_noad", func(t *testing.T) {
|
|
c := New()
|
|
h := &adBitHandler{}
|
|
c.Next = h
|
|
|
|
// First query: AD requested -> AD=1 expected.
|
|
ad := new(dns.Msg)
|
|
ad.SetQuestion("invent.example.org.", dns.TypeA)
|
|
ad.AuthenticatedData = true
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, ad)
|
|
if !rec.Msg.AuthenticatedData {
|
|
t.Errorf("first query requested AD on an authenticated answer, expected AuthenticatedData=true, got false")
|
|
}
|
|
if c.pcache.Len() != 1 {
|
|
t.Fatalf("expected first query to populate one cache entry, got %d", c.pcache.Len())
|
|
}
|
|
|
|
// Second query: AD not requested -> AD must be cleared for this client.
|
|
noad := new(dns.Msg)
|
|
noad.SetQuestion("invent.example.org.", dns.TypeA)
|
|
rec = dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, noad)
|
|
if h.calls != 1 {
|
|
t.Fatalf("expected second query to be served from cache, backend calls=%d", h.calls)
|
|
}
|
|
if rec.Msg.AuthenticatedData {
|
|
t.Errorf("second query did not request AD, expected AuthenticatedData=false, got true")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCacheADBitStaleVerify(t *testing.T) {
|
|
c := New()
|
|
h := &adBitHandler{}
|
|
c.Next = h
|
|
c.staleUpTo = time.Hour
|
|
c.verifyStale = true
|
|
|
|
now := time.Now()
|
|
c.now = func() time.Time { return now }
|
|
|
|
req := new(dns.Msg)
|
|
req.SetQuestion("invent.example.org.", dns.TypeA)
|
|
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, req)
|
|
if c.pcache.Len() != 1 {
|
|
t.Fatalf("expected first query to populate one cache entry, got %d", c.pcache.Len())
|
|
}
|
|
if !h.requestedAD[0] {
|
|
t.Errorf("cache refresh should ask upstream for AD when populating an AD-shared entry")
|
|
}
|
|
|
|
now = now.Add(2 * time.Minute)
|
|
|
|
ad := req.Copy()
|
|
ad.AuthenticatedData = true
|
|
rec = dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, ad)
|
|
if h.calls != 2 {
|
|
t.Fatalf("expected stale verify to call backend, backend calls=%d", h.calls)
|
|
}
|
|
if !h.requestedAD[1] {
|
|
t.Errorf("stale verify should ask upstream for AD when refreshing an AD-shared entry")
|
|
}
|
|
if !rec.Msg.AuthenticatedData {
|
|
t.Errorf("AD-requesting client should receive AD from a refreshed authenticated answer, got false")
|
|
}
|
|
}
|
|
|
|
func TestCacheADBitPrefetchRequestsAD(t *testing.T) {
|
|
requestedAD := make(chan bool, 2)
|
|
c := New()
|
|
h := &adBitHandler{requestedADCh: requestedAD}
|
|
c.Next = h
|
|
c.prefetch = 1
|
|
c.percentage = 100
|
|
|
|
now := time.Now()
|
|
c.now = func() time.Time { return now }
|
|
|
|
req := new(dns.Msg)
|
|
req.SetQuestion("invent.example.org.", dns.TypeA)
|
|
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, req)
|
|
if got := <-requestedAD; !got {
|
|
t.Errorf("cache refresh should ask upstream for AD when populating an AD-shared entry")
|
|
}
|
|
|
|
now = now.Add(time.Second)
|
|
rec = dnstest.NewRecorder(&test.ResponseWriter{})
|
|
c.ServeDNS(context.TODO(), rec, req.Copy())
|
|
|
|
select {
|
|
case got := <-requestedAD:
|
|
if !got {
|
|
t.Errorf("prefetch should ask upstream for AD when refreshing an AD-shared entry")
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("prefetch did not call backend")
|
|
}
|
|
}
|
|
|
|
type adBitHandler struct {
|
|
calls int
|
|
requestedAD []bool
|
|
requestedADCh chan bool
|
|
}
|
|
|
|
func (h *adBitHandler) Name() string { return "adBitHandler" }
|
|
|
|
func (h *adBitHandler) ServeDNS(_ context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
|
h.calls++
|
|
h.requestedAD = append(h.requestedAD, r.AuthenticatedData)
|
|
if h.requestedADCh != nil {
|
|
h.requestedADCh <- r.AuthenticatedData
|
|
}
|
|
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
state := request.Request{W: w, Req: r}
|
|
m.AuthenticatedData = r.AuthenticatedData || state.Do()
|
|
m.Answer = []dns.RR{test.A("invent.example.org. 60 IN A 192.0.2.1")}
|
|
w.WriteMsg(m)
|
|
return dns.RcodeSuccess, nil
|
|
}
|