知道 IP 之后,为什么还需要 MAC?

下一跳已经找到了,可它还是不能出发

上一次,数据包终于弄清楚了第一步该往哪走。

对于这次要去的 8.8.8.8,路由表给出了答案:不是本地直连,得走默认路由。下一跳,是 192.168.1.1,从 eth0 出发。

数据包本以为,事情到这里已经结束了。它知道该交给谁了,剩下的就是交出去。

可它试着把数据交出去的时候,卡住了。

192.168.1.1,只是一个 IP 地址。

而这一跳,真正要发生的,是把一份数据从这台主机的网卡,递到 192.168.1.1 所在的那台设备的网卡上。网卡和网卡之间的交付,靠的从来不是 IP 地址。

数据包第一次意识到:

知道该交给谁,和知道怎么把东西递过去,是两件事。

IP 地址和 MAC 地址,到底有什么不同?

先把这件事说清楚,不用比喻,直接从这次遇到的问题出发。

IP 地址,负责的是网络层的寻址。在这次旅行里:

1
2
3

目的 IP = 8.8.8.8

这个地址描述的是最终希望到达的地方。不管中间经过多少设备,这个目的 IP 自始至终都不会变。

MAC 地址,负责的是当前链路上的交付。此刻数据包要解决的问题是:

1
2
3
4

下一跳 IP: 192.168.1.1
下一跳 MAC: 未知

MAC 地址回答的是一个范围小得多的问题——这一跳的 Frame,应该交给当前这条链路上的哪块网卡。它的意义,只在当前这一跳有效。

这里有一件必须说清楚的事:MAC 地址不是最终目的地址。数据包最终要去的是 8.8.8.8,不是 aa:bb:cc:dd:ee:ff。MAC 地址只是这一跳临时需要用到的、链路层意义上的交付凭证。

数据包第一次听到 ARP

现在,数据包面前只剩一个具体的、可以解决的小问题:

1
2
3
4

已知: 192.168.1.1
未知: 它对应的 MAC 地址

负责解决这个问题的机制叫 ARP(Address Resolution Protocol,地址解析协议)。

它做的事情,可以理解成这样:主机在自己所在的这条链路上问了一句——

“谁是 192.168.1.1?”

这句”问”,在网络里的真实形式是一个 ARP Request,而且它通常是广播出去的:

1
2
3
4
5
6
7
8
9
10
11
12
13

ARP Request
"谁是 192.168.1.1?"


ff:ff:ff:ff:ff:ff

┌────────────┼────────────┐
▼ ▼ ▼
主机 A 主机 B 192.168.1.1


ARP Reply

同一条链路上的所有设备都会收到这个广播,但只有真正拥有 192.168.1.1 这个地址的那台设备,会回应一个 ARP Reply:

1
2
3
4

"我是 192.168.1.1,
我的 MAC 是 aa:bb:cc:dd:ee:ff。"

这里有一个边界必须划清楚:ARP 广播寻找的,是当前这条链路上、已经确定下来的下一跳 IP 对应的 MAC——不是在寻找最终目的地。 这次的 ARP 请求问的是 192.168.1.1,而不是 8.8.8.88.8.8.8 根本不在这条本地链路上,问了也不会有人回应。

答案会被记住

ARP Reply 回来了,数据包终于拿到了它要的东西:

1
2
3
4
5

192.168.1.1

aa:bb:cc:dd:ee:ff

但系统不会每发一次数据都重新广播一遍——这个映射关系,会被暂时记在一个叫邻居表(Neighbor Cache,传统上也叫 ARP Cache)的地方。可以直接看一眼:

1
ip neigh

输出类似:

1
2
3

192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE

这一行,就是刚才那次 ARP 交换留下的结果。不过要注意,这不是一个永久的绑定关系——邻居表的状态会随时间和链路情况变化,过一段时间没有通信,或者对方设备发生变化,这条记录也会重新被确认或者被丢弃。

终于可以真正封装出去

到这一步,数据包手里已经凑齐了三样东西:

1
2
3
4
5
6

最终目的 IP: 8.8.8.8
下一跳 IP: 192.168.1.1
下一跳 MAC: aa:bb:cc:dd:ee:ff


于是,一份真正可以在这条链路上发送的 Ethernet Frame 被组装出来了:

┌──────────────────────────────────────┐
│ Ethernet Frame │
│ │
│ 目的 MAC = aa:bb:cc:dd:ee:ff │
│ 源 MAC = 本机 MAC │
│ │
│ ┌──────────────────────────────────┐ │
│ │ IP Packet │ │
│ │ │ │
│ │ 源 IP = 本机 IP │ │
│ │ 目的 IP = 8.8.8.8 │ │
│ │ │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────┘

这张图里藏着这次旅行最重要的一次认知:

