aboutsummaryrefslogtreecommitdiff
path: root/middleware/cache/cache.go
blob: a1db2039efb8298c76232024513f42de45106593 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// 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 * time.Second

	defaultCap = 10000 // default capacity of the cache.

	// Success is the class for caching postive caching.
	Success = "success"
	// Denial is the class defined for negative caching.
	Denial = "denial"
)