Browse Source

Removed some (conditional) debug code.
Introduce (internal) ForwardedPortStatus to control state transition.
Align ForwardedPortDynamic and ForwardedPortLocal.

drieseng 9 năm trước cách đây
mục cha
commit
90145be344
25 tập tin đã thay đổi với 859 bổ sung641 xóa
  1. 4 1
      src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj
  2. 4 1
      src/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj
  3. 2 1
      src/Renci.SshNet.Tests/Classes/CipherInfoTest.cs
  4. 0 2
      src/Renci.SshNet.Tests/Classes/Compression/CompressorTest.cs
  5. 74 45
      src/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortStarted_ChannelBound.cs
  6. 227 0
      src/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_SessionErrorOccurred_ChannelBound.cs
  7. 33 31
      src/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortStarted_ChannelBound.cs
  8. 30 37
      src/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortStarted_ChannelBound.cs
  9. 30 36
      src/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortStarted_ChannelBound.cs
  10. 8 0
      src/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortStarted_ChannelBound.cs
  11. 5 4
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileAttributesTest.cs
  12. 3 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpListDirectoryAsyncResultTest.cs
  13. 1 0
      src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
  14. 3 0
      src/Renci.SshNet.UAP10/Renci.SshNet.UAP10.csproj
  15. 4 1
      src/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj
  16. 6 17
      src/Renci.SshNet/Channels/Channel.cs
  17. 2 56
      src/Renci.SshNet/Channels/ChannelDirectTcpip.cs
  18. 2 6
      src/Renci.SshNet/ForwardedPort.cs
  19. 129 224
      src/Renci.SshNet/ForwardedPortDynamic.NET.cs
  20. 25 20
      src/Renci.SshNet/ForwardedPortDynamic.cs
  21. 104 117
      src/Renci.SshNet/ForwardedPortLocal.NET.cs
  22. 24 18
      src/Renci.SshNet/ForwardedPortLocal.cs
  23. 30 22
      src/Renci.SshNet/ForwardedPortRemote.cs
  24. 108 0
      src/Renci.SshNet/ForwardedPortStatus.cs
  25. 1 0
      src/Renci.SshNet/Renci.SshNet.csproj

+ 4 - 1
src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -282,6 +282,9 @@
     <Compile Include="..\Renci.SshNet\ForwardedPortRemote.cs">
       <Link>ForwardedPortRemote.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ForwardedPortStatus.cs">
+      <Link>ForwardedPortStatus.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\HashInfo.cs">
       <Link>HashInfo.cs</Link>
     </Compile>
@@ -916,7 +919,7 @@
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 4 - 1
src/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj

@@ -306,6 +306,9 @@
     <Compile Include="..\Renci.SshNet\ForwardedPortRemote.cs">
       <Link>ForwardedPortRemote.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ForwardedPortStatus.cs">
+      <Link>ForwardedPortStatus.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\HashInfo.cs">
       <Link>HashInfo.cs</Link>
     </Compile>
@@ -920,7 +923,7 @@
       <FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}">
         <SilverlightProjectProperties />
       </FlavorProperties>
-      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 2 - 1
src/Renci.SshNet.Tests/Classes/CipherInfoTest.cs

@@ -15,7 +15,8 @@ namespace Renci.SshNet.Tests.Classes
         /// <summary>
         ///A test for CipherInfo Constructor
         ///</summary>
-        [TestMethod()]
+        [TestMethod]
+        [Ignore] // placeholder
         public void CipherInfoConstructorTest()
         {
             int keySize = 0; // TODO: Initialize to an appropriate value

+ 0 - 2
src/Renci.SshNet.Tests/Classes/Compression/CompressorTest.cs

@@ -1,7 +1,5 @@
 using Renci.SshNet.Compression;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System;
-using Renci.SshNet;
 using Renci.SshNet.Tests.Common;
 
 namespace Renci.SshNet.Tests.Classes.Compression

+ 74 - 45
src/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortStarted_ChannelBound.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Globalization;
 using System.Linq;
 using System.Net;
@@ -9,6 +8,7 @@ using System.Text;
 using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
+using Renci.SshNet.Abstractions;
 using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
@@ -27,8 +27,9 @@ namespace Renci.SshNet.Tests.Classes
         private Socket _client;
         private IPEndPoint _remoteEndpoint;
         private string _userName;
-        private TimeSpan _expectedElapsedTime;
-        private TimeSpan _elapsedTimeOfStop;
+        private TimeSpan _bindSleepTime;
+        private ManualResetEvent _channelBindStarted;
+        private ManualResetEvent _channelBindCompleted;
 
         [TestInitialize]
         public void Setup()
@@ -50,9 +51,26 @@ namespace Renci.SshNet.Tests.Classes
                 _forwardedPort.Dispose();
                 _forwardedPort = null;
             }
+            if (_channelBindStarted != null)
+            {
+                _channelBindStarted.Dispose();
+                _channelBindStarted = null;
+            }
+            if (_channelBindCompleted != null)
+            {
+                _channelBindCompleted.Dispose();
+                _channelBindCompleted = null;
+            }
         }
 
-        protected void Arrange()
+        private void CreateMocks()
+        {
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _channelMock = new Mock<IChannelDirectTcpip>(MockBehavior.Strict);
+        }
+
+        private void SetupData()
         {
             var random = new Random();
 
@@ -60,61 +78,62 @@ namespace Renci.SshNet.Tests.Classes
             _exceptionRegister = new List<ExceptionEventArgs>();
             _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
             _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
-            _expectedElapsedTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
+            _bindSleepTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
             _userName = random.Next().ToString(CultureInfo.InvariantCulture);
-            _forwardedPort = new ForwardedPortDynamic(_endpoint.Address.ToString(), (uint)_endpoint.Port);
+            _channelBindStarted = new ManualResetEvent(false);
+            _channelBindCompleted = new ManualResetEvent(false);
 
-            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
-            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
-            _channelMock = new Mock<IChannelDirectTcpip>(MockBehavior.Strict);
+            _forwardedPort = new ForwardedPortDynamic(_endpoint.Address.ToString(), (uint)_endpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
 
-            Socket handlerSocket = null;
+            _client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
+            {
+                ReceiveTimeout = 500,
+                SendTimeout = 500,
+                SendBufferSize = 0
+            };
+        }
 
+        private void SetupMocks()
+        {
             _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(15));
             _sessionMock.Setup(p => p.IsConnected).Returns(true);
             _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
             _sessionMock.Setup(p => p.CreateChannelDirectTcpip()).Returns(_channelMock.Object);
-            _channelMock.Setup(p => p.Open(_remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port, _forwardedPort, It.IsAny<Socket>())).Callback<string, uint, IForwardedPort, Socket>((address, port, forwardedPort, socket) => handlerSocket = socket);
+            _channelMock.Setup(p => p.Open(_remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port, _forwardedPort, It.IsAny<Socket>()));
             _channelMock.Setup(p => p.IsOpen).Returns(true);
             _channelMock.Setup(p => p.Bind()).Callback(() =>
                 {
-                    Thread.Sleep(_expectedElapsedTime);
-                    if (handlerSocket != null && handlerSocket.Connected)
-                        handlerSocket.Shutdown(SocketShutdown.Both);
+                    _channelBindStarted.Set();
+                    Thread.Sleep(_bindSleepTime);
+                    _channelBindCompleted.Set();
                 });
             _channelMock.Setup(p => p.Close());
             _channelMock.Setup(p => p.Dispose());
+        }
+
+        protected void Arrange()
+        {
+            CreateMocks();
+            SetupData();
+            SetupMocks();
 
-            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
-            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
-            _forwardedPort.Session = _sessionMock.Object;
             _forwardedPort.Start();
 
-            _client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
-                {
-                    ReceiveTimeout = 500,
-                    SendTimeout = 500,
-                    SendBufferSize = 0
-                };
             EstablishSocks4Connection(_client);
         }
 
         protected void Act()
         {
-            var stopwatch = new Stopwatch();
-            stopwatch.Start();
-
             _forwardedPort.Dispose();
-
-            stopwatch.Stop();
-            _elapsedTimeOfStop = stopwatch.Elapsed;
         }
 
         [TestMethod]
-        public void StopShouldBlockUntilBoundChannelHasClosed()
+        public void ShouldBlockUntilBindHasCompleted()
         {
-            Assert.IsTrue(_elapsedTimeOfStop >= _expectedElapsedTime);
-            Assert.IsTrue(_elapsedTimeOfStop < _expectedElapsedTime.Add(TimeSpan.FromMilliseconds(200)));
+            Assert.IsTrue(_channelBindCompleted.WaitOne(0));
         }
 
         [TestMethod]
@@ -141,17 +160,11 @@ namespace Renci.SshNet.Tests.Classes
         }
 
         [TestMethod]
-        public void ExistingConnectionShouldBeClosed()
+        public void BoundClientShouldNotBeClosed()
         {
-            try
-            {
-                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
-                Assert.Fail();
-            }
-            catch (SocketException ex)
-            {
-                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
-            }
+            // the forwarded port itself does not close the client connection when the channel is closed properly
+            // it's the channel that will take care of closing the client connection
+            _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
         }
 
         [TestMethod]
@@ -163,7 +176,7 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod]
         public void ExceptionShouldNotHaveFired()
         {
-            Assert.AreEqual(0, _exceptionRegister.Count);
+            Assert.AreEqual(0, _exceptionRegister.Count, GetReportedExceptions());
         }
 
         [TestMethod]
@@ -214,8 +227,24 @@ namespace Renci.SshNet.Tests.Classes
             // terminate user name with null
             client.Send(new byte[] { 0x00 }, 0, 1, SocketFlags.None);
 
-            var buffer = new byte[16];
-            client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
+            var buffer = new byte[8];
+            var bytesRead = SocketAbstraction.Read(client, buffer, 0, buffer.Length, TimeSpan.FromMilliseconds(500));
+            Assert.AreEqual(buffer.Length, bytesRead);
+
+            // wait until SOCKS client is bound to channel
+            Assert.IsTrue(_channelBindStarted.WaitOne(TimeSpan.FromMilliseconds(200)));
+        }
+
+        private string GetReportedExceptions()
+        {
+            if (_exceptionRegister.Count == 0)
+                return string.Empty;
+
+            string reportedExceptions = string.Empty;
+            foreach (var exceptionEvent in _exceptionRegister)
+                reportedExceptions += exceptionEvent.Exception.ToString();
+
+            return reportedExceptions;
         }
     }
 }

+ 227 - 0
src/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_SessionErrorOccurred_ChannelBound.cs

@@ -0,0 +1,227 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Abstractions;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_SessionErrorOccurred_ChannelBound
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private Exception _sessionException;
+        private IPEndPoint _endpoint;
+        private Socket _client;
+        private IPEndPoint _remoteEndpoint;
+        private string _userName;
+        private TimeSpan _bindSleepTime;
+        private ManualResetEvent _channelBindStarted;
+        private ManualResetEvent _channelBindCompleted;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_client != null)
+            {
+                _client.Dispose();
+                _client = null;
+            }
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+            if (_channelBindStarted != null)
+            {
+                _channelBindStarted.Dispose();
+                _channelBindStarted = null;
+            }
+            if (_channelBindCompleted != null)
+            {
+                _channelBindCompleted.Dispose();
+                _channelBindCompleted = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _bindSleepTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
+            _userName = random.Next().ToString(CultureInfo.InvariantCulture);
+            _forwardedPort = new ForwardedPortDynamic(_endpoint.Address.ToString(), (uint)_endpoint.Port);
+            _sessionException = new Exception();
+            _channelBindStarted = new ManualResetEvent(false);
+            _channelBindCompleted = new ManualResetEvent(false);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _channelMock = new Mock<IChannelDirectTcpip>(MockBehavior.Strict);
+
+            _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(15));
+            _sessionMock.Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
+            _sessionMock.Setup(p => p.CreateChannelDirectTcpip()).Returns(_channelMock.Object);
+            _channelMock.Setup(p => p.Open(_remoteEndpoint.Address.ToString(), (uint) _remoteEndpoint.Port, _forwardedPort, It.IsAny<Socket>()));
+            _channelMock.Setup(p => p.IsOpen).Returns(true);
+            _channelMock.Setup(p => p.Bind()).Callback(() =>
+                {
+                    _channelBindStarted.Set();
+                    Thread.Sleep(_bindSleepTime);
+                    _channelBindCompleted.Set();
+                });
+            _channelMock.Setup(p => p.Close());
+            _channelMock.Setup(p => p.Dispose());
+
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+
+            _client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
+            {
+                ReceiveTimeout = 500,
+                SendTimeout = 500,
+                SendBufferSize = 0
+            };
+            EstablishSocks4Connection(_client);
+        }
+
+        protected void Act()
+        {
+            _sessionMock.Raise(p => p.ErrorOccured += null, new ExceptionEventArgs(_sessionException));
+        }
+
+        [TestMethod]
+        public void ShouldBlockUntilBindHasCompleted()
+        {
+            Assert.IsTrue(_channelBindCompleted.WaitOne(0));
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRefuseNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_endpoint);
+                    Assert.Fail();
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void BoundClientShouldNotBeClosed()
+        {
+            // the forwarded port itself does not close the client connection when the channel is closed properly
+            // it's the channel that will take care of closing the client connection
+            _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldHaveFiredOne()
+        {
+            Assert.AreEqual(1, _exceptionRegister.Count);
+            Assert.IsNotNull(_exceptionRegister[0]);
+            Assert.AreSame(_sessionException, _exceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OpenOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(
+                p =>
+                    p.Open(_remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port, _forwardedPort,
+                        It.IsAny<Socket>()), Times.Once);
+        }
+
+        [TestMethod]
+        public void BindOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Bind(), Times.Once);
+        }
+
+        [TestMethod]
+        public void CloseOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Close(), Times.Once);
+        }
+
+        [TestMethod]
+        public void DisposeOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Dispose(), Times.Once);
+        }
+
+        private void EstablishSocks4Connection(Socket client)
+        {
+            var userNameBytes = Encoding.ASCII.GetBytes(_userName);
+            var addressBytes = _remoteEndpoint.Address.GetAddressBytes();
+            var portBytes = BitConverter.GetBytes((ushort)_remoteEndpoint.Port).Reverse().ToArray();
+
+            _client.Connect(_endpoint);
+
+            // send SOCKS version
+            client.Send(new byte[] { 0x04 }, 0, 1, SocketFlags.None);
+            // send command byte
+            client.Send(new byte[] { 0x00 }, 0, 1, SocketFlags.None);
+            // send port
+            client.Send(portBytes, 0, portBytes.Length, SocketFlags.None);
+            // send address
+            client.Send(addressBytes, 0, addressBytes.Length, SocketFlags.None);
+            // send user name
+            client.Send(userNameBytes, 0, userNameBytes.Length, SocketFlags.None);
+            // terminate user name with null
+            client.Send(new byte[] { 0x00 }, 0, 1, SocketFlags.None);
+
+            var buffer = new byte[8];
+            var bytesRead = SocketAbstraction.Read(client, buffer, 0, buffer.Length, TimeSpan.FromMilliseconds(500));
+            Assert.AreEqual(buffer.Length, bytesRead);
+
+            // wait until SOCKS client is bound to channel
+            Assert.IsTrue(_channelBindStarted.WaitOne(TimeSpan.FromMilliseconds(200)));
+        }
+    }
+}

+ 33 - 31
src/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortStarted_ChannelBound.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Globalization;
 using System.Linq;
 using System.Net;
@@ -9,6 +8,7 @@ using System.Text;
 using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
+using Renci.SshNet.Abstractions;
 using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
@@ -27,8 +27,9 @@ namespace Renci.SshNet.Tests.Classes
         private Socket _client;
         private IPEndPoint _remoteEndpoint;
         private string _userName;
-        private TimeSpan _expectedElapsedTime;
-        private TimeSpan _elapsedTimeOfStop;
+        private TimeSpan _bindSleepTime;
+        private ManualResetEvent _channelBindStarted;
+        private ManualResetEvent _channelBindCompleted;
 
         [TestInitialize]
         public void Setup()
@@ -50,6 +51,16 @@ namespace Renci.SshNet.Tests.Classes
                 _forwardedPort.Dispose();
                 _forwardedPort = null;
             }
+            if (_channelBindStarted != null)
+            {
+                _channelBindStarted.Dispose();
+                _channelBindStarted = null;
+            }
+            if (_channelBindCompleted != null)
+            {
+                _channelBindCompleted.Dispose();
+                _channelBindCompleted = null;
+            }
         }
 
         protected void Arrange()
@@ -60,8 +71,10 @@ namespace Renci.SshNet.Tests.Classes
             _exceptionRegister = new List<ExceptionEventArgs>();
             _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
             _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
-            _expectedElapsedTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
+            _bindSleepTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
             _userName = random.Next().ToString(CultureInfo.InvariantCulture);
+            _channelBindStarted = new ManualResetEvent(false);
+            _channelBindCompleted = new ManualResetEvent(false);
 
             _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
@@ -69,19 +82,17 @@ namespace Renci.SshNet.Tests.Classes
 
             _forwardedPort = new ForwardedPortDynamic(_endpoint.Address.ToString(), (uint) _endpoint.Port);
 
-            Socket handlerSocket = null;
-
             _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(15));
             _sessionMock.Setup(p => p.IsConnected).Returns(true);
             _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
             _sessionMock.Setup(p => p.CreateChannelDirectTcpip()).Returns(_channelMock.Object);
-            _channelMock.Setup(p => p.Open(_remoteEndpoint.Address.ToString(), (uint) _remoteEndpoint.Port, _forwardedPort, It.IsAny<Socket>())).Callback<string, uint, IForwardedPort, Socket>((address, port, forwardedPort, socket) => handlerSocket = socket);
+            _channelMock.Setup(p => p.Open(_remoteEndpoint.Address.ToString(), (uint) _remoteEndpoint.Port, _forwardedPort, It.IsAny<Socket>()));
             _channelMock.Setup(p => p.IsOpen).Returns(true);
             _channelMock.Setup(p => p.Bind()).Callback(() =>
                 {
-                    Thread.Sleep(_expectedElapsedTime);
-                    if (handlerSocket != null && handlerSocket.Connected)
-                        handlerSocket.Shutdown(SocketShutdown.Both);
+                    _channelBindStarted.Set();
+                    Thread.Sleep(_bindSleepTime);
+                    _channelBindCompleted.Set();
                 });
             _channelMock.Setup(p => p.Close());
             _channelMock.Setup(p => p.Dispose());
@@ -102,20 +113,13 @@ namespace Renci.SshNet.Tests.Classes
 
         protected void Act()
         {
-            var stopwatch = new Stopwatch();
-            stopwatch.Start();
-
             _forwardedPort.Stop();
-
-            stopwatch.Stop();
-            _elapsedTimeOfStop = stopwatch.Elapsed;
         }
 
         [TestMethod]
-        public void StopShouldBlockUntilBoundChannelHasClosed()
+        public void ShouldBlockUntilBindHasCompleted()
         {
-            Assert.IsTrue(_elapsedTimeOfStop >=_expectedElapsedTime);
-            Assert.IsTrue(_elapsedTimeOfStop < _expectedElapsedTime.Add(TimeSpan.FromMilliseconds(200)));
+            Assert.IsTrue(_channelBindCompleted.WaitOne(0));
         }
 
         [TestMethod]
@@ -142,17 +146,11 @@ namespace Renci.SshNet.Tests.Classes
         }
 
         [TestMethod]
-        public void ExistingConnectionShouldBeClosed()
+        public void BoundClientShouldNotBeClosed()
         {
-            try
-            {
-                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
-                Assert.Fail();
-            }
-            catch (SocketException ex)
-            {
-                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
-            }
+            // the forwarded port itself does not close the client connection when the channel is closed properly
+            // it's the channel that will take care of closing the client connection
+            _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
         }
 
         [TestMethod]
@@ -200,8 +198,12 @@ namespace Renci.SshNet.Tests.Classes
             // terminate user name with null
             client.Send(new byte[] { 0x00 }, 0, 1, SocketFlags.None);
 
-            var buffer = new byte[16];
-            client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
+            var buffer = new byte[8];
+            var bytesRead = SocketAbstraction.Read(client, buffer, 0, buffer.Length, TimeSpan.FromMilliseconds(500));
+            Assert.AreEqual(buffer.Length, bytesRead);
+
+            // wait until SOCKS client is bound to channel
+            Assert.IsTrue(_channelBindStarted.WaitOne(TimeSpan.FromMilliseconds(200)));
         }
     }
 }

+ 30 - 37
src/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortStarted_ChannelBound.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Net;
 using System.Net.Sockets;
 using System.Threading;
@@ -23,10 +22,9 @@ namespace Renci.SshNet.Tests.Classes
         private IPEndPoint _localEndpoint;
         private IPEndPoint _remoteEndpoint;
         private Socket _client;
