-
-
Notifications
You must be signed in to change notification settings - Fork 664
Description
Version
v20.11.0
Platform
Linux ndndev 6.1.0-17-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.69-1 (2023-12-30) x86_64 x86_64 x86_64 GNU/Linux
Subsystem
WebSocket
What steps will reproduce the bug?
main.mjs
import { WebSocketServer } from "ws";
import { once } from "node:events";
const server = new WebSocketServer({
host: "127.0.0.1",
port: 6680,
});
await once(server, "listening");
console.log("Server is listening")
server.on("connection", (sock, request) => {
console.log("Server has received client connection, port", request.socket.remotePort);
setTimeout(() => {
console.log("Server is closing client connection");
sock.close();
}, 3000);
});
const client = new WebSocket(`ws://127.0.0.1:6680`);
console.log("Client is created");
await once(client, "open");
console.log("Client is open");
await once(client, "close");
console.log("Client is closed");
server.close();
console.log("Server is closing");
await once(server, "close");
console.log("Server is closed");
package.json
{
"private": true,
"packageManager": "pnpm@8.15.1+sha256.245fe901f8e7fa8782d7f17d32b6a83995e2ae03984cb5b62b8949bfdc27c7b5",
"dependencies": {
"ws": "^8.16.0"
}
}
How often does it reproduce? Is there a required condition?
This bug always happens when the above snippet is invoked with --experimental-websocket
flag.
node --experimental-websocket main.mjs
What is the expected behavior? Why is that the expected behavior?
The expected behavior may be obtained by importing WebSocket
from ws package.
The console log is:
Server is listening
Client is created
Server has received client connection, port 49956
Client is open
Server is closing client connection
Client is closed
Server is closing
Server is closed
ws-websocket.pcapng.zip is the packet capture of this execution.
When the WebSocketServer closes the client connection in Frame 8, the WebSocket client responds with a Close frame in Frame 10.
Close frame from ws WebSocket client is encoded as:
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 1000 = Opcode: Connection Close (8)
1... .... = Mask: True
.000 0000 = Payload length: 0
Masking-Key: 9c72f352
Notably, there isn't a payload field in this packet.
What do you see instead?
The console log is:
Server is listening
Client is created
(node:289454) [UNDICI-WS] Warning: WebSockets are experimental, expect them to change at any time.
(Use `node --trace-warnings ...` to show where the warning was created)
Server has received client connection, port 34810
Client is open
Server is closing client connection
node:events:496
throw er; // Unhandled 'error' event
^
RangeError: Invalid WebSocket frame: invalid status code 0
at Receiver.controlMessage (/home/ubuntu/x/node_modules/.pnpm/ws@8.16.0/node_modules/ws/lib/receiver.js:633:30)
at Receiver.getData (/home/ubuntu/x/node_modules/.pnpm/ws@8.16.0/node_modules/ws/lib/receiver.js:481:12)
at Receiver.startLoop (/home/ubuntu/x/node_modules/.pnpm/ws@8.16.0/node_modules/ws/lib/receiver.js:171:16)
at Receiver._write (/home/ubuntu/x/node_modules/.pnpm/ws@8.16.0/node_modules/ws/lib/receiver.js:98:10)
at writeOrBuffer (node:internal/streams/writable:564:12)
at _write (node:internal/streams/writable:493:10)
at Writable.write (node:internal/streams/writable:502:10)
at Socket.socketOnData (/home/ubuntu/x/node_modules/.pnpm/ws@8.16.0/node_modules/ws/lib/websocket.js:1303:35)
at Socket.emit (node:events:518:28)
at addChunk (node:internal/streams/readable:559:12)
Emitted 'error' event on WebSocket instance at:
at Receiver.receiverOnError (/home/ubuntu/x/node_modules/.pnpm/ws@8.16.0/node_modules/ws/lib/websocket.js:1189:13)
at Receiver.emit (node:events:518:28)
at emitErrorNT (node:internal/streams/destroy:169:8)
at emitErrorCloseNT (node:internal/streams/destroy:128:3)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
code: 'WS_ERR_INVALID_CLOSE_CODE',
[Symbol(status-code)]: 1002
}
Node.js v20.11.0
While the error appears to come from ws, it is caused by incorrect packet encoding by Node.js native WebSocket implementation.
node-websocket.pcapng.zip is the packet capture of this execution.
When the WebSocketServer closes the client connection in Frame 8, the WebSocket client responds with a Close frame in Frame 10.
Close frame from Node.js native WebSocket is encoded as:
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 1000 = Opcode: Connection Close (8)
1... .... = Mask: True
.000 0010 = Payload length: 2
Masking-Key: 8d28378b
Masked payload
Payload
Close
Status code: Unknown (0)
Notably, there's a payload containing a Status code that is zero.
Additional information
According to RFC 6455 section 5.5.1:
The Close frame MAY contain a body that indicates a reason for closing.
If there is a body, the first two bytes of the body MUST be a 2-byte unsigned integer (in network byte order) representing a status code with value /code/ defined in Section 7.4.
According to RFC 6455 section 7.4.2:
Status codes in the range 0-999 are not used.
Node.js native WebSocket client has violated the protocol by sending an unassigned value as the status code.