ChannelDirectTcpip.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using System.Threading;
  5. using Renci.SshNet.Abstractions;
  6. using Renci.SshNet.Common;
  7. using Renci.SshNet.Messages.Connection;
  8. namespace Renci.SshNet.Channels
  9. {
  10. /// <summary>
  11. /// Implements "direct-tcpip" SSH channel.
  12. /// </summary>
  13. internal class ChannelDirectTcpip : ClientChannel, IChannelDirectTcpip
  14. {
  15. private readonly object _socketLock = new object();
  16. private EventWaitHandle _channelOpen = new AutoResetEvent(false);
  17. private EventWaitHandle _channelData = new AutoResetEvent(false);
  18. private IForwardedPort _forwardedPort;
  19. private Socket _socket;
  20. /// <summary>
  21. /// Initializes a new <see cref="ChannelDirectTcpip"/> instance.
  22. /// </summary>
  23. /// <param name="session">The session.</param>
  24. /// <param name="localChannelNumber">The local channel number.</param>
  25. /// <param name="localWindowSize">Size of the window.</param>
  26. /// <param name="localPacketSize">Size of the packet.</param>
  27. public ChannelDirectTcpip(ISession session, uint localChannelNumber, uint localWindowSize, uint localPacketSize)
  28. : base(session, localChannelNumber, localWindowSize, localPacketSize)
  29. {
  30. }
  31. /// <summary>
  32. /// Gets the type of the channel.
  33. /// </summary>
  34. /// <value>
  35. /// The type of the channel.
  36. /// </value>
  37. public override ChannelTypes ChannelType
  38. {
  39. get { return ChannelTypes.DirectTcpip; }
  40. }
  41. public void Open(string remoteHost, uint port, IForwardedPort forwardedPort, Socket socket)
  42. {
  43. if (IsOpen)
  44. throw new SshException("Channel is already open.");
  45. if (!IsConnected)
  46. throw new SshException("Session is not connected.");
  47. _socket = socket;
  48. _forwardedPort = forwardedPort;
  49. _forwardedPort.Closing += ForwardedPort_Closing;
  50. var ep = (IPEndPoint) socket.RemoteEndPoint;
  51. #if DEBUG_GERT
  52. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelOpenMessage send '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
  53. #endif // DEBUG_GERT
  54. // open channel
  55. SendMessage(new ChannelOpenMessage(LocalChannelNumber, LocalWindowSize, LocalPacketSize,
  56. new DirectTcpipChannelInfo(remoteHost, port, ep.Address.ToString(), (uint) ep.Port)));
  57. #if DEBUG_GERT
  58. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelOpenMessage sent '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
  59. #endif // DEBUG_GERT
  60. // Wait for channel to open
  61. WaitOnHandle(_channelOpen);
  62. #if DEBUG_GERT
  63. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelOpenMessage flagged '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
  64. #endif // DEBUG_GERT
  65. }
  66. /// <summary>
  67. /// Occurs as the forwarded port is being stopped.
  68. /// </summary>
  69. private void ForwardedPort_Closing(object sender, EventArgs eventArgs)
  70. {
  71. // signal to the client that we will not send anything anymore; this will also interrupt the
  72. // blocking receive in Bind if the client sends FIN/ACK in time
  73. //
  74. // if the FIN/ACK is not sent in time, the socket will be closed in Close(bool)
  75. ShutdownSocket(SocketShutdown.Send);
  76. }
  77. /// <summary>
  78. /// Binds channel to remote host.
  79. /// </summary>
  80. public void Bind()
  81. {
  82. // Cannot bind if channel is not open
  83. if (!IsOpen)
  84. return;
  85. var buffer = new byte[RemotePacketSize];
  86. SocketAbstraction.ReadContinuous(_socket, buffer, 0, buffer.Length, SendData);
  87. #if DEBUG_GERT
  88. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.Bind (after) '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
  89. #endif // DEBUG_GERT
  90. // even though the client has disconnected, we still want to properly close the
  91. // channel
  92. //
  93. // we'll do this in in Close(bool) that way we have a single place from which we
  94. // send an SSH_MSG_CHANNEL_EOF message and wait for the SSH_MSG_CHANNEL_CLOSE
  95. // message
  96. }
  97. /// <summary>
  98. /// Closes the socket, hereby interrupting the blocking receive in <see cref="Bind()"/>.
  99. /// </summary>
  100. private void CloseSocket()
  101. {
  102. if (_socket == null)
  103. return;
  104. lock (_socketLock)
  105. {
  106. if (_socket == null)
  107. return;
  108. #if DEBUG_GERT
  109. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.CloseSocket '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
  110. #endif // DEBUG_GERT
  111. // closing a socket actually disposes the socket, so we can safely dereference
  112. // the field to avoid entering the lock again later
  113. _socket.Dispose();
  114. _socket = null;
  115. }
  116. }
  117. /// <summary>
  118. /// Shuts down the socket.
  119. /// </summary>
  120. /// <param name="how">One of the <see cref="SocketShutdown"/> values that specifies the operation that will no longer be allowed.</param>
  121. private void ShutdownSocket(SocketShutdown how)
  122. {
  123. if (_socket == null)
  124. return;
  125. lock (_socketLock)
  126. {
  127. #if DEBUG_GERT
  128. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.ShutdownSocket '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
  129. #endif // DEBUG_GERT
  130. if (_socket == null || !_socket.Connected)
  131. return;
  132. _socket.Shutdown(how);
  133. }
  134. }
  135. /// <summary>
  136. /// Closes the channel, optionally waiting for the SSH_MSG_CHANNEL_CLOSE message to
  137. /// be received from the server.
  138. /// </summary>
  139. /// <param name="wait"><c>true</c> to wait for the SSH_MSG_CHANNEL_CLOSE message to be received from the server; otherwise, <c>false</c>.</param>
  140. protected override void Close(bool wait)
  141. {
  142. #if DEBUG_GERT
  143. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.Close(bool) '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
  144. #endif // DEBUG_GERT
  145. var forwardedPort = _forwardedPort;
  146. if (forwardedPort != null)
  147. {
  148. forwardedPort.Closing -= ForwardedPort_Closing;
  149. _forwardedPort = null;
  150. }
  151. // signal to the client that we will not send anything anymore; this will also interrupt the
  152. // blocking receive in Bind if the client sends FIN/ACK in time
  153. //
  154. // if the FIN/ACK is not sent in time, the socket will be closed after the channel is closed
  155. ShutdownSocket(SocketShutdown.Send);
  156. // close the SSH channel, and mark the channel closed
  157. base.Close(wait);
  158. // close the socket
  159. CloseSocket();
  160. }
  161. /// <summary>
  162. /// Called when channel data is received.
  163. /// </summary>
  164. /// <param name="data">The data.</param>
  165. protected override void OnData(byte[] data)
  166. {
  167. base.OnData(data);
  168. if (_socket != null && _socket.Connected)
  169. {
  170. lock (_socketLock)
  171. {
  172. if (_socket != null && _socket.Connected)
  173. {
  174. SocketAbstraction.Send(_socket, data, 0, data.Length);
  175. }
  176. }
  177. }
  178. }
  179. /// <summary>
  180. /// Called when channel is opened by the server.
  181. /// </summary>
  182. /// <param name="remoteChannelNumber">The remote channel number.</param>
  183. /// <param name="initialWindowSize">Initial size of the window.</param>
  184. /// <param name="maximumPacketSize">Maximum size of the packet.</param>
  185. protected override void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize)
  186. {
  187. base.OnOpenConfirmation(remoteChannelNumber, initialWindowSize, maximumPacketSize);
  188. _channelOpen.Set();
  189. #if DEBUG_GERT
  190. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelOpenMessage confirmed '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
  191. #endif // DEBUG_GERT
  192. }
  193. protected override void OnOpenFailure(uint reasonCode, string description, string language)
  194. {
  195. base.OnOpenFailure(reasonCode, description, language);
  196. _channelOpen.Set();
  197. #if DEBUG_GERT
  198. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelOpenMessage failure '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss.fff"));
  199. #endif // DEBUG_GERT
  200. }
  201. /// <summary>
  202. /// Called when channel has no more data to receive.
  203. /// </summary>
  204. protected override void OnEof()
  205. {
  206. base.OnEof();
  207. #if DEBUG_GERT
  208. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.OnEof '" + LocalChannelNumber + "' | " +
  209. DateTime.Now.ToString("hh:mm:ss"));
  210. #endif // DEBUG_GERT
  211. // the channel will send no more data, and hence it does not make sense to receive
  212. // any more data from the client to send to the remote party (and we surely won't
  213. // send anything anymore)
  214. //
  215. // this will also interrupt the blocking receive in Bind()
  216. ShutdownSocket(SocketShutdown.Send);
  217. }
  218. /// <summary>
  219. /// Called whenever an unhandled <see cref="Exception"/> occurs in <see cref="Session"/> causing
  220. /// the message loop to be interrupted, or when an exception occurred processing a channel message.
  221. /// </summary>
  222. protected override void OnErrorOccured(Exception exp)
  223. {
  224. base.OnErrorOccured(exp);
  225. #if DEBUG_GERT
  226. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.OnErrorOccured '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss"));
  227. #endif // DEBUG_GERT
  228. // signal to the client that we will not send anything anymore; this will also interrupt the
  229. // blocking receive in Bind if the client sends FIN/ACK in time
  230. //
  231. // if the FIN/ACK is not sent in time, the socket will be closed in Close(bool)
  232. ShutdownSocket(SocketShutdown.Send);
  233. }
  234. /// <summary>
  235. /// Called when the server wants to terminate the connection immmediately.
  236. /// </summary>
  237. /// <remarks>
  238. /// The sender MUST NOT send or receive any data after this message, and
  239. /// the recipient MUST NOT accept any data after receiving this message.
  240. /// </remarks>
  241. protected override void OnDisconnected()
  242. {
  243. base.OnDisconnected();
  244. #if DEBUG_GERT
  245. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | ChannelDirectTcpip.OnDisconnected '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss"));
  246. #endif // DEBUG_GERT
  247. // the channel will accept or send no more data, and hence it does not make sense
  248. // to accept any more data from the client (and we surely won't send anything
  249. // anymore)
  250. //
  251. //
  252. // so lets signal to the client that we will not send or receive anything anymore
  253. // this will also interrupt the blocking receive in Bind()
  254. ShutdownSocket(SocketShutdown.Both);
  255. }
  256. protected override void Dispose(bool disposing)
  257. {
  258. // make sure we've unsubscribed from all session events and closed the channel
  259. // before we starting disposing
  260. base.Dispose(disposing);
  261. if (disposing)
  262. {
  263. #if DEBUG_GERT
  264. Console.WriteLine("ID: " + Thread.CurrentThread.ManagedThreadId + " | Dispose '" + LocalChannelNumber + "' | " + DateTime.Now.ToString("hh:mm:ss"));
  265. #endif // DEBUG_GERT
  266. if (_socket != null)
  267. {
  268. lock (_socketLock)
  269. {
  270. if (_socket != null)
  271. {
  272. _socket.Dispose();
  273. _socket = null;
  274. }
  275. }
  276. }
  277. var channelOpen = _channelOpen;
  278. if (channelOpen != null)
  279. {
  280. channelOpen.Dispose();
  281. _channelOpen = null;
  282. }
  283. var channelData = _channelData;
  284. if (channelData != null)
  285. {
  286. channelData.Dispose();
  287. _channelData = null;
  288. }
  289. }
  290. }
  291. }
  292. }