Comments (5)
Hi there, you did some impressive job here :-)
I will do some additional testing on that topic, and I am planning to release a pre-release version , that could make your life much easier.
Thanks for the effort I will come back to you !
from tunnel-ssh.
Also with the new update it is possible for the ssConnection.isBroken
to be undefined and causes the package to fall over
if (sshConnection.isBroken) {
^
TypeError: Cannot read properties of undefined (reading 'isBroken')
from tunnel-ssh.
Also with the new update it is possible for the
ssConnection.isBroken
to be undefined and causes the package to fall overif (sshConnection.isBroken) { ^ TypeError: Cannot read properties of undefined (reading 'isBroken')
I had this error multiple times
from tunnel-ssh.
I ended up rewriting this to suit my needs. I did not do all the auto connection close things but this may help with solving the issues that I was facing here.
import { Client, type ConnectConfig } from 'ssh2';
import HandlerClient from './handlers/client.js';
import net, { type ListenOptions, type Server } from 'net';
import { type Debugger } from 'debug';
export interface ITunnelOptions {
autoClose: boolean;
reconnectOnError: boolean;
}
export interface IForwardOptions {
srcAddr: string;
srcPort: number;
dstAddr: string;
dstPort: number;
}
export default class sshTunnel {
discordClient: HandlerClient;
debug: Debugger;
tunnelOptions: ITunnelOptions;
listenOptions: ListenOptions;
connectConfig: ConnectConfig;
forwardOptions: IForwardOptions;
// Client, Server
server: Server | undefined;
client: Client | undefined;
clientReconnectInterval: NodeJS.Timeout | undefined;
constructor(options: { discordClient: HandlerClient; tunnelOptions: ITunnelOptions; listenOptions: ListenOptions; connectConfig: ConnectConfig; forwardOptions: IForwardOptions }) {
this.discordClient = options.discordClient;
this.tunnelOptions = Object.assign({ autoClose: false, reconnectOnError: false }, options.tunnelOptions || {});
this.listenOptions = options.listenOptions;
this.connectConfig = Object.assign({ port: 22, username: 'root' }, options.connectConfig);
this.forwardOptions = Object.assign({ dstAddr: '0.0.0.0' }, options.forwardOptions);
this.debug = this.discordClient.$debug.extend('sshTunnel');
this.debug('Starting SSH Tunnel');
}
createServer() {
const $debug = this.debug.extend('createServer');
$debug('Creating Server');
return Promise.resolve()
.then(() => net.createServer())
.then((server) => {
return new Promise((resolve, reject) => {
let errorHandler = (err) => {
$debug('Error', err);
reject(err);
};
server.on('error', errorHandler);
process.on('uncaughtException', errorHandler);
server.on('close', () => {
$debug('Server Close');
if (this.tunnelOptions.reconnectOnError) {
this.createServer().then(() => this.serverEventListeners());
}
});
server.on('drop', () => {
$debug('Server Drop');
});
server.listen(this.listenOptions);
server.on('listening', () => {
process.removeListener('uncaughtException', errorHandler);
$debug('Server Listening');
this.server = server;
resolve(server);
});
});
});
}
createSSHClient() {
const $debug = this.debug.extend('createSSHClient');
$debug('Creating SSH Client');
// make sure client is closed
if (this.client) {
$debug('Client has old connection and is trying to reconnect killing old client');
this.client.end();
this.client = undefined;
}
return new Promise((resolve, reject) => {
let conn: Client = new Client();
conn.on('ready', () => {
this.client = conn;
resolve(conn);
});
conn.on('error', reject);
conn.on('close', () => {
$debug('Client Close');
if (this.tunnelOptions.reconnectOnError) {
setTimeout(() => {
return this.createSSHClient()
.then(() => this.clientEventListeners())
.then(() => $debug('reconnected to client'))
.catch(() => {
$debug.extend('error')('failed to reconnect to client');
});
}, 10000);
}
});
conn.on('end', () => {
$debug('Client End');
});
Promise.resolve()
.then(() => conn.connect(this.connectConfig))
.catch((err: Error) => {
$debug.extend('error')('failed to connect', err);
throw err;
});
}).catch((err: Error) => {
$debug.extend('error')('failed to createSSHClient', err);
throw err;
});
}
serverEventListeners() {
const $debug = this.debug.extend('serverEventListeners');
$debug('Creating Server Event Listeners');
if (!this.server) throw new Error('No Server found');
// reco logic
if (this.tunnelOptions.reconnectOnError)
this.server.on('error', (err) => {
$debug.extend('error')('Server Error', err);
return Promise.resolve().then(() => this.createServer().then(() => this.serverEventListeners()));
});
this.server.on('connection', (connection) => {
$debug('Server Connection', connection.address());
if (!this.client) {
$debug('No Client Connection');
return connection.end(); // Kick the connection trying to connect to the server
}
connection.on('error', (err) => {
$debug('Connection Closed error', err);
});
// This is the mongo Connection itself
return this.client.forwardOut(this.forwardOptions.srcAddr, this.forwardOptions.srcPort, this.forwardOptions.dstAddr, this.forwardOptions.dstPort, (err, stream) => {
if (err) {
$debug.extend('error')('Server Connection Error', connection.address());
$debug('server con err', err);
setTimeout(() => {
return connection.end(); // end user connection to server
}, 10000);
} else
connection
.pipe(stream)
.pipe(connection)
.on('close', (test) => {
$debug('Server Connection Close', test);
})
.on('error', (err: Error) => {
$debug('Connection closed from server (Usually this is from the client not closing the connection) (usually ignore this)', err);
});
});
});
this.server.on('close', () => {
$debug('Server Close');
if (this.client) this.client.end();
});
}
clientEventListeners() {
const $debug = this.debug.extend('clientEventListeners');
$debug('Creating Client Event Listeners');
if (!this.client) throw new Error('No Client found');
this.client.on('ready', () => {
$debug('Client Ready');
});
this.client.on('close', () => {
$debug('Client Close');
// if (this.server) this.server.close();
});
}
createTunnel() {
this.debug('Creating SSH Tunnel');
return Promise.resolve()
.then(() => this.createSSHClient()) // Create SSH Client
.then(() => this.createServer()) // Create Server
.then(() => this.serverEventListeners()) // server event listners
.then(() => this.debug('SSH Tunnel Created'));
}
}
from tunnel-ssh.
I experience the same issue in my tests (and possibly in the app I'm working on):
it('should throw an error when the destination url is incorrect', async () => {
const privateKey = await readFile(path.resolve(__dirname, 'ssh-config', 'id_rsa'));
await expect(
buildMongooseInstance({
uri: 'mongodb://username:secret@invalid_host:27017/movies?authSource=admin',
connection: {
ssh: {
host: '127.0.0.1',
port: 2224,
username: 'forest',
privateKey,
},
socketTimeoutMS: 10,
connectTimeoutMS: 10,
serverSelectionTimeoutMS: 10,
},
}),
).rejects.toThrow(
new ConnectionError(
'mongodb://forest:***@invalid_host:27017/movies?authSource=admin',
'Server selection timed out after 10 ms',
),
);
});
it('should pass', async () => {
await new Promise(resolve => {
setTimeout(resolve, 1000);
});
expect(true).toBe(true);
});
The first test checks that the SSH error is correctly handled by our code when the URL is incorrect.
BUT when running this test suite, the second test actually fails!
I think this is caused by this line: https://github.com/agebrock/tunnel-ssh/blob/master/index.js#L123
sshConnection.forwardOut(
forwardOptionsLocal.srcAddr,
forwardOptionsLocal.srcPort,
forwardOptionsLocal.dstAddr,
forwardOptionsLocal.dstPort, (err, stream) => {
if (err) {
if (server) {
server.close()
}
throw err;
} else {
clientConnection.pipe(stream).pipe(clientConnection);
}
});
We should not throw the error in this callback, but instead send it back to sshConnection
from tunnel-ssh.
Related Issues (20)
- migration from v4 to v5 HOT 5
- Server is not closed if client fails HOT 3
- There is no way to specify dynamic port HOT 2
- About using this module to test the connection process HOT 1
- Add typescript support / update @types/tunnel-ssh HOT 5
- Can not listen to events on the latest version HOT 1
- Time to update ssh2 to v1.12.0 HOT 1
- Connecting to MongoDB via tunnel-ssh HOT 2
- Multiple ports forwards HOT 3
- Logging verbose level
- About tunneling with Redis cluster (AWS Memory DB) HOT 1
- how to close the tunnel connection HOT 2
- Question : How to handle errors HOT 1
- (love) Just to tell you how great your software is HOT 1
- Option to flip the forwarding direction?
- How to change to localhost server on my pc instead my remote mysql (if connection failed) ?
- cpu-features causes build error on M1 macOS HOT 1
- not working with nodejs current version >14 HOT 2
- Unhandled Error during connection. HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from tunnel-ssh.