现在的位置: 首页 > 综合 > 正文

网络子系统86_inet协议族-l4向下(一)

2014年04月10日 ⁄ 综合 ⁄ 共 4715字 ⁄ 字号 评论关闭
//	l4数据向下l3传递
//	步骤:
//		1.如果sock->sk_write_queue为空,初始化corking
//			1.1 corking信息用于帮助ip层对数据进行分片
1.1 int ip_append_data(struct sock *sk, struct flowi4 *fl4,
		   int getfrag(void *from, char *to, int offset, int len,
			       int odd, struct sk_buff *skb),
		   void *from, int length, int transhdrlen,
		   struct ipcm_cookie *ipc, struct rtable **rtp,
		   unsigned int flags)
{
	struct inet_sock *inet = inet_sk(sk);
	int err;

	//sk_write_queue为空
	if (skb_queue_empty(&sk->sk_write_queue)) {
		//初始化ip用于聚合,分片数据的信息
		err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
		if (err)
			return err;
	} else {
		transhdrlen = 0;
	}
	//添加数据到sk->sk_wirte_queue
	return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
				sk_page_frag(sk), getfrag,
				from, length, transhdrlen, flags);
}

//	添加数据到队列
//		注:skb->frags数组里的数据时主缓存区中数据的扩展,
//			而frags_list里的数据代表的是独立缓存区(也就是必须作为单独ip片段而独立传输)。
//	步骤:
//		1.如果length大于mtu,或大于前一个skb剩余的空间
//			1.1 分配新skb,通过getfrag将数据从from拷贝到新skb
//		2.在拷贝过程中,需要考虑设备是否支持分散/聚集
//			2.1 有更多数据并且出口设备不支持分散/聚集IO,以最大尺寸分配skb
//			2.2 否则,以分片尺寸分配skb
1.2 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)
{

	//transhdrlen L4首部长度,用以区分第一个片段和后续片段
	//	transhdrlen !=0 表示ip_append_data工作在第一个片段
	//	transhdrlen ==0 表示ip_append_data未工作在第一个片段

	struct inet_sock *inet = inet_sk(sk);
	struct sk_buff *skb;

	//ip选项
	struct ip_options *opt = cork->opt;
	int hh_len;
	int exthdrlen;
	int mtu;
	int copy;
	int err;
	int offset = 0;
	unsigned int maxfraglen, fragheaderlen;
	int csummode = CHECKSUM_NONE;
	//路由项
	struct rtable *rt = (struct rtable *)cork->dst;

	//最后一个skb
	skb = skb_peek_tail(queue);

	exthdrlen = !skb ? rt->dst.header_len : 0;
	//链路允许的最大报文长度
	mtu = cork->fragsize;

	//l2首部长度
	hh_len = LL_RESERVED_SPACE(rt->dst.dev);

	//分片ip首部长度
	fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);

	//分片最大长度
	//	ip报文的长度为8字节的倍数
	maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;

	//所有分片总长不超过64k
	if (cork->length + length > 0xFFFF - fragheaderlen) {
		ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
			       mtu-exthdrlen);
		return -EMSGSIZE;
	}

	//length为剩余要处理的数据量
	while (length > 0) {
		//copy为当前ip片段中剩余的空间量
		copy = mtu - skb->len;

		//要加入的片段大于剩余的空间量
		if (copy < length)
		{
			//通过使用maxfraglen是长度缩减到8字节边界
			copy = maxfraglen - skb->len;
		}
			
		//copy=0表示该分配一个新的sk_buff,因为最后一个已被完全填满
		//copy<0表明有些数据必须从当前ip片段中删除,并移至新片段,前一种情况的特例
		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;
			//保证ip数据报对齐到8字节,fraggap=已满skb超出maxfraglen的部分,将移动到新分配的skb中取
			if (skb_prev)
				fraggap = skb_prev->len - maxfraglen;
			else
				fraggap = 0;

			//剩余数据长度
			datalen = length + fraggap;
			//剩余数据长度大于mtu
			if (datalen > mtu - fragheaderlen)
				datalen = maxfraglen - fragheaderlen;
			//分片长度(数据长度+首部长度)
			fraglen = datalen + fragheaderlen;

			//有更多数据并且出口设备不支持分散/聚集IO
			if ((flags & MSG_MORE) &&
			    !(rt->dst.dev->features&NETIF_F_SG))
			{
				//以最大尺寸分配内存
				alloclen = mtu;
			}
			else
			{	//以分片长度分配内存
				alloclen = fraglen;
			}
			
			...

			//分配新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);
					cork->tx_flags = 0;
			}

			skb->ip_summed = csummode;
			skb->csum = 0;
			//预留l2首部
			skb_reserve(skb, hh_len);
			skb_shinfo(skb)->tx_flags = cork->tx_flags;

			//移动skb->tail到分片尾部,返回skb->data
			data = skb_put(skb, fraglen + exthdrlen);
			skb_set_network_header(skb, exthdrlen);
			skb->transport_header = (skb->network_header +
						 fragheaderlen);
			data += fragheaderlen + exthdrlen;

			//从上一个skb拷贝未对其到8字节边界的剩余字节到新skb
			if (fraggap) {
				//计算剩余字节的校验和
				skb->csum = skb_copy_and_csum_bits(
					skb_prev, maxfraglen,
					data + transhdrlen, fraggap, 0);
				//更新上一个skb的校验和
				skb_prev->csum = csum_sub(skb_prev->csum,
							  skb->csum);
				data += fraggap;
				pskb_trim_unique(skb_prev, maxfraglen);
			}

			//拷贝剩余数据到缓存
			copy = datalen - transhdrlen - fraggap;
			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添加到队列上
			__skb_queue_tail(queue, skb);
			continue;
		}
		//copy>length意味着skb有足够的空间
		if (copy > length)
			copy = length;

		//出口设备不支持分散/聚集IO
		if (!(rt->dst.dev->features&NETIF_F_SG)) {
			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 {
			...
			//将数据拷贝到skb->frags中
		}
		offset += copy;
		length -= copy;
	}

	return 0;

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


//	L4校验和
//		TCP,UDP协议计算的校验和会包括其报头,有效载荷以及一个伪装报头。

//TCP,UDP伪报头

                                                

                                                  

//	硬件计算L4校验和
//	1.使用硬件计算L4校验和的条件:
//		1.1 由ip_append_data所构建的ip封包不会被分段(及送给ip_append_data的总数不会超过PMTU)
//		1.2 出口设备支持硬件校验和计算
//		1.3 没有转换报头(也就是IPSec集组协议)
//	2.计算方法:
//		2.1 L4协议把skb->csum初始化成正确的偏移量,并用伪报头验证和设定L4报头的校验字段

//	软件计算L4校验和
//		如果出口设备不支持硬件设备校验和,或者虽然支持,但是因为sk_write_queue有一个以上的ip片段而
//		无法使用,则L4校验和必须在软件中计算,在这种情况,getfrag把数据拷贝到缓存区时,会对L4有效载荷
//		计算部分校验和,然后,L4协议稍后会将这些值结合起来得到放在L4报头内的值。

抱歉!评论已关闭.