add cf-page-vless

This commit is contained in:
zizifn
2023-05-18 20:15:58 +08:00
committed by zizifn
parent c72747e89e
commit 0b632c025f
29 changed files with 294 additions and 105 deletions

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Edge Tunnel VLESS CF</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/styles.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,7 +1,7 @@
{
"name": "cf-page",
"name": "cf-page-vless",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/cf-page/src",
"sourceRoot": "apps/cf-page-vless/src",
"projectType": "application",
"targets": {
"build": {
@@ -9,7 +9,7 @@
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/apps/cf-page"
"outputPath": "dist/apps/cf-page-vless"
},
"configurations": {
"development": {
@@ -24,15 +24,15 @@
"executor": "@nx/vite:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "cf-page:build"
"buildTarget": "cf-page-vless:build"
},
"configurations": {
"development": {
"buildTarget": "cf-page:build:development",
"buildTarget": "cf-page-vless:build:development",
"hmr": true
},
"production": {
"buildTarget": "cf-page:build:production",
"buildTarget": "cf-page-vless:build:production",
"hmr": false
}
}
@@ -40,7 +40,7 @@
"serve-cf-page": {
"executor": "nx:run-commands",
"options": {
"command": "wrangler pages dev dist/apps/cf-page"
"command": "wrangler pages dev dist/apps/cf-page-vless"
},
"dependsOn": ["^build"]
},
@@ -55,7 +55,7 @@
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/cf-page/**/*.{ts,tsx,js,jsx}"]
"lintFilePatterns": ["apps/cf-page-vless/**/*.{ts,tsx,js,jsx}"]
}
}
},

View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>401 - UUID Not Valid</title>
</head>
<body>
<h1 style="color: red">Not set valid UUID in Environment Variables.</h1>
<h2>
Please use tool to generate and
<span style="color: red">remember</span> UUID or use this one
<span style="color: blue" id="uuidSpan"></span>
</h2>
<h3>
You must use same UUID for login this page after config valid UUID
Environment Variables
</h3>
<h2>
Please refer to
<a
href="https://github.com/zizifn/edgetunnel/blob/main/doc/edge-tunnel-deno.md#%E6%B5%81%E7%A8%8B%E6%BC%94%E7%A4%BA"
>deno deploy guide</a
>
</h2>
<h3>
Or maybe check below
<a
href="https://raw.githubusercontent.com/zizifn/edgetunnel/main/doc/deno-deploy2.gif"
>GIF</a
>
</h3>
<img
src="https://raw.githubusercontent.com/zizifn/edgetunnel/main/doc/deno-deploy2.gif"
alt="guide"
srcset=""
/>
<script>
let uuid = URL.createObjectURL(new Blob([])).substr(-36);
document.getElementById('uuidSpan').textContent = uuid;
</script>
</body>
</html>

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Edge Tunnel VLESS CF</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/styles.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,33 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>401 - UUID Not Valid</title>
</head>
<body>
<h1 style="color: red;">Not set valid UUID in Environment Variables.</h1>
<h2>Please use tool to generate and <span style="color: red;">remember</span> UUID or use this one <span
style="color: blue;" id="uuidSpan"></span>
</h2>
<h3> You must use same UUID for login this page after config valid UUID Environment Variables
</h3>
<h2>Please refer to <a
href="https://github.com/zizifn/edgetunnel/blob/main/doc/edge-tunnel-deno.md#%E6%B5%81%E7%A8%8B%E6%BC%94%E7%A4%BA">deno
deploy guide</a>
</h2>
<h3>Or maybe check below <a
href="https://raw.githubusercontent.com/zizifn/edgetunnel/main/doc/deno-deploy2.gif">GIF</a> </h3>
<img src="https://raw.githubusercontent.com/zizifn/edgetunnel/main/doc/deno-deploy2.gif" alt="guide" srcset="">
<script>
let uuid = URL.createObjectURL(new Blob([])).substr(-36);
document.getElementById('uuidSpan').textContent = uuid
</script>
</body>
</html>

View File

@@ -42,6 +42,6 @@
}
}
},
"implicitDependencies": ["cf-page"],
"implicitDependencies": ["cf-page-vless"],
"tags": []
}

View File

@@ -60,6 +60,6 @@
}
}
},
"implicitDependencies": ["cf-page"],
"implicitDependencies": ["cf-page-vless"],
"tags": []
}

View File

