dnsrtt

command module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: BSD-2-Clause Imports: 12 Imported by: 0

README

dnsrtt

CI Release Go version License

TL;DR

go install github.com/farrokhi/dnsrtt@latest
dnsrtt quad9

That sends 50 queries each over plain DNS, DNS-over-QUIC and DNS-over-HTTPS/3 to the same Quad9 server and prints a latency table per transport. The point is to see what an encrypted transport costs you compared to plain DNS. Prebuilt binaries are on the releases page if you would rather not build anything.

What it does

dnsrtt is a small benchmark that compares the per-query latency of different DNS transports against the same resolver. It sends a fixed number of queries over each selected transport (Do53, DoT, DoQ, DoH2, DoH3) and reports the latency distribution along with how many times the underlying connection had to be re-established.

Every transport is pinned to the same server IP through a static bootstrap, so the only thing that changes between rows is the transport on the wire. The upstream clients come from AdGuardTeam/dnsproxy, driven directly, with no resolver selection or load balancing between the measurement and the network.

Why this tool exists

This started as an investigation into why an AdGuard Home dashboard reported a DNS-over-QUIC upstream averaging around > 60 ms while a DNS-over-HTTPS/3 upstream to the same Quad9 host averaged around < 20 ms. The suspicion was that the DoQ client opened a new QUIC connection, with a full TLS handshake, on every query.

Does the latency really matter?

When the connection is warm, the transports are more or less equal (unless you are a nitpicker). The additional latency in the dashboard average comes from connection re-establishment, which is expensive for encrypted protocols. DoQ reconnects tend to be extra costly compared to DoH3, which sends its query as QUIC 0-RTT early data, so its startup cost is lower than the other encrypted transports.

Install

Prebuilt binaries for Linux, macOS, FreeBSD and Windows (amd64 and arm64) are on the releases page. Or install with the Go toolchain:

go install github.com/farrokhi/dnsrtt@latest

Or build from a checkout:

go build -o dnsrtt .

Usage

dnsrtt TARGET [flags]

TARGET is required and decides which resolver you measure. It can be one of three things:

  • a provider preset (quad9, cloudflare, google, adguard), which carries a known hostname and a pinned anycast IP;
  • a resolver hostname (e.g. dns.quad9.net), whose IP is resolved once and then pinned for every transport;
  • a bare IP (e.g. 9.9.9.9), which runs Do53 only. The encrypted transports need a hostname to present as the TLS server name, so they are skipped with a note.

The IP and the TLS name always come from the same target, so they cannot disagree. That matters because an encrypted upstream validates the server's certificate against the hostname: a mismatched pair fails the handshake. If you need to test a specific point of presence, pass --ip to pin the address a hostname or preset resolves to.

Flags:

    --ip string            pin the server IP for a hostname/preset target (e.g. a specific PoP)
-n, --count int            timed queries per transport (default 50)
-g, --gap duration         idle sleep between queries (e.g. 25s) to observe re-dials
-t, --timeout duration     per-query timeout (default 5s)
-T, --transports strings   transports to test: do53,dot,doq,doh2,doh3 (default do53,doq,doh3)
    --no-warmup            do not send an untimed warm-up query first
-v, --version              print version and exit

Not every provider offers every transport (Cloudflare and Google do not run DoQ, for example). A transport the server will not accept is skipped after its first query fails, so the run finishes in about one timeout instead of stalling on every query.

Examples

Warm, back-to-back run against the Quad9 preset:

$ dnsrtt quad9
resolver=9.9.9.9 host=dns.quad9.net count=50 gap=0s timeout=5s warmup=true

transport  n   avg     median  p95     min     max     redials  outliers
Do53       50  13.9ms  13.8ms  15.8ms  11.8ms  16.3ms  0        0
DoQ        50  11.8ms  11.7ms  12.9ms  10.6ms  16.6ms  0        0
DoH3       50  13.3ms  13.0ms  17.1ms  11.1ms  19.3ms  0        0

A bare IP measures only Do53 and says so:

$ dnsrtt 9.9.9.9 -n 8
resolver=9.9.9.9 host=9.9.9.9 count=8 gap=0s timeout=5s warmup=true
skipping DoQ, DoH3: encrypted transports need a hostname, not a bare IP

transport  n  avg     median  p95     min     max     redials  outliers
Do53       8  15.2ms  14.9ms  17.9ms  13.9ms  17.9ms  0        0

The --gap flag inserts an idle sleep between queries so you can watch for reconnects. Note that the QUIC clients run a 20s keep-alive that pings the connection, so a moderate gap usually keeps it alive and you see no redial:

$ dnsrtt quad9 -T do53,doq,doh3 -n 4 -g 25s
resolver=9.9.9.9 host=dns.quad9.net count=4 gap=25s timeout=5s warmup=true

transport  n  avg     median  p95     min     max     redials  outliers
Do53       4  15.8ms  16.0ms  17.7ms  14.0ms  17.7ms  0        0
DoQ        4  13.6ms  13.4ms  16.4ms  12.3ms  16.4ms  0        0
DoH3       4  16.5ms  16.3ms  18.7ms  14.8ms  18.7ms  0        0

A redial shows up when the server itself drops the connection (a long idle period, or a server that recycles connections under load). When that happens the redials column ticks up and the reconnecting query lands as an outlier, and DoQ's reconnect tends to cost more than DoH3's. Numbers depend on your network path and on how aggressively the server recycles connections, so expect variation between runs.

Columns

avg, median, p95, min, max summarize the successful query latencies. redials counts connection re-establishments seen in the dnsproxy debug log after the first connection. outliers counts samples above 1.8x the median, a transport-agnostic proxy for reconnect cost.

License

BSD-2-Clause. See LICENSE. Release archives also carry a THIRD-PARTY-NOTICES file with the licenses of the Go modules linked into the binary (all permissive: Apache-2.0, BSD, MIT, and Unlicense).

Documentation

Overview

Command dnsrtt compares the per-query latency of different DNS transports (Do53, DoT, DoQ, DoH2, DoH3) against the same resolver, so the only variable is the transport on the wire.

Directories

Path Synopsis
internal
bench
Package bench measures and compares per-query latency of different DNS transports (Do53, DoT, DoQ, DoH2, DoH3) against the same resolver.
Package bench measures and compares per-query latency of different DNS transports (Do53, DoT, DoQ, DoH2, DoH3) against the same resolver.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL