Skip to content

渲染产物参考

agent 把 DesiredState 渲染成一组文件落在 <state-dir>/nodes/<node_id>/rendered/,再 bind 挂载给各容器。本文按目录逐个说明产出什么、被谁消费、由 schema 的哪一部分驱动。

渲染器在 packages/dn42_templates/,写盘计划在 packages/dn42_runtime/。逐字节的 golden 样本在 examples/rendered-hkg1/,改模板会触发回归比对

本文的示例取自生产节点 hkg1(AS4242420028,10 节点 fleet),密钥类内容已脱敏。


目录布局

rendered/
├── bird/            → bird-router 容器 /etc/bird
│   ├── bird.conf                主配置:常量、静态聚合、过滤器函数、kernel、模板
│   ├── rpki.conf                RPKI protocol(连 underlay 上的 stayrtr)
│   ├── community_filters.conf   DN42 large community 编解码与策略
│   ├── custom_filters.conf      import / export 过滤器主体
│   ├── ospf.conf                OSPF 实例
│   ├── ospf_interfaces.conf     OSPF 接口与 cost
│   ├── ibgp.conf                iBGP 会话(由 internal_topology 合成)
│   ├── dn42_peers.conf          eBGP 会话(由 bgp_sessions 合成)
│   ├── anycast_services.conf    direct protocol,承载任播地址
│   └── local_flapfeed.conf      ⚠ 旁路进程自管,不属于渲染集
├── wireguard/       → wg-gateway 容器 /etc/wireguard(只读挂载)
│   └── <iface>.conf             每接口一份
├── scripts/         → wg-gateway 与 bird-router 容器 /opt/dn42/scripts(只读挂载)
│   ├── wg/apply-<iface>.sh      单接口应用
│   ├── wg/apply-all-wg.sh       全量重放
│   ├── wg/start-wg-gateway.sh   容器入口
│   └── bird/{apply,start}-*.sh  BIRD 重载与容器入口
├── coredns/         → dns 容器 /etc/coredns(订阅 DNS 组时才有)
│   ├── Corefile
│   └── zones/db.<zone>
├── resolv/          → debug-shell 容器 /etc/resolv.conf(只读)
└── runtime/
    └── bird-run/    → bird-router 容器 /run/bird(可写;宿主 agent 由此直连 bird.ctl)

渲染产物不携带 generation,跨代逐字节稳定。这是最小扰动设计的前提:没有实质变化时 file plan 全是 noop。


bird/

bird.conf

NodeSpecBird2ConfigSpec 驱动的主配置。关键派生:

渲染出的内容源字段
define OWNASnode.asn
define OWNIP / OWNIPv6node.loopback_ipv4 / loopback_ipv6
define DN42_REGIONbird.region,回落 node.region
hostnamenode.node_id
static_routes4/6 的 reject routenode.ipv4_prefixes / ipv6_prefixes
is_self_net() / is_self_net_v6()同上,加 + 匹配 more-specific
is_self_net_aggregate()同上,只匹配聚合前缀本身
router idOWNIP
protocol kernelkrt_prefsrcOWNIP / OWNIPv6

自有前缀以 reject route 进入 BIRD——这样 BGP 能宣告聚合前缀,而内核不会把聚合内不存在的目的地转发出去。

is_valid_network() 是 DN42 可接受前缀的白名单(含 ChaosVPN、Freifunk、NeoNetwork 段),不随节点变化,属于协议级常量。

末尾是固定的 include 链:

include "/etc/bird/ospf.conf";
include "/etc/bird/ibgp.conf";
include "/etc/bird/dn42_peers.conf";
include "/etc/bird/local*.conf";      ← 旁路挂载点
include "/etc/bird/anycast_services.conf";

local*.conf 这一行是 flapfeed 的挂载点。 它让旁路进程往同一目录丢配置而不必改任何渲染文件——代价是 file plan 必须对该 glob 豁免 prune,否则每轮 reconcile 都会把它当孤儿删掉。见 flap 观测

dn42_peers.conf

每条 BgpSessionSpec 渲染一个 protocol bgp <name> from dnpeers

# 会话类型:MP-BGP
protocol bgp furry_4242420088_hkg1_v6_v4 from dnpeers {
    neighbor fe80::88 as 4242420088;
    interface "as4242420088";
    passive off;
    bfd yes;                                    ← BfdSpec.enabled
    ipv4 {
        import where dn42_import_filter(0,23,34);
        export where dn42_export_filter(0,23,34);
        import limit 8500 action block;         ← import_limit / _action
        import table;
        extended next hop on;                   ← extended_next_hop
    };
    ipv6 { …同上,无 extended next hop… };
}
  • 协议名即会话名,遵循 <slug>_<asn>_<node>_<af>命名规范
  • 过滤器的三个参数依次是 link_latency / link_bandwidth / link_crypto,驱动 DN42 链路质量社区;0 表示未配置。语义见 路由策略
  • import table 恒开birdc show route ... all 与过滤前 RPKI 分布采集都依赖它。
  • MP-BGP 形态下 extended next hop on 只出现在 ipv4 通道——那正是「用 v6 会话承载 v4 路由」的开关。

ibgp.conf

internal_topology 合成,不来自 bgp_sessions。full-mesh 下每个对端一个 protocol bgp ibgp_<node>

