2
0

Session.NET.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. using System.Linq;
  2. using System;
  3. using System.Net.Sockets;
  4. using System.Net;
  5. using Renci.SshNet.Common;
  6. using System.Threading;
  7. using Renci.SshNet.Messages.Transport;
  8. using System.Diagnostics;
  9. using System.Collections.Generic;
  10. namespace Renci.SshNet
  11. {
  12. public partial class Session
  13. {
  14. private readonly TraceSource _log =
  15. #if DEBUG
  16. new TraceSource("SshNet.Logging", SourceLevels.All);
  17. #else
  18. new TraceSource("SshNet.Logging");
  19. #endif
  20. /// <summary>
  21. /// Holds the lock object to ensure read access to the socket is synchronized.
  22. /// </summary>
  23. private readonly object _socketReadLock = new object();
  24. /// <summary>
  25. /// Gets a value indicating whether the socket is connected.
  26. /// </summary>
  27. /// <value>
  28. /// <c>true</c> if the socket is connected; otherwise, <c>false</c>.
  29. /// </value>
  30. /// <remarks>
  31. /// <para>
  32. /// As a first check we verify whether <see cref="Socket.Connected"/> is
  33. /// <c>true</c>. However, this only returns the state of the socket as of
  34. /// the last I/O operation. Therefore we use the combination of Socket.Poll
  35. /// with mode SelectRead and Socket.Available to verify if the socket is
  36. /// still connected.
  37. /// </para>
  38. /// <para>
  39. /// The MSDN doc mention the following on the return value of <see cref="Socket.Poll(int, SelectMode)"/>
  40. /// with mode <see cref="SelectMode.SelectRead"/>:
  41. /// <list type="bullet">
  42. /// <item>
  43. /// <description><c>true</c> if data is available for reading;</description>
  44. /// </item>
  45. /// <item>
  46. /// <description><c>true</c> if the connection has been closed, reset, or terminated; otherwise, returns <c>false</c>.</description>
  47. /// </item>
  48. /// </list>
  49. /// </para>
  50. /// <para>
  51. /// <c>Conclusion:</c> when the return value is <c>true</c> - but no data is available for reading - then
  52. /// the socket is no longer connected.
  53. /// </para>
  54. /// <para>
  55. /// When a <see cref="Socket"/> is used from multiple threads, there's a race condition
  56. /// between the invocation of <see cref="Socket.Poll(int, SelectMode)"/> and the moment
  57. /// when the value of <see cref="Socket.Available"/> is obtained. As a workaround, we signal
  58. /// when bytes are read from the <see cref="Socket"/>.
  59. /// </para>
  60. /// </remarks>
  61. partial void IsSocketConnected(ref bool isConnected)
  62. {
  63. isConnected = (_socket != null && _socket.Connected);
  64. if (isConnected)
  65. {
  66. // synchronize this to ensure thread B does not reset the wait handle before
  67. // thread A was able to check whether "bytes read from socket" signal was
  68. // actually received
  69. lock (_socketReadLock)
  70. {
  71. _bytesReadFromSocket.Reset();
  72. var connectionClosedOrDataAvailable = _socket.Poll(1000, SelectMode.SelectRead);
  73. isConnected = !(connectionClosedOrDataAvailable && _socket.Available == 0);
  74. if (!isConnected)
  75. {
  76. // the race condition is between the Socket.Poll call and
  77. // Socket.Available, but the event handler - where we signal that
  78. // bytes have been received from the socket - is sometimes invoked
  79. // shortly after
  80. isConnected = _bytesReadFromSocket.WaitOne(500);
  81. }
  82. }
  83. }
  84. }
  85. partial void SocketConnect(string host, int port)
  86. {
  87. const int socketBufferSize = 2 * MaximumSshPacketSize;
  88. var addr = host.GetIPAddress();
  89. var ep = new IPEndPoint(addr, port);
  90. this._socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  91. this._socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
  92. this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, socketBufferSize);
  93. this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, socketBufferSize);
  94. this.Log(string.Format("Initiating connect to '{0}:{1}'.", this.ConnectionInfo.Host, this.ConnectionInfo.Port));
  95. // Connect socket with specified timeout
  96. var connectResult = this._socket.BeginConnect(ep, null, null);
  97. if (!connectResult.AsyncWaitHandle.WaitOne(this.ConnectionInfo.Timeout, false))
  98. {
  99. throw new SshOperationTimeoutException("Connection Could Not Be Established");
  100. }
  101. this._socket.EndConnect(connectResult);
  102. }
  103. partial void SocketDisconnect()
  104. {
  105. _socket.Disconnect(true);
  106. }
  107. partial void SocketReadLine(ref string response)
  108. {
  109. var encoding = new ASCIIEncoding();
  110. // Read data one byte at a time to find end of line and leave any unhandled information in the buffer to be processed later
  111. var buffer = new List<byte>();
  112. var data = new byte[1];
  113. do
  114. {
  115. var asyncResult = this._socket.BeginReceive(data, 0, data.Length, SocketFlags.None, null, null);
  116. if (!asyncResult.AsyncWaitHandle.WaitOne(this.ConnectionInfo.Timeout))
  117. throw new SshOperationTimeoutException("Socket read operation has timed out");
  118. var received = this._socket.EndReceive(asyncResult);
  119. // If zero bytes received then exit
  120. if (received == 0)
  121. break;
  122. buffer.Add(data[0]);
  123. }
  124. while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == 0x0A || buffer[buffer.Count - 1] == 0x00)));
  125. // Return an empty version string if the buffer consists of a 0x00 character.
  126. if (buffer.Count > 0 && buffer[buffer.Count - 1] == 0x00)
  127. {
  128. response = string.Empty;
  129. }
  130. else if (buffer.Count > 1 && buffer[buffer.Count - 2] == 0x0D)
  131. response = encoding.GetString(buffer.Take(buffer.Count - 2).ToArray());
  132. else
  133. response = encoding.GetString(buffer.Take(buffer.Count - 1).ToArray());
  134. }
  135. /// <summary>
  136. /// Function to read <paramref name="length"/> amount of data before returning, or throwing an exception.
  137. /// </summary>
  138. /// <param name="length">The amount wanted.</param>
  139. /// <param name="buffer">The buffer to read to.</param>
  140. /// <exception cref="SshConnectionException">Happens when the socket is closed.</exception>
  141. /// <exception cref="Exception">Unhandled exception.</exception>
  142. partial void SocketRead(int length, ref byte[] buffer)
  143. {
  144. var receivedTotal = 0; // how many bytes is already received
  145. do
  146. {
  147. try
  148. {
  149. var receivedBytes = this._socket.Receive(buffer, receivedTotal, length - receivedTotal, SocketFlags.None);
  150. if (receivedBytes > 0)
  151. {
  152. // signal that bytes have been read from the socket
  153. // this is used to improve accuracy of Session.IsSocketConnected
  154. _bytesReadFromSocket.Set();
  155. receivedTotal += receivedBytes;
  156. continue;
  157. }
  158. // 2012-09-11: Kenneth_aa
  159. // When Disconnect or Dispose is called, this throws SshConnectionException(), which...
  160. // 1 - goes up to ReceiveMessage()
  161. // 2 - up again to MessageListener()
  162. // which is where there is a catch-all exception block so it can notify event listeners.
  163. // 3 - MessageListener then again calls RaiseError().
  164. // There the exception is checked for the exception thrown here (ConnectionLost), and if it matches it will not call Session.SendDisconnect().
  165. //
  166. // Adding a check for this._isDisconnecting causes ReceiveMessage() to throw SshConnectionException: "Bad packet length {0}".
  167. //
  168. if (_isDisconnecting)
  169. throw new SshConnectionException("An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost);
  170. throw new SshConnectionException("An established connection was aborted by the server.", DisconnectReason.ConnectionLost);
  171. }
  172. catch (SocketException exp)
  173. {
  174. if (exp.SocketErrorCode == SocketError.ConnectionAborted)
  175. {
  176. buffer = new byte[length];
  177. this.Disconnect();
  178. return;
  179. }
  180. if (exp.SocketErrorCode == SocketError.WouldBlock ||
  181. exp.SocketErrorCode == SocketError.IOPending ||
  182. exp.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
  183. {
  184. // socket buffer is probably empty, wait and try again
  185. Thread.Sleep(30);
  186. }
  187. else
  188. throw; // any serious error occurred
  189. }
  190. } while (receivedTotal < length);
  191. }
  192. partial void SocketWrite(byte[] data)
  193. {
  194. var sent = 0; // how many bytes is already sent
  195. var length = data.Length;
  196. do
  197. {
  198. try
  199. {
  200. sent += this._socket.Send(data, sent, length - sent, SocketFlags.None);
  201. }
  202. catch (SocketException ex)
  203. {
  204. if (ex.SocketErrorCode == SocketError.WouldBlock ||
  205. ex.SocketErrorCode == SocketError.IOPending ||
  206. ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
  207. {
  208. // socket buffer is probably full, wait and try again
  209. Thread.Sleep(30);
  210. }
  211. else
  212. throw; // any serious error occurr
  213. }
  214. } while (sent < length);
  215. }
  216. [Conditional("DEBUG")]
  217. partial void Log(string text)
  218. {
  219. this._log.TraceEvent(TraceEventType.Verbose, 1, text);
  220. }
  221. #if ASYNC_SOCKET_READ
  222. private void SocketRead(int length, ref byte[] buffer)
  223. {
  224. var state = new SocketReadState(_socket, length, ref buffer);
  225. _socket.BeginReceive(buffer, 0, length, SocketFlags.None, SocketReceiveCallback, state);
  226. var readResult = state.Wait();
  227. switch (readResult)
  228. {
  229. case SocketReadResult.Complete:
  230. break;
  231. case SocketReadResult.ConnectionLost:
  232. if (_isDisconnecting)
  233. throw new SshConnectionException(
  234. "An established connection was aborted by the software in your host machine.",
  235. DisconnectReason.ConnectionLost);
  236. throw new SshConnectionException("An established connection was aborted by the server.",
  237. DisconnectReason.ConnectionLost);
  238. case SocketReadResult.Failed:
  239. var socketException = state.Exception as SocketException;
  240. if (socketException != null)
  241. {
  242. if (socketException.SocketErrorCode == SocketError.ConnectionAborted)
  243. {
  244. buffer = new byte[length];
  245. this.Disconnect();
  246. return;
  247. }
  248. }
  249. throw state.Exception;
  250. }
  251. }
  252. private void SocketReceiveCallback(IAsyncResult ar)
  253. {
  254. var state = ar.AsyncState as SocketReadState;
  255. var socket = state.Socket;
  256. try
  257. {
  258. var bytesReceived = socket.EndReceive(ar);
  259. if (bytesReceived > 0)
  260. {
  261. _bytesReadFromSocket.Set();
  262. state.BytesRead += bytesReceived;
  263. if (state.BytesRead < state.TotalBytesToRead)
  264. {
  265. socket.BeginReceive(state.Buffer, state.BytesRead, state.TotalBytesToRead - state.BytesRead,
  266. SocketFlags.None, SocketReceiveCallback, state);
  267. }
  268. else
  269. {
  270. // we received all bytes that we wanted, so lets mark the read
  271. // complete
  272. state.Complete();
  273. }
  274. }
  275. else
  276. {
  277. // the remote host shut down the connection; this could also have been
  278. // triggered by a SSH_MSG_DISCONNECT sent by the client
  279. state.ConnectionLost();
  280. }
  281. }
  282. catch (SocketException ex)
  283. {
  284. if (ex.SocketErrorCode != SocketError.ConnectionAborted)
  285. {
  286. if (ex.SocketErrorCode == SocketError.WouldBlock ||
  287. ex.SocketErrorCode == SocketError.IOPending ||
  288. ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
  289. {
  290. // socket buffer is probably empty, wait and try again
  291. Thread.Sleep(30);
  292. socket.BeginReceive(state.Buffer, state.BytesRead, state.TotalBytesToRead - state.BytesRead,
  293. SocketFlags.None, SocketReceiveCallback, state);
  294. return;
  295. }
  296. }
  297. state.Fail(ex);
  298. }
  299. catch (Exception ex)
  300. {
  301. state.Fail(ex);
  302. }
  303. }
  304. private class SocketReadState
  305. {
  306. private SocketReadResult _result;
  307. /// <summary>
  308. /// WaitHandle to signal that read from socket has completed (either successfully
  309. /// or with failure)
  310. /// </summary>
  311. private EventWaitHandle _socketReadComplete;
  312. public SocketReadState(Socket socket, int totalBytesToRead, ref byte[] buffer)
  313. {
  314. Socket = socket;
  315. TotalBytesToRead = totalBytesToRead;
  316. Buffer = buffer;
  317. _socketReadComplete = new ManualResetEvent(false);
  318. }
  319. /// <summary>
  320. /// Gets the <see cref="Socket"/> to read from.
  321. /// </summary>
  322. /// <value>
  323. /// The <see cref="Socket"/> to read from.
  324. /// </value>
  325. public Socket Socket { get; private set; }
  326. /// <summary>
  327. /// Gets or sets the number of bytes that have been read from the <see cref="Socket"/>.
  328. /// </summary>
  329. /// <value>
  330. /// The number of bytes that have been read from the <see cref="Socket"/>.
  331. /// </value>
  332. public int BytesRead { get; set; }
  333. /// <summary>
  334. /// Gets the total number of bytes to read from the <see cref="Socket"/>.
  335. /// </summary>
  336. /// <value>
  337. /// The total number of bytes to read from the <see cref="Socket"/>.
  338. /// </value>
  339. public int TotalBytesToRead { get; private set; }
  340. /// <summary>
  341. /// Gets or sets the buffer to hold the bytes that have been read.
  342. /// </summary>
  343. /// <value>
  344. /// The buffer to hold the bytes that have been read.
  345. /// </value>
  346. public byte[] Buffer { get; private set; }
  347. /// <summary>
  348. /// Gets or sets the exception that was thrown while reading from the
  349. /// <see cref="Socket"/>.
  350. /// </summary>
  351. /// <value>
  352. /// The exception that was thrown while reading from the <see cref="Socket"/>,
  353. /// or <c>null</c> if no exception was thrown.
  354. /// </value>
  355. public Exception Exception { get; private set; }
  356. /// <summary>
  357. /// Signals that the total number of bytes has been read successfully.
  358. /// </summary>
  359. public void Complete()
  360. {
  361. _result = SocketReadResult.Complete;
  362. _socketReadComplete.Set();
  363. }
  364. /// <summary>
  365. /// Signals that the socket read failed.
  366. /// </summary>
  367. /// <param name="cause">The <see cref="Exception"/> that caused the read to fail.</param>
  368. public void Fail(Exception cause)
  369. {
  370. Exception = cause;
  371. _result = SocketReadResult.Failed;
  372. _socketReadComplete.Set();
  373. }
  374. /// <summary>
  375. /// Signals that the connection to the server was lost.
  376. /// </summary>
  377. public void ConnectionLost()
  378. {
  379. _result = SocketReadResult.ConnectionLost;
  380. _socketReadComplete.Set();
  381. }
  382. public SocketReadResult Wait()
  383. {
  384. _socketReadComplete.WaitOne();
  385. _socketReadComplete.Dispose();
  386. _socketReadComplete = null;
  387. return _result;
  388. }
  389. }
  390. private enum SocketReadResult
  391. {
  392. Complete,
  393. ConnectionLost,
  394. Failed
  395. }
  396. #endif
  397. }
  398. }