#2457·smartdns

[Feature Request] Per-upstream concurrency limit to avoid HTTP 429 from DoH servers

Author: polaris-archCreated Aug 31, 2026Updated Sep 7, 2026

Summary

Please consider adding a per-server option that caps the number of in-flight requests SmartDNS keeps open against a single upstream, e.g.:

server-https https://doh.pub/dns-query -group Direct -max-concurrent-queries 16

Some public DoH providers reject excess concurrent requests on a shared HTTP/2 connection with HTTP 429. Because SmartDNS multiplexes all queries for one upstream over a single connection and has no way to bound that concurrency, bursts of parallel queries reliably trigger 429 even when the average query rate is very low.

Environment

Item Value
SmartDNS version 1.2026.08.05-0921 (Release48.4)
OS OpenWrt / iStoreOS 24.10.8, x86_64
Affected upstream server-https https://doh.pub/dns-query -group Direct (Tencent DNSPod)

Observed behaviour

Over a 19-hour window, one router logged 557 occurrences of:

[WARN][client_http2.c:709] http2 server query from 1.12.12.12:443 failed, server return http code: 429

The important detail is the time distribution. Grouping the 429s by second:

1 per second  -> 64 occurrences
16 per second ->  3 occurrences
33 per second ->  2 occurrences
38 per second ->  1 occurrence

and the largest burst has all 38 rejections inside a 4 ms window (09:32:03.44009:32:03.444).

Meanwhile, per-upstream statistics for the same period show this server received only 402 queries in 35 minutes (~0.19 qps). So this is not a sustained-rate limit being exceeded — it is momentary in-flight concurrency on the single multiplexed connection.

Note that the server answers HTTP 429 rather than RST_STREAM(REFUSED_STREAM), so this appears to be an application-level concurrency policy rather than HTTP/2 SETTINGS_MAX_CONCURRENT_STREAMS enforcement.

Controlled reproduction

Issuing N concurrent DoH requests multiplexed over one HTTP/2 connection (curl --http2 --parallel, deliberately without --parallel-immediate so curl reuses a single connection):

concurrent streams doh.pub doh.360.cn 223.5.5.5
10 200 × 10 200 × 10 200 × 10
40 200 × 21, 429 × 19 200 × 40 200 × 40
80 200 × 11, 429 × 69 200 × 80 200 × 80

doh.pub starts rejecting somewhere between 10 and 40 concurrent streams; the other two providers tested handle 80 without a single 429. A cap of roughly 16 would keep SmartDNS comfortably under this particular provider's threshold.

Why the existing options do not cover this

  • There is no per-server option for rate or concurrency. The complete set accepted by _config_server is -group, -exclude-default-group, -blacklist-ip, -whitelist-ip, -check-edns, -set-mark, -subnet, -subnet-all-query-types, -host-ip, -host-name, -http-host, -interface, -fallback, -spki-pin, -tls-host-verify, -no-check-certificate, -bootstrap-dns, -tcp-keepalive, -drop-packet-latency, -hitchhiking — none of which bounds concurrency.
  • max-query-limit is a single global setting, not per-server. Lowering it to protect one upstream would throttle every upstream and every client equally.
  • -fallback does reduce traffic to the server, but it changes the semantics to standby-only. It is a reasonable workaround, not a way to keep an upstream in active rotation at a safe concurrency.
  • Switching the same provider to DoT (server-tls tls://dot.pub) was measured on the same hardware and load: 46.2 % success (37/80) versus 100 % for the other two upstreams in the group, so DoT is not a viable substitute here.

Suggested behaviour

When the configured limit is reached for a server, queries beyond it could either be briefly queued or simply skipped for that upstream — other servers in the group would still answer, so no client-visible failure. Applying the limit per server rather than globally keeps it useful for mixed groups where providers have very different tolerances.

Related issues

  • #2398 — SIGSEGV/SIGABRT when an upstream DoH returns HTTP 429. That crash is fixed, and Release48.4 no longer crashes under these bursts. A concurrency cap would reduce exposure to the trigger in the first place.
  • #2393 — asks about disabling parallel querying across multiple upstreams. Related but different: this request is about bounding in-flight requests to a single upstream while keeping parallel group behaviour.