Skip to content

Add configurable DNS query timeout#4079

Open
comebackto2021 wants to merge 59 commits intoSagerNet:testingfrom
comebackto2021:feat-configurable-dns-timeout
Open

Add configurable DNS query timeout#4079
comebackto2021 wants to merge 59 commits intoSagerNet:testingfrom
comebackto2021:feat-configurable-dns-timeout

Conversation

@comebackto2021
Copy link
Copy Markdown

Summary

Adds a timeout field to DNS configuration so the DNS exchange timeout can
be tuned per-deployment via JSON config. Previously hardcoded to
C.DNSTimeout = 10s in dns/client.go.

The Client.timeout field is already parameterized in code — NewClient()
accepts options.Timeout and falls back to C.DNSTimeout when zero. This
PR only wires up the JSON option and threads it through dns.NewRouter.

Motivation

On unstable mobile networks (carrier-grade NAT with aggressive timeouts,
DPI middleboxes on TCP/53), plain TCP DNS sockets can become "zombies"
after long idle periods — bytes are silently dropped while the kernel
still considers the socket valid. The full 10-second timeout is then
user-visible latency on the first query after inactivity.

Industry comparison for DNS exchange/per-attempt timeouts:

System Default
Microsoft Windows DNS client 1s first retry (5 attempts in 10s budget)
Microsoft Windows Server forwarder 3s
ThousandEyes DNS resolution test 3s measurement
Linux glibc RES_TIMEOUT 5s default, community recommends 1-3s
sing-box 10s, single attempt, not configurable

Operators deploying sing-box across many users on heterogeneous networks
have no way to tune this without forking and shipping custom libbox builds
to every client platform (Android AAR, Apple framework, Windows DLL).

Changes

  • option/dns.go: add Timeout badoption.Duration to DNSClientOptions
  • dns/router.go: pass it to dns.NewClient via existing ClientOptions.Timeout
  • docs/configuration/dns/index.md and index.zh.md: document the new field

Backward compatibility

Field is omitempty. When unset (time.Duration(0)), dns/client.go:80-82
falls back to C.DNSTimeout = 10s, preserving existing behavior. No
migration needed.

Example

{
  "dns": {
    "timeout": "3s",
    "servers": [...]
  }
}

Verification

  • go build ./... — passes
  • go vet ./option/... ./dns/... — clean
  • go test ./option/... ./dns/... — all green
  • Config validation: "timeout": "3s" accepted, invalid duration string rejected
    with clear error pointing to dns.timeout JSON path
  • Backward compatibility: configs without the field validate and run as before

nekohasekai and others added 24 commits April 23, 2026 07:49
DNS rules referencing rule-sets that contain only ip_cidr predicates
silently stopped matching when legacy DNS mode was disabled, because the
IP-CIDR branch cannot match against an in-flight DNS query. The existing
validation intentionally let every rule_set through on the premise that
mixed sets still work via their non-IP branches, which is only true when
such a branch exists. Track whether a rule-set carries any non-IP-CIDR
predicate and reject pure-IP references the same way bare ip_cidr fields
are already rejected.
Serialize probe rounds in startProber to eliminate unbounded fan-out of
fire-and-forget probe goroutines (up to 100/sec per direction), and close
HTTP/3 transports via transport.Close() in addition to CloseIdleConnections.
@nekohasekai nekohasekai force-pushed the testing branch 5 times, most recently from 1b0e6c5 to abedea4 Compare April 28, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants