跳转到内容

NSGW · 架构与功能设计

本页从架构视角描述 NSGW(数据网关) 的功能划分与设计特点,以 nsgw 仓库 main(3d31b5f,2026-10-01)为准(代码与本页冲突时以代码为准)。进行中的计划见 roadmap.md,relay 协议细节见 peer-relay-protocol.md。

NSGW 是公网边缘与中继组件。在已实现的 managed 模式中,NSD 持有控制状态并置身业务流量之外;NSGW 与 NS 消费它的投影并参与数据面。NSGW 主动向 NSD 发起登记、配置订阅、心跳与上报的出站控制连接;节点主动发起 relay 会话。

Quick 模式由独立二进制 nsgw-quick-relay 服务:src/core.rs 与 src/carrier.rs 是 UDP 与 TLS WebSocket(/keynet/v1)上的免账户 relay 核心,src/store.rs 是持久化的安全标记库,src/quick.rs 是 /quick/v1 短码配对会合,src/region/ 是最多 16 个成员的区域网状连接(keynet-member-<id> 证书、复制、快照、转发与短码 owner 交接),src/operations.rs 提供 /livez、/readyz 与状态。nsgw-server 不依赖它,它也不接触账户模式的登记、授权与路由。协议见 Quick 协议,会合见 Quick 模式 §3。

最重要的信任事实:NSGW 只在 terminate、公网边缘、Gateway Egress、Gateway Exit 四条路径上判用户 ACL。零信任的 relay 模式(wg-relay、wss-relay、direct)下 NSGW 盲转发、不做任何用户 ACL 决策,由发布端 NS 按访问端的 WireGuard 公钥终决。所有 NSGW 的 ACL 门都是 fail-closed:可用策略到达前拒绝,ExplicitEmpty / FetchFailed 回到拒绝,抓取失败超过 acl_fail_deny_after_secs(默认 600 s)转全拒。

运行约束:

  • NSGW 不再是”被动不心跳”的组件:60 s machine.heartbeat、30 s gateway.report、20 s realm 快照轮询都是 NSGW 主动发起的控制面调用。
  • 无身份维护:不签发 token、不维护用户会话;WSS 与 Exit 承载只校验 NSD 签发的短期 JWT,公网边缘的 OIDC 只在请求级别完成校验与主体提取。
  • WG 静态密钥在网关本地生成(SecretBox),NSD 从不提供私钥。
  • 登记:用 platform enrollment token 以 gateway 类型登记;宣告 supported_link_modes / preferred_mode 与公网边缘 / UDP 能力;第一次 WG 就绪后的上报追加 exit-wss-carrier 等能力;声明 relay_probe_set.v1 以接收中继测量名单。
  • 信任发现:只配 nsd_url HTTPS origin;control_trust.v1 给出端点顺序、Noise key、QUIC SPKI current/next pin、Envelope thumbprint;按顺序探测 SSE / Noise / QUIC,每个候选用已发现 signer 做签名探测;真实 QUIC 握手验证 SPKI pin;禁用 0-RTT;可选静态 Envelope pin 作交集门。旧的 endpoint list / bootstrap key / TOFU 入口已删除。
  • 配置流:消费 home realm 的签名 ControlMessage(传输无关解析器,未知 tag 丢弃);经 legacy 未签名或明文 http:// SSE 到达的 gateway_auth_config.v1 被丢弃。
  • 多 realm:RealmConfigSnapshotTask 每 20 s 调用 gateway.realm_config_snapshot,为每个 served realm 维护独立 RealmConfigBucket,暴露热替换的 ServedRealms 集合给 WSS 入口门与公网路由授权。桶永不合并。
  • 上报:gateway.report(遥测、身份、canonical 构建版本、证书状态、已上报的入口端口)、gateway.services_report(gateway_services.toml 的 Egress / Exit floor —— NSD 无法伪造的本地权威)、gateway.usage_report(计量)。
  • JWT / JWKS:nsd_jwt_key_pins / nsd_envelope_key_pins 可选 pin;WSS token audience nsgw-wss。

2.2 WireGuard 不解密中继(nsgw-wg · relay.rs)

