内核更新后 WireGuard 模块无法加载?请检查这些
外部模块必须针对将要加载它的内核构建,而不是磁盘上最新的头文件包。重新构建之前,请先确认正在运行的内核版本。
你的隧道昨天还能用,更新之后机器重启,现在接口起不来了。在把 WireGuard 作为外部模块构建的系统上,常见原因是磁盘上的模块属于与当前运行内核不同的版本。同一台机器上名字相似的头文件并不能证明这种匹配。只有版本字符串才能决定这一点。
正在运行的内核还是已安装的内核
一次更新会安装新的内核包,并把旧内核留在磁盘上,直到机器重启。重启后正在运行的版本发生变化,任何针对旧版本构建的模块都会失效,尽管它的文件仍然存在。这个差距解释了那个常见的报告:机器在重启前一切正常,重启后就坏了,而中间没有任何配置改动。它同样解释了为什么重装头文件有时看起来解决了问题,有时却没有。
你安装的头文件包可能对应最新的内核包,而不是真正启动的那个内核,而包名里没有任何东西能区分这些情况;只有版本字符串可以。在检查装了什么之前,先检查正在运行的是什么。
重建前的三项检查
第一项检查是哪个内核正在运行。第二项是该版本是否有模块,以及能否找到。第三项是内核是否已经加载了它。第四项问题,即接口能否创建,检验的是权限而不是模块,所以它属于重建之后,而不在这个列表里。
下面的命令在不改动任何东西的情况下回答前三项。继续之前请阅读每一项结果,因为每个答案排除的修复方式都不同。
uname -r
ls -l /lib/modules/$(uname -r)/build
modinfo wireguard
lsmod | grep wireguard
如果第二行报告路径不存在,说明当前运行内核的构建环境缺失,你编译的任何东西都不会与它匹配。如果路径存在但 modinfo 失败,模块可能根本没有安装,这在把实现直接放进树里的内核上是正常的。如果 modinfo 成功而 lsmod 什么也不显示,说明模块存在但没有加载,这是加载阶段的问题,而不是构建问题。每种结果都会把你引向不同的方向,所以读懂结果比命令本身更重要。
针对正在运行的版本构建
外部模块是针对准备好的内核树编译的,内核的外部模块指南描述的是使用属于当前运行内核的构建目录,而不是版本号看起来最高的那个目录。那个构建目录是内核包维护的符号链接,所以它指向当前启动版本对应的树。这种间接性正是关键:它替你排除了一个否则会做错的选择。明确指定它,目标就不会有歧义。
当内核树只是准备好而没有被完整构建时,有一个细节很重要。准备目标不会生成某些外部构建所需的符号版本文件,所以一棵满是头文件的树并不自动等于完整的构建环境。如果构建因为缺少符号而失败,那个文件就是第一个该看的地方。
sudo make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
sudo make -C /lib/modules/$(uname -r)/build M=$(pwd) modules_install
sudo depmod -a
当打包的头文件滞后时
在自己打包内核的发行版和单板计算机上,头文件可能落后版本几天甚至几周。头文件包可能名字正确但内容更旧,于是构建能够通过编译,却在加载时而不是编译时失败。混合来源会让情况更糟:发行版内核配上第三方仓库的头文件,或者厂商内核配上通用头文件,可能编译得很干净,却仍然因为配置不同而被运行中的内核拒绝。仅凭版本号并不能解决这一点。
如果机器跟随实验性内核通道,请有意识地决定是留在那里并配上匹配的构建产物,还是回到受支持的软件包集合。反复更新并指望各部分刚好对上并不是计划,因为同样的不匹配每次都会重现。
重建后验证接口
安装成功之后,创建接口才是真正重要的测试,接着要读取内核实际加载了什么,以及如果它拒绝了日志说了什么。能加载的模块和创建不了的接口是原因不同的两种失败。下面三条命令把两者区分开。
sudo ip link add wg9 type wireguard
lsmod | grep wireguard
sudo dmesg | tail -5
如果模块拒绝加载,内核日志会写明原因,而版本不匹配只是可能之一。在再次构建之前读那一行,因为第二次相同的构建会花掉同样的十分钟,并给出同样的拒绝。之后删除这个临时接口,让下一次测试从干净状态开始。