mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
fix(relay-bench): judge 6to4 and NAT64-local by the embedded v4, refuse Teredo, scrub the pair line and sibling scripts
This commit is contained in:
@@ -2,8 +2,9 @@
|
||||
// sample count, spread rule, and Node fetch, and prints why each region passed or failed.
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import {
|
||||
classifyPublicHttpsOrigin,
|
||||
LIVE_ENV_VAR,
|
||||
classifyPublicHttpsOrigin,
|
||||
describeUntrustedText,
|
||||
parseArgs,
|
||||
requireBoundedInteger,
|
||||
requireDirector,
|
||||
@@ -112,7 +113,7 @@ async function main() {
|
||||
console.error(
|
||||
timedOut
|
||||
? `director ${director}/v1/regions did not answer within ${CATALOG_TIMEOUT_MS} ms`
|
||||
: `director ${director}/v1/regions failed: ${err.message}`
|
||||
: `director ${director}/v1/regions failed: ${describeUntrustedText(err.code ?? err.message)}`
|
||||
)
|
||||
process.exitCode = 1
|
||||
return
|
||||
|
||||
@@ -190,28 +190,39 @@ function ipv6ToBytes(host) {
|
||||
return bytes
|
||||
}
|
||||
|
||||
function v4At(bytes, offset) {
|
||||
return (
|
||||
((bytes[offset] << 24) >>> 0) +
|
||||
(bytes[offset + 1] << 16) +
|
||||
(bytes[offset + 2] << 8) +
|
||||
bytes[offset + 3]
|
||||
)
|
||||
}
|
||||
|
||||
function isPublicIpv6(bytes) {
|
||||
const leadingZeros = bytes.slice(0, 10).every((byte) => byte === 0)
|
||||
if (leadingZeros && bytes[10] === 0xff && bytes[11] === 0xff) {
|
||||
return isPublicIpv4(
|
||||
((bytes[12] << 24) >>> 0) + (bytes[13] << 16) + (bytes[14] << 8) + bytes[15]
|
||||
)
|
||||
return isPublicIpv4(v4At(bytes, 12))
|
||||
}
|
||||
if (leadingZeros && bytes[10] === 0 && bytes[11] === 0) {
|
||||
// Covers :: and ::1 as well as the deprecated v4-compatible form.
|
||||
return false
|
||||
}
|
||||
// NAT64 (64:ff9b::/96) reaches the embedded v4 address, so judge that address.
|
||||
// Transition prefixes that reach an embedded v4 address: judge that address.
|
||||
// NAT64 64:ff9b::/96 and 64:ff9b:1::/48 (v4 in the last 32 bits), 6to4 2002::/16
|
||||
// (v4 in bits 16-47). Teredo 2001:0::/32 embeds the client v4 inverted, so it is refused.
|
||||
const nat64 = bytes[0] === 0x00 && bytes[1] === 0x64 && bytes[2] === 0xff && bytes[3] === 0x9b
|
||||
if (
|
||||
bytes[0] === 0x00 &&
|
||||
bytes[1] === 0x64 &&
|
||||
bytes[2] === 0xff &&
|
||||
bytes[3] === 0x9b &&
|
||||
bytes.slice(4, 12).every((byte) => byte === 0)
|
||||
nat64 &&
|
||||
(bytes.slice(4, 12).every((byte) => byte === 0) || (bytes[4] === 0 && bytes[5] === 1))
|
||||
) {
|
||||
return isPublicIpv4(
|
||||
((bytes[12] << 24) >>> 0) + (bytes[13] << 16) + (bytes[14] << 8) + bytes[15]
|
||||
)
|
||||
return isPublicIpv4(v4At(bytes, 12))
|
||||
}
|
||||
if (bytes[0] === 0x20 && bytes[1] === 0x02) {
|
||||
return isPublicIpv4(v4At(bytes, 2))
|
||||
}
|
||||
if (bytes[0] === 0x20 && bytes[1] === 0x01 && bytes[2] === 0 && bytes[3] === 0) {
|
||||
return false
|
||||
}
|
||||
if ((bytes[0] & 0xfe) === 0xfc || bytes[0] === 0xff) {
|
||||
return false
|
||||
|
||||
@@ -168,6 +168,10 @@ describe('classifyPublicHttpsOrigin', () => {
|
||||
['not a url', 'not a URL'],
|
||||
['https://[64:ff9b::7f00:1]/', 'loopback, link-local, or private'],
|
||||
['https://[64:ff9b::a9fe:a9fe]/', 'loopback, link-local, or private'],
|
||||
['https://[64:ff9b:1::7f00:1]/', 'loopback, link-local, or private'],
|
||||
['https://[2002:7f00:1::]/', 'loopback, link-local, or private'],
|
||||
['https://[2002:c0a8:101::1]/', 'loopback, link-local, or private'],
|
||||
['https://[2001:0:4136:e378:8000:63bf:3fff:fdd2]/', 'loopback, link-local, or private'],
|
||||
['', 'missing origin']
|
||||
])('refuses %s', (value, reason) => {
|
||||
const verdict = classifyPublicHttpsOrigin(value)
|
||||
@@ -175,8 +179,11 @@ describe('classifyPublicHttpsOrigin', () => {
|
||||
expect(verdict.reason).toContain(reason)
|
||||
})
|
||||
|
||||
it('accepts a NAT64 address that embeds a public v4 address', () => {
|
||||
it('accepts transition addresses that embed a public v4 address', () => {
|
||||
expect(classifyPublicHttpsOrigin('https://[64:ff9b::808:808]/').ok).toBe(true)
|
||||
expect(classifyPublicHttpsOrigin('https://[2002:808:808::]/').ok).toBe(true)
|
||||
// A 2001: address outside the Teredo /32 is ordinary global unicast.
|
||||
expect(classifyPublicHttpsOrigin('https://[2001:db8::1]/').ok).toBe(true)
|
||||
})
|
||||
|
||||
it('never echoes control bytes from a refused value, since the desktop chose it', () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { performance } from 'node:perf_hooks'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import {
|
||||
LIVE_ENV_VAR,
|
||||
describeUntrustedText,
|
||||
parseArgs,
|
||||
requireBoundedInteger,
|
||||
requireDirector,
|
||||
@@ -46,7 +47,9 @@ async function timeResolve(director, relayHostId) {
|
||||
return {
|
||||
ms: Math.round(performance.now() - started),
|
||||
status: null,
|
||||
error: timedOut ? `timeout after ${RESOLVE_TIMEOUT_MS} ms` : err.message
|
||||
error: timedOut
|
||||
? `timeout after ${RESOLVE_TIMEOUT_MS} ms`
|
||||
: describeUntrustedText(err.code ?? err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +94,7 @@ function timeCellHello(cell, relayHostId) {
|
||||
})
|
||||
ws.on('message', (message) => done({ hello: message.toString().slice(0, 80) }))
|
||||
ws.on('close', (code, reason) => done({ close: code, reason: reason.toString() }))
|
||||
ws.on('error', (err) => done({ error: err.message }))
|
||||
ws.on('error', (err) => done({ error: describeUntrustedText(err.code ?? err.message) }))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ export function dialRelay({
|
||||
const msg = JSON.parse(text)
|
||||
if (msg.type !== 'e2ee_authenticated') {
|
||||
// Type only: the plaintext is the desktop's and would land in row.error.
|
||||
throw new Error(`auth rejected: desktop sent ${JSON.stringify(msg.type ?? null)}`)
|
||||
throw new Error(`auth rejected: desktop sent ${describeUntrustedText(msg.type)}`)
|
||||
}
|
||||
mark('e2eeAuthenticated')
|
||||
stage = 'ready'
|
||||
@@ -401,7 +401,9 @@ async function pair(pairingUrl, statePath) {
|
||||
const resumeToken = b64url(nacl.randomBytes(32))
|
||||
const resumeTokenHash = b64url(sha256(utf8(resumeToken)))
|
||||
const installReqId = `install-${b64url(nacl.randomBytes(12))}`
|
||||
console.log(`pair: dialing ${relay.cellUrl} host=${relay.relayHostId}`)
|
||||
// The offer is operator-pasted text: the vetted origin, not the raw URL, and the host id
|
||||
// through the same scrub as any other peer-chosen string.
|
||||
console.log(`pair: dialing ${verdict.origin} host=${describeUntrustedText(relay.relayHostId)}`)
|
||||
const dial = await dialRelay({
|
||||
cellUrl: relay.cellUrl,
|
||||
relayHostId: relay.relayHostId,
|
||||
|
||||
Reference in New Issue
Block a user