Section titled “2.2 WireGuard 不解密中继(nsgw-wg · relay.rs)”
  • 独立 UDP socket(默认 :51821),与终结设备分离。
  • NSD 投影 gateway_wg_relay(按 pair 键控),NSGW 装入配对表并为每个目标预计算 mac1 key。
  • 发布端 NS 从其真实 WG 数据 socket签名 wg_relay.register_source 信封;NSGW 校验签名(pin 到投影身份)后绑定 nsn_pubkey → 观察到的 NAT 源(90 s TTL)。
  • 访问端的 HandshakeInit 用公开的目标公钥算 mac1;match_handshake_init 重算、选学到的源(否则静态 nsn_endpoint,否则丢弃),通过 cookie 与令牌桶准入后双向安装 receiver-index 会话路由;响应按 receiver-index 反向配对,同一目标的多个 pending init 按 last_seen 选择最近者(BUG-031)。TransportData 按 (receiver_index, src) 查表转发,从不解密。
  • 因果转发租约(PLAN-108 / 110 / 111):会话与签名的转发 epoch 绑定;quarantined lease 可恢复;批量发送与 fence 正确性。
  • DoS 边界:wg_relay_max_sessions(4096)、wg_relay_max_pending_inits(1024)、per-target 握手速率 64/s、burst 128、cookie 卸载阈值 16。
  • 同一 socket 服务 NSGWP2P1 反射探测,供 direct P2P 使用。每个反射请求在验签之前先过按源 IP 的令牌桶(每秒 10 个,突发 20,状态有界并按空闲淘汰),超出计为 rate_limited;回复携带 NSD 登记的机器 ID(而不是本地配置的网关标签),使节点能把 RTT 样本对应到测量名单。
  • 测量探测:NSD 随中继表投影 GatewayWgRelayConfig.probe_set(随 realm 一起租约与栅栏)。配对行会拒绝、但机器 ID、机器公钥与 WireGuard 公钥与本 realm 某个 probe set 条目一致的反射请求也会得到应答:响应与 nonce 防重放不变,但既不绑定 pair 源也不刷新中继路由,计为 measurement_accepted;未知机器仍被丢弃。
  • WSS 监听器 serve 按路径分类升级:/client(terminate)、/client?mode=wss-relay(不透明)、/relay、/wss-relay、/exit-wss/v1;verify_wss_claims 校验 issuer / aud=nsgw-wss / role / served realm。
  • 终结 ACL 门 AclState::evaluate_open fail-closed;权威 realm 快照加载后,terminate-WG 主体 peer 用其 home realm 的 ACL 桶而非 realm-less legacy 快照。
  • registry.rs 派发:handle_client_open、egress_for 选择 WSS 缝合或 WG bridge;handle_gateway_owned_client_open 短路 Egress / Exit backend。
  • WG 数据面:kernel(wireguard-uapi + rtnetlink,wg0)或 userspace(BoringTun noise::Tunn,NSGW 自持 TUN 与双栈 UDP socket);两者都用共享的 1360 明文 MTU。
  • WsFrame codec(MAX_FRAME_SIZE)、connector 会话状态、不透明配对 rendezvous。

2.4 WSS 不透明中继(nsgw-wss · serve.rs)

Section titled “2.4 WSS 不透明中继(nsgw-wss · serve.rs)”
  1. NSD 投影无端点的 gateway_wss_relay 配对表 + 访问端的拨号提示(PeerConfig.wss_relay)。
  2. 访问端开 /client?mode=wss-relay&nsn=<id>,发布端开 /wss-relay?nsc=<id>;JWT 校验后对照 realm 的配对快照(opaque_pair_allowed)。
  3. 以 (realm, nsc, nsn)(报文中 nsc / nsn 指访问端 / 发布端)为键的 oneshot rendezvous 停泊第一条 leg;每个精确 / legacy 键一个 owner token,第二条 leg 以精确优先取走并交接流;重复不会替换 pending 幸存者。第一条 leg 等待配对最多 30 s;建立后网关每 15 s 向两条 leg 发 ping,45 s 无应答即关闭整对。WSS 会话总数受 wss_max_sessions(默认 4096)约束。
  4. 超时、授权撤销、drain、关停、重复拒绝、交接失败都先移除 owned pending 键,再在 250 ms 硬期限内发送一次诊断 Close;不等待对端 Close。run_opaque_bridge 原样转发每个 Binary 帧,只在目的 sink 接受后计量。网关不持任何 WG 密钥。

