Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 3035218

Browse files
authored
add option to handle Ping TTL for raw socket and system utility (#30324)
* add option to handle TTL for raw socket and system utility * add missing blank line * incorporate feedback and merge with upstream * feedback from review
1 parent 271c083 commit 3035218

5 files changed

Lines changed: 101 additions & 39 deletions

File tree

src/Common/src/System/Net/NetworkInformation/UnixCommandLinePing.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System.Globalization;
66
using System.IO;
7+
using System.Runtime.InteropServices;
78
using System.Text;
89

910
namespace System.Net.NetworkInformation
@@ -17,6 +18,7 @@ internal static class UnixCommandLinePing
1718

1819
private static readonly string s_discoveredPing4UtilityPath = GetPingUtilityPath(ipv4: true);
1920
private static readonly string s_discoveredPing6UtilityPath = GetPingUtilityPath(ipv4: false);
21+
private static readonly bool s_isOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
2022

2123
// We don't want to pick up an arbitrary or malicious ping
2224
// command, so that's why we do the path probing ourselves.
@@ -51,8 +53,9 @@ private static string GetPingUtilityPath(bool ipv4)
5153
/// <param name="packetSize">The packet size to use in the ping. Exact packet payload cannot be specified.</param>
5254
/// <param name="address">A string representation of the IP address to ping.</param>
5355
/// <returns>The constructed command line arguments, which can be passed to ping or ping6.</returns>
54-
public static string ConstructCommandLine(int packetSize, string address, bool ipv4)
56+
public static string ConstructCommandLine(int packetSize, string address, bool ipv4, int ttl = 0)
5557
{
58+
5659
StringBuilder sb = new StringBuilder();
5760
sb.Append("-c 1"); // Just send a single ping ("count = 1")
5861

@@ -62,6 +65,22 @@ public static string ConstructCommandLine(int packetSize, string address, bool i
6265
// The ping utility is not flexible enough to specify an exact payload.
6366
// But we can at least send the right number of bytes.
6467

68+
if (ttl > 0)
69+
{
70+
if (!ipv4 && s_isOSX)
71+
{
72+
// OSX uses -h to set hop limit for IPv6
73+
sb.Append(" -h ");
74+
}
75+
else
76+
{
77+
// Linux uses -t ttl for both IPv4 & IPv6
78+
sb.Append(" -t ");
79+
}
80+
81+
sb.Append(ttl);
82+
}
83+
6584
// ping and ping6 do not report timing information unless at least 16 bytes are sent.
6685
if (packetSize < 16)
6786
{

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
157157
<Reference Include="System.Diagnostics.Process" />
158158
<Reference Include="System.IO.FileSystem" />
159+
<Reference Include="System.Runtime.InteropServices.RuntimeInformation" />
159160
</ItemGroup>
160161
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
161162
</Project>

src/System.Net.Ping/src/System/Net/NetworkInformation/Ping.Unix.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ private PingReply SendPingCore(IPAddress address, byte[] buffer, int timeout, Pi
2727
{
2828
PingReply reply = RawSocketPermissions.CanUseRawSockets(address.AddressFamily) ?
2929
SendIcmpEchoRequestOverRawSocket(address, buffer, timeout, options) :
30-
SendWithPingUtility(address, buffer, timeout);
30+
SendWithPingUtility(address, buffer, timeout, options);
3131

3232
return reply;
3333
}
@@ -43,7 +43,7 @@ private async Task<PingReply> SendPingAsyncCore(IPAddress address, byte[] buffer
4343
{
4444
Task<PingReply> t = RawSocketPermissions.CanUseRawSockets(address.AddressFamily) ?
4545
SendIcmpEchoRequestOverRawSocketAsync(address, buffer, timeout, options) :
46-
SendWithPingUtilityAsync(address, buffer, timeout);
46+
SendWithPingUtilityAsync(address, buffer, timeout, options);
4747

4848
PingReply reply = await t.ConfigureAwait(false);
4949
if (_canceled)
@@ -90,11 +90,14 @@ private Socket GetRawSocket(SocketConfig socketConfig)
9090
{
9191
// Setting Socket.DontFragment and .Ttl is not supported on Unix, so socketConfig.Options is ignored.
9292
AddressFamily addrFamily = ((IPEndPoint)socketConfig.EndPoint).Address.AddressFamily;
93-
return new Socket(addrFamily, SocketType.Raw, socketConfig.ProtocolType)
93+
Socket socket = new Socket(addrFamily, SocketType.Raw, socketConfig.ProtocolType);
94+
socket.ReceiveTimeout = socketConfig.Timeout;
95+
socket.SendTimeout = socketConfig.Timeout;
96+
if (socketConfig.Options != null && socketConfig.Options.Ttl > 0)
9497
{
95-
ReceiveTimeout = socketConfig.Timeout,
96-
SendTimeout = socketConfig.Timeout
97-
};
98+
socket.Ttl = (short)socketConfig.Options.Ttl;
99+
}
100+
return socket;
98101
}
99102

100103
private bool TryGetPingReply(SocketConfig socketConfig, byte[] receiveBuffer, int bytesReceived, Stopwatch sw, ref int ipHeaderLength, out PingReply reply)
@@ -245,7 +248,7 @@ await socket.SendToAsync(
245248
}
246249
}
247250

248-
private Process GetPingProcess(IPAddress address, byte[] buffer)
251+
private Process GetPingProcess(IPAddress address, byte[] buffer, PingOptions options)
249252
{
250253
bool isIpv4 = address.AddressFamily == AddressFamily.InterNetwork;
251254
string pingExecutable = isIpv4 ? UnixCommandLinePing.Ping4UtilityPath : UnixCommandLinePing.Ping6UtilityPath;
@@ -254,16 +257,17 @@ private Process GetPingProcess(IPAddress address, byte[] buffer)
254257
throw new PlatformNotSupportedException(SR.net_ping_utility_not_found);
255258
}
256259

257-
string processArgs = UnixCommandLinePing.ConstructCommandLine(buffer.Length, address.ToString(), isIpv4);
260+
string processArgs = UnixCommandLinePing.ConstructCommandLine(buffer.Length, address.ToString(), isIpv4, options?.Ttl ?? 0);
261+
258262
ProcessStartInfo psi = new ProcessStartInfo(pingExecutable, processArgs);
259263
psi.RedirectStandardOutput = true;
260264
psi.RedirectStandardError = true;
261265
return new Process() { StartInfo = psi };
262266
}
263267

264-
private PingReply SendWithPingUtility(IPAddress address, byte[] buffer, int timeout)
268+
private PingReply SendWithPingUtility(IPAddress address, byte[] buffer, int timeout, PingOptions options)
265269
{
266-
using (Process p = GetPingProcess(address, buffer))
270+
using (Process p = GetPingProcess(address, buffer, options))
267271
{
268272
p.Start();
269273
if (!p.WaitForExit(timeout) || p.ExitCode == 1 || p.ExitCode == 2)
@@ -284,9 +288,9 @@ private PingReply SendWithPingUtility(IPAddress address, byte[] buffer, int time
284288
}
285289
}
286290

287-
private async Task<PingReply> SendWithPingUtilityAsync(IPAddress address, byte[] buffer, int timeout)
291+
private async Task<PingReply> SendWithPingUtilityAsync(IPAddress address, byte[] buffer, int timeout, PingOptions options)
288292
{
289-
using (Process p = GetPingProcess(address, buffer))
293+
using (Process p = GetPingProcess(address, buffer, options))
290294
{
291295
var processCompletion = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
292296
p.EnableRaisingEvents = true;

src/System.Net.Ping/tests/FunctionalTests/PingTest.cs

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,17 @@ public async Task SendPingAsync_InvalidArgs()
9696
AssertExtensions.Throws<ArgumentException>("buffer", () => { p.Send(TestSettings.LocalHost, 1, new byte[65501]); });
9797
}
9898

99-
[Fact]
100-
public void SendPingWithIPAddress()
99+
[Theory]
100+
[InlineData(AddressFamily.InterNetwork)]
101+
[InlineData(AddressFamily.InterNetworkV6)]
102+
public void SendPingWithIPAddress(AddressFamily addressFamily)
101103
{
102-
IPAddress localIpAddress = TestSettings.GetLocalIPAddress();
104+
IPAddress localIpAddress = TestSettings.GetLocalIPAddress(addressFamily);
105+
if (localIpAddress == null)
106+
{
107+
// No local address for given address family.
108+
return;
109+
}
103110

104111
SendBatchPing(
105112
(ping) => ping.Send(localIpAddress),
@@ -109,10 +116,17 @@ public void SendPingWithIPAddress()
109116
});
110117
}
111118

112-
[Fact]
113-
public async Task SendPingAsyncWithIPAddress()
119+
[Theory]
120+
[InlineData(AddressFamily.InterNetwork)]
121+
[InlineData(AddressFamily.InterNetworkV6)]
122+
public async Task SendPingAsyncWithIPAddress(AddressFamily addressFamily)
114123
{
115-
IPAddress localIpAddress = await TestSettings.GetLocalIPAddressAsync();
124+
IPAddress localIpAddress = await TestSettings.GetLocalIPAddressAsync(addressFamily);
125+
if (localIpAddress == null)
126+
{
127+
// No local address for given address family.
128+
return;
129+
}
116130

117131
await SendBatchPingAsync(
118132
(ping) => ping.SendPingAsync(localIpAddress),
@@ -122,10 +136,17 @@ await SendBatchPingAsync(
122136
});
123137
}
124138

125-
[Fact]
126-
public void SendPingWithIPAddress_AddressAsString()
139+
[Theory]
140+
[InlineData(AddressFamily.InterNetwork)]
141+
[InlineData(AddressFamily.InterNetworkV6)]
142+
public void SendPingWithIPAddress_AddressAsString(AddressFamily addressFamily)
127143
{
128-
IPAddress localIpAddress = TestSettings.GetLocalIPAddress();
144+
IPAddress localIpAddress = TestSettings.GetLocalIPAddress(addressFamily);
145+
if (localIpAddress == null)
146+
{
147+
// No local address for given address family.
148+
return;
149+
}
129150

130151
SendBatchPing(
131152
(ping) => ping.Send(localIpAddress.ToString()),
@@ -295,10 +316,17 @@ await SendBatchPingAsync(
295316
}
296317

297318
[PlatformSpecific(TestPlatforms.AnyUnix)] // On Unix, Non-root pings cannot send arbitrary data in the buffer, and do not receive it back in the PingReply.
298-
[Fact]
299-
public void SendPingWithIPAddressAndTimeoutAndBufferAndPingOptions_Unix()
319+
[Theory]
320+
[InlineData(AddressFamily.InterNetwork)]
321+
[InlineData(AddressFamily.InterNetworkV6)]
322+
public void SendPingWithIPAddressAndTimeoutAndBufferAndPingOptions_Unix(AddressFamily addressFamily)
300323
{
301-
IPAddress localIpAddress = TestSettings.GetLocalIPAddress();
324+
IPAddress localIpAddress = TestSettings.GetLocalIPAddress(addressFamily);
325+
if (localIpAddress == null)
326+
{
327+
// No local address for given address family.
328+
return;
329+
}
302330

303331
byte[] buffer = TestSettings.PayloadAsBytes;
304332
SendBatchPing(
@@ -320,10 +348,17 @@ public void SendPingWithIPAddressAndTimeoutAndBufferAndPingOptions_Unix()
320348
}
321349

322350
[PlatformSpecific(TestPlatforms.AnyUnix)] // On Unix, Non-root pings cannot send arbitrary data in the buffer, and do not receive it back in the PingReply.
323-
[Fact]
324-
public async Task SendPingAsyncWithIPAddressAndTimeoutAndBufferAndPingOptions_Unix()
351+
[Theory]
352+
[InlineData(AddressFamily.InterNetwork)]
353+
[InlineData(AddressFamily.InterNetworkV6)]
354+
public async Task SendPingAsyncWithIPAddressAndTimeoutAndBufferAndPingOptions_Unix(AddressFamily addressFamily)
325355
{
326-
IPAddress localIpAddress = await TestSettings.GetLocalIPAddressAsync();
356+
IPAddress localIpAddress = await TestSettings.GetLocalIPAddressAsync(addressFamily);
357+
if (localIpAddress == null)
358+
{
359+
// No local address for given address family.
360+
return;
361+
}
327362

328363
byte[] buffer = TestSettings.PayloadAsBytes;
329364
await SendBatchPingAsync(

src/System.Net.Ping/tests/FunctionalTests/TestSettings.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,40 @@ internal static class TestSettings
1616
public const string PayloadAsString = "'Post hoc ergo propter hoc'. 'After it, therefore because of it'. It means one thing follows the other, therefore it was caused by the other. But it's not always true. In fact it's hardly ever true.";
1717
public static readonly byte[] PayloadAsBytes = Encoding.UTF8.GetBytes(TestSettings.PayloadAsString);
1818

19-
public static IPAddress GetLocalIPAddress()
19+
public static IPAddress GetLocalIPAddress(AddressFamily addressFamily = AddressFamily.Unspecified)
2020
{
2121
IPHostEntry hostEntry = Dns.GetHostEntry(LocalHost);
22-
return GetIPAddressForHost(hostEntry);
22+
return GetIPAddressForHost(hostEntry, addressFamily);
2323
}
2424

25-
public static async Task<IPAddress> GetLocalIPAddressAsync()
25+
public static async Task<IPAddress> GetLocalIPAddressAsync(AddressFamily addressFamily = AddressFamily.Unspecified)
2626
{
2727
IPHostEntry hostEntry = await Dns.GetHostEntryAsync(LocalHost);
28-
return GetIPAddressForHost(hostEntry);
28+
return GetIPAddressForHost(hostEntry, addressFamily);
2929
}
3030

31-
private static IPAddress GetIPAddressForHost(IPHostEntry hostEntry)
31+
private static IPAddress GetIPAddressForHost(IPHostEntry hostEntry, AddressFamily addressFamily = AddressFamily.Unspecified)
3232
{
33-
IPAddress ret = null;
34-
3533
foreach (IPAddress address in hostEntry.AddressList)
3634
{
37-
if (address.AddressFamily == AddressFamily.InterNetworkV6)
35+
if (address.AddressFamily == addressFamily || (addressFamily == AddressFamily.Unspecified && address.AddressFamily == AddressFamily.InterNetworkV6))
3836
{
3937
return address;
4038
}
4139
}
4240

4341
// If there's no IPv6 addresses, just take the first (IPv4) address.
44-
if (ret == null && hostEntry.AddressList.Length > 0)
42+
if (addressFamily == AddressFamily.Unspecified)
4543
{
46-
return hostEntry.AddressList[0];
44+
if (hostEntry.AddressList.Length > 0)
45+
{
46+
return hostEntry.AddressList[0];
47+
}
48+
49+
throw new InvalidOperationException("Unable to discover any addresses for the local host.");
4750
}
4851

49-
throw new InvalidOperationException("Unable to discover any addresses for the local host.");
52+
return addressFamily == AddressFamily.InterNetwork ? IPAddress.Loopback : IPAddress.IPv6Loopback;
5053
}
5154
}
5255
}

0 commit comments

Comments
 (0)