Click here to Skip to main content
15,881,712 members
Articles / Programming Languages / C++

Ping and Traceroute

Rate me:
Please Sign up or sign in to vote.
4.83/5 (25 votes)
21 Feb 2008CPOL2 min read 136.4K   5.1K   65  
In this article, we will see how Ping and Tracert networking tools work and we will create our own replicas for them.
// Tracert.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <cstdio>
#include <winsock2.h>
#include <windows.h>
#include <Ws2tcpip.h>

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

struct IPheader
{
    unsigned char	byVerLen;        
    unsigned char	byTos;       
    unsigned short	nTotalLength;   
    unsigned short	nId;            
    unsigned short	nOffset;        
    unsigned char	byTtl;          
    unsigned char	byProtocol;     
    unsigned short	nChecksum;      
    unsigned int	nSrcAddr;       
    unsigned int	nDestAddr;      
};

using namespace std;

unsigned short CalcChecksum (char *pBuffer, int nLen);
bool ValidateChecksum (char *pBuffer, int nLen);
bool Initialize ();
bool UnInitialize ();
bool ResolveIP (char *pszRemoteHost, char **pszIPAddress);
void PrintUsage ();

int main(int argc, char* argv[])
{
	if (argc < 2 || argc > 6)
	{
		PrintUsage ();
		return 0;
	}

	if (Initialize () == false)
	{
		return -1;
	}

	int nSequence = 0;
	int nMessageSize = 32;	//The message size that the ICMP echo request should carry with it
	int nTimeOut = 3000;	//Request time out for echo request (in milliseconds)
	int nHopCount = 30;	//Number of hops to retry
	int nMaxRetries = 3;
	
	char *pszRemoteIP = NULL, *pSendBuffer = NULL, *pszRemoteHost = NULL;

	pszRemoteHost = argv [1];
	
	for (int i = 2; i < argc; ++i)
	{
		switch (i)
		{
		case 2:			
			nHopCount = atoi (argv [2]);
			break;
		case 3:
			nMessageSize = atoi (argv [3]);
			break;
		case 4:
			nMaxRetries = atoi (argv [4]);
			break;
		case 5:
			nTimeOut = atoi (argv [5]);
			break;
		}
	}
	if (ResolveIP (pszRemoteHost, &pszRemoteIP) == false)
	{
		cerr << endl << "Unable to resolve hostname" << endl;
		return -1;
	}

	cout << "Tracing route to " << pszRemoteHost << " [" << pszRemoteIP << "] over a maximum of " << nHopCount 
		<< " hops." << endl << endl;
	ICMPheader sendHdr;

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

	SOCKADDR_IN destAddr;	//Dest address to send the ICMP request
	destAddr.sin_addr.S_un.S_addr = inet_addr (pszRemoteIP);
	destAddr.sin_family = AF_INET;
	destAddr.sin_port = rand ();	//Pick a random port

	SOCKADDR_IN remoteAddr;
	int nRemoteAddrLen = sizeof (remoteAddr);

	int nResult = 0;
	SYSTEMTIME timeSend, timeRecv;

	fd_set fdRead;

	timeval timeInterval = {0, 0};
	timeInterval.tv_usec = nTimeOut * 1000;
	
	sendHdr.nId = htons (rand ());	//Set the transaction Id
	sendHdr.byCode = 0;	//Zero for ICMP echo and reply messages
	sendHdr.byType = 8;	//Eight for ICMP echo message

	int nHopsTraversed = 0;
	int nTTL = 1;	//The all important TTL value, which will be incremented with each hop

	while (nHopsTraversed < nHopCount &&  
			memcmp (&destAddr.sin_addr, &remoteAddr.sin_addr, sizeof (in_addr)) != 0)
	{
		cout << "  " << nHopsTraversed + 1;

		//Set the TTL value for the message
		if (setsockopt (sock, IPPROTO_IP, IP_TTL, (char *)&nTTL, sizeof (nTTL)) == SOCKET_ERROR)
		{
			cerr << endl << "An error occured in setsockopt operation: " << "WSAGetLastError () = " << WSAGetLastError () << endl;
			UnInitialize ();
			delete []pSendBuffer;
			return -1;
		}

		//Create the message buffer, which is big enough to store the header and the message data
		pSendBuffer = new char [sizeof (ICMPheader) + nMessageSize];
		sendHdr.nSequence = htons (nSequence++);
		sendHdr.nChecksum = 0;	//Checksum is calculated later on

		memcpy_s (pSendBuffer, sizeof (ICMPheader), &sendHdr, sizeof (ICMPheader));	//Copy the message header in the buffer
		memset (pSendBuffer + sizeof (ICMPheader), 'x', nMessageSize);	//Fill the message with some arbitary value

		//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));

		//The number of time the request was sent out
		int nRetries = 0;
		
		IPheader ipHdr;

		//This flag indicates if we got a response to any request it
		//basically helps in determining whether all requests timed out or not, and
		//if they did then we print an appropriate message
		bool bGotAResponse = false;	
		while (nRetries < nMaxRetries)
		{
			nResult = sendto (sock, pSendBuffer, sizeof (ICMPheader) + nMessageSize, 0, (SOCKADDR *)&destAddr, 
				sizeof (SOCKADDR_IN));

			//Save the time at which the ICMP echo message was sent
			::GetSystemTime (&timeSend);

			if (nResult == SOCKET_ERROR)
			{
				cerr << endl << "An error occured in sendto operation: " << "WSAGetLastError () = " << WSAGetLastError () << endl;
				UnInitialize ();
				delete []pSendBuffer;
				return -1;
			}
			
			FD_ZERO (&fdRead);
			FD_SET (sock, &fdRead);

			if ((nResult = select (0, &fdRead, NULL, NULL, &timeInterval))
				== SOCKET_ERROR)
			{
				cerr << endl << "An error occured in select operation: " << "WSAGetLastError () = " << 
					WSAGetLastError () << endl;
				delete []pSendBuffer;
				return -1;
			}

			if (nResult > 0 && FD_ISSET (sock, &fdRead))
			{
				//Allocate a large buffer to store the response
				char *pRecvBuffer = new char [1500];

				//We got a response
				if ((nResult = recvfrom (sock, pRecvBuffer, 1500, 0, (SOCKADDR *)&remoteAddr, &nRemoteAddrLen)) 
					== SOCKET_ERROR)
				{
					cerr << endl << "An error occured in recvfrom operation: " << "WSAGetLastError () = " << 
						WSAGetLastError () << endl;
					UnInitialize ();
					delete []pSendBuffer;
					delete []pRecvBuffer;
					return -1;
				}	

				//Get the time at which response is received
				::GetSystemTime (&timeRecv);

				bGotAResponse = true;

				//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
				memcpy_s (&ipHdr, sizeof (ipHdr), pRecvBuffer, sizeof (ipHdr));

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

				//Check if the checksum is correct
				if (ValidateChecksum (pICMPbuffer, nICMPMsgLen))
				{
					//All's OK
					int nSec = timeRecv.wSecond - timeSend.wSecond;
					if (nSec < 0)
					{
						nSec = nSec + 60;
					}

					int nMilliSec = abs (timeRecv.wMilliseconds - timeSend.wMilliseconds);

					int nRoundTripTime = 0;
					nRoundTripTime = abs (nSec * 1000 - nMilliSec);

					cout << '\t' << nRoundTripTime << " ms";
				}
				else
				{
					//There was an error in the response
					cout << "\t!";					
				}

				delete []pRecvBuffer;
			}
			else
			{
				//Request timed out so we try again
				cout << "\t*";
			}
			++nRetries;
		}

		if (bGotAResponse == false)
		{
			//Since we didn't get any ICMP time exceeded message from this server so 
			//we try with the next one, with the TTL value being incremented
			cout << "\tRequest timed out.";
		}
		else
		{
			//Print the hop's IP and resolved name
			in_addr in;
			in.S_un.S_addr = ipHdr.nSrcAddr;

			char *pszSrcAddr = inet_ntoa (in);
			char szHostName [NI_MAXHOST];

			if (getnameinfo((SOCKADDR*)&remoteAddr,
					        sizeof (SOCKADDR_IN),
						    szHostName,
							NI_MAXHOST, 
							NULL, 
							0, 
							NI_NUMERICSERV) == SOCKET_ERROR)
			{
				strncpy_s (szHostName, NI_MAXHOST, "Error resolving host name", _TRUNCATE);
			}
			cout << '\t' << szHostName << " [" << pszSrcAddr << "]";
		}

		cout << endl << '\r';
		++nHopsTraversed;
		++nTTL;

		delete []pSendBuffer;
	}

	if (UnInitialize () == false)
	{
		return -1;
	}

	cout << endl << "Trace complete." << endl;

	return 0;
}

