diff --git a/src/Common/src/Interop/Windows/Winsock/Interop.SocketConstructorFlags.cs b/src/Common/src/Interop/Windows/Winsock/Interop.SocketConstructorFlags.cs new file mode 100644 index 000000000000..f3b61aab6252 --- /dev/null +++ b/src/Common/src/Interop/Windows/Winsock/Interop.SocketConstructorFlags.cs @@ -0,0 +1,25 @@ +// 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 + { + // Used as last parameter to WSASocket call. + [Flags] + internal enum SocketConstructorFlags + { + WSA_FLAG_OVERLAPPED = 0x01, + WSA_FLAG_MULTIPOINT_C_ROOT = 0x02, + WSA_FLAG_MULTIPOINT_C_LEAF = 0x04, + WSA_FLAG_MULTIPOINT_D_ROOT = 0x08, + WSA_FLAG_MULTIPOINT_D_LEAF = 0x10, + WSA_FLAG_NO_HANDLE_INHERIT = 0x80, + } + } +} diff --git a/src/Common/src/Interop/Windows/Winsock/Interop.WSASocketW.SafeCloseSocket.cs b/src/Common/src/Interop/Windows/Winsock/Interop.WSASocketW.SafeCloseSocket.cs index 36e7a404078e..7dac8e9e30cb 100644 --- a/src/Common/src/Interop/Windows/Winsock/Interop.WSASocketW.SafeCloseSocket.cs +++ b/src/Common/src/Interop/Windows/Winsock/Interop.WSASocketW.SafeCloseSocket.cs @@ -10,17 +10,6 @@ internal static partial class Interop { internal static partial class Winsock { - // Used as last parameter to WSASocket call. - [Flags] - internal enum SocketConstructorFlags - { - WSA_FLAG_OVERLAPPED = 0x01, - WSA_FLAG_MULTIPOINT_C_ROOT = 0x02, - WSA_FLAG_MULTIPOINT_C_LEAF = 0x04, - WSA_FLAG_MULTIPOINT_D_ROOT = 0x08, - WSA_FLAG_MULTIPOINT_D_LEAF = 0x10, - } - [DllImport(Interop.Libraries.Ws2_32, CharSet = CharSet.Unicode, SetLastError = true)] internal static extern SafeCloseSocket.InnerSafeCloseSocket WSASocketW( [In] AddressFamily addressFamily, diff --git a/src/Common/src/System/Net/SafeCloseSocket.Windows.cs b/src/Common/src/System/Net/SafeCloseSocket.Windows.cs index bc1cdf754a38..1cb22cbd0dfb 100644 --- a/src/Common/src/System/Net/SafeCloseSocket.Windows.cs +++ b/src/Common/src/System/Net/SafeCloseSocket.Windows.cs @@ -217,7 +217,7 @@ private SocketError InnerReleaseHandle() internal static InnerSafeCloseSocket CreateWSASocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) { - InnerSafeCloseSocket result = Interop.Winsock.WSASocketW(addressFamily, socketType, protocolType, IntPtr.Zero, 0, Interop.Winsock.SocketConstructorFlags.WSA_FLAG_OVERLAPPED); + InnerSafeCloseSocket result = Interop.Winsock.WSASocketW(addressFamily, socketType, protocolType, IntPtr.Zero, 0, Interop.Winsock.SocketConstructorFlags.WSA_FLAG_OVERLAPPED | Interop.Winsock.SocketConstructorFlags.WSA_FLAG_NO_HANDLE_INHERIT); if (result.IsInvalid) { result.SetHandleAsInvalid(); diff --git a/src/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs b/src/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs index 9717a2dda11a..9ab7db460425 100644 --- a/src/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs +++ b/src/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs @@ -63,7 +63,7 @@ private static bool IsProtocolSupported(AddressFamily af) try { - s = Interop.Winsock.WSASocketW(af, SocketType.Dgram, 0, IntPtr.Zero, 0, 0); + s = Interop.Winsock.WSASocketW(af, SocketType.Dgram, 0, IntPtr.Zero, 0, (int)Interop.Winsock.SocketConstructorFlags.WSA_FLAG_NO_HANDLE_INHERIT); if (s == IntPtr.Zero) { diff --git a/src/Native/Unix/System.Native/pal_networking.c b/src/Native/Unix/System.Native/pal_networking.c index 380d7a24c468..96ffcc5c4a12 100644 --- a/src/Native/Unix/System.Native/pal_networking.c +++ b/src/Native/Unix/System.Native/pal_networking.c @@ -1978,7 +1978,15 @@ int32_t SystemNative_Socket(int32_t addressFamily, int32_t socketType, int32_t p platformSocketType |= SOCK_CLOEXEC; #endif *createdSocket = socket(platformAddressFamily, platformSocketType, platformProtocolType); - return *createdSocket != -1 ? Error_SUCCESS : SystemNative_ConvertErrorPlatformToPal(errno); + if (*createdSocket == -1) + { + return SystemNative_ConvertErrorPlatformToPal(errno); + } + +#ifndef SOCK_CLOEXEC + fcntl(ToFileDescriptor(*createdSocket), F_SETFD, FD_CLOEXEC); // ignore any failures; this is best effort +#endif + return Error_SUCCESS; } int32_t SystemNative_GetAtOutOfBandMark(intptr_t socket, int32_t* atMark) diff --git a/src/System.Net.NameResolution/src/System.Net.NameResolution.csproj b/src/System.Net.NameResolution/src/System.Net.NameResolution.csproj index 61c73ebcdb26..a7e6b46976a6 100644 --- a/src/System.Net.NameResolution/src/System.Net.NameResolution.csproj +++ b/src/System.Net.NameResolution/src/System.Net.NameResolution.csproj @@ -103,6 +103,9 @@ Interop\Windows\Winsock\Interop.WSASocketW.cs + + Interop\Windows\Winsock\Interop.SocketConstructorFlags.cs + Common\System\Net\Sockets\ProtocolFamily.cs @@ -198,4 +201,4 @@ - \ No newline at end of file + diff --git a/src/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj b/src/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj index 373429d382fe..ba2fc45c19aa 100644 --- a/src/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj +++ b/src/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj @@ -108,6 +108,9 @@ Interop\Windows\Winsock\Interop.WSASocketW.cs + + Interop\Windows\Winsock\Interop.SocketConstructorFlags.cs + Common\System\Net\Sockets\ProtocolFamily.cs @@ -183,4 +186,4 @@ Interop\Unix\System.Native\Interop.SocketAddress.cs - \ No newline at end of file + diff --git a/src/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj b/src/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj index 90b86db3506e..b45e5145f515 100644 --- a/src/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj +++ b/src/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj @@ -206,6 +206,9 @@ Interop\Windows\Winsock\Interop.WSASocketW.SafeCloseSocket.cs + + Interop\Windows\Winsock\Interop.SocketConstructorFlags.cs + Interop\Windows\Winsock\WSABuffer.cs @@ -358,4 +361,4 @@ - \ No newline at end of file + diff --git a/src/System.Net.Ping/src/System.Net.Ping.csproj b/src/System.Net.Ping/src/System.Net.Ping.csproj index 53fe1d44c5ae..d67821b68447 100644 --- a/src/System.Net.Ping/src/System.Net.Ping.csproj +++ b/src/System.Net.Ping/src/System.Net.Ping.csproj @@ -123,6 +123,9 @@ Common\Interop\Windows\Winsock\Interop.WSASocketW.cs + + Interop\Windows\Winsock\Interop.SocketConstructorFlags.cs + Common\System\Net\Sockets\SocketType.cs @@ -151,4 +154,4 @@ - \ No newline at end of file + diff --git a/src/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/System.Net.Sockets/src/System.Net.Sockets.csproj index cf8db1b0d0cc..d462e97809f7 100644 --- a/src/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -228,6 +228,9 @@ Interop\Windows\Winsock\Interop.WSASocketW.SafeCloseSocket.cs + + Interop\Windows\Winsock\Interop.SocketConstructorFlags.cs + Interop\Windows\Winsock\SafeNativeOverlapped.cs @@ -403,4 +406,4 @@ - \ No newline at end of file + diff --git a/src/System.Net.Sockets/tests/FunctionalTests/CreateSocketTests.cs b/src/System.Net.Sockets/tests/FunctionalTests/CreateSocketTests.cs index ebbcc89011a7..58ab9d4714b8 100644 --- a/src/System.Net.Sockets/tests/FunctionalTests/CreateSocketTests.cs +++ b/src/System.Net.Sockets/tests/FunctionalTests/CreateSocketTests.cs @@ -2,11 +2,15 @@ // 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.IO; +using System.IO.Pipes; +using System.Threading.Tasks; using Xunit; namespace System.Net.Sockets.Tests { - public class CreateSocket + public class CreateSocket : RemoteExecutorTestBase { public static object[][] DualModeSuccessInputs = { new object[] { SocketType.Stream, ProtocolType.Tcp }, @@ -89,9 +93,95 @@ public void Ctor_Failure(AddressFamily addressFamily, SocketType socketType, Pro [ConditionalTheory(nameof(SupportsRawSockets))] public void Ctor_Raw_Success(AddressFamily addressFamily, ProtocolType protocolType) { - using (new Socket(addressFamily, SocketType.Raw, protocolType)) - { - } + using (new Socket(addressFamily, SocketType.Raw, protocolType)) + { + } + } + + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Sockets are still inheritable on netfx: https://github.com/dotnet/corefx/pull/32903")] + [Theory] + [InlineData(true, 0)] // Accept + [InlineData(false, 0)] + [InlineData(true, 1)] // AcceptAsync + [InlineData(false, 1)] + [InlineData(true, 2)] // Begin/EndAccept + [InlineData(false, 2)] + public void CtorAndAccept_SocketNotKeptAliveViaInheritance(bool validateClientOuter, int acceptApiOuter) + { + // Run the test in another process so as to not have trouble with other tests + // launching child processes that might impact inheritance. + RemoteInvoke((validateClientString, acceptApiString) => + { + bool validateClient = bool.Parse(validateClientString); + int acceptApi = int.Parse(acceptApiString); + + // Create a listening server. + using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + listener.Listen(int.MaxValue); + EndPoint ep = listener.LocalEndPoint; + + // Create a client and connect to that listener. + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + client.Connect(ep); + + // Accept the connection using one of multiple accept mechanisms. + Socket server = + acceptApi == 0 ? listener.Accept() : + acceptApi == 1 ? listener.AcceptAsync().GetAwaiter().GetResult() : + acceptApi == 2 ? Task.Factory.FromAsync(listener.BeginAccept, listener.EndAccept, null).GetAwaiter().GetResult() : + throw new Exception($"Unexpected {nameof(acceptApi)}: {acceptApi}"); + + // Get streams for the client and server, and create a pipe that we'll use + // to communicate with a child process. + using (var serverStream = new NetworkStream(server, ownsSocket: true)) + using (var clientStream = new NetworkStream(client, ownsSocket: true)) + using (var serverPipe = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)) + { + // Create a child process that blocks waiting to receive a signal on the anonymous pipe. + // The whole purpose of the child is to test whether handles are inherited, so we + // keep the child process alive until we're done validating that handles close as expected. + using (RemoteInvoke(clientPipeHandle => + { + using (var clientPipe = new AnonymousPipeClientStream(PipeDirection.In, clientPipeHandle)) + { + Assert.Equal(42, clientPipe.ReadByte()); + } + }, serverPipe.GetClientHandleAsString())) + { + if (validateClient) // Validate that the child isn't keeping alive the "new Socket" for the client + { + // Send data from the server to client, then validate the client gets EOF when the server closes. + serverStream.WriteByte(84); + Assert.Equal(84, clientStream.ReadByte()); + serverStream.Close(); + Assert.Equal(-1, clientStream.ReadByte()); + } + else // Validate that the child isn't keeping alive the "listener.Accept" for the server + { + // Send data from the client to server, then validate the server gets EOF when the client closes. + clientStream.WriteByte(84); + Assert.Equal(84, serverStream.ReadByte()); + clientStream.Close(); + Assert.Equal(-1, serverStream.ReadByte()); + } + + // And validate that we after closing the listening socket, we're not able to connect. + listener.Dispose(); + using (var tmpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + Assert.ThrowsAny(() => tmpClient.Connect(ep)); + } + + // Let the child process terminate. + serverPipe.WriteByte(42); + } + } + } + } + }, validateClientOuter.ToString(), acceptApiOuter.ToString()).Dispose(); } } }