-        private TimeSpan _expectedElapsedTime;
-        private TimeSpan _elapsedTimeOfStop;
-        private Stopwatch _stopwatch;
-
+        private TimeSpan _bindSleepTime;
+        private ManualResetEvent _channelBindStarted;
+        private ManualResetEvent _channelBindCompleted;
 
         [TestInitialize]
         public void Setup()
@@ -48,6 +46,16 @@ namespace Renci.SshNet.Tests.Classes
                 _forwardedPort.Dispose();
                 _forwardedPort = null;
             }
+            if (_channelBindStarted != null)
+            {
+                _channelBindStarted.Dispose();
+                _channelBindStarted = null;
+            }
+            if (_channelBindCompleted != null)
+            {
+                _channelBindCompleted.Dispose();
+                _channelBindCompleted = null;
+            }
         }
 
         protected void Arrange()
@@ -56,28 +64,26 @@ namespace Renci.SshNet.Tests.Classes
             _closingRegister = new List<EventArgs>();
             _exceptionRegister = new List<ExceptionEventArgs>();
             _localEndpoint = new IPEndPoint(IPAddress.Loopback, 8122);
-            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"),
-                random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
-            _expectedElapsedTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
-            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port,
-                _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _bindSleepTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint) _localEndpoint.Port, _remoteEndpoint.Address.ToString(), (uint) _remoteEndpoint.Port);
+            _channelBindStarted = new ManualResetEvent(false);
+            _channelBindCompleted = new ManualResetEvent(false);
 
             _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
             _channelMock = new Mock<IChannelDirectTcpip>(MockBehavior.Strict);
 
-            Socket handlerSocket = null;
-
             _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(15));
             _sessionMock.Setup(p => p.IsConnected).Returns(true);
             _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
             _sessionMock.Setup(p => p.CreateChannelDirectTcpip()).Returns(_channelMock.Object);
-            _channelMock.Setup(p => p.Open(_forwardedPort.Host, _forwardedPort.Port, _forwardedPort, It.IsAny<Socket>())).Callback<string, uint, IForwardedPort, Socket>((address, port, forwardedPort, socket) => handlerSocket = socket);
+            _channelMock.Setup(p => p.Open(_forwardedPort.Host, _forwardedPort.Port, _forwardedPort, It.IsAny<Socket>()));
             _channelMock.Setup(p => p.Bind()).Callback(() =>
                 {
-                    Thread.Sleep(_expectedElapsedTime);
-                    if (handlerSocket != null && handlerSocket.Connected)
-                        handlerSocket.Shutdown(SocketShutdown.Both);
+                    _channelBindStarted.Set();
+                    Thread.Sleep(_bindSleepTime);
+                    _channelBindCompleted.Set();
                 });
             _channelMock.Setup(p => p.Close());
             _channelMock.Setup(p => p.Dispose());
@@ -94,28 +100,21 @@ namespace Renci.SshNet.Tests.Classes
                     SendBufferSize = 0
                 };
 
-            _stopwatch = new Stopwatch();
-            _stopwatch.Start();
-
             _client.Connect(_localEndpoint);
 
-            // give client socket time to establish connection
-            Thread.Sleep(50);
+            // wait for SOCKS client to bind to channel
+            Assert.IsTrue(_channelBindStarted.WaitOne(TimeSpan.FromMilliseconds(200)));
         }
 
         protected void Act()
         {
             _forwardedPort.Dispose();
-
-            _stopwatch.Stop();
-            _elapsedTimeOfStop = _stopwatch.Elapsed;
         }
 
         [TestMethod]
-        public void StopShouldBlockUntilBoundChannelHasClosed()
+        public void ShouldBlockUntilBindHasCompleted()
         {
-            Assert.IsTrue(_elapsedTimeOfStop >= _expectedElapsedTime, string.Format("Expected {0} or greater but was {1}.", _expectedElapsedTime.TotalMilliseconds, _elapsedTimeOfStop.TotalMilliseconds));
-            Assert.IsTrue(_elapsedTimeOfStop < _expectedElapsedTime.Add(TimeSpan.FromMilliseconds(200)));
+            Assert.IsTrue(_channelBindCompleted.WaitOne(0));
         }
 
         [TestMethod]
@@ -142,17 +141,11 @@ namespace Renci.SshNet.Tests.Classes
         }
 
         [TestMethod]
-        public void ExistingConnectionShouldBeClosed()
+        public void BoundClientShouldNotBeClosed()
         {
-            try
-            {
-                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
-                Assert.Fail();
-            }
-            catch (SocketException ex)
-            {
-                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
-            }
+            // the forwarded port itself does not close the client connection when the channel is closed properly
+            // it's the channel that will take care of closing the client connection
+            _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
         }
 
         [TestMethod]

+ 30 - 36
src/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortStarted_ChannelBound.cs

@@ -23,9 +23,9 @@ namespace Renci.SshNet.Tests.Classes
         private IPEndPoint _localEndpoint;
         private IPEndPoint _remoteEndpoint;
         private Socket _client;
-        private TimeSpan _expectedElapsedTime;
-        private TimeSpan _elapsedTimeOfStop;
-        private Stopwatch _stopwatch;
+        private TimeSpan _bindSleepTime;
+        private ManualResetEvent _channelBound;
+        private ManualResetEvent _channelBindCompleted;
 
         [TestInitialize]
         public void Setup()
@@ -47,6 +47,16 @@ namespace Renci.SshNet.Tests.Classes
                 _forwardedPort.Dispose();
                 _forwardedPort = null;
             }
+            if (_channelBound != null)
+            {
+                _channelBound.Dispose();
+                _channelBound = null;
+            }
+            if (_channelBindCompleted != null)
+            {
+                _channelBindCompleted.Dispose();
+                _channelBindCompleted = null;
+            }
         }
 
         protected void Arrange()
@@ -55,28 +65,26 @@ namespace Renci.SshNet.Tests.Classes
             _closingRegister = new List<EventArgs>();
             _exceptionRegister = new List<ExceptionEventArgs>();
             _localEndpoint = new IPEndPoint(IPAddress.Loopback, 8122);
-            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"),
-                random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
-            _expectedElapsedTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
-            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port,
-                _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _bindSleepTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port, _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _channelBound = new ManualResetEvent(false);
+            _channelBindCompleted = new ManualResetEvent(false);
 
             _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
             _channelMock = new Mock<IChannelDirectTcpip>(MockBehavior.Strict);
 
-            Socket handlerSocket = null;
-
             _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(15));
             _sessionMock.Setup(p => p.IsConnected).Returns(true);
             _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
             _sessionMock.Setup(p => p.CreateChannelDirectTcpip()).Returns(_channelMock.Object);
-            _channelMock.Setup(p => p.Open(_forwardedPort.Host, _forwardedPort.Port, _forwardedPort, It.IsAny<Socket>())).Callback<string, uint, IForwardedPort, Socket>((address, port, forwardedPort, socket) => handlerSocket = socket);
+            _channelMock.Setup(p => p.Open(_forwardedPort.Host, _forwardedPort.Port, _forwardedPort, It.IsAny<Socket>()));
             _channelMock.Setup(p => p.Bind()).Callback(() =>
                 {
-                    Thread.Sleep(_expectedElapsedTime);
-                    if (handlerSocket != null && handlerSocket.Connected)
-                        handlerSocket.Shutdown(SocketShutdown.Both);
+                    _channelBound.Set();
+                    Thread.Sleep(_bindSleepTime);
+                    _channelBindCompleted.Set();
                 });
             _channelMock.Setup(p => p.Close());
             _channelMock.Setup(p => p.Dispose());
@@ -92,29 +100,21 @@ namespace Renci.SshNet.Tests.Classes
                     SendTimeout = 500,
                     SendBufferSize = 0
                 };
-
-            _stopwatch = new Stopwatch();
-            _stopwatch.Start();
-
             _client.Connect(_localEndpoint);
 
-            // give client socket time to establish connection
-            Thread.Sleep(50);
+            // wait for SOCKS client to bind to channel
+            Assert.IsTrue(_channelBound.WaitOne(TimeSpan.FromMilliseconds(200)));
         }
 
         protected void Act()
         {
             _forwardedPort.Stop();
-
-            _stopwatch.Stop();
-            _elapsedTimeOfStop = _stopwatch.Elapsed;
         }
 
         [TestMethod]
-        public void StopShouldBlockUntilBoundChannelHasClosed()
+        public void ShouldBlockUntilBindHasCompleted()
         {
-            Assert.IsTrue(_elapsedTimeOfStop >= _expectedElapsedTime, string.Format("Expected {0} or greater but was {1}.", _expectedElapsedTime.TotalMilliseconds, _elapsedTimeOfStop.TotalMilliseconds));
-            Assert.IsTrue(_elapsedTimeOfStop < _expectedElapsedTime.Add(TimeSpan.FromMilliseconds(200)));
+            Assert.IsTrue(_channelBindCompleted.WaitOne(0));
         }
 
         [TestMethod]
@@ -141,17 +141,11 @@ namespace Renci.SshNet.Tests.Classes
         }
 
         [TestMethod]
-        public void ExistingConnectionShouldBeClosed()
+        public void BoundClientShouldNotBeClosed()
         {
-            try
-            {
-                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
-                Assert.Fail();
-            }
-            catch (SocketException ex)
-            {
-                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
-            }
+            // the forwarded port itself does not close the client connection when the channel is closed properly
+            // it's the channel that will take care of closing the client connection
+            _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
         }
 
         [TestMethod]

+ 8 - 0
src/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortStarted_ChannelBound.cs

