NSIO Docs
NSIO Docs
首页

NSIO Next · 当前开发基线

统一 L3 + L4 架构
产品能力目录与实现闭环
功能闭环总账
产品闭环与功能实现
实施蓝图与交付契约
发行、升级与卸载
跨组件共享契约
控制资源生命周期
控制投影与 Runtime Protocol
数据面协议
Grant 与策略编译

组件实现规格

ns · 统一节点运行时
Client · App、CLI 与门户
NSD · 身份与控制面
NSGW · 增值数据面
身份、登录与会话
注册、准入与配额协议
商业模型与 Entitlement
采购、计费与发票
服务运营与 SLA
备份与灾难恢复
信任、合规与数据权利

Last Updated: 2026/8/10 04:54:34

Previous Page控制资源生命周期
Next Page数据面协议

#NSIO Next · 控制投影与 Runtime Protocol v1

本文定义 NSD、ns、NSGW 和 Client 共同实现的首版运行控制协议。字段名是实现基线;具体编码首选 canonical JSON/CBOR 中可稳定签名的一种,由 ns-shared-next 固定。所有示例省略共享事件信封中的签名和时间字段。Peer、Relay、ServiceFlow、RouteFlow 和 Gateway 的建流/传输以 Data Plane Protocol v1 为权威。

#1. Profile 与认证握手

一个 Profile 绑定一个 NSD deployment、账号会话和本地 Organization Device identity。profile_id 只是 Client 本地配置 ID,不发送给 NSD,也不进入签名 artifact。/control 使用四个 production SignedArtifact 完成认证,不能压缩成只校验 Device ID 的一次请求/响应:

ns                                                  NSD
 |-- Device Key 签名 ControlHello ----------------->|
 |<--------------- NSD Authority 签名 Challenge ---|
 |-- Device Key 签名 ControlAuthenticate ---------->|
 |<------- NSD Authority 签名 SessionCredential ---|

#1.1 ControlHello

ControlHello 至少包含:

  • protocol_version=1、expected_deployment_id、device_id、device_key_id;
  • 16-byte client_nonce;
  • producer 为 ns 的完整 CapabilityManifest,而不是未认证的 capability header;
  • 按 (target_membership_id, stream) 严格排序的 projection watermarks。

NSD 可以先解码 device_id 定位注册记录,但在注册 Device Key 验证原始完整 artifact前,任何 Hello 字段都不是权威。Device 已禁用/撤销、Deployment 不同、Key ID 不同、签名错误分别拒绝;不能通过未知字段或 capability 降级进入旧认证路径。

#1.2 ControlChallenge

NSD 验证 Hello 后计算两个已签名 manifest 的确定性交集,并要求结果仍包含 control@1.0。Challenge 由 NSD Authority 签名,绑定:

  • challenge_id、服务端 nonce、Deployment、Organization、Device 和 Device Key;
  • 完整 Hello artifact digest 与 client manifest digest;
  • NSD manifest、精确协商结果、签发时间和到期时间。

Challenge 最长 60 秒。NSD 还要在服务端保存 Device authority generation、预期 session generation、Hello/Challenge 原始字节及一个非 wire 字段 transport_binding。该 binding 必须来自已认证信道 exporter 或等价的连接唯一值,不能 使用客户端自报连接 ID。

#1.3 ControlAuthenticate

ns 先验证 NSD Challenge 签名、Deployment 和 Hello 绑定,再对 digest(exact signed Hello, exact signed Challenge) 签名。NSD 收到 Authenticate 后重新 读取当前 Device 权威,验证完整 transcript,并在一个原子提交里:

  1. 确认 Challenge 未过期、未被不同 proof 使用,且 transport binding 相同;
  2. 确认 Device 仍 active,authority/session generation 未变化;
  3. 确认 Authenticate digest 和 transcript 精确匹配;
  4. session generation 只增加一次,Challenge 同时转为 consumed;
  5. 签发并保存原始 ControlSessionCredential bytes。

同一认证 transport 上对同一个 Authenticate 的精确重传返回原来那份已签名 credential; 不同 transport、不同 proof、过期 Challenge、已被后续 session 取代的旧重传都拒绝。并发 提交不能签发两个新 generation。

#1.4 ControlSessionCredential

Credential 最长 15 分钟,scope 在 v1 只能是 runtime_projection_read,并精确绑定 Deployment、Organization、Device、Device Key、当前有权读取的排序 Membership 集、三个 capability digest、transcript digest、Authenticate artifact digest、session generation、 revoke key 和有效期。它只允许读取发给这些 Membership 的 runtime 投影,不能调用租户 管理 API,也不能因为 Device 后续加入了 Network 就自动扩大旧 credential。

过期前续期必须重新执行握手。Device authority、Membership 或 Device Key 发生变化时, 待提交 Challenge 失效;已发 credential 的在线撤销和短 TTL 共同满足撤销 SLA。

NSD 必须把当前 Credential 的 ID、session ID、generation、revoke_key、固定有效期和 撤销 disposition 与 Device session authority 原子持久化。按 revoke_key 撤销只命中这 一份当前 Credential,不改变 Device/Membership authority,也不增加 session generation; 重复撤销幂等,错误或已被替换的 key 返回 superseded,不能影响新会话。投影发送前、空闲 唤醒后和 ACK 持久化前都重新检查该 disposition。撤销后旧流不能再收事件或提交 ACK, 同一 active Device 可重新认证为新 generation,并从未清除的 outbox 继续收敛。

#1.5 实现边界

Shared 定义 canonical CBOR、domain-separated digest、签名与四消息验证;NSD 定义原子 Challenge/session authority;HTTP/QUIC framing 只负责有界传输和生成可信 transport binding。传输层不得重新解释身份、capability 或 Membership scope。持久化、多实例 claim、 故障转移和网络 ACK 是独立实现项,不能从单进程握手测试推断已经完成。

#2. Event Type Registry

事件的 event_type 与 schema_version 分开:

StreamSnapshotDelta/StatusTarget
netmapnetmap.snapshotnetmap.deltaMembership
grantsgrants.snapshotgrants.deltaMembership / Gateway
servicesservices.snapshotservices.deltaMembership
dnsdns.snapshotdns.deltaMembership
routesroutes.snapshotroutes.deltaClient/Connector Membership
gatewaysgateways.snapshotgateways.deltaMembership / Gateway
publicationpublication.requestpublication.statusDevice / Membership
capability activationcapability_activation.snapshotcapability_activation.revokeMembership / Gateway
edge ingressedge_ingress.snapshotedge_ingress.revokeGateway
operationoperation.status-Client Session

首版 schema_version=1。新增可选字段保持 1;改变必需语义才产生该 event type 的新 major,不联动其他 stream。

#3. 类型化目标

所有事件信封使用:

"target": {"type": "membership", "id": "nm_01"}

type 枚举为 device、membership、gateway、client_session。事件必须恰好有一个 target;消费者拒绝目标类型不适用、ID 不属于当前认证身份或 target 与 payload scope 不一致的事件。

#4. Netmap Snapshot v1

{
  "membership": {
    "membership_id": "nm_01",
    "network_id": "net_01",
    "node_id": "node_01",
    "node_name": "clark-mac",
    "node_fqdn": "clark-mac.home.node.ns.io",
    "node_ip4": "100.96.0.10",
    "node_ip6": "fd7a:...::10",
    "lease_expires_at": "..."
  },
  "peers": [
    {
      "membership_id": "nm_02",
      "peer_epoch_id": "pe_01",
      "node_id": "node_02",
      "display_name": "office-server",
      "node_fqdn": "office-server.home.node.ns.io",
      "node_ip4": "100.96.0.20",
      "node_ip6": null,
      "wg_public_key": "...",
      "wg_key_epoch": 7,
      "discovery_public_key": "...",
      "discovery_key_epoch": 12,
      "runtime_transport": {
        "ip": "100.96.0.20",
        "udp_port": 47998,
        "alpn": "nsio-flow/1"
      },
      "endpoints": [
        {"candidate_id": "cand_01", "kind": "lan", "address": "192.168.1.8:51820", "expires_at": "..."},
        {"candidate_id": "cand_02", "kind": "reflexive", "address": "203.0.113.8:40231", "expires_at": "..."}
      ],
      "relay_candidates": ["relay_hkg_01"],
      "peer_path_lease": "signed-peer-path-lease",
      "peer_lease_expires_at": "..."
    }
  ]
}

Peer 出现在 netmap 只表示有建立加密路径的必要,不等于允许所有流量。peer_path_lease 的完整 claims、路径用途与 Node L3 反欺骗规则见 Data Plane Protocol §4.3;它只授权到指定 key epoch 的加密 carrier,不授权任意 Node packet。runtime_transport 使用 Data Plane Protocol §3.1 的 userspace demux 或 exclusive-listener profile,只承载 ServiceFlow/代理型 RouteFlow,不产生 Node L3 权限;隔离验证失败整轴为 not_ready。Peer 删除或 lease 到期后停止新会话并按 drain policy 清理路径。

