package resolver_test import ( "context" "ekdns/resolver" "io" "testing" "time" ) // --------------------------------------------------------------------------- // CheckSplitHorizon tests (T015) // --------------------------------------------------------------------------- func TestCheckSplitHorizon_NoConflict_SameIPs(t *testing.T) { qname := mustNewName("www.example.com.") localAddr := startFakeDNSMulti(t, func(query []byte) []byte { return buildAResponse(queryID(query), qname, [][4]byte{{93, 184, 216, 34}}) }) authIPs := []string{"93.184.216.34"} result := resolver.CheckSplitHorizon( context.Background(), io.Discard, []string{localAddr}, "www.example.com", authIPs, "ns1.example.com", 2*time.Second, ) if result.HasConflict { t.Errorf("expected no conflict when IPs match, got conflict: local=%v auth=%v", result.LocalIPs, result.AuthoritativeIPs) } } func TestCheckSplitHorizon_Conflict_DifferentIPs(t *testing.T) { qname := mustNewName("www.example.com.") // Local resolver returns internal IP. localAddr := startFakeDNSMulti(t, func(query []byte) []byte { return buildAResponse(queryID(query), qname, [][4]byte{{10, 0, 5, 100}}) }) authIPs := []string{"203.0.113.50"} // authoritative returned a different IP result := resolver.CheckSplitHorizon( context.Background(), io.Discard, []string{localAddr}, "www.example.com", authIPs, "ns1.example.com", 2*time.Second, ) if !result.HasConflict { t.Error("expected conflict when IPs differ") } if len(result.LocalIPs) == 0 { t.Error("expected LocalIPs to be populated") } if result.LocalSource == "" { t.Error("expected LocalSource to be populated") } } func TestCheckSplitHorizon_NoConflict_LocalNXDOMAIN(t *testing.T) { qname := mustNewName("www.example.com.") localAddr := startFakeDNSMulti(t, func(query []byte) []byte { return buildNXDOMAINResponse(queryID(query), qname) }) authIPs := []string{"93.184.216.34"} result := resolver.CheckSplitHorizon( context.Background(), io.Discard, []string{localAddr}, "www.example.com", authIPs, "ns1.example.com", 2*time.Second, ) if result.HasConflict { t.Error("expected no conflict when local returns NXDOMAIN") } if result.LocalIPs != nil { t.Errorf("expected nil LocalIPs for NXDOMAIN, got %v", result.LocalIPs) } } func TestCheckSplitHorizon_NoConflict_LocalTimeout(t *testing.T) { silentAddr := startSilentDNS(t) authIPs := []string{"93.184.216.34"} result := resolver.CheckSplitHorizon( context.Background(), io.Discard, []string{silentAddr}, "www.example.com", authIPs, "ns1.example.com", 100*time.Millisecond, ) if result.HasConflict { t.Error("expected no conflict when local resolver times out") } if result.LocalIPs != nil { t.Errorf("expected nil LocalIPs for timeout, got %v", result.LocalIPs) } } func TestCheckSplitHorizon_NoConflict_OrderIndependent(t *testing.T) { qname := mustNewName("www.example.com.") // Local resolver returns IPs in different order than authoritative. localAddr := startFakeDNSMulti(t, func(query []byte) []byte { return buildAResponse(queryID(query), qname, [][4]byte{ {5, 6, 7, 8}, {1, 2, 3, 4}, }) }) // Authoritative had them in reverse order. authIPs := []string{"1.2.3.4", "5.6.7.8"} result := resolver.CheckSplitHorizon( context.Background(), io.Discard, []string{localAddr}, "www.example.com", authIPs, "ns1.example.com", 2*time.Second, ) if result.HasConflict { t.Errorf("expected no conflict for same IPs in different order: local=%v auth=%v", result.LocalIPs, result.AuthoritativeIPs) } } func TestCheckSplitHorizon_NoLocalResolvers(t *testing.T) { authIPs := []string{"93.184.216.34"} result := resolver.CheckSplitHorizon( context.Background(), io.Discard, nil, // no local resolvers "www.example.com", authIPs, "ns1.example.com", 2*time.Second, ) if result.HasConflict { t.Error("expected no conflict when no local resolvers provided") } if result.LocalIPs != nil { t.Error("expected nil LocalIPs when no resolvers provided") } }