* init vless

* change to cdn for uuid

* add TODO

* add support for ipv4 and 6

* add support for ipv4 and 6

* add error handle

* add error handle

* add error handle

* add debug log

* add debug log

* add udp log

* add udp log

* update vless-js

* add doc

* add doc

* add doc

* remove log

* remove log

* remove log

* remove log
This commit is contained in:
zizifn
2022-12-11 22:56:56 +08:00
committed by GitHub
parent 3d4fbd40c9
commit ceefb92803
30 changed files with 573 additions and 79 deletions

View File

@@ -2,6 +2,7 @@
"recommendations": [
"nrwl.angular-console",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
"dbaeumer.vscode-eslint",
"firsttris.vscode-jest-runner"
]
}

21
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"request": "launch",
"name": "Launch Program",
"type": "pwa-node",
"program": "C:\\github\\edgetunnel\\apps\\deno-vless\\src\\main.ts",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "C:\\Users\\zizifn\\scoop\\shims\\deno.EXE",
"runtimeArgs": ["run", "--inspect", "--allow-all", "--unstable"],
"attachSimplePort": 9229,
"env": {
"UUID": ""
}
}
]
}

View File

@@ -1,5 +1,5 @@
{
"deno.codeLens.implementations": true,
"deno.codeLens.references": true,
"deno.enablePaths": ["apps/deno-bypass"]
"deno.enablePaths": ["apps/deno-bypass", "apps/deno-vless"]
}

129
README.md
View File

@@ -1,14 +1,14 @@
# Edge Tunnel 正在开发
# Edge Tunnel Beta
一个无比简单安全,基于 edge 的 tunnel
把 V2ray 部署到 Edge/Serverless Functions 平台上
**v2ray-heroku 由于 heroku 取消免费,项目已经死了。
**v2ray-heroku 由于 heroku 取消免费,项目已经死了。**
> 项目正在开发,基本可用,会有 bug。。
> **请定期按照 github 的提示,只同步到自己的项目。只需要在乎下图红框的提示,其他提示不要点击**。
> ![sync](./doc/sync.jpg)
> 本项目纯属技术性验证,探索最新的 web standard。不给予任何保证。请大家酌情使用。如果有兴趣想一起进行技术探讨可以联系我。💕
> 本项目纯属技术性验证,探索最新的 web standard。不给予任何保证。
## Edge Tunnel server --- Deno deploy
@@ -21,97 +21,78 @@ Edge tunnel 的服务使用了 [Deno deploy](https://deno.com/deploy).
> 这里十分感谢 Deno deploy 严肃对待 web standard 支持 HTTP request & response streaming让 edge tunnel 成为可能。
> 这里没有使用 deno websocket其实技术上可以把 v2ray 移植过来。但是我暂时没有看明白 VLESS 协议内容.
## Edge Tunnel server --- Cloudflare Worker (敬请期待)
这个需要等 Cloudflare 发布下面的技术。
https://blog.cloudflare.com/introducing-socket-workers/
### 如何部署服务
请查看下面教程。
[Deno deploy Install](./doc/edge-tunnel-deno.md)
## Edge Tunnel 客户端
## Edge Tunnel server --- Cloudflare Worker (敬请期待)
> 由于看不懂 VLESS 协议内容, 所以无法使用常用的客户端软件.
这个需要等 Cloudflare 发布下面的技术。
https://blog.cloudflare.com/introducing-socket-workers/
### 安装
## 客户端 v2rayN 配置
请转到本项目 [releases](https://github.com/zizifn/edgetunnel/releases),选择正确的平台,下载最新客户端.
> ⚠️ 由于 edge 平台限制,无法转发 UDP 包。请在配置时候,把 DNS 的策略改成 "Asis", 否则会影响速度。
项目使用https://github.com/vercel/pkg, 所以支持下面平台.
> [ DNS 科普文章](https://tachyondevel.medium.com/%E6%BC%AB%E8%B0%88%E5%90%84%E7%A7%8D%E9%BB%91%E7%A7%91%E6%8A%80%E5%BC%8F-dns-%E6%8A%80%E6%9C%AF%E5%9C%A8%E4%BB%A3%E7%90%86%E7%8E%AF%E5%A2%83%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8-62c50e58cbd0)
1. **platform** alpine, linux, linuxstatic, win, macos, (freebsd)
2. **arch** x64, arm64, (armv6, armv7)
## VLESS websocket 客户端配置
## 启动
解压并且修改安装文件的 `config.json` 文件.
> ⚠️ 由于 edge 平台限制,无法转发 UDP 包。请在配置时候,把 DNS 的策略改成 "Asis", 否则会影响速度。
```json
{
"port": "1081", // 本地 HTTP 代理的端口
"address": "https://****.deno.dev/", // deno deploy URL
"uuid": "****" // 你 deno deploy 设置的用户
}
"outbounds": [
{
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "***.herokuapp.com", // edge app URL 或者 cloudflare worker url/ip
"port": 443,
"users": [
{
"id": "", // 填写你的 UUID
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"serverName": "***.***.com" // edge app host 或者 cloudflare worker host
}
}
}
]
```
然后,在解压目录下,打开命令行, 输入下面命令.
https://github.com/2dust/v2rayN
别人的配置教程参考https://v2raytech.com/v2rayn-config-tutorial/.
![v2rayN](./doc/v2rayn.jpg)
> Edge Tunnel Client 会在本地启动一个 http 代理.
## 建立 cloudflare worker (可选)
> 如果 window 系统提示,是否允许网络权限,请把所有允许。
> ![firewall](./doc/firewall.jpg)
```ps
.\edgetunnel-win-x64.exe run --config .\config.json
```js
const targetHost = 'xxx.xxxx.dev'; //你的 edge function 的hostname
addEventListener('fetch', (event) => {
let url = new URL(event.request.url);
url.hostname = targetHost;
let request = new Request(url, event.request);
event.respondWith(fetch(request));
});
```
![client-log](./doc/client-log.jpg)
# FAQ
> 请不要关闭关闭命令行. 如果关闭,代理会自动退出.
## 不支持 UDP
### 验证代理是否正常
由于 edge 平台限制,无法转发 UDP 包。所以 DNS 策略请设置成 `Asis`.
再次开启一个新的命令行,测试 proxy 是否启动正常。。**注意自己 proxy 的端口**。
## 不支持 VMESS
```bash
curl -v https://www.google.com --proxy http://127.0.0.1:1081
```
如果有返回结果,并且 edge tunnel 命令行有提示,说明一切成功。
```
proxy to www.google.com:443 and remote return 200
```
## 配置代理服务 (重要)
> 不像 v2rayN 可以在自动配置代理,本客户端目前需要手动配置代理才能工作。 下面三种方式可供参考。
### 浏览器 switchyomega 设置
具体安装和配置,请查看官网.
https://proxy-switchyomega.com/proxy/
除了端口port和下图可能不一样。其他都应该是一样的。
> 端口是你在 config.json 自己配置的
![switchyomega2](./doc/switchyomega2.jpg)
### 系统全局 proxy 配置
你也可以配置 proxy 到系统级别。
### 其他软件 proxy 设置
> 电报客户端一直不停的发送 HTTP 请求,所以代理电报可能会快速消耗资源和免费额度。
> 下面以电报为例,其他软件也是一样的。具体方式请用搜索引擎。
路径, setting--> Advance-->Conntction type--> Use Custom proxy
![tel](./doc/tel.jpg)
VMESS 协议过于复杂,并且所有 edge 平台都支持 HTTPS 所以无需 VMESS.

View File

@@ -3,7 +3,7 @@ import { buildRawHttp500, isVaildateReq } from './helper.ts';
const userID = Deno.env.get('UUID');
const handler = async (request: Request): Promise<Response> => {
// console.log('--------start--------');
console.log('--------start--------');
try {
const headers = request.headers;
const serverAddress = headers.get('x-host') || '';
@@ -80,4 +80,4 @@ ${userID ? 'has UUID env' : 'no UUID env'}
}
};
serve(handler, { port: 8080, hostname: '0.0.0.0' });
serve(handler, { port: 8888, hostname: '[::]' });

View File

@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@@ -0,0 +1,16 @@
/* eslint-disable */
export default {
displayName: 'deno-vless',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/deno-vless',
};

View File

@@ -0,0 +1,21 @@
{
"name": "deno-vless",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/deno-vless/src",
"projectType": "application",
"targets": {
"run": {
"executor": "nx:run-commands",
"options": {
"command": "deno run --allow-net --allow-env --allow-write apps/deno-vless/src/main.ts "
}
},
"serve": {
"executor": "nx:run-commands",
"options": {
"command": "deno run --allow-net --allow-write --allow-env --watch apps/deno-vless/src/main.ts "
}
}
},
"tags": []
}

View File

View File

View File

@@ -0,0 +1,3 @@
export const environment = {
production: true,
};

View File

@@ -0,0 +1,3 @@
export const environment = {
production: false,
};

194
apps/deno-vless/src/main.ts Normal file
View File

@@ -0,0 +1,194 @@
import { serve } from 'https://deno.land/std@0.167.0/http/server.ts';
import { parse, stringify, validate } from 'https://jspm.dev/uuid';
import { chunk, join } from 'https://jspm.dev/lodash-es';
const userID = Deno.env.get('UUID');
if (!validate(userID)) {
console.log('not valid userID');
}
const handler = async (req: Request): Promise<Response> => {
const upgrade = req.headers.get('upgrade') || '';
if (upgrade.toLowerCase() != 'websocket') {
return new Response("request isn't trying to upgrade to websocket.");
}
const { socket, response } = Deno.upgradeWebSocket(req);
let remoteConnection: Deno.TcpConn;
let skipHeader = false;
let address = '';
let port = 0;
socket.onopen = () => console.log('socket opened');
socket.onmessage = async (e) => {
try {
if (!(e.data instanceof ArrayBuffer)) {
return;
}
const vlessBuffer: ArrayBuffer = e.data;
if (remoteConnection) {
const number = await remoteConnection.write(
new Uint8Array(vlessBuffer)
);
} else {
//https://github.com/v2ray/v2ray-core/issues/2636
// 1 字节 16 字节 1 字节 M 字节 1 字节 2 字节 1 字节 S 字节 X 字节
// 协议版本 等价 UUID 附加信息长度 M 附加信息 ProtoBuf 指令 端口 地址类型 地址 请求数据
// 1 字节 1 字节 N 字节 Y 字节
// 协议版本,与请求的一致 附加信息长度 N 附加信息 ProtoBuf 响应数据
if (vlessBuffer.byteLength < 24) {
console.log('invalid data');
return;
}
const version = new Uint8Array(vlessBuffer.slice(0, 1));
let isValidUser = false;
if (stringify(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) {
isValidUser = true;
}
if (!isValidUser) {
console.log('in valid user');
return;
}
const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];
//skip opt for now
const command = new Uint8Array(
vlessBuffer.slice(18 + optLength, 18 + optLength + 1)
)[0];
// 0x01 TCP
// 0x02 UDP
// 0x03 MUX
if (command === 1) {
} else {
console.log(
`command ${command} is not support, command 01-tcp,02-udp,03-mux`
);
socket.close();
return;
}
const portIndex = 18 + optLength + 1;
const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);
// port is big-Endian in raw data etc 80 == 0x005d
const portRemote = new DataView(portBuffer).getInt16(0);
port = portRemote;
let addressIndex = portIndex + 2;
const addressBuffer = new Uint8Array(
vlessBuffer.slice(addressIndex, addressIndex + 1)
);
// 1--> ipv4 addressLength =4
// 2--> domain name addressLength=addressBuffer[1]
// 3--> ipv6 addressLength =16
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 addressChunkBy2: number[][] = chunk(
new Uint8Array(
vlessBuffer.slice(
addressValueIndex,
addressValueIndex + addressLength
)
),
2,
null
);
// 2001:0db8:85a3:0000:0000:8a2e:0370:7334
addressValue = addressChunkBy2
.map((items) =>
items.map((item) => item.toString(16).padStart(2, '0')).join('')
)
.join('.');
break;
default:
console.log(`[${address}:${port}] invild address`);
}
address = addressValue;
if (!addressValue) {
console.log(`[${address}:${port}] addressValue is empty`);
socket.close();
return;
}
// const addressType = requestAddr >> 4;
// const addressLength = requestAddr & 0x0f;
console.log(`[${address}:${port}] connecting`);
remoteConnection = await Deno.connect({
port: port,
hostname: addressValue,
});
const rawDataIndex = addressValueIndex + addressLength;
const rawClientData = vlessBuffer.slice(rawDataIndex);
await remoteConnection.write(new Uint8Array(rawClientData));
let chunkDatas = [new Uint8Array([version[0], 0])];
remoteConnection.readable
.pipeTo(
new WritableStream({
start() {
socket.send(new Blob(chunkDatas));
},
write(chunk, controller) {
socket.send(new Blob([chunk]));
// if (!skipHeader) {
// console.log(
// `[${address}:${port}] first time write to client socket`
// );
// chunkDatas.push(chunk);
// socket.send(new Blob(chunkDatas));
// chunkDatas = [];
// } else {
// socket.send(new Blob([chunk]));
// }
},
})
)
.catch((error) => {
console.log(
`[${address}:${port}] remoteConnection pipe to has error`,
error
);
});
}
} catch (error) {
// console.log(
// [...new Uint8Array(vlessBuffer.slice(0, 32))].map((x) =>
// x.toString(16).padStart(2, '0')
// )
// );
console.log(`[${address}:${port}] request hadler has error`, error);
}
};
socket.onerror = (e) =>
console.log(`[${address}:${port}] socket errored:`, e);
socket.onclose = () => console.log(`[${address}:${port}] socket closed`);
return response;
};
serve(handler, { port: 8080, hostname: '0.0.0.0' });

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["node"]
},
"exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"],
"include": ["**/*.ts"]
}

View File

@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
}

BIN
doc/v2rayn.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

11
libs/vless-js/README.md Normal file
View File

@@ -0,0 +1,11 @@
# vless-js
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build vless-js` to build the library.
## Running unit tests
Run `nx test vless-js` to execute the unit tests via [Jest](https://jestjs.io).

View File

@@ -0,0 +1,15 @@
/* eslint-disable */
export default {
displayName: 'vless-js',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/libs/vless-js',
};

View File

@@ -0,0 +1,5 @@
{
"name": "vless-js",
"version": "0.0.1",
"type": "commonjs"
}

View File

@@ -0,0 +1,41 @@
{
"name": "vless-js",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/vless-js/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nrwl/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/vless-js",
"main": "libs/vless-js/src/index.ts",
"tsConfig": "libs/vless-js/tsconfig.lib.json",
"assets": ["libs/vless-js/*.md"]
}
},
"publish": {
"executor": "nx:run-commands",
"options": {
"command": "node tools/scripts/publish.mjs vless-js {args.ver} {args.tag}"
},
"dependsOn": ["build"]
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/vless-js/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/vless-js/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}

View File

@@ -0,0 +1 @@
export * from './lib/vless-js';

View File

@@ -0,0 +1,7 @@
import { vlessJs } from './vless-js';
describe('vlessJs', () => {
it('should work', () => {
expect(vlessJs()).toEqual('vless-js');
});
});

View File

@@ -0,0 +1,3 @@
export function vlessJs(): string {
return 'vless-js';
}

View File

@@ -0,0 +1,22 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["**/*.ts"],
"exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"]
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
}

61
tools/scripts/publish.mjs Normal file
View File

@@ -0,0 +1,61 @@
/**
* This is a minimal script to publish your package to "npm".
* This is meant to be used as-is or customize as you see fit.
*
* This script is executed on "dist/path/to/library" as "cwd" by default.
*
* You might need to authenticate with NPM before running this script.
*/
import { readCachedProjectGraph } from '@nrwl/devkit';
import { execSync } from 'child_process';
import { readFileSync, writeFileSync } from 'fs';
import chalk from 'chalk';
function invariant(condition, message) {
if (!condition) {
console.error(chalk.bold.red(message));
process.exit(1);
}
}
// Executing publish script: node path/to/publish.mjs {name} --version {version} --tag {tag}
// Default "tag" to "next" so we won't publish the "latest" tag by accident.
const [, , name, version, tag = 'next'] = process.argv;
// A simple SemVer validation to validate the version
const validVersion = /^\d+\.\d+\.\d+(-\w+\.\d+)?/;
invariant(
version && validVersion.test(version),
`No version provided or version did not match Semantic Versioning, expected: #.#.#-tag.# or #.#.#, got ${version}.`
);
const graph = readCachedProjectGraph();
const project = graph.nodes[name];
invariant(
project,
`Could not find project "${name}" in the workspace. Is the project.json configured correctly?`
);
const outputPath = project.data?.targets?.build?.options?.outputPath;
invariant(
outputPath,
`Could not find "build.options.outputPath" of project "${name}". Is project.json configured correctly?`
);
process.chdir(outputPath);
// Updating the version in "package.json" before publishing
try {
const json = JSON.parse(readFileSync(`package.json`).toString());
json.version = version;
writeFileSync(`package.json`, JSON.stringify(json, null, 2));
} catch (e) {
console.error(
chalk.bold.red(`Error reading package.json file from library build output.`)
);
}
// Execute "npm publish" to publish
execSync(`npm publish --access public --tag ${tag}`);

View File

@@ -15,7 +15,8 @@
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@edge-bypass/test": ["libs/test/src/index.ts"]
"@edge-bypass/test": ["libs/test/src/index.ts"],
"vless-js": ["libs/vless-js/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]