mirror of
https://github.com/lush2020/edgetunnel.git
synced 2026-03-21 17:12:33 +08:00
add cf worker vless
This commit is contained in:
@@ -6,6 +6,6 @@ Running **V2ray** in the edge.
|
||||
|
||||
https://edgetunnel.114567.xyz/
|
||||
|
||||
# Feedback and Communication
|
||||
# Feedback
|
||||
|
||||
If you have any questions, please use https://t.me/edgetunnel for communication.
|
||||
If you have any questions, please open GitHub issue or use https://t.me/edgetunnel for communication.
|
||||
|
||||
337
libs/cf-worker-vless/cf-worker-vless-dev.js
Normal file
337
libs/cf-worker-vless/cf-worker-vless-dev.js
Normal file
@@ -0,0 +1,337 @@
|
||||
// node_modules/uuid/dist/esm-browser/regex.js
|
||||
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;
|
||||
|
||||
// node_modules/uuid/dist/esm-browser/validate.js
|
||||
function validate(uuid) {
|
||||
return typeof uuid === "string" && regex_default.test(uuid);
|
||||
}
|
||||
var validate_default = validate;
|
||||
|
||||
// node_modules/uuid/dist/esm-browser/stringify.js
|
||||
var byteToHex = [];
|
||||
for (let i = 0; i < 256; ++i) {
|
||||
byteToHex.push((i + 256).toString(16).slice(1));
|
||||
}
|
||||
function unsafeStringify(arr, offset = 0) {
|
||||
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();
|
||||
}
|
||||
function stringify(arr, offset = 0) {
|
||||
const uuid = unsafeStringify(arr, offset);
|
||||
if (!validate_default(uuid)) {
|
||||
throw TypeError("Stringified UUID is invalid");
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
var stringify_default = stringify;
|
||||
|
||||
// libs/vless-js/src/lib/vless-js.ts
|
||||
var WS_READY_STATE_OPEN = 1;
|
||||
function makeReadableWebSocketStream(ws, earlyDataHeader, log) {
|
||||
let readableStreamCancel = false;
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
ws.addEventListener("message", async (e) => {
|
||||
if (readableStreamCancel) {
|
||||
return;
|
||||
}
|
||||
const vlessBuffer = e.data;
|
||||
controller.enqueue(vlessBuffer);
|
||||
});
|
||||
ws.addEventListener("error", (e) => {
|
||||
log("socket has error");
|
||||
readableStreamCancel = true;
|
||||
controller.error(e);
|
||||
});
|
||||
ws.addEventListener("close", () => {
|
||||
try {
|
||||
log("webSocket is close");
|
||||
if (readableStreamCancel) {
|
||||
return;
|
||||
}
|
||||
controller.close();
|
||||
} catch (error2) {
|
||||
log(`websocketStream can't close DUE to `, error2);
|
||||
}
|
||||
});
|
||||
const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
|
||||
if (error) {
|
||||
log(`earlyDataHeader has invaild base64`);
|
||||
safeCloseWebSocket(ws);
|
||||
return;
|
||||
}
|
||||
if (earlyData) {
|
||||
controller.enqueue(earlyData);
|
||||
}
|
||||
},
|
||||
pull(controller) {
|
||||
},
|
||||
cancel(reason) {
|
||||
log(`websocketStream is cancel DUE to `, reason);
|
||||
if (readableStreamCancel) {
|
||||
return;
|
||||
}
|
||||
readableStreamCancel = true;
|
||||
safeCloseWebSocket(ws);
|
||||
}
|
||||
});
|
||||
}
|
||||
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 };
|
||||
}
|
||||
}
|
||||
function safeCloseWebSocket(socket) {
|
||||
try {
|
||||
if (socket.readyState === WS_READY_STATE_OPEN) {
|
||||
socket.close();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("safeCloseWebSocket error", error);
|
||||
}
|
||||
}
|
||||
function processVlessHeader(vlessBuffer, userID) {
|
||||
if (vlessBuffer.byteLength < 24) {
|
||||
return {
|
||||
hasError: true,
|
||||
message: "invalid data"
|
||||
};
|
||||
}
|
||||
const version = new Uint8Array(vlessBuffer.slice(0, 1));
|
||||
let isValidUser = false;
|
||||
let isUDP = false;
|
||||
if (stringify_default(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) {
|
||||
isValidUser = true;
|
||||
}
|
||||
if (!isValidUser) {
|
||||
return {
|
||||
hasError: true,
|
||||
message: "invalid user"
|
||||
};
|
||||
}
|
||||
const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];
|
||||
const command = new Uint8Array(
|
||||
vlessBuffer.slice(18 + optLength, 18 + optLength + 1)
|
||||
)[0];
|
||||
if (command === 1) {
|
||||
} else if (command === 2) {
|
||||
isUDP = true;
|
||||
} else {
|
||||
return {
|
||||
hasError: true,
|
||||
message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`
|
||||
};
|
||||
}
|
||||
const portIndex = 18 + optLength + 1;
|
||||
const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);
|
||||
const portRemote = new DataView(portBuffer).getInt16(0);
|
||||
let addressIndex = portIndex + 2;
|
||||
const addressBuffer = new Uint8Array(
|
||||
vlessBuffer.slice(addressIndex, addressIndex + 1)
|
||||
);
|
||||
const addressType = addressBuffer[0];
|
||||
let addressLength = 0;
|
||||
let addressValueIndex = addressIndex + 1;
|
||||
let addressValue = "";
|
||||
switch (addressType) {
|
||||
case 1:
|
||||
addressLength = 4;
|
||||
addressValue = new Uint8Array(
|
||||
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
|
||||
).join(".");
|
||||
break;
|
||||
case 2:
|
||||
addressLength = new Uint8Array(
|
||||
vlessBuffer.slice(addressValueIndex, addressValueIndex + 1)
|
||||
)[0];
|
||||
addressValueIndex += 1;
|
||||
addressValue = new TextDecoder().decode(
|
||||
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
addressLength = 16;
|
||||
const dataView = new DataView(
|
||||
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
|
||||
);
|
||||
const ipv6 = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
ipv6.push(dataView.getUint16(i * 2).toString(16));
|
||||
}
|
||||
addressValue = ipv6.join(":");
|
||||
break;
|
||||
default:
|
||||
console.log(`invild addressType is ${addressType}`);
|
||||
}
|
||||
if (!addressValue) {
|
||||
return {
|
||||
hasError: true,
|
||||
message: `addressValue is empty, addressType is ${addressType}`
|
||||
};
|
||||
}
|
||||
return {
|
||||
hasError: false,
|
||||
addressRemote: addressValue,
|
||||
portRemote,
|
||||
rawDataIndex: addressValueIndex + addressLength,
|
||||
vlessVersion: version,
|
||||
isUDP
|
||||
};
|
||||
}
|
||||
|
||||
// libs/cf-worker-vless/src/cf-worker-vless.ts
|
||||
import { connect } from "cloudflare:sockets";
|
||||
function delay2(ms) {
|
||||
return new Promise((resolve, rej) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
var cf_worker_vless_default = {
|
||||
async fetch(request, env, ctx) {
|
||||
let address = "";
|
||||
let portWithRandomLog = "";
|
||||
const userID = env.UUID;
|
||||
const log = (info, event) => {
|
||||
console.log(`[${address}:${portWithRandomLog}] ${info}`, event || "");
|
||||
};
|
||||
const upgradeHeader = request.headers.get("Upgrade");
|
||||
if (!upgradeHeader || upgradeHeader !== "websocket") {
|
||||
return new Response(`Expected Upgrade: websocket--uuid--${userID}`, {
|
||||
status: 426
|
||||
});
|
||||
}
|
||||
const webSocketPair = new WebSocketPair();
|
||||
const [client, webSocket] = Object.values(webSocketPair);
|
||||
const earlyDataHeader = request.headers.get("sec-websocket-protocol") || "";
|
||||
let remoteSocket = null;
|
||||
webSocket.accept();
|
||||
const readableWebSocketStream = makeReadableWebSocketStream(
|
||||
webSocket,
|
||||
earlyDataHeader,
|
||||
log
|
||||
);
|
||||
let vlessResponseHeader = new Uint8Array([0, 0]);
|
||||
let remoteConnectionReadyResolve;
|
||||
readableWebSocketStream.pipeTo(
|
||||
new WritableStream({
|
||||
async write(chunk, controller) {
|
||||
if (remoteSocket) {
|
||||
const writer2 = remoteSocket.writable.getWriter();
|
||||
await writer2.write(chunk);
|
||||
writer2.releaseLock();
|
||||
return;
|
||||
}
|
||||
const {
|
||||
hasError,
|
||||
message,
|
||||
portRemote,
|
||||
addressRemote,
|
||||
rawDataIndex,
|
||||
vlessVersion,
|
||||
isUDP
|
||||
} = processVlessHeader(chunk, userID);
|
||||
address = addressRemote || "";
|
||||
portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? "udp " : "tcp "} `;
|
||||
if (isUDP && portRemote != 53) {
|
||||
controller.error("UDP proxy only enable for DNS which is port 53");
|
||||
webSocket.close();
|
||||
return;
|
||||
}
|
||||
if (hasError) {
|
||||
controller.error(message);
|
||||
webSocket.close();
|
||||
return;
|
||||
}
|
||||
vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
|
||||
const rawClientData = chunk.slice(rawDataIndex);
|
||||
remoteSocket = connect({
|
||||
hostname: addressRemote,
|
||||
port: portRemote
|
||||
});
|
||||
log(`connected`);
|
||||
const writer = remoteSocket.writable.getWriter();
|
||||
await writer.write(rawClientData);
|
||||
writer.releaseLock();
|
||||
remoteConnectionReadyResolve(remoteSocket);
|
||||
},
|
||||
close() {
|
||||
console.log(
|
||||
`[${address}:${portWithRandomLog}] readableWebSocketStream is close`
|
||||
);
|
||||
},
|
||||
abort(reason) {
|
||||
console.log(
|
||||
`[${address}:${portWithRandomLog}] readableWebSocketStream is abort`,
|
||||
JSON.stringify(reason)
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
(async () => {
|
||||
await new Promise((resolve) => remoteConnectionReadyResolve = resolve);
|
||||
let count = 0;
|
||||
remoteSocket.readable.pipeTo(
|
||||
new WritableStream({
|
||||
start() {
|
||||
if (webSocket.readyState === WebSocket.READY_STATE_OPEN) {
|
||||
webSocket.send(vlessResponseHeader);
|
||||
}
|
||||
},
|
||||
async write(chunk, controller) {
|
||||
if (webSocket.readyState === WebSocket.READY_STATE_OPEN) {
|
||||
if (count++ > 2e4) {
|
||||
await delay2(1);
|
||||
}
|
||||
webSocket.send(chunk);
|
||||
} else {
|
||||
controller.error(
|
||||
"webSocket.readyState is not open, maybe close"
|
||||
);
|
||||
}
|
||||
},
|
||||
close() {
|
||||
console.log(
|
||||
`[${address}:${portWithRandomLog}] remoteConnection!.readable is close`
|
||||
);
|
||||
},
|
||||
abort(reason) {
|
||||
console.error(
|
||||
`[${address}:${portWithRandomLog}] remoteConnection!.readable abort`,
|
||||
reason
|
||||
);
|
||||
}
|
||||
})
|
||||
).catch((error) => {
|
||||
console.error(
|
||||
`[${address}:${portWithRandomLog}] processWebSocket has exception `,
|
||||
error.stack || error
|
||||
);
|
||||
safeCloseWebSocket2(webSocket);
|
||||
});
|
||||
})();
|
||||
return new Response(null, {
|
||||
status: 101,
|
||||
webSocket: client
|
||||
});
|
||||
}
|
||||
};
|
||||
function safeCloseWebSocket2(ws) {
|
||||
try {
|
||||
if (ws.readyState !== WebSocket.READY_STATE_CLOSED) {
|
||||
ws.close();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("safeCloseWebSocket error", error);
|
||||
}
|
||||
}
|
||||
export {
|
||||
cf_worker_vless_default as default
|
||||
};
|
||||
//# sourceMappingURL=cf-worker-vless.js.map
|
||||
@@ -14,7 +14,8 @@
|
||||
"node-vless:start": "node dist/apps/node-vless/main.js",
|
||||
"deno-vless:bunled": "nx deno-bunled deno-vless",
|
||||
"test": "nx test",
|
||||
"cf-worker": "wrangler dev ./libs/cf-worker-vless/src/index.ts --experimental-local"
|
||||
"cf-worker": "wrangler dev ./libs/cf-worker-vless/src/index.ts --experimental-local",
|
||||
"cf-worker-publish": "wrangler deploy"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
Reference in New Issue
Block a user