📚 第三章 数据链路
📄 一句话概括

交换机的 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 秒实验

  1. 把 seq 改成 [(1, 1)] 打头,看会不会出现「丢弃(同口)」。
  2. 在末尾加一帧 (6, 2),看表项数还是不是 6。
  3. 想一下:如果表项老化时间是 300 秒,一个「每 5 分钟发一次」的设备会怎样?

常见误区

  1. 以为交换机「学了目的地址」。 它只学源地址,目的地址只用来查表。
  2. 以为泛洪会一直持续。 表建好就停了,只有新设备出现或表项老化时才泛洪。
  3. 以为「同口转发」无害。 它会重复,所以交换机直接丢弃(叫过滤)。
自测三问(点开对答案) 1. 交换机的 MAC 表是怎么来的? —— **从每个进来的帧里读源 MAC,记下它来自哪个口。**不需要任何协议,是「听」出来的。 2. 目的 MAC 查不到时怎么办? —— **泛洪**:往除了来源口之外的所有口各送一份。 3. 为什么伪造源 MAC 能接到别人的流量? —— **交换机只认「源 MAC 在哪个口」,不验证身份。**伪造之后, 发给那个 MAC 的帧就全送到攻击者的口上。

下一节看:如果拓扑里有环,这张表会带来什么灾难。

相关笔记