@@ -40,6 +40,8 @@ namespace Renci.SshNet.Tests.Classes
         {
             Arrange();
 
+            // calculate stop time here instead of in Act() because that method can be overridden
+
             var stopwatch = new Stopwatch();
             stopwatch.Start();
 
@@ -133,7 +135,13 @@ namespace Renci.SshNet.Tests.Classes
 
         protected virtual void Act()
         {
+            var stopwatch = new Stopwatch();
+            stopwatch.Start();
+
             ForwardedPort.Dispose();
+
+            stopwatch.Stop();
+            _elapsedTimeOfStop = stopwatch.Elapsed;
         }
 
         [TestMethod]

+ 5 - 4
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileAttributesTest.cs

@@ -1,15 +1,16 @@
-using Renci.SshNet.Sftp;
+using System;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System;
+using Renci.SshNet.Sftp;
 using Renci.SshNet.Tests.Common;
 
-namespace Renci.SshNet.Tests
+namespace Renci.SshNet.Tests.Classes.Sftp
 {
     /// <summary>
     ///This is a test class for SftpFileAttributesTest and is intended
     ///to contain all SftpFileAttributesTest Unit Tests
     ///</summary>
-    [TestClass()]
+    [TestClass]
+    [Ignore] // placeholders only
     public class SftpFileAttributesTest : TestBase
     {
         /// <summary>

+ 3 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpListDirectoryAsyncResultTest.cs

@@ -9,13 +9,14 @@ namespace Renci.SshNet.Tests
     ///This is a test class for SftpListDirectoryAsyncResultTest and is intended
     ///to contain all SftpListDirectoryAsyncResultTest Unit Tests
     ///</summary>
-    [TestClass()]
+    [TestClass]
     public class SftpListDirectoryAsyncResultTest : TestBase
     {
         /// <summary>
         ///A test for SftpListDirectoryAsyncResult Constructor
         ///</summary>
-        [TestMethod()]
+        [TestMethod]
+        [Ignore] // placeholder
         public void SftpListDirectoryAsyncResultConstructorTest()
         {
             AsyncCallback asyncCallback = null; // TODO: Initialize to an appropriate value

+ 1 - 0
src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -141,6 +141,7 @@
     <Compile Include="Classes\ConnectionInfoTest_Authenticate_Failure.cs" />
     <Compile Include="Classes\ConnectionInfoTest_Authenticate_Success.cs" />
     <Compile Include="Classes\ForwardedPortDynamicTest_Dispose_PortStarted_ChannelBound.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_SessionErrorOccurred_ChannelBound.cs" />
     <Compile Include="Classes\ForwardedPortDynamicTest_Start_SessionNotConnected.cs" />
     <Compile Include="Classes\ForwardedPortDynamicTest_Start_SessionNull.cs" />
     <Compile Include="Classes\ForwardedPortDynamicTest_Stop_PortDisposed.cs" />

+ 3 - 0
src/Renci.SshNet.UAP10/Renci.SshNet.UAP10.csproj

@@ -345,6 +345,9 @@
     <Compile Include="..\Renci.SshNet\ForwardedPortRemote.cs">
       <Link>ForwardedPortRemote.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ForwardedPortStatus.cs">
+      <Link>ForwardedPortStatus.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\HashInfo.cs">
       <Link>HashInfo.cs</Link>
     </Compile>

+ 4 - 1
src/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj

@@ -325,6 +325,9 @@
     <Compile Include="..\Renci.SshNet\ForwardedPortRemote.cs">
       <Link>ForwardedPortRemote.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\ForwardedPortStatus.cs">
+      <Link>ForwardedPortStatus.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\HashInfo.cs">
       <Link>HashInfo.cs</Link>
     </Compile>
@@ -940,7 +943,7 @@
   <Import Project="$(MSBuildExtensionsPath)\Microsoft\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion)\Microsoft.$(TargetFrameworkIdentifier).CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
+      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 6 - 17
src/Renci.SshNet/Channels/Channel.cs

@@ -341,9 +341,6 @@ namespace Renci.SshNet.Channels
         /// </summary>
         public void Close()
         {
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Channel.Close");
-#endif // DEBUG_GERT
             Close(true);
         }
 
@@ -408,10 +405,6 @@ namespace Renci.SshNet.Channels
         {
             _closeMessageReceived = true;
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Channel.OnClose()");
-#endif // DEBUG_GERT
-
             // close the channel
             Close(false);
 
@@ -819,12 +812,12 @@ namespace Renci.SshNet.Channels
             } while (true);
         }
 
-        private InvalidOperationException CreateRemoteChannelInfoNotAvailableException()
+        private static InvalidOperationException CreateRemoteChannelInfoNotAvailableException()
         {
             throw new InvalidOperationException("The channel has not been opened, or the open has not yet been confirmed.");
         }
 
-        private InvalidOperationException CreateChannelClosedException()
+        private static InvalidOperationException CreateChannelClosedException()
         {
             throw new InvalidOperationException("The channel is closed.");
         }
@@ -853,15 +846,12 @@ namespace Renci.SshNet.Channels
 
             if (disposing)
             {
-#if DEBUG_GERT
-                Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Channel.Dipose(bool)");
-#endif // DEBUG_GERT
-
                 Close(false);
 
                 var session = _session;
                 if (session != null)
                 {
+                    _session = null;
                     session.ChannelWindowAdjustReceived -= OnChannelWindowAdjust;
                     session.ChannelDataReceived -= OnChannelData;
                     session.ChannelExtendedDataReceived -= OnChannelExtendedData;
@@ -872,28 +862,27 @@ namespace Renci.SshNet.Channels
                     session.ChannelFailureReceived -= OnChannelFailure;
                     session.ErrorOccured -= Session_ErrorOccured;
                     session.Disconnected -= Session_Disconnected;
-                    _session = null;
                 }
 
                 var channelClosedWaitHandle = _channelClosedWaitHandle;
                 if (channelClosedWaitHandle != null)
                 {
-                    channelClosedWaitHandle.Dispose();
                     _channelClosedWaitHandle = null;
+                    channelClosedWaitHandle.Dispose();
                 }
 
                 var channelServerWindowAdjustWaitHandle = _channelServerWindowAdjustWaitHandle;
                 if (channelServerWindowAdjustWaitHandle != null)
                 {
-                    channelServerWindowAdjustWaitHandle.Dispose();
                     _channelServerWindowAdjustWaitHandle = null;
+                    channelServerWindowAdjustWaitHandle.Dispose();
                 }
 
                 var errorOccuredWaitHandle = _errorOccuredWaitHandle;
                 if (errorOccuredWaitHandle != null)
                 {
-                    errorOccuredWaitHandle.Dispose();
                     _errorOccuredWaitHandle = null;
+                    errorOccuredWaitHandle.Dispose();
                 }
 
                 _isDisposed = true;

+ 2 - 56
src/Renci.SshNet/Channels/ChannelDirectTcpip.cs

@@ -56,24 +56,11 @@ namespace Renci.SshNet.Channels
 
             var ep = (IPEndPoint) socket.RemoteEndPoint;
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  ChannelOpenMessage send '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
             // open channel
             SendMessage(new ChannelOpenMessage(LocalChannelNumber, LocalWindowSize, LocalPacketSize,
                 new DirectTcpipChannelInfo(remoteHost, port, ep.Address.ToString(), (uint) ep.Port)));
-
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  ChannelOpenMessage sent '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
             //  Wait for channel to open
             WaitOnHandle(_channelOpen);
-
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  ChannelOpenMessage flagged '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
         }
 
         /// <summary>
@@ -101,10 +88,6 @@ namespace Renci.SshNet.Channels
 
             SocketAbstraction.ReadContinuous(_socket, buffer, 0, buffer.Length, SendData);
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.Bind (after) '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
             // even though the client has disconnected, we still want to properly close the
             // channel
             //
@@ -126,10 +109,6 @@ namespace Renci.SshNet.Channels
                 if (_socket == null)
                     return;
 
-#if DEBUG_GERT
-                Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.CloseSocket '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
                 // closing a socket actually disposes the socket, so we can safely dereference
                 // the field to avoid entering the lock again later
                 _socket.Dispose();
@@ -148,10 +127,6 @@ namespace Renci.SshNet.Channels
 
             lock (_socketLock)
             {
-#if DEBUG_GERT
-                Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.ShutdownSocket '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
                 if (_socket == null || !_socket.Connected)
                     return;
 
@@ -166,10 +141,6 @@ namespace Renci.SshNet.Channels
         /// <param name="wait"><c>true</c> to wait for the SSH_MSG_CHANNEL_CLOSE message to be received from the server; otherwise, <c>false</c>.</param>
         protected override void Close(bool wait)
         {
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.Close(bool) '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
             var forwardedPort = _forwardedPort;
             if (forwardedPort != null)
             {
@@ -221,10 +192,6 @@ namespace Renci.SshNet.Channels
             base.OnOpenConfirmation(remoteChannelNumber, initialWindowSize, maximumPacketSize);
 
             _channelOpen.Set();
-
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  ChannelOpenMessage confirmed '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
         }
 
         protected override void OnOpenFailure(uint reasonCode, string description, string language)
@@ -232,10 +199,6 @@ namespace Renci.SshNet.Channels
             base.OnOpenFailure(reasonCode, description, language);
 
             _channelOpen.Set();
-
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  ChannelOpenMessage failure '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
         }
 
         /// <summary>
@@ -245,11 +208,6 @@ namespace Renci.SshNet.Channels
         {
             base.OnEof();
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.OnEof '" + LocalChannelNumber + "' | " +
-                              DateTime.Now.ToString("hh:mm:ss"));
-#endif // DEBUG_GERT
-
             // the channel will send no more data, and hence it does not make sense to receive
             // any more data from the client to send to the remote party (and we surely won't
             // send anything anymore)
@@ -266,10 +224,6 @@ namespace Renci.SshNet.Channels
         {
             base.OnErrorOccured(exp);
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.OnErrorOccured '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss"));
-#endif // DEBUG_GERT
-
             // signal to the client that we will not send anything anymore; this will also interrupt the
             // blocking receive in Bind if the client sends FIN/ACK in time
             //
@@ -288,10 +242,6 @@ namespace Renci.SshNet.Channels
         {
             base.OnDisconnected();
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.OnDisconnected '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss"));
-#endif // DEBUG_GERT
-
             // the channel will accept or send no more data, and hence it does not make sense
             // to accept any more data from the client (and we surely won't send anything
             // anymore)
@@ -310,10 +260,6 @@ namespace Renci.SshNet.Channels
 
             if (disposing)
             {
-#if DEBUG_GERT
-                Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Dispose '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss"));
-#endif // DEBUG_GERT
-
                 if (_socket != null)
                 {
                     lock (_socketLock)
@@ -329,15 +275,15 @@ namespace Renci.SshNet.Channels
                 var channelOpen = _channelOpen;
                 if (channelOpen != null)
                 {
-                    channelOpen.Dispose();
                     _channelOpen = null;
+                    channelOpen.Dispose();
                 }
 
                 var channelData = _channelData;
                 if (channelData != null)
                 {
-                    channelData.Dispose();
                     _channelData = null;
+                    channelData.Dispose();
                 }
             }
         }

+ 2 - 6
src/Renci.SshNet/ForwardedPort.cs

@@ -31,7 +31,7 @@ namespace Renci.SshNet
         }
 
         /// <summary>
-        /// Gets or sets a value indicating whether port forwarding is started.
+        /// Gets a value indicating whether port forwarding is started.
         /// </summary>
         /// <value>
         /// <c>true</c> if port forwarding is started; otherwise, <c>false</c>.
@@ -111,11 +111,7 @@ namespace Renci.SshNet
                 var session = Session;
                 if (session != null)
                 {
-                    if (IsStarted)
-                    {
-                        StopPort(session.ConnectionInfo.Timeout);
-                    }
-
+                    StopPort(session.ConnectionInfo.Timeout);
                     Session = null;
                 }
             }

+ 129 - 224
src/Renci.SshNet/ForwardedPortDynamic.NET.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 using System.Linq;
 using System.Text;
 using System.Net;
@@ -13,7 +14,7 @@ namespace Renci.SshNet
     public partial class ForwardedPortDynamic
     {
         private Socket _listener;
-        private CountdownEvent _pendingRequestsCountdown;
+        private CountdownEvent _pendingChannelCountdown;
 
         partial void InternalStart()
         {
@@ -30,72 +31,38 @@ namespace Renci.SshNet
 #if FEATURE_SOCKET_SETSOCKETOPTION
             _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
             _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
-#endif //FEATURE_SOCKET_SETSOCKETOPTION
+#endif // FEATURE_SOCKET_SETSOCKETOPTION
             _listener.Bind(ep);
             _listener.Listen(5);
 
             Session.ErrorOccured += Session_ErrorOccured;
             Session.Disconnected += Session_Disconnected;
 
-            _listenerCompleted = new ManualResetEvent(false);
-            InitializePendingRequestsCountdown();
-
-            ThreadAbstraction.ExecuteThread(() =>
-                {
-                    try
-                    {
-#if FEATURE_SOCKET_EAP
-                        StartAccept(null);
-#elif FEATURE_SOCKET_APM
-                        _listener.BeginAccept(AcceptCallback, _listener);
-#elif FEATURE_SOCKET_TAP
-#error Accepting new socket connections is not implemented.
-#else
-#error Accepting new socket connections is not implemented.
-#endif
-                    }
-                    catch (ObjectDisposedException)
-                    {
-                        // BeginAccept / AcceptAsync will throw an ObjectDisposedException when the
-                        // server is closed before the listener has started accepting connections.
-                        //
-                        // As we start accepting connection on a separate thread, this is possible
-                        // when the listener is stopped right after it was started.
-                    }
-                    catch (Exception ex)
-                    {
-                        RaiseExceptionEvent(ex);
-                    }
+            InitializePendingChannelCountdown();
 
-                    // wait until listener is stopped
-                    _listenerCompleted.WaitOne();
+            // consider port started when we're listening for inbound connections
+            _status = ForwardedPortStatus.Started;
 
-                    var session = Session;
-                    if (session != null)
-                    {
-                        session.ErrorOccured -= Session_ErrorOccured;
-                        session.Disconnected -= Session_Disconnected;
-                    }
-                });
-        }
-
-        private void Session_Disconnected(object sender, EventArgs e)
-        {
-            if (IsStarted)
+            try
             {
-                StopListener();
+                StartAccept(null);
             }
-        }
-
-        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
-        {
-            if (IsStarted)
+            catch (ObjectDisposedException)
             {
-                StopListener();
+                // AcceptAsync will throw an ObjectDisposedException when the server is closed before
+                // the listener has started accepting connections.
+                //
+                // this is only possible when the listener is stopped (from another thread) right
+                // after it was started.
+                StopPort(Session.ConnectionInfo.Timeout);
+            }
+            catch (Exception ex)
+            {
+                StopPort(Session.ConnectionInfo.Timeout);
+                RaiseExceptionEvent(ex);
             }
         }
 
-#if FEATURE_SOCKET_EAP
         private void StartAccept(SocketAsyncEventArgs e)
         {
             if (e == null)
@@ -109,75 +76,57 @@ namespace Renci.SshNet
                 e.AcceptSocket = null;
             }
 
-            if (!_listener.AcceptAsync(e))
+            // only accept new connections while we are started
+            if (IsStarted)
             {
-                AcceptCompleted(null, e);
+                if (!_listener.AcceptAsync(e))
+                {
+                    AcceptCompleted(null, e);
+                }
             }
         }
 
-        private void AcceptCompleted(object sender, SocketAsyncEventArgs acceptAsyncEventArgs)
+        private void AcceptCompleted(object sender, SocketAsyncEventArgs e)
         {
-            if (acceptAsyncEventArgs.SocketError == SocketError.OperationAborted)
+            if (e.SocketError == SocketError.OperationAborted || e.SocketError == SocketError.NotSocket)
             {
                 // server was stopped
                 return;
             }
 
             // capture client socket
-            var clientSocket = acceptAsyncEventArgs.AcceptSocket;
+            var clientSocket = e.AcceptSocket;
 
-            if (acceptAsyncEventArgs.SocketError != SocketError.Success)
+            if (e.SocketError != SocketError.Success)
             {
                 // accept new connection
-                StartAccept(acceptAsyncEventArgs);
+                StartAccept(e);
                 // dispose broken client socket
                 CloseClientSocket(clientSocket);
                 return;
             }
 
             // accept new connection
-            StartAccept(acceptAsyncEventArgs);
+            StartAccept(e);
             // process connection
             ProcessAccept(clientSocket);
         }
-#elif FEATURE_SOCKET_APM
-        private void AcceptCallback(IAsyncResult ar)
-        {
-            // Get the socket that handles the client request
-            var serverSocket = (Socket) ar.AsyncState;
-
-            Socket clientSocket;
 
-            try
-            {
-                clientSocket = serverSocket.EndAccept(ar);
-            }
-            catch (ObjectDisposedException)
+        private void ProcessAccept(Socket clientSocket)
+        {
+            // close the client socket if we're no longer accepting new connections
+            if (!IsStarted)
             {
-                // when the server socket is closed, an ObjectDisposedException is thrown
-                // by Socket.EndAccept(IAsyncResult)
+                CloseClientSocket(clientSocket);
                 return;
             }
 
-            // accept new connection
-            _listener.BeginAccept(AcceptCallback, _listener);
-            // process connection
-            ProcessAccept(clientSocket);
-        }
-#endif
-
-        private void ProcessAccept(Socket remoteSocket)
-        {
             // 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();
+            // and at that time there may still be pending requests
+            var pendingChannelCountdown = _pendingChannelCountdown;
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + remoteSocket.RemoteEndPoint + " | ForwardedPortDynamic.ProcessAccept | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
+            pendingChannelCountdown.AddCount();
 
             try
             {
@@ -192,56 +141,34 @@ namespace Renci.SshNet
 
                     try
                     {
-                        if (!HandleSocks(channel, remoteSocket, Session.ConnectionInfo.Timeout))
+                        if (!HandleSocks(channel, clientSocket, Session.ConnectionInfo.Timeout))
                         {
-                            CloseClientSocket(remoteSocket);
+                            CloseClientSocket(clientSocket);
                             return;
                         }
 
                         // start receiving from client socket (and sending to server)
                         channel.Bind();
                     }
-#if DEBUG_GERT
-                    catch (SocketException ex)
-                    {
-                        Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + ex.SocketErrorCode + " | " + DateTime.Now.ToString("hh:mm:ss.fff") + " | " + ex);
-                        throw;
-                    }
-#endif // DEBUG_GERT
                     finally
                     {
                         channel.Close();
                     }
                 }
             }
-            catch (SocketException ex)
-            {
-                // ignore exception thrown by interrupting the blocking receive as part of closing
-                // the forwarded port
-                if (ex.SocketErrorCode != SocketError.Interrupted)
-                {
-#if DEBUG_GERT
-                    RaiseExceptionEvent(new Exception("ID: " + Thread.CurrentThread.ManagedThreadId, ex));
-#else
-                    RaiseExceptionEvent(ex);
-#endif // DEBUG_GERT
-                }
-                CloseClientSocket(remoteSocket);
-            }
             catch (Exception exp)
             {
-#if DEBUG_GERT
-                Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + exp + " | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
                 RaiseExceptionEvent(exp);
-                CloseClientSocket(remoteSocket);
+                CloseClientSocket(clientSocket);
             }
             finally
             {
-                // take into account that countdown event has since been disposed (after waiting for a given timeout)
+                // take into account that CountdownEvent has since been disposed; when stopping the port we
+                // wait for a given time for the channels to close, but once that timeout period has elapsed
+                // the CountdownEvent will be disposed
                 try
                 {
-                    pendingRequestsCountdown.Signal();
+                    pendingChannelCountdown.Signal();
                 }
                 catch (ObjectDisposedException)
                 {
@@ -249,42 +176,61 @@ namespace Renci.SshNet
             }
         }
 
-        private bool HandleSocks(IChannelDirectTcpip channel, Socket remoteSocket, TimeSpan timeout)
+        /// <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 InitializePendingChannelCountdown()
+        {
+            var original = Interlocked.Exchange(ref _pendingChannelCountdown, new CountdownEvent(1));
+            if (original != null)
+            {
+                original.Dispose();
+            }
+        }
+
+        private bool HandleSocks(IChannelDirectTcpip channel, Socket clientSocket, TimeSpan timeout)
         {
             // create eventhandler which is to be invoked to interrupt a blocking receive
             // when we're closing the forwarded port
-            EventHandler closeClientSocket = (_, args) => CloseClientSocket(remoteSocket);
+            EventHandler closeClientSocket = (_, args) => CloseClientSocket(clientSocket);
 
             Closing += closeClientSocket;
 
             try
             {
-#if DEBUG_GERT
-                Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Before ReadByte for version | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
-                var version = SocketAbstraction.ReadByte(remoteSocket, timeout);
-                if (version == -1)
-                {
-                    return false;
-                }
-
-#if DEBUG_GERT
-                Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After ReadByte for version | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
-                if (version == 4)
+                var version = SocketAbstraction.ReadByte(clientSocket, timeout);
+                switch (version)
                 {
-                    return HandleSocks4(remoteSocket, channel, timeout);
+                    case -1:
+                        // SOCKS client closed connection
+                        return false;
+                    case 4:
+                        return HandleSocks4(clientSocket, channel, timeout);
+                    case 5:
+                        return HandleSocks5(clientSocket, channel, timeout);
+                    default:
+                        throw new NotSupportedException(string.Format("SOCKS version {0} is not supported.", version));
                 }
-                else if (version == 5)
-                {
-                    return HandleSocks5(remoteSocket, channel, timeout);
-                }
-                else
+            }
+            catch (SocketException ex)
+            {
+                // ignore exception thrown by interrupting the blocking receive as part of closing
+                // the forwarded port
+                if (ex.SocketErrorCode != SocketError.Interrupted)
                 {
-                    throw new NotSupportedException(string.Format("SOCKS version {0} is not supported.", version));
+                    RaiseExceptionEvent(ex);
                 }
+                return false;
             }
             finally
             {
@@ -297,26 +243,24 @@ namespace Renci.SshNet
 
         private static void CloseClientSocket(Socket clientSocket)
         {
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ForwardedPortDynamic.CloseSocket | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
             if (clientSocket.Connected)
             {
                 try
                 {
-                    clientSocket.Shutdown(SocketShutdown.Both);
+                    clientSocket.Shutdown(SocketShutdown.Send);
                 }
                 catch (Exception)
                 {
                     // ignore exception when client socket was already closed
                 }
-
             }
 
             clientSocket.Dispose();
         }
 
+        /// <summary>
+        /// Interrupts the listener, and unsubscribes from <see cref="Session"/> events.
+        /// </summary>
         partial void StopListener()
         {
             // close listener socket
@@ -326,22 +270,23 @@ namespace Renci.SshNet
                 listener.Dispose();
             }
 
-            // allow listener thread to stop
-            var listenerCompleted = _listenerCompleted;
-            if (listenerCompleted != null)
+            // unsubscribe from session events
+            var session = Session;
+            if (session != null)
             {
-                listenerCompleted.Set();
+                session.ErrorOccured -= Session_ErrorOccured;
+                session.Disconnected -= Session_Disconnected;
             }
         }
 
         /// <summary>
-        /// Waits for pending requests to finish.
+        /// Waits for pending channels to close.
         /// </summary>
-        /// <param name="timeout">The maximum time to wait for the pending requests to finish.</param>
+        /// <param name="timeout">The maximum time to wait for the pending channels to close.</param>
         partial void InternalStop(TimeSpan timeout)
         {
-            _pendingRequestsCountdown.Signal();
-            _pendingRequestsCountdown.Wait(timeout);
+            _pendingChannelCountdown.Signal();
+            _pendingChannelCountdown.Wait(timeout);
         }
 
         partial void InternalDispose(bool disposing)
@@ -355,15 +300,38 @@ namespace Renci.SshNet
                     listener.Dispose();
                 }
 
-                var pendingRequestsCountdown = _pendingRequestsCountdown;
+                var pendingRequestsCountdown = _pendingChannelCountdown;
                 if (pendingRequestsCountdown != null)
                 {
-                    _pendingRequestsCountdown = null;
+                    _pendingChannelCountdown = null;
                     pendingRequestsCountdown.Dispose();
                 }
             }
         }
 
+        private void Session_Disconnected(object sender, EventArgs e)
+        {
+            var session = Session;
+            if (session != null)
+            {
+                StopPort(session.ConnectionInfo.Timeout);
+            }
+        }
+
+        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
+        {
+            var session = Session;
+            if (session != null)
+            {
+                StopPort(session.ConnectionInfo.Timeout);
+            }
+        }
+
+        private void Channel_Exception(object sender, ExceptionEventArgs e)
+        {
+            RaiseExceptionEvent(e.Exception);
+        }
+
         private bool HandleSocks4(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout)
         {
             var commandCode = SocketAbstraction.ReadByte(socket, timeout);
@@ -423,10 +391,6 @@ namespace Renci.SshNet
 
         private bool HandleSocks5(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout)
         {
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  Handling Socks5: " + socket.LocalEndPoint +  " | " + socket.RemoteEndPoint + " | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
             var authenticationMethodsCount = SocketAbstraction.ReadByte(socket, timeout);
             if (authenticationMethodsCount == -1)
             {
@@ -434,10 +398,6 @@ namespace Renci.SshNet
                 return false;
             }
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  After ReadByte for authenticationMethodsCount | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
             var authenticationMethods = new byte[authenticationMethodsCount];
             if (SocketAbstraction.Read(socket, authenticationMethods, 0, authenticationMethods.Length, timeout) == 0)
             {
@@ -445,19 +405,11 @@ namespace Renci.SshNet
                 return false;
             }
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  After Read for authenticationMethods | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-
             if (authenticationMethods.Min() == 0)
             {
                 // no user authentication is one of the authentication methods supported
                 // by the SOCKS client
                 SocketAbstraction.Send(socket, new byte[] { 0x05, 0x00 }, 0, 2);
-
-#if DEBUG_GERT
-                Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  After Send for authenticationMethods 0 | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
             }
             else
             {
@@ -467,9 +419,6 @@ namespace Renci.SshNet
                 // we continue business as usual but expect the client to close the connection
                 // so one of the subsequent reads should return -1 signaling that the client
                 // has effectively closed the connection
-#if DEBUG_GERT
-                Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  After Send for authenticationMethods 2 | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
             }
 
             var version = SocketAbstraction.ReadByte(socket, timeout);
@@ -574,24 +523,10 @@ namespace Renci.SshNet
 
             RaiseRequestReceived(host, port);
 
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  Before channel open | " + DateTime.Now.ToString("hh:mm:ss.fff"));
-
-            var stopWatch = new Stopwatch();
-            stopWatch.Start();
-#endif // DEBUG_GERT
-
             channel.Open(host, port, this, socket);
 
-#if DEBUG_GERT
-            stopWatch.Stop();
-
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " |  After channel open | " + DateTime.Now.ToString("hh:mm:ss.fff") + " => " + stopWatch.ElapsedMilliseconds);
-#endif // DEBUG_GERT
-
             SocketAbstraction.SendByte(socket, 0x05);
 
-
             if (channel.IsOpen)
             {
                 SocketAbstraction.SendByte(socket, 0x00);
@@ -624,37 +559,6 @@ namespace Renci.SshNet
             return true;
         }
 
-        private void Channel_Exception(object sender, ExceptionEventArgs e)
-        {
-#if DEBUG_GERT
-            Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Channel_Exception | " +
-                              DateTime.Now.ToString("hh:mm:ss.fff"));
-#endif // DEBUG_GERT
-            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>
         /// Reads a null terminated string from a socket.
         /// </summary>
@@ -689,3 +593,4 @@ namespace Renci.SshNet
         }
     }
 }
+

+ 25 - 20
src/Renci.SshNet/ForwardedPortDynamic.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Threading;
 
 namespace Renci.SshNet
 {
@@ -9,7 +8,7 @@ namespace Renci.SshNet
     /// </summary>
     public partial class ForwardedPortDynamic : ForwardedPort
     {
-        private EventWaitHandle _listenerCompleted;
+        private ForwardedPortStatus _status;
 
         /// <summary>
         /// Gets the bound host.
@@ -22,22 +21,21 @@ namespace Renci.SshNet
         public uint BoundPort { get; private set; }
 
         /// <summary>
-        /// Gets or sets a value indicating whether port forwarding is started.
+        /// Gets a value indicating whether port forwarding is started.
         /// </summary>
         /// <value>
         /// <c>true</c> if port forwarding is started; otherwise, <c>false</c>.
         /// </value>
         public override bool IsStarted
         {
-            get { return _listenerCompleted != null && !_listenerCompleted.WaitOne(0); }
+            get { return _status == ForwardedPortStatus.Started; }
         }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ForwardedPortDynamic"/> class.
         /// </summary>
         /// <param name="port">The port.</param>
-        public ForwardedPortDynamic(uint port)
-            : this(string.Empty, port)
+        public ForwardedPortDynamic(uint port) : this(string.Empty, port)
         {
         }
 
@@ -50,6 +48,7 @@ namespace Renci.SshNet
         {
             BoundHost = host;
             BoundPort = port;
+            _status = ForwardedPortStatus.Stopped;
         }
 
         /// <summary>
@@ -57,7 +56,18 @@ namespace Renci.SshNet
         /// </summary>
         protected override void StartPort()
         {
-            InternalStart();
+            if (!ForwardedPortStatus.ToStarting(ref _status))
+                return;
+
+            try
+            {
+                InternalStart();
+            }
+            catch (Exception)
+            {
+                _status = ForwardedPortStatus.Stopped;
+                throw;
+            }
         }
 
         /// <summary>
@@ -67,13 +77,18 @@ namespace Renci.SshNet
         /// <param name="timeout">The maximum amount of time to wait for pending requests to finish processing.</param>
         protected override void StopPort(TimeSpan timeout)
         {
-            // prevent new requests from getting processed before we signal existing
-            // channels that the port is closing
-            StopListener();
+            if (!ForwardedPortStatus.ToStopping(ref _status))
+                return;
+
             // signal existing channels that the port is closing
             base.StopPort(timeout);
             // wait for open channels to close
             InternalStop(timeout);
+            // prevent new requests from getting processed before we signal existing
+            // channels that the port is closing
+            StopListener();
+            // mark port stopped
+            _status = ForwardedPortStatus.Stopped;
         }
 
         /// <summary>
@@ -132,16 +147,6 @@ namespace Renci.SshNet
             base.Dispose(disposing);
             InternalDispose(disposing);
 
-            if (disposing)
-            {
-                var listenerCompleted = _listenerCompleted;
-                if (listenerCompleted != null)
-                {
-                    _listenerCompleted = null;
-                    listenerCompleted.Dispose();
-                }
-            }
-
             _isDisposed = true;
         }
 

+ 104 - 117
src/Renci.SshNet/ForwardedPortLocal.NET.cs

@@ -7,13 +7,10 @@ using Renci.SshNet.Common;
 
 namespace Renci.SshNet
 {
-    /// <summary>
-    /// Provides functionality for local port forwarding
-    /// </summary>
     public partial class ForwardedPortLocal
     {
         private Socket _listener;
-        private CountdownEvent _pendingRequestsCountdown;
+        private CountdownEvent _pendingChannelCountdown;
 
         partial void InternalStart()
         {
@@ -23,6 +20,7 @@ namespace Renci.SshNet
             _listener = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
             // TODO: decide if we want to have blocking socket
 #if FEATURE_SOCKET_SETSOCKETOPTION
+            _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
             _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
 #endif // FEATURE_SOCKET_SETSOCKETOPTION
             _listener.Bind(ep);
@@ -34,45 +32,31 @@ namespace Renci.SshNet
             Session.ErrorOccured += Session_ErrorOccured;
             Session.Disconnected += Session_Disconnected;
 
-            _listenerTaskCompleted = new ManualResetEvent(false);
-            InitializePendingRequestsCountdown();
+            InitializePendingChannelCountdown();
 
-            ThreadAbstraction.ExecuteThread(() =>
-                {
-                    try
-                    {
-#if FEATURE_SOCKET_EAP
-                        StartAccept(null);
-#elif FEATURE_SOCKET_APM
-                        _listener.BeginAccept(AcceptCallback, _listener);
-#elif FEATURE_SOCKET_TAP
-#error Accepting new socket connections is not implemented.
-#else
-#error Accepting new socket connections is not implemented.
-#endif
-                    }
-                    catch (ObjectDisposedException)
-                    {
-                        // BeginAccept / AcceptAsync will throw an ObjectDisposedException when the
-                        // server is closed before the listener has started accepting connections.
-                        //
-                        // As we start accepting connection on a separate thread, this is possible
-                        // when the listener is stopped right after it was started.
-                    }
-
-                    // wait until listener is stopped
-                    _listenerTaskCompleted.WaitOne();
+            // consider port started when we're listening for inbound connections
+            _status = ForwardedPortStatus.Started;
 
-                    var session = Session;
-                    if (session != null)
-                    {
-                        session.Disconnected -= Session_Disconnected;
-                        session.ErrorOccured -= Session_ErrorOccured;
-                    }
-                });
+            try
+            {
+                StartAccept(null);
+            }
+            catch (ObjectDisposedException)
+            {
+                // AcceptAsync will throw an ObjectDisposedException when the server is closed before
+                // the listener has started accepting connections.
+                //
+                // this is only possible when the listener is stopped (from another thread) right
+                // after it was started.
+                StopPort(Session.ConnectionInfo.Timeout);
+            }
+            catch (Exception ex)
+            {
+                StopPort(Session.ConnectionInfo.Timeout);
+                RaiseExceptionEvent(ex);
+            }
         }
 
-#if FEATURE_SOCKET_EAP
         private void StartAccept(SocketAsyncEventArgs e)
         {
             if (e == null)
@@ -86,89 +70,70 @@ namespace Renci.SshNet
                 e.AcceptSocket = null;
             }
 
-            if (!_listener.AcceptAsync(e))
+            // only accept new connections while we are started
+            if (IsStarted)
             {
-                AcceptCompleted(null, e);
+                try
+                {
+                    if (!_listener.AcceptAsync(e))
+                    {
+                        AcceptCompleted(null, e);
+                    }
+                }
+                catch (ObjectDisposedException)
+                {
+                    if (_status == ForwardedPortStatus.Stopped || _status == ForwardedPortStatus.Stopped)
+                    {
+                        // ignore ObjectDisposedException while stopping or stopped
+                        return;
+                    }
+
+                    throw;
+                }
             }
         }
 
-
-        private void AcceptCompleted(object sender, SocketAsyncEventArgs acceptAsyncEventArgs)
-        { 
-            if (acceptAsyncEventArgs.SocketError == SocketError.OperationAborted)
+        private void AcceptCompleted(object sender, SocketAsyncEventArgs e)
+        {
+            if (e.SocketError == SocketError.OperationAborted || e.SocketError == SocketError.NotSocket)
             {
                 // server was stopped
                 return;
             }
 
             // capture client socket
-            var clientSocket = acceptAsyncEventArgs.AcceptSocket;
+            var clientSocket = e.AcceptSocket;
 
-            if (acceptAsyncEventArgs.SocketError != SocketError.Success)
+            if (e.SocketError != SocketError.Success)
             {
                 // accept new connection
-                StartAccept(acceptAsyncEventArgs);
-                // close client socket
+                StartAccept(e);
+                // dispose broken client socket
                 CloseClientSocket(clientSocket);
                 return;
             }
 
             // accept new connection
-            StartAccept(acceptAsyncEventArgs);
+            StartAccept(e);
             // process connection
             ProcessAccept(clientSocket);
         }
-#elif FEATURE_SOCKET_APM
-        private void AcceptCallback(IAsyncResult ar)
-        {
-            // Get the socket that handles the client request
-            var serverSocket = (Socket) ar.AsyncState;
-
-            Socket clientSocket;
 
-            try
-            {
-                clientSocket = serverSocket.EndAccept(ar);
-            }
-            catch (ObjectDisposedException)
-            {
-                // when the server socket is closed, an ObjectDisposedException is thrown
-                // by Socket.EndAccept(IAsyncResult)
-                return;
-            }
-
-            // accept new connection
-            _listener.BeginAccept(AcceptCallback, _listener);
-            // process connection
-            ProcessAccept(clientSocket);
-        }
-#endif
-
-        private static void CloseClientSocket(Socket clientSocket)
+        private void ProcessAccept(Socket clientSocket)
         {
-            if (clientSocket.Connected)
+            // close the client socket if we're no longer accepting new connections
+            if (!IsStarted)
             {
-                try
-                {
-                    clientSocket.Shutdown(SocketShutdown.Send);
-                }
-                catch (Exception)
-                {
-                    // ignore exception when client socket was already closed
-                }
+                CloseClientSocket(clientSocket);
+                return;
             }
 
-            clientSocket.Dispose();
-        }
-
-        private void ProcessAccept(Socket clientSocket)
-        {
             // 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;
+            // and at that time there may still be pending requests
+            var pendingChannelCountdown = _pendingChannelCountdown;
 
-            pendingRequestsCountdown.AddCount();
+            pendingChannelCountdown.AddCount();
 
             try
             {
@@ -197,10 +162,12 @@ namespace Renci.SshNet
             }
             finally
             {
-                // take into account that countdown event has since been disposed (after waiting for a given timeout)
+                // take into account that CountdownEvent has since been disposed; when stopping the port we
+                // wait for a given time for the channels to close, but once that timeout period has elapsed
+                // the CountdownEvent will be disposed
                 try
                 {
-                    pendingRequestsCountdown.Signal();
+                    pendingChannelCountdown.Signal();
                 }
                 catch (ObjectDisposedException)
                 {
@@ -221,27 +188,34 @@ namespace Renci.SshNet
         /// initial count of <c>1</c>.
         /// </para>
         /// </remarks>
-        private void InitializePendingRequestsCountdown()
+        private void InitializePendingChannelCountdown()
         {
-            var original = Interlocked.Exchange(ref _pendingRequestsCountdown, new CountdownEvent(1));
+            var original = Interlocked.Exchange(ref _pendingChannelCountdown, new CountdownEvent(1));
             if (original != null)
             {
                 original.Dispose();
             }
         }
 
-        /// <summary>
-        /// Waits for pending requests to finish.
-        /// </summary>
-        /// <param name="timeout">The maximum time to wait for the pending requests to finish.</param>
-        partial void InternalStop(TimeSpan timeout)
+        private static void CloseClientSocket(Socket clientSocket)
         {
-            _pendingRequestsCountdown.Signal();
-            _pendingRequestsCountdown.Wait(timeout);
+            if (clientSocket.Connected)
+            {
+                try
+                {
+                    clientSocket.Shutdown(SocketShutdown.Send);
+                }
+                catch (Exception)
+                {
+                    // ignore exception when client socket was already closed
+                }
+            }
+
+            clientSocket.Dispose();
         }
 
         /// <summary>
-        /// Interrupts the listener, and waits for the listener loop to finish.
+        /// Interrupts the listener, and unsubscribes from <see cref="Session"/> events.
         /// </summary>
         partial void StopListener()
         {
@@ -252,14 +226,25 @@ namespace Renci.SshNet
                 listener.Dispose();
             }
 
-            // allow listener thread to stop
-            var listenerTaskCompleted = _listenerTaskCompleted;
-            if (listenerTaskCompleted != null)
+            // unsubscribe from session events
+            var session = Session;
+            if (session != null)
             {
-                listenerTaskCompleted.Set();
+                session.ErrorOccured -= Session_ErrorOccured;
+                session.Disconnected -= Session_Disconnected;
             }
         }
 
+        /// <summary>
+        /// Waits for pending channels to close.
+        /// </summary>
+        /// <param name="timeout">The maximum time to wait for the pending channels to close.</param>
+        partial void InternalStop(TimeSpan timeout)
+        {
+            _pendingChannelCountdown.Signal();
+            _pendingChannelCountdown.Wait(timeout);
+        }
+
         partial void InternalDispose(bool disposing)
         {
             if (disposing)
@@ -271,28 +256,30 @@ namespace Renci.SshNet
                     listener.Dispose();
                 }
 
-                var pendingRequestsCountdown = _pendingRequestsCountdown;
+                var pendingRequestsCountdown = _pendingChannelCountdown;
                 if (pendingRequestsCountdown != null)
                 {
-                    _pendingRequestsCountdown = null;
+                    _pendingChannelCountdown = null;
                     pendingRequestsCountdown.Dispose();
                 }
             }
         }
 
-        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
+        private void Session_Disconnected(object sender, EventArgs e)
         {
-            if (IsStarted)
+            var session = Session;
+            if (session != null)
             {
-                StopListener();
+                StopPort(session.ConnectionInfo.Timeout);
             }
         }
 
-        private void Session_Disconnected(object sender, EventArgs e)
+        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
         {
-            if (IsStarted)
+            var session = Session;
+            if (session != null)
             {
-                StopListener();
+                StopPort(session.ConnectionInfo.Timeout);
             }
         }
 

+ 24 - 18
src/Renci.SshNet/ForwardedPortLocal.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Threading;
 
 namespace Renci.SshNet
 {
@@ -8,7 +7,7 @@ namespace Renci.SshNet
     /// </summary>
     public partial class ForwardedPortLocal : ForwardedPort, IDisposable
     {
-        private EventWaitHandle _listenerTaskCompleted;
+        private ForwardedPortStatus _status;
 
         /// <summary>
         /// Gets the bound host.
@@ -31,14 +30,14 @@ namespace Renci.SshNet
         public uint Port { get; private set; }
 
         /// <summary>
-        /// Gets or sets a value indicating whether port forwarding is started.
+        /// Gets a value indicating whether port forwarding is started.
         /// </summary>
         /// <value>
         /// <c>true</c> if port forwarding is started; otherwise, <c>false</c>.
         /// </value>
         public override bool IsStarted
         {
-            get { return _listenerTaskCompleted != null && !_listenerTaskCompleted.WaitOne(0); }
+            get { return _status == ForwardedPortStatus.Started; }
         }
 
         /// <summary>
@@ -98,6 +97,7 @@ namespace Renci.SshNet
             BoundPort = boundPort;
             Host = host;
             Port = port;
+            _status = ForwardedPortStatus.Stopped;
         }
 
         /// <summary>
@@ -105,7 +105,18 @@ namespace Renci.SshNet
         /// </summary>
         protected override void StartPort()
         {
-            InternalStart();
+            if (!ForwardedPortStatus.ToStarting(ref _status))
+                return;
+
+            try
+            {
+                InternalStart();
+            }
+            catch (Exception)
+            {
+                _status = ForwardedPortStatus.Stopped;
+                throw;
+            }
         }
 
         /// <summary>
@@ -115,13 +126,18 @@ namespace Renci.SshNet
         /// <param name="timeout">The maximum amount of time to wait for pending requests to finish processing.</param>
         protected override void StopPort(TimeSpan timeout)
         {
-            // prevent new requests from getting processed before we signal existing
-            // channels that the port is closing
-            StopListener();
+            if (!ForwardedPortStatus.ToStopping(ref _status))
+                return;
+
             // signal existing channels that the port is closing
             base.StopPort(timeout);
             // wait for open channels to close
             InternalStop(timeout);
+            // prevent new requests from getting processed before we signal existing
+            // channels that the port is closing
+            StopListener();
+            // mark port stopped
+            _status = ForwardedPortStatus.Stopped;
         }
 
         /// <summary>
@@ -173,16 +189,6 @@ namespace Renci.SshNet
             base.Dispose(disposing);
             InternalDispose(disposing);
 
-            if (disposing)
-            {
-                var listenerTaskCompleted = _listenerTaskCompleted;
-                if (listenerTaskCompleted != null)
-                {
-                    _listenerTaskCompleted = null;
-                    listenerTaskCompleted.Dispose();
-                }
-            }
-
             _isDisposed = true;
         }
 

+ 30 - 22
src/Renci.SshNet/ForwardedPortRemote.cs

@@ -13,21 +13,21 @@ namespace Renci.SshNet
     /// </summary>
     public class ForwardedPortRemote : ForwardedPort, IDisposable
     {
+        private ForwardedPortStatus _status;
         private bool _requestStatus;
 
         private EventWaitHandle _globalRequestResponse = new AutoResetEvent(false);
-        private CountdownEvent _pendingRequestsCountdown;
-        private bool _isStarted;
+        private CountdownEvent _pendingChannelCountdown;
 
         /// <summary>
-        /// Gets or sets a value indicating whether port forwarding is started.
+        /// Gets a value indicating whether port forwarding is started.
         /// </summary>
         /// <value>
         /// <c>true</c> if port forwarding is started; otherwise, <c>false</c>.
         /// </value>
         public override bool IsStarted
         {
-            get { return _isStarted; }
+            get { return _status == ForwardedPortStatus.Started; }
         }
 
         /// <summary>
@@ -97,6 +97,7 @@ namespace Renci.SshNet
             BoundPort = boundPort;
             HostAddress = hostAddress;
             Port = port;
+            _status = ForwardedPortStatus.Stopped;
         }
 
         /// <summary>
@@ -133,6 +134,9 @@ namespace Renci.SshNet
         /// </summary>
         protected override void StartPort()
         {
+            if (!ForwardedPortStatus.ToStarting(ref _status))
+                return;
+
             Session.RegisterMessage("SSH_MSG_REQUEST_FAILURE");
             Session.RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
             Session.RegisterMessage("SSH_MSG_CHANNEL_OPEN");
@@ -141,7 +145,7 @@ namespace Renci.SshNet
             Session.RequestFailureReceived += Session_RequestFailure;
             Session.ChannelOpenReceived += Session_ChannelOpening;
 
-            InitializePendingRequestsCountdown();
+            InitializePendingChannelCountdown();
 
             // send global request to start direct tcpip
             Session.SendMessage(new GlobalRequestMessage(GlobalRequestName.TcpIpForward, true, BoundHost, BoundPort));
@@ -159,17 +163,17 @@ namespace Renci.SshNet
                 throw new SshException(string.Format(CultureInfo.CurrentCulture, "Port forwarding for '{0}' port '{1}' failed to start.", Host, Port));
             }
 
-            _isStarted = true;
+            _status = ForwardedPortStatus.Started;
         }
 
         /// <summary>
         /// Stops remote port forwarding.
         /// </summary>
-        /// <param name="timeout">The maximum amount of time to wait for pending requests to finish processing.</param>
+        /// <param name="timeout">The maximum amount of time to wait for the port to stop.</param>
         protected override void StopPort(TimeSpan timeout)
         {
-            // mark forwarded port stopped, this also causes open of new channels to be rejected
-            _isStarted = false;
+            if (!ForwardedPortStatus.ToStopping(ref _status))
+                return;
 
             base.StopPort(timeout);
 
@@ -185,9 +189,11 @@ namespace Renci.SshNet
             Session.RequestFailureReceived -= Session_RequestFailure;
             Session.ChannelOpenReceived -= Session_ChannelOpening;
 
-            // wait for pending requests to complete
-            _pendingRequestsCountdown.Signal();
-            _pendingRequestsCountdown.Wait(timeout);
+            // wait for pending channels to close
+            _pendingChannelCountdown.Signal();
+            _pendingChannelCountdown.Wait(timeout);
+
+            _status = ForwardedPortStatus.Stopped;
         }
 
         /// <summary>
@@ -209,7 +215,7 @@ namespace Renci.SshNet
                 //  Ensure this is the corresponding request
                 if (info.ConnectedAddress == BoundHost && info.ConnectedPort == BoundPort)
                 {
-                    if (!_isStarted)
+                    if (!IsStarted)
                     {
                         Session.SendMessage(new ChannelOpenFailureMessage(channelOpenMessage.LocalChannelNumber, "", ChannelOpenFailureMessage.AdministrativelyProhibited));
                         return;
@@ -219,10 +225,10 @@ namespace Renci.SshNet
                         {
                             // 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-initialize when the port is restarted
-                            // and that time they may still be pending requests
-                            var pendingRequestsCountdown = _pendingRequestsCountdown;
+                            // and that time there may still be pending requests
+                            var pendingChannelCountdown = _pendingChannelCountdown;
 
-                            pendingRequestsCountdown.AddCount();
+                            pendingChannelCountdown.AddCount();
 
                             try
                             {
@@ -244,10 +250,12 @@ namespace Renci.SshNet
                             }
                             finally
                             {
-                                // take into account that countdown event has since been disposed (after waiting for a given timeout)
+                                // take into account that CountdownEvent has since been disposed; when stopping the port we
+                                // wait for a given time for the channels to close, but once that timeout period has elapsed
+                                // the CountdownEvent will be disposed
                                 try
                                 {
-                                    pendingRequestsCountdown.Signal();
+                                    pendingChannelCountdown.Signal();
                                 }
                                 catch (ObjectDisposedException)
                                 {
@@ -271,9 +279,9 @@ namespace Renci.SshNet
         /// initial count of <c>1</c>.
         /// </para>
         /// </remarks>
-        private void InitializePendingRequestsCountdown()
+        private void InitializePendingChannelCountdown()
         {
-            var original = Interlocked.Exchange(ref _pendingRequestsCountdown, new CountdownEvent(1));
+            var original = Interlocked.Exchange(ref _pendingChannelCountdown, new CountdownEvent(1));
             if (original != null)
             {
                 original.Dispose();
@@ -344,10 +352,10 @@ namespace Renci.SshNet
                     globalRequestResponse.Dispose();
                 }
 
-                var pendingRequestsCountdown = _pendingRequestsCountdown;
+                var pendingRequestsCountdown = _pendingChannelCountdown;
                 if (pendingRequestsCountdown != null)
                 {
-                    _pendingRequestsCountdown = null;
+                    _pendingChannelCountdown = null;
                     pendingRequestsCountdown.Dispose();
                 }
             }

+ 108 - 0
src/Renci.SshNet/ForwardedPortStatus.cs

@@ -0,0 +1,108 @@
+using System;
+using System.Threading;
+
+namespace Renci.SshNet
+{
+    internal class ForwardedPortStatus
+    {
+        private readonly int _value;
+        private readonly string _name;
+
+        public static readonly ForwardedPortStatus Stopped = new ForwardedPortStatus(1, "Stopped");
+        public static readonly ForwardedPortStatus Stopping = new ForwardedPortStatus(2, "Stopping");
+        public static readonly ForwardedPortStatus Started = new ForwardedPortStatus(3, "Started");
+        public static readonly ForwardedPortStatus Starting = new ForwardedPortStatus(4, "Starting");
+
+        private ForwardedPortStatus(int value, string name)
+        {
+            _value = value;
+            _name = name;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(obj, null))
+                return false;
+
+            if (ReferenceEquals(this, obj))
+                return true;
+
+            var forwardedPortStatus = obj as ForwardedPortStatus;
+            if (forwardedPortStatus == null)
+                return false;
+
+            return forwardedPortStatus._value == _value;
+        }
+
+        public static bool operator ==(ForwardedPortStatus c1, ForwardedPortStatus c2)
+        {
+            // check if lhs is null
+            if (ReferenceEquals(c1, null))
+            {
+                // check if both lhs and rhs are null
+                return (ReferenceEquals(c2, null));
+            }
+
+            return c1.Equals(c2);
+        }
+
+        public static bool operator !=(ForwardedPortStatus c1, ForwardedPortStatus c2)
+        {
+            return !(c1==c2);
+        }
+
+        public override int GetHashCode()
+        {
+            return _value;
+        }
+
+        public override string ToString()
+        {
+            return _name;
+        }
+
+        public static bool ToStopping(ref ForwardedPortStatus status)
+        {
+            // attempt to transition from Started to Stopping
+            var previousStatus = Interlocked.CompareExchange(ref status, Stopping, Started);
+            if (previousStatus == Stopping || previousStatus == Stopped)
+            {
+                // status is already Stopping or Stopped, so no transition to Stopped is necessary
+                return false;
+            }
+
+            // we've successfully transitioned from Started to Stopping
+            if (status == Stopping)
+                return true;
+
+            // attempt to transition from Starting to Stopping
+            previousStatus = Interlocked.CompareExchange(ref status, Stopping, Starting);
+            if (previousStatus == Stopping || previousStatus == Stopped)
+            {
+                // status is already Stopping or Stopped, so no transition to Stopped is necessary
+                return false;
+            }
+
+            // we've successfully transitioned from Starting to Stopping
+            return status == Stopping;
+        }
+
+        public static bool ToStarting(ref ForwardedPortStatus status)
+        {
+            // attemp to transition from Stopped to Starting
+            var previousStatus = Interlocked.CompareExchange(ref status, Starting, Stopped);
+            if (previousStatus == Starting || previousStatus == Started)
+            {
+                // port is already Starting or Started, so no transition to Starting is necessary
+                return false;
+            }
+
+            // we've successfully transitioned from Stopped to Starting
+            if (status == Starting)
+                return true;
+
+            // there's no valid transition from Stopping to Starting
+            throw new InvalidOperationException("Forwarded port is stopping.");
+        }
+    }
+}

+ 1 - 0
src/Renci.SshNet/Renci.SshNet.csproj

@@ -143,6 +143,7 @@
       <SubType>Code</SubType>
     </Compile>
     <Compile Include="ConnectionInfo.cs" />
+    <Compile Include="ForwardedPortStatus.cs" />
     <Compile Include="HashInfo.cs">
       <SubType>Code</SubType>
     </Compile>