Cloudflare Worker+Vless,搭建零成本科学梯子 - 胡萝虎的博客

同步来源:树洞剪藏 源站剪藏 ID:272 原文地址:https://www.huluohu.com/posts/913/
Cloudflare Worker+Vless,搭建零成本科学梯子 - 胡萝虎的博客
set 限制解除
胡萝虎的博客
首页
Tech
NAS
AI
Apple
Windows
Linux
影视
资讯
工具箱
友链
友文录
关于
天下皆白,唯我独黑
首页
Tech 正文
Cloudflare Worker+Vless,搭建零成本科学梯子
2023年12月13日 256点热度 4人点赞 7条评论
这个话题笔者就不展开说了,就是搭一个稳定可靠的梯子,但是不需要买VPS,除了需要一个域名以外,基本上属于零成本了,懂得都懂。
1️⃣ 前提条件
只有2个非常简单的条件,没什么难度。
有一个Cloudflare账号,免费计划就行
有一个域名,并且将DNS解析转到Cloudflare中
2️⃣ 搭建步骤
搭建这个服务是使用了Cloudflare的Worker和Pages服务每天可以有10万个免费请求次数这个福利,对于个人使用来说完全不用担心。所以为了你个人安全和不超次数, 请不要分享给别人使用!请不要分享给别人使用!请不要分享给别人使用!
开始前请先完成上述前提条件,笔者就不赘述了,完全没难度。
1、打开Workers和Pages
登录Cloudflare之后,直接点击菜单 Workers和Pages ,进入到概述页面
2、创建Worker
点击 创建应用程序 按钮,进入创建页面,切换到 Workers 页签,接着点击页面中的 创建Worker 按钮,进入创建页面。
在创建页面中填写名称,比如就叫 vless-worker ,代码先保持默认不用管,然后点击 部署 按钮,完成创建。
3、编写Worker
重点来了。
首先回到概述页面,可以看到我们已经创建好的 Worker,直接点击名称进入 Worker 的主页。
接着点击右上角的 快速编辑 按钮,进入编辑页面,将 Vless 代码粘贴到编辑框中,然后点击右上角的 保存并部署 按钮,完成! 代码比较长,放到文末了 。
⚠️注意:代码中的第7行 userID ,其实就是UUID,默认的值已经被广泛使用了,请一定要修改一下,如果你不知道如何生成UUID,可以到这个网站生成一个: https://it.huluohu.com/uuid-generator 。
4、配置自定义域名
以上其实就已经完成了 Vless 的搭建,但是由于 Cloudflare 自带的Worker域名在国内属于不受欢迎的(你懂的),所以你的域名现在开始出场了,我们需要给这个 Worker 设置一个自定义域名。
首先还是回到到上面创建的 Worker 详情页面,点击 触发器 页签。继续点击 添加自定义域 按钮,开始添加一个自定义域名。
输入你的域名,例如 tiz.yourdomain.com ,并点击 添加自定义域 按钮,完成添加。请注意,输入的域名一定是已经将 DNS 解析添加到 Cloudflare 中的才可以。
至此,Cloudflare中的Worker都已经完成部署和设置了,下面就是如何使用这个 Vless 服务了。
3️⃣ 配置客户端
支持 Vless 协议的客户端很多,不同平台也都不大一样,这里笔者就不做任何推荐了,毕竟这玩意也是不受欢迎的。保险起见,笔者截了个图,请参考填写。
填写说明:
选择协议:选 vless
address:填前面设置的自定义域名
端口:支持6个https端口可任意选择(443、8443、2053、2083、2087、2096)
id:就是代码中的 userID,千万不要泄露给其他人
选择网络:选 ws
host:留空即可
path:填 /?ed=2048
security:选 tls
tls servername:同样填前面设置的自定义域名
4️⃣ Vless代码
下面就是 Vless 代码,请直接复制即可,代码很长,可不要复制漏了。
// version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC. // @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 = '77a571fb-4fd2-4b37-8596-1b7d9728bb5c';
// const proxyIPs = ["[2001:67c:2b0:db32:0:1:681a:404]"]; const proxyIPs = ['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']; let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];
let dohURL = 'https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg='; // https://cloudflare-dns.com/dns-query or https://dns.google/dns-query
// v2board api environment variables let nodeId = ''; // 1
let apiToken = ''; //abcdefghijklmnopqrstuvwxyz123456
let apiHost = ''; // api.v2board.com
if (!isValidUUID(userID)) { throw new Error('uuid is not valid'); }
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}
*/
async fetch(request, env, ctx) {
try {
userID = env.UUID || userID;
proxyIP = env.PROXYIP || proxyIP;
dohURL = env.DNS_RESOLVER_URL || dohURL;
nodeId = env.NODE_ID || nodeId;
apiToken = env.API_TOKEN || apiToken;
apiHost = env.API_HOST || apiHost;
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 '/connect': // for test connect to cf socket
const [hostname, port] = ['cloudflare.com', '80'];
console.log(
Connecting to ``{hostname}:``{port}...);
try { const socket = await connect({ hostname: hostname, port: parseInt(port, 10), });
const writer = socket.writable.getWriter();
try { await writer.write(new TextEncoder().encode('GET / HTTP/1.1\r Host: ' + hostname + '\r \r ')); } catch (writeError) { writer.releaseLock(); await socket.close(); return new Response(writeError.message, { status: 500 }); }
writer.releaseLock();
const reader = socket.readable.getReader(); let value;
try { const result = await reader.read(); value = result.value; } catch (readError) { await reader.releaseLock(); await socket.close(); return new Response(readError.message, { status: 500 }); }
await reader.releaseLock(); await socket.close();
return new Response(new TextDecoder().decode(value), { status: 200 });
} catch (connectError) {
return new Response(connectError.message, { status: 500 });
}
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 });
// For any other path, reverse proxy to 'www.fmprc.gov.cn' and return the original response
url.hostname = 'www.bing.com';
url.protocol = 'https:';
request = new Request(url, request);
return await fetch(request);
}
} 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, 8443, 2053, 2083, 2087, 2096, 80, 8080, 8880, 2052, 2082, 2086, 2095],
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, }); }
let apiResponseCache = null; let cacheTimeout = null;
/**
- Fetches the API response from the server and caches it for future use.
- @returns {Promise<object|null>} A Promise that resolves to the API response object or null if there was an error. */ async function fetchApiResponse() { const requestOptions = { method: 'GET', redirect: 'follow' };
try {
const response = await fetch(https://``{apiHost}/api/v1/server/UniProxy/user?node_id=``{nodeId}&node_type=v2ray&token=${apiToken}, requestOptions);
if (!response.ok) { console.error('Error: Network response was not ok'); return null; } const apiResponse = await response.json(); apiResponseCache = apiResponse;
// Refresh the cache every 5 minutes (300000 milliseconds) if (cacheTimeout) { clearTimeout(cacheTimeout); } cacheTimeout = setTimeout(() => fetchApiResponse(), 300000);
return apiResponse; } catch (error) { console.error('Error:', error); return null; } }
/**
- Returns the cached API response if it exists, otherwise fetches the API response from the server and caches it for future use.
- @returns {Promise<object|null>} A Promise that resolves to the cached API response object or the fetched API response object, or null if there was an error. */ async function getApiResponse() { if (!apiResponseCache) { return await fetchApiResponse(); } return apiResponseCache; }
/**
- Checks if a given UUID is present in the API response.
- @param {string} targetUuid The UUID to search for.
- @returns {Promise} 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 if (!nodeId || !apiToken || !apiHost) { return false; }
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; } }
// Usage example: // const targetUuid = "65590e04-a94c-4c59-a1f2-571bce925aad"; // checkUuidInApiResponse(targetUuid).then(result => console.log(result));
/**
- 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} 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 */ 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) | 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}@www.visa.com:8880?encryption=none&security=none&type=ws&host=``{hostName}&path=%2F%3Fed%3D2048#${hostName}; const pvlesswstls =vless://``{userID}@www.visa.com:8443?encryption=none&security=tls&type=ws&host=``{hostName}&sni=``{hostName}&fp=random&path=%2F%3Fed%3D2048#``{hostName};
if (hostName.includes('pages.dev')) { return ` ==========================配置详解==============================
################################################################ CF-pages-vless+ws+tls节点,分享链接如下:
${pvlesswstls}
注意:如果 ${hostName} 在本地网络打不开(中国移动用户注意) 客户端选项的伪装域名(host)必须改为你在CF解析完成的自定义域名
客户端必要文明参数如下: 客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应) 端口(port):6个https端口可任意选择(443、8443、2053、2083、2087、2096) 用户ID(uuid):${userID} 传输协议(network):ws 或者 websocket 伪装域名(host):${hostName} 路径(path):/?ed=2048 传输安全(TLS):开启 跳过证书验证(allowlnsecure):false ################################################################ `;
} else if (hostName.includes('workers.dev')) { return ` ==========================配置详解==============================
################################################################ 一、CF-workers-vless+ws节点,分享链接如下:
${wvlessws}
注意:当前节点无需使用CF解析完成的域名,客户端选项的TLS选项必须关闭
客户端必要文明参数如下: 客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应) 端口(port):7个http端口可任意选择(80、8080、8880、2052、2082、2086、2095) 用户ID(uuid):${userID} 传输协议(network):ws 或者 websocket 伪装域名(host):${hostName} 路径(path):/?ed=2048 ################################################################
################################################################
查看CF-workers-vless+ws+tls节点配置信息,请在浏览器地址栏输入:你设置的自定义域名/你设置的UUID 防止小白过多的操作失误,必须设置自定义域名后才能使用Workers方式的TLS模式,否则,建议只使用vless+ws节点即可 提示:使用pages方式部署,联通、电信用户大概率可以直接使用TLS模式,无需设置自定义域名 pages方式部署可参考此视频教程:https://youtu.be/McdRoLZeTqg
################################################################
; } else { return
==========================配置详解==============================
=====使用自定义域名查看配置,请确认使用的是workers还是pages=====
################################################################ 一、CF-workers-vless+ws节点,分享链接如下:
${wvlessws}
注意:当前节点无需使用CF解析完成的域名,客户端选项的TLS选项必须关闭
客户端必要文明参数如下: 客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应) 端口(port):7个http端口可任意选择(80、8080、8880、2052、2082、2086、2095) 用户ID(uuid):${userID} 传输协议(network):ws 或者 websocket 伪装域名(host):${hostName} 路径(path):/?ed=2048 ################################################################
################################################################ 二、CF-workers-vless+ws+tls 或者 CF-pages-vless+ws+tls节点,分享链接如下:
${pvlesswstls}
注意:客户端选项的伪装域名(host)必须改为你在CF解析完成的自定义域名
客户端必要文明参数如下: 客户端地址(address):自定义的域名 或者 优选域名 或者 优选IP(反代IP必须与反代端口对应) 端口(port):6个https端口可任意选择(443、8443、2053、2083、2087、2096) 用户ID(uuid):${userID} 传输协议(network):ws 或者 websocket 伪装域名(host):${hostName} 路径(path):/?ed=2048 传输安全(TLS):开启 跳过证书验证(allowlnsecure):false ################################################################ `; } }
原创不易,如果觉得此文对你有帮助,不妨点赞+收藏+关注,你的鼓励是我持续创作的动力!
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
标签: Cloudflare Worker Vless
最后更新:2024年8月18日
诺多
这个作者不懒,他正在折腾新玩意~
打赏 点赞
< 上一篇 下一篇 >
文章评论
山川
大佬,我把文后那一长串代码粘贴后,报错Uncaught SyntaxError: Cannot use import statement outside a module at worker.js:3 (Code: 10021),请问怎么办?谢谢
2023年12月14日
回复
诺多
@山川 是不是复制漏了,或者你到这个链接再复制下看看。
https://github.com/yonggekkk/Cloudflare-workers-pages-vless/blob/main/_worker.js
2023年12月14日
回复
书签网
这个教程必须收藏了
2024年1月18日
回复
LEOx
油管上看到说用了 worker 导致google账号被封了。。
好像是说因为IP转跳频繁导致的。 会又这种问题吗?
2024年3月13日
回复
诺多
@LEOx 我是没遇到这个情况,如果有这样的话,那么用✈️岂不是也会被封嘛。
2024年3月13日
回复
LEOx
@诺多 机场好像是固定IP的
然后WARP+好像是自动连接就近就快,然后没办法自己选择控制的。
您这个搭建VLESS是否是拿到了很多节点,然后可以自己选择的呢?
2024年3月13日
回复
猫叔
试了下 并不好使啊,域名啥的都没问题,但是用工具配置后 试了 就是-1,没速度,不起作用 到底哪里的问题?
2024年3月25日
回复
取消回复
搜索
搜索
诺多 这个作者不懒,他正在折腾新玩意~
最新 热点 随机
🚀 NAS-CAB:太强大了,能部署有手机端,这款一站式NAS管理器让我一见钟情! 🎬 ANI-RSS:2024年最强追番神器,Win/Mac/Linux/NAS统统都有! 🏖️ Domain Admin:这款开源的域名管理神器,让你的网站永不掉链子! 📺 强力推荐,一款神仙IPTV直播源更新工具! 📱 微信视频号神器!一键下载还能选画质,Windows独占! 😻 MD3:一款让你的Windows变身梦幻桌面的神器!
2024 年 12 月
一 二 三 四 五 六 日
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
« 11 月
COPYRIGHT © 2024 胡萝虎的博客. ALL RIGHTS RESERVED.
隐私政策 | 网站地图 | RSS
Theme Kratos Made By Seaton Jiang
'>