内核模块编译报‘fib_get_table未定义’错误的技术问询
问题
我正在编写一个Linux内核模块,通过dmesg打印路由表,尝试使用fib_get_table函数访问FIB(Forwarding Information Base,转发信息库)表。以下是模块中使用的简化版函数:
// Alternative approach: Read routing table entries directly static void dump_route_entries(struct seq_file *m) { struct fib_table *tb; struct net *net = &init_net; u32 table_id; seq_printf(m, "*** KERNEL ROUTING TABLES ***\n\n"); rtnl_lock(); // Main routing table tb = fib_get_table(net, RT_TABLE_MAIN); if (tb) { seq_printf(m, "=== MAIN TABLE (ID: %u) ===\n", RT_TABLE_MAIN); display_fib_table_info(tb, m); } // Local routing table tb = fib_get_table(net, RT_TABLE_LOCAL); if (tb) { seq_printf(m, "=== LOCAL TABLE (ID: %u) ===\n", RT_TABLE_LOCAL); display_fib_table_info(tb, m); } // Iterate other tables for (table_id = 0; table_id < 256; table_id++) { if (table_id == RT_TABLE_MAIN || table_id == RT_TABLE_LOCAL) continue; tb = fib_get_table(net, table_id); if (tb) { seq_printf(m, "=== TABLE %u ===\n", table_id); display_fib_table_info(tb, m); } } rtnl_unlock(); }
但编译模块时,modpost阶段出现如下错误:
make -C /lib/modules/5.15.0-139-generic/build M=../Desktop/fib_test modules make[1]: Entering directory '/usr/src/linux-headers-5.15.0-139-generic' CC [M] ../Desktop/fib_test/fib_reader.o MODPOST ../Desktop/fib_test/Module.symvers ERROR: modpost: "fib_get_table" [../Desktop/fib_test/fib_reader.ko] undefined! make[2]: *** [scripts/Makefile.modpost:133: ../Desktop/fib_test/Module.symvers] Error 1
我确认fib_get_table存在于内核源码中,但未通过EXPORT_SYMBOL导出,当前内核版本为5.15.0-139-generic。我的疑问如下:
- 为何
fib_get_table存在于内核代码中,编译模块时却提示未定义? - 有没有不依赖非导出符号
fib_get_table的内核模块路由表访问方法? - 手动修改内核源码导出该符号是否安全合适,或有推荐替代方案?
我已尝试的操作:
- 确认
fib_get_table存在于内核源码中 - 执行命令
cat /proc/kallsyms/ | grep fib_get_table,输出为0000000000000000 T fib_get_table - 寻找可替代的API或方法访问内核模块中的路由表
希望得到无需补丁或重建整个内核的解决方案。
解答
1. 为何fib_get_table存在却提示未定义?
内核函数分为导出符号和非导出符号两类:
- 只有通过
EXPORT_SYMBOL或EXPORT_SYMBOL_GPL标记的符号,才会被加入内核符号表,供外部模块链接使用。 fib_get_table是内核内部私有函数,未被导出,因此模块编译时无法找到该符号的链接入口。/proc/kallsyms能看到该函数,是因为它确实存在于内核代码段,但未对外暴露给模块调用。
2. 不依赖fib_get_table的替代方案
针对5.15内核,推荐两种合法的访问方式:
方式一:使用rtnetlink机制(推荐)
内核提供rtnetlink接口供内核态/用户态查询路由信息,模块可通过内核导出的API构造或处理netlink消息获取路由表:
#include <net/rtnetlink.h> static int dump_routes(struct sk_buff *skb, struct netlink_callback *cb) { struct net *net = sock_net(skb->sk); // 调用内核原生的路由表dump函数,支持指定表ID和地址族 return rt_dump_fib(skb, cb, RT_TABLE_MAIN, AF_INET); } static int __init fib_reader_init(void) { // 可注册RTM_GETROUTE消息回调,或直接调用dump_routes生成信息并输出到dmesg return 0; }
这种方式基于内核官方导出的稳定API,兼容性和安全性最佳。
方式二:遍历net->ipv4.fib_tables哈希表
在5.15内核中,struct net的ipv4.fib_tables是存储所有FIB表的哈希表,可通过导出的哈希表遍历API访问:
#include <linux/hashtable.h> #include <net/ip_fib.h> static void dump_all_fib_tables(struct seq_file *m, struct net *net) { struct fib_table *tb; unsigned int bkt; rtnl_lock(); // 遍历fib_tables哈希表,枚举所有FIB表 hash_for_each(net->ipv4.fib_tables, bkt, tb, tb_hash) { seq_printf(m, "=== TABLE %u ===\n", tb->tb_id); display_fib_table_info(tb, m); } rtnl_unlock(); }
注意:该方式依赖内核内部数据结构细节,不同版本可能有变化,但5.15版本稳定可用,且哈希表操作API是导出的。
3. 修改内核导出fib_get_table是否安全?
不推荐手动修改内核源码导出该符号:
- 内核将其设为私有函数,说明其接口或实现可能在未来版本中变更,模块依赖它会导致兼容性问题。
- 导出私有符号会破坏内核的模块化边界,可能因模块误用该函数引发内核崩溃等稳定性风险。
- 需要重新编译内核,不符合你“无需补丁或重建内核”的需求。
综上,优先选择rtnetlink机制,其次可考虑遍历fib_tables哈希表,两种方式均无需修改内核。
内容的提问来源于stack exchange,提问作者Alessandro Torrisi
相关产品推荐
相关产品推荐

