diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs index b837994f18e013..4ef96be26219c6 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs @@ -130,6 +130,21 @@ internal void ReplaceHandleIfNecessaryAfterFailedConnect() _handle.LastConnectFailed = false; } + // On platforms that lack IPv6 dual-mode support (e.g. OpenBSD), a socket created for one address + // family cannot connect to an address of the other. When the socket has not yet connected, re-create + // its handle for the requested family so a pending connect attempt can use it. Returns false if the + // family is unchanged or the handle could not be replaced. + internal bool TryReplaceHandleForAddressFamily(AddressFamily addressFamily) + { + if (_addressFamily == addressFamily) + { + return false; + } + + _addressFamily = addressFamily; + return ReplaceHandle() == SocketError.Success; + } + internal unsafe SocketError ReplaceHandle() { // Collect values of trackable socket options marked by SafeSocketHandle.TrackSocketOption(). 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 064b382fdfc68b..46d6fcb9aacec3 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 @@ -21,6 +21,10 @@ public partial class Socket #pragma warning disable CA1822 internal void ReplaceHandleIfNecessaryAfterFailedConnect() { /* nop on Windows */ } + + // Windows always supports IPv6 dual-mode, so OSSupportsIPv6DualMode short-circuits before this is + // ever called; the stub exists only to satisfy the cross-platform call site. + internal bool TryReplaceHandleForAddressFamily(AddressFamily _) => false; internal bool CanProceedWithMultiConnect => true; #pragma warning restore CA1822 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 57397ea0ace268..c618678c30dc67 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 @@ -752,8 +752,12 @@ async Task Core(MultiConnectSocketAsyncEventArgs internalArgs, Task 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)) + // If that instance doesn't support this address, move on to the next. The exception + // is platforms without IPv6 dual-mode (e.g. OpenBSD), where a single socket can't + // reach both families: re-create the still-unconnected handle for this address' + // family so the connect can proceed. Dual-mode platforms skip this entirely. + if (!_currentSocket.CanTryAddressFamily(address.AddressFamily) && + (Socket.OSSupportsIPv6DualMode || !_currentSocket.TryReplaceHandleForAddressFamily(address.AddressFamily))) { continue; }