diff --git a/adapter/dns.go b/adapter/dns.go index 7545f1663..eeaf12a44 100644 --- a/adapter/dns.go +++ b/adapter/dns.go @@ -38,6 +38,7 @@ type DNSQueryOptions struct { DisableCache bool DisableOptimisticCache bool RewriteTTL *uint32 + Timeout time.Duration ClientSubnet netip.Prefix } @@ -56,6 +57,7 @@ func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptio DisableCache: options.DisableCache, DisableOptimisticCache: options.DisableOptimisticCache, RewriteTTL: options.RewriteTTL, + Timeout: time.Duration(options.Timeout), ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), }, nil } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index f78aa9f3d..f0e804309 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -97,6 +97,7 @@ func NewWithOptions(options Options) (N.Dialer, error) { dnsQueryOptions = adapter.DNSQueryOptions{ Transport: transport, Strategy: strategy, + Timeout: time.Duration(dialOptions.DomainResolver.Timeout), DisableCache: dialOptions.DomainResolver.DisableCache, DisableOptimisticCache: dialOptions.DomainResolver.DisableOptimisticCache, RewriteTTL: dialOptions.DomainResolver.RewriteTTL, diff --git a/dns/client.go b/dns/client.go index 318ee2322..72f264a60 100644 --- a/dns/client.go +++ b/dns/client.go @@ -222,7 +222,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m return nil, ErrResponseRejectedCached } } - response, err := c.exchangeToTransport(ctx, transport, message) + response, err := c.exchangeToTransport(ctx, transport, message, options.Timeout) if err != nil { return nil, err } @@ -497,7 +497,7 @@ func (c *Client) backgroundRefreshDNS(transport adapter.DNSTransport, question d go func() { defer c.backgroundRefresh.Delete(key) ctx := contextWithTransportTag(c.ctx, transport.Tag()) - response, err := c.exchangeToTransport(ctx, transport, message) + response, err := c.exchangeToTransport(ctx, transport, message, options.Timeout) if err != nil { if c.logger != nil { c.logger.DebugContext(ctx, "optimistic refresh failed for ", FqdnToDomain(question.Name), ": ", err) @@ -552,8 +552,11 @@ func stripDNSPadding(response *dns.Msg) { } } -func (c *Client) exchangeToTransport(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg) (*dns.Msg, error) { - ctx, cancel := context.WithTimeout(ctx, c.timeout) +func (c *Client) exchangeToTransport(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, timeout time.Duration) (*dns.Msg, error) { + if timeout == 0 { + timeout = c.timeout + } + ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() response, err := transport.Exchange(ctx, message) if err == nil { diff --git a/dns/router.go b/dns/router.go index c0d681abd..ceb9ea2cd 100644 --- a/dns/router.go +++ b/dns/router.go @@ -80,6 +80,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp } router.client = NewClient(ClientOptions{ Context: ctx, + Timeout: time.Duration(options.DNSClientOptions.Timeout), DisableCache: options.DNSClientOptions.DisableCache, DisableExpire: options.DNSClientOptions.DisableExpire, OptimisticTimeout: optimisticTimeout, @@ -314,6 +315,9 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak if action.RewriteTTL != nil { options.RewriteTTL = action.RewriteTTL } + if action.Timeout > 0 { + options.Timeout = action.Timeout + } if action.ClientSubnet.IsValid() { options.ClientSubnet = action.ClientSubnet } @@ -328,6 +332,9 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak if action.RewriteTTL != nil { options.RewriteTTL = action.RewriteTTL } + if action.Timeout > 0 { + options.Timeout = action.Timeout + } if action.ClientSubnet.IsValid() { options.ClientSubnet = action.ClientSubnet } @@ -355,6 +362,9 @@ func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOpt if routeOptions.RewriteTTL != nil { options.RewriteTTL = routeOptions.RewriteTTL } + if routeOptions.Timeout > 0 { + options.Timeout = routeOptions.Timeout + } if routeOptions.ClientSubnet.IsValid() { options.ClientSubnet = routeOptions.ClientSubnet } diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index b78a49e7a..19a183ac2 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -5,7 +5,8 @@ icon: material/alert-decagram !!! quote "Changes in sing-box 1.14.0" :material-delete-clock: [independent_cache](#independent_cache) - :material-plus: [optimistic](#optimistic) + :material-plus: [optimistic](#optimistic) + :material-plus: [timeout](#timeout) !!! quote "Changes in sing-box 1.12.0" @@ -31,6 +32,7 @@ icon: material/alert-decagram "independent_cache": false, "cache_capacity": 0, "optimistic": false, // or {} + "timeout": "", "reverse_mapping": false, "client_subnet": "", "fakeip": {} @@ -115,6 +117,16 @@ The maximum time an expired cache entry can be served optimistically. `3d` is used by default. +#### timeout + +!!! question "Since sing-box 1.14.0" + +Default timeout for each DNS query. + +`10s` is used by default. + +Can be overridden by `rules.[].timeout` (DNS rule action) or `domain_resolver.timeout`. + #### reverse_mapping Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing. diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index ae06b8ab6..e5c221395 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -5,7 +5,8 @@ icon: material/alert-decagram !!! quote "sing-box 1.14.0 中的更改" :material-delete-clock: [independent_cache](#independent_cache) - :material-plus: [optimistic](#optimistic) + :material-plus: [optimistic](#optimistic) + :material-plus: [timeout](#timeout) !!! quote "sing-box 1.12.0 中的更改" @@ -31,6 +32,7 @@ icon: material/alert-decagram "independent_cache": false, "cache_capacity": 0, "optimistic": false, // or {} + "timeout": "", "reverse_mapping": false, "client_subnet": "", "fakeip": {} @@ -114,6 +116,16 @@ LRU 缓存容量。 默认使用 `3d`。 +#### timeout + +!!! question "自 sing-box 1.14.0 起" + +每次 DNS 查询的默认超时时间。 + +默认使用 `10s`。 + +可被 `rules.[].timeout`(DNS 规则动作)或 `domain_resolver.timeout` 覆盖。 + #### reverse_mapping 在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。 diff --git a/docs/configuration/dns/rule_action.md b/docs/configuration/dns/rule_action.md index e5a99be3c..3555d6ede 100644 --- a/docs/configuration/dns/rule_action.md +++ b/docs/configuration/dns/rule_action.md @@ -7,7 +7,8 @@ icon: material/new-box :material-delete-clock: [strategy](#strategy) :material-plus: [evaluate](#evaluate) :material-plus: [respond](#respond) - :material-plus: [disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [timeout](#timeout) !!! quote "Changes in sing-box 1.12.0" @@ -26,6 +27,7 @@ icon: material/new-box "disable_cache": false, "disable_optimistic_cache": false, "rewrite_ttl": null, + "timeout": "", "client_subnet": null } ``` @@ -64,6 +66,14 @@ Disable optimistic DNS caching in this query. Rewrite TTL in DNS responses. +#### timeout + +!!! question "Since sing-box 1.14.0" + +Override the DNS query timeout for matched queries. + +Will override `dns.timeout`. + #### client_subnet Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. @@ -83,6 +93,7 @@ Will override `dns.client_subnet`. "disable_cache": false, "disable_optimistic_cache": false, "rewrite_ttl": null, + "timeout": "", "client_subnet": null } ``` @@ -116,6 +127,14 @@ Disable optimistic DNS caching in this query. Rewrite TTL in DNS responses. +#### timeout + +!!! question "Since sing-box 1.14.0" + +Override the DNS query timeout for matched queries. + +Will override `dns.timeout`. + #### client_subnet Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default. @@ -148,6 +167,7 @@ Only allowed after a preceding top-level `evaluate` rule. If the action is reach "disable_cache": false, "disable_optimistic_cache": false, "rewrite_ttl": null, + "timeout": "", "client_subnet": null } ``` diff --git a/docs/configuration/dns/rule_action.zh.md b/docs/configuration/dns/rule_action.zh.md index 24179977f..756051dd9 100644 --- a/docs/configuration/dns/rule_action.zh.md +++ b/docs/configuration/dns/rule_action.zh.md @@ -7,7 +7,8 @@ icon: material/new-box :material-delete-clock: [strategy](#strategy) :material-plus: [evaluate](#evaluate) :material-plus: [respond](#respond) - :material-plus: [disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [timeout](#timeout) !!! quote "sing-box 1.12.0 中的更改" @@ -26,6 +27,7 @@ icon: material/new-box "disable_cache": false, "disable_optimistic_cache": false, "rewrite_ttl": null, + "timeout": "", "client_subnet": null } ``` @@ -64,6 +66,14 @@ icon: material/new-box 重写 DNS 回应中的 TTL。 +#### timeout + +!!! question "自 sing-box 1.14.0 起" + +覆盖匹配查询的 DNS 查询超时时间。 + +将覆盖 `dns.timeout`。 + #### client_subnet 默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 @@ -83,6 +93,7 @@ icon: material/new-box "disable_cache": false, "disable_optimistic_cache": false, "rewrite_ttl": null, + "timeout": "", "client_subnet": null } ``` @@ -114,6 +125,14 @@ icon: material/new-box 重写 DNS 回应中的 TTL。 +#### timeout + +!!! question "自 sing-box 1.14.0 起" + +覆盖匹配查询的 DNS 查询超时时间。 + +将覆盖 `dns.timeout`。 + #### client_subnet 默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。 @@ -146,6 +165,7 @@ icon: material/new-box "disable_cache": false, "disable_optimistic_cache": false, "rewrite_ttl": null, + "timeout": "", "client_subnet": null } ``` diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 1ba690398..f4dfad384 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -9,7 +9,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.14.0" - :material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [resolve.timeout](#timeout) !!! quote "Changes in sing-box 1.12.0" @@ -285,6 +286,7 @@ Timeout for sniffing. "disable_cache": false, "disable_optimistic_cache": false, "rewrite_ttl": null, + "timeout": "", "client_subnet": null } ``` @@ -319,6 +321,14 @@ Disable optimistic DNS caching in this query. Rewrite TTL in DNS responses. +#### timeout + +!!! question "Since sing-box 1.14.0" + +Override the DNS query timeout for this lookup. + +Will override `dns.timeout`. + #### client_subnet !!! question "Since sing-box 1.12.0" diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index f2ae41862..f832a30d7 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -9,7 +9,8 @@ icon: material/new-box !!! quote "sing-box 1.14.0 中的更改" - :material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache) + :material-plus: [resolve.timeout](#timeout) !!! quote "sing-box 1.12.0 中的更改" @@ -277,6 +278,7 @@ UDP 连接超时时间。 "disable_cache": false, "disable_optimistic_cache": false, "rewrite_ttl": null, + "timeout": "", "client_subnet": null } ``` @@ -311,6 +313,14 @@ DNS 解析策略,可用值有:`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、 重写 DNS 回应中的 TTL。 +#### timeout + +!!! question "自 sing-box 1.14.0 起" + +覆盖此查询的 DNS 查询超时时间。 + +将覆盖 `dns.timeout`。 + #### client_subnet !!! question "自 sing-box 1.12.0 起" diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 306952fc4..2ef2fbdb6 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.14.0" + + :material-alert: [domain_resolver](#domain_resolver) + !!! quote "Changes in sing-box 1.13.0" :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index daf7f8e03..24388fecf 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "sing-box 1.14.0 中的更改" + + :material-alert: [domain_resolver](#domain_resolver) + !!! quote "sing-box 1.13.0 中的更改" :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) diff --git a/option/dns.go b/option/dns.go index c09b3d5f3..c0d131a30 100644 --- a/option/dns.go +++ b/option/dns.go @@ -48,6 +48,7 @@ func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) e type DNSClientOptions struct { Strategy DomainStrategy `json:"strategy,omitempty"` + Timeout badoption.Duration `json:"timeout,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"` IndependentCache bool `json:"independent_cache,omitempty"` diff --git a/option/outbound.go b/option/outbound.go index d8fcb8221..aa32db814 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -94,6 +94,7 @@ type DialerOptions struct { type _DomainResolveOptions struct { Server string `json:"server"` + Timeout badoption.Duration `json:"timeout,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` @@ -107,6 +108,7 @@ func (o DomainResolveOptions) MarshalJSON() ([]byte, error) { if o.Server == "" { return []byte("{}"), nil } else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) && + o.Timeout == 0 && !o.DisableCache && !o.DisableOptimisticCache && o.RewriteTTL == nil && diff --git a/option/rule_action.go b/option/rule_action.go index c369cfeb3..303f77d6c 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -202,6 +202,7 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { type DNSRouteActionOptions struct { Server string `json:"server,omitempty"` + Timeout badoption.Duration `json:"timeout,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` @@ -211,6 +212,7 @@ type DNSRouteActionOptions struct { type _DNSRouteOptionsActionOptions struct { Strategy DomainStrategy `json:"strategy,omitempty"` + Timeout badoption.Duration `json:"timeout,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` @@ -324,6 +326,7 @@ type RouteActionSniff struct { type RouteActionResolve struct { Server string `json:"server,omitempty"` + Timeout badoption.Duration `json:"timeout,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"` diff --git a/route/network.go b/route/network.go index 858ea3b24..6598ead48 100644 --- a/route/network.go +++ b/route/network.go @@ -79,6 +79,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options DomainResolver: defaultDomainResolver.Server, DomainResolveOptions: adapter.DNSQueryOptions{ Strategy: C.DomainStrategy(defaultDomainResolver.Strategy), + Timeout: time.Duration(defaultDomainResolver.Timeout), DisableCache: defaultDomainResolver.DisableCache, DisableOptimisticCache: defaultDomainResolver.DisableOptimisticCache, RewriteTTL: defaultDomainResolver.RewriteTTL, diff --git a/route/route.go b/route/route.go index 0d5e1669a..1809971ed 100644 --- a/route/route.go +++ b/route/route.go @@ -791,6 +791,7 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon DisableCache: action.DisableCache, DisableOptimisticCache: action.DisableOptimisticCache, RewriteTTL: action.RewriteTTL, + Timeout: action.Timeout, ClientSubnet: action.ClientSubnet, }) if err != nil { diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index ea239b68c..2187d80d0 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -108,6 +108,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti case C.RuleActionTypeResolve: return &RuleActionResolve{ Server: action.ResolveOptions.Server, + Timeout: time.Duration(action.ResolveOptions.Timeout), Strategy: C.DomainStrategy(action.ResolveOptions.Strategy), DisableCache: action.ResolveOptions.DisableCache, DisableOptimisticCache: action.ResolveOptions.DisableOptimisticCache, @@ -128,6 +129,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) Server: action.RouteOptions.Server, RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ Strategy: C.DomainStrategy(action.RouteOptions.Strategy), + Timeout: time.Duration(action.RouteOptions.Timeout), DisableCache: action.RouteOptions.DisableCache, DisableOptimisticCache: action.RouteOptions.DisableOptimisticCache, RewriteTTL: action.RouteOptions.RewriteTTL, @@ -139,6 +141,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) Server: action.RouteOptions.Server, RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ Strategy: C.DomainStrategy(action.RouteOptions.Strategy), + Timeout: time.Duration(action.RouteOptions.Timeout), DisableCache: action.RouteOptions.DisableCache, DisableOptimisticCache: action.RouteOptions.DisableOptimisticCache, RewriteTTL: action.RouteOptions.RewriteTTL, @@ -150,6 +153,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) case C.RuleActionTypeRouteOptions: return &RuleActionDNSRouteOptions{ Strategy: C.DomainStrategy(action.RouteOptionsOptions.Strategy), + Timeout: time.Duration(action.RouteOptionsOptions.Timeout), DisableCache: action.RouteOptionsOptions.DisableCache, DisableOptimisticCache: action.RouteOptionsOptions.DisableOptimisticCache, RewriteTTL: action.RouteOptionsOptions.RewriteTTL, @@ -320,6 +324,9 @@ func formatDNSRouteAction(action string, server string, options RuleActionDNSRou if options.RewriteTTL != nil { descriptions = append(descriptions, F.ToString("rewrite-ttl=", *options.RewriteTTL)) } + if options.Timeout > 0 { + descriptions = append(descriptions, F.ToString("timeout=", options.Timeout.String())) + } if options.ClientSubnet.IsValid() { descriptions = append(descriptions, F.ToString("client-subnet=", options.ClientSubnet)) } @@ -328,6 +335,7 @@ func formatDNSRouteAction(action string, server string, options RuleActionDNSRou type RuleActionDNSRouteOptions struct { Strategy C.DomainStrategy + Timeout time.Duration DisableCache bool DisableOptimisticCache bool RewriteTTL *uint32 @@ -349,6 +357,9 @@ func (r *RuleActionDNSRouteOptions) String() string { if r.RewriteTTL != nil { descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL)) } + if r.Timeout > 0 { + descriptions = append(descriptions, F.ToString("timeout=", r.Timeout.String())) + } if r.ClientSubnet.IsValid() { descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet)) } @@ -522,6 +533,7 @@ func (r *RuleActionSniff) String() string { type RuleActionResolve struct { Server string + Timeout time.Duration Strategy C.DomainStrategy DisableCache bool DisableOptimisticCache bool @@ -550,6 +562,9 @@ func (r *RuleActionResolve) String() string { if r.RewriteTTL != nil { options = append(options, F.ToString("rewrite_ttl=", *r.RewriteTTL)) } + if r.Timeout > 0 { + options = append(options, F.ToString("timeout=", r.Timeout.String())) + } if r.ClientSubnet.IsValid() { options = append(options, F.ToString("client_subnet=", r.ClientSubnet)) }