@microsoft/dev-tunnels-ssh-tcp
Version:
SSH TCP extensions library for Dev Tunnels
612 lines • 35.7 kB
JavaScript
"use strict";
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var PortForwardingService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PortForwardingService = void 0;
const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh");
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const forwardedPort_1 = require("../events/forwardedPort");
const forwardedPortsCollection_1 = require("../events/forwardedPortsCollection");
const ipAddressConversions_1 = require("../ipAddressConversions");
const portForwardChannelOpenMessage_1 = require("../messages/portForwardChannelOpenMessage");
const portForwardRequestMessage_1 = require("../messages/portForwardRequestMessage");
const portForwardSuccessMessage_1 = require("../messages/portForwardSuccessMessage");
const tcpListenerFactory_1 = require("../tcpListenerFactory");
const portForwardMessageFactory_1 = require("../portForwardMessageFactory");
const localPortForwarder_1 = require("./localPortForwarder");
const remotePortForwarder_1 = require("./remotePortForwarder");
const remotePortStreamer_1 = require("./remotePortStreamer");
const forwardedPortEventArgs_1 = require("../events/forwardedPortEventArgs");
/**
* Implements the standard SSH port-forwarding protocol.
* @example
* Use `SshSessionConfiguration.addService()` on both client and server side configurations
* to add the `PortForwardingService` type before attempting to call methods on the service.
* Then use `SshSession.activateService()` to get the service instance:
*
* const config = new SshSessionConfiguration();
* config.addService(PortForwardingService);
* const client = new SshClient(config);
* const session = await client.openSession(host, port);
* await session.authenticate(clientCredentials);
* const pfs = session.activateService(PortForwardingService);
* const forwarder = pfs.forwardToRemotePort('::', 3000);
*/
let PortForwardingService = PortForwardingService_1 = class PortForwardingService extends dev_tunnels_ssh_1.SshService {
/* @internal */
constructor(session) {
super(session);
/**
* Maps from FORWARDED port number to the object that manages listening for incoming
* connections for that port and forwarding them through the session.
*
* Note the actual local source port number used may be different from the forwarded port
* number if the local TCP listener factory chose a different port. The forwarded port number
* is used to identify the port in any messages exchanged between client and server.
*/
this.localForwarders = new Map();
/**
* Maps from FORWARDED port numbers to the object that manages relaying forwarded connections
* from the session to a local port.
*
* Note the actual local destination port number used may be different from the forwarded port
* number. The forwarded port number is used to identify the port in any messages exchanged
* between client and server.
*/
this.remoteConnectors = new Map();
/* @internal */
this.streamForwarders = [];
/**
* Gets or sets a value that controls whether the port-forwarding service listens on
* local TCP sockets to accept connections for ports that are forwarded from the remote side.
*
* The default is true.
*
* This property is typically initialized before connecting a session (if not keeping the
* default). It may be changed at any time while the session is connected, and the new value
* will affect any newly forwarded ports after that, but not previously-forwarded ports.
*
* Regardless of whether this is enabled, connections to forwarded ports can be made using
* `connectToForwardedPort()`.
*/
this.acceptLocalConnectionsForForwardedPorts = true;
/**
* Gets or sets a value that controls whether the port-forwarding service forwards connections
* to local TCP sockets.
*
* The default is true.
*
* This property is typically initialized before connecting a session (if not keeping the
* default). It may be changed at any time while the session is connected, and the new value
* will affect any newly forwarded ports after that, but not previously-forwarded ports.
*/
this.forwardConnectionsToLocalPorts = true;
/**
* Gets or sets a value that controls whether the port-forwarding service accepts
* 'direct-tcpip' channel open requests and forwards the channel connections to the local port.
*
* The default is true.
*
* This property is typically initialized before connecting a session (if not keeping the
* default). It may be changed at any time while the session is connected, and the new value
* will affect any newly forwarded ports after that, but not previously-forwarded ports.
*
* Regardless of whether this is enabled, the remote side can open 'forwarded-tcpip' channels
* to connect to ports that were explicitly forwarded by this side.
*/
this.acceptRemoteConnectionsForNonForwardedPorts = true;
/**
* Gets the collection of ports that are currently being forwarded from the remote side
* to the local side.
*
* Ports are added to this collection when `forwardFromRemotePort()` or
* `streamFromRemotePort()` is called (and the other side accepts the
* 'tcpip-forward' request), and then are removed when the `RemotePortForwarder`
* is disposed (which also sends a 'cancel-tcpip-forward' message).
*
* Each forwarded port may have 0 or more active connections (channels).
*
* The collection does not include direct connections initiated via
* `forwardToRemotePort()` or `streamToRemotePort()`.
*
* Local forwarded ports may or may not have local TCP listeners automatically set up,
* depending on the value of `acceptLocalConnectionsForForwardedPorts`.
*/
this.localForwardedPorts = new forwardedPortsCollection_1.ForwardedPortsCollection();
/**
* Gets the collection of ports that are currently being forwarded from the local side
* to the remote side.
*
* Ports are added to this collection when the port-forwarding service handles a
* 'tcpip-forward' request message, and removed when it receives a 'cancel-tcpip-forward'
* request message.
*
* Each forwarded port may have 0 or more active connections (channels).
*
* The collection does not include direct connections initiated via
* `forwardToRemotePort()` or `streamToRemotePort()`.
*/
this.remoteForwardedPorts = new forwardedPortsCollection_1.ForwardedPortsCollection();
/**
* Gets or sets a factory for creating TCP listeners.
*
* Applications may override this factory to provide custom logic for selecting
* local port numbers to listen on for port-forwarding.
*
* This factory is not used when `acceptLocalConnectionsForForwardedPorts` is
* set to false.
*/
this.tcpListenerFactory = new tcpListenerFactory_1.DefaultTcpListenerFactory();
/**
* Gets or sets a factory for creating port-forwarding messages.
*
* A message factory enables applications to extend port-forwarding by providing custom
* message subclasses that may include additional properties.
*/
this.messageFactory = new portForwardMessageFactory_1.DefaultPortForwardMessageFactory();
this.forwardedPortConnectingEmitter = new vscode_jsonrpc_1.Emitter();
/**
* Event raised when an incoming or outgoing connection to a forwarded port is
* about to be established.
*/
this.onForwardedPortConnecting = this.forwardedPortConnectingEmitter.event;
}
/* @internal */
async forwardedPortConnecting(port, isIncoming, stream, cancellation) {
try {
const args = new forwardedPortEventArgs_1.ForwardedPortConnectingEventArgs(port, isIncoming, stream, cancellation);
this.forwardedPortConnectingEmitter.fire(args);
if (args.transformPromise) {
return await args.transformPromise;
}
}
catch (e) {
if (!(e instanceof Error))
throw e;
this.trace(dev_tunnels_ssh_1.TraceLevel.Warning, dev_tunnels_ssh_1.SshTraceEventIds.portForwardConnectionFailed, `Forwarded port connecting event-handler failed: ${e.message}`);
return null;
}
return stream;
}
async forwardFromRemotePort(remoteIPAddress, remotePort, localHostOrCancellation, localPort, cancellation) {
const localHost = typeof localHostOrCancellation === 'string' ? localHostOrCancellation : '127.0.0.1';
if (typeof localPort === 'undefined')
localPort = remotePort;
if (!remoteIPAddress)
throw new TypeError('Remote IP address is required.');
if (!Number.isInteger(remotePort) || remotePort < 0) {
throw new TypeError('Remote port must be a non-negative integer.');
}
if (!localHost)
throw new TypeError('Local host is required.');
if (!Number.isInteger(localPort) || localPort <= 0) {
throw new TypeError('Local port must be a positive integer.');
}
if (this.localForwardedPorts.find((p) => p.localPort === localPort)) {
throw new Error(`Local port ${localPort} is already forwarded.`);
}
else if (remotePort > 0 &&
this.localForwardedPorts.find((p) => p.remotePort === remotePort)) {
throw new Error(`Remote port ${remotePort} is already forwarded.`);
}
const forwarder = new remotePortForwarder_1.RemotePortForwarder(this, this.session, remoteIPAddress, remotePort, localHost, localPort);
const request = await this.messageFactory.createRequestMessageAsync(remotePort);
if (!(await forwarder.request(request, cancellation))) {
// The remote side rejected the forwarding request, or it was a duplicate request.
return null;
}
remotePort = forwarder.remotePort;
// The remote port is the port sent in the message to the other side,
// so the connector is indexed on that port number, rather than the local port.
//
// Do not track duplicate port forwarders. (The remote side may not have detected the
// duplicate if not accepting local connections.)
if (this.remoteConnectors.has(remotePort)) {
// Do not dispose the forwarder because that would send a message to cancel
// forwarding of the port.
return null;
}
this.remoteConnectors.set(remotePort, forwarder);
const forwardedPort = new forwardedPort_1.ForwardedPort(localPort, remotePort, false);
this.localForwardedPorts.addOrUpdatePort(forwardedPort);
forwarder.onDisposed(() => {
this.localForwardedPorts.removePort(forwardedPort);
this.remoteConnectors.delete(remotePort);
});
return forwarder;
}
async forwardToRemotePort(localIPAddress, localPort, remoteHostOrCancellation, remotePort, cancellation) {
const remoteHost = typeof remoteHostOrCancellation === 'string' ? remoteHostOrCancellation : '127.0.0.1';
if (typeof remotePort === 'undefined')
remotePort = localPort;
if (!localIPAddress)
throw new TypeError('Local IP address is required.');
if (!Number.isInteger(localPort) || localPort < 0) {
throw new TypeError('Local port must be a non-negative integer.');
}
if (!remoteHost)
throw new TypeError('Remote host is required.');
if (!Number.isInteger(remotePort) || remotePort <= 0) {
throw new TypeError('Remote port must be a positive integer.');
}
if (this.localForwarders.has(remotePort)) {
throw new Error(`Port ${remotePort} is already forwarded.`);
}
const forwarder = new localPortForwarder_1.LocalPortForwarder(this, this.session, PortForwardingService_1.reversePortForwardChannelType, localIPAddress, localPort, remoteHost, remotePort);
await forwarder.startForwarding(cancellation);
// The remote port is the port sent in the message to the other side,
// so the forwarder is indexed on that port number, rather than the local port.
this.localForwarders.set(remotePort, forwarder);
forwarder.onDisposed(() => {
this.localForwarders.delete(remotePort);
});
return forwarder;
}
/**
* Sends a request to the remote side to listen on a port and forward incoming connections as
* SSH channels of type 'forwarded-tcpip', which will then be relayed as local streams.
*
* @param remoteIPAddress IP address of the interface to bind to on the remote side.
* @param remotePort The remote port to listen on, or 0 to choose an available port.
* (The chosen port can then be obtained via the `remotePort` property on the returned object.)
* @param cancellation Cancellation token for the request; note this cannot cancel forwarding
* once it has started; use the returned disposable do do that.
* @returns A disposable object that when disposed will cancel forwarding the port, or `null`
* if the request was rejected by the remote side, possibly because the remote port was already
* in use. Handle the `onStreamOpened` event on this object to receive streams.
*/
async streamFromRemotePort(remoteIPAddress, remotePort, cancellation) {
if (!remoteIPAddress)
throw new TypeError('Remote IP address is required.');
if (!Number.isInteger(remotePort) || remotePort < 0) {
throw new TypeError('Remote port must be a non-negative integer.');
}
const streamer = new remotePortStreamer_1.RemotePortStreamer(this.session, remoteIPAddress, remotePort);
const request = await this.messageFactory.createRequestMessageAsync(remotePort);
if (!(await streamer.request(request, cancellation))) {
streamer.dispose();
return null;
}
remotePort = streamer.remotePort;
// The remote port is the port sent in the message to the other side,
// so the connector is indexed on that port number. (There is no local port anyway.)
this.remoteConnectors.set(remotePort, streamer);
const forwardedPort = new forwardedPort_1.ForwardedPort(null, remotePort, false);
this.localForwardedPorts.addOrUpdatePort(forwardedPort);
streamer.onDisposed(() => {
this.localForwardedPorts.removePort(forwardedPort);
this.remoteConnectors.delete(remotePort);
});
return streamer;
}
/**
* Opens a stream for an SSH channel of type 'direct-tcpip' that is relayed to remote port,
* regardless of whether the remote side has explicitly forwarded that port.
*
* @param remoteHost The destination hostname or IP address for forwarded connections, to be
* resolved on the remote side. WARNING: Avoid using the hostname `localhost` as the destination
* host; use `127.0.0.1` or `::1` instead. (OpenSSH does not recognize `localhost` as a valid
* destination host.)
* @param remotePort The destination port for the forwarded stream. (Must not be 0.)
* @param cancellation Cancellation token for the request; note this cannot cancel streaming
* once it has started; dipose the returned stream for that.
* @returns A stream that is relayed to the remote port.
* @throws `SshChannelError` if the streaming channel could not be opened, either because it
* was rejected by the remote side, or the remote connection failed.
*/
async streamToRemotePort(remoteHost, remotePort, cancellation) {
if (!remoteHost)
throw new TypeError('Remote host is required.');
if (!Number.isInteger(remotePort) || remotePort <= 0) {
throw new TypeError('Remote port must be a positive integer.');
}
const channel = await this.openChannel(this.session, PortForwardingService_1.reversePortForwardChannelType, null, null, remoteHost, remotePort, cancellation);
return new dev_tunnels_ssh_1.SshStream(channel);
}
/**
* Opens a stream for an SSH channel of type 'forwarded-tcpip' that is relayed to a remote
* port. The port must have been explicitly forwarded by the remote side.
*
* It may be necessary to call `waitForForwardedPort` before this method
* to ensure the port is ready for connections.
*
* An error is thrown if the requested port could not be forwarded, possibly because it was
* rejected by the remote side, or the remote connection failed.
*
* @param forwardedPort Remote port number that was forwarded.
* @param cancellation Cancellation token for the request; note this cannot
* cancel streaming once it has started; dipose the returned stream for that.
* @returns A stream that is relayed to the remote forwarded port.
*/
async connectToForwardedPort(forwardedPort, cancellation) {
if (!Number.isInteger(forwardedPort) || forwardedPort <= 0) {
throw new TypeError('Forwarded port must be a positive integer.');
}
const channel = await this.openChannel(this.session, PortForwardingService_1.portForwardChannelType, null, null, '127.0.0.1', forwardedPort, cancellation);
const forwardedStream = await this.forwardedPortConnecting(forwardedPort, false, new dev_tunnels_ssh_1.SshStream(channel), cancellation);
if (!forwardedStream) {
channel.close().catch((e) => { });
throw new dev_tunnels_ssh_1.SshChannelError('The connection to the forwarded port was rejected by the connecting event-handler.');
}
return forwardedStream;
}
/**
* Waits asynchronously for the remote side to forward an expected port number.
*
* A common pattern for some applications may be to call this method just before
* `ConnectToForwardedPortAsync`.
*
* @param forwardedPort Port number that is expected to be forwarded.
* @param cancellation Token that can be used to cancel waiting.
* @returns A promise that completes when the expected port number has been forwarded.
*/
async waitForForwardedPort(forwardedPort, cancellation) {
if (this.remoteForwardedPorts.find((p) => p.remotePort === forwardedPort)) {
// It's already forwarded, so there's no need to wait.
return;
}
const waitCompletion = new dev_tunnels_ssh_1.PromiseCompletionSource();
let cancellationRegistration;
if (cancellation) {
cancellationRegistration = cancellation.onCancellationRequested(() => waitCompletion.reject(new dev_tunnels_ssh_1.CancellationError()));
}
let portAddedRegistration;
let sessionClosedRegistration;
try {
portAddedRegistration = this.remoteForwardedPorts.onPortAdded((e) => {
if (e.port.remotePort === forwardedPort) {
waitCompletion.resolve();
}
});
sessionClosedRegistration = this.session.onClosed(() => {
waitCompletion.reject(new dev_tunnels_ssh_1.ObjectDisposedError('The session was closed.'));
});
await waitCompletion.promise;
}
finally {
portAddedRegistration === null || portAddedRegistration === void 0 ? void 0 : portAddedRegistration.dispose();
sessionClosedRegistration === null || sessionClosedRegistration === void 0 ? void 0 : sessionClosedRegistration.dispose();
cancellationRegistration === null || cancellationRegistration === void 0 ? void 0 : cancellationRegistration.dispose();
}
}
async onSessionRequest(request, cancellation) {
if (!request)
throw new TypeError('Request is required.');
else if (request.requestType !== PortForwardingService_1.portForwardRequestType &&
request.requestType !== PortForwardingService_1.cancelPortForwardRequestType) {
throw new Error(`Unexpected request type: ${request.requestType}`);
}
const portForwardRequest = request.request.convertTo(new portForwardRequestMessage_1.PortForwardRequestMessage());
const localIPAddress = ipAddressConversions_1.IPAddressConversions.fromSshAddress(portForwardRequest.addressToBind);
if (request.requestType === PortForwardingService_1.portForwardRequestType &&
portForwardRequest.port !== 0 &&
this.localForwarders.has(portForwardRequest.port)) {
// The port is already forwarded, so a failure response will be returned. This may happen
// when re-connecting, to ensure the state of forwarded ports is consistent.
//
// Note duplicate ports are not detected here when AcceptLocalConnectionsForForwardedPorts
// is false; in that case duplicate port requests may succeed (if authorized below), though
// they don't really do anything.
const message = `PortForwardingService port ${portForwardRequest.port} is already forwarded.`;
this.session.trace(dev_tunnels_ssh_1.TraceLevel.Verbose, dev_tunnels_ssh_1.SshTraceEventIds.portForwardRequestInvalid, message);
request.isAuthorized = false;
return;
}
const args = new dev_tunnels_ssh_1.SshRequestEventArgs(request.requestType, portForwardRequest, this.session.principal);
await super.onSessionRequest(args, cancellation);
let response;
let localPort = null;
if (args.isAuthorized) {
if (request.requestType === PortForwardingService_1.portForwardRequestType) {
try {
localPort = await this.startForwarding(localIPAddress, portForwardRequest.port, cancellation);
}
catch (e) {
// The error is already traced.
}
if (localPort !== null) {
// The chosen local port may be different from the requested port. Use the
// requested port in the response, unless the request was for a random port.
const forwardedPort = portForwardRequest.port === 0 ? localPort : portForwardRequest.port;
const portResponse = await this.messageFactory.createSuccessMessageAsync(forwardedPort);
portResponse.port = forwardedPort;
response = portResponse;
}
}
else if (request.requestType === PortForwardingService_1.cancelPortForwardRequestType) {
if (await this.cancelForwarding(portForwardRequest.port, cancellation)) {
response = new dev_tunnels_ssh_1.SessionRequestSuccessMessage();
}
}
}
request.responsePromise = Promise.resolve(response !== null && response !== void 0 ? response : new dev_tunnels_ssh_1.SessionRequestFailureMessage());
// Add to the collection (and raise event) after sending the response,
// to ensure event-handlers can immediately open a channel.
if (response instanceof portForwardSuccessMessage_1.PortForwardSuccessMessage) {
const forwardedPort = new forwardedPort_1.ForwardedPort(localPort !== null && localPort !== void 0 ? localPort : response.port, response.port, true);
this.remoteForwardedPorts.addOrUpdatePort(forwardedPort);
}
}
async startForwarding(localIPAddress, remotePort, cancellation) {
if (typeof remotePort !== 'number')
throw new TypeError('Remote port must be an integer.');
if (this.acceptLocalConnectionsForForwardedPorts) {
// The local port is initially set to the remote port, but it may change
// when starting forwarding, if there was a conflict.
let localPort = remotePort;
const forwarder = new localPortForwarder_1.LocalPortForwarder(this, this.session, PortForwardingService_1.portForwardChannelType, localIPAddress, localPort, undefined, remotePort === 0 ? undefined : remotePort);
await forwarder.startForwarding(cancellation);
localPort = forwarder.localPort;
if (remotePort === 0) {
// The other side requested a random port. Reply with the chosen port number.
remotePort = localPort;
}
if (this.localForwarders.has(remotePort)) {
// The forwarder (TCP listener factory) chose a port that is already forwarded.
// This can happen (though its' very unlikely) if a random port was requested.
// Returning null here causes the forward request to be rejected.
forwarder.dispose();
return null;
}
// The remote port is the port referenced in exchanged messages,
// so the forwarder is indexed on that port number, rather than the local port.
this.localForwarders.set(remotePort, forwarder);
localPort = forwarder.localPort;
forwarder.onDisposed(() => {
const forwardedPort = new forwardedPort_1.ForwardedPort(localPort, remotePort, true);
this.remoteForwardedPorts.removePort(forwardedPort);
this.localForwarders.delete(remotePort);
});
return localPort;
}
else if (remotePort !== 0) {
return remotePort;
}
else {
return null;
}
}
async cancelForwarding(forwardedPort, cancellation) {
const forwarder = this.localForwarders.get(forwardedPort);
if (forwarder) {
this.localForwarders.delete(forwardedPort);
forwarder.dispose();
return true;
}
const port = new forwardedPort_1.ForwardedPort(forwardedPort, forwardedPort, true);
if (this.remoteForwardedPorts.removePort(port)) {
return true;
}
return false;
}
async onChannelOpening(request, cancellation) {
var _a;
if (!request)
throw new TypeError('Request is required.');
const channelType = request.request.channelType;
if (channelType !== PortForwardingService_1.portForwardChannelType &&
channelType !== PortForwardingService_1.reversePortForwardChannelType) {
request.failureReason = dev_tunnels_ssh_1.SshChannelOpenFailureReason.unknownChannelType;
return;
}
let remoteConnector = null;
const portForwardMessage = request.request instanceof portForwardChannelOpenMessage_1.PortForwardChannelOpenMessage
? request.request
: request.request.convertTo(new portForwardChannelOpenMessage_1.PortForwardChannelOpenMessage());
if (request.isRemoteRequest) {
if (channelType === PortForwardingService_1.portForwardChannelType) {
const remoteIPAddress = ipAddressConversions_1.IPAddressConversions.fromSshAddress(portForwardMessage.host);
const remoteEndPoint = `${remoteIPAddress}:${portForwardMessage.port}`;
remoteConnector = (_a = this.remoteConnectors.get(portForwardMessage.port)) !== null && _a !== void 0 ? _a : null;
if (!remoteConnector) {
this.trace(dev_tunnels_ssh_1.TraceLevel.Error, dev_tunnels_ssh_1.SshTraceEventIds.portForwardRequestInvalid, 'PortForwardingService received forwarding channel ' +
`for ${remoteEndPoint} that was not requested.`);
request.failureReason = dev_tunnels_ssh_1.SshChannelOpenFailureReason.connectFailed;
request.failureDescription = 'Forwarding channel was not requested.';
return;
}
}
else if (!this.acceptRemoteConnectionsForNonForwardedPorts) {
const errorMessage = 'The session has disabled connections to non-forwarded ports.';
this.session.trace(dev_tunnels_ssh_1.TraceLevel.Warning, dev_tunnels_ssh_1.SshTraceEventIds.portForwardChannelOpenFailed, errorMessage);
request.failureReason = dev_tunnels_ssh_1.SshChannelOpenFailureReason.administrativelyProhibited;
request.failureDescription = errorMessage;
return;
}
}
const portForwardRequest = new dev_tunnels_ssh_1.SshChannelOpeningEventArgs(portForwardMessage, request.channel, request.isRemoteRequest);
await super.onChannelOpening(portForwardRequest, cancellation);
request.failureReason = portForwardRequest.failureReason;
request.failureDescription = portForwardRequest.failureDescription;
request.openingPromise = portForwardRequest.openingPromise;
if (request.failureReason === dev_tunnels_ssh_1.SshChannelOpenFailureReason.none &&
request.isRemoteRequest &&
this.forwardConnectionsToLocalPorts) {
if (remoteConnector) {
// The forwarding was initiated by this session.
await remoteConnector.onPortChannelOpening(request, cancellation);
const localPort = remoteConnector instanceof remotePortForwarder_1.RemotePortForwarder ? remoteConnector.localPort : null;
const remotePort = remoteConnector instanceof remotePortForwarder_1.RemotePortForwarder
? remoteConnector.remotePort
: portForwardMessage.port;
const forwardedPort = new forwardedPort_1.ForwardedPort(localPort, remotePort, false);
this.localForwardedPorts.addChannel(forwardedPort, request.channel);
}
else {
// THe forwarding was initiated by the remote session.
await remotePortForwarder_1.RemotePortForwarder.forwardChannel(this, request, portForwardMessage.host, portForwardMessage.port, portForwardMessage.port, this.trace, cancellation);
if (request.failureReason !== dev_tunnels_ssh_1.SshChannelOpenFailureReason.none) {
await request.channel.close(cancellation);
}
}
}
}
/* @internal */
async openChannel(session, channelType, originatorIPAddress, originatorPort, host, port, cancellation) {
let forwardedPort = undefined;
if (channelType === PortForwardingService_1.portForwardChannelType) {
forwardedPort = this.remoteForwardedPorts.find((p) => p.remotePort === port || (p.remotePort === null && p.localPort === port));
if (!forwardedPort) {
throw new Error(`Port ${port} is not being forwarded.`);
}
}
const openMessage = await this.messageFactory.createChannelOpenMessageAsync(port);
openMessage.channelType = channelType;
openMessage.originatorIPAddress = originatorIPAddress !== null && originatorIPAddress !== void 0 ? originatorIPAddress : '';
openMessage.originatorPort = originatorPort !== null && originatorPort !== void 0 ? originatorPort : 0;
openMessage.host = host;
openMessage.port = port;
const trace = this.session.trace;
let channel;
try {
channel = await session.openChannel(openMessage, null, cancellation);
trace(dev_tunnels_ssh_1.TraceLevel.Info, dev_tunnels_ssh_1.SshTraceEventIds.portForwardChannelOpened, `PortForwardingService opened ${channelType} channel #${channel.channelId} for ${host}:${port}.`);
}
catch (e) {
if (!(e instanceof Error))
throw e;
trace(dev_tunnels_ssh_1.TraceLevel.Error, dev_tunnels_ssh_1.SshTraceEventIds.portForwardChannelOpenFailed, `PortForwardingService failed to open ${channelType} channel for ${host}:${port}: ${e.message}`, e);
throw e;
}
if (channelType === PortForwardingService_1.portForwardChannelType) {
this.remoteForwardedPorts.addChannel(forwardedPort, channel);
}
return channel;
}
dispose() {
// Do not dispose StreamForwarder objects here, since they may be transferred to
// other sessions when reconnecting. The StreamForwarders will self-dispose when
// their underlying transport streams are closed.
const disposables = [
...this.localForwarders.values(),
...this.remoteConnectors.values(),
];
this.streamForwarders.splice(0, this.streamForwarders.length);
this.localForwarders.clear();
this.remoteConnectors.clear();
for (const disposable of disposables) {
disposable.dispose();
}
super.dispose();
}
};
PortForwardingService.portForwardRequestType = 'tcpip-forward';
PortForwardingService.cancelPortForwardRequestType = 'cancel-tcpip-forward';
PortForwardingService.portForwardChannelType = 'forwarded-tcpip';
PortForwardingService.reversePortForwardChannelType = 'direct-tcpip';
PortForwardingService = PortForwardingService_1 = __decorate([
(0, dev_tunnels_ssh_1.serviceActivation)({ sessionRequest: PortForwardingService_1.portForwardRequestType }),
(0, dev_tunnels_ssh_1.serviceActivation)({ sessionRequest: PortForwardingService_1.cancelPortForwardRequestType }),
(0, dev_tunnels_ssh_1.serviceActivation)({ channelType: PortForwardingService_1.portForwardChannelType }),
(0, dev_tunnels_ssh_1.serviceActivation)({ channelType: PortForwardingService_1.reversePortForwardChannelType })
], PortForwardingService);
exports.PortForwardingService = PortForwardingService;
//# sourceMappingURL=portForwardingService.js.map