TraceRoute(tracert)源码(基于原始套接字实现)

时间:2022-12-18 10:59:56

 TraceRoute(tracert)源码(基于原始套接字实现) 佟强 2008.11.4

    TraceRoute实现原理 http://blog.csdn.net/microtong/archive/2008/11/04/3220450.aspx

 

本程序实现Windows下tracert的功能,程序使用原始套接字发送ICMP回显请求报文,本接收ICMP超时差错报文,和ICMP回显应答报文,得到每一跳的路由器IP地址和往返时间。

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