IPv4之分片重组(二)

继上一篇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()将分片重组逻辑分割的非常清晰:

  1. ip_find()查找全局哈希表,找到(或者新建)该IP分片所属的IP分片队列struct ipq;
  2. 新收到了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
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值