CF创建workers

Untitled 13.

编辑代码

Untitled 1

替换worker.js内容

Untitled 2 4

// <!--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 = "272d28c1-2776-495f-b814-a1ebddcdc417";

const proxyIPs = ["cdn.xn--b6gac.eu.org"]; //workers.cloudflare.cyou bestproxy.onecf.eu.org cdn-all.xn--b6gac.eu.org cdn.xn--b6gac.eu.org
const cn_hostnames = [''];

let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];

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 "/cf":
return new Response(JSON.stringify(request.cf, null, 4), {
status: 200,
headers: {
"Content-Type": "application/json;charset=utf-8",
},
});

case `/${userID}`: {
const vlessConfig = getVLESSConfig(userID, request.headers.get("Host"));
return new Response(`${vlessConfig}`, {
status: 200,
headers: {
"Content-Type": "text/html;charset=utf-8",
},
});
}
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
if (cn_hostnames.includes('')) {
return new Response(JSON.stringify(request.cf, null, 4), {
status: 200,
headers: {
"Content-Type": "application/json;charset=utf-8",
},
});
}
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
*/
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,
} = await 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,
});
}

/**
* Checks if a given UUID is present in the API response.
* @param {string} targetUuid The UUID to search for.
* @returns {Promise<boolean>} A Promise that resolves to true if the UUID is present in the API response, false otherwise.
*/
async function checkUuidInApiResponse(targetUuid) {
// Check if any of the environment variables are empty

try {
const apiResponse = await getApiResponse();
if (!apiResponse) {
return false;
}
const isUuidInResponse = apiResponse.users.some((user) => user.uuid === targetUuid);
return isUuidInResponse;
} catch (error) {
console.error("Error:", error);
return false;
}
}

/**
* 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) {
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')}`;
/** @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
*/
async 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;
const slicedBuffer = new Uint8Array(vlessBuffer.slice(1, 17));
const slicedBufferString = stringify(slicedBuffer);

const uuids = userID.includes(",") ? userID.split(",") : [userID];

const checkUuidInApi = await checkUuidInApiResponse(slicedBufferString);
isValidUser = uuids.some((userUuid) => checkUuidInApi || slicedBufferString === userUuid.trim());

