C++实现路由追踪(Tracert)程序
目录
ICMP协议介绍
Tracert实现原理
代码部分
测试结果
ICMP协议介绍
ICMP是internet控制报文协议,它是TCP/IP协议族的一个子协议,用于IP主机、路由器之间传递控制消息。ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于网络安全具有极其重要的意义。从技术角度说,ICMP就是一个“错误侦测与回报机制”,其目的就是让我们能够检测网络的连接状况,也能确保连线的准确性,其功能主要有:
· 侦测远端主机是否存在。
· 建立及维护路由资料。
· 重导资料传送路径(ICMP重定向)。
· 资料流量控制。
ICMP 是个非常有用的协议﹐尤其是当我们要对网路连接状况进行判断的时候。
它传递差错报文以及其他需要注意的信息,经常供IP层或更高层协议(TCP或UDP)使用。所以它经常被认为是IP层的一个组成部分。它在IP数据报文中的封装如下:
ICMP的数据报文格式如下所示。所有报文的前4个字节都是一样的,其他的因报文类型不同而不一样。类型字段可以有15个不同的值,用以描述不同的ICMP报文。校验和字段覆盖整个ICMP报文,使用了和IP首部检验和一样的算法,详细请搜索TCP/IP检验和算法。
不同类型的报文是由类型字段和代码字段来共同决定。下表是各种类型的ICMP报文。
根据上表可知,ICMP协议大致分为两类,一种是查询报文,一种是差错报文。查询报文是用一对请求和应答定义的,它通常有以下几种用途:
- ping查询
- 子网掩码查询(用于无盘工作站在初始化自身的时候初始化子网掩码)
- 时间戳查询(可以用来同步时间)
而差错报文通常包含了引起错误的IP数据报的第一个分片的IP首部(和选项),加上该分片数据部分的前8个字节。RFC 792规范中定义的这8个字节中包含了该分组运输层首部的所有分用信息,这样运输层协议就可以向正确的进程提交ICMP差错报文。
当传送IP数据包发生错误时,比如主机不可达,端口不可达等,ICMP协议就会把错误信息封包,然后传送回给主机。给主机一个处理错误的机会,这也就是为什么说建立在IP层以上的协议是可能做到安全的原因。由上面可知,ICMP数据包由8bit的错误类型和8bit的代码和16bit的校验和组成,而前 16bit就组成了ICMP所要传递的信息。由数据链路层所能发送的最大数据帧,即MTU(Maximum Transmission Unit)为1500,计算易知ICMP协议在实际传输中数据包为:20字节IP首部 + 8字节ICMP首部+ 1472字节(数据大小)。
尽管在大多数情况下,错误的包传送应该给出ICMP报文,但是在特殊情况下,是不产生ICMP错误报文的。如下
- ICMP差错报文不会产生ICMP差错报文(出IMCP查询报文)(防止IMCP的无限产生和传送)
- 目的地址是广播地址或多播地址的IP数据报。
- 作为链路层广播的数据报。
- 不是IP分片的第一片。
- 源地址不是单个主机的数据报。这就是说,源地址不能为零地址、环回地址、广播地 址或多播地址。
Tracert实现原理
Tracert程序关键是对IP头部生存时间(time to live)TTL字段的使用,程序实现时是向目地主机发送一个ICMP回显请求消息,初始时TTL等于1,这样当该数据报抵达途中的第一个路由器时,TTL的值就被减为0,导致发生超时错误,因此该路由生成一份ICMP超时差错报文返回给源主机。随后,主机将数据报的TTL值递增1,以便IP报能传送到下一个路由器,并由下一个路由器生成ICMP超时差错报文返回给源主机。不断重复这个过程,直到数据报达到最终的目地主机,此时目地主机将返回ICMP回显应答消息。这样,源主机只需对返回的每一份ICMP报文进行解析处理,就可以掌握数据报从源主机到达目地主机途中所经过的路由信息。
代码部分
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;//IP报头
typedef struct
{unsigned char hdr_len:4; //4位头部长度unsigned char version:4; //4位版本号unsigned char tos; //8位服务类型unsigned short total_len; //16位总长度unsigned short identifier; //16位标识符unsigned short frag_and_flags; //3位标志加13位片偏移unsigned char ttl; //8位生存时间unsigned char protocol; //8位上层协议号unsigned short checksum; //16位效验和unsigned long sourceIP; //32位源IP地址unsigned long destIP; //32位目的IP地址
}IP_HEADER;//ICMP报头
typedef struct
{BYTE type; //8位类型字段BYTE code; //8位代码字段USHORT cksum; //16位效验和USHORT id; //16位标识符USHORT seq; //16位序列号
}ICMP_HEADER;//报文解码结构
typedef struct
{USHORT usSeqNo; //序列号DWORD dwRoundTripTime; //返回时间in_addr dwIPaddr; //返回报文的IP地址
}DECODE_RESULT;//计算网际效验和函数
USHORT checksum(USHORT *pBuf,int iSize)
{unsigned long cksum=0;while(iSize>1){cksum+=*pBuf++;iSize-=sizeof(USHORT);}if(iSize){cksum+=*(USHORT*)pBuf;}cksum=(cksum>>16)+(cksum&0xffff);cksum+=(cksum>>16);return(USHORT)(~cksum);
}//对数据包进行解码
BOOL DecodeIcmpResponse(char *pBuf,int iPacketSize,DECODE_RESULT &DecodeResult,BYTE ICMP_ECHO_REPLY,BYTE ICMP_TIMEOUT)
{//检查数据报大小的合法性IP_HEADER *pIpHdr=(IP_HEADER*)pBuf;int iIpHdrLen=pIpHdr->hdr_len*4;if(iPacketSize<(int)(iIpHdrLen+sizeof(ICMP_HEADER)))return FALSE;//根据ICMP报文类型提取ID字段和序列号字段ICMP_HEADER *pIcmpHdr=(ICMP_HEADER*)(pBuf+iIpHdrLen);USHORT usID,usSquNo;if(pIcmpHdr->type==ICMP_ECHO_REPLY) //ICMP回显应答报文{usID=pIcmpHdr->id; //报文IDusSquNo=pIcmpHdr->seq; //报文序列号}else if(pIcmpHdr->type==ICMP_TIMEOUT) //ICMP超时差错报文{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; //报文IDusSquNo=pInnerIcmpHdr->seq; //序列号}else{return false;}//检查ID和序列号以确定收到期待数据报if(usID!=(USHORT)GetCurrentProcessId()||usSquNo!=DecodeResult.usSeqNo){return false;}//记录IP地址并计算往返时间DecodeResult.dwIPaddr.s_addr=pIpHdr->sourceIP;DecodeResult.dwRoundTripTime=GetTickCount()-DecodeResult.dwRoundTripTime;//处理正确收到的ICMP数据报if(pIcmpHdr->type==ICMP_ECHO_REPLY||pIcmpHdr->type==ICMP_TIMEOUT){//输出往返时间信息if(DecodeResult.dwRoundTripTime)cout<<" "<<DecodeResult.dwRoundTripTime<<"ms"<<flush;elsecout<<" "<<"<1ms"<<flush;}return true;
}int main()
{//初始化Windows sockets网络环境WSADATA wsa;WSAStartup(MAKEWORD(2,2),&wsa);char IpAddress[255];cout<<"请输入一个IP地址或域名:";cin>>IpAddress;//得到IP地址u_long ulDestIP=inet_addr(IpAddress);//转换不成功时按域名解析if(ulDestIP==INADDR_NONE){hostent *pHostent=gethostbyname(IpAddress);if(pHostent){ulDestIP=(*(in_addr*)pHostent->h_addr).s_addr;}else{cout<<"输入的IP地址或域名无效"<<endl;WSACleanup();return 0;}}cout<<"Tracing route to "<<IpAddress<<" with a maximum of 30 hops.\n"<<endl;//填充目的端socket地址sockaddr_in destSockAddr;ZeroMemory(&destSockAddr,sizeof(sockaddr_in));destSockAddr.sin_family=AF_INET;destSockAddr.sin_addr.s_addr=ulDestIP;//创建原始套接字SOCKET sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);//超时时间int iTimeout=3000;//接收超时setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&iTimeout,sizeof(iTimeout));//发送超时setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&iTimeout,sizeof(iTimeout));//构造ICMP回显请求消息,并以TTL递增的顺序发送报文//ICMP类型字段const BYTE ICMP_ECHO_REQUEST=8; //请求回显const BYTE ICMP_ECHO_REPLY=0; //回显应答const BYTE ICMP_TIMEOUT=11; //传输超时//其他常量定义const int DEF_ICMP_DATA_SIZE=32; //ICMP报文默认数据字段长度const int MAX_ICMP_PACKET_SIZE=1024; //ICMP报文最大长度(包括报头)const DWORD DEF_ICMP_TIMEOUT=3000; //回显应答超时时间const int DEF_MAX_HOP=30; //最大跳站数//填充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_HEADER *pIcmpHeader=(ICMP_HEADER*)IcmpSendBuf;pIcmpHeader->type=ICMP_ECHO_REQUEST; //类型为请求回显pIcmpHeader->code=0; //代码字段为0pIcmpHeader->id=(USHORT)GetCurrentProcessId(); //ID字段为当前进程号memset(IcmpSendBuf+sizeof(ICMP_HEADER),'E',DEF_ICMP_DATA_SIZE); //数据字段USHORT usSeqNo=0; //ICMP报文序列号int iTTL=1; //TTL初始值为1BOOL bReachDestHost=FALSE; //循环退出标志int iMaxHot=DEF_MAX_HOP; //循环的最大次数DECODE_RESULT DecodeResult; //传递给报文解码函数的结构化参数while(!bReachDestHost&&iMaxHot--){//设置IP报头的TTL字段setsockopt(sockRaw,IPPROTO_IP,IP_TTL,(char*)&iTTL,sizeof(iTTL));cout<<iTTL<<flush; //输出当前序号//填充ICMP报文中每次发送变化的字段((ICMP_HEADER*)IcmpSendBuf)->cksum=0; //效验和先置为0((ICMP_HEADER*)IcmpSendBuf)->seq=htons(usSeqNo++); //填充序列号((ICMP_HEADER*)IcmpSendBuf)->cksum=checksum((USHORT*)IcmpSendBuf,sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE); //计算效验和//记录序列号和当前时间DecodeResult.usSeqNo=((ICMP_HEADER*)IcmpSendBuf)->seq; //当前序号DecodeResult.dwRoundTripTime=GetTickCount(); //当前时间//发送TCP回显请求信息sendto(sockRaw,IcmpSendBuf,sizeof(IcmpSendBuf),0,(sockaddr*)&destSockAddr,sizeof(destSockAddr));//接收ICMP差错报文并进行解析处理sockaddr_in from; //对端socket地址int iFromLen=sizeof(from); //地址结构大小int iReadDataLen; //接收数据长度while(1){//接收数据iReadDataLen=recvfrom(sockRaw,IcmpRecvBuf,MAX_ICMP_PACKET_SIZE,0,(sockaddr*)&from,&iFromLen);if(iReadDataLen!=SOCKET_ERROR)//有数据达到{//对数据包进行解码if(DecodeIcmpResponse(IcmpRecvBuf,iReadDataLen,DecodeResult,ICMP_ECHO_REPLY,ICMP_TIMEOUT)){//到达目的地,退出循环if(DecodeResult.dwIPaddr.s_addr==destSockAddr.sin_addr.s_addr)bReachDestHost=true;//输出IP地址cout<<'\t'<<inet_ntoa(DecodeResult.dwIPaddr)<<endl;break;}}else if(WSAGetLastError()==WSAETIMEDOUT) //接收超时,输出星号{cout<<" *"<<'\t'<<"Request timed out"<<endl;break;}else{break;}}iTTL++; //递增TTL值}
}
测试结果
C++实现路由追踪(Tracert)程序相关推荐
- 网络诊断工具之—路由追踪tracert命令
路由追踪-tracert Tracert通过递增"生存时间 (TTL)"字段的值将"Internet 控制消息协议 (ICMP) 回响请求" ...
- Windows 路由追踪tracert命令使用示例
- 3.ICMP_抓包分析traceroute路由追踪
一.路由追踪程序traceroute/tracert Traceroute是Linux和Mac OS等系统默认提供的路由追踪小程序,Tracert是Windows系统默认提供的路由追踪小程序.二者的功 ...
- 路由追踪traceroute分析
原文 : http://www.freebuf.com/articles/network/118221.html 一.路由追踪程序traceroute/tracert Traceroute是Linu ...
- 路由追踪——traceroute与tracert
一.路由追踪 (一)路由跟踪,就是获取从主机A到达目标主机B这个过程中所有需要经过的路由设备的转发接口IP. (二)ICMP协议 Internet控制报文协议(internet control mes ...
- 路由追踪命令Tracert
Dos命令Tracert跟踪路由详解 Tracert 通过递减"存在时间 (TTL)"字段的值将"Internet 控制消息协议 (ICMP)回显请求"或 IC ...
- 华三路由追踪命令 tracert详解
华三路由追踪命令 tracert 此命令用来查看IPv4报文从源端传到目的端所经过的路径 tracert [ -a source-ip | -f first-ttl | -m max-ttl | -p ...
- 路由追踪命令:tracert、pathping 值得收藏
对于网络工程师来说,需要熟练掌握的Windows路由追踪命令有两个:tracert和pathping,其中pathping是tracert和ping命令的结合,不但可以追踪目标IP地址的路由,还可以测 ...
- 路由追踪命令:tracert、pathping!值得收藏!
对于网络工程师来说,需要熟练掌握的Windows路由追踪命令有两个:tracert和pathping,其中pathping是tracert和ping命令的结合,不但可以追踪目标IP地址的路由,还可以测 ...
最新文章
- 被乐高AI“照妖镜”一照,发现你的本体原来是人偶
- Call to undefined function Workerman\posix_getpid
- 基于直方图的图像增强算法(HE、CLAHE、Retinex)
- 浅谈优化SQLServer数据库服务器内存配置的策略
- 谷歌:CNN击败Transformer,有望成为预训练界新霸主!LeCun却沉默了...
- pwm波如何控制电机代码_PWM波控制720电机
- python如何输入多个数据并增加到一个列表里_python 将表格多个列数据放到同一个单元格中...
- 蓝牙4.0大数据分包处理
- C#LeetCode刷题之#231-2的幂(Power of Two)
- 95-10-030-启动-deamon定时任务
- 解释说明CCIE考试中的RIP问题
- 今天来了一位妹纸面试,我问她:线程池中多余的线程是如何回收的?她有点懵~...
- 老式计算机如何设置u盘启动,技嘉主板老式bios设置u盘启动教程
- chrome浏览器多开工具
- Apache Flink 漫谈系列(12) - Time Interval(Time-windowed) JOIN
- 关于系统前端开发的那些事
- python3 全局变量_Python3基础 当函数中的局部变量与全局变量同名了,各管各的...
- 结构体与联合体概念引入
- pandoc按格式转换md为doc
- 【小知识】光的偏振态及镜头前加偏振片的去噪原理
热门文章
- Pytorch TTA(预测增强) 源码阅读
- pring Boot + Redis 实现延时队列
- 【c++算法篇】--图论之克鲁斯卡尔
- 联想笔记本怎么进入pe系统_联想笔记本电脑如何进入U盘启动,按哪个快捷键
- axios拦截器使用
- Unsatisfied dependency expressed through field 'baseMapper'
- 初学者学习vue路由与无痕浏览
- ~按位取反的计算步骤及例子
- [转]调研报告、论文中参考文献的格式(一)
- Oracle11g限制ip访问数据库,ORACLE 限制特定IP访问数据库