File: System\Net\NetworkInformation\Ping.cs
Web Access
Project: src\src\libraries\System.Net.Ping\src\System.Net.Ping.csproj (System.Net.Ping)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Net.NetworkInformation
{
    public partial class Ping : Component
    {
        private const int DefaultSendBufferSize = 32;  // Same as ping.exe on Windows.
        private const int DefaultTimeout = 5000;       // 5 seconds: same as ping.exe on Windows.
        private const int MaxBufferSize = 65500;       // Artificial constraint due to win32 api limitations.
 
        private readonly ManualResetEventSlim _lockObject = new ManualResetEventSlim(initialState: true); // doubles as the ability to wait on the current operation
        private SendOrPostCallback? _onPingCompletedDelegate;
        private bool _disposeRequested;
        private byte[]? _defaultSendBuffer;
        private CancellationTokenSource? _timeoutOrCancellationSource;
        // Used to differentiate between timeout and cancellation when _timeoutOrCancellationSource triggers
        private bool _canceled;
 
        // Thread safety:
        private const int Free = 0;
        private const int InProgress = 1;
        private new const int Disposed = 2;
        private int _status = Free;
 
        public Ping()
        {
            // This class once inherited a finalizer. For backward compatibility it has one so that
            // any derived class that depends on it will see the behaviour expected. Since it is
            // not used by this class itself, suppress it immediately if this is not an instance
            // of a derived class it doesn't suffer the GC burden of finalization.
            if (GetType() == typeof(Ping))
            {
                GC.SuppressFinalize(this);
            }
        }
 
        private void CheckArgs(int timeout, byte[] buffer)
        {
            ObjectDisposedException.ThrowIf(_disposeRequested, this);
            ArgumentNullException.ThrowIfNull(buffer);
 
            if (buffer.Length > MaxBufferSize)
            {
                throw new ArgumentException(SR.net_invalidPingBufferSize, nameof(buffer));
            }
 
            ArgumentOutOfRangeException.ThrowIfNegative(timeout);
        }
 
        private void CheckArgs(IPAddress address, int timeout, byte[] buffer)
        {
            CheckArgs(timeout, buffer);
 
            ArgumentNullException.ThrowIfNull(address);
 
            // Check if address family is installed.
            TestIsIpSupported(address);
 
            if (address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any))
            {
                throw new ArgumentException(SR.net_invalid_ip_addr, nameof(address));
            }
        }
 
        private void CheckDisposed()
        {
            ObjectDisposedException.ThrowIf(_disposeRequested, this);
        }
 
        [MemberNotNull(nameof(_timeoutOrCancellationSource))]
        private void CheckStart()
        {
            int currentStatus;
            lock (_lockObject)
            {
                currentStatus = _status;
                if (currentStatus == Free)
                {
                    _timeoutOrCancellationSource ??= new();
                    _canceled = false;
                    _status = InProgress;
                    _lockObject.Reset();
                    return;
                }
            }
 
            if (currentStatus == InProgress)
            {
                throw new InvalidOperationException(SR.net_inasync);
            }
            else
            {
                Debug.Assert(currentStatus == Disposed, $"Expected currentStatus == Disposed, got {currentStatus}");
                throw new ObjectDisposedException(GetType().FullName);
            }
        }
 
        private static IPAddress GetAddressSnapshot(IPAddress address)
        {
            IPAddress addressSnapshot = address.AddressFamily == AddressFamily.InterNetwork ?
#pragma warning disable CS0618 // IPAddress.Address is obsoleted, but it's the most efficient way to get the Int32 IPv4 address
                new IPAddress(address.Address) :
#pragma warning restore CS0618
                new IPAddress(address.GetAddressBytes(), address.ScopeId);
 
            return addressSnapshot;
        }
 
        private void Finish()
        {
            lock (_lockObject)
            {
                Debug.Assert(_status == InProgress, $"Invalid status: {_status}");
                _status = Free;
                if (!_timeoutOrCancellationSource!.TryReset())
                {
                    _timeoutOrCancellationSource = null;
                }
                _lockObject.Set();
            }
 
            if (_disposeRequested)
            {
                InternalDispose();
            }
        }
 
        private void InternalDispose()
        {
            _disposeRequested = true;
 
            lock (_lockObject)
            {
                if (_status != Free)
                {
                    // Already disposed, or Finish will call Dispose again once Free.
                    return;
                }
                _status = Disposed;
            }
 
            _timeoutOrCancellationSource?.Dispose();
 
            InternalDisposeCore();
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Only on explicit dispose.  Otherwise, the GC can cleanup everything else.
                InternalDispose();
            }
        }
 
        public event PingCompletedEventHandler? PingCompleted;
 
        protected void OnPingCompleted(PingCompletedEventArgs e)
        {
            PingCompleted?.Invoke(this, e);
        }
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the specified computer,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// A <see cref="string"/> that identifies the computer that is the destination for the ICMP echo message.
        /// The value specified for this parameter can be a host name or a string representation of an IP address.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="hostNameOrAddress"/> is <see langword="null"/> or is an empty string ("").</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(string hostNameOrAddress)
        {
            return Send(hostNameOrAddress, DefaultTimeout, DefaultSendBuffer);
        }
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the specified computer,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// A <see cref="string"/> that identifies the computer that is the destination for the ICMP echo message.
        /// The value specified for this parameter can be a host name or a string representation of an IP address.
        /// </param>
        /// <param name="timeout">
        /// An <see cref="int"/> value that specifies the maximum number of milliseconds (after sending the echo message) to wait for the ICMP echo reply message.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="hostNameOrAddress"/> is <see langword="null"/> or is an empty string ("").</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is less than zero.</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(string hostNameOrAddress, int timeout)
        {
            return Send(hostNameOrAddress, timeout, DefaultSendBuffer);
        }
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the computer that has the specified <see cref="IPAddress"/>,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="address">
        /// An <see cref="IPAddress"/> that identifies the computer that is the destination for the ICMP echo message.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="address"/> is <see langword="null"/>.</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(IPAddress address)
        {
            return Send(address, DefaultTimeout, DefaultSendBuffer);
        }
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the computer that has the specified <see cref="IPAddress"/>,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="address">
        /// An <see cref="IPAddress"/> that identifies the computer that is the destination for the ICMP echo message.
        /// </param>
        /// <param name="timeout">
        /// An <see cref="int"/> value that specifies the maximum number of milliseconds (after sending the echo message) to wait for the ICMP echo reply message.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="address"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is less than zero.</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(IPAddress address, int timeout)
        {
            return Send(address, timeout, DefaultSendBuffer);
        }
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the specified computer,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// A <see cref="string"/> that identifies the computer that is the destination for the ICMP echo message.
        /// The value specified for this parameter can be a host name or a string representation of an IP address.
        /// </param>
        /// <param name="timeout">
        /// An <see cref="int"/> value that specifies the maximum number of milliseconds (after sending the echo message) to wait for the ICMP echo reply message.
        /// </param>
        /// <param name="buffer">
        /// A <see cref="byte"/> array that contains data to be sent with the ICMP echo message and returned in the ICMP echo reply message.
        /// The array cannot contain more than 65,500 bytes.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="hostNameOrAddress"/> is <see langword="null"/> or is an empty string ("").</exception>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is less than zero.</exception>
        /// <exception cref="ArgumentException">The <paramref name="buffer"/>'s size is greater than 65,500 bytes.</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(string hostNameOrAddress, int timeout, byte[] buffer)
        {
            return Send(hostNameOrAddress, timeout, buffer, null);
        }
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the computer that has the specified <see cref="IPAddress"/>,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="address">
        /// An <see cref="IPAddress"/> that identifies the computer that is the destination for the ICMP echo message.
        /// </param>
        /// <param name="timeout">
        /// An <see cref="int"/> value that specifies the maximum number of milliseconds (after sending the echo message) to wait for the ICMP echo reply message.
        /// </param>
        /// <param name="buffer">
        /// A <see cref="byte"/> array that contains data to be sent with the ICMP echo message and returned in the ICMP echo reply message.
        /// The array cannot contain more than 65,500 bytes.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="address"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is less than zero.</exception>
        /// <exception cref="ArgumentException">The <paramref name="buffer"/>'s size is greater than 65,500 bytes.</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(IPAddress address, int timeout, byte[] buffer)
        {
            return Send(address, timeout, buffer, null);
        }
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the specified computer,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// A <see cref="string"/> that identifies the computer that is the destination for the ICMP echo message.
        /// The value specified for this parameter can be a host name or a string representation of an IP address.
        /// </param>
        /// <param name="timeout">
        /// An <see cref="int"/> value that specifies the maximum number of milliseconds (after sending the echo message) to wait for the ICMP echo reply message.
        /// </param>
        /// <param name="buffer">
        /// A <see cref="byte"/> array that contains data to be sent with the ICMP echo message and returned in the ICMP echo reply message.
        /// The array cannot contain more than 65,500 bytes.
        /// </param>
        /// <param name="options">
        /// A <see cref="PingOptions"/> object used to control fragmentation and Time-to-Live values for the ICMP echo message packet.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="hostNameOrAddress"/> is <see langword="null"/> or is an empty string ("").</exception>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is less than zero.</exception>
        /// <exception cref="ArgumentException">The <paramref name="buffer"/>'s size is greater than 65,500 bytes.</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(string hostNameOrAddress, int timeout, byte[] buffer, PingOptions? options)
        {
            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                throw new ArgumentNullException(nameof(hostNameOrAddress));
            }
 
            if (IPAddress.TryParse(hostNameOrAddress, out IPAddress? address))
            {
                return Send(address, timeout, buffer, options);
            }
 
            CheckArgs(timeout, buffer);
 
            return GetAddressAndSend(hostNameOrAddress, timeout, buffer, options);
        }
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the computer that has the specified <see cref="IPAddress"/>,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="address">
        /// An <see cref="IPAddress"/> that identifies the computer that is the destination for the ICMP echo message.
        /// </param>
        /// <param name="timeout">
        /// An <see cref="int"/> value that specifies the maximum number of milliseconds (after sending the echo message) to wait for the ICMP echo reply message.
        /// </param>
        /// <param name="buffer">
        /// A <see cref="byte"/> array that contains data to be sent with the ICMP echo message and returned in the ICMP echo reply message.
        /// The array cannot contain more than 65,500 bytes.
        /// </param>
        /// <param name="options">
        /// A <see cref="PingOptions"/> object used to control fragmentation and Time-to-Live values for the ICMP echo message packet.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="address"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is less than zero.</exception>
        /// <exception cref="ArgumentException">The <paramref name="buffer"/>'s size is greater than 65,500 bytes.</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(IPAddress address, int timeout, byte[] buffer, PingOptions? options)
        {
            CheckArgs(address, timeout, buffer);
 
            // Need to snapshot the address here, so we're sure that it's not changed between now
            // and the operation, and to be sure that IPAddress.ToString() is called and not some override.
            IPAddress addressSnapshot = GetAddressSnapshot(address);
 
            CheckStart();
            try
            {
                return SendPingCore(addressSnapshot, buffer, timeout, options);
            }
            catch (Exception e) when (e is not PlatformNotSupportedException)
            {
                throw new PingException(SR.net_ping, e);
            }
            finally
            {
                Finish();
            }
        }
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the computer that has the specified <see cref="IPAddress"/>,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="address">
        /// An <see cref="IPAddress"/> that identifies the computer that is the destination for the ICMP echo message.
        /// </param>
        /// <param name="timeout">
        /// A value that specifies the maximum amount of time (after sending the echo message) to wait for the ICMP echo reply message.
        /// </param>
        /// <param name="buffer">
        /// A <see cref="byte"/> array that contains data to be sent with the ICMP echo message and returned in the ICMP echo reply message.
        /// The array cannot contain more than 65,500 bytes.
        /// </param>
        /// <param name="options">
        /// A <see cref="PingOptions"/> object used to control fragmentation and Time-to-Live values for the ICMP echo message packet.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="address"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> represents a time less than zero milliseconds or greater than <see cref="int.MaxValue"/> milliseconds.</exception>
        /// <exception cref="ArgumentException">The <paramref name="buffer"/>'s size is greater than 65,500 bytes.</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(IPAddress address, TimeSpan timeout, byte[]? buffer = null, PingOptions? options = null) =>
            Send(address, ToTimeoutMilliseconds(timeout), buffer ?? DefaultSendBuffer, options);
 
        /// <summary>
        /// Attempts to send an Internet Control Message Protocol (ICMP) echo message to the specified computer,
        /// and receive a corresponding ICMP echo reply message from that computer.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// A <see cref="string"/> that identifies the computer that is the destination for the ICMP echo message.
        /// The value specified for this parameter can be a host name or a string representation of an IP address.
        /// </param>
        /// <param name="timeout">
        /// A value that specifies the maximum amount of time (after sending the echo message) to wait for the ICMP echo reply message.
        /// </param>
        /// <param name="buffer">
        /// A <see cref="byte"/> array that contains data to be sent with the ICMP echo message and returned in the ICMP echo reply message.
        /// The array cannot contain more than 65,500 bytes.
        /// </param>
        /// <param name="options">
        /// A <see cref="PingOptions"/> object used to control fragmentation and Time-to-Live values for the ICMP echo message packet.
        /// </param>
        /// <returns>
        /// A <see cref="PingReply"/> object that provides information about the ICMP echo reply message, if one was received,
        /// or provides the reason for the failure, if no message was received.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="hostNameOrAddress"/> is <see langword="null"/> or is an empty string ("").</exception>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> represents a time less than zero milliseconds or greater than <see cref="int.MaxValue"/> milliseconds.</exception>
        /// <exception cref="ArgumentException">The <paramref name="buffer"/>'s size is greater than 65,500 bytes.</exception>
        /// <exception cref="InvalidOperationException">A call to SendAsync is in progress.</exception>
        /// <exception cref="PingException">An exception was thrown while sending or receiving the ICMP messages. See the inner exception for the exact exception that was thrown.</exception>
        /// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
        public PingReply Send(string hostNameOrAddress, TimeSpan timeout, byte[]? buffer = null,
            PingOptions? options = null) => Send(hostNameOrAddress, ToTimeoutMilliseconds(timeout), buffer ?? DefaultSendBuffer, options);
 
        public void SendAsync(string hostNameOrAddress, object? userToken)
        {
            SendAsync(hostNameOrAddress, DefaultTimeout, DefaultSendBuffer, userToken);
        }
 
        public void SendAsync(string hostNameOrAddress, int timeout, object? userToken)
        {
            SendAsync(hostNameOrAddress, timeout, DefaultSendBuffer, userToken);
        }
 
        public void SendAsync(IPAddress address, object? userToken)
        {
            SendAsync(address, DefaultTimeout, DefaultSendBuffer, userToken);
        }
 
        public void SendAsync(IPAddress address, int timeout, object? userToken)
        {
            SendAsync(address, timeout, DefaultSendBuffer, userToken);
        }
 
        public void SendAsync(string hostNameOrAddress, int timeout, byte[] buffer, object? userToken)
        {
            SendAsync(hostNameOrAddress, timeout, buffer, null, userToken);
        }
 
        public void SendAsync(IPAddress address, int timeout, byte[] buffer, object? userToken)
        {
            SendAsync(address, timeout, buffer, null, userToken);
        }
 
        public void SendAsync(string hostNameOrAddress, int timeout, byte[] buffer, PingOptions? options, object? userToken)
        {
            TranslateTaskToEap(userToken, SendPingAsync(hostNameOrAddress, timeout, buffer, options));
        }
 
        public void SendAsync(IPAddress address, int timeout, byte[] buffer, PingOptions? options, object? userToken)
        {
            TranslateTaskToEap(userToken, SendPingAsync(address, timeout, buffer, options));
        }
 
        private void TranslateTaskToEap(object? userToken, Task<PingReply> pingTask)
        {
            pingTask.ContinueWith((t, state) =>
            {
                var asyncOp = (AsyncOperation)state!;
                var e = new PingCompletedEventArgs(t.IsCompletedSuccessfully ? t.Result : null, t.Exception, t.IsCanceled, asyncOp.UserSuppliedState);
                SendOrPostCallback callback = _onPingCompletedDelegate ??= new SendOrPostCallback(o => { OnPingCompleted((PingCompletedEventArgs)o!); });
                asyncOp.PostOperationCompleted(callback, e);
            }, AsyncOperationManager.CreateOperation(userToken), CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
        }
 
        public Task<PingReply> SendPingAsync(IPAddress address)
        {
            return SendPingAsync(address, DefaultTimeout, DefaultSendBuffer, null);
        }
 
        public Task<PingReply> SendPingAsync(string hostNameOrAddress)
        {
            return SendPingAsync(hostNameOrAddress, DefaultTimeout, DefaultSendBuffer, null);
        }
 
        public Task<PingReply> SendPingAsync(IPAddress address, int timeout)
        {
            return SendPingAsync(address, timeout, DefaultSendBuffer, null);
        }
 
        public Task<PingReply> SendPingAsync(string hostNameOrAddress, int timeout)
        {
            return SendPingAsync(hostNameOrAddress, timeout, DefaultSendBuffer, null);
        }
 
        public Task<PingReply> SendPingAsync(IPAddress address, int timeout, byte[] buffer)
        {
            return SendPingAsync(address, timeout, buffer, null);
        }
 
        public Task<PingReply> SendPingAsync(string hostNameOrAddress, int timeout, byte[] buffer)
        {
            return SendPingAsync(hostNameOrAddress, timeout, buffer, null);
        }
 
        public Task<PingReply> SendPingAsync(IPAddress address, int timeout, byte[] buffer, PingOptions? options)
        {
            return SendPingAsync(address, timeout, buffer, options, CancellationToken.None);
        }
 
        /// <summary>
        /// Sends an Internet Control Message Protocol (ICMP) echo message with the specified data buffer to the computer that has the specified
        /// <see cref="IPAddress"/>, and receives a corresponding ICMP echo reply message from that computer as an asynchronous operation. This
        /// overload allows you to specify a time-out value for the operation, a buffer to use for send and receive, control fragmentation and
        /// Time-to-Live values, and a <see cref="CancellationToken"/> for the ICMP echo message packet.
        /// </summary>
        /// <param name="address">An IP address that identifies the computer that is the destination for the ICMP echo message.</param>
        /// <param name="timeout">The amount of time (after sending the echo message) to wait for the ICMP echo reply message.</param>
        /// <param name="buffer">
        /// A <see cref="byte"/> array that contains data to be sent with the ICMP echo message and returned in the ICMP echo reply message.
        /// The array cannot contain more than 65,500 bytes.
        /// </param>
        /// <param name="options">A <see cref="PingOptions"/> object used to control fragmentation and Time-to-Live values for the ICMP echo message packet.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public Task<PingReply> SendPingAsync(IPAddress address, TimeSpan timeout, byte[]? buffer = null, PingOptions? options = null, CancellationToken cancellationToken = default)
        {
            return SendPingAsync(address, ToTimeoutMilliseconds(timeout), buffer ?? DefaultSendBuffer, options, cancellationToken);
        }
 
        private Task<PingReply> SendPingAsync(IPAddress address, int timeout, byte[] buffer, PingOptions? options, CancellationToken cancellationToken)
        {
            CheckArgs(address, timeout, buffer);
 
            return SendPingAsyncInternal(
                // Need to snapshot the address here, so we're sure that it's not changed between now
                // and the operation, and to be sure that IPAddress.ToString() is called and not some override.
                GetAddressSnapshot(address),
                static (address, cancellationToken) => new ValueTask<IPAddress>(address),
                timeout,
                buffer,
                options,
                cancellationToken);
        }
 
        public Task<PingReply> SendPingAsync(string hostNameOrAddress, int timeout, byte[] buffer, PingOptions? options)
        {
            return SendPingAsync(hostNameOrAddress, timeout, buffer, options, CancellationToken.None);
        }
 
        /// <summary>
        /// Sends an Internet Control Message Protocol (ICMP) echo message with the specified data buffer to the specified computer, and
        /// receives a corresponding ICMP echo reply message from that computer as an asynchronous operation. This overload allows you to
        /// specify a time-out value for the operation, a buffer to use for send and receive, control fragmentation and Time-to-Live values,
        /// and a <see cref="CancellationToken"/> for the ICMP echo message packet.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// The computer that is the destination for the ICMP echo message. The value specified for this parameter can be a host name or a
        /// string representation of an IP address.
        /// </param>
        /// <param name="timeout">The amount of time (after sending the echo message) to wait for the ICMP echo reply message.</param>
        /// <param name="buffer">
        /// A <see cref="byte"/> array that contains data to be sent with the ICMP echo message and returned in the ICMP echo reply message.
        /// The array cannot contain more than 65,500 bytes.
        /// </param>
        /// <param name="options">A <see cref="PingOptions"/> object used to control fragmentation and Time-to-Live values for the ICMP echo message packet.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public Task<PingReply> SendPingAsync(string hostNameOrAddress, TimeSpan timeout, byte[]? buffer = null, PingOptions? options = null, CancellationToken cancellationToken = default)
        {
            return SendPingAsync(hostNameOrAddress, ToTimeoutMilliseconds(timeout), buffer ?? DefaultSendBuffer, options, cancellationToken);
        }
 
        private Task<PingReply> SendPingAsync(string hostNameOrAddress, int timeout, byte[] buffer, PingOptions? options, CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                throw new ArgumentNullException(nameof(hostNameOrAddress));
            }
 
            if (IPAddress.TryParse(hostNameOrAddress, out IPAddress? address))
            {
                return SendPingAsync(address, timeout, buffer, options, cancellationToken);
            }
 
            CheckArgs(timeout, buffer);
 
            return SendPingAsyncInternal(
                hostNameOrAddress,
                static async (hostName, cancellationToken) =>
                    (await Dns.GetHostAddressesAsync(hostName, cancellationToken).ConfigureAwait(false))[0],
                timeout,
                buffer,
                options,
                cancellationToken);
        }
 
        private static int ToTimeoutMilliseconds(TimeSpan timeout)
        {
            long totalMilliseconds = (long)timeout.TotalMilliseconds;
 
            ArgumentOutOfRangeException.ThrowIfLessThan(totalMilliseconds, -1, nameof(timeout));
            ArgumentOutOfRangeException.ThrowIfGreaterThan(totalMilliseconds, int.MaxValue, nameof(timeout));
 
            return (int)totalMilliseconds;
        }
 
        public void SendAsyncCancel()
        {
            lock (_lockObject)
            {
                if (!_lockObject.IsSet)
                {
                    SetCanceled();
                }
            }
 
            // As in the .NET Framework, synchronously wait for the in-flight operation to complete.
            // If there isn't one in flight, this event will already be set.
            _lockObject.Wait();
        }
 
        private void SetCanceled()
        {
            _canceled = true;
            _timeoutOrCancellationSource?.Cancel();
        }
 
        private PingReply GetAddressAndSend(string hostNameOrAddress, int timeout, byte[] buffer, PingOptions? options)
        {
            CheckStart();
            try
            {
                IPAddress[] addresses = Dns.GetHostAddresses(hostNameOrAddress);
                return SendPingCore(addresses[0], buffer, timeout, options);
            }
            catch (Exception e) when (e is not PlatformNotSupportedException)
            {
                throw new PingException(SR.net_ping, e);
            }
            finally
            {
                Finish();
            }
        }
 
        private async Task<PingReply> SendPingAsyncInternal<TArg>(
            TArg getAddressArg,
            Func<TArg, CancellationToken, ValueTask<IPAddress>> getAddress,
            int timeout,
            byte[] buffer,
            PingOptions? options,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            CheckStart();
            try
            {
                using CancellationTokenRegistration _ = cancellationToken.UnsafeRegister(static state => ((Ping)state!).SetCanceled(), this);
 
                IPAddress address = await getAddress(getAddressArg, _timeoutOrCancellationSource.Token).ConfigureAwait(false);
 
                Task<PingReply> pingTask = SendPingAsyncCore(address, buffer, timeout, options);
                // Note: we set the cancellation-based timeout only after resolving the address and initiating the ping with the
                // intent that the timeout applies solely to the ping operation rather than to any setup steps.
                _timeoutOrCancellationSource.CancelAfter(timeout);
                return await pingTask.ConfigureAwait(false);
            }
            catch (Exception e) when (e is not PlatformNotSupportedException && !(e is OperationCanceledException && _canceled))
            {
                throw new PingException(SR.net_ping, e);
            }
            finally
            {
                Finish();
            }
        }
 
        // Tests if the current machine supports the given ip protocol family.
        private static void TestIsIpSupported(IPAddress ip)
        {
            if (ip.AddressFamily == AddressFamily.InterNetwork && !SocketProtocolSupportPal.OSSupportsIPv4)
            {
                throw new NotSupportedException(SR.net_ipv4_not_installed);
            }
            else if ((ip.AddressFamily == AddressFamily.InterNetworkV6 && !SocketProtocolSupportPal.OSSupportsIPv6))
            {
                throw new NotSupportedException(SR.net_ipv6_not_installed);
            }
        }
 
        partial void InternalDisposeCore();
 
        // Creates a default send buffer if a buffer wasn't specified.  This follows the ping.exe model.
        private byte[] DefaultSendBuffer
        {
            get
            {
                if (_defaultSendBuffer == null)
                {
                    _defaultSendBuffer = new byte[DefaultSendBufferSize];
                    for (int i = 0; i < DefaultSendBufferSize; i++)
                        _defaultSendBuffer[i] = (byte)((int)'a' + i % 23);
                }
                return _defaultSendBuffer;
            }
        }
    }
}