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:
SOCKET sock;
sock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
Now to send an ICMP request, first of all we populate the ICMP header structure:
struct ICMPheader
{
unsigned char byType;
unsigned char byCode;
unsigned short nChecksum;
unsigned short nId;
unsigned short nSequence;
};
ICMPheader sendHdr;
sendHdr.byCode = 0;
sendHdr.nSequence = htons (nSequence++);
sendHdr.byType = 8; sendHdr.nChecksum = 0;
Next we fill the ICMP header and message data into a buffer to be sent out:
pSendBuffer = new char [sizeof (ICMPheader) + nMessageSize];
memcpy_s (pSendBuffer, sizeof (ICMPheader), &sendHdr, sizeof (ICMPheader));
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.
sendHdr.nChecksum =
htons (CalcChecksum (pSendBuffer, sizeof (ICMPheader) + nMessageSize));
memcpy_s (pSendBuffer, sizeof (ICMPheader), &sendHdr, sizeof (ICMPheader));
We are all ready to send the ICMP echo request, so here we do that:
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.
timeval timeInterval = {0, 0};
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:
ICMPheader recvHdr;
char *pICMPbuffer = NULL;
pICMPbuffer = pRecvBuffer + sizeof(IPheader);
int nICMPMsgLen = nResult - sizeof(IPheader);
memcpy_s (&recvHdr, sizeof (recvHdr), pICMPbuffer, sizeof (recvHdr));
IPheader ipHdr;
memcpy_s (&ipHdr, sizeof (ipHdr), pRecvBuffer, sizeof (ipHdr));
recvHdr.nId = recvHdr.nId;
recvHdr.nSequence = recvHdr.nSequence;
recvHdr.nChecksum = ntohs (recvHdr.nChecksum);
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)
{
}
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:
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.