摘要
在去堆疊/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 /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/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 arp_validate=all arp_all_targets=all lp_interval=1 updelay=200 downdelay=200"
參數說明:
-
arp_validate=all + arp_all_targets=all
:用 ARP 健康檢查,兩個目標都 OK 才算通過(配合你環境的網關/探測 IP) -
lp_interval=1
:學習包間隔(ALB 需要) -
打開你打的補丁開關:
/etc/modprobe.d/bonding.conf
寫options bonding arp_broadcast_mode=1
-
交換機側:兩個上聯口不要加入聚合,保持二層轉發正常;必要時放寬 MAC 移動抑制/告警閾值,觀察是否有 MAC flap
-
updelay=200
-
當檢測到鏈路恢復(link up)時,bond 不會立刻啟用這塊口,而是要等 200ms 連續穩定后才認為口真正可用。
-
避免“抖動”或短暫恢復又掉線的情況。
-
-
downdelay=200
-
當檢測到鏈路斷開(link down)時,bond 不會立刻標記為 down,而是要連續 200ms 檢測失敗才認為口真的掛了。
-
避免偶發丟包/閃斷導致的誤判。
-
/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