最近接到一個需求,需求背景是這樣的:目前Windows平台下本身都有tracert和ping的實現,而且可以直接在cmd下使用。
需求中有兩個要求:
1. Windows平台中的tracert執行速度太慢,一次tracert可能要花十幾分鍾。所以,需要一個快速的tracert實現。
2.將tracert和ping結合起來:tracert出來的節點,需要ping一下,看看當前節點的連通性。
接到這個需求后,開始的時候有點無從下手,只能先從研究tracert的原理出發,搞清楚原理后才能去提升速率。
tracert原理:
Tracert 命令用 IP 生存時間 (TTL) 字段和 ICMP 錯誤消息來確定從一個主機到網絡上其他主機的路由。
首先,tracert送出一個TTL是1的IP 數據包到目的地,當路徑上的第一個路由器收到這個數據包時,它將TTL減1。此時,TTL變為0,所以該路由器會將此數據包丟掉,並送回一個「ICMP time exceeded」消息(包括發IP包的源地址,IP包的所有內容及路由器的IP地址),tracert 收到這個消息后,便知道這個路由器存在於這個路徑上,接着tracert 再送出另一個TTL是2 的數據包,發現第2 個路由器,以此往復。。。。。
tracert 每次將送出的數據包的TTL 加1來發現另一個路由器,這個重復的動作一直持續到某個數據包 抵達目的地。當數據包到達目的地后,該主機則不會送回ICMP time exceeded消息,一旦到達目的地,由於tracert通過UDP數據包向不常見端口(30000以上)發送數據包,因此會收到「ICMP port unreachable」消息,故可判斷到達目的地。
tracert 有一個固定的時間等待響應(ICMP TTL到期消息)。如果這個時間過了,它將打印出一系列的*號表明:在這個路徑上,這個設備不能在給定的時間內發出ICMP TTL到期消息的響應。然后,Tracert給TTL記數器加1,繼續進行。
讀懂tracert的原理后,其實不難發現,TTL=1到TTL=30的請求其實關聯性不大,我們要加速實現tracert可以從這里入手。
看一下tracert結果的格式:可以發現,每一次tracert最多會有30個節點,每個節點包含3個時間數據,經查閱是三次請求的響應時間
網上查閱了一些資料,發現可以基於ICMP.dll實現tracert,網友給出了代碼:

