diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs index 90f2d511473575..7b8859330820f8 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs @@ -1089,8 +1089,10 @@ public Socket Accept() Debug.Assert(!acceptedSocketHandle.IsInvalid); - Socket socket = CreateAcceptSocket(acceptedSocketHandle, _rightEndPoint.Create(socketAddress)); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Accepted(socket, socket.RemoteEndPoint!, socket.LocalEndPoint); + // macOS can return accept() success with an empty remote sockaddr when the peer reset before accept. + EndPoint? remoteEndPoint = socketAddress.Size > 0 ? _rightEndPoint.Create(socketAddress) : null; + Socket socket = CreateAcceptSocket(acceptedSocketHandle, remoteEndPoint); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Accepted(socket, remoteEndPoint, socket.LocalEndPoint); return socket; } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs index c618678c30dc67..f3b28c5ff3e9bc 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs @@ -1013,13 +1013,17 @@ internal void FinishOperationSyncSuccess(int bytesTransferred, SocketFlags flags if (socketError == SocketError.Success) { - _acceptSocket = _currentSocket.UpdateAcceptSocket(_acceptSocket!, _currentSocket._rightEndPoint!.Create(remoteSocketAddress)); + // macOS can return accept() success with an empty remote sockaddr when the peer reset before accept. + EndPoint? remoteEndPoint = remoteSocketAddress.Size > 0 + ? _currentSocket._rightEndPoint!.Create(remoteSocketAddress) + : null; + _acceptSocket = _currentSocket.UpdateAcceptSocket(_acceptSocket!, remoteEndPoint); if (NetEventSource.Log.IsEnabled()) { try { - NetEventSource.Accepted(_acceptSocket, _acceptSocket.RemoteEndPoint, _acceptSocket.LocalEndPoint); + NetEventSource.Accepted(_acceptSocket, remoteEndPoint, _acceptSocket.LocalEndPoint); } catch (ObjectDisposedException) { } } diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs index 3356a5ac3b7f05..4f37a9439b0749 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; @@ -493,4 +494,60 @@ public sealed class AcceptEap : Accept { public AcceptEap(ITestOutputHelper output) : base(output) {} } + + public sealed class AcceptDualStackResetTests + { + public static bool SupportsIPv6DualMode => + Socket.OSSupportsIPv6 && !RuntimeInformation.IsOSPlatform(OSPlatform.Create("OPENBSD")); + + [ConditionalTheory(typeof(AcceptDualStackResetTests), nameof(SupportsIPv6DualMode))] + [SkipOnPlatform(TestPlatforms.Wasi, "These platforms don't support dual-mode sockets")] + [InlineData(false)] + [InlineData(true)] + public async Task Accept_DualStackListener_PeerImmediatelyResets_ListenerStaysHealthy(bool useAsync) + { + for (int i = 0; i < 200; i++) + { + using Socket listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); + listener.DualMode = true; + listener.Bind(new IPEndPoint(IPAddress.IPv6Any, 0)); + int port = ((IPEndPoint)listener.LocalEndPoint!).Port; + listener.Listen(2); + + using Socket ipv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; + using Socket ipv4 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; + + await ipv4.ConnectAsync(IPAddress.Loopback, port).WaitAsync(TimeSpan.FromSeconds(5)); + ipv4.LingerState = new LingerOption(true, 0); + ipv4.Close(); + + await ipv6.ConnectAsync(IPAddress.IPv6Loopback, port).WaitAsync(TimeSpan.FromSeconds(5)); + byte[] message = [42]; + Assert.Equal(message.Length, ipv6.Send(message)); + + bool receivedMessage = false; + for (int acceptCount = 0; acceptCount < 2 && !receivedMessage; acceptCount++) + { + try + { + using Socket accepted = useAsync + ? await listener.AcceptAsync().WaitAsync(TimeSpan.FromSeconds(5)) + : listener.Accept(); + + byte[] received = new byte[message.Length]; + int receivedCount = await accepted.ReceiveAsync(received).WaitAsync(TimeSpan.FromSeconds(5)); + receivedMessage = receivedCount == message.Length && received.AsSpan().SequenceEqual(message); + } + catch (SocketException) + { + // Some platforms surface the reset connection from accept() or the following receive, + // while others discard it. Either way the listener must stay healthy, so tolerate the + // reset and try to accept the healthy peer on a subsequent iteration. + } + } + + Assert.True(receivedMessage); + } + } + } }