mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 04:38:28 +10:00
dns: make rule strategy legacy-only
This commit is contained in:
@@ -15,62 +15,31 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReproLookupWithRulesIgnoresRouteStrategy(t *testing.T) {
|
||||
func TestReproLookupWithRulesUsesRequestStrategy(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: "default"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
MatchResponse: true,
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{
|
||||
Server: "selected",
|
||||
Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &fakeDNSTransportManager{
|
||||
var qTypes []uint16
|
||||
router := newTestRouter(t, nil, &fakeDNSTransportManager{
|
||||
defaultTransport: defaultTransport,
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"default": defaultTransport,
|
||||
"selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP},
|
||||
"default": defaultTransport,
|
||||
},
|
||||
}, &fakeDNSClient{
|
||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
if transport.Tag() == "default" {
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
|
||||
}
|
||||
switch message.Question[0].Qtype {
|
||||
case mDNS.TypeA:
|
||||
qTypes = append(qTypes, message.Question[0].Qtype)
|
||||
if message.Question[0].Qtype == mDNS.TypeA {
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil
|
||||
case mDNS.TypeAAAA:
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil
|
||||
default:
|
||||
return nil, errors.New("unexpected qtype")
|
||||
}
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil
|
||||
},
|
||||
})
|
||||
|
||||
addrs, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
|
||||
addrs, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
|
||||
Strategy: C.DomainStrategyIPv4Only,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint16{mDNS.TypeA}, qTypes)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addrs)
|
||||
}
|
||||
|
||||
|
||||
360
dns/router.go
360
dns/router.go
@@ -38,22 +38,23 @@ type dnsRuleSetCallback struct {
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
transport adapter.DNSTransportManager
|
||||
outbound adapter.OutboundManager
|
||||
client adapter.DNSClient
|
||||
rawRules []option.DNSRule
|
||||
rules []adapter.DNSRule
|
||||
defaultDomainStrategy C.DomainStrategy
|
||||
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
||||
platformInterface adapter.PlatformInterface
|
||||
legacyAddressFilterMode bool
|
||||
rulesAccess sync.RWMutex
|
||||
closing bool
|
||||
ruleSetCallbacks []dnsRuleSetCallback
|
||||
runtimeRuleError error
|
||||
deprecatedReported bool
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
transport adapter.DNSTransportManager
|
||||
outbound adapter.OutboundManager
|
||||
client adapter.DNSClient
|
||||
rawRules []option.DNSRule
|
||||
rules []adapter.DNSRule
|
||||
defaultDomainStrategy C.DomainStrategy
|
||||
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
||||
platformInterface adapter.PlatformInterface
|
||||
legacyDNSMode bool
|
||||
rulesAccess sync.RWMutex
|
||||
closing bool
|
||||
ruleSetCallbacks []dnsRuleSetCallback
|
||||
runtimeRuleError error
|
||||
addressFilterDeprecatedReported bool
|
||||
ruleStrategyDeprecatedReported bool
|
||||
}
|
||||
|
||||
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
||||
@@ -152,17 +153,21 @@ func (r *Router) rebuildRules(startRules bool) error {
|
||||
if r.isClosing() {
|
||||
return nil
|
||||
}
|
||||
newRules, legacyAddressFilterMode, err := r.buildRules(startRules)
|
||||
newRules, legacyDNSMode, err := r.buildRules(startRules)
|
||||
if err != nil {
|
||||
if r.isClosing() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
shouldReportDeprecated := startRules &&
|
||||
legacyAddressFilterMode &&
|
||||
!r.deprecatedReported &&
|
||||
shouldReportAddressFilterDeprecated := startRules &&
|
||||
legacyDNSMode &&
|
||||
!r.addressFilterDeprecatedReported &&
|
||||
common.Any(newRules, func(rule adapter.DNSRule) bool { return rule.WithAddressLimit() })
|
||||
shouldReportRuleStrategyDeprecated := startRules &&
|
||||
legacyDNSMode &&
|
||||
!r.ruleStrategyDeprecatedReported &&
|
||||
hasDNSRuleActionStrategy(r.rawRules)
|
||||
r.rulesAccess.Lock()
|
||||
if r.closing {
|
||||
r.rulesAccess.Unlock()
|
||||
@@ -171,16 +176,22 @@ func (r *Router) rebuildRules(startRules bool) error {
|
||||
}
|
||||
oldRules := r.rules
|
||||
r.rules = newRules
|
||||
r.legacyAddressFilterMode = legacyAddressFilterMode
|
||||
r.legacyDNSMode = legacyDNSMode
|
||||
r.runtimeRuleError = nil
|
||||
if shouldReportDeprecated {
|
||||
r.deprecatedReported = true
|
||||
if shouldReportAddressFilterDeprecated {
|
||||
r.addressFilterDeprecatedReported = true
|
||||
}
|
||||
if shouldReportRuleStrategyDeprecated {
|
||||
r.ruleStrategyDeprecatedReported = true
|
||||
}
|
||||
r.rulesAccess.Unlock()
|
||||
closeRules(oldRules)
|
||||
if shouldReportDeprecated {
|
||||
if shouldReportAddressFilterDeprecated {
|
||||
deprecated.Report(r.ctx, deprecated.OptionLegacyDNSAddressFilter)
|
||||
}
|
||||
if shouldReportRuleStrategyDeprecated {
|
||||
deprecated.Report(r.ctx, deprecated.OptionLegacyDNSRuleStrategy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -192,19 +203,19 @@ func (r *Router) isClosing() bool {
|
||||
|
||||
func (r *Router) buildRules(startRules bool) ([]adapter.DNSRule, bool, error) {
|
||||
router := service.FromContext[adapter.Router](r.ctx)
|
||||
legacyAddressFilterMode, err := resolveLegacyAddressFilterMode(router, r.rawRules)
|
||||
legacyDNSMode, err := resolveLegacyDNSMode(router, r.rawRules)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !legacyAddressFilterMode {
|
||||
err = validateNonLegacyAddressFilterRules(r.rawRules)
|
||||
if !legacyDNSMode {
|
||||
err = validateLegacyDNSModeDisabledRules(r.rawRules)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
newRules := make([]adapter.DNSRule, 0, len(r.rawRules))
|
||||
for i, ruleOptions := range r.rawRules {
|
||||
dnsRule, err := R.NewDNSRule(r.ctx, r.logger, ruleOptions, true, legacyAddressFilterMode)
|
||||
dnsRule, err := R.NewDNSRule(r.ctx, r.logger, ruleOptions, true, legacyDNSMode)
|
||||
if err != nil {
|
||||
closeRules(newRules)
|
||||
return nil, false, E.Cause(err, "parse dns rule[", i, "]")
|
||||
@@ -220,7 +231,7 @@ func (r *Router) buildRules(startRules bool) ([]adapter.DNSRule, bool, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return newRules, legacyAddressFilterMode, nil
|
||||
return newRules, legacyDNSMode, nil
|
||||
}
|
||||
|
||||
func closeRules(rules []adapter.DNSRule) {
|
||||
@@ -349,12 +360,7 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
||||
return transport, nil, -1
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOptions R.RuleActionDNSRouteOptions) {
|
||||
if routeOptions.DisableCache {
|
||||
options.DisableCache = true
|
||||
}
|
||||
@@ -364,7 +370,6 @@ func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOpt
|
||||
if routeOptions.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = routeOptions.ClientSubnet
|
||||
}
|
||||
return strategyOverridden
|
||||
}
|
||||
|
||||
type dnsRouteStatus uint8
|
||||
@@ -375,20 +380,20 @@ const (
|
||||
dnsRouteStatusResolved
|
||||
)
|
||||
|
||||
func (r *Router) resolveDNSRoute(action *R.RuleActionDNSRoute, allowFakeIP bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, dnsRouteStatus, bool) {
|
||||
func (r *Router) resolveDNSRoute(action *R.RuleActionDNSRoute, allowFakeIP bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, dnsRouteStatus) {
|
||||
transport, loaded := r.transport.Transport(action.Server)
|
||||
if !loaded {
|
||||
return nil, dnsRouteStatusMissing, false
|
||||
return nil, dnsRouteStatusMissing
|
||||
}
|
||||
isFakeIP := transport.Type() == C.DNSTypeFakeIP
|
||||
if isFakeIP && !allowFakeIP {
|
||||
return transport, dnsRouteStatusSkipped, false
|
||||
return transport, dnsRouteStatusSkipped
|
||||
}
|
||||
strategyOverridden := r.applyDNSRouteOptions(options, action.RuleActionDNSRouteOptions)
|
||||
r.applyDNSRouteOptions(options, action.RuleActionDNSRouteOptions)
|
||||
if isFakeIP {
|
||||
options.DisableCache = true
|
||||
}
|
||||
return transport, dnsRouteStatusResolved, strategyOverridden
|
||||
return transport, dnsRouteStatusResolved
|
||||
}
|
||||
|
||||
func (r *Router) logRuleMatch(ctx context.Context, ruleIndex int, currentRule adapter.DNSRule) {
|
||||
@@ -400,12 +405,10 @@ func (r *Router) logRuleMatch(ctx context.Context, ruleIndex int, currentRule ad
|
||||
}
|
||||
|
||||
type exchangeWithRulesResult struct {
|
||||
response *mDNS.Msg
|
||||
transport adapter.DNSTransport
|
||||
queryOptions adapter.DNSQueryOptions
|
||||
strategyOverridden bool
|
||||
rejectAction *R.RuleActionReject
|
||||
err error
|
||||
response *mDNS.Msg
|
||||
transport adapter.DNSTransport
|
||||
rejectAction *R.RuleActionReject
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool) exchangeWithRulesResult {
|
||||
@@ -414,7 +417,6 @@ func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, optio
|
||||
panic("no context")
|
||||
}
|
||||
effectiveOptions := options
|
||||
effectiveStrategyOverridden := false
|
||||
var savedResponse *mDNS.Msg
|
||||
for currentRuleIndex, currentRule := range r.rules {
|
||||
metadata.ResetRuleCache()
|
||||
@@ -426,10 +428,10 @@ func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, optio
|
||||
r.logRuleMatch(ctx, currentRuleIndex, currentRule)
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *R.RuleActionDNSRouteOptions:
|
||||
effectiveStrategyOverridden = r.applyDNSRouteOptions(&effectiveOptions, *action) || effectiveStrategyOverridden
|
||||
r.applyDNSRouteOptions(&effectiveOptions, *action)
|
||||
case *R.RuleActionEvaluate:
|
||||
queryOptions := effectiveOptions
|
||||
transport, status, _ := r.resolveDNSRoute(&R.RuleActionDNSRoute{
|
||||
transport, status := r.resolveDNSRoute(&R.RuleActionDNSRoute{
|
||||
Server: action.Server,
|
||||
RuleActionDNSRouteOptions: action.RuleActionDNSRouteOptions,
|
||||
}, allowFakeIP, &queryOptions)
|
||||
@@ -454,7 +456,7 @@ func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, optio
|
||||
savedResponse = response
|
||||
case *R.RuleActionDNSRoute:
|
||||
queryOptions := effectiveOptions
|
||||
transport, status, strategyOverridden := r.resolveDNSRoute(action, allowFakeIP, &queryOptions)
|
||||
transport, status := r.resolveDNSRoute(action, allowFakeIP, &queryOptions)
|
||||
switch status {
|
||||
case dnsRouteStatusMissing:
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
@@ -468,11 +470,9 @@ func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, optio
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
return exchangeWithRulesResult{
|
||||
response: response,
|
||||
transport: transport,
|
||||
queryOptions: queryOptions,
|
||||
strategyOverridden: effectiveStrategyOverridden || strategyOverridden,
|
||||
err: err,
|
||||
response: response,
|
||||
transport: transport,
|
||||
err: err,
|
||||
}
|
||||
case *R.RuleActionReject:
|
||||
switch action.Method {
|
||||
@@ -486,23 +486,17 @@ func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, optio
|
||||
},
|
||||
Question: []mDNS.Question{message.Question[0]},
|
||||
},
|
||||
queryOptions: effectiveOptions,
|
||||
strategyOverridden: effectiveStrategyOverridden,
|
||||
rejectAction: action,
|
||||
rejectAction: action,
|
||||
}
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return exchangeWithRulesResult{
|
||||
queryOptions: effectiveOptions,
|
||||
strategyOverridden: effectiveStrategyOverridden,
|
||||
rejectAction: action,
|
||||
err: tun.ErrDrop,
|
||||
rejectAction: action,
|
||||
err: tun.ErrDrop,
|
||||
}
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
return exchangeWithRulesResult{
|
||||
response: action.Response(message),
|
||||
queryOptions: effectiveOptions,
|
||||
strategyOverridden: effectiveStrategyOverridden,
|
||||
response: action.Response(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -514,36 +508,20 @@ func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, optio
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
return exchangeWithRulesResult{
|
||||
response: response,
|
||||
transport: transport,
|
||||
queryOptions: queryOptions,
|
||||
strategyOverridden: effectiveStrategyOverridden,
|
||||
err: err,
|
||||
response: response,
|
||||
transport: transport,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
type lookupWithRulesResponse struct {
|
||||
addresses []netip.Addr
|
||||
strategy C.DomainStrategy
|
||||
explicitStrategy C.DomainStrategy
|
||||
addresses []netip.Addr
|
||||
}
|
||||
|
||||
func lookupInputStrategy(options adapter.DNSQueryOptions) C.DomainStrategy {
|
||||
func (r *Router) resolveLookupStrategy(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
|
||||
}
|
||||
for _, strategy := range strategies {
|
||||
if strategy != C.DomainStrategyAsIS {
|
||||
return strategy
|
||||
}
|
||||
}
|
||||
if options.Strategy != C.DomainStrategyAsIS {
|
||||
return options.Strategy
|
||||
}
|
||||
@@ -561,60 +539,6 @@ 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 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
|
||||
@@ -644,15 +568,16 @@ func filterAddressesByQueryType(addresses []netip.Addr, qType uint16) []netip.Ad
|
||||
}
|
||||
|
||||
func (r *Router) lookupWithRules(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||
strategy := r.resolveLookupStrategy(options)
|
||||
lookupOptions := options
|
||||
if options.LookupStrategy != C.DomainStrategyAsIS {
|
||||
lookupOptions.Strategy = options.LookupStrategy
|
||||
if strategy != C.DomainStrategyAsIS {
|
||||
lookupOptions.Strategy = strategy
|
||||
}
|
||||
if options.LookupStrategy == C.DomainStrategyIPv4Only {
|
||||
if strategy == C.DomainStrategyIPv4Only {
|
||||
response, err := r.lookupWithRulesType(ctx, domain, mDNS.TypeA, lookupOptions)
|
||||
return response.addresses, err
|
||||
}
|
||||
if options.LookupStrategy == C.DomainStrategyIPv6Only {
|
||||
if strategy == C.DomainStrategyIPv6Only {
|
||||
response, err := r.lookupWithRulesType(ctx, domain, mDNS.TypeAAAA, lookupOptions)
|
||||
return response.addresses, err
|
||||
}
|
||||
@@ -672,17 +597,10 @@ func (r *Router) lookupWithRules(ctx context.Context, domain string, options ada
|
||||
return err
|
||||
})
|
||||
err := group.Run(ctx)
|
||||
sortStrategy, filterStrategy := r.resolveLookupOutputStrategies(options, response4.explicitStrategy, response6.explicitStrategy)
|
||||
if !lookupStrategyAllowsQueryType(filterStrategy, mDNS.TypeA) {
|
||||
response4.addresses = nil
|
||||
}
|
||||
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, sortStrategy), nil
|
||||
return sortAddresses(response4.addresses, response6.addresses, strategy), nil
|
||||
}
|
||||
|
||||
func (r *Router) lookupWithRulesType(ctx context.Context, domain string, qType uint16, options adapter.DNSQueryOptions) (lookupWithRulesResponse, error) {
|
||||
@@ -697,11 +615,7 @@ func (r *Router) lookupWithRulesType(ctx context.Context, domain string, qType u
|
||||
}},
|
||||
}
|
||||
exchangeResult := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), request, options, false)
|
||||
explicitStrategy := lookupStrategyOverride(exchangeResult.queryOptions, exchangeResult.strategyOverridden)
|
||||
result := lookupWithRulesResponse{
|
||||
strategy: r.resolveLookupStrategy(options, explicitStrategy),
|
||||
explicitStrategy: explicitStrategy,
|
||||
}
|
||||
result := lookupWithRulesResponse{}
|
||||
if exchangeResult.rejectAction != nil {
|
||||
return result, exchangeResult.rejectAction.Error(ctx)
|
||||
}
|
||||
@@ -711,7 +625,7 @@ func (r *Router) lookupWithRulesType(ctx context.Context, domain string, qType u
|
||||
if exchangeResult.response.Rcode != mDNS.RcodeSuccess {
|
||||
return result, RcodeError(exchangeResult.response.Rcode)
|
||||
}
|
||||
if !lookupStrategyAllowsQueryType(result.strategy, qType) {
|
||||
if !lookupStrategyAllowsQueryType(r.resolveLookupStrategy(options), qType) {
|
||||
return result, nil
|
||||
}
|
||||
result.addresses = filterAddressesByQueryType(MessageToAddresses(exchangeResult.response), qType)
|
||||
@@ -761,7 +675,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||
} else if !r.legacyAddressFilterMode {
|
||||
} else if !r.legacyDNSMode {
|
||||
exchangeResult := r.exchangeWithRules(ctx, message, options, true)
|
||||
response, transport, err = exchangeResult.response, exchangeResult.transport, exchangeResult.err
|
||||
} else {
|
||||
@@ -878,7 +792,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
responseAddrs, err = r.client.Lookup(ctx, transport, domain, options, nil)
|
||||
} else if !r.legacyAddressFilterMode {
|
||||
} else if !r.legacyDNSMode {
|
||||
responseAddrs, err = r.lookupWithRules(ctx, domain, options)
|
||||
} else {
|
||||
var (
|
||||
@@ -975,7 +889,7 @@ func (r *Router) ResetNetwork() {
|
||||
}
|
||||
}
|
||||
|
||||
func hasDirectLegacyAddressFilterItemsInDefaultRule(rule option.DefaultDNSRule) bool {
|
||||
func defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule option.DefaultDNSRule) bool {
|
||||
if rule.IPAcceptAny || rule.RuleSetIPCIDRAcceptEmpty {
|
||||
return true
|
||||
}
|
||||
@@ -989,7 +903,7 @@ func hasResponseMatchFields(rule option.DefaultDNSRule) bool {
|
||||
len(rule.ResponseExtra) > 0
|
||||
}
|
||||
|
||||
func defaultRuleForcesNewDNSPath(rule option.DefaultDNSRule) bool {
|
||||
func defaultRuleDisablesLegacyDNSMode(rule option.DefaultDNSRule) bool {
|
||||
return rule.MatchResponse ||
|
||||
hasResponseMatchFields(rule) ||
|
||||
rule.Action == C.RuleActionTypeEvaluate ||
|
||||
@@ -997,76 +911,84 @@ func defaultRuleForcesNewDNSPath(rule option.DefaultDNSRule) bool {
|
||||
len(rule.QueryType) > 0
|
||||
}
|
||||
|
||||
func resolveLegacyAddressFilterMode(router adapter.Router, rules []option.DNSRule) (bool, error) {
|
||||
forceNew, needsLegacy, err := dnsRuleModeRequirements(router, rules)
|
||||
func resolveLegacyDNSMode(router adapter.Router, rules []option.DNSRule) (bool, error) {
|
||||
legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, err := dnsRuleModeRequirements(router, rules)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if forceNew {
|
||||
if legacyDNSModeDisabled && needsLegacyDNSModeFromStrategy {
|
||||
return false, E.New("DNS rule action strategy is only supported in legacyDNSMode")
|
||||
}
|
||||
if legacyDNSModeDisabled {
|
||||
return false, nil
|
||||
}
|
||||
return needsLegacy, nil
|
||||
return needsLegacyDNSMode, nil
|
||||
}
|
||||
|
||||
func dnsRuleModeRequirements(router adapter.Router, rules []option.DNSRule) (bool, bool, error) {
|
||||
var forceNew bool
|
||||
var needsLegacy bool
|
||||
func dnsRuleModeRequirements(router adapter.Router, rules []option.DNSRule) (bool, bool, bool, error) {
|
||||
var legacyDNSModeDisabled bool
|
||||
var needsLegacyDNSMode bool
|
||||
var needsLegacyDNSModeFromStrategy bool
|
||||
for i, rule := range rules {
|
||||
ruleForceNew, ruleNeedsLegacy, err := dnsRuleModeRequirementsInRule(router, rule)
|
||||
ruleLegacyDNSModeDisabled, ruleNeedsLegacyDNSMode, ruleNeedsLegacyDNSModeFromStrategy, err := dnsRuleModeRequirementsInRule(router, rule)
|
||||
if err != nil {
|
||||
return false, false, E.Cause(err, "dns rule[", i, "]")
|
||||
return false, false, false, E.Cause(err, "dns rule[", i, "]")
|
||||
}
|
||||
forceNew = forceNew || ruleForceNew
|
||||
needsLegacy = needsLegacy || ruleNeedsLegacy
|
||||
legacyDNSModeDisabled = legacyDNSModeDisabled || ruleLegacyDNSModeDisabled
|
||||
needsLegacyDNSMode = needsLegacyDNSMode || ruleNeedsLegacyDNSMode
|
||||
needsLegacyDNSModeFromStrategy = needsLegacyDNSModeFromStrategy || ruleNeedsLegacyDNSModeFromStrategy
|
||||
}
|
||||
return forceNew, needsLegacy, nil
|
||||
return legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, nil
|
||||
}
|
||||
|
||||
func dnsRuleModeRequirementsInRule(router adapter.Router, rule option.DNSRule) (bool, bool, error) {
|
||||
func dnsRuleModeRequirementsInRule(router adapter.Router, rule option.DNSRule) (bool, bool, bool, error) {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return dnsRuleModeRequirementsInDefaultRule(router, rule.DefaultOptions)
|
||||
case C.RuleTypeLogical:
|
||||
forceNew := dnsRuleActionType(rule) == C.RuleActionTypeEvaluate
|
||||
var needsLegacy bool
|
||||
legacyDNSModeDisabled := dnsRuleActionType(rule) == C.RuleActionTypeEvaluate
|
||||
needsLegacyDNSModeFromStrategy := dnsRuleActionHasStrategy(rule.LogicalOptions.DNSRuleAction)
|
||||
needsLegacyDNSMode := needsLegacyDNSModeFromStrategy
|
||||
for i, subRule := range rule.LogicalOptions.Rules {
|
||||
subForceNew, subNeedsLegacy, err := dnsRuleModeRequirementsInRule(router, subRule)
|
||||
subLegacyDNSModeDisabled, subNeedsLegacyDNSMode, subNeedsLegacyDNSModeFromStrategy, err := dnsRuleModeRequirementsInRule(router, subRule)
|
||||
if err != nil {
|
||||
return false, false, E.Cause(err, "sub rule[", i, "]")
|
||||
return false, false, false, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
forceNew = forceNew || subForceNew
|
||||
needsLegacy = needsLegacy || subNeedsLegacy
|
||||
legacyDNSModeDisabled = legacyDNSModeDisabled || subLegacyDNSModeDisabled
|
||||
needsLegacyDNSMode = needsLegacyDNSMode || subNeedsLegacyDNSMode
|
||||
needsLegacyDNSModeFromStrategy = needsLegacyDNSModeFromStrategy || subNeedsLegacyDNSModeFromStrategy
|
||||
}
|
||||
return forceNew, needsLegacy, nil
|
||||
return legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, nil
|
||||
default:
|
||||
return false, false, nil
|
||||
return false, false, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleModeRequirementsInDefaultRule(router adapter.Router, rule option.DefaultDNSRule) (bool, bool, error) {
|
||||
forceNew := defaultRuleForcesNewDNSPath(rule)
|
||||
needsLegacy := hasDirectLegacyAddressFilterItemsInDefaultRule(rule)
|
||||
func dnsRuleModeRequirementsInDefaultRule(router adapter.Router, rule option.DefaultDNSRule) (bool, bool, bool, error) {
|
||||
legacyDNSModeDisabled := defaultRuleDisablesLegacyDNSMode(rule)
|
||||
needsLegacyDNSModeFromStrategy := dnsRuleActionHasStrategy(rule.DNSRuleAction)
|
||||
needsLegacyDNSMode := defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule) || needsLegacyDNSModeFromStrategy
|
||||
if len(rule.RuleSet) == 0 {
|
||||
return forceNew, needsLegacy, nil
|
||||
return legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, nil
|
||||
}
|
||||
if router == nil {
|
||||
return false, false, E.New("router service not found")
|
||||
return false, false, false, E.New("router service not found")
|
||||
}
|
||||
for _, tag := range rule.RuleSet {
|
||||
ruleSet, loaded := router.RuleSet(tag)
|
||||
if !loaded {
|
||||
return false, false, E.New("rule-set not found: ", tag)
|
||||
return false, false, false, E.New("rule-set not found: ", tag)
|
||||
}
|
||||
metadata := ruleSet.Metadata()
|
||||
// Rule sets are built from headless rules, so query_type is the only
|
||||
// per-query DNS predicate they can contribute here. ip_version is not a
|
||||
// headless-rule item and is therefore intentionally absent from metadata.
|
||||
forceNew = forceNew || metadata.ContainsDNSQueryTypeRule
|
||||
legacyDNSModeDisabled = legacyDNSModeDisabled || metadata.ContainsDNSQueryTypeRule
|
||||
if !rule.RuleSetIPCIDRMatchSource && metadata.ContainsIPCIDRRule {
|
||||
needsLegacy = true
|
||||
needsLegacyDNSMode = true
|
||||
}
|
||||
}
|
||||
return forceNew, needsLegacy, nil
|
||||
return legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, nil
|
||||
}
|
||||
|
||||
func referencedDNSRuleSetTags(rules []option.DNSRule) []string {
|
||||
@@ -1096,9 +1018,9 @@ func referencedDNSRuleSetTags(rules []option.DNSRule) []string {
|
||||
return tags
|
||||
}
|
||||
|
||||
func validateNonLegacyAddressFilterRules(rules []option.DNSRule) error {
|
||||
func validateLegacyDNSModeDisabledRules(rules []option.DNSRule) error {
|
||||
for i, rule := range rules {
|
||||
consumesResponse, err := validateNonLegacyAddressFilterRuleTree(rule)
|
||||
consumesResponse, err := validateLegacyDNSModeDisabledRuleTree(rule)
|
||||
if err != nil {
|
||||
return E.Cause(err, "validate dns rule[", i, "]")
|
||||
}
|
||||
@@ -1110,14 +1032,14 @@ func validateNonLegacyAddressFilterRules(rules []option.DNSRule) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNonLegacyAddressFilterRuleTree(rule option.DNSRule) (bool, error) {
|
||||
func validateLegacyDNSModeDisabledRuleTree(rule option.DNSRule) (bool, error) {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return validateNonLegacyAddressFilterDefaultRule(rule.DefaultOptions)
|
||||
return validateLegacyDNSModeDisabledDefaultRule(rule.DefaultOptions)
|
||||
case C.RuleTypeLogical:
|
||||
var consumesResponse bool
|
||||
for i, subRule := range rule.LogicalOptions.Rules {
|
||||
subConsumesResponse, err := validateNonLegacyAddressFilterRuleTree(subRule)
|
||||
subConsumesResponse, err := validateLegacyDNSModeDisabledRuleTree(subRule)
|
||||
if err != nil {
|
||||
return false, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
@@ -1129,13 +1051,13 @@ func validateNonLegacyAddressFilterRuleTree(rule option.DNSRule) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func validateNonLegacyAddressFilterDefaultRule(rule option.DefaultDNSRule) (bool, error) {
|
||||
func validateLegacyDNSModeDisabledDefaultRule(rule option.DefaultDNSRule) (bool, error) {
|
||||
hasResponseRecords := hasResponseMatchFields(rule)
|
||||
if hasResponseRecords && !rule.MatchResponse {
|
||||
return false, E.New("response_* items require match_response")
|
||||
}
|
||||
if (len(rule.IPCIDR) > 0 || rule.IPIsPrivate) && !rule.MatchResponse {
|
||||
return false, E.New("ip_cidr and ip_is_private require match_response in DNS evaluate mode")
|
||||
return false, E.New("ip_cidr and ip_is_private require match_response when legacyDNSMode is disabled")
|
||||
}
|
||||
// Intentionally do not reject rule_set here. A referenced rule set may mix
|
||||
// destination-IP predicates with pre-response predicates such as domain items.
|
||||
@@ -1143,14 +1065,48 @@ func validateNonLegacyAddressFilterDefaultRule(rule option.DefaultDNSRule) (bool
|
||||
// pre-response evaluation instead of consuming DNS response state, while sibling
|
||||
// non-response branches remain matchable.
|
||||
if rule.IPAcceptAny {
|
||||
return false, E.New("ip_accept_any is removed in DNS evaluate mode, use ip_cidr with match_response")
|
||||
return false, E.New("ip_accept_any is removed when legacyDNSMode is disabled, use ip_cidr with match_response")
|
||||
}
|
||||
if rule.RuleSetIPCIDRAcceptEmpty {
|
||||
return false, E.New("rule_set_ip_cidr_accept_empty is removed in DNS evaluate mode")
|
||||
return false, E.New("rule_set_ip_cidr_accept_empty is removed when legacyDNSMode is disabled")
|
||||
}
|
||||
return rule.MatchResponse, nil
|
||||
}
|
||||
|
||||
func hasDNSRuleActionStrategy(rules []option.DNSRule) bool {
|
||||
for _, rule := range rules {
|
||||
if dnsRuleHasActionStrategy(rule) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dnsRuleHasActionStrategy(rule option.DNSRule) bool {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return dnsRuleActionHasStrategy(rule.DefaultOptions.DNSRuleAction)
|
||||
case C.RuleTypeLogical:
|
||||
if dnsRuleActionHasStrategy(rule.LogicalOptions.DNSRuleAction) {
|
||||
return true
|
||||
}
|
||||
return hasDNSRuleActionStrategy(rule.LogicalOptions.Rules)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleActionHasStrategy(action option.DNSRuleAction) bool {
|
||||
switch action.Action {
|
||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
||||
return C.DomainStrategy(action.RouteOptions.Strategy) != C.DomainStrategyAsIS
|
||||
case C.RuleActionTypeRouteOptions:
|
||||
return C.DomainStrategy(action.RouteOptionsOptions.Strategy) != C.DomainStrategyAsIS
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleActionType(rule option.DNSRule) string {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
|
||||
@@ -296,10 +296,10 @@ func fixedHTTPSHintResponse(question mDNS.Question, addresses ...netip.Addr) *mD
|
||||
return response
|
||||
}
|
||||
|
||||
func TestValidateNewDNSRules_RequireMatchResponseForDirectIPCIDR(t *testing.T) {
|
||||
func TestValidateLegacyDNSModeDisabledRules_RequireMatchResponseForDirectIPCIDR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := validateNonLegacyAddressFilterRules([]option.DNSRule{{
|
||||
err := validateLegacyDNSModeDisabledRules([]option.DNSRule{{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
@@ -316,10 +316,10 @@ func TestValidateNewDNSRules_RequireMatchResponseForDirectIPCIDR(t *testing.T) {
|
||||
require.ErrorContains(t, err, "ip_cidr and ip_is_private require match_response")
|
||||
}
|
||||
|
||||
func TestValidateNewDNSRules_AllowMatchResponseWithoutEvaluate(t *testing.T) {
|
||||
func TestValidateLegacyDNSModeDisabledRules_AllowMatchResponseWithoutEvaluate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := validateNonLegacyAddressFilterRules([]option.DNSRule{{
|
||||
err := validateLegacyDNSModeDisabledRules([]option.DNSRule{{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
@@ -419,7 +419,7 @@ func TestInitializeRejectsDirectLegacyRuleWhenRuleSetForcesNew(t *testing.T) {
|
||||
require.ErrorContains(t, err, "ip_cidr and ip_is_private require match_response")
|
||||
}
|
||||
|
||||
func TestLookupLegacyModeDefersRuleSetDestinationIPMatch(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDefersRuleSetDestinationIPMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -470,7 +470,7 @@ func TestLookupLegacyModeDefersRuleSetDestinationIPMatch(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
require.True(t, router.legacyAddressFilterMode)
|
||||
require.True(t, router.legacyDNSMode)
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
|
||||
LookupStrategy: C.DomainStrategyIPv4Only,
|
||||
@@ -566,7 +566,7 @@ func TestRuleSetUpdateSetsRuntimeErrorWhenRebuildFails(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
require.True(t, router.legacyAddressFilterMode)
|
||||
require.True(t, router.legacyDNSMode)
|
||||
|
||||
fakeSet.updateMetadata(adapter.RuleSetMetadata{
|
||||
ContainsDNSQueryTypeRule: true,
|
||||
@@ -642,7 +642,7 @@ func TestCloseIgnoresSnapshottedRuleSetCallback(t *testing.T) {
|
||||
require.NoError(t, router.runtimeRuleError)
|
||||
}
|
||||
|
||||
func TestLookupLegacyModeDefersDirectDestinationIPMatch(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDefersDirectDestinationIPMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -680,7 +680,7 @@ func TestLookupLegacyModeDefersDirectDestinationIPMatch(t *testing.T) {
|
||||
},
|
||||
}, client)
|
||||
|
||||
require.True(t, router.legacyAddressFilterMode)
|
||||
require.True(t, router.legacyDNSMode)
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
|
||||
LookupStrategy: C.DomainStrategyIPv4Only,
|
||||
@@ -689,7 +689,7 @@ func TestLookupLegacyModeDefersDirectDestinationIPMatch(t *testing.T) {
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("10.0.0.1")}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupLegacyModeFallsBackAfterRejectedAddressLimitResponse(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeFallsBackAfterRejectedAddressLimitResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -738,7 +738,7 @@ func TestLookupLegacyModeFallsBackAfterRejectedAddressLimitResponse(t *testing.T
|
||||
require.Equal(t, []string{"private", "default"}, lookups)
|
||||
}
|
||||
|
||||
func TestLookupLegacyModeRuleSetAcceptEmptyDoesNotTreatMismatchAsEmpty(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeRuleSetAcceptEmptyDoesNotTreatMismatchAsEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -810,7 +810,7 @@ func TestLookupLegacyModeRuleSetAcceptEmptyDoesNotTreatMismatchAsEmpty(t *testin
|
||||
},
|
||||
})
|
||||
|
||||
require.True(t, router.legacyAddressFilterMode)
|
||||
require.True(t, router.legacyDNSMode)
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
|
||||
LookupStrategy: C.DomainStrategyIPv4Only,
|
||||
@@ -831,7 +831,7 @@ func TestDNSResponseAddressesMatchesMessageToAddressesForHTTPSHints(t *testing.T
|
||||
require.Equal(t, MessageToAddresses(response), adapter.DNSResponseAddresses(response))
|
||||
}
|
||||
|
||||
func TestExchangeNewModeEvaluateMatchResponseRoute(t *testing.T) {
|
||||
func TestExchangeLegacyDNSModeDisabledEvaluateMatchResponseRoute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
transportManager := &fakeDNSTransportManager{
|
||||
@@ -890,7 +890,7 @@ func TestExchangeNewModeEvaluateMatchResponseRoute(t *testing.T) {
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response))
|
||||
}
|
||||
|
||||
func TestExchangeNewModeEvaluateMatchResponseRouteIgnoresTTL(t *testing.T) {
|
||||
func TestExchangeLegacyDNSModeDisabledEvaluateMatchResponseRouteIgnoresTTL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
transportManager := &fakeDNSTransportManager{
|
||||
@@ -949,7 +949,7 @@ func TestExchangeNewModeEvaluateMatchResponseRouteIgnoresTTL(t *testing.T) {
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response))
|
||||
}
|
||||
|
||||
func TestExchangeNewModeEvaluateMatchResponseRouteWithHTTPSHints(t *testing.T) {
|
||||
func TestExchangeLegacyDNSModeDisabledEvaluateMatchResponseRouteWithHTTPSHints(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
transportManager := &fakeDNSTransportManager{
|
||||
@@ -1008,7 +1008,7 @@ func TestExchangeNewModeEvaluateMatchResponseRouteWithHTTPSHints(t *testing.T) {
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response))
|
||||
}
|
||||
|
||||
func TestExchangeNewModeEvaluateDoesNotLeakAddressesToNextQuery(t *testing.T) {
|
||||
func TestExchangeLegacyDNSModeDisabledEvaluateDoesNotLeakAddressesToNextQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
transportManager := &fakeDNSTransportManager{
|
||||
@@ -1079,7 +1079,7 @@ func TestExchangeNewModeEvaluateDoesNotLeakAddressesToNextQuery(t *testing.T) {
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response))
|
||||
}
|
||||
|
||||
func TestExchangeNewModeEvaluateRouteResolutionFailureClearsResponse(t *testing.T) {
|
||||
func TestExchangeLegacyDNSModeDisabledEvaluateRouteResolutionFailureClearsResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
transportManager := &fakeDNSTransportManager{
|
||||
@@ -1152,7 +1152,7 @@ func TestExchangeNewModeEvaluateRouteResolutionFailureClearsResponse(t *testing.
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("4.4.4.4")}, MessageToAddresses(response))
|
||||
}
|
||||
|
||||
func TestExchangeNewModeEvaluateExchangeFailureUsesMatchResponseBooleanSemantics(t *testing.T) {
|
||||
func TestExchangeLegacyDNSModeDisabledEvaluateExchangeFailureUsesMatchResponseBooleanSemantics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
@@ -1235,7 +1235,7 @@ func TestExchangeNewModeEvaluateExchangeFailureUsesMatchResponseBooleanSemantics
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupNewModeAllowsPartialSuccess(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledAllowsPartialSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -1257,14 +1257,14 @@ func TestLookupNewModeAllowsPartialSuccess(t *testing.T) {
|
||||
}
|
||||
},
|
||||
})
|
||||
router.legacyAddressFilterMode = false
|
||||
router.legacyDNSMode = false
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupNewModeSkipsFakeIPRule(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledSkipsFakeIPRule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -1294,14 +1294,14 @@ func TestLookupNewModeSkipsFakeIPRule(t *testing.T) {
|
||||
return FixedResponse(0, message.Question[0], nil, 60), nil
|
||||
},
|
||||
})
|
||||
router.legacyAddressFilterMode = false
|
||||
router.legacyDNSMode = 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 TestLookupNewModeEvaluateSkipFakeIPPreservesResponse(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledEvaluateSkipFakeIPPreservesResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -1374,14 +1374,14 @@ func TestLookupNewModeEvaluateSkipFakeIPPreservesResponse(t *testing.T) {
|
||||
}
|
||||
},
|
||||
})
|
||||
router.legacyAddressFilterMode = false
|
||||
router.legacyDNSMode = 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 TestLookupNewModeUsesQueryTypeRule(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledUsesQueryTypeRule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -1417,14 +1417,14 @@ func TestLookupNewModeUsesQueryTypeRule(t *testing.T) {
|
||||
}
|
||||
},
|
||||
})
|
||||
require.False(t, router.legacyAddressFilterMode)
|
||||
require.False(t, router.legacyDNSMode)
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("9.9.9.9")}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupNewModeUsesRuleSetQueryTypeRule(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledUsesRuleSetQueryTypeRule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -1483,7 +1483,7 @@ func TestLookupNewModeUsesRuleSetQueryTypeRule(t *testing.T) {
|
||||
}
|
||||
},
|
||||
})
|
||||
require.False(t, router.legacyAddressFilterMode)
|
||||
require.False(t, router.legacyDNSMode)
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
|
||||
require.NoError(t, err)
|
||||
@@ -1493,7 +1493,7 @@ func TestLookupNewModeUsesRuleSetQueryTypeRule(t *testing.T) {
|
||||
}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupNewModeUsesIPVersionRule(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledUsesIPVersionRule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -1532,73 +1532,113 @@ func TestLookupNewModeUsesIPVersionRule(t *testing.T) {
|
||||
}
|
||||
},
|
||||
})
|
||||
require.False(t, router.legacyAddressFilterMode)
|
||||
require.False(t, router.legacyDNSMode)
|
||||
|
||||
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) {
|
||||
func TestInitializeRejectsDNSRuleStrategyWhenLegacyDNSModeIsDisabledByEvaluate(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),
|
||||
rules: make([]adapter.DNSRule, 0, 1),
|
||||
defaultDomainStrategy: C.DomainStrategyAsIS,
|
||||
}
|
||||
err := router.Initialize([]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: "default",
|
||||
Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only),
|
||||
},
|
||||
},
|
||||
},
|
||||
}})
|
||||
require.ErrorContains(t, err, "legacyDNSMode")
|
||||
}
|
||||
|
||||
func TestInitializeRejectsDNSRuleStrategyWhenLegacyDNSModeIsDisabledByMatchResponse(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),
|
||||
rules: make([]adapter.DNSRule, 0, 1),
|
||||
defaultDomainStrategy: C.DomainStrategyAsIS,
|
||||
}
|
||||
err := router.Initialize([]option.DNSRule{{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
MatchResponse: true,
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRouteOptions,
|
||||
RouteOptionsOptions: option.DNSRouteOptionsActionOptions{
|
||||
Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only),
|
||||
},
|
||||
},
|
||||
},
|
||||
}})
|
||||
require.ErrorContains(t, err, "legacyDNSMode")
|
||||
}
|
||||
|
||||
func TestLookupLegacyDNSModeUsesRouteStrategy(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: "default"},
|
||||
selectedTransport := &fakeDNSTransport{tag: "selected", 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.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{
|
||||
Server: "selected",
|
||||
Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
MatchResponse: true,
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{
|
||||
Server: "selected",
|
||||
Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &fakeDNSTransportManager{
|
||||
}}, &fakeDNSTransportManager{
|
||||
defaultTransport: defaultTransport,
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"default": defaultTransport,
|
||||
"selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP},
|
||||
"selected": selectedTransport,
|
||||
},
|
||||
}, &fakeDNSClient{
|
||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
if transport.Tag() == "default" {
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
|
||||
}
|
||||
switch message.Question[0].Qtype {
|
||||
case mDNS.TypeA:
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil
|
||||
case mDNS.TypeAAAA:
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil
|
||||
default:
|
||||
return nil, errors.New("unexpected qtype")
|
||||
}
|
||||
lookup: func(transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, *mDNS.Msg, error) {
|
||||
require.Equal(t, "selected", transport.Tag())
|
||||
require.Equal(t, C.DomainStrategyIPv4Only, options.Strategy)
|
||||
return []netip.Addr{netip.MustParseAddr("2.2.2.2")}, nil, nil
|
||||
},
|
||||
})
|
||||
|
||||
require.True(t, router.legacyDNSMode)
|
||||
|
||||
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 TestLookupNewModeReturnsRejectedErrorForRejectAction(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledReturnsRejectedErrorForRejectAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -1623,7 +1663,7 @@ func TestLookupNewModeReturnsRejectedErrorForRejectAction(t *testing.T) {
|
||||
"default": defaultTransport,
|
||||
},
|
||||
}, &fakeDNSClient{})
|
||||
require.False(t, router.legacyAddressFilterMode)
|
||||
require.False(t, router.legacyDNSMode)
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
|
||||
require.Nil(t, addresses)
|
||||
@@ -1631,7 +1671,7 @@ func TestLookupNewModeReturnsRejectedErrorForRejectAction(t *testing.T) {
|
||||
require.True(t, rulepkg.IsRejected(err))
|
||||
}
|
||||
|
||||
func TestExchangeNewModeReturnsRefusedResponseForRejectAction(t *testing.T) {
|
||||
func TestExchangeLegacyDNSModeDisabledReturnsRefusedResponseForRejectAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -1656,7 +1696,7 @@ func TestExchangeNewModeReturnsRefusedResponseForRejectAction(t *testing.T) {
|
||||
"default": defaultTransport,
|
||||
},
|
||||
}, &fakeDNSClient{})
|
||||
require.False(t, router.legacyAddressFilterMode)
|
||||
require.False(t, router.legacyDNSMode)
|
||||
|
||||
response, err := router.Exchange(context.Background(), &mDNS.Msg{
|
||||
Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)},
|
||||
@@ -1666,7 +1706,7 @@ func TestExchangeNewModeReturnsRefusedResponseForRejectAction(t *testing.T) {
|
||||
require.Equal(t, []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)}, response.Question)
|
||||
}
|
||||
|
||||
func TestLookupNewModeFiltersPerQueryTypeAddressesBeforeMerging(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledFiltersPerQueryTypeAddressesBeforeMerging(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
@@ -1694,7 +1734,7 @@ func TestLookupNewModeFiltersPerQueryTypeAddressesBeforeMerging(t *testing.T) {
|
||||
"default": defaultTransport,
|
||||
},
|
||||
}, &fakeDNSClient{})
|
||||
require.False(t, router.legacyAddressFilterMode)
|
||||
require.False(t, router.legacyDNSMode)
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{})
|
||||
require.NoError(t, err)
|
||||
@@ -1704,241 +1744,64 @@ func TestLookupNewModeFiltersPerQueryTypeAddressesBeforeMerging(t *testing.T) {
|
||||
}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupNewModePrefersExplicitBranchStrategyOverDefault(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledUsesInputStrategy(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{
|
||||
var qTypes []uint16
|
||||
router := newTestRouter(t, nil, &fakeDNSTransportManager{
|
||||
defaultTransport: defaultTransport,
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"default": defaultTransport,
|
||||
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
|
||||
"selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP},
|
||||
"default": defaultTransport,
|
||||
},
|
||||
}, &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":
|
||||
qTypes = append(qTypes, message.Question[0].Qtype)
|
||||
if message.Question[0].Qtype == mDNS.TypeA {
|
||||
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")
|
||||
}
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::2")}, 60), nil
|
||||
},
|
||||
})
|
||||
router.legacyAddressFilterMode = false
|
||||
router.legacyDNSMode = false
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
|
||||
Strategy: C.DomainStrategyIPv4Only,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint16{mDNS.TypeA}, qTypes)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
|
||||
}
|
||||
|
||||
func TestLookupNewModeKeepsConflictingExplicitBranchStrategies(t *testing.T) {
|
||||
func TestLookupLegacyDNSModeDisabledUsesDefaultStrategy(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{
|
||||
var qTypes []uint16
|
||||
router := newTestRouter(t, nil, &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},
|
||||
"default": defaultTransport,
|
||||
},
|
||||
}, &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":
|
||||
qTypes = append(qTypes, message.Question[0].Qtype)
|
||||
if message.Question[0].Qtype == mDNS.TypeA {
|
||||
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")
|
||||
}
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::2")}, 60), nil
|
||||
},
|
||||
})
|
||||
router.legacyAddressFilterMode = false
|
||||
router.defaultDomainStrategy = C.DomainStrategyIPv4Only
|
||||
router.legacyDNSMode = 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)
|
||||
require.Equal(t, []uint16{mDNS.TypeA}, qTypes)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
|
||||
}
|
||||
|
||||
func TestExchangeNewModeLogicalMatchResponseIPCIDRFallsThrough(t *testing.T) {
|
||||
func TestExchangeLegacyDNSModeDisabledLogicalMatchResponseIPCIDRFallsThrough(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
transportManager := &fakeDNSTransportManager{
|
||||
@@ -2007,7 +1870,7 @@ func TestExchangeNewModeLogicalMatchResponseIPCIDRFallsThrough(t *testing.T) {
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("4.4.4.4")}, MessageToAddresses(response))
|
||||
}
|
||||
|
||||
func TestOldModeReportsLegacyAddressFilterDeprecation(t *testing.T) {
|
||||
func TestLegacyDNSModeReportsLegacyAddressFilterDeprecation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := &fakeDeprecatedManager{}
|
||||
@@ -2038,3 +1901,38 @@ func TestOldModeReportsLegacyAddressFilterDeprecation(t *testing.T) {
|
||||
require.Len(t, manager.features, 1)
|
||||
require.Equal(t, deprecated.OptionLegacyDNSAddressFilter.Name, manager.features[0].Name)
|
||||
}
|
||||
|
||||
func TestLegacyDNSModeReportsDNSRuleStrategyDeprecation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := &fakeDeprecatedManager{}
|
||||
ctx := service.ContextWith[deprecated.Manager](context.Background(), manager)
|
||||
router := &Router{
|
||||
ctx: ctx,
|
||||
logger: log.NewNOPFactory().NewLogger("dns"),
|
||||
client: &fakeDNSClient{},
|
||||
rules: make([]adapter.DNSRule, 0, 1),
|
||||
defaultDomainStrategy: C.DomainStrategyAsIS,
|
||||
}
|
||||
err := router.Initialize([]option.DNSRule{{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
Domain: badoption.Listable[string]{"example.com"},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{
|
||||
Server: "default",
|
||||
Strategy: option.DomainStrategy(C.DomainStrategyIPv4Only),
|
||||
},
|
||||
},
|
||||
},
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.Start(adapter.StartStateStart)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, manager.features, 1)
|
||||
require.Equal(t, deprecated.OptionLegacyDNSRuleStrategy.Name, manager.features[0].Name)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,11 @@ Tag of target server.
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Set domain strategy for this query.
|
||||
!!! warning
|
||||
|
||||
`strategy` is deprecated and only supported in `legacyDNSMode`.
|
||||
|
||||
Set domain strategy for this query in `legacyDNSMode`.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
|
||||
@@ -34,7 +34,11 @@ icon: material/new-box
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
为此查询设置域名策略。
|
||||
!!! warning
|
||||
|
||||
`strategy` 已废弃,且仅在 `legacyDNSMode` 中可用。
|
||||
|
||||
在 `legacyDNSMode` 中为此查询设置域名策略。
|
||||
|
||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
|
||||
|
||||
@@ -14,6 +14,13 @@ check [Migration](../migration/#migrate-inline-acme-to-certificate-provider).
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
#### `strategy` in DNS rule actions
|
||||
|
||||
`strategy` in DNS rule actions is deprecated
|
||||
and only supported in `legacyDNSMode`.
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
## 1.12.0
|
||||
|
||||
#### Legacy DNS server formats
|
||||
|
||||
@@ -117,6 +117,14 @@ var OptionLegacyDNSAddressFilter = Note{
|
||||
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule/",
|
||||
}
|
||||
|
||||
var OptionLegacyDNSRuleStrategy = Note{
|
||||
Name: "legacy-dns-rule-strategy",
|
||||
Description: "`strategy` in DNS rule actions",
|
||||
DeprecatedVersion: "1.14.0",
|
||||
ScheduledVersion: "1.16.0",
|
||||
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule_action/",
|
||||
}
|
||||
|
||||
var Options = []Note{
|
||||
OptionOutboundDNSRuleItem,
|
||||
OptionMissingDomainResolver,
|
||||
@@ -125,4 +133,5 @@ var Options = []Note{
|
||||
OptionIPAcceptAny,
|
||||
OptionRuleSetIPCIDRAcceptEmpty,
|
||||
OptionLegacyDNSAddressFilter,
|
||||
OptionLegacyDNSRuleStrategy,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DNSRule, checkServer bool, legacyAddressFilter bool) (adapter.DNSRule, error) {
|
||||
func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DNSRule, checkServer bool, legacyDNSMode bool) (adapter.DNSRule, error) {
|
||||
switch options.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
if !options.DefaultOptions.IsValid() {
|
||||
@@ -30,7 +30,7 @@ func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DN
|
||||
return nil, E.New("missing server field")
|
||||
}
|
||||
}
|
||||
return NewDefaultDNSRule(ctx, logger, options.DefaultOptions, legacyAddressFilter)
|
||||
return NewDefaultDNSRule(ctx, logger, options.DefaultOptions, legacyDNSMode)
|
||||
case C.RuleTypeLogical:
|
||||
if !options.LogicalOptions.IsValid() {
|
||||
return nil, E.New("missing conditions")
|
||||
@@ -44,7 +44,7 @@ func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DN
|
||||
return nil, E.New("missing server field")
|
||||
}
|
||||
}
|
||||
return NewLogicalDNSRule(ctx, logger, options.LogicalOptions, legacyAddressFilter)
|
||||
return NewLogicalDNSRule(ctx, logger, options.LogicalOptions, legacyDNSMode)
|
||||
default:
|
||||
return nil, E.New("unknown rule type: ", options.Type)
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func (r *DefaultDNSRule) matchStates(metadata *adapter.InboundContext) ruleMatch
|
||||
return r.abstractDefaultRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule, legacyAddressFilter bool) (*DefaultDNSRule, error) {
|
||||
func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule, legacyDNSMode bool) (*DefaultDNSRule, error) {
|
||||
rule := &DefaultDNSRule{
|
||||
abstractDefaultRule: abstractDefaultRule{
|
||||
invert: options.Invert,
|
||||
@@ -163,10 +163,10 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if options.IPAcceptAny {
|
||||
if legacyAddressFilter {
|
||||
if legacyDNSMode {
|
||||
deprecated.Report(ctx, deprecated.OptionIPAcceptAny)
|
||||
} else {
|
||||
return nil, E.New("ip_accept_any is removed in DNS evaluate mode, use ip_cidr with match_response")
|
||||
return nil, E.New("ip_accept_any is removed when legacyDNSMode is disabled, use ip_cidr with match_response")
|
||||
}
|
||||
item := NewIPAcceptAnyItem()
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
@@ -321,10 +321,10 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
||||
matchSource = true
|
||||
}
|
||||
if options.RuleSetIPCIDRAcceptEmpty {
|
||||
if legacyAddressFilter {
|
||||
if legacyDNSMode {
|
||||
deprecated.Report(ctx, deprecated.OptionRuleSetIPCIDRAcceptEmpty)
|
||||
} else {
|
||||
return nil, E.New("rule_set_ip_cidr_accept_empty is removed in DNS evaluate mode")
|
||||
return nil, E.New("rule_set_ip_cidr_accept_empty is removed when legacyDNSMode is disabled")
|
||||
}
|
||||
}
|
||||
item := NewRuleSetItem(router, options.RuleSet, matchSource, options.RuleSetIPCIDRAcceptEmpty)
|
||||
@@ -450,7 +450,7 @@ func (r *LogicalDNSRule) matchStatesForMatch(metadata *adapter.InboundContext) r
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule, legacyAddressFilter bool) (*LogicalDNSRule, error) {
|
||||
func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule, legacyDNSMode bool) (*LogicalDNSRule, error) {
|
||||
r := &LogicalDNSRule{
|
||||
abstractLogicalRule: abstractLogicalRule{
|
||||
rules: make([]adapter.HeadlessRule, len(options.Rules)),
|
||||
@@ -467,7 +467,7 @@ func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
||||
return nil, E.New("unknown logical mode: ", options.Mode)
|
||||
}
|
||||
for i, subRule := range options.Rules {
|
||||
rule, err := NewDNSRule(ctx, logger, subRule, false, legacyAddressFilter)
|
||||
rule, err := NewDNSRule(ctx, logger, subRule, false, legacyDNSMode)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user