前言
本实验将会为大家解析cloud flare的反向解析代理服务如何搭建,works如何创建等等。本文中教学创建的实例已在文章编写结束后释放,该项技术不可用于违法用途!违者自行承担后果!!
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
原理拓扑图
一、知识链条
1、Cloud flare简介
Cloudflare 是一家全球性的云服务提供商,成立于2009年。它提供了一系列网络基础设施和安全性服务,帮助网站加速、保护、以及优化其在线内容。
Cloudflare 提供的主要服务包括:
1. CDN(内容分发网络):通过将内容分发到全球性的数据中心,加速网站加载速度,提高用户体验。
2. DDoS 攻击防护:保护网站免受分布式拒绝服务(DDoS)攻击,确保服务的可用性和稳定性。
3. WAF(网络应用防火墙):监控和过滤网站流量,阻止恶意攻击、SQL 注入和跨站脚本等网络攻击。
4. DNS(域名解析服务):提供快速、可靠的域名解析服务,帮助网站管理和优化其网络流量。
5. SSL/TLS 加密:提供免费的 SSL/TLS 加密证书,加密网站与访问者之间的通信,保护数据安全和隐私。
6. Workers:允许开发者在 Cloudflare 的边缘网络上运行自定义的代码,以实现更高级的功能和定制化的处理。
Cloudflare 的服务被广泛应用于各种规模的网站和在线应用程序中,包括企业网站、电子商务平台、博客、游戏、移动应用等。其目标是通过提供简单易用、高性能和高安全性的服务,帮助客户在互联网上取得成功。同时,Cloud flare持有全球顶级DNS1.1.1.1,可见其实力不凡。
=========================================================================
2、VLess协议
vless 协议是一个相对于 V2Ray 的一种新的协议,它是 V2Ray 的一个轻量级替代品。vless 的设计目标是减少传输数据的开销,提高性能,并且简化配置。
vless 协议的原理基本上与 V2Ray 中的 vmess 协议类似,都是在传输层上实现的一个加密隧道,用于在客户端和服务器之间传输数据。但是 vless 相对于 vmess 有一些不同之处:
1. **减少传输数据的开销**:vless 协议在传输数据时采用了更加精简的数据格式,以减少传输数据的开销,提高传输效率。
2. **简化配置**:相对于 vmess,vless 的配置更加简单,主要是因为它省略了一些在某些情况下不必要的选项和字段。
3. **增强安全性**:vless 在安全性方面与 vmess 相当,都是采用了 TLS 加密和传输,保障了数据的安全性和隐私。
4. **性能提升**:由于 vless 减少了数据传输的开销,并且在设计上更加精简,因此在一些场景下可能会提供更好的性能表现。
总的来说,vless 协议是为了简化配置、提高性能、减少传输开销而设计的一种协议,它保留了 vmess 协议的安全性和灵活性,同时简化了配置和提高了性能。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3、Cloud flare-works实例
Cloudflare的"Works"是Cloudflare推出的一系列整合服务,旨在为企业提供更全面的网络安全和性能优化解决方案。Works整合了Cloudflare的不同产品,包括:
1. **Access:** 提供零信任访问控制,确保用户可以安全地访问企业网络和应用程序,无论他们身在何处。
2. **Gateway:** 提供安全的互联网访问,包括网络内容过滤、恶意软件防护、广告拦截和隐私保护。
3. **Spectrum:** 扩展Cloudflare的边缘网络保护到任何TCP/UDP流量,使得所有的网络流量都能受到Cloudflare的保护。
4. **Workers:** 允许开发者在Cloudflare的边缘网络上运行轻量级的JavaScript代码,从而实现定制化的网络功能和逻辑。
5. **Orbit:** 提供实时的API和事件监控,使得开发者可以实时地查看和分析其应用程序的性能和行为。
通过将这些服务整合在一起,Cloudflare Works可以为企业提供更强大、更全面的网络安全和性能优化解决方案,帮助他们保护应用程序、数据和用户,并提高网络性能和可用性。
4、UUID
UUID(Universally Unique Identifier)是一种标识符,用于在计算系统中唯一地标识信息或实体。它是由一个128位的数字组成,通常以32个十六进制数字的形式表示,中间用连字符分隔。UUID的生成算法保证了在理论上具有非常低的重复概率。
UUID广泛应用于各种场景,例如:
1. 数据库系统:UUID可以用作数据库表格的主键,确保每个记录在整个数据库中具有唯一标识。
2. 分布式系统:在分布式系统中,UUID可以用于唯一标识各个节点、任务或事件,以确保全局唯一性。
3. 软件开发:开发人员可以使用UUID作为临时文件名、会话标识符或唯一的标识符来跟踪和管理数据。
4. 网络通信:UUID可以用于标识网络协议中的消息、数据包或会话,以确保唯一性和识别性。
总之,UUID是一种在计算系统中用于唯一标识信息或实体的标识符,具有广泛的应用领域。
接下来我们开始创建workers实例
二、创建workers
1、访问cloud flare
先进入官网>>>Cloud flare<<<
请记住,访问cloud flare时,会有人机验证,照常过就行,如果无法通过,请开启霍格沃兹力量或者切换IP即可。
这里已经进入首页,可以在右上角调整语言设置:
首先,你要有非CN的账号,(推荐使用Google)我这里使用Google/US账户进行注册登录:
进入cloud flare首页,在左侧找到workers和pages,进入。
2、创建workers
进入创建应用程序,开始创建一个workers实例。
给你的workers创建一个名称,我这里就使用ceshi,然后点击部署。
部署完成后,点击编辑代码。
将默认原来的代码全部清除
3、实例代码部署
edgetunnel/src/worker-vless.js at main · leilei223/edgetunnel · GitHub
实例代码1、(本章节使用)
清除后导入workers代码
// <!--GAMFC-->version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC<!--GAMFC-END-->.
// @ts-ignore
import { connect } from 'cloudflare:sockets';
// How to generate your own UUID:
// [Windows] Press "Win + R", input cmd and run: Powershell -NoExit -Command "[guid]::NewGuid()"
let userID = 'UUID';
let proxyIP = 'CDN-IP';
if (!isValidUUID(userID)) {
throw new Error('uuid is not valid');
}
export default {
/**
* @param {import("@cloudflare/workers-types").Request} request
* @param {{UUID: string, PROXYIP: string}} env
* @param {import("@cloudflare/workers-types").ExecutionContext} ctx
* @returns {Promise<Response>}
*/
async fetch(request, env, ctx) {
try {
userID = env.UUID || userID;
proxyIP = env.PROXYIP || proxyIP;
const upgradeHeader = request.headers.get('Upgrade');
if (!upgradeHeader || upgradeHeader !== 'websocket') {
const url = new URL(request.url);
switch (url.pathname) {
case '/':
return new Response(JSON.stringify(request.cf), { status: 200 });
case `/${userID}`: {
const vlessConfig = getVLESSConfig(userID, request.headers.get('Host'));
return new Response(`${vlessConfig}`, {
status: 200,
headers: {
"Content-Type": "text/plain;charset=utf-8",
}
});
}
default:
return new Response('Not found', { status: 404 });
}
} else {
return await vlessOverWSHandler(request);
}
} catch (err) {
/** @type {Error} */ let e = err;
return new Response(e.toString());
}
},
};
/**
*
* @param {import("@cloudflare/workers-types").Request} request
*/
async function vlessOverWSHandler(request) {
/** @type {import("@cloudflare/workers-types").WebSocket[]} */
// @ts-ignore
const webSocketPair = new WebSocketPair();
const [client, webSocket] = Object.values(webSocketPair);
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') || '';
const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
/** @type {{ value: import("@cloudflare/workers-types").Socket | null}}*/
let remoteSocketWapper = {
value: null,
};
let udpStreamWrite = null;
let isDns = false;
// ws --> remote
readableWebSocketStream.pipeTo(new WritableStream({
async write(chunk, controller) {
if (isDns && udpStreamWrite) {
return udpStreamWrite(chunk);
}
if (remoteSocketWapper.value) {
const writer = remoteSocketWapper.value.writable.getWriter()
await writer.write(chunk);
writer.releaseLock();
return;
}
const {
hasError,
message,
portRemote = 443,
addressRemote = '',
rawDataIndex,
vlessVersion = new Uint8Array([0, 0]),
isUDP,
} = processVlessHeader(chunk, userID);
address = addressRemote;
portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp '
} `;
if (hasError) {
// controller.error(message);
throw new Error(message); // cf seems has bug, controller.error will not end stream
// webSocket.close(1000, message);
return;
}
// if UDP but port not DNS port, close it
if (isUDP) {
if (portRemote === 53) {
isDns = true;
} else {
// controller.error('UDP proxy only enable for DNS which is port 53');
throw new Error('UDP proxy only enable for DNS which is port 53'); // cf seems has bug, controller.error will not end stream
return;
}
}
// ["version", "附加信息长度 N"]
const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
const rawClientData = chunk.slice(rawDataIndex);
// TODO: support udp here when cf runtime has udp support
if (isDns) {
const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log);
udpStreamWrite = write;
udpStreamWrite(rawClientData);
return;
}
handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);
},
close() {
log(`readableWebSocketStream is close`);
},
abort(reason) {
log(`readableWebSocketStream is abort`, JSON.stringify(reason));
},
})).catch((err) => {
log('readableWebSocketStream pipeTo error', err);
});
return new Response(null, {
status: 101,
// @ts-ignore
webSocket: client,
});
}
/**
* Handles outbound TCP connections.
*
* @param {any} remoteSocket
* @param {string} addressRemote The remote address to connect to.
* @param {number} portRemote The remote port to connect to.
* @param {Uint8Array} rawClientData The raw client data to write.
* @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to pass the remote socket to.
* @param {Uint8Array} vlessResponseHeader The VLESS response header.
* @param {function} log The logging function.
* @returns {Promise<void>} The remote socket.
*/
async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) {
async function connectAndWrite(address, port) {
/** @type {import("@cloudflare/workers-types").Socket} */
const tcpSocket = connect({
hostname: address,
port: port,
});
remoteSocket.value = tcpSocket;
log(`connected to ${address}:${port}`);
const writer = tcpSocket.writable.getWriter();
await writer.write(rawClientData); // first write, nomal is tls client hello
writer.releaseLock();
return tcpSocket;
}
// if the cf connect tcp socket have no incoming data, we retry to redirect ip
async function retry() {
const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote)
// no matter retry success or not, close websocket
tcpSocket.closed.catch(error => {
console.log('retry tcpSocket closed error', error);
}).finally(() => {
safeCloseWebSocket(webSocket);
})
remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log);
}
const tcpSocket = await connectAndWrite(addressRemote, portRemote);
// when remoteSocket is ready, pass to websocket
// remote--> ws
remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log);
}
/**
*
* @param {import("@cloudflare/workers-types").WebSocket} webSocketServer
* @param {string} earlyDataHeader for ws 0rtt
* @param {(info: string)=> void} log for ws 0rtt
*/
function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) {
let readableStreamCancel = false;
const stream = new ReadableStream({
start(controller) {
webSocketServer.addEventListener('message', (event) => {
if (readableStreamCancel) {
return;
}
const message = event.data;
controller.enqueue(message);
});
// The event means that the client closed the client -> server stream.
// However, the server -> client stream is still open until you call close() on the server side.
// The WebSocket protocol says that a separate close message must be sent in each direction to fully close the socket.
webSocketServer.addEventListener('close', () => {
// client send close, need close server
// if stream is cancel, skip controller.close
safeCloseWebSocket(webSocketServer);
if (readableStreamCancel) {
return;
}
controller.close();
}
);
webSocketServer.addEventListener('error', (err) => {
log('webSocketServer has error');
controller.error(err);
}
);
// for ws 0rtt
const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
if (error) {
controller.error(error);
} else if (earlyData) {
controller.enqueue(earlyData);
}
},
pull(controller) {
// if ws can stop read if stream is full, we can implement backpressure
// https://streams.spec.whatwg.org/#example-rs-push-backpressure
},
cancel(reason) {
// 1. pipe WritableStream has error, this cancel will called, so ws handle server close into here
// 2. if readableStream is cancel, all controller.close/enqueue need skip,
// 3. but from testing controller.error still work even if readableStream is cancel
if (readableStreamCancel) {
return;
}
log(`ReadableStream was canceled, due to ${reason}`)
readableStreamCancel = true;
safeCloseWebSocket(webSocketServer);
}
});
return stream;
}
// https://xtls.github.io/development/protocols/vless.html
// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw
/**
*
* @param { ArrayBuffer} vlessBuffer
* @param {string} userID
* @returns
*/
function processVlessHeader(
vlessBuffer,
userID
) {
if (vlessBuffer.byteLength < 24) {
return {
hasError: true,
message: 'invalid data',
};
}
const version = new Uint8Array(vlessBuffer.slice(0, 1));
let isValidUser = false;
let isUDP = false;
if (stringify(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) {
isValidUser = true;
}
if (!isValidUser) {
return {
hasError: true,
message: 'invalid user',
};
}
const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];
//skip opt for now
const command = new Uint8Array(
vlessBuffer.slice(18 + optLength, 18 + optLength + 1)
)[0];
// 0x01 TCP
// 0x02 UDP
// 0x03 MUX
if (command === 1) {
} else if (command === 2) {
isUDP = true;
} else {
return {
hasError: true,
message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`,
};
}
const portIndex = 18 + optLength + 1;
const portBuffer = vlessBuffer.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(
vlessBuffer.slice(addressIndex, addressIndex + 1)
);
// 1--> ipv4 addressLength =4
// 2--> domain name addressLength=addressBuffer[1]
// 3--> ipv6 addressLength =16
const addressType = addressBuffer[0];
let addressLength = 0;
let addressValueIndex = addressIndex + 1;
let addressValue = '';
switch (addressType) {
case 1:
addressLength = 4;
addressValue = new Uint8Array(
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
).join('.');
break;
case 2:
addressLength = new Uint8Array(
vlessBuffer.slice(addressValueIndex, addressValueIndex + 1)
)[0];
addressValueIndex += 1;
addressValue = new TextDecoder().decode(
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
);
break;
case 3:
addressLength = 16;
const dataView = new DataView(
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
);
// 2001:0db8:85a3:0000:0000:8a2e:0370:7334
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,
vlessVersion: version,
isUDP,
};
}
/**
*
* @param {import("@cloudflare/workers-types").Socket} remoteSocket
* @param {import("@cloudflare/workers-types").WebSocket} webSocket
* @param {ArrayBuffer} vlessResponseHeader
* @param {(() => Promise<void>) | null} retry
* @param {*} log
*/
async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) {
// remote--> ws
let remoteChunkCount = 0;
let chunks = [];
/** @type {ArrayBuffer | null} */
let vlessHeader = vlessResponseHeader;
let hasIncomingData = false; // check if remoteSocket has incoming data
await remoteSocket.readable
.pipeTo(
new WritableStream({
start() {
},
/**
*
* @param {Uint8Array} chunk
* @param {*} controller
*/
async write(chunk, controller) {
hasIncomingData = true;
// remoteChunkCount++;
if (webSocket.readyState !== WS_READY_STATE_OPEN) {
controller.error(
'webSocket.readyState is not open, maybe close'
);
}
if (vlessHeader) {
webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());
vlessHeader = null;
} else {
// seems no need rate limit this, CF seems fix this??..
// 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}`);
// safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway.
},
abort(reason) {
console.error(`remoteConnection!.readable abort`, reason);
},
})
)
.catch((error) => {
console.error(
`remoteSocketToWS has exception `,
error.stack || error
);
safeCloseWebSocket(webSocket);
});
// seems is cf connect socket have error,
// 1. Socket.closed will have error
// 2. Socket.readable will be close without any data coming
if (hasIncomingData === false && retry) {
log(`retry`)
retry();
}
}
/**
*
* @param {string} base64Str
* @returns
*/
function base64ToArrayBuffer(base64Str) {
if (!base64Str) {
return { error: null };
}
try {
// go use modified Base64 for URL rfc4648 which js atob not support
base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/');
const decode = atob(base64Str);
const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0));
return { earlyData: arryBuffer.buffer, error: null };
} catch (error) {
return { error };
}
}
/**
* This is not real UUID validation
* @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);
}
const WS_READY_STATE_OPEN = 1;
const WS_READY_STATE_CLOSING = 2;
/**
* Normally, WebSocket will not has exceptions when close.
* @param {import("@cloudflare/workers-types").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);
}
}
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
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();
}
function stringify(arr, offset = 0) {
const uuid = unsafeStringify(arr, offset);
if (!isValidUUID(uuid)) {
throw TypeError("Stringified UUID is invalid");
}
return uuid;
}
/**
*
* @param {import("@cloudflare/workers-types").WebSocket} webSocket
* @param {ArrayBuffer} vlessResponseHeader
* @param {(string)=> void} log
*/
async function handleUDPOutBound(webSocket, vlessResponseHeader, log) {
let isVlessHeaderSent = false;
const transformStream = new TransformStream({
start(controller) {
},
transform(chunk, controller) {
// udp message 2 byte is the the length of udp data
// TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message
for (let index = 0; index < chunk.byteLength;) {
const lengthBuffer = chunk.slice(index, index + 2);
const udpPakcetLength = new DataView(lengthBuffer).getUint16(0);
const udpData = new Uint8Array(
chunk.slice(index + 2, index + 2 + udpPakcetLength)
);
index = index + 2 + udpPakcetLength;
controller.enqueue(udpData);
}
},
flush(controller) {
}
});
// only handle dns udp for now
transformStream.readable.pipeTo(new WritableStream({
async write(chunk) {
const resp = await fetch('https://1.1.1.1/dns-query',
{
method: 'POST',
headers: {
'content-type': 'application/dns-message',
},
body: chunk,
})
const dnsQueryResult = await resp.arrayBuffer();
const udpSize = dnsQueryResult.byteLength;
// console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16)));
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}`);
if (isVlessHeaderSent) {
webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer());
} else {
webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer());
isVlessHeaderSent = true;
}
}
}
})).catch((error) => {
log('dns udp has error' + error)
});
const writer = transformStream.writable.getWriter();
return {
/**
*
* @param {Uint8Array} chunk
*/
write(chunk) {
writer.write(chunk);
}
};
}
/**
*
* @param {string} userID
* @param {string | null} hostName
* @returns {string}
*/
function getVLESSConfig(userID, hostName) {
const vlessMain = `vless://${userID}@${hostName}:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`
return `
################################################################
v2ray
---------------------------------------------------------------
${vlessMain}
---------------------------------------------------------------
################################################################
clash-meta
---------------------------------------------------------------
- type: vless
name: ${hostName}
server: ${hostName}
port: 443
uuid: ${userID}
network: ws
tls: true
udp: false
sni: ${hostName}
client-fingerprint: chrome
ws-opts:
path: "/?ed=2048"
headers:
host: ${hostName}
---------------------------------------------------------------
################################################################
`;
}
代码文件workers.js同时存于GitHub
实例代码2、(进阶玩家使用)
提供给有技术能力的极客玩家和开发者们自行开发,支持伪装域名等等,借鉴:E家之长,零度解说。
// @ts-ignore
import { connect } from 'cloudflare:sockets';
@ts-ignore import { connect } from 'cloudflare:sockets';
// How to generate your own UUID:
// [Windows] Press "Win + R", input cmd and run: Powershell -NoExit -Command "[guid]::NewGuid()"
let userID = 'd342d11e-d424-4583-b36e-524ab1f0afa4';
如何生成自己的 UUID: // [Windows] 按“Win + R”,输入 cmd 并运行: Powershell -NoExit -Command “[guid]::NewGuid()” let userID = 'd342d11e-d424-4583-b36e-524ab1f0afa4';
const proxyIPs = ['cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'workers.cloudflare.cyou'];
const proxyIPs = ['cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'workers.cloudflare.cyou'];
// if you want to use ipv6 or single proxyIP, please add comment at this line and remove comment at the next line
let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];
// use single proxyIP instead of random
// let proxyIP = 'cdn.xn--b6gac.eu.org';
// ipv6 proxyIP example remove comment to use
// let proxyIP = "[2a01:4f8:c2c:123f:64:5:6810:c55a]"
如果要使用 ipv6 或单个 proxyIP,请在此行添加注释,并在下一行删除注释 let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];use single proxyIP instead of random // let proxyIP = 'cdn.xn--b6gac.eu.org';ipv6 proxyIP 示例删除要使用的注释 // let proxyIP = “[2a01:4f8:c2c:123f:64:5:6810:c55a]”
let dohURL = 'https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg='; // https://cloudflare-dns.com/dns-query or https://dns.google/dns-query
让 dohURL = 'https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg=';https://cloudflare-dns.com/dns-query 或 https://dns.google/dns-query
if (!isValidUUID(userID)) {
throw new Error('uuid is invalid');
}
if (!isValidUUID(userID)) { throw new Error('uuid is invalid');
export default {
/**
* @param {import("@cloudflare/workers-types").Request} request
* @param {{UUID: string, PROXYIP: string, DNS_RESOLVER_URL: string, NODE_ID: int, API_HOST: string, API_TOKEN: string}} env
* @param {import("@cloudflare/workers-types").ExecutionContext} ctx
* @returns {Promise<Response>}
*/
async fetch(request, env, ctx) {
// uuid_validator(request);
try {
userID = env.UUID || userID;
proxyIP = env.PROXYIP || proxyIP;
dohURL = env.DNS_RESOLVER_URL || dohURL;
let userID_Path = userID;
if (userID.includes(',')) {
userID_Path = userID.split(',')[0];
}
const upgradeHeader = request.headers.get('Upgrade');
if (!upgradeHeader || upgradeHeader !== 'websocket') {
const url = new URL(request.url);
switch (url.pathname) {
case `/cf`: {
return new Response(JSON.stringify(request.cf, null, 4), {
status: 200,
headers: {
"Content-Type": "application/json;charset=utf-8",
},
});
}
case `/${userID_Path}`: {
const vlessConfig = getVLESSConfig(userID, request.headers.get('Host'));
return new Response(`${vlessConfig}`, {
status: 200,
headers: {
"Content-Type": "text/html; charset=utf-8",
}
});
};
case `/sub/${userID_Path}`: {
const url = new URL(request.url);
const searchParams = url.searchParams;
const vlessSubConfig = createVLESSSub(userID, request.headers.get('Host'));
// Construct and return response object
return new Response(btoa(vlessSubConfig), {
status: 200,
headers: {
"Content-Type": "text/plain;charset=utf-8",
}
});
};
case `/bestip/${userID_Path}`: {
const headers = request.headers;
const url = `https://sub.xf.free.hr/auto?host=${request.headers.get('Host')}&uuid=${userID}&path=/`;
const bestSubConfig = await fetch(url, { headers: headers });
return bestSubConfig;
};
default:
// return new Response('Not found', { status: 404 });
// For any other path, reverse proxy to 'ramdom website' and return the original response, caching it in the process
const randomHostname = cn_hostnames[Math.floor(Math.random() * cn_hostnames.length)];
const newHeaders = new Headers(request.headers);
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');
// Use fetch to proxy the request to 15 different domains
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' });
// Check for 302 or 301 redirect status and return an error response
if ([301, 302].includes(proxyResponse.status)) {
return new Response(`Redirects to ${randomHostname} are not allowed.`, {
status: 403,
statusText: 'Forbidden',
});
}
// Return the response from the proxy server
return proxyResponse;
}
} else {
return await vlessOverWSHandler(request);
}
} catch (err) {
/** @type {Error} */ let e = err;
return new Response(e.toString());
}
},
};
导出默认值 { /** * @param {import(“@cloudflare/workers-types”)。request} request * @param {{UUID: string, PROXYIP: string, DNS_RESOLVER_URL: string, NODE_ID: int, API_HOST: string, API_TOKEN: string}} env * @param {import(“@cloudflare/workers-types”)。ExecutionContext} ctx * @returns {Promise<Response>} */ async fetch(request, env, ctx) { // uuid_validator(request); try { userID = env.UUID ||用户 ID;proxyIP = 环境。代理IP ||代理IP;dohURL = 环境。DNS_RESOLVER_URL ||dohURL;let userID_Path = userID;if (userID.includes(',')) { userID_Path = userID.split(',')[0]; } const upgradeHeader = request.headers.get('升级');if (!upgradeHeader || upgradeHeader !== 'websocket') { const url = new URL(request.url); switch (url.pathname) { case '/cf': { return new Response(JSON.stringify(request.cf, null, 4), { status: 200, headers: { “Content-Type”: “application/json;charset=utf-8“, }, });} case '/${userID_Path}': { const vlessConfig = getVLESSConfig(userID, request.headers.get('Host')); return new Response('${vlessConfig}', { status: 200, headers: { “Content-Type”: “text/html;charset=utf-8“, } });};case '/sub/${userID_Path}': { const url = new URL(request.url); const searchParams = url.searchParams; const vlessSubConfig = createVLESSSub(userID, request.headers.get('Host')); // 构造并返回响应对象 return new Response(btoa(vlessSubConfig), { status: 200, headers: { “Content-Type”: “text/plain;charset=utf-8“, } });};case '/bestip/${userID_Path}': { const headers = request.headers; const url = 'https://sub.xf.free.hr/auto?host=${request.headers.get('主机')}&uuid=${userID}&path=/';const bestSubConfig = await fetch(url, { headers: headers });返回 bestSubConfig;};default: // 返回 new Response('Not found', { status: 404 });对于任何其他路径,反向代理到“ramdom website”并返回原始响应,将其缓存在进程中 const randomHostname = cn_hostnames[Math.floor(Math.random() * cn_hostnames.length)];const newHeaders = 新标头(request.headers);newHeaders.set('cf-connecting-ip', '1.2.3.4');newHeaders.set('x-转发-for', '1.2.3.4');newHeaders.set('x-real-ip', '1.2.3.4');newHeaders.set('referer', 'https://www.google.com/search?q=edtunnel');使用 fetch 将请求代理到 15 个不同的域 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' });检查 302 或 301 重定向状态并返回错误响应 if ([301, 302].includes(proxyResponse.status)) { return new Response('Redirects to ${randomHostname} are not allowed.', { status: 403, statusText: 'Forbidden', }); } // 从代理服务器返回响应 return proxyResponse;} } else { return await vlessOverWSHandler(request);catch (err) { /** @type {Error} */ let e = err; return new Response(e.toString());} }, };
export async function uuid_validator(request) {
const hostname = request.headers.get('Host');
const currentDate = new Date();
export async function uuid_validator(request) { const hostname = request.headers.get('Host'); const currentDate = new Date();
const subdomain = hostname.split('.')[0];
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
const day = String(currentDate.getDate()).padStart(2, '0');
常量子域 = hostname.split('.')[0];常量年份 = currentDate.getFullYear();常量月 = String(currentDate.getMonth() + 1).padStart(2, '0');const day = String(currentDate.getDate()).padStart(2, '0');
const formattedDate = `${year}-${month}-${day}`;
const formattedDate = '${year}-${month}-${day}';
// const daliy_sub = formattedDate + subdomain
const hashHex = await hashHex_f(subdomain);
// subdomain string contains timestamps utc and uuid string TODO.
console.log(hashHex, subdomain, formattedDate);
}
const daliy_sub = formattedDate + 子域 const hashHex = await hashHex_f(子域);子域字符串包含时间戳 UTC 和 UUID 字符串 TODO。console.log(hashHex, subdomain, formattedDate);}
export async function hashHex_f(string) {
const encoder = new TextEncoder();
const data = encoder.encode(string);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
return hashHex;
}
export async function hashHex_f(string) { const encoder = new TextEncoder(); const data = encoder.encode(string); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); return hashHex; }
/**
* Handles VLESS over WebSocket requests by creating a WebSocket pair, accepting the WebSocket connection, and processing the VLESS header.
* @param {import("@cloudflare/workers-types").Request} request The incoming request object.
* @returns {Promise<Response>} A Promise that resolves to a WebSocket response object.
*/
async function vlessOverWSHandler(request) {
const webSocketPair = new WebSocketPair();
const [client, webSocket] = Object.values(webSocketPair);
webSocket.accept();
/** * 通过创建 WebSocket 对、接受 WebSocket 连接并处理 VLESS 标头来处理 VLESS over WebSocket 请求。* @param {import(“@cloudflare/workers-types”)。Request} request 传入的请求对象。* @returns {Promise} 解<Response>析为 WebSocket 响应对象的 Promise。 */ 异步函数 vlessOverWSHandler(request) { const webSocketPair = new WebSocketPair(); const [client, webSocket] = Object.values(webSocketPair); webSocket.accept();
let address = '';
let portWithRandomLog = '';
let currentDate = new Date();
const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => {
console.log(`[${currentDate} ${address}:${portWithRandomLog}] ${info}`, event || '');
};
const earlyDataHeader = request.headers.get('sec-websocket-protocol') || '';
let address = '';easy portWithRandomLog = '';let currentDate = 新日期();const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => { console.log('[${currentDate} ${address}:${portWithRandomLog}] ${info}', event ||'');};const earlyDataHeader = request.headers.get('sec-websocket-protocol') ||'';
const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
/** @type {{ value: import("@cloudflare/workers-types").Socket | null}}*/
let remoteSocketWapper = {
value: null,
};
let udpStreamWrite = null;
let isDns = false;
/** @type {{ value: import(“@cloudflare/workers-types”)。插座 |null}}*/ let remoteSocketWapper = { value: null, };让 udpStreamWrite = null;let isDns = false;
// ws --> remote
readableWebSocketStream.pipeTo(new WritableStream({
async write(chunk, controller) {
if (isDns && udpStreamWrite) {
return udpStreamWrite(chunk);
}
if (remoteSocketWapper.value) {
const writer = remoteSocketWapper.value.writable.getWriter()
await writer.write(chunk);
writer.releaseLock();
return;
}
ws --> remote readableWebSocketStream.pipeTo(new WritableStream({ async write(chunk, controller) { if (isDns &&; udpStreamWrite) { return udpStreamWrite(chunk); } if (remoteSocketWapper.value) { const writer = remoteSocketWapper.value.writable.getWriter() await writer.write(chunk); writer.releaseLock(); return; }
const {
hasError,
message,
portRemote = 443,
addressRemote = '',
rawDataIndex,
vlessVersion = new Uint8Array([0, 0]),
isUDP,
} = processVlessHeader(chunk, userID);
address = addressRemote;
portWithRandomLog = `${portRemote} ${isUDP ? 'udp' : 'tcp'} `;
if (hasError) {
// controller.error(message);
throw new Error(message); // cf seems has bug, controller.error will not end stream
}
const { hasError, message, portRemote = 443, addressRemote = '', rawDataIndex, vlessVersion = new Uint8Array([0, 0]), isUDP, } = processVlessHeader(chunk, userID);地址 = addressRemote;portWithRandomLog = '${portRemote} ${isUDP ?'udp' : 'tcp'} ';if (hasError) { // controller.error(message); throw new Error(message); // cf 似乎有 bug,controller.error 不会结束流 }
// If UDP and not DNS port, close it
if (isUDP && portRemote !== 53) {
throw new Error('UDP proxy only enabled for DNS which is port 53');
// cf seems has bug, controller.error will not end stream
}
如果 UDP 而不是 DNS 端口,请关闭它 if (isUDP && portRemote !== 53) { throw new Error('UDP proxy only enabled for DNS which is port 53'); // cf 似乎有 bug,controller.error 不会结束流 }
if (isUDP && portRemote === 53) {
isDns = true;
}
if (isUDP && portRemote === 53) { isDns = true;
// ["version", "附加信息长度 N"]
const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
const rawClientData = chunk.slice(rawDataIndex);
[“version”, “附加信息长度 N”] const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);const rawClientData = chunk.slice(rawDataIndex);
// TODO: support udp here when cf runtime has udp support
if (isDns) {
const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log);
udpStreamWrite = write;
udpStreamWrite(rawClientData);
return;
}
handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);
},
close() {
log(`readableWebSocketStream is close`);
},
abort(reason) {
log(`readableWebSocketStream is abort`, JSON.stringify(reason));
},
})).catch((err) => {
log('readableWebSocketStream pipeTo error', err);
});
TODO:当 cf 运行时支持 udp 时,这里支持 udp if (isDns) { const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log); udpStreamWrite = write; udpStreamWrite(rawClientData); return; } handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);}, close() { log('readableWebSocketStream is close'); }, abort(reason) { log('readableWebSocketStream is abort', JSON.stringify(reason)); }, })).catch((err) => { log('readableWebSocketStream pipeTo error', err);
return new Response(null, {
status: 101,
webSocket: client,
});
}
return new Response(null, { status: 101, webSocket: client, });}
/**
* Handles outbound TCP connections.
*
* @param {any} remoteSocket
* @param {string} addressRemote The remote address to connect to.
* @param {number} portRemote The remote port to connect to.
* @param {Uint8Array} rawClientData The raw client data to write.
* @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to pass the remote socket to.
* @param {Uint8Array} vlessResponseHeader The VLESS response header.
* @param {function} log The logging function.
* @returns {Promise<void>} The remote socket.
*/
async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) {
/** * 处理出站 TCP 连接。* * @param {any} remoteSocket * @param {string} addressRemote 要连接到的远程地址。* @param {number} portRemote 要连接到的远程端口。* @param {Uint8Array} rawClientData 要写入的原始客户端数据。* @param {import(“@cloudflare/workers-types”)。WebSocket} webSocket 要将远程套接字传递到的 WebSocket。* @param {Uint8Array} vlessResponseHeader VLESS 响应标头。* @param {function} log 日志记录函数。* @returns {Promise<void>} 远程套接字。 */ 异步函数 handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) {
/**
* Connects to a given address and port and writes data to the socket.
* @param {string} address The address to connect to.
* @param {number} port The port to connect to.
* @returns {Promise<import("@cloudflare/workers-types").Socket>} A Promise that resolves to the connected socket.
*/
async function connectAndWrite(address, port) {
/** @type {import("@cloudflare/workers-types").Socket} */
const tcpSocket = connect({
hostname: address,
port: port,
});
remoteSocket.value = tcpSocket;
log(`connected to ${address}:${port}`);
const writer = tcpSocket.writable.getWriter();
await writer.write(rawClientData); // first write, nomal is tls client hello
writer.releaseLock();
return tcpSocket;
}
/** * 连接到给定的地址和端口,并将数据写入套接字。* @param {string} address 要连接到的地址。* @param {number} port 要连接到的端口。* @returns {Promise<import(“@cloudflare/workers-types”)。Socket>} 解析为连接的套接字的 Promise。*/ 异步函数 connectAndWrite(address, port) { /** @type {import(“@cloudflare/workers-types”)。套接字} */ const tcpSocket = connect({ hostname: address, port: port, });remoteSocket.value = tcpSocket;log('连接到${address}:${port}');常量写入器 = tcpSocket.writable.getWriter();等待 writer.write(rawClientData);首先写入,nomal 是 tls 客户端 hello writer.releaseLock();返回 tcpSocket;}
/**
* Retries connecting to the remote address and port if the Cloudflare socket has no incoming data.
* @returns {Promise<void>} A Promise that resolves when the retry is complete.
*/
async function retry() {
const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote)
tcpSocket.closed.catch(error => {
console.log('retry tcpSocket closed error', error);
}).finally(() => {
safeCloseWebSocket(webSocket);
})
remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log);
}
/** * 如果 Cloudflare 套接字没有传入数据,则重试连接到远程地址和端口。* @returns {Promise<void>} 重试完成后解析的 Promise。 */ 异步函数 retry() { const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote) tcpSocket.closed.catch(error => { console.log('retry tcpSocket closed error', error);finally(() => { safeCloseWebSocket(webSocket); remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log);}
const tcpSocket = await connectAndWrite(addressRemote, portRemote);
const tcpSocket = await connectAndWrite(addressRemote, portRemote);
// when remoteSocket is ready, pass to websocket
// remote--> ws
remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log);
}
当 remoteSocket 准备就绪时,传递到 websocket // remote--> ws remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log);}
/**
* Creates a readable stream from a WebSocket server, allowing for data to be read from the WebSocket.
* @param {import("@cloudflare/workers-types").WebSocket} webSocketServer The WebSocket server to create the readable stream from.
* @param {string} earlyDataHeader The header containing early data for WebSocket 0-RTT.
* @param {(info: string)=> void} log The logging function.
* @returns {ReadableStream} A readable stream that can be used to read data from the WebSocket.
*/
function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) {
let readableStreamCancel = false;
const stream = new ReadableStream({
start(controller) {
webSocketServer.addEventListener('message', (event) => {
const message = event.data;
controller.enqueue(message);
});
/** * 从 WebSocket 服务器创建可读流,允许从 WebSocket 读取数据。* @param {import(“@cloudflare/workers-types”)。WebSocket} webSocketServer 要从中创建可读流的 WebSocket 服务器。* @param {string} earlyDataHeader 包含 WebSocket 0-RTT 早期数据的标头。* @param {(info: string)=> void} log 日志函数。* @returns {ReadableStream} 可用于从 WebSocket 读取数据的可读流。*/ function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) { let readableStreamCancel = false; const stream = new ReadableStream({ start(controller) { webSocketServer.addEventListener('message', (event) => { const message = event.data; controller.enqueue(message);
webSocketServer.addEventListener('close', () => {
safeCloseWebSocket(webSocketServer);
controller.close();
});
webSocketServer.addEventListener('close', () => { safeCloseWebSocket(webSocketServer);
webSocketServer.addEventListener('error', (err) => {
log('webSocketServer has error');
controller.error(err);
});
const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
if (error) {
controller.error(error);
} else if (earlyData) {
controller.enqueue(earlyData);
}
},
webSocketServer.addEventListener('error', (err) => { log('webSocketServer has error'); controller.error(err);const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);if (error) { controller.error(error); } else if (earlyData) { controller.enqueue(earlyData); } },
pull(controller) {
// if ws can stop read if stream is full, we can implement backpressure
// https://streams.spec.whatwg.org/#example-rs-push-backpressure
},
pull(controller) { // 如果 ws 可以在流已满时停止读取,我们可以实现背压 // https://streams.spec.whatwg.org/#example-rs-push-backpressure },
cancel(reason) {
log(`ReadableStream was canceled, due to ${reason}`)
readableStreamCancel = true;
safeCloseWebSocket(webSocketServer);
}
});
cancel(reason) { log('由于 ${reason}',ReadableStream 已取消 = true; safeCloseWebSocket(webSocketServer);
return stream;
} 回流;}
// https://xtls.github.io/development/protocols/vless.html
// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw
https://xtls.github.io/development/protocols/vless.html // https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw
/**
* Processes the VLESS header buffer and returns an object with the relevant information.
* @param {ArrayBuffer} vlessBuffer The VLESS header buffer to process.
* @param {string} userID The user ID to validate against the UUID in the VLESS header.
* @returns {{
* hasError: boolean,
* message?: string,
* addressRemote?: string,
* addressType?: number,
* portRemote?: number,
* rawDataIndex?: number,
* vlessVersion?: Uint8Array,
* isUDP?: boolean
* }} An object with the relevant information extracted from the VLESS header buffer.
*/
function processVlessHeader(vlessBuffer, userID) {
if (vlessBuffer.byteLength < 24) {
return {
hasError: true,
message: 'invalid data',
};
}
/** * 处理 VLESS 标头缓冲区并返回包含相关信息的对象。* @param {ArrayBuffer} vlessBuffer 要处理的 VLESS 标头缓冲区。* @param {string} userID 要根据 VLESS 标头中的 UUID 进行验证的用户 ID。* @returns {{ * hasError: boolean, * message?: string, * addressRemote?: string, * addressType?: number, * portRemote?: number, * rawDataIndex?: number, * vlessVersion?: Uint8Array, * isUDP?: boolean * }} 包含从 VLESS 标头缓冲区提取的相关信息的对象。*/ 函数 processVlessHeader(vlessBuffer, userID) { if (vlessBuffer.byteLength < 24) { return { hasError: true, message: 'invalid data', }; }
const version = new Uint8Array(vlessBuffer.slice(0, 1));
let isValidUser = false;
let isUDP = false;
const slicedBuffer = new Uint8Array(vlessBuffer.slice(1, 17));
const slicedBufferString = stringify(slicedBuffer);
// check if userID is valid uuid or uuids split by , and contains userID in it otherwise return error message to console
const uuids = userID.includes(',') ? userID.split(",") : [userID];
// uuid_validator(hostName, slicedBufferString);
const version = new Uint8Array(vlessBuffer.slice(0, 1));let isValidUser = false;let isUDP = false;const slicedBuffer = new Uint8Array(vlessBuffer.slice(1, 17));const slicedBufferString = stringify(slicedBuffer);检查 userID 是否有效 uuid 或 uuids 拆分为 ,并且其中包含 userID,否则返回错误消息到控制台 const uuids = userID.includes(',') ?userID.split(“,”) : [用户ID];uuid_validator(主机名,slicedBufferString);
// isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim());
isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim()) || uuids.length === 1 && slicedBufferString === uuids[0].trim();
isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim());isValidUser = uuids.some(userUuid => slicedBufferString === userUuid.trim()) ||uuids.length === 1 && slicedBufferString === uuids[0].trim();
console.log(`userID: ${slicedBufferString}`);
console.log('用户 ID: ${slicedBufferString}');
if (!isValidUser) {
return {
hasError: true,
message: 'invalid user',
};
}
if (!isValidUser) { return { hasError: true, message: 'invalid user', };
const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];
//skip opt for now
const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];立即跳过选择
const command = new Uint8Array(
vlessBuffer.slice(18 + optLength, 18 + optLength + 1)
)[0];
const 命令 = new Uint8Array( vlessBuffer.slice(18 + optLength, 18 + optLength + 1) )[0];
// 0x01 TCP
// 0x02 UDP
// 0x03 MUX
if (command === 1) {
isUDP = false;
} else if (command === 2) {
isUDP = true;
} else {
return {
hasError: true,
message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`,
};
}
const portIndex = 18 + optLength + 1;
const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);
// port is big-Endian in raw data etc 80 == 0x005d
const portRemote = new DataView(portBuffer).getUint16(0);
0x01 TCP // 0x02 UDP // 0x03 MUX if (command === 1) { isUDP = false; } else if (command === 2) { isUDP = true; } else { return { hasError: true, message: 'command ${command} is not support, command 01-tcp,02-udp,03-mux', }; } const portIndex = 18 + optLength + 1;const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);port 在原始数据中是 big-Endian 等 80 == 0x005d const portRemote = new DataView(portBuffer).getUint16(0);
let addressIndex = portIndex + 2;
const addressBuffer = new Uint8Array(
vlessBuffer.slice(addressIndex, addressIndex + 1)
);
let addressIndex = 端口索引 + 2;const addressBuffer = new Uint8Array( vlessBuffer.slice(addressIndex, addressIndex + 1) );
// 1--> ipv4 addressLength =4
// 2--> domain name addressLength=addressBuffer[1]
// 3--> ipv6 addressLength =16
const addressType = addressBuffer[0];
let addressLength = 0;
let addressValueIndex = addressIndex + 1;
let addressValue = '';
switch (addressType) {
case 1:
addressLength = 4;
addressValue = new Uint8Array(
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
).join('.');
break;
case 2:
addressLength = new Uint8Array(
vlessBuffer.slice(addressValueIndex, addressValueIndex + 1)
)[0];
addressValueIndex += 1;
addressValue = new TextDecoder().decode(
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
);
break;
case 3:
addressLength = 16;
const dataView = new DataView(
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
);
// 2001:0db8:85a3:0000:0000:8a2e:0370:7334
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}`,
};
}
1--> ipv4 addressLength =4 // 2--> 域名 addressLength=addressBuffer[1] // 3--> ipv6 addressLength =16 const addressType = addressBuffer[0];let addressLength = 0;让 addressValueIndex = addressIndex + 1;let addressValue = '';switch (addressType) { case 1: addressLength = 4; addressValue = new Uint8Array( vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) ).join('.'); break; case 2: addressLength = new Uint8Array( vlessBuffer.slice(addressValueIndex, addressValueIndex + 1) )[0]; addressValueIndex += 1; addressValue = new TextDecoder().decode( vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) ); break; case 3: addressLength = 16; const dataView = new DataView( vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) );2001:0db8:85a3:0000:0000:8a2e:0370:7334 const IPv6 = [];for (let i = 0; i < 8; i++) { ipv6.push(dataView.getUint16(i * 2).toString(16)); } addressValue = ipv6.join(':');似乎不需要为 IPv6 中断添加 [];默认值: 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,
vlessVersion: version,
isUDP,
};
}
return { hasError: false, addressRemote: addressValue, addressType, portRemote, rawDataIndex: addressValueIndex + addressLength, vlessVersion: version, isUDP, };}
/**
* Converts a remote socket to a WebSocket connection.
* @param {import("@cloudflare/workers-types").Socket} remoteSocket The remote socket to convert.
* @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to connect to.
* @param {ArrayBuffer | null} vlessResponseHeader The VLESS response header.
* @param {(() => Promise<void>) | null} retry The function to retry the connection if it fails.
* @param {(info: string) => void} log The logging function.
* @returns {Promise<void>} A Promise that resolves when the conversion is complete.
*/
async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) {
// remote--> ws
let remoteChunkCount = 0;
let chunks = [];
/** @type {ArrayBuffer | null} */
let vlessHeader = vlessResponseHeader;
let hasIncomingData = false; // check if remoteSocket has incoming data
await remoteSocket.readable
.pipeTo(
new WritableStream({
start() {
},
/**
*
* @param {Uint8Array} chunk
* @param {*} controller
*/
async write(chunk, controller) {
hasIncomingData = true;
remoteChunkCount++;
if (webSocket.readyState !== WS_READY_STATE_OPEN) {
controller.error(
'webSocket.readyState is not open, maybe close'
);
}
if (vlessHeader) {
webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());
vlessHeader = null;
} else {
// console.log(`remoteSocketToWS send chunk ${chunk.byteLength}`);
// seems no need rate limit this, CF seems fix this??..
// 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}`);
// safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway.
},
abort(reason) {
console.error(`remoteConnection!.readable abort`, reason);
},
})
)
.catch((error) => {
console.error(
`remoteSocketToWS has exception `,
error.stack || error
);
safeCloseWebSocket(webSocket);
});
/** * 将远程套接字转换为 WebSocket 连接。* @param {import(“@cloudflare/workers-types”)。Socket} remoteSocket 要转换的远程套接字。* @param {import(“@cloudflare/workers-types”)。WebSocket} webSocket 要连接到的 WebSocket。* @param {ArrayBuffer | null} vlessResponseHeader VLESS 响应标头。* @param {(() => Promise<void>) | null} retry 如果连接失败,则重试连接的函数。 * @param {(info: string) => void} log 日志记录函数。 * @returns {Promise<void>} 转换完成后解析的 Promise。 */ 异步函数 remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { // remote--> ws let remoteChunkCount = 0; let chunks = []; /** @type {ArrayBuffer | null} */ let vlessHeader = vlessResponseHeader; let hasIncomingData = false; // 检查 remoteSocket 是否有传入数据 await remoteSocket.readable .pipeTo( new WritableStream({ start() { }, /** * * @param {Uint8Array} chunk * @param {*} controller */ async write(chunk, controller) { hasIncomingData = true; remoteChunkCount++; if (webSocket.readyState !== WS_READY_STATE_OPEN) { controller.error( 'webSocket.readyState is not open, may close' ); } if (vlessHeader) { webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); vlessHeader = null; } else { // 控制台。log('remoteSocketToWS 发送块 ${chunk.byteLength}');似乎不需要速率限制这个,CF似乎解决了这个问题??..if (remoteChunkCount > 20000) { // // cf 一个包是 4096 byte(4kb), 4096 * 20000 = 80M // await delay(1); // } webSocket.send(chunk);} }, close() { log('remoteConnection!.readable 接近,hasIncomingData 为 ${hasIncomingData}');safeCloseWebSocket(webSocket);在某些情况下,不需要服务器关闭 websocket 首先会导致 HTTP ERR_CONTENT_LENGTH_MISMATCH问题,客户端无论如何都会发送关闭事件。}, abort(reason) { console.error('remoteConnection!.可读中止',原因);}, }) ) .catch((error) => { console.error( 'remoteSocketToWS has exception ', error.堆栈 ||错误);safeCloseWebSocket(webSocket);});
// seems is cf connect socket have error,
// 1. Socket.closed will have error
// 2. Socket.readable will be close without any data coming
if (hasIncomingData === false && retry) {
log(`retry`)
retry();
}
}
似乎是 cf 连接套接字有错误, // 1.Socket.closed 将出现错误 // 2.Socket.readable 将关闭,没有任何数据传入,如果 (hasIncomingData === false &&; retry) { log('retry') retry();
/**
* Decodes a base64 string into an ArrayBuffer.
* @param {string} base64Str The base64 string to decode.
* @returns {{earlyData: ArrayBuffer|null, error: Error|null}} An object containing the decoded ArrayBuffer or null if there was an error, and any error that occurred during decoding or null if there was no error.
*/
function base64ToArrayBuffer(base64Str) {
if (!base64Str) {
return { earlyData: null, error: null };
}
try {
// go use modified Base64 for URL rfc4648 which js atob not support
base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/');
const decode = atob(base64Str);
const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0));
return { earlyData: arryBuffer.buffer, error: null };
} catch (error) {
return { earlyData: null, error };
}
}
/** * 将 base64 字符串解码为 ArrayBuffer。* @param {string} base64Str 要解码的 base64 字符串。* @returns {{earlyData: ArrayBuffer|null, error: Error|null}} 包含解码的 ArrayBuffer 或 null(如果有错误)的对象,以及解码期间发生的任何错误,如果没有错误,则包含 null。*/ function base64ToArrayBuffer(base64Str) { if (!base64Str) { return { earlyData: null, error: null }; } try { // go use modified Base64 for URL rfc4648 which js atob not support base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/'); const decode = atob(base64Str); const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0)); return { earlyData: arryBuffer.buffer, error: null }; } catch (error) { return { earlyData: null, error }; } }
/**
* Checks if a given string is a valid UUID.
* Note: This is not a real UUID validation.
* @param {string} uuid The string to validate as a UUID.
* @returns {boolean} True if the string is a valid UUID, false otherwise.
*/
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);
}
/** * 检查给定字符串是否为有效的 UUID。* 注意:这不是真正的 UUID 验证。* @param {string} uuid 要验证为 UUID 的字符串。* @returns {boolean} 如果字符串是有效的 UUID,则为 true,否则为 false。*/ 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); }
const WS_READY_STATE_OPEN = 1;
const WS_READY_STATE_CLOSING = 2;
/**
* Closes a WebSocket connection safely without throwing exceptions.
* @param {import("@cloudflare/workers-types").WebSocket} socket The WebSocket connection to close.
*/
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);
}
}
常量 WS_READY_STATE_OPEN = 1;常量 WS_READY_STATE_CLOSING = 2;/** * 安全地关闭 WebSocket 连接,而不会引发异常。* @param {import(“@cloudflare/workers-types”)。WebSocket} socket 要关闭的 WebSocket 连接。*/ 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);
const byteToHex = []; 常量字节ToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
for (let i = 0; i < 256; ++i) { byteToHex.push((i + 256).toString(16).slice(1));
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();
}
函数 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();}
function stringify(arr, offset = 0) {
const uuid = unsafeStringify(arr, offset);
if (!isValidUUID(uuid)) {
throw TypeError("Stringified UUID is invalid");
}
return uuid;
}
function stringify(arr, offset = 0) { const uuid = unsafeStringify(arr, offset); if (!isValidUUID(uuid)) { throw TypeError(“Stringified UUID is invalid”); } return uuid; }
/**
* Handles outbound UDP traffic by transforming the data into DNS queries and sending them over a WebSocket connection.
* @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket connection to send the DNS queries over.
* @param {ArrayBuffer} vlessResponseHeader The VLESS response header.
* @param {(string) => void} log The logging function.
* @returns {{write: (chunk: Uint8Array) => void}} An object with a write method that accepts a Uint8Array chunk to write to the transform stream.
*/
async function handleUDPOutBound(webSocket, vlessResponseHeader, log) {
/** * 通过将数据转换为 DNS 查询并通过 WebSocket 连接发送来处理出站 UDP 流量。* @param {import(“@cloudflare/workers-types”)。WebSocket} webSocket 用于发送 DNS 查询的 WebSocket 连接。* @param {ArrayBuffer} vlessResponseHeader VLESS 响应标头。* @param {(string) => void} log 日志函数。* @returns {{write: (chunk: Uint8Array) => void}} 一个对象,其写入方法接受 Uint8Array 块以写入转换流。*/ 异步函数 handleUDPOutBound(webSocket, vlessResponseHeader, log) {
let isVlessHeaderSent = false;
const transformStream = new TransformStream({
start(controller) {
let isVlessHeaderSent = false;const transformStream = new TransformStream({ start(controller) {
},
transform(chunk, controller) {
// udp message 2 byte is the the length of udp data
// TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message
for (let index = 0; index < chunk.byteLength;) {
const lengthBuffer = chunk.slice(index, index + 2);
const udpPakcetLength = new DataView(lengthBuffer).getUint16(0);
const udpData = new Uint8Array(
chunk.slice(index + 2, index + 2 + udpPakcetLength)
);
index = index + 2 + udpPakcetLength;
controller.enqueue(udpData);
}
},
flush(controller) {
}
});
}, transform(chunk, controller) { // udp message 2 byte 是 udp 数据的长度 // TODO: 这应该有 bug, beacsue 也许 udp 块可以在两个 websocket 消息中 for (let index = 0; index < chunk.byteLength;){ const lengthBuffer = chunk.slice(index, index + 2); const udpPakcetLength = new DataView(lengthBuffer).getUint16(0); const udpData = new Uint8Array( chunk.slice(index + 2, index + 2 + udpPakcetLength) ); index = index + 2 + udpPakcetLength; controller.enqueue(udpData); }}, flush(controller) { } });
// only handle dns udp for now
transformStream.readable.pipeTo(new WritableStream({
async write(chunk) {
const resp = await fetch(dohURL, // dns server url
{
method: 'POST',
headers: {
'content-type': 'application/dns-message',
},
body: chunk,
})
const dnsQueryResult = await resp.arrayBuffer();
const udpSize = dnsQueryResult.byteLength;
// console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16)));
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}`);
if (isVlessHeaderSent) {
webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer());
} else {
webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer());
isVlessHeaderSent = true;
}
}
}
})).catch((error) => {
log('dns udp has error' + error)
});
现在只处理 dns udp transformStream.readable.pipeTo(new WritableStream({ async write(chunk) { const resp = await fetch(dohURL, // dns server url { method: 'POST', headers: { 'content-type': 'application/dns-message', }, body: chunk, }) const dnsQueryResult = await resp.arrayBuffer(); const udpSize = dnsQueryResult.byteLength; // console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16)));const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]);if (webSocket.readyState === WS_READY_STATE_OPEN) { log('doh 成功且 dns 消息长度为 ${udpSize}'); if (isVlessHeaderSent) { webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer()); } else { webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer()); isVlessHeaderSent = true;})).catch((error) => { log('dns udp has error' + error) });
const writer = transformStream.writable.getWriter();
常量写入器 = transformStream.writable.getWriter();
return {
/**
*
* @param {Uint8Array} chunk
*/
write(chunk) {
writer.write(chunk);
}
};
}
return { /** * * @param {Uint8Array} 块 */ write(chunk) { writer.write(chunk);}
/**
*
* @param {string} userID - single or comma separated userIDs
* @param {string | null} hostName
* @returns {string}
*/
function getVLESSConfig(userIDs, hostName) {
const commonUrlPart = `:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`;
const hashSeparator = "################################################################";
/** * * @param {string} userID - 单个或逗号分隔的 userID * @param {string | null} hostName * @returns {string} */ function getVLESSConfig(userIDs, hostName) { const commonUrlPart = ':443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}'; const hashSeparator = “################################################################”;
// Split the userIDs into an array
const userIDArray = userIDs.split(",");
将 userID 拆分为一个数组 const userIDArray = userIDs.split(“,”);
// Prepare output string for each userID
const output = userIDArray.map((userID) => {
const vlessMain = `vless://${userID}@${hostName}${commonUrlPart}`;
const vlessSec = `vless://${userID}@${proxyIP}${commonUrlPart}`;
return `<h2>UUID: ${userID}</h2>${hashSeparator}\nv2ray default ip
---------------------------------------------------------------
${vlessMain}
<button onclick='copyToClipboard("${vlessMain}")'><i class="fa fa-clipboard"></i> Copy vlessMain</button>
---------------------------------------------------------------
v2ray with bestip
---------------------------------------------------------------
${vlessSec}
<button onclick='copyToClipboard("${vlessSec}")'><i class="fa fa-clipboard"></i> Copy vlessSec</button>
---------------------------------------------------------------`;
}).join('\n');
const sublink = `https://${hostName}/sub/${userIDArray[0]}?format=clash`
const subbestip = `https://${hostName}/bestip/${userIDArray[0]}`;
const clash_link = `https://api.v1.mk/sub?target=clash&url=${encodeURIComponent(sublink)}&insert=false&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`;
// Prepare header string
const header = `
<p align='center'><img src='https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky' alt='图片描述' style='margin-bottom: -50px;'>
<b style='font-size: 15px;'>Welcome! This function generates configuration for VLESS protocol. If you found this useful, please check our GitHub project for more:</b>
<b style='font-size: 15px;'>欢迎!这是生成 VLESS 协议的配置。如果您发现这个项目很好用,请查看我们的 GitHub 项目给我一个star:</b>
<a href='https://github.com/3Kmfi6HP/EDtunnel' target='_blank'>EDtunnel - https://github.com/3Kmfi6HP/EDtunnel</a>
<iframe src='https://ghbtns.com/github-btn.html?user=USERNAME&repo=REPOSITORY&type=star&count=true&size=large' frameborder='0' scrolling='0' width='170' height='30' title='GitHub'></iframe>
<a href='//${hostName}/sub/${userIDArray[0]}' target='_blank'>VLESS 节点订阅连接</a>
<a href='clash://install-config?url=${encodeURIComponent(`https://${hostName}/sub/${userIDArray[0]}?format=clash`)}}' target='_blank'>Clash for Windows 节点订阅连接</a>
<a href='${clash_link}' target='_blank'>Clash 节点订阅连接</a>
<a href='${subbestip}' target='_blank'>优选IP自动节点订阅</a>
<a href='clash://install-config?url=${encodeURIComponent(subbestip)}' target='_blank'>Clash优选IP自动</a>
<a href='sing-box://import-remote-profile?url=${encodeURIComponent(subbestip)}' target='_blank'>singbox优选IP自动</a>
<a href='sn://subscription?url=${encodeURIComponent(subbestip)}' target='_blank'>nekobox优选IP自动</a>
<a href='v2rayng://install-config?url=${encodeURIComponent(subbestip)}' target='_blank'>v2rayNG优选IP自动</a></p>`;
为每个用户 ID 准备输出字符串 const output = userIDArray.map((userID) => { const vlessMain = 'vless://${userID}@${hostName}${commonUrlPart}'; const vlessSec = 'vless://${userID}@${proxyIP}${commonUrlPart}'; return '<h2>UUID: ${userID}</h2>${hashSeparator}\nv2ray default ip --------------------------------------------------------------- ${vlessMain} <button onclick='copyToClipboard(“${vlessMain}”)'><i class=“fa fa-clipboard”></i> 复制 vlessMain</button>--------------------------------------------------------------- v2ray with bestip --------------------------------------------------------------- ${vlessSec} <button onclick='copyToClipboard(“${vlessSec}”)'><i class=“fa fa-clipboard”></i> Copy vlessSec</button> ---------------------------------------------------------------'; })。联接('\n');const sublink = 'https://${hostName}/sub/${userIDArray[0]}?format=clash' const subbestip = 'https://${hostName}/bestip/${userIDArray[0]}';const clash_link = 'https://api.v1.mk/sub?target=clash&url=${encodeURIComponent(sublink)}&insert=false&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true';准备头文件字符串 const header = ' <p align='center'><img src='https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky' alt='图片描述' style='margin-bottom: -50px;'> <b style='font-size: 15px;'>欢迎!此函数生成 VLESS 协议的配置。如果您觉得这很有用,请查看我们的 GitHub 项目了解更多信息:</b> <b style='font-size: 15px;'>欢迎!这是生成 VLESS 协议的配置。如果您发现这个项目很好用,请查看我们的 GitHub 项目给我一个星:</b> <a href='https://github.com/3Kmfi6HP/EDtunnel' target='_blank'>EDtunnel - https://github.com/3Kmfi6HP/EDtunnel</a> <iframe src='https://ghbtns.com/github-btn.html?user=USERNAME&repo=REPOSITORY&type=star&count=true&size=large' frameborder='0' scrolling='0' width='170' height='30' title='GitHub'></iframe> <a href='//${hostName}/sub/${userIDArray[0]}' target='_blank'>VLESS 节点订阅连接</a> <a href='clash://install-config?url=${encodeURIComponent('https://${hostName}/sub/${userIDArray[0]}?format=clash')}}' target='_blank'>Clash for Windows 节点订阅连接</a> <a href='${clash_link}' target='_blank'>Clash 节点订阅连接</a><a href='${subbestip}' target='_blank'>优选IP自动节点订阅</a> <a href='clash://install-config?url=${encodeURIComponent(subbestip)}' target='_blank'>Clash优选IP自动</a> <a href='sing-box://import-remote-profile?url=${encodeURIComponent(subbestip)}' target='_blank'>singbox优选IP自动</a> <a href='sn://subscription?url=${encodeURIComponent(subbestip)}' target='_blank'>nekobox优选IP自动</a> <a href='v2rayng://install-config?url=${encodeURIComponent(subbestip)}' target='_blank'>v2rayNG优选IP自动</a></p>';
// HTML Head with CSS and FontAwesome library
const htmlHead = `
<head>
<title>EDtunnel: VLESS configuration</title>
<meta name='description' content='This is a tool for generating VLESS protocol configurations. Give us a star on GitHub https://github.com/3Kmfi6HP/EDtunnel if you found it useful!'>
<meta name='keywords' content='EDtunnel, cloudflare pages, cloudflare worker, severless'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<meta property='og:site_name' content='EDtunnel: VLESS configuration' />
<meta property='og:type' content='website' />
<meta property='og:title' content='EDtunnel - VLESS configuration and subscribe output' />
<meta property='og:description' content='Use cloudflare pages and worker severless to implement vless protocol' />
<meta property='og:url' content='https://${hostName}/' />
<meta property='og:image' content='https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${encodeURIComponent(`vless://${userIDs.split(",")[0]}@${hostName}${commonUrlPart}`)}' />
<meta name='twitter:card' content='summary_large_image' />
<meta name='twitter:title' content='EDtunnel - VLESS configuration and subscribe output' />
<meta name='twitter:description' content='Use cloudflare pages and worker severless to implement vless protocol' />
<meta name='twitter:url' content='https://${hostName}/' />
<meta name='twitter:image' content='https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky' />
<meta property='og:image:width' content='1500' />
<meta property='og:image:height' content='1500' />
带有 CSS 和 FontAwesome 库的 HTML Head const htmlHead = ' <head> <title>EDtunnel: VLESS 配置</title> <meta name='description' content='这是一个用于生成 VLESS 协议配置的工具。 如果您觉得有用,请在 GitHub 上给我们一颗星 https://github.com/3Kmfi6HP/EDtunnel!'> <meta name='keywords' content='EDtunnel, cloudflare pages, cloudflare worker, severless'> <meta name='viewport' content='width=device-width, initial-scale=1'><meta property='og:site_name' content='EDtunnel: VLESS 配置' /> <meta property='og:type' content='website' /> <meta property='og:title' content='EDtunnel - VLESS 配置和订阅输出' /> <meta property='og:description' content='使用 cloudflare pages 和 worker severless 实现 vless 协议' /> <meta property='og:url' content='https://${hostName}/' /><meta property='og:image' content='https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${encodeURIComponent('vless://${userIDs.split(“,”)[0]}@${hostName}${commonUrlPart}')}' /> <meta name='twitter:card' content='summary_large_image' /> <meta name='twitter:title' content='EDtunnel - VLESS 配置和订阅输出' /> <meta name='twitter:description' content='使用 cloudflare pages 和 worker severless 实现 vless 协议' /><meta name='twitter:url' content='https://${hostName}/' /> <meta name='twitter:image' content='https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky' /> <meta property='og:image:width' content='1500' /> <meta property='og:image:height' content='1500' />
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
color: #333;
padding: 10px;
}
<style> body { font-family: Arial, sans-serif; background-color: #f0f0f0; color: #333; padding: 10px; }
a {
color: #1a0dab;
text-decoration: none;
}
img {
max-width: 100%;
height: auto;
}
a { color: #1a0dab; text-decoration: none; } img { max-width: 100%; height: auto; }
pre {
white-space: pre-wrap;
word-wrap: break-word;
background-color: #fff;
border: 1px solid #ddd;
padding: 15px;
margin: 10px 0;
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
body {
background-color: #333;
color: #f0f0f0;
}
pre { white-space: pre-wrap; word-wrap: break-word; background-color: #fff; border: 1px solid #ddd; padding: 15px; margin: 10px 0; } /* 深色模式 */ @media (prefers-color-scheme: dark) { body { background-color: #333; color: #f0f0f0; }
a {
color: #9db4ff;
}
a { 颜色: #9db4ff;
pre {
background-color: #282a36;
border-color: #6272a4;
}
}
</style>
<!-- Add FontAwesome library -->
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'>
</head>
`;
<!-- 添加 FontAwesome 库 --> <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'> </head> ';
// Join output with newlines, wrap inside <html> and <body>
return `
<html>
${htmlHead}
<body>
<pre style='background-color: transparent; border: none;'>${header}</pre>
<pre>${output}</pre>
</body>
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
alert("Copied to clipboard");
})
.catch((err) => {
console.error("Failed to copy to clipboard:", err);
});
}
</script>
</html>`;
}
用换行符连接输出,在里面<html>换行并<body>返回 ' <html> ${htmlHead} <body> <pre style='background-color: transparent; border: none;'>${header}</pre> <pre>${output}</pre> </body> <script> function copyToClipboard(text) { navigator.clipboard.writeText(text) .then(() => { alert(“复制到剪贴板”); }) .catch((err) => { console.error(“无法复制到剪贴板:”, err); </script> </html> });';}
const portSet_http = new Set([80, 8080, 8880, 2052, 2086, 2095, 2082]);
const portSet_https = new Set([443, 8443, 2053, 2096, 2087, 2083]);
const portSet_http = 新集([80, 8080, 8880, 2052, 2086, 2095, 2082]);常量 portSet_https = 新集([443, 8443, 2053, 2096, 2087, 2083]);
function createVLESSSub(userID_Path, hostName) {
const userIDArray = userID_Path.includes(',') ? userID_Path.split(',') : [userID_Path];
const commonUrlPart_http = `?encryption=none&security=none&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#`;
const commonUrlPart_https = `?encryption=none&security=tls&sni=${hostName}&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#`;
函数 createVLESSSub(userID_Path, hostName) { const userIDArray = userID_Path.includes(',') ? userID_Path.split(',') : [userID_Path]; const commonUrlPart_http = '?encryption=none&security=none&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#'; const commonUrlPart_https = '?encryption=none&security=tls&sni=${hostName}&fp=random&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#';
const output = userIDArray.flatMap((userID) => {
const httpConfigurations = Array.from(portSet_http).flatMap((port) => {
if (!hostName.includes('pages.dev')) {
const urlPart = `${hostName}-HTTP-${port}`;
const vlessMainHttp = `vless://${userID}@${hostName}:${port}${commonUrlPart_http}${urlPart}`;
return proxyIPs.flatMap((proxyIP) => {
const vlessSecHttp = `vless://${userID}@${proxyIP}:${port}${commonUrlPart_http}${urlPart}-${proxyIP}-EDtunnel`;
return [vlessMainHttp, vlessSecHttp];
});
}
return [];
});
const output = userIDArray.flatMap((userID) => { const httpConfigurations = Array.from(portSet_http).flatMap((port) => { if (!hostName.includes('pages.dev')) { const urlPart = '${hostName}-HTTP-${port}'; const vlessMainHttp = 'vless://${userID}@${hostName}:${port}${commonUrlPart_http}${urlPart}'; return proxyIPs.flatMap((proxyIP) => { const vlessSecHttp = 'vless://${userID}@${proxyIP}:${port}${commonUrlPart_http}${urlPart}-${proxyIP}-EDtunnel'; 返回 [vlessMainHttp,vlessSecHttp];});} 返回 [];});
const httpsConfigurations = Array.from(portSet_https).flatMap((port) => {
const urlPart = `${hostName}-HTTPS-${port}`;
const vlessMainHttps = `vless://${userID}@${hostName}:${port}${commonUrlPart_https}${urlPart}`;
return proxyIPs.flatMap((proxyIP) => {
const vlessSecHttps = `vless://${userID}@${proxyIP}:${port}${commonUrlPart_https}${urlPart}-${proxyIP}-EDtunnel`;
return [vlessMainHttps, vlessSecHttps];
});
});
const httpsConfigurations = Array.from(portSet_https).flatMap((port) => { const urlPart = '${hostName}-HTTPS-${port}'; const vlessMainHttps = 'vless://${userID}@${hostName}:${port}${commonUrlPart_https}${urlPart}'; return proxyIPs.flatMap((proxyIP) => { const vlessSecHttps = 'vless://${userID}@${proxyIP}:${port}${commonUrlPart_https}${urlPart}-${proxyIP}-EDtunnel'; return [vlessMainHttps, vlessSecHttps]; });
return [...httpConfigurations, ...httpsConfigurations];
});
返回 [...httpConfigurations、...https配置];});
return output.join('\n');
}
返回 output.join('\n');}
const cn_hostnames = [
'weibo.com', // Weibo - A popular social media platform
'www.baidu.com', // Baidu - The largest search engine in China
'www.qq.com', // QQ - A widely used instant messaging platform
'www.taobao.com', // Taobao - An e-commerce website owned by Alibaba Group
'www.jd.com', // JD.com - One of the largest online retailers in China
'www.sina.com.cn', // Sina - A Chinese online media company
'www.sohu.com', // Sohu - A Chinese internet service provider
'www.tmall.com', // Tmall - An online retail platform owned by Alibaba Group
'www.163.com', // NetEase Mail - One of the major email providers in China
'www.zhihu.com', // Zhihu - A popular question-and-answer website
'www.youku.com', // Youku - A Chinese video sharing platform
'www.xinhuanet.com', // Xinhua News Agency - Official news agency of China
'www.douban.com', // Douban - A Chinese social networking service
'www.meituan.com', // Meituan - A Chinese group buying website for local services
'www.toutiao.com', // Toutiao - A news and information content platform
'www.ifeng.com', // iFeng - A popular news website in China
'www.autohome.com.cn', // Autohome - A leading Chinese automobile online platform
'www.360.cn', // 360 - A Chinese internet security company
'www.douyin.com', // Douyin - A Chinese short video platform
'www.kuaidi100.com', // Kuaidi100 - A Chinese express delivery tracking service
'www.wechat.com', // WeChat - A popular messaging and social media app
'www.csdn.net', // CSDN - A Chinese technology community website
'www.imgo.tv', // ImgoTV - A Chinese live streaming platform
'www.aliyun.com', // Alibaba Cloud - A Chinese cloud computing company
'www.eyny.com', // Eyny - A Chinese multimedia resource-sharing website
'www.mgtv.com', // MGTV - A Chinese online video platform
'www.xunlei.com', // Xunlei - A Chinese download manager and torrent client
'www.hao123.com', // Hao123 - A Chinese web directory service
'www.bilibili.com', // Bilibili - A Chinese video sharing and streaming platform
'www.youth.cn', // Youth.cn - A China Youth Daily news portal
'www.hupu.com', // Hupu - A Chinese sports community and forum
'www.youzu.com', // Youzu Interactive - A Chinese game developer and publisher
'www.panda.tv', // Panda TV - A Chinese live streaming platform
'www.tudou.com', // Tudou - A Chinese video-sharing website
'www.zol.com.cn', // ZOL - A Chinese electronics and gadgets website
'www.toutiao.io', // Toutiao - A news and information app
'www.tiktok.com', // TikTok - A Chinese short-form video app
'www.netease.com', // NetEase - A Chinese internet technology company
'www.cnki.net', // CNKI - China National Knowledge Infrastructure, an information aggregator
'www.zhibo8.cc', // Zhibo8 - A website providing live sports streams
'www.zhangzishi.cc', // Zhangzishi - Personal website of Zhang Zishi, a public intellectual in China
'www.xueqiu.com', // Xueqiu - A Chinese online social platform for investors and traders
'www.qqgongyi.com', // QQ Gongyi - Tencent's charitable foundation platform
'www.ximalaya.com', // Ximalaya - A Chinese online audio platform
'www.dianping.com', // Dianping - A Chinese online platform for finding and reviewing local businesses
'www.suning.com', // Suning - A leading Chinese online retailer
'www.zhaopin.com', // Zhaopin - A Chinese job recruitment platform
'www.jianshu.com', // Jianshu - A Chinese online writing platform
'www.mafengwo.cn', // Mafengwo - A Chinese travel information sharing platform
'www.51cto.com', // 51CTO - A Chinese IT technical community website
'www.qidian.com', // Qidian - A Chinese web novel platform
'www.ctrip.com', // Ctrip - A Chinese travel services provider
'www.pconline.com.cn', // PConline - A Chinese technology news and review website
'www.cnzz.com', // CNZZ - A Chinese web analytics service provider
'www.telegraph.co.uk', // The Telegraph - A British newspaper website
'www.ynet.com', // Ynet - A Chinese news portal
'www.ted.com', // TED - A platform for ideas worth spreading
'www.renren.com', // Renren - A Chinese social networking service
'www.pptv.com', // PPTV - A Chinese online video streaming platform
'www.liepin.com', // Liepin - A Chinese online recruitment website
'www.881903.com', // 881903 - A Hong Kong radio station website
'www.aipai.com', // Aipai - A Chinese online video sharing platform
'www.ttpaihang.com', // Ttpaihang - A Chinese celebrity popularity ranking website
'www.quyaoya.com', // Quyaoya - A Chinese online ticketing platform
'www.91.com', // 91.com - A Chinese software download website
'www.dianyou.cn', // Dianyou - A Chinese game information website
'www.tmtpost.com', // TMTPost - A Chinese technology media platform
'www.douban.com', // Douban - A Chinese social networking service
'www.guancha.cn', // Guancha - A Chinese news and commentary website
'www.so.com', // So.com - A Chinese search engine
'www.58.com', // 58.com - A Chinese classified advertising website
'www.cnblogs.com', // Cnblogs - A Chinese technology blog community
'www.cntv.cn', // CCTV - China Central Television official website
'www.secoo.com', // Secoo - A Chinese luxury e-commerce platform
];
const cn_hostnames = [ 'weibo.com', // 微博 - 流行的社交媒体平台 'www.baidu.com', // 百度 - 中国最大的搜索引擎 'www.qq.com', // QQ - 广泛使用的即时通讯平台 'www.taobao.com', // 淘宝 - 阿里巴巴集团旗下的电子商务网站 'www.jd.com', // JD.com - 中国最大的在线零售商之一 'www.sina.com.cn', // 新浪 - 中国在线媒体公司 'www.sohu.com', // 搜狐 - 中国互联网服务提供商 'www.tmall.com', 天猫 - 阿里巴巴集团旗下的在线零售平台 “www.163.com”, // 网易邮箱 - 中国主要的电子邮件提供商之一 “www.zhihu.com”, // 知乎 - 一个受欢迎的问答网站 “www.youku.com”, // 优酷 - 中国视频分享平台“www.xinhuanet.com”, // 新华社 - 中国官方通讯社“www.douban.com”, // 豆瓣 - 中国社交网络服务“www.meituan.com”, // 美团 - 一个中国的本地服务团购网站'www.toutiao.com', // 今日头条 - 新闻和信息内容平台 'www.ifeng.com', // 爱风 - 中国流行的新闻网站 'www.autohome.com.cn', // 汽车之家 - 中国领先的汽车在线平台'www.360.cn', // 360 - 中国互联网安全公司 'www.douyin.com', // 抖音 - 中国短视频平台'www.kuaidi100.com', // 快的100 - 中国快递跟踪服务'www.wechat.com', // 微信 - 流行的消息传递和社交媒体应用程序'www.csdn.net', // CSDN - 中国科技社区网站 'www.imgo.tv', // ImgoTV - 中国直播平台'www.aliyun.com', // 阿里云 - 中国云计算公司 'www.eyny.com', // Eyny - 中国多媒体资源共享网站'www.mgtv.com', // MGTV - 中国在线视频平台'www.xunlei.com', // 迅雷 - 中国下载管理器和种子客户端 'www.hao123.com', // Hao123 - 中国网络目录服务 'www.bilibili.com', // 哔哩哔哩 - 中国视频分享和流媒体平台'www.youth.cn', // Youth.cn - 中国青年报新闻门户网站'www.hupu.com', // 虎浦 - 中国体育社区和论坛 'www.youzu.com', // 游豆互动 - 中国游戏开发商和发行商 'www.panda.tv', // 熊猫电视 - 中国直播平台'www.tudou.com', 土豆 - 中国视频分享网站“www.zol.com.cn”, // ZOL - 中国电子和小工具网站“www.toutiao.io”, // 今日头条 - 新闻和信息应用程序“www.tiktok.com”, // TikTok - 中国短视频应用程序“www.netease.com”, // 网易 - 中国互联网科技公司“www.cnki.net”, // CNKI - 中国国家知识基础设施,信息聚合器“www.zhibo8.cc”, // Zhibo8 - 提供体育直播的网站 'www.zhangzishi.cc', // 张子石 - 中国公共知识分子张子石的个人网站 “www.xueqiu.com”, // 雪球 - 面向投资者和交易者的中国在线社交平台 “www.qqgongyi.com”, // QQ公益 - 腾讯慈善基金会平台“www.ximalaya.com”, // 喜马拉雅 - 中国在线音频平台“www.dianping.com”, // 大众点评 - 寻找和评论本地企业的中国在线平台 “www.suning.com”, // 苏宁 - 中国领先的在线零售商 “www.zhaopin.com”, // 智联招聘 - A中国招聘平台'www.jianshu.com', // 建书 - 中国在线写作平台'www.mafengwo.cn', // 马峰窝 - 中国旅游信息共享平台'www.51cto.com', // 51CTO - 中国IT技术社区网站'www.qidian.com', // 启电 - 中国网络小说平台'www.ctrip.com', // 携程 - 中国旅游服务提供商 'www.pconline.com.cn', // PConline - 中国科技新闻和评论网站 'www.cnzz.com', // CNZZ - 中国网络分析服务提供商 'www.telegraph.co.uk', // The Telegraph - 英国报纸网站 'www.ynet.com', // Ynet - 中国新闻门户网站 'www.ted.com', // TED - 一个值得传播思想的平台 “www.renren.com”, // 人人网 - 中国社交网络服务 'www.pptv.com', // PPTV - 一个中文在线视频流媒体平台“www.liepin.com”, // 列品 - 中国在线招聘网站“www.881903.com”, // 881903 - 香港广播电台网站“www.aipai.com”, // 爱拍 - 中国在线视频分享平台“www.ttpaihang.com”, // Ttpaihang - 中国名人人气排行榜网站“www.quyaoya.com”, // 曲耀雅 - 中国在线票务平台“www.91.com”, // 91.com - 中国软件下载网站“www.dianyou.cn”, // 点游 - 中国游戏信息网站'www.tmtpost.com', // TMTPost - 中国科技媒体平台'www.douban.com', // 豆瓣 - 中国社交网络服务'www.guancha.cn', // 关查 - 中国新闻评论网站'www.so.com', // So.com - 中国搜索引擎'www.58.com', // 58.com - 中国分类广告网站'www.cnblogs.com', // Cnblogs - 中国科技博客社区'www.cntv.cn', // 央视 - 中国中央电视台官方网站'www.secoo.com', // 寺库 - 中国奢侈品电商平台 ];
仓库:https://github.com/Li468446/workers.js/tree/main
---------------------------------------------------------------------------------------------------------------------------------
导入完成后,我们需要修改两个地方,分别是UUID和CDN-IP。CDN-IP必须是反代IP,不能是官方IP!!!
4、UUID生成
UUID的大概解释在本文开头的知识链条中有比较标准的解释,大家可以作为参考。UUID因其属性,随机生成且基本不会重合,我们现在去生成它,将它放入代码中即可。这里给大家推荐一个UUID在线随机生成的网站,希望能帮到各位开发者和极客玩家>>>open<<<,网址:
https://1024tools.com/uuid
进入后点击生成即可,然后随便选择一个就行,UUID非常重要,请把你使用的UUID保存一份副本,避免后续的操作无法进行!!!
这里我则第二个,复制出来后回到实例代码中,修改UUID为生成的这个。
5、cloud flare-proxy-IP 、proxy域的选择
优质域名推荐
cdn-all.xn--b6gac.eu.org
cdn.xn--b6gac.eu.org
cdn-b100.xn--b6gac.eu.org
edgetunnel.anycast.eu.org
cdn.anycast.eu.org(亚洲地区)
jp.cloudflarest.link
achk.cloudflarest.link(阿里香港优选)
这里给大家两种优选cloud flare优质IP的方案:
2、个人推荐
113.64.186.198
103.200.112.108
199.15.76.35
edgetunnel.anycast.eu.org(美国的加速CDN)
cdn.anycast.eu.org(香港日本新加坡加速CDN)
香港优选IP:20.187.89.16
日本高速优选IP:146.56.149.205
美国优选IP:172.64.135.146)
韩国高速优选IP:129.154.199.251
日本高速优选IP:146.56.149.205
cdn-all.xn--b6gac.eu.org
cdn.xn--b6gac.eu.org
cdn-b100.xn--b6gac.eu.org
edgetunnel.anycast.eu.org
cdn.anycast.eu.org
同时也可以选出运营商的最优反代IP,放入实例代码中的CDN-IP位置即可。这里我就直接使用这个IP。
113.64.186.198
填入后点击部署即可。
我已经保存部署过了,所以这里这个按钮是灰色的
到这里,cloud flare的实例部署就已经完成了。
三、使用方法(无域名)
生成的实例流量转发的协议为vless,所以Vtwo(2)ray是个不错的选择.
1、V222222222222rayn
复制刚刚创建的代码地址,格式为:https://vless.xxx.workers.dev/
,其中 vless
为创建的脚本名称,xxx
为 CloudFlare 用户名。
复制出来后,后面加个/+UUID,如下
https://ceshi.xiaorantang68.workers.dev/37959939-2926-4e4c-a0d2-6468a1b9f7f7
这样我们就获取到了vless服务器的两个节点信息
复制红框内的内容后粘贴进v222222222222rayn
添加完成,但是现在这个服务器还不能使用,需要对节点进行进一步的修改。
因为不使用域名,所以加密方式不需要选择,将TLS改为空
如下图:
端口呢,也不能使用443端口,这里我切换为80端口(不使用TLS可以将端口改为80或2052)
2、修改地址
现在就可以进行IP的优选了,脚本可以在我的仓库下载:https://github.com/Li468446/workers.js
填入v222222222rayn的vless服务器连接信息中即可。
lanzou:https://pan.lanpw.com/b0742hkxehttps://pan.lanpw.com/b0742hkxe
下载下来后运行jp.bat批处理程序即可自动优选。优选前请关闭所有的系统代理,避免优选出现误差!!!
优选结果会写入rest.csv文件内,填入vless服务器IP中即可,这里就不多做解答
优选出来的IP直接填入vless服务器的地址栏即可,确定后切换为活动服务器,访问谷歌尝试:
到这里,v2222222222222rayn的配置就完成了,可以进行测试
四、实例测试
我这里优选出来的本地IP是104.19.170.**,填入后访问谷歌,测试成功。
查看cloud flare的实例,有正常的请求,正常工作。
实验成功
五、总结
需要有一定的web基础和网络代理的基础,过程繁琐,在这个过程中,最容易出错的地方就在于实例中的workers的proxy IP和v222222222222rayn的客户端连接的CF优选IP容易搞混淆,Proxy-IP必须是反向代理IP,否则实例的反向代理会出现问题。
=========================================================================
最后,建议各位开发者们仔细观看,认真学习,我在文中如有表达不够清晰,存在疑问的,可以在下方评论区提出,我会一一为大家解答,有错误的地方也请大佬指点一二,共同学习,一起进步。
本章节就到这里,感谢大家的支持!