From 8102f494027ac53279e8d8c26dd91a7f65dba0d0 Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:12:59 +0300 Subject: [PATCH 1/4] Support multi-connect on non-dual-mode IPv6 --- .../src/System/Net/Sockets/SocketAsyncEventArgs.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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; } From 3a951102d11f0ef1f11b37f7952eae9c427a4577 Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:18:17 +0300 Subject: [PATCH 2/4] . --- .../src/System/Net/Sockets/Socket.Windows.cs | 4 ++++ 1 file changed, 4 insertions(+) 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..2054d0080e7674 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 addressFamily) => false; internal bool CanProceedWithMultiConnect => true; #pragma warning restore CA1822 From 784075846cb01d79207ad4fbe79bb9728a71a4ff Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:18:47 +0300 Subject: [PATCH 3/4] . --- .../src/System/Net/Sockets/Socket.Unix.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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(). From 7b712d74e73ff940665aec8fe51a7e82c10e3867 Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:50:18 +0300 Subject: [PATCH 4/4] . --- .../System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2054d0080e7674..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 @@ -24,7 +24,7 @@ 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 addressFamily) => false; + internal bool TryReplaceHandleForAddressFamily(AddressFamily _) => false; internal bool CanProceedWithMultiConnect => true; #pragma warning restore CA1822