跳转到内容

NS · 账户模式(NSD)

账户模式是 NS 的默认使用方式:设备登记到 NSD,由 NSD 签发身份关系、编译 Access Grant 并投影配置,NS 在端侧执行。本页描述账户模式的功能划分与设计特点,以 ns 仓库分支 refactor/nsplane(a9c6a090,2026-10-07) 为准。NS 的另外两部分见 Quick 模式 与 应用模式,进行中的计划见 roadmap.md。

账户模式下,同一个 NS 进程既消费其他 Node 发布的资源,也发布本机资源;访问与发布只是同一条 Network 内不同 pair 上的方向差异。

  • 产品面:ns CLI(Linux / macOS / Windows)、libns C ABI(桌面与移动 App 嵌入)、官方容器镜像(作为业务容器的网络 sidecar)。
  • 价值锚点:
    • 统一节点 —— 一个身份、一个二进制,同 Account 下的 Node 之间默认全双向 L3 互通。
    • 本地能力声明是权威 —— 能发布什么由 services.toml 与 ns subnet 决定,NSD 只能裁剪不能凭空生成。
    • 三种数据面共享上层 —— 路由、DNS、ACL、链路阶梯在 tun / dns-vip / proxy 下是同一份代码。
    • 私钥不出节点 —— 协议级硬约束。
  • 机器身份:Ed25519 身份键(签 Envelope、登记)+ X25519 对等键(WireGuard)。Device 由身份公钥派生并记录在 Organization;Node 是 Network 内的 (Network, Device, Account) Profile,Node IP / Node DNS 名都是 Network 范围的。
  • 首次登记:ns up --server <URL>。NS 打开浏览器 / 设备授权流,由已登录 Account 批准;批准绑定精确的请求设备公钥。一次性 invite 走同一路径。租户 auth key 已不再被 NSD 接受。可选 --hostname 请求精确的 DNS label。不指定 --server 且本机没有登记时使用内置默认 NSD https://nsd.dev.ns.io(决策 2026-10-10-default-nsd;已登记的设备保持它提交的 NSD,--server 与 invite 优先)。
  • 多 membership 同时连接(2026-10-12,决策 2026-10-11-node-memberships):<state-dir>/state/manifest.json(schema v3)记录已连接的 membership 集合,每个是某个 NSD 的一个 Network(nsd:<uuid>/<network>),全部平等,没有 active NSD、主 NSD 或优先级;每个 NSD 在 state/nsds/<uuid>/ 下拥有独立的身份、Account、Space Profile、配置、数据、cache 与信任命名空间,同一 NSD 的多个 Network 共用该 NSD 的身份与登记,可以同时连接。ns network join <NETWORK_ID> [--server <URL>] / ns network leave <选择器> / ns network list [--json](ns.network-list.v1)增减 membership,守护进程运行时即时生效且不触碰其他 membership;ns nsd switch 已删除,ns nsd list / remove 保留;ns logout --server <URL> | --network <M> 只退出一个 NSD 并断开它的 membership。没有已连接 membership 时启动失败并提示 ns up --server <url>。schema v2 的 manifest 被拒绝并提示 ns reset --all:不迁移旧状态、旧身份,每台设备重新登记。
  • 一条选择器规则:作用于单个 membership 的命令(ns exit、ns subnet 含 ns subnet dns、ns space list / switch、ns policy)用 --network <选择器>(membership id、Network id 或 quick),NSD 选项用 --server;只有恰好一个 membership 或 NSD 时可以省略,否则命令失败并列出候选。ns space switch 在守护进程运行时只切换所选 membership。
  • 一个节点守护进程:带 TUN 的守护进程持有机器级锁,第二个状态目录的守护进程以 machine-daemon-busy 失败;免 root 的 proxy 守护进程不取锁。ns status --json(ns.status.v1)与 ns info --json(ns.info.v1)顶层是设备项,memberships 数组每个 membership 一项(种类、NSD、Network、Space、登记、控制状态、对端、冲突、投影的 Node、出口);没有单 NSD 字段。
  • 设备密钥链(2026-10-11):ns key init / unlock / passphrase / status。一个随机根密钥 R 由设备口令包裹(Argon2id + XChaCha20-Poly1305)并缓存在 secure store;其余密钥都是 derive(R, path)(HKDF-SHA256):每个 NSD 的 membership 身份 membership/<salt(规范 NSD URL)>/identity/v<N>(Ed25519 身份与 WireGuard 子密钥,轮换只重登记该 membership)、Quick 身份、本地状态密钥。ns up / ns quick up 先建立或解锁密钥链(--passphrase-file 或 NS_PASSPHRASE_FILE,否则在终端提示),锁着时以 KeyChainLocked 失败,libns 启动报告 key_chain_locked。不同 NSD 拿到的身份互不关联。
  • Space(Network)切换:ns space list / switch <NETWORK_ID> 只提交新的 active Profile,切换后保持 disconnected。App 持有运行时时,CLI 只读同代 committed snapshot。
  • 本地状态边界:state.lock 是不可替换的外层锁;破坏性 ns reset [--all] 按固定顺序(runtime 预留 → try-acquire 锁 → snapshot → 系统清理 → 平台凭据 → identity → 清空 state-dir)执行,争锁立即返回 E_RESET_STATE_BUSY。
  • 凭据与保险库:ns secure-store(file 或平台 keyring 后端)保存身份与代理凭据;ns credential 管理认证代理凭据;本地代理凭据是设备级的(<state-dir>/local-proxy/,不属于任何 NSD,ns proxy credential 与 ns doctor 不需要 Profile),按 NSD 的 secure vault 已删除;ns logout 只退出账号、保留安装与 Profile。
  • Account / Organization / Network 上下文:ns info 展示当前 Account、Organization、Network 与 Node 清单,后台刷新并离线缓存。
  • 信任发现:只接受 HTTPS origin;GET /api/v1/info 经系统 WebPKI 校验后读取 control_trust.v1。HTTP、跨源跳转、未知版本、非法 trust、transport 不匹配、legacy 顶层 trust 字段一律 fail-closed。--tls-ca-file 可追加私有 CA(不替换 WebPKI 根)。
  • 传输选择:--control-mode auto|sse|noise|quic,默认 auto:按发现顺序优先 SSE,同一传输连续三次可重试失败后冷却并切到下一候选;显式模式保持强制与 fail-closed。只有控制流会话(认证、services.report 前置、连接、读取)驱动候选健康,后台 unary 上报不改变候选。
  • Envelope v1:POST /api/v1/control CBOR 信封,Ed25519 签名 + 16 字节 nonce + ±60 s 时钟窗;响应由 NSD 签名并按 discovery 的 thumbprint 集合校验。
  • 凭证生命周期:短期机器 JWT 经 token_refresh 事件滚动;config stream 重连不再饿死刷新;pair WSS 返回 401 时触发运行态重认证并换入 client / provider token,403 保持投影拒绝语义。
  • 心跳与上报:60 s machine.heartbeat(服务端 TTL 300 s),携带 pair 异常快照(每次递增报告序号);services.report 上报本地 Service;Exit 选择 / 撤销经认证控制通道提交。
  • 来源级 ACL 运行时:以来源 NSD 为键保存 AclEngine;无引擎即拒,ExplicitEmpty / 来源撤销 / 加载失败立即拒,FetchFailed 保留 last-known-good。
  • 每个 membership 一条控制连接(2026-10-12):各自的 ControlPlane(Network 的控制 URL)、machine.auth 令牌与刷新、心跳、Node L3 ACK 与 Short DNS 上报;路由、DNS、网关快照按 membership 保存后合并成带来源标记的视图。某个 membership 失败(令牌刷新或认证失败、被撤销)只撤回它自己的 Node、策略、路由、DNS、网关与令牌,按自己的退避(1 s 起翻倍到 60 s)重连;受管配置禁止的 membership 不启动,显示 Forbidden。
  • 上报隔离:只向 NSD 上报该 membership 自己的事实。local_ips 与打洞候选不含任何 ns 引擎启动的地址(common::engine_addrs 登记 TUN、Quick、各 membership 的 N6 / N4 / carrier4、self4 与 alias4);态势、事件与候选上报器各只服务一个 membership;Node L3 ACK 只是回执(network_id、目标 machine id、代际、模式、快照哈希),不说明哪些规则被保留或拒绝。
  • 节点策略权威:ns::policy 按“提议 → 受管配置锁定 → 所有者决定”逐条决定每个 membership 的生效规则(Node L3 授权、NSD ACL、来源策略),所有者可以收窄但不能放宽第三方的授权;决定存在 <state-dir>/policy/decisions.v1.bin(密钥链派生的密钥加密),ns policy list / accept / reject / set [--network M] 查看与编辑;受管配置是 root / SYSTEM 拥有的只读 managed.json(允许与禁止的 membership、Quick 开关、锁定的规则决定、出口与 DNS)。内置规则:从不在 membership 之间转发。
