Add core traversal engine with iterative resolution from root servers: - referral.go: Referral struct with name, type, bailiwick, addresses, state - cache.go: Chained InfoCache with parent inheritance for NS and glue records - stack.go: LIFO stack with configurable max depth enforcement - response.go: Response classifier (referral, answer, CNAME follow, NODATA, NXDOMAIN, SERVFAIL) with child referral generation and probability splitting - traverser.go: Traverser orchestrates full traversal from root to leaf, following all referral branches, handling CNAME chains, and respecting max depth limits Add IterativeQuery/IterativeQueryWithExchange to dns package (RD=false queries). 57 new tests covering all acceptance criteria: traversal from root to leaf, comprehensive branch following, max depth enforcement, probability distribution, chained cache behavior, CNAME following, NXDOMAIN/SERVFAIL handling, context cancellation, and concurrent access. Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -125,6 +125,74 @@ func QueryWithExchange(ctx context.Context, server net.IP, name string, qtype ui
|
||||
return nil, fmt.Errorf("query %s %s failed after %d retries: %w", name, QNameType(qtype), cfg.Retries, lastErr)
|
||||
}
|
||||
|
||||
func IterativeQuery(ctx context.Context, server net.IP, name string, qtype uint16, cfg *QueryConfig) (*dns.Msg, error) {
|
||||
if cfg == nil {
|
||||
cfg = DefaultQueryConfig()
|
||||
}
|
||||
if cfg.UDPSize <= 0 {
|
||||
cfg.UDPSize = DefaultEDNS0UDPSize()
|
||||
}
|
||||
return IterativeQueryWithExchange(ctx, server, name, qtype, cfg, realExchange)
|
||||
}
|
||||
|
||||
func IterativeQueryWithExchange(ctx context.Context, server net.IP, name string, qtype uint16, cfg *QueryConfig, exchangeFn ExchangeFunc) (*dns.Msg, error) {
|
||||
if cfg == nil {
|
||||
cfg = DefaultQueryConfig()
|
||||
}
|
||||
if cfg.UDPSize <= 0 {
|
||||
cfg.UDPSize = DefaultEDNS0UDPSize()
|
||||
}
|
||||
|
||||
msg := buildQuery(name, qtype, cfg.UDPSize)
|
||||
msg.RecursionDesired = false
|
||||
serverStr := server.String()
|
||||
|
||||
var lastErr error
|
||||
|
||||
for attempt := 0; attempt < cfg.Retries; attempt++ {
|
||||
if attempt > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, fmt.Errorf("query retries cancelled: %w", ctx.Err())
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.UseTCP {
|
||||
resp, err := exchangeFn(ctx, serverStr, msg, true)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
resp, err := exchangeFn(ctx, serverStr, msg, false)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
if resp == nil {
|
||||
lastErr = fmt.Errorf("nil response")
|
||||
continue
|
||||
}
|
||||
|
||||
if resp.Truncated {
|
||||
resp, err = exchangeFn(ctx, serverStr, msg, true)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("iterative query %s %s failed after %d retries: %w", name, QNameType(qtype), cfg.Retries, lastErr)
|
||||
}
|
||||
|
||||
func buildQuery(name string, qtype uint16, udpSize int) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn(name), qtype)
|
||||
|
||||
Reference in New Issue
Block a user