164 lines
4.9 KiB
JavaScript
164 lines
4.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createServer, connect } from "node:net";
|
|
import test from "node:test";
|
|
|
|
import { createDeviceEdgeRelayRuntime } from "../src/runtime.mjs";
|
|
|
|
test("baseline starts only loopback health and no device TCP listener", async () => {
|
|
const runtime = createDeviceEdgeRelayRuntime({ healthPort: 0 });
|
|
const addresses = await runtime.start();
|
|
try {
|
|
assert.equal(addresses.tcpAddress, null);
|
|
const response = await fetch(
|
|
`http://127.0.0.1:${addresses.healthAddress.port}/healthz`,
|
|
);
|
|
assert.equal(response.status, 200);
|
|
const body = await response.json();
|
|
assert.equal(body.ingress, "disabled");
|
|
assert.equal(body.protocolInspection, "disabled");
|
|
assert.equal(body.commandTransport, "disabled");
|
|
} finally {
|
|
await runtime.stop();
|
|
}
|
|
});
|
|
|
|
test("relay is transparent and never emits its own protocol bytes", async () => {
|
|
const upstream = await startEchoServer();
|
|
const runtime = createDeviceEdgeRelayRuntime({
|
|
healthPort: 0,
|
|
ingressEnabled: true,
|
|
tcpHost: "0.0.0.0",
|
|
tcpPort: 0,
|
|
upstreamHost: "127.0.0.1",
|
|
upstreamPort: upstream.port,
|
|
resolveRemoteAddress: () => "8.8.8.8",
|
|
});
|
|
const addresses = await runtime.start();
|
|
try {
|
|
const response = await sendAndCollect(
|
|
addresses.tcpAddress.port,
|
|
Buffer.from("ff230102030405060708", "hex"),
|
|
);
|
|
assert.equal(response.toString("hex"), "ff230102030405060708");
|
|
assert.equal(runtime.status().totalForwarded, 1);
|
|
assert.equal(runtime.status().commandTransport, "disabled");
|
|
} finally {
|
|
await runtime.stop();
|
|
await closeServer(upstream.server);
|
|
}
|
|
});
|
|
|
|
test("enabled ingress rejects a non-public source before opening upstream", async () => {
|
|
const upstream = await startEchoServer();
|
|
const runtime = createDeviceEdgeRelayRuntime({
|
|
healthPort: 0,
|
|
ingressEnabled: true,
|
|
tcpHost: "0.0.0.0",
|
|
tcpPort: 0,
|
|
upstreamHost: "127.0.0.1",
|
|
upstreamPort: upstream.port,
|
|
resolveRemoteAddress: () => "127.0.0.1",
|
|
});
|
|
const addresses = await runtime.start();
|
|
try {
|
|
const response = await sendAndCollect(
|
|
addresses.tcpAddress.port,
|
|
Buffer.from("denied"),
|
|
);
|
|
assert.equal(response.length, 0);
|
|
assert.equal(runtime.status().totalAccepted, 0);
|
|
assert.equal(runtime.status().totalForwarded, 0);
|
|
assert.equal(runtime.status().sourceAdmission, "public-ipv4-only");
|
|
} finally {
|
|
await runtime.stop();
|
|
await closeServer(upstream.server);
|
|
}
|
|
});
|
|
|
|
test("relay terminates a byte stream that exceeds its per-direction budget", async () => {
|
|
const upstream = await startEchoServer();
|
|
const runtime = createDeviceEdgeRelayRuntime({
|
|
healthPort: 0,
|
|
ingressEnabled: true,
|
|
tcpHost: "0.0.0.0",
|
|
tcpPort: 0,
|
|
upstreamHost: "127.0.0.1",
|
|
upstreamPort: upstream.port,
|
|
resolveRemoteAddress: () => "8.8.8.8",
|
|
maxBytesPerDirection: 1024,
|
|
});
|
|
const addresses = await runtime.start();
|
|
try {
|
|
const response = await sendAndCollect(
|
|
addresses.tcpAddress.port,
|
|
Buffer.alloc(1025, 0x5d),
|
|
);
|
|
assert.ok(response.length <= 1024);
|
|
assert.equal(runtime.status().totalForwarded, 1);
|
|
assert.ok(runtime.status().totalRejected >= 1);
|
|
} finally {
|
|
await runtime.stop();
|
|
await closeServer(upstream.server);
|
|
}
|
|
});
|
|
|
|
test("production ingress cannot opt out of public IPv4 admission", () => {
|
|
assert.throws(
|
|
() => createDeviceEdgeRelayRuntime({
|
|
ingressEnabled: true,
|
|
tcpHost: "0.0.0.0",
|
|
upstreamHost: "device-edge-backhaul",
|
|
upstreamPort: 19921,
|
|
sourcePolicy: "any",
|
|
}),
|
|
/device_edge_relay_ingress_source_policy_invalid/,
|
|
);
|
|
});
|
|
|
|
test("enabled relay requires a concrete private upstream", () => {
|
|
assert.throws(
|
|
() => createDeviceEdgeRelayRuntime({
|
|
ingressEnabled: true,
|
|
tcpHost: "0.0.0.0",
|
|
upstreamPort: 19921,
|
|
}),
|
|
/device_edge_relay_upstream_host_invalid/,
|
|
);
|
|
});
|
|
|
|
test("baseline rejects a non-loopback device binding", () => {
|
|
assert.throws(
|
|
() => createDeviceEdgeRelayRuntime({ tcpHost: "0.0.0.0" }),
|
|
/device_edge_relay_baseline_loopback_only/,
|
|
);
|
|
});
|
|
|
|
function startEchoServer() {
|
|
const server = createServer((socket) => socket.pipe(socket));
|
|
return new Promise((resolve, reject) => {
|
|
server.once("error", reject);
|
|
server.listen(0, "127.0.0.1", () => {
|
|
server.off("error", reject);
|
|
resolve({ server, port: server.address().port });
|
|
});
|
|
});
|
|
}
|
|
|
|
function sendAndCollect(port, payload) {
|
|
return new Promise((resolve) => {
|
|
const chunks = [];
|
|
const socket = connect({ host: "127.0.0.1", port }, () => {
|
|
socket.end(payload);
|
|
});
|
|
socket.on("data", (chunk) => chunks.push(chunk));
|
|
socket.on("close", () => resolve(Buffer.concat(chunks)));
|
|
socket.on("error", () => {});
|
|
});
|
|
}
|
|
|
|
function closeServer(server) {
|
|
return new Promise((resolve, reject) => {
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
|
});
|
|
}
|