NSIO Docs
NSIO Docs
首页
总体架构

NSN · 站点节点

NSN · 站点节点
NSN · 架构与功能设计
NSN · 演进路线与开发计划

NSC · 用户客户端

NSC · 用户客户端
NSC · 架构与功能设计
NSC · 演进路线与开发计划

NSD · 控制中心

NSD · 控制中心
NSD · 架构与功能设计
NSD · 演进路线与开发计划

NSGW · 数据网关

NSGW · 数据网关
NSGW · 架构与功能设计
NSGW · WireGuard Peer Relay 协议层
NSGW · 演进路线与开发计划

Last Updated: 2026/5/8 08:44:39

Previous PageNSC · 用户客户端
Next PageNSC · 演进路线与开发计划

#NSC · 架构与功能设计

本页从纯架构视角描述 NSC(Network Site Client) 的功能划分与设计特点。改造路线见 roadmap.md。

#1. 角色定位

NSC 是 NSIO 生态在用户侧的运行时二进制,担任"访问端"的角色。它把远端 NSN 站点上的服务"拉到本地",让用户在本机上看到一组环回虚拟 IP 与一串 *.n.ns 域名,可用任意标准 TCP 工具(SSH、curl、psql、浏览器等)透明访问远端服务。

NSC 与 NSN 在协议栈上对称,但流量方向相反:NSN 是入站的服务暴露端,NSC 是出站的服务消费端。

NSC 的产品差异化:

  • 不需要 TUN / 不需要管理员权限:默认形态下无需建立内核虚拟网卡,只在环回地址上监听,可在普通用户权限下运行。
  • .ns 命名空间 + 环回 VIP:服务被映射到本机 127.127.x.x 段的虚拟 IP,操作系统层无需 NAT 或路由配置。
  • 本地 DNS 即查即用:对 *.n.ns 域名的解析就近发生在本机,既不依赖系统 DNS 也不污染上游 DNS。
  • HTTP/CONNECT 代理替代品:浏览器场景下可通过本地 HTTP 代理直连,完全绕过 VIP 监听器,实现单入口多目标。

#2. 功能模块划分

#2.1 注册与身份

  • 复用机器身份模型:Ed25519 签名身份 + X25519 对等身份,与 NSN 同结构;在用户侧的私有状态目录下持久化。
  • 多 realm 注册:同一台用户终端可同时注册到多个 NSD,按 realm 隔离注册凭证。
  • 首次入口:支持 authkey 与 OAuth2 device flow 两类(后者目前为契约预留,未实现)。
  • 续签静默化:运行期通过 SSE 通道接收凭证刷新事件,无需用户感知。

#2.2 控制面消费

NSC 通过同一套控制面订阅多类配置事件,但仅消费与"用户侧出站"相关的子集:

  • 路由配置:控制中心下发的"远端服务到 site/service 映射"集合,这是 NSC 自动建立 VIP 监听器与 DNS 记录的源头。
  • 网关拓扑配置:可用 NSGW 列表与选路策略,决定 WSS 流的目的地。
  • DNS 自定义记录:管理员显式配置的域名映射(例如把 git.company.com 也指到内部站点)。
  • 其他控制面通道(WireGuard、代理规则、ACL、凭证刷新)在当前实现中被显式忽略,但接收点已就位,等待出站 ACL 等能力接入。

#2.3 虚拟 IP 分配

  • 段隔离:在用户态模式下使用 127.127.0.0/16 段(完整 /8 环回内的留白段),避免与 systemd-resolved、传统 dnsmasq、mDNS 抢端口或 IP;在 TUN 模式下使用 RFC 6598 CGNAT 共享段。
  • 幂等映射:同名 site 始终分配到同一 VIP,使得用户的 SSH 配置、bookmark 与脚本可稳定复用。
  • 顺序分配 + 段内回绕:VIP 分配按内存顺序展开,段内空间对客户端体量充裕,失败仅影响单个 site,不会传染。
  • 进程级生命周期:当前形态下 VIP 只在内存中保存,进程重启会重新分配,site 下线后监听器残留(无害但需在生产化时引入持久化与清理)。

#2.4 本地 DNS 服务器

NSC 自带一个手写的 UDP DNS 服务器,部署在专用环回地址(默认 127.53.53.53:53),职责边界清晰:

  • 命中转发:对 *.n.ns 与 NSD 显式下发的自定义域名返回对应 VIP 的 A 记录。
  • 未命中透传:其他查询代理给上游 DNS,不解析也不缓存。
  • 协议简化:仅处理 A / ANY 查询,其他记录类型直接转发,避免重复造轮子。
  • 系统集成多档:支持"加入 resolv.conf 首行"、"systemd-resolved 按域转发"、"macOS 环回别名 + per-domain 解析器"三种部署口径,以适应不同操作系统的解析栈。

监听点选在 127.53.53.53:53 是出于回避主流 stub resolver 占用、保留标准端口 53、不需要为绑定特权端口加 capability 三方面考量。

#2.4.0 接管系统 DNS 的"接续原则" —— 默认上游来自系统快照

NSC 一旦接管本地解析(写 resolv.conf 首行、配 resolvectl、加 /etc/resolver/n.ns),就夺走了原本属于系统的默认 DNS——内核 stub、getaddrinfo、应用层全部走 NSC,而 NSC 自己的"未命中透传"上游决定了网络其余部分能否解析。当前实现把上游硬编码为 1.1.1.1:53,这条简化在以下场景下直接打破用户原有体验:

  • 企业 LAN 内的 DHCP-provided DNS 持有 *.corp / *.local / mDNS / Active Directory 等内部记录,改走 1.1.1.1 后这些查询全部失败;
  • 在 1.1.1.1 被运营商 / 国家级阻断的网络下,NSC 接管后等于全网解析瘫痪;
  • 出差 / 切 WiFi 切到酒店 captive portal,门户域名通常仅 ISP DNS 持有,走公网上游会卡在 portal 跳转;
  • 用户原本配置的私有 DoT / DoH(9.9.9.9 / 自建 PiHole 等)被静默替换成 1.1.1.1,违反"非破坏性集成"的产品承诺。

正确做法:NSC 启动时先快照系统当前的 DNS 上游(/etc/resolv.conf 的 nameserver 行 / resolvectl status 的 per-link servers / macOS scutil --dns 输出 / Windows 网络适配器 DNS 列表),把快照作为默认上游;然后再接管本地解析。这条顺序约束很硬:

  1. 必须读取在前、写入在后——若顺序颠倒,NSC 会把自己读成"系统 DNS",形成 127.53.53.53 → 127.53.53.53 的自循环。
  2. 快照保留 search domains——resolv.conf 的 search / domain 行也是非 FQDN 解析(ssh server → ssh server.corp.example.com)的关键,NSC 自己也应当透传这些后缀给应用,否则用户脚本里的短主机名都会 NXDOMAIN。
  3. 网络切换时刷新——WiFi 漫游、VPN 拨入、有线 / 无线切换会改变系统 DNS;NSC 应订阅 OS 网络变化事件(Linux netlink RTM_NEWLINK / NetworkManager DBus、macOS SCDynamicStore 通知、Windows NotifyAddrChange),刷新快照后 atomic swap 内部上游表。
  4. fallback 链可配置但保持 fail-soft——快照失败 / 解析全错 → 退到 CLI 显式 --upstream-dns → 退到 1.1.1.1(或为空)。最末端不阻塞 NSC 自身启动,只是降级为"无未命中透传",*.n.ns 仍然可用。
  5. 可被 dns_config 覆盖——管理员可以在 NSD 端显式声明默认上游(企业上游、按域分流见 §2.4.3),覆盖系统快照。优先级:dns_config 显式 > 系统快照 > CLI fallback > 硬编码兜底。

这条接续原则与 §2.4.3 的按域转发组合,使 NSC 接管 DNS 既"非破坏"(系统原有解析路径继续生效)、又"可增强"(在保留原行为的基础上新增 *.n.ns 与按域分流)。

#2.4.1 命中域名的两类来源

本地 DNS 服务器对外是单一的"命中即返 VIP"语义,但命中条目实际来自两条互不耦合的输入:

来源命名空间典型场景上游有无同名记录
routing_config 自动派生*.n.ns(协议级保留)自动注册的内部站点服务,如 ssh.office.n.ns无(.n.ns 不在公网 DNS)
dns_config 显式下发任意 FQDN(含真实公网域名)双栈服务:git.company.com、grafana.internal.example.com可能有,由管理员决定是否遮蔽

两类条目共用同一份 DnsRecords,在查询路径上无差异;差异只在"命中后用户感知"——*.n.ns 命中是"凭空多一个域名",真实域名命中是"在本机层面遮蔽掉公网 DNS 的解析结果"。

#2.4.2 双栈域名 / split-horizon DNS

许多服务在企业部署中同时具备两条访问路径:

  • 公网入口:git.company.com 在公网 DNS 中解析到对外 VIP / Ingress / 反代,任何外部用户可访问。
  • 内网入口:同名服务也通过 NSN 暴露在某个 site 上,希望从客户端走 NSC 隧道直达,而不是经过公网。

NSC 通过 dns_config 把同一域名映射到本地 VIP,在客户端层面"遮蔽"公网解析:用户与脚本无需知道双栈细节,继续使用熟悉的公网域名,流量却沿 127.127.x.x → VIP listener → WSS → NSGW → NSN 完整走隧道。这条路径与浏览器场景下 HTTP 代理 lookup_domain 命中走 WSS 的入口共享同一份 NscRouter 决策。

设计上需明确的边界:

  • 遮蔽是客户端单方面的:NSC 只能影响本机解析路径,无法控制 OS 缓存、浏览器自带 DoH、应用层硬编码 IP 等绕过通道。需要遮蔽生效的客户端应配套关闭 DoH 或将 NSC 上游设为按域转发的目标。
  • 遮蔽是非破坏性的:dns_config 仅修改 NSC 持有的命中表,不改写公网 DNS、不修改系统 hosts 文件。卸载或停用 NSC,公网域名立即恢复直连解析,不留残留状态。
  • 隧道不可达时无自动回退:一旦域名被映射到 VIP,客户端不会再去公网解析同名域名;隧道故障期间该域名"看起来不可用"。回退策略由路线图(健康降级 / 公网 fallback)规划,不在当前实现内。
  • 决策权留在控制中心:哪些域名应当被遮蔽、对哪些 realm / 用户群生效,由 NSD 端的 dns_config 编排;NSC 不在本地维护静态遮蔽表,以保证"用户终端永远跟随策略中心"。

#2.4.3 条件转发 / per-domain upstream

仅靠"命中即返 VIP / 未命中转发单一公网上游"两档策略,不足以覆盖企业级解析需求。许多场景里,某些域名族必须由特定 DNS 服务器才能解析正确:

  • *.corp.company.com 由企业 AD DNS(10.0.0.53)持有,公网 DNS 既不会回也不应当回;
  • *.k8s.internal 由集群内的 CoreDNS 解析,带集群拓扑感知;
  • *.dev.example.local 由开发环境专属 DNS 持有,与生产命名空间隔离;
  • 其余流量继续走默认公网上游(默认是接管前的系统 DNS 快照,见 §2.4.0;管理员可在 NSD 端覆盖为企业上游)。

NSC 的本地 DNS 服务器据此引入按域转发表,每条规则形如 (域名后缀, 上游列表, 上游可达性)。整体解析优先级:

查询到达 NSC DNS
  ├─ ① 命中本地 VIP 表(routing_config + dns_config 显式映射) → 立即返 A/VIP
  ├─ ② 命中按域转发表(后缀匹配,最长匹配优先)              → 转发到该规则的上游 DNS
  └─ ③ 默认上游 → 优先级:dns_config 显式 > 系统 DNS 快照 > CLI fallback > 硬编码兜底(见 §2.4.0)

#2.4.4 上游 DNS 的可达性 —— 让 DNS 服务自己拿一个 VIP

按域转发的关键特例是"指定上游本身只在隧道侧可达":企业 AD DNS(10.0.0.53)、集群 CoreDNS 等并不暴露在公网,客户端直连等于不可达。

直观的设计冲动是在 DNS 转发器内嵌"是否走隧道"的判断,但这本质上是在 DNS 层重做了一份"路由策略"——而 NSC 的产品哲学是**"任何内网服务都通过分配 VIP 来访问"。内网 DNS 服务并不特殊,把它当成普通 service 注册即可,DNS 转发器只需要看到一个上游 IP / FQDN,不需要知道它通向哪里**。

部署方式:

  1. 在 NSN 端把内网 DNS 注册为 service,例如 services.toml 里 service = "dns", port = 53 暴露给某 site,自动通过 services_report → routing_config 派生 dns.<site>.n.ns 域名与一个 127.127.x.x:53 VIP。
  2. NSD 的 dns_config 里转发条目直接写:for *.corp.company.com forward to dns.office.n.ns:53/tcp(或写裸 VIP 127.127.x.x:53/tcp,但 FQDN 形式对 VIP 重分配更稳健)。
  3. NSC DNS 转发器只看到"上游 = 一个地址",发出 53/TCP 查询。
  4. VIP listener 抓住,经 NscRouter::open_stream → WSS → NSGW → NSN → 内网真实 DNS。
  5. 响应原路返回,DNS 转发器不感知中间的隧道细节。

