注册 登录
LUPA开源社区 返回首页

yangqingsx的个人空间 http://www.lupaworld.com/?415320 [收藏] [复制] [分享] [RSS]

我的博客

C语言实现linux下ping命令及其源码分析(一)

热度 4已有 4681 次阅读2011-4-23 12:06 |个人分类:linux之旅|系统分类:IT技术|

1、ping命令简介
         ping是Packet Internet Groper的缩写,ping命令可以说是一个网络测试工具,可以测试计算机名和计算机的ip地址,验证与远程计算机的链接。它是最常见的用于检测网络设备可访问性的方法,它使用Internet Control Message Protocol(ICMP)的echo信息可以来决定:1)远程设备是否可用;2)与远程主机通信的来回旅程(round-trip)的延迟(delay);3)包(packet)的丢失情况。它通过将icmp回显数据包发送到计算机并侦听回显数据包来验证与一台或多台远程计算机的连接,该命令是基于TCP/IP协议栈的。
           ICMP是为网关和目标主机提供的一种差错控制机制,使它们在发生差错时把错误信息报告给源发送放。ICMP协议是IP层的一个协议,但是由于差错报告发送可能会经过若干个子网,因此牵扯到路由选择问题,所以ICMP报文通常由IP协议来发送。于是ICMP数据报文发送前需要进行两次封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。由于IP协议是一种对点的协议,而不是端对端的协议,它提供无连接的数据包服务(通常使用UDP协议),所以不需要bind()和connect()函数来绑定和连接端口。用sendto()函数来发送数据,接收数据使用recvfrom()函数。
2、ping命令工作过程
        首先ping发送echo request packet到某个地址,然后等待应答(reply),当echo request到达目标地址之后,在一个有效的时间内(timeout之前)返回echo reply packet给源地址,这样即说明能够ping通。在windows系统中,默认的情况下,一般只发送四个数据包,通过这个命令可以自己定义发送的个数,对衡量网络速度很有帮助,比如我想测试50个数据包返回平均时间,最快时间以及最慢时间,则可以使用如下命令:
ping -n 50 202.117.132.55
而linux下要求一直不断的向远程主机发送ICMP数据包,知道用户数据CTRL+C为止。
1)IP报头格式
         由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect() 函数,若有使用也只是用于设置IP地址。发送数据使用sendto()函数,接收数据使用recvfrom()函数。IP报头格式如下图:
在linux中,IP报头格式数据结构(<netinet/ip>)定义如下:
struct ip
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ip_hl:4;       /* header length */
    unsigned int ip_v:4;        /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
    unsigned int ip_v:4;        /* version */
    unsigned int ip_hl:4;       /* header length */
#endif
    u_int8_t ip_tos;            /* type of service */
    u_short ip_len;         /* total length */
    u_short ip_id;          /* identification */
    u_short ip_off;         /* fragment offset field */
#define IP_RF 0x8000            /* reserved fragment flag */
#define IP_DF 0x4000            /* dont fragment flag */
#define IP_MF 0x2000            /* more fragments flag */
#define IP_OFFMASK 0x1fff       /* mask for fragmenting bits */
    u_int8_t ip_ttl;            /* time to live */
    u_int8_t ip_p;          /* protocol */
    u_short ip_sum;         /* checksum */
    struct in_addr ip_src, ip_dst;  /* source and dest address */
  };
其中ping程序使用到的字段有:
IP报头长度IHL:以4字节为单位来记录IP报头的长度,是上述IP数据结构的ip_hl变量。
生存时间TTL(Time To Live):以秒为单位,指出IP数据包能在网络上停留的最长时间,其值由发送放设定,并在经过路由的每一个节点时减1,当該值为0时,数据包将被丢弃,是上述IP数据结构的ip_ttl变量。
2)ICMP报头格式
         ICMP报文分为两种,一是错误报告,二是查询报文。每个ICMP报头均包含类型、编码和校验和三部分,长度为8位和16位,其余选项则随ICMP的功能不同而不同。ping命令只使用众多ICMP报文中的两种:请求回送(ICMP_ECHO)和请求回应(ICMP_ECHOREPLY)。在linux中定义如下:
#define ICMP_ECHO   0
#define ICMP_ECHOREPLY  8
这两种ICMP类型报头格式如下:
在Linux中ICMP数据结构(<netinet/ip_icmp.h>)定义如下:
struct icmp { u_int8_t icmp_type; /* type of message, see below */ u_int8_t icmp_code; /* type sub code */ u_int16_t icmp_cksum; /* ones complement checksum of struct */ union { u_char ih_pptr; /* ICMP_PARAMPROB */ struct in_addr ih_gwaddr; /* gateway address */ struct ih_idseq /* echo datagram */ { u_int16_t icd_id; u_int16_t icd_seq; } ih_idseq; u_int32_t ih_void; /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ struct ih_pmtu { u_int16_t ipm_void; u_int16_t ipm_nextmtu; } ih_pmtu; struct ih_rtradv { u_int8_t irt_num_addrs; 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; /* options and then 64 bits of data */ } 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 #define icmp_ttime icmp_dun.id_ts.its_ttime #define icmp_ip icmp_dun.id_ip.idi_ip #define icmp_radv icmp_dun.id_radv #define icmp_mask icmp_dun.id_mask #define icmp_data icmp_dun.id_data };
使用宏定义令表达更简介,其中ICMP报头为8字节,数据报长度最大为64字节。
校验和算法,又称为网际校验算法,把被校验的数据16位进行累加,然后取反码,若数据字节长度为奇数,则数据尾部补一个字节的0以凑成偶数,此算法适用于IPv4、ICMP4、IGMPV4、ICMP6、UDP和TCP校验和,更详细的信息请参考RFC1071,校验和字段为上述ICMP数据结构的icmp_cksum变量。校验和源码实现如下:
unsigned short cal_chksum(unsigned short *addr,int len)
/*unsigned short *addr: 校验数据开始地址(注意是以两个字节为单位)
  int len: 校验数据的长度大小,以字节为单位*/
{
    int nleft = len;    /*未累加的数据长度*/
    int sum = 0;        /*校验和*/
    unsigned short *w = addr;   /*走动时的指针,2字节为单位*/
    unsigned short answer = 0;  /*奇数字节长度时用到*/
    while (nleft > 1)
    {
        sum += *w++;    /*累加*/
        nleft -= 2;
    }
    if (nleft == 1)      /*奇数字节长度*/
    {
        sum += *(unsigned char *)w;        /*将最后字节压入2字节的高位*/
        printf("sum = %x\n", sum);
    }
    sum = (sum>>16) + (sum&0xffff);         /*高低位相加*/
    sum += (sum>>16);      /*上一步溢出时,将溢出位也加到sum中*/
    answer =~ sum;         /*注意类型转换,现在的校验和为16位*/
    return answer;
}

刚表态过的朋友 (0 人)

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 注册
验证问答 换一个 验证码 换一个

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部