網絡子係統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