Skip to content

2.0 Release - #124

Open
liquidsec wants to merge 77 commits into
stablefrom
dev
Open

2.0 Release#124
liquidsec wants to merge 77 commits into
stablefrom
dev

Conversation

@liquidsec

Copy link
Copy Markdown
Collaborator

No description provided.

dependabot Bot and others added 30 commits April 13, 2026 00:32
Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@v8...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…tions/dev/actions/github-script-9

Bump actions/github-script from 8 to 9
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.185 to 0.2.186.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.186/CHANGELOG.md)
- [Commits](rust-lang/libc@0.2.185...0.2.186)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.186
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [lru](https://github.com/jeromefroe/lru-rs) from 0.16.4 to 0.18.0.
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](jeromefroe/lru-rs@0.16.4...0.18.0)

---
updated-dependencies:
- dependency-name: lru
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.0 to 1.52.3.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](tokio-rs/tokio@tokio-1.52.0...tokio-1.52.3)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.52.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.149 to 1.0.150.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](serde-rs/json@v1.0.149...v1.0.150)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…v/serde_json-1.0.150

Bump serde_json from 1.0.149 to 1.0.150
…/tokio-1.52.3

Bump tokio from 1.52.0 to 1.52.3
…/libc-0.2.186

Bump libc from 0.2.185 to 0.2.186
…/lru-0.18.0

Bump lru from 0.16.4 to 0.18.0
Bumps [regex](https://github.com/rust-lang/regex) from 1.12.3 to 1.12.4.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](rust-lang/regex@1.12.3...1.12.4)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 1.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…v/regex-1.12.4

Bump regex from 1.12.3 to 1.12.4
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.102 to 1.0.103.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](dtolnay/anyhow@1.0.102...1.0.103)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…v/anyhow-1.0.103

Bump anyhow from 1.0.102 to 1.0.103
Bumps [lru](https://github.com/jeromefroe/lru-rs) from 0.18.0 to 0.18.1.
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](jeromefroe/lru-rs@0.18.0...0.18.1)

---
updated-dependencies:
- dependency-name: lru
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.32 to 0.3.33.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/main/CHANGELOG.md)
- [Commits](rust-lang/futures-rs@0.3.32...0.3.33)

---
updated-dependencies:
- dependency-name: futures
  dependency-version: 0.3.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [regex](https://github.com/rust-lang/regex) from 1.12.4 to 1.13.1.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](rust-lang/regex@1.12.4...1.13.1)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [clap](https://github.com/clap-rs/clap) from 4.6.1 to 4.6.2.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](clap-rs/clap@clap_complete-v4.6.1...clap_complete-v4.6.2)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.228 to 1.0.229.
- [Commits](serde-rs/serde@v1.0.228...v1.0.229)

---
updated-dependencies:
- dependency-name: serde
  dependency-version: 1.0.229
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.3 to 1.53.0.
- [Commits](tokio-rs/tokio@tokio-1.52.3...tokio-1.53.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.53.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Workers were pinned one-per-resolver, each owning its own UDP socket, so
total concurrency was the derived quantity resolvers x threads_per_resolver
and the only way to go faster was to add resolvers. That also made sockets
scale with resolvers x workers, which put a large resolver pool out of reach.

Workers are now unpinned and pull from a shared pool, so concurrency is set
directly via max_concurrency while max_inflight_per_resolver keeps the
per-resolver politeness bound. One socket per resolver is shared by whoever
selects it, created on first use.

Adds:
- RateLimiter with an adjustable rate, ported from blasthttp
- per-resolver health: shared client, in-flight permits, RTT, counters,
  purgatory moved out of worker locals
- optional startup probe that drops resolvers which never answer
- per-resolver stats where attempted == answered + empty + timeout + error
- adaptive controller (on by default): finds the rate at which a resolver
  starts losing queries and holds below it, bounded by any configured
  rate_limit as a hard cap
- simulated resolver harness for deterministic convergence tests

Loss on one resolver throttles only that resolver. Only resolvers that have
delivered a clean tick can vote that our own rate is the bottleneck, so a
mostly-dead public resolver list does not read as congestion.

Renames threads_per_resolver to max_inflight_per_resolver.
Rewrites the Architecture section, which still described the pinned
worker-per-resolver model, and documents the three dispatch limits, adaptive
backoff, the startup probe, and stats().

Major bump: threads_per_resolver is renamed, BlastDNSConfig gained fields and
is not non_exhaustive so struct-literal construction breaks, check_ulimits
changed signature, and adaptive backoff is on by default.

Benchmarks now honor num_workers via --max-concurrency, with adaptive backoff
and caching off so they measure dispatch throughput rather than features the
other engines don't have.
…ctions/dev/actions/setup-python-7

Bump actions/setup-python from 6 to 7
…v/lru-0.18.1

Bump lru from 0.18.0 to 0.18.1
…v/regex-1.13.1

Bump regex from 1.12.4 to 1.13.1
…v/futures-0.3.33

Bump futures from 0.3.32 to 0.3.33
…v/tokio-1.53.0

Bump tokio from 1.52.3 to 1.53.0
…v/serde-1.0.229

Bump serde from 1.0.228 to 1.0.229
…v/clap-4.6.2

Bump clap from 4.6.1 to 4.6.2
dependabot Bot and others added 25 commits August 3, 2026 00:24
Bumps [clap](https://github.com/clap-rs/clap) from 4.6.2 to 4.6.3.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](clap-rs/clap@clap_complete-v4.6.2...clap_complete-v4.6.3)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.53.0 to 1.53.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](tokio-rs/tokio@tokio-1.53.0...tokio-1.53.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.53.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
The per-resolver loss ratio needed MIN_SAMPLES within a single tick, but the
in-flight cap holds a resolver to inflight/RTT queries per second -- 5 per
tick at 2 permits and 200ms -- so the branch could never fire and every
resolver took the relax path. Samples now carry across ticks until there are
enough to trust, and a window that cannot fill within WINDOW_TTL is discarded
rather than judged on samples spanning unrelated conditions.

The window's span is accumulated from each tick's reported elapsed time
instead of read off the clock, so a caller driving tick() directly gets
deterministic behavior rather than wall-clock timing mixed into the rate.
Growth was the fall-through for anything that was not congestion, so a tick
that finished fewer than MIN_AGGREGATE_SAMPLES queries raised the limit on no
evidence. Between two congested ticks that inflated it faster than the retreat
lowered it: against a pool losing every query the limit climbed 5,088 -> 8,441
instead of converging. It now steps 5,088 -> 4,325 -> 3,676, one RETREAT per
well-sampled tick.
The probe queried each resolver directly, so it never picked up the deadline
the worker applies. On a persistent socket, which carries no per-request
deadline of its own, every dead entry held its slot for ~5s instead of the
configured timeout: probing 8,149 resolvers took 36s against 4s for the
per-query transport.

Both callers now go through one bounded query on ResolverHealth rather than
each arranging its own deadline. Probe time drops to 4.3s, matching per-query.
Nothing the engine logged was reachable from Python: no subscriber was ever
installed, so resolver diagnostics, purgatory sentences, TCP refetches, and
adaptive rate changes all went nowhere. Every embedding, including BBOT, ran
blind.

Opt-in rather than automatic, because the subscriber is process-global and a
library should not take it from whatever embeds it on import. A second call
declines instead of replacing the first.
The engine reads a threshold of 0 as never bench, but the config surface
required at least 1, so that behavior could not be reached from Python at all.
Making the per-resolver branch reachable turned out to cost far more than it
bought. LOSS_THRESHOLD sits at 2% while real resolvers refuse or drop 5-9% of
attempts as a matter of course, so once the branch could fire it throttled
healthy resolvers -- all three of a small pool, and 65 of 68 in another. The
main DNS client has the few-resolvers-many-in-flight shape that trips it
hardest, and it gates the rest of a scan: 184s became 550s, against 287s for
massdns in the same window.

Attempts to make it safe each exposed the next problem: a relative threshold
has nothing to compare against in a pool of one, pacing off the attempt rate
settles above what a resolver can serve, pacing off the delivered rate
compounds as the window stretches, and a warm-up period leaves the first tick
unjudgeable. None of that is unsolvable, but none of it is a small change, and
politeness does not depend on it: max_inflight_per_resolver already caps each
resolver at inflight/RTT regardless.

The global branch keeps the quiet-tick fix, which the bursty case cleared.
Deactivation lasts for the life of the client, so the probe decides pool
membership on a single query. Judging that by the per-query timeout is too
strict: a root-NS query to a cold resolver is slower than the cached lookups a
scan mostly makes, and at a brute-force timeout of 500ms a passing latency
spike evicts resolvers that would have served fine.

The probe now allows at least two seconds. It stays bounded, which is what the
earlier change was for.
…v/tokio-1.53.1

Bump tokio from 1.53.0 to 1.53.1
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.186 to 0.2.189.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.189/CHANGELOG.md)
- [Commits](rust-lang/libc@0.2.186...0.2.189)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.189
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
A persistent socket routes every query for its resolver through one multiplexed
client, and hickory bounds that client's request channel at CHANNEL_BUFFER_SIZE
(32). Exceeding it does not degrade, it falls off a cliff: 45,018 qps at 32 in
flight against a local resolver, 188 qps at 40.

This also explains an earlier measurement read as an inherent limit of the
persistent transport. It was this bound, hit at inflight 8 and above.
…v/clap-4.6.3

Bump clap from 4.6.2 to 4.6.3
…v/serde_json-1.0.151

Bump serde_json from 1.0.150 to 1.0.151
Backing off recovers queries when the rate is what caused the loss. A resolver
dropping a fixed share, or one behind a lossy link, loses the same at any rate,
and the controller retreated anyway on every tick -- walking the rate toward
the floor without recovering anything. Under 10% rate-independent loss a 2,000
query workload stopped finishing at all, at every in-flight depth from 8 up,
while the same workload with adaptation off finished in 15-29s.

A resolver now earns its first retreat on sight of loss and further ones only
while the loss is falling against the best of the episode. Two consecutive
flat ticks hold the rate instead, since one flat reading is sampling noise
rather than proof.

Convergence toward a real capacity limit is unaffected: loss falls as the rate
approaches capacity, so the evidence keeps arriving and the rate now settles at
the capacity rather than overshooting beneath it.
The existing benchmark reports one number: queries per second against a single
local resolver with no impairment. That number moved 21,750 -> 29,676 between
two CI runs of identical code, so it cannot detect a regression, and no real
workload resembles the regime. None of this release's defects would have shown
up in it.

Six regimes now, each reporting delivery, retry amplification, completion
percentiles, and socket plus connection-tracking peaks. Throughput is printed
but is not a number to gate on; the ratio against dnspython in the same run is
stable across hardware where absolute rates are not, and the in-flight column
is there so two engines are never silently compared at different depths.

Loss and latency are injected in-process, deterministically and without root,
by an impaired resolver on its own thread. High-throughput regimes point at a
real resolver, since a Python one would be the ceiling. A regime that cannot
finish is recorded as a result rather than hanging the run.
A pool regime printed its total in flight, so 8 per resolver across 32 resolvers
read as 256 -- a figure that looks like it exceeds what one persistent socket can
carry, when the limit is per resolver and 8 is well under it. The column now
shows both, and run_blastdns refuses a persistent regime configured past the
transport bound rather than reporting the resulting collapse as a measurement.
Drop-in: same invocation, same report heading the sticky-comment step matches
on, same dnsmasq port the workflow starts. The old single-regime script is gone
rather than left alongside, since its one number is the thing that could not
detect a regression.

Clients are now warmed before timing. Socket setup and probing cost the same
whatever the query count, so leaving it inside the measurement made two runs at
different sizes incomparable: the persistent pool regime read 3,694 qps at 4,000
queries and 16,485 at 20,000 with no config change.
A query counts as lost only when its timeout expires, so losses arrive later
than the dispatches that caused them. The ratio was computed per 500ms tick
against a 1s timeout, which put the two in different windows: cutting the rate
dropped dispatches at once while the previous rate's losses were still landing,
and the next tick read 118% then 152% loss. Those readings then looked like
improvement on the way back down, so the retreat-must-help guard waved them
through and the rate slid anyway.

The window now accumulates until it is at least as long as the timeout, and the
ratio is clamped, since a window whose rate was just cut can still absorb more
losses than it sent. Under 10% rate-independent loss a 2,000 query workload goes
from 41.8s at 34 qps to 11.9s at 197 qps, with every query still delivered.

Judging on a window rather than a tick does not make the branch reachable at
brute-force scale: the window resets once it covers the timeout, so a resolver
drawing two queries a second still never reaches MIN_SAMPLES.
Two mechanisms it described no longer exist. Retries are not suppressed under
correlated loss -- that was tried and measured, taking unanswered queries from 0%
to 3.5% on a 5,000-name brute-force, and was removed. Resolvers do not vote on
the global rate by having delivered a clean interval; the global signal is
queries that failed after exhausting every retry.

persistent_socket was absent entirely despite being a shipped option, so the
architecture section still described one socket per resolver as the default when
that is now the opt-in path. It has a section covering what it costs: bounded
connection-tracking state against reduced spoofing entropy and a 32-query
ceiling per socket.

Also documents that retreat repeats only while loss is falling, that loss is
measured over a window outlasting the request timeout, that the startup probe
allows a looser deadline than a query, that stats().rate_qps is instantaneous
rather than cumulative, and that per-resolver judgement does not engage at
brute-force scale. CLI help regenerated from the binary, which was missing
--persistent-socket.
…v/anyhow-1.0.104

Bump anyhow from 1.0.103 to 1.0.104
…v/libc-0.2.189

Bump libc from 0.2.186 to 0.2.189
Bumps [lru](https://github.com/jeromefroe/lru-rs) from 0.18.1 to 0.18.2.
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](jeromefroe/lru-rs@0.18.1...0.18.2)

---
updated-dependencies:
- dependency-name: lru
  dependency-version: 0.18.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Decouple concurrency from resolver count and add adaptive rate control
…v/lru-0.18.2

Bump lru from 0.18.1 to 0.18.2
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

DNS Resolver Benchmark

Throughput is reported, never gated: it moves with hardware. The stable
columns are unanswered, attempts/query, sockets, and conntrack.

ceiling/1-resolver

engine in flight qps vs dnspython unanswered atts/query p50 p99 p100 sockets conntrack
blastdns 32 18,974 5.93x 0.000% 1.00 0.570s 1.051s 1.054s 44 17,180
dnspython 100 3,199 1.00x 0.000% n/a 0.329s 0.623s 0.625s 189 1,446
massdns - 9,553 2.99x 0.000% n/a n/a n/a n/a 7 1

pool/32-resolvers

engine in flight qps vs dnspython unanswered atts/query p50 p99 p100 sockets conntrack
blastdns 2/res, 64 total 18,487 - 0.000% 1.00 0.588s 1.079s 1.082s 76 19,726
massdns - 9,573 - 0.000% n/a n/a n/a n/a 7 32

pool/32-persistent

engine in flight qps vs dnspython unanswered atts/query p50 p99 p100 sockets conntrack
blastdns 8/res, 256 total 20,784 - 0.000% 1.00 0.511s 0.959s 0.962s 44 30

impaired/10%-loss

engine in flight qps vs dnspython unanswered atts/query p50 p99 p100 sockets conntrack
blastdns 32 36 - 0.000% 1.11 15.972s 53.565s 55.352s 48 1,405
massdns - 974 - 0.000% n/a n/a n/a n/a 7 1

impaired/20%-REFUSED

engine in flight qps vs dnspython unanswered atts/query p50 p99 p100 sockets conntrack
blastdns 32 2,673 - 0.000% 1.25 0.419s 0.748s 0.748s 52 2,413
massdns - 2,772 - 0.000% n/a n/a n/a n/a 7 1

impaired/50ms-latency

engine in flight qps vs dnspython unanswered atts/query p50 p99 p100 sockets conntrack
blastdns 32 619 - 0.000% 1.00 1.842s 3.228s 3.228s 56 1,945

dependabot Bot and others added 4 commits August 17, 2026 00:23
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.19 to 2.0.20.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](dtolnay/thiserror@2.0.19...2.0.20)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-version: 2.0.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.33 to 0.3.34.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/main/CHANGELOG.md)
- [Commits](rust-lang/futures-rs@0.3.33...0.3.34)

---
updated-dependencies:
- dependency-name: futures
  dependency-version: 0.3.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…v/thiserror-2.0.20

Bump thiserror from 2.0.19 to 2.0.20
…v/futures-0.3.34

Bump futures from 0.3.33 to 0.3.34
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