`

关于skb_make_writable()函数

阅读更多
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

1. 前言

由于2.6.1*内核netfilter架构重组IP包后不进行线性化操作,所以不能直接用skb中的协议头获取各协议字段头信息,必须用skb_header_pointer()函数来获取。同样,在进行NAT操作时,对数据的修改也不能直接修改,必须采用新函数预先进行处理,使 skb包可写,实现该功能的函数为skb_make_writable()。

以下内核代码版本为2.6.17.11。

2. skb_make_writable函数

/* net/netfilter/core.c */
int skb_make_writable(struct sk_buff **pskb, unsigned int writable_len)
{
 struct sk_buff *nskb;
// 检查要写入缓冲区大小是否大于数据包大小,防止溢出
 if (writable_len > (*pskb)->len)
  return 0;
// 数据包属于共享或克隆的,数据内部部分是被多个skb包共享的,需要
// 拷贝出一个新的skb包
 /* Not exclusive use of packet?  Must copy. */
 if (skb_shared(*pskb) || skb_cloned(*pskb))
  goto copy_skb;
// 这是个独立的包
 return pskb_may_pull(*pskb, writable_len);
copy_skb:
 nskb = skb_copy(*pskb, GFP_ATOMIC);
 if (!nskb)
  return 0;
// 拷贝出来的新包肯定都应该是线性化的了
 BUG_ON(skb_is_nonlinear(nskb));
 /* Rest of kernel will get very unhappy if we pass it a
    suddenly-orphaned skbuff */
// 设置一下新包的sock参数
 if ((*pskb)->sk)
  skb_set_owner_w(nskb, (*pskb)->sk);
// 释放老包
 kfree_skb(*pskb);
 *pskb = nskb;
 return 1;
}
 
pskb_may_pull()函数定义为:

/* include/linux/skbuff.h */

static inline int pskb_may_pull(struct sk_buff *skb, unsigned int len)
{
// 在绝大多数情况都满足的,分别用likely(),unlikely()进行优化
 if (likely(len <= skb_headlen(skb)))
  return 1;
 if (unlikely(len > skb->len))
  return 0;
// 这在很少情况下才会到达这里,主要是碎片重组包
 return __pskb_pull_tail(skb, len-skb_headlen(skb)) != NULL;
}

真正需要调整时进入__pskb_pull_tail()进行补全数据包的页外数据,把碎片部分
的数据拷贝为线性:

/* net/core/skbuff.c */
/* Moves tail of skb head forward, copying data from fragmented part,
 * when it is necessary.
 * 1. It may fail due to malloc failure.
 * 2. It may change skb pointers.
 *
 * It is pretty complicated. Luckily, it is called only in exceptional cases.
 */
unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta)
{
 /* If skb has not enough free space at tail, get new one
  * plus 128 bytes for future expansions. If we have enough
  * room at tail, reallocate without expansion only if skb is cloned.
  */
 int i, k, eat = (skb->tail + delta) - skb->end;
// 检查是否有必要扩展当前skb页面空间
 if (eat > 0 || skb_cloned(skb)) {
  if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0,
         GFP_ATOMIC))
   return NULL;
 }
// 将当前页外的delta大小的数据拷贝到skb缓冲区尾部空间
 if (skb_copy_bits(skb, skb_headlen(skb), skb->tail, delta))
  BUG();
 /* Optimization: no fragments, no reasons to preestimate
  * size of pulled pages. Superb.
  */
// 没碎片的话直接跳转
 if (!skb_shinfo(skb)->frag_list)
  goto pull_pages;
// 后面的操作都是在要腾出delta大小的线性缓冲区,腾出空间后就可以拉长skb包页面
 /* Estimate size of pulled pages. */
 eat = delta;
 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  if (skb_shinfo(skb)->frags[i].size >= eat)
   goto pull_pages;
  eat -= skb_shinfo(skb)->frags[i].size;
 }
 /* If we need update frag list, we are in troubles.
  * Certainly, it possible to add an offset to skb data,
  * but taking into account that pulling is expected to
  * be very rare operation, it is worth to fight against
  * further bloating skb head and crucify ourselves here instead.
  * Pure masohism, indeed. 8)8)
  */
 if (eat) {
  struct sk_buff *list = skb_shinfo(skb)->frag_list;
  struct sk_buff *clone = NULL;
  struct sk_buff *insp = NULL;
  do {
   BUG_ON(!list);
   if (list->len <= eat) {
    /* Eaten as whole. */
    eat -= list->len;
    list = list->next;
    insp = list;
   } else {
    /* Eaten partially. */
    if (skb_shared(list)) {
     /* Sucks! We need to fork list. :-( */
     clone = skb_clone(list, GFP_ATOMIC);
     if (!clone)
      return NULL;
     insp = list->next;
     list = clone;
    } else {
     /* This may be pulled without
      * problems. */
     insp = list;
    }
    if (!pskb_pull(list, eat)) {
     if (clone)
      kfree_skb(clone);
     return NULL;
    }
    break;
   }
  } while (eat);
  /* Free pulled out fragments. */
  while ((list = skb_shinfo(skb)->frag_list) != insp) {
   skb_shinfo(skb)->frag_list = list->next;
   kfree_skb(list);
  }
  /* And insert new clone at head. */
  if (clone) {
   clone->next = list;
   skb_shinfo(skb)->frag_list = clone;
  }
 }
 /* Success! Now we may commit changes to skb data. */
// 拉长页面
pull_pages:
 eat = delta;
 k = 0;
 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  if (skb_shinfo(skb)->frags[i].size <= eat) {
   put_page(skb_shinfo(skb)->frags[i].page);
   eat -= skb_shinfo(skb)->frags[i].size;
  } else {
   skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i];
   if (eat) {
    skb_shinfo(skb)->frags[k].page_offset += eat;
    skb_shinfo(skb)->frags[k].size -= eat;
    eat = 0;
   }
   k++;
  }
 }
 skb_shinfo(skb)->nr_frags = k;
// skb数据尾指针拉长delta,同时非页面内的数据长度减少delta
 skb->tail     += delta;
 skb->data_len -= delta;
 return skb->tail;
}

3. 应用位置

skb_make_writable()函数用在各个IP上层协议的的manip_pkt()函数、用于修改数据包内容的ip_nat_mangle_tcp_packet()、ip_nat_mangle_udp_packet()等函数中,一进入函数中使用。

4. 结论

对于IP上层协议的NAT代码,从2.4移植到2.6后要直接调用skb_make_writable()函数,代码属于需要修改的;而多连接协议的NAT处理修改内容部分的数据时,由于都是直接调用ip_nat_mangle_tcp_packet()、 ip_nat_mangle_udp_packet()等函数,因此不用考虑这方面的问题。
 
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

1. 前言

由于2.6.1*内核netfilter架构重组IP包后不进行线性化操作,所以不能直接用skb中的协议头获取各协议字段头信息,必须用skb_header_pointer()函数来获取。同样,在进行NAT操作时,对数据的修改也不能直接修改,必须采用新函数预先进行处理,使 skb包可写,实现该功能的函数为skb_make_writable()。

以下内核代码版本为2.6.17.11。

2. skb_make_writable函数

/* net/netfilter/core.c */
int skb_make_writable(struct sk_buff **pskb, unsigned int writable_len)
{
 struct sk_buff *nskb;
// 检查要写入缓冲区大小是否大于数据包大小,防止溢出
 if (writable_len > (*pskb)->len)
  return 0;
// 数据包属于共享或克隆的,数据内部部分是被多个skb包共享的,需要
// 拷贝出一个新的skb包
 /* Not exclusive use of packet?  Must copy. */
 if (skb_shared(*pskb) || skb_cloned(*pskb))
  goto copy_skb;
// 这是个独立的包
 return pskb_may_pull(*pskb, writable_len);
copy_skb:
 nskb = skb_copy(*pskb, GFP_ATOMIC);
 if (!nskb)
  return 0;
// 拷贝出来的新包肯定都应该是线性化的了
 BUG_ON(skb_is_nonlinear(nskb));
 /* Rest of kernel will get very unhappy if we pass it a
    suddenly-orphaned skbuff */
// 设置一下新包的sock参数
 if ((*pskb)->sk)
  skb_set_owner_w(nskb, (*pskb)->sk);
// 释放老包
 kfree_skb(*pskb);
 *pskb = nskb;
 return 1;
}
 
pskb_may_pull()函数定义为:

/* include/linux/skbuff.h */

static inline int pskb_may_pull(struct sk_buff *skb, unsigned int len)
{
// 在绝大多数情况都满足的,分别用likely(),unlikely()进行优化
 if (likely(len <= skb_headlen(skb)))
  return 1;
 if (unlikely(len > skb->len))
  return 0;
// 这在很少情况下才会到达这里,主要是碎片重组包
 return __pskb_pull_tail(skb, len-skb_headlen(skb)) != NULL;
}

真正需要调整时进入__pskb_pull_tail()进行补全数据包的页外数据,把碎片部分
的数据拷贝为线性:

/* net/core/skbuff.c */
/* Moves tail of skb head forward, copying data from fragmented part,
 * when it is necessary.
 * 1. It may fail due to malloc failure.
 * 2. It may change skb pointers.
 *
 * It is pretty complicated. Luckily, it is called only in exceptional cases.
 */
unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta)
{
 /* If skb has not enough free space at tail, get new one
  * plus 128 bytes for future expansions. If we have enough
  * room at tail, reallocate without expansion only if skb is cloned.
  */
 int i, k, eat = (skb->tail + delta) - skb->end;
// 检查是否有必要扩展当前skb页面空间
 if (eat > 0 || skb_cloned(skb)) {
  if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0,
         GFP_ATOMIC))
   return NULL;
 }
// 将当前页外的delta大小的数据拷贝到skb缓冲区尾部空间
 if (skb_copy_bits(skb, skb_headlen(skb), skb->tail, delta))
  BUG();
 /* Optimization: no fragments, no reasons to preestimate
  * size of pulled pages. Superb.
  */
// 没碎片的话直接跳转
 if (!skb_shinfo(skb)->frag_list)
  goto pull_pages;
// 后面的操作都是在要腾出delta大小的线性缓冲区,腾出空间后就可以拉长skb包页面
 /* Estimate size of pulled pages. */
 eat = delta;
 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  if (skb_shinfo(skb)->frags[i].size >= eat)
   goto pull_pages;
  eat -= skb_shinfo(skb)->frags[i].size;
 }
 /* If we need update frag list, we are in troubles.
  * Certainly, it possible to add an offset to skb data,
  * but taking into account that pulling is expected to
  * be very rare operation, it is worth to fight against
  * further bloating skb head and crucify ourselves here instead.
  * Pure masohism, indeed. 8)8)
  */
 if (eat) {
  struct sk_buff *list = skb_shinfo(skb)->frag_list;
  struct sk_buff *clone = NULL;
  struct sk_buff *insp = NULL;
  do {
   BUG_ON(!list);
   if (list->len <= eat) {
    /* Eaten as whole. */
    eat -= list->len;
    list = list->next;
    insp = list;
   } else {
    /* Eaten partially. */
    if (skb_shared(list)) {
     /* Sucks! We need to fork list. :-( */
     clone = skb_clone(list, GFP_ATOMIC);
     if (!clone)
      return NULL;
     insp = list->next;
     list = clone;
    } else {
     /* This may be pulled without
      * problems. */
     insp = list;
    }
    if (!pskb_pull(list, eat)) {
     if (clone)
      kfree_skb(clone);
     return NULL;
    }
    break;
   }
  } while (eat);
  /* Free pulled out fragments. */
  while ((list = skb_shinfo(skb)->frag_list) != insp) {
   skb_shinfo(skb)->frag_list = list->next;
   kfree_skb(list);
  }
  /* And insert new clone at head. */
  if (clone) {
   clone->next = list;
   skb_shinfo(skb)->frag_list = clone;
  }
 }
 /* Success! Now we may commit changes to skb data. */
// 拉长页面
pull_pages:
 eat = delta;
 k = 0;
 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  if (skb_shinfo(skb)->frags[i].size <= eat) {
   put_page(skb_shinfo(skb)->frags[i].page);
   eat -= skb_shinfo(skb)->frags[i].size;
  } else {
   skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i];
   if (eat) {
    skb_shinfo(skb)->frags[k].page_offset += eat;
    skb_shinfo(skb)->frags[k].size -= eat;
    eat = 0;
   }
   k++;
  }
 }
 skb_shinfo(skb)->nr_frags = k;
// skb数据尾指针拉长delta,同时非页面内的数据长度减少delta
 skb->tail     += delta;
 skb->data_len -= delta;
 return skb->tail;
}

3. 应用位置

skb_make_writable()函数用在各个IP上层协议的的manip_pkt()函数、用于修改数据包内容的ip_nat_mangle_tcp_packet()、ip_nat_mangle_udp_packet()等函数中,一进入函数中使用。

4. 结论

对于IP上层协议的NAT代码,从2.4移植到2.6后要直接调用skb_make_writable()函数,代码属于需要修改的;而多连接协议的NAT处理修改内容部分的数据时,由于都是直接调用ip_nat_mangle_tcp_packet()、 ip_nat_mangle_udp_packet()等函数,因此不用考虑这方面的问题。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics