NS · 应用通道
状态:部分实现。
confirm模式与 Quick 配对(kind = "pairing")已在 ns 实现(crates/ns/src/app_channel,2026-10-04):Offer/Answer/ACCEPT/COMPLETE经overlay的会合信箱客户端走/rendezvous/v1,派生K_ch与 WireGuard PSK;通道从不自己安装对端。pake/peer模式与文件发送等应用尚未实现。应用通道是 ns 内部的通用层:Quick 配对、文件发送以及之后的语音、聊天等应用都运行在它上面。通道的两端都是 ns;relay 只看到会合协议的不透明消息,nsplane 只看到对端与端口。本页取代会合协议原 §9 的逐应用映射、Quick 协议 v2 原 §10.2 的配对载荷,以及文件发送原 §4.2 与 §5.1 的会合与密钥协商。协议引用(§n)除非另有说明均指本页。
| 目标 | 推论 |
|---|---|
| 新应用只声明参数 | 应用给出一份 profile(§3);会合、引入、身份交换、PSK、对端安装与隧道入口全部复用 |
| 一套引入流程,三种鉴权 | 人工核对节点 ID(confirm)、一次性口令 PAKE(pake)、已有对端(peer),覆盖配对、票据与已配对设备之间的应用 |
| 应用之间互相隔离 | kind 进入每个签名与每次密钥派生,一个应用的消息不能在另一个应用里重放 |
| relay 不感知通道 | 只使用会合协议的信箱;不新增 relay 操作、报文类型或白名单 |
| 数据只走隧道 | 信箱只承载引入所需的小消息;应用数据始终在 WireGuard 隧道内(会合协议 §10) |
非目标:多于两方的会话、离线投递、按名字或身份查找对方、隧道内应用协议本身(各应用自己定义)。
| 层 | 组件 | 职责 |
|---|---|---|
| 应用 | ns:pairing、transfer,之后的语音、聊天;第三方应用经 ns 的应用接口 | 声明 profile;命令、确认提示与界面;隧道内的应用协议 |
| 应用通道 | ns | 本页:profile、引入、身份交换、密钥派生、对端安装、隧道内入口 |
| 会合 | relay 的 /rendezvous/v1;消息类型在 nsshared | 信箱、短码、接纳、队列、上限,与应用无关 |
| 数据面 | nsplane | 安装与移除对端、隧道内连接、按来源命名空间的 ACL 与 pinhole |
通道只在 ns 中实现,不进入 nsshared:两端都是 ns,第三方应用也经 ns 的应用接口使用它,不自行实现本页的协议。
3. 应用 profile
Section titled “3. 应用 profile”AppProfile { kind [a-z0-9-]{1,32}(会合协议 §5.1) auth confirm | pake | peer lifetime permanent | session | none ttl 信箱 TTL 的默认值与上限,≤ 900 s app_port 隧道内 TCP 端口;null 表示没有隧道内阶段}auth | 信箱用法 | 适用场景 | lifetime |
|---|---|---|---|
confirm | admission: owner,candidates 1–4 | 双方核对对方节点 ID 后建立长期关系 | permanent |
pake | admission: first,candidates: 1;或在已有对端的隧道内运行(§5.2) | 持有一次性口令即授权一次会话 | session |
peer | 不使用信箱 | 两端已是 ready 对端 | none |
内置 profile:
| 应用 | kind | auth | lifetime | ttl | app_port |
|---|---|---|---|---|---|
| Quick 配对 | pairing | confirm | permanent | 默认 600 s,最多 900 s | null |
| 文件发送(relay 票据) | transfer | pake | session | 600 s | 47901 |
| 文件发送(已有对端、对端票据) | transfer | peer;对端票据另在隧道内运行 pake | none | — | 47901 |
| 语音、聊天 | 待定 | peer 或 pake | none 或 session | — | 待定 |
其他组合不允许:confirm 总是建立长期对端,pake 从不写入允许名单,peer 从不安装对端。
4. 通道消息
Section titled “4. 通道消息”所有消息都是规范 CBOR 定长数组(Quick 协议 v2 §12.1),放在信箱的 intro 与 data 里;解码器在任何签名、MAC 或 AEAD 校验之前拒绝不规范的输入、多余字节、未知版本与未知类型。
// confirm:信箱 introOffer = [ver = 1, kind text, record_o, e_o bytes32, authority text, expires u64, name text|null, sig_o] sig_o = Ed25519_Io("ns/channel-offer/v1" ‖ CBOR(前七个字段))Answer = [ver = 1, kind text, record_p, e_p bytes32, mailbox_id bytes16, offer_hash bytes32, name text|null, sig_p] sig_p = Ed25519_Ip("ns/channel-answer/v1" ‖ CBOR(前七个字段))
// pake:信箱 introShare = [ver = 1, kind text, Y bytes] // CPace 公开消息
// 两种模式:信箱 dataMessage = [ver = 1, type u8, body] 1 ACCEPT [answer_hash bytes32, sig] // confirm;sig = Ed25519_Io("ns/channel-accept/v1" ‖ offer_hash ‖ answer_hash) 2 COMPLETE [] 3 HELLO [ciphertext bytes] // pake;AEAD(k_hello_dir, nonce = 0, Hello) 4 APP [payload bytes] // 应用自己的小消息,通道建立后才允许
Hello = [ver = 1, record, endpoint text|null, not_after u64, sig] sig = Ed25519_I("ns/channel-hello/v1" ‖ kind ‖ th ‖ CBOR(前四个字段))
offer_hash = SHA-256(CBOR(Offer)),answer_hash = SHA-256(CBOR(Answer))th = SHA-256(CBOR(Share_o) ‖ CBOR(Share_p) ‖ mailbox_id)record是发送方当前的签名NodeRecord(Quick 协议 v2 §3);接收方按其有效性与 §3.3 的代际 / 冲突规则处理。- 每个消息中的
kind必须等于信箱的kind与本地 profile 的kind,否则丢弃并中止通道。 name是对方给出的显示名,只用于界面与默认昵称,从不参与授权。APP受信箱的 8 KiBdata上限约束;大于此的数据走隧道。
4.1 密钥
Section titled “4.1 密钥”confirm: ikm = X25519(e_o, e_p)(全零结果被拒绝),t = offer_hash ‖ answer_hashpake: ikm = K_pake(CPace,§5.2), t = thK_ch = HKDF-SHA256(salt = 空, ikm, info = "ns/channel-key/v1" ‖ kind ‖ t)
psk = HKDF-Expand(K_ch, "ns/channel-psk/v1" ‖ I_o ‖ I_p, 32) // WireGuard PSKk_hello_o = HKDF-Expand(K_ch, "ns/channel-hello-o/v1", 32) // 创建方发出的 HELLOk_hello_p = HKDF-Expand(K_ch, "ns/channel-hello-p/v1", 32) // 加入方发出的 HELLOK_confirm = HKDF-Expand(K_ch, "ns/channel-confirm/v1", 32) // 隧道内 OPEN 的 MAC(§7)o 指信箱创建方,p 指被接纳的加入方。AEAD 为 ChaCha20-Poly1305;每个 HELLO 密钥只加密一条消息,因此 nonce 固定为 0。临时密钥 e_o、e_p、CPace 秘密与 K_ch 在通道结束后丢弃;psk 只交给对端安装(§6)。
5.1 confirm
Section titled “5.1 confirm”- 创建。 创建方
open {kind, admission: owner, candidates, ttl, intro: Offer};authority是所拨 relay 的标识,expires = now + ttl。用户分享短码(relay 短码 + 8 位数字,会合协议 §5.4)。 - 查看。 加入方
lookup得到 Offer 并校验:签名与记录有效、record_o.I与签名者一致、authority等于所拨 relay、now < expires、对方不在本机移除记录中、地址组不与自己或已有对端冲突。界面显示对方的节点 ID 与name,用户确认;应用可以提供”指定节点 ID 时自动确认、不匹配时失败”的参数。 - 加入。 加入方
join {id, intro: Answer}。 - 接纳。 创建方从
recv的waiting读取每个 Answer,做与第 2 步相同的校验,并检查mailbox_id与offer_hash。用户admit一个、reject其余(同样可以按指定节点 ID 自动接纳),随后发送 ACCEPT。 - 安装。 创建方发送 ACCEPT 后、加入方校验 ACCEPT 后,各自派生
psk并按lifetime安装对端(§6)。 - 结束。 加入方就绪后发送 COMPLETE 并
close;创建方收到 COMPLETE 后close,或在自己就绪 30 s 后关闭。
5.2 pake
Section titled “5.2 pake”- 创建。 创建方生成一次性口令,
open {kind, admission: first, candidates: 1, ttl, intro: Share_o}。口令与短码一起组成应用的票据(例如文件发送 §3)。 - 加入。 加入方
lookup得到Share_o,join {id, intro: Share_p},立即被接纳。 - 派生。 双方运行 CPace:
sid = mailbox_id,CI = "ns/channel-cpace/v1" ‖ kind ‖ authority,口令为票据中的口令,得到K_pake,再按 §4.1 派生K_ch。 - Hello。 双方各
send一条 HELLO。接收方解密、校验签名、记录(有效性与代际 / 冲突规则)、not_after > now与本机移除记录。 - 猜错口令。 创建方解不开对方的 HELLO 时
reject该对端;信箱回到open并保留同一短码,第 3 次reject后关闭(会合协议 §5.3)。加入方解不开时直接close。 - 安装与结束。 双方的 HELLO 都校验通过后,各自派生
psk,按lifetime安装对端(§6),然后close信箱。
在隧道内运行 pake。 两端已是对端、但应用仍要求持有口令(文件发送的对端票据)时,不使用信箱:Share 与 HELLO 作为隧道内 OPEN 之后的头几帧交换(§7),sid 为 OPEN 中的 channel_id,authority 为空串。身份已由 WireGuard 认证,HELLO 只用于确认口令并绑定双方身份;不安装新对端。
5.3 peer
Section titled “5.3 peer”两端已是 ready 对端(已配对,或同一账户内由 NSD 投影)。不使用会合与信箱:发起方直接在隧道内连接对方的 app_port 并发送 OPEN(§7),身份就是承载这条流的 WireGuard 对端。是否允许由对端来源命名空间的 pinhole 规则决定(nsplane)。
6. 对端安装
Section titled “6. 对端安装”lifetime | 写入 | AllowedIPs | 入站过滤 | 生命周期 |
|---|---|---|---|---|
permanent | Quick 来源的 Allow {I, added_by: kind, psk} 与对方记录(Quick 协议 v2 §10.1) | N6/128 + N4/128 | 发往本机的任意流量 | 直到移除 |
session | 仅限本次会话的对端:不进允许名单,不分配别名或名字,不留下移除记录 | 只有 N6/128 | 只接受 app_port 上来自 N6(peer) 的 TCP 及其返回流,加上隧道内控制端口 47900 | 直到会话完成、失败或过期;自创建起 ≤ ttl,加上应用报告有进展的时间,最长 24 h |
none | 不安装;使用已有对端 | — | 对端来源命名空间允许的 app_port pinhole,随会话关闭 | 随会话 |
- 已存在的长期对端被同一身份重新
confirm时,只替换 PSK 与记录,保留别名与昵称。 - 从安装起 5 s 内 WireGuard 路径可用并完成本地发布,否则报告
prepare_timed_out;长期对端保留供正常重连,会话对端删除。 - 仅限本次会话的对端计入每个网络 1 024 个对端的上限,另有每个节点默认最多 8 个并发的单独上限;超过任一上限时,通道在会合之前以容量错误失败。
- 两台设备之后经
confirm建立长期关系时,先删除双方之间的会话对端;会话 PSK 从不复用。 - 崩溃遗留的会话对端在节点下次启动时删除。
7. 隧道内入口
Section titled “7. 隧道内入口”有 app_port 的应用,在隧道内的 TCP 流上使用带长度前缀的帧 u32 length ‖ u8 type ‖ payload,每帧最大 1 MiB。类型 0 保留给通道,其余由应用定义:
OPEN = [ver = 1, kind text, channel_id bytes16, mac bytes32 | null] mac = HMAC-SHA256(K_confirm, "ns/channel-open/v1" ‖ channel_id ‖ I_sender ‖ I_receiver)- 连接方先发 OPEN,被连接方校验后回一个 OPEN(
I_sender与I_receiver对调),之后才交换应用帧。 - 经信箱建立的通道:
channel_id = mailbox_id,mac必须存在;MAC 中的两个身份必须与承载这条流的 WireGuard 对端一致。这样隧道证明对端身份,MAC 证明对端就是完成引入的那一方。 peer通道:channel_id为发起方生成的 16 字节随机值,mac为 null;需要口令时随后运行隧道内pake(§5.2)。kind不匹配、MAC 错误或第一帧不是 OPEN 时立即关闭连接。
8. 安全性质
Section titled “8. 安全性质”- relay 看到临时参与方密钥、
kind、短码、消息大小与时间。confirm的 Offer / Answer 明文携带双方的签名记录与临时 DH 公钥;pake的身份只出现在加密的 HELLO 中。relay 看不到口令、K_ch、PSK 或应用数据。 - relay 不能伪造 Offer、Answer、ACCEPT 或 HELLO,也不能伪造”已接纳”:加入方只认创建方签名的 ACCEPT 或能解开的 HELLO。
confirm中 relay 用自己的身份替换整份 Offer 或 Answer 时,用户核对的节点 ID 会不一致,这是唯一需要人工的一步;节点 ID 有 96 位,无法碰撞出相同的值。pake中知道短码的 relay 运营方,每次尝试最多在线猜一次口令;第 3 次失败后信箱关闭。口令熵由应用决定(文件发送约 33 位)。kind进入签名、CPace 的CI与K_ch的派生:为一个应用产生的消息在另一个应用里无法通过校验。- 信箱过期、被拒绝或取消时什么也不安装。
9. 添加一个新应用
Section titled “9. 添加一个新应用”- 选择
kind,以及 §3 中允许的auth与lifetime组合。 - 有隧道内阶段时分配
app_port,并定义 OPEN 之后的应用帧。 - 需要会合时定义票据或短码的界面;口令长度满足自己的安全需求。
- 在 ns 的应用接口中登记 profile 与命令。relay、nsshared、nsplane 都不需要改动。
10. 常量与上下文
Section titled “10. 常量与上下文”| 项目 | 值 |
|---|---|
| 消息版本 | 1 |
| 签名上下文 | ns/channel-offer/v1、ns/channel-answer/v1、ns/channel-accept/v1、ns/channel-hello/v1 |
| 派生标签 | ns/channel-key/v1、ns/channel-psk/v1、ns/channel-hello-o/v1、ns/channel-hello-p/v1、ns/channel-confirm/v1、ns/channel-open/v1 |
| CPace | CI 前缀 ns/channel-cpace/v1;sid 为信箱 id 或隧道内 channel_id |
| AEAD | ChaCha20-Poly1305,nonce 0,每个密钥一条消息 |
| 隧道内帧 | u32 length ‖ u8 type ‖ payload,最大 1 MiB;类型 0 为 OPEN |
| 会话对端 | 每个节点默认最多 8 个并发;最长 24 h |
签名为 RFC 8032 pure Ed25519,严格校验;被签名的消息是 ASCII 上下文紧接后续字节,没有分隔符(同 Quick 协议 v2 §12.2)。
11. 与原设计的对应
Section titled “11. 与原设计的对应”| 原设计 | 应用通道 |
|---|---|
配对 Invite / Join(ver = 2) | confirm 的 Offer / Answer(ver = 1),增加 kind 字段 |
ns/pairing-invite/v2、ns/pairing-join/v2、ns/pairing-accept/v2 | ns/channel-offer/v1、ns/channel-answer/v1、ns/channel-accept/v1 |
ns/pairing-psk/v2 | ns/channel-key/v1 + ns/channel-psk/v1 |
文件发送 TransferHello | pake 的 HELLO |
ns-transfer/psk/v1、ns-transfer/confirm/v1 | ns/channel-psk/v1、ns/channel-confirm/v1 + 隧道内 OPEN |
ns-transfer/cpace/v1 | ns/channel-cpace/v1 |
12. 待决事项
Section titled “12. 待决事项”- 第三方应用能否声明
session对端,以及可以使用哪些app_port(应用模式 §5)。 - 账户模式下没有现成对端时,是否允许
pake经 Quick relay 建立会话对端。 - 语音、聊天的
kind、端口与是否需要pake。