由此 DNS 模块不需要任何新的"隧道感知"代码;转发表条目就是一个普通的 (suffix, Vec<上游地址>) 二元组,与 dnsmasq / unbound 的条件转发表语义完全一致。是否进隧道、走哪个 site,这些决策已经被压到了"VIP 是哪个 site 的"这个早就建好的索引里。

与上一节 §2.4.3 的衔接:

查询到达 NSC DNS
  ├─ ① 命中本地 VIP 表(routing_config + dns_config 显式映射) → 立即返 A/VIP
  ├─ ② 命中按域转发表(后缀最长匹配) → 转发到该规则的上游地址
  │       └ 上游可能是公网 IP(`1.1.1.1`)、内网 LAN IP(`10.0.0.53`)、或某 site 的服务 VIP/FQDN
  └─ ③ 默认上游(当前硬编码 `1.1.1.1:53`,目标可配置) → 转发

#2.4.5 模式间差异:差异藏在 VIP 而不是 DNS 模块里

把"该不该进隧道"完全外推给 VIP 后,DNS 模块在三种数据面下的行为就是同一份代码;真正的差异落在"上游地址应当填什么"上,这是部署期 / dns_config 编排的职责,不是运行时分支。

上游地址类型TUN(规划)userspace(默认)HTTP proxy 入口
公网 IP(1.1.1.1)OS 默认路由送出同同
内网 LAN IP(10.0.0.53)OS 路由表选 LAN 直连或 TUN 隧道(由 metric 决定);用户在 LAN 内时走 LAN 直达,离开 LAN 时进隧道——这就是上一轮提到的"本地有路由,可以选择"OS 没有指向 10.0.0.53 的路由,直发会失败;该模式下不应当用此类上游同 userspace
Site 服务 VIP / FQDN(dns.office.n.ns:53)TUN 段是 100.64/16;OS 路由把这一段送进 TUN,等价于其他服务VIP listener 在 127.127.x.x:53 抓 TCP,经 WSS 进隧道——与所有 *.n.ns 服务同路同 userspace

编排建议:在多模式部署中,转发表条目可以并列两条上游(顺序回退),例如:

  • 主上游:dns.office.n.ns:53/tcp(VIP 版,任何模式都工作)
  • 备上游:10.0.0.53:53/udp(原生 IP 版,在 TUN 模式 + LAN 内时由 OS 选 LAN 直达,延迟最低)

NSC 按列出顺序尝试;userspace 模式下第二条会失败、自动回退到第一条;TUN 模式下两条都可工作,OS 路由表 / metric 决定细节。这种"用配置而不是代码表达模式差异"的取舍,与 NSC 整体"VIP 段 + lazy binding + 单一路由表"的精神保持一致——把分支推到数据,而不是推到逻辑。

#2.4.6 三类 DNS 能力的关系(收敛版)

至此 NSC 在 DNS 层提供两类能力,共同构成"客户端策略 DNS"模型,不再需要"上游可达性"这个第三类:

能力决定的事数据来源当前状态
命中 → VIP(§2.4.1 + §2.4.2)哪些域名在本机直接回 VIP(虚拟域名 / 公网遮蔽)routing_config + dns_config✅ 已实现
按域转发(§2.4.3 + §2.4.4)未命中时该走哪个上游;隧道侧上游通过指向 service VIP 的 FQDN 表达dns_config(规划项)⚠️ 仅有硬编码默认上游

DNS 层只负责名字解析,不关心上游可达性:可达性问题在它到达 DNS 模块之前已经被"VIP 分配 + 路由表"解决。这是 NSC 设计的一致性体现——任何要"走隧道"的需求,最后都收敛为"分配一个 VIP,通过现有路径访问"。

#2.4.5 三类 DNS 能力的关系

NSC 在 DNS 层提供的三类能力构成一个完整的"客户端策略 DNS"模型,彼此不耦合但配合使用:

能力决定的事数据来源当前状态
命中 → VIP(§2.4.1 + §2.4.2)哪些域名在本机直接回 VIP,使流量进隧道routing_config + dns_config✅ 已实现
按域转发(§2.4.3)未命中时该走哪个上游dns_config(规划项)⚠️ 仅有硬编码默认上游
上游可达性(§2.4.4)该上游通过 OS 路由还是 NSC 显式打隧道转发表条目 + 当前数据面模式❌ 当前仅 OS 直连;模式相关

