diff --git a/src/libraries/Common/tests/Common.Tests.csproj b/src/libraries/Common/tests/Common.Tests.csproj index 2d094017848e5..d790b945b9da9 100644 --- a/src/libraries/Common/tests/Common.Tests.csproj +++ b/src/libraries/Common/tests/Common.Tests.csproj @@ -1,4 +1,4 @@ - + true false @@ -108,6 +108,9 @@ System\PasteArguments.cs + + + @@ -115,6 +118,7 @@ + diff --git a/src/libraries/Common/tests/System/Net/Configuration.Sockets.cs b/src/libraries/Common/tests/System/Net/Configuration.Sockets.cs index 05a91d57b93e8..6f9096494e598 100644 --- a/src/libraries/Common/tests/System/Net/Configuration.Sockets.cs +++ b/src/libraries/Common/tests/System/Net/Configuration.Sockets.cs @@ -2,6 +2,8 @@ // 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.Threading; + namespace System.Net.Test.Common { public static partial class Configuration @@ -11,6 +13,20 @@ public static partial class Sockets public static Uri SocketServer => GetUriValue("DOTNET_TEST_NET_SOCKETS_SERVERURI", new Uri("http://" + DefaultAzureServer)); public static string InvalidHost => GetValue("DOTNET_TEST_NET_SOCKETS_INVALIDSERVER", "notahostname.invalid.corp.microsoft.com"); + + private static Lazy<(int Min, int Max)> s_portPoolRangeLazy = new Lazy<(int Min, int Max)>(() => + { + string configString = GetValue("DOTNET_TEST_NET_SOCKETS_PORTPOOLRANGE", "17000 22000"); + string[] portRange = configString.Split(' ', StringSplitOptions.RemoveEmptyEntries); + int minPort = int.Parse(portRange[0].Trim()); + int maxPort = int.Parse(portRange[1].Trim()); + return (minPort, maxPort); + }, LazyThreadSafetyMode.PublicationOnly); + + /// + /// Min: inclusive, Max: exclusive. + /// + public static (int Min, int Max) TestPoolPortRange => s_portPoolRangeLazy.Value; } } } diff --git a/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs b/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs index 76043a3d3653e..ae233db011072 100644 --- a/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs +++ b/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs @@ -13,6 +13,12 @@ public static int BindToAnonymousPort(this Socket socket, IPAddress address) return ((IPEndPoint)socket.LocalEndPoint).Port; } + // Binds to an IP address and a port assigned by TestPortPool. + public static PortLease BindToPoolPort(this Socket socket, IPAddress address) + { + return TestPortPool.RentPortAndBindSocket(socket, address); + } + // Binds to an OS-assigned port. public static TcpListener CreateAndStartTcpListenerOnAnonymousPort(out int port) { diff --git a/src/libraries/Common/tests/System/Net/Sockets/TestPortPool.cs b/src/libraries/Common/tests/System/Net/Sockets/TestPortPool.cs new file mode 100644 index 0000000000000..c692d389e019d --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Sockets/TestPortPool.cs @@ -0,0 +1,230 @@ +// 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.Collections.Concurrent; +using System.Diagnostics; +using System.Globalization; +using System.Net.Security; +using System.Net.Test.Common; +using System.Runtime.CompilerServices; +using System.Threading; +using Microsoft.DotNet.RemoteExecutor; + +namespace System.Net.Sockets.Tests +{ + internal readonly struct PortLease : IDisposable + { + public int Port { get; } + + internal PortLease(int port) => Port = port; + + public void Dispose() => TestPortPool.Return(this); + } + + internal class TestPortPoolExhaustedException : Exception + { + public TestPortPoolExhaustedException() + : base($"TestPortPool failed to find an available port after {TestPortPool.ThrowExhaustedAfter} attempts") + { + } + } + + /// + /// Distributes unique ports from the range defined by + /// Useful in socket testing scenarios, where port collisions across protocols are not acceptable. + /// This kind of uniqueness is not guaranteed when binding to OS ephemeral ports, and might lead to issues on Unix. + /// For more information see: + /// https://github.com/dotnet/runtime/issues/19162#issuecomment-523195762 + /// + internal static class TestPortPool + { + internal const int ThrowExhaustedAfter = 10; + + public static PortRange ConfiguredPortRange = new PortRange( + System.Net.Test.Common.Configuration.Sockets.TestPoolPortRange.Min, + System.Net.Test.Common.Configuration.Sockets.TestPoolPortRange.Max); + + + private static readonly ConcurrentDictionary s_usedPorts = GetAllPortsUsedBySystem(); + private static int s_counter = int.MinValue; + + public static PortLease RentPort() + { + for (int i = 0; i < ThrowExhaustedAfter; i++) + { + // Although race may occur theoretically because the following code block is not atomic, + // it requires the s_counter to move at least PortRangeLength steps between Increment and TryAdd, + // which is very unlikely considering the actual port range and the low number of tests utilizing TestPortPool + long portLong = (long)Interlocked.Increment(ref s_counter) - int.MinValue; + portLong = (portLong % ConfiguredPortRange.Length) + ConfiguredPortRange.Min; + int port = (int)portLong; + + if (s_usedPorts.TryAdd(port, 0)) + { + return new PortLease(port); + } + } + + throw new TestPortPoolExhaustedException(); + } + + public static void Return(PortLease portLease) + { + s_usedPorts.TryRemove(portLease.Port, out _); + } + + public static PortLease RentPortAndBindSocket(Socket socket, IPAddress address) + { + PortLease lease = RentPort(); + try + { + socket.Bind(new IPEndPoint(address, lease.Port)); + return lease; + } + catch (SocketException) + { + lease.Dispose(); + throw; + } + } + + // Exclude ports which are unavailable at initialization time + private static ConcurrentDictionary GetAllPortsUsedBySystem() + { + IPEndPoint ep4 = new IPEndPoint(IPAddress.Loopback, 0); + IPEndPoint ep6 = new IPEndPoint(IPAddress.IPv6Loopback, 0); + + bool IsPortUsed(int port, + AddressFamily addressFamily, + SocketType socketType, + ProtocolType protocolType) + { + try + { + IPEndPoint ep = addressFamily == AddressFamily.InterNetwork ? ep4 : ep6; + ep.Port = port; + using Socket socket = new Socket(addressFamily, socketType, protocolType); + socket.Bind(ep); + return false; + } + catch (SocketException) + { + return true; + } + } + + ConcurrentDictionary result = new ConcurrentDictionary(); + + for (int port = ConfiguredPortRange.Min; port < ConfiguredPortRange.Max; port++) + { + if (IsPortUsed(port, AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) || + IsPortUsed(port, AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) || + IsPortUsed(port, AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp) || + IsPortUsed(port, AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp)) + { + result.TryAdd(port, 0); + } + } + + return result; + } + } + + internal readonly struct PortRange + { + public int Min { get; } + public int Max { get; } + + public int Length => Max - Min; + + public PortRange(int min, int max) + { + Min = min; + Max = max; + } + + public override string ToString() => $"({Min} .. {Max})"; + + public static bool AreOverlappingRanges(in PortRange a, in PortRange b) + { + return a.Min < b.Min ? a.Max > b.Min : b.Max > a.Min; + } + + public static PortRange GetDefaultOsDynamicPortRange() + { + if (PlatformDetection.IsWindows) + { + // For TestPortPool functionality, we need to take the intersection of 4 intervals: + PortRange ipv4Tcp = ParseCmdletOutputWindows(RunCmldet("netsh", "int ipv4 show dynamicport tcp")); + PortRange ipv4Udp = ParseCmdletOutputWindows(RunCmldet("netsh", "int ipv4 show dynamicport udp")); + PortRange ipv6Tcp = ParseCmdletOutputWindows(RunCmldet("netsh", "int ipv6 show dynamicport tcp")); + PortRange ipv6Udp = ParseCmdletOutputWindows(RunCmldet("netsh", "int ipv6 show dynamicport udp")); + + int min = Math.Max(ipv4Tcp.Min, Math.Max(ipv4Udp.Min, Math.Max(ipv6Tcp.Min, ipv6Udp.Min))); + int max = Math.Min(ipv4Tcp.Max, Math.Min(ipv4Udp.Max, Math.Min(ipv6Tcp.Max, ipv6Udp.Max))); + return new PortRange(min, max); + } + + if (PlatformDetection.IsOSX) + { + return ParseCmdletOutputMac(RunCmldet("sysctl", "net.inet.ip.portrange.first net.inet.ip.portrange.last")); + } + + // Handle the Linux case otherwise. + // We may need to introduce additional branches as we extend our CI support. + return ParseCmdletOutputLinux(RunCmldet("cat", "/proc/sys/net/ipv4/ip_local_port_range")); + } + + internal static PortRange ParseCmdletOutputWindows(string cmdOutput) + { + PortRange temp = ParseCmdletOutputMac(cmdOutput); + + // On Windows, second number is 'Number of Ports' + return new PortRange(temp.Min, temp.Min + temp.Max); + } + + internal static PortRange ParseCmdletOutputLinux(string cmdOutput) + { + ReadOnlySpan span = cmdOutput.AsSpan(); + int firstWhiteSpace = span.IndexOf('\t'); + if (firstWhiteSpace < 0) firstWhiteSpace = span.IndexOf(' '); + ReadOnlySpan left = span.Slice(0, firstWhiteSpace).Trim(); + ReadOnlySpan right = span.Slice(firstWhiteSpace).Trim(); + return new PortRange(int.Parse(left), int.Parse(right)); + } + + internal static PortRange ParseCmdletOutputMac(string cmdOutput) + { + int semicolon1 = cmdOutput.IndexOf(':', 0); + int eol1 = cmdOutput.IndexOf('\n', semicolon1); + int semicolon2 = cmdOutput.IndexOf(':', eol1); + int eol2 = cmdOutput.IndexOf('\n', semicolon2); + if (eol2 < 0) eol2 = cmdOutput.Length; + + int start = ParseImpl(cmdOutput, semicolon1 + 1, eol1); + int end = ParseImpl(cmdOutput, semicolon2 + 1, eol2); + return new PortRange(start, end); + } + + private static string RunCmldet(string cmdlet, string args) + { + ProcessStartInfo psi = new ProcessStartInfo() + { + FileName = cmdlet, + Arguments = args, + RedirectStandardOutput = true + }; + + using Process process = Process.Start(psi); + process.WaitForExit(10000); + return process.StandardOutput.ReadToEnd(); + } + + private static int ParseImpl(string cmdOutput, int start, int end) + { + ReadOnlySpan span = cmdOutput.AsSpan(start, end - start).Trim(); + return int.Parse(span); + } + } +} diff --git a/src/libraries/Common/tests/Tests/System/Net/TestPortPoolTests.cs b/src/libraries/Common/tests/Tests/System/Net/TestPortPoolTests.cs new file mode 100644 index 0000000000000..2b06be00036fc --- /dev/null +++ b/src/libraries/Common/tests/Tests/System/Net/TestPortPoolTests.cs @@ -0,0 +1,281 @@ +// 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.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Net.Sockets.Tests; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Test.Common +{ + [Collection(nameof(DisableParallelExecution))] + public class TestPortPoolTests + { + [CollectionDefinition(nameof(DisableParallelExecution), DisableParallelization = true)] + public class DisableParallelExecution {} + + private const string OuterLoopReason = + "Tests are relatively long-running, and we do not expect the TestPortPool to be changed frequently"; + + // Port range 25010-25470 is likely unused: + // https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt + private const int FirstUnusedPort = 25010; + + private readonly ITestOutputHelper _output; + + public TestPortPoolTests(ITestOutputHelper output) + { + _output = output; + } + + private static RemoteInvokeOptions CreateRemoteOptions(string portRangeString) + { + return new RemoteInvokeOptions() + { + StartInfo = new ProcessStartInfo() + { + Environment = {["DOTNET_TEST_NET_SOCKETS_PORTPOOLRANGE"] = portRangeString} + } + }; + } + + [Fact] + public static void PortRange_IsConfigurable() + { + RemoteInvokeOptions options = CreateRemoteOptions(" 10 142 "); + + static void RunTest() + { + var range = Configuration.Sockets.TestPoolPortRange; + Assert.Equal(10, range.Min); + Assert.Equal(142, range.Max); + } + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [Fact] + public static void PortRange_HasCorrectDefaults() + { + static void RunTest() + { + var range = Configuration.Sockets.TestPoolPortRange; + + Assert.True(range.Min < range.Max); + Assert.True(range.Max < 32768); + Assert.True(range.Min > 15000); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [OuterLoop(OuterLoopReason)] + [Theory] + [InlineData(FirstUnusedPort, FirstUnusedPort + 300)] + [InlineData(FirstUnusedPort, FirstUnusedPort + 3)] + public static void AllPortsAreWithinRange(int minOuter, int maxOuter) + { + static void RunTest(string minStr, string maxStr) + { + int min = int.Parse(minStr); + int max = int.Parse(maxStr); + int rangeLength = max - min; + + HashSet allVisitedValues = new HashSet(); + for (long i = 0; i < rangeLength * 2 + 42; i++) + { + using PortLease lease = TestPortPool.RentPort(); + allVisitedValues.Add(lease.Port); + } + + Assert.Equal(rangeLength, allVisitedValues.Count); + Assert.Equal(min, allVisitedValues.Min()); + + // Maximum is exclusive: + Assert.Equal(max - 1, allVisitedValues.Max()); + } + + RemoteInvokeOptions options = CreateRemoteOptions($"{minOuter} {maxOuter}"); + RemoteExecutor.Invoke(RunTest, minOuter.ToString(), maxOuter.ToString(), options).Dispose(); + } + + [OuterLoop(OuterLoopReason)] + [Fact] + public void WhenExhausted_Throws() + { + static void RunTest() + { + Assert.Throws(() => + { + for (int i = 0; i < 21; i++) + { + TestPortPool.RentPort(); + } + }); + } + + RemoteInvokeOptions options = CreateRemoteOptions($"{FirstUnusedPort} {FirstUnusedPort + 20}"); + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [OuterLoop(OuterLoopReason)] + [Theory] + [InlineData(1200)] + public void ConcurrentAccess_AssignedPortsAreUnique(int portRangeLength) + { + const int levelOfParallelism = 8; + const int requestPerThread = 200; + const int maxDelayInTicks = 500; + const int returnPortsAfterTicks = 10000; + + static async Task RunTest() + { + Task[] workItems = new Task[levelOfParallelism]; + + ConcurrentDictionary currentPorts = new ConcurrentDictionary(); + + for (int i = 0; i < levelOfParallelism; i++) + { + workItems[i] = Task.Factory.StartNew( ii => + { + Random rnd = new Random((int)ii); + + List livingAssignments = new List(); + + Stopwatch sw = Stopwatch.StartNew(); + long returnPortsAfter = rnd.Next(returnPortsAfterTicks); + + for (int j = 0; j < requestPerThread; j++) + { + Thread.Sleep(TimeSpan.FromTicks(rnd.Next(maxDelayInTicks))); + + PortLease lease = TestPortPool.RentPort(); + + Assert.True(currentPorts.TryAdd(lease.Port, 0), + "Same port has been rented more than once!"); + + livingAssignments.Add(lease); + + if (sw.ElapsedTicks > returnPortsAfter) Reset(); + } + + void Reset() + { + sw.Stop(); + + foreach (PortLease assignment in livingAssignments) + { + Assert.True(currentPorts.TryRemove(assignment.Port, out _)); + assignment.Dispose(); + } + livingAssignments.Clear(); + returnPortsAfter = rnd.Next(returnPortsAfterTicks); + sw.Start(); + } + }, i); + + } + + await Task.WhenAll(workItems); + + return RemoteExecutor.SuccessExitCode; + } + + RemoteInvokeOptions options = CreateRemoteOptions($"{FirstUnusedPort} {FirstUnusedPort + portRangeLength}"); + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [OuterLoop(OuterLoopReason)] + [Fact] + public void TestSocketIntegration() + { + static async Task RunTest() + { + const int levelOfParallelism = 8; + const int requestPerThread = 200; + + Task[] workItems = Enumerable.Repeat(Task.Run(() => + { + for (int i = 0; i < requestPerThread; i++) + { + using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + using PortLease lease = TestPortPool.RentPortAndBindSocket(socket, IPAddress.Loopback); + + Assert.True(socket.IsBound); + IPEndPoint ep = (IPEndPoint)socket.LocalEndPoint; + Assert.Equal(lease.Port, ep.Port); + } + }), levelOfParallelism).ToArray(); + + await Task.WhenAll(workItems); + return RemoteExecutor.SuccessExitCode; + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + // This test case is intended to detect a potential OS configuration changes on CI machines + // that may prevent TestPortPool to operate correctly. The recommended action is to alter TestPortPool range + // in those cases. + // Although this test is relatively long running because of the external process execution it triggers, + // it's better to keep it in the Inner Loop to detect the potential issues fast. + [Fact] + public void ConfiguredPortRange_DoesNotOverlapWith_OsDynamicPortRange() + { + PortRange poolRange = TestPortPool.ConfiguredPortRange; + var osRange = PortRange.GetDefaultOsDynamicPortRange(); + string info = $"TestPortPool port range: {poolRange} | OS Dynamic Port Range: {osRange}"; + _output.WriteLine(info); + + Assert.False(PortRange.AreOverlappingRanges(poolRange, osRange), + $"Overlapping port ranges may prevent correct test execution! {info}" ); + } + + [Fact] + public void PortRange_ParseCmdOutputWindows() + { + const string cmdOutput = @" +Protocol tcp Dynamic Port Range +--------------------------------- +Start Port : 49152 +Number of Ports : 16384 +"; + PortRange range = PortRange.ParseCmdletOutputWindows(cmdOutput); + Assert.Equal(49152, range.Min); + Assert.Equal(49152 + 16384, range.Max); + } + + [Theory] + [InlineData("32768 60999")] + [InlineData("32768 60999\n")] + [InlineData("32768\t\t60999\n")] + public void PortRange_ParseCmdOutputLinux(string cmdOutput) + { + PortRange range = PortRange.ParseCmdletOutputLinux(cmdOutput); + Assert.Equal(32768, range.Min); + Assert.Equal(60999, range.Max); + } + + [Fact] + public void PortRange_ParseCmdOutputMac() + { + const string cmdOutput = @"net.inet.ip.portrange.first: 49152 +net.inet.ip.portrange.last: 65535 +"; + PortRange range = PortRange.ParseCmdletOutputMac(cmdOutput); + Assert.Equal(49152, range.Min); + Assert.Equal(65535, range.Max); + } + } +} diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs index 66a593c502a16..d97371f476a1f 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs @@ -112,12 +112,14 @@ public void ConnectV6IPAddressToV6Host_Success() DualModeConnect_IPAddressToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback, false); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] public void ConnectV4IPAddressToV6Host_Fails() { DualModeConnect_IPAddressToHost_Fails_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] public void ConnectV6IPAddressToV4Host_Fails() { @@ -203,12 +205,14 @@ public void ConnectV6IPEndPointToV6Host_Success() DualModeConnect_IPEndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback, false); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] public void ConnectV4IPEndPointToV6Host_Fails() { DualModeConnect_IPEndPointToHost_Fails_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] public void ConnectV6IPEndPointToV4Host_Fails() { @@ -274,14 +278,15 @@ public void Socket_ConnectV4IPAddressListToV4Host_Throws() } } + [OuterLoop] // Failing connection attempt takes long on Windows [Theory] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use [MemberData(nameof(DualMode_IPAddresses_ListenOn_DualMode_Throws_Data))] public void DualModeConnect_IPAddressListToHost_Throws(IPAddress[] connectTo, IPAddress listenOn, bool dualModeServer) { Assert.ThrowsAny(() => DualModeConnect_IPAddressListToHost_Success(connectTo, listenOn, dualModeServer)); } + [OuterLoop] // Long running [Theory] [MemberData(nameof(DualMode_IPAddresses_ListenOn_DualMode_Success_Data))] public void DualModeConnect_IPAddressListToHost_Success(IPAddress[] connectTo, IPAddress listenOn, bool dualModeServer) @@ -299,6 +304,7 @@ public void DualModeConnect_IPAddressListToHost_Success(IPAddress[] connectTo, I [Trait("IPv6", "true")] public class DualModeConnectToHostString : DualModeBase { + [OuterLoop] // Long running [ConditionalTheory(nameof(LocalhostIsBothIPv4AndIPv6))] [MemberData(nameof(DualMode_Connect_IPAddress_DualMode_Data))] public void DualModeConnect_LoopbackDnsToHost_Helper(IPAddress listenOn, bool dualModeServer) @@ -316,6 +322,7 @@ public void DualModeConnect_LoopbackDnsToHost_Helper(IPAddress listenOn, bool du [Trait("IPv6", "true")] public class DualModeConnectToDnsEndPoint : DualModeBase { + [OuterLoop] // Long running [ConditionalTheory(nameof(LocalhostIsBothIPv4AndIPv6))] [MemberData(nameof(DualMode_Connect_IPAddress_DualMode_Data))] public void DualModeConnect_DnsEndPointToHost_Helper(IPAddress listenOn, bool dualModeServer) @@ -350,12 +357,15 @@ public void Socket_BeginConnectV4IPAddressToV4Host_Throws() [Fact] public Task BeginConnectV4IPAddressToV4Host_Success() => DualModeBeginConnect_IPAddressToHost_Helper(IPAddress.Loopback, IPAddress.Loopback, false); + [OuterLoop] // Long running [Fact] public Task BeginConnectV6IPAddressToV6Host_Success() => DualModeBeginConnect_IPAddressToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback, false); + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] public Task BeginConnectV4IPAddressToV6Host_Fails() => DualModeBeginConnect_IPAddressToHost_Fails_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback); + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] public Task BeginConnectV6IPAddressToV4Host_Fails() => DualModeBeginConnect_IPAddressToHost_Fails_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback); @@ -432,6 +442,7 @@ private async Task DualModeBeginConnect_IPEndPointToHost_Helper(IPAddress connec } } + [OuterLoop] // Long-running [Trait("IPv4", "true")] [Trait("IPv6", "true")] public class DualModeBeginConnect : DualModeBase @@ -506,12 +517,14 @@ public void ConnectAsyncV6IPEndPointToV6Host_Success() DualModeConnectAsync_IPEndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback, false); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] public void ConnectAsyncV4IPEndPointToV6Host_Fails() { DualModeConnectAsync_IPEndPointToHost_Fails_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] public void ConnectAsyncV6IPEndPointToV4Host_Fails() { @@ -568,6 +581,7 @@ private void DualModeConnectAsync_IPEndPointToHost_Fails_Helper(IPAddress connec }); } + [OuterLoop] // Long running [ConditionalTheory(nameof(LocalhostIsBothIPv4AndIPv6))] [MemberData(nameof(DualMode_Connect_IPAddress_DualMode_Data))] public void DualModeConnectAsync_DnsEndPointToHost_Helper(IPAddress listenOn, bool dualModeServer) @@ -594,6 +608,7 @@ public void DualModeConnectAsync_DnsEndPointToHost_Helper(IPAddress listenOn, bo } } + [OuterLoop] // Long running [ConditionalTheory(nameof(LocalhostIsBothIPv4AndIPv6))] [ActiveIssue("https://github.com/dotnet/corefx/issues/20893")] [MemberData(nameof(DualMode_Connect_IPAddress_DualMode_Data))] @@ -720,6 +735,7 @@ public void AcceptV6BoundToAnyV6_Success() Accept_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] [PlatformSpecific(TestPlatforms.Windows)] // https://github.com/dotnet/corefx/issues/5832 public void AcceptV6BoundToSpecificV4_CantConnect() @@ -730,6 +746,7 @@ public void AcceptV6BoundToSpecificV4_CantConnect() }); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] [PlatformSpecific(TestPlatforms.Windows)] // https://github.com/dotnet/corefx/issues/5832 public void AcceptV4BoundToSpecificV6_CantConnect() @@ -740,6 +757,7 @@ public void AcceptV4BoundToSpecificV6_CantConnect() }); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] [PlatformSpecific(TestPlatforms.Windows)] // https://github.com/dotnet/corefx/issues/5832 public void AcceptV6BoundToAnyV4_CantConnect() @@ -799,8 +817,8 @@ public void BeginAcceptV6BoundToAnyV6_Success() DualModeConnect_BeginAccept_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void BeginAcceptV6BoundToSpecificV4_CantConnect() { Assert.Throws(() => @@ -809,8 +827,8 @@ public void BeginAcceptV6BoundToSpecificV4_CantConnect() }); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void BeginAcceptV4BoundToSpecificV6_CantConnect() { Assert.Throws(() => @@ -819,8 +837,8 @@ public void BeginAcceptV4BoundToSpecificV6_CantConnect() }); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void BeginAcceptV6BoundToAnyV4_CantConnect() { Assert.Throws(() => @@ -839,7 +857,8 @@ private void DualModeConnect_BeginAccept_Helper(IPAddress listenOn, IPAddress co { using (Socket serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp)) { - int port = serverSocket.BindToAnonymousPort(listenOn); + using PortLease portLease = serverSocket.BindToPoolPort(listenOn); + int port = portLease.Port; serverSocket.Listen(1); IAsyncResult async = serverSocket.BeginAccept(null, null); SocketClient client = new SocketClient(_log, serverSocket, connectTo, port); @@ -905,8 +924,8 @@ public void AcceptAsyncV6BoundToAnyV6_Success() DualModeConnect_AcceptAsync_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void AcceptAsyncV6BoundToSpecificV4_CantConnect() { Assert.Throws(() => @@ -915,8 +934,8 @@ public void AcceptAsyncV6BoundToSpecificV4_CantConnect() }); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void AcceptAsyncV4BoundToSpecificV6_CantConnect() { Assert.Throws(() => @@ -925,8 +944,8 @@ public void AcceptAsyncV4BoundToSpecificV6_CantConnect() }); } + [OuterLoop] // Failing connection attempt takes long on Windows [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void AcceptAsyncV6BoundToAnyV4_CantConnect() { Assert.Throws(() => @@ -945,7 +964,8 @@ private void DualModeConnect_AcceptAsync_Helper(IPAddress listenOn, IPAddress co { using (Socket serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp)) { - int port = serverSocket.BindToAnonymousPort(listenOn); + using PortLease portLease = serverSocket.BindToPoolPort(listenOn); + int port = portLease.Port; serverSocket.Listen(1); SocketAsyncEventArgs args = new SocketAsyncEventArgs(); @@ -1013,7 +1033,6 @@ private void DualModeConnect_AcceptAsync_Helper(IPAddress listenOn, IPAddress co } } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] public class DualModeConnectionlessSendTo : DualModeBase @@ -1059,7 +1078,6 @@ public void SendToV6IPEndPointToV6Host_Success() } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void SendToV4IPEndPointToV6Host_NotReceived() { Assert.Throws(() => @@ -1069,7 +1087,6 @@ public void SendToV4IPEndPointToV6Host_NotReceived() } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void SendToV6IPEndPointToV4Host_NotReceived() { Assert.Throws(() => @@ -1113,7 +1130,6 @@ private void DualModeSendTo_IPEndPointToHost_Helper(IPAddress connectTo, IPAddre #endregion SendTo Sync } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] public class DualModeConnectionlessBeginSendTo : DualModeBase @@ -1214,7 +1230,6 @@ private void DualModeBeginSendTo_EndPointToHost_Helper(IPAddress connectTo, IPAd #endregion SendTo Begin/End } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] public class DualModeConnectionlessSendToAsync : DualModeBase @@ -1280,7 +1295,6 @@ public void SendToAsyncV6IPEndPointToV6Host_Success() } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void SendToAsyncV4IPEndPointToV6Host_NotReceived() { Assert.Throws(() => @@ -1290,7 +1304,6 @@ public void SendToAsyncV4IPEndPointToV6Host_NotReceived() } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use public void SendToAsyncV6IPEndPointToV4Host_NotReceived() { Assert.Throws(() => @@ -1352,7 +1365,6 @@ private void DualModeSendToAsync_IPEndPointToHost_Helper(IPAddress connectTo, IP #endregion SendTo Async/Event } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] public class DualModeConnectionlessReceiveFrom : DualModeBase @@ -1419,10 +1431,9 @@ public void ReceiveFromV6BoundToAnyV6_Success() ReceiveFrom_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); } + [OuterLoop] // Long running [Fact] - // Binds to a specific port on 'connectTo' which on Unix may already be in use - // Also ReceiveFrom not supported on OSX - [PlatformSpecific(TestPlatforms.Windows)] + [PlatformSpecific(~TestPlatforms.OSX)] // ReceiveFrom not supported on OSX public void ReceiveFromV6BoundToSpecificV4_NotReceived() { Assert.Throws(() => @@ -1432,9 +1443,8 @@ public void ReceiveFromV6BoundToSpecificV4_NotReceived() } [Fact] - // Binds to a specific port on 'connectTo' which on Unix may already be in use - // Also expected behavior is different on OSX and Linux (ArgumentException instead of SocketException) - [PlatformSpecific(TestPlatforms.Windows)] + // Expected behavior is different on OSX and Linux(ArgumentException instead of SocketException) + [PlatformSpecific(~(TestPlatforms.Linux | TestPlatforms.OSX))] public void ReceiveFromV4BoundToSpecificV6_NotReceived() { Assert.Throws(() => @@ -1443,10 +1453,9 @@ public void ReceiveFromV4BoundToSpecificV6_NotReceived() }); } + [OuterLoop] // Long running [Fact] - // Binds to a specific port on 'connectTo' which on Unix may already be in use - // Also ReceiveFrom not supported on OSX - [PlatformSpecific(TestPlatforms.Windows)] + [PlatformSpecific(~TestPlatforms.OSX)] // ReceiveFrom not supported on OSX public void ReceiveFromV6BoundToAnyV4_NotReceived() { Assert.Throws(() => @@ -1483,7 +1492,6 @@ public void ReceiveFrom_NotSupported() } } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] public class DualModeConnectionlessBeginReceiveFrom : DualModeBase @@ -1552,9 +1560,7 @@ public void BeginReceiveFromV6BoundToAnyV6_Success() } [Fact] - // Binds to a specific port on 'connectTo' which on Unix may already be in use - // Also BeginReceiveFrom not supported on OSX - [PlatformSpecific(TestPlatforms.Windows)] + [PlatformSpecific(~TestPlatforms.OSX)] // BeginReceiveFrom not supported on OSX public void BeginReceiveFromV6BoundToSpecificV4_NotReceived() { Assert.Throws(() => @@ -1564,9 +1570,8 @@ public void BeginReceiveFromV6BoundToSpecificV4_NotReceived() } [Fact] - // Binds to a specific port on 'connectTo' which on Unix may already be in use - // Also expected behavior is different on OSX and Linux (ArgumentException instead of TimeoutException) - [PlatformSpecific(TestPlatforms.Windows)] + // Expected behavior is different on OSX and Linux (ArgumentException instead of TimeoutException) + [PlatformSpecific(~(TestPlatforms.Linux | TestPlatforms.OSX))] public void BeginReceiveFromV4BoundToSpecificV6_NotReceived() { Assert.Throws(() => @@ -1576,9 +1581,7 @@ public void BeginReceiveFromV4BoundToSpecificV6_NotReceived() } [Fact] - // Binds to a specific port on 'connectTo' which on Unix may already be in use - // Also BeginReceiveFrom not supported on OSX - [PlatformSpecific(TestPlatforms.Windows)] + [PlatformSpecific(~TestPlatforms.OSX)] // BeginReceiveFrom not supported on OSX public void BeginReceiveFromV6BoundToAnyV4_NotReceived() { Assert.Throws(() => @@ -1599,7 +1602,8 @@ private void BeginReceiveFrom_Helper(IPAddress listenOn, IPAddress connectTo, bo using (Socket serverSocket = new Socket(SocketType.Dgram, ProtocolType.Udp)) { serverSocket.ReceiveTimeout = 500; - int port = serverSocket.BindToAnonymousPort(listenOn); + using PortLease portLease = serverSocket.BindToPoolPort(listenOn); + int port = portLease.Port; EndPoint receivedFrom = new IPEndPoint(connectTo, port); IAsyncResult async = serverSocket.BeginReceiveFrom(new byte[1], 0, 1, SocketFlags.None, ref receivedFrom, null, null); @@ -1632,7 +1636,6 @@ private void BeginReceiveFrom_Helper(IPAddress listenOn, IPAddress connectTo, bo #endregion ReceiveFrom Begin/End } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] public class DualModeConnectionlessReceiveFromAsync : DualModeBase @@ -1795,7 +1798,6 @@ public void ReceiveFromAsync_NotSupported() } } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] public class DualModeConnectionlessReceiveMessageFrom : DualModeBase @@ -1912,7 +1914,7 @@ public void ReceiveMessageFromV6BoundToAnyV6_Success() } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use; ReceiveMessageFrom not supported on OSX + [PlatformSpecific(~TestPlatforms.OSX)] // ReceiveMessageFrom not supported on OSX public void ReceiveMessageFromV6BoundToSpecificV4_NotReceived() { Assert.Throws(() => @@ -1922,7 +1924,7 @@ public void ReceiveMessageFromV6BoundToSpecificV4_NotReceived() } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use; ReceiveMessageFrom not supported on OSX + [PlatformSpecific(~(TestPlatforms.Linux | TestPlatforms.OSX))] // Expected behavior is different on OSX and Linux public void ReceiveMessageFromV4BoundToSpecificV6_NotReceived() { Assert.Throws(() => @@ -1932,7 +1934,7 @@ public void ReceiveMessageFromV4BoundToSpecificV6_NotReceived() } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use; ReceiveMessageFrom not supported on OSX + [PlatformSpecific(~TestPlatforms.OSX)] // ReceiveMessageFrom not supported on OSX public void ReceiveMessageFromV6BoundToAnyV4_NotReceived() { Assert.Throws(() => @@ -1952,7 +1954,8 @@ private void ReceiveMessageFrom_Helper(IPAddress listenOn, IPAddress connectTo, { using (Socket serverSocket = new Socket(SocketType.Dgram, ProtocolType.Udp)) { - int port = serverSocket.BindToAnonymousPort(listenOn); + using PortLease portLease = serverSocket.BindToPoolPort(listenOn); + int port = portLease.Port; EndPoint receivedFrom = new IPEndPoint(connectTo, port); SocketFlags socketFlags = SocketFlags.None; @@ -2467,9 +2470,10 @@ protected static void AssertDualModeEnabled(Socket socket, IPAddress listenOn) protected class SocketServer : IDisposable { private readonly ITestOutputHelper _output; - private Socket _server; + private readonly Socket _server; private Socket _acceptedSocket; - private EventWaitHandle _waitHandle = new AutoResetEvent(false); + private readonly EventWaitHandle _waitHandle = new AutoResetEvent(false); + private readonly PortLease _portLease; public EventWaitHandle WaitHandle { @@ -2489,7 +2493,8 @@ public SocketServer(ITestOutputHelper output, IPAddress address, bool dualMode, _server = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); } - port = _server.BindToAnonymousPort(address); + _portLease = _server.BindToPoolPort(address); + port = _portLease.Port; _server.Listen(1); IPAddress remoteAddress = address.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any; @@ -2519,10 +2524,11 @@ public void Dispose() try { _server.Dispose(); - if (_acceptedSocket != null) - _acceptedSocket.Dispose(); + _acceptedSocket?.Dispose(); } catch (Exception) { } + + _portLease.Dispose(); } } @@ -2600,8 +2606,9 @@ private void Connected(object sender, SocketAsyncEventArgs e) protected class SocketUdpServer : IDisposable { private readonly ITestOutputHelper _output; - private Socket _server; - private EventWaitHandle _waitHandle = new AutoResetEvent(false); + private readonly Socket _server; + private readonly EventWaitHandle _waitHandle = new AutoResetEvent(false); + private readonly PortLease _portLease; public EventWaitHandle WaitHandle { @@ -2621,7 +2628,8 @@ public SocketUdpServer(ITestOutputHelper output, IPAddress address, bool dualMod _server = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp); } - port = _server.BindToAnonymousPort(address); + _portLease = _server.BindToPoolPort(address); + port = _portLease.Port; SocketAsyncEventArgs e = new SocketAsyncEventArgs(); e.SetBuffer(new byte[1], 0, 1); @@ -2648,6 +2656,8 @@ public void Dispose() _server.Dispose(); } catch (Exception) { } + + _portLease.Dispose(); } } @@ -2713,7 +2723,8 @@ protected void ReceiveFrom_Helper(IPAddress listenOn, IPAddress connectTo) using (Socket serverSocket = new Socket(SocketType.Dgram, ProtocolType.Udp)) { serverSocket.ReceiveTimeout = 1000; - int port = serverSocket.BindToAnonymousPort(listenOn); + using PortLease portLease = serverSocket.BindToPoolPort(listenOn); + int port = portLease.Port; SocketUdpClient client = new SocketUdpClient(_log, serverSocket, connectTo, port, sendNow: false); 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 abe574ab1c330..e919758e2e779 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 @@ -61,6 +61,9 @@ SocketCommon\Fletcher32.cs + + SocketCommon\TestPortPool.cs + SocketCommon\SocketTestExtensions.cs @@ -108,4 +111,4 @@ Common\System\Net\Logging\NetEventSource.Common.cs - \ No newline at end of file +