You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

662 lines
24 KiB
C#

using System;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Transport;
namespace Renci.SshNet.Abstractions
{
internal static class SocketAbstraction
{
public static bool CanRead(Socket socket)
{
if (socket.Connected)
{
#if FEATURE_SOCKET_POLL
return socket.Poll(-1, SelectMode.SelectRead) && socket.Available > 0;
#else
return true;
#endif // FEATURE_SOCKET_POLL
}
return false;
}
/// <summary>
/// Returns a value indicating whether the specified <see cref="Socket"/> can be used
/// to send data.
/// </summary>
/// <param name="socket">The <see cref="Socket"/> to check.</param>
/// <returns>
/// <c>true</c> if <paramref name="socket"/> can be written to; otherwise, <c>false</c>.
/// </returns>
public static bool CanWrite(Socket socket)
{
if (socket != null && socket.Connected)
{
#if FEATURE_SOCKET_POLL
return socket.Poll(-1, SelectMode.SelectWrite);
#else
return true;
#endif // FEATURE_SOCKET_POLL
}
return false;
}
public static Socket Connect(IPEndPoint remoteEndpoint, TimeSpan connectTimeout)
{
var socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) {NoDelay = true};
#if FEATURE_SOCKET_EAP
var connectCompleted = new ManualResetEvent(false);
var args = new SocketAsyncEventArgs
{
UserToken = connectCompleted,
RemoteEndPoint = remoteEndpoint
};
args.Completed += ConnectCompleted;
if (socket.ConnectAsync(args))
{
if (!connectCompleted.WaitOne(connectTimeout))
{
// avoid ObjectDisposedException in ConnectCompleted
args.Completed -= ConnectCompleted;
// dispose Socket
socket.Dispose();
// dispose ManualResetEvent
connectCompleted.Dispose();
// dispose SocketAsyncEventArgs
args.Dispose();
throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
"Connection failed to establish within {0:F0} milliseconds.",
connectTimeout.TotalMilliseconds));
}
}
// dispose ManualResetEvent
connectCompleted.Dispose();
if (args.SocketError != SocketError.Success)
{
var socketError = (int) args.SocketError;
// dispose Socket
socket.Dispose();
// dispose SocketAsyncEventArgs
args.Dispose();
throw new SocketException(socketError);
}
// dispose SocketAsyncEventArgs
args.Dispose();
return socket;
#elif FEATURE_SOCKET_APM
var connectResult = socket.BeginConnect(remoteEndpoint, null, null);
if (!connectResult.AsyncWaitHandle.WaitOne(connectTimeout, false))
throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
"Connection failed to establish within {0:F0} milliseconds.", connectTimeout.TotalMilliseconds));
socket.EndConnect(connectResult);
return socket;
#elif FEATURE_SOCKET_TAP
if (!socket.ConnectAsync(remoteEndpoint).Wait(connectTimeout))
throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
"Connection failed to establish within {0:F0} milliseconds.", connectTimeout.TotalMilliseconds));
return socket;
#else
#error Connecting to a remote endpoint is not implemented.
#endif
}
public static void ClearReadBuffer(Socket socket)
{
var timeout = TimeSpan.FromMilliseconds(500);
var buffer = new byte[256];
int bytesReceived;
do
{
bytesReceived = ReadPartial(socket, buffer, 0, buffer.Length, timeout);
}
while (bytesReceived > 0);
}
public static int ReadPartial(Socket socket, byte[] buffer, int offset, int size, TimeSpan timeout)
{
#if FEATURE_SOCKET_SYNC
socket.ReceiveTimeout = (int) timeout.TotalMilliseconds;
try
{
return socket.Receive(buffer, offset, size, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.TimedOut)
throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
"Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
throw;
}
#elif FEATURE_SOCKET_EAP
var receiveCompleted = new ManualResetEvent(false);
var sendReceiveToken = new PartialSendReceiveToken(socket, receiveCompleted);
var args = new SocketAsyncEventArgs
{
RemoteEndPoint = socket.RemoteEndPoint,
UserToken = sendReceiveToken
};
args.Completed += ReceiveCompleted;
args.SetBuffer(buffer, offset, size);
try
{
if (socket.ReceiveAsync(args))
{
if (!receiveCompleted.WaitOne(timeout))
throw new SshOperationTimeoutException(
string.Format(
CultureInfo.InvariantCulture,
"Socket read operation has timed out after {0:F0} milliseconds.",
timeout.TotalMilliseconds));
}
if (args.SocketError != SocketError.Success)
throw new SocketException((int) args.SocketError);
return args.BytesTransferred;
}
finally
{
// initialize token to avoid the waithandle getting used after it's disposed
args.UserToken = null;
args.Dispose();
receiveCompleted.Dispose();
}
#else
#error Receiving data from a Socket is not implemented.
#endif
}
public static void ReadContinuous(Socket socket, byte[] buffer, int offset, int size, Action<byte[], int, int> processReceivedBytesAction)
{
#if FEATURE_SOCKET_SYNC
// do not time-out receive
socket.ReceiveTimeout = 0;
while (socket.Connected)
{
try
{
var bytesRead = socket.Receive(buffer, offset, size, SocketFlags.None);
if (bytesRead == 0)
break;
processReceivedBytesAction(buffer, offset, bytesRead);
}
catch (SocketException ex)
{
if (IsErrorResumable(ex.SocketErrorCode))
continue;
switch (ex.SocketErrorCode)
{
case SocketError.ConnectionAborted:
case SocketError.ConnectionReset:
// connection was closed
return;
case SocketError.Interrupted:
// connection was closed because FIN/ACK was not received in time after
// shutting down the (send part of the) socket
return;
default:
throw; // throw any other error
}
}
}
#elif FEATURE_SOCKET_EAP
var completionWaitHandle = new ManualResetEvent(false);
var readToken = new ContinuousReceiveToken(socket, processReceivedBytesAction, completionWaitHandle);
var args = new SocketAsyncEventArgs
{
RemoteEndPoint = socket.RemoteEndPoint,
UserToken = readToken
};
args.Completed += ReceiveCompleted;
args.SetBuffer(buffer, offset, size);
if (!socket.ReceiveAsync(args))
{
ReceiveCompleted(null, args);
}
completionWaitHandle.WaitOne();
completionWaitHandle.Dispose();
if (readToken.Exception != null)
throw readToken.Exception;
#else
#error Receiving data from a Socket is not implemented.
#endif
}
/// <summary>
/// Reads a byte from the specified <see cref="Socket"/>.
/// </summary>
/// <param name="socket">The <see cref="Socket"/> to read from.</param>
/// <param name="timeout">Specifies the amount of time after which the call will time out.</param>
/// <returns>
/// The byte read, or <c>-1</c> if the socket was closed.
/// </returns>
/// <exception cref="SshOperationTimeoutException">The read operation timed out.</exception>
/// <exception cref="SocketException">The read failed.</exception>
public static int ReadByte(Socket socket, TimeSpan timeout)
{
var buffer = new byte[1];
if (Read(socket, buffer, 0, 1, timeout) == 0)
return -1;
return buffer[0];
}
/// <summary>
/// Sends a byte using the specified <see cref="Socket"/>.
/// </summary>
/// <param name="socket">The <see cref="Socket"/> to write to.</param>
/// <param name="value">The value to send.</param>
/// <exception cref="SocketException">The write failed.</exception>
public static void SendByte(Socket socket, byte value)
{
var buffer = new[] {value};
Send(socket, buffer, 0, 1);
}
/// <summary>
/// Receives data from a bound <see cref="Socket"/>into a receive buffer.
/// </summary>
/// <param name="socket"></param>
/// <param name="buffer">An array of type <see cref="byte"/> that is the storage location for the received data. </param>
/// <param name="offset">The position in <paramref name="buffer"/> parameter to store the received data.</param>
/// <param name="size">The number of bytes to receive.</param>
/// <param name="timeout">Specifies the amount of time after which the call will time out.</param>
/// <returns>
/// The number of bytes received.
/// </returns>
/// <remarks>
/// If no data is available for reading, the <see cref="Read(Socket,byte[], int, int, TimeSpan)"/> method will
/// block until data is available or the time-out value was exceeded. If the time-out value was exceeded, the
/// <see cref="Read(Socket,byte[], int, int, TimeSpan)"/> call will throw a <see cref="SshOperationTimeoutException"/>.
/// If you are in non-blocking mode, and there is no data available in the in the protocol stack buffer, the
/// <see cref="Read(Socket,byte[], int, int, TimeSpan)"/> method will complete immediately and throw a <see cref="SocketException"/>.
/// </remarks>
public static int Read(Socket socket, byte[] buffer, int offset, int size, TimeSpan timeout)
{
#if FEATURE_SOCKET_SYNC
var totalBytesRead = 0;
var totalBytesToRead = size;
socket.ReceiveTimeout = (int) timeout.TotalMilliseconds;
do
{
try
{
var bytesRead = socket.Receive(buffer, offset + totalBytesRead, totalBytesToRead - totalBytesRead, SocketFlags.None);
if (bytesRead == 0)
return 0;
totalBytesRead += bytesRead;
}
catch (SocketException ex)
{
if (IsErrorResumable(ex.SocketErrorCode))
{
ThreadAbstraction.Sleep(30);
continue;
}
if (ex.SocketErrorCode == SocketError.TimedOut)
throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
"Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
throw;
}
}
while (totalBytesRead < totalBytesToRead);
return totalBytesRead;
#elif FEATURE_SOCKET_EAP
var receiveCompleted = new ManualResetEvent(false);
var sendReceiveToken = new BlockingSendReceiveToken(socket, buffer, offset, size, receiveCompleted);
var args = new SocketAsyncEventArgs
{
UserToken = sendReceiveToken,
RemoteEndPoint = socket.RemoteEndPoint
};
args.Completed += ReceiveCompleted;
args.SetBuffer(buffer, offset, size);
try
{
if (socket.ReceiveAsync(args))
{
if (!receiveCompleted.WaitOne(timeout))
throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
"Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
}
if (args.SocketError != SocketError.Success)
throw new SocketException((int) args.SocketError);
return sendReceiveToken.TotalBytesTransferred;
}
finally
{
// initialize token to avoid the waithandle getting used after it's disposed
args.UserToken = null;
args.Dispose();
receiveCompleted.Dispose();
}
#else
#error Receiving data from a Socket is not implemented.
#endif
}
public static void Send(Socket socket, byte[] data)
{
Send(socket, data, 0, data.Length);
}
public static void Send(Socket socket, byte[] data, int offset, int size)
{
#if FEATURE_SOCKET_SYNC
var totalBytesSent = 0; // how many bytes are already sent
var totalBytesToSend = size;
do
{
try
{
var bytesSent = socket.Send(data, offset + totalBytesSent, totalBytesToSend - totalBytesSent,
SocketFlags.None);
if (bytesSent == 0)
throw new SshConnectionException("An established connection was aborted by the server.",
DisconnectReason.ConnectionLost);
totalBytesSent += bytesSent;
}
catch (SocketException ex)
{
if (IsErrorResumable(ex.SocketErrorCode))
{
// socket buffer is probably full, wait and try again
ThreadAbstraction.Sleep(30);
}
else
throw; // any serious error occurr
}
} while (totalBytesSent < totalBytesToSend);
#elif FEATURE_SOCKET_EAP
var sendCompleted = new ManualResetEvent(false);
var sendReceiveToken = new BlockingSendReceiveToken(socket, data, offset, size, sendCompleted);
var socketAsyncSendArgs = new SocketAsyncEventArgs
{
RemoteEndPoint = socket.RemoteEndPoint,
UserToken = sendReceiveToken
};
socketAsyncSendArgs.SetBuffer(data, offset, size);
socketAsyncSendArgs.Completed += SendCompleted;
try
{
if (socket.SendAsync(socketAsyncSendArgs))
{
if (!sendCompleted.WaitOne())
throw new SocketException((int) SocketError.TimedOut);
}
if (socketAsyncSendArgs.SocketError != SocketError.Success)
throw new SocketException((int) socketAsyncSendArgs.SocketError);
if (sendReceiveToken.TotalBytesTransferred == 0)
throw new SshConnectionException("An established connection was aborted by the server.",
DisconnectReason.ConnectionLost);
}
finally
{
// initialize token to avoid the completion waithandle getting used after it's disposed
socketAsyncSendArgs.UserToken = null;
socketAsyncSendArgs.Dispose();
sendCompleted.Dispose();
}
#else
#error Sending data to a Socket is not implemented.
#endif
}
public static bool IsErrorResumable(SocketError socketError)
{
switch (socketError)
{
case SocketError.WouldBlock:
case SocketError.IOPending:
case SocketError.NoBufferSpaceAvailable:
return true;
default:
return false;
}
}
#if FEATURE_SOCKET_EAP
private static void ConnectCompleted(object sender, SocketAsyncEventArgs e)
{
var eventWaitHandle = (ManualResetEvent) e.UserToken;
if (eventWaitHandle != null)
eventWaitHandle.Set();
}
#endif // FEATURE_SOCKET_EAP
#if FEATURE_SOCKET_EAP && !FEATURE_SOCKET_SYNC
private static void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
{
var sendReceiveToken = (Token) e.UserToken;
if (sendReceiveToken != null)
sendReceiveToken.Process(e);
}
private static void SendCompleted(object sender, SocketAsyncEventArgs e)
{
var sendReceiveToken = (Token) e.UserToken;
if (sendReceiveToken != null)
sendReceiveToken.Process(e);
}
private interface Token
{
void Process(SocketAsyncEventArgs args);
}
private class BlockingSendReceiveToken : Token
{
public BlockingSendReceiveToken(Socket socket, byte[] buffer, int offset, int size, EventWaitHandle completionWaitHandle)
{
_socket = socket;
_buffer = buffer;
_offset = offset;
_bytesToTransfer = size;
_completionWaitHandle = completionWaitHandle;
}
public void Process(SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
TotalBytesTransferred += args.BytesTransferred;
if (TotalBytesTransferred == _bytesToTransfer)
{
// finished transferring specified bytes
_completionWaitHandle.Set();
return;
}
if (args.BytesTransferred == 0)
{
// remote server closed the connection
_completionWaitHandle.Set();
return;
}
_offset += args.BytesTransferred;
args.SetBuffer(_buffer, _offset, _bytesToTransfer - TotalBytesTransferred);
ResumeOperation(args);
return;
}
if (IsErrorResumable(args.SocketError))
{
ThreadAbstraction.Sleep(30);
ResumeOperation(args);
return;
}
// we're dealing with a (fatal) error
_completionWaitHandle.Set();
}
private void ResumeOperation(SocketAsyncEventArgs args)
{
switch (args.LastOperation)
{
case SocketAsyncOperation.Receive:
_socket.ReceiveAsync(args);
break;
case SocketAsyncOperation.Send:
_socket.SendAsync(args);
break;
}
}
private readonly int _bytesToTransfer;
public int TotalBytesTransferred { get; private set; }
private readonly EventWaitHandle _completionWaitHandle;
private readonly Socket _socket;
private readonly byte[] _buffer;
private int _offset;
}
private class PartialSendReceiveToken : Token
{
public PartialSendReceiveToken(Socket socket, EventWaitHandle completionWaitHandle)
{
_socket = socket;
_completionWaitHandle = completionWaitHandle;
}
public void Process(SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
_completionWaitHandle.Set();
return;
}
if (IsErrorResumable(args.SocketError))
{
ThreadAbstraction.Sleep(30);
ResumeOperation(args);
return;
}
// we're dealing with a (fatal) error
_completionWaitHandle.Set();
}
private void ResumeOperation(SocketAsyncEventArgs args)
{
switch (args.LastOperation)
{
case SocketAsyncOperation.Receive:
_socket.ReceiveAsync(args);
break;
case SocketAsyncOperation.Send:
_socket.SendAsync(args);
break;
}
}
private readonly EventWaitHandle _completionWaitHandle;
private readonly Socket _socket;
}
private class ContinuousReceiveToken : Token
{
public ContinuousReceiveToken(Socket socket, Action<byte[], int, int> processReceivedBytesAction, EventWaitHandle completionWaitHandle)
{
_socket = socket;
_processReceivedBytesAction = processReceivedBytesAction;
_completionWaitHandle = completionWaitHandle;
}
public Exception Exception { get; private set; }
public void Process(SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
if (args.BytesTransferred == 0)
{
// remote socket was closed
_completionWaitHandle.Set();
return;
}
_processReceivedBytesAction(args.Buffer, args.Offset, args.BytesTransferred);
ResumeOperation(args);
return;
}
if (IsErrorResumable(args.SocketError))
{
ThreadAbstraction.Sleep(30);
ResumeOperation(args);
return;
}
if (args.SocketError != SocketError.OperationAborted)
{
Exception = new SocketException((int) args.SocketError);
}
// we're dealing with a (fatal) error
_completionWaitHandle.Set();
}
private void ResumeOperation(SocketAsyncEventArgs args)
{
switch (args.LastOperation)
{
case SocketAsyncOperation.Receive:
_socket.ReceiveAsync(args);
break;
case SocketAsyncOperation.Send:
_socket.SendAsync(args);
break;
}
}
private readonly EventWaitHandle _completionWaitHandle;
private readonly Socket _socket;
private readonly Action<byte[], int, int> _processReceivedBytesAction;
}
#endif // FEATURE_SOCKET_EAP && !FEATURE_SOCKET_SYNC
}
}