Skip to content

WebSocket: upgrade requests >= 512 bytes are never answered (blocks every real browser) #99

Description

@rubinlinux

Branch: ircv3.2-upgrade @ 3868b34. Related: #97, #98.

Summary

A WebSocket upgrade request of 512 bytes or more never gets a response — the connection just hangs until the registration timeout. Real browsers send upgrade requests of roughly 500–700 bytes (User-Agent, Accept-*, Origin, Sec-WebSocket-Extensions, Sec-Fetch-*, cookies…), so no browser can currently connect, even over TLS where #97 doesn't apply. Node's WebSocket sends a ~200-byte request, which is why the CLI probes in #97/#98 worked.

Reproduction

Against wss://localhost:8443/ (ssl = yes; websocket = yes;), send a handcrafted upgrade request padded with a harmless header to a given total size:

request size result
500 bytes HTTP/1.1 101 Switching Protocols
512 bytes no response, hang
528 bytes no response, hang

Chromium 13x headless with an otherwise-working page: its actual request is ~553 bytes → hang; the same page connecting through a local TLS proxy that strips User-Agent, Accept-*, Origin and Sec-WebSocket-Extensions (553 → 211 bytes) works end to end (registers, joins, chats).

Cause (likely)

The handshake path reads the request into the client's normal 512-byte input buffer (BUFSIZE) and only calls websocket_handshake() once it sees the terminating \r\n\r\n within that buffer (ircd/s_bsd.c around the IsWSNeedHandshake read path, ~:1076-1110). A request larger than the buffer never contains the terminator in a single read, so the handshake is never attempted and the pre-registration data is silently dropped/overwritten. Same family as #98 (fixed-size buffers sized for IRC lines being applied to WebSocket framing).

Suggested fix

Accumulate the upgrade request in a per-client handshake buffer (a few KB — browsers with cookies can exceed 2 KB) until \r\n\r\n arrives or a sane cap (e.g. 8 KB → 431/close) is hit, then run websocket_handshake() on the whole thing. autodetect ports need the same treatment.

Also observed (not necessarily a bug)

The TLS listener requests a client certificate during the handshake. Headless Chromium aborts with ERR_SSL_CLIENT_AUTH_CERT_NEEDED because it has no way to answer the prompt; interactive browsers may show a certificate picker on every connect when the user has any client certs installed. If the request is only there for CertFP / SASL EXTERNAL, consider making it optional per-port or per-feature for websocket listeners.

Context

Found while bringing up Seance (browser IRCv3 client, TheLounge fork) against a docker build of this branch. Happy to test a fix.

Metadata

Metadata

Assignees

Labels

bugircv3-upgradeIssues against the ircv3.2-upgrade / ircv3.2-hardening branches

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions