private bool TryGetPingReply( SocketConfig socketConfig, byte[] receiveBuffer, int bytesReceived, Stopwatch sw, ref int ipHeaderLength, [NotNullWhen(true)] out PingReply?reply) { byte type, code; reply = null; if (socketConfig.IsIpv4) { // Determine actual size of IP header byte ihl = (byte)(receiveBuffer[0] & 0x0f); // Internet Header Length ipHeaderLength = 4 * ihl; if (bytesReceived - ipHeaderLength < IcmpHeaderLengthInBytes) { return(false); // Not enough bytes to reconstruct actual IP header + ICMP header. } } int icmpHeaderOffset = ipHeaderLength; // Skip IP header. IcmpHeader receivedHeader = MemoryMarshal.Read <IcmpHeader>(receiveBuffer.AsSpan(icmpHeaderOffset)); type = receivedHeader.Type; code = receivedHeader.Code; if (socketConfig.Identifier != receivedHeader.Identifier || type == (byte)IcmpV4MessageType.EchoRequest || type == (byte)IcmpV6MessageType.EchoRequest) // Echo Request, ignore { return(false); } 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 = socketConfig.IsIpv4 ? IcmpV4MessageConstants.MapV4TypeToIPStatus(type, code) : IcmpV6MessageConstants.MapV6TypeToIPStatus(type, code); IPAddress address = ((IPEndPoint)socketConfig.EndPoint).Address; reply = new PingReply(address, socketConfig.Options, status, roundTripTime, dataBuffer); return(true); }
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 the current thread's ID as the identifier. ushort identifier = (ushort)Environment.CurrentManagedThreadId; 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()); } }
private bool TryGetPingReply( SocketConfig socketConfig, byte[] receiveBuffer, int bytesReceived, Stopwatch sw, ref int ipHeaderLength, [NotNullWhen(true)] out PingReply?reply) { byte type, code; reply = null; if (socketConfig.IsIpv4) { // Determine actual size of IP header byte ihl = (byte)(receiveBuffer[0] & 0x0f); // Internet Header Length ipHeaderLength = 4 * ihl; if (bytesReceived - ipHeaderLength < IcmpHeaderLengthInBytes) { return(false); // Not enough bytes to reconstruct actual IP header + ICMP header. } } int icmpHeaderOffset = ipHeaderLength; int dataOffset = ipHeaderLength + IcmpHeaderLengthInBytes; // Skip IP header. IcmpHeader receivedHeader = MemoryMarshal.Read <IcmpHeader>(receiveBuffer.AsSpan(icmpHeaderOffset)); ushort identifier = 0; type = receivedHeader.Type; code = receivedHeader.Code; // Validate the ICMP header and get the identifier if (socketConfig.IsIpv4) { if (type == (byte)IcmpV4MessageType.EchoReply) { // Reply packet has the identifier in the ICMP header. identifier = receivedHeader.Identifier; } else if (type == (byte)IcmpV4MessageType.DestinationUnreachable || type == (byte)IcmpV4MessageType.TimeExceeded || type == (byte)IcmpV4MessageType.ParameterProblemBadIPHeader || type == (byte)IcmpV4MessageType.SourceQuench || type == (byte)IcmpV4MessageType.RedirectMessage) { // Original IP+ICMP request is in the payload. Read the ICMP header from // the payload to get identifier. if (dataOffset + MinIpHeaderLengthInBytes + IcmpHeaderLengthInBytes > bytesReceived) { return(false); } byte ihl = (byte)(receiveBuffer[dataOffset] & 0x0f); // Internet Header Length int payloadIpHeaderLength = 4 * ihl; if (bytesReceived - dataOffset - payloadIpHeaderLength < IcmpHeaderLengthInBytes) { return(false); // Not enough bytes to reconstruct actual IP header + ICMP header. } IcmpHeader originalRequestHeader = MemoryMarshal.Read <IcmpHeader>(receiveBuffer.AsSpan(dataOffset + payloadIpHeaderLength)); identifier = originalRequestHeader.Identifier; // Update the date offset to point past the payload IP+ICMP headers. While the specification // doesn't indicate there should be any additional data the reality is that we often get the // original packet data back. dataOffset += payloadIpHeaderLength + IcmpHeaderLengthInBytes; } else { return(false); } } else { if (type == (byte)IcmpV6MessageType.EchoReply) { // Reply packet has the identifier in the ICMP header. identifier = receivedHeader.Identifier; } else if (type == (byte)IcmpV6MessageType.DestinationUnreachable || type == (byte)IcmpV6MessageType.TimeExceeded || type == (byte)IcmpV6MessageType.ParameterProblem || type == (byte)IcmpV6MessageType.PacketTooBig) { // Original IP+ICMP request is in the payload. Read the ICMP header from // the payload to get identifier. if (bytesReceived - dataOffset < IpV6HeaderLengthInBytes + IcmpHeaderLengthInBytes) { return(false); // Not enough bytes to reconstruct actual IP header + ICMP header. } IcmpHeader originalRequestHeader = MemoryMarshal.Read <IcmpHeader>(receiveBuffer.AsSpan(dataOffset + IpV6HeaderLengthInBytes)); identifier = originalRequestHeader.Identifier; // Update the date offset to point past the payload IP+ICMP headers. While the specification // doesn't indicate there should be any additional data the reality is that we often get the // original packet data back. dataOffset += IpV6HeaderLengthInBytes + IcmpHeaderLengthInBytes; } else { return(false); } } if (socketConfig.Identifier != identifier) { return(false); } sw.Stop(); long roundTripTime = sw.ElapsedMilliseconds; // 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 = socketConfig.IsIpv4 ? IcmpV4MessageConstants.MapV4TypeToIPStatus(type, code) : IcmpV6MessageConstants.MapV6TypeToIPStatus(type, code); IPAddress address = ((IPEndPoint)socketConfig.EndPoint).Address; reply = new PingReply(address, socketConfig.Options, status, roundTripTime, dataBuffer); return(true); }