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

websocket: proposal for a new core module #49478

Closed
wants to merge 15 commits into from

Conversation

prettydiff
Copy link
Contributor

@prettydiff prettydiff commented Sep 4, 2023

This is a proposal to add websockets as a new module for Node.js.

Standards Conformance

This implementation attempts to adhere to RFC 6455. If differences are identified I will supply the necessary changes. This implementation does not attempt to parse connection header values, such as extensions or sub-protocols values.

API

This proposal focuses exclusively on Node's APIs from its core modules and then supplies additional options as necessary to populate callbacks and RFC 6455 connection header values.

This proposal does not attempt to emulate any WHJATWG specification or execute with regards to HTTP. Node is not a web browser. The operating constraints are wildly different, therefore in the best interests of design a WebSocket implementation must adhere to the protocol standard closely, but otherwise isolated from other API concerns. All other API concerns represent additional constraints not necessary for the transport of messages over this protocol.

According to RFC 6455 section 1.7: The WebSocket Protocol is an independent TCP-based protocol. Its only relationship to HTTP is that its handshake is interpreted by HTTP servers as an Upgrade request. That means so long as the as the handshake completes according to the syntax provided HTTP remains completely unrelated. HTTP is a layer 7 (application) technology where WebSocket is defined as a TCP transport mechanism (layer 4). Executing WebSockets over HTTP then creates unnecessary overhead dramatically impacting maintenance, scale, and performance.

Performance

Some notes about performance.

Everybody that writes a websocket library wants to measure performance in terms of message sent speed, which seems to be a red herring. Message send speed is trivial compared to message receive speed.

Message send speed appears to be a memory bound operation. Using this approach to websockets I send at about 180,000 messages per second on my old desktop computer with slow DDR3 memory. On my newer laptop with faster DDR4 memory I can send at about 460,000 - 480,000 messages per second. This speed is largely irrelevant though because at around 460,000 messages garbage collection kicks in and message send speed slows to a crawl at around 5,000 per second.

Message receive speed is much slower and far more dependent upon the speed of the CPU. On my old desktop I can receive messages at a speed of around 18,000 messages per second while on my laptop its a bit slower at around 12,000-15,000 messages per second because the desktop has a more powerful CPU. The speed penalty on message reception appears to be due to analysis for message fragmentation. This approach accounts for 4 types of message fragmentation.

Origin

This implementation is based upon my own logic for my personal application. I have been experimenting with a faster native Node WebSocket implementation for more than 2 years. Origin code at https://github.com/prettydiff/share-file-systems/blob/master/lib/terminal/server/transmission/transmit_ws.ts

Test logic

A means to test and experiment with this proposal with minimal overhead.

const net = require("net");
const ws = require('./websocket.js');
const port = 567;
const host = '';
const secure = true;
const serverOpts = {
    ca: `-----BEGIN CERTIFICATE-----
    MIIF4jCCA8qgAwIBAgIJAODpjHdE+mQyMA0GCSqGSIb3DQEBDQUAMEQxGDAWBgNV
    BAMMD3NoYXJlLWZpbGUtcm9vdDETMBEGA1UECgwKc2hhcmUtZmlsZTETMBEGA1UE
    CwwKc2hhcmUtZmlsZTAgFw0yMzA3MTYyMzE3MTlaGA8yMDY4MDUyNDIzMTcxOVow
    QjEWMBQGA1UEAwwNc2hhcmUtZmlsZS1jYTETMBEGA1UECgwKc2hhcmUtZmlsZTET
    MBEGA1UECwwKc2hhcmUtZmlsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
    ggIBAPMrUIbWSZpn6XeasO57WNF5lYmpOxEOb61GdYq/tawxiRGj6e7wHXmL2ToQ
    62rQCGHQ0ylPr9yeUIxDDLwKL85H+kjzZSZzQg/+7shuiNRUBc6sumQuB34ldY/w
    vbqv1FnYtLfxFn45WnVFbRmZe3wxHr18cSfWPzb5s7/oga586pY6lcYJO1Y+xGhy
    3/jyEr+PAdqV3qSb/yU9qF+TNgaA2sAiDte4Bt5WYeQU7AQFgWflqdT6Dzr+NPin
    cTRtXBDe2rIwrdS+gOsuwEqG7OUiac/Ex32hBuDbR3qgpwzVGWRCbW+w31YBr4oY
    dPik1gg667ueqH4Jx5YN8YuisjoIouCc4/FJ2Vokh32udfY7Hdb3JSe6PyNvHjTt
    qtNYcIzU8bq5rREDuFHNmLDoKD1L0ycZcbO1ftPz3VXXJ8W5R+8/WsUnJx5gZN8k
    rnp3Ziq5KnUkCQwqgmqG7f4cQLB7h3Lq6znHjFrrwnauD1+ypHLa+Q7m6L1t4n5P
    J2QDOYQSy9J5mfTNXYl99VMB5Xclc0GTak9ymKzKr45yZepeNGmJT9K4GVDNjbHE
    GXt+1FX4uwncBlk4jSNKiqnXc9sEAPZ5wClSWOgboOdMTrN1Isi9ZPEzQ2owlUxX
    p+QVnh5blot6OhgL77KtM6a+Fs6Dz3mR3cEqv+WZwd0mn7+XAgMBAAGjgdYwgdMw
    EgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUqmfvp44Q4p0+BaQhXobyxEDb
    Az8wRwYDVR0RBEAwPoIJbG9jYWxob3N0ghhsb2NhbGhvc3QucHJldHR5ZGlmZi5j
    b22CEXNoYXJlZmlsZS5zeXN0ZW1zhwR/AAABMFUGA1UdHgROMEygSjALgglsb2Nh
    bGhvc3QwGoIYbG9jYWxob3N0LnByZXR0eWRpZmYuY29tMBOCEXNoYXJlZmlsZS5z
    eXN0ZW1zMAqHCH8AAAH/AAAAMA0GCSqGSIb3DQEBDQUAA4ICAQADH8+0kYtLBUaM
    CET/WTw21AvQgVMNAQil+QbFt0rz567cSL5uM2EVTK2+ERCuXcMBxECTzMz+nYsK
    z7nqrA5q9SE9M6q0weOULF/zPETXWAQLmRC2ej67pRIbRn10PoVTJIiyCTXKLEjR
    zRgRIiaC1vAxgVq+U7udo6MzpCyVo6JdCIlJQpF7aA9XjQJYbtKSO1cDHYM2dJUc
    fw3bzq0yYUodDscM7vD8SNJY+eynsApVFXi6h1TNGj283mVBvihnPq4PTOiRc4KK
    iyFIJsT5/mJ3XD+pAHI3dPwe95zz6G1afdL2EGU3OAf7SGQ46LhLMV9L+izcE6zR
    SnjaXzjk2rho1XtAityV82GJSUCE71Xv/ePQuEDNV4u0jFzF/uVHjIW40zzLFTYS
    hjBf6SdsjwXxOHu/7RgDgRGuYJfabX/CcbpxlocFCLeJUNE0SHcrFpfW4piiFD/o
    KPs2UNmuHSorUc7k6UTB8gQSqIjMSEeF6MHhVMcD5dgBCCEMhVWs5tRL/comCRTK
    BcRlCOouZJZaBMrseWpAdTl3GTSqLNAsCtqMakvOyxFOTrk/kGhPz3jLs1BTZZQ8
    b/boToH371317pu+k0eJ10CXr+W4+xUL9pBSMzaNw96/n5H7sCrWx9DUk3LUoXKN
    rYmowESTH/PehBFm3ndXq+zyoX7UGg==
    -----END CERTIFICATE-----`,
    cert: `-----BEGIN CERTIFICATE-----
    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
    -----END CERTIFICATE-----`,
    key: `-----BEGIN RSA PRIVATE KEY-----
    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
    -----END RSA PRIVATE KEY-----`
            };
// change the value of variable 'secure' to switch between net and tls operation

// server logic, execute as:
// node ./tester.js server
if (process.argv.includes('server')) {
    const server = ws.server({
        callbackConnect: function(headerValues, socket, ready) {
            //console.log("server callbackConnect, followed by header values");
            //console.log(headerValues);
            ready();
        },
        callbackListener: function(server) {
            console.log("server callbackListener");
        },
        callbackOpen: function(err, socket) {
            console.log("server callbackOpen");
        },
        messageHandler: function(message) {
            console.log("message received at server");
            console.log(message.toString());
        },
        listenerOptions: {
            host: host,
            port: port
        },
        secure: secure,
        serverOptions: serverOpts
    });
}

// client logic, execute as:
// node ./tester.js client
if (process.argv.includes('client')) {
    // unused throwaway server to halt thread termination
    const server = ws.server({
        callbackConnect: function(headerValues, socket, ready) {
            //console.log("server callbackConnect, followed by header values");
            //console.log(headerValues);
            // ready();
        },
        callbackListener: function(server) {
            // console.log("server callbackListener");
        },
        callbackOpen: function(err, data) {
            console.log("server callbackOpen, followed by http string");
            console.log(data);
        },
        messageHandler: function(message) {
            console.log("message received at server");
            console.log(message.toString());
        },
        listenerOptions: {
            host: host,
            port: 777
        },
        secure: secure,
        serverOptions: serverOpts
    });
    const client = ws.clientConnect({
        authentication: 'auth-string',
        callbackOpen: function(err, socket) {
            console.log("client callbackOpen");
            if (err === null) {
                socket.messageSend("hello from client");
            }
        },
        masking: false,
        id: 'id-string',
        messageHandler: function(message){
            console.log("message received at client");
            console.log(message.toString());
        },
        'proxy-authorization': 'proxy-auth',
        subProtocol: 'sub-proto',
        type: 'type-string',
        secure: secure,
        socketOptions: {
            host: host,
            port: port,
            rejectUnauthorized: false
        }
    });
}

This is a proposal to add websockets as a new module for Node.js.

This implementation attempts to adhere to RFC 6455. If differences are
identified I will supply the necessary changes. This implementation does
not attempt to parse connection header values, such as extensions or
sub-protocols values.

This proposal focuses exclusively on Node's APIs from its core modules
and then supplies additional options as necessary to populate callbacks
and RFC 6455 connection header values.

Some notes about performance.

Everybody that writes a websocket library wants to measure performance
in terms of message sent speed, which seems to be a red herring. Message
send speed is trivial compared to message receive speed.

Message send speed appears to be a memory bound operation. Using this
approach to websockets I send at about 180,000 messages per second on my
old desktop computer with slow DDR3 memory. On my newer laptop with
faster DDR4 memory I can send at about 460,000 - 480,000 messages per
second. This speed is largely irrelevant though because at around
460,000 messages garbage collection kicks in and message send speed
slows to a crawl at around 5,000 per second.

Message receive speed is much slower and far more dependent upon the
speed of the CPU. On my old desktop I can receive messages at a speed of
around 18,000 messages per second while on my laptop its a bit slower at
around 12,000-15,000 messages per second because the desktop has a more
powerful CPU. The speed penalty on message reception appears to be due
to analysis for message fragmentation. This approach accounts for 4
types of message fragmentation.
@nodejs-github-bot nodejs-github-bot added the needs-ci PRs that need a full CI run. label Sep 4, 2023
@RaisinTen
Copy link
Contributor

FYI, there is some ongoing discussion about exposing websocket into Node.js from undici itself - nodejs/undici#2217

@prettydiff
Copy link
Contributor Author

@RaisinTen Thank you. I was not aware of that project.

It seems associating WebSockets with HTTP imposes too much work and crushes performance while greatly complicating scale for future enhancements. I updated the pull request readme to be more clear on this matter. I understand the browser executes WebSockets in that manner, but Node is not a web browser. Browsers, aside from things like Chrome DevTools Protocol, are never servers in any capacity and browsers must accept anonymous connections to external servers. Those constraints, and many more, do not apply to Node, and so Node should not be restricted in the manner of a web browser.

@prettydiff
Copy link
Contributor Author

prettydiff commented Sep 4, 2023

TODO list:

  1. This will need code coverage and test automation.
  2. It will need proper error handling in the JS.
  3. websocket will need to be added to the subsystem list or the commit message must be changed to reflect an existing subsystem.
  4. Remain code style violation concerning documentation is not clear.

@devsnek
Copy link
Member

devsnek commented Sep 4, 2023

It seems associating WebSockets with HTTP imposes too much work and crushes performance while greatly complicating scale for future enhancements

Can you clarify what you mean when you say this? I see this PR also includes http machinery.

@piranna
Copy link
Contributor

piranna commented Sep 4, 2023

I like this pure protocol approach, at a similar level to the net built-in, later the http built in can add a http/websocket module to provide a browser-based high level API for compatibility purposes on top of this.

@lpinca
Copy link
Member

lpinca commented Sep 4, 2023

It seems associating WebSockets with HTTP imposes too much work and crushes performance while greatly complicating scale for future enhancements.

