Skip to content

Commit 5529efc

Browse files
committed
Do not send SSH_MSG_DISCONNECT when the server is closing the session by sending a SSH_MSG_DISCONNECT.
Reduces - but does not eliminate - likelyhood of race condition when remote server and client attempt to disconnect at the same time. Currently this leads to a NRE or ObjectDisposedException in Session.IsSocketConnected(ref bool isConnected) when one thread is attempting to check whether the socket is still connected, and the other thread is disposing the socket. This commit just reduces the likelyhood as the message loop thread that handes the SSH_MSG_DISCONNECT sent by the server will no longer attempt to send a SSH_MSG_DISCONNECT to the server, and as such will not check whether the socket is still connected.
1 parent c141c7d commit 5529efc

File tree

1 file changed

+16
-21
lines changed

1 file changed

+16
-21
lines changed

src/Renci.SshNet/Session.cs

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ public void Disconnect()
659659
{
660660
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Disconnecting session", ToHex(SessionId), DateTime.Now.Ticks));
661661

662+
// send SSH_MSG_DISCONNECT message, clear socket read buffer and dispose it
662663
Disconnect(DisconnectReason.ByApplication, "Connection terminated by the client.");
663664

664665
// at this point, we are sure that the listener thread will stop as we've
@@ -672,17 +673,16 @@ public void Disconnect()
672673

673674
private void Disconnect(DisconnectReason reason, string message)
674675
{
676+
// transition to disconnecting state to avoid throwing exceptions while cleaning up, and to
677+
// ensure any exceptions that are raised do not overwrite the exception that is set
675678
_isDisconnecting = true;
676679

677680
// send disconnect message to the server if the connection is still open
678681
// and the disconnect message has not yet been sent
679682
//
680683
// note that this should also cause the listener thread to be stopped as
681684
// the server should respond by closing the socket
682-
if (reason == DisconnectReason.ByApplication)
683-
{
684-
SendDisconnect(reason, message);
685-
}
685+
SendDisconnect(reason, message);
686686

687687
// disconnect socket, and dispose it
688688
SocketDisconnectAndDispose();
@@ -702,7 +702,7 @@ private void Disconnect(DisconnectReason reason, string message)
702702
/// </remarks>
703703
void ISession.WaitOnHandle(WaitHandle waitHandle)
704704
{
705-
WaitOnHandle(waitHandle, ConnectionInfo.Timeout);
705+
WaitOnHandle(waitHandle);
706706
}
707707

708708
/// <summary>
@@ -1027,7 +1027,6 @@ private void HandleMessage<T>(T message) where T : Message
10271027
private void HandleMessage(DisconnectMessage message)
10281028
{
10291029
OnDisconnectReceived(message);
1030-
Disconnect(message.ReasonCode, message.Description);
10311030
}
10321031

10331032
/// <summary>
@@ -1253,6 +1252,11 @@ protected virtual void OnDisconnectReceived(DisconnectMessage message)
12531252
{
12541253
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Disconnect received: {2} {3}", ToHex(SessionId), DateTime.Now.Ticks, message.ReasonCode, message.Description));
12551254

1255+
// transition to disconnecting state to avoid throwing exceptions while cleaning up, and to
1256+
// ensure any exceptions that are raised do not overwrite the SshConnectionException that we
1257+
// set below
1258+
_isDisconnecting = true;
1259+
12561260
_exception = new SshConnectionException(string.Format(CultureInfo.InvariantCulture, "The connection was closed by the server: {0} ({1}).", message.Description, message.ReasonCode), message.ReasonCode);
12571261
_exceptionWaitHandle.Set();
12581262

@@ -1263,6 +1267,9 @@ protected virtual void OnDisconnectReceived(DisconnectMessage message)
12631267
var disconnected = Disconnected;
12641268
if (disconnected != null)
12651269
disconnected(this, new EventArgs());
1270+
1271+
// disconnect socket, and dispose it
1272+
SocketDisconnectAndDispose();
12661273
}
12671274

12681275
/// <summary>
@@ -1763,21 +1770,9 @@ private void SocketRead(int length, byte[] buffer)
17631770
return;
17641771
}
17651772

1766-
// 2012-09-11: Kenneth_aa
1767-
// When Disconnect or Dispose is called, this throws SshConnectionException(), which...
1768-
// 1 - goes up to ReceiveMessage()
1769-
// 2 - up again to MessageListener()
1770-
// which is where there is a catch-all exception block so it can notify event listeners.
1771-
// 3 - MessageListener then again calls RaiseError().
1772-
// There the exception is checked for the exception thrown here (ConnectionLost), and if it matches it will not call Session.SendDisconnect().
1773-
//
1774-
// Adding a check for _isDisconnecting causes ReceiveMessage() to throw SshConnectionException: "Bad packet length {0}".
1775-
//
1776-
1777-
if (_isDisconnecting)
1778-
throw new SshConnectionException(
1779-
"An established connection was aborted by the software in your host machine.",
1780-
DisconnectReason.ConnectionLost);
1773+
// when we're in the disconnecting state (either triggered by client or server), then the
1774+
// SshConnectionException will interrupt the message listener loop (if not already interrupted)
1775+
// and the exception itself will be ignored (in RaiseError)
17811776
throw new SshConnectionException("An established connection was aborted by the server.",
17821777
DisconnectReason.ConnectionLost);
17831778
}

0 commit comments

Comments
 (0)