Ethernet Frame 的目的 MAC,是下一跳设备的 MAC——不是最终服务器的 MAC。而被包在里面的 IP Packet,目的 IP 始终是 8.8.8.8,从头到尾没有变过。

也就是说,IP Packet 正朝着最终目的地前进,但 Ethernet Frame,只负责把它送到眼前这一跳。

每一跳,外包装都会变

数据包这才想起,自己第一次出发的时候,曾经隐约看到过一句话:每一跳,链路层的外包装都会发生变化。当时它没太在意,现在,它终于亲眼看清楚了这是什么意思。

在这一跳:

1
2
3
4

Ethernet: 目的 MAC = 下一跳路由器的 MAC
IP: 目的 IP = 8.8.8.8

当这个 Frame 到达下一跳设备(通常是一台路由器)以后,事情会这样发生:

1
2
3
4
5
6
7
8
9
10
11
12
13

Ethernet Frame 到达

拆掉当前这层链路层封装

取出里面的 IP Packet

重新查一次路由,决定下一个下一跳

重新做一次 ARP(如果还不知道对方 MAC)

用新的目的 MAC,重新封装一个新的 Ethernet Frame

于是,下一跳的样子会变成:

数据包这下真正明白了:

IP 层关心的是最终目的地,这个目的地全程不变;链路层关心的只是当前这一跳,每经过一台设备都会被重新包装一次。

同一个 IP Packet,可以在旅途中的不同链路上,被包进完全不同的 Ethernet Frame。

让 Linux 告诉我们这一切真的发生了

实验一:看邻居表

1
ip neigh

找到对应行:

1
2
3

192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE

这就是 IP 和 MAC 之间那条暂时的映射记录。

实验二:主动触发一次解析

1
ping -c 1 192.168.1.1

再看一次:

1
ip neigh

这里要说清楚一件容易搞混的事:ping 用的是 ICMP,它本身并不是 ARP。 只是当主机准备向 192.168.1.1 发送 IP 数据、又发现邻居表里没有对应记录时,内核会先触发一次 ARP 解析,这才让邻居表里多出这条记录。ARP 是这个过程的副产品,不是 ping 本身。

实验三:用 tcpdump 亲眼看到那声”喊话”

如果环境允许,这是本篇最值得做的一个实验:

1
sudo tcpdump -ni eth0 arp

然后另开一个终端:

1
ping -c 1 192.168.1.1

如果邻居表里还没有对应记录,大概率能看到:

1
2
3
4

ARP, Request who-has 192.168.1.1 tell 192.168.1.100
ARP, Reply 192.168.1.1 is-at aa:bb:cc:dd:ee:ff

这两行,就是前面故事里”喊了一声谁是 192.168.1.1”和”对方回答我在这里”的真实样子。

如果邻居表里已经有缓存,可能什么都看不到——因为根本不需要重新问。想重新观察这个过程,可以先删掉这条邻居记录:

1
sudo ip neigh del 192.168.1.1 dev eth0

再重新发一次流量。

(IP 地址、接口名请替换成自己环境里实际的值;也不建议在正通过某条连接远程登录的生产服务器上,随手删除这条连接依赖的邻居项。)

三个问题,三个不同的答案

走到这里,可以把这次旅行里出现过的三件事放在一起看看,它们看起来相似,实际回答的是完全不同层次的问题:

1
2
3
4
5

DNS 回答: 最终要去哪里? 域名 → IP
路由 回答: 这一跳交给谁? 目的 IP → 下一跳 IP
ARP 回答: 这一跳在当前链路上对应哪个地址? 下一跳 IP → 下一跳 MAC

整个过程串起来,就是这次旅行里最重要的一条主线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

最终目的 IP
8.8.8.8

│ 路由

下一跳 IP
192.168.1.1

│ ARP

下一跳 MAC
aa:bb:cc:dd:ee:ff

│ Ethernet

下一跳设备

(补充一句:如果这次目的地是 IPv6 地址,流程里对应的机制不叫 ARP,而是 Neighbor Discovery——但基本思路是相通的,这里先不展开。)

出发了,但这只是第一跳

Frame 终于封装完成,从网卡送了出去。

数据包松了一口气——这一路走来,它总算把”下一跳 IP”变成了真正可以交付的东西。

但它很快意识到一件事:自己现在离开的,只是本机这一小段链路。而它前面用来判断”下一跳交给谁””这一跳的 MAC 是什么”的这一整套过程——路由查询、ARP 解析、重新封装——刚才在路由器那一侧被自己隐约瞥见过一次。

它开始好奇:

当这个 Frame 抵达下一跳那台路由器时,对方会怎么处理它?它会不会,把刚才这整套判断,原样再做一遍?

这个问题,留给下一次旅行。

知道 IP 之后,为什么还需要 MAC?

https://pengtech.net/network/packet-journey/arp-mac.html

作者

鹏叔

发布于

2026-09-24

更新于

2026-09-24

许可协议

评论