跳转到内容

nsshared · 共享协议库

nsshared 是 NSIO 跨仓库共享的 Rust 工作区,只放多个组件都要编解码或执行的通用协议与工具。仓库为公开的 github.com/dotns/nsshared(原 git.pvv.cc/nsiod/ns-shared),消费方以 Cargo git 依赖引入并按 tag 固定版本。本页以 2026-10-08 的 main(tag v0.6.0,fc1c884)为准。

nsshared 只做通用层:两个以上组件必须一致理解的线上格式与规则,以及每个组件都会用到的基础工具。具体的应用实现与整体架构定义不放在这里,留在拥有它们的仓库。

这样划分是为了让 nsshared 保持稳定:应用、节点架构与 relay 内部的变化不需要发布新的 nsshared,消费方也不必为别人的功能升级依赖。

判断一项内容是否属于 nsshared:

问题是否
是否有两个以上的组件编解码同一份数据,或执行同一条规则?继续判断留在唯一使用它的仓库
它是通用协议或工具,而不是某个应用、某个组件的内部协议或架构选择?放进 nsshared留在拥有它的仓库
crate内容两端
nsshared-proto身份与密钥:身份键 I 与 WireGuard 键 K 的类型、生成与规范文本 / 二进制编码;节点 ID、node6 / node4 派生(Quick 协议 v2 §2)ns、relay;之后的 NSD 与权威 DNS
nsshared-protoNodeRecord 的编码、签名与 §3.3 的顺序 / 冲突规则(Quick 协议 v2 §3)ns、relay 登记表
nsshared-protorelay 报文 v2:NSR2 报文、REGISTER 准入、会话密钥与 MAC、载体常量(Quick 协议 v2 §8)ns 的 RelayClient、relay
nsshared-proto会合协议 /rendezvous/v1 的消息:JSON 传输、challenge / hello、能力标记、错误码、信箱请求与阶段规则、登记表 publish / lookup_node、relay 目录 v2(会合协议)ns 的会合客户端、relay
nsshared-proto公网 DNS 发现(discovery):<节点 ID>.n.ns.io 名字、v=ns2 r= TXT 的编码与校验(Quick 协议 v2 §11.2);发布的三档 none / address / record;登记表状态(RecordMarks、RegistryEntry)的持久化与成员间合并ns 的 resolver、relay 登记表、计划中的权威 DNS
nsshared-proto通用工具:严格的 base64url(无填充)与标准 base64(WireGuard 键)编解码、规范 CBOR 读写、密钥生成全部 Rust 组件
nsshared-telemetrytracing 订阅器初始化(文本或 JSON、带默认值的环境变量过滤)、敏感字段脱敏;不含导出器全部 Rust 组件
examples/给 ns 开发与测试用的模拟服务:relay v2、/rendezvous/v1、二者合一的单端口服务;publish = false,不进入生产ns 的集成测试

nsshared-proto 由 v0.1.0 的 nsshared-wire 与 nsshared-rendezvous 合并而成。所有协议 crate 都是纯库:没有 I/O、异步运行时或 TLS,时间、随机数与密钥由调用方传入。

规则:

  1. 契约在一处定义。 同一份线上数据只有一个 Rust 定义;任何组件都不复制、不分叉。
  2. 规范先行。 协议变化先改本站的规范,再改独立生成的测试向量,最后改代码。
  3. 按 tag 固定。 消费方用 Cargo git 依赖固定 tag;本地联合开发用不提交的 [patch] 指向本地检出。
  4. 破坏性变更同步发布。 改变线上契约时,在同一个发布窗口内更新所有消费方,并在 changelog 中列出受影响的消费方与切换顺序。
  5. 同一套基线。 Rust 2024、与消费方一致的 MSRV、工作区 lint 拒绝警告、运行时代码不允许 unwrap / expect / panic、rustls 使用 aws-lc-rs。
内容归属原因
数据面:WireGuard 引擎、传输、路径策略机制、过滤链、netstack、TUNnsplane数据面机制,不是线上契约
应用通道与应用:Quick 配对、文件发送、语音、聊天、第三方应用的协议与状态机ns两端都是 ns;属于应用实现
隧道内控制通道(ENDPOINTS、RECORD、探测,Quick 协议 v2 §9)ns只在节点之间
节点地址规划:本地 IPv4 别名、lan6、对端来源与路径标签ns节点的架构选择
relay 区域成员协议(mesh、复制、owner 栅栏、转发帧)与 relay 运行时relay(NSGW)只在 relay 实例之间;v0.1.0 中的实现留作参考
控制面契约(配置流、ACL 判定、WSS 复用帧等 v1 的 control、common、acl、tunnel-ws)随迁移 M6 重新设计(迁移计划)v1 的账户契约在 M6 被替换;归档于 tag archive/v1-20261002
产品逻辑、NSD 服务端、部署与运维ns、nsd、deploy单方使用
项目状态
仓库公开仓库 github.com/dotns/nsshared(原 git.pvv.cc/nsiod/ns-shared);消费方按 tag 以 Cargo git 依赖引入,CI 不再需要读取令牌
发布v0.1.0(nsshared-wire、nsshared-rendezvous)→ v0.2.0(合并为 nsshared-proto,会合服务改名 /rendezvous/v1,应用层内容移到 ns)→ v0.3.0(按模块导出、#[non_exhaustive]、just semver API 兼容门)→ v0.4.0(公网 DNS 发现 discovery)→ v0.5.0(发布三档 none / address / record)→ v0.6.0(RecordMarks 与 RegistryEntry 的持久化、恢复与合并,供 relay 复制登记表)
版本规则0.x 内 minor 可破坏 API、patch 不可;1.0 起按 semver。线上格式只随新的协议版本或上下文字符串变化
维护期v0.6.0 起进入维护期:只接受修复、依赖与工具链更新,以及里程碑需要的新契约(L11 控制资源与会话协议、L8 M6 载荷 profile、L9 M8 短名与 DNS 供数),以新模块或新 crate 的次版本发布,先写规范。只属于单个组件的功能(例如 relay 的信箱存储、节点的 relay 客户端)不进入 nsshared。ns 与 relay v2 服务端在同一实现上通过互通测试后发布 v1.0.0(nsshared 仓库 docs/plan/20261010-1200-nsshared-maintenance.md)
测试向量tools/vectors 用 Python 独立生成一致性向量(registry、mailbox_json、dns_txt、registry_state 等),nsshared-proto 对其测试;覆盖见 docs/spec-coverage/
ns用 v0.5.0(nsshared-proto、nsshared-telemetry,测试用三个 mock);v0.6.0 已评估、暂不采用(无编译破坏,等 relay 侧需要时一起升)
NSGW仍使用 v1 工作区的本地副本;relay v2 服务端(迁移 M2)实现时改为依赖 nsshared,v0.6.0 的登记表状态即为它准备
NSD线上类型在 nsd 内自行定义;M6 的控制面契约设计时决定是否进入 nsshared
v1 工作区control、common、acl、tunnel-ws、telemetry 归档于 tag archive/v1-20261002

三个 crate 都是库加二进制,publish = false,只给 ns 的开发与测试用,从不进入生产;在 nsgw 的 relay v2 服务端(迁移 M2)落地之前,它们是 Quick v2 唯一可对接的服务端。

crate提供边界
nsshared-mock-relay单实例、内存中的 relay v2:UDP 与 WSS 两种载体共用一个端口,REGISTER、经 PRESENCE 确认的会话、DATA 转发;核心无 I/O,直接建在 nsshared-proto 上没有登记表,没有区域
nsshared-mock-rendezvous单实例的 /rendezvous/v1,TLS WebSocket:challenge / hello、信箱 broker、directory、节点登记表;会合协议 §6 与 Quick 协议 v2 §7.4 的每一条上限都执行编码与协议规则全部复用 nsshared_proto::rendezvous,自己只加存储、随机数、时钟、限速与 I/O(tokio、rustls + aws-lc-rs、tokio-tungstenite);状态模块无 I/O,时间与随机数由调用方传入
nsshared-mock-server两个 mock 合在一个 host:port 与一张证书后面:relay 的 UDP 与一个 TCP + TLS 监听共用端口,按路径把 WebSocket 升级分给 /ns/v2(relay)与 /rendezvous/v1(会合)只加共享监听、按升级路由与生命周期;两个 mock 各自保留状态
  • 向量独立生成。 tools/vectors 是只用标准库的 Python 生成器,直接按规范实现,从不构建或调用 Rust crate,生成 crates/nsshared-proto/tests/fixtures/vectors;nsshared-proto 的 tests/vectors_*.rs 对其测试。just vectors 重新生成,just vectors-check 以检查模式重跑并进入 just qa。本站的示例(Quick 协议 v2 §2.3 与 §11.2 的 TXT)由同一生成器复现。
  • 规范到代码的对照表。 docs/spec-coverage/nsshared-proto.md 与 nsshared-proto-rendezvous.md 逐条列出本站规范章节对应的源码与测试,固定到本站的某个 commit;状态为 implemented、out of scope: <归属>(纯库留给调用方的 I/O、定时器、持久化与状态机)或 deferred。
  • API 兼容门。 just semver(cargo-semver-checks)把 nsshared-proto 与 nsshared-telemetry 与上一个发布 tag 比较,进入 just qa;just doc 拒绝 rustdoc 警告。
  • just qa = fmt-check、lint、deny、shear、typos、vectors-check、semver、test、doc;仓库用 Gitea 的 QA 工作流(.gitea/workflows/qa.yml)跑同一套。
  • 基线(ADR 2026-10-02-ns-shared-next-baseline):edition 2024,rust-version 等于固定的工具链,resolver 3,工作区 lint 拒绝警告、禁止 unsafe、测试之外禁止 unwrap / expect / panic;需要 TLS 时用 rustls + aws-lc-rs,与 ns 一致,使一个进程只链接一个提供方。
[dependencies]
nsshared-proto = { git = "https://github.com/dotns/nsshared", tag = "v0.6.0" }
nsshared-telemetry = { git = "https://github.com/dotns/nsshared", tag = "v0.6.0" }
[dev-dependencies]
nsshared-mock-server = { git = "https://github.com/dotns/nsshared", tag = "v0.6.0" }
  • 按 tag 固定,Cargo.lock 记录确切 commit;仓库公开后 CI 不需要凭据(ns 自 2026-10-07 起这样取)。README 里的示例仍写着 git.pvv.cc 的地址,两处指向同一仓库。
  • 本地联合开发在消费方的 .cargo/config.toml 加不提交的覆盖:[patch."https://github.com/dotns/nsshared"] nsshared-proto = { path = "../nsshared/crates/nsshared-proto" }。
  • 0.x 期间 minor 升级可能破坏 API(v0.2.0、v0.3.0、v0.5.0、v0.6.0 都是),changelog 的每条发布记录列出受影响的消费方与切换顺序;线上格式只随新的协议版本或上下文字符串变化,不在一个版本内悄悄改。