plugin/cache: Add option to adjust SERVFAIL response cache TTL (#5320)
* add servfail cache opt Signed-off-by: Chris O'Haver <cohaver@infoblox.com>
This commit is contained in:
parent
d60ce0c8d4
commit
dded10420b
5 changed files with 78 additions and 2 deletions
4
plugin/cache/README.md
vendored
4
plugin/cache/README.md
vendored
|
@ -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...] {
|
||||||
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
|
||||||
|
|
||||||
|
|
5
plugin/cache/cache.go
vendored
5
plugin/cache/cache.go
vendored
|
@ -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 {
|
||||||
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 {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
17
plugin/cache/cache_test.go
vendored
17
plugin/cache/cache_test.go
vendored
|
@ -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)
|
||||||
|
|
17
plugin/cache/setup.go
vendored
17
plugin/cache/setup.go
vendored
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
37
plugin/cache/setup_test.go
vendored
37
plugin/cache/setup_test.go
vendored
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue