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.
This commit is contained in:
@@ -242,7 +242,7 @@ icon: material/alert-decagram
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||
When a rule-set contains only a single default rule without `invert`, its fields are considered merged into the outer rule per the logic above; otherwise, it is matched as an `other field`; different rule-sets always keep OR semantics.
|
||||
|
||||
#### inbound
|
||||
|
||||
|
||||
@@ -240,9 +240,9 @@ icon: material/alert-decagram
|
||||
(`port` || `port_range`) &&
|
||||
(`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
`其他字段`
|
||||
|
||||
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。
|
||||
当规则集仅包含一条默认规则且非 invert 时,其中字段视为按以上规则与外层规则合并;否则,作为一条 `其他字段` 匹配;不同规则集之间始终保持 or。
|
||||
|
||||
#### inbound
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ icon: material/new-box
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||
When a rule-set contains only a single default rule without `invert`, its fields are considered merged into the outer rule per the logic above; otherwise, it is matched as an `other field`; different rule-sets always keep OR semantics.
|
||||
|
||||
#### inbound
|
||||
|
||||
|
||||
@@ -210,9 +210,9 @@ icon: material/new-box
|
||||
(`port` || `port_range`) &&
|
||||
(`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
`其他字段`
|
||||
|
||||
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。
|
||||
当规则集仅包含一条默认规则且非 invert 时,其中字段视为按以上规则与外层规则合并;否则,作为一条 `其他字段` 匹配;不同规则集之间始终保持 or。
|
||||
|
||||
#### inbound
|
||||
|
||||
|
||||
+9
-101
@@ -1,7 +1,5 @@
|
||||
package rule
|
||||
|
||||
import "github.com/sagernet/sing-box/adapter"
|
||||
|
||||
type ruleMatchState uint8
|
||||
|
||||
const (
|
||||
@@ -11,108 +9,18 @@ const (
|
||||
ruleMatchDestinationPort
|
||||
)
|
||||
|
||||
type ruleMatchStateSet uint16
|
||||
|
||||
func singleRuleMatchState(state ruleMatchState) ruleMatchStateSet {
|
||||
return 1 << state
|
||||
type ruleGroupMatch struct {
|
||||
required ruleMatchState
|
||||
satisfied ruleMatchState
|
||||
}
|
||||
|
||||
func emptyRuleMatchState() ruleMatchStateSet {
|
||||
return singleRuleMatchState(0)
|
||||
func (g ruleGroupMatch) done() bool {
|
||||
return g.required&^g.satisfied == 0
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) isEmpty() bool {
|
||||
return s == 0
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) contains(state ruleMatchState) bool {
|
||||
return s&(1<<state) != 0
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) add(state ruleMatchState) ruleMatchStateSet {
|
||||
return s | singleRuleMatchState(state)
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) merge(other ruleMatchStateSet) ruleMatchStateSet {
|
||||
return s | other
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) combine(other ruleMatchStateSet) ruleMatchStateSet {
|
||||
if s.isEmpty() || other.isEmpty() {
|
||||
return 0
|
||||
func (g ruleGroupMatch) mergeWith(other ruleGroupMatch) ruleGroupMatch {
|
||||
return ruleGroupMatch{
|
||||
required: g.required | other.required,
|
||||
satisfied: g.satisfied | other.satisfied,
|
||||
}
|
||||
var combined ruleMatchStateSet
|
||||
for left := range ruleMatchState(16) {
|
||||
if !s.contains(left) {
|
||||
continue
|
||||
}
|
||||
for right := range ruleMatchState(16) {
|
||||
if !other.contains(right) {
|
||||
continue
|
||||
}
|
||||
combined = combined.add(left | right)
|
||||
}
|
||||
}
|
||||
return combined
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) withBase(base ruleMatchState) ruleMatchStateSet {
|
||||
if s.isEmpty() {
|
||||
return 0
|
||||
}
|
||||
var withBase ruleMatchStateSet
|
||||
for state := range ruleMatchState(16) {
|
||||
if !s.contains(state) {
|
||||
continue
|
||||
}
|
||||
withBase = withBase.add(state | base)
|
||||
}
|
||||
return withBase
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) filter(allowed func(ruleMatchState) bool) ruleMatchStateSet {
|
||||
var filtered ruleMatchStateSet
|
||||
for state := range ruleMatchState(16) {
|
||||
if !s.contains(state) {
|
||||
continue
|
||||
}
|
||||
if allowed(state) {
|
||||
filtered = filtered.add(state)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
type ruleStateMatcher interface {
|
||||
matchStates(metadata *adapter.InboundContext) ruleMatchStateSet
|
||||
}
|
||||
|
||||
type ruleStateMatcherWithBase interface {
|
||||
matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet
|
||||
}
|
||||
|
||||
func matchHeadlessRuleStatesWithBase(rule adapter.HeadlessRule, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
if matcher, isStateMatcher := rule.(ruleStateMatcherWithBase); isStateMatcher {
|
||||
return matcher.matchStatesWithBase(metadata, base)
|
||||
}
|
||||
if matcher, isStateMatcher := rule.(ruleStateMatcher); isStateMatcher {
|
||||
return matcher.matchStates(metadata).withBase(base)
|
||||
}
|
||||
if rule.Match(metadata) {
|
||||
return emptyRuleMatchState().withBase(base)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func matchRuleItemStatesWithBase(item RuleItem, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
if matcher, isStateMatcher := item.(ruleStateMatcherWithBase); isStateMatcher {
|
||||
return matcher.matchStatesWithBase(metadata, base)
|
||||
}
|
||||
if matcher, isStateMatcher := item.(ruleStateMatcher); isStateMatcher {
|
||||
return matcher.matchStates(metadata).withBase(base)
|
||||
}
|
||||
if item.Match(metadata) {
|
||||
return emptyRuleMatchState().withBase(base)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
+67
-111
@@ -18,7 +18,7 @@ type abstractDefaultRule struct {
|
||||
destinationIPCIDRItems []RuleItem
|
||||
destinationPortItems []RuleItem
|
||||
allItems []RuleItem
|
||||
ruleSetItem RuleItem
|
||||
ruleSetItem *RuleSetItem
|
||||
invert bool
|
||||
action adapter.RuleAction
|
||||
}
|
||||
@@ -52,7 +52,43 @@ func (r *abstractDefaultRule) Close() error {
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||
return !r.matchStates(metadata).isEmpty()
|
||||
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 {
|
||||
@@ -63,112 +99,51 @@ func (r *abstractDefaultRule) destinationIPCIDRMatchesDestination(metadata *adap
|
||||
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
|
||||
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) {
|
||||
baseState |= ruleMatchSourceAddress
|
||||
groups.satisfied |= ruleMatchSourceAddress
|
||||
}
|
||||
}
|
||||
if r.destinationIPCIDRMatchesSource(metadata) && !baseState.has(ruleMatchSourceAddress) {
|
||||
if r.destinationIPCIDRMatchesSource(metadata) {
|
||||
metadata.DidMatch = true
|
||||
if matchAnyItem(r.destinationIPCIDRItems, metadata) {
|
||||
baseState |= ruleMatchSourceAddress
|
||||
groups.required |= ruleMatchSourceAddress
|
||||
if !groups.satisfied.has(ruleMatchSourceAddress) && matchAnyItem(r.destinationIPCIDRItems, metadata) {
|
||||
groups.satisfied |= ruleMatchSourceAddress
|
||||
}
|
||||
} else if r.destinationIPCIDRMatchesSource(metadata) {
|
||||
metadata.DidMatch = true
|
||||
}
|
||||
if len(r.sourcePortItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
groups.required |= ruleMatchSourcePort
|
||||
if matchAnyItem(r.sourcePortItems, metadata) {
|
||||
baseState |= ruleMatchSourcePort
|
||||
groups.satisfied |= ruleMatchSourcePort
|
||||
}
|
||||
}
|
||||
if len(r.destinationAddressItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
groups.required |= ruleMatchDestinationAddress
|
||||
if matchAnyItem(r.destinationAddressItems, metadata) {
|
||||
baseState |= ruleMatchDestinationAddress
|
||||
groups.satisfied |= ruleMatchDestinationAddress
|
||||
}
|
||||
}
|
||||
if r.destinationIPCIDRMatchesDestination(metadata) && !baseState.has(ruleMatchDestinationAddress) {
|
||||
if r.destinationIPCIDRMatchesDestination(metadata) {
|
||||
metadata.DidMatch = true
|
||||
if matchAnyItem(r.destinationIPCIDRItems, metadata) {
|
||||
baseState |= ruleMatchDestinationAddress
|
||||
groups.required |= ruleMatchDestinationAddress
|
||||
if !groups.satisfied.has(ruleMatchDestinationAddress) && matchAnyItem(r.destinationIPCIDRItems, metadata) {
|
||||
groups.satisfied |= ruleMatchDestinationAddress
|
||||
}
|
||||
} else if r.destinationIPCIDRMatchesDestination(metadata) {
|
||||
metadata.DidMatch = true
|
||||
}
|
||||
if len(r.destinationPortItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
groups.required |= ruleMatchDestinationPort
|
||||
if matchAnyItem(r.destinationPortItems, metadata) {
|
||||
baseState |= ruleMatchDestinationPort
|
||||
groups.satisfied |= 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 {
|
||||
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
|
||||
return groups
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) Action() adapter.RuleAction {
|
||||
@@ -226,50 +201,31 @@ func (r *abstractLogicalRule) Close() error {
|
||||
}
|
||||
|
||||
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
|
||||
var matched bool
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
stateSet = emptyRuleMatchState().withBase(evaluationBase)
|
||||
matched = true
|
||||
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
|
||||
if !rule.Match(&nestedMetadata) {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
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)
|
||||
if rule.Match(&nestedMetadata) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
if r.invert {
|
||||
return 0
|
||||
return !matched
|
||||
}
|
||||
return stateSet
|
||||
return matched
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) Action() adapter.RuleAction {
|
||||
|
||||
@@ -47,10 +47,6 @@ type DefaultRule struct {
|
||||
abstractDefaultRule
|
||||
}
|
||||
|
||||
func (r *DefaultRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractDefaultRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
type RuleItem interface {
|
||||
Match(metadata *adapter.InboundContext) bool
|
||||
String() string
|
||||
@@ -309,10 +305,6 @@ type LogicalRule struct {
|
||||
abstractLogicalRule
|
||||
}
|
||||
|
||||
func (r *LogicalRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractLogicalRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func NewLogicalRule(ctx context.Context, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
||||
action, err := NewRuleAction(ctx, logger, options.RuleAction)
|
||||
if err != nil {
|
||||
|
||||
+26
-47
@@ -72,10 +72,6 @@ type DefaultDNSRule struct {
|
||||
matchResponse bool
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractDefaultRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule, legacyDNSMode bool) (*DefaultDNSRule, error) {
|
||||
rule := &DefaultDNSRule{
|
||||
abstractDefaultRule: abstractDefaultRule{
|
||||
@@ -365,17 +361,11 @@ func (r *DefaultDNSRule) WithAddressLimit() bool {
|
||||
if len(r.destinationIPCIDRItems) > 0 {
|
||||
return true
|
||||
}
|
||||
if r.ruleSetItem != nil {
|
||||
ruleSet, isRuleSet := r.ruleSetItem.(*RuleSetItem)
|
||||
if isRuleSet && ruleSet.ContainsDestinationIPCIDRRule() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return r.ruleSetItem != nil && r.ruleSetItem.ContainsDestinationIPCIDRRule()
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
|
||||
return !r.matchStatesForMatch(metadata).isEmpty()
|
||||
return r.matchForMatch(metadata)
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool {
|
||||
@@ -384,26 +374,26 @@ func (r *DefaultDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool {
|
||||
}
|
||||
metadata.IgnoreDestinationIPCIDRMatch = true
|
||||
defer func() { metadata.IgnoreDestinationIPCIDRMatch = false }()
|
||||
return !r.abstractDefaultRule.matchStates(metadata).isEmpty()
|
||||
return r.abstractDefaultRule.Match(metadata)
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) matchStatesForMatch(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
func (r *DefaultDNSRule) matchForMatch(metadata *adapter.InboundContext) bool {
|
||||
if r.matchResponse {
|
||||
if metadata.DNSResponse == nil {
|
||||
return r.abstractDefaultRule.invertedFailure(0)
|
||||
return r.invert
|
||||
}
|
||||
matchMetadata := *metadata
|
||||
matchMetadata.DestinationAddressMatchFromResponse = true
|
||||
return r.abstractDefaultRule.matchStates(&matchMetadata)
|
||||
return r.abstractDefaultRule.Match(&matchMetadata)
|
||||
}
|
||||
return r.abstractDefaultRule.matchStates(metadata)
|
||||
return r.abstractDefaultRule.Match(metadata)
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext, response *dns.Msg) bool {
|
||||
matchMetadata := *metadata
|
||||
matchMetadata.DNSResponse = response
|
||||
matchMetadata.DestinationAddressMatchFromResponse = true
|
||||
return !r.abstractDefaultRule.matchStates(&matchMetadata).isEmpty()
|
||||
return r.abstractDefaultRule.Match(&matchMetadata)
|
||||
}
|
||||
|
||||
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
||||
@@ -412,54 +402,43 @@ type LogicalDNSRule struct {
|
||||
abstractLogicalRule
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractLogicalRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func matchDNSHeadlessRuleStatesForMatch(rule adapter.HeadlessRule, metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
func matchDNSHeadlessRuleForMatch(rule adapter.HeadlessRule, metadata *adapter.InboundContext) bool {
|
||||
switch typedRule := rule.(type) {
|
||||
case *DefaultDNSRule:
|
||||
return typedRule.matchStatesForMatch(metadata)
|
||||
return typedRule.matchForMatch(metadata)
|
||||
case *LogicalDNSRule:
|
||||
return typedRule.matchStatesForMatch(metadata)
|
||||
return typedRule.matchForMatch(metadata)
|
||||
default:
|
||||
return matchHeadlessRuleStatesWithBase(typedRule, metadata, 0)
|
||||
return typedRule.Match(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) matchStatesForMatch(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
var stateSet ruleMatchStateSet
|
||||
func (r *LogicalDNSRule) matchForMatch(metadata *adapter.InboundContext) bool {
|
||||
var matched bool
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
stateSet = emptyRuleMatchState()
|
||||
matched = true
|
||||
for _, rule := range r.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleCache()
|
||||
nestedStateSet := matchDNSHeadlessRuleStatesForMatch(rule, &nestedMetadata)
|
||||
if nestedStateSet.isEmpty() {
|
||||
if r.invert {
|
||||
return emptyRuleMatchState()
|
||||
}
|
||||
return 0
|
||||
if !matchDNSHeadlessRuleForMatch(rule, &nestedMetadata) {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
stateSet = stateSet.combine(nestedStateSet)
|
||||
}
|
||||
} else {
|
||||
for _, rule := range r.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleCache()
|
||||
stateSet = stateSet.merge(matchDNSHeadlessRuleStatesForMatch(rule, &nestedMetadata))
|
||||
}
|
||||
if stateSet.isEmpty() {
|
||||
if r.invert {
|
||||
return emptyRuleMatchState()
|
||||
if matchDNSHeadlessRuleForMatch(rule, &nestedMetadata) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
if r.invert {
|
||||
return 0
|
||||
return !matched
|
||||
}
|
||||
return stateSet
|
||||
return matched
|
||||
}
|
||||
|
||||
func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule, legacyDNSMode bool) (*LogicalDNSRule, error) {
|
||||
@@ -513,18 +492,18 @@ func (r *LogicalDNSRule) WithAddressLimit() bool {
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
|
||||
return !r.matchStatesForMatch(metadata).isEmpty()
|
||||
return r.matchForMatch(metadata)
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool {
|
||||
metadata.IgnoreDestinationIPCIDRMatch = true
|
||||
defer func() { metadata.IgnoreDestinationIPCIDRMatch = false }()
|
||||
return !r.abstractLogicalRule.matchStates(metadata).isEmpty()
|
||||
return r.abstractLogicalRule.Match(metadata)
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext, response *dns.Msg) bool {
|
||||
matchMetadata := *metadata
|
||||
matchMetadata.DNSResponse = response
|
||||
matchMetadata.DestinationAddressMatchFromResponse = true
|
||||
return !r.abstractLogicalRule.matchStates(&matchMetadata).isEmpty()
|
||||
return r.abstractLogicalRule.Match(&matchMetadata)
|
||||
}
|
||||
|
||||
@@ -34,10 +34,6 @@ type DefaultHeadlessRule struct {
|
||||
abstractDefaultRule
|
||||
}
|
||||
|
||||
func (r *DefaultHeadlessRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractDefaultRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) {
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
rule := &DefaultHeadlessRule{
|
||||
@@ -216,10 +212,6 @@ type LogicalHeadlessRule struct {
|
||||
abstractLogicalRule
|
||||
}
|
||||
|
||||
func (r *LogicalHeadlessRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractLogicalRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func NewLogicalHeadlessRule(ctx context.Context, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) {
|
||||
r := &LogicalHeadlessRule{
|
||||
abstractLogicalRule{
|
||||
|
||||
@@ -52,23 +52,68 @@ func (r *RuleSetItem) Close() error {
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
|
||||
return !r.matchStates(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.matchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
var stateSet ruleMatchStateSet
|
||||
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()
|
||||
nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource
|
||||
nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
|
||||
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(ruleSet, &nestedMetadata, base))
|
||||
if rule.Match(&nestedMetadata) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return stateSet
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool {
|
||||
|
||||
@@ -199,19 +199,9 @@ func (s *LocalRuleSet) Close() error {
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||
return !s.matchStates(metadata).isEmpty()
|
||||
return matchAnyHeadlessRule(s.rules, metadata)
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return s.matchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
var stateSet ruleMatchStateSet
|
||||
for _, rule := range s.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleMatchCache()
|
||||
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||
}
|
||||
return stateSet
|
||||
func (s *LocalRuleSet) mergeableRule() *DefaultHeadlessRule {
|
||||
return mergeableRuleIn(s.rules)
|
||||
}
|
||||
|
||||
@@ -297,19 +297,9 @@ func (s *RemoteRuleSet) Close() error {
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||
return !s.matchStates(metadata).isEmpty()
|
||||
return matchAnyHeadlessRule(s.rules, metadata)
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return s.matchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
var stateSet ruleMatchStateSet
|
||||
for _, rule := range s.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleMatchCache()
|
||||
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||
}
|
||||
return stateSet
|
||||
func (s *RemoteRuleSet) mergeableRule() *DefaultHeadlessRule {
|
||||
return mergeableRuleIn(s.rules)
|
||||
}
|
||||
|
||||
@@ -295,11 +295,11 @@ func TestRouteRuleSetOrSemantics(t *testing.T) {
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("later rule in same set can satisfy outer group", func(t *testing.T) {
|
||||
t.Run("multi rule set does not satisfy outer group", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest(
|
||||
"rule-set-or",
|
||||
"rule-set-and",
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
|
||||
}),
|
||||
@@ -311,7 +311,7 @@ func TestRouteRuleSetOrSemantics(t *testing.T) {
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
require.False(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("cross ruleset union is not allowed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -333,7 +333,7 @@ func TestRouteRuleSetOrSemantics(t *testing.T) {
|
||||
|
||||
func TestRouteRuleSetLogicalSemantics(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("logical or keeps all successful branch states", func(t *testing.T) {
|
||||
t.Run("logical set does not satisfy outer group", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("logical-or", headlessLogicalRule(
|
||||
@@ -350,9 +350,9 @@ func TestRouteRuleSetLogicalSemantics(t *testing.T) {
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
require.False(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("logical and unions child states", func(t *testing.T) {
|
||||
t.Run("logical branch does not lift outer group requirements", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("logical-and", headlessLogicalRule(
|
||||
@@ -370,9 +370,28 @@ func TestRouteRuleSetLogicalSemantics(t *testing.T) {
|
||||
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||
addSourcePortItem(rule, []uint16{2000})
|
||||
})
|
||||
require.False(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("logical branch matches on its own conditions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("logical-and-self", headlessLogicalRule(
|
||||
C.LogicalTypeAnd,
|
||||
false,
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||
}),
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addSourcePortItem(rule, []uint16{1000})
|
||||
}),
|
||||
))
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
addSourcePortItem(rule, []uint16{1000})
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("invert success does not contribute positive state", func(t *testing.T) {
|
||||
t.Run("inverted set does not satisfy outer group", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("invert", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
@@ -387,9 +406,240 @@ func TestRouteRuleSetLogicalSemantics(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteRuleSetInvertMergedBranchSemantics(t *testing.T) {
|
||||
func TestRuleSetShapeBoundary(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("default invert keeps inherited group outside grouped predicate", func(t *testing.T) {
|
||||
buildOuter := func(ruleSet *LocalRuleSet) *DefaultRule {
|
||||
return routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, []string{"extra.example.org"}, nil)
|
||||
addDestinationPortItem(rule, []uint16{443})
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
})
|
||||
}
|
||||
singleShape := buildOuter(newLocalRuleSetForTest("flat-single", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"a.example.com", "b.example.com"})
|
||||
})))
|
||||
multiShape := buildOuter(newLocalRuleSetForTest(
|
||||
"flat-multi",
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"a.example.com"})
|
||||
}),
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"b.example.com"})
|
||||
}),
|
||||
))
|
||||
queries := []struct {
|
||||
name string
|
||||
domain string
|
||||
port uint16
|
||||
singleResult bool
|
||||
multiResult bool
|
||||
}{
|
||||
{"in set", "www.b.example.com", 443, true, false},
|
||||
{"outer own domain", "extra.example.org", 443, true, false},
|
||||
{"neither", "other.example.net", 443, false, false},
|
||||
{"in set with wrong port", "www.b.example.com", 80, false, false},
|
||||
}
|
||||
for _, query := range queries {
|
||||
t.Run(query.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
singleMetadata := testMetadata(query.domain)
|
||||
singleMetadata.Destination.Port = query.port
|
||||
multiMetadata := testMetadata(query.domain)
|
||||
multiMetadata.Destination.Port = query.port
|
||||
require.Equal(t, query.singleResult, singleShape.Match(&singleMetadata))
|
||||
require.Equal(t, query.multiResult, multiShape.Match(&multiMetadata))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleSetCaseBoundary(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("single cross group rule merges into outer", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("port-single", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationPortRangeItem(t, rule, []string{"400:500"})
|
||||
}))
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addDestinationPortItem(rule, []uint16{8080})
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("multi rule set keeps outer group absolute", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest(
|
||||
"port-multi",
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationPortRangeItem(t, rule, []string{"400:500"})
|
||||
}),
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"never.example"})
|
||||
}),
|
||||
)
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addDestinationPortItem(rule, []uint16{8080})
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
})
|
||||
require.False(t, rule.Match(&metadata))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRuleSetLogicalBranchSelfContained(t *testing.T) {
|
||||
t.Parallel()
|
||||
newRuleSet := func() *LocalRuleSet {
|
||||
return newLocalRuleSetForTest("and-branch", headlessLogicalRule(
|
||||
C.LogicalTypeAnd,
|
||||
false,
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"b.example.com"})
|
||||
}),
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationPortRangeItem(t, rule, []string{"800:900"})
|
||||
}),
|
||||
))
|
||||
}
|
||||
t.Run("branch matches only on its own conditions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{newRuleSet()}})
|
||||
})
|
||||
matchedMetadata := testMetadata("www.b.example.com")
|
||||
matchedMetadata.Destination.Port = 850
|
||||
require.True(t, rule.Match(&matchedMetadata))
|
||||
unmatchedMetadata := testMetadata("www.b.example.com")
|
||||
require.False(t, rule.Match(&unmatchedMetadata))
|
||||
})
|
||||
t.Run("outer condition and set are both required", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
matchedRule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addSourcePortItem(rule, []uint16{1000})
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{newRuleSet()}})
|
||||
})
|
||||
metadata := testMetadata("www.b.example.com")
|
||||
metadata.Destination.Port = 850
|
||||
require.True(t, matchedRule.Match(&metadata))
|
||||
setMissMetadata := testMetadata("other.example.net")
|
||||
setMissMetadata.Destination.Port = 850
|
||||
require.False(t, matchedRule.Match(&setMissMetadata))
|
||||
outerMissRule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addSourcePortItem(rule, []uint16{2000})
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{newRuleSet()}})
|
||||
})
|
||||
outerMissMetadata := testMetadata("www.b.example.com")
|
||||
outerMissMetadata.Destination.Port = 850
|
||||
require.False(t, outerMissRule.Match(&outerMissMetadata))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRuleSetMixedReference(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("single rule set merges as or", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ruleSet := newLocalRuleSetForTest("mixed-single", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"set.example.com"})
|
||||
}))
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, []string{"extra.example.org"}, nil)
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
})
|
||||
extraMetadata := testMetadata("extra.example.org")
|
||||
require.True(t, rule.Match(&extraMetadata))
|
||||
setMetadata := testMetadata("www.set.example.com")
|
||||
require.True(t, rule.Match(&setMetadata))
|
||||
otherMetadata := testMetadata("other.example.net")
|
||||
require.False(t, rule.Match(&otherMetadata))
|
||||
})
|
||||
t.Run("multi rule set is an independent condition", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ruleSet := newLocalRuleSetForTest(
|
||||
"mixed-multi",
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"set.example.com"})
|
||||
}),
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"set2.example.com"})
|
||||
}),
|
||||
)
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addDestinationPortItem(rule, []uint16{443})
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
})
|
||||
matchedMetadata := testMetadata("www.set2.example.com")
|
||||
require.True(t, rule.Match(&matchedMetadata))
|
||||
portMissMetadata := testMetadata("www.set2.example.com")
|
||||
portMissMetadata.Destination.Port = 80
|
||||
require.False(t, rule.Match(&portMissMetadata))
|
||||
setMissMetadata := testMetadata("other.example.net")
|
||||
require.False(t, rule.Match(&setMissMetadata))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRuleSetStandaloneReference(t *testing.T) {
|
||||
t.Parallel()
|
||||
ruleSet := newLocalRuleSetForTest(
|
||||
"standalone",
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"a.example.net"})
|
||||
}),
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationPortRangeItem(t, rule, []string{"400:500"})
|
||||
}),
|
||||
)
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
})
|
||||
domainMetadata := testMetadata("www.a.example.net")
|
||||
domainMetadata.Destination.Port = 80
|
||||
require.True(t, rule.Match(&domainMetadata))
|
||||
portMetadata := testMetadata("other.example.org")
|
||||
require.True(t, rule.Match(&portMetadata))
|
||||
missMetadata := testMetadata("other.example.org")
|
||||
missMetadata.Destination.Port = 80
|
||||
require.False(t, rule.Match(&missMetadata))
|
||||
}
|
||||
|
||||
func TestRuleSetInvertedSingleRuleIsBoolean(t *testing.T) {
|
||||
t.Parallel()
|
||||
ruleSet := newLocalRuleSetForTest("inverted-single", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
rule.invert = true
|
||||
addDestinationAddressItem(t, rule, nil, []string{"blocked.example"})
|
||||
}))
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
})
|
||||
allowedMetadata := testMetadata("good.example.org")
|
||||
require.True(t, rule.Match(&allowedMetadata))
|
||||
blockedMetadata := testMetadata("www.blocked.example")
|
||||
require.False(t, rule.Match(&blockedMetadata))
|
||||
}
|
||||
|
||||
func TestRuleSetEmptySetNeverMatches(t *testing.T) {
|
||||
t.Parallel()
|
||||
emptySet := newLocalRuleSetForTest("empty")
|
||||
t.Run("outer own group does not bypass the set", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{emptySet}})
|
||||
})
|
||||
require.False(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("standalone reference does not match", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{emptySet}})
|
||||
})
|
||||
require.False(t, rule.Match(&metadata))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteRuleSetInvertBranchSemantics(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("inverted default branch acts as boolean term", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
@@ -402,7 +652,7 @@ func TestRouteRuleSetInvertMergedBranchSemantics(t *testing.T) {
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("default invert keeps inherited group after negation succeeds", func(t *testing.T) {
|
||||
t.Run("inverted default branch with non grouped condition acts as boolean term", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("invert-network", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
@@ -415,7 +665,7 @@ func TestRouteRuleSetInvertMergedBranchSemantics(t *testing.T) {
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("logical invert keeps inherited group outside grouped predicate", func(t *testing.T) {
|
||||
t.Run("inverted logical branch acts as boolean term", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("logical-invert-grouped", headlessLogicalRule(
|
||||
@@ -431,7 +681,7 @@ func TestRouteRuleSetInvertMergedBranchSemantics(t *testing.T) {
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("logical invert keeps inherited group after negation succeeds", func(t *testing.T) {
|
||||
t.Run("inverted logical branch with non grouped condition acts as boolean term", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("logical-invert-network", headlessLogicalRule(
|
||||
@@ -498,21 +748,26 @@ func TestDefaultRuleDoesNotReuseGroupedMatchCacheAcrossEvaluations(t *testing.T)
|
||||
|
||||
func TestRouteRuleSetRemoteUsesSameSemantics(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newRemoteRuleSetForTest(
|
||||
"remote",
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
|
||||
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||
}),
|
||||
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||
}),
|
||||
)
|
||||
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
standaloneRule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
})
|
||||
standaloneMetadata := testMetadata("www.example.com")
|
||||
require.True(t, standaloneRule.Match(&standaloneMetadata))
|
||||
combinedRule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
combinedMetadata := testMetadata("www.example.com")
|
||||
require.False(t, combinedRule.Match(&combinedMetadata))
|
||||
}
|
||||
|
||||
func TestDNSRuleSetSemantics(t *testing.T) {
|
||||
@@ -541,7 +796,7 @@ func TestDNSRuleSetSemantics(t *testing.T) {
|
||||
})
|
||||
require.False(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("outer destination group stays outside inverted grouped branch", func(t *testing.T) {
|
||||
t.Run("inverted branch acts as boolean term", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.baidu.com")
|
||||
ruleSet := newLocalRuleSetForTest("dns-invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||
@@ -554,7 +809,7 @@ func TestDNSRuleSetSemantics(t *testing.T) {
|
||||
})
|
||||
require.True(t, rule.Match(&metadata))
|
||||
})
|
||||
t.Run("outer destination group stays outside inverted logical branch", func(t *testing.T) {
|
||||
t.Run("inverted logical branch acts as boolean term", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
metadata := testMetadata("www.example.com")
|
||||
ruleSet := newLocalRuleSetForTest("dns-logical-invert-network", headlessLogicalRule(
|
||||
|
||||
Reference in New Issue
Block a user