Переглянути джерело

Channel:
* Improve robustness of channels by not letting an exception - that occurs while processing events signaled by session - up to session where this would cause the message loop to interrupt, and hence the SSH client would be disconnected.
Instead, exceptions that are thrown during processing of session events are now signaled using a new Exception event.
* Renamed RequestSuccessed event to RequestSucceeded, fixing a typo.

ChannelDirectTcpip:
* Interrupt blocking receive when forwarded port is closing allowing for a clean close of the channel.
* Send SSH_MSG_CHANNEL_EOF to server when client quits sending data.

ChannelForwardedTcpip:
* Interrupt blocking receive when forwarded port is closing or when an error occurs in the session allowing for a clean close of the channel.

ForwardedPortDynamic:
* When stopping the port, signal all channels that the port is closing and wait for the channels to close.
* Modify Start() and Stop() to throw ObjectDisposedException when instance is disposed.
* Modify Dispose() to also interrupt blocking receive and wait for the channels to close.
* Fixed IPv6 support for SOCKS5.
* Signal exceptions in channels using the Exception event on the forwarded port.

ForwardedPortLocal:
* When stopping the port, signal all channels that the port is closing and wait for the channels to close.
* Modify Start() and Stop() to throw ObjectDisposedException when instance is disposed.
* Modify Dispose() to also interrupt blocking receive and wait for the channels to close.
* Signal exceptions in channels using the Exception event on the forwarded port.

ForwardedPortRemote:
* When stopping the port, cancel the tcpip-forward, interrupt pending channels and for the channels to close.
* Modify Start() and Stop() to throw ObjectDisposedException when instance is disposed.
* Modify Dispose() to also cancel the tcpip-forward, interrupt pending channels and for the channels to close.
* Signal exceptions in channels using the Exception event on the forwarded port.

Overall:
* Add huge set of unit tests for channels and forwarded ports.

Gert Driesen 11 роки тому
батько
коміт
45f99561b4
100 змінених файлів з 8535 додано та 574 видалено
  1. 1 7
      Renci.SshClient/Renci.SshNet.Silverlight/ForwardedPortLocal.SilverlightShared.cs
  2. 1 1
      Renci.SshClient/Renci.SshNet.Silverlight/ForwardedPortRemote.SilverlightShared.cs
  3. 1 1
      Renci.SshClient/Renci.SshNet.Silverlight/Session.SilverlightShared.cs
  4. 122 6
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest.cs
  5. 133 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelStub.cs
  6. 75 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsNotOpen.cs
  7. 125 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsOpen.cs
  8. 75 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsNotConnectedAndChannelIsNotOpen.cs
  9. 76 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsNotConnectedAndChannelIsOpen.cs
  10. 67 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_OnClose_Exception.cs
  11. 94 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen.cs
  12. 67 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelDataReceived_OnData_Exception.cs
  13. 75 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelEofReceived_OnEof_Exception.cs
  14. 67 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelExtendedDataReceived_OnExtendedData_Exception.cs
  15. 67 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelFailureReceived_OnFailure_Exception.cs
  16. 71 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelRequestReceived_OnRequest_Exception.cs
  17. 67 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelSuccessReceived_OnSuccess_Exception.cs
  18. 76 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelWindowAdjustReceived_OnWindowAdjust_Exception.cs
  19. 65 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionDisconnected_OnDisconnected_Exception.cs
  20. 74 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionDisconnected_SessionIsConnectedAndChannelIsOpen.cs
  21. 67 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionErrorOccurred_OnErrorOccurred_Exception.cs
  22. 0 36
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest.cs
  23. 60 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortDisposed.cs
  24. 97 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortNeverStarted.cs
  25. 221 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortStarted_ChannelBound.cs
  26. 149 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortStarted_ChannelNotBound.cs
  27. 100 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortStopped.cs
  28. 76 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_PortDisposed.cs
  29. 93 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_PortNeverStarted.cs
  30. 110 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_PortStarted.cs
  31. 97 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_PortStopped.cs
  32. 112 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_SessionNotConnected.cs
  33. 101 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_SessionNull.cs
  34. 142 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Started_SocketSendShutdownImmediately.cs
  35. 147 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Started_SocketVersionNotSupported.cs
  36. 102 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortDisposed.cs
  37. 85 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortNeverStarted.cs
  38. 207 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortStarted_ChannelBound.cs
  39. 149 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortStarted_ChannelNotBound.cs
  40. 100 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortStopped.cs
  41. 60 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortDisposed.cs
  42. 102 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortNeverStarted.cs
  43. 194 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortStarted_ChannelBound.cs
  44. 107 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortStarted_ChannelNotBound.cs
  45. 105 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortStopped.cs
  46. 84 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_PortDisposed.cs
  47. 109 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_PortNeverStarted.cs
  48. 126 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_PortStarted.cs
  49. 113 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_PortStopped.cs
  50. 117 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_SessionNotConnected.cs
  51. 116 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_SessionNull.cs
  52. 82 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortDisposed.cs
  53. 99 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortNeverStarted.cs
  54. 193 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortStarted_ChannelBound.cs
  55. 107 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortStarted_ChannelNotBound.cs
  56. 107 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortStopped.cs
  57. 76 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortDisposed.cs
  58. 110 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortNeverStarted.cs
  59. 222 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortStarted_ChannelBound.cs
  60. 141 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortStopped.cs
  61. 82 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_PortDisposed.cs
  62. 145 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_PortNeverStarted.cs
  63. 162 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_PortStarted.cs
  64. 161 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_PortStopped.cs
  65. 127 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_SessionNotConnected.cs
  66. 93 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_SessionNull.cs
  67. 226 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Started.cs
  68. 88 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Stop_PortDisposed.cs
  69. 135 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Stop_PortNeverStarted.cs
  70. 13 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Stop_PortStarted_ChannelBound.cs
  71. 13 0
      Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Stop_PortStopped.cs
  72. 12 0
      Renci.SshClient/Renci.SshNet.Tests/Common/AsyncSocketListener.cs
  73. 67 1
      Renci.SshClient/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
  74. 1 7
      Renci.SshClient/Renci.SshNet.WindowsPhone8/ForwardedPortLocal.SilverlightShared.cs
  75. 3 3
      Renci.SshClient/Renci.SshNet/BaseClient.cs
  76. 133 61
      Renci.SshClient/Renci.SshNet/Channels/Channel.cs
  77. 112 53
      Renci.SshClient/Renci.SshNet/Channels/ChannelDirectTcpip.cs
  78. 4 6
      Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.NET40.cs
  79. 98 27
      Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.cs
  80. 14 0
      Renci.SshClient/Renci.SshNet/Channels/IChannelDirectTcpip.cs
  81. 18 6
      Renci.SshClient/Renci.SshNet/Channels/IChannelForwardedTcpip.cs
  82. 68 19
      Renci.SshClient/Renci.SshNet/ForwardedPort.cs
  83. 279 121
      Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.NET.cs
  84. 4 0
      Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.NET40.cs
  85. 71 25
      Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.cs
  86. 124 91
      Renci.SshClient/Renci.SshNet/ForwardedPortLocal.NET.cs
  87. 4 0
      Renci.SshClient/Renci.SshNet/ForwardedPortLocal.NET40.cs
  88. 50 19
      Renci.SshClient/Renci.SshNet/ForwardedPortLocal.cs
  89. 4 0
      Renci.SshClient/Renci.SshNet/ForwardedPortRemote.NET40.cs
  90. 140 76
      Renci.SshClient/Renci.SshNet/ForwardedPortRemote.cs
  91. 1 1
      Renci.SshClient/Renci.SshNet/IForwardedPort.cs
  92. 24 0
      Renci.SshClient/Renci.SshNet/ISession.cs
  93. 4 0
      Renci.SshClient/Renci.SshNet/KeyboardInteractiveAuthenticationMethod.NET40.cs
  94. 19 0
      Renci.SshClient/Renci.SshNet/Messages/Connection/ChannelOpen/ForwardedTcpipChannelInfo.cs
  95. 5 0
      Renci.SshClient/Renci.SshNet/Messages/Connection/ChannelOpenFailureMessage.cs
  96. 4 1
      Renci.SshClient/Renci.SshNet/PasswordAuthenticationMethod.NET40.cs
  97. 4 0
      Renci.SshClient/Renci.SshNet/Session.NET40.cs
  98. 36 2
      Renci.SshClient/Renci.SshNet/Session.cs
  99. 2 3
      Renci.SshClient/Renci.SshNet/SftpClient.NET40.cs
  100. 1 1
      Renci.SshClient/Renci.SshNet/SftpClient.cs

+ 1 - 7
Renci.SshClient/Renci.SshNet.Silverlight/ForwardedPortLocal.SilverlightShared.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Threading;
 
 namespace Renci.SshNet
 {
@@ -8,17 +7,12 @@ namespace Renci.SshNet
     /// </summary>
     public partial class ForwardedPortLocal
     {
-        partial void ExecuteThread(Action action)
-        {
-            ThreadPool.QueueUserWorkItem((o) => action());
-        }
-
         partial void InternalStart()
         {
             throw new NotImplementedException();
         }
 
-        partial void InternalStop()
+        partial void InternalStop(TimeSpan timeout)
         {
             throw new NotImplementedException();
         }

+ 1 - 1
Renci.SshClient/Renci.SshNet.Silverlight/ForwardedPortRemote.SilverlightShared.cs

@@ -10,7 +10,7 @@ namespace Renci.SshNet
     {
         partial void ExecuteThread(Action action)
         {
-            ThreadPool.QueueUserWorkItem((o) => action());
+            ThreadPool.QueueUserWorkItem(o => action());
         }
     }
 }

+ 1 - 1
Renci.SshClient/Renci.SshNet.Silverlight/Session.SilverlightShared.cs

@@ -128,7 +128,7 @@ namespace Renci.SshNet
         /// <exception cref="SocketException">The read failed.</exception>
         partial void SocketRead(int length, ref byte[] buffer)
         {
-            var timeout = Infinite;
+            var timeout = InfiniteTimeSpan;
             var totalBytesReceived = 0;  // how many bytes are already received
 
             do

+ 122 - 6
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest.cs

@@ -2,11 +2,11 @@
 using System.Globalization;
 using System.Net;
 using System.Net.Sockets;
-using System.Runtime;
 using System.Threading;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Moq;
 using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
 using Renci.SshNet.Messages;
 using Renci.SshNet.Messages.Connection;
 using Renci.SshNet.Tests.Common;
@@ -22,7 +22,6 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _localPacketSize;
         private string _remoteHost;
         private uint _port;
-        private Socket _socket;
         private uint _localChannelNumber;
         private uint _remoteWindowSize;
         private uint _remotePacketSize;
@@ -34,8 +33,8 @@ namespace Renci.SshNet.Tests.Classes.Channels
 
             var random = new Random();
 
-            _localWindowSize = (uint) random.Next(0, int.MaxValue);
-            _localPacketSize = (uint) random.Next(0, int.MaxValue);
+            _localWindowSize = (uint) random.Next(2000, 3000);
+            _localPacketSize = (uint) random.Next(1000, 2000);
             _remoteHost = random.Next().ToString(CultureInfo.InvariantCulture);
             _port = (uint) random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort);
             _localChannelNumber = (uint) random.Next(0, int.MaxValue);
@@ -74,7 +73,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
                             new Thread(() =>
                                 {
                                     // sleep for a short period to allow channel to actually start receiving from socket
-                                    Thread.Sleep(1000);
+                                    Thread.Sleep(100);
                                     // raise Closing event on forwarded port
                                     _forwardedPortMock.Raise(p => p.Closing += null, EventArgs.Empty);
                                 });
@@ -88,11 +87,128 @@ namespace Renci.SshNet.Tests.Classes.Channels
                 var client = new Socket(localPortEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                 client.Connect(localPortEndPoint);
 
-                // attempt to receive from socket to verify it was shut down by forwarded port
+                // attempt to receive from socket to verify it was shut down by channel
                 var buffer = new byte[16];
                 var bytesReceived = client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
                 Assert.AreEqual(0, bytesReceived);
             }
         }
+
+        [TestMethod]
+        public void SocketShouldBeClosedAndBindShouldEndWhenOnErrorOccurredIsInvoked()
+        {
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+            _sessionMock.Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.Setup(p => p.SendMessage(It.IsAny<ChannelOpenMessage>()))
+                .Callback<Message>(m => _sessionMock.Raise(p => p.ChannelOpenConfirmationReceived += null,
+                    new MessageEventArgs<ChannelOpenConfirmationMessage>(
+                        new ChannelOpenConfirmationMessage(((ChannelOpenMessage)m).LocalChannelNumber, _remoteWindowSize, _remotePacketSize, _remoteChannelNumber))));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
+                .Callback<WaitHandle>(p => p.WaitOne(-1));
+
+            var localPortEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            using (var localPortListener = new AsyncSocketListener(localPortEndPoint))
+            {
+                localPortListener.Start();
+
+                localPortListener.Connected += socket =>
+                {
+                    var channel = new ChannelDirectTcpip();
+                    channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+                    channel.Open(_remoteHost, _port, _forwardedPortMock.Object, socket);
+
+                    var signalSessionErrorOccurredThread =
+                        new Thread(() =>
+                            {
+                                // sleep for a short period to allow channel to actually start receiving from socket
+                                Thread.Sleep(100);
+                                // raise ErrorOccured event on session
+                                _sessionMock.Raise(s => s.ErrorOccured += null,
+                                    new ExceptionEventArgs(new SystemException()));
+                            });
+                    signalSessionErrorOccurredThread.Start();
+
+                    channel.Bind();
+
+                    signalSessionErrorOccurredThread.Join();
+                };
+
+                var client = new Socket(localPortEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+                client.Connect(localPortEndPoint);
+
+                // attempt to receive from socket to verify it was shut down by channel
+                var buffer = new byte[16];
+                var bytesReceived = client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
+                Assert.AreEqual(0, bytesReceived);
+            }
+        }
+
+        [TestMethod]
+        public void SocketShouldBeClosedAndEofShouldBeSentToServerWhenClientShutsDownSocket()
+        {
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+            _sessionMock.Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.Setup(p => p.SendMessage(It.IsAny<ChannelOpenMessage>()))
+                .Callback<Message>(m => _sessionMock.Raise(p => p.ChannelOpenConfirmationReceived += null,
+                    new MessageEventArgs<ChannelOpenConfirmationMessage>(
+                        new ChannelOpenConfirmationMessage(((ChannelOpenMessage) m).LocalChannelNumber,
+                            _remoteWindowSize, _remotePacketSize, _remoteChannelNumber))));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
+                .Callback<WaitHandle>(p => p.WaitOne(-1));
+            _sessionMock.Setup(p => p.SendMessage(It.IsAny<ChannelEofMessage>()))
+                .Callback<Message>(
+                    m => new Thread(() =>
+                        {
+                            Thread.Sleep(50);
+                            _sessionMock.Raise(s => s.ChannelEofReceived += null,
+                                new MessageEventArgs<ChannelEofMessage>(new ChannelEofMessage(_localChannelNumber)));
+                        }).Start());
+            _sessionMock.Setup(p => p.SendMessage(It.IsAny<ChannelCloseMessage>()))
+                .Callback<Message>(
+                    m => new Thread(() =>
+                        {
+                            Thread.Sleep(50);
+                            _sessionMock.Raise(s => s.ChannelCloseReceived += null,
+                                new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(_localChannelNumber)));
+                        }).Start());
+            var channelBindFinishedWaitHandle = new ManualResetEvent(false);
+            Socket handler = null;
+            ChannelDirectTcpip channel = null;
+
+            var localPortEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+            using (var localPortListener = new AsyncSocketListener(localPortEndPoint))
+            {
+                localPortListener.Start();
+
+                localPortListener.Connected += socket =>
+                {
+                    channel = new ChannelDirectTcpip();
+                    channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+                    channel.Open(_remoteHost, _port, _forwardedPortMock.Object, socket);
+                    channel.Bind();
+
+                    handler = socket;
+
+                    channelBindFinishedWaitHandle.Set();
+                };
+
+                var client = new Socket(localPortEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+                client.Connect(localPortEndPoint);
+                client.Shutdown(SocketShutdown.Send);
+
+                channelBindFinishedWaitHandle.WaitOne();
+
+                Assert.IsNotNull(handler);
+                Assert.IsFalse(handler.Connected);
+
+                _sessionMock.Verify(p => p.SendMessage(It.IsAny<ChannelEofMessage>()), Times.Once);
+                _sessionMock.Verify(p => p.SendMessage(It.IsAny<ChannelCloseMessage>()), Times.Never);
+
+                channel.Close();
+
+                _sessionMock.Verify(p => p.SendMessage(It.IsAny<ChannelEofMessage>()), Times.Once);
+                _sessionMock.Verify(p => p.SendMessage(It.IsAny<ChannelCloseMessage>()), Times.Once);
+            }
+        }
     }
 }

+ 133 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelStub.cs

@@ -0,0 +1,133 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    internal class ChannelStub : Channel
+    {
+        public override ChannelTypes ChannelType
+        {
+            get { return ChannelTypes.X11; }
+        }
+
+        public ChannelStub()
+        {
+            OnErrorOccurredInvocations = new List<Exception>();
+        }
+
+        public IList<Exception> OnErrorOccurredInvocations { get; private set; }
+
+        public Exception OnCloseException { get; set; }
+
+        public Exception OnDataException { get; set; }
+
+        public Exception OnDisconnectedException { get; set; }
+
+        public Exception OnEofException{ get; set; }
+
+        public Exception OnErrorOccurredException { get; set; }
+
+        public Exception OnExtendedDataException { get; set; }
+
+        public Exception OnFailureException { get; set; }
+
+        public Exception OnRequestException { get; set; }
+
+        public Exception OnSuccessException { get; set; }
+
+        public Exception OnWindowAdjustException { get; set; }
+
+        public void SetIsOpen(bool value)
+        {
+            IsOpen = value;
+        }
+
+        public void InitializeRemoteChannelInfo(uint remoteChannelNumber, uint remoteWindowSize, uint remotePacketSize)
+        {
+            base.InitializeRemoteInfo(remoteChannelNumber, remoteWindowSize, remotePacketSize);
+        }
+
+        protected override void OnClose()
+        {
+            base.OnClose();
+
+            if (OnCloseException != null)
+                throw OnCloseException;
+        }
+
+        protected override void OnData(byte[] data)
+        {
+            base.OnData(data);
+
+            if (OnDataException != null)
+                throw OnDataException;
+        }
+
+        protected override void OnDisconnected()
+        {
+            base.OnDisconnected();
+
+            if (OnDisconnectedException != null)
+                throw OnDisconnectedException;
+        }
+
+        protected override void OnEof()
+        {
+            base.OnEof();
+
+            if (OnEofException != null)
+                throw OnEofException;
+        }
+
+        protected override void OnExtendedData(byte[] data, uint dataTypeCode)
+        {
+            base.OnExtendedData(data, dataTypeCode);
+
+            if (OnExtendedDataException != null)
+                throw OnExtendedDataException;
+        }
+
+        protected override void OnErrorOccured(Exception exp)
+        {
+            OnErrorOccurredInvocations.Add(exp);
+
+            if (OnErrorOccurredException != null)
+                throw OnErrorOccurredException;
+        }
+
+        protected override void OnFailure()
+        {
+            base.OnFailure();
+
+            if (OnFailureException != null)
+                throw OnFailureException;
+        }
+
+        protected override void OnRequest(RequestInfo info)
+        {
+            base.OnRequest(info);
+
+            if (OnRequestException != null)
+                throw OnRequestException;
+        }
+
+        protected override void OnSuccess()
+        {
+            base.OnSuccess();
+
+            if (OnSuccessException != null)
+                throw OnSuccessException;
+        }
+
+        protected override void OnWindowAdjust(uint bytesToAdd)
+        {
+            base.OnWindowAdjust(bytesToAdd);
+
+            if (OnWindowAdjustException != null)
+                throw OnWindowAdjustException;
+        }
+    }
+}

+ 75 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsNotOpen.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_Close_SessionIsConnectedAndChannelIsNotOpen
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private Channel _channel;
+        private List<ChannelEventArgs> _channelClosedRegister;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+            _sessionMock.Setup(p => p.IsConnected).Returns(true);
+
+            _channel = new ChannelStub();
+            _channel.Closed += (sender, args) =>
+            {
+                lock (this)
+                {
+                    _channelClosedRegister.Add(args);
+                }
+            };
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+        }
+
+        private void Act()
+        {
+            _channel.Close();
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void SendMessageOnSessionShouldNeverBeInvoked()
+        {
+            _sessionMock.Verify(p => p.SendMessage(It.IsAny<Message>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelClosedRegister.Count);
+        }
+    }
+}

+ 125 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsConnectedAndChannelIsOpen.cs

@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_Close_SessionIsConnectedAndChannelIsOpen
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private ChannelStub _channel;
+        private Stopwatch _closeTimer;
+        private List<ChannelEventArgs> _channelClosedRegister;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(0, int.MaxValue);
+            _closeTimer = new Stopwatch();
+            _channelClosedRegister = new List<ChannelEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+            _sessionMock.Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.Setup(p => p.SendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
+                .Callback<WaitHandle>(w =>
+                    {
+                        new Thread(() =>
+                            {
+                                Thread.Sleep(100);
+                                // raise ChannelCloseReceived event to set waithandle for receiving
+                                // SSH_MSG_CHANNEL_CLOSE message from server which is waited on after
+                                // sending the SSH_MSG_CHANNEL_CLOSE message to the server
+                                _sessionMock.Raise(s => s.ChannelCloseReceived += null,
+                                    new MessageEventArgs<ChannelCloseMessage>(
+                                        new ChannelCloseMessage(_localChannelNumber)));
+                            }).Start();
+                        _closeTimer.Start();
+                        try
+                        {
+                            w.WaitOne();
+                        }
+                        finally
+                        {
+                            _closeTimer.Stop();
+                        }
+                    });
+
+            _channel = new ChannelStub();
+            _channel.Closed += (sender, args) =>
+            {
+                lock (this)
+                {
+                    _channelClosedRegister.Add(args);
+                }
+            };
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.SetIsOpen(true);
+        }
+
+        private void Act()
+        {
+            _channel.Close();
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void SendMessageOnSessionShouldBeInvokedOnceForChannelCloseMessage()
+        {
+            _sessionMock.Verify(
+                p => p.SendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Once);
+        }
+
+        [TestMethod]
+        public void WaitOnHandleOnSessionShouldBeInvokedOnce()
+        {
+            _sessionMock.Verify(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()), Times.Once);
+        }
+
+        [TestMethod]
+        public void WaitOnHandleOnSessionShouldWaitForChannelCloseMessageToBeReceived()
+        {
+            Assert.IsTrue(_closeTimer.ElapsedMilliseconds >= 100, "Elapsed milliseconds=" + _closeTimer.ElapsedMilliseconds);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelClosedRegister.Count);
+            Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
+        }
+    }
+}

