TraceRoute(tracert)实现原理

来源:互联网 发布:软件质量问题处理流程 编辑:程序博客网 时间:2024/05/01 19:16

TraceRoute程序的实现主要涉及IP头部生存时间(time to live, TTL)字段的使用。

    设置TTL字段的目的是为了防止数据报由于选路错误或其他软硬件原因从而导致在网络中无休止的流动,TTL字段指定了数据报的生存时间。TTL的初始值由源主机设置,当一份数据报经过路由器时,处理该数据报的路由器都需要把TTL值减去数据报在路由器中停留的秒数。但事实上大多数路由器只是简单地将TTL值减1,因此TTL字段最终被实现为一个跳站计数器。当TTL字段的值被减为0时,路由器就不会转发该数据报,而是将其丢弃,并产生一份ICMP超时差错报文发往源主机以通知错误的发生。TraceRoute程序的关键就在于返回的这份ICMP超时差错报文的源地址就是途经路由器的IP地址。由此,通过依次递增TTL字段的值,就可以得到一份数据报在其传输路径上所经过的路由信息。

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


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/microtong/archive/2008/11/04/3220450.aspx

 

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

 

 

 

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

 

//TraceRoute.h

#ifndef _ITRACERT_H_
#define _ITRACERT_H_

#pragma pack(1)
//IP数据报头
typedef struct
{
 unsigned char hdr_len :4;  // length of the header
 unsigned char version :4;  // version of IP
 unsigned char tos;   // type of service
 unsigned short total_len;  // total length of the packet
 unsigned short identifier;  // unique identifier
 unsigned short frag_and_flags; // flags
 unsigned char ttl;   // time to live
 unsigned char protocol;  // protocol (TCP, UDP etc)
 unsigned short checksum;  // IP checksum

 unsigned long sourceIP;  // source IP address
 unsigned long destIP;   // destination IP address

} 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;

#pragma pack()

//ICMP类型字段
const BYTE ICMP_ECHO_REQUEST = 8; //请求回显
const BYTE ICMP_ECHO_REPLY  = 0; //回显应答
const BYTE ICMP_TIMEOUT   = 11; //传输超时

const DWORD DEF_ICMP_TIMEOUT = 3000; //默认超时时间,单位ms
const int DEF_ICMP_DATA_SIZE = 32; //默认ICMP数据部分长度
const int MAX_ICMP_PACKET_SIZE = 1024; //最大ICMP数据报的大小
const int DEF_MAX_HOP = 30;    //最大跳站数

USHORT GenerateChecksum(USHORT* pBuf, int iSize);
BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult);

#endif // _ITRACERT_H_

 

//TraceRoute.cpp

/*----------------------------------------------------------
功能说明:该程序简单实现了Windows操作系统的tracert命令功能,
      可以输出IP报文从本机出发到达目的主机所经过的路由信息。
-----------------------------------------------------------*/
#include <iostream>
#include <iomanip>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include "TraceRoute.h"

#pragma comment(lib,"ws2_32")

using namespace std;