模式权限入口适用
tun(默认,alias l3)root / CAP_NET_ADMIN内核 TUN;Node IP、Service VIP(100.96.0.0/15 本地铸造)、子网 ULA、Exit 捕获路由由 NS 安装统一 L3 + L4:Node 互通、Service、子网、Exit
dns-vip免 root本地 DNS + 127.127.0.0/16 环回 VIP listener(每条 VIP:port 一只 TCP 监听)只需要 L4 Service 访问的受限终端
proxy免 rootSOCKS5 127.0.0.1:1080(无认证 CONNECT)、HTTP 代理 127.0.0.1:8080(CONNECT + 明文 HTTP)、PAC http://127.0.0.1:9091/proxy.pac、系统自动代理设置(默认开启,退出 / 崩溃后恢复)浏览器与不能改路由的环境

三种模式共享路由表、Short DNS、预检 ACL、链路阶梯与 WireGuard 客户端;差异只在”用户进程的包如何进入 NS”。proxy 模式对非 NS 目标的策略可配:PAC 未命中默认 direct,认证代理未命中默认 reject,防止本地代理沦为任意出口。

本机 IPv4 地址计划(2026-10-12 起,节点本地,不再由 NSD 分配):100.64.0.0/11 是账户别名池(self4 = 100.64.0.1、每个对端一个 alias4、每个 membership 一个 carrier4,按身份的候选地址取第一个空闲的);Service VIP 100.96.0.0/15(访问端本地铸造);100.98.0.1 是设备级出口连接器 EXIT4(所有出口载体的源),100.98.0.2 – 100.126.255.255 是重叠 LAN 的本地虚拟网段池;网关 100.127.0.0/16。都不与 RFC 1918 重叠。线上只有身份派生的 N6 / N4,IPv4 只是本机视图:本机到对端走 self4 → alias4,经该 membership 的翻译器到达对端的 N4。包身份仍来自精确投影(身份键与 /128),从不由范围推断。

  • 登记声明:--transport auto|wg|wss(能力集:auto = wg-relay / direct 优先、wss-relay 作 TLS 回退;wg 只声明 WG;wss 只声明 WSS);--gateway-terminate 是能力标志而非传输模式。source 级策略持久化,随登记对齐。
  • 投影:NSD 在 WgConfig 每个 peer 上附带 wg_relay(NAT 学习源 + 网关)、wss_relay(不透明 leg 拨号提示)、direct 候选(PeerCandidates)。NS 本地为每个 wg_relay peer 合成同网关的 wss_relay_fallback;同一 pair 两端按 machine id 稳定生成互补的 client / relay leg。
  • 阶梯:wg-relay pair 在有真实出站需求、连续两个窗口无握手 / RX 证据时,先向原 relay 端点强制握手验证,再降级到同 gateway_id 的 WSS;健康证据可取消 pending 转换;空闲 pair 不降级;恢复有冷却与退避。direct 只在已有经认证的 WG 会话后竞争,不做冷启动或跨网关选择。
  • 权威合并:NSD 的 pair transport 权威(capability-gated)覆盖同 peer 的本地决策;本地决策仍上报以便 NSD 收敛。
  • WSS 不透明 pump:为每个 wss_relay peer 绑定本地 OpaquePump,把引擎的密文经网关 /client?mode=wss-relay&nsn=<id> 或 /wss-relay?nsc=<id> 缝合到对端。
  • direct 晋升:直连探测前先经当前 wg-relay 协商短期能力(旧版本无法应答时安全保留 wg-relay);候选必须从同一精确端点连续三轮、每轮 16 个 1200 字节加密外层报文无丢失返回才算本端就绪;只有两端都在加密响应中声明就绪后才切到 direct,退回 relay 清除协商与验证状态。有效配置为 opaque WSS relay 的 peer 不参与 direct 候选、打洞或 roaming。
  • 网关 RTT 探测:声明 relay_gateway_rtt.v1 的节点接收 NSD 投影的 gateway_config.probe_roster。每 20 s 的反射轮次同时探测 sticky anchor 与 roster 中每个端点(各自 nonce);roster 回复只更新 RTT 统计,不改变 server-reflexive 候选与候选代际。平滑 RTT 映射为 lt30 / lt100 / ge100 三档(边界有 20 % 滞回),连续三轮无回复记为 unreachable。只有 NSD 确认 gateway_rtt.v1 时,心跳才携带完整列表 gateway_rtt:档位变化时立即发送,且至少每 5 分钟一次。
  • 可观测:ns status --json 按可信 gateway_id 展示 relay pair 汇总与单 pair 明细(connected / stale / never_handshaked / disconnected、握手年龄),不暴露端点与公钥。

自 2026-10-03(迁移里程碑 M4)起,账户模式只运行在 nsplane 引擎上,没有运行时开关,也没有旧数据路径;2026-10-04 的后续轮次补上了直连路径并删除了全部旧数据面 crate(tunnel-wg、netstack、tunnel-ws、ns-engine)。2026-10-10 升到 nsplane 0.11:Node L3 模型移回 ns(account_engine::wg::node_l3),编译成 nsplane 通用流网关的一份策略,对 nsplane 对照数据(6,361 个报文步)的回放只有已计数的规格偏差。2026-10-12 起账户模式作为节点引擎上的 membership 运行(§2.1):EngineTunnel 与 NSD 分配的 IPv4 已删除,Node 是其 Network 来源的身份对端。控制面协议(Envelope v1、配置流)、WireGuard 中继(register_source)、gateway.reflexive、不透明 WSS 中继与代理 WsFrame 线上格式不变,但 WgConfig 的内容已变:没有 ip_address,node_identity 携带 Node 的 Ed25519 身份键 identity(CBOR 字节串,JSON 为 hex)而不是 tunnel_ip,withdrawn 是唯一的撤回方式。当前 NSD(6bce2220)仍投影 tunnel_ip 与 /32,所以这条分支的账户模式目前只对 ns 自带的模拟 NSD(MockNsd)与模拟网关(SimGateway)运行,等 NSD 的 M6 配套工作。规则:ns 每个进程只运行一个 nsplane 引擎,只做业务逻辑与控制面;每一处数据面代码(WireGuard、传输与载体、TUN、用户态栈、NAT 与翻译、ACL 执行、报文泵)都属于 nsplane。nsplane 以发布 tag v0.14.0 固定(2026-10-13;smoltcp 用 dotns/smoltcp 分支 v0.14.0-nsplane.6):0.13 的双栈流网关与运行时作用域、0.14 的 LAN 前缀映射已接入,0.14 的按本地身份密钥路由尚未。

部分实现
引擎宿主ns::node_engine 是 ns 里唯一构建并运行 nsplane 引擎的地方:EngineHost::start 接 PacketIo(经 nsplane-tun 的 TUN,或 nsplane-netstack),或 start_on 接调用方自己的报文源与汇;NodeEngine 自身没有密钥,每个 membership 是一个 nsplane 本地身份,限定在自己的 UDP 传输与 relay 传输槽上(别的 membership 的传输上收到它的握手不应答);账户模式与 Quick 都跑在它上面,它本身没有逐报文代码
membershipAccountMembership(account_engine::membership)在进程的账户引擎上运行一个 NSD:密钥链身份 I_m 与 WireGuard 键 K_m 作为本地身份,该 NSD 的每个 Network 是这个身份的一个来源(nsd:<uuid>/<network>,只贡献自己的 Node);NsdPlane 合并各 Network 的配置(投影一致的对端安装一次,不一致的全部不装)、运行该 NSD 的 relay WG 运行时并发送网关状态更新;每个网关(terminate、wg-relay / WSS 中继、出口)是 membership 自己安装的载体(install_carrier),只带它自己的路由
控制代码account_engine::wg 保留原 tunnel-wg 的控制部分:Node L3 模型(node_l3:快照、来源与墓碑、设备投影、Provider 监听、就绪、ACK、Subnet 查询)及其到 GatePolicy 的编译、动态 L3 租约表、反射发现与候选上报、wg-relay 登记类型、恢复探测编码、对端准入与状态类型;只做决策与配置
对端每份 WgConfig 按原有规则准入(每个 machine id 与每个身份一个 owner,网关必须带 gateway id);每个 Node 带 Ed25519 身份键,成为 membership nsd: 来源的身份对端,合并对端表把它装在 N6 / N4(加 NSD 的 IPv6 前缀与动态 L3 租约路由),本机经 self4 → alias4 由该 membership 的翻译器到达;新密钥按代际替换旧密钥;同一个远端身份在两个 membership 里是两个引擎对端
传输每个 membership 自己的 nsplane UdpTransport(relay 传输槽也按 membership 预留),承载到 terminate 对端、Node 对端与 WireGuard 中继的报文;它的旁路通道(SideSender)承载 wg-relay 登记数据报与 gateway.reflexive 探测,NSGWP2P1 应答在进入 WireGuard 前被取走。WSS 中继对端(wg_relay 的不透明 wss-relay leg 与 Gateway Exit 载体)经 WssLeg:一条 nsplane-wss WssDialer 链接作为独立 LinkTransport 加入引擎,WgConfig 中用合成的回环端点;每个数据报一条二进制 WebSocket 消息,线上格式不变。代理 WsFrame 客户端跑在 nsplane-wss WssStreamClient 上
过滤器自线路侧起:LinkFilter(载荷与需求计数、NSPATH01 交接)、控制通道、该 membership 的 AclFilter(来源策略命名空间加载体默认规则;作用域只接受 N6、N4、carrier4 为源)、翻译器,然后本地侧的 Node L3 门(account_gate:nsplane-acl GateFilter 加网关消费者分流):判定本地 IPv4 视图(self4 / alias4),Enforce 下也按同样的绑定、授权与保留双向判定 membership N6 与 Node N6 之间的原生 IPv6 流,没有授权的出向原生 N6 被拒绝(2026-10-13,nsplane 0.13);经 membership 的 Subnet L3 回程身份随租约变化在运行时加入 ACL 作用域。Node L3 由 ns 编译成一份 GatePolicy:每个 membership 来源与模式一个作用域,同一 NSD 多个 Network 共享的 Node 按各 Network 授权的并集判定,两个 membership 绑定同一对保持 ambiguous_network;丢弃原因读作 flow gate: <reason>。acl crate 只保留策略编译与按流的 AclEngine。IPv6 由引擎核心按目的地授权:每个对端的入站目的地按 fail-closed 顺序推送
网关 IPv4 契约到网关的 IPv6 从 membership 的 N6 / N4 发出;到网关载体的 IPv4(terminate 服务、出口默认路由)从该 membership 的本机 carrier4 不经翻译在 WireGuard 内承载,地址不上报,所以 nsgw 必须接受已准入客户端的任意 IPv4 源并按 WireGuard 对端做伪装(模拟网关 SimGateway 这样做,真实 nsgw 尚未实现);出口载体从设备级出口连接器 EXIT4(100.98.0.1)发出,Gateway Egress 从各 membership 自己的 carrier4 发出。这与迁移计划 D3 的“网关只有 IPv6”不同,见那里的 2026-10-12 修订
分片引擎分片器把本地报文控制在隧道 MTU 1360 内:DF 未置位的 IPv4 分片,DF 置位的 IPv4 回 ICMP Fragmentation Needed,IPv6 回 Packet Too Big,均限速
路径overlay 的 Ladder 是引擎的 PathPolicy,账户的 AccountProbe 作为 DirectProbe:向交叉发布的打洞候选探测直连,先建后拆地从 relay 切换,执行 NS-RELAY-004..007;用 inject_outbound_on 发探测、inbound_from 读到达路径、observe_every_message 观察每条消息、force_handshake_on 强制握手;恢复探测在到达路径上应答。不在阶梯上的对端按 WgConfig 走,只有网关 terminate 对端漫游。已晋升的直连路径空闲时不再每两分钟回落到 relay
用户态栈account_engine::stack 在 nsplane-netstack 上产出发布端、TUN 服务与移动端 DNS 消费的 TCP 连接 / UDP 流事件;account_engine::client_stack 在 nsplane-netstack 上运行每个 wg_client 的客户端侧,包括出口占用主机源端口的运行时。接受有反压,TCP 缓冲可配置
服务 TUNLinux / macOS 经 nsplane_tun 打开并由引擎直接泵送;Android 用 TunSlot 接管宿主 VPN fd;iOS 跑在 nsplane-tun 的宿主 TUN 上;Windows 在 nsplane MT-3 之前仍用 tun crate。客户端栈不拥有的解密 L3 报文经 nsplane Splitter 分到 TUN、Subnet 会话栈或 Subnet ICMP Echo 处理器之一
翻译TUN 服务与 Subnet 翻译跑在 nsplane-nat(Nat64Lan、Redirect),Subnet LAN 入口用 Masquerade,Subnet Echo 应答由 nsplane 的 echo_reply_in_place 合成
观测ns 自有路径上的逐报文丢弃导出为 nsc_packet_drops_total{path,reason}(tun_ingress、tun_service、netstack_to_tun、subnet_lan);crypto_workers 保持 nsplane 默认 0(评估 +12..24 % CPU,无收益)

ns 里仍手写的逐报文循环只剩入口分流、netstack 到 TUN 的循环、出口加密交换与 pump_exit_packets_to_wg,各对应 nsplane 的 MG-4 / MG-5 / MG-6 事项;nat crate 只剩 ServiceRouter 与 SubnetRoute(Set) 两个控制侧模型。发布的快照(路由表、链路映射、阶梯视图、Node 绑定、ACL 引擎、服务)用 common::snapshot::Snapshot(16 个分片的 RwLock<Arc<T>>,安全 Rust)而不是 arc-swap(1.9.x 有跨实例复用缺陷)。

membership 运行时的已知边界(2026-10-13,工作流 A–E 全部合入,多节点互通套件通过):两个 membership 投影同一个 Node 身份时只有先绑定的可达,后者在 conflicts 里列出并在前者放手后接管(nsplane 0.14 已提供按本地身份的密钥路由,ns 尚未接入);网关 IPv4 契约只在模拟网关里实现(等 nsgw);同时只能有一个默认路由出口(设计如此);运行中加入的 membership 的出口下次启动才生效;移动端的出口与本地网段 fail-closed;在跑着 NSD membership 的守护进程上 ns up --invite 需要先 ns down;ns logout 会停掉 CLI 拥有的运行时,其他 NSD 的 membership 随之停止直到下次 ns up。

