深刻理解iputils网络工具-第2篇 ping:通路检测程序

2.1       引言

   “ping”这个名字源于声纳定位操做。Ping程序由Mike Muuss编写,目的是为了测试另外一台主机是否可达。该程序发送一份ICMP回显请求报文给主机,并等待返回ICMP回显应答。linux

2.2       ping程序的使用

    敲入命令:算法

lixi@lixi-desktop:~$ ping -V
ping utility, iputils-sss20071127

    说明本机中安装的ping程序和本文研究的ping程序同样,是最新版本。数组

lixi@lixi-desktop:~$ ping -T tsonly www.ustc.edu.cn -c 1
PING www.ustc.edu.cn (202.38.64.9) 56(124) bytes of data.
64 bytes from 202.38.64.9: icmp_seq=1 ttl=62 time=0.795 ms
TS: 	6123570 absolute
	493
	364
	-857378
	0
	857378
	-363
	-493


--- www.ustc.edu.cn ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.795/0.795/0.795/0.000 ms

    6123570是时间戳的绝对值,而输出的其余时间戳是相对上一个时间戳的差异。在北京时间9:42作的测试,北京时区为UTC+8,故此有9+42/60-8=1.7网络

而6123570/60/60/1000=1.7。故此出现这个结果是很是有道理的。并发

lixi@lixi-desktop:~$ ping www.ustc.edu.cn -R -c 1
PING www.ustc.edu.cn (202.38.64.9) 56(124) bytes of data.
64 bytes from 202.38.64.9: icmp_seq=1 ttl=62 time=0.852 ms
RR: 	lixi-desktop.local (210.45.74.25)
	202.38.96.36
	local-gw.ustc.edu.cn (202.38.64.126)
	202.38.64.9
	202.38.64.9
	202.38.96.33
	210.45.74.1
	lixi-desktop.local (210.45.74.25)

    对照上面的路由信息,咱们就能够分析出为何时间戳信息里会有对称的现象,而对称轴的值是0了。socket

   产生对称性的另外一个条件是RTT很小,这里只有0.8ms。ide

    咱们能够还能够分析出202.38.64.9的系统时间和其余路由的系统时间相差很大,大约有-14分钟。函数

lixi@lixi-desktop:~$ ping -T tsandaddr www.ustc.edu.cn -c 1
PING www.ustc.edu.cn (202.38.64.9) 56(124) bytes of data.
64 bytes from 202.38.64.9: icmp_seq=1 ttl=62 time=1.66 ms
TS: 	lixi-desktop.local (210.45.74.25)	7375300 absolute
	210.45.74.1	828
	local-gw.ustc.edu.cn (202.38.64.126)	26
	202.38.64.9	-857405
Unrecorded hops: 3


--- www.ustc.edu.cn ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.664/1.664/1.664/0.000 ms
   上面是同时记录路由和时间戳信息。不过这里因为IP选项长度的限制,只能存储4个路由和它对应的时间戳。

lixi@lixi-desktop:~$ ping -T tsprespec 202.38.64.9 202.38.96.33 210.45.74.1 www.ustc.edu.cn -c 1
PING www.ustc.edu.cn (202.38.64.9) 56(124) bytes of data.
64 bytes from 202.38.64.9: icmp_seq=1 ttl=62 time=0.893 ms
TS: 	202.38.64.9	6741320 absolute
	202.38.96.33	0
	210.45.74.1	857353
Unrecorded hops: 1


--- www.ustc.edu.cn ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.893/0.893/0.893/0.000 ms

    若是咱们必定要获得之后的几个路由和时间戳,咱们能够采用上述的办法。oop

    这里在北京时间10:07分的时候进行的测试,202.38.64.9的时间戳是6741320,而咱们上面的分析,202.38.64.9的系统时间大约比北京时间晚14分钟。10-8-14/60=1.87。6741320/60/60/1000=1.87。故此出现这个时间戳也是颇有道理的。测试

    ping程序的选项解释以下:

    -a   

        可听见的ping。

        所谓可听见,不过是在ping.c文件的parse_reply的函数中,输出ASCII码'/a',beep一下。

   -A

        自适应的ping。调整报文间隔时间,使其适应于RTT,这样很是有效率地使得网络中传输的不超过一个(若是-l参数设置了就为多个)。对于非超级用户,最小的时间间隔为200毫秒,在一个RTT比较得下的网络中,这个模式和-f的洪泛模式基本相同。

        为了调整报文时间间隔,使用update_interval()函数来调整时间间隔。

    -b

        容许ping广播地址。

        设置broadcast_pings为1,当判断到这个选项设为1以后,且地址是广播地址,那么就设置setsockopt(icmp_sock, SOL_SOCKET, SO_BROADCAST,&broadcast_pings, sizeof(broadcast_pings))。

    -B

        不容许ping改变报文的源主机地址,该地址在ping开始运行的时候就已经指定了。

        为了指定源主机地址,ping.c使用函数bind()来把套接字和本地套接字地址绑定,即在已建立的套接字上加上本地套接字地址。

    -c <count>

        在发送<count>个ECHO_REQUEST报文。当和-w <deadline>一块儿设置时,ping等待收到<count>个ECHO_REPLY报文,直到超出时间限制为止。

        设置npackets选项为<count>就能够了。在没有设置deadline的状况下,当nreceived + nerrors >=npackets时就能够退出循环,完成ping的任务了。

    -d

        设置socket中的SO_DEBUG选项,使能调试跟踪。实质上Linux内核中没有使用这个套接字选项。

        设置方法:setsockopt(icmp_sock,SOL_SOCKET, SO_DEBUG, (char *)&hold, sizeof(hold));

    -F <flow> <label>

       这个选项只有ping6才有。

    -f

      洪泛模式。对每个ECHO_REQUEST报文的发送,打印一个“.”,当接受到ECHO_REPLY报文时,打印一个backspace字符。这样可以快速地代表网络丢失了多少个报文。若是interval没有设置,则设置interval为0,并按照报文接受的速度和一百次每秒的速度来发送报文(看哪一个速度快)。只有超级用户可以和-i 0选项一块儿使用这个选项。

    -i <interval>

       在发送每一个报文之间等待<interval>秒。默认设置是等待一秒,在洪泛模式下则不等待。只有超级用户才能将<interval>设置为小于0.2秒的数。

       interval为<interval>*1000,程序的实现决定了<interval>输入整型数和浮点数都能被正确接受。

    -I <interface/address>

        设置发送的地址或者网络设备。

        程序首先尝试用intinet_pton(int af, const char *src, void *dst)函数由将src表明的字符串转化为dst中的IP地址。若是不能正确转换,则意味着这个选项不是地址,例如210.45.74.25,而是设备名如eth0。若是是后者,则设置device为<interface>,并用bind(icmp_sock, (structsockaddr*)&source, sizeof(source))      setsockopt(probe_fd,SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device)+1)来将套接字和本地套接字地址进行绑定。

    -l <preload>

        <preload>是在没有接受到回复报文以前能发送的最多报文。非超级用户最多只能设置为3。

        尽量快地发送预载的报文,而后再返回到正常发送模式。

        将<preload>值赋到preload变量中。若是不赋值preload默认为1。

    -L

        禁止多播数据包的回环,只有在ping的目的主机是广播地址时才管用。

    -n

        只有数字形式ip地址值的输出,不经过查询DNS获知IP地址对应的主机名,以节省时间。

        设置F_NUMERIC,不用调用gethostbyaddr来查询DNS主机名了。

        用gethostbyaddr的由查询目的主机的IP地址。

    -p <pattern>

        容许为传输的回显报文中包含的内容指定字节模式。这对于诊断与传输数据有关的网络问题可能颇有用。数据采用16进制,例如“-p ff”可将传输的报文填充为全1。

    -Q <tos>

        用来设置服务质量(Quality of Service )

        例如最小开销、 可靠性、吞吐量、低延迟。

        IP协议有一个8bit的DS区分服务(之前叫服务类型)。前三位是优先(precedence)字段(在目前,优先字段并未被你们使用),接着4bit是TOS位,最后1bit没有使用,但必须置0。

        4比特TOS位的意义分别为D(最小时延)、T(最大吞吐量)、R(最高可靠性)、C(最小代价)。要设置TOS位为对应意义,能够设置-Q <tos>分别为0x10,0x08,0x04,0x02。TOS的各个位不能同时置1。

    -q

        静默模式。这种模式下,出了开始的提示和结束的数据统计,不会输出任何东西。

    -R

        记录路由信息。在发送的IP报文首部选项中放入记录路由选项,在接到到报文回复以后,打印出回复报文的路由信息。

        注意:IP报文的选项中最多只能计算9个路由信息,计算方式以下:

        首部长度HLEN。这4bit字段用来定义首部的长度,以4字节为单位。因为首部长度可变,默认长度是20字节,此时4bit字段值为5。4bit的字段最大能够表示的数为15,故此首部长度最大为15*4byte,即60byte。首部的可变字节数为60-20=40byte,RR选项用去3byte(参见记录路由选项的通常格式),只剩下37byte,最多只能放下9个IP地址。

        注意:不少的主机会略过IP报文的路由选项,所以有可能在回复报文中没有路由信息。

        注意:不能和-T选项一块儿使用。

    -r

        绕过通常的路由表而直接向一个链接着的主机发送报文。若是主机不是经过直接链接的网络相连,则会出现错误。这个选项能够用来ping一个没有经过路由相连而是经过一个接口相连(假设也使用了-I选项)的本地主机。

        使用setsockopt函数设置套接字的SOL_SOCKET级别的SO_DONTROUTE选项便可。

    -s <packetsize>

        设置ICMP报文的数据部分的长度。默认值是56,和ICMP首部的8字节一块儿做为IP报文的数据部分。

        设置datalen变量就能够了,datalen默认为DEFDATALEN(值是56)。长度为datalen的数据和8字节的首部一块儿做为ICMP报文。

    -S <sndbuf>

        设置套接字的发送缓冲区大小。若是没有设置,则被设定为不超过一个报文长度的长度。

    -t <ttl>

        设置TTL(time to live)。

        使用setsockopt函数设置套接字的IPPROTO_IP级别的IP_MULTICAST_TTL和IP_TTL选项便可。

    -T <timestamp> <option>

        设置IP时间戳选项。时间戳选项能够是如下三种:

        -T tsonly 只记录时间戳。

        -T tsandaddr 收集时间戳和IP地址。

        -T tsprespec [host1 [host2 [host3[host4]]]] 收集来自预约的网络段的时间戳和地址,发送端对选项列表进行初始化,存放了4个IP地址和四个取值为0的时间戳。只有在列表中的下一个地址和当前路由地址相匹配时,才记录它的时间戳。

        与-R选项的分析相似,首部的可变字节数为60-20=40byte,选项用去4byte(参见时间戳选项的通常格式),只剩下36byte,最多只能放下9个时间戳。

        注意:因为IP首部的空间限制,程序限制-R选项与-T不能同时使用。

    -M <hint>

        设定Path MTU查找选下项,可设置成下列三种:

        -M do 不容许分段,甚至不容许在本地分段。

        -M want 找出PMTU,在若是包太大就在本地分段。

        -M dont 不要设置IP首部中的DF位,即容许分段。

        使用setsockopt函数设置套接字的SOL_IP级别的IP_MTU_DISCOVER选项便可。

    -U

    -v

       冗余输出。输出不少具体信息。

    -V

        打印ping的版本,而后退出。

    -w <deadline>

        设定时间期限为<deadline>秒,无论已经发送和接到了多少包,只要达到时间期限就结束ping的过程。

    -W <timeout>

        等待回复的时间,单位是秒。这个选项只在没有接到任何的回复的状况下有效,只要接到了一个回复,就将等待时间设置为两倍的RTT。若是没有设置,则等待时间设置为一个最大值。

2.3       ping程序的流程图

    ping程序的流程图以下所示:


2.4       IP报文结构

    IP报文结构以下所示:


    IP数据报文的首部中有选项部分,这个部分能够用来存储IP时间戳或者IP记录路由选项。

    存储IP时间戳,以下图所示:

 

    IP记录路由选项,以下图所示:


2.5       ICMP报文结构

    ICMP的封装方式以下图所示:


    ICMP报文的结构以下图所示:


2.6       ICMP回显请求和回显应答报文格式

    ICMP回显请求和回显应答报文格式以下所示:


2.7       ICMP报文类型列表

    不一样种类的ICMP报文的首部有所不一样。以下:

类型

代码

描述

0

ICMP_ECHOREPLY

0

 

回显应答

3

ICMP_DEST_UNREACH

 

目的不可达

0

ICMP_NET_UNREACH

网络不可达

1

ICMP_HOST_UNREACH

主机不可达

2

ICMP_PROT_UNREACH

端口不可达

3

ICMP_PORT_UNREACH

协议不可达

4

ICMP_FRAG_NEEDED

须要进行分片单设置了不分片比特

5

ICMP_SR_FAILED

源站选路失败

6

ICMP_NET_UNKNOWN

目的网络不认识

7

ICMP_HOST_UNKNOWN

目的主机不认识

8

ICMP_HOST_ISOLATED

源主机被隔离(做废不用)

9

