Files
dsh_ai1net_server/归档/交接单-20260924-归档/交接单_网抽象与地址规划R6_20260916.md
T
admin 7bd1151c67 chore(工作区): 归档交接单 27 件 + 新增 CODEBUDDY §9 工作区卫生
交接单按归属约定(正文落文档库、工作区只放指针)归档至
归档/交接单-20260924-归档/,含逐件判定 README:
- 16 件已被文档库正式版取代(T09–T21 + 覆盖网络-24/25/26)
- 4 件主题已被覆盖网络线入口汇总
- 7 件历史接续包/规划件

CODEBUDDY §9:收口清本棒 tmp、tmp 保留期 7 天、禁「待清理」中间态、
工作区入库只放文档与文件、不保留脚本副本、>60 KB 单文件须逐个判。
2026-09-24 07:58:57 +08:00

14 KiB
Raw Blame History

交接单 · 覆盖网络主线 ②「网抽象 + 地址规划 + 引导」(执行会话专用 · 2026-09-16 23:4x)

口径指纹(必须校验):tail -n +4 交接单_网抽象与地址规划R6_20260916.md | md5sum = 0d3f1f305e9e6219afa97cdfecff05ba 不符 ⇒ 停下,只报告「口径已更新,需重新接续」。


执行进度(⛔ 下一棒先读这一段,别从头重做)

步 状态 证据 / 落点
Step 1 · P0-1 网抽象 ✅ 已完成并端到端验收(2026-09-17 00:0x–00:15) 新增 src/net/relay/network.ts + test/overlay-network.test.mjs(7 条);会话/端点表按逻辑名索引;DIAL 双门(本网白名单 + 同网);dsh_hosts.network_id(SQLite V9 / PG V9 已迁);单测 38/38;产物铺 47 并 md5 逐同
Step 2 · P0-2 引导三级链 ✅ 已完成并端到端验收(2026-09-17 06:45–07:05) 新增 src/net/relay/directory.ts + src/web/routes/overlay.ts + test/overlay-bootstrap.test.mjs(9 条);目录端点 GET /dshs-overlay/bootstrap(Ed25519 签名,no-store);客户端取址 = env > 缓存 > 种子(origin 优先取缓存里的 bootstrap[] ⇒ 引导地址可在线轮换);47 已清 DSHS_RELAY_URL 仍正常起来;单测 47/47;六件产物 md5 与 47 逐同
Step 3 · P0-3 名字解析与授权 ✅ 已完成并端到端验收(2026-09-17 07:2x–07:4x) 逻辑名 <network>/<hostId> 成为 place(parseReachability)/ resolve(Rendezvous.resolve)的唯一入参;控制面所有内存键(地址 / via / 端口 / relay 端点 / 拨号口池)改逻辑名;Reachability 加 networkId;新增 assertSameNetwork;RelayDialer 加跨网失败关闭(带日志);agentBaseUrlOf 在 via=relay 时禁回落 endpoint;relay 跨网拒绝对外统一 target-offline(不泄露对端清单,区分只留服务端日志)。新增 test/overlay-auth.test.mjs(7 条);单测 64/64

✅ 本单三步已全部执行完毕(2026-09-17)。下方 §4/§5 保留为当时的规格(回看用); 下一步的依据不再是本单 —— 见 接续入口_覆盖网络线_20260916.md 的"下一棒"。

⚠️ 指纹已随本段更新(改了正文 ⇒ 指纹连带);接续入口_覆盖网络线_20260916.md 的引用行已同步。


上游依据(⛔ 先读它们,别凭本单复述 —— 本单只做"收敛成可执行"这一步)

  • 主线与优先级:覆盖网络_应用场景与待完善清单_20260916.md §四(P0-1/2/3) + §五
  • 三条 P0 的设计推演(实体在这里):覆盖网络_问题逐条推演与解决方案_20260916.md §A1 / §A2 / §A3
  • 三概念拆分(接入 / 成员 / 可见):覆盖网络_骨干层方案_20260916.md §3.1
  • ① 的落地事实(本单的起点,含回滚表):交接单_relay落地R2-R4_20260916.md §9 / §10 / §11

0. 判定:这一单在解决什么

主线 ① 已完工(会合中继拆分 S0–S4 + R5 会合可换机,见上表第 4 行那份单子)⇒ 机制层已经"能用"。 但机制层有一个结构性缺口:relay 把平台自己的 Worker 隧道和未来的用户设备放进同一个扁平 hostId 命名空间 (src/net/relay/server.ts 的 hostId 全局唯一、无网络维度;全仓 grep -ri networkId|tailnet = 0 命中)。 ⇒ 今天只有 1 个用户、1 个网络,问题不显形;一进第二类节点就会变成"一张巨网 + 靠 ACL 兜", 与项目"权限只准收窄"直接冲突(这正是 清单 §四 P0-1 的判定)。

本单要做的三件事(= 清单 §四 的 P0-1/2/3,别扩大):

  1. P0-1 网抽象:network_id 成为结构性隔离的维度(每用户一张网,不是一张巨网 + ACL)。
  2. P0-2 首次入网引导:三级引导链(内置种子 → 签名目录 → 离线降级),且引导地址可在线轮换。
  3. P0-3 地址规划与名字解析:逻辑名 <network>/<host> + 按网络授权。

⛔ 本单明确不做:应用层 / 房间层 / presence(清单 §五 排在第 7 步)、内容分发、游戏服、 虚拟网卡(L3)—— 理由见 §3 的 D1。


1. 只读前置(开工前逐条跑,全绿才动代码)

# 1) 指纹(不符 ⇒ 停)
tail -n +4 交接单_网抽象与地址规划R6_20260916.md | md5sum

# 2) 抢全局执行锁(抢不到 = 有会话在跑 = 停手,R9)
bash "D:/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh" --claim-exec "<你的会话名>"

# 3) 状态快照(1 次调用顶十几轮探索;⛔ 跑它之前别 Glob/Grep 全库)
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" \
  "E:/ProgramData/AI技能/aliyun-dsh-server/state.py"

# 4) 起点事实复核(① 的终态必须仍然成立,任一条不符 ⇒ 先查为什么,别在本单里顺手修)
ssh [email protected] 'systemctl is-active dshs dshs-relay; curl -s -m 8 127.0.0.1:20080/status | python3 -c "
import json,sys; d=json.load(sys.stdin)
print(\"sessions:\", [(s[\"hostId\"], s[\"ports\"]) for s in d.get(\"sessions\", [])])
print(\"dialers:\", d.get(\"dialers\"))"'

# 5) 改哪些文件的现状(本单要碰的 3 个接缝)
grep -n "hostId" D:/github/dsh_shenxian/src/net/relay/server.ts | head -20   # 扁平命名空间的现场
grep -n "relayUrl\|relayStatusUrl\|relayDialHost" D:/github/dsh_shenxian/src/config.ts

2. 已定项(我的技术决策 —— 已定,可推翻;⛔ 不要再拿去问用户)

D1 · ② 阶段不引入虚拟网卡 / L3 地址

沿用 hostId 命名 + wss mux 流转发(R0–R5 已跑通、零新增公网口、不需要管理员权限)。

  • 为什么:L3(TUN)会立刻带进三个我们自己文档已经点名的坑 —— ① 虚拟网卡驱动需要管理员权限(清单 §四 P1 明列为 R5 门禁); ② 中国移动等运营商的大内网正是 100.64.0.0/10,而我们设备池里有 200 台 CGNAT + 150 台移动网 (§A3 推演)⇒ 覆盖网地址若落进该段,会出现宿主路由冲突、表现是"部分节点时通时不通、极难排查"; ③ MagicDNS / split DNS 有覆盖用户系统 DNS 的风险(§A3)。
  • ⇒ 本单不碰 100.64.0.0/10、不自建 DNS、不改用户解析。P0-3 在本阶段收敛为"逻辑名 + 授权"; 物理地址段问题整体推迟到将来的 L3 专项,前置约束已在 §6 落笔(别丢)。

D2 · 网络标识用租户维度的显式列,不新造体系

  • network_id 的取值:运维网固定 ops(47 / 106 / 未来的中继与骨干);用户网 = u:<userId>(该用户全部设备)。
  • 节点逻辑名 = <network_id>/<hostId>;relay 侧按网络授权(ops 只拨 ops;u:X 只拨 u:X)。
  • 结构性隔离 > 策略性隔离(§A1 的关键判断):每用户一张网,不是"一张巨网 + ACL"。理由:写错一条 ACL 就泄露,而结构性隔离不可能越界。
  • ⚠️ §A1 明写「要在第一次落地时就分开,不能等」⇒ 本单必须真落地,只写文档不算完成。
  • ⚠️ 与既有判据一致:会合/中继可多实例(数据面),成员资格只能由控制面签发(骨干层方案 §3.2)。

D3 · 引导走三级链,种子复用现有域名(不新增域名成本)

  • ① 内置种子:常量位 = https://alotbuy.com/dshs-relay(已持证书);同时留出第二地域的常量位但不填。
  • ② 签名目录:控制面下发(签名 + 版本号),客户端缓存,按 update_frequency 刷新。
  • ③ 离线降级:缓存过期仍可用(只影响新节点加入,不影响已建连接)。
  • 🔑 §A2 的关键设计要求(不做会成灾):目录里必须带 bootstrap[] 字段, 引导地址要能通过已建立的连接在线下发 —— 否则将来换域名/换机器 = 所有客户端必须升级重装。

D4 · 只做逻辑名,不做 DNS / 不下发对端清单

  • 连接授权按 network_id 判;接入与可见解耦(骨干层方案 §3.1:可借道转发,但不暴露对端清单)。

3. 范围

会碰(预计 6–9 个文件,全部 ≤10 ⇒ 不受 R7 批量写入约束): src/config.ts(+ overlayBootstrapSeeds / overlayNetworkId)|src/net/relay/wire.ts(HELLO 加 network)| src/net/relay/server.ts(按网络授权 + 目录签名)|src/net/relay/client.ts / dialer.ts(带上自己的网络)| src/web/server.ts(/dshs-overlay/bootstrap 只读端点)|DB 迁移(dsh_hosts 加 network_id)|新增 test/overlay-network.test.mjs。

⛔ 不许碰:@deepseek-ai/dsh 主程序与缓存(R2)|client bundle 的 exports.default(R3)| relay 的既有帧语义(只能加帧,不能改旧帧)|32023 / SSH 版中继(已判「不做」)| 100.64.0.0/10(D1)|应用层 / 房间层(§0)。


4. 步骤(每步单独可回滚,做完一步就停下来报一次)

Step 1 · P0-1 网抽象(network_id 落地)

  1. dsh_hosts 加列 network_id text not null default 'ops'(运维网是现网事实 ⇒ 存量行天然正确)。
  2. relay HELLO 帧加 network 字段(只加字段);relay 侧由 dialers: Set<string> 升级为 dialers: Map<networkId, Set<hostId>> 语义(权限只收窄:默认拒绝,命中才放行)。
  3. Manager 的 DSHS_RELAY_DIALERS 由「扁平 hostId 列表」改为「network:hostId 列表」,旧值照旧可用(过渡期不破坏现网)。
  4. 判据(这一步的核心):ops/manager 仍能拨 ops/w-106;构造一个 u:test-network 的注册方, 断言它拨不动 ops/w-106(dialer-not-in-network)。 ⇒ 把"不能到达"变成可回归断言(这就是 §A1 说的"ACL tests 进 CI"的等价物)。

Step 2 · P0-2 引导(三级链)

  1. 新增只读端点 GET /dshs-overlay/bootstrap:返回 {version, relays[], bootstrap[], sig}, bootstrap[] = 可轮换的引导地址清单(D3 的 🔑)。
  2. config.ts 加 overlayBootstrapSeeds(默认 ['https://alotbuy.com/dshs-relay'],第二地域留空)。
  3. 客户端取址顺序:env 显式 > 缓存目录(未过期)> 内置种子;任一成功即写缓存。
  4. 判据:清空 env + 清缓存启动 ⇒ 仍能起来(走种子);改目录里的 bootstrap[] ⇒ 新会话读到新值(不重装、不升级);签名不对 ⇒ 失败关闭(不得静默接受)。

Step 3 · P0-3 名字解析与授权

  1. 节点逻辑名统一为 <network_id>/<hostId>(place/resolve 的唯一入口各改一处,别在调用方拼)。
  2. 跨网络访问在 relay / 控制面被拒(⛔ 不许靠客户端自觉)。
  3. 判据:u:A 的节点看不到、也到不了 u:B 的节点;ops 节点对用户网络默认不可见。

5. 验收(命令级,缺一条不算完成)

# 判据 期望
1 node --test test/relay.test.mjs test/remote-spawner.test.mjs test/remote-user-fs.test.mjs test/overlay-network.test.mjs 全绿(含 ① 的 T18/T19 与 A1 的 U1–U8,⛔ 一条都不许退化)
2 node scripts/check-layering.mjs 无新增违规(基线 5 条)
3 ops/manager → ops/w-106 控制面 + 实例面 全通(R5 终态不退化)
4 u:test-network → ops/w-106 被拒,且拒绝点是 relay(有日志),不是客户端
5 清 env + 清缓存冷启动 经内置种子取到目录并连上
6 目录里 bootstrap[] 改值 新会话读到新值(不重装)
7 门户 + 实例页 仍 200(用户可见面不受影响)
8 公网暴露面 零新增(ss -lntH 对比基线;relay 仍只绑 127.0.0.1:20080)

6. 回滚(分层,任一层可单独退)

层 动作 影响
代码 scp 回 /opt/dshs/lib-bak-* 对应文件 ⇒ systemctl restart dshs 回到本单之前
relay DSHS_RELAY_DIALERS 改回扁平 hostId 列表 ⇒ restart dshs-relay 网络维度失效,行为回到 ① 终态
DB dsh_hosts.network_id 列保留但不再读(⛔ 不要 drop:现网列删除不可逆) 无影响
端点 /dshs-overlay/bootstrap 直接下线路由 客户端回落到 env 取址(= 今天行为)

📌 L3 专项的前置约束(将来谁做谁先读):① 虚拟网卡需管理员权限 ⇒ 命中 R5,先出权限影响评估; ② 避开 100.64.0.0/10(中国移动大内网就是它,本池 200 台 CGNAT + 150 台移动网); ③ 避开 10/8 · 192.168/16 · 172.16/12;④ base_domain 必须与 server_url 域名不同; ⑤ 默认不覆盖用户系统 DNS(magic_dns: false + 客户端 --accept-dns=false)。


7. 残留与已知项(本单不解决,别顺手做 —— R7)

  • state.py 的锁判据已于 2026-09-16 修(原把 .exec-lock 目录当文件读 ⇒ 恒报"空闲")—— 复验:持锁时运行必须报占用。
  • 腾讯云安全组 32022 放行规则仍在(本机已无监听)⇒ 需控制台凭据,不属本单。
  • 覆盖网络_瓶颈落地方案 §3 标题的 10.8 GB 笔误(实为 10.8 MB/台)⇒ 清单 §四 P2,不属本单。

8. 回报格式(每步做完照这个写,别自由发挥)

## Step N · <P0-x 名称>
- 改动:<文件:行 级清单>(新增/修改分开写)
- 判据:<命令> ⇒ <实测输出原文>
- 未过 / 存疑:<逐条,没有就写"无">
- 回滚:<本步的退法,一句话>
- 纪律:未 commit / 未 push;锁 <持/放>;工具调用 N 次

9. 纪律(沿用本线惯例)

  • ⛔ 不 commit、不 push(未获授权);改完只铺 47 的 /opt/dshs/lib/ + 重启 dshs。
  • 动手前抢全局执行锁,做完立刻释放(带锁结束回合 = 把所有人挡在门外)。
  • 每步 只做一件事;发现的额外问题先报告后动手(R7)。
  • ⛔ 不要回头问"要不要部署" —— 部署是本 lane 内的执行细节,直接做,动手前一句话说明。
  • ⛔ 完成任一段后不要把"下一棒"写成应用层 / presence(清单 §五 排在第 7 步)。