Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid WebSocket frame: invalid status code 0 #2679

Closed
yoursunny opened this issue Jan 31, 2024 · 0 comments · Fixed by #2680
Closed

Invalid WebSocket frame: invalid status code 0 #2679

yoursunny opened this issue Jan 31, 2024 · 0 comments · Fixed by #2680

Comments

@yoursunny
Copy link

yoursunny commented Jan 31, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant