diff --git a/internal/proxy/udprelay/listener.go b/internal/proxy/udprelay/listener.go index f794b30..ea8c1ee 100644 --- a/internal/proxy/udprelay/listener.go +++ b/internal/proxy/udprelay/listener.go @@ -22,7 +22,7 @@ type Packet struct { // packetPool переиспользует буферы датаграмм. var packetPool = sync.Pool{ - New: func() any { return &Packet{Data: make([]byte, 2048)} }, + New: func() any { return &Packet{Data: make([]byte, maxDatagramLen)} }, } func runListener(ctx context.Context, listenConn net.PacketConn, activeLocalPeer *atomic.Value, inboundChan chan<- *Packet) error { diff --git a/internal/proxy/udprelay/loop.go b/internal/proxy/udprelay/loop.go index 4c96483..20bf597 100644 --- a/internal/proxy/udprelay/loop.go +++ b/internal/proxy/udprelay/loop.go @@ -24,6 +24,10 @@ var errPairRecycled = errors.New("udprelay: stream pair recycled") const ( pipeBufLimit = 256 << 10 + // maxDatagramLen - бюджет локальной датаграммы (WG/AWG) по всей цепочке, как у сервера. + maxDatagramLen = 2048 + // maxRecordLen - та же датаграмма в DTLS-записи: заголовок с CID, явный nonce, тег GCM. + maxRecordLen = maxDatagramLen + 64 ) // streamPair связывает DTLS-сессию с аллокацией, поверх которой она поднята. Смерть @@ -188,7 +192,7 @@ func dtlsSession(dtlsctx context.Context, dtlscancel context.CancelFunc, deps *D forwardDone := make(chan struct{}) go func() { defer close(forwardDone) - var buf [2048]byte + var buf [maxDatagramLen]byte for { n, err := dtlsConn.Read(buf[:]) if err != nil { @@ -284,8 +288,6 @@ func oneTURN(ctx context.Context, deps *Deps, params *Params, peer *net.UDPAddr, return } - const maxPayload = 1600 - wg.Go(func() { select { case <-turnctx.Done(): @@ -304,10 +306,10 @@ func oneTURN(ctx context.Context, deps *Deps, params *Params, peer *net.UDPAddr, defer turncancel() var buf, readSlot []byte if obfConn != nil { - buf = make([]byte, obfConn.MaxWire(maxPayload)) - readSlot = buf[obfConn.HeaderLen() : obfConn.HeaderLen()+maxPayload] + buf = make([]byte, obfConn.MaxWire(maxRecordLen)) + readSlot = buf[obfConn.HeaderLen() : obfConn.HeaderLen()+maxRecordLen] } else { - buf = make([]byte, maxPayload) + buf = make([]byte, maxRecordLen) readSlot = buf } addrStored := false @@ -350,9 +352,9 @@ func oneTURN(ctx context.Context, deps *Deps, params *Params, peer *net.UDPAddr, wg.Go(func() { defer turncancel() - readBufLen := maxPayload + readBufLen := maxRecordLen if obfConn != nil { - readBufLen = obfConn.MaxWire(maxPayload) + readBufLen = obfConn.MaxWire(maxRecordLen) } buf := make([]byte, readBufLen) for { diff --git a/internal/proxy/udpserver/udpserver.go b/internal/proxy/udpserver/udpserver.go index 5392118..209e781 100644 --- a/internal/proxy/udpserver/udpserver.go +++ b/internal/proxy/udpserver/udpserver.go @@ -11,7 +11,7 @@ import ( ) const ( - udpRelayBufSize = 1600 + udpRelayBufSize = 2048 udpIdleTimeout = 30 * time.Minute udpDeadlineRefresh = time.Minute ) diff --git a/internal/proxy/udpserver/udpserver_test.go b/internal/proxy/udpserver/udpserver_test.go new file mode 100644 index 0000000..62b9da3 --- /dev/null +++ b/internal/proxy/udpserver/udpserver_test.go @@ -0,0 +1,90 @@ +package udpserver + +import ( + "bytes" + "context" + "net" + "testing" + "time" + + "github.com/pion/dtls/v3" + + "github.com/samosvalishe/free-turn-proxy/internal/logx" + "github.com/samosvalishe/free-turn-proxy/internal/transport/dtlsdial" +) + +func echoBackend(t *testing.T) 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, 64<<10) + for { + n, addr, err := pc.ReadFrom(buf) + if err != nil { + return + } + _, _ = pc.WriteTo(buf[:n], addr) + } + }() + return pc.LocalAddr().String() +} + +func dtlsPair(t *testing.T, ctx context.Context, backend string) net.Conn { + t.Helper() + cert, err := dtlsdial.GenerateSelfSignedCert() + if err != nil { + t.Fatal(err) + } + ln, err := dtls.ListenWithOptions("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}, dtls.WithCertificates(cert)) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = ln.Close() }) + go func() { + conn, aerr := ln.Accept() + if aerr != nil { + return + } + Handle(ctx, logx.Nop(), conn, backend) + }() + + pc, err := (&net.ListenConfig{}).ListenPacket(ctx, "udp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + serverAddr, ok := ln.Addr().(*net.UDPAddr) + if !ok { + t.Fatalf("listener addr %T", ln.Addr()) + } + dialer := &dtlsdial.Dialer{HandshakeTimeout: 5 * time.Second} + conn, err := dialer.Dial(ctx, pc, serverAddr) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = conn.Close() }) + return conn +} + +// Датаграмма в пределах клиентского буфера 2048 проходит сервер в обе стороны целиком. +func TestHandleRelaysLargeDatagram(t *testing.T) { + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + defer cancel() + conn := dtlsPair(t, ctx, echoBackend(t)) + + for _, size := range []int{1200, 1800, 2048} { + payload := bytes.Repeat([]byte{0x5a}, size) + if _, err := conn.Write(payload); err != nil { + t.Fatalf("size %d: write: %v", size, err) + } + _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + buf := make([]byte, 4096) + n, err := conn.Read(buf) + if err != nil || !bytes.Equal(buf[:n], payload) { + t.Fatalf("size %d: echo n=%d err=%v", size, n, err) + } + } +}