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 RuleItem 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 { return !r.matchStates(metadata).isEmpty() } 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) requiresSourceAddressMatch(metadata *adapter.InboundContext) bool { return len(r.sourceAddressItems) > 0 || r.destinationIPCIDRMatchesSource(metadata) } func (r *abstractDefaultRule) requiresDestinationAddressMatch(metadata *adapter.InboundContext) bool { return len(r.destinationAddressItems) > 0 || r.destinationIPCIDRMatchesDestination(metadata) } func (r *abstractDefaultRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { return r.matchStatesWithBase(metadata, 0) } func (r *abstractDefaultRule) matchStatesWithBase(metadata *adapter.InboundContext, inheritedBase ruleMatchState) ruleMatchStateSet { if len(r.allItems) == 0 { return emptyRuleMatchState().withBase(inheritedBase) } evaluationBase := inheritedBase if r.invert { evaluationBase = 0 } baseState := evaluationBase if len(r.sourceAddressItems) > 0 { metadata.DidMatch = true if matchAnyItem(r.sourceAddressItems, metadata) { baseState |= ruleMatchSourceAddress } } if r.destinationIPCIDRMatchesSource(metadata) && !baseState.has(ruleMatchSourceAddress) { metadata.DidMatch = true if matchAnyItem(r.destinationIPCIDRItems, metadata) { baseState |= ruleMatchSourceAddress } } else if r.destinationIPCIDRMatchesSource(metadata) { metadata.DidMatch = true } if len(r.sourcePortItems) > 0 { metadata.DidMatch = true if matchAnyItem(r.sourcePortItems, metadata) { baseState |= ruleMatchSourcePort } } if len(r.destinationAddressItems) > 0 { metadata.DidMatch = true if matchAnyItem(r.destinationAddressItems, metadata) { baseState |= ruleMatchDestinationAddress } } if r.destinationIPCIDRMatchesDestination(metadata) && !baseState.has(ruleMatchDestinationAddress) { metadata.DidMatch = true if matchAnyItem(r.destinationIPCIDRItems, metadata) { baseState |= ruleMatchDestinationAddress } } else if r.destinationIPCIDRMatchesDestination(metadata) { metadata.DidMatch = true } if len(r.destinationPortItems) > 0 { metadata.DidMatch = true if matchAnyItem(r.destinationPortItems, metadata) { baseState |= ruleMatchDestinationPort } } for _, item := range r.items { metadata.DidMatch = true if !item.Match(metadata) { return r.invertedFailure(inheritedBase) } } var stateSet ruleMatchStateSet if r.ruleSetItem != nil { metadata.DidMatch = true stateSet = matchRuleItemStatesWithBase(r.ruleSetItem, metadata, baseState) } else { stateSet = singleRuleMatchState(baseState) } stateSet = stateSet.filter(func(state ruleMatchState) bool { if r.requiresSourceAddressMatch(metadata) && !state.has(ruleMatchSourceAddress) { return false } if len(r.sourcePortItems) > 0 && !state.has(ruleMatchSourcePort) { return false } if r.requiresDestinationAddressMatch(metadata) && !state.has(ruleMatchDestinationAddress) { return false } if len(r.destinationPortItems) > 0 && !state.has(ruleMatchDestinationPort) { return false } return true }) if stateSet.isEmpty() { return r.invertedFailure(inheritedBase) } if r.invert { // DNS pre-lookup defers destination address-limit checks until the response phase. if metadata.IgnoreDestinationIPCIDRMatch && stateSet == emptyRuleMatchState() && !metadata.DidMatch && len(r.destinationIPCIDRItems) > 0 { return emptyRuleMatchState().withBase(inheritedBase) } return 0 } return stateSet } func (r *abstractDefaultRule) invertedFailure(base ruleMatchState) ruleMatchStateSet { if r.invert { return emptyRuleMatchState().withBase(base) } return 0 } 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 { return !r.matchStates(metadata).isEmpty() } func (r *abstractLogicalRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet { return r.matchStatesWithBase(metadata, 0) } func (r *abstractLogicalRule) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet { evaluationBase := base if r.invert { evaluationBase = 0 } var stateSet ruleMatchStateSet if r.mode == C.LogicalTypeAnd { stateSet = emptyRuleMatchState().withBase(evaluationBase) for _, rule := range r.rules { nestedMetadata := *metadata nestedMetadata.ResetRuleCache() nestedStateSet := matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase) if nestedStateSet.isEmpty() { if r.invert { return emptyRuleMatchState().withBase(base) } return 0 } stateSet = stateSet.combine(nestedStateSet) } } else { for _, rule := range r.rules { nestedMetadata := *metadata nestedMetadata.ResetRuleCache() stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase)) } if stateSet.isEmpty() { if r.invert { return emptyRuleMatchState().withBase(base) } return 0 } } if r.invert { return 0 } return stateSet } 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 }