Files
shell-scripts/1-代理Xray/cloudflare-机场/cmliu-vless-worker.js
2025-07-10 16:49:54 +08:00

773 lines
26 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { connect } from 'cloudflare:sockets';
let userID = '86c50e3a-5b87-49dd-bd20-03c7f2735e40';
let proxyIP = '';
// let socks5Address = 'zeaslity:cd28a746-283e-47cc-88f7-bb43d7f6b53a@140.238.8.73:28888';
let sock5User = "zeaslity"
let sock5Pass = "cd28a746-283e-47cc-88f7-bb43d7f6b53a"
let sock5Host = "140.238.8.73"
let sock5Port = 28888
let enableSocks = true;
let go2Socks5s = [
'*ttvnw.net',
'*tapecontent.net',
'*cloudatacdn.com',
'*.loadshare.org',
"whoer.net",
"whatismyipaddress.com",
"*.cloudflare.com",
"*.cloudflare.net",
"*.cloudflare.workers.dev",
"dnschecker.org",
"ip.sb",
"ipinfo.io"
];
const httpPorts = ["8080", "8880", "2052", "2082", "2086", "2095"];
let httpsPorts = ["2053", "2083", "2087", "2096", "8443"];
export default {
async fetch(request, env, ctx) {
try {
const UA = request.headers.get('User-Agent') || 'null';
const upgradeHeader = request.headers.get('Upgrade');
const url = new URL(request.url);
if (!upgradeHeader || upgradeHeader !== 'websocket') {
return new Response('Hello World! to ' + url);
}
// handle 请求
return await WddGoOverWSHandler(request);
} catch (err) {
let e = err;
return new Response(e.toString());
}
},
};
async function WddGoOverWSHandler(request) {
// @ts-ignore
const webSocketPair = new WebSocketPair();
const [client, webSocket] = Object.values(webSocketPair);
// 接受 WebSocket 连接
webSocket.accept();
let address = '';
let portWithRandomLog = '';
// 日志函数,用于记录连接信息
const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => {
console.log(`[${address}:${portWithRandomLog}] ${info}`, event || '');
};
// 获取早期数据头部,可能包含了一些初始化数据
const earlyDataHeader = request.headers.get('sec-websocket-protocol') || '';
// 创建一个可读的 WebSocket 流,用于接收客户端数据
const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
// 用于存储远程 Socket 的包装器
let remoteSocketWapper = {
value: null,
};
// 标记是否为 DNS 查询
let isDns = false;
// WebSocket 数据流向远程服务器的管道
readableWebSocketStream.pipeTo(new WritableStream({
async write(chunk, controller) {
if (isDns) {
// 如果是 DNS 查询,调用 DNS 处理函数
return await handleDNSQuery(chunk, webSocket, null, log);
}
if (remoteSocketWapper.value) {
// 如果已有远程 Socket直接写入数据
const writer = remoteSocketWapper.value.writable.getWriter()
await writer.write(chunk);
writer.releaseLock();
return;
}
// 处理 WddGo 协议头部
const {
hasError,
message,
addressType,
portRemote = 443,
addressRemote = '',
rawDataIndex,
WddGoVersion = new Uint8Array([0, 0]),
isUDP,
} = processWddGoHeader(chunk, userID);
// 设置地址和端口信息,用于日志
address = addressRemote;
portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp '} `;
if (hasError) {
// 如果有错误,抛出异常
throw new Error(message);
return;
}
// 如果是 UDP 且端口不是 DNS 端口53则关闭连接
if (isUDP) {
if (portRemote === 53) {
isDns = true;
} else {
throw new Error('UDP 代理仅对 DNS53 端口)启用');
return;
}
}
// 构建 WddGo 响应头部
const WddGoResponseHeader = new Uint8Array([WddGoVersion[0], 0]);
// 获取实际的客户端数据
const rawClientData = chunk.slice(rawDataIndex);
if (isDns) {
// 如果是 DNS 查询,调用 DNS 处理函数
return handleDNSQuery(rawClientData, webSocket, WddGoResponseHeader, log);
}
// 处理 TCP 出站连接
log(`处理 TCP 出站连接 ${addressRemote}:${portRemote}`);
handleTCPOutBound(remoteSocketWapper, addressType, addressRemote, portRemote, rawClientData, webSocket, WddGoResponseHeader, log);
},
close() {
log(`readableWebSocketStream 已关闭`);
},
abort(reason) {
log(`readableWebSocketStream 已中止`, JSON.stringify(reason));
},
})).catch((err) => {
log('readableWebSocketStream 管道错误', err);
});
// 返回一个 WebSocket 升级的响应
return new Response(null, {
status: 101,
// @ts-ignore
webSocket: client,
});
}
async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portRemote, rawClientData, webSocket, WddGoResponseHeader, log,) {
async function useSocks5Pattern(address) {
if (go2Socks5s.includes(address) || go2Socks5s.includes(address)) return true;
return go2Socks5s.some(pattern => {
let regexPattern = pattern.replace(/\*/g, '.*');
let regex = new RegExp(`^${regexPattern}$`, 'i');
return regex.test(address);
});
}
async function connectAndWrite(address, port, socks = false) {
log(`connected to ${address}:${port}`);
//if (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(address)) address = `${atob('d3d3Lg==')}${address}${atob('LmlwLjA5MDIyNy54eXo=')}`;
// 如果指定使用 SOCKS5 代理,则通过 SOCKS5 协议连接;否则直接连接
const tcpSocket = socks ? await socks5Connect(addressType, address, port, log)
: connect({
hostname: address,
port: port,
});
remoteSocket.value = tcpSocket;
//log(`connected to ${address}:${port}`);
const writer = tcpSocket.writable.getWriter();
// 首次写入,通常是 TLS 客户端 Hello 消息
await writer.write(rawClientData);
writer.releaseLock();
return tcpSocket;
}
/**
* 重试函数:当 Cloudflare 的 TCP Socket 没有传入数据时,我们尝试重定向 IP
* 这可能是因为某些网络问题导致的连接失败
*/
async function retry() {
if (enableSocks) {
// 如果启用了 SOCKS5通过 SOCKS5 代理重试连接
tcpSocket = await connectAndWrite(addressRemote, portRemote, true);
} else {
// 使用代理 IP
proxyIP = '[2603:c022:8008:8923:88a0:5c7d:2bb6:6ed5]'
portRemote = 27443
tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote);
}
// 无论重试是否成功,都要关闭 WebSocket可能是为了重新建立连接
tcpSocket.closed.catch(error => {
console.log('retry tcpSocket closed error', error);
}).finally(() => {
safeCloseWebSocket(webSocket);
})
// 建立从远程 Socket 到 WebSocket 的数据流
remoteSocketToWS(tcpSocket, webSocket, WddGoResponseHeader, null, log);
}
let useSocks = false;
if (go2Socks5s.length > 0 && enableSocks) useSocks = await useSocks5Pattern(addressRemote);
// 首次尝试连接远程服务器
let tcpSocket = await connectAndWrite(addressRemote, portRemote, useSocks);
// 当远程 Socket 就绪时,将其传递给 WebSocket
// 建立从远程服务器到 WebSocket 的数据流,用于将远程服务器的响应发送回客户端
// 如果连接失败或无数据retry 函数将被调用进行重试
remoteSocketToWS(tcpSocket, webSocket, WddGoResponseHeader, retry, log);
}
function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) {
// 标记可读流是否已被取消
let readableStreamCancel = false;
// 创建一个新的可读流
const stream = new ReadableStream({
// 当流开始时的初始化函数
start(controller) {
// 监听 WebSocket 的消息事件
webSocketServer.addEventListener('message', (event) => {
// 如果流已被取消,不再处理新消息
if (readableStreamCancel) {
return;
}
const message = event.data;
// 将消息加入流的队列中
controller.enqueue(message);
});
// 监听 WebSocket 的关闭事件
// 注意:这个事件意味着客户端关闭了客户端 -> 服务器的流
// 但是,服务器 -> 客户端的流仍然打开,直到在服务器端调用 close()
// WebSocket 协议要求在每个方向上都要发送单独的关闭消息,以完全关闭 Socket
webSocketServer.addEventListener('close', () => {
// 客户端发送了关闭信号,需要关闭服务器端
safeCloseWebSocket(webSocketServer);
// 如果流未被取消,则关闭控制器
if (readableStreamCancel) {
return;
}
controller.close();
});
// 监听 WebSocket 的错误事件
webSocketServer.addEventListener('error', (err) => {
log('WebSocket 服务器发生错误');
// 将错误传递给控制器
controller.error(err);
});
// 处理 WebSocket 0-RTT零往返时间的早期数据
// 0-RTT 允许在完全建立连接之前发送数据,提高了效率
const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
if (error) {
// 如果解码早期数据时出错,将错误传递给控制器
controller.error(error);
} else if (earlyData) {
// 如果有早期数据,将其加入流的队列中
controller.enqueue(earlyData);
}
},
// 当使用者从流中拉取数据时调用
pull(controller) {
// 这里可以实现反压机制
// 如果 WebSocket 可以在流满时停止读取,我们就可以实现反压
// 参考https://streams.spec.whatwg.org/#example-rs-push-backpressure
},
// 当流被取消时调用
cancel(reason) {
// 流被取消的几种情况:
// 1. 当管道的 WritableStream 有错误时,这个取消函数会被调用,所以在这里处理 WebSocket 服务器的关闭
// 2. 如果 ReadableStream 被取消,所有 controller.close/enqueue 都需要跳过
// 3. 但是经过测试,即使 ReadableStream 被取消controller.error 仍然有效
if (readableStreamCancel) {
return;
}
log(`可读流被取消,原因是 ${reason}`);
readableStreamCancel = true;
// 安全地关闭 WebSocket
safeCloseWebSocket(webSocketServer);
}
});
return stream;
}
/**
* 解析 WddGo 协议的头部数据
* @param { ArrayBuffer} WddGoBuffer WddGo 协议的原始头部数据
* @param {string} userID 用于验证的用户 ID
* @returns {Object} 解析结果,包括是否有错误、错误信息、远程地址信息等
*/
function processWddGoHeader(WddGoBuffer, userID) {
// 检查数据长度是否足够(至少需要 24 字节)
if (WddGoBuffer.byteLength < 24) {
return {
hasError: true,
message: 'invalid data',
};
}
// 解析 WddGo 协议版本(第一个字节)
const version = new Uint8Array(WddGoBuffer.slice(0, 1));
let isValidUser = false;
let isUDP = false;
// 验证用户 ID接下来的 16 个字节)
function isUserIDValid(userID, buffer) {
const userIDArray = new Uint8Array(buffer.slice(1, 17));
const userIDString = stringify(userIDArray);
return userIDString === userID
}
// 使用函数验证
isValidUser = isUserIDValid(userID, WddGoBuffer);
// 如果用户 ID 无效,返回错误
if (!isValidUser) {
return {
hasError: true,
message: `invalid user ${(new Uint8Array(WddGoBuffer.slice(1, 17)))}`,
};
}
// 获取附加选项的长度(第 17 个字节)
const optLength = new Uint8Array(WddGoBuffer.slice(17, 18))[0];
// 暂时跳过附加选项
// 解析命令(紧跟在选项之后的 1 个字节)
// 0x01: TCP, 0x02: UDP, 0x03: MUX多路复用
const command = new Uint8Array(
WddGoBuffer.slice(18 + optLength, 18 + optLength + 1)
)[0];
// 0x01 TCP
// 0x02 UDP
// 0x03 MUX
if (command === 1) {
// TCP 命令,不需特殊处理
} else if (command === 2) {
// UDP 命令
isUDP = true;
} else {
// 不支持的命令
return {
hasError: true,
message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`,
};
}
// 解析远程端口大端序2 字节)
const portIndex = 18 + optLength + 1;
const portBuffer = WddGoBuffer.slice(portIndex, portIndex + 2);
// port is big-Endian in raw data etc 80 == 0x005d
const portRemote = new DataView(portBuffer).getUint16(0);
// 解析地址类型和地址
let addressIndex = portIndex + 2;
const addressBuffer = new Uint8Array(
WddGoBuffer.slice(addressIndex, addressIndex + 1)
);
// 地址类型1-IPv4(4字节), 2-域名(可变长), 3-IPv6(16字节)
const addressType = addressBuffer[0];
let addressLength = 0;
let addressValueIndex = addressIndex + 1;
let addressValue = '';
switch (addressType) {
case 1:
// IPv4 地址
addressLength = 4;
// 将 4 个字节转为点分十进制格式
addressValue = new Uint8Array(
WddGoBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
).join('.');
break;
case 2:
// 域名
// 第一个字节是域名长度
addressLength = new Uint8Array(
WddGoBuffer.slice(addressValueIndex, addressValueIndex + 1)
)[0];
addressValueIndex += 1;
// 解码域名
addressValue = new TextDecoder().decode(
WddGoBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
);
break;
case 3:
// IPv6 地址
addressLength = 16;
const dataView = new DataView(
WddGoBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
);
// 每 2 字节构成 IPv6 地址的一部分
const ipv6 = [];
for (let i = 0; i < 8; i++) {
ipv6.push(dataView.getUint16(i * 2).toString(16));
}
addressValue = ipv6.join(':');
// seems no need add [] for ipv6
break;
default:
// 无效的地址类型
return {
hasError: true,
message: `invild addressType is ${addressType}`,
};
}
// 确保地址不为空
if (!addressValue) {
return {
hasError: true,
message: `addressValue is empty, addressType is ${addressType}`,
};
}
// 返回解析结果
return {
hasError: false,
addressRemote: addressValue, // 解析后的远程地址
addressType, // 地址类型
portRemote, // 远程端口
rawDataIndex: addressValueIndex + addressLength, // 原始数据的实际起始位置
WddGoVersion: version, // WddGo 协议版本
isUDP, // 是否是 UDP 请求
};
}
async function remoteSocketToWS(remoteSocket, webSocket, WddGoResponseHeader, retry, log) {
// 将数据从远程服务器转发到 WebSocket
let remoteChunkCount = 0;
let chunks = [];
/** @type {ArrayBuffer | null} */
let WddGoHeader = WddGoResponseHeader;
let hasIncomingData = false; // 检查远程 Socket 是否有传入数据
// 使用管道将远程 Socket 的可读流连接到一个可写流
await remoteSocket.readable
.pipeTo(
new WritableStream({
start() {
// 初始化时不需要任何操作
},
/**
* 处理每个数据块
* @param {Uint8Array} chunk 数据块
* @param {*} controller 控制器
*/
async write(chunk, controller) {
hasIncomingData = true; // 标记已收到数据
// remoteChunkCount++; // 用于流量控制,现在似乎不需要了
// 检查 WebSocket 是否处于开放状态
if (webSocket.readyState !== WS_READY_STATE_OPEN) {
controller.error(
'webSocket.readyState is not open, maybe close'
);
}
if (WddGoHeader) {
// 如果有 WddGo 响应头部,将其与第一个数据块一起发送
webSocket.send(await new Blob([WddGoHeader, chunk]).arrayBuffer());
WddGoHeader = null; // 清空头部,之后不再发送
} else {
// 直接发送数据块
// 以前这里有流量控制代码,限制大量数据的发送速率
// 但现在 Cloudflare 似乎已经修复了这个问题
// if (remoteChunkCount > 20000) {
// // cf one package is 4096 byte(4kb), 4096 * 20000 = 80M
// await delay(1);
// }
webSocket.send(chunk);
}
},
close() {
// 当远程连接的可读流关闭时
log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`);
// 不需要主动关闭 WebSocket因为这可能导致 HTTP ERR_CONTENT_LENGTH_MISMATCH 问题
// 客户端无论如何都会发送关闭事件
// safeCloseWebSocket(webSocket);
},
abort(reason) {
// 当远程连接的可读流中断时
console.error(`remoteConnection!.readable abort`, reason);
},
})
)
.catch((error) => {
// 捕获并记录任何异常
console.error(
`remoteSocketToWS has exception `,
error.stack || error
);
// 发生错误时安全地关闭 WebSocket
safeCloseWebSocket(webSocket);
});
// 处理 Cloudflare 连接 Socket 的特殊错误情况
// 1. Socket.closed 将有错误
// 2. Socket.readable 将关闭,但没有任何数据
if (hasIncomingData === false && retry) {
log(`retry`);
retry(); // 调用重试函数,尝试重新建立连接
}
}
/**
* 将 Base64 编码的字符串转换为 ArrayBuffer
*
* @param {string} base64Str Base64 编码的输入字符串
* @returns {{ earlyData: ArrayBuffer | undefined, error: Error | null }} 返回解码后的 ArrayBuffer 或错误
*/
function base64ToArrayBuffer(base64Str) {
// 如果输入为空,直接返回空结果
if (!base64Str) {
return { earlyData: undefined, error: null };
}
try {
// Go 语言使用了 URL 安全的 Base64 变体RFC 4648
// 这种变体使用 '-' 和 '_' 来代替标准 Base64 中的 '+' 和 '/'
// JavaScript 的 atob 函数不直接支持这种变体,所以我们需要先转换
base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/');
// 使用 atob 函数解码 Base64 字符串
// atob 将 Base64 编码的 ASCII 字符串转换为原始的二进制字符串
const decode = atob(base64Str);
// 将二进制字符串转换为 Uint8Array
// 这是通过遍历字符串中的每个字符并获取其 Unicode 编码值0-255来完成的
const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0));
// 返回 Uint8Array 的底层 ArrayBuffer
// 这是实际的二进制数据,可以用于网络传输或其他二进制操作
return { earlyData: arryBuffer.buffer, error: null };
} catch (error) {
// 如果在任何步骤中出现错误(如非法 Base64 字符),则返回错误
return { earlyData: undefined, error };
}
}
/**
* 这不是真正的 UUID 验证,而是一个简化的版本
* @param {string} uuid 要验证的 UUID 字符串
* @returns {boolean} 如果字符串匹配 UUID 格式则返回 true否则返回 false
*/
function isValidUUID(uuid) {
// 定义一个正则表达式来匹配 UUID 格式
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
// 使用正则表达式测试 UUID 字符串
return uuidRegex.test(uuid);
}
// WebSocket 的两个重要状态常量
const WS_READY_STATE_OPEN = 1; // WebSocket 处于开放状态,可以发送和接收消息
const WS_READY_STATE_CLOSING = 2; // WebSocket 正在关闭过程中
function safeCloseWebSocket(socket) {
try {
// 只有在 WebSocket 处于开放或正在关闭状态时才调用 close()
// 这避免了在已关闭或连接中的 WebSocket 上调用 close()
if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) {
socket.close();
}
} catch (error) {
// 记录任何可能发生的错误,虽然按照规范不应该有错误
console.error('safeCloseWebSocket error', error);
}
}
// 预计算 0-255 每个字节的十六进制表示
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
// (i + 256).toString(16) 确保总是得到两位数的十六进制
// .slice(1) 删除前导的 "1",只保留两位十六进制数
byteToHex.push((i + 256).toString(16).slice(1));
}
/**
* 快速地将字节数组转换为 UUID 字符串,不进行有效性检查
* 这是一个底层函数,直接操作字节,不做任何验证
* @param {Uint8Array} arr 包含 UUID 字节的数组
* @param {number} offset 数组中 UUID 开始的位置,默认为 0
* @returns {string} UUID 字符串
*/
function unsafeStringify(arr, offset = 0) {
// 直接从查找表中获取每个字节的十六进制表示,并拼接成 UUID 格式
// 8-4-4-4-12 的分组是通过精心放置的连字符 "-" 实现的
// toLowerCase() 确保整个 UUID 是小写的
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" +
byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" +
byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" +
byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" +
byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] +
byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
}
/**
* 将字节数组转换为 UUID 字符串,并验证其有效性
* 这是一个安全的函数,它确保返回的 UUID 格式正确
* @param {Uint8Array} arr 包含 UUID 字节的数组
* @param {number} offset 数组中 UUID 开始的位置,默认为 0
* @returns {string} 有效的 UUID 字符串
* @throws {TypeError} 如果生成的 UUID 字符串无效
*/
function stringify(arr, offset = 0) {
// 使用不安全的函数快速生成 UUID 字符串
const uuid = unsafeStringify(arr, offset);
// 验证生成的 UUID 是否有效
if (!isValidUUID(uuid)) {
// 原throw TypeError("Stringified UUID is invalid");
throw TypeError(`生成的 UUID 不符合规范 ${uuid}`);
//uuid = userID;
}
return uuid;
}
/**
* 处理 DNS 查询的函数
* @param {ArrayBuffer} udpChunk - 客户端发送的 DNS 查询数据
* @param {ArrayBuffer} WddGoResponseHeader - WddGo 协议的响应头部数据
* @param {(string)=> void} log - 日志记录函数
*/
async function handleDNSQuery(udpChunk, webSocket, WddGoResponseHeader, log) {
// 无论客户端发送到哪个 DNS 服务器,我们总是使用硬编码的服务器
// 因为有些 DNS 服务器不支持 DNS over TCP
try {
// 选用 Google 的 DNS 服务器(注:后续可能会改为 Cloudflare 的 1.1.1.1
const dnsServer = '8.8.4.4'; // 在 Cloudflare 修复连接自身 IP 的 bug 后,将改为 1.1.1.1
const dnsPort = 53; // DNS 服务的标准端口
let WddGoHeader = WddGoResponseHeader; // 保存 WddGo 响应头部,用于后续发送
// 与指定的 DNS 服务器建立 TCP 连接
const tcpSocket = connect({
hostname: dnsServer,
port: dnsPort,
});
log(`连接到 ${dnsServer}:${dnsPort}`); // 记录连接信息
const writer = tcpSocket.writable.getWriter();
await writer.write(udpChunk); // 将客户端的 DNS 查询数据发送给 DNS 服务器
writer.releaseLock(); // 释放写入器,允许其他部分使用
// 将从 DNS 服务器接收到的响应数据通过 WebSocket 发送回客户端
await tcpSocket.readable.pipeTo(new WritableStream({
async write(chunk) {
if (webSocket.readyState === WS_READY_STATE_OPEN) {
if (WddGoHeader) {
// 如果有 WddGo 头部,则将其与 DNS 响应数据合并后发送
webSocket.send(await new Blob([WddGoHeader, chunk]).arrayBuffer());
WddGoHeader = null; // 头部只发送一次,之后置为 null
} else {
// 否则直接发送 DNS 响应数据
webSocket.send(chunk);
}
}
},
close() {
log(`DNS 服务器(${dnsServer}) TCP 连接已关闭`); // 记录连接关闭信息
},
abort(reason) {
console.error(`DNS 服务器(${dnsServer}) TCP 连接异常中断`, reason); // 记录异常中断原因
},
}));
} catch (error) {
// 捕获并记录任何可能发生的错误
console.error(
`handleDNSQuery 函数发生异常,错误信息: ${error.message}`
);
}
}
async function socks5Connect(addressType, addressRemote, portRemote, log) {
// 从环境变量获取认证信息(假设在运行环境中已定义)
const username = sock5User
const password = sock5Pass
const hostname = sock5Host
const port = sock5Port
// 直连代理服务器
const socket = connect({ hostname, port });
const writer = socket.writable.getWriter();
const reader = socket.readable.getReader();
const encoder = new TextEncoder();
// 精简握手流程:直接声明需要用户名密码认证
await writer.write(new Uint8Array([5, 1, 2])); // 只支持 0x02 方法
log('SOCKS5 认证方法协商');
// 处理认证响应
let res = (await reader.read()).value;
if (res[0] !== 0x05 || res[1] !== 0x02) {
res[1] === 0xff && log("不支持的认证方式");
return;
}
// 构造认证数据包(提前计算长度避免重复编码)
const userBytes = encoder.encode(username);
const passBytes = encoder.encode(password);
const authHeader = new Uint8Array(3 + userBytes.length + passBytes.length);
authHeader.set([1, userBytes.length, ...userBytes, passBytes.length], 0);
authHeader.set(passBytes, 2 + userBytes.length); // 优化内存拷贝
await writer.write(authHeader);
res = (await reader.read()).value;
if (res[0] !== 0x01 || res[1] !== 0x00) {
log(`认证失败 code: 0x${res[1].toString(16)}`);
return;
}
// 构造目标地址(优化二进制操作)
const header = new Uint8Array([5, 1, 0]);
const addrBuffer = new Uint8Array(
addressType === 3 ? 16 + 1 : // IPv6
addressType === 2 ? encoder.encode(addressRemote).length + 2 : // 域名
4 + 1 // IPv4
);
let offset = 0;
addrBuffer[offset++] = addressType === 3 ? 4 : addressType;
if (addressType === 2) {
addrBuffer[offset++] = addressRemote.length;
encoder.encodeInto(addressRemote, addrBuffer.subarray(offset));
} else {
const octets = addressType === 3 ?
new Uint16Array(addressRemote.split(':').flatMap(p =>
[parseInt(p.substring(0,4),16), parseInt(p.substring(4),16)])) :
addressRemote.split('.').map(Number);
addrBuffer.set(new Uint8Array(octets.buffer || octets), offset);
}
// 合并数据包并发送
const finalPacket = new Uint8Array([
...header,
...addrBuffer,
portRemote >> 8, portRemote & 0xff
]);
await writer.write(finalPacket);
log('SOCKS5 连接请求已发送');
// 验证最终响应
res = (await reader.read()).value;
if (res[1] !== 0x00) {
log(`连接失败 code: 0x${res[1].toString(16)}`);
return;
}
writer.releaseLock();
reader.releaseLock();
return socket;
}