Skip to content

Commit

Permalink
[release/6.0] Fix parsing the output of ping on Samsung phones (#80692)
Browse files Browse the repository at this point in the history
* Explicitly check for TTL expiration when using Ping utility (#65312)

* Explicitly check for TTL expiration when using Ping utility

* Fixes

* Improve the implementation

* Account for different formats of ping output

* Avoid allocation

Co-authored-by: Radek Zikmund <32671551+rzikm@users.noreply.github.com>
  • Loading branch information
simonrozsival and rzikm authored Jan 17, 2023
1 parent dea5221 commit 8e8a6e0
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -82,16 +82,10 @@ private async Task<PingReply> 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)
{
Expand All @@ -101,15 +95,49 @@ private async Task<PingReply> 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<byte>()); // 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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,
Expand Down Expand Up @@ -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<byte>());
return new PingReply(address ?? new IPAddress(0), null, status, rtt, Array.Empty<byte>());
}

#if DEBUG
Expand Down

0 comments on commit 8e8a6e0

Please sign in to comment.