├── proxyip.zip └── worker -trojan.js /proxyip.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freedomnet25500/trojsn-worker/1cd6d0d27dda1bf5bdb37f27147bc4623fc0b65f/proxyip.zip -------------------------------------------------------------------------------- /worker -trojan.js: -------------------------------------------------------------------------------- 1 | // src/worker.js 2 | import { connect } from "cloudflare:sockets"; 3 | let sha224Password = '08f32643dbdacf81d0d511f1ee24b06de759e90f8edf742bbdc57d88'; 4 | const proxyIPs = ["workers.cloudflare.cyou"]; // 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']; 5 | let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; 6 | const worker_default = { 7 | /** 8 | * @param {import("@cloudflare/workers-types").Request} request 9 | * @param {{UUID: string, PROXYIP: string}} env 10 | * @param {import("@cloudflare/workers-types").ExecutionContext} ctx 11 | * @returns {Promise} 12 | */ 13 | async fetch(request, env, ctx) { 14 | try { 15 | proxyIP = env.PROXYIP || proxyIP; 16 | const upgradeHeader = request.headers.get("Upgrade"); 17 | if (!upgradeHeader || upgradeHeader !== "websocket") { 18 | const url = new URL(request.url); 19 | switch (url.pathname) { 20 | case "/link": 21 | const host = request.headers.get('Host'); 22 | return new Response(`trojan://ca110us@${host}:443/?type=ws&host=${host}&security=tls`, { 23 | status: 200, 24 | headers: { 25 | "Content-Type": "text/plain;charset=utf-8", 26 | } 27 | }); 28 | default: 29 | return new Response("404 Not found", { status: 404 }); 30 | } 31 | } else { 32 | return await trojanOverWSHandler(request); 33 | } 34 | } catch (err) { 35 | let e = err; 36 | return new Response(e.toString()); 37 | } 38 | } 39 | }; 40 | 41 | async function trojanOverWSHandler(request) { 42 | const webSocketPair = new WebSocketPair(); 43 | const [client, webSocket] = Object.values(webSocketPair); 44 | webSocket.accept(); 45 | let address = ""; 46 | let portWithRandomLog = ""; 47 | const log = (info, event) => { 48 | console.log(`[${address}:${portWithRandomLog}] ${info}`, event || ""); 49 | }; 50 | const earlyDataHeader = request.headers.get("sec-websocket-protocol") || ""; 51 | const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); 52 | let remoteSocketWapper = { 53 | value: null 54 | }; 55 | let udpStreamWrite = null; 56 | readableWebSocketStream.pipeTo(new WritableStream({ 57 | async write(chunk, controller) { 58 | if (udpStreamWrite) { 59 | return udpStreamWrite(chunk); 60 | } 61 | if (remoteSocketWapper.value) { 62 | const writer = remoteSocketWapper.value.writable.getWriter(); 63 | await writer.write(chunk); 64 | writer.releaseLock(); 65 | return; 66 | } 67 | const { 68 | hasError, 69 | message, 70 | portRemote = 443, 71 | addressRemote = "", 72 | rawClientData 73 | } = await parseTrojanHeader(chunk); 74 | address = addressRemote; 75 | portWithRandomLog = `${portRemote}--${Math.random()} tcp`; 76 | if (hasError) { 77 | throw new Error(message); 78 | return; 79 | } 80 | handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, log); 81 | }, 82 | close() { 83 | log(`readableWebSocketStream is closed`); 84 | }, 85 | abort(reason) { 86 | log(`readableWebSocketStream is aborted`, JSON.stringify(reason)); 87 | } 88 | })).catch((err) => { 89 | log("readableWebSocketStream pipeTo error", err); 90 | }); 91 | return new Response(null, { 92 | status: 101, 93 | // @ts-ignore 94 | webSocket: client 95 | }); 96 | } 97 | 98 | async function parseTrojanHeader(buffer) { 99 | if (buffer.byteLength < 56) { 100 | return { 101 | hasError: true, 102 | message: "invalid data" 103 | }; 104 | } 105 | let crLfIndex = 56; 106 | if (new Uint8Array(buffer.slice(56, 57))[0] !== 0x0d || new Uint8Array(buffer.slice(57, 58))[0] !== 0x0a) { 107 | return { 108 | hasError: true, 109 | message: "invalid header format (missing CR LF)" 110 | }; 111 | } 112 | const password = new TextDecoder().decode(buffer.slice(0, crLfIndex)); 113 | if (password !== sha224Password) { 114 | return { 115 | hasError: true, 116 | message: "invalid password" 117 | }; 118 | } 119 | 120 | const socks5DataBuffer = buffer.slice(crLfIndex + 2); 121 | if (socks5DataBuffer.byteLength < 6) { 122 | return { 123 | hasError: true, 124 | message: "invalid SOCKS5 request data" 125 | }; 126 | } 127 | 128 | const view = new DataView(socks5DataBuffer); 129 | const cmd = view.getUint8(0); 130 | if (cmd !== 1) { 131 | return { 132 | hasError: true, 133 | message: "unsupported command, only TCP (CONNECT) is allowed" 134 | }; 135 | } 136 | 137 | const atype = view.getUint8(1); 138 | // 0x01: IPv4 address 139 | // 0x03: Domain name 140 | // 0x04: IPv6 address 141 | let addressLength = 0; 142 | let addressIndex = 2; 143 | let address = ""; 144 | switch (atype) { 145 | case 1: 146 | addressLength = 4; 147 | address = new Uint8Array( 148 | socks5DataBuffer.slice(addressIndex, addressIndex + addressLength) 149 | ).join("."); 150 | break; 151 | case 3: 152 | addressLength = new Uint8Array( 153 | socks5DataBuffer.slice(addressIndex, addressIndex + 1) 154 | )[0]; 155 | addressIndex += 1; 156 | address = new TextDecoder().decode( 157 | socks5DataBuffer.slice(addressIndex, addressIndex + addressLength) 158 | ); 159 | break; 160 | case 4: 161 | addressLength = 16; 162 | const dataView = new DataView(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)); 163 | const ipv6 = []; 164 | for (let i = 0; i < 8; i++) { 165 | ipv6.push(dataView.getUint16(i * 2).toString(16)); 166 | } 167 | address = ipv6.join(":"); 168 | break; 169 | default: 170 | return { 171 | hasError: true, 172 | message: `invalid addressType is ${atype}` 173 | }; 174 | } 175 | 176 | if (!address) { 177 | return { 178 | hasError: true, 179 | message: `address is empty, addressType is ${atype}` 180 | }; 181 | } 182 | 183 | const portIndex = addressIndex + addressLength; 184 | const portBuffer = socks5DataBuffer.slice(portIndex, portIndex + 2); 185 | const portRemote = new DataView(portBuffer).getUint16(0); 186 | return { 187 | hasError: false, 188 | addressRemote: address, 189 | portRemote, 190 | rawClientData: socks5DataBuffer.slice(portIndex + 4) 191 | }; 192 | } 193 | 194 | async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, log) { 195 | async function connectAndWrite(address, port) { 196 | const tcpSocket2 = connect({ 197 | hostname: address, 198 | port 199 | }); 200 | remoteSocket.value = tcpSocket2; 201 | log(`connected to ${address}:${port}`); 202 | const writer = tcpSocket2.writable.getWriter(); 203 | await writer.write(rawClientData); 204 | writer.releaseLock(); 205 | return tcpSocket2; 206 | } 207 | async function retry() { 208 | const tcpSocket2 = await connectAndWrite(proxyIP || addressRemote, portRemote); 209 | tcpSocket2.closed.catch((error) => { 210 | console.log("retry tcpSocket closed error", error); 211 | }).finally(() => { 212 | safeCloseWebSocket(webSocket); 213 | }); 214 | remoteSocketToWS(tcpSocket2, webSocket, null, log); 215 | } 216 | const tcpSocket = await connectAndWrite(addressRemote, portRemote); 217 | remoteSocketToWS(tcpSocket, webSocket, retry, log); 218 | } 219 | 220 | function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) { 221 | let readableStreamCancel = false; 222 | const stream = new ReadableStream({ 223 | start(controller) { 224 | webSocketServer.addEventListener("message", (event) => { 225 | if (readableStreamCancel) { 226 | return; 227 | } 228 | const message = event.data; 229 | controller.enqueue(message); 230 | }); 231 | webSocketServer.addEventListener("close", () => { 232 | safeCloseWebSocket(webSocketServer); 233 | if (readableStreamCancel) { 234 | return; 235 | } 236 | controller.close(); 237 | }); 238 | webSocketServer.addEventListener("error", (err) => { 239 | log("webSocketServer error"); 240 | controller.error(err); 241 | }); 242 | const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); 243 | if (error) { 244 | controller.error(error); 245 | } else if (earlyData) { 246 | controller.enqueue(earlyData); 247 | } 248 | }, 249 | pull(controller) {}, 250 | cancel(reason) { 251 | if (readableStreamCancel) { 252 | return; 253 | } 254 | log(`readableStream was canceled, due to ${reason}`); 255 | readableStreamCancel = true; 256 | safeCloseWebSocket(webSocketServer); 257 | } 258 | }); 259 | return stream; 260 | } 261 | 262 | async function remoteSocketToWS(remoteSocket, webSocket, retry, log) { 263 | let hasIncomingData = false; 264 | await remoteSocket.readable.pipeTo( 265 | new WritableStream({ 266 | start() {}, 267 | /** 268 | * 269 | * @param {Uint8Array} chunk 270 | * @param {*} controller 271 | */ 272 | async write(chunk, controller) { 273 | hasIncomingData = true; 274 | if (webSocket.readyState !== WS_READY_STATE_OPEN) { 275 | controller.error( 276 | "webSocket connection is not open" 277 | ); 278 | } 279 | webSocket.send(chunk); 280 | }, 281 | close() { 282 | log(`remoteSocket.readable is closed, hasIncomingData: ${hasIncomingData}`); 283 | }, 284 | abort(reason) { 285 | console.error("remoteSocket.readable abort", reason); 286 | } 287 | }) 288 | ).catch((error) => { 289 | console.error( 290 | `remoteSocketToWS error:`, 291 | error.stack || error 292 | ); 293 | safeCloseWebSocket(webSocket); 294 | }); 295 | if (hasIncomingData === false && retry) { 296 | log(`retry`); 297 | retry(); 298 | } 299 | } 300 | 301 | function base64ToArrayBuffer(base64Str) { 302 | if (!base64Str) { 303 | return { error: null }; 304 | } 305 | try { 306 | base64Str = base64Str.replace(/-/g, "+").replace(/_/g, "/"); 307 | const decode = atob(base64Str); 308 | const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0)); 309 | return { earlyData: arryBuffer.buffer, error: null }; 310 | } catch (error) { 311 | return { error }; 312 | } 313 | } 314 | 315 | let WS_READY_STATE_OPEN = 1; 316 | let WS_READY_STATE_CLOSING = 2; 317 | 318 | function safeCloseWebSocket(socket) { 319 | try { 320 | if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) { 321 | socket.close(); 322 | } 323 | } catch (error) { 324 | console.error("safeCloseWebSocket error", error); 325 | } 326 | } 327 | export { 328 | worker_default as 329 | default 330 | }; 331 | //# sourceMappingURL=worker.js.map 332 | --------------------------------------------------------------------------------