diff --git a/client-config/config-client-with-dns-lcoal.json b/client-config/config-client-with-dns-lcoal copy.json similarity index 100% rename from client-config/config-client-with-dns-lcoal.json rename to client-config/config-client-with-dns-lcoal copy.json diff --git a/client-config/config-client-without-dns-lcoal.json b/client-config/config-client-without-dns-lcoal.json new file mode 100644 index 0000000..b866277 --- /dev/null +++ b/client-config/config-client-without-dns-lcoal.json @@ -0,0 +1,72 @@ +{ + "log": { + "loglevel": "debug" + }, + "inbounds": [ + { + "listen": "0.0.0.0", + "port": "4080", + "protocol": "socks", + "settings": { + "auth": "noauth", + "udp": true, + "ip": "0.0.0.0" + } + }, + { + "listen": "0.0.0.0", + "port": "4081", + "protocol": "http" + } + ], + "dns": { + "servers": ["8.8.8.8"] + }, + "outbounds": [ + { + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "127.0.0.1", + "port": 8787, + "users": [ + { + "id": "1a403b79-039b-4dc2-9b45-1ad13197b99a", + "encryption": "none", + "level": 0 + } + ] + } + ] + }, + "streamSettings": { + "network": "ws" + // "wsSettings": { + // "path": "/node-vless" + // } + // "security": "tls" + }, + "tag": "zizi-ws" + }, + { + "protocol": "freedom", + "tag": "direct" + } + ], + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + // { + // "type": "field", + // "ip": ["8.8.8.8"], + // "outboundTag": "zizi-ws" + // }, + { + "type": "field", + "ip": ["geoip:private"], + "outboundTag": "zizi-ws" + } + ] + } +} diff --git a/client-config/config-client.json b/client-config/config-client.json new file mode 100644 index 0000000..9dbf231 --- /dev/null +++ b/client-config/config-client.json @@ -0,0 +1,74 @@ +{ + "log": { + "loglevel": "debug" + }, + "inbounds": [ + { + "listen": "0.0.0.0", + "port": "4080", + "protocol": "socks", + "settings": { + "auth": "noauth", + "udp": true, + "ip": "0.0.0.0" + } + }, + { + "listen": "0.0.0.0", + "port": "4081", + "protocol": "http" + } + ], + "dns": { + "servers": ["8.8.8.8"] + }, + "outbounds": [ + { + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "192.203.230.111", + "port": 80, + "users": [ + { + "id": "1a403b79-039b-4dc2-9b45-1ad13197b99a", + "encryption": "none", + "level": 0 + } + ] + } + ] + }, + "streamSettings": { + "network": "ws", + "wsSettings": { + "headers": { + "Host": "vless-dev.121107.xyz" + } + } + // "security": "tls" + }, + "tag": "zizi-ws" + }, + { + "protocol": "freedom", + "tag": "direct" + } + ], + "routing": { + "domainStrategy": "AsIs", + "rules": [ + // { + // "type": "field", + // "ip": ["8.8.8.8"], + // "outboundTag": "zizi-ws" + // }, + { + "type": "field", + "ip": ["geoip:private"], + "outboundTag": "zizi-ws" + } + ] + } +} diff --git a/functions/connect.ts b/functions/connect.ts index e1a63a4..6f996bf 100644 --- a/functions/connect.ts +++ b/functions/connect.ts @@ -2,7 +2,7 @@ // @ts-ignore import { connect } from 'cloudflare:sockets'; -export const onRequest: PagesFunction = async (context) => { +export const onRequest: PagesFunction = async (context) => { console.log('start fetch'); const socket = connect({ hostname: 'neverssl.com', diff --git a/libs/cf-worker-vless/.eslintrc.json b/libs/cf-worker-vless/.eslintrc.json new file mode 100644 index 0000000..9d9c0db --- /dev/null +++ b/libs/cf-worker-vless/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/cf-worker-vless/README.md b/libs/cf-worker-vless/README.md new file mode 100644 index 0000000..da94231 --- /dev/null +++ b/libs/cf-worker-vless/README.md @@ -0,0 +1,11 @@ +# cf-worker-vless + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build cf-worker-vless` to build the library. + +## Running unit tests + +Run `nx test cf-worker-vless` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/cf-worker-vless/jest.config.ts b/libs/cf-worker-vless/jest.config.ts new file mode 100644 index 0000000..58c788e --- /dev/null +++ b/libs/cf-worker-vless/jest.config.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ +export default { + displayName: 'cf-worker-vless', + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/libs/cf-worker-vless', +}; diff --git a/libs/cf-worker-vless/package.json b/libs/cf-worker-vless/package.json new file mode 100644 index 0000000..ea16bd4 --- /dev/null +++ b/libs/cf-worker-vless/package.json @@ -0,0 +1,5 @@ +{ + "name": "@edge-bypass/cf-worker-vless", + "version": "0.0.1", + "type": "commonjs" +} diff --git a/libs/cf-worker-vless/project.json b/libs/cf-worker-vless/project.json new file mode 100644 index 0000000..654d34c --- /dev/null +++ b/libs/cf-worker-vless/project.json @@ -0,0 +1,40 @@ +{ + "name": "cf-worker-vless", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/cf-worker-vless/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/cf-worker-vless", + "main": "libs/cf-worker-vless/src/index.ts", + "tsConfig": "libs/cf-worker-vless/tsconfig.lib.json", + "assets": ["libs/cf-worker-vless/*.md"] + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/cf-worker-vless/**/*.ts"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/cf-worker-vless/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + }, + "tags": [] +} diff --git a/libs/cf-worker-vless/src/index.ts b/libs/cf-worker-vless/src/index.ts new file mode 100644 index 0000000..1c68327 --- /dev/null +++ b/libs/cf-worker-vless/src/index.ts @@ -0,0 +1,140 @@ +import { + makeReadableWebSocketStream, + processVlessHeader, + vlessJs, +} from 'vless-js'; +import { connect } from 'cloudflare:sockets'; +import { Buffer } from 'node:buffer'; +import { validate } from 'uuid'; + +interface Env { + UUID: string; +} + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + let address = ''; + let portWithRandomLog = ''; + const userID = env.UUID; + + const log = (info: string, event?: any) => { + 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); + + console.log(WebSocket.READY_STATE_OPEN); + + const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; + let remoteSocket: TransformStream = null; + webSocket.accept(); + webSocket.addEventListener('message', async (event) => { + if (remoteSocket) { + const writer = remoteSocket.writable.getWriter(); + await writer.write(event.data); + writer.releaseLock(); + return; + } + const vlessBuffer: ArrayBuffer = event.data as ArrayBuffer; + const { + hasError, + message, + portRemote, + addressRemote, + rawDataIndex, + vlessVersion, + isUDP, + } = processVlessHeader(vlessBuffer, userID); + address = addressRemote || ''; + portWithRandomLog = `${portRemote}--${Math.random()} ${ + isUDP ? 'udp ' : 'tcp ' + } `; + log(`connecting`); + if (hasError) { + webSocket.close(); // server close will not casuse worker throw error + } + const vlessResponseHeader = new Uint8Array([vlessVersion![0], 0]); + const rawClientData = vlessBuffer.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.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) { + 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 + ); + safeCloseWebSocket(webSocket); + }); + + // end + }); + + webSocket.addEventListener('close', async (event) => { + console.log('-------------close-----------------', event); + }); + + webSocket.addEventListener('error', () => { + console.log('-------------error-----------------'); + }); + + return new Response(null, { + status: 101, + webSocket: client, + }); + }, +}; + +function safeCloseWebSocket(ws: WebSocket) { + try { + if (ws.readyState === WebSocket.READY_STATE_OPEN) { + ws.close(); + } + } catch (error) { + console.error('safeCloseWebSocket error', error); + } +} diff --git a/libs/cf-worker-vless/src/index2.ts b/libs/cf-worker-vless/src/index2.ts new file mode 100644 index 0000000..2ec7f36 --- /dev/null +++ b/libs/cf-worker-vless/src/index2.ts @@ -0,0 +1,36 @@ +import { connect } from 'cloudflare:sockets'; + +interface Env { + UUID: string; +} + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + console.log('start fetch'); + const socket = connect({ + hostname: 'neverssl.com', + port: 80, + }); + + const writer = socket.writable.getWriter(); + const encoder = new TextEncoder(); + const encoded = encoder.encode( + 'GET / HTTP/1.1\r\nHost: neverssl.com\r\n\r\n' + ); + await writer.write(encoded); + + const reader = socket.readable.getReader(); + const decoder = new TextDecoder(); + let response = ''; + while (true) { + const res = await reader.read(); + if (res.done) { + console.log('Stream done, socket connection has been closed.'); + break; + } + response += decoder.decode(res.value); + } + + return new Response(response); + }, +}; diff --git a/libs/cf-worker-vless/tsconfig.json b/libs/cf-worker-vless/tsconfig.json new file mode 100644 index 0000000..07f5e7a --- /dev/null +++ b/libs/cf-worker-vless/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "lib": ["esnext"], + "types": ["@cloudflare/workers-types"], + "paths": { + "vless-js": ["../vless-js/src/index.ts"] + } + } +} diff --git a/libs/cf-worker-vless/tsconfig.lib.json b/libs/cf-worker-vless/tsconfig.lib.json new file mode 100644 index 0000000..33eca2c --- /dev/null +++ b/libs/cf-worker-vless/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/cf-worker-vless/tsconfig.spec.json b/libs/cf-worker-vless/tsconfig.spec.json new file mode 100644 index 0000000..9b2a121 --- /dev/null +++ b/libs/cf-worker-vless/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/libs/vless-js/src/lib/vless-js.ts b/libs/vless-js/src/lib/vless-js.ts index f7a06ad..a8bc8d0 100644 --- a/libs/vless-js/src/lib/vless-js.ts +++ b/libs/vless-js/src/lib/vless-js.ts @@ -3,12 +3,21 @@ export function vlessJs(): string { return 'vless-js'; } +const WS_READY_STATE_OPEN = 1; + export function delay(ms: number) { return new Promise((resolve, rej) => { setTimeout(resolve, ms); }); } +/** + * we need make sure read websocket message in order + * @param ws + * @param earlyDataHeader + * @param log + * @returns + */ export function makeReadableWebSocketStream( ws: WebSocket | any, earlyDataHeader: string, @@ -90,7 +99,7 @@ function base64ToArrayBuffer(base64Str: string) { export function safeCloseWebSocket(socket: WebSocket | any) { try { - if (socket.readyState === socket.OPEN) { + if (socket.readyState === WS_READY_STATE_OPEN) { socket.close(); } } catch (error) { diff --git a/package.json b/package.json index 5dffee2..460ebd8 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "node-vless:bunled": "nx build cf-page --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" + "test": "nx test", + "cf-worker": "wrangler dev ./libs/cf-worker-vless/src/index.ts --experimental-local" }, "private": true, "dependencies": { diff --git a/tsconfig.base.json b/tsconfig.base.json index 8e7f855..3a66eaf 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -15,6 +15,7 @@ "skipDefaultLibCheck": true, "baseUrl": ".", "paths": { + "@edge-bypass/cf-worker-vless": ["libs/cf-worker-vless/src/index.ts"], "edge-ui": ["libs/edge-ui/src/index.ts"], "vless-js": ["libs/vless-js/src/index.ts"] }