#5. Compiled Grant Projection v1

NSD 把创作层 selectors 规范化成目标节点可直接执行的规则:

{
  "rules": [
    {
      "compiled_rule_id": "cr_01",
      "source_grant_set_id": "gs_01",
      "source_grant_rule_id": "gr_04",
      "direction": "ingress",
      "subject": {"kind": "membership", "ids": ["nm_02"]},
      "resource": {"kind": "node", "id": "node_01"},
      "actions": ["connect"],
      "constraints": {
        "protocols": ["tcp"],
        "ports": [{"from": 22, "to": 22}],
        "source_network_ids": ["net_01"],
        "not_before": null,
        "not_after": "..."
      }
    }
  ],
  "default_effect": "deny"
}

Resource kind 包括 node、service、route、exit、ingress-admin、publication。数据面 projection 只含执行所需 ID,不下发用户邮箱、Group 完整成员或 authoring selector。规则为空表示明确无授权;读取/编译失败不会产生这份空 snapshot。

Node 流量同时通过源端 egress projection 和目标端 ingress projection。Service 在 consumer 与 publisher/NSGW 两端执行同一 service_id 的动作约束。

#6. Service Snapshot v1

{
  "role": "consumer",
  "services": [
    {
      "service_id": "svc_01",
      "name": "git",
      "display_name": "研发 Git",
      "service_fqdn": "git.dev.svc.ns.io",
      "custom_domains": ["git.internal.example.com"],
      "vip4": "100.100.0.15",
      "vip6": null,
      "protocol": "https",
      "ports": [{"public": 443, "target": 443}],
      "backend_kind": "node",
      "endpoints": [
        {
          "endpoint_id": "ep_01",
          "publisher_membership_id": "nm_20",
          "flow_credential": "signed-service-flow-credential",
          "priority": 100,
          "health": "ready",
          "lease_expires_at": "..."
        }
      ],
      "labels": {"category": "dev"},
      "lease_expires_at": "..."
    }
  ]
}

backend_kind 为 node 或 gateway_egress。Gateway Exit 不是 Service,不得出现在 Service 列表。消费者不接收 backend origin;publisher/NSGW 在自己的目标配置中接收后端。

发给发布 Membership 的同一 stream 使用 role=publisher,每项至少包含 service_id、endpoint_id、publisher audience、local manifest ID/revision/digest、允许的 credential issuer/audience、protocol/port/L7 enforcement、generation、service_projection_digest、input revision、完整 provenance 和有效期。Publisher 不接收 User/Group 目录;收到 role 不匹配的 payload 必须拒绝。

ServiceFlowCredential 不是不透明占位符。其完整 claims、本地验签、建流握手、半关闭、UDP timeout、换代和撤销见 Data Plane Protocol §6。消费者与 publisher 必须绑定同一 generation/digest,且不得 union 两代允许集合。

Endpoint health 枚举 ready、degraded、offline、disabled。Service 没有健康 endpoint 时仍可保留目录项并显示 unavailable;服务读取失败是 projection 级 error,不等于 services=[]。

#7. DNS Snapshot v1

{
  "zones": [
    {"kind": "node", "suffix": "home.node.ns.io"},
    {"kind": "service", "suffix": "dev.svc.ns.io"}
  ],
  "records": [
    {"name": "office-server.home.node.ns.io", "type": "A", "values": ["100.96.0.20"], "ttl": 30},
    {"name": "git.dev.svc.ns.io", "type": "A", "values": ["100.100.0.15"], "ttl": 30}
  ],
  "short_names": [
    {"name": "git", "target": "git.dev.svc.ns.io", "state": "active"},
    {"name": "build", "target": null, "state": "withheld_collision"}
  ],
  "split_domains": [
    {"suffix": "corp.example.com", "route_id": "ar_01", "resolver": "managed"}
  ],
  "upstream_policy": "system"
}

DNS 和对应 Service/Route snapshot 使用同一 config_revision_group,runtime 只在依赖全部可应用后切换。Custom domain 只发给有权访问 Service 的目标。

#8. Route Snapshot v1

发给客户端 Membership 的 payload:

