dnst-scanner

March 13, 2026 · View on GitHub

A CLI tool to scan and evaluate DNS resolvers for tunneling viability. Supports basic liveness checks, censorship detection, NS delegation verification, and end-to-end tunnel connectivity tests through DNSTT and Slipstream.

Build

go build -o dnst-scanner ./cmd

Commands

ping

Check IP reachability via ICMP ping. Sends --count pings with --timeout seconds wait each, reports average RTT.

./dnst-scanner ping -i resolvers.txt -o result.json
./dnst-scanner ping -i resolvers.txt -o result.json -c 5 -t 2

resolve

Test if resolvers can resolve a given domain. Queries --count times and reports average resolve time.

./dnst-scanner resolve -i resolvers.txt -o result.json --domain google.com

resolve tunnel

Test if resolvers can reach a tunnel domain's NS server. Sends an NS query for the tunnel domain through each resolver — any response (including NXDOMAIN) proves the resolver can route queries to the tunnel server. SERVFAIL or timeout means the resolver can't reach it. Requires the DNSTT server to be running on the target.

./dnst-scanner resolve tunnel -i resolvers.txt -o result.json --domain t.example.com

e2e dnstt

End-to-end connectivity test through a DNSTT SOCKS tunnel. Requires dnstt-client and curl in PATH.

./dnst-scanner e2e dnstt -i resolvers.txt -o result.json \
  --domain q.example.com --pubkey <hex-pubkey> \
  --socks-user <user> --socks-pass <pass>

e2e slipstream

End-to-end connectivity test through a Slipstream SOCKS tunnel. Requires slipstream-client and curl in PATH.

./dnst-scanner e2e slipstream -i resolvers.txt -o result.json \
  --domain s.example.com --cert /path/to/cert.pem

chain

Run multiple scan steps in sequence, passing results in-memory. Only IPs that pass a step are forwarded to the next one.

./dnst-scanner chain -i resolvers.txt -o result.json \
  --step "ping" \
  --step "resolve:domain=google.com" \
  --step "resolve/tunnel:domain=q.example.com" \
  --step "e2e/dnstt:domain=q.example.com,pubkey=<hex-pubkey>" \
  --step "e2e/slipstream:domain=q.example.com,cert=/path/to/cert.pem"

Step format is type:key=val,key=val.

StepRequired paramsOptional params (defaults)
pingcount (3), timeout (3)
resolvedomaincount (3), timeout (3)
resolve/tunneldomaincount (3), timeout (3)
e2e/dnsttdomain, pubkeysocks-user, socks-pass, test-url (https://httpbin.org/ip), timeout (5)
e2e/slipstreamdomaincert, test-url (https://httpbin.org/ip), timeout (5)

Global Flags

FlagShortDescriptionDefault
--input-iInput file (text or JSON)required
--output-oOutput JSON filerequired
--timeout-tTimeout per attempt (seconds)3
--count-cAttempts per IP for ping/resolve checks3
--workersConcurrent workers50
--include-failedAlso scan failed IPs from JSON inputfalse

Metrics and Sorting

Each check captures timing metrics. Results are sorted ascending by the step's primary metric (lower = better).

StepMetricDescription
pingping_msAverage RTT across successful pings
resolveresolve_msAverage resolve time across attempts
resolve/tunnelresolve_msAverage NS query round-trip time
e2e/dnstte2e_msTime from start to successful curl
e2e/slipstreame2e_msTime from start to successful curl

For ping/resolve checks, an IP is marked as failed if 3 consecutive attempts fail (early exit). Otherwise, the metric is the average of successful attempts.

Input / Output

Input can be a plain text file (one IP per line) or a JSON file from a previous scan. When using JSON input, only passed IPs are scanned by default — use --include-failed to scan all.

Output is always JSON with structured records including per-IP metrics:

{
  "passed": [
    {"ip": "1.1.1.1", "metrics": {"ping_ms": 4.2}},
    {"ip": "8.8.8.8", "metrics": {"ping_ms": 12.7}}
  ],
  "failed": [
    {"ip": "9.9.9.9"}
  ]
}

The chain command adds a steps array with per-step metadata, and passed records accumulate metrics from all steps:

{
  "steps": [
    {
      "name": "ping",
      "tested": 10000,
      "passed": 9200,
      "failed": 800,
      "duration_secs": 15.1
    },
    {
      "name": "resolve",
      "tested": 9200,
      "passed": 8500,
      "failed": 700,
      "duration_secs": 42.3
    }
  ],
  "passed": [
    {"ip": "1.1.1.1", "metrics": {"ping_ms": 4.2, "resolve_ms": 15.3}}
  ],
  "failed": [
    {"ip": "9.9.9.9"}
  ]
}