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

websocat does not properly perform the close handshake as defined in the RFC #269

Open
mkurz opened this issue Oct 13, 2024 · 5 comments
Open

Comments

@mkurz
Copy link

mkurz commented Oct 13, 2024

We are working on a WebSocket server implementation. websocat is a really nice tool so far, however it violates the WebSocket specs:
In 5.5.1:

If an endpoint receives a Close frame and did not previously send a Close frame, the endpoint MUST send a Close frame in response. (When sending a Close frame in response, the endpoint typically echos the status code it received.)

So when our server sends a close frame (with, lets say status code 4001) websocat does receive that close frame, but does not reply to it. So what happens now is that our server waits for the close reply, but since this never happens, the tcp connection stays open until our server times out and the connections eventually closes.
(BTW, I am aware of the -E option of websocat, which immediately shuts down the connection.)

Using Wireshark, I can see the close frame gets send from the server to websocat, however websocat does not reply:

Calling the exact same webservice endpoint on our server with Firefox or Chrome, they correctly reply with an close frame (the "echo" with the same status code). After receiving this echoed close frame, our WebSocket server happily closes the underlying tcp connection immediately. Here is how that looks like in wireshark:

You can see the [MASKED] message (with the red arrow) is the reply from Firefox (or Chrome) to the WebSocket Server.

Others also already are aware that websocat does not correctly handle the close handshake:

@mkurz
Copy link
Author

mkurz commented Oct 13, 2024

I think without the -E option, websocat should definitely reply with an echoed close frame. This is just how things should work.

With the -E option it might make sense to not send a close frame by default, but introduce another option, that only applies if -E is given, where you can also enable to echo back a close frame when -E is turned on.

@vi
Copy link
Owner

vi commented Oct 14, 2024

If an endpoint receives a Close frame and did not previously send a Close frame, the endpoint MUST send a Close frame in response.

Websocat expects close frame to be equivalent to EOF, i.e. you can send some request, followed by a close frame, then expect a reply, also followed by a close frame i.e. close frames are interpreted closer to FIN then to RST. Premature close frames may disrupt this flow, leading to trimmed replies, so Websocat gives users a change to finish replying prior to closing the connection, unless -E is used.

More compliant behaviour may be implemented as a separate opt-in option (though probably more likely in Websocat4 branch instead of master).

I don't really understand the reason why close frames are needed in Websockets in the first place (apart from providing additional message) and how they should interact with semi-closed (shutdown(SHUT_RD)) sockets and backpressure, so this area is somewhat unattended in Websocat. Maybe it's actually the time for me to read the RFC fully?

Current Websocat4 behaviour is to interpret incoming close frames as EOF indicators and turn outgoing EOF indicators to close frames, with only pings having a special, direct patch from ingress to egress. There is a mechanism to close the whole socket (and its associated tasks) based on certain events, but currently reception of close frames is not connected to that mechanism. Activating it would probably just abandon all involved sockets abruptly, without a goodbye message (or only with a goodbye message in an uncongested case).

vi added a commit that referenced this issue Oct 14, 2024
@vi
Copy link
Owner

vi commented Oct 14, 2024

Quickly implemented proper shutdown in -E mode in websocat4 branch.

When using echo | websocat ws://127.0.0.1:1234 as client and websocat4 -E -s 1234 --log-traffic as server, I get

READ 153 "GET / HTTP/1.1\r\nHost: 127.0.0.1:1234\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: Qmeetcn4xXlkrRijORtbqw==\r\n\r\n"
WRITE 166 "HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ubk4zLDxGCGwLEM63UQ/jxbIkYk=\r\ndate: Mon, 14 Oct 2024 00:34:40 GMT\r\n\r\n"
READ 7 "\x81\x81\xa6E8\x11\xac"

READ 6 "\x88\x80?V2$"
^C

before the change and

READ 153 "GET / HTTP/1.1\r\nHost: 127.0.0.1:1234\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: FsdyjOUWO1tLYLjB+yZZ8w==\r\n\r\n"
WRITE 166 "HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: 9ooB1foN59bSUVXCNixlDmps7L4=\r\ndate: Mon, 14 Oct 2024 00:35:00 GMT\r\n\r\n"
READ 7 "\x81\x81U\x11\x7fZ_"

READ 6 "\x88\x80\xaf\xfb4\x82"
WRITE 2 "\x88\x00"
^C

After the change.

It does not copy the close frame, but it does send it's own empty close frame. The change only affects the -E mode.

@mkurz
Copy link
Author

mkurz commented Oct 16, 2024

The change only affects the -E mode.

Why just this mode?

@vi
Copy link
Owner

vi commented Oct 16, 2024

Because non--E mode already closed the writer properly (i.e. with an attempt to send the close frame).

But without -E that closing is only triggered by a writer to the WebSocket, not by the WebSocket peer.

Here is the table (built from my understanding, without actually checking all the cases):

Websocat version and mode v1 no -E v1 with -E v4 no -E v4 with -E without the fix v4 with -E with the fix
Aborts the session when getting EOF from user N Y N Y Y
Sends a close frame when getting EOF from user Y N? Y N? Y
Aborts the session upon receiving the close frame (but not EOF from user) N Y N Y Y
Sends a close frame when observing incoming close frame (but not EOF from user) N N N N Y
Aborts the session when getting EOF from user and incoming close frame Y Y Y Y Y
Sends a close frame when getting EOF from user and incoming close frame Y ? Y N Y

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

No branches or pull requests

2 participants