unsigned short CalcChecksum (char *pBuffer, int nLen)
{
	//Checksum for ICMP is calculated in the same way as for
	//IP header

	//This code was taken from: http://www.netfor2.com/ipsum.htm

	unsigned short nWord;
	unsigned int nSum = 0;
	int i;
    
	//Make 16 bit words out of every two adjacent 8 bit words in the packet
	//and add them up
	for (i = 0; i < nLen; i = i + 2)
	{
		nWord =((pBuffer [i] << 8)& 0xFF00) + (pBuffer [i + 1] & 0xFF);
		nSum = nSum + (unsigned int)nWord;	
	}
	
	//Take only 16 bits out of the 32 bit sum and add up the carries
	while (nSum >> 16)
	{
		nSum = (nSum & 0xFFFF) + (nSum >> 16);
	}

	//One's complement the result
	nSum = ~nSum;
	
	return ((unsigned short) nSum);
}

bool ValidateChecksum (char *pBuffer, int nLen)
{
	unsigned short nWord;
	unsigned int nSum = 0;
	int i;
    
	//Make 16 bit words out of every two adjacent 8 bit words in the packet
	//and add them up
	for (i = 0; i < nLen; i = i + 2)
	{
		nWord =((pBuffer [i] << 8)& 0xFF00) + (pBuffer [i + 1] & 0xFF);
		nSum = nSum + (unsigned int)nWord;	
	}
	
	//Take only 16 bits out of the 32 bit sum and add up the carries
	while (nSum >> 16)
	{
		nSum = (nSum & 0xFFFF) + (nSum >> 16);
	}

	//To validate the checksum on the received message we don't complement the sum
	//of one's complement
	//One's complement the result
	//nSum = ~nSum;
	
	//The sum of one's complement should be 0xFFFF
	return ((unsigned short)nSum == 0xFFFF);
}