protocol bgp ibgp_sha2 {
    local as 4242420028;
    neighbor fdc1:e451:2020:9500::3 as 4242420028;   ← 对端 hosts[].ownip6
    source address fdc1:e451:2020:9500::1;           ← 本节点 ownip6
    advertise hostname on;
    ipv4 {
        next hop address 172.20.202.1;               ← 本节点 ownip
        next hop self ebgp;
        import table;
    };
    ipv6 { next hop address fdc1:e451:2020:9500::1; … };
}

ibgp_transport 决定 neighborsource address 用 v6 还是 v4 loopback。生产 fleet 十个节点当前全部是 ipv6——ipv4 回落是给 underlay 无 IPv6 的节点准备的能力,尚未启用。

ospf_interfaces.conf 与 anycast_services.conf

OSPF 接口由 igp_adjacencies 渲染,每条带 costtype ptp

任播服务是一条 direct protocol:

protocol direct direct_anycast {
    ipv4;
    ipv6;
    interface "dns-anycast";
}

接口名 dns-anycast 是保留名,由 _normalize_dns_anycastdns.bind_addresses 派生——这条链是单一真相源的范例:改一处地址,接口、内核路由、BGP 宣告全部跟随。

rpki.conf

连 underlay 上的 stayrtr:remote "10.254.42.3" port 8282,取自 RpkiSpec.listen_host / listen_port。用 retry keep 保守策略,避免缓存短暂不可用时立即清空 ROA 表。


wireguard/

每个 WG 接口一份 .conf,形态极简:

ini
[Interface]
PrivateKey = <节点密钥>
ListenPort = 51829          ← 端口池 external 段内
MTU = 1420

[Peer]
PublicKey = eIv+hTDFg6Nb69FY68LWnfI0bMS5ui7VWUyKZK4DCy0=
Endpoint = hkg1.exploro.one:32641
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

[Interface] 里没有 Address 接口地址由 apply 脚本用 ip addr 配,不走 wg-quick 的地址管理——这样地址变更不必重建接口。

PersistentKeepalive 恒为 25 秒。

⚠️ 这些文件含明文私钥,且模式为 0644、目录链 0755 同一节点全部接口共用同一把节点密钥。节点上任何本地用户都能读到它,见安全模型


scripts/

apply-<iface>.sh

单接口应用脚本,被 convergence plan 的 WG_SYNC_INTERFACE 调用。骨架:

  1. flock 串行化/run/dn42-control/wg-apply.lock)——避免 reconcile 收敛与 L3 自愈并发时 ip link add 的 check-then-create 竞态。
  2. 把持久 conf 复制到 /run/dn42-control/wireguard/chmod 600
  3. ip link add ... type wireguard(已存在则忽略),随后 ip link show 兜底确认。
  4. wg-quick strip 去掉 wg-quick 专有指令 → wg syncconf 增量同步。
  5. ip link set ... up,再配地址——顺序是刻意的:地址命令万一失败(改名时旧接口未拆、点对点地址撞用),接口至少已经 UP,不会留下「建了却 DOWN」的半成品。

脚本里还有一段读 /run/dn42-control/secrets/node.key 替换 PrivateKey 的分支,那是密钥收权前的兑现逻辑。现在是死代码:该文件不存在(实测),渲染 conf 里已是字面私钥。脚本内的注释「持久产物全程不见明文」也随之失效。

其它脚本

脚本作用
wg/apply-all-wg.sh全量重放所有接口;容器重建后与 L3 自愈补齐时调用
wg/apply-dn42-lo.shdummy loopback 接口(承载节点身份地址)
wg/apply-dns-anycast.shdns-anycast dummy 接口(承载任播地址)
wg/start-wg-gateway.shwg-gateway 容器入口
bird/apply-bird.shbirdc configure 热重载,对应 convergence plan 的 BIRD_RELOAD
bird/start-bird-router.shbird-router 容器入口

coredns/

只在节点订阅了 DNS 组时才渲染。

Corefile 每个 zone 一个 server 块,全部 binddns.bind_addresses

natlan.dn42:53 {
  bind 172.20.202.57 fdc1:e451:2020:56::53
  file /etc/coredns/zones/db.natlan.dn42
  cache 300
  reload
}

外加一个 .:53 根块只做 errorsreload不做递归——节点只跑权威 DNS。

zone 文件由 DnsZoneSpec 加其记录渲染,SOA 各字段来自 zone 配置(未配置时取 schema 默认)。反向 zone 的文件名把 / 替换成 _(RFC 2317 委派形如 0/26.202.20.172.in-addr.arpa)。


resolv/ 与 runtime/

resolv/resolv.conf 只读挂给 debug-shell,指向 DN42 递归解析器 172.20.0.53——节点自身只跑权威 DNS,逐跳反解不能指向本机。见主动拨测

runtime/bird-run/唯一可写的挂载点,容器内是 /run/bird,里面是 BIRD 的控制 socket bird.ctl。宿主上的 agent 由此直连 BIRD 采集路由,不再走 docker exec birdc。该挂载由 _normalize_bird_control_socket 无条件注入,是 bird-router 的一等不变量。


与 file plan 的关系

planner 对渲染集算 create / update / delete / noop(SHA-256 对比),并开启 prune——不在渲染集里的文件视为孤儿删除,这样隧道删除后不留残留 conf。

唯一豁免是 bird/local*.conf:它与渲染集共目录,但归旁路进程自行读写。

计划三层与执行顺序见 Node Agent 内部