int main(int argc, char* argv[])
{
 //检查命令行参数
 if (argc != 2)
 {
  cerr << "/nUsage: itracert ip_or_hostname/n";
  return -1;
 }

 //初始化winsock2环境
 WSADATA wsa;
 if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
 {
  cerr << "/nFailed to initialize the WinSock2 DLL/n"
    << "error code: " << WSAGetLastError() << endl;
  return -1;
 }

 //将命令行参数转换为IP地址
 u_long ulDestIP = inet_addr(argv[1]);
 if (ulDestIP == INADDR_NONE)
 {
  //转换不成功时按域名解析
  hostent* pHostent = gethostbyname(argv[1]);
  if (pHostent)
  {
   ulDestIP = (*(in_addr*)pHostent->h_addr).s_addr;

   //输出屏幕信息
   cout << "/nTracing route to " << argv[1] 
     << " [" << inet_ntoa(*(in_addr*)(&ulDestIP)) << "]"
     << " with a maximum of " << DEF_MAX_HOP << " hops./n" << endl;
  }
  else //解析主机名失败
  {
   cerr << "/nCould not resolve the host name " << argv[1] << '/n'
     << "error code: " << WSAGetLastError() << endl;
   WSACleanup();
   return -1;
  }
 }
 else
 {
  //输出屏幕信息
  cout << "/nTracing route to " << argv[1] 
    << " with a maximum of " << DEF_MAX_HOP << " hops./n" << endl;
 }

 //填充目的Socket地址
 sockaddr_in destSockAddr;
 ZeroMemory(&destSockAddr, sizeof(sockaddr_in));
 destSockAddr.sin_family = AF_INET;
 destSockAddr.sin_addr.s_addr = ulDestIP;

 //使用ICMP协议创建Raw Socket
 SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
 if (sockRaw == INVALID_SOCKET)
 {
  cerr << "/nFailed to create a raw socket/n"
    << "error code: " << WSAGetLastError() << endl;
  WSACleanup();
  return -1;
 }
 //设置端口属性
 int iTimeout = DEF_ICMP_TIMEOUT;

 if (setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeout, sizeof(iTimeout)) == SOCKET_ERROR)
 {
  cerr << "/nFailed to set recv timeout/n"
    << "error code: " << WSAGetLastError() << endl;
  closesocket(sockRaw);
  WSACleanup();
  return -1;
 }


 //创建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包
 ICMP_HEADER* pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;
 pIcmpHeader->type = ICMP_ECHO_REQUEST;
 pIcmpHeader->code = 0;
 pIcmpHeader->id = (USHORT)GetCurrentProcessId();
 memset(IcmpSendBuf+sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);

 //开始探测路由
 DECODE_RESULT stDecodeResult;
 BOOL bReachDestHost = FALSE;
 USHORT usSeqNo = 0;
 int iTTL = 1;
 int iMaxHop = DEF_MAX_HOP;
 while (!bReachDestHost && iMaxHop--)
 {
  //设置IP数据报头的ttl字段
  setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char*)&iTTL, sizeof(iTTL));

  //输出当前跳站数作为路由信息序号
  cout << setw(3) << iTTL << flush;

  //填充ICMP数据报剩余字段
  ((ICMP_HEADER*)IcmpSendBuf)->cksum = 0;
  ((ICMP_HEADER*)IcmpSendBuf)->seq = htons(usSeqNo++);
  ((ICMP_HEADER*)IcmpSendBuf)->cksum = GenerateChecksum((USHORT*)IcmpSendBuf, sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE);
  
  //记录序列号和当前时间
  stDecodeResult.usSeqNo = ((ICMP_HEADER*)IcmpSendBuf)->seq;
  stDecodeResult.dwRoundTripTime = GetTickCount();
  
  //发送ICMP的EchoRequest数据报
  if (sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0, 
       (sockaddr*)&destSockAddr, sizeof(destSockAddr)) == SOCKET_ERROR)
  {
   //如果目的主机不可达则直接退出
   if (WSAGetLastError() == WSAEHOSTUNREACH)
    cout << '/t' << "Destination host unreachable./n" 
      << "/nTrace complete./n" << endl;
   closesocket(sockRaw);
   WSACleanup();
   return 0;
  }

  //接收ICMP的EchoReply数据报
  //因为收到的可能并非程序所期待的数据报,所以需要循环接收直到收到所要数据或超时
  sockaddr_in from;
  int iFromLen = sizeof(from);
  int iReadDataLen;
  while (1)
  {
   //等待数据到达
   iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 
         0, (sockaddr*)&from, &iFromLen);
   if (iReadDataLen != SOCKET_ERROR) //有数据包到达
   {
    //解码得到的数据包,如果解码正确则跳出接收循环发送下一个EchoRequest包
    if (DecodeIcmpResponse(IcmpRecvBuf, iReadDataLen, stDecodeResult))
    {
     if (stDecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr)
      bReachDestHost = TRUE;

     cout << '/t' << inet_ntoa(stDecodeResult.dwIPaddr) << endl;
     break;
    }
   }
   else if (WSAGetLastError() == WSAETIMEDOUT) //接收超时,打印星号
   {
    cout << setw(9) << '*' << '/t' << "Request timed out." << endl;
    break;
   }
   else
   {
    cerr << "/nFailed to call recvfrom/n"
      << "error code: " << WSAGetLastError() << endl;
    closesocket(sockRaw);
    WSACleanup();
    return -1;
   }
  }

  //TTL值加1
  iTTL++;
 }
 //输出屏幕信息
 cout << "/nTrace complete./n" << endl;

 closesocket(sockRaw);
 WSACleanup();
 return 0;
}

//产生网际校验和
USHORT GenerateChecksum(USHORT* pBuf, int iSize) 
{
 unsigned long cksum = 0;
 while (iSize>1) 
 {
  cksum += *pBuf++;
  iSize -= sizeof(USHORT);
 }
 if (iSize) 
  cksum += *(UCHAR*)pBuf;

 cksum = (cksum >> 16) + (cksum & 0xffff);
 cksum += (cksum >> 16);

 return (USHORT)(~cksum);
}


