docs: comprehensive documentation for ExploreDNS
CI / test (pull_request) Failing after 2m13s

- Rewrite README.md with overview, features, installation (go install +
  build from source), quick start, full CLI flag reference table,
  output section descriptions, project structure, and development guide

- Add package-level doc comments to all five internal packages:
  config, dns, fingerprint, output, traverse (via doc.go or existing
  package-declaration files)

- Add GoDoc comments on every exported type, constant, function, and
  method across all packages:
  - internal/config: Config struct fields, all Parse*/Default/Validate
  - internal/dns: QueryConfig, Resolver, BasicResolver, CachingResolver,
    ExchangeFunc, RootServer, RootDiscoveryConfig, DecodedResponse,
    ResponseClassification, all exported helpers
  - internal/fingerprint: Fingerprinter, New, NewWithTimeout, Query,
    FingerprintAll
  - internal/traverse: Traverser, TraverserConfig, TraversalResult,
    Referral, ResolutionState, Response, ResponseType, InfoCache,
    Stack, TraverserHooks, EventStage, TraversalEvent, EventHandler,
    CircularReferralError, UnresolvableNameserverError
  - internal/output: Format, Config, Formatter, SummaryStats,
    NewFormatter, AttachHooks, RunTraversal, DefaultConfig,
    ComputeSummary

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 04:01:51 +10:00
co-authored by Copilot multica-agent
parent fe1afe2a97
commit 9aa85d8e5d
21 changed files with 675 additions and 91 deletions
+34 -10
View File
@@ -7,17 +7,20 @@ import (
"github.com/miekg/dns"
)
// ResponseClassification categorises a DNS response at a high level,
// independent of the raw RCODE.
type ResponseClassification int
// Classification constants, in order from most to least specific.
const (
ResponseAnswer ResponseClassification = iota
ResponseReferral
ResponseNODATA
ResponseNXDOMAIN
ResponseSERVFAIL
ResponseREFUSED
ResponseNOTIMPL
ResponseOther
ResponseAnswer ResponseClassification = iota // answer section contains records
ResponseReferral // non-authoritative NS referral
ResponseNODATA // NOERROR with empty answer section
ResponseNXDOMAIN // name does not exist (RCODE 3)
ResponseSERVFAIL // server failure (RCODE 2)
ResponseREFUSED // query refused (RCODE 5)
ResponseNOTIMPL // not implemented (RCODE 4)
ResponseOther // any other RCODE
)
func (rc ResponseClassification) String() string {
@@ -41,6 +44,7 @@ func (rc ResponseClassification) String() string {
}
}
// DecodedResponse holds the extracted, structured fields from a raw *dns.Msg.
type DecodedResponse struct {
Rcode int
RcodeName string
@@ -51,8 +55,10 @@ type DecodedResponse struct {
Answers []dns.RR
Authority []dns.RR
Additional []dns.RR
CNAMEChain []string
DNAMEMappings []DNAMEMapping
// CNAMEChain contains the ordered CNAME targets from the answer section.
CNAMEChain []string
// DNAMEMappings contains any DNAME records for redirect synthesis.
DNAMEMappings []DNAMEMapping
}
// DNAMEMapping holds a DNAME record's owner and target for redirect synthesis.
@@ -61,6 +67,8 @@ type DNAMEMapping struct {
Target string // e.g., "example.net."
}
// DecodeResponse converts a raw *dns.Msg into a DecodedResponse.
// Returns nil when msg is nil.
func DecodeResponse(msg *dns.Msg) *DecodedResponse {
if msg == nil {
return nil
@@ -171,10 +179,13 @@ func SynthesizeCNAMEFromDNAME(queryName, dnameOwner, dnameTarget string) string
return prefix + target
}
// IsTruncated reports whether msg has the TC (truncated) bit set.
func IsTruncated(msg *dns.Msg) bool {
return msg != nil && msg.Truncated
}
// RcodeName returns the string representation of the RCODE in msg (e.g. "NOERROR").
// Returns "UNKNOWN" when msg is nil.
func RcodeName(msg *dns.Msg) string {
if msg == nil {
return "UNKNOWN"
@@ -182,6 +193,7 @@ func RcodeName(msg *dns.Msg) string {
return dns.RcodeToString[msg.Rcode]
}
// ExtractAnswers returns the answer section of msg, or nil when msg is nil.
func ExtractAnswers(msg *dns.Msg) []dns.RR {
if msg == nil {
return nil
@@ -189,6 +201,7 @@ func ExtractAnswers(msg *dns.Msg) []dns.RR {
return msg.Answer
}
// ExtractAuthority returns the authority section of msg, or nil when msg is nil.
func ExtractAuthority(msg *dns.Msg) []dns.RR {
if msg == nil {
return nil
@@ -196,6 +209,8 @@ func ExtractAuthority(msg *dns.Msg) []dns.RR {
return msg.Ns
}
// ExtractCNAMEChain returns the ordered list of CNAME targets from the answer section.
// Returns nil when msg is nil.
func ExtractCNAMEChain(msg *dns.Msg) []string {
if msg == nil {
return nil
@@ -203,6 +218,8 @@ func ExtractCNAMEChain(msg *dns.Msg) []string {
return extractCNAMEChain(msg)
}
// IsReferral reports whether msg is a non-authoritative NS referral
// (NOERROR, empty answer section, NS records in authority, AA=false).
func IsReferral(msg *dns.Msg) bool {
if msg == nil || msg.Rcode != dns.RcodeSuccess || len(msg.Answer) > 0 {
return false
@@ -210,6 +227,8 @@ func IsReferral(msg *dns.Msg) bool {
return hasNSRecords(msg.Ns) && !msg.Authoritative
}
// IsNODATA reports whether msg is a NODATA response
// (NOERROR with an empty answer section and no referral).
func IsNODATA(msg *dns.Msg) bool {
if msg == nil || msg.Rcode != dns.RcodeSuccess {
return false
@@ -223,6 +242,8 @@ func IsNODATA(msg *dns.Msg) bool {
return true
}
// HasCNAMEChain reports whether msg contains at least one CNAME record in its
// answer section.
func HasCNAMEChain(msg *dns.Msg) bool {
if msg == nil {
return false
@@ -230,6 +251,9 @@ func HasCNAMEChain(msg *dns.Msg) bool {
return len(extractCNAMEChain(msg)) > 0
}
// FormatRecord returns a human-readable representation of a DNS resource record
// in the form: NAME TTL CLASS TYPE RDATA.
// Returns an empty string when rr is nil.
func FormatRecord(rr dns.RR) string {
if rr == nil {
return ""