2. SR-MPLS 数据平面与标签栈转发

SR-MPLS 的数据平面并不引入新的报文封装格式,它复用了 MPLS 的标签转发机制,把“路径信息”编码进标签栈里。理解 SR-MPLS 的转发行为,核心是理解三件事:SID 如何编码成标签、标签栈如何被逐跳操作、以及 PHP 与显式空标签如何影响最后一跳。本节围绕这三条主线展开,并结合一个可复现的示例,说明报文在 SR-MPLS 域内逐跳转发的完整过程。

SID 编码与标签值

在 SR-MPLS 中,一个 SID 被编码为一个 32 位标签值,其结构与 MPLS 标签一致:高 20 位是标签值(Label Value),随后 3 位是 TC(Traffic Class,原 EXP),1 位是 S 标志(Bottom of Stack),低 8 位是 TTL。SR-MPLS 的 SID 通常落在 16000~23999 的标签区间(具体范围由设备实现决定),这个区间被称为 SR 专用标签空间,与 LDP、RSVP-TE 等传统 MPLS 标签空间区分开,避免冲突。

SID 分为两类。全局 SID(Global SID)在整个 SR 域内唯一,通常由 IGP(IS-IS 或 OSPF)扩展通告,常见形式是前缀 SID(Prefix-SID),它绑定到某个节点的 Loopback 地址,代表“到达该节点”。本地 SID(Local SID)只在本地节点有效,由节点自行分配,常见形式是邻接 SID(Adjacency-SID),它绑定到某条具体链路,代表“从这条链路发出”。全局 SID 用于构建端到端路径,本地 SID 用于强制指定某一段链路,两者组合可以表达任意显式路径。

需要特别区分“SID”和“转发表项”。SID 是控制平面通告的标识,节点收到 SID 后,会在本地生成对应的转发表项(Incoming Label Map,ILM),把入标签映射到出标签和出接口。转发时真正查的是这张表,而不是 SID 本身。

标签栈操作:Push、Swap、Pop、Continue

SR-MPLS 的转发动作可以归纳为四类,它们决定了报文在每一跳的标签栈变化:

  • Push(压栈):在报文头部压入一个或多个标签。入口 PE 通常执行 Push,把整条路径的 SID 列表按顺序压入,栈顶是第一个要经过的节点。
  • Swap(交换):把栈顶标签替换为另一个标签。传统 MPLS 中常见,但在 SR-MPLS 中,如果使用全局 SID,中间节点通常不需要 Swap,直接按栈顶标签转发即可。
  • Pop(弹栈):弹出栈顶标签。当栈顶 SID 表示“到达本节点”时,节点弹出该标签,继续按新的栈顶转发。
  • Continue(继续):不改变标签栈,仅按栈顶标签查表转发。这是 SR-MPLS 中间节点最常见的动作。

这四类动作的组合,使得 SR-MPLS 可以在不依赖每跳信令的情况下,让报文沿着标签栈中预先编排的 SID 序列前进。栈顶 SID 决定当前跳,弹出后下一个 SID 成为新的栈顶,如此逐跳推进。

PHP 与显式空标签

PHP(Penultimate Hop Popping,倒数第二跳弹出)是 MPLS 的经典优化:倒数第二跳节点在把报文发给最后一跳之前,提前弹出栈顶标签,使最后一跳节点直接按 IP 或内层标签转发,减轻其负担。在 SR-MPLS 中,PHP 依然存在,但需要与 SID 语义配合。

当某个前缀 SID 被通告为“显式空标签”(Explicit NULL,标签值 0)时,表示该 SID 对应的节点希望自己执行弹出动作,而不是让上一跳提前弹出。此时倒数第二跳不会弹出标签,而是把标签 0 压入并转发;最后一跳收到标签 0 后,弹出标签并按内层继续处理。显式空标签的价值在于:它保留了最后一跳对 SID 的可见性,便于最后一跳做基于 SID 的统计、策略或保护,代价是多消耗一个标签栈深度。

与之相对的是“隐式空标签”(Implicit NULL,标签值 3)。当节点通告隐式空标签时,倒数第二跳执行 PHP,直接把标签弹出,最后一跳收到的是无标签报文。隐式空标签节省栈深,但最后一跳无法感知该 SID。工程上选择哪种,取决于是否需要最后一跳做 SID 级处理。

