Fix evaluate response-match validation

This commit is contained in:
世界
2026-04-01 18:50:19 +08:00
parent 78348540a9
commit 40e40ea7a6
4 changed files with 151 additions and 12 deletions

View File

@@ -1062,14 +1062,17 @@ func referencedDNSRuleSetTags(rules []option.DNSRule) []string {
}
func validateLegacyDNSModeDisabledRules(rules []option.DNSRule) error {
var seenEvaluate bool
for i, rule := range rules {
consumesResponse, err := validateLegacyDNSModeDisabledRuleTree(rule)
requiresPriorEvaluate, err := validateLegacyDNSModeDisabledRuleTree(rule)
if err != nil {
return E.Cause(err, "validate dns rule[", i, "]")
}
action := dnsRuleActionType(rule)
if action == C.RuleActionTypeEvaluate && consumesResponse {
return E.New("dns rule[", i, "]: evaluate action cannot be used with match_response in the same rule")
if requiresPriorEvaluate && !seenEvaluate {
return E.New("dns rule[", i, "]: response-based matching requires a preceding evaluate action")
}
if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate {
seenEvaluate = true
}
}
return nil
@@ -1080,15 +1083,15 @@ func validateLegacyDNSModeDisabledRuleTree(rule option.DNSRule) (bool, error) {
case "", C.RuleTypeDefault:
return validateLegacyDNSModeDisabledDefaultRule(rule.DefaultOptions)
case C.RuleTypeLogical:
var consumesResponse bool
var requiresPriorEvaluate bool
for i, subRule := range rule.LogicalOptions.Rules {
subConsumesResponse, err := validateLegacyDNSModeDisabledRuleTree(subRule)
subRequiresPriorEvaluate, err := validateLegacyDNSModeDisabledRuleTree(subRule)
if err != nil {
return false, E.Cause(err, "sub rule[", i, "]")
}
consumesResponse = consumesResponse || subConsumesResponse
requiresPriorEvaluate = requiresPriorEvaluate || subRequiresPriorEvaluate
}
return consumesResponse, nil
return requiresPriorEvaluate, nil
default:
return false, nil
}

View File

@@ -2163,6 +2163,140 @@ func TestInitializeRejectsDNSRuleStrategyWhenLegacyDNSModeIsDisabledByMatchRespo
require.ErrorContains(t, err, "deprecated")
}
func TestInitializeRejectsDNSMatchResponseWithoutPrecedingEvaluate(t *testing.T) {
t.Parallel()
router := &Router{
ctx: context.Background(),
logger: log.NewNOPFactory().NewLogger("dns"),
transport: &fakeDNSTransportManager{},
client: &fakeDNSClient{},
rawRules: make([]option.DNSRule, 0, 1),
defaultDomainStrategy: C.DomainStrategyAsIS,
}
router.currentRules.Store(newRulesSnapshot(make([]adapter.DNSRule, 0, 1), false))
err := router.Initialize([]option.DNSRule{{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
MatchResponse: true,
ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeRoute,
RouteOptions: option.DNSRouteActionOptions{Server: "default"},
},
},
}})
require.ErrorContains(t, err, "preceding evaluate action")
}
func TestInitializeRejectsEvaluateRuleWithResponseMatchWithoutPrecedingEvaluate(t *testing.T) {
t.Parallel()
router := &Router{
ctx: context.Background(),
logger: log.NewNOPFactory().NewLogger("dns"),
transport: &fakeDNSTransportManager{},
client: &fakeDNSClient{},
rawRules: make([]option.DNSRule, 0, 1),
defaultDomainStrategy: C.DomainStrategyAsIS,
}
router.currentRules.Store(newRulesSnapshot(make([]adapter.DNSRule, 0, 1), false))
err := router.Initialize([]option.DNSRule{{
Type: C.RuleTypeLogical,
LogicalOptions: option.LogicalDNSRule{
RawLogicalDNSRule: option.RawLogicalDNSRule{
Mode: C.LogicalTypeOr,
Rules: []option.DNSRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
Domain: badoption.Listable[string]{"example.com"},
},
},
},
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
MatchResponse: true,
ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")},
},
},
},
},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeEvaluate,
RouteOptions: option.DNSRouteActionOptions{Server: "default"},
},
},
}})
require.ErrorContains(t, err, "preceding evaluate action")
}
func TestInitializeAllowsEvaluateRuleWithResponseMatchAfterPrecedingEvaluate(t *testing.T) {
t.Parallel()
router := &Router{
ctx: context.Background(),
logger: log.NewNOPFactory().NewLogger("dns"),
transport: &fakeDNSTransportManager{},
client: &fakeDNSClient{},
rawRules: make([]option.DNSRule, 0, 2),
defaultDomainStrategy: C.DomainStrategyAsIS,
}
router.currentRules.Store(newRulesSnapshot(make([]adapter.DNSRule, 0, 2), false))
err := router.Initialize([]option.DNSRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
Domain: badoption.Listable[string]{"bootstrap.example"},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeEvaluate,
RouteOptions: option.DNSRouteActionOptions{Server: "bootstrap"},
},
},
},
{
Type: C.RuleTypeLogical,
LogicalOptions: option.LogicalDNSRule{
RawLogicalDNSRule: option.RawLogicalDNSRule{
Mode: C.LogicalTypeOr,
Rules: []option.DNSRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
Domain: badoption.Listable[string]{"example.com"},
},
},
},
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
MatchResponse: true,
ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN A 1.1.1.1")},
},
},
},
},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeEvaluate,
RouteOptions: option.DNSRouteActionOptions{Server: "default"},
},
},
},
})
require.NoError(t, err)
}
func TestLookupLegacyDNSModeDisabledReturnsRejectedErrorForRejectAction(t *testing.T) {
t.Parallel()