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

Getting "existing connection forcibly closed by remote host" when I should be getting websocket::error::closed #1209

Closed
emag37 opened this issue Jul 26, 2018 · 8 comments
Labels
Stale No recent activity

Comments

@emag37
Copy link

emag37 commented Jul 26, 2018

Hello,

I have a simple test setup where I have a simple secure websocket server running in Tornado websockets in Python 2.7, and a C++ websocket client using the version of boost::beast included with the Boost 1.67.0 release. The connection is made over SSL with TLSv1.2. I'm compiling with MSVC 19.14.26433.0 x86_64 (32 bit compiler for 64 bit host)

My C++ client calls async_read and waits for data. When it receives data, it checks for any errors then fires a callback with said data. It then starts another async_read.

From what I understand, when I call close() on the server-side websocket connection, the async_read callback on the C++ side should gracefully intercept and respond to the websocket 'close' frame, and callback with the error code set to websocket::error::closed.

However, my async_read callback always seems to have "An existing connection was forcibly closed by the remote host" set as the error. I've also seen it as "stream truncated" a couple of times.

I registered a control_callback, and am able to see the 'close' frame being received there, so I'm not sure why async_read does not have the correct error message.

Is this a known issue? Am I doing something wrong? Thank you!

@vinniefalco
Copy link
Member

From what I understand, when I call close() on the server-side websocket connection, the async_read callback on the C++ side should gracefully intercept and respond to the websocket 'close' frame, and callback with the error code set to websocket::error::closed.

Yeah you have that all correct. ECONNRESET ("An existing connection was forcibly closed by the remote host") means exactly what it says. The remote host (Tornado websockets in this case) reset the connection.

And if we look at the source code[1] for Tornado websockets we find this:

# Give the client a few seconds to complete a clean shutdown,
# otherwise just close the connection.
self._waiting = self.stream.io_loop.add_timeout(
    self.stream.io_loop.time() + 5, self._abort)

Ho ho! What's this? A call to self_.abort()? Not best practices :) Maybe on the Beast client side you are still writing data? Or for some other reason there is a delay? What happens when you don't use SSL?

[1] http://www.tornadoweb.org/en/stable/_modules/tornado/websocket.html

@emag37
Copy link
Author

emag37 commented Jul 26, 2018

Hi Vinnie, I appreciate the quick and detailed response!

So I did some more testing: I compiled simple test apps using the provided example applications:

https://www.boost.org/doc/libs/1_67_0/libs/beast/example/websocket/client/

With sync and async clients and no SSL, I get the websocket::error::closed as expected.

With the async SSL client, I get the "An existing connection was forcibly closed by the remote host" error.

I timed the delay between receiving the 'close' control frame and getting the async_read callback - it is in the order of microseconds, so Tornado is not calling self_.abort() due to the timeout delay. Also, Tornado calls its on_close handler, so it is getting the 'close' frame from Beast as expected. I have a funny feeling Tornado is killing the SSL stream too quickly when it gets the 'close' frame, which might cause Beast to give the wrong error.

Anyways, not a big deal - since I can consistently catch the close frame in the control_callback I'll set a flag to override the error.

I have attached my Tornado test server, just send it a 'close' string and it will initiate the close from the server-side.

websocket_server_test.zip

@vinniefalco
Copy link
Member

I have a funny feeling Tornado is killing the SSL stream too quickly when it gets the 'close' frame, which might cause Beast to give the wrong error.

I think that is precisely what is happening. Consider trying a few other servers. You might also try a beast SSL websocket server. Google servers in particular love to close the socket without finishing the SSL shutdown handshake.

@emag37
Copy link
Author

emag37 commented Jul 26, 2018

I actually had tried it with a synchronous boost::beast websocket server and I did get the correct error code. Not entirely sure who's court the bug lands in- whether Tornado shouldn't close the socket so quickly, or if boost::beast should preserve the close error code even if the SSL stream closes. Anyways, I guess I'll just hook onto control_callback for now. Thanks!

@vinniefalco
Copy link
Member

preserve the close error code even if the SSL stream close

Definitely not, because this would mask a man in the middle attack on the closing handshake.

@zlojvavan
Copy link

I've also seen it as "stream truncated" a couple of times

could you clarify please? what precise error code and message you get? in my app I sometimes can see ec.value()=2 and ec.message() returning "End of file" in void fail(boost::system::error_code ec, char const* what) handler

@stale
Copy link

stale bot commented Sep 6, 2018

This issue has been open for a while with no activity, has it been resolved?

@stale stale bot added the Stale No recent activity label Sep 6, 2018
@stale
Copy link

stale bot commented Sep 13, 2018

It looks like this issue has either been abandoned or resolved so I will go ahead and close it. Feel free to re-open this issue if you feel it needs attention, or open new issues as needed. Thanks!

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

No branches or pull requests

3 participants