File: System\Net\Sockets\SafeSocketHandle.Unix.OptionTracking.cs
Web Access
Project: src\src\libraries\System.Net.Sockets\src\System.Net.Sockets.csproj (System.Net.Sockets)
// 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 |= GetFlag(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 lingerFlag = GetFlag(TrackableSocketOptions.SO_LINGER);
            if ((trackedOptions & lingerFlag) == lingerFlag)
            {
                SocketError errorCode = SocketPal.GetLingerOption(this, out lingerOption);
                if (errorCode != SocketError.Success)
                {
                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"GetLingerOption returned errorCode:{errorCode}");
 
                    // Untrack this option, so we don't try to set it.
                    _trackedOptions &= ~lingerFlag;
                }
 
                // Ignore it during the processing of int-value options.
                trackedOptions &= ~lingerFlag;
            }
 
            // 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 ipv6OnlyFlag = GetFlag(TrackableSocketOptions.IPV6_V6ONLY);
            if ((trackedOptions & ipv6OnlyFlag) == ipv6OnlyFlag)
            {
                values[(int)TrackableSocketOptions.IPV6_V6ONLY - 1] = DualMode ? 0 : 1;
                trackedOptions &= ~ipv6OnlyFlag;
            }
 
            for (int i = 0; i < values.Length; i++)
            {
                int flag = 1 << i;
                if ((trackedOptions & flag) == flag)
                {
                    TrackableSocketOptions tracked = (TrackableSocketOptions)(i + 1);
                    (SocketOptionName name, SocketOptionLevel level) = ToSocketOptions(tracked);
                    SocketError errorCode = SocketPal.GetSockOpt(this, level, name, out values[i]);
                    if (errorCode != SocketError.Success)
                    {
                        if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"GetSockOpt({level},{name}) returned errorCode:{errorCode}");
 
                        // Untrack this option, so we don't try to set it.
                        _trackedOptions &= ~flag;
                    }
                }
            }
        }
 
        internal void SetTrackedSocketOptions(ReadOnlySpan<int> values, LingerOption? lingerOption)
        {
            Debug.Assert(values.Length == TrackableOptionCount);
            int lingerFlag = GetFlag(TrackableSocketOptions.SO_LINGER);
            if (lingerOption is not null)
            {
                Debug.Assert((_trackedOptions & lingerFlag) == lingerFlag);
                SocketError errorCode = SocketPal.SetLingerOption(this, lingerOption);
                if (NetEventSource.Log.IsEnabled() && errorCode != SocketError.Success) NetEventSource.Info(this, $"SetLingerOption returned errorCode:{errorCode}");
            }
 
            int trackedOptions = _trackedOptions & ~lingerFlag;
 
            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 GetFlag(TrackableSocketOptions tracked) => 1 << ((int)tracked - 1);
 
        // The code below is auto-generated based on option names and values defined in Windows headers:
        // https://gist.github.com/antonfirsov/2cbfc37e665ad840ed7734994948c29a
        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 const int TrackableOptionCount = (int)TrackableSocketOptions.SO_RCVTIMEO;
 
        private static TrackableSocketOptions ToTrackableSocketOptions(SocketOptionName name, SocketOptionLevel level)
            => ((int)name, level) switch
            {
                (3, SocketOptionLevel.IP) => TrackableSocketOptions.IP_TOS,
                (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
            };
    }
}