ICMP_NET_ANO

目的网络被强制禁止

10

ICMP_HOST_ANO

目的主机被强制禁止

11

ICMP_NET_UNR_TOS

因为服务类型TOS,网络不可达

12

ICMP_HOST_UNR_TOS

因为服务类型TOS,主机不可达

13

ICMP_PKT_FILTERED

因为过滤,通讯被强制禁止

14

ICMP_PREC_VIOLATION

主机越权

15

ICMP_PREC_CUTOFF

优先权终止生效

ICMP_SOURCE_QUENCH

4

0

源端被关闭

ICMP_REDIRECT

5

 

重定向

0

ICMP_REDIR_NET

对网络重定向

1

ICMP_REDIR_HOST

对主机重定向

2

ICMP_REDIR_NETTOS

对服务类型和网络重定向

3

ICMP_REDIR_HOSTTOS

对服务类型和主机重定向

ICMP_ECHO

8

0

请求回显

9

0

路由器通告

10

0

路由器请求

ICMP_TIME_EXCEEDED

11

 

超时

0

ICMP_EXC_TTL

传输请见生存时间为0

1

ICMP_EXC_FRAGTIME

在数据包组装期间生存时间为0

ICMP_PARAMETERPROB

12  

 

参数问题

0

坏的IP首部

1

缺乏必需的选项

ICMP_TIMESTAMP      

13

0

时间戳请求

ICMP_TIMESTAMPREPLY

14

0

时间戳应答

ICMP_INFO_REQUEST

15

0

信息请求

ICMP_INFO_REPLY

16

0

信息应答

ICMP_ADDRESS

17

0

地址掩码请求

ICMP_ADDRESSREPLY

18

0

地址掩码应答

    pr_icmph()函数中分析ICMP报文类型,并针对错误报文打印出出错问题。惨照上表就能比较好地分析各类问题出现的大体缘由了。

    另外在rdisc.c文件中使用了ICMP的路由器通告报文(类型为9)和ICMP路由器请求报文(类型为10)。

    各类ICMP类型和代码的常量定义在linux-2.6.27/include/linux/icmp.h文件中。

2.8       socket选项

    程序中使用setsockopt()函数设定了套接字的选项。用到的选项以下:

level(级别)

optname(选项名)

说明

标志

SOL_SOCKET

SO_BROADCAST

容许或禁止发送广播数据

Ö

SO_ATTACH_FILTER

安装过滤器。

 

SO_SNDBUF

设置发送缓冲区的大小。

 

SO_RCVBUF

设置接收缓冲区的大小。

 

SO_DEBUG

打开或关闭调试信息

Ö

SO_DONTROUTE

打开或关闭路由查找功能。

Ö

SO_TIMESTAMP

打开或关闭数据报中的时间戳接收。

Ö

SO_SNDTIMEO

设置发送超时时间。

 

SO_RCVTIMEO

设置接收超时时间。

 

SO_BINDTODEVICE

将套接字绑定到一个特定的设备上。

 

SOL_RAW

ICMP_FILTER

设置套接字ICMP过滤选项。

 

IPPROTO_IP

IP_OPTIONS

设置发出的数据报中的IP选项

 

IP_MULTICAST_LOOP

多播API,禁止组播数据回送

Ö

IP_MULTICAST_TTL

多播API,设置输出组播数据的TTL值

 

IP_TOS

设置发出的数据报中的IP TOS

 

SOL_IP

IP_MTU_DISCOVER

为套接字设置Path MTU Discovery setting(路径MTU发现设置)

Ö

IP_RECVERR

容许传递扩展的可靠的错误信息

Ö

    程序首先取得了一个UDP的套接字probe_fd,并根据用户的输入配置套接字的选项。probe_fd用到的选项主要有:SO_BINDTODEVICE、SO_BINDTODEVICE、SO_BROADCAST、IP_TOS等。

    ICMP报文的套接字icmp_sock用到的选项除了SO_BINDTODEVICE选项之外,列表中的全部选项都用到了。

2.9       ping.c程序的全局变量的分析

    static int ts_type;

        timestamp的类型

        在-T选项中设置,能够设置为IPOPT_TS_TSONLY、IPOPT_TS_TSANDADDR或者IPOPT_TS_PRESPEC。

    static int nroute = 0;

         主机输入的总数,最多为9个,由于IP首部选项中最多能存储9个地址

    static __u32 route[10];     

        在输入多个主机时,存储地址。

        可能输入多个主机的状况是:-Ttsprespec [host1 [host2 [host3 [host4]]]] 选项,或者ping hostName1 hostName2 ... hostNameN;前者是想得到肯定几个路由对应的时间戳,然后者为何这么设置,我还不大明白  。

    struct sockaddr_in whereto;

        存储了目的主机的信息。

    int optlen = 0;

        ip选项的长度。

        由IP的协议可知,最大为40,在须要在IP首部选项字段中存储数据时(例如-T、-R选项)就设置为最大值。

    int settos = 0;

        服务质量的设置。

        能够用-Q选项用来设置服务质量,例如最小开销、 可靠性、吞吐量、低延迟。

        IP协议有一个8bit的DS区分服务(之前叫服务类型)。前三位是优先(precedence)字段(在目前,优先字段并未被你们使用),接着4bit是TOS位,最后1bit好像没有使用。

        4比特TOS位的意义分别为D(最小时延)、T(最大吞吐量)、R(最高可靠性)、C(最小代价)。

        要设置TOS位为对应意义,能够设置-Q <tos>中的 <tos>分别为0x10,0x08,0x04,0x02     。

    int icmp_sock;

        ICMP的soket文件描述符。

    u_char outpack[0x10000];

        用来存储ICMP报文首部和数据的数组,为ICMP报文分配的存储空间。

    int maxpacket = sizeof(outpack);

        用来存储ICMP报文首部和数据的数组的最大大小。

    static int broadcast_pings = 0;

        标识用户是否是想ping广播地址。

        能够经过-b选项设置。

        若是不设置,则默认为0。

    struct sockaddr_in source;

        存储了源主机的信息。

        若是-I选项后面带的是源主机地址而不是设备名的话,就将主机的信息存储在source中。在socket试探的链接成功后,程序还用getsockname从新肯定了source的值。

    char *device;

        若是-I选项后面带的是设备名而不是源主机地址的话,如eth0,就用device指向该设备名。

        该device指向一个设备名以后,会设置socket的对应设备为该设备。

    int pmtudisc = -1;

      

2.10   ping_common.c程序的全局变量的分析

    int options;

        存储各类选项的FLAG设置状况。

        在判断输入选项时设置各个bit位。

    int sndbuf;

        发送缓冲区大小。

        能够在-S <sndbuf>中设置,若是没有设置,则估计一个大小。

    int ttl;

        报文ttl的值。

        能够在-t选项中设置。

        在设置soket选项时设置IP广播报文TTL和IP报文的TTL都为ttl值。

    int rtt;

        用指数加权移动平均算法估计出来的RTT值。

        初始值是0。

        gather_statistics()函数中根据上次的RTT值和原来的rtt值加权获得新rtt的值。

        在update_interva()函数中用来计算新的interval的值。

    int rtt_addend;

        配合rtt使用。

        用来计算新的interval的值,彷佛是更具上个rtt的值给interval留部分余量。

    __u16 acked;

        接到ACK的报文的16bit序列号。

        在gather_statistics()函数里更新,实际的更新方法似的acked不超过0x7FFF,否则就会发生回绕。

    int mx_dup_ck = MAX_DUP_CHK;

        ?

    long npackets;

        须要传输的最多报文数。

        能够在-c 选项里设置。

        若是没有设置则默认是0,故此每次在查询此值时就判断是否为0,0彷佛做为无穷大来考虑。

    long nreceived;

        获得回复的报文数。

        初始值是0。

        在gather_statistics函数中递加,进行统计。在程序执行finsh时,使用这个变量,打印出来做为参考。

    long nrepeats;

        重复的报文数。

        初始值是0。

        在gather_statistics函数中递加,进行统计。在程序执行finsh时,使用这个变量,打印出来做为参考。

    long ntransmitted;

        发送的报文的最大序列号。

        初始值是0。

       在pinger函数中递加,进行统计。在程序执行finsh时,使用这个变量,打印出来做为参考。

    long nchecksum;

        checksum错误的恢复报文。

        初始值是0。

        在gather_statistics函数中,若csfailed为1的时候,则递加,进行统计。在程序执行finsh时,使用这个变量,打印出来做为参考。

        不过彷佛checksum是不会被改变的,由于gather_statistics的选项csfailed在惟一的一次调用中(parse_reply()函数中)为0。

    long nerrors;

        icmp错误数。

        初始值是0。

        在程序接受到出错的报文以后,就会调用receive_error_msg。在这个函数里若是判断确实是一个错误,错误有多是本地出错,有多是网络出错,无论是哪一个出错,都将这nerrors递加。parse_reply也会改变这个变量。在程序执行finsh时,使用这个变量,打印出来做为参考。

    int interval = 1000;           

        发送两个相邻报文之间相距的时间,单位为毫秒。

        能够在-i选项中设置。

        在设置-f的洪泛模式下,会设置interval为0。

        若是没有设置,则默认是1000。

    int preload;

        在接受到第一个回复报文以前所发送的报文数。

        能够经过-l <preload>选项设置。

        若是没有设置,默认值是1。

    int deadline = 0;

        在deadline秒以后,程序退出。

        能够由-w选项设置。若是设置了,则在setup函数中设置闹钟,当程序执行到deadline秒时产生SIGALRM中断,退出程序。

若是没有设置则默认值是0,程序运行没有时间限制。

    int lingertime = MAXWAIT*1000;

        等待回复的最长时间,单位为毫秒。

        能够经过-W选项设置。这个值在完成一次正确发收过程后就由2*tmax代替,而失去做用了。

        默认值是MAXWAIT*1000即10000,MAXWAIT定义在ping_common.h中。

    struct timeval start_time;

        程序运行开始时的主机时间。

        在setup函数中使用gettimeofday初始化,在finish函数中和cur_time一块儿用来计算程序运行的时间。

    struct timeval cur_time;

        程序运行时当前的主机时间。

    volatile int exiting;

        程序是否是应该退出。

        初始值是0,就是不该该退出。

        在中断处理程序sigexit中会将这个值设为1。这个中断处理程序只在产生SIGALRM和SIGINT中断时(能够用Ctrl+c产生)才会执行。中断处理程序在setup函数中安装。

    volatile int status_snapshot;

        程序是否是应该调用status()函数打印出程序的运行状态。

        初始值是0。

        在中断处理程序sigstatus中会将这个值设为1。这个中断处理程序只在产生SIGQUIT中断时(能够用Ctrl+\产生)才会执行。中断处理程序在setup函数中安装。

    int confirm = 0;

        代表sendmsg函数的选项的MSG_CONFIRM选项是否设置。

        若是设置MSG_CONFIRM,则会告诉链路层的传送有了进展:已经接受到对方的一个成功的答复。因为MSG_CONFIRM的这个意义,因此在发送第一个数据是MSG_CONFIRM选项不因该设置,即confirm初始值为0。在成功接受到一个回复以后,confirm则应该设置为MSG_CONFIRM了。只有在肯定取得一个回复时才将confirm由0改成MSG_CONFIRM,这就是为何confirm只有在gather_statistics()才会被改变的缘由。然而更麻烦的是MSG_CONFIRM选项只有在Linux 2.3及以上内核中才支持,因此就须要confirm_flag变量了。

    int confirm_flag = MSG_CONFIRM;

        用来修补老版本linux内核的问题。

        confirm_flag的初始值为MSG_CONFIRM。这样在gather_statistics()里confirm就更新为confirm_flag了。可是,若是因为设置MSG_CONFIRM而产生了发送错误(linux版本较老,不支持MSG_CONFIRM选项)。这样就会在下个循环里调用gather_statistics(),更新confirm变量,保证不会发送出错了。

    int working_recverr;

        ?

    int timing;

        是否可以在ping过程当中测算时间

        若是ICMP报文的数据长度足以存储timeval结构数据,则timing设置为1。若是timing设置为1,则在ICMP报文中插入发送的时间,这样在接受到ICMP回复时,就能够根据该数据计算RRT。不然就没法计算RRT,也就没法进行时间统计了。

        从根本上说timing的值由datalen变量的大小决定。

        能够尝试运行ping -s1 www.ustc.edu.cn -c 1,看看运行结果怎样。

        能够看到没有时间统计输出,由于-s选项设置的datalen值过小。

    long tmin = LONG_MAX;             /*minimum round trip time */

        最小RRT

        初始值为LONG_MAX,每次接受到回复报文以后,就在gather_statistics函数中本次RRT是否是比tin大,若是是,就更新tmin。在程序执行完成以后,将打印出这个信息做为参考。

    long tmax;                        

        最大RRT

        初始值为0,每次接受到回复报文以后,就在gather_statistics函数中本次RRT是否是比tmax大,若是是,就更新tmax。在程序执行完成以后,将打印出这个信息做为参考。

        此外tmax还做为每次发送报文后等待接受报文的时间长度的参考,见__schedule_exit函数。若是超出这个时间长度尚未完成一次发送和接受,则发生超时中断。

    long long tsum;                 /*sum of all times, for doing average */

        每次RRT之和。

        初始值为0,每次接受到回复报文以后,就在gather_statistics函数中加上本次RRT。

        用来计算平均RRT。

    long long tsum2;

        每次RRT的平方和。

        初始值为0,每次接受到回复报文以后,就在gather_statistics函数中加上本次RRT的平方。

        用来计算RRT的方差。

    int  pipesize =-1;

        初始值为-1。

    int datalen = DEFDATALEN;

        数据长度。

        初始值为DEFDATALEN,即56。

        能够经过-s选项设置     。

    char *hostname;

        目的主机名字。

        在开始的时候,由用户做为程序的选项输入。随后经过gethostbyname()函数由主机名获得主机,而后将主机名改成函数返回的官方主机名。

        在最后输出的目的主机名就是这个名字。

    int uid;

        用户ID。

        在main函数中经过getuid()取得。

        若是uid不是0,即用户不是超级用户,则在设置选项的时候有限制:

        -i<interval>,<interval>不得小于0.2;在ping广播地址时,<interval>不能设置为小于1的数。

        -M<hint>,在ping广播地址时,<hint>不能设置为IP_PMTUDISC_DO以外的IP_PMTUDISC_DONT或IP_PMTUDISC_WANT。

        -s<packetsize>, <packetsize>不能超过sizeof(outpack)-8。

        -v,不会输出比较敏感的冗长信息,例如parse_reply函数中可能输出的额外信息。

        -l<preload>,ping广播地址时,<preload>不能大于3。

        -f,必需要和-i选项配合使用,且<interval>不小于0.2。

    int ident;

        本进程的ID。

        在setup函数中经过getpid()取得。

        在ICMP的数据中添加进程ID,并经过判断接受到的ICMP回复的进程ID是否是正确来判断ICMP回复是否是本进程的回复。

    static int screen_width = INT_MAX;

       窗口的宽度大小,也就是控制台一行能打印多少字符。

       在setup函数中经过ioctl()取得。

2.11   重要函数的分析

    int main(int argc, char **argv);

        主函数。

        在这个函数里:取得用户输入的选项,并根据这些选项及其参数设置相应的标识和参数值。根据这些标识和参数值,首先链接(connect)一个探测的UDP报文,以探知目的地址的基本状况。而后设置ICMP报文的套接字选项,而后调用setup()函数来进一步设置与协议无关的套接字选项(与ping6公用)。在套接字设置好后,调用main_loop()函数完成探测。

        定义在ping.c文件中。

    void main_loop(int icmp_sock, __u8 *packet, intpacklen);

        完成报文发送、分析的主要函数。

        在这个函数里:一直调用pinger()函数发ICMP报文和调用recvmsg()函数接受报文。若是recvmsg()函数没有正确接受报文,调用receive_error_msg()函数处理接受到的ICMP差错报文。如此反复,直到用户要求终止或者报文发送次数达到要求,或者超出的程序的时间限制,程序才中止发送/接受;程序在中止发送/接受后,调用finish()函数打印出统计数据。

        在main()函数中调用到此函数。

        定义在ping_common.c文件中。在这个文件中的全部函数都可以被ping和ping6共同使用。

    void int pinger(void);

        构成并发送报文。

        在这个函数里:调用send_probe()尝试发送报文,并处理send_probe()没有成功发送时出现的错误。在处理某些种类的错误时,用到receive_error_msg()函数。

        在main_loop()函数中调用到此函数。

        定义在ping_common.c文件中。

    int send_probe()

        构建报文,并发送报文。

        在这个函数里:根据用户的参数设置,设置ICMP报文的类型、代码、序号、标识符,并往ICMP报文的选项数据部分添加发送时间,而后计算校验和。构建出这个ICMP报文后,调用sendmsg()函数发送ICMP报文。此函数不处理发送出错。

        在pinger()函数中调用到此函数。

        定义在ping.c文件中。

    int receive_error_msg()

        处理ICMP差错报文。

        在这个函数里:调用设置了MSG_ERRQUEUE标识的recvmsg()来接收错误队列中的ICMP错误报文。取得错误信息以后,分析出错的缘由是因为本地缘由仍是网络缘由,并进行处理(好比设置更严格的ICMP过滤)。

        在main_loop()函数和pinger()函数中调用到此函数。

        定义在ping.c文件中。

    void setup(int icmp_sock)

        设置与协议无关的选项。

        在这个函数里:根据用户设置,这些设置包括interval的设置,socket的是否打开调试信息(SO_DEBUG)、是否打开路由查找功能(SO_DONTROUTE)、是否打开数据报中的时间戳接收(SO_TIMESTAMP)、发送时间限制(SO_SNDTIMEO)、接受时间限制(SO_RCVTIMEO)等选项,往报文内填内容的设置,中断处理程序的设置,闹钟的设置等。

        在main()函数和pinger()函数中调用到此函数。

        定义在ping_common.c文件中。

