Rocky9.0去堆叠双发arp(支持“ARP 广播双发”)
摘要
在去堆叠/MLAG 场景下,默认 bonding 只会以单口回复 ARP,另一台交换机收不到 ARP Reply。本文在 Linux bonding 驱动中增加参数 arp_broadcast_mode
,当开启时对 ARP 包临时切换到 广播模式,实现双口同时发 ARP Reply。文内提供可直接套用的补丁与完整编译、替换、验证流程,并覆盖常见坑位(Module.symvers 不匹配、Secure Boot、NetworkManager/ifcfg、sshd OpenSSL 版本错配等)。
基础信息
-
OS:Rocky Linux 9.0(RHEL 9 系列适用)
-
内核:
5.14.0-70.13.1.el9_0.x86_64
-
bonding 模式:
mode=6
(balance-alb,其他模式同理) -
构建工具:
gcc make bison flex elfutils-libelf-devel openssl-devel perl kernel-devel kernel-headers
关键点:运行内核版本必须与kernel-devel 版本一致,否则会出现
Module.symvers missing / undefined symbol
等问题。
一、环境准备
1. 确认内核版本
uname -r
例如:5.14.0-70.13.1.el9_0.x86_64
2. 安装依赖工具链
dnf install -y gcc make bison flex elfutils-libelf-devel \openssl-devel perl
注:依据实际情况,如有其他依赖正常安装即可。 PS:最好直接报报错给大模型,输出命令执行就行。
3. 安装内核开发包(必须和 uname -r
一致)
dnf download --source kernel-$(uname -r) rpm -ivh kernel-*.src.rpm
如果下载的不对dnf,你就要手动安装如下三个包。
rpm -ivh kernel-5.14.0-70.13.1.el9_0.src.rpm
rpm -ivh kernel-devel-5.14.0-70.13.1.el9_0.x86_64.rpm
rpm -ivh kernel-headers-5.14.0-70.13.1.el9_0.x86_64.rpm
源码安装后会解压到:
ls ~/rpmbuild/BUILD/kernel-<version>/
其实就是安装这三个包,如果你看到安装的不是, 那你就自己下载就行。
https://dl.rockylinux.org/stg/rocky/9.0-sources-only/tree/Packages/k/kernel-5.14.0-70.13.1.el9.src.rpm
https://downloads.rockylinux.org/vault/rocky/9.0/devel/x86_64/kickstart/Packages/k/kernel-devel-5.14.0-70.13.1.el9_0.x86_64.rpm
https://downloads.rockylinux.org/vault/rocky/9.0/devel/x86_64/kickstart/Packages/k/kernel-headers-5.14.0-70.13.1.el9_0.x86_64.rpm
验证:
ls -ld /lib/modules/$(uname -r)/build
ls /usr/src/kernels/$(uname -r)/
正常会看到对应源码目录存在。
二、补丁说明(新增模块参数 + 仅对 ARP 强制临时广播)
思路:
-
在
bond_main.c
中新增可控参数:
/* ---- ARP broadcast extension ---- */
static int arp_broadcast_mode = 0;
module_param(arp_broadcast_mode, int, 0644);
MODULE_PARM_DESC(arp_broadcast_mode,"broadcast ARP packet to all slaves, 0=off(default), 1=on.");
2.在 __bond_start_xmit()
函数里,仅当是 ARP 且开关=1 时,将 switch (BOND_MODE(bond))
动态替换为 BOND_MODE_BROADCAST
,从而实现仅对 ARP双发。对其他报文不影响原有模式。
三、开始打补丁(适配 RHEL9 5.14 源码)
文件路径:~/rpmbuild/BUILD/kernel-<version>/
drivers/net/bonding/bond_main.c
说明:上下文行适配 RHEL9 5.14;若行号有偏差,可采用“手工改动”或下文“一键 sed”方案。
我测试的路径:/root/rpmbuild/BUILD/kernel-5.14.0-70.13.1.el9_0/linux-5.14.0-70.13.1.el9.x86_64/drivers/net/bonding
增加1:打开之后放在include部分第一行就行
#include <linux/moduleparam.h>
增加2:放在#include部分最后就行。
/* ---- ARP broadcast extension ---- */
static int arp_broadcast_mode = 0;
module_param(arp_broadcast_mode, int, 0644);
MODULE_PARM_DESC(arp_broadcast_mode,"broadcast ARP packet to all slaves, 0=off(default), 1=on.");
在我的测试里, 我把#include <linux/moduleparam.h> 放在了首行,如图:
增加3:检索 __bond_start_xmit部分,
注意,+号是增加的内容, +号不用复制。-号部分是删除的。
static netdev_tx_t __bond_start_xmit(struct sk_buff *skb, struct net_device *dev){struct bonding *bond = netdev_priv(dev);
+
+ /* ARP-only broadcast trigger */
+ bool is_arp = (vlan_get_protocol(skb) == cpu_to_be16(ETH_P_ARP));
+ bool do_bcast = arp_broadcast_mode && is_arp;if (bond_should_override_tx_queue(bond) &&!bond_slave_override(bond, skb))return NETDEV_TX_OK;#if IS_ENABLED(CONFIG_TLS_DEVICE)if (skb->sk && tls_is_sk_tx_device_offloaded(skb->sk))return bond_tls_device_xmit(bond, skb, dev);#endif
- switch (BOND_MODE(bond)) {
+ switch (do_bcast ? BOND_MODE_BROADCAST : BOND_MODE(bond)) {case BOND_MODE_ROUNDROBIN:return bond_xmit_roundrobin(skb, dev);case BOND_MODE_ACTIVEBACKUP:
如果补丁由于上下文差异无法直接打入,建议手工按“三处改动”完成:①在 include 区增加
#include <linux/moduleparam.h>
;②在全局参数区增加arp_broadcast_mode
参数;③在__bond_start_xmit()
里加is_arp/do_bcast
并替换switch
。
四、(可选)一键 sed
注入脚本
在源码根目录(Makefile
所在处)执行:
# 1) 确保引入 moduleparam 头(若已有不会重复)
grep -q 'linux/moduleparam.h' drivers/net/bonding/bond_main.c || \sed -i '/#include <linux\/preempt.h>/a #include <linux/moduleparam.h>' drivers/net/bonding/bond_main.c# 2) 在 "bonding_priv.h" 之后插入参数块(若已存在请忽略)
awk '
/#include "bonding_priv.h"/ && !done {print;print "";print "/* ---- ARP broadcast extension ---- */";print "static int arp_broadcast_mode = 0;";print "module_param(arp_broadcast_mode, int, 0644);";print "MODULE_PARM_DESC(arp_broadcast_mode,";print " \"broadcast ARP packet to all slaves, 0=off(default), 1=on.\");";print "";done=1; next
}1' drivers/net/bonding/bond_main.c > .bond_main.c.new && mv .bond_main.c.new drivers/net/bonding/bond_main.c# 3) 在 __bond_start_xmit() 中插入 is_arp/do_bcast(若已插过会失败,忽略即可)
sed -i '/struct bonding \*bond = netdev_priv(dev);/a \ \ \ \ bool is_arp = (vlan_get_protocol(skb) == cpu_to_be16(ETH_P_ARP));\n\ \ \ \ bool do_bcast = arp_broadcast_mode && is_arp;' drivers/net/bonding/bond_main.c# 4) 仅替换 __bond_start_xmit() 中的第一个 switch
awk 'BEGIN{infunc=0; depth=0}/static netdev_tx_t __bond_start_xmit\(struct sk_buff \*skb, struct net_device \*dev\)/{infunc=1}{if(infunc && $0 ~ /switch \(BOND_MODE\(bond\)\) \{/ && !done){sub(/switch \(BOND_MODE\(bond\)\) \{/, "switch (do_bcast ? BOND_MODE_BROADCAST : BOND_MODE(bond)) {"); done=1}print}/{/{if(infunc) depth++}/}/{if(infunc){depth--; if(depth==0) infunc=0}}
' drivers/net/bonding/bond_main.c > .bond_main.c.new && mv .bond_main.c.new drivers/net/bonding/bond_main.c
五、编译 bonding 模块
1)我们在第一步环境准备时已经安装好相关源码包了
2)进入源码目录并构建(外部构建,确保符号版本匹配)
cd ~/rpmbuild/BUILD/kernel-5.14.0-70.13.1.el9_0/linux-5.14.0-70.13.1.el9.x86_64# 使用当前运行内核的配置
cp -v /boot/config-$(uname -r) .config
yes "" | make olddefconfig# 准备头文件(只有第一次需要)
make prepare && make modules_prepare# 用匹配内核的 build 来编译指定子目录模块(推荐)
make -C /lib/modules/$(uname -r)/build M=$PWD/drivers/net/bonding clean
make -C /lib/modules/$(uname -r)/build M=$PWD/drivers/net/bonding modules -j"$(nproc)"
产物:drivers/net/bonding/bonding.ko
提示 “Skipping BTF generation … vmlinux 不在” 不影响加载。
六、部署并让系统优先用自编译模块
方案 A(推荐):放到 weak-updates
并覆盖 modprobe 安装行为
# 放置新模块
mkdir -p /lib/modules/$(uname -r)/weak-updates/bonding/
cp -v drivers/net/bonding/bonding.ko /lib/modules/$(uname -r)/weak-updates/bonding/
depmod -a# 持久化默认参数(开启双发)
echo 'options bonding arp_broadcast_mode=1' > /etc/modprobe.d/bonding.conf# 强制 modprobe 始终加载我们这份(避免回退到内置 .xz)
cat >/etc/modprobe.d/override-bonding.conf <<'EOF'
install bonding /sbin/insmod /lib/modules/$(uname -r)/weak-updates/bonding/bonding.ko $CMDLINE_OPTS
EOF
之后
modprobe bonding
会优先加载我们放在 weak-updates 的模块。
Secure Boot 提醒:若启用 Secure Boot,加载未签名模块会报 Required key not available
。需关闭 Secure Boot 或对 bonding.ko
本地签名并导入 MOK。
重新加载
modprobe -r bonding 2>/dev/null || true modprobe bonding
验证参数存在:
modinfo bonding | egrep 'filename|vermagic|parm'
# filename 应指向 weak-updates/bonding/bonding.ko
# parm 应含 arp_broadcast_modecat /sys/module/bonding/parameters/arp_broadcast_mode # 应=1
注意:有可能你编译的内核和使用的内核一样, 所以加载的bonding需要注意一下,使用的是新编译的内核, 还是老内核。
七、网络脚本参考(ifcfg)
/etc/sysconfig/network-scripts/ifcfg-bond0
DEVICE=bond0
NAME=bond0
TYPE=Bond
ONBOOT=yes
BOOTPROTO=none
IPADDR=10.10.10.11
NETWORK=24
GATEWAY=10.10.10.1
NM_CONTROLLED=yes
BONDING_OPTS="mode=6 miimon=100"
/etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
MASTER=bond0
SLAVE=yes
NM_CONTROLLED=yes
/etc/sysconfig/network-scripts/ifcfg-eth1
DEVICE=eth1
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
MASTER=bond0
SLAVE=yes
NM_CONTROLLED=yes
启用:
nmcli con reload
nmcli con up bond0
cat /proc/net/bonding/bond0
八、功能验证(ARP 双发)
方法一:远端 ping
触发 ARP
# 终端A
tcpdump -ni eth0 arp -vv
# 终端B
tcpdump -ni eth1 arp -vv
在另一台同网段机器 ping bond0_IP
,你应看到同一时间两个口都有 ARP Reply。
方法二:本机主动 arping
arping -c 3 -I bond0 <对端IP或网关IP>
# 同时在 eth0/eth1 抓包观察
九、常见坑位与排障速查
-
Module.symvers 缺失/符号未定义
-
现象:
Module.symvers missing
、undefined symbol …
-
处理:确保
kernel-devel
与uname -r
一致;使用
make -C /lib/modules/$(uname -r)/build M=...
外部构建。
-
加载到旧模块(.xz)
-
现象:
modinfo bonding | grep filename
显示.xz
-
处理:
modprobe -r bonding
→depmod -a
→ 用override-bonding.conf
强制insmod
我们的weak-updates
版本。
-
Secure Boot 导致无法加载
-
报错:
Required key not available
-
处理:关闭 Secure Boot 或签名模块并导入 MOK。
-
bond0 为 DOWN 或无 IP
-
检查:
cat /proc/net/bonding/bond0
,确认 slave 绑定与MII Status
。 -
给 bond0 配置 IP/PREFIX,
BONDING_OPTS
中设置miimon=100
; -
nmcli con up bond0
后再ip a show bond0
。
-
sshd 起不来(OpenSSL 版本错配)
-
错误:
OpenSSL version mismatch. Built against 30000010, you have 30200020
-
处理:升级
openssh-server
到与系统 OpenSSL 对齐的版本(Rocky 有8.7p1-45.el9.rocky.0.1
等),或降级 OpenSSL(不推荐)。
十、结语
本文通过极小的代码改动,为 bonding 增加了 arp_broadcast_mode
参数,实现仅对 ARP 报文的“临时广播”,从而满足去堆叠/MLAG 场景下的“双发 ARP”需求。方案对非 ARP 流量无侵入,并保留原有模式行为。生产环境上线前,建议在维护时段配合交换机侧做充分验证。
附:最小操作清单(懒人版)
# 0) 安装匹配的 kernel-devel
rpm -ivh --oldpackage /opt/kernel-devel-$(uname -r).x86_64.rpm# 1) 修改源码 (按上文补丁三处改动)
cd ~/rpmbuild/BUILD/kernel-*/linux-*/ # 进入源码根
# (可用上文 sed 快捷脚本)# 2) 构建
cp -v /boot/config-$(uname -r) .config
yes "" | make olddefconfig
make prepare && make modules_prepare
make -C /lib/modules/$(uname -r)/build M=$PWD/drivers/net/bonding clean
make -C /lib/modules/$(uname -r)/build M=$PWD/drivers/net/bonding modules -j"$(nproc)"# 3) 部署 + 持久化参数
mkdir -p /lib/modules/$(uname -r)/weak-updates/bonding/
cp -v drivers/net/bonding/bonding.ko /lib/modules/$(uname -r)/weak-updates/bonding/
depmod -a
echo 'options bonding arp_broadcast_mode=1' > /etc/modprobe.d/bonding.conf
cat >/etc/modprobe.d/override-bonding.conf <<'EOF'
install bonding /sbin/insmod /lib/modules/$(uname -r)/weak-updates/bonding/bonding.ko $CMDLINE_OPTS
EOF# 4) 重载并验证
modprobe -r bonding 2>/dev/null || true
modprobe bonding
modinfo bonding | egrep 'filename|parm'
cat /sys/module/bonding/parameters/arp_broadcast_mode# 5) 抓包验证
tcpdump -ni eth0 arp -vv &
tcpdump -ni eth1 arp -vv &
# 在另一台同网段 ping bond0_IP,观察两口同时 ARP Reply