看过ip协议源码后,将分片与重组注意事项记录下来,以免忘记。
1。ip重组结构体组织形式
理解任何代码,首先需要理解其数据结构,以及凌驾于数据结构之上的操作。
整体而言,所有分片都存储在全局变量ip4_frags中:
#define INETFRAGS_HASHSZ 64
static struct inet_frags ip4_frags->hash
struct hlist_head hash[INETFRAGS_HASHSZ]
该全局变量还包含了一组函数指针,用于处理封包的分片,其中一个重要的函数指针是match,用于查找分片封包的队列时进行比较。
ip层收到的每一个ip分片都存储在由skb_buff的成员next链接起来的链表中,该链表嵌入在一个表示独立封包的队列结构体inet_frag_queue中,每一个这样的队列结构体表示唯一一个封包,多个封包通过该结构体成员struct hlist_node list链接成链表形式,inet_frag_queue又嵌入在struct ipq结构体中,该结构体又包含了ip分片相关的重要字段。
现在看struct ipq 、struct inet_frag_queue、与全局变量的成员hash数组之间的关系ip4_frags->hash:
hash是一个数组,大小默认是64,即包含64个元素,每个元素都是一个链表,每个链表的结点类型为inet_frag_queue,所以查找分片封包的队列时,计算hash值,得到队列链表头指针,即可挨着比较每个封包的队列,找到封包的队列后,再比较队列的关键字是否与分片的关键字相同。
需要注意inet_frag_queue是嵌入在ipq中,所以存储封包的结构体实际类型是ipq,程序里根据inet_frag_queue类型的q得到q所属ipq的地址即指针,ipq里存储了一个封包的队列(队列里是所有相关的分片),与查找时使用的ip头相关信息(源目的地址、协议等)。
手工白板画图
2。ip重组的时机
ip重组是指,当封包到达最终目的地址,则进行重组,即在3层ip层收到ip封包时,如果该封包的最后一个封包到达时,将该封包的所有分片按照分片的偏移量组成一个封包,将该封包传递到4层。
ip重组函数是ip_defrag,该函数的前后调用关系链如下:
ip_rcv-》NF_HOOK(netfilter pre-routing)-》ip_rcv_finish-》ip_route_input_noref(ip路由查找)-》dst_input-》分支如下:
分支1:
设置Router Alert选项走此路径:dst_input-》ip_forward-》ip_call_ra_chain-》ip_defrag(ip重组)-》raw_rcv
直接转发的封包走此路径 :dst_input-》ip_forward-》NF_HOOK(netfilter ip-forwarding)-》ip_forward_finish-》dst_output-》ip_output
分支2:dst_input-》ip_local_deliver-》{ ip_defrag,NF_HOOK } -》ip_local_deliver_finish-》{tcp_v4_rcv,udp_rcv,icmp_rcv,igmp_rcv}(4层协议处理函数)
到达本地主机的封包走此路径,如果是分片则执行ip_defrag,如果ip_defrag返回非0则表示封包的分片尚未完全到达,直接返回;如果不是分片或者封包所有分片均已到达并且重组成功,则继续执行 -》NF_HOOK(netfilter local-in)
经过以上了解了重组的位置,下边看看ip_defrag函数的重组过程:
3。ip重组函数解释
参数:skb ip封包片段,user 说明重组原因,因为什么要重组
/* Process an incoming IP datagram fragment. */
int ip_defrag(struct sk_buff *skb, u32 user)
{
struct ipq *qp;
struct net *net;
net = skb->dev ? dev_net(skb->dev) : dev_net(skb_dst(skb)->dev);
IP_INC_STATS_BH(net, IPSTATS_MIB_REASMREQDS);
/* Start by cleaning up the memory. */
if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh)
ip_evictor(net);
/* Lookup (or create) queue header */
// 根据ip头信息查找ip片段所属封包的ipq,ipq的inet_frag_queue存储了封包的所有分片
// 分片以链表形式链接在inet_frag_queue的sk_buff类型的链表fragments上
if ((qp = ip_find(net, ip_hdr(skb), user)) != NULL) {
int ret;
spin_lock(&qp->q.lock);
// 将ip片段放入hash队列,如果是做好一个片段,内部会重组,返回0
ret = ip_frag_queue(qp, skb);
spin_unlock(&qp->q.lock);
ipq_put(qp);
return ret;
}
IP_INC_ST