透析ICMP协议(五): 应用篇路由追踪

类别:VC语言 点击:0 评论:0 推荐:

透析ICMP协议(五):
 应用篇路由追踪
===============================
这篇文章出自bugfree/CSDN
平台: VC6 Windows XP

这篇文章本应该在三天之前写完, 但是由于众所周知的原因, 我在北大bbs上转了三天, 所以这么迟才发出. 由于的懒惰导致对大家的不便, 我表示深深的道歉.


原理简介:
-------- 
   通过前四节的介绍, 可能大家对ICMP的应用有了初步的了解. 不过开始本节之前我对ICMP协议再从宏观上做些介绍. 大家都知道ICMP是为于ISO的第三层---网络层。 既是它同IP协议为于同一层, 然而大家可能也只到,ICMP协议要用到IP协议, 所以有一些书上说ICMP位ISO的第四层, 那是错误的。 同样这样那些书上这样画的的例子也是错误的, 我就发现某外资通讯公司的资料上有这样两种错误的画法
   --------------------------       
   | ICMP | TCP(SCTP)  |
   --------------------------
   |         IP                   |
   --------------------------
  
   ---------------------------
   |...        | TCP(SCTP)   |
   ---------------------------
   | ICMP |   IP                |
   ----------------------------

其实如上的画法是错误的, 正确地画法应为:
   ---------------------       
   |...   | TCP(SCTP)   |
   ---------------------
   | ICMP |                 |
   ----------                |
   |       IP                  |
   ---------------------

接下来,让我们来说明怎样实现追踪路由的功能, 大家通过我的第一节的阅读可能已经了解了超时报文的具体内容(参见透析ICMP协议(一):  协议原理), 它在如果网关在处理数据报时发现生存周期域(ttl)为零,此数据报必须抛弃。网关同时必须通过超时信息通知源主机。这是它的报文的具体结构:
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     Type(11)  |     Code(0/1) |          Checksum             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             unused                            |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |      Internet Header + 64 bits of Original Data Datagram      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

通过利用setsockopt()函数设置ICMP包的IP包头中的ttl字段便可以达到这种效果。 具体过程如下, 假设你的IP到达目标地址需要过n个路由器(n>1)。 则
 1. 初始化第一个ICMP包,并设置IP包头中的TTL为1, 则得到第一个数据路由器发回的超时报文
 2. 一般情况下:初始化第i(i<n)个ICMP包,并设置IP包头中的TTL为i, 则得到第一个数据路由器发回的超时报文

剩下的问题为如何确定超时ICMP报文的路由器IP地址得到它的机器名的信息。 这个问题可能很多读者都会求, 用gethostbyaddr()可以得到答案。

经过理论的论证后, 让我们看看如何实现。
  

具体实现:(具体如何初试化ICMP的数据包上节已有详细的介绍,这里只是补充路由追踪的代码)
--------
主要代码如下:

unsigned long ipback = 0; //超时报文的IP的初试值
unsigned long ms = 0; //超时值
struct hostent *hHost;
char m_address[256];

//直到找到目标主机, 或达到最大跳数(HOPS)
while (ipback != ipfinal){ 
 hHost = 0;


 //对到目标主机中间的某个路由器发放ping的报文(ttl为1~N-1之间)
 if (Ping(m_address,ttl,ipback,ms))
 {
  sin.sin_family = AF_INET;
  sin.sin_addr.S_un.S_addr = ipback; // 由函数返回的IP地址
    // 查找主机名
  hHost = gethostbyaddr((char*)&sin.sin_addr, 4, PF_INET);
  //这里可以输出hHost的内容
 }
 ttl++;
 if (ttl > MAX_HOPS)  //达到最大跳数
 {
  break;
 }
}

==================
ping函数的代码
==================
int Ping(const char * host, int ttl, unsigned long& ipback, unsigned long& ms)
{
 SOCKET sockRaw;
 struct sockaddr_in dest,from;
 struct hostent * hp;
 int bread,datasize;
 int fromlen = sizeof(from);
 int timeout = 100;
 char *dest_ip;
 char *icmp_data;
 char *recvbuf;
 unsigned int addr=0;
 const int MAX_PACKET = 1024;

 //初始化Socket
 sockRaw = WSASocket (AF_INET,
      SOCK_RAW,
      IPPROTO_ICMP,
      NULL, 0, WSA_FLAG_OVERLAPPED);

 if (sockRaw == INVALID_SOCKET)
 {
  // 错误
 }

   // 设置IP包头的ttl字段
 bread = setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(int));
 if(bread == SOCKET_ERROR)
 {
  // 错误
 }

 // 设置接受超时为100ms
 bread = setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));
 if(bread == SOCKET_ERROR)
 {
  // 错误
 }

 //禁止用Nagle算法缓存数据
 bread = setsockopt(sockRaw, SOL_SOCKET, TCP_NODELAY, (const char*)&killnagle, sizeof(int));
 if (bread == SOCKET_ERROR)
 {
  // 错误 
 }

 timeout = 1000;
 // 设置发送超时为100ms
 bread = setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,
       sizeof(timeout));
 if(bread == SOCKET_ERROR)
 {
  // 错误 
 }

 //下面的代码生成ICMP包
 memset(&dest,0,sizeof(dest));
 hp = gethostbyname(host);
 if (!hp)
 {
  addr = inet_addr(host);
 }
 if ((!hp)  && (addr == INADDR_NONE) )
 {
  // 错误 
 }
 if (hp != NULL)
  memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length);
 else
  dest.sin_addr.s_addr = addr;

 //初始化dest
 if (hp)
  dest.sin_family = hp->h_addrtype;
 else
  dest.sin_family = AF_INET;
 
 dest_ip = inet_ntoa(dest.sin_addr);

 // 设置包长度
 datasize = DEF_PACKET_SIZE;

 // 计算包大小
 datasize += sizeof(IcmpHeader); 

 icmp_data = (char *)new[MAX_PACKET]; //分配内存,可以用new 和 delete
 recvbuf = (char *)new[MAX_PACKET];

 if (!icmp_data)
 {
  // 释放内存,退出
 }

 if (!recvbuf)
 {
  // 释放内存,退出 }
 }

 memset(icmp_data,0,MAX_PACKET);
 fill_icmp_data(icmp_data,datasize); // 这个函数用来填充ICMP的数据包

 int bwrote;
 ((IcmpHeader*)icmp_data)->i_cksum = 0;
 ((IcmpHeader*)icmp_data)->timestamp = GetTickCount(); // 存入当前时间值
 ((IcmpHeader*)icmp_data)->i_seq = seq_no++;
 
 // 计算校验和
 ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);

  // 为了最后计算ICMP包回来的总时间
 unsigned long tc = GetTickCount();
 //发送数据包
 bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest, sizeof(dest));

 if (bwrote == SOCKET_ERROR)
 {
  // 错误
 }
 if (bwrote < datasize ) //发送字节数对否
 {
 }

 // 接受数据包
 bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,
     &fromlen);
 //计算总时间
 ms = GetTickCount() - tc;

 if (bread == SOCKET_ERROR)
 {
  // 错误
 }

 // 得到返回的路由器
 ipback = from.sin_addr.s_addr;

 return 1;
}

===============================
函数fill_icmp_data()的源代码
===============================
//这个结构下面将用到
typedef struct _ihdr {
  BYTE i_type;
  BYTE i_code;
  USHORT i_cksum;
  USHORT i_id;
  USHORT i_seq;
  ULONG timestamp; /* 这不是ICMP包的一部分, 只是为了计算时间 */
}IcmpHeader;

void fill_icmp_data(char * icmp_data, int datasize){

  IcmpHeader *icmp_hdr;
  char *datapart;

  icmp_hdr = (IcmpHeader*)icmp_data; 

  icmp_hdr->i_type = ICMP_ECHO;
  icmp_hdr->i_code = 0;
  icmp_hdr->i_id = (USHORT)GetCurrentProcessId();
  icmp_hdr->i_cksum = 0;
  icmp_hdr->i_seq = 0;
 
  datapart = icmp_data + sizeof(IcmpHeader);  //计算数据域的开始地址
  // 初试化数据域
  memset(datapart,'E', datasize - sizeof(IcmpHeader));

}


链接:
-------
我的其它文章,<<透析ICMP协议>>, 和其它文章参见:
http://www.csdn.net/develop/author/netauthor/bugfree/

联系方式:
-------
 [email protected]

本文地址:http://com.8s8s.com/it/it1838.htm