fix(websocket): tear down peer connection and bound backend messages

Two defects in the bidirectional proxy:

1. Goroutine/socket leak on client disconnect. proxyClientToBackend and
   proxyBackendToClient each block on their own Read; when the client drops,
   the client-direction goroutine returns but the backend-direction goroutine
   stays blocked on backend.ReadMessage, so wg.Wait() never returns and the
   goroutine plus backend socket leak until the backend independently closes
   or sends. Each direction now closes the opposite connection on exit,
   unblocking the peer.

2. Backend->client messages were unbounded. client.SetReadLimit only bound
   what the client could send; a large or misbehaving backend could push
   arbitrarily large frames through. backend.SetReadLimit now bounds it.

Tests: TestWebSocketProxy_TeardownOnClientDisconnect (backend must be closed
after client disconnect), TestWebSocketProxy_BackendReadLimit (connection
torn down when backend exceeds the limit).
This commit is contained in:
2026-08-13 03:10:04 +01:00
parent db2216973e
commit 0e8a8fe33f
2 changed files with 123 additions and 0 deletions
+12
View File
@@ -207,6 +207,11 @@ func (wsp *WebSocketProxy) handleConnection(ctx context.Context, clientConn *web
})
}
// Bound messages read from the backend as well, so a large or
// misbehaving backend cannot force us to forward an unbounded frame to the
// client (the client-side limit only bounds what the client can send us).
backendConn.SetReadLimit(wsp.maxMessageSize)
// Set up bidirectional proxying
var wg sync.WaitGroup
wg.Add(2)
@@ -214,12 +219,19 @@ func (wsp *WebSocketProxy) handleConnection(ctx context.Context, clientConn *web
// Client -> Backend
go func() {
defer wg.Done()
// When the client side finishes (e.g. the client disconnected), close
// the backend so the reverse goroutine's blocked read returns instead
// of leaking the goroutine and holding the backend connection open.
defer func() { _ = backendConn.Close() }()
wsp.proxyClientToBackend(ctx, clientConn, backendConn, connectionID)
}()
// Backend -> Client
go func() {
defer wg.Done()
// Symmetric teardown: when the backend side finishes, close the
// client so the client-direction goroutine isn't left blocked either.
defer func() { _ = clientConn.Close() }()
wsp.proxyBackendToClient(ctx, backendConn, clientConn, connectionID)
}()