ping源代码完全解析

来源:互联网 发布:可以看老王搞事的软件 编辑:程序博客网 时间:2024/06/05 20:39
author:    Zero1,lingyi.pro#163.com
date:        2008-04-27
--------------------------------------------------
目录
-------------
1.相关知识
2.相关数据结构
3.相关函数
4.代码分析
5.小结



1.相关知识
-----------
ping命令可以查看一个系统到另一个系统是否可达,即判断网络连接是否正常。它的工作原理是:向网络上的另一台主机发送ICMP报文,并等待ICMP回显应答(ECHO_REPLY);如果目标系统接收到ICMP报文,它将返回给发送者一样的报文;同时ping可以计算这两台主机间的往返时间,以表明两主机间的距离。

ps:当然现在有些主机为了隐藏自己,对于ping发送来ICMP报文不返回回显信息。

想要深入了解ping的工作原理,还得了解ping命令所使用的TCP/IP协议。

ICMP(Internet Control Message Protocol,网际控制报文协议)是为网关和目标主机提供的一种差错控制机制,使它们在发生差错是把错误信息报告给源发送方(source sender)。ICMP协议是IP层的一个协议,但是由于差错报告发送会源发送方的过程中可能要经过若干子网,因此牵扯到路由选择问题,所以ICMP报文通常有IP协议来发送。于是ICMP数据报发送前需要进行两次封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报,如下图所示

        ----------------
        |       IP报头       |
        |---------------------|
        |  |----------------| |
        | |    ICMP报头   ||
        | |-----------------| |
        | |  |------------| | |
        | | | ICMP数据报 | | |
        | |  |------------| | |
        | |-----------------| |
        |---------------------|
        

由于IP协议是一种点对点的协议,而不是端对端的协议,它提供无连接的数据包服务(通常使用UDP协议),所以不需要bind()和connect()函数来绑定和连接端口。用sendto()函数来发送数据报,接收数据使用recvfrom()函数。(更多信息参看socket编程相关资料)



2.相关数据结构
----------------
2.1 IP报头格式

IP报头格式:版本号(4bit),IP报头长度(4),服务类型(8),数据报长度(16),报文标志ID(16),报文标志F(3),分段偏移量(13),生存时间(8),协议号(8),报头校验和(16),源地址(32),目的地址(32),任选项和填充位(若干)。示意图如下:

    --------------------------------------------------------------------------
    | 版本号VER(4) | IP报头长度IHL(4) | 服务类型TOS(8) | 数据报长度TL(16) |
    |------------------------------------------------------------------------|
    |     报文标志ID(16)   |   报文标志F(3)   |    分段偏移量 F0(13)      |
    |------------------------------------------------------------------------|
    |     生存时间(8)    |     协议号PORT(8)    |     报头校验和(16)         |
    |------------------------------------------------------------------------|
    |                           源地址(32)                                    |
    |------------------------------------------------------------------------|
    |                           目的地址(32)                                |
    |------------------------------------------------------------------------|
    |                        任选项和填充位(若干)                              |
    --------------------------------------------------------------------------

根据IP报头格式信息,可定义IP报头格式数据结构如下(更详细请阅读<netinet/ip.h>):

 
/****************************
*  计算icmp校验和算法
*  2008-05-02
* *************************
*/


unsigned 
short chksum(addr,len)   
    unsigned 
short *addr;  /* 校验数据开始地址(注意是以2字节为单位) */   
    
