Phase 4.1: Error handling, edge cases, and robustness (#11)
CI / test (push) Failing after 2m36s

This commit was merged in pull request #11.
This commit is contained in:
2026-06-07 17:24:00 +00:00
parent 76f5010a5e
commit fe1afe2a97
12 changed files with 1118 additions and 22 deletions
+42 -8
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"net"
"sync"
"time"
"github.com/hits/ExploreDNS/internal/dns"
miekgdns "github.com/miekg/dns"
@@ -17,6 +18,12 @@ type TraverserConfig struct {
QueryConfig *dns.QueryConfig
RootAddrs []net.IP
Hooks *TraverserHooks
// Fast controls cache sharing across branches. When true (default), child
// branches inherit glue discovered by earlier branches via the shared root
// cache, trading accuracy for speed. When false, each branch gets a
// completely independent cache — slower but results are not contaminated by
// sibling branch observations.
Fast bool
}
func DefaultTraverserConfig() *TraverserConfig {
@@ -26,6 +33,7 @@ func DefaultTraverserConfig() *TraverserConfig {
RootConfig: nil,
QueryConfig: nil,
RootAddrs: nil,
Fast: true,
}
}
@@ -97,9 +105,19 @@ func (t *Traverser) Traverse(ctx context.Context, name string) ([]TraversalResul
break
}
cache := rootCache
if ref.Parent != nil {
cache = rootCache.Child()
var cache *InfoCache
if t.config.Fast {
// Fast mode: inherit glue from the shared root cache so earlier
// branch discoveries are visible to later branches.
cache = rootCache
if ref.Parent != nil {
cache = rootCache.Child()
}
} else {
// Non-fast mode: every referral gets its own independent cache so
// no cross-branch glue is reused, ensuring each path is resolved
// from scratch.
cache = NewInfoCache(nil)
}
if t.config.Hooks != nil {
@@ -141,7 +159,19 @@ func (t *Traverser) Traverse(ctx context.Context, name string) ([]TraversalResul
if resp.Type == RespCNAMEFollow {
follow := resp.CNAMEFollowReferral()
if follow != nil {
if !stack.Push(follow) {
// Detect CNAME loop: target name already appears in the ancestor chain.
if follow.Parent != nil && follow.Parent.IsNameInChain(follow.Name) {
mu.Lock()
results = append(results, TraversalResult{
Referral: follow,
Response: &Response{
Referral: follow,
Type: RespCNAMELoop,
ErrorMessage: fmt.Sprintf("CNAME loop detected: %s already in traversal chain", follow.Name),
},
})
mu.Unlock()
} else if !stack.Push(follow) {
mu.Lock()
results = append(results, TraversalResult{
Referral: follow,
@@ -412,12 +442,16 @@ func (t *Traverser) resolveGlueViaSystem(ctx context.Context, name string, cache
c := &miekgdns.Client{
Net: "udp",
ReadTimeout: 5,
WriteTimeout: 5,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}
if deadline, ok := ctx.Deadline(); ok {
c.ReadTimeout = deadline.Sub(deadline)
c.WriteTimeout = deadline.Sub(deadline)
remaining := time.Until(deadline)
if remaining <= 0 {
return nil
}
c.ReadTimeout = remaining
c.WriteTimeout = remaining
}
fqdn := miekgdns.Fqdn(name)