发布门(用户在发布前执行):Windows / macOS 原生 TUN smoke(just windows-smoke 已自动化,账户引擎部分在 Wintun 上通过)、tests/live-e2e、吞吐不低于旧基线(在嘈杂主机上的配对 A/B 为 −3.7 %,在噪声内,待在安静主机上复测)。已知未闭合:Windows 上 quick0 的 MTU 为 65535(nsplane MT-3)。

  • 声明:--services-file(默认 /etc/nsio/services.toml)。文件缺失视为”不发布”;文件损坏使启动失败,避免被误认为有意为空。父目录稍后出现会自动恢复热更新(5 s 重试 / 60 s 对账)。
  • 上报与回执:services.report → NSD 合成目录、路由与 Short DNS → services_ack 三态(已匹配 / 上报但未绑定 / 拒绝)。
  • 执行:发布端 Service PEP 对每条 Service 流做 fail-closed 终决,services.toml 是不可绕过的 floor;访问端在流创建前做预检 ACL。
  • VIP 铸造:访问端为每个可达 Service 在 100.96.0.0/15(tun)或 127.127.0.0/16(dns-vip)本地铸造 VIP;NSD 从不分配 Service VIP。
  • Short DNS v2:NSD 为 Network 分配短 token,为 Node 分配 label;NS 能力协商后消费签名投影,为每条记录提供 canonical FQDN 与两个短名 alias(Node 与 Service 命名空间分离),并安装平台 resolver scope。撤回的 zone 保留为 NXDOMAIN tombstone,绝不回落公网上游。
  • 按 membership 的命名空间(2026-10-12):查询按后缀路由到持有它的 membership,没有进程级后缀;每个 membership 的命名空间由其 NSD 的 domain_base 派生(默认 node.ns.io / svc.ns.io / gw.ns.io),落在别的 membership 命名空间里的记录被丢弃;每个名字还以 <name>.<network><后缀> 限定应答,两个 membership 都声明的名字对两者都 NXDOMAIN,只能用限定名区分;Node 的身份记录按 machine id 应答 alias4(A)与 N6(AAAA),指向 Node 的服务路由端到端拨 alias4;系统 resolver zone 随运行中的 NSD 安装与移除,不需要重启;Quick 的 n.ns.io 也由守护进程的 DNS 应答。有出口时从不转发到系统 resolver(经出口问出口的上游或内置公共回退)。
  • 监听地址:macOS / Windows 127.53.53.53:53;Linux / Android / iOS 169.254.53.53:53。
  • 上游策略:启动时先快照系统 DNS(resolv.conf / resolvectl / scutil --dns / Windows 适配器),读取在前、写入在后;优先级 系统快照 > --dns > 内置公共回退(1.1.1.1)。Exit 的 DNS 上游属于 ns exit set,不与普通 DNS 混用。
  • 子网名字:<ipv4-dashed>.<subnet-label>.<network-token>.subnet.<dns-root> 与短名 <ipv4-dashed>.<subnet-label>.subnet;语义 LAN 名只进入 <name>.<subnet-label>.subnet。resolver 只发布稳定父 scope subnet / subnet.<dns-root>。
  • Gateway Egress 短名:canonical 名称为 <service>.<gateway-label>.<network-token>.svc.<dns-root>;受支持的短名是 <service>.<gateway-label>.svc,裸 <service>.<gateway-label> 只作为 local stub 兼容记录投递、不安装系统 resolver scope;三类名称都不回落公网 DNS(capability-gated,FFI catalog v3)。
  • 容器:NS_CONTAINER=1 时由 NS 原地管理 Docker 挂载的 /etc/resolv.conf(单文件 subpath 卷),保留原 upstream,重复启动 / 退出 / 失败恢复中维持唯一受管块;Short DNS 只在 resolver 复核成功后激活并 ACK。
  • 选择:ns exit set <gateway> [--scope default-route|cidrs] [--allow-lan] [--dns exit|local] [--transport wg|wss],候选来自 GatewayExitCandidates;ns exit clear 先持久化 fenced withdrawal,再经认证通道撤销 NSD 的 machine-default selection,断网时 pending、重连先撤销再上报。
  • 路由安装:tun 模式下 NS 生成 split-default 捕获路由(有上限、可回滚、有所有权 journal),保护物理下一跳与底层控制路径(macOS 有专门的 next-hop 保护)。
  • 承载:WG 直连网关,或 /exit-wss/v1 WSS 承载(NSD 签发 role=gateway-exit-wss 短期 token);流量感知的直连健康检测、快速 WSS 回退与稳定承载所有权;旧承载代际不再泄漏状态。
  • 就绪:exit-readiness.v1 独立查询,不启动、不重连。
  • 按 membership 的出口(2026-10-12):每个 membership 至多一个出口,设备配置 <state-dir>/config/config.toml 按 membership id([exit."nsd:<uuid>/<network>"])保存;至多一个 membership 持有默认路由,不同 membership 的 CIDR 不重叠(否则 ExitChoiceConflict,文件不合规则 fail-closed);ns exit [--network M] 列出全部,ns exit set <GW> --network M [--replace] / ns exit clear --network M 作用于一个;撤回在各自的日志项里重试,选择 id 不会到达别的 NSD。所有出口载体从设备级出口连接器 EXIT4(100.98.0.1)发出,按 NSD 与网关分组为载体,ns up --exit 同时启动各 membership 的出口,运行时每秒重读配置只启停受影响的载体;运行中的 ns exit clear 只停该出口的载体。运行中加入的 membership 的出口下次启动才生效。
  • 发布:ns subnet add <real-cidr> [--join <subnet-id>] [--bind [--interface] [--source-ipv4]];同一 LAN 的备用网关用 --join 加入,CIDR 必须与既有映射精确相同。ns subnet remove / list / bind。
  • 地址:NSD 为每个逻辑 LAN 分配持久 32 位 Subnet ID;由 Network token 与 Realm ID 派生 RFC 4193 ULA /48,:1::/64 为映射(映射 /64 + Subnet ID /32 + 原 IPv4 /32,真实 /p 对应 /96+p),:2::/64 为访问端回程身份。真实 RFC1918 CIDR 不直接路由。
  • 转发:发布端以绑定的 {interface, source_ipv4} 做用户态 IPv6→IPv4 session gateway(TCP / UDP / ICMP Echo),首版始终 SNAT;绑定只保存在本机私有状态,不上传 NSD。策略检查在转换前,SNAT 不能绕过策略。
  • 高可用:NSD 选 active、保留 standby;切换只影响新连接。
  • LAN↔LAN 互联:未安装 NS 的 IPv6 终端经本机 ns 作为子网网关访问对侧映射目标;操作者在上游路由器配置 mapped prefix → gateway_ipv6 或让 NS 主机成为默认网关;外部路由未能观察时状态保持 unverified。LAN DNS 使用 subnet_lan_dns.v1 专用数据面。
  • 模式:personal_auto 网络自动互联;policy_required 网络需要显式 Subnet Grant。
  • 重叠 LAN 的本地视图(2026-10-13):两个 NSD 的路由 IPv4 网段重叠(含相同)时,只有 ns subnet assign <CIDR> --network M 指定的 owner 保留真实网段,其余路由从本地池 100.98.0.2 – 100.126.255.255 取同样长度的虚拟网段,由该 membership 的翻译器映射到它自己的 IPv6 前缀(nsplane 0.14 的 LAN 前缀映射);默认(--none)全部虚拟。分配与指定存于 <state-dir>/routes/local-ranges.v1.json,不上报任何 NSD;LAN 名字的 A 记录回答本地地址,服务 TUN 路由本地网段,ns subnet routes [--json](ns.subnet-routes.v1)显示每条路由的本地网段与类型。只有映射 IPv6 前缀重叠或池耗尽仍作为 conflict 扣留(无 LAN 条目、无路由、名字不解析)。移动端不路由这些本地网段。
  • 能力:node_l3_acl.v1;NSD 投影目标绑定的 L3 策略快照与 enforced 安全代际。
  • 分类顺序:访问端返回流 → 已发布 Service listener → 目标 Node → 被路由子网。
  • 语义:解密时把内网源地址绑定到投影 peer,未知或伪造源 fail-closed;同 Account Node 与 Service 经系统 Grant 放行;跨 Account 新流需显式 Service / Node / Subnet Grant;返回流有状态,绝不成为反向发起授权。peer 存在与 /32 cryptokey 路由只允许传输与回复,不授权新流。
  • ns status [--json] [--probe] [--dns] [--all]:Node / Service / 子网 / relay pair / Exit 状态;--probe 对 L3 peer 做 ICMP。
  • ns health [--ready]:进程存活或引擎就绪;ns info [--json]:身份、登记、构建、运行时所有者;ns doctor:只读环境诊断(权限、DNS 端口、resolver、环回别名、守护进程、NSD 可达性);ns report:反馈入口。
  • --metrics:Prometheus 文本与健康检查暴露在 127.0.0.1:9091;--log-dir 输出按日滚动的结构化 JSON 日志;NS_LOG_LEVEL 控制级别。
  • App 持有运行时时,CLI 通过本机私有 IPC(受限 Unix socket / Named Pipe,只接受 status\n)读取非秘密状态。
  • ns up [--detach] [--wait-ready]、ns down、ns repair(修复默认状态权限)、ns reset [--all --yes]。
  • ns update [--check] [--yes]:校验签名的 Admin releases.jws,六平台目标选择,直接二进制安装,保留旧二进制直到新版本证明可运行,防回滚;登记 NSD 之前也能发现官方更新;容器内提示改用新镜像。Windows 构建附带按架构固定的 Wintun 组件,安装器与更新器在替换可执行文件前先认证它,从不覆盖未知 DLL。
  • 发布:一次构建六端二进制,testing / production 只改变产物来源,-prod 原字节提升;产物固定 20 个文件(六端二进制、两种安装器、双架构 Docker 离线包及各自 checksum);Docker 包附 SPDX 3.0.1 与 in-toto / SLSA provenance、Cosign bundle。
  • 版本号:正式 tag 与显式覆盖保持不变;未打 tag 的发布构建在全部目标架构上使用同一个 YYYYMMDD-g<commit>,并校验干净的源码溯源。
  • 引导安装:Unix、Windows 与 Docker Linux 的引导脚本从 Admin 已验证的发布描述中独立解析目标,校验描述身份与下载长度 / SHA-256;普通发布不再需要修改脚本字节。Docker 升级失败后检查恢复部署的健康。
  • 环境变量统一 NS_*(旧名 fail-closed 并提示替代名);正式 Compose 输入 NS_SERVER_URL、NS_WORKLOAD_IMAGE、NS_STATE_VOLUME、NS_RESOLVER_VOLUME 等。
  • CLI 本地化:en-US / zh-Hans Fluent 资源编译期嵌入;JSON、错误码、日志字段、协议值不本地化。
  • 产物:Rust package ns-ffi;Apple libns.xcframework、Linux libns.so、Windows ns.dll;header ns.h;符号统一 ns_ 前缀。ABI 唯一权威是 abi/manifest.v1.json。

  • 契约:复杂入参 / 状态 / 事件 / 错误全走 JSON schema,版本化:start-options 当前 v11(server_url 可激活已配置 NSD)、status v5–v11(v11 含 lan_egress)、source-gateway-status.v1、exit-readiness.v1、Short DNS status v1 / v2、registration v1(不启动引擎的独立浏览器注册,可取消)、Account sign-out。

  • 设备显示名:App 先探测 ns_supports_device_name_v1,再在浏览器注册与连接选项中传入宿主解析的 device_name;同一名称用于浏览器批准、签名登记、系统元数据与态势,不改变服务端手工命名与 DNS 身份。桌面端使用平台计算机名,拒绝 localhost 等通用占位名。

  • 移动宿主契约:只读能力 mobile_network_settings_v1 按绑定实例的待应用代次提供已验证的 Node、DNS 与 IPv6 guard 描述;宿主提交的系统 DNS 快照按网络代次生效,拒绝旧代次、冲突与递归上游,空快照无公共回退;出口 DNS 走当前就绪代次的 WireGuard 数据通路,出口缺失时不回落系统 DNS。手机端接入与真机验收由 ns PLAN-213 跟踪。

  • 运行时所有权:App 持有 engine 时发布只读 status endpoint;事件订阅有明确的 callback 静默与所有权规则;disconnect → start 后需重新订阅。

  • 平台:macOS / Windows 原生 TUN;iOS PacketTunnel 使用 Core MTU;Windows native 是发布门而非本地实现门。

  • membership 面(2026-10-12,不兼容):启动参数 v12 列出要运行的 membership,status.v13 与 local-state.v3 列出每个 membership,NS_ABI_VERSION 为 3;删除 ns_engine_status_json_v6/v9/v10/v11/v12、ns_engine_local_state_json_v2 与 ns_engine_join_disposition_json;能力 registration_state_v2 改为 registration_state_v3,subnet_status_v10/v11/v12 删除。