+ 75 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsNotConnectedAndChannelIsNotOpen.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_Close_SessionIsNotConnectedAndChannelIsNotOpen
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private Channel _channel;
+        private List<ChannelEventArgs> _channelClosedRegister;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint) random.Next(0, int.MaxValue);
+            _localPacketSize = (uint) random.Next(0, int.MaxValue);
+            _localChannelNumber = (uint) random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+            _sessionMock.Setup(p => p.IsConnected).Returns(false);
+
+            _channel = new ChannelStub();
+            _channel.Closed += (sender, args) =>
+            {
+                lock (this)
+                {
+                    _channelClosedRegister.Add(args);
+                }
+            };
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+        }
+
+        private void Act()
+        {
+            _channel.Close();
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void SendMessageOnSessionShouldNeverBeInvoked()
+        {
+            _sessionMock.Verify(p => p.SendMessage(It.IsAny<Message>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelClosedRegister.Count);
+        }
+    }
+}

+ 76 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Close_SessionIsNotConnectedAndChannelIsOpen.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_Close_SessionIsNotConnectedAndChannelIsOpen
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private List<ChannelEventArgs> _channelClosedRegister;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+            _sessionMock.Setup(p => p.IsConnected).Returns(false);
+
+            _channel = new ChannelStub();
+            _channel.Closed += (sender, args) =>
+                {
+                    lock (this)
+                    {
+                        _channelClosedRegister.Add(args);
+                    }
+                };
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.SetIsOpen(true);
+        }
+
+        private void Act()
+        {
+            _channel.Close();
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void SendMessageOnSessionShouldNeverBeInvoked()
+        {
+            _sessionMock.Verify(p => p.SendMessage(It.IsAny<Message>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelClosedRegister.Count);
+        }
+    }
+}

+ 67 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_OnClose_Exception.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelCloseReceived_OnClose_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onCloseException;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(1000, int.MaxValue);
+            _localPacketSize = _localWindowSize - 1;
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _onCloseException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.OnCloseException = _onCloseException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.ChannelCloseReceived += null,
+                new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(_localChannelNumber)));
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onCloseException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_onCloseException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 94 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private IList<ChannelEventArgs> _channelClosedRegister;
+        private ChannelStub _channel;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+            _sessionMock.Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.Setup(p => p.SendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)));
+
+            _channel = new ChannelStub();
+            _channel.Closed += (sender, args) =>
+                {
+                    lock (this)
+                    {
+                        _channelClosedRegister.Add(args);
+                    }
+                };
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.SetIsOpen(true);
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(p => p.ChannelCloseReceived += null,
+                new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(_localChannelNumber)));
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void SendMessageOnSessionShouldBeInvokedOnceForChannelCloseMessage()
+        {
+            _sessionMock.Verify(
+                p => p.SendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Once);
+        }
+
+        [TestMethod]
+        public void WaitOnHandleOnSessionShouldNeverBeInvoked()
+        {
+            _sessionMock.Verify(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelClosedRegister.Count);
+            Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
+        }
+    }
+}

+ 67 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelDataReceived_OnData_Exception.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelDataReceived_OnData_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onDataException;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(1000, int.MaxValue);
+            _localPacketSize = _localWindowSize - 1;
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _onDataException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.OnDataException = _onDataException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.ChannelDataReceived += null,
+                new MessageEventArgs<ChannelDataMessage>(new ChannelDataMessage(_localChannelNumber, new byte[0])));
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onDataException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_onDataException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 75 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelEofReceived_OnEof_Exception.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelEofReceived_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onEofException;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(0, 1000);
+            _localPacketSize = (uint)random.Next(1001, int.MaxValue);
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(0, int.MaxValue);
+            _onEofException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.SetIsOpen(true);
+            _channel.OnEofException = _onEofException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.ChannelEofReceived += null,
+                new MessageEventArgs<ChannelEofMessage>(new ChannelEofMessage(_localChannelNumber)));
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onEofException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_onEofException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 67 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelExtendedDataReceived_OnExtendedData_Exception.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelExtendedDataReceived_OnExtendedData_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onExtendedDataException;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(1000, int.MaxValue);
+            _localPacketSize = _localWindowSize - 1;
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _onExtendedDataException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.OnExtendedDataException = _onExtendedDataException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.ChannelExtendedDataReceived += null,
+                new MessageEventArgs<ChannelExtendedDataMessage>(new ChannelExtendedDataMessage(_localChannelNumber, 5, new byte[0])));
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onExtendedDataException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_onExtendedDataException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 67 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelFailureReceived_OnFailure_Exception.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelFailureReceived_OnFailure_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onFailureException;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(0, 1000);
+            _localPacketSize = (uint)random.Next(1001, int.MaxValue);
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _onFailureException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.OnFailureException = _onFailureException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.ChannelFailureReceived += null,
+                new MessageEventArgs<ChannelFailureMessage>(new ChannelFailureMessage(_localChannelNumber)));
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onFailureException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_onFailureException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 71 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelRequestReceived_OnRequest_Exception.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelRequestReceived_OnRequest_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onRequestException;
+        private SignalRequestInfo _requestInfo;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(1000, int.MaxValue);
+            _localPacketSize = _localWindowSize - 1;
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _onRequestException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+            _requestInfo = new SignalRequestInfo("ABC");
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _sessionMock.Setup(p => p.ConnectionInfo)
+                .Returns(new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "password")));
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.OnRequestException = _onRequestException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.ChannelRequestReceived += null,
+                new MessageEventArgs<ChannelRequestMessage>(new ChannelRequestMessage(_localChannelNumber, _requestInfo)));
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onRequestException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_onRequestException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 67 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelSuccessReceived_OnSuccess_Exception.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelSuccessReceived_OnSuccess_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onSuccessException;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(0, 1000);
+            _localPacketSize = (uint)random.Next(1001, int.MaxValue);
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _onSuccessException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.OnSuccessException = _onSuccessException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.ChannelSuccessReceived += null,
+                new MessageEventArgs<ChannelSuccessMessage>(new ChannelSuccessMessage(_localChannelNumber)));
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onSuccessException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_onSuccessException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 76 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelWindowAdjustReceived_OnWindowAdjust_Exception.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelWindowAdjustReceived_OnWindowAdjust_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onWindowAdjustException;
+        private uint _bytesToAdd;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(1000, int.MaxValue);
+            _localPacketSize = _localWindowSize - 1;
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(1000, int.MaxValue);
+            _remotePacketSize = _localWindowSize - 1;
+            _bytesToAdd = (uint)random.Next(0, int.MaxValue);
+            _onWindowAdjustException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.OnWindowAdjustException = _onWindowAdjustException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.ChannelWindowAdjustReceived += null,
+                new MessageEventArgs<ChannelWindowAdjustMessage>(new ChannelWindowAdjustMessage(_localChannelNumber, _bytesToAdd)));
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onWindowAdjustException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_onWindowAdjustException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 65 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionDisconnected_OnDisconnected_Exception.cs

@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionDisconnected_OnDisconnected_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onDisconnectedException;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(1000, int.MaxValue);
+            _localPacketSize = _localWindowSize - 1;
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _onDisconnectedException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.OnDisconnectedException = _onDisconnectedException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.Disconnected += null, EventArgs.Empty);
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onDisconnectedException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_onDisconnectedException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 74 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionDisconnected_SessionIsConnectedAndChannelIsOpen.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionDisconnected_SessionIsConnectedAndChannelIsOpen
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private ChannelStub _channel;
+        private List<ChannelEventArgs> _channelClosedRegister;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+            _sessionMock.Setup(p => p.IsConnected).Returns(true);
+
+            _channel = new ChannelStub();
+            _channel.Closed += (sender, args) =>
+                {
+                    lock (this)
+                    {
+                        _channelClosedRegister.Add(args);
+                    }
+                };
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.SetIsOpen(true);
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.Disconnected += null, EventArgs.Empty);
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelClosedRegister.Count);
+        }
+    }
+}

+ 67 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionErrorOccurred_OnErrorOccurred_Exception.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionErrorOccurred_OnErrorOccurred_Exception
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _localChannelNumber;
+        private ChannelStub _channel;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private Exception _onErrorOccurredException;
+        private Exception _errorOccurredException;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            var random = new Random();
+            _localWindowSize = (uint)random.Next(1000, int.MaxValue);
+            _localPacketSize = _localWindowSize - 1;
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _onErrorOccurredException = new SystemException();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+            _errorOccurredException = new SystemException();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.NextChannelNumber).Returns(_localChannelNumber);
+
+            _channel = new ChannelStub();
+            _channel.Initialize(_sessionMock.Object, _localWindowSize, _localPacketSize);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.OnErrorOccurredException = _onErrorOccurredException;
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(s => s.ErrorOccured += null, new ExceptionEventArgs(_errorOccurredException));
+        }
+
+        [TestMethod]
+        public void ExceptionEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelExceptionRegister.Count);
+            Assert.AreSame(_onErrorOccurredException, _channelExceptionRegister[0].Exception);
+        }
+
+        [TestMethod]
+        public void OnErrorOccuredShouldBeInvokedOnce()
+        {
+            Assert.AreEqual(1, _channel.OnErrorOccurredInvocations.Count);
+            Assert.AreSame(_errorOccurredException, _channel.OnErrorOccurredInvocations[0]);
+        }
+    }
+}

+ 0 - 36
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest.cs

@@ -9,42 +9,6 @@ namespace Renci.SshNet.Tests.Classes
     [TestClass]
     public partial class ForwardedPortDynamicTest : TestBase
     {
-        /// <summary>
-        ///A test for Stop
-        ///</summary>
-        [TestMethod()]
-        public void StopTest()
-        {
-            uint port = 0; // TODO: Initialize to an appropriate value
-            ForwardedPortDynamic target = new ForwardedPortDynamic(port); // TODO: Initialize to an appropriate value
-            target.Stop();
-            Assert.Inconclusive("A method that does not return a value cannot be verified.");
-        }
-
-        /// <summary>
-        ///A test for Start
-        ///</summary>
-        [TestMethod()]
-        public void StartTest()
-        {
-            uint port = 0; // TODO: Initialize to an appropriate value
-            ForwardedPortDynamic target = new ForwardedPortDynamic(port); // TODO: Initialize to an appropriate value
-            target.Start();
-            Assert.Inconclusive("A method that does not return a value cannot be verified.");
-        }
-
-        /// <summary>
-        ///A test for Dispose
-        ///</summary>
-        [TestMethod()]
-        public void DisposeTest()
-        {
-            uint port = 0; // TODO: Initialize to an appropriate value
-            ForwardedPortDynamic target = new ForwardedPortDynamic(port); // TODO: Initialize to an appropriate value
-            target.Dispose();
-            Assert.Inconclusive("A method that does not return a value cannot be verified.");
-        }
-
         /// <summary>
         ///A test for ForwardedPortDynamic Constructor
         ///</summary>

+ 60 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortDisposed.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Dispose_PortDisposed
+    {
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+
+            _forwardedPort = new ForwardedPortDynamic("host", 22);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Dispose();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 97 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortNeverStarted.cs

@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Dispose_PortNeverStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _endpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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);
+
+            _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;
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_endpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 221 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortStarted_ChannelBound.cs

@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+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.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Dispose_PortStarted_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 IPEndPoint _endpoint;
+        private Socket _client;
+        private IPEndPoint _remoteEndpoint;
+        private string _userName;
+        private TimeSpan _expectedElapsedTime;
+        private TimeSpan _elapsedTimeOfStop;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_client != null)
+            {
+                _client.Dispose();
+                _client = null;
+            }
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = 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));
+            _expectedElapsedTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
+            _userName = random.Next().ToString(CultureInfo.InvariantCulture);
+            _forwardedPort = new ForwardedPortDynamic(_endpoint.Address.ToString(), (uint)_endpoint.Port);
+
+            _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(_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.IsOpen).Returns(true);
+            _channelMock.Setup(p => p.Bind()).Callback(() =>
+                {
+                    Thread.Sleep(_expectedElapsedTime);
+                    if (handlerSocket != null && handlerSocket.Connected)
+                        handlerSocket.Shutdown(SocketShutdown.Both);
+                });
+            _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()
+        {
+            var stopwatch = new Stopwatch();
+            stopwatch.Start();
+
+            _forwardedPort.Dispose();
+
+            stopwatch.Stop();
+            _elapsedTimeOfStop = stopwatch.Elapsed;
+        }
+
+        [TestMethod]
+        public void StopShouldBlockUntilBoundChannelHasClosed()
+        {
+            Assert.IsTrue(_elapsedTimeOfStop >= _expectedElapsedTime);
+            Assert.IsTrue(_elapsedTimeOfStop < _expectedElapsedTime.Add(TimeSpan.FromMilliseconds(200)));
+        }
+
+        [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 ExistingConnectionShouldBeClosed()
+        {
+            try
+            {
+                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [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[16];
+            client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
+        }
+    }
+}

+ 149 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortStarted_ChannelNotBound.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Dispose_PortStarted_ChannelNotBound
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _endpoint;
+        private Socket _client;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_client != null)
+            {
+                _client.Dispose();
+                _client = null;
+            }
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _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.Close());
+            _channelMock.Setup(p => p.Dispose());
+
+            _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;
+            _forwardedPort.Start();
+
+            _client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
+                {
+                    ReceiveTimeout = 500,
+                    SendTimeout = 500,
+                    SendBufferSize = 0
+                };
+            _client.Connect(_endpoint);
+
+            // allow for client socket to establish connection
+            Thread.Sleep(50);
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Stop();
+        }
+
+        [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 ExistingConnectionShouldBeClosed()
+        {
+            try
+            {
+                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void CloseOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Close(), Times.Once);
+        }
+
+        [TestMethod]
+        public void DisposeOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Dispose(), Times.Once);
+        }
+    }
+}

+ 100 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Dispose_PortStopped.cs

@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Dispose_PortStopped
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _endpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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);
+
+            _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;
+            _forwardedPort.Start();
+            _forwardedPort.Stop();
+
+            _closingRegister.Clear();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_endpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 76 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_PortDisposed.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Start_PortDisposed
+    {
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private ObjectDisposedException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+
+            _forwardedPort = new ForwardedPortDynamic("host", 22);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Dispose();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowObjectDisposedException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual(_forwardedPort.GetType().FullName, _actualException.ObjectName);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 93 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_PortNeverStarted.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Start_PortNeverStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _endpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _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);
+
+            _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;
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Start();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnTrue()
+        {
+            Assert.IsTrue(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                client.Connect(_endpoint);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 110 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_PortStarted.cs

@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Start_PortStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _endpoint;
+        private InvalidOperationException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _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);
+
+            _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;
+            _forwardedPort.Start();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (InvalidOperationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowInvalidOperatationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual("Forwarded port is already started.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnTrue()
+        {
+            Assert.IsTrue(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                client.Connect(_endpoint);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 97 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_PortStopped.cs

@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Start_PortStopped
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _endpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _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);
+
+            _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;
+            _forwardedPort.Start();
+            _forwardedPort.Stop();
+
+            _closingRegister.Clear();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Start();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnTrue()
+        {
+            Assert.IsTrue(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                client.Connect(_endpoint);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 112 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_SessionNotConnected.cs

@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Start_SessionNotConnected
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private SshConnectionException _actualException;
+        private IPEndPoint _endpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(1));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.IsConnected).Returns(false);
+            _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
+
+            _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;
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (SshConnectionException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowSshConnectionException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual("Client not connected.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_endpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 101 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Start_SessionNull.cs

@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Start_SessionNull
+    {
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private InvalidOperationException _actualException;
+        private IPEndPoint _endpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _forwardedPort = new ForwardedPortDynamic(_endpoint.Address.ToString(), (uint)_endpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (InvalidOperationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowInvalidOperationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual("Forwarded port is not added to a client.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_endpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 142 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Started_SocketSendShutdownImmediately.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Started_SocketSendShutdownImmediately
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private Socket _client;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private int _bytesReceived;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null && _forwardedPort.IsStarted)
+            {
+                _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
+                _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(5));
+                _forwardedPort.Stop();
+            }
+            if (_client != null)
+            {
+                if (_client.Connected)
+                {
+                    _client.Shutdown(SocketShutdown.Both);
+                    _client.Close();
+                    _client = null;
+                }
+            }
+        }
+
+        private void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _channelMock = new Mock<IChannelDirectTcpip>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.Setup(p => p.CreateChannelDirectTcpip()).Returns(_channelMock.Object);
+            _channelMock.Setup(p => p.Close());
+            _channelMock.Setup(p => p.Dispose());
+
+            _forwardedPort = new ForwardedPortDynamic(8122);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+
+            var endPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _client = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+            _client.Connect(endPoint);
+        }
+
+        private void Act()
+        {
+            _client.Shutdown(SocketShutdown.Send);
+
+            var buffer = new byte[1];
+            _bytesReceived = _client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
+        }
+
+        [TestMethod]
+        public void SocketShouldNotBeConnected()
+        {
+            Assert.IsFalse(_client.Connected);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldShutdownSendOnSocket()
+        {
+            Assert.AreEqual(0, _bytesReceived);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNeverBeFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count, GetExceptions());
+        }
+
+        [TestMethod]
+        public void CreateChannelDirectTcpipOnSessionShouldBeInvokedOnce()
+        {
+            _sessionMock.Verify(p => p.CreateChannelDirectTcpip(), 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 string GetExceptions()
+        {
+            var sb = new StringBuilder();
+
+            foreach (var exceptionEventArgs in _exceptionRegister)
+            {
+                if (sb.Length > 0)
+                    sb.AppendLine();
+                sb.Append(exceptionEventArgs.Exception);
+            }
+
+            return sb.ToString();
+        }
+    }
+}

+ 147 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Started_SocketVersionNotSupported.cs

@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Started_SocketVersionNotSupported
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private Socket _client;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private int _bytesReceived;
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null && _forwardedPort.IsStarted)
+            {
+                _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
+                _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(5));
+                _forwardedPort.Stop();
+            }
+            if (_client != null)
+            {
+                if (_client.Connected)
+                {
+                    _client.Shutdown(SocketShutdown.Both);
+                    _client.Close();
+                    _client = null;
+                }
+            }
+        }
+
+        private void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _channelMock = new Mock<IChannelDirectTcpip>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.Setup(p => p.CreateChannelDirectTcpip()).Returns(_channelMock.Object);
+            _channelMock.Setup(p => p.Close());
+            _channelMock.Setup(p => p.Dispose());
+
+            _forwardedPort = new ForwardedPortDynamic(8122);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+
+            var endPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _client = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+            _client.Connect(endPoint);
+        }
+
+        private void Act()
+        {
+            var buffer = new byte[] {0x07};
+            _client.Send(buffer, 0, buffer.Length, SocketFlags.None);
+            _bytesReceived = _client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
+        }
+
+        [TestMethod]
+        public void SocketShouldBeConnected()
+        {
+            Assert.IsTrue(_client.Connected);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldShutdownSendOnSocket()
+        {
+            Assert.AreEqual(0, _bytesReceived);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _exceptionRegister.Count, GetExceptions());
+
+            var exception = _exceptionRegister[0].Exception;
+            Assert.IsNotNull(exception);
+            var notSupportedException = exception as NotSupportedException;
+            Assert.IsNotNull(notSupportedException, exception.ToString());
+            Assert.AreEqual("SOCKS version 7 is not supported.", notSupportedException.Message);
+        }
+
+        [TestMethod]
+        public void CreateChannelDirectTcpipOnSessionShouldBeInvokedOnce()
+        {
+            _sessionMock.Verify(p => p.CreateChannelDirectTcpip(), 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 string GetExceptions()
+        {
+            var sb = new StringBuilder();
+
+            foreach (var exceptionEventArgs in _exceptionRegister)
+            {
+                if (sb.Length > 0)
+                    sb.AppendLine();
+                sb.Append(exceptionEventArgs.Exception);
+            }
+
+            return sb.ToString();
+        }
+    }
+}

+ 102 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortDisposed.cs

@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Stop_PortDisposed
+    {
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private ObjectDisposedException _actualException;
+        private IPEndPoint _endpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _forwardedPort = new ForwardedPortDynamic(_endpoint.Address.ToString(), (uint)_endpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Dispose();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Stop();
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StopShouldThrowObjectDisposedException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual(_forwardedPort.GetType().FullName, _actualException.ObjectName);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_endpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 85 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortNeverStarted.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Stop_PortNeverStarted
+    {
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _endpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _forwardedPort = new ForwardedPortDynamic(_endpoint.Address.ToString(), (uint)_endpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Stop();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_endpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 207 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortStarted_ChannelBound.cs

@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+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.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Stop_PortStarted_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 IPEndPoint _endpoint;
+        private Socket _client;
+        private IPEndPoint _remoteEndpoint;
+        private string _userName;
+        private TimeSpan _expectedElapsedTime;
+        private TimeSpan _elapsedTimeOfStop;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_client != null)
+            {
+                _client.Dispose();
+                _client = null;
+            }
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = 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));
+            _expectedElapsedTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
+            _userName = random.Next().ToString(CultureInfo.InvariantCulture);
+
+            _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);
+
+            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.IsOpen).Returns(true);
+            _channelMock.Setup(p => p.Bind()).Callback(() =>
+                {
+                    Thread.Sleep(_expectedElapsedTime);
+                    if (handlerSocket != null && handlerSocket.Connected)
+                        handlerSocket.Shutdown(SocketShutdown.Both);
+                });
+            _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()
+        {
+            var stopwatch = new Stopwatch();
+            stopwatch.Start();
+
+            _forwardedPort.Stop();
+
+            stopwatch.Stop();
+            _elapsedTimeOfStop = stopwatch.Elapsed;
+        }
+
+        [TestMethod]
+        public void StopShouldBlockUntilBoundChannelHasClosed()
+        {
+            Assert.IsTrue(_elapsedTimeOfStop >=_expectedElapsedTime);
+            Assert.IsTrue(_elapsedTimeOfStop < _expectedElapsedTime.Add(TimeSpan.FromMilliseconds(200)));
+        }
+
+        [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 ExistingConnectionShouldBeClosed()
+        {
+            try
+            {
+                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [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[16];
+            client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
+        }
+    }
+}

+ 149 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortStarted_ChannelNotBound.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Stop_PortStarted_ChannelNotBound
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _endpoint;
+        private Socket _client;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_client != null)
+            {
+                _client.Dispose();
+                _client = null;
+            }
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _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.Close());
+            _channelMock.Setup(p => p.Dispose());
+
+            _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;
+            _forwardedPort.Start();
+
+            _client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
+                {
+                    ReceiveTimeout = 500,
+                    SendTimeout = 500,
+                    SendBufferSize = 0
+                };
+            _client.Connect(_endpoint);
+
+            // allow for client socket to establish connection
+            Thread.Sleep(50);
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Stop();
+        }
+
+        [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 ExistingConnectionShouldBeClosed()
+        {
+            try
+            {
+                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void CloseOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Close(), Times.Once);
+        }
+
+        [TestMethod]
+        public void DisposeOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Dispose(), Times.Once);
+        }
+    }
+}