int len;                /* 校验数据的长度大小,以字节为单位 */
{  
    
int sum = 0;        /* 校验和 */   
    
int nleft = len;    /* 未累加的数据长度 */   
    unsigned 
short *p;  /* 走动的临时指针,2字节为单位 */   
    unsigned 
short tmp = 0/* 奇数字节长度时用到 */   
   
while( nleft > 1)   
   
{       
       sum 
+= *p++;    /* 累加 */       
       nleft 
-= 2;   
    }
   
   
if(nleft == 1)      /* 奇数字节长度 */   
   
{       
       
*(unsigned char *)&tmp = *(&(unsigned char *)p); /* 将最后字节压如2字节的高位 */       
       sum 
+= tmp;   
   }
  
    sum 
+= (sum >> 16+ (sum & 0xffff);    /* 高位低位相加 */   
    sum 
+= sum >> 16;                       /* 上一步溢出时,将溢出位也加到sum中 */   
    tmp 
= ~sum;     /* 注意类型转换,现在的校验和为16位 */    
    
return tmp;
}


ping程序只用到以下数据段:

ip_hl:IP报头长度(Internet Header Length),以4字节为一单位来记录IP报头长度。
ip_ttl:Time to Live,生存期,以秒为单位,指明此IP数据包在网络上停留的最长时间,其值由发送端设定,每经过一个路由,其值自减1,当值为0时,该IP数据包将被丢弃。



2.2 ICMP报头格式

ICMP分为错误报告报文和查询报文两种。每个ICMP报头均包含类型、编码和校验和三项,长度分别为8位、8位和16位,其他选项随ICMP功能的不同而不同(详见《TCP/IP协议详解-卷1:协议》p59)。

ICMP报头格式示意图如下:

    ----------------------------------------------
    |   类型(8)  |  代码(8)  |  校验和(16)  |
    |--------------------------------------------|
    |          (不同类型代码有不同的内容)          |
    ----------------------------------------------

ping命令中只用到"回显应答"(ICMP_ECHO)和"回显请求"(ICMP_ECHOREPLY)两种。Linux下定义为:

#define ICMP_ECHO    0
#define ICMP_ECHOREPLY    8

这两种ICMP类型的报头格式示意图如下:
    
    --------------------------------------------------------------
    | 类型TYPE(0或8) | 编码CODE(没有使用) | 校验和SHECKSUM(16) |
    |-------------------------------------------------------------|
    |        标志符Identifier      |     序列号Sequence       |
    --------------------------------------------------------------- 


Linux下ICMP数据结构的定义如下(详见<netinet/ip_icmp.h>):

struct icmp
{
    u_int8_t icmp_type;        
/* 消息类型 */
    u_int8_t icmp_code;        
/* 编码 */
    u_int16_t icmp_cksum;    
/* 校验和 */
    union
    
{
        u_char ih_pptr;        
/* ICMP_PARAMPROB */
        
struct in_addr ih_gwaddr;    /* 网关地址 */
        
struct ih_idseq        /* 显示数据报 */
        {
            u_int16_t icd_id;    
/*  */
            u_int16_t icd_seq;    
/*  */
        }ih_idseq;
       
        u_int32_t ih_void;        
/*  */
       
        
/* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery(rffc1191) */
        
struct ih_pmtu
        
{
            u_int16_t ipm_void;        
/*  */
            u_int16_t ipm_nextmtu;    
/*  */
        }
ih_pmtu;
       
        
struct ih_rtradv
        
{
            u_int8_t irt_num_addr;    
/*  */
            u_int8_t irt_wpa;        
/*  */
            u_int16_t irt_lifetime;    
/*  */
        }
ih_rtradv;
    }
icmp_hun;
   
    
#define icmp_pptr    icmp_hun.ih_pptr
    
#define icmp_gwaddr    icmp_hun.ih_gwaddr
    
#define icmp_id    icmp_hun.ih_idseq.icd_id
    
#define icmp_seq    icmp_hun.ih_idseq.icd_seq
    
#define icmp_void    icmp_hun.ih_void
    
#define icmp_pmvoid    icmp_hun.ih_pmtu.ipm_void
    
#define icmp_nextmtu    icmp_hun.ih_pmtu.ipm_nextmtu
    
#define icmp_num_addrs    icmp_hun.ih_rtradv.irt_num_addrs
    
#define icmp_wpa    icmp_hun.ih_rtradv.irt_wpa
    
#define icmp_lifetime    icmp_hun.ih_rtradv.irt_lifetime
   
    union
    
{
        
struct
        
{
            u_int32_t its_otime;
            u_int32_t its_rtime;
            u_int32_t its_ttime;
        }
id_ts;
       
        
struct
        
{
            
struct ip idi_ip;
            
/* 选项及64位数据 */
        }
id_ip;
       
        
struct icmp_ra_addr id_radv;
        u_int32_t id_mask;    
/**/
        u_int8_t id_data[
1];
    }
icmp_dun;
   
    
#define icmp_otime    icmp_dun.id_ts.its_otime
    
#define icmp_rtime    icmp_dun.id_ts.its_rtime
    #edfine icmp_ttime    icmp_dun.id_ts.its_ttime
    
#define icmp_ip    icmp_dun.id_ip.idi_ip
    #edfine icmp_radv    icmp_dun.id_radv
    
#define icmp_mask    icmp_dun.id_mask
    
#define icmp_data    icmp_dun.id_data
}
;

        /****************************
*  计算icmp校验和算法
*  2008-05-02
* *************************
*/

unsigned 
short chksum(addr,len)   
    unsigned 
short *addr;  /* 校验数据开始地址(注意是以2字节为单位) */   
    
int len;                /* 校验数据的长度大小,以字节为单位 */
{  
    
int sum = 0;        /* 校验和 */   
    
int nleft = len;    /* 未累加的数据长度 */   
    unsigned 
short *p;  /* 走动的临时指针,2字节为单位 */   
    unsigned 
short tmp = 0/* 奇数字节长度时用到 */   
   
while( nleft > 1)   
   {       
       sum 
+= *p++;    /* 累加 */       
       nleft 
-= 2;   
    }   
   
if(nleft == 1)      /* 奇数字节长度 */   
   {       
       
*(unsigned char *)&tmp = *(&(unsigned char *)p); /* 将最后字节压如2字节的高位 */       
       sum 
+= tmp;   
   }  
    sum 
+= (sum >> 16+ (sum & 0xffff);    /* 高位低位相加 */   
    sum 
+= sum >> 16;                       /* 上一步溢出时,将溢出位也加到sum中 */   
    tmp 
= ~sum;     /* 注意类型转换,现在的校验和为16位 */    
    
return tmp;
}
原创粉丝点击