From 0fa223c21627205a809668bda3b8a607bda4eeb6 Mon Sep 17 00:00:00 2001 From: Leonewu <379747139@qq.com> Date: Tue, 10 Jan 2023 22:17:26 +0800 Subject: [PATCH] support id for proxy --- README.md | 55 +++++++++++++++++++++++++++++++++---------- __test__/index.ts | 18 +++++++++++++- package.json | 2 +- src/index.ts | 60 +++++++++++++++++++++++++++++++++++------------ 4 files changed, 105 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 0685fdc..ca319a3 100644 --- a/README.md +++ b/README.md @@ -68,9 +68,16 @@ const sshConfig = { privateKey: fs.readFileSync('~/.ssh/myPrivateKey'), }; const client = new SshTunnel(sshConfig); -const forwardInfo = client.forwardOut('3000:192.168.1.1:3000'); -console.log(forwardInfo); -// { localPort: 3000, destPort: 3000, destHost: '192.168.1.1' key: '3000:192.168.1.1:3000' } +const forwardInfo1 = client.forwardOut('3000:192.168.1.1:3000'); +console.log(forwardInfo1); +// { localPort: 3000, destPort: 3000, destHost: '192.168.1.1', id: '3000:192.168.1.1:3000', type: 'out' } +// or passing an id, it'll use that id. +const forwardInfo2 = await client.forwardOut({ + id: 'my-id', + proxy: '3001:192.168.1.1:3000' +}); +console.log(forwardInfo2); +// { localPort: 3000, destPort: 3000, destHost: '192.168.1.1', id: 'my-id', type: 'out' } ``` If the local port is occupied, it will choose a idle local port to listen and return the info in result. @@ -89,10 +96,10 @@ const forwardInfo1 = client.forwardOut('3000:192.168.1.1:3000'); const forwardInfo2 = client.forwardOut('3000:192.168.1.1:3000'); console.log(forwardInfo1); // port 3000 is idle -// { localPort: 3000, destPort: 3000, destHost: '192.168.1.1' key: '3000:192.168.1.1:3000', type: 'out' } +// { localPort: 3000, destPort: 3000, destHost: '192.168.1.1', id: '3000:192.168.1.1:3000', type: 'out' } console.log(forwardInfo2); // port 3000 is using, so it use another idle port 3001 -// { localPort: 3001, destPort: 3000, destHost: '192.168.1.1' key: '3000:192.168.1.1:3000', type: 'out' } +// { localPort: 3001, destPort: 3000, destHost: '192.168.1.1', id: '3000:192.168.1.1:3000', type: 'out' } ``` @@ -108,12 +115,28 @@ const sshConfig = { privateKey: fs.readFileSync('~/.ssh/myPrivateKey'), }; const client = new SshTunnel(sshConfig); -const forwardInfo = client.forwardOut(['3000:192.168.1.1:3000', '3001:192.168.1.1:3001']); -console.log(forwardInfo); -// output +const forwardInfo1 = client.forwardOut(['3000:192.168.1.1:3000', '3001:192.168.1.1:3001']); +console.log(forwardInfo1); +// [ +// { localPort: 3000, destPort: 3000, destHost: '192.168.1.1', id: '3000:192.168.1.1:3000', type: 'out' }, +// { localPort: 3001, destPort: 3001, destHost: '192.168.1.1', id: '3001:192.168.1.1:3001', type: 'out' }, +// ] + +// or passing an id and it'll use that id +const forwardInfo2 = client.forwardOut([ + { + id: 'my-id-1', + proxy: '3000:192.168.1.1:3000' + }, + { + id: 'my-id-2', + proxy: '3001:192.168.1.1:3000' + } +]); +console.log(forwardInfo2); // [ -// { localPort: 3000, destPort: 3000, destHost: '192.168.1.1' key: '3000:192.168.1.1:3000', type: 'out' }, -// { localPort: 3001, destPort: 3001, destHost: '192.168.1.1' key: '3001:192.168.1.1:3001', type: 'out' }, +// { localPort: 3000, destPort: 3000, destHost: '192.168.1.1', id: 'my-id-1', type: 'out' }, +// { localPort: 3001, destPort: 3001, destHost: '192.168.1.1', id: 'my-id-2', type: 'out' }, // ] ``` @@ -164,7 +187,7 @@ const client = new SshTunnel(sshConfig); const echo = await client.exec('echo 1'); const forwardInfo = client.forwardOut(['3000:192.168.1.1:3000', '3001:192.168.1.1:3001']); // close one proxy server -client.close(forwardInfo[0].key); +client.close(forwardInfo[0].id); // close all proxy server client.close(); ``` @@ -187,7 +210,7 @@ const sshConfig = { }; const client = new SshTunnel(sshConfig); const forwardInfo = await client.forwardOut('3000:192.168.1.1:3000'); -// { localPort: 3000, destHost: '192.168.1.1', destPort: 3000, key: '3000:192.168.1.1:3000', type: 'out' } +// { localPort: 3000, destHost: '192.168.1.1', destPort: 3000, id: '3000:192.168.1.1:3000', type: 'out' } ``` ### Ssh port forwarding through a socks5 server @@ -207,7 +230,7 @@ const sshConfig = { }; const client = new SshTunnel(sshConfig); const forwardInfo = await client.forwardOut('3000:192.168.1.1:3000'); -// { localPort: 3000, destHost: '192.168.1.1', destPort: 3000, key: '3000:192.168.1.1:3000', type: 'out' } +// { localPort: 3000, destHost: '192.168.1.1', destPort: 3000, id: '3000:192.168.1.1:3000', type: 'out' } ``` ### Commands executing @@ -225,3 +248,9 @@ const client = new SshTunnel(sshConfig); const result = await client.exec('echo 1'); // 1 ``` + +## coming soon + +- support private key path, password +- forward in +- ssh server hopping diff --git a/__test__/index.ts b/__test__/index.ts index 7e1521f..fac1fa1 100644 --- a/__test__/index.ts +++ b/__test__/index.ts @@ -33,7 +33,23 @@ import path from 'path'; }); const res = await client.exec('echo 1'); console.log('echo 1 expected 1, received:', res); - await client.forwardOut('88:127.0.0.1:80'); + const proxy1 = await client.forwardOut([{ + id: 'proxy1-1', + proxy: '88:127.0.0.1:80' + }, { + id: 'proxy1-2', + proxy: '88:127.0.0.1:80' + }]); + const proxy2 = await client.forwardOut({ + id: 'proxy2', + proxy: '88:127.0.0.1:80' + }); + const proxy3 = await client.forwardOut(['88:127.0.0.1:80', '89:127.0.0.1:80']); + const proxy4 = await client.forwardOut('90:127.0.0.1:80'); + console.log(proxy1); + console.log(proxy2); + console.log(proxy3); + console.log(proxy4); const response = await fetch('http://127.0.0.1:88'); const data = await response.text(); console.log('http request expected ssh, received: ', data); diff --git a/package.json b/package.json index 3c09fd1..3f6a7a6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "ssh-tunneling", - "version": "1.1.0", + "version": "1.1.1", "description": "a ssh-tunneling client for nodejs", "keywords": [ "ssh tunnel", diff --git a/src/index.ts b/src/index.ts index 2bd3436..f877e57 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ type ProxyConfig = { localPort: number; destHost: string; destPort: number; - key?: string | number; + id: string | number; } class SshTunnel { @@ -61,7 +61,7 @@ class SshTunnel { localPort: number; destHost: string; destPort: number; - key?: string | number; + id?: string | number; server: net.Server; type: 'out' | 'in'; }[] = []; @@ -312,9 +312,9 @@ class SshTunnel { } private _forwardOut = async (proxyConfig: ProxyConfig) => { - const { localPort, destHost, destPort, key } = proxyConfig; - if (this.proxyList.find(item => item.localPort === localPort && item.server?.listening)) { - throw new Error(`localPort ${localPort} is proxying`); + const { localPort, destHost, destPort, id } = proxyConfig; + if (this.proxyList.find(item => item.id === id)) { + throw new Error(`id ${id} is duplicated, use another one please`); } // logger.lightWhite(`echo -e "${this.sshConfig.privateKey}" > ~/.ssh/${this.sshConfig.username}`); logger.bgBlack(this.genSshCommand(proxyConfig)); @@ -383,14 +383,14 @@ class SshTunnel { destHost, destPort, server, - key, + id, type: 'out' }); logger.cyan( `proxy server listening on 127.0.0.1:${localPort} => ${destHost}:${destPort}`, ); process.once('exit', () => { - console.log('exit'); + logger.lightWhite(`proxy server ${id} exit`); this.close(); }); return proxyConfig; @@ -399,8 +399,12 @@ class SshTunnel { public forwardOut(proxyConfig: string): Promise + public forwardOut(proxyConfig: { id: string | number, proxy: string }): Promise + public forwardOut(proxyConfig: string[]): Promise + public forwardOut(proxyConfig: { id: string | number, proxy: string }[]): Promise + /** * @description ssh port forwarding * @expample proxy('3000:192.168.1.1:3000') @@ -411,13 +415,27 @@ class SshTunnel { const result: ProxyConfig[] = []; await proxyConfig.reduce((pre, config) => { return pre.then(async () => { - const [localPort, destHost, destPort] = config.split(':') || []; - const availablePort = await getAvailablePort(Number(localPort)); + let localPort: string = ''; + let destHost: string = ''; + let destPort: string = ''; + let id: string | number = ''; + if (typeof config === 'string') { + [localPort, destHost, destPort] = config.split(':') || []; + id = config; + } + if (Object.prototype.toString.call(config) === '[object Object]') { + [localPort, destHost, destPort] = config.proxy.split(':') || []; + id = config.id; + } + if ([localPort, destHost, destPort, id].some(s => !s)) { + throw new Error(`params ${typeof proxyConfig === 'string' ? proxyConfig :JSON.stringify(proxyConfig)} is invalid`) + } + localPort = await getAvailablePort(Number(localPort)); const params = { - localPort: availablePort, + localPort: Number(localPort), destHost, destPort: Number(destPort), - key: config + id } await this._forwardOut(params); result.push(params); @@ -432,7 +450,19 @@ class SshTunnel { localPort: availablePort, destHost, destPort: Number(destPort), - key: proxyConfig + id: proxyConfig + } + await this._forwardOut(params); + return params; + } + if (Object.prototype.toString.call(proxyConfig) === '[object Object]') { + const [localPort, destHost, destPort] = proxyConfig.proxy.split(':') || []; + const availablePort = await getAvailablePort(Number(localPort)); + const params: ProxyConfig = { + localPort: availablePort, + destHost, + destPort: Number(destPort), + id: proxyConfig.id } await this._forwardOut(params); return params; @@ -444,12 +474,12 @@ class SshTunnel { * @descrption close tunnel and destroy all the instance * @params key: The server key you want to close.If passing empty, it will close all the servers and the main ssh client. */ - public close = async (key?: string | number) => { - if (!key) { + public close = async (id?: string | number) => { + if (!id) { this.sshClient?.destroy(); this.socksSocket?.destroy(); } - const targetList = this.proxyList.filter(item => key ? item.key === key : true); + const targetList = this.proxyList.filter(item => id ? item.id === id : true); targetList.forEach(item => item.server.close()); }