You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Netfilter模块中如何获取skb内分页数据的有效载荷?

Netfilter模块TCP有效载荷丢失排查

我在实现Netfilter模块时,调试TCP数据包发现skb->data里没有有效载荷,推测和数据分页有关。

补充可复现代码(NF钩子函数):

static struct nf_hook_ops nfho = {
    .hook = hook_func,
    .hooknum = NF_INET_PRE_ROUTING,
    .pf = PF_INET,
    .priority = NF_IP_PRI_FIRST,
};

static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) {
    struct iphdr *iphdr;
    struct tcphdr *tcphdr;
    
    iphdr = ip_hdr(skb);
    if(iphdr->protocol = IPPROTO_TCP) { // 注:此处为赋值运算符,正确应使用==
        for(int i = 0; i < skb->len; i++) {
            printk(KERN_CONT "%02x\t", skb->data[i]);
        }
    }
    return NF_ACCEPT;
}

发送"hello world"后,skb->data_len = 12,skb->len = 64,Wireshark捕获到完整数据包:

0000    00 00 00 00 00 00 00 00    00 00 00 00 08 00 45 00
0010    00 40 71 33 40 00 40 06    cb 82 7f 00 00 01 7f 00
0020    00 01 d8 7c 0d 05 c9 1c    57 6f e0 ad a7 51 80 18
0030    02 00 fe 34 00 00 01 01    08 0a 86 63 f5 54 86 62
0040    bb c8 68 65 6c 6c 6f 20    77 6f 72 6c 64 0a

但dmesg输出的数据包末尾没有有效载荷:

0000    00 00 00 00 00 00 00 00    00 00 00 00 08 00 45 00
0010    00 40 71 33 40 00 40 06    cb 82 7f 00 00 01 7f 00
0020    00 01 d8 7c 0d 05 c9 1c    57 6f e0 ad a7 51 80 18
0030    02 00 fe 34 00 00 01 01    08 0a 86 63 f5 54 86 62
0040    bb c8 00 00 00 00 00 00    00 00 00 00 00 00

解决方法

这是Linux内核sk_buff的线性区/非线性区机制导致的:

  • skb->len是数据包总长度(包含所有分片/分页数据)
  • skb->data仅指向线性区的数据,skb->data_len则是存储在非线性区(分页)的数据长度

要读取完整数据包,不能直接遍历skb->data,必须使用内核提供的API访问非线性区:

  1. 复制完整数据包到线性缓冲区
    使用skb_copy_bits()提取所有数据(包括非线性区):

    static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) {
        struct iphdr *iphdr;
        unsigned char buf[1500]; // 足够容纳MTU大小的数据包
        int copy_ret;
        
        iphdr = ip_hdr(skb);
        if(iphdr->protocol == IPPROTO_TCP) { // 修正之前的赋值错误
            copy_ret = skb_copy_bits(skb, 0, buf, skb->len);
            if(copy_ret == 0) { // 复制成功
                for(int i = 0; i < skb->len; i++) {
                    printk(KERN_CONT "%02x\t", buf[i]);
                }
            }
        }
        return NF_ACCEPT;
    }
    
  2. 遍历skb分片直接读取
    若不想复制数据,可通过skb_walk_frags()遍历所有分片,逐个读取页数据:

    static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) {
        struct iphdr *iphdr;
        struct sk_buff *frag;
        
        iphdr = ip_hdr(skb);
        if(iphdr->protocol == IPPROTO_TCP) {
            // 打印线性区数据
            for(int i = 0; i < skb_headlen(skb); i++) {
                printk(KERN_CONT "%02x\t", skb->data[i]);
            }
            // 遍历分片,打印非线性区数据
            skb_walk_frags(skb, frag) {
                for(int i = 0; i < frag->len; i++) {
                    printk(KERN_CONT "%02x\t", frag->data[i]);
                }
            }
        }
        return NF_ACCEPT;
    }
    

另外注意修正代码中的逻辑错误:if(iphdr->protocol = IPPROTO_TCP)是赋值操作,应改为==,否则会将所有数据包误判为TCP类型。


内容的提问来源于stack exchange,提问作者sebasion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 23:42:03