第6章Tracert程序概述利用Tracert程序可以探测出IP报文从本机抵达网络内任一指定主机途中所经过的路由信息,如路由器的IP地址和往返时间等,它是一个测试TCP/IP协议不可缺少的有力工具。目录编程训练目的相关知识介绍程序流程图程序运行结果核心代码及其说明程序源代码编程训练目的掌握ICMP协议的基本工作原理,熟悉ICMP报头格式与各字段的含义掌握Tracert程序的基本功能、原理与实现方法掌握Tracert程序设计与软件编程方法回到目录Tracert的实现原理IP报头中TTL字段的含义和作用:生存时间(Timetolive,TTL)字段,限制IP报文在网络中的停留时间(跳站数)Tracert程序利用TTL字段,通过从1开始递增TTL字段值,并接收与解析从各跳路由器发回的超时差错报文来收集路由信息回到目录ICMP协议简介ICMP是IP层的一个组成部分(子协议),主要用于在IP主机和路由器之间传递差错和控制消息ICMP位于IP层的上方,它与IP报文的关系如图所示:即ICMP报文是封装在IP数据报内作为有效载荷传输的IP头部ICMP报文IP报文20B回到目录ICMP报文结构ICMP是因特网控制报文协议(Internetcontrolmessageprotocol)的缩写.通常被认为是IP层的一个组成部分(子协议),主要用于在IP主机和路由器之间传递控制消息。从结构上看,它位于IP层的上方,因为ICMP报文是封装在IP数据报内作为有效载荷传输的ICMP报文的一般格式:8位类型8位代码16位校验和(不同类型和代码有不同的内容)078151631回到目录类型代码描述查询报文差错报文00回显应答√80回显请求√11超时:0传输期间生存时间字段减为0√1数据报组装期间生存时间字段减为0√ICMP报文类型本章编程训练中用到的ICMP报文类型:回到目录ICMP回显请求与回显应答报文格式:类型(0或8)代码(0)校验和选项数据078151631标识符ID序列号8字节本章编程训练涉及3种类型的ICMP报文:ICMP回显请求报文、ICMP回显应答报文和ICMP超时差错报文。其中ICMP回显请求和回显应答报文结构如图所示。这两种报文也被称作ping报文,目的是为了测试一台主机到另一主机是否可达。当一台主机收到ICMP回显请求报文后,接受主机应向源主机回送ICMP回显应答报文,且该回显报文必须响应源报文中标识符、序列号和选项数据三个字段。回到目录ICMP超时差错报文格式:类型(11)代码(0)校验和IP头部(包括选项)+原始IP报文中数据的前8个字节078151631未用(必须为0)8字节ICMP超时报文产生的两种情况:1、路由器在转发数据报时IP头部的TTL值减为0;2、当组成一个数据报的所有分片未能在某一限定时间内到达目的主机而导致的超时。两种情况下路由器或目的主机都将丢弃相应分组并向源主机发送超时差错报文。回到目录回显请求报文产生的ICMP超时差错报文ICMP(所有)差错报文的规则是:报文中的数据部分必须包含产生该差错报文的IP数据报头部(包含选项字段),以及(至少包含)跟在该IP头部后面的前8个字节。这样接受ICMP的进程就可以根据所包含的差错数据将其与之前的某个特定的报文或进程关联起来。回显请求报文产生的ICMP超时差错报文的完整格式。以太网首部IP数据报ICMP首部产生差错的数据报IP首部IP首部ICMP首部ICMP报文ICMP报文的数据部分14字节20字节20字节8字节8字节回到目录程序流程图:回到目录程序运行结果:回到目录回到目录核心代码及其说明解析命令行参数:初始化winsock2网络环境:WSADATAwsa;WSAStartup(MAKEWORD(2,2),&wsa);u_longulDestIP=inet_addr(argv[1]);if(ulDestIP==INADDR_NONE){hostent*pHostent=gethostbyname(argv[1]);if(pHostent)ulDestIP=(*(in_addr*)pHostent-h_addr).s_addr;}回到目录核心代码及其说明创建原始套接字rawsocket:sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);设置rawsocket接收超时属性:setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&iTimeout,sizeof(iTimeout));回到目录核心代码及其说明定义IP和ICMP报头数据结构//IP报头typedefstruct{unsignedcharhdr_len:4;unsignedcharversion:4;unsignedchartos;unsignedshorttotal_len;unsignedshortidentifier;unsignedshortfrag_and_flags;unsignedcharttl;unsignedcharprotocol;unsignedshortchecksum;unsignedlongsourceIP;unsignedlongdestIP;}IP_HEADER;//ICMP报头typedefstruct{BYTEtype;BYTEcode;USHORTcksum;USHORTid;USHORTseq;}ICMP_HEADER;回到目录核心代码及其说明填充ICMP回显请求消息//ICMP类型字段constBYTEICMP_ECHO_REQUEST=8;//请求回显constBYTEICMP_ECHO_REPLY=0;//回显应答constBYTEICMP_TIMEOUT=11;//传输超时constDWORDDEF_ICMP_TIMEOUT=3000;//默认超时时间,单位msconstintDEF_ICMP_DATA_SIZE=32;//默认ICMP数据部分长度constintMAX_ICMP_PACKET_SIZE=1024;//最大ICMP数据报的大小constintDEF_MAX_HOP=30;//最大跳站数USHORTGenerateChecksum(USHORT*pBuf,intiSize);回到目录核心代码及其说明设置IP报头的TTL字段,从1开始递增//开始探测路由DECODE_RESULTstDecodeResult;BOOLbReachDestHost=FALSE;USHORTusSeqNo=0;intiTTL=1;intiMaxHop=DEF_MAX_HOP;while(!bReachDestHost&&iMaxHop--){//设置IP数据报头的ttl字段setsockopt(sockRaw,IPPROTO_IP,IP_TTL,(char*)&iTTL,sizeof(iTTL));//填充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);回到目录核心代码及其说明设置IP报头的TTL字段,从1开始递增//记录序列号和当前时间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'Destinationhostunreachable.\n}//TTL值加1iTTL++;回到目录核心代码及其说明接收ICMP差错报文或ICMP回显应答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;break;}}elseif(WSAGetLastError()==WSAETIMEDOUT)//接收超时,打印星号{coutsetw(9)'*''\t'Requesttimedout.endl;break;}回到目录核心代码及其说明对接收报文进行解析处理//解码得到的数据报BOOLDecodeIcmpResponse(char*pBuf,intiPacketSize,DECODE_RESULT&stDecodeResult){//检查数据报大小的合法性IP_HEADER*pIpHdr=(IP_HEADER*)pBuf;intiIpHdrLen=pIpHdr-hdr_len*4;//按照ICMP包类型检查id字段和序列号以确定是否程序应接收的Icmp包......//处理正确收到的ICMP数据报if(pIcmpHdr-type==ICMP_ECHO_REPLY||pIcmpHdr-type==ICMP_TIMEOUT){//返回解码结果stDecodeResult.dwIPaddr.s_addr=pIpHdr-sourceIP;stDecodeResult.dwRoundTripTime=GetTickCount()-stDecodeResult.dwRoundTripTime;回到目录程序源代码#pragmacomment(lib,ws2_32.lib)//需要使用网络API函数的时候,就必须使用这条语句加载ws2_32.lib库或者动态载入ws2_32.dll#includeiostream.h#includeiomanip.h//I/O流控制头文件#includewinsock2.h///*socket通信,系统头文件*/#includews2tcpip.h//设置或获取套接字选项#includeitracert.h//类型文件////////////////////////////////////////////////////////回到目录程序源代码intmain(intargc,char*argv[])//argc是命令行总的参数个数,argv[]是argc个参数,其中第0个参数是程序的全名,以后的参数命令行后面跟的用户输入的参数{//检查命令行参数if(argc!=2)//有两个参数,一个是程序名,一个是ip地址或者域名{cerr\nUsage:itracertip_or_hostname\n;//cerr对应标准错误流,用于显示错误消息。return-1;//退出程序程序