TCP实现之:IP分片内核实现

TCP实现之:IP分片内核实现

一、前言

先来回顾一下基本概念吧。啥是分片?啥是分段?

报文在网络设备间传输的时候,一次能够传输单个报文的尺寸是有限制的,这个限制被称为MTU:最大传输单元(Maximum Transmission Unit)。不同类型的网络的MTU大小可能有差异,如以太网的MTU为1500。这个MTU指的是报文所能携带的有效数据,因此并不是以太网数据帧的真实大小,其真实大小为:
1500 + 14 + 4 = 1518 1500 + 14 + 4 = 1518 1500+14+4=1518
其中14为以太网报头,4位为尾部校验和FCS

IP报文的长度大于MTU时,需要将报文拆分为多个长度不大于MTU的数据片,这个过程称为IP层的分片。由于单个数据片的丢失会导致整个IP报文的失效与重传,因此IP的分片要尽量避免。因此,对于TCP报文,在将报文交给IP协议之前,会将报文拆分成长度不大于MTU的数据段,这个过程称为TCP层的分段。

二、IP分片的实现

下面我们主要以原始套接字在进行报文发送时的IP分片为例来进行讲解。

2.1 原始套接字

针对于IP分片,原始套接字有两种情况:是否设置了IP_HDRINCL套接字选项。该选项用来控制内核是否生成IP报头:当设置该选项时,意味着IP头部已经包含在了用户数据里,不需要内核添加IP头部;否则,内核将根据套接字中的信息来为报文添加IP头部。

当设置了该选项时,内核将不会为该报文分片,报文长度如果大于MTU将直接返回错误。官方可能认为,在这种模式下,用户态将负责报文的全权处理,包括分片等操作。

当未设置该选项的时候,内核会调用ip_append_data来将用户数据添加到套接字的待发送队列中。下面我们来详细看一下这个函数的实现。

ip_append_data
static int __ip_append_data(struct sock *sk,
			    struct flowi4 *fl4,
			    struct sk_buff_head *queue,
			    struct inet_cork *cork,
			    struct page_frag *pfrag,
			    int getfrag(void *from, char *to, int offset,
					int len, int odd, struct sk_buff *skb),
			    void *from, int length, int transhdrlen,
			    unsigned int flags)
{
	struct inet_sock *inet = inet_sk(sk);
	struct sk_buff *skb;

	struct ip_options *opt = cork->opt;
	int hh_len;
	int exthdrlen;
	int mtu;
	int copy;
	int err;
	int offset = 0;
	unsigned int maxfraglen, fragheaderlen, maxnonfragsize;
	int csummode = CHECKSUM_NONE;
	struct rtable *rt = (struct rtable *)cork->dst;
	u32 tskey = 0;

	skb = skb_peek_tail(queue); //取出发送队列最后一个skb

	exthdrlen = !skb ? rt->dst.header_len : 0;
	mtu = cork->fragsize; //cork里面保存了IP分片过程中需要用到的很多参数,fragsize为MTU
	if (cork->tx_flags & SKBTX_ANY_SW_TSTAMP &&
	    sk->sk_tsflags & SOF_TIMESTAMPING_OPT_ID)
		tskey = sk->sk_tskey++;

	hh_len = LL_RESERVED_SPACE(rt->dst.dev); //以太网头部长度

	fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0); //IP头部长度
	maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen; //最大片段长度,指的是IP报文总长度。
	maxnonfragsize = ip_sk_ignore_df(sk) ? 0xFFFF : mtu; //最大允许的未分片的数据的尺寸。也就是说,数据的总长度不能超过这个值。如果sk设置的ignore_df(忽略禁止分片),那么其大小为0xFFFF;否则为MTU。

    /* cork->length为已经添加的数据长度。若数据长度超过允许的长度,返回EMSGSIZE。 */
	if (cork->length + length > maxnonfragsize - fragheaderlen) {
		ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
			       mtu - (opt ? opt->optlen : 0));
		return -EMSGSIZE;
	}
    
    /*
	 * transhdrlen > 0 means that this is the first fragment and we wish
	 * it won't be fragmented in the future.
	 */
	if (transhdrlen &&
	    length + fragheaderlen <= mtu &&
	    rt->dst.dev->features & (NETIF_F_HW_CSUM | NETIF_F_IP_CSUM) &&
	    !(flags & MSG_MORE) &&
	    !exthdrlen)
		csummode = CHECKSUM_PARTIAL;

	cork->length += length;
    /* 对于GSO标志的报文,或者是支持UFO的网卡驱动(网卡驱动进行的UDP分段),使用ip_ufo_append_data方法来处理。该方法不会对报文进行分片。 */
	if ((skb && skb_is_gso(skb)) ||
	    ((length > mtu) &&
	    (skb_queue_len(queue) <= 1) &&
	    (sk->sk_protocol == IPPROTO_UDP) &&
	    (rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len &&
	    (sk->sk_type == SOCK_DGRAM) && !sk->sk_no_check_tx)) {
		err = ip_ufo_append_data(sk, queue, getfrag, from, length,
					 hh_len, fragheaderlen, transhdrlen,
					 maxfraglen, flags);
		if (err)
			goto error;
		return 0;
	}
    
    /* So, what's going on in the loop below?
	 *
	 * We use calculated fragment length to generate chained skb,
	 * each of segments is IP fragment ready for sending to network after
	 * adding appropriate IP header.
	 */

	if (!skb)
		goto alloc_new_skb;
    
    /* 开始循环将报文数据添加到套接口的发送队列中。 */
    while (length > 0) {
		/* 检查当前的skb可以容纳的报文数据长度。 */
		copy = mtu - skb->len;
		if (copy < length)
			copy = maxfraglen - skb->len;
        /* 当前skb满了,重新分配一个新的skb。 */
		if (copy <= 0) {
			char *data;
			unsigned int datalen;
			unsigned int fraglen;
			unsigned int fraggap;
			unsigned int alloclen;
			struct sk_buff *skb_prev;
alloc_new_skb:
			skb_prev = skb;
			if (skb_prev)
				fraggap = skb_prev->len - maxfraglen;
			else
				fraggap = 0;

			/* datalen为要拷贝的数据的长度(不包括报文头部) */
			datalen = length + fraggap;
			if (datalen > mtu - fragheaderlen)
				datalen = maxfraglen - fragheaderlen;
			fraglen = datalen + fragheaderlen;

			if ((flags & MSG_MORE) &&
			    !(rt->dst.dev->features&NETIF_F_SG))
				alloclen = mtu;
			else
				alloclen = fraglen;

            /* 计算需要分配的skb线性缓存区的大小。该大小由报文长度、headroom和tailroom组成。 */
			alloclen += exthdrlen;

			/* The last fragment gets additional space at tail.
			 * Note, with MSG_MORE we overallocate on fragments,
			 * because we have no idea what fragment will be
			 * the last.
			 */
			if (datalen == length + fraggap)
				alloclen += rt->dst.trailer_len;

            /* 进行skb的创建 */
			if (transhdrlen) {
				skb = sock_alloc_send_skb(sk,
						alloclen + hh_len + 15,
						(flags & MSG_DONTWAIT), &err);
			} else {
				skb = NULL;
				if (atomic_read(&sk->sk_wmem_alloc) <=
				    2 * sk->sk_sndbuf)
					skb = sock_wmalloc(sk,
							   alloclen + hh_len + 15, 1,
							   sk->sk_allocation);
				if (unlikely(!skb))
					err = -ENOBUFS;
			}
			if (!skb)
				goto error;

			/*
			 *	Fill in the control structures
			 */
			skb->ip_summed = csummode;
			skb->csum = 0;
            /* 开辟headroom。在此之前,head、data、tail指向同一位置:线性缓存区的头部。 */
			skb_reserve(skb, hh_len);

			/* only the initial fragment is time stamped */
			skb_shinfo(skb)->tx_flags = cork->tx_flags;
			cork->tx_flags = 0;
			skb_shinfo(skb)->tskey = tskey;
			tskey = 0;

            /* 开辟data空间,将tail往下移动,并增加len计数。 */
			data = skb_put(skb, fraglen + exthdrlen);
            /* 设置三层协议报头偏移。 */
			skb_set_network_header(skb, exthdrlen);
            /* 设置四层协议报头偏移。 */
			skb->transport_header = (skb->network_header +
						 fragheaderlen);
            /* 此时data指向了IP数据区 */
			data += fragheaderlen + exthdrlen;

            /* fraggap代表上一个skb多余的数据长度,要转移到当前的skb。不太懂这种操作。 */
			if (fraggap) {
				skb->csum = skb_copy_and_csum_bits(
					skb_prev, maxfraglen,
					data + transhdrlen, fraggap, 0);
				skb_prev->csum = csum_sub(skb_prev->csum,
							  skb->csum);
				data += fraggap;
				pskb_trim_unique(skb_prev, maxfraglen);
			}

            /* 计算要拷贝的数据长度,去除fraggap和transhdrlen。 */
			copy = datalen - transhdrlen - fraggap;
            
            /*
             * getfrag是用于对from进行数据分片的函数,其可以是raw_getfrag、udp_getfrag等。
             * 对于raw_getfrag,它所做的事情是把msg迭代器中的copy长度的报文数据拷贝到skb的报文区。
             * 其实这里的offset没有多大的意义,因为msg迭代器拷贝时会从上一次结束的地方重新开始。
             */
			if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
				err = -EFAULT;
				kfree_skb(skb);
				goto error;
			}

			offset += copy;
			length -= datalen - fraggap;
			transhdrlen = 0;
			exthdrlen = 0;
			csummode = CHECKSUM_NONE;

			/*
			 * 将新创建的skb添加到发送队列。从这里我们可以看出,其并没有对IP头部进行初始化,只是保留了IP头部空间。
			 */
			__skb_queue_tail(queue, skb);
			continue;
		}

        /* 之前的skb有足够的空间,不需要重新分配skb。 */
		if (copy > length)
			copy = length;

        /* 设备不支持分散聚合IO,并且skb的tailroom有足够的空间。 */
		if (!(rt->dst.dev->features&NETIF_F_SG) &&
		    skb_tailroom(skb) >= copy) {
			unsigned int off;

			off = skb->len;
			if (getfrag(from, skb_put(skb, copy),
					offset, copy, off, skb) < 0) {
				__skb_trim(skb, off);
				err = -EFAULT;
				goto error;
			}
		} else {
            /* 使用frags。此时,skb里存的数据量还没有达到MTU,但是线性缓存区已经满了 */
			int i = skb_shinfo(skb)->nr_frags;

			err = -ENOMEM;
			if (!sk_page_frag_refill(sk, pfrag))
				goto error;

			if (!skb_can_coalesce(skb, i, pfrag->page,
					      pfrag->offset)) {
				err = -EMSGSIZE;
				if (i == MAX_SKB_FRAGS)
					goto error;

				__skb_fill_page_desc(skb, i, pfrag->page,
						     pfrag->offset, 0);
				skb_shinfo(skb)->nr_frags = ++i;
				get_page(pfrag->page);
			}
			copy = min_t(int, copy, pfrag->size - pfrag->offset);
			if (getfrag(from,
				    page_address(pfrag->page) + pfrag->offset,
				    offset, copy, skb->len, skb) < 0)
				goto error_efault;

			pfrag->offset += copy;
			skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
			skb->len += copy;
			skb->data_len += copy;
			skb->truesize += copy;
			atomic_add(copy, &sk->sk_wmem_alloc);
		}
		offset += copy;
		length -= copy;
	}
    
    return 0;

error_efault:
	err = -EFAULT;g
        
error:
	cork->length -= length;
	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
	return err;

总结一下,ip_append_data是一个相对比较通用的函数。它的作用是把数据添加到套接字发送队列中的skb中去。如果数据量很小,它不会创建新的skb,而是将数据添加到最后一个skbfrag中(skb的数据量没有超过MTU)。如果数据量很大,那么它会将其进行拆分。需要注意的是,这里的拆分并不是IP的分片,具体的拆分过程会根据传递进去的getfrag函数而不同。这里创建的skb单个数据量都不会超过MTU,而且skb中没有构建IP报头。

ip_push_pending_frames

这个函数一般都是与上面那个ip_append_data配合使用的,即前者想将用户态的报文数据整理好存储到套接字发送队列中,此函数再将队列中的数据一次性发送出去。值得一提的是,当前队列中的所有数据都是一块的,也就是说他们都属于同一个上层(用户态)传递下来的数据,比如说一个大的UDP数据块。

int ip_push_pending_frames(struct sock *sk, struct flowi4 *fl4)
{
	struct sk_buff *skb;

	skb = ip_finish_skb(sk, fl4);
	if (!skb)
		return 0;

	/* Netfilter gets whole the not fragmented skb. */
	return ip_send_skb(sock_net(sk), skb);
}

