By checking state.Do() were are checking if the request had DO, but we are _always_ adding Do now - do we need to save the DO from the ORIGINAL request, which must be done in the ResponseWriter. Also skip OPT records in filterDNSSEC as we can't set the TTL on those records, this prevents writing a number to OPT's MBZ. Note none of the tests have changed and still PASS. This is due to the fact that CoreDNSServerAndPorts isn't a full server as we start in main, it lacks the scrubwriter for instance. This is not bad per se, but should be documented in the test code. Signed-off-by: Miek Gieben <miek@miek.nl>
161 lines
4.8 KiB
Go
161 lines
4.8 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/coredns/coredns/plugin"
|
|
"github.com/coredns/coredns/plugin/metrics"
|
|
"github.com/coredns/coredns/request"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// ServeDNS implements the plugin.Handler interface.
|
|
func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
|
state := request.Request{W: w, Req: r}
|
|
do := state.Do()
|
|
|
|
zone := plugin.Zones(c.Zones).Matches(state.Name())
|
|
if zone == "" {
|
|
return plugin.NextOrFailure(c.Name(), c.Next, ctx, w, r)
|
|
}
|
|
|
|
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?
|
|
|
|
ttl := 0
|
|
i := c.getIgnoreTTL(now, state, server)
|
|
if i != nil {
|
|
ttl = i.ttl(now)
|
|
}
|
|
if i == nil {
|
|
if !do {
|
|
setDo(r)
|
|
}
|
|
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do}
|
|
return plugin.NextOrFailure(c.Name(), c.Next, ctx, crr, r)
|
|
}
|
|
if ttl < 0 {
|
|
servedStale.WithLabelValues(server).Inc()
|
|
// Adjust the time to get a 0 TTL in the reply built from a stale item.
|
|
now = now.Add(time.Duration(ttl) * time.Second)
|
|
go func() {
|
|
r := r.Copy()
|
|
if !do {
|
|
setDo(r)
|
|
}
|
|
crr := &ResponseWriter{Cache: c, state: state, server: server, prefetch: true, remoteAddr: w.LocalAddr(), do: do}
|
|
plugin.NextOrFailure(c.Name(), c.Next, ctx, crr, r)
|
|
}()
|
|
}
|
|
resp := i.toMsg(r, now, do)
|
|
w.WriteMsg(resp)
|
|
|
|
if c.shouldPrefetch(i, now) {
|
|
go c.doPrefetch(ctx, state, server, i, now)
|
|
}
|
|
return dns.RcodeSuccess, nil
|
|
}
|
|
|
|
func (c *Cache) doPrefetch(ctx context.Context, state request.Request, server string, i *item, now time.Time) {
|
|
cw := newPrefetchResponseWriter(server, state, c)
|
|
|
|
cachePrefetches.WithLabelValues(server).Inc()
|
|
plugin.NextOrFailure(c.Name(), c.Next, ctx, cw, state.Req)
|
|
|
|
// When prefetching we loose the item i, and with it the frequency
|
|
// that we've gathered sofar. See we copy the frequencies info back
|
|
// into the new item that was stored in the cache.
|
|
if i1 := c.exists(state); i1 != nil {
|
|
i1.Freq.Reset(now, i.Freq.Hits())
|
|
}
|
|
}
|
|
|
|
func (c *Cache) shouldPrefetch(i *item, now time.Time) bool {
|
|
if c.prefetch <= 0 {
|
|
return false
|
|
}
|
|
i.Freq.Update(c.duration, now)
|
|
threshold := int(math.Ceil(float64(c.percentage) / 100 * float64(i.origTTL)))
|
|
return i.Freq.Hits() >= c.prefetch && i.ttl(now) <= threshold
|
|
}
|
|
|
|
// Name implements the Handler interface.
|
|
func (c *Cache) Name() string { return "cache" }
|
|
|
|
func (c *Cache) get(now time.Time, state request.Request, server string) (*item, bool) {
|
|
k := hash(state.Name(), state.QType())
|
|
|
|
if i, ok := c.ncache.Get(k); ok && i.(*item).ttl(now) > 0 {
|
|
cacheHits.WithLabelValues(server, Denial).Inc()
|
|
return i.(*item), true
|
|
}
|
|
|
|
if i, ok := c.pcache.Get(k); ok && i.(*item).ttl(now) > 0 {
|
|
cacheHits.WithLabelValues(server, Success).Inc()
|
|
return i.(*item), true
|
|
}
|
|
cacheMisses.WithLabelValues(server).Inc()
|
|
return nil, false
|
|
}
|
|
|
|
// 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())
|
|
|
|
if i, ok := c.ncache.Get(k); ok {
|
|
ttl := i.(*item).ttl(now)
|
|
if ttl > 0 || (c.staleUpTo > 0 && -ttl < int(c.staleUpTo.Seconds())) {
|
|
cacheHits.WithLabelValues(server, Denial).Inc()
|
|
return i.(*item)
|
|
}
|
|
}
|
|
if i, ok := c.pcache.Get(k); ok {
|
|
ttl := i.(*item).ttl(now)
|
|
if ttl > 0 || (c.staleUpTo > 0 && -ttl < int(c.staleUpTo.Seconds())) {
|
|
cacheHits.WithLabelValues(server, Success).Inc()
|
|
return i.(*item)
|
|
}
|
|
}
|
|
cacheMisses.WithLabelValues(server).Inc()
|
|
return nil
|
|
}
|
|
|
|
func (c *Cache) exists(state request.Request) *item {
|
|
k := hash(state.Name(), state.QType())
|
|
if i, ok := c.ncache.Get(k); ok {
|
|
return i.(*item)
|
|
}
|
|
if i, ok := c.pcache.Get(k); ok {
|
|
return i.(*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
|