mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
Fix DNS cache not working when domain strategy is set
The cache lookup was performed before rule matching, using the caller's strategy (usually AsIS/0) instead of the resolved strategy. This caused cache misses when ipv4_only was configured globally but the cache lookup expected both A and AAAA records. Remove LookupCache and ExchangeCache from Router, as the cache checks inside client.Lookup and client.Exchange already handle caching correctly after rule matching with the proper strategy and transport.
This commit is contained in:
@@ -353,68 +353,6 @@ func (c *Client) ClearCache() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool) {
|
||||
if c.disableCache || c.independentCache {
|
||||
return nil, false
|
||||
}
|
||||
if dns.IsFqdn(domain) {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
dnsName := dns.Fqdn(domain)
|
||||
if strategy == C.DomainStrategyIPv4Only {
|
||||
addresses, err := c.questionCache(dns.Question{
|
||||
Name: dnsName,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}, nil)
|
||||
if err != ErrNotCached {
|
||||
return addresses, true
|
||||
}
|
||||
} else if strategy == C.DomainStrategyIPv6Only {
|
||||
addresses, err := c.questionCache(dns.Question{
|
||||
Name: dnsName,
|
||||
Qtype: dns.TypeAAAA,
|
||||
Qclass: dns.ClassINET,
|
||||
}, nil)
|
||||
if err != ErrNotCached {
|
||||
return addresses, true
|
||||
}
|
||||
} else {
|
||||
response4, _ := c.loadResponse(dns.Question{
|
||||
Name: dnsName,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}, nil)
|
||||
if response4 == nil {
|
||||
return nil, false
|
||||
}
|
||||
response6, _ := c.loadResponse(dns.Question{
|
||||
Name: dnsName,
|
||||
Qtype: dns.TypeAAAA,
|
||||
Qclass: dns.ClassINET,
|
||||
}, nil)
|
||||
if response6 == nil {
|
||||
return nil, false
|
||||
}
|
||||
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *Client) ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool) {
|
||||
if c.disableCache || c.independentCache || len(message.Question) != 1 {
|
||||
return nil, false
|
||||
}
|
||||
question := message.Question[0]
|
||||
response, ttl := c.loadResponse(question, nil)
|
||||
if response == nil {
|
||||
return nil, false
|
||||
}
|
||||
logCachedResponse(c.logger, ctx, response, ttl)
|
||||
response.Id = message.Id
|
||||
return response, true
|
||||
}
|
||||
|
||||
func sortAddresses(response4 []netip.Addr, response6 []netip.Addr, strategy C.DomainStrategy) []netip.Addr {
|
||||
if strategy == C.DomainStrategyPreferIPv6 {
|
||||
return append(response6, response4...)
|
||||
|
||||
174
dns/router.go
174
dns/router.go
@@ -214,97 +214,95 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
}
|
||||
r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String()))
|
||||
var (
|
||||
response *mDNS.Msg
|
||||
transport adapter.DNSTransport
|
||||
err error
|
||||
)
|
||||
response, cached := r.client.ExchangeCache(ctx, message)
|
||||
if !cached {
|
||||
var metadata *adapter.InboundContext
|
||||
ctx, metadata = adapter.ExtendContext(ctx)
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
metadata.QueryType = message.Question[0].Qtype
|
||||
switch metadata.QueryType {
|
||||
case mDNS.TypeA:
|
||||
metadata.IPVersion = 4
|
||||
case mDNS.TypeAAAA:
|
||||
metadata.IPVersion = 6
|
||||
}
|
||||
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
||||
if options.Transport != nil {
|
||||
transport = options.Transport
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
}
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
var metadata *adapter.InboundContext
|
||||
ctx, metadata = adapter.ExtendContext(ctx)
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
metadata.QueryType = message.Question[0].Qtype
|
||||
switch metadata.QueryType {
|
||||
case mDNS.TypeA:
|
||||
metadata.IPVersion = 4
|
||||
case mDNS.TypeAAAA:
|
||||
metadata.IPVersion = 6
|
||||
}
|
||||
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
||||
if options.Transport != nil {
|
||||
transport = options.Transport
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
}
|
||||
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||
} else {
|
||||
var (
|
||||
rule adapter.DNSRule
|
||||
ruleIndex int
|
||||
)
|
||||
ruleIndex = -1
|
||||
for {
|
||||
dnsCtx := adapter.OverrideContext(ctx)
|
||||
dnsOptions := options
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
||||
if rule != nil {
|
||||
switch action := rule.Action().(type) {
|
||||
case *R.RuleActionReject:
|
||||
switch action.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
return &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: message.Id,
|
||||
Rcode: mDNS.RcodeRefused,
|
||||
Response: true,
|
||||
},
|
||||
Question: []mDNS.Question{message.Question[0]},
|
||||
}, nil
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return nil, tun.ErrDrop
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
return action.Response(message), nil
|
||||
}
|
||||
}
|
||||
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||
if rule != nil && rule.WithAddressLimit() {
|
||||
responseCheck = func(responseAddrs []netip.Addr) bool {
|
||||
metadata.DestinationAddresses = responseAddrs
|
||||
return rule.MatchAddressLimit(metadata)
|
||||
}
|
||||
}
|
||||
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
||||
dnsOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)
|
||||
var rejected bool
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrResponseRejectedCached) {
|
||||
rejected = true
|
||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())), " (cached)")
|
||||
} else if errors.Is(err, ErrResponseRejected) {
|
||||
rejected = true
|
||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||
} else if len(message.Question) > 0 {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||
}
|
||||
}
|
||||
if responseCheck != nil && rejected {
|
||||
continue
|
||||
}
|
||||
break
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||
} else {
|
||||
var (
|
||||
rule adapter.DNSRule
|
||||
ruleIndex int
|
||||
)
|
||||
ruleIndex = -1
|
||||
for {
|
||||
dnsCtx := adapter.OverrideContext(ctx)
|
||||
dnsOptions := options
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
||||
if rule != nil {
|
||||
switch action := rule.Action().(type) {
|
||||
case *R.RuleActionReject:
|
||||
switch action.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
return &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: message.Id,
|
||||
Rcode: mDNS.RcodeRefused,
|
||||
Response: true,
|
||||
},
|
||||
Question: []mDNS.Question{message.Question[0]},
|
||||
}, nil
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return nil, tun.ErrDrop
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
return action.Response(message), nil
|
||||
}
|
||||
}
|
||||
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||
if rule != nil && rule.WithAddressLimit() {
|
||||
responseCheck = func(responseAddrs []netip.Addr) bool {
|
||||
metadata.DestinationAddresses = responseAddrs
|
||||
return rule.MatchAddressLimit(metadata)
|
||||
}
|
||||
}
|
||||
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
||||
dnsOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)
|
||||
var rejected bool
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrResponseRejectedCached) {
|
||||
rejected = true
|
||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())), " (cached)")
|
||||
} else if errors.Is(err, ErrResponseRejected) {
|
||||
rejected = true
|
||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||
} else if len(message.Question) > 0 {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||
}
|
||||
}
|
||||
if responseCheck != nil && rejected {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -327,7 +325,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||
var (
|
||||
responseAddrs []netip.Addr
|
||||
cached bool
|
||||
err error
|
||||
)
|
||||
printResult := func() {
|
||||
@@ -347,13 +344,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
err = E.Cause(err, "lookup ", domain)
|
||||
}
|
||||
}
|
||||
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
||||
if cached {
|
||||
if len(responseAddrs) == 0 {
|
||||
return nil, E.New("lookup ", domain, ": empty result (cached)")
|
||||
}
|
||||
return responseAddrs, nil
|
||||
}
|
||||
r.logger.DebugContext(ctx, "lookup domain ", domain)
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
|
||||
Reference in New Issue
Block a user