官方镜像不发布到 registry,而是架构专属的自包含 Docker 离线包(manifest.json 绑定镜像字节、版本、commit、架构)。同一镜像也运行 Quick 模式(见 Quick 模式 §6.5),两种模式使用各自的 Compose 模板;固定安装器的 setup / upgrade / rollback 区分模式,拒绝在已有安装中隐式切换模式。运行合同:最小 capability、只读 rootfs、持久 identity 卷、与业务容器共享 network namespace、业务 DNS、health / readiness、services.toml 热更新、优雅清理。首次启动打印浏览器授权 URL。

访问与发布只是 pair 的方向。NSD 把双向关系折叠为一个确定的 opaque-splice 朝向(machine id 较小者为 client leg),网关的 relay 路径是 role-neutral 的;nsc_* / nsn_* 字段描述 pair 两端而非机器角色。

ACL 未到、显式为空、来源撤销、投影不针对本机、schema 不支持,一律拒绝;只有传输抓取失败保留 last-known-good。子网投影、L3 策略、Short DNS 快照都经过”针对本机 + 版本受支持 + 内部一致”三重校验后才替换本地持久快照。

链路模式、Exit 候选、子网 active / standby、Short DNS 名字都由 NSD 投影;NS 只在投影范围内做健康驱动的降级与恢复,并把本地决策上报以便收敛。没有”直连失败 → 申请中继”的运行期控制信道。

