您的位置:首页 > 理论基础 > 计算机网络

网络子系统51_ip协议报文分片

2013-10-11 22:13 501 查看
//ip分片
//	快速路径的条件:
//		1.skb
//			1.skb的数据长度(主缓存区+frags缓存区)小于输出路径的mtu
//			2.skb的数据长度对齐到8字节的边界
//			3.skb没有被分片
//			4.skb没有被共享
//		2.skb->frag_list
//			1.长度小于(mtu-ip报头-选项)
//			2.除最后一个分片外,长度都需要对齐到8字节边界
//			3.head-data之间的空间,可以容纳ip报头
//	注:skb->frag_list的skb,没有填充ip头,skb填充有ip头
//
//	慢速路径条件:
//		只要不满足快速路径其中的一条,使用慢速路径

//	快速路径处理过程:
//		1.第一个分片使用完整的ip选项
//		2.其余分片使用部分ip选项
//		3.除最后一个分片外,设置MF标志
//		4.设置offset
//		5.使用相同的路由信息,向下层传递

//	慢速路径处理过程:
//		1.分配新的skb,长度为mtu,或者剩余数据量,对齐到8字节边界
//		2.预留l2帧头空间
//		3.拷贝l3报头,以及数据到新skb中
//		4.除第一个分片使用完整的ip选项,其余分片使用部分ip选项
//		5.设置offset
//		5.使用相同的路由信息,向下层传递

//	对比快速路径与慢速路径:
//		1.慢速路径的慢主要表现在分配新的缓存区,从旧缓存区中拷贝数据

//	注:ip报头的offset字段,只针对有效载荷(ip头,ip选项不包括在内)

//调用路径ip_output/ip_mc_output->ip_fragment
1.1 int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))
{
struct iphdr *iph;
int raw = 0;
int ptr;
struct net_device *dev;
struct sk_buff *skb2;
unsigned int mtu, hlen, left, len, ll_rs;
int offset;
int not_last_frag;
struct rtable *rt = (struct rtable*)skb->dst;
int err = 0;
//出口设备
dev = rt->u.dst.dev;
//ip头
iph = skb->nh.iph;
//ip报头设置有DF标志,禁止分片
////skb->local_df如果被设置,则在需要分片,但是设置DF标志,不向发送方传送ICMP消息
if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) {
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,//需要分片,但是设置DF标志,通知发送方不可达,原因是需要分片,并告知对方mtu
htonl(dst_pmtu(&rt->u.dst)));
kfree_skb(skb);
return -EMSGSIZE;
}

//可以快速分片的条件:
//	skb
//		1.skb的数据长度(主缓存区+frags缓存区)小于输出路径的mtu
//		2.skb的数据长度对齐到8字节的边界
//		3.skb没有被分片
//		4.skb没有被共享
//	skb->frag_list
//		1.长度小于(mtu-ip报头)
//		2.除最后一个分片外,长度都需要对齐到8字节边界
//		3.head-data之间的空间,可以容纳ip报头
//	注:skb->frag_list的skb,没有填充ip头,skb填充有ip头
hlen = iph->ihl * 4;//ip头长度
mtu = dst_pmtu(&rt->u.dst) - hlen;	//数据空间的大小

if (skb_shinfo(skb)->frag_list) {//frag_list存在skb
struct sk_buff *frag;
int first_len = skb_pagelen(skb);//skb主缓存区,frags片段中的数据长度,不包括frag_list中的skb

if (first_len - hlen > mtu ||//超过允许的最大数据量
((first_len - hlen) & 7) ||//数据长度没有对齐到8字节
(iph->frag_off & htons(IP_MF|IP_OFFSET)) ||//此skb是一个分片
skb_cloned(skb))//克隆一份skb,慢速分片
goto slow_path;
//检查frag_list中的skb是否可以快速分片
for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {
//frag_list中的skb没有ip头
if (frag->len > mtu ||
((frag->len & 7) && frag->next) ||//除最后一个分片外,其他分片的长度必须对其到8字节
skb_headroom(frag) < hlen)//头空间不够容纳ip报头(ip头+选项)
goto slow_path;

if (skb_shared(frag))//skb被共享
goto slow_path;
}
//快速路径:
err = 0;
offset = 0;
frag = skb_shinfo(skb)->frag_list;
skb_shinfo(skb)->frag_list = NULL;
//更新skb->data_len为skb->frags中数据的大小,原始skb->data_len包括frags,frag_list中所有数据的长度
skb->data_len = first_len - skb_headlen(skb);
skb->len = first_len;//更新总长度为主缓存区,frags中数据的大小,原始skb->len包括主缓存区,frags,frag_list中所有数据的长度
iph->tot_len = htons(first_len);//ip报头的数据包长度
iph->frag_off |= htons(IP_MF);//表示有更多的分片,第一个分片,offset=0
ip_send_check(iph);//计算ip校验和

for (;;) {
if (frag) {//处理skb->frag_list中的skb,为其准备分片的ip报头
frag->ip_summed = CHECKSUM_NONE;//表示校验和没有计算
frag->h.raw = frag->data;
frag->nh.raw = __skb_push(frag, hlen);//移动skb->data指针,填充ip报头和选项
memcpy(frag->nh.raw, iph, hlen);
iph = frag->nh.iph;
iph->tot_len = htons(frag->len);//总长度
ip_copy_metadata(frag, skb);//使分片skb与头skb有一样的出口设备,路由信息
if (offset == 0)//第一个分片具有完整的选项,其他分片将所有非copied的选项,均设置为NOOP
ip_options_fragment(frag);
offset += skb->len - hlen;//计算本分片的偏移量
iph->frag_off = htons(offset>>3);//偏移量对齐在8字节
if (frag->next != NULL)
iph->frag_off |= htons(IP_MF);//设置还有更多skb
ip_send_check(iph);//计算ip校验和
}

err = output(skb);//向下传递前一个skb,调用ip_finish_output

if (err || !frag)
break;

skb = frag;//保留指向前一个skb的指针
frag = skb->next;//frag为下一个待处理的skb
skb->next = NULL;//
}

if (err == 0) {
IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
return 0;
}
//快速路径分片出现错误,释放所有skb
while (frag) {
skb = frag->next;
kfree_skb(frag);
frag = skb;
}
IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
return err;
}

