private async Task<PingReply> SendIcmpEchoRequestOverRawSocket(IPAddress address, byte[] buffer, int timeout, PingOptions options)
{
EndPoint endPoint = new IPEndPoint(address, 0);
bool isIpv4 = address.AddressFamily == AddressFamily.InterNetwork;
ProtocolType protocolType = isIpv4 ? ProtocolType.Icmp : ProtocolType.IcmpV6;
// Use a random value as the identifier. This doesn't need to be perfectly random
// or very unpredictable, rather just good enough to avoid unexpected conflicts.
Random rand = t_idGenerator ?? (t_idGenerator = new Random());
ushort identifier = (ushort)rand.Next((int)ushort.MaxValue + 1);
IcmpHeader header = new IcmpHeader()
{
Type = isIpv4 ? (byte)IcmpV4MessageType.EchoRequest : (byte)IcmpV6MessageType.EchoRequest,
Code = 0,
HeaderChecksum = 0,
Identifier = identifier,
SequenceNumber = 0,
};
byte[] sendBuffer = CreateSendMessageBuffer(header, buffer);
using (Socket socket = new Socket(address.AddressFamily, SocketType.Raw, protocolType))
{
socket.ReceiveTimeout = timeout;
socket.SendTimeout = timeout;
// Setting Socket.DontFragment and .Ttl is not supported on Unix, so ignore the PingOptions parameter.
int ipHeaderLength = isIpv4 ? IpHeaderLengthInBytes : 0;
await socket.SendToAsync(new ArraySegment<byte>(sendBuffer), SocketFlags.None, endPoint).ConfigureAwait(false);
byte[] receiveBuffer = new byte[ipHeaderLength + IcmpHeaderLengthInBytes + buffer.Length];
long elapsed;
Stopwatch sw = Stopwatch.StartNew();
// 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.
while ((elapsed = sw.ElapsedMilliseconds) < timeout)
{
Task<SocketReceiveFromResult> receiveTask = socket.ReceiveFromAsync(
new ArraySegment<byte>(receiveBuffer),
SocketFlags.None,
endPoint);
var cts = new CancellationTokenSource();
Task finished = await Task.WhenAny(receiveTask, Task.Delay(timeout - (int)elapsed, cts.Token)).ConfigureAwait(false);
cts.Cancel();
if (finished != receiveTask)
{
sw.Stop();
return CreateTimedOutPingReply();
}
SocketReceiveFromResult receiveResult = receiveTask.GetAwaiter().GetResult();
int bytesReceived = receiveResult.ReceivedBytes;
if (bytesReceived - ipHeaderLength < IcmpHeaderLengthInBytes)
{
continue; // Not enough bytes to reconstruct IP header + ICMP header.
}
byte type, code;
unsafe
{
fixed (byte* bytesPtr = receiveBuffer)
{
int icmpHeaderOffset = ipHeaderLength;
IcmpHeader receivedHeader = *((IcmpHeader*)(bytesPtr + icmpHeaderOffset)); // Skip IP header.
type = receivedHeader.Type;
code = receivedHeader.Code;
if (identifier != receivedHeader.Identifier
|| type == (byte)IcmpV4MessageType.EchoRequest
|| type == (byte)IcmpV6MessageType.EchoRequest) // Echo Request, ignore
{
continue;
}
}
}
sw.Stop();
long roundTripTime = sw.ElapsedMilliseconds;
int dataOffset = ipHeaderLength + IcmpHeaderLengthInBytes;
// We want to return a buffer with the actual data we sent out, not including the header data.
byte[] dataBuffer = new byte[bytesReceived - dataOffset];
Buffer.BlockCopy(receiveBuffer, dataOffset, dataBuffer, 0, dataBuffer.Length);
IPStatus status = isIpv4
? IcmpV4MessageConstants.MapV4TypeToIPStatus(type, code)
: IcmpV6MessageConstants.MapV6TypeToIPStatus(type, code);
return new PingReply(address, options, status, roundTripTime, dataBuffer);
}
// We have exceeded our timeout duration, and no reply has been received.
sw.Stop();
return CreateTimedOutPingReply();
}
}