您的位置:首页 > 理论基础 > 计算机网络

深入理解iputils网络工具-第4篇 tracepath:路由追踪程序

2012-08-19 21:19 405 查看

4.1 引言

tracepath和更为强大和更为广泛使用的程序traceroute一样,可以让我们看到IP数据报从一台主机传到另一台主机所经过的路由。

tracepath的作者是Alexey Kuznetsov。

4.2 tracepath程序的使用

lixi@lixi-desktop:~$ tracepath 210.45.74.25/8888
1:  lixi-desktop.local (210.45.74.25)                      0.123ms pmtu 16436
1:  lixi-desktop.local (210.45.74.25)                      0.054ms reached
1:  lixi-desktop.local (210.45.74.25)                      0.045ms reached
Resume: pmtu 16436 hops 1 back 64

210.45.74.25是本地主机的IP地址,8888是选择的测试端口。

可以发现在本机进行了三次测试,为什么有三次测试,在下面的内容中有分析。

lixi@lixi-desktop:~$ tracepath 210.45.74.25/8888
1:  lixi-desktop.local (210.45.74.25)                      0.122ms pmtu 16436
1?: reply received 8)
1:  lixi-desktop.local (210.45.74.25)                      0.048ms reached
Resume: pmtu 16436 hops 1 back 64

编写简单的UDP服务程序,对8888端口的UDP请求进行服务(程序见<./test/udpserv.c>)。在运行这个服务程序之后,得到的测试结果如上。

在第二轮时程序接受到了UDP的回复程序,所以输出了一个'?'表示疑问。

lixi@lixi-desktop:~$ tracepath 210.45.74.25/44444
1:  lixi-desktop.local (210.45.74.25)                      0.131ms pmtu 16436
1:  lixi-desktop.local (210.45.74.25)                      0.054ms reached
1:  lixi-desktop.local (210.45.74.25)                      0.046ms reached
Resume: pmtu 16436 hops 1 back 64

在运行对8888端口进行服务的UDP服务程序时,如果tracepath采用其他端口就不会产生上例中的情况了。

lixi@lixi-desktop:~$ tracepath www.ustc.edu.cn
1:  lixi-desktop.local (210.45.74.25)                      0.198ms pmtu 1500
1:  210.45.74.1 (210.45.74.1)                              0.777ms
1:  210.45.74.1 (210.45.74.1)                              0.775ms
2:  202.38.96.33 (202.38.96.33)                            1.068ms
3:  202.38.64.9 (202.38.64.9)                              1.012ms reached
Resume: pmtu 1500 hops 3 back 253

对比此例和上例,可以发现PMTU发生了变化,由16436变成了1500。

lixi@lixi-desktop:~$ tracepath www.ustc.edu.cn -l 1500
1:  210.45.74.1 (210.45.74.1)                              0.828ms
2:  202.38.96.33 (202.38.96.33)                            0.988ms
3:  202.38.64.9 (202.38.64.9)                              1.140ms reached
Resume: pmtu 1500 hops 3 back 253

我们将MTU手动设置为1500,程序就不会默认将MTU设置为一个很大的数,然后找出PMTU了。

tracepath程序的选项解释如下:

-n

与ping命令的-n选项差不多。

只有数字形式的输出,不查找DNS主机以节省时间,不查寻主机名,仅仅给出ip地址值。

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

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

-l

设置初始的包的大小。如果不设置则,则报文的大小为65535

4.3 tracepath程序的流程图

tracepath程序的流程图如下:



4.4 tracepath重要函数的分析

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

接受用户的选项,设置发送MTU或者设置是否不要验证主机名等标识。取得一个UDP类型的套接字,并设置好这个套接字的选项。,从1开始递增,直至31,设置套接字的IP_TTL选项为不同的值,调用probe_ttl()函数,直到probe_ttl()函数告知找到目的主机或者出现严重的错误为止。

int probe_ttl(int fd, int ttl);

循环执行十次如下操作,直到正确发送报文跳出循环,或者recverr()返回0:

调用sendto()函数尝试发送UDP报文到目的地址,如果发送出现错误则调用recverr()函数处理接受到的ICMP差错报文。如果正确发送报文,则跳出循环。

如果循环过程中:recverr()函数返回0,则本函数返回0;如果recverr()函数返回大于0的数,则重新进行如上循环。

如果循环超过十次,则表明因为某种原因,无法发送UDP报文,程序返回0。

如果正确发送了UDP报文,则尝试使用recv()函数接受UDP报文的回复。正常情况下不会有UDP的回复,如果果真接受到了回复,则打印‘?’号表示吃惊,返回0。如果正如所预见到的,没有接到回复,则调用recverr()函数处理可能接受到的ICMP差错报文,返回recverr()返回的数值。

