06112602ec
Version the NestJS TDLib gateway with Docker deployment, migrations, automated tests and platform integration documentation while excluding all account sessions, credentials, runtime databases and backups.
124 lines
3.9 KiB
JavaScript
124 lines
3.9 KiB
JavaScript
const net = require('net');
|
|
const http = require('http');
|
|
|
|
const listenPort = Number(process.env.SOCKS5_PORT || 1080);
|
|
const metricsPort = Number(process.env.SOCKS5_METRICS_PORT || 1081);
|
|
const stats = { accepted: 0, connected: 0, failed: 0, active: 0 };
|
|
|
|
function reply(socket, code, addressType = 1) {
|
|
const address = addressType === 4 ? Buffer.alloc(16) : Buffer.alloc(4);
|
|
socket.write(Buffer.concat([
|
|
Buffer.from([5, code, 0, addressType]),
|
|
address,
|
|
Buffer.from([0, 0]),
|
|
]));
|
|
}
|
|
|
|
function consumeRequest(socket, state) {
|
|
if (state.phase === 'greeting') {
|
|
if (state.buffer.length < 2) return;
|
|
const methodsLength = state.buffer[1];
|
|
if (state.buffer.length < 2 + methodsLength) return;
|
|
const methods = state.buffer.subarray(2, 2 + methodsLength);
|
|
state.buffer = state.buffer.subarray(2 + methodsLength);
|
|
if (state.buffer[0] !== undefined && state.buffer[0] !== 5) {
|
|
socket.destroy();
|
|
return;
|
|
}
|
|
if (!methods.includes(0)) {
|
|
socket.end(Buffer.from([5, 255]));
|
|
return;
|
|
}
|
|
socket.write(Buffer.from([5, 0]));
|
|
state.phase = 'request';
|
|
}
|
|
|
|
if (state.phase !== 'request' || state.buffer.length < 4) return;
|
|
const [version, command, , addressType] = state.buffer;
|
|
if (version !== 5 || command !== 1) {
|
|
reply(socket, command === 1 ? 1 : 7);
|
|
socket.end();
|
|
return;
|
|
}
|
|
|
|
let offset = 4;
|
|
let host;
|
|
if (addressType === 1) {
|
|
if (state.buffer.length < offset + 4 + 2) return;
|
|
host = Array.from(state.buffer.subarray(offset, offset + 4)).join('.');
|
|
offset += 4;
|
|
} else if (addressType === 3) {
|
|
if (state.buffer.length < offset + 1) return;
|
|
const length = state.buffer[offset++];
|
|
if (state.buffer.length < offset + length + 2) return;
|
|
host = state.buffer.subarray(offset, offset + length).toString('utf8');
|
|
offset += length;
|
|
} else if (addressType === 4) {
|
|
if (state.buffer.length < offset + 16 + 2) return;
|
|
const words = [];
|
|
for (let index = 0; index < 16; index += 2) {
|
|
words.push(state.buffer.readUInt16BE(offset + index).toString(16));
|
|
}
|
|
host = words.join(':');
|
|
offset += 16;
|
|
} else {
|
|
reply(socket, 8);
|
|
socket.end();
|
|
return;
|
|
}
|
|
|
|
const port = state.buffer.readUInt16BE(offset);
|
|
state.buffer = state.buffer.subarray(offset + 2);
|
|
state.phase = 'connecting';
|
|
|
|
const upstream = net.createConnection({ host, port });
|
|
upstream.setTimeout(30000);
|
|
upstream.once('connect', () => {
|
|
stats.connected += 1;
|
|
stats.active += 1;
|
|
console.log(JSON.stringify({ event: 'connect', host, port, addressType }));
|
|
reply(socket, 0, addressType === 4 ? 4 : 1);
|
|
if (state.buffer.length) upstream.write(state.buffer);
|
|
socket.pipe(upstream);
|
|
upstream.pipe(socket);
|
|
});
|
|
upstream.once('error', (error) => {
|
|
stats.failed += 1;
|
|
console.error(JSON.stringify({ event: 'connect_error', host, port, code: error.code }));
|
|
if (!socket.destroyed) {
|
|
reply(socket, error.code === 'ECONNREFUSED' ? 5 : 4);
|
|
socket.end();
|
|
}
|
|
});
|
|
upstream.once('close', () => {
|
|
if (stats.active > 0) stats.active -= 1;
|
|
});
|
|
socket.once('close', () => upstream.destroy());
|
|
}
|
|
|
|
const server = net.createServer((socket) => {
|
|
stats.accepted += 1;
|
|
const state = { phase: 'greeting', buffer: Buffer.alloc(0) };
|
|
socket.setTimeout(30000);
|
|
socket.on('data', (chunk) => {
|
|
if (state.phase === 'connecting') return;
|
|
state.buffer = Buffer.concat([state.buffer, chunk]);
|
|
consumeRequest(socket, state);
|
|
});
|
|
socket.on('timeout', () => socket.destroy());
|
|
socket.on('error', () => {});
|
|
});
|
|
|
|
server.listen(listenPort, '0.0.0.0', () => {
|
|
console.log(`SOCKS5 test proxy listening on ${listenPort}`);
|
|
});
|
|
|
|
http.createServer((request, response) => {
|
|
if (request.url !== '/stats') {
|
|
response.writeHead(404).end();
|
|
return;
|
|
}
|
|
response.writeHead(200, { 'content-type': 'application/json' });
|
|
response.end(JSON.stringify(stats));
|
|
}).listen(metricsPort, '0.0.0.0');
|