Tracert和Ping

实现原理:Tracert 程序关键是对 IP 头部生存时间(time to live)TTL 字段的使用,程序实现时是向目 地主机发送一个 ICMP 回显请求消息,初始时 TTL 等于 1,这样当该数据报抵达途中的第一个路由器 时,TTL 的值就被减为 0,导致发生超时错误,因此该路由生成一份 ICMP 超时差错报文返回给源主 机。随后,主机将数据报的 TTL 值递增 1,以便 IP 报能传送到下一个路由器,并由下一个路由器生成 ICMP 超时差错报文返回给源主机。不断重复这个过程,直到数据报达到最终的目地主机,此时目地 主机将返回 ICMP 回显应答消息。这样,源主机只需对返回的每一份 ICMP 报文进行解析处理,就可以掌握数据报从源主机到达目地主机途中所经过的路由信息。

老师要求能tracert一个域名或者ip地址,还要能ping所有局域网的机器。

结果如下:

代码:

  1 #include <iostream>
  2 #include <winsock2.h>
  3 #include <ws2tcpip.h>
  4 #include <stdio.h>
  5 using namespace std;
  6 #pragma comment(lib, "Ws2_32.lib")
  7 //IP 报头
  8 typedef struct
  9 {
 10     unsigned char hdr_len:4; //4 位头部长度
 11     unsigned char version:4; //4 位版本号
 12     unsigned char tos; //8 位服务类型
 13     unsigned short total_len; //16 位总长度
 14     unsigned short identifier; //16 位标识符
 15     unsigned short frag_and_flags; //3 位标志加 13 位片偏移
 16     unsigned char ttl; //8 位生存时间
 17     unsigned char protocol; //8 位上层协议号
 18     unsigned short checksum; //16 位校验和
 19     unsigned long sourceIP; //32 位源 IP 地址
 20     unsigned long destIP; //32 位目的 IP 地址
 21 } IP_HEADER;
 22 //ICMP 报头
 23 typedef struct
 24 {
 25     BYTE type; //8 位类型字段
 26     BYTE code; //8 位代码字段
 27     USHORT cksum; //16 位校验和
 28     USHORT id; //16 位标识符
 29     USHORT seq; //16 位序列号
 30 } ICMP_HEADER;
 31 //报文解码结构
 32 typedef struct
 33 {
 34     USHORT usSeqNo; //序列号
 35     DWORD dwRoundTripTime; //往返时间
 36     in_addr dwIPaddr; //返回报文的 IP 地址
 37 } DECODE_RESULT;
 38 //计算网际校验和函数
 39 USHORT checksum(USHORT *pBuf,int iSize)
 40 {
 41     unsigned long cksum=0;
 42     while(iSize>1)
 43     {
 44         cksum+=*pBuf++;
 45         iSize-=sizeof(USHORT);
 46     }
 47     if(iSize)
 48     {
 49         cksum+=*(UCHAR *)pBuf;
 50     }
 51     cksum=(cksum>>16)+(cksum&0xffff);
 52     cksum+=(cksum>>16);
 53     return (USHORT)(~cksum);
 54 }
 55 //对数据包进行解码
 56 BOOL DecodeIcmpResponse(char * pBuf,int iPacketSize,DECODE_RESULT &DecodeResult,BYTE
 57                         ICMP_ECHO_REPLY,BYTE ICMP_TIMEOUT)
 58 {
 59     //检查数据报大小的合法性
 60     IP_HEADER* pIpHdr = (IP_HEADER*)pBuf;
 61     int iIpHdrLen = pIpHdr->hdr_len * 4;
 62     if (iPacketSize < (int)(iIpHdrLen+sizeof(ICMP_HEADER)))
 63         return FALSE;
 64     //根据 ICMP 报文类型提取 ID 字段和序列号字段
 65     ICMP_HEADER *pIcmpHdr=(ICMP_HEADER *)(pBuf+iIpHdrLen);
 66     USHORT usID,usSquNo;
 67     if(pIcmpHdr->type==ICMP_ECHO_REPLY) //ICMP 回显应答报文
 68     {
 69         usID=pIcmpHdr->id; //报文 ID
 70         usSquNo=pIcmpHdr->seq; //报文序列号
 71     }
 72     else if(pIcmpHdr->type==ICMP_TIMEOUT)//ICMP 超时差错报文
 73     {
 74         char * pInnerIpHdr=pBuf+iIpHdrLen+sizeof(ICMP_HEADER); //载荷中的 IP 头
 75         int iInnerIPHdrLen=((IP_HEADER *)pInnerIpHdr)->hdr_len*4; //载荷中的 IP 头长
 76         ICMP_HEADER * pInnerIcmpHdr=(ICMP_HEADER *)(pInnerIpHdr+iInnerIPHdrLen);//载荷中的 ICMP 头
 77         usID=pInnerIcmpHdr->id; //报文 ID
 78         usSquNo=pInnerIcmpHdr->seq; //序列号
 79     }
 80     else
 81     {
 82         return false;
 83     }
 84     //检查 ID 和序列号以确定收到期待数据报
 85     if(usID!=(USHORT)GetCurrentProcessId()||usSquNo!=DecodeResult.usSeqNo)
 86     {
 87         return false;
 88     }
 89     //记录 IP 地址并计算往返时间
 90     DecodeResult.dwIPaddr.s_addr=pIpHdr->sourceIP;
 91     DecodeResult.dwRoundTripTime=GetTickCount()-DecodeResult.dwRoundTripTime;
 92     //处理正确收到的 ICMP 数据报
 93     if (pIcmpHdr->type == ICMP_ECHO_REPLY ||pIcmpHdr->type == ICMP_TIMEOUT)
 94     {
 95         //输出往返时间信息
 96         if(DecodeResult.dwRoundTripTime)
 97             cout<<" "<<DecodeResult.dwRoundTripTime<<"ms"<<flush;
 98         else
 99             cout<<" "<<"<1ms"<<flush;
100     }
101     return true;
102 }
103 int main()
104 {
105     //初始化 Windows sockets 网络环境
106     WSADATA wsa;
107     WSAStartup(MAKEWORD(2,2),&wsa);
108     char IpAddress[255];
109     int choose;
110     cout<<"输入一个数字,选择不同功能\n";
111     cout<<"1:tracert一个外网地址\n2:ping本地局域网的所有机器\n";
112 L1:
113     cin>>choose;
114     if(choose==1)
115     {
116         cout<<"请输入一个 IP 地址或域名:";
117         cin>>IpAddress;
118         //得到 IP 地址
119         u_long ulDestIP=inet_addr(IpAddress);
120         //转换不成功时按域名解析
121         if(ulDestIP==INADDR_NONE)
122         {
123             hostent * pHostent=gethostbyname(IpAddress);
124             if(pHostent)
125             {
126                 ulDestIP=(*(in_addr*)pHostent->h_addr).s_addr;
127             }
128             else
129             {
130                 cout<<"输入的 IP 地址或域名无效!"<<endl;
131                 WSACleanup();
132                 return 0;
133             }
134         }
135         cout<<"Tracing route to "<<IpAddress<<" with a maximum of 30 hops.\n"<<endl;
136         //填充目地端 socket 地址
137         sockaddr_in destSockAddr;
138         ZeroMemory(&destSockAddr,sizeof(sockaddr_in));
139         destSockAddr.sin_family=AF_INET;
140         destSockAddr.sin_addr.s_addr=ulDestIP;
141         //创建原始套接字
142         SOCKET sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,
143                                  WSA_FLAG_OVERLAPPED);
144         //超时时间
145         int iTimeout=3000;
146         //接收超时
147         setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char *)&iTimeout,sizeof(iTimeout));
148         //发送超时
149         setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char *)&iTimeout,sizeof(iTimeout));
150         //构造 ICMP 回显请求消息,并以 TTL 递增的顺序发送报文
151         //ICMP 类型字段
152         const BYTE ICMP_ECHO_REQUEST=8; //请求回显
153         const BYTE ICMP_ECHO_REPLY=0; //回显应答
154         const BYTE ICMP_TIMEOUT=11; //传输超时
155         //其他常量定义
156         const int DEF_ICMP_DATA_SIZE=32; //ICMP 报文默认数据字段长度
157         const int MAX_ICMP_PACKET_SIZE=1024;//ICMP 报文最大长度(包括报头)
158         const DWORD DEF_ICMP_TIMEOUT=3000; //回显应答超时时间
159         const int DEF_MAX_HOP=30; //最大跳站数
160         //填充 ICMP 报文中每次发送时不变的字段
161         char IcmpSendBuf[sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE];//发送缓冲区
162         memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf)); //初始化发送缓冲区
163         char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE]; //接收缓冲区
164         memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf)); //初始化接收缓冲区
165         ICMP_HEADER * pIcmpHeader=(ICMP_HEADER*)IcmpSendBuf;
166         pIcmpHeader->type=ICMP_ECHO_REQUEST; //类型为请求回显
167         pIcmpHeader->code=0; //代码字段为 0
168         pIcmpHeader->id=(USHORT)GetCurrentProcessId(); //ID 字段为当前进程号
169         memset(IcmpSendBuf+sizeof(ICMP_HEADER),'E',DEF_ICMP_DATA_SIZE);//数据字段
170         USHORT usSeqNo=0; //ICMP 报文序列号
171         int iTTL=1; //TTL 初始值为 1
172         BOOL bReachDestHost=FALSE; //循环退出标志
173         int iMaxHot=DEF_MAX_HOP; //循环的最大次数
174         DECODE_RESULT DecodeResult; //传递给报文解码函数的结构化参数
175         while(!bReachDestHost&&iMaxHot--)
176         {
177             //设置 IP 报头的 TTL 字段
178             setsockopt(sockRaw,IPPROTO_IP,IP_TTL,(char *)&iTTL,sizeof(iTTL));
179             cout<<iTTL<<flush; //输出当前序号
180             //填充 ICMP 报文中每次发送变化的字段
181             ((ICMP_HEADER *)IcmpSendBuf)->cksum=0; //校验和先置为 0
182             ((ICMP_HEADER *)IcmpSendBuf)->seq=htons(usSeqNo++); //填充序列号
183             ((ICMP_HEADER *)IcmpSendBuf)->cksum=checksum((USHORT *)IcmpSendBuf,
184                                                 sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE); //计算校验和
185             //记录序列号和当前时间
186             DecodeResult.usSeqNo=((ICMP_HEADER*)IcmpSendBuf)->seq; //当前序号
187             DecodeResult.dwRoundTripTime=GetTickCount(); //当前时间
188             //发送 TCP 回显请求信息
189             sendto(sockRaw,IcmpSendBuf,sizeof(IcmpSendBuf),0,(sockaddr*)&destSockAddr,sizeof(destSockAddr));
190             //接收 ICMP 差错报文并进行解析处理
191             sockaddr_in from; //对端 socket 地址
192             int iFromLen=sizeof(from); //地址结构大小
193             int iReadDataLen; //接收数据长度
194             while(1)
195             {
196                 //接收数据
197                 iReadDataLen=recvfrom(sockRaw,IcmpRecvBuf,MAX_ICMP_PACKET_SIZE,0,(sockaddr*)&from,&
198                                       iFromLen);
199                 if(iReadDataLen!=SOCKET_ERROR)//有数据到达
200                 {
201                     //对数据包进行解码
202                     if(DecodeIcmpResponse(IcmpRecvBuf,iReadDataLen,DecodeResult,ICMP_ECHO_REPLY,ICMP_TIMEOUT))
203                     {
204                         //到达目的地,退出循环
205                         if(DecodeResult.dwIPaddr.s_addr==destSockAddr.sin_addr.s_addr)
206                             bReachDestHost=true;
207                         //输出 IP 地址
208                         cout<<'\t'<<inet_ntoa(DecodeResult.dwIPaddr)<<endl;
209                         break;
210                     }
211                 }
212                 else if(WSAGetLastError()==WSAETIMEDOUT) //接收超时,输出*号
213                 {
214                     cout<<" *"<<'\t'<<"Request timed out."<<endl;
215                     break;
216                 }
217                 else
218                 {
219                     break;
220                 }
221             }
222             iTTL++; //递增 TTL 值
223         }
224     }
225     else if(choose==2)
226     {
227         //获得本地ip地址
228         char host[255];
229         char IpAddress[255];
230         if(gethostname(host,sizeof(host))==SOCKET_ERROR)
231         {
232             cout<<"无法获取主机名"<<endl;
233         }
234         else
235         {
236             cout<<"本机计算机名为:"<<host<<endl;
237         }
238         struct hostent *p=gethostbyname(host);
239         struct in_addr hostaddr;
240         memcpy(&hostaddr,p->h_addr_list[0],p->h_length);
241         char Ip[255];
242         strncpy(IpAddress,inet_ntoa(hostaddr),255);
243         int point =0;
244         for(int i=0;; i++)//把主机号切下来
245         {
246             if(point==3)    //第三个点前面切下来
247             {
248                 Ip[i]='\0'; //不加这个后面的sprintf函数会出错
249                 break;
250             }
251             Ip[i]=IpAddress[i]; 
252             if(IpAddress[i]=='.') 
253                 point++;
254         }
255         for(int i=1; i<=254; i++)
256         {
257             sprintf(IpAddress,"%s%d",Ip,i); //拼接在一起,局域网是c类地址吧?从1到254的有效地址。
258             u_long ulDestIP=inet_addr(IpAddress);
259 
260             sockaddr_in destSockAddr;
261             ZeroMemory(&destSockAddr,sizeof(sockaddr_in));
262             destSockAddr.sin_family=AF_INET;
263             destSockAddr.sin_addr.s_addr=ulDestIP;
264             //创建原始套接字
265             SOCKET sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,
266                                      WSA_FLAG_OVERLAPPED);
267             //超时时间
268             int iTimeout=3000;
269             //接收超时
270             setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char *)&iTimeout,sizeof(iTimeout));
271             //发送超时
272             setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char *)&iTimeout,sizeof(iTimeout));
273             //构造 ICMP 回显请求消息,并以 TTL 递增的顺序发送报文
274             //ICMP 类型字段
275             const BYTE ICMP_ECHO_REQUEST=8; //请求回显
276             const BYTE ICMP_ECHO_REPLY=0; //回显应答
277             const BYTE ICMP_TIMEOUT=11; //传输超时
278             //其他常量定义
279             const int DEF_ICMP_DATA_SIZE=32; //ICMP 报文默认数据字段长度
280             const int MAX_ICMP_PACKET_SIZE=1024;//ICMP 报文最大长度(包括报头)
281             const DWORD DEF_ICMP_TIMEOUT=3000; //回显应答超时时间
282             const int DEF_MAX_HOP=1; //最大跳站数
283             //填充 ICMP 报文中每次发送时不变的字段
284             char IcmpSendBuf[sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE];//发送缓冲区
285             memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf)); //初始化发送缓冲区
286             char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE]; //接收缓冲区
287             memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf)); //初始化接收缓冲区
288             ICMP_HEADER * pIcmpHeader=(ICMP_HEADER*)IcmpSendBuf;
289             pIcmpHeader->type=ICMP_ECHO_REQUEST; //类型为请求回显
290             pIcmpHeader->code=0; //代码字段为 0
291             pIcmpHeader->id=(USHORT)GetCurrentProcessId(); //ID 字段为当前进程号
292             memset(IcmpSendBuf+sizeof(ICMP_HEADER),'E',DEF_ICMP_DATA_SIZE);//数据字段
293             USHORT usSeqNo=0; //ICMP 报文序列号
294             int iTTL=1; //TTL 初始值为 1
295             BOOL bReachDestHost=FALSE; //循环退出标志
296             int iMaxHot=DEF_MAX_HOP; //循环的最大次数
297             DECODE_RESULT DecodeResult; //传递给报文解码函数的结构化参数
298             //设置 IP 报头的 TTL 字段
299             setsockopt(sockRaw,IPPROTO_IP,IP_TTL,(char *)&iTTL,sizeof(iTTL));
300             //填充 ICMP 报文中每次发送变化的字段
301             ((ICMP_HEADER *)IcmpSendBuf)->cksum=0; //校验和先置为 0
302             ((ICMP_HEADER *)IcmpSendBuf)->seq=htons(usSeqNo++); //填充序列号
303             ((ICMP_HEADER *)IcmpSendBuf)->cksum=checksum((USHORT *)IcmpSendBuf,
304                                                 sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE); //计算校验和
305             //记录序列号和当前时间
306             DecodeResult.usSeqNo=((ICMP_HEADER*)IcmpSendBuf)->seq; //当前序号
307             DecodeResult.dwRoundTripTime=GetTickCount(); //当前时间
308             //发送 TCP 回显请求信息
309             sendto(sockRaw,IcmpSendBuf,sizeof(IcmpSendBuf),0,(sockaddr*)&destSockAddr,sizeof(destSockAddr));
310             //接收 ICMP 差错报文并进行解析处理
311             sockaddr_in from; //对端 socket 地址
312             int iFromLen=sizeof(from); //地址结构大小
313             int iReadDataLen; //接收数据长度
314             //接收数据
315             iReadDataLen=recvfrom(sockRaw,IcmpRecvBuf,MAX_ICMP_PACKET_SIZE,0,(sockaddr*)&from,&
316                                   iFromLen);
317             if(iReadDataLen!=SOCKET_ERROR)//有数据到达
318             {
319                 //对数据包进行解码
320                 if(DecodeIcmpResponse(IcmpRecvBuf,iReadDataLen,DecodeResult,ICMP_ECHO_REPLY,ICMP_TIMEOUT))
321                 {
322                     //到达目的地,退出循环
323                     if(DecodeResult.dwIPaddr.s_addr==destSockAddr.sin_addr.s_addr)
324                         bReachDestHost=true;
325                     //输出 IP 地址
326                     cout<<'\t'<<IpAddress<<" 在线"<<endl;
327                 }else{
328                     cout<<'\t'<<IpAddress<<" 不在线"<<endl;
329                 }
330             }
331             else if(WSAGetLastError()==WSAETIMEDOUT) //接收超时,输出*号
332             {
333                 cout<<'\t'<<IpAddress<<" 超时"<<endl;
334             }
335         }
336     }
337     else
338     {
339         cout<<"输入不合法,请重新输入:";
340         goto L1;
341 
342     }
343 }

主要是弄清楚sockaddr_in和sockaddr还有hostent 这几个结构体。当时做课设的时候在这里面研究了很久这个。

要是需要就自己去查吧。

还有就是要把防火墙关闭,我当时做实验的时候,有三个状态,ping的通,超时,主机不可达。主机不可达好像就是防火墙把发回来的

icmp拦截掉了。

扫描二维码关注公众号,回复: 9097471 查看本文章

猜你喜欢

转载自www.cnblogs.com/tangdingkang/p/12298100.html