-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use unprivileged socket to send ping on macOS/iOS/tvOS/Mac Catalyst (#…
…52240) Revival of #42006 with some feedback incorporated. This improves Ping behavior on macOS/iOS family. For unprivileged users, it allows to send/receive buffer with custom content and removes craft around spawning new process. (uses [this example](https://developer.apple.com/library/archive/samplecode/SimplePing/Introduction/Intro.html)). It adds new macOS/iOS/tvOS targets for the assembly that exclude the process creation code. Fixes #36941 Fixes #51395 Co-authored-by: Tomas Weinfurt <tweinfurt@yahoo.com>
- Loading branch information
1 parent
594e87a
commit e98614e
Showing
8 changed files
with
547 additions
and
472 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.OSX.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// 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 static bool SendIpHeader => true; | ||
private static bool NeedsConnect => false; | ||
|
||
private PingReply SendPingCore(IPAddress address, byte[] buffer, int timeout, PingOptions? options) | ||
=> SendIcmpEchoRequestOverRawSocket(address, buffer, timeout, options); | ||
|
||
private async Task<PingReply> SendPingAsyncCore(IPAddress address, byte[] buffer, int timeout, PingOptions? options) | ||
{ | ||
Task<PingReply> t = SendIcmpEchoRequestOverRawSocketAsync(address, buffer, timeout, options); | ||
PingReply reply = await t.ConfigureAwait(false); | ||
|
||
if (_canceled) | ||
{ | ||
throw new OperationCanceledException(); | ||
} | ||
|
||
return reply; | ||
} | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.PingUtility.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// 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); | ||
} | ||
|
||
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) || p.ExitCode == 1 || p.ExitCode == 2) | ||
{ | ||
return CreateTimedOutPingReply(); | ||
} | ||
|
||
try | ||
{ | ||
string output = p.StandardOutput.ReadToEnd(); | ||
return ParsePingUtilityOutput(address, output); | ||
} | ||
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) | ||
{ | ||
using (Process p = GetPingProcess(address, buffer, timeout, options)) | ||
{ | ||
var processCompletion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); | ||
p.EnableRaisingEvents = true; | ||
p.Exited += (s, e) => processCompletion.SetResult(); | ||
p.Start(); | ||
|
||
try | ||
{ | ||
await processCompletion.Task.WaitAsync(TimeSpan.FromMilliseconds(timeout)).ConfigureAwait(false); | ||
} | ||
catch (TimeoutException) | ||
{ | ||
p.Kill(); | ||
return CreateTimedOutPingReply(); | ||
} | ||
|
||
if (p.ExitCode == 1 || p.ExitCode == 2) | ||
{ | ||
// Throw timeout for known failure return codes from ping functions. | ||
return CreateTimedOutPingReply(); | ||
} | ||
|
||
try | ||
{ | ||
string output = await p.StandardOutput.ReadToEndAsync().ConfigureAwait(false); | ||
return ParsePingUtilityOutput(address, output); | ||
} | ||
catch (Exception) | ||
{ | ||
// If the standard output cannot be successfully parsed, throw a generic PingException. | ||
throw new PingException(SR.net_ping); | ||
} | ||
} | ||
} | ||
|
||
private PingReply ParsePingUtilityOutput(IPAddress address, string output) | ||
{ | ||
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. | ||
} | ||
} | ||
} |
Oops, something went wrong.