ChannelForwardedTcpip.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using Renci.SshNet.Abstractions;
  5. using Renci.SshNet.Common;
  6. using Renci.SshNet.Messages.Connection;
  7. namespace Renci.SshNet.Channels
  8. {
  9. /// <summary>
  10. /// Implements "forwarded-tcpip" SSH channel.
  11. /// </summary>
  12. internal class ChannelForwardedTcpip : ServerChannel, IChannelForwardedTcpip
  13. {
  14. private readonly object _socketShutdownAndCloseLock = new object();
  15. private Socket _socket;
  16. private IForwardedPort _forwardedPort;
  17. /// <summary>
  18. /// Initializes a new <see cref="ChannelForwardedTcpip"/> instance.
  19. /// </summary>
  20. /// <param name="session">The session.</param>
  21. /// <param name="localChannelNumber">The local channel number.</param>
  22. /// <param name="localWindowSize">Size of the window.</param>
  23. /// <param name="localPacketSize">Size of the packet.</param>
  24. /// <param name="remoteChannelNumber">The remote channel number.</param>
  25. /// <param name="remoteWindowSize">The window size of the remote party.</param>
  26. /// <param name="remotePacketSize">The maximum size of a data packet that we can send to the remote party.</param>
  27. internal ChannelForwardedTcpip(ISession session, uint localChannelNumber, uint localWindowSize, uint localPacketSize, uint remoteChannelNumber, uint remoteWindowSize, uint remotePacketSize)
  28. : base(session, localChannelNumber, localWindowSize, localPacketSize, remoteChannelNumber, remoteWindowSize, remotePacketSize)
  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.ForwardedTcpip; }
  40. }
  41. /// <summary>
  42. /// Binds the channel to the specified endpoint.
  43. /// </summary>
  44. /// <param name="remoteEndpoint">The endpoint to connect to.</param>
  45. /// <param name="forwardedPort">The forwarded port for which the channel is opened.</param>
  46. public void Bind(IPEndPoint remoteEndpoint, IForwardedPort forwardedPort)
  47. {
  48. byte[] buffer;
  49. if (!IsConnected)
  50. {
  51. throw new SshException("Session is not connected.");
  52. }
  53. _forwardedPort = forwardedPort;
  54. _forwardedPort.Closing += ForwardedPort_Closing;
  55. // Try to connect to the socket
  56. try
  57. {
  58. // Get buffer in memory for data exchange
  59. buffer = new byte[RemotePacketSize];
  60. _socket = SocketAbstraction.Connect(remoteEndpoint, ConnectionInfo.Timeout);
  61. // send channel open confirmation message
  62. SendMessage(new ChannelOpenConfirmationMessage(RemoteChannelNumber, LocalWindowSize, LocalPacketSize, LocalChannelNumber));
  63. }
  64. catch (Exception exp)
  65. {
  66. // send channel open failure message
  67. SendMessage(new ChannelOpenFailureMessage(RemoteChannelNumber, exp.ToString(), ChannelOpenFailureMessage.ConnectFailed, "en"));
  68. throw;
  69. }
  70. // Start reading data from the port and send to channel
  71. while (_socket != null && _socket.Connected)
  72. {
  73. try
  74. {
  75. var read = SocketAbstraction.ReadPartial(_socket, buffer, 0, buffer.Length, ConnectionInfo.Timeout);
  76. if (read > 0)
  77. {
  78. #if TUNING
  79. SendData(buffer, 0, read);
  80. #else
  81. SendMessage(new ChannelDataMessage(RemoteChannelNumber, buffer.Take(read).ToArray()));
  82. #endif
  83. }
  84. else
  85. {
  86. // server quit sending
  87. break;
  88. }
  89. }
  90. catch (SocketException exp)
  91. {
  92. if (exp.SocketErrorCode == SocketError.WouldBlock ||
  93. exp.SocketErrorCode == SocketError.IOPending ||
  94. exp.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
  95. {
  96. // socket buffer is probably empty, wait and try again
  97. ThreadAbstraction.Sleep(30);
  98. }
  99. else if (exp.SocketErrorCode == SocketError.ConnectionAborted || exp.SocketErrorCode == SocketError.Interrupted)
  100. {
  101. break;
  102. }
  103. else
  104. throw; // throw any other error
  105. }
  106. }
  107. }
  108. protected override void OnErrorOccured(Exception exp)
  109. {
  110. base.OnErrorOccured(exp);
  111. // signal to the server that we will not send anything anymore; this will also interrupt the
  112. // blocking receive in Bind if the server sends FIN/ACK in time
  113. //
  114. // if the FIN/ACK is not sent in time, the socket will be closed in Close(bool)
  115. ShutdownSocket(SocketShutdown.Send);
  116. }
  117. /// <summary>
  118. /// Occurs as the forwarded port is being stopped.
  119. /// </summary>
  120. private void ForwardedPort_Closing(object sender, EventArgs eventArgs)
  121. {
  122. // signal to the server that we will not send anything anymore; this will also interrupt the
  123. // blocking receive in Bind if the server sends FIN/ACK in time
  124. //
  125. // if the FIN/ACK is not sent in time, the socket will be closed in Close(bool)
  126. ShutdownSocket(SocketShutdown.Send);
  127. }
  128. /// <summary>
  129. /// Shuts down the socket.
  130. /// </summary>
  131. /// <param name="how">One of the <see cref="SocketShutdown"/> values that specifies the operation that will no longer be allowed.</param>
  132. private void ShutdownSocket(SocketShutdown how)
  133. {
  134. if (_socket == null || !_socket.Connected)
  135. return;
  136. lock (_socketShutdownAndCloseLock)
  137. {
  138. if (_socket == null || !_socket.Connected)
  139. return;
  140. _socket.Shutdown(how);
  141. }
  142. }
  143. /// <summary>
  144. /// Closes the socket, hereby interrupting the blocking receive in <see cref="Bind(IPEndPoint,IForwardedPort)"/>.
  145. /// </summary>
  146. private void CloseSocket()
  147. {
  148. if (_socket == null)
  149. return;
  150. lock (_socketShutdownAndCloseLock)
  151. {
  152. if (_socket == null)
  153. return;
  154. // closing a socket actually disposes the socket, so we can safely dereference
  155. // the field to avoid entering the lock again later
  156. _socket.Dispose();
  157. _socket = null;
  158. }
  159. }
  160. /// <summary>
  161. /// Closes the channel, optionally waiting for the SSH_MSG_CHANNEL_CLOSE message to
  162. /// be received from the server.
  163. /// </summary>
  164. /// <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>
  165. protected override void Close(bool wait)
  166. {
  167. if (_forwardedPort != null)
  168. {
  169. _forwardedPort.Closing -= ForwardedPort_Closing;
  170. _forwardedPort = null;
  171. }
  172. // signal to the server that we will not send anything anymore; this will also interrupt the
  173. // blocking receive in Bind if the server sends FIN/ACK in time
  174. //
  175. // if the FIN/ACK is not sent in time, the socket will be closed after the channel is closed
  176. ShutdownSocket(SocketShutdown.Send);
  177. // close the SSH channel, and mark the channel closed
  178. base.Close(wait);
  179. // close the socket
  180. CloseSocket();
  181. }
  182. /// <summary>
  183. /// Called when channel data is received.
  184. /// </summary>
  185. /// <param name="data">The data.</param>
  186. protected override void OnData(byte[] data)
  187. {
  188. base.OnData(data);
  189. if (_socket != null && _socket.Connected)
  190. SocketAbstraction.Send(_socket, data, 0, data.Length);
  191. }
  192. /// <summary>
  193. /// Releases unmanaged and - optionally - managed resources
  194. /// </summary>
  195. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  196. protected override void Dispose(bool disposing)
  197. {
  198. // make sure we've unsubscribed from all session events and closed the channel
  199. // before we starting disposing
  200. base.Dispose(disposing);
  201. if (disposing)
  202. {
  203. if (_socket != null)
  204. {
  205. _socket.Dispose();
  206. _socket = null;
  207. }
  208. }
  209. }
  210. }
  211. }