linux内核 快速分片,linux內核學習筆記------ip報文的分片

對網絡比較熟悉的童鞋都知道,當發送的ip報文長度超出了最大的傳輸單位MTU,且允許分片的情況下,就會對ip報文進行分片。在上層要發送數據時就會調用dst_output,dst_output就會調用ip_output,而ip_output就會調用ip_finish_output,在ip_finish_output把數據發送出去之前就會判斷該報文是否進行分片。

static int ip_finish_output(struct sk_buff *skb)

{

if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))

return ip_fragment(skb, ip_finish_output2);

else

return ip_finish_output2(skb);

}從源碼中可以看出,當報文的長度大於mtu,gso的長度不為0就會調用ip_fragment進行分片。否則就會調用ip_finish_output2把數據發送出去。

ip分片目前有兩種分片方式:1、快速分片;2、慢速分片。在快速分片中,將數據分割成片段已經由傳輸層完成,三層只需將這寫片段組成ip分片;而慢速分片則需要完成全部的工作,即對一個完整的ip數據報根據mtu值循環進行分片,直至完成。整個分片工作都在ip_fragment中完成。

int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))

{

.......

struct rtable *rt = skb_rtable(skb);

int err = 0;

dev = rt->u.dst.dev;

......

/*

* 如果待分片IP數據包禁止分片,則調用

* icmp_send()向發送方發送一個原因為需要

* 分片而設置了不分片標志的目的不可達

* ICMP報文,並丟棄報文,即設置IP狀態

* 為分片失敗,釋放skb,返回消息過長

* 錯誤碼。

*/

if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) {

IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);

icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,

htonl(ip_skb_dst_mtu(skb)));

kfree_skb(skb);

return -EMSGSIZE;

}

hlen = iph->ihl * 4;

mtu = dst_mtu(&rt->u.dst) - hlen;/* Size of data space */

/*

* 在分片之前先給IP數據包的控制塊設置

* IPSKB_FRAG_COMPLETE標志,標識完成分片。

*/

IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE;

if (skb_has_frags(skb)) {

/*

* 獲得此IP數據包第一個分片長度,包括SG類型

* 聚合分散I/O數據區中的數據。

*/

int first_len = skb_pagelen(skb);

if (first_len - hlen > mtu ||

((first_len - hlen) & 7) ||

(iph->frag_off & htons(IP_MF|IP_OFFSET)) ||

skb_cloned(skb))

goto slow_path;

skb_walk_frags(skb, frag) {

if (frag->len > mtu ||

((frag->len & 7) && frag->next) ||

skb_headroom(frag) < hlen)

goto slow_path;

if (skb_shared(frag))

goto slow_path;

if (skb->sk) {

frag->sk = skb->sk;

frag->destructor = sock_wfree;

truesizes += frag->truesize;

}

......

frag = skb_shinfo(skb)->frag_list;

skb_frag_list_init(skb);

......

for (;;) {

if (frag) {

/*

* 設置后一個分片skb中指向三層和四層首部

* 的指針。

*/

skb_reset_transport_header(frag);

__skb_push(frag, hlen);

skb_reset_network_header(frag);

/*

* 將當前分片的IP首部復制給后一個分片,

* 並修改后一個分片IP首部的總長度字段。

*/

memcpy(skb_network_header(frag), iph, hlen);

iph = ip_hdr(frag);

iph->tot_len = htons(frag->len);

/*

* 根據當前分片的skb填充后一個分片

* skb中的參數。

*/

ip_copy_metadata(frag, skb);

/*

* 如果是在處理第一個分片,則調用ip_options_fragment()

* 將第二個分片skb中無需復制到每個分片的IP選項都

* 填充為IPOPT_NOOP,此后所有的分片選項部分都簡單

* 地復制上一個的即可。

*/

if (offset == 0)

ip_options_fragment(frag);

......

offset += skb->len - hlen;

iph->frag_off = htons(offset>>3);

if (frag->next != NULL)

iph->frag_off |= htons(IP_MF);

/* Ready, complete checksum */

ip_send_check(iph);

err = output(skb);

skb = frag;

frag = skb->next;

skb->next = NULL;快速分片和慢速分片主要通過skb_has_frags這個來判斷,也就是判斷該數據的第一個skb中的frag_list是否為空,如果為空就是需要進行慢速分片,否則傳輸層已經為快速分片做好了准備。上面的代碼大部分都有注釋,需要注意一種情況

1、要進行快速分片還需要對傳輸層傳遞的所有的skb進行判斷:

有分片長度大於mtu

除最后一個分片外還有分片長度未與8字節對其

ip首部中的MF或片偏移不為0,說明不是一個完整的ip報文

此skb被克隆 上述四種情況是不能進行ip分片的。上面是快速分片;

當不能進行快速分片時就會轉到慢速分片,慢速分片其實就需要對skb數據進行復制,而快速分片就不需要此操作。

slow_path:

/*

* 獲取待分片的IP數據包的數據長度,此處減去hlen是

* 為二層首部留出空間。

*/

left = skb->len - hlen;/* Space per frame */

/*

* 獲取IP數據包中數據區指針

*/

ptr = raw + hlen;/* Where to start from */

/* for bridged IP traffic encapsulated inside f.e. a vlan header,

* we need to make room for the encapsulating header

*/

/*

* 如果是橋轉發基於VLAN的IP數據包,則需

* 獲得VLAN首部長度,在后面分配skb

* 緩沖區時留下相應的空間,同時還需

* 修改MTU值。

*/

pad = nf_bridge_pad(skb);

/*

* 獲得IP首部中的片偏移值,即每個分片

* 起始處在原始數據包中位置,該值是

* 13位的,因此要乘8.

*/

offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;

/*

* 取MF位值,MF值除最后一個分片外

* 都應該置為1,表示該分片之后還

* 有分片。

*/

not_last_frag = iph->frag_off & htons(IP_MF);

/*

* 循環對left長度的數據進行分片,為

* 每一個分片創建一個新的SKB。

*/

while (left > 0) {

len = left;

/* IF: it doesn't fit, use 'mtu' - the data space left */

/*

* 如果剩余數據的長度大於MTU,則以MTU為

* 分片長度進行分片;否則就以剩余數據

* 的長度作為分片長度,顯然后一種情況

* 只會出現在最后一個分片。

*/

if (len > mtu)

len = mtu;

/* IF: we are not sending upto and including the packet end

then align the next start on an eight byte boundary */

/*

* 除非是最后一個分節,否則分片不包括IP

* 首部的數據部分,需8字節對齊。

*/

if (len < left){

len &= ~7;

}

/*

*Allocate buffer.

*/

/*

* 為分片分配一個SKB,其長度為分片長、

* IP首部長,以及二層首部長之和。

*/

if ((skb2 = alloc_skb(len+hlen+ll_rs, GFP_ATOMIC)) == NULL) {

NETDEBUG(KERN_INFO "IP: frag: no memory for new fragment!\n");

err = -ENOMEM;

goto fail;

}

......

/*

* 復制分片數據,並更新原始數據包剩余未分片數據量。

* 此處調用了skb_copy_bits(),是因為skb中的數據存儲有多種

* 可能性,而skb_copy_bits可以處理這些細節。

*/

if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len))

/*

* 設置分片的片偏移字段,對於第一個分片,

* 該值即原始IP數據包的片偏移字段值。

*/

iph = ip_hdr(skb2);

iph->frag_off = htons((offset >> 3));

if (offset == 0)

ip_options_fragment(skb);

* 如果不是最后一個分節,則設置IP首部中

* 標識字段的MF位。

*/

if (left > 0 || not_last_frag)

iph->frag_off |= htons(IP_MF);

/*

* 更新后一個分節在整個原始數據包中的偏移量,

* 以及后一個分片在當前被分片數據包中的偏移量。

* 這兩個偏移量是有區別的,因為一個數據包在

* 傳輸過程中可能被多次分片,因此當前被分片

* 數據包也由可能是另外一個數據包的分片。

*/

ptr += len;

offset += len;

/*

*Put this fragment into the sending queue.

*/

/*

* 設置分片IP首部中總長度字段。

*/

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

......

上述代碼也是有注釋的,只提示兩點:

1、分片的片偏移

分段偏移用於指明分段起始點相對報文起始點的偏移,長度為13位,以8個位組為單位。若MTU=1500時,一個大小為3000字節的數據經過該接口,會被分為端傳輸:

第一段長度為1480+20,第二段為1480,第三段為40,那么第一段分段的偏移為0,第二段為1480/8,第三段為185+185,所以在源碼中需要乘以8,而在設置ip首部片偏移時又除以8的原因

2、ip選項的處理要注意,有的ip選項需要體現在所有的分片中,而有的不需要。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值