diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index 8db079595b1217..bbaaf1b890477c 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -45,9 +45,7 @@ - - @@ -100,7 +98,6 @@ - @@ -198,7 +195,6 @@ - diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Unix.cs deleted file mode 100644 index 13b8061a397b0a..00000000000000 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Unix.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net; -using System.Runtime.InteropServices; -using System.Threading; -using Microsoft.Win32; - -namespace System.Net.Sockets -{ - // ConnectOverlappedAsyncResult - used to take care of storage for async Socket BeginConnect call. - internal sealed partial class ConnectOverlappedAsyncResult : BaseOverlappedAsyncResult - { - public void CompletionCallback(SocketError errorCode) - { - CompletionCallback(0, errorCode); - } - - // This method is called by base.CompletionPortCallback base.OverlappedCallback as part of IO completion - internal override object? PostCompletion(int numBytes) - { - var errorCode = (SocketError)ErrorCode; - if (errorCode == SocketError.Success) - { - var socket = (Socket)AsyncObject!; - socket.SetToConnected(); - return socket; - } - - return null; - } - } -} diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Windows.cs deleted file mode 100644 index 0288bd648047ad..00000000000000 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Windows.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Net.Sockets -{ - // ConnectOverlappedAsyncResult - used to take care of storage for async Socket BeginConnect call. - internal sealed partial class ConnectOverlappedAsyncResult : BaseOverlappedAsyncResult - { - // This method is called by base.CompletionPortCallback base.OverlappedCallback as part of IO completion - internal override unsafe object? PostCompletion(int numBytes) - { - SocketError errorCode = (SocketError)ErrorCode; - Socket socket = (Socket)AsyncObject!; - - if (errorCode == SocketError.Success) - { - // Set the socket context. - try - { - errorCode = Interop.Winsock.setsockopt( - socket.SafeHandle, - SocketOptionLevel.Socket, - SocketOptionName.UpdateConnectContext, - null, - 0); - if (errorCode == SocketError.SocketError) - { - errorCode = SocketPal.GetLastSocketError(); - } - } - catch (ObjectDisposedException) - { - errorCode = SocketError.OperationAborted; - } - - ErrorCode = (int)errorCode; - } - - if (errorCode == SocketError.Success) - { - socket.SetToConnected(); - return socket; - } - - return null; - } - } -} diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.cs deleted file mode 100644 index c283707194913d..00000000000000 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Net.Sockets -{ - // ConnectOverlappedAsyncResult - used to take care of storage for async Socket BeginConnect call. - internal sealed partial class ConnectOverlappedAsyncResult : BaseOverlappedAsyncResult - { - private readonly EndPoint _endPoint; - - internal ConnectOverlappedAsyncResult(Socket socket, EndPoint endPoint, object? asyncState, AsyncCallback? asyncCallback) : - base(socket, asyncState, asyncCallback) - { - _endPoint = endPoint; - } - - internal override EndPoint RemoteEndPoint - { - get { return _endPoint; } - } - } -} diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/MultipleConnectAsync.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/MultipleConnectAsync.cs deleted file mode 100644 index 17ca33376cbffd..00000000000000 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/MultipleConnectAsync.cs +++ /dev/null @@ -1,527 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; - -namespace System.Net.Sockets -{ - // This object is used to wrap a bunch of ConnectAsync operations - // on behalf of a single user call to ConnectAsync with a DnsEndPoint - internal abstract class MultipleConnectAsync - { - protected SocketAsyncEventArgs? _userArgs; - protected SocketAsyncEventArgs? _internalArgs; - - protected DnsEndPoint? _endPoint; - protected IPAddress[]? _addressList; - protected int _nextAddress; - - private enum State - { - NotStarted, - DnsQuery, - ConnectAttempt, - Completed, - Canceled, - } - - private State _state; - - private readonly object _lockObject = new object(); - - // Called by Socket to kick off the ConnectAsync process. We'll complete the user's SAEA when it's done. - // Returns true if the operation is pending, false if it completed synchronously. - public bool StartConnectAsync(SocketAsyncEventArgs args, DnsEndPoint endPoint) - { - IAsyncResult result; - - Debug.Assert(!Monitor.IsEntered(_lockObject)); - lock (_lockObject) - { - Debug.Assert(endPoint.AddressFamily == AddressFamily.Unspecified || - endPoint.AddressFamily == AddressFamily.InterNetwork || - endPoint.AddressFamily == AddressFamily.InterNetworkV6, - $"Unexpected endpoint address family: {endPoint.AddressFamily}"); - - _userArgs = args; - _endPoint = endPoint; - - // If Cancel() was called before we got the lock, it only set the state to Canceled: we need to - // fail synchronously from here. Once State.DnsQuery is set, the Cancel() call will handle calling AsyncFail. - if (_state == State.Canceled) - { - SyncFail(new SocketException((int)SocketError.OperationAborted)); - return false; - } - - Debug.Assert(_state == State.NotStarted, "MultipleConnectAsync.StartConnectAsync(): Unexpected object state"); - - _state = State.DnsQuery; - - result = Dns.BeginGetHostAddresses(endPoint.Host, new AsyncCallback(DnsCallback), null); - } - - if (result.CompletedSynchronously) - { - return DoDnsCallback(result, true); - } - else - { - return true; - } - } - - // Callback which fires when the Dns Resolve is complete - private void DnsCallback(IAsyncResult result) - { - if (!result.CompletedSynchronously) - { - DoDnsCallback(result, false); - } - } - - // Called when the DNS query completes (either synchronously or asynchronously). Checks for failure and - // starts the first connection attempt if it succeeded. - // Returns true if the operation is pending, false if it completed synchronously. - private bool DoDnsCallback(IAsyncResult result, bool sync) - { - Exception? exception = null; - bool pending = false; - - Debug.Assert(!Monitor.IsEntered(_lockObject)); - lock (_lockObject) - { - // If the connection attempt was canceled during the dns query, the user's callback has already been - // called asynchronously and we simply need to return. - if (_state == State.Canceled) - { - return true; - } - - Debug.Assert(_state == State.DnsQuery, "MultipleConnectAsync.DoDnsCallback(): Unexpected object state"); - - try - { - _addressList = Dns.EndGetHostAddresses(result); - Debug.Assert(_addressList != null, "MultipleConnectAsync.DoDnsCallback(): EndGetHostAddresses returned null!"); - } - catch (Exception e) - { - _state = State.Completed; - exception = e; - } - - // If the dns query succeeded, try to connect to the first address - if (exception == null) - { - _state = State.ConnectAttempt; - - _internalArgs = new SocketAsyncEventArgs(); - _internalArgs.Completed += InternalConnectCallback; - _internalArgs.CopyBufferFrom(_userArgs!); - - (exception, pending) = AttemptConnection(); - - if (exception != null) - { - // There was a synchronous error while connecting - _state = State.Completed; - } - } - } - - // Call this outside of the lock because it might call the user's callback. - if (exception != null) - { - return Fail(sync, exception); - } - else if (!pending) - { - return DoConnectCallback(_internalArgs!, sync); - } - else - { - return true; - } - } - - private void InternalConnectCallback(object? sender, SocketAsyncEventArgs args) - { - DoConnectCallback(args, false); - } - - // Callback which fires when an internal connection attempt completes. - // If it failed and there are more addresses to try, do it. - // Returns true if the operation is pending, false if it completed synchronously. - private bool DoConnectCallback(SocketAsyncEventArgs args, bool sync) - { - Exception? exception = null; - - Debug.Assert(!Monitor.IsEntered(_lockObject)); - lock (_lockObject) - { - if (_state == State.Canceled) - { - // If Cancel was called before we got the lock, the Socket will be closed soon. We need to report - // OperationAborted (even though the connection actually completed), or the user will try to use a - // closed Socket. - exception = new SocketException((int)SocketError.OperationAborted); - } - else - { - while (true) - { - Debug.Assert(_state == State.ConnectAttempt); - - if (args.SocketError == SocketError.Success) - { - // The connection attempt succeeded; go to the completed state. - // The callback will be called outside the lock. - _state = State.Completed; - break; - } - else if (args.SocketError == SocketError.OperationAborted) - { - // The socket was closed while the connect was in progress. This can happen if the user - // closes the socket, and is equivalent to a call to CancelConnectAsync - exception = new SocketException((int)SocketError.OperationAborted); - _state = State.Canceled; - break; - } - else - { - - // Keep track of this because it will be overwritten by AttemptConnection - SocketError currentFailure = args.SocketError; - - (Exception? connectException, bool pending) = AttemptConnection(); - - if (connectException == null) - { - if (pending) - { - // don't call the callback, another connection attempt is successfully started - return true; - } - - // We have a sync completion from AttemptConnection. - // Loop around and process its results. - } - else - { - SocketException? socketException = connectException as SocketException; - if (socketException != null && socketException.SocketErrorCode == SocketError.NoData) - { - // If the error is NoData, that means there are no more IPAddresses to attempt - // a connection to. Return the last error from an actual connection instead. - exception = new SocketException((int)currentFailure); - } - else - { - exception = connectException; - } - - _state = State.Completed; - break; - } - } - } - } - } - - if (exception != null) - { - return Fail(sync, exception); - } - else - { - return Succeed(sync); - } - } - - // Called to initiate a connection attempt to the next address in the list. - // Returns (exception, false) if the attempt failed synchronously. - // Returns (null, true) if pending, or (null, false) if completed synchronously. - private (Exception? exception, bool pending) AttemptConnection() - { - try - { - IPAddress? attemptAddress = GetNextAddress(out Socket? attemptSocket); - if (attemptAddress == null) - { - return (new SocketException((int)SocketError.NoData), false); - } - Debug.Assert(attemptSocket != null); - - SocketAsyncEventArgs args = _internalArgs!; - args.RemoteEndPoint = new IPEndPoint(attemptAddress, _endPoint!.Port); - bool pending = attemptSocket.ConnectAsync(args); - return (null, pending); - } - catch (ObjectDisposedException) - { - // This can happen if the user closes the socket and is equivalent to a call to CancelConnectAsync. - return (new SocketException((int)SocketError.OperationAborted), false); - } - catch (Exception e) - { - return (e, false); - } - } - - protected abstract void OnSucceed(); - - private bool Succeed(bool sync) - { - OnSucceed(); - - if (sync) - { - _userArgs!.FinishWrapperConnectSyncSuccess(_internalArgs!.ConnectSocket, _internalArgs.BytesTransferred, _internalArgs.SocketFlags); - } - else - { - _userArgs!.FinishWrapperConnectAsyncSuccess(_internalArgs!.ConnectSocket, _internalArgs.BytesTransferred, _internalArgs.SocketFlags); - } - - _internalArgs.Dispose(); - return !sync; - } - - protected abstract void OnFail(bool abortive); - - private bool Fail(bool sync, Exception e) - { - if (sync) - { - SyncFail(e); - return false; - } - else - { - AsyncFail(e); - return true; - } - } - - private void SyncFail(Exception e) - { - OnFail(false); - - if (_internalArgs != null) - { - _internalArgs.Dispose(); - } - - SocketException? socketException = e as SocketException; - if (socketException != null) - { - _userArgs!.FinishConnectByNameSyncFailure(socketException, 0, SocketFlags.None); - } - else - { - ExceptionDispatchInfo.Throw(e); - } - } - - private void AsyncFail(Exception e) - { - OnFail(false); - - if (_internalArgs != null) - { - _internalArgs.Dispose(); - } - - _userArgs!.FinishConnectByNameAsyncFailure(e, 0, SocketFlags.None); - } - - public void Cancel() - { - bool callOnFail = false; - - Debug.Assert(!Monitor.IsEntered(_lockObject)); - lock (_lockObject) - { - switch (_state) - { - case State.NotStarted: - // Cancel was called before the Dns query was started. The dns query won't be started - // and the connection attempt will fail synchronously after the state change to DnsQuery. - // All we need to do here is close all the sockets. - callOnFail = true; - break; - - case State.DnsQuery: - // Cancel was called after the Dns query was started, but before it finished. We can't - // actually cancel the Dns query, but we'll fake it by failing the connect attempt asynchronously - // from here, and silently dropping the connection attempt when the Dns query finishes. - Task.Factory.StartNew( - s => CallAsyncFail(s), - null, - CancellationToken.None, - TaskCreationOptions.DenyChildAttach, - TaskScheduler.Default); - - callOnFail = true; - break; - - case State.ConnectAttempt: - // Cancel was called after the Dns query completed, but before we had a connection result to give - // to the user. Closing the sockets will cause any in-progress ConnectAsync call to fail immediately - // with OperationAborted, and will cause ObjectDisposedException from any new calls to ConnectAsync - // (which will be translated to OperationAborted by AttemptConnection). - callOnFail = true; - break; - - case State.Completed: - // Cancel was called after we locked in a result to give to the user. Ignore it and give the user - // the real completion. - break; - - default: - Debug.Fail($"Unexpected object state: {_state}"); - break; - } - - _state = State.Canceled; - } - - // Call this outside the lock because Socket.Close may block - if (callOnFail) - { - OnFail(true); - } - } - - // Call AsyncFail on a threadpool thread so it's asynchronous with respect to Cancel(). - private void CallAsyncFail(object? ignored) - { - AsyncFail(new SocketException((int)SocketError.OperationAborted)); - } - - protected abstract IPAddress? GetNextAddress(out Socket? attemptSocket); - } - - // Used when the instance ConnectAsync method is called, or when the DnsEndPoint specified - // an AddressFamily. There's only one Socket, and we only try addresses that match its - // AddressFamily - internal sealed class SingleSocketMultipleConnectAsync : MultipleConnectAsync - { - private readonly Socket _socket; - private readonly bool _userSocket; - - public SingleSocketMultipleConnectAsync(Socket socket, bool userSocket) - { - _socket = socket; - _userSocket = userSocket; - } - - protected override IPAddress? GetNextAddress(out Socket? attemptSocket) - { - _socket.ReplaceHandleIfNecessaryAfterFailedConnect(); - - IPAddress? rval = null; - do - { - if (_nextAddress >= _addressList!.Length) - { - attemptSocket = null; - return null; - } - - rval = _addressList[_nextAddress]; - ++_nextAddress; - } - while (!_socket.CanTryAddressFamily(rval.AddressFamily)); - - attemptSocket = _socket; - return rval; - } - - protected override void OnFail(bool abortive) - { - // Close the socket if this is an abortive failure (CancelConnectAsync) - // or if we created it internally - if (abortive || !_userSocket) - { - _socket.Dispose(); - } - } - - // nothing to do on success - protected override void OnSucceed() { } - } - - // This is used when the static ConnectAsync method is called. We don't know the address family - // ahead of time, so we create both IPv4 and IPv6 sockets. - internal sealed class DualSocketMultipleConnectAsync : MultipleConnectAsync - { - private readonly Socket? _socket4; - private readonly Socket? _socket6; - - public DualSocketMultipleConnectAsync(SocketType socketType, ProtocolType protocolType) - { - if (Socket.OSSupportsIPv4) - { - _socket4 = new Socket(AddressFamily.InterNetwork, socketType, protocolType); - } - if (Socket.OSSupportsIPv6) - { - _socket6 = new Socket(AddressFamily.InterNetworkV6, socketType, protocolType); - } - } - - protected override IPAddress? GetNextAddress(out Socket? attemptSocket) - { - IPAddress? rval = null; - attemptSocket = null; - - while (attemptSocket == null) - { - if (_nextAddress >= _addressList!.Length) - { - return null; - } - - rval = _addressList[_nextAddress]; - ++_nextAddress; - - if (rval.AddressFamily == AddressFamily.InterNetworkV6) - { - attemptSocket = _socket6; - } - else if (rval.AddressFamily == AddressFamily.InterNetwork) - { - attemptSocket = _socket4; - } - } - - attemptSocket?.ReplaceHandleIfNecessaryAfterFailedConnect(); - return rval; - } - - // on success, close the socket that wasn't used - protected override void OnSucceed() - { - if (_socket4 != null && !_socket4.Connected) - { - _socket4.Dispose(); - } - if (_socket6 != null && !_socket6.Connected) - { - _socket6.Dispose(); - } - } - - // close both sockets whether its abortive or not - we always create them internally - protected override void OnFail(bool abortive) - { - _socket4?.Dispose(); - _socket6?.Dispose(); - } - } -} diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs index 2318077011db88..6c6655f0ed94ce 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs @@ -128,36 +128,67 @@ static async ValueTask WaitForConnectWithCancellation(AwaitableSocketAsyncEventA internal ValueTask ConnectAsync(IPAddress[] addresses, int port, CancellationToken cancellationToken) { + ThrowIfDisposed(); + if (addresses == null) { throw new ArgumentNullException(nameof(addresses)); } + if (addresses.Length == 0) { throw new ArgumentException(SR.net_invalidAddressList, nameof(addresses)); } - return DoConnectAsync(addresses, port, cancellationToken); - } + if (!TcpValidationHelpers.ValidatePortNumber(port)) + { + throw new ArgumentOutOfRangeException(nameof(port)); + } - private async ValueTask DoConnectAsync(IPAddress[] addresses, int port, CancellationToken cancellationToken) - { - Exception? lastException = null; - foreach (IPAddress address in addresses) + if (_isListening) { - try - { - await ConnectAsync(address, port, cancellationToken).ConfigureAwait(false); - return; - } - catch (Exception ex) when (ex is not OperationCanceledException) + throw new InvalidOperationException(SR.net_sockets_mustnotlisten); + } + + if (_isConnected) + { + throw new SocketException((int)SocketError.IsConnected); + } + + ValidateForMultiConnect(isMultiEndpoint: false); + + return Core(addresses, port, cancellationToken); + + async ValueTask Core(IPAddress[] addresses, int port, CancellationToken cancellationToken) + { + Exception? lastException = null; + IPEndPoint? endPoint = null; + foreach (IPAddress address in addresses) { - lastException = ex; + try + { + if (endPoint is null) + { + endPoint = new IPEndPoint(address, port); + } + else + { + endPoint.Address = address; + Debug.Assert(endPoint.Port == port); + } + + await ConnectAsync(endPoint, cancellationToken).ConfigureAwait(false); + return; + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + lastException = ex; + } } - } - Debug.Assert(lastException != null); - ExceptionDispatchInfo.Throw(lastException); + Debug.Assert(lastException != null); + ExceptionDispatchInfo.Throw(lastException); + } } internal Task ConnectAsync(string host, int port) => ConnectAsync(host, port, default).AsTask(); @@ -735,7 +766,7 @@ public ValueTask ConnectAsync(Socket socket) try { - if (socket.ConnectAsync(this)) + if (socket.ConnectAsync(this, userSocket: true, saeaCancelable: false)) { return new ValueTask(this, _token); } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs index 4432190294bc52..7fa5853e579024 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs @@ -13,10 +13,25 @@ namespace System.Net.Sockets { public partial class Socket { + private static CachedSerializedEndPoint? s_cachedAnyEndPoint; + private static CachedSerializedEndPoint? s_cachedAnyV6EndPoint; + private static CachedSerializedEndPoint? s_cachedMappedAnyV6EndPoint; private DynamicWinsockMethods? _dynamicWinsockMethods; internal void ReplaceHandleIfNecessaryAfterFailedConnect() { /* nop on Windows */ } + private sealed class CachedSerializedEndPoint + { + public readonly IPEndPoint IPEndPoint; + public readonly Internals.SocketAddress SocketAddress; + + public CachedSerializedEndPoint(IPAddress address) + { + IPEndPoint = new IPEndPoint(address, 0); + SocketAddress = IPEndPointExtensions.Serialize(IPEndPoint); + } + } + [SupportedOSPlatform("windows")] public Socket(SocketInformation socketInformation) { @@ -223,25 +238,26 @@ partial void WildcardBindForConnectIfNecessary(AddressFamily addressFamily) // The socket must be bound before using ConnectEx. - IPAddress address; + CachedSerializedEndPoint csep; switch (addressFamily) { case AddressFamily.InterNetwork: - address = IsDualMode ? s_IPAddressAnyMapToIPv6 : IPAddress.Any; + csep = IsDualMode ? + s_cachedMappedAnyV6EndPoint ??= new CachedSerializedEndPoint(s_IPAddressAnyMapToIPv6) : + s_cachedAnyEndPoint ??= new CachedSerializedEndPoint(IPAddress.Any); break; case AddressFamily.InterNetworkV6: - address = IPAddress.IPv6Any; + csep = s_cachedAnyV6EndPoint ??= new CachedSerializedEndPoint(IPAddress.IPv6Any); break; default: return; } - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, address); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, csep.IPEndPoint); - var endPoint = new IPEndPoint(address, 0); - DoBind(endPoint, IPEndPointExtensions.Serialize(endPoint)); + DoBind(csep.IPEndPoint, csep.SocketAddress); } internal unsafe bool ConnectEx(SafeSocketHandle socketHandle, 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 dde46961d16e88..1496abb598a8b2 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 @@ -5,15 +5,13 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Tracing; -using System.Globalization; using System.IO; using System.Net.Internals; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; -using System.Runtime.InteropServices; -using System.Threading; using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; namespace System.Net.Sockets { @@ -64,7 +62,6 @@ public partial class Socket : IDisposable private class CacheSet { - internal CallbackClosure? ConnectClosureCache; internal CallbackClosure? AcceptClosureCache; internal CallbackClosure? SendClosureCache; internal CallbackClosure? ReceiveClosureCache; @@ -2056,220 +2053,17 @@ public static void Select(IList? checkRead, IList? checkWrite, IList? checkError } } - // Routine Description: - // - // BeginConnect - Does an async connect. - // - // Arguments: - // - // remoteEP - status line that we wish to parse - // Callback - Async Callback Delegate that is called upon Async Completion - // State - State used to track callback, set by caller, not required - // - // Return Value: - // - // IAsyncResult - Async result used to retrieve result - public IAsyncResult BeginConnect(EndPoint remoteEP, AsyncCallback? callback, object? state) - { - // Validate input parameters. - ThrowIfDisposed(); - - if (remoteEP == null) - { - throw new ArgumentNullException(nameof(remoteEP)); - } - - if (_isListening) - { - throw new InvalidOperationException(SR.net_sockets_mustnotlisten); - } - - if (_isConnected) - { - throw new SocketException((int)SocketError.IsConnected); - } - - - DnsEndPoint? dnsEP = remoteEP as DnsEndPoint; - if (dnsEP != null) - { - ValidateForMultiConnect(isMultiEndpoint: true); // needs to come before CanTryAddressFamily call - - if (dnsEP.AddressFamily != AddressFamily.Unspecified && !CanTryAddressFamily(dnsEP.AddressFamily)) - { - throw new NotSupportedException(SR.net_invalidversion); - } - - return BeginConnect(dnsEP.Host, dnsEP.Port, callback, state); - } - - ValidateForMultiConnect(isMultiEndpoint: false); - return UnsafeBeginConnect(remoteEP, callback, state, flowContext: true); - } - - private bool CanUseConnectEx(EndPoint remoteEP) - { - Debug.Assert(remoteEP.GetType() != typeof(DnsEndPoint)); - - // ConnectEx supports connection-oriented sockets. - // The socket must be bound before calling ConnectEx. - // In case of IPEndPoint, the Socket will be bound using WildcardBindForConnectIfNecessary. - // Unix sockets are not supported by ConnectEx. - - return (_socketType == SocketType.Stream) && - (_rightEndPoint != null || remoteEP.GetType() == typeof(IPEndPoint)) && - (remoteEP.AddressFamily != AddressFamily.Unix); - } - - internal IAsyncResult UnsafeBeginConnect(EndPoint remoteEP, AsyncCallback? callback, object? state, bool flowContext = false) - { - if (CanUseConnectEx(remoteEP)) - { - return BeginConnectEx(remoteEP, flowContext, callback, state); - } - - EndPoint endPointSnapshot = remoteEP; - var asyncResult = new ConnectAsyncResult(this, endPointSnapshot, state, callback); - - // For connectionless protocols, Connect is not an I/O call. - Connect(remoteEP); - asyncResult.FinishPostingAsyncOp(); - - // Synchronously complete the I/O and call the user's callback. - asyncResult.InvokeCallback(); - return asyncResult; - } - - public IAsyncResult BeginConnect(string host, int port, AsyncCallback? requestCallback, object? state) - { - ThrowIfDisposed(); - - if (host == null) - { - throw new ArgumentNullException(nameof(host)); - } - if (!TcpValidationHelpers.ValidatePortNumber(port)) - { - throw new ArgumentOutOfRangeException(nameof(port)); - } - if (_addressFamily != AddressFamily.InterNetwork && _addressFamily != AddressFamily.InterNetworkV6) - { - throw new NotSupportedException(SR.net_invalidversion); - } - - if (_isListening) - { - throw new InvalidOperationException(SR.net_sockets_mustnotlisten); - } - - if (_isConnected) - { - throw new SocketException((int)SocketError.IsConnected); - } - - IPAddress? parsedAddress; - if (IPAddress.TryParse(host, out parsedAddress)) - { - return BeginConnect(parsedAddress, port, requestCallback, state); - } - - ValidateForMultiConnect(isMultiEndpoint: true); - - // Here, want to flow the context. No need to lock. - MultipleAddressConnectAsyncResult result = new MultipleAddressConnectAsyncResult(null, port, this, state, requestCallback); - result.StartPostingAsyncOp(false); - - IAsyncResult dnsResult = Dns.BeginGetHostAddresses(host, new AsyncCallback(DnsCallback), result); - if (dnsResult.CompletedSynchronously) - { - if (DoDnsCallback(dnsResult, result)) - { - result.InvokeCallback(); - } - } - - // Done posting. - result.FinishPostingAsyncOp(ref Caches.ConnectClosureCache); - - return result; - } - - public IAsyncResult BeginConnect(IPAddress address, int port, AsyncCallback? requestCallback, object? state) - { - ThrowIfDisposed(); - - if (address == null) - { - throw new ArgumentNullException(nameof(address)); - } - if (!TcpValidationHelpers.ValidatePortNumber(port)) - { - throw new ArgumentOutOfRangeException(nameof(port)); - } - - if (_isConnected) - { - throw new SocketException((int)SocketError.IsConnected); - } - - ValidateForMultiConnect(isMultiEndpoint: false); // needs to be called before CanTryAddressFamily - - if (!CanTryAddressFamily(address.AddressFamily)) - { - throw new NotSupportedException(SR.net_invalidversion); - } - - return BeginConnect(new IPEndPoint(address, port), requestCallback, state); - } - - public IAsyncResult BeginConnect(IPAddress[] addresses, int port, AsyncCallback? requestCallback, object? state) - { - ThrowIfDisposed(); - - if (addresses == null) - { - throw new ArgumentNullException(nameof(addresses)); - } - if (addresses.Length == 0) - { - throw new ArgumentException(SR.net_invalidAddressList, nameof(addresses)); - } - if (!TcpValidationHelpers.ValidatePortNumber(port)) - { - throw new ArgumentOutOfRangeException(nameof(port)); - } - if (_addressFamily != AddressFamily.InterNetwork && _addressFamily != AddressFamily.InterNetworkV6) - { - throw new NotSupportedException(SR.net_invalidversion); - } - - if (_isListening) - { - throw new InvalidOperationException(SR.net_sockets_mustnotlisten); - } + public IAsyncResult BeginConnect(EndPoint remoteEP, AsyncCallback? callback, object? state) => + TaskToApm.Begin(ConnectAsync(remoteEP), callback, state); - if (_isConnected) - { - throw new SocketException((int)SocketError.IsConnected); - } - - ValidateForMultiConnect(isMultiEndpoint: true); + public IAsyncResult BeginConnect(string host, int port, AsyncCallback? requestCallback, object? state) => + TaskToApm.Begin(ConnectAsync(host, port), requestCallback, state); - // Set up the result to capture the context. No need for a lock. - MultipleAddressConnectAsyncResult result = new MultipleAddressConnectAsyncResult(addresses, port, this, state, requestCallback); - result.StartPostingAsyncOp(false); - - if (DoMultipleAddressConnectCallback(PostOneBeginConnect(result), result)) - { - // If the call completes synchronously, invoke the callback from here. - result.InvokeCallback(); - } + public IAsyncResult BeginConnect(IPAddress address, int port, AsyncCallback? requestCallback, object? state) => + TaskToApm.Begin(ConnectAsync(address, port), requestCallback, state); - // Finished posting async op. Possibly will call callback. - result.FinishPostingAsyncOp(ref Caches.ConnectClosureCache); - - return result; - } + public IAsyncResult BeginConnect(IPAddress[] addresses, int port, AsyncCallback? requestCallback, object? state) => + TaskToApm.Begin(ConnectAsync(addresses, port), requestCallback, state); public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state) { @@ -2330,93 +2124,10 @@ public void Disconnect(bool reuseSocket) _localEndPoint = null; } - // Routine Description: - // - // EndConnect - Called after receiving callback from BeginConnect, - // in order to retrieve the result of async call - // - // Arguments: - // - // AsyncResult - the AsyncResult Returned from BeginConnect call - // - // Return Value: - // - // int - Return code from async Connect, 0 for success, SocketError.NotConnected otherwise public void EndConnect(IAsyncResult asyncResult) { - // There are three AsyncResult types we support in EndConnect: - // - ConnectAsyncResult - a fully synchronous operation that already completed, wrapped in an AsyncResult - // - MultipleAddressConnectAsyncResult - a parent operation for other Connects (connecting to DnsEndPoint) - // - ConnectOverlappedAsyncResult - a connect to an IPEndPoint - // For Telemetry, we already logged everything for ConnectAsyncResult in DoConnect, - // and we want to avoid logging duplicated events for MultipleAddressConnect. - // Therefore, we always check that asyncResult is ConnectOverlapped before logging. - - if (Disposed) - { - if (SocketsTelemetry.Log.IsEnabled() && asyncResult is ConnectOverlappedAsyncResult) - { - SocketsTelemetry.Log.AfterConnect(SocketError.NotSocket); - } - - ThrowObjectDisposedException(); - } - - // Validate input parameters. - if (asyncResult == null) - { - throw new ArgumentNullException(nameof(asyncResult)); - } - - ContextAwareResult? castedAsyncResult = - asyncResult as ConnectOverlappedAsyncResult ?? - asyncResult as MultipleAddressConnectAsyncResult ?? - (ContextAwareResult?)(asyncResult as ConnectAsyncResult); - - if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this) - { - throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult)); - } - if (castedAsyncResult.EndCalled) - { - throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, "EndConnect")); - } - - castedAsyncResult.InternalWaitForCompletion(); - castedAsyncResult.EndCalled = true; - - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"asyncResult:{asyncResult}"); - - Exception? ex = castedAsyncResult.Result as Exception; - - if (ex != null || (SocketError)castedAsyncResult.ErrorCode != SocketError.Success) - { - SocketError errorCode = (SocketError)castedAsyncResult.ErrorCode; - - if (ex == null) - { - UpdateConnectSocketErrorForDisposed(ref errorCode); - // Update the internal state of this socket according to the error before throwing. - SocketException se = SocketExceptionFactory.CreateSocketException((int)errorCode, castedAsyncResult.RemoteEndPoint); - UpdateStatusAfterSocketError(se); - ex = se; - } - - if (SocketsTelemetry.Log.IsEnabled() && castedAsyncResult is ConnectOverlappedAsyncResult) - { - SocketsTelemetry.Log.AfterConnect(errorCode, ex.Message); - } - - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, ex); - ExceptionDispatchInfo.Throw(ex); - } - - if (SocketsTelemetry.Log.IsEnabled() && castedAsyncResult is ConnectOverlappedAsyncResult) - { - SocketsTelemetry.Log.AfterConnect(SocketError.Success); - } - - if (NetEventSource.Log.IsEnabled()) NetEventSource.Connected(this, LocalEndPoint, RemoteEndPoint); + ThrowIfDisposed(); + TaskToApm.End(asyncResult); } public void EndDisconnect(IAsyncResult asyncResult) @@ -3712,9 +3423,9 @@ public bool AcceptAsync(SocketAsyncEventArgs e) } public bool ConnectAsync(SocketAsyncEventArgs e) => - ConnectAsync(e, userSocket: true); + ConnectAsync(e, userSocket: true, saeaCancelable: true); - private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket) + internal bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket, bool saeaCancelable) { bool pending; @@ -3757,14 +3468,11 @@ private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket) throw new NotSupportedException(SR.net_invalidversion); } - MultipleConnectAsync multipleConnectAsync = new SingleSocketMultipleConnectAsync(this, userSocket: true); - e.StartOperationCommon(this, SocketAsyncOperation.Connect); - e.StartOperationConnect(multipleConnectAsync, userSocket: true); - + e.StartOperationConnect(saeaCancelable, userSocket); try { - pending = multipleConnectAsync.StartConnectAsync(e, dnsEP); + pending = e.DnsConnectAsync(dnsEP, default, default); } catch { @@ -3788,10 +3496,7 @@ private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket) // Save the old RightEndPoint and prep new RightEndPoint. EndPoint? oldEndPoint = _rightEndPoint; - if (_rightEndPoint == null) - { - _rightEndPoint = endPointSnapshot; - } + _rightEndPoint ??= endPointSnapshot; if (SocketsTelemetry.Log.IsEnabled()) { @@ -3800,21 +3505,17 @@ private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket) // Prepare for the native call. e.StartOperationCommon(this, SocketAsyncOperation.Connect); - e.StartOperationConnect(multipleConnect: null, userSocket); + e.StartOperationConnect(saeaMultiConnectCancelable: false, userSocket); // Make the native call. - SocketError socketError; try { - if (CanUseConnectEx(endPointSnapshot)) - { - socketError = e.DoOperationConnectEx(this, _handle); - } - else - { - // For connectionless protocols, Connect is not an I/O call. - socketError = e.DoOperationConnect(this, _handle); - } + // ConnectEx supports connection-oriented sockets but not UDS. The socket must be bound before calling ConnectEx. + bool canUseConnectEx = _socketType == SocketType.Stream && endPointSnapshot.AddressFamily != AddressFamily.Unix; + SocketError socketError = canUseConnectEx ? + e.DoOperationConnectEx(this, _handle) : + e.DoOperationConnect(this, _handle); // For connectionless protocols, Connect is not an I/O call. + pending = socketError == SocketError.IOPending; } catch (Exception ex) { @@ -3830,8 +3531,6 @@ private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket) e.Complete(); throw; } - - pending = (socketError == SocketError.IOPending); } return pending; @@ -3859,27 +3558,12 @@ public static bool ConnectAsync(SocketType socketType, ProtocolType protocolType if (dnsEP != null) { - Socket? attemptSocket = null; - MultipleConnectAsync? multipleConnectAsync = null; - if (dnsEP.AddressFamily == AddressFamily.Unspecified) - { - // This is the only *Connect* API that fully supports multiple endpoint attempts, as it's responsible - // for creating each Socket instance and can create one per attempt. - multipleConnectAsync = new DualSocketMultipleConnectAsync(socketType, protocolType); -#pragma warning restore - } - else - { - attemptSocket = new Socket(dnsEP.AddressFamily, socketType, protocolType); - multipleConnectAsync = new SingleSocketMultipleConnectAsync(attemptSocket, userSocket: false); - } - + Socket? attemptSocket = dnsEP.AddressFamily != AddressFamily.Unspecified ? new Socket(dnsEP.AddressFamily, socketType, protocolType) : null; e.StartOperationCommon(attemptSocket, SocketAsyncOperation.Connect); - e.StartOperationConnect(multipleConnectAsync, userSocket: false); - + e.StartOperationConnect(saeaMultiConnectCancelable: true, userSocket: false); try { - pending = multipleConnectAsync.StartConnectAsync(e, dnsEP); + pending = e.DnsConnectAsync(dnsEP, socketType, protocolType); } catch { @@ -3890,7 +3574,7 @@ public static bool ConnectAsync(SocketType socketType, ProtocolType protocolType else { Socket attemptSocket = new Socket(endPointSnapshot.AddressFamily, socketType, protocolType); - pending = attemptSocket.ConnectAsync(e, userSocket: false); + pending = attemptSocket.ConnectAsync(e, userSocket: false, saeaCancelable: true); } return pending; @@ -4632,277 +4316,6 @@ internal void InternalSetBlocking(bool desired) InternalSetBlocking(desired, out current); } - // Implements ConnectEx - this provides completion port IO and support for disconnect and reconnects. - // Since this is private, the unsafe mode is specified with a flag instead of an overload. - private IAsyncResult BeginConnectEx(EndPoint remoteEP, bool flowContext, AsyncCallback? callback, object? state) - { - EndPoint endPointSnapshot = remoteEP; - Internals.SocketAddress socketAddress = Serialize(ref endPointSnapshot); - - if (SocketsTelemetry.Log.IsEnabled()) - { - SocketsTelemetry.Log.ConnectStart(socketAddress); - - // Ignore flowContext when using Telemetry to avoid losing Activity tracking - flowContext = true; - } - - WildcardBindForConnectIfNecessary(endPointSnapshot.AddressFamily); - - // Allocate the async result and the event we'll pass to the thread pool. - ConnectOverlappedAsyncResult asyncResult = new ConnectOverlappedAsyncResult(this, endPointSnapshot, state, callback); - - // If context flowing is enabled, set it up here. No need to lock since the context isn't used until the callback. - if (flowContext) - { - asyncResult.StartPostingAsyncOp(false); - } - - EndPoint? oldEndPoint = _rightEndPoint; - if (_rightEndPoint == null) - { - _rightEndPoint = endPointSnapshot; - } - - SocketError errorCode; - try - { - errorCode = SocketPal.ConnectAsync(this, _handle, socketAddress.Buffer, socketAddress.Size, asyncResult); - } - catch (Exception ex) - { - if (SocketsTelemetry.Log.IsEnabled()) - { - SocketsTelemetry.Log.AfterConnect(SocketError.NotSocket, ex.Message); - } - - // _rightEndPoint will always equal oldEndPoint. - _rightEndPoint = oldEndPoint; - _localEndPoint = null; - throw; - } - - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"ConnectAsync returns:{errorCode}"); - - if (errorCode == SocketError.Success) - { - // Synchronous success. Indicate that we're connected. - SetToConnected(); - } - - if (!CheckErrorAndUpdateStatus(errorCode)) - { - UpdateConnectSocketErrorForDisposed(ref errorCode); - // Update the internal state of this socket according to the error before throwing. - _rightEndPoint = oldEndPoint; - _localEndPoint = null; - - if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.AfterConnect(errorCode); - - throw new SocketException((int)errorCode); - } - - // We didn't throw, so indicate that we're returning this result to the user. This may call the callback. - // This is a nop if the context isn't being flowed. - asyncResult.FinishPostingAsyncOp(ref Caches.ConnectClosureCache); - - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{endPointSnapshot} returning AsyncResult:{asyncResult}"); - return asyncResult; - } - - private static void DnsCallback(IAsyncResult result) - { - if (result.CompletedSynchronously) - { - return; - } - - bool invokeCallback = false; - - MultipleAddressConnectAsyncResult context = (MultipleAddressConnectAsyncResult)result.AsyncState!; - try - { - invokeCallback = DoDnsCallback(result, context); - } - catch (Exception exception) - { - context.InvokeCallback(exception); - } - - // Invoke the callback outside of the try block so we don't catch user exceptions. - if (invokeCallback) - { - context.InvokeCallback(); - } - } - - private static bool DoDnsCallback(IAsyncResult result, MultipleAddressConnectAsyncResult context) - { - IPAddress[] addresses = Dns.EndGetHostAddresses(result); - context._addresses = addresses; - return DoMultipleAddressConnectCallback(PostOneBeginConnect(context), context); - } - - private sealed class ConnectAsyncResult : ContextAwareResult - { - private readonly EndPoint _endPoint; - - internal ConnectAsyncResult(object myObject, EndPoint endPoint, object? myState, AsyncCallback? myCallBack) : - base(myObject, myState, myCallBack) - { - _endPoint = endPoint; - } - - internal override EndPoint RemoteEndPoint - { - get { return _endPoint; } - } - } - - private sealed class MultipleAddressConnectAsyncResult : ContextAwareResult - { - internal MultipleAddressConnectAsyncResult(IPAddress[]? addresses, int port, Socket socket, object? myState, AsyncCallback? myCallBack) : - base(socket, myState, myCallBack) - { - _addresses = addresses; - _port = port; - _socket = socket; - } - - internal Socket _socket; // Keep this member just to avoid all the casting. - internal IPAddress[]? _addresses; - internal int _index; - internal int _port; - internal Exception? _lastException; - - internal override EndPoint? RemoteEndPoint - { - get - { - if (_addresses != null && _index > 0 && _index < _addresses.Length) - { - return new IPEndPoint(_addresses[_index], _port); - } - else - { - return null; - } - } - } - } - - private static AsyncCallback? s_multipleAddressConnectCallback; - private static AsyncCallback CachedMultipleAddressConnectCallback - { - get - { - if (s_multipleAddressConnectCallback == null) - { - s_multipleAddressConnectCallback = new AsyncCallback(MultipleAddressConnectCallback); - } - return s_multipleAddressConnectCallback; - } - } - - private static object? PostOneBeginConnect(MultipleAddressConnectAsyncResult context) - { - IPAddress currentAddressSnapshot = context._addresses![context._index]; - - context._socket.ReplaceHandleIfNecessaryAfterFailedConnect(); - - if (!context._socket.CanTryAddressFamily(currentAddressSnapshot.AddressFamily)) - { - return context._lastException != null ? context._lastException : new ArgumentException(SR.net_invalidAddressList, nameof(context)); - } - - try - { - EndPoint endPoint = new IPEndPoint(currentAddressSnapshot, context._port); - - context._socket.Serialize(ref endPoint); - - IAsyncResult connectResult = context._socket.UnsafeBeginConnect(endPoint, CachedMultipleAddressConnectCallback, context); - if (connectResult.CompletedSynchronously) - { - return connectResult; - } - } - catch (Exception exception) when (!(exception is OutOfMemoryException)) - { - return exception; - } - - return null; - } - - private static void MultipleAddressConnectCallback(IAsyncResult result) - { - if (result.CompletedSynchronously) - { - return; - } - - bool invokeCallback = false; - - MultipleAddressConnectAsyncResult context = (MultipleAddressConnectAsyncResult)result.AsyncState!; - try - { - invokeCallback = DoMultipleAddressConnectCallback(result, context); - } - catch (Exception exception) - { - context.InvokeCallback(exception); - } - - // Invoke the callback outside of the try block so we don't catch user Exceptions. - if (invokeCallback) - { - context.InvokeCallback(); - } - } - - // This is like a regular async callback worker, except the result can be an exception. This is a useful pattern when - // processing should continue whether or not an async step failed. - private static bool DoMultipleAddressConnectCallback(object? result, MultipleAddressConnectAsyncResult context) - { - while (result != null) - { - Exception? ex = result as Exception; - if (ex == null) - { - try - { - context._socket.EndConnect((IAsyncResult)result); - } - catch (Exception exception) - { - ex = exception; - } - } - - if (ex == null) - { - // Don't invoke the callback from here, because we're probably inside - // a catch-all block that would eat exceptions from the callback. - // Instead tell our caller to invoke the callback outside of its catchall. - return true; - } - else - { - if (++context._index >= context._addresses!.Length) - { - ExceptionDispatchInfo.Throw(ex); - } - - context._lastException = ex; - result = PostOneBeginConnect(context); - } - } - - // Don't invoke the callback at all, because we've posted another async connection attempt. - return false; - } - // CreateAcceptSocket - pulls unmanaged results and assembles them into a new Socket object. internal Socket CreateAcceptSocket(SafeSocketHandle fd, EndPoint remoteEP) { diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs index 7f86d700dc1894..fa5b1b2b597c22 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs @@ -159,7 +159,7 @@ private unsafe SocketError ProcessIOCPResult(bool success, int bytesTransferred, FreeNativeOverlapped(overlapped); FinishOperationSyncSuccess(bytesTransferred, SocketFlags.None); - if (SocketsTelemetry.Log.IsEnabled()) AfterConnectAcceptTelemetry(); + if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry(); return SocketError.Success; } @@ -177,7 +177,7 @@ private unsafe SocketError ProcessIOCPResult(bool success, int bytesTransferred, FreeNativeOverlapped(overlapped); FinishOperationSyncFailure(socketError, bytesTransferred, SocketFlags.None); - if (SocketsTelemetry.Log.IsEnabled()) AfterConnectAcceptTelemetry(); + if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry(); return socketError; } @@ -212,7 +212,7 @@ private unsafe SocketError ProcessIOCPResultWithSingleBufferHandle(SocketError s FreeNativeOverlapped(overlapped); FinishOperationSyncSuccess(bytesTransferred, SocketFlags.None); - if (SocketsTelemetry.Log.IsEnabled()) AfterConnectAcceptTelemetry(); + if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry(); return SocketError.Success; } @@ -231,7 +231,7 @@ private unsafe SocketError ProcessIOCPResultWithSingleBufferHandle(SocketError s FreeNativeOverlapped(overlapped); FinishOperationSyncFailure(socketError, bytesTransferred, SocketFlags.None); - if (SocketsTelemetry.Log.IsEnabled()) AfterConnectAcceptTelemetry(); + if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry(); return socketError; } 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 e85bb70d70148a..89a9bfffb1bc98 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 @@ -3,9 +3,10 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.Tracing; using System.Runtime.InteropServices; using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Sources; namespace System.Net.Sockets { @@ -73,6 +74,7 @@ public partial class SocketAsyncEventArgs : EventArgs, IDisposable private Socket? _currentSocket; private bool _userSocket; // if false when performing Connect, _currentSocket should be disposed private bool _disposeCalled; + private protected bool _disableTelemetry; // Controls thread safety via Interlocked. private const int Configuring = -1; @@ -81,7 +83,7 @@ public partial class SocketAsyncEventArgs : EventArgs, IDisposable private const int Disposed = 2; private int _operating; - private MultipleConnectAsync? _multipleConnect; + private CancellationTokenSource? _multipleConnectCancellation; public SocketAsyncEventArgs() : this(unsafeSuppressExecutionContextFlow: false) { @@ -200,10 +202,7 @@ public int BytesTransferred private void OnCompletedInternal() { - if (SocketsTelemetry.Log.IsEnabled()) - { - AfterConnectAcceptTelemetry(); - } + if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry(); OnCompleted(this); } @@ -222,10 +221,7 @@ private void AfterConnectAcceptTelemetry() break; case SocketAsyncOperation.Connect: - if (_multipleConnect is null) - { - SocketsTelemetry.Log.AfterConnect(SocketError); - } + SocketsTelemetry.Log.AfterConnect(SocketError); break; } } @@ -578,9 +574,9 @@ internal void StartOperationAccept() } } - internal void StartOperationConnect(MultipleConnectAsync? multipleConnect, bool userSocket) + internal void StartOperationConnect(bool saeaMultiConnectCancelable, bool userSocket) { - _multipleConnect = multipleConnect; + _multipleConnectCancellation = saeaMultiConnectCancelable ? new CancellationTokenSource() : null; _connectSocket = null; _userSocket = userSocket; } @@ -589,11 +585,11 @@ internal void CancelConnectAsync() { if (_operating == InProgress && _completedOperation == SocketAsyncOperation.Connect) { - MultipleConnectAsync? multipleConnect = _multipleConnect; - if (multipleConnect != null) + CancellationTokenSource? multipleConnectCancellation = _multipleConnectCancellation; + if (multipleConnectCancellation != null) { // If a multiple connect is in progress, abort it. - multipleConnect.Cancel(); + multipleConnectCancellation.Cancel(); } else { @@ -635,15 +631,6 @@ internal void FinishOperationSyncFailure(SocketError socketError, int bytesTrans Complete(); } - internal void FinishConnectByNameSyncFailure(Exception exception, int bytesTransferred, SocketFlags flags) - { - SetResults(exception, bytesTransferred, flags); - - _currentSocket?.UpdateStatusAfterSocketError(_socketError); - - Complete(); - } - internal void FinishOperationAsyncFailure(SocketError socketError, int bytesTransferred, SocketFlags flags) { ExecutionContext? context = _context; // store context before it's cleared as part of finishing the operation @@ -660,48 +647,212 @@ internal void FinishOperationAsyncFailure(SocketError socketError, int bytesTran } } - internal void FinishConnectByNameAsyncFailure(Exception exception, int bytesTransferred, SocketFlags flags) + /// Performs an asynchronous connect involving a DNS lookup. + /// The DNS end point to which to connect. + /// The SocketType to use to construct new sockets, if necessary. + /// The ProtocolType to use to construct new sockets, if necessary. + /// true if the operation is pending; otherwise, false if it's already completed. + internal bool DnsConnectAsync(DnsEndPoint endPoint, SocketType socketType, ProtocolType protocolType) { - ExecutionContext? context = _context; // store context before it's cleared as part of finishing the operation + Debug.Assert(endPoint.AddressFamily == AddressFamily.Unspecified || + endPoint.AddressFamily == AddressFamily.InterNetwork || + endPoint.AddressFamily == AddressFamily.InterNetworkV6); + + CancellationToken cancellationToken = _multipleConnectCancellation?.Token ?? default; + + // In .NET 5 and earlier, the APM implementation allowed for synchronous exceptions from this to propagate + // synchronously. This call is made here rather than in the Core async method below to preserve that behavior. + Task addressesTask = Dns.GetHostAddressesAsync(endPoint.Host, endPoint.AddressFamily, cancellationToken); + + // Initialize the internal event args instance. It needs to be initialized with `this` instance's buffer + // so that it may be used as part of receives during a connect. + // TODO https://github.com/dotnet/runtime/issues/30252#issuecomment-511231055: Try to avoid this extra level of SAEA. + var internalArgs = new MultiConnectSocketAsyncEventArgs(); + internalArgs.CopyBufferFrom(this); + + // Delegate to the actual implementation. The returned Task is unused and ignored, as the whole body is surrounded + // by a try/catch. Thus we ignore the result. We avoid an "async void" method so as to skip the implicit SynchronizationContext + // interactions async void methods entail. + _ = Core(internalArgs, addressesTask, endPoint.Port, socketType, protocolType, cancellationToken); + + // Determine whether the async operation already completed and stored the results into `this`. + // If we reached this point and the operation hasn't yet stored the results, then it's considered + // pending. If by the time we get here it has stored the results, it's considered completed. + // The callback won't invoke the Completed event if it gets there first. + return internalArgs.ReachedCoordinationPointFirst(); + + async Task Core(MultiConnectSocketAsyncEventArgs internalArgs, Task addressesTask, int port, SocketType socketType, ProtocolType protocolType, CancellationToken cancellationToken) + { + Socket? tempSocketIPv4 = null, tempSocketIPv6 = null; + Exception? caughtException = null; + try + { + // Try each address in turn. We store the last error received, such that if we fail to connect to all addresses, + // we can use the last error to represent the entire operation. + SocketError lastError = SocketError.NoData; + foreach (IPAddress address in await addressesTask.ConfigureAwait(false)) + { + Socket? attemptSocket = null; + if (_currentSocket != null) + { + // If this SocketAsyncEventArgs was configured with a socket, then use it. + // If that instance doesn't support this address, move on to the next. + if (!_currentSocket.CanTryAddressFamily(address.AddressFamily)) + { + continue; + } - FinishConnectByNameSyncFailure(exception, bytesTransferred, flags); + attemptSocket = _currentSocket; + } + else + { + // If this SocketAsyncEventArgs doesn't have a socket, then we need to create a temporary one, which we do + // based on this address' address family (and then reuse for subsequent addresses for the same family). + if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + attemptSocket = tempSocketIPv6 ??= (Socket.OSSupportsIPv6 ? new Socket(AddressFamily.InterNetworkV6, socketType, protocolType) : null); + } + else if (address.AddressFamily == AddressFamily.InterNetwork) + { + attemptSocket = tempSocketIPv4 ??= (Socket.OSSupportsIPv4 ? new Socket(AddressFamily.InterNetwork, socketType, protocolType) : null); + } - if (context == null) - { - OnCompletedInternal(); - } - else - { - ExecutionContext.Run(context, s_executionCallback, this); - } - } + // If we were unable to get a socket to use for this address, move on to the next address. + if (attemptSocket is null) + { + continue; + } + } - internal void FinishWrapperConnectSyncSuccess(Socket? connectSocket, int bytesTransferred, SocketFlags flags) - { - SetResults(SocketError.Success, bytesTransferred, flags); - _currentSocket = connectSocket; - _connectSocket = connectSocket; + // Reset the socket if necessary to support another connect. This is necessary on Unix in particular where + // the same socket handle can't be used for another connect, so we swap in a new handle under the covers if + // possible. We do this not just for the 2nd+ address but also for the first in case the Socket was already + // used for a connection attempt outside of this call. + attemptSocket.ReplaceHandleIfNecessaryAfterFailedConnect(); - if (SocketsTelemetry.Log.IsEnabled()) LogBytesTransferEvents(connectSocket?.SocketType, SocketAsyncOperation.Connect, bytesTransferred); + // Reconfigure the internal event args for the new address. + if (internalArgs.RemoteEndPoint is IPEndPoint existing) + { + existing.Address = address; + Debug.Assert(existing.Port == port); + } + else + { + internalArgs.RemoteEndPoint = new IPEndPoint(address, port); + } - // Complete the operation and raise the event. - Complete(); - } + // Issue the connect. If it pends, wait for it to complete. + if (attemptSocket.ConnectAsync(internalArgs)) + { + using (cancellationToken.UnsafeRegister(s => Socket.CancelConnectAsync((SocketAsyncEventArgs)s!), internalArgs)) + { + await new ValueTask(internalArgs, internalArgs.Version).ConfigureAwait(false); + } + } - internal void FinishWrapperConnectAsyncSuccess(Socket? connectSocket, int bytesTransferred, SocketFlags flags) - { - ExecutionContext? context = _context; // store context before it's cleared as part of completing the operation + // If it completed successfully, we're done; cleanup will be handled by the finally. + if (internalArgs.SocketError == SocketError.Success) + { + return; + } + + // If the operation was canceled, simulate the appropriate SocketError. + if (cancellationToken.IsCancellationRequested) + { + throw new SocketException((int)SocketError.OperationAborted); + } - FinishWrapperConnectSyncSuccess(connectSocket, bytesTransferred, flags); + lastError = internalArgs.SocketError; + internalArgs.Reset(); + } - if (context == null) - { - OnCompletedInternal(); + caughtException = new SocketException((int)lastError); + } + catch (ObjectDisposedException) + { + // This can happen if the user closes the socket and is equivalent to a call to CancelConnectAsync. + caughtException = new SocketException((int)SocketError.OperationAborted); + } + catch (Exception exc) + { + caughtException = exc; + } + finally + { + // Close the sockets as needed. + if (tempSocketIPv4 != null && !tempSocketIPv4.Connected) + { + tempSocketIPv4.Dispose(); + } + if (tempSocketIPv6 != null && !tempSocketIPv6.Connected) + { + tempSocketIPv6.Dispose(); + } + if (_currentSocket != null) + { + // If the caller-provided socket was a temporary and isn't connected now, or if the failed with an abortive exception, + // dispose of the socket. + if ((!_userSocket && !_currentSocket.Connected) || + caughtException is OperationCanceledException || + (caughtException is SocketException se && se.SocketErrorCode == SocketError.OperationAborted)) + { + _currentSocket.Dispose(); + } + } + + // Store the results. + if (caughtException != null) + { + SetResults(caughtException, 0, SocketFlags.None); + _currentSocket?.UpdateStatusAfterSocketError(_socketError); + } + else + { + SetResults(SocketError.Success, internalArgs.BytesTransferred, internalArgs.SocketFlags); + _connectSocket = _currentSocket = internalArgs.ConnectSocket!; + } + + // Complete the operation. + if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) LogBytesTransferEvents(_connectSocket?.SocketType, SocketAsyncOperation.Connect, internalArgs.BytesTransferred); + Complete(); + + // If the caller is treating this operation as pending, own the completion. + internalArgs.Dispose(); + if (!internalArgs.ReachedCoordinationPointFirst()) + { + // Regardless of _flowExecutionContext, context will have been flown through this async method, as that's part + // of what async methods do. As such, we're already on whatever ExecutionContext is the right one to invoke + // the completion callback. This method may have even mutated the ExecutionContext, in which case for telemetry + // we need those mutations to be surfaced as part of this callback, so that logging performed here sees those + // mutations (e.g. to the current Activity). + OnCompletedInternal(); + } + } } - else + } + + private sealed class MultiConnectSocketAsyncEventArgs : SocketAsyncEventArgs, IValueTaskSource + { + private ManualResetValueTaskSourceCore _mrvtsc; + private int _isCompleted; + + public MultiConnectSocketAsyncEventArgs() : base(unsafeSuppressExecutionContextFlow: false) { - ExecutionContext.Run(context, s_executionCallback, this); + // Instances of this type are an implementation detail of an overarching connect operation. + // We don't want to emit telemetry specific to operations on this inner instance. + _disableTelemetry = true; } + + public void GetResult(short token) => _mrvtsc.GetResult(token); + public ValueTaskSourceStatus GetStatus(short token) => _mrvtsc.GetStatus(token); + public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _mrvtsc.OnCompleted(continuation, state, token, flags); + + public short Version => _mrvtsc.Version; + public void Reset() => _mrvtsc.Reset(); + + protected override void OnCompleted(SocketAsyncEventArgs e) => _mrvtsc.SetResult(true); + + public bool ReachedCoordinationPointFirst() => Interlocked.Exchange(ref _isCompleted, 1) == 0; } internal void FinishOperationSyncSuccess(int bytesTransferred, SocketFlags flags) @@ -811,7 +962,7 @@ internal void FinishOperationSyncSuccess(int bytesTransferred, SocketFlags flags break; } - if (SocketsTelemetry.Log.IsEnabled()) LogBytesTransferEvents(_currentSocket?.SocketType, _completedOperation, bytesTransferred); + if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) LogBytesTransferEvents(_currentSocket?.SocketType, _completedOperation, bytesTransferred); Complete(); } @@ -846,7 +997,7 @@ private void FinishOperationSync(SocketError socketError, int bytesTransferred, FinishOperationSyncFailure(socketError, bytesTransferred, flags); } - if (SocketsTelemetry.Log.IsEnabled()) AfterConnectAcceptTelemetry(); + if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry(); } private static void LogBytesTransferEvents(SocketType? socketType, SocketAsyncOperation operation, int bytesTransferred) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs index 62d5719ea18745..7c5e38bcde2ad6 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs @@ -1770,16 +1770,6 @@ public static SocketError Shutdown(SafeSocketHandle handle, bool isConnected, bo return GetSocketErrorForErrorCode(err); } - public static SocketError ConnectAsync(Socket socket, SafeSocketHandle handle, byte[] socketAddress, int socketAddressLen, ConnectOverlappedAsyncResult asyncResult) - { - SocketError socketError = handle.AsyncContext.ConnectAsync(socketAddress, socketAddressLen, asyncResult.CompletionCallback); - if (socketError == SocketError.Success) - { - asyncResult.CompletionCallback(SocketError.Success); - } - return socketError; - } - public static SocketError SendAsync(SafeSocketHandle handle, byte[] buffer, int offset, int count, SocketFlags socketFlags, OverlappedAsyncResult asyncResult) { int bytesSent; diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs index 8ebb6c42ff738f..430574a42f4559 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs @@ -1026,31 +1026,6 @@ public static SocketError Shutdown(SafeSocketHandle handle, bool isConnected, bo return err; } - public static unsafe SocketError ConnectAsync(Socket socket, SafeSocketHandle handle, byte[] socketAddress, int socketAddressLen, ConnectOverlappedAsyncResult asyncResult) - { - // This will pin the socketAddress buffer. - asyncResult.SetUnmanagedStructures(socketAddress); - try - { - int ignoreBytesSent; - bool success = socket.ConnectEx( - handle, - Marshal.UnsafeAddrOfPinnedArrayElement(socketAddress, 0), - socketAddressLen, - IntPtr.Zero, - 0, - out ignoreBytesSent, - asyncResult.DangerousOverlappedPointer); // SafeHandle was just created in SetUnmanagedStructures - - return asyncResult.ProcessOverlappedResult(success, 0); - } - catch - { - asyncResult.ReleaseUnmanagedStructures(); - throw; - } - } - public static unsafe SocketError SendAsync(SafeSocketHandle handle, byte[] buffer, int offset, int count, SocketFlags socketFlags, OverlappedAsyncResult asyncResult) { // Set up unmanaged structures for overlapped WSASend. diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs index 40a15f7185dfe9..c3c20fdf47ad6b 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.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.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -121,18 +122,17 @@ public async Task Connect_AfterDisconnect_Fails() } } - public static readonly TheoryData ConnectGetsCanceledByDispose_Data = new TheoryData - { - { IPAddress.Parse("1.1.1.1") }, - { IPAddress.Parse("1.1.1.1").MapToIPv6() }, - }; - [OuterLoop("Connects to external server")] - [Theory] - [MemberData(nameof(ConnectGetsCanceledByDispose_Data))] [PlatformSpecific(~(TestPlatforms.OSX | TestPlatforms.FreeBSD))] // Not supported on BSD like OSes. - public async Task ConnectGetsCanceledByDispose(IPAddress address) + [Theory] + [InlineData("1.1.1.1", false)] + [InlineData("1.1.1.1", true)] + [InlineData("[::ffff:1.1.1.1]", false)] + [InlineData("[::ffff:1.1.1.1]", true)] + public async Task ConnectGetsCanceledByDispose(string addressString, bool useDns) { + IPAddress address = IPAddress.Parse(addressString); + // 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; @@ -141,10 +141,12 @@ await RetryHelper.ExecuteAsync(async () => var client = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); if (address.IsIPv4MappedToIPv6) client.DualMode = true; - Task connectTask = ConnectAsync(client, new IPEndPoint(address, 23)); + Task connectTask = ConnectAsync(client, useDns ? + new DnsEndPoint("one.one.one.one", 23) : + new IPEndPoint(address, 23)); // Wait a little so the operation is started. - await Task.Delay(msDelay); + await Task.Delay(Math.Min(msDelay, 1000)); msDelay *= 2; Task disposeTask = Task.Run(() => client.Dispose()); @@ -176,7 +178,7 @@ await RetryHelper.ExecuteAsync(async () => } else if (UsesSync) { - Assert.Equal(SocketError.NotSocket, localSocketError); + Assert.True(disposedException || localSocketError == SocketError.NotSocket, $"{disposedException} {localSocketError}"); } else { diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DnsEndPointTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DnsEndPointTest.cs index a723f449a4b06a..be8cdead578272 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DnsEndPointTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DnsEndPointTest.cs @@ -118,7 +118,7 @@ public void Socket_BeginConnectDnsEndPoint_Success(SocketImplementationType type { IAsyncResult result = sock.BeginConnect(new DnsEndPoint("localhost", port), null, null); sock.EndConnect(result); - Assert.Throws(() => sock.EndConnect(result)); // validate can't call end twice + Assert.True(sock.Connected); } } @@ -330,9 +330,10 @@ public void Socket_StaticConnectAsync_Success(SocketImplementationType type) ManualResetEvent complete = new ManualResetEvent(false); args.UserToken = complete; - Assert.True(Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, args)); - - Assert.True(complete.WaitOne(TestSettings.PassingTestTimeout), "Timed out while waiting for connection"); + if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, args)) + { + Assert.True(complete.WaitOne(TestSettings.PassingTestTimeout), "Timed out while waiting for connection"); + } Assert.Equal(SocketError.Success, args.SocketError); Assert.Null(args.ConnectByNameError); @@ -345,9 +346,11 @@ public void Socket_StaticConnectAsync_Success(SocketImplementationType type) args.RemoteEndPoint = new DnsEndPoint("localhost", port6); complete.Reset(); - Assert.True(Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, args)); + if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, args)) + { + Assert.True(complete.WaitOne(TestSettings.PassingTestTimeout), "Timed out while waiting for connection"); + } - Assert.True(complete.WaitOne(TestSettings.PassingTestTimeout), "Timed out while waiting for connection"); complete.Dispose(); // only dispose on success as we know we're done with the instance Assert.Equal(SocketError.Success, args.SocketError); diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs index 38ea1a28c1087c..532cba4444f003 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs @@ -394,17 +394,13 @@ private async Task DualModeBeginConnect_IPAddressToHost_Fails_Helper(IPAddress c [Trait("IPv6", "true")] public class DualModeBeginConnectToIPEndPoint : DualModeBase { - [Fact] // Base case - // "The system detected an invalid pointer address in attempting to use a pointer argument in a call" + [Fact] public void Socket_BeginConnectV4IPEndPointToV4Host_Throws() { using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) { socket.DualMode = false; - Assert.Throws(() => - { - socket.BeginConnect(new IPEndPoint(IPAddress.Loopback, UnusedPort), null, null); - }); + Assert.Throws(() => socket.BeginConnect(new IPEndPoint(IPAddress.Loopback, UnusedPort), null, null)); } } @@ -438,7 +434,7 @@ public class DualModeBeginConnect : DualModeBase [Theory] [MemberData(nameof(DualMode_IPAddresses_ListenOn_DualMode_Data))] [PlatformSpecific(TestPlatforms.Windows)] // Connecting sockets to DNS endpoints via the instance Connect and ConnectAsync methods not supported on Unix - private async Task DualModeBeginConnect_IPAddressListToHost_Helper(IPAddress[] connectTo, IPAddress listenOn, bool dualModeServer) + public async Task DualModeBeginConnect_IPAddressListToHost_Helper(IPAddress[] connectTo, IPAddress listenOn, bool dualModeServer) { using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) using (SocketServer server = new SocketServer(_log, listenOn, dualModeServer, out int port))