mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
133 lines
3.5 KiB
Go
133 lines
3.5 KiB
Go
package dnsdial
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
func dnsAnswer(t *testing.T, query []byte) []byte {
|
|
t.Helper()
|
|
req := new(dns.Msg)
|
|
if err := req.Unpack(query); err != nil {
|
|
t.Fatalf("unpack query: %v", err)
|
|
}
|
|
if len(req.Question) != 1 {
|
|
t.Fatalf("expected 1 question, got %d", len(req.Question))
|
|
}
|
|
reply := new(dns.Msg)
|
|
reply.SetReply(req)
|
|
if q := req.Question[0]; q.Qtype == dns.TypeA {
|
|
reply.Answer = append(reply.Answer, &dns.A{
|
|
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 300},
|
|
A: net.IPv4(9, 9, 9, 9),
|
|
})
|
|
}
|
|
out, err := reply.Pack()
|
|
if err != nil {
|
|
t.Fatalf("pack reply: %v", err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func mockDoH(t *testing.T) *DohResolver {
|
|
t.Helper()
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Errorf("read body: %v", err)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/dns-message")
|
|
_, _ = w.Write(dnsAnswer(t, body))
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
return newDohResolverWithClient(
|
|
[]DohEndpoint{{URL: srv.URL, Hostname: "mock", BootstrapIPs: []string{"127.0.0.1"}}},
|
|
srv.Client(),
|
|
)
|
|
}
|
|
|
|
// udpDNSServer поднимает UDP/53-стенд; reply == nil - сервер молчит.
|
|
func udpDNSServer(t *testing.T, reply func(query []byte) []byte) string {
|
|
t.Helper()
|
|
pc, err := (&net.ListenConfig{}).ListenPacket(t.Context(), "udp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = pc.Close() })
|
|
go func() {
|
|
buf := make([]byte, 512)
|
|
for {
|
|
n, addr, err := pc.ReadFrom(buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if reply != nil {
|
|
_, _ = pc.WriteTo(reply(buf[:n]), addr)
|
|
}
|
|
}
|
|
}()
|
|
return pc.LocalAddr().String()
|
|
}
|
|
|
|
func useUDPServers(t *testing.T, servers []string) {
|
|
t.Helper()
|
|
old := udpDNSServersPtr.Load()
|
|
udpDNSServersPtr.Store(&servers)
|
|
t.Cleanup(func() { udpDNSServersPtr.Store(old) })
|
|
}
|
|
|
|
func TestAutoDial_DoHStickyUntilServersChange(t *testing.T) {
|
|
dial := autoDial(mockDoH(t))
|
|
// Невалидный адрес валит UDP-пробу сразу.
|
|
servers := []string{"not-a-valid-host-port"}
|
|
useUDPServers(t, servers)
|
|
|
|
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
|
defer cancel()
|
|
dialTo := func() string {
|
|
t.Helper()
|
|
conn, err := dial(ctx, "udp", "unused")
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
return conn.RemoteAddr().String()
|
|
}
|
|
dialTo()
|
|
|
|
good := udpDNSServer(t, func(q []byte) []byte { return dnsAnswer(t, q) })
|
|
servers[0] = good
|
|
if dialTo() == good {
|
|
t.Fatal("re-probed UDP without servers change")
|
|
}
|
|
|
|
SetUDPDNSServers([]string{good})
|
|
if got := dialTo(); got != good {
|
|
t.Fatalf("after servers change dial went to %s, want UDP %s", got, good)
|
|
}
|
|
}
|
|
|
|
// Молчащий первый сервер не должен съедать бюджет второго.
|
|
func TestUDPProbe_SilentFirstServer(t *testing.T) {
|
|
useUDPServers(t, []string{udpDNSServer(t, nil), udpDNSServer(t, func(q []byte) []byte { return dnsAnswer(t, q) })})
|
|
if !udpProbe(300 * time.Millisecond) {
|
|
t.Fatal("udpProbe = false, want true via second server")
|
|
}
|
|
}
|
|
|
|
// Эхо запроса - не DNS-ответ, UDP/53 такой сервер не обслуживает.
|
|
func TestUDPProbe_RejectsNonResponse(t *testing.T) {
|
|
useUDPServers(t, []string{udpDNSServer(t, func(q []byte) []byte { return append([]byte(nil), q...) })})
|
|
if udpProbe(300 * time.Millisecond) {
|
|
t.Fatal("udpProbe = true on echoed query, want false")
|
|
}
|
|
}
|