背景

某台便携式 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 优先级。

最初发现的问题

一次系统自检中发现,设备上同时存在 dhcpcddhclient,并且二者都在处理同一个无线接口。

这类情况容易造成网络栈混乱:

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 和手动路由强行接管网络。长期来看,这种做法会增加不稳定性,也会让后续排障困难。

修复原则

修复时遵循几个原则:

  1. 只保留 dhcpcd 作为 DHCP / DNS / 路由 / metric 管理器;
  2. 不引入额外网络管理器;
  3. 不修改当前正在运行的网络状态;
  4. 修改前必须备份;
  5. 先只改自定义脚本,不动系统网络配置;
  6. 不在修改过程中执行连接脚本、断开脚本、dhclient 或网络重启;
  7. 修改完成后由人工手动重启或手动验证。

备份使用 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 客户端只能保留一套。dhcpcddhclient 同时管理同一个接口时,即使短期内看似可用,也会让 IP、DNS、默认路由变得难以预测。

本次最终方案可以概括为一句话:

让 dhcpcd 做网络管理器,让 Wi-Fi 脚本只做连接和断开控制。

这比让脚本直接接管 DHCP 和路由更稳定,也更容易维护。

Leave a Reply

Your email address will not be published. Required fields are marked *