services.toml 与 ns subnet 的绑定是发布端的本地权威;NSD 即使误投影也无法让 NS 发布本地未声明的目标。

manifest / journal / 平台凭据都以短锁事务、原子写与严格权限维护;多 NSD、多 Space 的切换只提交指针,不部分迁移。

NS 不做的事情:

  • 不签发身份、不编译策略、不维护其他节点状态(NSD 职责)。
  • 不解析应用层、不做 L7 中间件(NSGW 公网入口职责)。
  • 不承担公网入口、Exit SNAT、Egress 终结(NSGW 职责)。
  • 不合并多个 NSD 的策略:多 NSD 只是本地保存,任一时刻只有一个 active。
  • 不自动修改 LAN 路由器、不创建热点 / RA / DHCPv6。
层组件(crate)职责
控制面control信任发现、三传输客户端、Envelope v1、ControlMessage 解析与签名校验、API 类型
身份与状态common、secure-storage、privdrop、certstore状态目录、密钥、保险库、权限降级、证书存储
数据面nsplane v0.14.0 + ns::node_engine / ns::account_engine;控制侧残余 nat(Service 路由与子网路由模型)、proxy、acl(策略编译)隧道、用户态协议栈、Service 路由、代理
策略aclacl_config 模型与匹配引擎(与 NSGW 共享)
运行时ns(engine(含 memberships)、node_engine、account_engine、key_chain、policy、link_ladder、route、dns、subnet*、exit_*、wg_relay、pair_transport、http_proxy、socks5_proxy、pac、status、doctor、cli)CLI 与守护进程
平台ns-macos-platform、ns-windows-platform原生 TUN、路由、resolver、系统代理
观测 / 其他telemetry、posture、eventbus、updater、build-version指标、态势、事件、更新、构建溯源
FFIns-ffilibns C ABI、schema、事件回调、错误 envelope
测试工具tools/ns-host-smoke*、tools/ns-ffi-host主机冒烟(含特权)、FFI 宿主测试