Any WebSocket implementation has nothing to do with HTTP apart from the initial handshake request. Using a plain net.Socket or tls.Socket to do the handshake request has little to no advantage. The 'upgrade' events (https://nodejs.org/api/http.html#event-upgrade and https://nodejs.org/api/http.html#event-upgrade_1) were designed exactly for this and return a plain net.Socket or tls.Socket which is completely detached from the HTTP stuff. Using http.Server and http.get() for the initial handshake greatly simplify things with no significant overhead.

@piranna
Copy link
Contributor

piranna commented Sep 4, 2023

It seems associating WebSockets with HTTP imposes too much work and crushes performance while greatly complicating scale for future enhancements.

Any WebSocket implementation has nothing to do with HTTP apart from the initial handshake request. Using a plain net.Socket or tls.Socket to do the handshake request has little to no advantage. The 'upgrade' events (nodejs.org/api/http.html#event-upgrade and nodejs.org/api/http.html#event-upgrade_1) were designed exactly for this and return a plain net.Socket or tls.Socket which is completely detached from the HTTP stuff. Using http.Server and http.get() for the initial handshake greatly simplify things with no significant overhead.

But also, process the HTTP Upgrade header "by hand" directly on top of net module or with a helper function, it's not too much complex either, just process the incoming bytes as text and look for the Upgrade header. I see it as a valid use case.

@prettydiff
Copy link
Contributor Author

@devsnek More precisely HTTP is an application layer protocol (OSI layer 7). What happens typically with HTTP is that a socket is opened for each request. A keep alive header modifies that behavior and HTTP/2 and QUIK work very differently. Even still HTTP always imposes a connection roundtrip request/response and HTTP is both sessionless and anonymous. There are advantages to sessionless communication, most primarily initiating other communication conventions. There are also conveniences to the HTTP round trip process.

TCP sockets do not make these distinctions. TCP sockets can be session-oriented or not, and anonymous or not. They are too low level to care. In most use cases WebSockets are session-oriented and long lived to push multiple messages across a network over some undefined length of time.

TCP sockets are also full-duplex, at least with regard to WebSockets, which means both sides can send messages at any time. That further means that once the handshake completes the sockets send and listen for messages in identical ways irrespective of whether the socket occurs in the role of client or server. RFC 6455 provides a distinction there though that client's must mask their messages and servers must not. My personal opinion is that message masking is completely unnecessary so I provided an option to force it on or off for both the client and the server.

This proposal sends a text message across a network that looks like an HTTP heading. It is just a text message sent using socket.write. The server responds with a confirmation text message. Just about everything in those two messages that make up the handshake process is decoration. The required pieces are the Sec-WebSocket-Key from the client and the Sec-WebSocket-Accept from the server. The strings conveyed there provide for the handshake process. Once the handshake completes I overwrite the net socket.write method with my own method to impose RFC 6455 frame buffers on all messaging.

Something that will demand further examination is piping a stream directly through the internal messageSend function.

@lpinca
Copy link
Member

lpinca commented Sep 4, 2023

But also, process the HTTP Upgrade header "by hand" directly on top of net module or with a helper function, it's not too much complex either, just process the incoming bytes as text and look for the Upgrade header. I see it as a valid use case.

It means reimplementing the whole headers parsing which ironically has nothing to do with WebSocket. It might make sense on the client but it is not so easy on the server if you want a server that handles both normal and upgrade requests and with things like slowloris. Why reinventing the wheel?

Also the API should be discussed and there is work to do. I see no validation (invalid method, subprotol header, sec-websocket-extensions header, invalid frames, UTF8), no closing handshake, no way to send a fragmented message, etc. This is far from ready and usable. I would convert it to draft.

@prettydiff
Copy link
Contributor Author

The way to send a fragmented message is through the third argument of socket.write which is internally the function messageSend. That third argument is the fragmentation size. I will update the documentation to better identify this.

Closing occurs through sending a control frame with opcode 8. When the client receives that opcode it will destroy the socket from its side if its not already destroyed by the server.

There is also support for sub-protocol and extension headers. This implementation makes no attempt to parse the values internal from those headers as that is best left to user land code. The sub-protocol and extension headers are populated by the client user into the options argument for the clientConnect function. Once the headers are evaluated those header values are passed into the callbackOpen functions of both the client and server sides as well as the callbackConnect function passed into the server (if provided).

@lpinca
Copy link
Member

lpinca commented Sep 4, 2023

The way to send a fragmented message is through the third argument of socket.write which is internally the function messageSend. That third argument is the fragmentation size. I will update the documentation to better identify this.

I might be missing something but the I would expect an api that would allow me to do write(fragment1, fin = false), write(fragment2, fin = false)write(fragment3, fin = true) where the whole message is fragment1 + fragment2 + fragment3.

Closing occurs through sending a control frame with opcode 8. When the client receives that opcode it will destroy the socket from its side if its not already destroyed by the server.

That's not how the closing handshake work. Each peer should have both sent and received a close frame before closing the TCP socket.

This implementation makes no attempt to parse the values internal from those headers as that is best left to user land code.

The RFC mandates that the connection must be closed during the opening handshake if the those headers are invalid.

@tniessen
Copy link
Member

tniessen commented Sep 4, 2023

There are many existing WebSocket implementations for Node.js. I assume folks would want to see an implementation following the WebSockets Standard in core.

@prettydiff
Copy link
Contributor Author

prettydiff commented Sep 4, 2023

I might be missing something but the I would expect an api that would allow me to do write(fragment1, fin = false), write(fragment2, fin = false), write(fragment3, fin = true) where the whole message is fragment1 + fragment2 + fragment3.

That is handled internally by the messageSend function. My recommendation is to refactor that approach to whatever solution best accommodates stream piping.

The RFC mandates that the connection must be closed during the opening handshake if the those headers are invalid.

The spec does say that for extensions in section 9.1. I can add that logic, but its pretty draconian. According to the spec values must be applied according to the conventions of 2616, which is super old and also means you cannot apply a JSON based value. The extensions header is the primary place to add custom header data without adding additional custom headers which would also exceed the scope of the specification.

@prettydiff
Copy link
Contributor Author

@tniessen that is the API standard for the browser. This implementation takes no position on API and will change to whatever is best for sending messages. Since the WHATWG spec is for the browser it provides no API definition for a WebSocket server.

@prettydiff
Copy link
Contributor Author

Closing process updated to better comply with RFC 6455 and status codes updated to better align with the WHATWG status codes.

@prettydiff
Copy link
Contributor Author

  • Added support for a custom header option.
  • Now allows messageHandler definitions, and any other customization according to the options provided, on the server side per socket.
  • Upon closer examination I am already solving for extensions validation. According to RFC 6455 section 9.1 the field values must conform to RFC 2616 section 4.2 which is based upon RFC 822 section 3.1 (email). This is just basic HTTP/Email message header syntax. If the headers cannot be parsed according to that criteria the handshake does not complete. The server has 5 seconds to complete the handshake before the socket is destroyed.

@jcoglan
Copy link

jcoglan commented Sep 5, 2023

I have spent a little time running and reviewing this code, including running the server and connecting my own client implementations to it. I have found a number of correctness issues that ought to be addressed before this is considered production-ready.

  • Many protocol edge cases are not checked or handled, including checks for invalid handshake headers, opcodes, frame headers, UTF-8 text, bad mixing of fragmented messages, etc. None of the standard error codes (1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011) appear to be used. I would recommend running this implementation against the Autobahn test suite to check it thoroughly and make it compatible with other implementations. The remaining issues here are just what I've been able to detect by reviewing the code.

  • HTTP message parsing throughout the code is not robust and relies on basic string operations like splitting on line breaks and colons, rather than using a proper HTTP parser implementation. It does not ensure that the HTTP headers are truly valid, and the naive parsing method used leaves the implementation open to misinterpret messages. Several recent Node.js releases have addressed security problems related to HTTP header parsing and this code would create new vectors for malformed messages being treated as genuine.

  • The status code is checked by seeing if the response message contains the string HTTP/1.1 101 Switching Protocols anywhere, as opposed to only as the first line of the response. This check is also overly strict as the spec only requires a 101 status code, and not the specific status message Switching Protocols.

  • Although you can argue the HTTP handshake is incidental to the protocol, and that WebSocket frame data could be sent over any TCP/TLS connection (as I'll cover later, this is not actually the case), an implementation still needs to deal with the fact that HTTP messages and subsequent frame data is sent over the same data stream. However, the event handler the client uses to process the HTTP response assumes that the first buffer the client receives will contain the entire response message, and that it will not contain any WebSocket frame data after the response headers. This is not true in general. The client needs to handle both these as a single stream, detect when the HTTP headers have ended, and begin parsing the stream as frame data after that. This implementation will be confused when the headers are received as multiple data events, and will ignore any frame data in the first event. This means it can lose messages, or even miss a partial frame supplied in the first event, putting its frame parser in an invalid state and making it mis-parse the rest of the stream.

  • The client expects the Sec-WebSocket-Accept header value to be hex-encoded, when it should expect base64. The server also sends this value in hex.

  • There is more ad-hoc parsing used to process the client's request URL, this should use a real URL parser.

  • The Sec-WebSocket-Key value is deterministic, based on the current time and hostname. These values are not hashed or otherwise obfuscated so the client effectively discloses its hostname and clock time to the server. The spec states:

    The value of this header field MUST be a nonce consisting of a randomly
    selected 16-byte value that has been base64-encoded (see Section 4 of
    [RFC4648]). The nonce MUST be selected randomly for each connection.

    I would consider using crypto.randomBytes() here.

  • The writeFrame() function is synchronously self-recursive and is used to process an unbounded queue, leading to the possibility of a stack overflow.

  • The masking key is generated from Math.random() and will only contain 10 possible byte values; the ASCII codes for digits 0 to 9. The spec says:

    The masking key is a 32-bit value chosen at random by the client. When
    preparing a masked frame, the client MUST pick a fresh masking key from the
    set of allowed 32-bit values. The masking key needs to be unpredictable;
    thus, the masking key MUST be derived from a strong source of entropy, and
    the masking key for a given frame MUST NOT make it simple for a server/proxy
    to predict the masking key for a subsequent frame. The unpredictability of
    the masking key is essential to prevent authors of malicious applications
    from selecting the bytes that appear on the wire.

    Again I would suggest using crypto.randomBytes() here.

  • The implementation handles many undefined opcodes with the same code path used for text/binary frames. These opcodes should be excluded from use, and receiving them should trigger a protocol error.

  • The close, ping, pong and several unused opcodes also go through the same code path.

  • The frame parser proceeds to parse the frame header whenever there are at least 2 bytes in the buffer. For frames with extended length headers, the parser will read past 2 bytes into the input and trigger out of bounds memory access. You can trigger this by sending the bytes 81 fe 00 in one chunk; this chunk contains only the first byte of a 2-byte extended length header, and it crashes the server.

  • The frame parser attempts to extract the mask key even if the buffer is not yet long enough. This does not cause a crash but is nevertheless not advisable to create frame representations that are incomplete and could cause incorrect processing.

  • The frame handler responds to close by echoing the message back to the other peer and stopping the parser. This is not always the correct course of action and depends on the state of the connection. It does not give any signal to the application that the closing handshake as been performed.

  • The ping() function has a bunch of problems that stop its current implementation from working, e.g.: socket.hash does not seem to be set, socket.messageSend() does not exist. Replacing this call with socket.write(Buffer.from(nameSlice), 9) does at least send a ping frame to the client, but the pong reply does not seem to be handled properly and the ping() callback is fired after the ttl timeout rather than when the pong frame is received. Sometimes this callback is executed multiple times.

  • Closing the socket does not clear the pong timeout. Calling ping() sets a single socket.pong object that is not indexed by the ping payload, but the pong handler expects socket.websocket.pong[payload] to exist in order to clear the timeout.

  • The pong handler uses its own timer calculation to decide whether the ping's timeOut timer will have triggered, which is not reliable and allows for the timer to still trigger after socket.websocket.pong[payload] has been deleted.

  • The server request handler makes the same incorrect assumption as the client: the first buffer received will contain a complete HTTP message and no frame data.

  • The server header handler compares a lowercased string to one containing uppercase characters.

  • I could not find where in the code that flags.authentication should be set to true but I had to remove this check to allow a client to make a connection.

In addition to these issues there seems to be some confusion about the purpose of extensions and the Sec-WebSocket-Extensions header:

According to the spec values must be applied according to the conventions of
2616, which is super old and also means you cannot apply a JSON based value.
The extensions header is the primary place to add custom header data without
adding additional custom headers which would also exceed the scope of the
specification.

This is not what the Sec-WebSocket-Extensions header is for -- if the client wants to add additional metadata to its request, it should do so using additional headers, because the opening handshake is just an HTTP request message. Sec-WebSocket-Extensions is not a place for the client to add arbitrary additional data, but a means for the client and server to negotiate additional protocol behaviour. The client proposes a set of extensions and parameters it wishes to use, and the server accepts or declines these proposals by returning a Sec-WebSocket-Extensions header in its handshake response. Any extensions that have been mutually agreed can then change how messages are processed.

As such, this header requires a standard format for how extension names and parameters are represented, you cannot put arbitrary data in there. Though it may be "super old", that syntax is defined in the spec with reference to grammar from RFC 2616. This is also why the HTTP handshake and binary framing protocol are not fully separable -- the HTTP handshake establishes state that affects the operation of the socket for the rest of its lifetime. If you want to keep extensions but discard the HTTP handshake then you need to specify a different mechanism for extension negotiation; RFC 6455 simply leans on existing HTTP conventions to achieve this.

For example, the Compression Extensions allow for messages to be compressed; specifically permessage-deflate allows each message to be compressed using zlib. A client may send a header like:

Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits; server_max_window_bits=10

This indicates the client wants to use the permessage-deflate extension, that it supports the ability for the server to limit its sliding window size, and that it wants the server to use at most a 10-bit sliding window for its compression. This extension also supports the ability to request that the other peer use a fresh compression context for each message rather than carrying a compression context between messages. If the server wants to accept this extension proposal, it must include a Sec-WebSocket-Extensions header in its response indicating as much. After this, the client and server may both compress their outgoing messages according to the agreed parameters, and any compressed message must have its RSV1 bit set.

Handling of extensions in general imposes a bunch of extra requirements on an implementation:

  • You must parse, validate and generate the syntax mandated for Sec-WebSocket-Extensions header values.

  • You must implement a way for the server to consider the client's proposed extensions, decide which ones to accept and with which parameters, and generate a response header to communicate this decision.

  • You must let the client interpret the server's response header so that it knows which extensions have been accepted.

  • You must adjust the handling of RSV bits to take any active extensions into account.

  • You must pass any incoming and outgoing messages through the active extension(s) so that they may have their data transformed, e.g. compressed/decompressed.

  • You must make allowances for the fact that extension logic may be both asynchronous and stateful and be sure to preserve the order of messages between the socket and the application so as not to e.g. confuse the zlib decompressor, or any application logic that expects to see messages in the same order the peer sent them.

  • You must adjust the logic for closing the WebSocket to allow any messages that are still passing through the extension processing pipeline to drain away.

  • You have to propagate any errors encountered during an extension's message processing without breaking message ordering or the closing handshake.

This is a non-trivial set of requirements and I have a library dedicated just to handling them. This is used in faye-websocket, websocket-driver and permessage-deflate. All this behaviour also carries security implications and we have experienced two denial-of-service vulnerabilities, one due to a breaking change in zlib and one due to a regex used in parsing the header.

This implementation lets the client provide a Sec-WebSocket-Extensions header value as plain text, but the server ignores its value (aside from passing it to a callback which cannot meaningfully act on it), and the server does not generate its own response header or provide any way for an activated extension to take part in message processing. I think that any implementation that goes into core should at least support permessage-deflate even if it stops short of providing an open-ended framework for additional extensions.

That covers everything I have noticed in terms of correctness issues. While not strictly issues of correctness, I also noticed the following places where the code's efficiency could be substantially improved:

  • Better use of the Buffer API could be made, for example using Buffer.copy() rather than copying bytes one byte at a time.

  • The frame parser works by adding each incoming chunk to a buffer by using Buffer.concat() to combine it with the existing unparsed data, copying the existing buffer's data each time. It then bails out if the buffer is not yet as long as the frame header says it needs to be. This logic runs every time the socket receives data. This makes it possible to cause a large amount of buffer copying, and generate a large amount of garbage, by sending a large frame length header and then drip-feeding the frame payload over the connection in small chunks. It also re-runs the frame header parsing logic on every data event until the buffer is the expected size, which is wasted work.

  • Some header bytes are parsed by converting bytes to string representations and checking the characters, rather than using bitwise math.

Finally, some notes on the proposed API:

  • There is inconsistent use of kebab case and camel case in the client header options.

  • Implementing this functionality by replacing a bunch of the TCP/TLS socket's API with different implementations carries too much risk of breaking things, creating a confusing experience for users and being hard to debug. It would be better to create a distinct WebSocket stream object that has an internal reference to a TCP/TLS stream. WebSocket is a protocol build on top of TCP, not a replacement for TCP's machinery, and has significant behaviour differences, e.g. having frame-based discrete messages. (The spec calls it "layered over TCP", it's not a layer 4 protocol, it's built on top of TCP, just like HTTP is. As @lpinca says, the upgrade event gives you a bare TCP/TLS socket and imposes no other overhead on a WebSocket implementation.)

  • I was not able to find a way to tell which socket received the incoming message in the server's messageHandler() function and thereby respond to the relevant client. Does this callback expose the socket in any way?

@lpinca
Copy link
Member

lpinca commented Sep 5, 2023

@jcoglan thank you for taking the time to review and write all of that. From a quick glance, in addition to what you wrote, many conditions are not handled according to RFC 6455. This is what I meant in my previous comment with "missing validation". I'll copy paste some here.

Once a connection to the server has been established (including a
connection via a proxy or over a TLS-encrypted tunnel), the client
MUST send an opening handshake to the server.  The handshake consists
of an HTTP Upgrade request, along with a list of required and
optional header fields.  The requirements for this handshake are as
follows.

10.  The request MAY include a header field with the name
     |Sec-WebSocket-Protocol|.  If present, this value indicates one
     or more comma-separated subprotocol the client wishes to speak,
     ordered by preference.  The elements that comprise this value
     MUST be non-empty strings with characters in the range U+0021 to
     U+007E not including separator characters as defined in
     [RFC2616] and MUST all be unique strings.  The ABNF for the
     value of this header field is 1#token, where the definitions of
     constructs and rules are as given in [RFC2616].

12.  The request MAY include any other header fields, for example,
     cookies [RFC6265] and/or authentication-related header fields
     such as the |Authorization| header field [RFC2616], which are
     processed according to documents that define them.

Once the client's opening handshake has been sent, the client MUST
wait for a response from the server before sending any further data.
The client MUST validate the server's response as follows:

1.  If the status code received from the server is not 101, the
    client handles the response per HTTP [RFC2616] procedures.  In
    particular, the client might perform authentication if it
    receives a 401 status code; the server might redirect the client
    using a 3xx status code (but clients are not required to follow
    them), etc.  Otherwise, proceed as follows.

2.  If the response lacks an |Upgrade| header field or the |Upgrade|
    header field contains a value that is not an ASCII case-
    insensitive match for the value "websocket", the client MUST
    _Fail the WebSocket Connection_.

3.  If the response lacks a |Connection| header field or the
    |Connection| header field doesn't contain a token that is an
    ASCII case-insensitive match for the value "Upgrade", the client
    MUST _Fail the WebSocket Connection_.

5.  If the response includes a |Sec-WebSocket-Extensions| header
    field and this header field indicates the use of an extension
    that was not present in the client's handshake (the server has
    indicated an extension not requested by the client), the client
    MUST _Fail the WebSocket Connection_.  (The parsing of this
    header field to determine which extensions are requested is
    discussed in Section 9.1.)

6.  If the response includes a |Sec-WebSocket-Protocol| header field
    and this header field indicates the use of a subprotocol that was
    not present in the client's handshake (the server has indicated a
    subprotocol not requested by the client), the client MUST _Fail
    the WebSocket Connection_.

If the server's response does not conform to the requirements for the
server's handshake as defined in this section and in Section 4.2.2,
the client MUST _Fail the WebSocket Connection_.
If the server, while reading the handshake, finds that the client did
not send a handshake that matches the description below (note that as
per [RFC2616], the order of the header fields is not important),
including but not limited to any violations of the ABNF grammar
specified for the components of the handshake, the server MUST stop
processing the client's handshake and return an HTTP response with an
appropriate error code (such as 400 Bad Request).

1.   An HTTP/1.1 or higher GET request, including a "Request-URI"
     [RFC2616] that should be interpreted as a /resource name/
     defined in Section 3 (or an absolute HTTP/HTTPS URI containing
     the /resource name/).

2.   A |Host| header field containing the server's authority.

3.   An |Upgrade| header field containing the value "websocket",
     treated as an ASCII case-insensitive value.

4.   A |Connection| header field that includes the token "Upgrade",
     treated as an ASCII case-insensitive value.

5.   A |Sec-WebSocket-Key| header field with a base64-encoded (see
     Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in
     length.

6.   A |Sec-WebSocket-Version| header field, with a value of 13.
A client requests extensions by including a |Sec-WebSocket-
Extensions| header field, which follows the normal rules for HTTP
header fields (see [RFC2616], Section 4.2) and the value of the
header field is defined by the following ABNF [RFC2616].  Note that
this section is using ABNF syntax/rules from [RFC2616], including the
"implied *LWS rule".  If a value is received by either the client or
the server during negotiation that does not conform to the ABNF
below, the recipient of such malformed data MUST immediately _Fail
the WebSocket Connection_.

      Sec-WebSocket-Extensions = extension-list
      extension-list = 1#extension
      extension = extension-token *( ";" extension-param )
      extension-token = registered-token
      registered-token = token
      extension-param = token [ "=" (token | quoted-string) ]
          ;When using the quoted-string syntax variant, the value
          ;after quoted-string unescaping MUST conform to the
          ;'token' ABNF.

@prettydiff
Copy link
Contributor Author

I will apply these changes, but it will take several days. Tomorrow morning I move across the country.

@mcollina mcollina added the tsc-agenda Issues and PRs to discuss during the meetings of the TSC. label Sep 6, 2023
@joyeecheung
Copy link
Member

By the way we already have a C++ websocket server implementation in core for the inspector protocol: https://github.com/nodejs/node/blob/main/src/inspector_socket.cc it's just not exposed to users (and probably quite specific to the inspector protocol, but can surely be tweaked to be more general)

@piranna
Copy link
Contributor

piranna commented Sep 6, 2023

By the way we already have a C++ websocket server implementation in core for the inspector protocol: main/src/inspector_socket.cc it's just not exposed to users (and probably quite specific to the inspector protocol, but can surely be tweaked to be more general)

I think that could be the best aproach, or one of them.

@lpinca
Copy link
Member

lpinca commented Sep 6, 2023

I think that could be the best aproach, or one of them.

If we want something that is significantly faster than userland implementations, then yes, but it that case I'm not sure how hard it would be to integrate it well with the net, http, and http2 modules. Anything built on top of net.Socket obviously can't be faster than it. A ws WebSocket is around 15% slower than a plain net.Socket. A more optimized implementation can reduce it to to 5%? but that's it.

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel strongly that we should prefer an existing battle tested implementation or a very fast one instead of "rolling our brand new one", especially given the spec compliance concerns outlined by others.

Thank you for the effort it would be great if you'd be involved in the efforts to add websockets to Node.js 🙇

@prettydiff
Copy link
Contributor Author

@benjamingr A production tested implementation is a better idea. My approach is only tested within my personal application, which is a self-contained environment. Either way there will need to be a checklist of conformance checks and corresponding test units. Performance checks, if those should be included, must test message send speed and message receive speed independently.

One final consideration of note, and perhaps the most important distinction from HTTP, is that once the socket is established at both ends it sends and receives in an identical manner irrespective of whether the socket initiated as a client or server role (aside from default message masking). For stability any test automation validating message transfer or message integrity must be applied to both server and client sockets identically.

Copy link
Member

@jasnell jasnell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I appreciate the work here and agree that adding WebSocket support in core is a good thing, I would rather avoid adding a new, bespoke, non-standard WebSocket API. While the Web platform standard WebSocket API leaves lots to be desired, we should focus on implementing the standard API and work with the ecosystem to improve the standard.

@tniessen tniessen removed the tsc-agenda Issues and PRs to discuss during the meetings of the TSC. label Sep 20, 2023
@RedYetiDev
Copy link
Member

RedYetiDev commented Apr 21, 2024

Thank you very much for your contribution! Unfortunately, It seems like the discussion of WebSockets is at https://github.com/nodejs/undici, and it's unlikely this PR will receive any action.

I'm closing this to reduce the number of open PRs and issues in Node.js. Please let me know if you disagree or have a team member reopen it.

Nonetheless, thank you for your contribution to NodeJS!

@RedYetiDev RedYetiDev closed this Apr 21, 2024
@piranna
Copy link
Contributor

piranna commented Apr 21, 2024

Does this means built-in support for WebSockets will be discarded? I was really interested on that... :-(

@benjamingr
Copy link
Member

@piranna websocket is already enabled by default in v22 see #51594, not sure about the state of server efforts.

@piranna
Copy link
Contributor

piranna commented Apr 21, 2024

@piranna websocket is already enabled by default in v22 see #51594

Didn't know about that, good to know :-)

@piranna
Copy link
Contributor

piranna commented Apr 21, 2024

not sure about the state of server efforts.

Where can I get more details about this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet