zapret-kvn/runtime/amnezia/relay_test.go
loop-uh 38e951c129
Some checks failed
Windows project source guards / test (push) Failing after 1m11s
fix: preserve AWG relay TCP half-close and verify sustained traffic
2026-09-06 17:25:30 +03:00

254 lines
7.1 KiB
Go

package main
import (
"bytes"
"context"
"io"
"net"
"net/netip"
"testing"
"time"
)
func startTestRelay(t *testing.T) (config, string) {
t.Helper()
c := testConfig()
s := relay{username: c.Username, password: c.Password, journal: newJournal(c),
dialTCP: func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, "tcp", dst.String())
},
dialUDP: func(dst netip.AddrPort) (net.Conn, error) { return net.Dial("udp", dst.String()) },
}
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
done := make(chan error, 1)
go func() { done <- s.serve(ctx, l) }()
t.Cleanup(func() {
cancel()
l.Close()
select {
case <-done:
case <-time.After(3 * time.Second):
t.Error("relay teardown blocked")
}
})
return c, l.Addr().String()
}
func login(t *testing.T, address string, c config) net.Conn {
t.Helper()
client, err := net.DialTimeout("tcp", address, time.Second)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { client.Close() })
client.SetDeadline(time.Now().Add(3 * time.Second))
client.Write([]byte{5, 1, 2})
var result [2]byte
if _, err := io.ReadFull(client, result[:]); err != nil || result != [2]byte{5, 2} {
t.Fatal("method", result, err)
}
credentials := append([]byte{1, byte(len(c.Username))}, []byte(c.Username)...)
credentials = append(credentials, byte(len(c.Password)))
credentials = append(credentials, []byte(c.Password)...)
client.Write(credentials)
if _, err := io.ReadFull(client, result[:]); err != nil || result != [2]byte{1, 0} {
t.Fatal("authentication", result, err)
}
return client
}
func request(t *testing.T, client net.Conn, command byte, dst netip.AddrPort) netip.AddrPort {
t.Helper()
client.Write(append([]byte{5, command, 0}, addressBytes(dst)...))
var response [3]byte
if _, err := io.ReadFull(client, response[:]); err != nil || response != [3]byte{5, 0, 0} {
t.Fatal("request", response, err)
}
addr, err := readAddress(client)
if err != nil {
t.Fatal(err)
}
return addr
}
func TestRelayTCPAndAuthentication(t *testing.T) {
c, address := startTestRelay(t)
echo, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer echo.Close()
go func() {
conn, err := echo.Accept()
if err == nil {
defer conn.Close()
io.Copy(conn, conn)
}
}()
client := login(t, address, c)
request(t, client, 1, echo.Addr().(*net.TCPAddr).AddrPort())
client.Write([]byte("transport-round-trip"))
data := make([]byte, len("transport-round-trip"))
if _, err := io.ReadFull(client, data); err != nil || string(data) != "transport-round-trip" {
t.Fatal(string(data), err)
}
unauth, err := net.Dial("tcp", address)
if err != nil {
t.Fatal(err)
}
defer unauth.Close()
unauth.SetDeadline(time.Now().Add(time.Second))
unauth.Write([]byte{5, 1, 0})
var response [2]byte
if _, err := io.ReadFull(unauth, response[:]); err != nil || response[1] != 255 {
t.Fatal("unauthenticated relay accepted", err)
}
}
func TestRelayTCPHalfCloseKeepsDownload(t *testing.T) {
c, address := startTestRelay(t)
server, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer server.Close()
assertHalfCloseResponse(t, address, c, server, server.Addr().(*net.TCPAddr).AddrPort())
}
func assertHalfCloseResponse(t *testing.T, address string, c config, server net.Listener, dst netip.AddrPort) {
t.Helper()
done := make(chan error, 1)
go func() {
remote, err := server.Accept()
if err != nil {
done <- err
return
}
defer remote.Close()
remote.SetDeadline(time.Now().Add(3 * time.Second))
body, err := io.ReadAll(remote)
if err == nil {
_, err = remote.Write(append([]byte("response:"), body...))
}
done <- err
}()
client := login(t, address, c)
request(t, client, 1, dst)
if _, err := client.Write([]byte("request")); err != nil {
t.Fatal(err)
}
if err := client.(*net.TCPConn).CloseWrite(); err != nil {
t.Fatal(err)
}
response, err := io.ReadAll(client)
if err != nil || string(response) != "response:request" {
t.Fatalf("upload EOF discarded the response: %q (%v)", response, err)
}
if err := <-done; err != nil {
t.Fatal(err)
}
}
func TestRelayTCPCancellationUnblocksBothDirections(t *testing.T) {
client, application := net.Pipe()
remote, server := net.Pipe()
defer application.Close()
defer server.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
done := make(chan error, 1)
go func() { done <- relayTCP(ctx, client, remote) }()
cancel()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("cancelled relay retained a blocked stream")
}
}
// More completed sessions than the relay's concurrency limit must not exhaust
// admission slots. A leak here would look like an alive core with no traffic.
func TestRelayCompletedConnectionsReleaseAdmission(t *testing.T) {
c, address := startTestRelay(t)
server, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer server.Close()
go func() {
for {
remote, err := server.Accept()
if err != nil {
return
}
go func() { defer remote.Close(); io.Copy(remote, remote) }()
}
}()
for i := 0; i < 384; i++ {
client := login(t, address, c)
request(t, client, 1, server.Addr().(*net.TCPAddr).AddrPort())
if _, err := client.Write([]byte("round-trip")); err != nil {
t.Fatal(i, err)
}
b := make([]byte, len("round-trip"))
if _, err := io.ReadFull(client, b); err != nil || string(b) != "round-trip" {
t.Fatal(i, "reply", err)
}
client.Close()
}
}
func TestRelayUDPAssociationLifetimeAndSource(t *testing.T) {
c, address := startTestRelay(t)
echo, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
if err != nil {
t.Fatal(err)
}
defer echo.Close()
go func() {
buf := make([]byte, 65535)
for {
n, addr, err := echo.ReadFromUDP(buf)
if err != nil {
return
}
echo.WriteToUDP(buf[:n], addr)
}
}()
udp, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
if err != nil {
t.Fatal(err)
}
defer udp.Close()
client := login(t, address, c)
relayAddr := request(t, client, 3, udp.LocalAddr().(*net.UDPAddr).AddrPort())
packet := append([]byte{0, 0, 0}, addressBytes(echo.LocalAddr().(*net.UDPAddr).AddrPort())...)
packet = append(packet, bytes.Repeat([]byte("a"), 20000)...)
udp.SetDeadline(time.Now().Add(time.Second))
udp.WriteToUDPAddrPort(packet, relayAddr)
result := make([]byte, 65535)
n, _, err := udp.ReadFromUDPAddrPort(result)
if err != nil || !bytes.Equal(packet, result[:n]) {
t.Fatal("UDP payload mismatch", n, err)
}
other, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
if err != nil {
t.Fatal(err)
}
defer other.Close()
other.SetDeadline(time.Now().Add(100 * time.Millisecond))
other.WriteToUDPAddrPort(packet, relayAddr)
if _, _, err := other.ReadFromUDPAddrPort(result); err == nil {
t.Fatal("foreign UDP source accepted")
}
client.Close()
udp.SetDeadline(time.Now().Add(150 * time.Millisecond))
udp.WriteToUDPAddrPort(packet, relayAddr)
if _, _, err := udp.ReadFromUDPAddrPort(result); err == nil {
t.Fatal("association survived control close")
}
}