互联网的路由系统建立在信任之上: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,AS64501(上游 provider) - 邬居
10.0.2.1,AS64502(另一个上游 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 同时启用,形成双重防线