Skip to content

路由策略

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                                                          ← 网络类型或前缀范围不合法
  1. 前缀范围白名单is_valid_network() 限定 DN42 及相关 overlay 的段(dn42 主段、任播段、ChaosVPN、Freifunk、NeoNetwork)。这是协议级常量,不随节点变。

  2. 自有前缀防回学!is_self_net() 挡住从 eBGP 重新学到自己的前缀。

  3. RPKI

    if roa_check(...) = ROA_INVALID then reject

    只拒 ROA_INVALID ROA_VALIDROA_UNKNOWN(无 ROA,即 not-found)都接受——DN42 里大量前缀没有 ROA,拒 not-found 等于自断大半个网。被拒的路由会打印日志,含前缀与起源 AS。

  4. update_flags() 处理 DN42 链路质量社区(见下)。

  5. ebgp_import_filter()(本 AS 策略层)。

  6. accept

第二段:ebgp_import_filter(本 AS 策略层)

严格按这个顺序:

#动作说明
1运维拒收前缀net ~ BLOCKED_NETS_4/6 即 reject。由 flap 处置端点下发,精确匹配,块本身只在有配置时才渲染出来
2AS_PATH 长度闸bgp_path.len > 8 即 reject
3bgp_med = 100先给所有路由一个基准 MED
4拒收 ASNbgp_path ~ [= * ASES_REJECT * =]——路径里任意位置出现即 reject,不只是起源
5清洗本 AS large community删掉对端可能伪造的 (OWNAS, origin_region|origin_nodeid|policy, *)
6prefer_cold_potato()命中则把 MED 降到 50
7per-prefix local-prefroute_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。

NODEIDlarge_communities.origin_node_id未显式配置时_derive_origin_node_idloopback_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折叠为
441
42–432
413
50–534
465
其它 / 无0

判定有两个入口:同大区,或根本没带 region communityincoming_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_transittransit 路由 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_collectorimport none、只导出,用于向外部 route collector 喂路由

生产 hkg1 当前全部外部会话都是 dnpeers


一张图