in sources/Google.Solutions.Ssh/SshWorkerThread.cs [197:417]
private void WorkerThreadProc()
{
//
// NB. libssh2 has limited support for multi-threading and in general,
// it's best to use a libssh2 session from a single thread only.
// Therefore, all libssh2 operations are performed by this one thead.
//
using (SshTraceSource.Log.TraceMethod().WithoutParameters())
{
try
{
using (workerThreadRundownProtection.Acquire())
using (var session = new Libssh2Session())
{
session.SetTraceHandler(
LIBSSH2_TRACE.SOCKET |
LIBSSH2_TRACE.ERROR |
LIBSSH2_TRACE.CONN |
LIBSSH2_TRACE.AUTH |
LIBSSH2_TRACE.KEX |
LIBSSH2_TRACE.SFTP,
SshTraceSource.Log.TraceVerbose);
if (!string.IsNullOrEmpty(this.Banner))
{
session.Banner = this.Banner!;
}
session.Timeout = this.ConnectionTimeout;
//
// Open connection and perform handshake using blocking I/O.
//
using (var connectedSession = session.Connect(this.endpoint))
using (var authenticatedSession = connectedSession.Authenticate(
this.credential,
this.keyboardHandler))
using (Disposable.Create(() => OnBeforeCloseSession()))
{
//
// Make sure the readyToSend handle remains valid throughout
// this thread's lifetime.
//
var readyToSendHandleSafeToUse = false;
this.readyToSend.DangerousAddRef(ref readyToSendHandleSafeToUse);
Debug.Assert(readyToSendHandleSafeToUse);
//
// With the channel established, switch to non-blocking I/O.
// Use a disposable scope to make sure that tearing down the
// connection is done using blocking I/O again.
//
using (session.AsNonBlocking())
using (Disposable.Create(() => this.readyToSend.DangerousRelease()))
using (var readyToReceive = NativeMethods.WSACreateEvent())
{
//
// Create an event that is signalled whenever there is data
// available to read on the socket.
//
// NB. This is a manual-reset event that must be reset by
// calling WSAEnumNetworkEvents.
//
if (NativeMethods.WSAEventSelect(
connectedSession.Socket.Handle,
readyToReceive,
NativeMethods.FD_READ) != 0)
{
throw new Win32Exception(
NativeMethods.WSAGetLastError(),
"WSAEventSelect failed");
}
//
// Looks good so far, consider the connection successful.
//
OnConnected();
//
// Set up keepalives. Because we use non-blocking I/O, we have to
// send keepalives by ourselves.
//
// NB. This method must not be called before the handshake has completed.
//
connectedSession.ConfigureKeepAlive(false, this.KeepAliveInterval);
var waitHandles = new[]
{
readyToReceive.DangerousGetHandle(),
this.readyToSend.DangerousGetHandle()
};
while (!this.workerCancellationSource.IsCancellationRequested)
{
var currentOperation = Operation.Receiving | Operation.Sending;
try
{
//
// In each iteration, wait for
// (data received on socket) OR (user data to send)
//
// NB. The timeout should not be lower than approx.
// one second, otherwise we spend too much time calling
// libssh2's keepalive function, which causes the terminal
// to become sluggish.
//
var waitResult = NativeMethods.WSAWaitForMultipleEvents(
(uint)waitHandles.Length,
waitHandles,
false,
(uint)this.SocketWaitInterval.TotalMilliseconds,
false);
if (waitResult == NativeMethods.WSA_WAIT_EVENT_0)
{
//
// Socket has data available.
//
currentOperation = Operation.Receiving;
//
// Reset the WSA event.
//
var wsaEvents = new NativeMethods.WSANETWORKEVENTS()
{
iErrorCode = new int[10]
};
if (NativeMethods.WSAEnumNetworkEvents(
connectedSession.Socket.Handle,
readyToReceive,
ref wsaEvents) != 0)
{
throw new Win32Exception(
NativeMethods.WSAGetLastError(),
"WSAEnumNetworkEvents failed");
}
//
// Perform whatever receiving operation we need to do.
//
// NB. We already reset the WSA event, so we must now read
// all data that's available.
//
OnReadyToReceive(authenticatedSession);
}
else if (waitResult == NativeMethods.WSA_WAIT_EVENT_0 + 1)
{
//
// User has data to send. Perform whatever send operation
// we need to do.
//
currentOperation = Operation.Sending;
OnReadyToSend(authenticatedSession);
}
else if (waitResult == NativeMethods.WSA_WAIT_TIMEOUT)
{
//
// Channel is idle - use the opportunity to send a
// keepalive. Libssh2 will ignore the call if no
// keepalive is due yet.
//
connectedSession.SendKeepAlive();
}
else if (waitResult == NativeMethods.WSA_WAIT_FAILED)
{
throw new Win32Exception(
NativeMethods.WSAGetLastError(),
"WSAWaitForMultipleEvents failed");
}
}
catch (Libssh2Exception e) when (e.ErrorCode == LIBSSH2_ERROR.EAGAIN)
{
//
// Retry operation.
//
}
catch (Exception e)
{
SshTraceSource.Log.TraceError(
"Socket I/O failed for {0}: {1}",
Thread.CurrentThread.Name,
e);
if ((currentOperation & Operation.Sending) != 0)
{
OnSendError(e);
}
else
{
//
// Consider it a receive error.
//
OnReceiveError(e);
}
//
// Bail out.
//
return;
}
} // while
} // nonblocking
}
}
}
catch (Exception e)
{
SshTraceSource.Log.TraceError(
"Connection failed for {0}: {1}",
Thread.CurrentThread.Name,
e);
OnConnectionError(e);
}
}
}