背景
某台便携式 Linux 设备同时具备两种联网方式:
- 默认使用 USB 共享网络作为兜底连接;
- 到达可用 Wi-Fi 环境后,手动执行一个 Wi-Fi 连接脚本接入无线网络;
- Wi-Fi 连接成功后,应自动成为最高优先级出口;
- Wi-Fi 断开后,应自动回落到 USB 共享网络;
- 不希望引入额外的图形化网络管理器;
- 不希望多套 DHCP 客户端同时管理同一个接口。
系统中原本有几类自定义辅助脚本:
<Wi-Fi连接脚本>
<Wi-Fi断开脚本>
<Wi-Fi状态脚本>
其中,连接脚本用于扫描并连接 Wi-Fi,断开脚本用于手动断开 Wi-Fi。系统本身则启用了 dhcpcd,用于统一管理 DHCP、IP 地址、DNS、默认路由和 metric 优先级。
最初发现的问题
一次系统自检中发现,设备上同时存在 dhcpcd 和 dhclient,并且二者都在处理同一个无线接口。
这类情况容易造成网络栈混乱:
dhcpcd 管理 <无线接口>
dhclient 也绑定 <无线接口>
二者都可能修改 IP、DNS 和默认路由
同时还观察到:
- USB 共享网络接口正常获得地址;
- Wi-Fi 接口在某些情况下也能获得地址;
- 默认路由有时由 Wi-Fi 接管,有时回落到 USB;
- 日志中出现 DHCP、路由更新相关的重复行为;
- 自动化检查工具曾经给出过不完全可靠的结论。
这里的一个重要教训是:自动化代理可以帮助收集信息,但安全和网络相关结论必须回到原始命令输出核对。尤其在高权限环境下,不能让代理在未经确认的情况下直接执行修复、重启服务或修改网络状态。
需求澄清
这台设备不是固定服务器,而是便携式设备,因此网络策略与普通服务器不同。
实际需求可以概括为:
默认 USB 共享网络在线;
手动连接 Wi-Fi;
Wi-Fi 连上后优先级最高;
Wi-Fi 断开后自动回退 USB;
只保留一套 DHCP / 路由 / DNS 管理逻辑。
因此,不适合让 Wi-Fi 连接脚本同时负责 Wi-Fi 认证、DHCP 请求、DNS 更新和默认路由修改。更合理的分工是:
dhcpcd:
统一管理 DHCP、IP、DNS、路由、metric。
<Wi-Fi连接脚本>:
只负责 Wi-Fi 扫描、选择 SSID、生成 wpa_supplicant 配置、启动 Wi-Fi 认证。
<Wi-Fi断开脚本>:
只负责断开 Wi-Fi 认证进程,必要时关闭无线接口。
dhclient:
不再参与。
只读排查
正式修改前,先进行只读检查,重点确认当前到底有哪些网络组件在运行:
ps aux | grep -E 'dhcpcd|dhclient|wpa_supplicant' | grep -v grep
ip -br addr
ip route
resolvectl status
systemctl status dhcpcd --no-pager
同时检查自定义脚本内容:
type -a <Wi-Fi连接脚本>
type -a <Wi-Fi断开脚本>
type -a <Wi-Fi状态脚本>
bash -n <Wi-Fi连接脚本路径>
bash -n <Wi-Fi断开脚本路径>
grep -nE 'dhclient|dhcpcd|wpa_supplicant|ip route|ip addr|systemctl' \
<Wi-Fi连接脚本路径> <Wi-Fi断开脚本路径>
检查结果表明,dhcpcd 已经在系统中启用并运行,而旧版 Wi-Fi 连接脚本中仍然包含类似逻辑:
dhclient -r "$IFACE"
dhclient "$IFACE"
ip route del default ...
ip route add default ...
这意味着脚本在启动 Wi-Fi 认证后,又手动释放 DHCP、重新请求 DHCP,并修改默认路由。这样会与 dhcpcd 的职责重叠,形成“双网络管理器”冲突。
根因
根因不是 Wi-Fi 本身,也不是 USB 共享网络本身,而是职责边界混乱:
系统层面:
dhcpcd 已经负责网络管理。
脚本层面:
<Wi-Fi连接脚本> 又手动调用 dhclient 并修改默认路由。
结果:
同一个无线接口被 dhcpcd 和 dhclient 重复管理。
旧脚本虽然在某些时候“看起来能连上网”,但它是通过手动 DHCP 和手动路由强行接管网络。长期来看,这种做法会增加不稳定性,也会让后续排障困难。
修复原则
修复时遵循几个原则:
- 只保留
dhcpcd作为 DHCP / DNS / 路由 / metric 管理器; - 不引入额外网络管理器;
- 不修改当前正在运行的网络状态;
- 修改前必须备份;
- 先只改自定义脚本,不动系统网络配置;
- 不在修改过程中执行连接脚本、断开脚本、
dhclient或网络重启; - 修改完成后由人工手动重启或手动验证。
备份使用 cp -a,以保留原权限、属主和时间戳:
ts=$(date +%Y%m%d-%H%M%S)
cp -a <Wi-Fi连接脚本路径> <Wi-Fi连接脚本路径>.before-dhcpcd-only-"$ts"
cp -a <Wi-Fi断开脚本路径> <Wi-Fi断开脚本路径>.before-dhcpcd-only-"$ts"
第一轮修改:移除 dhclient 和手动路由
Wi-Fi 连接脚本中删除以下逻辑:
dhclient -r "$IFACE"
dhclient "$IFACE"
ip route del default ...
ip route add default ...
Wi-Fi 断开脚本中删除:
dhclient -r "$IFACE"
同时将原本过宽的进程清理逻辑:
pkill wpa_supplicant
收窄为只匹配目标无线接口,避免误杀其他 wpa_supplicant 进程:
pkill -f "wpa_supplicant .* -i ${IFACE}( |$)" 2>/dev/null || true
修改后执行静态检查:
bash -n <Wi-Fi连接脚本路径>
bash -n <Wi-Fi断开脚本路径>
grep -nE 'dhclient|ip route add|ip route del' \
<Wi-Fi连接脚本路径> <Wi-Fi断开脚本路径> || true
期望结果是:
bash -n 通过;
grep 无输出;
脚本中不再残留 dhclient 和手动默认路由操作。
第一轮验证结果
重启后,默认状态下只剩 dhcpcd 管理网络,dhclient 不再出现。USB 共享网络正常获得地址,默认路由走 USB 共享网络。
此时状态符合“默认 USB 兜底”的需求。
但首次执行新版 Wi-Fi 连接脚本后发现,脚本启动了 wpa_supplicant,却过早输出结果。Wi-Fi 尚未完成关联,IPv4 地址和默认路由也尚未出现。
这说明第一轮修复已经解决了 dhclient 冲突,但脚本还缺少“等待 Wi-Fi 关联和等待 DHCP 完成”的逻辑。
第二轮修改:增加等待和诊断
第二轮只修改 Wi-Fi 连接脚本,不修改 Wi-Fi 断开脚本和系统网络配置。
新增逻辑:
- 启动
wpa_supplicant后最多等待 30 秒; - 优先使用
wpa_cli检查wpa_state=COMPLETED; - 如果
wpa_cli不可用,则使用iw检查无线连接状态; - Wi-Fi 关联成功后,继续等待
dhcpcd给无线接口分配 IPv4; - 等待结束后输出完整诊断信息。
诊断输出包括:
wpa_cli -i <无线接口> status
iw dev <无线接口> link
ip -br addr
ip route
resolvectl status
同时仍然保持禁止项:
不调用 dhclient;
不执行 ip route add;
不执行 ip route del;
不手动修改默认路由。
修改后再次执行:
bash -n <Wi-Fi连接脚本路径>
grep -nE 'dhclient|ip route add|ip route del' <Wi-Fi连接脚本路径> || true
确认没有危险逻辑残留。
最终验证
手动执行 Wi-Fi 连接脚本后,Wi-Fi 成功关联:
wpa_state=COMPLETED
ssid=<已脱敏>
ip_address=<Wi-Fi网段地址>
无线接口获得 IPv4 地址,路由表中出现两条默认路由:
default via <Wi-Fi网关> dev <无线接口> metric <较低值>
default via <USB共享网关> dev <USB共享接口> metric <较高值>
这说明:
Wi-Fi 默认路由优先;
USB 共享网络仍然保留为备用;
dhcpcd 正常负责 IP / DNS / 路由 / metric;
dhclient 没有重新出现。
随后执行 Wi-Fi 断开脚本,Wi-Fi 认证进程被清理,无线接口关闭,默认路由回落到 USB 共享网络:
default via <USB共享网关> dev <USB共享接口> metric <较高值>
DNS 也回到 USB 共享网络对应的网关。再次检查进程,仍然没有 dhclient。
最后通过连通性验证:
ip route get <外网测试地址>
ping -c 3 <USB共享网关>
ping -c 3 <外网测试地址>
ping -c 3 <域名测试地址>
结果显示:
- 外网路由正常;
- USB 共享网络网关可达;
- 外网 IP 可达;
- DNS 解析正常;
- 丢包率为 0%。
至此,便携式网络切换逻辑闭环完成。
最终架构
修复后的职责划分如下:
dhcpcd
├─ 负责 DHCP
├─ 负责 IPv4 地址
├─ 负责 DNS
├─ 负责默认路由
└─ 负责 metric 优先级
<Wi-Fi连接脚本>
├─ 打开无线接口
├─ 扫描 Wi-Fi
├─ 选择 SSID
├─ 生成 wpa_supplicant 配置
├─ 启动 wpa_supplicant
├─ 等待 Wi-Fi 关联
└─ 等待 dhcpcd 分配 IPv4
<Wi-Fi断开脚本>
├─ 停止目标无线接口对应的 wpa_supplicant
└─ 关闭无线接口
USB 共享网络
└─ 默认兜底网络
Wi-Fi
└─ 连接后成为最高优先级出口
经验总结
这次排查的关键不是简单删除某个服务,而是重新划清职责边界。
便携式设备的网络需求通常不同于固定服务器:USB 共享网络可以作为稳定兜底,Wi-Fi 则作为手动接入后的高优先级出口。只要 metric 配置正确,就不需要脚本手动修改默认路由。
更重要的是,DHCP 客户端只能保留一套。dhcpcd 和 dhclient 同时管理同一个接口时,即使短期内看似可用,也会让 IP、DNS、默认路由变得难以预测。
本次最终方案可以概括为一句话:
让 dhcpcd 做网络管理器,让 Wi-Fi 脚本只做连接和断开控制。
这比让脚本直接接管 DHCP 和路由更稳定,也更容易维护。