using System; using System.Diagnostics; using System.Linq; using System.Text; using System.Net; using System.Net.Sockets; using System.Threading; using Renci.SshNet.Abstractions; using Renci.SshNet.Channels; using Renci.SshNet.Common; namespace Renci.SshNet { public partial class ForwardedPortDynamic { private Socket _listener; private int _pendingRequests; #if FEATURE_SOCKET_EAP private ManualResetEvent _stoppingListener; #endif // FEATURE_SOCKET_EAP partial void InternalStart() { var ip = IPAddress.Any; if (!string.IsNullOrEmpty(BoundHost)) { ip = DnsAbstraction.GetHostAddresses(BoundHost)[0]; } var ep = new IPEndPoint(ip, (int) BoundPort); _listener = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // TODO: decide if we want to have blocking socket #if FEATURE_SOCKET_SETSOCKETOPTION _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true); #endif //FEATURE_SOCKET_SETSOCKETOPTION _listener.Bind(ep); _listener.Listen(5); Session.ErrorOccured += Session_ErrorOccured; Session.Disconnected += Session_Disconnected; _listenerCompleted = new ManualResetEvent(false); ThreadAbstraction.ExecuteThread(() => { try { #if FEATURE_SOCKET_EAP _stoppingListener = new ManualResetEvent(false); StartAccept(); _stoppingListener.WaitOne(); #elif FEATURE_SOCKET_APM while (true) { // 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 #elif FEATURE_SOCKET_TAP #error Accepting new socket connections is not implemented. #else #error Accepting new socket connections is not implemented. #endif } catch (Exception ex) { RaiseExceptionEvent(ex); } finally { if (Session != null) { Session.ErrorOccured -= Session_ErrorOccured; Session.Disconnected -= Session_Disconnected; } // mark listener stopped _listenerCompleted.Set(); } }); } private void Session_Disconnected(object sender, EventArgs e) { StopListener(); } private void Session_ErrorOccured(object sender, ExceptionEventArgs e) { StopListener(); } #if FEATURE_SOCKET_EAP private void StartAccept() { var args = new SocketAsyncEventArgs(); args.Completed += AcceptCompleted; if (!_listener.AcceptAsync(args)) { AcceptCompleted(null, args); } } private void AcceptCompleted(object sender, SocketAsyncEventArgs acceptAsyncEventArgs) { if (acceptAsyncEventArgs.SocketError != SocketError.Success) { StartAccept(); acceptAsyncEventArgs.AcceptSocket.Dispose(); return; } StartAccept(); ProcessAccept(acceptAsyncEventArgs.AcceptSocket); } #elif FEATURE_SOCKET_APM private void AcceptCallback(IAsyncResult ar) { // Get the socket that handles the client request var serverSocket = (Socket) ar.AsyncState; Socket clientSocket; try { clientSocket = serverSocket.EndAccept(ar); } catch (ObjectDisposedException) { // when the socket is closed, an ObjectDisposedException is thrown // by Socket.EndAccept(IAsyncResult) return; } ProcessAccept(clientSocket); } #endif private void ProcessAccept(Socket remoteSocket) { Interlocked.Increment(ref _pendingRequests); #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + remoteSocket.RemoteEndPoint + " | ForwardedPortDynamic.ProcessAccept | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT try { #if FEATURE_SOCKET_SETSOCKETOPTION remoteSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); remoteSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true); #endif //FEATURE_SOCKET_SETSOCKETOPTION using (var channel = Session.CreateChannelDirectTcpip()) { channel.Exception += Channel_Exception; try { if (!HandleSocks(channel, remoteSocket, Session.ConnectionInfo.Timeout)) { CloseSocket(remoteSocket); return; } // start receiving from client socket (and sending to server) channel.Bind(); } #if DEBUG_GERT catch (SocketException ex) { Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + ex.SocketErrorCode + " | " + DateTime.Now.ToString("hh:mm:ss.fff") + " | " + ex); } #endif // DEBUG_GERT finally { channel.Close(); } } } catch (SocketException ex) { // ignore exception thrown by interrupting the blocking receive as part of closing // the forwarded port if (ex.SocketErrorCode != SocketError.Interrupted) { #if DEBUG_GERT RaiseExceptionEvent(new Exception("ID: " + Thread.CurrentThread.ManagedThreadId, ex)); #else RaiseExceptionEvent(ex); #endif // DEBUG_GERT } CloseSocket(remoteSocket); } catch (Exception exp) { #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | " + exp + " | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT RaiseExceptionEvent(exp); CloseSocket(remoteSocket); } finally { Interlocked.Decrement(ref _pendingRequests); } } private bool HandleSocks(IChannelDirectTcpip channel, Socket remoteSocket, TimeSpan timeout) { // create eventhandler which is to be invoked to interrupt a blocking receive // when we're closing the forwarded port EventHandler closeClientSocket = (_, args) => CloseSocket(remoteSocket); Closing += closeClientSocket; try { #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Before ReadByte for version | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT var version = SocketAbstraction.ReadByte(remoteSocket, timeout); if (version == -1) { return false; } #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After ReadByte for version | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT if (version == 4) { return HandleSocks4(remoteSocket, channel, timeout); } else if (version == 5) { return HandleSocks5(remoteSocket, channel, timeout); } else { throw new NotSupportedException(string.Format("SOCKS version {0} is not supported.", version)); } } finally { // interrupt of blocking receive is now handled by channel (SOCKS4 and SOCKS5) // or no longer necessary Closing -= closeClientSocket; } } private static void CloseSocket(Socket socket) { #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ForwardedPortDynamic.CloseSocket | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT if (socket.Connected) { socket.Shutdown(SocketShutdown.Both); socket.Dispose(); } } partial void StopListener() { // if the port is not started then there's nothing to stop if (!IsStarted) return; #if FEATURE_SOCKET_EAP _stoppingListener.Set(); #endif // FEATURE_SOCKET_EAP // close listener socket _listener.Dispose(); // wait for listener loop to finish _listenerCompleted.WaitOne(); } /// /// Waits for pending requests to finish, and channels to close. /// /// The maximum time to wait for the forwarded port to stop. partial void InternalStop(TimeSpan timeout) { if (timeout == TimeSpan.Zero) return; 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 ThreadAbstraction.Sleep(50); } stopWatch.Stop(); } partial void InternalDispose(bool disposing) { if (disposing) { if (_listener != null) { _listener.Dispose(); _listener = null; } #if FEATURE_SOCKET_EAP if (_stoppingListener != null) { _stoppingListener.Dispose(); _stoppingListener = null; } #endif // FEATURE_SOCKET_EAP } } private bool HandleSocks4(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout) { var commandCode = SocketAbstraction.ReadByte(socket, timeout); if (commandCode == 0) { // SOCKS client closed connection return false; } // TODO: See what need to be done depends on the code var portBuffer = new byte[2]; if (SocketAbstraction.Read(socket, portBuffer, 0, portBuffer.Length, timeout) == 0) { // SOCKS client closed connection return false; } var port = (uint)(portBuffer[0] * 256 + portBuffer[1]); var ipBuffer = new byte[4]; if (SocketAbstraction.Read(socket, ipBuffer, 0, ipBuffer.Length, timeout) == 0) { // SOCKS client closed connection return false; } var ipAddress = new IPAddress(ipBuffer); var username = ReadString(socket, timeout); if (username == null) { // SOCKS client closed connection return false; } var host = ipAddress.ToString(); RaiseRequestReceived(host, port); channel.Open(host, port, this, socket); SocketAbstraction.SendByte(socket, 0x00); if (channel.IsOpen) { SocketAbstraction.SendByte(socket, 0x5a); SocketAbstraction.Send(socket, portBuffer, 0, portBuffer.Length); SocketAbstraction.Send(socket, ipBuffer, 0, ipBuffer.Length); return true; } // signal that request was rejected or failed SocketAbstraction.SendByte(socket, 0x5b); return false; } private bool HandleSocks5(Socket socket, IChannelDirectTcpip channel, TimeSpan timeout) { #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Handling Socks5: " + socket.LocalEndPoint + " | " + socket.RemoteEndPoint + " | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT var authenticationMethodsCount = SocketAbstraction.ReadByte(socket, timeout); if (authenticationMethodsCount == -1) { // SOCKS client closed connection return false; } #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After ReadByte for authenticationMethodsCount | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT var authenticationMethods = new byte[authenticationMethodsCount]; if (SocketAbstraction.Read(socket, authenticationMethods, 0, authenticationMethods.Length, timeout) == 0) { // SOCKS client closed connection return false; } #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After Read for authenticationMethods | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT if (authenticationMethods.Min() == 0) { // no user authentication is one of the authentication methods supported // by the SOCKS client SocketAbstraction.Send(socket, new byte[] { 0x05, 0x00 }, 0, 2); #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After Send for authenticationMethods 0 | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT } else { // the SOCKS client requires authentication, which we currently do not support SocketAbstraction.Send(socket, new byte[] { 0x05, 0xFF }, 0, 2); // we continue business as usual but expect the client to close the connection // so one of the subsequent reads should return -1 signaling that the client // has effectively closed the connection #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After Send for authenticationMethods 2 | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT } var version = SocketAbstraction.ReadByte(socket, timeout); if (version == -1) { // SOCKS client closed connection return false; } if (version != 5) throw new ProxyException("SOCKS5: Version 5 is expected."); var commandCode = SocketAbstraction.ReadByte(socket, timeout); if (commandCode == -1) { // SOCKS client closed connection return false; } var reserved = SocketAbstraction.ReadByte(socket, timeout); if (reserved == -1) { // SOCKS client closed connection return false; } if (reserved != 0) { throw new ProxyException("SOCKS5: 0 is expected for reserved byte."); } var addressType = SocketAbstraction.ReadByte(socket, timeout); if (addressType == -1) { // SOCKS client closed connection return false; } IPAddress ipAddress; byte[] addressBuffer; switch (addressType) { case 0x01: { addressBuffer = new byte[4]; if (SocketAbstraction.Read(socket, addressBuffer, 0, 4, timeout) == 0) { // SOCKS client closed connection return false; } ipAddress = new IPAddress(addressBuffer); } break; case 0x03: { var length = SocketAbstraction.ReadByte(socket, timeout); addressBuffer = new byte[length]; if (SocketAbstraction.Read(socket, addressBuffer, 0, addressBuffer.Length, timeout) == 0) { // SOCKS client closed connection return false; } ipAddress = IPAddress.Parse(SshData.Ascii.GetString(addressBuffer)); //var hostName = new Common.ASCIIEncoding().GetString(addressBuffer); //ipAddress = Dns.GetHostEntry(hostName).AddressList[0]; } break; case 0x04: { addressBuffer = new byte[16]; if (SocketAbstraction.Read(socket, addressBuffer, 0, 16, timeout) == 0) { // SOCKS client closed connection return false; } ipAddress = new IPAddress(addressBuffer); } break; default: throw new ProxyException(string.Format("SOCKS5: Address type '{0}' is not supported.", addressType)); } var portBuffer = new byte[2]; if (SocketAbstraction.Read(socket, portBuffer, 0, portBuffer.Length, timeout) == 0) { // SOCKS client closed connection return false; } var port = (uint)(portBuffer[0] * 256 + portBuffer[1]); var host = ipAddress.ToString(); RaiseRequestReceived(host, port); #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Before channel open | " + DateTime.Now.ToString("hh:mm:ss.fff")); var stopWatch = new Stopwatch(); stopWatch.Start(); #endif // DEBUG_GERT channel.Open(host, port, this, socket); #if DEBUG_GERT stopWatch.Stop(); Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | After channel open | " + DateTime.Now.ToString("hh:mm:ss.fff") + " => " + stopWatch.ElapsedMilliseconds); #endif // DEBUG_GERT var replyBuffer = new byte[10]; replyBuffer[0] = 0x05; // SocketAbstraction.SendByte(socket, 0x05); if (channel.IsOpen) { replyBuffer[1] = 0x00; // SocketAbstraction.SendByte(socket, 0x00); } else { replyBuffer[1] = 0x01; //SocketAbstraction.SendByte(socket, 0x01); #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Channel not open"); #endif // DEBUG_GERT } // reserved replyBuffer[2] = 0x00; // reserved //SocketAbstraction.SendByte(socket, 0x00); //if (ipAddress.AddressFamily == AddressFamily.InterNetwork) //{ // SocketAbstraction.SendByte(socket, 0x01); //} //else if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) //{ // SocketAbstraction.SendByte(socket, 0x04); //} //else //{ // throw new NotSupportedException("Not supported address family."); //} // IPv4 replyBuffer[3] = 0x01; SocketAbstraction.Send(socket, replyBuffer, 0, replyBuffer.Length); //var addressBytes = IPAddress.Any.GetAddressBytes(); //SocketAbstraction.Send(socket, addressBytes, 0, addressBytes.Length); //SocketAbstraction.Send(socket, new byte[] {0x00, 0x00}, 0, 2); //var addressBytes = ipAddress.GetAddressBytes(); //SocketAbstraction.Send(socket, addressBytes, 0, addressBytes.Length); //SocketAbstraction.Send(socket, portBuffer, 0, portBuffer.Length); return true; } private void Channel_Exception(object sender, ExceptionEventArgs e) { #if DEBUG_GERT Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Channel_Exception | " + DateTime.Now.ToString("hh:mm:ss.fff")); #endif // DEBUG_GERT RaiseExceptionEvent(e.Exception); } /// /// Reads a null terminated string from a socket. /// /// The to read from. /// The timeout to apply to individual reads. /// /// The read, or null when the socket was closed. /// private static string ReadString(Socket socket, TimeSpan timeout) { var text = new StringBuilder(); var buffer = new byte[1]; while (true) { if (SocketAbstraction.Read(socket, buffer, 0, 1, timeout) == 0) { // SOCKS client closed connection return null; } var byteRead = buffer[0]; if (byteRead == 0) { // end of the string break; } var c = (char) byteRead; text.Append(c); } return text.ToString(); } } }