Procházet zdrojové kódy

Added internal IForwardPort interface to allow mocking of forwarded ports.
Added internal IChannelDirectTcpip and IChannelForwardedTcpip interfaces.
Extended ISession and IConnection interface to allow for unit tests of ChannelDirectTcpip.

Modified ChannelDirectTcpip to interrupt blocking receive when the ForwardedPort is closed.
Shutdown send of socket when EOF is received.
Close the socket when unhandled exception occurs in session message loop, and when server terminates connection using SSH_MSG_DISCONNECT message.
Fixes issue #1558.

When stopping ForwardedLocalPort, block until all channels have been closed or until Connection.Timeout has elapsed.

Gert Driesen před 11 roky
rodič
revize
00ed218425
29 změnil soubory, kde provedl 639 přidání a 154 odebrání
  1. 1 8
      Renci.SshClient/Renci.SshNet.NET35/Channels/ChannelDirectTcpip.NET35.cs
  2. 10 1
      Renci.SshClient/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj
  3. 10 1
      Renci.SshClient/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj
  4. 0 1
      Renci.SshClient/Renci.SshNet.Silverlight/Session.SilverlightShared.cs
  5. 10 1
      Renci.SshClient/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj
  6. 90 5
      Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest.cs
  7. 9 0
      Renci.SshClient/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj
  8. 26 0
      Renci.SshClient/Renci.SshNet.WindowsPhone8/ForwardedPortLocal.SilverlightShared.cs
  9. 11 1
      Renci.SshClient/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj
  10. 3 5
      Renci.SshClient/Renci.SshNet/BaseClient.cs
  11. 4 4
      Renci.SshClient/Renci.SshNet/Channels/Channel.cs
  12. 1 8
      Renci.SshClient/Renci.SshNet/Channels/ChannelDirectTcpip.NET40.cs
  13. 109 67
      Renci.SshClient/Renci.SshNet/Channels/ChannelDirectTcpip.cs
  14. 1 8
      Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.cs
  15. 1 1
      Renci.SshClient/Renci.SshNet/Channels/ClientChannel.cs
  16. 38 0
      Renci.SshClient/Renci.SshNet/Channels/IChannelDirectTcpip.cs
  17. 17 0
      Renci.SshClient/Renci.SshNet/Channels/IChannelForwardedTcpip.cs
  18. 0 1
      Renci.SshClient/Renci.SshNet/ConnectionInfo.cs
  19. 27 2
      Renci.SshClient/Renci.SshNet/ForwardedPort.cs
  20. 6 6
      Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.NET.cs
  21. 1 1
      Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.cs
  22. 26 8
      Renci.SshClient/Renci.SshNet/ForwardedPortLocal.NET.cs
  23. 4 6
      Renci.SshClient/Renci.SshNet/ForwardedPortLocal.cs
  24. 1 2
      Renci.SshClient/Renci.SshNet/ForwardedPortRemote.cs
  25. 30 1
      Renci.SshClient/Renci.SshNet/IConnectionInfo.cs
  26. 15 0
      Renci.SshClient/Renci.SshNet/IForwardedPort.cs
  27. 122 0
      Renci.SshClient/Renci.SshNet/ISession.cs
  28. 3 0
      Renci.SshClient/Renci.SshNet/Renci.SshNet.csproj
  29. 63 16
      Renci.SshClient/Renci.SshNet/Session.cs

+ 1 - 8
Renci.SshClient/Renci.SshNet.NET35/Channels/ChannelDirectTcpip.NET35.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Net.Sockets;
-using System.Threading;
+using System.Net.Sockets;
 
 namespace Renci.SshNet.Channels
 {
@@ -9,11 +7,6 @@ namespace Renci.SshNet.Channels
     /// </summary>
     internal partial class ChannelDirectTcpip
     {
-        partial void ExecuteThread(Action action)
-        {
-            ThreadPool.QueueUserWorkItem((o) => { action(); });
-        }
-
         partial void InternalSocketReceive(byte[] buffer, ref int read)
         {
             read = this._socket.Receive(buffer);

+ 10 - 1
Renci.SshClient/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -84,6 +84,12 @@
     <Compile Include="..\Renci.SshNet\Channels\IChannel.cs">
       <Link>Channels\IChannel.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelDirectTcpip.cs">
+      <Link>Channels\IChannelDirectTcpip.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelForwardedTcpip.cs">
+      <Link>Channels\IChannelForwardedTcpip.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Channels\IChannelSession.cs">
       <Link>Channels\IChannelSession.cs</Link>
     </Compile>
@@ -291,6 +297,9 @@
     <Compile Include="..\Renci.SshNet\IConnectionInfo.cs">
       <Link>IConnectionInfo.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\IForwardedPort.cs">
+      <Link>IForwardedPort.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ISession.cs">
       <Link>ISession.cs</Link>
     </Compile>
@@ -913,7 +922,7 @@
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
+      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 10 - 1
Renci.SshClient/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj

@@ -97,6 +97,12 @@
     <Compile Include="..\Renci.SshNet\Channels\IChannel.cs">
       <Link>Channels\IChannel.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelDirectTcpip.cs">
+      <Link>Channels\IChannelDirectTcpip.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelForwardedTcpip.cs">
+      <Link>Channels\IChannelForwardedTcpip.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Channels\IChannelSession.cs">
       <Link>Channels\IChannelSession.cs</Link>
     </Compile>
@@ -262,6 +268,9 @@
     <Compile Include="..\Renci.SshNet\IConnectionInfo.cs">
       <Link>IConnectionInfo.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\IForwardedPort.cs">
+      <Link>IForwardedPort.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ISession.cs">
       <Link>ISession.cs</Link>
     </Compile>
@@ -870,7 +879,7 @@
       <FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}">
         <SilverlightProjectProperties />
       </FlavorProperties>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
+      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

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

@@ -15,7 +15,6 @@ namespace Renci.SshNet
         private const byte Null = 0x00;
         private const byte CarriageReturn = 0x0d;
         private const byte LineFeed = 0x0a;
-        private static readonly TimeSpan Infinite = new TimeSpan(0, 0, 0, 0, -1);
 
         private readonly AutoResetEvent _connectEvent = new AutoResetEvent(false);
         private readonly AutoResetEvent _sendEvent = new AutoResetEvent(false);

+ 10 - 1
Renci.SshClient/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj

@@ -139,6 +139,12 @@
     <Compile Include="..\Renci.SshNet\Channels\IChannel.cs">
       <Link>Channels\IChannel.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelDirectTcpip.cs">
+      <Link>Channels\IChannelDirectTcpip.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelForwardedTcpip.cs">
+      <Link>Channels\IChannelForwardedTcpip.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Channels\IChannelSession.cs">
       <Link>Channels\IChannelSession.cs</Link>
     </Compile>
@@ -307,6 +313,9 @@
     <Compile Include="..\Renci.SshNet\IConnectionInfo.cs">
       <Link>IConnectionInfo.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\IForwardedPort.cs">
+      <Link>IForwardedPort.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ISession.cs">
       <Link>ISession.cs</Link>
     </Compile>
@@ -902,7 +911,7 @@
       <FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}">
         <SilverlightProjectProperties />
       </FlavorProperties>
-      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
+      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 90 - 5
Renci.SshClient/Renci.SshNet.Tests/Classes/Channels/ChannelDirectTcpipTest.cs

@@ -1,13 +1,98 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+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.Messages;
+using Renci.SshNet.Messages.Connection;
 using Renci.SshNet.Tests.Common;
 
-namespace Renci.SshNet.Tests.Channels
+namespace Renci.SshNet.Tests.Classes.Channels
 {
-    /// <summary>
-    /// Implements "direct-tcpip" SSH channel.
-    /// </summary>
     [TestClass]
     public partial class ChannelDirectTcpipTestTest : TestBase
     {
+        private Mock<ISession> _sessionMock;
+        private Mock<IForwardedPort> _forwardedPortMock;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private string _remoteHost;
+        private uint _port;
+        private Socket _socket;
+        private uint _localChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private uint _remoteChannelNumber;
+
+        protected override void OnInit()
+        {
+            base.OnInit();
+
+            var random = new Random();
+
+            _localWindowSize = (uint) random.Next(0, int.MaxValue);
+            _localPacketSize = (uint) random.Next(0, int.MaxValue);
+            _remoteHost = random.Next().ToString(CultureInfo.InvariantCulture);
+            _port = (uint) random.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort);
+            _localChannelNumber = (uint) random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint) random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(100, 200);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+            _forwardedPortMock = new Mock<IForwardedPort>(MockBehavior.Strict);
+        }
+
+        [TestMethod]
+        public void SocketShouldBeClosedAndBindShouldEndWhenForwardedPortSignalsClosingEvent()
+        {
+            _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 closeForwardedPortThread =
+                            new Thread(() =>
+                                {
+                                    // sleep for a short period to allow channel to actually start receiving from socket
+                                    Thread.Sleep(1000);
+                                    // raise Closing event on forwarded port
+                                    _forwardedPortMock.Raise(p => p.Closing += null, EventArgs.Empty);
+                                });
+                        closeForwardedPortThread.Start();
+
+                        channel.Bind();
+
+                        closeForwardedPortThread.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 forwarded port
+                var buffer = new byte[16];
+                var bytesReceived = client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
+                Assert.AreEqual(0, bytesReceived);
+            }
+        }
     }
 }

+ 9 - 0
Renci.SshClient/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj

@@ -82,6 +82,12 @@
     <Compile Include="..\Renci.SshNet\Channels\IChannel.cs">
       <Link>Channels\IChannel.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelDirectTcpip.cs">
+      <Link>Channels\IChannelDirectTcpip.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelForwardedTcpip.cs">
+      <Link>Channels\IChannelForwardedTcpip.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Channels\IChannelSession.cs">
       <Link>Channels\IChannelSession.cs</Link>
     </Compile>
@@ -250,6 +256,9 @@
     <Compile Include="..\Renci.SshNet\IConnectionInfo.cs">
       <Link>IConnectionInfo.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\IForwardedPort.cs">
+      <Link>IForwardedPort.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ISession.cs">
       <Link>ISession.cs</Link>
     </Compile>

