bigywater.tech
网络协议 · 4

ARP:IP 怎么变成 MAC,缓存、老化与免费 ARP

IP 地址要变成 MAC 才能发出帧,这一步靠 ARP。邻居表里存着什么、请求和应答在线上长什么样、没人应答时会怎样重试、网段里每分钟有多少 ARP 在跑?这篇用 tcpdump 抓真实网段,并看 macOS 的邻居表。

42 → 60
ARP 请求(广播)与应答(单播)的帧长;应答在 93.53 ms 后到达
5 个 / 1 秒
无人应答时 ARP 请求的次数与间隔(maxtries=5)
166 vs 2
30 秒网段里抓到的 ARP 请求与应答——请求是广播,应答是单播

邻居表

实测 · 本机执行输出
  arp -an 在 en0 上有 81 条已解析的邻居(同一个网段里的设备)
  ? (<本机局域网IP>) at 70:85:6c:xx:xx:xx on en0 ifscope [ethernet]
  netstat -rn 里网关 <本机局域网IP> 的主机路由:标志 UHLWIir(U 可用 H 主机路由 L 链路层地址 W 由 ARP 自动生成 I 接口作用域 R 拒绝 i 地址完成),Expire 剩余 1180 秒
  系统参数: max_age=1200, maxtries=5, host_down_time=20
  5 秒之后 Expire 剩余 1199 秒(倒计时;每次用到这条邻居时系统会刷新 / 到期后重新发 ARP 请求确认)

每条记录是 IP → MAC,带寿命(macOS 的 max_age 是 1200 秒);netstat -rn 里 ARP 学来的条目标志含 W 和 L。

请求与应答

实测 · 本机执行输出
  +  0.000 ms  请求  帧长 42  以太网: 目的 ff:ff:ff:xx:xx:xx(广播) 源 7e:aa:6f:xx:xx:xx 类型 0x0806
      ARP: 硬件类型 1(1=以太网) 协议类型 0x0800(IPv4) 硬件地址长 6 协议地址长 4 操作 1(请求 who-has)
      发送方 MAC 7e:aa:6f:xx:xx:xx IP <本机局域网IP>;目标 MAC 00:00:00:00:00:00(未知) IP <本机局域网IP>
  + 93.529 ms  应答  帧长 60  以太网: 目的 7e:aa:6f:xx:xx:xx(单播) 源 a6:a0:d9:xx:xx:xx 类型 0x0806
      ARP: 硬件类型 1(1=以太网) 协议类型 0x0800(IPv4) 硬件地址长 6 协议地址长 4 操作 2(应答 is-at)
      发送方 MAC a6:a0:d9:xx:xx:xx IP <本机局域网IP>;目标 MAC 7e:aa:6f:xx:xx:xx IP <本机局域网IP>
  请求到应答 93.53 ms(解析一个邻居的代价);之后 ping 才真正发出。ARP 报文本体 28 字节
  这台主机的 MAC 现在在本机缓存里: True

请求是广播帧,目标 MAC 全 0;应答是单播,带上对方的 MAC。ARP 报文本体 28 字节,放在 14 字节的以太网头后面。

没人应答

实测 · 本机执行输出
  本机发出的 ARP 请求 5 个,时刻(相对第一个,秒): [0.0, 1.01, 2.01, 3.01, 4.02]
  ping 的输出: PING <本机局域网IP> (<本机局域网IP>): 56 data bytes | Request timeout for icmp_seq 0 | Request timeout for icmp_seq 1 | Request timeout for icmp_seq 2 | Request timeout for icmp_seq 3 | Request timeout for icmp_seq 4 | Request timeout for icmp_seq 5 | Request timeout for icmp_seq 6 | --- <本机局域网IP>
  arp -an 里这个地址的状态: ? (<本机局域网IP>) at (incomplete) on en0 ifscope [ethernet]  ← incomplete = 已发了请求、一直没等到应答

内核每秒重发一次请求,最多 maxtries=5 次,条目停在 (incomplete);再往这个地址发包会立刻失败。

网段里的 ARP

实测 · 本机执行输出
  30 秒共 168 个 ARP 帧:请求 166 个(广播)、应答 2 个(单播);其中免费 ARP(发送方 IP = 目标 IP)12 个,ARP 探测(发送方 IP = 0.0.0.0)7 个
  配上对的请求 → 应答 2 对,延迟 p50 0.09 ms,p90 0.09 ms,最大 0.09 ms
  应答只抓到极少数:应答是单播,通常只送到提问者,本机这个抓包点看不到别人收到的单播;而请求是广播,整个网段都能看到(请求 166 个对应答 2 个就是这个原因)

广播的请求整个网段都听得到;免费 ARP 和探测用于宣告 / 检测地址冲突。

不需要 ARP 的情形

实测 · 本机执行输出
  en0 上与本机地址 <本机局域网IP> 相关的 ARP 帧:0 个;lo0 上非 IP 的帧(ARP 或别的):0 个——发往本机自己的地址走本机内部(lo0),根本不会出网卡,也就不需要 ARP

发给本机自己的地址走 lo0,不会出网卡,也就没有 ARP。

分步演示

ARP
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。邻居表通过 sysd.py(系统 python 常驻进程)执行 arp / netstat,因为从 Homebrew python 起的子进程会被 macOS 的本地网络隐私限制看不到邻居表;MAC 地址只保留前 3 字节;第二节挑的是同网段一台在线的主机,第四节抓的是整个网段的 ARP(包含别的设备之间的),只统计数量
    • 没有验证:ARP 欺骗 / 缓存投毒(没有原始包注入权限);代理 ARP;ARP 老化时的单播探测(NUD)细节;IPv6 的邻居发现(见 NDP 一篇);交换机的 ARP 检测;不同操作系统对 incomplete 条目的处理
    京ICP备2026067502号-1京公网安备11010502063487号