Files
sing-box/route/rule/rule_abstract.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

259 lines
6.3 KiB
Go

package rule
import (
"io"
"strings"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format"
)
type abstractDefaultRule struct {
items []RuleItem
sourceAddressItems []RuleItem
sourcePortItems []RuleItem
destinationAddressItems []RuleItem
destinationIPCIDRItems []RuleItem
destinationPortItems []RuleItem
allItems []RuleItem
ruleSetItem *RuleSetItem
invert bool
action adapter.RuleAction
}
func (r *abstractDefaultRule) Type() string {
return C.RuleTypeDefault
}
func (r *abstractDefaultRule) Start() error {
for _, item := range r.allItems {
if starter, isStarter := item.(interface {
Start() error
}); isStarter {
err := starter.Start()
if err != nil {
return err
}
}
}
return nil
}
func (r *abstractDefaultRule) Close() error {
for _, item := range r.allItems {
err := common.Close(item)
if err != nil {
return err
}
}
return nil
}
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
if len(r.allItems) == 0 {
return true
}
matched := r.matchInner(metadata)
if r.invert {
if matched && metadata.IgnoreDestinationIPCIDRMatch && !metadata.DidMatch && len(r.destinationIPCIDRItems) > 0 {
return true
}
return !matched
}
return matched
}
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
}
}
if r.ruleSetItem != nil {
metadata.DidMatch = true
return r.ruleSetItem.matchWithOuterGroups(metadata, groups)
}
return groups.done()
}
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
}
}
return groups, true
}
func (r *abstractDefaultRule) destinationIPCIDRMatchesSource(metadata *adapter.InboundContext) bool {
return !metadata.IgnoreDestinationIPCIDRMatch && metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
}
func (r *abstractDefaultRule) destinationIPCIDRMatchesDestination(metadata *adapter.InboundContext) bool {
return !metadata.IgnoreDestinationIPCIDRMatch && !metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
}
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
}
}
return groups
}
func (r *abstractDefaultRule) Action() adapter.RuleAction {
return r.action
}
func (r *abstractDefaultRule) String() string {
if !r.invert {
return strings.Join(F.MapToString(r.allItems), " ")
} else {
return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")"
}
}
type abstractLogicalRule struct {
rules []adapter.HeadlessRule
mode string
invert bool
action adapter.RuleAction
}
func (r *abstractLogicalRule) Type() string {
return C.RuleTypeLogical
}
func (r *abstractLogicalRule) Start() error {
for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (interface {
Start() error
}, bool,
) {
rule, loaded := it.(interface {
Start() error
})
return rule, loaded
}) {
err := rule.Start()
if err != nil {
return err
}
}
return nil
}
func (r *abstractLogicalRule) Close() error {
for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (io.Closer, bool) {
rule, loaded := it.(io.Closer)
return rule, loaded
}) {
err := rule.Close()
if err != nil {
return err
}
}
return nil
}
func (r *abstractLogicalRule) Match(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 (r *abstractLogicalRule) Action() adapter.RuleAction {
return r.action
}
func (r *abstractLogicalRule) String() string {
var op string
switch r.mode {
case C.LogicalTypeAnd:
op = "&&"
case C.LogicalTypeOr:
op = "||"
}
if !r.invert {
return strings.Join(F.MapToString(r.rules), " "+op+" ")
} else {
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
}
}
func matchAnyItem(items []RuleItem, metadata *adapter.InboundContext) bool {
return common.Any(items, func(it RuleItem) bool {
return it.Match(metadata)
})
}
func (s ruleMatchState) has(target ruleMatchState) bool {
return s&target != 0
}