Files
ExploreDNS/docs/dnstraverse-reference-spec.md
T
Gary HansenandClaude Fable 5 af15c9c2d4 docs: add dnstraverse reference spec, rework design, and golden tooling
Reconstructed behaviour spec of dns.squish.net / Ruby dnstraverse 0.1.14
(inputs, traversal semantics, probability model, verbatim output formats,
sourced from the live site, Wayback captures, and the Ruby source), the
engine rework design that maps it onto Go, a point-in-time codebase review,
golden reference captures, and tools/golden/run-reference.sh for running
the reference Ruby engine locally (clone is gitignored, GPL-3 dev-only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 21:41:47 +10:00

26 KiB
Raw Blame History

dns.squish.net / dnstraverse — Reference Behaviour Spec

Compiled 2026-07-07 for diffing ExploreDNS against the original. Source precedence used throughout: archived/live site output > Ruby source (squish/dnstraverse @ 733cc0b, v0.1.13) > secondhand accounts. Each claim is tagged [LIVE] (probed 2026-07-07), [WB] (Wayback capture, date given), [SRC] (Ruby source file), or [DOC] (author's own docs at www.squish.net/dnstraverse/). Raw artifacts referenced are preserved under /private/tmp/claude-502/-Users-ghansen-src-ExploreDNS/8c18e9d7-a07d-43e8-a21b-b9aad8182912/scratchpad/ (subdirs squish/, websweep/, Ruby clone in dnstraverse/).


1. Availability

The site is UP. [LIVE] https://dns.squish.net/ and http://dns.squish.net/ both return HTTP 200 (Cloudflare edge; backend X-Powered-By: Phusion Passenger 5.0.30, Rails session cookie _dnstraverseweb_session). Footer: "UI 2.0.1 / Engine 0.1.14" — the engine is the Ruby dnstraverse gem; the Rails wrapper ("dnstraverseweb") is closed-source (not in any public repo of GitHub user squish). "Kindly hosted by the ISC"; author James Ponder.

Practical constraints on the live site:

  • Anonymous submission requires solving a Google reCAPTCHA v2 (a captcha-less POST to /traverses/create_anon gets 302 → / with flash Sorry, the captcha did not succeed, please try again.). [LIVE]
  • Old traverse data is purged: pre-2026 traverse IDs still return the page shell (Name/Type/Date), but /fetch replies only setTimeout("poll()", 1000); forever and /detail/fetch replies count = 0; setTimeout("poll()", 1000); — results are never re-rendered. [LIVE] So a fresh captcha-solved run in a browser is the only way to capture new complete output.

Best archival/primary sources, in order of authority:

  1. Wayback Machine — ~4,076 archived /traverses/<32-hex> show pages and 114 /detail/fetch RJS streams (CDX API). Best complete artifacts: careerhosts.net/A show+detail+servers (2016-03), learningsuite.byu.edu/A (2015-03), dadzi.ir/A all-failure (2019-06), www.ilpd-afrique.com/A NXDOMAIN (2017-11), intellivida.ca/A mixed (2011-09), www.nike.com CNAME chain detail/fetch (2018-03), /info?packet=N popups (aruba.it, 2021-01). Site captured live as recently as 2026-06-20; page wording is essentially unchanged 2011→2026 (engine only moved 0.1.11→0.1.14), so any era is representative. [WB]
  2. Ruby engine source — github.com/squish/dnstraverse master @ 733cc0b (2011-12-17, v0.1.13; rubygems shows 0.1.14 released 2012-04-30). Cloned locally; this is the exact engine behind the site.
  3. Author's engine docs — http://www.squish.net/dnstraverse/ (+ manual.html, switches.html), live. [DOC]
  4. Old v1 Perl service — http://www.squish.net/dnscheck/dnscheck.cgi still executes live queries with no captcha (verified 2026-07-07; full www.example.com/A run captured). It is a different, cruder engine (author's FAQ: the new one "is more accurate than the Perl-based dnscheck service, including DNS bailiwick concepts, simulation of the resolver cache"; v1 source "is not available") — useful only for cross-checking aggregate percentage math, not wording.

2. Inputs

2.1 Web form (dns.squish.net, confirmed identical 2011→live 2026) [LIVE][WB]

Form id=new_traverse, POST /traverses/create_anon, submit button name=commit value=Create:

Field Details Default
traverse[domain_name] text, size 30, hint "e.g. www.example.com". Help: "This is the full DNS entry to check. For example: www.google.com, news.bbc.co.uk, or www.cnn.com" / "If your web site is http://www.example.com/ you should enter the www.example.com component only." (empty)
traverse[domain_type] select, options exactly in order: A, NS, MX, PTR, TXT, AAAA, SOA, SPF. Caption: "Leave as 'A' unless you know what you're doing." Help notes PTR format is D.C.B.A.in-addr.arpa. A
Human Check reCAPTCHA v2 (sitekey 6LdIf08UAAAAAJgYP1bvi7vmf-EXEHWXH_AqthF9), required for anonymous; "Registration is optional, but means you don't need to enter a reCAPTCHA every time!" —
authenticity_token Rails CSRF, session-bound —

That is the entire web surface: no root-server, depth, retries, UDP size, fast-mode or IPv6 options ("Currently this is IPv4 only."). Success redirects to /traverses/<32-hex-id> (ID is random per submission, not md5(name/type) — tested [WB]; "Friendly URLs for emailing"). Other routes: /traverses/<id>/detail, /traverses/<id>/servers, /traverses/<id>/info?packet=N, POST /traverses/<id>/fetch and /detail/fetch?count=N (1 s Prototype.js polls); /login, /register; /traverses index requires login.

Note the web type menu (adds SPF; lacks SRV/WKS/CNAME/HINFO/MINFO/ANY) is a deliberate restriction of the engine's larger CLI list.

2.2 CLI options (dnstraverse [options] DOMAIN) — [DOC manual.html, confirmed against SRC bin/dnstraverse]

Option Meaning Default
-v/--[no-]verbose verbose progress ([qname]/ shown) false
-d/--[no-]debug debug; twice = library debug 0
-r/--root-server HOST initial root nil (ask local resolver)
-a/--all-root-servers traverse all roots false (one root)
-t/--type TYPE A, AAAA, SRV, WKS, NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY :a
--udp-size SIZE 512 turns EDNS0 off 2048
--allow-tcp retry over TCP on truncation true
--always-tcp false
--max-depth DEPTH 20
--retries TIMES "Number of 2s retries before timing out" 2
--[no-]follow-aaaa, --[no-]root-aaaa documented "isn't implemented yet" false
--[no-]show-progress true
--[no-]show-resolves false
--[no-]show-servers false
--[no-]show-versions true
--[no-]show-all-stats false
--[no-]show-results true
--[no-]show-summary-results true
--save-objects false
--[no-]fast "Fast mode (default true) turn off to be more accurate" true
-q/--quiet, -V/--version, -h/--help

Caveat [SRC]: the Traverser library class internally defaults to maxdepth 10 / udpsize 512 / fast false — the CLI overrides to 20/2048/true, and observed web behaviour matches the CLI-style config (fast mode "completed earlier" labels, single root), except the web's packet dumps show EDNS0 payloadsize 4096 [WB 2021 /info?packet pages] — the web app evidently runs with udp_size 4096, not 2048. Archived output wins here.

2.3 Old v1 form (www.squish.net/dnscheck/v1.html — different engine, listed for completeness) [LIVE]

host text; type select: Choose…/A/SOA/CNAME/MX/NS/PTR/AAAA/ANY; checkboxes (checked by default unless noted): show_rootservers, findroot (unchecked), removebroken, cnameprocessing ("Indirect through CNAMEs encountered"), show_mainworkings, show_resolving (unchecked), show_allservers, getbindversions; retries=3 (exponential delay); querylevel=10 (max query depth).


3. Traversal semantics

All from [SRC] unless noted; web/CLI observations corroborate.

3.1 Entry and roots

  • Traverser#run_query builds a root pseudo-Referral ("rootroot", server=nil) with qname/qtype and runs an explicit stack loop (not recursion); :calc_resolve/:calc_answer markers give post-order stats computation after all children finish.
  • Default is ONE root server per run: get_a_root queries '' NS at the local resolver and picks the first root with an A record in additional (else resolves root names). --all-root-servers asks that root for the full set. Rationale [DOC FAQ]: "since most DNS problems are not because of a root server problem, the default mode is to only use one." The web service also uses a single root, which varies per run (f/m/e/b roots observed): detail page shows Initial Root: f.root-servers.net, 192.5.5.241 + (1 roots returned) [WB]. Rootroot's children (one per root) combine with equal weight.

3.2 Branching

  • Referral#process queries each IP of the current server (recursion-desired off; EDNS0 OPT added when udp_size>512). Children referrals are created for statuses :referral and :restart — one child per nameserver returned in the referral, including glueless ones (shown with empty parens ns1.virtualempire.com ( ) on the web [WB]). Child order is randomized per run [WB].
  • Refids: dotted path per node — 1, 1.1, 1.1.1… Children number from 1; a resolve subtree inserts a .0 component (e.g. 1.2.0.1); nested resolves nest further (1.1.2.0.1.4.2.0.2.0.2 observed [WB]). If more than one IP of a server produced children, an extra "childset" digit is added.
  • Depth limit: refid components excluding 0 counted against maxdepth (CLI/web 20); exceeding injects RuntimeError Maxdepth N exceeded as an :exception response.

3.3 Response classification (DecodedQuery#process, in order)

  1. network exception → :exception
  2. follow CNAMEs within the message (msg_follow_cnames; a chain leaving the bailiwick stops following and returns the target; an in-message loop returns nil → :cname_loop)
  3. rcode != NOERROR → :error with messages: Formate error (FORMERR) [sic, typo in source], Server failure (SERVFAIL), No such domain (NXDOMAIN), Not implemented (NOTIMP), Refused, else rcode string
  4. answers exist for endname/qtype → :answered
  5. endname != qname (CNAME landed elsewhere) → :restart
  6. SOA in authority, or no NS in authority → :nodata
  7. NS in authority → :referral; else :restart

Response layer adds :referral_lame; the resolve phase adds :noglue and :loop. Full outcome vocabulary: answered, nodata, referral, restart, referral_lame, error, exception, cname_loop, noglue, loop.

3.4 Bailiwick and per-branch cache

  • inside_bailiwick?(name) = bailiwick nil, or name == bailiwick (case-insensitive), or name ends with .<bailiwick>.
  • msg_cacheable partitions all sections (answer/authority/additional; OPT discarded) into in-bailiwick "good" (cached) vs out-of-bailiwick "bad" (discarded). [DOC FAQ confirms: "keeps track of the bailiwick from the referring parent and discards entries that are outside… additional record hints are typically ignored".]
  • InfoCache is hierarchical per branch (each Response creates InfoCache.new(parent)); add() clears same name:class:type key before storing; lookups recurse to the parent. get_startservers(domain) walks labels upward to the nearest cached NS RRset; returns [{:name, :ips-or-nil}] plus newbailiwick = NS owner.
  • Lame referral detection: a :referral becomes :referral_lame unless the new zone is strictly deeper than the current bailiwick (starters_bailiwick =~ /\.#{bailiwick}$/i, or bailiwick nil). Warning Referred authority names do not match query cache expectations when starter names ≠ referred NS names.

3.5 Glue / resolve / loop

  • Child with ips=nil: if the server name is inside the current bailiwick with no glue → :noglue dead end (probability mass retained). [DOC FAQ: "dnstraverse has nobody to go to next, so this error is generated as the domain is unreachable."]
  • If an ancestor Referral had the same qname/qclass/qtype/server with unresolved IPs → :loop dead end.
  • Otherwise a resolve sub-traversal for A <servername> starts (refid .0. component), starting from get_startservers(server) in this branch's cache — never the local resolver [DOC FAQ]. Every :answered leaf of the resolve distributes its probability evenly across returned A records into serverweights[ip]; non-answered outcomes carry the failure probability up as pseudo-IP key:... entries.

3.6 Probability model (exact)

  • On Referral creation with known IPs: serverweight = 1.0/serverips.length per IP.
  • stats_calculate_children(stats, children, weight): percent = (1.0/children.length) * weight; each child stat's prob accumulates child_prob * percent. I.e. equal split among sibling children, multiplied down the tree; all root-level probabilities sum to 1.0.
  • Leaf aggregation key: key:#{status}:#{ip}:#{server}:#{qname}:#{qclass}:#{qtype} (+:#{exception message} for :exception, +:#{parent_ip} for :referral_lame; NoGlue/Loop use key:#{status}:#{ip}:#{qname}:#{qclass}:#{qtype}:#{server}:#{bailiwick}). Identical keys merge by adding prob.
  • Summary groups by status, summing probs; answered entries additionally grouped by sorted rdata strings joined @@@ (so one summary line per distinct RRset content).
  • Corroborating observations: 13 gTLDs → 7.7% each; 7 google NS → 14.3%; 2 NS → 50.0%; 3 → 33.3%; 4 → 25.0% [WB, DOC]; deep resolve failures → 0.2%/0.0% [mcgill.org.za CLI run, 2017].

3.7 Fast mode and low-level cache

  • Fast mode (default on): global hash keyed "#{qname}:#{qclass}:#{qtype}:#{server}:#{txt_ips_verbose}".downcase (txt_ips_verbose embeds per-IP weights like 50.0%=1.2.3.4). A completed :normal Referral with no :referral_lame response is stored; a hit replaces the child before processing and marks it "completed earlier". [DOC FAQ: fast mode "will make the assumption that the cache won't make any difference to the result"; normal mode re-walks because per-branch caches may differ.]
  • Independently, a low-level packet cache ensures each (server IP, question, udp_size) is asked only once per run regardless of options (CachingResolver, key key:res:#{ip}:#{name}:#{klass}:#{type}:#{udp_size}).

3.8 CNAME restarts

:restart children are built like referrals but with qname = CNAME target, starters from the per-branch cache — so the restart resumes from the deepest cached zone, not always the root. Confirmed [WB www.nike.com 2018 detail/fetch]: restart for www-geo.nike.com.akadns.net began at a root (nothing cached); later restarts began directly at <akadns.net> / <net> servers. Each restart adds a new Query Key legend entry; in CLI the bracketed qname simply changes mid-tree ([www.google.com] → [www.l.google.com]) with refid numbering continuing. In-packet CNAME chains are followed silently inside one response (byu.edu answers show only the target's A records [WB 2015]).

3.9 Resolver details

Retries default 2 (2 s delay, 2 s timeout); recurse off; dnssec off; src 0.0.0.0. EDNS fallback: on FORMERR/NOTIMP/SERVFAIL with udpsize>512, retry at 512; success adds warning #{answerfrom} doesn't seem to support EDNS0. Other warnings: ... allows recursion, ... doesn't allow recursion (local queries), ... sent truncated packet. IPv4 only.


4. Output format

4.1 Web show page /traverses/<id> [WB, structure confirmed LIVE]

Details table: Name / Type / Date (e.g. 2016-03-13 19:05:26 -0700; live 2013 shell shows UTC) and, while running, Progress (<refid> - <current name/server> + integer percent, e.g. 1.9 - testcname.yellowtealpurple.net / 61%, updated by 1 s polls to /fetch). Then:

Summary (div#traverse_summary_stats, heading "Summary"):

<div class="traverse_summary_stats_line">100% answered with <div class="traverse_summary_stats_answer">careerhosts.net.	86400	IN	A	38.71.67.100</div></div>

[WB 2016 careerhosts.net]. Percent format = sprintf("%.1f%%", prob*100).sub(/\.0%/, '%') right-justified to width 5 [SRC summary_stats.rb:135-136] — so 100%, 93.3%, 6.7%, 50% (archived HTML shows the 100% case unpadded; padding is whitespace-collapsed in rendering). Category wordings [SRC:76-99, all confirmed in WB captures except the last three]: N% answered with <RRs>, found no such record, resulted in a lame referral, resulted in an exception, resulted in an error, found no glue, resulted in a loop, resulted in a CNAME loop. Multiple RRs join with <br>; CSS renders the answer italic, offset under its line. Observed mixes: 93.3% answered with … / 6.7% resulted in an exception [WB 2011 intellivida.ca]; 100% resulted in an error (all-NXDOMAIN) [WB 2017]; 100% resulted in an exception [WB 2019 dadzi.ir]; 100% resulted in a lame referral [WB 2013 g-p.es].

Results (div#results, heading "Results"), one <p class="stats_line"> per aggregated outcome, percent as %5.1f%% (no colon on web):

<p class="stats_line">
 50.0%
  Answered from  ns1.virtualempire.com (38.71.66.4)
  <pre>careerhosts.net.	86400	IN	A	38.71.67.100
</pre>
</p>

(note two spaces after "from", tab-separated dig-style RRs in <pre>; failure lines have no <pre>). Verbatim failure catalogue from archived pages [WB]:

  • No such domain (NXDOMAIN) at ns1.cctld.co (156.154.100.25)
  • Query timed out at ns2.twisted4life.com (194.152.92.62)
  • Server failure (SERVFAIL) at casper.ln1x.ablesky.com (173.255.217.216)
  • Refused at ns1.afdns.net (190.183.61.2)
  • NODATA (for this type) at ns1.iranfirewall.in (148.251.110.146)
  • Lame referral received from f.nic.es (130.206.1.2) to dns2.namecheaphosting.com (69.160.33.71) — web adds "received"; CLI says Lame referral from … [SRC referral.rb:506]
  • recvfrom failed from 185.208.174.92; No route to host - recvfrom(2) at ns1.dadzi.ir (185.208.174.92) (exception text verbatim from Ruby)
  • Failures inside a resolve append <div class="while">While querying ns1.twisted4life.com/IN/A</div> (CLI: While querying #{qname}/#{qclass}/#{qtype}).

Then links: "Traversal detail" → /detail; "Server location and version information" → /servers; while running: "Please wait while your traversal is completed, or alternatively watch the traversal as it progresses."

4.2 Web detail page /traverses/<id>/detail [WB streams; shell confirmed LIVE]

Adds table rows Initial Root (f.root-servers.net, 192.5.5.241 + (1 roots returned)) and Query Key — one coloured dotted-border chip per query, text like A careerhosts.net; colours cycle #fefecc, #ccfecc, #ccfefe, #ccccfe, #feccfe, #fee5cc. Banner node: Traversing for careerhosts.net type A starting at the root(s).

Tree rows are div.node_line tables: indentation drawn with 32×23 px spacer images (spacer_gap/line/tee/end.png); node text is #{server} (#{ip}) <#{bailiwick}> (root renders <>; glueless renders ( ) with a placeholder span later filled, truncated by the UI, e.g. 95.130.252.149,Loop encounter...). Node extras: progress spinner → status icon linking info_url('/traverses/<id>/info?packet=N') (referral.png / success.png / warning.png), show resolve/hide resolve toggle revealing the hidden .0 resolve subtree, and italic completed earlier span for fast-mode hits. Colour lifecycle: inserted muted (#ededdd; resolve nodes #ddeddd) → active border:1px solid black + query-key colour → completed. Updates arrive as Prototype.js RJS from /detail/fetch?count=N (Element.insert, visualEffect, setStyle), each chunk ending count = N; setTimeout("poll()", 1000);. On completion: Results injected (same stats_line HTML as show page), then a Fingerprinting phase cycling every server name, then the "further" (servers) link appears.

4.3 Web packet popup /info?packet=N [WB 2021]

<h2>What is arudns1.aruba.it type A?</h2> + <p>Answer from 192.12.192.5 (dns.nic.it)</p> + <pre> dnsruby dump (;; Answer received from 192.12.192.5 (189 bytes), ;; Security Level : UNCHECKED, header/flags/sections, OPT pseudo-record : payloadsize 4096, xrcode 0, version 0, flags 0) + <h3>Status: referral</h3> "A referral occured. This means that the resolver did not know the answer, but indicated that results can be found elsewhere." (or Status: answered / "The server was able to answer the question successfully.") + <h3>The following records were considered worthy of caching:</h3> + <h3>These servers were chosen in bailiwick 'aruba.it' for the referral:</h3> (name (ip) lines) + <h2>Results after processing this node and all branches beneath:</h2> with subtree-scoped stats_lines. Fast-mode variant prepends blue This was completed earlier (fast mode).

4.4 Web servers page /traverses/<id>/servers [WB 2016/2025; shell LIVE]

Google map + table#servers_table, columns exactly: Country | City | Area code | Postal code | Servers | Software guess | (+Show on map). Servers cell: 38.71.66.4 (ns1.virtualempire.com)<br/>… grouped by geolocation; Software guess: ISC BIND 9.2.3rc1 -- 9.4.0a4 (9.6.1-P1), VeriSign ATLAS (2025 adds (ATLAS) suffix), TIMEOUT, No match found. Fingerprint DB is fpdns-derived ("Portions Copyright (c) 2003,2004,2005 Roy Arends & Jakob Schlyter").

4.5 CLI output [DOC home page sample + mcgill.org.za 2017 run + SRC]

Header (unless -q): # Using fast mode / # Limiting traverse to one root / # UDP size N (EDNS0 is on) [always prints "on" due to source bug options[:udpsize == 512]] / # Retries N, max depth N / # Allow TCP is true, always TCP is false; then Using E.ROOT-SERVERS.NET (192.203.230.10) as initial root / Running query www.google.com type a.

Progress, verbose (refid [qname] server (ips) <bailiwick>) [DOC, verbatim]:

1 [www.google.com] E.ROOT-SERVERS.NET (192.203.230.10) <>
1.1 [www.google.com] B.GTLD-SERVERS.NET (192.33.14.30) <com>
1.1.1 [www.google.com] ns1.google.com (216.239.32.10) <google.com>
1.1.1.1 [www.l.google.com] a.l.google.com (209.85.139.9) <l.google.com>

Non-verbose omits [qname]/<bailiwick> [mcgill 2017, verbatim]:

1.2.2 ns1.coza.net.za -- resolving
1.2.2 ns1.coza.net.za (66.135.62.20,Loop encountered resolving ns.coza.net.za)
1.3.1 ns4.iafrica.com (196.7.142.131) -- completed earlier (1.2.3)

Results: — blocks with printf "%5.1f%%: " then per status [SRC referral.rb:503-532]: Answer from <server> (<ip>) + RRs indented 12 spaces; No glue at <parent.server> (<ip>) for <server>; Lame referral from <parent.server> (<parent_ip>) to <server> (<ip>); Loop encountered at <server>; CNAME loop encountered at <server>; <error message> at <server> (<ip>); NODATA (for this type) at <server> (<ip>); <exception message> at …; fallback Stopped at <server> (<ip>)) [trailing ) is a source bug]; plus While querying <qname>/<qclass>/<qtype> when the failing query differs. [DOC, verbatim]:

 14.3%: Answer from e.l.google.com (209.85.137.9)
            www.l.google.com.   300     IN      A       74.125.77.99

Summary Results: — same wording set as web (Section 4.1), prefix two spaces, e.g. 99.8% answered with co.za. 3600 IN NS ns0.is.co.za. … 0.2% resulted in a loop [mcgill 2017]. RR whitespace collapsed to single spaces; continuation RRs aligned under text start.

--show-servers: The following servers were encountered: then printf "%#{w}s: %-15s%s" rows sorted by lowercased reversed name [DOC, verbatim]:

    ns1.google.com: 216.239.32.10   ISC BIND 9.2.3rc1 -- 9.4.0a0 (9.4.2-P1)
a.gtld-servers.net: 192.5.6.30      VeriSign ATLAS

4.6 Legacy v1 (Perl dnscheck) — different engine, different wording [LIVE run 2026-07-07]

Nested-table HTML; sections: root-server workings and SOA-serial check, then Traversal of DNS for <name>. with units Asking a.gtld-servers.net (192.5.6.30) for www.example.com (type A) / Referral: example.com is at hera.ns.cloudflare.com (108.162.192.162) / Response is: with per-branch 16.7% <ip> (<name>) with <ips>; dedupe [see above for results]; final Results: 16.1% of queries will be returned by 108.162.195.228 (elliott.ns.cloudflare.com) + RRset, 3.5% of queries will end in failure at too many nested queries, 0.0% of queries will end in failure at 192.5.6.30 (a.gtld-servers.net) - failed to resolve h.gtld-servers.net due to 192.5.6.30 - nameserver loop detected. Do not copy this wording into ExploreDNS comparisons against dns.squish.net — only the aggregate math (probability multiplication/summation) transfers.


5. Confidence notes

Confirmed by primary sources (archived/live site output + Ruby source agree):

  • Web form fields/defaults, routes, captcha behaviour, polling protocol (LIVE + WB, identical 2011–2026).
  • Single-initial-root default, equal-split probability model and exact aggregation formulas, bailiwick filtering, hierarchical per-branch cache, fast mode, refid scheme with .0 resolve subtrees, CNAME restart-from-cache, depth 20 / retries 2 (SRC, corroborated by WB output and DOC).
  • Result/Summary wording and percent formatting for: answered, error (NXDOMAIN/SERVFAIL/Refused), exception (timeout/recvfrom), nodata, lame referral, "While querying" suffix (WB verbatim + SRC format strings, verified in the local clone: summary_stats.rb:76-99,135-136, referral.rb:503-532).
  • CLI output shapes (DOC's own annotated sample + one full independent 0.1.14 run from mcgill.org.za 2017 + SRC).

Confirmed by source only (never seen in captured output): web wording for noglue ("found no glue" / "No glue at X (ip) for Y"), loop and cname_loop result/summary lines on the web (CLI loop lines are confirmed via mcgill); Stopped at fallback; Formate error (FORMERR) typo; fast-cache key details; lame-referral "strictly deeper zone" rule. These are near-certain — the site runs this gem — but flagged CONFIRMED-by-source, not by output.

Known divergences to be careful with when diffing: web "Answered from␣␣" (double space) vs CLI "Answer from"; web "Lame referral received from" vs CLI without "received"; web EDNS payloadsize 4096 vs CLI default 2048 (archived output preferred for the web service); library-vs-CLI internal defaults (10/512/false vs 20/2048/true); source bugs worth deciding whether to replicate ((9.4.2-P1)-style CLI EDNS banner always "on", Stopped at …) stray paren, answered-merge prob discard in stats_display, "CNANE loop" typo in DecodedQuery#to_s).

Not recovered / open items:

  • The Rails front-end (dnstraverseweb) source — closed; web-only wording is reconstructed from captures.
  • Exactly one engine version gap: site runs 0.1.14; the cloned source is 0.1.13 master (rubygems has 0.1.14; diff not fetched — format strings match 0.1.14 outputs observed, so risk is low).
  • No archived web sample of a completed SPF/TXT/SOA traversal, of the web progress-percent computation, or of web noglue/loop/cname_loop result lines.
  • A fresh end-to-end capture is achievable: the live site works behind one reCAPTCHA — solve it once in a browser and record /traverses/<hash>, /detail (streaming XHRs to /fetch and /detail/fetch), and /servers. The author's test domains yellowtealpurple.net / testcname.yellowtealpurple.net (seen Aug 2025) are candidate CNAME test vectors. The v1 CGI remains captcha-free for aggregate-math cross-checks.

Where sources conflicted, archived site output was used (traverse IDs random not md5 — WB tests; EDNS 4096 on web — WB packet dumps; web "received"/double-space wordings — WB HTML), with the Ruby source as tie-breaker for everything unobserved.