BGP AS_PATH 第一跳验证——比 RPKI 更基础的防线

2026-06-04 14 预计阅读时间:1 分钟
来源:blog.cloudflare.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:10 分钟

互联网的路由系统建立在信任之上:BGP 默认相信邻居通告的一切。这种信任带来了两类常见攻击——路由劫持(hijack)和路径泄漏(path leak)。RPKI 通过验证前缀的起源 AS,堵住了部分漏洞,但它无法判断 AS_PATH 中间跳是否被伪造。一个更简单、更古老的机制填补了这个缺口:First AS Enforcement——检查收到的路由中,AS_PATH 的第一个 AS 是否等于直连邻居的 AS 号。

路径伪造:RPKI 管不到的盲区

RPKI 的核心逻辑是:AS 1 如果持有 ROA,就有权宣告 203.0.113.0/24。当 AS 999 试图劫持这个前缀时,RPKI 会将其标记为 Invalid。但如果攻击者稍微聪明一点——在 AS_PATH 前面插入合法 AS:

AS_PATH: 1 999 65000

从 RPKI 角度看,起源 AS 是 1,与 ROA 一致,路由是 Valid。然而你实际从 AS 65000 收到这条路由,AS_PATH 的第一跳却是 AS 1——这不可能发生,因为 AS 1 不是你的直连邻居。攻击者通过在路径头部插入合法 AS,绕过了 RPKI 的起源验证。

这就是 First AS Enforcement 要解决的问题:一条从邻居 AS X 收到的路由,其 AS_PATH 的第一个 AS 必须是 X。如果不匹配,要么是配置错误,要么是恶意伪造。

First AS Enforcement 的工作原理

规则本身极其简单:

对于从 eBGP 邻居 AS N 收到的 UPDATE,如果 AS_PATH 非空,则 AS_PATH 的最左侧 AS 号必须等于 N。否则拒绝该路由。

这条规则不需要任何外部基础设施——不依赖 ROA 数据库、不依赖第三方验证服务,纯粹是本地路由器的逻辑判断。它的覆盖范围包括:

  • 伪造路径头部:攻击者插入不属于自己的 AS 到 AS_PATH 开头。
  • 路径泄漏的某些形态:一个多宿 AS 从 provider A 学到路由后,向 provider B 通告时,AS_PATH 的第一跳可能不是 provider B 的 AS。
  • 配置失误:邻居误将内部路由以错误的 AS_PATH 向外通告。

但也有局限:它只检查第一跳,无法发现 AS_PATH 中间段的篡改;对于 AS_PATH 为空的情况(邻居宣告自己的路由),规则自然放行。

在 FRR 上实现 First AS 验证

Free Range Routing(FRR)是 Linux 上最常用的 BGP 实现,下面是一个完整的配置示例,展示如何通过路由策略强制执行 First AS 检查。

基本拓扑

假设你的路由器 AS 号为 64512,有两个 eBGP 邻居:

  • 邻居 10.0.1.1,AS 64501(上游 provider)
  • 邬居 10.0.2.1,AS 64502(另一个上游 provider)

FRR 配置

# /etc/frr/frr.conf

router bgp 64512
 bgp router-id 192.168.1.1

 ! 邻居 AS 64501
 neighbor 10.0.1.1 ebgp-multihop 2
 neighbor 10.0.1.1 remote-as 64501
 neighbor 10.0.1.1 soft-reconfiguration inbound

 ! 邻居 AS 64502
 neighbor 10.0.2.1 ebgp-multihop 2
 neighbor 10.0.2.1 remote-as 64502
 neighbor 10.0.2.1 soft-reconfiguration inbound

 ! 对每个邻居应用 First AS 策略
 neighbor 10.0.1.1 route-map FIRST_AS_64501 in
 neighbor 10.0.2.1 route-map FIRST_AS_64502 in
!

! 验证从 AS 64501 收到的路由,第一跳必须是 64501
route-map FIRST_AS_64501 permit 10
 match as-path ^64501_
!
route-map FIRST_AS_64501 deny 20
!

! 验证从 AS 64502 收到的路由,第一跳必须是 64502
route-map FIRST_AS_64502 permit 10
 match as-path ^64502_
!
route-map FIRST_AS_64502 deny 20
!

关键点:

  • ^64501_ 是正则表达式,^ 匹配 AS_PATH 的起始位置,64501 是期望的 AS 号,_ 匹配 AS 边界(空格或路径段分隔符)。
  • permit 10 放行合法路由,deny 20 拒绝所有不匹配的路由。
  • 每个 eBGP 邻居需要独立的 route-map,因为期望的第一跳 AS 号不同。

验证配置生效

配置加载后,用以下命令检查路由过滤效果:

# 查看从特定邻居收到的所有路由(包括被过滤的)
vtysh -c "show bgp neighbor 10.0.1.1 received-routes"

# 查看实际被接受的路由
vtysh -c "show bgp neighbor 10.0.1.1 accepted-routes"

# 对比:如果 received-routes 中有路由的 AS_PATH
# 第一跳不是 64501,但 accepted-routes 中没有它,
# 说明 First AS 策略正在生效

用 Python 批量检查 AS_PATH 合规性

如果你从多个邻居收集了 BGP 路由表,可以用脚本批量验证 First AS 规则:

"""
检查 BGP 路由表中每条路由的 AS_PATH 第一跳
是否与预期的邻居 AS 一致。

输入格式:每行包含 "邻居AS 前缀 AS_PATH"
AS_PATH 用空格分隔的 AS 号序列

示例输入:
64501 203.0.113.0/24 64501 64510 64520
64502 198.51.100.0/24 64501 64502 64530
"""

import sys

def check_first_as(line: str) -> dict:
    parts = line.strip().split()
    if len(parts) < 3:
        return {"valid": False, "error": "格式不完整"}

    neighbor_as = parts[0]
    prefix = parts[1]
    as_path = parts[2:]

    first_as = as_path[0]
    valid = (first_as == neighbor_as)

    return {
        "prefix": prefix,
        "neighbor_as": neighbor_as,
        "first_as": first_as,
        "as_path": as_path,
        "valid": valid,
    }

def main():
    violations = 0
    for line in sys.stdin:
        if not line.strip():
            continue
        result = check_first_as(line)
        status = "✓" if result["valid"] else "✗ VIOLATION"
        if not result["valid"]:
            violations += 1
        print(f"{status}  {result['prefix']}  "
              f"neighbor={result['neighbor_as']}  "
              f"first_as={result['first_as']}  "
              f"path={'>'.join(result['as_path'])}")

    print(f"\n总计违规路由: {violations}")

if __name__ == "__main__":
    main()

运行方式:

# 从 FRR 导出路由表并格式化
vtysh -c "show bgp ipv4 unicast summary json" | python3 extract_routes.py | python3 check_first_as.py

# 或直接手工输入测试
echo "64501 203.0.113.0/24 64501 64510 64520" | python3 check_first_as.py
echo "64502 198.51.100.0/24 64501 64502 64530" | python3 check_first_as.py

第二行会输出 ✗ VIOLATION——从 AS 64502 收到的路由,AS_PATH 第一跳却是 64501,这正是 First AS Enforcement 要拦截的情况。

与 RPKI 的互补关系

维度 RPKI Origin Validation First AS Enforcement
验证对象 前缀的起源 AS AS_PATH 的第一跳 AS
需要外部基础设施 是(ROA 数据库)
能否检测中间 AS 篡改
能否检测起源 AS 伪造 部分(仅当伪造 AS 出现在第一跳)
部署复杂度 中等

两者叠加使用效果最好:RPKI 拦截起源 AS 伪造,First AS Enforcement 拦截路径头部伪造。对于 AS_PATH 中间段的篡改,目前没有广泛部署的机制,BGPsec 是理论上的解决方案但实际采用率极低。

部署建议与注意事项

优先级:First AS Enforcement 的部署成本几乎为零——只需在每个 eBGP neighbor 上加一条 route-map。它应该成为 BGP 安全配置的基线,与 RPKI 并列,甚至更优先部署。

渐进式部署:如果担心误拦合法路由,可以先用 soft-reconfiguration inbound 收集数据,观察是否有路由被错误过滤,再切换到强制拒绝:

# 第一阶段:仅记录,不拒绝
route-map FIRST_AS_64501 permit 10
 match as-path ^64501_
!
route-map FIRST_AS_64501 permit 99
 set community 64512:9999  ! 标记违规路由,便于监控
!

# 第二阶段:确认无误拦后,改为拒绝
route-map FIRST_AS_64501 permit 10
 match as-path ^64501_
!
route-map FIRST_AS_64501 deny 20
!

iBGP 不适用:这条规则只适用于 eBGP 会话。iBGP 邻居之间传递的路由,AS_PATH 第一跳可能不是邻居的 AS(因为 iBGP 不修改 AS_PATH),强行套用会拦截几乎所有 iBGP 路由。

AS Confederation 场景:在联盟内部,子 AS 号出现在 AS_PATH 中但对外不可见。部署 First AS Enforcement 时需要考虑联盟边界的特殊性,避免在联盟内部接口上误用。

监控清单

  • ✅ 每个 eBGP 邻居配置独立的 First AS route-map
  • ✅ 正则表达式使用 ^AS_ 格式,确保只匹配路径起始
  • ✅ 部署前用 soft-reconfiguration inbound 观察过滤效果
  • ✅ 对被拒绝的路由建立日志和告警
  • ✅ 不在 iBGP 和联盟内部接口上应用此规则
  • ✅ 与 RPKI Origin Validation 同时启用,形成双重防线

相关推荐