// 本代码源自yonggekkk
// version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC.
// @ts-ignore
// 导入 Cloudflare Sockets API,用于建立出站 TCP 连接。
import { connect } from "cloudflare:sockets";
// 如何生成你自己的 UUID:
// [Windows] 按 "Win + R",输入 cmd 并运行: Powershell -NoExit -Command "[guid]::NewGuid()"
// 默认的用户 UUID,用于 VLESS 协议的身份验证。可以被环境变量覆盖。
let userID = "86c50e3a-5b87-49dd-bd20-03c7f2735e40";
// 备选的反向代理 IP 地址列表。
const proxyIPs = ["ts.hpc.tw"];
// 一个用于判断是否为中国大陆访问的空主机名列表(此处逻辑似乎未完整实现)。
const cn_hostnames = [''];
// 默认的 CDN IP 地址,用于生成 VLESS 配置。注意这里使用了 Unicode 转义字符来混淆字符串。
// 解码后为 'www.visa.com.sg'
let CDNIP = '\u0077\u0077\u0077\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d\u002e\u0073\u0067'
// 预设的一系列用于生成节点配置的 IP 或域名,分为 HTTP 和 HTTPS 两组。
// 同样使用 Unicode 转义字符进行混淆。
// http_ip (用于 HTTP 端口的地址)
let IP1 = '\u0077\u0077\u0077\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d' // www.visa.com
let IP2 = '\u0063\u0069\u0073\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d' // cis.visa.com
let IP3 = '\u0061\u0066\u0072\u0069\u0063\u0061\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d' // africa.visa.com
let IP4 = '\u0077\u0077\u0077\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d\u002e\u0073\u0067' // www.visa.com.sg
let IP5 = '\u0077\u0077\u0077\u002e\u0076\u0069\u0073\u0061\u0065\u0075\u0072\u006f\u0070\u0065\u002e\u0061\u0074' // www.visaeurope.at
let IP6 = '\u0077\u0077\u0077\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d\u002e\u006d\u0074' // www.visa.com.mt
let IP7 = '\u0071\u0061\u002e\u0076\u0069\u0073\u0061\u006d\u0069\u0064\u0064\u006c\u0065\u0065\u0061\u0073\u0074\u002e\u0063\u006f\u006d' // qa.visamiddleeast.com
// https_ip (用于 HTTPS/TLS 端口的地址)
let IP8 = '\u0075\u0073\u0061\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d' // usa.visa.com
let IP9 = '\u006d\u0079\u0061\u006e\u006d\u0061\u0072\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d' // myanmar.visa.com
let IP10 = '\u0077\u0077\u0077\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d\u002e\u0074\u0077' // www.visa.com.tw
let IP11 = '\u0077\u0077\u0077\u002e\u0076\u0069\u0073\u0061\u0065\u0075\u0072\u006f\u0070\u0065\u002e\u0063\u0068' // www.visaeurope.ch
let IP12 = '\u0077\u0077\u0077\u002e\u0076\u0069\u0073\u0061\u002e\u0063\u006f\u006d\u002e\u0062\u0072' // www.visa.com.br
let IP13 = '\u0077\u0077\u0077\u002e\u0076\u0069\u0073\u0061\u0073\u006f\u0075\u0074\u0068\u0065\u0061\u0073\u0074\u0065\u0075\u0072\u006f\u0070\u0065\u002e\u0063\u006f\u006d' // www.visasoutheasteurope.com
// 预设的 HTTP 端口
let PT1 = '80'
let PT2 = '8080'
let PT3 = '8880'
let PT4 = '2052'
let PT5 = '2082'
let PT6 = '2086'
let PT7 = '2095'
// 预设的 HTTPS/TLS 端口
let PT8 = '443'
let PT9 = '8443'
let PT10 = '2053'
let PT11 = '2083'
let PT12 = '2087'
let PT13 = '2096'
// 从 proxyIPs 列表中随机选择一个作为代理 IP。
let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];
// 从选定的 proxyIP 中提取端口,如果不存在则默认为 '443'。
let proxyPort = proxyIP.match(/:(\d+)$/) ? proxyIP.match(/:(\d+)$/)[1] : '443';
// DNS-over-HTTPS (DoH) 服务器地址,用于代理 UDP DNS 查询。
const dohURL = "https://cloudflare-dns.com/dns-query";
// 检查初始的 userID 是否为有效的 UUID 格式。
if (!isValidUUID(userID)) {
throw new Error("uuid is not valid");
}
// 导出 Worker 的主处理对象。
export default {
/**
* Worker 的 fetch 事件处理器,处理所有进入的 HTTP 请求。
* @param {Request} request - 入站的请求对象。
* @param {object} env - Cloudflare 的环境变量。
* @param {any} ctx - 执行上下文。
* @returns {Promise<Response>} - 返回一个响应对象。
*/
async fetch(request, env, ctx) {
try {
// 尝试从环境变量中获取 `proxyip`。
const { proxyip } = env;
// 优先使用环境变量中的 `uuid`,否则使用代码中预设的 `userID`。
userID = env.uuid || userID;
// 如果环境变量中设置了 `proxyip`。
if (proxyip) {
// 处理 IPv6 地址和端口,格式如 `[IPv6]:port`。
if (proxyip.includes(']:')) {
let lastColonIndex = proxyip.lastIndexOf(':');
proxyPort = proxyip.slice(lastColonIndex + 1);
proxyIP = proxyip.slice(0, lastColonIndex);
} else if (!proxyip.includes(']:') && !proxyip.includes(']')) {
// 处理 IPv4 或域名和端口,格式如 `IP:port`。
[proxyIP, proxyPort = '443'] = proxyip.split(':');
} else {
// 仅有 IP 或域名,端口默认为 443。
proxyPort = '443';
proxyIP = proxyip;
}
} else {
// 如果环境变量中没有 `proxyip`,则使用代码中预设的 `proxyIP`。
// 同样处理 IPv6 和 IPv4 的情况。
if (proxyIP.includes(']:')) {
let lastColonIndex = proxyIP.lastIndexOf(':');
proxyPort = proxyIP.slice(lastColonIndex + 1);
proxyIP = proxyIP.slice(0, lastColonIndex);
} else {
// 使用正则表达式解析 IP 和端口,更稳健。
const match = proxyIP.match(/^(.*?)(?::(\d+))?$/);
proxyIP = match[1];
proxyPort = match[2] || '443';
console.log("IP:", proxyIP, "Port:", proxyPort);
}
}
console.log('ProxyIP:', proxyIP);
console.log('ProxyPort:', proxyPort);
// 从环境变量中读取并覆盖预设的 IP 和端口,实现高度自定义。
CDNIP = env.cdnip || CDNIP;
IP1 = env.ip1 || IP1;
IP2 = env.ip2 || IP2;
IP3 = env.ip3 || IP3;
IP4 = env.ip4 || IP4;
IP5 = env.ip5 || IP5;
IP6 = env.ip6 || IP6;
IP7 = env.ip7 || IP7;
IP8 = env.ip8 || IP8;
IP9 = env.ip9 || IP9;
IP10 = env.ip10 || IP10;
IP11 = env.ip11 || IP11;
IP12 = env.ip12 || IP12;
IP13 = env.ip13 || IP13;
PT1 = env.pt1 || PT1;
PT2 = env.pt2 || PT2;
PT3 = env.pt3 || PT3;
PT4 = env.pt4 || PT4;
PT5 = env.pt5 || PT5;
PT6 = env.pt6 || PT6;
PT7 = env.pt7 || PT7;
PT8 = env.pt8 || PT8;
PT9 = env.pt9 || PT9;
PT10 = env.pt10 || PT10;
PT11 = env.pt11 || PT11;
PT12 = env.pt12 || PT12;
PT13 = env.pt13 || PT13;
// 获取请求头中的 'Upgrade' 字段,用于判断是否为 WebSocket 升级请求。
const upgradeHeader = request.headers.get("Upgrade");
// 解析请求的 URL。
const url = new URL(request.url);
// 如果不是 WebSocket 升级请求,则判断为配置获取请求或普通网页访问。
if (!upgradeHeader || upgradeHeader !== "websocket") {
const url = new URL(request.url);
// 根据 URL 路径进行路由。
switch (url.pathname) {
// 路径为 `/<uuid>`,返回主配置页面(HTML 格式)。
case `/${userID}`: {
// 解码混淆的函数名 getvlessConfig
const \u0076\u006c\u0065\u0073\u0073Config = get\u0076\u006c\u0065\u0073\u0073Config(userID, request.headers.get("Host"));
return new Response(`${\u0076\u006c\u0065\u0073\u0073Config}`, {
status: 200,
headers: {
"Content-Type": "text/html;charset=utf-8",
},
});
}
// 路径为 `/<uuid>/ty`,返回通用格式的订阅内容 (Base64 编码)。
case `/${userID}/ty`: {
const tyConfig = gettyConfig(userID, request.headers.get('Host'));
return new Response(`${tyConfig}`, {
status: 200,
headers: {
"Content-Type": "text/plain;charset=utf-8",
}
});
}
// 路径为 `/<uuid>/cl`,返回 Clash-meta 格式的订阅配置 (YAML)。
case `/${userID}/cl`: {
const clConfig = getclConfig(userID, request.headers.get('Host'));
return new Response(`${clConfig}`, {
status: 200,
headers: {
"Content-Type": "text/plain;charset=utf-8",
}
});
}
// 路径为 `/<uuid>/sb`,返回 Sing-box 格式的订阅配置 (JSON)。
case `/${userID}/sb`: {
const sbConfig = getsbConfig(userID, request.headers.get('Host'));
return new Response(`${sbConfig}`, {
status: 200,
headers: {
"Content-Type": "application/json;charset=utf-8",
}
});
}
// 以下为针对 pages/自定义域名 的订阅链接,只包含 TLS 节点
case `/${userID}/pty`: {
const ptyConfig = getptyConfig(userID, request.headers.get('Host'));
return new Response(`${ptyConfig}`, {
status: 200,
headers: {
"Content-Type": "text/plain;charset=utf-8",
}
});
}
case `/${userID}/pcl`: {
const pclConfig = getpclConfig(userID, request.headers.get('Host'));
return new Response(`${pclConfig}`, {
status: 200,
headers: {
"Content-Type": "text/plain;charset=utf-8",
}
});
}
case `/${userID}/psb`: {
const psbConfig = getpsbConfig(userID, request.headers.get('Host'));
return new Response(`${psbConfig}`, {
status: 200,
headers: {
"Content-Type": "application/json;charset=utf-8",
}
});
}
// 对于其他所有路径
default:
// 此处可以返回一个 404 Not Found 页面。
// 当前逻辑是反向代理到一个随机的网站。
if (cn_hostnames.includes('')) {
// 这个判断条件 `cn_hostnames.includes('')` 永远为真,
// 意图可能是判断来访 IP 是否来自中国,但目前实现会直接返回请求的 cf 信息。
return new Response(JSON.stringify(request.cf, null, 4), {
status: 200,
headers: {
"Content-Type": "application/json;charset=utf-8",
},
});
}
// 从 cn_hostnames 列表中随机选择一个主机名(当前列表为空)。
const randomHostname = cn_hostnames[Math.floor(Math.random() * cn_hostnames.length)];
const newHeaders = new Headers(request.headers);
// 修改请求头,伪造客户端 IP 和来源。
newHeaders.set("cf-connecting-ip", "1.2.3.4");
newHeaders.set("x-forwarded-for", "1.2.3.4");
newHeaders.set("x-real-ip", "1.2.3.4");
newHeaders.set("referer", "https://www.google.com/search?q=edtunnel");
// 构建代理请求的 URL。
const proxyUrl = "https://" + randomHostname + url.pathname + url.search;
// 创建一个新的请求对象,用于向上游服务器发起请求。
let modifiedRequest = new Request(proxyUrl, {
method: request.method,
headers: newHeaders,
body: request.body,
redirect: "manual", // 手动处理重定向。
});
// 发起代理请求。
const proxyResponse = await fetch(modifiedRequest, { redirect: "manual" });
// 如果上游服务器返回 301 或 302 重定向,则返回 403 Forbidden。
if ([301, 302].includes(proxyResponse.status)) {
return new Response(`Redirects to ${randomHostname} are not allowed.`, {
status: 403,
statusText: "Forbidden",
});
}
// 返回上游服务器的响应。
return proxyResponse;
}
} else {
// 如果是 WebSocket 升级请求
// 允许通过 URL 路径临时指定代理 IP,格式如 `/path?pyip=<ip:port>`
if(url.pathname.includes('/pyip='))
{
const tmp_ip=url.pathname.split("=")[1];
// 验证 IP 格式(当前函数 isValidIP 逻辑简单,实际只检查非空)
if(isValidIP(tmp_ip))
{
proxyIP=tmp_ip;
// 解析 IP 和端口
if (proxyIP.includes(']:')) {
let lastColonIndex = proxyIP.lastIndexOf(':');
proxyPort = proxyIP.slice(lastColonIndex + 1);
proxyIP = proxyIP.slice(0, lastColonIndex);
} else if (!proxyIP.includes(']:') && !proxyIP.includes(']')) {
[proxyIP, proxyPort = '443'] = proxyIP.split(':');
} else {
proxyPort = '443';
}
}
}
// 调用 WebSocket 处理器。函数名 `vlessOverWSHandler` 被混淆。
return await \u0076\u006c\u0065\u0073\u0073OverWSHandler(request);
}
} catch (err) {
// 捕获任何在处理过程中发生的错误,并返回错误信息。
/** @type {Error} */ let e = err;
return new Response(e.toString());
}
},
};
/**
* 检查给定的字符串是否为有效的 IP(此函数实现过于简单,仅检查是否为任意字符串)。
* @param {string} ip - 要检查的 IP 字符串。
* @returns {boolean} - 返回 true。
*/
function isValidIP(ip) {
var reg = /^[\s\S]*$/;
return reg.test(ip);
}
/**
* 处理 VLESS over WebSocket 的核心函数。
* @param {Request} request - 客户端发来的 WebSocket 升级请求。
*/
async function \u0076\u006c\u0065\u0073\u0073OverWSHandler(request) {
// 创建一个 WebSocket 对,一个用于客户端,一个用于 Worker 内部。
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 || "");
};
// 获取 "sec-websocket-protocol" 头,用于 WebSocket 的 0-RTT (早期数据)。
const earlyDataHeader = request.headers.get("sec-websocket-protocol") || "";
// 创建一个可读流,用于从 WebSocket 中读取客户端发送的数据。
const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
/** @type {{ value: any | null }} */
// 一个包装器,用于持有到目标服务器的 TCP socket 连接。
let remoteSocketWapper = {
value: null,
};
let udpStreamWrite = null; // 用于写入 UDP 数据的函数。
let isDns = false; // 标记当前是否为 DNS 请求。
// 将 WebSocket 的可读流通过管道连接到一个可写流,处理从客户端到目标服务器的数据流。
readableWebSocketStream
.pipeTo(
new WritableStream({
// 当有数据块写入时被调用。
async write(chunk, controller) {
// 如果是 DNS 请求并且 UDP 写入函数已准备好,则直接写入。
if (isDns && udpStreamWrite) {
return udpStreamWrite(chunk);
}
// 如果已经建立了到目标服务器的连接,则直接将数据写入该连接。
if (remoteSocketWapper.value) {
const writer = remoteSocketWapper.value.writable.getWriter();
await writer.write(chunk);
writer.releaseLock();
return;
}
// 如果是第一个数据块,需要解析 VLESS 头部信息。
// 函数名 `processcloudflareHeader` 被混淆。
const {
hasError, // 是否有错误
message, // 错误信息
portRemote = 443, // 目标端口
addressRemote = "", // 目标地址
rawDataIndex, // 原始负载数据的起始索引
cloudflareVersion = new Uint8Array([0, 0]), // VLESS 版本
isUDP, // 是否为 UDP 请求
} = await processcloudflareHeader(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 {
// 对于非 DNS 的 UDP 请求,抛出错误。
throw new Error("UDP proxy only enable for DNS which is port 53");
return;
}
}
// 构建 VLESS 响应头部。
const cloudflareResponseHeader = new Uint8Array([cloudflareVersion[0], 0]);
// 提取真正的请求数据(去除了 VLESS 头部)。
const rawClientData = chunk.slice(rawDataIndex);
// 如果是 DNS 请求,则使用 UDP 出站处理器。
if (isDns) {
const { write } = await handleUDPOutBound(webSocket, cloudflareResponseHeader, log);
udpStreamWrite = write;
udpStreamWrite(rawClientData); // 将第一个数据块写入 UDP 处理器。
return;
}
// 否则,使用 TCP 出站处理器。
handleTCPOutBound(
remoteSocketWapper,
addressRemote,
portRemote,
rawClientData,
webSocket,
cloudflareResponseHeader,
log
);
},
// 流关闭时的回调。
close() {
log(`readableWebSocketStream is close`);
},
// 流异常终止时的回调。
abort(reason) {
log(`readableWebSocketStream is abort`, JSON.stringify(reason));
},
})
)
// 捕获管道处理过程中的任何错误。
.catch((err) => {
log("readableWebSocketStream pipeTo error", err);
});
// 返回一个 101 Switching Protocols 响应,并将 `client` WebSocket 返回给客户端,
// 完成 WebSocket 握手。
return new Response(null, {
status: 101,
webSocket: client,
});
}
/**
* 检查给定的 UUID 是否存在于一个模拟的 API 响应中。
* @param {string} targetUuid The UUID to search for.
* @returns {Promise<boolean>} A Promise that resolves to true if the UUID is present, false otherwise.
*/
async function checkUuidInApiResponse(targetUuid) {
try {
// 获取模拟的 API 响应。
const apiResponse = await getApiResponse();
if (!apiResponse) {
return false;
}
// 检查 UUID 是否在用户列表中。
const isUuidInResponse = apiResponse.users.some((user) => user.uuid === targetUuid);
return isUuidInResponse;
} catch (error) {
console.error("Error:", error);
return false;
}
}
/**
* 模拟一个 API 响应,实际返回一个空的用户列表。
* 这个功能可能是为了未来扩展多用户验证而预留的。
*/
async function getApiResponse() {
return { users: [] };
}
/**
* 处理出站的 TCP 连接。
* @param {{ value: any | null }} remoteSocket - 持有远程 TCP socket 的包装器对象。
* @param {string} addressRemote - 目标地址。
* @param {number} portRemote - 目标端口。
* @param {Uint8Array} rawClientData - 第一个数据块(通常是 TLS Client Hello)。
* @param {WebSocket} webSocket - 到客户端的 WebSocket 连接。
* @param {Uint8Array} cloudflareResponseHeader - VLESS 响应头。
* @param {Function} log - 日志函数。
*/
async function handleTCPOutBound(
remoteSocket,
addressRemote,
portRemote,
rawClientData,
webSocket,
cloudflareResponseHeader,
log
) {
/**
* 连接到目标服务器并写入初始数据。
* @param {string} address - 目标地址
* @param {number} port - 目标端口
* @returns {Promise<any>} - 返回建立的 TCP socket。
*/
async function connectAndWrite(address, port) {
// 这是一个特殊的技巧:如果地址是纯 IP,则在其前后加上固定的字符串,
// 转换成一个域名 `www.<address>.sslip.io`,这可能是为了绕过某些对纯 IP 连接的限制。
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('LnNzbGlwLmlv')}`; // atob('d3d3Lg==') -> "www.", atob('LnNzbGlwLmlv') -> ".sslip.io"
/** @type {any} */
// 使用 `connect` API 建立 TCP 连接。
const tcpSocket = connect({
hostname: address,
port: port,
});
// 将建立的连接保存到包装器中。
remoteSocket.value = tcpSocket;
log(`connected to ${address}:${port}`);
// 写入第一个数据块。
const writer = tcpSocket.writable.getWriter();
await writer.write(rawClientData);
writer.releaseLock();
return tcpSocket;
}
// 如果直接连接目标服务器失败或无响应,则尝试连接到预设的 `proxyIP`。
async function retry() {
const tcpSocket = await connectAndWrite(proxyIP || addressRemote, proxyPort || portRemote);
// 不管重试是否成功,最终都要安全地关闭 WebSocket。
tcpSocket.closed
.catch((error) => {
console.log("retry tcpSocket closed error", error);
})
.finally(() => {
safeCloseWebSocket(webSocket);
});
// 将重试后建立的连接和 WebSocket 连接起来。
remoteSocketToWS(tcpSocket, webSocket, cloudflareResponseHeader, null, log);
}
// 首次尝试直接连接客户端请求的目标地址。
const tcpSocket = await connectAndWrite(addressRemote, portRemote);
// 将 TCP socket 的可读流和 WebSocket 的可写流连接起来,
// 实现从目标服务器到客户端的数据转发。
// `retry` 函数作为回调传入,在连接失败时触发。
remoteSocketToWS(tcpSocket, webSocket, cloudflareResponseHeader, retry, log);
}
/**
* 将 WebSocket 封装成一个 ReadableStream,使其能方便地使用 Stream API。
* @param {WebSocket} webSocketServer - Worker 端的 WebSocket 对象。
* @param {string} earlyDataHeader - 用于 0-RTT 的早期数据。
* @param {(info: string)=> void} log - 日志函数。
* @returns {ReadableStream}
*/
function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) {
let readableStreamCancel = false;
const stream = new ReadableStream({
start(controller) {
// 监听 'message' 事件,当从客户端收到数据时,将其推入流中。
webSocketServer.addEventListener("message", (event) => {
if (readableStreamCancel) {
return;
}
const message = event.data;
controller.enqueue(message);
});
// 监听 'close' 事件,当客户端关闭连接时,也关闭流。
webSocketServer.addEventListener("close", () => {
safeCloseWebSocket(webSocketServer);
if (readableStreamCancel) {
return;
}
controller.close();
});
// 监听 'error' 事件,将错误传递给流。
webSocketServer.addEventListener("error", (err) => {
log("webSocketServer has error");
controller.error(err);
});
// 处理 WebSocket 的 0-RTT 早期数据。
const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
if (error) {
controller.error(error);
} else if (earlyData) {
controller.enqueue(earlyData);
}
},
pull(controller) {
// 拉取数据的逻辑,此处为空,因为 WebSocket 是推送模型。
},
// 当流被取消(通常是下游的 WritableStream 出错)时的回调。
cancel(reason) {
if (readableStreamCancel) {
return;
}
log(`ReadableStream was canceled, due to ${reason}`);
readableStreamCancel = true;
safeCloseWebSocket(webSocketServer); // 安全地关闭 WebSocket。
},
});
return stream;
}
// VLESS 协议头部格式文档:
// https://xtls.github.io/development/protocols/cloudflare.html
// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw
/**
* 解析 VLESS 协议头部。
* @param {ArrayBuffer} cloudflareBuffer - 从客户端收到的第一个数据块。
* @param {string} userID - 用户的 UUID。
* @returns {Promise<object>} - 返回解析结果的对象。
*/
async function processcloudflareHeader(cloudflareBuffer, userID) {
// VLESS 头部至少需要 24 字节。
if (cloudflareBuffer.byteLength < 24) {
return { hasError: true, message: "invalid data" };
}
// 第 1 字节:协议版本
const version = new Uint8Array(cloudflareBuffer.slice(0, 1));
let isValidUser = false;
let isUDP = false;
// 接下来 16 字节:用户 UUID
const slicedBuffer = new Uint8Array(cloudflareBuffer.slice(1, 17));
// 将 UUID 字节数组转换为字符串。
const slicedBufferString = stringify(slicedBuffer);
// 支持多个 UUID,以逗号分隔。
const uuids = userID.includes(",") ? userID.split(",") : [userID];
// 检查 UUID 是否在模拟的 API 响应中(当前此功能无效)。
const checkUuidInApi = await checkUuidInApiResponse(slicedBufferString);
// 验证客户端 UUID 是否匹配。
isValidUser = uuids.some((userUuid) => checkUuidInApi || slicedBufferString === userUuid.trim());
console.log(`checkUuidInApi: ${await checkUuidInApiResponse(slicedBufferString)}, userID: ${slicedBufferString}`);
if (!isValidUser) {
return { hasError: true, message: "invalid user" };
}
// 第 18 字节:附加信息长度,此处跳过。
const optLength = new Uint8Array(cloudflareBuffer.slice(17, 18))[0];
// 附加信息后的第 1 字节:指令
const command = new Uint8Array(cloudflareBuffer.slice(18 + optLength, 18 + optLength + 1))[0];
// 0x01 TCP
// 0x02 UDP
// 0x03 MUX (不支持)
if (command === 1) {
// TCP
} else if (command === 2) {
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 = cloudflareBuffer.slice(portIndex, portIndex + 2);
const portRemote = new DataView(portBuffer).getUint16(0);
// 接下来 1 字节:目标地址类型
let addressIndex = portIndex + 2;
const addressBuffer = new Uint8Array(cloudflareBuffer.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;
addressValue = new Uint8Array(cloudflareBuffer.slice(addressValueIndex, addressValueIndex + addressLength)).join(".");
break;
case 2: // 域名
addressLength = new Uint8Array(cloudflareBuffer.slice(addressValueIndex, addressValueIndex + 1))[0];
addressValueIndex += 1;
addressValue = new TextDecoder().decode(cloudflareBuffer.slice(addressValueIndex, addressValueIndex + addressLength));
break;
case 3: // IPv6
addressLength = 16;
const dataView = new DataView(cloudflareBuffer.slice(addressValueIndex, addressValueIndex + addressLength));
const ipv6 = [];
for (let i = 0; i < 8; i++) {
ipv6.push(dataView.getUint16(i * 2).toString(16));
}
addressValue = ipv6.join(":");
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,
cloudflareVersion: version,
isUDP,
};
}
/**
* 将远程 TCP socket 的数据流转发到 WebSocket。
* @param {any} remoteSocket - 到目标服务器的 TCP socket。
* @param {WebSocket} webSocket - 到客户端的 WebSocket 连接。
* @param {ArrayBuffer} cloudflareResponseHeader - VLESS 响应头。
* @param {(() => Promise<void>) | null} retry - 连接失败时的重试函数。
* @param {*} log - 日志函数。
*/
async function remoteSocketToWS(remoteSocket, webSocket, cloudflareResponseHeader, retry, log) {
// remote--> ws
let remoteChunkCount = 0;
let chunks = [];
let cloudflareHeader = cloudflareResponseHeader;
let hasIncomingData = false; // 标记是否收到了来自目标服务器的数据。
// 将远程 socket 的可读流通过管道连接到可写流。
await remoteSocket.readable
.pipeTo(
new WritableStream({
start() {},
/**
* 当从目标服务器收到数据时调用。
* @param {Uint8Array} chunk - 数据块。
* @param {*} controller
*/
async write(chunk, controller) {
hasIncomingData = true;
// 检查 WebSocket 是否仍然打开。
if (webSocket.readyState !== WS_READY_STATE_OPEN) {
controller.error("webSocket.readyState is not open, maybe close");
}
// 对于第一个数据块,需要先发送 VLESS 响应头。
if (cloudflareHeader) {
webSocket.send(await new Blob([cloudflareHeader, chunk]).arrayBuffer());
cloudflareHeader = null; // 清空头部,后续不再发送。
} else {
// 直接转发数据。
webSocket.send(chunk);
}
},
// 远程连接关闭时的回调。
close() {
log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`);
},
// 远程连接异常终止时的回调。
abort(reason) {
console.error(`remoteConnection!.readable abort`, reason);
},
})
)
.catch((error) => {
console.error(`remoteSocketToWS has exception `, error.stack || error);
safeCloseWebSocket(webSocket);
});
// 如果连接建立后没有收到任何数据,并且有重试函数,则调用重试。
// 这可以处理目标服务器不响应或连接被中间设备拦截的情况。
if (hasIncomingData === false && retry) {
log(`retry`);
retry();
}
}
/**
* 将 Base64 字符串解码为 ArrayBuffer。
* @param {string} base64Str
* @returns {{earlyData: ArrayBuffer, error: Error | null}}
*/
function base64ToArrayBuffer(base64Str) {
if (!base64Str) {
return { error: null };
}
try {
// 替换 URL-safe Base64 字符为标准字符。
base64Str = base64Str.replace(/-/g, "+").replace(/_/g, "/");
// 使用 atob 解码。
const decode = atob(base64Str);
const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0));
return { earlyData: arryBuffer.buffer, error: null };
} catch (error) {
return { error };
}
}
/**
* 验证字符串是否符合 UUID v4 格式。
* @param {string} uuid
*/
function isValidUUID(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;
return uuidRegex.test(uuid);
}
// WebSocket 的状态常量
const WS_READY_STATE_OPEN = 1;
const WS_READY_STATE_CLOSING = 2;
/**
* 安全地关闭 WebSocket 连接,避免在已关闭的连接上再次调用 close() 导致异常。
* @param {WebSocket} socket
*/
function safeCloseWebSocket(socket) {
try {
if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) {
socket.close();
}
} catch (error) {
console.error("safeCloseWebSocket error", error);
}
}
// 预先计算好 0-255 的十六进制表示,用于提高 UUID 字符串化的性能。
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
/**
* 将 16 字节的数组快速转换为 UUID 格式的字符串(不进行验证)。
* @param {Uint8Array} arr - 字节数组
* @param {number} offset - 起始偏移
* @returns {string}
*/
function unsafeStringify(arr, offset = 0) {
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 字符串,并进行格式验证。
* @param {Uint8Array} arr - 字节数组
* @param {number} offset - 起始偏移
* @returns {string}
*/
function stringify(arr, offset = 0) {
const uuid = unsafeStringify(arr, offset);
if (!isValidUUID(uuid)) {
throw TypeError("Stringified UUID is invalid");
}
return uuid;
}
/**
* 处理出站的 UDP 流量(仅限 DNS)。
* @param {WebSocket} webSocket - 到客户端的 WebSocket 连接。
* @param {ArrayBuffer} cloudflareResponseHeader - VLESS 响应头。
* @param {(string)=> void} log - 日志函数。
* @returns {Promise<{write: (chunk: Uint8Array) => void}>} 返回一个包含 write 方法的对象。
*/
async function handleUDPOutBound(webSocket, cloudflareResponseHeader, log) {
let iscloudflareHeaderSent = false;
const transformStream = new TransformStream({
start(controller) {},
// 转换函数,处理 VLESS UDP 数据包格式。
transform(chunk, controller) {
// VLESS UDP 格式:[2字节长度][UDP负载]...
for (let index = 0; index < chunk.byteLength; ) {
// 读取 2 字节的长度信息。
const lengthBuffer = chunk.slice(index, index + 2);
const udpPakcetLength = new DataView(lengthBuffer).getUint16(0);
// 读取 UDP 负载。
const udpData = new Uint8Array(chunk.slice(index + 2, index + 2 + udpPakcetLength));
index = index + 2 + udpPakcetLength;
// 将解析出的 UDP 负载推入下一个流。
controller.enqueue(udpData);
}
},
flush(controller) {},
});
// 将转换后的流(纯 DNS 查询报文)通过管道连接到可写流。
transformStream.readable
.pipeTo(
new WritableStream({
// 当有 DNS 查询报文时调用。
async write(chunk) {
// 使用 fetch API 将 DNS 查询发送到 DoH 服务器。
const resp = await fetch(
dohURL,
{
method: "POST",
headers: {
"content-type": "application/dns-message",
},
body: chunk,
}
);
// 获取 DoH 服务器返回的 DNS 响应。
const dnsQueryResult = await resp.arrayBuffer();
const udpSize = dnsQueryResult.byteLength;
// 将 DNS 响应重新打包成 VLESS UDP 格式。
const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]);
if (webSocket.readyState === WS_READY_STATE_OPEN) {
log(`doh success and dns message length is ${udpSize}`);
// 如果是第一个响应包,需要附加 VLESS 响应头。
if (iscloudflareHeaderSent) {
webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer());
} else {
webSocket.send(await new Blob([cloudflareResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer());
iscloudflareHeaderSent = true;
}
}
},
})
)
.catch((error) => {
log("dns udp has error" + error);
});
// 获取 transformStream 的写入器。
const writer = transformStream.writable.getWriter();
return {
/**
* 返回一个 write 方法,用于接收上游传来的 VLESS UDP 数据。
* @param {Uint8Array} chunk
*/
write(chunk) {
writer.write(chunk);
},
};
}
/**
* 生成主配置页面(HTML)。
* @param {string} userID - 用户 UUID。
* @param {string | null} hostName - 当前请求的主机名。
* @returns {string} - 返回生成的 HTML 字符串。
*/
function get\u0076\u006c\u0065\u0073\u0073Config(userID, hostName) {
// 生成 VLESS + WS (no-tls) 和 VLESS + WS + TLS 两种单节点分享链接。
// 注意,函数名和变量名中的 `vless` 被 Unicode 混淆。
const w\u0076\u006c\u0065\u0073\u0073ws = `\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${CDNIP}:8880?encryption=none&security=none&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#${hostName}`;
const p\u0076\u006c\u0065\u0073\u0073wstls = `\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${CDNIP}:8443?encryption=none&security=tls&type=ws&host=${hostName}&sni=${hostName}&fp=random&path=%2F%3Fed%3D2560#${hostName}`;
const note = `甬哥博客地址:https://ygkkk.blogspot.com\n甬哥YouTube频道:https://www.youtube.com/@ygkkk\n甬哥TG电报群组:https://t.me/ygkkktg\n甬哥TG电报频道:https://t.me/ygkkktgpd\n\nProxyIP全局运行中:${proxyIP}:${proxyPort}`;
// 生成各种订阅链接的 URL。
const ty = `https://${hostName}/${userID}/ty` // 通用
const cl = `https://${hostName}/${userID}/cl` // Clash
const sb = `https://${hostName}/${userID}/sb` // Sing-box
const pty = `https://${hostName}/${userID}/pty` // 通用 (TLS-only)
const pcl = `https://${hostName}/${userID}/pcl` // Clash (TLS-only)
const psb = `https://${hostName}/${userID}/psb` // Sing-box (TLS-only)
// 生成包含所有 13 个节点的通用分享链接(Base64 编码)。
const wk\u0076\u006c\u0065\u0073\u0073share = btoa(`\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP1}:${PT1}?encryption=none&security=none&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V1_${IP1}_${PT1}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP2}:${PT2}?encryption=none&security=none&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V2_${IP2}_${PT2}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP3}:${PT3}?encryption=none&security=none&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V3_${IP3}_${PT3}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP4}:${PT4}?encryption=none&security=none&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V4_${IP4}_${PT4}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP5}:${PT5}?encryption=none&security=none&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V5_${IP5}_${PT5}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP6}:${PT6}?encryption=none&security=none&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V6_${IP6}_${PT6}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP7}:${PT7}?encryption=none&security=none&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V7_${IP7}_${PT7}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP8}:${PT8}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V8_${IP8}_${PT8}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP9}:${PT9}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V9_${IP9}_${PT9}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP10}:${PT10}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V10_${IP10}_${PT10}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP11}:${PT11}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V11_${IP11}_${PT11}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP12}:${PT12}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V12_${IP12}_${PT12}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP13}:${PT13}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V13_${IP13}_${PT13}`);
// 生成只包含 TLS 节点的通用分享链接(Base64 编码)。
const pg\u0076\u006c\u0065\u0073\u0073share = btoa(`\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP8}:${PT8}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V8_${IP8}_${PT8}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP9}:${PT9}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V9_${IP9}_${PT9}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP10}:${PT10}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V10_${IP10}_${PT10}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP11}:${PT11}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V11_${IP11}_${PT11}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP12}:${PT12}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V12_${IP12}_${PT12}\n\u0076\u006c\u0065\u0073\u0073\u003A//${userID}\u0040${IP13}:${PT13}?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#CF_V13_${IP13}_${PT13}`);
// 将备注中的换行符替换为 HTML 的 <br> 标签。
const noteshow = note.replace(/\n/g, '<br>');
// 定义 HTML 页面的头部和样式,以及一个用于复制文本到剪贴板的 JavaScript 函数。
const displayHtml = `
...// 此处省略了大部分 HTML 结构代码
`;
// 根据主机名是否为 workers.dev 的子域名,显示不同的页面内容。
// workers.dev 域名显示包含非 TLS 和 TLS 节点的完整版本。
if (hostName.includes("workers.dev")) {
return `
...// 此处省略了 workers.dev 域名的 HTML 结构代码
`;
} else {
// 自定义域名或 Pages 域名只显示 TLS 节点版本。
return `
...// 此处省略了自定义域名的 HTML 结构代码
`;
}
}
/**
* 生成通用订阅内容 (Base64 编码,包含所有节点)。
* @param {string} userID
* @param {string} hostName
* @returns {string} Base64 编码的 VLESS 链接列表
*/
function gettyConfig(userID, hostName) {
const \u0076\u006c\u0065\u0073\u0073share = btoa(`...`); // 同上,省略了所有节点的拼接
return `${\u0076\u006c\u0065\u0073\u0073share}`
}
/**
* 生成 Clash-meta 订阅配置文件 (YAML 格式,包含所有节点)。
* @param {string} userID
* @param {string} hostName
* @returns {string} YAML 格式的配置
*/
function getclConfig(userID, hostName) {
return `
port: 7890
allow-lan: true
...// 此处省略了完整的 Clash YAML 配置模板
proxies:
- name: CF_V1_${IP1}_${PT1}
type: \u0076\u006c\u0065\u0073\u0073
server: ${IP1.replace(/[\[\]]/g, '')}
port: ${PT1}
uuid: ${userID}
...// 动态填充所有节点
proxy-groups:
...// 预设的代理组
rules:
...// 预设的规则
`
}
/**
* 生成 Sing-box 订阅配置文件 (JSON 格式,包含所有节点)。
* @param {string} userID
* @param {string} hostName
* @returns {string} JSON 格式的配置
*/
function getsbConfig(userID, hostName) {
return `{
"log": {...},
"experimental": {...},
"dns": {...},
"inbounds": [...],
"outbounds": [
{
"tag": "select",
"type": "selector",
...// 动态填充所有节点
},
...
],
"route": {...}
}`
}
/**
* 生成通用订阅内容 (Base64 编码,仅 TLS 节点)。
* @param {string} userID
* @param {string} hostName
* @returns {string}
*/
function getptyConfig(userID, hostName) {
const \u0076\u006c\u0065\u0073\u0073share = btoa(`...`); // 省略了 TLS 节点的拼接
return `${\u0076\u006c\u0065\u0073\u0073share}`
}
/**
* 生成 Clash-meta 订阅配置文件 (YAML 格式,仅 TLS 节点)。
* @param {string} userID
* @param {string} hostName
* @returns {string}
*/
function getpclConfig(userID, hostName) {
return `
...// 省略了仅包含 TLS 节点的 Clash YAML 配置
`
}
/**
* 生成 Sing-box 订阅配置文件 (JSON 格式,仅 TLS 节点)。
* @param {string} userID
* @param {string} hostName
* @returns {string}
*/
function getpsbConfig(userID, hostName) {
return `{
...// 省略了仅包含 TLS 节点的 Sing-box JSON 配置
}`
}
cloudfare Vless workers pages详细说明
发布于: 6/19/2025 | 分类: 技术