mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
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!
66 lines
2.9 KiB
TypeScript
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();
|
|
});
|
|
});
|