{
  "role": "client",
  "routes": [
    {
      "route_id": "route_01",
      "kind": "subnet",
      "prefixes": ["10.20.0.0/16"],
      "domains": [],
      "via_membership_id": "nm_router",
      "generation": 12,
      "route_projection_digest": "sha256:...",
      "credential": "signed-route-access-credential",
      "activation_epoch": "...",
      "priority": 100,
      "preserve_local": true,
      "lease_expires_at": "..."
    }
  ]
}

发给 Connector Membership 的 payload 使用同一 stream,但 role=connector,每代至少包含 route_id、generation、digest、input revision、精确 prefixes/domain resolution、protocols/ports、NAT mode、allowed source credential issuers/audiences、not_before/not_after 和完整 provenance。消费者必须拒绝与自身角色不匹配的 payload。

RouteAccessCredential 的签名 claims 至少包括 issuer、organization/network、source membership、route ID、connector audience、generation、route projection digest、grant rule、input revision、kernel version、issued/expires 和 credential ID。Connector 本地验签并同时校验实际目的地;建流不能回查 NSD。

kind 为 subnet、application、exit_cidr。Default Exit 放在 Gateway projection,避免普通 Route 与用户 selection 混淆。runtime 对现有 OS route 做精确 owner 检查;projection 不能要求强制覆盖外部对象。

Connector 可同时保存 current、next 和可选 previous generation,但每代目的地集合独立。security_first 以 activation epoch 切换且到点立即拒绝旧代;bounded_drain 的 previous 不超过 credential TTL。消费者绝不能 union 两代目的地。

#9. Gateway Snapshot v1

发给 Membership 的交互和执行投影:

{
  "relays": [
    {
      "relay_id": "relay_hkg_01",
      "region": "hkg",
      "endpoints": ["..."],
      "relay_route_id": "opaque-random-id",
      "relay_session_id": "opaque-random-id",
      "relay_route_lease": "signed-relay-route-lease",
      "lease_expires_at": "..."
    }
  ],
  "exit_candidates": {
    "state": "available",
    "items": [
      {
        "gateway_id": "gw_01",
        "gateway_name": "香港办公出口",
        "profiles": [
          {"profile_ref": "default", "scope": {"kind": "default_route"}, "availability": "ready"}
        ]
      }
    ]
  },
  "exit_selection": {
    "gateway_id": "gw_01",
    "scope": {"kind": "default_route"},
    "state": "persisted"
  },
  "exit_runtime": {
    "tunnel": {
      "peer_public_key": "...",
      "endpoints": ["..."],
      "generation": 4,
      "scope_digest": "...",
      "exit_tunnel_lease": "signed-exit-tunnel-lease"
    },
    "dns_routing": "local",
    "private_network_routing": "local",
    "ipv6_policy": "fail_closed"
  }
}

候选集合错误使用事件/查询 error envelope,不伪装成空 items。no_authorized_candidates 与 temporarily_unavailable 是已读通后的集合状态。

发给 Gateway 的配置按 capability 分开,包含 tenant scope、listener/tunnel/backend、允许 source/resource、limits、secret reference 和 Capability Lease;不包含完整候选或用户目录。

Gateway-target payload 必须带 role=relay|egress|exit|ingress。Relay 使用轮换 opaque session/route ID,不下发稳定 Membership/WG 标识;Egress 复用 Service publisher projection并携带本地 EgressOriginLease;Exit 带 scope digest、tunnel generation 和 ExitTunnelLease;Ingress 带 PublicApplication/Binding revision 和 backend Service credential。各角色收到不适用字段或 capability 时拒绝整份候选配置,不从其它 role 猜默认值。

#10. 上行声明

上行命令共享 idempotency/revision 信封:

CommandProducer关键 payload
node.endpoint.reportnsmembership、endpoint、network type、TTL
node.posture.reportns/MDMsigned claims、observed time
service.declarenslocal manifest revision、protocol、backend metadata、health
service.endpoint.healthns/NSGWendpoint、state、reason、TTL
route.advertisensCIDR/domain、kind、local authorization
gateway.capabilities.reportNSGWcapability/profile、region、capacity、health
gateway.exit.selectionnsgateway ID、scope、expected config revision
projection.ackns/NSGWstream、epoch/revision、applied/rejected
node.key.preparensmembership、current/next key epoch、公钥、proof
node.key.activate.acknsmembership、key epoch、prepared/activated/rejected

