137 lines
3.2 KiB
Go
137 lines
3.2 KiB
Go
// Package cache implements a cache.
|
|
package cache
|
|
|
|
import (
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/miekg/coredns/middleware"
|
|
"github.com/miekg/coredns/middleware/pkg/response"
|
|
|
|
"github.com/hashicorp/golang-lru"
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// Cache is middleware that looks up responses in a cache and caches replies.
|
|
// It has a success and a denial of existence cache.
|
|
type Cache struct {
|
|
Next middleware.Handler
|
|
Zones []string
|
|
|
|
ncache *lru.Cache
|
|
ncap int
|
|
nttl time.Duration
|
|
|
|
pcache *lru.Cache
|
|
pcap int
|
|
pttl time.Duration
|
|
}
|
|
|
|
// Return key under which we store the item. The empty string is returned
|
|
// when we don't want to cache the message. Currently we do not cache Truncated, errors
|
|
// zone transfers or dynamic update messages.
|
|
func key(m *dns.Msg, t response.Type, do bool) string {
|
|
// We don't store truncated responses.
|
|
if m.Truncated {
|
|
return ""
|
|
}
|
|
// Nor errors or Meta or Update
|
|
if t == response.OtherError || t == response.Meta || t == response.Update {
|
|
return ""
|
|
}
|
|
|
|
qtype := m.Question[0].Qtype
|
|
qname := strings.ToLower(m.Question[0].Name)
|
|
return rawKey(qname, qtype, do)
|
|
}
|
|
|
|
func rawKey(qname string, qtype uint16, do bool) string {
|
|
if do {
|
|
return "1" + qname + "." + strconv.Itoa(int(qtype))
|
|
}
|
|
return "0" + qname + "." + strconv.Itoa(int(qtype))
|
|
}
|
|
|
|
// ResponseWriter is a response writer that caches the reply message.
|
|
type ResponseWriter struct {
|
|
dns.ResponseWriter
|
|
*Cache
|
|
}
|
|
|
|
// WriteMsg implements the dns.ResponseWriter interface.
|
|
func (c *ResponseWriter) WriteMsg(res *dns.Msg) error {
|
|
do := false
|
|
mt, opt := response.Typify(res)
|
|
if opt != nil {
|
|
do = opt.Do()
|
|
}
|
|
|
|
// key returns empty string for anything we don't want to cache.
|
|
key := key(res, mt, do)
|
|
|
|
duration := c.pttl
|
|
if mt == response.NameError || mt == response.NoData {
|
|
duration = c.nttl
|
|
}
|
|
|
|
msgTTL := minMsgTTL(res, mt)
|
|
if msgTTL < duration {
|
|
duration = msgTTL
|
|
}
|
|
|
|
if key != "" {
|
|
c.set(res, key, mt, duration)
|
|
|
|
cacheSize.WithLabelValues(Success).Set(float64(c.pcache.Len()))
|
|
cacheSize.WithLabelValues(Denial).Set(float64(c.ncache.Len()))
|
|
}
|
|
|
|
setMsgTTL(res, uint32(duration.Seconds()))
|
|
|
|
return c.ResponseWriter.WriteMsg(res)
|
|
}
|
|
|
|
func (c *ResponseWriter) set(m *dns.Msg, key string, mt response.Type, duration time.Duration) {
|
|
if key == "" {
|
|
log.Printf("[ERROR] Caching called with empty cache key")
|
|
return
|
|
}
|
|
|
|
switch mt {
|
|
case response.NoError, response.Delegation:
|
|
i := newItem(m, duration)
|
|
c.pcache.Add(key, i)
|
|
|
|
case response.NameError, response.NoData:
|
|
i := newItem(m, duration)
|
|
c.ncache.Add(key, i)
|
|
|
|
case response.OtherError:
|
|
// don't cache these
|
|
default:
|
|
log.Printf("[WARNING] Caching called with unknown classification: %d", mt)
|
|
}
|
|
}
|
|
|
|
// Write implements the dns.ResponseWriter interface.
|
|
func (c *ResponseWriter) Write(buf []byte) (int, error) {
|
|
log.Printf("[WARNING] Caching called with Write: not caching reply")
|
|
n, err := c.ResponseWriter.Write(buf)
|
|
return n, err
|
|
}
|
|
|
|
const (
|
|
maxTTL = 1 * time.Hour
|
|
maxNTTL = 30 * time.Minute
|
|
|
|
minTTL = 5 // seconds
|
|
|
|
defaultCap = 10000 // default capacity of the cache.
|
|
|
|
// Success is the class for caching positive caching.
|
|
Success = "success"
|
|
// Denial is the class defined for negative caching.
|
|
Denial = "denial"
|
|
)
|