Files
dsh_shenxian/test/overlay-network.test.mjs
T
admin 452924d89c feat(config): 涉密内容外置到配置目录(档案 140)
把散落在代码里的真实部署值统一收进 config/,代码改为引用配置,
使仓库副本/开源导出不再带出生产域名、IP、内网路径与凭据。

新增 config/:platform.env.example(模板)· load.sh(shell 加载器)·
index.cjs(node 加载器)· README.md(键一览与优先级)。
真实值放 config/platform.env —— 已 .gitignore 排除,不入库、不进导出。

TS 侧新增 src/platform-paths.ts 作部署路径的唯一解析处(零副作用):
platformDir/stateDir/backupDir/artifactDir/installDir/scriptPath。
config.ts 接入这些字段;内置中继种子由生产 URL 改为空(改由
DSHS_OVERLAY_BOOTSTRAP_SEEDS 提供)。修掉 5 处硬编码绝对路径,
src/** 注释中性化 116 行/53 文件。

scripts/** 36 个内部运维脚本:真令牌/PG 口令/隧道目标/主机号/路径
一律改从配置取;web/wake.html 的注册域白名单改为运行时从
location.hostname 推导;test/** 夹具 119 行/13 文件改 RFC 2606/5737
保留值,并把「内置种子必须为空」固化为回归断言。

取证:tsc 0 错;npm test 373/375(唯一失败 lease 属既有);
全仓扫描(大小写不敏感)代码面涉密标识 = 0;已部署 47 并零回归
(/opt/dsh/* 未搬家,/var/lib/dshs/platform 未被误建)。
2026-09-19 15:12:19 +08:00

528 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 覆盖网络 · **② 网抽象(P0-1)** 单测 —— `network_id` 是不是**结构性隔离**。
*
* ## 这个文件要回答的唯一问题
* 「不同网络的节点之间**不能互相到达**」这件事,是**结构上做不到**,还是"靠某条 ACL 记得拦"?
* —— 所以本文件的验收标准不是"配置写对了",而是:
* 1. **跨网拨号被 relay 拒**,且拒绝点在**服务端**(有日志、有计数);
* ⚠️ P0-3 起**对外没有专属拒码**:跨网一律回 `target-offline`(与"本网无此节点"逐字同形),
* 区分只留在服务端日志 —— 否则"它在别张网"本身就是一份**可探测的对端清单**。
* 2. **同名 hostId 在两张网里互不干扰**(会话表按 `<network>/<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,
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'])]])), /自相矛盾/)
// ⑥ 客户端侧同一条口径:非法 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)
})