sussudio
Version:
An unofficial VS Code Internal API
238 lines (237 loc) • 11.5 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import * as net from 'net';
import * as os from 'os';
import { BROWSER_RESTRICTED_PORTS, findFreePortFaster } from "../../../base/node/ports.mjs";
import { nodeSocketFactory } from "../../remote/node/nodeSocketFactory.mjs";
import { Barrier } from "../../../base/common/async.mjs";
import { Disposable } from "../../../base/common/lifecycle.mjs";
import { IConfigurationService } from "../../configuration/common/configuration.mjs";
import { ILogService } from "../../log/common/log.mjs";
import { IProductService } from "../../product/common/productService.mjs";
import { connectRemoteAgentTunnel } from "../../remote/common/remoteAgentConnection.mjs";
import { AbstractTunnelService, isAllInterfaces, isLocalhost, isPortPrivileged, TunnelPrivacyId } from "../common/tunnel.mjs";
import { ISignService } from "../../sign/common/sign.mjs";
import { OS } from "../../../base/common/platform.mjs";
async function createRemoteTunnel(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort) {
let readyTunnel;
for (let attempts = 3; attempts; attempts--) {
readyTunnel?.dispose();
const tunnel = new NodeRemoteTunnel(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
readyTunnel = await tunnel.waitForReady();
if ((tunnelLocalPort && BROWSER_RESTRICTED_PORTS[tunnelLocalPort]) || !BROWSER_RESTRICTED_PORTS[readyTunnel.tunnelLocalPort]) {
break;
}
}
return readyTunnel;
}
class NodeRemoteTunnel extends Disposable {
defaultTunnelHost;
suggestedLocalPort;
tunnelRemotePort;
tunnelLocalPort;
tunnelRemoteHost;
localAddress;
privacy = TunnelPrivacyId.Private;
_options;
_server;
_barrier;
_listeningListener;
_connectionListener;
_errorListener;
_socketsDispose = new Map();
constructor(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, suggestedLocalPort) {
super();
this.defaultTunnelHost = defaultTunnelHost;
this.suggestedLocalPort = suggestedLocalPort;
this._options = options;
this._server = net.createServer();
this._barrier = new Barrier();
this._listeningListener = () => this._barrier.open();
this._server.on('listening', this._listeningListener);
this._connectionListener = (socket) => this._onConnection(socket);
this._server.on('connection', this._connectionListener);
// If there is no error listener and there is an error it will crash the whole window
this._errorListener = () => { };
this._server.on('error', this._errorListener);
this.tunnelRemotePort = tunnelRemotePort;
this.tunnelRemoteHost = tunnelRemoteHost;
}
async dispose() {
super.dispose();
this._server.removeListener('listening', this._listeningListener);
this._server.removeListener('connection', this._connectionListener);
this._server.removeListener('error', this._errorListener);
this._server.close();
const disposers = Array.from(this._socketsDispose.values());
disposers.forEach(disposer => {
disposer();
});
}
async waitForReady() {
const startPort = this.suggestedLocalPort ?? this.tunnelRemotePort;
const hostname = isAllInterfaces(this.defaultTunnelHost) ? '0.0.0.0' : '127.0.0.1';
// try to get the same port number as the remote port number...
let localPort = await findFreePortFaster(startPort, 2, 1000, hostname);
// if that fails, the method above returns 0, which works out fine below...
let address = null;
this._server.listen(localPort, this.defaultTunnelHost);
await this._barrier.wait();
address = this._server.address();
// It is possible for findFreePortFaster to return a port that there is already a server listening on. This causes the previous listen call to error out.
if (!address) {
localPort = 0;
this._server.listen(localPort, this.defaultTunnelHost);
await this._barrier.wait();
address = this._server.address();
}
this.tunnelLocalPort = address.port;
this.localAddress = `${this.tunnelRemoteHost === '127.0.0.1' ? '127.0.0.1' : 'localhost'}:${address.port}`;
return this;
}
async _onConnection(localSocket) {
// pause reading on the socket until we have a chance to forward its data
localSocket.pause();
const tunnelRemoteHost = (isLocalhost(this.tunnelRemoteHost) || isAllInterfaces(this.tunnelRemoteHost)) ? 'localhost' : this.tunnelRemoteHost;
const protocol = await connectRemoteAgentTunnel(this._options, tunnelRemoteHost, this.tunnelRemotePort);
const remoteSocket = protocol.getSocket().socket;
const dataChunk = protocol.readEntireBuffer();
protocol.dispose();
if (dataChunk.byteLength > 0) {
localSocket.write(dataChunk.buffer);
}
localSocket.on('end', () => {
if (localSocket.localAddress) {
this._socketsDispose.delete(localSocket.localAddress);
}
remoteSocket.end();
});
localSocket.on('close', () => remoteSocket.end());
localSocket.on('error', () => {
if (localSocket.localAddress) {
this._socketsDispose.delete(localSocket.localAddress);
}
remoteSocket.destroy();
});
remoteSocket.on('end', () => localSocket.end());
remoteSocket.on('close', () => localSocket.end());
remoteSocket.on('error', () => {
localSocket.destroy();
});
localSocket.pipe(remoteSocket);
remoteSocket.pipe(localSocket);
if (localSocket.localAddress) {
this._socketsDispose.set(localSocket.localAddress, () => {
// Need to end instead of unpipe, otherwise whatever is connected locally could end up "stuck" with whatever state it had until manually exited.
localSocket.end();
remoteSocket.end();
});
}
}
}
let BaseTunnelService = class BaseTunnelService extends AbstractTunnelService {
socketFactory;
signService;
productService;
constructor(socketFactory, logService, signService, productService, configurationService) {
super(logService, configurationService);
this.socketFactory = socketFactory;
this.signService = signService;
this.productService = productService;
}
isPortPrivileged(port) {
return isPortPrivileged(port, this.defaultTunnelHost, OS, os.release());
}
retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol) {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
++existing.refcount;
return existing.value;
}
if (this._tunnelProvider) {
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
}
else {
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
const options = {
commit: this.productService.commit,
quality: this.productService.quality,
socketFactory: this.socketFactory,
addressProvider,
signService: this.signService,
logService: this.logService,
ipcLogger: null
};
const tunnel = createRemoteTunnel(options, this.defaultTunnelHost, remoteHost, remotePort, localPort);
this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.');
this.addTunnelToMap(remoteHost, remotePort, tunnel);
return tunnel;
}
}
};
BaseTunnelService = __decorate([
__param(1, ILogService),
__param(2, ISignService),
__param(3, IProductService),
__param(4, IConfigurationService)
], BaseTunnelService);
export { BaseTunnelService };
let TunnelService = class TunnelService extends BaseTunnelService {
constructor(logService, signService, productService, configurationService) {
super(nodeSocketFactory, logService, signService, productService, configurationService);
}
};
TunnelService = __decorate([
__param(0, ILogService),
__param(1, ISignService),
__param(2, IProductService),
__param(3, IConfigurationService)
], TunnelService);
export { TunnelService };
let SharedTunnelsService = class SharedTunnelsService extends Disposable {
logService;
productService;
signService;
configurationService;
_tunnelServices = new Map();
constructor(logService, productService, signService, configurationService) {
super();
this.logService = logService;
this.productService = productService;
this.signService = signService;
this.configurationService = configurationService;
}
async openTunnel(authority, addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol) {
this.logService.trace(`ForwardedPorts: (SharedTunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`);
if (!this._tunnelServices.has(authority)) {
const tunnelService = new TunnelService(this.logService, this.signService, this.productService, this.configurationService);
this._register(tunnelService);
this._tunnelServices.set(authority, tunnelService);
tunnelService.onTunnelClosed(async () => {
if ((await tunnelService.tunnels).length === 0) {
tunnelService.dispose();
this._tunnelServices.delete(authority);
}
});
}
return this._tunnelServices.get(authority).openTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
}
};
SharedTunnelsService = __decorate([
__param(0, ILogService),
__param(1, IProductService),
__param(2, ISignService),
__param(3, IConfigurationService)
], SharedTunnelsService);
export { SharedTunnelsService };