From 36dc883c7c288a4cc5e5b40aa2c9825da239e44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 9 Oct 2025 22:56:18 +0800 Subject: [PATCH] Fix DNS negative caching to comply with RFC 2308 --- dns/client.go | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/dns/client.go b/dns/client.go index c1c98c796..8db45d412 100644 --- a/dns/client.go +++ b/dns/client.go @@ -95,6 +95,20 @@ func (c *Client) Start() { } } +func extractNegativeTTL(response *dns.Msg) (uint32, bool) { + for _, record := range response.Ns { + if soa, isSOA := record.(*dns.SOA); isSOA { + soaTTL := soa.Header().Ttl + soaMinimum := soa.Minttl + if soaTTL < soaMinimum { + return soaTTL, true + } + return soaMinimum, true + } + } + return 0, false +} + func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) { if len(message.Question) == 0 { if c.logger != nil { @@ -214,7 +228,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m response.Answer = append(response.Answer, validResponse.Answer...) } }*/ - disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError) || len(response.Answer) == 0 + disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError) if responseChecker != nil { var rejected bool // TODO: add accept_any rule and support to check response instead of addresses @@ -251,10 +265,17 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m } } var timeToLive uint32 - for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { - for _, record := range recordList { - if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive { - timeToLive = record.Header().Ttl + if len(response.Answer) == 0 { + if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA { + timeToLive = soaTTL + } + } + if timeToLive == 0 { + for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { + for _, record := range recordList { + if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive { + timeToLive = record.Header().Ttl + } } } }