ip分片重组函数-ip_frag_queue

在讲解此函数前 有个概念需要再次说明一下,即分片重叠的问题 

比如在没有接收到新来的分片时候 分片队列的分布如下:

当在接收到一个分片的时候,遍历链表(已经升序排序好)后找到插入位置后根据重叠与非重叠 有如下情况:

(1)不发生重叠的正常情况:
比如新分片的offset=300 len=99
则插入情况如下:

(2)发生前重叠
比如新分片的offset=280 len=119
则插入的情况如下:

(3)发送后重叠
比如新分片的offet=300 len=280
则插入的情况如下:

以上图就是对代码中覆盖部分的总结,根据图我们来看下面代码的重叠处理部分会容易理解一些

/* 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;

	//碎片重组已经完成
	if (qp->q.last_in & INET_FRAG_COMPLETE)
		goto err;

   /*
    IPCB(skb)->flags只有在本机发送IPv4分片时被置位,那么这里的检查应该是预防收到本机自己发出的IP分片。
    
   */	
	if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&//检查skb中的分片标志 
	
        //关于ip_frag_too_far:该函数主要保证了来自同一个peer(相同的源地址)不会占用过多的IP分片队列
	    unlikely(ip_frag_too_far(qp)) &&

	   //重新初始化该队列
	    unlikely(err = ip_frag_reinit(qp))) 
    {
		ipq_kill(qp);
		goto err;
	}
	
    //计算碎片的偏移量和标志
	offset = ntohs(ip_hdr(skb)->frag_off);
	flags = offset & ~IP_OFFSET;
	offset &= IP_OFFSET;
	offset <<= 3;		/* offset is in 8-byte chunks */

	//获得ip头的长度
	ihl = ip_hdrlen(skb);

	/* Determine the position of this fragment. */
	//获得报文总的长度
	end = offset + skb->len - ihl;
	err = -EINVAL;

	//如果是最后一个分片
	if ((flags & IP_MF) == 0) 
	{
		/* If we already have some bits beyond end
		 * or have different end, the segment is corrrupted.
		 */
		//末端小于之前获得的总长度  则出现问题 
		//之前已经收到了一个最后分片,且这次判断的末端不等于之前获得的值 则出错
		if (end < qp->q.len ||
		    ((qp->q.last_in & INET_FRAG_LAST_IN) && end != qp->q.len))
			goto err;

		qp->q.last_in |= INET_FRAG_LAST_IN; //标志这是最后一个分片
		qp->q.len = end;//更新总长度
	} 
	else //不是最后一个分片
    {
        //检查是否8字节对齐
		if (end&7) 
		{
		    //除最后一个分片外 其余分片均为8字节对齐
			end &= ~7;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)//没有设置跳过校验和计算
				skb->ip_summed = CHECKSUM_NONE;//传输层自己计算校验和
		}

		//此分片尾部是否超过之前获得的报文总长度
		if (end > qp->q.len) 
		{
			/* Some bits beyond end -> corruption. */
			if (qp->q.last_in & INET_FRAG_LAST_IN)//最后一个分片已经接受到 在来的分片是错误的
				goto err;
			qp->q.len = end;//更新报文的总长度
		}
	}
	//表示空的ip分片
	if (end == offset)
		goto err;

	err = -ENOMEM;
	//将skb->data移到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?
	 */
	//遍历ipq中的fragments链表,每个skb的cb成员记录着当前skb进行ip重组的
    //时候所需要的偏移,fragments中的skb都是按照offset升序排好的,所以,找到
    //第一项offset大于当前IP包偏移的数据包就可以了
	prev = NULL;
	for (next = qp->q.fragments; next != NULL; next = next->next) {
		if (FRAG_CB(next)->offset >= offset)
			break;	/* bingo! */
		prev = next;
	}

	/* We found where to put this one.  Check for overlap with
	 * preceding fragment, and, if needed, align things so that
	 * any overlaps are eliminated.
	 */
	//在上面的循环中找到了插入的位置 但可能发生重叠,
	if (prev) 
	{
	    //若没发生重叠 很显然offset的大小 应该在FRAG_CB(prev)->offset + prev->len 之后也就是i值会小于0
	    /*若发生了重叠 offset的大小 在FRAG_CB(prev)->offset和FRAG_CB(prev)->offset + prev->len之间
          则i的值会大于0
	    
         从上面的循环可知,此分片应该插入到prev指向的位置后面比如 prev的offset=100 len=150 
         若此分片的offset的值 在100+150的后面  i值就会小于0 没有重叠发生
         若此分片的offset的值 在100,350之间 i的值会大于0  发生了重叠
                                             插入位置   前重叠
                                             \|/ 
       ------------------------------------------------------
         分片1   |  (与分片2发生重叠)分片2    |   分片3     |
       ------------------------------------------------------
		*/
		int i = (FRAG_CB(prev)->offset + prev->len) - offset;

		if (i > 0)//发生了重叠 
		{
			offset += i; //将偏移完后移动i 跑到prev报文区间的最右边
			err = -EINVAL;
			if (end <= offset) //此分片移动后的offset是否超过了此分片的最大长度
				goto err;//偏移超过了分片的最大长度 出错
			err = -ENOMEM;
			
			if (!pskb_pull(skb, i))//将skb的数据指针减少i个字节 改变偏移值
				goto err;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)//没有设置跳过校验和计算
				skb->ip_summed = CHECKSUM_NONE;//传输层自己计算校验和
		}
	}

	err = -ENOMEM;
    //此分片与队列中存在的分片发生后面的重叠  后重叠
    /*                      插入位置
                            \|/
       ------------------------------------------------------------------------
         分片1   |  分片2    |  (重叠与分片3甚至后面的分片发生重叠) 分片3     |
       ------------------------------------------------------------------------
       
     */
    //此分片的最大报文长度 大于后面分片的起始位置 发生后重叠
	while (next && FRAG_CB(next)->offset < end) 
	{
	    //计算重叠的字节数
		int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */
	 
		if (i < next->len)//是否只重叠了此next分片 
		{
			/* Eat head of the next overlapped fragment
			 * and leave the loop. The next ones cannot overlap.
			 */
			//将覆盖的i个字节在next分片中剔除
			//将data指针减少i个字节
			if (!pskb_pull(next, i))
				goto err;
			FRAG_CB(next)->offset += i;//更新next新的偏移 往后移动i个字节
			qp->q.meat -= i;
			if (next->ip_summed != CHECKSUM_UNNECESSARY)//没有设置跳过校验和计算
				next->ip_summed = CHECKSUM_NONE;//传输层自己计算校验和
			break;
		} 
		else 
	    {
	        //覆盖了后面的整个分片 则将覆盖的分片释放掉
			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;

	/* Insert this fragment in the chain of fragments. */
    //将skb加入到队列中
	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;
	}
	qp->q.stamp = skb->tstamp;//记录这个包接收的时间戳
	qp->q.meat += skb->len; //增加已经获得的报文的长度
	atomic_add(skb->truesize, &qp->q.net->mem);

	//说明是第一个分片 设置第一个分片的标志
	if (offset == 0)
		qp->q.last_in |= INET_FRAG_FIRST_IN;

	//如果收到了第一个分片和最后一个分片 并且报文长度也也等于原始的ip报文长度
	//则对所有的分片进行组装
	if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
	    qp->q.meat == qp->q.len)
		return ip_frag_reasm(qp, prev, dev);//根据所有的分段创建一个新的数据包

	//ip分片还未完全收齐
	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;
}

 

 

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的C语言实现IP分片重组的示例代码,仅供参考: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define IP_HEADER_LEN 20 // IP header length #define MAX_PACKET_LEN 65535 // maximum packet length typedef struct ip_header { uint8_t ver_ihl; // Version (4 bits) + Internet header length (4 bits) uint8_t tos; // Type of service uint16_t total_len; // Total length uint16_t id; // Identification uint16_t frag_off; // Fragment offset field uint8_t ttl; // Time to live uint8_t protocol; // Protocol uint16_t checksum; // Header checksum uint32_t src_addr; // Source address uint32_t dest_addr; // Destination address } ip_header_t; void fragment_packet(uint8_t *packet, uint32_t len, uint32_t mtu) { // check packet length if (len > MAX_PACKET_LEN) { printf("Packet is too long!\n"); return; } // check MTU if (mtu < IP_HEADER_LEN + 8) { printf("MTU is too small!\n"); return; } // calculate number of fragments uint32_t num_frags = (len + mtu - 1) / mtu; if (num_frags > 65535) { printf("Too many fragments!\n"); return; } // create IP header for each fragment ip_header_t *headers[num_frags]; for (uint32_t i = 0; i < num_frags; i++) { headers[i] = malloc(IP_HEADER_LEN); memset(headers[i], 0, IP_HEADER_LEN); ip_header_t *header = headers[i]; header->ver_ihl = 0x45; // IPv4, 5 * 4 = 20 bytes header length header->total_len = htons(mtu); header->id = htons((uint16_t) rand()); header->frag_off = htons(i * (mtu - IP_HEADER_LEN) / 8); if (i == num_frags - 1) { header->frag_off |= htons(0x2000); // last fragment } header->ttl = 64; header->protocol = 17; // UDP header->src_addr = 0x0100007f; // localhost header->dest_addr = 0x0100007f; // localhost // calculate header checksum uint32_t sum = 0; for (int j = 0; j < IP_HEADER_LEN; j += 2) { sum += ((uint16_t) header[j]) << 8 | header[j + 1]; } while (sum >> 16) { sum = (sum & 0xffff) + (sum >> 16); } header->checksum = htons(~sum); } // fragment packet uint32_t offset = 0; for (uint32_t i = 0; i < num_frags; i++) { uint32_t frag_len = mtu - IP_HEADER_LEN; if (offset + frag_len > len) { frag_len = len - offset; } memcpy(headers[i] + IP_HEADER_LEN, packet + offset, frag_len); offset += frag_len; } // send fragments for (uint32_t i = 0; i < num_frags; i++) { ip_header_t *header = headers[i]; uint32_t frag_len = ntohs(header->total_len); uint8_t *fragment = malloc(frag_len); memcpy(fragment, header, IP_HEADER_LEN); memcpy(fragment + IP_HEADER_LEN, header + IP_HEADER_LEN, frag_len - IP_HEADER_LEN); printf("Sending fragment %d...\n", i); // send fragment over network free(fragment); } // free headers for (uint32_t i = 0; i < num_frags; i++) { free(headers[i]); } } void reassemble_packet(ip_header_t **headers, uint32_t num_frags) { uint32_t packet_len = 0; for (uint32_t i = 0; i < num_frags; i++) { packet_len += ntohs(headers[i]->total_len) - IP_HEADER_LEN; } uint8_t *packet = malloc(packet_len); uint32_t offset = 0; for (uint32_t i = 0; i < num_frags; i++) { uint32_t frag_len = ntohs(headers[i]->total_len) - IP_HEADER_LEN; memcpy(packet + offset, headers[i] + IP_HEADER_LEN, frag_len); offset += frag_len; } printf("Reassembled packet:\n"); // process reassembled packet free(packet); } int main() { srand(time(NULL)); uint8_t packet[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa}; uint32_t len = sizeof(packet); uint32_t mtu = 1472; fragment_packet(packet, len, mtu); // assume fragments are received in order and all belong to the same packet uint32_t num_frags = 3; ip_header_t *headers[num_frags]; uint16_t ids[] = {12345, 12345, 12345}; uint16_t offsets[] = {0, 185, 370}; for (uint32_t i = 0; i < num_frags; i++) { headers[i] = malloc(IP_HEADER_LEN); memset(headers[i], 0, IP_HEADER_LEN); ip_header_t *header = headers[i]; header->ver_ihl = 0x45; header->total_len = htons(1480); header->id = htons(ids[i]); header->frag_off = htons(offsets[i] / 8); if (i == num_frags - 1) { header->frag_off |= htons(0x2000); } header->ttl = 64; header->protocol = 17; header->src_addr = 0x0100007f; header->dest_addr = 0x0100007f; } reassemble_packet(headers, num_frags); // free headers for (uint32_t i = 0; i < num_frags; i++) { free(headers[i]); } return 0; } ``` 该示例代码实现IP分片重组的基本功能,可以按照指定的MTU将一个IP数据包分成多个片段,并将这些片段发送到网络上;同时,也可以接收多个IP分片并将它们重组成原始数据包。这个示例代码可以作为学习IP分片重组的入门教程,但实际应用中还需要考虑更多的细节和安全问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值