UNPKG

@microsoft/dev-tunnels-ssh-tcp

Version:

SSH TCP extensions library for Dev Tunnels

116 lines 6.67 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // Object.defineProperty(exports, "__esModule", { value: true }); exports.LocalPortForwarder = void 0; const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh"); const streamForwarder_1 = require("./streamForwarder"); /** * Listens on a local port and forwards incoming connections as SSH channels. */ class LocalPortForwarder extends dev_tunnels_ssh_1.SshService { /* @internal */ constructor(pfs, session, channelType, localIPAddress, localPort, remoteHost, remotePort) { super(session); this.pfs = pfs; this.channelType = channelType; this.localIPAddress = localIPAddress; this.port = localPort; this.remoteHost = remoteHost; // The remote port defaults to the same as the local port, if the remote port // was unspecified and a specific (nonzero) local port was specified. Whether // or not a specific local port was specified, the local port may be changed // by the TCP listener factory. In that case the remote port does not change. this.remotePort = remotePort !== null && remotePort !== void 0 ? remotePort : (localPort !== 0 ? localPort : undefined); } /** * Local port that the forwarder is listening on. */ get localPort() { return this.port; } /* @internal */ async startForwarding(cancellation) { var _a; let listenAddress = this.localIPAddress; try { this.tcpListener = await this.pfs.tcpListenerFactory.createTcpListener(this.remotePort, listenAddress, this.port, true); const serverAddress = this.tcpListener.address(); if (!(serverAddress.port > 0)) { this.tcpListener.close(); throw new Error('Could not get server port.'); } this.port = serverAddress.port; // The SSH protocol specifies that "localhost" or "" (any) should be dual-mode. // So 2 TCP listener instances are required in those cases. if (this.localIPAddress === '127.0.0.1' || this.localIPAddress === '0.0.0.0') { // Call the factory again to create another listener, but this time with the // corresponding IPv6 local address, and not allowing a port change. listenAddress = this.localIPAddress === '0.0.0.0' ? '::' : '::1'; try { this.tcpListener2 = await this.pfs.tcpListenerFactory.createTcpListener(this.remotePort, listenAddress, this.port, false); } catch (e) { if (!(e instanceof Error) || e.code !== 'EADDRNOTAVAIL') { throw e; } // The OS may not support IPv6 or there may be no IPv6 network interfaces. this.trace(dev_tunnels_ssh_1.TraceLevel.Warning, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListenFailed, 'PortForwardingService failed to listen on ' + `${listenAddress}:{this.port}: ${e.message}`, e); // Do not rethrow, just skip IPv6 in this case. } } } catch (e) { if (!(e instanceof Error)) throw e; this.trace(dev_tunnels_ssh_1.TraceLevel.Error, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListenFailed, `PortForwardingService failed to listen on ${listenAddress}:${this.port}: ${e.message}`, e); throw e; } this.tcpListener.on('connection', this.acceptConnection.bind(this)); (_a = this.tcpListener2) === null || _a === void 0 ? void 0 : _a.on('connection', this.acceptConnection.bind(this)); this.trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListening, `PortForwardingService listening on ${this.localIPAddress}:${this.port}.`); if (this.tcpListener2) { this.trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListening, `PortForwardingService also listening on ${listenAddress}:${this.port}.`); } } async acceptConnection(socket) { var _a, _b, _c, _d, _e; this.trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardConnectionAccepted, 'PortForwardingService accepted connection from: ' + `${socket.remoteAddress} on port ${this.port}`); // TODO: Set socket options? let channel; try { channel = await this.pfs.openChannel(this.session, this.channelType, (_a = socket.remoteAddress) !== null && _a !== void 0 ? _a : null, (_b = socket.remotePort) !== null && _b !== void 0 ? _b : null, (_c = this.remoteHost) !== null && _c !== void 0 ? _c : this.localIPAddress, (_d = this.remotePort) !== null && _d !== void 0 ? _d : this.localPort); } catch (e) { if (!(e instanceof Error)) throw e; // TODO: Destroy the socket in a way that causes a connection reset: // https://github.com/nodejs/node/issues/27428 socket.destroy(); // Don't re-throw. This is an async event handler so the caller isn't awaiting. // The error details have already been traced. return; } // The event handler may return a transformed stream. const forwardedStream = await this.pfs.forwardedPortConnecting((_e = this.remotePort) !== null && _e !== void 0 ? _e : this.localPort, false, new dev_tunnels_ssh_1.SshStream(channel)); if (!forwardedStream) { // The event handler rejected the connection. return; } const forwarder = new streamForwarder_1.StreamForwarder(socket, forwardedStream, channel.session.trace); this.pfs.streamForwarders.push(forwarder); } dispose() { var _a, _b; // Note stopping the listener does not disconnect any already-accepted sockets. (_a = this.tcpListener) === null || _a === void 0 ? void 0 : _a.close(); (_b = this.tcpListener2) === null || _b === void 0 ? void 0 : _b.close(); this.trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardServerListening, `PortForwardingService stopped listening on ${this.localIPAddress}:${this.port}.`); super.dispose(); } } exports.LocalPortForwarder = LocalPortForwarder; //# sourceMappingURL=localPortForwarder.js.map