From 3cfeeffe630de523034077d27b7fc96ad9d4b8cd Mon Sep 17 00:00:00 2001 From: hengwu0 <17380582683@163.com> Date: Thu, 26 Mar 2026 14:18:48 +0800 Subject: [PATCH] proxy: preserve zero TTL in respectTTLOverrides TTL=0 means the response must not be cached and should only be used for the current transaction. Before this change, respectTTLOverrides() promoted TTL=0 to cacheMinTTL, which could turn a non-cacheable response into a cacheable one. Skip the minimum TTL override when ttl is zero so zero-TTL responses preserve their original semantics. Signed-off-by: hengwu0 <17380582683@163.com> --- proxy/cache.go | 2 +- proxy/cache_internal_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/proxy/cache.go b/proxy/cache.go index d35211f0c..9845c538a 100644 --- a/proxy/cache.go +++ b/proxy/cache.go @@ -521,7 +521,7 @@ func minTTL(h *dns.RR_Header, ttl uint32) uint32 { // Updates a given TTL to fall within the range specified by the cacheMinTTL and // cacheMaxTTL settings. func respectTTLOverrides(ttl, cacheMinTTL, cacheMaxTTL uint32) uint32 { - if ttl < cacheMinTTL { + if 0 < ttl && ttl < cacheMinTTL { return cacheMinTTL } diff --git a/proxy/cache_internal_test.go b/proxy/cache_internal_test.go index 3fd91fdd6..ad00080df 100644 --- a/proxy/cache_internal_test.go +++ b/proxy/cache_internal_test.go @@ -429,6 +429,32 @@ func TestCacheExpirationWithTTLOverride(t *testing.T) { require.NotNil(t, ci) assert.Equal(t, dnsProxy.CacheMaxTTL, ci.m.Answer[0].Header().Ttl) }) + + t.Run("preserve_zero", func(t *testing.T) { + d.Req = newHostTestMessage("host3") + d.Addr = netip.AddrPort{} + + u.ans = []dns.RR{&dns.A{ + Hdr: dns.RR_Header{ + Rrtype: dns.TypeA, + Name: "host3.", + Ttl: 0, + }, + A: net.IP{4, 3, 2, 1}, + }} + + err := dnsProxy.Resolve(testutil.ContextWithTimeout(t, defaultTimeout), d) + require.NoError(t, err) + + require.NotNil(t, d.Res) + require.Len(t, d.Res.Answer, 1) + assert.Zero(t, d.Res.Answer[0].Header().Ttl) + + ci, expired, key := dnsProxy.cache.get(d.Req) + assert.False(t, expired) + assert.Equal(t, msgToKey(d.Req), key) + assert.Nil(t, ci) + }) } type testEntry struct {