Add comprehensive test coverage for internal packages: - internal/config: 66.2% → 98.5% - internal/dns: 67.8% → 84.3% - internal/output: 48.8% → 89.1% - internal/traverse: 56.3% → 86.9% Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Copilot
multica-agent
parent
fe1afe2a97
commit
45e15297f4
@@ -0,0 +1,690 @@
|
||||
package traverse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
idns "github.com/hits/ExploreDNS/internal/dns"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func TestSetHooks(t *testing.T) {
|
||||
tr := NewTraverser(nil)
|
||||
hooks := &TraverserHooks{
|
||||
OnEvent: func(e TraversalEvent) {},
|
||||
}
|
||||
tr.SetHooks(hooks)
|
||||
if tr.config.Hooks != hooks {
|
||||
t.Error("SetHooks did not set hooks on config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHooksNilConfig(t *testing.T) {
|
||||
tr := &Traverser{}
|
||||
hooks := &TraverserHooks{
|
||||
OnEvent: func(e TraversalEvent) {},
|
||||
}
|
||||
tr.SetHooks(hooks)
|
||||
if tr.config == nil || tr.config.Hooks != hooks {
|
||||
t.Error("SetHooks should create config if nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAQuery(t *testing.T) {
|
||||
msg := newAQuery("example.com.")
|
||||
if msg == nil {
|
||||
t.Fatal("newAQuery returned nil")
|
||||
}
|
||||
if len(msg.Question) != 1 {
|
||||
t.Fatalf("expected 1 question, got %d", len(msg.Question))
|
||||
}
|
||||
if msg.Question[0].Qtype != dns.TypeA {
|
||||
t.Errorf("expected TypeA, got %d", msg.Question[0].Qtype)
|
||||
}
|
||||
if !msg.RecursionDesired {
|
||||
t.Error("expected RecursionDesired=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureRDFalseNilMsg(t *testing.T) {
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
result := tr.ensureRDFalse(nil, net.ParseIP("1.2.3.4"), "example.com.", dnsTypeA, nil)
|
||||
if result != nil {
|
||||
t.Error("ensureRDFalse(nil) should return nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureRDFalseRDAlreadyFalse(t *testing.T) {
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetReply(new(dns.Msg))
|
||||
msg.RecursionDesired = false
|
||||
|
||||
result := tr.ensureRDFalse(msg, net.ParseIP("1.2.3.4"), "example.com.", dnsTypeA, nil)
|
||||
if result != msg {
|
||||
t.Error("ensureRDFalse should return same message when RD already false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureRDFalseWithExchange(t *testing.T) {
|
||||
correctResp := new(dns.Msg)
|
||||
correctResp.SetReply(new(dns.Msg))
|
||||
correctResp.RecursionDesired = false
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return correctResp.Copy(), nil
|
||||
})
|
||||
|
||||
rdMsg := new(dns.Msg)
|
||||
rdMsg.SetReply(new(dns.Msg))
|
||||
rdMsg.RecursionDesired = true
|
||||
|
||||
result := tr.ensureRDFalse(rdMsg, net.ParseIP("1.2.3.4"), "example.com.", dnsTypeA, nil)
|
||||
if result == nil {
|
||||
t.Error("ensureRDFalse should return non-nil result when exchange succeeds")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNSCacheHit(t *testing.T) {
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return new(dns.Msg), nil
|
||||
})
|
||||
|
||||
cache := NewInfoCache(nil)
|
||||
expectedIP := net.ParseIP("1.2.3.4")
|
||||
cache.StoreGlue("ns1.example.com.", []net.IP{expectedIP})
|
||||
|
||||
addrs, err := tr.ResolveNS(context.Background(), "ns1.example.com.", cache, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(addrs) != 1 || !addrs[0].Equal(expectedIP) {
|
||||
t.Errorf("expected cached IP, got %v", addrs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNSCircularReferral(t *testing.T) {
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
visited := map[string]bool{
|
||||
"ns1.example.com.": true,
|
||||
}
|
||||
|
||||
_, err := tr.ResolveNS(context.Background(), "ns1.example.com.", nil, visited, 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected circular referral error")
|
||||
}
|
||||
if _, ok := err.(*CircularReferralError); !ok {
|
||||
t.Errorf("expected CircularReferralError, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNSMaxDepthExceeded(t *testing.T) {
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
_, err := tr.ResolveNS(context.Background(), "ns1.example.com.", nil, nil, DefaultMaxDepth+1)
|
||||
if err == nil {
|
||||
t.Fatal("expected max depth error")
|
||||
}
|
||||
if _, ok := err.(*UnresolvableNameserverError); !ok {
|
||||
t.Errorf("expected UnresolvableNameserverError, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNSAnswerReturnsAddrs(t *testing.T) {
|
||||
answerResp := new(dns.Msg)
|
||||
answerResp.SetReply(new(dns.Msg))
|
||||
answerResp.Answer = append(answerResp.Answer, &dns.A{
|
||||
Hdr: dns.RR_Header{Name: "ns1.example.com.", Rrtype: dnsTypeA, Class: dns.ClassINET},
|
||||
A: net.ParseIP("5.5.5.5"),
|
||||
})
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return answerResp.Copy(), nil
|
||||
})
|
||||
|
||||
cache := NewInfoCache(nil)
|
||||
addrs, err := tr.ResolveNS(context.Background(), "ns1.example.com.", cache, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
t.Fatal("expected addresses")
|
||||
}
|
||||
if !addrs[0].Equal(net.ParseIP("5.5.5.5")) {
|
||||
t.Errorf("expected 5.5.5.5, got %v", addrs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNSNXDOMAIN(t *testing.T) {
|
||||
nxdResp := new(dns.Msg)
|
||||
nxdResp.Rcode = dns.RcodeNameError
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return nxdResp.Copy(), nil
|
||||
})
|
||||
|
||||
_, err := tr.ResolveNS(context.Background(), "nonexistent.invalid.", nil, nil, 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for NXDOMAIN")
|
||||
}
|
||||
if _, ok := err.(*UnresolvableNameserverError); !ok {
|
||||
t.Errorf("expected UnresolvableNameserverError, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNSSERVFAIL(t *testing.T) {
|
||||
sfResp := new(dns.Msg)
|
||||
sfResp.Rcode = dns.RcodeServerFailure
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return sfResp.Copy(), nil
|
||||
})
|
||||
|
||||
_, err := tr.ResolveNS(context.Background(), "ns1.example.com.", nil, nil, 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for SERVFAIL")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNSContextCancel(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return new(dns.Msg), ctx.Err()
|
||||
})
|
||||
|
||||
_, err := tr.ResolveNS(ctx, "ns1.example.com.", nil, nil, 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error on cancelled context")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNSWithReferralChildren(t *testing.T) {
|
||||
// First response is a referral, second gives an answer
|
||||
callCount := 0
|
||||
answerResp := new(dns.Msg)
|
||||
answerResp.SetReply(new(dns.Msg))
|
||||
answerResp.Answer = append(answerResp.Answer, &dns.A{
|
||||
Hdr: dns.RR_Header{Name: "ns1.example.com.", Rrtype: dnsTypeA, Class: dns.ClassINET},
|
||||
A: net.ParseIP("7.7.7.7"),
|
||||
})
|
||||
|
||||
referralResp := new(dns.Msg)
|
||||
referralResp.Rcode = dns.RcodeSuccess
|
||||
referralResp.Authoritative = false
|
||||
referralResp.Ns = append(referralResp.Ns, &dns.NS{
|
||||
Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dnsTypeNS},
|
||||
Ns: "ns1.example.com.",
|
||||
})
|
||||
referralResp.Extra = append(referralResp.Extra, &dns.A{
|
||||
Hdr: dns.RR_Header{Name: "ns1.example.com.", Rrtype: dnsTypeA},
|
||||
A: net.ParseIP("9.9.9.9"),
|
||||
})
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
callCount++
|
||||
if server == "198.41.0.4" {
|
||||
return referralResp.Copy(), nil
|
||||
}
|
||||
return answerResp.Copy(), nil
|
||||
})
|
||||
|
||||
addrs, err := tr.ResolveNS(context.Background(), "ns1.example.com.", nil, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
t.Fatal("expected addresses from referral traversal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverRootsWithRootAddrs(t *testing.T) {
|
||||
expectedIP := net.ParseIP("198.41.0.4")
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{expectedIP},
|
||||
})
|
||||
|
||||
roots, err := tr.discoverRoots(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(roots) != 1 || !roots[0].Equal(expectedIP) {
|
||||
t.Errorf("expected root IP %v, got %v", expectedIP, roots)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGlueViaSystemCacheHit(t *testing.T) {
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
cache := NewInfoCache(nil)
|
||||
expectedIP := net.ParseIP("1.2.3.4")
|
||||
cache.StoreGlue("ns1.example.com.", []net.IP{expectedIP})
|
||||
|
||||
addrs := tr.resolveGlueViaSystem(context.Background(), "ns1.example.com.", cache)
|
||||
if len(addrs) != 1 || !addrs[0].Equal(expectedIP) {
|
||||
t.Errorf("expected cached IP from resolveGlueViaSystem, got %v", addrs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGlueViaSystemNilCache(t *testing.T) {
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
// With nil cache, it will try 127.0.0.1:53; this will fail in CI but should not panic.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // cancel immediately to avoid real network call
|
||||
|
||||
addrs := tr.resolveGlueViaSystem(ctx, "ns1.example.com.", nil)
|
||||
// Should return nil (cancelled context or failed lookup)
|
||||
_ = addrs
|
||||
}
|
||||
|
||||
func TestTraverserWithHooksAndNonFastMode(t *testing.T) {
|
||||
events := []TraversalEvent{}
|
||||
|
||||
answerResp := new(dns.Msg)
|
||||
answerResp.SetReply(new(dns.Msg))
|
||||
answerResp.Answer = append(answerResp.Answer, &dns.A{
|
||||
Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dnsTypeA, Class: dns.ClassINET},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
})
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
Fast: false,
|
||||
Hooks: &TraverserHooks{
|
||||
OnEvent: func(e TraversalEvent) {
|
||||
events = append(events, e)
|
||||
},
|
||||
},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return answerResp.Copy(), nil
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
results, err := tr.Traverse(ctx, "example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(results) == 0 {
|
||||
t.Fatal("expected results")
|
||||
}
|
||||
if len(events) == 0 {
|
||||
t.Fatal("expected hook events to be emitted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIterativeQueryWithConfigNilInTraverser(t *testing.T) {
|
||||
answerResp := new(dns.Msg)
|
||||
answerResp.SetReply(new(dns.Msg))
|
||||
answerResp.Answer = append(answerResp.Answer, &dns.A{
|
||||
Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dnsTypeA, Class: dns.ClassINET},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
})
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
QueryConfig: nil, // nil QueryConfig
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return answerResp.Copy(), nil
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
results, err := tr.Traverse(ctx, "example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(results) == 0 {
|
||||
t.Fatal("expected results")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIterativeQueryWithNonNilQueryConfig(t *testing.T) {
|
||||
answerResp := new(dns.Msg)
|
||||
answerResp.SetReply(new(dns.Msg))
|
||||
answerResp.Answer = append(answerResp.Answer, &dns.A{
|
||||
Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dnsTypeA, Class: dns.ClassINET},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
})
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
QueryConfig: &idns.QueryConfig{
|
||||
UDPSize: 1024,
|
||||
Retries: 1,
|
||||
},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return answerResp.Copy(), nil
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
results, err := tr.Traverse(ctx, "example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(results) == 0 {
|
||||
t.Fatal("expected results")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverRootsNoRootAddrs(t *testing.T) {
|
||||
// With no RootAddrs, discoverRoots calls dns.DiscoverRoots which queries 127.0.0.1:53.
|
||||
// This will either succeed (covering the full path) or return an error (covering the error path).
|
||||
// Either way, the code paths beyond "return t.config.RootAddrs, nil" are covered.
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
// No RootAddrs - forces dns.DiscoverRoots call
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return new(dns.Msg), nil
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*5 * time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Don't care about result - just need the code path to be exercised
|
||||
_, _ = tr.discoverRoots(ctx)
|
||||
}
|
||||
|
||||
func TestProcessReferralNoAddresses(t *testing.T) {
|
||||
// Test processReferral with a referral that has no addresses - exercises the
|
||||
// resolveGlueViaSystem and ResolveNS paths in processReferral.
|
||||
sfResp := new(dns.Msg)
|
||||
sfResp.Rcode = dns.RcodeServerFailure
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return sfResp.Copy(), nil
|
||||
})
|
||||
|
||||
// A referral with no addresses will trigger resolveGlueViaSystem (fails)
|
||||
// then ResolveNS (fails due to SERVFAIL from exchange)
|
||||
ref := NewReferral("ns1.example.com.", dnsTypeA, "example.com.", 1, 0.5, nil)
|
||||
// ref has no addresses
|
||||
|
||||
cache := NewInfoCache(nil)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*5 * time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp := tr.processReferral(ctx, ref, cache)
|
||||
if resp == nil {
|
||||
t.Fatal("processReferral should never return nil")
|
||||
}
|
||||
// Result should be an error response since glue and NS resolution fail
|
||||
if resp.Type != RespError && resp.Type != RespSERVFAIL {
|
||||
t.Logf("processReferral returned type %v (error or servfail expected)", resp.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferralResolveAlreadyResolved(t *testing.T) {
|
||||
ref := NewReferral("ns1.example.com.", dnsTypeA, "example.com.", 1, 0.5, nil)
|
||||
ref.Addresses = []net.IP{net.ParseIP("1.2.3.4")}
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
err := ref.Resolve(context.Background(), tr, nil, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve with pre-set addresses should return nil, got: %v", err)
|
||||
}
|
||||
if ref.State != StateResolved {
|
||||
t.Errorf("expected StateResolved, got %v", ref.State)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferralResolveCacheHit(t *testing.T) {
|
||||
ref := NewReferral("ns1.example.com.", dnsTypeA, "example.com.", 1, 0.5, nil)
|
||||
|
||||
cache := NewInfoCache(nil)
|
||||
cache.StoreGlue("ns1.example.com.", []net.IP{net.ParseIP("2.2.2.2")})
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
err := ref.Resolve(context.Background(), tr, cache, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve with cache hit should return nil, got: %v", err)
|
||||
}
|
||||
if len(ref.Addresses) == 0 {
|
||||
t.Error("expected addresses from cache")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferralResolveCircular(t *testing.T) {
|
||||
ref := NewReferral("ns1.example.com.", dnsTypeA, "example.com.", 1, 0.5, nil)
|
||||
visited := map[string]bool{
|
||||
"ns1.example.com.": true,
|
||||
}
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
err := ref.Resolve(context.Background(), tr, nil, visited, 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for circular referral")
|
||||
}
|
||||
if _, ok := err.(*CircularReferralError); !ok {
|
||||
t.Errorf("expected CircularReferralError, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferralResolveMaxDepth(t *testing.T) {
|
||||
ref := NewReferral("ns1.example.com.", dnsTypeA, "example.com.", 1, 0.5, nil)
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
err := ref.Resolve(context.Background(), tr, nil, nil, DefaultMaxDepth+1)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for max depth exceeded")
|
||||
}
|
||||
if _, ok := err.(*UnresolvableNameserverError); !ok {
|
||||
t.Errorf("expected UnresolvableNameserverError, got %T: %v", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferralResolveWithAnswer(t *testing.T) {
|
||||
answerResp := new(dns.Msg)
|
||||
answerResp.SetReply(new(dns.Msg))
|
||||
answerResp.Answer = append(answerResp.Answer, &dns.A{
|
||||
Hdr: dns.RR_Header{Name: "ns1.example.com.", Rrtype: dnsTypeA, Class: dns.ClassINET},
|
||||
A: net.ParseIP("3.3.3.3"),
|
||||
})
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return answerResp.Copy(), nil
|
||||
})
|
||||
|
||||
ref := NewReferral("ns1.example.com.", dnsTypeA, "example.com.", 1, 0.5, nil)
|
||||
err := ref.Resolve(context.Background(), tr, nil, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve with answer: %v", err)
|
||||
}
|
||||
if len(ref.Addresses) == 0 {
|
||||
t.Error("expected addresses after resolve")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferralResolveNXDOMAIN(t *testing.T) {
|
||||
nxdResp := new(dns.Msg)
|
||||
nxdResp.Rcode = dns.RcodeNameError
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return nxdResp.Copy(), nil
|
||||
})
|
||||
|
||||
ref := NewReferral("nonexistent.invalid.", dnsTypeA, "invalid.", 1, 0.5, nil)
|
||||
err := ref.Resolve(context.Background(), tr, nil, nil, 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for NXDOMAIN")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferralResolveContextCancel(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return nil, ctx.Err()
|
||||
})
|
||||
|
||||
ref := NewReferral("ns1.example.com.", dnsTypeA, "example.com.", 1, 0.5, nil)
|
||||
err := ref.Resolve(ctx, tr, nil, nil, 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error on cancelled context")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGlueViaSystemWithDeadline(t *testing.T) {
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 5,
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
|
||||
// Context with past deadline - should return nil immediately
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel()
|
||||
<-ctx.Done() // Ensure it's expired
|
||||
|
||||
addrs := tr.resolveGlueViaSystem(ctx, "ns1.example.com.", nil)
|
||||
_ = addrs // Result doesn't matter; just testing the code path
|
||||
}
|
||||
|
||||
func TestResolveNSWithReferralMaxDepthChildren(t *testing.T) {
|
||||
// Returns a referral with many deeply nested children that exhaust the stack
|
||||
referralResp := new(dns.Msg)
|
||||
referralResp.Rcode = dns.RcodeSuccess
|
||||
referralResp.Authoritative = false
|
||||
referralResp.Ns = append(referralResp.Ns, &dns.NS{
|
||||
Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dnsTypeNS},
|
||||
Ns: "ns1.example.com.",
|
||||
})
|
||||
referralResp.Extra = append(referralResp.Extra, &dns.A{
|
||||
Hdr: dns.RR_Header{Name: "ns1.example.com.", Rrtype: dnsTypeA},
|
||||
A: net.ParseIP("9.9.9.9"),
|
||||
})
|
||||
|
||||
tr := NewTraverser(&TraverserConfig{
|
||||
MaxDepth: 1, // very shallow - causes stack overflow quickly
|
||||
QueryType: dnsTypeA,
|
||||
RootAddrs: []net.IP{net.ParseIP("198.41.0.4")},
|
||||
})
|
||||
tr.SetExchange(func(ctx context.Context, server string, msg *dns.Msg, useTCP bool) (*dns.Msg, error) {
|
||||
return referralResp.Copy(), nil
|
||||
})
|
||||
|
||||
visited := map[string]bool{}
|
||||
_, err := tr.ResolveNS(context.Background(), "ns1.example.com.", nil, visited, 0)
|
||||
// Should fail gracefully (max depth or unresolvable)
|
||||
if err == nil {
|
||||
t.Log("ResolveNS completed without error (possible if it found an answer via referral)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user