Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -752,8 +752,12 @@ async Task Core(MultiConnectSocketAsyncEventArgs internalArgs, Task<IPAddress[]>
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we could hide it inside of the CanTryAddressFamily. But I'm fine with this as well. It looks reasonable to me and it should not impact the major platforms.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new helper mutates state (reassigns _addressFamily + recreates the handle), whereas CanTryAddressFamily is a pure predicate used by ~12 call sites. The re-create is only valid in the multi-connect still-unconnected path, so I'd rather keep it at the call site. This unblocks dotnet restore etc. which connect to IPv6 endpoint on OpenBSD.

// 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;
}
Expand Down