该函数首先调用了ip_finish_skb,这个函数是__ip_make_skb的封装。而__ip_make_skb这个函数的作用我们之前也已经讲过了,它是用来将套接字发送队列中的skb组装成一个skb的。具体是怎么个逻辑呢?它取出发送队列queue中的第一个skb,并将其余的skb一个接一个的连接到第一个skbfrag_list中,为后面的IP分片做准备。第一个skb的长度为所有skb的数据部分总长度,且其余的skbdata均指向IP数据部分(不包含IP头部),只有第一个skb拥有IP头部。

struct sk_buff *__ip_make_skb(struct sock *sk,
			      struct flowi4 *fl4,
			      struct sk_buff_head *queue,
			      struct inet_cork *cork)
{
	struct sk_buff *skb, *tmp_skb;
	struct sk_buff **tail_skb;
	struct inet_sock *inet = inet_sk(sk);
	struct net *net = sock_net(sk);
	struct ip_options *opt = NULL;
	struct rtable *rt = (struct rtable *)cork->dst;
	struct iphdr *iph;
	__be16 df = 0;
	__u8 ttl;

    /* 取出队列中的第一个skb元素 */
	skb = __skb_dequeue(queue);
	if (!skb)
		goto out;
    /* 取出第一个元素的frag_list */
	tail_skb = &(skb_shinfo(skb)->frag_list);

	/* 将skb的data设置到IP头部 */
	if (skb->data < skb_network_header(skb))
		__skb_pull(skb, skb_network_offset(skb));
    
    /* 循环取出queue中的所有skb,并逐个添加到第一个skb的frag_list中。 */
	while ((tmp_skb = __skb_dequeue(queue)) != NULL) {
        /* 将tmp_skb的data移动到IP报文的数据区域(排除IP头部) */
		__skb_pull(tmp_skb, skb_network_header_len(skb));
		*tail_skb = tmp_skb;
		tail_skb = &(tmp_skb->next);
		skb->len += tmp_skb->len;
		skb->data_len += tmp_skb->len;
		skb->truesize += tmp_skb->truesize;
		tmp_skb->destructor = NULL;
		tmp_skb->sk = NULL;
	}

	/* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow
	 * to fragment the frame generated here. No matter, what transforms
	 * how transforms change size of the packet, it will come out.
	 */
	skb->ignore_df = ip_sk_ignore_df(sk);

	/* DF bit is set when we want to see DF on outgoing frames.
	 * If ignore_df is set too, we still allow to fragment this frame
	 * locally. */
	if (inet->pmtudisc == IP_PMTUDISC_DO ||
	    inet->pmtudisc == IP_PMTUDISC_PROBE ||
	    (skb->len <= dst_mtu(&rt->dst) &&
	     ip_dont_fragment(sk, &rt->dst)))
		df = htons(IP_DF);

	if (cork->flags & IPCORK_OPT)
		opt = cork->opt;

	if (cork->ttl != 0)
		ttl = cork->ttl;
	else if (rt->rt_type == RTN_MULTICAST)
		ttl = inet->mc_ttl;
	else
		ttl = ip_select_ttl(inet, &rt->dst);

    /* 构造IP头部 */
	iph = ip_hdr(skb);
	iph->version = 4;
	iph->ihl = 5;
	iph->tos = (cork->tos != -1) ? cork->tos : inet->tos;
	iph->frag_off = df;
	iph->ttl = ttl;
	iph->protocol = sk->sk_protocol;
	ip_copy_addrs(iph, fl4);
	ip_select_ident(net, skb, sk);

	if (opt) {
		iph->ihl += opt->optlen>>2;
		ip_options_build(skb, opt, cork->addr, rt, 0);
	}

	skb->priority = (cork->tos != -1) ? cork->priority: sk->sk_priority;
	skb->mark = cork->mark;
	skb->tstamp = cork->transmit_time;
	/*
	 * Steal rt from cork.dst to avoid a pair of atomic_inc/atomic_dec
	 * on dst refcount
	 */
	cork->dst = NULL;
	skb_dst_set(skb, &rt->dst);

	if (iph->protocol == IPPROTO_ICMP)
		icmp_out_count(net, ((struct icmphdr *)
			skb_transport_header(skb))->type);

	ip_cork_release(cork);
out:
	return skb;
}

构造出来一个完整的skb后,ip_send_skb负责将这个报文发送出去。后面的发送流程与我们之前的关于IP协议实现部分就是一致的了,流程为:

ip_send_skb -> ip_local_out -> __ip_local_out -> dst_output -> ip_output -> ip_finish_output

值得注意的是,在ip_finish_output函数中会根据skb的长度来选择是否需要对skb进行分片,即真正的IP分片是在这里进行的,具体的分片函数为:ip_fragment

ip_fragment

分片函数会先进行简单的检查,主要为DFDon't Fragment)标志位的检查。

static int ip_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
		       unsigned int mtu,
		       int (*output)(struct net *, struct sock *, struct sk_buff *))
{
	struct iphdr *iph = ip_hdr(skb);

    /* 检查IP头部是否设置了DF,没有的话直接进行分片。 */
	if ((iph->frag_off & htons(IP_DF)) == 0)
		return ip_do_fragment(net, sk, skb, output);

    /* IP头部设置了DF,且skb没有设置ignore_df,则返回错误。 */
	if (unlikely(!skb->ignore_df ||
		     (IPCB(skb)->frag_max_size &&
		      IPCB(skb)->frag_max_size > mtu))) {
		IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
			  htonl(mtu));
		kfree_skb(skb);
		return -EMSGSIZE;
	}

	return ip_do_fragment(net, sk, skb, output);
}

检查都通过后,调用ip_do_fragment来对报文进行真正的分片。其实分片要做的工作不多,因为skb的构造在之前就已经完成了,主要包括以下几点:

  1. 对于具有片段的skb,将第一个skbIP头部拷贝到frag_list中所有的skb里;
  2. 计算IP报头中分片的偏移量;
  3. 调用output将报文发送出去。

以下代码为针对frag_listIP分片。如果存在frag_list,就意味着队列中的每个skb的长度都不会大于MTU,只需要进行IP头部的封装就行了。

