diff --git a/internal/resolver/iterative.go b/internal/resolver/iterative.go index 8f41b6d..3b7d16d 100644 --- a/internal/resolver/iterative.go +++ b/internal/resolver/iterative.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math/rand/v2" "net" "sort" "strings" @@ -13,7 +14,13 @@ import ( ) const ( - queryTimeoutDuration = 5 * time.Second + // queryTimeoutDuration is the per-exchange DNS timeout. + // + // Rationale: maximum RTT to antipodal root/TLD servers is + // ~300ms. We use 3× max RTT + 10ms processing ≈ 910ms, + // rounded to 1s. Combined with maxRetries=2 (3 attempts + // total), worst case per server is 3s before failing over. + queryTimeoutDuration = 1 * time.Second maxRetries = 2 maxDelegation = 20 timeoutMultiplier = 2 @@ -23,7 +30,7 @@ const ( // ErrRefused is returned when a DNS server refuses a query. var ErrRefused = errors.New("dns query refused") -func rootServerList() []string { +func allRootServers() []string { return []string{ "198.41.0.4", // a.root-servers.net "170.247.170.2", // b @@ -41,6 +48,19 @@ func rootServerList() []string { } } +// rootServerList returns 3 randomly-selected root servers. +// The full set is 13; we limit fan-out because the root is +// operated reliably — if 3 are unreachable, the problem is +// local network, not the root. +func rootServerList() []string { + shuffled := allRootServers() + rand.Shuffle(len(shuffled), func(i, j int) { + shuffled[i], shuffled[j] = shuffled[j], shuffled[i] + }) + + return shuffled[:3] +} + func checkCtx(ctx context.Context) error { err := ctx.Err() if err != nil { @@ -302,7 +322,7 @@ func (r *Resolver) resolveNSRecursive( msg.SetQuestion(domain, dns.TypeNS) msg.RecursionDesired = true - for _, ip := range rootServerList()[:3] { + for _, ip := range rootServerList() { if checkCtx(ctx) != nil { return nil, ErrContextCanceled } @@ -333,7 +353,7 @@ func (r *Resolver) resolveARecord( msg.SetQuestion(hostname, dns.TypeA) msg.RecursionDesired = true - for _, ip := range rootServerList()[:3] { + for _, ip := range rootServerList() { if checkCtx(ctx) != nil { return nil, ErrContextCanceled }