天天看點

VC++實作路由跟蹤

 tracert指令及用法

Tracert(跟蹤路由)是路由跟蹤實用程式,用于确定 IP 資料報通路目标所采取的路徑。

Tracert 指令用 IP 生存時間 (TTL) 字段和 ICMP 錯誤消息來确定從一個主機到網絡上其他主機的路由。

 Tracert 工作原理 通過向目标發送不同 IP 生存時間 (TTL) 值的“Internet 控制消息協定 (ICMP)”回應資料包,Tracert 診斷程式确定到目标所采取的路由。要求路徑上的每個路由器在轉發資料包之前至少将資料包上的 TTL 遞減 1。資料包上的 TTL 減為 0 時,路由器應該将“ICMP 已逾時”的消息發回源系統。

 Tracert 先發送 TTL 為 1 的回應資料包,并在随後的每次發送過程将 TTL 遞增 1,直到目标響應或 TTL 達到最大值,進而确定路由。

通過檢查中間路由器發回的“ICMP 已逾時”的消息确定路由。某些路由器不經詢問直接丢棄 TTL 過期的資料包,這在 Tracert 實用程式中看不到。

 Tracert 指令按順序列印出傳回“ICMP 已逾時”消息的路徑中的近端路由器接口清單。

如果使用 -d 選項,則 Tracert 實用程式不在每個 IP 位址上查詢 DNS。

在下例中,資料包必須通過兩個路由器(10.0.0.1 和 192.168.0.1)才能到達主機 172.16.0.99。

主機的預設網關是 10.0.0.1,192.168.0.0 網絡上的路由器的 IP 位址是 192.168.0.1。

/*----------------------------------------------------------
功能說明:該程式簡單實作了Windows作業系統的tracert指令功能,
      可以輸出IP封包從本機出發到達目的主機所經過的路由資訊。
注意:程式編譯時應使用1位元組對齊方式調整邊界!
-----------------------------------------------------------*/
#include <iostream.h>
#include <iomanip.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include "itracert.h"

////////////////////////////////////////////////////////

int main(int argc, char* argv[])
{
	//檢查指令行參數
	if (argc != 2)
	{
		cerr << "\nUsage: itracert ip_or_hostname\n";
		return -1;
	}

	//初始化winsock2環境
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		cerr << "\nFailed to initialize the WinSock2 DLL\n"
			 << "error code: " << WSAGetLastError() << endl;
		return -1;
	}

	//将指令行參數轉換為IP位址
	u_long ulDestIP = inet_addr(argv[1]);
	if (ulDestIP == INADDR_NONE)
	{
		//轉換不成功時按域名解析
		hostent* pHostent = gethostbyname(argv[1]);
		if (pHostent)
		{
			ulDestIP = (*(in_addr*)pHostent->h_addr).s_addr;

			//輸出螢幕資訊
			cout << "\nTracing route to " << argv[1] 
				 << " [" << inet_ntoa(*(in_addr*)(&ulDestIP)) << "]"
				 << " with a maximum of " << DEF_MAX_HOP << " hops.\n" << endl;
		}
		else //解析主機名失敗
		{
			cerr << "\nCould not resolve the host name " << argv[1] << '\n'
				 << "error code: " << WSAGetLastError() << endl;
			WSACleanup();
			return -1;
		}
	}
	else
	{
		//輸出螢幕資訊
		cout << "\nTracing route to " << argv[1] 
			 << " with a maximum of " << DEF_MAX_HOP << " hops.\n" << endl;
	}

	//填充目的Socket位址
	sockaddr_in destSockAddr;
	ZeroMemory(&destSockAddr, sizeof(sockaddr_in));
	destSockAddr.sin_family = AF_INET;
	destSockAddr.sin_addr.s_addr = ulDestIP;

	//使用ICMP協定建立Raw Socket
	SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (sockRaw == INVALID_SOCKET)
	{
		cerr << "\nFailed to create a raw socket\n"
			 << "error code: " << WSAGetLastError() << endl;
		WSACleanup();
		return -1;
	}
	//設定端口屬性
	int iTimeout = DEF_ICMP_TIMEOUT;

	if (setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeout, sizeof(iTimeout)) == SOCKET_ERROR)
	{
		cerr << "\nFailed to set recv timeout\n"
			 << "error code: " << WSAGetLastError() << endl;
		closesocket(sockRaw);
		WSACleanup();
		return -1;
	}
	if (setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&iTimeout, sizeof(iTimeout)) == SOCKET_ERROR)
	{
		cerr << "\nFailed to set send timeout\n"
			 << "error code: " << WSAGetLastError() << endl;
		closesocket(sockRaw);
		WSACleanup();
		return -1;
	}


	//建立ICMP包發送緩沖區和接收緩沖區
	char IcmpSendBuf[sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE];
	memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf));
	char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE];
	memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf));

	//填充待發送的ICMP包
	ICMP_HEADER* pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;
	pIcmpHeader->type = ICMP_ECHO_REQUEST;
	pIcmpHeader->code = 0;
	pIcmpHeader->id = (USHORT)GetCurrentProcessId();
	memset(IcmpSendBuf+sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);

	//開始探測路由
	DECODE_RESULT stDecodeResult;
	BOOL bReachDestHost = FALSE;
	USHORT usSeqNo = 0;
	int iTTL = 1;
	int iMaxHop = DEF_MAX_HOP;
	while (!bReachDestHost && iMaxHop--)
	{
		//設定IP資料報頭的ttl字段
		setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char*)&iTTL, sizeof(iTTL));

		//輸出目前跳站數作為路由資訊序号
		cout << setw(3) << iTTL << flush;

		//填充ICMP資料報剩餘字段
		((ICMP_HEADER*)IcmpSendBuf)->cksum = 0;
		((ICMP_HEADER*)IcmpSendBuf)->seq = htons(usSeqNo++);
		((ICMP_HEADER*)IcmpSendBuf)->cksum = GenerateChecksum((USHORT*)IcmpSendBuf, sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE);
		
		//記錄序列号和目前時間
		stDecodeResult.usSeqNo = ((ICMP_HEADER*)IcmpSendBuf)->seq;
		stDecodeResult.dwRoundTripTime = GetTickCount();
		
		//發送ICMP的EchoRequest資料報
		if (sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0, 
				   (sockaddr*)&destSockAddr, sizeof(destSockAddr)) == SOCKET_ERROR)
		{
			//如果目的主機不可達則直接退出
			if (WSAGetLastError() == WSAEHOSTUNREACH)
				cout << '\t' << "Destination host unreachable.\n" 
					 << "\nTrace complete.\n" << endl;
			closesocket(sockRaw);
			WSACleanup();
			return 0;
		}

		//接收ICMP的EchoReply資料報
		//因為收到的可能并非程式所期待的資料報,是以需要循環接收直到收到所要資料或逾時
		sockaddr_in from;
		int iFromLen = sizeof(from);
		int iReadDataLen;
		while (1)
		{
			//等待資料到達
			iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 
									0, (sockaddr*)&from, &iFromLen);
			if (iReadDataLen != SOCKET_ERROR) //有資料包到達
			{
				//解碼得到的資料包,如果解碼正确則跳出接收循環發送下一個EchoRequest包
				if (DecodeIcmpResponse(IcmpRecvBuf, iReadDataLen, stDecodeResult))
				{
					if (stDecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr)
						bReachDestHost = TRUE;

					cout << '\t' << inet_ntoa(stDecodeResult.dwIPaddr) << endl;
					break;
				}
			}
			else if (WSAGetLastError() == WSAETIMEDOUT) //接收逾時,列印星号
			{
				cout << setw(9) << '*' << '\t' << "Request timed out." << endl;
				break;
			}
			else
			{
				cerr << "\nFailed to call recvfrom\n"
					 << "error code: " << WSAGetLastError() << endl;
				closesocket(sockRaw);
				WSACleanup();
				return -1;
			}
		}

		//TTL值加1
		iTTL++;
	}
	//輸出螢幕資訊
	cout << "\nTrace complete.\n" << endl;

	closesocket(sockRaw);
	WSACleanup();
	return 0;
}

//産生網際校驗和
USHORT GenerateChecksum(USHORT* pBuf, int iSize) 
{
	unsigned long cksum = 0;
	while (iSize>1) 
	{
		cksum += *pBuf++;
		iSize -= sizeof(USHORT);
	}
	if (iSize) 
		cksum += *(UCHAR*)pBuf;

	cksum = (cksum >> 16) + (cksum & 0xffff);
	cksum += (cksum >> 16);

	return (USHORT)(~cksum);
}


//解碼得到的資料報
BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult)
{
	//檢查資料報大小的合法性
	IP_HEADER* pIpHdr = (IP_HEADER*)pBuf;
	int iIpHdrLen = pIpHdr->hdr_len * 4;
	if (iPacketSize < (int)(iIpHdrLen+sizeof(ICMP_HEADER)))
		return FALSE;

	//按照ICMP包類型檢查id字段和序列号以确定是否是程式應接收的Icmp包
	ICMP_HEADER* pIcmpHdr = (ICMP_HEADER*)(pBuf+iIpHdrLen);
	USHORT usID, usSquNo;
	if (pIcmpHdr->type == ICMP_ECHO_REPLY)
	{
		usID = pIcmpHdr->id;
		usSquNo = pIcmpHdr->seq;
	}
	else if(pIcmpHdr->type == ICMP_TIMEOUT)
	{
		char* pInnerIpHdr = pBuf+iIpHdrLen+sizeof(ICMP_HEADER);		//載荷中的IP頭
		int iInnerIPHdrLen = ((IP_HEADER*)pInnerIpHdr)->hdr_len * 4;//載荷中的IP頭長
		ICMP_HEADER* pInnerIcmpHdr = (ICMP_HEADER*)(pInnerIpHdr+iInnerIPHdrLen);//載荷中的ICMP頭
		usID = pInnerIcmpHdr->id;
		usSquNo = pInnerIcmpHdr->seq;
	}
	else
		return FALSE;

	if (usID != (USHORT)GetCurrentProcessId() || usSquNo !=stDecodeResult.usSeqNo) 
		return FALSE;

	//處理正确收到的ICMP資料報
	if (pIcmpHdr->type == ICMP_ECHO_REPLY ||
		pIcmpHdr->type == ICMP_TIMEOUT)
	{
		//傳回解碼結果
		stDecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
		stDecodeResult.dwRoundTripTime = GetTickCount()-stDecodeResult.dwRoundTripTime;

		//列印螢幕資訊
		if (stDecodeResult.dwRoundTripTime)
			cout << setw(6) << stDecodeResult.dwRoundTripTime << " ms" << flush;
		else
			cout << setw(6) << "<1" << " ms" << flush;

		return TRUE;
	}

	return FALSE;
}