int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
		   int (*output)(struct net *, struct sock *, struct sk_buff *))
{
	struct iphdr *iph;
	struct sk_buff *skb2;
	struct rtable *rt = skb_rtable(skb);
	unsigned int mtu, hlen, ll_rs;
	struct ip_fraglist_iter iter;
	ktime_t tstamp = skb->tstamp;
	struct ip_frag_state state;
	int err = 0;

	/* for offloaded checksums cleanup checksum before fragmentation */
	if (skb->ip_summed == CHECKSUM_PARTIAL &&
	    (err = skb_checksum_help(skb)))
		goto fail;

	/*
	 *	提取出第一个skb的IP头部地址,作为所有片段的IP头部.
	 */

	iph = ip_hdr(skb);

	/* 计算分片的MTU。 */
	mtu = ip_skb_dst_mtu(sk, skb);
	if (IPCB(skb)->frag_max_size && IPCB(skb)->frag_max_size < mtu)
		mtu = IPCB(skb)->frag_max_size;

	/*
	 *	Setup starting values.
	 */

	hlen = iph->ihl * 4;
	mtu = mtu - hlen;	/* Size of data space */
	IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE;
	ll_rs = LL_RESERVED_SPACE(rt->dst.dev);

	/* 
	 * 检查到skb存在frag_list,进入frag_list分片流程。
	 */
	if (skb_has_frag_list(skb)) {
		struct sk_buff *frag, *frag2;
		/* 第一个skb线性和frags数据长度的总和,不包括分片队列的长度。 */
		unsigned int first_len = skb_pagelen(skb);

		/* 如果去除了分片队列的长度后,skb的长度还是大于MTU,则进入到slow_path流程,即线性数据IP分片流程 */
		if (first_len - hlen > mtu ||
		    ((first_len - hlen) & 7) ||
		    ip_is_fragment(iph) ||
		    skb_cloned(skb) ||
		    skb_headroom(skb) < ll_rs)
			goto slow_path;

        /* 遍历skb的分片队列,检查每个片段是否有效。 */
		skb_walk_frags(skb, frag) {
			/* 对于长度超标或者headroom不足的报文,进入到slow_path_clean流程。该流程会恢复skb片段的初始状态,并进入slow_path处理流程。 */
			if (frag->len > mtu ||
			    ((frag->len & 7) && frag->next) ||
			    skb_headroom(frag) < hlen + ll_rs)
				goto slow_path_clean;

			/* Partially cloned skb? */
			if (skb_shared(frag))
				goto slow_path_clean;

			BUG_ON(frag->sk);
			if (skb->sk) {
				frag->sk = skb->sk;
				frag->destructor = sock_wfree;
			}
			skb->truesize -= frag->truesize;
		}

		/* 生成IP头部校验码、设置MF标志、设置IP头部正确的数据长度等。 */
		ip_fraglist_init(skb, iph, hlen, &iter);

        /* 使用迭代器进入片段发送流程。 */
		for (;;) {
			/* Prepare header of the next frame,
			 * before previous one went down. */
			if (iter.frag) {
                /* 拷贝上一个skb的cb部分内容到当前片段。 */
				ip_fraglist_ipcb_prepare(skb, &iter);
                /* 拷贝上一个skb的IP头部部分内容到当前片段,设置分片偏移量,计算校验码等。 */
				ip_fraglist_prepare(skb, &iter);
			}

            /* 设置时间戳,并调用output进行报文的发送。 */
			skb->tstamp = tstamp;
			err = output(net, sk, skb);

			if (!err)
				IP_INC_STATS(net, IPSTATS_MIB_FRAGCREATES);
			if (err || !iter.frag)
				break;

			skb = ip_fraglist_next(&iter);
		}

		if (err == 0) {
			IP_INC_STATS(net, IPSTATS_MIB_FRAGOKS);
			return 0;
		}

		kfree_skb_list(iter.frag);

		IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
		return err;

下面部分的代码是进入到线性数据分片的流程:

/* 清空之前设置的属性。 */
slow_path_clean:
		skb_walk_frags(skb, frag2) {
			if (frag2 == frag)
				break;
			frag2->sk = NULL;
			frag2->destructor = NULL;
			skb->truesize += frag2->truesize;
		}
	}

slow_path:
	
	/* 初始化state对象,该对象用于存储分片过程中用到的一些信息,包括MTU、剩余数据长度、分片偏移等。 */
	ip_frag_init(skb, hlen, ll_rs, mtu, IPCB(skb)->flags & IPSKB_FRAG_PMTU,
		     &state);

	/* 循环拷贝skb中的数据 */
	while (state.left > 0) {
        /* 根据偏移量判断当前是否是第一个IP分片。 */
		bool first_frag = (state.offset == 0);

        /* 分配一个新的skb2,并将skb中的元数据、IP报头、以及剩余的数据(长度不超过MTU)拷贝进去,设置分片偏移量、计算校验码等。注意,在进行数据拷贝时,会依次从线性区域、frags和frag_list中取数据。 */
		skb2 = ip_frag_next(skb, &state);
		if (IS_ERR(skb2)) {
			err = PTR_ERR(skb2);
			goto fail;
		}
        /* 拷贝IP标志和选项。 */
		ip_frag_ipcb(skb, skb2, first_frag, &state);

		/*
		 *	Put this fragment into the sending queue.
		 */
		skb2->tstamp = tstamp;
        /* 发送出去。 */
		err = output(net, sk, skb2);
		if (err)
			goto fail;

		IP_INC_STATS(net, IPSTATS_MIB_FRAGCREATES);
	}
	consume_skb(skb);
	IP_INC_STATS(net, IPSTATS_MIB_FRAGOKS);
	return err;

fail:
	kfree_skb(skb);
	IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
	return err;
}

至此,整个IP的分片过程就完成了。

从以上的内容中我们可以看出,虽然IP_HDRINCL类型的套接字不支持IP分片,那也只是在一开始长度检查的时候进行了限制,如果把raw_send_hdrinc里长度检查部分去掉,后面的流程里是可以进行正常的IP分片的。

2.2 UDP报文分片

UDP报文发送的流程为:

udp_sendmsg -> __ip_append_data -> __ip_make_skb -> ip_send_skb -> ip_local_out -> __ip_local_out -> dst_output -> ip_output -> ip_finish_output

可以看出来,UDP的发送与原始套接字的流程基本上相似,只不过在udp_sendmsg函数中,内核会对用户态传递进来的UDP数据封装UDP头部。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值