|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
namespace System.Net.Sockets
{
internal static partial class SocketPal
{
public const bool SupportsMultipleConnectAttempts = false;
public static readonly int MaximumAddressSize = Interop.Sys.GetMaximumAddressSize();
private static readonly bool SupportsDualModeIPv4PacketInfo = GetPlatformSupportsDualModeIPv4PacketInfo();
private static readonly bool SelectOverPollIsBroken = OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsMacCatalyst();
// IovStackThreshold matches Linux's UIO_FASTIOV, which is the number of 'struct iovec'
// that get stackalloced in the Linux kernel.
private const int IovStackThreshold = 8;
private static bool GetPlatformSupportsDualModeIPv4PacketInfo() =>
Interop.Sys.PlatformSupportsDualModeIPv4PacketInfo() != 0;
public static SocketError GetSocketErrorForErrorCode(Interop.Error errorCode)
{
return SocketErrorPal.GetSocketErrorForNativeError(errorCode);
}
public static void CheckDualModePacketInfoSupport(Socket socket)
{
if (!SupportsDualModeIPv4PacketInfo && socket.AddressFamily == AddressFamily.InterNetworkV6 && socket.DualMode)
{
throw new PlatformNotSupportedException(SR.net_sockets_dualmode_receivefrom_notsupported);
}
}
private static unsafe IPPacketInformation GetIPPacketInformation(Interop.Sys.MessageHeader* messageHeader, bool isIPv4, bool isIPv6)
{
if (!isIPv4 && !isIPv6)
{
return default(IPPacketInformation);
}
Interop.Sys.IPPacketInformation nativePacketInfo = default;
if (!Interop.Sys.TryGetIPPacketInformation(messageHeader, isIPv4, &nativePacketInfo))
{
return default(IPPacketInformation);
}
return new IPPacketInformation(nativePacketInfo.Address.GetIPAddress(), nativePacketInfo.InterfaceIndex);
}
public static unsafe SocketError CreateSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, out SafeSocketHandle socket)
{
socket = new SafeSocketHandle();
IntPtr fd;
SocketError errorCode;
Interop.Error error = Interop.Sys.Socket((int)addressFamily, (int)socketType, (int)protocolType, &fd);
if (error == Interop.Error.SUCCESS)
{
Debug.Assert(fd != (IntPtr)(-1), "fd should not be -1");
errorCode = SocketError.Success;
// The socket was created successfully; enable IPV6_V6ONLY by default for normal AF_INET6 sockets.
// This fails on raw sockets so we just let them be in default state.
// WASI is always IPv6-only when IPv6 is enabled.
if (!OperatingSystem.IsWasi() && addressFamily == AddressFamily.InterNetworkV6 && socketType != SocketType.Raw)
{
int on = 1;
error = Interop.Sys.SetSockOpt(fd, SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, (byte*)&on, sizeof(int));
if (error != Interop.Error.SUCCESS)
{
Interop.Sys.Close(fd);
fd = (IntPtr)(-1);
errorCode = GetSocketErrorForErrorCode(error);
}
}
}
else
{
Debug.Assert(fd == (IntPtr)(-1), $"Unexpected fd: {fd}");
errorCode = GetSocketErrorForErrorCode(error);
}
Marshal.InitHandle(socket, fd);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, socket);
if (socket.IsInvalid)
{
socket.Dispose();
}
return errorCode;
}
private static unsafe int SysRead(SafeSocketHandle handle, Span<byte> buffer, out Interop.Error errno)
{
Debug.Assert(!handle.IsSocket);
int received = 0;
fixed (byte* b = &MemoryMarshal.GetReference(buffer))
{
received = Interop.Sys.Read(handle, b, buffer.Length);
errno = received != -1 ? Interop.Error.SUCCESS : Interop.Sys.GetLastError();
}
return received;
}
private static unsafe int SysReceive(SafeSocketHandle socket, SocketFlags flags, Span<byte> buffer, out Interop.Error errno)
{
Debug.Assert(socket.IsSocket);
int received = 0;
fixed (byte* b = &MemoryMarshal.GetReference(buffer))
{
errno = Interop.Sys.Receive(
socket,
b,
buffer.Length,
flags,
&received);
}
if (errno != Interop.Error.SUCCESS)
{
return -1;
}
return received;
}
private static unsafe int SysReceive(SafeSocketHandle socket, SocketFlags flags, Span<byte> buffer, Span<byte> socketAddress, out int socketAddressLen, out SocketFlags receivedFlags, out Interop.Error errno)
{
Debug.Assert(socket.IsSocket);
long received = 0;
fixed (byte* sockAddr = &MemoryMarshal.GetReference(socketAddress))
fixed (byte* b = &MemoryMarshal.GetReference(buffer))
{
var iov = new Interop.Sys.IOVector {
Base = b,
Count = (UIntPtr)buffer.Length
};
Debug.Assert(socketAddress.Length != 0 || sockAddr == null);
var messageHeader = new Interop.Sys.MessageHeader {
SocketAddress = sockAddr,
SocketAddressLen = socketAddress.Length,
IOVectors = &iov,
IOVectorCount = 1
};
errno = Interop.Sys.ReceiveMessage(
socket,
&messageHeader,
flags,
&received);
receivedFlags = messageHeader.Flags;
socketAddressLen = messageHeader.SocketAddressLen;
}
if (errno != Interop.Error.SUCCESS)
{
return -1;
}
return checked((int)received);
}
private static unsafe int SysWrite(SafeSocketHandle handle, ReadOnlySpan<byte> buffer, ref int offset, ref int count, out Interop.Error errno)
{
Debug.Assert(!handle.IsSocket);
int sent;
fixed (byte* b = &MemoryMarshal.GetReference(buffer))
{
sent = Interop.Sys.Write(handle, b + offset, count);
if (sent == -1)
{
errno = Interop.Sys.GetLastError();
}
else
{
errno = Interop.Error.SUCCESS;
offset += sent;
count -= sent;
}
}
return sent;
}
// The Linux kernel doesn't like it if we pass a null reference for buffer pointers, even if the length is 0.
// Replace any null pointer (e.g. from Memory<byte>.Empty) with a valid pointer.
private static ReadOnlySpan<byte> AvoidNullReference(ReadOnlySpan<byte> buffer) =>
Unsafe.IsNullRef(ref MemoryMarshal.GetReference(buffer)) ? Array.Empty<byte>() : buffer;
private static unsafe int SysSend(SafeSocketHandle socket, SocketFlags flags, ReadOnlySpan<byte> buffer, ref int offset, ref int count, out Interop.Error errno)
{
Debug.Assert(socket.IsSocket);
buffer = AvoidNullReference(buffer);
int sent;
fixed (byte* b = &MemoryMarshal.GetReference(buffer))
{
errno = Interop.Sys.Send(
socket,
b + offset,
count,
flags,
&sent);
}
if (errno != Interop.Error.SUCCESS)
{
return -1;
}
offset += sent;
count -= sent;
return sent;
}
private static unsafe int SysSend(SafeSocketHandle socket, SocketFlags flags, ReadOnlySpan<byte> buffer, ref int offset, ref int count, ReadOnlySpan<byte> socketAddress, out Interop.Error errno)
{
Debug.Assert(socket.IsSocket);
buffer = AvoidNullReference(buffer);
int sent;
fixed (byte* sockAddr = socketAddress)
fixed (byte* b = &MemoryMarshal.GetReference(buffer))
{
var iov = new Interop.Sys.IOVector
{
Base = b + offset,
Count = (UIntPtr)count
};
var messageHeader = new Interop.Sys.MessageHeader
{
SocketAddress = sockAddr,
SocketAddressLen = socketAddress.Length,
IOVectors = &iov,
IOVectorCount = 1
};
long bytesSent = 0;
errno = Interop.Sys.SendMessage(
socket,
&messageHeader,
flags,
&bytesSent);
sent = checked((int)bytesSent);
}
if (errno != Interop.Error.SUCCESS)
{
return -1;
}
offset += sent;
count -= sent;
return sent;
}
private static unsafe int SysSend(SafeSocketHandle socket, SocketFlags flags, IList<ArraySegment<byte>> buffers, ref int bufferIndex, ref int offset, ReadOnlySpan<byte> socketAddress, out Interop.Error errno)
{
Debug.Assert(socket.IsSocket);
// Pin buffers and set up iovecs.
int startIndex = bufferIndex, startOffset = offset;
int maxBuffers = buffers.Count - startIndex;
if (OperatingSystem.IsWasi())
{
// WASI doesn't have iovecs and recvmsg in preview2
maxBuffers = Math.Max(maxBuffers, 1);
}
bool allocOnStack = maxBuffers <= IovStackThreshold;
Span<GCHandle> handles = allocOnStack ? stackalloc GCHandle[IovStackThreshold] : new GCHandle[maxBuffers];
Span<Interop.Sys.IOVector> iovecs = allocOnStack ? stackalloc Interop.Sys.IOVector[IovStackThreshold] : new Interop.Sys.IOVector[maxBuffers];
int sent;
int iovCount = 0;
try
{
for (int i = 0; i < maxBuffers; i++, startOffset = 0)
{
ArraySegment<byte> buffer = buffers[startIndex + i];
RangeValidationHelpers.ValidateSegment(buffer);
handles[i] = GCHandle.Alloc(buffer.Array, GCHandleType.Pinned);
iovCount++;
iovecs[i].Base = &((byte*)handles[i].AddrOfPinnedObject())[buffer.Offset + startOffset];
iovecs[i].Count = (UIntPtr)(buffer.Count - startOffset);
}
// Make the call
fixed (byte* sockAddr = socketAddress)
fixed (Interop.Sys.IOVector* iov = iovecs)
{
var messageHeader = new Interop.Sys.MessageHeader {
SocketAddress = sockAddr,
SocketAddressLen = socketAddress.Length,
IOVectors = iov,
IOVectorCount = iovCount
};
long bytesSent = 0;
errno = Interop.Sys.SendMessage(
socket,
&messageHeader,
flags,
&bytesSent);
sent = checked((int)bytesSent);
}
}
finally
{
// Free GC handles.
for (int i = 0; i < iovCount; i++)
{
handles[i].Free();
}
}
if (errno != Interop.Error.SUCCESS)
{
return -1;
}
// Update position.
int endIndex = bufferIndex, endOffset = offset, unconsumed = sent;
for (; endIndex < buffers.Count && unconsumed > 0; endIndex++, endOffset = 0)
{
int space = buffers[endIndex].Count - endOffset;
if (space > unconsumed)
{
endOffset += unconsumed;
break;
}
unconsumed -= space;
}
bufferIndex = endIndex;
offset = endOffset;
return sent;
}
private static unsafe long SendFile(SafeSocketHandle socket, SafeFileHandle fileHandle, ref long offset, ref long count, out Interop.Error errno)
{
long bytesSent;
errno = Interop.Sys.SendFile(socket, fileHandle, offset, count, out bytesSent);
offset += bytesSent;
count -= bytesSent;
return bytesSent;
}
private static unsafe int SysReceive(SafeSocketHandle socket, SocketFlags flags, IList<ArraySegment<byte>> buffers, Span<byte> socketAddress, out int socketAddressLen, out SocketFlags receivedFlags, out Interop.Error errno)
{
Debug.Assert(socket.IsSocket);
int maxBuffers = buffers.Count;
if (OperatingSystem.IsWasi())
{
// WASI doesn't have iovecs and recvmsg in preview2
maxBuffers = Math.Max(maxBuffers, 1);
}
bool allocOnStack = maxBuffers <= IovStackThreshold;
// When there are many buffers, reduce the number of pinned buffers based on available bytes.
int available = int.MaxValue;
if (!allocOnStack)
{
errno = Interop.Sys.GetBytesAvailable(socket, &available);
if (errno != Interop.Error.SUCCESS)
{
receivedFlags = 0;
socketAddressLen = 0;
return -1;
}
if (available == 0)
{
// Don't truncate iovecs.
available = int.MaxValue;
}
}
// Pin buffers and set up iovecs.
Span<GCHandle> handles = allocOnStack ? stackalloc GCHandle[IovStackThreshold] : new GCHandle[maxBuffers];
Span<Interop.Sys.IOVector> iovecs = allocOnStack ? stackalloc Interop.Sys.IOVector[IovStackThreshold] : new Interop.Sys.IOVector[maxBuffers];
int sockAddrLen = socketAddress.Length;
long received = 0;
int toReceive = 0, iovCount = 0;
try
{
for (int i = 0; i < maxBuffers; i++)
{
ArraySegment<byte> buffer = buffers[i];
RangeValidationHelpers.ValidateSegment(buffer);
int bufferCount = buffer.Count;
handles[i] = GCHandle.Alloc(buffer.Array, GCHandleType.Pinned);
iovCount++;
iovecs[i].Base = &((byte*)handles[i].AddrOfPinnedObject())[buffer.Offset];
iovecs[i].Count = (UIntPtr)bufferCount;
toReceive += bufferCount;
if (toReceive >= available)
{
for (int j = i + 1; j < maxBuffers; j++)
{
// We're not going to use these extra buffers, but validate their args
// to alert the dev to a mistake and to be consistent with Windows.
RangeValidationHelpers.ValidateSegment(buffers[j]);
}
break;
}
}
// Make the call.
fixed (byte* sockAddr = socketAddress)
fixed (Interop.Sys.IOVector* iov = iovecs)
{
var messageHeader = new Interop.Sys.MessageHeader {
SocketAddress = sockAddr,
SocketAddressLen = sockAddrLen,
IOVectors = iov,
IOVectorCount = iovCount
};
errno = Interop.Sys.ReceiveMessage(
socket,
&messageHeader,
flags,
&received);
receivedFlags = messageHeader.Flags;
sockAddrLen = messageHeader.SocketAddressLen;
}
}
finally
{
// Free GC handles.
for (int i = 0; i < iovCount; i++)
{
handles[i].Free();
}
}
socketAddressLen = sockAddrLen;
if (errno != Interop.Error.SUCCESS)
{
return -1;
}
return checked((int)received);
}
private static unsafe int SysReceiveMessageFrom(SafeSocketHandle socket, SocketFlags flags, Span<byte> buffer, Span<byte> socketAddress, out int socketAddressLen, bool isIPv4, bool isIPv6, out SocketFlags receivedFlags, out IPPacketInformation ipPacketInformation, out Interop.Error errno)
{
Debug.Assert(socket.IsSocket);
int cmsgBufferLen = Interop.Sys.GetControlMessageBufferSize(Convert.ToInt32(isIPv4), Convert.ToInt32(isIPv6));
byte* cmsgBuffer = stackalloc byte[cmsgBufferLen];
Interop.Sys.MessageHeader messageHeader;
long received = 0;
fixed (byte* rawSocketAddress = socketAddress)
fixed (byte* b = &MemoryMarshal.GetReference(buffer))
{
var iov = new Interop.Sys.IOVector {
Base = b,
Count = (UIntPtr)buffer.Length
};
Debug.Assert(socketAddress.Length != 0 || rawSocketAddress == null);
messageHeader = new Interop.Sys.MessageHeader {
SocketAddress = rawSocketAddress,
SocketAddressLen = socketAddress.Length,
IOVectors = &iov,
IOVectorCount = 1,
ControlBuffer = cmsgBuffer,
ControlBufferLen = cmsgBufferLen
};
errno = Interop.Sys.ReceiveMessage(
socket,
&messageHeader,
flags,
&received);
receivedFlags = messageHeader.Flags;
socketAddressLen = messageHeader.SocketAddressLen;
}
if (errno != Interop.Error.SUCCESS)
{
ipPacketInformation = default(IPPacketInformation);
return -1;
}
if (socketAddressLen == 0)
{
// We can fail to get peer address on TCP
socketAddressLen = socketAddress.Length;
SocketAddressPal.Clear(socketAddress);
}
ipPacketInformation = GetIPPacketInformation(&messageHeader, isIPv4, isIPv6);
return checked((int)received);
}
private static unsafe int SysReceiveMessageFrom(
SafeSocketHandle socket, SocketFlags flags, IList<ArraySegment<byte>> buffers,
Span<byte> socketAddress, out int socketAddressLen, bool isIPv4, bool isIPv6,
out SocketFlags receivedFlags, out IPPacketInformation ipPacketInformation, out Interop.Error errno)
{
Debug.Assert(socket.IsSocket);
int buffersCount = buffers.Count;
if (OperatingSystem.IsWasi())
{
// WASI doesn't have iovecs and sendmsg in preview2
buffersCount = Math.Max(buffersCount, 1);
}
bool allocOnStack = buffersCount <= IovStackThreshold;
Span<GCHandle> handles = allocOnStack ? stackalloc GCHandle[IovStackThreshold] : new GCHandle[buffersCount];
Span<Interop.Sys.IOVector> iovecs = allocOnStack ? stackalloc Interop.Sys.IOVector[IovStackThreshold] : new Interop.Sys.IOVector[buffersCount];
int iovCount = 0;
try
{
// Pin buffers and set up iovecs.
for (int i = 0; i < buffersCount; i++)
{
ArraySegment<byte> buffer = buffers[i];
RangeValidationHelpers.ValidateSegment(buffer);
handles[i] = GCHandle.Alloc(buffer.Array, GCHandleType.Pinned);
iovCount++;
iovecs[i].Base = &((byte*)handles[i].AddrOfPinnedObject())[buffer.Offset];
iovecs[i].Count = (UIntPtr)buffer.Count;
}
// Make the call.
fixed (byte* sockAddr = socketAddress)
fixed (Interop.Sys.IOVector* iov = iovecs)
{
int cmsgBufferLen = Interop.Sys.GetControlMessageBufferSize(Convert.ToInt32(isIPv4), Convert.ToInt32(isIPv6));
byte* cmsgBuffer = stackalloc byte[cmsgBufferLen];
var messageHeader = new Interop.Sys.MessageHeader
{
SocketAddress = sockAddr,
SocketAddressLen = socketAddress.Length,
IOVectors = iov,
IOVectorCount = iovCount,
ControlBuffer = cmsgBuffer,
ControlBufferLen = cmsgBufferLen
};
long received = 0;
errno = Interop.Sys.ReceiveMessage(
socket,
&messageHeader,
flags,
&received);
receivedFlags = messageHeader.Flags;
socketAddressLen = messageHeader.SocketAddressLen;
if (errno == Interop.Error.SUCCESS)
{
ipPacketInformation = GetIPPacketInformation(&messageHeader, isIPv4, isIPv6);
if (socketAddressLen == 0)
{
// We can fail to get peer address on TCP
socketAddressLen = socketAddress.Length;
SocketAddressPal.Clear(socketAddress);
}
return checked((int)received);
}
else
{
ipPacketInformation = default(IPPacketInformation);
return -1;
}
}
}
finally
{
// Free GC handles.
for (int i = 0; i < iovCount; i++)
{
handles[i].Free();
}
}
}
public static unsafe bool TryCompleteAccept(SafeSocketHandle socket, Memory<byte> socketAddress, out int socketAddressLen, out IntPtr acceptedFd, out SocketError errorCode)
{
IntPtr fd = IntPtr.Zero;
Interop.Error errno;
int sockAddrLen = socketAddress.Length;
fixed (byte* rawSocketAddress = socketAddress.Span)
{
try
{
errno = Interop.Sys.Accept(socket, rawSocketAddress, &sockAddrLen, &fd);
socketAddressLen = sockAddrLen;
}
catch (ObjectDisposedException)
{
// The socket was closed, or is closing.
errorCode = SocketError.OperationAborted;
acceptedFd = (IntPtr)(-1);
socketAddressLen = 0;
return true;
}
}
if (errno == Interop.Error.SUCCESS)
{
Debug.Assert(fd != (IntPtr)(-1), "Expected fd != -1");
errorCode = SocketError.Success;
acceptedFd = fd;
return true;
}
acceptedFd = (IntPtr)(-1);
if (errno != Interop.Error.EAGAIN && errno != Interop.Error.EWOULDBLOCK)
{
errorCode = GetSocketErrorForErrorCode(errno);
return true;
}
errorCode = SocketError.Success;
return false;
}
public static unsafe bool TryStartConnect(SafeSocketHandle socket, Memory<byte> socketAddress, out SocketError errorCode) => TryStartConnect(socket, socketAddress, out errorCode, Span<byte>.Empty, false, out int _ );
public static unsafe bool TryStartConnect(SafeSocketHandle socket, Memory<byte> socketAddress, out SocketError errorCode, Span<byte> data, bool tfo, out int sent)
{
Debug.Assert(socketAddress.Length > 0, $"Unexpected socketAddressLen: {socketAddress.Length}");
sent = 0;
if (socket.IsDisconnected)
{
errorCode = SocketError.IsConnected;
return true;
}
Interop.Error err;
fixed (byte* rawSocketAddress = socketAddress.Span)
{
if (data.Length > 0)
{
int sentBytes = 0;
err = Interop.Sys.Connectx(socket, rawSocketAddress, socketAddress.Length, data, data.Length, tfo ? 1 : 0, &sentBytes);
sent = sentBytes;
}
else
{
err = Interop.Sys.Connect(socket, rawSocketAddress, socketAddress.Length);
}
}
if (err == Interop.Error.SUCCESS)
{
errorCode = SocketError.Success;
return true;
}
if (err != Interop.Error.EINPROGRESS)
{
errorCode = GetSocketErrorForErrorCode(err);
return true;
}
errorCode = SocketError.Success;
return false;
}
public static unsafe bool TryCompleteConnect(SafeSocketHandle socket, out SocketError errorCode)
{
Interop.Error socketError = default;
Interop.Error err;
try
{
// Due to fd recyling, TryCompleteConnect may be called when there was a write event
// for the previous socket that used the fd.
// The SocketErrorOption in that case is the same as for a successful connect.
// To filter out these false events, we check whether the socket is writable, before
// reading the socket option.
Interop.PollEvents outEvents;
err = Interop.Sys.Poll(socket, Interop.PollEvents.POLLOUT, timeout: 0, out outEvents);
if (err == Interop.Error.SUCCESS)
{
if (outEvents == Interop.PollEvents.POLLNONE)
{
socketError = Interop.Error.EINPROGRESS;
}
else
{
err = Interop.Sys.GetSocketErrorOption(socket, &socketError);
}
}
}
catch (ObjectDisposedException)
{
// The socket was closed, or is closing.
errorCode = SocketError.OperationAborted;
return true;
}
if (err != Interop.Error.SUCCESS)
{
Debug.Assert(err == Interop.Error.EBADF, $"Unexpected err: {err}");
errorCode = SocketError.SocketError;
return true;
}
if (socketError == Interop.Error.SUCCESS)
{
errorCode = SocketError.Success;
return true;
}
else if (socketError == Interop.Error.EINPROGRESS)
{
errorCode = SocketError.Success;
return false;
}
errorCode = GetSocketErrorForErrorCode(socketError);
return true;
}
public static bool TryCompleteReceiveFrom(SafeSocketHandle socket, Span<byte> buffer, SocketFlags flags, Span<byte> socketAddress, out int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, out SocketError errorCode) =>
TryCompleteReceiveFrom(socket, buffer, null, flags, socketAddress, out socketAddressLen, out bytesReceived, out receivedFlags, out errorCode);
public static bool TryCompleteReceiveFrom(SafeSocketHandle socket, IList<ArraySegment<byte>> buffers, SocketFlags flags, Span<byte> socketAddress, out int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, out SocketError errorCode) =>
TryCompleteReceiveFrom(socket, default(Span<byte>), buffers, flags, socketAddress, out socketAddressLen, out bytesReceived, out receivedFlags, out errorCode);
public static unsafe bool TryCompleteReceive(SafeSocketHandle socket, Span<byte> buffer, SocketFlags flags, out int bytesReceived, out SocketError errorCode)
{
try
{
Interop.Error errno;
int received;
if (!socket.IsSocket)
{
Debug.Assert(flags == SocketFlags.None);
received = SysRead(socket, buffer, out errno);
}
else if (buffer.Length == 0)
{
// Special case a receive of 0 bytes into a single buffer. A common pattern is to ReceiveAsync 0 bytes in order
// to be asynchronously notified when data is available, without needing to dedicate a buffer. Some platforms (e.g. macOS),
// however complete a 0-byte read successfully when data isn't available, as the request can logically be satisfied
// synchronously. As such, we treat 0 specially, and perform a 1-byte peek.
byte oneBytePeekBuffer;
received = SysReceive(socket, flags | SocketFlags.Peek, new Span<byte>(&oneBytePeekBuffer, 1), out errno);
if (received > 0)
{
// Peeked for 1-byte, but the actual request was for 0.
received = 0;
}
}
else
{
// Receive > 0 bytes into a single buffer
received = SysReceive(socket, flags, buffer, out errno);
}
if (received != -1)
{
bytesReceived = received;
errorCode = SocketError.Success;
return true;
}
bytesReceived = 0;
if (errno != Interop.Error.EAGAIN && errno != Interop.Error.EWOULDBLOCK)
{
errorCode = GetSocketErrorForErrorCode(errno);
return true;
}
errorCode = SocketError.Success;
return false;
}
catch (ObjectDisposedException)
{
// The socket was closed, or is closing.
bytesReceived = 0;
errorCode = SocketError.OperationAborted;
return true;
}
}
public static unsafe bool TryCompleteReceiveFrom(SafeSocketHandle socket, Span<byte> buffer, IList<ArraySegment<byte>>? buffers, SocketFlags flags, Span<byte> socketAddress, out int receivedSocketAddressLength, out int bytesReceived, out SocketFlags receivedFlags, out SocketError errorCode)
{
try
{
Interop.Error errno;
int received;
if (!socket.IsSocket)
{
Debug.Assert(flags == SocketFlags.None);
Debug.Assert(buffers == null);
receivedFlags = default;
received = SysRead(socket, buffer, out errno);
receivedSocketAddressLength = 0;
}
else if (buffers != null)
{
// Receive into a set of buffers
received = SysReceive(socket, flags, buffers, socketAddress, out receivedSocketAddressLength, out receivedFlags, out errno);
}
else if (buffer.Length == 0)
{
// Special case a receive of 0 bytes into a single buffer. A common pattern is to ReceiveAsync 0 bytes in order
// to be asynchronously notified when data is available, without needing to dedicate a buffer. Some platforms (e.g. macOS),
// however complete a 0-byte read successfully when data isn't available, as the request can logically be satisfied
// synchronously. As such, we treat 0 specially, and perform a 1-byte peek.
byte oneBytePeekBuffer;
received = SysReceive(socket, flags | SocketFlags.Peek, new Span<byte>(&oneBytePeekBuffer, 1), socketAddress, out receivedSocketAddressLength, out receivedFlags, out errno);
if (received > 0)
{
// Peeked for 1-byte, but the actual request was for 0.
received = 0;
}
}
else
{
// Receive > 0 bytes into a single buffer
received = SysReceive(socket, flags, buffer, socketAddress, out receivedSocketAddressLength, out receivedFlags, out errno);
}
if (received != -1)
{
bytesReceived = received;
errorCode = SocketError.Success;
return true;
}
bytesReceived = 0;
receivedSocketAddressLength = 0;
if (errno != Interop.Error.EAGAIN && errno != Interop.Error.EWOULDBLOCK)
{
errorCode = GetSocketErrorForErrorCode(errno);
return true;
}
errorCode = SocketError.Success;
return false;
}
catch (ObjectDisposedException)
{
// The socket was closed, or is closing.
bytesReceived = 0;
receivedFlags = 0;
receivedSocketAddressLength = 0;
errorCode = SocketError.OperationAborted;
return true;
}
}
public static unsafe bool TryCompleteReceiveMessageFrom(SafeSocketHandle socket, Span<byte> buffer, IList<ArraySegment<byte>>? buffers, SocketFlags flags, Memory<byte> socketAddress, out int receivedSocketAddressLength, bool isIPv4, bool isIPv6, out int bytesReceived, out SocketFlags receivedFlags, out IPPacketInformation ipPacketInformation, out SocketError errorCode)
{
try
{
Interop.Error errno;
int received = buffers == null ?
SysReceiveMessageFrom(socket, flags, buffer, socketAddress.Span, out receivedSocketAddressLength, isIPv4, isIPv6, out receivedFlags, out ipPacketInformation, out errno) :
SysReceiveMessageFrom(socket, flags, buffers, socketAddress.Span, out receivedSocketAddressLength, isIPv4, isIPv6, out receivedFlags, out ipPacketInformation, out errno);
if (received != -1)
{
if (socketAddress.Length > 0 && receivedSocketAddressLength == 0)
{
// We can fail to get peer address on TCP
receivedSocketAddressLength = socketAddress.Length;
SocketAddressPal.Clear(socketAddress.Span);
}
bytesReceived = received;
errorCode = SocketError.Success;
return true;
}
bytesReceived = 0;
if (errno != Interop.Error.EAGAIN && errno != Interop.Error.EWOULDBLOCK)
{
errorCode = GetSocketErrorForErrorCode(errno);
return true;
}
errorCode = SocketError.Success;
return false;
}
catch (ObjectDisposedException)
{
// The socket was closed, or is closing.
bytesReceived = 0;
receivedFlags = 0;
receivedSocketAddressLength = 0;
ipPacketInformation = default(IPPacketInformation);
errorCode = SocketError.OperationAborted;
return true;
}
}
public static bool TryCompleteSendTo(SafeSocketHandle socket, Span<byte> buffer, ref int offset, ref int count, SocketFlags flags, ReadOnlySpan<byte> socketAddress, ref int bytesSent, out SocketError errorCode)
{
int bufferIndex = 0;
return TryCompleteSendTo(socket, buffer, null, ref bufferIndex, ref offset, ref count, flags, socketAddress, ref bytesSent, out errorCode);
}
public static bool TryCompleteSendTo(SafeSocketHandle socket, ReadOnlySpan<byte> buffer, SocketFlags flags, ReadOnlySpan<byte> socketAddress, ref int bytesSent, out SocketError errorCode)
{
int bufferIndex = 0, offset = 0, count = buffer.Length;
return TryCompleteSendTo(socket, buffer, null, ref bufferIndex, ref offset, ref count, flags, socketAddress, ref bytesSent, out errorCode);
}
public static bool TryCompleteSendTo(SafeSocketHandle socket, IList<ArraySegment<byte>> buffers, ref int bufferIndex, ref int offset, SocketFlags flags, ReadOnlySpan<byte> socketAddress, ref int bytesSent, out SocketError errorCode)
{
int count = 0;
return TryCompleteSendTo(socket, default(ReadOnlySpan<byte>), buffers, ref bufferIndex, ref offset, ref count, flags, socketAddress, ref bytesSent, out errorCode);
}
public static bool TryCompleteSendTo(SafeSocketHandle socket, ReadOnlySpan<byte> buffer, IList<ArraySegment<byte>>? buffers, ref int bufferIndex, ref int offset, ref int count, SocketFlags flags, ReadOnlySpan<byte> socketAddress, ref int bytesSent, out SocketError errorCode)
{
bool successfulSend = false;
long start = socket.IsUnderlyingHandleBlocking && socket.SendTimeout > 0 ? Environment.TickCount64 : 0; // Get ticks only if timeout is set and socket is blocking.
while (true)
{
int sent;
Interop.Error errno;
try
{
if (!socket.IsSocket)
{
Debug.Assert(flags == SocketFlags.None);
Debug.Assert(buffers == null);
sent = SysWrite(socket, buffer, ref offset, ref count, out errno);
}
else
{
sent = buffers != null ?
SysSend(socket, flags, buffers, ref bufferIndex, ref offset, socketAddress, out errno) :
socketAddress.IsEmpty ? SysSend(socket, flags, buffer, ref offset, ref count, out errno) :
SysSend(socket, flags, buffer, ref offset, ref count, socketAddress, out errno);
}
}
catch (ObjectDisposedException)
{
// The socket was closed, or is closing.
errorCode = SocketError.OperationAborted;
return true;
}
if (sent == -1)
{
if (!successfulSend && errno != Interop.Error.EAGAIN && errno != Interop.Error.EWOULDBLOCK)
{
errorCode = GetSocketErrorForErrorCode(errno);
return true;
}
errorCode = successfulSend ? SocketError.Success : SocketError.WouldBlock;
return false;
}
successfulSend = true;
bytesSent += sent;
bool isComplete = sent == 0 ||
(buffers == null && count == 0) ||
(buffers != null && bufferIndex == buffers.Count);
if (isComplete)
{
errorCode = SocketError.Success;
return true;
}
if (socket.IsUnderlyingHandleBlocking && socket.SendTimeout > 0 && (Environment.TickCount64 - start) >= socket.SendTimeout)
{
// When socket is truly in blocking mode, we depend on OS to enforce send timeout.
// When we are here we had partial send when we neither completed or failed.
// If we loop again, OS will wait another configured timeout before returning from system call.
// This block check checks is we used all our timer across all iterations.
errorCode = SocketError.TimedOut;
return true;
}
}
}
public static bool TryCompleteSendFile(SafeSocketHandle socket, SafeFileHandle handle, ref long offset, ref long count, ref long bytesSent, out SocketError errorCode)
{
while (true)
{
long sent;
Interop.Error errno;
try
{
sent = SendFile(socket, handle, ref offset, ref count, out errno);
bytesSent += sent;
}
catch (ObjectDisposedException)
{
// The socket was closed, or is closing.
errorCode = SocketError.OperationAborted;
return true;
}
if (errno != Interop.Error.SUCCESS)
{
if (errno != Interop.Error.EAGAIN && errno != Interop.Error.EWOULDBLOCK)
{
errorCode = GetSocketErrorForErrorCode(errno);
return true;
}
errorCode = SocketError.Success;
return false;
}
if (sent == 0 || count == 0)
{
errorCode = SocketError.Success;
return true;
}
}
}
public static SocketError SetBlocking(SafeSocketHandle handle, bool shouldBlock, out bool willBlock)
{
if(OperatingSystem.IsWasi() && shouldBlock) throw new PlatformNotSupportedException();
handle.IsNonBlocking = !shouldBlock;
willBlock = shouldBlock;
return SocketError.Success;
}
public static unsafe SocketError GetSockName(SafeSocketHandle handle, byte* buffer, int* nameLen)
{
Interop.Error err = Interop.Sys.GetSockName(handle, buffer, nameLen);
return err == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(err);
}
public static unsafe SocketError GetAvailable(SafeSocketHandle handle, out int available)
{
int value = 0;
Interop.Error err = Interop.Sys.GetBytesAvailable(handle, &value);
available = value;
return err == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(err);
}
public static unsafe SocketError GetAtOutOfBandMark(SafeSocketHandle handle, out int atOutOfBandMark)
{
int value = 0;
Interop.Error err = Interop.Sys.GetAtOutOfBandMark(handle, &value);
atOutOfBandMark = value;
return err == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(err);
}
public static unsafe SocketError GetPeerName(SafeSocketHandle handle, Span<byte> buffer, ref int nameLen)
{
Interop.Error err;
int addrLen = nameLen;
fixed (byte* rawBuffer = buffer)
{
err = Interop.Sys.GetPeerName(handle, rawBuffer, &addrLen);
}
nameLen = addrLen;
return err == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(err);
}
public static unsafe SocketError Bind(SafeSocketHandle handle, ProtocolType socketProtocolType, ReadOnlySpan<byte> buffer)
{
Interop.Error err = Interop.Sys.Bind(handle, socketProtocolType, buffer);
return err == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(err);
}
public static SocketError Listen(SafeSocketHandle handle, int backlog)
{
Interop.Error err = Interop.Sys.Listen(handle, backlog);
return err == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(err);
}
public static SocketError Accept(SafeSocketHandle listenSocket, Memory<byte> socketAddress, out int socketAddressLen, out SafeSocketHandle socket)
{
socket = new SafeSocketHandle();
IntPtr acceptedFd;
SocketError errorCode;
if (!listenSocket.IsNonBlocking)
{
errorCode = listenSocket.AsyncContext.Accept(socketAddress, out socketAddressLen, out acceptedFd);
}
else
{
if (!TryCompleteAccept(listenSocket, socketAddress, out socketAddressLen, out acceptedFd, out errorCode))
{
errorCode = SocketError.WouldBlock;
}
}
Marshal.InitHandle(socket, acceptedFd);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, socket);
return errorCode;
}
public static SocketError Connect(SafeSocketHandle handle, Memory<byte> socketAddress)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.Connect(socketAddress);
}
SocketError errorCode;
bool completed = TryStartConnect(handle, socketAddress, out errorCode);
if (completed)
{
handle.RegisterConnectResult(errorCode);
return errorCode;
}
else
{
return SocketError.WouldBlock;
}
}
public static SocketError Send(SafeSocketHandle handle, IList<ArraySegment<byte>> buffers, SocketFlags socketFlags, out int bytesTransferred)
{
var bufferList = buffers;
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.Send(bufferList, socketFlags, handle.SendTimeout, out bytesTransferred);
}
bytesTransferred = 0;
int bufferIndex = 0;
int offset = 0;
SocketError errorCode;
TryCompleteSendTo(handle, bufferList, ref bufferIndex, ref offset, socketFlags, ReadOnlySpan<byte>.Empty, ref bytesTransferred, out errorCode);
return errorCode;
}
public static SocketError Send(SafeSocketHandle handle, byte[] buffer, int offset, int count, SocketFlags socketFlags, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.Send(buffer, offset, count, socketFlags, handle.SendTimeout, out bytesTransferred);
}
bytesTransferred = 0;
SocketError errorCode;
TryCompleteSendTo(handle, buffer, ref offset, ref count, socketFlags, ReadOnlySpan<byte>.Empty, ref bytesTransferred, out errorCode);
return errorCode;
}
public static SocketError Send(SafeSocketHandle handle, ReadOnlySpan<byte> buffer, SocketFlags socketFlags, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.Send(buffer, socketFlags, handle.SendTimeout, out bytesTransferred);
}
bytesTransferred = 0;
SocketError errorCode;
TryCompleteSendTo(handle, buffer, socketFlags, ReadOnlySpan<byte>.Empty, ref bytesTransferred, out errorCode);
return errorCode;
}
public static SocketError SendFile(SafeSocketHandle handle, SafeFileHandle fileHandle)
{
long offset = 0;
long length = RandomAccess.GetLength(fileHandle);
long bytesTransferred = 0;
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.SendFile(fileHandle, offset, length, handle.SendTimeout, out _);
}
SocketError errorCode;
bool completed = TryCompleteSendFile(handle, fileHandle, ref offset, ref length, ref bytesTransferred, out errorCode);
return completed ? errorCode : SocketError.WouldBlock;
}
public static SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int offset, int count, SocketFlags socketFlags, Memory<byte> socketAddress, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.SendTo(buffer, offset, count, socketFlags, socketAddress, handle.SendTimeout, out bytesTransferred);
}
bytesTransferred = 0;
SocketError errorCode;
TryCompleteSendTo(handle, buffer, ref offset, ref count, socketFlags, socketAddress.Span, ref bytesTransferred, out errorCode);
return errorCode;
}
public static SocketError SendTo(SafeSocketHandle handle, ReadOnlySpan<byte> buffer, SocketFlags socketFlags, Memory<byte> socketAddress, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.SendTo(buffer, socketFlags, socketAddress, handle.SendTimeout, out bytesTransferred);
}
bytesTransferred = 0;
SocketError errorCode;
TryCompleteSendTo(handle, buffer, socketFlags, socketAddress.Span, ref bytesTransferred, out errorCode);
return errorCode;
}
public static SocketError Receive(SafeSocketHandle handle, IList<ArraySegment<byte>> buffers, SocketFlags socketFlags, out int bytesTransferred)
{
SocketError errorCode;
if (!handle.IsNonBlocking)
{
errorCode = handle.AsyncContext.Receive(buffers, socketFlags, handle.ReceiveTimeout, out bytesTransferred);
}
else
{
if (!TryCompleteReceiveFrom(handle, buffers, socketFlags, Span<byte>.Empty, out int _, out bytesTransferred, out _, out errorCode))
{
errorCode = SocketError.WouldBlock;
}
}
return errorCode;
}
public static SocketError Receive(SafeSocketHandle handle, byte[] buffer, int offset, int count, SocketFlags socketFlags, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.Receive(new Memory<byte>(buffer, offset, count), socketFlags, handle.ReceiveTimeout, out bytesTransferred);
}
SocketError errorCode;
bool completed = TryCompleteReceive(handle, new Span<byte>(buffer, offset, count), socketFlags, out bytesTransferred, out errorCode);
return completed ? errorCode : SocketError.WouldBlock;
}
public static SocketError Receive(SafeSocketHandle handle, Span<byte> buffer, SocketFlags socketFlags, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.Receive(buffer, socketFlags, handle.ReceiveTimeout, out bytesTransferred);
}
SocketError errorCode;
bool completed = TryCompleteReceive(handle, buffer, socketFlags, out bytesTransferred, out errorCode);
return completed ? errorCode : SocketError.WouldBlock;
}
public static SocketError ReceiveMessageFrom(Socket socket, SafeSocketHandle handle, byte[] buffer, int offset, int count, ref SocketFlags socketFlags, SocketAddress socketAddress, out SocketAddress receiveAddress, out IPPacketInformation ipPacketInformation, out int bytesTransferred)
{
int socketAddressLen;
bool isIPv4, isIPv6;
Socket.GetIPProtocolInformation(socket.AddressFamily, socketAddress, out isIPv4, out isIPv6);
SocketError errorCode;
if (!handle.IsNonBlocking)
{
errorCode = handle.AsyncContext.ReceiveMessageFrom(new Memory<byte>(buffer, offset, count), ref socketFlags, socketAddress.Buffer, out socketAddressLen, isIPv4, isIPv6, handle.ReceiveTimeout, out ipPacketInformation, out bytesTransferred);
}
else
{
if (!TryCompleteReceiveMessageFrom(handle, new Span<byte>(buffer, offset, count), null, socketFlags, socketAddress.Buffer, out socketAddressLen, isIPv4, isIPv6, out bytesTransferred, out socketFlags, out ipPacketInformation, out errorCode))
{
errorCode = SocketError.WouldBlock;
}
}
socketAddress.Size = socketAddressLen;
receiveAddress = socketAddress;
return errorCode;
}
public static SocketError ReceiveMessageFrom(Socket socket, SafeSocketHandle handle, Span<byte> buffer, ref SocketFlags socketFlags, SocketAddress socketAddress, out SocketAddress receiveAddress, out IPPacketInformation ipPacketInformation, out int bytesTransferred)
{
int socketAddressLen;
bool isIPv4, isIPv6;
Socket.GetIPProtocolInformation(socket.AddressFamily, socketAddress, out isIPv4, out isIPv6);
SocketError errorCode;
if (!handle.IsNonBlocking)
{
errorCode = handle.AsyncContext.ReceiveMessageFrom(buffer, ref socketFlags, socketAddress.Buffer, out socketAddressLen, isIPv4, isIPv6, handle.ReceiveTimeout, out ipPacketInformation, out bytesTransferred);
}
else
{
if (!TryCompleteReceiveMessageFrom(handle, buffer, null, socketFlags, socketAddress.Buffer, out socketAddressLen, isIPv4, isIPv6, out bytesTransferred, out socketFlags, out ipPacketInformation, out errorCode))
{
errorCode = SocketError.WouldBlock;
}
}
socketAddress.Size = socketAddressLen;
receiveAddress = socketAddress;
return errorCode;
}
public static SocketError ReceiveFrom(SafeSocketHandle handle, byte[] buffer, int offset, int count, SocketFlags socketFlags, Memory<byte> socketAddress, out int socketAddressLen, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.ReceiveFrom(new Memory<byte>(buffer, offset, count), ref socketFlags, socketAddress, out socketAddressLen, handle.ReceiveTimeout, out bytesTransferred);
}
SocketError errorCode;
bool completed = TryCompleteReceiveFrom(handle, new Span<byte>(buffer, offset, count), socketFlags, socketAddress.Span, out socketAddressLen, out bytesTransferred, out socketFlags, out errorCode);
return completed ? errorCode : SocketError.WouldBlock;
}
public static SocketError ReceiveFrom(SafeSocketHandle handle, Span<byte> buffer, SocketFlags socketFlags, Memory<byte> socketAddress, out int socketAddressLen, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.ReceiveFrom(buffer, ref socketFlags, socketAddress, out socketAddressLen, handle.ReceiveTimeout, out bytesTransferred);
}
SocketError errorCode;
bool completed = TryCompleteReceiveFrom(handle, buffer, socketFlags, socketAddress.Span, out socketAddressLen, out bytesTransferred, out socketFlags, out errorCode);
return completed ? errorCode : SocketError.WouldBlock;
}
public static SocketError WindowsIoctl(SafeSocketHandle handle, int ioControlCode, byte[]? _ /*optionInValue*/, byte[]? optionOutValue, out int optionLength)
{
// Three codes are called out in the Winsock IOCTLs documentation as "The following Unix IOCTL codes (commands) are supported." They are
// also the three codes available for use with ioctlsocket on Windows. Developers should be discouraged from using Socket.IOControl in
// cross -platform applications, as it accepts Windows-specific values (the value of FIONREAD is different on different platforms), but
// we make a best-effort attempt to at least keep these codes behaving as on Windows.
const int FIONBIO = unchecked((int)IOControlCode.NonBlockingIO);
const int FIONREAD = (int)IOControlCode.DataToRead;
const int SIOCATMARK = (int)IOControlCode.OobDataRead;
optionLength = 0;
switch (ioControlCode)
{
case FIONBIO:
// The Windows implementation explicitly throws this exception, so that all
// changes to blocking/non-blocking are done via Socket.Blocking.
throw new InvalidOperationException(SR.net_sockets_useblocking);
case FIONREAD:
case SIOCATMARK:
if (optionOutValue == null || optionOutValue.Length < sizeof(int))
{
return SocketError.Fault;
}
int result;
SocketError error = ioControlCode == FIONREAD ?
GetAvailable(handle, out result) :
GetAtOutOfBandMark(handle, out result);
if (error == SocketError.Success)
{
optionLength = sizeof(int);
BitConverter.TryWriteBytes(optionOutValue, result);
}
return error;
default:
// Every other control code is unknown to us for and is considered unsupported on Unix.
throw new PlatformNotSupportedException(SR.PlatformNotSupported_IOControl);
}
}
private static SocketError GetErrorAndTrackSetting(SafeSocketHandle handle, SocketOptionLevel optionLevel, SocketOptionName optionName, Interop.Error err)
{
if (err == Interop.Error.SUCCESS)
{
handle.TrackOption(optionLevel, optionName);
return SocketError.Success;
}
return GetSocketErrorForErrorCode(err);
}
public static unsafe SocketError SetSockOpt(SafeSocketHandle handle, SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue)
{
Interop.Error err;
if (optionLevel == SocketOptionLevel.Socket)
{
if (optionName == SocketOptionName.ReceiveTimeout)
{
handle.ReceiveTimeout = optionValue == 0 ? -1 : optionValue;
err = Interop.Sys.SetReceiveTimeout(handle, optionValue);
return GetErrorAndTrackSetting(handle, optionLevel, optionName, err);
}
else if (optionName == SocketOptionName.SendTimeout)
{
handle.SendTimeout = optionValue == 0 ? -1 : optionValue;
err = Interop.Sys.SetSendTimeout(handle, optionValue);
return GetErrorAndTrackSetting(handle, optionLevel, optionName, err);
}
}
else if (optionLevel == SocketOptionLevel.IP)
{
if (optionName == SocketOptionName.MulticastInterface)
{
// if the value of the IP_MULTICAST_IF is an address in the 0.x.x.x block
// the value is interpreted as an interface index
int interfaceIndex = IPAddress.NetworkToHostOrder(optionValue);
if ((interfaceIndex & 0xff000000) == 0)
{
var opt = new Interop.Sys.IPv4MulticastOption
{
MulticastAddress = 0,
LocalAddress = 0,
InterfaceIndex = interfaceIndex
};
err = Interop.Sys.SetIPv4MulticastOption(handle, Interop.Sys.MulticastOption.MULTICAST_IF, &opt);
return GetErrorAndTrackSetting(handle, optionLevel, optionName, err);
}
}
}
err = Interop.Sys.SetSockOpt(handle, optionLevel, optionName, (byte*)&optionValue, sizeof(int));
if (err == Interop.Error.SUCCESS)
{
if (optionLevel == SocketOptionLevel.IPv6 && optionName == SocketOptionName.IPv6Only)
{
// Unix stacks may set IPv6Only to true once bound to an address. This causes problems
// for Socket.DualMode, and anything that depends on it, like CanTryAddressFamily.
// To aid in connecting to multiple addresses (e.g. a DNS endpoint), we need to remember
// whether DualMode / !IPv6Only was set, so that we can restore that value to a subsequent
// handle after a failed connect.
handle.DualMode = optionValue == 0;
}
}
#if SYSTEM_NET_SOCKETS_APPLE_PLATFROM
// macOS fails to even query it if socket is not actively listening.
// To provide consistent platform experience we will track if
// it was ret and we will use it later as needed.
if (optionLevel == SocketOptionLevel.Tcp && optionName == SocketOptionName.FastOpen)
{
handle.TfoEnabled = optionValue != 0;
// Silently ignore errors - TFO is best effort and it may be disabled by configuration or not
// supported by OS.
err = Interop.Error.SUCCESS;
}
#endif
return GetErrorAndTrackSetting(handle, optionLevel, optionName, err);
}
public static unsafe SocketError SetSockOpt(SafeSocketHandle handle, SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue)
{
fixed (byte* pinnedValue = optionValue)
{
Interop.Error err = Interop.Sys.SetSockOpt(handle, optionLevel, optionName, pinnedValue, optionValue != null ? optionValue.Length : 0);
return GetErrorAndTrackSetting(handle, optionLevel, optionName, err);
}
}
public static unsafe SocketError SetRawSockOpt(SafeSocketHandle handle, int optionLevel, int optionName, ReadOnlySpan<byte> optionValue)
{
fixed (byte* optionValuePtr = optionValue)
{
Interop.Error err = Interop.Sys.SetRawSockOpt(handle, optionLevel, optionName, optionValuePtr, optionValue.Length);
if (err == Interop.Error.SUCCESS)
{
// When dealing with SocketOptionLevel/SocketOptionName, we know what the values mean and can use GetErrorAndTrackSetting
// to keep track of changed values. But since we don't know what these levels/names mean (hence, "raw"), we have to
// assume it's not tracked, and thus call SetExposed. It's a reasonable assumption, given that the only values we
// track are ones for which we have portable names, and if a portable name exists for it, why would the caller choose
// the more difficult path of using the "raw" level/name?
handle.SetExposed();
return SocketError.Success;
}
return GetSocketErrorForErrorCode(err);
}
}
public static unsafe SocketError SetMulticastOption(SafeSocketHandle handle, SocketOptionName optionName, MulticastOption optionValue)
{
Debug.Assert(optionName == SocketOptionName.AddMembership || optionName == SocketOptionName.DropMembership, $"Unexpected optionName: {optionName}");
Interop.Sys.MulticastOption optName = optionName == SocketOptionName.AddMembership ?
Interop.Sys.MulticastOption.MULTICAST_ADD :
Interop.Sys.MulticastOption.MULTICAST_DROP;
IPAddress localAddress = optionValue.LocalAddress ?? IPAddress.Any;
#pragma warning disable CS0618 // Address is marked obsolete
var opt = new Interop.Sys.IPv4MulticastOption
{
MulticastAddress = unchecked((uint)optionValue.Group.Address),
LocalAddress = unchecked((uint)localAddress.Address),
InterfaceIndex = optionValue.InterfaceIndex
};
#pragma warning restore CS0618
Interop.Error err = Interop.Sys.SetIPv4MulticastOption(handle, optName, &opt);
return GetErrorAndTrackSetting(handle, SocketOptionLevel.IP, optionName, err);
}
public static unsafe SocketError SetIPv6MulticastOption(SafeSocketHandle handle, SocketOptionName optionName, IPv6MulticastOption optionValue)
{
Debug.Assert(optionName == SocketOptionName.AddMembership || optionName == SocketOptionName.DropMembership, $"Unexpected optionName={optionName}");
Interop.Sys.MulticastOption optName = optionName == SocketOptionName.AddMembership ?
Interop.Sys.MulticastOption.MULTICAST_ADD :
Interop.Sys.MulticastOption.MULTICAST_DROP;
var opt = new Interop.Sys.IPv6MulticastOption {
Address = optionValue.Group.GetNativeIPAddress(),
InterfaceIndex = (int)optionValue.InterfaceIndex
};
Interop.Error err = Interop.Sys.SetIPv6MulticastOption(handle, optName, &opt);
return GetErrorAndTrackSetting(handle, SocketOptionLevel.IPv6, optionName, err);
}
public static unsafe SocketError SetLingerOption(SafeSocketHandle handle, LingerOption optionValue)
{
var opt = new Interop.Sys.LingerOption {
OnOff = optionValue.Enabled ? 1 : 0,
Seconds = optionValue.LingerTime
};
Interop.Error err = Interop.Sys.SetLingerOption(handle, &opt);
return GetErrorAndTrackSetting(handle, SocketOptionLevel.Socket, SocketOptionName.Linger, err);
}
public static void SetReceivingDualModeIPv4PacketInformation(Socket socket)
{
// NOTE: some platforms (e.g. OS X) do not support receiving IPv4 packet information for packets received
// on dual-mode sockets. On these platforms, this call is a no-op.
if (SupportsDualModeIPv4PacketInfo)
{
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);
}
}
#pragma warning disable IDE0060
public static void SetIPProtectionLevel(Socket socket, SocketOptionLevel optionLevel, int protectionLevel)
{
throw new PlatformNotSupportedException(SR.PlatformNotSupported_IPProtectionLevel);
}
#pragma warning restore IDE0060
public static unsafe SocketError GetSockOpt(SafeSocketHandle handle, SocketOptionLevel optionLevel, SocketOptionName optionName, out int optionValue)
{
if (optionLevel == SocketOptionLevel.Socket)
{
if (optionName == SocketOptionName.ReceiveTimeout)
{
optionValue = handle.ReceiveTimeout == -1 ? 0 : handle.ReceiveTimeout;
return SocketError.Success;
}
else if (optionName == SocketOptionName.SendTimeout)
{
optionValue = handle.SendTimeout == -1 ? 0 : handle.SendTimeout;
return SocketError.Success;
}
}
if (optionName == SocketOptionName.Error)
{
Interop.Error socketError = default(Interop.Error);
Interop.Error getErrorError = Interop.Sys.GetSocketErrorOption(handle, &socketError);
optionValue = (int)GetSocketErrorForErrorCode(socketError);
return getErrorError == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(getErrorError);
}
int value = 0;
int optLen = sizeof(int);
Interop.Error err = Interop.Sys.GetSockOpt(handle, optionLevel, optionName, (byte*)&value, &optLen);
#if SYSTEM_NET_SOCKETS_APPLE_PLATFROM
// macOS fails to even query it if socket is not actively listening.
// To provide consistent platform experience we will track if
// it was set and we will use it later as needed.
if (optionLevel == SocketOptionLevel.Tcp && optionName == SocketOptionName.FastOpen && err != Interop.Error.SUCCESS)
{
value = handle.TfoEnabled ? 1 : 0;
err = Interop.Error.SUCCESS;
}
#endif
optionValue = value;
return err == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(err);
}
public static unsafe SocketError GetSockOpt(SafeSocketHandle handle, SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue, ref int optionLength)
{
int optLen = optionLength;
Interop.Error err;
if (optionValue == null || optionValue.Length == 0)
{
optLen = 0;
err = Interop.Sys.GetSockOpt(handle, optionLevel, optionName, null, &optLen);
}
else if (optionName == SocketOptionName.Error && optionValue.Length >= sizeof(int))
{
int outError;
SocketError returnError = GetSockOpt(handle, optionLevel, optionName, out outError);
if (returnError == SocketError.Success)
{
fixed (byte* pinnedValue = &optionValue[0])
{
*((int*)pinnedValue) = outError;
}
optionLength = sizeof(int);
}
return returnError;
}
else
{
fixed (byte* pinnedValue = &optionValue[0])
{
err = Interop.Sys.GetSockOpt(handle, optionLevel, optionName, pinnedValue, &optLen);
}
}
if (err == Interop.Error.SUCCESS)
{
optionLength = optLen;
return SocketError.Success;
}
return GetSocketErrorForErrorCode(err);
}
public static unsafe SocketError GetRawSockOpt(SafeSocketHandle handle, int optionLevel, int optionName, Span<byte> optionValue, ref int optionLength)
{
Debug.Assert((uint)optionLength <= optionValue.Length);
int optLen = optionLength;
fixed (byte* pinnedValue = optionValue)
{
Interop.Error err = Interop.Sys.GetRawSockOpt(handle, optionLevel, optionName, pinnedValue, &optLen);
if (err == Interop.Error.SUCCESS)
{
optionLength = optLen;
return SocketError.Success;
}
return GetSocketErrorForErrorCode(err);
}
}
public static unsafe SocketError GetMulticastOption(SafeSocketHandle handle, SocketOptionName optionName, out MulticastOption? optionValue)
{
Debug.Assert(optionName == SocketOptionName.AddMembership || optionName == SocketOptionName.DropMembership, $"Unexpected optionName={optionName}");
Interop.Sys.MulticastOption optName = optionName == SocketOptionName.AddMembership ?
Interop.Sys.MulticastOption.MULTICAST_ADD :
Interop.Sys.MulticastOption.MULTICAST_DROP;
Interop.Sys.IPv4MulticastOption opt = default;
Interop.Error err = Interop.Sys.GetIPv4MulticastOption(handle, optName, &opt);
if (err != Interop.Error.SUCCESS)
{
optionValue = default(MulticastOption);
return GetSocketErrorForErrorCode(err);
}
var multicastAddress = new IPAddress((long)opt.MulticastAddress);
var localAddress = new IPAddress((long)opt.LocalAddress);
optionValue = new MulticastOption(multicastAddress, localAddress) {
InterfaceIndex = opt.InterfaceIndex
};
return SocketError.Success;
}
public static unsafe SocketError GetIPv6MulticastOption(SafeSocketHandle handle, SocketOptionName optionName, out IPv6MulticastOption? optionValue)
{
Debug.Assert(optionName == SocketOptionName.AddMembership || optionName == SocketOptionName.DropMembership, $"Unexpected optionName={optionName}");
Interop.Sys.MulticastOption optName = optionName == SocketOptionName.AddMembership ?
Interop.Sys.MulticastOption.MULTICAST_ADD :
Interop.Sys.MulticastOption.MULTICAST_DROP;
Interop.Sys.IPv6MulticastOption opt = default;
Interop.Error err = Interop.Sys.GetIPv6MulticastOption(handle, optName, &opt);
if (err != Interop.Error.SUCCESS)
{
optionValue = default(IPv6MulticastOption);
return GetSocketErrorForErrorCode(err);
}
optionValue = new IPv6MulticastOption(opt.Address.GetIPAddress(), opt.InterfaceIndex);
return SocketError.Success;
}
public static unsafe SocketError GetLingerOption(SafeSocketHandle handle, out LingerOption? optionValue)
{
Interop.Sys.LingerOption opt = default;
Interop.Error err = Interop.Sys.GetLingerOption(handle, &opt);
if (err != Interop.Error.SUCCESS)
{
optionValue = default(LingerOption);
return GetSocketErrorForErrorCode(err);
}
optionValue = new LingerOption(opt.OnOff != 0, opt.Seconds);
return SocketError.Success;
}
public static unsafe SocketError Poll(SafeSocketHandle handle, int microseconds, SelectMode mode, out bool status)
{
Interop.PollEvents inEvent = Interop.PollEvents.POLLNONE;
switch (mode)
{
case SelectMode.SelectRead: inEvent = Interop.PollEvents.POLLIN; break;
case SelectMode.SelectWrite: inEvent = Interop.PollEvents.POLLOUT; break;
case SelectMode.SelectError: inEvent = Interop.PollEvents.POLLPRI; break;
}
int milliseconds = microseconds == -1 ? -1 : microseconds / 1000;
Interop.PollEvents outEvents;
Interop.Error err = Interop.Sys.Poll(handle, inEvent, milliseconds, out outEvents);
if (err != Interop.Error.SUCCESS)
{
status = false;
return GetSocketErrorForErrorCode(err);
}
switch (mode)
{
case SelectMode.SelectRead: status = (outEvents & (Interop.PollEvents.POLLIN | Interop.PollEvents.POLLHUP)) != 0; break;
case SelectMode.SelectWrite: status = (outEvents & Interop.PollEvents.POLLOUT) != 0; break;
case SelectMode.SelectError: status = (outEvents & (Interop.PollEvents.POLLERR | Interop.PollEvents.POLLPRI)) != 0; break;
default: status = false; break;
}
return SocketError.Success;
}
public static unsafe SocketError Select(IList? checkRead, IList? checkWrite, IList? checkError, int microseconds)
{
int checkReadInitialCount = checkRead != null ? checkRead.Count : 0;
int checkWriteInitialCount = checkWrite != null ? checkWrite.Count : 0;
int checkErrorInitialCount = checkError != null ? checkError.Count : 0;
int count = checked(checkReadInitialCount + checkWriteInitialCount + checkErrorInitialCount);
Debug.Assert(count > 0, $"Expected at least one entry.");
// Rather than using the select syscall, we use poll. While this has a mismatch in API from Select and
// requires some translation, it avoids the significant limitation of select only working with file descriptors
// less than FD_SETSIZE, and thus failing arbitrarily depending on the file descriptor value assigned
// by the system. Since poll then expects an array of entries, we try to allocate the array on the stack,
// only falling back to allocating it on the heap if it's deemed too big.
if (SelectOverPollIsBroken)
{
return SelectViaSelect(checkRead, checkWrite, checkError, microseconds);
}
const int StackThreshold = 80; // arbitrary limit to avoid too much space on stack
if (count < StackThreshold)
{
Interop.PollEvent* eventsOnStack = stackalloc Interop.PollEvent[count];
return SelectViaPoll(
checkRead, checkReadInitialCount,
checkWrite, checkWriteInitialCount,
checkError, checkErrorInitialCount,
eventsOnStack, count, microseconds);
}
else
{
var eventsOnHeap = new Interop.PollEvent[count];
fixed (Interop.PollEvent* eventsOnHeapPtr = &eventsOnHeap[0])
{
return SelectViaPoll(
checkRead, checkReadInitialCount,
checkWrite, checkWriteInitialCount,
checkError, checkErrorInitialCount,
eventsOnHeapPtr, count, microseconds);
}
}
}
private static SocketError SelectViaSelect(IList? checkRead, IList? checkWrite, IList? checkError, int microseconds)
{
const int MaxStackAllocCount = 20; // this is just arbitrary limit 3x 20 -> 60 e.g. close to 64 we have in some other places
Span<int> readFDs = checkRead?.Count > MaxStackAllocCount ? new int[checkRead.Count] : stackalloc int[checkRead?.Count ?? 0];
Span<int> writeFDs = checkWrite?.Count > MaxStackAllocCount ? new int[checkWrite.Count] : stackalloc int[checkWrite?.Count ?? 0];
Span<int> errorFDs = checkError?.Count > MaxStackAllocCount ? new int[checkError.Count] : stackalloc int[checkError?.Count ?? 0];
int refsAdded = 0;
int maxFd = 0;
try
{
AddDesriptors(readFDs, checkRead, ref refsAdded, ref maxFd);
AddDesriptors(writeFDs, checkWrite, ref refsAdded, ref maxFd);
AddDesriptors(errorFDs, checkError, ref refsAdded, ref maxFd);
int triggered = 0;
Interop.Error err = Interop.Sys.Select(readFDs, readFDs.Length, writeFDs, writeFDs.Length, errorFDs, errorFDs.Length, microseconds, maxFd, out triggered);
if (err != Interop.Error.SUCCESS)
{
return GetSocketErrorForErrorCode(err);
}
Socket.SocketListDangerousReleaseRefs(checkRead, ref refsAdded);
Socket.SocketListDangerousReleaseRefs(checkWrite, ref refsAdded);
Socket.SocketListDangerousReleaseRefs(checkError, ref refsAdded);
if (triggered == 0)
{
checkRead?.Clear();
checkWrite?.Clear();
checkError?.Clear();
}
else
{
FilterSelectList(checkRead, readFDs);
FilterSelectList(checkWrite, writeFDs);
FilterSelectList(checkError, errorFDs);
}
}
finally
{
// This order matches with the AddToPollArray calls
// to release only the handles that were ref'd.
Socket.SocketListDangerousReleaseRefs(checkRead, ref refsAdded);
Socket.SocketListDangerousReleaseRefs(checkWrite, ref refsAdded);
Socket.SocketListDangerousReleaseRefs(checkError, ref refsAdded);
Debug.Assert(refsAdded == 0);
}
return (SocketError)0;
}
private static void AddDesriptors(Span<int> buffer, IList? socketList, ref int refsAdded, ref int maxFd)
{
if (socketList == null || socketList.Count == 0 )
{
return;
}
Debug.Assert(buffer.Length == socketList.Count);
for (int i = 0; i < socketList.Count; i++)
{
Socket? socket = socketList[i] as Socket;
if (socket == null)
{
throw new ArgumentException(SR.Format(SR.net_sockets_select, socket?.GetType().FullName ?? "null", typeof(Socket).FullName), nameof(socketList));
}
if (socket.Handle > maxFd)
{
maxFd = (int)socket.Handle;
}
bool success = false;
socket.InternalSafeHandle.DangerousAddRef(ref success);
buffer[i] = (int)socket.InternalSafeHandle.DangerousGetHandle();
refsAdded++;
}
}
private static void FilterSelectList(IList? socketList, Span<int> results)
{
if (socketList == null)
return;
// This loop can be O(n^2) in the unexpected and worst case. Some more thoughts are written in FilterPollList that does exactly same operation.
for (int i = socketList.Count - 1; i >= 0; --i)
{
if (results[i] == 0)
{
socketList.RemoveAt(i);
}
}
}
private static unsafe SocketError SelectViaPoll(
IList? checkRead, int checkReadInitialCount,
IList? checkWrite, int checkWriteInitialCount,
IList? checkError, int checkErrorInitialCount,
Interop.PollEvent* events, int eventsLength,
int microseconds)
{
// Add each of the list's contents to the events array
Debug.Assert(eventsLength == checkReadInitialCount + checkWriteInitialCount + checkErrorInitialCount, "Invalid eventsLength");
int offset = 0;
int refsAdded = 0;
try
{
// In case we can't increase the reference count for each Socket,
// we'll unref refAdded Sockets in the finally block ordered: [checkRead, checkWrite, checkError].
AddToPollArray(events, eventsLength, checkRead, ref offset, Interop.PollEvents.POLLIN | Interop.PollEvents.POLLHUP, ref refsAdded);
AddToPollArray(events, eventsLength, checkWrite, ref offset, Interop.PollEvents.POLLOUT, ref refsAdded);
AddToPollArray(events, eventsLength, checkError, ref offset, Interop.PollEvents.POLLPRI, ref refsAdded);
Debug.Assert(offset == eventsLength, $"Invalid adds. offset={offset}, eventsLength={eventsLength}.");
Debug.Assert(refsAdded == eventsLength, $"Invalid ref adds. refsAdded={refsAdded}, eventsLength={eventsLength}.");
// Do the poll
uint triggered = 0;
int milliseconds = microseconds == -1 ? -1 : microseconds / 1000;
Interop.Error err = Interop.Sys.Poll(events, (uint)eventsLength, milliseconds, &triggered);
if (err != Interop.Error.SUCCESS)
{
return GetSocketErrorForErrorCode(err);
}
// Remove from the lists any entries which weren't set
if (triggered == 0)
{
Socket.SocketListDangerousReleaseRefs(checkRead, ref refsAdded);
Socket.SocketListDangerousReleaseRefs(checkWrite, ref refsAdded);
Socket.SocketListDangerousReleaseRefs(checkError, ref refsAdded);
checkRead?.Clear();
checkWrite?.Clear();
checkError?.Clear();
}
else
{
FilterPollList(checkRead, events, checkReadInitialCount - 1, Interop.PollEvents.POLLIN | Interop.PollEvents.POLLHUP, ref refsAdded);
FilterPollList(checkWrite, events, checkWriteInitialCount + checkReadInitialCount - 1, Interop.PollEvents.POLLOUT, ref refsAdded);
FilterPollList(checkError, events, checkErrorInitialCount + checkWriteInitialCount + checkReadInitialCount - 1, Interop.PollEvents.POLLERR | Interop.PollEvents.POLLPRI, ref refsAdded);
}
return SocketError.Success;
}
finally
{
// This order matches with the AddToPollArray calls
// to release only the handles that were ref'd.
Socket.SocketListDangerousReleaseRefs(checkRead, ref refsAdded);
Socket.SocketListDangerousReleaseRefs(checkWrite, ref refsAdded);
Socket.SocketListDangerousReleaseRefs(checkError, ref refsAdded);
Debug.Assert(refsAdded == 0);
}
}
private static unsafe void AddToPollArray(Interop.PollEvent* arr, int arrLength, IList? socketList, ref int arrOffset, Interop.PollEvents events, ref int refsAdded)
{
if (socketList == null)
return;
int listCount = socketList.Count;
for (int i = 0; i < listCount; i++)
{
Debug.Assert(arrOffset < arrLength, "IList.Count must have been faulty, returning a negative value and/or returning a different value across calls.");
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(arrOffset, arrLength, nameof(socketList));
Socket? socket = socketList[i] as Socket;
if (socket == null)
{
throw new ArgumentException(SR.Format(SR.net_sockets_select, socket?.GetType().FullName ?? "null", typeof(Socket).FullName), nameof(socketList));
}
bool success = false;
socket.InternalSafeHandle.DangerousAddRef(ref success);
int fd = (int)socket.InternalSafeHandle.DangerousGetHandle();
arr[arrOffset++] = new Interop.PollEvent { Events = events, FileDescriptor = fd };
refsAdded++;
}
}
private static unsafe void FilterPollList(IList? socketList, Interop.PollEvent* arr, int arrEndOffset, Interop.PollEvents desiredEvents, ref int refsAdded)
{
if (socketList == null)
return;
// The Select API requires leaving in the input lists only those sockets that were ready. As such, we need to loop
// through each poll event, and for each that wasn't ready, remove the corresponding Socket from its list. Technically
// this is O(n^2), due to removing from the list requiring shifting down all elements after it. However, this doesn't
// happen with the most common cases. If very few sockets were ready, then as we iterate from the end of the list, each
// removal will typically be O(1) rather than O(n). If most sockets were ready, then we only need to remove a few, in
// which case we're only doing a small number of O(n) shifts. It's only for the intermediate case, where a non-trivial
// number of sockets are ready and a non-trivial number of sockets are not ready that we end up paying the most. We could
// avoid these costs by, for example, allocating a side list that we fill with the sockets that should remain, clearing
// the original list, and then populating the original list with the contents of the side list. That of course has its
// own costs, and so for now we do the "simple" thing. This can be changed in the future as needed.
for (int i = socketList.Count - 1; i >= 0; --i, --arrEndOffset)
{
Debug.Assert(arrEndOffset >= 0, "IList.Count must have been faulty, returning a negative value and/or returning a different value across calls.");
ArgumentOutOfRangeException.ThrowIfNegative(arrEndOffset);
if ((arr[arrEndOffset].TriggeredEvents & desiredEvents) == 0)
{
Socket socket = (Socket)socketList[i]!;
socket.InternalSafeHandle.DangerousRelease();
refsAdded--;
socketList.RemoveAt(i);
}
}
}
public static SocketError Shutdown(SafeSocketHandle handle, bool isConnected, bool isDisconnected, SocketShutdown how)
{
Interop.Error err = Interop.Sys.Shutdown(handle, how);
if (err == Interop.Error.SUCCESS)
{
handle.TrackShutdown(how);
return SocketError.Success;
}
// If shutdown returns ENOTCONN and we think that this socket has ever been connected,
// ignore the error. This can happen for TCP connections if the underlying connection
// has reached the CLOSE state. Ignoring the error matches Winsock behavior.
if (err == Interop.Error.ENOTCONN && (isConnected || isDisconnected))
{
handle.TrackShutdown(how);
return SocketError.Success;
}
return GetSocketErrorForErrorCode(err);
}
private static SocketError SendFileAsync(SafeSocketHandle handle, SafeFileHandle fileHandle, long offset, long count, CancellationToken cancellationToken, Action<long, SocketError> callback)
{
long bytesSent;
SocketError socketError = handle.AsyncContext.SendFileAsync(fileHandle, offset, count, out bytesSent, callback, cancellationToken);
if (socketError == SocketError.Success)
{
callback(bytesSent, SocketError.Success);
}
return socketError;
}
public static async Task SendPacketsAsync(
Socket socket, TransmitFileOptions options, SendPacketsElement[] elements, SafeFileHandle[] fileHandles, CancellationToken cancellationToken, Action<long, SocketError> callback)
{
SocketError error = SocketError.Success;
long bytesTransferred = 0;
try
{
Debug.Assert(elements.Length == fileHandles.Length);
for (int i = 0; i < elements.Length; i++)
{
SendPacketsElement e = elements[i];
if (e != null)
{
if (e.MemoryBuffer != null)
{
bytesTransferred = await socket.SendAsync(e.MemoryBuffer.Value, SocketFlags.None, cancellationToken).ConfigureAwait(false) + bytesTransferred;
}
else
{
SafeFileHandle fileHandle = fileHandles[i] ?? e.FileStream!.SafeFileHandle;
long fsLength = RandomAccess.GetLength(fileHandle);
if (e.Count > fsLength - e.OffsetLong)
{
throw new ArgumentOutOfRangeException();
}
var tcs = new TaskCompletionSource<SocketError>();
error = SendFileAsync(socket.InternalSafeHandle, fileHandle, e.OffsetLong,
e.Count > 0 ? e.Count : fsLength - e.OffsetLong,
cancellationToken,
(transferred, se) =>
{
bytesTransferred += transferred;
tcs.TrySetResult(se);
});
if (error == SocketError.IOPending)
{
error = await tcs.Task.ConfigureAwait(false);
}
if (error != SocketError.Success)
{
throw new SocketException((int)error);
}
}
}
}
if ((options & (TransmitFileOptions.Disconnect | TransmitFileOptions.ReuseSocket)) != 0)
{
error = Disconnect(socket, socket.InternalSafeHandle, (options & TransmitFileOptions.ReuseSocket) != 0);
if (error != SocketError.Success)
{
throw new SocketException((int)error);
}
}
}
catch (Exception exc)
{
foreach (SafeFileHandle fs in fileHandles)
{
fs?.Dispose();
}
error =
exc is SocketException se ? se.SocketErrorCode :
exc is ArgumentException ? SocketError.InvalidArgument :
exc is OperationCanceledException ? SocketError.OperationAborted :
SocketError.SocketError;
}
finally
{
callback(bytesTransferred, error);
}
}
internal static SocketError Disconnect(Socket socket, SafeSocketHandle handle, bool reuseSocket)
{
handle.SetToDisconnected();
socket.Shutdown(SocketShutdown.Both);
return reuseSocket ?
socket.ReplaceHandle() :
SocketError.Success;
}
internal static unsafe SafeSocketHandle CreateSocket(IntPtr fileDescriptor)
{
var res = new SafeSocketHandle(fileDescriptor, ownsHandle: true);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, res);
return res;
}
internal static bool HasNonBlockingConnectCompleted(SafeSocketHandle handle, out bool success)
{
Interop.Error err = Interop.Sys.Poll(handle, Interop.PollEvents.POLLOUT, timeout: 0, out Interop.PollEvents outEvents);
if (err != Interop.Error.SUCCESS)
{
throw new SocketException((int)GetSocketErrorForErrorCode(err));
}
// When connect completes the socket is writable.
if ((outEvents & Interop.PollEvents.POLLOUT) == 0)
{
success = false;
return false;
}
// Get the connect result from SocketOptionName.Error.
SocketError errorCode = GetSockOpt(handle, SocketOptionLevel.Socket, SocketOptionName.Error, out int optionValue);
if (errorCode != SocketError.Success)
{
throw new SocketException((int)errorCode);
}
success = (SocketError)optionValue == SocketError.Success;
return true;
}
}
}
|