2013年8月28日星期三

IP数据包的输入与输出

IP层主要函数之间的调用关系如下图所示:

上面的图主要是拷贝的《Linux内核源码剖析----TCP/IP实现上册》中的图11.3,原图中有部分错误,所以这里重新绘制了一下,并且去掉了一些冗余的部分。
下面简述一下数据包传递的大致过程:
一、IP数据包的输入
ip_rcv()是网络层(IPv4,以下同)接收数据包的入口函数,链路层在接收到数据包后调用netif_receive_skb()将数据包传递到网络层。网络层的packet_type实例为ip_packet_type,在Internet协议族的初始化函数inet_init()中调用dev_add_pack()来注册到ptype_base散列表中。
ip_rcv()中接收到数据包后会检查是否是一个完整的、没有错误的数据包。如果是合法的数据包,会传递到netfilter的NF_INET_PRE_ROUTING钩子点进行处理,如果钩子处理函数中没有截获数据包,则传递到ip_rcv_finish()进行下一阶段的处理。
ip_rcv_finish()会检查是否已设置路由缓存项,如果没有,则调用ip_route_input()来查找路由,如果查找失败,则丢弃数据包。如果找到路由项,在路由缓存项描述结构dst_entry的input和outpu接口中设置下一个阶段的处理函数。
如果是要转发的数据包,input接口设置的是ip_forward()函数,output接口设置的是ip_output()。ip_forward()中会检查数据包的IP选项,并做相应处理。如果没有问题,将数据包的TTl值减1,然后将数据包传递到NF_INET_FORWARD钩子点进行处理。如果钩子处理函数没有截获数据包,则调用路由缓存项的output接口输出数据包,即ip_output()函数。
如果是要交给本地的数据包,input接口设置的ip_local_deliver()函数,output接口设置的ip_rt_bug()。ip_local_deliver()中会检查接收到的数据包是否是IP报文的分片,如果是,则调用ip_defrag()组装分片的各个部分。如果分片没有到齐或出错,则直接返回,当前报文不再向传输层传递。如果不是IP报文的分片或IP报文的所有分片组装成功,则将报文传递到netfilter的NF_INET_LOCAL_IN钩子点进行处理。如果钩子处理函数没有截获报文,则调用ip_local_deliver_finish()将报文传递到传输层。在传递到传输层之前,ip_local_deliver_finish()中会将sk_buff中的成员指向传输层报文的位置。
二、IP数据包的输出
传输层向IP层输出数据包主要是调用ip_queue_xmit()和ip_push_pending_frames()。TCP协议主要使用ip_queue_xmit()来发送数据,UDP协议主要使用ip_push_pending_frames(),不过这两者都是由本地发送的数据包,需要转发的数据包也需要IP层来处理。本地发送的数据包需要传递到netfilter的NF_INET_LOCAL_OUT钩子点处理,转发的数据包则不需要。如果没有钩子处理函数截获数据包,则继续进行处理。
如果是组播数据包,则output接口设置的是ip_mc_output()接口;如果是单播数据包,则output接口设置的ip_output。这里只讨论单播数据包。ip_output()中只是简单地将路由缓存项中存储的网络设备设置到数据包的dev成员中,并且设置三层的协议类型(protocol成员),然后将数据包传递到netfilter的NF_INET_POST_ROUTING钩子点。如果没有钩子处理函数截获数据包,则将数据包传递到ip_finish_output()中处理。
ip_finish_output()中会检查报文的长度是否大于MTU。如果大于MTU,则调用ip_fragment()对数据包进行分片,然后再调用ip_finish_output2()将数据包通过邻居子系统传递到网络设备。如果不需要分片,则直接调用ip_finish_output2()处理。

没有评论:

发表评论