Commit 95fcf2c4 authored by Chris O'Haver's avatar Chris O'Haver Committed by GitHub

plugin/cache: Add cache disable option (#5540)

* add cache disable options
Signed-off-by: default avatarChris O'Haver <cohaver@infoblox.com>
parent 2fe5273c
......@@ -39,6 +39,7 @@ cache [TTL] [ZONES...] {
prefetch AMOUNT [[DURATION] [PERCENTAGE%]]
serve_stale [DURATION] [REFRESH_MODE]
servfail DURATION
disable success|denial [ZONES...]
}
~~~
......@@ -67,6 +68,8 @@ cache [TTL] [ZONES...] {
* `servfail` cache SERVFAIL responses for **DURATION**. Setting **DURATION** to 0 will disable caching of SERVFAIL
responses. If this option is not set, SERVFAIL responses will be cached for 5 seconds. **DURATION** may not be
greater than 5 minutes.
* `disable` disable the success or denial cache for the listed **ZONES**. If no **ZONES** are given, the specified
cache will be disabled for all zones.
## Capacity and Eviction
......@@ -124,3 +127,13 @@ example.org {
}
}
~~~
Enable caching for `example.org`, but do not cache denials in `sub.example.org`:
~~~ corefile
example.org {
cache {
disable denial sub.example.org
}
}
~~~
\ No newline at end of file
......@@ -43,6 +43,10 @@ type Cache struct {
staleUpTo time.Duration
verifyStale bool
// Positive/negative zone exceptions
pexcept []string
nexcept []string
// Testing.
now func() time.Time
}
......@@ -117,6 +121,8 @@ type ResponseWriter struct {
wildcardFunc func() string // function to retrieve wildcard name that synthesized the result.
pexcept []string // positive zone exceptions
nexcept []string // negative zone exceptions
}
// newPrefetchResponseWriter returns a Cache ResponseWriter to be used in
......@@ -204,6 +210,10 @@ func (w *ResponseWriter) set(m *dns.Msg, key uint64, mt response.Type, duration
// and key is valid
switch mt {
case response.NoError, response.Delegation:
if plugin.Zones(w.pexcept).Matches(m.Question[0].Name) != "" {
// zone is in exception list, do not cache
return
}
i := newItem(m, w.now(), duration)
if w.wildcardFunc != nil {
i.wildcard = w.wildcardFunc()
......@@ -217,6 +227,10 @@ func (w *ResponseWriter) set(m *dns.Msg, key uint64, mt response.Type, duration
}
case response.NameError, response.NoData, response.ServerError:
if plugin.Zones(w.nexcept).Matches(m.Question[0].Name) != "" {
// zone is in exception list, do not cache
return
}
i := newItem(m, w.now(), duration)
if w.wildcardFunc != nil {
i.wildcard = w.wildcardFunc()
......
......@@ -17,8 +17,8 @@ import (
)
type cacheTestCase struct {
test.Case
in test.Case
test.Case // the expected message coming "out" of cache
in test.Case // the test message going "in" to cache
AuthenticatedData bool
RecursionAvailable bool
Truncated bool
......@@ -163,6 +163,62 @@ var cacheTestCases = []cacheTestCase{
},
shouldCache: true,
},
{
in: test.Case{
Rcode: dns.RcodeNameError,
Qname: "neg-disabled.example.org.", Qtype: dns.TypeA,
Ns: []dns.RR{
test.SOA("example.org. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082540 7200 3600 1209600 3600"),
},
},
Case: test.Case{},
shouldCache: false,
},
{
in: test.Case{
Rcode: dns.RcodeSuccess,
Qname: "pos-disabled.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("pos-disabled.example.org. 3600 IN A 127.0.0.1"),
},
},
Case: test.Case{},
shouldCache: false,
},
{
in: test.Case{
Rcode: dns.RcodeNameError,
Qname: "pos-disabled.example.org.", Qtype: dns.TypeA,
Ns: []dns.RR{
test.SOA("example.org. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082540 7200 3600 1209600 3600"),
},
},
Case: test.Case{
Rcode: dns.RcodeNameError,
Qname: "pos-disabled.example.org.", Qtype: dns.TypeA,
Ns: []dns.RR{
test.SOA("example.org. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082540 7200 3600 1209600 3600"),
},
},
shouldCache: true,
},
{
in: test.Case{
Rcode: dns.RcodeSuccess,
Qname: "neg-disabled.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("neg-disabled.example.org. 3600 IN A 127.0.0.1"),
},
},
Case: test.Case{
Rcode: dns.RcodeSuccess,
Qname: "neg-disabled.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("neg-disabled.example.org. 3600 IN A 127.0.0.1"),
},
},
shouldCache: true,
},
}
func cacheMsg(m *dns.Msg, tc cacheTestCase) *dns.Msg {
......@@ -183,6 +239,9 @@ func newTestCache(ttl time.Duration) (*Cache, *ResponseWriter) {
c.nttl = ttl
crr := &ResponseWriter{ResponseWriter: nil, Cache: c}
crr.nexcept = []string{"neg-disabled.example.org."}
crr.pexcept = []string{"pos-disabled.example.org."}
return c, crr
}
......
......@@ -38,7 +38,8 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
ttl := 0
i := c.getIgnoreTTL(now, state, server)
if i == nil {
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do, ad: ad, wildcardFunc: wildcardFunc(ctx)}
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do, ad: ad,
nexcept: c.nexcept, pexcept: c.pexcept, wildcardFunc: wildcardFunc(ctx)}
return c.doRefresh(ctx, state, crr)
}
ttl = i.ttl(now)
......
......@@ -205,6 +205,35 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
return nil, errors.New("caching SERVFAIL responses over 5 minutes is not permitted")
}
ca.failttl = d
case "disable":
// disable [success|denial] [zones]...
args := c.RemainingArgs()
if len(args) < 1 {
return nil, c.ArgErr()
}
var zones []string
if len(args) > 1 {
for _, z := range args[1:] { // args[1:] define the list of zones to disable
nz := plugin.Name(z).Normalize()
if nz == "" {
return nil, fmt.Errorf("invalid disabled zone: %s", z)
}
zones = append(zones, nz)
}
} else {
// if no zones specified, default to root
zones = []string{"."}
}
switch args[0] { // args[0] defines which cache to disable
case Denial:
ca.nexcept = zones
case Success:
ca.pexcept = zones
default:
return nil, fmt.Errorf("cache type for disable must be %q or %q", Success, Denial)
}
default:
return nil, c.ArgErr()
}
......
......@@ -192,3 +192,42 @@ func TestServfail(t *testing.T) {
}
}
}
func TestDisable(t *testing.T) {
tests := []struct {
input string
shouldErr bool
nexcept []string
pexcept []string
}{
// positive
{"disable denial example.com example.org", false, []string{"example.com.", "example.org."}, nil},
{"disable success example.com example.org", false, nil, []string{"example.com.", "example.org."}},
{"disable denial", false, []string{"."}, nil},
{"disable success", false, nil, []string{"."}},
{"disable denial example.com example.org\ndisable success example.com example.org", false,
[]string{"example.com.", "example.org."}, []string{"example.com.", "example.org."}},
// negative
{"disable invalid example.com example.org", true, nil, nil},
}
for i, test := range tests {
c := caddy.NewTestController("dns", fmt.Sprintf("cache {\n%s\n}", test.input))
ca, err := cacheParse(c)
if test.shouldErr && err == nil {
t.Errorf("Test %v: Expected error but found nil", i)
continue
} else if !test.shouldErr && err != nil {
t.Errorf("Test %v: Expected no error but found error: %v", i, err)
continue
}
if test.shouldErr {
continue
}
if fmt.Sprintf("%v", test.nexcept) != fmt.Sprintf("%v", ca.nexcept) {
t.Errorf("Test %v: Expected %v but got: %v", i, test.nexcept, ca.nexcept)
}
if fmt.Sprintf("%v", test.pexcept) != fmt.Sprintf("%v", ca.pexcept) {
t.Errorf("Test %v: Expected %v but got: %v", i, test.pexcept, ca.pexcept)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment