# 覆盖网络 · 设备登录接入 —— 决策与改造方案(2026-09-20) > **一句话**:中继承载走 **C(直连打洞优先、中继兜底)+ A(relay 集群横扩)**,**B(分层中继)后续再考虑**;凭证分发改成"**设备带用户登录态换 per-device grant**",人工 invite 通道只留给服务器节点。 > **执行载体**:交接单 `D:\github\dsh_shenxian\dsh-server-docs\交接单\覆盖网络-序46-设备登录接入网络.md`(本文件只做决策与背景沉淀,⛔ 不含逐步操作)。 --- ## §0 用户口径(原话,⛔ 不改写) - 「想知道50w个客户端要接入覆盖网络,怎么给这些设备一个个发凭证,那登录注册不用留着干啥」 - 「c + a 后续再考虑b 沉淀文档,改造功能支持设备登录接入网络,然后再对接文档中写明接入方式」 ⇒ 三件事:① 决策沉淀 ② 功能改造(设备登录接入) ③ 对接文档写明接入方式。 --- ## §1 结论(先给判定) 1. **凭证分发不是瓶颈**:一次 Ed25519 签名是微秒级,50 万张凭证= 50 万次自动签发,零人工。 2. **人工 invite 不是分发通道**:它服务"没有账号的服务器节点"(几十台)。设备接入的授权来源是**用户登录态**。 3. **真正要改造的是三处**:① relay 的 `DIAL` 白名单是**逐 hostId 名单**(不可扩展)② 网注册表是**单 JSON 全量读写**(不可扩展)③ 缺"用户态签发端点"(现在只有本机 CLI)。 4. **承载才是花钱那一项**:50 万条长连接必须 relay 集群横扩(路线 A);打洞成功的那部分不过中继(路线 C),成本曲线最缓。 --- ## §2 现状取证(2026-09-20 只读实测,⛔ 别凭记忆复用) | 事实 | 证据 | |---|---| | relay 强制身份校验 | 47 `dshs-relay` env `DSHS_OVERLAY_REQUIRE_IDENTITY=1`;`loadIdentityForServer()` 配不全即**起动抛错** | | 两道独立证明 | `server.ts`:① `grant + grantSig` 由受信签名者签 ② `nodeSig` 证明握有节点私钥(光有证书=可转发,所以必须第②条)| | 受信签名者来源 | **不是** env `DSHS_OVERLAY_SIGNER_PUBKEYS`(47 上没有该变量)⇒ 实际 = `/etc/dshs/overlay-signers.json`(SignerSet,由 `DSHS_OVERLAY_ROOT_PUBKEYS` 验签);签名者公钥 hex = `bad464dfd53048efe7b8531029b3030eda49bc12930703d3a2a8f60e3a7daddf` | | `DIAL` 白名单 | `dialers` 按网分桶、**构造时定型、运行期不可改**;`DIAL` 默认拒绝;落点 = 单个 drop-in 的 `Environment=DSHS_RELAY_DIALERS=网/host,…`(`registry.ts#deriveDropIn`)| | 网注册表 | `/var/lib/dshs/overlay/nodes.json` = **单 JSON 全量读写**(`loadRegistry/saveRegistry`);47 上内容为**空** `nodes:{}` | | invite 通道**不产 grant** | `grep grant scripts/overlay-node-admit.cjs` = 空 ⇒ 走 invite/join 的节点只进白名单,在 `REQUIRE_IDENTITY=1` 下仍会被 `verifyPeerGrant` 拒 | | 在线接收端点**未实现** | `grep "overlay/join" src/` = 空;`GET /dshs-overlay/bootstrap` 是**地址目录**(可轮换种子),不是签发入口;overlay 管理端点全 `requireAdmin` | | 网名三形态 | `ops`(运维网)|`u:<租户>`(租户网)|具名网;节点逻辑名 = `/` | | 既有规模推演 | `覆盖网络_千台全场景推演_20260916.md`(**千台**量级)⇒ 50 万台超出两个数量级,须重做推演 | --- ## §3 目标形态:设备登录接入 ``` 设备首启 → 本机生成密钥对(私钥 0600,永不出机) → 带平台登录态调 POST /api/overlay/device-grant → 平台校验:会话有效 ∧ 用户已开通客户端接入 ∧ 设备数未超配额 ∧ 公钥未在吊销清单 → 在线签名者当场签 NodeGrant{version,network,hostId,nodeKey,issuedAt,expiresAt} → 设备持 grant + nodeSig 拨入 relay → relay 验签 + 查吊销 → 准入 ``` - **登录注册的作用**= 这条链的鉴权与授权来源(这台设备属于谁、进哪张网、配额多少)。 - **一设备一 grant**,绑定 `hostId + nodeKey`(搬走无效);**可过期**(`expiresAt`,空串=不过期,仅运维常驻机器用)。 - **短租约+拒绝续签**即等效吊销 ⇒ 吊销清单只装**紧急封禁**,不必承载 50 万条。 --- ## §4 改造项 S0–S4(每项都可独立开关、可回滚) ### S0 · 用户态签发端点(新增) - 落点建议:`src/web/routes/overlay-device.ts`,路由 `POST /api/overlay/device-grant`。 - 鉴权:**用户会话**(⛔ 不用 `requireAdmin`);入参 `{devicePubKey(64hex), deviceName}`;出参 `{network, hostId, grant, grantSig, expiresAt}`。 - 签名:**复用 `identity.ts` 里 keyring CLI 用的那个签发函数**(⛔ 不新造签名实现);私钥仍是 47 的 `/etc/dshs/overlay-signer-key.pem`(0600)。 - `hostId` 生成:`d--`(满足 `isHostId`:≤63、小写字母数字 `_.-`、⛔ 不用中文/大写)。 - 有效期:默认 24 h,键 `DESKTOP_GRANT_TTL_HOURS` 进 `config/platform.env`(⛔ 不硬编码进代码)。 ### S1 · 客户端侧接入(属桌面线 lane) - grant 落 `0600`;拨入时带 `grant/grantSig/nodeKey/nodeSig` —— **现有 wire 已支持这四个字段**,协议零改动。 - 每次启动用登录态换新 grant(=自动续期),换不到即视为被停用。 ### S2 · relay 准入改造(**最关键**) - 现状矛盾:逐设备 `DIAL` 白名单与 50 万设备**结构性不兼容**。 - 目标:设备网(`u:`)改为「**网级放行 + 逐设备 grant 授权**」。实现二选一(执行时定):① dialers 支持网级通配(`u:5/*`)② 新增显式开关 `DSHS_RELAY_TENANT_NET_OPEN=1`。 - 🔴 纪律:**⛔ 不把 `dialers` 改成"运行期可变"**("构造时定型"是有意的安全判据);放行粒度一律用**显式开关**表达,绝不引入隐式放行。 ### S3 · 台账与吊销 - 设备**不进** `nodes.json`(避免 50 万条全量 JSON);设备台账进平台 **PG** 新表 `overlay_devices(user_id, device_id, host_id, node_key, created_at, revoked_at, last_grant_at)`。 - 吊销:优先**停发**(租约到期自然失效);紧急封禁才写 revocations 清单(`overlay-keyring.cjs sign-revocations`)。 ### S4 · 承载:路线 C + A - **A**:relay 集群横扩 —— 复用既有地址目录机制(`directory.json` + `DSHS_OVERLAY_ADDR_OVERRIDES` + 无鉴权 `bootstrap` 端点做种子下发),客户端按地域/哈希选点;relay 侧仍是"每会话声明网名",一台可承载多网。 - **C**:打洞优先、中继兜底 —— ⚠️ 前置是 **D8 云安全组放通 UDP `21100/21115`(对端 `/32`,⛔ 零个 `0.0.0.0/0`)**,未落地前打洞成功率无法测。 - **B 分层中继**:本轮**不做**,保留为后续选项(触发条件:S4 压测显示单层集群成本/延迟不可接受)。 --- ## §5 容量(**量级估算,未实测**) | 项 | 量级 | 依据 | |---|---|---| | 50 万次签发 | 对平台可忽略(每次签名微秒级) | Ed25519 签名成本 | | 50 万条 relay 长连接 | 每连接 TLS + 用户态缓冲 **20–60 KB** ⇒ 单机 10–30 GB | **待压测**,⛔ 不作为决策依据直接引用 | | 单机连接数上限 | 须实测后定 | 现有观测面 `scripts/overlay-probe.cjs` 可扩展为压测 | ⇒ 结论:**先小规模实测"每连接成本 + 打洞成功率",再定集群规模**(否则 A 的机器数只能靠猜)。 --- ## §6 风险与未决 1. 🔴 **relay 集群的寻址一致性**:多 relay 下"同一对节点被分到不同 relay"时的互连路径,须在 S4 明确(现网只有 2 台自研 relay,且只绑回环 20080)。 2. ⚠️ **租户网尚未接线**:现网 relay 只有 `ops` 一张网的 dialers 桶;S2 落地前先要把 `u:` 网端到端跑通一次。 3. ⚠️ **grant 私钥单点**:所有设备凭证都由 47 上同一把签名者私钥签 —— 泄漏即全网可伪造。离线根可撤换签名者,但**轮换演练未做**(`overlay-keyring.cjs recover-root` 有演练能力)。 4. ⚠️ **52 万次/天的续签请求**(24 h 租约 × 50 万设备)会打到平台 API:须做批量/合并续签或延长租约(这是 S0 的容量项,与 relay 承载无关)。 --- ## §7 关联文档 - 交接单(执行载体):`dsh-server-docs/交接单/覆盖网络-序46-设备登录接入网络.md` - 对接(桌面线):`E:\ProgramData\AIProject\dsh-ai1net-desktop\docs\对接单_桌面端接入覆盖网络_给平台会话_20260920.md` - 规模推演(千台量级):`覆盖网络_千台全场景推演_20260916.md`、`覆盖网络_百台规模推演_20260916.md` - 路径/参数:`参数表_覆盖网络_20260917.md`;线入口 = 工作区根 `接续入口_覆盖网络线_20260916.md`