设计上严格保持"DNS 层只负责名字解析,真正的流量路径由路由表与 ACL 决定"——按域转发表不重新发明一份"路由策略",它仅负责把 NSC 自己发出的 DNS 查询分流到正确的解析者。

#2.4.6 整体选路矩阵(数据面 × 目标场景)

把 §2.4.1–§2.4.5 涉及的所有路径并到一张表,展开 NSC 在不同数据面模式下对四类目标的处理方式:

目标TUN 模式(规划)userspace(默认)HTTP proxy 入口
*.n.ns 虚拟域名DNS 命中返 VIP;OS 路由把 VIP 段经 TUN 送出DNS 命中返 VIP;TCP 在 127.127.x.x:port 被 VIP listener 抓到不依赖 DNS,直接 lookup_domain 命中 → open_stream
公网域名遮蔽(git.company.com 等被 dns_config 重定向)同上同上同上
内网 DNS(10.0.0.53 等仅隧道侧可达)NSC 把查询发往 10.0.0.53:53 UDP/TCP,OS 路由表根据 nsio0 route 决定送进 TUN;若用户已在 LAN 内,优先走直连 LANNSC 转发器显式调 open_stream(site, "10.0.0.53", 53) 走 53/TCP;OS 不知道 10.0.0.53 存在于隧道侧同 userspace
公网通用解析(默认上游 1.1.1.1)UDP 直发,OS 默认路由经物理网卡UDP 直发,OS 默认路由经物理网卡同 userspace

关键观察:只有 TUN 模式真正给了 OS 路由表"本地 vs 隧道"的选择权;userspace 与 HTTP proxy 模式下,NSC 必须自己在每条规则上声明"该上游能不能走 OS",对应 §2.4.4 中的 OsRoute / Tunneled 二选一。这让"同一份 dns_config 转发表在 TUN 部署和 userspace 部署上有不同的执行行为"成为合法的设计意图,而不是 bug。

#2.5 路由表与出站 NAT

NSC 内部维护一份小型路由表,在多个出站入口之间共享:

  • 以 site 为聚合单位:把 site 名 ↔ VIP ↔ 域名三者绑定,VIP 监听器与 HTTP 代理两条入口共享同一份映射。
  • lazy binding:每条 TCP 连接到达时才从路由表中读取目标网关,而不是在 listener 启动时定格;这意味着网关切换后新连接立即采用新网关,旧连接继续维持原网关,避免抖动。
  • 网关全量替换:网关拓扑变更时整体替换网关索引,但路由条目级的"应该走哪个网关"按当前主网关或权重动态决定。
  • 统一封装出口:无论是 VIP 监听器还是 HTTP 代理,最终都通过同一个"打开 WSS 流到主网关"的封装出去,确保策略与可观测点单一。

#2.6 出站入口 1 · VIP 监听器

  • 每条 (VIP, port) 一只监听器:为路由表中每个端口启动一个本机 TCP 监听,接收用户进程的 connect。
  • 解析与转发:连接到达后查路由表,选定 site 与端口,按封装的"开 WSS 流"逻辑送出,然后做字节级双向 copy。
  • 单 listener 单端口语义:简单、稳定;site 下线后 listener 不主动停止,资源代价极小,因为环回段空间充裕。

#2.7 出站入口 2 · HTTP 代理(可选)

  • 请求形态:支持 CONNECT(SSH / HTTPS / 任意 TCP 隧道入口)与明文 HTTP 方法。
  • 目标解析三级优先:先匹配本地路由表里的域名(命中即不经 VIP 监听器,直接打 WSS 流);否则若是字面 IP 走系统直连;再否则做公网域名解析后直连。
  • 互补关系:VIP listener 适合"已分配端口"的细粒度访问;HTTP 代理适合"任意 host:port"的广泛访问,两者共用同一份路由决策但使用各自的连接管道。

#2.8 数据面三种形态

CLI 提供三种"数据面"选项,但当前实现的功能差异比命名暗示的小:

  • 用户态(默认):127.127.0.0/16 + WSS 中继到主网关,功能完整,无需 root。
  • WSS 强制:语义与用户态等价,意在向运维表达"绝不尝试 UDP",当前与默认行为一致。
  • TUN(规划):意图通过内核 TUN 接收出站包,经 WireGuard 直连 NSGW;当前仅切换了 VIP 段前缀,未真正建立 TUN 设备。

数据面之间共享 NSC 上层的所有路由 / DNS / HTTP 代理逻辑,差异只在"用户进程发出的包以何种方式进入 NSC 的处理路径"。

#2.8.1 TUN 模式的寻址抉择 —— VIP 不是必需的

127.127/16 的 VIP 段是为 userspace 模式量身设计的:loopback 段使 NSC 在普通用户权限下能 bind() 监听,且不与系统 stub resolver 抢端口。这套约束在 TUN 模式下完全不存在:NSC 已经获得 root 并管控一张虚拟网卡,任何 IP 段都能由内核路由表导引到本进程,VIP 间接层从此变成"可选设计",而不是必需结构。

由此 TUN 模式有两种合法的寻址模型,可以共存:

寻址模型服务 → IP 映射路由安装NSC 端代价NSN 端代价
VIP 模式(100.64/16,延续 userspace 的抽象)每个 service 由 VipAllocator 分配一个 CGNAT IP100.64.0.0/16 dev nsio0 一条VIP 分配器 + DNS 改写 + per-service 监听语义NSN 用 nat::ServiceRouter 做端口 → 本地服务的查表(现状)
Passthrough 模式(直接路由到站点 CIDR)服务保留真实内部 IP / 域名,NSC 不分配 VIP<site_cidr> dev nsio0 一条(可多条)几乎为零——内核路由 + ACL 即可,DNS 不改写NSN 直接转发已解密的 IP 包到本机网络;不需要端口映射

两种模型的对比:

维度VIP 模式Passthrough 模式
与本机网络的冲突100.64/16 在 RFC 6598 CGNAT 段,极少冲突取决于站点 CIDR——若站点是 10.0.0.0/24,用户家里也是同段就麻烦;需要"站点 CIDR 全局唯一"或显式 NAT 重映射
service 粒度天然 per-service —— 每条 (VIP, port) 一个 listener,ACL 可按 service 精细授权路由是 per-CIDR —— 整段网络对客户端可见;service 粒度由 ACL 在端口层补
DNS 体验必须由 NSC 本地 DNS 改写 *.n.ns / 公网遮蔽到 VIP真实域名解析直接命中真实 IP,NSC 不做改写;DNS 只在"内网 DNS 也只在隧道侧可达"的场景需要按 §2.4 处理
多站点同 IP 冲突不冲突——VIP 由 NSC 分配,两个站点的 10.0.0.5 各自得到不同 VIP冲突——两站点 CIDR 重叠时路由表无法表达;需 NSD 在编排层判负
与传统 VPN / WG 客户端的兼容自成一套 —— *.n.ns + VIP 是 NSIO 特有与 WireGuard / Tailscale subnet router / OpenVPN 同模型,迁移用户零学习成本
NSC 实现复杂度VIP 分配 + DNS server + per-service listenerTUN + 路由 + 既有 router 表(直接按 dst IP 查 site)

*.n.ns 命名空间在 passthrough 模式下的归宿:不是被废弃,而是变成"纯虚拟服务的命名空间"——GatewayEgress 这类没有真实 IP 后端的服务必须有一个名字,VIP 是它的物理化形式;NsnBacked 在 passthrough 模式下可以不取 *.n.ns 名,直接用站点的真实域名 (db.office.example.com) 即可。

推荐策略(规划):

  • TUN 模式默认采用混合:站点级 passthrough(在 NSN 的 services.toml 或 NSD 编排里声明 cidr_passthrough = "10.20.30.0/24") + 虚拟服务 VIP(GatewayEgress 等没有 IP 后端的形态);
  • userspace 模式继续单一 VIP 模型,因为 loopback 段只能这么玩;
  • NSN 端 services.toml 增 addressing = "vip" | "passthrough" 字段(默认 "vip" 保现状),决定该 service 是分配 VIP 还是直通真实 IP;
  • 站点 CIDR 唯一性由 NSD 在编排时校验(冲突拒收),延续"NSD 做 cross-config 一致性检查"的现有原则。

详细演进项见 roadmap.md F.1 TUN 数据面真实化(已扩展为"真实化 + 寻址模型抉择")。

#2.9 状态展示

启动完成后,NSC 在终端输出一份"site → VIP → 域名"映射表,作为最低门槛的状态视图。当前没有独立的运行时查询接口(规划中将提供 UNIX socket / HTTP 端点暴露 sites、VIPs、DNS 记录、隧道状态等)。

#3. 设计特点

