Skip to content

Commit

Permalink
Implement Socket.DuplicateAndClose() for Windows (#1858)
Browse files Browse the repository at this point in the history
* reimplement socket duplication, fix #1760

* additional cleanup

* ungroup PNSE tests, no RemoteExecutor timeout

* address review findings

* fix path for Interop.WSADuplicateSocket.cs

* remove swallowing in Socket(SocketInformation)

* review suggestions

* make sure duplicate socket is not inheritable

* remove debug code leftover

* harden SocketPal.CreateSocket()

* blittable WSAPROTOCOL_INFOW

* additional naming fixes,

default System.Net.Sockets.sln configurations to Windows_NT again

* disposal on errors, improve comments, more tests

* nits

* handle GetSockName() error
  • Loading branch information
antonfirsov authored Feb 4, 2020
2 parents a05c599 + 4b0ccb2 commit a716fa2
Show file tree
Hide file tree
Showing 18 changed files with 715 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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 Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Kernel32
{
[Flags]
internal enum HandleFlags : uint
{
None = 0,
HANDLE_FLAG_INHERIT = 1,
HANDLE_FLAG_PROTECT_FROM_CLOSE = 2
}

[DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern bool SetHandleInformation(SafeHandle hObject, HandleFlags dwMask, HandleFlags dwFlags);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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;
using System.Net.Sockets;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Winsock
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct WSAPROTOCOLCHAIN
{
private const int MAX_PROTOCOL_CHAIN = 7;

internal int ChainLen;
internal fixed uint ChainEntries[MAX_PROTOCOL_CHAIN];
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal unsafe struct WSAPROTOCOL_INFOW
{
private const int WSAPROTOCOL_LEN = 255;

internal uint dwServiceFlags1;
internal uint dwServiceFlags2;
internal uint dwServiceFlags3;
internal uint dwServiceFlags4;
internal uint dwProviderFlags;
internal Guid ProviderId;
internal uint dwCatalogEntryId;
internal WSAPROTOCOLCHAIN ProtocolChain;
internal int iVersion;
internal AddressFamily iAddressFamily;
internal int iMaxSockAddr;
internal int iMinSockAddr;
internal SocketType iSocketType;
internal ProtocolType iProtocol;
internal int iProtocolMaxOffset;
internal int iNetworkByteOrder;
internal int iSecurityScheme;
internal uint dwMessageSize;
internal uint dwProviderReserved;
internal fixed char szProtocol[WSAPROTOCOL_LEN + 1];
}

[DllImport(Interop.Libraries.Ws2_32, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern unsafe int WSADuplicateSocket(
[In] SafeSocketHandle s,
[In] uint dwProcessId,
[In] WSAPROTOCOL_INFOW* lpProtocolInfo
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
4 changes: 2 additions & 2 deletions src/libraries/System.Net.Sockets/System.Net.Sockets.sln
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ Global
{8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Debug|Any CPU.Build.0 = netcoreapp5.0-Windows_NT-Debug|Any CPU
{8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.ActiveCfg = netcoreapp5.0-Windows_NT-Release|Any CPU
{8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.Build.0 = netcoreapp5.0-Windows_NT-Release|Any CPU
{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Debug|Any CPU.ActiveCfg = netcoreapp5.0-Unix-Debug|Any CPU
{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Debug|Any CPU.Build.0 = netcoreapp5.0-Unix-Debug|Any CPU
{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Debug|Any CPU.ActiveCfg = netcoreapp5.0-Windows_NT-Debug|Any CPU
{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Debug|Any CPU.Build.0 = netcoreapp5.0-Windows_NT-Debug|Any CPU
{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Release|Any CPU.ActiveCfg = netcoreapp5.0-Windows_NT-Release|Any CPU
{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Release|Any CPU.Build.0 = netcoreapp5.0-Windows_NT-Release|Any CPU
{834E3534-6A11-4A8D-923F-35C1E71CCEC3}.Debug|Any CPU.ActiveCfg = netcoreapp5.0-Debug|Any CPU
Expand Down
6 changes: 6 additions & 0 deletions src/libraries/System.Net.Sockets/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,10 @@
<data name="net_sockets_valuetaskmisuse" xml:space="preserve">
<value>A ValueTask returned from an asynchronous socket operation was consumed concurrently. ValueTasks must only ever be awaited once. (Id: {0}).</value>
</data>
<data name="net_sockets_invalid_socketinformation" xml:space="preserve">
<value>The specified value for the socket information is invalid.</value>
</data>
<data name="net_sockets_asyncoperations_notallowed" xml:space="preserve">
<value>Asynchronous operations are not allowed on this socket. The underlying OS handle might have been already bound to an IO completion port.</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>System.Net.Sockets</AssemblyName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down Expand Up @@ -197,6 +197,9 @@
<Compile Include="$(CommonPath)Interop\Windows\WinSock\Interop.WSAConnect.cs">
<Link>Common\Interop\Windows\WinSock\Interop.WSAConnect.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\WinSock\Interop.WSADuplicateSocket.cs">
<Link>Common\Interop\Windows\WinSock\Interop.WSADuplicateSocket.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\WinSock\Interop.WSAGetOverlappedResult.cs">
<Link>Common\Interop\Windows\WinSock\Interop.WSAGetOverlappedResult.cs</Link>
</Compile>
Expand Down Expand Up @@ -236,6 +239,9 @@
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SetFileCompletionNotificationModes.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.SetFileCompletionNotificationModes.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.HandleInformation.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.HandleInformation.cs</Link>
</Compile>
<Compile Include="$(CommonPath)System\Net\CompletionPortHelper.Windows.cs">
<Link>Common\System\Net\CompletionPortHelper.Windows.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -67,6 +68,12 @@ internal ThreadPoolBoundHandle GetOrAllocateThreadPoolBoundHandle(bool trySkipCo
// instead propagate as an ObjectDisposedException.
ThrowSocketDisposedException(exception);
}

if (alreadyBound)
{
throw new InvalidOperationException(SR.net_sockets_asyncoperations_notallowed, exception);
}

throw;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ namespace System.Net.Sockets
{
public partial class Socket
{
public Socket(SocketInformation socketInformation)
{
// This constructor works in conjunction with DuplicateAndClose, which is not supported on Unix.
// 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 file descriptors between processes
// requires Unix Domain Sockets. The programming model is fundamentally different,
// and incompatible with the design of SocketInformation-related methods.
throw new PlatformNotSupportedException(SR.net_sockets_duplicateandclose_notsupported);
}

partial void ValidateForMultiConnect(bool isMultiEndpoint)
{
// ValidateForMultiConnect is called before any {Begin}Connect{Async} call,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,88 @@ 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);
_handle = null;

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 (_addressFamily != AddressFamily.InterNetwork && _addressFamily != AddressFamily.InterNetworkV6)
{
_handle.Dispose();
_handle = null;
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)
{
_rightEndPoint = ep.Create(socketAddress);
}
else if (errorCode == SocketError.InvalidArgument)
{
// Socket is not yet bound.
}
else
{
_handle.Dispose();
_handle = null;
throw new SocketException((int)errorCode);
}

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(timeout: -1);

if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return info;
}

private void EnsureDynamicWinsockMethods()
{
if (_dynamicWinsockMethods == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,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)
{
Expand Down Expand Up @@ -2043,16 +2034,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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,16 @@ 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) == option;
}
}
}
Loading

0 comments on commit a716fa2

Please sign in to comment.