2.5 公网边缘(nsgw-envoy + nsgw-api + nsgw-server/auth)

Section titled “2.5 公网边缘(nsgw-envoy + nsgw-api + nsgw-server/auth)”
  • 拓扑:Envoy 在 host network(:443 + NSD 分配的 L4 端口),NSGW 在私有 bridge;Envoy 主动连接 AdsService,一条流取全部 xDS(CDS / LDS / RDS,按 type URL;无 delta-ADS,无流上 SDS);TLS 材料走 filesystem SDS。每个 Envoy cluster 只指向 NSGW 内部 listener(HTTP :8080、L4 ≥ 20000、WSS passthrough),从不直指发布端地址(fail-closed TunnelVipClusterTarget)。
  • 快照:EnvoySnapshotController::reconcile 从 GatewayHttpConfig / GatewayL4Map 构建网关级快照;每条路由只加入其所属 realm 的 RoutingConfig 桶,hostname 与 L4 端口冲突在 ADS 前拒绝;backend 或内部 L4 listener 尚未激活的路由单独省略并带审计状态;其他构建错误保留上一个完整快照;served realm 变化重放保留的投影,撤销不等下一条流事件。语义 schema nsgw.xds.semantic.v2 让新旧渲染器不会复用 version_info。
  • :443 过滤链(build_http_connection_manager):源 IP RBAC(classify_source_ip,deny-before-allow,支持 XFF 可信跳数 / CIDR)→ jwt_authn → ext_authz → router。每条应用 RouteAction 总超时为 0,每个 HCM stream_idle_timeout 60 s;WSS SNI passthrough 链无最大连接时长,本地 ACME passthrough 与普通 L4 保留 600 s。
  • ext_authz(auth/ext_authz.rs):gRPC Check 走路由的投影方法链(OIDC RP / API key / 签名 URL(AWS SigV4 canonical request,HMAC-SHA256)/ mTLS / Basic(argon2id PHC,RFC 7617 challenge));成功后注入 x-ns-external-subject(s) / x-ns-external-idp 并覆盖客户端自带头,failure_mode_allow=false。JWT authority 由网关本地发现与 JWKS 刷新(默认每 10 min)、持久有界 last-good 快照维护(发行方不可达时最多沿用 12 h)。OIDC 登录会话默认 8 h,登录事务 5 min,对外请求超时 10 s。
  • 证书与客户端 CA:GatewayCertificateBundle → per-host PEM + <host>.yaml 原子写入;GatewayClientCaBundle → CA SDS secret + CRL(only_verify_leaf_cert_crl)。可选本地 lego ACME 仅作回滚。
  • 入口 ACL(nsgw-api/public_ingress.rs):只用路由所属 realm 的桶判 external: 主体;缺失、未 served 或争用的 realm 状态 fail-closed;prepare_upstream_request 可按路由改写上游 Host / SNI;回程剥离 hop-by-hop 头。
  • L4 内部 listener(tcp_ingress.rs / udp_ingress.rs):按 route_id 幂等,绑定 bridge IP;UDP per-flow 回复泵;订阅 served realm 代际以激活 / 撤回外部 realm 的 listener。
  • 请求体 / 并发:64 MiB 请求 / 响应体上限、60 s body 超时、1024 并发。

2.6 Gateway Egress(nsgw-gateway-egress + nsgw-wg/gateway_egress.rs)

Section titled “2.6 Gateway Egress(nsgw-gateway-egress + nsgw-wg/gateway_egress.rs)”
  • evaluate_gateway_egress_acl:fail-closed 的权威主体 ACL(没有发布端兜底);gateway_owned_route_for_open + gateway_egress_target_for_route 取投影与本地 floor 的交集。
  • run_gateway_egress_proxy:解析与连接共用 10 s 期限;WG TCP listener 进程级最多 1024 流并持有 permit 到流结束;一个流 owner 监督客户端读、响应写与目标代理:干净 EOF 半关闭目标并排空回复,硬错误立即终止并释放 permit;WSS 与 UDP 保持即时投递语义。
  • WG client leg:kernel WG + nft / TPROXY 拦截(GatewayEgressWgManager);部署模式 docker_bridge_single_tenant。透明监听默认 TCP :15083、UDP :15084(本地),策略路由表与规则优先级 23083,UDP 流空闲 1 min、最长 5 min。

