|
@@ -1,5 +1,4 @@
|
|
|
using System;
|
|
using System;
|
|
|
-using System.Diagnostics;
|
|
|
|
|
using System.Linq;
|
|
using System.Linq;
|
|
|
using System.Text;
|
|
using System.Text;
|
|
|
using System.Net;
|
|
using System.Net;
|
|
@@ -14,7 +13,7 @@ namespace Renci.SshNet
|
|
|
public partial class ForwardedPortDynamic
|
|
public partial class ForwardedPortDynamic
|
|
|
{
|
|
{
|
|
|
private Socket _listener;
|
|
private Socket _listener;
|
|
|
- private int _pendingRequests;
|
|
|
|
|
|
|
+ private CountdownEvent _pendingRequestsCountdown;
|
|
|
|
|
|
|
|
partial void InternalStart()
|
|
partial void InternalStart()
|
|
|
{
|
|
{
|
|
@@ -39,13 +38,14 @@ namespace Renci.SshNet
|
|
|
Session.Disconnected += Session_Disconnected;
|
|
Session.Disconnected += Session_Disconnected;
|
|
|
|
|
|
|
|
_listenerCompleted = new ManualResetEvent(false);
|
|
_listenerCompleted = new ManualResetEvent(false);
|
|
|
|
|
+ InitializePendingRequestsCountdown();
|
|
|
|
|
|
|
|
ThreadAbstraction.ExecuteThread(() =>
|
|
ThreadAbstraction.ExecuteThread(() =>
|
|
|
{
|
|
{
|
|
|
try
|
|
try
|
|
|
{
|
|
{
|
|
|
#if FEATURE_SOCKET_EAP
|
|
#if FEATURE_SOCKET_EAP
|
|
|
- StartAccept();
|
|
|
|
|
|
|
+ StartAccept(null);
|
|
|
#elif FEATURE_SOCKET_APM
|
|
#elif FEATURE_SOCKET_APM
|
|
|
_listener.BeginAccept(AcceptCallback, _listener);
|
|
_listener.BeginAccept(AcceptCallback, _listener);
|
|
|
#elif FEATURE_SOCKET_TAP
|
|
#elif FEATURE_SOCKET_TAP
|
|
@@ -53,9 +53,6 @@ namespace Renci.SshNet
|
|
|
#else
|
|
#else
|
|
|
#error Accepting new socket connections is not implemented.
|
|
#error Accepting new socket connections is not implemented.
|
|
|
#endif
|
|
#endif
|
|
|
-
|
|
|
|
|
- // wait until listener is stopped
|
|
|
|
|
- _listenerCompleted.WaitOne();
|
|
|
|
|
}
|
|
}
|
|
|
catch (ObjectDisposedException)
|
|
catch (ObjectDisposedException)
|
|
|
{
|
|
{
|
|
@@ -64,48 +61,57 @@ namespace Renci.SshNet
|
|
|
//
|
|
//
|
|
|
// As we start accepting connection on a separate thread, this is possible
|
|
// As we start accepting connection on a separate thread, this is possible
|
|
|
// when the listener is stopped right after it was started.
|
|
// when the listener is stopped right after it was started.
|
|
|
-
|
|
|
|
|
- // mark listener stopped
|
|
|
|
|
- _listenerCompleted.Set();
|
|
|
|
|
}
|
|
}
|
|
|
catch (Exception ex)
|
|
catch (Exception ex)
|
|
|
{
|
|
{
|
|
|
RaiseExceptionEvent(ex);
|
|
RaiseExceptionEvent(ex);
|
|
|
-
|
|
|
|
|
- // mark listener stopped
|
|
|
|
|
- _listenerCompleted.Set();
|
|
|
|
|
}
|
|
}
|
|
|
- finally
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // wait until listener is stopped
|
|
|
|
|
+ _listenerCompleted.WaitOne();
|
|
|
|
|
+
|
|
|
|
|
+ var session = Session;
|
|
|
|
|
+ if (session != null)
|
|
|
{
|
|
{
|
|
|
- var session = Session;
|
|
|
|
|
- if (session != null)
|
|
|
|
|
- {
|
|
|
|
|
- session.ErrorOccured -= Session_ErrorOccured;
|
|
|
|
|
- session.Disconnected -= Session_Disconnected;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ session.ErrorOccured -= Session_ErrorOccured;
|
|
|
|
|
+ session.Disconnected -= Session_Disconnected;
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void Session_Disconnected(object sender, EventArgs e)
|
|
private void Session_Disconnected(object sender, EventArgs e)
|
|
|
{
|
|
{
|
|
|
- StopListener();
|
|
|
|
|
|
|
+ if (IsStarted)
|
|
|
|
|
+ {
|
|
|
|
|
+ StopListener();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
|
|
private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
|
|
|
{
|
|
{
|
|
|
- StopListener();
|
|
|
|
|
|
|
+ if (IsStarted)
|
|
|
|
|
+ {
|
|
|
|
|
+ StopListener();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#if FEATURE_SOCKET_EAP
|
|
#if FEATURE_SOCKET_EAP
|
|
|
- private void StartAccept()
|
|
|
|
|
|
|
+ private void StartAccept(SocketAsyncEventArgs e)
|
|
|
{
|
|
{
|
|
|
- var args = new SocketAsyncEventArgs();
|
|
|
|
|
- args.Completed += AcceptCompleted;
|
|
|
|
|
|
|
+ if (e == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ e = new SocketAsyncEventArgs();
|
|
|
|
|
+ e.Completed += AcceptCompleted;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // clear the socket as we're reusing the context object
|
|
|
|
|
+ e.AcceptSocket = null;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (!_listener.AcceptAsync(args))
|
|
|
|
|
|
|
+ if (!_listener.AcceptAsync(e))
|
|
|
{
|
|
{
|
|
|
- AcceptCompleted(null, args);
|
|
|
|
|
|
|
+ AcceptCompleted(null, e);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -117,19 +123,22 @@ namespace Renci.SshNet
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // capture client socket
|
|
|
|
|
+ var clientSocket = acceptAsyncEventArgs.AcceptSocket;
|
|
|
|
|
+
|
|
|
if (acceptAsyncEventArgs.SocketError != SocketError.Success)
|
|
if (acceptAsyncEventArgs.SocketError != SocketError.Success)
|
|
|
{
|
|
{
|
|
|
// accept new connection
|
|
// accept new connection
|
|
|
- StartAccept();
|
|
|
|
|
- // dispose broken socket
|
|
|
|
|
- acceptAsyncEventArgs.AcceptSocket.Dispose();
|
|
|
|
|
|
|
+ StartAccept(acceptAsyncEventArgs);
|
|
|
|
|
+ // dispose broken client socket
|
|
|
|
|
+ CloseClientSocket(clientSocket);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// accept new connection
|
|
// accept new connection
|
|
|
- StartAccept();
|
|
|
|
|
|
|
+ StartAccept(acceptAsyncEventArgs);
|
|
|
// process connection
|
|
// process connection
|
|
|
- ProcessAccept(acceptAsyncEventArgs.AcceptSocket);
|
|
|
|
|
|
|
+ ProcessAccept(clientSocket);
|
|
|
}
|
|
}
|
|
|
#elif FEATURE_SOCKET_APM
|
|
#elif FEATURE_SOCKET_APM
|
|
|
private void AcceptCallback(IAsyncResult ar)
|
|
private void AcceptCallback(IAsyncResult ar)
|
|
@@ -159,7 +168,12 @@ namespace Renci.SshNet
|
|
|
|
|
|
|
|
private void ProcessAccept(Socket remoteSocket)
|
|
private void ProcessAccept(Socket remoteSocket)
|
|
|
{
|
|
{
|
|
|
- Interlocked.Increment(ref _pendingRequests);
|
|
|
|
|
|
|
+ // capture the countdown event that we're adding a count to, as we need to make sure that we'll be signaling
|
|
|
|
|
+ // that same instance; the instance field for the countdown event is re-initialized when the port is restarted
|
|
|
|
|
+ // and at that time they may still be pending requests
|
|
|
|
|
+ var pendingRequestsCountdown = _pendingRequestsCountdown;
|
|
|
|
|
+
|
|
|
|
|
+ pendingRequestsCountdown.AddCount();
|
|
|
|
|
|
|
|
#if DEBUG_GERT
|
|
#if DEBUG_GERT
|
|
|
Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + remoteSocket.RemoteEndPoint + " | ForwardedPortDynamic.ProcessAccept | " + DateTime.Now.ToString("hh:mm:ss.fff"));
|
|
Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + remoteSocket.RemoteEndPoint + " | ForwardedPortDynamic.ProcessAccept | " + DateTime.Now.ToString("hh:mm:ss.fff"));
|
|
@@ -180,7 +194,7 @@ namespace Renci.SshNet
|
|
|
{
|
|
{
|
|
|
if (!HandleSocks(channel, remoteSocket, Session.ConnectionInfo.Timeout))
|
|
if (!HandleSocks(channel, remoteSocket, Session.ConnectionInfo.Timeout))
|
|
|
{
|
|
{
|
|
|
- CloseSocket(remoteSocket);
|
|
|
|
|
|
|
+ CloseClientSocket(remoteSocket);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -191,6 +205,7 @@ namespace Renci.SshNet
|
|
|
catch (SocketException ex)
|
|
catch (SocketException ex)
|
|
|
{
|
|
{
|
|
|
Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + ex.SocketErrorCode + " | " + DateTime.Now.ToString("hh:mm:ss.fff") + " | " + ex);
|
|
Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + ex.SocketErrorCode + " | " + DateTime.Now.ToString("hh:mm:ss.fff") + " | " + ex);
|
|
|
|
|
+ throw;
|
|
|
}
|
|
}
|
|
|
#endif // DEBUG_GERT
|
|
#endif // DEBUG_GERT
|
|
|
finally
|
|
finally
|
|
@@ -211,7 +226,7 @@ namespace Renci.SshNet
|
|
|
RaiseExceptionEvent(ex);
|
|
RaiseExceptionEvent(ex);
|
|
|
#endif // DEBUG_GERT
|
|
#endif // DEBUG_GERT
|
|
|
}
|
|
}
|
|
|
- CloseSocket(remoteSocket);
|
|
|
|
|
|
|
+ CloseClientSocket(remoteSocket);
|
|
|
}
|
|
}
|
|
|
catch (Exception exp)
|
|
catch (Exception exp)
|
|
|
{
|
|
{
|
|
@@ -219,11 +234,18 @@ namespace Renci.SshNet
|
|
|
Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + exp + " | " + DateTime.Now.ToString("hh:mm:ss.fff"));
|
|
Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + exp + " | " + DateTime.Now.ToString("hh:mm:ss.fff"));
|
|
|
#endif // DEBUG_GERT
|
|
#endif // DEBUG_GERT
|
|
|
RaiseExceptionEvent(exp);
|
|
RaiseExceptionEvent(exp);
|
|
|
- CloseSocket(remoteSocket);
|
|
|
|
|
|
|
+ CloseClientSocket(remoteSocket);
|
|
|
}
|
|
}
|
|
|
finally
|
|
finally
|
|
|
{
|
|
{
|
|
|
- Interlocked.Decrement(ref _pendingRequests);
|
|
|
|
|
|
|
+ // take into account that countdown event has since been disposed (after waiting for a given timeout)
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ pendingRequestsCountdown.Signal();
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (ObjectDisposedException)
|
|
|
|
|
+ {
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -231,7 +253,7 @@ namespace Renci.SshNet
|
|
|
{
|
|
{
|
|
|
// create eventhandler which is to be invoked to interrupt a blocking receive
|
|
// create eventhandler which is to be invoked to interrupt a blocking receive
|
|
|
// when we're closing the forwarded port
|
|
// when we're closing the forwarded port
|
|
|
- EventHandler closeClientSocket = (_, args) => CloseSocket(remoteSocket);
|
|
|
|
|
|
|
+ EventHandler closeClientSocket = (_, args) => CloseClientSocket(remoteSocket);
|
|
|
|
|
|
|
|
Closing += closeClientSocket;
|
|
Closing += closeClientSocket;
|
|
|
|
|
|
|
@@ -273,61 +295,53 @@ namespace Renci.SshNet
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static void CloseSocket(Socket socket)
|
|
|
|
|
|
|
+ private static void CloseClientSocket(Socket clientSocket)
|
|
|
{
|
|
{
|
|
|
#if DEBUG_GERT
|
|
#if DEBUG_GERT
|
|
|
Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ForwardedPortDynamic.CloseSocket | " + DateTime.Now.ToString("hh:mm:ss.fff"));
|
|
Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ForwardedPortDynamic.CloseSocket | " + DateTime.Now.ToString("hh:mm:ss.fff"));
|
|
|
#endif // DEBUG_GERT
|
|
#endif // DEBUG_GERT
|
|
|
|
|
|
|
|
- if (socket.Connected)
|
|
|
|
|
|
|
+ if (clientSocket.Connected)
|
|
|
{
|
|
{
|
|
|
- socket.Shutdown(SocketShutdown.Both);
|
|
|
|
|
- socket.Dispose();
|
|
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ clientSocket.Shutdown(SocketShutdown.Both);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception)
|
|
|
|
|
+ {
|
|
|
|
|
+ // ignore exception when client socket was already closed
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ clientSocket.Dispose();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
partial void StopListener()
|
|
partial void StopListener()
|
|
|
{
|
|
{
|
|
|
- // if the port is not started then there's nothing to stop
|
|
|
|
|
- if (!IsStarted)
|
|
|
|
|
- return;
|
|
|
|
|
-
|
|
|
|
|
// close listener socket
|
|
// close listener socket
|
|
|
- _listener.Dispose();
|
|
|
|
|
|
|
+ var listener = _listener;
|
|
|
|
|
+ if (listener != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ listener.Dispose();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// allow listener thread to stop
|
|
// allow listener thread to stop
|
|
|
- _listenerCompleted.Set();
|
|
|
|
|
|
|
+ var listenerCompleted = _listenerCompleted;
|
|
|
|
|
+ if (listenerCompleted != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ listenerCompleted.Set();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
- /// Waits for pending requests to finish, and channels to close.
|
|
|
|
|
|
|
+ /// Waits for pending requests to finish.
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
- /// <param name="timeout">The maximum time to wait for the forwarded port to stop.</param>
|
|
|
|
|
|
|
+ /// <param name="timeout">The maximum time to wait for the pending requests to finish.</param>
|
|
|
partial void InternalStop(TimeSpan timeout)
|
|
partial void InternalStop(TimeSpan timeout)
|
|
|
{
|
|
{
|
|
|
- if (timeout == TimeSpan.Zero)
|
|
|
|
|
- return;
|
|
|
|
|
-
|
|
|
|
|
- var stopWatch = new Stopwatch();
|
|
|
|
|
- stopWatch.Start();
|
|
|
|
|
-
|
|
|
|
|
- // break out of loop when one of the following conditions are met:
|
|
|
|
|
- // * the forwarded port is restarted
|
|
|
|
|
- // * all pending requests have been processed and corresponding channel are closed
|
|
|
|
|
- // * the specified timeout has elapsed
|
|
|
|
|
- while (!IsStarted)
|
|
|
|
|
- {
|
|
|
|
|
- // break out of loop when all pending requests have been processed
|
|
|
|
|
- if (Interlocked.CompareExchange(ref _pendingRequests, 0, 0) == 0)
|
|
|
|
|
- break;
|
|
|
|
|
- // break out of loop when specified timeout has elapsed
|
|
|
|
|
- if (stopWatch.Elapsed >= timeout && timeout != SshNet.Session.InfiniteTimeSpan)
|
|
|
|
|
- break;
|
|
|
|
|
- // give channels time to process pending requests
|
|
|
|
|
- ThreadAbstraction.Sleep(50);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- stopWatch.Stop();
|
|
|
|
|
|
|
+ _pendingRequestsCountdown.Signal();
|
|
|
|
|
+ _pendingRequestsCountdown.Wait(timeout);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
partial void InternalDispose(bool disposing)
|
|
partial void InternalDispose(bool disposing)
|
|
@@ -340,6 +354,13 @@ namespace Renci.SshNet
|
|
|
_listener = null;
|
|
_listener = null;
|
|
|
listener.Dispose();
|
|
listener.Dispose();
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ var pendingRequestsCountdown = _pendingRequestsCountdown;
|
|
|
|
|
+ if (pendingRequestsCountdown != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ _pendingRequestsCountdown = null;
|
|
|
|
|
+ pendingRequestsCountdown.Dispose();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -612,6 +633,28 @@ namespace Renci.SshNet
|
|
|
RaiseExceptionEvent(e.Exception);
|
|
RaiseExceptionEvent(e.Exception);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Initializes the <see cref="CountdownEvent"/>.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <remarks>
|
|
|
|
|
+ /// <para>
|
|
|
|
|
+ /// When the port is started for the first time, a <see cref="CountdownEvent"/> is created with an initial count
|
|
|
|
|
+ /// of <c>1</c>.
|
|
|
|
|
+ /// </para>
|
|
|
|
|
+ /// <para>
|
|
|
|
|
+ /// On subsequent (re)starts, we'll dispose the current <see cref="CountdownEvent"/> and create a new one with
|
|
|
|
|
+ /// initial count of <c>1</c>.
|
|
|
|
|
+ /// </para>
|
|
|
|
|
+ /// </remarks>
|
|
|
|
|
+ private void InitializePendingRequestsCountdown()
|
|
|
|
|
+ {
|
|
|
|
|
+ var original = Interlocked.Exchange(ref _pendingRequestsCountdown, new CountdownEvent(1));
|
|
|
|
|
+ if (original != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ original.Dispose();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Reads a null terminated string from a socket.
|
|
/// Reads a null terminated string from a socket.
|
|
|
/// </summary>
|
|
/// </summary>
|