<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>聚合 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/%E8%81%9A%E5%90%88/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/%E8%81%9A%E5%90%88/index.xml" rel="self" type="application/rss+xml"/><item><title>BGP 聚合收窄路由表</title><link>https://www.xbcatm.com/zh/posts/bgp-aggregation-shrinks-the-table/</link><pubDate>Fri, 08 Oct 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/bgp-aggregation-shrinks-the-table/</guid><description>抑制更具体前缀后，聚合可以减少下游表项，但覆盖路由仍需要明确授权、起源策略，以及成员不可达时的处理。</description><content:encoded>&lt;p&gt;聚合可以用一个覆盖前缀，替代若干更具体的路由通告。只有更具体前缀确实受到抑制、且其他对等体没有继续提供它们时，下游表项才会减少。成员不必填满整个汇总范围：几个稀疏子网也能形成一个较大的覆盖块，但该通告同时会吸引发往空洞地址的流量。因此，聚合既是规模问题，也是可达性和地址授权问题。&lt;/p&gt;
&lt;h2 id="聚合究竟减少了什么"&gt;聚合究竟减少了什么&lt;/h2&gt;
&lt;p&gt;对等体同时收到三个成员前缀和一条汇总时，仍然持有四条路由。FRR 的 &lt;code&gt;aggregate-address 10.1.0.0/16 summary-only&lt;/code&gt; 抑制被覆盖的更具体前缀通告，并不会把它们从聚合路由器自己的 BGP 表删除。生成汇总的前提是 BGP 表中至少有一个符合条件的更具体前缀，仅在其他路由表中存在一条记录还不够。&lt;/p&gt;
&lt;p&gt;稳定的汇总可以把部分成员变化挡在下游之外，但这个收益有前提。最后一个成员消失、汇总属性改变，或者出口策略变化，都可能继续产生更新。抑制通告不会修复客户链路，只会改变其他网络获得的故障信息。聚合者仍然需要监控内部成员，不能因为上游只看到一条稳定路由，就认为内部全部可达。&lt;/p&gt;
&lt;p&gt;汇总覆盖的地址通常也比列出来的几个子网更多。宣告之前，应检查整个范围的使用授权以及预期的汇总起源 AS。地址对齐、数值相邻、能节省表项，都不是拥有宣告权限的证据。提供商可以在自己的地址分配范围内合法汇总客户网络；成员来自不同起源 AS，并不自动意味着这次聚合非法，关键是整体授权和出口契约。&lt;/p&gt;
&lt;h2 id="保留清晰的起源"&gt;保留清晰的起源&lt;/h2&gt;
&lt;p&gt;FRR 的 &lt;code&gt;suppress-map&lt;/code&gt; 用路由映射选择哪些更具体前缀受到抑制。&lt;code&gt;as-set&lt;/code&gt; 处理的是另一个维度，即怎样表示成员路径中的 AS 信息。AS_SET 是无序集合，并非只包含客户起源号码的清单。历史上的 RFC 4271 在计算 AS 路径长度时，将整个集合计作一个，不会按集合成员数量逐个增加长度。&lt;/p&gt;
&lt;p&gt;RFC 9774 已经废弃 AS_SET 与 AS_CONFED_SET。除非运营者为了过渡显式选择其他行为，发言者不得继续通告这些集合，收到含集合的路由时应按撤回处理。应选择明确、获得授权的汇总起源，并配置相应 ROA，不能把 AS_SET 当作常规修补办法。邻居的起源过滤器也不会因为路由器生成了一条汇总，就自动接受这个范围。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;在选定转发表中的普通目的地址查找，使用最长的已安装匹配前缀。因此，有效的 &lt;code&gt;/24&lt;/code&gt; 会覆盖其上方的 &lt;code&gt;/16&lt;/code&gt;，不论两者的 BGP 本地优先级谁更高。不过，路由首先必须通过导入策略、下一跳解析和安装过程。BGP 为同一前缀选择候选路径，与转发时在不同前缀之间做匹配，是两个不同阶段。&lt;/p&gt;
&lt;p&gt;一个成员消失时，只有失去可用更具体路由的目的地才回落到汇总，健康成员仍可继续转发。某个客户撤回路由，不会天然让汇总覆盖的所有地址同时黑洞。在生成汇总的路由器上，对没有成员路由匹配的地址设置丢弃路径，可以防止数据包顺着默认路由返回上游，进而形成转发环路。&lt;/p&gt;
&lt;p&gt;丢弃时是否生成诊断报文，取决于设备和配置，不能保证必然静默，也不能保证 ICMP 一定送达。撤回汇总也不必然等于没有路由，因为另一个覆盖前缀或默认路由可能仍然存在。应该检查真实转发表和相关计数器，而不是只因为看不到某个更具体前缀，就断言汇总本身配置错误。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;将下面代码交给 Python 3 运行，保持断言开启。它不创建套接字，也不修改路由器配置。两个客户路由位于同一条覆盖丢弃路由之下；这个刻意缩小的模型只隔离最长前缀匹配和成员撤回的影响范围，不实现 BGP 会话、策略交换或者真实收敛过程。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;ipaddress&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ip_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_network&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ip_network&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.0.0/16&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;discard&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ip_network&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.4.0/24&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;customer-a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ip_network&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.7.0/24&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;customer-b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;no-route&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefixlen&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.4.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;customer-a&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.7.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;customer-b&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ip_network&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.4.0/24&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.4.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;discard&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.7.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;customer-b&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;withdraw A: A=discard, B=customer-b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ip_network&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.0.0/16&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.4.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;no-route&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.1.7.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;customer-b&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;withdraw summary: A=no-route, B=customer-b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第一行输出表示客户甲回落到丢弃路由，而客户乙仍然命中自己的表项。删除汇总之后，甲变成没有路由，这是因为夹具刻意没有放入默认路由；乙仍独立可达。可以把增加默认表项作为后续练习，观察无路由结果如何变化，避免把这个特定模型的结果误写成所有网络都会遵循的行为。&lt;/p&gt;
&lt;p&gt;在获得授权的实验路由器上，还应分别检查收到、选中、发出以及实际安装的路由，再撤回一个成员进行对照。确认汇总是否保留、属性是否改变，以及目的地址最终命中哪条路由。真实路由器集成与转发效果需要那一步实验；这里的 Python 断言仅证明所声明的查找模型，不能代替硬件转发表或协议实现验证。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc4271.html"&gt;RFC 4271 BGP-4&lt;/a&gt;，定义 ATOMIC_AGGREGATE、AGGREGATOR 与 AS_SET，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.frrouting.org/en/latest/bgp.html"&gt;FRR BGP 路由聚合&lt;/a&gt;，说明聚合生成、成员抑制与 AS-set 选项，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc4632.html"&gt;RFC 4632 CIDR&lt;/a&gt;，无类前缀与最长匹配转发模型，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9774.html"&gt;RFC 9774：废弃 AS_SET&lt;/a&gt;，更新的聚合与起源要求，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>