Skip to content

Control Server —— 自动对等 API(用户面)

源码:apps/control-server/app/api/v1/autopeering/。前缀 /control/v1/autopeering,下文表格的「路径」列省略该前缀。

这是面向其他 DN42 运营者的自助对等接口,与管理面平级但信任域完全分开:鉴权凭据是门户会话令牌(按会话内 ASN 划权)或 auth-server 签发的会话 JWT,两者都打不进 Admin API。用户旅程的实现原理见 自动对等,运维侧的供给策略见 管理面 API

生产前端在 https://peering.natlan.io,身份提供方是 https://auth.natlan.io


会话模型

浏览器只持有门户会话令牌,上游 OIDC 的 access / refresh token 留在服务端行里。整条闭环:

前端 → GET /autopeering/auth/login-url → 跳 auth.natlan.io 授权页
     → 用户完成 ASN 归属验证并批准
     → auth-server 回调 /autopeering/auth/callback(服务端换令牌 + 验 ID Token)
     → 302 回 {frontend_base_url}/auth/complete?handoff=<一次性码>
     → 前端 POST /autopeering/auth/exchange 用交接码换门户会话令牌

RP 凭据([portal] 配置段)未配齐时,登录端点 fail-closed 503,控制面其余功能不受影响。

方法路径鉴权说明
GET/auth/login建登录事务并 302 跳授权页。?next= 记住登录后要回的站内相对路径;绝对 URL 与协议相对 URL 一律丢弃(防开放重定向)
GET/auth/login-url同上但返回 JSON { authorize_url },供 SPA 自己控制跳转时机
GET/auth/callbackOIDC 回调:换令牌 → 验 ID Token(含 nonce 绑定)→ 建门户会话 → 302 回前端;失败也回前端并带 error=
POST/auth/exchange一次性交接码换门户会话令牌 + 身份摘要;兑换即轮换令牌并作废交接码
GET/me门户会话令牌当前登录身份(ASN / 维护者 / 起源前缀,取自缓存 claims)+ expires_at
POST/auth/refresh-claims门户会话令牌现查上游 userinfo 刷新 dn42 claim;上游 access token 过期时先用 refresh token 静默换新
POST/auth/logout门户会话令牌吊销门户会话(auth-server 侧登录态不受影响)
GET/session会话 JWT直接用 auth-server 挑战流签发的会话 JWT 回显身份,面向不走浏览器的 API 调用方。返回 { asn, mntner, method, sub, expires_at }

/me/session 是两条不同凭据的回显端点,不可互换。/session 走 JWKS 本地无状态验签,验证热路径不打网络、不依赖 auth-server 在线;未配置 [auth_service] 时 fail-closed 503


可对等节点

方法路径鉴权说明
GET/nodes门户会话令牌开放自动对等的节点清单(站点、拨入主机、容量、展示元数据)

容量由外部端口池派生:

字段语义
capacity池总席位
used_ports已占席位,含手工建立的对等
free_ports池内空闲席位
remaining实际可接纳数
provisioned其中经自动对等落成的数量
max_sessions可选人工上限;null 表示只受端口池约束

容量条画 used_ports / capacity。列表页元数据 region / country_code / description / bandwidth_mbps / monthly_quota_gb / notice 由管理面同名字段配置。endpoint_host 为空的节点不接受拨入,不出现在本清单里。


对等请求

全部挂 require_portal_session,按会话内 ASN 划权。会话里没有已验证的 ASN 时(claims 缺失或异常)一律 403——门户会话必须携带经归属验证的 ASN 才能提交或查看对等。

方法路径说明
POST/requests提交即落成(免审批),201
GET/requests我的请求列表(按会话 ASN)
GET/requests/{id}单条详情;他人请求一律 404(防枚举)
DELETE/requests/{id}自助拆除;非 provisioned 409
GET/requests/{id}/traffic?range=会话流量:当前速率、时 / 日 / 月用量(含月配额展示位)、5min 序列(per-interface 存档,保留 60 天)
GET/requests/{id}/metrics?range=会话路由计数时序(5min gauge:per-AF imported;agent ≥1.0.233 起含 exported)
GET/requests/{id}/routes?family=&limit=&offset=对端宣告进本网、被该节点收到的路由明细(node_route_entries 按会话协议过滤)
POST/requests/{id}/probe-mtuDF ping 二分扫描对端隧道地址
POST/requests/{id}/probe-latency按需 RTT / 丢包采样(ping 10 包)

提交请求

RequestCreateIn

字段必填语义
node_id目标接入节点
public_key对端 WireGuard 公钥
endpoint对端 WireGuard host:port;NAT 后可留空由本网侧被拨
link_local对端 link-local;缺省按 ASN 尾四位派生
mtu1280..1420,缺省 1420(WG over IPv4 满额值)
mp_bgp默认 true(单条 MP-BGP 会话)
session_addressinglink-local(默认)或 ipv6-ula,仅 MP 形态有意义
peer_ipv4 / peer_ipv6视形态对端隧道内地址
extended_next_hopMP 形态恒为 true;显式给 false 回一条解释性 422
notes≤512 字符

