mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 23:27:14 +00:00
5815254fc3
udpRelay.flowFor returned "max relay flows reached" once the table held maxRelayFlows entries, and only the idle sweep (every minute, two-minute cutoff) freed slots. One host sending a single datagram from each of 4096 source ports therefore locked every new TUIC client out of the inbound for up to two minutes, repeatably. A full table now evicts the flow last seen longest ago, which under such a flood is one of the junk flows, and the newcomer is admitted. TestUDPRelayFullTableAdmitsNewClient fails on the refusing code with a read timeout for the third client.
151 lines
4.0 KiB
Go
151 lines
4.0 KiB
Go
package tuic
|
|
|
|
import (
|
|
"bytes"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// doublingEcho answers every datagram with the payload repeated twice, so a
|
|
// relay that mislabels directions or clients cannot pass by accident.
|
|
func doublingEcho(t *testing.T) *net.UDPAddr {
|
|
t.Helper()
|
|
echo, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = echo.Close() })
|
|
go func() {
|
|
buf := make([]byte, 65535)
|
|
for {
|
|
n, from, err := echo.ReadFromUDP(buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, _ = echo.WriteToUDP(append(append([]byte{}, buf[:n]...), buf[:n]...), from)
|
|
}
|
|
}()
|
|
return echo.LocalAddr().(*net.UDPAddr)
|
|
}
|
|
|
|
func roundTrip(t *testing.T, relay *udpRelay, payload []byte) int {
|
|
t.Helper()
|
|
c, err := net.DialUDP("udp", nil, relay.LocalAddr().(*net.UDPAddr))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
if _, err := c.Write(payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = c.SetReadDeadline(time.Now().Add(3 * time.Second))
|
|
buf := make([]byte, 65535)
|
|
n, err := c.Read(buf)
|
|
if err != nil {
|
|
t.Fatalf("no reply through the relay: %v", err)
|
|
}
|
|
return n
|
|
}
|
|
|
|
func collectUntil(t *testing.T, relay *udpRelay, wantUp, wantDown int64) (int64, int64) {
|
|
t.Helper()
|
|
var up, down int64
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for {
|
|
u, d := relay.CollectTraffic()
|
|
up, down = up+u, down+d
|
|
if (up >= wantUp && down >= wantDown) || time.Now().After(deadline) {
|
|
return up, down
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func TestUDPRelayMetersBothDirectionsPerClient(t *testing.T) {
|
|
relay, err := startUDPRelay("127.0.0.1:0", doublingEcho(t), relayFlowIdle)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(relay.Close)
|
|
|
|
if got := roundTrip(t, relay, bytes.Repeat([]byte("a"), 100)); got != 200 {
|
|
t.Fatalf("client A reply = %d bytes, want 200", got)
|
|
}
|
|
if got := roundTrip(t, relay, bytes.Repeat([]byte("b"), 50)); got != 100 {
|
|
t.Fatalf("client B reply = %d bytes, want 100", got)
|
|
}
|
|
if up, down := collectUntil(t, relay, 150, 300); up != 150 || down != 300 {
|
|
t.Fatalf("delta = (%d up, %d down), want (150, 300)", up, down)
|
|
}
|
|
if up, down := relay.CollectTraffic(); up != 0 || down != 0 {
|
|
t.Fatalf("second collect = (%d, %d), want (0, 0): deltas must reset", up, down)
|
|
}
|
|
}
|
|
|
|
func TestUDPRelayExpiresIdleFlows(t *testing.T) {
|
|
relay, err := startUDPRelay("127.0.0.1:0", doublingEcho(t), 50*time.Millisecond)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(relay.Close)
|
|
|
|
roundTrip(t, relay, []byte("hello"))
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for {
|
|
relay.mu.Lock()
|
|
n := len(relay.flows)
|
|
relay.mu.Unlock()
|
|
if n == 0 {
|
|
break
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("%d flow(s) still open after the idle window", n)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if got := roundTrip(t, relay, []byte("again")); got != 10 {
|
|
t.Fatalf("reply after expiry = %d bytes, want 10", got)
|
|
}
|
|
}
|
|
|
|
func TestUDPRelayRefusesFlowsAfterClose(t *testing.T) {
|
|
relay, err := startUDPRelay("127.0.0.1:0", doublingEcho(t), relayFlowIdle)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
relay.Close()
|
|
if _, err := relay.flowFor(&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 9}); err == nil {
|
|
t.Fatal("flowFor after Close must refuse: its pump would outlive the relay and hang Close's WaitGroup")
|
|
}
|
|
relay.mu.Lock()
|
|
n := len(relay.flows)
|
|
relay.mu.Unlock()
|
|
if n != 0 {
|
|
t.Fatalf("%d flow(s) registered after Close", n)
|
|
}
|
|
}
|
|
|
|
func TestUDPRelayFullTableAdmitsNewClient(t *testing.T) {
|
|
relay, err := startUDPRelay("127.0.0.1:0", doublingEcho(t), relayFlowIdle)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(relay.Close)
|
|
relay.mu.Lock()
|
|
relay.maxFlows = 2
|
|
relay.mu.Unlock()
|
|
|
|
roundTrip(t, relay, []byte("a"))
|
|
roundTrip(t, relay, []byte("b"))
|
|
if got := roundTrip(t, relay, []byte("c")); got != 2 {
|
|
t.Fatalf("third client reply = %d bytes, want 2: a full table must evict, not refuse", got)
|
|
}
|
|
relay.mu.Lock()
|
|
n := len(relay.flows)
|
|
relay.mu.Unlock()
|
|
if n != 2 {
|
|
t.Fatalf("flow table holds %d flows after admitting a third client, want the cap of 2", n)
|
|
}
|
|
}
|