参考 ca110us 的代码,修改了代理和加密部分,使用 CF 的 Worker 脚本自动生成订阅地址,也可后续手动优选 IP。
原 Github 项目地址:
https://github.com/ca110us/epeius
前提
确定有自己的域名托管在 Cloud Flare 中。
部署
1.登录 CloudFlare,点击 Workers/Pages,创建Workers。
2.输入一个喜欢的项目名字,创建项目。
3.部署站点,编辑CODE,贴入下面代码:
// 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 = 'bd7a2ae56e0de3f72a0655b3f74b355fb4fdad5004149e6aa7ce9026';
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://TSI2nice@${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
4.密码部分默认是TSI2nice,也可自行修改,SHA224 加密运算工具:
https://tools.nonni.cn/hash.html
5.修改后点击部署,然后进入项目,在 Setting 的 Triggers 中设置一个自定义域名。
6.地址栏输入链接查看节点订阅信息地址:
https://自定义域名/link
使用 CM 修改的 Worker 脚本自动生成自适应订阅,SUB CLASH 和 SURGE 订阅地址,自动优选 IP。
Github 项目地址:
https://github.com/cmliu/edgetunnel
部署
1.登录 CloudFlare,点击 Workers 和 Pages,Pages ,直接上传创建 – 上传资产。
2.输入一个喜欢的项目名字,创建项目。
3.下载 worker.zip,然后在上传资产页面点击从计算机中选择 – 上传压缩文件,部署站点,继续处理项目。
4.在 UUID 生成工具网站 https://nonni.cn/uuid 生成一个 UUID。
5.在项目页面点设置 – 环境变量 – 添加变量,变量名称为 UUID ,变量值为刚才生成的那个 UUID ,点击保存。
6.在项目页面点部署,创建新部署,再次上传刚才上传过的 worker.zip 文件,保存并部署。
7.点击部署中的域名链接访问站点,如果有内容出现,就证明部署成功了。
8.查看节点信息地址: https://域名链接/UUID
9.(可选项)如果有在 Cloudflare 中有域名托管,也可以在自定义域中设置自定义域。
SSH to Linux
CoreMark 跑分
/etc/coremark.sh && cat /etc/bench.log
CPU 信息
lscpu
cat /proc/cpuinfo
CPU研发代号
gcc -march=native -Q --help=target|grep march
系统负载情况
cat /proc/loadavg
查看磁盘内容
cat /proc/mounts
查看内存
cat /proc/meminfo
free -h
网卡大包测试
ping 10.10.10.10 -l 1000 -t
Linux 安装 V2RayA 客户端
官方文档参考
https://v2raya.org/docs/prologue/introduction/
在线安装
- 在线安装 V2ray-core 核心需要科学上网,然后执行
curl -O https://cdn.jsdelivr.net/gh/v2rayA/v2rayA@master/install/go.sh
sudo bash go.sh - 添加公钥和软件源,并安装v2raya客户端
wget -qO - https://apt.v2raya.mzz.pub/key/public-key.asc | sudo apt-key add -
echo "deb http://apt.v2raya.mzz.pub/ v2raya main" | sudo tee /etc/apt/sources.list.d/v2raya.list
sudo apt update
sudo apt install v2raya - 设置服务开机启动
systemctl enable --now v2raya.service
- 重启服务
systemctl restart v2raya.service
- 浏览器登录后台,配置节点信息,代理协议与端口,勾选服务器,左上角点击启动。
http://127.0.0.1:2017
下载安装
- 下载对应平台的 deb 安装包
- 安装对应 deb
sudo apt install /path/download/installer_debian_xxx_vxxx.deb
- 启动服务
sudo systemctl start v2raya.service
- 设置服务开机自动启动
sudo systemctl enable v2raya.service
- 重启服务
systemctl restart v2raya.service
- 浏览器登录后台,配置节点信息,代理协议与端口,勾选服务器,左上角点击启动。
http://127.0.0.1:2017
其他
忘记面板密码重置
v2raya --reset-password
Cloudflare 开始推广自己的 WARP 服务,推广期内提供免费的VPN服务体验,能薅羊毛多久不可知,不过目前还是能用的,只是最近中国区与香港区的用户经常不能注册,是因为滥用被 Cloudflare 限制了,能不能用有时靠缘分,以下是网上常用的方法分享一下。
安装使用方法:
- 下载解压WARP.7z,安装 Cloudflare_WARP_Release-x64.msi。
- 运行 warpip 目录中的 优选IP.exe (Macbook 上在终端运行 warpip-mac.sh,Linux 上在终端运行 warpip-linux.sh),输入 1 回车。
- 等待运行结果,目录中输出文件 result.csv,用记事本打开挑选靠前的低延迟IP端口。
- 运行目录中的 设置优选IP.exe,填入上一步挑选的低延迟IP端口。
- 桌面右下角系统区打开 WARP,连接。
- 打开浏览器,访问 www.google.com 测试一下。
下载地址:
http://pan.nonni.cn/%e4%b8%80%e6%84%8f%e5%ad%a4%e8%a1%8c/%e8%bd%af%e4%bb%b6/VPN/WARP.7z