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

简单实现了 socks5 #4

Merged
merged 1 commit into from
May 13, 2024
Merged
Changes from all commits
Commits
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
Update _worker.src.js
  • Loading branch information
xream committed May 13, 2024
commit 80f4be3db23bc636cf013bc262f513bbaede5916
233 changes: 226 additions & 7 deletions _worker.src.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ if (!isValidSHA224(sha224Password)) {
throw new Error('sha224Password is not valid');
}
*/
// The user name and password do not contain special characters
// Setting the address will ignore proxyIP
// Example: user:pass@host:port or host:port
let socks5Address = '';

let parsedSocks5Address = {};
let enableSocks = false;

export default {
async fetch(request, env, ctx) {
try {
Expand All @@ -50,6 +58,20 @@ export default {
proxyIP = env.PROXYIP || proxyIP;
proxyIPs = await ADD(proxyIP);
proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];
socks5Address = env.SOCKS5 || socks5Address;
if (socks5Address) {
RproxyIP = env.RPROXYIP || 'false';
try {
parsedSocks5Address = socks5AddressParser(socks5Address);
enableSocks = true;
} catch (err) {
/** @type {Error} */ let e = err;
console.log(e.toString());
enableSocks = false;
}
} else {
RproxyIP = env.RPROXYIP || !proxyIP ? 'true' : 'false';
}
password = env.PASSWORD || password;
sha224Password = env.SHA224 || env.SHA224PASS || sha256.sha224(password);
//console.log(sha224Password);
Expand Down Expand Up @@ -146,6 +168,20 @@ export default {
if (new RegExp('/proxyip=', 'i').test(url.pathname)) proxyIP = url.pathname.toLowerCase().split('/proxyip=')[1];
else if (new RegExp('/proxyip.', 'i').test(url.pathname)) proxyIP = `proxyip.${url.pathname.toLowerCase().split("/proxyip.")[1]}`;
else if (!proxyIP || proxyIP == '') proxyIP = 'proxyip.fxxk.dedyn.io';
socks5Address = url.searchParams.get('socks5') || socks5Address;
if (socks5Address) {
RproxyIP = env.RPROXYIP || 'false';
try {
parsedSocks5Address = socks5AddressParser(socks5Address);
enableSocks = true;
} catch (err) {
/** @type {Error} */ let e = err;
console.log(e.toString());
enableSocks = false;
}
} else {
RproxyIP = env.RPROXYIP || !proxyIP ? 'true' : 'false';
}
return await trojanOverWSHandler(request);
}
} catch (err) {
Expand Down Expand Up @@ -186,15 +222,16 @@ async function trojanOverWSHandler(request) {
message,
portRemote = 443,
addressRemote = "",
rawClientData
rawClientData,
addressType
} = await parseTrojanHeader(chunk);
address = addressRemote;
portWithRandomLog = `${portRemote}--${Math.random()} tcp`;
if (hasError) {
throw new Error(message);
return;
}
handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, log);
handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, log, addressType);
},
close() {
log(`readableWebSocketStream is closed`);
Expand Down Expand Up @@ -304,13 +341,15 @@ async function parseTrojanHeader(buffer) {
hasError: false,
addressRemote: address,
portRemote,
rawClientData: socks5DataBuffer.slice(portIndex + 4)
rawClientData: socks5DataBuffer.slice(portIndex + 4),
addressType: atype
};
}

