Skip to content

Commit 35ecc33

Browse files
filipnavarawfurt
andcommitted
Use unprivileged socket to send ping on macOS/iOS/tvOS/Mac Catalyst
Co-authored-by: Tomas Weinfurt <tweinfurt@yahoo.com>
1 parent 14c0350 commit 35ecc33

File tree

9 files changed

+548
-475
lines changed

9 files changed

+548
-475
lines changed

src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs

+1-4
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ public static partial class PlatformDetection
3232
public static bool IsFedora => IsDistroAndVersion("fedora");
3333

3434
// OSX family
35-
public static bool IsOSXLike =>
36-
RuntimeInformation.IsOSPlatform(OSPlatform.Create("IOS")) ||
37-
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ||
38-
RuntimeInformation.IsOSPlatform(OSPlatform.Create("TVOS"));
35+
public static bool IsOSXLike => IsOSX || IsiOS || IstvOS || IsMacCatalyst;
3936
public static bool IsOSX => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
4037
public static bool IsNotOSX => !IsOSX;
4138
public static bool IsMacOsMojaveOrHigher => IsOSX && Environment.OSVersion.Version >= new Version(10, 14);

src/libraries/System.Net.Ping/src/System.Net.Ping.csproj

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
4-
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)</TargetFrameworks>
4+
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent)</TargetFrameworks>
55
<Nullable>enable</Nullable>
66
</PropertyGroup>
77
<PropertyGroup>
88
<GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetsAnyOS)' == 'true'">SR.SystemNetPing_PlatformNotSupported</GeneratePlatformNotSupportedAssemblyMessage>
9+
<TargetsApple Condition="'$(TargetsOSX)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</TargetsApple>
910
</PropertyGroup>
1011
<ItemGroup Condition="'$(TargetsAnyOS)' != 'true'">
1112
<Compile Include="System\Net\NetworkInformation\IPStatus.cs" />
@@ -34,7 +35,7 @@
3435
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
3536
<Compile Include="System\Net\NetworkInformation\IcmpV4MessageConstants.cs" />
3637
<Compile Include="System\Net\NetworkInformation\IcmpV6MessageConstants.cs" />
37-
<Compile Include="System\Net\NetworkInformation\Ping.Unix.cs" />
38+
<Compile Include="System\Net\NetworkInformation\Ping.RawSocket.cs" />
3839
<!-- System.Net Common -->
3940
<Compile Include="$(CommonPath)System\Net\RawSocketPermissions.cs"
4041
Link="Common\System\Net\RawSocketPermissions.cs" />
@@ -58,6 +59,13 @@
5859
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.SocketAddress.cs"
5960
Link="Common\Interop\Unix\System.Native\Interop.SocketAddress.cs" />
6061
</ItemGroup>
62+
<ItemGroup Condition="'$(TargetsUnix)' == 'true' AND '$(TargetsApple)' != 'true'">
63+
<Compile Include="System\Net\NetworkInformation\Ping.Unix.cs" />
64+
<Compile Include="System\Net\NetworkInformation\Ping.PingUtility.cs" />
65+
</ItemGroup>
66+
<ItemGroup Condition="'$(TargetsUnix)' == 'true' AND '$(TargetsApple)' == 'true'">
67+
<Compile Include="System\Net\NetworkInformation\Ping.OSX.cs" />
68+
</ItemGroup>
6169
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
6270
<Compile Include="System\Net\NetworkInformation\Ping.Windows.cs" />
6371
<!-- System.Net Common -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.IO;
8+
using System.Net.Sockets;
9+
using System.Runtime.InteropServices;
10+
using System.Text;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
14+
namespace System.Net.NetworkInformation
15+
{
16+
public partial class Ping
17+
{
18+
private PingReply SendPingCore(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
19+
=> SendIcmpEchoRequestOverRawSocket(address, buffer, timeout, options);
20+
21+
private async Task<PingReply> SendPingAsyncCore(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
22+
{
23+
Task<PingReply> t = SendIcmpEchoRequestOverRawSocketAsync(address, buffer, timeout, options);
24+
PingReply reply = await t.ConfigureAwait(false);
25+
26+
if (_canceled)
27+
{
28+
throw new OperationCanceledException();
29+
}
30+
31+
return reply;
32+
}
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.IO;
8+
using System.Net.Sockets;
9+
using System.Runtime.InteropServices;
10+
using System.Text;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
14+
namespace System.Net.NetworkInformation
15+
{
16+
public partial class Ping
17+
{
18+
private Process GetPingProcess(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
19+
{
20+
bool isIpv4 = address.AddressFamily == AddressFamily.InterNetwork;
21+
string? pingExecutable = isIpv4 ? UnixCommandLinePing.Ping4UtilityPath : UnixCommandLinePing.Ping6UtilityPath;
22+
if (pingExecutable == null)
23+
{
24+
throw new PlatformNotSupportedException(SR.net_ping_utility_not_found);
25+
}
26+
27+
UnixCommandLinePing.PingFragmentOptions fragmentOption = UnixCommandLinePing.PingFragmentOptions.Default;
28+
if (options != null && address.AddressFamily == AddressFamily.InterNetwork)
29+
{
30+
fragmentOption = options.DontFragment ? UnixCommandLinePing.PingFragmentOptions.Do : UnixCommandLinePing.PingFragmentOptions.Dont;
31+
}
32+
33+
string processArgs = UnixCommandLinePing.ConstructCommandLine(buffer.Length, timeout, address.ToString(), isIpv4, options?.Ttl ?? 0, fragmentOption);
34+
35+
ProcessStartInfo psi = new ProcessStartInfo(pingExecutable, processArgs);
36+
psi.RedirectStandardOutput = true;
37+
psi.RedirectStandardError = true;
38+
// 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.
39+
psi.EnvironmentVariables["LC_ALL"] = "C";
40+
return new Process() { StartInfo = psi };
41+
}
42+
43+
private PingReply SendWithPingUtility(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
44+
{
45+
using (Process p = GetPingProcess(address, buffer, timeout, options))
46+
{
47+
p.Start();
48+
if (!p.WaitForExit(timeout) || p.ExitCode == 1 || p.ExitCode == 2)
49+
{
50+
return CreateTimedOutPingReply();
51+
}
52+
53+
try
54+
{
55+
string output = p.StandardOutput.ReadToEnd();
56+
return ParsePingUtilityOutput(address, output);
57+
}
58+
catch (Exception)
59+
{
60+
// If the standard output cannot be successfully parsed, throw a generic PingException.
61+
throw new PingException(SR.net_ping);
62+
}
63+
}
64+
}
65+
66+
private async Task<PingReply> SendWithPingUtilityAsync(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
67+
{
68+
using (Process p = GetPingProcess(address, buffer, timeout, options))
69+
{
70+
var processCompletion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
71+
p.EnableRaisingEvents = true;
72+
p.Exited += (s, e) => processCompletion.SetResult();
73+
p.Start();
74+
75+
try
76+
{
77+
await processCompletion.Task.WaitAsync(TimeSpan.FromMilliseconds(timeout)).ConfigureAwait(false);
78+
}
79+
catch (TimeoutException)
80+
{
81+
p.Kill();
82+
return CreateTimedOutPingReply();
83+
}
84+
85+
if (p.ExitCode == 1 || p.ExitCode == 2)
86+
{
87+
// Throw timeout for known failure return codes from ping functions.
88+
return CreateTimedOutPingReply();
89+
}
90+
91+
try
92+
{
93+
string output = await p.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
94+
return ParsePingUtilityOutput(address, output);
95+
}
96+
catch (Exception)
97+
{
98+
// If the standard output cannot be successfully parsed, throw a generic PingException.
99+
throw new PingException(SR.net_ping);
100+
}
101+
}
102+
}
103+
104+
private PingReply ParsePingUtilityOutput(IPAddress address, string output)
105+
{
106+
long rtt = UnixCommandLinePing.ParseRoundTripTime(output);
107+
return new PingReply(
108+
address,
109+
null, // Ping utility cannot accommodate these, return null to indicate they were ignored.
110+
IPStatus.Success,
111+
rtt,
112+
Array.Empty<byte>()); // Ping utility doesn't deliver this info.
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)