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

基于Netfilter的内核模块向出站数据包添加数据问题

内核模块添加TCP数据包载荷导致崩溃的问题修复

代码中的关键错误

  • 未定义类型与变量:struct iph、struct tcph是错误类型,应使用内核标准的struct iphdr、struct tcphdr;new_skb未提前声明。
  • skb_copy_expand参数错误:第三个参数是需要新增的尾部空间大小,而非skb->tail + extra_size,错误参数会导致分配远超需求的内存,触发OOM或内存越界。
  • 未正确修改TCP载荷:没有将额外数据写入数据包的有效载荷区域,也未更新TCP相关长度字段。
  • 校验和未重新计算:修改IP、TCP头部后未更新校验和,导致数据包校验失败,引发内核错误。
  • ip_local_out使用不当:在NETFILTER钩子中无需手动调用ip_local_out,钩子返回后内核会自动处理后续流程,手动调用会导致数据包重复处理或状态混乱。
  • 非线性skb处理缺失:未确保skb处于线性可写状态,直接访问数据可能导致非法内存访问。

修正后的代码实现

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/slab.h>
#include <linux/inet.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>

static struct nf_hook_ops nfho_out;

unsigned int hook_out(void* priv, struct sk_buff* skb, const struct nf_hook_state* state)
{
    struct iphdr* iphdr;
    struct tcphdr* tcphdr;
    struct sk_buff* new_skb;
    unsigned char extra_data[] = {'A', 'D', 'D', 'T', 'H', 'I', 'S', 0};
    int extra_size = sizeof(extra_data);
    int tcp_payload_len;
    unsigned char* payload_ptr;

    // 检查skb有效性
    if (!skb)
        return NF_ACCEPT;

    // 线性化skb确保可修改
    if (skb_linearize(skb) != 0) {
        printk(KERN_WARNING "Failed to linearize skb\n");
        return NF_ACCEPT;
    }

    // 获取IP头部并校验TCP协议
    iphdr = ip_hdr(skb);
    if (!iphdr || iphdr->protocol != IPPROTO_TCP)
        return NF_ACCEPT;

    // 获取TCP头部
    tcphdr = tcp_hdr(skb);
    if (!tcphdr)
        return NF_ACCEPT;

    // 过滤目标端口9999的数据包
    if (ntohs(tcphdr->dest) != 9999)
        return NF_ACCEPT;

    // 复制skb并预留额外尾部空间
    new_skb = skb_copy_expand(skb, skb_headroom(skb), extra_size, GFP_ATOMIC);
    if (!new_skb) {
        printk(KERN_WARNING "Failed to allocate new skb\n");
        return NF_ACCEPT;
    }

    // 更新新skb的IP头部指针
    iphdr = ip_hdr(new_skb);
    if (!iphdr) {
        kfree_skb(new_skb);
        return NF_ACCEPT;
    }

    // 更新IP总长度
    int new_ip_len = ntohs(iphdr->tot_len) + extra_size;
    iphdr->tot_len = htons(new_ip_len);

    // 重新计算IP校验和
    iphdr->check = 0;
    iphdr->check = ip_fast_csum((unsigned char*)iphdr, iphdr->ihl);

    // 更新新skb的TCP头部指针
    tcphdr = tcp_hdr(new_skb);
    if (!tcphdr) {
        kfree_skb(new_skb);
        return NF_ACCEPT;
    }

    // 获取TCP载荷起始地址与原始长度
    tcp_payload_len = new_ip_len - (iphdr->ihl * 4) - (tcphdr->doff * 4);
    payload_ptr = (unsigned char*)tcphdr + (tcphdr->doff * 4);

    // 将额外数据追加到TCP载荷末尾
    memcpy(payload_ptr + tcp_payload_len, extra_data, extra_size);

    // 更新TCP校验和:先清零,再让内核自动计算
    tcphdr->check = 0;
    new_skb->ip_summed = CHECKSUM_NONE; // 强制内核重新计算校验和

    // 替换原skb,让内核继续处理新数据包
    skb_replace(skb, new_skb);

    printk(KERN_INFO "Modified TCP Packet: added %d bytes\n", extra_size);

    return NF_ACCEPT;
}

static int __init mod_init()
{
    nfho_out.hook = hook_out;
    nfho_out.hooknum = NF_INET_LOCAL_OUT;
    nfho_out.pf = PF_INET;
    nfho_out.priority = NF_IP_PRI_FIRST;
    return nf_register_hook(&nfho_out);
}

static void __exit mod_exit()
{
    nf_unregister_hook(&nfho_out);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Add extra data to TCP packets destined to port 9999");

module_init(mod_init);
module_exit(mod_exit);

关键修复说明

  • skb线性化:使用skb_linearize确保数据包处于线性状态,避免非线性skb导致的内存访问错误。
  • 正确的skb扩展:skb_copy_expand的第三个参数传入extra_size,仅分配所需的额外尾部空间。
  • 头部字段更新:修改IP总长度后,重新计算IP校验和;TCP校验和清零并设置new_skb->ip_summed = CHECKSUM_NONE,让内核自动重新计算。
  • skb替换:使用skb_replace将原skb替换为修改后的新skb,返回NF_ACCEPT让内核继续处理后续流程,无需手动调用ip_local_out。
  • 变量声明规范:使用内核标准的struct iphdr、struct tcphdr类型,所有变量提前声明。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 18:23:17