SR-MPLS 逐跳转发与标签栈变化(示例:PE1 → P1 → P2 → PE2) PE1 入口 · Push P1 Continue P2 PHP / 显式空 PE2 Pop · 出栈 标签栈(栈顶在上) 16002 (PE2) 16001 (P1) 16003 (P2) P1 收到 16002 (PE2) 16001 (P1) 16003 (P2) 栈顶=16001,命中本节点,Pop P2 收到 16002 (PE2) 16003 (P2) 栈顶=16003,命中本节点 PHP:弹出 16003,转发 16002 PE2 收到 16002 (PE2) 栈顶=16002,命中本节点 Pop,按内层 IP 转发 PHP 与显式空标签的差异(以 P2 → PE2 为例) 隐式空标签(Implicit NULL,标签 3) P2 执行 PHP:弹出 16002,PE2 收到无标签报文 优点:节省栈深;缺点:PE2 无法感知 SID 16002 显式空标签(Explicit NULL,标签 0) P2 不弹出,压入标签 0 后转发 PE2 收到标签 0,弹出后按内层处理,可感知 SID

逐跳转发示例分析

假设一个 SR-MPLS 域内有四台设备:PE1、P1、P2、PE2。PE1 的 Loopback 为 1.1.1.1/32,P1 为 2.2.2.2/32,P2 为 3.3.3.3/32,PE2 为 4.4.4.4/32。IGP 为每个 Loopback 分配前缀 SID:PE1=16001,P1=16002,P2=16003,PE2=16004。现在 PE1 要向 PE2 发送报文,并希望路径为 PE1 → P1 → P2 → PE2。

PE1 作为入口,执行 Push,把路径 SID 列表按“从远到近”的顺序压栈,栈顶是第一个要经过的节点。因此栈内容自顶向下为:16002(P1)、16003(P2)、16004(PE2)。报文发出后:

  1. P1 收到报文,查看栈顶标签 16002。该标签是 P1 自己的前缀 SID,表示“到达 P1”。P1 执行 Pop,弹出 16002,此时栈顶变为 16003。
  2. P1 按新的栈顶 16003 查转发表,发现下一跳是 P2,执行 Continue,把报文转发给 P2。
  3. P2 收到报文,栈顶为 16003,命中自己的前缀 SID,执行 Pop,栈顶变为 16004。
  4. P2 按栈顶 16004 查表,下一跳是 PE2。此时若 PE2 通告的是隐式空标签,P2 执行 PHP,弹出 16004,把无标签报文发给 PE2;若 PE2 通告的是显式空标签,P2 不弹出,而是压入标签 0 后转发。
  5. PE2 收到报文:隐式空场景下直接按内层 IP 转发;显式空场景下先弹出标签 0,再按内层 IP 转发。

这个过程中,P1 和 P2 都没有执行 Swap,只做了 Pop 和 Continue,这正是 SR-MPLS 使用全局 SID 时的典型行为:中间节点不需要维护每条路径的标签映射,只需要认识自己的 SID 即可。转发表规模从“路径数量级”降到“节点数量级”,这是 SR-MPLS 可扩展性的关键来源。

判断栈顶 SID 是否命中本节点:节点收到报文后,用栈顶标签查 ILM 表。若表项类型为“本地 SID”,说明该 SID 属于本节点,执行 Pop 或按本地 SID 指令处理;若表项类型为“转发”,说明该 SID 属于其他节点,执行 Continue 或 Swap。这个判断是理解 SR-MPLS 转发行为的关键。

工程要点与常见误区

第一,SID 与标签值不是一一对应的固定关系,不同厂商、不同域可能使用不同的标签区间,工程上要以实际通告为准。第二,全局 SID 的唯一性由 IGP 保证,若两个节点通告了相同的前缀 SID,会导致转发冲突,必须通过规划或冲突检测避免。第三,PHP 与显式空标签的选择会影响最后一跳的可观测性,若需要在 PE2 上做基于 SID 的流量统计或策略,应使用显式空标签。第四,标签栈深度受设备能力和 MTU 限制,路径越长栈越深,规划时需要评估。

掌握以上内容后,读者应能根据给定的 SID 列表,推导出报文在每一跳的标签栈变化和转发动作,并能判断 PHP 与显式空标签对最后一跳行为的影响。这是分析 SR-MPLS 数据平面问题的基础能力。