import type { Duplex } from "stream"; import { tunnelLogger } from "../utils/logger.js"; function parseSocksAddress(buffer: Buffer): { address: string; port: number; bytesRead: number; } | null { if (buffer.length < 7 || buffer[0] !== 0x05 || buffer[1] !== 0x01) { return null; } const addressType = buffer[3]; let offset = 4; let address: string; if (addressType === 0x01) { if (buffer.length < offset + 4 + 2) return null; address = Array.from(buffer.subarray(offset, offset + 4)).join("."); offset += 4; } else if (addressType === 0x03) { const len = buffer[offset]; offset += 1; if (buffer.length < offset + len + 2) return null; address = buffer.subarray(offset, offset + len).toString("utf8"); offset += len; } else if (addressType === 0x04) { if (buffer.length < offset + 16 + 2) return null; const parts: string[] = []; for (let i = 0; i < 16; i += 2) { parts.push(buffer.readUInt16BE(offset + i).toString(16)); } address = parts.join(":"); offset += 16; } else { return null; } const port = buffer.readUInt16BE(offset); return { address, port, bytesRead: offset + 2 }; } export function handleSocks5Connect( inbound: Duplex, openOutbound: (host: string, port: number) => Promise, tunnelName: string, ): void { let buffer = Buffer.alloc(0); let stage: "greeting" | "connect" | "piping" = "greeting"; const fail = (code = 0x01) => { if (!inbound.destroyed) { inbound.write(Buffer.from([0x05, code, 0x00, 0x01, 0, 0, 0, 0, 0, 0])); inbound.destroy(); } }; const onData = (chunk: Buffer) => { buffer = Buffer.concat([buffer, chunk]); if (stage === "greeting") { if (buffer.length < 2) return; if (buffer[0] !== 0x05) { fail(0x01); return; } const methodsLength = buffer[1]; if (buffer.length < 2 + methodsLength) return; inbound.write(Buffer.from([0x05, 0x00])); buffer = buffer.subarray(2 + methodsLength); stage = "connect"; } if (stage === "connect") { const parsed = parseSocksAddress(buffer); if (!parsed) return; stage = "piping"; inbound.off("data", onData); const remainder = buffer.subarray(parsed.bytesRead); openOutbound(parsed.address, parsed.port) .then((outbound) => { inbound.write( Buffer.from([0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]), ); if (remainder.length > 0) { outbound.write(remainder); } inbound.pipe(outbound).pipe(inbound); inbound.on("error", () => outbound.destroy()); outbound.on("error", () => inbound.destroy()); }) .catch((error) => { tunnelLogger.error("SOCKS5 tunnel connect failed", error, { operation: "managed_tunnel_socks_connect_failed", tunnelName, targetHost: parsed.address, targetPort: parsed.port, }); fail(0x05); }); } }; inbound.on("data", onData); inbound.on("error", () => inbound.destroy()); }