FreeBSD 双网卡自动故障转移:用 devd 实现实时路由切换
场景:一台 FreeBSD 机器同时插了有线网卡(em0)和无线网卡(wlan0),都连同一个路由器。希望有线优先,无线备用,有线断了自动切无线,有线恢复了自动切回,全程无人值守。
一、问题背景
最开始遇到的问题是:双网卡都通过 DHCP 获取 IP,两个网卡都自动加了默认路由,导致系统出现两个默认路由:
default 192.168.0.1 UGS em0 default 192.168.0.1 UGS wlan0这时候 FreeBSD 默认走 ECMP(等价多路径),轮流往两个网卡发包。但无线网卡有时候是no carrier状态(没连 WiFi),这时候就会出现:
- DNS 解析时好时坏
- NTP 一直报
Name could not be resolved - 有时候 ping 外网通,有时候不通
- 网络非常不稳定
二、方案选型对比
我先后试了几种方案,从差到好排一下:
❌ 方案 1:手动删路由
每次开机手动删 wlan0 的默认路由。
- 缺点:重启就忘,治标不治本
⚠️ 方案 2:cron 每分钟轮询
写个脚本每分钟跑一次,检查 em0 是否 active,如果 active 就删掉 wlan0 的默认路由。
- 缺点:响应慢,最坏情况要等 1 分钟才切换;一直空跑浪费资源
✅ 方案 3:devd 事件驱动(最终方案)
利用 FreeBSD 自带的devd守护进程,监听网卡 link up/down 事件,网卡状态一变就立即触发脚本。
- 优点:秒级实时响应,不用轮询,系统原生支持,稳定可靠
三、最终实现方案
3.1 整体思路
┌─────────────┐ link up/down ┌─────────────┐ │ em0 有线 │ ────────────────────> │ │ └─────────────┘ │ │ │ devd │ ──> 触发 route-failover 脚本 ┌─────────────┐ link up/down │ 事件守护 │ │ wlan0 无线 │ ────────────────────> │ │ └─────────────┘ └─────────────┘脚本逻辑非常简单:
- 如果 em0 是 active 状态 → 删掉 wlan0 的默认路由(只走有线)
- 如果 em0 是 down 状态 → 什么都不做(让 wlan0 的 DHCP 默认路由生效)
💡 为什么不用手动加路由?因为两个网卡都是 DHCP,当 wlan0 连上 WiFi 时,DHCP 客户端会自动帮我们加好默认路由。我们只需要在有线正常的时候把它删掉就行。
3.2 第一步:写路由切换脚本
创建脚本/usr/local/sbin/route-failover:
#!/bin/sh# 双网卡自动故障转移 - 有线em0优先,无线wlan0备用# 由 devd 事件触发,网卡状态变化时自动执行# 检查 em0 是否连接正常em0_active=$(ifconfigem0|grep-c"status: active")if["$em0_active"-gt0];then# 有线正常,删除 wlan0 的默认路由(如果存在)route delete default-interfacewlan02>/dev/nullelse# 有线断了,不处理,让 wlan0 的 DHCP 默认路由生效:fi加执行权限:
chmod+x /usr/local/sbin/route-failover3.3 第二步:配置 devd 事件监听
创建 devd 配置文件/usr/local/etc/devd/route-failover.conf:
# 网卡 link 事件触发路由切换 # 监听 em0 有线网卡 notify 100 { match "system" "IFNET"; match "subsystem" "em0"; action /usr/local/sbin/route-failover; }; # 监听 wlan0 无线网卡 notify 100 { match "system" "IFNET"; match "subsystem" "wlan0"; action /usr/local/sbin/route-failover; };重启 devd 让配置生效:
servicedevd restart四、工作原理详解
4.1 devd 是什么?
devd是 FreeBSD 自带的设备事件守护进程,开机就运行。当系统硬件状态发生变化时(比如插U盘、插网线、WiFi连接),内核会发出事件,devd 收到事件后根据配置执行对应的 action。
我们这里监听的是IFNET(网络接口)类事件,当 em0 或 wlan0 的 link 状态变化时(up/down),就自动执行我们的路由脚本。
4.2 为什么这样设计最稳?
- 不用手动加路由:两个网卡都用 DHCP,IP 和网关都自动获取,我们只需要管默认路由的去留
- 有线优先:只要有线插着,就强制走有线,无线只做备用
- 自动切回:有线一恢复,脚本立刻删掉无线路由,自动切回有线
- 无状态:脚本不需要记录当前走哪个网卡,每次都根据 em0 实际状态判断,简单可靠
五、验证效果
5.1 正常状态(有线 + 无线都连着)
$netstat-rn-finet|grepdefault default192.168.0.1 UGS em0✅ 只有一个默认路由,走有线 em0
5.2 拔掉网线
# 拔网线瞬间,devd 自动触发脚本$netstat-rn-finet|grepdefault default192.168.0.1 UGS wlan0✅ 自动切到无线 wlan0,网络不断
5.3 插回网线
# 插网线瞬间,devd 自动触发脚本$netstat-rn-finet|grepdefault default192.168.0.1 UGS em0✅ 自动切回有线 em0
六、注意事项
- 网卡名要对应:配置文件里的
em0和wlan0要和你实际的网卡名一致,用ifconfig查看 - 网关地址:脚本里没写死网关,因为我们只是删除路由,添加路由是 DHCP 自动做的,所以网关变了也不用改脚本
- 为什么不用负载均衡?:两个网卡连同一个路由器,出口是同一个公网 IP。负载均衡不仅没意义,还可能因为同一个连接的包走不同路径导致连接断开,主备模式是最稳定的选择
- 切换时间:devd 事件触发是毫秒级的,几乎感觉不到断网
七、总结
这个方案的优点:
- ✅实时响应:网卡状态一变就切,不用等
- ✅系统原生:用的都是 FreeBSD 自带功能,没装额外软件
- ✅简单可靠:脚本只有几行,逻辑清晰,不容易出 bug
- ✅开机自启:devd 是系统服务,重启后自动生效
如果你的 FreeBSD 机器也有双网卡的需求,强烈推荐用 devd 事件驱动的方式,比 cron 轮询优雅太多了。
作者:小浣熊运维笔记
平台:FreeBSD 15.1-STABLE
环境:有线 em0 + 无线 wlan0,同一路由器