1 //TraceRoute3.cpp 2 #include <stdio.h> 3 #include <winsock2.h> 4 #include <windows.h> 5 #include <IPHlpApi.h> 6 //增加靜態鏈接庫ws2_32.lib 7 #pragma comment(lib,"ws2_32.lib") 8 //聲明3個函數類型的指針 9 typedef HANDLE (WINAPI *lpIcmpCreateFile)(VOID); 10 typedef BOOL (WINAPI *lpIcmpCloseHandle)(HANDLE IcmpHandle); 11 typedef DWORD (WINAPI *lpIcmpSendEcho)( 12 HANDLE IcmpHandle, 13 IPAddr DestinationAddress, 14 LPVOID RequestData, 15 WORD RequestSize, 16 PIP_OPTION_INFORMATION RequestOptions, 17 LPVOID ReplyBuffer, 18 DWORD ReplySize, 19 DWORD Timeout 20 ); 21 int main(int argc,char* argv[]){ 22 if(argc!=2){ 23 printf("Usage: %s destIP/n",argv[0]); 24 exit(-1); 25 } 26 WSADATA wsa; 27 if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ 28 printf("WSAStartup failed./n"); 29 exit(-1); 30 } 31 //轉換IP地址到整數 32 unsigned long ip = inet_addr(argv[1]); 33 if(ip==INADDR_NONE){ 34 //用戶可能輸入的是域名 35 hostent* pHost = gethostbyname(argv[1]); 36 //如果域名無法解析 37 if(pHost==NULL){ 38 printf("Invalid IP or domain name: %s/n", argv[1]); 39 exit(-1); 40 } 41 //取域名的第一個IP地址 42 ip = *(unsigned long*)pHost->h_addr_list[0]; 43 printf("trace route to %s(%s)/n/n",argv[1],inet_ntoa(*(in_addr*)&ip)); 44 }else{ 45 printf("trace route to %s/n/n",argv[1]); 46 } 47 //載入ICMP.DLL動態庫 48 HMODULE hIcmpDll = LoadLibrary("icmp.dll"); 49 if(hIcmpDll==NULL){ 50 printf("fail to load icmp.dll/n"); 51 exit(-1); 52 } 53 //定義3個函數指針 54 lpIcmpCreateFile IcmpCreateFile; 55 lpIcmpCloseHandle IcmpCloseHandle; 56 lpIcmpSendEcho IcmpSendEcho; 57 //從ICMP.DLL中獲取所需的函數入口地址 58 IcmpCreateFile = (lpIcmpCreateFile)GetProcAddress(hIcmpDll,"IcmpCreateFile"); 59 IcmpCloseHandle = (lpIcmpCloseHandle)GetProcAddress(hIcmpDll,"IcmpCloseHandle"); 60 IcmpSendEcho = (lpIcmpSendEcho)GetProcAddress(hIcmpDll,"IcmpSendEcho"); 61 //打開ICMP句柄 62 HANDLE hIcmp; 63 if ((hIcmp = IcmpCreateFile()) == INVALID_HANDLE_VALUE){ 64 printf("/tUnable to open ICMP file./n"); 65 exit(-1); 66 } 67 //設置IP報頭的TTL值 68 IP_OPTION_INFORMATION IpOption; 69 ZeroMemory(&IpOption,sizeof(IP_OPTION_INFORMATION)); 70 IpOption.Ttl = 1; 71 //設置要發送的數據 72 char SendData[32]; 73 memset(SendData,'0',sizeof(SendData)); 74 //設置接收緩沖區 75 char ReplyBuffer[sizeof(ICMP_ECHO_REPLY)+32]; 76 PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer; 77 BOOL bLoop = TRUE; 78 int iMaxHop = 30; 79 while(bLoop && iMaxHop--){ 80 printf("%2d: ",IpOption.Ttl); 81 //發送ICMP回顯請求 82 if(IcmpSendEcho(hIcmp,(IPAddr)ip, SendData, sizeof(SendData), &IpOption, 83 ReplyBuffer, sizeof(ReplyBuffer), 3000)!=0){ 84 if(pEchoReply->RoundTripTime==0){ 85 printf("/t<1ms"); 86 }else{ 87 printf("/t%dms",pEchoReply->RoundTripTime); 88 } 89 printf("/t%s/n",inet_ntoa(*(in_addr*)&(pEchoReply->Address))); 90 //判斷是否完成路由路徑探測 91 if((unsigned long)pEchoReply->Address==ip){ 92 printf("/nTrace complete./n"); 93 bLoop = FALSE; 94 } 95 }else{ 96 printf("/t*/tRequest time out./n"); 97 } 98 IpOption.Ttl++; 99 } 100 IcmpCloseHandle(hIcmp); 101 FreeLibrary(hIcmpDll); 102 WSACleanup(); 103 return 0; 104 }
運行后可以看到,這里實現了和windows的dos里面一模一樣的tracert,但距離我們想要的“快速”還有一定差距。
我們可以在這個基礎上做改進:使用多線程來實現最多30跳躍點的請求,每次請求分別執行三次,最后我們可以直接使用90個線程來執行程序,最終將結果
按固定位置填充到定義的數組中。
至於結合ping就比較簡單了,直接調用 windows下的ping命令。
最后,需要注意的是,我們使用c++做成一個exe供程序直接調用,這里需要接收外部參數:網址,是否執行ping命令,ping命令的-l參數,ping命令的-n參數
最終結果:
最終效果非常好,執行效率相比windows下的tracert快了非常多,而且非常方便。
注意:在某些系統上執行的時候會報錯,需要添加兩個dll文件:msvcp120d.dll和msvcr120d.dll到本機的windows/system32或者windows/SysWOW64下
小工具分享給大家,下載地址