Handle WebSocket errors before the handshake completes

WsServer attached no error listener to a new socket. WsServerConnector adds
one, but only after the handshake. ws emits an error for an invalid frame,
for example a close frame with the reserved status code 1006, and an error
event with no listener throws. That stopped the controller.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7obMEi4L4PzUQ8w8q9Uyz
This commit is contained in:
ILLISISandClaude Opus 5.5 committed 2026-10-11 12:53:13 +11:00
1 parent ae6e25d1b9
commit 16666c3ee0
2 files changed
+26

No files matched your search

+7
View File
@@ -116,6 +116,13 @@ export default class WsServer {
handleConnection(socket: lib.WebSocketClusterio, req: IncomingMessage) {
logger.verbose(`WsServer | new connection from ${this.remoteAddr(req)}`);
// ws emits errors like invalid frames on the socket, and an error with
// no listener crashes the controller. The connector adds its own
// listener, but only once the handshake has completed.
socket.on("error", (err: Error) => {
logger.verbose(`WsServer | error on connection from ${this.remoteAddr(req)}: ${err.message}`);
});
// Track statistics
wsConnectionsCounter.inc();
socket.on("message", (message: WebSocket.RawData) => {
+19
View File
@@ -1,4 +1,5 @@
import assert from "node:assert/strict";
import events from "node:events";
import { Controller, WsServer } from "@clusterio/controller";
import { ControllerConfig } from "@clusterio/lib";
@@ -68,5 +69,23 @@ describe("controller/src/WsServer", function() {
assert.equal(ipv6, "2000::1");
});
});
describe(".handleConnection()", function() {
it("should handle socket errors before the handshake completes", function() {
// A ws socket emits "error" for e.g. a close frame with the reserved code 1006
const socket = new events.EventEmitter();
socket.send = () => {};
socket.close = () => {};
socket.terminate = () => {};
const req = { socket: { remoteAddress: "1.2.3.4", remoteFamily: "IPv4" }, headers: {} };
wsServer.handleConnection(socket, req);
try {
assert.doesNotThrow(() => {
socket.emit("error", new RangeError("Invalid WebSocket frame: invalid status code 1006"));
});
} finally {
socket.emit("message", "not json"); // ends the handshake and its timeout
}
});
});
});
});