TraceRoute程序的实现主要涉及IP头部生存时间(time to live, TTL)字段的使用。
设置TTL字段的目的是为了防止数据报由于选路错误或其他软硬件原因从而导致在网络中无休止的流动,TTL字段指定了数据报的生存时间。TTL的初始值由源主机设置,当一份数据报经过路由器时,处理该数据报的路由器都需要把TTL值减去数据报在路由器中停留的秒数。但事实上大多数路由器只是简单地将TTL值减1,因此TTL字段最终被实现为一个跳站计数器。当TTL字段的值被减为0时,路由器就不会转发该数据报,而是将其丢弃,并产生一份ICMP超时差错报文发往源主机以通知错误的发生。TraceRoute程序的关键就在于返回的这份ICMP超时差错报文的源地址就是途经路由器的IP地址。由此,通过依次递增TTL字段的值,就可以得到一份数据报在其传输路径上所经过的路由信息。
TraceRoute程序在具体实现时,是令其向目的主机发送一个ICMP回显请求(Echo request)消息,并重复递增IP头部TTL字段的值。刚开始的时候TTL等于1,这样当该数据报抵达途中的第一个路由器时,TTL的值就被减为0,导致发生超时错误,因此该路由器生成一份ICMP超时差错报文返回给源主机。随后,主机将数据报的TTL值递增1,以便IP报文能传递到下一个路由器,下一个路由器将会生成ICMP超时超时差错报文返回给源主机。不断重复这个过程,直到数据报到达最终的目的主机,此时目的主机将返回ICMP回显应答(Echo replay)消息。这样,源主机只需对返回的每一份ICMP报文进行解析处理,就可以掌握数据报从源主机到达目的主机途中所经过的路由信息。
本程序使用Windows IP帮助函数发送ICMP回显请求报文,实现tracert的功能,探测每一跳路由器的IP地址和往返时间。
#include <stdio.h> #include <winsock2.h> #include <windows.h> #include <IPHlpApi.h> #include <IcmpAPI.h> #pragma comment(lib,"Iphlpapi.lib") #pragma comment(lib,"ws2_32.lib") int main(int argc,char* argv[]){ if(argc!=2){ printf("Usage: %s destIP/n",argv[0]); exit(-1); } WSADATA wsa; if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ printf("WSAStartup failed./n"); exit(-1); } //转换IP地址到整数 unsigned long ip = inet_addr(argv[1]); if(ip==INADDR_NONE){ //用户可能输入的是域名 hostent* pHost = gethostbyname(argv[1]); //如果域名无法解析 if(pHost==NULL){ printf("Invalid IP or domain name: %s/n", argv[1]); exit(-1); } //取域名的第一个IP地址 ip = *(unsigned long*)pHost->h_addr_list[0]; printf("trace route to %s(%s)/n/n",argv[1],inet_ntoa(*(in_addr*)&ip)); }else{ printf("trace route to %s/n/n",argv[1]); } //打开ICMP句柄 HANDLE hIcmp; if ((hIcmp = IcmpCreateFile()) == INVALID_HANDLE_VALUE){ printf("/tUnable to open ICMP file./n"); exit(-1); } //设置IP报头的TTL值 IP_OPTION_INFORMATION IpOption; ZeroMemory(&IpOption,sizeof(IP_OPTION_INFORMATION)); IpOption.Ttl = 1; //设置要发送的数据 char SendData[32]; memset(SendData,'0',sizeof(SendData)); //设置接收缓冲区 char ReplyBuffer[sizeof(ICMP_ECHO_REPLY)+32]; PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer; BOOL bLoop = TRUE; int iMaxHop = 30; while(bLoop && iMaxHop--){ printf("%2d: ",IpOption.Ttl); //发送ICMP回显请求 if(IcmpSendEcho(hIcmp,(IPAddr)ip, SendData, sizeof(SendData), &IpOption, ReplyBuffer, sizeof(ReplyBuffer), 3000)!=0){ if(pEchoReply->RoundTripTime==0){ printf("/t<1ms"); }else{ printf("/t%dms",pEchoReply->RoundTripTime); } printf("/t%s/n",inet_ntoa(*(in_addr*)&(pEchoReply->Address))); //判断是否完成路由路径探测 if((unsigned long)pEchoReply->Address==ip){ printf("/nTrace complete./n"); bLoop = FALSE; } }else{ printf("/t*/tRequest time out./n"); } IpOption.Ttl++; } IcmpCloseHandle(hIcmp); WSACleanup(); return 0; }