|
// 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.IO;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.NetworkInformation
{
public partial class Ping
{
private Process GetPingProcess(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
{
bool isIpv4 = address.AddressFamily == AddressFamily.InterNetwork;
string? pingExecutable = isIpv4 ? UnixCommandLinePing.Ping4UtilityPath : UnixCommandLinePing.Ping6UtilityPath;
if (pingExecutable == null)
{
throw new PlatformNotSupportedException(SR.net_ping_utility_not_found);
}
// although the ping utility supports custom pattern via -p option, it supports
// specifying only up to 16B pattern which repeats in the payload. The option also might
// not be present in all distributions, so we forbid ping payload in general.
if (buffer != DefaultSendBuffer && buffer != Array.Empty<byte>())
{
throw new PlatformNotSupportedException(SR.net_ping_utility_custom_payload);
}
UnixCommandLinePing.PingFragmentOptions fragmentOption = UnixCommandLinePing.PingFragmentOptions.Default;
if (options != null && address.AddressFamily == AddressFamily.InterNetwork)
{
fragmentOption = options.DontFragment ? UnixCommandLinePing.PingFragmentOptions.Do : UnixCommandLinePing.PingFragmentOptions.Dont;
}
string processArgs = UnixCommandLinePing.ConstructCommandLine(buffer.Length, timeout, address.ToString(), isIpv4, options?.Ttl ?? 0, fragmentOption);
ProcessStartInfo psi = new ProcessStartInfo(pingExecutable, processArgs);
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
// Set LC_ALL=C to make sure to get ping output which is not affected by locale environment variables such as LANG and LC_MESSAGES.
psi.EnvironmentVariables["LC_ALL"] = "C";
return new Process() { StartInfo = psi };
}
private PingReply SendWithPingUtility(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
{
using (Process p = GetPingProcess(address, buffer, timeout, options))
{
p.Start();
if (!p.WaitForExit(timeout))
{
return CreatePingReply(IPStatus.TimedOut);
}
try
{
string stdout = p.StandardOutput.ReadToEnd();
return ParsePingUtilityOutput(address, p.ExitCode, stdout);
}
catch (Exception)
{
// If the standard output cannot be successfully parsed, throw a generic PingException.
throw new PingException(SR.net_ping);
}
}
}
private async Task<PingReply> SendWithPingUtilityAsync(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
{
CancellationToken timeoutOrCancellationToken = _timeoutOrCancellationSource!.Token;
using Process pingProcess = GetPingProcess(address, buffer, timeout, options);
pingProcess.Start();
try
{
await pingProcess.WaitForExitAsync(timeoutOrCancellationToken).ConfigureAwait(false);
string stdout = await pingProcess.StandardOutput.ReadToEndAsync(timeoutOrCancellationToken).ConfigureAwait(false);
return ParsePingUtilityOutput(address, pingProcess.ExitCode, stdout);
}
catch (OperationCanceledException) when (timeoutOrCancellationToken.IsCancellationRequested)
{
if (!pingProcess.HasExited)
{
pingProcess.Kill();
}
if (_canceled)
{
throw;
}
return CreatePingReply(IPStatus.TimedOut);
}
catch (Exception e)
{
// If the standard output cannot be successfully read/parsed, throw a generic PingException.
throw new PingException(SR.net_ping, e);
}
}
private static PingReply ParsePingUtilityOutput(IPAddress address, int exitCode, string stdout)
{
// Throw timeout for known failure return codes from ping functions.
if (exitCode == 1 || exitCode == 2)
{
// TTL exceeded may have occurred
if (TryParseTtlExceeded(stdout, out PingReply? reply))
{
return reply!;
}
// otherwise assume timeout
return CreatePingReply(IPStatus.TimedOut);
}
// On success, report RTT
long rtt = UnixCommandLinePing.ParseRoundTripTime(stdout);
return CreatePingReply(IPStatus.Success, address, rtt);
}
private static bool TryParseTtlExceeded(string stdout, out PingReply? reply)
{
reply = null;
if (!stdout.Contains("Time to live exceeded", StringComparison.Ordinal))
{
return false;
}
// look for address in:
// - "From 172.21.64.1 icmp_seq=1 Time to live exceeded"
// - "From 172.21.64.1: icmp_seq=1 Time to live exceeded"
int addressStart = stdout.IndexOf("From ", StringComparison.Ordinal) + 5;
int addressLength = stdout.AsSpan(Math.Max(addressStart, 0)).IndexOfAny(' ', ':');
IPAddress? address;
if (addressStart < 5 || addressLength <= 0 || !IPAddress.TryParse(stdout.AsSpan(addressStart, addressLength), out address))
{
// failed to parse source address (which in case of TTL is different than the original
// destination address), fallback to all 0
address = new IPAddress(0);
}
reply = CreatePingReply(IPStatus.TimeExceeded, address);
return true;
}
}
}
|