ARP表排查与防护:内网丢包的另一半原因

很多人第一次遇到「明明能连上,速度也还行,可间歇性丢几十毫秒」的时候,会一头扎进内核参数里,改 tcp_rmem、调 cubic、换 BBR,折腾一晚上没结果。真正的原因往往在二层——同一网段内有另一台机器把 ARP 表污染了,或者网卡自己把不该应答的 ARP 应答了。这篇文章把 ARP 从一个抽象概念讲到能实际排查:怎么读表、怎么防欺骗、怎么处理同一个 MAC 出现两个 IP、以及虚拟化和容器环境里那些让人摸不着头脑的 ARP 现象。

一、先搞清楚 ARP 到底解决什么问题

IP 包在以太网里发不出去,必须先知道目标的 MAC 地址。ARP 就是「用 IP 换 MAC」的协议:广播一个「谁拥有 10.0.0.5,请告诉 10.0.0.1」,拥有者单播回一个应答,双方各自把结果记进缓存。它是无状态、无认证的协议,任何主机只要愿意,都可以对任何请求作出应答,也可以主动发一个「我才是 10.0.0.1」的免费 ARP 通告。这个设计在 1982 年的局域网里没有问题,放到今天就是所有 ARP 相关故障的根源。

在 Linux 上,这份缓存就是邻居表,用 ip neigh 查看,老命令 arp -n 已经属于 net-tools,很多新系统默认不装。邻居表里每条记录的状态很关键:REACHABLE 表示近期确认过,STALE 表示条目还在但需要重新确认(这时候发第一个包会先做一次 ARP,会有几毫秒延迟,属正常),DELAY 是已经开始探测但还没到时,FAILED 表示探测失败,INCOMPLETE 表示请求发了没人应答。

ip neigh show
ip -s neigh show 10.0.0.5
ip neigh flush dev eth0
ip route get 10.0.0.5

遇到「同网段某台机器不通,但 ping 网关正常」这类问题,第一步永远是看邻居表里那条记录到底是 FAILED 还是根本没有。如果是 FAILED,说明二层就断了,跟防火墙、跟应用层的 iptables 规则都没关系,别去翻 nft 规则浪费时间。

二、ARP 欺骗是怎么发生的,以及最小成本的防护

在内网里,攻击者只要持续发送伪造的 ARP 应答,把网关的 IP 绑到自己的 MAC 上,就能让同网段的机器把流量交给它。经典的中转式攻击会给每台机器发「网关在我这里」、给网关发「这些机器都在我这里」,形成透明中间人。个人站长的服务器如果在共享机房、或者家里有一堆 IoT 设备的环境,这个风险是真实存在的。

防护有四个层次,从便宜到贵:

第一层是静态邻居表。对于不会变的关键节点,用 ip neigh add 10.0.0.1 lladdr 00:11:22:33:44:55 nud permanent dev eth0 写死。缺点是虚拟机会迁移、云厂商会换网关 MAC,一旦 MAC 变了机器直接断网,所以只在物理机房自有设备上用,并且要写进开机脚本和监控里。

第二层是 arp_ignore 和 arp_announce。这两个参数在有多网卡、或者网卡上有多个 IP 的机器上非常重要。默认 arp_ignore=0 意味着「不管请求的是我哪一个 IP,只要我有这个 IP 就应答」,这会让本来该走 eth1 的流量被人用 eth0 的 MAC 骗走。arp_filter、arp_ignore=1(只应答目标 IP 在本接口上的请求)配合 arp_announce=2(总是用最适合的源地址做通告)能大幅减少这类混乱。

sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2
sysctl -w net.ipv4.conf.all.arp_filter=1
sysctl -w net.ipv4.conf.all.rp_filter=1

注意 all 和具体接口两个路径都要写,内核的语义是两边取更严格的那个,只写一边在部分发行版上不生效。写进 /etc/sysctl.d/99-arp.conf,别直接改 /etc/sysctl.conf,免得被别的脚本覆盖。

第三层是交换机侧的动态 ARP 检测和端口隔离。机房如果有 Cisco 或华为的设备,可以开 DAI 配合 DHCP Snooping,把非法的 ARP 应答直接丢在接入端口上。这一层不做,光靠主机侧的静态表很难防住全部。

第四层是能不开二层就尽量不开。云服务器默认就是隔离的,这也是为什么大多数站长从没遇到过 ARP 问题——直到他为了省钱把几台物理机放进一个 /24 里共享公网 IP 段。

三、同一个 MAC 出现两个 IP:最常见的两种真实原因

排查时会看到非常诡异的现象:两台机器的 IP 不同,但 ip neigh 里看到的 lladdr 一模一样,于是有人断定「被 ARP 欺骗了」,实际上八成的场景是下面两种。

情况一:绑定网卡(bonding)的 MAC 克隆

两块网卡做 active-backup 绑定后,从交换机看过去只有一个 MAC,这是正常的。但如果两台不同的机器各自把 bond0 的 MAC 手工设成了同一个值(比如复制了同一份配置模板),那就是真事故。判断方法:在两边同时执行 ip link show 看 MAC,再在交换机上 show mac address-table address 看这个 MAC 出现在几个端口上。如果出现在两个端口,交换机就会不断在两个口之间切换转发,表现为大面积间歇丢包,且故障现象随流量变化。修法只有一个:给其中一台重新生成 MAC 并重启网络。

情况二:容器的 macvlan 与宿主机抢答

用 macvlan 给容器分配同网段 IP 时,容器的 MAC 和宿主机的 MAC 都可能被上游网关当作同一个 IP 的候选。更常见的是 macvlan 容器无法与宿主机自身通信——这是 macvlan 的设计限制,不是故障。正确的做法是给宿主机再建一个 macvlan 接口专门用来跟容器通信,或者在容器侧改用 ipvlan L2。

还有一个隐蔽的来源:KVM 虚机的桥接网卡如果配了 bridge-nf-call-arptables=1,再叠加 iptables 的 ARP 规则,会出现「虚机 ARP 请求出不了宿主机」的怪现象。检查 sysctl net.bridge.bridge-nf-call-arptables,多数情况下这个值应该是 0,除非你确实在用 arptables 做过滤。

四、ARP 缓存的时长与刷新,别指望立刻消失

很多人把主机 A 的 IP 换到主机 B 上以后,发现同网段的其他机器还要等好几分钟才恢复。这不是配置错了,是邻居表的正常行为:

  • base_reachable_time 默认 30 秒左右,是条目保持 REACHABLE 的基准时间;
  • gc_stale_time 默认 60 秒,控制 STALE 的清理;
  • ucast_solicit 与 mcast_solicit 控制探测重试次数,默认 3 次;
  • retrans_time_ms 默认 1000 毫秒,就是探测间隔。

要想让 IP 迁移后尽快生效,正确顺序是:在旧机器上先 ip addr del,在新机器上 ip addr add 后立刻发一次免费 ARP(arping -A -I eth0 -c 3 10.0.0.5),同时在新机器上主动访问一次网关,让上游交换机刷新 MAC 表。交换机侧的 MAC 老化一般 300 秒,这一步才是真正的瓶颈,主机侧的清理只能解决一半问题。

五、监控与日常巡检:把 ARP 异常变成一条告警

ARP 相关的故障最讨厌的地方是它不报错,只变慢。所以值得花二十分钟做两个小检查,写进每日巡检。

第一个检查是 ip neigh show | grep -c FAILED,正常情况下这个数应该是 0 或者个位数。如果持续增长,说明有同网段地址不通,或者上游有设备在做 ARP 抑制,比如某些云厂商的网关对未备案的 IP 不回 ARP。

第二个检查是 MAC 冲突检测:把 ip neigh show 输出里的 lladdr 排序,看是否有同一个 MAC 关联了多个非本机 IP。可以用一行 awk 实现:

ip neigh show | awk '{print $5}' | grep -v '^$' | sort | uniq -d

输出非空就要人工看一眼,可能是正常的 bond 或网关多 IP,也可能是冲突。

第三个是看内核的邻居表溢出告警,dmesg -T | grep -i 'neighbour table overflow'。在容器密集、或者遭受扫描的机器上,邻居表默认 1024 个表项很容易打满,打满之后新连接会静默失败。处理方式是调大 net.ipv4.neigh.default.gc_thresh1/2/3,同时配合 gc_thresh3 的 OOM 风险一起评估。

六、几条容易踩的具体坑

坑一:用 arping 只测本机 ARP 层,它不通不代表三层不通,反过来也一样。判断分层要用 ping 和 ip neigh 交叉验证。

坑二:OpenVZ 或部分老旧的 VPS 方案里,宿主机会替你应答所有 ARP,于是你在虚机里抓包永远看不到 ARP 请求。这种环境下静态 ARP 绑定往往不生效,别在虚机里死磕,去找宿主侧配置。

坑三:交换机的端口安全(port security)限制 MAC 数量时,加了一台机器就可能导致整个端口被 err-disable,表现为全员断网。搬家、扩容之前先问一句机房有没有开端口安全。

坑四:ip neigh flush all 之后立刻 curl,可能因为 ARP 请求还没完成而超时,这是正常的几毫秒到几十毫秒延迟,不要误判为故障。

七、小结

ARP 的问题几乎都能用三步定位:ip neigh 看条目状态,arping 确认二层可达,交换机上看 MAC 表。把 arp_ignore、arp_announce 这两个参数配好,静态表只给关键节点用,再在巡检里加一个 FAILED 计数和一个 MAC 重复检查,就能覆盖 90% 的内网老问题。剩下的那一成,通常是交换机上开了端口安全或者 DAI,去机房侧找答案比在服务器上猜快得多。

Last modification:October 28th, 2026 at 10:24 pm

Leave a Comment