Refactor PortForwarding

Fix ChannelDirectTcpip clean up after socket is close by remote host
Add ForwardedPortDynamic to support dynamic forwarding, SOCKS4 and SOCKS5 are supported.
This commit is contained in:
olegkap_cp
2012-01-06 18:57:48 +00:00
parent 81b9e21b8f
commit cfa1a7028b
18 changed files with 886 additions and 421 deletions
@@ -0,0 +1,13 @@
using System;
using System.Threading;
namespace Renci.SshNet
{
public partial class ForwardedPortDynamic
{
partial void ExecuteThread(Action action)
{
ThreadPool.QueueUserWorkItem((o) => { action(); });
}
}
}
@@ -11,84 +11,9 @@ namespace Renci.SshNet
/// </summary>
public partial class ForwardedPortLocal
{
private TcpListener _listener;
partial void ExecuteThread(Action action)
{
ThreadPool.QueueUserWorkItem((o) => { action(); });
}
partial void InternalStart()
{
// If port already started don't start it again
if (this.IsStarted)
return;
var ep = new IPEndPoint(Dns.GetHostAddresses(this.BoundHost)[0], (int)this.BoundPort);
this._listener = new TcpListener(ep);
this._listener.Start();
this._listenerTaskCompleted = new ManualResetEvent(false);
this.ExecuteThread(() =>
{
try
{
while (true)
{
var socket = this._listener.AcceptSocket();
this.ExecuteThread(() =>
{
try
{
IPEndPoint originatorEndPoint = socket.RemoteEndPoint as IPEndPoint;
this.RaiseRequestReceived(originatorEndPoint.Address.ToString(), (uint)originatorEndPoint.Port);
var channel = this.Session.CreateChannel<ChannelDirectTcpip>();
channel.Bind(this.Host, this.Port, socket);
}
catch (Exception exp)
{
this.RaiseExceptionEvent(exp);
}
});
}
}
catch (SocketException exp)
{
if (!(exp.SocketErrorCode == SocketError.Interrupted))
{
this.RaiseExceptionEvent(exp);
}
}
catch (Exception exp)
{
this.RaiseExceptionEvent(exp);
}
finally
{
this._listenerTaskCompleted.Set();
}
});
this.IsStarted = true;
}
partial void InternalStop()
{
// If port not started you cant stop it
if (!this.IsStarted)
return;
this._listener.Stop();
this._listenerTaskCompleted.WaitOne(this.Session.ConnectionInfo.Timeout);
this._listenerTaskCompleted.Dispose();
this._listenerTaskCompleted = null;
this.IsStarted = false;
}
}
}
@@ -217,9 +217,18 @@
<Compile Include="..\Renci.SshNet\ForwardedPort.cs">
<Link>ForwardedPort.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\ForwardedPortDynamic.cs">
<Link>ForwardedPortDynamic.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\ForwardedPortDynamic.NET.cs">
<Link>ForwardedPortDynamic.NET.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\ForwardedPortLocal.cs">
<Link>ForwardedPortLocal.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\ForwardedPortLocal.NET.cs">
<Link>ForwardedPortLocal.NET.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\ForwardedPortRemote.cs">
<Link>ForwardedPortRemote.cs</Link>
</Compile>
@@ -729,6 +738,7 @@
</Compile>
<Compile Include="Channels\ChannelDirectTcpip.NET35.cs" />
<Compile Include="Common\Extensions.NET35.cs" />
<Compile Include="ForwardedPortDynamic.NET35.cs" />
<Compile Include="ForwardedPortLocal.NET35.cs" />
<Compile Include="ForwardedPortRemote.NET35.cs" />
<Compile Include="KeyboardInteractiveConnectionInfo.NET35.cs" />
@@ -742,7 +752,7 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
<UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
@@ -201,6 +201,9 @@
<Compile Include="..\Renci.SshNet\ForwardedPort.cs">
<Link>ForwardedPort.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\ForwardedPortDynamic.cs">
<Link>ForwardedPortDynamic.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\ForwardedPortLocal.cs">
<Link>ForwardedPortLocal.cs</Link>
</Compile>
@@ -716,7 +719,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.
@@ -12,112 +12,115 @@ namespace Renci.SshNet.Tests.SshClientTests
/// <summary>
/// Summary description for UnitTest1
/// </summary>
public partial class TestPortForwarding
{
[TestMethod]
[ExpectedException(typeof(SshConnectionException))]
public void Test_PortForwarding_Local_Without_Connecting()
{
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
var port1 = client.AddForwardedPort<ForwardedPortLocal>("localhost", 8084, "www.renci.org", 80);
port1.Exception += delegate(object sender, ExceptionEventArgs e)
{
Assert.Fail(e.Exception.ToString());
};
port1.Start();
public partial class TestPortForwarding
{
[TestMethod]
[ExpectedException(typeof(SshConnectionException))]
public void Test_PortForwarding_Local_Without_Connecting()
{
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
var port1 = new ForwardedPortLocal("localhost", 8084, "www.renci.org", 80);
client.AddForwardedPort(port1);
port1.Exception += delegate(object sender, ExceptionEventArgs e)
{
Assert.Fail(e.Exception.ToString());
};
port1.Start();
System.Threading.Tasks.Parallel.For(0, 100,
//new ParallelOptions
//{
// MaxDegreeOfParallelism = 20,
//},
(counter) =>
{
var start = DateTime.Now;
var req = HttpWebRequest.Create("http://localhost:8084");
using (var response = req.GetResponse())
{
System.Threading.Tasks.Parallel.For(0, 100,
//new ParallelOptions
//{
// MaxDegreeOfParallelism = 20,
//},
(counter) =>
{
var start = DateTime.Now;
var req = HttpWebRequest.Create("http://localhost:8084");
using (var response = req.GetResponse())
{
var data = ReadStream(response.GetResponseStream());
var end = DateTime.Now;
var data = ReadStream(response.GetResponseStream());
var end = DateTime.Now;
Debug.WriteLine(string.Format("Request# {2}: Lenght: {0} Time: {1}", data.Length, (end - start), counter));
}
}
);
}
}
Debug.WriteLine(string.Format("Request# {2}: Lenght: {0} Time: {1}", data.Length, (end - start), counter));
}
}
);
}
}
[TestMethod]
public void Test_PortForwarding_Remote()
{
// ******************************************************************
// ************* Tests are still in not finished ********************
// ******************************************************************
[TestMethod]
public void Test_PortForwarding_Remote()
{
// ******************************************************************
// ************* Tests are still in not finished ********************
// ******************************************************************
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = client.AddForwardedPort<ForwardedPortRemote>(8082, "www.renci.org", 80);
port1.Exception += delegate(object sender, ExceptionEventArgs e)
{
Assert.Fail(e.Exception.ToString());
};
port1.Start();
var boundport = port1.BoundPort;
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = new ForwardedPortRemote(8082, "www.renci.org", 80);
client.AddForwardedPort(port1);
port1.Exception += delegate(object sender, ExceptionEventArgs e)
{
Assert.Fail(e.Exception.ToString());
};
port1.Start();
var boundport = port1.BoundPort;
System.Threading.Tasks.Parallel.For(0, 5,
//new ParallelOptions
//{
// MaxDegreeOfParallelism = 1,
//},
(counter) =>
{
var cmd = client.CreateCommand(string.Format("wget -O- http://localhost:{0}", boundport));
var result = cmd.Execute();
var end = DateTime.Now;
Debug.WriteLine(string.Format("Length: {0}", result.Length));
}
);
Thread.Sleep(1000 * 100);
port1.Stop();
}
}
System.Threading.Tasks.Parallel.For(0, 5,
//new ParallelOptions
//{
// MaxDegreeOfParallelism = 1,
//},
(counter) =>
{
var cmd = client.CreateCommand(string.Format("wget -O- http://localhost:{0}", boundport));
var result = cmd.Execute();
var end = DateTime.Now;
Debug.WriteLine(string.Format("Length: {0}", result.Length));
}
);
Thread.Sleep(1000 * 100);
port1.Stop();
}
}
[TestMethod]
public void Test_PortForwarding_Local()
{
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = client.AddForwardedPort<ForwardedPortLocal>("localhost", 8084, "www.renci.org", 80);
port1.Exception += delegate(object sender, ExceptionEventArgs e)
{
Assert.Fail(e.Exception.ToString());
};
port1.Start();
[TestMethod]
public void Test_PortForwarding_Local()
{
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = new ForwardedPortLocal("localhost", 8084, "www.renci.org", 80);
client.AddForwardedPort(port1);
port1.Exception += delegate(object sender, ExceptionEventArgs e)
{
Assert.Fail(e.Exception.ToString());
};
port1.Start();
System.Threading.Tasks.Parallel.For(0, 100,
//new ParallelOptions
//{
// MaxDegreeOfParallelism = 20,
//},
(counter) =>
{
var start = DateTime.Now;
var req = HttpWebRequest.Create("http://localhost:8084");
using (var response = req.GetResponse())
{
System.Threading.Tasks.Parallel.For(0, 100,
//new ParallelOptions
//{
// MaxDegreeOfParallelism = 20,
//},
(counter) =>
{
var start = DateTime.Now;
var req = HttpWebRequest.Create("http://localhost:8084");
using (var response = req.GetResponse())
{
var data = ReadStream(response.GetResponseStream());
var end = DateTime.Now;
var data = ReadStream(response.GetResponseStream());
var end = DateTime.Now;
Debug.WriteLine(string.Format("Request# {2}: Length: {0} Time: {1}", data.Length, (end - start), counter));
}
}
);
}
}
}
Debug.WriteLine(string.Format("Request# {2}: Length: {0} Time: {1}", data.Length, (end - start), counter));
}
}
);
}
}
}
}
@@ -15,80 +15,81 @@ namespace Renci.SshNet.Tests.SshClientTests
[TestClass]
public partial class TestPortForwarding
{
[TestMethod]
[WorkItem(713)]
[Owner("kenneth_aa")]
[Description("Test if calling Stop on ForwardedPortLocal instance causes wait.")]
public void Test_PortForwarding_Local_Stop_Hangs_On_Wait()
{
using (var client = new SshClient(Resources.HOST, Int32.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
[TestMethod]
[WorkItem(713)]
[Owner("kenneth_aa")]
[Description("Test if calling Stop on ForwardedPortLocal instance causes wait.")]
public void Test_PortForwarding_Local_Stop_Hangs_On_Wait()
{
using (var client = new SshClient(Resources.HOST, Int32.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = client.AddForwardedPort<ForwardedPortLocal>("localhost", 8084, "www.google.com",80);
var port1 = new ForwardedPortLocal("localhost", 8084, "www.google.com", 80);
client.AddForwardedPort(port1);
port1.Exception += delegate(object sender, ExceptionEventArgs e)
{
Assert.Fail(e.Exception.ToString());
};
{
Assert.Fail(e.Exception.ToString());
};
port1.Start();
port1.Start();
bool hasTestedTunnel = false;
System.Threading.ThreadPool.QueueUserWorkItem(delegate(object state)
{
try
{
var url = "http://www.google.com/";
Debug.WriteLine("Starting web request to \"" + url + "\"");
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
bool hasTestedTunnel = false;
System.Threading.ThreadPool.QueueUserWorkItem(delegate(object state)
{
try
{
var url = "http://www.google.com/";
Debug.WriteLine("Starting web request to \"" + url + "\"");
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Assert.IsNotNull(response);
Assert.IsNotNull(response);
Debug.WriteLine("Http Response status code: " + response.StatusCode.ToString());
Debug.WriteLine("Http Response status code: " + response.StatusCode.ToString());
response.Close();
response.Close();
hasTestedTunnel = true;
}
catch (Exception ex)
{
Assert.Fail(ex.ToString());
}
hasTestedTunnel = true;
}
catch (Exception ex)
{
Assert.Fail(ex.ToString());
}
});
});
// Wait for the web request to complete.
while(!hasTestedTunnel)
{
System.Threading.Thread.Sleep(1000);
}
// Wait for the web request to complete.
while(!hasTestedTunnel)
{
System.Threading.Thread.Sleep(1000);
}
try
{
// Try stop the port forwarding, wait 3 seconds and fail if it is still started.
System.Threading.ThreadPool.QueueUserWorkItem(delegate(object state)
{
Debug.WriteLine("Trying to stop port forward.");
port1.Stop();
Debug.WriteLine("Port forwarding stopped.");
try
{
// Try stop the port forwarding, wait 3 seconds and fail if it is still started.
System.Threading.ThreadPool.QueueUserWorkItem(delegate(object state)
{
Debug.WriteLine("Trying to stop port forward.");
port1.Stop();
Debug.WriteLine("Port forwarding stopped.");
});
});
System.Threading.Thread.Sleep(3000);
if (port1.IsStarted)
{
Assert.Fail("Port forwarding not stopped.");
}
}
catch (Exception ex)
{
Assert.Fail(ex.ToString());
}
client.Disconnect();
Debug.WriteLine("Success.");
}
}
System.Threading.Thread.Sleep(3000);
if (port1.IsStarted)
{
Assert.Fail("Port forwarding not stopped.");
}
}
catch (Exception ex)
{
Assert.Fail(ex.ToString());
}
client.Disconnect();
Debug.WriteLine("Success.");
}
}
@@ -101,8 +102,9 @@ namespace Renci.SshNet.Tests.SshClientTests
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = client.AddForwardedPort<ForwardedPortLocal>(null, 8080 , null, 80);
client.Disconnect();
var port1 = new ForwardedPortLocal(null, 8080, null, 80);
client.AddForwardedPort(port1);
client.Disconnect();
}
}
@@ -114,8 +116,9 @@ namespace Renci.SshNet.Tests.SshClientTests
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = client.AddForwardedPort<ForwardedPortRemote>(null, 8080, null, 80);
client.Disconnect();
var port1 = new ForwardedPortRemote(null, 8080, null, 80);
client.AddForwardedPort(port1);
client.Disconnect();
}
}
@@ -127,8 +130,9 @@ namespace Renci.SshNet.Tests.SshClientTests
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = client.AddForwardedPort<ForwardedPortRemote>(string.Empty, 8080, string.Empty, 80);
client.Disconnect();
var port1 = new ForwardedPortRemote(string.Empty, 8080, string.Empty, 80);
client.AddForwardedPort(port1);
client.Disconnect();
}
}
@@ -140,8 +144,9 @@ namespace Renci.SshNet.Tests.SshClientTests
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = client.AddForwardedPort<ForwardedPortLocal>(string.Empty, 8080, string.Empty, 80);
client.Disconnect();
var port1 = new ForwardedPortLocal(string.Empty, 8080, string.Empty, 80);
client.AddForwardedPort(port1);
client.Disconnect();
}
}
@@ -153,8 +158,9 @@ namespace Renci.SshNet.Tests.SshClientTests
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var port1 = client.AddForwardedPort<ForwardedPortRemote>("localhost", IPEndPoint.MaxPort+1, "www.renci.org", IPEndPoint.MaxPort+1);
client.Disconnect();
var port1 = new ForwardedPortRemote("localhost", IPEndPoint.MaxPort + 1, "www.renci.org", IPEndPoint.MaxPort + 1);
client.AddForwardedPort(port1);
client.Disconnect();
}
}
@@ -197,6 +197,9 @@
<Compile Include="..\Renci.SshNet\ForwardedPort.cs">
<Link>ForwardedPort.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\ForwardedPortDynamic.cs">
<Link>ForwardedPortDynamic.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\ForwardedPortLocal.cs">
<Link>ForwardedPortLocal.cs</Link>
</Compile>
@@ -720,7 +723,7 @@
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\$(TargetFrameworkVersion)\Microsoft.Silverlight.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.
@@ -41,18 +41,12 @@ namespace Renci.SshNet.Channels
}
/// <summary>
/// Binds channel to specified remote host.
/// </summary>
/// <param name="remoteHost">The remote host.</param>
/// <param name="port">The port.</param>
/// <param name="socket">The socket.</param>
public void Bind(string remoteHost, uint port, Socket socket)
public void Open(string remoteHost, uint port, Socket socket)
{
this._socket = socket;
IPEndPoint ep = socket.RemoteEndPoint as IPEndPoint;
if (!this.IsConnected)
{
@@ -65,10 +59,21 @@ namespace Renci.SshNet.Channels
// Wait for channel to open
this.WaitHandle(this._channelOpen);
}
/// <summary>
/// Binds channel to specified remote host.
/// </summary>
/// <param name="remoteHost">The remote host.</param>
/// <param name="port">The port.</param>
/// <param name="socket">The socket.</param>
public void Bind()
{
// Cannot bind if channel is not open
if (!this.IsOpen)
return;
// Start reading data from the port and send to channel
EventWaitHandle readerTaskError = new AutoResetEvent(false);
var readerTaskCompleted = new ManualResetEvent(false);
Exception exception = null;
@@ -103,7 +108,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.ConnectionReset)
{
break;
}
@@ -114,7 +119,6 @@ namespace Renci.SshNet.Channels
}
catch (Exception exp)
{
readerTaskError.Set();
exception = exp;
}
finally
@@ -125,14 +129,12 @@ namespace Renci.SshNet.Channels
// Channel was open and we MUST receive EOF notification,
// data transfer can take longer then connection specified timeout
System.Threading.WaitHandle.WaitAny(new WaitHandle[] { this._channelEof, readerTaskError });
// If listener thread is finished then socket was closed
System.Threading.WaitHandle.WaitAny(new WaitHandle[] { this._channelEof, readerTaskCompleted });
this._socket.Dispose();
this._socket = null;
// Wait for task to finish and will throw any errors if any
readerTaskCompleted.WaitOne();
if (exception != null)
throw exception;
}
@@ -169,6 +171,13 @@ namespace Renci.SshNet.Channels
this._channelOpen.Set();
}
protected override void OnOpenFailure(uint reasonCode, string description, string language)
{
base.OnOpenFailure(reasonCode, description, language);
this._channelOpen.Set();
}
/// <summary>
/// Called when channel has no more data to receive.
/// </summary>
@@ -179,6 +188,13 @@ namespace Renci.SshNet.Channels
this._channelEof.Set();
}
protected override void OnClose()
{
base.OnClose();
this._channelEof.Set();
}
partial void ExecuteThread(Action action);
partial void InternalSocketReceive(byte[] buffer, ref int read);
@@ -16,26 +16,6 @@ namespace Renci.SshNet
/// </value>
internal Session Session { get; set; }
/// <summary>
/// Gets the bound host.
/// </summary>
public string BoundHost { get; internal set; }
/// <summary>
/// Gets the bound port.
/// </summary>
public uint BoundPort { get; internal set; }
/// <summary>
/// Gets the forwarded host.
/// </summary>
public string Host { get; internal set; }
/// <summary>
/// Gets the forwarded port.
/// </summary>
public uint Port { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether port forwarding started.
/// </summary>
@@ -0,0 +1,277 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
namespace Renci.SshNet
{
public partial class ForwardedPortDynamic
{
private TcpListener _listener;
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))
{
ip = Dns.GetHostAddresses(this.BoundHost)[0];
}
var ep = new IPEndPoint(ip, (int)this.BoundPort);
this._listener = new TcpListener(ep);
this._listener.Start();
this._listenerTaskCompleted = new ManualResetEvent(false);
this.ExecuteThread(() =>
{
try
{
while (true)
{
var socket = this._listener.AcceptSocket();
this.ExecuteThread(() =>
{
try
{
using (var channel = this.Session.CreateChannel<ChannelDirectTcpip>())
{
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();
}
}
catch (Exception exp)
{
this.RaiseExceptionEvent(exp);
}
finally
{
socket.Close();
}
});
}
}
catch (SocketException exp)
{
if (!(exp.SocketErrorCode == SocketError.Interrupted))
{
this.RaiseExceptionEvent(exp);
}
}
catch (Exception exp)
{
this.RaiseExceptionEvent(exp);
}
finally
{
this._listenerTaskCompleted.Set();
}
});
this.IsStarted = true;
}
partial void InternalStop()
{
// If port not started you cant stop it
if (!this.IsStarted)
return;
this._listener.Stop();
this._listenerTaskCompleted.WaitOne(this.Session.ConnectionInfo.Timeout);
this._listenerTaskCompleted.Dispose();
this._listenerTaskCompleted = null;
this.IsStarted = false;
}
private void HandleSocks4(Socket socket, ChannelDirectTcpip channel)
{
using (var stream = new NetworkStream(socket))
{
var commandCode = stream.ReadByte();
// TODO: See what need to be done depends on the code
var portBuffer = new byte[2];
stream.Read(portBuffer, 0, portBuffer.Length);
var port = (uint)(portBuffer[0] * 256 + portBuffer[1]);
var ipBuffer = new byte[4];
stream.Read(ipBuffer, 0, ipBuffer.Length);
var ipAddress = new IPAddress(ipBuffer);
var username = ReadString(stream);
var host = ipAddress.ToString();
this.RaiseRequestReceived(host, port);
channel.Open(host, port, socket);
stream.WriteByte(0x00);
if (channel.IsOpen)
{
stream.WriteByte(0x5a);
}
else
{
stream.WriteByte(0x5b);
}
stream.Write(portBuffer, 0, portBuffer.Length);
stream.Write(ipBuffer, 0, ipBuffer.Length);
}
}
private void HandleSocks5(Socket socket, ChannelDirectTcpip channel)
{
using (var stream = new NetworkStream(socket))
{
var authenticationMethodsCount = stream.ReadByte();
var authenticationMethods = new byte[authenticationMethodsCount];
stream.Read(authenticationMethods, 0, authenticationMethods.Length);
stream.WriteByte(0x05);
if (authenticationMethods.Min() == 0)
{
stream.WriteByte(0x00);
}
else
{
stream.WriteByte(0xFF);
}
var version = stream.ReadByte();
if (version != 5)
throw new ProxyException("SOCKS5: Version 5 is expected.");
var commandCode = stream.ReadByte();
if (stream.ReadByte() != 0)
{
throw new ProxyException("SOCKS5: 0 is expected.");
}
var addressType = stream.ReadByte();
IPAddress ipAddress = null;
byte[] addressBuffer = null;
switch (addressType)
{
case 0x01:
{
addressBuffer = new byte[4];
stream.Read(addressBuffer, 0, 4);
ipAddress = new IPAddress(addressBuffer);
}
break;
case 0x03:
{
var length = stream.ReadByte();
addressBuffer = new byte[length];
stream.Read(addressBuffer, 0, addressBuffer.Length);
ipAddress = IPAddress.Parse(new Renci.SshNet.Common.ASCIIEncoding().GetString(addressBuffer));
}
break;
case 0x04:
{
addressBuffer = new byte[16];
stream.Read(addressBuffer, 0, 16);
ipAddress = new IPAddress(addressBuffer);
}
break;
default:
throw new ProxyException(string.Format("SOCKS5: Address type '{0}' is not supported.", addressType));
}
var portBuffer = new byte[2];
stream.Read(portBuffer, 0, portBuffer.Length);
var port = (uint)(portBuffer[0] * 256 + portBuffer[1]);
var host = ipAddress.ToString();
this.RaiseRequestReceived(host, port);
channel.Open(host, port, socket);
stream.WriteByte(0x05);
if (channel.IsOpen)
{
stream.WriteByte(0x00);
}
else
{
stream.WriteByte(0x01);
}
stream.WriteByte(0x00);
var buffer = ipAddress.GetAddressBytes();
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.");
}
stream.Write(buffer, 0, buffer.Length);
stream.Write(portBuffer, 0, portBuffer.Length);
}
}
private static string ReadString(NetworkStream stream)
{
StringBuilder text = new StringBuilder();
var aa = (char)stream.ReadByte();
while (aa != 0)
{
text.Append(aa);
aa = (char)stream.ReadByte();
}
return text.ToString();
}
}
}
@@ -0,0 +1,13 @@
using System;
using System.Threading.Tasks;
namespace Renci.SshNet
{
public partial class ForwardedPortDynamic
{
partial void ExecuteThread(Action action)
{
Task.Factory.StartNew(action);
}
}
}
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Renci.SshNet
{
public partial class ForwardedPortDynamic : ForwardedPort
{
private EventWaitHandle _listenerTaskCompleted;
/// <summary>
/// Gets the bound host.
/// </summary>
public string BoundHost { get; protected set; }
/// <summary>
/// Gets the bound port.
/// </summary>
public uint BoundPort { get; protected set; }
/// <summary>
/// Initializes a new instance of the <see cref="ForwardedPortDynamic"/> class.
/// </summary>
/// <param name="port">The port.</param>
public ForwardedPortDynamic(uint port)
: this(string.Empty, port)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ForwardedPortDynamic"/> class.
/// </summary>
/// <param name="host">The host.</param>
/// <param name="port">The port.</param>
public ForwardedPortDynamic(string host, uint port)
{
this.BoundHost = host;
this.BoundPort = port;
}
/// <summary>
/// Starts local port forwarding.
/// </summary>
public override void Start()
{
this.InternalStart();
}
/// <summary>
/// Stops local port forwarding.
/// </summary>
public override void Stop()
{
base.Stop();
this.InternalStop();
}
partial void InternalStart();
partial void InternalStop();
partial void ExecuteThread(Action action);
#region IDisposable Members
private bool _isDisposed = false;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged ResourceMessages.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <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)
{
// Check to see if Dispose has already been called.
if (!this._isDisposed)
{
// If disposing equals true, dispose all managed
// and unmanaged ResourceMessages.
if (disposing)
{
// Dispose managed ResourceMessages.
if (this._listenerTaskCompleted != null)
{
this._listenerTaskCompleted.Dispose();
this._listenerTaskCompleted = null;
}
}
// Note disposing has been done.
_isDisposed = true;
}
}
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="ForwardedPortLocal"/> is reclaimed by garbage collection.
/// </summary>
~ForwardedPortDynamic()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
#endregion
}
}
@@ -0,0 +1,91 @@
using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using Renci.SshNet.Channels;
namespace Renci.SshNet
{
/// <summary>
/// Provides functionality for local port forwarding
/// </summary>
public partial class ForwardedPortLocal
{
private TcpListener _listener;
partial void InternalStart()
{
// If port already started don't start it again
if (this.IsStarted)
return;
var ep = new IPEndPoint(Dns.GetHostAddresses(this.BoundHost)[0], (int)this.BoundPort);
this._listener = new TcpListener(ep);
this._listener.Start();
this._listenerTaskCompleted = new ManualResetEvent(false);
this.ExecuteThread(() =>
{
try
{
while (true)
{
var socket = this._listener.AcceptSocket();
this.ExecuteThread(() =>
{
try
{
IPEndPoint originatorEndPoint = socket.RemoteEndPoint as IPEndPoint;
this.RaiseRequestReceived(originatorEndPoint.Address.ToString(), (uint)originatorEndPoint.Port);
var channel = this.Session.CreateChannel<ChannelDirectTcpip>();
channel.Open(this.Host, this.Port, socket);
channel.Bind();
}
catch (Exception exp)
{
this.RaiseExceptionEvent(exp);
}
});
}
}
catch (SocketException exp)
{
if (!(exp.SocketErrorCode == SocketError.Interrupted))
{
this.RaiseExceptionEvent(exp);
}
}
catch (Exception exp)
{
this.RaiseExceptionEvent(exp);
}
finally
{
this._listenerTaskCompleted.Set();
}
});
this.IsStarted = true;
}
partial void InternalStop()
{
// If port not started you cant stop it
if (!this.IsStarted)
return;
this._listener.Stop();
this._listenerTaskCompleted.WaitOne(this.Session.ConnectionInfo.Timeout);
this._listenerTaskCompleted.Dispose();
this._listenerTaskCompleted = null;
this.IsStarted = false;
}
}
}
@@ -1,9 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using Renci.SshNet.Channels;
using System;
using System.Threading.Tasks;
namespace Renci.SshNet
{
@@ -12,84 +8,9 @@ namespace Renci.SshNet
/// </summary>
public partial class ForwardedPortLocal
{
private TcpListener _listener;
partial void ExecuteThread(Action action)
{
Task.Factory.StartNew(action);
}
partial void InternalStart()
{
// If port already started don't start it again
if (this.IsStarted)
return;
var ep = new IPEndPoint(Dns.GetHostAddresses(this.BoundHost)[0], (int)this.BoundPort);
this._listener = new TcpListener(ep);
this._listener.Start();
this._listenerTaskCompleted = new ManualResetEvent(false);
this.ExecuteThread(() =>
{
try
{
while (true)
{
var socket = this._listener.AcceptSocket();
this.ExecuteThread(() =>
{
try
{
IPEndPoint originatorEndPoint = socket.RemoteEndPoint as IPEndPoint;
this.RaiseRequestReceived(originatorEndPoint.Address.ToString(), (uint)originatorEndPoint.Port);
var channel = this.Session.CreateChannel<ChannelDirectTcpip>();
channel.Bind(this.Host, this.Port, socket);
}
catch (Exception exp)
{
this.RaiseExceptionEvent(exp);
}
});
}
}
catch (SocketException exp)
{
if (!(exp.SocketErrorCode == SocketError.Interrupted))
{
this.RaiseExceptionEvent(exp);
}
}
catch (Exception exp)
{
this.RaiseExceptionEvent(exp);
}
finally
{
this._listenerTaskCompleted.Set();
}
});
this.IsStarted = true;
}
partial void InternalStop()
{
// If port not started you cant stop it
if (!this.IsStarted)
return;
this._listener.Stop();
this._listenerTaskCompleted.WaitOne(this.Session.ConnectionInfo.Timeout);
this._listenerTaskCompleted.Dispose();
this._listenerTaskCompleted = null;
this.IsStarted = false;
}
}
}
@@ -10,6 +10,71 @@ namespace Renci.SshNet
{
private EventWaitHandle _listenerTaskCompleted;
/// <summary>
/// Gets the bound host.
/// </summary>
public string BoundHost { get; protected set; }
/// <summary>
/// Gets the bound port.
/// </summary>
public uint BoundPort { get; protected set; }
/// <summary>
/// Gets the forwarded host.
/// </summary>
public string Host { get; protected set; }
/// <summary>
/// Gets the forwarded port.
/// </summary>
public uint Port { get; protected set; }
/// <summary>
/// Initializes a new instance of the <see cref="ForwardedPortLocal"/> class.
/// </summary>
/// <param name="boundPort">The bound port.</param>
/// <param name="host">The host.</param>
/// <param name="port">The port.</param>
public ForwardedPortLocal(uint boundPort, string host, uint port)
: this(string.Empty, boundPort, host, port)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ForwardedPortLocal"/> class.
/// </summary>
/// <param name="boundHost">The bound host.</param>
/// <param name="boundPort">The bound port.</param>
/// <param name="host">The host.</param>
/// <param name="port">The port.</param>
public ForwardedPortLocal(string boundHost, uint boundPort, string host, uint port)
{
if (boundHost == null)
throw new ArgumentNullException("boundHost");
if (host == null)
throw new ArgumentNullException("host");
if (!boundHost.IsValidHost())
throw new ArgumentException("boundHost");
if (!boundPort.IsValidPort())
throw new ArgumentOutOfRangeException("boundPort");
if (!host.IsValidHost())
throw new ArgumentException("host");
if (!port.IsValidPort())
throw new ArgumentOutOfRangeException("port");
this.BoundHost = boundHost;
this.BoundPort = boundPort;
this.Host = host;
this.Port = port;
}
/// <summary>
/// Starts local port forwarding.
/// </summary>
@@ -17,6 +17,65 @@ namespace Renci.SshNet
private EventWaitHandle _globalRequestResponse = new AutoResetEvent(false);
/// <summary>
/// Gets the bound host.
/// </summary>
public string BoundHost { get; protected set; }
/// <summary>
/// Gets the bound port.
/// </summary>
public uint BoundPort { get; protected set; }
/// <summary>
/// Gets the forwarded host.
/// </summary>
public string Host { get; protected set; }
/// <summary>
/// Gets the forwarded port.
/// </summary>
public uint Port { get; protected set; }
public ForwardedPortRemote(uint boundPort, string host, uint port)
: this(string.Empty, boundPort, host, port)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ForwardedPortRemote"/> class.
/// </summary>
/// <param name="boundHost">The bound host.</param>
/// <param name="boundPort">The bound port.</param>
/// <param name="host">The host.</param>
/// <param name="port">The port.</param>
public ForwardedPortRemote(string boundHost, uint boundPort, string host, uint port)
{
if (boundHost == null)
throw new ArgumentNullException("boundHost");
if (host == null)
throw new ArgumentNullException("host");
if (!boundHost.IsValidHost())
throw new ArgumentException("boundHost");
if (!boundPort.IsValidPort())
throw new ArgumentOutOfRangeException("boundPort");
if (!host.IsValidHost())
throw new ArgumentException("host");
if (!port.IsValidPort())
throw new ArgumentOutOfRangeException("port");
this.BoundHost = boundHost;
this.BoundPort = boundPort;
this.Host = host;
this.Port = port;
}
/// <summary>
/// Starts remote port forwarding.
/// </summary>
@@ -129,6 +129,14 @@
<Compile Include="ConnectionInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="ForwardedPortDynamic.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="ForwardedPortDynamic.NET.cs" />
<Compile Include="ForwardedPortDynamic.NET40.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="ForwardedPortLocal.NET.cs" />
<Compile Include="ProxyTypes.cs">
<SubType>Code</SubType>
</Compile>
+8 -57
View File
@@ -120,71 +120,20 @@ namespace Renci.SshNet
}
/// <summary>
/// Adds forwarded port to the list.
/// Adds the forwarded port.
/// </summary>
/// <typeparam name="T">Type of forwarded port to add</typeparam>
/// <param name="boundHost">The bound host.</param>
/// <param name="boundPort">The bound port.</param>
/// <param name="connectedHost">The connected host.</param>
/// <param name="connectedPort">The connected port.</param>
/// <returns>
/// Forwarded port
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="boundHost"/> or <paramref name="connectedHost"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="boundHost"/> or <paramref name="connectedHost"/> is invalid.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="boundPort"/> or <paramref name="connectedPort"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
/// <exception cref="Renci.SshNet.Common.SshConnectionException">Client is not connected.</exception>
public T AddForwardedPort<T>(string boundHost, uint boundPort, string connectedHost, uint connectedPort) where T : ForwardedPort, new()
{
if (boundHost == null)
throw new ArgumentNullException("boundHost");
if (connectedHost == null)
throw new ArgumentNullException("connectedHost");
if (!boundHost.IsValidHost())
throw new ArgumentException("boundHost");
if (!boundPort.IsValidPort())
throw new ArgumentOutOfRangeException("boundPort");
if (!connectedHost.IsValidHost())
throw new ArgumentException("connectedHost");
if (!connectedPort.IsValidPort())
throw new ArgumentOutOfRangeException("connectedPort");
/// <param name="port">The port.</param>
public void AddForwardedPort(ForwardedPort port)
{
// Ensure that connection is established.
this.EnsureConnection();
T port = new T();
if (port.Session != null && port.Session != this.Session)
throw new InvalidOperationException("Forwarded port is already added to a different client.");
port.Session = this.Session;
port.BoundHost = boundHost;
port.BoundPort = boundPort;
port.Host = connectedHost;
port.Port = connectedPort;
this._forwardedPorts.Add(port);
return port;
}
/// <summary>
/// Adds forwarded port to the list bound to "localhost".
/// </summary>
/// <typeparam name="T">Type of forwarded port to add</typeparam>
/// <param name="boundPort">The bound port.</param>
/// <param name="connectedHost">The connected host.</param>
/// <param name="connectedPort">The connected port.</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"><paramref name="connectedHost"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="boundPort"/>, <paramref name="connectedPort"/> or <paramref name="connectedHost"/> is invalid.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="boundPort"/> or <paramref name="connectedPort"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
/// <exception cref="Renci.SshNet.Common.SshConnectionException">Client is not connected.</exception>
public T AddForwardedPort<T>(uint boundPort, string connectedHost, uint connectedPort) where T : ForwardedPort, new()
{
return this.AddForwardedPort<T>("localhost", boundPort, connectedHost, connectedPort);
}
/// <summary>
@@ -200,6 +149,8 @@ namespace Renci.SshNet
// Stop port forwarding before removing it
port.Stop();
port.Session = null;
this._forwardedPorts.Remove(port);
}