KCPListener hands accepted sessions to a consumer through an unbuffered
channel, but its accept goroutine wrote to that channel without watching for
shutdown. A Close racing with a handoff left the goroutine blocked forever,
holding the session it was about to deliver, because closing the underlying
listener does not unblock a send. The close flag it read was also written by
Close without synchronisation.
Watch a done channel in the handoff and close the connection that was never
delivered, so the goroutine can exit and the session is released. Accept now
returns ErrKCPListenerClosed as soon as the listener is closed instead of
blocking on the channel, and the close state is a done channel guarded by
sync.Once.
A ready handoff can still win the race with Close; the session then belongs to
the caller of Accept, matching how WebsocketListener behaves.
Tests cover a pending handoff that is cancelled by Close and a blocked Accept
that is woken by it.
the websocket listener kept its accept channel open on Close, so an Accept call that was already waiting blocked forever
every other listener in pkg/util/net closes the channel and returns an error, but this one never did, so the HandleListener goroutine stayed stuck after the server closed
i close the channel on Close, guard the hand off send so a late connection does not panic, and return ErrWebsocketListenerClosed which was declared but never reached
Signed-off-by: Abdel <62770500+hktitof@users.noreply.github.com>
The ws/wss transport carries a raw byte stream (yamux), but the
golang.org/x/net/websocket Conn defaults to text frames (PayloadType
TextFrame). Per RFC 6455 §5.6 a text frame must contain valid UTF-8, so
RFC-compliant intermediaries (API gateways / reverse proxies) validate
the payload and close the connection when the binary tunnel data is not
valid UTF-8.
This goes unnoticed peer-to-peer because x/net/websocket does not
validate UTF-8 on read, but it breaks the connection through a compliant
validating proxy. Set PayloadType to BinaryFrame on both the server
listener and the client dialer so the tunnel is framed as binary.
- pkg/nathole: add RLock when reading clientCfgs map in PreCheck path
to prevent concurrent map read/write crash
- server/proxy: fix error variable shadowing in GetWorkConnFromPool
that could return a closed connection with nil error
- pkg/util/net: check ListenUDP error before spawning goroutines
and assign readConn to struct field so Close() works correctly
- pkg/util/net/websocket.go: store ln parameter in struct to prevent
nil pointer panic when Addr() is called
- pkg/auth/oidc.go: replace unsynchronized []string with map + RWMutex
for subjectsFromLogin to fix data race across concurrent connections
The CloseNotifyConn.Close() function calls itself if the closeFlag is equal to 0 which would mean it would immediately swap the closeFlag value to 1 and never call closeFn. It is unclear what the intent of this call to cc.Close() was but I assume it was meant to be a call to close the Conn object instead.
2025-10-17 10:53:43 +08:00
fatedierGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Update go.mod to use github.com/quic-go/quic-go v0.53.0
- Replace quic.Connection interface with *quic.Conn struct
- Replace quic.Stream interface with *quic.Stream struct
- Update all affected files to use new API:
- pkg/util/net/conn.go: Update QuicStreamToNetConn function and wrapQuicStream struct
- server/service.go: Update HandleQUICListener function parameter
- client/visitor/xtcp.go: Update QUICTunnelSession struct field
- client/connector.go: Update defaultConnectorImpl struct field
Fixes#4852
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>