/** * 覆盖网络 · **② 网抽象(P0-1)** 单测 —— `network_id` 是不是**结构性隔离**。 * * ## 这个文件要回答的唯一问题 * 「不同网络的节点之间**不能互相到达**」这件事,是**结构上做不到**,还是"靠某条 ACL 记得拦"? * —— 所以本文件的验收标准不是"配置写对了",而是: * 1. **跨网拨号被 relay 拒**,且拒绝点在**服务端**(有日志、有计数); * ⚠️ P0-3 起**对外没有专属拒码**:跨网一律回 `target-offline`(与"本网无此节点"逐字同形), * 区分只留在服务端日志 —— 否则"它在别张网"本身就是一份**可探测的对端清单**。 * 2. **同名 hostId 在两张网里互不干扰**(会话表按 `/` 索引,不是扁平 `hostId`); * 3. **旧形态照旧可用**(旧客户端不声明 `network`、旧 drop-in 的扁平白名单 ⇒ 全部落在 `ops`, * 现网 47 / 106 一行配置不改也不受影响)—— "不破坏现网"与"真落地"必须同时成立。 * * 运行:`node --test test/overlay-network.test.mjs`(Node ≥ 22;测的是 `lib/` 产物,先 `npm run build`)。 * * @module test/overlay-network */ import assert from 'node:assert/strict' import { createHmac, randomBytes } from 'node:crypto' import { createServer as createTcpServer, connect } from 'node:net' import { test } from 'node:test' import { MUX, OPS_NETWORK, RelayClient, RelayServer, assertNetworkId, decodeMux, describeDialers, encodeJsonFrame, isAllowedDialer, logicalName, normalizeDialers, parseLogicalName, } from '../lib/net/relay/index.js' const BASE = 46000 const SPAN = 200 const PATH = '/dshs-relay' const U_TEST = 'u:test-network' /* ─────────── 小工具(与 relay.test.mjs 同款,本文件自足) ─────────── */ async function listenInRange(server, lo, hi) { for (let p = lo; p < hi; p++) { const ok = await new Promise((resolve) => { const onErr = () => { server.off('error', onErr) resolve(false) } server.once('error', onErr) server.listen(p, '127.0.0.1', () => { server.off('error', onErr) resolve(true) }) }) if (ok) return server.address().port } throw new Error(`no free port in ${lo}..${hi}`) } async function waitFor(cond, ms = 5000) { const t0 = Date.now() while (Date.now() - t0 < ms) { if (cond()) return true await new Promise((r) => setTimeout(r, 20)) } return cond() } /** 回显服务,但**带一个前缀** —— 用来分辨"这条流到底落在哪张网的那台机上"。 */ function taggedEcho(tag) { const server = createTcpServer((s) => s.on('data', (c) => s.write(`${tag}:${c.toString('utf8')}`))) return server } /** 拨号流往返:写进去、读回(前缀长度显式传入,避免"读到一半就认为对了")。 */ function dialRoundTrip(duplex, text, prefixLen = 0, ms = 5000) { return new Promise((resolve, reject) => { let got = '' const timer = setTimeout(() => { duplex.destroy() reject(new Error(`dial roundTrip timeout (got ${got.length}/${text.length + prefixLen})`)) }, ms) duplex.on('data', (chunk) => { got += chunk.toString('utf8') if (got.length >= text.length + prefixLen) { clearTimeout(timer) resolve(got) } }) duplex.on('error', (err) => { clearTimeout(timer) reject(err) }) duplex.write(text) }) } /** * 手工发一帧 `HELLO`(构造"正常 client 做不到"的输入:**不带** `network` 的旧客户端、非法 `network`)。 * * `network === undefined` ⇒ 字段**根本不出现**(= 现网 47/106 上正在跑的旧客户端)。 */ async function rawHello(wsUrl, hostId, secret, portsCsv, nonce, network) { const ws = new WebSocket(wsUrl) ws.binaryType = 'arraybuffer' await new Promise((resolve, reject) => { ws.addEventListener('open', resolve, { once: true }) ws.addEventListener('error', () => reject(new Error('ws open failed')), { once: true }) }) const ts = Date.now() // ⚠️ 与 client.ts 逐字同源的 MAC 输入(**不含 network**)—— 这条"一字不改"本身就是被测对象之一: // 旧客户端算出来的 MAC 必须仍被接受。 const mac = createHmac('sha256', Buffer.from(secret, 'hex')).update(`${hostId}|${ts}|${nonce}|${portsCsv}`).digest('hex') const payload = { v: 1, hostId, ts, nonce, portsCsv, mac } if (network !== undefined) payload.network = network ws.send(encodeJsonFrame(MUX.HELLO, 0, payload)) const frame = await new Promise((resolve) => { const timer = setTimeout(() => resolve(undefined), 3000) ws.addEventListener('message', (ev) => { clearTimeout(timer) resolve(decodeMux(Buffer.from(ev.data))) }) ws.addEventListener('close', () => { clearTimeout(timer) resolve(undefined) }) }) return { ws, frame } } /** 在一个**已建立**的连接上手工发一帧、等回帧(用于构造"正常 client 不会做"的请求)。 */ async function rawSend(ws, frameBuf) { const p = new Promise((resolve) => { const timer = setTimeout(() => resolve(undefined), 3000) ws.addEventListener('message', (ev) => { clearTimeout(timer) resolve(decodeMux(Buffer.from(ev.data))) }) ws.addEventListener('close', () => { clearTimeout(timer) resolve(undefined) }) }) ws.send(frameBuf) return p } /* ─────────── U1:纯函数(逻辑名与白名单解析) ─────────── */ test('U1 逻辑名唯一入口:旧形态 / 新形态 / `u:<租户>` 里的冒号 / 非法值一律抛', () => { // ① 规范形态 assert.equal(logicalName('u:5', 'w-1'), 'u:5/w-1') assert.deepEqual(parseLogicalName('ops/manager'), { network: 'ops', hostId: 'manager' }) assert.deepEqual(parseLogicalName('u:5/w-2'), { network: 'u:5', hostId: 'w-2' }) // ② 兼容形态:`network:hostId`(运维习惯写法) assert.deepEqual(parseLogicalName('ops:manager'), { network: 'ops', hostId: 'manager' }) // 🔴 必须**从右**切:`u:5` 里的冒号属于网络 id,从左切会得到 network='u'(不存在那张网 ⇒ 静默拒绝) assert.deepEqual(parseLogicalName('u:5:manager'), { network: 'u:5', hostId: 'manager' }) assert.deepEqual(parseLogicalName('u:test-network:pc-1'), { network: 'u:test-network', hostId: 'pc-1' }) // ③ 旧形态(R5 时代的扁平 hostId)⇒ 落在运维网,**旧配置照旧可用** assert.deepEqual(parseLogicalName('manager'), { network: OPS_NETWORK, hostId: 'manager' }) assert.deepEqual(parseLogicalName('w-2'), { network: OPS_NETWORK, hostId: 'w-2' }) // ④ 非法 ⇒ **抛**(配置错就炸,不静默变成"谁也没匹配上") assert.throws(() => parseLogicalName('u:5'), /网络段/) assert.throws(() => parseLogicalName('bad net:manager'), /网络段/) assert.throws(() => parseLogicalName('ops/'), /逻辑名/) assert.throws(() => assertNetworkId('Ops'), /非法/) assert.throws(() => assertNetworkId(''), /非法/) // ⑤ 白名单归一化:扁平 `Set` = 全在 ops;`Map` 按网络分桶 assert.deepEqual([...normalizeDialers(new Set(['manager'])).get(OPS_NETWORK)], ['manager']) const m = normalizeDialers(new Map([[U_TEST, new Set(['d1'])]])) assert.equal(m.has(OPS_NETWORK), false, '没列到的网络不该凭空出现') assert.deepEqual([...m.get(U_TEST)], ['d1']) // 混写(桶里再带前缀)不会静默失配;非法网络段在装载时就炸 const mixed = normalizeDialers(new Set(['ops:manager', 'u:5:d1'])) assert.deepEqual(describeDialers(mixed), ['manager', 'u:5/d1'], 'ops 省略前缀、其它网写全逻辑名') assert.throws(() => normalizeDialers(new Set(['BAD net:d1'])), /网络段/) // 分桶形态:桶内的**裸 hostId 属于本桶那张网**(不是 ops)—— 这一条错了会变成"配对了却静默拒绝" assert.deepEqual([...normalizeDialers(new Map([['u:5', new Set(['d1'])]])).get('u:5')], ['d1']) // 桶内带了前缀就必须与本桶一致,否则说明配置自相矛盾 ⇒ 装载时就炸 assert.throws(() => normalizeDialers(new Map([['u:5', new Set(['ops:d1'])]])), /自相矛盾/) // ⑤b 网级通配(序㊻ · S2):`u:<租户>/*` = 该网整网可拨 const wildCards = new Map([['u:5', new Set(['*'])]]) assert.equal(isAllowedDialer(normalizeDialers(wildCards), 'u:5', 'd-brand-new'), true) assert.equal(isAllowedDialer(normalizeDialers(new Set(['u:5/*'])), 'u:5', 'd-brand-new'), true) assert.equal(isAllowedDialer(normalizeDialers(new Set(['u:5:*'])), 'u:5', 'd-brand-new'), true) assert.deepEqual(describeDialers(normalizeDialers(new Set(['u:5/*']))), ['u:5/*']) // ⛔ 通配不外溢:别的网照旧默认拒绝(结构性隔离不因它松动) assert.equal(isAllowedDialer(normalizeDialers(new Set(['u:5/*'])), OPS_NETWORK, 'manager'), false) assert.equal(isAllowedDialer(normalizeDialers(new Set(['u:5/*'])), 'u:6', 'd1'), false) // ⛔ 只准用在租户网:ops / 命名网上写通配 ⇒ 装载时就炸(否则会把声明端口的 worker 一并拒掉) assert.throws(() => normalizeDialers(new Set(['ops/*'])), /租户网/) assert.throws(() => normalizeDialers(new Set(['*'])), /租户网/) assert.throws(() => normalizeDialers(new Set(['named-net/*'])), /租户网/) assert.throws(() => normalizeDialers(new Map([['ops', new Set(['*'])]])), /租户网/) // `*` 不是合法 hostId ⇒ 它**只**在白名单这一条通道上有意义,普通逻辑名照旧拒 assert.throws(() => parseLogicalName('u:5/*'), /hostId/) // ⑥ 客户端侧同一条口径:非法 networkId ⇒ 构造时抛(不静默回落 ops) assert.throws( () => new RelayClient({ url: 'ws://127.0.0.1:1' + PATH, hostId: 'x', secret: randomBytes(32).toString('hex'), ports: [BASE], networkId: 'BAD' }), /networkId/, ) }) /* ─────────── U2:现网不退化(旧客户端 + 旧白名单写法) ─────────── */ test('U2 旧客户端(不声明 network)+ 旧扁平白名单 ⇒ 一切照旧,全部落在 ops', async (t) => { const wSecret = randomBytes(32).toString('hex') const w47Secret = randomBytes(32).toString('hex') const mSecret = randomBytes(32).toString('hex') const echo = taggedEcho('ops') const servePort = await listenInRange(echo, BASE + 10, BASE + SPAN - 1) const legacyPort = BASE + 12 const srv = new RelayServer({ port: 0, keys: new Map([ ['w-2', wSecret], ['w-1', w47Secret], ['manager', mSecret], ]), instancePortBase: BASE, instancePortSpan: SPAN, // 🔴 旧写法:扁平 `Set`(= 现网 `DSHS_RELAY_DIALERS="manager"` 的形态) dialers: new Set(['manager']), log: () => {}, }) await srv.start() const url = `ws://127.0.0.1:${srv.boundPort}${PATH}` t.after(async () => { echo.close() await srv.stop() }) // ① **旧客户端握手**:HELLO 里根本没有 network 字段(= 现网 47/106 上正在跑的那一版) const { ws: rawWs, frame: ack } = await rawHello(url, 'w-2', wSecret, String(legacyPort), randomBytes(16).toString('hex')) t.after(() => rawWs.close()) assert.ok(ack !== undefined, '旧客户端必须能注册(否则这次改动就是硬断)') assert.equal(ack.type, MUX.HELLO_ACK, '旧客户端应拿到 HELLO_ACK') const parsed = JSON.parse(ack.payload.toString('utf8')) assert.equal(parsed.network, OPS_NETWORK, 'HELLO_ACK 应回显服务端认定的网 = ops') assert.equal(parsed.name, logicalName(OPS_NETWORK, 'w-2')) assert.ok(await waitFor(() => srv.isOnline('w-2')), '默认网络(ops)里应看到它') assert.ok( await waitFor(() => srv.localPortOf('w-2', legacyPort) !== undefined), '旧客户端照样拿到回环落点(R1–R4 的行为不变)', ) // ② 不传 `networkId` 的真 client = ops(默认值即现网事实);旧扁平白名单照旧拨得动 const worker = new RelayClient({ url, hostId: 'w-1', secret: w47Secret, ports: [servePort], log: () => {} }) const dialer = new RelayClient({ url, hostId: 'manager', secret: mSecret, ports: [], dialer: true, log: () => {} }) worker.start() dialer.start() t.after(() => { worker.stop() dialer.stop() }) assert.ok(await waitFor(() => worker.status().state === 'up'), 'worker 未注册') assert.equal(worker.status().network, OPS_NETWORK, '不声明网络 ⇒ 默认 ops(不是空、不是 undefined)') assert.ok(await waitFor(() => dialer.status().state === 'up'), '拨号方未注册') assert.deepEqual(srv.status().dialers, ['manager'], '/status 的 dialers 仍按旧写法展示(不破坏既有消费者)') const duplex = await dialer.openStream('w-1', servePort) assert.equal(await dialRoundTrip(duplex, 'legacy-ok', 'ops:'.length), 'ops:legacy-ok', '字节要真的过去') duplex.destroy() }) /* ─────────── U3:跨网隔离是**结构性**的(拒绝点在 relay) ─────────── */ test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-2 ⇒ relay 拒,且**不泄露**目标在哪张网', async (t) => { const wSecret = randomBytes(32).toString('hex') const opsSecret = randomBytes(32).toString('hex') const foreignSecret = randomBytes(32).toString('hex') const logs = [] const echo = taggedEcho('ops') const workerPort = await listenInRange(echo, BASE + 20, BASE + SPAN - 1) const srv = new RelayServer({ port: 0, keys: new Map([ ['w-2', wSecret], ['manager', opsSecret], // 序③:`dt` 真正属于 U_TEST(密钥表说了算)⇒ 它能进自己的网,然后在 **DIAL 那一步** // 被跨网判据挡住 —— 这才是本用例要测的那道门,而不是『压根没登记』那道。 [logicalName(U_TEST, 'dt'), foreignSecret], ]), instancePortBase: BASE, instancePortSpan: SPAN, /** * 两张网**各自都有合法的拨号方**(不是"没配白名单"那种低级情形)—— * 这样才证明隔离来自**网络维度本身**,而不是来自"忘了加白名单"。 */ dialers: new Map([ [OPS_NETWORK, new Set(['manager'])], [U_TEST, new Set(['dt'])], ]), log: (line) => logs.push(line), }) await srv.start() const url = `ws://127.0.0.1:${srv.boundPort}${PATH}` const worker = new RelayClient({ url, hostId: 'w-2', secret: wSecret, ports: [workerPort], log: () => {} }) const foreign = new RelayClient({ url, hostId: 'dt', secret: foreignSecret, ports: [], dialer: true, networkId: U_TEST, log: (line) => logs.push(`[client] ${line}`), }) worker.start() foreign.start() t.after(async () => { worker.stop() foreign.stop() echo.close() await srv.stop() }) const opsUp = await waitFor(() => srv.isOnline('w-2')) assert.ok(opsUp, 'ops 侧 worker 未注册') const foreignUp = await waitFor(() => srv.isOnline('dt', U_TEST)) assert.ok(foreignUp, `别网拨号方未注册;最近日志:${logs.slice(-10).join(' | ')}`) // 网维度视图必须可断言(Step 3「看不到」那一半的落地基础) const nets = srv.status().networks assert.deepEqual( nets.find((n) => n.network === OPS_NETWORK)?.sessions, ['w-2'], 'ops 网里应只有 w-2', ) assert.deepEqual(nets.find((n) => n.network === U_TEST)?.sessions, ['dt'], '别张网里应只有 dt') // 🔴 核心判据 ①:**拨不动** const before = srv.status().counters.refused let foreignMsg = '' await assert.rejects( () => foreign.openStream('w-2', workerPort), (err) => { foreignMsg = err instanceof Error ? err.message : String(err) return true }, '跨网必须被拒', ) // 🔴 核心判据 ②:**看不到** —— 对外与"本网没有这个节点"逐字同形(P0-3 · D4 不下发对端清单)。 // 若这里回 `dialer-not-in-network` / `targetNetwork`,拨号方拿一串猜测的 hostId 打过来 // 就能把别张网的成员**枚举出来**(判据「看不到」当场失效)。 assert.match(foreignMsg, /target-offline/, `对外必须是 target-offline,实测:${foreignMsg}`) assert.equal(foreignMsg.includes(U_TEST), false, `响应里不得出现任何网络名,实测:${foreignMsg}`) assert.equal( foreignMsg.includes('dialer-not-in-network'), false, `对外不得有"跨网"专属拒码,实测:${foreignMsg}`, ) // 但**服务端**必须留得下区分(否则运维分不清"配错网"与"节点离线") assert.ok(srv.status().counters.refused > before, '拒绝必须在 relay 侧计数(本机就得留痕)') assert.ok( logs.some((l) => l.includes('dialer-not-in-network')), `relay 日志必须记下跨网拒绝(实测日志:${logs.filter((l) => l.includes('DIAL')).join(' | ')})`, ) // 目标侧没有任何流被建立(不是"建了再断") assert.equal( srv.status().endpoints.find((e) => e.hostId === 'w-2' && e.network === OPS_NETWORK && e.port === workerPort)?.streams, 0, '被拒的跨网拨号不得在目标侧留下流', ) assert.equal(foreign.status().dialStreams, 0, '被拒后拨号方也不该留悬挂流') // 反证:**同网**照旧全通 ⇒ 隔离挡的是"跨网",不是"拨号"本身 const ops = new RelayClient({ url, hostId: 'manager', secret: opsSecret, ports: [], dialer: true, log: () => {} }) ops.start() t.after(() => ops.stop()) assert.ok(await waitFor(() => ops.status().state === 'up'), 'ops 拨号方未注册') const duplex = await ops.openStream('w-2', workerPort) assert.equal(await dialRoundTrip(duplex, 'same-net', 'ops:'.length), 'ops:same-net') duplex.destroy() }) /* ─────────── U4 / U5:未列白名单的网 & 跨网同名 hostId ─────────── */ test('U4 没列进白名单的网:连"拨号方"这个身份都拿不到(no-ports,默认拒绝)', async (t) => { const s1 = randomBytes(32).toString('hex') const srv = new RelayServer({ port: 0, // 序③:`dt` 登记在 U_TEST ⇒ 成员资格这一关**过得去**,于是拒绝来自**白名单**那一道门 //(`no-ports`)。不这么登记的话,成员资格会先一步拒,本用例就测不到它原本要测的东西。 keys: new Map([[logicalName(U_TEST, 'dt'), s1]]), instancePortBase: BASE, instancePortSpan: SPAN, dialers: new Map([[OPS_NETWORK, new Set(['manager'])]]), // 只有 ops 有拨号方 log: () => {}, }) await srv.start() const url = `ws://127.0.0.1:${srv.boundPort}${PATH}` t.after(async () => { await srv.stop() }) /** * R1 的**默认拒绝**姿态决定了一件好事:拨号方的形态是"**端口表为空**",而空端口表只在 * 该 hostId 命中**本网**白名单时才被接受 ⇒ **白名单外的网络连"拨号方身份"都拿不到** * (比"拿到了身份但拨不动"更早一道门)。这比 U3 那道门更靠前,两条都要能回归。 */ const before = srv.status().counters.authFailed const { ws, frame } = await rawHello(url, 'dt', s1, '', randomBytes(16).toString('hex'), U_TEST) t.after(() => ws.close()) assert.ok(frame !== undefined, '必须得到结构化拒绝') assert.equal(frame.type, MUX.HELLO_ERR) assert.equal(JSON.parse(frame.payload.toString('utf8')).reason, 'no-ports', '白名单外的网不该拿到拨号方身份') assert.ok(srv.status().counters.authFailed > before) assert.equal(srv.status().sessions.length, 0) // 对照:同一个 hostId 只要落进 `ops` 桶(且声明 ops),立刻拿到 `dialer: true` // ⇒ 说明拒绝来自**网络归属**,不是来自"这个 hostId 本身没被信任"。 const srv2 = new RelayServer({ port: 0, keys: new Map([ ['dt', s1], ['w2', s1], ]), instancePortBase: BASE, instancePortSpan: SPAN, dialers: new Map([[OPS_NETWORK, new Set(['dt'])]]), log: () => {}, }) await srv2.start() t.after(async () => { await srv2.stop() }) const ok = await rawHello(`ws://127.0.0.1:${srv2.boundPort}${PATH}`, 'dt', s1, '', randomBytes(16).toString('hex'), OPS_NETWORK) t.after(() => ok.ws.close()) assert.equal(ok.frame?.type, MUX.HELLO_ACK) assert.equal(JSON.parse(ok.frame.payload.toString('utf8')).dialer, true) /** * ② `not-a-dialer` 这条门(R5 就有的默认拒绝)不能被这次改动改坏: * 一个**已注册的普通 worker**(有端口、不在拨号桶里)手工发 `DIAL` ⇒ relay 必须回拒。 */ const wPort = BASE + 95 const w2 = await rawHello(`ws://127.0.0.1:${srv2.boundPort}${PATH}`, 'w2', s1, String(wPort), randomBytes(16).toString('hex'), OPS_NETWORK) t.after(() => w2.ws.close()) assert.equal(w2.frame?.type, MUX.HELLO_ACK, 'worker 形态(声明端口)应能注册') const ack = await rawSend(w2.ws, encodeJsonFrame(MUX.DIAL, 1, { target: 'dt', port: wPort })) assert.equal(ack?.type, MUX.DIAL_ACK, 'DIAL 必须被应答(不能静默)') const ackBody = JSON.parse(ack.payload.toString('utf8')) assert.equal(ackBody.ok, false) assert.equal(ackBody.error, 'not-a-dialer', '不在本网拨号桶里的会话不得发起 DIAL') }) test('U5 跨网同名 hostId 互不干扰:各拨各的(会话表按逻辑名索引)', async (t) => { const wSecret = randomBytes(32).toString('hex') const dOps = randomBytes(32).toString('hex') const dUser = randomBytes(32).toString('hex') const opsEcho = taggedEcho('ops') const userEcho = taggedEcho('user') const opsPort = await listenInRange(opsEcho, BASE + 40, BASE + 60) const userPort = await listenInRange(userEcho, BASE + 61, BASE + 80) assert.notEqual(opsPort, userPort) const srv = new RelayServer({ port: 0, // 序③:键 = **逻辑名** ⇒ 同一台机器名可以**同时**出现在两张网里,各自是一条独立登记项。 //(用裸 hostId 当键做不到这件事:`w-1` 只能属于一张网 ⇒ 第二张网就注册不进来了。) keys: new Map([ [logicalName(OPS_NETWORK, 'w-1'), wSecret], [logicalName('u:x', 'w-1'), wSecret], [logicalName(OPS_NETWORK, 'm-ops'), dOps], [logicalName('u:x', 'd-user'), dUser], ]), instancePortBase: BASE, instancePortSpan: SPAN, dialers: new Map([ [OPS_NETWORK, new Set(['m-ops'])], ['u:x', new Set(['d-user'])], ]), log: () => {}, }) await srv.start() const url = `ws://127.0.0.1:${srv.boundPort}${PATH}` const wOps = new RelayClient({ url, hostId: 'w-1', secret: wSecret, ports: [opsPort], networkId: OPS_NETWORK, log: () => {} }) const wUser = new RelayClient({ url, hostId: 'w-1', secret: wSecret, ports: [userPort], networkId: 'u:x', log: () => {} }) const dOpsC = new RelayClient({ url, hostId: 'm-ops', secret: dOps, ports: [], dialer: true, log: () => {} }) const dUserC = new RelayClient({ url, hostId: 'd-user', secret: dUser, ports: [], dialer: true, networkId: 'u:x', log: () => {} }) for (const c of [wOps, wUser, dOpsC, dUserC]) c.start() t.after(async () => { for (const c of [wOps, wUser, dOpsC, dUserC]) c.stop() opsEcho.close() userEcho.close() await srv.stop() }) assert.ok(await waitFor(() => srv.isOnline('w-1', OPS_NETWORK)), 'ops 侧 w-1 未注册') assert.ok(await waitFor(() => srv.isOnline('w-1', 'u:x')), 'u:x 侧 w-1 未注册') const w1Names = srv.status() .sessions.filter((s) => s.hostId === 'w-1') .map((s) => s.name) .sort() assert.deepEqual(w1Names, ['ops/w-1', 'u:x/w-1'], '两个同名节点必须各自在册(扁平命名空间会只剩一个)') // 两张网的拨号方各拨**自己网里**的 w-1 ⇒ 各自落到自己那张网的回显服务(前缀不同 ⇒ 不可能看错) const dOpsStream = await dOpsC.openStream('w-1', opsPort) assert.equal(await dialRoundTrip(dOpsStream, 'A', 'ops:'.length), 'ops:A') dOpsStream.destroy() const dUserStream = await dUserC.openStream('w-1', userPort) assert.equal(await dialRoundTrip(dUserStream, 'B', 'user:'.length), 'user:B') dUserStream.destroy() }) /* ─────────── U6:非法 network ⇒ 失败关闭 ─────────── */ test('U6 非法 network 声明 ⇒ bad-network 失败关闭(不建会话、不静默当 ops)', async (t) => { const secret = randomBytes(32).toString('hex') const srv = new RelayServer({ port: 0, keys: new Map([['w-x', secret]]), instancePortBase: BASE, instancePortSpan: SPAN, log: () => {}, }) await srv.start() const url = `ws://127.0.0.1:${srv.boundPort}${PATH}` t.after(async () => { await srv.stop() }) const before = srv.status().counters.authFailed const { ws, frame } = await rawHello(url, 'w-x', secret, String(BASE + 90), randomBytes(16).toString('hex'), 'bad net!') t.after(() => ws.close()) assert.ok(frame !== undefined, '非法网必须得到**结构化拒绝**,而不是静默断开') assert.equal(frame.type, MUX.HELLO_ERR, '应回 HELLO_ERR') assert.equal(JSON.parse(frame.payload.toString('utf8')).reason, 'bad-network') assert.ok(srv.status().counters.authFailed > before, '拒绝要计数') assert.equal(srv.status().sessions.length, 0, '非法网不得建会话(更不得落到 ops)') assert.equal(srv.localPortOf('w-x', BASE + 90), undefined, '被拒的节点不得留下任何回环监听') }) /* ─────────── 说明:本文件不覆盖的部分 ─────────── */ test('U7 本文件的范围声明(Step 1 只做 P0-1,别在这里顺手做后面两步)', () => { // Step 2(引导三级链 / `/dshs-overlay/bootstrap`)与 Step 3(名字解析对外接口)见后续步骤, // ⛔ 不要在本步追加 —— 每步单独可回滚(交接单 §4)。 assert.ok(true) })