图 1:本文封面,广播请求与单播应答的一次完整往返。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。「IP 地址和物理地址之间靠什么转换」是网络面试的入门题,但多数人只答得出「ARP」三个字母。面试官真正想听的是全过程:报文长什么样、缓存怎么工作、免费 ARP 是干什么的。
为什么需要 ARP:IP 管往哪去,MAC 管这一跳给谁。
IP 地址是逻辑地址,回答「数据最终要到哪台机器」;而以太网帧在链路上跑,需要的是 48 位的物理地址(MAC)。同一局域网里发包,网卡只认 MAC——所以发出之前,必须先把「下一跳的 IP」翻译成「下一跳的 MAC」。RFC 826 定义的这个翻译协议就是 ARP:地址解析协议。
报文长什么样。
ARP 报文直接装在以太网帧里(帧类型 0x0806),关键字段有:硬件类型(以太网 = 1)、协议类型(IPv4 = 0x0800)、两个地址长度、操作码(请求 = 1,应答 = 2),然后是发送方 MAC、发送方 IP、目标 MAC、目标 IP。请求报文里目标 MAC 填全 0——这正是要问的东西;而承载它的以太网帧,目的 MAC 填广播地址全 F,交换机会把它送进网内每一个端口。
一次解析的完整过程,四步。
第一步查缓存。每台主机维护一张 ARP 缓存表(IP → MAC 的映射),有记录且未老化就直接用,一个 ARP 包都不发。
第二步广播请求。缓存未命中,主机 A 向全网广播:「谁是 192.168.1.1?请告诉 192.168.1.10。」同一广播域里所有机器都能收到。
第三步目标单播应答。网关认出目标 IP 是自己,单独回 A 一条应答:「我是,MAC 是 BB:…:02」;其他机器看一眼发现与己无关,直接丢弃。
第四步双方写缓存。这里有 RFC 826 里一个容易被忽略的细节:收到 ARP 报文的主机,要先把发送方的 IP/MAC 映射并入自己的表,然后才看操作码决定要不要回复。也就是说 A 问网关的同时,网关也顺带记下了 A——协议假设通信是双向的,这次顺手能省掉将来的一次反向广播。
缓存与免费 ARP,两个常考补丁。
缓存条目带老化时间:MAC 理论上固定,但主机会换网卡、虚拟机会迁移,旧映射不能永存。Linux 上可以用 arp -a 或 ip neigh 查看这张表。
免费 ARP 是一个「自问自答」的请求:源 IP 和目标 IP 都填自己。它干两件事——一是开机自检,若有人应答说明 IP 冲突了;二是主动刷新,本机 MAC 变了(换网卡、高可用集群里虚拟 IP 漂移到备机)后广播一声,让所有邻居把缓存里的旧映射换掉。主备切换后业务能秒级恢复,靠的就是这一声广播。
跨网段时,ARP 解析的其实不是对端。
ARP 广播出不了路由器。跨网段通信时,主机查路由表发现目标不在本网段,于是改为解析默认网关的 MAC,把帧交给网关去转发。所以「能 ping 通网关却到不了同网段另一台机器」这类故障,最后往往落在 ARP 表上——容器网络与 VPC 排障时尤其常见。
把全过程跑一遍。
下面这段 JavaScript 模拟三台主机的局域网:A 两次访问网关,随后 A「换网卡」发免费 ARP。用 Node.js 22 实测:
const net = { hosts: [], broadcast: 0, unicast: 0 };
function host(name, ip, mac) {
const h = { name, ip, mac, cache: new Map(),
resolve(t) {
if (h.cache.has(t)) return console.log(`${name}: 缓存命中 ${t}(0 个 ARP 包)`);
net.broadcast++;
console.log(`${name}: 广播请求「谁是 ${t}?」`);
for (const o of net.hosts) {
if (o === h) continue;
o.cache.set(h.ip, h.mac); // 收方先把发送方映射并入表
if (o.ip === t) {
net.unicast++;
console.log(`${o.name}: 单播应答「我是 ${t},MAC=${o.mac}」`);
h.cache.set(t, o.mac);
}
}
},
gratuitous() { // 免费 ARP:源 IP 与目标 IP 都是自己
net.broadcast++;
for (const o of net.hosts) if (o !== h) o.cache.set(h.ip, h.mac);
console.log(`${name}: 免费 ARP,全网刷新我的映射`);
} };
net.hosts.push(h); return h;
}
const A = host('A', '192.168.1.10', 'AA:01');
const GW = host('GW', '192.168.1.1', 'BB:02');
host('C', '192.168.1.30', 'CC:03');
A.resolve('192.168.1.1');
A.resolve('192.168.1.1');
A.mac = 'DD:04'; // 模拟换网卡
A.gratuitous();
console.log('免费 ARP 后 GW 的缓存:', [...GW.cache]);
图 2:解析四步时序、报文字段与两个常考补丁。
实测输出:第一次访问产生 1 个广播加 1 个单播;第二次缓存命中,0 个 ARP 包;网关的表里也被顺手写入了 A 的映射;免费 ARP 之后,网关缓存里 A 的旧 MAC「AA:01」被新值「DD:04」刷新。四步机制与两个补丁全部兑现。
回到面试现场,建议的答题结构:一句话定位(ARP 把下一跳 IP 解析成 MAC,链路层发包需要)→ 四步过程(查缓存、广播请求、单播应答、写缓存,主动提「收方也顺手记发送方」这个细节)→ 两个补丁(缓存老化、免费 ARP)→ 一个边界(跨网段解析的是网关 MAC)。答到这里,这道题就从背诵题变成了展示题。你排障时遇到过 ARP 相关的坑吗,比如 IP 明明能通、二层却收不到包?评论区聊聊。