PVE 万兆网卡只跑 1G?一次 ARP Flux 的完整排查记录

random-pic-api

背景

起因是家里 Homelab 的一台 NUC 升级,给它加了张 Intel X520-2 双口万兆,顺手把 NAS(DS923+)那边也换上了万兆。理论上这套组合应该是”一步到位、全屋万兆起飞”的那种。

但真正测下来,iperf3 从 NUC 打到 NAS 只有大概 950Mbps。

ethtool enp2s0f0 明明写着 Speed: 10000Mb/sLink detected: yes,我盯着那一行看了半天。硬件、线缆、交换机、NAS 全都换过交叉验证过,问题死死卡在这台 NUC 上。

最后折腾下来才发现根本不是硬件的锅,是 Linux 网络里一个经典得不能再经典的坑:ARP Flux——多张网卡挂在同一个子网里,彼此抢答 ARP,导致流量走错路径。

这篇文章把完整的排查链路、每一步的命令和我当时的判断都写出来,既是给未来的自己留个底,也希望你以后踩到类似问题时能少走一圈弯路。

环境说明

先把桌面摆开,这台 NUC 上一共挂了 4 张网卡:

接口IP类型Bridge
enp2s0f0192.168.31.99Intel X520 10Gvmbrt
enp2s0f1192.168.21.99Intel X520 10Gvmbru
enp92s0192.168.31.9主板 2.5Gvmbr0
enx00e04c68bbcf192.168.21.9USB 2.5Gvmbr1

目标很朴素:192.168.31.99(10G)作为主网卡连 NAS(DS923,192.168.31.2),192.168.31.9 做管理备用,两个都在 192.168.31.0/24 这个网段里。

为什么要这么摆?因为我希望管理网和存储网分别走不同的物理口,同时又懒得单独规划网段,就想着”反正都是 31 网段,系统会自己选最快的那个吧”。

这种想当然,后面吃了大亏。

问题现象

直接上 iperf3 做对比,横向看两台机器打同一台 NAS 的速度:

测试机目标结果
PVE(192.168.31.9)192.168.31.2~950 Mbps(不对劲)
另一台万兆机器(192.168.31.6)192.168.31.2~9.3 Gbps(正常)

两台机器都是单线程 iperf3,NAS 端没任何特殊配置。既然另一台万兆能干到接近 10G,说明 NAS、交换机、线缆都没问题,瓶颈就在 NUC 自己。

第一轮排查:硬件

ethtool 明明协商到 10G,按常规思路先把硬件层扫一遍。

1. 驱动和型号

1
2
3
$ ethtool -i enp2s0f0
driver: ixgbe
bus-info: 0000:02:00.0

Intel 82599ES / X520-2,ixgbe 驱动,都在预期内,没什么奇怪的。

2. PCIe 链路宽度

1
2
3
$ lspci -vv -s 02:00.0 | grep -i width -A2
LnkCap: Port #0, Speed 5GT/s, Width x8
LnkSta: Speed 5GT/s, Width x4 (downgraded)

这里有点意思,理论 x8 实际跑在 x4(downgraded)。算一下 PCIe 2.0 x4 的理论带宽大约 16Gbps,跑 10G 足够,不构成瓶颈,所以暂时不追这条线。

顺便提一句,NUC 的 M.2 转接卡压到 x4 是常见现象,见得多了也就不奇怪。

3. 队列数量

1
2
3
$ ethtool -l enp2s0f0
Current hardware settings:
Combined: 16

16 个队列,CPU 足够,不会单核瓶颈。

4. 中断分布

1
$ cat /proc/interrupts | grep ixgbe

这一条反而最关键——空的

按理说只要流量真的在这张卡上跑,ixgbe 的中断计数肯定会噌噌往上涨。现在一条都没有,这是一个非常强的信号:流量压根没从这张卡上走。

当时我还没完全反应过来,以为是中断被绑到别的 CPU 或者 IRQ balance 出了问题,后来才知道是真字面意义上的”没流量”。

5. MTU

已配置 MTU 9000,和 NAS 对齐,这一项也正常。

第一轮扫完的结论:硬件没问题

第二轮排查:是不是测错了接口

默认路由挂在 vmbr0(2.5G 那张),192.168.31.9 也绑在 vmbr0 上。于是很自然地有个怀疑:iperf 根本没走 10G 那张卡,流量被默认路由吸到 2.5G 去了。

强制绑源地址再测一遍:

1
iperf3 -c 192.168.31.2 -B 192.168.31.99

结果还是 ~950 Mbps。源 IP 都绑定到万兆那一侧了,速度却完全没变,说明问题不是”走错接口”这么表层。

同时看一眼网卡的 byte 计数:

1
2
3
$ ip -s link show enp2s0f0
RX: 57859644 bytes
TX: 9065 bytes

iperf 作为 client 主要是数据,理论上 TX 应该疯狂增长。但 TX 几乎不动,只有 RX 在涨。结合前面 /proc/interrupts 为空,结论已经很清楚:

iperf 发出去的包,没有真正从这张 10G 口出去,而是被路由到别的接口了。

第三轮排查:能不能把 2.5G 口 down 掉

为了验证是不是和 2.5G 口抢路径,我手贱试了一下:

1
ip link set enp92s0 down

下一秒 PVE 管理台立刻断开,因为我是通过 192.168.31.9 这个地址连进来的,管理流量走 vmbr0 → enp92s0。这一下虽然没解决问题,但是副产品:

坐实了管理流量确实走 2.5G 口。

然后赶紧切到服务器前面接显示器 up 回来,以后这种验证改用更安全的手法,不能这样莽。

破案:看邻居表

转折点在这一条命令上。既然流量路径选择不对,那就直接看二层邻居表:

1
2
3
$ ip neigh show 192.168.31.2
192.168.31.2 dev vmbr0 lladdr 02:76:c6:01:27:e4 STALE
192.168.31.2 dev vmbrt lladdr 02:76:c6:01:27:e4 STALE

看到这一行我就乐了。同一个 IP、同一个 MAC,同时出现在 vmbr0 和 vmbrt 两个接口上,这就是教科书级别的 ARP Flux

再看路由表确认一下:

1
2
3
4
$ ip route
default via 192.168.31.1 dev vmbr0 proto kernel onlink
192.168.31.0/24 dev vmbr0 proto kernel scope link src 192.168.31.9
192.168.31.0/24 dev vmbrt proto kernel scope link src 192.168.31.99

同一个 192.168.31.0/24 网段,在 vmbr0 和 vmbrt 两个接口上都有直连路由。这会带来几个后果:

  • 两个接口都会应答 ARP 请求
  • 邻居表里同一个 IP 出现在多个接口上
  • TCP 包可能从 10G 口发出去,回程却从 2.5G 口收(非对称路径)
  • 吞吐被卡在 ~1G,也就是慢那一侧的速率

这里有个关键点我当时也想明白过来:同网段通信不走默认路由,而是直接查邻居表选接口。所以哪怕我把默认路由改到 vmbrt 上,只要邻居表里 192.168.31.2 还绑在 vmbr0,iperf 该走哪还走哪。

之前白费那些力气调路由 metric 的,全都是无用功。

验证

临时把 2.5G 那边的 IP 摘掉(注意不是 down 接口,只是把 IP 去掉),然后清邻居表重测:

1
2
3
ip addr del 192.168.31.9/24 dev vmbr0
ip neigh flush all
iperf3 -c 192.168.31.2

速度一下就冲到 9.x Gbits/sec

到这一步,故事已经很明朗:问题不是硬件,是同网段双接口 + Linux 默认 ARP 行为带来的 Flux。

根本原因:Linux 和 macOS 的设计差异

这个差别挺有意思的,顺便展开说两句。

  • Linux 是多宿主模型(weak host model):默认允许多接口同子网、多接口应答 ARP,方便也容易出事
  • macOS / BSD 是 strong host model:每个接口只响应自己 IP 的 ARP,多网卡挂同网段一般不会出这种事

这就解释了为什么同样的拓扑,Mac 上一切正常,Linux 上就会翻车。也解释了为什么你在 Mac 上调网卡优先级能生效,在 Linux 上却看不出效果——同网段根本不走默认路由,走的是邻居表。

我以前一直以为”多宿主”就是支持多网卡而已,这次算是结结实实理解了它在 ARP 层面的具体含义。

最终方案

梳一下目标:默认走 192.168.31.99(10G),另外三个 IP(192.168.31.9 / 192.168.21.9 / 192.168.21.99)都保留可用,四个网口全部在线,且两个子网也都保持原样。

1. 默认路由走 10G

把 gateway 配到 vmbrt(也就是万兆那个 bridge)上:

1
vmbrt: gateway 192.168.31.1

2. 为备用网卡配策略路由

默认路由走 10G 之后,从 192.168.31.9 这个备用 IP 出去的流量就没网关了。解决办法是按源 IP 加一条策略路由,让它走自己的桌面:

1
2
3
4
5
post-up ip rule add from 192.168.31.9 table 100
post-up ip route add 192.168.31.0/24 dev vmbr0 table 100
post-up ip route add default via 192.168.31.1 dev vmbr0 table 100
post-down ip rule del from 192.168.31.9 table 100
post-down ip route flush table 100

vmbr1、vmbru 同样操作,table 编号换成 101、102 之类就行。这些 post-up/post-down 钩子写在 /etc/network/interfaces 里,重启后能自动生效。

3. 关闭 ARP Flux(最核心的一步)

1
2
3
4
5
6
echo "net.ipv4.conf.all.arp_ignore=1"     >> /etc/sysctl.conf
echo "net.ipv4.conf.all.arp_announce=2" >> /etc/sysctl.conf
echo "net.ipv4.conf.default.arp_ignore=1" >> /etc/sysctl.conf
echo "net.ipv4.conf.default.arp_announce=2" >> /etc/sysctl.conf
sysctl -p
ip neigh flush all

两个参数值得单独解释一下:

  • arp_ignore=1:只回答目标 IP 和接收接口 IP 匹配的 ARP 请求,也就是每个接口只为自己的 IP 回答 ARP
  • arp_announce=2:发出 ARP 请求时,源 IP 优先选择和接收接口在同一子网的地址

这两个加起来就是在告诉内核:各回各家,各找各妈,别抢答。

4. 验证

改完之后跑这几条:

  • ip route:默认路由应该是 default via 192.168.31.1 dev vmbrt
  • iperf3 -c 192.168.31.2:稳定在 9.x Gbits/sec
  • ip neigh show 192.168.31.2:只在一个接口上出现

到这里就完事了。我还特意复核过,这些改动只动了主机层的网络行为,容器接在哪个 bridge 上不用动,PVE 里每个 LXC 和 VM 的配置也都不用改。

排查路径总览

把整个过程按”排查动作 → 结论”列一张表,以后遇到类似现象可以照着跑:

排查项结果
ethtool 协商10000Mb/s,正常
驱动ixgbe,正常
PCIex4,带宽足够
队列16,正常
测错接口?-B 192.168.31.99 仍 ~950M
ip -s link 的 TX几乎不涨,流量没走 10G 口
/proc/interrupts无 ixgbe 中断,佐证上一条
ip neigh同一 IP 出现在 vmbr0 和 vmbrt 上
ip addr del 验证删掉 31.9 后瞬间 9G+
最终方案arp_ignore + arp_announce + 策略路由

一些经验

  1. 同网段挂多张物理接口,在服务器环境下风险很大。管理网、存储网、业务网尽量分网段,Homelab 也一样
  2. Linux 不是 Mac,多宿主行为默认开放,多网卡同网段一定要配 arp_ignore + arp_announce,否则迟早踩 Flux
  3. 协商 10G 但只跑 900M 出头的时候,不要第一时间怀疑硬件,优先排查:
    • ip neigh:同一 IP 是不是挂在多个接口上
    • ip -s link 的 TX 计数:看流量到底有没有真从目标网卡出去
    • ip route:同网段是不是有多条直连路由
    • /proc/interrupts:目标网卡的中断有没有在涨
  4. 多网卡 + 同子网 + 同交换机的组合,记住这个口诀:要么分网段,要么策略路由 + arp_ignore

网络问题里,很多时候不是硬件的锅,是拓扑设计的锅。硬件给你开了一条 10 车道的高速路,但你在入口放了一堆错的路标,车当然开不快。