#3.1 用户体验优先的"零配置"

NSC 在本机以"加几行 resolv.conf"或"配 systemd-resolved 按域转发"为代价,即可让 *.n.ns 在系统层直接解析。配合环回 VIP,用户使用任意标准 TCP 工具,语义如同访问本机服务,无需切网络环境、无需走系统 VPN 接口。

#3.2 不依赖系统级 VPN 接口

默认形态下 NSC 不创建 TUN 设备、不修改路由表、不修改防火墙、不需要管理员权限。这使 NSC 能跑在普通桌面用户、CI runner、容器、Android App 沙箱等多种受限环境。

#3.3 与 NSN 共享栈、方向相反

NSC 复用 NSN 的控制面、机器身份、WireGuard / WSS 协议栈、proxy / ACL 子模块,但流量方向相反、视角相反:

维度NSN(站点端)NSC(客户端)
流量方向入站出站
隧道入口NSGW → NSNNSC → NSGW
NAT 类比DNAT(端口 → 服务)SNAT + PAT(VIP → site+port)
ACL 视角按入站源过滤按出站目标过滤(预留)
服务来源本地白名单控制中心自动发现

这种对称性使大部分组件可在两个二进制之间复用,降低代码维护成本。

#3.4 单一路由表 × 多入口共享

VIP 监听器、HTTP 代理、本地 DNS 三个入口共享同一份路由表与 site 索引。任何一处的状态更新对其他入口立即可见,避免了配置重复维护与状态分裂。

#3.4.0 backend 无关的客户端

NSC 收到的 routing_config 只是"site → service → VIP"的映射,对 backend 是 NSN 还是其他形态完全不感知。当 NSIO 引入三类 service backend(规划,见总体架构 §6.5与 nsgw architecture §2.8.1)后,NSC 的适配压力按 backend 类型分两档:

BackendNSC 是否需要改造原因
NsnBacked(现有)不需要现状即此
GatewayEgress(指定目标 L4 转发)不需要NSC 看到的仍是"VIP + service",open_stream 走主网关,网关侧自己 connect 到目标——NSC 无感知
GatewayExit(exit node 模式)需要 TUN 数据面userspace 模式只能捕获 127.127/16 VIP 段,无法承接 0.0.0.0/0 默认路由;HTTP 代理只能覆盖 HTTP/CONNECT;exit node 启用后 NSC 需在系统路由表写 0.0.0.0/0 dev nsio0 或目标 CIDR,前置依赖见 roadmap.md F.1

前两类是"VIP 抽象 + lazy binding"的副产品——NSC 从一开始就把"流量到底通向何处"封装在主网关的 WSS 流里,客户端永远只与"VIP 与主网关"两个抽象打交道。GatewayExit 是唯一突破这个模型的形态,它的目标不再是"某个 VIP",而是"任意公网 IP",必须由内核路由表把流量送进 TUN 设备才能成立。

这进一步强化了 roadmap.md F.1 的优先级:TUN 数据面真实化不仅是"补齐 CLI 表面承诺",也是 NSC 进入 exit-node 形态的前置条件;若选 (A) 删除 TUN 路线,则 NSC 永远不参与 GatewayExit 场景,该形态只能由"非 NSC 的 WireGuard 客户端"使用 NSGW 的 exit 能力。

#3.4.1 双栈域名作为一等公民

NSC 的核心价值并不局限于"凭空创造 *.n.ns 这套虚拟域名空间",更重要的是允许任意域名(尤其是用户已在使用的真实公网域名)通过 dns_config 在本机被遮蔽到 VIP,使流量走隧道。这是 NSC 与传统纯 VPN 客户端的关键区别——传统方案要么"全流量走 VPN",要么"按目标 IP / CIDR 路由",难以做到"按 FQDN 在客户端层面拆分公网与隧道流量";NSC 把这一拆分推到 DNS 层,实现细粒度、低侵入、可由控制中心动态变更的双栈访问。详见 §2.4.2。

#3.5 lazy binding 屏蔽网关切换

新建 TCP 连接时才解析"走哪个网关",而不是在监听器启动时绑死。这使得网关健康状态变化、控制面切主、跨区 failover 等事件对用户连接透明,新连接立即享有新拓扑,旧连接保持原路径。

#3.6 控制面接收点先就位、消费按需开启

NSC 在控制面建立时声明全部 8 类事件接收器,即便当前只消费其中 3 类,也保证未来引入"出站 ACL 预检"、"WireGuard TUN 模式"、"凭证轮转"等能力时不需要重新设计接入点。