@@ -29,10 +29,10 @@
"vnext": [
{
"address": "127.0.0.1",
"port": 4200,
"port": 8788,
"users": [
{
"id": "e2839021-2313-427f-977c-a1b1dec79ace",
"id": "1a403b79-039b-4dc2-9b45-1ad13197b99a",
"encryption": "none",
"level": 0
}
@@ -41,10 +41,10 @@
]
},
"streamSettings": {
"network": "ws"
// "wsSettings": {
// "path": "/node-vless"
// }
"network": "ws",
"wsSettings": {
"path": "/vless"
}
// "security": "tls"
},
"tag": "zizi-ws"

View File

@@ -57,11 +57,11 @@
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
// {
// "type": "field",
// "ip": ["8.8.8.8"],
// "outboundTag": "zizi-ws"
// },
{
"type": "field",
"ip": ["8.8.8.8"],
"outboundTag": "zizi-ws"
},
{
"type": "field",
"ip": ["geoip:private"],

View File

@@ -1,6 +1,6 @@
import { index401 } from './util';
import { parse, stringify, validate } from 'uuid';
const skipUrls = ['ws', 'assets', 'http2', 'connect'];
const skipUrls = ['ws', 'assets', 'http2', 'connect', 'vless'];
async function errorHandling(context: EventContext<any, any, any>) {
try {

177
functions/vless.ts Normal file
View File

@@ -0,0 +1,177 @@
import {
makeReadableWebSocketStream,
processVlessHeader,
vlessJs,
} from 'vless-js';
import { connect } from 'cloudflare:sockets';
interface Env {
KV: KVNamespace;
UUID: string;
}
export const onRequest: PagesFunction<Env> = async (context) => {
let address = '';
let portWithRandomLog = '';
const userID = context.env['UUID'];
const log = (info: string, event?: any) => {
console.log(`[${address}:${portWithRandomLog}] ${info}`, event || '');
};
const upgradeHeader = context.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 =
context.request.headers.get('sec-websocket-protocol') || '';
let remoteSocket: TransformStream = null;
webSocket.accept();
const readableWebSocketStream = makeReadableWebSocketStream(
webSocket,
earlyDataHeader,
log
);
let vlessResponseHeader = new Uint8Array([0, 0]);
let remoteConnectionReadyResolve: Function;
// ws-->remote
readableWebSocketStream.pipeTo(
new WritableStream({
async write(chunk, controller) {
if (remoteSocket) {
const writer = remoteSocket.writable.getWriter();
await writer.write(chunk);
writer.releaseLock();
return;
}
const {
hasError,
message,
portRemote,
addressRemote,
rawDataIndex,
vlessVersion,
isUDP,
} = processVlessHeader(chunk, userID);
address = addressRemote || '';
portWithRandomLog = `${portRemote}--${Math.random()} ${
isUDP ? 'udp ' : 'tcp '
} `;
// if UDP but port not DNS port, close it
if (isUDP && portRemote != 53) {
controller.error('UDP proxy only enable for DNS which is port 53');
webSocket.close(); // server close will not casuse worker throw error
return;
}
if (hasError) {
controller.error(message);
webSocket.close(); // server close will not casuse worker throw error
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); // first write, nomal is tls client hello
writer.releaseLock();
// remoteSocket ready
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));
// remote--> ws
let count = 0;
remoteSocket.readable
.pipeTo(
new WritableStream({
start() {
if (webSocket.readyState === WebSocket.READY_STATE_OPEN) {
webSocket.send(vlessResponseHeader!);
}
},
async write(chunk: Uint8Array, controller) {
if (webSocket.readyState === WebSocket.READY_STATE_OPEN) {
if (count++ > 20000) {
// cf one package is 4096 byte(4kb), 4096 * 20000 = 80M
await delay(1);
}
webSocket.send(chunk);
// console.log(chunk.byteLength);
} 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
);
safeCloseWebSocket(webSocket);
});
})();
return new Response(null, {
status: 101,
webSocket: client,
});
};
function safeCloseWebSocket(ws: WebSocket) {
try {
if (ws.readyState !== WebSocket.READY_STATE_CLOSED) {
ws.close();
}
} catch (error) {
console.error('safeCloseWebSocket error', error);
}
}
function delay(ms) {
return new Promise((resolve, rej) => {
setTimeout(resolve, ms);
});
}

28
node-ws.mjs Normal file
View File

@@ -0,0 +1,28 @@
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('error', console.error);
ws.on('message', function message(data) {
console.log('received: %s', data);
if (data.toString() === 'close') {
console.log('---------close--------');
ws.close()
}
});
ws.on('close', () => {
console.log('-----------in close-------------close');
console.log(ws.readyState);
ws.send("xxxxxx")
ws.close()
setTimeout(() => {
console.log(ws.readyState);
ws.close()
}, 10000)
})
ws.send('something');
});

View File

@@ -50,5 +50,5 @@
}
}
},
"defaultProject": "cf-page"
"defaultProject": "cf-page-vless"
}

View File

@@ -8,9 +8,9 @@
"scripts": {
"start": "node dist/apps/node-vless/main.js",
"build": "nx build",
"cf-page": "nx build cf-page",
"node-vless:build": "nx build cf-page --configuration=production && nx build node-vless --configurations=production",
"node-vless:bunled": "nx build cf-page --configuration=production && nx build node-vless --skip-nx-cache --configurations=production -- --externalDependencies=none",
"cf-page-vless": "wrangler pages dev dist/apps/cf-page-vless",
"node-vless:build": "nx build cf-page-vless --configuration=production && nx build node-vless --configurations=production",
"node-vless:bunled": "nx build cf-page-vless --configuration=production && nx build node-vless --skip-nx-cache --configurations=production -- --externalDependencies=none",
"node-vless:start": "node dist/apps/node-vless/main.js",
"deno-vless:bunled": "nx deno-bunled deno-vless",
"test": "nx test",

View File

@@ -1,27 +0,0 @@
// Follow this setup guide to integrate the Deno language server with your editor:
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
console.log("Hello from Functions!")
serve(async (req) => {
const socket = await Deno.connect({
port: 443,
hostname: 'google.com',
})
console.log(socket);
return new Response(
JSON.stringify( {
message: `Hello from Functions!`,
}),
{ headers: { "Content-Type": "application/json" } },
)
})
// To invoke:
// curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
// --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
// --header 'Content-Type: application/json' \
// --data '{"name":"Functions"}'