//慢速路径
slow_path:
left = skb->len - hlen;	//(主缓存区,frags,frag_list)数据的总大小(不包括ip头,选项)
ptr = raw + hlen;		//新分片的数据在原skb中的起始位置

offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;//当前分片的偏移量
not_last_frag = iph->frag_off & htons(IP_MF);//判断是否为最后一个分片

//开始进行分片
while(left > 0)	{
len = left;
if (len > mtu)//使用mtu(此处的mtu去掉ip报头和选项长度)
len = mtu;

if (len < left)	{
len &= ~7;//长度对齐到8字节边界
}
//分配新的skb,长度包括l2帧头,l3报头,l3有效载荷
if ((skb2 = alloc_skb(len+hlen+ll_rs, GFP_ATOMIC)) == NULL) {
NETDEBUG(printk(KERN_INFO "IP: frag: no memory for new fragment!\n"));
err = -ENOMEM;
goto fail;
}
//使所有分片都使用相同的路由信息,出口设备
ip_copy_metadata(skb2, skb);
skb_reserve(skb2, ll_rs);//预留l2帧头
skb_put(skb2, len + hlen);//data-tail之间空间大小为(ip报头+ip选项+ip有效载荷)
skb2->nh.raw = skb2->data;//设置l3报头起始地址
skb2->h.raw = skb2->data + hlen;//l3有效载荷
//设置新创建的skb所属的sock
if (skb->sk)
skb_set_owner_w(skb2, skb->sk);
//拷贝ip头,选项
memcpy(skb2->nh.raw, skb->data, hlen);
//将skb起始地址为ptr的len个字节拷贝到skb2中
//由skb_copy_bits处理frag,frag_list
if (skb_copy_bits(skb, ptr, skb2->h.raw, len))
BUG();
left -= len;//更新剩余待分片的数据量

iph = skb2->nh.iph;
iph->frag_off = htons((offset >> 3));//偏移量

//1.只有第一个分片需要完整的选项,其他分片将非copied的选项设置为NOOP
//2.由于第一个分片已经拷贝完整的ip报头以及选项到其分片中
//3.优化效率,在第一个分片拷贝完整的选项后,更新选项,非第一个分片都使用相同的ip选项
if (offset == 0)
ip_options_fragment(skb);

if (left > 0 || not_last_frag)
iph->frag_off |= htons(IP_MF);//还有跟多的分片
ptr += len;//下一个分片的数据在原skb中的起始位置
offset += len;//偏移量

IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);

iph->tot_len = htons(len + hlen);

ip_send_check(iph);

err = output(skb2);
if (err)
goto fail;
}
kfree_skb(skb);
IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
return err;

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

//调用路径 ip_fragment->ip_options_fragment
//	修改ip_option(skb->cb),将非copied类型的选项,均设置为NOOP类型
2.1 void ip_options_fragment(struct sk_buff * skb)
{
unsigned char * optptr = skb->nh.raw;
struct ip_options * opt = &(IPCB(skb)->opt);
int  l = opt->optlen;
int  optlen;

while (l > 0) {
switch (*optptr) {
case IPOPT_END:
return;
case IPOPT_NOOP:
l--;
optptr++;
continue;
}
optlen = optptr[1];
if (optlen<2 || optlen>l)
return;
if (!IPOPT_COPIED(*optptr))//在选项type的第8个比特,指出该选项是否应该复制到ip分片中
memset(optptr, IPOPT_NOOP, optlen);//对不需要拷贝到除第一个分片外的选项,设置为NOOP
l -= optlen;
optptr += optlen;
}
opt->ts = 0;//均设置为0,表明不需要time stamp,record route
opt->rr = 0;
opt->rr_needaddr = 0;
opt->ts_needaddr = 0;
opt->ts_needtime = 0;
return;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Linux ip 网络 kernel ip分片