mmdev 发表于 2013-1-17 02:48:21

linux内核ip分片函数ip_fragment解析

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, pad;int offset;__be16 not_last_frag;/* 取得路由表 */struct rtable *rt = skb->rtable;int err = 0;/* 网络设备 */dev = rt->u.dst.dev;/* *Point into the IP datagram header. *//* 取得ip头 */iph = ip_hdr(skb);/* * 判断DF位,知道如果df位被设置了话就表示不要被分片, * 这时ip_fragment将会发送一个icmp报文返回到源主机。这里 * 主要是为forward数据所判断。*/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的大小。这里要注意,他的大小减去了hlen,也就是ip头的大小 */mtu = dst_mtu(&rt->u.dst) - hlen;/* Size of data space */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. *//** 如果4层将数据包分片了,那么就会把这些数据包放到skb的frag_list链表中,* 因此这里首先先判断frag_list链表是否为空,为空的话将会进行slow 分片*/if (skb_shinfo(skb)->frag_list) {struct sk_buff *frag;/* * 取得第一个数据报的len.当sk_write_queue队列被flush后, * 除了第一个切好包的另外的包都会加入到frag_list中,而这里 * 需要得到的第一个包(也就是本身这个sk_buff)的长度。 */int first_len = skb_pagelen(skb);int truesizes = 0;/* * 接下来的判断都是为了确定能进行fast分片。分片不能被共享, * 这是因为在fast path 中,需要加给每个分片不同的ip头(而并 * 不会复制每个分片)。因此在fast path中是不可接受的。而在 * slow path中,就算有共享也无所谓,因为他会复制每一个分片, * 使用一个新的buff。    *//* * 判断第一个包长度是否符合一些限制(包括mtu,mf位等一些限制). * 如果第一个数据报的len没有包含mtu的大小这里之所以要把第一个 * 切好片的数据包单独拿出来检测,是因为一些域是第一个包所独有 * 的(比如IP_MF要为1)。这里由于这个mtu是不包括hlen的mtu,因此 * 需要减去一个hlen。   */if (first_len - hlen > mtu ||    ((first_len - hlen) & 7) ||    (iph->frag_off & htons(IP_MF|IP_OFFSET)) ||    skb_cloned(skb))goto slow_path;/* 遍历剩余的frag */for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {/* Correct geometry. *//* * 判断每个帧的mtu,以及相关的东西,如果不符合条件则要 * 进行slow path,基本和上面的第一个skb的判断类似 */if (frag->len > mtu ||    ((frag->len & 7) && frag->next) ||    skb_headroom(frag) < hlen)    goto slow_path;/* Partially cloned skb? *//* 判断是否共享 */if (skb_shared(frag))goto slow_path;BUG_ON(frag->sk);/* 进行socket的一些操作 */if (skb->sk) {sock_hold(skb->sk);frag->sk = skb->sk;frag->destructor = sock_wfree;truesizes += frag->truesize;}}/* Everything is OK. Generate! *//* 通过上面的检测,都通过了,因此可以进行fast path分片了 */err = 0;offset = 0;/* 取得frag_list列表*/frag = skb_shinfo(skb)->frag_list;skb_shinfo(skb)->frag_list = NULL;/* 得到数据(不包括头)的大小 */skb->data_len = first_len - skb_headlen(skb);skb->truesize -= truesizes;skb->len = first_len;iph->tot_len = htons(first_len);/* 设置mf位*/iph->frag_off = htons(IP_MF);ip_send_check(iph);/* 开始进行发送 */for (;;) {/* Prepare header of the next frame, * before previous one went down. */if (frag) {frag->ip_summed = CHECKSUM_NONE;/* 设置相应的头部 */skb_reset_transport_header(frag);/* 预留ddos header 空间 */// hlen += DDOS_HDR_LEN;__skb_push(frag, hlen);skb_reset_network_header(frag);// hlen -= DDOS_HDR_LEN;/* 复制ip头 */memcpy(skb_network_header(frag), iph, hlen);iph = ip_hdr(frag);iph->tot_len = htons(frag->len);/* 将当前skb的一些属性付给将要传递的分片好的帧 */ip_copy_metadata(frag, skb);/* 处理ip_option*/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);if (!err)IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);if (err || !frag)break;/* 处理链表中下一个buf */skb = frag;frag = skb->next;skb->next = NULL;}if (err == 0) {IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);return 0;}/* 释放内存 */while (frag) {skb = frag->next;kfree_skb(frag);frag = skb;}IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);return err;}slow_path:/* 分片的数据剩余长度 */left = skb->len - hlen;/* Space per frame *//* 而ptr就是分片开始的数据指针 */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、PPPOE相关MTU */pad = nf_bridge_pad(skb);ll_rs = LL_RESERVED_SPACE_EXTRA(rt->u.dst.dev, pad);mtu -= pad;/* *Fragment the datagram. *//* 取出ip offset域 */offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;/* not_last_frag表明这个帧是否是最后一个分片 */not_last_frag = iph->frag_off & htons(IP_MF);/* *Keep copying data until we run out. *//* 开始为循环处理,每一个分片创建一个skb buffer */while (left > 0) {len = left;/* IF: it doesn't fit, use 'mtu' - the data space left *//* 如果len大于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 *//* 长度对齐 */if (len < left){len &= ~7;}/* *Allocate buffer. *//* malloc一个新的buff,它的大小包括ip payload,ip head,以及L2 head */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;}/* *Set up data on packet *//* 调用ip_copy_metadata复制一些相同的值的域 */ip_copy_metadata(skb2, skb);/* 保留L2 header空间 */skb_reserve(skb2, ll_rs);/* 设置ip header & ddos header & ip paylod 空间 */skb_put(skb2, len + hlen);skb_reset_network_header(skb2);/* L4 header指针为ip header + ddos header数据偏移位置,用于复制原始payload */skb2->transport_header = skb2->network_header + hlen;/* *Charge the memory for the fragment to any owner *it might possess *//* 将每一个分片的ip包都关联到源包的socket */if (skb->sk)skb_set_owner_w(skb2, skb->sk);/* *Copy the packet header into the new buffer. *//* 拷贝ip header */skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen);/* *Copy a block of the IP datagram. *//* 拷贝ip payload数据 */if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len))BUG();/* 分片的数据剩余长度 */left -= len;/* *Fill in the new header fields. *//* 填充相应的ip头 */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_option处理 */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 *//* 不是最后一个包,因此设置mf位 */if (left > 0 || not_last_frag)iph->frag_off |= htons(IP_MF);/* 移动数据指针以及更改数据偏移 */ptr += len;offset += len;/* *Put this fragment into the sending queue. *//* 增加ddos header 长度 */// hlen += DDOS_HDR_LEN;/* 更新包头的数据长度 */iph->tot_len = htons(len + hlen);/* 重新计算校验 */ip_send_check(iph);/* 复位ip header大小 */// hlen -= DDOS_HDR_LEN;err = output(skb2);if (err)goto fail;IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);}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;}
页: [1]
查看完整版本: linux内核ip分片函数ip_fragment解析