Skip to content

Major update: port mapping, vless outbound, and more #190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
23eb8b7
Abstract worker-vless.js, create launcher for node
Jun 27, 2023
c7ee1a0
Fix indentation
Jun 27, 2023
0a6d83a
Move NodeJS launcher to a separated folder
Jun 27, 2023
d7cf50e
Archieve worker-vless.js and prepare to revert
Jun 27, 2023
30c01a8
Abstract socks5 version
Jun 27, 2023
7b9b464
Better logging
Jun 27, 2023
40100e1
Revert worker-vless.js
Jun 27, 2023
6869e32
Introduce fallback and globalConfig
Jun 28, 2023
3e70643
Add fallback and code clean up
Jun 28, 2023
00f4d39
Better websocket close logic
Jun 28, 2023
eaeb94a
Fix tryOutbound
Jun 28, 2023
6dad634
Add vless header maker
Jun 28, 2023
2e4208f
Impl Vless client without error handling
Jun 28, 2023
c69f300
Add some error handling to vless
Jun 28, 2023
7adaeef
Add Vless string parser
Jun 28, 2023
89a5bc9
Better Vless outbound
Jun 29, 2023
d1c4fca
More comments
Jun 29, 2023
f6eecad
Fix vless on workers
Jun 29, 2023
6a1ae11
Allowing the worker to push log to a remote server
Jun 30, 2023
7637d59
Support UDP tunneling via vless
Jul 1, 2023
29542c5
Add UDP outbound support if run on node
Jul 3, 2023
e54a093
Support IPv6 UDP outbound
Jul 3, 2023
f8434ba
Simply DNS over TCP implementation
Jul 6, 2023
85301ea
Fix indentation
Jul 9, 2023
54734bd
Fix UDP outbound on discontinued streams
Jul 10, 2023
d0ce27a
Fix DNS over TCP
Jul 10, 2023
a88b9fb
Use Uint8Array throughout the code
Jul 16, 2023
2dbb1cf
Fix earlydata
rikkagcp1 Jul 16, 2023
a63c7d3
Remove the use of Blob
Jul 17, 2023
217ddb3
better shadowrocket compatiblity
Jul 25, 2023
ad473ba
Code clean-up
Jul 25, 2023
d171ec9
More clean up
Jul 25, 2023
1c64027
Less verbose log
Jul 25, 2023
a58b6b2
More code clean up
Jul 25, 2023
ead7a93
Massive code clean-up, add type annotations.
Dec 1, 2023
3d332fb
Restore worker-with-socks5-experimental.js
Dec 1, 2023
6988af0
Add websocket message processor
Dec 2, 2023
9527803
Delay the instantiation of response processor
Dec 2, 2023
d69aad5
Add deno support, improve node support
Dec 3, 2023
dd33548
Fix IPv6 UDP on deno
Dec 3, 2023
c2334b2
Fix IPv6 inbound
Dec 6, 2023
41a7e75
Move type definition, better outbound impl
Dec 12, 2023
06334a4
Fix no 0rtt
Dec 12, 2023
43c2330
Fix zero-length earlyData handling
Dec 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
.DS_Store
/node_modules
node_modules
*-lock.*
*.lock
*.log
dist
dist
src/package.json
node/config.js
deno/deno.lock
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// 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": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/node/index.js"
}
]
}
5 changes: 5 additions & 0 deletions deno/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"tasks": {
"start": "deno run --allow-net --unstable main.ts"
}
}
101 changes: 101 additions & 0 deletions deno/denoplatform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
platformAPI,

NodeJSUDP,
NodeJSUDPRemoteInfo,
} from '../src/worker-neo.js'

platformAPI.connect = async (address, port) => {
const tcpSocket = await Deno.connect({
hostname: address,
port: port,
transport: 'tcp',
});

return {
// A ReadableStream Object
readable: tcpSocket.readable,

// Contains functions to write to a TCP stream
writable: tcpSocket.writable,

// Handles socket close
// Deno does not have a onclose callback!
closed: new Promise<void>((resolve, reject) => {})
};
};

platformAPI.newWebSocket = (url) => new WebSocket(url);

