網絡子係統51_ip協議報文分片
//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;
}
最後更新:2017-04-03 14:53:40