2.7 Gateway Exit(nsgw-wg/gateway_exit.rs)

Section titled “2.7 Gateway Exit(nsgw-wg/gateway_exit.rs)”
  • GatewayExitManager:per-pairing nft 表 nsgw_gateway_exit + SNAT(fixed 或 masquerade);静态 CIDR 集在一个原子 nft 事务内 flush + 重填,动态 conntrack 集与命名计数器保留生命周期;全局 / per-profile conntrack 上限;gateway_exit_reserved_cidrs 排除段。
  • nsgw-server 序列化最新校验的 Exit 投影(精确 realm / source / peer ACL 准入),先替换 nft 再发布过滤后的完整 WG 快照;只有真正安装或跳过了 admitted desired state 的 reconcile 才清除 dirty 状态;新 realm 快照可通过独立 backend / Egress / terminate 角色保留同一 WG peer 而不保留其已撤销的 Exit 准入;不可用或过期快照保持 fail-closed;retained-old-state 与 usage 边界失败共用一个合并重试驱动。
  • WSS 承载 GET /exit-wss/v1:独立的原始 WireGuard 数据报路径;只接受 aud=nsgw-wss、role=gateway-exit-wss、精确 realm / sub / gateway_id / 32 字节 wg_peer_public_key 的短期 NSD token;wss_max_sessions 与每会话 writer 容量(1024)有界;每个 Binary 帧是一个非空 UDP 数据报(≤ 65535 字节);投影变化、reconcile 失败与 fail-closed 只取消受影响的承载会话;drain 先上报再拆适配器,让 NSD 撤回候选投影。
  • 部署模式 gateway_exit_deployment_mode:默认 docker_bridge_single_tenant(NSGW 是 Docker bridge 容器命名空间中唯一的 netfilter 租户,持有 Exit 表、conntrack 配额与计数,做第一次 SNAT,容器运行时做第二次);host_network 按字面含义支持;启用 Exit 时拒绝 cni_pod。模式由运维声明,进程不探测命名空间拓扑。
  • 每个 Exit usage 行附带 per-NS 机器主体(计量归属)。

2.8 管理、观测与运维(nsgw-api + nsgw-server)

Section titled “2.8 管理、观测与运维(nsgw-api + nsgw-server)”
  • :9091:/livez(只表示进程存活,与就绪和 drain 无关)、/ready 与别名 /readyz(drain 时 503)、/metrics(Prometheus 文本,含 build_info、可选 per-peer 指标、Egress 事件计数、pending / reap / close 计数)、admin(Bearer token)。
  • 审计环(audit_buffer 64..65536)、结构化 JSON 日志、drain_timeout_secs(300);配置加载产生的警告在 tracing 初始化后统一输出并进入审计。
  • 计量:terminate、公网边缘、Gateway Egress 与 Gateway Exit 各自计量,由 reporter 按窗口经签名的 gateway.usage_report 上报 NSD(超大窗口按行数与签名后大小切分)。WG terminate 的用量按 realm 汇总;两个 served realm 声明同一 WG 公钥时,该 peer 被排除并计入 nsgw_terminate_meter_peer_exclusions_total{reason="ambiguous_realm"};投影不到 realm 的公网路由不计量,计入 nsgw_public_edge_usage_empty_realm_requests_total。
  • 部署:Alpine 运行镜像,cap_drop ALL + cap_add NET_ADMIN,no-new-privileges,core dump 抑制;slim 特性裁剪出无 Envoy / WSS / 数据面、无需 CAP_NET_ADMIN 的 relay-only 二进制。
  • 版本:Git-described 构建版本注入,gateway.report、登记与 build_info 同源。