// deno-lint-ignore require-await
platformAPI.associate = async (isIPv6) => {
const family = isIPv6 ? 'IPv6' : 'IPv4';

const UDPSocket = Deno.listenDatagram({
transport: 'udp',
port: 0,
hostname: isIPv6 ? '[::]' : '0.0.0.0',
});

let messageHandler: null | ((msg: Uint8Array, rinfo: NodeJSUDPRemoteInfo) => void) = null;
let errorHandler: null | ((err: Error) => void) = null;

function receivingLoop() {
UDPSocket.receive().then(([buffer, from]) => {
// We only support UDP datagram here
const remoteAddress = <Deno.NetAddr> from;

if (messageHandler) {
messageHandler(buffer, {
address: remoteAddress.hostname,
family: family,
port: remoteAddress.port,
size: buffer.byteLength
});
}

// Receive more messages
receivingLoop();
}).catch((err) => {
if (errorHandler) {
errorHandler(err);
}
});
}
receivingLoop();

return {
send: async (datagram, offset, length, port, address, sendDoneCallback) => {
const addr: Deno.Addr = {
transport: 'udp',
hostname: address,
port
};

const buffer = new Uint8Array(datagram, offset, length);

try {
const bytesSent = await UDPSocket.send(buffer, addr);
sendDoneCallback(null, bytesSent);
} catch (err) {
sendDoneCallback(err, 0);
if (errorHandler) {
errorHandler(err);
}
}
},
close: () => {
UDPSocket.close();
},
onmessage: (handler) => {
messageHandler = handler;
},
onerror: (handler) => {
errorHandler = handler;
}
} as NodeJSUDP;
}

export function onDenoStart() {

}
76 changes: 76 additions & 0 deletions deno/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {globalConfig, vlessOverWSHandler, getVLESSConfig} from '../src/worker-neo.js'
import {onDenoStart} from './denoplatform.ts'
onDenoStart();

