Trojan

https://github.com/ca110us/epeius/blob/main/src/worker.js https://github.com/shiteThings/cfworker_trojan/blob/main/worker.js

  • 在 Cloudflare Workers 仪表盘中创建一个新的 Worker

  • 将以下代码粘贴到 Worker 代码编辑器中

原版代码

ca110us and worker.js
// src/worker.js
import { connect } from "cloudflare:sockets";
let sha224Password = '08f32643dbdacf81d0d511f1ee24b06de759e90f8edf742bbdc57d88';
let proxyIP = "";

if (!isValidSHA224(sha224Password)) {
    throw new Error('sha224Password is not valid');
}

const worker_default = {
    /**
     * @param {import("@cloudflare/workers-types").Request} request
     * @param {{SHA224PASS: string, PROXYIP: string}} env
     * @param {import("@cloudflare/workers-types").ExecutionContext} ctx
     * @returns {Promise<Response>}
     */
    async fetch(request, env, ctx) {
        try {
            proxyIP = env.PROXYIP || proxyIP;
            sha224Password = env.SHA224PASS || sha224Password
            const upgradeHeader = request.headers.get("Upgrade");
            if (!upgradeHeader || upgradeHeader !== "websocket") {
                const url = new URL(request.url);
                switch (url.pathname) {
                    case "/link":
                        const host = request.headers.get('Host');
                        return new Response(`trojan://ca110us@${host}:443/?type=ws&host=${host}&security=tls`, {
                            status: 200,
                            headers: {
                                "Content-Type": "text/plain;charset=utf-8",
                            }
                        });
                    default:
                        return new Response("404 Not found", { status: 404 });
                }
            } else {
                return await trojanOverWSHandler(request);
            }
        } catch (err) {
            let e = err;
            return new Response(e.toString());
        }
    }
};

async function trojanOverWSHandler(request) {
    const webSocketPair = new WebSocketPair();
    const [client, webSocket] = Object.values(webSocketPair);
    webSocket.accept();
    let address = "";
    let portWithRandomLog = "";
    const log = (info, event) => {
        console.log(`[${address}:${portWithRandomLog}] ${info}`, event || "");
    };
    const earlyDataHeader = request.headers.get("sec-websocket-protocol") || "";
    const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
    let remoteSocketWapper = {
        value: null
    };
    let udpStreamWrite = null;
    readableWebSocketStream.pipeTo(new WritableStream({
        async write(chunk, controller) {
            if (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 = "",
                rawClientData
            } = await parseTrojanHeader(chunk);
            address = addressRemote;
            portWithRandomLog = `${portRemote}--${Math.random()} tcp`;
            if (hasError) {
                throw new Error(message);
                return;
            }
            handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, log);
        },
        close() {
            log(`readableWebSocketStream is closed`);
        },
        abort(reason) {
            log(`readableWebSocketStream is aborted`, JSON.stringify(reason));
        }
    })).catch((err) => {
        log("readableWebSocketStream pipeTo error", err);
    });
    return new Response(null, {
        status: 101,
        // @ts-ignore
        webSocket: client
    });
}

async function parseTrojanHeader(buffer) {
    if (buffer.byteLength < 56) {
        return {
            hasError: true,
            message: "invalid data"
        };
    }
    let crLfIndex = 56;
    if (new Uint8Array(buffer.slice(56, 57))[0] !== 0x0d || new Uint8Array(buffer.slice(57, 58))[0] !== 0x0a) {
        return {
            hasError: true,
            message: "invalid header format (missing CR LF)"
        };
    }
    const password = new TextDecoder().decode(buffer.slice(0, crLfIndex));
    if (password !== sha224Password) {
        return {
            hasError: true,
            message: "invalid password"
        };
    }

    const socks5DataBuffer = buffer.slice(crLfIndex + 2);
    if (socks5DataBuffer.byteLength < 6) {
        return {
            hasError: true,
            message: "invalid SOCKS5 request data"
        };
    }

    const view = new DataView(socks5DataBuffer);
    const cmd = view.getUint8(0);
    if (cmd !== 1) {
        return {
            hasError: true,
            message: "unsupported command, only TCP (CONNECT) is allowed"
        };
    }

    const atype = view.getUint8(1);
    // 0x01: IPv4 address
    // 0x03: Domain name
    // 0x04: IPv6 address
    let addressLength = 0;
    let addressIndex = 2;
    let address = "";
    switch (atype) {
        case 1:
            addressLength = 4;
            address = new Uint8Array(
              socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)
            ).join(".");
            break;
        case 3:
            addressLength = new Uint8Array(
              socks5DataBuffer.slice(addressIndex, addressIndex + 1)
            )[0];
            addressIndex += 1;
            address = new TextDecoder().decode(
              socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)
            );
            break;
        case 4:
            addressLength = 16;
            const dataView = new DataView(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength));
            const ipv6 = [];
            for (let i = 0; i < 8; i++) {
                ipv6.push(dataView.getUint16(i * 2).toString(16));
            }
            address = ipv6.join(":");
            break;
        default:
            return {
                hasError: true,
                message: `invalid addressType is ${atype}`
            };
    }

    if (!address) {
        return {
            hasError: true,
            message: `address is empty, addressType is ${atype}`
        };
    }

    const portIndex = addressIndex + addressLength;
    const portBuffer = socks5DataBuffer.slice(portIndex, portIndex + 2);
    const portRemote = new DataView(portBuffer).getUint16(0);
    return {
        hasError: false,
        addressRemote: address,
        portRemote,
        rawClientData: socks5DataBuffer.slice(portIndex + 4)
    };
}

async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, log) {
    async function connectAndWrite(address, port) {
        const tcpSocket2 = connect({
            hostname: address,
            port
        });
        remoteSocket.value = tcpSocket2;
        log(`connected to ${address}:${port}`);
        const writer = tcpSocket2.writable.getWriter();
        await writer.write(rawClientData);
        writer.releaseLock();
        return tcpSocket2;
    }
    async function retry() {
        const tcpSocket2 = await connectAndWrite(proxyIP || addressRemote, portRemote);
        tcpSocket2.closed.catch((error) => {
            console.log("retry tcpSocket closed error", error);
        }).finally(() => {
            safeCloseWebSocket(webSocket);
        });
        remoteSocketToWS(tcpSocket2, webSocket, null, log);
    }
    const tcpSocket = await connectAndWrite(addressRemote, portRemote);
    remoteSocketToWS(tcpSocket, webSocket, retry, log);
}

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);
            });
            webSocketServer.addEventListener("close", () => {
                safeCloseWebSocket(webSocketServer);
                if (readableStreamCancel) {
                    return;
                }
                controller.close();
            });
            webSocketServer.addEventListener("error", (err) => {
                log("webSocketServer error");
                controller.error(err);
            });
            const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
            if (error) {
                controller.error(error);
            } else if (earlyData) {
                controller.enqueue(earlyData);
            }
        },
        pull(controller) {},
        cancel(reason) {
            if (readableStreamCancel) {
                return;
            }
            log(`readableStream was canceled, due to ${reason}`);
            readableStreamCancel = true;
            safeCloseWebSocket(webSocketServer);
        }
    });
    return stream;
}

async function remoteSocketToWS(remoteSocket, webSocket, retry, log) {
    let hasIncomingData = false;
    await remoteSocket.readable.pipeTo(
        new WritableStream({
            start() {},
            /**
             *
             * @param {Uint8Array} chunk
             * @param {*} controller
             */
            async write(chunk, controller) {
                hasIncomingData = true;
                if (webSocket.readyState !== WS_READY_STATE_OPEN) {
                    controller.error(
                        "webSocket connection is not open"
                    );
                }
                webSocket.send(chunk);
            },
            close() {
                log(`remoteSocket.readable is closed, hasIncomingData: ${hasIncomingData}`);
            },
            abort(reason) {
                console.error("remoteSocket.readable abort", reason);
            }
        })
    ).catch((error) => {
        console.error(
            `remoteSocketToWS error:`,
            error.stack || error
        );
        safeCloseWebSocket(webSocket);
    });
    if (hasIncomingData === false && retry) {
        log(`retry`);
        retry();
    }
}

function isValidSHA224(hash) {
    const sha224Regex = /^[0-9a-f]{56}$/i;
    return sha224Regex.test(hash);
}

function base64ToArrayBuffer(base64Str) {
    if (!base64Str) {
        return { error: null };
    }
    try {
        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 };
    }
}

