What is the proper process for ICMP echo request/reply on unreachable destinations?

user1298780 picture user1298780 · Mar 28, 2012 · Viewed 12.5k times · Source

Goal:

I need to be able to ping a network switch to determine whether or not it is available. This is meant to tell the user that either the network cabling is unplugged, the network switch is unavailable, or some other problem lies within the network communication pathway. I realize this is not a comprehensive diagnosis tool, but something is better than nothing.

Design:

I planned on using ICMP with raw sockets to send five (5) ping messages to a particular address in IPv4 dot-notation. I will setup an ICMP filter on the socket and will not be creating my own IP header. Transmission of the ICMP will be through the sendto method and reception through the recvfrom method. This will occur on a single thread (though another thread can be used to break transmission and reception apart). Reception of a message will further be filtered by matching the ID of the received message to the ID that was transmitted. The ID stored will be the running process ID of the application. If an ICMP_ECHOREPLY message is received and the ID of the message and the stored ID match, then a counter is incremented until five (4) has been reached (the counter is zero-based). I will attempt to send a ping, wait for its reply, and repeat this process five (5) times.

The Problem:

After having implemented my design, whenever I ping a particular valid network address (say 192.168.11.15) with an active network participant, I receive ICMP_ECHOREPLY messages for each of the five (5) pings. However, whenever I ping a valid network address (say 192.168.30.30) with inactive network participants (meaning no device is connected to the particular address), I get one (1) ICMP_DEST_UNREACH, and four (4) ICMP_ECHOREPLY messages. The ID in the reply messages match the ID stored within the software. Whenever I perform a 'ping 192.168.30.30' from the commandline, I get 'From 192.168.40.50 icmp_seq=xx Destination Host Unreachable'. Am I not supposed to be receiving ICMP_DEST_UNREACH messages instead of ICMP_ECHOREPLY messages?

The Code:

Ping.h:

#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/ipmc.h>
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <string>
#include <cstring>
#include <netdb.h>

class Ping
{
    public:
        Ping(std::string host) : _host(host) {}
        ~Ping() {}

