mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Fix DNS evaluate regressions
This commit is contained in:
103
dns/router.go
103
dns/router.go
@@ -232,9 +232,11 @@ func (r *Router) applyTransportDefaults(transport adapter.DNSTransport, options
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOptions R.RuleActionDNSRouteOptions) {
|
||||
func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOptions R.RuleActionDNSRouteOptions) bool {
|
||||
var strategyOverridden bool
|
||||
if routeOptions.Strategy != C.DomainStrategyAsIS {
|
||||
options.Strategy = routeOptions.Strategy
|
||||
strategyOverridden = true
|
||||
}
|
||||
if routeOptions.DisableCache {
|
||||
options.DisableCache = true
|
||||
@@ -245,23 +247,32 @@ func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOpt
|
||||
if routeOptions.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = routeOptions.ClientSubnet
|
||||
}
|
||||
return strategyOverridden
|
||||
}
|
||||
|
||||
func (r *Router) resolveDNSRoute(action *R.RuleActionDNSRoute, allowFakeIP bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, bool) {
|
||||
type dnsRouteStatus uint8
|
||||
|
||||
const (
|
||||
dnsRouteStatusMissing dnsRouteStatus = iota
|
||||
dnsRouteStatusSkipped
|
||||
dnsRouteStatusResolved
|
||||
)
|
||||
|
||||
func (r *Router) resolveDNSRoute(action *R.RuleActionDNSRoute, allowFakeIP bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, dnsRouteStatus, bool) {
|
||||
transport, loaded := r.transport.Transport(action.Server)
|
||||
if !loaded {
|
||||
return nil, false
|
||||
return nil, dnsRouteStatusMissing, false
|
||||
}
|
||||
isFakeIP := transport.Type() == C.DNSTypeFakeIP
|
||||
if isFakeIP && !allowFakeIP {
|
||||
return transport, false
|
||||
return transport, dnsRouteStatusSkipped, false
|
||||
}
|
||||
r.applyDNSRouteOptions(options, action.RuleActionDNSRouteOptions)
|
||||
strategyOverridden := r.applyDNSRouteOptions(options, action.RuleActionDNSRouteOptions)
|
||||
if isFakeIP {
|
||||
options.DisableCache = true
|
||||
}
|
||||
r.applyTransportDefaults(transport, options)
|
||||
return transport, true
|
||||
return transport, dnsRouteStatusResolved, strategyOverridden
|
||||
}
|
||||
|
||||
func (r *Router) logRuleMatch(ctx context.Context, ruleIndex int, currentRule adapter.DNSRule) {
|
||||
@@ -276,12 +287,13 @@ func (r *Router) logRuleMatch(ctx context.Context, ruleIndex int, currentRule ad
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool) (*mDNS.Msg, adapter.DNSTransport, adapter.DNSQueryOptions, error) {
|
||||
func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool) (*mDNS.Msg, adapter.DNSTransport, adapter.DNSQueryOptions, bool, error) {
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
panic("no context")
|
||||
}
|
||||
effectiveOptions := options
|
||||
effectiveStrategyOverridden := false
|
||||
var savedResponse *mDNS.Msg
|
||||
for currentRuleIndex, currentRule := range r.rules {
|
||||
metadata.ResetRuleCache()
|
||||
@@ -293,24 +305,26 @@ func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, optio
|
||||
r.logRuleMatch(ctx, currentRuleIndex, currentRule)
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *R.RuleActionDNSRouteOptions:
|
||||
r.applyDNSRouteOptions(&effectiveOptions, *action)
|
||||
effectiveStrategyOverridden = r.applyDNSRouteOptions(&effectiveOptions, *action) || effectiveStrategyOverridden
|
||||
case *R.RuleActionEvaluate:
|
||||
queryOptions := effectiveOptions
|
||||
transport, loaded := r.resolveDNSRoute(&R.RuleActionDNSRoute{
|
||||
transport, status, _ := r.resolveDNSRoute(&R.RuleActionDNSRoute{
|
||||
Server: action.Server,
|
||||
RuleActionDNSRouteOptions: action.RuleActionDNSRouteOptions,
|
||||
}, allowFakeIP, &queryOptions)
|
||||
if !loaded {
|
||||
if transport == nil {
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
}
|
||||
switch status {
|
||||
case dnsRouteStatusMissing:
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
savedResponse = nil
|
||||
continue
|
||||
case dnsRouteStatusSkipped:
|
||||
continue
|
||||
}
|
||||
if queryOptions.Strategy == C.DomainStrategyAsIS {
|
||||
queryOptions.Strategy = r.defaultDomainStrategy
|
||||
exchangeOptions := queryOptions
|
||||
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
|
||||
exchangeOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, queryOptions, nil)
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
if err != nil {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||
savedResponse = nil
|
||||
@@ -319,18 +333,20 @@ func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, optio
|
||||
savedResponse = response
|
||||
case *R.RuleActionDNSRoute:
|
||||
queryOptions := effectiveOptions
|
||||
transport, loaded := r.resolveDNSRoute(action, allowFakeIP, &queryOptions)
|
||||
if !loaded {
|
||||
if transport == nil {
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
}
|
||||
transport, status, strategyOverridden := r.resolveDNSRoute(action, allowFakeIP, &queryOptions)
|
||||
switch status {
|
||||
case dnsRouteStatusMissing:
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
continue
|
||||
case dnsRouteStatusSkipped:
|
||||
continue
|
||||
}
|
||||
if queryOptions.Strategy == C.DomainStrategyAsIS {
|
||||
queryOptions.Strategy = r.defaultDomainStrategy
|
||||
exchangeOptions := queryOptions
|
||||
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
|
||||
exchangeOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, queryOptions, nil)
|
||||
return response, transport, queryOptions, err
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
return response, transport, queryOptions, effectiveStrategyOverridden || strategyOverridden, err
|
||||
case *R.RuleActionReject:
|
||||
switch action.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
@@ -341,27 +357,29 @@ func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, optio
|
||||
Response: true,
|
||||
},
|
||||
Question: []mDNS.Question{message.Question[0]},
|
||||
}, nil, effectiveOptions, nil
|
||||
}, nil, effectiveOptions, effectiveStrategyOverridden, nil
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return nil, nil, effectiveOptions, tun.ErrDrop
|
||||
return nil, nil, effectiveOptions, effectiveStrategyOverridden, tun.ErrDrop
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
return action.Response(message), nil, effectiveOptions, nil
|
||||
return action.Response(message), nil, effectiveOptions, effectiveStrategyOverridden, nil
|
||||
}
|
||||
}
|
||||
queryOptions := effectiveOptions
|
||||
transport := r.transport.Default()
|
||||
r.applyTransportDefaults(transport, &queryOptions)
|
||||
if queryOptions.Strategy == C.DomainStrategyAsIS {
|
||||
queryOptions.Strategy = r.defaultDomainStrategy
|
||||
exchangeOptions := queryOptions
|
||||
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
|
||||
exchangeOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, queryOptions, nil)
|
||||
return response, transport, queryOptions, err
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
return response, transport, queryOptions, effectiveStrategyOverridden, err
|
||||
}
|
||||
|
||||
type lookupWithRulesResponse struct {
|
||||
addresses []netip.Addr
|
||||
strategy C.DomainStrategy
|
||||
addresses []netip.Addr
|
||||
strategy C.DomainStrategy
|
||||
explicitStrategy C.DomainStrategy
|
||||
}
|
||||
|
||||
func (r *Router) resolveLookupStrategy(options adapter.DNSQueryOptions, strategies ...C.DomainStrategy) C.DomainStrategy {
|
||||
@@ -390,6 +408,13 @@ func lookupStrategyAllowsQueryType(strategy C.DomainStrategy, qType uint16) bool
|
||||
}
|
||||
}
|
||||
|
||||
func lookupStrategyOverride(queryOptions adapter.DNSQueryOptions, strategyOverridden bool) C.DomainStrategy {
|
||||
if !strategyOverridden {
|
||||
return C.DomainStrategyAsIS
|
||||
}
|
||||
return queryOptions.Strategy
|
||||
}
|
||||
|
||||
func (r *Router) lookupWithRules(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||
lookupOptions := options
|
||||
if options.LookupStrategy != C.DomainStrategyAsIS {
|
||||
@@ -419,7 +444,7 @@ func (r *Router) lookupWithRules(ctx context.Context, domain string, options ada
|
||||
return err
|
||||
})
|
||||
err := group.Run(ctx)
|
||||
strategy := r.resolveLookupStrategy(options, response4.strategy, response6.strategy)
|
||||
strategy := r.resolveLookupStrategy(options, response4.explicitStrategy, response6.explicitStrategy)
|
||||
if !lookupStrategyAllowsQueryType(strategy, mDNS.TypeA) {
|
||||
response4.addresses = nil
|
||||
}
|
||||
@@ -443,9 +468,11 @@ func (r *Router) lookupWithRulesType(ctx context.Context, domain string, qType u
|
||||
Qclass: mDNS.ClassINET,
|
||||
}},
|
||||
}
|
||||
response, _, queryOptions, err := r.exchangeWithRules(adapter.OverrideContext(ctx), request, options, false)
|
||||
response, _, queryOptions, strategyOverridden, err := r.exchangeWithRules(adapter.OverrideContext(ctx), request, options, false)
|
||||
explicitStrategy := lookupStrategyOverride(queryOptions, strategyOverridden)
|
||||
result := lookupWithRulesResponse{
|
||||
strategy: r.resolveLookupStrategy(options, queryOptions.Strategy),
|
||||
strategy: r.resolveLookupStrategy(options, explicitStrategy),
|
||||
explicitStrategy: explicitStrategy,
|
||||
}
|
||||
if err != nil {
|
||||
return result, err
|
||||
@@ -500,7 +527,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
}
|
||||
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||
} else if !r.legacyAddressFilterMode {
|
||||
response, transport, _, err = r.exchangeWithRules(ctx, message, options, true)
|
||||
response, transport, _, _, err = r.exchangeWithRules(ctx, message, options, true)
|
||||
} else {
|
||||
var (
|
||||
rule adapter.DNSRule
|
||||
|
||||
@@ -583,6 +583,86 @@ func TestLookupNewModeSkipsFakeIPRule(t *testing.T) {
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupNewModeEvaluateSkipFakeIPPreservesResponse(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{
|
||||
Domain: badoption.Listable[string]{"example.com"},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeEvaluate,
|
||||
RouteOptions: option.DNSRouteActionOptions{Server: "fake"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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: "selected"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &fakeDNSTransportManager{
|
||||
defaultTransport: defaultTransport,
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"default": defaultTransport,
|
||||
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
|
||||
"fake": &fakeDNSTransport{tag: "fake", transportType: C.DNSTypeFakeIP},
|
||||
"selected": &fakeDNSTransport{tag: "selected", 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], nil, 60), nil
|
||||
case "selected":
|
||||
if message.Question[0].Qtype == mDNS.TypeA {
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil
|
||||
}
|
||||
return FixedResponse(0, message.Question[0], nil, 60), nil
|
||||
case "default":
|
||||
if message.Question[0].Qtype == mDNS.TypeA {
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("4.4.4.4")}, 60), nil
|
||||
}
|
||||
return FixedResponse(0, message.Question[0], nil, 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")}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupNewModeDoesNotUseQueryTypeRule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -685,6 +765,159 @@ func TestLookupNewModeAppliesRouteStrategyAfterEvaluate(t *testing.T) {
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupNewModePrefersExplicitBranchStrategyOverDefault(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 AAAA 2001:db8::1")},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{
|
||||
Server: "selected",
|
||||
Strategy: option.DomainStrategy(C.DomainStrategyIPv6Only),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &fakeDNSTransportManager{
|
||||
defaultTransport: defaultTransport,
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"default": defaultTransport,
|
||||
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
|
||||
"selected": &fakeDNSTransport{tag: "selected", 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 "selected":
|
||||
if message.Question[0].Qtype == mDNS.TypeA {
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil
|
||||
}
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::2")}, 60), nil
|
||||
case "default":
|
||||
if message.Question[0].Qtype == mDNS.TypeA {
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("4.4.4.4")}, 60), nil
|
||||
}
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::4")}, 60), nil
|
||||
default:
|
||||
return nil, errors.New("unexpected transport")
|
||||
}
|
||||
},
|
||||
})
|
||||
router.defaultDomainStrategy = C.DomainStrategyIPv4Only
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2001:db8::2")}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupNewModeKeepsExplicitBranchStrategyMatchingInput(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{
|
||||
Strategy: C.DomainStrategyIPv4Only,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
|
||||
}
|
||||
|
||||
func TestExchangeNewModeLogicalMatchResponseIPCIDRFallsThrough(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -65,9 +65,21 @@ func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) e
|
||||
}
|
||||
if !dontUpgrade {
|
||||
rcodeMap := make(map[string]int)
|
||||
for _, server := range o.Servers {
|
||||
if server.Type == C.DNSTypeLegacyRcode {
|
||||
rcodeMap[server.Tag] = server.Options.(int)
|
||||
}
|
||||
}
|
||||
if len(rcodeMap) > 0 {
|
||||
for i := 0; i < len(o.Rules); i++ {
|
||||
err = rejectEvaluateLegacyRcode(rcodeMap, o.Rules[i])
|
||||
if err != nil {
|
||||
return E.Cause(err, "dns rule[", i, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
o.Servers = common.Filter(o.Servers, func(it DNSServerOptions) bool {
|
||||
if it.Type == C.DNSTypeLegacyRcode {
|
||||
rcodeMap[it.Tag] = it.Options.(int)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -81,6 +93,35 @@ func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func rejectEvaluateLegacyRcode(rcodeMap map[string]int, rule DNSRule) error {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
return rejectEvaluateLegacyRcodeAction(rcodeMap, &rule.DefaultOptions.DNSRuleAction)
|
||||
case C.RuleTypeLogical:
|
||||
err := rejectEvaluateLegacyRcodeAction(rcodeMap, &rule.LogicalOptions.DNSRuleAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, subRule := range rule.LogicalOptions.Rules {
|
||||
err = rejectEvaluateLegacyRcode(rcodeMap, subRule)
|
||||
if err != nil {
|
||||
return E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rejectEvaluateLegacyRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) error {
|
||||
if ruleAction.Action != C.RuleActionTypeEvaluate {
|
||||
return nil
|
||||
}
|
||||
if _, loaded := rcodeMap[ruleAction.RouteOptions.Server]; loaded {
|
||||
return E.New("evaluate action cannot reference legacy rcode server: ", ruleAction.RouteOptions.Server)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rewriteRcode(rcodeMap map[string]int, rule *DNSRule) {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
|
||||
33
option/dns_test.go
Normal file
33
option/dns_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type stubDNSTransportOptionsRegistry struct{}
|
||||
|
||||
func (stubDNSTransportOptionsRegistry) CreateOptions(string) (any, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func TestDNSOptionsRejectsEvaluateLegacyRcodeServer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := service.ContextWith[DNSTransportOptionsRegistry](context.Background(), stubDNSTransportOptionsRegistry{})
|
||||
var options DNSOptions
|
||||
err := json.UnmarshalContext(ctx, []byte(`{
|
||||
"servers": [
|
||||
{"tag": "legacy-rcode", "address": "rcode://success"},
|
||||
{"tag": "default", "address": "1.1.1.1"}
|
||||
],
|
||||
"rules": [
|
||||
{"domain": ["example.com"], "action": "evaluate", "server": "legacy-rcode"}
|
||||
]
|
||||
}`), &options)
|
||||
require.ErrorContains(t, err, "evaluate action cannot reference legacy rcode server: legacy-rcode")
|
||||
}
|
||||
Reference in New Issue
Block a user