继上一篇IPv4之接收过程中的分片重组(一),这篇笔记从整体上看linux是如何进行IP片段重组的。
在接收路径上的ip_local_deliver()函数中,此时已经确认数据包是给本机的,会首先调用ip_defrag()判断是否是一个完整的IP报文,即是否需要进行重组,如果一切ok,那么继续过防火墙的LOCAL_IN点,继续数据包的接收流程,这里重点看报文重组逻辑:
int ip_local_deliver(struct sk_buff *skb)
{
// 第一个if条件成立,说明收到的skb是一个IP片段;
// ip_defrag()返回非0表示此数据包不完整,需要等更多的数据包才能完成重组,所以现在还不能递交,接收流程结束
if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
return 0;
}
return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);
}
显然,IP报文重组的核心在ip_defrag()中。
IP片段重组: ip_defrag()
/* Process an incoming IP datagram fragment. */
int ip_defrag(struct sk_buff *skb, u32 user)
{
struct ipq *qp;
struct net *net;
IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);
net = skb->dev ? skb->dev->nd_net : skb->dst->dev->nd_net;
// 后面很可能需要分配内存来保存新来的分片,所以先检查分片占用内存是否已经超过了最大上限,
// 如果超过了则调用ip_evictor()进行内存清理
if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh)
ip_evictor(net);
// 查询该IP片段所属IP分片队列(每一个IP数据报有一个该队列),如果没有则新建一个
if ((qp = ip_find(net, ip_hdr(skb), user)) != NULL) {
int ret;
spin_lock(&qp->q.lock);
// 尝试进行分片重组,如果能够重组出一个完整的IP报文,则返回非0,这样数据包就会传递给L4协议
ret = ip_frag_queue(qp, skb);
spin_unlock(&qp->q.lock);
ipq_put(qp);
return ret;
}
// 新建IP分片队列失败,则丢包
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
kfree_skb(skb);
return -ENOMEM;
}
可见,ip_defrag()将分片重组逻辑分割的非常清晰:
- ip_find()查找全局哈希表,找到(或者新建)该IP分片所属的IP分片队列struct ipq;
- 新收到了IP片段,所以调用ip_frag_queue()尝试对该IP分片队列中的片段进行重组。
查找IP分片队列:ip_find()
// ip_find()内部的一个临时传参用的结构
struct ip4_create_arg {
struct iphdr *iph;
u32 user;
};
/* Find the correct entry in the "incomplete datagrams" queue for
* this IP datagram, and create new one, if nothing is found.
*/
static inline struct ipq *ip_find(struct net *net, struct iphdr *iph, u32 user)
{
struct inet_frag_queue *q;
struct ip4_create_arg arg;
unsigned int hash;
arg.iph = iph;
arg.user = user;
// 根据ipid、源IP、目的IP、L4协议号以及初始化时生成的一个随机数共5个信息计算hash值
hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
// 查找哈希表,检查是否有该片段所属报文对应的inet_frag_queue队列,如果没有那么函数会新建
q = inet_frag_find(&net->ipv4.frags, &ip4_frags, &arg, hash);
if (q == NULL)
goto out_nomem;
return container_of(q, struct ipq, q);
out_nomem:
LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
return NULL;
}
inet_frag_find()
struct inet_frag_queue *inet_frag_find(struct netns_frags *nf, struct inet_frags *f, void *key, unsigned int hash)
{
struct inet_frag_queue *q;
struct hlist_node *n;
// 遍历冲突链,尝试寻找匹配的,如果找到,增加引用计数并返回
read_lock(&f->lock);
hlist_for_each_entry(q, n, &f->hash[hash], list) {
if (q->net == nf && f->match(q, key)) {
// 找到了对应的IP分片队列,说明该分片不是第一个分片
atomic_inc(&q->refcnt