+ 100 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortDynamicTest_Stop_PortStopped.cs

@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortDynamicTest_Stop_PortStopped
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortDynamic _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _endpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _endpoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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);
+
+            _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;
+            _forwardedPort.Start();
+            _forwardedPort.Stop();
+
+            _closingRegister.Clear();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Stop();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_endpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 60 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortDisposed.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Dispose_PortDisposed
+    {
+        private ForwardedPortLocal _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+
+            _forwardedPort = new ForwardedPortLocal("boundHost", "host", 22);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Dispose();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 102 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortNeverStarted.cs

@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Dispose_PortNeverStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+
+            _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));
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint) _localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint) _remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 194 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortStarted_ChannelBound.cs

@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Dispose_PortStarted_ChannelBound
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private Socket _client;
+        private TimeSpan _expectedElapsedTime;
+        private TimeSpan _elapsedTimeOfStop;
+        private Stopwatch _stopwatch;
+
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_client != null)
+            {
+                _client.Dispose();
+                _client = null;
+            }
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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);
+
+            _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.Bind()).Callback(() =>
+                {
+                    Thread.Sleep(_expectedElapsedTime);
+                    if (handlerSocket != null && handlerSocket.Connected)
+                        handlerSocket.Shutdown(SocketShutdown.Both);
+                });
+            _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(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
+                {
+                    ReceiveTimeout = 500,
+                    SendTimeout = 500,
+                    SendBufferSize = 0
+                };
+
+            _stopwatch = new Stopwatch();
+            _stopwatch.Start();
+
+            _client.Connect(_localEndpoint);
+
+            // give client socket time to establish connection
+            Thread.Sleep(50);
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+
+            _stopwatch.Stop();
+            _elapsedTimeOfStop = _stopwatch.Elapsed;
+        }
+
+        [TestMethod]
+        public void StopShouldBlockUntilBoundChannelHasClosed()
+        {
+            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)));
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRefuseNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                    Assert.Fail();
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ExistingConnectionShouldBeClosed()
+        {
+            try
+            {
+                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void OpenOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Open(_forwardedPort.Host, _forwardedPort.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);
+        }
+    }
+}

+ 107 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortStarted_ChannelNotBound.cs

@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Dispose_PortStarted_ChannelNotBound
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _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);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint) _localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint) _remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRefuseNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                    Assert.Fail();
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 105 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Dispose_PortStopped.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Dispose_PortStopped
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+
+            _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));
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port, _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+            _forwardedPort.Stop();
+
+            _closingRegister.Clear();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 84 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_PortDisposed.cs

@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Start_PortDisposed
+    {
+        private ForwardedPortLocal _forwardedPort;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private ObjectDisposedException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Dispose();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowObjectDisposedException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual(_forwardedPort.GetType().FullName, _actualException.ObjectName);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 109 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_PortNeverStarted.cs

@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Start_PortNeverStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _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);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint) _localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint) _remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Start();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnTrue()
+        {
+            Assert.IsTrue(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptNewConnections()
+        {
+            Socket handlerSocket = null;
+
+            _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.Bind()).Callback(() =>
+                {
+                    if (handlerSocket != null && handlerSocket.Connected)
+                        handlerSocket.Shutdown(SocketShutdown.Both);
+                });
+            _channelMock.Setup(p => p.Close());
+            _channelMock.Setup(p => p.Dispose());
+
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                client.Connect(_localEndpoint);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 126 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_PortStarted.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Start_PortStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private InvalidOperationException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _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);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (InvalidOperationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowInvalidOperatationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual("Forwarded port is already started.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnTrue()
+        {
+            Assert.IsTrue(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptNewConnections()
+        {
+            Socket handlerSocket = null;
+
+            _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.Bind()).Callback(() =>
+            {
+                if (handlerSocket != null && handlerSocket.Connected)
+                    handlerSocket.Shutdown(SocketShutdown.Both);
+            });
+            _channelMock.Setup(p => p.Close());
+            _channelMock.Setup(p => p.Dispose());
+
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                client.Connect(_localEndpoint);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 113 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_PortStopped.cs

@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Start_PortStopped
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _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);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+            _forwardedPort.Stop();
+
+            _closingRegister.Clear();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Start();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnTrue()
+        {
+            Assert.IsTrue(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptNewConnections()
+        {
+            Socket handlerSocket = null;
+
+            _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.Bind()).Callback(() =>
+            {
+                if (handlerSocket != null && handlerSocket.Connected)
+                    handlerSocket.Shutdown(SocketShutdown.Both);
+            });
+            _channelMock.Setup(p => p.Close());
+            _channelMock.Setup(p => p.Dispose());
+
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                client.Connect(_localEndpoint);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 117 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_SessionNotConnected.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Start_SessionNotConnected
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private SshConnectionException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
+                _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(1));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.IsConnected).Returns(false);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (SshConnectionException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowSshConnectionException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual("Client not connected.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 116 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Start_SessionNull.cs

@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Start_SessionNull
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private InvalidOperationException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(1));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.IsConnected).Returns(false);
+            _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (InvalidOperationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowInvalidOperationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual("Forwarded port is not added to a client.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 82 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortDisposed.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Stop_PortDisposed
+    {
+        private ForwardedPortLocal _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private ObjectDisposedException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+
+            _forwardedPort = new ForwardedPortLocal("boundHost", "host", 22);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Dispose();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Stop();
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StopShouldThrowObjectDisposedException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual(_forwardedPort.GetType().FullName, _actualException.ObjectName);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 99 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortNeverStarted.cs

@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Stop_PortNeverStarted
+    {
+        private ForwardedPortLocal _forwardedPort;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
+                _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(1));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Stop();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 193 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortStarted_ChannelBound.cs

@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Stop_PortStarted_ChannelBound
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private Socket _client;
+        private TimeSpan _expectedElapsedTime;
+        private TimeSpan _elapsedTimeOfStop;
+        private Stopwatch _stopwatch;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_client != null)
+            {
+                _client.Dispose();
+                _client = null;
+            }
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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);
+
+            _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.Bind()).Callback(() =>
+                {
+                    Thread.Sleep(_expectedElapsedTime);
+                    if (handlerSocket != null && handlerSocket.Connected)
+                        handlerSocket.Shutdown(SocketShutdown.Both);
+                });
+            _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(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
+                {
+                    ReceiveTimeout = 500,
+                    SendTimeout = 500,
+                    SendBufferSize = 0
+                };
+
+            _stopwatch = new Stopwatch();
+            _stopwatch.Start();
+
+            _client.Connect(_localEndpoint);
+
+            // give client socket time to establish connection
+            Thread.Sleep(50);
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Stop();
+
+            _stopwatch.Stop();
+            _elapsedTimeOfStop = _stopwatch.Elapsed;
+        }
+
+        [TestMethod]
+        public void StopShouldBlockUntilBoundChannelHasClosed()
+        {
+            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)));
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRefuseNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                    Assert.Fail();
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ExistingConnectionShouldBeClosed()
+        {
+            try
+            {
+                _client.Send(new byte[] { 0x0a }, 0, 1, SocketFlags.None);
+                Assert.Fail();
+            }
+            catch (SocketException ex)
+            {
+                Assert.AreEqual(SocketError.ConnectionReset, ex.SocketErrorCode);
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void OpenOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Open(_forwardedPort.Host, _forwardedPort.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);
+        }
+    }
+}

+ 107 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortStarted_ChannelNotBound.cs

@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Stop_PortStarted_ChannelNotBound
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelDirectTcpip> _channelMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _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);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint) _localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint) _remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Stop();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRefuseNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                    Assert.Fail();
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 107 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortLocalTest_Stop_PortStopped.cs

@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortLocalTest_Stop_PortStopped
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortLocal _forwardedPort;
+        private IPEndPoint _localEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _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));
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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);
+
+            _forwardedPort = new ForwardedPortLocal(_localEndpoint.Address.ToString(), (uint)_localEndpoint.Port,
+                _remoteEndpoint.Address.ToString(), (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+            _forwardedPort.Stop();
+
+            _closingRegister.Clear();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Stop();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRefuseNewConnections()
+        {
+            using (var client = new Socket(_localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    client.Connect(_localEndpoint);
+                    Assert.Fail();
+                }
+                catch (SocketException ex)
+                {
+                    Assert.AreEqual(SocketError.ConnectionRefused, ex.SocketErrorCode);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 76 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortDisposed.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Dispose_PortDisposed
+    {
+        private ForwardedPortRemote _forwardedPort;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _remoteEndpoint  = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint) _bindEndpoint.Port, _remoteEndpoint.Address, (uint) _remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Dispose();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 110 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortNeverStarted.cs

@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Dispose_PortNeverStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortRemote _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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);
+
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldIgnoreChannelOpenMessagesWhenDisposed()
+        {
+            var channelNumberDisposed = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSizeDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSizeDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddressDisposed = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPortDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed)).Returns(channelMock.Object);
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(new ChannelOpenFailureMessage(channelNumberDisposed, string.Empty,
+                        ChannelOpenFailureMessage.AdministrativelyProhibited)));
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null, new ChannelOpenMessage(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed, new ForwardedTcpipChannelInfo(_forwardedPort.BoundHost, _forwardedPort.BoundPort, originatorAddressDisposed, originatorPortDisposed)));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed), Times.Never);
+            _sessionMock.Verify(p => p.SendMessage(It.Is<ChannelOpenFailureMessage>(c => c.LocalChannelNumber == channelNumberDisposed && c.ReasonCode == ChannelOpenFailureMessage.AdministrativelyProhibited && c.Description == string.Empty && c.Language == null)), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 222 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortStarted_ChannelBound.cs

@@ -0,0 +1,222 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Dispose_PortStarted_ChannelBound
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private Mock<IChannelForwardedTcpip> _channelMock;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private TimeSpan _expectedElapsedTime;
+        private TimeSpan _elapsedTimeOfStop;
+        private uint _remoteChannelNumberWhileClosing;
+        private uint _remoteWindowSizeWhileClosing;
+        private uint _remotePacketSizeWhileClosing;
+        private uint _remoteChannelNumberStarted;
+        private uint _remoteWindowSizeStarted;
+        private uint _remotePacketSizeStarted;
+        private string _originatorAddress;
+        private uint _originatorPort;
+
+        protected ForwardedPortRemote ForwardedPort { get; private set; }
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+
+            var stopwatch = new Stopwatch();
+            stopwatch.Start();
+
+            Act();
+
+            stopwatch.Stop();
+            _elapsedTimeOfStop = stopwatch.Elapsed;
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (ForwardedPort != null)
+            {
+                ForwardedPort.Dispose();
+                ForwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _expectedElapsedTime = TimeSpan.FromMilliseconds(random.Next(100, 500));
+            ForwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+            _remoteChannelNumberWhileClosing = (uint) random.Next(0, 1000);
+            _remoteWindowSizeWhileClosing = (uint) random.Next(0, int.MaxValue);
+            _remotePacketSizeWhileClosing = (uint) random.Next(0, int.MaxValue);
+            _remoteChannelNumberStarted = (uint)random.Next(0, 1000);
+            _remoteWindowSizeStarted = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSizeStarted = (uint)random.Next(0, int.MaxValue);
+            _originatorAddress = random.Next().ToString(CultureInfo.InvariantCulture);
+            _originatorPort = (uint)random.Next(0, int.MaxValue);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _channelMock = new Mock<IChannelForwardedTcpip>(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.RegisterMessage("SSH_MSG_REQUEST_FAILURE"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_CHANNEL_OPEN"));
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.TcpIpForward &&
+                                g.AddressToBind == ForwardedPort.BoundHost &&
+                                g.PortToBind == ForwardedPort.BoundPort)))
+                .Callback(
+                    () =>
+                        _sessionMock.Raise(s => s.RequestSuccessReceived += null,
+                            new MessageEventArgs<RequestSuccessMessage>(new RequestSuccessMessage())));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<WaitHandle>()));
+            _sessionMock.Setup(p => p.SendMessage(It.Is<ChannelOpenFailureMessage>(c => c.LocalChannelNumber == _remoteChannelNumberWhileClosing && c.ReasonCode == ChannelOpenFailureMessage.AdministrativelyProhibited && c.Description == string.Empty && c.Language == null)));
+            _sessionMock.Setup(p => p.CreateChannelForwardedTcpip(_remoteChannelNumberStarted, _remoteWindowSizeStarted, _remotePacketSizeStarted)).Returns(_channelMock.Object);
+            _channelMock.Setup(
+                p =>
+                    p.Bind(
+                        It.Is<IPEndPoint>(
+                            ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port),
+                        ForwardedPort)).Callback(() => Thread.Sleep(_expectedElapsedTime));
+            _channelMock.Setup(p => p.Close());
+            _channelMock.Setup(p => p.Dispose());
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.CancelTcpIpForward &&
+                                g.AddressToBind == ForwardedPort.BoundHost && g.PortToBind == ForwardedPort.BoundPort)));
+            _sessionMock.Setup(p => p.MessageListenerCompleted).Returns(new ManualResetEvent(true));
+
+            ForwardedPort.Closing += (sender, args) =>
+                {
+                    _closingRegister.Add(args);
+                    _sessionMock.Raise(p => p.ChannelOpenReceived += null, new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(_remoteChannelNumberWhileClosing, _remoteWindowSizeWhileClosing, _remotePacketSizeWhileClosing, new ForwardedTcpipChannelInfo(ForwardedPort.BoundHost, ForwardedPort.BoundPort, _originatorAddress, _originatorPort))));
+                };
+            ForwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            ForwardedPort.Session = _sessionMock.Object;
+            ForwardedPort.Start();
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null, new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(_remoteChannelNumberStarted, _remoteWindowSizeStarted, _remotePacketSizeStarted, new ForwardedTcpipChannelInfo(ForwardedPort.BoundHost, ForwardedPort.BoundPort, _originatorAddress, _originatorPort))));
+        }
+
+        protected virtual void Act()
+        {
+            ForwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void StopShouldBlockUntilBoundChannelHasClosed()
+        {
+            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)));
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(ForwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldRejectChannelOpenMessagesThatAreReceivedWhileTheSuccessMessageForTheCancelOfTheForwardedPortIsNotReceived()
+        {
+            _sessionMock.Verify(p => p.SendMessage(new ChannelOpenFailureMessage(_remoteChannelNumberWhileClosing, string.Empty,
+                        ChannelOpenFailureMessage.AdministrativelyProhibited)), Times.Never);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldIgnoreChannelOpenMessagesWhenDisposed()
+        {
+            var channelNumberDisposed = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSizeDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSizeDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddressDisposed = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPortDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed)).Returns(channelMock.Object);
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(new ChannelOpenFailureMessage(channelNumberDisposed, string.Empty,
+                        ChannelOpenFailureMessage.AdministrativelyProhibited)));
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumberDisposed,
+                    initialWindowSizeDisposed, maximumPacketSizeDisposed,
+                    new ForwardedTcpipChannelInfo(ForwardedPort.BoundHost, ForwardedPort.BoundPort,
+                        originatorAddressDisposed, originatorPortDisposed))));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed), Times.Never);
+            _sessionMock.Verify(p => p.SendMessage(It.Is<ChannelOpenFailureMessage>(c => c.LocalChannelNumber == channelNumberDisposed && c.ReasonCode == ChannelOpenFailureMessage.AdministrativelyProhibited && c.Description == string.Empty && c.Language == null)), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void BindOnChannelShouldBeInvokedOnceForChannelOpenedWhileStarted()
+        {
+            _channelMock.Verify(
+                c =>
+                    c.Bind(
+                        It.Is<IPEndPoint>(
+                            ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port),
+                        ForwardedPort), Times.Once);
+        }
+
+        [TestMethod]
+        public void CloseOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Close(), Times.Once);
+        }
+
+        [TestMethod]
+        public void DisposeOnChannelShouldBeInvokedOnce()
+        {
+            _channelMock.Verify(p => p.Dispose(), Times.Once);
+        }
+    }
+}

+ 141 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Dispose_PortStopped.cs

@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Dispose_PortStopped
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        protected ForwardedPortRemote ForwardedPort { get; private set; }
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (ForwardedPort != null)
+            {
+                ForwardedPort.Dispose();
+                ForwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            ForwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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.RegisterMessage("SSH_MSG_REQUEST_FAILURE"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_CHANNEL_OPEN"));
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.TcpIpForward &&
+                                g.AddressToBind == ForwardedPort.BoundHost &&
+                                g.PortToBind == ForwardedPort.BoundPort)))
+                .Callback(
+                    () =>
+                        _sessionMock.Raise(s => s.RequestSuccessReceived += null,
+                            new MessageEventArgs<RequestSuccessMessage>(new RequestSuccessMessage())));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<WaitHandle>()));
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.CancelTcpIpForward &&
+                                g.AddressToBind == ForwardedPort.BoundHost && g.PortToBind == ForwardedPort.BoundPort)))
+                .Callback(
+                    () =>
+                        _sessionMock.Raise(s => s.RequestSuccessReceived += null,
+                            new MessageEventArgs<RequestSuccessMessage>(new RequestSuccessMessage())));
+            _sessionMock.Setup(p => p.MessageListenerCompleted).Returns(new ManualResetEvent(false));
+
+            ForwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            ForwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            ForwardedPort.Session = _sessionMock.Object;
+            ForwardedPort.Start();
+            ForwardedPort.Stop();
+        }
+
+        protected virtual void Act()
+        {
+            ForwardedPort.Dispose();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(ForwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldIgnoreChannelOpenMessagesWhenDisposed()
+        {
+            var channelNumberDisposed = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSizeDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSizeDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddressDisposed = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPortDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed)).Returns(channelMock.Object);
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(new ChannelOpenFailureMessage(channelNumberDisposed, string.Empty,
+                        ChannelOpenFailureMessage.AdministrativelyProhibited)));
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null, new ChannelOpenMessage(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed, new ForwardedTcpipChannelInfo(ForwardedPort.BoundHost, ForwardedPort.BoundPort, originatorAddressDisposed, originatorPortDisposed)));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed), Times.Never);
+            _sessionMock.Verify(p => p.SendMessage(It.Is<ChannelOpenFailureMessage>(c => c.LocalChannelNumber == channelNumberDisposed && c.ReasonCode == ChannelOpenFailureMessage.AdministrativelyProhibited && c.Description == string.Empty && c.Language == null)), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosingShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 82 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_PortDisposed.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Start_PortDisposed
+    {
+        private ForwardedPortRemote _forwardedPort;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private ObjectDisposedException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Dispose();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowObjectDisposedException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual(_forwardedPort.GetType().FullName, _actualException.ObjectName);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 145 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_PortNeverStarted.cs

@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Start_PortNeverStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortRemote _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _sessionMock.Setup(
+                    p =>
+                        p.SendMessage(
+                            It.Is<GlobalRequestMessage>(
+                                g =>
+                                    g.RequestName == GlobalRequestName.CancelTcpIpForward &&
+                                    g.AddressToBind == _forwardedPort.BoundHost && g.PortToBind == _forwardedPort.BoundPort)));
+                _sessionMock.Setup(p => p.MessageListenerCompleted).Returns(new ManualResetEvent(true));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, 1000));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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.RegisterMessage("SSH_MSG_REQUEST_FAILURE"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_CHANNEL_OPEN"));
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.TcpIpForward &&
+                                g.AddressToBind == _forwardedPort.BoundHost &&
+                                g.PortToBind == _forwardedPort.BoundPort)))
+                .Callback(
+                    () =>
+                        _sessionMock.Raise(s => s.RequestSuccessReceived += null,
+                            new MessageEventArgs<RequestSuccessMessage>(new RequestSuccessMessage())));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<WaitHandle>()))
+                .Callback<WaitHandle>(handle => handle.WaitOne());
+
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Start();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnTrue()
+        {
+            Assert.IsTrue(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptNewConnections()
+        {
+            var channelNumber = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSize = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSize = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddress = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPort = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize)).Returns(channelMock.Object);
+            channelMock.Setup(
+                p =>
+                    p.Bind(
+                        It.Is<IPEndPoint>(
+                            ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port),
+                        _forwardedPort));
+            channelMock.Setup(p => p.Close());
+            channelMock.Setup(p => p.Dispose());
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumber, initialWindowSize,
+                    maximumPacketSize,
+                    new ForwardedTcpipChannelInfo(_forwardedPort.BoundHost, _forwardedPort.BoundPort, originatorAddress,
+                        originatorPort))));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize), Times.Once);
+            channelMock.Verify(p => p.Bind(It.Is<IPEndPoint>(ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port), _forwardedPort), Times.Once);
+            channelMock.Verify(p => p.Close(), Times.Once);
+            channelMock.Verify(p => p.Dispose(), Times.Once);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 162 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_PortStarted.cs

@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Start_PortStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortRemote _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private InvalidOperationException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _sessionMock.Setup(
+                    p =>
+                        p.SendMessage(
+                            It.Is<GlobalRequestMessage>(
+                                g =>
+                                    g.RequestName == GlobalRequestName.CancelTcpIpForward &&
+                                    g.AddressToBind == _forwardedPort.BoundHost && g.PortToBind == _forwardedPort.BoundPort)));
+                _sessionMock.Setup(p => p.MessageListenerCompleted).Returns(new ManualResetEvent(true));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, 1000));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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.RegisterMessage("SSH_MSG_REQUEST_FAILURE"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_CHANNEL_OPEN"));
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.TcpIpForward &&
+                                g.AddressToBind == _forwardedPort.BoundHost &&
+                                g.PortToBind == _forwardedPort.BoundPort)))
+                .Callback(
+                    () =>
+                        _sessionMock.Raise(s => s.RequestSuccessReceived += null,
+                            new MessageEventArgs<RequestSuccessMessage>(new RequestSuccessMessage())));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<WaitHandle>()))
+                .Callback<WaitHandle>(handle => handle.WaitOne());
+
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (InvalidOperationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowInvalidOperatationException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual("Forwarded port is already started.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnTrue()
+        {
+            Assert.IsTrue(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptNewConnections()
+        {
+            var channelNumber = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSize = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSize = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddress = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPort = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize)).Returns(channelMock.Object);
+            channelMock.Setup(
+                p =>
+                    p.Bind(
+                        It.Is<IPEndPoint>(
+                            ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port),
+                        _forwardedPort));
+            channelMock.Setup(p => p.Close());
+            channelMock.Setup(p => p.Dispose());
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumber, initialWindowSize,
+                    maximumPacketSize,
+                    new ForwardedTcpipChannelInfo(_forwardedPort.BoundHost, _forwardedPort.BoundPort, originatorAddress,
+                        originatorPort))));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize), Times.Once);
+            channelMock.Verify(p => p.Bind(It.Is<IPEndPoint>(ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port), _forwardedPort), Times.Once);
+            channelMock.Verify(p => p.Close(), Times.Once);
+            channelMock.Verify(p => p.Dispose(), Times.Once);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 161 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_PortStopped.cs

@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Start_PortStopped
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortRemote _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _sessionMock.Setup(
+                    p =>
+                        p.SendMessage(
+                            It.Is<GlobalRequestMessage>(
+                                g =>
+                                    g.RequestName == GlobalRequestName.CancelTcpIpForward &&
+                                    g.AddressToBind == _forwardedPort.BoundHost && g.PortToBind == _forwardedPort.BoundPort)));
+                _sessionMock.Setup(p => p.MessageListenerCompleted).Returns(new ManualResetEvent(true));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, 1000));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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.RegisterMessage("SSH_MSG_REQUEST_FAILURE"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_CHANNEL_OPEN"));
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.TcpIpForward &&
+                                g.AddressToBind == _forwardedPort.BoundHost &&
+                                g.PortToBind == _forwardedPort.BoundPort)))
+                .Callback(
+                    () =>
+                        _sessionMock.Raise(s => s.RequestSuccessReceived += null,
+                            new MessageEventArgs<RequestSuccessMessage>(new RequestSuccessMessage())));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<WaitHandle>()))
+                .Callback<WaitHandle>(handle => handle.WaitOne());
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.CancelTcpIpForward &&
+                                g.AddressToBind == _forwardedPort.BoundHost && g.PortToBind == _forwardedPort.BoundPort)))
+                .Callback(
+                    () =>
+                        _sessionMock.Raise(s => s.RequestSuccessReceived += null,
+                            new MessageEventArgs<RequestSuccessMessage>(new RequestSuccessMessage())));
+            _sessionMock.Setup(p => p.MessageListenerCompleted).Returns(new ManualResetEvent(false));
+
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+            _forwardedPort.Stop();
+
+            _closingRegister.Clear();
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Start();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnTrue()
+        {
+            Assert.IsTrue(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptNewConnections()
+        {
+            var channelNumber = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSize = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSize = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddress = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPort = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize)).Returns(channelMock.Object);
+            channelMock.Setup(
+                p =>
+                    p.Bind(
+                        It.Is<IPEndPoint>(
+                            ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port),
+                        _forwardedPort));
+            channelMock.Setup(p => p.Close());
+            channelMock.Setup(p => p.Dispose());
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumber, initialWindowSize,
+                    maximumPacketSize,
+                    new ForwardedTcpipChannelInfo(_forwardedPort.BoundHost, _forwardedPort.BoundPort, originatorAddress,
+                        originatorPort))));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize), Times.Once);
+            channelMock.Verify(p => p.Bind(It.Is<IPEndPoint>(ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port), _forwardedPort), Times.Once);
+            channelMock.Verify(p => p.Close(), Times.Once);
+            channelMock.Verify(p => p.Dispose(), Times.Once);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 127 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_SessionNotConnected.cs

@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Start_SessionNotConnected
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortRemote _forwardedPort;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private SshConnectionException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object);
+                _connectionInfoMock.Setup(p => p.Timeout).Returns(TimeSpan.FromSeconds(1));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, 1000));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+
+            _sessionMock.Setup(p => p.IsConnected).Returns(false);
+
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port); 
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (SshConnectionException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowSshConnectionException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual("Client not connected.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldIgnoreReceivedSignalForNewConnection()
+        {
+            var channelNumber = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSize = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSize = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddress = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPort = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize)).Returns(channelMock.Object);
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumber, initialWindowSize,
+                    maximumPacketSize,
+                    new ForwardedTcpipChannelInfo(_forwardedPort.BoundHost, _forwardedPort.BoundPort, originatorAddress,
+                        originatorPort))));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize), Times.Never);
+            _sessionMock.Verify(p => p.SendMessage(new ChannelOpenFailureMessage(channelNumber, string.Empty, ChannelOpenFailureMessage.AdministrativelyProhibited)), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}
+

+ 93 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Start_SessionNull.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Start_SessionNull
+    {
+        private ForwardedPortRemote _forwardedPort;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private InvalidOperationException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, 1000));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Start();
+                Assert.Fail();
+            }
+            catch (InvalidOperationException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StartShouldThrowSshConnectionException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual("Forwarded port is not added to a client.", _actualException.Message);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}
+

+ 226 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Started.cs

@@ -0,0 +1,226 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Started
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortRemote _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _sessionMock.Setup(
+                    p =>
+                        p.SendMessage(
+                            It.Is<GlobalRequestMessage>(
+                                g =>
+                                    g.RequestName == GlobalRequestName.CancelTcpIpForward &&
+                                    g.AddressToBind == _forwardedPort.BoundHost && g.PortToBind == _forwardedPort.BoundPort)));
+                _sessionMock.Setup(p => p.MessageListenerCompleted).Returns(new ManualResetEvent(true));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, 1000));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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.RegisterMessage("SSH_MSG_REQUEST_FAILURE"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_CHANNEL_OPEN"));
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.TcpIpForward &&
+                                g.AddressToBind == _forwardedPort.BoundHost &&
+                                g.PortToBind == _forwardedPort.BoundPort)))
+                .Callback(
+                    () =>
+                        _sessionMock.Raise(s => s.RequestSuccessReceived += null,
+                            new MessageEventArgs<RequestSuccessMessage>(new RequestSuccessMessage())));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<WaitHandle>()))
+                .Callback<WaitHandle>(handle => handle.WaitOne());
+
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+            _forwardedPort.Start();
+        }
+
+        protected void Act()
+        {
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldAcceptChannelOpenMessageForBoundAddressAndBoundPort()
+        {
+            var channelNumber = (uint) new Random().Next(1001, int.MaxValue);
+            var initialWindowSize = (uint) new Random().Next(0, int.MaxValue);
+            var maximumPacketSize = (uint) new Random().Next(0, int.MaxValue);
+            var originatorAddress = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPort = (uint) new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize)).Returns(channelMock.Object);
+            channelMock.Setup(
+                p =>
+                    p.Bind(
+                        It.Is<IPEndPoint>(
+                            ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port),
+                        _forwardedPort));
+            channelMock.Setup(p => p.Close());
+            channelMock.Setup(p => p.Dispose());
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumber, initialWindowSize,
+                    maximumPacketSize,
+                    new ForwardedTcpipChannelInfo(_forwardedPort.BoundHost, _forwardedPort.BoundPort, originatorAddress,
+                        originatorPort))));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize), Times.Once);
+            channelMock.Verify(p => p.Bind(It.Is<IPEndPoint>(ep => ep.Address.Equals(_remoteEndpoint.Address) && ep.Port == _remoteEndpoint.Port), _forwardedPort), Times.Once);
+            channelMock.Verify(p => p.Close(), Times.Once);
+            channelMock.Verify(p => p.Dispose(), Times.Once);
+
+            Assert.AreEqual(0, _closingRegister.Count);
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldIgnoreChannelOpenMessageForBoundHostAndOtherPort()
+        {
+            var channelNumber = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSize = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSize = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddress = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPort = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize)).Returns(channelMock.Object);
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumber, initialWindowSize,
+                    maximumPacketSize,
+                    new ForwardedTcpipChannelInfo(_forwardedPort.BoundHost, _forwardedPort.BoundPort + 1, originatorAddress,
+                        originatorPort))));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize), Times.Never);
+
+            Assert.AreEqual(0, _closingRegister.Count);
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldIgnoreChannelOpenMessageForOtherHostAndBoundPort()
+        {
+            var channelNumber = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSize = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSize = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddress = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPort = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize)).Returns(channelMock.Object);
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumber, initialWindowSize,
+                    maximumPacketSize,
+                    new ForwardedTcpipChannelInfo("111.111.111.111", _forwardedPort.BoundPort, originatorAddress,
+                        originatorPort))));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize), Times.Never);
+
+            Assert.AreEqual(0, _closingRegister.Count);
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldIgnoreChannelOpenMessageWhenChannelOpenInfoIsNull()
+        {
+            var channelNumber = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSize = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSize = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize)).Returns(channelMock.Object);
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumber, initialWindowSize,
+                    maximumPacketSize, null)));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize), Times.Never);
+
+            Assert.AreEqual(0, _closingRegister.Count);
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldIgnoreChannelOpenMessageWhenChannelOpenInfoIsNotForwardedTcpipChannelInfo()
+        {
+            var channelNumber = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSize = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSize = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize)).Returns(channelMock.Object);
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null,
+                new MessageEventArgs<ChannelOpenMessage>(new ChannelOpenMessage(channelNumber, initialWindowSize,
+                    maximumPacketSize, new DirectTcpipChannelInfo())));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumber, initialWindowSize, maximumPacketSize), Times.Never);
+
+            Assert.AreEqual(0, _closingRegister.Count);
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 88 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Stop_PortDisposed.cs

@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Stop_PortDisposed
+    {
+        private ForwardedPortRemote _forwardedPort;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private ObjectDisposedException _actualException;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Dispose();
+        }
+
+        protected void Act()
+        {
+            try
+            {
+                _forwardedPort.Stop();
+                Assert.Fail();
+            }
+            catch (ObjectDisposedException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void StopShouldThrowObjectDisposedException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreEqual(_forwardedPort.GetType().FullName, _actualException.ObjectName);
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 135 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Stop_PortNeverStarted.cs

@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Stop_PortNeverStarted
+    {
+        private Mock<ISession> _sessionMock;
+        private Mock<IConnectionInfo> _connectionInfoMock;
+        private ForwardedPortRemote _forwardedPort;
+        private IList<EventArgs> _closingRegister;
+        private IList<ExceptionEventArgs> _exceptionRegister;
+        private IPEndPoint _bindEndpoint;
+        private IPEndPoint _remoteEndpoint;
+
+        [TestInitialize]
+        public void Setup()
+        {
+            Arrange();
+            Act();
+        }
+
+        [TestCleanup]
+        public void Cleanup()
+        {
+            if (_forwardedPort != null)
+            {
+                _sessionMock.Setup(
+                    p =>
+                        p.SendMessage(
+                            It.Is<GlobalRequestMessage>(
+                                g =>
+                                    g.RequestName == GlobalRequestName.CancelTcpIpForward &&
+                                    g.AddressToBind == _forwardedPort.BoundHost && g.PortToBind == _forwardedPort.BoundPort)));
+                _sessionMock.Setup(p => p.MessageListenerCompleted).Returns(new ManualResetEvent(true));
+                _forwardedPort.Dispose();
+                _forwardedPort = null;
+            }
+        }
+
+        protected void Arrange()
+        {
+            var random = new Random();
+            _closingRegister = new List<EventArgs>();
+            _exceptionRegister = new List<ExceptionEventArgs>();
+            _bindEndpoint = new IPEndPoint(IPAddress.Any, random.Next(IPEndPoint.MinPort, 1000));
+            _remoteEndpoint = new IPEndPoint(IPAddress.Parse("193.168.1.5"), random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort));
+            _forwardedPort = new ForwardedPortRemote(_bindEndpoint.Address, (uint)_bindEndpoint.Port, _remoteEndpoint.Address, (uint)_remoteEndpoint.Port);
+
+            _connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
+            _sessionMock = new Mock<ISession>(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.RegisterMessage("SSH_MSG_REQUEST_FAILURE"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_REQUEST_SUCCESS"));
+            _sessionMock.Setup(p => p.RegisterMessage("SSH_MSG_CHANNEL_OPEN"));
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(
+                        It.Is<GlobalRequestMessage>(
+                            g =>
+                                g.RequestName == GlobalRequestName.TcpIpForward &&
+                                g.AddressToBind == _forwardedPort.BoundHost &&
+                                g.PortToBind == _forwardedPort.BoundPort)))
+                .Callback(
+                    () =>
+                        _sessionMock.Raise(s => s.RequestSuccessReceived += null,
+                            new MessageEventArgs<RequestSuccessMessage>(new RequestSuccessMessage())));
+            _sessionMock.Setup(p => p.WaitOnHandle(It.IsAny<WaitHandle>()))
+                .Callback<WaitHandle>(handle => handle.WaitOne());
+
+            _forwardedPort.Closing += (sender, args) => _closingRegister.Add(args);
+            _forwardedPort.Exception += (sender, args) => _exceptionRegister.Add(args);
+            _forwardedPort.Session = _sessionMock.Object;
+        }
+
+        protected void Act()
+        {
+            _forwardedPort.Stop();
+        }
+
+        [TestMethod]
+        public void IsStartedShouldReturnFalse()
+        {
+            Assert.IsFalse(_forwardedPort.IsStarted);
+        }
+
+        [TestMethod]
+        public void ForwardedPortShouldIgnoreNewConnections()
+        {
+            var channelNumberDisposed = (uint)new Random().Next(1001, int.MaxValue);
+            var initialWindowSizeDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var maximumPacketSizeDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var originatorAddressDisposed = new Random().Next().ToString(CultureInfo.InvariantCulture);
+            var originatorPortDisposed = (uint)new Random().Next(0, int.MaxValue);
+            var channelMock = new Mock<IChannelForwardedTcpip>(MockBehavior.Strict);
+
+            _sessionMock.Setup(
+                p =>
+                    p.CreateChannelForwardedTcpip(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed)).Returns(channelMock.Object);
+            _sessionMock.Setup(
+                p =>
+                    p.SendMessage(new ChannelOpenFailureMessage(channelNumberDisposed, string.Empty,
+                        ChannelOpenFailureMessage.AdministrativelyProhibited)));
+
+            _sessionMock.Raise(p => p.ChannelOpenReceived += null, new ChannelOpenMessage(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed, new ForwardedTcpipChannelInfo(_forwardedPort.BoundHost, _forwardedPort.BoundPort, originatorAddressDisposed, originatorPortDisposed)));
+
+            _sessionMock.Verify(p => p.CreateChannelForwardedTcpip(channelNumberDisposed, initialWindowSizeDisposed, maximumPacketSizeDisposed), Times.Never);
+            _sessionMock.Verify(p => p.SendMessage(It.Is<ChannelOpenFailureMessage>(c => c.LocalChannelNumber == channelNumberDisposed && c.ReasonCode == ChannelOpenFailureMessage.AdministrativelyProhibited && c.Description == string.Empty && c.Language == null)), Times.Never);
+        }
+
+        [TestMethod]
+        public void ClosingShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _closingRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _exceptionRegister.Count);
+        }
+    }
+}

+ 13 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Stop_PortStarted_ChannelBound.cs

@@ -0,0 +1,13 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Stop_PortStarted_ChannelBound : ForwardedPortRemoteTest_Dispose_PortStarted_ChannelBound
+    {
+        protected override void Act()
+        {
+            ForwardedPort.Stop();
+        }
+    }
+}

+ 13 - 0
Renci.SshClient/Renci.SshNet.Tests/Classes/ForwardedPortRemoteTest_Stop_PortStopped.cs

@@ -0,0 +1,13 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Renci.SshNet.Tests.Classes
+{
+    [TestClass]
+    public class ForwardedPortRemoteTest_Stop_PortStopped : ForwardedPortRemoteTest_Dispose_PortStopped
+    {
+        protected override void Act()
+        {
+            ForwardedPort.Stop();
+        }
+    }
+}

+ 12 - 0
Renci.SshClient/Renci.SshNet.Tests/Common/AsyncSocketListener.cs

@@ -18,6 +18,7 @@ namespace Renci.SshNet.Tests.Common
 
         public event BytesReceivedHandler BytesReceived;
         public event ConnectedHandler Connected;
+        public event ConnectedHandler Disconnected;
 
         public AsyncSocketListener(IPEndPoint endPoint)
         {
@@ -116,6 +117,10 @@ namespace Renci.SshNet.Tests.Common
                     // when the socket is closed, an ObjectDisposedException is thrown
                 }
             }
+            else
+            {
+                SignalDisconnected(handler);
+            }
         }
 
         private void SignalBytesReceived(byte[] bytesReceived, Socket client)
@@ -132,6 +137,13 @@ namespace Renci.SshNet.Tests.Common
                 subscribers(client);
         }
 
+        private void SignalDisconnected(Socket client)
+        {
+            var subscribers = Disconnected;
+            if (subscribers != null)
+                subscribers(client);
+        }
+
         private class SocketStateObject
         {
             public Socket Socket { get; private set; }

+ 67 - 1
Renci.SshClient/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -66,6 +66,24 @@
     </CodeAnalysisDependentAssemblyPaths>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Classes\Channels\ChannelDirectTcpipTest.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_Close_SessionIsNotConnectedAndChannelIsOpen.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelCloseReceived_OnClose_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelDataReceived_OnData_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_Close_SessionIsConnectedAndChannelIsOpen.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_Close_SessionIsConnectedAndChannelIsNotOpen.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_Close_SessionIsNotConnectedAndChannelIsNotOpen.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelEofReceived_OnEof_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelExtendedDataReceived_OnExtendedData_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelFailureReceived_OnFailure_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelRequestReceived_OnRequest_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelSuccessReceived_OnSuccess_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelWindowAdjustReceived_OnWindowAdjust_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionDisconnected_OnDisconnected_Exception.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionDisconnected_SessionIsConnectedAndChannelIsOpen.cs" />
+    <Compile Include="Classes\Channels\ChannelStub.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionErrorOccurred_OnErrorOccurred_Exception.cs" />
     <Compile Include="Classes\ClientAuthenticationTestBase.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodFailed.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Failure_SingleList_AuthenticationMethodNotConfigured.cs" />
@@ -73,6 +91,54 @@
     <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_SameAllowedAuthenticationsAfterPartialSuccess.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Success_MultiList_SkipFailedAuthenticationMethod.cs" />
     <Compile Include="Classes\ClientAuthenticationTest_Success_SingleList_SameAllowedAuthenticationAfterPartialSuccess.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Dispose_PortStarted_ChannelBound.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Start_SessionNotConnected.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Start_SessionNull.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Stop_PortDisposed.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Stop_PortNeverStarted.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Stop_PortStarted_ChannelBound.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Stop_PortStarted_ChannelNotBound.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Stop_PortStopped.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Dispose_PortDisposed.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Dispose_PortNeverStarted.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Dispose_PortStarted_ChannelNotBound.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Dispose_PortStopped.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Started_SocketSendShutdownImmediately.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Started_SocketVersionNotSupported.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Start_PortDisposed.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Start_PortNeverStarted.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Start_PortStopped.cs" />
+    <Compile Include="Classes\ForwardedPortDynamicTest_Start_PortStarted.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Dispose_PortDisposed.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Dispose_PortNeverStarted.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Dispose_PortStarted_ChannelNotBound.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Dispose_PortStopped.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Start_PortDisposed.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Start_PortNeverStarted.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Start_PortStarted.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Start_PortStopped.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Start_SessionNotConnected.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Start_SessionNull.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Stop_PortDisposed.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Stop_PortNeverStarted.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Stop_PortStarted_ChannelBound.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Stop_PortStarted_ChannelNotBound.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Stop_PortStopped.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Dispose_PortDisposed.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Dispose_PortNeverStarted.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Dispose_PortStarted_ChannelBound.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Dispose_PortStopped.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Started.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Start_PortDisposed.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Start_PortNeverStarted.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Start_PortStarted.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Start_PortStopped.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Start_SessionNotConnected.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Start_SessionNull.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Stop_PortDisposed.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Stop_PortNeverStarted.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Stop_PortStarted_ChannelBound.cs" />
+    <Compile Include="Classes\ForwardedPortRemoteTest_Stop_PortStopped.cs" />
     <Compile Include="Classes\Security\AlgorithmTest.cs" />
     <Compile Include="Classes\Security\CertificateHostAlgorithmTest.cs" />
     <Compile Include="Classes\Security\Cryptography\BlockCipherTest.cs" />
@@ -104,7 +170,6 @@
     <Compile Include="Classes\AuthenticationMethodTest.cs" />
     <Compile Include="Classes\BaseClientTest.cs" />
     <Compile Include="Classes\ChannelAsyncResultTest.cs" />
-    <Compile Include="Classes\Channels\ChannelDirectTcpipTest.cs" />
     <Compile Include="Classes\Channels\ChannelForwardedTcpipTest.cs" />
     <Compile Include="Classes\Channels\ChannelSessionTest.cs" />
     <Compile Include="Classes\CipherInfoTest.cs" />
@@ -278,6 +343,7 @@
     <Compile Include="Classes\Security\KeyExchangeDiffieHellmanTest.cs" />
     <Compile Include="Classes\Security\KeyExchangeTest.cs" />
     <Compile Include="Classes\Security\KeyTest.cs" />
+    <Compile Include="Classes\ForwardedPortLocalTest_Dispose_PortStarted_ChannelBound.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
       <DependentUpon>Resources.resx</DependentUpon>
       <DesignTime>True</DesignTime>

+ 1 - 7
Renci.SshClient/Renci.SshNet.WindowsPhone8/ForwardedPortLocal.SilverlightShared.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Threading;
 
 namespace Renci.SshNet
 {
@@ -8,17 +7,12 @@ namespace Renci.SshNet
     /// </summary>
     public partial class ForwardedPortLocal
     {
-        partial void ExecuteThread(Action action)
-        {
-            ThreadPool.QueueUserWorkItem((o) => action());
-        }
-
         partial void InternalStart()
         {
             throw new NotImplementedException();
         }
 
-        partial void InternalStop()
+        partial void InternalStop(TimeSpan timeout)
         {
             throw new NotImplementedException();
         }

+ 3 - 3
Renci.SshClient/Renci.SshNet/BaseClient.cs

@@ -81,7 +81,7 @@ namespace Renci.SshNet
                 if (value == _keepAliveInterval)
                     return;
 
-                if (value == Session.Infinite)
+                if (value == Session.InfiniteTimeSpan)
                 {
                     // stop the timer when the value is -1 milliseconds
                     StopKeepAliveTimer();
@@ -133,7 +133,7 @@ namespace Renci.SshNet
 
             ConnectionInfo = connectionInfo;
             _ownsConnectionInfo = ownsConnectionInfo;
-            _keepAliveInterval = Session.Infinite;
+            _keepAliveInterval = Session.InfiniteTimeSpan;
         }
 
         /// <summary>
@@ -353,7 +353,7 @@ namespace Renci.SshNet
         /// </remarks>
         private void StartKeepAliveTimer()
         {
-            if (_keepAliveInterval == Session.Infinite)
+            if (_keepAliveInterval == Session.InfiniteTimeSpan)
                 return;
 
             if (_keepAliveTimer == null)

+ 133 - 61
Renci.SshClient/Renci.SshNet/Channels/Channel.cs

@@ -15,7 +15,6 @@ namespace Renci.SshNet.Channels
         private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false);
         private EventWaitHandle _channelServerWindowAdjustWaitHandle = new ManualResetEvent(false);
         private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false);
-        private EventWaitHandle _disconnectedWaitHandle = new ManualResetEvent(false);
         private readonly object _serverWindowSizeLock = new object();
         private bool _closeMessageSent;
         private uint _initialWindowSize;
@@ -24,6 +23,11 @@ namespace Renci.SshNet.Channels
         private uint? _remotePacketSize;
         private ISession _session;
 
+        /// <summary>
+        /// Occurs when an exception is thrown when processing channel messages.
+        /// </summary>
+        public event EventHandler<ExceptionEventArgs> Exception;
+
         /// <summary>
         /// Gets the session.
         /// </summary>
@@ -167,7 +171,7 @@ namespace Renci.SshNet.Channels
         /// <summary>
         /// Occurs when <see cref="ChannelSuccessMessage"/> message received
         /// </summary>
-        public event EventHandler<ChannelEventArgs> RequestSuccessed;
+        public event EventHandler<ChannelEventArgs> RequestSucceeded;
 
         /// <summary>
         /// Occurs when <see cref="ChannelFailureMessage"/> message received
@@ -259,9 +263,9 @@ namespace Renci.SshNet.Channels
         /// <summary>
         /// Closes the channel.
         /// </summary>
-        public virtual void Close()
+        public void Close()
         {
-            this.Close(true);
+            Close(true);
         }
 
         #region Channel virtual methods
@@ -344,7 +348,7 @@ namespace Renci.SshNet.Channels
         /// </summary>
         protected virtual void OnSuccess()
         {
-            var requestSuccessed = RequestSuccessed;
+            var requestSuccessed = RequestSucceeded;
             if (requestSuccessed != null)
                 requestSuccessed(this, new ChannelEventArgs(LocalChannelNumber));
         }
@@ -361,6 +365,19 @@ namespace Renci.SshNet.Channels
 
         #endregion
 
+        /// <summary>
+        /// Raises <see cref="Channel.Exception"/> event.
+        /// </summary>
+        /// <param name="exception">The exception.</param>
+        protected void RaiseExceptionEvent(Exception exception)
+        {
+            var handlers = Exception;
+            if (handlers != null)
+            {
+                handlers(this, new ExceptionEventArgs(exception));
+            }
+        }
+
         /// <summary>
         /// Sends SSH message to the server.
         /// </summary>
@@ -380,14 +397,7 @@ namespace Renci.SshNet.Channels
         /// <param name="message">The message to send.</param>
         private void SendMessage(ChannelCloseMessage message)
         {
-            // send channel messages only while channel is open
-            if (!this.IsOpen)
-                return;
-
             this._session.SendMessage(message);
-
-            // when channel close message is sent channel considered to be closed
-            this.IsOpen = false;
         }
 
         /// <summary>
@@ -456,7 +466,7 @@ namespace Renci.SshNet.Channels
         /// </remarks>
         protected void SendMessage(ChannelExtendedDataMessage message)
         {
-            // end channel messages only while channel is open
+            // send channel messages only while channel is open
             if (!this.IsOpen)
                 return;
 
@@ -494,11 +504,16 @@ namespace Renci.SshNet.Channels
             this._session.WaitOnHandle(waitHandle);
         }
 
+        /// <summary>
+        /// Closes the channel, optionally waiting for the SSH_MSG_CHANNEL_CLOSE message to
+        /// be received from the server.
+        /// </summary>
+        /// <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 virtual void Close(bool wait)
         {
             // send message to close the channel on the server
             // ignore sending close message when client not connected
-            if (!_closeMessageSent && this.IsConnected)
+            if (!_closeMessageSent && IsOpen && IsConnected)
             {
                 lock (this)
                 {
@@ -509,14 +524,13 @@ namespace Renci.SshNet.Channels
                     }
                 }
             }
-            else
-            {
-                // also mark the channel closed if the session is no longer connected
-                IsOpen = false;
-            }
 
-            // wait for channel to be closed
-            if (wait)
+            // mark the channel closed
+            IsOpen = false;
+
+            // wait for channel to be closed if we actually sent a close message (either to initiate closing
+            // the channel, or as response to a SSH_MSG_CHANNEL_CLOSE message sent by the server
+            if (wait && _closeMessageSent)
             {
                 WaitOnHandle(this._channelClosedWaitHandle);
             }
@@ -532,28 +546,38 @@ namespace Renci.SshNet.Channels
 
         private void Session_Disconnected(object sender, EventArgs e)
         {
-            this.OnDisconnected();
+            IsOpen = false;
 
-            //  If object is disposed or being disposed don't handle this event
-            if (this._isDisposed)
-                return;
+            try
+            {
+                this.OnDisconnected();
+            }
+            catch (Exception ex)
+            {
+                HandleChannelException(ex);
+            }
+        }
 
-            var disconnectedWaitHandle = this._disconnectedWaitHandle;
-            if (disconnectedWaitHandle != null)
-                disconnectedWaitHandle.Set();
+        private void HandleChannelException(Exception ex)
+        {
+            OnErrorOccured(ex);
+            RaiseExceptionEvent(ex);
         }
 
         private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
         {
-            this.OnErrorOccured(e.Exception);
-
-            //  If object is disposed or being disposed don't handle this event
-            if (this._isDisposed)
-                return;
+            try
+            {
+                OnErrorOccured(e.Exception);
 
-            var errorOccuredWaitHandle = this._errorOccuredWaitHandle;
-            if (errorOccuredWaitHandle != null)
-                errorOccuredWaitHandle.Set();
+                var errorOccuredWaitHandle = _errorOccuredWaitHandle;
+                if (errorOccuredWaitHandle != null)
+                    errorOccuredWaitHandle.Set();
+            }
+            catch (Exception ex)
+            {
+                RaiseExceptionEvent(ex);
+            }
         }
 
         #region Channel message event handlers
@@ -562,7 +586,14 @@ namespace Renci.SshNet.Channels
         {
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
             {
-                this.OnWindowAdjust(e.Message.BytesToAdd);
+                try
+                {
+                    this.OnWindowAdjust(e.Message.BytesToAdd);
+                }
+                catch (Exception ex)
+                {
+                    HandleChannelException(ex);
+                }
             }
         }
 
@@ -570,7 +601,14 @@ namespace Renci.SshNet.Channels
         {
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
             {
-                this.OnData(e.Message.Data);
+                try
+                {
+                    OnData(e.Message.Data);
+                }
+                catch (Exception ex)
+                {
+                    HandleChannelException(ex);
+                }
             }
         }
 
@@ -578,7 +616,14 @@ namespace Renci.SshNet.Channels
         {
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
             {
-                this.OnExtendedData(e.Message.Data, e.Message.DataTypeCode);
+                try
+                {
+                    OnExtendedData(e.Message.Data, e.Message.DataTypeCode);
+                }
+                catch (Exception ex)
+                {
+                    HandleChannelException(ex);
+                }
             }
         }
 
@@ -586,7 +631,14 @@ namespace Renci.SshNet.Channels
         {
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
             {
-                this.OnEof();
+                try
+                {
+                    OnEof();
+                }
+                catch (Exception ex)
+                {
+                    HandleChannelException(ex);
+                }
             }
         }
 
@@ -594,7 +646,14 @@ namespace Renci.SshNet.Channels
         {
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
             {
-                this.OnClose();
+                try
+                {
+                    OnClose();
+                }
+                catch (Exception ex)
+                {
+                    HandleChannelException(ex);
+                }
 
                 var channelClosedWaitHandle = _channelClosedWaitHandle;
                 if (channelClosedWaitHandle != null)
@@ -606,20 +665,28 @@ namespace Renci.SshNet.Channels
         {
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
             {
-                if (this._session.ConnectionInfo.ChannelRequests.ContainsKey(e.Message.RequestName))
+                try
                 {
-                    //  Get request specific class
-                    var requestInfo = this._session.ConnectionInfo.ChannelRequests[e.Message.RequestName];
+                    if (this._session.ConnectionInfo.ChannelRequests.ContainsKey(e.Message.RequestName))
+                    {
+                        //  Get request specific class
+                        var requestInfo = this._session.ConnectionInfo.ChannelRequests[e.Message.RequestName];
 
-                    //  Load request specific data
-                    requestInfo.Load(e.Message.RequestData);
+                        //  Load request specific data
+                        requestInfo.Load(e.Message.RequestData);
 
-                    //  Raise request specific event
-                    this.OnRequest(requestInfo);
+                        //  Raise request specific event
+                        this.OnRequest(requestInfo);
+                    }
+                    else
+                    {
+                        // TODO: we should also send a SSH_MSG_CHANNEL_FAILURE message
+                        throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Request '{0}' is not supported.", e.Message.RequestName));
+                    }
                 }
-                else
+                catch (Exception ex)
                 {
-                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Request '{0}' is not supported.", e.Message.RequestName));
+                    HandleChannelException(ex);
                 }
             }
         }
@@ -628,7 +695,14 @@ namespace Renci.SshNet.Channels
         {
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
             {
-                this.OnSuccess();
+                try
+                {
+                    OnSuccess();
+                }
+                catch (Exception ex)
+                {
+                    HandleChannelException(ex);
+                }
             }
         }
 
@@ -636,7 +710,14 @@ namespace Renci.SshNet.Channels
         {
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
             {
-                this.OnFailure();
+                try
+                {
+                    OnFailure();
+                }
+                catch (Exception ex)
+                {
+                    HandleChannelException(ex);
+                }
             }
         }
 
@@ -710,11 +791,8 @@ namespace Renci.SshNet.Channels
         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
         protected virtual void Dispose(bool disposing)
         {
-            // Check to see if Dispose has already been called.
             if (!this._isDisposed)
             {
-                // If disposing equals true, dispose all managed
-                // and unmanaged resources.
                 if (disposing)
                 {
                     this.Close(false);
@@ -747,14 +825,8 @@ namespace Renci.SshNet.Channels
                         this._errorOccuredWaitHandle.Dispose();
                         this._errorOccuredWaitHandle = null;
                     }
-                    if (this._disconnectedWaitHandle != null)
-                    {
-                        this._disconnectedWaitHandle.Dispose();
-                        this._disconnectedWaitHandle = null;
-                    }
                 }
 
-                // Note disposing has been done.
                 this._isDisposed = true;
             }
         }

+ 112 - 53
Renci.SshClient/Renci.SshNet/Channels/ChannelDirectTcpip.cs

@@ -13,13 +13,30 @@ namespace Renci.SshNet.Channels
     /// </summary>
     internal partial class ChannelDirectTcpip : ClientChannel, IChannelDirectTcpip
     {
+        private readonly object _socketShutdownAndCloseLock = new object();
+
         private EventWaitHandle _channelEof = new AutoResetEvent(false);
         private EventWaitHandle _channelOpen = new AutoResetEvent(false);
         private EventWaitHandle _channelData = new AutoResetEvent(false);
+
+        /// <summary>
+        /// An <see cref="EventWaitHandle"/> that is signaled when the blocking receive is cancelled because the
+        /// forwarded port is closing.
+        /// </summary>
         private EventWaitHandle _channelInterrupted = new ManualResetEvent(false);
+
         private IForwardedPort _forwardedPort;
         private Socket _socket;
 
+        /// <summary>
+        /// Holds a value indicating whether the SSH_MSG_CHANNEL_EOF has been sent to the server.
+        /// </summary>
+        /// <value>
+        /// <c>0</c> when the SSH_MSG_CHANNEL_EOF message has not been sent to the server, and
+        /// <c>1</c> when this message was already sent.
+        /// </value>
+        private int _sentEof;
+
         /// <summary>
         /// Gets the type of the channel.
         /// </summary>
@@ -35,28 +52,30 @@ namespace Renci.SshNet.Channels
         {
             if (IsOpen)
                 throw new SshException("Channel is already open.");
-            if (!this.IsConnected)
+            if (!IsConnected)
                 throw new SshException("Session is not connected.");
 
             _socket = socket;
             _forwardedPort = forwardedPort;
             _forwardedPort.Closing += ForwardedPort_Closing;
+            _sentEof = 0;
 
             var ep = socket.RemoteEndPoint as IPEndPoint;
 
-            //  Open channel
-            this.SendMessage(new ChannelOpenMessage(this.LocalChannelNumber, this.LocalWindowSize, this.LocalPacketSize,
-                                                        new DirectTcpipChannelInfo(remoteHost, port, ep.Address.ToString(), (uint)ep.Port)));
+            // open channel
+            SendMessage(new ChannelOpenMessage(LocalChannelNumber, LocalWindowSize, LocalPacketSize,
+                new DirectTcpipChannelInfo(remoteHost, port, ep.Address.ToString(), (uint) ep.Port)));
 
             //  Wait for channel to open
-            this.WaitOnHandle(this._channelOpen);
+            WaitOnHandle(_channelOpen);
         }
 
+        /// <summary>
+        /// Occurs as the forwarded port is being stopped.
+        /// </summary>
         private void ForwardedPort_Closing(object sender, EventArgs eventArgs)
         {
-            // close the socket, hereby interrupting the blocking receive in Bind()
-            if (_socket != null)
-                CloseSocket();
+            CloseSocket();
         }
 
         /// <summary>
@@ -65,24 +84,34 @@ namespace Renci.SshNet.Channels
         public void Bind()
         {
             //  Cannot bind if channel is not open
-            if (!this.IsOpen)
+            if (!IsOpen)
                 return;
 
-            var buffer = new byte[this.RemotePacketSize];
+            var buffer = new byte[RemotePacketSize];
 
-            while (this._socket != null && _socket.Connected)
+            while (_socket != null && _socket.Connected)
             {
                 try
                 {
                     var read = 0;
-                    this.InternalSocketReceive(buffer, ref read);
+                    InternalSocketReceive(buffer, ref read);
                     if (read > 0)
                     {
-                        this.SendMessage(new ChannelDataMessage(this.RemoteChannelNumber, buffer.Take(read).ToArray()));
+                        SendMessage(new ChannelDataMessage(RemoteChannelNumber, buffer.Take(read).ToArray()));
                     }
                     else
                     {
-                        // client quit sending
+                        // client quit sending (but the server may still send data or an EOF)
+                        if (Interlocked.CompareExchange(ref _sentEof, 1, 0) == 0)
+                        {
+                            // inform server that we won't be sending anything anymore if we
+                            // haven't already sent a SSH_MSG_CHANNEL_EOF message
+                            //
+                            // note that we'll still wait for a SSH_MSG_CHANNEL_EOF or
+                            // SSH_MSG_CHANNEL_CLOSE message once we've broken the receive
+                            // loop
+                            SendEof();
+                        }
                         break;
                     }
                 }
@@ -111,7 +140,7 @@ namespace Renci.SshNet.Channels
                 }
             }
 
-            WaitHandle.WaitAny(new WaitHandle[] { _channelEof, _channelInterrupted });
+            WaitHandle.WaitAny(new WaitHandle[] {_channelEof, _channelInterrupted});
         }
 
         /// <summary>
@@ -119,17 +148,39 @@ namespace Renci.SshNet.Channels
         /// </summary>
         private void CloseSocket()
         {
-            if (!_socket.Connected)
+            if (_socket == null || !_socket.Connected)
                 return;
 
-            _socket.Shutdown(SocketShutdown.Both);
-            _socket.Close();
+            lock (_socketShutdownAndCloseLock)
+            {
+                if (_socket == null || !_socket.Connected)
+                    return;
+
+                _socket.Shutdown(SocketShutdown.Both);
+                _socket.Close();
+            }
+        }
+
+        private void ShutdownSocket(SocketShutdown how)
+        {
+            if (_socket == null || !_socket.Connected)
+                return;
+
+            lock (_socketShutdownAndCloseLock)
+            {
+                if (_socket == null || !_socket.Connected)
+                    return;
+
+                _socket.Shutdown(how);
+            }
         }
 
         /// <summary>
-        /// Closes the channel.
+        /// Closes the channel, optionally waiting for the SSH_MSG_CHANNEL_CLOSE message to
+        /// be received from the server.
         /// </summary>
-        public override void Close()
+        /// <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 (_forwardedPort != null)
             {
@@ -138,13 +189,15 @@ namespace Renci.SshNet.Channels
             }
 
             // close the socket, hereby interrupting the blocking receive in Bind()
-            if (this._socket != null)
-                CloseSocket();
+            CloseSocket();
 
-            //  Send EOF message first when channel need to be closed
-            this.SendMessage(new ChannelEofMessage(this.RemoteChannelNumber));
+            //  send EOF message first when channel needs to be closed
+            if (IsOpen && Interlocked.CompareExchange(ref _sentEof, 1, 0) == 0)
+            {
+                SendEof();
+            }
 
-            base.Close();
+            base.Close(wait);
         }
 
         /// <summary>
@@ -155,7 +208,8 @@ namespace Renci.SshNet.Channels
         {
             base.OnData(data);
 
-            this.InternalSocketSend(data);
+            if (_socket != null && _socket.Connected)
+                InternalSocketSend(data);
         }
 
         /// <summary>
@@ -168,14 +222,14 @@ namespace Renci.SshNet.Channels
         {
             base.OnOpenConfirmation(remoteChannelNumber, initialWindowSize, maximumPacketSize);
 
-            this._channelOpen.Set();
+            _channelOpen.Set();
         }
 
         protected override void OnOpenFailure(uint reasonCode, string description, string language)
         {
             base.OnOpenFailure(reasonCode, description, language);
 
-            this._channelOpen.Set();
+            _channelOpen.Set();
         }
 
         /// <summary>
@@ -187,10 +241,9 @@ namespace Renci.SshNet.Channels
 
             // the channel will send no more data, so signal to the client that
             // we won't be sending anything anymore
-            if (_socket != null && _socket.Connected)
-                _socket.Shutdown(SocketShutdown.Send);
+            ShutdownSocket(SocketShutdown.Send);
 
-            var channelEof = this._channelEof;
+            var channelEof = _channelEof;
             if (channelEof != null)
                 channelEof.Set();
         }
@@ -199,25 +252,31 @@ namespace Renci.SshNet.Channels
         {
             base.OnClose();
 
-            var channelEof = this._channelEof;
+            // the channel will send no more data, so signal to the client that
+            // we won't be sending anything anymore
+            //
+            // we need to do this here in case the server sends the SSH_MSG_CHANNEL_CLOSE
+            // message without first sending SSH_MSG_CHANNEL_EOF
+            ShutdownSocket(SocketShutdown.Send);
+
+            var channelEof = _channelEof;
             if (channelEof != null)
                 channelEof.Set();
         }
 
         /// <summary>
         /// Called whenever an unhandled <see cref="Exception"/> occurs in <see cref="Session"/> causing
-        /// the message loop to be interrupted.
+        /// the message loop to be interrupted, or when an exception occurred processing a channel message.
         /// </summary>
         protected override void OnErrorOccured(Exception exp)
         {
             base.OnErrorOccured(exp);
 
             // close the socket, hereby interrupting the blocking receive in Bind()
-            if (_socket != null)
-                CloseSocket();
+            CloseSocket();
 
             //  if error occured, no more data can be received
-            var channelEof = this._channelEof;
+            var channelEof = _channelEof;
             if (channelEof != null)
                 channelEof.Set();
         }
@@ -234,11 +293,10 @@ namespace Renci.SshNet.Channels
             base.OnDisconnected();
 
             // close the socket, hereby interrupting the blocking receive in Bind()
-            if (_socket != null)
-                CloseSocket();
+            CloseSocket();
 
             //  If disconnected, no more data can be received
-            var channelEof = this._channelEof;
+            var channelEof = _channelEof;
             if (channelEof != null)
                 channelEof.Set();
         }
@@ -249,34 +307,37 @@ namespace Renci.SshNet.Channels
 
         protected override void Dispose(bool disposing)
         {
+            // make sure we've unsubscribed from all session events before we starting disposing
+            base.Dispose(disposing);
+
             if (_forwardedPort != null)
             {
                 _forwardedPort.Closing -= ForwardedPort_Closing;
                 _forwardedPort = null;
             }
 
-            if (this._socket != null)
+            if (_socket != null)
             {
-                this._socket.Dispose();
-                this._socket = null;
+                _socket.Dispose();
+                _socket = null;
             }
 
-            if (this._channelEof != null)
+            if (_channelEof != null)
             {
-                this._channelEof.Dispose();
-                this._channelEof = null;
+                _channelEof.Dispose();
+                _channelEof = null;
             }
 
-            if (this._channelOpen != null)
+            if (_channelOpen != null)
             {
-                this._channelOpen.Dispose();
-                this._channelOpen = null;
+                _channelOpen.Dispose();
+                _channelOpen = null;
             }
 
-            if (this._channelData != null)
+            if (_channelData != null)
             {
-                this._channelData.Dispose();
-                this._channelData = null;
+                _channelData.Dispose();
+                _channelData = null;
             }
 
             if (_channelInterrupted != null)
@@ -284,8 +345,6 @@ namespace Renci.SshNet.Channels
                 _channelInterrupted.Dispose();
                 _channelInterrupted = null;
             }
-
-            base.Dispose(disposing);
         }
     }
 }

+ 4 - 6
Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.NET40.cs

@@ -6,13 +6,12 @@ namespace Renci.SshNet.Channels
     /// <summary>
     /// Implements "forwarded-tcpip" SSH channel.
     /// </summary>
-    internal partial class ChannelForwardedTcpip : ServerChannel
+    internal partial class ChannelForwardedTcpip
     {
-        partial void OpenSocket(IPAddress connectedHost, uint connectedPort)
+        partial void OpenSocket(IPEndPoint remoteEndpoint)
         {
-            var ep = new IPEndPoint(connectedHost, (int)connectedPort);
-            this._socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
-            this._socket.Connect(ep);
+            this._socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+            this._socket.Connect(remoteEndpoint);
             this._socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 1);
         }
 
@@ -21,7 +20,6 @@ namespace Renci.SshNet.Channels
             read = this._socket.Receive(buffer);
         }
 
-
         partial void InternalSocketSend(byte[] data)
         {
             this._socket.Send(data);

+ 98 - 27
Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.cs

@@ -13,8 +13,20 @@ namespace Renci.SshNet.Channels
     /// </summary>
     internal partial class ChannelForwardedTcpip : ServerChannel, IChannelForwardedTcpip
     {
+        private readonly object _socketShutdownAndCloseLock = new object();
         private Socket _socket;
 
+        /// <summary>
+        /// Holds a value indicating whether the SSH_MSG_CHANNEL_EOF has been sent to the client.
+        /// </summary>
+        /// <value>
+        /// <c>0</c> when the SSH_MSG_CHANNEL_EOF message has not been sent to the client, and
+        /// <c>1</c> when this message was already sent.
+        /// </value>
+        private int _sentEof;
+
+        private IForwardedPort _forwardedPort;
+
         /// <summary>
         /// Gets the type of the channel.
         /// </summary>
@@ -27,53 +39,56 @@ namespace Renci.SshNet.Channels
         }
 
         /// <summary>
-        /// Binds channel to specified connected host.
+        /// Binds the channel to the specified endpoint.
         /// </summary>
-        /// <param name="connectedHost">The connected host.</param>
-        /// <param name="connectedPort">The connected port.</param>
-        public void Bind(IPAddress connectedHost, uint connectedPort)
+        /// <param name="remoteEndpoint">The endpoint to connect to.</param>
+        /// <param name="forwardedPort">The forwarded port for which the channel is opened.</param>
+        public void Bind(IPEndPoint remoteEndpoint, IForwardedPort forwardedPort)
         {
             byte[] buffer;
 
-            if (!this.IsConnected)
+            if (!IsConnected)
             {
                 throw new SshException("Session is not connected.");
             }
 
+            _forwardedPort = forwardedPort;
+            _forwardedPort.Closing += ForwardedPort_Closing;
+
             //  Try to connect to the socket 
             try
             {
                 //  Get buffer in memory for data exchange
-                buffer = new byte[this.RemotePacketSize];
+                buffer = new byte[RemotePacketSize];
 
-                this.OpenSocket(connectedHost, connectedPort);
+                OpenSocket(remoteEndpoint);
 
-                //  Send channel open confirmation message
-                this.SendMessage(new ChannelOpenConfirmationMessage(this.RemoteChannelNumber, this.LocalWindowSize, this.LocalPacketSize, this.LocalChannelNumber));
+                // send channel open confirmation message
+                SendMessage(new ChannelOpenConfirmationMessage(RemoteChannelNumber, LocalWindowSize, LocalPacketSize, LocalChannelNumber));
             }
             catch (Exception exp)
             {
-                //  Send channel open failure message
-                this.SendMessage(new ChannelOpenFailureMessage(this.RemoteChannelNumber, exp.ToString(), 2));
+                // send channel open failure message
+                SendMessage(new ChannelOpenFailureMessage(RemoteChannelNumber, exp.ToString(), 2));
 
                 throw;
             }
 
             //  Start reading data from the port and send to channel
-            while (this._socket != null && this._socket.CanRead())
+            while (_socket != null && _socket.Connected)
                 {
                 try
                 {
                     var read = 0;
-                    this.InternalSocketReceive(buffer, ref read);
+                    InternalSocketReceive(buffer, ref read);
 
                     if (read > 0)
                     {
-                        this.SendMessage(new ChannelDataMessage(this.RemoteChannelNumber, buffer.Take(read).ToArray()));
+                        SendMessage(new ChannelDataMessage(RemoteChannelNumber, buffer.Take(read).ToArray()));
                     }
                     else
                     {
-                        //  Zero bytes received when remote host shuts down the connection
+                        // server quit sending
                         break;
                     }
                 }
@@ -86,7 +101,7 @@ namespace Renci.SshNet.Channels
                         // socket buffer is probably empty, wait and try again
                         Thread.Sleep(30);
                     }
-                    else if (exp.SocketErrorCode == SocketError.ConnectionAborted)
+                    else if (exp.SocketErrorCode == SocketError.ConnectionAborted || exp.SocketErrorCode == SocketError.Interrupted)
                     {
                         break;
                     }
@@ -94,18 +109,68 @@ namespace Renci.SshNet.Channels
                         throw;  // throw any other error
                 }
             }
+        }
+
+        protected override void OnErrorOccured(Exception exp)
+        {
+            base.OnErrorOccured(exp);
 
-            this.Close();
+            // close the socket, hereby interrupting the blocking receive in Bind(IPEndPoint,IForwardedPort)
+            CloseSocket();
         }
 
-        partial void OpenSocket(IPAddress connectedHost, uint connectedPort);
+        /// <summary>
+        /// Occurs as the forwarded port is being stopped.
+        /// </summary>
+        private void ForwardedPort_Closing(object sender, EventArgs eventArgs)
+        {
+            // close the socket, hereby interrupting the blocking receive in Bind(IPEndPoint,IForwardedPort)
+            CloseSocket();
+        }
 
-        public override void Close()
+        partial void OpenSocket(IPEndPoint remoteEndpoint);
+
+        /// <summary>
+        /// Closes the socket, hereby interrupting the blocking receive in <see cref="Bind(IPEndPoint,IForwardedPort)"/>.
+        /// </summary>
+        private void CloseSocket()
         {
-            //  Send EOF message first when channel need to be closed
-            this.SendMessage(new ChannelEofMessage(this.RemoteChannelNumber));
+            if (_socket == null || !_socket.Connected)
+                return;
 
-            base.Close();
+            lock (_socketShutdownAndCloseLock)
+            {
+                if (_socket == null || !_socket.Connected)
+                    return;
+
+                _socket.Shutdown(SocketShutdown.Both);
+                _socket.Close();
+            }
+        }
+
+        /// <summary>
+        /// Closes the channel, optionally waiting for the SSH_MSG_CHANNEL_CLOSE message to
+        /// be received from the server.
+        /// </summary>
+        /// <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 (_forwardedPort != null)
+            {
+                _forwardedPort.Closing -= ForwardedPort_Closing;
+                _forwardedPort = null;
+            }
+
+            // close the socket, hereby interrupting the blocking receive in Bind()
+            CloseSocket();
+
+            //  send EOF message first when channel need to be closed
+            if (IsOpen && Interlocked.CompareExchange(ref _sentEof, 1, 0) == 0)
+            {
+                SendEof();
+            }
+
+            base.Close(wait);
         }
 
         /// <summary>
@@ -116,8 +181,8 @@ namespace Renci.SshNet.Channels
         {
             base.OnData(data);
 
-            //  Read data from the channel and send it to the port
-            this.InternalSocketSend(data);
+            if (_socket != null && _socket.Connected)
+                InternalSocketSend(data);
         }
 
         partial void InternalSocketSend(byte[] data);
@@ -130,10 +195,16 @@ namespace Renci.SshNet.Channels
         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
         protected override void Dispose(bool disposing)
         {
-            if (this._socket != null)
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Closing -= ForwardedPort_Closing;
+                _forwardedPort = null;
+            }
+
+            if (_socket != null)
             {
-                this._socket.Dispose();
-                this._socket = null;
+                _socket.Dispose();
+                _socket = null;
             }
 
             base.Dispose(disposing);

+ 14 - 0
Renci.SshClient/Renci.SshNet/Channels/IChannelDirectTcpip.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Net.Sockets;
+using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Channels
 {
@@ -8,6 +9,11 @@ namespace Renci.SshNet.Channels
     /// </summary>
     internal interface IChannelDirectTcpip : IDisposable
     {
+        /// <summary>
+        /// Occurs when an exception is thrown while processing channel messages.
+        /// </summary>
+        event EventHandler<ExceptionEventArgs> Exception;
+
         /// <summary>
         /// Gets a value indicating whether this channel is open.
         /// </summary>
@@ -16,6 +22,14 @@ namespace Renci.SshNet.Channels
         /// </value>
         bool IsOpen { get; }
 
+        /// <summary>
+        /// Gets the local channel number.
+        /// </summary>
+        /// <value>
+        /// The local channel number.
+        /// </value>
+        uint LocalChannelNumber { get; }
+
         /// <summary>
         /// Opens a channel for a locally forwarded TCP/IP port.
         /// </summary>

+ 18 - 6
Renci.SshClient/Renci.SshNet/Channels/IChannelForwardedTcpip.cs

@@ -1,17 +1,29 @@
-using System.Net;
+using System;
+using System.Net;
+using Renci.SshNet.Common;
 
 namespace Renci.SshNet.Channels
 {
     /// <summary>
     /// A "forwarded-tcpip" SSH channel.
     /// </summary>
-    internal interface IChannelForwardedTcpip
+    internal interface IChannelForwardedTcpip : IDisposable
     {
         /// <summary>
-        /// Binds the channel to the specified host.
+        /// Occurs when an exception is thrown while processing channel messages.
         /// </summary>
-        /// <param name="address">The IP address of the host to bind to.</param>
-        /// <param name="port">The port to bind to.</param>
-        void Bind(IPAddress address, uint port);
+        event EventHandler<ExceptionEventArgs> Exception;
+
+        /// <summary>
+        /// Binds the channel to the specified endpoint.
+        /// </summary>
+        /// <param name="remoteEndpoint">The endpoint to connect to.</param>
+        /// <param name="forwardedPort">The forwarded port for which the channel is opened.</param>
+        void Bind(IPEndPoint remoteEndpoint, IForwardedPort forwardedPort);
+
+        /// <summary>
+        /// Closes the channel.
+        /// </summary>
+        void Close();
     }
 }

+ 68 - 19
Renci.SshClient/Renci.SshNet/ForwardedPort.cs

@@ -8,8 +8,6 @@ namespace Renci.SshNet
     /// </summary>
     public abstract class ForwardedPort : IForwardedPort
     {
-        private EventHandler _closingEvent;
-
         /// <summary>
         /// Gets or sets the session.
         /// </summary>
@@ -19,12 +17,17 @@ namespace Renci.SshNet
         internal ISession Session { get; set; }
 
         /// <summary>
-        /// The <see cref="IForwardedPort.Closing"/> event occurs as the forward port is being stopped.
+        /// The <see cref="Closing"/> event occurs as the forwarded port is being stopped.
+        /// </summary>
+        internal event EventHandler Closing;
+
+        /// <summary>
+        /// The <see cref="IForwardedPort.Closing"/> event occurs as the forwarded port is being stopped.
         /// </summary>
         event EventHandler IForwardedPort.Closing
         {
-            add { _closingEvent += value; }
-            remove { _closingEvent -= value; }
+            add { Closing += value; }
+            remove { Closing -= value; }
         }
 
         /// <summary>
@@ -33,7 +36,7 @@ namespace Renci.SshNet
         /// <value>
         /// <c>true</c> if port forwarding is started; otherwise, <c>false</c>.
         /// </value>
-        public bool IsStarted { get; protected set; }
+        public abstract bool IsStarted { get; }
 
         /// <summary>
         /// Occurs when an exception is thrown.
@@ -50,32 +53,78 @@ namespace Renci.SshNet
         /// </summary>
         public virtual void Start()
         {
-            if (this.Session == null)
-            {
-                throw new InvalidOperationException("Forwarded port is not added to a client.");
-            }
+            CheckDisposed();
 
-            if (!this.Session.IsConnected)
-            {
+            if (IsStarted)
+                throw new InvalidOperationException("Forwarded port is already started.");
+            if (Session == null)
+                throw new InvalidOperationException("Forwarded port is not added to a client.");
+            if (!Session.IsConnected)
                 throw new SshConnectionException("Client not connected.");
-            }
 
-            this.Session.ErrorOccured += Session_ErrorOccured;
+            Session.ErrorOccured += Session_ErrorOccured;
+            StartPort();
         }
 
         /// <summary>
         /// Stops port forwarding.
         /// </summary>
-        public virtual void Stop()
+        public void Stop()
+        {
+            CheckDisposed();
+
+            if (!IsStarted)
+                return;
+
+            StopPort(Session.ConnectionInfo.Timeout);
+        }
+
+        /// <summary>
+        /// Starts port forwarding.
+        /// </summary>
+        protected abstract void StartPort();
+
+        /// <summary>
+        /// Stops port forwarding, and waits for the specified timeout until all pending
+        /// requests are processed.
+        /// </summary>
+        /// <param name="timeout">The maximum amount of time to wait for pending requests to finish processing.</param>
+        protected virtual void StopPort(TimeSpan timeout)
         {
             RaiseClosing();
 
-            if (this.Session != null)
+            if (Session != null)
+            {
+                Session.ErrorOccured -= Session_ErrorOccured;
+            }
+        }
+
+        /// <summary>
+        /// Releases unmanaged and - optionally - managed resources
+        /// </summary>
+        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                if (Session != null)
+                {
+                    Session.ErrorOccured -= Session_ErrorOccured;
+                    StopPort(Session.ConnectionInfo.Timeout);
+                }
+            }
+            else
             {
-                this.Session.ErrorOccured -= Session_ErrorOccured;
+                StopPort(TimeSpan.Zero);
             }
         }
 
+        /// <summary>
+        /// Ensures the current instance is not disposed.
+        /// </summary>
+        /// <exception cref="ObjectDisposedException">The current instance is disposed.</exception>
+        protected abstract void CheckDisposed();
+
         /// <summary>
         /// Raises <see cref="Renci.SshNet.ForwardedPort.Exception"/> event.
         /// </summary>
@@ -108,10 +157,10 @@ namespace Renci.SshNet
         /// </summary>
         private void RaiseClosing()
         {
-            var handlers = _closingEvent;
+            var handlers = Closing;
             if (handlers != null)
             {
-                handlers(this, new EventArgs());
+                handlers(this, EventArgs.Empty);
             }
         }
 

+ 279 - 121
Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.NET.cs

@@ -1,4 +1,6 @@
 using System;
+using System.Diagnostics;
+using System.IO;
 using System.Linq;
 using System.Text;
 using System.Net;
@@ -11,111 +13,233 @@ namespace Renci.SshNet
 {
     public partial class ForwardedPortDynamic
     {
-        private TcpListener _listener;
-        private readonly object _listenerLocker = new object();
+        private Socket _listener;
+        private int _pendingRequests;
 
         partial void InternalStart()
         {
-            //  If port already started don't start it again
-            if (this.IsStarted)
-                return;
-
             var ip = IPAddress.Any;
-            if (!string.IsNullOrEmpty(this.BoundHost))
+            if (!string.IsNullOrEmpty(BoundHost))
             {
-                ip = this.BoundHost.GetIPAddress();
+                ip = BoundHost.GetIPAddress();
             }
 
-            var ep = new IPEndPoint(ip, (int)this.BoundPort);
+            var ep = new IPEndPoint(ip, (int) BoundPort);
 
-            this._listener = new TcpListener(ep);
-            this._listener.Start();
+            _listener = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp) {Blocking = true};
+            _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
+            _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
+            _listener.Bind(ep);
+            _listener.Listen(1);
 
-            this._listenerTaskCompleted = new ManualResetEvent(false);
-            this.ExecuteThread(() =>
-            {
-                try
+            Session.ErrorOccured += Session_ErrorOccured;
+            Session.Disconnected += Session_Disconnected;
+
+            _listenerCompleted = new ManualResetEvent(false);
+
+            ExecuteThread(() =>
                 {
-                    while (true)
+                    try
                     {
-                        lock (this._listenerLocker)
+                        while (true)
                         {
-                            if (this._listener == null)
-                                break;
+                            // accept new inbound connection
+                            var asyncResult = _listener.BeginAccept(AcceptCallback, _listener);
+                            // wait for the connection to be established
+                            asyncResult.AsyncWaitHandle.WaitOne();
                         }
+                    }
+                    catch (ObjectDisposedException)
+                    {
+                        // BeginAccept will throw an ObjectDisposedException when the
+                        // socket is closed
+                    }
+                    catch (Exception ex)
+                    {
+                        RaiseExceptionEvent(ex);
+                    }
+                    finally
+                    {
+                        // mark listener stopped
+                        _listenerCompleted.Set();
+                    }
+                });
+        }
+
+        private void Session_Disconnected(object sender, EventArgs e)
+        {
+            StopListener();
+        }
 
-                        var socket = this._listener.AcceptSocket();
+        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
+        {
+            StopListener();
+        }
+
+        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 socket is closed, an ObjectDisposedException is thrown
+                // by Socket.EndAccept(IAsyncResult)
+                return;
+            }
+
+            Interlocked.Increment(ref _pendingRequests);
+
+            try
+            {
+                clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
+                clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
+
+                using (var channel = Session.CreateChannelDirectTcpip())
+                {
+                    channel.Exception += Channel_Exception;
 
-                        this.ExecuteThread(() =>
+                    var version = new byte[1];
+
+                    // create eventhandler which is to be invoked to interrupt a blocking receive
+                    // when we're closing the forwarded port
+                    EventHandler closeClientSocket = (sender, args) => CloseSocket(clientSocket);
+
+                    try
+                    {
+                        Closing += closeClientSocket;
+
+                        var bytesRead = clientSocket.Receive(version);
+                        if (bytesRead == 0)
+                        {
+                            CloseSocket(clientSocket);
+                            return;
+                        }
+
+                        if (version[0] == 4)
+                        {
+                            this.HandleSocks4(clientSocket, channel);
+                        }
+                        else if (version[0] == 5)
+                        {
+                            this.HandleSocks5(clientSocket, channel);
+                        }
+                        else
                         {
-                            try
-                            {
-                                using (var channel = this.Session.CreateChannelDirectTcpip())
-                                {
-                                    var version = new byte[1];
-
-                                    socket.Receive(version);
-
-                                    if (version[0] == 4)
-                                    {
-                                        this.HandleSocks4(socket, channel);
-                                    }
-                                    else if (version[0] == 5)
-                                    {
-                                        this.HandleSocks5(socket, channel);
-                                    }
-                                    else
-                                    {
-                                        throw new NotSupportedException(string.Format("SOCKS version {0} is not supported.", version));
-                                    }
-
-                                    channel.Bind();
-
-                                    channel.Close();
-                                }
-                            }
-                            catch (Exception exp)
-                            {
-                                this.RaiseExceptionEvent(exp);
-                            }
-                        });
+                            throw new NotSupportedException(string.Format("SOCKS version {0} is not supported.",
+                                version[0]));
+                        }
+
+                        // interrupt of blocking receive is now handled by channel (SOCKS4 and SOCKS5)
+                        // or no longer necessary
+                        Closing -= closeClientSocket;
+
+                        // start receiving from client socket (and sending to server)
+                        channel.Bind();
                     }
-                }
-                catch (SocketException exp)
-                {
-                    if (exp.SocketErrorCode != SocketError.Interrupted)
+                    finally
                     {
-                        this.RaiseExceptionEvent(exp);
+                        channel.Close();
                     }
                 }
-                catch (Exception exp)
-                {
-                    this.RaiseExceptionEvent(exp);
-                }
-                finally
+            }
+            catch (SocketException ex)
+            {
+                // ignore exception thrown by interrupting the blocking receive as part of closing
+                // the forwarded port
+                if (ex.SocketErrorCode != SocketError.Interrupted)
                 {
-                    this._listenerTaskCompleted.Set();
+                    RaiseExceptionEvent(ex);
                 }
-            });
+                CloseSocket(clientSocket);
+            }
+            catch (Exception exp)
+            {
+                RaiseExceptionEvent(exp);
+                CloseSocket(clientSocket);
+            }
+            finally
+            {
+                Interlocked.Decrement(ref _pendingRequests);
+            }
+        }
+
+        private static void CloseSocket(Socket socket)
+        {
+            if (socket.Connected)
+            {
+                socket.Shutdown(SocketShutdown.Both);
+                socket.Close();
+            }
+        }
 
-            this.IsStarted = true;
+        partial void StopListener()
+        {
+            //  if the port is not started then there's nothing to stop
+            if (!IsStarted)
+                return;
+
+            Session.ErrorOccured -= Session_ErrorOccured;
+            Session.Disconnected -= Session_Disconnected;
+
+            // close listener socket
+            _listener.Close();
+            // wait for listener loop to finish
+            _listenerCompleted.WaitOne();
         }
 
