Skip to content

Commit

Permalink
support id for proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
Leonewu committed Jan 10, 2023
1 parent 382c948 commit 0fa223c
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 30 deletions.
55 changes: 42 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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' }

```

Expand All @@ -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' },
// ]
```

Expand Down Expand Up @@ -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();
```
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
18 changes: 17 additions & 1 deletion __test__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
60 changes: 45 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type ProxyConfig = {
localPort: number;
destHost: string;
destPort: number;
key?: string | number;
id: string | number;
}

class SshTunnel {
Expand Down Expand Up @@ -61,7 +61,7 @@ class SshTunnel {
localPort: number;
destHost: string;
destPort: number;
key?: string | number;
id?: string | number;
server: net.Server;
type: 'out' | 'in';
}[] = [];
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand All @@ -399,8 +399,12 @@ class SshTunnel {

public forwardOut(proxyConfig: string): Promise<ProxyConfig>

public forwardOut(proxyConfig: { id: string | number, proxy: string }): Promise<ProxyConfig>

public forwardOut(proxyConfig: string[]): Promise<ProxyConfig[]>

public forwardOut(proxyConfig: { id: string | number, proxy: string }[]): Promise<ProxyConfig[]>

/**
* @description ssh port forwarding
* @expample proxy('3000:192.168.1.1:3000')
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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());
}

Expand Down

0 comments on commit 0fa223c

Please sign in to comment.