diff --git a/plugin/cache/handler.go b/plugin/cache/handler.go index 54b4c81b7..643101e6f 100644 --- a/plugin/cache/handler.go +++ b/plugin/cache/handler.go @@ -26,7 +26,7 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) return plugin.NextOrFailure(c.Name(), c.Next, ctx, w, rc) } - now := c.now().UTC() + now := c.now() server := metrics.WithServer(ctx) // On cache refresh, we will just use the DO bit from the incoming query for the refresh since we key our cache @@ -158,7 +158,7 @@ func (c *Cache) verifyWithTimeout(ctx context.Context, state request.Request, w // Should not happen: refreshed=true means the upstream response was cacheable. return true, res.code, res.err } - now := c.now().UTC() + now := c.now() if c.keepttl { now = fresh.stored } diff --git a/plugin/cache/item.go b/plugin/cache/item.go index 1aa16a5df..17f68a04e 100644 --- a/plugin/cache/item.go +++ b/plugin/cache/item.go @@ -62,7 +62,9 @@ func newItem(m *dns.Msg, now time.Time, d time.Duration) *item { i.Extra = i.Extra[:j] i.origTTL = uint32(d.Seconds()) - i.stored = now.UTC() + // Keep the monotonic clock reading so TTL expiry is unaffected by wall + // clock adjustments. + i.stored = now i.Freq = new(freq.Freq) @@ -102,7 +104,7 @@ func (i *item) toMsg(m *dns.Msg, now time.Time, do bool, ad bool) *dns.Msg { } func (i *item) ttl(now time.Time) int { - ttl := int(i.origTTL) - int(now.UTC().Sub(i.stored).Seconds()) + ttl := int(i.origTTL) - int(now.Sub(i.stored).Seconds()) return ttl } diff --git a/plugin/cache/item_test.go b/plugin/cache/item_test.go new file mode 100644 index 000000000..f925c8d01 --- /dev/null +++ b/plugin/cache/item_test.go @@ -0,0 +1,23 @@ +package cache + +import ( + "reflect" + "testing" + "time" + + "github.com/miekg/dns" +) + +func TestNewItemPreservesMonotonicClock(t *testing.T) { + now := time.Now() + if reflect.DeepEqual(now, now.Round(0)) { + t.Fatal("time.Now did not include a monotonic clock reading") + } + i := newItem(new(dns.Msg), now, time.Minute) + + // DeepEqual compares the complete time representation, including its + // monotonic clock reading. Time.Equal intentionally ignores that detail. + if !reflect.DeepEqual(i.stored, now) { + t.Fatalf("stored time = %v; want original time %v", i.stored, now) + } +}