//解码得到的数据报
BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& stDecodeResult)
{
 //检查数据报大小的合法性
 IP_HEADER* pIpHdr = (IP_HEADER*)pBuf;
 int iIpHdrLen = pIpHdr->hdr_len * 4;
 if (iPacketSize < (int)(iIpHdrLen+sizeof(ICMP_HEADER)))
  return FALSE;

 //按照ICMP包类型检查id字段和序列号以确定是否是程序应接收的Icmp包
 ICMP_HEADER* pIcmpHdr = (ICMP_HEADER*)(pBuf+iIpHdrLen);
 USHORT usID, usSquNo;
 if (pIcmpHdr->type == ICMP_ECHO_REPLY)
 {
  usID = pIcmpHdr->id;
  usSquNo = pIcmpHdr->seq;
 }
 else if(pIcmpHdr->type == ICMP_TIMEOUT)
 {
  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;
  usSquNo = pInnerIcmpHdr->seq;
 }
 else
  return FALSE;

 if (usID != (USHORT)GetCurrentProcessId() || usSquNo !=stDecodeResult.usSeqNo) 
  return FALSE;

 //处理正确收到的ICMP数据报
 if (pIcmpHdr->type == ICMP_ECHO_REPLY ||
  pIcmpHdr->type == ICMP_TIMEOUT)
 {
  //返回解码结果
  stDecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
  stDecodeResult.dwRoundTripTime = GetTickCount()-stDecodeResult.dwRoundTripTime;

  //打印屏幕信息
  if (stDecodeResult.dwRoundTripTime)
   cout << setw(6) << stDecodeResult.dwRoundTripTime << " ms" << flush;
  else
   cout << setw(6) << "<1" << " ms" << flush;

  return TRUE;
 }

 return FALSE;
}


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/microtong/archive/2008/11/04/3220463.aspx

 

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

 

    本程序使用Windows  IP帮助函数发送ICMP回显请求报文,实现tracert的功能,探测每一跳路由器的IP地址和往返时间。

//TraceRoute2.cpp
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#include <IPHlpApi.h>
#include <IcmpAPI.h>

#pragma comment(lib,"Iphlpapi.lib")
#pragma comment(lib,"ws2_32.lib")


int main(int argc,char* argv[]){

 if(argc!=2){
  printf("Usage: %s destIP/n",argv[0]);
  exit(-1);
 }
 WSADATA wsa;
 if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){
  printf("WSAStartup failed./n");
  exit(-1);
 }
 //转换IP地址到整数
 unsigned long ip = inet_addr(argv[1]);
 if(ip==INADDR_NONE){
  //用户可能输入的是域名
  hostent* pHost = gethostbyname(argv[1]);
  //如果域名无法解析
  if(pHost==NULL){
   printf("Invalid IP or domain name: %s/n", argv[1]);
   exit(-1);
  }
  //取域名的第一个IP地址
  ip = *(unsigned long*)pHost->h_addr_list[0];
  printf("trace route to %s(%s)/n/n",argv[1],inet_ntoa(*(in_addr*)&ip));
 }else{
  printf("trace route to %s/n/n",argv[1]);
 }

 //打开ICMP句柄
 HANDLE hIcmp;
 if ((hIcmp = IcmpCreateFile()) == INVALID_HANDLE_VALUE){
  printf("/tUnable to open ICMP file./n");
  exit(-1);
 }

 //设置IP报头的TTL值
 IP_OPTION_INFORMATION IpOption;
 ZeroMemory(&IpOption,sizeof(IP_OPTION_INFORMATION));
 IpOption.Ttl = 1;

 //设置要发送的数据
 char SendData[32];
 memset(SendData,'0',sizeof(SendData));

 //设置接收缓冲区
 char ReplyBuffer[sizeof(ICMP_ECHO_REPLY)+32];
 PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer;

 BOOL bLoop = TRUE;
 int iMaxHop = 30;
 while(bLoop && iMaxHop--){
  printf("%2d: ",IpOption.Ttl);
  //发送ICMP回显请求
  if(IcmpSendEcho(hIcmp,(IPAddr)ip, SendData, sizeof(SendData), &IpOption, 
   ReplyBuffer, sizeof(ReplyBuffer), 3000)!=0){
    if(pEchoReply->RoundTripTime==0){
     printf("/t<1ms");
    }else{
     printf("/t%dms",pEchoReply->RoundTripTime);
    }
    printf("/t%s/n",inet_ntoa(*(in_addr*)&(pEchoReply->Address)));
    //判断是否完成路由路径探测
    if((unsigned long)pEchoReply->Address==ip){
     printf("/nTrace complete./n");
     bLoop = FALSE;
    }
  }else{
   printf("/t*/tRequest time out./n");
  }
  IpOption.Ttl++;
 }

 IcmpCloseHandle(hIcmp);
 WSACleanup();
 return 0;

}


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/microtong/archive/2008/11/04/3220496.aspx

 

 

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

    本程序直接使用ICMP.DLL动态链接库中的函数来发送ICMP回显请求报文,用以探测路由信息,输出每一跳路由器的IP和往返时间,实现tracert的功能。