端口协议语义进入方NSGW 是否见明文
:51821/udpWireGuard不解密中继 + NSGWP2P1 反射探测访问端 / 发布端 NS✗
:51820/udpWireGuard终结设备 wg0;Exit / Egress 的 WG legNS✓
:9443/tcpWSS(rustls)/client terminate;/client?mode=wss-relay + /wss-relay 不透明缝合;/relay connector;/exit-wss/v1 Exit 承载NSterminate ✓ / relay ✗ / exit ✓
:443/tcp + L4 端口HTTPS / TCP / UDP(Envoy)公网边缘:TLS 终结、Edge Auth、源 IP 准入、入口 ACL任意公网客户端✓
:9091/tcpHTTPlivez / ready(readyz)/ metrics / admin编排系统 / 运维—
:18000 / :18010 / :18011gRPC / HTTP(loopback)Envoy ADS;ext_authz Check;OIDC login / callback / logoutEnvoy sidecar—
127.0.0.1:8080 / :8443 / L4 ≥ 20000HTTP / TCP / UDP(loopback)NSGW 内部公网入口 listener;Envoy 内部明文 HTTP listenerEnvoy sidecar✓

链路模式由 NSD 为每条 pair 解析,NSGW 从不自己选;backend 种类与链路模式正交。这让网关无状态、可水平扩展。

relay 与不透明缝合的准入键是方向性 pair 身份(RelayTarget / RelayAllowanceKey),不是机器角色枚举;同一主体可在不同 pair 的两侧出现。统一节点无需网关协议改动(PLAN-123)。

ACL 门、公网路由 realm 一致性、Envoy cluster 目标、Exit 快照、Egress floor 交集都以”拒绝”为默认;只有传输抓取失败保留 last-known-good,且有 600 s 的全拒上限。

NSGW 本体是纯 WG / WSS 隧道网关(2026-05-25 决策放弃 Traefik,不再自带反向代理);公网入口是能力门控的可选特性,由 Envoy sidecar 经本地 ADS 承载,NSGW 只提供内部 listener 与验证器。

一台网关同时服务多个 realm,每个 realm 独立配置桶、独立 ACL、独立路由;桶永不扁平化,未 served 或不可用的 realm fail-closed。

gateway_services.toml 声明本网关能做的 Egress / Exit;NSD 只能在其中裁剪,投影与本地清单取交集。

NSGW 不做的事情:

  • 不签发身份、不维护用户会话(NSD 职责)。
  • 不选链路模式、不选网关(NSD 投影 + NS 阶梯)。
  • 不解析被中继的 IP 包、不安装普通 LAN 前缀、不做子网 SNAT(发布端 NS 职责)。
  • 不在 relay 路径上判用户 ACL(发布端 NS 终决)。
  • 不自带管理台。
crate职责
nsgw-server进程入口、配置 / 密钥 / drain、full / slim 特性装配、公网边缘 ext_authz 与 OIDC RP、JWT authority、Exit 投影切换
nsgw-control登记、JWT / JWKS、配置流、心跳、gateway.report / services_report、realm 快照、版本
nsgw-wgWG relay(relay.rs)、kernel / userspace 终结设备、Gateway Exit(nft)、Gateway Egress WG leg、路由对账
nsgw-wssTLS WSS 监听、terminate ACL 门与派发、不透明缝合、Exit WSS 承载、WsFrame codec
nsgw-envoyEnvoy ADS、快照构建、:443 过滤链、证书与客户端 CA 写入、源 IP 分类
nsgw-apiadmin / ready / metrics、公网入口内部 HTTP backend 与 ACL、TCP / UDP 内部 listener、审计
nsgw-routingRoutingSnapshot、公网路由激活状态、per-realm 路由源
nsgw-gateway-egressEgress 主体 ACL、floor 交集、L4 代理
nsgw-quick-relayQuick 模式的独立 relay 二进制:UDP 与 /keynet/v1 WSS 中继、持久安全标记、/quick/v1 短码会合、区域成员网状连接与 owner 交接、/livez / /readyz / /status
共享(ns-shared)common、control、acl、tunnel-ws、telemetry;Quick relay 另用 keynet-proto、keynet-quick(v1 工作区,归档于 nsshared 的 tag archive/v1-20261002;新线改用 nsshared-proto,见新架构设计)