let WS_READY_STATE_OPEN = 1;
let WS_READY_STATE_CLOSING = 2;

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);
    }
}
export {
    worker_default as
    default
};
//# sourceMappingURL=worker.js.map

已经添加了proxyIPs👇

cfworker_trojan and worker.js
// src/worker.js
import { connect } from "cloudflare:sockets";
const proxyIPs = ['cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'workers.cloudflare.cyou'];

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

let sha224Password = '08f32643dbdacf81d0d511f1ee24b06de759e90f8edf742bbdc57d88';

const worker_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 {
           proxyIP = env.PROXYIP || proxyIP;
           const upgradeHeader = request.headers.get("Upgrade");
           if (!upgradeHeader || upgradeHeader !== "websocket") {
               const url = new URL(request.url);
               switch (url.pathname) {
                   case "/link":
                       const host = request.headers.get('Host');
                       return new Response(`trojan://ca110us@${host}:443/?type=ws&host=${host}&security=tls`, {
                           status: 200,
                           headers: {
                               "Content-Type": "text/plain;charset=utf-8",
                          }
                      });
                   default:
                       return new Response("404 Not found", { status: 404 });
              }
          } else {
               return await trojanOverWSHandler(request);
          }
      } catch (err) {
           let e = err;
           return new Response(e.toString());
      }
  }
};

async function trojanOverWSHandler(request) {
   const webSocketPair = new WebSocketPair();
   const [client, webSocket] = Object.values(webSocketPair);
   webSocket.accept();
   let address = "";
   let portWithRandomLog = "";
   const log = (info, event) => {
       console.log(`[${address}:${portWithRandomLog}] ${info}`, event || "");
  };
   const earlyDataHeader = request.headers.get("sec-websocket-protocol") || "";
   const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
   let remoteSocketWapper = {
       value: null
  };
   let udpStreamWrite = null;
   readableWebSocketStream.pipeTo(new WritableStream({
       async write(chunk, controller) {
           if (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 = "",
               rawClientData
          } = await parseTrojanHeader(chunk);
           address = addressRemote;
           portWithRandomLog = `${portRemote}--${Math.random()} tcp`;
           if (hasError) {
               throw new Error(message);
               return;
          }
           handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, log);
      },
       close() {
           log(`readableWebSocketStream is closed`);
      },
       abort(reason) {
           log(`readableWebSocketStream is aborted`, JSON.stringify(reason));
      }
  })).catch((err) => {
       log("readableWebSocketStream pipeTo error", err);
  });
   return new Response(null, {
       status: 101,
       // @ts-ignore
       webSocket: client
  });
}

async function parseTrojanHeader(buffer) {
   if (buffer.byteLength < 56) {
       return {
           hasError: true,
           message: "invalid data"
      };
  }
   let crLfIndex = 56;
   if (new Uint8Array(buffer.slice(56, 57))[0] !== 0x0d || new Uint8Array(buffer.slice(57, 58))[0] !== 0x0a) {
       return {
           hasError: true,
           message: "invalid header format (missing CR LF)"
      };
  }
   const password = new TextDecoder().decode(buffer.slice(0, crLfIndex));
   if (password !== sha224Password) {
       return {
           hasError: true,
           message: "invalid password"
      };
  }

   const socks5DataBuffer = buffer.slice(crLfIndex + 2);
   if (socks5DataBuffer.byteLength < 6) {
       return {
           hasError: true,
           message: "invalid SOCKS5 request data"
      };
  }

   const view = new DataView(socks5DataBuffer);
   const cmd = view.getUint8(0);
   if (cmd !== 1) {
       return {
           hasError: true,
           message: "unsupported command, only TCP (CONNECT) is allowed"
      };
  }

   const atype = view.getUint8(1);
   // 0x01: IPv4 address
   // 0x03: Domain name
   // 0x04: IPv6 address
   let addressLength = 0;
   let addressIndex = 2;
   let address = "";
   switch (atype) {
       case 1:
           addressLength = 4;
           address = new Uint8Array(
             socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)
          ).join(".");
           break;
       case 3:
           addressLength = new Uint8Array(
             socks5DataBuffer.slice(addressIndex, addressIndex + 1)
          )[0];
           addressIndex += 1;
           address = new TextDecoder().decode(
             socks5DataBuffer.slice(addressIndex, addressIndex + addressLength)
          );
           break;
       case 4:
           addressLength = 16;
           const dataView = new DataView(socks5DataBuffer.slice(addressIndex, addressIndex + addressLength));
           const ipv6 = [];
           for (let i = 0; i < 8; i++) {
               ipv6.push(dataView.getUint16(i * 2).toString(16));
          }
           address = ipv6.join(":");
           break;
       default:
           return {
               hasError: true,
               message: `invalid addressType is ${atype}`
          };
  }

   if (!address) {
       return {
           hasError: true,
           message: `address is empty, addressType is ${atype}`
      };
  }

   const portIndex = addressIndex + addressLength;
   const portBuffer = socks5DataBuffer.slice(portIndex, portIndex + 2);
   const portRemote = new DataView(portBuffer).getUint16(0);
   return {
       hasError: false,
       addressRemote: address,
       portRemote,
       rawClientData: socks5DataBuffer.slice(portIndex + 4)
  };
}

async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, log) {
   async function connectAndWrite(address, port) {
       const tcpSocket2 = connect({
           hostname: address,
           port
      });
       remoteSocket.value = tcpSocket2;
       log(`connected to ${address}:${port}`);
       const writer = tcpSocket2.writable.getWriter();
       await writer.write(rawClientData);
       writer.releaseLock();
       return tcpSocket2;
  }
   async function retry() {
       const tcpSocket2 = await connectAndWrite(proxyIP || addressRemote, portRemote);
       tcpSocket2.closed.catch((error) => {
           console.log("retry tcpSocket closed error", error);
      }).finally(() => {
           safeCloseWebSocket(webSocket);
      });
       remoteSocketToWS(tcpSocket2, webSocket, null, log);
  }
   const tcpSocket = await connectAndWrite(addressRemote, portRemote);
   remoteSocketToWS(tcpSocket, webSocket, retry, log);
}

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);
          });
           webSocketServer.addEventListener("close", () => {
               safeCloseWebSocket(webSocketServer);
               if (readableStreamCancel) {
                   return;
              }
               controller.close();
          });
           webSocketServer.addEventListener("error", (err) => {
               log("webSocketServer error");
               controller.error(err);
          });
           const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
           if (error) {
               controller.error(error);
          } else if (earlyData) {
               controller.enqueue(earlyData);
          }
      },
       pull(controller) {},
       cancel(reason) {
           if (readableStreamCancel) {
               return;
          }
           log(`readableStream was canceled, due to ${reason}`);
           readableStreamCancel = true;
           safeCloseWebSocket(webSocketServer);
      }
  });
   return stream;
}

async function remoteSocketToWS(remoteSocket, webSocket, retry, log) {
   let hasIncomingData = false;
   await remoteSocket.readable.pipeTo(
       new WritableStream({
           start() {},
           /**
            *
            * @param {Uint8Array} chunk
            * @param {*} controller
            */
           async write(chunk, controller) {
               hasIncomingData = true;
               if (webSocket.readyState !== WS_READY_STATE_OPEN) {
                   controller.error(
                       "webSocket connection is not open"
                  );
              }
               webSocket.send(chunk);
          },
           close() {
               log(`remoteSocket.readable is closed, hasIncomingData: ${hasIncomingData}`);
          },
           abort(reason) {
               console.error("remoteSocket.readable abort", reason);
          }
      })
  ).catch((error) => {
       console.error(
           `remoteSocketToWS error:`,
           error.stack || error
      );
       safeCloseWebSocket(webSocket);
  });
   if (hasIncomingData === false && retry) {
       log(`retry`);
       retry();
  }
}

function base64ToArrayBuffer(base64Str) {
   if (!base64Str) {
       return { error: null };
  }
   try {
       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 };
  }
}

let WS_READY_STATE_OPEN = 1;
let WS_READY_STATE_CLOSING = 2;

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);
  }
}
export {
   worker_default as
   default
};
//# sourceMappingURL=worker.js.map
  • 用你自己的密码替换 sha224Password,你可以在 这里 生成密码。或者,你可以之后在 Cloudflare Workers 设置中增加 SHA224PASS 环境变量

  • 将自定义域名绑定到 Worker

  • 访问 https://[你的域名]/link 并用你的明文密码替换 ca110us

Last updated