声明只报告本机事实,不自授予可见性。Posture claim 有来源与过期;NSD 不信任普通节点自报“managed”。

Endpoint candidate、authenticated discovery probe、漫游和 Node/WG key epoch 状态机见 Data Plane Protocol §4。上报 endpoint 或 next key 只创建候选事实,不授权 peer,也不要求目标端接受。

#11. Runtime Status v1

本地 API 向 Client 返回:

{
  "api_version": 1,
  "lifecycle": "connected_degraded",
  "active_profile": {"profile_id": "...", "organization_id": "...", "network_id": "..."},
  "control": {"state": "ready", "last_event_at": "..."},
  "projections": {
    "netmap": {"state": "applied", "revision": 44, "expires_at": "..."},
    "services": {"state": "outdated_applied", "revision": 18, "expires_at": "...", "error_code": "service_projection_read_failed"}
  },
  "data_plane": {"kind": "tun", "state": "ready", "interface": "..."},
  "path": {"state": "ready", "kind": "direct", "relay_id": null},
  "dns": {"state": "ready", "revision": 21},
  "exit": {
    "intent": "enabled",
    "selection": "persisted",
    "projection": "matched",
    "capture": "ready",
    "forwarding": "ready",
    "ipv6_routing": "external_unmanaged"
  },
  "active_operations": []
}

字段允许新增可选轴。未知 enum/major 必须产生 incompatible,而不是回落到 ready/disabled。

#12. Operation Status v1

长操作返回 operation_id,Client 订阅:

{
  "operation_id": "op_01",
  "kind": "connect",
  "state": "running",
  "phase": "installing_network",
  "progress": null,
  "cancelable": true,
  "result": null,
  "error": null
}

phase 是可枚举真实阶段,不能由计时器伪造百分比。终态 succeeded/failed/canceled 持久化有限时间,重复查询返回同一结果。

#13. Delta 规则

Delta 使用 typed operations:upsert/delete + object ID + object revision。数组整体顺序不是语义;消费者按 ID 构建候选集合,完成全量引用校验后原子替换。Delete 不允许删除本 stream 不拥有的对象。

对规则、DNS/Route 协调组和 Gateway safety config,服务端可直接发送 snapshot,避免复杂 delta 产生半状态。首版优先正确快照,达到实测规模瓶颈后再为具体 stream 引入 delta。

#14. PolicyKernel 与 Projection 不变量

  • 同一 authority revision 产生确定 canonical payload/digest;
  • 每个目标只收到执行和展示所需最小对象;
  • User/Group selector 在 NSD 展开,不下发完整目录;
  • Node、Service、Route、Exit 使用同一 AccessGrant 命中函数;Capability、Edge 和 Admin 使用各自生产执行器但共享 PolicyKernel;
  • provenance 包含 policy schema、kernel/evaluator/compiler version、input revision、rule ID 和 projection digest;
  • 本地 Manifest 与远端 Service authorization 取交集;
  • Route 客户端凭据与 Connector 投影必须匹配同一 generation/digest,Connector 本地校验来源和精确目的地;
  • health 影响 endpoint/candidate availability,不改写授权事实;
  • read/compile failure 不发布空 snapshot;
  • revoke 通过新 revision + 短 lease 到期双重收敛。

#15. 实现顺序

  1. 在 ns-shared-next 建 ID、envelope、target、error 和 canonical fixtures;
  2. 建 Netmap + Runtime Status,使两节点可建立控制会话;
  3. 建 Grant projection 与双端 enforcement;
  4. 建 Service + DNS;
  5. 建 Route;
  6. 建 CapabilityActivation、Gateway 和 Edge Ingress lease;
  7. 增加 delta,首版功能不因 delta 延后;
  8. 为每个 payload 发布 schema、producer fixture、consumer fixture 和反向安全测试。

#16. 验收

  • 真实类型序列化通过 schema;
  • 每个 target 类型有正向和跨类型重放拒绝;
  • snapshot 应用失败不覆盖 active revision;
  • 空授权 snapshot 与 read_failed 可明确区分;
  • Node/Service/Route/Gateway 精确集合多一个对象就失败;
  • Route 双端投影、跨 generation/digest 重放和新旧目的地误 union 必须失败;
  • Gateway 收不到完整用户/Service 目录;
  • 未知可选字段可忽略,未知必需 major 明确拒绝;
  • operation/status 贯穿 daemon、helper、FFI、App 不嗅探字符串。