Files
OmniRoute/tests/unit/socks-connect-timeout-e2e.test.ts
Dizzle b7102140d5 fix(socks): forward Agent.connectTimeout to SocksClient and TLS, unify family null (#11842)
Fixes a SOCKS proxy timeout bypass: Agent.connectTimeout now reaches both the SocksClient.createConnection handshake and the TLS buildConnector phases (previously a stalled/blackholed SOCKS connection could hang past the configured budget), and the fetch-socks family===null path is unified onto createSocksDispatcherWithFamily. Verified against a faux RFC 1928 SOCKS server exercising both pre-grant and post-grant stalls. 6/6 focused tests passing. Thanks!
2026-08-28 12:37:32 -03:00

66 lines
2.9 KiB
TypeScript

import { describe, it, afterEach } from "node:test";
import assert from "node:assert/strict";
import net from "node:net";
import { fetch } from "undici";
import { createSocksDispatcherWithFamily } from "../../open-sse/utils/socksConnectorWithFamily.ts";
import { clearDispatcherCache } from "../../open-sse/utils/proxyDispatcher.ts";
async function startFakeSocks(opts: { stallAfterGrant: boolean }): Promise<{ port: number; close: () => Promise<void> }> {
return new Promise((resolve) => {
const serverSockets = new Set<net.Socket>();
const server = net.createServer((socket) => {
let step = 0;
serverSockets.add(socket);
socket.on("close", () => serverSockets.delete(socket));
socket.on("data", (_data: Buffer) => {
if (step === 0) {
socket.write(Buffer.from([0x05, 0x00]));
step = 1;
return;
}
if (step === 1) {
if (!opts.stallAfterGrant) return;
socket.write(Buffer.from([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
step = 2;
}
});
});
server.listen(0, "127.0.0.1", () => {
const addr = server.address() as net.AddressInfo;
resolve({
port: addr.port,
close: () =>
new Promise<void>((r) => {
for (const s of serverSockets) s.destroy();
server.close(() => r());
}),
});
});
});
}
describe("stub SOCKS e2e", () => {
afterEach(() => clearDispatcherCache());
it("pre-grant stall (SOCKS timer) \u2192 error < 1000ms", async () => {
const { port, close } = await startFakeSocks({ stallAfterGrant: false });
const dispatcher = createSocksDispatcherWithFamily({ host: "127.0.0.1", port, type: 5 } as any, 4 as any, { connectTimeout: 300, connect: {} } as any);
const t0 = Date.now();
await assert.rejects(() => fetch("https://example.invalid/", { dispatcher } as any));
assert.ok(Date.now() - t0 < 1000, `pre-grant stall must error < 1000ms, took ${Date.now() - t0}ms`);
await close();
});
it("post-grant stall (TLS timer, https:// only) \u2192 error < 1500ms", async () => {
const { port, close } = await startFakeSocks({ stallAfterGrant: true });
const dispatcher = createSocksDispatcherWithFamily({ host: "127.0.0.1", port, type: 5 } as any, 4 as any, { connectTimeout: 300, connect: {} } as any);
const t0 = Date.now();
let caught: any = null;
await assert.rejects(async () => { try { await fetch("https://example.invalid/", { dispatcher } as any); } catch (e) { caught = e; throw e; } });
const err: any = caught;
// The ~1000ms wall time is connectTimeout 300ms + undici immediate/queue overhead, not the 10000ms default.
assert.ok(Date.now() - t0 < 1500, `post-grant stall must error < 1500ms, took ${Date.now() - t0}ms (err: ${String((err as any)?.message ?? err).slice(0, 120)})`);
await close();
});
});