会话形态三选一

形态参数组合产出
link-local MP-BGP(默认)mp_bgp=true, session_addressing=link-local单会话,不接受 peer_ipv4 / peer_ipv6
ULA 单会话mp_bgp=true, session_addressing=ipv6-ula, peer_ipv6单会话,peer_ipv6 须为 fd00::/8
v4 / v6 双纯会话mp_bgp=false, peer_ipv4 peer_ipv6两条会话,命名 autopeer_<asn>_v4 / _v6

对端隧道内地址须落在该 ASN 在 registry 登记的 route / route6 起源前缀内(现查副本):不符 422 并指明该注册哪个前缀,副本不可用 503

落成事务(同一事务内完成,任一步失败整体回滚、不留半成品请求行):行锁节点 → 从外部端口池分配最小空闲端口 → 组 spec(接口名 as<ASN>,继承节点固定钥,policy dnpeers)→ 建 Peering 聚合并 materialize → 提交后广播门铃。

错误:校验失败或节点落成前置不齐 422;节点未开放 404每节点每 AS 最多一条——目标节点已有该 AS 的外部对等(含手工建立)、或容量 / 端口池满 409。同一个 AS 可以在多个不同节点各建一条。

201 响应含 provisioned 请求行、provision 连接参数与 generation

请求列表与详情

读取列表时惰性收养:手工建立的外部对等按登录 ASN 归并为请求行(adopted: true,前端可标注「由运维建立」;多 WG 接口的特调聚合跳过)。这样门户是单一入口,用户看得到也管得了运维手工建的那条。

展示态(公钥 / endpoint / 端口 / MTU / 接口名 / 会话形态)从 Peering 聚合 spec 读侧派生——管理面调参或改名后门户自动跟进;聚合已删的行(cancelled)回落审计快照。

详情额外带两个块:

  • provision:本网侧连接参数——endpoint(拨入主机 + 分配端口)、node_public_key(从节点私钥现场推导)、node_link_localnode_asnmtu
  • live:本会话 BGP 活性,从节点最新 runtime snapshot 派生——bgp_statebgp_healthup / connecting / down / unknown)、sinceobserved_atestablished_secondswireguard(握手时刻与累计字节)、routes(per-AF 导入导出计数,旧 agent 只有导入侧)。双会话形态另有 sessions 数组。节点无快照时为 null

拆除

DELETE /requests/{id} 删 Peering 聚合(接口与 BGP 会话随 cascade)并 materialize,提交后广播。配额位与端口随之释放,请求行留作 cancelled 审计痕迹,之后可重新提交。

落成后探测

端点产出限制
POST /requests/{id}/probe-mtu{status: ok|mismatch|unreachable, pmtu, mtu},落详情的 mtu_probe。落成后自动跑一次每会话 10min 冷却 429;agent 离线 409;超时 504
POST /requests/{id}/probe-latency{rtt_ms_avg, rtt_ms_max, loss_pct},落详情的 latency同上

只读网络态势

源码:apps/control-server/app/api/v1/autopeering/observability.py。与控制台复用同一批 builder 函数,但不复用端点——/ui/adminrequire_admin 挂在路由级,信任域不跨越;这里另起一个挂 require_portal_session 的路由,直接调那些纯函数。

方法路径说明
GET/fleet/dashboard门户版仪表盘(概述 + 流量 + 路由),字段经白名单投影
GET/fleet/traffic全 fleet 吞吐时间线
GET/nodes/{node_id}/traffic单节点吞吐时间线
GET/fleet/traffic-breakdown按节点 / 按对端的当前吞吐排行
GET/fleet/prefix-flaps只读前缀抖动榜,与管理面同 builder、同参数,含根因定位
GET/fleet/flap-statsfleet 级抖动速率仪表与时序
GET/my/flaps「我这家 AS 最近抖不抖」——按会话 ASN 划权的单对端 flap 详情

披露口径:AS 号、前缀、流量占比、抖动明细不脱敏——这些本就在 DN42 registry 与各家 explorer 上公开。收窄两处,且一律白名单投影(builder 将来新增字段默认不出,不会静默泄漏):

  • traffic-breakdown 的 WG 对端只保留 node_id / interface / 收发速率,不出 public_keyendpoint(密钥材料与攻击面,不是路由信息)。
  • overview 节点行只保留 node_id / health / site / region / geo / capabilities不出 agent 版本、心跳、drift、generation、release 遥测(内部运维信息,版本号还会给出漏洞窗口线索)。

其他差异:门户侧流量时段最长 24h7d / 30d 档不下放);管理面的处置写路径与告警事件流不在门户暴露;/my/flaps 在无 flap 痕迹时返回 200 空态并带 has_flaps: false,而不是像管理面那样 404——对门户用户来说「没抖」是正向结果,不该长得像出错。