Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions src/vs/platform/remote/common/tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isWindows } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
Expand All @@ -30,6 +31,10 @@ export interface TunnelCreationOptions {
elevationRequired?: boolean;
}

export interface TunnelProviderFeatures {
elevation: boolean;
}

export interface ITunnelProvider {
forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined;
}
Expand All @@ -40,10 +45,11 @@ export interface ITunnelService {
readonly tunnels: Promise<readonly RemoteTunnel[]>;
readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<{ host: string, port: number }>;
readonly canElevate: boolean;

openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number): Promise<RemoteTunnel | undefined> | undefined;
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean): Promise<RemoteTunnel | undefined> | undefined;
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable;
setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable;
}

export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined {
Expand Down Expand Up @@ -74,6 +80,10 @@ function getOtherLocalhost(host: string): string | undefined {
return (host === 'localhost') ? '127.0.0.1' : ((host === '127.0.0.1') ? 'localhost' : undefined);
}

export function isPortPrivileged(port: number): boolean {
return !isWindows && (port < 1024);
}

export abstract class AbstractTunnelService implements ITunnelService {
declare readonly _serviceBrand: undefined;

Expand All @@ -83,25 +93,33 @@ export abstract class AbstractTunnelService implements ITunnelService {
public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
protected readonly _tunnels = new Map</*host*/ string, Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel | undefined> }>>();
protected _tunnelProvider: ITunnelProvider | undefined;
protected _canElevate: boolean = false;

public constructor(
@ILogService protected readonly logService: ILogService
) { }

setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable {
this._tunnelProvider = provider;
if (!provider) {
// clear features
this._canElevate = false;
return {
dispose: () => { }
};
}
this._tunnelProvider = provider;
this._canElevate = features.elevation;
return {
dispose: () => {
this._tunnelProvider = undefined;
}
};
}

public get canElevate(): boolean {
return this._canElevate;
}

public get tunnels(): Promise<readonly RemoteTunnel[]> {
return new Promise(async (resolve) => {
const tunnels: RemoteTunnel[] = [];
Expand Down Expand Up @@ -129,7 +147,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
this._tunnels.clear();
}

openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort: number): Promise<RemoteTunnel | undefined> | undefined {
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false): Promise<RemoteTunnel | undefined> | undefined {
if (!addressProvider) {
return undefined;
}
Expand All @@ -138,7 +156,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
remoteHost = 'localhost';
}

const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort);
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded);
if (!resolvedTunnel) {
return resolvedTunnel;
}
Expand Down Expand Up @@ -238,15 +256,11 @@ export abstract class AbstractTunnelService implements ITunnelService {
return portMap ? portMap.get(remotePort) : undefined;
}

protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel | undefined> | undefined;

protected isPortPrivileged(port: number): boolean {
return port < 1024;
}
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean): Promise<RemoteTunnel | undefined> | undefined;
}

export class TunnelService extends AbstractTunnelService {
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number | undefined): Promise<RemoteTunnel | undefined> | undefined {
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
++existing.refcount;
Expand All @@ -256,7 +270,7 @@ export class TunnelService extends AbstractTunnelService {
if (this._tunnelProvider) {
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort };
const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) };
const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo);
if (tunnel) {
this.addTunnelToMap(remoteHost, remotePort, tunnel);
Expand Down
6 changes: 3 additions & 3 deletions src/vs/platform/remote/node/tunnelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { connectRemoteAgentTunnel, IConnectionOptions, IAddressProvider, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { AbstractTunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { AbstractTunnelService, isPortPrivileged, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
import { ISignService } from 'vs/platform/sign/common/sign';

Expand Down Expand Up @@ -139,7 +139,7 @@ export class BaseTunnelService extends AbstractTunnelService {
super(logService);
}

protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel | undefined> | undefined {
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
++existing.refcount;
Expand All @@ -148,7 +148,7 @@ export class BaseTunnelService extends AbstractTunnelService {

if (this._tunnelProvider) {
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) };
const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort };
const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo);
if (tunnel) {
Expand Down
10 changes: 9 additions & 1 deletion src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,18 @@ declare module 'vscode' {
*/
tunnelFactory?: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable<Tunnel> | undefined;

/**
/**p
* Provides filtering for candidate ports.
*/
showCandidatePort?: (host: string, port: number, detail: string) => Thenable<boolean>;

/**
* Lets the resolver declare which tunnel factory features it supports.
* UNDER DISCUSSION! MAY CHANGE SOON.
*/
tunnelFeatures?: {
elevation: boolean;
};
}

export namespace workspace {
Expand Down
6 changes: 3 additions & 3 deletions src/vs/workbench/api/browser/mainThreadTunnelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostCont
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { CandidatePort, IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';

Expand Down Expand Up @@ -52,7 +52,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
this.remoteExplorerService.onFoundNewCandidates(candidates);
}

async $setTunnelProvider(): Promise<void> {
async $setTunnelProvider(features: TunnelProviderFeatures): Promise<void> {
const tunnelProvider: ITunnelProvider = {
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => {
const forward = this._proxy.$forwardPort(tunnelOptions, tunnelCreationOptions);
Expand All @@ -75,7 +75,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
return undefined;
}
};
this.tunnelService.setTunnelProvider(tunnelProvider);
this.tunnelService.setTunnelProvider(tunnelProvider, features);
}

dispose(): void {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import * as search from 'vs/workbench/services/search/common/search';
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon';
Expand Down Expand Up @@ -972,7 +972,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
$openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise<TunnelDto | undefined>;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
$getTunnels(): Promise<TunnelDescription[]>;
$setTunnelProvider(): Promise<void>;
$setTunnelProvider(features: TunnelProviderFeatures): Promise<void>;
$onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void>;
}

Expand Down
4 changes: 3 additions & 1 deletion src/vs/workbench/api/node/extHostTunnelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}
if (provider.tunnelFactory) {
this._forwardPortProvider = provider.tunnelFactory;
await this._proxy.$setTunnelProvider();
await this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? {
elevation: false
});
}
} else {
this._forwardPortProvider = undefined;
Expand Down
Loading