File: System\Net\Sockets\Socket.Unix.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.Collections;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
 
namespace System.Net.Sockets
{
    public partial class Socket
    {
        [SupportedOSPlatform("windows")]
        public Socket(SocketInformation socketInformation)
        {
            // This constructor works in conjunction with DuplicateAndClose, which is not supported on Unix.
            // See comments in DuplicateAndClose.
            throw new PlatformNotSupportedException(SR.net_sockets_duplicateandclose_notsupported);
        }
 
        [SupportedOSPlatform("windows")]
        public SocketInformation DuplicateAndClose(int targetProcessId)
        {
            // DuplicateAndClose is not supported on Unix, since passing file descriptors between processes
            // requires Unix Domain Sockets. The programming model is fundamentally different,
            // and incompatible with the design of SocketInformation-related methods.
            throw new PlatformNotSupportedException(SR.net_sockets_duplicateandclose_notsupported);
        }
 
        internal bool PreferInlineCompletions
        {
            get => _handle.PreferInlineCompletions;
            set => _handle.PreferInlineCompletions = value;
        }
 
        partial void ValidateForMultiConnect(bool isMultiEndpoint)
        {
            // ValidateForMultiConnect is called before any {Begin}Connect{Async} call,
            // regardless of whether it's targeting an endpoint with multiple addresses.
            // If it is targeting such an endpoint, then any exposure of the socket's handle
            // or configuration of the socket we haven't tracked would prevent us from
            // replicating the socket's file descriptor appropriately.  Similarly, if it's
            // only targeting a single address, but it already experienced a failure in a
            // previous connect call, then this is logically part of a multi endpoint connect,
            // and the same logic applies.  Either way, in such a situation we throw.
            if (_handle.ExposedHandleOrUntrackedConfiguration && (isMultiEndpoint || _handle.LastConnectFailed))
            {
                ThrowMultiConnectNotSupported();
            }
 
            // If the socket was already used for a failed connect attempt, replace it
            // with a fresh one, copying over all of the state we've tracked.
            ReplaceHandleIfNecessaryAfterFailedConnect();
            Debug.Assert(!_handle.LastConnectFailed);
        }
 
        private static unsafe void LoadSocketTypeFromHandle(
            SafeSocketHandle handle, out AddressFamily addressFamily, out SocketType socketType, out ProtocolType protocolType, out bool blocking, out bool isListening, out bool isSocket)
        {
            if (OperatingSystem.IsWasi())
            {
                // FIXME: Unify with unix after https://github.com/WebAssembly/wasi-libc/issues/537
                blocking = false;
                Interop.Error e = Interop.Sys.GetSocketType(handle, out addressFamily, out socketType, out protocolType, out isListening);
                if (e == Interop.Error.ENOTSOCK)
                {
                    throw new SocketException((int)SocketError.NotSocket);
                }
                handle.IsSocket = isSocket = true;
                return;
            }
 
            if (Interop.Sys.FStat(handle, out Interop.Sys.FileStatus stat) == -1)
            {
                throw new SocketException((int)SocketError.NotSocket);
            }
            isSocket = (stat.Mode & Interop.Sys.FileTypes.S_IFSOCK) == Interop.Sys.FileTypes.S_IFSOCK;
 
            handle.IsSocket = isSocket;
 
            if (isSocket)
            {
                // On Linux, GetSocketType will be able to query SO_DOMAIN, SO_TYPE, and SO_PROTOCOL to get the
                // address family, socket type, and protocol type, respectively.  On macOS, this will only succeed
                // in getting the socket type, and the others will be unknown.  Subsequently the Socket ctor
                // can use getsockname to retrieve the address family as part of trying to get the local end point.
                Interop.Error e = Interop.Sys.GetSocketType(handle, out addressFamily, out socketType, out protocolType, out isListening);
                Debug.Assert(e == Interop.Error.SUCCESS, e.ToString());
            }
            else
            {
                addressFamily = AddressFamily.Unknown;
                socketType = SocketType.Unknown;
                protocolType = ProtocolType.Unknown;
                isListening = false;
            }
 
            // Get whether the socket is in non-blocking mode.  On Unix, we automatically put the underlying
            // Socket into non-blocking mode whenever an async method is first invoked on the instance, but we
            // maintain a shadow bool that maintains the Socket.Blocking value set by the developer.  Because
            // we're querying the underlying socket here, and don't have access to the original Socket instance
            // (if there even was one... the Socket(SafeSocketHandle) ctor is likely being used because there
            // wasn't one, Socket.Blocking will end up reflecting the actual state of the socket even if the
            // developer didn't set Blocking = false.
            bool nonBlocking;
            int rv = Interop.Sys.Fcntl.GetIsNonBlocking(handle, out nonBlocking);
            blocking = !nonBlocking;
            Debug.Assert(rv == 0 || blocking); // ignore failures
        }
 
