本文档的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()等函数,因此不用考虑这方面的问题。
分享到:
相关推荐
linux 操作系统的skb_buff 结构的详细介绍
linux下网络重要结构skb_buff的包缓冲过程。是了解网络工作原理的很好的资料。
linux数据栈的关键数据结构skb_buf
linux数据栈的关键数据结构skb
AG22_INT_ALI_SKB_Timeline-V1.2.xlsm
无线网卡 Atheros AR5212/AR5213 802.11a/b/g Wireless Adapter 制造商 富士通 驱动
SKYLAB SKB 369 user manual based on nrf52832
新版的列车时刻表V4.5.4可应用多个地点间火车时刻的查询
SKB_平台 Sziale Netzwerke的编程 Gruppe entwickelt Netzwerkplattform 产品负责人 Scrum Master- 质量保证经理 基础架构经理- 跑步 SKB_Project$ npm install client$ npm install (eventuelle !!,应该从...
skb_i-bem_test 使用i-bem库和mustache模板引擎测试任务实现
根据阿里官网上的介绍,sysAK,全称是system analyse kit,目前主要来自于阿里百万服务器运维经验,通过对这些经验进行抽象总结出典型场景,提供了一系列工具针对不同的运维需求。 主要包括: ...
linux内核skb_buf相关操作介绍
78K0S/KB1+系列单片机(nec)中文资料 pdf文档
struct sk_buff 可能是linux网络代码中最重要的数据结构,它表示接受或发送数据包的包头信息,并包含很多成员变量供网络代码中的各子系统使用。
非常详细的linux TCP/IP协议栈分析文档。
kernel_net_chart, 它是Linux内核的skb路径 命令行目无论是用户空间,内核,客户端还是服务器,我总是关注于网络软件开发在这个库中,我尝试尽可能多地描述内核网络...kernel_skb_path: 描述skb路径的流。netfilter_pat
根据linux内核进行图解,很全对skb的处理,有arp、ip、等多种报文,还有socket等,很全,很实用
SKB376是一个高度集成的蓝牙4.2模组,可用来在2.4GHz ISM频段内做高速率、短距离无线通信。模组基于SYD8811蓝牙无线收发芯片,内部集成32位ARM Cotex-M0处理器,主频64MHz,同时内置32kB RAM和512kB Flash,可以支持...