聚合可以用一个覆盖前缀,替代若干更具体的路由通告。只有更具体前缀确实受到抑制、且其他对等体没有继续提供它们时,下游表项才会减少。成员不必填满整个汇总范围:几个稀疏子网也能形成一个较大的覆盖块,但该通告同时会吸引发往空洞地址的流量。因此,聚合既是规模问题,也是可达性和地址授权问题。

聚合究竟减少了什么

对等体同时收到三个成员前缀和一条汇总时,仍然持有四条路由。FRR 的 aggregate-address 10.1.0.0/16 summary-only 抑制被覆盖的更具体前缀通告,并不会把它们从聚合路由器自己的 BGP 表删除。生成汇总的前提是 BGP 表中至少有一个符合条件的更具体前缀,仅在其他路由表中存在一条记录还不够。

稳定的汇总可以把部分成员变化挡在下游之外,但这个收益有前提。最后一个成员消失、汇总属性改变,或者出口策略变化,都可能继续产生更新。抑制通告不会修复客户链路,只会改变其他网络获得的故障信息。聚合者仍然需要监控内部成员,不能因为上游只看到一条稳定路由,就认为内部全部可达。

汇总覆盖的地址通常也比列出来的几个子网更多。宣告之前,应检查整个范围的使用授权以及预期的汇总起源 AS。地址对齐、数值相邻、能节省表项,都不是拥有宣告权限的证据。提供商可以在自己的地址分配范围内合法汇总客户网络;成员来自不同起源 AS,并不自动意味着这次聚合非法,关键是整体授权和出口契约。

保留清晰的起源

FRR 的 suppress-map 用路由映射选择哪些更具体前缀受到抑制。as-set 处理的是另一个维度,即怎样表示成员路径中的 AS 信息。AS_SET 是无序集合,并非只包含客户起源号码的清单。历史上的 RFC 4271 在计算 AS 路径长度时,将整个集合计作一个,不会按集合成员数量逐个增加长度。

RFC 9774 已经废弃 AS_SET 与 AS_CONFED_SET。除非运营者为了过渡显式选择其他行为,发言者不得继续通告这些集合,收到含集合的路由时应按撤回处理。应选择明确、获得授权的汇总起源,并配置相应 ROA,不能把 AS_SET 当作常规修补办法。邻居的起源过滤器也不会因为路由器生成了一条汇总,就自动接受这个范围。

坏掉的样子

在选定转发表中的普通目的地址查找,使用最长的已安装匹配前缀。因此,有效的 /24 会覆盖其上方的 /16,不论两者的 BGP 本地优先级谁更高。不过,路由首先必须通过导入策略、下一跳解析和安装过程。BGP 为同一前缀选择候选路径,与转发时在不同前缀之间做匹配,是两个不同阶段。

一个成员消失时,只有失去可用更具体路由的目的地才回落到汇总,健康成员仍可继续转发。某个客户撤回路由,不会天然让汇总覆盖的所有地址同时黑洞。在生成汇总的路由器上,对没有成员路由匹配的地址设置丢弃路径,可以防止数据包顺着默认路由返回上游,进而形成转发环路。

丢弃时是否生成诊断报文,取决于设备和配置,不能保证必然静默,也不能保证 ICMP 一定送达。撤回汇总也不必然等于没有路由,因为另一个覆盖前缀或默认路由可能仍然存在。应该检查真实转发表和相关计数器,而不是只因为看不到某个更具体前缀,就断言汇总本身配置错误。

快速验证

将下面代码交给 Python 3 运行,保持断言开启。它不创建套接字,也不修改路由器配置。两个客户路由位于同一条覆盖丢弃路由之下;这个刻意缩小的模型只隔离最长前缀匹配和成员撤回的影响范围,不实现 BGP 会话、策略交换或者真实收敛过程。

from ipaddress import ip_address, ip_network

routes = {
    ip_network("10.1.0.0/16"): "discard",
    ip_network("10.1.4.0/24"): "customer-a",
    ip_network("10.1.7.0/24"): "customer-b",
}
def lookup(address):
    matches = [prefix for prefix in routes if ip_address(address) in prefix]
    if not matches:
        return "no-route"
    return routes[max(matches, key=lambda prefix: prefix.prefixlen)]

assert lookup("10.1.4.9") == "customer-a"
assert lookup("10.1.7.9") == "customer-b"
del routes[ip_network("10.1.4.0/24")]
assert lookup("10.1.4.9") == "discard"
assert lookup("10.1.7.9") == "customer-b"
print("withdraw A: A=discard, B=customer-b")
del routes[ip_network("10.1.0.0/16")]
assert lookup("10.1.4.9") == "no-route"
assert lookup("10.1.7.9") == "customer-b"
print("withdraw summary: A=no-route, B=customer-b")

第一行输出表示客户甲回落到丢弃路由,而客户乙仍然命中自己的表项。删除汇总之后,甲变成没有路由,这是因为夹具刻意没有放入默认路由;乙仍独立可达。可以把增加默认表项作为后续练习,观察无路由结果如何变化,避免把这个特定模型的结果误写成所有网络都会遵循的行为。

在获得授权的实验路由器上,还应分别检查收到、选中、发出以及实际安装的路由,再撤回一个成员进行对照。确认汇总是否保留、属性是否改变,以及目的地址最终命中哪条路由。真实路由器集成与转发效果需要那一步实验;这里的 Python 断言仅证明所声明的查找模型,不能代替硬件转发表或协议实现验证。

来源