diff --git a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.PingUtility.cs b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.PingUtility.cs index dc9f5d2124633e..dc22063a5aa40b 100644 --- a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.PingUtility.cs +++ b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.PingUtility.cs @@ -53,15 +53,15 @@ private PingReply SendWithPingUtility(IPAddress address, byte[] buffer, int time using (Process p = GetPingProcess(address, buffer, timeout, options)) { p.Start(); - if (!p.WaitForExit(timeout) || p.ExitCode == 1 || p.ExitCode == 2) + if (!p.WaitForExit(timeout)) { return CreatePingReply(IPStatus.TimedOut); } try { - string output = p.StandardOutput.ReadToEnd(); - return ParsePingUtilityOutput(address, output); + string stdout = p.StandardOutput.ReadToEnd(); + return ParsePingUtilityOutput(address, p.ExitCode, stdout); } catch (Exception) { @@ -90,16 +90,10 @@ private async Task SendWithPingUtilityAsync(IPAddress address, byte[] return CreatePingReply(IPStatus.TimedOut); } - if (p.ExitCode == 1 || p.ExitCode == 2) - { - // Throw timeout for known failure return codes from ping functions. - return CreatePingReply(IPStatus.TimedOut); - } - try { - string output = await p.StandardOutput.ReadToEndAsync().ConfigureAwait(false); - return ParsePingUtilityOutput(address, output); + string stdout = await p.StandardOutput.ReadToEndAsync().ConfigureAwait(false); + return ParsePingUtilityOutput(address, p.ExitCode, stdout); } catch (Exception) { @@ -109,15 +103,47 @@ private async Task SendWithPingUtilityAsync(IPAddress address, byte[] } } - private PingReply ParsePingUtilityOutput(IPAddress address, string output) + private static PingReply ParsePingUtilityOutput(IPAddress address, int exitCode, string stdout) { - long rtt = UnixCommandLinePing.ParseRoundTripTime(output); - return new PingReply( - address, - null, // Ping utility cannot accommodate these, return null to indicate they were ignored. - IPStatus.Success, - rtt, - Array.Empty()); // Ping utility doesn't deliver this info. + // Throw timeout for known failure return codes from ping functions. + if (exitCode == 1 || exitCode == 2) + { + // TTL exceeded may have occured + 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" + int addressStart = stdout.IndexOf("From ", StringComparison.Ordinal) + 5; + int addressLength = stdout.IndexOf(' ', Math.Max(addressStart, 0)) - addressStart; + 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; } } } diff --git a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs index 94cdeb2fe890c6..28effe40d0729d 100644 --- a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs +++ b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs @@ -32,10 +32,10 @@ private unsafe SocketConfig GetSocketConfig(IPAddress address, byte[] buffer, in bool sendIpHeader = ipv4 && options != null && SendIpHeader; int totalLength = 0; - if (sendIpHeader) - { + if (sendIpHeader) + { iph.VersionAndLength = 0x45; - totalLength = sizeof(IpHeader) + checked(sizeof(IcmpHeader) + buffer.Length); + totalLength = sizeof(IpHeader) + checked(sizeof(IcmpHeader) + buffer.Length); // On OSX this strangely must be host byte order. iph.TotalLength = OperatingSystem.IsFreeBSD() ? (ushort)IPAddress.HostToNetworkOrder((short)totalLength) : (ushort)totalLength; iph.Protocol = 1; // ICMP @@ -46,7 +46,7 @@ private unsafe SocketConfig GetSocketConfig(IPAddress address, byte[] buffer, in #pragma warning restore 618 // No need to fill in SourceAddress or checksum. // If left blank, kernel will fill it in - at least on OSX. - } + } return new SocketConfig( new IPEndPoint(address, 0), timeout, options, @@ -340,11 +340,11 @@ await socket.SendToAsync( } } - private static PingReply CreatePingReply(IPStatus status) + private static PingReply CreatePingReply(IPStatus status, IPAddress? address = null, long rtt = 0) { // Documentation indicates that you should only pay attention to the IPStatus value when // its value is not "Success", but the rest of these values match that of the Windows implementation. - return new PingReply(new IPAddress(0), null, status, 0, Array.Empty()); + return new PingReply(address ?? new IPAddress(0), null, status, rtt, Array.Empty()); } #if DEBUG