diff --git a/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs b/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs index 5d2ef3ee1f0980..374aa5a21e9392 100644 --- a/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs +++ b/src/libraries/Common/tests/System/Net/Sockets/SocketTestExtensions.cs @@ -38,14 +38,21 @@ public static void ForceNonBlocking(this Socket socket, bool force) } } - public static (Socket, Socket) CreateConnectedSocketPair() + public static (Socket, Socket) CreateConnectedSocketPair() => CreateConnectedSocketPair(IPAddress.Loopback, false); + + public static (Socket, Socket) CreateConnectedSocketPair(IPAddress serverAddress, bool dualModeClient) { - using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + using Socket listener = new Socket(serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + listener.Bind(new IPEndPoint(serverAddress, 0)); listener.Listen(1); - Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - client.Connect(listener.LocalEndPoint); + IPEndPoint connectTo = (IPEndPoint)listener.LocalEndPoint; + if (dualModeClient) connectTo = new IPEndPoint(connectTo.Address.MapToIPv6(), connectTo.Port); + + + Socket client = new Socket(connectTo.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + if (dualModeClient) client.DualMode = true; + client.Connect(connectTo); Socket server = listener.Accept(); return (client, server); diff --git a/src/libraries/Native/Unix/System.Native/pal_networking.c b/src/libraries/Native/Unix/System.Native/pal_networking.c index 09ec260149f497..cf080d439ac478 100644 --- a/src/libraries/Native/Unix/System.Native/pal_networking.c +++ b/src/libraries/Native/Unix/System.Native/pal_networking.c @@ -3085,6 +3085,11 @@ int32_t SystemNative_Disconnect(intptr_t socket) addr.sa_family = AF_UNSPEC; err = connect(fd, &addr, sizeof(addr)); + if (err != 0) + { + // On some older kernels connect(AF_UNSPEC) may fail. Fall back to shutdown in these cases: + err = shutdown(fd, SHUT_RDWR); + } #elif HAVE_DISCONNECTX // disconnectx causes a FIN close on OSX. It's the best we can do. err = disconnectx(fd, SAE_ASSOCID_ANY, SAE_CONNID_ANY); diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs index 7f0dc3adc14a41..15a6de75bc1739 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs @@ -289,16 +289,26 @@ public async Task AcceptAsync_MultipleAcceptsThenDispose_AcceptsThrowAfterDispos } } - [Fact] - public async Task AcceptGetsCanceledByDispose() + public static readonly TheoryData AcceptGetsCanceledByDispose_Data = new TheoryData + { + { IPAddress.Loopback }, + { IPAddress.IPv6Loopback }, + { IPAddress.Loopback.MapToIPv6() } + }; + + [Theory] + [MemberData(nameof(AcceptGetsCanceledByDispose_Data))] + public async Task AcceptGetsCanceledByDispose(IPAddress loopback) { // We try this a couple of times to deal with a timing race: if the Dispose happens // before the operation is started, we won't see a SocketException. int msDelay = 100; - await RetryHelper.ExecuteAsync(async () => + int retries = 10; + while (true) { - var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + var listener = new Socket(loopback.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + if (loopback.IsIPv4MappedToIPv6) listener.DualMode = true; + listener.Bind(new IPEndPoint(loopback, 0)); listener.Listen(1); Task acceptTask = AcceptAsync(listener); @@ -330,20 +340,33 @@ await RetryHelper.ExecuteAsync(async () => disposedException = true; } - if (UsesApm) - { - Assert.Null(localSocketError); - Assert.True(disposedException); - } - else if (UsesSync) + try { - Assert.Equal(SocketError.Interrupted, localSocketError); + if (UsesApm) + { + Assert.Null(localSocketError); + Assert.True(disposedException); + } + else if (UsesSync) + { + Assert.Equal(SocketError.Interrupted, localSocketError); + } + else + { + Assert.Equal(SocketError.OperationAborted, localSocketError); + } + break; } - else + catch { - Assert.Equal(SocketError.OperationAborted, localSocketError); + if (retries-- > 0) + { + continue; + } + + throw; } - }, maxAttempts: 10); + } } [Fact] diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs index 08ecf15f3b925b..89b5e68d4c53e7 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs @@ -120,18 +120,28 @@ public async Task Connect_AfterDisconnect_Fails() } } - [Fact] + public static readonly TheoryData ConnectGetsCanceledByDispose_Data = new TheoryData + { + { IPAddress.Parse("1.1.1.1") }, + // TODO: Figure out how to test this with vanilla IPV6 + { IPAddress.Parse("1.1.1.1").MapToIPv6() }, + }; + + [Theory] + [MemberData(nameof(ConnectGetsCanceledByDispose_Data))] [PlatformSpecific(~(TestPlatforms.OSX | TestPlatforms.FreeBSD))] // Not supported on BSD like OSes. - public async Task ConnectGetsCanceledByDispose() + public async Task ConnectGetsCanceledByDispose(IPAddress address) { // We try this a couple of times to deal with a timing race: if the Dispose happens // before the operation is started, we won't see a SocketException. int msDelay = 100; - await RetryHelper.ExecuteAsync(async () => + int retries = 10; + while (true) { - var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + var client = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + if (address.IsIPv4MappedToIPv6) client.DualMode = true; - Task connectTask = ConnectAsync(client, new IPEndPoint(IPAddress.Parse("1.1.1.1"), 23)); + Task connectTask = ConnectAsync(client, new IPEndPoint(address, 23)); // Wait a little so the operation is started. await Task.Delay(msDelay); @@ -153,9 +163,6 @@ await RetryHelper.ExecuteAsync(async () => } catch (SocketException se) { - // On connection timeout, retry. - Assert.NotEqual(SocketError.TimedOut, se.SocketErrorCode); - localSocketError = se.SocketErrorCode; } catch (ObjectDisposedException) @@ -163,20 +170,36 @@ await RetryHelper.ExecuteAsync(async () => disposedException = true; } - if (UsesApm) - { - Assert.Null(localSocketError); - Assert.True(disposedException); - } - else if (UsesSync) + try { - Assert.Equal(SocketError.NotSocket, localSocketError); + // On connection timeout, retry. + Assert.NotEqual(SocketError.TimedOut, localSocketError); + + if (UsesApm) + { + Assert.Null(localSocketError); + Assert.True(disposedException); + } + else if (UsesSync) + { + Assert.Equal(SocketError.NotSocket, localSocketError); + } + else + { + Assert.Equal(SocketError.OperationAborted, localSocketError); + } + break; } - else + catch { - Assert.Equal(SocketError.OperationAborted, localSocketError); + if (retries-- > 0) + { + continue; + } + + throw; } - }, maxAttempts: 10); + } } } diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs index 3823e511ba59b4..d73d831c1f2bfb 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/InlineCompletions.Unix.cs @@ -31,8 +31,8 @@ public void InlineSocketContinuations() await new SendReceiveEap(null).SendRecv_Stream_TCP(IPAddress.Loopback, useMultipleBuffers: false); await new SendReceiveEap(null).SendRecv_Stream_TCP_MultipleConcurrentReceives(IPAddress.Loopback, useMultipleBuffers: false); await new SendReceiveEap(null).SendRecv_Stream_TCP_MultipleConcurrentSends(IPAddress.Loopback, useMultipleBuffers: false); - await new SendReceiveEap(null).TcpReceiveSendGetsCanceledByDispose(receiveOrSend: true); - await new SendReceiveEap(null).TcpReceiveSendGetsCanceledByDispose(receiveOrSend: false); + await new SendReceiveEap(null).TcpReceiveSendGetsCanceledByDispose(receiveOrSend: true, serverAddress: IPAddress.Loopback, dualModeClient: false); + await new SendReceiveEap(null).TcpReceiveSendGetsCanceledByDispose(receiveOrSend: false, serverAddress: IPAddress.Loopback, dualModeClient: false); }, options).Dispose(); } } diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs index 3a8db0f161ee6e..31f3ccea9482d4 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs @@ -288,7 +288,8 @@ public async Task SyncSendFileGetsCanceledByDispose() // before the operation is started, the peer won't see a ConnectionReset SocketException and we won't // see a SocketException either. int msDelay = 100; - await RetryHelper.ExecuteAsync(async () => + int retries = 10; + while (true) { (Socket socket1, Socket socket2) = SocketTestExtensions.CreateConnectedSocketPair(); using (socket2) @@ -328,34 +329,48 @@ await RetryHelper.ExecuteAsync(async () => } catch (ObjectDisposedException) { } - Assert.Equal(SocketError.ConnectionAborted, localSocketError); - // On OSX, we're unable to unblock the on-going socket operations and - // perform an abortive close. - if (!PlatformDetection.IsOSXLike) + try { - SocketError? peerSocketError = null; - var receiveBuffer = new byte[4096]; - while (true) + Assert.Equal(SocketError.ConnectionAborted, localSocketError); + + // On OSX, we're unable to unblock the on-going socket operations and + // perform an abortive close. + if (!PlatformDetection.IsOSXLike) { - try + SocketError? peerSocketError = null; + var receiveBuffer = new byte[4096]; + while (true) { - int received = socket2.Receive(receiveBuffer); - if (received == 0) + try { + int received = socket2.Receive(receiveBuffer); + if (received == 0) + { + break; + } + } + catch (SocketException se) + { + peerSocketError = se.SocketErrorCode; break; } } - catch (SocketException se) - { - peerSocketError = se.SocketErrorCode; - break; - } + Assert.Equal(SocketError.ConnectionReset, peerSocketError); } - Assert.Equal(SocketError.ConnectionReset, peerSocketError); + break; + } + catch + { + if (retries-- > 0) + { + continue; + } + + throw; } } - }, maxAttempts: 10); + } } [OuterLoop] diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs index 50a329e044c2b0..2cfb14393a4ee8 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive.cs @@ -937,17 +937,27 @@ error is SocketException || } } - [Fact] + public static readonly TheoryData UdpReceiveGetsCanceledByDispose_Data = new TheoryData + { + { IPAddress.Loopback }, + { IPAddress.IPv6Loopback }, + { IPAddress.Loopback.MapToIPv6() } + }; + + [Theory] + [MemberData(nameof(UdpReceiveGetsCanceledByDispose_Data))] [PlatformSpecific(~TestPlatforms.OSX)] // Not supported on OSX. - public async Task UdpReceiveGetsCanceledByDispose() + public async Task UdpReceiveGetsCanceledByDispose(IPAddress address) { // We try this a couple of times to deal with a timing race: if the Dispose happens // before the operation is started, we won't see a SocketException. int msDelay = 100; - await RetryHelper.ExecuteAsync(async () => + int retries = 10; + while (true) { - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - socket.BindToAnonymousPort(IPAddress.Loopback); + var socket = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + if (address.IsIPv4MappedToIPv6) socket.DualMode = true; + socket.BindToAnonymousPort(address); Task receiveTask = ReceiveAsync(socket, new ArraySegment(new byte[1])); @@ -978,34 +988,57 @@ await RetryHelper.ExecuteAsync(async () => disposedException = true; } - if (UsesApm) - { - Assert.Null(localSocketError); - Assert.True(disposedException); - } - else if (UsesSync) + try { - Assert.Equal(SocketError.Interrupted, localSocketError); + if (UsesApm) + { + Assert.Null(localSocketError); + Assert.True(disposedException); + } + else if (UsesSync) + { + Assert.Equal(SocketError.Interrupted, localSocketError); + } + else + { + Assert.Equal(SocketError.OperationAborted, localSocketError); + } + break; } - else + catch { - Assert.Equal(SocketError.OperationAborted, localSocketError); + if (retries-- > 0) + { + continue; + } + + throw; } - }, maxAttempts: 10); + } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task TcpReceiveSendGetsCanceledByDispose(bool receiveOrSend) + public static readonly TheoryData TcpReceiveSendGetsCanceledByDispose_Data = new TheoryData + { + { true, IPAddress.Loopback, false }, + { true, IPAddress.Loopback, true }, + { true, IPAddress.IPv6Loopback, false }, + { false, IPAddress.Loopback, false }, + { false, IPAddress.Loopback, true }, + { false, IPAddress.IPv6Loopback, false }, + }; + + [Theory(Timeout = 30000)] + [MemberData(nameof(TcpReceiveSendGetsCanceledByDispose_Data))] + public async Task TcpReceiveSendGetsCanceledByDispose(bool receiveOrSend, IPAddress serverAddress, bool dualModeClient) { // We try this a couple of times to deal with a timing race: if the Dispose happens // before the operation is started, the peer won't see a ConnectionReset SocketException and we won't // see a SocketException either. int msDelay = 100; - await RetryHelper.ExecuteAsync(async () => + int retries = 10; + while (true) { - (Socket socket1, Socket socket2) = SocketTestExtensions.CreateConnectedSocketPair(); + (Socket socket1, Socket socket2) = SocketTestExtensions.CreateConnectedSocketPair(serverAddress, dualModeClient); using (socket2) { Task socketOperation; @@ -1031,7 +1064,7 @@ await RetryHelper.ExecuteAsync(async () => Task disposeTask = Task.Run(() => socket1.Dispose()); var cts = new CancellationTokenSource(); - Task timeoutTask = Task.Delay(30000, cts.Token); + Task timeoutTask = Task.Delay(15000, cts.Token); Assert.NotSame(timeoutTask, await Task.WhenAny(disposeTask, socketOperation, timeoutTask)); cts.Cancel(); @@ -1052,46 +1085,59 @@ await RetryHelper.ExecuteAsync(async () => disposedException = true; } - if (UsesApm) - { - Assert.Null(localSocketError); - Assert.True(disposedException); - } - else if (UsesSync) - { - Assert.Equal(SocketError.ConnectionAborted, localSocketError); - } - else + try { - Assert.Equal(SocketError.OperationAborted, localSocketError); - } + if (UsesApm) + { + Assert.Null(localSocketError); + Assert.True(disposedException); + } + else if (UsesSync) + { + Assert.Equal(SocketError.ConnectionAborted, localSocketError); + } + else + { + Assert.Equal(SocketError.OperationAborted, localSocketError); + } - // On OSX, we're unable to unblock the on-going socket operations and - // perform an abortive close. - if (!(UsesSync && PlatformDetection.IsOSXLike)) - { - SocketError? peerSocketError = null; - var receiveBuffer = new ArraySegment(new byte[4096]); - while (true) + // On OSX, we're unable to unblock the on-going socket operations and + // perform an abortive close. + if (!(UsesSync && PlatformDetection.IsOSXLike)) { - try + SocketError? peerSocketError = null; + var receiveBuffer = new ArraySegment(new byte[4096]); + while (true) { - int received = await ReceiveAsync(socket2, receiveBuffer); - if (received == 0) + try + { + int received = await ReceiveAsync(socket2, receiveBuffer); + if (received == 0) + { + break; + } + } + catch (SocketException se) { + peerSocketError = se.SocketErrorCode; break; } } - catch (SocketException se) - { - peerSocketError = se.SocketErrorCode; - break; - } + Assert.Equal(SocketError.ConnectionReset, peerSocketError); } - Assert.Equal(SocketError.ConnectionReset, peerSocketError); + break; + } + catch + { + if (retries-- > 0) + { + continue; + } + + throw; } } - }, maxAttempts: 10); + } } [Fact]