ip_local_deliver的代码片段就清楚了:
//取出mf位和offset域,从而决定是否要组包。 if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) { if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER)) return 0; }
3 被本地函数所创建的buffer,简而言之也就是本地所要传输的数据包(还未加包头),但是需要被切片的。
而ip_fragment所必须处理下面几种情况:
1 一大块数据需要被分割为更小的部分。接下来来看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; //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; } //得到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;不管是slow还是fast 被切片的任何一个帧如果传输失败,ip_fragment都会立即返回一个错误给4层,并且紧跟着的帧也不会再被传输,然后将处理方法交给4层去做。
很容易一目了然。
static inline int skb_pagelen(const struct sk_buff *skb) { int i, len = 0; //我们知道如果设备支持S/G IO的话,nr_frags会包含一些L4 payload,因此我们需要先遍历nr_frags.然后加入它的长度。 for (i = (int)skb_shinfo(skb)->nr_frags - 1; i >= 0; i--) len += skb_shinfo(skb)->frags[i].size; //最后加上skb_headlen,而skb_headlen = skb->len - skb->data_len;因此这里就会返回这个数据包的len。 return len + skb_headlen(skb); }
/*通过上一篇blog我们知道,如果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; } } //通过上面的检测,都通过了,因此我们可以进行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 (;;) { //开始进行发送。 if (frag) { //设置校验位 frag->ip_summed = CHECKSUM_NONE; //设置相应的头部。 skb_reset_transport_header(frag); __skb_push(frag, hlen); skb_reset_network_header(frag); //复制ip头。 memcpy(skb_network_header(frag), iph, hlen); //修改每个切片的ip头的一些属性。 iph = ip_hdr(frag); iph->tot_len = htons(frag->len); //将当前skb的一些属性付给将要传递的切片好的帧。 ip_copy_metadata(frag, skb); if (offset == 0) //处理ip_option 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 fragmentation:
//切片开始的位移 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 */ //处理桥接的相关操作。 pad = nf_bridge_pad(skb); ll_rs = LL_RESERVED_SPACE_EXTRA(rt->u.dst.dev, pad); mtu -= pad; //其实也就是取出取出ip offset域。 offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3; //not_last_frag,顾名思义,其实也就是表明这个帧是否是最后一个切片。 not_last_frag = iph->frag_off & htons(IP_MF); //开始为循环处理,每一个切片创建一个skb buffer。 while (left > 0) { len = left; //如果len大于mtu,我们设置当前的将要切片的数据大小为mtu。 if (len > mtu) len = mtu; //长度也必须位对齐。 if (len < left) { len &= ~7; } //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; } //调用ip_copy_metadata复制一些相同的值的域。 ip_copy_metadata(skb2, skb); //进行skb的相关操作。为了加上ip头。 skb_reserve(skb2, ll_rs); skb_put(skb2, len + hlen); skb_reset_network_header(skb2); skb2->transport_header = skb2->network_header + hlen; //将每一个分片的ip包都关联到源包的socket上。 if (skb->sk) skb_set_owner_w(skb2, skb->sk); //开始填充新的ip包的数据。 //先拷贝包头。 skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen); //拷贝数据部分,这个函数实现的比较复杂。 if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len)) BUG(); left -= len; //填充相应的ip头。 iph = ip_hdr(skb2); iph->frag_off = htons((offset >> 3)); //第一个包,因此进行ip_option处理。 if (offset == 0) ip_options_fragment(skb); //不是最后一个包,因此设置mf位。 if (left > 0 || not_last_frag) iph->frag_off |= htons(IP_MF); //移动指针以及更改位移大小。 ptr += len; offset += len; //update包头的大小。 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); } kfree_skb(skb); IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS); return err;
static struct inet_frags ip4_frags; #define INETFRAGS_HASHSZ 64 struct inet_frags { struct hlist_head hash[INETFRAGS_HASHSZ]; rwlock_t lock; //随机值,它被用在计算hash值上面,下面会介绍到,过一段时间,内核就会更新这个值。 u32 rnd; int qsize; int secret_interval; struct timer_list secret_timer; //hash函数 unsigned int (*hashfn)(struct inet_frag_queue *); void (*constructor)(struct inet_frag_queue *q, void *arg); void (*destructor)(struct inet_frag_queue *); void (*skb_free)(struct sk_buff *); int (*match)(struct inet_frag_queue *q, void *arg); void (*frag_expire)(unsigned long data); }; struct ipq { struct inet_frag_queue q; u32 user; //都是ip头相关的一些域。 __be32 saddr; __be32 daddr; __be16 id; u8 protocol; int iif; unsigned int rid; struct inet_peer *peer; }; struct inet_frag_queue { struct hlist_node list; struct netns_frags *net; //基于LRU算法,主要用在GC上。 struct list_head lru_list; /* lru list member */ spinlock_t lock; atomic_t refcnt; //属于同一个源的数据包的定时器,当定时器到期,切片还没到达,此时就会drop掉所有的数据切片。 struct timer_list timer; /* when will this queue expire? */ //保存有所有的切片链表(从属于同一个ip包) struct sk_buff *fragments; /* list of received fragments */ ktime_t stamp; int len; /* total length of orig datagram */ //表示从源ip包已经接收的字节数。 int meat; //这个域主要可以设置为下面的3种值。 __u8 last_in; /* first/last segment arrived? */ //完成,第一个帧以及最后一个帧。 #define INET_FRAG_COMPLETE 4 #define INET_FRAG_FIRST_IN 2 #define INET_FRAG_LAST_IN 1 };
void inet_frags_init(struct inet_frags *f) { int i; for (i = 0; i < INETFRAGS_HASHSZ; i++) INIT_HLIST_HEAD(&f->hash[i]); rwlock_init(&f->lock); f->rnd = (u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6))); //安装定时器,当定时器到期就会调用inet_frag_secret_rebuild方法。 setup_timer(&f->secret_timer, inet_frag_secret_rebuild, (unsigned long)f); f->secret_timer.expires = jiffies + f->secret_interval; add_timer(&f->secret_timer); } static void inet_frag_secret_rebuild(unsigned long dummy) { ................................................ write_lock(&f->lock); //得到随机值 get_random_bytes(&f->rnd, sizeof(u32)); //然后通过这个随机值重新计算整个hash表的hash值。 for (i = 0; i < INETFRAGS_HASHSZ; i++) { struct inet_frag_queue *q; struct hlist_node *p, *n; hlist_for_each_entry_safe(q, p, n, &f->hash[i], list) { unsigned int hval = f->hashfn(q); if (hval != i) { hlist_del(&q->list); /* Relink to new hash chain. */ hlist_add_head(&q->list, &f->hash[hval]); } } } .............................................. }