HostYun 原名主机分享,由国内的几个老服务器玩家创立和运营,机器用于学习和入门型测试使用的,不是很适合建立商业站点,但是性价比很好,它的CN2GIA,三网直连线路和香港主机比较推荐,优惠码: hostyun。
HostYun 官网链接
Bandwagonhost 俗称搬瓦工,非常知名的服务商,早年推广便宜主机,后来全面转型成为高品质服务商,价格自然也水涨船高,一分钱一分货,它家提供 CN2-GIA 的线路,国内三网直连,性能稳定可靠。
它家机房很多,用了快十年了,目前测试发现 LOS ANGELES - CN2 GIA DC9 线路最优,但不是随时都有产品提供,最便宜的主机是2核/1G RAM/20GB SSD/1T月流量,年付 $49.99,优惠后价格是 $46.70 USD Annually,如果购买注意使用优惠码 BWHCGLUKKB (Promotional Code BWHCGLUKKB - 6.58% Recurring Discount)。
搬瓦工官网链接
Vultr 是比较稳的商家,特点是功能简单方便,价格合理,还可以用来方便部署廉价Linux邮件服务器(25端口需开ticket人工开放),是个可用性较高的云服务商,支持的区域很多,缺点是缺少到国内的优化线路,适合本土业务。
支持的操作系统有Windows, centOS,Ubuntu,Debian,Fedora,coreOS,FreeBSD,OpenBSD。
Vultr 官网链接
HostDare 是一家小型国外服务商,偶尔有营销补货便宜年付方案,提供普通 QKVM 方案 和 CN2 GIACKVM 方案。CKVM 九折优惠码: 0LFV8EG02E,最便宜的 50M 带宽 CKVM 方案年付 44$,截止到5月31日。
HostDare 官网链接
LocVPS 是一家深圳个人厂商,稳景科技,主要专注香港,新加坡,日本等亚洲CN2优化线路,以及部分的美国西岸线路。产品包括 Xen 和 KVM 架构。它家产品相对价格低廉,可以使用老左的渠道七五折优惠码 LAOZUO。机房选择可以用 ping 等工具测一下到自家的网络情况,各个机房的测试IP: 香港葵湾 154.91.194.172,香港大浦 103.193.128.44,日本东京 103.85.24.254,新加坡 185.239.225.55。
LocVPS官网链接
选择主机后,可以使用站长工具多地ping一下:
https://ping.chinaz.com/
通过映射sni伪装,可以无需翻墙直接访问google。
食用方法
1.桌面右键创建一个快捷方式,在键入对象位置填入:
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --host-rules="MAP *google* g.cn," --host-resolver-rules="MAP g.cn 188.132.183.152," --test-type --ignore-certificate-errors https://www.google.com
2.点击下一步完成后,在任务管理器中终止EDGE进程,然后打开EDGE,在设置里--系统和性能--关闭“在 Microsoft Edge 关闭后继续运行后台扩展和应用”,关闭EDGE,运行快捷方式即可。
3.如果是chrome浏览器,则更简单,只需要桌面右键创建一个快捷方式,在键入对象位置填入:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --host-rules="MAP *google* g.cn," --host-resolver-rules="MAP g.cn 188.132.183.152," --test-type --ignore-certificate-errors https://www.google.com
参考 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
1.在 KoolCenter 固件中心下载对应型号的梅林改版固件:
https://fw.koolcenter.com
2.电脑连接路由器,点击左边的『系统管理』,选择『固件升级』- 点击上传,更新固件为梅林改版固件,进度条100%路由器会自行重启。
3.登录路由器,进行双清,手动配置路由器,完成后登录路由器,在『系统管理』–『系统设置』内勾选:Format JFFS partition at next boot 和 Enable JFFS custom scripts and configs, 点击『应用本页面设置』,成功后重启路由器,再次登录后出现『软件中心』。
4.下载安装科学插件,点击『软件中心』,软件中心版本,『进入设置』,点击『更新』保持当前版本与在线版本一致。点击 『手动安装』,选择已下载好的 MerlinClash插件文件。
merlinclash 插件使用文档与插件下载地址:
https://mcreadme.gitbook.io/mc
https://t.me/merlinclashfile
科学上网插件下载地址:
https://github.com/hq450/fancyss
使用 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 中有域名托管,也可以在自定义域中设置自定义域。
问题:
login Ubuntu 显示 There is 1 zombie process.
解决:
ps axo stat,ppid,pid,comm | grep -w defunct
sudo kill -9 <ppid>