tcp/ip 协议栈Linux内核源码分析九 IPv6分片ip6_fragment 分析
生活随笔
收集整理的這篇文章主要介紹了
tcp/ip 协议栈Linux内核源码分析九 IPv6分片ip6_fragment 分析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
內核版本:3.4.39
IPv6的分片流程和IPv4基本一致,這一點內核源碼作者也說了。流程比較簡單,分片的時候判斷是否滿足快速分片,滿足的話直接一個接一個加上分片擴展選項發送出去,不滿足的話就只能走慢速分片通道了,這時候需要重新分配每一個skb,然后從原始SKB報文那里復制數據發送出去。
int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *)) {struct sk_buff *frag;struct rt6_info *rt = (struct rt6_info*)skb_dst(skb);struct ipv6_pinfo *np = skb->sk ? inet6_sk(skb->sk) : NULL;struct ipv6hdr *tmp_hdr;struct frag_hdr *fh;unsigned int mtu, hlen, left, len;int hroom, troom;__be32 frag_id = 0;int ptr, offset = 0, err=0;u8 *prevhdr, nexthdr = 0;struct net *net = dev_net(skb_dst(skb)->dev);//獲取不能分片的擴展選項長度,不是每個擴展選項都能夠分片的hlen = ip6_find_1stfragopt(skb, &prevhdr);nexthdr = *prevhdr;//獲取mtu大小mtu = ip6_skb_dst_mtu(skb);/* We must not fragment if the socket is set to force MTU discovery* or if the skb it not generated by a local socket.*///檢查下是否允許分片if (!skb->local_df && skb->len > mtu) {skb->dev = skb_dst(skb)->dev;icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),IPSTATS_MIB_FRAGFAILS);kfree_skb(skb);return -EMSGSIZE;}//獲取mtu大小if (np && np->frag_size < mtu) {if (np->frag_size)mtu = np->frag_size;}//hlen表示每個分片報文都必須攜帶的擴展選項頭mtu -= hlen + sizeof(struct frag_hdr);//判斷是否存在分片隊列,存在的話說明應用層可以已經幫忙分片了,這時候只需要檢查//分片的長度是否合法,合法的話則可以使用快速分片。if (skb_has_frag_list(skb)) {int first_len = skb_pagelen(skb);struct sk_buff *frag2;//如果第一個報文長度大于MTU或者長度不是8字節的整數倍//克隆的報文也不能使用快速分片。if (first_len - hlen > mtu ||((first_len - hlen) & 7) ||skb_cloned(skb))goto slow_path;//檢查分片是否滿足快速分片要求skb_walk_frags(skb, frag) {/* Correct geometry. *///首先是長度不能大于MTU//此外除了最后一個報文其它報文長度必須是8字節整數倍//當然首部空間不夠的話也不行if (frag->len > mtu ||((frag->len & 7) && frag->next) ||skb_headroom(frag) < hlen)goto slow_path_clean;/* Partially cloned skb? */if (skb_shared(frag))goto slow_path_clean;BUG_ON(frag->sk);if (skb->sk) {frag->sk = skb->sk;frag->destructor = sock_wfree;}//報文要各自為戰了,所以從第一個報文那個分出來。//這么做是因為發送的時候需要將報文所占大小還給系統。//每個報文返還自己的。skb->truesize -= frag->truesize;}err = 0;offset = 0;//將分片從skb鏈表上掛載到frag上。frag = skb_shinfo(skb)->frag_list;skb_frag_list_init(skb);/* BUILD HEADER */*prevhdr = NEXTHDR_FRAGMENT;tmp_hdr = kmemdup(skb_network_header(skb), hlen, GFP_ATOMIC);if (!tmp_hdr) {IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),IPSTATS_MIB_FRAGFAILS);return -ENOMEM;}//追加一個分片頭__skb_pull(skb, hlen);fh = (struct frag_hdr*)__skb_push(skb, sizeof(struct frag_hdr));__skb_push(skb, hlen);skb_reset_network_header(skb);memcpy(skb_network_header(skb), tmp_hdr, hlen);//選擇一個報文ID標識ipv6_select_ident(fh, rt);//設置分片擴展選項fh->nexthdr = nexthdr;fh->reserved = 0;fh->frag_off = htons(IP6_MF);frag_id = fh->identification;//重新調整長度first_len = skb_pagelen(skb);skb->data_len = first_len - skb_headlen(skb);skb->len = first_len;ipv6_hdr(skb)->payload_len = htons(first_len -sizeof(struct ipv6hdr));//增加路由統計計數dst_hold(&rt->dst);for (;;) {/* Prepare header of the next frame,* before previous one went down. *///處理其它分片報文 if (frag) {frag->ip_summed = CHECKSUM_NONE;skb_reset_transport_header(frag);//添加分片擴展選項fh = (struct frag_hdr*)__skb_push(frag, sizeof(struct frag_hdr));__skb_push(frag, hlen);skb_reset_network_header(frag);memcpy(skb_network_header(frag), tmp_hdr,hlen);//調整選項頭 offset += skb->len - hlen - sizeof(struct frag_hdr);fh->nexthdr = nexthdr;fh->reserved = 0;fh->frag_off = htons(offset);if (frag->next != NULL)fh->frag_off |= htons(IP6_MF);fh->identification = frag_id;ipv6_hdr(frag)->payload_len =htons(frag->len -sizeof(struct ipv6hdr));ip6_copy_metadata(frag, skb);}//先將上一個報文發送出去//分片報文按照處理的順序發送出去err = output(skb);if(!err)IP6_INC_STATS(net, ip6_dst_idev(&rt->dst),IPSTATS_MIB_FRAGCREATES);if (err || !frag)break;skb = frag;frag = skb->next;skb->next = NULL;}kfree(tmp_hdr);//分片成功則返回if (err == 0) {IP6_INC_STATS(net, ip6_dst_idev(&rt->dst),IPSTATS_MIB_FRAGOKS);dst_release(&rt->dst);return 0;}//走到這里說明發送那里出現了錯誤,功虧一簣,剩下的報文原地解散回家去吧while (frag) {skb = frag->next;kfree_skb(frag);frag = skb;}//失敗的統計計數IP6_INC_STATS(net, ip6_dst_idev(&rt->dst),IPSTATS_MIB_FRAGFAILS);//釋放占用的路由指針 dst_release(&rt->dst);return err;slow_path_clean:skb_walk_frags(skb, frag2) {if (frag2 == frag)break;frag2->sk = NULL;frag2->destructor = NULL;skb->truesize += frag2->truesize;}}slow_path://慢速通道,需要重新分配skb//left表示分片報文數據部分總長度left = skb->len - hlen; /* Space per frame */ptr = hlen; /* Where to start from *//** Fragment the datagram.*/*prevhdr = NEXTHDR_FRAGMENT;//預留鏈路層頭部長度hroom = LL_RESERVED_SPACE(rt->dst.dev);troom = rt->dst.dev->needed_tailroom;/** Keep copying data until we run out.*/while(left > 0) {len = left;/* IF: it doesn't fit, use 'mtu' - the data space left *///分片長度最大時mtuif (len > mtu)len = mtu;/* IF: we are not sending up to and including the packet endthen align the next start on an eight byte boundary *///除了最后一個分片報文,其它報文長度必須是8字節整數倍 if (len < left) {len &= ~7;}/** Allocate buffer.*///分配一個SKB,果然是慢速,快速已經分配好了直接發送就可以了。if ((frag = alloc_skb(len + hlen + sizeof(struct frag_hdr) +hroom + troom, GFP_ATOMIC)) == NULL) {NETDEBUG(KERN_INFO "IPv6: frag: no memory for new fragment!\n");IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),IPSTATS_MIB_FRAGFAILS);err = -ENOMEM;goto fail;}/** Set up data on packet*///重新分配數據ip6_copy_metadata(frag, skb);skb_reserve(frag, hroom);skb_put(frag, len + hlen + sizeof(struct frag_hdr));skb_reset_network_header(frag);fh = (struct frag_hdr *)(skb_network_header(frag) + hlen);frag->transport_header = (frag->network_header + hlen +sizeof(struct frag_hdr));/** Charge the memory for the fragment to any owner* it might possess*/if (skb->sk)skb_set_owner_w(frag, skb->sk);/** Copy the packet header into the new buffer.*/skb_copy_from_linear_data(skb, skb_network_header(frag), hlen);/** Build fragment header.*///配置分片選項頭 fh->nexthdr = nexthdr;fh->reserved = 0;if (!frag_id) {ipv6_select_ident(fh, rt);frag_id = fh->identification;} elsefh->identification = frag_id;/** Copy a block of the IP datagram.*///復制其它數據 if (skb_copy_bits(skb, ptr, skb_transport_header(frag), len))BUG();left -= len;//設置分片選項頭fh->frag_off = htons(offset);if (left > 0)fh->frag_off |= htons(IP6_MF);ipv6_hdr(frag)->payload_len = htons(frag->len -sizeof(struct ipv6hdr));ptr += len;offset += len;/** Put this fragment into the sending queue.*///發送報文 err = output(frag);if (err)goto fail;IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),IPSTATS_MIB_FRAGCREATES);}IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),IPSTATS_MIB_FRAGOKS);kfree_skb(skb);return err;fail:IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),IPSTATS_MIB_FRAGFAILS);kfree_skb(skb);return err; }?
總結
以上是生活随笔為你收集整理的tcp/ip 协议栈Linux内核源码分析九 IPv6分片ip6_fragment 分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tcp/ip 协议栈Linux内核源码分
- 下一篇: tcp/ip 协议栈Linux内核源码分