const portString = Deno.env.get("PORT") || '8000';
Deno.serve({port: Number(portString)}, (req) => {
if (req.headers.get("upgrade") === "websocket") {
const upgradeResult = Deno.upgradeWebSocket(req);
upgradeResult.socket.binaryType = 'arraybuffer';
upgradeResult.socket.onopen = () => {
const earlyData = req.headers.get('sec-websocket-protocol');
vlessOverWSHandler(upgradeResult.socket, earlyData || '');
};
return upgradeResult.response;
}


const reqURL = new URL(req.url);
const hostname = reqURL.hostname;
const path_config = '/' + globalConfig.userID;
const path_qrcode = '/' + globalConfig.userID + '.html';
console.log(reqURL.pathname);
switch (reqURL.pathname) {
case path_config:
return new Response(getVLESSConfig(hostname), {
status: 200,
headers: {
'content-type': 'text/plain;charset=UTF-8',
},
});
case path_qrcode: {
const vlessMain = `vless://${globalConfig.userID}@${hostname}:443?encryption=none&security=tls&sni=${hostname}&fp=randomized&type=ws&host=${hostname}&path=%2F%3Fed%3D2048#${hostname}`
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs/qrcode.js"></script>
</head>
<body>
<div>${vlessMain}</div>
<div id="vless_qr"></div>
<script type="text/javascript">
function createQRCode(elementId, qrText) {
new QRCode(document.getElementById(elementId), {
text: qrText,
correctLevel: QRCode.CorrectLevel.M,
});
}

createQRCode("vless_qr", "${vlessMain}");
</script>
</body>
</html>
`;

return new Response(htmlContent, {
status: 200,
headers: {
'content-type': 'text/html; charset=utf-8',
},
});
}
case '/':
return new Response('Hello from the HTTP server!', {
status: 200,
headers: {
'content-type': 'text/html; charset=utf-8',
},
});
default:
return new Response('Not found! (Code 404)', {
status: 404
});
}
});
63 changes: 63 additions & 0 deletions node/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Run this on NodeJS to test edgeworker without wrangler.
// The only dependency is the ws package from npm
// To run, first run "setup.sh", then "node index.js"

import http from 'http';
import WebSocket from 'ws';

import {globalConfig, setConfigFromEnv, vlessOverWSHandler, getVLESSConfig} from '../src/worker-neo.js';
import {onNodeStart} from './nodeplatform.js';

onNodeStart();

// Create an HTTP server
const server = http.createServer((req, res) => {
switch (req.url) {
case '/':
res.write('Hello from the HTTP server!');
break;
case '/vless_config':
res.write(getVLESSConfig('YOUR-HOSTNAME'));
break;
default:
res.statusCode = 404;
}
res.end();
});

// Create a WebSocket server and attach it to the HTTP server
const wss = new WebSocket.Server({ server });

// Define what should happen when a new WebSocket connection is established
wss.on('connection', (ws, req) => {
vlessOverWSHandler(ws, req.headers['sec-websocket-protocol'] || '');
});

// Start the server on port 8000
server.listen(8000);

function buf2hex(buffer) { // buffer is an ArrayBuffer
return [...new Uint8Array(buffer)]
.map(x => x.toString(16).padStart(2, '0'))
.join(' ');
}

async function loadModule() {
try {
const customConfig = await import('./config.js');

if (customConfig.useCustomOutbound) {
globalConfig.outbounds = customConfig.outbounds;
} else {
setConfigFromEnv(customConfig.env);
if (customConfig.forceProxy) {
globalConfig.outbounds = globalConfig.outbounds.filter(obj => obj.protocol !== "freedom");
}
}
console.log(JSON.stringify(globalConfig.outbounds));
} catch (err) {
console.error('Failed to load the module', err);
}
}

loadModule();
113 changes: 113 additions & 0 deletions node/nodeplatform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {platformAPI} from '../src/worker-neo.js';

import net from 'net';
import WebSocket from 'ws';
import {createSocket as createUDPSocket} from 'dgram';


/**
* Portable function for creating a outbound TCP connections.
* Has to be "async" because some platforms open TCP connection asynchronously.
*
* @param {string} address The remote address to connect to.
* @param {number} port The remote port to connect to.
* @returns {object} The wrapped TCP connection, to be compatible with Cloudflare Workers
*/
platformAPI.connect = async (address, port) => {
const socket = net.createConnection(port, address);

let readableStreamCancel = false;
const readableStream = new ReadableStream({
start(controller) {
socket.on('data', (data) => {
if (readableStreamCancel) {
return;
}
controller.enqueue(data);
});

socket.on('close', () => {
socket.destroy();
if (readableStreamCancel) {
return;
}
controller.close();
});
},

pull(controller) {
// if ws can stop read if stream is full, we can implement backpressure
// https://streams.spec.whatwg.org/#example-rs-push-backpressure
},
cancel(reason) {
// 1. pipe WritableStream has error, this cancel will called, so ws handle server close into here
// 2. if readableStream is cancel, all controller.close/enqueue need skip,
// 3. but from testing controller.error still work even if readableStream is cancel
if (readableStreamCancel) {
return;
}
readableStreamCancel = true;
socket.destroy();
}
});

const onSocketCloses = new Promise((resolve, reject) => {
socket.on('close', (err) => {
if (err) {
reject(socket.errored);
} else {
resolve();
}
});

socket.on('error', (err) => {
reject(err);
});
});

return {
// A ReadableStream Object
readable: readableStream,

// Contains functions to write to a TCP stream
writable: {
getWriter: () => {
return {
write: (data) => {
socket.write(data);
},
releaseLock: () => {
// console.log('Dummy writer.releaseLock()');
}
};
}
},

// Handles socket close
closed: onSocketCloses
};
};

platformAPI.newWebSocket = (url) => new WebSocket(url);

platformAPI.associate = async (isIPv6) => {
const UDPSocket = createUDPSocket(isIPv6 ? 'udp6' : 'udp4');
return {
send: (datagram, offset, length, port, address, sendDoneCallback) => {
UDPSocket.send(datagram, offset, length, port, address, sendDoneCallback);
},
close: () => {
UDPSocket.close();
},
onmessage: (handler) => {
UDPSocket.on('message', handler);
},
onerror: (handler) => {
UDPSocket.on('error', handler);
}
};
}

export function onNodeStart() {

}
Loading