【Linux 网络编程】ARP 的具体过程与 ARP 欺骗:一条 IP → MAC 映射,怎样改变数据的去向?

🔥 星光编译者 · 个人主页

📚 学习专栏: 《C/C++ 成长笔记》 · 《Linux 实践手册》 · 《数据结构与算法》

🌄 向云端飞扬,编译属于自己的代码星河。


☕ 写在开篇

  你好,这里是 星光编译者。

  这里记录我在 C/C++、Linux、数据结构与算法 学习中遇到的真实问题、亲手验证过的代码,以及那些容易被忽略的实现细节。

  比起简单罗列结论,我更愿意从问题出发,把一个知识点的来由讲清楚,把"为什么会这样"和"应该怎样解决"说明白,让每一次踩坑都沉淀成可以复用的经验。

  如果这篇记录能帮你少绕一点路,或让某个模糊的地方忽然变得清晰,那么这次分享便有了意义。愿我们在一次次阅读、编译与调试中稳步向前,慢慢搭起属于自己的技术世界。


🔥 本文定位:认识以太网帧之后,继续沿着"下一跳 IP → ARP 请求 → ARP 应答 → 邻居缓存 → 数据发送"走一遍,再看错误映射怎样把流量引向第三台设备。

💡 学习目标:能手填一组 ARP 请求与应答字段;分清以太网地址与 ARP 内部地址;读懂 Linux 邻居状态;解释双向欺骗、中间人转发与断网之间的关系。

📌 阅读范围:以同一 VLAN 内的 Ethernet II、IPv4 单播为主。基础例子不含 NAT、代理 ARP、隧道和网关冗余;涉及这些情况时单独说明。地址、抓包输出和流量路径均为教学示意,离线实验不会发送网络报文。

假设你正在访问一台远端服务器。路由表已经告诉内核:这份 IP 报文应该从 eth0 发出,下一跳是 192.168.10.1。

到这里,事情还没有完成。因为以太网帧的目的地址需要填 MAC 地址 ,而不是 192.168.10.1 这串 IP 地址。

内核会继续问:谁知道 192.168.10.1 对应哪一个 MAC?

ARP 就是在回答这个问题。但新的问题随之而来:

  • 还不知道对方 MAC,第一份请求怎么发出去?
  • 以太网首部已经写了 MAC,ARP 里面为什么还要写一次?
  • 收到一句"这个 IP 对应我的 MAC",主机凭什么相信?
  • 如果有人把网关 IP 对应的 MAC 换成自己的,IP 目的地址明明没变,流量为什么会绕路?
  • 绕到了第三台主机,是马上断网,还是仍然可以正常访问?

把正常地址解析和错误地址解析放在同一套地址里观察,这些问题就能连起来。


