| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741 |
- package amneziawgnet
- import (
- "bytes"
- "encoding/binary"
- "fmt"
- "io"
- "net"
- "net/netip"
- "strconv"
- "strings"
- "testing"
- "time"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "github.com/amnezia-vpn/amneziawg-go/v3/device"
- "github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
- wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
- )
- func verboseLoggerForTest(prefix string) *device.Logger {
- return device.NewLogger(device.LogLevelVerbose, prefix)
- }
- const (
- tunnelTestClientAddr = "10.203.0.2"
- tunnelTestServerAddr = "10.203.0.1"
- tunnelTestClientAddrV6 = "fd00:203::2"
- tunnelTestServerAddrV6 = "fd00:203::1"
- egressTestDialTimeout = 5 * time.Second
- )
- // pairedTunnel wires an outbound client device to an embedded server device
- // over host UDP; the server stack hosts the far-end services under test.
- type pairedTunnel struct {
- client *Device
- server *Device
- serverIP netip.Addr
- }
- func newPairedTunnelForTest(t *testing.T) *pairedTunnel {
- t.Helper()
- slog := verboseLoggerForTest("(tsrv) ")
- serverPriv, serverPub, err := wgutil.GenerateWireguardKeypair()
- if err != nil {
- t.Fatal(err)
- }
- clientPriv, clientPub, err := wgutil.GenerateWireguardKeypair()
- if err != nil {
- t.Fatal(err)
- }
- pc, err := net.ListenPacket("udp", "127.0.0.1:0")
- if err != nil {
- t.Fatal(err)
- }
- listenPort := pc.LocalAddr().(*net.UDPAddr).Port
- pc.Close()
- obf := amneziawg.Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20}
- serverInst := amneziawg.Instance{
- Id: 1,
- InterfaceName: "awg-dnstest",
- ListenPort: listenPort,
- PrivateKey: serverPriv,
- PublicKey: serverPub,
- Address: []string{tunnelTestServerAddr + "/24"},
- MTU: 1420,
- Obfuscation: obf,
- Peers: []amneziawg.Peer{{
- PublicKey: clientPub,
- AllowedIPs: []string{tunnelTestClientAddr + "/32"},
- }},
- }
- server, err := newUnconfiguredDevice(serverInst, DeviceOptions{Logger: slog})
- if err != nil {
- t.Fatalf("server device: %v", err)
- }
- t.Cleanup(server.Close)
- // Server Up before client exists: the first handshake fires at
- // ConfigureClient; a missed initiation costs a 5s REKEY_TIMEOUT.
- if err := server.Configure(serverInst, DeviceOptions{Logger: slog}); err != nil {
- t.Fatalf("server Configure: %v", err)
- }
- clientInst := amneziawg.OutboundInstance{
- Tag: "awg-dom-test",
- Address: []string{tunnelTestClientAddr + "/32"},
- MTU: 1420,
- PrivateKey: clientPriv,
- Obfuscation: obf,
- Peers: []amneziawg.OutboundPeer{{
- PublicKey: serverPub,
- Endpoint: net.JoinHostPort("127.0.0.1", strconv.Itoa(listenPort)),
- AllowedIPs: []string{"0.0.0.0/0", "::/0"},
- KeepAlive: 1,
- }},
- }
- clog := verboseLoggerForTest("(tcli) ")
- client, err := newUnconfiguredClientDevice(clientInst, DeviceOptions{Logger: clog})
- if err != nil {
- t.Fatalf("client device: %v", err)
- }
- if err := client.ConfigureClient(clientInst, DeviceOptions{Logger: clog}); err != nil {
- client.Close()
- t.Fatalf("ConfigureClient: %v", err)
- }
- t.Cleanup(client.Close)
- return &pairedTunnel{
- client: client,
- server: server,
- serverIP: netip.MustParseAddr(tunnelTestServerAddr),
- }
- }
- func newPairedTunnelV6ForTest(t *testing.T) *pairedTunnel {
- t.Helper()
- slog := verboseLoggerForTest("(tsrv6) ")
- serverPriv, serverPub, err := wgutil.GenerateWireguardKeypair()
- if err != nil {
- t.Fatal(err)
- }
- clientPriv, clientPub, err := wgutil.GenerateWireguardKeypair()
- if err != nil {
- t.Fatal(err)
- }
- pc, err := net.ListenPacket("udp", "127.0.0.1:0")
- if err != nil {
- t.Fatal(err)
- }
- listenPort := pc.LocalAddr().(*net.UDPAddr).Port
- pc.Close()
- obf := amneziawg.Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20}
- serverInst := amneziawg.Instance{
- Id: 2,
- InterfaceName: "awg-dnstest6",
- ListenPort: listenPort,
- PrivateKey: serverPriv,
- PublicKey: serverPub,
- Address: []string{tunnelTestServerAddrV6 + "/64", "2606:4700:4700::1111/128"},
- MTU: 1420,
- Obfuscation: obf,
- Peers: []amneziawg.Peer{{
- PublicKey: clientPub,
- AllowedIPs: []string{tunnelTestClientAddrV6 + "/128"},
- }},
- }
- server, err := newUnconfiguredDevice(serverInst, DeviceOptions{Logger: slog})
- if err != nil {
- t.Fatalf("server device: %v", err)
- }
- t.Cleanup(server.Close)
- if err := server.Configure(serverInst, DeviceOptions{Logger: slog}); err != nil {
- t.Fatalf("server Configure: %v", err)
- }
- clientInst := amneziawg.OutboundInstance{
- Tag: "awg-dom-v6-test",
- Address: []string{tunnelTestClientAddrV6 + "/128"},
- MTU: 1420,
- PrivateKey: clientPriv,
- Obfuscation: obf,
- Peers: []amneziawg.OutboundPeer{{
- PublicKey: serverPub,
- Endpoint: net.JoinHostPort("127.0.0.1", strconv.Itoa(listenPort)),
- AllowedIPs: []string{"::/0"},
- KeepAlive: 1,
- }},
- }
- clog := verboseLoggerForTest("(tcli6) ")
- client, err := newUnconfiguredClientDevice(clientInst, DeviceOptions{Logger: clog})
- if err != nil {
- t.Fatalf("client device: %v", err)
- }
- if err := client.ConfigureClient(clientInst, DeviceOptions{Logger: clog}); err != nil {
- client.Close()
- t.Fatalf("ConfigureClient: %v", err)
- }
- t.Cleanup(client.Close)
- return &pairedTunnel{
- client: client,
- server: server,
- serverIP: netip.MustParseAddr(tunnelTestServerAddrV6),
- }
- }
- func registerEgressDeviceForTest(t *testing.T, dev *Device) {
- t.Helper()
- srv := GetEgressServer()
- srv.SetStack("awg-dom-test", dev)
- if err := srv.Listen(); err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() { srv.DeleteStack("awg-dom-test") })
- }
- // startTunnelDNS answers A/AAAA queries from INSIDE the server's netstack;
- // reaching it proves DNS rode the tunnel, not the host resolver.
- func (p *pairedTunnel) startDNS(t *testing.T, answer netip.Addr) chan string {
- t.Helper()
- proto := ipv4.ProtocolNumber
- if p.serverIP.Is6() {
- proto = ipv6.ProtocolNumber
- }
- ln, err := gonet.DialUDP(p.server.Stack, &tcpip.FullAddress{NIC: 1, Port: 53}, nil, proto)
- if err != nil {
- t.Fatalf("bind fake dns in server stack: %v", err)
- }
- got := make(chan string, 8)
- go func() {
- defer ln.Close()
- buf := make([]byte, 512)
- for {
- n, from, rerr := ln.ReadFrom(buf)
- if rerr != nil {
- return
- }
- q := buf[:n]
- if name := dnsQuestionName(q); name != "" {
- select {
- case got <- name:
- default:
- }
- }
- if resp := buildARecordReply(q, answer); resp != nil {
- if _, werr := ln.WriteTo(resp, from); werr != nil {
- return
- }
- }
- }
- }()
- t.Cleanup(func() { ln.Close() })
- return got
- }
- func (p *pairedTunnel) overrideDNS(t *testing.T, answer netip.Addr) chan string {
- t.Helper()
- srv := GetEgressServer()
- prev := srv.currentDNSServer()
- srv.SetDNSServer(net.JoinHostPort(p.serverIP.String(), "53"))
- t.Cleanup(func() { srv.SetDNSServer(prev) })
- resetTunnelDNSCacheForTest()
- return p.startDNS(t, answer)
- }
- func resetTunnelDNSCacheForTest() {
- tunnelDNSCache.mu.Lock()
- tunnelDNSCache.m = map[string]tunnelDNSCacheEntry{}
- tunnelDNSCache.mu.Unlock()
- }
- func dnsQuestionName(q []byte) string {
- if len(q) < 12 {
- return ""
- }
- i := 12
- var parts []byte
- for i < len(q) {
- l := int(q[i])
- i++
- if l == 0 {
- break
- }
- if i+l > len(q) || l > 63 {
- return ""
- }
- parts = append(parts, q[i:i+l]...)
- parts = append(parts, '.')
- i += l
- }
- for len(parts) > 0 && parts[len(parts)-1] == '.' {
- parts = parts[:len(parts)-1]
- }
- return string(parts)
- }
- func buildARecordReply(q []byte, answer netip.Addr) []byte {
- if len(q) < 17 {
- return nil
- }
- out := make([]byte, 0, len(q)+16)
- header := make([]byte, 12)
- copy(header[0:2], q[0:2])
- header[2] = 0x81 // QR=1 RD=1
- header[3] = 0x80 // RA=1 RCODE=0
- binary.BigEndian.PutUint16(header[4:], 1)
- binary.BigEndian.PutUint16(header[6:], 1)
- out = append(out, header...)
- end := len(q)
- for end >= 5 && q[end-4] == 0 && q[end-3] == 0 && q[end-2] == 0 && q[end-1] == 0 {
- end -= 4
- }
- out = append(out, q[12:end]...)
- if answer.Is4() {
- a := answer.As4()
- rr := make([]byte, 16)
- rr[0], rr[1] = 0xc0, 0x0c
- binary.BigEndian.PutUint16(rr[2:], 1) // Type A
- binary.BigEndian.PutUint16(rr[4:], 1) // IN
- binary.BigEndian.PutUint32(rr[6:], 30) // TTL
- binary.BigEndian.PutUint16(rr[10:], 4)
- copy(rr[12:], a[:])
- out = append(out, rr...)
- } else if answer.Is6() {
- a16 := answer.As16()
- rr := make([]byte, 28)
- rr[0], rr[1] = 0xc0, 0x0c
- binary.BigEndian.PutUint16(rr[2:], 28) // Type AAAA
- binary.BigEndian.PutUint16(rr[4:], 1) // IN
- binary.BigEndian.PutUint32(rr[6:], 30) // TTL
- binary.BigEndian.PutUint16(rr[10:], 16)
- copy(rr[12:], a16[:])
- out = append(out, rr...)
- }
- return out
- }
- func socksAuthUser(t *testing.T, ctl net.Conn, user string) {
- t.Helper()
- ctl.SetDeadline(time.Now().Add(egressTestDialTimeout))
- if _, err := ctl.Write([]byte{0x05, 0x02, 0x00, 0x02}); err != nil {
- t.Fatal(err)
- }
- r := make([]byte, 2)
- if _, err := io.ReadFull(ctl, r); err != nil {
- t.Fatalf("greeting read: %v", err)
- }
- pass := SocksPassword()
- req := make([]byte, 0, 3+len(user)+len(pass))
- req = append(req, 0x01, byte(len(user)))
- req = append(req, user...)
- req = append(req, byte(len(pass)))
- req = append(req, pass...)
- if _, err := ctl.Write(req); err != nil {
- t.Fatal(err)
- }
- auth := make([]byte, 2)
- if _, err := io.ReadFull(ctl, auth); err != nil || auth[1] != 0x00 {
- t.Fatalf("auth rejected: %v %v", err, auth)
- }
- }
- func socksAuth(t *testing.T, ctl net.Conn) {
- t.Helper()
- socksAuthUser(t, ctl, "awg-dom-test")
- }
- func TestEgressGreetingRejectsNoAuthClient(t *testing.T) {
- tun := newPairedTunnelForTest(t)
- registerEgressDeviceForTest(t, tun.client)
- ctl, err := (&net.Dialer{Timeout: egressTestDialTimeout}).Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(EgressBasePort)))
- if err != nil {
- t.Fatal(err)
- }
- defer ctl.Close()
- ctl.SetDeadline(time.Now().Add(egressTestDialTimeout))
- // Client offers only NO-AUTH; server must answer 0xFF.
- if _, err := ctl.Write([]byte{0x05, 0x01, 0x00}); err != nil {
- t.Fatal(err)
- }
- r := make([]byte, 2)
- if _, err := io.ReadFull(ctl, r); err != nil {
- t.Fatalf("greeting read: %v", err)
- }
- if r[0] != 0x05 || r[1] != 0xFF {
- t.Fatalf("greeting reply = %v, want 05 FF (auth required)", r)
- }
- }
- func TestEgressConnectDomainResolvesThroughTunnel(t *testing.T) {
- tun := newPairedTunnelForTest(t)
- registerEgressDeviceForTest(t, tun.client)
- // Resolving to the server's own tunnel address makes the follow-up dial
- // fail fast (nothing listens on :80), while proving resolution happened.
- gotQuery := tun.overrideDNS(t, tun.serverIP)
- ctl, err := (&net.Dialer{Timeout: egressTestDialTimeout}).Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(EgressBasePort)))
- if err != nil {
- t.Fatal(err)
- }
- defer ctl.Close()
- socksAuth(t, ctl)
- name := "example.internal"
- req := make([]byte, 0, 7+len(name))
- req = append(req, 0x05, 0x01, 0x00, 0x03, byte(len(name)))
- req = append(req, name...)
- req = append(req, 0x00, 0x50)
- if _, err := ctl.Write(req); err != nil {
- t.Fatal(err)
- }
- select {
- case queried := <-gotQuery:
- if len(queried) < len(name) || queried[:len(name)] != name {
- t.Fatalf("resolver queried %q, want prefix %q -- DNS did not ride the tunnel", queried, name)
- }
- case <-time.After(egressTestDialTimeout):
- t.Fatal("no DNS query reached the in-tunnel resolver")
- }
- reply := make([]byte, 10)
- ctl.SetDeadline(time.Now().Add(egressTestDialTimeout))
- if _, err := io.ReadFull(ctl, reply); err != nil {
- t.Fatalf("read reply: %v", err)
- }
- if reply[1] == 0x00 {
- t.Fatal("unexpected success: nothing should be listening on the resolved address")
- }
- }
- func TestEgressConnectDomainIPv6OnlyTunnelResolvesThroughTunnel(t *testing.T) {
- tun := newPairedTunnelV6ForTest(t)
- srv := GetEgressServer()
- srv.SetStack("awg-dom-v6-test", tun.client)
- if err := srv.Listen(); err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() { srv.DeleteStack("awg-dom-v6-test") })
- // No override: a blank dns has to fall through currentDNSServer to
- // defaultDNSFor, which the server stack answers on its own v6 /128.
- prevDNS := srv.currentDNSServer()
- srv.SetDNSServer("")
- t.Cleanup(func() { srv.SetDNSServer(prevDNS) })
- resetTunnelDNSCacheForTest()
- gotQuery := tun.startDNS(t, tun.serverIP)
- ctl, err := (&net.Dialer{Timeout: egressTestDialTimeout}).Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(EgressBasePort)))
- if err != nil {
- t.Fatal(err)
- }
- defer ctl.Close()
- socksAuthUser(t, ctl, "awg-dom-v6-test")
- name := "v6.example.internal"
- req := make([]byte, 0, 7+len(name))
- req = append(req, 0x05, 0x01, 0x00, 0x03, byte(len(name)))
- req = append(req, name...)
- req = append(req, 0x00, 0x50)
- if _, err := ctl.Write(req); err != nil {
- t.Fatal(err)
- }
- select {
- case queried := <-gotQuery:
- if len(queried) < len(name) || queried[:len(name)] != name {
- t.Fatalf("resolver queried %q, want prefix %q -- DNS did not ride the v6 tunnel", queried, name)
- }
- case <-time.After(egressTestDialTimeout):
- t.Fatal("no DNS query reached the in-tunnel v6 resolver")
- }
- reply := make([]byte, 10)
- ctl.SetDeadline(time.Now().Add(egressTestDialTimeout))
- if _, err := io.ReadFull(ctl, reply); err != nil {
- t.Fatalf("read reply: %v", err)
- }
- if reply[1] == 0x00 {
- t.Fatal("unexpected success: nothing should be listening on the resolved address")
- }
- }
- func TestEgressUDPDatagramDomainForwardedIntoTunnel(t *testing.T) {
- tun := newPairedTunnelForTest(t)
- registerEgressDeviceForTest(t, tun.client)
- gotQuery := tun.overrideDNS(t, tun.serverIP)
- in, err := gonet.DialUDP(tun.server.Stack, &tcpip.FullAddress{NIC: 1, Port: 9999}, nil, ipv4.ProtocolNumber)
- if err != nil {
- t.Fatal(err)
- }
- defer in.Close()
- ctl, err := (&net.Dialer{Timeout: egressTestDialTimeout}).Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(EgressBasePort)))
- if err != nil {
- t.Fatal(err)
- }
- defer ctl.Close()
- socksAuth(t, ctl)
- if _, err := ctl.Write([]byte{0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0}); err != nil {
- t.Fatal(err)
- }
- reply := make([]byte, 10)
- if _, err := io.ReadFull(ctl, reply); err != nil || reply[1] != 0x00 {
- t.Fatalf("associate failed: %v %v", err, reply)
- }
- bindPort := binary.BigEndian.Uint16(reply[8:10])
- udp, err := net.DialUDP("udp", nil, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: int(bindPort)})
- if err != nil {
- t.Fatal(err)
- }
- defer udp.Close()
- udp.SetDeadline(time.Now().Add(egressTestDialTimeout))
- // Plain-IP control datagram isolates domain parsing from transport.
- // Retried to avoid warmup race on slow -race runners.
- ctrl := []byte{0x00, 0x00, 0x00, 0x01, 10, 203, 0, 1, 0x27, 0x0f, 'c', 't', 'r', 'l'}
- rcv := make([]byte, 64)
- var nr int
- var rerr error
- for attempt := 0; attempt < 3; attempt++ {
- if _, err := udp.Write(ctrl); err != nil {
- t.Fatal(err)
- }
- in.SetReadDeadline(time.Now().Add(3 * time.Second))
- nr, _, rerr = in.ReadFrom(rcv)
- if rerr == nil {
- break
- }
- }
- if rerr != nil {
- t.Fatalf("CONTROL datagram never reached the tunnel target: %v", rerr)
- }
- if string(rcv[:nr]) != "ctrl" {
- t.Fatalf("control payload = %q", rcv[:nr])
- }
- name := "quic.internal"
- dgram := make([]byte, 0, 5+len(name)+2+4)
- dgram = append(dgram, 0x00, 0x00, 0x00, 0x03, byte(len(name)))
- dgram = append(dgram, name...)
- dgram = append(dgram, 0x27, 0x0f)
- dgram = append(dgram, 'p', 'i', 'n', 'g')
- var queried string
- for attempt := 0; attempt < 3 && queried == ""; attempt++ {
- if _, err := udp.Write(dgram); err != nil {
- t.Fatal(err)
- }
- select {
- case q := <-gotQuery:
- queried = q
- case <-time.After(1500 * time.Millisecond):
- }
- }
- if len(queried) < len(name) || queried[:len(name)] != name {
- t.Fatalf("resolver queried %q, want prefix %q -- DNS did not ride the tunnel", queried, name)
- }
- in.SetReadDeadline(time.Now().Add(3 * time.Second))
- nr, _, rerr = in.ReadFrom(rcv)
- if rerr != nil {
- t.Fatalf("domain datagram never reached the tunnel target: %v", rerr)
- }
- if nr < 4 || string(rcv[:4]) != "ping" {
- t.Fatalf("payload = %q (n=%d)", rcv[:nr], nr)
- }
- }
- // TestEgressUDPDatagramDomainInterleavedClients ensures datagrams pass client
- // address by value into resolver goroutines so responses route correctly.
- func TestEgressUDPDatagramDomainInterleavedClients(t *testing.T) {
- tun := newPairedTunnelForTest(t)
- registerEgressDeviceForTest(t, tun.client)
- gotQuery := tun.overrideDNS(t, tun.serverIP)
- in, err := gonet.DialUDP(tun.server.Stack, &tcpip.FullAddress{NIC: 1, Port: 9999}, nil, ipv4.ProtocolNumber)
- if err != nil {
- t.Fatal(err)
- }
- defer in.Close()
- go func() {
- buf := make([]byte, 512)
- for {
- n, from, rerr := in.ReadFrom(buf)
- if rerr != nil {
- return
- }
- _, _ = in.WriteTo(append([]byte("echo:"), buf[:n]...), from)
- }
- }()
- dialUDP := func() *net.UDPConn {
- ctl, err := (&net.Dialer{Timeout: egressTestDialTimeout}).Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(EgressBasePort)))
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() { ctl.Close() })
- socksAuth(t, ctl)
- if _, err := ctl.Write([]byte{0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0}); err != nil {
- t.Fatal(err)
- }
- reply := make([]byte, 10)
- if _, err := io.ReadFull(ctl, reply); err != nil || reply[1] != 0x00 {
- t.Fatalf("associate failed: %v %v", err, reply)
- }
- bindPort := binary.BigEndian.Uint16(reply[8:10])
- udp, err := net.DialUDP("udp", nil, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: int(bindPort)})
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() { udp.Close() })
- udp.SetDeadline(time.Now().Add(egressTestDialTimeout))
- return udp
- }
- name := func(i int) string { return fmt.Sprintf("interleaved-%d.internal", i) }
- dgram := func(i int, payload string) []byte {
- n := name(i)
- d := make([]byte, 0, 5+len(n)+2+len(payload))
- d = append(d, 0x00, 0x00, 0x00, 0x03, byte(len(n)))
- d = append(d, n...)
- d = append(d, 0x27, 0x0f)
- return append(d, payload...)
- }
- for seq := 0; seq < 4; seq++ {
- udp := dialUDP()
- payload := fmt.Sprintf("p-%d", seq)
- if _, err := udp.Write(dgram(seq, payload)); err != nil {
- t.Fatal(err)
- }
- select {
- case q := <-gotQuery:
- if !strings.HasPrefix(q, "interleaved-") {
- t.Fatalf("resolver queried %q, want an interleaved-* name", q)
- }
- case <-time.After(4 * time.Second):
- t.Fatalf("query %d not observed", seq)
- }
- rcv := make([]byte, 512)
- nr, _, rerr := udp.ReadFrom(rcv)
- if rerr != nil {
- t.Fatalf("reply %d never reached client: %v", seq, rerr)
- }
- if nr < 10 || !strings.Contains(string(rcv[:nr]), "echo:"+payload) {
- t.Fatalf("reply payload = %q, want echo:%s", rcv[:nr], payload)
- }
- }
- }
- func TestDefaultDNSFor(t *testing.T) {
- v4 := netip.MustParseAddr("10.8.0.2")
- v6 := netip.MustParseAddr("2001:db8::2")
- if got := defaultDNSFor([]netip.Addr{v4}); got != DefaultTunnelDNSServer {
- t.Errorf("defaultDNSFor(v4) = %q, want %q", got, DefaultTunnelDNSServer)
- }
- if got := defaultDNSFor([]netip.Addr{v4, v6}); got != DefaultTunnelDNSServer {
- t.Errorf("defaultDNSFor(dual) = %q, want %q", got, DefaultTunnelDNSServer)
- }
- if got := defaultDNSFor([]netip.Addr{v6}); got != DefaultTunnelDNSServerV6 {
- t.Errorf("defaultDNSFor(v6-only) = %q, want %q", got, DefaultTunnelDNSServerV6)
- }
- if got := defaultDNSFor(nil); got != DefaultTunnelDNSServer {
- t.Errorf("defaultDNSFor(nil) = %q, want %q", got, DefaultTunnelDNSServer)
- }
- }
- func TestParseDatagramDomainHeader(t *testing.T) {
- hdr := []byte{0, 0, 0, 0x03, 4, 'a', 'b', '.', 'd', 0x00, 0x35, 'x'}
- name, port, hdrLen, err := parseDatagramDomainHeader(hdr)
- if err != nil {
- t.Fatal(err)
- }
- if name != "ab.d" || port != 53 || hdrLen != 11 {
- t.Fatalf("name=%q port=%d hdrLen=%d", name, port, hdrLen)
- }
- truncated := []byte{0, 0, 0, 0x03, 200, 'a'}
- if _, _, _, err := parseDatagramDomainHeader(truncated); err == nil {
- t.Fatal("truncated domain accepted")
- }
- empty := []byte{0, 0, 0, 0x03, 0, 0x00, 0x35}
- if _, _, _, err := parseDatagramDomainHeader(empty); err == nil {
- t.Fatal("empty domain accepted")
- }
- }
- func TestReadSocksRequestTargetKeepsHostnameUnresolved(t *testing.T) {
- payload := append([]byte{byte(len("invalid."))}, []byte("invalid.")...)
- payload = append(payload, 0x01, 0xbb)
- tr, err := readSocksRequestTarget(bytes.NewReader(payload), 0x03)
- if err != nil {
- t.Fatalf("domain request rejected: %v", err)
- }
- if tr.host != "invalid." || tr.port != 443 || tr.ip.IsValid() {
- t.Fatalf("target = %+v", tr)
- }
- }
- func TestTunnelDNSCache_ScopedPerTagAndServer(t *testing.T) {
- resetTunnelDNSCacheForTest()
- tagA, tagB := "out-a", "out-b"
- dns1, dns2 := "1.1.1.1:53", "8.8.8.8:53"
- host := "example.com"
- addrA := netip.MustParseAddr("10.0.0.1")
- addrB := netip.MustParseAddr("10.0.0.2")
- keyA := dnsCacheKey(tagA, dns1, host)
- keyB := dnsCacheKey(tagB, dns1, host)
- keyA2 := dnsCacheKey(tagA, dns2, host)
- tunnelDNSCache.mu.Lock()
- tunnelDNSCache.m[keyA] = tunnelDNSCacheEntry{addr: addrA, exp: time.Now().Add(time.Hour)}
- tunnelDNSCache.m[keyB] = tunnelDNSCacheEntry{addr: addrB, exp: time.Now().Add(time.Hour)}
- tunnelDNSCache.mu.Unlock()
- tunnelDNSCache.mu.Lock()
- eA, okA := tunnelDNSCache.m[keyA]
- eB, okB := tunnelDNSCache.m[keyB]
- _, okA2 := tunnelDNSCache.m[keyA2]
- tunnelDNSCache.mu.Unlock()
- if !okA || eA.addr != addrA {
- t.Fatalf("tagA cache entry mismatch: %v, %v", okA, eA)
- }
- if !okB || eB.addr != addrB {
- t.Fatalf("tagB cache entry mismatch: %v, %v", okB, eB)
- }
- if okA2 {
- t.Fatal("key with different DNS server should not match")
- }
- flushTunnelDNSCacheForTag(tagA)
- tunnelDNSCache.mu.Lock()
- _, okAAfter := tunnelDNSCache.m[keyA]
- _, okBAfter := tunnelDNSCache.m[keyB]
- tunnelDNSCache.mu.Unlock()
- if okAAfter {
- t.Fatal("tagA entry should be flushed")
- }
- if !okBAfter {
- t.Fatal("tagB entry should survive flush of tagA")
- }
- }
|