pangoliner
Version:
pangoliner is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name.
255 lines (233 loc) • 9.03 kB
JavaScript
const net = require('net');
const encryptList = ['aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc'];
const crypto = require('crypto');
const { uid } = require('./utils/common')
class ActiveServer {
natPort = 3000;
activeIO;
// the main long connection socket
activeSocketObjs = [];
/* the requests from front client
* {requestKey: 'UC243232345', socket: socket}
*/
requests = {};
constructor(argv) {
console.log('starting...');
this.natPort = argv.port;
if (!this.natPort) {
this.natPort = 27;
}
this.encryptMethod = argv.encrypt;
if (encryptList.includes(this.encryptMethod)) {
this.key = argv.key;
this.iv = argv.iv;
}
this.requests = {};
}
encrypt(data) {
if (this.key) {
const cipher = crypto.createCipheriv(this.encryptMethod, this.key, this.iv);
let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
return data;
}
decrypt(encrypted) {
if (this.key) {
const decipher = crypto.createDecipheriv(this.encryptMethod, this.key, this.iv);
let decrypted = decipher.update(encrypted,'hex','utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
return encrypted;
}
getActiveSocketByKey(key) {
return this.activeSocketObjs.find(item => item.key === key);
}
getLocalListeningSocketByActiveSocketAndKey(persistentConnectionUID, key) {
this.printDataStructure();
const activeSocketObj = this.activeSocketObjs.find(item => item.localListeningSockets === persistentConnectionUID);
console.log(`activeSocketObj: ${activeSocketObj}`);
if (activeSocketObj) {
console.log(`activeSocketObj.localListeningSockets[key]: ${activeSocketObj.localListeningSockets[key]}`);
return activeSocketObj.localListeningSockets[key];
}
}
createactiveSocket(options) {
if (options.port === undefined) {
console.error('please set a local port for activeSocket!');
return;
}
this.httpServer = require('http').Server();
this.activeIO = require('socket.io')(this.httpServer);
try {
// 服务端监听连接状态:io的connection事件表示客户端与服务端成功建立连接,它接收一个回调函数,回调函数会接收一个socket参数。
this.activeIO.on('connection', (socket)=>{
console.log('client connect server, ok!');
const localListeningSockets = []
const activeObj = {activeSocket: socket, localListeningSockets};
socket.emit('server message', 'hello');
socket.on('cli add msg', (data) => {
console.log('recieve cli msg : ' + data );// hi server
});
// 监听断开连接状态:socket的disconnect事件表示客户端与服务端断开连接
socket.on('disconnect', ()=>{
console.log('connect disconnect');
for (let key in localListeningSockets) {
console.log(`关闭: key: ${key}, 端口: ${localListeningSockets[key]._connectionKey}`);
localListeningSockets[key].close();
localListeningSockets[key] = null;
delete localListeningSockets[key];
}
for (let key in this.requests) {
this.requests[key].socket.end();
this.requests[key] = null;
delete this.requests[key];
}
this.activeSocketObjs = this.activeSocketObjs.filter(item => item.activeSocket !== socket);
});
socket.on('persistentConnectionUID', (data) => {
console.log(`event persistentConnectionUID: ${JSON.stringify(data)}`);
activeObj.persistentConnectionUID = this.decrypt(data);
const tmp_activeObj = this.getActiveSocketByKey(activeObj.persistentConnectionUID);
console.log(`tmp_activeObj 是否存在, ${tmp_activeObj}`);
if (tmp_activeObj) {
tmp_activeObj.activeSocket = socket;
} else {
this.activeSocketObjs.push(activeObj);
}
});
socket.on('addBinds', (data) => {
console.log(`event addBinds: ${JSON.stringify(data)}`);
const binds = this.decrypt(data);
for (let key in binds) {
if (!this.getLocalListeningSocketByActiveSocketAndKey(activeObj.persistentConnectionUID, key)) {
this.createLocalListeningSocket(key, {port: binds[key].remote_port}, activeObj);
this.printDataStructure();
}
}
});
socket.on('removeBinds', (data) => {
data = this.decrypt(data);
console.log(`remove binds: ${data}`);
this.handleRemoveBinds(data, localListeningSockets);
});
});
return new Promise(((resolve, reject) => {
this.httpServer.listen(options.port, () => {
resolve();
});
this.httpServer.on('error', e => {
reject(e);
})
}));
} catch(ex) {
console.error(ex);
}
}
createLocalListeningSocket(serverKey, options, activeObj) {
if (options.port === undefined) {
console.error('please set a local port for the local listening socket!');
return;
}
console.log(`listening the port of ${options.port}`);
const that = this;
const activeSocket = activeObj.activeSocket;
// if (activeObj.localListeningSockets[serverKey]) {
// return;
// }
try {
activeObj.localListeningSockets[serverKey] = net.Server(async socket => {
const requestKey = uid('UR');
console.log(`new requestKey: ${requestKey} serverKey: ${serverKey}`);
console.log('ken.length: ', requestKey.length);
this.requests[requestKey] = {serverKey, socket};
const data = this.encrypt({cmd: 'create', requestKey});
activeSocket.emit(serverKey, data);
socket.on('data',data=>{
// console.log(`data from client 1 : ${data.toString()}`);
if (activeSocket) {
activeSocket.emit(serverKey, this.encrypt({cmd: 'data', requestKey, data}));
}
});
socket.on('close', () => {
console.log('连接关闭');
if (activeSocket && this.requests[requestKey]) {
activeSocket.emit(serverKey, this.encrypt({cmd: 'close', requestKey}));
}
this.requests[requestKey] = null;
delete this.requests[requestKey];
socket.end();
socket.destroy();
});
socket.on('error',(err)=>{
socket.end();
this.requests[requestKey] = null;
delete this.requests[requestKey];
console.error(`${new Date().toLocaleString()}: ${err.message}`);
});
});
activeObj.localListeningSockets[serverKey].on('error', args => {
console.error(args);
})
activeObj.localListeningSockets[serverKey].listen(options.port,()=>{
console.log(`NAR serv running at ${options.port}`)
});
activeSocket.on(serverKey, d => {
d = this.decrypt(d);
const { cmd, data, requestKey } = d;
if (!this.requests[requestKey]) {
return;
}
if (cmd === 'close') {
this.requests[requestKey].socket.end();
this.requests[requestKey] = null;
delete this.requests[requestKey];
} else if (cmd === 'data') {
console.log(`来自代理客户端数据: ${Buffer.from(data).toString().substr(0, 100)}`);
console.log(`requestKey: ${requestKey}, requests: ${Object.getOwnPropertyNames(this.requests)}`);
this.requests[requestKey].socket.write(data);
}
})
} catch (err) {
console.error('端口已被占用');
console.error(err);
}
}
/*
* binds: ex:['wall', 'www']
*/
handleRemoveBinds(data, localListeningSockets) {
if (Object.prototype.toString.call(data) === '[object Array]') {
data.forEach(key => {
if (localListeningSockets[key]) {
console.log(key);
localListeningSockets[key].close();
localListeningSockets[key] = null;
delete localListeningSockets[key];
}
});
}
}
printDataStructure() {
console.log('\n-----------------------------------begin-----------------------------------------');
this.activeSocketObjs.forEach(item => {
console.log(`active socket key: ${item.persistentConnectionUID}`);
console.log('----------------begin----------------');
for (const i in item.localListeningSockets) {
console.log(`key: ${i}, socket: ${item.localListeningSockets[i]}`);
}
console.log('----------------end------------------');
})
console.log('----------------------------------end---------------------------------------------\n');
}
start() {
return this.createactiveSocket({ port: this.natPort });
}
close() {
this.activeIO.close();
this.httpServer.close();
}
}
module.exports = { ActiveServer };