aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Chris O'Haver <cohaver@infoblox.com> 2022-06-17 15:48:57 -0400
committerGravatar GitHub <noreply@github.com> 2022-06-17 15:48:57 -0400
commitdded10420b8a477ebd86cd2ceed9207a42c226cc (patch)
tree6b0679260b212428c74a3fbdc6ee3013d6460e0b
parentd60ce0c8d4fd647e880a118f469e8239d6effc7d (diff)
downloadcoredns-dded10420b8a477ebd86cd2ceed9207a42c226cc.tar.gz
coredns-dded10420b8a477ebd86cd2ceed9207a42c226cc.tar.zst
coredns-dded10420b8a477ebd86cd2ceed9207a42c226cc.zip
plugin/cache: Add option to adjust SERVFAIL response cache TTL (#5320)
* add servfail cache opt Signed-off-by: Chris O'Haver <cohaver@infoblox.com>
-rw-r--r--plugin/cache/README.md4
-rw-r--r--plugin/cache/cache.go5
-rw-r--r--plugin/cache/cache_test.go17
-rw-r--r--plugin/cache/setup.go17
-rw-r--r--plugin/cache/setup_test.go37
5 files changed, 78 insertions, 2 deletions
diff --git a/plugin/cache/README.md b/plugin/cache/README.md
index 647909faf..9322af16c 100644
--- a/plugin/cache/README.md
+++ b/plugin/cache/README.md
@@ -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
diff --git a/plugin/cache/cache.go b/plugin/cache/cache.go
index fb84fcec0..fc42866ac 100644
--- a/plugin/cache/cache.go
+++ b/plugin/cache/cache.go
@@ -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)
}
diff --git a/plugin/cache/cache_test.go b/plugin/cache/cache_test.go
index 7299dc073..69bea61a4 100644
--- a/plugin/cache/cache_test.go
+++ b/plugin/cache/cache_test.go
@@ -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)
diff --git a/plugin/cache/setup.go b/plugin/cache/setup.go
index e5258dc06..aa487105c 100644
--- a/plugin/cache/setup.go
+++ b/plugin/cache/setup.go
@@ -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()
}
diff --git a/plugin/cache/setup_test.go b/plugin/cache/setup_test.go
index 675147d1b..5e684c510 100644
--- a/plugin/cache/setup_test.go
+++ b/plugin/cache/setup_test.go
@@ -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)
+ }
+ }
+}