NSGW · 架构与功能设计
本页从架构视角描述 NSGW(数据网关) 的功能划分与设计特点,以
nsgw仓库main(3d31b5f,2026-10-01)为准(代码与本页冲突时以代码为准)。进行中的计划见 roadmap.md,relay 协议细节见 peer-relay-protocol.md。
1. 角色定位
Section titled “1. 角色定位”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 sgateway.report、20 s realm 快照轮询都是 NSGW 主动发起的控制面调用。 - 无身份维护:不签发 token、不维护用户会话;WSS 与 Exit 承载只校验 NSD 签发的短期 JWT,公网边缘的 OIDC 只在请求级别完成校验与主体提取。
- WG 静态密钥在网关本地生成(
SecretBox),NSD 从不提供私钥。
2. 功能模块划分
Section titled “2. 功能模块划分”2.1 控制面接入(nsgw-control)
Section titled “2.1 控制面接入(nsgw-control)”- 登记:用 platform enrollment token 以 gateway 类型登记;宣告
supported_link_modes/preferred_mode与公网边缘 / UDP 能力;第一次 WG 就绪后的上报追加exit-wss-carrier等能力;声明relay_probe_set.v1以接收中继测量名单。 - 信任发现:只配
nsd_urlHTTPS 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 audiencensgw-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;未知机器仍被丢弃。
2.3 终结(nsgw-wss + nsgw-wg)
Section titled “2.3 终结(nsgw-wss + nsgw-wg)”- 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_openfail-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(BoringTunnoise::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)”- NSD 投影无端点的
gateway_wss_relay配对表 + 访问端的拨号提示(PeerConfig.wss_relay)。 - 访问端开
/client?mode=wss-relay&nsn=<id>,发布端开/wss-relay?nsc=<id>;JWT 校验后对照 realm 的配对快照(opaque_pair_allowed)。 - 以
(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)约束。 - 超时、授权撤销、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-closedTunnelVipClusterTarget)。 - 快照:
EnvoySnapshotController::reconcile从GatewayHttpConfig/GatewayL4Map构建网关级快照;每条路由只加入其所属 realm 的RoutingConfig桶,hostname 与 L4 端口冲突在 ADS 前拒绝;backend 或内部 L4 listener 尚未激活的路由单独省略并带审计状态;其他构建错误保留上一个完整快照;served realm 变化重放保留的投影,撤销不等下一条流事件。语义 schemansgw.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,每个 HCMstream_idle_timeout60 s;WSS SNI passthrough 链无最大连接时长,本地 ACME passthrough 与普通 L4 保留 600 s。- ext_authz(
auth/ext_authz.rs):gRPCCheck走路由的投影方法链(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_buffer64..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同源。
3. 入站端口矩阵
Section titled “3. 入站端口矩阵”| 端口 | 协议 | 语义 | 进入方 | NSGW 是否见明文 |
|---|---|---|---|---|
:51821/udp | WireGuard | 不解密中继 + NSGWP2P1 反射探测 | 访问端 / 发布端 NS | ✗ |
:51820/udp | WireGuard | 终结设备 wg0;Exit / Egress 的 WG leg | NS | ✓ |
:9443/tcp | WSS(rustls) | /client terminate;/client?mode=wss-relay + /wss-relay 不透明缝合;/relay connector;/exit-wss/v1 Exit 承载 | NS | terminate ✓ / relay ✗ / exit ✓ |
:443/tcp + L4 端口 | HTTPS / TCP / UDP(Envoy) | 公网边缘:TLS 终结、Edge Auth、源 IP 准入、入口 ACL | 任意公网客户端 | ✓ |
:9091/tcp | HTTP | livez / ready(readyz)/ metrics / admin | 编排系统 / 运维 | — |
:18000 / :18010 / :18011 | gRPC / HTTP(loopback) | Envoy ADS;ext_authz Check;OIDC login / callback / logout | Envoy sidecar | — |
127.0.0.1:8080 / :8443 / L4 ≥ 20000 | HTTP / TCP / UDP(loopback) | NSGW 内部公网入口 listener;Envoy 内部明文 HTTP listener | Envoy sidecar | ✓ |
4. 设计特点
Section titled “4. 设计特点”4.1 只服务 NSD 投影的模式
Section titled “4.1 只服务 NSD 投影的模式”链路模式由 NSD 为每条 pair 解析,NSGW 从不自己选;backend 种类与链路模式正交。这让网关无状态、可水平扩展。
4.2 role-neutral 的 pair 身份
Section titled “4.2 role-neutral 的 pair 身份”relay 与不透明缝合的准入键是方向性 pair 身份(RelayTarget / RelayAllowanceKey),不是机器角色枚举;同一主体可在不同 pair 的两侧出现。统一节点无需网关协议改动(PLAN-123)。
4.3 fail-closed 无处不在
Section titled “4.3 fail-closed 无处不在”ACL 门、公网路由 realm 一致性、Envoy cluster 目标、Exit 快照、Egress floor 交集都以”拒绝”为默认;只有传输抓取失败保留 last-known-good,且有 600 s 的全拒上限。
4.4 Envoy 是可选边缘,不是核心
Section titled “4.4 Envoy 是可选边缘,不是核心”NSGW 本体是纯 WG / WSS 隧道网关(2026-05-25 决策放弃 Traefik,不再自带反向代理);公网入口是能力门控的可选特性,由 Envoy sidecar 经本地 ADS 承载,NSGW 只提供内部 listener 与验证器。
4.5 多 realm 隔离
Section titled “4.5 多 realm 隔离”一台网关同时服务多个 realm,每个 realm 独立配置桶、独立 ACL、独立路由;桶永不扁平化,未 served 或不可用的 realm fail-closed。
4.6 本地 floor 是兜底
Section titled “4.6 本地 floor 是兜底”gateway_services.toml 声明本网关能做的 Egress / Exit;NSD 只能在其中裁剪,投影与本地清单取交集。
5. 与其他模块的边界
Section titled “5. 与其他模块的边界”NSGW 不做的事情:
- 不签发身份、不维护用户会话(NSD 职责)。
- 不选链路模式、不选网关(NSD 投影 + NS 阶梯)。
- 不解析被中继的 IP 包、不安装普通 LAN 前缀、不做子网 SNAT(发布端 NS 职责)。
- 不在 relay 路径上判用户 ACL(发布端 NS 终决)。
- 不自带管理台。
6. 协议层组件清单
Section titled “6. 协议层组件清单”| crate | 职责 |
|---|---|
nsgw-server | 进程入口、配置 / 密钥 / drain、full / slim 特性装配、公网边缘 ext_authz 与 OIDC RP、JWT authority、Exit 投影切换 |
nsgw-control | 登记、JWT / JWKS、配置流、心跳、gateway.report / services_report、realm 快照、版本 |
nsgw-wg | WG relay(relay.rs)、kernel / userspace 终结设备、Gateway Exit(nft)、Gateway Egress WG leg、路由对账 |
nsgw-wss | TLS WSS 监听、terminate ACL 门与派发、不透明缝合、Exit WSS 承载、WsFrame codec |
nsgw-envoy | Envoy ADS、快照构建、:443 过滤链、证书与客户端 CA 写入、源 IP 分类 |
nsgw-api | admin / ready / metrics、公网入口内部 HTTP backend 与 ACL、TCP / UDP 内部 listener、审计 |
nsgw-routing | RoutingSnapshot、公网路由激活状态、per-realm 路由源 |
nsgw-gateway-egress | Egress 主体 ACL、floor 交集、L4 代理 |
nsgw-quick-relay | Quick 模式的独立 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,见新架构设计) |