diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs
new file mode 100644
index 00000000000000..66d3dd8b53443a
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Winsock
+ {
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
+ internal struct WSAProtocolChain
+ {
+ internal int ChainLen;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst=7)]
+ internal uint[] ChainEntries;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
+ internal struct WSAProtocolInfo
+ {
+ internal uint ServiceFlags1;
+ internal uint ServiceFlags2;
+ internal uint ServiceFlags3;
+ internal uint ServiceFlags4;
+ internal uint ProviderFlags;
+ internal Guid ProviderId;
+ internal uint CatalogEntryId;
+ internal WSAProtocolChain ProtocolChain;
+ internal int Version;
+ internal AddressFamily AddressFamily;
+ internal int MaxSockAddr;
+ internal int MinSockAddr;
+ internal SocketType SocketType;
+ internal ProtocolType ProtocolType;
+ internal int ProtocolMaxOffset;
+ internal int NetworkByteOrder;
+ internal int SecurityScheme;
+ internal uint MessageSize;
+ internal uint ProviderReserved;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
+ internal string ProtocolName;
+
+ public static readonly int Size = Marshal.SizeOf(typeof(WSAProtocolInfo));
+ }
+
+ [DllImport(Interop.Libraries.Ws2_32, SetLastError = true)]
+ internal static extern unsafe SocketError WSADuplicateSocket(
+ [In] SafeHandle socketHandle,
+ [In] uint targetProcessId,
+ [In] byte* pinnedBuffer
+ );
+ }
+}
diff --git a/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs b/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs
index dacf42640985fa..76043a3d3653e9 100644
--- a/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs
+++ b/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs
@@ -36,5 +36,18 @@ public static void ForceNonBlocking(this Socket socket, bool force)
socket.Blocking = true;
}
}
+
+ public static (Socket, Socket) CreateConnectedSocketPair()
+ {
+ using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ listener.Listen(1);
+
+ Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ client.Connect(listener.LocalEndPoint);
+ Socket server = listener.Accept();
+
+ return (client, server);
+ }
}
}
diff --git a/src/libraries/System.Net.Sockets/src/Resources/Strings.resx b/src/libraries/System.Net.Sockets/src/Resources/Strings.resx
index cad4341429f314..92c78d49cd8e52 100644
--- a/src/libraries/System.Net.Sockets/src/Resources/Strings.resx
+++ b/src/libraries/System.Net.Sockets/src/Resources/Strings.resx
@@ -249,4 +249,7 @@
A ValueTask returned from an asynchronous socket operation was consumed concurrently. ValueTasks must only ever be awaited once. (Id: {0}).
+
+ The specified value for the socket information is invalid.
+
diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj
index 5126d460fccdd2..3c15df46e493f6 100644
--- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj
+++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj
@@ -1,4 +1,4 @@
-
+
System.Net.Sockets
true
@@ -197,6 +197,9 @@
Common\Interop\Windows\WinSock\Interop.WSAConnect.cs
+
+ Common\Interop\Windows\WinSock\Interop.WSADuplicateSocket.cs
+
Common\Interop\Windows\WinSock\Interop.WSAGetOverlappedResult.cs
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Windows.cs
index 8a28356077a783..0ac2ff1a5b61e4 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Windows.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Windows.cs
@@ -59,6 +59,7 @@ internal ThreadPoolBoundHandle GetOrAllocateThreadPoolBoundHandle(bool trySkipCo
catch (Exception exception) when (!ExceptionCheck.IsFatal(exception))
{
bool closed = IsClosed;
+ bool alreadyBound = !IsInvalid && !IsClosed && (exception is ArgumentException);
CloseAsIs(abortive: false);
if (closed)
{
@@ -67,6 +68,12 @@ internal ThreadPoolBoundHandle GetOrAllocateThreadPoolBoundHandle(bool trySkipCo
// instead propagate as an ObjectDisposedException.
ThrowSocketDisposedException(exception);
}
+
+ if (alreadyBound)
+ {
+ throw new InvalidOperationException("Asynchronous operations are not allowed on this socket. It's handle might have been previously bound to a Thread Pool / IOCP port.");
+ }
+
throw;
}
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs
index 5afe05b6cfa71a..bd195ce4a61c3b 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs
@@ -11,6 +11,25 @@ namespace System.Net.Sockets
{
public partial class Socket
{
+ public Socket(SocketInformation socketInformation)
+ {
+ //
+ // This constructor works in conjunction with DuplicateAndClose, which is not supported.
+ // See comments in DuplicateAndClose.
+ //
+ throw new PlatformNotSupportedException(SR.net_sockets_duplicateandclose_notsupported);
+ }
+
+ public SocketInformation DuplicateAndClose(int targetProcessId)
+ {
+ //
+ // DuplicateAndClose is not supported on Unix, since passing FD-s between processes
+ // should involve Unix Domain Sockets. This programming model is fundamentally different,
+ // and incompatible with the design of SocketInformation API-s.
+ //
+ throw new PlatformNotSupportedException(SR.net_sockets_duplicateandclose_notsupported);
+ }
+
partial void ValidateForMultiConnect(bool isMultiEndpoint)
{
// ValidateForMultiConnect is called before any {Begin}Connect{Async} call,
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs
index 53065619f682c8..8a9919a141eaf3 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs
@@ -17,6 +17,85 @@ public partial class Socket
internal void ReplaceHandleIfNecessaryAfterFailedConnect() { /* nop on Windows */ }
+ public Socket(SocketInformation socketInformation)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ InitializeSockets();
+
+ SocketError errorCode = SocketPal.CreateSocket(socketInformation, out _handle,
+ ref _addressFamily, ref _socketType, ref _protocolType);
+
+ if (errorCode != SocketError.Success)
+ {
+ Debug.Assert(_handle.IsInvalid);
+
+ if (errorCode == SocketError.InvalidArgument)
+ {
+ throw new ArgumentException(SR.net_sockets_invalid_socketinformation, nameof(socketInformation));
+ }
+
+ // Failed to create the socket, throw.
+ throw new SocketException((int)errorCode);
+ }
+
+ if (_handle.IsInvalid)
+ {
+ throw new SocketException();
+ }
+
+ if (_addressFamily != AddressFamily.InterNetwork && _addressFamily != AddressFamily.InterNetworkV6)
+ {
+ throw new NotSupportedException(SR.net_invalidversion);
+ }
+
+ _isConnected = socketInformation.GetOption(SocketInformationOptions.Connected);
+ _willBlock = !socketInformation.GetOption(SocketInformationOptions.NonBlocking);
+ InternalSetBlocking(_willBlock);
+ _isListening = socketInformation.GetOption(SocketInformationOptions.Listening);
+
+ IPAddress tempAddress = _addressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any;
+ IPEndPoint ep = new IPEndPoint(tempAddress, 0);
+
+ Internals.SocketAddress socketAddress = IPEndPointExtensions.Serialize(ep);
+ errorCode = SocketPal.GetSockName(_handle, socketAddress.Buffer, ref socketAddress.InternalSize);
+ if (errorCode == SocketError.Success)
+ {
+ try
+ {
+ _rightEndPoint = ep.Create(socketAddress);
+ }
+ catch
+ {
+ }
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ }
+
+ public SocketInformation DuplicateAndClose(int targetProcessId)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this, targetProcessId);
+
+ ThrowIfDisposed();
+
+ SocketError errorCode = SocketPal.DuplicateSocket(_handle, targetProcessId, out SocketInformation info);
+
+ if (errorCode != SocketError.Success)
+ {
+ throw new SocketException((int)errorCode);
+ }
+
+ info.SetOption(SocketInformationOptions.Connected, Connected);
+ info.SetOption(SocketInformationOptions.NonBlocking, !Blocking);
+ info.SetOption(SocketInformationOptions.Listening, _isListening);
+
+ Close(-1);
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ return info;
+ }
+
private void EnsureDynamicWinsockMethods()
{
if (_dynamicWinsockMethods == null)
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs
index cfa04ff669a8c7..5ab52ce72f6d61 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs
@@ -92,7 +92,6 @@ public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType p
if (errorCode != SocketError.Success)
{
Debug.Assert(_handle.IsInvalid);
-
// Failed to create the socket, throw.
throw new SocketException((int)errorCode);
}
@@ -106,15 +105,6 @@ public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType p
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
}
- public Socket(SocketInformation socketInformation)
- {
- //
- // This constructor works in conjunction with DuplicateAndClose, which is not supported.
- // See comments in DuplicateAndClose.
- //
- throw new PlatformNotSupportedException(SR.net_sockets_duplicateandclose_notsupported);
- }
-
// Called by the class to create a socket to accept an incoming request.
private Socket(SafeSocketHandle fd)
{
@@ -2043,16 +2033,7 @@ private bool CanUseConnectEx(EndPoint remoteEP)
(_rightEndPoint != null || remoteEP.GetType() == typeof(IPEndPoint));
}
- public SocketInformation DuplicateAndClose(int targetProcessId)
- {
- //
- // On Windows, we cannot duplicate a socket that is bound to an IOCP. In this implementation, we *only*
- // support IOCPs, so this will not work.
- //
- // On Unix, duplication of a socket into an arbitrary process is not supported at all.
- //
- throw new PlatformNotSupportedException(SR.net_sockets_duplicateandclose_notsupported);
- }
+
internal IAsyncResult UnsafeBeginConnect(EndPoint remoteEP, AsyncCallback callback, object state, bool flowContext = false)
{
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketInformation.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketInformation.cs
index 2f9e510a3978a2..95ed8a183047a1 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketInformation.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketInformation.cs
@@ -2,11 +2,24 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Runtime.Serialization;
+
namespace System.Net.Sockets
{
public struct SocketInformation
{
public byte[] ProtocolInformation { get; set; }
public SocketInformationOptions Options { get; set; }
+
+ internal void SetOption(SocketInformationOptions option, bool value)
+ {
+ if (value) Options |= option;
+ else Options &= ~option;
+ }
+
+ internal bool GetOption(SocketInformationOptions option)
+ {
+ return ((Options & option) != 0);
+ }
}
}
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs
index dad69e95d86906..7e209c250e6e84 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs
@@ -8,6 +8,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
@@ -49,6 +50,43 @@ public static SocketError CreateSocket(AddressFamily addressFamily, SocketType s
return socket.IsInvalid ? GetLastSocketError() : SocketError.Success;
}
+ public static unsafe SocketError CreateSocket(
+ SocketInformation socketInformation,
+ out SafeSocketHandle socket,
+ ref AddressFamily addressFamily,
+ ref SocketType socketType,
+ ref ProtocolType protocolType)
+ {
+ if (socketInformation.ProtocolInformation == null || socketInformation.ProtocolInformation.Length < Interop.Winsock.WSAProtocolInfo.Size)
+ {
+ throw new ArgumentException(SR.net_sockets_invalid_socketinformation, nameof(socketInformation));
+ }
+
+ fixed (byte* pinnedBuffer = socketInformation.ProtocolInformation)
+ {
+ IntPtr handle = Interop.Winsock.WSASocketW(
+ (AddressFamily)(-1),
+ (SocketType)(-1),
+ (ProtocolType)(-1),
+ (IntPtr)pinnedBuffer, 0, Interop.Winsock.SocketConstructorFlags.WSA_FLAG_OVERLAPPED);
+
+ socket = new SafeSocketHandle(handle, ownsHandle: true);
+ if (NetEventSource.IsEnabled) NetEventSource.Info(null, socket);
+
+ if (socket.IsInvalid)
+ {
+ return GetLastSocketError();
+ }
+
+ Interop.Winsock.WSAProtocolInfo protocolInfo = Marshal.PtrToStructure((IntPtr)pinnedBuffer);
+ addressFamily = protocolInfo.AddressFamily;
+ socketType = protocolInfo.SocketType;
+ protocolType = protocolInfo.ProtocolType;
+
+ return SocketError.Success;
+ }
+ }
+
public static SocketError SetBlocking(SafeSocketHandle handle, bool shouldBlock, out bool willBlock)
{
int intBlocking = shouldBlock ? 0 : -1;
@@ -1246,5 +1284,18 @@ internal static SocketError Disconnect(Socket socket, SafeSocketHandle handle, b
return errorCode;
}
+
+ internal static unsafe SocketError DuplicateSocket(SafeSocketHandle handle, int targetProcessId, out SocketInformation socketInformation)
+ {
+ socketInformation = new SocketInformation
+ {
+ ProtocolInformation = new byte[Interop.Winsock.WSAProtocolInfo.Size]
+ };
+
+ fixed (byte* pinnedBuffer = socketInformation.ProtocolInformation)
+ {
+ return Interop.Winsock.WSADuplicateSocket(handle, (uint)targetProcessId, pinnedBuffer);
+ }
+ }
}
}
diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs
index d49fe656be0b29..c63500fb0b5319 100644
--- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs
+++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs
@@ -26,21 +26,6 @@ public void SupportsIPv6_MatchesOSSupportsIPv6()
#pragma warning restore
}
- [Fact]
- public void UseOnlyOverlappedIO_AlwaysFalse()
- {
- using (var s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
- {
- Assert.Equal(AddressFamily.InterNetwork, s.AddressFamily);
- Assert.Equal(SocketType.Stream, s.SocketType);
- Assert.Equal(ProtocolType.Tcp, s.ProtocolType);
-
- Assert.False(s.UseOnlyOverlappedIO);
- s.UseOnlyOverlappedIO = true;
- Assert.False(s.UseOnlyOverlappedIO);
- }
- }
-
[Fact]
public void IOControl_FIONREAD_Success()
{
diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs
index 26371fc4b1a18a..832b6ce6d7920f 100644
--- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs
+++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs
@@ -189,7 +189,7 @@ public async Task SyncSendFileGetsCanceledByDispose()
int msDelay = 100;
await RetryHelper.ExecuteAsync(async () =>
{
- (Socket socket1, Socket socket2) = CreateConnectedSocketPair();
+ (Socket socket1, Socket socket2) = SocketTestExtensions.CreateConnectedSocketPair();
using (socket2)
{
Task socketOperation = Task.Run(() =>
@@ -315,20 +315,5 @@ await Task.Factory.FromAsync(
// Clean up the file we created
File.Delete(filename);
}
-
- protected static (Socket, Socket) CreateConnectedSocketPair()
- {
- using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
- {
- listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
- listener.Listen(1);
-
- Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- client.Connect(listener.LocalEndPoint);
- Socket server = listener.Accept();
-
- return (client, server);
- }
- }
}
}
diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs
index cd8d032e2fc91b..9127d0fd35c5df 100644
--- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs
+++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs
@@ -865,7 +865,7 @@ public async Task SendAsync_ConcurrentDispose_SucceedsOrThrowsAppropriateExcepti
for (int i = 0; i < 20; i++) // run multiple times to attempt to force various interleavings
{
- (Socket client, Socket server) = CreateConnectedSocketPair();
+ (Socket client, Socket server) = SocketTestExtensions.CreateConnectedSocketPair();
using (client)
using (server)
using (var b = new Barrier(2))
@@ -903,7 +903,7 @@ public async Task ReceiveAsync_ConcurrentDispose_SucceedsOrThrowsAppropriateExce
for (int i = 0; i < 20; i++) // run multiple times to attempt to force various interleavings
{
- (Socket client, Socket server) = CreateConnectedSocketPair();
+ (Socket client, Socket server) = SocketTestExtensions.CreateConnectedSocketPair();
using (client)
using (server)
using (var b = new Barrier(2))
@@ -935,21 +935,6 @@ error is SocketException ||
}
}
- protected static (Socket, Socket) CreateConnectedSocketPair()
- {
- using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
- {
- listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
- listener.Listen(1);
-
- Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- client.Connect(listener.LocalEndPoint);
- Socket server = listener.Accept();
-
- return (client, server);
- }
- }
-
[Fact]
[PlatformSpecific(~TestPlatforms.OSX)] // Not supported on OSX.
public async Task UdpReceiveGetsCanceledByDispose()
@@ -1018,7 +1003,7 @@ public async Task TcpReceiveSendGetsCanceledByDispose(bool receiveOrSend)
int msDelay = 100;
await RetryHelper.ExecuteAsync(async () =>
{
- (Socket socket1, Socket socket2) = CreateConnectedSocketPair();
+ (Socket socket1, Socket socket2) = SocketTestExtensions.CreateConnectedSocketPair();
using (socket2)
{
Task socketOperation;
@@ -1117,7 +1102,7 @@ public async Task TcpPeerReceivesFinOnShutdownWithPendingData()
byte[] receiveBuffer = new byte[1024];
await RetryHelper.ExecuteAsync(async () =>
{
- (Socket socket1, Socket socket2) = CreateConnectedSocketPair();
+ (Socket socket1, Socket socket2) = SocketTestExtensions.CreateConnectedSocketPair();
using (socket1)
using (socket2)
{
@@ -1590,7 +1575,7 @@ public void BlockingRead_DoesntRequireAnotherThreadPoolThread()
ThreadPool.SetMaxThreads(Environment.ProcessorCount, completionPortThreads);
// Create twice that many socket pairs, for good measure.
- (Socket, Socket)[] socketPairs = Enumerable.Range(0, Environment.ProcessorCount * 2).Select(_ => CreateConnectedSocketPair()).ToArray();
+ (Socket, Socket)[] socketPairs = Enumerable.Range(0, Environment.ProcessorCount * 2).Select(_ => SocketTestExtensions.CreateConnectedSocketPair()).ToArray();
try
{
// Ensure that on Unix all of the first socket in each pair are configured for sync-over-async.
diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketDuplicationTests.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketDuplicationTests.cs
new file mode 100644
index 00000000000000..7bcb5746bc2d88
--- /dev/null
+++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketDuplicationTests.cs
@@ -0,0 +1,306 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Drawing.Drawing2D;
+using System.IO;
+using System.IO.Pipes;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Sockets.Tests
+{
+ // Test cases for DuplicateAndClose, Socket(socketInformation), Socket.UseOnlyOverlappedIO,
+ // and asynchronous IO behavior for duplicate sockets.
+ public class SocketDuplicationTests
+ {
+ private readonly ArraySegment _receiveBuffer = new ArraySegment(new byte[32]);
+ const string TestMessage = "test123!";
+ private static ArraySegment TestBytes => Encoding.ASCII.GetBytes(TestMessage);
+ private static string GetMessageString(ArraySegment data, int count) =>
+ Encoding.ASCII.GetString(data.AsSpan().Slice(0, count));
+ private static int CurrentProcessId => Process.GetCurrentProcess().Id;
+
+
+ [Fact]
+ public void UseOnlyOverlappedIO_AlwaysFalse()
+ {
+ using Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+ Assert.False(s.UseOnlyOverlappedIO);
+ s.UseOnlyOverlappedIO = true;
+ Assert.False(s.UseOnlyOverlappedIO);
+ }
+
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [Fact]
+ public void DuplicateAndClose_TargetProcessDoesNotExist_Throws_SocketException()
+ {
+ using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+
+ Assert.Throws(() => socket.DuplicateAndClose(-1));
+ }
+
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [Fact]
+ public void DuplicateAndClose_WhenDisposed_Throws()
+ {
+ Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ socket.Dispose();
+
+ Assert.Throws(() => socket.DuplicateAndClose(CurrentProcessId));
+ }
+
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void BlockingState_IsTransferred(bool blocking)
+ {
+ using Socket original = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
+ {
+ Blocking = blocking
+ };
+ Assert.Equal(blocking, original.Blocking);
+
+ SocketInformation info = original.DuplicateAndClose(CurrentProcessId);
+
+ using Socket clone = new Socket(info);
+ Assert.Equal(blocking, clone.Blocking);
+ }
+
+ public class NotSupportedOnUnix
+ {
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ [Fact]
+ public void SocketCtr_SocketInformation()
+ {
+ SocketInformation socketInformation = default;
+ Assert.Throws(() => new Socket(socketInformation));
+ }
+
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ [Fact]
+ public void DuplicateAndClose()
+ {
+ using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ int processId = CurrentProcessId;
+
+ Assert.Throws(() => socket.DuplicateAndClose(processId));
+ }
+ }
+
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [Fact]
+ public async Task DuplicateAndClose_TcpClient()
+ {
+ using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ listener.Listen(1);
+
+ using Socket client0 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+ using Socket client1 = new Socket(client0.DuplicateAndClose(CurrentProcessId));
+ Assert.False(client1.Connected);
+ client1.Connect(listener.LocalEndPoint);
+
+ using Socket client2 = new Socket(client1.DuplicateAndClose(CurrentProcessId));
+ Assert.True(client2.Connected);
+
+ using Socket handler = await listener.AcceptAsync();
+ await client2.SendAsync(TestBytes, SocketFlags.None);
+
+ int rcvCount = await handler.ReceiveAsync(_receiveBuffer, SocketFlags.None);
+
+ string receivedMessage = GetMessageString(_receiveBuffer, rcvCount);
+ Assert.Equal(TestMessage, receivedMessage);
+ }
+
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [Fact]
+ public async Task DuplicateAndClose_TcpListener()
+ {
+ using Socket listener0 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ listener0.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ listener0.Listen(1);
+
+ using Socket listener1 = new Socket(listener0.DuplicateAndClose(CurrentProcessId));
+
+ using Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ _ = client.ConnectAsync(listener1.LocalEndPoint);
+
+ using Socket handler = await listener1.AcceptAsync();
+ await client.SendAsync(TestBytes, SocketFlags.None);
+
+ byte[] receivedBuffer = new byte[32];
+ int rcvCount = await handler.ReceiveAsync(new ArraySegment(receivedBuffer), SocketFlags.None);
+
+ string receivedMessage = GetMessageString(receivedBuffer, rcvCount);
+ Assert.Equal(TestMessage, receivedMessage);
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public async Task DoAsyncOperation_OnBothOriginalAndClone_ThrowsInvalidOperationException()
+ {
+ // Not applicable for synchronous operations:
+ (Socket client, Socket originalServer) = SocketTestExtensions.CreateConnectedSocketPair();
+
+ using (client)
+ using (originalServer)
+ {
+ client.Send(TestBytes);
+
+ await originalServer.ReceiveAsync(_receiveBuffer, SocketFlags.None);
+
+ SocketInformation info = originalServer.DuplicateAndClose(CurrentProcessId);
+
+ using Socket cloneServer = new Socket(info);
+ await Assert.ThrowsAsync(() =>
+ cloneServer.ReceiveAsync(_receiveBuffer, SocketFlags.None));
+ }
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void SocketCtr_SocketInformation_WhenProtocolInformationIsNull_Throws()
+ {
+ SocketInformation socketInformation = default;
+
+ ArgumentException ex = Assert.Throws(() => new Socket(socketInformation));
+ Assert.Equal("socketInformation", ex.ParamName);
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void SocketCtr_SocketInformation_WhenProtocolInformationTooShort_Throws()
+ {
+ SocketInformation socketInformation = new SocketInformation() {ProtocolInformation = new byte[4]};
+
+ ArgumentException ex = Assert.Throws(() => new Socket(socketInformation));
+ Assert.Equal("socketInformation", ex.ParamName);
+ }
+
+ // A smaller subset of the tests is being executed against the different Send/Receive implementations of Socket
+ // to make sure async IO works as expected in all of those cases.
+ public abstract class PolymorphicTests where T : SocketHelperBase, new()
+ {
+ private static readonly T Helper = new T();
+ private readonly string _ipcPipeName = Path.GetRandomFileName();
+
+ private static void WriteSocketInfo(Stream stream, SocketInformation socketInfo)
+ {
+ BinaryWriter bw = new BinaryWriter(stream);
+ bw.Write((int)socketInfo.Options);
+ bw.Write(socketInfo.ProtocolInformation.Length);
+ bw.Write(socketInfo.ProtocolInformation);
+ }
+
+ private static SocketInformation ReadSocketInfo(Stream stream)
+ {
+ BinaryReader br = new BinaryReader(stream);
+ SocketInformationOptions options = (SocketInformationOptions)br.ReadInt32();
+ int protocolInfoLength = br.ReadInt32();
+ SocketInformation result = new SocketInformation()
+ {
+ Options = options, ProtocolInformation = new byte[protocolInfoLength]
+ };
+ br.Read(result.ProtocolInformation);
+ return result;
+ }
+
+ [Theory]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task DuplicateAndClose_TcpServerHandler(bool sameProcess)
+ {
+ using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ using Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+ listener.BindToAnonymousPort(IPAddress.Loopback);
+ listener.Listen(1);
+
+ client.Connect(listener.LocalEndPoint);
+
+ // Async is allowed on the listener:
+ using Socket handlerOriginal = await listener.AcceptAsync();
+
+ // pipe used to exchange socket info
+ await using NamedPipeServerStream pipeServerStream =
+ new NamedPipeServerStream(_ipcPipeName, PipeDirection.Out);
+
+ if (sameProcess)
+ {
+ Task handlerCode = Task.Run(() => HandlerServerCode(_ipcPipeName));
+ RunCommonHostLogic(CurrentProcessId);
+ await handlerCode;
+ }
+ else
+ {
+ RemoteInvokeOptions options = new RemoteInvokeOptions() {TimeOut = 500};
+ using RemoteInvokeHandle hServerProc =
+ RemoteExecutor.Invoke(HandlerServerCode, _ipcPipeName, options);
+ RunCommonHostLogic(hServerProc.Process.Id);
+ }
+
+ void RunCommonHostLogic(int processId)
+ {
+ pipeServerStream.WaitForConnection();
+
+ // Duplicate the socket:
+ SocketInformation socketInfo = handlerOriginal.DuplicateAndClose(processId);
+ WriteSocketInfo(pipeServerStream, socketInfo);
+
+ // Send client data:
+ client.Send(TestBytes);
+ }
+
+ static async Task HandlerServerCode(string ipcPipeName)
+ {
+ await using NamedPipeClientStream pipeClientStream =
+ new NamedPipeClientStream(".", ipcPipeName, PipeDirection.In);
+ pipeClientStream.Connect();
+
+ SocketInformation socketInfo = ReadSocketInfo(pipeClientStream);
+ using Socket handler = new Socket(socketInfo);
+
+ Assert.True(handler.IsBound);
+ Assert.NotNull(handler.RemoteEndPoint);
+ Assert.NotNull(handler.LocalEndPoint);
+
+ byte[] data = new byte[32];
+
+ int rcvCount = await Helper.ReceiveAsync(handler, new ArraySegment(data));
+ string actual = GetMessageString(data, rcvCount);
+
+ Assert.Equal(TestMessage, actual);
+
+ return RemoteExecutor.SuccessExitCode;
+ }
+ }
+ }
+
+ public class Synchronous : PolymorphicTests
+ {
+ }
+
+ public class Apm : PolymorphicTests
+ {
+ }
+
+ public class TaskBased : PolymorphicTests
+ {
+ }
+
+ public class Eap : PolymorphicTests
+ {
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketInformationTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketInformationTest.cs
index 3adc5f253b8912..d0cabfe15e4237 100644
--- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketInformationTest.cs
+++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketInformationTest.cs
@@ -9,16 +9,6 @@ namespace System.Net.Sockets.Tests
{
public class SocketInformationTest
{
- [Fact]
- public void Socket_Ctor_DuplicateAndClose_Throw()
- {
- Assert.Throws(() => new Socket(new SocketInformation()));
- using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
- {
- Assert.Throws(() => s.DuplicateAndClose(0));
- }
- }
-
[Fact]
public void Properties_Roundtrip()
{
diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj
index 2759f21e0a6e0f..829ac7fa35156b 100644
--- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj
+++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj
@@ -33,6 +33,7 @@
+