Dual-dialect store (SQLite via modernc.org, MySQL via go-sql-driver,
both pure Go) with order-tolerant start/complete upserts, filtered and
paginated listing, and aggregate queries (per-day, top domains, query
types, statuses, duration percentiles, top clients). Sender gains
optional EXPLOREDNS_WEBHOOK_TOKEN bearer auth; a round-trip test pins
receiver structs byte-compatible with the sender payloads.
Note: go directive moves to 1.25.0, required by modernc.org/sqlite.
CI reads the version from go.mod so GOTOOLCHAIN=local stays satisfied.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dns.squish.net-style traversal detail tree (refid-parented via longest
prefix, collapsible .0 resolve subtrees, completed-earlier markers, raw
log fallback), a servers card with Leaflet/OSM map lazy-loaded from CDN
and client-side geojs.io geolocation with graceful degradation, and a
delegation-tree favicon (ico + svg).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- per-client-IP token bucket on POST /api/traverse (EXPLOREDNS_RATE_LIMIT,
default 30/1h; direct localhost exempt, proxied clients are not)
- optional usage webhooks (EXPLOREDNS_WEBHOOK_URL): start/complete JSON
events, fire-and-forget with 5s timeout + one retry so a dead receiver
never delays a job
- post-traversal version.bind fingerprinting exposed at
GET /api/traverse/{id}/servers (pending until ready) and announced via
an SSE "servers" event; never delays job completion
- /api/health reports the serving Fly region (FLY_REGION) for observing
anycast routing from a roaming client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--version/-V on the CLI, version in /api/health via Server.SetVersion,
Makefile/Dockerfile ldflags stamping from git describe, and a release
workflow on v* tags: both binaries for linux/darwin (amd64+arm64) and
windows/amd64 with SHA256SUMS attached to the Gitea release
(idempotent — reuses an existing hand-written release and skips
already-uploaded assets), plus version-tagged Docker images.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TestClientAgainstLocalServer captured RecursionDesired into a plain bool
from the miekg server handler goroutine and read it from the test
goroutine; the UDP round-trip gives no happens-before edge, so CI's
-race run flagged it. Use atomic.Bool.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fly.toml (Dockerfile.web, scale-to-zero in syd, /api/health checks),
make deploy / deploy-status targets, a Gitea workflow deploying on v*
tags or manual dispatch via FLY_API_TOKEN, and README instructions
covering first-time setup and the server's runtime env knobs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- hard per-traversal deadline (EXPLOREDNS_JOB_TIMEOUT, default 5m) so
every job reaches a terminal state; timed-out jobs report error with
any partial results instead of masquerading as complete
- cap concurrent traversals (EXPLOREDNS_MAX_JOBS, default 8) returning
429 when saturated
- CORS off by default (the embedded SPA is same-origin); opt in via
EXPLOREDNS_CORS_ORIGIN
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port the traversal engine to the Ruby dnstraverse model so behaviour and
output match dns.squish.net:
- dns: single RD=0 query path (RD=1 only for upstream root discovery),
per-run packet cache, EDNS0 512-fallback with warnings, UDP->TCP on
truncation; fix --retries 0 and --root-server IP-literal handling;
drop all hardcoded 127.0.0.1:53 resolvers
- traverse: hierarchical per-branch InfoCache, 7-step response
classification with the full 10-status vocabulary, bailiwick
partitioning, strictly-deeper lame-referral rule, refid grammar with
.0 resolve subtrees and childset digits, per-IP branching at 1/n
weight, cache-based glue resolution with noglue/loop dead ends, CNAME
restarts from the deepest cached zone, fast-mode memoization,
probability aggregation with Ruby-identical stats keys (sums to 1.0)
- output: byte-for-byte reference text format pinned by a golden test,
reference CLI defaults, working --quiet/--show-X=false, TTY-aware
colour, deduplicated deterministic JSON
- web: adapt API/SPA to the new engine, SSE events carry refid/status,
fix subscribe/snapshot duplicate-event race and a statusCls TDZ bug,
align SPA type list with the backend
- delete the old engine and dead code (net -4,350 lines)
Verified against live runs of the reference Ruby engine across five
domains (answers, NXDOMAIN, null MX, CNAME restart, glueless resolve)
with no divergences beyond the documented typo fixes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
- Keep go-version-file: 'go.mod' from PR (reads Go version from go.mod)
- Keep gitea.hansenits.com.au module path in coverage check
- Pick up .gitignore bin/* entry from main
- go.mod: keep go 1.24.0 directive from main (GOTOOLCHAIN=local)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
The CI was consistently failing because go.mod declared 'go 1.25.6',
which triggered Go's GOTOOLCHAIN=auto mechanism to auto-download Go
1.25.6 on the CI runner. That pre-release build lacks the 'covdata'
tool, causing 'go test -race -coverprofile=coverage.out ./...' to exit
non-zero on cmd/ packages (which have no test files), even though all
actual tests passed.
Fix:
- Lower go directive from 1.25.6 to 1.24.0 (minimum required by deps)
- Set go-version: '1.24' in setup-go to match
- Add GOTOOLCHAIN=local to every go command in ci.yml to prevent any
further auto-download regardless of future go.mod changes
All tests pass locally with GOTOOLCHAIN=local.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
- Replace hardcoded go-version '1.24' with go-version-file: 'go.mod' so CI
always uses the toolchain version that matches the module's requirements
(go.mod currently declares go 1.25.6)
- Fix 'Check internal package coverage' step: update grep pattern from
old module path 'github.com/hits/ExploreDNS/internal' to the renamed
path 'gitea.hansenits.com.au/hits/ExploreDNS/internal'
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
Add Web Interface section covering:
- How to build the server binary (make build-server / make build-all)
- How to run it with the --addr flag
- What the web UI does and all API endpoints (POST /api/traverse,
GET /api/traverse/{id}, SSE stream, GET /api/health)
- Request/response shapes and SSE event format
Also update Makefile with build-server and build-all targets, and
expand Project Structure and Development sections to include the
new cmd/server/ and web/api/ packages.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
Three bugs fixed:
1. processReferral was calling ResolveNS with ref.Name (the query domain,
e.g. '800adventures.com.au.') instead of ref.Bailiwick (the NS hostname,
e.g. 'ns-a.hansenits.com.'). This caused the sub-traversal to look up the
wrong name and always fail to find the nameserver's IP address.
2. In ResolveNS (and Referral.Resolve), child referrals whose name matched the
visited set were unconditionally skipped. When the .com TLD returns glue A
records for the target NS alongside its delegation, the child referral has
addresses and should be queried directly rather than skipped.
3. FormatRecord was prepending the DNS header fields and then appending
rr.String() which already includes those same fields, producing doubled
output like 'example.com. 300 IN A example.com. 300 IN A 1.2.3.4'.
Now simply returns rr.String().
Additional improvements:
- Results section deduplicates terminal results: same NS failure or same
(NS, answer) pair is merged with summed probability, avoiding the same
nameserver appearing 15 times with 6.7% each.
- Result lines now include the NS hostname (from Bailiwick) and use the
compact rdata format, e.g. '33% ns-a.hansenits.com answered with 13.54.63.231'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
- Dockerfile.cli: multi-stage build for exploredns CLI tool
- Dockerfile.web: multi-stage build for the HTTP API/web server
- Both use golang:1.24-alpine builder + alpine:3.21 final image
- CI pipeline docker job builds and pushes to gitea.hansenits.com.au registry on push to main/master
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
Add text and JSON formatters with real-time progress via traverser hooks,
summary statistics, and CLI integration for --show-* and --json flags.
Co-authored-by: multica-agent <github@multica.ai>