Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Ping and Traceroute

4.83/5 (25 votes)
21 Feb 2008CPOL2 min read 1   5.2K  
In this article, we will see how Ping and Tracert networking tools work and we will create our own replicas for them.
Ping_and_Tracert_Codes

Ping

Ping is an old Unix utility which is pretty useful even today. The basic idea behind Ping is to find out whether the network is reachable and alive or not. Ping basically sends an ICMP echo request and then waits for an ICMP echo response; the roundtrip time is calculated from the time at which the request was sent and the time at which a response is received. For more information on ICMP, please see RFC 792.

To send out ICMP requests, we create a socket which will use ICMP as the protocol:

C++
SOCKET sock;

//Create a raw socket which will use ICMP
sock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);

Now to send an ICMP request, first of all we populate the ICMP header structure:

C++
struct ICMPheader
{
    unsigned char    byType;
    unsigned char    byCode;
    unsigned short    nChecksum;
    unsigned short    nId;
    unsigned short    nSequence;
};

ICMPheader sendHdr;
sendHdr.byCode = 0;       //Zero for ICMP echo and reply messages

//Sequence number is incremented with each request, this should be different
//so we can match a response with a request
sendHdr.nSequence = htons (nSequence++);    
sendHdr.byType = 8;       //Eight for ICMP echo message
sendHdr.nChecksum = 0;    //Checksum is calculated later on

Next we fill the ICMP header and message data into a buffer to be sent out:

C++
//Create the message buffer, which is big enough to store the header and the 
//message data
pSendBuffer = new char [sizeof (ICMPheader) + nMessageSize];

//Copy the message header in the buffer
memcpy_s (pSendBuffer, sizeof (ICMPheader), &sendHdr, sizeof (ICMPheader));

//Fill the message with some arbitrary value
memset (pSendBuffer + sizeof (ICMPheader), 'x', nMessageSize);

Now we calculate the checksum of the ICMP header and the message being sent with it. The checksum for ICMP messages is calculated in the same way as for IP headers.

C++
//Calculate checksum over ICMP header and message data
sendHdr.nChecksum = 
    htons (CalcChecksum (pSendBuffer, sizeof (ICMPheader) + nMessageSize)); 
   
//Copy the message header back into the buffer
memcpy_s (pSendBuffer, sizeof (ICMPheader), &sendHdr, sizeof (ICMPheader));

We are all ready to send the ICMP echo request, so here we do that:

C++
nResult = sendto (sock, pSendBuffer, sizeof (ICMPheader) + 
            nMessageSize, 0, (SOCKADDR *)&dest, sizeof (SOCKADDR_IN));

With an ICMP request sent out, we now wait for the timeout interval (default is 5 seconds) to get a response. We use I/O socket model for this.

C++
timeval timeInterval = {0, 0};
//Set the timeout interval for the select call to expire
timeInterval.tv_usec = nTimeOut * 1000;

FD_ZERO (&fdRead);
FD_SET (sock, &fdRead);

select (0, &fdRead, NULL, NULL, &timeInterval);

If we get a response, then the response is parsed to see if it has the correct checksum, the sequence ID is the same as in the request which was sent out, etc. One point to note here is that the bytes received contain the IP header + ICMP header + message, so we need to take them out accordingly and then proceed. The following code explains it further:

C++
//We got a response so we construct the ICMP header and message out of it
ICMPheader recvHdr;
char *pICMPbuffer = NULL;

//The response includes the IP header as well, so we move 20 bytes ahead to 
//read the ICMP header
pICMPbuffer = pRecvBuffer + sizeof(IPheader);

//ICMP message length is calculated by subtracting the IP header size from 
//the total bytes received
int nICMPMsgLen = nResult - sizeof(IPheader);

//Construct the ICMP header
memcpy_s (&recvHdr, sizeof (recvHdr), pICMPbuffer, sizeof (recvHdr));

//Construct the IP header from the response
IPheader ipHdr;
memcpy_s (&ipHdr, sizeof (ipHdr), pRecvBuffer, sizeof (ipHdr));

recvHdr.nId = recvHdr.nId;
recvHdr.nSequence = recvHdr.nSequence;
recvHdr.nChecksum = ntohs (recvHdr.nChecksum);

//Check if the response is an echo reply, transaction ID and sequence number 
//are same as for the request, and that the checksum is correct
if (recvHdr.byType == 0 &&
    recvHdr.nId == sendHdr.nId &&
    recvHdr.nSequence == sendHdr.nSequence &&
    ValidateChecksum (pICMPbuffer, nICMPMsgLen) && 
memcmp (pSendBuffer + sizeof(ICMPheader), pRecvBuffer + sizeof(ICMPheader) + 
        sizeof(IPheader), nResult - sizeof (ICMPheader) - sizeof(IPheader)) == 0)
{
    //All's OK
    //So just calculate the round trip time and print it
}

And lastly, we repeat the above process (sending ICMP echo request and receiving responses) three or four times, as asked by the user, to calculate Ping statistics.

Tracert

Tracert is another networking utility which determines the route taken by packets across an IP network. The basic idea behind tracert is that it sends ICMP requests and increments their TTL value in the IP header by one on each request. The first request has a TTL of one, the next one two, and so on. When the first request reaches the next host, the host ignores the request and sends an ICMP time exceeded response. By seeing this response we are able to determine the host and thus able to produce a lists of hosts by sending incremented TTL values.

The code for Tracert is very similar to Ping. The only point of interest here is that we need to modify the IP header to change the TTL values. This is done using setsockopt with IPPROTO_IP and IP_TTL values as shown below:

C++
setsockopt (sock, IPPROTO_IP, IP_TTL, (char *)&nTTL, sizeof (nTTL));

Conclusion

I mainly wrote this article for learning purposes and I hope that the code in this article is useful to others as well. Please be kind enough to notify me about any mistakes or a better way to do things.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)