-
Couldn't load subscription status.
- Fork 109
Description
Need to support Websocket (RFCs 6455), both the ws:// and wss:// schemes.
HTTP/1
Parser extensions
Upgrade: websocketheader RFC 7230 6.7 must be implemented. We support onlywebsocket, so values likeh2corh2c, websocketshould be just ignored.Upgradedirective forConnectionheader. This must be done for requests (HTTP/1 and HTTP/2) and responses. It seems we shouldn't do anything if a server just advertisesUpgradew/o preceding clientUpgraderequest.
Since Upgrade and Connection is a hop by hop header, if we ignore their values, then a backend receives a request w/o the headers.
If all the headers are good and we can upgrade to websocket, then a new websocket flag for the request must be set and tfw_h1_adjust_req() must regenerate the headers. Note that Sec-WebSocket-* headers aren't hop-by-hop and shouldn't be stripped.
All the extensions must be tested for HTTP/1 in test_http_parser.c
Scheme parsing
Req_Uri state at the moment handles only http:// scheme, which must be fixed to allow https:// and extend with the new ws:// and wss://. Since we'll need to match 4 strings, it makes sense to introduce new states with switch on prefixes (see as headers parsing is done). It seems that browsers never (or almost) never send full URLs, so the states should be cold and unlikely.
HTTP/2
Websockets for HTTP/2 are described in 8441 and we have a very special case of proxying HTTP/2 CONNECT to HTTP/1 websocket.
Nginx just proxies CONNECT method. I don't understand how reliable websockets in these setups, but there are issues in the Internet with websockets over HTTP/2 proxying by Nginx, e.g. RocketChat/Rocket.Chat#15028 . The problem is that RFC 8441 does not require an HTTP/2 client to set Sec-WebSocket-Key header, but maybe the most implementations actually do set the header.
HAproxy implements the header computation for this case (see h1_search_websocket_key()). I believe we also should generate the header from tfw_h2_adjust_req() plus to generation of Upgrade and Connection headers as in tfw_h1_adjust_req().
Parser extensions
- send SETTINGS_ENABLE_CONNECT_PROTOCOL to a client
- CONNECT method in the h2 request parser. The parser must take care about restrictions described in RFC 7540 8.3: the ":scheme" and ":path" pseudo-header fields MUST be omitted and the ":authority" pseudo-header field contains the host and port to connect to.
- parse
:protocolpseudo header and allowwebsocketvalue only. It's not in the HPACK static table, but it seems we need to support it for the dynamic table compression from the client side - Extend the
Req_HdrPsSchemeVstate withhttpscheme forws
Please add the h2 tests to t/unit/test_http_parser.c.
Backend connections
WebSockets must use separate TCP backend connection (i.e. a connection shall not be used to send send websocket frames from other client and/or HTTP messages from the same or other client). The same backend connection can be upgraded to websocket, meaning that a new TCP connection must be established (provisioned) with the backend to satisfy upcoming HTTP client requests. In this sense the issue depends/linked with #710 (dynamic server connections).
Once we receive 101 response from the server, we do
- set client
TfwConn->proto.typeto a new typesConn_WsClntandConn_WssClntand server's type toConn_WsSrvandConn_WssSrv, just like appropriate HTTP and HTTPS connections - move the current server connection out of the scheduler and initialize the reconnection procedure (as the connection was terminated). The websocket connection must be properly closed on Tempesta shutdown
- WS and WSS network handling must copy the similar handling for HTTP and HTTPS, but WS protocol is just a plain proxy, which simply retransmits all the data to the linked peer socket. Should be implemented in new
websocket.[ch]files. - point 3 implies that the step 1 must also reset
TfwConnHooksfor the connection to the appropriate websocket hooks
Since we need to introduce a new connection type, it makes sense to port 2eae1da to not to bother with GFSM here.
Rate limits
Client and server inactivity timeouts as well as upgraded connection timeout must be configurable. Websocket server is a frequent target for DDoS attacks, so ratelimits for websocket client connections client_ws_timeout must be implemented. We can handle the server timeouts just as normal TCP timeouts.
General considerations
Understanding the protocol
There is no need to decode or analyse WebSocket binary frames somehow, just pass them through. In future, probably we'll add some security checks, but not in the issue.
TCP proxying
It's recommended to use wss://, WebSocket over TLS, so we should just retransmit WebSocket application data messages after handshake. Because of TLS we can not use IPVS, and basically there is not much logic for IPVS.
Testing
The code probably can be tested by Node.js using the Websocket module. See Nginx's test as an example.
Functional tests for the issue are in #881, which depends on migration to Python3. If the migration isn't here in time, then just a simple Python3 scripts using the Python websockets must be developed. The scripts will later be used for #881.
References
Server-Sent Events, WebSockets, and HTTP by Mark Nottingham