标题看起来有点别扭,但的确如此,在我们前面的例子中,我们并没有在对端主机172.16.48.1的16000端口上创建一个UDP套接字(简单地讲就
是没有开启UDP服务器),所以,我们不能期望向这个目的地址发送一个UDP数据报后会收到一个相应的UDP回应。但是,这并不表示我们发出去的数据报石
沉大海,对端主机的TCP/IP协议栈在检查了16000的端口并没有相应的进程需要接收UDP数据后,会回应一个ICMP包,告诉发送端,目的地址不可
达,具体原因是目的端口不可达。下面是在网络中截获的这个ICMP包:
16进制表示的数据包内容 内容解析
00 0c 29 80 d8 70 以太网首部,目的MAC地址,即172.16.48.2
00 50 56 c0 00 08 以太网首部,源MAC地址,即172.16.48.1
08 00 以太网首部,帧类型
45 IP首部,4位版本(4), 4位长度(5)
c0 IP首部,服务类型(一般服务)
00 3f IP首部,总长度(63字节,不包括以太网首部)
66 54 IP首部,标识符
00 00 IP首部,3位标识加13位偏移(未分片)
40 IP首部,TTL(64)
01 IP首部,协议
5b 86 IP首部,校验和
ac 10 30 01 IP首部,源IP地址,172.16.48.1
ac 10 30 02 IP首部,目的IP地址,172.16.48.2
03 03 ICMP首部,8位类型(3),8位代码(3),端口不可达。
b5 41 ICMP首部,校验和
00 00 00 00 ICMP首部,必须置0
以下为产生错差的那个数据报(除以太网首部外的全部内容)。
45 00 00 23 40 f6 40 00 40 11 41 b0 ac 10 30 02
ac 10 30 01 80 00 3e 80 00 0f f7 fd 61 62 63 64
65 66 67
以太网首部总共14字节,分别为目的MAC地址,源MAC地址和帧类型,帧类型在my_inet域中,一般有两种取值,ETH_P_IP(0x8000)和ETH_P_ARP(0x8086),分别表示因特网协议包和地址解析协议包。
在IP首部中有一个8位的协议字段,由于TCP,UDP,ICMP,IGMP都要向IP传送数据,因此IP必须在生成的首部中加入某种标识,以表明数据属
于哪一层。为此,加入了这个协议域,1表示为ICMP,2表示为IGMP,6表示为TCP,17表示UDP。这里是一个ICMP报文不可达数据报,所以为
1。
当172.16.48.2收到这个ICMP数据报后,会作出怎么样的处理?
我们还是从myip_rcv开始跟踪整个执行流。myip_rcv对数据报的IP头进行一些正确性检查,然后交给了
myip_rcv_finish,myip_recv_finish进行输入路由的查询,并调用myip_local_deliver进行本地接
收,myip_local_deliver调用myip_local_deliver_finish,在该函数中,首先检查
myraw_v4_htable哈希表,看有没有要接收这个数据报的raw套接字,因为这是一个ICMP包,有可能是对一个raw套接字发出的数据包的回
应,显然没有。再根据协议类型(ICMP)查询myinet_protos数组,该数组中注册有当前所有支持的协议,然后调用icmp的处理函数
myicmp_rcv。
myicmp_rcv检查icmp头中的类型字段,发现是ICMP_DEST_UNREACH(目的不可达),将数据报交给myicmp_unreach
处理。myicmp_unreach检查代码字段,发现是ICMP_PORT_UNREACH(端口不可达),取出差错数据报中的ip首部,其协议字段值
是17(UDP)。同样先到myraw_v4_htable中查询是否有raw套接字要处理该差错报文,显然没有。然后再到myinet_protos数
组中查询协议相符的成员处理该差错报文,调用到myudp_err。
myudp_err函数会首先从myudp_hash表中找出发送时hash在里面的相应的socket(差错报文中的源端口的主机字节序跟socket
结构的成员hnum相等即可,简单地,可以理解为一个进程标识符,该差错报文曾经是那个进程发送的,现在要交还它处理)。然后查看这个ICMP报文的类
型,是ICMP_DEST_UNREACH,所以接下来,通过数组icmp_err_convert(把目的不可达ICMP报文的代码号转化为相应错误号
的一个数组),把ICMP_PORT_UNREACH代码号转化为错误号ECONNREFUSED。
为了把错误信息传达给进程(进程在调用recv来接收数据时,可以通过struct
socket的一些成员值看到)。我们还需要在套接字上存放一些信息。成员sk_error_queue是一个错误队列,存放的是一些特殊的
sk_buff结构,用于报告错误信息。我们创建这样的一个sk_buff,放入,同时,置成员sk_err=ECONNREFUSED。到此为止,我们
整个接收ICMP报文的流程全部结束。
当然,应用程序这个时候并不知道对端主机端口不可达,将调用recv系统调用进行数据接收。但内核做的第一件事情就是检查socket的sk_err成员,如果不为零,直接将错误号返回。应用程序马上得到错误返回:连接被拒绝。
阅读(5985) | 评论(0) | 转发(1) |