Files
sing-box/route/rule/rule_item_rule_set.go
世界 52e72736ad route: simplify rule_set matching semantics
Since b0c6762bc, every rule inside a referenced rule-set was evaluated
as if merged into the outer rule, which required tracking per-branch
group states and let outer rules and rule-set rules satisfy each
other's grouped conditions in both directions.

Restrict merging to the only designed case: a rule-set containing
exactly one non-inverted default rule is merged into the outer rule as
before. Any other rule-set now matches as an ordinary condition of the
outer rule: it matches when any of its rules matches on its own, and
its rules no longer exchange grouped match state with the outer rule
in either direction. Multiple referenced rule-sets keep OR semantics.

Flat address rule-sets such as generated geosite/geoip sets contain a
single default rule, so their behavior is unchanged. The group-state
set machinery is replaced by a single required/satisfied mask pair.

Also update the route and DNS rule docs.
2026-07-23 14:16:55 +08:00

135 lines
3.3 KiB
Go

package rule
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"
)
var _ RuleItem = (*RuleSetItem)(nil)
type RuleSetItem struct {
router adapter.Router
tagList []string
setList []adapter.RuleSet
ipCidrMatchSource bool
ipCidrAcceptEmpty bool
}
func NewRuleSetItem(router adapter.Router, tagList []string, ipCIDRMatchSource bool, ipCidrAcceptEmpty bool) *RuleSetItem {
return &RuleSetItem{
router: router,
tagList: tagList,
ipCidrMatchSource: ipCIDRMatchSource,
ipCidrAcceptEmpty: ipCidrAcceptEmpty,
}
}
func (r *RuleSetItem) Start() error {
_ = r.Close()
for _, tag := range r.tagList {
ruleSet, loaded := r.router.RuleSet(tag)
if !loaded {
_ = r.Close()
return E.New("rule-set not found: ", tag)
}
ruleSet.IncRef()
r.setList = append(r.setList, ruleSet)
}
return nil
}
func (r *RuleSetItem) Close() error {
for _, ruleSet := range r.setList {
ruleSet.DecRef()
}
clear(r.setList)
r.setList = nil
return nil
}
func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
for _, ruleSet := range r.setList {
nestedMetadata := r.nestedMetadata(metadata)
if ruleSet.Match(&nestedMetadata) {
return true
}
}
return false
}
func (r *RuleSetItem) matchWithOuterGroups(metadata *adapter.InboundContext, outerGroups ruleGroupMatch) bool {
outerDone := outerGroups.done()
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
}
continue
}
}
if outerDone && ruleSet.Match(&nestedMetadata) {
return true
}
}
return false
}
func (r *RuleSetItem) nestedMetadata(metadata *adapter.InboundContext) adapter.InboundContext {
nestedMetadata := *metadata
nestedMetadata.ResetRuleMatchCache()
nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource
nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
return nestedMetadata
}
type mergeableRuleProvider interface {
mergeableRule() *DefaultHeadlessRule
}
func mergeableRuleIn(rules []adapter.HeadlessRule) *DefaultHeadlessRule {
if len(rules) != 1 {
return nil
}
rule, isDefault := rules[0].(*DefaultHeadlessRule)
if !isDefault || rule.invert || rule.ruleSetItem != nil {
return nil
}
return rule
}
func matchAnyHeadlessRule(rules []adapter.HeadlessRule, metadata *adapter.InboundContext) bool {
for _, rule := range rules {
nestedMetadata := *metadata
nestedMetadata.ResetRuleMatchCache()
if rule.Match(&nestedMetadata) {
return true
}
}
return false
}
func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool {
if r.ipCidrMatchSource {
return false
}
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])
} else {
return F.ToString("rule_set=[", strings.Join(r.tagList, " "), "]")
}
}