bool Initialize ()
{
	//Initialize WinSock
	WSADATA wsaData;

	if (WSAStartup (MAKEWORD (2, 2), &wsaData) == SOCKET_ERROR)
	{
		cerr << endl << "An error occured in WSAStartup operation: " << "WSAGetLastError () = " << WSAGetLastError () << endl;
		return false;
	}

	SYSTEMTIME time;
	::GetSystemTime (&time);

	//Seed the random number generator with current millisecond value
	srand (time.wMilliseconds);

	return true;
}

bool UnInitialize ()
{
	//Cleanup
	if (WSACleanup () == SOCKET_ERROR)
	{
		cerr << endl << "An error occured in WSACleanup operation: WSAGetLastError () = " << WSAGetLastError () << endl;
		return false;
	}

	return true;
}

bool ResolveIP (char *pszRemoteHost, char **pszIPAddress)
{
	hostent *pHostent = gethostbyname (pszRemoteHost);
	if (pHostent == NULL)
	{
		cerr << endl << "An error occured in gethostbyname operation: WSAGetLastError () = " << WSAGetLastError () << endl;
		return false;
	}

	in_addr in;
	memcpy_s (&in, sizeof (in_addr), pHostent->h_addr_list [0], sizeof (in_addr));
	*pszIPAddress = inet_ntoa (in);

	return true;
}

void PrintUsage ()
{
	cout << "Usage: tracert r h b n t" << endl << endl;
	cout << "  r - Remote host" << endl;
	cout << "  h - Maximum number of hops" << endl;
	cout << "  b - Bytes to send" << endl;
	cout << "  n - Number of requests to send" << endl;
	cout << "  t - Timeout after these many milliseconds" << endl << endl;

	cout << "\rtracert microsoft.com 30 32 3 3000" << endl << endl;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions