跳转到内容

NS · 文件发送(应用)

状态:设计阶段,ns 尚未实现。 文件发送是应用模式的第一个应用(kind = "transfer"):在 NS 节点之间做一次性的、类似 croc 的文件传输。它运行在节点唯一的 nsplane 引擎之上,只使用节点已有的能力:relay 负责会合与转发,传输的每个字节都走 WireGuard 隧道。没有查找服务,不新增 relay 报文类型,也不引入第二套传输加密。已配对的两台设备使用现有隧道;未配对的两台设备建立一个只为本次传输存在的短期隧道,传输结束即删除。本页先给出节点运行在 Quick 模式时的设计;账户模式下的做法见 §9。节点协议(记录、relay 报文、隧道内控制通道)见 Quick 协议 v2。

目标推论
像 croc 一样的交互send 打印一张短票据;接收方确认文件清单后,receive <票据> 拉取文件
复用 NS 的 relay 与隧道会合使用 ns 的应用通道与 relay 的会合信箱 /rendezvous/v1;字节始终在 WireGuard 内,经节点平常的直连或 relay 路径传输
已配对与未配对设备都能用已配对设备使用现有对端;未配对设备使用仅限本次传输的对端,它从不进入允许名单
relay 无法读取或改道传输票据口令从不到达 relay;在安装任何隧道之前,基于口令的 PAKE 绑定双方身份键
可口述、一次性的票据relay 目录前缀 + 8 位数字 + 3 个单词,与 Quick 邀请短码的格式一致

非目标:不带票据的推送(Taildrop 方式)、在 relay 上离线投递或存储、经第三个节点的多跳转发、按目录或名字查找发送方。

终端窗口
ns send <path>... # 打印票据,等待一个接收方
ns receive [TICKET] # 别名 recv;省略 TICKET 时在隐藏提示中粘贴
  • 两个命令使用正在运行的节点;节点未运行时按其当前模式启动它,权限规则与该模式的命令相同(Quick 模式见 Quick 模式 §2.5)。
  • receive 显示发送方的 20 字符节点 ID、发送方是已配对对端时的本地昵称,以及文件清单;除非给出 --yes,否则要求确认。--yes 只接受文件清单,从不接纳对端。
  • --to DIR 指定目标目录(默认当前目录)。send --peers-only 签发对端票据(§3.2),只有已配对的设备能兑现,且不需要 relay。
  • --json 按 invite / join 的方式输出按行分隔的生命周期事件(ticket_created、receiver_connected、manifest、progress、completed、failed)。ticket_created 包含票据:请保密截获的 stdout。
  • 退出码与其他 ns 命令一致:输入错误为 2,运行时失败非零(通常为 1),取消为 130。
KT58307264-apple-river-cloud
└┘└──────┘ └───────────────┘
│ │ 口令:从 2048 词表中随机取 3 个单词(约 33 位),每次 send 随机生成
│ 会话数字:在该 relay 上定位传输会话,与 Quick 邀请相同
relay 目录前缀(与 Quick 邀请使用同一张表)
  • 前缀与数字是 relay 能看到的路由信息,遵循 Quick 邀请的规则:未知前缀触发一次可信目录刷新;自定义 relay 时打印 ns receive 58307264-apple-river-cloud --relay <host>。
  • 单词从不离开两端。它们作为 §5.1 PAKE 的输入,因此即使知道数字的 relay 运营方,每次尝试也最多只能在线猜一次。
k7qf3m9x2p-4-apple-river-cloud
└────────┘ │ └───────────────┘
节点 ID │ 口令
前缀 会话编号(1–2 位数字,按发送方编号)
  • 前缀是发送方 20 字符节点 ID(Quick 协议 v2 §2.2)的前 10–12 个字符。它只用于路由:接收方在自己的对端表中查找,并尝试每个节点 ID 以该前缀开头、状态为 ready 的对端。对端票据从不联系 relay 会合服务,未配对的设备无法兑现。

与 Quick 短码相同:口令从不作为单独的命令行参数接受;不带 TICKET 的 receive 在隐藏提示中读取;--stdin 为自动化读取恰好一行票据。调试日志从不包含票据的任何部分。

channel(ticket) -> (sender I, receiver I, K_ch, 发送方可达信息) // 应用通道 pake:经 relay 信箱,或在已有对端的隧道内
tunnel(I_peer) -> 现有 ready 对端 | 仅限本次传输的对端 // 节点的 nsplane 引擎,直连或经 relay
session(tcp, K) -> transfer // 唯一实现,始终在隧道内
情形会合隧道
Relay 票据,发送方已是接收方的 ready 对端应用通道 pake,经 /rendezvous/v1 信箱现有对端
Relay 票据,未配对(或已配对但不是 ready)应用通道 pake,经 /rendezvous/v1 信箱仅限本次传输的对端(§4.3)
对端票据无(本地对端表);应用通道 pake 在隧道内运行现有对端

“Ready” 就是 ns quick peers 已经报告的判定:数据面持有与该对端的当前 WireGuard 会话,且 transport 选定了直连路径,或选定的 relay 的确切载体是 relay 客户端的活动会话。已配对但不是 ready 的对端不视为可达;使用 relay 票据时,接收方回退到该对端的正常重连,只有 5 s 内仍未 ready 才报告 peer_unreachable(它从不为已配置的身份再安装第二个对端)。

文件发送是应用通道上的一个应用,relay 票据的 profile 为 {kind: "transfer", auth: pake, lifetime: session, ttl: 600 s, app_port: 47901}。会合、CPace、加密的 HELLO、猜错口令的处理与安全性质见应用通道 §4、§5.2 与 §8;本节只列文件发送特有的部分:

  1. 创建。 send 生成 3 个单词的口令,以应用通道 pake 打开信箱,把得到的数字与口令组成票据(§3.1),信箱最多 10 分钟。
  2. 兑现。 receive 从票据得到 relay、数字与口令,按应用通道 §5.2 加入。第一个加入者直接被接纳,数字码随即从查找中移除:每张票据只有一个接收方。
  3. HELLO。 发送方的 HELLO 可以携带直连 authority(endpoint),供接收方优先尝试直连。接收方除应用通道的校验外,还检查本地移除记录:接收方已移除的发送方身份会被拒绝,反之亦然。
  4. 猜错口令。 发送方解不开 HELLO 时 reject 该接收方,信箱回到等待状态;第 3 次后票据作废(§7)。
  5. 结束会合。 双方的 HELLO 都校验通过后关闭信箱;会合服务除正常的有界过期外不保留记录。此后每个字节都经隧道传输。

会合服务能得知 kind、数字、时间、两个 WebSocket 源地址、每个信箱的临时参与方公钥以及消息的大小;它得不到口令、任何一方的设备身份键或文件元数据。通信 relay(与会合服务可能不同)看到的就是已注册的记录、slot 关系与加密报文的大小(Quick 协议 v2 §8)。

对未配对的两台设备,双方各安装一个只为本次会话存在的对端:

属性普通已配对对端仅限本次传输的对端
准入应用通道 confirm 配对或按节点 ID 放行 + 允许名单应用通道 pake + 校验通过的 HELLO;从不加入允许名单或待批准列表;不留下移除记录
WireGuard PSK配对派生的 PSK;按节点 ID 放行的对端没有应用通道的 psk(应用通道 §4.1)
AllowedIPs对端的 N6、N4 /128(同一个 /127 地址组)只有 N6 /128
IPv4 别名、DNS 名字、PTR分配并发布无
入站过滤(在 Quick 协议 v2 §5 的入站目的地检查之后)发往本机的任意流量发送方:只接受来自 N6(peer)、发往 [N6(self)]:47901 的 TCP;接收方:只接受对应的返回流;另加用于端点快照与探测的隧道内控制端口 47900
生命周期直到被移除直到会话完成、失败或过期(自创建起 ≤ 10 分钟,加上传输持续有进展的时间,最长 24 h);两端连同其拒绝路由一起删除
是否出现在 peers是只在节点状态中显示为进行中的传输(Quick 模式为 ns quick status)

