mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 04:38:28 +10:00
dns: complete lookup rule execution in new mode
This commit is contained in:
103
dns/router.go
103
dns/router.go
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user