Skip to content
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

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

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Add vless header maker
  • Loading branch information
rikkagcp1 committed Jun 28, 2023
commit 6dad63479ea09ec70bd9212fe788d3a1285d8f9a
9 changes: 4 additions & 5 deletions node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ function buf2hex(buffer) { // buffer is an ArrayBuffer
*
* @param {string} address The remote address to connect to.
* @param {number} port The remote port to connect to.
* @param {function} log A destination-dependent logging function
* @param {boolean} useTLS
* @returns {object} The wrapped TCP connection, to be compatible with Cloudflare Workers
*/
platformAPI.connect = async (address, port, log) => {
platformAPI.connect = async (address, port, useTLS) => {
const socket = net.createConnection(port, address);

let readableStreamCancel = false;
Expand Down Expand Up @@ -82,7 +82,6 @@ platformAPI.connect = async (address, port, log) => {
if (readableStreamCancel) {
return;
}
log(`ReadableStream was canceled, due to ${reason}`)
readableStreamCancel = true;
socket.destroy();
}
Expand Down Expand Up @@ -114,7 +113,7 @@ platformAPI.connect = async (address, port, log) => {
socket.write(data);
},
releaseLock: () => {
// log('Dummy writer.releaseLock()');
// console.log('Dummy writer.releaseLock()');
}
};
}
Expand All @@ -128,4 +127,4 @@ platformAPI.connect = async (address, port, log) => {
platformAPI.newWebSocket = (url) => new WebSocket(url);

import {customConfig} from './config.js';
globalConfig.outbounds = customConfig.outbounds;
globalConfig.outbounds = customConfig.outbounds;
157 changes: 151 additions & 6 deletions src/worker-with-socks5-experimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ function getOutbound(curPos) {
retVal.port = curServer.port;

retVal.pass = curServer.users.at(0).id;
retVal.streamSettings = outbound.streamSettings;
break;

default:
Expand Down Expand Up @@ -217,8 +218,8 @@ export default {

try {
const module = await import('cloudflare:sockets');
platformAPI.connect = async (address, port, log) => {
return module.connect({hostname: address, port: port});
platformAPI.connect = async (address, port, useTLS) => {
return module.connect({hostname: address, port: port}, {secureTransport: useTLS ? 'on' : 'off'});
};

platformAPI.newWebSocket = (url) => new WebSocket(url);
Expand Down Expand Up @@ -323,7 +324,7 @@ async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portR
let curOutBoundPtr = {index: 0, serverIndex: 0};

async function direct() {
const tcpSocket = await platformAPI.connect(addressRemote, portRemote, log);
const tcpSocket = await platformAPI.connect(addressRemote, portRemote, false);
tcpSocket.closed.catch(error => log('[freedom] tcpSocket closed with error: ', error.message));
remoteSocket.value = tcpSocket;
log(`Connecting to ${addressRemote}:${portRemote}`);
Expand All @@ -339,7 +340,7 @@ async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portR
portDest = portMap[portRemote];
}

const tcpSocket = await platformAPI.connect(proxyServer, portDest, log);
const tcpSocket = await platformAPI.connect(proxyServer, portDest, false);
tcpSocket.closed.catch(error => log('[forward] tcpSocket closed with error: ', error.message));
remoteSocket.value = tcpSocket;
log(`Forwarding ${addressRemote}:${portRemote} to ${proxyServer}:${portDest}`);
Expand All @@ -350,7 +351,7 @@ async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portR
}

async function socks5(address, port, user, pass) {
const tcpSocket = await platformAPI.connect(address, port, log);
const tcpSocket = await platformAPI.connect(address, port, false);
tcpSocket.closed.catch(error => log('[socks] tcpSocket closed with error: ', error.message));
log(`Connecting to ${addressRemote}:${portRemote} via socks5 ${address}:${port}`);
try {
Expand All @@ -365,6 +366,42 @@ async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portR
writer.releaseLock();
return tcpSocket.readable;
}

/**
*
* @param {string} address
* @param {number} port
* @param {string} uuid
* @param {{network: string, security: string}} streamSettings
*/
async function vless(address, port, uuid, streamSettings) {
try {
checkVlessConfig(address, streamSettings);
} catch(err) {
log(`Vless outbound failed with: ${err.message}`);
return null;
}

let wsURL = streamSettings.security === 'tls' ? 'wss://' : 'ws://';
wsURL = wsURL + address + ':' + port;
if (streamSettings.wsSettings && streamSettings.wsSettings.path) {
wsURL = wsURL + '/' + streamSettings.wsSettings.path;
}

const webSocket = platformAPI.newWebSocket(wsURL);
const openPromise = new Promise((resolve, reject) => {
webSocket.onopen = () => resolve();
webSocket.onerror = (error) => reject(error);
});

// Wait for the connection to open
await openPromise;

const vlessFirstPacket = makeVlessHeader(addressType, addressRemote, portRemote, uuid, rawClientData);
const readableStream = makeReadableWebSocketStream(webSocket, null, log);
log(`Connected to ${addressRemote}:${portRemote} via vless-ws ${address}:${port}`);
return makeReadableWebSocketStream(webSocket, null, log);
}

/** @returns {Promise<ReadableStream>} */
async function connectAndWrite() {
Expand All @@ -381,6 +418,8 @@ async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portR
return await forward(outbound.address, outbound.portMap);
case 'socks':
return await socks5(outbound.address, outbound.port, outbound.user, outbound.pass);
case 'vless':
return await vless(outbound.address, outbound.port, outbound.pass, outbound.streamSettings);
}

return null;
Expand Down Expand Up @@ -740,7 +779,7 @@ async function handleDNSQuery(udpChunk, webSocket, vlessResponseHeader, log) {
/** @type {ArrayBuffer | null} */
let vlessHeader = vlessResponseHeader;
/** @type {import("@cloudflare/workers-types").Socket} */
const tcpSocket = await platformAPI.connect(dnsServer, dnsPort);
const tcpSocket = await platformAPI.connect(dnsServer, dnsPort, false);

log(`connected to ${dnsServer}:${dnsPort}`);
const writer = tcpSocket.writable.getWriter();
Expand Down Expand Up @@ -933,6 +972,112 @@ function socks5AddressParser(address) {
}
}

/**
* @param {number} destType
* @param {string} destAddr
* @param {number} destPort
* @param {string} uuid
* @param {ArrayBuffer | Uint8Array} rawClientData
* @returns {Uint8Array}
*/
async function makeVlessHeader(destType, destAddr, destPort, uuid, rawClientData) {
/** @type {number} */
let addressFieldLength;
/** @type {Uint8Array | undefined} */
let addressEncoded;
switch (destType) {
case 1:
addressFieldLength = 4;
break;
case 2:
addressEncoded = new TextEncoder().encode(destAddr);
addressFieldLength = addressEncoded.length + 1;
break;
case 3:
addressFieldLength = 16;
break;
default:
throw new Error(`Unknown address type: ${destType}`);
}

const uuidString = uuid.replace(/-/g, '');
const uuidOffset = 1;
const vlessHeader = new Uint8Array(22 + addressFieldLength + rawClientData.length);

// Protocol Version = 0
vlessHeader[0] = 0x00;

for (let i = 0; i < uuidString.length; i += 2) {
vlessHeader[uuidOffset + i / 2] = parseInt(uuidString.substr(i, 2), 16);
}

// Additional Information Length M = 0
vlessHeader[17] = 0x00;

// Instruction
// 0x01 TCP
// 0x02 UDP
// 0x03 MUX
vlessHeader[18] = 0x01; // Assume TCP

// Port, 2-byte big-endian
vlessHeader[19] = destPort >> 8;
vlessHeader[20] = destPort & 0xFF ;

// Address Type
// 1--> ipv4 addressLength =4
// 2--> domain name addressLength=addressBuffer[1]
// 3--> ipv6 addressLength =16
vlessHeader[21] = destType;

// Address
switch (destType) {
case 1:
const octetsIPv4 = destAddr.split('.');
for (let i = 0; i < 4; i++) {
vlessHeader[22 + i] = parseInt(octetsIPv4[i]);
}
break;
case 2:
vlessHeader[22] = addressEncoded.length;
vlessHeader.set(addressEncoded, 23);
break;
case 3:
const groupsIPv6 = ipv6.split(':');
for (let i = 0; i < 8; i++) {
const hexGroup = parseInt(groupsIPv6[i], 16);
vlessHeader[i * 2 + 22] = hexGroup >> 8;
vlessHeader[i * 2 + 23] = hexGroup & 0xFF;
}
break;
default:
throw new Error(`Unknown address type: ${destType}`);
}

// Payload
vlessHeader.set(addressEncoded, 23 + addressFieldLength);

return vlessHeader;
}

function checkVlessConfig(address, streamSettings) {
if (streamSettings.network !== 'ws') {
throw new Error(`Unsupported outbound stream method: ${streamSettings.network}, has to be ws (Websocket)`);
}

if (streamSettings.security !== 'tls' && streamSettings.security !== 'none') {
throw new Error(`Usupported security layer: ${streamSettings.network}, has to be none or tls.`);
}

if (streamSettings.wsSettings && streamSettings.wsSettings.headers && streamSettings.wsSettings.headers.Host !== address) {
throw new Error(`The Host field in the http header is different from the server address, this is unsupported due to Cloudflare API restrictions`);
}

if (streamSettings.tlsSettings && streamSettings.tlsSettings.serverName !== address) {
throw new Error(`The SNI is different from the server address, this is unsupported due to Cloudflare API restrictions`);
}
}

/**
*
* @param {string | null} hostName
Expand Down