dns: complete lookup rule execution in new mode

This commit is contained in:
世界
2026-03-25 23:40:53 +08:00
parent 40afb6525b
commit ac452580d6
2 changed files with 213 additions and 23 deletions

View File

@@ -77,7 +77,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
}
func (r *Router) Initialize(rules []option.DNSRule) error {
r.legacyAddressFilterMode = !hasNonLegacyAddressFilterItems(rules)
r.legacyAddressFilterMode = hasLegacyAddressFilterItems(rules)
if !r.legacyAddressFilterMode {
err := validateNonLegacyAddressFilterRules(rules)
if err != nil {
@@ -353,6 +353,13 @@ type lookupWithRulesResponse struct {
explicitStrategy C.DomainStrategy
}
func lookupInputStrategy(options adapter.DNSQueryOptions) C.DomainStrategy {
if options.LookupStrategy != C.DomainStrategyAsIS {
return options.LookupStrategy
}
return options.Strategy
}
func (r *Router) resolveLookupStrategy(options adapter.DNSQueryOptions, strategies ...C.DomainStrategy) C.DomainStrategy {
if options.LookupStrategy != C.DomainStrategyAsIS {
return options.LookupStrategy
@@ -386,6 +393,66 @@ func lookupStrategyOverride(queryOptions adapter.DNSQueryOptions, strategyOverri
return queryOptions.Strategy
}
func isSingleFamilyLookupStrategy(strategy C.DomainStrategy) bool {
return strategy == C.DomainStrategyIPv4Only || strategy == C.DomainStrategyIPv6Only
}
func resolveExplicitLookupStrategy(strategies ...C.DomainStrategy) (C.DomainStrategy, bool) {
var resolvedStrategy C.DomainStrategy
for _, strategy := range strategies {
if strategy == C.DomainStrategyAsIS {
continue
}
if resolvedStrategy == C.DomainStrategyAsIS {
resolvedStrategy = strategy
continue
}
if resolvedStrategy != strategy {
return C.DomainStrategyAsIS, true
}
}
return resolvedStrategy, false
}
func (r *Router) resolveLookupOutputStrategies(options adapter.DNSQueryOptions, explicitStrategies ...C.DomainStrategy) (C.DomainStrategy, C.DomainStrategy) {
inputStrategy := lookupInputStrategy(options)
if inputStrategy != C.DomainStrategyAsIS {
return inputStrategy, inputStrategy
}
explicitStrategy, explicitConflict := resolveExplicitLookupStrategy(explicitStrategies...)
sortStrategy := r.defaultDomainStrategy
if !explicitConflict && explicitStrategy != C.DomainStrategyAsIS {
sortStrategy = explicitStrategy
}
filterStrategy := C.DomainStrategyAsIS
if explicitConflict {
return sortStrategy, filterStrategy
}
if explicitStrategy != C.DomainStrategyAsIS {
if isSingleFamilyLookupStrategy(explicitStrategy) {
filterStrategy = explicitStrategy
}
return sortStrategy, filterStrategy
}
if isSingleFamilyLookupStrategy(sortStrategy) {
filterStrategy = sortStrategy
}
return sortStrategy, filterStrategy
}
func withLookupQueryMetadata(ctx context.Context, qType uint16) context.Context {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.QueryType = qType
metadata.IPVersion = 0
switch qType {
case mDNS.TypeA:
metadata.IPVersion = 4
case mDNS.TypeAAAA:
metadata.IPVersion = 6
}
return ctx
}
func (r *Router) lookupWithRules(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
lookupOptions := options
if options.LookupStrategy != C.DomainStrategyAsIS {
@@ -415,17 +482,17 @@ func (r *Router) lookupWithRules(ctx context.Context, domain string, options ada
return err
})
err := group.Run(ctx)
strategy := r.resolveLookupStrategy(options, response4.explicitStrategy, response6.explicitStrategy)
if !lookupStrategyAllowsQueryType(strategy, mDNS.TypeA) {
sortStrategy, filterStrategy := r.resolveLookupOutputStrategies(options, response4.explicitStrategy, response6.explicitStrategy)
if !lookupStrategyAllowsQueryType(filterStrategy, mDNS.TypeA) {
response4.addresses = nil
}
if !lookupStrategyAllowsQueryType(strategy, mDNS.TypeAAAA) {
if !lookupStrategyAllowsQueryType(filterStrategy, mDNS.TypeAAAA) {
response6.addresses = nil
}
if len(response4.addresses) == 0 && len(response6.addresses) == 0 {
return nil, err
}
return sortAddresses(response4.addresses, response6.addresses, strategy), nil
return sortAddresses(response4.addresses, response6.addresses, sortStrategy), nil
}
func (r *Router) lookupWithRulesType(ctx context.Context, domain string, qType uint16, options adapter.DNSQueryOptions) (lookupWithRulesResponse, error) {
@@ -439,7 +506,7 @@ func (r *Router) lookupWithRulesType(ctx context.Context, domain string, qType u
Qclass: mDNS.ClassINET,
}},
}
response, _, queryOptions, strategyOverridden, err := r.exchangeWithRules(adapter.OverrideContext(ctx), request, options, false)
response, _, queryOptions, strategyOverridden, err := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), request, options, false)
explicitStrategy := lookupStrategyOverride(queryOptions, strategyOverridden)
result := lookupWithRulesResponse{
strategy: r.resolveLookupStrategy(options, explicitStrategy),
@@ -702,30 +769,26 @@ func (r *Router) ResetNetwork() {
}
}
func hasNonLegacyAddressFilterItems(rules []option.DNSRule) bool {
return common.Any(rules, hasNonLegacyAddressFilterItemsInRule)
func hasLegacyAddressFilterItems(rules []option.DNSRule) bool {
return common.Any(rules, hasLegacyAddressFilterItemsInRule)
}
func hasNonLegacyAddressFilterItemsInRule(rule option.DNSRule) bool {
func hasLegacyAddressFilterItemsInRule(rule option.DNSRule) bool {
switch rule.Type {
case "", C.RuleTypeDefault:
return hasNonLegacyAddressFilterItemsInDefaultRule(rule.DefaultOptions)
return hasLegacyAddressFilterItemsInDefaultRule(rule.DefaultOptions)
case C.RuleTypeLogical:
action := rule.LogicalOptions.Action
return action == C.RuleActionTypeEvaluate || common.Any(rule.LogicalOptions.Rules, hasNonLegacyAddressFilterItemsInRule)
return common.Any(rule.LogicalOptions.Rules, hasLegacyAddressFilterItemsInRule)
default:
return false
}
}
func hasNonLegacyAddressFilterItemsInDefaultRule(rule option.DefaultDNSRule) bool {
action := rule.Action
return action == C.RuleActionTypeEvaluate ||
rule.MatchResponse ||
rule.ResponseRcode != nil ||
len(rule.ResponseAnswer) > 0 ||
len(rule.ResponseNs) > 0 ||
len(rule.ResponseExtra) > 0
func hasLegacyAddressFilterItemsInDefaultRule(rule option.DefaultDNSRule) bool {
if rule.IPAcceptAny || rule.RuleSetIPCIDRAcceptEmpty {
return true
}
return !rule.MatchResponse && (len(rule.IPCIDR) > 0 || rule.IPIsPrivate)
}
func validateNonLegacyAddressFilterRules(rules []option.DNSRule) error {

View File

@@ -776,7 +776,7 @@ func TestLookupNewModeEvaluateSkipFakeIPPreservesResponse(t *testing.T) {
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
}
func TestLookupNewModeDoesNotUseQueryTypeRule(t *testing.T) {
func TestLookupNewModeUsesQueryTypeRule(t *testing.T) {
t.Parallel()
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
@@ -812,11 +812,57 @@ func TestLookupNewModeDoesNotUseQueryTypeRule(t *testing.T) {
}
},
})
router.legacyAddressFilterMode = false
require.False(t, router.legacyAddressFilterMode)
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
require.NoError(t, err)
require.Equal(t, []netip.Addr{netip.MustParseAddr("3.3.3.3")}, addresses)
require.Equal(t, []netip.Addr{netip.MustParseAddr("9.9.9.9")}, addresses)
}
func TestLookupNewModeUsesIPVersionRule(t *testing.T) {
t.Parallel()
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
router := newTestRouter(t, []option.DNSRule{{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
IPVersion: 6,
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeRoute,
RouteOptions: option.DNSRouteActionOptions{Server: "only-v6"},
},
},
}}, &fakeDNSTransportManager{
defaultTransport: defaultTransport,
transports: map[string]adapter.DNSTransport{
"default": defaultTransport,
"only-v6": &fakeDNSTransport{tag: "only-v6", transportType: C.DNSTypeUDP},
},
}, &fakeDNSClient{
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
switch transport.Tag() {
case "default":
if message.Question[0].Qtype == mDNS.TypeA {
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("3.3.3.3")}, 60), nil
}
return FixedResponse(0, message.Question[0], nil, 60), nil
case "only-v6":
if message.Question[0].Qtype == mDNS.TypeAAAA {
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::9")}, 60), nil
}
return FixedResponse(0, message.Question[0], nil, 60), nil
default:
return nil, errors.New("unexpected transport")
}
},
})
require.False(t, router.legacyAddressFilterMode)
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
require.NoError(t, err)
require.Equal(t, []netip.Addr{netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("2001:db8::9")}, addresses)
}
func TestLookupNewModeAppliesRouteStrategyAfterEvaluate(t *testing.T) {
@@ -1031,6 +1077,87 @@ func TestLookupNewModeKeepsExplicitBranchStrategyMatchingInput(t *testing.T) {
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
}
func TestLookupNewModeKeepsConflictingExplicitBranchStrategies(t *testing.T) {
t.Parallel()
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
router := newTestRouter(t, []option.DNSRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
Domain: badoption.Listable[string]{"example.com"},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeEvaluate,
RouteOptions: option.DNSRouteActionOptions{Server: "upstream"},
},
},
},
{
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: "selected4",
Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only),
},
},
},
},
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
MatchResponse: true,
ResponseAnswer: badoption.Listable[option.DNSRecordOptions]{mustRecord(t, "example.com. IN AAAA 2001:db8::1")},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeRoute,
RouteOptions: option.DNSRouteActionOptions{
Server: "selected6",
Strategy: option.DomainStrategy(C.DomainStrategyIPv6Only),
},
},
},
},
}, &fakeDNSTransportManager{
defaultTransport: defaultTransport,
transports: map[string]adapter.DNSTransport{
"default": defaultTransport,
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
"selected4": &fakeDNSTransport{tag: "selected4", transportType: C.DNSTypeUDP},
"selected6": &fakeDNSTransport{tag: "selected6", transportType: C.DNSTypeUDP},
},
}, &fakeDNSClient{
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
switch transport.Tag() {
case "upstream":
if message.Question[0].Qtype == mDNS.TypeA {
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
}
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil
case "selected4":
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil
case "selected6":
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::2")}, 60), nil
default:
return nil, errors.New("unexpected transport")
}
},
})
router.legacyAddressFilterMode = false
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
require.NoError(t, err)
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2"), netip.MustParseAddr("2001:db8::2")}, addresses)
}
func TestExchangeNewModeLogicalMatchResponseIPCIDRFallsThrough(t *testing.T) {
t.Parallel()