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 @@ +