async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, log) {
async function connectAndWrite(address, port) {
const tcpSocket2 = connect({
async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, log, addressType) {
async function connectAndWrite(address, port, socks = false) {
const tcpSocket2 = socks ? await socks5Connect(addressType, address, port, log)
: connect({
hostname: address,
port
});
Expand All @@ -322,7 +361,12 @@ async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawCli
return tcpSocket2;
}
async function retry() {
const tcpSocket2 = await connectAndWrite(proxyIP || addressRemote, portRemote);
let tcpSocket2
if (enableSocks) {
tcpSocket2 = await connectAndWrite(addressRemote, portRemote, true);
} else {
tcpSocket2 = await connectAndWrite(proxyIP || addressRemote, portRemote);
}
tcpSocket2.closed.catch((error) => {
console.log("retry tcpSocket closed error", error);
}).finally(() => {
Expand Down Expand Up @@ -1477,3 +1521,178 @@ async function getSum(accountId, accountIndex, email, key, startDate, endDate) {
return [ 0, 0 ];
}
}

/**
*
* @param {number} addressType
* @param {string} addressRemote
* @param {number} portRemote
* @param {function} log The logging function.
*/
async function socks5Connect(addressType, addressRemote, portRemote, log) {
const { username, password, hostname, port } = parsedSocks5Address;
// Connect to the SOCKS server
const socket = connect({
hostname,
port,
});

// Request head format (Worker -> Socks Server):
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+

// https://en.wikipedia.org/wiki/SOCKS#SOCKS5
// For METHODS:
// 0x00 NO AUTHENTICATION REQUIRED
// 0x02 USERNAME/PASSWORD https://datatracker.ietf.org/doc/html/rfc1929
const socksGreeting = new Uint8Array([5, 2, 0, 2]);

const writer = socket.writable.getWriter();

await writer.write(socksGreeting);
log('sent socks greeting');

const reader = socket.readable.getReader();
const encoder = new TextEncoder();
let res = (await reader.read()).value;
// Response format (Socks Server -> Worker):
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
if (res[0] !== 0x05) {
log(`socks server version error: ${res[0]} expected: 5`);
return;
}
if (res[1] === 0xff) {
log("no acceptable methods");
return;
}

// if return 0x0502
if (res[1] === 0x02) {
log("socks server needs auth");
if (!username || !password) {
log("please provide username/password");
return;
}
// +----+------+----------+------+----------+
// |VER | ULEN | UNAME | PLEN | PASSWD |
// +----+------+----------+------+----------+
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
// +----+------+----------+------+----------+
const authRequest = new Uint8Array([
1,
username.length,
...encoder.encode(username),
password.length,
...encoder.encode(password)
]);
await writer.write(authRequest);
res = (await reader.read()).value;
// expected 0x0100
if (res[0] !== 0x01 || res[1] !== 0x00) {
log("fail to auth socks server");
return;
}
}

// Request data format (Worker -> Socks Server):
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// ATYP: address type of following address
// 0x01: IPv4 address
// 0x03: Domain name
// 0x04: IPv6 address
// DST.ADDR: desired destination address
// DST.PORT: desired destination port in network octet order

// addressType
// 0x01: IPv4 address
// 0x03: Domain name
// 0x04: IPv6 address
// 1--> ipv4 addressLength =4
// 2--> domain name
// 3--> ipv6 addressLength =16
let DSTADDR; // DSTADDR = ATYP + DST.ADDR
switch (addressType) {
case 1:
DSTADDR = new Uint8Array(
[1, ...addressRemote.split('.').map(Number)]
);
break;
case 3:
DSTADDR = new Uint8Array(
[3, addressRemote.length, ...encoder.encode(addressRemote)]
);
break;
case 4:
DSTADDR = new Uint8Array(
[4, ...addressRemote.split(':').flatMap(x => [parseInt(x.slice(0, 2), 16), parseInt(x.slice(2), 16)])]
);
break;
default:
log(`invild addressType is ${addressType}`);
return;
}
const socksRequest = new Uint8Array([5, 1, 0, ...DSTADDR, portRemote >> 8, portRemote & 0xff]);
await writer.write(socksRequest);
log('sent socks request');

res = (await reader.read()).value;
// Response format (Socks Server -> Worker):
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
if (res[1] === 0x00) {
log("socks connection opened");
} else {
log("fail to open socks connection");
return;
}
writer.releaseLock();
reader.releaseLock();
return socket;
}


/**
*
* @param {string} address
*/
function socks5AddressParser(address) {
let [latter, former] = address.split("@").reverse();
let username, password, hostname, port;
if (former) {
const formers = former.split(":");
if (formers.length !== 2) {
throw new Error('Invalid SOCKS address format');
}
[username, password] = formers;
}
const latters = latter.split(":");
port = Number(latters.pop());
if (isNaN(port)) {
throw new Error('Invalid SOCKS address format');
}
hostname = latters.join(":");
const regex = /^\[.*\]$/;
if (hostname.includes(":") && !regex.test(hostname)) {
throw new Error('Invalid SOCKS address format');
}
return {
username,
password,
hostname,
port,
}
}