package output import ( "context" "fmt" "net" "github.com/hits/ExploreDNS/internal/fingerprint" "github.com/hits/ExploreDNS/internal/traverse" ) // RunTraversal is the high-level entry point that wires together a Traverser, // a Config, and a Formatter. It: // // 1. Attaches output hooks to traverser so events are formatted in real time. // 2. Calls traverser.Traverse(ctx, domain) to perform the traversal. // 3. Optionally fingerprints encountered servers (when ShowVersions && ShowServers). // 4. Writes the summary and flushes the formatter. // // Returns all TraversalResults and any error from the traversal or output. func RunTraversal(ctx context.Context, traverser *traverse.Traverser, cfg *Config, formatter Formatter, domain string) ([]traverse.TraversalResult, error) { if traverser == nil { return nil, fmt.Errorf("traverser is required") } if cfg == nil { cfg = DefaultConfig() } if formatter == nil { formatter = NewFormatter(cfg, nil) } traverser.SetHooks(AttachHooks(cfg, formatter)) results, err := traverser.Traverse(ctx, domain) if err != nil { return results, err } // Fingerprint servers when both ShowVersions and ShowServers are enabled. // Gating on ShowServers avoids unnecessary network calls when versions // would not be displayed anyway. if cfg.ShowVersions && cfg.ShowServers { cfg.Fingerprints = fingerprint.New().FingerprintAll(ctx, collectUniqueServerIPs(results)) } if err := formatter.WriteSummary(results); err != nil { return results, err } if err := formatter.Flush(); err != nil { return results, err } return results, nil } // collectUniqueServerIPs returns the set of unique server IPs seen in results. func collectUniqueServerIPs(results []traverse.TraversalResult) []net.IP { seen := make(map[string]bool) var ips []net.IP for _, r := range results { if r.Response == nil || r.Response.Server == nil { continue } key := r.Response.Server.String() if !seen[key] { seen[key] = true ips = append(ips, r.Response.Server) } } return ips }