#3.7 失败的可见性

启动时 NSC 立即输出 site→VIP→域名映射表,任何配置错位、连接失败、网关未就绪都通过日志立即可见;未来的运行时状态接口将以 UNIX socket / HTTP 形式暴露更细的连接、隧道、DNS、VIP 维度。

#4. 与其他模块的边界

NSC 不做的事情:

  • 不参与认证签发,只承担"作为客户端的注册主体"角色。
  • 不维护 site 的服务清单(由 NSN 上报、NSD 编排、NSC 只接收路由结果)。
  • 不做权威 ACL 决策(策略最终由 NSN 终决,NSC 仅在演进中加入"客户端预检"以提升用户感知)。
  • 不分配域名、不签发证书:.n.ns 命名空间是协议级约定,证书由网关或外部入口层负责。
  • 不承担流量审计的权威记录:审计记录在 NSN / NSGW 一侧,NSC 仅记录本地事件。

#5. 演进方向

NSC 的演进核心围绕三条主线:

#5.1 把"声明的"变成"真实的"

  • TUN 模式真正建立内核 TUN 并复用 WireGuard 用户态/内核栈。
  • WSS 强制模式表达"严格走 WSS",在 UDP 被探测可达时也不切换。
  • device flow 入口接通,使桌面端首次注册不再依赖一次性 authkey。
  • 状态子命令对外暴露真实的运行时视图,而非占位输出。

#5.2 把"接收的"变成"生效的"

  • 把当前被丢弃的 4 类控制面事件(WireGuard、代理规则、ACL、凭证刷新)接通到对应子模块。
  • 出站 ACL 预检:在客户端立即反馈越权访问,降低用户排错成本,同时不取代 NSN 的权威决策(双层信任)。
  • 凭证刷新真正接管会话密钥滚动,不再依赖重启续命。

#5.3 把"个人工具"变成"企业入口"

  • 暴露 Prometheus / OpenTelemetry 指标,与 NSN 对称,接入企业可观测设施。
  • 支持 SOCKS5、PAC 自动配置、企业 split DNS。
  • 支持 IPv6(AAAA 解析与 IPv6 VIP 段),与 NSN IPv6 同步推进。
  • 移动端 / 嵌入式形态:把"不需要 TUN / 不需要 root"做成 Android、iOS 的差异化卖点,移动 App 直接以 NSC 作为内嵌网络栈。

#6. 协议层组件清单

按协议层把 NSC 内部的组件做一次扁平汇总,详细职责见上文 §2。

#6.1 控制面接入(客户端方向)

  • SSE 长连客户端:订阅控制中心的事件流(当前 CLI 仅暴露 SSE,Noise / QUIC 已在协议栈层支持但未接入命令行入口)。
  • 事件签名校验器:与站点节点共享同一套机制,对每条配置事件校验签名。
  • 多控制面聚合层:目前消费单一控制中心,聚合接口已就位。

#6.2 数据面接入(客户端方向)

  • WSS 客户端:当前主用形态,向主网关打多路复用流。
  • WireGuard 用户态客户端:TUN 模式下的预期数据通路,目前为占位形态。
  • 多网关管理器:负责"客户端侧选路 + 两层 fallback(网关级与传输级)"。

#6.3 本地接入面(NSC 独有)

  • 本地 DNS 服务器:专用环回地址监听,命中转发的 wire format 实现独立于系统 stub resolver。
  • 虚拟 IP 监听器:每条 (VIP, port) 一只 TCP 监听,捕获用户进程的 connect。
  • HTTP / CONNECT 代理(可选):单 listener 通吃任意 host:port,与虚拟 IP 监听器共享路由决策但走独立连接管道。
  • 虚拟 IP 分配器:段顺序分配 + site 名幂等,用户态形态使用环回留白段,TUN 形态使用 CGNAT 共享段。
  • 路由表与出站封装:VIP ↔ site / port / 网关 映射,采用 lazy binding 屏蔽网关切换。

#6.4 身份与凭证

  • 签名身份:用于注册、签名换 token、对端事件签名校验,与站点节点同结构,持久化在用户侧状态目录。
  • 对等身份:用于 TUN 模式下的 WireGuard,与签名身份共用一份私钥文件。
  • 会话凭证:通过 SSE 通道刷新,接收点已就位但当前实现忽略。
  • 首次入口:authkey 多 realm 注册;device flow 入口为契约预留。