SocketAbstraction.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. using System;
  2. using System.Globalization;
  3. using System.Net;
  4. using System.Net.Sockets;
  5. using System.Threading;
  6. using Renci.SshNet.Common;
  7. using Renci.SshNet.Messages.Transport;
  8. namespace Renci.SshNet.Abstractions
  9. {
  10. internal static class SocketAbstraction
  11. {
  12. public static bool CanRead(Socket socket)
  13. {
  14. if (socket.Connected)
  15. {
  16. #if FEATURE_SOCKET_POLL
  17. return socket.Poll(-1, SelectMode.SelectRead) && socket.Available > 0;
  18. #else
  19. return true;
  20. #endif // FEATURE_SOCKET_POLL
  21. }
  22. return false;
  23. }
  24. public static bool CanWrite(Socket socket)
  25. {
  26. if (socket.Connected)
  27. {
  28. #if FEATURE_SOCKET_POLL
  29. return socket.Poll(-1, SelectMode.SelectWrite);
  30. #else
  31. return true;
  32. #endif // FEATURE_SOCKET_POLL
  33. }
  34. return false;
  35. }
  36. public static Socket Connect(IPEndPoint remoteEndpoint, TimeSpan connectTimeout)
  37. {
  38. var socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) {NoDelay = true};
  39. #if FEATURE_SOCKET_EAP
  40. var connectCompleted = new ManualResetEvent(false);
  41. var args = new SocketAsyncEventArgs
  42. {
  43. UserToken = connectCompleted,
  44. RemoteEndPoint = remoteEndpoint
  45. };
  46. args.Completed += ConnectCompleted;
  47. if (socket.ConnectAsync(args))
  48. {
  49. if (!connectCompleted.WaitOne(connectTimeout))
  50. throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
  51. "Connection failed to establish within {0:F0} milliseconds.", connectTimeout.TotalMilliseconds));
  52. }
  53. if (args.SocketError != SocketError.Success)
  54. throw new SocketException((int) args.SocketError);
  55. return socket;
  56. #elif FEATURE_SOCKET_APM
  57. var connectResult = socket.BeginConnect(remoteEndpoint, null, null);
  58. if (!connectResult.AsyncWaitHandle.WaitOne(connectTimeout, false))
  59. throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
  60. "Connection failed to establish within {0:F0} milliseconds.", connectTimeout.TotalMilliseconds));
  61. socket.EndConnect(connectResult);
  62. return socket;
  63. #elif FEATURE_SOCKET_TAP
  64. if (!socket.ConnectAsync(remoteEndpoint).Wait(connectTimeout))
  65. throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
  66. "Connection failed to establish within {0:F0} milliseconds.", connectTimeout.TotalMilliseconds));
  67. return socket;
  68. #else
  69. #error Connecting to a remote endpoint is not implemented.
  70. #endif
  71. }
  72. public static void ClearReadBuffer(Socket socket)
  73. {
  74. var timeout = TimeSpan.FromMilliseconds(500);
  75. var buffer = new byte[256];
  76. int bytesReceived;
  77. do
  78. {
  79. bytesReceived = ReadPartial(socket, buffer, 0, buffer.Length, timeout);
  80. }
  81. while (bytesReceived > 0);
  82. }
  83. public static int ReadPartial(Socket socket, byte[] buffer, int offset, int size, TimeSpan timeout)
  84. {
  85. #if FEATURE_SOCKET_SYNC
  86. socket.ReceiveTimeout = (int) timeout.TotalMilliseconds;
  87. try
  88. {
  89. return socket.Receive(buffer, offset, size, SocketFlags.None);
  90. }
  91. catch (SocketException ex)
  92. {
  93. if (ex.SocketErrorCode == SocketError.TimedOut)
  94. throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
  95. "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
  96. throw;
  97. }
  98. #elif FEATURE_SOCKET_EAP
  99. var receiveCompleted = new ManualResetEvent(false);
  100. var sendReceiveToken = new PartialSendReceiveToken(socket, receiveCompleted);
  101. var args = new SocketAsyncEventArgs
  102. {
  103. RemoteEndPoint = socket.RemoteEndPoint,
  104. UserToken = sendReceiveToken
  105. };
  106. args.Completed += ReceiveCompleted;
  107. args.SetBuffer(buffer, offset, size);
  108. try
  109. {
  110. if (socket.ReceiveAsync(args))
  111. {
  112. if (!receiveCompleted.WaitOne(timeout))
  113. throw new SshOperationTimeoutException(
  114. string.Format(
  115. CultureInfo.InvariantCulture,
  116. "Socket read operation has timed out after {0:F0} milliseconds.",
  117. timeout.TotalMilliseconds));
  118. }
  119. if (args.SocketError != SocketError.Success)
  120. throw new SocketException((int) args.SocketError);
  121. return args.BytesTransferred;
  122. }
  123. finally
  124. {
  125. // initialize token to avoid the waithandle getting used after it's disposed
  126. args.UserToken = null;
  127. args.Dispose();
  128. receiveCompleted.Dispose();
  129. }
  130. #else
  131. #error Receiving data from a Socket is not implemented.
  132. #endif
  133. }
  134. public static void ReadContinuous(Socket socket, byte[] buffer, int offset, int size, Action<byte[], int, int> processReceivedBytesAction)
  135. {
  136. #if FEATURE_SOCKET_SYNC
  137. // do not time-out receive
  138. socket.ReceiveTimeout = 0;
  139. while (socket.Connected)
  140. {
  141. try
  142. {
  143. var bytesRead = socket.Receive(buffer, offset, size, SocketFlags.None);
  144. if (bytesRead == 0)
  145. break;
  146. processReceivedBytesAction(buffer, offset, bytesRead);
  147. }
  148. catch (SocketException ex)
  149. {
  150. if (IsErrorResumable(ex.SocketErrorCode))
  151. continue;
  152. switch (ex.SocketErrorCode)
  153. {
  154. case SocketError.ConnectionAborted:
  155. case SocketError.ConnectionReset:
  156. // connection was closed
  157. return;
  158. case SocketError.Interrupted:
  159. // connection was closed because FIN/ACK was not received in time after
  160. // shutting down the (send part of the) socket
  161. return;
  162. default:
  163. throw; // throw any other error
  164. }
  165. }
  166. }
  167. #elif FEATURE_SOCKET_EAP
  168. var completionWaitHandle = new ManualResetEvent(false);
  169. var readToken = new ContinuousReceiveToken(socket, processReceivedBytesAction, completionWaitHandle);
  170. var args = new SocketAsyncEventArgs
  171. {
  172. RemoteEndPoint = socket.RemoteEndPoint,
  173. UserToken = readToken
  174. };
  175. args.Completed += ReceiveCompleted;
  176. args.SetBuffer(buffer, offset, size);
  177. if (!socket.ReceiveAsync(args))
  178. {
  179. ReceiveCompleted(null, args);
  180. }
  181. completionWaitHandle.WaitOne();
  182. completionWaitHandle.Dispose();
  183. if (readToken.Exception != null)
  184. throw readToken.Exception;
  185. #else
  186. #error Receiving data from a Socket is not implemented.
  187. #endif
  188. }
  189. /// <summary>
  190. /// Reads a byte from the specified <see cref="Socket"/>.
  191. /// </summary>
  192. /// <param name="socket">The <see cref="Socket"/> to read from.</param>
  193. /// <param name="timeout">Specifies the amount of time after which the call will time out.</param>
  194. /// <returns>
  195. /// The byte read, or <c>-1</c> if the socket was closed.
  196. /// </returns>
  197. /// <exception cref="SshOperationTimeoutException">The read operation timed out.</exception>
  198. /// <exception cref="SocketException">The read failed.</exception>
  199. public static int ReadByte(Socket socket, TimeSpan timeout)
  200. {
  201. var buffer = new byte[1];
  202. if (Read(socket, buffer, 0, 1, timeout) == 0)
  203. return -1;
  204. return buffer[0];
  205. }
  206. /// <summary>
  207. /// Sends a byte using the specified <see cref="Socket"/>.
  208. /// </summary>
  209. /// <param name="socket">The <see cref="Socket"/> to write to.</param>
  210. /// <param name="value">The value to send.</param>
  211. /// <exception cref="SocketException">The write failed.</exception>
  212. public static void SendByte(Socket socket, byte value)
  213. {
  214. var buffer = new[] {value};
  215. Send(socket, buffer, 0, 1);
  216. }
  217. /// <summary>
  218. /// Receives data from a bound <see cref="Socket"/>into a receive buffer.
  219. /// </summary>
  220. /// <param name="socket"></param>
  221. /// <param name="buffer">An array of type <see cref="byte"/> that is the storage location for the received data. </param>
  222. /// <param name="offset">The position in <paramref name="buffer"/> parameter to store the received data.</param>
  223. /// <param name="size">The number of bytes to receive.</param>
  224. /// <param name="timeout">Specifies the amount of time after which the call will time out.</param>
  225. /// <returns>
  226. /// The number of bytes received.
  227. /// </returns>
  228. /// <remarks>
  229. /// If no data is available for reading, the <see cref="Read(Socket,byte[], int, int, TimeSpan)"/> method will
  230. /// block until data is available or the time-out value was exceeded. If the time-out value was exceeded, the
  231. /// <see cref="Read(Socket,byte[], int, int, TimeSpan)"/> call will throw a <see cref="SshOperationTimeoutException"/>.
  232. /// If you are in non-blocking mode, and there is no data available in the in the protocol stack buffer, the
  233. /// <see cref="Read(Socket,byte[], int, int, TimeSpan)"/> method will complete immediately and throw a <see cref="SocketException"/>.
  234. /// </remarks>
  235. public static int Read(Socket socket, byte[] buffer, int offset, int size, TimeSpan timeout)
  236. {
  237. #if FEATURE_SOCKET_SYNC
  238. var totalBytesRead = 0;
  239. var totalBytesToRead = size;
  240. socket.ReceiveTimeout = (int) timeout.TotalMilliseconds;
  241. do
  242. {
  243. try
  244. {
  245. var bytesRead = socket.Receive(buffer, offset + totalBytesRead, totalBytesToRead - totalBytesRead, SocketFlags.None);
  246. if (bytesRead == 0)
  247. return 0;
  248. totalBytesRead += bytesRead;
  249. }
  250. catch (SocketException ex)
  251. {
  252. if (IsErrorResumable(ex.SocketErrorCode))
  253. {
  254. ThreadAbstraction.Sleep(30);
  255. continue;
  256. }
  257. if (ex.SocketErrorCode == SocketError.TimedOut)
  258. throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
  259. "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
  260. throw;
  261. }
  262. }
  263. while (totalBytesRead < totalBytesToRead);
  264. return totalBytesRead;
  265. #elif FEATURE_SOCKET_EAP
  266. var receiveCompleted = new ManualResetEvent(false);
  267. var sendReceiveToken = new BlockingSendReceiveToken(socket, buffer, offset, size, receiveCompleted);
  268. var args = new SocketAsyncEventArgs
  269. {
  270. UserToken = sendReceiveToken,
  271. RemoteEndPoint = socket.RemoteEndPoint
  272. };
  273. args.Completed += ReceiveCompleted;
  274. args.SetBuffer(buffer, offset, size);
  275. try
  276. {
  277. if (socket.ReceiveAsync(args))
  278. {
  279. if (!receiveCompleted.WaitOne(timeout))
  280. throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
  281. "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
  282. }
  283. if (args.SocketError != SocketError.Success)
  284. throw new SocketException((int) args.SocketError);
  285. return sendReceiveToken.TotalBytesTransferred;
  286. }
  287. finally
  288. {
  289. // initialize token to avoid the waithandle getting used after it's disposed
  290. args.UserToken = null;
  291. args.Dispose();
  292. receiveCompleted.Dispose();
  293. }
  294. #else
  295. #error Receiving data from a Socket is not implemented.
  296. #endif
  297. }
  298. public static void Send(Socket socket, byte[] data)
  299. {
  300. Send(socket, data, 0, data.Length);
  301. }
  302. public static void Send(Socket socket, byte[] data, int offset, int size)
  303. {
  304. #if FEATURE_SOCKET_SYNC
  305. var totalBytesSent = 0; // how many bytes are already sent
  306. var totalBytesToSend = size;
  307. do
  308. {
  309. try
  310. {
  311. var bytesSent = socket.Send(data, offset + totalBytesSent, totalBytesToSend - totalBytesSent,
  312. SocketFlags.None);
  313. if (bytesSent == 0)
  314. throw new SshConnectionException("An established connection was aborted by the server.",
  315. DisconnectReason.ConnectionLost);
  316. totalBytesSent += bytesSent;
  317. }
  318. catch (SocketException ex)
  319. {
  320. if (IsErrorResumable(ex.SocketErrorCode))
  321. {
  322. // socket buffer is probably full, wait and try again
  323. ThreadAbstraction.Sleep(30);
  324. }
  325. else
  326. throw; // any serious error occurr
  327. }
  328. } while (totalBytesSent < totalBytesToSend);
  329. #elif FEATURE_SOCKET_EAP
  330. var sendCompleted = new ManualResetEvent(false);
  331. var sendReceiveToken = new BlockingSendReceiveToken(socket, data, offset, size, sendCompleted);
  332. var socketAsyncSendArgs = new SocketAsyncEventArgs
  333. {
  334. RemoteEndPoint = socket.RemoteEndPoint,
  335. UserToken = sendReceiveToken
  336. };
  337. socketAsyncSendArgs.SetBuffer(data, offset, size);
  338. socketAsyncSendArgs.Completed += SendCompleted;
  339. try
  340. {
  341. if (socket.SendAsync(socketAsyncSendArgs))
  342. {
  343. if (!sendCompleted.WaitOne())
  344. throw new SocketException((int) SocketError.TimedOut);
  345. }
  346. if (socketAsyncSendArgs.SocketError != SocketError.Success)
  347. throw new SocketException((int) socketAsyncSendArgs.SocketError);
  348. if (sendReceiveToken.TotalBytesTransferred == 0)
  349. throw new SshConnectionException("An established connection was aborted by the server.",
  350. DisconnectReason.ConnectionLost);
  351. }
  352. finally
  353. {
  354. // initialize token to avoid the completion waithandle getting used after it's disposed
  355. socketAsyncSendArgs.UserToken = null;
  356. socketAsyncSendArgs.Dispose();
  357. sendCompleted.Dispose();
  358. }
  359. #else
  360. #error Sending data to a Socket is not implemented.
  361. #endif
  362. }
  363. public static bool IsErrorResumable(SocketError socketError)
  364. {
  365. switch (socketError)
  366. {
  367. case SocketError.WouldBlock:
  368. case SocketError.IOPending:
  369. case SocketError.NoBufferSpaceAvailable:
  370. return true;
  371. default:
  372. return false;
  373. }
  374. }
  375. #if FEATURE_SOCKET_EAP
  376. private static void ConnectCompleted(object sender, SocketAsyncEventArgs e)
  377. {
  378. var eventWaitHandle = (ManualResetEvent) e.UserToken;
  379. if (eventWaitHandle != null)
  380. eventWaitHandle.Set();
  381. }
  382. #endif // FEATURE_SOCKET_EAP
  383. #if FEATURE_SOCKET_EAP && !FEATURE_SOCKET_SYNC
  384. private static void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
  385. {
  386. var sendReceiveToken = (Token) e.UserToken;
  387. if (sendReceiveToken != null)
  388. sendReceiveToken.Process(e);
  389. }
  390. private static void SendCompleted(object sender, SocketAsyncEventArgs e)
  391. {
  392. var sendReceiveToken = (Token) e.UserToken;
  393. if (sendReceiveToken != null)
  394. sendReceiveToken.Process(e);
  395. }
  396. private interface Token
  397. {
  398. void Process(SocketAsyncEventArgs args);
  399. }
  400. private class BlockingSendReceiveToken : Token
  401. {
  402. public BlockingSendReceiveToken(Socket socket, byte[] buffer, int offset, int size, EventWaitHandle completionWaitHandle)
  403. {
  404. _socket = socket;
  405. _buffer = buffer;
  406. _offset = offset;
  407. _bytesToTransfer = size;
  408. _completionWaitHandle = completionWaitHandle;
  409. }
  410. public void Process(SocketAsyncEventArgs args)
  411. {
  412. if (args.SocketError == SocketError.Success)
  413. {
  414. TotalBytesTransferred += args.BytesTransferred;
  415. if (TotalBytesTransferred == _bytesToTransfer)
  416. {
  417. // finished transferring specified bytes
  418. _completionWaitHandle.Set();
  419. return;
  420. }
  421. if (args.BytesTransferred == 0)
  422. {
  423. // remote server closed the connection
  424. _completionWaitHandle.Set();
  425. return;
  426. }
  427. _offset += args.BytesTransferred;
  428. args.SetBuffer(_buffer, _offset, _bytesToTransfer - TotalBytesTransferred);
  429. ResumeOperation(args);
  430. return;
  431. }
  432. if (IsErrorResumable(args.SocketError))
  433. {
  434. ThreadAbstraction.Sleep(30);
  435. ResumeOperation(args);
  436. return;
  437. }
  438. // we're dealing with a (fatal) error
  439. _completionWaitHandle.Set();
  440. }
  441. private void ResumeOperation(SocketAsyncEventArgs args)
  442. {
  443. switch (args.LastOperation)
  444. {
  445. case SocketAsyncOperation.Receive:
  446. _socket.ReceiveAsync(args);
  447. break;
  448. case SocketAsyncOperation.Send:
  449. _socket.SendAsync(args);
  450. break;
  451. }
  452. }
  453. private readonly int _bytesToTransfer;
  454. public int TotalBytesTransferred { get; private set; }
  455. private readonly EventWaitHandle _completionWaitHandle;
  456. private readonly Socket _socket;
  457. private readonly byte[] _buffer;
  458. private int _offset;
  459. }
  460. private class PartialSendReceiveToken : Token
  461. {
  462. public PartialSendReceiveToken(Socket socket, EventWaitHandle completionWaitHandle)
  463. {
  464. _socket = socket;
  465. _completionWaitHandle = completionWaitHandle;
  466. }
  467. public void Process(SocketAsyncEventArgs args)
  468. {
  469. if (args.SocketError == SocketError.Success)
  470. {
  471. _completionWaitHandle.Set();
  472. return;
  473. }
  474. if (IsErrorResumable(args.SocketError))
  475. {
  476. ThreadAbstraction.Sleep(30);
  477. ResumeOperation(args);
  478. return;
  479. }
  480. // we're dealing with a (fatal) error
  481. _completionWaitHandle.Set();
  482. }
  483. private void ResumeOperation(SocketAsyncEventArgs args)
  484. {
  485. switch (args.LastOperation)
  486. {
  487. case SocketAsyncOperation.Receive:
  488. _socket.ReceiveAsync(args);
  489. break;
  490. case SocketAsyncOperation.Send:
  491. _socket.SendAsync(args);
  492. break;
  493. }
  494. }
  495. private readonly EventWaitHandle _completionWaitHandle;
  496. private readonly Socket _socket;
  497. }
  498. private class ContinuousReceiveToken : Token
  499. {
  500. public ContinuousReceiveToken(Socket socket, Action<byte[], int, int> processReceivedBytesAction, EventWaitHandle completionWaitHandle)
  501. {
  502. _socket = socket;
  503. _processReceivedBytesAction = processReceivedBytesAction;
  504. _completionWaitHandle = completionWaitHandle;
  505. }
  506. public Exception Exception { get; private set; }
  507. public void Process(SocketAsyncEventArgs args)
  508. {
  509. if (args.SocketError == SocketError.Success)
  510. {
  511. if (args.BytesTransferred == 0)
  512. {
  513. // remote socket was closed
  514. _completionWaitHandle.Set();
  515. return;
  516. }
  517. _processReceivedBytesAction(args.Buffer, args.Offset, args.BytesTransferred);
  518. ResumeOperation(args);
  519. return;
  520. }
  521. if (IsErrorResumable(args.SocketError))
  522. {
  523. ThreadAbstraction.Sleep(30);
  524. ResumeOperation(args);
  525. return;
  526. }
  527. if (args.SocketError != SocketError.OperationAborted)
  528. {
  529. Exception = new SocketException((int) args.SocketError);
  530. }
  531. // we're dealing with a (fatal) error
  532. _completionWaitHandle.Set();
  533. }
  534. private void ResumeOperation(SocketAsyncEventArgs args)
  535. {
  536. switch (args.LastOperation)
  537. {
  538. case SocketAsyncOperation.Receive:
  539. _socket.ReceiveAsync(args);
  540. break;
  541. case SocketAsyncOperation.Send:
  542. _socket.SendAsync(args);
  543. break;
  544. }
  545. }
  546. private readonly EventWaitHandle _completionWaitHandle;
  547. private readonly Socket _socket;
  548. private readonly Action<byte[], int, int> _processReceivedBytesAction;
  549. }
  550. #endif // FEATURE_SOCKET_EAP && !FEATURE_SOCKET_SYNC
  551. }
  552. }