//TraceRoute3.cpp
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#include <IPHlpApi.h>

//增加静态链接库ws2_32.lib
#pragma comment(lib,"ws2_32.lib")

//声明3个函数类型的指针
typedef HANDLE (WINAPI *lpIcmpCreateFile)(VOID);
typedef BOOL (WINAPI *lpIcmpCloseHandle)(HANDLE  IcmpHandle);
typedef DWORD (WINAPI *lpIcmpSendEcho)(
    HANDLE                   IcmpHandle,
    IPAddr                   DestinationAddress,
    LPVOID                   RequestData,
    WORD                     RequestSize,
    PIP_OPTION_INFORMATION   RequestOptions,
    LPVOID                   ReplyBuffer,
    DWORD                    ReplySize,
    DWORD                    Timeout
    );

int main(int argc,char* argv[]){

 if(argc!=2){
  printf("Usage: %s destIP/n",argv[0]);
  exit(-1);
 }
 WSADATA wsa;
 if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){
  printf("WSAStartup failed./n");
  exit(-1);
 }
 //转换IP地址到整数
 unsigned long ip = inet_addr(argv[1]);
 if(ip==INADDR_NONE){
  //用户可能输入的是域名
  hostent* pHost = gethostbyname(argv[1]);
  //如果域名无法解析
  if(pHost==NULL){
   printf("Invalid IP or domain name: %s/n", argv[1]);
   exit(-1);
  }
  //取域名的第一个IP地址
  ip = *(unsigned long*)pHost->h_addr_list[0];
  printf("trace route to %s(%s)/n/n",argv[1],inet_ntoa(*(in_addr*)&ip));
 }else{
  printf("trace route to %s/n/n",argv[1]);
 }

 //载入ICMP.DLL动态库
 HMODULE hIcmpDll = LoadLibrary("icmp.dll");
 if(hIcmpDll==NULL){
  printf("fail to load icmp.dll/n");
  exit(-1);
 }
 //定义3个函数指针
 lpIcmpCreateFile IcmpCreateFile;
 lpIcmpCloseHandle IcmpCloseHandle;
 lpIcmpSendEcho IcmpSendEcho;

 //从ICMP.DLL中获取所需的函数入口地址
 IcmpCreateFile = (lpIcmpCreateFile)GetProcAddress(hIcmpDll,"IcmpCreateFile");
 IcmpCloseHandle = (lpIcmpCloseHandle)GetProcAddress(hIcmpDll,"IcmpCloseHandle");
 IcmpSendEcho = (lpIcmpSendEcho)GetProcAddress(hIcmpDll,"IcmpSendEcho");

 //打开ICMP句柄
 HANDLE hIcmp;
 if ((hIcmp = IcmpCreateFile()) == INVALID_HANDLE_VALUE){
  printf("/tUnable to open ICMP file./n");
  exit(-1);
 }

 //设置IP报头的TTL值
 IP_OPTION_INFORMATION IpOption;
 ZeroMemory(&IpOption,sizeof(IP_OPTION_INFORMATION));
 IpOption.Ttl = 1;

 //设置要发送的数据
 char SendData[32];
 memset(SendData,'0',sizeof(SendData));

 //设置接收缓冲区
 char ReplyBuffer[sizeof(ICMP_ECHO_REPLY)+32];
 PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer;

 BOOL bLoop = TRUE;
 int iMaxHop = 30;
 while(bLoop && iMaxHop--){
  printf("%2d: ",IpOption.Ttl);
  //发送ICMP回显请求
  if(IcmpSendEcho(hIcmp,(IPAddr)ip, SendData, sizeof(SendData), &IpOption, 
   ReplyBuffer, sizeof(ReplyBuffer), 3000)!=0){
    if(pEchoReply->RoundTripTime==0){
     printf("/t<1ms");
    }else{
     printf("/t%dms",pEchoReply->RoundTripTime);
    }
    printf("/t%s/n",inet_ntoa(*(in_addr*)&(pEchoReply->Address)));
    //判断是否完成路由路径探测
    if((unsigned long)pEchoReply->Address==ip){
     printf("/nTrace complete./n");
     bLoop = FALSE;
    }
  }else{
   printf("/t*/tRequest time out./n");
  }
  IpOption.Ttl++;
 }

 IcmpCloseHandle(hIcmp);

 FreeLibrary(hIcmpDll);

 WSACleanup();
 return 0;
}


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/microtong/archive/2008/11/04/3220523.aspx

原创粉丝点击