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