Files
x/resolver/exchanger/exchanger_test.go
T

377 lines
9.9 KiB
Go

package exchanger
import (
"context"
"crypto/tls"
"net"
"strings"
"testing"
"time"
"github.com/go-gost/core/chain"
xlogger "github.com/go-gost/x/logger"
"github.com/miekg/dns"
)
func TestNewExchanger_UDP(t *testing.T) {
ex, err := NewExchanger("1.1.1.1:53", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.network != "udp" {
t.Errorf("expected udp network, got %q", e.network)
}
if e.addr != "1.1.1.1:53" {
t.Errorf("expected addr 1.1.1.1:53, got %q", e.addr)
}
}
func TestNewExchanger_UDPDefaultPort(t *testing.T) {
ex, err := NewExchanger("1.1.1.1", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.addr != "1.1.1.1:53" {
t.Errorf("expected addr 1.1.1.1:53 (default port), got %q", e.addr)
}
}
func TestNewExchanger_TCP(t *testing.T) {
ex, err := NewExchanger("tcp://1.1.1.1:53", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.network != "tcp" {
t.Errorf("expected tcp network, got %q", e.network)
}
}
func TestNewExchanger_TLS(t *testing.T) {
ex, err := NewExchanger("tls://1.1.1.1:853", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.network != "tcp" {
t.Errorf("expected tls→tcp network, got %q", e.network)
}
if e.options.tlsConfig == nil {
t.Error("expected TLS config to be set")
}
}
func TestNewExchanger_DoT(t *testing.T) {
ex, err := NewExchanger("dot://1.1.1.1:853", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.network != "tcp" {
t.Errorf("expected dot→tcp network, got %q", e.network)
}
}
func TestNewExchanger_HTTPS(t *testing.T) {
ex, err := NewExchanger("https://1.0.0.1/dns-query", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.network != "https" {
t.Errorf("expected https network, got %q", e.network)
}
if e.client == nil {
t.Error("expected HTTP client to be created for DoH")
}
}
func TestNewExchanger_DefaultTimeout(t *testing.T) {
ex, err := NewExchanger("1.1.1.1:53", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.options.timeout != 5*time.Second {
t.Errorf("expected default timeout 5s, got %v", e.options.timeout)
}
}
func TestNewExchanger_CustomTimeout(t *testing.T) {
ex, err := NewExchanger("1.1.1.1:53", TimeoutOption(10*time.Second), LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.options.timeout != 10*time.Second {
t.Errorf("expected timeout 10s, got %v", e.options.timeout)
}
}
func TestNewExchanger_DefaultScheme(t *testing.T) {
ex, err := NewExchanger("8.8.8.8", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.network != "udp" {
t.Errorf("expected default to udp, got %q", e.network)
}
}
func TestNewExchanger_InvalidURL(t *testing.T) {
_, err := NewExchanger("://invalid", LoggerOption(xlogger.Nop()))
if err == nil {
t.Error("expected error for invalid URL")
}
}
func TestNewExchanger_CustomTLSConfig(t *testing.T) {
customTLS := &tls.Config{MinVersion: tls.VersionTLS12}
ex, err := NewExchanger("tls://1.1.1.1:853", TLSConfigOption(customTLS), LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.options.tlsConfig != customTLS {
t.Error("expected custom TLS config to be preserved")
}
}
func TestNewExchanger_SchemeNormalization(t *testing.T) {
tests := []struct {
addr string
network string
}{
{"udp://1.1.1.1:53", "udp"},
{"tcp://1.1.1.1:53", "tcp"},
{"tls://1.1.1.1:853", "tcp"},
{"dot://1.1.1.1:853", "tcp"},
{"https://1.1.1.1/dns-query", "https"},
{"1.1.1.1:53", "udp"}, // bare addr → udp
{"quic://1.1.1.1:53", "udp"}, // unknown → udp
}
for _, tt := range tests {
ex, err := NewExchanger(tt.addr, LoggerOption(xlogger.Nop()))
if err != nil {
t.Errorf("NewExchanger(%q): %v", tt.addr, err)
continue
}
e := ex.(*exchanger)
if e.network != tt.network {
t.Errorf("NewExchanger(%q): network=%q, want %q", tt.addr, e.network, tt.network)
}
}
}
func TestNewExchanger_String(t *testing.T) {
ex, err := NewExchanger("udp://1.1.1.1:53", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ex.String() != "udp://1.1.1.1:53" {
t.Errorf("String() = %q, want %q", ex.String(), "udp://1.1.1.1:53")
}
}
func TestNewExchanger_System(t *testing.T) {
ex, err := NewExchanger("system", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, ok := ex.(*systemExchanger); !ok {
t.Fatalf("NewExchanger(system) = %T, want *systemExchanger", ex)
}
if ex.String() != "system" {
t.Errorf("String() = %q, want system", ex.String())
}
}
func TestNewExchanger_AutoSystem(t *testing.T) {
ex, err := NewExchanger("auto", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, ok := ex.(*systemExchanger); !ok {
t.Fatalf("NewExchanger(auto) = %T, want *systemExchanger", ex)
}
}
func TestSystemExchanger_ExchangeA(t *testing.T) {
ex := &systemExchanger{
rawAddr: "system",
lookupIP: func(_ context.Context, network, host string) ([]net.IP, error) {
if network != "ip4" {
t.Errorf("network = %q, want ip4", network)
}
if host != "example.com" {
t.Errorf("host = %q, want example.com", host)
}
return []net.IP{
net.ParseIP("192.0.2.1"),
net.ParseIP("2001:db8::1"),
}, nil
},
}
mq := new(dns.Msg)
mq.SetQuestion(dns.Fqdn("example.com"), dns.TypeA)
packed, err := mq.Pack()
if err != nil {
t.Fatal(err)
}
reply, err := ex.Exchange(context.Background(), packed)
if err != nil {
t.Fatalf("Exchange: %v", err)
}
mr := new(dns.Msg)
if err := mr.Unpack(reply); err != nil {
t.Fatalf("Unpack: %v", err)
}
if len(mr.Answer) != 1 {
t.Fatalf("len(Answer) = %d, want 1", len(mr.Answer))
}
a, ok := mr.Answer[0].(*dns.A)
if !ok {
t.Fatalf("Answer[0] = %T, want *dns.A", mr.Answer[0])
}
if !a.A.Equal(net.ParseIP("192.0.2.1")) {
t.Errorf("A = %s, want 192.0.2.1", a.A)
}
}
func TestSystemExchanger_ExchangeAAAA(t *testing.T) {
ex := &systemExchanger{
rawAddr: "system",
lookupIP: func(_ context.Context, network, host string) ([]net.IP, error) {
if network != "ip6" {
t.Errorf("network = %q, want ip6", network)
}
if host != "example.com" {
t.Errorf("host = %q, want example.com", host)
}
return []net.IP{
net.ParseIP("192.0.2.1"),
net.ParseIP("2001:db8::1"),
}, nil
},
}
mq := new(dns.Msg)
mq.SetQuestion(dns.Fqdn("example.com"), dns.TypeAAAA)
packed, err := mq.Pack()
if err != nil {
t.Fatal(err)
}
reply, err := ex.Exchange(context.Background(), packed)
if err != nil {
t.Fatalf("Exchange: %v", err)
}
mr := new(dns.Msg)
if err := mr.Unpack(reply); err != nil {
t.Fatalf("Unpack: %v", err)
}
if len(mr.Answer) != 1 {
t.Fatalf("len(Answer) = %d, want 1", len(mr.Answer))
}
aaaa, ok := mr.Answer[0].(*dns.AAAA)
if !ok {
t.Fatalf("Answer[0] = %T, want *dns.AAAA", mr.Answer[0])
}
if !aaaa.AAAA.Equal(net.ParseIP("2001:db8::1")) {
t.Errorf("AAAA = %s, want 2001:db8::1", aaaa.AAAA)
}
}
func TestExchange_DialFailure(t *testing.T) {
ex, err := NewExchanger("udp://127.0.0.1:1", TimeoutOption(100*time.Millisecond), LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = ex.Exchange(context.Background(), []byte{0})
if err == nil {
t.Error("expected error for failed dial")
}
}
func TestExchange_ContextCancelled(t *testing.T) {
ex, err := NewExchanger("udp://1.1.1.1:53", TimeoutOption(5*time.Second), LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err = ex.Exchange(ctx, []byte{0})
if err == nil {
t.Error("expected error for cancelled context")
}
}
func TestExchange_TimeoutExceeded(t *testing.T) {
ex, err := NewExchanger("tcp://192.0.2.1:53", TimeoutOption(50*time.Millisecond), LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
start := time.Now()
_, err = ex.Exchange(context.Background(), []byte{0})
elapsed := time.Since(start)
if err == nil {
t.Error("expected timeout error")
}
if elapsed > 2*time.Second {
t.Errorf("timeout took too long: %v", elapsed)
}
}
func TestDoHExchange_Failure(t *testing.T) {
ex, err := NewExchanger("https://1.0.0.1/dns-query", TimeoutOption(100*time.Millisecond), LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = ex.Exchange(context.Background(), []byte("not-a-dns-query"))
if err == nil {
t.Error("expected error for DoH exchange failure")
}
}
func TestNewExchanger_BareAddr(t *testing.T) {
ex, err := NewExchanger("8.8.8.8", LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if !strings.Contains(e.addr, "53") {
t.Errorf("expected default port 53 in addr, got %q", e.addr)
}
}
func TestNewExchanger_WithRouter(t *testing.T) {
r := &stubRouter{}
ex, err := NewExchanger("udp://1.1.1.1:53", RouterOption(r), LoggerOption(xlogger.Nop()))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
e := ex.(*exchanger)
if e.router != r {
t.Error("expected custom router to be set")
}
}
// stubRouter implements chain.Router for tests.
type stubRouter struct{}
func (s *stubRouter) Options() *chain.RouterOptions { return nil }
func (s *stubRouter) Dial(_ context.Context, _, _ string, _ ...chain.DialOption) (net.Conn, error) {
return nil, net.UnknownNetworkError("stub")
}
func (s *stubRouter) Bind(_ context.Context, _, _ string, _ ...chain.BindOption) (net.Listener, error) {
return nil, net.UnknownNetworkError("stub")
}