        internal void ReplaceHandleIfNecessaryAfterFailedConnect()
        {
            if (!_handle.LastConnectFailed)
            {
                return;
            }
 
            SocketError errorCode = ReplaceHandle();
            if (errorCode != SocketError.Success)
            {
                throw new SocketException((int)errorCode);
            }
 
            _handle.LastConnectFailed = false;
        }
 
        internal SocketError ReplaceHandle()
        {
            // Copy out values from key options. The copied values should be kept in sync with the
            // handling in SafeSocketHandle.TrackOption.  Note that we copy these values out first, before
            // we change _handle, so that we can use the helpers on Socket which internally access _handle.
            // Then once _handle is switched to the new one, we can call the setters to propagate the retrieved
            // values back out to the new underlying socket.
            bool broadcast = false, dontFragment = false, noDelay = false;
            int receiveSize = -1, receiveTimeout = -1, sendSize = -1, sendTimeout = -1;
            short ttl = -1;
            LingerOption? linger = null;
            if (_handle.IsTrackedOption(TrackedSocketOptions.DontFragment)) dontFragment = DontFragment;
            if (_handle.IsTrackedOption(TrackedSocketOptions.EnableBroadcast)) broadcast = EnableBroadcast;
            if (_handle.IsTrackedOption(TrackedSocketOptions.LingerState)) linger = LingerState;
            if (_handle.IsTrackedOption(TrackedSocketOptions.NoDelay)) noDelay = NoDelay;
            if (_handle.IsTrackedOption(TrackedSocketOptions.ReceiveBufferSize)) receiveSize = ReceiveBufferSize;
            if (_handle.IsTrackedOption(TrackedSocketOptions.ReceiveTimeout)) receiveTimeout = ReceiveTimeout;
            if (_handle.IsTrackedOption(TrackedSocketOptions.SendBufferSize)) sendSize = SendBufferSize;
            if (_handle.IsTrackedOption(TrackedSocketOptions.SendTimeout)) sendTimeout = SendTimeout;
            if (_handle.IsTrackedOption(TrackedSocketOptions.Ttl)) ttl = Ttl;
 
            // Then replace the handle with a new one
            SafeSocketHandle oldHandle = _handle;
            SocketError errorCode = SocketPal.CreateSocket(_addressFamily, _socketType, _protocolType, out SafeSocketHandle newHandle);
            Volatile.Write(ref _handle, newHandle);
            oldHandle.TransferTrackedState(_handle);
            oldHandle.Dispose();
 
            if (errorCode != SocketError.Success)
            {
                return errorCode;
            }
 
            if (Volatile.Read(ref _disposed))
            {
                _handle.Dispose();
                throw new ObjectDisposedException(GetType().FullName);
            }
 
            // And put back the copied settings.  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 the IPv6Only setting to revert to true.
            if (_handle.IsTrackedOption(TrackedSocketOptions.DualMode)) DualMode = _handle.DualMode;
            if (_handle.IsTrackedOption(TrackedSocketOptions.DontFragment)) DontFragment = dontFragment;
            if (_handle.IsTrackedOption(TrackedSocketOptions.EnableBroadcast)) EnableBroadcast = broadcast;
            if (_handle.IsTrackedOption(TrackedSocketOptions.LingerState)) LingerState = linger!;
            if (_handle.IsTrackedOption(TrackedSocketOptions.NoDelay)) NoDelay = noDelay;
            if (_handle.IsTrackedOption(TrackedSocketOptions.ReceiveBufferSize)) ReceiveBufferSize = receiveSize;
            if (_handle.IsTrackedOption(TrackedSocketOptions.ReceiveTimeout)) ReceiveTimeout = receiveTimeout;
            if (_handle.IsTrackedOption(TrackedSocketOptions.SendBufferSize)) SendBufferSize = sendSize;
            if (_handle.IsTrackedOption(TrackedSocketOptions.SendTimeout)) SendTimeout = sendTimeout;
            if (_handle.IsTrackedOption(TrackedSocketOptions.Ttl)) Ttl = ttl;
 
            return SocketError.Success;
        }
 
