交接单按归属约定(正文落文档库、工作区只放指针)归档至 归档/交接单-20260924-归档/,含逐件判定 README: - 16 件已被文档库正式版取代(T09–T21 + 覆盖网络-24/25/26) - 4 件主题已被覆盖网络线入口汇总 - 7 件历史接续包/规划件 CODEBUDDY §9:收口清本棒 tmp、tmp 保留期 7 天、禁「待清理」中间态、 工作区入库只放文档与文件、不保留脚本副本、>60 KB 单文件须逐个判。
87 KiB
交接单 · 覆盖网络「节点直连 + P2P 取回」(P2 · S5–S7)
上游依据:
交接单_节点一键加入与分组准入_20260918.md§4.3(缺口 3 · 直连与 P2P)+ §6 P2 段(S5 / S6 / S7)+ §7 权限影响评估(P2 专用) + §8.11(序㊳ 登记的"待你拍板"清单) 归档号:132(现核:04-调整方案/正式档案最大 128;129 / 130 / 131 已分别用于单 A / 单 B / 序㊱ P1 ⇒ ⛔ 不重用;mkdir .lock-132原子占号成功) 本单性质:规划棒产出 —— ⛔ 零代码改动 · ⛔ 零服务器改动 · ⛔ 未 commit / push 用户拍板时间:2026-09-18 12:22(原话见 §1)
§1 用户口径(功能卡,⛔ 别改写成技术题)
用户原话(2026-09-18 12:22,回答 §8.11 的两问):
1 用户可设置,默认开启提示用户 2 乙
⇒ 解读(= 本期唯一功能口径,⛔ 不许扩大也不许缩小):
| # | 口径 | 落地含义 |
|---|---|---|
| ① | 用户可设置 | 直连(打洞)能力由用户自己控制 —— 提供设置项,可开可关 |
| ② | 默认开启 | 设置项缺省值 = 开(⛔ 不是"缺省关闭、用户主动开") |
| ③ | 提示用户 | 开启时必须向用户提示(说清"会怎样、怎么关"),⛔ 不是静默生效 |
| ④ | 来源范围 = 乙 | 47 / 106 云安全组新增的 UDP 入站,仅放行对端节点 IP(⛔ 不是 0.0.0.0/0) |
⇒ 三条产品口径(序㊴ 自决,⚠️ 可推翻):
- 设置项落在两处:ⓐ 节点加入时(
overlay-node-join.cjs)默认启用并打印提示;ⓑ 设置/管理面可查看与切换(默认true,带提示文案)。 - 关闭后行为 = 全部走中继(= 既有路径,⛔ 不是功能降级、⛔ 不影响 P1 已交付的"一键加入 + 分组准入")。
- 提示必须"可行动":说清①开了之后谁可能直连到这台机器 ②怎么关 ③关掉不影响什么。⛔ 禁止只写"已启用直连"。
⛔ 本单不做(防范围蔓延):虚拟网卡 / TUN-TAP(参数表 §8-① 已收窄为"不做")|mDNS / 广播发现|房间层|打洞之外的第二套传输协议。
§2 只读前置(开工前必读,⛔ 别重做调研)
| # | 事实(已取证) | 证据位置 |
|---|---|---|
| 1 | 🔴 src/ 零 UDP / 穿透代码 —— 直连要从零建 |
grep -rniE "candidate|punch|directPath|natType" src/net/relay/*.ts src/net/relay/content/*.ts ⇒ 仅命中 directory.ts 的中继候选(⛔ 不是节点候选地址) |
| 2 | ✅ 打洞可行性已实测(n=3 对、2/2 可打洞) | scripts/overlay-holepunch.cjs(序⑥ S4 · ⛔ 一次性脚本、不进产品路径);参数表 §3.2 HOLE_PUNCH_RATE_LOCAL |
| 3 | 🔴 47 的云安全组拦 UDP 入站 | 序⑥ S4:UDP 观察器 0.0.0.0:21100/21101 开放 ≈ 75 s、零收包(连同机发出的都收不到);⛔ 不是 nft(input policy = accept) |
| 4 | peer 档"有账本、传输走 wss" | src/net/relay/content/peer.ts 头注原话:「不做传输(取块走 source.ts 注入的 fetcher,底层复用既有 wss 通道)」 |
| 5 | relay 拨号白名单 = Map<network, Set<hostId>>;DIAL 必须同网 + 在白名单(结构性隔离) |
src/net/relay/server.ts:184 / :201 / :643 |
| 6 | 443 兜底 ≠ 打洞(它是 DNS 层"钉 IP + 保持 SNI") | src/net/relay/addr-override.ts 头注 |
| 7 | 不引入新依赖:client.ts 用 Node 22 内建全局 WebSocket |
addr-override.ts 头注原话:「本项目不引入 undici / ws」⇒ 打洞同理,只用内建 dgram |
| 8 | 引导目录端点 = src/web/routes/overlay.ts(83 行,只读无鉴权,P0-2) |
⚠️ 它与 §3 提到的"管理面 API"不是同一个东西(后者 = src/web/routes/overlay-nodes.ts,未建) |
§3 范围与在册文件集
新增(预期 · ⛔ 越界即停)
| 文件 | 职责 |
|---|---|
src/net/relay/direct/candidate.ts |
候选地址的收发与校验(走既有 relay wss 通道,⛔ 零新口) |
src/net/relay/direct/punch.ts |
UDP 打洞探测(Node 内建 dgram),失败判死 |
src/net/relay/direct/index.ts |
开关 + 默认值 + 提示文案 + 降级编排 |
scripts/overlay-direct-probe.cjs |
观测面(只读探针;判据进 OBS-26 起) |
test/overlay-direct.test.mjs |
单测(⚠️ 刻意不进 npm test —— 那是硬编码文件列表,加进去会改测试总数) |
修改(预期)
| 文件 | 改什么 | ⛔ 边界 |
|---|---|---|
src/net/relay/content/source.ts / runtime.ts |
peer fetcher 接上直连通道(优先直连、回落 wss) | 🔴 接线后必须复算块 id |
src/net/relay/join.ts |
加入时默认启用 + 提示 | — |
scripts/overlay-node-join.cjs |
同上(提示文案) | — |
参数表_覆盖网络_20260917.md |
新键 + OBS-26+ 行 |
值格只加不改 |
⛔ 明确不改:src/net/relay/server.ts 的 DIAL / 白名单语义(只读取证)|官方 dsh 主程序与缓存(R2)|nft / nginx|bwrap 参数(47 = bubblewrap 0.4.0)。
§4 方案(技术路线)
4.1 候选地址交换 —— 复用既有通道,⛔ 零新增暴露面
- 在既有 relay wss 会话上新增一种消息(暂名
DIRECT_CANDIDATE),载荷 ={hostId, network, addrs[]}。 - 🔴 准入判定必须复用既有白名单(
server.ts的Map<network, Set<hostId>>)—— ⛔ 不许在直连模块里另写一份("同一事实两处写"是本线老病根)。 - 跨网 / 不在白名单 ⇒ 显式拒绝 + 计数(⛔ 不许静默返空 —— 本线反复踩的假绿就是这一类)。
- ⛔ 候选只含地址与端口,⛔ 不含任何密钥 / 身份材料。
4.2 打洞探测 —— 照抄成熟做法的最小集(⛔ 不发明)
- 用 Node 内建
dgram,本机绑一个 UDP 口(端口从既有参数表区间取,⛔ 不写魔数)。 - 双方同时向对方候选地址发包(hole punching 的标准做法);任一方在
PUNCH_DEADLINE_MS内收到对方的包 ⇒ 该方向成立。 - 双向都成立才判直连可用;否则判死。
- 判死后:立即降级中继,且该候选进冷却(复用本线既有冷却纪律,⛔ 不自造一套)。
4.3 开关与默认值(用户口径 ①②③ 的落点)
- 配置项(暂名
DSHS_OVERLAY_DIRECT)缺省 = 开。 - 关闭 ⇒ 零 UDP socket(可断言:
ss -lunp无新增),且不发DIRECT_CANDIDATE。 - 提示:ⓐ
join时 stdout 打印;ⓑ 设置/管理面显示。
4.4 P2P 取回接线(S6)
source.ts的 fetcher:优先直连通道取块,失败回落 wss。- 🔴 接线后必须复算块 id(否则命中本线老病根"假绿")。
4.5 乙方案的落地形态(⚠️ 如实写的技术现实)
- 乙 = 云安全组只放行对端节点 IP。⚠️ 源 IP 的现实:家宽 / 移动节点的公网出口 IP 会变 ⇒ 规则会失效、且云安全组规则数有上限。
- ⇒ 序㊴ 自决(可推翻):乙-1 起步 —— 只对有固定公网 IP 的节点(云节点 / 专线)开入站;家宽节点本期走中继(⛔ 不影响可用性,只影响"它能不能被直连")。
- 乙-2(半自动:平台算清单 + 人工 / 脚本写控制台)与 乙-3(云 API 自动化,🔴 需云 AK/SK)登记为回头条件(§9),⛔ 本单不请求凭据。
§5 判据(先红后绿;⛔ 不许事后补写)
| # | 判据 | 形状 | 先红腿(对旧产物) |
|---|---|---|---|
| D1 | 候选交换只走同网 + 白名单 | 跨网 / 白名单外 ⇒ 显式拒绝 + 计数(⛔ 非静默返空) | 旧产物无该消息类型 ⇒ 入口即缺 ⇒ 红 |
| D2 | 开关生效 | direct = false ⇒ 零 UDP socket(ss -lunp 无新增)+ 不发候选 |
旧产物无开关 ⇒ 红 |
| D3 | 默认值 = 开(用户口径 ②) | 新节点加入后配置读回 = true |
旧产物无该键 ⇒ 红 |
| D4 | 提示存在(用户口径 ③) | join stdout 匹配提示文案(含"怎么关") |
旧产物无提示 ⇒ 红 |
| D5 | 打洞成功路径 | 同网 + 双向白名单 ⇒ 直连建立(判别器 punchOk > 0) |
旧产物无实现 ⇒ 红 |
| D6 | 打洞失败判死 | 对端不响应 ⇒ 在 deadline 内降级中继+不重试风暴 | 旧产物无实现 ⇒ 红 |
| D7 | 🔴 块 id 复算 | peer 接线后块 id 与接线前逐字一致 | ⛔ 漏了这条 ⇒ 判据不算通过(会把"零回源"做成假绿) |
| D8 | 乙方案生效 | 云安全组 UDP 段 source ≠ 0.0.0.0/0;且仅列出的对端 IP 可打洞成功 |
收口前态 = 无 UDP 入站 ⇒ 红 |
零回归三件套(每期收口必跑):npm.cmd test(Node 22,基线 201/200/0/1)|node scripts/overlay-failover-drill.cjs --scene all --table(基线 12P/0S/0F)|node scripts/overlay-probe.cjs --table(基线 25P/0S/0F)。⚠️ npm.cmd test 没有 --table 参数;探针不在 npm test 覆盖内。
§6 步骤(每步自带验收 + 回滚)
S5 · 直连候选交换 + 打洞探测
direct/candidate.ts(复用白名单判定)⇒ 验收 = D1 先红后绿。direct/punch.ts(dgram,判死 + 冷却)⇒ 验收 = D5 / D6。direct/index.ts(开关 + 默认值 + 提示)+join.ts/overlay-node-join.cjs接线 ⇒ 验收 = D2 / D3 / D4。scripts/overlay-direct-probe.cjs(只读观测)+ 参数表新键与OBS-26+。- ⚠️ 云安全组改动(乙-1):只在确认对端 IP 清单后开 UDP 小段入站 ⇒ 验收 = D8。
回滚:关 DSHS_OVERLAY_DIRECT ⇒ 秒级回落中继;云安全组规则删除即恢复零 UDP 入站。
S6 · peer 档接线
source.tsfetcher 优先直连、回落 wss ⇒ 验收 = D7(块 id 复算,⛔ 漏了不算过)。- 回归"零回源 / 共享命中"既有读数(对照序㉔ / 序㉛ 基线)。
S7 · 规模回归
候选数、直连成功率、切换耗时、回源字节四项复测 ⇒ 与既有基线(参数表 §3.2 / §7)逐项对照。
§7 权限影响评估(R5)—— ✅ 用户已拍板(2026-09-18 12:22)
| # | 项 | 结论 |
|---|---|---|
| 1 | 扩了什么 | 47 / 106 云安全组新增 UDP 小段入站,source 仅对端节点 IP(乙,⛔ 非 0.0.0.0/0);节点侧新增 UDP socket(用户态,无内核驱动) |
| 2 | 谁受影响 | 仅同网 + 双向在白名单的节点之间;⛔ 不对公网任意源开放、⛔ 不跨网、⛔ 不影响租户实例既有隔离面 |
| 3 | 不扩大也能实现的方案 | 有 —— 不做直连(跨机流量继续走中继);P1 已交付的"一键加入 + 分组准入"仍然完整可用 |
| 4 | 回滚方式 | 关闭开关 ⇒ 秒级回落"全走中继";删云安全组规则 ⇒ 恢复零 UDP 入站 |
| 5 | 用户已知悉 | 2026-09-18 08:2x 选 B 时已写明"需新开直连端口 = 扩大暴露面、命中 R5";12:22 拍板 = 用户可控 + 默认开 + 提示 + 乙 |
🔴 硬约束(⛔ 不许松):⛔ 直连不得让任何节点反向进入别人的机器|⛔ 打洞不得绕过既有身份强制与吊销|⛔ 白名单 / 同网判定必须复用既有实现(⛔ 不另写一份)|🔴 密钥本体 ⛔ 不经网络 / 不经 relay。
§8 回报格式(执行棒回填;⛔ 只填空,不改小节点名)
本棒(序㊵ 执行棒 · P2/S5)已回填 · 2026-09-18 12:33–13:2x
8.1 开工门(必填)
- 本单 §8 前前缀校验值:
8925c2460810c6dc5cdbc075b6cdaed2(现算sed '/^## §8 回报格式/,$d' <本单> | md5sum,与 §11.2 逐字一致 ✅;口径复核 = 本文件CR=0 / LF=239= 纯 LF) - 取得全局执行锁的会话名与时间:
序㊵ 执行棒(P2·S5)/ 2026-09-18 12:33(bash dsh-server-docs/scripts/handoff-guard.sh --claim-exec;收口时--release-exec释锁)
8.2 逐判据回报(D1–D8 各一条,含原始读数)
D1 · 候选交换「只走同网 + 白名单」
- 🔴 先红(对旧产物):
node -e "require('/opt/dshs/lib/net/relay/direct/index.js')"⇒Error: Cannot find module '/opt/dshs/lib/net/relay/direct/index.js';direct 目录: ❌ 不存在(旧产物无该入口 ⇒ 入口即缺) - 🟢 后绿:
PASS OBS-27 接受 3(阈值 ≥ 2) ✅|具名拒绝 9 条(阈值 ≥ 3) ✅|静默拒绝=0 ✅|按网分桶(同名跨网互不可见)=true ✅|准入实现=network.ts#isAllowedDialer - 机读原文字段:
candidate.accepted=3/rejected=9(每条都带具名reason:cross-network/not-self-candidate/not-a-dialer/self-not-a-dialer/bad-shape×2 /secret-field/bad-address×2 /too-many-addrs/stale)/silentRejections=0/bucketsPerNetwork=true/matcher='network.ts#isAllowedDialer' - 🔴 "不另写一份"的机器守卫:
test/overlay-direct.test.mjs#T4读src/net/relay/server.ts源码原文核对两处闸门(注册闸门const wantDialer = this.dialers.get(network)?.has(hostId) === true/ DIAL 闸门if (this.dialers.get(session.network)?.has(session.hostId) !== true)),并断言 direct 模块内不出现dialers.get(与.has(hostId)⇒ 本棒本地node --test test/overlay-direct.test.mjs= 10 tests / 10 pass / 0 fail
D2 · 开关生效(direct=false ⇒ 零 UDP socket + 不发候选)
- 🔴 先红:旧产物无该入口(同上);且 47 上
DSHS_OVERLAY_DIRECT在/etc/dshs.env+/etc/dshs-worker.env+/etc/systemd/system/**的命中数 = 0("无处可关" = 红) - 🟢 后绿(三层证据并列,缺一层都可能是假绿):
① 实现层(47 真机读数,同一份代码):
onCase { raw:'true', enabled:true, candidatesEmitted:1, udpSocketsOpened:1 }⇄offCase { raw:'false', enabled:false, candidateReason:'disabled', candidatesEmitted:0, udpSocketsOpened:0 }⇒ 开 ⇒ 1/1,关 ⇒ 双零 ② 🔴 内核可见层(47 真机ss -lunp,端口区间 21100–21115):udp = { ssBefore:0, ssDuringOnCase:1, ssDuringOffCase:0, ssAvailable:true, directRange:[21100,21115] }—— ⚠️ssDuringOnCase是正对照(真绑一个直连口后ss必须看得见),原文UNCONN 0 0 0.0.0.0:21100 0.0.0.0:* users:(("node",…))⇒ "关闭态 0 行"因此是可信的 0,⛔ 不是"测量坏了也是 0" ③ 真机打洞腿:overlay-direct-probe.cjs punch --peer 106.54.21.172:21100 --deadline 3000⇒reason=deadline 收包=0 发出=21 耗时=3000ms(⛔ 被云安全组挡 ⇒ 见 D8) - ⚠️ 口径(⛔ 不许读成"生产流量已变"):S5 未做 S6 接线 ⇒ 直连尚未进入数据面;D2 量的是实现层 + 内核可见层,⛔ 不是端到端流量。
D3 · 默认值 = 开(用户口径 ②)
- 🔴 先红:旧产物无该键(入口缺失);47 上
/etc/dshs/overlay-node.json不存在(node_config_exists=no) - 🟢 后绿:
switch.defaultCase = { enabled:true, source:'default' }(⛔ 不是"缺省关")+joinConf = { direct:true, readback:true, respectsArg:true, stepDetail:'已落地 /tmp/dshs-direct-selfcheck-9w6NY0/node-a.json(0600)|直连(打洞)=开(缺省)' }⇒ 新节点加入后配置读回 =true - 🟢 P1 出口项(管理面 API)先红后绿:
/api/admin/overlay-nodes404 → 401、/api/admin/overlay-nodes/direct404 → 401(对照/dshs-overlay/bootstrap= 200、门户 = 200) - 🔴 诚实登记:真机先红腿不可取 —— 旧产物根本没有"本机配置带回
direct"这一行为(47 上无overlay-node.json⇒ 无从"读到旧键")⇒ D3 主证据落在本棒自己的joinConf读数(走部署后真 lib + 系统临时目录,⛔ 零生产副作用)
D4 · 提示存在(用户口径 ③,须含"怎么关")
- 🔴 先红:旧产物无提示(入口缺失)
- 🟢 后绿:
hint = { parts:3, mentionsOff:true, mentionsScope:true, mentionsImpact:true }(阈值DIRECT_HINT_MIN_PARTS = 3);三段原文要点 = ① 谁会连进来(只有同网 + 双向在白名单里的节点;⛔ 公网任意源、⛔ 跨网连不进来)② 怎么关(DSHS_OVERLAY_DIRECT=0/false/off/no,或管理面把本机配置direct改false;关闭后不再绑任何 UDP 端口、也不再发候选)③ 关掉影响什么(⛔ 不影响接入与准入,跨机流量改走中继,代价只是"多一跳")
D5 · 打洞成功路径
- 🔴 先红:旧产物无实现(入口缺失)
- 🟢 后绿:
punch.success.bidirectional = true、attemptsOk = 2(阈值DIRECT_PUNCH_MIN_OK = 1);单向负腿oneWay = { bidirectional:false, a:{reason:'one-way',recvLocal:0,peerSeen:true}, b:{reason:'one-way',recvLocal:8,peerSeen:false} }⇒ "有收包"一律不算成功 - ⚠️ 诚实边界(⛔ 不许夸大):云上真机双向不可达(云安全组拦 UDP 入站;实测双向
0/20、0/20)⇒ 成功路径由离线 NAT 模拟器(SimulatedNat+runPunchPair,用真dgramsocket 跑同一份打洞代码)承载,机器可断言。⛔ 这不是"公网一定能打洞"的证明。
D6 · 打洞失败判死(+ 不重试风暴)
- 🟢 后绿:
dead[0] = { reason:'deadline', elapsedMs:600, deadlineMs:600, bounded:true, sent:5, recvLocal:0 }⇒ 判死有界;冷却cooldown = { ms:300000, blocked:1, secondAttemptReason:'cooldown', secondAttemptBlocked:true, zeroRejected:'throws' }(⚠️DirectCooldown收ms <= 0⇒ 构造期直接抛,原文直连冷却时长必须 > 0,收到 0(🔴 0 ⇒ 判死退化成重试风暴……)) - 🟢 真机腿:同 D2-③ ——
deadline / 收包 0 / 发出 21 / 耗时 3000ms(有界)⇒ 判死 + 进冷却 300000 ms - 🔴
RELAY_FAILOVER_COOLDOWN_MS=0命中数 = 0(⛔ 全仓 + 两机 env + drop-in 全扫)
D7 · 块 id 复算 —— 🔴 不在本棒范围(属 S6)
- ⛔ 本棒(S5)未做
peer接线 ⇒ 无块 id 变化可复算。如实登记 = 🔴 未验证(⛔ 不编数) - 变更面自证:本棒未改
src/net/relay/content/**任何一个字节(块 id 计算路径零改动) - 🔴 提醒下一棒(S6):漏了这条 ⇒ 判据不算通过(§9-5)
D8 · 乙方案生效 —— 🔴 未实施(红)
- 🔴 先红腿(收口前态)成立 · 双向实测:
106 → 47.77.182.89:21100发 20 收 0(RECV_COUNT=0 / RECV_SOURCES=(none))|47 → 106.54.21.172:21100发 20 收 0(同) - 现状:未开任何 UDP 入站规则;宿主机侧读不到云安全组规则本体(
nft input policy = accept、nft list ruleset= 72 行与收口态逐字一致 ⇒ 挡在云侧、⛔ 非 nft) - 🔴 未实施的理由(属 §1 边界外第 ④ 类「需用户提供的账号凭据或审批」):改云安全组需云控制台访问方式 / 审批,而本棒硬门明令不许索取或硬编码任何云 AK/SK(乙-3 = 回头条件)⇒ 上抛,⛔ 未绕过、⛔ 未试探性开洞
- 🔴 拟实施的 乙-1 规则(逐条,用户开控制台后按此录;⛔ 本棒未录):
| # | 目标实例(公网 IP) | 方向 | 协议 | 端口范围 | 源 source | 动作 |
|---|---|---|---|---|---|---|
| 1 | 47 · 47.77.182.89 |
入站 | UDP | 21100/21115 |
106.54.21.172/32 |
允许 |
| 2 | 106 · 106.54.21.172 |
入站 | UDP | 21100/21115 |
47.77.182.89/32 |
允许 |
- ⛔ 零个
0.0.0.0/0条目;⛔ 拆分等价写法一并禁止(0.0.0.0/1 + 128.0.0.0/1、::/0、0-255.0-255.0-255.0-255等任何"对公网任意源开放"的形态 ⇒ 命中 §9-3 = 立刻停手回滚) - ⛔ 不开 TCP 段|⛔ 不开出站变更|⛔ 不动既有任何规则
- ⚠️ 端口区间来自参数表
PUNCH_PORT_BASE = 21100/PUNCH_PORT_SPAN = 16⇒[21100, 21115](⚠️ 值随参数表变,⛔ 不写死在别处) - ⚠️ 回头条件:§9-6(家宽 / 移动节点公网出口 IP 会变 ⇒ 乙-1 覆盖不到 ⇒ 乙-2 / 乙-3,后者需用户提供云 AK/SK)
8.3 零回归三件套(含各项原始输出尾部)
| # | 命令 | 本次读数 | 基线 | 判定 |
|---|---|---|---|---|
| 1 | npm.cmd test(Node 22) |
201 tests / 200 pass / 0 fail / 1 skipped(rc=0) | 201/200/0/1 | ✅ 逐字一致 |
| 2 | overlay-failover-drill.cjs --scene all --table |
12 PASS / 0 SKIP / 0 FAIL(rc=0;幕4-C 23422 ms / deadline 30000) | 12P/0S/0F | ✅ |
| 3 | overlay-probe.cjs --table(真机) |
28 PASS / 0 SKIP / 0 FAIL(rc=0) | 25P/0S/0F | ✅ +3 = 新增 OBS-26/27/28 |
⚠️ 两条口径沿用序㊲:❶ npm.cmd test 没有 --table;❷ 探针不在 npm test 覆盖内。
⚠️ 一次跑偏 + 已修复(如实登记):第一次 --scene all 被本棒追加的 timeout 300 截断 ⇒ 只出 8 行、且幕4 之后的 restore() 未执行 ⇒ 106 dshs-relay 被留在停机态(13:05:58 停、未启)。发现后立即 systemctl start 复原(active / /status=200),随后不加外层时限重跑 ⇒ 12P/0S/0F 且末尾 restore() 正常执行(两台均 active)。教训:--scene all 内含 120 s + 90 s 两段观察窗口 ⇒ ⛔ 不许给它套小于 ~8 min 的外层超时。
8.4 恢复形态核实(49 / 106 两端单元与服务状态 + 门户码)
- 47:
dshs=active/dshs-worker=active/dshs-relay=active;relay/status= 200;门户(Host: alotbuy.com)= 200;/dshs-overlay/bootstrap= 200 - 106:
dshs-worker=active/dshs-relay=active;relay/status= 200 - 47 视角中继读数:
used=2 / max=7515 / endpoints=[["w-106",19000],["w-106",21001]] - 探针四条口径行:
PASS OBS-01 在册节点 used=2 (阈值 ≥ 2)|PASS OBS-04 identityOk=2 (阈值 ≥ 2)|PASS OBS-08 端点表 2 条 / 离线 0 条|PASS OBS-09 在册实例面(派生 1 条):w-106:40679=401 - ⚠️ 演练态曾出现 24P / 2F / 2S(2F =
OBS-01 used=1、OBS-04 identityOk=1—— 计数判据;2S =OBS-08/OBS-09走"对端中继声明了端口 ⇒ 客户端挂在另一台中继上"腿)⇒ 按既有程序停 / 启 106dshs-relay(~24 s)⇒ 47 视角回落used=2⇒ 探针回 28P/0S/0F(⛔ 未重启任何 worker、⛔ 未换实例) - ⚠️
OBS-09派生端点端口由 42783 → 40679 再漂一次(设计内:判据跟着事实走、⛔ 未改表值 —— 这正是序㊲ 要拿到的收益)
8.5 边界自证(逐条打勾)
- ⛔ 未 commit / 未 push:HEAD 仍
09ce76f;git status --short= 17 处(与开工逐数相同 ⇒ 零新增条目;全部为 P1/P2 在册工作树文件) - 🔴 云安全组 UDP 段的 source 逐条列出:当前无任何 UDP 入站规则(本棒未实施);拟录清单见 §8.2-D8 两条 —— 源分别为
106.54.21.172/32与47.77.182.89/32,⛔ 零个0.0.0.0/0、⛔ 零个拆分等价写法 - ⛔ 未改
server.ts的 DIAL / 白名单语义:src/net/relay/server.ts本棒零字节改动;src/net/relay/network.ts只加isAllowedDialer(纯新增,⛔ 无调用点替换、⛔ 未动既有函数)—— 由T4读源码的机器守卫固化 - ⛔ 未改 nft / nginx:
nft list ruleset= 72 行(与收口态逐字一致);nginx 配置合计 md5 =ea0cfc17c60872eecd626b840370793(未动) - ⛔ 未改 bwrap 参数(
--perms等 0.5+ 专属选项一律未出现;47 = bubblewrap 0.4.0 未被触碰) - 🔴
RELAY_FAILOVER_COOLDOWN_MS=0命中数 = 0(⛔ 未写进任何回滚 / 演练 / 夹具路径;本棒新建的冷却构造期断言进一步把0变成"抛异常") - 🔴 密钥本体不经网络 / relay:本棒不下发、不传输、不打印任何密钥;
/etc/dshs/overlay-dir-key.pub(公钥)为既有文件、本棒未读其内容 - 块 id 复算证据(D7):🔴 不适用(D7 属 S6)⇒ 本棒 未验证;变更面自证 =
src/net/relay/content/**零改动 - 本棒对生产的动作清单(均 R8,动手前已一句话说明,⛔ 无不可逆项):① 铺产物 4 处(47
/opt/dshs/lib+/opt/dsh-relay/lib;106/opt/dshs-cluster/lib+/opt/dsh-relay/lib,32 个 md5 全同、不一致条数 0)② 铺 3 个脚本到 47/opt/dshs/scripts/③ 重启 47dshs+dshs-worker④ 跑--scene all(自停 / 自启 relay)⑤ 停 / 启 106dshs-relay一次(复原 47 视角)⑥ 🔴 106dshs-relay一次意外停机(超时截断所致,已立即复原 —— 见 §8.3 末)⇒ 全程 ⛔ 未删任何数据 / 配置、⛔ 未改任何env值(DSHS_OVERLAY_DIRECT两机命中 0 ⇒ 走缺省即开)
8.6 如实留档(必填,哪怕"没有")
- 🔴 D8 未实施(红) —— 见 §8.2-D8:需用户提供云控制台访问方式 / 审批(§1 边界外第 ④ 类)。⛔ 未索取、未硬编码任何云 AK/SK;⛔ 未做任何"自动化开洞"。
- 🔴 一次自造的生产影响(已修复):
--scene all被timeout 300截断 ⇒restore()未执行 ⇒ 106dshs-relay停机约 4 分钟(13:05:58 → 13:10:0x 发现后重启)。影响面 = 该窗口内 106 worker 的跨机流量走候选链(自愈),⚠️ 无业务中断证据(该窗口内门户 / 实例面全程 200)。⛔ 已把"不许给它套小超时"写进 §8.3。 - 🔴 本棒修掉的两条自造缺陷(均在本棒新建的观测脚本内,⛔ 不涉任何既有生产代码):
--ss是布尔开关却被按"带值选项"解析(opt()只认字符串)⇒ 该腿恒为null(死代码);且正对照腿硬编码null⇒ "关闭态 0 行"在测量本身坏了时也会是 0 = 潜在假绿。修 = ① 按"出现过"判定 ② 新增正对照(真绑一个直连口,要求ss必须看得见)。punch子命令从direct/index.js(选择性桶导出 —— 它 import 了那几个常量供内部用、不向外转发)取DEFAULT_PUNCH_DEADLINE_MS/DEFAULT_DIRECT_COOLDOWN_MS⇒undefined⇒Number(undefined)=NaN⇒ 冷却构造期断言直接抛(原文直连冷却时长必须 > 0,收到 null;⚠️null是假象 ——JSON.stringify(NaN) === 'null')。修 = 改从punch.js(常量真身)取 + 新增启动期硬断言(缺任一常量 ⇒ 具名exit 2,⛔ 不再静默取缺省)。
- ⚠️ 参数表口径与实现的一处微差(只登记,⛔ 未改判据):
OBS-26的参数表附注写"真机的ss -lunp增量腿由selfcheck --ss另取" ⇒ 该腿产出证据、但未参与判据(判据仍是offCase.udpSocketsOpened === 0)。本棒未改已发布的阈值口径 ⇒ 如实登记为证据项而非判据项。 - ⚠️ 夹具"先红后绿"的 diff 是 4 行不是 3 行(原文级):第 34–36 行 =
OBS-26/27/28由PASS → FAIL,第 37 行 = 汇总行❌ 8 项红:…→❌ 11 项红:…(由前三行派生)。序㊵ 之前"只差三行"的口径指的是判据行。 - ⚠️ 夹具模式下有 8 条既有红项(
OBS-08/11/13/14/16/17/19/20)—— 夹具未给它们的输入、而那些判据judged=true仍按真值判 ⇒ ⛔ 非本棒引入;判定本棒判据的唯一正确口径 = 绿 / 红两腿的逐行 diff(⛔ 不许拿"夹具全绿"当验收)。 - ⚠️
overlay-direct-probe.cjs未被npm test覆盖(与test/overlay-direct.test.mjs同类:npm test是硬编码文件列表)⇒ 该脚本的回归靠本棒手工三腿(selfcheck/punch/ 夹具对照)。⛔ 未改package.json。 - ⚠️ 本棒只做 S5:⛔ 未做 S6(
peer接线 +D7块 id 复算)、⛔ 未做 S7(规模回归)—— 各是独立后续棒。 - ⚠️ 模板笔误(只留档,⛔ 未改小节点名):§8.4 小节点名把 47 写成「49」("49 / 106 两端…")。本棒按 47 / 106 如实回报,⛔ 未改小节点名(保 §8 结构不动)。
- ⚠️ 本棒两处"未验证"如实登记(⛔ 不编数):D7 块 id 复算(属 S6)|真机双向打洞成功(云安全组未开 ⇒ 只测到
deadline / 0 收包;成功路径由离线 NAT 模拟器承载,见 D5)。
8.7 指纹
- 本单 §8 前前缀(口径:
sed '/^## §8 回报格式/,$d' <本单> | md5sum):8925c2460810c6dc5cdbc075b6cdaed2(回填 §8 全节后现算,与 §11.2 逐字一致 ✅ —— §8 是截断点,故回填不改变它;= "§8 是截断点"这条口径的又一次自证) - 参数表 §10 指纹:
d408d640246a980f702fe7b0a2895219→6b37bfd506758d882d9f803678f85d23(本棒改动后现算并同步;口径 =sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum)
8.8 序㊶ 执行棒回报(P2 · S6:peer 档接线 + D7 块 id 复算;2026-09-18 13:35–14:0x)
⛔ 本小节只新增:未改 §1–§7 正文、未改 §8.1–§8.7 小节点名。§8 前前缀
8925c2460810c6dc5cdbc075b6cdaed2回填本小节后现算仍逐字不变 ✅(见 §8.8-⑧)。
① 开工门:state.py 已读;入口 接续入口_覆盖网络线_20260916.md §0 最新两行 + §2 首行已读(= 唯一执行依据);全局执行锁 --claim-exec "序㊶ 执行棒(P2·S6)" 抢到(09-18 13:35)。§8 前前缀开工前现算 = 8925c2460810c6dc5cdbc075b6cdaed2 ✅ 逐字一致(与 §11.2 / §11.5 记的值相同)。范围 = 只做 §6 的 S6(⛔ 未做 S7)。
② D7 逐条回报(先红后绿;每一项都是原文读数,⛔ 无编数)
- D7 主判据 · 块 id 不变性(接线前后逐字一致) —— 取数脚本 = 同一份
_tmp_seq41/ids.mjs(两版本只用共同构造参数,⛔ 避免"接线前"侧报错 = 假红):idsDigest=ec70e7166ae24b1367c7c251ead8ad2979f66463ae3e6380eeda84fabb985c3a—— 四处读数逐字相同: ① 本机_tmp_seq41/lib_before(接线前本机产物)② 本机lib(接线后)③ 47/opt/dshs/lib(生产接线前) ④ 47/opt/dshs/lib(生产接线后)。 明细:plain putPlan[0] = 3374319c90813e8f7911654132f34a0c/cipher putPlan[0] = 8dd3ea58093df7130ba07722cd28bf81(夹具 = 5,255,225 B 确定性字节;两臂plain/cipher,密文臂 =ContentCipher({ groupKey: 'ops|s6', epoch: 1, key: makeKey(7) }))。 - D7 先红腿 ① · 扰动块 id:临时把
blockIdOf改成sha256(bytes ‖ ff)⇒idsDigest→fbb80585c86a4c492f6d71a7af9212121f35c2c43f2cc7018d08149e2cff5ac3、六个 plain id 全变(3374319c…→7fdda0ab1af32e8f531528518d7baa49);还原后chunker.tsmd5 逐字回到a69b3379c719562c46d3e63832bdf25d、diff= 空 ✅(⇒ 判据能真红、还原可逐字复现)。 - D7 先红腿 ② · 拆掉复算闸门(把
const actual = blockIdOf(gotBytes)改成const actual = id):peer-wire判据 ⑦ 由 PASS 转 FAIL,原文❌ ⑦ 🔴 D7 篡改块被丢弃(不算命中)|{"tier":"peer","bytesMatch":false,"hits":{"direct":0,"wss":1},"errors":{"direct":0,"wss":0},"idChecks":1,"idMismatches":0,"peerHitsTotal":1}⇒ 篡改块被记成"peer 命中成功"、而字节与块 id 都不符 = 假绿本形(这正是 D7 要拦的东西);还原后 ✅ 转绿、idMismatches=1、tier=null(= 丢弃且不算命中)。 - D7 观测面 · 新增子命令
peer-wire(scripts/overlay-direct-probe.cjs,13 条具名判据):本机 + 47 侧各 13/13 全绿;块 id 不变性读数idsDigest = 79c77473fd4f4de815d529e04055091e0403fb4c1321a0dc768283b5d9955952(两机逐字相同 = D7 的机器侧旁证)。判据覆盖:direct 优先且不碰 wss /no-address⇒ 具名回落 wss / 开关关 ⇒disabled/ 开关非法 ⇒invalid-switch/ 冷却 ⇒ 具名降级 / 未装通道 ⇒not-wired×2 且诚实回"没有" / 篡改块被丢弃 / 好块被接受 / 通道说没有 ⇒miss/ 通道抛错 ⇒errors/ 只收 S5 准入ok:true的候选 ⇒data-plane-pending/ 被拒 verdict 不进候选 / 不变性读数产出。 - D7 真机形态 ·
content.peerWire新键在线(两机 relay/status;纯新增键、⛔ 未动任何既有键):{"order":["direct","wss"],"wired":{"direct":true,"wss":false},"attempts":{"direct":0,"wss":0},"misses":{"direct":0,"wss":0},"errors":{"direct":0,"wss":0},"unavailable":{"direct":0,"wss":0},"hits":{"direct":0,"wss":0},"downReasons":{},"idChecks":0,"idMismatches":0,"directCandidates":0,"last":null}⇒ 接线已装、空闲零流量;既有读数source = {"local":2,"peer":0,"edge":0,"region":0,"origin":0}未变;106 侧crypto present: True。 - D7 结论:🟢 通过 ——
peer档接线后块 id 与接线前逐字一致(ec70e716…,四处同值)+ 复算闸门先红后绿可用 + 真机新键在线且零流量。
③ E1 回归("零回源 / 共享命中";对照序㉔ / 序㉛ 基线) —— 同一份 _tmp_seq41/e1.mjs,两臂:
| 臂 | 接线口径 | 对照腿(⛔ 不装通道) | 序㉔/㉛ 基线 | 判定 |
|---|---|---|---|---|
| A · 链级夹具(序㉔ 原口径) | originFetches=6 / originBytes=5,255,225 / amp=1.0000 |
originFetches=24 / originBytes=21,020,900 / amp=4.0000 |
1.00× ⇄ 4.00× | ✅ 逐项对齐 |
B · S6 接线口径(4 个真 ContentRuntime) |
servedFromPeer=18 / amp=1.0000 / 逐节点 {B:peer6, C:peer6, D:peer6} / peerWire.attempts={direct:0,wss:6}、hits=[6,6,6]、idChecks=[6,6,6]、idMismatches=[0,0,0]、downReasons=[{direct:not-wired:6}×3] |
servedFromPeer=0 / amp=4.0000 / downReasons={direct:not-wired:6,wss:not-wired:6} |
4.00× → 1.00× | ✅ |
idGate = {idChecks:18, idMismatches:0} ⇒ 命中链路每一步都复算过块 id(⛔ 不是"先信后验")。node --test test/overlay-content.test.mjs = 44 tests / 44 pass / 0 fail。
④ 零回归三件套
| 项 | 本棒读数 | 基线 | 判定 |
|---|---|---|---|
npm.cmd test |
201 / 200 / 0 / 1(rc=0) | 201/200/0/1 | ✅ 逐字同 |
node scripts/overlay-failover-drill.cjs --scene all --table |
12 PASS / 0 SKIP / 0 FAIL(rc=0;6m27s;幕4-C 18990 ms / 30000;未套外层超时) | 12P/0S/0F | ✅ |
node scripts/overlay-probe.cjs --table |
28 PASS / 0 SKIP / 0 FAIL(rc=0;部署后 + 演练后两次同值) | 28P/0S/0F | ✅(⚠️ 基线已是 28P,⛔ 未拿旧的 25P 当对照) |
🔴 上棒的坑本棒已避开:--scene all 未套任何外层超时(跑满 6m27s)⇒ 末尾 restore() 正常执行 ⇒ 两台 dshs-relay 均 active ✅(⛔ 未出现 106 relay 停机态);演练后 47 视角未出现 endpoints=[] / used=1 ⇒ ⛔ 无需停/启 106 relay。
⑤ 交付与部署(R8:动手前一句话说明;⛔ 无不可逆项)
- 改动面 = 6 个文件(
diff -rq定位):lib/net/relay/content/runtime.{js,d.ts,js.map}+lib/net/relay/content/source.{js,d.ts,js.map}。源侧 =src/net/relay/content/runtime.ts(peer接线 + D7 复算闸门 +peerWireSnapshot()+ 直连候选入册/退册 + 注入式peerChannels)+src/net/relay/content/source.ts(仅注释兑现度说明,零逻辑改动)。 - 部署 4 处(47
/opt/dshs/lib+/opt/dsh-relay/lib;106/opt/dshs-cluster/lib+/opt/dsh-relay/lib):runtime.jsmd5 四处全同 =c9a8b01a78524d31cd77fde32895839a、source.js=43cb1b7ab5387ff9745e8dab7726b68b(接线前runtime.js=7fe548c42b71b9e4785f139561fe8925= 47 生产原值 ✅ 先红腿凭此成立)。脚本侧 =scripts/overlay-direct-probe.cjs铺 47/opt/dshs/scripts/(md58d1ea60a01fc09fb5f56e492afb76fc9)。 - 重启:47
dshs-relay+dshs;106dshs-relay。回滚点 = 两机/opt/dsh/backups/seq41-20260918-135146/���各 4 个文件);48 h 内旧产物另有/opt/dsh/backups/seq40-0918-1255/。
⑥ 边界自证(逐条打勾)
- ⛔ 未 commit / 未 push:HEAD 仍
09ce76f;git status --short= 19 处 = 开工 17 + 本棒新改content/runtime.ts、content/source.ts。 - ⛔ 未改
src/net/relay/server.ts(DIAL / 白名单语义零字节改动;准入判定复用network.ts#isAllowedDialer,⛔ 未另写一份)。 - ⛔ 未改
src/net/relay/content/**的块 id 计算路径(sha256(密文)口径未动;D7 量的是"接线前后一致"—— 扰动实验是临时的、已逐字还原)。 - ⛔ 未改
nft(72 行逐字一致)/nginx/bwrap参数(47 = bubblewrap 0.4.0 未被触碰)。 - 🔴
RELAY_FAILOVER_COOLDOWN_MS=0命中数 = 0(两机 env + systemd + scripts 全扫)。 - 🔴 密钥本体不经网络 / relay:本棒不下发、不传输、不打印任何密钥;
/etc/dshs/content-group-key.json两机仍600。 - ⛔ 零个新增 / 改动生产 env(
DSHS_OVERLAY_DIRECT两机命中 0 ⇒ 走缺省即开);⛔ 未新增任何监听口。 - 收口形态:47
dshs/dshs-relay/dshs-worker/dshs-pg全active;relay/status=200、门户=200、/dshs-overlay/bootstrap=200;106dshs-worker/dshs-relay全active、relay/status=200。端点表 2 条全online: True(w-106:19000localPort 40337 /w-106:21001localPort 42099);OBS-09派生w-106:42099=401。
⑦ 如实留档(必填)
- 🔴 D8 仍未放行(红 · 只读核对,⛔ 未实施):双向并发 UDP 实测 —— 47 侧
reason=deadline recvLocal=0 sent=29 elapsed=4001ms sources=[];106 侧{"side":"106","target":"47.77.182.89:21100","reason":"deadline","recvLocal":0,"sent":28,"elapsedMs":4000,"sources":[]}(发 28/29、收 0)。承载线未动:两机nft72 行逐字一致 ⇒ 挡在云侧。⛔ 未改任何云侧规则、⛔ 无任何0.0.0.0/0形态(未新增任何规则)、⛔ 未索取、未硬编码任何云 AK/SK(乙-3 = 回头条件,不在本棒范围)。拟录 乙-1 两条(源为对端/32)原样维持,待你拍板。 - ⚠️
peer档在 SG 未开时的正确形态 = 回落 wss 且块 id 不变 —— 本棒真机即此形态(wired={direct:true, wss:false}、attempts双零 = 空闲;一旦有 peer 取块则 direct 侧以data-plane-pending具名降级、不静默回"没有")。⛔ 未把"直连建立了"写成成功(真机双向直连不可达;D5 的成功路径仍由离线 NAT 模拟器承载)。 - ⚠️ S6 的"数据面"(真实直连取块)本棒刻意未建成:
createDirectBlockChannel#available()在数据面建成前具名返回data-plane-pending、fetch()抛错(⛔ 不许静默回undefined)—— 刻意保留的具名降级,⛔ 不是缺陷;真机直连取块须待 D8 放行。 - ⚠️ 106 侧观测面缺口(只读核对的绕行):106
/opt/dshs-cluster/lib无registry.js⇒overlay-direct-probe.cjs在 106 上跑不起来(原文Error: Cannot find module '../lib/net/relay/registry.js')⇒ D8 的 106 侧核对改用独立最小脚本d8_punch_106.cjs(只requirepunch.js)。本棒 ⛔ 未改探针逻辑。⚠️ 残留:探针文件已被 scp 到 106/opt/dshs-cluster/scripts/,在该位置不可跑(修它 = 让探针不依赖registry.js,属独立后续项)。 - ⚠️ 106
bwrap= 0.11.0(47 = 0.4.0,与红线口径一致 ✅)—— 既存环境差异,本棒未触碰 bwrap。 - ⚠️ 本棒三处"过程测错并当场修正"(⛔ 未编数):①
e1.mjs头部注释里的**…**被 Node 当代码解析(rc=1)⇒ 去掉反引号嵌套后跑通;②originBytes被误当"回源次数"(amp=3503483.3333天文数字)且对照腿复用了已填充的 store(= 假对照) ⇒ 拆成originFetches+ 4 台全新冷 store 才成立;③peer-wire判据 ⑪ 首跑失败(direct:no-address≠data-plane-pending)⇒ 根因 = 直连候选入册键用载荷hostId、而peer.ts#candidates()给的是逻辑名(network/hostId)⇒ 键改为`${msg.network}/${msg.hostId}`后 13/13。 - ⚠️
overlay-probe.cjs真机首跑见used=None max=None—— 键名与该探针OBS-01的实际口径不同,⛔ 非本棒引入(探针仍 28P/0S/0F)。 - ⚠️ 本棒只做 S6:⛔ 未做 S7(规模回归:候选数 / 直连成功率 / 切换耗时 / 回源字节四项复测,对照参数表 §3.2 / §7)—— 独立后续棒。
- ⚠️
source.ts的改动是注释、⛔ 不是逻辑(把 §8.13 登记的"peer 档接线时必须复算blockIdOf"缺口兑现点写清:闸门落在装配层runtime.ts#fetchFromPeers,刻意不放进链 —— 放进去会让"五档通用链"认识块格式、属分层退化)⇒ 单测 44/44 佐证零逻辑改动。
⑧ 指纹
- 本单 §8 前前缀(口径:
sed '/^## §8 回报格式/,$d' <本单> | md5sum):8925c2460810c6dc5cdbc075b6cdaed2(写入本小节后现算,与 §11.2 / §11.5 逐字一致 ✅ —— §8 是截断点,故新增 §8.8 不改变它;= 该口径的又一次自证)。 - 参数表 §10 指纹:
6b37bfd506758d882d9f803678f85d23(现算,与 §11.8 记的值一致 —— 本棒新增 §11.9 在 §10 口径之外 ⇒ 指纹不变,未动任何键值)。
8.9 序㊷ 执行棒回报(P2 · S7:规模回归 —— 候选数 / 直连成功率 / 切换耗时 / 回源字节 四项复测;2026-09-18 14:14–14:5x)
⛔ 本小节只新增:未改 §1–§7 正文、未改 §8.1–§8.8 小节点名。§8 前前缀
8925c2460810c6dc5cdbc075b6cdaed2写入本小节后现算仍逐字不变 ✅(见 §8.9-⑦)。
① 开工门:state.py 已读;入口 接续入口_覆盖网络线_20260916.md §0 最新两行 + §2 首行已读(= 唯一执行依据);全局执行锁 --claim-exec "序㊷ 执行棒(P2·S7)" 抢到(09-18 14:14)。§8 前前缀开工前现算 = 8925c2460810c6dc5cdbc075b6cdaed2 ✅ 逐字一致(与 §11.2 / §11.5 / §8.8-⑧ 记的值相同)。范围 = 只做 §6 的 S7(四项复测;⛔ 未重做 S5 / S6、⛔ 未扩到新功能)。
② 四项复测逐条(原始读数 + 与表内基线逐项对照;⛔ 无编数)
D-S7-① 候选数
| 面 | 本次原始读数 | 表内基线 / 阈值 | 判定 |
|---|---|---|---|
dshs@47 候选链 |
count=3 hosts=3 ✓ source=chain → 收口后 source=cache;urls=wss://alotbuy.com/dshs-relay|wss://relay-direct.alotbuy.com/dshs-relay|wss://106.54.21.172/dshs-relay |
CAND_MIN=2(参数表 §6 OBS-21) |
✅ ≥ 2 |
dshs-worker@106 候选链 |
count=3 hosts=3 ✓ source=chain;观测行原文 resolves=744 count=3 hosts=3 source=chain |
同上;序㉙ 基线 count=3 hosts=3 |
✅ 逐字同基线 |
直连候选(DIRECT_CANDIDATE)在册 |
peerWire.directCandidates=0(两机,空闲零流量) |
⛔ 无阈值(空闲态) | ℹ️ 信息项 |
| 直连候选准入(47 真机自检) | accepted=3 / rejected=9(每条具名)/ silentRejections=0 / bucketsPerNetwork=true / matcher=network.ts#isAllowedDialer |
DIRECT_CAND_ACCEPT_MIN=2 / DIRECT_CAND_REJECT_MIN=3 |
✅ |
规模夹具(CandidateLedger#judge,2 张网 × {4,16,64} 节点) |
accepted = 10 / 34 / 130(= 期望值,线性)|rejected = 32 / 128 / 512(8 类原因各 perNet 条,全部具名)|silentRejections = 0|bucketsPerNetwork = true(同名 hostId 跨网互不可见,跨网窥探 ⇒ cross-network) |
不变量守卫 | ✅ 四项判据全绿 |
🔴 口径(⛔ 别读成"冗余够"):hosts=3 不是独立物理路径数 —— 前两条候选 alotbuy.com 与 relay-direct.alotbuy.com 摘名不同、同落 47 ⇒ 机器级独立路径只有 2(47 + 106),与 §6 OBS-21 注记一致。"条数够"✅ / "冗余建成"= 2 台机器,两者分开报。
D-S7-② 直连成功率(🔴 必须区分"本系统的成功率"与"网络能不能打洞")
| 腿 | 本次原始读数 | 基线 / 阈值 | 判定 |
|---|---|---|---|
本系统(离线 NAT 模拟器,同一份 runPunchAttempt) |
正腿 24/24 双向 = 1.0000(reasons={a:ok:24, b:ok:24});p50 建连 155 ms(deadline 1200 ms);负腿 one-way(a)=12 / one-way(b)=12 全 0;负腿 both=12 全 deadline |
DIRECT_PUNCH_MIN_OK=1(OBS-28①) |
✅ 判据成立(1.0000,n=24) |
| 真机(47 → 106.54.21.172:21100) | reason=deadline 收包=0 发出=21 耗时=3000ms |
HOLE_PUNCH_RATE_LOCAL = 分层(⚠️ 那是可行性口径、⛔ 非本系统成功率) |
🔴 不可达(0/1 方向) |
| 真机(106 → 47.77.182.89:21100) | {"side":"106","reason":"deadline","recvLocal":0,"sent":28,"elapsedMs":4001,"sources":[]} |
同上 | 🔴 不可达(0/1 方向) |
| 47 真机自检的打洞段 | success.bidirectional=true(模拟器腿)|oneWay = one-way / one-way|dead[0] = deadline 601ms bounded=true|cooldown=300000 blocked=1 zeroRejected=throws |
OBS-28②③④ |
✅ |
🔴 两条必须同时成立才算"诚实":(甲) 判据成立 = 模拟器 1.0000 + OBS-26/27/28 三行 PASS(机器可断言);(乙) 冗余建成 = 未达成 —— 真机双向直连两个方向都是 recvLocal=0(D8 云安全组未放行,见 §8.9-⑥-1)。⇒ ⛔ 本棒不把"直连建立了"写成成功;真机形态 = 回落 wss 且块 id 不变(D7,§8.8)。
D-S7-③ 切换耗时
| 项 | 本次原始读数 | 阈值 / 基线 | 判定 |
|---|---|---|---|
幕4-C 切换耗时 |
20871 ms / 30000 ms(PASS 幕4-C) |
RELAY_FAILOVER_DEADLINE_MS = 30000(⚠️ 权威落点 = 参数表 §4,不是 §3.2) |
✅ 内侧 |
幕1-A(杀当前入口 ⇒ 切到另一台) |
20605 ms / 30000 ms | 同上 | ✅ |
幕4-A(停 47 ⇒ 切到 106) |
18815 ms / 30000 ms | 同上 | ✅ |
| 四段分解基线(序⑨,n=5+n=5) | 本棒三读数 18.8–20.9 s ∈ 基线区间 20.9–23.9 s(修后)同量级、三处均在 deadline 内侧 | 参数表 §9 第 9 行 | ✅ |
| 派生健康读数 | 收口后 jitter.p95AbsDeltaMs = 6 ms / overThreshold=false / alerts=3(对照 JITTER_LINK_MEASURED=3);⚠️ 演练窗口内曾读到 p95=920 ms / overThreshold=true(同源 = 心跳 RTT,含应用层与验签 ⇒ ⛔ 按 §3.5 口径不得当链路 RTT 用) |
JITTER_LIMIT_MS=20 |
✅ 收口态达标 |
D-S7-④ 回源字节(夹具 = 同一份 5,255,225 B / 6 块;口径 = 逐次计 origin fetcher 的字节)
| 规模点 | originFetches |
originBytes |
放大比 | 每组合计 | 对照腿(peer 关) | 节省比例 |
|---|---|---|---|---|---|---|
| 1 组 × 4 节点(N=4) | 6 | 5,255,225 | 1.0000 | 1.0000 | 4.0000 | 75.00% |
| 1 组 × 16 节点(N=16) | 6 | 5,255,225 | 1.0000 | 1.0000 | 16.0000 | 93.75% |
| 4 组 × 4 节点(N=16) | 24 | 21,020,900 | 4.0000 | 1.0000 | 16.0000 | 75.00% |
| 8 组 × 4 节点(N=32) | 48 | 42,041,800 | 8.0000 | 1.0000 | 32.0000 | 75.00% |
- 🔴 主判据(规模不变量):放大比恒 ≡ 组数、每组合计恒
1.0000⇒ 回源字节 ∝ 组数,⛔ 不随组内节点数增长(perGroupAmplificationAlwaysOne=true/amplificationEqualsGroupCount=true/amplificationIndependentOfGroupSize=true/controlEqualsNodeCount=true)。 - ✅ 与既有读数逐项对齐:序㉔
1.0000⇄ 对照4.0000(本棒 N=4 点逐字复现);序㊛4 组 × 4 节点 ⇒ 21,021,572 B = 4.0001 × 包(本棒 4 组点 = 21,020,900 B / 4.0000,差 = 序⑳ 口径的尾块取整,同量级同结论)。 - 运行时接线腿(真
ContentRuntime,8 组 × 4 节点):wss-回落腿served=144/144、hits.wss=144、downReasons={direct:no-address:144}、idChecks=144 / idMismatches=0;direct-优先腿served=144/144、hits.direct=144 / hits.wss=0、idChecks=144 / idMismatches=0⇒ 优先直连且 ⛔ 不碰 wss,且每次命中都过 D7 复算闸门。 - 📌
§10-2(直连带来的中继带宽节省比例):内容面 =1 − 组数/节点数⇒ 组内 4 人 75.00% / 组内 16 人 93.75%(⚠️ 这是内容分发面的节省;真机直连的节省仍 = 0,因 D8 未放行)。
③ 零回归三件套
| 项 | 本棒读数 | 基线 | 判定 |
|---|---|---|---|
npm.cmd test(Node 22) |
201 tests / 200 pass / 0 fail / 1 skipped(rc=0;duration_ms=53333;注入脚本自检「全部合格 ✅」) |
201/200/0/1 | ✅ 逐字一致 |
node scripts/overlay-failover-drill.cjs --scene all --table |
12 PASS / 0 SKIP / 0 FAIL(rc=0;6m20s;未套任何外层超时) | 12P/0S/0F | ✅ |
node scripts/overlay-probe.cjs --table(真机) |
28 PASS / 0 SKIP / 0 FAIL(rc=0;开工前 + 收口后两次同值) | 28P/0S/0F(⚠️ 非旧的 25P) | ✅ |
🔴 上上棒的坑本棒已避开:--scene all 未套任何外层超时(跑满 6m20s)⇒ 末尾 restore() 正常执行 ⇒ 两台 dshs-relay 均 active(⛔ 未出现 106 relay 被留在停机态)。
④ 恢复形态核实(47 / 106 两端单元与服务状态 + 门户码)
- 47:
dshs=active/dshs-worker=active/dshs-relay=active;relay/status= 200;门户(Host: alotbuy.com)= 200;https://alotbuy.com/dshs-overlay/bootstrap= 200 - 106:
dshs-worker=active/dshs-relay=active;relay/status= 200 - 47 视角中继读数:
used=2 / max=7515 / free=7513 / utilPct=0;endpoints=[["w-106",19000,online],["w-106",21001,online]];sessions=[manager(rtt 8ms), w-106(rtt 373ms)];counters={dial:0, dialDenied:0, dialFailed:0, refused:0, utilRefused:0, authFailed:0} - 探针四条口径行:
PASS OBS-01 used=2|PASS OBS-04 identityOk=2|PASS OBS-08 端点表 2 条 / 离线 0 条|PASS OBS-09 在册实例面(派生 1 条):w-106:37653=401(⚠️ 端口由 42099 → 37653 再漂一次 = 设计内,判据跟着事实走、⛔ 未改表值) - ⚠️ 演练后的复原动作:
--scene all收口时 47 视角 =used=1 / endpoints=[](106 worker 挂到 106 自己的中继)⇒ 按既有程序处理,见 §8.9-⑥-3(两次尝试,第二次成功);复原后探针 28P/0S/0F。
⑤ 边界自证(逐条打勾)
- ⛔ 未 commit / 未 push:HEAD 仍
09ce76f;git status --short= 19 处(= 序㊶ 收口的 19 ⇒ 零新增条目;全部为 P1/P2 在册工作树文件) - ⛔ 未改
src/net/relay/server.ts的 DIAL / 白名单语义:本棒零字节改动(git status里server.ts条目为序⑳-㊶ 既有改动,非本棒) - ⛔ 未改
src/net/relay/content/**的块 id 计算路径(sha256(密文)口径未动;⛔ 本棒未做任何扰动实验,D7 用的是序㊶ 已固化的闸门) - ⛔ 未改
nft/nginx/bwrap:47nft list ruleset= 72 行(逐字一致);bubblewrap 47 = 0.4.0 / 106 = 0.11.0(未触碰) - 🔴
RELAY_FAILOVER_COOLDOWN_MS=0命中数 = 0(两机/etc/**+ systemd +scripts/全扫) - 🔴 密钥本体不经网络 / relay:本棒不下发、不传输、不打印任何密钥;
/etc/dshs/content-group-key.json两机仍600 - ⛔ 零个新增 / 改动生产 env:
DSHS_OVERLAY_DIRECT两机命中 0 ⇒ 走缺省即开;⛔ 未新增任何监听口(OBS-11 多出 0 缺失 0;ss -lunp在21100–21115段收口后 0 行) - ⛔ 本棒未改任何仓内文件(⛔
scripts/**、⛔src/**、⛔test/**全零改动)—— 三项夹具全部落在工作区_tmp_seq42/(⛔ 仓外) - 本棒对生产的动作清单(均 R8,动手前已一句话说明,⛔ 无不可逆项):①
--scene all演练一次(自停 / 自启 relay;幕 4 含一次 Manager 重启 = 演练设计内)② 47 上重跑overlay-direct-probe.cjs selfcheck --json两次(⛔ 无--out⇒ ⛔ 未写/var/lib/dshs/overlay/direct-selfcheck.json,该文件仍是 13:13:20 的序㊵ 版本)+ 一次--ss(正对照真绑一个 UDP 口后立即释放)③ 47 上跑punch腿(只读 UDP,3 s)④ 106 上跑/tmp/d8_punch_106.cjs(本棒 scp 的最小脚本,只requirepunch.js)⑤ 停 / 启 106dshs-relay两次(见 ⑥-3)⇒ ⛔ 全程未重启任何 worker、⛔ 未换实例、⛔ 未删任何数据 / 配置
⑥ 如实留档(必填)
- 🔴 D8 仍未放行(红 · 只读核对,⛔ 未实施):真机双向并发 UDP 实测 —— 47 侧
reason=deadline recvLocal=0 sent=21 elapsed=3000ms sources=[];106 侧{"side":"106","reason":"deadline","recvLocal":0,"sent":28,"elapsedMs":4001,"sources":[]}(发 21/28、收 0)。承载线未动:47nft72 行逐字一致(106 无 nft ruleset = 既存形态)⇒ 挡在云侧。⛔ 未改任何云侧规则、⛔ 无任何0.0.0.0/0形态(本棒未新增任何规则)、⛔ 未索取、未硬编码任何云 AK/SK(乙-3 = 回头条件)。拟录 乙-1 两条(源为对端/32)原样维持,待拍板。 - ⚠️
overlay-probe.cjs/overlay-failover-drill.cjs必须从「工作区根」跑(它们按/^参数表_覆盖网络_.+\.md$/在 cwd 找参数表)—— 从代码仓根跑 ⇒❌ 参数表装载失败 … 找到 0 个参数表(rc=2)。本棒首次两个命令都踩了,已改用工作区根 + 脚本绝对路径(⛔ 该失败不发生任何生产动作,仅 rc=2 退出)。 - ⚠️ 复原程序要"停够久"才生效(本棒实测修正):
--scene all后 47 视角used=1 / endpoints=[]。按既有程序"停 / 启 106dshs-relay"——第一次「停 2 s 即启」未触发回落(worker 在RELAY_GRACEFUL_BURST_MS=15000的快速重试窗内重连回同一条 url;该窗内attempts恒 0 ⇒ 切流条件不成立)⇒ 第二次「停 26 s 后再启」成功(停稳期间 47 侧原候选可用 ⇒ worker 切回 47,used=2、端点 2 条全在线)⇒ 中间 26 s 内 106 的跨机流量走 47,属设计内回落。⛔ 全程未重启 worker、⛔ 未换实例。 - ⚠️ 一处过程测错并当场修正(⛔ 未编数):首次
selfcheck --ss与punch腿并发运行 ⇒ punch 腿先绑住21100⇒ 正对照ctrl.open(21100)EADDRINUSE ⇒udp.ssDuringOnCase读成null、ssBefore读成 1(看起来像"UDP 口被占"的假红)。单独重跑后 =ssBefore 0 / ssDuringOffCase 0 / ssDuringOnCase 1(与序㊵ 读数逐字同值)⇒ 测量错,⛔ 非产品缺陷。 - ⚠️ §6 与 prompt 的"切换耗时阈值"落点不一致(只登记,⛔ 未改正文):本棒 prompt 与验收写「对照参数表 §3.2(切换耗时阈值)」,而 §3.2 是"打洞率"(
HOLE_PUNCH_RATE_*);切换耗时的权威阈值是 §4 的RELAY_FAILOVER_DEADLINE_MS = 30000 ms,四段分解在 §9 第 9 行。本棒按权威落点对照(§8.9-②D-S7-③),⛔ 未为迁就指针改 §3.2 / §4 任一格。 - ⚠️
--ss是布尔开关(按"出现过"判定)⇒ 不给它就没有任何udp.*读数(ssAvailable=false)。真机腿的取证顺序 = 单跑、⛔ 不与其它绑同一端口段的腿并发(见 ⑥-4)。 - ⚠️ 106 侧观测面缺口仍在(承接 §8.8-④):106
/opt/dshs-cluster/lib无registry.js⇒overlay-direct-probe.cjs在 106 上跑不起来 ⇒ 106 侧真机打洞腿改用最小脚本d8_punch_106.cjs(只requirepunch.js,本棒 scp 到/tmp/,⛔ 未落在任何 lib / units 路径)。 - ⚠️
§10未验证项的本棒增量(⛔ 不冒充"已判定"):§10-1「本系统打洞成功率」 ⇒ 本棒给出两条并列读数:模拟器 1.0000(n=24,双向)/真机 0.0000(n=2 方向) ⇒ 该项仍不可判定"公网场景成功率"(真机腿被 D8 挡着);§10-3 切换耗时 ⇒ 本棒 18.8–20.9 s(n=3,均在 30000 ms 内侧);§10-2 中继带宽节省 ⇒ 内容面 75.00% / 93.75%(模拟器),真机 = 0(D8)。 - ⚠️ 本棒只做 S7:⛔ 未重做 S5(
OBS-26/27/28判据面本棒只读取)/S6(peer接线零改动,peerWire只读取);⛔ 未开工任何新功能。 - ⚠️ 三条夹具脚本落在工作区
_tmp_seq42/(s7_candidate_scale.mjs/s7_punch_scale.mjs/s7_origin_scale.mjs+ 读数*.json+drill.txt/probe_*.txt)⇒ ⛔ 属临时产物,按任务收尾纪律保留至用户确认后再清理(⛔ 本棒未删)。
⑦ 指纹
- 本单 §8 前前缀(口径:
sed '/^## §8 回报格式/,$d' <本单> | md5sum):8925c2460810c6dc5cdbc075b6cdaed2(写入本小节后现算,与 §11.2 / §11.5 / §8.8-⑧ 逐字一致 ✅ —— §8 是截断点,故新增 §8.9 不改变它;= 该口径的又一次自证)。 - 参数表 §10 指纹:
6b37bfd506758d882d9f803678f85d23(现算,与 §11.7–§11.9 记的值逐字一致 —— 本棒新增 §11.10 在 §10 口径之外 ⇒ 指纹不变,未动任何键值)。
8.10 序㊸ 执行棒回报(观测面缺口收口:① 探针在 106 上可跑 ② 参数表定位与 cwd 解耦 ③ 106 不可跑残留副本处置;2026-09-18 14:47–15:2x)
⛔ 本小节只新增:未改 §1–§7 正文、未改 §8.1–§8.9 小节点名。§8 前前缀
8925c2460810c6dc5cdbc075b6cdaed2写入本小节后现算仍逐字不变 ✅(见 §8.10-⑦)。
① 开工门:state.py 已读;入口 接续入口_覆盖网络线_20260916.md §0 最新两行 + §2 首行已读(= 唯一执行依据);全局执行锁 --claim-exec "序㊸ 执行棒(观测面缺口收口)" 抢到(09-18 14:47)。§8 前前缀开工前现算 = 8925c2460810c6dc5cdbc075b6cdaed2 ✅ 逐字一致(与 §11.2 / §11.5 / §8.8-⑧ / §8.9-⑦ 记的值相同)。范围 = 只做观测面三件事(⛔ 未碰数据面、⛔ 未做 D8、⛔ 未重做 S5/S6/S7)。
② 三项逐条(原始读数;⛔ 无编数)
Ⅰ 探针在 106 上可跑(先红后绿)
| 腿 | 原文读数 | 判定 |
|---|---|---|
先红(106 上改前的副本 /opt/dshs-cluster/scripts/overlay-direct-probe.cjs,md5 8d1ea60a01fc09fb5f56e492afb76fc9) |
status 与 selfcheck --json 两条命令都:Error: Cannot find module '../lib/net/relay/registry.js' + Require stack: - /opt/dshs-cluster/scripts/overlay-direct-probe.cjs |
🔴 跑不起来(106 /opt/dshs-cluster/lib 无 registry.js,实测 ls = No such file or directory) |
| 后绿(同一命令、同一规范路径) | ⓵ status ⇒ rc=0 原文 直连(打洞):开|来源 default|缺省 true + 本机配置 /etc/dshs/overlay-node.json:不存在 ⓶ selfcheck --json ⇒ rc=0,机器可读读数 {"degraded":true,"degradedLegs":["joinConf"],"joinConfAvailable":false,"joinConfReason":"module-missing","candAcc":3,"candRej":9,"silent":0,"bi":true,"ok":2,"cooldown":300000,"zero":"throws","deps":true} ⓷ punch 真机腿 --peer 47.77.182.89:21100 --json ⇒ rc=0 原文 窗内零收包(deadline 3000 ms,实耗 3000 ms,发 21 包)⇒ 判死 + 进冷却 300000 ms |
✅ 跑通并给出该子命令读数 |
- 处置方式 = 惰性 / 可选加载 + 具名降级(⛔ 未"再铺一份
registry.js"):overlay-direct-probe.cjs顶层原先require了registry.js(控制面注册表模块)与同样 import 它的join.js⇒ 106 上任何子命令都死在第一行。现改为:只有join 回读(判据 D3)这条腿惰性加载这两个模块,加载不到 ⇒ 返回{ available:false, reason:'module-missing', missing:[{spec, code, message}] }。 - 🔴 具名降级(文本原文):
join 回读:⛔ 不可用(module-missing)—— 缺 ../lib/net/relay/registry.js , ../lib/net/relay/join.js|⚠️ 这是「没装」不是「没采到」|原文 message:Cannot find module '../lib/net/relay/registry.js' …⇒ ⛔ 未静默返"没有";读数里另给机器可读标记degraded:true/degradedLegs:["joinConf"],且缺块不填direct/readback("没装"与"跑了但读数为空"形状完全不同 = 可分)。 - ⚠️ 只吞
MODULE_NOT_FOUND/ERR_MODULE_NOT_FOUND(且原文 message 原样带出);其它异常(语法错 / 依赖内部崩)一律照抛 —— 否则会把真故障伪装成"缺模块"。 - 本地等效夹具(零机器风险):把本机
lib/net/relay/{registry,join}.js临时改名后跑selfcheck⇒ 同一降级形状、rc=0、其余腿全在(候选 3 / 具名拒绝 9 / 静默 0 / 打洞双向 ✅ / 冷却 300000throws/ 依赖面 ✅);还原后 md5 逐字回位(951a2526789b897aa58e3abbb5dea332/6946e1d802494ccb7f522ef7b937614a)。
Ⅱ 参数表定位与 cwd 解耦(两腿对照 + 防错腿)
| 腿 | 命令(cwd) | 原文读数 | 判定 |
|---|---|---|---|
先红(旧逻辑副本,与 git show HEAD:scripts/overlay-probe.cjs 的 resolveTablePath 逐字一致) |
cd D:\github\dsh_shenxian && node <旧逻辑副本> |
rc=2 ❌ 参数表装载失败:在 D:\github\dsh_shenxian 下按 /^参数表_覆盖网络_.+\.md$/ 找到 0 个参数表(要求恰好 1 个) |
🔴 复现 §8.9-⑥-2 的原始失败 |
| 后绿 · 探针 | cd D:\github\dsh_shenxian && node scripts/overlay-probe.cjs(不给 --table) |
rc=0,28 PASS / 0 SKIP / 0 FAIL | ✅ |
| 后绿 · 演练 | cd D:\github\dsh_shenxian && node scripts/overlay-failover-drill.cjs --scene all(不给 --table) |
rc=0;第 1 行 # 参数表=E:\ProgramData\AI技能\aliyun-dsh-server\参数表_覆盖网络_20260917.md;末行 # 结果:12 PASS / 0 SKIP / 0 FAIL |
✅ 从代码仓根跑通并定位到工作区那份表 |
| 防错腿 A(同目录人为造 2 份) | 探针 + 演练 各一次,--dir <2 份的目录> |
两者均 rc=2:在 E:\…\_tmp_seq43\two_tables 下按 /^参数表_覆盖网络_.+\.md$/ 找到 2 个参数表(要求恰好 1 个):参数表_覆盖网络_20260917.md , 参数表_覆盖网络_A.md |
✅ 报错退出 |
| 防错腿 B(跨候选目录合计 2 份) | 探针 + 演练 各一次;cwd = 含 1 份的目录 + DSHS_OVERLAY_TABLE_REGISTRY 指向另一含 1 份的目录 |
两者均 rc=2:跨候选目录共找到 2 个参数表(要求恰好 1 个,⛔ 不猜哪一份是对的): + 逐份列出 |
✅ 报错退出 |
- 候选目录链(写死):
--table(显式文件,最高优先、直接返回)>--dir>DSHS_OVERLAY_TABLE_DIR> 机器本地注册文件(DSHS_OVERLAY_TABLE_REGISTRY,缺省~/.dshs/overlay-table-dir,一行一个目录、#注释)>cwd> 脚本自身目录及其上两级。 - 🔴 "恰好 1 个才算合法"保留、且更严:任一候选目录 ≥ 2 份 ⇒ 立即报错;跨候选目录合计 ≥ 2 份 ⇒ 也报错(列出全部命中)。⛔ 一份都没找到 ⇒ 报错并列出所有搜过的目录 + 注册方法(⛔ 未放宽成"找不到就用缺省")。
- 🔑 为什么必须有"注册文件"这一环(实测事实):参数表不在仓内(
git ls-files | grep -c 参数表= 0),它在工作区根,而本机仓在D:、工作区在E:—— 不同盘 ⇒ 纯路径推算永远到不了。故一律把机器差异放在$HOME(⛔ 仓内零机器路径)。
Ⅲ 106 残留副本处置(二选一 ⇒ 选"使其可跑")
- 理由:106 正是真机打洞腿的取证机;删掉它只会逼出更多
/tmp/*.cjs临时脚本(那正是 §8.8-④ / §8.9-⑥-7 的脆点)。⇒ 不删,改为可跑(⛔ 未动dshs-*单元、⛔ 未动lib)。 - 证据:修复版 scp 到三处 且 md5 全同 =
4734686b5270059697ccbcf966c34ade—— 本机仓 / 47/opt/dshs/scripts/overlay-direct-probe.cjs/ 106/opt/dshs-cluster/scripts/overlay-direct-probe.cjs;106 上在规范路径(非/tmp)跑通status/selfcheck/punch三条子命令(见 Ⅰ)。 - 回滚点 = 两机
/opt/dsh/backups/seq43-20260918-150549/overlay-direct-probe.cjs(各 1 个文件,md58d1ea60a01fc09fb5f56e492afb76fc9= 改前值);chmod 755保持可执行位。
③ 零回归三件套
| 项 | 本棒读数 | 基线 | 判定 |
|---|---|---|---|
npm.cmd test(Node 22.22.2) |
201 tests / 200 pass / 0 fail / 1 skipped(rc=0;duration_ms=53315.1989;「结论:全部合格 ✅」) |
201/200/0/1 | ✅ 逐字一致 |
node scripts/overlay-failover-drill.cjs --scene all(无 --table,从代码仓根) |
12 PASS / 0 SKIP / 0 FAIL(rc=0;6m23s;未套任何外层超时;幕4-C 17240 ms / 30000 ms) |
12P/0S/0F | ✅ |
node scripts/overlay-probe.cjs --table(真机) |
28 PASS / 0 SKIP / 0 FAIL(rc=0)—— 开工后(代码仓根,不给 --table)+ 收口后(工作区根)两次同值 |
28P/0S/0F | ✅ |
④ 恢复形态核实(演练后 + 复原程序)
- 47:
dshs=active/dshs-worker=active/dshs-relay=active;relay/status= 200;门户(Host: alotbuy.com)= 200 - 106:
dshs-worker=active/dshs-relay=active - 演练后 47 视角 =
endpoints=[](106 worker 挂到 106 自己的中继)⇒ 按既有程序复原:systemctl stop dshs-relay(106)⇒ 停够 26 s ⇒systemctl start⇒ 等 ~20 s ⇒ 47 视角sessions=["ops/manager","ops/w-106"]、endpoints=[["w-106",19000,true],["w-106",21001,true]]2 条全在线 ⇒ 探针OBS-01 used=2✅(⛔ 全程未重启 worker、⛔ 未换实例) - 探针四条口径行(收口态):
OBS-01 used=2|OBS-08 端点表 2 条 / 离线 0 条|OBS-09 在册实例面(派生 1 条):w-106:42477=401(端口漂移 = 设计内)|OBS-11 集合 … 多出 0 缺失 0 | nft accept 0 多出 0 | relay 口绑定回环=1/1 条
⑤ 边界自证(逐条打勾)
- ⛔ 未 commit / 未 push:HEAD 仍
09ce76f;git status --short= 19 处(= 序㊷ 收口的 19 ⇒ 零新增条目;本棒改动的scripts/overlay-probe.cjs/scripts/overlay-failover-drill.cjs本就已M、scripts/overlay-direct-probe.cjs本就连??) - ⛔ 未改
src/net/relay/server.ts的 DIAL / 白名单语义:本棒零字节改动(git status里无server.ts新条目) - ⛔ 未改
src/net/relay/content/**的块 id 计算路径(sha256(密文)口径未动;⛔ 本棒未做任何扰动实验) - ⛔ 未改
nft/nginx/bwrap:47nft list ruleset= 72 行(逐字一致);bubblewrap 47 = 0.4.0 / 106 = 0.11.0(未触碰);⛔ 未改任何 nginx 配置 - 🔴
RELAY_FAILOVER_COOLDOWN_MS=0命中数 = 0(两机/etc/**+/opt/dshs*/scripts+ unitEnvironment全扫,四处计数全 0) - 🔴 密钥本体不经网络 / relay:本棒不下发、不传输、不打印任何密钥;
/etc/dshs/content-group-key.json两机仍600 - ⛔ 零个新增 / 改动生产 env:
DSHS_OVERLAY_DIRECT两机命中 0(走缺省即开);⛔ 零新增监听口(收口后ss -lunp在21100–21115段 0 行) - ⛔ 本棒只改 3 个脚本(
scripts/overlay-direct-probe.cjs+scripts/overlay-probe.cjs+scripts/overlay-failover-drill.cjs);⛔ 未改src/**/test/**;⛔ 未npm run build(本棒无 TS 改动) - 本棒对生产的动作清单(均 R8,动手前已一句话说明,⛔ 无不可逆项):① 两机各 scp 一份探针(覆盖旧副本,先备份)② 106 上跑
status/selfcheck/ 一次只读punch(UDP 探测 3 s)③--scene all演练一次(自停 / 自启 relay;幕 4 含一次 Manager 重启 = 演练设计内)④ 停 / 启 106dshs-relay各一次(复原程序,见 ④)⇒ ⛔ 全程未重启任何 worker、⛔ 未换实例、⛔ 未删任何数据 / 配置
⑥ 如实留档(必填)
- 🔴 D8 本棒零动作(在册待拍板):⛔ 未改任何云侧规则、⛔ 无任何
0.0.0.0/0形态(含拆分等价写法)、⛔ 未索取 / 未硬编码任何云 AK/SK。⚠️ 本棒唯一的"触碰网络"动作 = 106 上一次只读punch(--peer 47.77.182.89:21100,UDP 探测 3 s,发 21 包 / 收 0),其结论与 §8.9-⑥-1(D8未放行)同向,⛔ 不构成任何 D8 实施。 - 🔑 Ⅱ 的解法含一个"机器本地注册"环节(必须知道):候选目录链里的注册文件 =
~/.dshs/overlay-table-dir(本机已写入一行 = 工作区根)。⇒ 换机器 / 换工作区路径时需补这一行(或改用--table/--dir/DSHS_OVERLAY_TABLE_DIR/DSHS_OVERLAY_TABLE_REGISTRY)。🔴 ⛔ 仓内零机器路径(该文件不入库、也不进git status)。 - ⚠️ 范围新增(1 处,已声明):
scripts/overlay-probe.cjs的OBS-26子判据加了 3 行 —— 当读数生产方显式给出joinConf.available === false(即 Ⅰ 的具名降级)时,该子判据取 SKIP + 留痕(⛔ 不判红;detail 里点名缺哪个模块)。理由 = Ⅰ 改变了读数形状;不改这里 ⇒ 一份合法的 106 读数会让OBS-26假红(本线明令"长期挂红项会降低判据灵敏度")。零回归佐证 = 老形状(无available键,如 47 那份/var/lib/dshs/overlay/direct-selfcheck.json)走原路径、判定与文本逐字不变 ⇒ 三件套 28P/0S/0F 成立。⚠️ 该改动可随时回退(3 行)。 - ⚠️ 参数表 §6
OBS-26行本棒 ⛔ 未改(为保 §10 指纹不变 —— §6 在 §10 口径之内)⇒OBS-26的"具名降级 ⇒ SKIP + 留痕"这条语义登记在参数表 §11.11;下次有键值变更时顺带并入 §6 行文本。 - ⚠️ 演练脚本的
--trace/--sample仍按cwd落盘_中间产物_待清理/seq9-trace/(⛔ 本棒未改 —— 它属那两幕的取证落盘、⛔ 不在"参数表定位"这一条的范围;--scene all不会走到它们 ⇒ 从代码仓根跑零副作用,实测无多余文件产生)。 - ⚠️ 106 上
/tmp/d8_punch_106.cjs(序㊷ 的临时最小脚本)仍在(信息项,⛔ 本棒未动它)—— 本棒起,106 侧真机腿用规范路径的探针即可,该临时脚本已无必要。 - ⚠️ 一处既有行为(⛔ 未改,只登记):
--table裸开关(不给值)会被判为"未给"(argOf要求带值)⇒ 走候选目录链。本棒头一次探针就是这么跑的(意图如此:证明"从代码仓根、不给--table"也能跑)。 - ⚠️ 本棒 ⛔ 未碰
src/**/test/**/package.json(⛔ 零新依赖:改动只用了 Node 内建child_process/fs/os/path)。 - ⚠️ 夹具与读数全部落在工作区
_tmp_seq43/(overlay-probe.old.cjs= 旧逻辑副本、mk_old_probe.py、two_tables//one_table_a//one_table_b//reg_cross防错夹具、red_106.txt/green_106.txt/probe_from_repohost.txt/probe_old_from_repohost.txt/probe_final.txt/drill_all.txt/npm_test.txt/dp_degraded.*)⇒ ⛔ 属临时产物,按任务收尾纪律保留至用户确认后再清理(⛔ 本棒未删)。
⑦ 指纹
- 本单 §8 前前缀(口径:
sed '/^## §8 回报格式/,$d' <本单> | md5sum):8925c2460810c6dc5cdbc075b6cdaed2(写入本小节后现算,与 §11.2 / §11.5 / §8.8-⑧ / §8.9-⑦ 逐字一致 ✅ —— §8 是截断点,故新增 §8.10 不改变它)。 - 参数表 §10 指纹:
6b37bfd506758d882d9f803678f85d23(现算)—— 本棒新增 §11.11 在 §10 口径之外 ⇒ 指纹不变,未动任何键值 / 阈值。
8.11 序㊹ 执行棒回报(D8 云安全组乙-1)—— ⏸ 未收官 · 阻塞上报(2026-09-18 15:3x–16:0x)
⏸ 本棒未完成实施:用户已于本轮批准开通(原话见 §8.11-⑥),但执行侧无云 API 凭据 ⇒ 依 §1 判据(边界外④「需用户提供的账号凭据或审批」)停下上报。⛔ 本棒零写入生产、零改代码、零改 nft / nginx / bwrap。
① 开工门(必填)
| 项 | 值 |
|---|---|
| 全局执行锁 | ✓ --claim-exec "序㊹ 执行棒(D8 云安全组乙-1)"(09-18 15:37 起持锁) |
| HEAD | 09ce76f(⛔ 未 commit / 未 push) |
| 本棒动作 | 只读取证(ssh 只读命令;⛔ 未 scp、未 build、未重启任何单元) |
��� D8 执行侧前置取证(只读实测)
| # | 项 | 实测值 |
|---|---|---|
| a | 47 固定公网 IP / 实例 | 47.77.182.89 / 阿里云 i-rj99af19cibck1ge93tq |
| b | 106 固定公网 IP / 实例 | 106.54.21.172 / 腾讯云 ins-3q6k1p8t |
| c | 网卡形态 | 47 eth0 172.18.16.212/18;106 eth0 10.0.0.8/22 ⇒ 两侧公网 IP 均为云网关 NAT 映射(⛔ 非直挂网卡)⇒ 入站需云安全组放行(本地防火墙不参与 DNAT 前段) |
| d | UDP 端口区间 | [21100, 21116)(punch.ts#PUNCH_PORT_BASE=21100 + PUNCH_PORT_SPAN=16)⇒ 规则须覆盖 21100–21115。⚠️ 两机 ss -lunp 在该段均无监听 —— 正常:打洞 socket 只在实打时才开,⛔ 不是"没装"(「没装 vs 没采到」不可混) |
| e | 直连总开关 env | DSHS_OVERLAY_DIRECT 在两机 /etc/dshs*.env 命中 0 ⇒ 走缺省 = 开,⛔ 无需改任何 drop-in |
| f | 47 本地防火墙 | nft 72 行;hook input … policy accept ⇒ 不拦入站 UDP。⚠️ 第 68 行那条 udp … reject 是 沙箱 uid 100000-199999 的出站限制(daddr 含 47.77.182.89),⛔ 与本腿无关(打洞跑在服务 uid) |
| g | 106 本地防火墙 | nft 0 行;iptables -P INPUT ACCEPT + YJ-FIREWALL-INPUT(5 行,仅 4 条 /32 REJECT 黑名单,不含 47);ipset test YJ-GLOBAL-INBLOCK 47.77.182.89 ⇒ NOT in set ⇒ 不拦入站 UDP |
| h | 47 节点注册表 | /var/lib/dshs/overlay/nodes.json = {"version":1,"nodes":{}}(空) |
| i | 47 目录(relays) | directory.json ⇒ alotbuy.com/dshs-relay / relay-direct.alotbuy.com/dshs-relay / 106.54.21.172/dshs-relay(3 条) |
③ 阻塞判定:为什么本棒自己做不到(原文级证据)
| 位置 | 证据 | 结论 |
|---|---|---|
| 本机(WorkBuddy 开发机) | 无 cloud CLI、无 ~/.aliyun / ~/.tccli / ~/.aws |
无法调用云 API |
| 47(阿里云) | aliyun CLI 存在(/usr/local/bin/aliyun)但 ~/.aliyun/config.json 不存在;aliyun configure list = default * | (空) | Invalid | (空) | en |
凭据未配置 |
| 47 metadata | http://100.100.100.200/latest/meta-data/ram/security-credentials/ ⇒ 404 - Not Found |
无 RAM 角色可用(拿不到 STS 临时凭据) |
| 106(腾讯云) | 无 tccli;metadata http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials/ ⇒ 404 |
无 CAM 角色可用 |
⇒ 两机均无法自我调用云 API;且本线纪律 🔴 ⛔ 不索取、⛔ 不硬编码任何云 AK/SK ⇒ 只能由用户侧控制台落地(或由用户主动给出一次性凭据 —— 本棒 ⛔ 不索要)。
④ 待落地规则形态(乙-1,原文级 —— ⛔ 执行侧不得自行改形)
- 47 阿里云安全组 · 入方向:协议
UDP· 端口范围21100/21115· 授权对象106.54.21.172/32 - 106 腾讯云安全组 · 入方向:协议
UDP· 端口范围21100/21115· 授权对象47.77.182.89/32 - 🔴 硬门:⛔ 零个
0.0.0.0/0(含任何拆分 / 等价写法);⛔ 不放行 TCP 段(LISTEN_ALLOWED_RANGES是 TCP,⛔ 别混);⛔ 不改nft/nginx/bwrap。
④-补 控制台定位信息与人工步骤(2026-09-18 16:0x 追加,供人工落地)
(a)定位信息(实测,⛔ 别靠默认地域猜)
| 角色 | 云 / 产品 | 地域 · 可用区 | 实例 ID | 公网 IP | 私网 IP |
|---|---|---|---|---|---|
| 47 | 阿里云 ECS | 🔴 美国(硅谷)us-west-1 · us-west-1a |
i-rj99af19cibck1ge93tq |
47.77.182.89 |
172.18.16.212(VPC vpc-rj900gd84ra3taamu6tbr) |
| 106 | 腾讯云 CVM | 上海 ap-shanghai · ap-shanghai-2 |
ins-3q6k1p8t |
106.54.21.172 |
10.0.0.8 |
⚠️ 两机跨洋(硅谷 ↔ 上海)⇒ 打洞腿的建连时延天然偏高(不影响规则形态,但影响 S5/D5 的耗时读数解读)。
⚠️ 两机的安全组 ID 都取不到:阿里云 metadata security-group-ids ⇒ 404;腾讯云 metadata security-groups ⇒ 404 ⇒ 只能从控制台实例详情页点进去(或先按实例 ID 找到实例,再点它的安全组)。
⚠️ 106 的实例 ID 前缀是 ins-(CVM),不是 lhins-(轻量)⇒ 走 CVM 安全组口径,⛔ 不是轻量「防火墙」口径。
(b)阿里云(47)人工步骤:ECS 控制台 → 顶部地域切到 美国(硅谷) → 实例 i-rj99af19cibck1ge93tq → 「安全组」→ 点安全组 ID → 「访问规则」→ 入方向 → 「手动添加」→ 授权策略 允许 / 优先级 1 / 协议类型 自定义 UDP / 端口范围 21100/21115 / 授权对象 106.54.21.172/32 / 描述 dshs overlay hole-punch peer 106 → 保存(立即生效,⛔ 无需重启实例)。
(c)腾讯云(106)人工步骤:CVM 控制台 → 地域 上海 → 实例 ins-3q6k1p8t → 「安全组」→ 编辑规则 → 入站规则 → 添加规则 → 类型 自定义 / 来源 47.77.182.89/32 / 协议端口 UDP:21100-21115 / 策略 允许 / 备注 dshs overlay hole-punch peer 47 → 保存(立即生效)。
(d)若改为由执行侧代做(用户主动给凭据时的最小权限口径):阿里云 = RAM 子账号 AccessKey,仅授 ecs:DescribeSecurityGroups + ecs:AuthorizeSecurityGroup,地域限 us-west-1;腾讯云 = CAM 子账号,仅授 cvm:DescribeSecurityGroupPolicies + cvm:ModifySecurityGroupPolicies,地域限 ap-shanghai。🔴 有效期尽量短、用完即撤;⛔ 本棒不索取、⛔ 不落盘、⛔ 不写进任何配置文件。
⚠️ 已核:连接器市场内不存在阿里云 / 腾讯云 CVM·ECS 安全组相关的 Connector ⇒ 没有"装个连接器就自动获得授权"这条路。
⑤ 本机(WorkBuddy 开发机)在测试中的角色判定 —— 回应「所有的测试是否包含本机,当做结点或终端」
- 判定:本机 = 测试发起端 / 观测台 + 开发机。⛔ 不是覆盖网节点,⛔ 不是覆盖网终端("终端"在本线从未定义过 —— 本线节点形态只有
worker一种)。 - 证据(五条,逐条可分):
- 四条测试命令均从本机发起(
npm.cmd test/overlay-probe.cjs/overlay-failover-drill.cjs/overlay-direct-probe.cjs)—— 本机是发起端,不是被测对象。 - 本机 零
dshs*/dsh-*进程、零20080/19100/20000/21000–21001/21100–21115监听 ⇒ 结构上不在/status.endpoints[]/sessions[]里。 - 47 权威注册表
nodes.json={}(空)⇒ 本机未注册为节点。 - 本机
curl -4 ifconfig.me回落到 IPv6(240e:332:cd0:a710:448:…)⇒ 无固定公网 IPv4 ⇒ ⛔ 给不出稳定/32⇒ D8 式对端放行对本机结构上不成立。 - 本机
~/.dshs/仅两项:overlay-table-dir(序㊸ 本线自建的参数表定位注册,⛔ 非节点注册)+secret.key(64 B · 09-13 15:40 · 系src/config.ts#ensureSecretKey的数据根密钥,⛔ 非覆盖网节点密钥;密钥本体 ⛔ 未读取)。
- 四条测试命令均从本机发起(
- 若要纳入:只能作「只拨出」从节点(本线铁律:worker 永远只拨出),需装运行时 + profile + 注册为 Worker;⚠️ 因无固定公网 IPv4,该节点天然只能用 relay / 443 兜底腿,直连腿结构上不可用(会成为 L2 的"永久只走中继"节点)。
- 本棒动作:⛔ 未改测试拓扑,⛔ 未注册本机 —— 仅登记判定。
⑥ 用户口径(原话,⛔ 不改写)
「1 开通,需要我操作还是你可以自己操作,所有的测试是否包含本机,当做结点或终端」(2026-09-18 15:4x)
⑦ 边界自证(逐条打勾)
- ✓ ⛔ 未 commit / 未 push(HEAD 仍
09ce76f);⛔ 未 scp 任何文件;⛔ 未 build - ✓ ⛔ 未改
nft(47 仍 72 行)/nginx/bwrap/server.ts/content/** - ✓ ⛔ 未出现任何
0.0.0.0/0;⛔ 未索取 / 未硬编码任何云 AK/SK - ✓ ⛔ 未动
dshs*/dsh-*单元;⛔ 未改RELAY_FAILOVER_COOLDOWN_MS(命中 0) - ✓ 密钥本体 ⛔ 未读取(只取
stat大小 / 时间);两机密钥权限仍600
⑧ 指纹
- 本单 §8 前前缀(口径:
sed '/^## §8 回报格式/,$d' <本单> | md5sum):8925c2460810c6dc5cdbc075b6cdaed2(写入本小节后现算,与 §11.2 / §11.5 / §8.8-⑧ / §8.9-⑦ / §8.10-⑦ 逐字一致 ✅ —— §8 是截断点,故新增 §8.11 不改变它)。 - 参数表 §10 指纹:
6b37bfd506758d882d9f803678f85d23(现算)—— 本棒对参数表零改动(⛔ 未新增 §11 小节,因本棒未产生任何键值 / 口径变更)。
⑨ 下一棒
- ⏸ 阻塞中:等用户侧云控制台落地 ④ 的两条规则(或用户主动给凭据)。
- 落地后的第一动作 = 双向实测复测(当前基线:47→106 发 21 收 0;106→47 发 28 收 0)+ 探针
OBS-27/28复跑 + 参数表 §11 补记(含 §10 指纹现算)。 - ⚠️ 本棒 ⛔ 未登记接续棒(阻塞项需人操作,⛔ 无人值守重试无意义)。
§9 回头条件(一出现必须回头)
- 🔴 任何一棒都不许把
RELAY_FAILOVER_COOLDOWN_MS=0写进回滚 / 演练 / 夹具路径。 - 要改
server.ts的 DIAL / 白名单语义(而非只读取)⇒ 停下报告。 - 出现任何"对公网任意源开放"的入站规则 ⇒ 立刻停手回滚(违反用户拍板 ④)。
- 跨网 / 白名单拒绝退化成"静默返空" ⇒ 停下修(本线老病根)。
- 🔴
peer接线后块 id 未复算 ⇒ 判据不算通过。 - 🔴 家宽节点直连需求出现(= 乙-1 覆盖不到)⇒ 回到 乙-2 / 乙-3,后者需要用户提供云 AK/SK(⛔ 不许自行索取或硬编码凭据)。
- 打洞实现需要内核态 / 虚拟网卡 ⇒ 停下报告(与参数表 §8-① "不做虚拟网卡"冲突)。
- 打洞失败率导致中继容量超 45% 设计值 ⇒ 回头重算
RELAY_MAX_HOSTS(参数表 §5 口径)。
§10 未验证项(一个都不许编数)
| # | 项 | 状态 |
|---|---|---|
| 1 | 本系统的打洞成功率(⛔ 非"网络能不能打洞") | 待测 —— 需 S5 落地后测;HOLE_PUNCH_RATE_LOCAL 记的是可行性,⛔ 不是本系统成功率 |
| 2 | 直连带来的中继带宽节省比例 | 待测(需 S6 / S7) |
| 3 | 直连切换耗时 | 待测 |
| 4 | 家宽节点的公网出口 IP 变化频率(决定乙-2/乙-3 的紧迫度) | 待测 |
| 5 | 云安全组规则数量上限(能否容纳全部节点 IP) | 待测(需查云厂商配额) |
§11 指纹与状态
11.1 归档号
132(⚠️ 本单不带 04 孪生 —— 本线 交接单_*.md 一律无孪生;mkdir .lock-132 已原子占号)
11.2 指纹
- §8 前前缀(口径:
sed '/^## §8 回报格式/,$d' <本单> | md5sum)=8925c2460810c6dc5cdbc075b6cdaed2(落单时现算) - ⚠️ §8 回填后前缀不变、全文 md5 会变 ⇒ 全文 md5 不写死,权威取数 = 现算
11.3 状态
- 本单状态:待开工(P2 · S5–S7);🔴 权限影响评估已获用户拍板(§7,2026-09-18 12:22)
- P1 关系:⛔ 不重做 P1(一键加入 + 分组准入已收官,见
交接单_节点一键加入与分组准入_20260918.md§8.9) - P1 出口项归入本单:
src/web/routes/overlay-nodes.ts(管理面 API)连同 S5 一并做
11.4 与既有文档的关系
- 本单取代
交接单_节点一键加入与分组准入_20260918.md的 §6 P2 段(三行粗描述)作为开工依据;⚠️ 不改该单正文(§8 前前缀085e28b56a08aa1f01999bdb3e238911是不变量)。 覆盖网络_补遗与参考方案_20260916.md(WebRTC / ICE 参考)与本单 §4.2「照抄成熟做法」一致 —— ⛔ 不引入 WebRTC 依赖(§2-7)。
11.5 落单后实测
- §8 前前缀 =
8925c2460810c6dc5cdbc075b6cdaed2(落单后实测;⚠️ 本行在截断点之后 ⇒ 改它不影响该不变量) - 落单快照:全文 md5
127fe5204004cb9ea895b852a15dd143/239 行(2026-09-18 12:2x;⚠️ 本行写入后即失效 ⇒ 引用前现算)