Skip to content

地址概念模型

本文把项目里所有类型的地址盘一遍:每个地址是什么、有什么用、真相源在哪、影响半径多大,并按对「单一真相源」最关键的维度给每个地址分类。

这是概念参考。fleet 的具体数值分配见 寻址与 renumberinternal_topology 的语义与一致性不变量见 内部互联;承载这些地址的 schema 见 DesiredState


三种分类:源 / 派生 / 副本

每个地址都标两件事——影响半径真相源分类。后者直接决定它是不是 renumber 的危险点:

分类含义renumber 时例子
源(source)被人手 authored 的权威值,改这里才算改改这一处Node.loopback_ipv4DnsGroup.bind_addressesNodeSpec.link_local
派生(derived)渲染 / normalize / materialize 时从源算出来,不落独立存储不用动,自动跟随OWNIPdns-anycast 接口地址、外部 eBGP 接口上的 link-local
副本(copy)源的独立第二份,没有机制保证一致必须手动同步,漏一处就坏internal_topology.hosts[*].ownipbgp.source_address

设计方向是把「副本」逐个变成「派生」。 代码里已有三个落地范例:

  1. _normalize_dns_anycastpackages/dn42_schemas/dn42_schemas/desired_state.py):dns.bind_addresses 是 DNS 服务地址的唯一真源,dns-anycast dummy 接口地址与它进 BGP 的任播前缀全部在 normalize 阶段派生。
  2. _interface_payload 的 LLA 派生(apps/control-server/app/services/materializer.py):NodeSpec.link_local 是节点 link-local 的唯一真源,materialize 时派生到所有外部 eBGP WG 接口的 addresses
  3. _inject_node_keys(同上):节点 WireGuard 私钥的唯一真源是 node_wireguard_keys 表,materialize 时无条件覆盖每个 WG 接口的 private_key_ref

以最贵的节点 loopback 为例,一处真值的下游全景(实线为自动派生,虚线为仍需手动同步的副本):


按影响半径分层

影响半径最大(动一下牵动全 fleet 与注册表)到最小(节点内部、链路层)。

第 1 层:节点身份地址

影响半径:全 fleet + DN42 注册表 + DNS。 这层是最贵的地址——一处真值,但被复制或派生到很多地方。

地址是什么 / 用途真相源分类影响半径
router_idBIRD router id;iBGP / OSPF 身份;large-community 的 origin_node_id 取它低 16 位Node.router_idiBGP / OSPF 会话重建;community 编号变化
loopback_ipv4 / loopback_ipv6节点身份 loopback。渲染成 OWNIP / OWNIPv6:内核导出源地址 krt_prefsrc、iBGP next-hop 与 source address、router id 兜底。必须属于对应的 *_prefixesNode.loopback_ipv4/ipv6全 fleet(见 1.1)+ DNS 正反向记录 + 注册表
ipv4_prefixes / ipv6_prefixes节点自有、对外宣告的聚合前缀。派生 static reject 路由、is_self_net ipset、iBGP 宣告前缀Node.ipv4_prefixes/ipv6_prefixes节点对 DN42 起源什么(注册表 route 对象)

派生自本层(安全,自动跟随)OWNIP / OWNIPv6router id OWNIPkrt_prefsrcorigin_node_idownnets*_ipset、iBGP 的 source address 与 next hop——全部在 packages/dn42_templates/ 里从 loopback 与 prefixes 算出。

1.1 副本:internal_topology.hosts[*].ownip / ownip6

bird.internal_topology.hosts[<node>].ownip/ownip6每个节点 loopback 的独立第二份,而且按 fleet 一致性不变量要复制进每一个节点base_template

  • 影响半径:全 fleet。renumber 一个节点的 loopback,要同步 N 个节点里的这份 hosts。
  • 这是历史上「改不干净」最严重的点,deploy/tools/unify_internal_topology.py 里硬编码旧地址导致回退的坑也出在这里。
  • 收敛方向:hosts 的 ownip / ownip6 从各节点 Node.loopback_* 派生。改动牵动全 fleet iBGP、须逐节点灰度验证收敛,属于不可逆风险,尚未实施。

第 2 层:eBGP 会话地址

影响半径:单个对端。

地址是什么 / 用途真相源分类
BgpSessionSpec.neighbor对端 BGP 地址;可带 %zone会话 spec
BgpSessionSpec.source_address本端建会话的源地址会话 spec副本

2.1 副本:source_address 与接口地址

source_addressInterfaceSpec.addresses(第 3 层)里的本端地址是同一个值、独立写两遍,没有任何机制强制相等。renumber 链路时改了接口漏了 source(或反之),BGP 就起不来。

收敛方向:source_address 改可选,从绑定接口按地址族派生并加强校验。这牵动寻址模型的一次决策(per-link /31 还是节点单 /32 + link-local),须先拍板。