console.log(`checkUuidInApi: ${await checkUuidInApiResponse(slicedBufferString)}, userID: ${slicedBufferString}`);

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(
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);
});

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 wvlessws = `vless://${userID}\u0040www.visa.com.sg:8880?encryption=none&security=none&type=ws&host=${hostName}&path=%2F%3Fed%3D2560#${hostName}`;
const pvlesswstls = `vless://${userID}\u0040www.visa.com.sg:8443?encryption=none&security=tls&type=ws&host=${hostName}&sni=${hostName}&fp=random&path=%2F%3Fed%3D2560#${hostName}`;
const note = `正在使用的ProxyIP:${proxyIP}`;
const noteshow = note.replace(/\n/g, '<br>');
const displayHtml = `
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<style>
.limited-width {
max-width: 200px;
overflow: auto;
word-wrap: break-word;
}
</style>
</head>
<script>
function copyToClipboard(text) {
const input = document.createElement('textarea');
input.style.position = 'fixed';
input.style.opacity = 0;
input.value = text;
document.body.appendChild(input);
input.select();
document.execCommand('Copy');
document.body.removeChild(input);
alert('已复制到剪贴板');
}
</script>
`;
if (hostName.includes("pages.dev")) {
return `
${displayHtml}
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>配置详解</h2>
<p>${noteshow}</p>
<hr>
<br>
<br>
<h3>CF-pages-vless+ws+tls节点,分享链接如下:</h3>
<table class="table">
<thead>
<tr>
<th>节点类型</th>
<th>分享链接</th>
<th>复制</th>
</tr>
</thead>
<tbody>
<tr>
<td class="limited-width">CF-pages-vless+ws+tls</td>
<td class="limited-width">${pvlesswstls}</td>
<td><button class="btn btn-primary" onclick="copyToClipboard('${pvlesswstls}')">复制</button></td>
</tr>
</tbody>
</table>
<hr>
<p>注意:如果 ${hostName} 在本地网络打不开(中国移动用户注意),客户端必须开启切片功能</p>
<hr>
<h3>客户端必要文明参数如下:</h3>
<ul>
<li>客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应)</li>
<li>端口(port):6个https端口可任意选择(443、8443、2053、2083、2087、2096)</li>
<li>用户ID(uuid):${userID}</li>
<li>传输协议(network):ws 或者 websocket</li>
<li>伪装域名(host):${hostName}</li>
<li>路径(path):/?ed=2048</li>
<li>传输安全(TLS):开启</li>
<li>跳过证书验证(allowlnsecure):false</li>
</ul>
</div>
</div>
</div>
</body>
`;
} else {
return `
${displayHtml}
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>配置详解</h2>
<p>${noteshow}</p>
<hr>
<h3>1:CF-workers-vless+ws节点,分享链接如下:</h3>
<table class="table">
<thead>
<tr>
<th>节点类型</th>
<th>分享链接</th>
<th>复制</th>
</tr>
</thead>
<tbody>
<tr>
<td class="limited-width">CF-workers-vless+ws</td>
<td class="limited-width">${wvlessws}</td>
<td><button class="btn btn-primary" onclick="copyToClipboard('${wvlessws}')">复制</button></td>
</tr>
</tbody>
</table>
<hr>
<p>注意:当前节点无需使用CF解析完成的域名,客户端选项的TLS选项必须关闭</p>
<hr>
<h3>客户端必要文明参数如下:</h3>
<ul>
<li>客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应)</li>
<li>端口(port):7个http端口可任意选择(80、8080、8880、2052、2082、2086、2095)</li>
<li>用户ID(uuid):${userID}</li>
<li>传输协议(network):ws 或者 websocket</li>
<li>伪装域名(host):${hostName}</li>
<li>路径(path):/?ed=2048</li>
</ul>
<hr>
<br>
<br>
<h3>2:CF-workers-vless+ws+tls 或者 CF-pages-vless+ws+tls节点,分享链接如下:</h3>
<table class="table">
<thead>
<tr>
<th>节点类型</th>
<th>分享链接</th>
<th>复制</th>
</tr>
</thead>
<tbody>
<tr>
<td class="limited-width">CF-workers-vless+ws+tls 或者 CF-pages-vless+ws+tls</td>
<td class="limited-width">${pvlesswstls}</td>
<td><button class="btn btn-primary" onclick="copyToClipboard('${pvlesswstls}')">复制</button></td>
</tr>
</tbody>
</table>
<hr>
<p>注意:使用workers域名开启TLS,客户端必须开启切片功能</p>
<hr>
<h3>客户端必要文明参数如下:</h3>
<ul>
<li>客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应)</li>
<li>端口(port):6个https端口可任意选择(443、8443、2053、2083、2087、2096)</li>
<li>用户ID(uuid):${userID}</li>
<li>传输协议(network):ws 或者 websocket</li>
<li>伪装域名(host):${hostName}</li>
<li>路径(path):/?ed=2048</li>
<li>传输安全(TLS):开启</li>
<li>跳过证书验证(allowlnsecure):false</li>
</ul>
</div>
</div>
</div>
</body>
`;
}
}

==!!!重要==,修改uuid,可以在v2ray内新建,生成随机uuid入内。即红色内容,userID = “==272d28c1-2776-495f-b814-a1ebddcdc417==”;

Untitled 3 4.

worker域名加/uuid,访问页面

Untitled 4.

使用vless+ws裸奔,安全性较低,与2相比较少了一个tls,目前tls已被GFW阻断,加了tls也不过多穿内裤罢了。直接不穿内裤,代码都直接部署到workers内了。优势:纯免费。

优化

使用优选IP地址替换

Untitled 5 3.

使用银行,生活网站,IP地址查询网站等使用Cloudflare进行反代的网站,盗用cdn节点进行优选反代,降低延迟。

www.whoer.net

www.whatismyip.com

www.visa.com.tw

www.4chan.com

www.shopify.com

malaysia.com

singapore.com

japan.com

晚高峰跑满带宽,速度惊人 电信

Untitled 6 3

晚高峰iphone测试,联通

IMG\_5950

无DNS泄露

Untitled 7 3