Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CancellationToken-accepting overloads for SendPingAsync #72338

Merged
merged 4 commits into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libraries/System.Net.Ping/ref/System.Net.Ping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,12 @@ public void SendAsyncCancel() { }
public System.Threading.Tasks.Task<System.Net.NetworkInformation.PingReply> SendPingAsync(System.Net.IPAddress address, int timeout) { throw null; }
public System.Threading.Tasks.Task<System.Net.NetworkInformation.PingReply> SendPingAsync(System.Net.IPAddress address, int timeout, byte[] buffer) { throw null; }
public System.Threading.Tasks.Task<System.Net.NetworkInformation.PingReply> SendPingAsync(System.Net.IPAddress address, int timeout, byte[] buffer, System.Net.NetworkInformation.PingOptions? options) { throw null; }
public System.Threading.Tasks.Task<System.Net.NetworkInformation.PingReply> SendPingAsync(System.Net.IPAddress address, System.TimeSpan timeout, byte[]? buffer = null, System.Net.NetworkInformation.PingOptions? options = null, System.Threading.CancellationToken cancellationToken = default) { throw null; }
madelson marked this conversation as resolved.
Show resolved Hide resolved
public System.Threading.Tasks.Task<System.Net.NetworkInformation.PingReply> SendPingAsync(string hostNameOrAddress) { throw null; }
public System.Threading.Tasks.Task<System.Net.NetworkInformation.PingReply> SendPingAsync(string hostNameOrAddress, int timeout) { throw null; }
public System.Threading.Tasks.Task<System.Net.NetworkInformation.PingReply> SendPingAsync(string hostNameOrAddress, int timeout, byte[] buffer) { throw null; }
public System.Threading.Tasks.Task<System.Net.NetworkInformation.PingReply> SendPingAsync(string hostNameOrAddress, int timeout, byte[] buffer, System.Net.NetworkInformation.PingOptions? options) { throw null; }
public System.Threading.Tasks.Task<System.Net.NetworkInformation.PingReply> SendPingAsync(string hostNameOrAddress, System.TimeSpan timeout, byte[]? buffer = null, System.Net.NetworkInformation.PingOptions? options = null, System.Threading.CancellationToken cancellationToken = default) { throw null; }
}
public partial class PingCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,7 @@ public partial class Ping
private static 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;
}
private Task<PingReply> SendPingAsyncCore(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
=> SendIcmpEchoRequestOverRawSocketAsync(address, buffer, timeout, options);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,33 +73,29 @@ private PingReply SendWithPingUtility(IPAddress address, byte[] buffer, int time

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();
CancellationToken timeoutOrCancellationToken = _timeoutOrCancellationSource!.Token;

try
{
await processCompletion.Task.WaitAsync(TimeSpan.FromMilliseconds(timeout)).ConfigureAwait(false);
}
catch (TimeoutException)
{
p.Kill();
return CreatePingReply(IPStatus.TimedOut);
}
using Process pingProcess = GetPingProcess(address, buffer, timeout, options);
pingProcess.Start();

try
try
{
await pingProcess.WaitForExitAsync(timeoutOrCancellationToken).ConfigureAwait(false);

string stdout = await pingProcess.StandardOutput.ReadToEndAsync(timeoutOrCancellationToken).ConfigureAwait(false);
madelson marked this conversation as resolved.
Show resolved Hide resolved
return ParsePingUtilityOutput(address, pingProcess.ExitCode, stdout);
}
catch when (timeoutOrCancellationToken.IsCancellationRequested)
madelson marked this conversation as resolved.
Show resolved Hide resolved
{
if (!pingProcess.HasExited)
{
string stdout = await p.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
return ParsePingUtilityOutput(address, p.ExitCode, stdout);
pingProcess.Kill();
}
catch (Exception)
if (_canceled)
{
// If the standard output cannot be successfully parsed, throw a generic PingException.
throw new PingException(SR.net_ping);
throw;
}
return CreatePingReply(IPStatus.TimedOut);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,67 +276,63 @@ private static PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byt
}
}

private static async Task<PingReply> SendIcmpEchoRequestOverRawSocketAsync(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
private async Task<PingReply> SendIcmpEchoRequestOverRawSocketAsync(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
{
CancellationToken timeoutOrCancellationToken = _timeoutOrCancellationSource!.Token;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this down into the try block?


SocketConfig socketConfig = GetSocketConfig(address, buffer, timeout, options);
using (Socket socket = GetRawSocket(socketConfig))
{
int ipHeaderLength = socketConfig.IsIpv4 ? MinIpHeaderLengthInBytes : 0;
CancellationTokenSource timeoutTokenSource = new CancellationTokenSource(timeout);
using Socket socket = GetRawSocket(socketConfig);
int ipHeaderLength = socketConfig.IsIpv4 ? MinIpHeaderLengthInBytes : 0;

try
try
{
await socket.SendToAsync(
socketConfig.SendBuffer.AsMemory(),
SocketFlags.None,
socketConfig.EndPoint,
timeoutOrCancellationToken)
.ConfigureAwait(false);

byte[] receiveBuffer = new byte[2 * (MaxIpHeaderLengthInBytes + IcmpHeaderLengthInBytes) + buffer.Length];

// Read from the socket in a loop. We may receive messages that are not echo replies, or that are not in response
// to the echo request we just sent. We need to filter such messages out, and continue reading until our timeout.
// For example, when pinging the local host, we need to filter out our own echo requests that the socket reads.
long startingTimestamp = Stopwatch.GetTimestamp();
while (!timeoutOrCancellationToken.IsCancellationRequested)
{
await socket.SendToAsync(
new ArraySegment<byte>(socketConfig.SendBuffer),
SocketFlags.None, socketConfig.EndPoint,
timeoutTokenSource.Token)
SocketReceiveFromResult receiveResult = await socket.ReceiveFromAsync(
receiveBuffer.AsMemory(),
SocketFlags.None,
socketConfig.EndPoint,
timeoutOrCancellationToken)
.ConfigureAwait(false);

byte[] receiveBuffer = new byte[2 * (MaxIpHeaderLengthInBytes + IcmpHeaderLengthInBytes) + buffer.Length];

// Read from the socket in a loop. We may receive messages that are not echo replies, or that are not in response
// to the echo request we just sent. We need to filter such messages out, and continue reading until our timeout.
// For example, when pinging the local host, we need to filter out our own echo requests that the socket reads.
long startingTimestamp = Stopwatch.GetTimestamp();
while (!timeoutTokenSource.IsCancellationRequested)
int bytesReceived = receiveResult.ReceivedBytes;
if (bytesReceived - ipHeaderLength < IcmpHeaderLengthInBytes)
{
SocketReceiveFromResult receiveResult = await socket.ReceiveFromAsync(
new ArraySegment<byte>(receiveBuffer),
SocketFlags.None,
socketConfig.EndPoint,
timeoutTokenSource.Token)
.ConfigureAwait(false);

int bytesReceived = receiveResult.ReceivedBytes;
if (bytesReceived - ipHeaderLength < IcmpHeaderLengthInBytes)
{
continue; // Not enough bytes to reconstruct IP header + ICMP header.
}
continue; // Not enough bytes to reconstruct IP header + ICMP header.
}

if (TryGetPingReply(socketConfig, receiveBuffer, bytesReceived, startingTimestamp, ref ipHeaderLength, out PingReply? reply))
{
return reply;
}
if (TryGetPingReply(socketConfig, receiveBuffer, bytesReceived, startingTimestamp, ref ipHeaderLength, out PingReply? reply))
{
return reply;
}
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
{
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.MessageSize)
{
return CreatePingReply(IPStatus.PacketTooBig);
}
catch (OperationCanceledException)
{
}
finally
{
timeoutTokenSource.Dispose();
}

// We have exceeded our timeout duration, and no reply has been received.
return CreatePingReply(IPStatus.TimedOut);
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
{
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.MessageSize)
{
return CreatePingReply(IPStatus.PacketTooBig);
}
catch (OperationCanceledException) when (!_canceled)
{
}

// We have exceeded our timeout duration, and no reply has been received.
return CreatePingReply(IPStatus.TimedOut);
}

private static PingReply CreatePingReply(IPStatus status, IPAddress? address = null, long rtt = 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,11 @@ private PingReply SendPingCore(IPAddress address, byte[] buffer, int timeout, Pi
return reply;
}

private async Task<PingReply> SendPingAsyncCore(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
private Task<PingReply> SendPingAsyncCore(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
{
Task<PingReply> t = RawSocketPermissions.CanUseRawSockets(address.AddressFamily) ?
return RawSocketPermissions.CanUseRawSockets(address.AddressFamily) ?
SendIcmpEchoRequestOverRawSocketAsync(address, buffer, timeout, options) :
SendWithPingUtilityAsync(address, buffer, timeout, options);

PingReply reply = await t.ConfigureAwait(false);

if (_canceled)
{
throw new OperationCanceledException();
}

return reply;
}
}
}
Loading