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 74e85ca493a24e..78a2a4429fdc72 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 @@ -45,15 +45,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) { @@ -82,16 +82,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) { @@ -101,15 +95,49 @@ 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" + // - "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; } } } 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 341c328265cbe0..107517aaab780d 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 @@ -29,8 +29,8 @@ private unsafe SocketConfig GetSocketConfig(IPAddress address, byte[] buffer, in bool ipv4 = address.AddressFamily == AddressFamily.InterNetwork; bool sendIpHeader = ipv4 && options != null && SendIpHeader; - if (sendIpHeader) - { + if (sendIpHeader) + { iph.VersionAndLength = 0x45; // On OSX this strangely must be host byte order. iph.TotalLength = (ushort)(sizeof(IpHeader) + checked(sizeof(IcmpHeader) + buffer.Length)); @@ -42,7 +42,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, @@ -260,11 +260,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