fix(listener): dedup AcceptKCP for handshake retransmit from same client (#340)#341
Draft
luongs3 wants to merge 1 commit into
Draft
fix(listener): dedup AcceptKCP for handshake retransmit from same client (#340)#341luongs3 wants to merge 1 commit into
luongs3 wants to merge 1 commit into
Conversation
…ent (xtaci#340) When two packets from the same remote address race the Listener's session-tracking map, both can miss on the RLock lookup, each spawn a new UDPSession, push it to chAccepts, and the second insert overwrites the first in l.sessions. AcceptKCP then returns two UDPSession objects for the same RemoteAddr; closing one causes the listener's map to lose the binding, which 'kills' the other from the application's perspective. The fix re-checks the map under the write lock before inserting. If a concurrent goroutine already created the session for this remote, we feed the data into it (when the conv matches) and skip the duplicate. A regression test in dedup_test.go drives Listener.packetInput concurrently with N=32 copies of the same handshake packet over a fake PacketConn and asserts exactly one session is handed out. Fixes xtaci#340 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft for direction — bug is fresh (filed today) and not maintainer-validated yet, so I'd love your read before going further.
Problem
Listener.packetInput(insess.go) looks upl.sessions[addr.String()]under anRLock, and if the entry is missing it falls through to create a newUDPSessionand writes it into the map under a separateLock. There is a TOCTOU window between the two locks: two packets from the same remote address (e.g. a handshake retransmit, a duplicate fromrecvmmsg, or any case where the application invokespacketInputfrom more than one goroutine) can both miss on the read lock, each construct aUDPSession, and eachl.chAccepts <- s. The second write intol.sessions[addr]silently overwrites the first.Net effect, matching the symptom in #340:
AcceptKCP()returns two*UDPSessionfor the sameRemoteAddr.l.sessions[addr]is whichever one wrote last, socloseSession(remote)removes the map binding regardless of whichUDPSessionthe caller closes — and from the caller's perspective that "closes" the other session too (no more packets get routed to it).Repro
dedup_test.go(added in this PR) drivesListener.packetInputdirectly with a fakenet.PacketConnand fires 32 copies of the same PUSH packet from the same remote address, then drainschAccepts. OnmasterI see 2–3 sessions handed out; the assert wants exactly 1.Candidate fix
Take
sessionLock.Lock()before allocating the new session and re-check the map under the write lock. If a concurrent goroutine already inserted a session for this remote, feed the incoming packet into it (whenconvmatches) and skip the duplicate.Diff is ~20 lines in
sess.go. Insertion order is also tightened so the session is inl.sessionsbeforechAccepts <- s— that way any later packet for the same remote that races the accept handler can still find it.Open questions
addr.String()(same key already used by the map). Should the dedup also considerconv, or is the existing stale-conv reset path above (s.Close()whenconv != s.kcp.conv && sn == 0) intended to be the canonical reconnect mechanism?sn != 0. That matches the existing behavior in the "session already exists" branch above, but I want to make sure I'm not papering over a different bug.sync.Mapor a per-addr lock map but didn't want to widen the patch surface. Happy to follow whichever direction you prefer.Reproducer is small and standalone; happy to iterate on style, structure, or rewrite the whole fix in a different direction if I read this wrong.