        void start()
        {
            int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
            if(sock < 0)
            {
                printf("Failed to create socket!\n");
                close(sock);
                exit(1);
            }

            setuid(getuid());

            sockaddr_in pingaddr;
            memset(&pingaddr, 0, sizeof(sockaddr_in));
            pingaddr.sin_family = AF_INET;

            hostent *h = gethostbyname(_host.c_str());
            if(not h)
            {
                printf("Failed to get host by name!\n");
                close(sock);
                exit(1);
            }

            memcpy(&pingaddr.sin_addr, h->h_addr, sizeof(pingaddr.sin_addr));

            // Set the ID of the sender (will go into the ID of the echo msg)
            int pid = getpid();

            // Only want to receive the following messages
            icmp_filter filter;
            filter.data = ~((1<<ICMP_SOURCE_QUENCH) |
                            (1<<ICMP_DEST_UNREACH) |
                            (1<<ICMP_TIME_EXCEEDED) |
                            (1<<ICMP_REDIRECT) |
                            (1<<ICMP_ECHOREPLY));
            if(setsockopt(sock, SOL_RAW, ICMP_FILTER, (char *)&filter, sizeof(filter)) < 0)
            {
                perror("setsockopt(ICMP_FILTER)");
                exit(3);
            }

            // Number of valid echo receptions
            int nrec = 0;

            // Send the packet
            for(int i = 0; i < 5; ++i)
            {
                char packet[sizeof(icmphdr)];
                memset(packet, 0, sizeof(packet));

                icmphdr *pkt = (icmphdr *)packet;
                pkt->type = ICMP_ECHO;
                pkt->code = 0;
                pkt->checksum = 0;
                pkt->un.echo.id = htons(pid & 0xFFFF);
                pkt->un.echo.sequence = i;
                pkt->checksum = checksum((uint16_t *)pkt, sizeof(packet));

                int bytes = sendto(sock, packet, sizeof(packet), 0, (sockaddr *)&pingaddr, sizeof(sockaddr_in));
                if(bytes < 0)
                {
                    printf("Failed to send to receiver\n");
                    close(sock);
                    exit(1);
                }
                else if(bytes != sizeof(packet))
                {
                    printf("Failed to write the whole packet --- bytes: %d, sizeof(packet): %d\n", bytes, sizeof(packet));
                    close(sock);
                    exit(1);
                }

                while(1)
                {
                    char inbuf[192];
                    memset(inbuf, 0, sizeof(inbuf));

                    int addrlen = sizeof(sockaddr_in);
                    bytes = recvfrom(sock, inbuf, sizeof(inbuf), 0, (sockaddr *)&pingaddr, (socklen_t *)&addrlen);
                    if(bytes < 0)
                    {
                        printf("Error on recvfrom\n");
                        exit(1);
                    }
                    else
                    {
                        if(bytes < sizeof(iphdr) + sizeof(icmphdr))
                        {
                            printf("Incorrect read bytes!\n");
                            continue;
                        }

                        iphdr *iph = (iphdr *)inbuf;
                        int hlen = (iph->ihl << 2);
                        bytes -= hlen;

                        pkt = (icmphdr *)(inbuf + hlen);
                        int id = ntohs(pkt->un.echo.id);
                        if(pkt->type == ICMP_ECHOREPLY)
                        {
                            printf("    ICMP_ECHOREPLY\n");
                            if(id == pid)
                            {
                                nrec++;
                                if(i < 5) break;
                            }
                        }
                        else if(pkt->type == ICMP_DEST_UNREACH)
                        {
                            printf("    ICMP_DEST_UNREACH\n");
                            // Extract the original data out of the received message
                            int offset = sizeof(iphdr) + sizeof(icmphdr) + sizeof(iphdr);
                            if(((bytes + hlen) - offset) == sizeof(icmphdr))
                            {
                                icmphdr *p = reinterpret_cast<icmphdr *>(inbuf + offset);
                                id = ntohs(p->un.echo.id);
                                if(origid == pid)
                                {
                                    printf("        IDs match!\n");
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            printf("nrec: %d\n", nrec);
        }

    private:
        int32_t checksum(uint16_t *buf, int32_t len)
        {
            int32_t nleft = len;
            int32_t sum = 0;
            uint16_t *w = buf;
            uint16_t answer = 0;

            while(nleft > 1)
            {
                sum += *w++;
                nleft -= 2;
            }

            if(nleft == 1)
            {
                *(uint16_t *)(&answer) = *(uint8_t *)w;
                sum += answer;
            }

            sum = (sum >> 16) + (sum & 0xFFFF);
            sum += (sum >> 16);
            answer = ~sum;

            return answer;
        }

        std::string _host;
};

main.cpp:

#include "Ping.h"

int main()
{
//     Ping ping("192.168.11.15");
    Ping ping("192.168.30.30");
    ping.start();

    while(1) sleep(10);
}

In order to compile, just type 'g++ main.cpp -o ping' into the command-line of a Linux box, and it should compile (that is, if all of the source code is installed).

Conclusion:

Can anyone tell me why I am receiving one (1) ICMP_DEST_UNREACH and four (4) ICMP_ECHOREPLY messages from a device that isn't on that particular network address?

NOTE: You can change the network IP address from the main.cpp file. Just change the IP to a device that actually exists on your network or a device that doesn't exist on your network.

I'm also not interested in criticisms about coding style. I know it isn't pretty, has 'C' style casting mixed with C++ casts, has poor memory management, etc, but this is only prototype code. It isn't meant to be pretty.

Answer

Davide Berra picture Davide Berra · Jan 16, 2013

Ok i found the error. Look at this two lines.

int bytes = sendto(sock, packet, sizeof(packet), 0, (sockaddr *)&pingaddr, sizeof(sockaddr_in));

bytes = recvfrom(sock, inbuf, sizeof(inbuf), 0, (sockaddr *)&pingaddr, (socklen_t *)&addrlen);

both functions uses pingaddr pointer as parameter, but this should avoided because in the sendto() function is used to point the destination IP of the icmp packet but in the recvfrom() is used to get back the IP of the host that's replying.

Let's say pingaddr is set with an IP not reachable. After your first ICMP_REQUEST the first gateway will reply to you with a ICMP_DEST_UNREACH and... here comes the error... when recvfrom is called, pingaddr structure will be overwritten with the IP of the gateway.

SO... from the second ping you'll be pointing to the gateway IP that, obviously, exists and will reply with a ICMP_ECHOREPLY.

SOLUTION:

avoid pass the same sockaddr_in structure pointer to both sendto() and recvfrom().