关于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()等函数,因此不用考虑这方面的问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值