mirror of
https://github.com/YuzuZensai/Termix.git
synced 2026-09-14 02:59:06 +00:00
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
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<Duplex>,
|
||
|
|
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());
|
||
|
|
}
|