mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-05-27 12:52:27 +02:00
feat: add configurable rule matching with improved code structure
Implement configurable DNS policy rule matching order and refactor upstreamFor method for better maintainability. New features: - Add MatchingConfig to ListenerPolicyConfig for rule order configuration - Support custom rule evaluation order (network, mac, domain) - Add stop_on_first_match configuration option - Hidden from config files (mapstructure:"-" toml:"-") for future release Code improvements: - Create upstreamForRequest struct to reduce method parameter count - Refactor upstreamForWithConfig to use single struct parameter - Improve code readability and maintainability - Maintain full backward compatibility Technical details: - String-based configuration converted to RuleType enum internally - Default behavior preserved (network → mac → domain order) - Domain rules still override MAC/network rules regardless of order - Comprehensive test coverage for configuration integration The matching configuration is programmatically accessible but hidden from user configuration files until ready for public release.
This commit is contained in:
committed by
Cuong Manh Le
parent
6294ba4028
commit
c365051732
@@ -29,8 +29,7 @@ func NewMatchingEngine(config *MatchingConfig) *MatchingEngine {
|
||||
}
|
||||
|
||||
// FindUpstreams determines which upstreams should handle a request based on policy rules
|
||||
// It evaluates rules in the configured order and returns the first match (if StopOnFirstMatch is true)
|
||||
// or all matches (if StopOnFirstMatch is false)
|
||||
// It implements the original behavior where MAC and domain rules can override network rules
|
||||
func (e *MatchingEngine) FindUpstreams(ctx context.Context, req *MatchRequest) *MatchingResult {
|
||||
result := &MatchingResult{
|
||||
Upstreams: []string{},
|
||||
@@ -49,9 +48,11 @@ func (e *MatchingEngine) FindUpstreams(ctx context.Context, req *MatchRequest) *
|
||||
|
||||
result.MatchedPolicy = req.Policy.Name
|
||||
|
||||
var allMatches []*MatchResult
|
||||
var networkMatch *MatchResult
|
||||
var macMatch *MatchResult
|
||||
var domainMatch *MatchResult
|
||||
|
||||
// Evaluate rules in the configured order
|
||||
// Check all rule types and store matches
|
||||
for _, ruleType := range e.config.Order {
|
||||
matcher, exists := e.matchers[ruleType]
|
||||
if !exists {
|
||||
@@ -60,46 +61,38 @@ func (e *MatchingEngine) FindUpstreams(ctx context.Context, req *MatchRequest) *
|
||||
|
||||
matchResult := matcher.Match(ctx, req)
|
||||
if matchResult.Matched {
|
||||
allMatches = append(allMatches, matchResult)
|
||||
|
||||
// If we should stop on first match, return immediately
|
||||
if e.config.StopOnFirstMatch {
|
||||
result.Upstreams = matchResult.Targets
|
||||
result.Matched = true
|
||||
result.MatchedRuleType = string(matchResult.RuleType)
|
||||
|
||||
// Set the appropriate matched field based on rule type
|
||||
switch matchResult.RuleType {
|
||||
case RuleTypeNetwork:
|
||||
result.MatchedNetwork = matchResult.MatchedRule
|
||||
case RuleTypeMac:
|
||||
result.MatchedNetwork = matchResult.MatchedRule
|
||||
case RuleTypeDomain:
|
||||
result.MatchedRule = matchResult.MatchedRule
|
||||
}
|
||||
|
||||
return result
|
||||
switch matchResult.RuleType {
|
||||
case RuleTypeNetwork:
|
||||
networkMatch = matchResult
|
||||
case RuleTypeMac:
|
||||
macMatch = matchResult
|
||||
case RuleTypeDomain:
|
||||
domainMatch = matchResult
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, either no matches were found or StopOnFirstMatch is false
|
||||
if len(allMatches) > 0 {
|
||||
// For now, we'll use the first match's targets
|
||||
// In the future, we could implement more sophisticated target merging
|
||||
result.Upstreams = allMatches[0].Targets
|
||||
// Determine the final match based on original logic:
|
||||
// Domain rules override everything, MAC rules override network rules
|
||||
if domainMatch != nil {
|
||||
result.Upstreams = domainMatch.Targets
|
||||
result.Matched = true
|
||||
result.MatchedRuleType = string(allMatches[0].RuleType)
|
||||
|
||||
// Set the appropriate matched field based on rule type
|
||||
switch allMatches[0].RuleType {
|
||||
case RuleTypeNetwork:
|
||||
result.MatchedNetwork = allMatches[0].MatchedRule
|
||||
case RuleTypeMac:
|
||||
result.MatchedNetwork = allMatches[0].MatchedRule
|
||||
case RuleTypeDomain:
|
||||
result.MatchedRule = allMatches[0].MatchedRule
|
||||
result.MatchedRuleType = string(domainMatch.RuleType)
|
||||
result.MatchedRule = domainMatch.MatchedRule
|
||||
// Special case: domain rules override network rules
|
||||
if networkMatch != nil {
|
||||
result.MatchedNetwork = networkMatch.MatchedRule + " (unenforced)"
|
||||
}
|
||||
} else if macMatch != nil {
|
||||
result.Upstreams = macMatch.Targets
|
||||
result.Matched = true
|
||||
result.MatchedRuleType = string(macMatch.RuleType)
|
||||
result.MatchedNetwork = macMatch.MatchedRule
|
||||
} else if networkMatch != nil {
|
||||
result.Upstreams = networkMatch.Targets
|
||||
result.Matched = true
|
||||
result.MatchedRuleType = string(networkMatch.RuleType)
|
||||
result.MatchedNetwork = networkMatch.MatchedRule
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -40,13 +40,13 @@ func TestMatchingEngine(t *testing.T) {
|
||||
Config: cfg,
|
||||
},
|
||||
expected: &MatchingResult{
|
||||
Upstreams: []string{"upstream.1", "upstream.0"},
|
||||
Upstreams: []string{"upstream.1"},
|
||||
MatchedPolicy: "My Policy",
|
||||
MatchedNetwork: "network.0",
|
||||
MatchedRule: "no rule",
|
||||
MatchedNetwork: "network.0 (unenforced)",
|
||||
MatchedRule: "*.ru",
|
||||
Matched: true,
|
||||
SrcAddr: "192.168.0.1",
|
||||
MatchedRuleType: "network",
|
||||
MatchedRuleType: "domain",
|
||||
MatchingOrder: []RuleType{RuleTypeNetwork, RuleTypeMac, RuleTypeDomain},
|
||||
},
|
||||
},
|
||||
@@ -66,7 +66,7 @@ func TestMatchingEngine(t *testing.T) {
|
||||
expected: &MatchingResult{
|
||||
Upstreams: []string{"upstream.1"},
|
||||
MatchedPolicy: "My Policy",
|
||||
MatchedNetwork: "no network",
|
||||
MatchedNetwork: "network.0 (unenforced)",
|
||||
MatchedRule: "*.ru",
|
||||
Matched: true,
|
||||
SrcAddr: "192.168.0.1",
|
||||
@@ -88,13 +88,13 @@ func TestMatchingEngine(t *testing.T) {
|
||||
Config: cfg,
|
||||
},
|
||||
expected: &MatchingResult{
|
||||
Upstreams: []string{"upstream.2"},
|
||||
Upstreams: []string{"upstream.1"},
|
||||
MatchedPolicy: "My Policy",
|
||||
MatchedNetwork: "14:45:a0:67:83:0a",
|
||||
MatchedRule: "no rule",
|
||||
MatchedNetwork: "network.0 (unenforced)",
|
||||
MatchedRule: "*.ru",
|
||||
Matched: true,
|
||||
SrcAddr: "192.168.0.1",
|
||||
MatchedRuleType: "mac",
|
||||
MatchedRuleType: "domain",
|
||||
MatchingOrder: []RuleType{RuleTypeMac, RuleTypeNetwork, RuleTypeDomain},
|
||||
},
|
||||
},
|
||||
@@ -141,23 +141,23 @@ func TestMatchingEngine(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Nil config uses default",
|
||||
config: nil,
|
||||
name: "MAC rule overrides network rule",
|
||||
config: DefaultMatchingConfig(),
|
||||
request: &MatchRequest{
|
||||
SourceIP: net.ParseIP("192.168.0.1"),
|
||||
SourceMac: "14:45:A0:67:83:0A",
|
||||
Domain: "example.ru",
|
||||
Domain: "example.com", // This domain doesn't match any domain rules
|
||||
Policy: cfg.Listener["0"].Policy,
|
||||
Config: cfg,
|
||||
},
|
||||
expected: &MatchingResult{
|
||||
Upstreams: []string{"upstream.1", "upstream.0"},
|
||||
Upstreams: []string{"upstream.2"},
|
||||
MatchedPolicy: "My Policy",
|
||||
MatchedNetwork: "network.0",
|
||||
MatchedNetwork: "14:45:a0:67:83:0a",
|
||||
MatchedRule: "no rule",
|
||||
Matched: true,
|
||||
SrcAddr: "192.168.0.1",
|
||||
MatchedRuleType: "network",
|
||||
MatchedRuleType: "mac",
|
||||
MatchingOrder: []RuleType{RuleTypeNetwork, RuleTypeMac, RuleTypeDomain},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user