tcp/ip 协议栈Linux源码分析四 IPv4分片 ip_fragment函数分析
生活随笔
收集整理的這篇文章主要介紹了
tcp/ip 协议栈Linux源码分析四 IPv4分片 ip_fragment函数分析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
內核版本:3.4.39
很多項目涉及到IP分片的時候都是繞過去了,感覺分片挺難的。但是老這么做也不行啊,抽空分析了內核的分片處理函數ip_fragment,也不是特別復雜,感覺挺簡單的,看來事情只有實際去做才知道。
int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *)) {struct iphdr *iph;int ptr;struct net_device *dev;struct sk_buff *skb2;unsigned int mtu, hlen, left, len, ll_rs;int offset;__be16 not_last_frag;struct rtable *rt = skb_rtable(skb);int err = 0;/* 獲取路由里面的出口設備 */dev = rt->dst.dev;/* 獲取IP頭指針 */iph = ip_hdr(skb);/* 如果數據包攜帶不分片標志并且本地開啟了pmtu發現(local_df==0),則需要* 給發送方返回一個icmp不可達報文。 */ 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;}/** Setup starting values.*//* 獲取IP首部長度 */hlen = iph->ihl * 4;/* 獲取分片報文數據部分大小(mtu 最大傳輸單元) */mtu = dst_mtu(&rt->dst) - hlen; /* Size of data space */#ifdef CONFIG_BRIDGE_NETFILTERif (skb->nf_bridge)mtu -= nf_bridge_mtu_reduction(skb); #endif/* 設置標志位,ip_defrag重組函數會去判斷這個標志位 */ IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE;/* When frag_list is given, use it. First, check its validity:* some transformers could create wrong frag_list or break existing* one, it is not prohibited. In this case fall back to copying.** LATER: this step can be merged to real generation of fragments,* we can switch to copy when see the first bad fragment.*//* 傳輸層如果已經進行了分片 */ if (skb_has_frag_list(skb)) {struct sk_buff *frag, *frag2;/* 同一個頁面內的報文長度,不包括其它分片 */int first_len = skb_pagelen(skb);/** 以下四種情況是不能進行快速分片的* 1. 第一個報文長度大于mtu。* 2. 第一個報文長度不是8的倍數。* 3. 該報文自身就是分片報文,需要走慢速通道。* 4. 該報文是克隆的,因為快速分片是直接修改skb的,如果是克隆的* 則在其它地方存在引用,因此不能直接修改。*/if (first_len - hlen > mtu ||((first_len - hlen) & 7) ||ip_is_fragment(iph) ||skb_cloned(skb))goto slow_path;/* 判斷其它分片是否滿足快速分片要求 */skb_walk_frags(skb, frag) {/* 如果SKB分片超過mtu則進入慢速分片。* 如果SKB分片不是8的倍數并且不是最后一個分片也走慢速通道。* 如果SKB分片頭部空間無法塞下一個IP頭* 符合上述情況就進入慢速通道,和上面的慢速通道略有區別*/if (frag->len > mtu ||((frag->len & 7) && frag->next) ||skb_headroom(frag) < hlen)goto slow_path_clean;/* 如果某個地方在引用skb結構體* 就進入慢速通道。*/if (skb_shared(frag))goto slow_path_clean;/* 這里我有點疑問,為啥分片的skb不能有sk */BUG_ON(frag->sk);if (skb->sk) {/* 將所有片段都關聯到同一個套接字* 設置套接字的回調函數*/frag->sk = skb->sk;frag->destructor = sock_wfree;}/* truesize 是skb的總長度,包括skb結構體和數據部分大小 * 這里將其長度從skb中移除,相當于分離開來*/skb->truesize -= frag->truesize;}/* Everything is OK. Generate! */err = 0;offset = 0;/* 保存那些獨立分片 */frag = skb_shinfo(skb)->frag_list;/* 清空首個報文的分片指針,該指針已經保存在frag里 */skb_frag_list_init(skb);/* 第一個報文非線性數據區長度 */skb->data_len = first_len - skb_headlen(skb);/* 設置skb數據長度,包括線性區和非線性區 */skb->len = first_len;/* 設置ip頭域里面數據長度 */iph->tot_len = htons(first_len);/* 設置MF標志位 */iph->frag_off = htons(IP_MF);/* 設置IP頭域校驗和 */ip_send_check(iph);/* 準備其它分片 */for (;;) {if (frag) {frag->ip_summed = CHECKSUM_NONE;/* 設置傳輸層首部指針 */skb_reset_transport_header(frag);/* 插入IP首部長度 */ __skb_push(frag, hlen);/* 設置網絡層首部指針 */skb_reset_network_header(frag);/* 復制IP頭部,因為是后續分片,所以要設置* 偏移值,長度和分片標志位,當然,校驗和要重新計算。*/memcpy(skb_network_header(frag), iph, hlen);iph = ip_hdr(frag);iph->tot_len = htons(frag->len);/* 復制第一個分片報文的標志位,包括優先級,出口設備* netfilter子模塊使用到的標志位等等。* 所有報文設置相同的屬性*/ip_copy_metadata(frag, skb);/* 處理IP擴展選項,分片情況下擴展選項處理還是比較特殊的* 有些選項要求所有分片報文都要攜帶,有些選項只需要首個分片報文攜帶。* 詳細情況參考RFC791*/if (offset == 0)ip_options_fragment(frag);/* 分片偏移值,等于之前報文長度累積和 */ /* 這里已經假定之前的報文長度都是一樣的 */offset += skb->len - hlen;/* 一定能被8整除? *//* 沒錯,畢竟大小是之前分片的總和 */iph->frag_off = htons(offset>>3);/* 如果不是最后一個分片,就設置MF標識 */if (frag->next != NULL)iph->frag_off |= htons(IP_MF);/* Ready, complete checksum *//* 重新計算校驗和 */ip_send_check(iph);}/* 調用發送接口發送出去 */err = output(skb);if (!err)IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);/* 如果出錯并且沒有多余分片 */if (err || !frag)break;/* 當前skb傳輸完成,指向下一個分片 */skb = frag;frag = skb->next;skb->next = NULL;}/* 傳輸正常,增加MIB統計計數 */if (err == 0) {IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);return 0;}/* 當傳輸結束后,釋放skb */while (frag) {skb = frag->next;kfree_skb(frag);frag = skb;}IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);return err;slow_path_clean:/* 這一步到時沒有很好理解⊙﹏⊙‖∣,了解的大??梢哉f下 */skb_walk_frags(skb, frag2) {if (frag2 == frag)break; frag2->sk = NULL;frag2->destructor = NULL;skb->truesize += frag2->truesize;}}/* 慢速分片 */ slow_path:/* 先計算純數據總長度,這個長度不包括IP頭長 */left = skb->len - hlen; /* Space per frame *//* 指向數據的起始位置 */ptr = 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*//* (鏈路層預留空間) */ ll_rs = LL_RESERVED_SPACE_EXTRA(rt->dst.dev, nf_bridge_pad(skb));/** Fragment the datagram.*//* 獲取偏移值,不從0開始是因為有可能需要分片的報文自身就是一個分片報文 */ offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;/* 可能的取值包括0和1 */not_last_frag = iph->frag_off & htons(IP_MF);/* 進行分片處理,left的初值就是數據總長 */while (left > 0) {len = left;/* 確保每個報文長度不超過MTU */if (len > mtu)len = mtu;/* 確保分片報文大小長度為8字節的整數倍,最后一個分片除外 */ if (len < left) {len &= ~7;}/* 分配一個新的skb buffer* 數據長度+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;}/* 復制一些公共數據 */ip_copy_metadata(skb2, skb);/* 預留鏈路層首部空間 */skb_reserve(skb2, ll_rs);/* 插入數據部分和IP頭 */skb_put(skb2, len + hlen);/* 重置網絡頭指針 */skb_reset_network_header(skb2);/* 設置傳輸層頭部指針 */skb2->transport_header = skb2->network_header + hlen;/** 將每一個分片的skb包都關聯到源包的socket */if (skb->sk)skb_set_owner_w(skb2, skb->sk);/** Copy the packet header into the new buffer.*//* 復制IP報頭 */skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen);/** Copy a block of the IP datagram.* 從原始報文中的ptr起復制長度為len的數據到skb2中*/if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len))BUG();/* 從總長度中減去這個分片的長度,得到剩余部分的長度 */ left -= len;/** 接下來就是設置分片報文的網絡層首部*/iph = ip_hdr(skb2);/* 設置片偏移 */iph->frag_off = htons((offset >> 3));/* ANK: dirty, but effective trick. Upgrade options only if* the segment to be fragmented was THE FIRST (otherwise,* options are already fixed) and make it ONCE* on the initial skb, so that all the following fragments* will inherit fixed options.*//* 處理IP選項,對于分片報文來說有些選項是需要每個報文必須攜帶,* 有些選項只需第一個分片報文攜帶就可以了。具體的操作可以參考RFC791*/if (offset == 0)ip_options_fragment(skb);/** Added AC : If we are fragmenting a fragment that's not the* last fragment then keep MF on each bit*//* 設置分片標識位 * 剛才提到not_last_frag可以是0或者1,取決于它在原始分片報文的值*/ if (left > 0 || not_last_frag)iph->frag_off |= htons(IP_MF);/* 更新數據指針,指向下一個分片報文的起始復制位置 */ptr += len;/* 更新片偏移字段 */offset += len;/* 更新長度字段 */iph->tot_len = htons(len + hlen);/* 更新校驗和字段 */ip_send_check(iph);/* 配置完成發送 */err = output(skb2);if (err)goto fail;IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);}/* 分片結束,釋放原始報文* 這個和快速分片不同,快速分片是發送原始分片* 慢速分片是新建skb然后復制發送*/kfree_skb(skb);IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);return err;fail:kfree_skb(skb);IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);return err; } EXPORT_SYMBOL(ip_fragment);?
參考文檔:
1.?linux內核ip分片函數ip_fragment解析??https://blog.csdn.net/force_eagle/article/details/4555314
2. SKB結構體分析?http://vger.kernel.org/~davem/skb.html
總結
以上是生活随笔為你收集整理的tcp/ip 协议栈Linux源码分析四 IPv4分片 ip_fragment函数分析的全部內容,希望文章能夠幫你解決所遇到的問題。