ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux内核编译kernel-aodv v2.2.2:从源码到无线自组网稳定运行

Linux内核编译kernel-aodv v2.2.2:从源码到无线自组网稳定运行 简介AODV 是一种用于无线自组织网络的按需距离矢量路由协议kernel-aodv_v2.2.2 是其在 Linux 内核中的开源实现。这份资源压缩包仅 53KB却包含 48 个文件其中 21 个头文件与 20 个 C 源文件完整实现了路由表管理、RREQ/RREP 路由发现、RERR 路由撤销、邻居探测等核心功能另有 2 个 shell 脚本与构建文件支持模块加载和环境配置使用说明与更新日志补充了协议背景与版本演化信息。面向网络方向的研究生、Linux 内核开发者以及对 Ad Hoc 网络感兴趣的工程技术人员这份资料提供了从源码阅读到实际部署的完整链路通过逐行分析这些短小精悍的源文件读者可以厘清 AODV 在内核中如何维护路由表、处理控制报文以及应对链路断裂还能借助脚本和文档快速完成内核模块编译、网络接口配置与协议验证。该资源在教学与科研中同样具有参考价值能将课本上的路由协议理论映射到真实实现。目前已有 195 人学习使用这一资源是理解无线自组织网络协议栈的轻量而完整的实用样本。1. AODV 内核模块是干什么的自组网里那个“按需找路”的协议做无线自组网MANET的人十有八九都听过 AODV也就是 Ad hoc 按需距离矢量路由协议。它不像 OLSR 那样周期性地全网广播拓扑而是当源节点真正有数据要发给目标节点时才发起路由发现找到一个临时路由然后按这条路径转发。这样开销低、适合节点移动的场景。而 kernel-aodv 这个项目是把 AODV 的实现从用户态搬进了 Linux 内核直接操作路由表和 netfilter hook转发路径比用户态代理短得多。这里要解决的问题是怎么把这份 v2.2.2 源码编译进当前内核并且让它在真实无线链路上稳定跑起来。适合做无人机自组、应急通信、车载 mesh 的工程师以及想把协议栈从用户态迁到内核态做低延迟转发的研究者。越往后你会越发现编译只是开始真正麻烦的是版本 API 和无线网卡模式这两个坎。2. 编译前先看清kernel-aodv_v2.2.2 的源码结构和依赖2.1 源码里都有什么从 .rar 解压后先找这几个文件拿到 kernel-aodv_v2.2.2.rar 之后第一件事不是急着 make而是先把压缩包解开看看里面到底是不是你预期的那套东西。常见做法是mkdir -p /home/you/aodv-src cd /home/you/aodv-src unrar x kernel-aodv_v2.2.2.rar如果你的系统里没装 unrar可以用unp或者7z但最保险的还是直接unrar x。解压后一般能看到这样一组文件kernel-aodv_v2.2.2/ ├── Makefile ├── Kconfig (如果放在内核树外可能没有) ├── README ├── aodv.c (协议主逻辑) ├── aodv.h (数据结构与常量声明) ├── aodv_netlink.c / aodv_socket.c (用户态通信) ├── aodv_proc.c (proc 接口) └── ...对 v2.2.2 这一版我比较有把握的结构是它把 AODV 核心状态机放在aodv.c把与用户态交互的 netlink 或 ioctl 通道单独拆出来路由表操作则直接调用内核的fib_*或ip_route_*接口。所以你在阅读源码时先看README和Makefile搞清楚它默认是编成.o然后链进内核镜像还是编成.ko独立模块。从 v2.2.2 的命名推断它大概率已经支持模块化编译也就是那套make module的方式。这里有一个实用习惯解压后先跑一次grep -n KERNEL_VERSION\|RHEL\|proc_create\|netlink_kernel_create aodv.c看看它调用的内核 API 是什么年代的。因为很多旧版 AODV 用的还是proc_create或者netlink_kernel_create这些函数在新内核里的签名改过不止一次直接决定你能不能编译通过。2.2 依赖检查内核源码树和无线网卡驱动缺一不可kernel-aodv 不是 standalone 的用户态进程它需要接入内核网络栈。因此编译前必须准备两样东西目标内核的源码树或者至少是带头文件和 Module.symvers 的构建目录以及一块能被驱动起来、支持 ad-hoc 模式的无线网卡。对于 2020 年以后的发行版还要额外注意 Secure Boot 和内核模块签名否则insmod会被拒绝。先说内核源码树。假设你用的是 Ubuntu 或者 CentOS 自带的内核sudo apt install linux-headers-$(uname -r) # Debian/Ubuntu sudo yum install kernel-devel-$(uname -r) # CentOS/RHEL然后确认/lib/modules/$(uname -r)/build存在并且指向源码目录。如果你用的是高通 CAF 这类厂商定制内核或者麒麟Kylin Linux Advanced Server V10这种国产发行版那么必须用厂商随系统提供的kernel-source包而不能拿主线内核对凑。CAF 内核的版本号里往往带着android-msm-前缀AODV 编译时KVER判断会多一层坑。无线网卡这边AODV 依赖链路层的广播和转发所以网卡必须工作在ad-hoc模式下也就是 IBSS 模式。你可以先用iw list查看网卡是否支持IBSS别等到测试时才发现驱动不支持那就晚了。我自己就翻过一次车用一块老式 USB 网卡驱动号称支持 ad-hoc但实际发包时网卡固件直接把管理帧过滤掉了AODV 的 HELLO 包根本出不去。2.3 配置选项Kconfig 里藏着哪些开关如果源码树里带了Kconfig说明你可以把 AODV 编入主内核。但更常见的是作为外部模块编译此时 Makefile 里的选项才是关键。打开Makefile看看类似这样的内容# kernel-aodv Makefile 片段 KSRC ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) obj-m : kernel_aodv.o kernel_aodv-objs : aodv.o aodv_proc.o aodv_netlink.o all: $(MAKE) -C $(KSRC) M$(PWD) modules clean: $(MAKE) -C $(KSRC) M$(PWD) clean这里的obj-m说明它最终会编成.ko。你可能会遇到一个问题如果源码里的aodv.c引用了CONFIG_NET_SCHED或CONFIG_INET相关配置但你的内核没开就会报未知符号。所以编译前建议先看dmesg或者.config里是否有CONFIG_NETFILTERy因为 netfilter hook 是 AODV 截获数据包的主要手段。如果源码里找不到 Kconfig别慌它不影响外部模块编译。你只要保证KSRC指向的内核源码树的版本和当前运行内核一致就行。有一个非常常见的错误系统跑了 5.15 内核但/lib/modules/$(uname -r)/build指向的是 5.14 的头文件编译出来的 .ko 用modinfo看版本是 5.14一 insmod 就提示版本魔术不匹配。后面避坑章节我会再展开。3. 编译安装的全过程从解压到 insmod3.1 解压和配置make config 之前先改内核版本准备好依赖之后进入源码目录先做一次make clean如果之前编过再跑make。但直接跑多半会失败因为新版内核的很多 API 变了。比如skb_dst变成了skb_dst_setip_route_output_key的参数从结构体指针变成了两个参数。v2.2.2 这套代码很可能是为 2.4/2.6 时代的内核写的你需要先做一层兼容适配。我一般会先看Makefile里有没有版本分支比如# Makefile 中的版本判断 KERNELRELEASE : $(shell uname -r) ifeq ($(shell echo $(KERNELRELEASE) | cut -d. -f1),3) EXTRA_CFLAGS -DKERNEL_3X else ifeq ($(shell echo $(KERNELRELEASE) | cut -d. -f1),4) EXTRA_CFLAGS -DKERNEL_4X endif如果没有你得自己在aodv.c里加宏兼容。最省事的办法是先用 release 版本的内核编译一把记下第一个错误按错误去改。比如新内核里proc_create(aodv, S_IFREG|S_IRUGO, NULL, aodv_proc_fops)这个调用在 2.6.31 以后改成只返回proc_dir_entry*而不需要proc_net参数你就把传入参数改成 NULL。下面是一个我常用的最小修改点// aodv_proc.c 中的修改 -static int aodv_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) static ssize_t aodv_read(struct file *file, char __user *buf, size_t len, loff_t *off)改完再编译。如果aodv.c里用了dev_queue_xmit新内核里这个接口需要传入skb时已经设置好dev和cb否则会 panic。这些细节可以在编译错误里逐一解决不用一次改完。3.2 编译与安装make make install 会遇到的路障第一次编译建议打开编译命令详细输出方便排错。不要直接用make而是make KSRC/lib/modules/$(uname -r)/build V1V1会把每条 gcc 命令打印出来你能看到-I路径有没有指错。如果出现一堆fatal error: linux/module.h: No such file or directory说明KSRC没有指向带 build 的内核源码或者你的内核开发包没装全。编译成功的标志是当前目录生成了kernel_aodv.ko或者aodv.ko。运行modinfo检查modinfo aodv.ko重点看vermagic一行。它应该和当前内核的uname -r一致。如果多了SMP mod_unload modversions之类的标识说明内核编译时的配置和你现在跑的不完全一样这时insmod会拒绝加载。解决方法是重新用同样的.config编译内核或者退出当前内核源码目录到/lib/modules/$(uname -r)/build下重新 make modules_prepare。安装模块到系统路径然后加载sudo make KSRC/lib/modules/$(uname -r)/build install sudo insmod /lib/modules/$(uname -r)/extra/kernel-aodv.ko如果没有 write 权限写/lib/modules可以跳过 install直接用insmod指定路径加载。注意install行为不一定会复制模块到extra目录具体要看 Makefile 里的install:目标怎么写。有些版本只是把 .ko 放在当前目录要求你手动拷贝。3.3 加载模块insmod 后如何验证 AODV 己经在内核里加载之后用lsmod | grep aodv看模块存在不。然后立即看dmesg | tail -n 50。一个健康的 AODV 模块在初始化时会输出类似这样的日志router:aodv: initializing aodv module. router:aodv: routing table cache size 32. router:aodv: netlink socket created.如果没有任何输出可能是模块里的 printk 级别被内核隐藏了试着dmesg -n 7把控制台日志级别调低再 insmod。另外AODV 模块通常会注册一个 proc 接口试试ls -la /proc/aodv cat /proc/aodv/neighbor # 常见实现是列举邻居节点如果ls找不到说明 init 函数可能在注册 proc 之前就失败了。回到dmesg看有没有 panic 或者 WARNING。我还会在这个阶段做一次简短的ip route检查确认模块没有把你的正常路由表搞乱。如果发现默认路由被替换成奇怪的东西多半是 AODV 的rt_ops注册出了问题先 insmod 前备份路由表出问题再恢复。4. 运行时参数调优AODV 的 5 个关键参数别用默认值4.1 参数表hello 间隔、路由超时、修复阈值等AODV 协议本身由 RFC 3561 定义但内核版的实现通常会把这些协议常数暴露成模块参数或者 proc 可写的变量。在你编译和加载好 v2.2.2 之后insmod时可以带上参数比如sudo insmod aodv.ko act_rto3000 hello_interval1000或者加载后通过 proc 写入echo 1500 /proc/aodv/hello_interval下面我整理了一份在 v2.2.2 里最常见、也最影响行为的参数表参数名可能因版本小有差异但含义一致参数名默认值含义调优方向hello_interval1000 ms节点广播 HELLO 的周期移动速度快就调小代价是广播开销上升allowed_hello_loss2连续丢多少 HELLO 判定邻居失效丢包率高时调大但路由修复变慢act_rto3000 ms活跃路由超时时间过期则删除路由业务流长且链路稳定可调大减少重新发现rreq_retries2源节点重发 RREQ 的最大次数网络规模大时调大但容易增加广播风暴net_diameter35网络中最大跳数节点数少就改小避免 RREQ 的 ID 列表过长这些参数不是孤立生效的。hello_interval和allowed_hello_loss组合决定“邻居失效检测”速度如果你设置hello_interval1000、allowed_hello_loss2那么链路断掉后最多 2 秒内能发现邻居没了。但在这 2 秒内发送给该邻居的包都会被没有等到的新路由挡住。4.2 两个典型场景的调参组合低速移动 vs 高密度节点场景一低速移动的固定无线 mesh比如仓库内 AGV 车组移动速度小于 1 m/s。此时邻居相对稳定关键是降低控制开销。我常用的参数组合是sudo insmod aodv.ko hello_interval3000 allowed_hello_loss3 act_rto5000 rreq_retries1 net_diameter10HELLO 间隔放大到 3 秒允许丢 3 次相当于 9 秒才能发现邻居断开但对低速场景够用。act_rto5000让有效路由在多活几秒避免小波动触发重建。跳数上限压到 10因为仓库网格通常不超过 10 跳RREQ 范围被限制广播数量明显减少。场景二高密度无人机编队节点 30 架相对速度 5~10 m/s。这时要更快地感知链路变化否则路由丢失后要重新发现延迟增加。我采用sudo insmod aodv.ko hello_interval500 allowed_hello_loss2 act_rto1500 rreq_retries4 net_diameter20HELLO 间隔 500ms丢 2 次就判定链路断了大概 1 秒内开始路由修复。rreq_retries调到 4因为高移动下 RREQ 很容易被转发过程中的链路问题丢掉。但要注意这样的组合会让 HELLO 和 RREQ 广播频繁20 个节点同时广播时无线信道利用率会迅速高企所以网卡速率至少要有 54Mbps否则数据吞吐会被协议开销吃掉。还有一个所有场景都适用的参数local_repair_enable。默认开启它能跳过源节点重建直接在中间节点发起 RREQ 修复后半段路由。如果你的网络节点是无人机这种高速移动的建议关掉因为局部修复往往第一时间找不到新路由反而多一次超时等待不如让源节点立刻重建。关闭方法echo 0 /proc/aodv/local_repair5. 避坑指南编译、加载和测试的 4 个高频翻车点5.1 现象模块编译报 Unknown symbolmake时一堆类似Unknown symbol ipproto_xxx的 error或者链接后 insmod 时 dmesg 提示Unknown symbol in module。原因有二一是你的内核没有导出这个符号比如 AODV 调用了nf_register_hook但你编译内核时把 netfilter 关成了m或者没编二是模块版本化modversions导致符号 CRC 不匹配源码树和运行内核不一致。解决方法是先查符号grep symbol /proc/kallsyms如果不存在说明内核没编这个功能需要重新配置内核或者改用模块加载方式。如果是 CRC 不匹配用相同的源码树重新 make modules_prepare再编译 AODV。5.2 现象insmod 说 Operation not permittedinsmod返回Operation not permitted但dmesg里看不到任何错误。这在 2020 年后的发行版上很常见Secure Boot 开启了内核拒绝加载任何未签名的模块。解决方法是先确认mokutil --sb-state如果是 enabled要么在 BIOS 里关闭 Secure Boot要么为模块签名。签名需要生成 MOK 密钥并注册比较繁琐我自己在测试机上都是关掉省事。另一个可能你用了modprobe尝试加载但没有先 depmod导致它找到的模块路径不对这时dmesg可能显示module not found。改用insmod /绝对路径/xxx.ko能绕过。5.3 现象无线网卡收不到 AODV 数据包 / 路由表不更新加载模块之后AODV 的 HELLO 报文应该周期性地从无线接口发出去。查看方法tcpdump -i wlan0 udp port 654默认端口可能不同如果抓不到任何 UDP 或裸 IP 包说明网卡没有进入 ad-hoc 模式或者驱动把广播帧过滤了。用iwconfig wlan0 mode ad-hoc channel 6 essid mesh重新设置然后ip link set wlan0 up。如果网卡即使设成 ad-hoc 也不通试着关掉网卡的省电模式iw dev wlan0 set power_save off。有些网卡驱动默认开启电源管理接收窗口会断续HELLO 包丢失严重。5.4 现象测试时 ping 通但吞吐很低问题在广播风暴路由能建立ping 延时就几十毫秒但一跑 iperf 吞吐掉到几百 kbps。打开重传和丢包统计通常会看到大量 RREQ 被转发。罪魁祸首是rreq_retries和net_diameter设置过大网络上弥漫着广播包。另外AODV 的 RREQ 需要等待一定时间才会重发如果rreq_retries太大在节点多、信道差的情况下广播风暴瞬间占满空口。解决方法是把rreq_retries降到 1 或 2同时把net_diameter改成实际网络直径可覆盖的值。再不行在 AODV 的转发逻辑里加一个反压当本节点看到同一 RREQ 的转发次数超过 3 次直接丢弃不做无脑洪泛。这已经属于协议级修改了但对自组网吞吐的提升非常显著。6. 验证和进阶用真实流量确认 AODV 在干活而不是“假活”6.1 用 /proc 和 rt 表确认路由创建模块加载后不要急着连业务。先构造最简单的两个节点 A、B都启用 ad-hocIP 设为同网段然后在 A 上ping 10.0.0.2。这时 A 会发出 RREQB 回 RREP等到 ping 通了立刻查看 A 的 proc 接口cat /proc/aodv/routes预期能看到一条到 10.0.0.2 的路由包含跃点数、下一跳 MAC 或 IP。同时看ip route show table all是否多了以dev wlan0为出口的主机路由。如果 proc 里有路由但ip route里没有说明 AODV 没有把路由插入到内核 FIB常见原因是fib_insert_node调用失败这时 dmesg 里通常会有route not added之类的日志。别忽略这一步——很多移植后的版本proc 里是“自己管理”的路由表但转发时内核根本不用它白搭。6.2 用 ping 和 iperf 做端到端验证路由建立后做两个层级的验证。第一层级ping -c 20 10.0.0.2看丢包率和抖动。AODV 邻居断开再重建时会有一次连续的丢包窗口这个窗口时长应当与你的参数相符。比如hello_interval1000, allowed_hello_loss2时丢包窗口大约 2~3 秒。如果远超这个数说明本地修复没起作用它直接等 RREQ/RREP 全流程超时了。第二层级是 iperfiperf -s # 在 B 上 iperf -c 10.0.0.2 -t 30 -b 10M # 在 A 上观察吞吐是否稳定。如果吞吐从 5M 掉到 0 再恢复那是路由重建的代价可以接受如果一直很低且同时在 A 上tcpdump -i wlan0 arp or udp port 654看到大量重复的广播包说明广播风暴迫使网卡退避严重。这时对比调参前后的吞吐变化基本能确认是rreq_retries或 HELLO 间隔的问题。6.3 进阶打补丁到新内核需要改哪些地方如果你想把 v2.2.2 移植到 4.x/5.x 内核最常见要动的是这四处proc_create的变元。新版要求传struct proc_dir_entry *为四参数且 read/write 回调改为file_operations。skb_dst(skb)的设置。旧代码直接赋值给skb-dst新内核要求用skb_dst_set(skb, dst)并把dst强制转换struct dst_entry *。否则编译能过运行时崩。netlink_kernel_create的注册方式。新版改为netlink_kernel_cfg结构体且要求提供.input函数。如果 v2.2.2 用的是旧式五参数必须改。路由查找函数ip_route_output_key的参数。新版需要两个参数struct net *和struct flowi4 *不能用旧的rtable *。我自己的血泪经验是先把 4.19 内核上跑通再用 5.10 跑老老实实对照drivers/net/里同类老的协议模块是任何改的。不要幻想一步到 6.xAODV 这类直接操作 skb 和 FIB 的模块每跨一个大版本都要重新面对 API 变化。改完之后跑一遍 6.1 和 6.2 的验证确认路由建立时间和吞吐没有劣化才算移植完成。希望这篇笔记能帮你在 AODV 内核路由上少走几圈弯路尤其是编译参数和无线网卡这两座山翻过去之后剩下的就是愉快的调参时光了。本文还有配套的精品资源点击获取
返回列表