fix(tuic): evict the oldest relay flow instead of refusing new clients

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.
This commit is contained in:
Sanaei
2026-09-11 23:51:55 +02:00
parent 6d96accd63
commit 5815254fc3
2 changed files with 45 additions and 7 deletions
+23
View File
@@ -125,3 +125,26 @@ func TestUDPRelayRefusesFlowsAfterClose(t *testing.T) {
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)
}
}