文章目录

  1. [先决定问谁:ARP 解析的是当前下一跳](#先决定问谁:ARP 解析的是当前下一跳)
  2. [拆开 ARP 报文:外层负责送达,内层描述映射](#拆开 ARP 报文:外层负责送达,内层描述映射)
  3. 完整走一次:从广播请求到真正发送数据
  4. 邻居缓存不是一张永远不变的通讯录
  5. [Linux 观察:把路由、缓存和抓包对应起来](#Linux 观察:把路由、缓存和抓包对应起来)
  6. [ARP 欺骗:改掉映射,改变当前帧的收件人](#ARP 欺骗:改掉映射,改变当前帧的收件人)
  7. 单向、双向与转发:为什么有时绕路,有时断网
  8. 离线实验:解析报文,再模拟缓存被改写
  9. 发现与防护:把异常线索串成证据
  10. 常见误区与自测:把几个边界真正分清

一、先决定问谁:ARP 解析的是当前下一跳

1.1 先约定整篇文章的地址

为了让每一张图、每一个字段都能对应起来,本文统一使用下面这组地址。

设备 角色 IPv4 地址 MAC 地址 图中简称
A 普通主机 192.168.10.10/24 02:00:00:00:00:0a MAC_A
B 同一子网的主机 192.168.10.20/24 02:00:00:00:00:0b MAC_B
R 默认网关的 LAN 接口 192.168.10.1/24 02:00:00:00:00:01 MAC_R
M 后文模拟的第三台主机 192.168.10.66/24 02:00:00:00:00:66 MAC_M
S 远端服务器 203.0.113.80 当前局域网不需要知道 ---

A、B、R、M 位于同一个以太网广播域,由交换机连接。203.0.113.80 属于文档示例地址,只用于解释跨网段路径,不是供读者直接连通测试的公网服务。

这些 02: 开头的 MAC 是教学用的本地管理单播地址。这里需要记住的是它们彼此不同,而不是把 MAC 当成永不变化、绝对可信的设备身份证。

1.2 同网段:问 B;跨网段:问网关 R

在上述普通路由配置下,A 访问 B 时,内核选中直连路由,下一跳就是 B。于是要解析:

text 复制代码
192.168.10.20 → MAC_B

A 访问 S 时,内核选中经由 R 的路由,当前链路应该交给 R。此时解析的是:

text 复制代码
192.168.10.1 → MAC_R

这时发出的 IPv4 报文和以太网帧分别指向:

text 复制代码
IP 目的地址:203.0.113.80
以太网目的 MAC:02:00:00:00:00:01

所以,"已知目的 IP,查它的 MAC"必须补上前提:这个 IP 是当前链路上需要交付的下一跳。 访问远端网站时,A 通常不会广播询问网站服务器的 MAC。

"是否同网段"是便于入门的描述,真正执行时应以路由查找结果为准。策略路由、主机路由、VPN 和代理 ARP 都可能让实际行为超出一个 /24 示例;排查时先看 ip route get,不要仅凭 IP 前三段相同就下结论。

1.3 ARP 的广播不会随着 IP 路由一路传到服务器

普通二层交换机会在相应 VLAN 内转发广播,但路由器不会把这份 ARP 广播当成普通 IP 报文继续路由到远端。

如果 R 的出接口也使用需要 ARP 的以太网链路,它会按自己的路由选择,在那条链路上解析自己的下一跳。这是另一次本地解析,和 A 查询 R 的过程是两回事。

还有两个范围要分清:ARP 在本文中服务于 IPv4;IPv6 使用邻居发现机制。ARP 也不是 DNS,DNS 解决名称到地址等记录的查询,不负责告诉本地网卡下一帧该交给哪个 MAC。


二、拆开 ARP 报文:外层负责送达,内层描述映射

2.1 ARP 不是"放在 IP 包里的一个小协议"

在本文的 Ethernet II 场景中,ARP 直接作为以太网帧的载荷:

text 复制代码
以太网首部 | ARP 报文 | 必要的填充 | FCS

它前面没有 IPv4 首部,也没有 TCP/UDP 首部,因此不谈 TCP 端口、UDP 端口或 IP TTL。

最容易混淆的是两个"类型"字段:

位置 字段 本例值 回答的问题
以太网首部 EtherType 0x0806 这帧装的是哪种协议?装的是 ARP
ARP 内部 Protocol Type,PTYPE 0x0800 正在解析哪种协议地址?解析的是 IPv4 地址

如果一帧外层 EtherType 是 0x0800,表示它的以太网载荷是 IPv4;如果它是 0x0806,而里面的 PTYPE 是 0x0800,则表示"这是一份解析 IPv4 地址的 ARP 报文"。两个字段在不同层次,不能交换。

2.2 28 字节怎样组成

对 Ethernet + IPv4 这个组合,ARP 报文有以下字段。

字段 长度 本例含义与常见取值
HTYPE 2 字节 硬件类型,Ethernet 为 1
PTYPE 2 字节 协议类型,IPv4 为 0x0800
HLEN 1 字节 硬件地址长度,MAC 为 6
PLEN 1 字节 协议地址长度,IPv4 为 4
OPER 2 字节 操作码,请求为 1,应答为 2
SHA 6 字节 Sender Hardware Address,发送方硬件地址
SPA 4 字节 Sender Protocol Address,发送方协议地址
THA 6 字节 Target Hardware Address,目标硬件地址
TPA 4 字节 Target Protocol Address,目标协议地址

总长度是:

text 复制代码
2 + 2 + 1 + 1 + 2 + 6 + 4 + 6 + 4 = 28 字节

更一般地说,ARP 的长度由地址长度决定,可以写成 8 + 2 × HLEN + 2 × PLEN。因此"ARP 永远是 28 字节"不够严谨;这里的 28 来自 Ethernet 和 IPv4 这两个条件。

在无 VLAN 标签的普通以太网里,14 字节首部加上 28 字节 ARP 只有 42 字节,还需要填充到最小帧要求。按包含 FCS、不包含前导码与帧开始定界符的口径计算:

text 复制代码
14 字节首部 + 28 字节 ARP + 18 字节填充 + 4 字节 FCS = 64 字节

抓包是否看到填充和 FCS,取决于抓包位置、驱动及硬件。看到 length 28 时,先确定工具描述的是 ARP 长度,别直接认定整条线上只传输了 28 字节。

2.3 为什么 MAC 地址出现了两遍,却不总是一样

以太网首部解决"把这一帧交到哪里";ARP 内部解决"哪一个协议地址对应哪一个硬件地址"。ARP 自己携带地址类型和地址长度,可以适用于不止一种链路环境。

在普通 ARP 请求中,发送方尚不知道目标 MAC,常见填写方式是:

text 复制代码
以太网目的 MAC = ff:ff:ff:ff:ff:ff    # 让本广播域内的设备收到
ARP 的 THA     = 00:00:00:00:00:00    # 此时未知,不靠它投递
ARP 的 TPA     = 192.168.10.20         # 真正要询问的目标 IP

这里两处"目的 MAC"显然不同。外层广播地址是投递方式,内层全零目标硬件地址表示还未获知目标。 不要把全零 THA 当作广播地址,也不要认为网卡会根据 ARP 内部的 THA 决定是否接收这一帧。

普通应答中,双方地址已经明确,外层地址与内层地址通常对应一致。但字段相同只是常见报文形态,不构成身份认证;伪造者完全可以把外层源 MAC 与 SHA 同时填成自己的 MAC。

字段定义与基础收发算法可对照 RFC 826。


三、完整走一次:从广播请求到真正发送数据

下面让 A 向 B 发送一个普通 IPv4 报文,并假设 A 当前没有可用的 B 邻居映射。

3.1 第一步:查路由,再查邻居

内核先选定出口和下一跳。直连场景中,B 的 192.168.10.20 就是下一跳。随后查询该接口对应的邻居信息。

如果已有可用 MAC,可以直接封装;如果没有,就启动地址解析。等待发送的数据可能被暂存在有容量限制的队列里,因此不能理解成"解析没结束,所有待发报文都能无限等待"。

ARP 也不是每次调用 send() 都发生一次。应用调用、IP 报文、以太网帧和地址解析不是一一对应关系。

3.2 第二步:A 广播询问"谁是 B"

请求可以翻译成一句话:

我是 192.168.10.10,我的 MAC 是 MAC_A。谁拥有 192.168.10.20?请告诉我你的 MAC。

字段具体如下。

字段 A 发出的请求
Ethernet Destination ff:ff:ff:ff:ff:ff
Ethernet Source 02:00:00:00:00:0a
EtherType 0x0806
HTYPE / PTYPE 1 / 0x0800
HLEN / PLEN 6 / 4
OPER 1
SHA / SPA 02:00:00:00:00:0a / 192.168.10.10
THA / TPA 00:00:00:00:00:00 / 192.168.10.20

交换机把这份广播转发到该 VLAN 中符合转发条件的其他端口。B、R、M 都可能看到请求,但在不考虑代理 ARP、地址冲突等特殊情况时,只有拥有目标 IP 的 B 应当回应这个查询。

"其他设备不应答"不等于"所有其他设备完全不处理任何字段"。是否学习或更新发送者映射,与接收端已有表项、实现和配置有关;不能推导成全网一定自动新增 A 的缓存。

3.3 第三步:B 通常单播回复 A

B 可以从请求里的 SHA、SPA 得到 A 的地址信息,通常不必为了回复 A 再广播查询一次。

应答相当于说:

192.168.10.20 对应的 MAC 是 MAC_B;这份答复发给刚才询问的 A。

字段 B 发回的应答
Ethernet Destination 02:00:00:00:00:0a
Ethernet Source 02:00:00:00:00:0b
EtherType 0x0806
HTYPE / PTYPE 1 / 0x0800
HLEN / PLEN 6 / 4
OPER 2
SHA / SPA 02:00:00:00:00:0b / 192.168.10.20
THA / TPA 02:00:00:00:00:0a / 192.168.10.10

"请求广播、应答单播"描述的是这里的普通首次解析过程。后续邻居探测可能使用单播请求,地址冲突检测等场景也会出现广播形式的 ARP 报文,不能把这句话当成没有例外的报文分类规则。

3.4 第四步:A 保存映射,再发真正的数据

A 接受应答并得到有效映射后,可以把待发 IPv4 报文封装成:

text 复制代码
Ethernet Destination = MAC_B
Ethernet Source      = MAC_A
EtherType            = 0x0800
IPv4 Source          = 192.168.10.10
IPv4 Destination     = 192.168.10.20

注意 EtherType 从 ARP 阶段的 0x0806 变成了 IPv4 数据阶段的 0x0800。

ARP 请求与应答是为了获得映射,随后才是数据帧。不能把 ARP 应答本身当成刚才准备发送的应用数据。 即使抓包里 ARP 完整成功,也只能说明这一步有进展,不保证 B 的应用正在监听、更不保证应用一定返回正确结果。

3.5 如果是路由器向局域网主机交付呢

换一个观察方向:R 收到一份目的 IP 为 B 的报文,查路由后发现 B 在自己的 LAN 侧直连网络。

如果 R 没有 B 的映射,它同样要询问 192.168.10.20。此时请求中的 SHA、SPA 换成 R 的 LAN 接口 MAC 和 IP,TPA 仍是 B 的 IP。B 回复 R,R 随后封装发给 B 的以太网帧。

所以 ARP 不只在"电脑第一次上网"时出现。凡是需要在这条链路上解析 IPv4 邻居的设备,都可能发起这个过程。


四、邻居缓存不是一张永远不变的通讯录

4.1 先区分三张表

如果把不同设备上的表都叫作"地址表",ARP 欺骗就很容易讲歪。

信息 常见所在位置 典型对应关系 解决什么
路由表 主机、路由器 目的前缀 → 出口、下一跳 下一步走哪条路
ARP / IPv4 邻居表 主机、路由器 接口上的 IPv4 邻居 → MAC、状态 当前帧交给哪个 MAC
MAC 转发表 / FDB 二层交换机 VLAN + MAC → 端口 从哪个交换机端口转发

ARP 欺骗的核心是让受影响设备接受错误的 IP 与 MAC 绑定。交换机可能完全没有被改错:它只是正常把"目的 MAC 为 MAC_M"的帧送到 M 所在端口。

因此,交换机工作正常与终端遭遇 ARP 欺骗,并不矛盾。它们判断的依据不同。

4.2 Linux 中的 STALE 不等于"这条映射已经不能用了"

Linux 把 IPv4 ARP 信息放在邻居子系统中管理。ip neigh 可以看到地址和状态,但状态描述的是可达性判断,不能当作安全认证结果。

状态 可以怎样理解
INCOMPLETE 尚未完成地址解析
REACHABLE 最近有可达性确认,当前认为邻居可达
STALE 映射仍在,但需要在后续使用中重新确认可达性
DELAY 延迟主动探测,给其他确认机会
PROBE 正在主动探测邻居
FAILED 探测达到失败条件,未能确认邻居
PERMANENT 管理员配置的永久表项,不按普通动态老化处理

图中是便于理解的一条典型路径,不是完整内核状态机。实际迁移还受收到的报文、上层确认、定时器和管理员操作影响。STALE 表项通常仍可用于发送,随后再确认;不能看到它就判断已经断网。状态含义可查 ip-neighbour 手册。

缓存也没有适用于所有设备的统一"固定几分钟失效"公式。Linux 的可达性确认和垃圾回收是不同机制,成功通信的上层反馈也可能帮助确认邻居。Linux arp 手册

4.3 为什么允许更新,又为什么留下欺骗空间

设备更换网卡、虚拟机迁移或网络发生正常切换时,同一 IP 对应的 MAC 确实可能改变。如果旧映射永远不允许更新,网络就很难从合法变化中恢复。

问题在于:基础 ARP 报文中没有用来证明"我确实有权代表这个 IP"的密码学认证。发送者可以声明一个 IP 与 MAC 的组合,接收者再根据本地逻辑决定是否采用。

这里既不能夸大成"收到任意 ARP 都会无条件覆盖",也不能简化成"只接收自己刚问过的应答,所以绝对安全"。已有缓存、报文类型、接收策略、静态配置和交换机防护都会影响结果。

例如,Linux 的 arp_accept 涉及免费 ARP 对不存在条目的创建行为,不能把它当成一键启用完整 ARP 身份认证的开关。Linux 内核 ARP 配置说明

4.4 主动宣告和冲突检测不等于攻击

网络中可能看到设备主动宣告自己的地址映射,也可能看到它在使用地址前进行冲突检测。这类流量有正常用途。

以 RFC 5227 的两个报文为例:ARP Probe 使用 SPA=0.0.0.0 探测候选地址;ARP Announcement 则以请求形式宣告已使用的地址,其 SPA 与 TPA 相同。仅凭"没有先看到请求""请求的源 IP 是 0.0.0.0",不能直接定性为欺骗。RFC 5227:IPv4 地址冲突检测

真正要核查的是:宣告是否符合网络设计,IP 的拥有者是否发生合法变化,声明的 MAC 是否能通过独立信息确认。


五、Linux 观察:把路由、缓存和抓包对应起来

5.1 先确定观察的是哪张网卡

在自己的 Linux 测试环境中,可以从这些只读命令开始:

bash 复制代码
ip -br addr
ip route
ip route get 192.168.10.20
ip route get 203.0.113.80
ip neigh show

ip route get 查询内核会怎样选择路径,本身不等同于向目标发送一次业务请求。如果当前环境没有相应路由,可能直接返回不可达;不要为了得到和文章一样的结果而修改正在使用的网络。

假设本地接口确实叫 eth0,进一步查看:

bash 复制代码
ip neigh show dev eth0
ip neigh show to 192.168.10.1 dev eth0

教学示意输出:

text 复制代码
192.168.10.1 dev eth0 lladdr 02:00:00:00:00:01 REACHABLE
192.168.10.20 dev eth0 lladdr 02:00:00:00:00:0b STALE

第一行是网关映射,第二行是 B 的映射。表里没有 S 的地址,在经由网关访问 S 的场景下很正常。

旧工具 arp -a 也能查看 ARP 缓存,但并非每个 Linux 环境都安装了 net-tools。新学习时优先熟悉 ip neigh,也能避免把不同工具的显示格式混为一谈。

5.2 抓包时要同时看到外层 MAC 和 ARP 内容

下面的命令在选定接口上观察 ARP:

bash 复制代码
sudo tcpdump -i eth0 -nn -e -vvv arp

其中 -i 选择接口,-nn 避免名称和端口名解析,-e 输出链路层首部,-vvv 增加显示细节,最后的 arp 是过滤表达式。按 Ctrl+C 停止即可。

要对照 Ethernet II 字段,尽量使用实际以太网接口;Linux 的 any 抓包可能使用 cooked capture 格式,不适合直接照着普通 Ethernet 首部偏移解析。tcpdump 官方手册

在另一个终端访问自己可控的同网段主机,例如:

bash 复制代码
ping -c 1 192.168.10.20

如果已有可用邻居缓存,抓不到新的广播请求并不反常。可以先等自然重新解析,或在独立测试环境里观察一个尚未解析的可达邻居;不必清空整张邻居表来强行制造现象。

5.3 两行抓包怎样读回字段

下面是省略时间戳、长度和部分格式的教学输出,并非真实抓包记录:

text 复制代码
02:00:00:00:00:0a > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806),
Request who-has 192.168.10.20 tell 192.168.10.10

02:00:00:00:00:0b > 02:00:00:00:00:0a, ethertype ARP (0x0806),
Reply 192.168.10.20 is-at 02:00:00:00:00:0b

第一份报文里,who-has 后是 TPA,tell 后是 SPA;第二份报文里,is-at 把发送方协议地址与硬件地址连成了一条声明。

如果只过滤 arp,后续 ping 的 ICMP 数据不会出现在输出里。要观察解析与数据的先后关系,可改为:

bash 复制代码
sudo tcpdump -i eth0 -nn -e 'arp or icmp'

不要因为过滤器没显示某种流量,就断定网络里没有发生这种流量。

5.4 "看不到"本身也需要解释

ARP 成功但 ping 失败,可能是 ICMP 被过滤;ping 成功但连接服务失败,可能是端口没有监听。它们分别属于不同层次。

另外,交换网络中普通接入口往往看不到其他端口之间的单播。即使开启混杂模式,也不能让交换机自动把所有单播发给观察者。要确认 A 与 R 之间的实际报文,应选择端点抓包、交换机镜像等合适观察位置。

这条边界对后面判断欺骗同样重要:第三台主机没有抓到某条单播应答,既不能证明应答不存在,也不能证明缓存没有变化。


六、ARP 欺骗:改掉映射,改变当前帧的收件人

6.1 正常情况下,双方分别记住谁

现在回到 A 通过网关 R 访问 S 的场景。正常映射应当是:

text 复制代码
A 的邻居表:192.168.10.1  → MAC_R
R 的邻居表:192.168.10.10 → MAC_A

A 把出站帧交给 R;R 把返回 A 的帧交给 A。M 即便接在同一台交换机上,也不会因为"在同一个局域网"就自动收到全部 A 与 R 之间的单播。

6.2 M 向 A 冒充网关,向 R 冒充 A

经典双向欺骗模型中,M 试图让两端接受两条虚假声明:

接收者 虚假声明 ARP 中被冒充的 SPA 被宣称的 SHA
A 网关 IP 对应 MAC_M 192.168.10.1 02:00:00:00:00:66
R A 的 IP 对应 MAC_M 192.168.10.10 02:00:00:00:00:66

注意,M 并不是对 A 宣告"我的 IP 是 192.168.10.66"。那只是它正常的地址。欺骗发生在它声称自己可以代表网关的 IP ,并对另一端声称可以代表 A 的 IP。

若两端按其接收策略采用了这些信息,结果就变成:

text 复制代码
A 的邻居表:192.168.10.1  → MAC_M
R 的邻居表:192.168.10.10 → MAC_M

图中的"采用"是条件,不是收到报文必然发生的动作。防护设备、静态绑定或接收端策略都可能拒绝更新。讨论机制时,应该先明确"错误映射已经被接受"这个前提,再推导流量怎样走。

6.3 目的 IP 没变,为什么帧已经绕路

A 此时仍然认为网关是 192.168.10.1,默认路由也可能完全没变。只是解析到的链路层目的地址错了。

信息 正常发送 错误映射被采用后
IP 目的地址 203.0.113.80 203.0.113.80
路由下一跳 IP 192.168.10.1 192.168.10.1
Ethernet 目的 MAC MAC_R MAC_M

于是交换机收到目的 MAC 为 MAC_M 的帧,把它送到 M 的端口。这恰恰可能是交换机按规则工作的结果。

这一点也解释了:为什么只检查默认路由不一定能发现问题。路由正确只能说明选中的下一跳 IP 正确,不能证明这个 IP 对应的 MAC 也正确。

6.4 为什么错误映射可能反复出现

合法设备也会继续通信并发送真实信息,缓存还会经历确认、老化与刷新。因此,错误绑定不一定一直稳定存在,有时会在真实 MAC 和异常 MAC 之间来回变化。

如果只删除本地表项,却没有消除错误声明的来源,下一次解析仍可能接受错误结果。这也是"清了缓存暂时恢复,过一会儿又不正常"只能算现象、不能算根因修复的原因。

不要把攻击是否成功归结成"谁发得快谁赢"的唯一规则。顺序和频率可能影响结果,但表项状态、报文是否合法、系统更新策略及交换机检查同样会参与决定。


七、单向、双向与转发:为什么有时绕路,有时断网

7.1 只影响 A,不代表两个方向都经过 M

假设只有 A 把网关映射成 MAC_M,而 R 仍然正确记着 MAC_A。

如果 M 能把从 A 收到的数据继续交给 R,那么出站方向可能是:

text 复制代码
A → M → R

返回方向却可能仍是:

text 复制代码
R → A

这就形成了方向不对称。只控制一个端点的邻居映射,不能直接声称已经看到了完整双向会话。

7.2 双向欺骗配合正确转发,才形成经典中间路径

若 A 和 R 都接受错误映射,而且 M 能把数据正确送给真实下一跳,局域网这一段可能变成:

text 复制代码
出站:A → M → R
入站:R → M → A

M 需要知道真实的 MAC_A 与 MAC_R,并有能力接收、处理和转发这些原本不以自己为最终 IP 目的地的数据。操作系统默认不会因为收到一个发往自身 MAC 的帧,就自动替其中任意目的 IP 完成合适的转发。

对于教学中的不做 NAT 的转发模型,A 发往 S 的 IP 目的地址仍是 S;M 转交给 R 时,当前链路的目的 MAC 改为 MAC_R。若使用三层转发,TTL 等字段会按转发规则变化;二层中继又有不同细节。不要把所有中间人实现都描述成"只复制字节,其他任何字段绝不变化"。

7.3 如果 M 不转发,通常表现为通信受阻

错误映射只负责把帧引向 M,并不负责保证业务继续成功。M 如果丢弃报文、没有转发能力或受到防火墙限制,A 访问外部网络就可能超时。

因此,同一类缓存污染可能造成不同后果:

条件 可能看到的现象
只有一个方向的映射受影响 部分流量绕行,路径不对称
双方映射受影响,M 能正确转发 业务可能继续运行,但链路中出现第三方
错误映射被采用,M 不转发 丢包、超时或连接中断
防护拒绝虚假映射 欺骗尝试未必改变正常路径

"网络还能用"不能排除中间路径;"突然断网"也不能单凭这一条就证明受到 ARP 欺骗。网线、接口、VLAN、地址冲突和普通配置错误都可能制造类似表象。

7.4 经过 M,不等于 HTTPS 内容就能被解密

在正确验证服务端身份、密钥未泄露、终端没有被控制的前提下,TLS 会保护应用数据的机密性和完整性。仅仅因为流量经过 M,M 并不能凭 ARP 欺骗自动得到 HTTPS 明文。

它仍可能看到部分网络元数据,并能丢弃或延迟流量;对于没有加密的协议,风险范围又不同。如果还存在证书信任被篡改、用户忽略认证错误或终端失陷,那是需要额外分析的条件,不能统统归功于 ARP 本身。RFC 8446:TLS 1.3 的安全目标

所以,"防止错误链路绑定"和"保护应用层通信"是两项互相补充的工作。


八、离线实验:解析报文,再模拟缓存被改写

光看表格,很容易把 SHA、SPA、THA、TPA 的顺序记反。这里用一个离线脚本把两件事接起来:先从字节解析真实的字段布局,再在 Python 字典中观察错误绑定的影响。

配套文件:code/arp_lab.py。只使用 Python 3 标准库,不打开套接字,不发送报文,也不读取或修改系统邻居表。

8.1 把 Ethernet 与 ARP 分开解包

核心结构如下;完整脚本还包含输入长度和类型检查、示例构造及自检。

python 复制代码
import ipaddress
import struct

ETHERNET = struct.Struct("!6s6sH")
ARP_IPV4 = struct.Struct("!HHBBH6s4s6s4s")

def mac_text(value):
    return ":".join(f"{byte:02x}" for byte in value)

def decode(frame):
    # 教学解析器只接受无 VLAN 标签的 Ethernet + IPv4 ARP。
    if len(frame) < ETHERNET.size + ARP_IPV4.size:
        raise ValueError("truncated Ethernet/ARP frame")
    dst, src, ether_type = ETHERNET.unpack_from(frame)
    if ether_type != 0x0806:
        raise ValueError("expected EtherType 0x0806")
    htype, ptype, hlen, plen, op, sha, spa, tha, tpa = (
        ARP_IPV4.unpack_from(frame, ETHERNET.size)
    )
    if (htype, ptype, hlen, plen) != (1, 0x0800, 6, 4):
        raise ValueError("expected Ethernet/IPv4 ARP")
    if op not in (1, 2):
        raise ValueError("expected ARP request or reply")
    return {
        "ether_dst": mac_text(dst), "ether_src": mac_text(src),
        "op": op, "sha": mac_text(sha), "tha": mac_text(tha),
        "spa": str(ipaddress.IPv4Address(spa)),
        "tpa": str(ipaddress.IPv4Address(tpa)),
    }

格式字符串开头的 ! 表示采用网络字节序及标准尺寸。H 是 2 字节无符号整数,B 是 1 字节整数,6s 与 4s 则是固定长度字节串。

对于无标签 Ethernet II,ARP 从偏移 14 开始。带有 VLAN 标签或采用其他链路捕获格式时,这个起点不能直接套用,因此解析器明确拒绝不支持的外层类型。

8.2 用"发送者声明"理解缓存更新

简化模型只关注发送方字段:

python 复制代码
def learn_for_demo(table, packet):
    # 仅用于演示:真实系统不是无条件执行这一条赋值。
    table[packet["spa"]] = packet["sha"]

正常应答让模型记住 B_IP → MAC_B;模拟 M 声称网关 IP 对应 MAC_M,就让 A 的字典中出现 R_IP → MAC_M。

这里的函数特意叫 learn_for_demo:它不是 Linux ARP 接收算法,没有模拟全部邻居状态、有效性检查、接口约束和系统参数。实验说明的是"如果错误绑定已被采用,接下来会发生什么",不是证明任意真实主机会接受任意报文。

8.3 运行与预期结果

进入文章文件夹后运行:

bash 复制代码
python3 code/arp_lab.py

Windows 中若命令名为 python,可使用:

powershell 复制代码
python code/arp_lab.py

脚本先打印请求和正常应答的字段,然后打印两条模拟虚假声明。关键结果应是:

text 复制代码
ARP payload bytes: 28
A before: 192.168.10.1 -> 02:00:00:00:00:01
R before: 192.168.10.10 -> 02:00:00:00:00:0a
A after : 192.168.10.1 -> 02:00:00:00:00:66
R after : 192.168.10.10 -> 02:00:00:00:00:66
IP destination stays: 203.0.113.80
Ethernet destination becomes: 02:00:00:00:00:66
Self-checks passed: 12

脚本构造的是供解析使用的"14 字节 Ethernet 首部 + 28 字节 ARP"字节串,不包含线上的填充和 FCS。它不是可直接照搬为完整线速抓包长度的样本。

可以自己做两个小修改来检验理解:把请求中的 THA 改成其他值,观察外层目的 MAC 是否随之改变,并解释为什么投递仍由外层地址决定;或只更新 A 的字典、不更新 R 的字典,再画出返回路径。前者是分析字段用途,后者是分析方向性,都不需要向实际网络发送任何伪造报文。


九、发现与防护:把异常线索串成证据

9.1 一个 MAC 变化,只是一条线索

"网关 MAC 和昨天不同"值得核查,但合法设备替换、虚拟化迁移、网关冗余切换或代理机制也可能改变观察结果。

同理,"多个 IP 对应同一个 MAC"也不必然是攻击:一台设备可以拥有多个地址,代理 ARP 也可能使用同一个 MAC 回应多个 IP。反过来,REACHABLE 只是可达性状态,不意味着这个绑定已被认证。

更可靠的办法是关联信息:

观察项 它能说明什么 不能单独证明什么
网关邻居项突然改为陌生 MAC 本地下一跳映射发生变化 一定是恶意改写
多个设备声明同一个 IP 存在竞争声明或特殊设计 一定存在成功的中间人
ARP 映射反复来回变化 可能存在地址冲突或竞争更新 仅靠频率就能确定原因
业务仍能访问 当前路径可能仍可用 路径中没有第三方
交换机发现陌生接入口 可以进一步定位设备 这个端口一定是攻击源

9.2 沿着"路由 → 绑定 → 声明 → 端口"排查

一个便于操作的顺序是:

  1. 确认路径:核对出口接口、网关地址及路由,排除连错网络。
  2. 记录绑定:查看异常发生时的邻居映射与时间,不只看恢复后的状态。
  3. 对照声明:在合适位置抓取 ARP,比较 SPA、SHA、TPA 和以太网源地址。
  4. 独立核验:通过网关管理信息、网络资产记录、交换机 MAC 表和变更记录确认真正归属。
  5. 定位并处置:确认异常设备或配置后,按网络管理流程隔离或纠正,再验证绑定和业务是否稳定。

抓包里的 ARP 声明本身正是被怀疑的对象,不能再拿另一份同样未经验证的声明作为唯一"可信基准"。还要注意,外层源 MAC 与 SHA 一致只能排除一种不一致现象,并不能证明其有权代表 SPA。

9.3 不同防护措施,各自管哪一段

静态邻居绑定适合少量、稳定且有明确维护责任的关键关系。它可以减少相应动态映射被改写的机会,但设备合法更换 MAC 后也需要同步维护。在 A 上固定网关映射,不会自动固定 R 上指向 A 的映射,覆盖范围要按方向考虑。

Dynamic ARP Inspection,DAI 由支持该功能的交换机检查 ARP 声明,通常结合 DHCP Snooping 形成的绑定数据库。静态地址设备需要相应的 ARP ACL 或平台支持的静态绑定策略;信任端口也必须符合真实拓扑,不能把所有端口设成可信。Cisco:Dynamic ARP Inspection

VLAN 划分和接入控制减少不可信设备直接进入相同广播域的机会,但同一 VLAN 内仍需要考虑绑定安全。只启用 MAC 数量限制也不等于已经校验 IP 与 MAC 的关系。

TLS、SSH 等安全协议保护上层数据及对端身份,不能修复所有链路可用性问题;它们依然是减少错误路径影响的重要一层。排查网络异常时,不应为了"先连上"而忽略证书或主机身份告警。

这些措施没有一个可以不看网络结构就一键套用。小规模实验网络和需要高可用切换的生产网络,对静态绑定、可信端口和维护流程的要求并不相同。

9.4 清缓存是动作,消除根因才是恢复

如果问题来自重复 IP,应先纠正地址配置;如果来自合法切换与旧绑定,应核对切换机制;如果确认有异常声明源,应先阻止它继续污染。

随后再按需要刷新相关动态邻居项,重新观察映射、双向通信和业务表现。只做一次 ping 成功就结束,可能漏掉反复污染或只影响部分流量的问题。


十、常见误区与自测:把几个边界真正分清

10.1 八个容易说错的结论

容易说错 更准确的理解
ARP 根据网站 IP 查询网站 MAC 先查路由,解析当前链路上的下一跳
ARP 请求的两个目的 MAC 都是广播 外层目的通常是广播,内层 THA 常填零
ARP 放在 IPv4 包里面 本文场景下直接由以太网承载,EtherType 为 0x0806
28 字节就是完整以太网帧长度 还要考虑外层首部、填充及 FCS 的统计口径
每发一个 IP 包都要广播 ARP 可复用邻居缓存,解析与业务发送不是一一对应
STALE 表项不能用了,REACHABLE 就很安全 状态反映可达性判断,不是身份认证
双方都被欺骗就一定能正常上网 还取决于中间设备是否正确转发
ARP 欺骗能自动破解 HTTPS 错误路径本身不提供 TLS 解密能力

10.2 自测一:究竟询问哪个 IP

题目: A 的路由结果是 203.0.113.80 via 192.168.10.1 dev eth0。如果没有可用邻居映射,ARP 请求中的 TPA 应填哪个地址?后续 IP 报文的目的地址又是什么?

答案: TPA 填 192.168.10.1,因为当前链路要交给网关;IP 目的地址仍是 203.0.113.80。两者不是同一个层次的目标。

10.3 自测二:改变的到底是哪张表

题目: A 的默认路由未变,邻居表中 192.168.10.1 从 MAC_R 变成 MAC_M。交换机把 A 的出站帧送到了 M。是否说明交换机的 MAC 转发表也被改错?

答案: 不一定。A 已经把目的 MAC 填成 MAC_M,交换机按 MAC_M 所在端口转发完全可能是正常行为。首先要解释的是 A 为何采用了错误 IP 与 MAC 绑定。

10.4 自测三:哪些方向会经过第三方

题目: 只有 A 的网关映射受影响,R 仍然正确映射 A。假设 M 能正常转发 A 发出的数据,返回流量是否也必然经过 M?

答案: 不必然。A 的出站流量可能走 A → M → R,返回流量仍可能走 R → A。要讨论完整双向中间路径,必须把两端映射与转发条件一起说明。

10.5 自测四:看到两个网关 MAC 就能断言攻击吗

题目: 一段观察中,同一个网关 IP 先后对应两个不同 MAC,业务在切换后恢复。是否可以直接认定 ARP 欺骗?

答案: 不能。还要排查合法设备更换、冗余切换、地址冲突以及观察位置的影响,再结合可信配置和交换机端口信息判断。变化是核查入口,不是完整结论。


写在最后

ARP 把一个看起来抽象的"下一跳 IP",落实成网卡真正能够使用的链路层地址。正常时,它完成广播询问、应答学习和后续封装;错误绑定被接受时,同一套发送逻辑又可能把数据交给错误的设备。

读懂 ARP,可以始终追问四句话:当前问的是谁,谁声称自己代表它,系统最终采用了哪条映射,这一帧因此交到了哪里。 把路由、邻居表、以太网封装和转发条件放在一起,既能解释正常通信,也能更准确地定位异常。

愿我们不只记住"ARP 请求广播、应答单播",还能在真正看到一份报文时,把它每一个地址字段的来由讲清楚。

相关推荐
坚定的共产主义生产设备永不宕机1 小时前
服务器从裸机开始搭建ESXI虚拟机
linux·服务器·网络
刚入门的大一新生1 小时前
Linux-线程封装
linux·运维·服务器
god_cvz1 小时前
实践:Linux环境构建CTB工具(cesium-terrain-builder)
linux·gis·ctb
Android系统攻城狮2 小时前
Linux FFmpeg深度解析之av_dump_format调用流程与实战(十一)
linux·运维·服务器·ffmpeg·1024程序员节·音视频进阶
2301_780789662 小时前
智能汽车的“软肋”:车载系统漏洞分析与安全防护框架初探
网络·安全·车载系统·汽车·ddos
Android系统攻城狮2 小时前
Linux FFmpeg深度解析之avcodec_find_decoder调用流程与实战(十二)
linux·运维·服务器·ffmpeg·1024程序员节·音视频进阶
小白的码BUG之路2 小时前
K8s -- 启动一主二从节点
linux·运维·服务器
Escalating_xu2 小时前
【Linux 网络编程】IP 分片与重组:3000 字节的数据报,怎样拆开又拼回来?
linux·网络·tcp/ip
小相会自律2 小时前
全平台远程控制软件推荐2026:Win/Mac/Linux/手机/鸿蒙互通哪款好
linux·macos·智能手机·todesk·teamviewer·向日葵