VC++实现路由跟踪

tracert命令及用法

Tracert(跟踪路由)是路由跟踪实用程序,用于确定 IP 数据报访问目标所采取的路径。

Tracert 命令用 IP 生存时间 (TTL) 字段和 ICMP 错误消息来确定从一个主机到网络上其他主机的路由。

 Tracert 工作原理 通过向目标发送不同 IP 生存时间 (TTL) 值的“Internet 控制消息协议 (ICMP)”回应数据包,Tracert 诊断程序确定到目标所采取的路由。要求路径上的每个路由器在转发数据包之前至少将数据包上的 TTL 递减 1。数据包上的 TTL 减为 0 时,路由器应该将“ICMP 已超时”的消息发回源系统。

 Tracert 先发送 TTL 为 1 的回应数据包,并在随后的每次发送过程将 TTL 递增 1,直到目标响应或 TTL 达到最大值,从而确定路由。

通过检查中间路由器发回的“ICMP 已超时”的消息确定路由。某些路由器不经询问直接丢弃 TTL 过期的数据包,这在 Tracert 实用程序中看不到。

 Tracert 命令按顺序打印出返回“ICMP 已超时”消息的路径中的近端路由器接口列表。

如果使用 -d 选项,则 Tracert 实用程序不在每个 IP 地址上查询 DNS。

在下例中,数据包必须通过两个路由器(10.0.0.1 和 192.168.0.1)才能到达主机 172.16.0.99。

主机的默认网关是 10.0.0.1,192.168.0.0 网络上的路由器的 IP 地址是 192.168.0.1。

C:\>tracert 172.16.0.99 -d Tracing route to 172.16.0.99 over a maximum of 30 hops 1 2s 3s 2s 10,0.0,1 2 75 ms 83 ms 88 ms 192.168.0.1 3 73 ms 79 ms 93 ms 172.16.0.99 Trace complete.

 

[cpp]  view plain copy
  1. /*---------------------------------------------------------- 
  2. 功能说明:该程序简单实现了Windows操作系统的tracert命令功能, 
  3.       可以输出IP报文从本机出发到达目的主机所经过的路由信息。 
  4. 注意:程序编译时应使用1字节对齐方式调整边界! 
  5. -----------------------------------------------------------*/  
  6. #include <iostream.h>  
  7. #include <iomanip.h>  
  8. #include <winsock2.h>  
  9. #include <ws2tcpip.h>  
  10. #include "itracert.h"  
  11.   
  12.   
  13.   
  14. int main(int argc, char* argv[])  
  15. {  
  16.     //检查命令行参数  
  17.     if (argc != 2)  
  18.     {  
  19.         cerr << "\nUsage: itracert ip_or_hostname\n";  
  20.         return -1;  
  21.     }  
  22.   
  23.     //初始化winsock2环境  
  24.     WSADATA wsa;  
  25.     if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)  
  26.     {  
  27.         cerr << "\nFailed to initialize the WinSock2 DLL\n"  
  28.              << "error code: " << WSAGetLastError() << endl;  
  29.         return -1;  
  30.     }  
  31.   
  32.     //将命令行参数转换为IP地址  
  33.     u_long ulDestIP = inet_addr(argv[1]);  
  34.     if (ulDestIP == INADDR_NONE)  
  35.     {  
  36.         //转换不成功时按域名解析  
  37.         hostent* pHostent = gethostbyname(argv[1]);  
  38.         if (pHostent)  
  39.         {  
  40.             ulDestIP = (*(in_addr*)pHostent->h_addr).s_addr;  
  41.   
  42.             //输出屏幕信息  
  43.             cout << "\nTracing route to " << argv[1]   
  44.                  << " [" << inet_ntoa(*(in_addr*)(&ulDestIP)) << "]"  
  45.                  << " with a maximum of " << DEF_MAX_HOP << " hops.\n" << endl;  
  46.         }  
  47.         else //解析主机名失败  
  48.         {  
  49.             cerr << "\nCould not resolve the host name " << argv[1] << '\n'  
  50.                  << "error code: " << WSAGetLastError() << endl;  
  51.             WSACleanup();  
  52.             return -1;  
  53.         }  
  54.     }  
  55.     else  
  56.     {  
  57.         //输出屏幕信息  
  58.         cout << "\nTracing route to " << argv[1]   
  59.              << " with a maximum of " << DEF_MAX_HOP << " hops.\n" << endl;  
  60.     }  
  61.   
  62.     //填充目的Socket地址  
  63.     sockaddr_in destSockAddr;  
  64.     ZeroMemory(&destSockAddr, sizeof(sockaddr_in));  
  65.     destSockAddr.sin_family = AF_INET;  
  66.     destSockAddr.sin_addr.s_addr = ulDestIP;  
  67.   
  68.     //使用ICMP协议创建Raw Socket  
  69.     SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);  
  70.     if (sockRaw == INVALID_SOCKET)  
  71.     {  
  72.         cerr << "\nFailed to create a raw socket\n"  
  73.              << "error code: " << WSAGetLastError() << endl;  
  74.         WSACleanup();  
  75.         return -1;  
  76.     }  
  77.     //设置端口属性  
  78.     int iTimeout = DEF_ICMP_TIMEOUT;  
  79.   
  80.     if (setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeout, sizeof(iTimeout)) == SOCKET_ERROR)  
  81.     {  
  82.         cerr << "\nFailed to set recv timeout\n"  
  83.              << "error code: " << WSAGetLastError() << endl;  
  84.         closesocket(sockRaw);  
  85.         WSACleanup();  
  86.         return -1;  
  87.     }  
  88.     if (setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&iTimeout, sizeof(iTimeout)) == SOCKET_ERROR)  
  89.     {  
  90.         cerr << "\nFailed to set send timeout\n"  
  91.              << "error code: " << WSAGetLastError() << endl;  
  92.         closesocket(sockRaw);  
  93.         WSACleanup();  
  94.         return -1;  
  95.     }  
  96.   
  97.   
  98.     //创建ICMP包发送缓冲区和接收缓冲区  
  99.     char IcmpSendBuf[sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE];  
  100.     memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf));  
  101.     char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE];  
  102.     memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf));  
  103.   
  104.     //填充待发送的ICMP包  
  105.     ICMP_HEADER* pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;  
  106.     pIcmpHeader->type = ICMP_ECHO_REQUEST;  
  107.     pIcmpHeader->code = 0;  
  108.     pIcmpHeader->id = (USHORT)GetCurrentProcessId();  
  109.     memset(IcmpSendBuf+sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);  
  110.   
  111.     //开始探测路由  
  112.     DECODE_RESULT stDecodeResult;  
  113.     BOOL bReachDestHost = FALSE;  
  114.     USHORT usSeqNo = 0;  
  115.     int iTTL = 1;  
  116.     int iMaxHop = DEF_MAX_HOP;  
  117.     while (!bReachDestHost && iMaxHop--)  
  118.     {  
  119.         //设置IP数据报头的ttl字段  
  120.         setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char*)&iTTL, sizeof(iTTL));  
  121.   
  122.         //输出当前跳站数作为路由信息序号  
  123.         cout << setw(3) << iTTL << flush;  
  124.   
  125.         //填充ICMP数据报剩余字段  
  126.         ((ICMP_HEADER*)IcmpSendBuf)->cksum = 0;  
  127.         ((ICMP_HEADER*)IcmpSendBuf)->seq = htons(usSeqNo++);  
  128.         ((ICMP_HEADER*)IcmpSendBuf)->cksum = GenerateChecksum((USHORT*)IcmpSendBuf, sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE);  
  129.           
  130.         //记录序列号和当前时间  
  131.         stDecodeResult.usSeqNo = ((ICMP_HEADER*)IcmpSendBuf)->seq;  
  132.         stDecodeResult.dwRoundTripTime = GetTickCount();  
  133.           
  134.         //发送ICMP的EchoRequest数据报  
  135.         if (sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0,   
  136.                    (sockaddr*)&destSockAddr, sizeof(destSockAddr)) == SOCKET_ERROR)  
  137.         {  
  138.             //如果目的主机不可达则直接退出  
  139.             if (WSAGetLastError() == WSAEHOSTUNREACH)  
  140.                 cout << '\t' << "Destination host unreachable.\n"   
  141.                      << "\nTrace complete.\n" << endl;  
  142.             closesocket(sockRaw);  
  143.             WSACleanup();  
  144.             return 0;  
  145.         }  
  146.   
  147.         //接收ICMP的EchoReply数据报  
  148.         //因为收到的可能并非程序所期待的数据报,所以需要循环接收直到收到所要数据或超时  
  149.         sockaddr_in from;  
  150.         int iFromLen = sizeof(from);  
  151.         int iReadDataLen;  
  152.         while (1)  
  153.         {  
  154.             //等待数据到达  
  155.             iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE,   
  156.                                     0, (sockaddr*)&from, &iFromLen);  
  157.             if (iReadDataLen != SOCKET_ERROR) //有数据包到达  
  158.             {  
  159.                 //解码得到的数据包,如果解码正确则跳出接收循环发送下一个EchoRequest包  
  160.                 if (DecodeIcmpResponse(IcmpRecvBuf, iReadDataLen, stDecodeResult))  
  161.                 {  
  162.                     if (stDecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr)  
  163.                         bReachDestHost = TRUE;  
  164.   
  165.                     cout << '\t' << inet_ntoa(stDecodeResult.dwIPaddr) << endl;  
  166.                     break;  
  167.                 }  
  168.             }  
  169.             else if (WSAGetLastError() == WSAETIMEDOUT) //接收超时,打印星号  
  170.             {  
  171.                 cout << setw(9) << '*' << '\t' << "Request timed out." << endl;  
  172.                 break;  
  173.             }  
  174.             else  
  175.             {  
  176.                 cerr << "\nFailed to call recvfrom\n"  
  177.                      << "error code: " << WSAGetLastError() << endl;  
  178.                 closesocket(sockRaw);  
  179.                 WSACleanup();  
  180.                 return -1;  
  181.             }  
  182.         }  
  183.   
  184.         //TTL值加1  
  185.         iTTL++;  
  186.     }  
  187.     //输出屏幕信息  
  188.     cout << "\nTrace complete.\n" << endl;  
  189.   
  190.     closesocket(sockRaw);  
  191.     WSACleanup();  
  192.     return 0;  
  193. }  
  194.   
  195. //产生网际校验和  
  196. USHORT GenerateChecksum(USHORT* pBuf, int iSize)   
  197. {  
  198.     unsigned long cksum = 0;  
  199.     while (iSize>1)   
  200.     {  
  201.         cksum += *pBuf++;  
  202.         iSize -= sizeof(USHORT);  
  203.     }  
  204.     if (iSize)   
  205.         cksum += *(UCHAR*)pBuf;  
  206.   
  207.     cksum = (cksum >> 16) + (cksum & 0xffff);  
  208.     cksum += (cksum >> 16);  
  209.   
  210.     return (USHORT)(~cksum);  
  211. }  
  212.   
  213.   
  214. //解码得到的数据报  
  215. BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult)  
  216. {  
  217.     //检查数据报大小的合法性  
  218.     IP_HEADER* pIpHdr = (IP_HEADER*)pBuf;  
  219.     int iIpHdrLen = pIpHdr->hdr_len * 4;  
  220.     if (iPacketSize < (int)(iIpHdrLen+sizeof(ICMP_HEADER)))  
  221.         return FALSE;  
  222.   
  223.     //按照ICMP包类型检查id字段和序列号以确定是否是程序应接收的Icmp包  
  224.     ICMP_HEADER* pIcmpHdr = (ICMP_HEADER*)(pBuf+iIpHdrLen);  
  225.     USHORT usID, usSquNo;  
  226.     if (pIcmpHdr->type == ICMP_ECHO_REPLY)  
  227.     {  
  228.         usID = pIcmpHdr->id;  
  229.         usSquNo = pIcmpHdr->seq;  
  230.     }  
  231.     else if(pIcmpHdr->type == ICMP_TIMEOUT)  
  232.     {  
  233.         char* pInnerIpHdr = pBuf+iIpHdrLen+sizeof(ICMP_HEADER);     //载荷中的IP头  
  234.         int iInnerIPHdrLen = ((IP_HEADER*)pInnerIpHdr)->hdr_len * 4;//载荷中的IP头长  
  235.         ICMP_HEADER* pInnerIcmpHdr = (ICMP_HEADER*)(pInnerIpHdr+iInnerIPHdrLen);//载荷中的ICMP头  
  236.         usID = pInnerIcmpHdr->id;  
  237.         usSquNo = pInnerIcmpHdr->seq;  
  238.     }  
  239.     else  
  240.         return FALSE;  
  241.   
  242.     if (usID != (USHORT)GetCurrentProcessId() || usSquNo !=stDecodeResult.usSeqNo)   
  243.         return FALSE;  
  244.   
  245.     //处理正确收到的ICMP数据报  
  246.     if (pIcmpHdr->type == ICMP_ECHO_REPLY ||  
  247.         pIcmpHdr->type == ICMP_TIMEOUT)  
  248.     {  
  249.         //返回解码结果  
  250.         stDecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;  
  251.         stDecodeResult.dwRoundTripTime = GetTickCount()-stDecodeResult.dwRoundTripTime;  
  252.   
  253.         //打印屏幕信息  
  254.         if (stDecodeResult.dwRoundTripTime)  
  255.             cout << setw(6) << stDecodeResult.dwRoundTripTime << " ms" << flush;  
  256.         else  
  257.             cout << setw(6) << "<1" << " ms" << flush;  
  258.   
  259.         return TRUE;  
  260.     }  
  261.   
  262.     return FALSE;  
  263. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值