交换机的 MAC 表不是配出来的,是「听源地址」学出来的。 查不到目的就泛洪,查到就单播,目的口等于源口就丢弃。
3.2.3 说交换机「每口独立」,这一节看它凭什么知道「该往哪个口送」。 本机 ARP 表里的真实 MAC 就是这张表的原料。
一、三个动作:学、查、转发
每个进来的帧都要过这三步:
| 步骤 | 看哪个字段 | 动作 |
|---|---|---|
| 学 | 源 MAC | 记下「这个 MAC 在哪个口」 |
| 查 | 目的 MAC | 查表决定往哪送 |
| 转发 | — | 查到单播、查不到泛洪、同口丢弃 |
第一步「学源不学目的」是最反直觉的地方:交换机不关心「你想发给谁」,只关心「你从哪来」。 因为「从哪来」是确定的,「发给谁」可能查不到。 所以 MAC 表是「被动积累」出来的,不需要任何协议。 第三步那个「同口丢弃」叫过滤:如果目的口就是源口,说明收方和发方在同一侧, 再送回去就是重复。 这个动作不产生任何线路流量,所以「同网段两台机器互传」走的是本机回环。
二、泛洪只发生在开头
表是空的,所以头几个帧只能泛洪:
| 阶段 | 动作 | 代价 |
|---|---|---|
| 开头几帧 | 往所有别的口各送一份 | 占用多个口的带宽 |
| 表建好之后 | 只送一个口 | 正常 |
| 表项老化后 | 重新泛洪一次 | 周期性开销 |
第一行的代价是「复制」:一个帧泛洪到 23 个口,就是 23 份带宽。 所以「广播风暴」之所以可怕,就是因为它把这个代价乘上了「环路」——下一节会算这笔账。 第三行的「老化」是必需的:设备换了网卡、换了位置,旧表项必须失效。 典型老化时间是 300 秒,所以「拔了网线插到另一个口」之后,头几帧会泛洪一次。
三、这张表带来的安全缺口
MAC 表是「谁说什么就信什么」:
| 攻击 | 怎么做 | 后果 | |---|---| | MAC 泛洪 | 灌入大量假源 MAC | 表被填满,全部退化成泛洪 | | MAC 欺骗 | 伪造别人的源 MAC | 流量被引到攻击者口上 |
第一行是「把交换机变成集线器」:交换机的表有容量上限(通常几千条), 灌满之后新帧就查不到,只能泛洪。 于是「独占」变成了「共享」,攻击者就能旁听别人的流量。 第二行更直接:交换机只认「源 MAC 在哪个口」,不验证这个 MAC 是不是你的。 所以伪造一下,之后发给那个 MAC 的帧就全送到你这里。 防御办法是「端口安全」:一个口只允许固定几个 MAC,多了就关端口。 这两条和上一章讲的 ARP 欺骗是同一个层面的问题:二层没有「身份」这个概念, 所以任何「基于二层的信任」都是可伪造的。
四、动手 Demo:交换机怎么学 MAC 表——泛洪几次就够
6 台主机接在 6 个口上,按固定顺序发 12 个帧。 每发一个帧,先看表里有没有目的 MAC,再决定泛洪还是单播。
hosts = [(1, 'AA-00-00-00-00-01'), (2, 'AA-00-00-00-00-02'),
(3, 'AA-00-00-00-00-03'), (4, 'AA-00-00-00-00-04'),
(5, 'AA-00-00-00-00-05'), (6, 'AA-00-00-00-00-06')]
pmac = dict((p, m) for p, m in hosts)
seq = [(1, 2), (2, 1), (3, 4), (1, 3), (5, 6), (4, 1),
(2, 5), (6, 3), (1, 4), (3, 5), (2, 6), (5, 1)]
tbl, flood, uni, filt = {}, 0, 0, 0
for i, (src, dst) in enumerate(seq, 1):
dkey = pmac[dst]
if dkey not in tbl:
flood += 1
act = '泛洪'
elif tbl[dkey] == src:
filt += 1
act = '丢弃(同口)'
else:
uni += 1
act = '单播 -> 口 %d' % tbl[dkey]
tbl[pmac[src]] = src
print('%2d 帧 %d -> %d %-14s 表项 %d' % (i, src, dst, act, len(tbl)))
实跑输出:
--------------------------------------------------------------------
交换机怎么学 MAC 表——泛洪几次就够
--------------------------------------------------------------------
**交换机不像集线器那样无脑复制,它有一张「MAC → 端口」的表。**
**这张表不是配出来的,是自己听出来的**:每个进来的帧都记下「这个源 MAC 在哪个口」。
下面用 6 台主机、12 个帧跑一遍。
帧号 源口 → 目的口 动作 表项数
------------------------------------------------
1 1 → 2 泛洪 1
2 2 → 1 单播→口 1 2
3 3 → 4 泛洪 3
4 1 → 3 单播→口 3 3
5 5 → 6 泛洪 4
6 4 → 1 单播→口 1 5
7 2 → 5 单播→口 5 5
8 6 → 3 单播→口 3 6
9 1 → 4 单播→口 4 6
10 3 → 5 单播→口 5 6
11 2 → 6 单播→口 6 6
12 5 → 1 单播→口 1 6
**合计:泛洪 3 次、单播 9 次、丢弃 0 次。**
**泛洪只发生在第 1、3、5 帧**:
**这三帧的目的主机还没「说过话」,交换机没学到它的端口,只能往所有别的口都送一份。**
**注意「所有别的口」不包括来的那个口**——
**这是交换机和集线器最本质的差别:它知道「从哪来的不用再送回去」。**
**第 5 帧之后泛洪彻底停了**:**6 台主机的 MAC 全被学到,剩下 9 帧全是单播,直接查表转发。**
**所以「交换机刚开始会慢一下」是真的,但代价只有头几个帧。**
**第 3 列那个「丢弃(同口)」这次一次都没出现**:**因为上面这 12 帧全是「跨口通信」。**
**如果换成同一台机器上两个程序互发(源口等于目的口),交换机会直接丢掉**,
**因为那一份本机已经收到了,再发回去就是重复。**
**这个动作叫「过滤」,它不产生任何线路流量。**
泛洪只发生在第 1、3、5 帧:这三帧的目的主机还没「说过话」,交换机没学到它的端口。 注意「所有别的口」不包括来的那个口——这是交换机和集线器最本质的差别。 第 5 帧之后泛洪彻底停了:6 台主机的 MAC 全被学到,剩下 9 帧全是单播,直接查表转发。 所以「交换机刚开始会慢一下」是真的,但代价只有头几个帧。 表里始终没有「丢弃」:因为上面这 12 帧全是跨口通信。 如果换成同一台机器上两个程序互发(源口等于目的口),交换机会直接丢掉—— 那一份本机已经收到了,再发回去就是重复。 这个动作叫「过滤」,它不产生任何线路流量。
30 秒实验
- 把
seq改成[(1, 1)]打头,看会不会出现「丢弃(同口)」。 - 在末尾加一帧
(6, 2),看表项数还是不是 6。 - 想一下:如果表项老化时间是 300 秒,一个「每 5 分钟发一次」的设备会怎样?
常见误区
- 以为交换机「学了目的地址」。 它只学源地址,目的地址只用来查表。
- 以为泛洪会一直持续。 表建好就停了,只有新设备出现或表项老化时才泛洪。
- 以为「同口转发」无害。 它会重复,所以交换机直接丢弃(叫过滤)。
自测三问(点开对答案)
1. 交换机的 MAC 表是怎么来的? —— **从每个进来的帧里读源 MAC,记下它来自哪个口。**不需要任何协议,是「听」出来的。 2. 目的 MAC 查不到时怎么办? —— **泛洪**:往除了来源口之外的所有口各送一份。 3. 为什么伪造源 MAC 能接到别人的流量? —— **交换机只认「源 MAC 在哪个口」,不验证身份。**伪造之后, 发给那个 MAC 的帧就全送到攻击者的口上。下一节看:如果拓扑里有环,这张表会带来什么灾难。
- ☐ (本节完,下一节 3.2.5 环路检测技术)