+ 26 - 0
Renci.SshClient/Renci.SshNet.WindowsPhone8/ForwardedPortLocal.SilverlightShared.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Threading;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Provides functionality for local port forwarding
+    /// </summary>
+    public partial class ForwardedPortLocal
+    {
+        partial void ExecuteThread(Action action)
+        {
+            ThreadPool.QueueUserWorkItem((o) => action());
+        }
+
+        partial void InternalStart()
+        {
+            throw new NotImplementedException();
+        }
+
+        partial void InternalStop()
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 11 - 1
Renci.SshClient/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj

@@ -132,6 +132,12 @@
     <Compile Include="..\Renci.SshNet\Channels\IChannel.cs">
       <Link>Channels\IChannel.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelDirectTcpip.cs">
+      <Link>Channels\IChannelDirectTcpip.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Channels\IChannelForwardedTcpip.cs">
+      <Link>Channels\IChannelForwardedTcpip.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Channels\IChannelSession.cs">
       <Link>Channels\IChannelSession.cs</Link>
     </Compile>
@@ -300,6 +306,9 @@
     <Compile Include="..\Renci.SshNet\IConnectionInfo.cs">
       <Link>IConnectionInfo.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\IForwardedPort.cs">
+      <Link>IForwardedPort.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\ISession.cs">
       <Link>ISession.cs</Link>
     </Compile>
@@ -900,6 +909,7 @@
     <Compile Include="Common\Extensions.WP.cs">
       <SubType>Code</SubType>
     </Compile>
+    <Compile Include="ForwardedPortLocal.SilverlightShared.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="..\Renci.SshNet\Properties\CommonAssemblyInfo.cs">
       <Link>Properties\CommonAssemblyInfo.cs</Link>
@@ -910,7 +920,7 @@
   <Import Project="$(MSBuildExtensionsPath)\Microsoft\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion)\Microsoft.$(TargetFrameworkIdentifier).CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

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

@@ -10,8 +10,6 @@ namespace Renci.SshNet
     /// </summary>
     public abstract class BaseClient : IDisposable
     {
-        private static readonly TimeSpan Infinite = new TimeSpan(0, 0, 0, 0, -1);
-
         /// <summary>
         /// Holds value indicating whether the connection info is owned by this client.
         /// </summary>
@@ -83,7 +81,7 @@ namespace Renci.SshNet
                 if (value == _keepAliveInterval)
                     return;
 
-                if (value == Infinite)
+                if (value == Session.Infinite)
                 {
                     // stop the timer when the value is -1 milliseconds
                     StopKeepAliveTimer();
@@ -135,7 +133,7 @@ namespace Renci.SshNet
 
             ConnectionInfo = connectionInfo;
             _ownsConnectionInfo = ownsConnectionInfo;
-            _keepAliveInterval = Infinite;
+            _keepAliveInterval = Session.Infinite;
         }
 
         /// <summary>
@@ -355,7 +353,7 @@ namespace Renci.SshNet
         /// </remarks>
         private void StartKeepAliveTimer()
         {
-            if (_keepAliveInterval == Infinite)
+            if (_keepAliveInterval == Session.Infinite)
                 return;
 
             if (_keepAliveTimer == null)

+ 4 - 4
Renci.SshClient/Renci.SshNet/Channels/Channel.cs

@@ -22,7 +22,7 @@ namespace Renci.SshNet.Channels
         private uint? _remoteWindowSize;
         private uint? _remoteChannelNumber;
         private uint? _remotePacketSize;
-        private Session _session;
+        private ISession _session;
 
         /// <summary>
         /// Gets the session.
@@ -30,7 +30,7 @@ namespace Renci.SshNet.Channels
         /// <value>
         ///  Thhe session.
         /// </value>
-        protected Session Session
+        protected ISession Session
         {
             get { return _session; }
         }
@@ -191,7 +191,7 @@ namespace Renci.SshNet.Channels
         /// Gets the connection info.
         /// </summary>
         /// <value>The connection info.</value>
-        protected ConnectionInfo ConnectionInfo
+        protected IConnectionInfo ConnectionInfo
         {
             get { return this._session.ConnectionInfo; }
         }
@@ -211,7 +211,7 @@ namespace Renci.SshNet.Channels
         /// <param name="session">The session.</param>
         /// <param name="localWindowSize">Size of the window.</param>
         /// <param name="localPacketSize">Size of the packet.</param>
-        internal virtual void Initialize(Session session, uint localWindowSize, uint localPacketSize)
+        internal virtual void Initialize(ISession session, uint localWindowSize, uint localPacketSize)
         {
             _session = session;
             _initialWindowSize = localWindowSize;

+ 1 - 8
Renci.SshClient/Renci.SshNet/Channels/ChannelDirectTcpip.NET40.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Net.Sockets;
-using System.Threading;
+using System.Net.Sockets;
 
 namespace Renci.SshNet.Channels
 {
@@ -9,11 +7,6 @@ namespace Renci.SshNet.Channels
     /// </summary>
     internal partial class ChannelDirectTcpip 
     {
-        partial void ExecuteThread(Action action)
-        {
-            ThreadPool.QueueUserWorkItem(o => action());
-        }
-
         partial void InternalSocketReceive(byte[] buffer, ref int read)
         {
             read = this._socket.Receive(buffer);

+ 109 - 67
Renci.SshClient/Renci.SshNet/Channels/ChannelDirectTcpip.cs

@@ -11,11 +11,13 @@ namespace Renci.SshNet.Channels
     /// <summary>
     /// Implements "direct-tcpip" SSH channel.
     /// </summary>
-    internal partial class ChannelDirectTcpip : ClientChannel
+    internal partial class ChannelDirectTcpip : ClientChannel, IChannelDirectTcpip
     {
         private EventWaitHandle _channelEof = new AutoResetEvent(false);
         private EventWaitHandle _channelOpen = new AutoResetEvent(false);
         private EventWaitHandle _channelData = new AutoResetEvent(false);
+        private EventWaitHandle _channelInterrupted = new ManualResetEvent(false);
+        private IForwardedPort _forwardedPort;
         private Socket _socket;
 
         /// <summary>
@@ -29,24 +31,19 @@ namespace Renci.SshNet.Channels
             get { return ChannelTypes.DirectTcpip; }
         }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ChannelDirectTcpip"/> class.
-        /// </summary>
-        public ChannelDirectTcpip()
+        public void Open(string remoteHost, uint port, IForwardedPort forwardedPort, Socket socket)
         {
-        }
+            if (IsOpen)
+                throw new SshException("Channel is already open.");
+            if (!this.IsConnected)
+                throw new SshException("Session is not connected.");
 
-        public void Open(string remoteHost, uint port, Socket socket)
-        {
-            this._socket = socket;
+            _socket = socket;
+            _forwardedPort = forwardedPort;
+            _forwardedPort.Closing += ForwardedPort_Closing;
 
             var ep = socket.RemoteEndPoint as IPEndPoint;
 
-            if (!this.IsConnected)
-            {
-                throw new SshException("Session is not connected.");
-            }
-
             //  Open channel
             this.SendMessage(new ChannelOpenMessage(this.LocalChannelNumber, this.LocalWindowSize, this.LocalPacketSize,
                                                         new DirectTcpipChannelInfo(remoteHost, port, ep.Address.ToString(), (uint)ep.Port)));
@@ -55,6 +52,13 @@ namespace Renci.SshNet.Channels
             this.WaitOnHandle(this._channelOpen);
         }
 
+        private void ForwardedPort_Closing(object sender, EventArgs eventArgs)
+        {
+            // close the socket, hereby interrupting the blocking receive in Bind()
+            if (_socket != null)
+                CloseSocket();
+        }
+
         /// <summary>
         /// Binds channel to remote host.
         /// </summary>
@@ -64,76 +68,79 @@ namespace Renci.SshNet.Channels
             if (!this.IsOpen)
                 return;
 
-            //  Start reading data from the port and send to channel
-            Exception exception = null;
+            var buffer = new byte[this.RemotePacketSize];
 
-            try
+            while (this._socket != null && _socket.Connected)
             {
-                var buffer = new byte[this.RemotePacketSize];
-
-                while (this._socket != null && this._socket.CanRead())
+                try
                 {
-                    try
+                    var read = 0;
+                    this.InternalSocketReceive(buffer, ref read);
+                    if (read > 0)
                     {
-                        var read = 0;
-                        this.InternalSocketReceive(buffer, ref read);
-                        if (read > 0)
-                        {
-                            this.SendMessage(new ChannelDataMessage(this.RemoteChannelNumber, buffer.Take(read).ToArray()));
-                        }
-                        else
-                        {
-                            break;
-                        }
+                        this.SendMessage(new ChannelDataMessage(this.RemoteChannelNumber, buffer.Take(read).ToArray()));
                     }
-                    catch (SocketException exp)
+                    else
                     {
-                        if (exp.SocketErrorCode == SocketError.WouldBlock ||
-                            exp.SocketErrorCode == SocketError.IOPending ||
-                            exp.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
-                        {
+                        // client quit sending
+                        break;
+                    }
+                }
+                catch (SocketException exp)
+                {
+                    switch (exp.SocketErrorCode)
+                    {
+                        case SocketError.WouldBlock:
+                        case SocketError.IOPending:
+                        case SocketError.NoBufferSpaceAvailable:
                             // socket buffer is probably empty, wait and try again
                             Thread.Sleep(30);
-                        }
-                        else if (exp.SocketErrorCode == SocketError.ConnectionAborted || exp.SocketErrorCode == SocketError.ConnectionReset)
-                        {
                             break;
-                        }
-                        else
-                            throw;  // throw any other error
+                        case SocketError.ConnectionAborted:
+                        case SocketError.ConnectionReset:
+                            // connection was closed after receiving SSH_MSG_CHANNEL_CLOSE message
+                            // in which case the _channelEof waithandle is also set
+                            break;
+                        case SocketError.Interrupted:
+                            // connection was interrupted as part of closing the forwarded port
+                            _channelInterrupted.Set();
+                            break;
+                        default:
+                            throw; // throw any other error
                     }
                 }
             }
-            catch (Exception exp)
-            {
-                exception = exp;
-            }
 
-            //  Channel was open and we MUST receive EOF notification,
-            //  data transfer can take longer than connection specified timeout
-            //  If listener thread is finished then socket was closed
-            WaitHandle.WaitAny(new WaitHandle[] {_channelEof});
+            WaitHandle.WaitAny(new WaitHandle[] { _channelEof, _channelInterrupted });
+        }
 
-            //  Close socket if still open
-            if (this._socket != null)
-            {
-                this._socket.Dispose();
-                this._socket = null;
-            }
+        /// <summary>
+        /// Closes the socket, hereby interrupting the blocking receive in <see cref="Bind()"/>.
+        /// </summary>
+        private void CloseSocket()
+        {
+            if (!_socket.Connected)
+                return;
 
-            if (exception != null)
-                throw exception;
+            _socket.Shutdown(SocketShutdown.Both);
+            _socket.Close();
         }
 
+        /// <summary>
+        /// Closes the channel.
+        /// </summary>
         public override void Close()
         {
-            //  Close socket if still open
-            if (this._socket != null)
+            if (_forwardedPort != null)
             {
-                this._socket.Dispose();
-                this._socket = null;
+                _forwardedPort.Closing -= ForwardedPort_Closing;
+                _forwardedPort = null;
             }
 
+            // close the socket, hereby interrupting the blocking receive in Bind()
+            if (this._socket != null)
+                CloseSocket();
+
             //  Send EOF message first when channel need to be closed
             this.SendMessage(new ChannelEofMessage(this.RemoteChannelNumber));
 
@@ -174,8 +181,14 @@ namespace Renci.SshNet.Channels
         /// <summary>
         /// Called when channel has no more data to receive.
         /// </summary>
-        protected override void OnEof() {
-	        base.OnEof();
+        protected override void OnEof()
+        {
+            base.OnEof();
+
+            // 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);
 
             var channelEof = this._channelEof;
             if (channelEof != null)
@@ -191,34 +204,57 @@ namespace Renci.SshNet.Channels
                 channelEof.Set();
         }
 
+        /// <summary>
+        /// Called whenever an unhandled <see cref="Exception"/> occurs in <see cref="Session"/> causing
+        /// the message loop to be interrupted.
+        /// </summary>
         protected override void OnErrorOccured(Exception exp)
         {
             base.OnErrorOccured(exp);
 
-            //  If error occured, no more data can be received
+            // close the socket, hereby interrupting the blocking receive in Bind()
+            if (_socket != null)
+                CloseSocket();
+
+            //  if error occured, no more data can be received
             var channelEof = this._channelEof;
             if (channelEof != null)
                 channelEof.Set();
         }
 
+        /// <summary>
+        /// Called when the server wants to terminate the connection immmediately.
+        /// </summary>
+        /// <remarks>
+        /// The sender MUST NOT send or receive any data after this message, and
+        /// the recipient MUST NOT accept any data after receiving this message.
+        /// </remarks>
         protected override void OnDisconnected()
         {
             base.OnDisconnected();
 
+            // close the socket, hereby interrupting the blocking receive in Bind()
+            if (_socket != null)
+                CloseSocket();
+
             //  If disconnected, no more data can be received
             var channelEof = this._channelEof;
             if (channelEof != null)
                 channelEof.Set();
         }
 
-        partial void ExecuteThread(Action action);
-
         partial void InternalSocketReceive(byte[] buffer, ref int read);
 
         partial void InternalSocketSend(byte[] data);
 
         protected override void Dispose(bool disposing)
         {
+            if (_forwardedPort != null)
+            {
+                _forwardedPort.Closing -= ForwardedPort_Closing;
+                _forwardedPort = null;
+            }
+
             if (this._socket != null)
             {
                 this._socket.Dispose();
@@ -243,6 +279,12 @@ namespace Renci.SshNet.Channels
                 this._channelData = null;
             }
 
+            if (_channelInterrupted != null)
+            {
+                _channelInterrupted.Dispose();
+                _channelInterrupted = null;
+            }
+
             base.Dispose(disposing);
         }
     }

+ 1 - 8
Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.cs

@@ -11,7 +11,7 @@ namespace Renci.SshNet.Channels
     /// <summary>
     /// Implements "forwarded-tcpip" SSH channel.
     /// </summary>
-    internal partial class ChannelForwardedTcpip : ServerChannel
+    internal partial class ChannelForwardedTcpip : ServerChannel, IChannelForwardedTcpip
     {
         private Socket _socket;
 
@@ -26,13 +26,6 @@ namespace Renci.SshNet.Channels
             get { return ChannelTypes.ForwardedTcpip; }
         }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ChannelForwardedTcpip"/> class.
-        /// </summary>
-        public ChannelForwardedTcpip()
-        {
-        }
-
         /// <summary>
         /// Binds channel to specified connected host.
         /// </summary>

+ 1 - 1
Renci.SshClient/Renci.SshNet/Channels/ClientChannel.cs

@@ -22,7 +22,7 @@ namespace Renci.SshNet.Channels
         /// <param name="session">The session.</param>
         /// <param name="localWindowSize">Size of the window.</param>
         /// <param name="localPacketSize">Size of the packet.</param>
-        internal override void Initialize(Session session, uint localWindowSize, uint localPacketSize)
+        internal override void Initialize(ISession session, uint localWindowSize, uint localPacketSize)
         {
             base.Initialize(session, localWindowSize, localPacketSize);
             Session.ChannelOpenConfirmationReceived += OnChannelOpenConfirmation;

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

@@ -0,0 +1,38 @@
+using System;
+using System.Net.Sockets;
+
+namespace Renci.SshNet.Channels
+{
+    /// <summary>
+    /// A "direct-tcpip" SSH channel.
+    /// </summary>
+    internal interface IChannelDirectTcpip : IDisposable
+    {
+        /// <summary>
+        /// Gets a value indicating whether this channel is open.
+        /// </summary>
+        /// <value>
+        /// <c>true</c> if this channel is open; otherwise, <c>false</c>.
+        /// </value>
+        bool IsOpen { get; }
+
+        /// <summary>
+        /// Opens a channel for a locally forwarded TCP/IP port.
+        /// </summary>
+        /// <param name="remoteHost">The name of the remote host to forward to.</param>
+        /// <param name="port">The port of the remote hosts to forward to.</param>
+        /// <param name="forwardedPort">The forwarded port for which the channel is opened.</param>
+        /// <param name="socket">The socket to receive requests from, and send responses from the remote host to.</param>
+        void Open(string remoteHost, uint port, IForwardedPort forwardedPort, Socket socket);
+
+        /// <summary>
+        /// Binds the channel to the remote host.
+        /// </summary>
+        void Bind();
+
+        /// <summary>
+        /// Closes the channel.
+        /// </summary>
+        void Close();
+    }
+}

+ 17 - 0
Renci.SshClient/Renci.SshNet/Channels/IChannelForwardedTcpip.cs

@@ -0,0 +1,17 @@
+using System.Net;
+
+namespace Renci.SshNet.Channels
+{
+    /// <summary>
+    /// A "forwarded-tcpip" SSH channel.
+    /// </summary>
+    internal interface IChannelForwardedTcpip
+    {
+        /// <summary>
+        /// Binds the channel to the specified host.
+        /// </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);
+    }
+}

+ 0 - 1
Renci.SshClient/Renci.SshNet/ConnectionInfo.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Linq;
 using System.Text;
 using Renci.SshNet.Security;

+ 27 - 2
Renci.SshClient/Renci.SshNet/ForwardedPort.cs

@@ -6,15 +6,26 @@ namespace Renci.SshNet
     /// <summary>
     /// Base class for port forwarding functionality.
     /// </summary>
-    public abstract class ForwardedPort
+    public abstract class ForwardedPort : IForwardedPort
     {
+        private EventHandler _closingEvent;
+
         /// <summary>
         /// Gets or sets the session.
         /// </summary>
         /// <value>
         /// The session.
         /// </value>
-        internal Session Session { get; set; }
+        internal ISession Session { get; set; }
+
+        /// <summary>
+        /// The <see cref="IForwardedPort.Closing"/> event occurs as the forward port is being stopped.
+        /// </summary>
+        event EventHandler IForwardedPort.Closing
+        {
+            add { _closingEvent += value; }
+            remove { _closingEvent -= value; }
+        }
 
         /// <summary>
         /// Gets or sets a value indicating whether port forwarding is started.
@@ -57,6 +68,8 @@ namespace Renci.SshNet
         /// </summary>
         public virtual void Stop()
         {
+            RaiseClosing();
+
             if (this.Session != null)
             {
                 this.Session.ErrorOccured -= Session_ErrorOccured;
@@ -90,6 +103,18 @@ namespace Renci.SshNet
             }
         }
 
+        /// <summary>
+        /// Raises the <see cref="IForwardedPort.Closing"/> event.
+        /// </summary>
+        private void RaiseClosing()
+        {
+            var handlers = _closingEvent;
+            if (handlers != null)
+            {
+                handlers(this, new EventArgs());
+            }
+        }
+
         /// <summary>
         /// Handles session ErrorOccured event.
         /// </summary>

+ 6 - 6
Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.NET.cs

@@ -50,7 +50,7 @@ namespace Renci.SshNet
                         {
                             try
                             {
-                                using (var channel = this.Session.CreateClientChannel<ChannelDirectTcpip>())
+                                using (var channel = this.Session.CreateChannelDirectTcpip())
                                 {
                                     var version = new byte[1];
 
@@ -83,7 +83,7 @@ namespace Renci.SshNet
                 }
                 catch (SocketException exp)
                 {
-                    if (!(exp.SocketErrorCode == SocketError.Interrupted))
+                    if (exp.SocketErrorCode != SocketError.Interrupted)
                     {
                         this.RaiseExceptionEvent(exp);
                     }
@@ -118,7 +118,7 @@ namespace Renci.SshNet
             this.IsStarted = false;
         }
 
-        private void HandleSocks4(Socket socket, ChannelDirectTcpip channel)
+        private void HandleSocks4(Socket socket, IChannelDirectTcpip channel)
         {
             using (var stream = new NetworkStream(socket))
             {
@@ -139,7 +139,7 @@ namespace Renci.SshNet
 
                 this.RaiseRequestReceived(host, port);
 
-                channel.Open(host, port, socket);
+                channel.Open(host, port, this, socket);
 
                 stream.WriteByte(0x00);
 
@@ -157,7 +157,7 @@ namespace Renci.SshNet
             }
         }
 
-        private void HandleSocks5(Socket socket, ChannelDirectTcpip channel)
+        private void HandleSocks5(Socket socket, IChannelDirectTcpip channel)
         {
             using (var stream = new NetworkStream(socket))
             {
@@ -231,7 +231,7 @@ namespace Renci.SshNet
 
                 this.RaiseRequestReceived(host, port);
 
-                channel.Open(host, port, socket);
+                channel.Open(host, port, this, socket);
 
                 stream.WriteByte(0x05);
 

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

@@ -6,7 +6,7 @@ namespace Renci.SshNet
     /// <summary>
     /// Provides functionality for dynamic port forwarding
     /// </summary>
-    public partial class ForwardedPortDynamic : ForwardedPort, IDisposable
+    public partial class ForwardedPortDynamic : ForwardedPort, IForwardedPort
     {
         private EventWaitHandle _listenerTaskCompleted;
 

+ 26 - 8
Renci.SshClient/Renci.SshNet/ForwardedPortLocal.NET.cs

@@ -1,8 +1,8 @@
 using System;
+using System.Diagnostics;
 using System.Net.Sockets;
 using System.Net;
 using System.Threading;
-using Renci.SshNet.Channels;
 
 namespace Renci.SshNet
 {
@@ -13,6 +13,7 @@ namespace Renci.SshNet
     {
         private TcpListener _listener;
         private readonly object _listenerLocker = new object();
+        private int _pendingRequests;
 
         partial void InternalStart()
         {
@@ -50,16 +51,17 @@ namespace Renci.SshNet
                         {
                             try
                             {
-                                IPEndPoint originatorEndPoint = socket.RemoteEndPoint as IPEndPoint;
+                                Interlocked.Increment(ref _pendingRequests);
 
-                                this.RaiseRequestReceived(originatorEndPoint.Address.ToString(), (uint)originatorEndPoint.Port);
+                                var originatorEndPoint = (IPEndPoint) socket.RemoteEndPoint;
 
-                                using (var channel = this.Session.CreateClientChannel<ChannelDirectTcpip>())
-                                {
-                                    channel.Open(this.Host, this.Port, socket);
+                                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();
                                 }
                             }
@@ -67,12 +69,16 @@ namespace Renci.SshNet
                             {
                                 this.RaiseExceptionEvent(exp);
                             }
+                            finally
+                            {
+                                Interlocked.Decrement(ref _pendingRequests);
+                            }
                         });
                     }
                 }
                 catch (SocketException exp)
                 {
-                    if (!(exp.SocketErrorCode == SocketError.Interrupted))
+                    if (exp.SocketErrorCode != SocketError.Interrupted)
                     {
                         this.RaiseExceptionEvent(exp);
                     }
@@ -105,6 +111,18 @@ namespace Renci.SshNet
             this._listenerTaskCompleted.Dispose();
             this._listenerTaskCompleted = null;
 
+            var stopWatch = new Stopwatch();
+            stopWatch.Start();
+
+            while (stopWatch.Elapsed < this.Session.ConnectionInfo.Timeout || this.Session.ConnectionInfo.Timeout == SshNet.Session.Infinite)
+            {
+                if (Interlocked.CompareExchange(ref _pendingRequests, 0, 0) == 0)
+                    break;
+                Thread.Sleep(50);
+            }
+
+            stopWatch.Stop();
+
             this.IsStarted = false;
         }
 

+ 4 - 6
Renci.SshClient/Renci.SshNet/ForwardedPortLocal.cs

@@ -13,22 +13,22 @@ namespace Renci.SshNet
         /// <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 the forwarded host.
         /// </summary>
-        public string Host { get; protected set; }
+        public string Host { get; private set; }
 
         /// <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="ForwardedPortLocal"/> class.
@@ -103,7 +103,6 @@ namespace Renci.SshNet
         public override void Stop()
         {
             base.Stop();
-
             this.InternalStop();
         }
 
@@ -123,7 +122,6 @@ namespace Renci.SshNet
         public void Dispose()
         {
             Dispose(true);
-
             GC.SuppressFinalize(this);
         }
 

+ 1 - 2
Renci.SshClient/Renci.SshNet/ForwardedPortRemote.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Threading;
-using Renci.SshNet.Channels;
 using Renci.SshNet.Messages.Connection;
 using Renci.SshNet.Common;
 using System.Globalization;
@@ -158,7 +157,7 @@ namespace Renci.SshNet
                         {
                             this.RaiseRequestReceived(info.OriginatorAddress, info.OriginatorPort);
 
-                            var channel = this.Session.CreateServerChannel<ChannelForwardedTcpip>(
+                            var channel = this.Session.CreateChannelForwardedTcpip(
                                 channelOpenMessage.LocalChannelNumber, channelOpenMessage.InitialWindowSize,
                                 channelOpenMessage.MaximumPacketSize);
                             channel.Bind(this.HostAddress, this.Port);

+ 30 - 1
Renci.SshClient/Renci.SshNet/IConnectionInfo.cs

@@ -1,6 +1,8 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Text;
 using Renci.SshNet.Messages.Authentication;
+using Renci.SshNet.Messages.Connection;
 
 namespace Renci.SshNet
 {
@@ -9,6 +11,14 @@ namespace Renci.SshNet
     /// </summary>
     internal interface IConnectionInfo
     {
+        /// <summary>
+        /// Gets the supported channel requests for this connection.
+        /// </summary>
+        /// <value>
+        /// The supported channel requests for this connection.
+        /// </value>
+        IDictionary<string, RequestInfo> ChannelRequests { get; }
+
         /// <summary>
         /// Gets the character encoding.
         /// </summary>
@@ -17,6 +27,25 @@ namespace Renci.SshNet
         /// </value>
         Encoding Encoding { get; }
 
+        /// <summary>
+        /// Gets the number of retry attempts when session channel creation failed.
+        /// </summary>
+        /// <value>
+        /// The number of retry attempts when session channel creation failed.
+        /// </value>
+        int RetryAttempts { get; }
+
+        /// <summary>
+        /// Gets or sets connection timeout.
+        /// </summary>
+        /// <value>
+        /// The connection timeout. The default value is 30 seconds.
+        /// </value>
+        /// <example>
+        ///   <code source="..\..\Renci.SshNet.Tests\Classes\SshClientTest.cs" region="Example SshClient Connect Timeout" language="C#" title="Specify connection timeout" />
+        /// </example>
+        TimeSpan Timeout { get; }
+
         /// <summary>
         /// Gets the supported authentication methods for this connection.
         /// </summary>

+ 15 - 0
Renci.SshClient/Renci.SshNet/IForwardedPort.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// Supports port forwarding functionality.
+    /// </summary>
+    internal interface IForwardedPort
+    {
+        /// <summary>
+        /// The <see cref="Closing"/> event occurs as the forward port is being stopped.
+        /// </summary>
+        event EventHandler Closing;
+    }
+}

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

@@ -1,8 +1,11 @@
 using System;
+using System.Net.Sockets;
+using System.Threading;
 using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages;
 using Renci.SshNet.Messages.Authentication;
+using Renci.SshNet.Messages.Connection;
 
 namespace Renci.SshNet
 {
@@ -17,6 +20,30 @@ namespace Renci.SshNet
         ///// <value>The connection info.</value>
         IConnectionInfo ConnectionInfo { get; }
 
+        /// <summary>
+        /// Gets a value indicating whether the session is connected.
+        /// </summary>
+        /// <value>
+        /// <c>true</c> if the session is connected; otherwise, <c>false</c>.
+        /// </value>
+        bool IsConnected { get; }
+
+        /// <summary>
+        /// Gets the next channel number.
+        /// </summary>
+        /// <value>
+        /// The next channel number.
+        /// </value>
+        uint NextChannelNumber { get; }
+
+        /// <summary>
+        /// Gets the session semaphore that controls session channels.
+        /// </summary>
+        /// <value>
+        /// The session semaphore.
+        /// </value>
+        SemaphoreLight SessionSemaphore { get; }
+
         /// <summary>
         /// Create a new SSH session channel.
         /// </summary>
@@ -25,6 +52,22 @@ namespace Renci.SshNet
         /// </returns>
         IChannelSession CreateChannelSession();
 
+        /// <summary>
+        /// Create a new channel for a locally forwarded TCP/IP port.
+        /// </summary>
+        /// <returns>
+        /// A new channel for a locally forwarded TCP/IP port.
+        /// </returns>
+        IChannelDirectTcpip CreateChannelDirectTcpip();
+
+        /// <summary>
+        /// Creates a "forwarded-tcpip" SSH channel.
+        /// </summary>
+        /// <returns>
+        /// A new "forwarded-tcpip" SSH channel.
+        /// </returns>
+        IChannelForwardedTcpip CreateChannelForwardedTcpip(uint remoteChannelNumber, uint remoteWindowSize, uint remoteChannelDataPacketSize);
+
        /// <summary>
         /// Registers SSH message with the session.
         /// </summary>
@@ -46,6 +89,75 @@ namespace Renci.SshNet
         /// <param name="messageName">The name of the message to unregister with the session.</param>
         void UnRegisterMessage(string messageName);
 
+        /// <summary>
+        /// Waits for the specified handle or the exception handle for the receive thread
+        /// to signal within the connection timeout.
+        /// </summary>
+        /// <param name="waitHandle">The wait handle.</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);
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelCloseMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelCloseMessage>> ChannelCloseReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelDataMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelDataMessage>> ChannelDataReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelEofMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelEofMessage>> ChannelEofReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelExtendedDataMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelExtendedDataMessage>> ChannelExtendedDataReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelFailureMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelFailureMessage>> ChannelFailureReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelOpenConfirmationMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelOpenConfirmationMessage>> ChannelOpenConfirmationReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelOpenFailureMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelOpenFailureMessage>> ChannelOpenFailureReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelOpenMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelOpenMessage>> ChannelOpenReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelRequestMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelRequestMessage>> ChannelRequestReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelSuccessMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelSuccessMessage>> ChannelSuccessReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelWindowAdjustMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<ChannelWindowAdjustMessage>> ChannelWindowAdjustReceived;
+
         /// <summary>
         /// Occurs when session has been disconnected from the server.
         /// </summary>
@@ -56,6 +168,16 @@ namespace Renci.SshNet
         /// </summary>
         event EventHandler<ExceptionEventArgs> ErrorOccured;
 
+        /// <summary>
+        /// Occurs when <see cref="RequestSuccessMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<RequestSuccessMessage>> RequestSuccessReceived;
+
+        /// <summary>
+        /// Occurs when <see cref="RequestFailureMessage"/> message received
+        /// </summary>
+        event EventHandler<MessageEventArgs<RequestFailureMessage>> RequestFailureReceived;
+
         /// <summary>
         /// Occurs when <see cref="BannerMessage"/> message is received from the server.
         /// </summary>

+ 3 - 0
Renci.SshClient/Renci.SshNet/Renci.SshNet.csproj

@@ -58,6 +58,8 @@
     </Compile>
     <Compile Include="BaseClient.cs" />
     <Compile Include="Channels\IChannel.cs" />
+    <Compile Include="Channels\IChannelDirectTcpip.cs" />
+    <Compile Include="Channels\IChannelForwardedTcpip.cs" />
     <Compile Include="Channels\IChannelSession.cs" />
     <Compile Include="CommandAsyncResult.cs" />
     <Compile Include="Channels\Channel.cs" />
@@ -152,6 +154,7 @@
     </Compile>
     <Compile Include="IAuthenticationMethod.cs" />
     <Compile Include="IConnectionInfo.cs" />
+    <Compile Include="IForwardedPort.cs" />
     <Compile Include="ISession.cs" />
     <Compile Include="Messages\Transport\KeyExchangeEcdhInitMessage.cs" />
     <Compile Include="Messages\Transport\KeyExchangeEcdhReplyMessage.cs" />

+ 63 - 16
Renci.SshClient/Renci.SshNet/Session.cs

@@ -25,6 +25,11 @@ namespace Renci.SshNet
     /// </summary>
     public partial class Session : IDisposable, ISession
     {
+        /// <summary>
+        /// Specifies an infinite waiting period.
+        /// </summary>
+        internal static readonly TimeSpan Infinite = new TimeSpan(0, 0, 0, 0, -1);
+
         /// <summary>
         /// Specifies maximum packet size defined by the protocol.
         /// </summary>
@@ -151,7 +156,9 @@ namespace Renci.SshNet
         /// <summary>
         /// Gets the session semaphore that controls session channels.
         /// </summary>
-        /// <value>The session semaphore.</value>
+        /// <value>
+        /// The session semaphore.
+        /// </value>
         public SemaphoreLight SessionSemaphore
         {
             get
@@ -178,7 +185,7 @@ namespace Renci.SshNet
         /// Gets the next channel number.
         /// </summary>
         /// <value>The next channel number.</value>
-        internal uint NextChannelNumber
+        uint ISession.NextChannelNumber
         {
             get
             {
@@ -367,73 +374,73 @@ namespace Renci.SshNet
 
         /// <summary>
         /// Occurs when <see cref="GlobalRequestMessage"/> message received
-        /// </summary>        
+        /// </summary>
         internal event EventHandler<MessageEventArgs<GlobalRequestMessage>> GlobalRequestReceived;
 
         /// <summary>
         /// Occurs when <see cref="RequestSuccessMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<RequestSuccessMessage>> RequestSuccessReceived;
+        public event EventHandler<MessageEventArgs<RequestSuccessMessage>> RequestSuccessReceived;
 
         /// <summary>
         /// Occurs when <see cref="RequestFailureMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<RequestFailureMessage>> RequestFailureReceived;
+        public event EventHandler<MessageEventArgs<RequestFailureMessage>> RequestFailureReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelOpenMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelOpenMessage>> ChannelOpenReceived;
+        public event EventHandler<MessageEventArgs<ChannelOpenMessage>> ChannelOpenReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelOpenConfirmationMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelOpenConfirmationMessage>> ChannelOpenConfirmationReceived;
+        public event EventHandler<MessageEventArgs<ChannelOpenConfirmationMessage>> ChannelOpenConfirmationReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelOpenFailureMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelOpenFailureMessage>> ChannelOpenFailureReceived;
+        public event EventHandler<MessageEventArgs<ChannelOpenFailureMessage>> ChannelOpenFailureReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelWindowAdjustMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelWindowAdjustMessage>> ChannelWindowAdjustReceived;
+        public event EventHandler<MessageEventArgs<ChannelWindowAdjustMessage>> ChannelWindowAdjustReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelDataMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelDataMessage>> ChannelDataReceived;
+        public event EventHandler<MessageEventArgs<ChannelDataMessage>> ChannelDataReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelExtendedDataMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelExtendedDataMessage>> ChannelExtendedDataReceived;
+        public event EventHandler<MessageEventArgs<ChannelExtendedDataMessage>> ChannelExtendedDataReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelEofMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelEofMessage>> ChannelEofReceived;
+        public event EventHandler<MessageEventArgs<ChannelEofMessage>> ChannelEofReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelCloseMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelCloseMessage>> ChannelCloseReceived;
+        public event EventHandler<MessageEventArgs<ChannelCloseMessage>> ChannelCloseReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelRequestMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelRequestMessage>> ChannelRequestReceived;
+        public event EventHandler<MessageEventArgs<ChannelRequestMessage>> ChannelRequestReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelSuccessMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelSuccessMessage>> ChannelSuccessReceived;
+        public event EventHandler<MessageEventArgs<ChannelSuccessMessage>> ChannelSuccessReceived;
 
         /// <summary>
         /// Occurs when <see cref="ChannelFailureMessage"/> message received
         /// </summary>
-        internal event EventHandler<MessageEventArgs<ChannelFailureMessage>> ChannelFailureReceived;
+        public event EventHandler<MessageEventArgs<ChannelFailureMessage>> ChannelFailureReceived;
 
         /// <summary>
         /// Occurs when message received and is not handled by any of the event handlers
@@ -690,6 +697,23 @@ namespace Renci.SshNet
             this.SendMessage(new IgnoreMessage());
         }
 
+        /// <summary>
+        /// Waits for the specified handle or the exception handle for the receive thread
+        /// to signal within the connection timeout.
+        /// </summary>
+        /// <param name="waitHandle">The wait handle.</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)
+        {
+            WaitOnHandle(waitHandle, ConnectionInfo.Timeout);
+        }
+
         /// <summary>
         /// Waits for the specified handle or the exception handle for the receive thread
         /// to signal within the connection timeout.
@@ -2203,6 +2227,29 @@ namespace Renci.SshNet
             return CreateClientChannel<ChannelSession>();
         }
 
+        /// <summary>
+        /// Create a new channel for a locally forwarded TCP/IP port.
+        /// </summary>
+        /// <returns>
+        /// A new channel for a locally forwarded TCP/IP port.
+        /// </returns>
+        IChannelDirectTcpip ISession.CreateChannelDirectTcpip()
+        {
+            return CreateClientChannel<ChannelDirectTcpip>();
+        }
+
+        /// <summary>
+        /// Creates a "forwarded-tcpip" SSH channel.
+        /// </summary>
+        /// <returns>
+        /// A new "forwarded-tcpip" SSH channel.
+        /// </returns>
+        IChannelForwardedTcpip ISession.CreateChannelForwardedTcpip(uint remoteChannelNumber, uint remoteWindowSize,
+            uint remoteChannelDataPacketSize)
+        {
+            return CreateServerChannel<ChannelForwardedTcpip>(remoteChannelNumber, remoteWindowSize, remoteChannelDataPacketSize);
+        }
+
         /// <summary>
         /// Sends a message to the server.
         /// </summary>