首页
隐私政策
iYoRoy DN42 Network
关于
更多
友情链接
Language
简体中文
English
Search
1
Docker下中心化部署EasyTier
4,378 阅读
2
给Android 4.9内核添加KernelSU支持
3,508 阅读
3
记一次为Android 4.9内核的ROM启用erofs支持
1,503 阅读
4
DN42探究日记 - Ep.1 加入DN42网络
1,181 阅读
5
在TrueNAS上使用Docker安装1Panel
965 阅读
Android
运维
NAS
开发
网络技术
专题向研究
DN42
个人ISP
CTF
Kubernetes
网络安全
奇思妙想
物联网
登录
Search
标签搜索
BGP
网络技术
BIRD
Linux
DN42
iBGP
Android
C&C++
OSPF
网络安全
Clearnet
CTF
MSVC
AOSP
Web
Docker
IGP
Windows
Kernel
TrueNAS
神楽悠笙
累计撰写
36
篇文章
累计收到
23
条评论
首页
栏目
Android
运维
NAS
开发
网络技术
专题向研究
DN42
个人ISP
CTF
Kubernetes
网络安全
奇思妙想
物联网
页面
隐私政策
iYoRoy DN42 Network
关于
友情链接
Language
简体中文
English
搜索到
6
篇与
的结果
DN42&OneManISP - 联邦, 自动化, IPAM 与 Overlay Network
本文不详细展开代码实现, 仅记录内网架构设计过程和自动化实现思路. 若需要参考代码/配置文件, 可参阅 iYoRoy-Network/bird2-config: BIRD2 Configuration for iYoRoy Network (AS4242422024, AS205369). 仓库包含较多的 AI 生成代码. 本文仅借用 Underlay/Overlay 的逻辑分层概念(基础层/业务层),特指地址角色的分离,不涉及 VXLAN/GRE 等隧道封装技术。 TL;DR 对整个内网架构进行了一次重构, 原本按“DN42 / IANA / WireGuard / BIRD 配置文件”水平切开的网络, 改造成一套按网络意图组织的基础设施. 最终设计可以概括为: 用 WireGuard 承载节点间 underlay transport; 用 BGP Confederation 替代 OSPF / full-mesh iBGP, 作为内部路由骨干; 用 Underlay / Overlay 分离节点身份和服务地址; 用 Tier / Region / Node ID / Token 自动派生 AS、loopback 和 link-local; 用 BGP Large Community 表达路由来源、传播范围和导出策略; 用 Ansible 把高层 peer intent 编译成 WireGuard + BIRD + interface 配置并自动下发. 背景 在这两个系列之前的一些文章中, 我们已经成功运营起了 DN42 AS 和 IANA AS, 并且同一套基础设施同时承载着两张网的流量. 在先前的 DN42&OneManISP - 共存环境下的OSPF源地址故障排除 - 悠笙の开发日记 中, 对于这两张网已经有了一个初步的拆分和隔离, 但是随着节点的增多, 手动维护这一坨东西的 WireGuard 和 BIRD 配置文件已经变得越来越复杂, 而且公网节点之间也需要互联与互相 IP Transit, 因此最终狠下心来打算对这一整套内网进行一次彻底的重构. 分析 旧架构的问题 重构之前, 各个节点之间的 DN42 路由是相互导通的, 使用 OSPF over WireGuard 作为 IGP, 在此基础上使用 Full-Mesh iBGP 来在各个边界路由器之间传递完整路由信息; 对于IANA 流量, 各个节点之间完全隔离, 每个节点单独广播自己的 /48 IANA IPv6 Prefix. 总结出来就是: WireGuard 负责节点间隧道; OSPF over WireGuard 负责 IGP; DN42 边界路由器之间再跑 full-mesh iBGP; IANA 节点基本各自独立广播自己的公网前缀. 这个架构在节点少的时候很好用, 但节点多起来之后问题开始明显: WireGuard、OSPF、BIRD 配置需要分别维护; 新增节点需要同时改多个地方, 容易漏; DN42 和 IANA 的路由策略互相影响, 但配置上又是分散的; IANA prefix 的跨 PoP 调度不够自然; 有些节点只是转发节点, 却仍然被迫拥有 overlay 身份; 源地址选择、路由泄漏、内部地址暴露都变得越来越难控制. 设计目标与 BGP Confederation 这次重构主要希望达成的几个目标: 节点加入要简单: 最好只需要描述节点身份和 peer 关系; DN42 / IANA / 内网骨干要能共享基础设施, 但策略上互相隔离; IP 地址应该是可以调度的资源, 而不是节点的固定身份; 内部拓扑不应该泄漏到外部 BGP peer; 配置应该由声明式数据生成, 而不是手写大量重复 WireGuard/BIRD session; 部署流程自动化 同时还有其他的一些比较细碎的需求: 部分 IANA 节点希望能承载其他节点的 IP Transit, 用 IANA PoP 来为其他节点广播公网 IP 当前 IANA AS 是たのしい和我共同在维护, 我们之间需要达成统一的协调, 部分基础设施需要互相 Transit 以及调控路由 我们只有一个 44Net IPv4 /24 (Sponsored by たのしい), 要播的话所有节点都需要 iBGP 互联和 IGP, 否则只能一个 PoP 使用 IANA AS 需要承接下游 IANA PoP 之间需要路由调优 先前的 IANA IPv6 Prefix 分配规则遵循的是 大洲 3 bits + 地区 3 bits + 同地区多节点 2 bits, 由一个完整的 /40 拆出来若干 /48, 但是因为某些上游限制播出前缀数量, 再加上这样碎片化的前缀不利于做 IPAM, 因此我们打算最终合并对外广播只详细到大洲层面, 其次走内部 iBGP 路由来处理更加详细的部分. たのしい那边的内网结构是 BGP Confederation, 用的是手动模拟的方案. 其实这样分析下来内网的架构就可以很轻松的定下来了, 最符合需求的就是把内网也切换成 BGP Confederation, 然后利用 BGP as IGP 的理念, 在联邦内广播 /32 和 /128 来处理内部路由. 同时, 因为联邦避免了传统 iBGP 水平分割的特性, 所以联邦不需要严格 Full-Mesh, 也不需要单独起 iBGP, 非常的去中心化啊xD 同时, BGP Confederation 可以很轻松的和たのしい那边的基础设施合并, 只需要我们两边都将对方的内部 AS 视作联邦 AS 处理即可. 这个方案还有一个好处, 因为实际上我们之间的基础设施包含关系是这样的: graph subgraph 4242423377 Infrastructure 3377_DN42_PoP[DN42 PoP] 3377_IANA_PoP[IANA PoP] end subgraph 4242422024 Infrastructure 2024_DN42_PoP[DN42 PoP] 2024_IANA_PoP[IANA PoP] end 3377_IANA_PoP <==跨基础设施联邦==> 2024_IANA_PoP 3377_DN42_PoP <--> DN42 <--> 2024_DN42_PoP 3377_IANA_PoP <--> IANA <--> 2024_IANA_PoP 我们之间的 DN42 PoP 和 Transit 是隔离的, 但是 IANA PoP 和 Transit 又是互通的, 在 BGP Confederation 场景下, 可以利用过滤器 + BGP (Large) Community 来对路由来源进行隔离的同时互通 IANA 之间的 Traffic Engineering 意图, 实现同一套标准两份基础设施通用. Overlay 与 IPAM 在旧设计里, 一个节点通常同时承担两种身份: 它是网络中的一个路由器; 它也是 DN42 / IANA 中可以被访问的服务地址持有者. 重构之后, 我决定把这两种身份拆开: Underlay: 节点作为路由器的身份, 用于内部互联、下一跳、隧道和转发; Overlay: 节点对外提供服务或承接流量时使用的 DN42 / IANA 地址. 这样, 一个节点可以只参与转发而没有 DN42 IPv4; 也可以在需要时临时宣告某个 overlay /32 或 /128. 实际应用场景下, 我的 DN42 段地址不算很充裕, 当初注册的是 /28, 只有 16 个可用地址. 有些节点比如 IEPL 之类的隧道节点或者 IX 节点, 它们只作转发作用, 不需要承载服务, 不需要被外部连接, 因此理论上并不需要为其分配独立可访问的 DN42/IANA 地址. 再加上, 前面我们已经打算将内网切换成 BGP Confederation, 基于此套配置, 我们可以将原本 DN42 + IANA 这样水平分割的网络结构重新规划, 改成 Underlay + Overlay 的垂直划分, DN42 和 IANA 的地址/流量作为上层 Overlay Network 的负载, 下层 Underlay Network 用作节点间底层通讯和转发的基础设施. 在这种情形下, IP 地址变成了一种可以被在内网里轻松调度的资源而非和节点绑定的唯一 ID. 这样的优势很明显: 节省稀缺地址资源: 转发节点可以只拥有 underlay 身份, 不必分配 DN42 IPv4 或公网地址; Anycast 与地址迁移: Overlay 地址变成可以通过 BGP 调度的资源, 不再强绑定某台机器; 隐藏转发节点: 纯转发节点不需要暴露全局可达地址, 外部更难直接探测内部拓扑; 新节点入网简单: 新节点先加入 underlay, 需要承载业务时再分配 overlay 地址; 优雅 Transit 交付: IANA PoP 可以通过内部 confederation 把客户前缀或服务地址投递到其他节点. 设计 综上所述, 最终设计了这样一套 IPAM 机制和自动化流程. 每个节点拥有一些基本的元数据: Tier: 指代当前节点用途, 如用作骨干网, 接入 IX, 或是作为网内节点 Region: 节点所在大洲区域 ID: 同区域下该节点ID Token: 给该节点分配的一个随机 16bit 字符串, 在 IPv6 下给节点作为唯一 ID 使用 对于 Tier 和 Region, 编写了几张表格作为数据来源: Tier Desc 1 Backbone 2 IX 3 Backbone + IX Mixed 9 Node Region Desc 0 Reserved 1 Asia 2 Europe 3 Africa 4 North America 5 South America 6 Oceania 7 Antarctica 联邦 AS: 4220240000 - 4220249999 分配规则: 422024{tier}{region}{node_id:02d} 其中 tier 指代节点用途, region 指代节点所在区域, 最后两位 node_id 用于区分同区域内的不同节点. Underlay IPAM IPv4 Prefix: 100.64.0.0/16 分配规则: 100.64.{tier*10 + region}.{node_id}/32 IPv6 Prefix: fd18:3e15:61d0:ffff::/64 分配规则: fd18:3e15:61d0:ffff:{tier}:{region}::{node_token}/128 为了简化自动化流程, 我打算将 WireGuard 隧道之间的 Link-Local 地址也接入自动化管理, 利用上述 Tier, Region, Token 生成, 由自动化脚本生成 WireGuard 配置并自动配置隧道和联邦 BGP Session. Link-Local 分配规则也大体上沿用普通 IPv6 的规则: fe80::2024:{tier}:{region}:{node_token}/64 这样, 每个节点只需要为其指派元数据便可以按照上述规则自动生成内网联邦 AS 和地址. Overlay IPAM DN42 IPv4 部分就手动指派了, 这部分存在一些历史遗留架构, 修改需要合并到官方 Registry, 因此就打算手动指派. Underlay 的 IPv6 本身是 DN42 可达的, 如果还要分配其他 DN42 IPv6 直接按需广播即可. 对于 IANA IPv6, 目前的设计格式是先按照最初提出的规则计算大洲 /43, 比如亚洲为: 2a14:7583:f220::/43 再按照地区计算出 /46, 比如香港地区 HKG: 2a14:7583:f224::/46 接着将从 /46 到 /112 全部置零, 最后的 16bits 填入节点 Token 作为节点的 IANA IPv6 地址. 比如我的某台香港地区骨干网节点: 2a14:7583:f224::7d89/128 Community 隔离机制 在这套架构里, BGP Large Community 实际上就是一条路由在不同模块和基础设施之间传递的元数据信息和控制信息. 基于这些元数据, 我们在向不同外部对等体导出路由的时候就能判断: 它属于 DN42 还是 IANA? 它是本地生成、下游带来、peer 学到, 还是 upstream 学到? 它能不能导出到别的 AS? 它是不是 underlay-only? 这条路由需不需要 prepend as? 举个具体的例子, 比如用第一位标识路由来源/目标是 DN42 还是 IANA, 允许发往 DN42 的路由全部会被打上 (4242422024, 1, 1) 同理, 发往 IANA 的路由全部会被打上 (205369, 1, 1) 在各个 Protocol 中, 这一项作为总开关, 控制是否应当导出. IANA 的上游导出过滤器: template bgp iana_upstream_v6 { ... ipv6 { ... import filter { if !iana_filter_default_check() then reject; # 基础检查 remove_confederation_as(); # 移除 confederation as, 防止外部 peer 恶意携带内部 as remove_private_community(); # 移除私有 community, 防止外部 peer 恶意操纵内部路由 iana_upstream_add_community(); # 为所有来自 IANA 的路由打上允许在 IANA 基础设施广播的 community accept; }; export filter { if !iana_filter_default_check() then reject; # 基础检查 if !iana_upstream_check_community() then reject; # 检查 community, 是否是发往 IANA 的路由/是否携带了 no-advertise/no-export 等 remove_confederation_as(); # 移除 confederation as remove_private_community(); # 移除私有 community accept; }; ... }; ... } 其中 iana_filter_default_check() 用于检查前缀长度, ROA, 是否是默认路由等比较琐碎的内容: function iana_filter_default_check(){ if net ~ [::/0] then return false; if net.len > 48 then return false; if bgp_large_community ~ [(IANA_OWNAS,1,1)] && is_self_iana_v6() then return true; if roa_check(iana_roa_v6, net, bgp_path.last) = ROA_INVALID then return false; return true; } remove_confederation_as(), 顾名思义, 移除联邦内部 AS: function remove_confederation_as() { bgp_path.delete([4220240000..4220249999]); # 4242422024 Infrastructure bgp_path.delete([4233770000..4233779999]); # 4242423377 Infrastructure } remove_private_community(), 顾名思义, 移除内部 Community. 这部分目前实施的很粗糙, 后续需要详细细化, 因为部分 Community 应当对下游开放, 使其可以通过 Community 来传递路由意图, 做一些优化, 比如 prepend AS path, 可用于路由调优或者流量工程: function remove_private_community(){ bgp_large_community.delete([(4242422024, *, *)]); bgp_large_community.delete([(205369, *, *)]); } iana_upstream_add_community(), 为来自上游的路由全都打上允许在 IANA 基础设施流通的 Community 以及路由来源标识: function iana_upstream_add_community(){ bgp_large_community.add((205369,1,1)); bgp_large_community.add((205369,2,102)); } 其中, (205369,2,102) 标识该路由来自于上游. 路由来源分为三种, 上游, 对等体, 下游/客户/自己(看作客户). 对这三种不同类型的 BGP Session, 导出的路由通常不相同: 对上游, 我们需要让上游帮我们广播我们自己的和我们下游的段, 因此需要向上游导出所有来自下游的前缀 对下游, 我们需要为其提供网络服务, 需要向其导出我们知道的所有路由, 即来自上游, 对等体, 下游的路由 对对等体, 对等体之间的连接仅仅是为了双方互相访问彼此的网络, 因此不能互相导出互相的上游或者对等体, 不然就变成免费 Transit/Tunnel 了, 因此只导出来自下游的路由 在当前内网中与之对应的, 来自对等体的会被打上(205369,2,101), 来自下游的会被打上(205369,2,100). iana_upstream_check_community() 会检查路由的 Community, 基于上述原则判断其是否应该被广播给上游: function iana_upstream_check_community(){ if !(bgp_large_community ~ [(205369,1,1)]) then return false; # 没被允许在 IANA 基础设施广播, 则拒绝 if bgp_large_community ~ [(205369,65535,65282)] then return false; # no-advertise if bgp_large_community ~ [(205369,65535,65281)] then return false; # no-export if bgp_community ~ [(65535,65281)] then reject; # no-advertise if bgp_community ~ [(65535,65282)] then reject; # no-export if bgp_large_community ~ [(205369,2,0)] then return false; # 来自内网, 该 community 被用于标识路由来自内网, 不应该往外广播, 此处拒绝广播 if bgp_large_community ~ [(205369,2,101)] then return false; # 来自对等体, 拒绝广播 if bgp_large_community ~ [(205369,2,102)] then return false; # 来自上游, 拒绝广播 return true; } 同理, 对于 DN42 也是类似的检查机制. 基于这套机制, 可以很轻松的隔离 DN42 和 IANA 两张网. 上述只是一个大致的解释, 更细的 community 设计和各类出口策略其实还可以继续展开, 但那就会变成另一篇路由策略详解了. 这部分其实大量参考来源于たのしい的BGP Communities, 毕竟两者基础设施需要互通, 很多 Community 规范基本上都是照搬过来的. 在此感谢たのしい大佬提供的思路~ 路由生命周期 第一阶段: Ingress / Import 所有外部路由进入系统时, 先经过对应 domain 的 import filter. IANA 有三类来源: upstream; peer / IX; downstream. DN42 也有两类主要来源: 普通 transit / eBGP peer; IX / route server. 除此之外, 本机也会产生一些 route: underlay loopback; DN42 overlay address; IANA own / anycast address. 这些路由在进入联邦时会被打上 Large Community. 后面的所有 filter 都需要据此调控. 第二阶段: Core / Intra Confederation 进入 BIRD RIB 之后, 会通过内部的 Intra BGP Confederation 在节点之间传播. 此处 Intra Confederation 不止单纯为了处理连通性, 更是为了让内部路由也能携带策略信息. 先前的 OSPF 很适合解决: 这个 loopback 在哪里? 这个 next-hop 怎么到? 但是它无法传递 AS 信息和策略意图, 比如: 这条路由是 IANA peer 学来的, 不能再导给另一个 peer; 这条路由是 DN42 underlay-only, 绝不能泄漏给 eBGP; 这条路由是 downstream customer prefix, 可以导给 upstream; 这些都是 BGP policy 的强项. 所以内部骨干从 OSPF / full-mesh iBGP 转向 confederation-style BGP, 本质上是把内部控制平面升级成了“可携带策略的控制平面”. 第三阶段: Egress / Export 一条路由要离开基础设施时, 会再次经过 export filter. 此处根据 community 可以系统性的防止路由泄露并进行宏观调控如 AS prepend. 比如: DN42 underlay route 带有 underlay-only community, 所以不会被导出到 DN42 eBGP; IANA upstream 学来的路由不会再导给另一个 upstream; IANA peer 学来的路由不会再导给另一个 peer; DN42 IX 学来的路由不会再导回 IX; NO_EXPORT / NO_ADVERTISE 会被尊重; 对外导出前会删除内部 confederation AS 和 private community. 其实总结下来就是这样一张图: flowchart LR classDef ext fill:#eef7ff,stroke:#5b8def,stroke-width:1px; classDef local fill:#f5f5f5,stroke:#888,stroke-width:1px; classDef filter fill:#fff3d6,stroke:#d19a00,stroke-width:1px; classDef core fill:#eaf8ea,stroke:#3c9b43,stroke-width:1px; classDef export fill:#fdecec,stroke:#d45a5a,stroke-width:1px; subgraph SRC["路由来源"] direction TB IU["IANA上游<br/>传输/全量路由"] IP["IANA对等 / IX"] ID["IANA下游<br/>客户前缀"] DT["DN42传输 / eBGP对等"] DX["DN42 IX / 路由服务器"] LU["本地底层环回<br/>100.64.x.y / fd18:...:ffff"] LO["本地覆盖地址<br/>DN42自有 / IANA自有 / 任播"] end subgraph INFRA["Bird2配置基础设施"] direction LR subgraph IMPORT["入口 / 导入过滤器"] direction TB IUF["IANA上游导入<br/>标记: 205369:1:1<br/>标记: 205369:2:102"] IPF["IANA对等导入<br/>标记: 205369:1:1<br/>标记: 205369:2:101"] IDF["IANA下游导入<br/>AS-SET / 路径检查<br/>标记: 205369:2:100"] DTF["DN42 eBGP导入<br/>前缀 / ROA / 自身检查<br/>标记: 4242422024:2:101/102"] DXF["DN42 IX导入<br/>前缀 / ROA检查<br/>标记: 4242422024:2:101"] STF["静态起源<br/>底层/覆盖路由标记"] end META["Large Community 元数据层<br/>域 + 来源 + 作用域<br/>内部路由API"] subgraph CORE["内部控制平面"] direction TB RIB["BIRD路由信息库"] CONFED["BGP联邦内部<br/>成员AS: 422024xxxx<br/>策略: full / default / iana_full / no_iana"] WG["WireGuard底层传输<br/>链路本地下一跳<br/>fwmark策略路由"] UNDERLAY["net_underlay<br/>环回 + 表1142"] OVERLAY["net_overlay<br/>DN42 / IANA服务地址"] end subgraph EGRESS["出口 / 导出过滤器"] direction TB DEXP["DN42 eBGP导出<br/>拒绝 Underlay 路由<br/>遵守no-export/no-advertise"] DXEXP["DN42 IX导出<br/>拒绝从对等/传输学习的路由"] IEXP["IANA上游/对等导出<br/>仅导出本地/下游<br/>拒绝从对等/上游学习的"] DOWNEXP["IANA下游导出<br/>策略: default / own_only / reject"] KEXP["内核导出<br/>按 Community 设置krt_prefsrc"] CLEAN["外部导出前清理<br/>移除联邦 AS<br/>移除私有 Community"] end end subgraph DST["路由去向"] direction TB OD["DN42对等 / 传输"] OX["DN42 IX"] OI["IANA上游 / 对等"] OC["IANA下游"] KF["Linux内核FIB<br/>实际报文转发"] end IU --> IUF IP --> IPF ID --> IDF DT --> DTF DX --> DXF LU --> STF LO --> STF IUF --> META IPF --> META IDF --> META DTF --> META DXF --> META STF --> META META --> RIB RIB <--> CONFED CONFED --- WG WG --- UNDERLAY RIB --- OVERLAY RIB --> DEXP RIB --> DXEXP RIB --> IEXP RIB --> DOWNEXP RIB --> KEXP DEXP --> CLEAN --> OD DXEXP --> CLEAN --> OX IEXP --> CLEAN --> OI DOWNEXP --> CLEAN --> OC KEXP --> KF class IU,IP,ID,DT,DX ext; class LU,LO local; class IUF,IPF,IDF,DTF,DXF,STF filter; class META,RIB,CONFED,WG,UNDERLAY,OVERLAY core; class DEXP,DXEXP,IEXP,DOWNEXP,KEXP,CLEAN export; class OD,OX,OI,OC,KF ext; 自动化: 把网络意图编译成配置 前面讲了这么多 IPAM, Confederation AS, Underlay / Overlay 和 BGP Community, 这些设计如果最后还是靠手写配置维护, 那其实并没有真正解决问题, 只是把复杂度从一种形式搬到了另一种形式. 在这次重构之前, 新增一个节点或者 peer 往往需要同时修改很多地方: WireGuard 配置: interface 名称, listen port, peer public key, endpoint, allowed IPs BIRD 配置: BGP protocol 名称, neighbor 地址, neighbor interface, ASN, import/export filter IPAM 相关配置: loopback, link-local, router id, overlay 地址 部署相关配置: 哪些节点需要渲染, 哪些服务需要重启 一些特殊情况: NAT 后面的节点, passive peer, 特殊 local AS, 特殊 filter 这些东西在逻辑上其实描述的是同一件事: 两个节点之间存在一条属于某个路由域的连接. 但是在手工配置里, 它们会散落在 WireGuard, BIRD, network interface, systemd/openrc 等多个地方. 只要其中一个字段漏改或者不一致, 就可能出现很诡异的问题, 比如 WireGuard 已经起来了, 但是 BIRD neighbor 指向了错误的 interface; 或者 link-local 改了, 但是对端 session 里还写着旧地址. 所以这次重构里, 我不希望 Ansible 只是一个"把 YAML 填进 Jinja2 模板"的工具. 如果只是把原来的手写配置搬进模板, 本质上还是在维护一堆低层配置, 只是换了个文件格式而已. 既然都上自动化了, 那输入不应该是"我要生成一个长什么样的配置文件", 而应该是: 这个节点是谁 它属于哪个 tier / region 它和谁互联 这条连接属于 DN42, IANA 还是 Intra 这条连接应该使用什么 import/export policy 它是否有 NAT, passive, endpoint override 之类的特殊约束 至于具体的 WireGuard interface 怎么写, BIRD session 怎么写, link-local 地址是什么, 这些都应该尽量由自动化系统根据规则生成. Source of Truth: 节点状态而不是配置文件 这套自动化的输入主要放在 Ansible 的 host_vars 里, 每个节点的变量不再只是模板渲染需要的参数, 而是这个节点在网络里的身份描述. 例如一个节点至少会有: tier: 节点层级, 比如 backbone, IX, 普通 node region: 节点所在区域 node_id: 同区域内的节点编号 node_token: 一个稳定的 16-bit token, 用来生成 IPv6 地址尾部 这些字段会被用于 IPAM 的地址管理和 Confederation 内节点的识别信息 (就像前面提到的, AS 计算和 IP 地址派生). 这样做的好处是, 新节点加入时, 我们只需要给这个节点配置好基本的元数据, 它在 underlay 里的所有基础身份就都是确定的. 这可以大量减少后续维护工作. 如果 link-local 地址是手写在每个 BIRD session 里的, 那么一旦节点 token 或地址规则发生变化, 所有对端都要一起改; 但如果 BIRD neighbor 地址是根据 peer node 的 metadata 自动派生出来的, 那么它永远和 source of truth 保持一致. Domain Intent: peer 声明式配置 这次自动化里最重要的抽象是 Domain Intent, 也就是将 Session 和与之对应的 WireGuard 隧道绑定视作一个整体, 再按照所属网络类型 (DN42/IANA/Intra) 进行区域划分. 以 Intra peer 为例, 一条内部连接本质上同时需要两类配置: WireGuard interface, 用来提供传输层隧道 BIRD BGP session, 用来在这条隧道上交换路由 如果手动维护, 这两部分很容易重复描述同样的信息: interface name peer node listen port endpoint neighbor interface BGP protocol name import/export policy 所以我们将常规 Intra peer 抽象成一条 peer intent. 简化之后大概像这样: - node: tyo03-jp interface: intra_tyo03 wireguard: listen_port: 10234 passive: true bgp: protocol: intra_ibgp_tyo03 ipv4: import_policy: full export_policy: full ipv6: import_policy: full export_policy: full 这条声明并不是直接对应某一个配置文件, 而是对 Peer 意图进行描述: 当前节点和 tyo03-jp 之间有一条名为 intra_tyo03 的内部连接, 这条连接使用 WireGuard 承载, 并在其上建立一条 Intra BGP session, BGP policy 使用 full. 之后 Ansible 基于此自动渲染对应 WireGuard 和 BIRD 配置. 这样, 一个 peer 关系只需要描述一次, 后面的低层配置都由自动化生成. 例如 BIRD neighbor 的 link-local 地址不需要手写, 可以通过对端节点的 tier / region / node_token 自动得到; WireGuard endpoint 也可以根据对端 inventory 里的 ansible_host 和对端回程 peer 的 listen_port 自动推导. 这样做最大的优点是可以避免多个配置层之间出现状态不一致. 自动派生 endpoint 与 passive peer 在我的内网架构中, 并不是所有节点都有完全对称的公网可达性, 比如有的节点 / DN42 Peer 在 NAT 后面, 只能主动连出去, 有些隧道需要固定某一端作为监听, 由另一端主动发起连接, 因此 peer intent 里需要支持被动监听这样的特殊情况. 如果一个 peer 不是 passive, 自动化可以尝试从对端 inventory 中读取 ansible_host, 再结合对端声明中的 listen_port, 自动生成 WireGuard endpoint: endpoint = peer_ansible_host + ":" + peer_return_listen_port 如果 peer 标记为 passive, 则不渲染 endpoint, 让对端主动发起连接. 本质上 endpoint 是由"对端如何被访问"和"对端监听哪个端口"共同决定的, 这两个信息本来就已经存在于 inventory 和 peer intent 里, 没必要再复制一遍. 当然, 对于特别奇怪的链路, 保留最基础的手动覆盖 endpoint. 自动化的目标不是消灭所有特殊情况, 而是让常规情况不需要特殊处理. 通用场景 & 例外 其实就是为了特殊情况考虑的 Fallback: 历史遗留 tunnel DN42 上承载的特殊内部 peer NAT 后面的节点 临时 workaround 对端要求奇怪的 BGP 参数 某些 session 需要特殊 local AS 某些路由只想进入 IANA domain, 不应该进入 DN42 domain 最后保留的设计是: 大部分普通 Intra peer 可以通过 domain intent 生成 WireGuard + BIRD, DN42 peer 的 WireGuard 也可以从 DN42 peer intent 里生成, 但是一些特殊 BIRD session, 比如需要单独指定 local_as, 或者需要使用特殊导出策略的 session, 仍然可以继续用低层配置显式声明. 最终渲染前, Ansible 会把生成的配置和 legacy/manual override 合并成最终配置. 这样做的好处是, 自动化覆盖了 90% 重复且容易出错的部分, 同时给剩下 10% 特殊情况也预留了位置. 配置渲染 整个渲染过程大概可以理解成这样: flowchart TD A[host_vars: node metadata + domain intent] --> B[materialize domain intent] B --> C[effective_wireguard_interfaces] B --> D[effective_intra_ibgp_sessions] C --> E[render wg-quick configs] D --> F[render BIRD intra sessions] A --> G[render BIRD root / DN42 / IANA modules] A --> H[render underlay / overlay interfaces] E --> I[.rendered-wireguard] F --> J[.rendered] G --> J H --> K[.rendered-network] 这里有三类最终产物: .rendered/: BIRD 配置 .rendered-wireguard/: WireGuard 配置 .rendered-network/: underlay / overlay dummy interface 配置 BIRD 配置里又分成几个模块, 这部分是通过 Jinja2 Templates 编写的, 实际拆分 Overlay Network 也是通过这里: root config: 全局 define, include, kernel protocol DN42 module: DN42 filter, RPKI, eBGP peer, IX peer IANA module: upstream, downstream, peer, RPKI, static route Intra module: confederation-style BGP session, static route, internal filter 这些模块是否 include, 也尽量由数据自动决定, 比如一个节点没有 DN42 peer, 就不需要渲染和 include ebgp.conf; 没有 IX session, 就不需要 include ix.conf. 这可以减少空配置和无意义 include, 也避免某些节点因为缺少相关变量导致模板渲染失败. 验证 & 部署 最终部署流程还添加了一些检查和验证阶段: render BIRD validate BIRD render WireGuard validate WireGuard deploy dummy interfaces deploy WireGuard deploy BIRD BIRD 配置先在本地渲染和验证, 尽量提前发现语法错误; WireGuard 配置也先渲染和验证, 避免缺 key 或者生成出明显非法的 wg-quick 配置; 最后验证都通过了再部署底层 interfaces + WireGuard, 最后才上传并 reload BIRD. 总结 我不行了这文章给我写力竭了, 这玩意的架构用线性的语言叙述怎么这么难 理顺之后代码基本上都交给 AI 写的, 效果还行, 如需参考可查阅 bird2-config/ansible at dev · iYoRoy-Network/bird2-config. 这套方案的适用情形其实挺窄的, DN42 + IANA 这种 BGP Player 场景还是太小众了 xD. 而且这套方案能正常运行, 很大一部分原因是 IANA 的前缀和 DN42 以及我自己的内网选择的网段完全不冲突, 若 Transit 网段存在冲突的话, 可能还是得考虑 VRF/MPLS L3VPN 这类正儿八经 ISP 会用的隔离方案. 参考文章: Bird 配置 BGP Confederation, 及模拟 Confederation(2020-06-07 更新) - Lan Tian @ Blog 浅谈 BGP 中的 Transit(中转)与 Peering(对等互联) | 网络蝙蝠侠部落
2026年07月24日
53 阅读
0 评论
3 点赞
DN42&OneManISP - 共存环境下的OSPF源地址故障排除
前情提要 正如这个系列的上文所说,因为VRF方案太过于隔离,导致我部署在HKG节点(172.20.234.225)的DNS服务无法被DN42网络所访问,查阅资料得知可以通过设置veth或者NAT地址转发的方式来实现,但是因为现有的资料比较少,最终还是放弃了VRF这个方案。 结构分析 这次我打算将DN42和公网BGP的路由都放入系统的主路由表,然后再分开导出,通过过滤器来区分是否应该导出。同时,为了更加直观,我将DN42部分的配置和公网(以下简称inet)部分的配置分别单独存放,再由主配置文件引入。同时,因为kernel部分配置一个路由表只应该存在一个,因此合并DN42和inet的kernel部分,仅保留一个。 经过多次优化和修改,我最终的目录结构如下: /etc/bird/ ├─envvars ├─bird.conf: Bird主配置文件,负责定义基本信息(ASN、IP等),引入下面的子配置 ├─kernel.conf: 内核配置,负责将路由导入系统路由表 ├─dn42 | ├─defs.conf: DN42的函数定义,如is_self_dn42_net()这类 | ├─ibgp.conf: DN42 iBGP模板 | ├─rpki.conf: DN42 RPKI路由验证 | ├─ospf.conf: DN42 OSPF内网 | ├─static.conf: DN42静态路由 | ├─ebgp.conf: DN42 Peer模板 | ├─ibgp | | └<ibgp configs>: DN42 iBGP各个节点的配置 | ├─ospf | | └backbone.conf: OSPF区域 | ├─peers | | └<ibgp configs>: DN42 Peer各个节点的配置 ├─inet | ├─peer.conf: 公网Peer | ├─ixp.conf: 公网IXP接入 | ├─defs.conf: 公网部分的函数定义,如is_self_inet_v6() | ├─upstream.conf: 公网上游 | └static.conf: 公网静态路由 将定义函数的部分单独拿出来是因为我需要在kernel.conf的过滤器中引用,因此单独拿出来以便于提前include。 完成后分别填入对应配置,然后由写好include关系,birdc configure后发现也成功跑起来了。于是乎告一段落...吗? 发现问题 运行一段时间后,我突然发现通过我的内网设备Ping HKG节点无法Ping通,通过HKG节点Ping我的其他内部节点也无法Ping通。奇怪的是,外部AS可以通过我的HKG节点Ping到我的其他节点或者其他外部AS,我的内部节点也可以通过HKG节点Ping到其他不直接相连的节点(如:226(NKG)->225(HKG)->229(LAX))。 通过ip route get <内网其他节点地址>发现: root@iYoRoyNetworkHKG:/etc/bird# ip route get 172.20.234.226 172.20.234.226 via 172.20.234.226 dev dn42_nkg src 23.149.120.51 uid 0 cache 看出问题了吗?src地址本来应该是HKG节点自己的DN42地址(OSPF部分stub网卡配置的),但是这里显示的却是HKG节点的公网地址。 尝试通过birdc s r for 172.20.234.226读取bird学习到的路由: root@iYoRoyNetworkHKGBGP:/etc/bird/dn42/ospf# birdc s r for 172.20.234.226 BIRD 2.17.1 ready. Table master4: 172.20.234.226/32 unicast [dn42_ospf_iyoroynet_v4 00:30:29.307] * I (150/50) [172.20.234.226] via 172.20.234.226 on dn42_nkg onlink 看起来貌似一切正常...? 理论上来说,虽然DN42的源IP和正常的不太一样,但是DN42在导出到内核的时候改写了krt_prefsrc来告诉内核正确的源地址,理论上不应该出现这样的问题: protocol kernel kernel_v4{ ipv4 { import none; export filter { if source = RTS_STATIC then reject; + if is_valid_dn42_network() then krt_prefsrc = DN42_OWNIP; accept; }; }; } protocol kernel kernel_v6 { ipv6 { import none; export filter { if source = RTS_STATIC then reject; + if is_valid_dn42_network_v6() then krt_prefsrc = DN42_OWNIPv6; accept; }; }; } 关于krt_prefsrc,其含义是Kernel Route Preferred Source。这个属性并非直接操作路由,而是为路由附加一个元数据,它直接告诉 Linux 内核:当通过这条路由发送数据包时,应优先使用这里指定的 IP 地址作为源地址。 在这里卡了好久的说 解决方案 最终,某次无意间尝试给OSPF的导出配置中也加上了krt_prefsrc改写: protocol ospf v3 dn42_ospf_iyoroynet_v4 { router id DN42_OWNIP; ipv4 { - import where is_self_dn42_net() && source != RTS_BGP; + import filter { + if is_self_dn42_net() && source != RTS_BGP then { + krt_prefsrc=DN42_OWNIP; + accept; + } + reject; + }; export where is_self_dn42_net() && source != RTS_BGP; }; include "ospf/*"; }; protocol ospf v3 dn42_ospf_iyoroynet_v6 { router id DN42_OWNIP; ipv6 { - import where is_self_dn42_net_v6() && source != RTS_BGP; + import filter { + if is_self_dn42_net_v6() && source != RTS_BGP then { + krt_prefsrc=DN42_OWNIPv6; + accept; + } + reject; + }; export where is_self_dn42_net_v6() && source != RTS_BGP; }; include "ospf/*"; }; 之后再运行发现src地址正确了,互相Ping也都能通。 配置文件可参考:KaguraiYoRoy/Bird2-Configuration
2025年10月29日
169 阅读
0 评论
1 点赞
DN42探究日记 - Ep.4 配置BGP Communities
写在前面 本人是BGP小白,文章中可能会存在不严谨内容/小白理解/低级错误,请诸位大佬们手下留情。若发现存在问题,您愿意的话可以邮件联系我,我会在第一时间更正。如果您不能接受,建议现在就关闭此文章。 什么是BGP Communities TL;DR: BGP Communities给路由“打标签”,其他人可以通过这个标签实现路由优选 这个概念对于刚入坑的小白 (比如我) 来说可能比较陌生,可能不理解它是干吗用的 简单来说,BGP Communities是一种标记路由的机制,类似于给路由打标签。它允许网络管理员给通过BGP传播的路由附加一个或多个“标签”(即社区值)。这些标签本身并不改变路由的路径属性(如AS_PATH、LOCAL_PREF、MED等),但它们提供了一种信号机制,用于向本AS内或下游对等AS中的其他路由器指示应该对该路由执行何种策略或处理。BGP Communities可以用来: 简化策略配置: 网络内部或下游 AS 的路由器只需配置基于社区值的策略(如设置LOCAL_PREF、添加NO_EXPORT、应用路由图等),无需知道具体的前缀细节。这使得策略更集中、更易管理、更不易出错 传达策略意图给下游AS: 可以将社区值附加在它通告给下游客户或对等AS的路由上。这些社区值传达了对这些路由应如何处理的要求或建议,如根据不同地理位置、不同延迟和带宽来进行路由优选等 在AS内部协调策: 在大型AS内部,IBGP全互联或使用路由反射器时,可以在 AS 边缘路由器(接收 EBGP 路由或重分发路由的路由器)给路由打上社区值,AS内部的核心路由器或路由反射器可以识别这些社区值,并应用相应的内部策略(如设置LOCAL_PREF、MED、决定是否向某些IBGP对等体通告、打上其他社区等),而无需在每个内部路由器上配置复杂的基于前缀的策略 DN42中有一套自己的Communities规范,详情可参考:BGP-communities - DN42 Wiki 配置 本文大致思路和方案来源于Xe_iu大佬,仅针对地理位置信息添加BGP Communities并进行优选。一般来说这样就足够了。(还有个原因是其他的我还没太搞明白) 注意: 我们应该只对自己的AS添加地理位置信息相关的BGP Communities,不应当对邻居传递来的路由添加相关条目。若对邻居的路由加上自己的地区Communities则会造成伪造路由起源,引发路由劫持。下游可能会误判流量路径,将本应直连的流量绕道至你的网络,增加延迟的同时大量消耗你的网络的流量。(Large Communities除外,Large Community有一套验证机制可以防止此类事情发生,但是不在本文讨论范围内) 思路已经很明白了,在导出路由时我们需要先验证是否是我们自己的路由,如果是则为路由打上communities标签即可。DN42官方给的样例配置中已经写好了两个函数is_self_net()和is_self_net_v6()用于检查是否是自己的路由,因此编写配置文件部分就很简单了。 为路由添加Communities 首先我们需要在节点配置文件开头定义好当前节点的地理区域信息,详情请查询BGP-communities - DN42 Wiki: define DN42_REGION = 52; # 52代表亚洲东部地区 define DN42_COUNTRY= 1344; # 1344代表香港 请将上述数值按照你的节点地理位置情况修改 随后在dnpeers模板中修改导出的过滤器: }; export filter { - if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then accept; + if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then{ + if (is_self_net()) then { # 检查是否是自己的路由 + bgp_community.add((64511, DN42_REGION)); # 打上大洲级别的区域信息 + bgp_community.add((64511, DN42_COUNTRY)); # 打上国家/地区信息 + } + accept; + } reject; }; import limit 1000 action block; 在导出时检查,如果是自己的路由则按照上面设置的DN42_REGION和DN42_COUNTRY为该路由设置bgp_community。其中,64511是专门为地理标签(Region/Country)保留的公共AS号标识符,直接照抄即可。在IPv6的导出规则中也如法炮制,不过将is_self_net()换成is_self_net_v6()即可。 根据Communities优选 此处我们需要引入另一个概念:local_pref(Local Preference),他用于在AS内部标识路由的优先级。其默认值为100,值越大优先级越高。并且在BGP选路逻辑中,local_pref有着最高的优先级,甚至高于AS_PATH长度。也就是说,通过设置local_pref我们可以调整整个路由的优先级,以实现路由优选。 再结合上文的BGP Communities,我们可以根据Communities来为路由设置相应的local_pref来实现优选。同时,因为BGP.local_pref会在AS内部传递,所以需要同时更改eBGP和iBGP的路由导入和导出逻辑。 此处我处理BGP.local_pref的逻辑参考 (实际上是照抄) 了Xe_iu大佬的方案: 对于同一个大洲的路由,优先级+10 对于同一个国家,优先级再+5 对于直接和自己Peer的路由,优先级再+20 创建一个函数用于优先级计算: function ebgp_calculate_priority() { int priority = 100; # 基础优先级 # 同区域检测(+10) if bgp_community ~ [(64511, DN42_REGION)] then priority = priority + 10; # 同国家检测(+5) if bgp_community ~ [(64511, DN42_COUNTRY)] then priority = priority + 5; # eBGP直接邻居检测(+20) if bgp_path.len = 1 then priority = priority + 20; return priority; } 接着,在dnpeers模板中的导入过滤器中将bgp_local_pref设置为从函数计算出来的值: template bgp dnpeers { local as OWNAS; path metric 1; ipv4 { import filter { if is_valid_network() && !is_self_net() then { if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then { print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last; reject; } + bgp_local_pref = ebgp_calculate_priority(); accept; } reject; }; IPv6也如法炮制即可。 运行birdc configure之后,我们应该就能在自己的邻居那里看到我们的路由已经被打上了Communities标签: (此处截图来自:https://lg.milu.moe/route_all/hk/172.20.234.224) 特别感谢Nuro Trace大佬和Xe_iu大佬,他们帮助我加深了对BGP Communities的理解并提供了很多帮助 参考文章: [DN42] bird2的配置文件 – Xe_iu's Blog | Xe_iu的杂物间 [DN42] 谈一谈如何配置 BGP community – Xe_iu's Blog | Xe_iu的杂物间 BGP-communities - DN42 Wiki
2025年08月17日
349 阅读
0 评论
2 点赞
DN42探究日记 - Ep.3 在DN42中注册域名并搭建权威DNS
写在前面 本人是BGP小白,文章中可能会存在不严谨内容/小白理解/低级错误,请诸位大佬们手下留情。若发现存在问题,您愿意的话可以邮件联系我,我会在第一时间更正。如果您不能接受,建议现在就关闭此文章。 假设你已经加入了DN42,并且能够正常收发路由表并且访问到DN42内的IP 本文更新日志 {timeline} {timeline-item color="#50BFFF"} 2025年8月3日:文章第一版发布 {/timeline-item} {timeline-item color="#4F9E28"} 2026年3月15日:更新修复一些typo(感谢@Auride大佬的指正) {/timeline-item} {/timeline} 起因 在调试网络的时候发现Ping或traceroute别人的DN42 IP都能显示出来反向解析的域名,能知道路由经过了哪些节点而不是单纯的看IP(如下图),非常一目了然,让别人一眼就看出来你绕路了(逃), 因此打算自己也注册一个DN42域名并搭建权威DNS服务。 查阅了蓝天大佬的这篇文章,他使用了PowerDNS+MySQL主从同步方案,但是我的服务器性能较差(只有1核心1GB内存),因此打算使用KnotDNS作为DNS服务器,用标准区域传输协议(AXFR/IXFR)实现主从同步。 准备工作 {alert type="warning"} 本章及后续章节中提到的域名和IP均为我自己的域名和IP,实际部署时请换成你自己的;文章中尖括号括起来的值需要按你的需求修改 {/alert} 挑选一个自己心仪的域名:yori.dn42,并且计划在三台机器上部署DNS服务器: 172.20.234.225, fd18:3e15:61d0::1, ns1.yori.dn42 172.20.234.227, fd18:3e15:61d0::3, ns2.yori.dn42 172.20.234.229, fd18:3e15:61d0::5, ns3.yori.dn42 其中,ns1.yori.dn42作为主服务器,ns2、ns3作为从服务器。 安装KnotDNS 如果系统的53端口被systemd-resolvd之类的进程占用了,就先将其禁用: systemctl stop systemd-resolved systemctl disable systemd-resolved unlink /etc/resolv.conf echo "nameserver 8.8.8.8" > /etc/resolv.conf 我使用的是Debian12系统,因此使用APT安装: apt install knot knot-dnsutils -y 设置KnotDNS自启动: systemctl enable knot 配置KnotDNS 创建key 首先创建一个key用于同步: keymgr -t key_knsupdate 将输出部分复制下来: # hmac-sha256:key_knsupdate:<your secret> key: - id: key_knsupdate algorithm: hmac-sha256 secret: <your secret> 编辑配置文件 主服务器 编辑/etc/knot/knot.conf,填入如下内容: server: rundir: "/run/knot" user: knot:knot automatic-acl: on listen: [ <监听地址1>@53, <监听地址2>@53, ... ] log: - target: syslog any: info database: storage: "/var/lib/knot" ### 此处粘贴上一步生成的Key # hmac-sha256:key_knsupdate:<your secret> key: - id: key_knsupdate algorithm: hmac-sha256 secret: <your secret> remote: - id: <1号DNS服务器ID> address: <1号DNS服务器IP>@53 - id: <2号DNS服务器ID> address: <2号DNS服务器IP>@53 - id: <3号DNS服务器ID> address: <3号DNS服务器IP>@53 acl: - id: acl_slave key: key_knsupdate action: transfer - id: acl_master key: key_knsupdate action: notify - id: acl_knsupdate key: key_knsupdate action: update template: - id: default storage: "/var/lib/knot" file: "%s.zone" zone: - domain: <DN42 域名> notify: [ <从服务器1ID>, <从服务器2ID> ] acl: [ acl_slave, acl_knsupdate ] - domain: <IPv4反向解析域名> notify: [ <从服务器1ID>, <从服务器2ID> ] acl: [ acl_slave, acl_knsupdate ] - domain: <IPv6反向解析域名> notify: [ <从服务器1ID>, <从服务器2ID> ] acl: [ acl_slave, acl_knsupdate ] 其中,监听地址需要填写本机的DN42 IPv4和DN42 IPv6,如果需要本地调试可再加上127.0.0.1和localhost等内网IP 从服务器ID即remote里设置的服务器的ID,你选定哪台(哪些)机器作为从服务器就填写哪台(哪些)服务器的ID remote中的address可以填写内网地址或者DN42 IPv4或者DN42 IPv6,仅用于同步主从服务器。如果使用内网地址请将地址加到监听列表中 template中设置了将Zone文件存储在/var/lib/knot下 IPv4反向解析域名按照你申请的IPv4段填写,遵循RFC 2317规定的格式,如我的IPv4段是172.20.234.224/28,我的IPv4反向解析域名应该为224/28.234.20.172.in-addr.arpa,即将IPv4的最后一段225/28看作一个整体,剩下的按照点来分隔,再将各个部分倒序拼接,最后加上.in-addr.arpa IPv6反向解析域名按照你申请的IPv6段填写,遵循RFC 3152规定的格式,如我的IPv6段是fd18:3e15:61d0::/48,我的IPv6反向解析域名应该为0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa,即将地址块中的各个字符倒序拼接,最后加上.ip6.arpa。如果有0需要补全。 {collapse} {collapse-item label="示例"} server: rundir: "/run/knot" user: knot:knot automatic-acl: on listen: [ 172.20.234.225@53, fd18:3e15:61d0::1@53, localhost@53, 127.0.0.1@53 ] log: - target: syslog any: info database: storage: "/var/lib/knot" # hmac-sha256:key_knsupdate:<key> key: - id: key_knsupdate algorithm: hmac-sha256 secret: <key> remote: - id: 225 # 主服务器 address: 172.20.234.225@53 - id: 227 # 从服务器 address: 172.20.234.227@53 - id: 229 # 从服务器 address: 172.20.234.229@53 acl: - id: acl_slave key: key_knsupdate action: transfer - id: acl_master key: key_knsupdate action: notify - id: acl_knsupdate key: key_knsupdate action: update template: - id: default storage: "/var/lib/knot" file: "%s.zone" zone: - domain: yori.dn42 notify: [ 227, 229 ] acl: [ acl_slave, acl_knsupdate ] - domain: 224/28.234.20.172.in-addr.arpa notify: [ 227, 229 ] acl: [ acl_slave, acl_knsupdate ] - domain: 0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa notify: [ 227, 229 ] acl: [ acl_slave, acl_knsupdate ] # # Secondary zone # - domain: example.net # master: primary {/collapse-item} {/collapse} 从服务器 从服务器的大致内容和主服务器相同,只需要将监听地址改成从服务器的地址,并且将zone部分配置修改一下: --- a/knot.conf +++ b/knot.conf zone: - domain: <DN42 域名> - notify: [ <从服务器1ID>, <从服务器2ID> ] - acl: [ acl_slave, acl_knsupdate ] + master: <主服务器ID> + zonefile-load: whole + acl: acl_master - domain: <IPv4反向解析域名> - notify: [ <从服务器1ID>, <从服务器2ID> ] - acl: [ acl_slave, acl_knsupdate ] + master: <主服务器ID> + zonefile-load: whole + acl: acl_master - domain: <IPv6反向解析域名> - notify: [ <从服务器1ID>, <从服务器2ID> ] - acl: [ acl_slave, acl_knsupdate ] + master: <主服务器ID> + zonefile-load: whole + acl: acl_master 主服务器ID即remote里设置的服务器的ID,你选定哪台机器作为主服务器就填写哪台服务器的ID {collapse} {collapse-item label="样例"} server: rundir: "/run/knot" user: knot:knot automatic-acl: on listen: [ 172.20.234.227@53, fd18:3e15:61d0::3@53, localhost@53, 127.0.0.1@53 ] log: - target: syslog any: info database: storage: "/var/lib/knot" # hmac-sha256:key_knsupdate:<key> key: - id: key_knsupdate algorithm: hmac-sha256 secret: <key> remote: - id: 225 address: 172.20.234.225@53 - id: 227 address: 172.20.234.227@53 - id: 229 address: 172.20.234.229@53 acl: - id: acl_slave key: key_knsupdate action: transfer - id: acl_master key: key_knsupdate action: notify - id: acl_knsupdate key: key_knsupdate action: update template: - id: default storage: "/var/lib/knot" file: "%s.zone" zone: - domain: yori.dn42 master: 225 zonefile-load: whole acl: acl_master - domain: 224/28.234.20.172.in-addr.arpa master: 225 zonefile-load: whole acl: acl_master - domain: 0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa master: 225 zonefile-load: whole acl: acl_master {/collapse-item} {/collapse} 编写完配置文件后运行如下指令重启KnotDNS: systemctl restart knot 编辑Zone区域文件 此章节中的记录值(非主机名)若需要填写域名,除了特殊说明外,都请遵循RFC 1034规范填写FQDN格式。此章节中的所有配置均在主DNS服务器上完成 DN42域名 进入/var/lib/knot,创建文件<dn42域名>.zone SOA记录 域名的第一条记录必须为SOA记录,SOA记录是起始授权记录,记录了域名的一些基本信息如主要NS服务器地址。填入如下内容: @ <TTL> SOA <主要NS服务器地址> <联系人邮件> <记录编号> <AXFR刷新时间> <AXFR重试时间> <AXFR过期时间> <最小TTL> @表示是当前域名本身,不用修改 TTL: 当前SOA记录的TTL值 主要NS服务器地址: 当前域名的主要权威NS服务器地址,可以是域名内的解析值,如我的主要NS服务器是172.20.234.225,我打算使用ns1.yori.dn42.指向此地址,那么此处可填写ns1.yori.dn42. 联系人邮件: 邮箱地址,并且用.代替@,如我的邮箱是i@iyoroy.cn,那么此处可填写i.iyoroy.cn 记录编号: 一个10位数字,遵循RFC 1912,表示Zone文件的版本号。其他DNS服务器在获取SOA后发现序列号增加了就会重新拉取新的记录。一般使用日期+编号的方式编码,因此此项值应该在每次修改后递增。 AXFR刷新时间: AXFR从服务器两次拉取的间隔 AXFR重试时间: AXFR从服务器拉取失败后重试时间 AXFR过期时间: AXFR从服务器拉取失败后,最多用先前最后一次拉取成功的记录继续提供服务这么长时间,之后停止应答 最小TTL: 当前整个域名的最小TTL值,所有记录的最小刷新时间,至少过了这么长时间才会刷新 {collapse} {collapse-item label="样例"} ; SOA @ 3600 SOA ns1.yori.dn42. i.iyoroy.cn. 2025072705 60 60 1800 60 {/collapse-item} {/collapse} NS记录 @ <TTL> NS <NS服务器1> @ <TTL> NS <NS服务器2> @ <TTL> NS <NS服务器3> 根据你的实际情况填写,有几台服务器就填写几条记录。 {collapse} {collapse-item label="样例"} ; NS @ 3600 NS ns1.yori.dn42. @ 3600 NS ns2.yori.dn42. @ 3600 NS ns3.yori.dn42. {/collapse-item} {/collapse} A、AAAA、CNAME等记录 按照如下格式填写即可: <主机名> <TTL> <类型> <记录值> 如果你的NS服务器值指向了你自己的DN42域名的主机,请务必为其添加A类型或者AAAA类型的解析记录 {collapse} {collapse-item label="样例"} ; A ns1 600 A 172.20.234.225 ns2 600 A 172.20.234.227 ns3 600 A 172.20.234.229 hkg-cn.node 600 A 172.20.234.225 nkg-cn.node 600 A 172.20.234.226 tyo-jp.node 600 A 172.20.234.227 hfe-cn.node 600 A 172.20.234.228 lax-us.node 600 A 172.20.234.229 ; AAAA ns1 600 AAAA fd18:3e15:61d0::1 ns2 600 AAAA fd18:3e15:61d0::3 ns3 600 AAAA fd18:3e15:61d0::5 hkg-cn.node 600 AAAA fd18:3e15:61d0::1 nkg-cn.node 600 AAAA fd18:3e15:61d0::2 tyo-jp.node 600 AAAA fd18:3e15:61d0::3 hfe-cn.node 600 AAAA fd18:3e15:61d0::4 lax-us.node 600 AAAA fd18:3e15:61d0::5 {/collapse-item} {collapse-item label="完整样例"} /var/lib/knot/yori.dn42.zone ; SOA @ 3600 SOA ns1.yori.dn42. i.iyoroy.cn. 2025072705 60 60 1800 60 ; NS @ 3600 NS ns1.yori.dn42. @ 3600 NS ns2.yori.dn42. @ 3600 NS ns3.yori.dn42. ; A ns1 600 A 172.20.234.225 ns2 600 A 172.20.234.227 ns3 600 A 172.20.234.229 hkg-cn.node 600 A 172.20.234.225 nkg-cn.node 600 A 172.20.234.226 tyo-jp.node 600 A 172.20.234.227 hfe-cn.node 600 A 172.20.234.228 lax-us.node 600 A 172.20.234.229 ; AAAA ns1 600 AAAA fd18:3e15:61d0::1 ns2 600 AAAA fd18:3e15:61d0::3 ns3 600 AAAA fd18:3e15:61d0::5 hkg-cn.node 600 AAAA fd18:3e15:61d0::1 nkg-cn.node 600 AAAA fd18:3e15:61d0::2 tyo-jp.node 600 AAAA fd18:3e15:61d0::3 hfe-cn.node 600 AAAA fd18:3e15:61d0::4 lax-us.node 600 AAAA fd18:3e15:61d0::5 {/collapse-item} {/collapse} IPv4反向解析域名 在/var/lib/knot下创建文件<IPv4反向解析域名>.in-addr.arpa,并用_代替/。如我的IPv4段是172.20.234.224/28,我的IPv4反向解析域名是224/28.234.20.172.in-addr.arpa,那么此处文件名为224_28.234.20.172.in-addr.arpa.zone。填入解析记录: ; SOA @ <TTL> SOA <主要NS服务器地址> <联系人邮件> <记录编号> <AXFR刷新时间> <AXFR重试时间> <AXFR过期时间> <最小TTL> ; NS @ <TTL> NS <NS服务器1> @ <TTL> NS <NS服务器2> @ <TTL> NS <NS服务器3> ; PTR <IPv4地址最后一位> <TTL> PTR <反向解析DNS值> <IPv4地址最后一位> <TTL> PTR <反向解析DNS值> <IPv4地址最后一位> <TTL> PTR <反向解析DNS值> ... SOA和NS记录和上方相同 IPv4地址最后一位: 你给设备绑定的DN42 IPv4地址四段中的最后一段,如我的HK节点分配的172.20.234.225地址,那么此处就填写`225 {collapse} {collapse-item label="样例"} 224_28.234.20.172.in-addr.arpa.zone ; SOA @ 3600 SOA ns1.yori.dn42. i.iyoroy.cn. 2025072802 60 60 1800 60 ; NS @ 3600 NS ns1.yori.dn42. @ 3600 NS ns2.yori.dn42. @ 3600 NS ns3.yori.dn42. ; PTR 225 600 PTR hkg-cn.node.yori.dn42. 226 600 PTR nkg-cn.node.yori.dn42. 227 600 PTR tyo-jp.node.yori.dn42. 228 600 PTR hfe-cn.node.yori.dn42. 229 600 PTR lax-us.node.yori.dn42. {/collapse-item} {/collapse} 你可能会疑惑为何需要带上CIDR掩码,这与Clearnet中常见的、按八位组倒序的格式(如234.20.172.in-addr.arpa)不同;以及如果你尝试本地测试,你会发现直接测试你自己的IP地址的反解会失败。 原因在于DN42的分布式注册机制:单个区域文件无法覆盖你的地址段的所有反向查询入口点(即每个具体IP地址对应的.in-addr.arpa名称)。为了解决这个问题,DN42官方DNS会在你的PR被合并后,在其权威DNS服务器上为你的地址段添加CNAME重定向,将单个IP的PTR记录指向你的带CIDR的格式,如下: ~$ dig PTR 225.234.20.172.in-addr.arpa +short 225.224/28.234.20.172.in-addr.arpa. # <-- Registry 添加的 CNAME (重定向) hkg-cn.node.yori.dn42. # <-- DNS 返回的最终 PTR 记录 当外部解析器查询某个具体IP (如172.20.234.225) 的反向记录时(查询225.234.20.172.in-addr.arpa),官方DNS会返回一个CNAME记录,将其指向CIDR的区域名称下的具体记录(225.224/28.234.20.172.in-addr.arpa)。最终,PTR 记录由你配置的权威 DNS 服务器提供。 IPv6反向解析域名 在/var/lib/knot下创建文件<IPv6反向解析域名>.ip6.arpa。如我的IPv6段是fd18:3e15:61d0::/48,我的IPv6反向解析域名是0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa,那么此处文件名为0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa.zone。填入解析记录: ; SOA @ <TTL> SOA <主要NS服务器地址> <联系人邮件> <记录编号> <AXFR刷新时间> <AXFR重试时间> <AXFR过期时间> <最小TTL> ; NS @ <TTL> NS <NS服务器1> @ <TTL> NS <NS服务器2> @ <TTL> NS <NS服务器3> ; PTR <最后20字符反向序列> <TTL> PTR <反向解析DNS值> <最后20字符反向序列> <TTL> PTR <反向解析DNS值> <最后20字符反向序列> <TTL> PTR <反向解析DNS值> ... SOA和NS记录处理方式同上 PTR的主机名,需要你将主机IPv6地址移除/48前缀后的80位部分展开为20个十六进制字符,并倒序排列这些字符并用点分隔。如我的香港节点IPv6是fd18:3e15:61d0::1,展开就是fd18:3e15:61d0:0000:0000:0000:0000:0001,此处主机名就填写1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 {collapse} {collapse-item label="样例"} 0.d.1.6.5.1.e.3.8.1.d.f.ip6.arpa.zone ; SOA @ 3600 SOA ns1.yori.dn42. i.iyoroy.cn. 2025072802 60 60 1800 60 ; NS @ 3600 NS ns1.yori.dn42. @ 3600 NS ns2.yori.dn42. @ 3600 NS ns3.yori.dn42. ; PTR 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR hkg-cn.node.yori.dn42. 2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR nkg-cn.node.yori.dn42. 3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR tyo-jp.node.yori.dn42. 4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR hfe-cn.node.yori.dn42. 5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 600 PTR lax-us.node.yori.dn42. {/collapse-item} {/collapse} 验证设置 全部保存后在每台DNS服务器上都运行一次knot reload重载,不出意外的话应该能看到从服务器同步了主服务器的zone文件,此时通过dig或者nslookup指定服务器查询应该能查到解析记录了 注册 域名 克隆下DN42 Registry,进入data/dns,新建文件<你打算注册的域名>,填入如下内容: domain: <你打算注册的域名> admin-c: <管理员NIC句柄> tech-c: <技术人员NIC句柄> mnt-by: <维护者> nserver: <NS1服务器域名> <NS1服务器IP> nserver: <NS2服务器域名> <NS2服务器IP> nserver: <NS3服务器域名> <NS3服务器IP> ... source: DN42 admin-c、tech-c、mnt-by请参考DN42探究日记 - Ep.1 加入DN42网络 {collapse} {collapse-item label="样例"} data/dns/yori.dn42 domain: yori.dn42 admin-c: IYOROY-DN42 tech-c: IYOROY-DN42 mnt-by: IYOROY-MNT nserver: ns1.yori.dn42 172.20.234.225 nserver: ns1.yori.dn42 fd18:3e15:61d0::1 nserver: ns2.yori.dn42 172.20.234.227 nserver: ns2.yori.dn42 fd18:3e15:61d0::3 nserver: ns3.yori.dn42 172.20.234.229 nserver: ns3.yori.dn42 fd18:3e15:61d0::5 source: DN42 {/collapse-item} {/collapse} IPv4反向解析域名 进入data/inetnum,找到你注册的地址块,加nserver字段,填写为你自己的DNS服务器: nserver: <你的DNS服务器地址> nserver: <你的DNS服务器地址> ... {collapse} {collapse-item label="样例"} diff --git a/data/inetnum/172.20.234.224_28 b/data/inetnum/172.20.234.224_28 index 50c800945..5ad60e23d 100644 --- a/data/inetnum/172.20.234.224_28 +++ b/data/inetnum/172.20.234.224_28 @@ -8,3 +8,6 @@ tech-c: IYOROY-DN42 mnt-by: IYOROY-MNT status: ASSIGNED source: DN42 +nserver: ns1.yori.dn42 +nserver: ns2.yori.dn42 +nserver: ns3.yori.dn42 {/collapse-item} {/collapse} IPv6反向解析域名 进入data/inet6num,找到你注册的地址块,加nserver字段,填写为你自己的DNS服务器: nserver: <你的DNS服务器地址> nserver: <你的DNS服务器地址> ... {collapse} {collapse-item label="样例"} diff --git a/data/inet6num/fd18:3e15:61d0::_48 b/data/inet6num/fd18:3e15:61d0::_48 index 53f0de06d..1ae067b00 100644 --- a/data/inet6num/fd18:3e15:61d0::_48 +++ b/data/inet6num/fd18:3e15:61d0::_48 @@ -8,3 +8,6 @@ tech-c: IYOROY-DN42 mnt-by: IYOROY-MNT status: ASSIGNED source: DN42 +nserver: ns1.yori.dn42 +nserver: ns2.yori.dn42 +nserver: ns3.yori.dn42 {/collapse-item} {/collapse} 提交PR,等待合并 填写完后推送并提交Pull Request。因为DN42中人人都可以建立递归DNS,DNS配置完全生效可能要一周左右。虽然我实测合并后半天以内公共DNS(172.20.0.53)就已经能查询到我的记录了 特别感谢たのしい大佬,让我明白了DN42中IPv4反向解析与公网的不同之处 参考文章: https://www.haiyun.me/archives/1398.html https://www.jianshu.com/p/7d69ec2976c7 https://www.potat0.cc/posts/20220726/Register_DN42_Domain/ https://bbs.csdn.net/topics/393775423 https://blog.snorlax.blue/knot-reverse-dns-kickstart/ http://www.kkdlabs.jp/dns/automatic-dnssec-signing-by-knot-dns/ https://lantian.pub/article/modify-website/register-own-domain-in-dn42.lantian/ https://datatracker.ietf.org/doc/html/rfc2317 https://datatracker.ietf.org/doc/html/rfc3152 https://datatracker.ietf.org/doc/html/rfc1912#section-2.2
2025年08月03日
245 阅读
0 评论
2 点赞
DN42探究日记 - Ep.2 通过OSPF搭建内部网络并启用iBGP
写在前面 本人是BGP小白,文章中可能会存在不严谨内容/小白理解/低级错误,请诸位大佬们手下留情。若发现存在问题,您愿意的话可以邮件联系我,我会在第一时间更正。如果您不能接受,建议现在就关闭此文章。 本文更新日志 {timeline} {timeline-item color="#50BFFF"} 2025年7月22日:文章第一版发布,使用VXLAN over WireGuard隧道 {/timeline-item} {timeline-item color="#50BFFF"} 2025年7月25日:更新隧道方案,使用type ptp;以支持直接通过WireGuard传输OSPF流量(特别感谢Nuro Trance大佬指导!) {/timeline-item} {timeline-item color="#50BFFF"} 2025年8月8日:添加iBGP部分的解释和配置 {/timeline-item} {timeline-item color="#4F9E28"} 2025年8月27日:更新节点拓扑结构图 {/timeline-item} {/timeline} 为什么需要内部路由 当节点数量增多,我们需要一个合适的方式处理自己AS的内部路由。因为BGP路由只负责将数据包路由到AS,这就导致了一个问题:假如我有A、B两个节点都与别人peer,但是在路由器看来这两台设备同属于一个AS,从A节点发出的请求回包可能被回到B上。这个时候,如果没有做内部路由,则A会无法收到回包。因此,我们需要保证自家内网各个设备之间都能连通。常见的几种方式如下: 通过ZeroTier等组网工具:这种方式较为简单,只需要在每个节点上配置一个客户端即可实现各个节点之间的P2P连接。 通过WireGuard等P2P工具手动建立$\frac{n(n-1)}{2}$条隧道,效果同1,但是节点一多工作量呈平方级上升 通过WireGuard等P2P工具手动建立<$\frac{n(n-1)}{2}$条隧道,再通过OSPF、Babel等内网寻路协议建立内网路由,优点是较为灵活,易于后期添加新节点,缺点就是较为危险,容易配置错误引爆DN42。 因此我决定冒险一下 节点拓扑 graph LR A[HKG<br>172.20.234.225<br>fd18:3e15:61d0::1] B[NKG<br>172.20.234.226<br>fd18:3e15:61d0::2] C[TYO<br>172.20.234.227<br>fd18:3e15:61d0::3] D[FRA<br>172.20.234.228<br>fd18:3e15:61d0::4] E[LAX<br>172.20.234.229<br>fd18:3e15:61d0::5] B <--> A C <--> A A <--> E A <--> D C <--> D C <--> E D <--> E 更新Bird2至v2.16及以上 因为我希望使用IPv6 Link-Local地址传递IPv4的OSPF数据,而Bird在2.16及以后才支持这项功能,因此需要更新至v2.16。如果你不希望更新,可以先跳过此步骤。 使用以下指令安装最新版本的Bird2: sudo apt update && sudo apt -y install apt-transport-https ca-certificates wget lsb-release sudo wget -O /usr/share/keyrings/cznic-labs-pkg.gpg https://pkg.labs.nic.cz/gpg echo "deb [signed-by=/usr/share/keyrings/cznic-labs-pkg.gpg] https://pkg.labs.nic.cz/bird2 $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/cznic-labs-bird2.list sudo apt update && sudo apt install bird2 -y 配置隧道 [Interface] PrivateKey = <本地WireGuard私钥> ListenPort = <监听端口> Table = off Address = <IPv6 LLA>/64 PostUp = sysctl -w net.ipv6.conf.%i.autoconf=0 [Peer] PublicKey = <对端公钥> Endpoint = <对端公网接入点> AllowedIPs = 10.0.0.0/8, 172.20.0.0/14, 172.31.0.0/16, fd00::/8, fe00::/8, ff02::5 ff02::5是OSPFv3路由器专用的链路本地范围组播地址,需要添加进AllowedIPs。 如果你正在使用v2.16以前的Bird,请再为隧道配置一个IPv4地址,不一定非要是DN42 IPv4,其他私有地址也可以。请参考: {collapse} {collapse-item label="包含IPv4的WireGuard配置示例"} [Interface] PrivateKey = <本地WireGuard私钥> ListenPort = <监听端口> Table = off Address = <IPv6 LLA>/64 PostUp = ip addr add 100.64.0.225/32 peer 100.64.0.226/32 dev %i PostUp = sysctl -w net.ipv6.conf.%i.autoconf=0 [Peer] PublicKey = <对端公钥> Endpoint = <对端公网接入点> AllowedIPs = 10.0.0.0/8, 172.20.0.0/14, 100.64.0.0/16, 172.31.0.0/16, fd00::/8, fe00::/8, ff02::5 请将100.64.0.225、100.64.0.226替换为你本机的IPv4和对端的IPv4,并且记得加入AllowedIPs。 {/collapse-item} {/collapse} 启用OSPF 默认你已经配置好了如上一篇文章所写的bird基础配置 在/etc/bird下新建一个名为ospf.conf的文件,填入如下内容: protocol ospf v3 <name> { ipv4 { import where is_self_net() && source != RTS_BGP; export where is_self_net() && source != RTS_BGP; }; include "/etc/bird/ospf/*"; }; protocol ospf v3 <name> { ipv6 { import where is_self_net_v6() && source != RTS_BGP; export where is_self_net_v6() && source != RTS_BGP; }; include "/etc/bird/ospf/*"; }; 理论上来说应该使用OSPF v2处理IPv4,但是因为需要通过IPv6 LLA地址通信IPv4,因此此处IPv4也使用OSPF v3。 过滤规则保证只有本网段内的路由能够通过OSPF传递,并且过滤掉外部BGP协议的路由 千万不要随意使用import all;export all;,有可能会导致路由劫持并影响到整个DN42网络。OSPF只应该处理网段内部的路由。 {collapse} {collapse-item label="配置示例"} /etc/bird/ospf.conf protocol ospf v3 dn42_iyoroynet_ospf { ipv4 { import where is_self_net() && source != RTS_BGP; export where is_self_net() && source != RTS_BGP; }; include "/etc/bird/ospf/*"; }; protocol ospf v3 dn42_iyoroynet_ospf6 { ipv6 { import where is_self_net_v6() && source != RTS_BGP; export where is_self_net_v6() && source != RTS_BGP; }; include "/etc/bird/ospf/*"; }; {/collapse-item} {/collapse} 接着,新建/etc/bird/ospf文件夹,在其中创建area配置文件(如:/etc/bird/ospf/0.conf),填写区域信息: area 0.0.0.0 { interface "<DN42 dummy网卡>" { stub; }; interface "<wg0网卡名称>" { cost 80; # 按照你的网络情况修改 type ptp; }; interface "<wg1网卡名称>" { cost 100; # 按照你的网络情况修改 type ptp; }; # 以此类推 }; 0.0.0.0区域代表骨干网 此处dummy网卡指上一篇文章中所写的DN42虚拟网卡 cost值本应该是用于开销计算,但在DN42这种对带宽要求不大而对延迟较为敏感的场景下可以直接填写延迟,OSPF会自动走开销值之和最短的路由。 {collapse} {collapse-item label="配置示例"} /etc/bird/ospf/0.conf area 0.0.0.0 { interface "dn42" { stub; }; interface "dn42_hkg" { cost 80; type ptp; }; interface "dn42_hfe" { cost 150; type ptp; }; interface "dn42_lax"{ cost 100; type ptp; }; }; {/collapse-item} {/collapse} 最后,打开/etc/bird/bird.conf,在末尾引入OSPF的配置文件: include "ospf.conf"; 运行birdc configure,然后birdc show protocols应该就能看到OSPF的状态是Running了。如果不是,请检查配置步骤是否出错。 此时,在非直连的两台机器上互相ping应该就能通了: 配置iBGP 在从多个地点建立对等连接之前,您的各个节点必须首先完整掌握自身网络的拓扑结构。除了所有外部 BGP 连接外,这还需要配置另一个关键组件:内部 BGP(即 iBGP)。 必要性 iBGP可以保证AS内部所有运行BGP的路由器都能获知到达外部目的地的完整BGP路由信息,进而确保: 内部路由器可以选择最优的出口路径。 流量能够被正确地引导到负责连接特定外部网络的边界路由器。 即使存在多个边界路由器连接到同一个外部网络,内部路由器也能根据策略选择最佳出口。 相比于在AS内部使用默认路由指向边界路由器,iBGP提供了精确的外部路由信息,使得内部路由器能做出更智能的转发决策。 缺点与解决方案 为了防止路由信息在AS内部无控制地扩散导致环路,iBGP路由器不会将从某个iBGP邻居学到的路由再通告给其他iBGP邻居,这就要求传统的iBGP要求在同一个AS内所有运行iBGP的路由器之间必须建立全网状的iBGP邻居关系(Full Mesh)。(还是要建立$\frac{n(n+1)}{2}$条连接 ,没办法。不过OSPF起来之后iBGP配置还是比配置隧道简单的 ) 解决方案就是: 使用路由反射器(Route Reflector, RR): 由RR路由器管理整个AS内部所有的路由信息,缺点就是RR路由器故障将会导致整个网络瘫痪(这很不Decentralized) 通过BGP联盟(BGP Confederation) 搭建内部网络: 将AS内的路由器虚拟成一个个子AS,再把各个路由器之间的连接当作BGP处理,最后向外传递的时候抹去内部AS的路由路径。 后面两种方案我没有尝试过,下面是一些可能有用的参考文章。本文着重讨论iBGP的配置。 DN42 实验网络介绍及注册教程(2022-12 更新) - Lan Tian @ Blog Bird 配置 BGP Confederation,及模拟 Confederation(2020-06-07 更新) - Lan Tian @ Blog 编写iBGP配置文件 在/etc/bird下新建文件ibgp.conf,填入如下内容: template bgp ibgpeers { local as OWNAS; ipv4 { import where source = RTS_BGP && is_valid_network() && !is_self_net(); export where source = RTS_BGP && is_valid_network() && !is_self_net(); next hop self; extended next hop; }; ipv6 { import where source = RTS_BGP && is_valid_network_v6() && !is_self_net_v6(); export where source = RTS_BGP && is_valid_network_v6() && !is_self_net_v6(); next hop self; }; }; include "ibgp/*"; 导入和导出规则确保iBGP仅处理BGP协议学到的路由,并且过滤掉IGP的路由防止环回 next hop self是必须的,指示 BIRD 在向 iBGP 邻居导出路由时,将下一跳重写为边界路由器自身的IP地址(而非原始的外部下一跳)。因为内部路由器无法直接访问外部邻居地址,若不重写则会被认定为地址不可达。重写后,内部路由器只需通过 IGP 路由将流量送至边界路由器,由边界路由器完成最终的外部转发。 因为我希望使用IPv6地址建立MP-BGP,通过IPv6路由IPv4,因此在IPv4中启用了extended next hop 接着创建/etc/bird/ibgp文件夹,在其中为每台节点都创建一个iBGP Peer配置文件: protocol bgp 'dn42_ibgp_<节点>' from ibgpeers{ neighbor <对应节点的IPv6 ULA地址> as OWNAS; }; {collapse} {collapse-item label="样例"} /etc/bird/ibgp/hkg.conf: protocol bgp 'dn42_ibgp_HKG' from ibgpeers{ neighbor fd18:3e15:61d0::1 as OWNAS; }; {/collapse-item} {/collapse} 注意:每个节点上都需要建立(n-1)个iBGP连接,保证和AS内其他所有机器都建立连接,这也是为什么需要使用ULA地址 使用ULA地址确保即使两个节点之间的WireGuard断开,iBGP仍然能通过OSPF建立的内部路由建立,否则将会导致整个内部网络的崩溃 最后,在/etc/bird/bird.conf中加入对ibgp.conf的引入: include "ibgp.conf"; 并运行birdc configure应用配置即可。 参考文章: BIRD 与 BGP 的新手开场 - 海上的宫殿 萌新入坑 DN42 之 —— 基于 tailscale + vxlan + OSPF 的组网 – 米露小窝 使用 Bird2 配置 WireGuard + OSPF 实现网络的高可用 | bs' realm DN42 实验网络介绍及注册教程(2022-12 更新) - Lan Tian @ Blog 如何引爆 DN42 网络(2023-05-12 更新) - Lan Tian @ Blog Bird 配置 BGP Confederation,及模拟 Confederation(2020-06-07 更新) - Lan Tian @ Blog 深入解析OSPF路径开销、优先级和计时器 - 51CTO New release 2.16 | BIRD Internet Routing Daemon 第一章·第二节 如何在 Linux 上安装最新版本的 BIRD? | BIRD 中文文档 [DN42] 使用 OSPF ptp 搭建内网与IBGP配置 – Xe_iu's Blog | Xe_iu的杂物间 [译] dn42 多服务器环境中的 iBGP 与 IGP 配置 | liuzhen932 的小窝
2025年07月22日
502 阅读
1 评论
3 点赞
1
2