Example #1
0
        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);
        }
Example #2
0
        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());
            }
        }
Example #3
0
        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);
        }