aboutsummaryrefslogtreecommitdiff
path: root/plugin
diff options
context:
space:
mode:
authorGravatar Grant Spence <gcs278@vt.edu> 2022-10-21 09:29:04 -0600
committerGravatar GitHub <noreply@github.com> 2022-10-21 11:29:04 -0400
commit403e979934254713789ec9eef7a5127758104c8e (patch)
tree34903db6ec3c03055e28c0cccdfec758c929f839 /plugin
parentc6fa91b36704795997ed3953221317990c10cc30 (diff)
downloadcoredns-403e979934254713789ec9eef7a5127758104c8e.tar.gz
coredns-403e979934254713789ec9eef7a5127758104c8e.tar.zst
coredns-403e979934254713789ec9eef7a5127758104c8e.zip
plugin/cache: cache now uses source query DNSSEC option for upstream refresh (#5671)
Signed-off-by: Grant Spence <gspence@redhat.com> Signed-off-by: Grant Spence <gspence@redhat.com>
Diffstat (limited to 'plugin')
-rw-r--r--plugin/cache/README.md3
-rw-r--r--plugin/cache/cache.go26
-rw-r--r--plugin/cache/cache_test.go22
-rw-r--r--plugin/cache/dnssec.go28
-rw-r--r--plugin/cache/dnssec_test.go33
-rw-r--r--plugin/cache/handler.go36
-rw-r--r--plugin/cache/item.go6
7 files changed, 69 insertions, 85 deletions
diff --git a/plugin/cache/README.md b/plugin/cache/README.md
index 562f5bd9a..6fc20ae2c 100644
--- a/plugin/cache/README.md
+++ b/plugin/cache/README.md
@@ -10,8 +10,7 @@ With *cache* enabled, all records except zone transfers and metadata records wil
3600s. Caching is mostly useful in a scenario when fetching data from the backend (upstream,
database, etc.) is expensive.
-*Cache* will change the query to enable DNSSEC (DNSSEC OK; DO) if it passes through the plugin. If
-the client didn't request any DNSSEC (records), these are filtered out when replying.
+*Cache* will pass DNSSEC (DNSSEC OK; DO) options through the plugin for upstream queries.
This plugin can only be used once per Server Block.
diff --git a/plugin/cache/cache.go b/plugin/cache/cache.go
index b4767937d..54f2587fa 100644
--- a/plugin/cache/cache.go
+++ b/plugin/cache/cache.go
@@ -76,7 +76,7 @@ func New() *Cache {
// key returns key under which we store the item, -1 will be returned if we don't store the message.
// Currently we do not cache Truncated, errors zone transfers or dynamic update messages.
// qname holds the already lowercased qname.
-func key(qname string, m *dns.Msg, t response.Type) (bool, uint64) {
+func key(qname string, m *dns.Msg, t response.Type, do bool) (bool, uint64) {
// We don't store truncated responses.
if m.Truncated {
return false, 0
@@ -86,11 +86,21 @@ func key(qname string, m *dns.Msg, t response.Type) (bool, uint64) {
return false, 0
}
- return true, hash(qname, m.Question[0].Qtype)
+ return true, hash(qname, m.Question[0].Qtype, do)
}
-func hash(qname string, qtype uint16) uint64 {
+var one = []byte("1")
+var zero = []byte("0")
+
+func hash(qname string, qtype uint16, do bool) uint64 {
h := fnv.New64()
+
+ if do {
+ h.Write(one)
+ } else {
+ h.Write(zero)
+ }
+
h.Write([]byte{byte(qtype >> 8)})
h.Write([]byte{byte(qtype)})
h.Write([]byte(qname))
@@ -145,6 +155,7 @@ func newPrefetchResponseWriter(server string, state request.Request, c *Cache) *
Cache: c,
state: state,
server: server,
+ do: state.Do(),
prefetch: true,
remoteAddr: addr,
}
@@ -163,7 +174,7 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
mt, _ := response.Typify(res, w.now().UTC())
// key returns empty string for anything we don't want to cache.
- hasKey, key := key(w.state.Name(), res, mt)
+ hasKey, key := key(w.state.Name(), res, mt, w.do)
msgTTL := dnsutil.MinimalTTL(res, mt)
var duration time.Duration
@@ -191,11 +202,10 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
}
// Apply capped TTL to this reply to avoid jarring TTL experience 1799 -> 8 (e.g.)
- // We also may need to filter out DNSSEC records, see toMsg() for similar code.
ttl := uint32(duration.Seconds())
- res.Answer = filterRRSlice(res.Answer, ttl, w.do, false)
- res.Ns = filterRRSlice(res.Ns, ttl, w.do, false)
- res.Extra = filterRRSlice(res.Extra, ttl, w.do, false)
+ res.Answer = filterRRSlice(res.Answer, ttl, false)
+ res.Ns = filterRRSlice(res.Ns, ttl, false)
+ res.Extra = filterRRSlice(res.Extra, ttl, false)
if !w.do && !w.ad {
// unset AD bit if requester is not OK with DNSSEC
diff --git a/plugin/cache/cache_test.go b/plugin/cache/cache_test.go
index 0328c8411..861e9b8fd 100644
--- a/plugin/cache/cache_test.go
+++ b/plugin/cache/cache_test.go
@@ -27,6 +27,7 @@ type cacheTestCase struct {
var cacheTestCases = []cacheTestCase{
{
+ // Test with Authenticated Data bit
RecursionAvailable: true, AuthenticatedData: true,
Case: test.Case{
Qname: "miek.nl.", Qtype: dns.TypeMX,
@@ -45,6 +46,7 @@ var cacheTestCases = []cacheTestCase{
shouldCache: true,
},
{
+ // Test case sensitivity
RecursionAvailable: true, AuthenticatedData: true,
Case: test.Case{
Qname: "miek.nl.", Qtype: dns.TypeMX,
@@ -58,13 +60,12 @@ var cacheTestCases = []cacheTestCase{
Answer: []dns.RR{
test.MX("miek.nl. 3601 IN MX 1 aspmx.l.google.com."),
test.MX("miek.nl. 3601 IN MX 10 aspmx2.googlemail.com."),
- // RRSIG must be here, because we are always doing DNSSEC lookups, and miek.nl MX is tested later in this list as well.
- test.RRSIG("miek.nl. 3600 IN RRSIG MX 8 2 1800 20160521031301 20160421031301 12051 miek.nl. lAaEzB5teQLLKyDenatmyhca7blLRg9DoGNrhe3NReBZN5C5/pMQk8Jc u25hv2fW23/SLm5IC2zaDpp2Fzgm6Jf7e90/yLcwQPuE7JjS55WMF+HE LEh7Z6AEb+Iq4BWmNhUz6gPxD4d9eRMs7EAzk13o1NYi5/JhfL6IlaYy qkc="),
},
},
shouldCache: true,
},
{
+ // Test truncated responses don't get cached
Truncated: true,
Case: test.Case{
Qname: "miek.nl.", Qtype: dns.TypeMX,
@@ -74,6 +75,7 @@ var cacheTestCases = []cacheTestCase{
shouldCache: false,
},
{
+ // Test dns.RcodeNameError cache
RecursionAvailable: true,
Case: test.Case{
Rcode: dns.RcodeNameError,
@@ -92,6 +94,7 @@ var cacheTestCases = []cacheTestCase{
shouldCache: true,
},
{
+ // Test dns.RcodeServerFailure cache
RecursionAvailable: true,
Case: test.Case{
Rcode: dns.RcodeServerFailure,
@@ -106,6 +109,7 @@ var cacheTestCases = []cacheTestCase{
shouldCache: true,
},
{
+ // Test dns.RcodeNotImplemented cache
RecursionAvailable: true,
Case: test.Case{
Rcode: dns.RcodeNotImplemented,
@@ -120,6 +124,8 @@ var cacheTestCases = []cacheTestCase{
shouldCache: true,
},
{
+ // Test cache has separate items for query DO Bit value
+ // This doesn't cache because this RRSIG is expired and previous tests used DO Bit false
RecursionAvailable: true,
Case: test.Case{
Qname: "miek.nl.", Qtype: dns.TypeMX,
@@ -139,9 +145,10 @@ var cacheTestCases = []cacheTestCase{
test.RRSIG("miek.nl. 1800 IN RRSIG MX 8 2 1800 20160521031301 20160421031301 12051 miek.nl. lAaEzB5teQLLKyDenatmyhca7blLRg9DoGNrhe3NReBZN5C5/pMQk8Jc u25hv2fW23/SLm5IC2zaDpp2Fzgm6Jf7e90/yLcwQPuE7JjS55WMF+HE LEh7Z6AEb+Iq4BWmNhUz6gPxD4d9eRMs7EAzk13o1NYi5/JhfL6IlaYy qkc="),
},
},
- shouldCache: true,
+ shouldCache: false,
},
{
+ // Test DO Bit with a RRSIG that is not expired
RecursionAvailable: true,
Case: test.Case{
Qname: "example.org.", Qtype: dns.TypeMX,
@@ -164,6 +171,7 @@ var cacheTestCases = []cacheTestCase{
shouldCache: true,
},
{
+ // Test negative zone exception
in: test.Case{
Rcode: dns.RcodeNameError,
Qname: "neg-disabled.example.org.", Qtype: dns.TypeA,
@@ -175,6 +183,7 @@ var cacheTestCases = []cacheTestCase{
shouldCache: false,
},
{
+ // Test positive zone exception
in: test.Case{
Rcode: dns.RcodeSuccess,
Qname: "pos-disabled.example.org.", Qtype: dns.TypeA,
@@ -186,6 +195,7 @@ var cacheTestCases = []cacheTestCase{
shouldCache: false,
},
{
+ // Test positive zone exception with negative answer
in: test.Case{
Rcode: dns.RcodeNameError,
Qname: "pos-disabled.example.org.", Qtype: dns.TypeA,
@@ -203,6 +213,7 @@ var cacheTestCases = []cacheTestCase{
shouldCache: true,
},
{
+ // Test negative zone exception with positive answer
in: test.Case{
Rcode: dns.RcodeSuccess,
Qname: "neg-disabled.example.org.", Qtype: dns.TypeA,
@@ -258,7 +269,7 @@ func TestCache(t *testing.T) {
state := request.Request{W: &test.ResponseWriter{}, Req: m}
mt, _ := response.Typify(m, utc)
- valid, k := key(state.Name(), m, mt)
+ valid, k := key(state.Name(), m, mt, state.Do())
if valid {
crr.set(m, k, mt, c.pttl)
@@ -647,6 +658,7 @@ func TestCacheWildcardMetadata(t *testing.T) {
req := new(dns.Msg)
req.SetQuestion(qname, dns.TypeA)
+ state := request.Request{W: &test.ResponseWriter{}, Req: req}
// 1. Test writing wildcard metadata retrieved from backend to the cache
@@ -656,7 +668,7 @@ func TestCacheWildcardMetadata(t *testing.T) {
if c.pcache.Len() != 1 {
t.Errorf("Msg should have been cached")
}
- _, k := key(qname, w.Msg, response.NoError)
+ _, k := key(qname, w.Msg, response.NoError, state.Do())
i, _ := c.pcache.Get(k)
if i.(*item).wildcard != wildcard {
t.Errorf("expected wildcard reponse to enter cache with cache item's wildcard = %q, got %q", wildcard, i.(*item).wildcard)
diff --git a/plugin/cache/dnssec.go b/plugin/cache/dnssec.go
index cf908037e..ec5ff41cb 100644
--- a/plugin/cache/dnssec.go
+++ b/plugin/cache/dnssec.go
@@ -2,35 +2,13 @@ package cache
import "github.com/miekg/dns"
-// isDNSSEC returns true if r is a DNSSEC record. NSEC,NSEC3,DS and RRSIG/SIG
-// are DNSSEC records. DNSKEYs is not in this list on the assumption that the
-// client explicitly asked for it.
-func isDNSSEC(r dns.RR) bool {
- switch r.Header().Rrtype {
- case dns.TypeNSEC:
- return true
- case dns.TypeNSEC3:
- return true
- case dns.TypeDS:
- return true
- case dns.TypeRRSIG:
- return true
- case dns.TypeSIG:
- return true
- }
- return false
-}
-
-// filterRRSlice filters rrs and removes DNSSEC RRs when do is false. In the returned slice
-// the TTLs are set to ttl. If dup is true the RRs in rrs are _copied_ into the slice that is
+// filterRRSlice filters out OPT RRs, and sets all RR TTLs to ttl.
+// If dup is true the RRs in rrs are _copied_ into the slice that is
// returned.
-func filterRRSlice(rrs []dns.RR, ttl uint32, do, dup bool) []dns.RR {
+func filterRRSlice(rrs []dns.RR, ttl uint32, dup bool) []dns.RR {
j := 0
rs := make([]dns.RR, len(rrs))
for _, r := range rrs {
- if !do && isDNSSEC(r) {
- continue
- }
if r.Header().Rrtype == dns.TypeOPT {
continue
}
diff --git a/plugin/cache/dnssec_test.go b/plugin/cache/dnssec_test.go
index a746387bf..b73d52cf7 100644
--- a/plugin/cache/dnssec_test.go
+++ b/plugin/cache/dnssec_test.go
@@ -7,6 +7,7 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"
+ "github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@@ -67,19 +68,27 @@ func dnssecHandler() plugin.Handler {
return plugin.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
m := new(dns.Msg)
m.SetQuestion("example.org.", dns.TypeA)
+ state := request.Request{W: &test.ResponseWriter{}, Req: r}
m.AuthenticatedData = true
- m.Answer = make([]dns.RR, 4)
- m.Answer[0] = test.CNAME("invent.example.org. 1781 IN CNAME leptone.example.org.")
- m.Answer[1] = test.RRSIG("invent.example.org. 1781 IN RRSIG CNAME 8 3 1800 20201012085750 20200912082613 57411 example.org. ijSv5FmsNjFviBcOFwQgqjt073lttxTTNqkno6oMa3DD3kC+")
- m.Answer[2] = test.A("leptone.example.org. 1781 IN A 195.201.182.103")
- m.Answer[3] = test.RRSIG("leptone.example.org. 1781 IN RRSIG A 8 3 1800 20201012093630 20200912083827 57411 example.org. eLuSOkLAzm/WIOpaZD3/4TfvKP1HAFzjkis9LIJSRVpQt307dm9WY9")
+ // If query has the DO bit, then send DNSSEC responses (RRSIGs)
+ if state.Do() {
+ m.Answer = make([]dns.RR, 4)
+ m.Answer[0] = test.CNAME("invent.example.org. 1781 IN CNAME leptone.example.org.")
+ m.Answer[1] = test.RRSIG("invent.example.org. 1781 IN RRSIG CNAME 8 3 1800 20201012085750 20200912082613 57411 example.org. ijSv5FmsNjFviBcOFwQgqjt073lttxTTNqkno6oMa3DD3kC+")
+ m.Answer[2] = test.A("leptone.example.org. 1781 IN A 195.201.182.103")
+ m.Answer[3] = test.RRSIG("leptone.example.org. 1781 IN RRSIG A 8 3 1800 20201012093630 20200912083827 57411 example.org. eLuSOkLAzm/WIOpaZD3/4TfvKP1HAFzjkis9LIJSRVpQt307dm9WY9")
+ } else {
+ m.Answer = make([]dns.RR, 2)
+ m.Answer[0] = test.CNAME("invent.example.org. 1781 IN CNAME leptone.example.org.")
+ m.Answer[1] = test.A("leptone.example.org. 1781 IN A 195.201.182.103")
+ }
w.WriteMsg(m)
return dns.RcodeSuccess, nil
})
}
-func TestFliterRRSlice(t *testing.T) {
+func TestFilterRRSlice(t *testing.T) {
rrs := []dns.RR{
test.CNAME("invent.example.org. 1781 IN CNAME leptone.example.org."),
test.RRSIG("invent.example.org. 1781 IN RRSIG CNAME 8 3 1800 20201012085750 20200912082613 57411 example.org. ijSv5FmsNjFviBcOFwQgqjt073lttxTTNqkno6oMa3DD3kC+"),
@@ -87,7 +96,7 @@ func TestFliterRRSlice(t *testing.T) {
test.RRSIG("leptone.example.org. 1781 IN RRSIG A 8 3 1800 20201012093630 20200912083827 57411 example.org. eLuSOkLAzm/WIOpaZD3/4TfvKP1HAFzjkis9LIJSRVpQt307dm9WY9"),
}
- filter1 := filterRRSlice(rrs, 0, true, false)
+ filter1 := filterRRSlice(rrs, 0, false)
if len(filter1) != 4 {
t.Errorf("Expected 4 RRs after filtering, got %d", len(filter1))
}
@@ -101,9 +110,9 @@ func TestFliterRRSlice(t *testing.T) {
t.Errorf("Expected 2 RRSIGs after filtering, got %d", rrsig)
}
- filter2 := filterRRSlice(rrs, 0, false, false)
- if len(filter2) != 2 {
- t.Errorf("Expected 2 RRs after filtering, got %d", len(filter2))
+ filter2 := filterRRSlice(rrs, 0, false)
+ if len(filter2) != 4 {
+ t.Errorf("Expected 4 RRs after filtering, got %d", len(filter2))
}
rrsig = 0
for _, f := range filter2 {
@@ -111,7 +120,7 @@ func TestFliterRRSlice(t *testing.T) {
rrsig++
}
}
- if rrsig != 0 {
- t.Errorf("Expected 0 RRSIGs after filtering, got %d", rrsig)
+ if rrsig != 2 {
+ t.Errorf("Expected 2 RRSIGs after filtering, got %d", rrsig)
}
}
diff --git a/plugin/cache/handler.go b/plugin/cache/handler.go
index ec2135e8c..e60637af8 100644
--- a/plugin/cache/handler.go
+++ b/plugin/cache/handler.go
@@ -28,12 +28,10 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
now := c.now().UTC()
server := metrics.WithServer(ctx)
- // On cache miss, if the request has the OPT record and the DO bit set we leave the message as-is. If there isn't a DO bit
- // set we will modify the request to _add_ one. This means we will always do DNSSEC lookups on cache misses.
- // When writing to cache, any DNSSEC RRs in the response are written to cache with the response.
- // When sending a response to a non-DNSSEC client, we remove DNSSEC RRs from the response. We use a 2048 buffer size, which is
- // less than 4096 (and older default) and more than 1024 which may be too small. We might need to tweaks this
- // value to be smaller still to prevent UDP fragmentation?
+ // On cache refresh, we will just use the DO bit from the incoming query for the refresh since we key our cache
+ // with the query DO bit. That means two separate cache items for the query DO bit true or false. In the situation
+ // in which upstream doesn't support DNSSEC, the two cache items will effectively be the same. Regardless, any
+ // DNSSEC RRs in the response are written to cache with the response.
ttl := 0
i := c.getIgnoreTTL(now, state, server)
@@ -101,9 +99,6 @@ func (c *Cache) doPrefetch(ctx context.Context, state request.Request, cw *Respo
}
func (c *Cache) doRefresh(ctx context.Context, state request.Request, cw dns.ResponseWriter) (int, error) {
- if !state.Do() {
- setDo(state.Req)
- }
return plugin.NextOrFailure(c.Name(), c.Next, ctx, cw, state.Req)
}
@@ -121,7 +116,7 @@ func (c *Cache) Name() string { return "cache" }
// getIgnoreTTL unconditionally returns an item if it exists in the cache.
func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string) *item {
- k := hash(state.Name(), state.QType())
+ k := hash(state.Name(), state.QType(), state.Do())
cacheRequests.WithLabelValues(server, c.zonesMetricLabel, c.viewMetricLabel).Inc()
if i, ok := c.ncache.Get(k); ok {
@@ -145,7 +140,7 @@ func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string
}
func (c *Cache) exists(state request.Request) *item {
- k := hash(state.Name(), state.QType())
+ k := hash(state.Name(), state.QType(), state.Do())
if i, ok := c.ncache.Get(k); ok {
return i.(*item)
}
@@ -154,22 +149,3 @@ func (c *Cache) exists(state request.Request) *item {
}
return nil
}
-
-// setDo sets the DO bit and UDP buffer size in the message m.
-func setDo(m *dns.Msg) {
- o := m.IsEdns0()
- if o != nil {
- o.SetDo()
- o.SetUDPSize(defaultUDPBufSize)
- return
- }
-
- o = &dns.OPT{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT}}
- o.SetDo()
- o.SetUDPSize(defaultUDPBufSize)
- m.Extra = append(m.Extra, o)
-}
-
-// defaultUDPBufsize is the bufsize the cache plugin uses on outgoing requests that don't
-// have an OPT RR.
-const defaultUDPBufSize = 2048
diff --git a/plugin/cache/item.go b/plugin/cache/item.go
index 6b51a5bad..c5aeccdcc 100644
--- a/plugin/cache/item.go
+++ b/plugin/cache/item.go
@@ -87,9 +87,9 @@ func (i *item) toMsg(m *dns.Msg, now time.Time, do bool, ad bool) *dns.Msg {
m1.Extra = make([]dns.RR, len(i.Extra))
ttl := uint32(i.ttl(now))
- m1.Answer = filterRRSlice(i.Answer, ttl, do, true)
- m1.Ns = filterRRSlice(i.Ns, ttl, do, true)
- m1.Extra = filterRRSlice(i.Extra, ttl, do, true)
+ m1.Answer = filterRRSlice(i.Answer, ttl, true)
+ m1.Ns = filterRRSlice(i.Ns, ttl, true)
+ m1.Extra = filterRRSlice(i.Extra, ttl, true)
return m1
}