外观
路由策略
BIRD 过滤器的实际语义:一条路由进来会依次经过哪些检查、被打上什么、什么情况下被拒。
策略分两层:community_filters.conf 是 DN42 社区通用逻辑(基本沿用上游 Bird-communities 写法),custom_filters.conf 是本 AS 的策略。渲染产物的目录布局见 渲染产物参考,可调字段见 BIRD 与路由 spec。
本文的代码片段取自生产节点 hkg1。
eBGP 会话的基座
所有外部会话都 from dnpeers 继承同一个模板:
template bgp dnpeers {
local as OWNAS;
prefer older on; # 属性等价时留住旧路由,而不是比 router ID
med metric on; # 跨 peer 比较 MED —— cold potato 选路的前提
enforce first as on; # 对端必须把自己的 ASN 放 AS_PATH 首位
advertise hostname on;
graceful restart off; # 重启不保留过期转发表
}两条值得注意:
med metric on是 cold potato 生效的前提。 BIRD 默认只在同一个邻居 AS 内部比较 MED;开了它才会跨 peer 比。下面prefer_cold_potato()调 MED 才有意义。enforce first as on挡掉对端把别人的 ASN 放首位的常见错误导出。
导入路径
一条 eBGP 路由从进来到被接受,顺序如下。任何一步 reject 即整条丢弃。
第一段:dn42_import_filter(通用层)
if net.type = NET_IP4 && is_valid_network() && !is_self_net() → 走 v4 分支
elif net.type = NET_IP6 && is_valid_network_v6() && !is_self_net_v6() → 走 v6 分支
else reject ← 网络类型或前缀范围不合法前缀范围白名单。
is_valid_network()限定 DN42 及相关 overlay 的段(dn42 主段、任播段、ChaosVPN、Freifunk、NeoNetwork)。这是协议级常量,不随节点变。自有前缀防回学。
!is_self_net()挡住从 eBGP 重新学到自己的前缀。RPKI:
if roa_check(...) = ROA_INVALID then reject只拒
ROA_INVALID。ROA_VALID与ROA_UNKNOWN(无 ROA,即 not-found)都接受——DN42 里大量前缀没有 ROA,拒 not-found 等于自断大半个网。被拒的路由会打印日志,含前缀与起源 AS。update_flags()处理 DN42 链路质量社区(见下)。调
ebgp_import_filter()(本 AS 策略层)。accept。
第二段:ebgp_import_filter(本 AS 策略层)
严格按这个顺序:
| # | 动作 | 说明 |
|---|---|---|
| 1 | 运维拒收前缀 | net ~ BLOCKED_NETS_4/6 即 reject。由 flap 处置端点下发,精确匹配,块本身只在有配置时才渲染出来 |
| 2 | AS_PATH 长度闸 | bgp_path.len > 8 即 reject |
| 3 | bgp_med = 100 | 先给所有路由一个基准 MED |
| 4 | 拒收 ASN | bgp_path ~ [= * ASES_REJECT * =]——路径里任意位置出现即 reject,不只是起源 |
| 5 | 清洗本 AS large community | 删掉对端可能伪造的 (OWNAS, origin_region|origin_nodeid|policy, *) |
| 6 | prefer_cold_potato() | 命中则把 MED 降到 50 |
| 7 | per-prefix local-pref | route_local_pref 的每条规则渲染成一行精确匹配的 bgp_local_pref = N |
| 8 | 黑洞社区 | 带 (65535, 666)(RFC 7999)的路由保留,但 dest = RTD_BLACKHOLE |
| 9 | 打上来源标记 | 加 (OWNAS, LC_ORIGIN_REGION, DN42_REGION) 与 (OWNAS, LC_ORIGIN_NODEID, NODEID) |
第 5 步的顺序是安全性的关键:先删后加,意味着对端无法伪造「这条路由源自本网某节点」的标记。清洗与打标之间夹着的策略动作(6–8)都只影响本地选路,不写出对外 community。
NODEID 取 large_communities.origin_node_id。未显式配置时由 _derive_origin_node_id 从 loopback_ipv4(回落 router_id)取低 16 位——注意那是整个地址的低 16 位,不是最后一段:172.20.202.1 会得到 51713,不是 1。
生产 fleet 用的是显式配置的小编号(hkg1 = 1),因为 large community 的值位要人能读。新节点接入时记得一并配 origin_node_id,否则会拿到派生出的五位数。
cold potato 的判定
function prefer_cold_potato() {
my_region_tag = get_region_tag(DN42_REGION);
incoming_tag = get_region_tag(filter(bgp_community, [(64511, 41..53)]).min.data);
if (incoming_tag = 0 || incoming_tag = my_region_tag) then bgp_med = COLD_POTATO_MED;
}get_region_tag() 把 DN42 标准 region community 折叠成 5 个粗大区:
| DN42 region community | 折叠为 |
|---|---|
| 44 | 1 |
| 42–43 | 2 |
| 41 | 3 |
| 50–53 | 4 |
| 46 | 5 |
| 其它 / 无 | 0 |
判定有两个入口:同大区,或根本没带 region community(incoming_tag = 0)。后者是刻意的——没带标记的路由不该因为缺信息就被判成远路,否则一个误标或漏标的对端会过度影响选路。
命中则 MED 从 100 降到 COLD_POTATO_MED(默认 50),配合模板里的 med metric on 实现「同区域优先就近」。改的是 MED 而不是 local-pref,所以它比 AS_PATH 长度和 eBGP-direct 的优先级低——是温和的偏好,不是强制。
链路质量社区
update_latency / update_bandwidth / update_crypto 维护 DN42 的 (64511, N) 三组社区,逻辑一致:沿路径保留最差的一档。
- 延迟
(64511, 1..9):保留最大值(最慢的一跳决定端到端)。 - 带宽
(64511, 21..29):保留最小值(瓶颈决定)。 - 加密
(64511, 31..34):保留最小值(最弱的一段决定)。
update_flags() 的入参来自会话配置。link_latency = 0 表示未配置,此时把路径上已有的延迟社区全部删掉,而不是写一个假值——生产 hkg1 的全部外部会话当前都是 dn42_import_filter(0, 23, 34),即没有配延迟档。
这三组社区在本 fleet 是纯可见性信号:打出去给对端与 looking glass 看,本网选路不读它们。原因是 latency 社区只累计 eBGP 链路、不含 fleet 内部跳数,据它选路会让节点弃近就远。
导出路径
dn42_export_filter(通用层)
if (is_valid_network() || is_valid_network_v6()) && source ~ [RTS_STATIC, RTS_BGP, RTS_DEVICE]不满足即 reject——只有静态、BGP、直连三种来源可能出去。
对 RTS_STATIC / RTS_DEVICE(本地产生的路由)多一道闸:
if (is_self_net() || is_self_net_v6()) && !is_self_net_aggregate() then reject;只放行聚合前缀本身,挡住它下面的 more-specific。 这就是 bird.conf 里自有前缀要以 reject route 形式存在的配套——对外只宣告聚合,内部细项不泄漏。
随后给本地路由加上 (64511, DN42_REGION) 区域社区,跑 update_flags(),再调 ebgp_export_filter()。
ebgp_export_filter(本 AS 策略层)
bgp_med = OUTGOING_BGP_MED; # 0
if (bgp_path.len = 0) then { # 仅本地产生的路由
bgp_large_community.add((OWNAS, LC_ORIGIN_NODEID, NODEID));
bgp_large_community.add((OWNAS, LC_ORIGIN_REGION, DN42_REGION));
}只给自己产生的路由补来源标记。 bgp_path.len = 0 是「本地起源」的判据;转发别人的路由时不重写来源信息,否则下游会以为那条路由源自本网。
出站 MED 一律归零,不把内部的 cold-potato 数值泄漏给对端。
iBGP 与 OSPF
iBGP 过滤器
function ibgp_import_filter() {
if (source != RTS_BGP && !is_ibgp_network()) then reject;
if (bgp_path.len = 0) then bgp_med = 10;
accept;
}
function ibgp_export_filter() {
if (source != RTS_BGP && !is_ibgp_network()) then reject;
accept;
}两侧同一道闸:非 BGP 来源的路由必须命中内部前缀白名单才能进出 iBGP。这防的是本地直连或静态路由(比如某个容器网段)意外扩散到全网。
白名单 IBGP_NETS_4 / IBGP_NETS_6 由渲染注入,内容是本 AS 聚合前缀加本节点 loopback:
define IBGP_NETS_4 = [ 172.20.202.0/26+, 172.20.202.1/32+ ];
define IBGP_NETS_6 = [ fdc1:e451:2020:9500::1/128+, fdc1:e451:2020::/48+ ];本地起源进 iBGP 时设 bgp_med = 10,低于 eBGP 学来路由的基准,保证内部服务地址优先走内部路径。
会话本身的生成规则(full-mesh、next hop、transport)见 内部互联。
OSPF
两个实例(v2 / v3),过滤条件对称且极窄:
import where source != RTS_BGP && is_self_net();
export where source != RTS_BGP && is_self_net();OSPF 只承载自有前缀内的非 BGP 路由——它的职责是让节点之间互相看得见 loopback 与内部链路,路由传播交给 iBGP。接口与 cost 由 igp_adjacencies 渲染进 ospf_interfaces.conf。
上游模板里的未用函数
community_filters.conf 末尾有一组来自上游示例的便利函数,本 fleet 一个都没用到——生成的 peer 配置一律直接调 dn42_import_filter / dn42_export_filter:
| 函数 | 语义 |
|---|---|
dn42_import_peer_only | 只收起源 = 对端自身的路由(不收 transit) |
dn42_import_depref_transit | transit 路由 MED +20 降权 |
dn42_export_noexport | 导出时加 NO-EXPORT,只在 iBGP 内传播 |
dn42_export_peer_only | 只导出静态与直连路由 |
dn42_export_prepend / _prepend2x / _prepend3x | 导出时 prepend 本 AS 一到三次 |
它们随上游模板一起渲染出来,无害但也无人调用。要用得先在 schema 与模板里加上「按会话选择过滤器函数」的能力——当前 policy 字段选的是会话模板而非过滤器,取值三选一:
policy | 效果 |
|---|---|
dnpeers(默认) | 外部 eBGP 会话,from dnpeers |
internal | 视为内部会话,不进 dn42_peers.conf |
route_collector | 多跳收集器喂送会话(无 WG 接口),from route_collector:import none、只导出,用于向外部 route collector 喂路由 |
生产 hkg1 当前全部外部会话都是 dnpeers。