2.12   时间间隔和报文预发机制的实现

    程序使用一个分配时间片的概念,来控制发送报文的时间间隔,并实如今没有接到回复报文以前就预先发送preload个请求报文。

    初始时分配interval*preload的时间片用来发送报文(程序中第一次发送设置时间片为interval*(preload-1),因为设置后没有减去第一次发送用去的interval时间片,因此至关于分配了interval*preload的时间片)。每次发送报文都要用掉interval毫秒的时间片。若是时间片不为负数的话,则一直持续发送报文。若是时间片为负数,则退出循环,开始处理接受到的回复报文。处理接受到的回复报文,会用去比较长的时间。

    从上次发送报文,到当前准备发送报文的时间被计时器记录(其实是经过记录上次发送报文的系统时间到当前系统时间之差来记录的),并做为新的时间片加入原时间片中,做为下次发送报文的时间片。为了确保没有接到回复而发送了的报文数目不会超过preload个,这个新的时间片若是超过interval*preload,则被改成interval*preload。若是新的时间片小于发送一个报文的时间interval,则仍然不发送报文,退出发送报文的循环,接受回复报文和处理可能出现的中断。

    经过上述方法,实现了两个功能:

     1. 能够在不等待回复的状况下,预先发送preload个报文。因为初始时分配的时间片为interval*preload,因此刚开始,程序就连续发送interval个请求报文;若是程序等了很长时间没有发送报文,则计时器的引入使得这一段时间也做为发送时间片的新的一部分,这样程序又能够连续发送几个报文。

    2. 能够控制报文发送的时间间隔为interval。从初始时开始,在连续发送preload个报文后,时间片被耗尽。只有在计时器中累加的时间片超过interval时才能再连续发送一个或几个报文(不超过preload个)。

    相关函数:

    int pinger(void);

    void main_loop(int icmp_sock, __u8 *packet, int packlen);

    相关选项:

    -l <preload>

    -i <interval>

2.13   回复等待计时的实现

    当用户使用-c <count>设置了须要传送/接受的报文数,且经过-w <deadline>设置了程序运行的时间,那么则程序只须要在发送<count>个报文,并等待接受报文,直到接受到<count>个回复或者程序运行时间超过限制为止。若是用户只使用-c<count>设置了须要传送/接受的报文数,没有设置程序运行的时间,那么鉴于有些请求报文丢失而永远不会接到报文,程序不能在发送了<count>个报文以后一直等待。程序一直等待一个可能不再会出现的事情是难以接受的,它应该作的是在发送<count>个请求报文后,等待一段时间,若是实在没有等到回复报文,就退出。

    上面说的等待时间怎么肯定呢?若是程序成功地收到了一个或者几个针对请求报文的回复,那么就将两倍的最大RTT做为等待的时间。若是程序没有接到任何的回复,RTT无从得知,就使用lingertime做为等待的最长时间。这个lingertime能够经过-W <timeout>选项由用户设置;若是用户没有设置则为一个常量(程序中,默认等待10秒)。不过值得主注意的是lingertime这个变量在程序成功地收到了回复以后,就没有任何做用了。

    最长等待时间由一个闹钟实现。如上所述,设定这个闹钟的条件有下面几个:

    1. 须要传送/接受的报文被设置了。

    2. 程序运行的时间没有被设置。

    3. 已经发送的报文数等于或大于须要传送/接受的报文数。

    闹钟的时间被设置为:

    1. 若是程序成功地收到了一个或者几个针对请求报文的回复,那么就将两倍的最大RTT做为等待的时间。

    2. 不然,设置为lingertime。

    当超出闹钟的时间以后,就会产生SIGALRM中断,使得程序退出。

    相关函数:

    void main_loop(int icmp_sock, __u8 *packet, int packlen);

    staticinline int schedule_exit(int next);

    schedule_exit(int next)

    相关选项:

    -c<count>

    -w<deadline>


本文章欢迎转载,请保留原始博客连接http://blog.csdn.net/fsdev/article