
这是《Ubuntu 开发环境搭建避坑》系列的第一篇。笔者是做运维开发的,新装了一台 Ubuntu 台式机(主板自带 Realtek RTL8125 2.5G 网卡)准备搭日常开发环境:Docker、JDK、一堆 apt 依赖,再配个 RustDesk 方便从别的机器远程进来写代码。结果环境还没搭完,网络先"半残"了------apt 下载龟速、git clone 断断续续、RustDesk 远程画面卡成幻灯片,自建的 RustDesk 中继服务器日志里反复报这台机器"网络质量差"。
最后定位到的根因有点出乎意料:网卡的一个"节能"特性(EEE,节能以太网)唤醒失败,造成了周期性的通信黑洞。这篇把完整的排查路径、量化数据、修复方法和一键脚本分享出来------如果你也在新装的 Ubuntu 上遇到"莫名卡顿但一切计数正常",大概率能直接对号入座。
一、现象:先量化,再动手
排查网络问题的第一原则是先拿数字,"感觉卡"是不能定位问题的。装好基础工具后采了一轮基线数据:
| 指标 | 故障时 | 修复后 |
|---|---|---|
| ping 网关 / 局域网设备 / 外网 | 全部丢包 60% | 0% |
| 丢包模式 | 周期性:每 2.5~3s 一循环,通约 1s → 黑洞 1.5~2s | --- |
| 收到包的 RTT | 极稳(0.5ms,mdev 0.04ms) | 0.5ms |
| 下载速度(阿里云镜像) | 236 KB/s | 19.4 MB/s(82 倍) |
| RustDesk 到中继的 TCP 发送积压 | 74483 字节 | 437 字节 |
| 网卡错误计数 / 链路闪断 / ARP 冲突 | 全部为零 | --- |
三个关键观察,后面都会用到:
- 网关、局域网、外网丢包率完全相同(都是 60%)------问题在共享的近端段,不在远端;
- 丢包是周期性的,不是随机散布------通 1 秒、断 1~2 秒,像呼吸一样规律;
- 网卡所有错误计数为零,链路不闪断------这条最有迷惑性,让人一开始就把硬件排除了。
二、五层排查法:从近到远逐层收窄
这种"逐层收窄、每层拿数据说话"的思路,和之前 Java 进程 Unable to create new native thread 排查:Linux 线程数五层限制 里定位线程数上限的方法论是同一套------先穷尽约束层,再对号入座。
2.1 第 1 层:本机与链路层
bash
ip -s link # 接口收发错误/丢包计数
ethtool enp2s0 # 协商速率、双工、链路状态
ethtool -S enp2s0 # 硬件详细计数(CRC/FIFO 等)
dmesg | grep -iE 'enp2s0|link|r8169' # 链路闪断、驱动重置
判读:协商速率异常(比如降到 100M 或半双工)说明网线/端口有问题。本机协商在 2.5G 全双工、链路 up,所有错误计数为零。
这里的坑:计数全零 ≠ 链路正常。 EEE 唤醒失败、WiFi 干扰这类问题都不计入任何网卡错误计数器------"硬件计数全零"具有极强迷惑性,这也是本文案例最初被带偏的原因。
2.2 第 2 层:本地网段(ping 网关)
bash
ping -c 50 -i 0.2 -q 192.168.1.1
- 0% 丢包 → 本地网段正常,问题在更远端或应用层,直接跳第 4 层;
- 丢包 → 问题锁定在【本机网卡↔网线↔交换机/路由器】这一段,进第 3 层。
本机实测 60% 丢包------问题在近端这一段。
2.3 第 3 层:区分丢包模式(本案的胜负手)
bash
ping -D -c 40 -i 0.2 192.168.1.1 # -D 给每个包打时间戳
看丢包的时间分布,两种模式指向完全不同的根因:
| 模式 | 特征 | 首要怀疑 |
|---|---|---|
| 周期性黑洞 | 连续通过 N 个包 → 整段丢失 1~2s → 循环(周期 2~10s) | ① EEE/ASPM 等电源管理唤醒失败 ② ARP/IP 冲突 ③ STP 拓扑震荡 ④ 某设备周期性重启/广播风暴 |
| 随机丢包 | 无规律散布 | 网线/水晶头质量、拥塞、WiFi 干扰、路由器过载 |
本机抓到的是标准的周期性黑洞:每 2.5~3 秒一个循环。"周期性"这个特征价值极大------它把"线路质量差"这类静态原因排除掉了(坏网线不会按时坏),把嫌疑集中在有状态机、会周期性休眠/唤醒的机制上:电源管理首当其冲。
2.4 第 4 层:局域网对端 + 外网交叉验证
bash
ping 192.168.1.88 # 同网段其它设备(本例为 RustDesk 服务器)
ping 223.5.5.5 # 外网对照(阿里 DNS,国内质量稳定)
mtr -rwb -c 30 -n 223.5.5.5
判读逻辑:
- 所有目标(网关/局域网/外网/mtr 每一跳)丢包率相同 → 问题在共享的近端段(本机网卡、网线、直连交换设备),与远端无关。这是最重要的定位推理之一;
- 只有外网丢、局域网干净 → 路由器 WAN/运营商问题;
- 只有某一台局域网设备丢 → 那台设备或它的接入链路问题。
本机对网关、RustDesk 服务器、阿里 DNS 三个目标丢包率清一色 60%------凶手在共享段,基本就是本机网卡或那根网线。
2.5 第 5 层:排除 ICMP 假阳性(协议对照)
路由器(尤其某些型号)会限速/丢弃 ICMP,制造假丢包。三种对照手段:
bash
# 手段1: ICMPv6 对照(路由器一般不限速 NDP 相关的 ICMPv6,还能暴露队列延迟)
ping -6 -I enp2s0 -c 20 fe80::<网关链路本地地址>
# 本例故障时它显示 RTT 高达 1 秒 → 暴露真实存在通信黑洞,不是被过滤
# 手段2: TCP 握手成功率
for i in $(seq 20); do curl -o /dev/null -sS --connect-timeout 2 -w '%{time_connect}\n' http://192.168.1.88:21118/; done
# 手段3: 真实下载吞吐(最贴近用户体感,至少测两个源)
curl -4 -o /dev/null -sS -w '%{speed_download} B/s\n' --max-time 20 <镜像源1>
curl -4 -o /dev/null -sS -w '%{speed_download} B/s\n' --max-time 20 <镜像源2>
判读:ICMP 大量丢但 TCP/下载完全正常 → ICMP 被过滤,别被带偏;下载同样崩溃(本例 236 KB/s)→ 真实劣化。单一测速源慢可能是服务器限速,至少测两个源再下结论。
三、进阶判别:三个快速排除实验
3.1 区分"本机没发出"还是"对端没回"
在 ping 前后取网卡硬件发包计数差:
bash
ethtool -S enp2s0 | grep tx_packets > /tmp/b
ping -c 30 -i 0.2 -q 192.168.1.1
ethtool -S enp2s0 | grep tx_packets > /tmp/a
diff /tmp/b /tmp/a
内核计 30 个已发、硬件计数也涨了 ≥30 → 包离开了网卡,问题在外部路径;硬件计数明显少 → 本机 qdisc/驱动/NIC 停摆。本例计数吻合------包发出去了,但响应回不来或回来时正赶上网卡"睡着"。
3.2 ARP 冲突检测
bash
timeout 15 ip monitor neigh # 15 秒内邻居表零变化 → 无 ARP 冲突
3.3 本机配置快速排除清单
bash
tc qdisc show dev enp2s0 # 应只有 fq_codel,出现 netem = 人为丢包
ip rule show # 应只有 0/32766/32767 三条默认规则
ip route show table all # 单一默认路由,无 VPN 隧道劫持
awk '{d+=$2} END{print d}' /proc/net/softnet_stat # 软中断丢包,应为 0
3.4 两个必须知道的"数据会骗人"
- nstat 的 TCP 计数因 GRO/TSO 卸载严重失真:InSegs/OutSegs 差值算出的"重传率"不可直接采信,只作弱参考;
- 网卡错误计数为零 ≠ 链路正常:EEE 唤醒失败、信号裕量不足都不计数。
四、锁定根因:EEE(节能以太网)唤醒失败
到此可以收网了。证据链:全目标均匀丢包 + 周期性黑洞 + RTT 极稳 + 硬件计数全零------这个组合的典型指纹就是电源管理类故障。查 EEE 状态:
bash
sudo apt install ethtool
sudo ethtool --show-eee enp2s0
输出显示 EEE status: enabled(enabled-active)------Realtek RTL8125(r8169 驱动)的 EEE 处于开启状态。
EEE(Energy-Efficient Ethernet,节能以太网,IEEE 802.3az)的机制是:链路空闲时让 PHY(物理层芯片)进入低功耗休眠,有数据时再快速唤醒。问题在于 RTL8125 上这个"快速唤醒"并不可靠------PHY 睡着后唤醒失败或过慢,就出现周期性的双向通信黑洞;而唤醒失败不产生任何错误计数,所以计数器看起来一切正常。这与前面观察到的"通 1 秒、断 1~2 秒"的呼吸式丢包完全吻合:流量来了 PHY 醒着就通,空闲了睡过去,下一批包赶上了唤醒延迟就全丢。
对实时流(RustDesk/视频会议/游戏)这是毁灭性的:它们持续小流量发包,恰好反复触发休眠/唤醒循环。
五、修复:ethtool --set-eee 一条命令关闭 EEE + 复测
bash
sudo ethtool --set-eee enp2s0 eee off
复测(同参数):
| 指标 | 修复前 | 修复后 |
|---|---|---|
| ping 网关丢包 | 60% | 0% |
| 下载速度 | 236 KB/s | 19.4 MB/s |
| RustDesk TCP 发送积压 | 74483 B | 437 B |
RustDesk 远程立刻恢复流畅。注意 ethtool --set-eee 的效果重启后失效 ,必须持久化,否则哪天重启完"怎么又卡了"。"一次修复不算完,重启后仍要成立"这个意识,和 OpenSSH 漏洞修复升级前先搭 telnet 救生通道 里"防锁死要先兜底"是同一种工程习惯。
六、持久化:重启不复发
方案 A:NetworkManager dispatcher 钩子(Ubuntu 桌面版默认走 NetworkManager,推荐):
bash
sudo tee /etc/NetworkManager/dispatcher.d/90-disable-eee <<'EOF'
#!/bin/bash
[ "$1" = "enp2s0" ] && [ "$2" = "up" ] && /usr/sbin/ethtool --set-eee "$1" eee off
EOF
sudo chmod 755 /etc/NetworkManager/dispatcher.d/90-disable-eee
网卡每次 up 时自动关 EEE。
方案 B:systemd oneshot 服务(服务器版无 NetworkManager 时兜底):
ini
[Unit]
Description=Disable EEE on boot (fix RTL8125 periodic stall)
After=network-pre.target
Before=network.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/ethtool --set-eee enp2s0 eee off
[Install]
WantedBy=multi-user.target
systemctl enable --now disable-eee.service 生效。重启后用 ethtool --show-eee enp2s0 | head -1 验证仍为 disabled。
七、一键脚本:fix-eee-stall.sh
把上面的"检测 → 关闭 → 复测 → 持久化"整条链路写成了脚本,核心逻辑:
bash
# EEE 状态探测: enabled / disabled / unsupported
eee_status() {
local out
out=$(ethtool --show-eee "$1" 2>&1) || { echo unsupported; return; }
if echo "$out" | grep -q 'EEE status:.*enabled'; then echo enabled
elif echo "$out" | grep -q 'EEE status:.*disabled'; then echo disabled
else echo unsupported; fi
}
# 丢包率采样(纯数字输出便于比较)
loss_pct() {
ping -c 50 -i 0.2 -q "$1" 2>/dev/null \
| sed -n 's/.*, \([0-9.]*\)% packet loss.*/\1/p' | head -1
}
# 修复: 对 EEE=enabled 的在线有线网卡执行关闭, 然后同参数复测
ethtool --set-eee "$IF" eee off
用法:
bash
./fix-eee-stall.sh --check # 仅诊断不修改, 无需 root: 报告 EEE 状态+丢包基线
sudo ./fix-eee-stall.sh # 自动检测所有在线有线网卡并修复
sudo ./fix-eee-stall.sh enp2s0 # 只处理指定网卡
脚本会输出修复前后丢包对比,EEE 关闭后若丢包仍超标,会提示按"换网线 → 换端口 → 锁 1G 验证 → 查 ASPM"的顺序继续排查;持久化自动选择 dispatcher 或 systemd 方案安装。
八、EEE 关闭无效时的决策树
- 换网线(2.5G 需 Cat5e 以上,推荐 Cat6)------成本最低,先做;
- 换路由器/交换机端口;
- 锁 1G 验证 :
sudo ethtool -s enp2s0 speed 1000 duplex full autoneg on------1G 下正常则确认 2.5G 信号裕量不足,停在 1G 或换线后恢复 2.5G; - 关 PCIe ASPM :BIOS 中关闭,或内核参数
pcie_aspm=off试验(与 EEE 同族的节能机制); - 换网卡/PCIe 插槽(板载网卡故障的最后手段)。
九、症状 → 根因速查表
| 症状组合 | 首要怀疑 | 验证命令 |
|---|---|---|
| 全目标均匀丢包 + 周期性通断 + RTT 极稳 + 计数全零 | EEE/ASPM 电源管理 | ethtool --show-eee <if> |
| 全目标均匀丢包 + 邻居表频繁变化 | IP/MAC 冲突 | ip monitor neigh |
| 全目标丢包 + 路由器管理页也卡 | 路由器 CPU 过载/风暴 | 登录路由器看 CPU,拔设备二分 |
| 只有远端丢,局域网干净 | 运营商/WAN | mtr -rb <公网IP> 看从哪跳开始丢 |
| 下载慢但 ping 完美 | 限速/单线程瓶颈/QoS | 多源测速 + 换时段 |
| RTT 抖动大(无丢包) | 缓冲膨胀(bufferbloat)/拥塞 | 边下载边 ping 网关对比 |
| 协商速率 100M 或半双工 | 网线/端口老化 | `ethtool |
十、踩坑点小结
- 网卡错误计数全零是最强的烟雾弹------EEE 唤醒失败不进任何计数器,别据此排除硬件;
- 周期性丢包是电源管理类故障的指纹------坏网线不会"按时"坏,能画出规律的丢包就去查会休眠的东西(EEE/ASPM/选择性暂停);
- 先量化再动手 ------每层修复前取基线、修复后同参数复测,
ping -c 50 -i 0.2 -q+ping -D时间戳这两条命令信息量最大; - ICMP 会骗人------用 ICMPv6/TCP 握手/真实下载三重对照再下结论,测速至少两个源;
ethtool --set-eee重启失效------不装持久化钩子,问题一定会"复发";- 实时流卡顿但错误日志干净时优先怀疑节能特性------RustDesk/会议/游戏持续小流量恰好反复触发 PHY 休眠唤醒。
写在最后
新机器搭开发环境,第一坑居然埋在网卡的"节能"特性里。这个案例给我的最大教训是:网络排查要从近到远逐层收窄,用对照实验排除假象,抓住丢包的分布特征(周期性/均匀性)反推机制------而不是一上来就换网线、重装驱动。
这是《Ubuntu 开发环境搭建避坑》系列的第一篇,后续会继续记录环境搭建过程中的真实踩坑与解法。同类的 Linux 系统层排障实战,可以参考我之前的几篇:OpenSSH 漏洞修复升级实战、Java 进程 Unable to create new native thread 排查、Hadoop 集群日常运维 SOP。
你有没有遇到过"计数全零但网络就是卡"的诡异故障?最后查出来是什么原因?欢迎在评论区分享你的案例。
系列相关:OpenSSH 升级实战 · JVM 线程数五层限制排查 · Hadoop 日常运维 SOP