From: Anton Firszov Date: Tue, 7 Feb 2023 17:28:13 +0000 (+0100) Subject: Harden and re-enable DualMode socket tests (#80715) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055536~4187 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=5b310646988ccd080897d314e61a3017cee5c8ad;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Harden and re-enable DualMode socket tests (#80715) - UDP tests are moved to a non-parallel collection, since there is no way to prevent parallel UDP tests from intercepting each other's packets. This only increases the execution time by ~2 seconds. - TCP interferences are prevented by a utility PortBlocker, which creates and binds a "shadow" socket of the opposite address family to prevent sockets in parallel tests to bind to the same port. - Accept and SendTo tests are refactored to use SocketTestHelperBase, reducing duplicate code. --- diff --git a/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs b/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs index b316d8e..1e252f0 100644 --- a/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs +++ b/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs @@ -42,8 +42,16 @@ namespace System.Net.Sockets.Tests { IPAddress serverAddress = ipv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback; - using Socket listener = new Socket(serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - listener.Bind(new IPEndPoint(serverAddress, 0)); + // PortBlocker creates a temporary socket of the opposite AddressFamily in the background, so parallel tests won't attempt + // to create their listener sockets on the same port, regardless of address family. + // This should prevent 'listener' from accepting DualMode connections of unrelated tests. + using PortBlocker portBlocker = new PortBlocker(() => + { + Socket l = new Socket(serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + l.BindToAnonymousPort(serverAddress); + return l; + }); + Socket listener = portBlocker.MainSocket; // PortBlocker shall dispose this listener.Listen(1); IPEndPoint connectTo = (IPEndPoint)listener.LocalEndPoint; @@ -84,4 +92,121 @@ namespace System.Net.Sockets.Tests return false; } } + + /// + /// A utility to create and bind a socket while blocking it's port for both IPv4 and IPv6 + /// by also creating and binding a "shadow" socket of the opposite address family. + /// + internal class PortBlocker : IDisposable + { + private const int MaxAttempts = 16; + private Socket _shadowSocket; + public Socket MainSocket { get; } + + public PortBlocker(Func socketFactory) + { + bool success = false; + for (int i = 0; i < MaxAttempts; i++) + { + MainSocket = socketFactory(); + if (MainSocket.LocalEndPoint is not IPEndPoint) + { + MainSocket.Dispose(); + throw new Exception($"{nameof(socketFactory)} is expected create and bind the socket."); + } + + IPAddress shadowAddress = MainSocket.AddressFamily == AddressFamily.InterNetwork ? + IPAddress.IPv6Loopback : + IPAddress.Loopback; + int port = ((IPEndPoint)MainSocket.LocalEndPoint).Port; + IPEndPoint shadowEndPoint = new IPEndPoint(shadowAddress, port); + + try + { + _shadowSocket = new Socket(shadowAddress.AddressFamily, MainSocket.SocketType, MainSocket.ProtocolType); + success = TryBindWithoutReuseAddress(_shadowSocket, shadowEndPoint, out _); + + if (success) break; + } + catch (SocketException) + { + MainSocket.Dispose(); + _shadowSocket?.Dispose(); + } + } + + if (!success) + { + throw new Exception($"Failed to create the 'shadow' (port blocker) socket in {MaxAttempts} attempts."); + } + } + + public void Dispose() + { + MainSocket.Dispose(); + _shadowSocket.Dispose(); + } + + // Socket.Bind() auto-enables SO_REUSEADDR on Unix to allow Bind() during TIME_WAIT to emulate Windows behavior, see SystemNative_Bind() in 'pal_networking.c'. + // To prevent other sockets from succesfully binding to the same port port, we need to avoid this logic when binding the shadow socket. + // This method is doing a custom P/Invoke to bind() on Unix to achieve that. + private static unsafe bool TryBindWithoutReuseAddress(Socket socket, IPEndPoint endPoint, out int port) + { + if (PlatformDetection.IsWindows) + { + try + { + socket.Bind(endPoint); + } + catch (SocketException) + { + port = default; + return false; + } + + port = ((IPEndPoint)socket.LocalEndPoint).Port; + return true; + } + + SocketAddress addr = endPoint.Serialize(); + byte[] data = new byte[addr.Size]; + for (int i = 0; i < data.Length; i++) + { + data[i] = addr[i]; + } + + fixed (byte* dataPtr = data) + { + int result = bind(socket.SafeHandle, (nint)dataPtr, (uint)data.Length); + if (result != 0) + { + port = default; + return false; + } + uint sockLen = (uint)data.Length; + result = getsockname(socket.SafeHandle, (nint)dataPtr, (IntPtr)(&sockLen)); + if (result != 0) + { + port = default; + return false; + } + + addr = new SocketAddress(endPoint.AddressFamily, (int)sockLen); + } + + for (int i = 0; i < data.Length; i++) + { + addr[i] = data[i]; + } + + port = ((IPEndPoint)endPoint.Create(addr)).Port; + return true; + + [Runtime.InteropServices.DllImport("libc", SetLastError = true)] + static extern int bind(SafeSocketHandle socket, IntPtr socketAddress, uint addrLen); + + [Runtime.InteropServices.DllImport("libc", SetLastError = true)] + static extern int getsockname(SafeSocketHandle socket, IntPtr socketAddress, IntPtr addrLenPtr); + } + } } diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs index 8141ae2..a85abab 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs @@ -195,6 +195,28 @@ namespace System.Net.Sockets.Tests } }, maxAttempts: 10, retryWhen: e => e is XunitException); } + + [OuterLoop("Connection failure takes long on Windows.")] + [Fact] + public async Task Connect_WithoutListener_ThrowSocketExceptionWithAppropriateInfo() + { + using PortBlocker portBlocker = new PortBlocker(() => + { + Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.BindToAnonymousPort(IPAddress.Loopback); + return socket; + }); + Socket a = portBlocker.MainSocket; + using Socket b = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + SocketException ex = await Assert.ThrowsAsync(() => ConnectAsync(b, a.LocalEndPoint)); + Assert.Contains(Marshal.GetPInvokeErrorMessage(ex.NativeErrorCode), ex.Message); + + if (UsesSync) + { + Assert.Contains(a.LocalEndPoint.ToString(), ex.Message); + } + } } public sealed class ConnectSync : Connect @@ -215,29 +237,6 @@ namespace System.Net.Sockets.Tests public sealed class ConnectTask : Connect { public ConnectTask(ITestOutputHelper output) : base(output) {} - - [OuterLoop] - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/79820", TestPlatforms.Linux | TestPlatforms.Android)] - public static void Connect_ThrowSocketException_Success() - { - using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) - { - int anonymousPort = socket.BindToAnonymousPort(IPAddress.Loopback); - IPEndPoint ep = new IPEndPoint(IPAddress.Loopback, anonymousPort); - Assert.ThrowsAsync(() => socket.ConnectAsync(ep)); - try - { - socket.Connect(ep); - Assert.Fail("Socket Connect should throw SocketException in this case."); - } - catch (SocketException ex) - { - Assert.Contains(Marshal.GetPInvokeErrorMessage(ex.NativeErrorCode), ex.Message); - Assert.Contains(ep.ToString(), ex.Message); - } - } - } } public sealed class ConnectEap : Connect diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs index 91ff1ce..0a5f3ad 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net.Test.Common; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -144,6 +145,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); socket.Connect(connectTo, port); Assert.True(socket.Connected); } @@ -235,6 +237,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); socket.Connect(new IPEndPoint(connectTo, port)); Assert.True(socket.Connected); } @@ -269,6 +272,7 @@ namespace System.Net.Sockets.Tests using (SocketServer server = new SocketServer(_log, IPAddress.Loopback, false, out int port)) { + server.Start(); AssertExtensions.Throws("addresses", () => { socket.Connect(new IPAddress[] { IPAddress.Loopback }, port); @@ -278,11 +282,31 @@ namespace System.Net.Sockets.Tests } [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)); + using Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + SocketServer server = null; + int port = 0; + + // PortBlocker creates a temporary socket of the opposite AddressFamily in the background, so parallel tests won't attempt + // to create their listener sockets on the same port. + // This should prevent 'server' from accepting DualMode connections of unrelated tests. + using PortBlocker blocker = new PortBlocker(() => + { + server = new SocketServer(_log, listenOn, dualModeServer, out port); + return server.Socket; + }); + + using (server) + { + server.Start(); + Assert.ThrowsAny(() => + { + socket.Connect(connectTo, port); + }); + Assert.False(socket.Connected); + } } [Theory] @@ -292,6 +316,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); socket.Connect(connectTo, port); Assert.True(socket.Connected); } @@ -309,6 +334,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); socket.Connect("localhost", port); Assert.True(socket.Connected); } @@ -326,6 +352,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); socket.Connect(new DnsEndPoint("localhost", port, AddressFamily.Unspecified)); Assert.True(socket.Connected); } @@ -374,6 +401,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); await Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, connectTo, port, null); Assert.True(socket.Connected); } @@ -426,6 +454,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); await Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, new IPEndPoint(connectTo, port), null); Assert.True(socket.Connected); } @@ -444,6 +473,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); await Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, connectTo, port, null); Assert.True(socket.Connected); } @@ -457,6 +487,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); await Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, "localhost", port, null); Assert.True(socket.Connected); } @@ -470,6 +501,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); await Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, new DnsEndPoint("localhost", port), null); Assert.True(socket.Connected); } @@ -535,6 +567,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); ManualResetEvent waitHandle = new ManualResetEvent(false); SocketAsyncEventArgs args = new SocketAsyncEventArgs(); args.Completed += new EventHandler(AsyncCompleted); @@ -575,6 +608,7 @@ namespace System.Net.Sockets.Tests using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); ManualResetEvent waitHandle = new ManualResetEvent(false); SocketAsyncEventArgs args = new SocketAsyncEventArgs(); args.Completed += new EventHandler(AsyncCompleted); @@ -601,6 +635,7 @@ namespace System.Net.Sockets.Tests { using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port)) { + server.Start(); ManualResetEvent waitHandle = new ManualResetEvent(false); SocketAsyncEventArgs args = new SocketAsyncEventArgs(); args.Completed += new EventHandler(AsyncCompleted); @@ -692,1051 +727,363 @@ namespace System.Net.Sockets.Tests } } - [Trait("IPv4", "true")] - [Trait("IPv6", "true")] - public class DualModeAccept : DualModeBase + public abstract class DualModeAcceptBase : SocketTestHelperBase where T : SocketHelperBase, new() { - [Fact] - public void AcceptV4BoundToSpecificV4_Success() + public DualModeAcceptBase(ITestOutputHelper output) : base(output) { - Accept_Helper(IPAddress.Loopback, IPAddress.Loopback); } [Fact] - public void AcceptV4BoundToAnyV4_Success() - { - Accept_Helper(IPAddress.Any, IPAddress.Loopback); - } + public Task AcceptV4BoundToSpecificV4_Success() => Accept_Helper(IPAddress.Loopback, IPAddress.Loopback); [Fact] - public void AcceptV6BoundToSpecificV6_Success() - { - Accept_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback); - } + public Task AcceptV4BoundToAnyV4_Success() => Accept_Helper(IPAddress.Any, IPAddress.Loopback); [Fact] - public void AcceptV6BoundToAnyV6_Success() - { - Accept_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); - } + public Task AcceptV6BoundToSpecificV6_Success() => Accept_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback); [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // https://github.com/dotnet/runtime/issues/16265 - public void AcceptV6BoundToSpecificV4_CantConnect() - { - Assert.Throws(() => - { - Accept_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback); - }); - } + public Task AcceptV6BoundToAnyV6_Success() => Accept_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // https://github.com/dotnet/runtime/issues/16265 - public void AcceptV4BoundToSpecificV6_CantConnect() - { - Assert.Throws(() => - { - Accept_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback); - }); - } - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // https://github.com/dotnet/runtime/issues/16265 - public void AcceptV6BoundToAnyV4_CantConnect() - { - Assert.Throws(() => - { - Accept_Helper(IPAddress.Any, IPAddress.IPv6Loopback); - }); - } - - [Fact] - public void AcceptV4BoundToAnyV6_Success() - { - Accept_Helper(IPAddress.IPv6Any, IPAddress.Loopback); - } - - private void Accept_Helper(IPAddress listenOn, IPAddress connectTo) - { - using (Socket serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp)) - { - int port = serverSocket.BindToAnonymousPort(listenOn); - serverSocket.Listen(1); - SocketClient client = new SocketClient(_log, serverSocket, connectTo, port); - Socket clientSocket = serverSocket.Accept(); - Assert.True(clientSocket.Connected); - AssertDualModeEnabled(clientSocket, listenOn); - Assert.Equal(AddressFamily.InterNetworkV6, clientSocket.AddressFamily); - } - } - } - - [Trait("IPv4", "true")] - [Trait("IPv6", "true")] - public class DualModeBeginAccept : DualModeBase - { - [Fact] - public void BeginAcceptV4BoundToSpecificV4_Success() - { - DualModeConnect_BeginAccept_Helper(IPAddress.Loopback, IPAddress.Loopback); - } - - [Fact] - public void BeginAcceptV4BoundToAnyV4_Success() - { - DualModeConnect_BeginAccept_Helper(IPAddress.Any, IPAddress.Loopback); - } - - [Fact] - public void BeginAcceptV6BoundToSpecificV6_Success() - { - DualModeConnect_BeginAccept_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback); - } + public Task AcceptV4BoundToAnyV6_Success() => Accept_Helper(IPAddress.IPv6Any, IPAddress.Loopback); [Fact] - public void BeginAcceptV6BoundToAnyV6_Success() - { - DualModeConnect_BeginAccept_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); - } + public Task AcceptV6BoundToSpecificV4_CantConnect() => Accept_Helper_Failing(IPAddress.Loopback, IPAddress.IPv6Loopback); [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(() => - { - DualModeConnect_BeginAccept_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback); - }); - } + public Task AcceptV4BoundToSpecificV6_CantConnect() => Accept_Helper_Failing(IPAddress.IPv6Loopback, IPAddress.Loopback); [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(() => - { - DualModeConnect_BeginAccept_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback); - }); - } + public Task AcceptV6BoundToAnyV4_CantConnect() => Accept_Helper_Failing(IPAddress.Any, IPAddress.IPv6Loopback); - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use - public void BeginAcceptV6BoundToAnyV4_CantConnect() + private async Task Accept_Helper(IPAddress listenOn, IPAddress connectTo) { - Assert.Throws(() => - { - DualModeConnect_BeginAccept_Helper(IPAddress.Any, IPAddress.IPv6Loopback); - }); - } + using Socket serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); - [Fact] - public void BeginAcceptV4BoundToAnyV6_Success() - { - DualModeConnect_BeginAccept_Helper(IPAddress.IPv6Any, IPAddress.Loopback); - } + int port = serverSocket.BindToAnonymousPort(listenOn); + serverSocket.Listen(1); - private void DualModeConnect_BeginAccept_Helper(IPAddress listenOn, IPAddress connectTo) - { - using (Socket serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp)) + using Socket client = new Socket(connectTo.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + Task connectTask = client.ConnectAsync(connectTo, port); + Socket clientSocket = await AcceptAsync(serverSocket); + await connectTask; + Assert.True(clientSocket.Connected); + AssertDualModeEnabled(clientSocket, listenOn); + Assert.Equal(AddressFamily.InterNetworkV6, clientSocket.AddressFamily); + if (connectTo == IPAddress.Loopback) { - int port = serverSocket.BindToAnonymousPort(listenOn); - serverSocket.Listen(1); - IAsyncResult async = serverSocket.BeginAccept(null, null); - SocketClient client = new SocketClient(_log, serverSocket, connectTo, port); - - Assert.True( - client.WaitHandle.WaitOne(TestSettings.PassingTestTimeout), - "Timed out while waiting for connection"); - Assert.True( - async.AsyncWaitHandle.WaitOne(TestSettings.PassingTestTimeout), - "Timed out while waiting to accept the client"); - - // Due to the nondeterministic nature of calling dispose on a Socket that is doing - // an EndAccept operation, we expect two types of exceptions to happen. - Socket clientSocket; - try - { - clientSocket = serverSocket.EndAccept(async); - Assert.True(clientSocket.Connected); - AssertDualModeEnabled(clientSocket, listenOn); - Assert.Equal(AddressFamily.InterNetworkV6, clientSocket.AddressFamily); - if (connectTo == IPAddress.Loopback) - { - Assert.Contains(((IPEndPoint)clientSocket.LocalEndPoint).Address, ValidIPv6Loopbacks); - } - else - { - Assert.Equal(connectTo.MapToIPv6(), ((IPEndPoint)clientSocket.LocalEndPoint).Address); - } - } - catch (ObjectDisposedException) { } - catch (SocketException) { } - - if (client.Error != SocketError.Success) - { - throw new SocketException((int)client.Error); - } + Assert.Contains(((IPEndPoint)clientSocket.LocalEndPoint).Address, DualModeBase.ValidIPv6Loopbacks); } - } - } - - [Trait("IPv4", "true")] - [Trait("IPv6", "true")] - public class DualModeAcceptAsync : DualModeBase - { - [Fact] - public void AcceptAsyncV4BoundToSpecificV4_Success() - { - DualModeConnect_AcceptAsync_Helper(IPAddress.Loopback, IPAddress.Loopback); - } - - [Fact] - public void AcceptAsyncV4BoundToAnyV4_Success() - { - DualModeConnect_AcceptAsync_Helper(IPAddress.Any, IPAddress.Loopback); - } - - [Fact] - public void AcceptAsyncV6BoundToSpecificV6_Success() - { - DualModeConnect_AcceptAsync_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback); - } - - [Fact] - public void AcceptAsyncV6BoundToAnyV6_Success() - { - DualModeConnect_AcceptAsync_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); - } - - [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(() => - { - DualModeConnect_AcceptAsync_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback); - }); - } - - [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(() => + else { - DualModeConnect_AcceptAsync_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback); - }); + Assert.Equal(connectTo.MapToIPv6(), ((IPEndPoint)clientSocket.LocalEndPoint).Address); + } } - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Binds to a specific port on 'connectTo' which on Unix may already be in use - public void AcceptAsyncV6BoundToAnyV4_CantConnect() + private async Task Accept_Helper_Failing(IPAddress listenOn, IPAddress connectTo) { - Assert.Throws(() => - { - DualModeConnect_AcceptAsync_Helper(IPAddress.Any, IPAddress.IPv6Loopback); - }); - } + using Socket serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); + int port = serverSocket.BindToAnonymousPort(listenOn); + serverSocket.Listen(1); + _ = AcceptAsync(serverSocket); - [Fact] - public void AcceptAsyncV4BoundToAnyV6_Success() - { - DualModeConnect_AcceptAsync_Helper(IPAddress.IPv6Any, IPAddress.Loopback); + using Socket client = new Socket(connectTo.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + await Assert.ThrowsAsync(() => client.ConnectAsync(connectTo, port)); } - private void DualModeConnect_AcceptAsync_Helper(IPAddress listenOn, IPAddress connectTo) - { - using (Socket serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp)) - { - int port = serverSocket.BindToAnonymousPort(listenOn); - serverSocket.Listen(1); - - SocketAsyncEventArgs args = new SocketAsyncEventArgs(); - args.Completed += AsyncCompleted; - ManualResetEvent waitHandle = new ManualResetEvent(false); - args.UserToken = waitHandle; - args.SocketError = SocketError.SocketError; - - _log.WriteLine(args.GetHashCode() + " SocketAsyncEventArgs with manual event " + waitHandle.GetHashCode()); - if (!serverSocket.AcceptAsync(args)) - { - throw new SocketException((int)args.SocketError); - } - - SocketClient client = new SocketClient(_log, serverSocket, connectTo, port); - - var waitHandles = new WaitHandle[2]; - waitHandles[0] = waitHandle; - waitHandles[1] = client.WaitHandle; - - int completedHandle = WaitHandle.WaitAny(waitHandles, TestSettings.PassingTestTimeout); - - if (completedHandle == WaitHandle.WaitTimeout) - { - throw new TimeoutException("Timed out while waiting for either of client and server connections..."); - } - - if (completedHandle == 1) // Client finished - { - if (client.Error != SocketError.Success) - { - // Client SocketException - throw new SocketException((int)client.Error); - } - - if (!waitHandle.WaitOne(5000)) // Now wait for the server. - { - throw new TimeoutException("Timed out while waiting for the server accept..."); - } - } - - _log.WriteLine(args.SocketError.ToString()); - - - if (args.SocketError != SocketError.Success) - { - throw new SocketException((int)args.SocketError); - } - - Socket clientSocket = args.AcceptSocket; - Assert.NotNull(clientSocket); - Assert.True(clientSocket.Connected); - AssertDualModeEnabled(clientSocket, listenOn); - Assert.Equal(AddressFamily.InterNetworkV6, clientSocket.AddressFamily); - if (connectTo == IPAddress.Loopback) - { - Assert.Contains(((IPEndPoint)clientSocket.LocalEndPoint).Address, ValidIPv6Loopbacks); - } - else - { - Assert.Equal(connectTo.MapToIPv6(), ((IPEndPoint)clientSocket.LocalEndPoint).Address); - } - clientSocket.Dispose(); - } - } - } - - [OuterLoop] - [Trait("IPv4", "true")] - [Trait("IPv6", "true")] - public class DualModeConnectionlessSendTo : DualModeBase - { - #region SendTo Sync IPEndPoint - - [Fact] - public void Socket_SendToV4IPEndPointToV4Host_Throws() + protected static void AssertDualModeEnabled(Socket socket, IPAddress listenOn) { - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) + if (OperatingSystem.IsWindows()) { - socket.DualMode = false; - Assert.Throws(() => - { - socket.SendTo(new byte[1], new IPEndPoint(IPAddress.Loopback, UnusedPort)); - }); + Assert.True(socket.DualMode); } - } - - [Fact] // Base case - // "The parameter remoteEP must not be of type DnsEndPoint." - public void Socket_SendToDnsEndPoint_Throws() - { - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) + else if (OperatingSystem.IsFreeBSD()) { - AssertExtensions.Throws("remoteEP", () => - { - socket.SendTo(new byte[1], new DnsEndPoint("localhost", UnusedPort)); - }); + // This is not valid check on FreeBSD. + // Accepted socket is never DualMode and cannot be changed. } - } - - [Fact] - public void SendToV4IPEndPointToV4Host_Success() - { - DualModeSendTo_IPEndPointToHost_Helper(IPAddress.Loopback, IPAddress.Loopback, false); - } - - [Fact] - public void SendToV6IPEndPointToV6Host_Success() - { - DualModeSendTo_IPEndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback, false); - } - - [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(() => - { - DualModeSendTo_IPEndPointToHost_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback, false, expectedToTimeout: true); - }); - } - - [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(() => - { - DualModeSendTo_IPEndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback, false, expectedToTimeout: true); - }); - } - - [Fact] - public void SendToV4IPEndPointToDualHost_Success() - { - DualModeSendTo_IPEndPointToHost_Helper(IPAddress.Loopback, IPAddress.IPv6Any, true); - } - - [Fact] - public void SendToV6IPEndPointToDualHost_Success() - { - DualModeSendTo_IPEndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Any, true); - } - - private void DualModeSendTo_IPEndPointToHost_Helper(IPAddress connectTo, IPAddress listenOn, bool dualModeServer, bool expectedToTimeout = false) - { - using (Socket client = new Socket(SocketType.Dgram, ProtocolType.Udp)) - using (SocketUdpServer server = new SocketUdpServer(_log, listenOn, dualModeServer, out int port)) + else { - int sent = client.SendTo(new byte[1], new IPEndPoint(connectTo, port)); - Assert.Equal(1, sent); - - bool success = server.WaitHandle.WaitOne(expectedToTimeout ? TestSettings.FailingTestTimeout : TestSettings.PassingTestTimeout); // Make sure the bytes were received - if (!success) - { - throw new TimeoutException(); - } + Assert.True((listenOn != IPAddress.IPv6Any && !listenOn.IsIPv4MappedToIPv6) || socket.DualMode); } } - - #endregion SendTo Sync } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] - public class DualModeConnectionlessBeginSendTo : DualModeBase + public class DualModeAcceptSync : DualModeAcceptBase { - #region SendTo Begin/End - - [Fact] - public void Socket_BeginSendToV4IPEndPointToV4Host_Throws() - { - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - socket.DualMode = false; - - Assert.Throws(() => - { - // [ActiveIssue("https://github.com/dotnet/runtime/issues/47905")] - // TODO: When fixing the issue above, revert this test to check that the exception is being thrown in BeginSendTo - // without the need to call EndSendTo. - IAsyncResult result = socket.BeginSendTo(new byte[1], 0, 1, SocketFlags.None, new IPEndPoint(IPAddress.Loopback, UnusedPort), null, null); - socket.EndSendTo(result); - }); - } - } - - [Fact] // Base case - // "The parameter remoteEP must not be of type DnsEndPoint." - public void Socket_BeginSendToDnsEndPoint_Throws() - { - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - AssertExtensions.Throws("remoteEP", () => - { - socket.BeginSendTo(new byte[1], 0, 1, SocketFlags.None, new DnsEndPoint("localhost", UnusedPort), null, null); - }); - } - } - - [Fact] - public void BeginSendToV4IPEndPointToV4Host_Success() - { - DualModeBeginSendTo_EndPointToHost_Helper(IPAddress.Loopback, IPAddress.Loopback, false); - } - - [Fact] - public void BeginSendToV6IPEndPointToV6Host_Success() - { - DualModeBeginSendTo_EndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback, false); - } - - [Fact] - public void BeginSendToV4IPEndPointToV6Host_NotReceived() - { - Assert.Throws(() => - { - DualModeBeginSendTo_EndPointToHost_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback, false, expectedToTimeout: true); - }); - } - - [Fact] - public void BeginSendToV6IPEndPointToV4Host_NotReceived() - { - Assert.Throws(() => - { - DualModeBeginSendTo_EndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback, false, expectedToTimeout: true); - }); - } - - [Fact] - public void BeginSendToV4IPEndPointToDualHost_Success() - { - DualModeBeginSendTo_EndPointToHost_Helper(IPAddress.Loopback, IPAddress.IPv6Any, true); - } - - [Fact] - public void BeginSendToV6IPEndPointToDualHost_Success() - { - DualModeBeginSendTo_EndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Any, true); - } - - private void DualModeBeginSendTo_EndPointToHost_Helper(IPAddress connectTo, IPAddress listenOn, bool dualModeServer, bool expectedToTimeout = false) - { - using (Socket client = new Socket(SocketType.Dgram, ProtocolType.Udp)) - using (SocketUdpServer server = new SocketUdpServer(_log, listenOn, dualModeServer, out int port)) - { - IAsyncResult async = client.BeginSendTo(new byte[1], 0, 1, SocketFlags.None, new IPEndPoint(connectTo, port), null, null); - - int sent = client.EndSendTo(async); - Assert.Equal(1, sent); - - bool success = server.WaitHandle.WaitOne(expectedToTimeout ? TestSettings.FailingTestTimeout : TestSettings.PassingTestTimeout); // Make sure the bytes were received - if (!success) - { - throw new TimeoutException(); - } - } - } - - #endregion SendTo Begin/End + public DualModeAcceptSync(ITestOutputHelper output) : base(output) { } } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] - public class DualModeConnectionlessSendToAsync : DualModeBase + public class DualModeAcceptApm : DualModeAcceptBase { - #region SendTo Async/Event - - [Fact] - public void Socket_SendToAsyncV4IPEndPointToV4Host_Throws() - { - using (Socket socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp)) - { - SocketAsyncEventArgs args = new SocketAsyncEventArgs(); - args.RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, UnusedPort); - args.SetBuffer(new byte[1], 0, 1); - bool async = socket.SendToAsync(args); - Assert.False(async); - - if (OperatingSystem.IsWindows()) - { - Assert.Equal(SocketError.Fault, args.SocketError); - } - else if (OperatingSystem.IsLinux()) - { - // NOTE: on Linux, this API returns ENETUNREACH instead of EFAULT: this platform - // checks the family of the provided socket address before checking its size - // (as long as the socket address is large enough to store an address family). - Assert.Equal(SocketError.NetworkUnreachable, args.SocketError); - } - else - { - // NOTE: on other Unix platforms, this API returns EINVAL instead of EFAULT. - Assert.Equal(SocketError.InvalidArgument, args.SocketError); - } - } - } - - [Fact] // Base case - // "The parameter remoteEP must not be of type DnsEndPoint." - public void Socket_SendToAsyncDnsEndPoint_Throws() - { - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - SocketAsyncEventArgs args = new SocketAsyncEventArgs(); - args.RemoteEndPoint = new DnsEndPoint("localhost", UnusedPort); - args.SetBuffer(new byte[1], 0, 1); - AssertExtensions.Throws("remoteEP", () => - { - socket.SendToAsync(args); - }); - } - } - - [Fact] - public void SendToAsyncV4IPEndPointToV4Host_Success() - { - DualModeSendToAsync_IPEndPointToHost_Helper(IPAddress.Loopback, IPAddress.Loopback, false); - } - - [Fact] - public void SendToAsyncV6IPEndPointToV6Host_Success() - { - DualModeSendToAsync_IPEndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback, false); - } - - [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(() => - { - DualModeSendToAsync_IPEndPointToHost_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback, false, expectedToTimeout: true); - }); - } - - [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(() => - { - DualModeSendToAsync_IPEndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback, false, expectedToTimeout: true); - }); - } - - [Fact] - public void SendToAsyncV4IPEndPointToDualHost_Success() - { - DualModeSendToAsync_IPEndPointToHost_Helper(IPAddress.Loopback, IPAddress.IPv6Any, true); - } - - [Fact] - public void SendToAsyncV6IPEndPointToDualHost_Success() - { - DualModeSendToAsync_IPEndPointToHost_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Any, true); - } - - private void DualModeSendToAsync_IPEndPointToHost_Helper(IPAddress connectTo, IPAddress listenOn, bool dualModeServer, bool expectedToTimeout = false) - { - using (Socket client = new Socket(SocketType.Dgram, ProtocolType.Udp)) - using (SocketUdpServer server = new SocketUdpServer(_log, listenOn, dualModeServer, out int port)) - { - using (ManualResetEvent waitHandle = new ManualResetEvent(false)) - { - SocketAsyncEventArgs args = new SocketAsyncEventArgs(); - args.RemoteEndPoint = new IPEndPoint(connectTo, port); - args.SetBuffer(new byte[1], 0, 1); - args.UserToken = waitHandle; - args.Completed += AsyncCompleted; - - bool async = client.SendToAsync(args); - if (async) - { - Assert.True(waitHandle.WaitOne(TestSettings.PassingTestTimeout), "Timeout while waiting for connection"); - } - - Assert.Equal(1, args.BytesTransferred); - if (args.SocketError != SocketError.Success) - { - throw new SocketException((int)args.SocketError); - } - } - - bool success = server.WaitHandle.WaitOne(expectedToTimeout ? TestSettings.FailingTestTimeout : TestSettings.PassingTestTimeout); // Make sure the bytes were received - if (!success) - { - throw new TimeoutException(); - } - } - } - - #endregion SendTo Async/Event + public DualModeAcceptApm(ITestOutputHelper output) : base(output) { } } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] - public class DualModeConnectionlessReceiveFrom : DualModeBase + public class DualModeAcceptEap : DualModeAcceptBase { - #region ReceiveFrom Sync - - [Fact] // Base case - public void Socket_ReceiveFromV4IPEndPointFromV4Client_Throws() - { - // "The supplied EndPoint of AddressFamily InterNetwork is not valid for this Socket, use InterNetworkV6 instead." - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - socket.DualMode = false; - - EndPoint receivedFrom = new IPEndPoint(IPAddress.Loopback, UnusedPort); - AssertExtensions.Throws("remoteEP", () => - { - int received = socket.ReceiveFrom(new byte[1], ref receivedFrom); - }); - } - } - - [Fact] // Base case - public void Socket_ReceiveFromDnsEndPoint_Throws() - { - // "The parameter remoteEP must not be of type DnsEndPoint." - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - int port = socket.BindToAnonymousPort(IPAddress.IPv6Loopback); - EndPoint receivedFrom = new DnsEndPoint("localhost", port, AddressFamily.InterNetworkV6); - AssertExtensions.Throws("remoteEP", () => - { - int received = socket.ReceiveFrom(new byte[1], ref receivedFrom); - }); - } - } - - [Fact] - public void ReceiveFromV4BoundToSpecificV4_Success() - { - ReceiveFrom_Helper(IPAddress.Loopback, IPAddress.Loopback); - } - - [Fact] - public void ReceiveFromV4BoundToAnyV4_Success() - { - ReceiveFrom_Helper(IPAddress.Any, IPAddress.Loopback); - } - - [Fact] - public void ReceiveFromV6BoundToSpecificV6_Success() - { - ReceiveFrom_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback); - } - - [Fact] - public void ReceiveFromV6BoundToAnyV6_Success() - { - ReceiveFrom_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); - } - - [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)] - public void ReceiveFromV6BoundToSpecificV4_NotReceived() - { - Assert.Throws(() => - { - ReceiveFrom_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback); - }); - } - - [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)] - public void ReceiveFromV4BoundToSpecificV6_NotReceived() - { - Assert.Throws(() => - { - ReceiveFrom_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback); - }); - } - - [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)] - public void ReceiveFromV6BoundToAnyV4_NotReceived() - { - Assert.Throws(() => - { - ReceiveFrom_Helper(IPAddress.Any, IPAddress.IPv6Loopback); - }); - } - - [Fact] - public void ReceiveFromV4BoundToAnyV6_Success() - { - ReceiveFrom_Helper(IPAddress.IPv6Any, IPAddress.Loopback); - } - - #endregion ReceiveFrom Sync + public DualModeAcceptEap(ITestOutputHelper output) : base(output) { } } - [OuterLoop] [Trait("IPv4", "true")] - [Trait("IPv6", "true")] - public class DualModeConnectionlessBeginReceiveFrom : DualModeBase - { - #region ReceiveFrom Begin/End - - [Fact] // Base case - // "The supplied EndPoint of AddressFamily InterNetwork is not valid for this Socket, use InterNetworkV6 instead." - public void Socket_BeginReceiveFromV4IPEndPointFromV4Client_Throws() - { - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - socket.DualMode = false; - - EndPoint receivedFrom = new IPEndPoint(IPAddress.Loopback, UnusedPort); - AssertExtensions.Throws("remoteEP", () => - { - socket.BeginReceiveFrom(new byte[1], 0, 1, SocketFlags.None, ref receivedFrom, null, null); - }); - } - } + [Trait("IPv6", "true")] + public class DualModeAcceptTask : DualModeAcceptBase + { + public DualModeAcceptTask(ITestOutputHelper output) : base(output) { } + } - [Fact] // Base case - // "The parameter remoteEP must not be of type DnsEndPoint." - public void Socket_BeginReceiveFromDnsEndPoint_Throws() + public abstract class DualModeConnectionlessSendToBase : SocketTestHelperBase where T : SocketHelperBase, new() + { + protected DualModeConnectionlessSendToBase(ITestOutputHelper output) : base(output) { - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - int port = socket.BindToAnonymousPort(IPAddress.IPv6Loopback); - EndPoint receivedFrom = new DnsEndPoint("localhost", port, AddressFamily.InterNetworkV6); - - AssertExtensions.Throws("remoteEP", () => - { - socket.BeginReceiveFrom(new byte[1], 0, 1, SocketFlags.None, ref receivedFrom, null, null); - }); - } } [Fact] - public void BeginReceiveFromV4BoundToSpecificV4_Success() + public async Task Socket_SendToV4IPEndPointToV4Host_Throws() { - BeginReceiveFrom_Helper(IPAddress.Loopback, IPAddress.Loopback); + using Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp); + socket.DualMode = false; + await Assert.ThrowsAsync( + () => SendToAsync(socket, new byte[1], new IPEndPoint(IPAddress.Loopback, DualModeBase.UnusedPort))); } - [Fact] - [SkipOnPlatform(TestPlatforms.OSX | TestPlatforms.MacCatalyst | TestPlatforms.iOS | TestPlatforms.tvOS, "BeginReceiveFrom not supported on Apple platforms")] - public void BeginReceiveFromV4BoundToAnyV4_Success() + [Fact] // Base case + // "The parameter remoteEP must not be of type DnsEndPoint." + public async Task Socket_SendToDnsEndPoint_Throws() { - BeginReceiveFrom_Helper(IPAddress.Any, IPAddress.Loopback); + using Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp); + + await AssertExtensions.ThrowsAsync("remoteEP", + () => SendToAsync(socket, new byte[1], new DnsEndPoint("localhost", DualModeBase.UnusedPort))); } [Fact] - public void BeginReceiveFromV6BoundToSpecificV6_Success() - { - BeginReceiveFrom_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback); - } + public Task SendToV4IPEndPointToV4Host_Success() => DualModeSendTo_IPEndPointToHost_Success_Helper(IPAddress.Loopback, IPAddress.Loopback, false); [Fact] - public void BeginReceiveFromV6BoundToAnyV6_Success() - { - BeginReceiveFrom_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); - } + public Task SendToV6IPEndPointToV6Host_Success() => DualModeSendTo_IPEndPointToHost_Success_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback, false); [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)] - public void BeginReceiveFromV6BoundToSpecificV4_NotReceived() - { - Assert.Throws(() => - { - BeginReceiveFrom_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback, expectedToTimeout: true); - }); - } + public Task SendToV4IPEndPointToDualHost_Success() => DualModeSendTo_IPEndPointToHost_Success_Helper(IPAddress.Loopback, IPAddress.IPv6Any, true); [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)] - public void BeginReceiveFromV4BoundToSpecificV6_NotReceived() - { - Assert.Throws(() => - { - BeginReceiveFrom_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback, expectedToTimeout: true); - }); - } + public Task SendToV6IPEndPointToDualHost_Success() => DualModeSendTo_IPEndPointToHost_Success_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Any, true); [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)] - public void BeginReceiveFromV6BoundToAnyV4_NotReceived() - { - Assert.Throws(() => - { - BeginReceiveFrom_Helper(IPAddress.Any, IPAddress.IPv6Loopback, expectedToTimeout: true); - }); - } + public Task SendToV4IPEndPointToV6Host_NotReceived() => DualModeSendTo_IPEndPointToHost_Failing_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback, false); [Fact] - public void BeginReceiveFromV4BoundToAnyV6_Success() - { - BeginReceiveFrom_Helper(IPAddress.IPv6Any, IPAddress.Loopback); - } + public Task SendToV6IPEndPointToV4Host_NotReceived() => DualModeSendTo_IPEndPointToHost_Failing_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback, false); - private void BeginReceiveFrom_Helper(IPAddress listenOn, IPAddress connectTo, bool expectedToTimeout = false) + private async Task DualModeSendTo_IPEndPointToHost_Success_Helper(IPAddress connectTo, IPAddress listenOn, bool dualModeServer) { - using (Socket serverSocket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - serverSocket.ReceiveTimeout = 500; - int port = serverSocket.BindToAnonymousPort(listenOn); - - EndPoint receivedFrom = new IPEndPoint(connectTo, port); - IAsyncResult async = serverSocket.BeginReceiveFrom(new byte[1], 0, 1, SocketFlags.None, ref receivedFrom, null, null); - - // Behavior difference from Desktop: receivedFrom will _not_ change during the synchronous phase. + using Socket client = new Socket(SocketType.Dgram, ProtocolType.Udp); + using Socket server = dualModeServer ? + new Socket(SocketType.Dgram, ProtocolType.Udp) : + new Socket(listenOn.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + int port = server.BindToAnonymousPort(listenOn); - // IPEndPoint remoteEndPoint = receivedFrom as IPEndPoint; - // Assert.Equal(AddressFamily.InterNetworkV6, remoteEndPoint.AddressFamily); - // Assert.Equal(connectTo.MapToIPv6(), remoteEndPoint.Address); + Task receiveTask = server.ReceiveAsync(new byte[1]); + int sent = await SendToAsync(client, new byte[1], new IPEndPoint(connectTo, port)).WaitAsync(TestSettings.PassingTestTimeout); + Assert.Equal(1, sent); - SocketUdpClient client = new SocketUdpClient(_log, serverSocket, connectTo, port); - bool success = async.AsyncWaitHandle.WaitOne(expectedToTimeout ? TestSettings.FailingTestTimeout : TestSettings.PassingTestTimeout); - if (!success) - { - throw new TimeoutException(); - } + int received = await receiveTask.WaitAsync(TestSettings.PassingTestTimeout); + Assert.Equal(1, received); + } - receivedFrom = new IPEndPoint(connectTo, port); - int received = serverSocket.EndReceiveFrom(async, ref receivedFrom); + private async Task DualModeSendTo_IPEndPointToHost_Failing_Helper(IPAddress connectTo, IPAddress listenOn, bool dualModeServer) + { + using Socket client = new Socket(SocketType.Dgram, ProtocolType.Udp); + using Socket server = dualModeServer ? + new Socket(SocketType.Dgram, ProtocolType.Udp) : + new Socket(listenOn.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + int port = server.BindToAnonymousPort(listenOn); - Assert.Equal(1, received); - Assert.Equal(typeof(IPEndPoint), receivedFrom.GetType()); + _ = SendToAsync(client, new byte[1], new IPEndPoint(connectTo, port)).WaitAsync(TestSettings.PassingTestTimeout); + await Assert.ThrowsAsync(() => server.ReceiveAsync(new byte[1]).WaitAsync(TestSettings.FailingTestTimeout)); + } + } - IPEndPoint remoteEndPoint = receivedFrom as IPEndPoint; - Assert.Equal(AddressFamily.InterNetworkV6, remoteEndPoint.AddressFamily); - Assert.Equal(connectTo.MapToIPv6(), remoteEndPoint.Address); - } + [Trait("IPv4", "true")] + [Trait("IPv6", "true")] + [Collection(nameof(DisableParallelization))] + public class DualModeConnectionlessSendToSync : DualModeConnectionlessSendToBase + { + public DualModeConnectionlessSendToSync(ITestOutputHelper output) : base(output) + { } + } - #endregion ReceiveFrom Begin/End + [Trait("IPv4", "true")] + [Trait("IPv6", "true")] + [Collection(nameof(DisableParallelization))] + public class DualModeConnectionlessSendToApm : DualModeConnectionlessSendToBase + { + public DualModeConnectionlessSendToApm(ITestOutputHelper output) : base(output) + { + } } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] - public class DualModeConnectionlessReceiveFromAsync : DualModeBase + [Collection(nameof(DisableParallelization))] + public class DualModeConnectionlessSendToEap : DualModeConnectionlessSendToBase { - #region ReceiveFrom Async/Event + public DualModeConnectionlessSendToEap(ITestOutputHelper output) : base(output) + { + } + } - [Fact] // Base case - // "The supplied EndPoint of AddressFamily InterNetwork is not valid for this Socket, use InterNetworkV6 instead." - public void Socket_ReceiveFromAsyncV4IPEndPointFromV4Client_Throws() + [Trait("IPv4", "true")] + [Trait("IPv6", "true")] + [Collection(nameof(DisableParallelization))] + public class DualModeConnectionlessSendToTask : DualModeConnectionlessSendToBase + { + public DualModeConnectionlessSendToTask(ITestOutputHelper output) : base(output) { - using (Socket socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp)) - { - SocketAsyncEventArgs args = new SocketAsyncEventArgs(); - args.RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, UnusedPort); - args.SetBuffer(new byte[1], 0, 1); + } + } - AssertExtensions.Throws("e", () => - { - socket.ReceiveFromAsync(args); - }); - } + public abstract class DualModeConnectionlessReceiveFromBase : SocketTestHelperBase where T : SocketHelperBase, new() + { + protected DualModeConnectionlessReceiveFromBase(ITestOutputHelper output) : base(output) + { } [Fact] // Base case - // "The parameter remoteEP must not be of type DnsEndPoint." - public void Socket_ReceiveFromAsyncDnsEndPoint_Throws() + public async Task Socket_ReceiveFromV4IPEndPointFromV4Client_Throws() { - using (Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - int port = socket.BindToAnonymousPort(IPAddress.IPv6Loopback); - SocketAsyncEventArgs args = new SocketAsyncEventArgs(); - args.RemoteEndPoint = new DnsEndPoint("localhost", port, AddressFamily.InterNetworkV6); - args.SetBuffer(new byte[1], 0, 1); + // "The supplied EndPoint of AddressFamily InterNetwork is not valid for this Socket, use InterNetworkV6 instead." + using Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp); + socket.DualMode = false; - AssertExtensions.Throws("remoteEP", () => - { - socket.ReceiveFromAsync(args); - }); - } + EndPoint receivedFrom = new IPEndPoint(IPAddress.Loopback, DualModeBase.UnusedPort); + await Assert.ThrowsAsync(() => ReceiveFromAsync(socket, new byte[1], receivedFrom)); } - [Fact] - public void ReceiveFromAsyncV4BoundToSpecificV4_Success() + [Fact] // Base case + public async Task Socket_ReceiveFromDnsEndPoint_Throws() { - ReceiveFromAsync_Helper(IPAddress.Loopback, IPAddress.Loopback); + // "The parameter remoteEP must not be of type DnsEndPoint." + using Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp); + + int port = socket.BindToAnonymousPort(IPAddress.IPv6Loopback); + EndPoint receivedFrom = new DnsEndPoint("localhost", port, AddressFamily.InterNetworkV6); + await AssertExtensions.ThrowsAsync("remoteEP", () => ReceiveFromAsync(socket, new byte[1], receivedFrom)); } [Fact] - public void ReceiveFromAsyncV4BoundToAnyV4_Success() - { - ReceiveFromAsync_Helper(IPAddress.Any, IPAddress.Loopback); - } + public Task ReceiveFromV4BoundToSpecificV4_Success() => ReceiveFrom_Success_Helper(IPAddress.Loopback, IPAddress.Loopback); [Fact] - public void ReceiveFromAsyncV6BoundToSpecificV6_Success() - { - ReceiveFromAsync_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback); - } + public Task ReceiveFromV4BoundToAnyV4_Success() => ReceiveFrom_Success_Helper(IPAddress.Any, IPAddress.Loopback); [Fact] - public void ReceiveFromAsyncV6BoundToAnyV6_Success() - { - ReceiveFromAsync_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); - } + public Task ReceiveFromV6BoundToSpecificV6_Success() => ReceiveFrom_Success_Helper(IPAddress.IPv6Loopback, IPAddress.IPv6Loopback); [Fact] - public void ReceiveFromAsyncV6BoundToSpecificV4_NotReceived() - { - Assert.Throws(() => - { - ReceiveFromAsync_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback, expectedToTimeout: true); - }); - } + public Task ReceiveFromV6BoundToAnyV6_Success() => ReceiveFrom_Success_Helper(IPAddress.IPv6Any, IPAddress.IPv6Loopback); [Fact] - public void ReceiveFromAsyncV4BoundToSpecificV6_NotReceived() - { - Assert.Throws(() => - { - ReceiveFromAsync_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback, expectedToTimeout: true); - }); - } + public Task ReceiveFromV4BoundToAnyV6_Success() => ReceiveFrom_Success_Helper(IPAddress.IPv6Any, IPAddress.Loopback); [Fact] - public void ReceiveFromAsyncV6BoundToAnyV4_NotReceived() - { - Assert.Throws(() => - { - ReceiveFromAsync_Helper(IPAddress.Any, IPAddress.IPv6Loopback, expectedToTimeout: true); - }); - } + // Binds to a specific port on 'connectTo' which on Unix may already be in use + // Also ReceiveFrom not supported on OSX + [PlatformSpecific(TestPlatforms.Windows)] + public Task ReceiveFromV6BoundToSpecificV4_NotReceived() => ReceiveFrom_Failure_Helper(IPAddress.Loopback, IPAddress.IPv6Loopback); + + [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)] + public Task ReceiveFromV4BoundToSpecificV6_NotReceived() => ReceiveFrom_Failure_Helper(IPAddress.IPv6Loopback, IPAddress.Loopback); [Fact] - public void ReceiveFromAsyncV4BoundToAnyV6_Success() + // Binds to a specific port on 'connectTo' which on Unix may already be in use + // Also ReceiveFrom not supported on OSX + [PlatformSpecific(TestPlatforms.Windows)] + public Task ReceiveFromV6BoundToAnyV4_NotReceived() => ReceiveFrom_Failure_Helper(IPAddress.Any, IPAddress.IPv6Loopback); + + protected async Task ReceiveFrom_Success_Helper(IPAddress listenOn, IPAddress connectTo) { - ReceiveFromAsync_Helper(IPAddress.IPv6Any, IPAddress.Loopback); + using Socket serverSocket = new Socket(SocketType.Dgram, ProtocolType.Udp); + int port = serverSocket.BindToAnonymousPort(listenOn); + + Socket client = new Socket(connectTo.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + Task sendTask = client.SendToAsync(new byte[1], new IPEndPoint(connectTo, port)) + .WaitAsync(TestSettings.PassingTestTimeout); + + var result = await ReceiveFromAsync(serverSocket, new byte[1], new IPEndPoint(connectTo, port)) + .WaitAsync(TestSettings.PassingTestTimeout); + + Assert.Equal(1, result.ReceivedBytes); + IPEndPoint remoteEndPoint = Assert.IsType(result.RemoteEndPoint); + Assert.Equal(AddressFamily.InterNetworkV6, remoteEndPoint.AddressFamily); + Assert.Equal(connectTo.MapToIPv6(), remoteEndPoint.Address); } - private void ReceiveFromAsync_Helper(IPAddress listenOn, IPAddress connectTo, bool expectedToTimeout = false) + protected async Task ReceiveFrom_Failure_Helper(IPAddress listenOn, IPAddress connectTo) { - using (Socket serverSocket = new Socket(SocketType.Dgram, ProtocolType.Udp)) - { - int port = serverSocket.BindToAnonymousPort(listenOn); - - ManualResetEvent waitHandle = new ManualResetEvent(false); + using Socket serverSocket = new Socket(SocketType.Dgram, ProtocolType.Udp); + int port = serverSocket.BindToAnonymousPort(listenOn); - SocketAsyncEventArgs args = new SocketAsyncEventArgs(); - args.RemoteEndPoint = new IPEndPoint(listenOn, port); - args.SetBuffer(new byte[1], 0, 1); - args.UserToken = waitHandle; - args.Completed += AsyncCompleted; + Socket client = new Socket(connectTo.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + _ = client.SendToAsync(new byte[1], new IPEndPoint(connectTo, port)).WaitAsync(TestSettings.PassingTestTimeout); + await Assert.ThrowsAsync(() => ReceiveFromAsync(serverSocket, new byte[1], new IPEndPoint(connectTo, port)) + .WaitAsync(TestSettings.FailingTestTimeout)); + } + } - bool async = serverSocket.ReceiveFromAsync(args); - SocketUdpClient client = new SocketUdpClient(_log, serverSocket, connectTo, port); - if (async && !waitHandle.WaitOne(expectedToTimeout ? TestSettings.FailingTestTimeout : TestSettings.PassingTestTimeout)) - { - throw new TimeoutException(); - } + [Trait("IPv4", "true")] + [Trait("IPv6", "true")] + [Collection(nameof(DisableParallelization))] + public class DualModeConnectionlessReceiveFromSync : DualModeConnectionlessReceiveFromBase + { + public DualModeConnectionlessReceiveFromSync(ITestOutputHelper output) : base(output) + { + } + } - if (args.SocketError != SocketError.Success) - { - throw new SocketException((int)args.SocketError); - } + [Trait("IPv4", "true")] + [Trait("IPv6", "true")] + [Collection(nameof(DisableParallelization))] + public class DualModeConnectionlessReceiveFromApm : DualModeConnectionlessReceiveFromBase + { + public DualModeConnectionlessReceiveFromApm(ITestOutputHelper output) : base(output) + { + } + } - Assert.Equal(1, args.BytesTransferred); - Assert.Equal(typeof(IPEndPoint), args.RemoteEndPoint.GetType()); - IPEndPoint remoteEndPoint = args.RemoteEndPoint as IPEndPoint; - Assert.Equal(AddressFamily.InterNetworkV6, remoteEndPoint.AddressFamily); - Assert.Equal(connectTo.MapToIPv6(), remoteEndPoint.Address); - } + [Trait("IPv4", "true")] + [Trait("IPv6", "true")] + [Collection(nameof(DisableParallelization))] + public class DualModeConnectionlessReceiveFromEap : DualModeConnectionlessReceiveFromBase + { + public DualModeConnectionlessReceiveFromEap(ITestOutputHelper output) : base(output) + { } + } - #endregion ReceiveFrom Async/Event + [Trait("IPv4", "true")] + [Trait("IPv6", "true")] + [Collection(nameof(DisableParallelization))] + public class DualModeConnectionlessReceiveFromTask : DualModeConnectionlessReceiveFromBase + { + public DualModeConnectionlessReceiveFromTask(ITestOutputHelper output) : base(output) + { + } } - [OuterLoop] [Trait("IPv4", "true")] [Trait("IPv6", "true")] + [Collection(nameof(DisableParallelization))] public class DualModeConnectionlessReceiveMessageFrom : DualModeBase { [Fact] @@ -2356,12 +1703,12 @@ namespace System.Net.Sockets.Tests public class DualModeBase { // Ports 8 and 8887 are unassigned as per https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt - protected const int UnusedPort = 8; + internal const int UnusedPort = 8; protected const int UnusedBindablePort = 8887; protected readonly ITestOutputHelper _log; - protected static IPAddress[] ValidIPv6Loopbacks = new IPAddress[] { + internal static IPAddress[] ValidIPv6Loopbacks = new IPAddress[] { new IPAddress(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1 }, 0), // ::127.0.0.1 IPAddress.Loopback.MapToIPv6(), // ::ffff:127.0.0.1 IPAddress.IPv6Loopback // ::1 @@ -2442,10 +1789,11 @@ namespace System.Net.Sockets.Tests protected class SocketServer : IDisposable { private readonly ITestOutputHelper _output; - private Socket _server; private Socket _acceptedSocket; private EventWaitHandle _waitHandle = new AutoResetEvent(false); + public Socket Socket { get; } + public EventWaitHandle WaitHandle { get { return _waitHandle; } @@ -2457,24 +1805,27 @@ namespace System.Net.Sockets.Tests if (dualMode) { - _server = new Socket(SocketType.Stream, ProtocolType.Tcp); + Socket = new Socket(SocketType.Stream, ProtocolType.Tcp); } else { - _server = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + Socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); } - port = _server.BindToAnonymousPort(address); - _server.Listen(1); + port = Socket.BindToAnonymousPort(address); + Socket.Listen(1); + } - IPAddress remoteAddress = address.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any; + public void Start() + { + IPAddress remoteAddress = Socket.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any; EndPoint remote = new IPEndPoint(remoteAddress, 0); SocketAsyncEventArgs e = new SocketAsyncEventArgs(); e.RemoteEndPoint = remote; e.Completed += new EventHandler(Accepted); e.UserToken = _waitHandle; - _server.AcceptAsync(e); + Socket.AcceptAsync(e); } private void Accepted(object sender, SocketAsyncEventArgs e) @@ -2493,7 +1844,7 @@ namespace System.Net.Sockets.Tests { try { - _server.Dispose(); + Socket.Dispose(); if (_acceptedSocket != null) _acceptedSocket.Dispose(); } @@ -2501,77 +1852,6 @@ namespace System.Net.Sockets.Tests } } - protected class SocketClient - { - private IPAddress _connectTo; - private Socket _serverSocket; - private int _port; - private readonly ITestOutputHelper _output; - - private EventWaitHandle _waitHandle = new AutoResetEvent(false); - public EventWaitHandle WaitHandle - { - get { return _waitHandle; } - } - - public SocketError Error - { - get; - private set; - } - - public SocketClient(ITestOutputHelper output, Socket serverSocket, IPAddress connectTo, int port) - { - _output = output; - _connectTo = connectTo; - _serverSocket = serverSocket; - _port = port; - Error = SocketError.Success; - - Task.Run(() => ConnectClient(null)); - } - - private void ConnectClient(object state) - { - try - { - Socket socket = new Socket(_connectTo.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - - SocketAsyncEventArgs e = new SocketAsyncEventArgs(); - e.Completed += new EventHandler(Connected); - e.RemoteEndPoint = new IPEndPoint(_connectTo, _port); - e.UserToken = _waitHandle; - - if (!socket.ConnectAsync(e)) - { - Connected(socket, e); - } - } - catch (SocketException ex) - { - Error = ex.SocketErrorCode; - Thread.Sleep(TestSettings.FailingTestTimeout); // Give the other end a chance to call Accept(). - _serverSocket.Dispose(); // Cancels the test - _waitHandle.Set(); - } - } - private void Connected(object sender, SocketAsyncEventArgs e) - { - EventWaitHandle handle = (EventWaitHandle)e.UserToken; - _output.WriteLine( - "Connected: " + e.GetHashCode() + " SocketAsyncEventArgs with manual event " + - handle.GetHashCode() + " error: " + e.SocketError); - - Error = e.SocketError; - if (Error != SocketError.Success) - { - Thread.Sleep(TestSettings.FailingTestTimeout); // Give the other end a chance to call Accept(). - _serverSocket.Dispose(); // Cancels the test - } - handle.Set(); - } - } - protected class SocketUdpServer : IDisposable { private readonly ITestOutputHelper _output; diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive/SendReceive.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive/SendReceive.cs index b94fe8c..e096d53 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive/SendReceive.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive/SendReceive.cs @@ -892,14 +892,14 @@ namespace System.Net.Sockets.Tests { b.SignalAndWait(); client.Dispose(); - }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).WaitAsync(TestSettings.PassingTestTimeout); Task send = Task.Factory.StartNew(() => { SendAsync(server, new ArraySegment(new byte[1])).GetAwaiter().GetResult(); b.SignalAndWait(); ReceiveAsync(client, new ArraySegment(new byte[1])).GetAwaiter().GetResult(); - }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).WaitAsync(TestSettings.PassingTestTimeout); await dispose; Exception error = await Record.ExceptionAsync(() => send);