最近接到一个需求,需求背景是这样的:目前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 }
View Code
运行后可以看到,这里实现了和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下