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:
Chris O'Haver 2022-06-17 15:48:57 -04:00 committed by GitHub
parent d60ce0c8d4
commit dded10420b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 2 deletions

View file

@ -38,6 +38,7 @@ cache [TTL] [ZONES...] {
denial CAPACITY [TTL] [MINTTL]
prefetch AMOUNT [[DURATION] [PERCENTAGE%]]
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
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.
* `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

View file

@ -32,6 +32,7 @@ type Cache struct {
pcap int
pttl time.Duration
minpttl time.Duration
failttl time.Duration // TTL for caching SERVFAIL responses
// Prefetch.
prefetch int
@ -59,6 +60,7 @@ func New() *Cache {
ncache: cache.New(defaultCap),
nttl: maxNTTL,
minnttl: minNTTL,
failttl: minNTTL,
prefetch: 0,
duration: 1 * time.Minute,
percentage: 10,
@ -158,8 +160,7 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
if mt == response.NameError || mt == response.NoData {
duration = computeTTL(msgTTL, w.minnttl, w.nttl)
} else if mt == response.ServerError {
// use default ttl which is 5s
duration = minTTL
duration = w.failttl
} else {
duration = computeTTL(msgTTL, w.minpttl, w.pttl)
}

View file

@ -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) {
c := New()
c.Next = ttlBackend(60)

17
plugin/cache/setup.go vendored
View file

@ -188,6 +188,23 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
}
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:
return nil, c.ArgErr()
}

View file

@ -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)
}
}
}