File: System\Net\Sockets\UDPClient.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.CodeAnalysis;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Net.Sockets
{
    // The System.Net.Sockets.UdpClient class provides access to UDP services at a higher abstraction
    // level than the System.Net.Sockets.Socket class. System.Net.Sockets.UdpClient is used to
    // connect to a remote host and to receive connections from a remote client.
    public partial class UdpClient : IDisposable
    {
        private const int MaxUDPSize = 0x10000;
 
        private Socket _clientSocket = null!; // initialized by helper called from ctor
        private bool _active;
        private readonly byte[] _buffer = new byte[MaxUDPSize];
        private AddressFamily _family = AddressFamily.InterNetwork;
 
        // Initializes a new instance of the System.Net.Sockets.UdpClientclass.
        public UdpClient() : this(AddressFamily.InterNetwork)
        {
        }
 
        // Initializes a new instance of the System.Net.Sockets.UdpClientclass.
        public UdpClient(AddressFamily family)
        {
            if (family != AddressFamily.InterNetwork && family != AddressFamily.InterNetworkV6)
            {
                throw new ArgumentException(SR.Format(SR.net_protocol_invalid_family, "UDP"), nameof(family));
            }
 
            _family = family;
 
            CreateClientSocket();
        }
 
        // Creates a new instance of the UdpClient class that communicates on the
        // specified port number.
        //
        // NOTE: We should obsolete this. This also breaks IPv6-only scenarios.
        // But fixing it has many complications that we have decided not
        // to fix it and instead obsolete it later.
        public UdpClient(int port) : this(port, AddressFamily.InterNetwork)
        {
        }
 
        // Creates a new instance of the UdpClient class that communicates on the
        // specified port number.
        public UdpClient(int port, AddressFamily family)
        {
            if (!TcpValidationHelpers.ValidatePortNumber(port))
            {
                throw new ArgumentOutOfRangeException(nameof(port));
            }
            if (family != AddressFamily.InterNetwork && family != AddressFamily.InterNetworkV6)
            {
                throw new ArgumentException(SR.Format(SR.net_protocol_invalid_family, "UDP"), nameof(family));
            }
 
            IPEndPoint localEP;
            _family = family;
 
            if (_family == AddressFamily.InterNetwork)
            {
                localEP = new IPEndPoint(IPAddress.Any, port);
            }
            else
            {
                localEP = new IPEndPoint(IPAddress.IPv6Any, port);
            }
 
            CreateClientSocket();
 
            _clientSocket.Bind(localEP);
        }
 
        // Creates a new instance of the UdpClient class that communicates on the
        // specified end point.
        public UdpClient(IPEndPoint localEP)
        {
            ArgumentNullException.ThrowIfNull(localEP);
 
            // IPv6 Changes: Set the AddressFamily of this object before
            //               creating the client socket.
            _family = localEP.AddressFamily;
 
            CreateClientSocket();
 
            _clientSocket.Bind(localEP);
        }
 
        // Used by the class to indicate that a connection to a remote host has been made.
        protected bool Active
        {
            get
            {
                return _active;
            }
            set
            {
                _active = value;
            }
        }
 
        public int Available
        {
            get
            {
                return _clientSocket.Available;
            }
        }
 
        public Socket Client
        {
            get
            {
                return _clientSocket;
            }
            set
            {
                _clientSocket = value;
            }
        }
 
        public short Ttl
        {
            get
            {
                return _clientSocket.Ttl;
            }
            set
            {
                _clientSocket.Ttl = value;
            }
        }
 
        public bool DontFragment
        {
            get
            {
                return _clientSocket.DontFragment;
            }
            set
            {
                _clientSocket.DontFragment = value;
            }
        }
 
        public bool MulticastLoopback
        {
            get
            {
                return _clientSocket.MulticastLoopback;
            }
            set
            {
                _clientSocket.MulticastLoopback = value;
            }
        }
 
        public bool EnableBroadcast
        {
            get
            {
                return _clientSocket.EnableBroadcast;
            }
            set
            {
                _clientSocket.EnableBroadcast = value;
            }
        }
 
        public bool ExclusiveAddressUse
        {
            get
            {
                return _clientSocket.ExclusiveAddressUse;
            }
            set
            {
                _clientSocket.ExclusiveAddressUse = value;
            }
        }
 
        [SupportedOSPlatform("windows")]
        public void AllowNatTraversal(bool allowed)
        {
            _clientSocket.SetIPProtectionLevel(allowed ? IPProtectionLevel.Unrestricted : IPProtectionLevel.EdgeRestricted);
        }
 
        private bool _disposed;
 
        private bool IsAddressFamilyCompatible(AddressFamily family)
        {
            // Check if the provided address family is compatible with the socket address family
            if (family == _family)
            {
                return true;
            }
 
            if (family == AddressFamily.InterNetwork)
            {
                return _family == AddressFamily.InterNetworkV6 && _clientSocket.DualMode;
            }
 
            return false;
        }
 
        public void Dispose()
        {
            Dispose(true);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this);
 
                // The only resource we need to free is the network stream, since this
                // is based on the client socket, closing the stream will cause us
                // to flush the data to the network, close the stream and (in the
                // NetoworkStream code) close the socket as well.
                if (_disposed)
                {
                    return;
                }
 
                Socket chkClientSocket = _clientSocket;
                if (chkClientSocket != null)
                {
                    // If the NetworkStream wasn't retrieved, the Socket might
                    // still be there and needs to be closed to release the effect
                    // of the Bind() call and free the bound IPEndPoint.
                    chkClientSocket.InternalShutdown(SocketShutdown.Both);
                    chkClientSocket.Dispose();
                    _clientSocket = null!;
                }
 
                _disposed = true;
                GC.SuppressFinalize(this);
            }
        }
 
        private bool _isBroadcast;
        private void CheckForBroadcast(IPAddress ipAddress)
        {
            // Here we check to see if the user is trying to use a Broadcast IP address
            // we only detect IPAddress.Broadcast (which is not the only Broadcast address)
            // and in that case we set SocketOptionName.Broadcast on the socket to allow its use.
            // if the user really wants complete control over Broadcast addresses they need to
            // inherit from UdpClient and gain control over the Socket and do whatever is appropriate.
            if (_clientSocket != null && !_isBroadcast && IsBroadcast(ipAddress))
            {
                // We need to set the Broadcast socket option.
                // Note that once we set the option on the Socket we never reset it.
                _isBroadcast = true;
                _clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            }
        }
 
        private static bool IsBroadcast(IPAddress address)
        {
            if (address.AddressFamily == AddressFamily.InterNetworkV6)
            {
                // No such thing as a broadcast address for IPv6.
                return false;
            }
            else
            {
                return address.Equals(IPAddress.Broadcast);
            }
        }
 
        public IAsyncResult BeginSend(byte[] datagram, int bytes, AsyncCallback? requestCallback, object? state) =>
            BeginSend(datagram, bytes, null, requestCallback, state);
 
        public IAsyncResult BeginSend(byte[] datagram, int bytes, string? hostname, int port, AsyncCallback? requestCallback, object? state) =>
            BeginSend(datagram, bytes, GetEndpoint(hostname, port), requestCallback, state);
 
        public IAsyncResult BeginSend(byte[] datagram, int bytes, IPEndPoint? endPoint, AsyncCallback? requestCallback, object? state)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ValidateDatagram(datagram, bytes, endPoint);
 
            if (endPoint is null)
            {
                return _clientSocket.BeginSend(datagram, 0, bytes, SocketFlags.None, requestCallback, state);
            }
            else
            {
                CheckForBroadcast(endPoint.Address);
                return _clientSocket.BeginSendTo(datagram, 0, bytes, SocketFlags.None, endPoint, requestCallback, state);
            }
        }
 
        public int EndSend(IAsyncResult asyncResult)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            return _active ?
                _clientSocket.EndSend(asyncResult) :
                _clientSocket.EndSendTo(asyncResult);
        }
 
        private void ValidateDatagram(byte[] datagram, int bytes, IPEndPoint? endPoint)
        {
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(datagram);
 
            ArgumentOutOfRangeException.ThrowIfNegative(bytes);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(bytes, datagram.Length);
 
            if (_active && endPoint != null)
            {
                // Do not allow sending packets to arbitrary host when connected.
                throw new InvalidOperationException(SR.net_udpconnected);
            }
        }
 
        private IPEndPoint? GetEndpoint(string? hostname, int port)
        {
            if (_active && ((hostname != null) || (port != 0)))
            {
                // Do not allow sending packets to arbitrary host when connected.
                throw new InvalidOperationException(SR.net_udpconnected);
            }
 
            IPEndPoint? ipEndPoint = null;
            if (hostname != null && port != 0)
            {
                IPAddress[] addresses = Dns.GetHostAddresses(hostname);
 
                int i = 0;
                for (; i < addresses.Length && !IsAddressFamilyCompatible(addresses[i].AddressFamily); i++)
                {
                }
 
                if (addresses.Length == 0 || i == addresses.Length)
                {
                    throw new ArgumentException(SR.net_invalidAddressList, nameof(hostname));
                }
 
                CheckForBroadcast(addresses[i]);
                ipEndPoint = new IPEndPoint(addresses[i], port);
            }
 
            return ipEndPoint;
        }
 
        public IAsyncResult BeginReceive(AsyncCallback? requestCallback, object? state)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            // Due to the nature of the ReceiveFrom() call and the ref parameter convention,
            // we need to cast an IPEndPoint to its base class EndPoint and cast it back down
            // to IPEndPoint.
            EndPoint tempRemoteEP = _family == AddressFamily.InterNetwork ?
                IPEndPointStatics.Any :
                IPEndPointStatics.IPv6Any;
 
            return _clientSocket.BeginReceiveFrom(_buffer, 0, MaxUDPSize, SocketFlags.None, ref tempRemoteEP, requestCallback, state);
        }
 
        public byte[] EndReceive(IAsyncResult asyncResult, ref IPEndPoint? remoteEP)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            EndPoint tempRemoteEP = _family == AddressFamily.InterNetwork ?
                IPEndPointStatics.Any :
                IPEndPointStatics.IPv6Any;
 
            int received = _clientSocket.EndReceiveFrom(asyncResult, ref tempRemoteEP);
            remoteEP = (IPEndPoint)tempRemoteEP;
 
            // Because we don't return the actual length, we need to ensure the returned buffer
            // has the appropriate length.
            return _buffer.AsSpan(0, received).ToArray();
        }
 
        // Joins a multicast address group.
        public void JoinMulticastGroup(IPAddress multicastAddr)
        {
            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNull(multicastAddr);
            if (multicastAddr.AddressFamily != _family)
            {
                // For IPv6, we need to create the correct MulticastOption and must also check for address family compatibility.
                // Note: we cannot reliably use IPv4 multicast over IPv6 in DualMode, as such we keep the compatibility explicit between IP stack versions
                throw new ArgumentException(SR.Format(SR.net_protocol_invalid_multicast_family, "UDP"), nameof(multicastAddr));
            }
 
            if (_family == AddressFamily.InterNetwork)
            {
                _clientSocket.SetSocketOption(
                    SocketOptionLevel.IP,
                    SocketOptionName.AddMembership,
                    new MulticastOption(multicastAddr));
            }
            else
            {
                _clientSocket.SetSocketOption(
                    SocketOptionLevel.IPv6,
                    SocketOptionName.AddMembership,
                    new IPv6MulticastOption(multicastAddr));
            }
        }
 
        public void JoinMulticastGroup(IPAddress multicastAddr, IPAddress localAddress)
        {
            ThrowIfDisposed();
 
            if (_family != AddressFamily.InterNetwork)
            {
                throw new SocketException((int)SocketError.OperationNotSupported);
            }
 
            _clientSocket.SetSocketOption(
               SocketOptionLevel.IP,
               SocketOptionName.AddMembership,
               new MulticastOption(multicastAddr, localAddress));
        }
 
        // Joins an IPv6 multicast address group.
        public void JoinMulticastGroup(int ifindex, IPAddress multicastAddr)
        {
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(multicastAddr);
            ArgumentOutOfRangeException.ThrowIfNegative(ifindex);
            if (_family != AddressFamily.InterNetworkV6)
            {
                // Ensure that this is an IPv6 client, otherwise throw WinSock
                // Operation not supported socked exception.
                throw new SocketException((int)SocketError.OperationNotSupported);
            }
 
            _clientSocket.SetSocketOption(
                SocketOptionLevel.IPv6,
                SocketOptionName.AddMembership,
                new IPv6MulticastOption(multicastAddr, ifindex));
        }
 
        // Joins a multicast address group with the specified time to live (TTL).
        public void JoinMulticastGroup(IPAddress multicastAddr, int timeToLive)
        {
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(multicastAddr);
            if (!RangeValidationHelpers.ValidateRange(timeToLive, 0, 255))
            {
                throw new ArgumentOutOfRangeException(nameof(timeToLive));
            }
 
            // Join the Multicast Group.
            JoinMulticastGroup(multicastAddr);
 
            // Set Time To Live (TTL).
            _clientSocket.SetSocketOption(
                (_family == AddressFamily.InterNetwork) ? SocketOptionLevel.IP : SocketOptionLevel.IPv6,
                SocketOptionName.MulticastTimeToLive,
                timeToLive);
        }
 
        // Leaves a multicast address group.
        public void DropMulticastGroup(IPAddress multicastAddr)
        {
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(multicastAddr);
            if (multicastAddr.AddressFamily != _family)
            {
                // For IPv6, we need to create the correct MulticastOption and must also check for address family compatibility.
                throw new ArgumentException(SR.Format(SR.net_protocol_invalid_multicast_family, "UDP"), nameof(multicastAddr));
            }
 
            if (_family == AddressFamily.InterNetwork)
            {
                _clientSocket.SetSocketOption(
                    SocketOptionLevel.IP,
                    SocketOptionName.DropMembership,
                    new MulticastOption(multicastAddr));
            }
            else
            {
                _clientSocket.SetSocketOption(
                    SocketOptionLevel.IPv6,
                    SocketOptionName.DropMembership,
                    new IPv6MulticastOption(multicastAddr));
            }
        }
 
        // Leaves an IPv6 multicast address group.
        public void DropMulticastGroup(IPAddress multicastAddr, int ifindex)
        {
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(multicastAddr);
            ArgumentOutOfRangeException.ThrowIfNegative(ifindex);
            if (_family != AddressFamily.InterNetworkV6)
            {
                // Ensure that this is an IPv6 client.
                throw new SocketException((int)SocketError.OperationNotSupported);
            }
 
            _clientSocket.SetSocketOption(
                SocketOptionLevel.IPv6,
                SocketOptionName.DropMembership,
                new IPv6MulticastOption(multicastAddr, ifindex));
        }
 
        public Task<int> SendAsync(byte[] datagram, int bytes) =>
            SendAsync(datagram, bytes, null);
 
        /// <summary>
        /// Sends a UDP datagram asynchronously to a remote host.
        /// </summary>
        /// <param name="datagram">
        /// An <see cref="ReadOnlyMemory{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
        /// </param>
        /// <param name="cancellationToken">
        /// The token to monitor for cancellation requests. The default value is None.
        /// </param>
        /// <returns>A <see cref="ValueTask{T}"/> that represents the asynchronous send operation. The value of its Result property contains the number of bytes sent.</returns>
        /// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
        /// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
        public ValueTask<int> SendAsync(ReadOnlyMemory<byte> datagram, CancellationToken cancellationToken = default) =>
            SendAsync(datagram, null, cancellationToken);
 
        public Task<int> SendAsync(byte[] datagram, int bytes, string? hostname, int port) =>
            SendAsync(datagram, bytes, GetEndpoint(hostname, port));
 
        /// <summary>
        /// Sends a UDP datagram asynchronously to a remote host.
        /// </summary>
        /// <param name="datagram">
        /// An <see cref="ReadOnlyMemory{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
        /// </param>
        /// <param name="hostname">
        /// The name of the remote host to which you intend to send the datagram.
        /// </param>
        /// <param name="port">
        /// The remote port number with which you intend to communicate.
        /// </param>
        /// <param name="cancellationToken">
        /// The token to monitor for cancellation requests. The default value is None.
        /// </param>
        /// <returns>A <see cref="ValueTask{T}"/> that represents the asynchronous send operation. The value of its Result property contains the number of bytes sent.</returns>
        /// <exception cref="InvalidOperationException">The <see cref="UdpClient"/> has already established a default remote host.</exception>
        /// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
        /// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
        public ValueTask<int> SendAsync(ReadOnlyMemory<byte> datagram, string? hostname, int port, CancellationToken cancellationToken = default) =>
            SendAsync(datagram, GetEndpoint(hostname, port), cancellationToken);
 
        public Task<int> SendAsync(byte[] datagram, int bytes, IPEndPoint? endPoint)
        {
            ValidateDatagram(datagram, bytes, endPoint);
 
            if (endPoint is null)
            {
                return _clientSocket.SendAsync(new ArraySegment<byte>(datagram, 0, bytes), SocketFlags.None);
            }
            else
            {
                CheckForBroadcast(endPoint.Address);
                return _clientSocket.SendToAsync(new ArraySegment<byte>(datagram, 0, bytes), SocketFlags.None, endPoint);
            }
        }
 
        /// <summary>
        /// Sends a UDP datagram asynchronously to a remote host.
        /// </summary>
        /// <param name="datagram">
        /// An <see cref="ReadOnlyMemory{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
        /// </param>
        /// <param name="endPoint">
        /// An <see cref="IPEndPoint"/> that represents the host and port to which to send the datagram.
        /// </param>
        /// <param name="cancellationToken">
        /// The token to monitor for cancellation requests. The default value is None.
        /// </param>
        /// <returns>A <see cref="ValueTask{T}"/> that represents the asynchronous send operation. The value of its Result property contains the number of bytes sent.</returns>
        /// <exception cref="InvalidOperationException"><see cref="UdpClient"/> has already established a default remote host and <paramref name="endPoint"/> is not <see langword="null"/>.</exception>
        /// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
        /// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
        public ValueTask<int> SendAsync(ReadOnlyMemory<byte> datagram, IPEndPoint? endPoint, CancellationToken cancellationToken = default)
        {
            ThrowIfDisposed();
 
            if (endPoint is null)
            {
                return _clientSocket.SendAsync(datagram, SocketFlags.None, cancellationToken);
            }
            if (_active)
            {
                // Do not allow sending packets to arbitrary host when connected.
                throw new InvalidOperationException(SR.net_udpconnected);
            }
            CheckForBroadcast(endPoint.Address);
            return _clientSocket.SendToAsync(datagram, SocketFlags.None, endPoint, cancellationToken);
        }
 
        public Task<UdpReceiveResult> ReceiveAsync()
        {
            ThrowIfDisposed();
 
            return WaitAndWrap(_clientSocket.ReceiveFromAsync(
                new ArraySegment<byte>(_buffer, 0, MaxUDPSize),
                SocketFlags.None,
                _family == AddressFamily.InterNetwork ? IPEndPointStatics.Any : IPEndPointStatics.IPv6Any));
 
            async Task<UdpReceiveResult> WaitAndWrap(Task<SocketReceiveFromResult> task)
            {
                SocketReceiveFromResult result = await task.ConfigureAwait(false);
                byte[] buffer = _buffer.AsSpan(0, result.ReceivedBytes).ToArray();
                return new UdpReceiveResult(buffer, (IPEndPoint)result.RemoteEndPoint);
            }
        }
 
        /// <summary>
        /// Returns a UDP datagram asynchronously that was sent by a remote host.
        /// </summary>
        /// <param name="cancellationToken">
        /// The token to monitor for cancellation requests.
        /// </param>
        /// <returns>A <see cref="ValueTask{TResult}"/> representing the asynchronous operation.</returns>
        /// <exception cref="ObjectDisposedException">The underlying <see cref="Socket"/> has been closed.</exception>
        /// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
        public ValueTask<UdpReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
        {
            ThrowIfDisposed();
 
            return WaitAndWrap(_clientSocket.ReceiveFromAsync(
                _buffer,
                SocketFlags.None,
                _family == AddressFamily.InterNetwork ? IPEndPointStatics.Any : IPEndPointStatics.IPv6Any, cancellationToken));
 
            async ValueTask<UdpReceiveResult> WaitAndWrap(ValueTask<SocketReceiveFromResult> task)
            {
                SocketReceiveFromResult result = await task.ConfigureAwait(false);
                byte[] buffer = _buffer.AsSpan(0, result.ReceivedBytes).ToArray();
                return new UdpReceiveResult(buffer, (IPEndPoint)result.RemoteEndPoint);
            }
        }
 
        private void CreateClientSocket()
        {
            // Common initialization code.
            //
            // IPv6 Changes: Use the AddressFamily of this class rather than hardcode.
            _clientSocket = new Socket(_family, SocketType.Dgram, ProtocolType.Udp);
        }
 
        public UdpClient(string hostname, int port)
        {
            ArgumentNullException.ThrowIfNull(hostname);
            if (!TcpValidationHelpers.ValidatePortNumber(port))
            {
                throw new ArgumentOutOfRangeException(nameof(port));
            }
 
            // NOTE: Need to create different kinds of sockets based on the addresses
            //       returned from DNS. As a result, we defer the creation of the
            //       socket until the Connect method.
 
            Connect(hostname, port);
        }
 
        public void Close()
        {
            Dispose(true);
        }
 
        public void Connect(string hostname, int port)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(hostname);
            if (!TcpValidationHelpers.ValidatePortNumber(port))
            {
                throw new ArgumentOutOfRangeException(nameof(port));
            }
 
            // We must now look for addresses that use a compatible address family to the client socket. However, in the
            // case of the <hostname,port> constructor we will have deferred creating the socket and will do that here
            // instead.
 
            IPAddress[] addresses = Dns.GetHostAddresses(hostname);
 
            Exception? lastex = null;
            Socket? ipv6Socket = null;
            Socket? ipv4Socket = null;
 
            try
            {
                if (_clientSocket == null)
                {
                    if (Socket.OSSupportsIPv4)
                    {
                        ipv4Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                    }
                    if (Socket.OSSupportsIPv6)
                    {
                        ipv6Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
                    }
                }
 
 
                foreach (IPAddress address in addresses)
                {
                    try
                    {
                        if (_clientSocket == null)
                        {
                            // We came via the <hostname,port> constructor. Set the
                            // address family appropriately, create the socket and
                            // try to connect.
                            if (address.AddressFamily == AddressFamily.InterNetwork && ipv4Socket != null)
                            {
                                ipv4Socket.Connect(address, port);
                                _clientSocket = ipv4Socket;
                                ipv6Socket?.Close();
                            }
                            else if (ipv6Socket != null)
                            {
                                ipv6Socket.Connect(address, port);
                                _clientSocket = ipv6Socket;
                                ipv4Socket?.Close();
                            }
 
 
                            _family = address.AddressFamily;
                            _active = true;
                            break;
                        }
                        else if (IsAddressFamilyCompatible(address.AddressFamily))
                        {
                            // Only use addresses with a matching family
                            Connect(new IPEndPoint(address, port));
                            _active = true;
                            break;
                        }
                    }
                    catch (Exception ex)
                    {
                        if (ExceptionCheck.IsFatal(ex))
                        {
                            throw;
                        }
                        lastex = ex;
                    }
                }
            }
 
            catch (Exception ex)
            {
                if (ExceptionCheck.IsFatal(ex))
                {
                    throw;
                }
                lastex = ex;
            }
            finally
            {
                //cleanup temp sockets if failed
                //main socket gets closed when tcpclient gets closed
 
                //did we connect?
                if (!_active)
                {
                    ipv6Socket?.Close();
                    ipv4Socket?.Close();
 
                    // The connect failed - rethrow the last error we had
                    if (lastex != null)
                    {
                        throw lastex;
                    }
                    else
                    {
                        throw new SocketException((int)SocketError.NotConnected);
                    }
                }
            }
        }
 
        public void Connect(IPAddress addr, int port)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(addr);
            if (!TcpValidationHelpers.ValidatePortNumber(port))
            {
                throw new ArgumentOutOfRangeException(nameof(port));
            }
 
            IPEndPoint endPoint = new IPEndPoint(addr, port);
 
            Connect(endPoint);
        }
 
        public void Connect(IPEndPoint endPoint)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(endPoint);
 
            CheckForBroadcast(endPoint.Address);
            Client.Connect(endPoint);
            _active = true;
        }
 
        public byte[] Receive([NotNull] ref IPEndPoint? remoteEP)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            // this is a fix due to the nature of the ReceiveFrom() call and the
            // ref parameter convention, we need to cast an IPEndPoint to it's base
            // class EndPoint and cast it back down to IPEndPoint. ugly but it works.
            EndPoint tempRemoteEP = _family == AddressFamily.InterNetwork ?
                IPEndPointStatics.Any :
                IPEndPointStatics.IPv6Any;
 
            int received = Client.ReceiveFrom(_buffer, MaxUDPSize, 0, ref tempRemoteEP);
            remoteEP = (IPEndPoint)tempRemoteEP;
 
            // because we don't return the actual length, we need to ensure the returned buffer
            // has the appropriate length.
            return _buffer.AsSpan(0, received).ToArray();
        }
 
 
        // Sends a UDP datagram to the host at the remote end point.
        public int Send(byte[] dgram, int bytes, IPEndPoint? endPoint)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(dgram);
            if (_active && endPoint != null)
            {
                // Do not allow sending packets to arbitrary host when connected
                throw new InvalidOperationException(SR.net_udpconnected);
            }
 
            if (endPoint == null)
            {
                return Client.Send(dgram, 0, bytes, SocketFlags.None);
            }
 
            CheckForBroadcast(endPoint.Address);
 
            return Client.SendTo(dgram, 0, bytes, SocketFlags.None, endPoint);
        }
 
        /// <summary>
        /// Sends a UDP datagram to the host at the specified remote endpoint.
        /// </summary>
        /// <param name="datagram">
        /// An <see cref="ReadOnlySpan{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
        /// </param>
        /// <param name="endPoint">
        /// An <see cref="IPEndPoint"/> that represents the host and port to which to send the datagram.
        /// </param>
        /// <returns>The number of bytes sent.</returns>
        /// <exception cref="InvalidOperationException"><see cref="UdpClient"/> has already established a default remote host and <paramref name="endPoint"/> is not <see langword="null"/>.</exception>
        /// <exception cref="ObjectDisposedException"><see cref="UdpClient"/> is closed.</exception>
        /// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
        public int Send(ReadOnlySpan<byte> datagram, IPEndPoint? endPoint)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            if (_active && endPoint != null)
            {
                // Do not allow sending packets to arbitrary host when connected
                throw new InvalidOperationException(SR.net_udpconnected);
            }
 
            if (endPoint == null)
            {
                return Client.Send(datagram, SocketFlags.None);
            }
 
            CheckForBroadcast(endPoint.Address);
 
            return Client.SendTo(datagram, SocketFlags.None, endPoint);
        }
 
        // Sends a UDP datagram to the specified port on the specified remote host.
        public int Send(byte[] dgram, int bytes, string? hostname, int port) => Send(dgram, bytes, GetEndpoint(hostname, port));
 
        /// <summary>
        /// Sends a UDP datagram to a specified port on a specified remote host.
        /// </summary>
        /// <param name="datagram">
        /// An <see cref="ReadOnlySpan{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
        /// </param>
        /// <param name="hostname">
        /// The name of the remote host to which you intend to send the datagram.
        /// </param>
        /// <param name="port">
        /// The remote port number with which you intend to communicate.
        /// </param>
        /// <returns>The number of bytes sent.</returns>
        /// <exception cref="InvalidOperationException">The <see cref="UdpClient"/> has already established a default remote host.</exception>
        /// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
        /// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
        public int Send(ReadOnlySpan<byte> datagram, string? hostname, int port) => Send(datagram, GetEndpoint(hostname, port));
 
        // Sends a UDP datagram to a remote host.
        public int Send(byte[] dgram, int bytes)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            ArgumentNullException.ThrowIfNull(dgram);
            if (!_active)
            {
                // only allowed on connected socket
                throw new InvalidOperationException(SR.net_notconnected);
            }
 
            return Client.Send(dgram, 0, bytes, SocketFlags.None);
        }
 
        /// <summary>
        /// Sends a UDP datagram to a remote host.
        /// </summary>
        /// <param name="datagram">
        /// An <see cref="ReadOnlySpan{T}"/> of Type <see cref="byte"/> that specifies the UDP datagram that you intend to send.
        /// </param>
        /// <returns>The number of bytes sent.</returns>
        /// <exception cref="InvalidOperationException">The <see cref="UdpClient"/> has not established a default remote host.</exception>
        /// <exception cref="ObjectDisposedException">The <see cref="UdpClient"/> is closed.</exception>
        /// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
        public int Send(ReadOnlySpan<byte> datagram)
        {
            if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
 
            ThrowIfDisposed();
 
            if (!_active)
            {
                // only allowed on connected socket
                throw new InvalidOperationException(SR.net_notconnected);
            }
 
            return Client.Send(datagram, SocketFlags.None);
        }
 
        private void ThrowIfDisposed()
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
        }
    }
}