UNPKG

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
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 };