总结本函数的返回值意义如下:

返回0表示找到主机或者有严重的错误。

返回负数-1,表示没有接受到ICMP差错报文回复。

返回正数是当前MTU,表示接受并处理了一些错误,但是还没有找到目的主机。

int recverr(int fd, int ttl);

函数将progress初始化为-1,然后不断循环执行如下操作,直到循环中函数返回:调用recvmsg()函数接受错误报文,并处理错误,如果没有错误返回progress。在错误队列中查找对应错误(SOL_IP级别IP_RECVERR类型),progress设置为MTU的值,并处理错误。如果错误队列的错误不是对应错误,返回0。

并处理错误的几种情况如下:

如果是MTU太大(EMSGSIZE),则修改MTU的变量值,继续循环。

如果是UDP端口不可达错误(ECONNREFUSED),则UDP报文已经在规定TTL内传送到了目的主机。这种情况返回0。

如果是EHOSTUNREACH错误且出错原因是接受到了ICMP差错报文,这个ICMP差错报文如果类型为11,代码为0,则表示因为在传输期间TTL等于0所以出错(参看ICMP报文类型)。这就说明UDP还没有到达目的主机TTL就变成了0,需要进一步递加TTL进行试探。

如果是其他的错误,对于不严重的错误继续循环,否则返回0。

总结本函数的返回值意义如下:

返回0表示找到主机或者有严重的错误。

返回的数如果大于0,其值是当前的MTU,表示接受并处理了一个或几个错误。

返回负数-1,表示没有发现任何错误,也就是没有进展(progress)。

4.5 tracepath全局变量的分析

struct hhistory his[64];

用来存放历史上发出的UDP报文的ttl设置值和发送时间。

当发送UDP报文时,将报文的端口号设置为base_port + hisptr,在his[hisptr]元素中,存储该UDP报文的ttl设置值和发送时间。

当接受到一个UDP的回复报文(“端口不可达”错误ICMP报文)时,通过ICMP报文的端口号,就可以知道该UDP报文对应的ttl设置值和发送时间存储在his数组的哪个元素里了。

his数组大小有限,只有64个元素。不过已经能够保证即使hisptr回绕也不会出错了。

int hisptr;

用来指向his数组的元素。

当发送UDP报文时,hisptr递加,将发出的UDP报文的ttl设置值和发送时间存储在his[hisptr]元素中。

由于his数组大小为64,故此hisptr每次加到63,下一次就会回绕到0。

struct sockaddr_in target;

要查询的目的主机的地址。

包括地址种类(IPv4)、IP地址、端口号等信息。

端口号会被设置为基础端口号加上hisptr。

__u16 base_port;

基础端口号

可以在设定目的主机时连带设定,否则程序默认是44444端口。

基础端口号加上hisptr就是UDP报文的发送端口号。

设置这么大的端口号,是为了使得目的主机的任何一个应用程序都不可能使用该端口,而产生一个“端口不可达”错误。

这个值很大,目的就是让UDP出现“端口不可达”错误。

const int overhead = 28;

在UDP数据部分之前的头部大小。

IP首部为20,UDP首部为8,故此总共为28字节。

这个数是个常量。

int mtu = 65535;

可以通过-l选项设置,如果设置的值不大于传输路径的MTU。

如果不设置默认值是65535,这个默认值肯定会超过传输路径的MTU,当超过了路径的MTU时,程序会受到错误消息,并根据这个错误消息所带的MTU值,更新MTU值。这样就tracepath能找出路径的MTU了。

int hops_to = -1;

从本地主机到目的主机的跳数。

如果目的主机不可达,则hops_to一直维持-1,最后就不会输出hops_to。

当程序接受到目的主机发出的“拒绝服务”ICMP错误报文时,就说明探测到了目的主机。hops_to取为此时的recverr()函数局部变量sndhops的值。

sndhops有两种方式取得,一种是取得发送时存储在his数组中的ttl值(前面已经谈到如何通过错误报文的IP端口得到存储地址),另一种是取得当前探测阶段发送的UDP报文的ttl的值。

如果不出意外,第一种方式能比较可靠地取得;但是由于某种原因,前一种方式出问题后,就用后一种方式作为代替。

int hops_from = -1;

从目的主机到本地主机的剩余TTL值。

如果目的主机不可达,则hops_from一直维持-1,最后就不会输出。

hops_from从目的主机发送给本地主机的IP报文头取得TTL字段值即可。

int no_resolve = 0;

标识是否不要验证主机名。

可以通过-n选项设置为1。

如果设置为0,就调用gethostbyaddr的由查询目的主机的IP地址,否则就不用,以节省时间。

本文章欢迎转载,请保留原始博客链接http://blog.csdn.net/fsdev/article
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: