很多人第一次遇到「明明能连上,速度也还行,可间歇性丢几十毫秒」的时候,会一头扎进内核参数里,改 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,去机房侧找答案比在服务器上猜快得多。