实现原理: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 查看本文章