仅限本次传输的对端就是应用通道的 session 对端(应用通道 §6)。到达对端完全沿用 Quick 协议 v2:双方照常在各自的 relay 注册;接收方把 WireGuard 握手发起报文作为 DATA 发往 HELLO 中记录的 relays 里发送方的 home relay slot,发送方经接收方的 relay 返回。会话建立后,隧道内的 ENDPOINTS 与经认证的探测(v2 §9)像对待任何对端一样晋升直连路径。relay 看到的是普通 DATA,无需改动。

如果两台设备之后通过 quick invite / join 配对,先删除仅限本次传输的对端;其 PSK 从不复用。

容量:仅限本次传输的对端计入每个网络 1 024 个对端的上限,另有每个节点默认最多 8 个并发的单独上限。超过任一上限时,send 与 receive 在任何会合或隧道工作之前以容量错误失败。

所有路径共用一个实现。接收方在隧道内向 [N6(sender)]:47901 建立 TCP。WireGuard 已经用对端的 K 加密并认证了这条流,而签名记录把 K 关联到 I,因此会话层不再增加传输加密,只把这条流绑定到本张票据。

  • Relay 票据。 CPace 已经在会合中完成(§4.2)。接收方在隧道内的第一帧是应用通道的 OPEN(channel_id 为信箱 id,带 K_confirm 的 MAC),发送方以自己的 OPEN 应答(应用通道 §7)。MAC 中的身份必须与承载这条流的 WireGuard 对端一致。
  • 对端票据。 OPEN 不带 MAC,随后在隧道内运行应用通道 pake(应用通道 §5.2),关联数据额外包含 node_id_prefix ‖ session_number。接收方从承载这条流的 WireGuard 对端取得 I_sender,并检查其节点 ID 以票据前缀开头。

两条路径都必须做 PAKE:隧道证明的是对端身份,而不是对端持有这张票据。

带长度前缀的帧 u32 length ‖ u8 type ‖ payload,每帧最大 1 MiB;类型 0 是应用通道的 OPEN,文件发送的帧从类型 1 开始:

  1. 清单(发送方):[{path, size, mode, hash}],使用相对路径与 BLAKE3 哈希,附总大小与文件数。
  2. 确认(接收方):接受或拒绝;断点续传时给出每个文件在暂存区已有的字节偏移。
  3. 数据:文件序号、偏移、数据块。
  4. 校验:接收方校验每个哈希,把文件从暂存区移到目标位置并确认;双方结束会话,并删除仅限本次传输的对端。
规则说明
路径相对路径、以 / 分隔并规范化;拒绝绝对路径、..、空路径段、盘符、NUL 与控制字符,以及 Windows 保留名。目录参数递归发送。
特殊文件发送时跳过符号链接、设备、FIFO 与套接字,并在清单中标为已跳过;接收方从不创建它们。
上限不限制文件数与总大小。接收方在确认前检查目标卷的剩余空间是否足够容纳清单总量,并在写入过程中再次检查。
暂存文件先落在目标卷上的私有暂存目录(仅所有者可访问),哈希校验通过后才移到最终位置。
重名从不静默覆盖已存在的目标文件:接收方会询问,或在 --yes 时写为 name (1).ext。
权限位只携带可执行位;不携带所有者及其他元数据。
主题规则
PAKE 尝试一个会话允许 3 次 PAKE 失败,之后作废。约 33 位熵下,3 次猜测约 3·10⁹ 分之一的概率成功。第一次失败就作废会让一次错误猜测取消整个传输。
会合服务滥用会合信箱的按源与全局限速(会合协议 §6)同样适用于 transfer 信箱;查找未知数字消耗一次尝试。
接收方尝试面对多个对端票据候选时,接收方累计 3 次 PAKE 失败后停止,并提示票据可能有误。
过期若无人兑现,票据在 send 后 10 分钟过期;会合服务执行同样的上限。
一次性成功、失败、过期与取消时都销毁票据、PAKE 密钥、仅限本次传输的 PSK 与对端。崩溃遗留的此类对端在节点下次启动时删除。
密钥分离应用通道的 K_ch 通过不同的 HKDF 标签分别派生 HELLO 的 AEAD 密钥、确认 MAC 与 WireGuard PSK,且都绑定 kind。传输用到的密钥都不是配对凭据。
身份暴露与 Quick 配对不同,用户不比对完整指纹:持有票据即授权一次只读传输。receive 在确认前仍显示发送方节点 ID,以及已知对端的昵称。

