nsplane · 数据面
nsplane 是 NSIO 的数据面库:一个 Rust 库,负责从”本机出现一个 IP 报文”到”加密后的数据报从某条路径发出”之间的全部工作。它源自 cloudflare/boringtun 的分支(BSD-3-Clause),仓库为
dotns/nsplane。本页以nsplane仓库的docs/design.md与docs/architecture.md为准(tagv0.14.0,b28d649,2026-10-13)。从 0.10.x 升级见 0.11 迁移说明,0.12–0.14 的补充在同一页的 §7–§9。改名:项目于 2026-10-02 由 nstun 改名为 nsplane(ADR
2026-10-02-rename-nsplane):它已经不是一个 TUN 设备,而是节点底层的数据通道,TUN 只是它的一种本地接法。仓库为dotns/nsplane;crate 为nsplane-*,原boringtun改为nsplane-noise,原boringtun-cli改为nsplane-cli。
nsplane 拥有报文 I/O(TUN 或进程内协议栈)、WireGuard 引擎、传输、报文过滤(ACL、NAT / 翻译、流计量)以及每个对端路径决策的机制。它与业务无关(ADR 2026-10-06-business-agnostic-scope):只认网段、端口、协议、不透明的标签与规则 ID;主体、组、realm、服务、授权、Quick 地址这类产品概念由使用方在上层编译成通用输入。ns 是使用它的一个产品,nsgw 是另一个。在 NS 的目标架构中它是四层中的第 1 层,其余三层在 ns:
| 层 | 归属 | 内容 |
|---|---|---|
| 4 功能 | ns | 服务发布、反向代理 / 公网入口、子网路由、出口、本地代理、应用(send、第三方) |
| 3 呈现 | ns | 覆盖网络在本机的呈现方式:TUN、用户态协议栈监听、DNS、DNS-VIP、SOCKS5 / HTTP / PAC |
| 2 覆盖核心 | ns | 身份、身份派生的 IPv6、注册表客户端、对端来源(NSD、Quick、应用会话)与合并后的对端表、策略命名空间、名字、路径阶梯的规则 |
| 1 引擎 | nsplane | WireGuard、传输、路径策略机制、过滤链、netstack、TUN、ACL、4↔6 翻译 |
每个节点一个引擎。账户模式、Quick 模式与应用只是同一引擎之上的对端来源和呈现选择,“模式”一词不会出现在 nsplane 内部。CLI(nsplane-cli)只是 Linux / macOS 的开发工具。
应用模式的分工(2026-10-02 定,0.11 起用通用术语):nsplane 提供机制:随会话增删的引擎对端、经 nsplane-netstack 与 Splitter 的隧道内连接、按标签的命名空间 ACL(跨命名空间默认拒绝)、定向授权、Pinholes 类命名空间的出向规则、随会话关闭的 pinhole,以及 4↔6 翻译。ns 保留 /rendezvous/v1 会合客户端与应用通道、app:<session> 对端来源的生命周期、按 kind 分发、第三方应用接入、配对与传输的状态机和 relay 客户端载体。应用会话对已有对端复用现有隧道(经对端来源命名空间允许该应用类型的 pinhole),只为未配对的对端安装仅限本次会话的对端。详见应用模式。
现状(2026-10-13):最新发布是 v0.14.0。v0.11.0(2026-10-08)是不兼容版本:ACL 改为通用核心与通用流网关,翻译器改用 EAM 命名,产品概念移到仓库 docs/specs/ 的规格文档;此后冻结在 0.11.x,只按 ns 的请求开过三次例外:v0.12.0(本地多身份)、v0.13.0(按对端的双栈流网关与运行时 ACL 作用域、去 arc-swap)与 v0.14.0(LAN 前缀映射、按本地身份的密钥路由、快照单元读取开销修复)。ns 的账户模式(M4)与 Quick 模式(M5)都只运行在 nsplane 引擎上:2026-10-10 采用 0.11(按迁移说明在 ns 侧实现被移出的语义),2026-10-12 采用 0.12(每个 membership 一个本地身份),2026-10-13 采用 0.13 与 0.14。网关新线(nsgw R2)以 nsplane 引擎为唯一的 WireGuard 实现;网关作为地址固定的 WireGuard 服务端(UDP 与 WebSocket),使用 0.11 新增的 WssServerTransport。
2. 非目标
Section titled “2. 非目标”- 没有控制面、配对、身份、地址分配、注册表或 relay 客户端协议:nsplane 在被告知的路径上承载数据报,并报告哪条路径通过了认证;路径的含义由 ns 决定。
- 没有产品的身份与策略模型:不解析产品的策略文档,不带产品预设;这些在
docs/specs/写成规格,由使用方实现。 - 不复制、不内嵌任何参考项目(firezone、gotatun、tailscale、NepTUN 等)的代码,无论其许可证如何;只借鉴设计。基础构件用成熟的 crate(
bytes、smallvec、zerocopy、smoltcp、aws-lc-rs)。 - 不做
wg守护进程产品:UAPI 只为开发工具与wg show存在。 - 不做 DAITA、pcap、替代分配器。
- 无 I/O 的核心。
nsplane-core是一个状态机:输入进、输出出,没有套接字,也没有自己的时钟,由驱动拥有它。测试用假时钟让两个核心互相对话;嵌入方(tokio 驱动、移动端宿主、ns 的quick-runtime)从自己的事件循环驱动它。 - 引擎从不自行改变路径。
PathPolicy回答”这条消息走哪条路径”和”一条通过认证的消息从这条路径到达,是否采纳”。内置的StandardRoaming是标准 WireGuard 漫游;ns 插入自己的阶梯。 - 传输是一等公民。 一个引擎可以有多个
Transport(UDP、relay、WSS 载体、候选路径);Path指明传输与地址,携带 ECN。不用假地址掩饰经 relay 的对端。 - 过滤器,而不是特例。 解密到交付、本地读取到加密之间的一切都是
PacketFilter,返回Accept、Drop或Handled:ACL、NAT、4↔6 翻译、节点 L3 门、探测应答、流计量。过滤链按洋葱顺序:从线路侧到本地侧安装,入向按安装顺序、出向按相反顺序执行。 - 可选功能不进基础路径。 翻译、端口映射、ACL、流计量、分片阶段、加密工作线程池、netstack 都需要显式启用;未启用的功能不在数据路径上,基础客户端(TUN + UDP + 对端)不为它们多付延迟。卸载(GSO / GRO)默认开启并在不支持时回退。
- 零拷贝与有界队列。
PacketBuf(带 32 字节头部预留的BytesMut)原地加解密;批次是smallvec;所有通道有界,丢弃计数,网络队列满时反压本地来源。 - pma-rust 基线。 edition 2024,MSRV 1.95,工作区 lint 拒绝警告,运行时代码不允许
unwrap/expect/panic,unsafe只在平台 / FFI 模块并附 SAFETY 说明;没有 CI,just check、just cross、just test-windows、just e2e、just e2e-lib、just e2e-examples是门禁。
| 文档 | 内容 |
|---|---|
| architecture.md | crate 划分、公开接口总览、引擎模型与驱动 trait、反压与统计、可选功能、报文 / 翻译 / MTU、ACL 与应用模式机制、性能、安全基线、作为依赖的使用方式、门禁 |
仓库 docs/architecture.md | 现状的完整说明与测量数据(英文) |
仓库 docs/design.md | 目标设计、路线图与决策(英文) |
仓库 docs/specs/ | 移出 nsplane 的产品语义的规格与对照数据(英文),供 ns 等使用方实现 |
| 0.11 迁移说明 | 从 0.10.x 升级要改什么、可以删什么、行为变化与 ns 待办 |
rustdoc(cargo doc --workspace --no-deps) | 每个公开类型、函数与常量的签名和约定 |
5. 路线图与状态
Section titled “5. 路线图与状态”| 阶段 | 交付 | 状态(2026-10-08) |
|---|---|---|
| 基线 | aws-lc-rs 后端、pma-rust lint、协议修复、零拷贝 noise、Windows 设备层、CLI 定位为开发工具 | 已完成 |
| 1 | nsplane-packet、nsplane-core、nsplane 驱动、nsplane-tun、nsplane-uapi、nsplane-e2e、CLI 迁到引擎上、删除上游的 device 层 | 已完成 |
| 1b | 改名为 nsplane、crates/ 目录布局、清理 | 已完成(5578a90) |
| 2 | 多传输、PathPolicy、带 Handled 的过滤链、报文注入、force_handshake、暂停 / 恢复、引擎驱动的定时器 | 已完成 |
| 3 + 4 | 按传输的反压、nsplane-netstack、Splitter / MergeSource、nsplane-acl、示例与呈现 × 传输矩阵、ACL 命名空间 / 授权 / 出向规则 / pinhole | 已完成 |
| 5 | nsplane-nat(翻译、conntrack、端口映射)、分片阶段、洋葱过滤顺序、TUN 与 UDP 卸载、ACL 流钩子、队列高水位、加密工作线程池 | 已完成 |
| 5 后续 | 批量核心入口、无工作线程时无锁、精确的发送失败计数、工作线程 / 分片统计、smoltcp 分支 | 已完成(91c4943) |
| 状态 | 按传输的流量计数、EngineHandle::status | 已完成 |
| ns 请求两轮 | M4 钩子(inject_outbound_on、inbound_from、observe_every_message、connect_tcp_from…)、数据面搬迁(owns()、重组、UDP 旁路通道、LinkTransport、Nat64Lan、Redirect、force_handshake_on)、每路径 MTU 与 PMTUD、nsplane-wss 的 plain ws / 事件 / 保活 | 已完成 |
| 吞吐与 WSS | 对内核 WireGuard 与 wireguard-go 的基准;引擎快路径;netstack 修复与 TCP 缓冲;nsplane-wss(数据报载体、WsFrame 流客户端与服务端) | 已完成 |
| ACL 与 L3 门、本地侧 | 按来源的身份标签、分片模式、Ipv6Mode、入站目的地;MapSink / MapSource、pump、pipe、Masquerade、echo_reply_in_place、TunSlot、host_tun | 已完成 |
ns 请求第二轮(20261006-0900) | 本地图原语(Splitter::new_map、MapSink::with_after、SwapSink、AbortSink)、缓冲回收(SharedPacketPool、send_batch_spent)、Windows 服务 TUN(Wintun 固定、独占、遗留网卡替换、双栈 MTU)、netstack PMTU 与尾部丢包探测、datagram_capacity 256、无工作线程时的投递余量(sink 满时反压)、Event::Authenticated 限频、调用方更新的入站目的地址 | 已完成 |
| 发布 | v0.8.0(2026-10-04,首个发布)、v0.9.0(2026-10-04)、v0.10.0(2026-10-05,优化轮:引擎快路径、按对端并行加密、UDP 批处理、TUN 缓冲)、v0.10.1(2026-10-06,修复开加密线程时本地报文在 transmit full 被丢弃)、v0.11.0(2026-10-08,不兼容:业务无关)、v0.12.0(2026-10-12,本地多身份)、v0.13.0(2026-10-12,按对端的 IPv6 流网关、运行时作用域、去 arc-swap;破坏:GatePolicyError::Ipv6 删除,Nat64Lan::new 改收 LanRoutes)、v0.14.0(2026-10-13,LAN 前缀映射、按本地身份的密钥路由、快照单元读取开销修复;全部为增量) | 已完成 |
业务无关轮(20261007-0900) | 去掉 nsplane 中的产品概念:nsplane-acl 的通用核心(标签、规则 ID、策略状态、Flow 判定)与通用流网关 gate 取代身份断言、策略文档与 Node L3 模型(移到 docs/specs/);翻译器 EAM 命名;just check 末尾的产品术语扫描;网关用的 WSS 服务端传输;WsFrame 流载体弃用 | 已完成(2026-10-07),发布于 v0.11.0 |
发布 v0.11.0 与冻结(2026-10-08) | 0.11.0 合入 ns 请求第二轮与业务无关轮,破坏公开 API;随后冻结在 0.11.x(ADR 2026-10-08-feature-freeze-0-11):只修 bug,以 patch 版本发布,不加功能、不改 API,使用方需要新 API 时记在它的任务里等待 | 已完成 |
使用方集成(20261008-0800-consumer-integration) | ns 从 v0.10.0 升到 v0.11.0:探测显示 crates/overlay 有 11 处库编译错误(ACL 通用化、来源身份、节点 L3 门、翻译器改名、WsFrame 弃用、统计结构新字段),清单见 0.11 迁移说明;nsgw 在引擎上的重建开始。nsplane 只提供探测、迁移说明与 0.11.x 修复,不改 ns / nsgw | ns 已完成 0.11(2026-10-10)、0.12(2026-10-12)、0.13 与 0.14(2026-10-13)的采用;nsgw 的重建仍是草案 |
本地多身份(20261011-0900) | 一个引擎持有多把静态密钥(本地身份),每个对端绑定一个身份;按 mac1 分派握手发起,所有身份共用一份握手预算、各自有 cookie 秘密;身份可限定在指定传输上以防跨覆盖网探测;单身份时线上字节与开销不变 | 已完成,发布于 v0.12.0(冻结期间的唯一例外) |
按对端的流网关(20261012-1800-peer-keyed-gate,GF-1..4) | 流网关对 IPv4 与 IPv6 一视同仁;GateBinding 不列地址即按 PeerId 绑定该对端任一地址族的全部报文;出向按目的地授权与保留;AclFilter::replace_scopes 在运行时按本地身份原子替换作用域。IPv4-only 策略的行为与开销不变 | 已完成,发布于 v0.13.0(冻结期间第二次例外,应 ns 的请求) |
去 arc-swap(20261012-1830-no-arc-swap,AS-1..2) | arc-swap 1.9.x 的混合策略在多实例下有可靠性缺陷(跨实例读到别的值、丢更新、非法释放):nsplane-core 新增 std-only 的分片快照单元 SnapshotCell,ACL 引擎、作用域、流网关、翻译器与 NAT64 LAN 路由改用它;Nat64Lan 改收 LanRoutes 句柄 | 已完成,发布于 v0.13.0;安静主机上的完整 A/B 留到发布后 |
LAN 前缀映射与按本地身份的密钥路由(20261013-1310-lan-prefix-and-identity-routing,LP-1..2、LR-1..2) | LanPrefix 的 lan6 可以是 /(96 + IPv4 长度)、低位嵌入另一个 IPv4 网络(RFC 7757 风格),一个远端 LAN 可经本地自己的 IPv4 网段到达,/96 形式不变;允许 IP 按对端的本地身份划分,Core::set_local_sources(identity, sources) 用源地址集合选出向身份,route_from(src, dst) 暴露路由决定,同一远端密钥与地址可同时服务多个身份;单身份或无源集合时开销不变 | 已完成,发布于 v0.14.0(冻结期间第三次例外,应 ns 的请求) |
快照单元读取开销(20261013-1330-snapshot-cell-read-cost,OP) | SnapshotCell::read 改为按线程缓存的闭包读取:一次引擎快照读取从 0.13.0 的 15.8 ns 降到 2.9 ns(0.12.0 为 8.8 ns),多数 ACL 路径比 0.12.0 更快;残余(出向翻译 +6..7 %、流网关已建立路径 +3..7 %、使用源集合时每往返约 +60 ns)记在任务 20261013-1600-perf-residuals,入向分片路径的 2.7 ns 回退已在 Unreleased 修复 | 已完成,发布于 v0.14.0 |
| 6 | ns 迁移:tunnel-wg 与 quick-runtime 两套数据面都迁到引擎上(在 ns 进行,见新架构 阶段 C、D) | 两套都已完成(ns M4 2026-10-03、M5 2026-10-04);MG、MR、MT、MD-7 等 ns 请求已在 0.11 提供,ns 侧的删除与 0.11 适配见迁移说明 |
仓库内未完成的事项:Windows 真机验证(场景见仓库 docs/task/20261006-1700-windows-smoke-for-ns.md,请 ns 的 Windows 冒烟测试执行);把 smoltcp 修复提交上游(待决定);发送端限速的设计(无工作线程时的接收反压已在 0.11 落地);WsFrame 流载体在 ns M6 之后删除;relay v2 节点客户端的归属(建议在 ns 或 nsshared)。阶段 0(ns 固定到 nsplane 的 noise 层,decapsulate 的源地址参数从 IP 改为 SocketAddr)独立于上述阶段,在 ns 当前重构落地后进行。
| 日期 | 决策 |
|---|---|
| 2026-10-01 | 分支基线:ring → aws-lc-rs;没有 CI,本地门禁;unsafe 只在平台 / FFI 模块;接受 Wintun C 库 |
| 2026-10-02 | CLI 是 Linux / macOS 开发工具 |
| 2026-10-02 | 完整数据面,无 I/O 核心;Transport 与 PathPolicy 分开;上游 device 层在第 1 阶段后删除 |
| 2026-10-02 | 不复制参考项目代码;缓冲区基于 bytes |
| 2026-10-02 | 值类型(PeerId、Path、TransportId、Ecn)放在 nsplane-packet,由 nsplane-core 重新导出 |
| 2026-10-02 | 每个 NS 节点一个引擎;4↔6 翻译与分片在引擎内;两套 ns 数据面都迁移 |
| 2026-10-02 | 改名为 nsplane,已执行;crate 统一保留 nsplane- 前缀 |
| 2026-10-02 | 示例包用真实的 WebSocket over TLS;单端口 relay 使用 WireGuard 未定义的消息类型,标准 WireGuard 对端照常可用(与 ns / nsgw 共享的线上约定,状态 Proposed) |
| 2026-10-02 | 应用模式分工:nsplane 提供对端、隧道内连接、ACL 命名空间、授权、出向规则与 pinhole;ns 负责会合、kind 分发与应用状态机;对已有对端的应用流量只经来源命名空间允许的 pinhole,随会话关闭 |
| 2026-10-02 | ACL 做成按流的钩子:未安装时零开销、按对端旁路、带代际的判定缓存 |
| 2026-10-02 | 所有可选功能显式启用,未启用时不在数据路径上;卸载默认开启并可回退 |
| 2026-10-03 | 批量核心入口与只取已排队报文的引擎批处理(不等待、无定时器,本地读取受发送余量约束);饱和时接收端 sink 满的丢包被接受,限速另行设计 |
| 2026-10-03 | nsplane-netstack 依赖固定 tag 的 dotns/smoltcp 分支,原有绕过代码删除 |
| 2026-10-03 | 每一种数据通道协议(传输与流载体)都在 nsplane 实现,ns 只提供配置与业务决策;重依赖放在可选 crate(nsplane-wss) |
| 2026-10-06 | 业务无关:nsplane 不含产品概念(Node L3、账户、Quick 等),ACL 以通用标签与规则表达;just check 用产品术语扫描把关 |
| 2026-10-06 | 被移出的产品语义写成规格文档(docs/specs/)由使用方实现,0.11 不提供兼容层 |
| 2026-10-07 | 网关(nsgw)是地址固定的 WireGuard 服务端(UDP 与 WebSocket),不做穿透、不用 relay 客户端;nsplane 提供通用的 WSS 数据报服务端传输,不提供 WsFrame 接受端与按令牌绑定对端 |
| 2026-10-08 | 冻结在 0.11.x:只修 bug,patch 版本从 main 发布;新功能、优化、重构与 API 变化等所有者解除冻结;已弃用的 WsFrame 流载体在解冻后才删除 |
| 2026-10-11 | 冻结例外一:本地多身份(应 ns 多 membership 的请求),发布为 v0.12.0 |
| 2026-10-12 | 冻结例外二:按对端的双栈流网关与运行时作用域、去 arc-swap,发布为 v0.13.0;性能复测推迟到发布后 |
| 2026-10-13 | 冻结例外三:LAN 前缀映射、按本地身份的密钥路由(应 ns 的请求)与快照单元读取开销修复,发布为 v0.14.0;性能残余记录后在后续 patch 处理 |
| 2026-10-03 | 状态与流量:nsplane 只提供计数(status、transport_stats);速率、指标导出与直连 / relay 标签在 ns |
设计参考(只借鉴设计):firezone connlib(无 I/O 节点、I/O 驱动、ip-packet、卸载)、Tailscale tstun / wgengine(过滤钩子、注入、头部预留、零拷贝接收)、mullvad/gotatun(I/O trait、builder)、NordSecurity/NepTUN(加密工作线程)、wireguard-go conn.Bind 与 netbird ICEBind(单一传输对象的替代方案及其代价)、EasyTier(多传输 mesh)、tun2proxy/ipstack 与 netstack-smoltcp(用户态协议栈)。