From 72e8d997cb2abb8387acb9831648c9a3045b2d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 3 Feb 2024 17:45:27 +0800 Subject: [PATCH] Add address filter support for DNS rules --- adapter/inbound.go | 12 ++- adapter/router.go | 3 + go.mod | 2 +- go.sum | 4 +- option/rule_dns.go | 3 + route/router_dns.go | 186 +++++++++++++++++++++++++----------- route/router_rule.go | 4 + route/rule_abstract.go | 24 ++++- route/rule_default.go | 6 +- route/rule_dns.go | 86 +++++++++++++++++ route/rule_headless.go | 4 +- route/rule_item_rule_set.go | 9 +- route/rule_set_local.go | 1 + route/rule_set_remote.go | 1 + 14 files changed, 276 insertions(+), 69 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index f32b804d21..2a406d08e9 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -51,11 +51,13 @@ type InboundContext struct { // rule cache - IPCIDRMatchSource bool - SourceAddressMatch bool - SourcePortMatch bool - DestinationAddressMatch bool - DestinationPortMatch bool + IPCIDRMatchSource bool + SourceAddressMatch bool + SourcePortMatch bool + DestinationAddressMatch bool + DestinationPortMatch bool + DidMatch bool + IgnoreDestinationIPCIDRMatch bool } func (c *InboundContext) ResetRuleCache() { diff --git a/adapter/router.go b/adapter/router.go index 5828ab3530..297d15e8f4 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -84,6 +84,8 @@ type DNSRule interface { Rule DisableCache() bool RewriteTTL() *uint32 + WithAddressLimit() bool + MatchAddressLimit(metadata *InboundContext) bool } type RuleSet interface { @@ -97,6 +99,7 @@ type RuleSet interface { type RuleSetMetadata struct { ContainsProcessRule bool ContainsWIFIRule bool + ContainsIPCIDRRule bool } type RuleSetStartContext interface { diff --git a/go.mod b/go.mod index 890cff403e..df28d54ef3 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/quic-go v0.40.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.3.1-0.20240105061852-782bc05c5573 - github.com/sagernet/sing-dns v0.1.12 + github.com/sagernet/sing-dns v0.1.13-0.20240203102504-27e217be9060 github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-quic v0.1.8 github.com/sagernet/sing-shadowsocks v0.2.6 diff --git a/go.sum b/go.sum index 5feb32be65..33e86cd218 100644 --- a/go.sum +++ b/go.sum @@ -111,8 +111,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.3.1-0.20240105061852-782bc05c5573 h1:1wGN3eNanp8r+Y3bNBys3ZnAVF5gdtDoDwtosMZEbgA= github.com/sagernet/sing v0.3.1-0.20240105061852-782bc05c5573/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g= -github.com/sagernet/sing-dns v0.1.12 h1:1HqZ+ln+Rezx/aJMStaS0d7oPeX2EobSV1NT537kyj4= -github.com/sagernet/sing-dns v0.1.12/go.mod h1:rx/DTOisneQpCgNQ4jbFU/JNEtnz0lYcHXenlVzpjEU= +github.com/sagernet/sing-dns v0.1.13-0.20240203102504-27e217be9060 h1:ah78H3NjlBEov2MGAKC5Wtn71LhFfRatVrJ88PCQPjE= +github.com/sagernet/sing-dns v0.1.13-0.20240203102504-27e217be9060/go.mod h1:IxOqfSb6Zt6UVCy8fJpDxb2XxqzHUytNqeOuJfaiLu8= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-quic v0.1.8 h1:G4iBXAKIII+uTzd55oZ/9cAQswGjlvHh/0yKMQioDS0= diff --git a/option/rule_dns.go b/option/rule_dns.go index 443f931475..d148e2645f 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -77,6 +77,9 @@ type DefaultDNSRule struct { DomainRegex Listable[string] `json:"domain_regex,omitempty"` Geosite Listable[string] `json:"geosite,omitempty"` SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` + GeoIP Listable[string] `json:"geoip,omitempty"` + IPCIDR Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` SourcePort Listable[uint16] `json:"source_port,omitempty"` diff --git a/route/router_dns.go b/route/router_dns.go index 8ae9171002..a42003eb48 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -2,13 +2,13 @@ package route import ( "context" + "errors" "net/netip" "strings" "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/cache" E "github.com/sagernet/sing/common/exceptions" @@ -37,41 +37,51 @@ func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) { return domain, loaded } -func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool) (context.Context, dns.Transport, dns.DomainStrategy) { +func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (context.Context, dns.Transport, dns.DomainStrategy, adapter.DNSRule, int) { metadata := adapter.ContextFrom(ctx) if metadata == nil { panic("no context") } - for i, rule := range r.dnsRules { - metadata.ResetRuleCache() - if rule.Match(metadata) { - detour := rule.Outbound() - transport, loaded := r.transportMap[detour] - if !loaded { - r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour) - continue - } - if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP { - continue - } - r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour) - if rule.DisableCache() { - ctx = dns.ContextWithDisableCache(ctx, true) - } - if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { - ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) - } - if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { - return ctx, transport, domainStrategy - } else { - return ctx, transport, r.defaultDomainStrategy + if index < len(r.dnsRules) { + dnsRules := r.dnsRules + if index != -1 { + dnsRules = dnsRules[index+1:] + } + for ruleIndex, rule := range dnsRules { + metadata.ResetRuleCache() + if rule.Match(metadata) { + detour := rule.Outbound() + transport, loaded := r.transportMap[detour] + if !loaded { + r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour) + continue + } + if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP { + continue + } + displayRuleIndex := ruleIndex + if index != -1 { + displayRuleIndex += index + 1 + } + r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", detour) + if rule.DisableCache() { + ctx = dns.ContextWithDisableCache(ctx, true) + } + if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { + ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) + } + if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { + return ctx, transport, domainStrategy, rule, ruleIndex + } else { + return ctx, transport, r.defaultDomainStrategy, rule, ruleIndex + } } } } if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded { - return ctx, r.defaultTransport, domainStrategy + return ctx, r.defaultTransport, domainStrategy, nil, -1 } else { - return ctx, r.defaultTransport, r.defaultDomainStrategy + return ctx, r.defaultTransport, r.defaultDomainStrategy, nil, -1 } } @@ -86,7 +96,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er ) response, cached = r.dnsClient.ExchangeCache(ctx, message) if !cached { - ctx, metadata := adapter.AppendContext(ctx) + var metadata *adapter.InboundContext + ctx, metadata = adapter.AppendContext(ctx) if len(message.Question) > 0 { metadata.QueryType = message.Question[0].Qtype switch metadata.QueryType { @@ -97,17 +108,47 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er } metadata.Domain = fqdnToDomain(message.Question[0].Name) } - ctx, transport, strategy := r.matchDNS(ctx, true) - ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout) - defer cancel() - response, err = r.dnsClient.Exchange(ctx, transport, message, strategy) - if err != nil && len(message.Question) > 0 { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String()))) + var ( + transport dns.Transport + strategy dns.DomainStrategy + rule adapter.DNSRule + ruleIndex int + ) + ruleIndex = -1 + for { + var ( + dnsCtx context.Context + cancel context.CancelFunc + addressLimit bool + ) + + dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex) + dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout) + if rule != nil && rule.WithAddressLimit() && isAddressQuery(message) { + addressLimit = true + response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool { + metadata.DestinationAddresses, _ = dns.MessageToAddresses(response) + return rule.MatchAddressLimit(metadata) + }) + } else { + addressLimit = false + response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy) + } + cancel() + if err != nil { + if errors.Is(err, dns.ErrResponseRejected) { + r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String()))) + } else if len(message.Question) > 0 { + r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String()))) + } else { + r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ")) + } + } + if !addressLimit || err == nil { + break + } } } - if len(message.Question) > 0 && response != nil { - LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer) - } if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { for _, answer := range response.Answer { switch record := answer.(type) { @@ -125,22 +166,56 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS r.dnsLogger.DebugContext(ctx, "lookup domain ", domain) ctx, metadata := adapter.AppendContext(ctx) metadata.Domain = domain - ctx, transport, transportStrategy := r.matchDNS(ctx, false) - if strategy == dns.DomainStrategyAsIS { - strategy = transportStrategy + var ( + transport dns.Transport + transportStrategy dns.DomainStrategy + rule adapter.DNSRule + ruleIndex int + resultAddrs []netip.Addr + err error + ) + ruleIndex = -1 + for { + var ( + dnsCtx context.Context + cancel context.CancelFunc + addressLimit bool + ) + metadata.ResetRuleCache() + metadata.DestinationAddresses = nil + dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex) + if strategy == dns.DomainStrategyAsIS { + strategy = transportStrategy + } + dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout) + if rule != nil && rule.WithAddressLimit() { + addressLimit = true + resultAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool { + metadata.DestinationAddresses = responseAddrs + return rule.MatchAddressLimit(metadata) + }) + } else { + addressLimit = false + resultAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy) + } + cancel() + if err != nil { + if errors.Is(err, dns.ErrResponseRejected) { + r.dnsLogger.DebugContext(ctx, "response rejected for ", domain) + } else { + r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) + } + } else if len(resultAddrs) == 0 { + r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") + } + if !addressLimit || err == nil { + break + } } - ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout) - defer cancel() - addrs, err := r.dnsClient.Lookup(ctx, transport, domain, strategy) - if len(addrs) > 0 { - r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(addrs), " ")) - } else if err != nil { - r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) - } else { - r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") - err = dns.RCodeNameError + if len(resultAddrs) > 0 { + r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(resultAddrs), " ")) } - return addrs, err + return resultAddrs, err } func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) { @@ -154,10 +229,13 @@ func (r *Router) ClearDNSCache() { } } -func LogDNSAnswers(logger log.ContextLogger, ctx context.Context, domain string, answers []mDNS.RR) { - for _, answer := range answers { - logger.InfoContext(ctx, "exchanged ", domain, " ", mDNS.Type(answer.Header().Rrtype).String(), " ", formatQuestion(answer.String())) +func isAddressQuery(message *mDNS.Msg) bool { + for _, question := range message.Question { + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { + return true + } } + return false } func fqdnToDomain(fqdn string) string { diff --git a/route/router_rule.go b/route/router_rule.go index 9850b5bc10..252da243e0 100644 --- a/route/router_rule.go +++ b/route/router_rule.go @@ -97,3 +97,7 @@ func isWIFIDNSRule(rule option.DefaultDNSRule) bool { func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0 } + +func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool { + return len(rule.IPCIDR) > 0 || rule.IPSet != nil +} diff --git a/route/rule_abstract.go b/route/rule_abstract.go index 6decb9f37e..2cedd80782 100644 --- a/route/rule_abstract.go +++ b/route/rule_abstract.go @@ -15,6 +15,7 @@ type abstractDefaultRule struct { sourceAddressItems []RuleItem sourcePortItems []RuleItem destinationAddressItems []RuleItem + destinationIPCIDRItems []RuleItem destinationPortItems []RuleItem allItems []RuleItem ruleSetItem RuleItem @@ -64,6 +65,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch { + metadata.DidMatch = true for _, item := range r.sourceAddressItems { if item.Match(metadata) { metadata.SourceAddressMatch = true @@ -73,6 +75,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch { + metadata.DidMatch = true for _, item := range r.sourcePortItems { if item.Match(metadata) { metadata.SourcePortMatch = true @@ -82,6 +85,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch { + metadata.DidMatch = true for _, item := range r.destinationAddressItems { if item.Match(metadata) { metadata.DestinationAddressMatch = true @@ -90,7 +94,18 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } } + if !metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0 && !metadata.DestinationAddressMatch { + metadata.DidMatch = true + for _, item := range r.destinationIPCIDRItems { + if item.Match(metadata) { + metadata.DestinationAddressMatch = true + break + } + } + } + if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch { + metadata.DidMatch = true for _, item := range r.destinationPortItems { if item.Match(metadata) { metadata.DestinationPortMatch = true @@ -100,6 +115,9 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } for _, item := range r.items { + if _, isRuleSet := item.(*RuleSetItem); !isRuleSet { + metadata.DidMatch = true + } if !item.Match(metadata) { return r.invert } @@ -113,7 +131,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { return r.invert } - if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch { + if ((!metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0) || len(r.destinationAddressItems) > 0) && !metadata.DestinationAddressMatch { return r.invert } @@ -121,6 +139,10 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { return r.invert } + if !metadata.DidMatch { + return false + } + return !r.invert } diff --git a/route/rule_default.go b/route/rule_default.go index d2227bb314..d1d13f7d72 100644 --- a/route/rule_default.go +++ b/route/rule_default.go @@ -109,7 +109,7 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt } if len(options.GeoIP) > 0 { item := NewGeoIPItem(router, logger, false, options.GeoIP) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } if len(options.SourceIPCIDR) > 0 { @@ -130,12 +130,12 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt if err != nil { return nil, E.Cause(err, "ipcidr") } - rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } if options.IPIsPrivate { item := NewIPIsPrivateItem(false) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } if len(options.SourcePort) > 0 { diff --git a/route/rule_dns.go b/route/rule_dns.go index c43f629083..0a2bb847aa 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -5,6 +5,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" ) @@ -111,6 +112,11 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.sourceAddressItems = append(rule.sourceAddressItems, item) rule.allItems = append(rule.allItems, item) } + if len(options.GeoIP) > 0 { + item := NewGeoIPItem(router, logger, false, options.GeoIP) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) + } if len(options.SourceIPCIDR) > 0 { item, err := NewIPCIDRItem(true, options.SourceIPCIDR) if err != nil { @@ -119,11 +125,24 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.sourceAddressItems = append(rule.sourceAddressItems, item) rule.allItems = append(rule.allItems, item) } + if len(options.IPCIDR) > 0 { + item, err := NewIPCIDRItem(false, options.IPCIDR) + if err != nil { + return nil, E.Cause(err, "ip_cidr") + } + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) + } if options.SourceIPIsPrivate { item := NewIPIsPrivateItem(true) rule.sourceAddressItems = append(rule.sourceAddressItems, item) rule.allItems = append(rule.allItems, item) } + if options.IPIsPrivate { + item := NewIPIsPrivateItem(false) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) + rule.allItems = append(rule.allItems, item) + } if len(options.SourcePort) > 0 { item := NewPortItem(true, options.SourcePort) rule.sourcePortItems = append(rule.sourcePortItems, item) @@ -211,6 +230,35 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } +func (r *DefaultDNSRule) WithAddressLimit() bool { + if len(r.destinationIPCIDRItems) > 0 { + return true + } + for _, rawRule := range r.items { + ruleSet, isRuleSet := rawRule.(*RuleSetItem) + if !isRuleSet { + continue + } + if ruleSet.ContainsIPCIDRRule() { + return true + } + } + return false +} + +func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool { + metadata.IgnoreDestinationIPCIDRMatch = true + defer func() { + metadata.IgnoreDestinationIPCIDRMatch = false + }() + return r.abstractDefaultRule.Match(metadata) +} + +func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool { + rr := r.abstractDefaultRule.Match(metadata) + return rr +} + var _ adapter.DNSRule = (*LogicalDNSRule)(nil) type LogicalDNSRule struct { @@ -254,3 +302,41 @@ func (r *LogicalDNSRule) DisableCache() bool { func (r *LogicalDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } + +func (r *LogicalDNSRule) WithAddressLimit() bool { + for _, rawRule := range r.rules { + switch rule := rawRule.(type) { + case *DefaultDNSRule: + if rule.WithAddressLimit() { + return true + } + case *LogicalDNSRule: + if rule.WithAddressLimit() { + return true + } + } + } + return false +} + +func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool { + metadata.IgnoreDestinationIPCIDRMatch = true + defer func() { + metadata.IgnoreDestinationIPCIDRMatch = false + }() + return r.MatchAddressLimit(metadata) +} + +func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool { + if r.mode == C.LogicalTypeAnd { + return common.All(r.rules, func(it adapter.HeadlessRule) bool { + metadata.ResetRuleCache() + return it.(adapter.DNSRule).MatchAddressLimit(metadata) + }) != r.invert + } else { + return common.Any(r.rules, func(it adapter.HeadlessRule) bool { + metadata.ResetRuleCache() + return it.(adapter.DNSRule).MatchAddressLimit(metadata) + }) != r.invert + } +} diff --git a/route/rule_headless.go b/route/rule_headless.go index 82c07d3102..92b6720c18 100644 --- a/route/rule_headless.go +++ b/route/rule_headless.go @@ -80,11 +80,11 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles if err != nil { return nil, E.Cause(err, "ipcidr") } - rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } else if options.IPSet != nil { item := NewRawIPCIDRItem(false, options.IPSet) - rule.destinationAddressItems = append(rule.destinationAddressItems, item) + rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item) rule.allItems = append(rule.allItems, item) } if len(options.SourcePort) > 0 { diff --git a/route/rule_item_rule_set.go b/route/rule_item_rule_set.go index 959b2f6110..8354e4215d 100644 --- a/route/rule_item_rule_set.go +++ b/route/rule_item_rule_set.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" ) @@ -13,7 +14,7 @@ var _ RuleItem = (*RuleSetItem)(nil) type RuleSetItem struct { router adapter.Router tagList []string - setList []adapter.HeadlessRule + setList []adapter.RuleSet ipcidrMatchSource bool } @@ -46,6 +47,12 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool { return false } +func (r *RuleSetItem) ContainsIPCIDRRule() bool { + return common.Any(r.setList, func(ruleSet adapter.RuleSet) bool { + return ruleSet.Metadata().ContainsIPCIDRRule + }) +} + func (r *RuleSetItem) String() string { if len(r.tagList) == 1 { return F.ToString("rule_set=", r.tagList[0]) diff --git a/route/rule_set_local.go b/route/rule_set_local.go index 635f22ed01..be8dc17c99 100644 --- a/route/rule_set_local.go +++ b/route/rule_set_local.go @@ -55,6 +55,7 @@ func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleS var metadata adapter.RuleSetMetadata metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) + metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) return &LocalRuleSet{rules, metadata}, nil } diff --git a/route/rule_set_remote.go b/route/rule_set_remote.go index 595e328c5c..a864aaf2f0 100644 --- a/route/rule_set_remote.go +++ b/route/rule_set_remote.go @@ -150,6 +150,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { } s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) + s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) s.rules = rules return nil }