route: merge rule_set branches into outer rules
Treat rule_set items as merged branches instead of standalone boolean sub-items. Evaluate each branch inside a referenced rule-set as if it were merged into the outer rule and keep OR semantics between branches. This lets outer grouped fields satisfy matching groups inside a branch without introducing a standalone outer fallback or cross-branch state union. Keep inherited grouped state outside inverted default and logical branches. Negated rule-set branches now evaluate !(...) against their own conditions and only reapply the outer grouped match after negation succeeds, so configs like outer-group && !inner-condition continue to work. Add regression tests for same-group merged matches, cross-group and extra-AND failures, DNS merged-branch behaviour, and inverted merged branches. Update the route and DNS rule docs to clarify that rule-set branches merge into the outer rule while keeping OR semantics between branches.
This commit is contained in:
@@ -209,7 +209,7 @@ icon: material/alert-decagram
|
|||||||
(`source_port` || `source_port_range`) &&
|
(`source_port` || `source_port_range`) &&
|
||||||
`other fields`
|
`other fields`
|
||||||
|
|
||||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
@@ -546,4 +546,4 @@ Match any IP with query response.
|
|||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
Included rules.
|
Included rules.
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ icon: material/alert-decagram
|
|||||||
(`source_port` || `source_port_range`) &&
|
(`source_port` || `source_port_range`) &&
|
||||||
`other fields`
|
`other fields`
|
||||||
|
|
||||||
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
|
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
@@ -550,4 +550,4 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
包括的规则。
|
包括的规则。
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ icon: material/new-box
|
|||||||
(`source_port` || `source_port_range`) &&
|
(`source_port` || `source_port_range`) &&
|
||||||
`other fields`
|
`other fields`
|
||||||
|
|
||||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ icon: material/new-box
|
|||||||
(`source_port` || `source_port_range`) &&
|
(`source_port` || `source_port_range`) &&
|
||||||
`other fields`
|
`other fields`
|
||||||
|
|
||||||
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
|
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
@@ -501,4 +501,4 @@ icon: material/new-box
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
包括的规则。
|
包括的规则。
|
||||||
|
|||||||
@@ -87,22 +87,40 @@ type ruleStateMatcher interface {
|
|||||||
matchStates(metadata *adapter.InboundContext) ruleMatchStateSet
|
matchStates(metadata *adapter.InboundContext) ruleMatchStateSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ruleStateMatcherWithBase interface {
|
||||||
|
matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet
|
||||||
|
}
|
||||||
|
|
||||||
func matchHeadlessRuleStates(rule adapter.HeadlessRule, metadata *adapter.InboundContext) ruleMatchStateSet {
|
func matchHeadlessRuleStates(rule adapter.HeadlessRule, metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return matchHeadlessRuleStatesWithBase(rule, metadata, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if matcher, isStateMatcher := rule.(ruleStateMatcher); isStateMatcher {
|
||||||
return matcher.matchStates(metadata)
|
return matcher.matchStates(metadata).withBase(base)
|
||||||
}
|
}
|
||||||
if rule.Match(metadata) {
|
if rule.Match(metadata) {
|
||||||
return emptyRuleMatchState()
|
return emptyRuleMatchState().withBase(base)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchRuleItemStates(item RuleItem, metadata *adapter.InboundContext) ruleMatchStateSet {
|
func matchRuleItemStates(item RuleItem, metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return matchRuleItemStatesWithBase(item, metadata, 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 {
|
if matcher, isStateMatcher := item.(ruleStateMatcher); isStateMatcher {
|
||||||
return matcher.matchStates(metadata)
|
return matcher.matchStates(metadata).withBase(base)
|
||||||
}
|
}
|
||||||
if item.Match(metadata) {
|
if item.Match(metadata) {
|
||||||
return emptyRuleMatchState()
|
return emptyRuleMatchState().withBase(base)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,10 +72,18 @@ func (r *abstractDefaultRule) requiresDestinationAddressMatch(metadata *adapter.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractDefaultRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
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 {
|
if len(r.allItems) == 0 {
|
||||||
return emptyRuleMatchState()
|
return emptyRuleMatchState().withBase(inheritedBase)
|
||||||
}
|
}
|
||||||
var baseState ruleMatchState
|
evaluationBase := inheritedBase
|
||||||
|
if r.invert {
|
||||||
|
evaluationBase = 0
|
||||||
|
}
|
||||||
|
baseState := evaluationBase
|
||||||
if len(r.sourceAddressItems) > 0 {
|
if len(r.sourceAddressItems) > 0 {
|
||||||
metadata.DidMatch = true
|
metadata.DidMatch = true
|
||||||
if matchAnyItem(r.sourceAddressItems, metadata) {
|
if matchAnyItem(r.sourceAddressItems, metadata) {
|
||||||
@@ -119,17 +127,15 @@ func (r *abstractDefaultRule) matchStates(metadata *adapter.InboundContext) rule
|
|||||||
for _, item := range r.items {
|
for _, item := range r.items {
|
||||||
metadata.DidMatch = true
|
metadata.DidMatch = true
|
||||||
if !item.Match(metadata) {
|
if !item.Match(metadata) {
|
||||||
return r.invertedFailure()
|
return r.invertedFailure(inheritedBase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateSet := singleRuleMatchState(baseState)
|
var stateSet ruleMatchStateSet
|
||||||
if r.ruleSetItem != nil {
|
if r.ruleSetItem != nil {
|
||||||
metadata.DidMatch = true
|
metadata.DidMatch = true
|
||||||
ruleSetStates := matchRuleItemStates(r.ruleSetItem, metadata)
|
stateSet = matchRuleItemStatesWithBase(r.ruleSetItem, metadata, baseState)
|
||||||
if ruleSetStates.isEmpty() {
|
} else {
|
||||||
return r.invertedFailure()
|
stateSet = singleRuleMatchState(baseState)
|
||||||
}
|
|
||||||
stateSet = ruleSetStates.withBase(baseState)
|
|
||||||
}
|
}
|
||||||
stateSet = stateSet.filter(func(state ruleMatchState) bool {
|
stateSet = stateSet.filter(func(state ruleMatchState) bool {
|
||||||
if r.requiresSourceAddressMatch(metadata) && !state.has(ruleMatchSourceAddress) {
|
if r.requiresSourceAddressMatch(metadata) && !state.has(ruleMatchSourceAddress) {
|
||||||
@@ -147,21 +153,21 @@ func (r *abstractDefaultRule) matchStates(metadata *adapter.InboundContext) rule
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
if stateSet.isEmpty() {
|
if stateSet.isEmpty() {
|
||||||
return r.invertedFailure()
|
return r.invertedFailure(inheritedBase)
|
||||||
}
|
}
|
||||||
if r.invert {
|
if r.invert {
|
||||||
// DNS pre-lookup defers destination address-limit checks until the response phase.
|
// DNS pre-lookup defers destination address-limit checks until the response phase.
|
||||||
if metadata.IgnoreDestinationIPCIDRMatch && stateSet == emptyRuleMatchState() && !metadata.DidMatch && len(r.destinationIPCIDRItems) > 0 {
|
if metadata.IgnoreDestinationIPCIDRMatch && stateSet == emptyRuleMatchState() && !metadata.DidMatch && len(r.destinationIPCIDRItems) > 0 {
|
||||||
return emptyRuleMatchState()
|
return emptyRuleMatchState().withBase(inheritedBase)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return stateSet
|
return stateSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractDefaultRule) invertedFailure() ruleMatchStateSet {
|
func (r *abstractDefaultRule) invertedFailure(base ruleMatchState) ruleMatchStateSet {
|
||||||
if r.invert {
|
if r.invert {
|
||||||
return emptyRuleMatchState()
|
return emptyRuleMatchState().withBase(base)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -225,16 +231,24 @@ func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractLogicalRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
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 stateSet ruleMatchStateSet
|
||||||
if r.mode == C.LogicalTypeAnd {
|
if r.mode == C.LogicalTypeAnd {
|
||||||
stateSet = emptyRuleMatchState()
|
stateSet = emptyRuleMatchState().withBase(evaluationBase)
|
||||||
for _, rule := range r.rules {
|
for _, rule := range r.rules {
|
||||||
nestedMetadata := *metadata
|
nestedMetadata := *metadata
|
||||||
nestedMetadata.ResetRuleCache()
|
nestedMetadata.ResetRuleCache()
|
||||||
nestedStateSet := matchHeadlessRuleStates(rule, &nestedMetadata)
|
nestedStateSet := matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase)
|
||||||
if nestedStateSet.isEmpty() {
|
if nestedStateSet.isEmpty() {
|
||||||
if r.invert {
|
if r.invert {
|
||||||
return emptyRuleMatchState()
|
return emptyRuleMatchState().withBase(base)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -244,11 +258,11 @@ func (r *abstractLogicalRule) matchStates(metadata *adapter.InboundContext) rule
|
|||||||
for _, rule := range r.rules {
|
for _, rule := range r.rules {
|
||||||
nestedMetadata := *metadata
|
nestedMetadata := *metadata
|
||||||
nestedMetadata.ResetRuleCache()
|
nestedMetadata.ResetRuleCache()
|
||||||
stateSet = stateSet.merge(matchHeadlessRuleStates(rule, &nestedMetadata))
|
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase))
|
||||||
}
|
}
|
||||||
if stateSet.isEmpty() {
|
if stateSet.isEmpty() {
|
||||||
if r.invert {
|
if r.invert {
|
||||||
return emptyRuleMatchState()
|
return emptyRuleMatchState().withBase(base)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,13 +45,17 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleSetItem) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
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
|
var stateSet ruleMatchStateSet
|
||||||
for _, ruleSet := range r.setList {
|
for _, ruleSet := range r.setList {
|
||||||
nestedMetadata := *metadata
|
nestedMetadata := *metadata
|
||||||
nestedMetadata.ResetRuleMatchCache()
|
nestedMetadata.ResetRuleMatchCache()
|
||||||
nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource
|
nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource
|
||||||
nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
|
nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
|
||||||
stateSet = stateSet.merge(matchHeadlessRuleStates(ruleSet, &nestedMetadata))
|
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(ruleSet, &nestedMetadata, base))
|
||||||
}
|
}
|
||||||
return stateSet
|
return stateSet
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,11 +206,15 @@ func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
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
|
var stateSet ruleMatchStateSet
|
||||||
for _, rule := range s.rules {
|
for _, rule := range s.rules {
|
||||||
nestedMetadata := *metadata
|
nestedMetadata := *metadata
|
||||||
nestedMetadata.ResetRuleMatchCache()
|
nestedMetadata.ResetRuleMatchCache()
|
||||||
stateSet = stateSet.merge(matchHeadlessRuleStates(rule, &nestedMetadata))
|
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||||
}
|
}
|
||||||
return stateSet
|
return stateSet
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,11 +326,15 @@ func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
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
|
var stateSet ruleMatchStateSet
|
||||||
for _, rule := range s.rules {
|
for _, rule := range s.rules {
|
||||||
nestedMetadata := *metadata
|
nestedMetadata := *metadata
|
||||||
nestedMetadata.ResetRuleMatchCache()
|
nestedMetadata.ResetRuleMatchCache()
|
||||||
stateSet = stateSet.merge(matchHeadlessRuleStates(rule, &nestedMetadata))
|
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||||
}
|
}
|
||||||
return stateSet
|
return stateSet
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,95 @@ func TestRouteRuleSetMergeSourceAndPortGroups(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetOuterGroupedStateMergesIntoSameGroup(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
metadata adapter.InboundContext
|
||||||
|
buildOuter func(*testing.T, *abstractDefaultRule)
|
||||||
|
buildInner func(*testing.T, *abstractDefaultRule)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "destination address",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "source address",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addSourceAddressItem(t, rule, []string{"10.0.0.0/8"})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addSourceAddressItem(t, rule, []string{"198.51.100.0/24"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "source port",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addSourcePortItem(rule, []uint16{1000})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addSourcePortItem(rule, []uint16{2000})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "destination port",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationPortItem(rule, []uint16{443})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationPortItem(rule, []uint16{8443})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "destination ip cidr",
|
||||||
|
metadata: func() adapter.InboundContext {
|
||||||
|
metadata := testMetadata("lookup.example")
|
||||||
|
metadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("203.0.113.1")}
|
||||||
|
return metadata
|
||||||
|
}(),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"198.51.100.0/24"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ruleSet := newLocalRuleSetForTest("outer-merge-"+testCase.name, headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
testCase.buildInner(t, rule)
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
testCase.buildOuter(t, rule)
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&testCase.metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRouteRuleSetOtherFieldsStayAnd(t *testing.T) {
|
func TestRouteRuleSetOtherFieldsStayAnd(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
metadata := testMetadata("www.example.com")
|
metadata := testMetadata("www.example.com")
|
||||||
@@ -162,6 +251,34 @@ func TestRouteRuleSetOtherFieldsStayAnd(t *testing.T) {
|
|||||||
require.False(t, rule.Match(&metadata))
|
require.False(t, rule.Match(&metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetMergedBranchKeepsAndConstraints(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("outer group does not bypass inner non grouped condition", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("network-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("outer group does not satisfy different grouped branch", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("different-group", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addSourcePortItem(rule, []uint16{1000})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRouteRuleSetOrSemantics(t *testing.T) {
|
func TestRouteRuleSetOrSemantics(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Run("later ruleset can satisfy outer group", func(t *testing.T) {
|
t.Run("later ruleset can satisfy outer group", func(t *testing.T) {
|
||||||
@@ -271,6 +388,68 @@ func TestRouteRuleSetLogicalSemantics(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetInvertMergedBranchSemantics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("default invert keeps inherited group outside grouped predicate", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("default invert keeps inherited group after negation succeeds", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("invert-network", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("logical invert keeps inherited group outside grouped predicate", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("logical-invert-grouped", headlessLogicalRule(
|
||||||
|
C.LogicalTypeOr,
|
||||||
|
true,
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("logical invert keeps inherited group after negation succeeds", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("logical-invert-network", headlessLogicalRule(
|
||||||
|
C.LogicalTypeOr,
|
||||||
|
true,
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRouteRuleSetNoLeakageRegressions(t *testing.T) {
|
func TestRouteRuleSetNoLeakageRegressions(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Run("same ruleset failed branch does not leak", func(t *testing.T) {
|
t.Run("same ruleset failed branch does not leak", func(t *testing.T) {
|
||||||
@@ -339,6 +518,59 @@ func TestRouteRuleSetRemoteUsesSameSemantics(t *testing.T) {
|
|||||||
|
|
||||||
func TestDNSRuleSetSemantics(t *testing.T) {
|
func TestDNSRuleSetSemantics(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
t.Run("outer destination group merges into matching ruleset branch", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.baidu.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-merged-branch", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"baidu.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("outer destination group does not bypass ruleset non grouped condition", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-network-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("outer destination group stays outside inverted grouped branch", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.baidu.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"baidu.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("outer destination group stays outside inverted logical branch", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-logical-invert-network", headlessLogicalRule(
|
||||||
|
C.LogicalTypeOr,
|
||||||
|
true,
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
t.Run("match address limit merges destination group", func(t *testing.T) {
|
t.Run("match address limit merges destination group", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
metadata := testMetadata("www.example.com")
|
metadata := testMetadata("www.example.com")
|
||||||
|
|||||||
Reference in New Issue
Block a user