-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Rework Unix socket option tracking #111676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
antonfirsov
merged 13 commits into
dotnet:main
from
antonfirsov:SocketOption-tracking-02
Feb 7, 2025
Merged
Changes from 3 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
2033a3e
rework socket option tracking
antonfirsov a6b9404
simplify ValidateForMultiConnect()
antonfirsov d60a9e0
LastTrackableSocketOptionIndex -> TrackableOptionCount
antonfirsov 08dbaef
"mask" -> flag
antonfirsov 6f58054
MultiConnect: if the handle is exposed and the first attempt fails, f…
antonfirsov fae29fa
reference the code-generator script
antonfirsov da323fd
more defensive handling of optionValues
antonfirsov 3f70a35
Disable MultiConnect tests on WASI (#112039)
antonfirsov 10c469f
harden tests for Mac
antonfirsov 22b484a
Merge branch 'main' into SocketOption-tracking-02
antonfirsov 955916a
Apply suggestions from code review
antonfirsov 7d47258
Merge branch 'main' into SocketOption-tracking-02
antonfirsov 25da5a0
Connect_ExposeHandle_FirstAttemptSucceeds
antonfirsov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
199 changes: 199 additions & 0 deletions
199
...braries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Unix.OptionTracking.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| // 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; | ||
|
|
||
| namespace System.Net.Sockets | ||
| { | ||
| // On Unix it is not possible to use a socket FD after a failed connect attempt for another connect, meaning that a new FD needs to be created | ||
| // after each failing attempt during a multi-connect. When creating a new handle, we need to make sure that relevant socket options are | ||
| // transferred to the new socket FD and we need to track which options have been changed so we know we need to transfer them. | ||
| // We are only tracking options which are relevant for sockets that can do multi-connect. Options which are only relevant for UDP/datagram sockets | ||
| // are not tracked, since multi-connect is not a meaningful operation for such sockets. | ||
| public partial class SafeSocketHandle | ||
| { | ||
| private int _trackedOptions; | ||
|
|
||
| internal void TrackSocketOption(SocketOptionLevel level, SocketOptionName name) | ||
| { | ||
| TrackableSocketOptions tracked = ToTrackableSocketOptions(name, level); | ||
|
|
||
| // For untracked socket options, we need to remember that they were used | ||
| // so that we can error out if a multi-connect attempt is made. | ||
| if (tracked == TrackableSocketOptions.None) | ||
| { | ||
| ExposedHandleOrUntrackedConfiguration = true; | ||
| return; | ||
| } | ||
|
|
||
| _trackedOptions |= GetMask(tracked); | ||
| } | ||
|
|
||
| internal void GetTrackedSocketOptions(Span<int> values, out LingerOption? lingerOption) | ||
| { | ||
| Debug.Assert(values.Length == TrackableOptionCount); | ||
| int trackedOptions = _trackedOptions; | ||
|
|
||
| // SO_LINGER is the only tracked socket option with a non-int value. | ||
| lingerOption = null; | ||
| int lingerMask = GetMask(TrackableSocketOptions.SO_LINGER); | ||
| if ((trackedOptions & lingerMask) == lingerMask) | ||
| { | ||
| SocketError errorCode = SocketPal.GetLingerOption(this, out lingerOption); | ||
| if (NetEventSource.Log.IsEnabled() && errorCode != SocketError.Success) NetEventSource.Info(this, $"GetLingerOption returned errorCode:{errorCode}"); | ||
|
|
||
| // Ignore it during the processing of int-value options. | ||
| trackedOptions &= ~lingerMask; | ||
| } | ||
|
|
||
| // For DualMode, we use the value stored in the handle rather than querying the socket itself, | ||
| // as on Unix stacks binding a dual-mode socket to an IPv6 address may cause IPV6_V6ONLY to revert to true. | ||
| int ipv6OnlyMask = GetMask(TrackableSocketOptions.IPV6_V6ONLY); | ||
| if ((trackedOptions & ipv6OnlyMask) == ipv6OnlyMask) | ||
| { | ||
| values[(int)TrackableSocketOptions.IPV6_V6ONLY - 1] = DualMode ? 0 : 1; | ||
| trackedOptions &= ~ipv6OnlyMask; | ||
| } | ||
|
|
||
| for (int i = 0; i < values.Length; i++) | ||
| { | ||
| int mask = 1 << i; | ||
| if ((trackedOptions & mask) == mask) | ||
| { | ||
| TrackableSocketOptions tracked = (TrackableSocketOptions)(i + 1); | ||
| (SocketOptionName name, SocketOptionLevel level) = ToSocketOptions(tracked); | ||
| SocketError errorCode = SocketPal.GetSockOpt(this, level, name, out values[i]); | ||
| if (NetEventSource.Log.IsEnabled() && errorCode != SocketError.Success) NetEventSource.Info(this, $"GetSockOpt({level},{name}) returned errorCode:{errorCode}"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| internal void SetTrackedSocketOptions(ReadOnlySpan<int> values, LingerOption? lingerOption) | ||
| { | ||
| Debug.Assert(values.Length == TrackableOptionCount); | ||
| int lingerMask = GetMask(TrackableSocketOptions.SO_LINGER); | ||
| if (lingerOption is not null) | ||
| { | ||
| Debug.Assert((_trackedOptions & lingerMask) == lingerMask); | ||
| SocketError errorCode = SocketPal.SetLingerOption(this, lingerOption); | ||
| if (NetEventSource.Log.IsEnabled() && errorCode != SocketError.Success) NetEventSource.Info(this, $"SetLingerOption returned errorCode:{errorCode}"); | ||
| } | ||
|
|
||
| int trackedOptions = _trackedOptions & ~lingerMask; | ||
|
|
||
| for (int i = 0; i < values.Length; i++) | ||
| { | ||
| int mask = 1 << i; | ||
| if ((trackedOptions & mask) == mask) | ||
| { | ||
| TrackableSocketOptions tracked = (TrackableSocketOptions)(i + 1); | ||
| (SocketOptionName name, SocketOptionLevel level) = ToSocketOptions(tracked); | ||
| SocketError errorCode = SocketPal.SetSockOpt(this, level, name, values[i]); | ||
| if (NetEventSource.Log.IsEnabled() && errorCode != SocketError.Success) NetEventSource.Info(this, $"GetSockOpt({level},{name}) returned errorCode:{errorCode}"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static int GetMask(TrackableSocketOptions tracked) => 1 << ((int)tracked - 1); | ||
|
|
||
| // Relevant option names and values taken from Windows headers. | ||
| private enum TrackableSocketOptions | ||
| { | ||
| None = 0, | ||
| IP_TOS, | ||
| IP_TTL, | ||
| IPV6_PROTECTION_LEVEL, | ||
| IPV6_V6ONLY, | ||
| TCP_NODELAY, | ||
| TCP_EXPEDITED_1122, | ||
| TCP_KEEPALIVE, | ||
| TCP_FASTOPEN, | ||
| TCP_KEEPCNT, | ||
| TCP_KEEPINTVL, | ||
| SO_DEBUG, | ||
| SO_ACCEPTCONN, | ||
| SO_REUSEADDR, | ||
| SO_KEEPALIVE, | ||
| SO_DONTROUTE, | ||
| SO_USELOOPBACK, | ||
| SO_LINGER, | ||
| SO_OOBINLINE, | ||
| SO_DONTLINGER, | ||
| SO_EXCLUSIVEADDRUSE, | ||
| SO_SNDBUF, | ||
| SO_RCVBUF, | ||
| SO_SNDLOWAT, | ||
| SO_RCVLOWAT, | ||
| SO_SNDTIMEO, | ||
| SO_RCVTIMEO | ||
| } | ||
|
|
||
| internal static int TrackableOptionCount => (int)TrackableSocketOptions.SO_RCVTIMEO; | ||
antonfirsov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| private static TrackableSocketOptions ToTrackableSocketOptions(SocketOptionName name, SocketOptionLevel level) | ||
| => ((int)name, level) switch | ||
| { | ||
| (3, SocketOptionLevel.IP) => TrackableSocketOptions.IP_TOS, | ||
MihaZupan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| (4, SocketOptionLevel.IP) => TrackableSocketOptions.IP_TTL, | ||
| (23, SocketOptionLevel.IPv6) => TrackableSocketOptions.IPV6_PROTECTION_LEVEL, | ||
| (27, SocketOptionLevel.IPv6) => TrackableSocketOptions.IPV6_V6ONLY, | ||
| (1, SocketOptionLevel.Tcp) => TrackableSocketOptions.TCP_NODELAY, | ||
| (2, SocketOptionLevel.Tcp) => TrackableSocketOptions.TCP_EXPEDITED_1122, | ||
| (3, SocketOptionLevel.Tcp) => TrackableSocketOptions.TCP_KEEPALIVE, | ||
| (15, SocketOptionLevel.Tcp) => TrackableSocketOptions.TCP_FASTOPEN, | ||
| (16, SocketOptionLevel.Tcp) => TrackableSocketOptions.TCP_KEEPCNT, | ||
| (17, SocketOptionLevel.Tcp) => TrackableSocketOptions.TCP_KEEPINTVL, | ||
| (1, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_DEBUG, | ||
| (2, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_ACCEPTCONN, | ||
| (4, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_REUSEADDR, | ||
| (8, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_KEEPALIVE, | ||
| (16, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_DONTROUTE, | ||
| (64, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_USELOOPBACK, | ||
| (128, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_LINGER, | ||
| (256, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_OOBINLINE, | ||
| (-129, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_DONTLINGER, | ||
| (-5, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_EXCLUSIVEADDRUSE, | ||
| (4097, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_SNDBUF, | ||
| (4098, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_RCVBUF, | ||
| (4099, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_SNDLOWAT, | ||
| (4100, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_RCVLOWAT, | ||
| (4101, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_SNDTIMEO, | ||
| (4102, SocketOptionLevel.Socket) => TrackableSocketOptions.SO_RCVTIMEO, | ||
|
|
||
| _ => TrackableSocketOptions.None | ||
| }; | ||
|
|
||
| private static (SocketOptionName, SocketOptionLevel) ToSocketOptions(TrackableSocketOptions options) => | ||
| options switch | ||
| { | ||
| TrackableSocketOptions.IP_TOS => ((SocketOptionName)3, SocketOptionLevel.IP), | ||
| TrackableSocketOptions.IP_TTL => ((SocketOptionName)4, SocketOptionLevel.IP), | ||
| TrackableSocketOptions.IPV6_PROTECTION_LEVEL => ((SocketOptionName)23, SocketOptionLevel.IPv6), | ||
| TrackableSocketOptions.IPV6_V6ONLY => ((SocketOptionName)27, SocketOptionLevel.IPv6), | ||
| TrackableSocketOptions.TCP_NODELAY => ((SocketOptionName)1, SocketOptionLevel.Tcp), | ||
| TrackableSocketOptions.TCP_EXPEDITED_1122 => ((SocketOptionName)2, SocketOptionLevel.Tcp), | ||
| TrackableSocketOptions.TCP_KEEPALIVE => ((SocketOptionName)3, SocketOptionLevel.Tcp), | ||
| TrackableSocketOptions.TCP_FASTOPEN => ((SocketOptionName)15, SocketOptionLevel.Tcp), | ||
| TrackableSocketOptions.TCP_KEEPCNT => ((SocketOptionName)16, SocketOptionLevel.Tcp), | ||
| TrackableSocketOptions.TCP_KEEPINTVL => ((SocketOptionName)17, SocketOptionLevel.Tcp), | ||
| TrackableSocketOptions.SO_DEBUG => ((SocketOptionName)1, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_ACCEPTCONN => ((SocketOptionName)2, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_REUSEADDR => ((SocketOptionName)4, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_KEEPALIVE => ((SocketOptionName)8, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_DONTROUTE => ((SocketOptionName)16, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_USELOOPBACK => ((SocketOptionName)64, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_LINGER => ((SocketOptionName)128, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_OOBINLINE => ((SocketOptionName)256, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_DONTLINGER => ((SocketOptionName)(-129), SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_EXCLUSIVEADDRUSE => ((SocketOptionName)(-5), SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_SNDBUF => ((SocketOptionName)4097, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_RCVBUF => ((SocketOptionName)4098, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_SNDLOWAT => ((SocketOptionName)4099, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_RCVLOWAT => ((SocketOptionName)4100, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_SNDTIMEO => ((SocketOptionName)4101, SocketOptionLevel.Socket), | ||
| TrackableSocketOptions.SO_RCVTIMEO => ((SocketOptionName)4102, SocketOptionLevel.Socket), | ||
|
|
||
| _ => default | ||
| }; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.