如何获取指定网络命名空间的struct net指针以挂载Netfilter钩子函数
获取特定网络命名空间的
struct net *指针用于Netfilter钩子注册 我来帮你搞定这个问题——要把Netfilter钩子挂载到你创建的自定义网络命名空间(比如ns1),核心是获取对应命名空间的struct net *指针,替换代码里的&init_net。下面是具体的实现方案和修改后的代码:
核心思路
内核中每个网络命名空间对应一个struct net实例,我们可以通过命名空间的名称直接查找获取指针,同时必须遵循内核的引用计数规则:用get_net系列函数获取指针时会自动增加引用计数,模块退出时必须调用put_net释放,避免内存泄漏。
具体实现步骤
1. 添加必要的头文件和模块参数
首先,我们需要添加网络命名空间相关的头文件,同时定义一个模块参数,方便你在加载模块时指定目标命名空间的名字(比如ns1):
#include <net/net_namespace.h> #include <linux/moduleparam.h> // 模块参数:指定目标网络命名空间的名字,默认是ns1 static char *netns_name = "ns1"; module_param(netns_name, charp, 0644); MODULE_PARM_DESC(netns_name, "Name of the target network namespace (e.g., ns1)"); // 全局变量存储目标网络命名空间的指针 static struct net *target_net; // 全局声明nf_hook_ops,避免函数间作用域问题 static struct nf_hook_ops nfho;
2. 修改初始化函数,获取目标netns指针并注册钩子
在模块初始化函数中,用get_net_by_name函数获取指定名称的网络命名空间指针,然后用这个指针注册Netfilter钩子:
static unsigned int verify_hookfunc(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { char srcIP[16]; struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb); if(!skb) return NF_ACCEPT; memset(srcIP, '\0', sizeof(srcIP)); sprintf(srcIP, "%pI4", &ip_header->saddr); if(strcmp(srcIP, "1.2.3.4") == 0){ printk("see sent modified packet\n"); } return NF_ACCEPT; } static int __init init_main(void) { // 获取目标网络命名空间指针 target_net = get_net_by_name(&init_net, netns_name); if (!target_net) { printk(KERN_ERR "Failed to locate network namespace: %s\n", netns_name); return -ENOENT; } // 配置Netfilter钩子信息 memset(&nfho, 0, sizeof(nfho)); // 初始化结构体避免未定义行为 nfho.hook = verify_hookfunc; nfho.hooknum = NF_BR_PRE_ROUTING; nfho.pf = PF_BRIDGE; nfho.priority = INT_MIN; // 注册钩子到目标网络命名空间 nf_register_net_hook(target_net, &nfho); printk(KERN_INFO "Successfully registered hook in netns: %s\n", netns_name); return 0; }
3. 修改清理函数,释放资源
模块退出时,要先注销钩子,再释放struct net的引用计数:
static void __exit cleanup_main(void) { if (target_net) { nf_unregister_net_hook(target_net, &nfho); put_net(target_net); // 释放引用计数,避免内存泄漏 printk(KERN_INFO "Successfully unregistered hook from netns: %s\n", netns_name); } } module_init(init_main); module_exit(cleanup_main); MODULE_LICENSE("GPLv3");
额外注意事项
- 内核配置要求:确保你的内核开启了
CONFIG_NET_NS(网络命名空间支持),这是ip netns命令和网络命名空间API工作的前提。 - 替代方案(通过路径获取):如果你想通过
/var/run/netns/ns1路径直接获取,也可以用kern_path和get_net_from_inode组合实现,但用名称查找更简洁:struct path path; int ret = kern_path("/var/run/netns/ns1", LOOKUP_FOLLOW, &path); if (ret) { printk(KERN_ERR "Failed to resolve netns path\n"); return ret; } target_net = get_net_from_inode(path.dentry->d_inode); if (!target_net) { printk(KERN_ERR "Failed to get net from inode\n"); path_put(&path); return -ENOENT; } path_put(&path); - 内核版本兼容性:
get_net_by_name函数在Linux 3.10及以上内核版本可用,如果你的内核版本更旧,可以使用netns_get_by_name替代(注意函数参数略有不同)。
内容的提问来源于stack exchange,提问作者CE KUAN