-        partial void InternalStop()
+        /// <summary>
+        /// Waits for pending requests to finish, and channels to close.
+        /// </summary>
+        /// <param name="timeout">The maximum time to wait for the forwarded port to stop.</param>
+        partial void InternalStop(TimeSpan timeout)
         {
-            //  If port not started you cant stop it
-            if (!this.IsStarted)
+            if (timeout == TimeSpan.Zero)
                 return;
 
-            lock (this._listenerLocker)
+            var stopWatch = new Stopwatch();
+            stopWatch.Start();
+
+            // break out of loop when one of the following conditions are met:
+            // * the forwarded port is restarted
+            // * all pending requests have been processed and corresponding channel are closed
+            // * the specified timeout has elapsed
+            while (!IsStarted)
+            {
+                // break out of loop when all pending requests have been processed
+                if (Interlocked.CompareExchange(ref _pendingRequests, 0, 0) == 0)
+                    break;
+                // break out of loop when specified timeout has elapsed
+                if (stopWatch.Elapsed >= timeout && timeout != SshNet.Session.InfiniteTimeSpan)
+                    break;
+                // give channels time to process pending requests
+                Thread.Sleep(50);
+            }
+
+            stopWatch.Stop();
+        }
+
+        partial void InternalDispose(bool disposing)
+        {
+            if (disposing)
             {
-                this._listener.Stop();
-                this._listener = null;
+                if (Session != null)
+                {
+                    Session.ErrorOccured -= Session_ErrorOccured;
+                    Session.Disconnected -= Session_Disconnected;
+                }
+                if (_listener != null)
+                {
+                    _listener.Dispose();
+                    _listener = null;
+                }
             }
-            this._listenerTaskCompleted.WaitOne(this.Session.ConnectionInfo.Timeout);
-            this._listenerTaskCompleted.Dispose();
-            this._listenerTaskCompleted = null;
-            this.IsStarted = false;
         }
 
         private void HandleSocks4(Socket socket, IChannelDirectTcpip channel)
@@ -141,19 +265,27 @@ namespace Renci.SshNet
 
                 channel.Open(host, port, this, socket);
 
-                stream.WriteByte(0x00);
-
-                if (channel.IsOpen)
+                using (var writeStream = new MemoryStream())
                 {
-                    stream.WriteByte(0x5a);
-                }
-                else
-                {
-                    stream.WriteByte(0x5b);
-                }
+                    writeStream.WriteByte(0x00);
+
+                    if (channel.IsOpen)
+                    {
+                        writeStream.WriteByte(0x5a);
+                    }
+                    else
+                    {
+                        writeStream.WriteByte(0x5b);
+                    }
+
+                    writeStream.Write(portBuffer, 0, portBuffer.Length);
+                    writeStream.Write(ipBuffer, 0, ipBuffer.Length);
 
-                stream.Write(portBuffer, 0, portBuffer.Length);
-                stream.Write(ipBuffer, 0, ipBuffer.Length);
+                    // write buffer to stream
+                    var writeBuffer = writeStream.ToArray();
+                    stream.Write(writeBuffer, 0, writeBuffer.Length);
+                    stream.Flush();
+                }
             }
         }
 
@@ -166,15 +298,13 @@ namespace Renci.SshNet
                 var authenticationMethods = new byte[authenticationMethodsCount];
                 stream.Read(authenticationMethods, 0, authenticationMethods.Length);
 
-                stream.WriteByte(0x05);
-
                 if (authenticationMethods.Min() == 0)
                 {
-                    stream.WriteByte(0x00);
+                    stream.Write(new byte[] { 0x05, 0x00 }, 0, 2);
                 }
                 else
                 {
-                    stream.WriteByte(0xFF);
+                    stream.Write(new byte[] { 0x05, 0xFF }, 0, 2);
                 }
 
                 var version = stream.ReadByte();
@@ -210,6 +340,10 @@ namespace Renci.SshNet
                             stream.Read(addressBuffer, 0, addressBuffer.Length);
 
                             ipAddress = IPAddress.Parse(new Common.ASCIIEncoding().GetString(addressBuffer));
+
+                            //var hostName = new Common.ASCIIEncoding().GetString(addressBuffer);
+
+                            //ipAddress = Dns.GetHostEntry(hostName).AddressList[0];
                         }
                         break;
                     case 0x04:
@@ -233,47 +367,71 @@ namespace Renci.SshNet
 
                 channel.Open(host, port, this, socket);
 
-                stream.WriteByte(0x05);
-
-                if (channel.IsOpen)
+                using (var writeStream = new MemoryStream())
                 {
-                    stream.WriteByte(0x00);
-                }
-                else
-                {
-                    stream.WriteByte(0x01);
-                }
+                    writeStream.WriteByte(0x05);
 
-                stream.WriteByte(0x00);
+                    if (channel.IsOpen)
+                    {
+                        writeStream.WriteByte(0x00);
+                    }
+                    else
+                    {
+                        writeStream.WriteByte(0x01);
+                    }
 
-                var buffer = ipAddress.GetAddressBytes();
+                    writeStream.WriteByte(0x00);
 
-                if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
-                {
-                    stream.WriteByte(0x01);
-                }
-                else if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
-                {
-                    stream.WriteByte(0x04);
-                }
-                else
-                {
-                    throw new NotSupportedException("Not supported address family.");
-                }
+                    if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
+                    {
+                        writeStream.WriteByte(0x01);
+                    }
+                    else if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
+                    {
+                        writeStream.WriteByte(0x04);
+                    }
+                    else
+                    {
+                        throw new NotSupportedException("Not supported address family.");
+                    }
 
-                stream.Write(buffer, 0, buffer.Length);
-                stream.Write(portBuffer, 0, portBuffer.Length);
+                    var addressBytes = ipAddress.GetAddressBytes();
+                    writeStream.Write(addressBytes, 0, addressBytes.Length);
+                    writeStream.Write(portBuffer, 0, portBuffer.Length);
+
+                    // write buffer to stream
+                    var writeBuffer = writeStream.ToArray();
+                    stream.Write(writeBuffer, 0, writeBuffer.Length);
+                    stream.Flush();
+                }
             }
         }
 
-        private static string ReadString(NetworkStream stream)
+        private void Channel_Exception(object sender, ExceptionEventArgs e)
         {
-            StringBuilder text = new StringBuilder();
-            var aa = (char)stream.ReadByte();
-            while (aa != 0)
+            RaiseExceptionEvent(e.Exception);
+        }
+
+        private static string ReadString(Stream stream)
+        {
+            var text = new StringBuilder();
+            while (true)
             {
-                text.Append(aa);
-                aa = (char)stream.ReadByte();
+                var byteRead = stream.ReadByte();
+                if (byteRead == 0)
+                {
+                    // end of the string
+                    break;
+                }
+
+                if (byteRead == -1)
+                {
+                    // the client shut down the socket
+                    break;
+                }
+
+                var c = (char) byteRead;
+                text.Append(c);
             }
             return text.ToString();
         }

+ 4 - 0
Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.NET40.cs

@@ -5,6 +5,10 @@ namespace Renci.SshNet
 {
     public partial class ForwardedPortDynamic
     {
+        /// <summary>
+        /// Executes the specified action in a separate thread.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
         partial void ExecuteThread(Action action)
         {
             ThreadPool.QueueUserWorkItem(o => action());

+ 71 - 25
Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.cs

@@ -6,19 +6,30 @@ namespace Renci.SshNet
     /// <summary>
     /// Provides functionality for dynamic port forwarding
     /// </summary>
-    public partial class ForwardedPortDynamic : ForwardedPort, IForwardedPort
+    public partial class ForwardedPortDynamic : ForwardedPort
     {
-        private EventWaitHandle _listenerTaskCompleted;
+        private EventWaitHandle _listenerCompleted;
 
         /// <summary>
         /// Gets the bound host.
         /// </summary>
-        public string BoundHost { get; protected set; }
+        public string BoundHost { get; private set; }
 
         /// <summary>
         /// Gets the bound port.
         /// </summary>
-        public uint BoundPort { get; protected set; }
+        public uint BoundPort { get; private set; }
+
+        /// <summary>
+        /// Gets or sets 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); }
+        }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ForwardedPortDynamic"/> class.
@@ -36,36 +47,73 @@ namespace Renci.SshNet
         /// <param name="port">The port.</param>
         public ForwardedPortDynamic(string host, uint port)
         {
-            this.BoundHost = host;
-            this.BoundPort = port;
+            BoundHost = host;
+            BoundPort = port;
         }
 
         /// <summary>
         /// Starts local port forwarding.
         /// </summary>
-        public override void Start()
+        protected override void StartPort()
         {
-            this.InternalStart();
+            InternalStart();
         }
 
         /// <summary>
-        /// Stops local port forwarding.
+        /// Stops local port forwarding, and waits for the specified timeout until all pending
+        /// requests are processed.
         /// </summary>
-        public override void Stop()
+        protected override void StopPort(TimeSpan timeout)
         {
-            base.Stop();
+            if (IsStarted)
+            {
+                // prevent new requests from getting processed before we signal existing
+                // channels that the port is closing
+                StopListener();
+                // signal existing channels that the port is closing
+                base.StopPort(timeout);
+            }
+            // wait for open channels to close
+            InternalStop(timeout);
+        }
 
-            this.InternalStop();
+        /// <summary>
+        /// Ensures the current instance is not disposed.
+        /// </summary>
+        /// <exception cref="ObjectDisposedException">The current instance is disposed.</exception>
+        protected override void CheckDisposed()
+        {
+            if (_isDisposed)
+                throw new ObjectDisposedException(GetType().FullName);
         }
 
         partial void InternalStart();
 
-        partial void InternalStop();
+        /// <summary>
+        /// Stops the listener.
+        /// </summary>
+        partial void StopListener();
+
+        /// <summary>
+        /// Waits for pending requests to finish, and channels to close.
+        /// </summary>
+        /// <param name="timeout">The maximum time to wait for the forwarded port to stop.</param>
+        partial void InternalStop(TimeSpan timeout);
 
+        /// <summary>
+        /// Executes the specified action in a separate thread.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
         partial void ExecuteThread(Action action);
 
         #region IDisposable Members
 
+        /// <summary>
+        /// Holds a value indicating whether the current instance is disposed.
+        /// </summary>
+        /// <value>
+        /// <c>true</c> if the current instance is disposed; otherwise, <c>false</c>.
+        /// </value>
         private bool _isDisposed;
 
         /// <summary>
@@ -74,34 +122,32 @@ namespace Renci.SshNet
         public void Dispose()
         {
             Dispose(true);
-
             GC.SuppressFinalize(this);
         }
 
+        partial void InternalDispose(bool disposing);
+
         /// <summary>
         /// Releases unmanaged and - optionally - managed resources
         /// </summary>
         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
-        protected virtual void Dispose(bool disposing)
+        protected override void Dispose(bool disposing)
         {
-            // Check to see if Dispose has already been called.
-            if (!this._isDisposed)
+            if (!_isDisposed)
             {
-                this.InternalStop();
+                base.Dispose(disposing);
 
-                // If disposing equals true, dispose all managed
-                // and unmanaged ResourceMessages.
                 if (disposing)
                 {
-                    // Dispose managed ResourceMessages.
-                    if (this._listenerTaskCompleted != null)
+                    if (_listenerCompleted != null)
                     {
-                        this._listenerTaskCompleted.Dispose();
-                        this._listenerTaskCompleted = null;
+                        _listenerCompleted.Dispose();
+                        _listenerCompleted = null;
                     }
                 }
 
-                // Note disposing has been done.
+                InternalDispose(disposing);
+
                 _isDisposed = true;
             }
         }

+ 124 - 91
Renci.SshClient/Renci.SshNet/ForwardedPortLocal.NET.cs

@@ -3,6 +3,7 @@ using System.Diagnostics;
 using System.Net.Sockets;
 using System.Net;
 using System.Threading;
+using Renci.SshNet.Common;
 
 namespace Renci.SshNet
 {
@@ -11,141 +12,173 @@ namespace Renci.SshNet
     /// </summary>
     public partial class ForwardedPortLocal
     {
-        private TcpListener _listener;
-        private readonly object _listenerLocker = new object();
+        private Socket _listener;
         private int _pendingRequests;
 
+        partial void ExecuteThread(Action action);
+
         partial void InternalStart()
         {
-            //  If port already started don't start it again
-            if (this.IsStarted)
-                return;
+            var addr = BoundHost.GetIPAddress();
+            var ep = new IPEndPoint(addr, (int) BoundPort);
 
-            IPAddress addr = this.BoundHost.GetIPAddress();
-            var ep = new IPEndPoint(addr, (int)this.BoundPort);
+            _listener = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp) {Blocking = true};
+            _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
+            _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
+            _listener.Bind(ep);
+            _listener.Listen(1);
 
-            this._listener = new TcpListener(ep);
-            this._listener.Start();
-            //  Update bound port if original was passed as zero
-            this.BoundPort = (uint)((IPEndPoint)_listener.LocalEndpoint).Port;
+            // update bound port (in case original was passed as zero)
+            BoundPort = (uint)((IPEndPoint)_listener.LocalEndPoint).Port;
 
-            this.Session.ErrorOccured += Session_ErrorOccured;
-            this.Session.Disconnected += Session_Disconnected;
+            Session.ErrorOccured += Session_ErrorOccured;
+            Session.Disconnected += Session_Disconnected;
 
-            this._listenerTaskCompleted = new ManualResetEvent(false);
-            this.ExecuteThread(() =>
-            {
-                try
+            _listenerTaskCompleted = new ManualResetEvent(false);
+
+            ExecuteThread(() =>
                 {
-                    while (true)
+                    try
                     {
-                        lock (this._listenerLocker)
+                        while (true)
                         {
-                            if (this._listener == null)
-                                break;
+                            // accept new inbound connection
+                            var asyncResult = _listener.BeginAccept(AcceptCallback, _listener);
+                            // wait for the connection to be established
+                            asyncResult.AsyncWaitHandle.WaitOne();
                         }
-
-                        var socket = this._listener.AcceptSocket();
-
-                        this.ExecuteThread(() =>
-                        {
-                            try
-                            {
-                                Interlocked.Increment(ref _pendingRequests);
-
-                                var originatorEndPoint = (IPEndPoint) socket.RemoteEndPoint;
-
-                                this.RaiseRequestReceived(originatorEndPoint.Address.ToString(),
-                                    (uint) originatorEndPoint.Port);
-
-                                using (var channel = this.Session.CreateChannelDirectTcpip())
-                                {
-                                    channel.Open(this.Host, this.Port, this, socket);
-                                    channel.Bind();
-                                    channel.Close();
-                                }
-                            }
-                            catch (Exception exp)
-                            {
-                                this.RaiseExceptionEvent(exp);
-                            }
-                            finally
-                            {
-                                Interlocked.Decrement(ref _pendingRequests);
-                            }
-                        });
                     }
-                }
-                catch (SocketException exp)
-                {
-                    if (exp.SocketErrorCode != SocketError.Interrupted)
+                    catch (ObjectDisposedException)
                     {
-                        this.RaiseExceptionEvent(exp);
+                        // BeginAccept will throw an ObjectDisposedException when the
+                        // socket is closed
                     }
-                }
-                catch (Exception exp)
-                {
-                    this.RaiseExceptionEvent(exp);
-                }
-                finally
-                {
-                    this._listenerTaskCompleted.Set();
-                }
-            });
-
-            this.IsStarted = true;
+                    catch (Exception ex)
+                    {
+                        RaiseExceptionEvent(ex);
+                    }
+                    finally
+                    {
+                        // mark listener stopped
+                        _listenerTaskCompleted.Set();
+                    }
+                });
         }
 
-        partial void InternalStop()
+        private void AcceptCallback(IAsyncResult ar)
         {
-            //  If port not started you cant stop it
-            if (!this.IsStarted)
+            // Get the socket that handles the client request
+            var serverSocket = (Socket)ar.AsyncState;
+
+            Socket clientSocket;
+
+            try
+            {
+                clientSocket = serverSocket.EndAccept(ar);
+            }
+            catch (ObjectDisposedException)
+            {
+                // when the socket is closed, an ObjectDisposedException is thrown
+                // by Socket.EndAccept(IAsyncResult)
                 return;
+            }
+
+            Interlocked.Increment(ref _pendingRequests);
+
+            try
+            {
+                clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
+                clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
 
-            this.Session.Disconnected -= Session_Disconnected;
-            this.Session.ErrorOccured -= Session_ErrorOccured;
+                var originatorEndPoint = (IPEndPoint) clientSocket.RemoteEndPoint;
 
-            this.StopListener();
+                RaiseRequestReceived(originatorEndPoint.Address.ToString(),
+                    (uint)originatorEndPoint.Port);
 
-            this._listenerTaskCompleted.WaitOne(this.Session.ConnectionInfo.Timeout);
-            this._listenerTaskCompleted.Dispose();
-            this._listenerTaskCompleted = null;
+                using (var channel = Session.CreateChannelDirectTcpip())
+                {
+                    channel.Exception += Channel_Exception;
+                    channel.Open(Host, Port, this, clientSocket);
+                    channel.Bind();
+                    channel.Close();
+                }
+            }
+            catch (Exception exp)
+            {
+                RaiseExceptionEvent(exp);
+                CloseSocket(clientSocket);
+            }
+            finally
+            {
+                Interlocked.Decrement(ref _pendingRequests);
+            }
+        }
+
+        private static void CloseSocket(Socket socket)
+        {
+            if (socket.Connected)
+            {
+                socket.Shutdown(SocketShutdown.Both);
+                socket.Close();
+            }
+        }
+
+        partial void InternalStop(TimeSpan timeout)
+        {
+            if (timeout == TimeSpan.Zero)
+                return;
 
             var stopWatch = new Stopwatch();
             stopWatch.Start();
 
-            while (stopWatch.Elapsed < this.Session.ConnectionInfo.Timeout || this.Session.ConnectionInfo.Timeout == SshNet.Session.Infinite)
+            while (true)
             {
+                // break out of loop when all pending requests have been processed
                 if (Interlocked.CompareExchange(ref _pendingRequests, 0, 0) == 0)
                     break;
+                // break out of loop when specified timeout has elapsed
+                if (stopWatch.Elapsed >= timeout && timeout != SshNet.Session.InfiniteTimeSpan)
+                    break;
+                // give channels time to process pending requests
                 Thread.Sleep(50);
             }
 
             stopWatch.Stop();
-
-            this.IsStarted = false;
         }
 
-        private void StopListener()
+        /// <summary>
+        /// Interrupts the listener, and waits for the listener loop to finish.
+        /// </summary>
+        /// <remarks>
+        /// When the forwarded port is stopped, then any further action is skipped.
+        /// </remarks>
+        partial void StopListener()
         {
-            lock (this._listenerLocker)
-            {
-                if (this._listener != null)
-                {
-                    this._listener.Stop();
-                    this._listener = null;
-                }
-            }
+            if (!IsStarted)
+                return;
+
+            Session.Disconnected -= Session_Disconnected;
+            Session.ErrorOccured -= Session_ErrorOccured;
+
+            // close listener socket
+            _listener.Close();
+            // wait for listener loop to finish
+            _listenerTaskCompleted.WaitOne();
         }
 
         private void Session_ErrorOccured(object sender, Common.ExceptionEventArgs e)
         {
-            this.StopListener();
+            StopListener();
         }
 
         private void Session_Disconnected(object sender, EventArgs e)
         {
-            this.StopListener();
+            StopListener();
+        }
+
+        private void Channel_Exception(object sender, ExceptionEventArgs e)
+        {
+            RaiseExceptionEvent(e.Exception);
         }
     }
 }

+ 4 - 0
Renci.SshClient/Renci.SshNet/ForwardedPortLocal.NET40.cs

@@ -8,6 +8,10 @@ namespace Renci.SshNet
     /// </summary>
     public partial class ForwardedPortLocal 
     {
+        /// <summary>
+        /// Executes the specified action in a separate thread.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
         partial void ExecuteThread(Action action)
         {
             ThreadPool.QueueUserWorkItem(o => action());

+ 50 - 19
Renci.SshClient/Renci.SshNet/ForwardedPortLocal.cs

@@ -30,6 +30,17 @@ namespace Renci.SshNet
         /// </summary>
         public uint Port { get; private set; }
 
+        /// <summary>
+        /// Gets or sets 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); }
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ForwardedPortLocal"/> class.
         /// </summary>
@@ -92,32 +103,57 @@ namespace Renci.SshNet
         /// <summary>
         /// Starts local port forwarding.
         /// </summary>
-        public override void Start()
+        protected override void StartPort()
         {
             this.InternalStart();
         }
 
         /// <summary>
-        /// Stops local port forwarding.
+        /// Stops local port forwarding, and waits for the specified timeout until all pending
+        /// requests are processed.
+        /// </summary>
+        protected override void StopPort(TimeSpan timeout)
+        {
+            if (IsStarted)
+            {
+                // prevent new requests from getting processed before we signal existing
+                // channels that the port is closing
+                StopListener();
+                // signal existing channels that the port is closing
+                base.StopPort(timeout);
+            }
+            // wait for open channels to close
+            InternalStop(timeout);
+        }
+
+        /// <summary>
+        /// Ensures the current instance is not disposed.
         /// </summary>
-        public override void Stop()
+        /// <exception cref="ObjectDisposedException">The current instance is disposed.</exception>
+        protected override void CheckDisposed()
         {
-            base.Stop();
-            this.InternalStop();
+            if (_isDisposed)
+                throw new ObjectDisposedException(GetType().FullName);
         }
 
         partial void InternalStart();
 
-        partial void InternalStop();
+        /// <summary>
+        /// Interrupts the listener, and waits for the listener loop to finish.
+        /// </summary>
+        /// <remarks>
+        /// When the forwarded port is stopped, then any further action is skipped.
+        /// </remarks>
+        partial void StopListener();
 
-        partial void ExecuteThread(Action action);
+        partial void InternalStop(TimeSpan timeout);
 
         #region IDisposable Members
 
         private bool _isDisposed;
 
         /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged ResourceMessages.
+        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
         /// </summary>
         public void Dispose()
         {
@@ -129,26 +165,21 @@ namespace Renci.SshNet
         /// Releases unmanaged and - optionally - managed resources
         /// </summary>
         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
-        protected virtual void Dispose(bool disposing)
+        protected override void Dispose(bool disposing)
         {
-            // Check to see if Dispose has already been called.
-            if (!this._isDisposed)
+            if (!_isDisposed)
             {
-                this.InternalStop();
+                base.Dispose(disposing);
 
-                // If disposing equals true, dispose all managed
-                // and unmanaged ResourceMessages.
                 if (disposing)
                 {
-                    // Dispose managed ResourceMessages.
-                    if (this._listenerTaskCompleted != null)
+                    if (_listenerTaskCompleted != null)
                     {
-                        this._listenerTaskCompleted.Dispose();
-                        this._listenerTaskCompleted = null;
+                        _listenerTaskCompleted.Dispose();
+                        _listenerTaskCompleted = null;
                     }
                 }
 
-                // Note disposing has been done.
                 _isDisposed = true;
             }
         }

+ 4 - 0
Renci.SshClient/Renci.SshNet/ForwardedPortRemote.NET40.cs

@@ -8,6 +8,10 @@ namespace Renci.SshNet
     /// </summary>
     public partial class ForwardedPortRemote
     {
+        /// <summary>
+        /// Executes the specified action in a separate thread.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
         partial void ExecuteThread(Action action)
         {
             ThreadPool.QueueUserWorkItem(o => action());

+ 140 - 76
Renci.SshClient/Renci.SshNet/ForwardedPortRemote.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 using System.Threading;
 using Renci.SshNet.Messages.Connection;
 using Renci.SshNet.Common;
@@ -15,11 +16,24 @@ namespace Renci.SshNet
         private bool _requestStatus;
 
         private EventWaitHandle _globalRequestResponse = new AutoResetEvent(false);
+        private int _pendingRequests;
+        private bool _isStarted;
+
+        /// <summary>
+        /// Gets or sets 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; }
+        }
 
         /// <summary>
         /// Gets the bound host.
         /// </summary>
-        public IPAddress BoundHostAddress { get; protected set; }
+        public IPAddress BoundHostAddress { get; private set; }
 
         /// <summary>
         /// Gets the bound host.
@@ -28,19 +42,19 @@ namespace Renci.SshNet
         {
             get
             {
-                return this.BoundHostAddress.ToString();
+                return BoundHostAddress.ToString();
             }
         }
 
         /// <summary>
         /// Gets the bound port.
         /// </summary>
-        public uint BoundPort { get; protected set; }
+        public uint BoundPort { get; private set; }
 
         /// <summary>
         /// Gets the forwarded host.
         /// </summary>
-        public IPAddress HostAddress { get; protected set; }
+        public IPAddress HostAddress { get; private set; }
 
         /// <summary>
         /// Gets the forwarded host.
@@ -49,14 +63,14 @@ namespace Renci.SshNet
         {
             get
             {
-                return this.HostAddress.ToString();
+                return HostAddress.ToString();
             }
         }
 
         /// <summary>
         /// Gets the forwarded port.
         /// </summary>
-        public uint Port { get; protected set; }
+        public uint Port { get; private set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ForwardedPortRemote" /> class.
@@ -79,67 +93,95 @@ namespace Renci.SshNet
             boundPort.ValidatePort("boundPort");
             port.ValidatePort("port");
 
-            this.BoundHostAddress = boundHostAddress;
-            this.BoundPort = boundPort;
-            this.HostAddress = hostAddress;
-            this.Port = port;
+            BoundHostAddress = boundHostAddress;
+            BoundPort = boundPort;
+            HostAddress = hostAddress;
+            Port = port;
         }
 
         /// <summary>
         /// Starts remote port forwarding.
         /// </summary>
-        public override void Start()
+        protected override void StartPort()
         {
-            base.Start();
-
-            //  If port already started don't start it again
-            if (this.IsStarted)
-                return;
-
-            this.Session.RegisterMessage("SSH_MSG_REQUEST_FAILURE");
-            this.Session.RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
-            this.Session.RegisterMessage("SSH_MSG_CHANNEL_OPEN");
+            Session.RegisterMessage("SSH_MSG_REQUEST_FAILURE");
+            Session.RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
+            Session.RegisterMessage("SSH_MSG_CHANNEL_OPEN");
 
-            this.Session.RequestSuccessReceived += Session_RequestSuccess;
-            this.Session.RequestFailureReceived += Session_RequestFailure;
-            this.Session.ChannelOpenReceived += Session_ChannelOpening;
+            Session.RequestSuccessReceived += Session_RequestSuccess;
+            Session.RequestFailureReceived += Session_RequestFailure;
+            Session.ChannelOpenReceived += Session_ChannelOpening;
 
-            //  Send global request to start direct tcpip
-            this.Session.SendMessage(new GlobalRequestMessage(GlobalRequestName.TcpIpForward, true, this.BoundHost, this.BoundPort));
+            // send global request to start direct tcpip
+            Session.SendMessage(new GlobalRequestMessage(GlobalRequestName.TcpIpForward, true, BoundHost, BoundPort));
+            // wat for response on global request to start direct tcpip
+            Session.WaitOnHandle(_globalRequestResponse);
 
-            this.Session.WaitOnHandle(this._globalRequestResponse);
-
-            if (!this._requestStatus)
+            if (!_requestStatus)
             {
-                //  If request  failed don't handle channel opening for this request
-                this.Session.ChannelOpenReceived -= Session_ChannelOpening;
+                // when the request to start port forward was rejected, then we're no longer
+                // interested in these events
+                Session.RequestSuccessReceived -= Session_RequestSuccess;
+                Session.RequestFailureReceived -= Session_RequestFailure;
+                Session.ChannelOpenReceived -= Session_ChannelOpening;
 
-                throw new SshException(string.Format(CultureInfo.CurrentCulture, "Port forwarding for '{0}' port '{1}' failed to start.", this.Host, this.Port));
+                throw new SshException(string.Format(CultureInfo.CurrentCulture, "Port forwarding for '{0}' port '{1}' failed to start.", Host, Port));
             }
-            this.IsStarted = true;
+
+            _isStarted = true;
         }
 
         /// <summary>
         /// Stops remote port forwarding.
         /// </summary>
-        public override void Stop()
+        protected override void StopPort(TimeSpan timeout)
         {
-            base.Stop();
-
-            //  If port not started you cant stop it
-            if (!this.IsStarted)
+            // if the port not started, then there's nothing to stop
+            if (!IsStarted)
                 return;
 
-            //  Send global request to cancel direct tcpip
-            this.Session.SendMessage(new GlobalRequestMessage(GlobalRequestName.CancelTcpIpForward, true, this.BoundHost, this.BoundPort));
+            // mark forwarded port stopped, this also causes open of new channels to be rejected
+            _isStarted = false;
+
+            base.StopPort(timeout);
 
-            this.Session.WaitOnHandle(this._globalRequestResponse);
+            // send global request to cancel direct tcpip
+            Session.SendMessage(new GlobalRequestMessage(GlobalRequestName.CancelTcpIpForward, true, BoundHost, BoundPort));
+            // wait for response on global request to cancel direct tcpip or completion of message
+            // listener loop (in which case response on global request can never be received)
+            WaitHandle.WaitAny(new[] { _globalRequestResponse, Session.MessageListenerCompleted }, timeout);
 
-            this.Session.RequestSuccessReceived -= Session_RequestSuccess;
-            this.Session.RequestFailureReceived -= Session_RequestFailure;
-            this.Session.ChannelOpenReceived -= Session_ChannelOpening;
+            // unsubscribe from session events as either the tcpip forward is cancelled at the
+            // server, or our session message loop has completed
+            Session.RequestSuccessReceived -= Session_RequestSuccess;
+            Session.RequestFailureReceived -= Session_RequestFailure;
+            Session.ChannelOpenReceived -= Session_ChannelOpening;
+
+            var startWaiting = DateTime.Now;
+
+            while (true)
+            {
+                // break out of loop when all pending requests have been processed
+                if (Interlocked.CompareExchange(ref _pendingRequests, 0, 0) == 0)
+                    break;
+                // determine time elapsed since waiting for pending requests to finish
+                var elapsed = DateTime.Now - startWaiting;
+                // break out of loop when specified timeout has elapsed
+                if (elapsed >= timeout && timeout != SshNet.Session.InfiniteTimeSpan)
+                    break;
+                // give channels time to process pending requests
+                Thread.Sleep(50);
+            }
+        }
 
-            this.IsStarted = false;
+        /// <summary>
+        /// Ensures the current instance is not disposed.
+        /// </summary>
+        /// <exception cref="ObjectDisposedException">The current instance is disposed.</exception>
+        protected override void CheckDisposed()
+        {
+            if (_isDisposed)
+                throw new ObjectDisposedException(GetType().FullName);
         }
 
         private void Session_ChannelOpening(object sender, MessageEventArgs<ChannelOpenMessage> e)
@@ -149,43 +191,62 @@ namespace Renci.SshNet
             if (info != null)
             {
                 //  Ensure this is the corresponding request
-                if (info.ConnectedAddress == this.BoundHost && info.ConnectedPort == this.BoundPort)
+                if (info.ConnectedAddress == BoundHost && info.ConnectedPort == BoundPort)
                 {
-                    this.ExecuteThread(() =>
+                    if (!_isStarted)
                     {
-                        try
-                        {
-                            this.RaiseRequestReceived(info.OriginatorAddress, info.OriginatorPort);
-
-                            var channel = this.Session.CreateChannelForwardedTcpip(
-                                channelOpenMessage.LocalChannelNumber, channelOpenMessage.InitialWindowSize,
-                                channelOpenMessage.MaximumPacketSize);
-                            channel.Bind(this.HostAddress, this.Port);
-                        }
-                        catch (Exception exp)
+                        Session.SendMessage(new ChannelOpenFailureMessage(channelOpenMessage.LocalChannelNumber, "", ChannelOpenFailureMessage.AdministrativelyProhibited));
+                        return;
+                    }
+
+                    ExecuteThread(() =>
                         {
-                            this.RaiseExceptionEvent(exp);
-                        }
-                    });
+                            Interlocked.Increment(ref _pendingRequests);
+
+                            try
+                            {
+                                RaiseRequestReceived(info.OriginatorAddress, info.OriginatorPort);
+
+                                using (var channel = Session.CreateChannelForwardedTcpip(channelOpenMessage.LocalChannelNumber, channelOpenMessage.InitialWindowSize, channelOpenMessage.MaximumPacketSize))
+                                {
+                                    channel.Exception += Channel_Exception;
+                                    channel.Bind(new IPEndPoint(HostAddress, (int) Port), this);
+                                    channel.Close();
+                                }
+                            }
+                            catch (Exception exp)
+                            {
+                                RaiseExceptionEvent(exp);
+                            }
+                            finally
+                            {
+                                Interlocked.Decrement(ref _pendingRequests);
+                            }
+                        });
                 }
             }
         }
 
+        private void Channel_Exception(object sender, ExceptionEventArgs exceptionEventArgs)
+        {
+            RaiseExceptionEvent(exceptionEventArgs.Exception);
+        }
+
         private void Session_RequestFailure(object sender, EventArgs e)
         {
-            this._requestStatus = false;
-            this._globalRequestResponse.Set();
+            _requestStatus = false;
+            _globalRequestResponse.Set();
         }
 
         private void Session_RequestSuccess(object sender, MessageEventArgs<RequestSuccessMessage> e)
         {
-            this._requestStatus = true;
-            if (this.BoundPort == 0)
+            _requestStatus = true;
+            if (BoundPort == 0)
             {
-                this.BoundPort = (e.Message.BoundPort == null) ? 0 : e.Message.BoundPort.Value;
+                BoundPort = (e.Message.BoundPort == null) ? 0 : e.Message.BoundPort.Value;
             }
 
-            this._globalRequestResponse.Set();
+            _globalRequestResponse.Set();
         }
 
         partial void ExecuteThread(Action action);
@@ -200,7 +261,6 @@ namespace Renci.SshNet
         public void Dispose()
         {
             Dispose(true);
-
             GC.SuppressFinalize(this);
         }
 
@@ -208,24 +268,28 @@ namespace Renci.SshNet
         /// Releases unmanaged and - optionally - managed resources
         /// </summary>
         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool disposing)
+        protected override void Dispose(bool disposing)
         {
-            // Check to see if Dispose has already been called.
-            if (!this._isDisposed)
+            if (!_isDisposed)
             {
-                // If disposing equals true, dispose all managed
-                // and unmanaged resources.
+                base.Dispose(disposing);
+
                 if (disposing)
                 {
-                    // Dispose managed resources.
-                    if (this._globalRequestResponse != null)
+                    if (Session != null)
+                    {
+                        Session.RequestSuccessReceived -= Session_RequestSuccess;
+                        Session.RequestFailureReceived -= Session_RequestFailure;
+                        Session.ChannelOpenReceived -= Session_ChannelOpening;
+                        Session = null;
+                    }
+                    if (_globalRequestResponse != null)
                     {
-                        this._globalRequestResponse.Dispose();
-                        this._globalRequestResponse = null;
+                        _globalRequestResponse.Dispose();
+                        _globalRequestResponse = null;
                     }
                 }
 
-                // Note disposing has been done.
                 _isDisposed = true;
             }
         }

+ 1 - 1
Renci.SshClient/Renci.SshNet/IForwardedPort.cs

@@ -8,7 +8,7 @@ namespace Renci.SshNet
     internal interface IForwardedPort
     {
         /// <summary>
-        /// The <see cref="Closing"/> event occurs as the forward port is being stopped.
+        /// The <see cref="Closing"/> event occurs as the forwarded port is being stopped.
         /// </summary>
         event EventHandler Closing;
     }

+ 24 - 0
Renci.SshClient/Renci.SshNet/ISession.cs

@@ -44,6 +44,15 @@ namespace Renci.SshNet
         /// </value>
         SemaphoreLight SessionSemaphore { get; }
 
+        /// <summary>
+        /// Gets a <see cref="WaitHandle"/> that can be used to wait for the message listener loop to complete.
+        /// </summary>
+        /// <value>
+        /// A <see cref="WaitHandle"/> that can be used to wait for the message listener loop to complete, or
+        /// <c>null</c> when the session has not been connected.
+        /// </value>
+        WaitHandle MessageListenerCompleted { get; }
+
         /// <summary>
         /// Create a new SSH session channel.
         /// </summary>
@@ -103,6 +112,21 @@ namespace Renci.SshNet
         /// </remarks>
         void WaitOnHandle(WaitHandle waitHandle);
 
+        /// <summary>
+        /// Waits for the specified handle or the exception handle for the receive thread
+        /// to signal within the specified timeout.
+        /// </summary>
+        /// <param name="waitHandle">The wait handle.</param>
+        /// <param name="timeout">The time to wait for any of the handles to become signaled.</param>
+        /// <exception cref="SshConnectionException">A received package was invalid or failed the message integrity check.</exception>
+        /// <exception cref="SshOperationTimeoutException">None of the handles are signaled in time and the session is not disconnecting.</exception>
+        /// <exception cref="SocketException">A socket error was signaled while receiving messages from the server.</exception>
+        /// <remarks>
+        /// When neither handles are signaled in time and the session is not closing, then the
+        /// session is disconnected.
+        /// </remarks>
+        void WaitOnHandle(WaitHandle waitHandle, TimeSpan timeout);
+
         /// <summary>
         /// Occurs when <see cref="ChannelCloseMessage"/> message received
         /// </summary>

+ 4 - 0
Renci.SshClient/Renci.SshNet/KeyboardInteractiveAuthenticationMethod.NET40.cs

@@ -5,6 +5,10 @@ namespace Renci.SshNet
 {
     public partial  class KeyboardInteractiveAuthenticationMethod : AuthenticationMethod
     {
+        /// <summary>
+        /// Executes the specified action in a separate thread.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
         partial void ExecuteThread(Action action)
         {
             ThreadPool.QueueUserWorkItem(o => action());

+ 19 - 0
Renci.SshClient/Renci.SshNet/Messages/Connection/ChannelOpen/ForwardedTcpipChannelInfo.cs

@@ -5,6 +5,25 @@
     /// </summary>
     internal class ForwardedTcpipChannelInfo : ChannelOpenInfo
     {
+        /// <summary>
+        /// Initializes a new <see cref="ForwardedTcpipChannelInfo"/> instance.
+        /// </summary>
+        public ForwardedTcpipChannelInfo()
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new <see cref="ForwardedTcpipChannelInfo"/> instance with the specified connector
+        /// address and port, and originator address and port.
+        /// </summary>
+        public ForwardedTcpipChannelInfo(string connectedAddress, uint connectedPort, string originatorAddress, uint originatorPort)
+        {
+            ConnectedAddress = connectedAddress;
+            ConnectedPort = connectedPort;
+            OriginatorAddress = originatorAddress;
+            OriginatorPort = originatorPort;
+        }
+
         /// <summary>
         /// Specifies channel open type
         /// </summary>

+ 5 - 0
Renci.SshClient/Renci.SshNet/Messages/Connection/ChannelOpenFailureMessage.cs

@@ -6,6 +6,11 @@
     [Message("SSH_MSG_CHANNEL_OPEN_FAILURE", 92)]
     public class ChannelOpenFailureMessage : ChannelMessage
     {
+        internal const uint AdministrativelyProhibited = 1;
+        internal const uint ConnectFailed = 2;
+        internal const uint UnknownChannelType = 3;
+        internal const uint ResourceShortage = 4;
+
         /// <summary>
         /// Gets failure reason code.
         /// </summary>

+ 4 - 1
Renci.SshClient/Renci.SshNet/PasswordAuthenticationMethod.NET40.cs

@@ -5,7 +5,10 @@ namespace Renci.SshNet
 {
     public partial class PasswordAuthenticationMethod : AuthenticationMethod
     {
-        /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
+        /// <summary>
+        /// Executes the specified action in a separate thread.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
         partial void ExecuteThread(Action action)
         {
             ThreadPool.QueueUserWorkItem(o => action());

+ 4 - 0
Renci.SshClient/Renci.SshNet/Session.NET40.cs

@@ -16,6 +16,10 @@ namespace Renci.SshNet
             this.HandleMessage((dynamic)message);
         }
 
+        /// <summary>
+        /// Executes the specified action in a separate thread.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
         partial void ExecuteThread(Action action)
         {
             ThreadPool.QueueUserWorkItem(o => action());

+ 36 - 2
Renci.SshClient/Renci.SshNet/Session.cs

@@ -28,7 +28,18 @@ namespace Renci.SshNet
         /// <summary>
         /// Specifies an infinite waiting period.
         /// </summary>
-        internal static readonly TimeSpan Infinite = new TimeSpan(0, 0, 0, 0, -1);
+        /// <remarks>
+        /// The value of this field is <c>-1</c> millisecond. 
+        /// </remarks>
+        internal static readonly TimeSpan InfiniteTimeSpan = new TimeSpan(0, 0, 0, 0, -1);
+
+        /// <summary>
+        /// Specifies an infinite waiting period.
+        /// </summary>
+        /// <remarks>
+        /// The value of this field is <c>-1</c>.
+        /// </remarks>
+        internal static readonly int Infinite = -1;
 
         /// <summary>
         /// Specifies maximum packet size defined by the protocol.
@@ -83,7 +94,7 @@ namespace Renci.SshNet
         private readonly object _socketLock = new object();
 
         /// <summary>
-        /// Holds reference to task that listens for incoming messages
+        /// Holds a <see cref="WaitHandle"/> that is signaled when the message listener loop has completed.
         /// </summary>
         private EventWaitHandle _messageListenerCompleted;
 
@@ -714,6 +725,24 @@ namespace Renci.SshNet
             WaitOnHandle(waitHandle, ConnectionInfo.Timeout);
         }
 
+        /// <summary>
+        /// Waits for the specified handle or the exception handle for the receive thread
+        /// to signal within the specified timeout.
+        /// </summary>
+        /// <param name="waitHandle">The wait handle.</param>
+        /// <param name="timeout">The time to wait for any of the handles to become signaled.</param>
+        /// <exception cref="SshConnectionException">A received package was invalid or failed the message integrity check.</exception>
+        /// <exception cref="SshOperationTimeoutException">None of the handles are signaled in time and the session is not disconnecting.</exception>
+        /// <exception cref="SocketException">A socket error was signaled while receiving messages from the server.</exception>
+        /// <remarks>
+        /// When neither handles are signaled in time and the session is not closing, then the
+        /// session is disconnected.
+        /// </remarks>
+        void ISession.WaitOnHandle(WaitHandle waitHandle, TimeSpan timeout)
+        {
+            WaitOnHandle(waitHandle, timeout);
+        }
+
         /// <summary>
         /// Waits for the specified handle or the exception handle for the receive thread
         /// to signal within the connection timeout.
@@ -2216,6 +2245,11 @@ namespace Renci.SshNet
             get { return ConnectionInfo; }
         }
 
+        WaitHandle ISession.MessageListenerCompleted
+        {
+            get { return _messageListenerCompleted; }
+        }
+
         /// <summary>
         /// Create a new SSH session channel.
         /// </summary>

+ 2 - 3
Renci.SshClient/Renci.SshNet/SftpClient.NET40.cs

@@ -9,10 +9,9 @@ namespace Renci.SshNet
     public partial class SftpClient
     {
         /// <summary>
-        /// 
+        /// Executes the specified action in a separate thread.
         /// </summary>
-        /// <param name="action"></param>
-        /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
+        /// <param name="action">The action to execute.</param>
         partial void ExecuteThread(Action action)
         {
             ThreadPool.QueueUserWorkItem(o => action());

+ 1 - 1
Renci.SshClient/Renci.SshNet/SftpClient.cs

@@ -37,7 +37,7 @@ namespace Renci.SshNet
         /// </summary>
         /// <value>
         /// The timeout to wait until an operation completes. The default value is negative
-        /// one (-1) milliseconds, which indicates an infinite time-out period.
+        /// one (-1) milliseconds, which indicates an infinite timeout period.
         /// </value>
         /// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
         public TimeSpan OperationTimeout

Деякі файли не було показано, через те що забагато файлів було змінено