第 3 层:接口与链路地址

影响半径:单条链路(两端)。 承载在 InterfaceSpec / WgInterface.spec

地址是什么 / 用途真相源分类
addressesip addr add 到接口的本端地址。外部 eBGP 接口的 fe80::X/64 由 materialize 派生注入接口 spec(LLA 部分为派生)源(LLA 为派生)
peer_routes经该接口可达的对端地址(peer_v4/32peer_v6/128、对端 link-local)接口 spec副本(对端本地地址的镜像)
wireguard_peer.endpoint拨向对端的 underlay host:port;可为域名,agent 周期重解析接口 spec
wireguard_peer.allowed_ipsWG 加密选路范围,通常 0.0.0.0/0 + ::/0接口 spec

3.1 已收敛:节点 LLA fe80::X

节点本端 link-local(一节点一个,所有外部 eBGP WG 接口复用)已从副本收敛为派生

  • 唯一真源 NodeSpec.link_local(裸 fe80::/10 地址,不带 %zone)。
  • materialize 的 _interface_payloadpeering.is_internal=False 的 WG 接口,把 <link_local>/64 注入接口 addresses(已存在则去重)。渲染器照旧与各接口的 fe80 peer_route 配成 ip addr add fe80::X/64 peer fe80::Y,输出不变。
  • 存量接口里的副本由 scripts/tools/backfill_node_lla.py 一次性剥除,之后由派生重新提供。
  • 内部互联的 WG 接口用各自的 link-local,不复用节点级值;对端 link-local 是 per-peer,留在 peer_routes 不动。

第 4 层:任播服务地址

影响半径:跨节点共享 + 注册表。

地址是什么 / 用途真相源分类
dns.bind_addressesDNS 任播服务地址。normalize 派生 dns-anycast dummy 接口(v4 /32、v6 /128)并登记 track_service,于是 BIRD 起源任播前缀进 BGPDnsGroup.bind_addresses
dns-anycast 接口地址与任播前缀上者的派生产物—(派生)派生
DNS A / AAAA / PTR 记录值解析到 loopback 或任播地址DnsRecord.content副本(指向 loopback 的手工绑定)

bind_addresses → dns-anycast 这条链是全项目最干净的单一真相源实现。见 DNS 与任播

第 5 层:runtime / underlay

影响半径:节点内部。 仅在单节点容器编排内有意义,不进公网身份或任播分区。

地址是什么 / 用途真相源分类
runtime.underlay.subnet / gateway / ipv6_subnetrouter netns 的容器互联网。ipv6_subnet 纳入容器 config_hash,改动触发声明式重建UnderlayNetworkSpec
runtime.rpki.listen_hostunderlay 内 RPKI cache 地址(默认 10.254.42.3RpkiSpec.listen_host
RuntimeServiceSpec.ipv4_address某服务在 underlay 的显式地址;须属于 underlay 子网且不等于网关service spec

不随节点变的字面量

is_valid_network / is_valid_network_v6 里的 DN42 可接受前缀范围、anycast 172.2x.0.0/24 段等,是协议级常量,不随本节点 renumber 变化,不属于源 / 派生 / 副本三分类。


副本清单(renumber 危险点速查)

#副本应派生自状态
1internal_topology.hosts[*].ownip/ownip6各节点 Node.loopback_*仍是副本。影响半径全 fleet,历史事故最多
2bgp_sessions[*].source_address绑定接口的 addresses仍是副本。收敛前需先定寻址模型
3interfaces[*].peer_routes 的对端地址对端 addresses(以 Peering 聚合根作单源)仍是副本
4DNS A / AAAA / PTR 记录指向 loopback 的手工绑定仍是副本。renumber 必须连带改
5节点 LLA fe80::XNodeSpec.link_local已派生(materialize 注入 + backfill 剥副本)
6内部对端 WG 公钥对端 Node.wireguard_public_key已派生(materialize 注入)
7节点 WG 私钥node_wireguard_keys已派生(materialize 无条件注入)
8dns-anycast 接口地址与任播前缀DnsGroup.bind_addresses已派生

其它几处已知的同源双写(不属于地址范畴,一并记在这里备查):

重复的事实现状
router_idloopback_ipv4 常恒等却分存二者仍各存一份。合并是破坏性 schema 变更,影响存量节点数据
NodeSpec.regionBird2ConfigSpec.region后者是前者的可选覆盖,但字段名没有体现覆盖意图
BgpSessionSpec.neighborpeer_routes 的镜像关系无校验强制二者一致
applied_generationobserved_generation 术语两个名字指同一件事的不同侧面,易混淆;改名会动 agent 上报协议

每消灭一个副本,就少一处「改不干净」。范例形态见 _normalize_dns_anycast_interface_payload