Giter VIP home page Giter VIP logo

Comments (5)

agebrock avatar agebrock commented on July 27, 2024 2

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.

nick22985 avatar nick22985 commented on July 27, 2024 1

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.

negativems avatar negativems commented on July 27, 2024

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')

I had this error multiple times

from tunnel-ssh.

nick22985 avatar nick22985 commented on July 27, 2024

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.

ghusse avatar ghusse commented on July 27, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.