Browse Source

Add options to custom DNS query timeout

世界 3 months ago
parent
commit
0d8c15932f

+ 4 - 0
adapter/dns.go

@@ -3,6 +3,7 @@ package adapter
 import (
 import (
 	"context"
 	"context"
 	"net/netip"
 	"net/netip"
+	"time"
 
 
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
@@ -36,6 +37,7 @@ type DNSQueryOptions struct {
 	Transport      DNSTransport
 	Transport      DNSTransport
 	Strategy       C.DomainStrategy
 	Strategy       C.DomainStrategy
 	LookupStrategy C.DomainStrategy
 	LookupStrategy C.DomainStrategy
+	Timeout        time.Duration
 	DisableCache   bool
 	DisableCache   bool
 	RewriteTTL     *uint32
 	RewriteTTL     *uint32
 	ClientSubnet   netip.Prefix
 	ClientSubnet   netip.Prefix
@@ -53,6 +55,7 @@ func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptio
 	return &DNSQueryOptions{
 	return &DNSQueryOptions{
 		Transport:    transport,
 		Transport:    transport,
 		Strategy:     C.DomainStrategy(options.Strategy),
 		Strategy:     C.DomainStrategy(options.Strategy),
+		Timeout:      time.Duration(options.Timeout),
 		DisableCache: options.DisableCache,
 		DisableCache: options.DisableCache,
 		RewriteTTL:   options.RewriteTTL,
 		RewriteTTL:   options.RewriteTTL,
 		ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
 		ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
@@ -70,6 +73,7 @@ type DNSTransport interface {
 	Type() string
 	Type() string
 	Tag() string
 	Tag() string
 	Dependencies() []string
 	Dependencies() []string
+	HasDetour() bool
 	Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
 	Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
 }
 }
 
 

+ 1 - 0
common/dialer/dialer.go

@@ -89,6 +89,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
 			dnsQueryOptions = adapter.DNSQueryOptions{
 			dnsQueryOptions = adapter.DNSQueryOptions{
 				Transport:    transport,
 				Transport:    transport,
 				Strategy:     strategy,
 				Strategy:     strategy,
+				Timeout:      time.Duration(dialOptions.DomainResolver.Timeout),
 				DisableCache: dialOptions.DomainResolver.DisableCache,
 				DisableCache: dialOptions.DomainResolver.DisableCache,
 				RewriteTTL:   dialOptions.DomainResolver.RewriteTTL,
 				RewriteTTL:   dialOptions.DomainResolver.RewriteTTL,
 				ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
 				ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),

+ 1 - 0
constant/timeout.go

@@ -9,6 +9,7 @@ const (
 	TCPTimeout                 = 15 * time.Second
 	TCPTimeout                 = 15 * time.Second
 	ReadPayloadTimeout         = 300 * time.Millisecond
 	ReadPayloadTimeout         = 300 * time.Millisecond
 	DNSTimeout                 = 10 * time.Second
 	DNSTimeout                 = 10 * time.Second
+	DirectDNSTimeout           = 5 * time.Second
 	UDPTimeout                 = 5 * time.Minute
 	UDPTimeout                 = 5 * time.Minute
 	DefaultURLTestInterval     = 3 * time.Minute
 	DefaultURLTestInterval     = 3 * time.Minute
 	DefaultURLTestIdleTimeout  = 30 * time.Minute
 	DefaultURLTestIdleTimeout  = 30 * time.Minute

+ 9 - 7
dns/client.go

@@ -30,7 +30,6 @@ var (
 var _ adapter.DNSClient = (*Client)(nil)
 var _ adapter.DNSClient = (*Client)(nil)
 
 
 type Client struct {
 type Client struct {
-	timeout          time.Duration
 	disableCache     bool
 	disableCache     bool
 	disableExpire    bool
 	disableExpire    bool
 	independentCache bool
 	independentCache bool
@@ -43,7 +42,6 @@ type Client struct {
 }
 }
 
 
 type ClientOptions struct {
 type ClientOptions struct {
-	Timeout          time.Duration
 	DisableCache     bool
 	DisableCache     bool
 	DisableExpire    bool
 	DisableExpire    bool
 	IndependentCache bool
 	IndependentCache bool
@@ -55,7 +53,6 @@ type ClientOptions struct {
 
 
 func NewClient(options ClientOptions) *Client {
 func NewClient(options ClientOptions) *Client {
 	client := &Client{
 	client := &Client{
-		timeout:          options.Timeout,
 		disableCache:     options.DisableCache,
 		disableCache:     options.DisableCache,
 		disableExpire:    options.DisableExpire,
 		disableExpire:    options.DisableExpire,
 		independentCache: options.IndependentCache,
 		independentCache: options.IndependentCache,
@@ -63,9 +60,6 @@ func NewClient(options ClientOptions) *Client {
 		initRDRCFunc:     options.RDRC,
 		initRDRCFunc:     options.RDRC,
 		logger:           options.Logger,
 		logger:           options.Logger,
 	}
 	}
-	if client.timeout == 0 {
-		client.timeout = C.DNSTimeout
-	}
 	cacheCapacity := options.CacheCapacity
 	cacheCapacity := options.CacheCapacity
 	if cacheCapacity < 1024 {
 	if cacheCapacity < 1024 {
 		cacheCapacity = 1024
 		cacheCapacity = 1024
@@ -153,7 +147,15 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
 			return nil, ErrResponseRejectedCached
 			return nil, ErrResponseRejectedCached
 		}
 		}
 	}
 	}
-	ctx, cancel := context.WithTimeout(ctx, c.timeout)
+	timeout := options.Timeout
+	if timeout == 0 {
+		if transport.HasDetour() {
+			timeout = C.DNSTimeout
+		} else {
+			timeout = C.DirectDNSTimeout
+		}
+	}
+	ctx, cancel := context.WithTimeout(ctx, timeout)
 	response, err := transport.Exchange(ctx, message)
 	response, err := transport.Exchange(ctx, message)
 	cancel()
 	cancel()
 	if err != nil {
 	if err != nil {

+ 6 - 0
dns/router.go

@@ -158,6 +158,9 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
 				if action.Strategy != C.DomainStrategyAsIS {
 				if action.Strategy != C.DomainStrategyAsIS {
 					options.Strategy = action.Strategy
 					options.Strategy = action.Strategy
 				}
 				}
+				if action.Timeout > 0 {
+					options.Timeout = action.Timeout
+				}
 				if isFakeIP || action.DisableCache {
 				if isFakeIP || action.DisableCache {
 					options.DisableCache = true
 					options.DisableCache = true
 				}
 				}
@@ -180,6 +183,9 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
 				if action.Strategy != C.DomainStrategyAsIS {
 				if action.Strategy != C.DomainStrategyAsIS {
 					options.Strategy = action.Strategy
 					options.Strategy = action.Strategy
 				}
 				}
+				if action.Timeout > 0 {
+					options.Timeout = action.Timeout
+				}
 				if action.DisableCache {
 				if action.DisableCache {
 					options.DisableCache = true
 					options.DisableCache = true
 				}
 				}

+ 6 - 0
dns/transport/dhcp/dhcp.go

@@ -41,6 +41,7 @@ type Transport struct {
 	dns.TransportAdapter
 	dns.TransportAdapter
 	ctx               context.Context
 	ctx               context.Context
 	dialer            N.Dialer
 	dialer            N.Dialer
+	hasDetour         bool
 	logger            logger.ContextLogger
 	logger            logger.ContextLogger
 	networkManager    adapter.NetworkManager
 	networkManager    adapter.NetworkManager
 	interfaceName     string
 	interfaceName     string
@@ -59,6 +60,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
 		TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions),
 		TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions),
 		ctx:              ctx,
 		ctx:              ctx,
 		dialer:           transportDialer,
 		dialer:           transportDialer,
+		hasDetour:        options.Detour != "",
 		logger:           logger,
 		logger:           logger,
 		networkManager:   service.FromContext[adapter.NetworkManager](ctx),
 		networkManager:   service.FromContext[adapter.NetworkManager](ctx),
 		interfaceName:    options.Interface,
 		interfaceName:    options.Interface,
@@ -89,6 +91,10 @@ func (t *Transport) Close() error {
 	return nil
 	return nil
 }
 }
 
 
+func (t *Transport) HasDetour() bool {
+	return t.hasDetour
+}
+
 func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
 func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
 	err := t.fetchServers()
 	err := t.fetchServers()
 	if err != nil {
 	if err != nil {

+ 6 - 0
dns/transport_adapter.go

@@ -14,6 +14,7 @@ type TransportAdapter struct {
 	transportType string
 	transportType string
 	transportTag  string
 	transportTag  string
 	dependencies  []string
 	dependencies  []string
+	hasDetour     bool
 	strategy      C.DomainStrategy
 	strategy      C.DomainStrategy
 	clientSubnet  netip.Prefix
 	clientSubnet  netip.Prefix
 }
 }
@@ -35,6 +36,7 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
 		transportType: transportType,
 		transportType: transportType,
 		transportTag:  transportTag,
 		transportTag:  transportTag,
 		dependencies:  dependencies,
 		dependencies:  dependencies,
+		hasDetour:     localOptions.Detour != "",
 		strategy:      C.DomainStrategy(localOptions.LegacyStrategy),
 		strategy:      C.DomainStrategy(localOptions.LegacyStrategy),
 		clientSubnet:  localOptions.LegacyClientSubnet,
 		clientSubnet:  localOptions.LegacyClientSubnet,
 	}
 	}
@@ -69,6 +71,10 @@ func (a *TransportAdapter) Dependencies() []string {
 	return a.dependencies
 	return a.dependencies
 }
 }
 
 
+func (a *TransportAdapter) HasDetour() bool {
+	return a.hasDetour
+}
+
 func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
 func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
 	return a.strategy
 	return a.strategy
 }
 }

+ 2 - 0
option/outbound.go

@@ -91,6 +91,7 @@ type DialerOptions struct {
 type _DomainResolveOptions struct {
 type _DomainResolveOptions struct {
 	Server       string                `json:"server"`
 	Server       string                `json:"server"`
 	Strategy     DomainStrategy        `json:"strategy,omitempty"`
 	Strategy     DomainStrategy        `json:"strategy,omitempty"`
+	Timeout      badoption.Duration    `json:"timeout,omitempty"`
 	DisableCache bool                  `json:"disable_cache,omitempty"`
 	DisableCache bool                  `json:"disable_cache,omitempty"`
 	RewriteTTL   *uint32               `json:"rewrite_ttl,omitempty"`
 	RewriteTTL   *uint32               `json:"rewrite_ttl,omitempty"`
 	ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
 	ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
@@ -102,6 +103,7 @@ func (o DomainResolveOptions) MarshalJSON() ([]byte, error) {
 	if o.Server == "" {
 	if o.Server == "" {
 		return []byte("{}"), nil
 		return []byte("{}"), nil
 	} else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) &&
 	} else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) &&
+		o.Timeout == 0 &&
 		!o.DisableCache &&
 		!o.DisableCache &&
 		o.RewriteTTL == nil &&
 		o.RewriteTTL == nil &&
 		o.ClientSubnet == nil {
 		o.ClientSubnet == nil {

+ 2 - 0
option/rule_action.go

@@ -180,6 +180,7 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
 type DNSRouteActionOptions struct {
 type DNSRouteActionOptions struct {
 	Server       string                `json:"server,omitempty"`
 	Server       string                `json:"server,omitempty"`
 	Strategy     DomainStrategy        `json:"strategy,omitempty"`
 	Strategy     DomainStrategy        `json:"strategy,omitempty"`
+	Timeout      badoption.Duration    `json:"timeout,omitempty"`
 	DisableCache bool                  `json:"disable_cache,omitempty"`
 	DisableCache bool                  `json:"disable_cache,omitempty"`
 	RewriteTTL   *uint32               `json:"rewrite_ttl,omitempty"`
 	RewriteTTL   *uint32               `json:"rewrite_ttl,omitempty"`
 	ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
 	ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
@@ -187,6 +188,7 @@ type DNSRouteActionOptions struct {
 
 
 type _DNSRouteOptionsActionOptions struct {
 type _DNSRouteOptionsActionOptions struct {
 	Strategy     DomainStrategy        `json:"strategy,omitempty"`
 	Strategy     DomainStrategy        `json:"strategy,omitempty"`
+	Timeout      badoption.Duration    `json:"timeout,omitempty"`
 	DisableCache bool                  `json:"disable_cache,omitempty"`
 	DisableCache bool                  `json:"disable_cache,omitempty"`
 	RewriteTTL   *uint32               `json:"rewrite_ttl,omitempty"`
 	RewriteTTL   *uint32               `json:"rewrite_ttl,omitempty"`
 	ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
 	ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`

+ 1 - 0
route/network.go

@@ -76,6 +76,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
 			DomainResolver: defaultDomainResolver.Server,
 			DomainResolver: defaultDomainResolver.Server,
 			DomainResolveOptions: adapter.DNSQueryOptions{
 			DomainResolveOptions: adapter.DNSQueryOptions{
 				Strategy:     C.DomainStrategy(defaultDomainResolver.Strategy),
 				Strategy:     C.DomainStrategy(defaultDomainResolver.Strategy),
+				Timeout:      time.Duration(defaultDomainResolver.Timeout),
 				DisableCache: defaultDomainResolver.DisableCache,
 				DisableCache: defaultDomainResolver.DisableCache,
 				RewriteTTL:   defaultDomainResolver.RewriteTTL,
 				RewriteTTL:   defaultDomainResolver.RewriteTTL,
 				ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}),
 				ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}),

+ 1 - 0
route/route.go

@@ -666,6 +666,7 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon
 		addresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{
 		addresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{
 			Transport:    transport,
 			Transport:    transport,
 			Strategy:     action.Strategy,
 			Strategy:     action.Strategy,
+			Timeout:      action.Timeout,
 			DisableCache: action.DisableCache,
 			DisableCache: action.DisableCache,
 			RewriteTTL:   action.RewriteTTL,
 			RewriteTTL:   action.RewriteTTL,
 			ClientSubnet: action.ClientSubnet,
 			ClientSubnet: action.ClientSubnet,

+ 19 - 10
route/rule/rule_action.go

@@ -113,6 +113,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
 			Server: action.RouteOptions.Server,
 			Server: action.RouteOptions.Server,
 			RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
 			RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
 				Strategy:     C.DomainStrategy(action.RouteOptions.Strategy),
 				Strategy:     C.DomainStrategy(action.RouteOptions.Strategy),
+				Timeout:      time.Duration(action.RouteOptions.Timeout),
 				DisableCache: action.RouteOptions.DisableCache,
 				DisableCache: action.RouteOptions.DisableCache,
 				RewriteTTL:   action.RouteOptions.RewriteTTL,
 				RewriteTTL:   action.RouteOptions.RewriteTTL,
 				ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
 				ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
@@ -121,6 +122,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
 	case C.RuleActionTypeRouteOptions:
 	case C.RuleActionTypeRouteOptions:
 		return &RuleActionDNSRouteOptions{
 		return &RuleActionDNSRouteOptions{
 			Strategy:     C.DomainStrategy(action.RouteOptionsOptions.Strategy),
 			Strategy:     C.DomainStrategy(action.RouteOptionsOptions.Strategy),
+			Timeout:      time.Duration(action.RouteOptionsOptions.Timeout),
 			DisableCache: action.RouteOptionsOptions.DisableCache,
 			DisableCache: action.RouteOptionsOptions.DisableCache,
 			RewriteTTL:   action.RouteOptionsOptions.RewriteTTL,
 			RewriteTTL:   action.RouteOptionsOptions.RewriteTTL,
 			ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
 			ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
@@ -235,20 +237,13 @@ func (r *RuleActionDNSRoute) Type() string {
 func (r *RuleActionDNSRoute) String() string {
 func (r *RuleActionDNSRoute) String() string {
 	var descriptions []string
 	var descriptions []string
 	descriptions = append(descriptions, r.Server)
 	descriptions = append(descriptions, r.Server)
-	if r.DisableCache {
-		descriptions = append(descriptions, "disable-cache")
-	}
-	if r.RewriteTTL != nil {
-		descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL))
-	}
-	if r.ClientSubnet.IsValid() {
-		descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
-	}
+	descriptions = append(descriptions, r.Descriptions()...)
 	return F.ToString("route(", strings.Join(descriptions, ","), ")")
 	return F.ToString("route(", strings.Join(descriptions, ","), ")")
 }
 }
 
 
 type RuleActionDNSRouteOptions struct {
 type RuleActionDNSRouteOptions struct {
 	Strategy     C.DomainStrategy
 	Strategy     C.DomainStrategy
+	Timeout      time.Duration
 	DisableCache bool
 	DisableCache bool
 	RewriteTTL   *uint32
 	RewriteTTL   *uint32
 	ClientSubnet netip.Prefix
 	ClientSubnet netip.Prefix
@@ -259,7 +254,17 @@ func (r *RuleActionDNSRouteOptions) Type() string {
 }
 }
 
 
 func (r *RuleActionDNSRouteOptions) String() string {
 func (r *RuleActionDNSRouteOptions) String() string {
+	return F.ToString("route-options(", strings.Join(r.Descriptions(), ","), ")")
+}
+
+func (r *RuleActionDNSRouteOptions) Descriptions() []string {
 	var descriptions []string
 	var descriptions []string
+	if r.Strategy != C.DomainStrategyAsIS {
+		descriptions = append(descriptions, F.ToString("strategy=", option.DomainStrategy(r.Strategy)))
+	}
+	if r.Timeout > 0 {
+		descriptions = append(descriptions, F.ToString("timeout=", r.Timeout.String()))
+	}
 	if r.DisableCache {
 	if r.DisableCache {
 		descriptions = append(descriptions, "disable-cache")
 		descriptions = append(descriptions, "disable-cache")
 	}
 	}
@@ -269,7 +274,7 @@ func (r *RuleActionDNSRouteOptions) String() string {
 	if r.ClientSubnet.IsValid() {
 	if r.ClientSubnet.IsValid() {
 		descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
 		descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
 	}
 	}
-	return F.ToString("route-options(", strings.Join(descriptions, ","), ")")
+	return descriptions
 }
 }
 
 
 type RuleActionDirect struct {
 type RuleActionDirect struct {
@@ -421,6 +426,7 @@ func (r *RuleActionSniff) String() string {
 type RuleActionResolve struct {
 type RuleActionResolve struct {
 	Server       string
 	Server       string
 	Strategy     C.DomainStrategy
 	Strategy     C.DomainStrategy
+	Timeout      time.Duration
 	DisableCache bool
 	DisableCache bool
 	RewriteTTL   *uint32
 	RewriteTTL   *uint32
 	ClientSubnet netip.Prefix
 	ClientSubnet netip.Prefix
@@ -438,6 +444,9 @@ func (r *RuleActionResolve) String() string {
 	if r.Strategy != C.DomainStrategyAsIS {
 	if r.Strategy != C.DomainStrategyAsIS {
 		options = append(options, F.ToString(option.DomainStrategy(r.Strategy)))
 		options = append(options, F.ToString(option.DomainStrategy(r.Strategy)))
 	}
 	}
+	if r.Timeout > 0 {
+		options = append(options, F.ToString("timeout=", r.Timeout.String()))
+	}
 	if r.DisableCache {
 	if r.DisableCache {
 		options = append(options, "disable_cache")
 		options = append(options, "disable_cache")
 	}
 	}