Add options to custom DNS query timeout

This commit is contained in:
世界 2025-06-30 18:23:10 +08:00
parent 70371c3cbe
commit 0d8c15932f
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
12 changed files with 58 additions and 17 deletions

View File

@ -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)
} }

View File

@ -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{}),

View File

@ -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

View File

@ -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 {

View File

@ -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
} }

View File

@ -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 {

View File

@ -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
} }

View File

@ -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 {

View File

@ -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"`

View File

@ -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{}),

View File

@ -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,

View File

@ -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, r.Descriptions()...)
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))
}
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")
} }