Fix address filter matching for inverted legacy DNS rules

Inverted rules whose address filter conditions come from rule sets or
logical rules were rejected during pre-lookup matching, so the lookup was
never sent and the rules never matched; for plain ip rule sets this was a
regression from the rule-set invert fix in 1.12.22. Pre-lookup matching
now tracks whether the result depends on deferred address filter
conditions and defers such rules to the response phase, while results
already determined by evaluated conditions are still decided during
pre-lookup.
This commit is contained in:
世界
2026-08-30 17:41:46 +08:00
parent aea28ca6dc
commit 08986ad405
5 changed files with 460 additions and 57 deletions
+2 -2
View File
@@ -108,7 +108,7 @@ type InboundContext struct {
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
DidMatch bool
DeferredIPCIDRMatchGroups uint8
IgnoreDestinationIPCIDRMatch bool
}
@@ -123,7 +123,7 @@ func (c *InboundContext) ResetRuleMatchCache() {
c.SourcePortMatch = false
c.DestinationAddressMatch = false
c.DestinationPortMatch = false
c.DidMatch = false
c.DeferredIPCIDRMatchGroups = 0
}
func (c *InboundContext) DNSResponseAddressesForMatch() []netip.Addr {
+33 -17
View File
@@ -57,10 +57,11 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
}
matched := r.matchInner(metadata)
if r.invert {
if matched && metadata.IgnoreDestinationIPCIDRMatch && !metadata.DidMatch && len(r.destinationIPCIDRItems) > 0 {
if !matched {
metadata.DeferredIPCIDRMatchGroups = 0
return true
}
return !matched
return metadata.DeferredIPCIDRMatchGroups != 0
}
return matched
}
@@ -68,22 +69,25 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
func (r *abstractDefaultRule) matchInner(metadata *adapter.InboundContext) bool {
groups := r.evaluateGroups(metadata)
for _, item := range r.items {
metadata.DidMatch = true
if !item.Match(metadata) {
return false
}
}
var matched bool
if r.ruleSetItem != nil {
metadata.DidMatch = true
return r.ruleSetItem.matchWithOuterGroups(metadata, groups)
matched = r.ruleSetItem.matchWithOuterGroups(metadata, groups)
} else {
matched = groups.done()
}
return groups.done()
if matched {
metadata.DeferredIPCIDRMatchGroups &^= uint8(groups.satisfied)
}
return matched
}
func (r *abstractDefaultRule) evaluateForMerge(metadata *adapter.InboundContext) (ruleGroupMatch, bool) {
groups := r.evaluateGroups(metadata)
for _, item := range r.items {
metadata.DidMatch = true
if !item.Match(metadata) {
return ruleGroupMatch{}, false
}
@@ -92,7 +96,7 @@ func (r *abstractDefaultRule) evaluateForMerge(metadata *adapter.InboundContext)
}
func (r *abstractDefaultRule) destinationIPCIDRMatchesSource(metadata *adapter.InboundContext) bool {
return !metadata.IgnoreDestinationIPCIDRMatch && metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
return metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
}
func (r *abstractDefaultRule) destinationIPCIDRMatchesDestination(metadata *adapter.InboundContext) bool {
@@ -102,47 +106,44 @@ func (r *abstractDefaultRule) destinationIPCIDRMatchesDestination(metadata *adap
func (r *abstractDefaultRule) evaluateGroups(metadata *adapter.InboundContext) ruleGroupMatch {
var groups ruleGroupMatch
if len(r.sourceAddressItems) > 0 {
metadata.DidMatch = true
groups.required |= ruleMatchSourceAddress
if matchAnyItem(r.sourceAddressItems, metadata) {
groups.satisfied |= ruleMatchSourceAddress
}
}
if r.destinationIPCIDRMatchesSource(metadata) {
metadata.DidMatch = true
groups.required |= ruleMatchSourceAddress
if !groups.satisfied.has(ruleMatchSourceAddress) && matchAnyItem(r.destinationIPCIDRItems, metadata) {
groups.satisfied |= ruleMatchSourceAddress
}
}
if len(r.sourcePortItems) > 0 {
metadata.DidMatch = true
groups.required |= ruleMatchSourcePort
if matchAnyItem(r.sourcePortItems, metadata) {
groups.satisfied |= ruleMatchSourcePort
}
}
if len(r.destinationAddressItems) > 0 {
metadata.DidMatch = true
groups.required |= ruleMatchDestinationAddress
if matchAnyItem(r.destinationAddressItems, metadata) {
groups.satisfied |= ruleMatchDestinationAddress
}
}
if r.destinationIPCIDRMatchesDestination(metadata) {
metadata.DidMatch = true
groups.required |= ruleMatchDestinationAddress
if !groups.satisfied.has(ruleMatchDestinationAddress) && matchAnyItem(r.destinationIPCIDRItems, metadata) {
groups.satisfied |= ruleMatchDestinationAddress
}
}
if len(r.destinationPortItems) > 0 {
metadata.DidMatch = true
groups.required |= ruleMatchDestinationPort
if matchAnyItem(r.destinationPortItems, metadata) {
groups.satisfied |= ruleMatchDestinationPort
}
}
if metadata.IgnoreDestinationIPCIDRMatch && !metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0 && len(r.destinationAddressItems) == 0 {
metadata.DeferredIPCIDRMatchGroups |= uint8(ruleMatchDestinationAddress)
}
return groups
}
@@ -201,7 +202,10 @@ func (r *abstractLogicalRule) Close() error {
}
func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
var matched bool
var (
matched bool
deferredGroups uint8
)
if r.mode == C.LogicalTypeAnd {
matched = true
for _, rule := range r.rules {
@@ -209,8 +213,10 @@ func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
nestedMetadata.ResetRuleCache()
if !rule.Match(&nestedMetadata) {
matched = false
deferredGroups = 0
break
}
deferredGroups |= nestedMetadata.DeferredIPCIDRMatchGroups
}
} else {
for _, rule := range r.rules {
@@ -218,12 +224,22 @@ func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
nestedMetadata.ResetRuleCache()
if rule.Match(&nestedMetadata) {
matched = true
break
if nestedMetadata.DeferredIPCIDRMatchGroups == 0 {
deferredGroups = 0
break
}
deferredGroups |= nestedMetadata.DeferredIPCIDRMatchGroups
}
}
}
if matched {
metadata.DeferredIPCIDRMatchGroups |= deferredGroups
}
if r.invert {
return !matched
if !matched {
return true
}
return deferredGroups != 0
}
return matched
}
+2 -32
View File
@@ -462,6 +462,7 @@ func (r *DefaultDNSRule) matchForMatch(metadata *adapter.InboundContext) bool {
func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext, response *dns.Msg) bool {
matchMetadata := *metadata
matchMetadata.ResetRuleCache()
matchMetadata.DNSResponse = response
matchMetadata.DestinationAddressMatchFromResponse = true
return r.abstractDefaultRule.Match(&matchMetadata)
@@ -492,34 +493,6 @@ func (r *LogicalDNSRule) Race() bool {
return r.race
}
func (r *LogicalDNSRule) matchForMatch(metadata *adapter.InboundContext) bool {
var matched bool
if r.mode == C.LogicalTypeAnd {
matched = true
for _, rule := range r.rules {
nestedMetadata := *metadata
nestedMetadata.ResetRuleCache()
if !rule.Match(&nestedMetadata) {
matched = false
break
}
}
} else {
for _, rule := range r.rules {
nestedMetadata := *metadata
nestedMetadata.ResetRuleCache()
if rule.Match(&nestedMetadata) {
matched = true
break
}
}
}
if r.invert {
return !matched
}
return matched
}
func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule, legacyDNSMode bool) (*LogicalDNSRule, error) {
r := &LogicalDNSRule{
abstractLogicalRule: abstractLogicalRule{
@@ -574,10 +547,6 @@ func (r *LogicalDNSRule) WithAddressLimit() bool {
return false
}
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
return r.matchForMatch(metadata)
}
func (r *LogicalDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool {
metadata.IgnoreDestinationIPCIDRMatch = true
defer func() { metadata.IgnoreDestinationIPCIDRMatch = false }()
@@ -586,6 +555,7 @@ func (r *LogicalDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool {
func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext, response *dns.Msg) bool {
matchMetadata := *metadata
matchMetadata.ResetRuleCache()
matchMetadata.DNSResponse = response
matchMetadata.DestinationAddressMatchFromResponse = true
return r.abstractLogicalRule.Match(&matchMetadata)
+386
View File
@@ -0,0 +1,386 @@
package rule
import (
"context"
"net"
"testing"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/service"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
)
type addressFilterRouter struct {
adapter.Router
ruleSets map[string]adapter.RuleSet
}
func (r *addressFilterRouter) RuleSet(tag string) (adapter.RuleSet, bool) {
ruleSet, loaded := r.ruleSets[tag]
return ruleSet, loaded
}
func addressFilterContext(t *testing.T, ruleSetConfigs map[string]string) context.Context {
t.Helper()
router := &addressFilterRouter{ruleSets: make(map[string]adapter.RuleSet)}
ctx := service.ContextWith[adapter.Router](context.Background(), router)
for tag, config := range ruleSetConfigs {
var plainOptions option.PlainRuleSetCompat
err := json.UnmarshalContext(ctx, []byte(config), &plainOptions)
require.NoError(t, err)
ruleSet, err := NewLocalRuleSet(ctx, log.NewNOPFactory().Logger(), tag, option.RuleSet{
Type: C.RuleSetTypeInline,
InlineOptions: plainOptions.Options,
})
require.NoError(t, err)
router.ruleSets[tag] = ruleSet
}
return ctx
}
func addressFilterDNSRule(t *testing.T, ctx context.Context, config string) adapter.DNSRule {
t.Helper()
var ruleOptions option.DNSRule
err := json.UnmarshalContext(ctx, []byte(config), &ruleOptions)
require.NoError(t, err)
rule, err := NewDNSRule(ctx, log.NewNOPFactory().NewLogger("test"), ruleOptions, true, true)
require.NoError(t, err)
require.NoError(t, rule.Start())
return rule
}
func addressFilterResponse(address string) *dns.Msg {
response := &dns.Msg{}
response.Rcode = dns.RcodeSuccess
response.Answer = append(response.Answer, &dns.A{
Hdr: dns.RR_Header{Rrtype: dns.TypeA, Class: dns.ClassINET},
A: net.ParseIP(address).To4(),
})
return response
}
// addressFilterFlow mirrors dns/router.go: LegacyPreMatch under
// IgnoreDestinationIPCIDRMatch, then addressLimitResponseCheck against the
// response.
func addressFilterFlow(rule adapter.DNSRule, domain string, responseAddress string) (preMatched bool, routed bool) {
metadata := adapter.InboundContext{
Domain: domain,
QueryType: dns.TypeA,
Source: M.ParseSocksaddrHostPort("192.168.1.10", 5353),
}
metadata.ResetRuleCache()
preMatched = rule.LegacyPreMatch(&metadata)
if !preMatched {
return false, false
}
if !rule.WithAddressLimit() {
return true, true
}
checkMetadata := metadata
return true, rule.MatchAddressLimit(&checkMetadata, addressFilterResponse(responseAddress))
}
func TestDNSAddressFilterInvert(t *testing.T) {
t.Parallel()
ctx := addressFilterContext(t, map[string]string{
"mixed": `{"version": 3, "rules": [{"domain_suffix": ["ads.example"]}, {"ip_cidr": ["1.1.1.0/24"]}]}`,
"cn-ip": `{"version": 3, "rules": [{"ip_cidr": ["1.1.1.0/24"]}]}`,
"lan-ip": `{"version": 3, "rules": [{"ip_cidr": ["192.168.0.0/16"]}]}`,
"other-net": `{"version": 3, "rules": [{"ip_cidr": ["10.99.0.0/16"]}]}`,
"cn-domain": `{"version": 3, "rules": [{"domain_suffix": ["cn.example"]}]}`,
})
testCases := []struct {
name string
rule string
domain string
responseAddress string
expectPreMatch bool
expectRouted bool
}{
{
name: "direct mixed invert, domain hit",
rule: `{"domain_suffix": ["lookup.example"], "ip_cidr": ["1.1.1.0/24"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
},
{
name: "direct mixed invert, both miss",
rule: `{"domain_suffix": ["lookup.example"], "ip_cidr": ["1.1.1.0/24"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "other.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "direct mixed invert, ip hit",
rule: `{"domain_suffix": ["lookup.example"], "ip_cidr": ["1.1.1.0/24"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "other.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "ip rule-set invert, ip miss",
rule: `{"rule_set": ["cn-ip"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "ip rule-set invert, ip hit",
rule: `{"rule_set": ["cn-ip"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "mixed rule-set invert, both miss",
rule: `{"rule_set": ["mixed"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "mixed rule-set invert, domain hit skips pre-lookup",
rule: `{"rule_set": ["mixed"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "ads.example",
responseAddress: "8.8.8.8",
},
{
name: "mixed rule-set invert, ip hit",
rule: `{"rule_set": ["mixed"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "logical invert, ip miss",
rule: `{"type": "logical", "mode": "or", "invert": true, "rules": [{"rule_set": ["cn-ip"]}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "logical invert, ip hit",
rule: `{"type": "logical", "mode": "or", "invert": true, "rules": [{"rule_set": ["cn-ip"]}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "logical and with inverted ip rule-set, ip miss",
rule: `{"type": "logical", "mode": "and", "rules": [{"domain_suffix": ["lookup.example"]}, {"rule_set": ["cn-ip"], "invert": true}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "logical and with inverted ip rule-set, ip hit",
rule: `{"type": "logical", "mode": "and", "rules": [{"domain_suffix": ["lookup.example"]}, {"rule_set": ["cn-ip"], "invert": true}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "logical or invert, domain hit skips pre-lookup",
rule: `{"type": "logical", "mode": "or", "invert": true, "rules": [{"domain_suffix": ["ads.example"]}, {"rule_set": ["cn-ip"]}], "action": "route", "server": "proxy"}`,
domain: "ads.example",
responseAddress: "8.8.8.8",
},
{
name: "logical or invert, both miss",
rule: `{"type": "logical", "mode": "or", "invert": true, "rules": [{"domain_suffix": ["ads.example"]}, {"rule_set": ["cn-ip"]}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "logical or invert, ip hit",
rule: `{"type": "logical", "mode": "or", "invert": true, "rules": [{"domain_suffix": ["ads.example"]}, {"rule_set": ["cn-ip"]}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "outer domain with ip rule-set invert, domain hit skips pre-lookup",
rule: `{"domain_suffix": ["cn.example"], "rule_set": ["cn-ip"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "cn.example",
responseAddress: "8.8.8.8",
},
{
name: "outer domain with ip rule-set invert, both miss",
rule: `{"domain_suffix": ["cn.example"], "rule_set": ["cn-ip"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "other.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "source ip with ip invert, source hit ip miss",
rule: `{"source_ip_cidr": ["192.168.1.0/24"], "ip_cidr": ["1.1.1.0/24"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "source ip with ip invert, source hit ip hit",
rule: `{"source_ip_cidr": ["192.168.1.0/24"], "ip_cidr": ["1.1.1.0/24"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "source ip with ip invert, source miss",
rule: `{"source_ip_cidr": ["10.99.0.0/16"], "ip_cidr": ["1.1.1.0/24"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "ip rule-set without invert, ip hit",
rule: `{"rule_set": ["cn-ip"], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
expectRouted: true,
},
{
name: "ip rule-set without invert, ip miss",
rule: `{"rule_set": ["cn-ip"], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
},
{
name: "mixed rule-set without invert, ip hit",
rule: `{"rule_set": ["mixed"], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
expectRouted: true,
},
{
name: "mixed rule-set without invert, both miss",
rule: `{"rule_set": ["mixed"], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
},
{
name: "direct ip invert, ip miss",
rule: `{"ip_cidr": ["1.1.1.0/24"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "direct ip invert, ip hit",
rule: `{"ip_cidr": ["1.1.1.0/24"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "nested logical invert, ip miss",
rule: `{"type": "logical", "mode": "or", "invert": true, "rules": [{"type": "logical", "mode": "or", "rules": [{"rule_set": ["cn-ip"]}]}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "nested logical invert, ip hit",
rule: `{"type": "logical", "mode": "or", "invert": true, "rules": [{"type": "logical", "mode": "or", "rules": [{"rule_set": ["cn-ip"]}]}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "nested logical and invert, domain hit ip miss",
rule: `{"type": "logical", "mode": "or", "invert": true, "rules": [{"type": "logical", "mode": "and", "rules": [{"domain_suffix": ["lookup.example"]}, {"rule_set": ["cn-ip"]}]}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "nested logical and invert, domain hit ip hit",
rule: `{"type": "logical", "mode": "or", "invert": true, "rules": [{"type": "logical", "mode": "and", "rules": [{"domain_suffix": ["lookup.example"]}, {"rule_set": ["cn-ip"]}]}], "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
{
name: "match-source rule-set invert, source in set",
rule: `{"rule_set": ["lan-ip"], "rule_set_ip_cidr_match_source": true, "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
},
{
name: "match-source rule-set invert, source not in set",
rule: `{"rule_set": ["other-net"], "rule_set_ip_cidr_match_source": true, "invert": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "match-source rule-set, source in set",
rule: `{"rule_set": ["lan-ip"], "rule_set_ip_cidr_match_source": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "match-source rule-set, source not in set",
rule: `{"rule_set": ["other-net"], "rule_set_ip_cidr_match_source": true, "action": "route", "server": "proxy"}`,
domain: "lookup.example",
responseAddress: "8.8.8.8",
},
{
name: "direct ip with domain rule-set invert, domain hit skips pre-lookup",
rule: `{"ip_cidr": ["1.1.1.0/24"], "rule_set": ["cn-domain"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "cn.example",
responseAddress: "8.8.8.8",
},
{
name: "direct ip with domain rule-set invert, both miss",
rule: `{"ip_cidr": ["1.1.1.0/24"], "rule_set": ["cn-domain"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "other.example",
responseAddress: "8.8.8.8",
expectPreMatch: true,
expectRouted: true,
},
{
name: "direct ip with domain rule-set invert, ip hit",
rule: `{"ip_cidr": ["1.1.1.0/24"], "rule_set": ["cn-domain"], "invert": true, "action": "route", "server": "proxy"}`,
domain: "other.example",
responseAddress: "1.1.1.5",
expectPreMatch: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
rule := addressFilterDNSRule(t, ctx, testCase.rule)
preMatched, routed := addressFilterFlow(rule, testCase.domain, testCase.responseAddress)
require.Equal(t, testCase.expectPreMatch, preMatched, "pre-lookup match")
require.Equal(t, testCase.expectRouted, routed, "routed")
})
}
}
+37 -6
View File
@@ -63,23 +63,43 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
func (r *RuleSetItem) matchWithOuterGroups(metadata *adapter.InboundContext, outerGroups ruleGroupMatch) bool {
outerDone := outerGroups.done()
var (
matched bool
deferredGroups uint8
)
for _, ruleSet := range r.setList {
nestedMetadata := r.nestedMetadata(metadata)
if provider, isProvider := ruleSet.(mergeableRuleProvider); isProvider {
branch := provider.mergeableRule()
if branch != nil {
branchGroups, branchMatched := branch.evaluateForMerge(&nestedMetadata)
if branchMatched && outerGroups.mergeWith(branchGroups).done() {
return true
if branchMatched {
merged := outerGroups.mergeWith(branchGroups)
if merged.done() {
branchDeferredGroups := nestedMetadata.DeferredIPCIDRMatchGroups &^ uint8(merged.satisfied)
if branchDeferredGroups == 0 {
metadata.DeferredIPCIDRMatchGroups &^= uint8(merged.satisfied)
return true
}
matched = true
deferredGroups |= branchDeferredGroups
}
}
continue
}
}
if outerDone && ruleSet.Match(&nestedMetadata) {
return true
if nestedMetadata.DeferredIPCIDRMatchGroups == 0 {
return true
}
matched = true
deferredGroups |= nestedMetadata.DeferredIPCIDRMatchGroups
}
}
return false
if matched {
metadata.DeferredIPCIDRMatchGroups |= deferredGroups
}
return matched
}
func (r *RuleSetItem) nestedMetadata(metadata *adapter.InboundContext) adapter.InboundContext {
@@ -106,14 +126,25 @@ func mergeableRuleIn(rules []adapter.HeadlessRule) *DefaultHeadlessRule {
}
func matchAnyHeadlessRule(rules []adapter.HeadlessRule, metadata *adapter.InboundContext) bool {
var (
matched bool
deferredGroups uint8
)
for _, rule := range rules {
nestedMetadata := *metadata
nestedMetadata.ResetRuleMatchCache()
if rule.Match(&nestedMetadata) {
return true
if nestedMetadata.DeferredIPCIDRMatchGroups == 0 {
return true
}
matched = true
deferredGroups |= nestedMetadata.DeferredIPCIDRMatchGroups
}
}
return false
if matched {
metadata.DeferredIPCIDRMatchGroups |= deferredGroups
}
return matched
}
func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool {