Chinaunix首页 | 论坛 | 博客
  • 博客访问: 288902
  • 博文数量: 67
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 802
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-14 16:23
文章分类
文章存档

2011年(4)

2010年(18)

2009年(32)

2008年(13)

我的朋友

分类: LINUX

2009-10-25 19:25:12

独孤阁 博客的文章,有助于理解skb,因此转帖过来。原文链接:http://blog.chinaunix.net/u/12313/showart_162787.html

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

1. 前言

在2.6.17内核中获取sk_buff中各协议头的参数不再是直接读取skb包中定义的各协议头部分指针,而是使用skb_header_pointer()函数来获取,为什么要进行这种变化值得玩味。

以下内核代码版本为2.6.17.11。

2. skb_header_pointer函数

该函数本身很简单,定义如下:

/* include/linux/skbuff.h */
static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,
           int len, void *buffer)
{
int hlen = skb_headlen(skb);
if (hlen - offset >= len)
  return skb->data + offset;
if (skb_copy_bits(skb, offset, buffer, len) < 0)
  return NULL;
return buffer;
}


其中参数为:
skb:数据包struct sk_buff的指针
offset:相对数据起始头(如IP头)的偏移量
len:数据长度
buffer:缓冲区,大小不小于len

其中skb_headlen()定义为:

/* include/linux/skbuff.h */
static inline unsigned int skb_headlen(const struct sk_buff *skb)
{
return skb->len - skb->data_len;
}


其中skb->len是数据包长度,在IPv4中就是单个完整IP包的总长,但这些数据并不一定都在当前内存页;skb->data_len表示在其他页的数据长度,因此skb->len - skb->data_len表示在当前页的数据大小。

如果skb->data_len不为0,表示该IP包的数据分属不同的页,该数据包也就被成为非线性化的,函数 skb_is_nonlinear()就是通过该参数判断,一般刚进行完碎片重组的skb包就属于此类。更详细的关于sk_buff的那几个数据指针的意 思可参考:


这样skb_header_pointer()函数就好理解了,先判断要处理的数据是否都在当前页面内,如果是,则返回可以直接对数据处理,返回所求数据指针,否则用skb_copy_bits()函数进行拷贝,这个函数也是个比较复杂的处理函数:

/* net/core/skbuff.c */

/* Copy some data bits from skb to kernel buffer. */
int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
{
int i, copy;
int start = skb_headlen(skb);
if (offset > (int)skb->len - len)
  goto fault;
/* Copy header. */
if ((copy = start - offset) > 0) {
// 拷贝在当前页面中的部分

  if (copy > len)
   copy = len;
  memcpy(to, skb->data + offset, copy);
  if ((len -= copy) == 0)
   return 0;
  offset += copy;
  to += copy;
}
// 拷贝本skb中其他碎片中的部分

for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  int end;
  BUG_TRAP(start <= offset + len);
  end = start + skb_shinfo(skb)->frags[i].size;
  if ((copy = end - offset) > 0) {
   u8 *vaddr;
   if (copy > len)
    copy = len;
   vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
   memcpy(to,
          vaddr + skb_shinfo(skb)->frags[i].page_offset+
          offset - start, copy);
   kunmap_skb_frag(vaddr);
   if ((len -= copy) == 0)
    return 0;
   offset += copy;
   to += copy;
  }
  start = end;
}
// 拷贝其他碎片skb中的数据部分

if (skb_shinfo(skb)->frag_list) {
  struct sk_buff *list = skb_shinfo(skb)->frag_list;
  for (; list; list = list->next) {
   int end;
   BUG_TRAP(start <= offset + len);
   end = start + list->len;
   if ((copy = end - offset) > 0) {
    if (copy > len)
     copy = len;
    if (skb_copy_bits(list, offset - start,
        to, copy))
     goto fault;
    if ((len -= copy) == 0)
     return 0;
    offset += copy;
    to += copy;
   }
   start = end;
  }
}
if (!len)
  return 0;
fault:
return -EFAULT;
}

3. 与2.4的区别

在2.4中是没有这一函数的,因为2.4的netfilter首先进行碎片包重组,随即进行skb的线性化检查,对非线性skb包进行线性化,因此合法skb包进入后续hook点操作时实际skb->data_len就都是0了,可以直接操作。

netfilter的碎片重组函数为ip_ct_gather_frags(),在2.4中碎片重组完还进行线性化,而2.6.17中重组完就直接返回了,并不进行线性化操作,因此以后在使用的时候必须检查要处理的数据是否在内存页面中。

4. 结论

由于2.6.17中的碎片重组操作后不进行skb数据包的线性化,因此数据可能存在于不同的内存页面中,对于不在同一页面中的情况不能直接进行数据操作,需要将数据拷贝到一个单独缓冲区后再进行处理。
不知道为什么要这么作,是为提高效率?是否因为现在网络上碎片包已经很少了,大部分情况下都是完整包,但提前线性化检查一下会降低多少效率呢?是为以后发出包的时候不需要再重新分片?
阅读(1352) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~