zapret-kvn/runtime/amnezia/dns_test.go
loop-uh 120bbc5613
Some checks failed
Windows project source guards / test (push) Failing after 44s
fix: refresh managed DNS and cache server table rendering
2026-09-06 14:51:07 +03:00

179 lines
5.1 KiB
Go

package main
import (
"context"
"encoding/binary"
"io"
"net"
"net/netip"
"testing"
"time"
"golang.org/x/net/dns/dnsmessage"
)
func TestDNSDelegatesOnlyToConfiguredSingboxListener(t *testing.T) {
server, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer server.Close()
questions := make(chan dnsmessage.Question, 8)
done := make(chan struct{})
go func() {
defer close(done)
for {
client, err := server.Accept()
if err != nil {
return
}
client.SetDeadline(time.Now().Add(time.Second))
var length [2]byte
if _, err := io.ReadFull(client, length[:]); err != nil {
client.Close()
return
}
data := make([]byte, binary.BigEndian.Uint16(length[:]))
if _, err := io.ReadFull(client, data); err != nil {
client.Close()
return
}
var request dnsmessage.Message
if err := request.Unpack(data); err != nil {
client.Close()
return
}
response := dnsmessage.Message{
Header: dnsmessage.Header{ID: request.ID, Response: true, RecursionAvailable: true},
Questions: request.Questions,
}
for _, q := range request.Questions {
questions <- q
if q.Type == dnsmessage.TypeA {
response.Answers = append(response.Answers, dnsmessage.Resource{
Header: dnsmessage.ResourceHeader{Name: q.Name, Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, TTL: 1},
Body: &dnsmessage.AResource{A: [4]byte{192, 0, 2, 42}},
})
}
}
encoded, err := response.Pack()
if err != nil {
client.Close()
return
}
binary.BigEndian.PutUint16(length[:], uint16(len(encoded)))
client.Write(append(length[:], encoded...))
client.Close()
}
}()
lookup := delegatedDNS(server.Addr().String(), []netip.Addr{netip.MustParseAddr("10.0.0.2")})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
for _, host := range []string{"only-in-singbox.invalid", "localhost"} {
ips, err := lookup(ctx, host)
if err != nil {
t.Fatal(err)
}
if len(ips) != 1 || ips[0] != netip.MustParseAddr("192.0.2.42") {
t.Fatalf("%s bypassed sing-box DNS: %v", host, ips)
}
}
server.Close()
<-done
close(questions)
count := 0
for q := range questions {
count++
if (q.Name.String() != "only-in-singbox.invalid." && q.Name.String() != "localhost.") || q.Type != dnsmessage.TypeA {
t.Fatalf("unexpected DNS query: %v", q)
}
}
if count != 2 {
t.Fatalf("expected both names at the private listener, got %d", count)
}
}
func TestMissingDelegationDoesNotUseSystemDNS(t *testing.T) {
lookup := delegatedDNS("", []netip.Addr{netip.MustParseAddr("10.0.0.2")})
if ips, err := lookup(context.Background(), "localhost"); err == nil || len(ips) != 0 {
t.Fatalf("missing sing-box DNS accepted: %v %v", ips, err)
}
}
func TestRelayListenerFailureCancelsActiveClients(t *testing.T) {
c := testConfig()
s := relay{username: c.Username, password: c.Password, journal: newJournal(c)}
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
done := make(chan error, 1)
go func() { done <- s.serve(context.Background(), listener) }()
client := login(t, listener.Addr().String(), c)
defer client.Close()
listener.Close()
select {
case err := <-done:
if err == nil {
t.Fatal("unexpected listener failure was hidden")
}
case <-time.After(time.Second):
t.Fatal("listener failure left active clients running")
}
}
// The native DNS chain can spend six seconds on VPN DoH before trying direct
// DoH. The sidecar must not impose the former five-second cutoff on that chain.
func TestDNSWaitsForFrontFallback(t *testing.T) {
server, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer server.Close()
done := make(chan struct{})
go func() {
defer close(done)
client, err := server.Accept()
if err != nil {
return
}
defer client.Close()
client.SetDeadline(time.Now().Add(10 * time.Second))
var length [2]byte
if _, err = io.ReadFull(client, length[:]); err != nil {
return
}
data := make([]byte, binary.BigEndian.Uint16(length[:]))
if _, err = io.ReadFull(client, data); err != nil {
return
}
var query dnsmessage.Message
if query.Unpack(data) != nil {
return
}
time.Sleep(5200 * time.Millisecond)
response := dnsmessage.Message{
Header: dnsmessage.Header{ID: query.ID, Response: true, RecursionAvailable: true},
Questions: query.Questions,
Answers: []dnsmessage.Resource{{
Header: dnsmessage.ResourceHeader{Name: query.Questions[0].Name, Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, TTL: 1},
Body: &dnsmessage.AResource{A: [4]byte{192, 0, 2, 42}},
}},
}
encoded, err := response.Pack()
if err != nil {
return
}
binary.BigEndian.PutUint16(length[:], uint16(len(encoded)))
client.Write(append(length[:], encoded...))
}()
lookup := delegatedDNS(server.Addr().String(), []netip.Addr{netip.MustParseAddr("10.0.0.2")})
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
ips, err := lookup(ctx, "fallback.test")
<-done
if err != nil || len(ips) != 1 || ips[0] != netip.MustParseAddr("192.0.2.42") {
t.Fatalf("front fallback cut off: %v %v", ips, err)
}
}