Configuration for VLESS tunnel on Cloudflare Worker
3 |Also, you can read this in my personal notebook.
4 |نسخهی فارسی این نوشته: اینجا کلیک کنید.
6 |It's Super easy (you don't need any server). Just create a CF Worker and then copy & paste all of the worker.js file's content as the worker's configuration, then do all of these things:
9 |engage.cloudflareclient.com
as the destination address.Now you're all done. Test your VLESS configuration.
22 |Read more on note.al1almasi.ir
25 | -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliAlmasi/vless-cf-worker/e9dc36cc2ee6863401f493b961d4f496b9cae08e/image.png -------------------------------------------------------------------------------- /screenshots/vless-config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliAlmasi/vless-cf-worker/e9dc36cc2ee6863401f493b961d4f496b9cae08e/screenshots/vless-config.jpg -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // node_modules/uuid/dist/esm-browser/regex.js 3 | var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; 4 | 5 | // node_modules/uuid/dist/esm-browser/validate.js 6 | function validate(uuid) { 7 | return typeof uuid === "string" && regex_default.test(uuid); 8 | } 9 | var validate_default = validate; 10 | 11 | // node_modules/uuid/dist/esm-browser/stringify.js 12 | var byteToHex = []; 13 | for (let i = 0; i < 256; ++i) { 14 | byteToHex.push((i + 256).toString(16).slice(1)); 15 | } 16 | function unsafeStringify(arr, offset = 0) { 17 | 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(); 18 | } 19 | function stringify(arr, offset = 0) { 20 | const uuid = unsafeStringify(arr, offset); 21 | if (!validate_default(uuid)) { 22 | throw TypeError("Stringified UUID is invalid"); 23 | } 24 | return uuid; 25 | } 26 | var stringify_default = stringify; 27 | 28 | // libs/vless-js/src/lib/vless-js.ts 29 | var WS_READY_STATE_OPEN = 1; 30 | function makeReadableWebSocketStream(ws, earlyDataHeader, log) { 31 | let readableStreamCancel = false; 32 | return new ReadableStream({ 33 | start(controller) { 34 | ws.addEventListener("message", async (e) => { 35 | if (readableStreamCancel) { 36 | return; 37 | } 38 | const vlessBuffer = e.data; 39 | controller.enqueue(vlessBuffer); 40 | }); 41 | ws.addEventListener("error", (e) => { 42 | log("socket has error"); 43 | readableStreamCancel = true; 44 | controller.error(e); 45 | }); 46 | ws.addEventListener("close", () => { 47 | try { 48 | log("webSocket is close"); 49 | if (readableStreamCancel) { 50 | return; 51 | } 52 | controller.close(); 53 | } catch (error2) { 54 | log(`websocketStream can't close DUE to `, error2); 55 | } 56 | }); 57 | const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); 58 | if (error) { 59 | log(`earlyDataHeader has invaild base64`); 60 | safeCloseWebSocket(ws); 61 | return; 62 | } 63 | if (earlyData) { 64 | controller.enqueue(earlyData); 65 | } 66 | }, 67 | pull(controller) { 68 | }, 69 | cancel(reason) { 70 | log(`websocketStream is cancel DUE to `, reason); 71 | if (readableStreamCancel) { 72 | return; 73 | } 74 | readableStreamCancel = true; 75 | safeCloseWebSocket(ws); 76 | } 77 | }); 78 | } 79 | function base64ToArrayBuffer(base64Str) { 80 | if (!base64Str) { 81 | return { error: null }; 82 | } 83 | try { 84 | base64Str = base64Str.replace(/-/g, "+").replace(/_/g, "/"); 85 | const decode = atob(base64Str); 86 | const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0)); 87 | return { earlyData: arryBuffer.buffer, error: null }; 88 | } catch (error) { 89 | return { error }; 90 | } 91 | } 92 | function safeCloseWebSocket(socket) { 93 | try { 94 | if (socket.readyState === WS_READY_STATE_OPEN) { 95 | socket.close(); 96 | } 97 | } catch (error) { 98 | console.error("safeCloseWebSocket error", error); 99 | } 100 | } 101 | function processVlessHeader(vlessBuffer, userID) { 102 | if (vlessBuffer.byteLength < 24) { 103 | return { 104 | hasError: true, 105 | message: "invalid data" 106 | }; 107 | } 108 | const version = new Uint8Array(vlessBuffer.slice(0, 1)); 109 | let isValidUser = false; 110 | let isUDP = false; 111 | if (stringify_default(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) { 112 | isValidUser = true; 113 | } 114 | if (!isValidUser) { 115 | return { 116 | hasError: true, 117 | message: "invalid user" 118 | }; 119 | } 120 | const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0]; 121 | const command = new Uint8Array( 122 | vlessBuffer.slice(18 + optLength, 18 + optLength + 1) 123 | )[0]; 124 | if (command === 1) { 125 | } else if (command === 2) { 126 | isUDP = true; 127 | } else { 128 | return { 129 | hasError: true, 130 | message: `command ${command} is not support, command 01-tcp,02-udp,03-mux` 131 | }; 132 | } 133 | const portIndex = 18 + optLength + 1; 134 | const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2); 135 | const portRemote = new DataView(portBuffer).getInt16(0); 136 | let addressIndex = portIndex + 2; 137 | const addressBuffer = new Uint8Array( 138 | vlessBuffer.slice(addressIndex, addressIndex + 1) 139 | ); 140 | const addressType = addressBuffer[0]; 141 | let addressLength = 0; 142 | let addressValueIndex = addressIndex + 1; 143 | let addressValue = ""; 144 | switch (addressType) { 145 | case 1: 146 | addressLength = 4; 147 | addressValue = new Uint8Array( 148 | vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) 149 | ).join("."); 150 | break; 151 | case 2: 152 | addressLength = new Uint8Array( 153 | vlessBuffer.slice(addressValueIndex, addressValueIndex + 1) 154 | )[0]; 155 | addressValueIndex += 1; 156 | addressValue = new TextDecoder().decode( 157 | vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) 158 | ); 159 | break; 160 | case 3: 161 | addressLength = 16; 162 | const dataView = new DataView( 163 | vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) 164 | ); 165 | const ipv6 = []; 166 | for (let i = 0; i < 8; i++) { 167 | ipv6.push(dataView.getUint16(i * 2).toString(16)); 168 | } 169 | addressValue = ipv6.join(":"); 170 | break; 171 | default: 172 | console.log(`invild addressType is ${addressType}`); 173 | } 174 | if (!addressValue) { 175 | return { 176 | hasError: true, 177 | message: `addressValue is empty, addressType is ${addressType}` 178 | }; 179 | } 180 | return { 181 | hasError: false, 182 | addressRemote: addressValue, 183 | portRemote, 184 | rawDataIndex: addressValueIndex + addressLength, 185 | vlessVersion: version, 186 | isUDP 187 | }; 188 | } 189 | 190 | // libs/cf-worker-vless/src/cf-worker-vless.ts 191 | import { connect } from "cloudflare:sockets"; 192 | function delay2(ms) { 193 | return new Promise((resolve, rej) => { 194 | setTimeout(resolve, ms); 195 | }); 196 | } 197 | var cf_worker_vless_default = { 198 | async fetch(request, env, ctx) { 199 | let address = ""; 200 | let portWithRandomLog = ""; 201 | const userID = env.UUID || "00fdxxxd-81xx-xx54-bxx2-b6xxx2aXXXXX"; // ENTER YOUR OWN UUID HERE 202 | const isVaildUUID = validate_default(userID); 203 | const log = (info, event) => { 204 | console.log(`[${address}:${portWithRandomLog}] ${info}`, event || ""); 205 | }; 206 | const upgradeHeader = request.headers.get("Upgrade"); 207 | if (!upgradeHeader || upgradeHeader !== "websocket") { 208 | return new Response( 209 | ` 210 |