feat: implement DNS server fingerprinting (HAN-384)
CI / test (pull_request) Failing after 2m24s

- Add internal/fingerprint package with Fingerprinter type
  - Sends version.bind CHAOS TXT query to each server
  - Caches results per IP to avoid redundant queries
  - Concurrent batch fingerprinting via FingerprintAll
  - Gracefully handles non-responding servers (returns empty string)
  - Injectable exchange function for testability

- Wire fingerprinting into RunTraversal
  - Triggered when both ShowVersions and ShowServers are enabled
  - Collects unique server IPs from traversal results
  - Stores results in output.Config.Fingerprints before WriteSummary

- Display versions in text output (writeServers)
  - Appends version string after IP when ShowVersions is true
  - No output change when version is unknown

- Include version in JSON output (jsonServer.Version field)
  - omitempty: field absent when version is unknown

- Add comprehensive fingerprint tests (11 test cases)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Gary Hansen
2026-06-08 02:47:23 +10:00
co-authored by Copilot multica-agent
parent 35be7539b4
commit 3e7580b919
6 changed files with 395 additions and 7 deletions
+13 -6
View File
@@ -44,8 +44,9 @@ type jsonResult struct {
}
type jsonServer struct {
Name string `json:"name"`
IPs []string `json:"ips"`
Name string `json:"name"`
IPs []string `json:"ips"`
Version string `json:"version,omitempty"`
}
type jsonSummary struct {
@@ -104,10 +105,16 @@ func (f *jsonFormatter) WriteSummary(results []traverse.TraversalResult) error {
if f.cfg.ShowServers {
servers := collectServers(results)
for name, ips := range servers {
f.payload.Servers = append(f.payload.Servers, jsonServer{
Name: name,
IPs: ips,
})
srv := jsonServer{Name: name, IPs: ips}
if f.cfg.ShowVersions && f.cfg.Fingerprints != nil {
for _, ip := range ips {
if v := f.cfg.Fingerprints[ip]; v != "" {
srv.Version = v
break
}
}
}
f.payload.Servers = append(f.payload.Servers, srv)
}
}