直连路径的吞吐受 WireGuard 与主机限制。经 relay 时适用默认的 relay 转发预算:每个发送方与每个目的地 2 MiB/s,突发 4 MiB(§5.2.1)。传输期间节点持续探测直连路径,progress 事件报告当前字节是直连还是经 relay。只能经 relay 的大文件传输会比较慢;本设计不提高 relay 预算。

  • 账户模式。 节点运行在账户模式时,使用同样的会话协议经 Node IP / Node DNS 传输,受 Node L3 ACL 约束(同一 Account 的流放行,跨 Account 的流需显式 Grant);对端由 NSD 已知的 Node 身份确定,不经会合信箱。没有现成 Node 对端时的做法待设计(应用模式 §3)。
  • 并行流,用于高延迟的直连路径。
  • 移动端与 libns 入口。
组件改动
nsns send / ns receive 命令;会合信箱客户端与应用通道(CPace、HELLO、OPEN、会话对端);transfer 的 profile、票据与会话协议状态机;存储投影与数据面中的”仅限本次传输”对端类型(不进允许名单、不发布别名或 DNS、入站端口过滤、有界生命周期、启动时清理);端口 47901 上的会话协议
nsplane节点唯一的数据面引擎;会话对端的安装与移除、隧道内连接(netstack)、按来源命名空间的 ACL 与 pinhole(会话结束或过期时自动关闭)
relay实现与应用无关的 /rendezvous/v1 会合服务(会合协议);不包含任何文件发送专用逻辑
Quick 协议 v2在控制端口 47900 旁保留隧道内 TCP 端口 47901;把仅限本次传输的对端记为本地的、未准入的对端类别。v2 §8 的 relay 报文不变
项目相关机制采纳或否定
magic-wormhole口令短语 + SPAKE2,信箱与中转 relay采纳:信箱式会合,以及经不可信会合服务运行 PAKE
croc口令短语 + PAKE;自 Tailcat 起,每次传输在进程内建立 WireGuard 隧道,密钥由 PAKE 派生最接近的模型:限于本次传输的 WireGuard 对端,这里装进节点唯一的 nsplane 引擎,而不是每次传输另起一个引擎
iroh / sendme节点 ID 票据、QUIC、打洞、relay 回退否定:票据长且没有 PAKE,另起一套传输栈
Syncthing设备 ID = 证书哈希;发现服务与 relay 服务器同样是身份到路由的思路;NS 不需要发现服务
Tailscale Taildrop在隧道内向 PeerAPI 做 HTTP PUT;仅限同一用户的设备采纳暂存目录;否定推送模式
OnionShare通过 Tor onion 服务分享仅在需要更强匿名性时参考

croc Tailcat(2026-09 核对源码):croc vendor 了 tailscale/tailcat,每次传输构建一个带 gVisor netstack 的用户态 WireGuard 引擎;双方节点密钥由 PAKE 密钥派生,端点通过 DERP 以 disco CallMeMaybe 交换,传输后撤销接收方密钥(https://github.com/schollz/croc,internal/tailcat/)。

  • 词表:BIP39 英文词表,还是更短、更易口述的词表再加第四个单词。
  • 节点归属于其他用户时,receive 是否也应可用(目前不可,与 Quick 模式的命令一致)。
  • 已配对但不是 ready 的对端,是否允许在旁边再建一个仅限本次传输的对端,而不是以 peer_unreachable 失败。
  • 24 小时进展上限。