Commit dded1042 authored by Chris O'Haver's avatar Chris O'Haver Committed by GitHub

plugin/cache: Add option to adjust SERVFAIL response cache TTL (#5320)

* add servfail cache opt
Signed-off-by: default avatarChris O'Haver <cohaver@infoblox.com>
parent d60ce0c8
...@@ -38,6 +38,7 @@ cache [TTL] [ZONES...] { ...@@ -38,6 +38,7 @@ cache [TTL] [ZONES...] {
denial CAPACITY [TTL] [MINTTL] denial CAPACITY [TTL] [MINTTL]
prefetch AMOUNT [[DURATION] [PERCENTAGE%]] prefetch AMOUNT [[DURATION] [PERCENTAGE%]]
serve_stale [DURATION] [REFRESH_MODE] serve_stale [DURATION] [REFRESH_MODE]
servfail DURATION
} }
~~~ ~~~
...@@ -63,6 +64,9 @@ cache [TTL] [ZONES...] { ...@@ -63,6 +64,9 @@ cache [TTL] [ZONES...] {
checking to see if the entry is available from the source. **REFRESH_MODE** defaults to `immediate`. Setting this checking to see if the entry is available from the source. **REFRESH_MODE** defaults to `immediate`. Setting this
value to `verify` can lead to increased latency when serving stale responses, but will prevent stale entries value to `verify` can lead to increased latency when serving stale responses, but will prevent stale entries
from ever being served if an updated response can be retrieved from the source. from ever being served if an updated response can be retrieved from the source.
* `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.
## Capacity and Eviction ## Capacity and Eviction
......
...@@ -32,6 +32,7 @@ type Cache struct { ...@@ -32,6 +32,7 @@ type Cache struct {
pcap int pcap int
pttl time.Duration pttl time.Duration
minpttl time.Duration minpttl time.Duration
failttl time.Duration // TTL for caching SERVFAIL responses
// Prefetch. // Prefetch.
prefetch int prefetch int
...@@ -59,6 +60,7 @@ func New() *Cache { ...@@ -59,6 +60,7 @@ func New() *Cache {
ncache: cache.New(defaultCap), ncache: cache.New(defaultCap),
nttl: maxNTTL, nttl: maxNTTL,
minnttl: minNTTL, minnttl: minNTTL,
failttl: minNTTL,
prefetch: 0, prefetch: 0,
duration: 1 * time.Minute, duration: 1 * time.Minute,
percentage: 10, percentage: 10,
...@@ -158,8 +160,7 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error { ...@@ -158,8 +160,7 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
if mt == response.NameError || mt == response.NoData { if mt == response.NameError || mt == response.NoData {
duration = computeTTL(msgTTL, w.minnttl, w.nttl) duration = computeTTL(msgTTL, w.minnttl, w.nttl)
} else if mt == response.ServerError { } else if mt == response.ServerError {
// use default ttl which is 5s duration = w.failttl
duration = minTTL
} else { } else {
duration = computeTTL(msgTTL, w.minpttl, w.pttl) duration = computeTTL(msgTTL, w.minpttl, w.pttl)
} }
......
...@@ -258,6 +258,23 @@ func TestCacheZeroTTL(t *testing.T) { ...@@ -258,6 +258,23 @@ func TestCacheZeroTTL(t *testing.T) {
} }
} }
func TestCacheServfailTTL0(t *testing.T) {
c := New()
c.minpttl = minTTL
c.minnttl = minNTTL
c.failttl = 0
c.Next = servFailBackend(0)
req := new(dns.Msg)
req.SetQuestion("example.org.", dns.TypeA)
ctx := context.TODO()
c.ServeDNS(ctx, &test.ResponseWriter{}, req)
if c.ncache.Len() != 0 {
t.Errorf("SERVFAIL response should not have been cached")
}
}
func TestServeFromStaleCache(t *testing.T) { func TestServeFromStaleCache(t *testing.T) {
c := New() c := New()
c.Next = ttlBackend(60) c.Next = ttlBackend(60)
......
...@@ -188,6 +188,23 @@ func cacheParse(c *caddy.Controller) (*Cache, error) { ...@@ -188,6 +188,23 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
} }
ca.verifyStale = mode == "verify" ca.verifyStale = mode == "verify"
} }
case "servfail":
args := c.RemainingArgs()
if len(args) != 1 {
return nil, c.ArgErr()
}
d, err := time.ParseDuration(args[0])
if err != nil {
return nil, err
}
if d < 0 {
return nil, errors.New("invalid negative ttl for servfail")
}
if d > 5*time.Minute {
// RFC 2308 prohibits caching SERVFAIL longer than 5 minutes
return nil, errors.New("caching SERVFAIL responses over 5 minutes is not permitted")
}
ca.failttl = d
default: default:
return nil, c.ArgErr() return nil, c.ArgErr()
} }
......
...@@ -155,3 +155,40 @@ func TestServeStale(t *testing.T) { ...@@ -155,3 +155,40 @@ func TestServeStale(t *testing.T) {
} }
} }
} }
func TestServfail(t *testing.T) {
tests := []struct {
input string
shouldErr bool
failttl time.Duration
}{
{"servfail 1s", false, 1 * time.Second},
{"servfail 5m", false, 5 * time.Minute},
{"servfail 0s", false, 0},
{"servfail 0", false, 0},
// fails
{"servfail", true, minNTTL},
{"servfail 6m", true, minNTTL},
{"servfail 20", true, minNTTL},
{"servfail -1s", true, minNTTL},
{"servfail aa", true, minNTTL},
{"servfail 1m invalid", true, minNTTL},
}
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 && err != nil {
continue
}
if ca.failttl != test.failttl {
t.Errorf("Test %v: Expected stale %v but found: %v", i, test.failttl, ca.staleUpTo)
}
}
}
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