linux内核协议栈 IPv4分片重组Ⅱ之 ip_defrag() 接口

本文深入解析Linux内核中IP分片的重组过程,重点探讨ip_defrag()函数的作用,包括ip_find()的哈希查找与新建IP分片队列,以及ip_frag_queue()如何重组IP报文。通过ip_frag_reasm()完成所有分片的重组工作。
摘要由CSDN通过智能技术生成

Table of Contents

1 IP片段接收入口 ip_local_deliver()

2 IP片段重组 ip_defrag()

2.1 查找IP分片队列 ip_find()

2.1.1 哈希查找分片队列 inet_frag_find()

2.1.2 新建IP分片队列 inet_frag_create()

2.2 重组IP报文 ip_frag_queue()

2.2.1 所有分片均已收到重组ip报文 ip_frag_reasm()


1 IP片段接收入口 ip_local_deliver()

在接收路径上的 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()中。

2 IP片段重组 ip_defrag()

ip_defrag()将分片重组逻辑分割的非常清晰:

  1. ip_find() 查找全局哈希表,找到(或者新建)该IP分片所属的 IP分片队列struct ipq
  2. 新收到了IP片段,所以调用 ip_frag_queue() 尝试对该IP分片队列中的片段进行重组。
/* 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;
}

2.1 查找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;
}

2.1.1 哈希查找分片队列 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);
			read_unlock(&f->lock);
			return q;
		}
	}
	read_unlock(&f->lock);
	// 没找到则新建一个,注意此时并未持有哈希表的读写锁
	return inet_frag_create(nf, f, key, hash);
}

2.1.2 新建IP分片队列 inet_frag_create()

inet_frag_create()流程新建一个IP分片队列,并且将该新的IP分片队列插入哈希表、LRU表中。

static struct inet_frag_queue *inet_frag_create(struct netns_frags *nf, struct inet_frags *f,
	void *arg, unsigned int hash)
{
	struct inet_frag_queue *q;
	// 分配IP分片队列并对其进行初始化
	q = inet_frag_alloc(nf, f, arg);
	if (q == NULL)
		return NULL;
	// 将新建的IP分片队列放入全局的IP分片重组哈希表中
	return inet_frag_intern(nf, q, f, hash, arg);
}

static struct inet_frag_queue *inet_frag_alloc(struct netns_frags *nf, struct inet_frags *f, void *arg)
{
	struct inet_frag_queue *q;
	// 按照指定大小进行分配,对于IPv4,就是sizeof(struct ipq)
	q = kzalloc(f->qsize, GFP_ATOMIC);
	if (q == NULL)
		return NULL;
	// 调用回调进行初始化,对于IPv4,这里是ip4_frag_init()
	f->constructor(q, arg);
	// 内存消耗记账
	atomic_add(f->qsize, &nf->mem);
	// 建立定时器,但是并不启动,启动是在inet_frag_intern()中完成的
	setup_timer(&q->timer, f->frag_expire, (unsigned long)q);
	spin_lock_init(&q->lock);
	atomic_set(&q->refcnt, 1);
	q->net = nf;
	return q;
}

static struct inet_frag_queue *inet_frag_intern(struct netns_frags *nf,
		struct inet_frag_queue *qp_in, struct inet_frags *f, unsigned int hash, void *arg)
{
	struct inet_frag_queue *qp;
#ifdef CONFIG_SMP
	struct hlist_node *n;
#endif

	write_lock(&f->lock);
#ifdef CONFIG_SMP
	/* With SMP race we have to recheck hash table, because
	 * such entry could be created on other cpu, while we
	 * promoted read lock to write lock.
	 */
	hlist_for_each_entry(qp, n, &f->hash[hash], list) {
		if (qp->net == nf && f->match(qp, arg)) {
			atomic_inc(&qp->refcnt);
			write_unlock(&f->lock);
			qp_in->last_in |= COMPLETE;
			inet_frag_put(qp_in, f);
			return qp;
		}
	}
#endif
	qp = qp_in;
	// 启动该IP分片队列的定时器,超时时间来自系统参数(见网络命名空间中的frags)
	if (!mod_timer(&qp->timer, jiffies + nf->timeout))
		atomic_inc(&qp->refcnt);
	// 将新的IP分片队列加入哈希表中,并累加计数器
	atomic_inc(&qp->refcnt);
	hlist_add_head(&qp->list, &f->hash[hash]);
	// 将qp加入LRU链表
	list_add_tail(&qp->lru_list, &nf->lru_list);
	nf->nqueues++;
	write_unlock(&f->lock);
	return qp;
}


2.2 重组IP报文 ip_frag_queue()

在上面ip_find()的处理过程中,仅仅是拿到了该IP分片对应的IP分片队列,并没有将新收到的IP分片添加到队列中,是否添加以及它们是否能够重组成一个IP报文是由ip_frag_queue()实现的。

/* Add new segment to existing queue. */
static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
	struct sk_buff *prev, *next;
	struct net_device *dev;
	int flags, offset;
	int ihl, end;
	int err = -ENOENT;

	// 如果该IP报文已经重组完毕,但是又收到了属于它的片段,那么新收到的IP分片一定是个重复分片,丢弃
	if (qp->q.last_in & COMPLETE)
		goto err;

	if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
	    unlikely(ip_frag_too_far(qp)) &&  unlikely(err = ip_frag_reinit(qp)))
	{
		ipq_kill(qp);
		goto err;
	}
	// offset记录偏移量,字节为单位,flags记录MF和DF标记
	offset = ntohs(ip_hdr(skb)->frag_off);
	flags = offset & ~IP_OFFSET;
	offset &= IP_OFFSET;
	offset <<= 3;		/* offset is in 8-byte chunks */
	ihl = ip_hdrlen(skb);

	// end记录的是该IP分片最后一个字节在整个IP报文中的偏移量
	end = offset + skb->len - ihl;
	err = -EINVAL;

	if ((flags & IP_MF) == 0) {
		// MF标记为0,说明该IP片段是IP报文的最后一个分片

		// q.len记录的是当前收到的该IP报文的最大偏移量,所以下面两种情况分别是:
		// cond1成立,表示新收到这个片段虽然说自己是该IP报文的最后一个片段,
		// 但是其最后一个字节之前已经收过了,这显然是一种传输错误;
		// cond2成立,之前已经收到了最后一个IP分片,现在又收到了一个最后分片(可能是重传),
		// 但是其最后一个字节的偏移量不正确,这显然是一种错误
		if (end < qp->q.len || ((qp->q.last_in & LAST_IN) && end != qp->q.len))
			goto err;
		// 标记最后一个片段接收成功,更新q.len
		qp->q.last_in |= LAST_IN;
		qp->q.len = end;
	} else {
		// IP分片不是IP报文的最后一个分片,其end必须是8字节对齐的,这是由offset首部格式决定的
		if (end&7) {
			end &= ~7;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
				skb->ip_summed = CHECKSUM_NONE;
		}
		if (end > qp->q.len) {
			// 不是最后一个分片,但是其最后一个字节的偏移超过了报文总长度,显然是错误
			if (qp->q.last_in & LAST_IN)
				goto err;
			// 偏移量ok,更新q.len
			qp->q.len = end;
		}
	}
	// 上面调整过end的值,或者该IP分片没有携带数据,那么是一种错误
	if (end == offset)
		goto err;

	err = -ENOMEM;
	// 调整skb,删除IP首部,只保留数据部分
	if (pskb_pull(skb, ihl) == NULL)
		goto err;
	// 校验和调整
	err = pskb_trim_rcsum(skb, end - offset);
	if (err)
		goto err;

	/* Find out which fragments are in front and at the back of us
	 * in the chain of fragments so far.  We must know where to put
	 * this fragment, right?
	 */
	prev = NULL;
	// 根据offset,找到该分片的插入点,最后将插入到prev的后面
	for (next = qp->q.fragments; next != NULL; next = next->next) {
		if (FRAG_CB(next)->offset >= offset)
			break;	/* bingo! */
		prev = next;
	}

	// 检查新接收的skb和prev是否有重叠
	if (prev) {
		int i = (FRAG_CB(prev)->offset + prev->len) - offset;
		// 因为prev->offset肯定是小于offset的,如果prev报文的最后一个字节的
		// 偏移量超过了offset(i > 0),说明二者一定有重叠,需要调整
		if (i > 0) {
			// 调整策略就是将新收到的skb前面的一部分删除
			offset += i;
			err = -EINVAL;
			// 删除后,新的报文已经没有数据了,这种情况是新收到的skb之前已经完全收过了
			if (end <= offset)
				goto err;
			err = -ENOMEM;
			// 将新收到的skb的前i字节数据删除
			if (!pskb_pull(skb, i))
				goto err;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
				skb->ip_summed = CHECKSUM_NONE;
		}
	}

	err = -ENOMEM;
	// 检查新接收的skb和next是否有重叠
	while (next && FRAG_CB(next)->offset < end) {
		int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */

		if (i < next->len) {
			// i < next->len说明next末尾还有一部分数据是不重叠的,更新next
			/* Eat head of the next overlapped fragment
			 * and leave the loop. The next ones cannot overlap.
			 */
			if (!pskb_pull(next, i))
				goto err;
			FRAG_CB(next)->offset += i;
			qp->q.meat -= i;
			if (next->ip_summed != CHECKSUM_UNNECESSARY)
				next->ip_summed = CHECKSUM_NONE;
			break;
		} else {
			// next中所有字节都是重复的,删除它
			struct sk_buff *free_it = next;

			/* Old fragment is completely overridden with new one drop it. */
			next = next->next;
			if (prev)
				prev->next = next;
			else
				qp->q.fragments = next;
			qp->q.meat -= free_it->len;
			frag_kfree_skb(qp->q.net, free_it, NULL);
		}
	}
	FRAG_CB(skb)->offset = offset;

	// 将新收到的skb插入IP分片队列中
	skb->next = next;
	if (prev)
		prev->next = skb;
	else
		qp->q.fragments = skb;
	// 记录输入设备索引
	dev = skb->dev;
	if (dev) {
		qp->iif = dev->ifindex;
		skb->dev = NULL;
	}
	// 更新时间戳和meat
	qp->q.stamp = skb->tstamp;
	qp->q.meat += skb->len;
	// 内存记账
	atomic_add(skb->truesize, &qp->q.net->mem);
	// 偏移量为0,说明是第一个IP片段,设置FIRST_IN标记
	if (offset == 0)
		qp->q.last_in |= FIRST_IN;
	// 所有片段都已经收到,重组IP报文
	if (qp->q.last_in == (FIRST_IN | LAST_IN) && qp->q.meat == qp->q.len)
		return ip_frag_reasm(qp, prev, dev);
	// 重组条件不满足,更新LRU链表
	write_lock(&ip4_frags.lock);
	list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
	write_unlock(&ip4_frags.lock);
	return -EINPROGRESS;

err:
	kfree_skb(skb);
	return err;
}

2.2.1 所有分片均已收到重组ip报文 ip_frag_reasm()

当ip_frag_queue()发现一个IP报文的所有片段都已经收到后,就调用ip_frag_reasm()进行重组IP报文。

/* Build a new IP datagram from all its fragments. */
static int ip_frag_reasm(struct ipq *qp, struct sk_buff *prev, struct net_device *dev)
{
	struct iphdr *iph;
	struct sk_buff *fp, *head = qp->q.fragments;
	int len;
	int ihlen;
	int err;
	// 将IP分片队列从哈希表中摘下来,递减IP分片队列引用计数,以及停止相关定时器
	ipq_kill(qp);

	/* Make the one we just received the head. */
	if (prev) {
		head = prev->next;
		fp = skb_clone(head, GFP_ATOMIC);
		if (!fp)
			goto out_nomem;
		fp->next = head->next;
		prev->next = fp;

		skb_morph(head, qp->q.fragments);
		head->next = qp->q.fragments->next;

		kfree_skb(qp->q.fragments);
		qp->q.fragments = head;
	}
	BUG_TRAP(head != NULL);
	BUG_TRAP(FRAG_CB(head)->offset == 0);

	// 计算整个IP报文的总长度
	ihlen = ip_hdrlen(head);
	len = ihlen + qp->q.len;
	// 整个IP报文长度不能超过65535
	err = -E2BIG;
	if (len > 65535)
		goto out_oversize;

	/* Head of list must not be cloned. */
	// 组装时,所有的IP片段会被拷贝到第一个IP片段上,第一个IP片段不能是克隆的
	if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
		goto out_nomem;

	/* If the first fragment is fragmented itself, we split
	 * it to two chunks: the first with data and paged part
	 * and the second, holding only fragments. */
	// 第一个IP片段不能有分片,如果有,把它拆成两部分,第一部分没有片段,片段部分拷贝到新建的skb中
	if (skb_shinfo(head)->frag_list) {
		struct sk_buff *clone;
		int i, plen = 0;

		if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
			goto out_nomem;
		clone->next = head->next;
		head->next = clone;
		skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
		skb_shinfo(head)->frag_list = NULL;
		for (i=0; i<skb_shinfo(head)->nr_frags; i++)
			plen += skb_shinfo(head)->frags[i].size;
		clone->len = clone->data_len = head->data_len - plen;
		head->data_len -= clone->len;
		head->len -= clone->len;
		clone->csum = 0;
		clone->ip_summed = head->ip_summed;
		atomic_add(clone->truesize, &qp->q.net->mem);
	}

	// 将所有的IP分片链接到第一个IP片段的frag_list中,并且重新计算校验和以及长度信息
	skb_shinfo(head)->frag_list = head->next;
	skb_push(head, head->data - skb_network_header(head));
	atomic_sub(head->truesize, &qp->q.net->mem);
	for (fp=head->next; fp; fp = fp->next) {
		head->data_len += fp->len;
		head->len += fp->len;
		if (head->ip_summed != fp->ip_summed)
			head->ip_summed = CHECKSUM_NONE;
		else if (head->ip_summed == CHECKSUM_COMPLETE)
			head->csum = csum_add(head->csum, fp->csum);
		head->truesize += fp->truesize;
		atomic_sub(fp->truesize, &qp->q.net->mem);
	}

	head->next = NULL;
	head->dev = dev;
	head->tstamp = qp->q.stamp;
	// 重新设定IP首部的一些字段
	iph = ip_hdr(head);
	iph->frag_off = 0;
	iph->tot_len = htons(len);
	IP_INC_STATS_BH(IPSTATS_MIB_REASMOKS);
	qp->q.fragments = NULL;
	return 0;

out_nomem:
	LIMIT_NETDEBUG(KERN_ERR "IP: queue_glue: no memory for gluing queue %p\n", qp);
	err = -ENOMEM;
	goto out_fail;
out_oversize:
	if (net_ratelimit())
		printk(KERN_INFO "Oversized IP packet from %d.%d.%d.%d.\n", NIPQUAD(qp->saddr));
out_fail:
	IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
	return err;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值