        private static void ThrowMultiConnectNotSupported()
        {
            throw new PlatformNotSupportedException(SR.net_sockets_connect_multiconnect_notsupported);
        }
 
#pragma warning disable IDE0060, CA1822
        private Socket? GetOrCreateAcceptSocket(Socket? acceptSocket, bool checkDisconnected, string propertyName, out SafeSocketHandle? handle)
        {
            if (acceptSocket != null)
            {
                if (acceptSocket._handle.HasShutdownSend)
                {
                    throw new SocketException((int)SocketError.InvalidArgument);
                }
 
                if (acceptSocket._rightEndPoint != null && (!checkDisconnected || !acceptSocket._isDisconnected))
                {
                    throw new InvalidOperationException(SR.Format(SR.net_sockets_namedmustnotbebound, propertyName));
                }
            }
 
            handle = null;
            return acceptSocket;
        }
#pragma warning restore IDE0060, CA1822
 
        private static void CheckTransmitFileOptions(TransmitFileOptions flags)
        {
            // Note, UseDefaultWorkerThread is the default and is == 0.
            // Unfortunately there is no TransmitFileOptions.None.
            if (flags != TransmitFileOptions.UseDefaultWorkerThread)
            {
                throw new PlatformNotSupportedException(SR.net_sockets_transmitfileoptions_notsupported);
            }
        }
 
        private void SendFileInternal(string? fileName, ReadOnlySpan<byte> preBuffer, ReadOnlySpan<byte> postBuffer, TransmitFileOptions flags)
        {
            CheckTransmitFileOptions(flags);
 
            SocketError errorCode = SocketError.Success;
 
            // Open the file, if any
            // Open it before we send the preBuffer so that any exception happens first
            using (SafeFileHandle? fileHandle = OpenFileHandle(fileName))
            {
                // Send the preBuffer, if any
                // This will throw on error
                if (!preBuffer.IsEmpty)
                {
                    Send(preBuffer);
                }
 
                // Send the file, if any
                if (fileHandle != null)
                {
                    // This can throw ObjectDisposedException.
                    errorCode = SocketPal.SendFile(_handle, fileHandle);
                }
            }
 
            if (errorCode != SocketError.Success)
            {
                UpdateSendSocketErrorForDisposed(ref errorCode);
 
                UpdateStatusAfterSocketErrorAndThrowException(errorCode);
            }
 
            // Send the postBuffer, if any
            // This will throw on error
            if (!postBuffer.IsEmpty)
            {
                Send(postBuffer);
            }
        }
 
        internal void DisposeHandle()
        {
            _handle.Dispose();
        }
 
        internal void ClearHandle()
        {
            _handle = null!;
        }
 
        internal Socket CopyStateFromSource(Socket source)
        {
            _addressFamily = source._addressFamily;
            _closeTimeout = source._closeTimeout;
            _disposed = source._disposed;
            _handle = source._handle;
            _isConnected = source._isConnected;
            _isDisconnected = source._isDisconnected;
            _isListening = source._isListening;
            _nonBlockingConnectInProgress = source._nonBlockingConnectInProgress;
            _protocolType = source._protocolType;
            _receivingPacketInformation = source._receivingPacketInformation;
            _remoteEndPoint = source._remoteEndPoint;
            _rightEndPoint = source._rightEndPoint;
            _socketType = source._socketType;
            _willBlock = source._willBlock;
            _willBlockInternal = source._willBlockInternal;
            _localEndPoint = source._localEndPoint;
            _multiBufferReceiveEventArgs = source._multiBufferReceiveEventArgs;
            _multiBufferSendEventArgs = source._multiBufferSendEventArgs;
            _pendingConnectRightEndPoint = source._pendingConnectRightEndPoint;
            _singleBufferReceiveEventArgs = source._singleBufferReceiveEventArgs;
#if DEBUG
            // Try to detect if a property gets added that we're not copying correctly.
            foreach (PropertyInfo pi in typeof(Socket).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
            {
                try
                {
                    object? origValue = pi.GetValue(source);
                    object? cloneValue = pi.GetValue(this);
 
                    Debug.Assert(Equals(origValue, cloneValue), $"{pi.Name}. Expected: {origValue}, Actual: {cloneValue}");
                }
                catch (TargetInvocationException ex) when (ex.InnerException is SocketException se && se.SocketErrorCode == SocketError.OperationNotSupported)
                {
                    // macOS fails to retrieve DontFragment and MulticastLoopback at the moment
                }
            }
#endif
            return this;
        }
    }
}