ForwardedPortLocal.NET.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. using System;
  2. using System.Diagnostics;
  3. using System.Net.Sockets;
  4. using System.Net;
  5. using System.Threading;
  6. using Renci.SshNet.Common;
  7. namespace Renci.SshNet
  8. {
  9. /// <summary>
  10. /// Provides functionality for local port forwarding
  11. /// </summary>
  12. public partial class ForwardedPortLocal
  13. {
  14. private Socket _listener;
  15. private int _pendingRequests;
  16. partial void ExecuteThread(Action action);
  17. partial void InternalStart()
  18. {
  19. var addr = BoundHost.GetIPAddress();
  20. var ep = new IPEndPoint(addr, (int) BoundPort);
  21. _listener = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp) {Blocking = true};
  22. _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
  23. _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
  24. _listener.Bind(ep);
  25. _listener.Listen(1);
  26. // update bound port (in case original was passed as zero)
  27. BoundPort = (uint)((IPEndPoint)_listener.LocalEndPoint).Port;
  28. Session.ErrorOccured += Session_ErrorOccured;
  29. Session.Disconnected += Session_Disconnected;
  30. _listenerTaskCompleted = new ManualResetEvent(false);
  31. ExecuteThread(() =>
  32. {
  33. try
  34. {
  35. while (true)
  36. {
  37. // accept new inbound connection
  38. var asyncResult = _listener.BeginAccept(AcceptCallback, _listener);
  39. // wait for the connection to be established
  40. asyncResult.AsyncWaitHandle.WaitOne();
  41. }
  42. }
  43. catch (ObjectDisposedException)
  44. {
  45. // BeginAccept will throw an ObjectDisposedException when the
  46. // socket is closed
  47. }
  48. catch (Exception ex)
  49. {
  50. RaiseExceptionEvent(ex);
  51. }
  52. finally
  53. {
  54. // mark listener stopped
  55. _listenerTaskCompleted.Set();
  56. }
  57. });
  58. }
  59. private void AcceptCallback(IAsyncResult ar)
  60. {
  61. // Get the socket that handles the client request
  62. var serverSocket = (Socket)ar.AsyncState;
  63. Socket clientSocket;
  64. try
  65. {
  66. clientSocket = serverSocket.EndAccept(ar);
  67. }
  68. catch (ObjectDisposedException)
  69. {
  70. // when the socket is closed, an ObjectDisposedException is thrown
  71. // by Socket.EndAccept(IAsyncResult)
  72. return;
  73. }
  74. Interlocked.Increment(ref _pendingRequests);
  75. try
  76. {
  77. clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
  78. clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
  79. var originatorEndPoint = (IPEndPoint) clientSocket.RemoteEndPoint;
  80. RaiseRequestReceived(originatorEndPoint.Address.ToString(),
  81. (uint)originatorEndPoint.Port);
  82. using (var channel = Session.CreateChannelDirectTcpip())
  83. {
  84. channel.Exception += Channel_Exception;
  85. channel.Open(Host, Port, this, clientSocket);
  86. channel.Bind();
  87. channel.Close();
  88. }
  89. }
  90. catch (Exception exp)
  91. {
  92. RaiseExceptionEvent(exp);
  93. CloseSocket(clientSocket);
  94. }
  95. finally
  96. {
  97. Interlocked.Decrement(ref _pendingRequests);
  98. }
  99. }
  100. private static void CloseSocket(Socket socket)
  101. {
  102. if (socket.Connected)
  103. {
  104. socket.Shutdown(SocketShutdown.Both);
  105. socket.Close();
  106. }
  107. }
  108. partial void InternalStop(TimeSpan timeout)
  109. {
  110. if (timeout == TimeSpan.Zero)
  111. return;
  112. var stopWatch = new Stopwatch();
  113. stopWatch.Start();
  114. while (true)
  115. {
  116. // break out of loop when all pending requests have been processed
  117. if (Interlocked.CompareExchange(ref _pendingRequests, 0, 0) == 0)
  118. break;
  119. // break out of loop when specified timeout has elapsed
  120. if (stopWatch.Elapsed >= timeout && timeout != SshNet.Session.InfiniteTimeSpan)
  121. break;
  122. // give channels time to process pending requests
  123. Thread.Sleep(50);
  124. }
  125. stopWatch.Stop();
  126. }
  127. /// <summary>
  128. /// Interrupts the listener, and waits for the listener loop to finish.
  129. /// </summary>
  130. /// <remarks>
  131. /// When the forwarded port is stopped, then any further action is skipped.
  132. /// </remarks>
  133. partial void StopListener()
  134. {
  135. if (!IsStarted)
  136. return;
  137. Session.Disconnected -= Session_Disconnected;
  138. Session.ErrorOccured -= Session_ErrorOccured;
  139. // close listener socket
  140. _listener.Close();
  141. // wait for listener loop to finish
  142. _listenerTaskCompleted.WaitOne();
  143. }
  144. private void Session_ErrorOccured(object sender, Common.ExceptionEventArgs e)
  145. {
  146. StopListener();
  147. }
  148. private void Session_Disconnected(object sender, EventArgs e)
  149. {
  150. StopListener();
  151. }
  152. private void Channel_Exception(object sender, ExceptionEventArgs e)
  153. {
  154. RaiseExceptionEvent(e.Exception);
  155. }
  156. }
  157. }