You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I called Listen on an SSH Client, and then started an HTTP server on the Listener. When an HTTP Get was received on the HTTP server, I tried to call Hijack on the ResponseWriter.
For context, the purpose of the Hijack was to upgrade the connection to a Websocket; Hijack was called indirectly by github.com/gorilla/websocket.
The reason seems to be that Hijack tries to abort any pending reads on the connection before returning it to the caller of Hijack. In this scenario there is indeed a pending read, because the http server is trying to read data using http.*connReader).backgroundRead. Hijack tries to abort the pending read by setting the read deadline of the connection to a time that has already passed using SetReadDeadline, and then waits for the read to be aborted by waiting on a condition variable which gets signalled when the read unblocks.
However, the SSH channel struct which implements the connection that HTTP is using does not support SetReadDeadline. Thus, the read is never terminated and so the Hijack method blocks forever.
See the following gorouting stacktraces for what I believe is the cause:
One way to fix the issue is to implement SetReadDeadline on the channel. I've attached a patch that implements it and solves this particular starvation issue, but requires more thorough testing.
An alternative solution might be to provide the user a mechanism in the HTTP package to prevent background reads. I assume the background read is just an optimization to help improve response times, in which case having a method to disable it seems reasonable.
One other thought is that some other protocol for aborting the read could be supported between the HTTP package and the underlying connection to abort any pending reads, rather than relying on SetReadDeadline. Perhaps the connection could be checked to see if it implements an Aborter interface with an Abort method, and if present that method could be called before SetReadDeadline. That could reduce the need to implement full support for SetReadDeadline in ssh.channel and instead just implement a simpler functionality.
Go version
go version go1.21.4 linux/amd64
Output of
go env
in your module/workspace:What did you do?
I called Listen on an SSH Client, and then started an HTTP server on the Listener. When an HTTP Get was received on the HTTP server, I tried to call Hijack on the ResponseWriter.
For context, the purpose of the Hijack was to upgrade the connection to a Websocket; Hijack was called indirectly by github.com/gorilla/websocket.
See the attached program which reproduces the issue on Linux.
hijack_unresponsive_repro.tar.gz
What did you see happen?
Hijack hung forever.
What did you expect to see?
I expected Hijack to return.
The reason seems to be that Hijack tries to abort any pending reads on the connection before returning it to the caller of Hijack. In this scenario there is indeed a pending read, because the http server is trying to read data using http.*connReader).backgroundRead. Hijack tries to abort the pending read by setting the read deadline of the connection to a time that has already passed using SetReadDeadline, and then waits for the read to be aborted by waiting on a condition variable which gets signalled when the read unblocks.
However, the SSH
channel
struct which implements the connection that HTTP is using does not support SetReadDeadline. Thus, the read is never terminated and so the Hijack method blocks forever.See the following gorouting stacktraces for what I believe is the cause:
One way to fix the issue is to implement SetReadDeadline on the channel. I've attached a patch that implements it and solves this particular starvation issue, but requires more thorough testing.
An alternative solution might be to provide the user a mechanism in the HTTP package to prevent background reads. I assume the background read is just an optimization to help improve response times, in which case having a method to disable it seems reasonable.
One other thought is that some other protocol for aborting the read could be supported between the HTTP package and the underlying connection to abort any pending reads, rather than relying on SetReadDeadline. Perhaps the connection could be checked to see if it implements an Aborter interface with an Abort method, and if present that method could be called before SetReadDeadline. That could reduce the need to implement full support for SetReadDeadline in ssh.channel and instead just implement a simpler functionality.
add-read-deadline.patch
The text was updated successfully, but these errors were encountered: