本文档的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()等函数,因此不用考虑这方面的问题。

关于skb_make_writable()函数相关推荐

  1. 数据库中自定义排序规则,Mysql中自定义字段排序规则,Oracle中自定义字段排序规则,decode函数的用法,field函数的用法

    数据库中自定义排序 场景:有一张banner表,表中有一个status字段,有0, 1, 2三个状态位,我想要 1,0,2的自定义排序(这里是重点),然后再进行之上对sequence字段进行二次排序( ...

  2. Mysql函数group_concat、find_in_set 多值分隔字符字段进行数据库字段值翻译

    Mysql函数group_concat.find_in_set进行数据库字段值翻译 场景 配方表:记录包含的原料 sources表示原料,字段值之间用逗号分隔 原料表:对应原料id和原料名称 现需要查 ...

  3. C++ 笔记(34)— C++ exit 函数

    当遇到 main 函数中的 return 语句时,C++ 程序将停止执行.但其他函数结束时,程序并不会停止.程序的控制将返回到函数调用之后的位置.然而,有时候会出现一些非常少见的情况,使得程序有必要在 ...

  4. C++ 笔记(30)— 友元函数与友元类

    我们知道类的私有成员只能在类的成员函数内部访问,如果想在别处访问对象的私有成员,只能通过类提供的接口(成员函数)间接地进行.这固然能够带来数据隐藏的好处,利于将来程序的扩充,但也会增加程序书写的麻烦. ...

  5. 浅显易懂 Makefile 入门 (07)— 其它函数(foreach 、if、call、origin )

    1. foreach 函数 foreach 函数定义如下: $(foreach <var>,<list>,<text>) 函数的功能是:把参数 <list&g ...

  6. 浅显易懂 Makefile 入门 (06)— 文件名操作函数(dir、notdir、suffix、basename、addsuffix、addperfix、join、wildcard)

    编写 Makefile 的时候,很多情况下需要对文件名进行操作.例如获取文件的路径,去除文件的路径,取出文件前缀或后缀等等. 注意:下面的每个函数的参数字符串都会被当作或是一个系列的文件名来看待. 1 ...

  7. Go 学习笔记(65)— Go 中函数参数是传值还是传引用

    Go 语言中,函数参数传递采用是值传递的方式.所谓"值传递",就是将实际参数在内存中的表示逐位拷贝到形式参数中.对于像整型.数组.结构体这类类型,它们的内存表示就是它们自身的数据内 ...

  8. Go 学习笔记(61)— Go 高阶函数、函数作为一等公民(函数作为输入参数、返回值、变量)的写法

    函数在 Go 语言中属于"一等公民(First-Class Citizen)"拥有"一等公民"待遇的语法元素可以如下使用 可以存储在变量中: 可以作为参数传递给 ...

  9. C++ 笔记(26)— 主函数 main(int argc, char *argv[]) 参数说明

    带形参的 main 函数,如 int main( int argc, char* argv[], char **env ) 是 UNIX .Linux 以及 Mac OS 操作系统中 C/C++ 的 ...

最新文章

  1. oracle12c bug,Oracle12c R2注意事项: 因BUG生成大量的trace file 包含KRB: (rman module)
  2. scum开服务器延迟高怎么办,人渣SCUM卡顿优化方法 人渣SCUM卡顿怎么办
  3. 对话腾讯安全杨勇:产业互联网带来哪些新的安全挑战
  4. Ecilpse常用快捷键
  5. 协议圣经 五 rtsp client
  6. C#.Net工作笔记009---c#中Yield Return语法的作用和好处
  7. 设计表的时候,对变长字段长度选择的一点思考
  8. 远程访问dmz和虚拟服务器的设置
  9. 深入理解JVM虚拟机-Ubuntu中安装openJDK
  10. 【Android命令行】jarsigner参数详解
  11. 博士预答辩之后关于中文论文踩坑心得
  12. HTTP网络劫持的原理与过程、网站被劫持怎么办?
  13. python金融分析培训课程_Python金融分析相关书籍推荐
  14. SAP中报表清单导出的常用方法
  15. 微软拼音输入法卸载相关
  16. InputStream输入字节流
  17. sct分散加载文件格式与应用
  18. systemUI 学习记录1
  19. Pixhawk飞控代码(2019.11.28)
  20. 打破边界,边缘计算有何应用场景?

热门文章

  1. 世界电信日| 谈谈电信行业缘何牵线云计算?
  2. 单元格内容分列多行_excel表格数据换行分列-EXCEL怎么分行呢?一个单元格有多行字,怎么向分列......
  3. Hadoop总结——Hadoop基础
  4. 数据结构实验八 图及其应用
  5. GYM 101350 H. Mirrored String I
  6. 学校教务管理系统(第二弹
  7. 微信小程序开发笔记(二)
  8. 【100%通过率】华为OD机试真题 Python 实现【完美走位】【2022.11 Q4新题】
  9. VS 2015 更换exe的图标
  10. MySQL5.5 的安装与配置