提交 b4e4bd16 编写于 作者: A Alex Ross

Allow tunnel providers to support making a tunnel public

上级 a4f9e607
......@@ -18,13 +18,15 @@ export interface RemoteTunnel {
readonly tunnelRemoteHost: string;
readonly tunnelLocalPort?: number;
readonly localAddress: string;
readonly public: boolean;
dispose(silent?: boolean): Promise<void>;
}
export interface TunnelOptions {
remoteAddress: { port: number, host: string };
remoteAddress: { port: number, host: string; };
localAddressPort?: number;
label?: string;
public?: boolean;
}
export interface TunnelCreationOptions {
......@@ -33,6 +35,7 @@ export interface TunnelCreationOptions {
export interface TunnelProviderFeatures {
elevation: boolean;
public: boolean;
}
export interface ITunnelProvider {
......@@ -43,17 +46,18 @@ export interface ITunnelService {
readonly _serviceBrand: undefined;
readonly tunnels: Promise<readonly RemoteTunnel[]>;
readonly canMakePublic: boolean;
readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<{ host: string, port: number }>;
readonly onTunnelClosed: Event<{ host: string, port: number; }>;
readonly canElevate: boolean;
canTunnel(uri: URI): boolean;
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean): Promise<RemoteTunnel | undefined> | undefined;
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean, isPublic?: boolean): Promise<RemoteTunnel | undefined> | undefined;
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable;
}
export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined {
export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number; } | undefined {
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
return undefined;
}
......@@ -90,11 +94,12 @@ export abstract class AbstractTunnelService implements ITunnelService {
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter();
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> }>>();
private _onTunnelClosed: Emitter<{ host: string, port: number; }> = new Emitter();
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;
private _canMakePublic: boolean = false;
public constructor(
@ILogService protected readonly logService: ILogService
......@@ -105,14 +110,18 @@ export abstract class AbstractTunnelService implements ITunnelService {
if (!provider) {
// clear features
this._canElevate = false;
this._canMakePublic = false;
return {
dispose: () => { }
};
}
this._canElevate = features.elevation;
this._canMakePublic = features.public;
return {
dispose: () => {
this._tunnelProvider = undefined;
this._canElevate = false;
this._canMakePublic = false;
}
};
}
......@@ -121,6 +130,10 @@ export abstract class AbstractTunnelService implements ITunnelService {
return this._canElevate;
}
public get canMakePublic() {
return this._canMakePublic;
}
public get tunnels(): Promise<readonly RemoteTunnel[]> {
return new Promise(async (resolve) => {
const tunnels: RemoteTunnel[] = [];
......@@ -148,7 +161,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
this._tunnels.clear();
}
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false): Promise<RemoteTunnel | undefined> | undefined {
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false, isPublic: boolean = false): Promise<RemoteTunnel | undefined> | undefined {
if (!addressProvider) {
return undefined;
}
......@@ -157,7 +170,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
remoteHost = 'localhost';
}
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded);
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic);
if (!resolvedTunnel) {
return resolvedTunnel;
}
......@@ -182,6 +195,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
tunnelRemoteHost: tunnel.tunnelRemoteHost,
tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress,
public: tunnel.public,
dispose: async () => {
const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost);
if (existingHost) {
......@@ -261,7 +275,18 @@ export abstract class AbstractTunnelService implements ITunnelService {
return !!extractLocalHostUriMetaDataForPortMapping(uri);
}
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean): Promise<RemoteTunnel | undefined> | undefined;
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined;
protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined {
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
const tunnelOptions: TunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort, public: isPublic };
const tunnel = tunnelProvider.forwardPort(tunnelOptions, creationInfo);
if (tunnel) {
this.addTunnelToMap(remoteHost, remotePort, tunnel);
}
return tunnel;
}
}
......@@ -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, isPortPrivileged, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { AbstractTunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
import { ISignService } from 'vs/platform/sign/common/sign';
......@@ -26,6 +26,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
public tunnelLocalPort!: number;
public tunnelRemoteHost: string;
public localAddress!: string;
public readonly public = false;
private readonly _options: IConnectionOptions;
private readonly _server: net.Server;
......@@ -139,7 +140,7 @@ export class BaseTunnelService extends AbstractTunnelService {
super(logService);
}
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean): Promise<RemoteTunnel | undefined> | undefined {
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
++existing.refcount;
......@@ -147,14 +148,7 @@ export class BaseTunnelService extends AbstractTunnelService {
}
if (this._tunnelProvider) {
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
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) {
this.addTunnelToMap(remoteHost, remotePort, tunnel);
}
return tunnel;
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic);
} else {
const options: IConnectionOptions = {
commit: this.productService.commit,
......
......@@ -154,12 +154,14 @@ declare module 'vscode' {
// The desired local port. If this port can't be used, then another will be chosen.
localAddressPort?: number;
label?: string;
public?: boolean;
}
export interface TunnelDescription {
remoteAddress: { port: number, host: string; };
//The complete local address(ex. localhost:1234)
localAddress: { port: number, host: string; } | string;
public?: boolean;
}
export interface Tunnel extends TunnelDescription {
......@@ -221,6 +223,7 @@ declare module 'vscode' {
*/
tunnelFeatures?: {
elevation: boolean;
public: boolean;
};
}
......
......@@ -66,6 +66,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
tunnelRemoteHost: tunnel.remoteAddress.host,
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined,
public: tunnel.public,
dispose: async (silent?: boolean) => {
return this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }, silent);
}
......
......@@ -15,14 +15,22 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'
export interface TunnelDto {
remoteAddress: { port: number, host: string };
localAddress: { port: number, host: string } | string;
public: boolean;
}
export namespace TunnelDto {
export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto {
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress };
return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress, public: !!tunnel.public };
}
export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto {
return { remoteAddress: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress };
return {
remoteAddress: {
host: tunnel.tunnelRemoteHost,
port: tunnel.tunnelRemotePort
},
localAddress: tunnel.localAddress,
public: tunnel.public
};
}
}
......
......@@ -196,7 +196,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
if (provider.tunnelFactory) {
this._forwardPortProvider = provider.tunnelFactory;
await this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? {
elevation: false
elevation: false,
public: false
});
}
} else {
......
......@@ -26,7 +26,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { ActionRunner, IAction } from 'vs/base/common/actions';
import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction, ILocalizedString, SubmenuItemAction, Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { createAndFillInContextMenuActions, createAndFillInActionBarActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IRemoteExplorerService, TunnelModel, makeAddress, TunnelType, ITunnelItem, Tunnel, mapHasAddressLocalhostOrAllInterfaces, TUNNEL_VIEW_ID, parseAddress, CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IRemoteExplorerService, TunnelModel, makeAddress, TunnelType, ITunnelItem, Tunnel, mapHasAddressLocalhostOrAllInterfaces, TUNNEL_VIEW_ID, parseAddress, CandidatePort, TunnelPrivacy } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
......@@ -410,7 +410,17 @@ interface ITunnelGroup {
class TunnelItem implements ITunnelItem {
static createFromTunnel(tunnel: Tunnel, type: TunnelType = TunnelType.Forwarded, closeable?: boolean) {
return new TunnelItem(type, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.localPort, closeable === undefined ? tunnel.closeable : closeable, tunnel.name, tunnel.runningProcess, tunnel.source, tunnel.pid);
return new TunnelItem(type,
tunnel.remoteHost,
tunnel.remotePort,
tunnel.localAddress,
tunnel.localPort,
closeable === undefined ? tunnel.closeable : closeable,
tunnel.name,
tunnel.runningProcess,
tunnel.source,
tunnel.pid,
tunnel.privacy);
}
constructor(
......@@ -423,7 +433,8 @@ class TunnelItem implements ITunnelItem {
public name?: string,
private runningProcess?: string,
private source?: string,
private pid?: number
private pid?: number,
public privacy?: TunnelPrivacy,
) { }
get label(): string {
if (this.name) {
......@@ -511,6 +522,7 @@ class TunnelItem implements ITunnelItem {
export const TunnelTypeContextKey = new RawContextKey<TunnelType>('tunnelType', TunnelType.Add);
export const TunnelCloseableContextKey = new RawContextKey<boolean>('tunnelCloseable', false);
const TunnelPrivacyContextKey = new RawContextKey<TunnelPrivacy | undefined>('tunnelPrivacy', undefined);
const TunnelViewFocusContextKey = new RawContextKey<boolean>('tunnelViewFocus', false);
const TunnelViewSelectionKeyName = 'tunnelViewSelection';
const TunnelViewSelectionContextKey = new RawContextKey<ITunnelItem | undefined>(TunnelViewSelectionKeyName, undefined);
......@@ -524,6 +536,7 @@ export class TunnelPanel extends ViewPane {
private tree!: TunnelDataTree;
private tunnelTypeContext: IContextKey<TunnelType>;
private tunnelCloseableContext: IContextKey<boolean>;
private tunnelPrivacyContext: IContextKey<TunnelPrivacy | undefined>;
private tunnelViewFocusContext: IContextKey<boolean>;
private tunnelViewSelectionContext: IContextKey<ITunnelItem | undefined>;
private portChangableContextKey: IContextKey<boolean>;
......@@ -552,6 +565,7 @@ export class TunnelPanel extends ViewPane {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService);
this.tunnelCloseableContext = TunnelCloseableContextKey.bindTo(contextKeyService);
this.tunnelPrivacyContext = TunnelPrivacyContextKey.bindTo(contextKeyService);
this.tunnelViewFocusContext = TunnelViewFocusContextKey.bindTo(contextKeyService);
this.tunnelViewSelectionContext = TunnelViewSelectionContextKey.bindTo(contextKeyService);
this.portChangableContextKey = PortChangableContextKey.bindTo(contextKeyService);
......@@ -679,11 +693,13 @@ export class TunnelPanel extends ViewPane {
this.tunnelViewSelectionContext.set(item);
this.tunnelTypeContext.set(item.tunnelType);
this.tunnelCloseableContext.set(!!item.closeable);
this.tunnelPrivacyContext.set(item.privacy);
this.portChangableContextKey.set(!!item.localPort);
} else {
this.tunnelTypeContext.reset();
this.tunnelViewSelectionContext.reset();
this.tunnelCloseableContext.reset();
this.tunnelPrivacyContext.reset();
this.portChangableContextKey.reset();
}
}
......@@ -702,10 +718,12 @@ export class TunnelPanel extends ViewPane {
this.tree!.setFocus([node]);
this.tunnelTypeContext.set(node.tunnelType);
this.tunnelCloseableContext.set(!!node.closeable);
this.tunnelPrivacyContext.set(node.privacy);
this.portChangableContextKey.set(!!node.localPort);
} else {
this.tunnelTypeContext.set(TunnelType.Add);
this.tunnelCloseableContext.set(false);
this.tunnelPrivacyContext.set(undefined);
this.portChangableContextKey.set(false);
}
......@@ -1073,6 +1091,36 @@ namespace ChangeLocalPortAction {
}
}
namespace MakePortPublicAction {
export const ID = 'remote.tunnel.makePublic';
export const LABEL = nls.localize('remote.tunnel.makePublic', "Make Public");
export function handler(): ICommandHandler {
return async (accessor, arg) => {
if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
await remoteExplorerService.close({ host: arg.remoteHost, port: arg.remotePort });
return remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }, arg.localPort, arg.name, undefined, true, true);
}
};
}
}
namespace MakePortPrivateAction {
export const ID = 'remote.tunnel.makePrivate';
export const LABEL = nls.localize('remote.tunnel.makePrivate', "Make Private");
export function handler(): ICommandHandler {
return async (accessor, arg) => {
if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
await remoteExplorerService.close({ host: arg.remoteHost, port: arg.remotePort });
return remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }, arg.localPort, arg.name, undefined, true, false);
}
};
}
}
const tunnelViewCommandsWeightBonus = 10; // give our commands a little bit more weight over other default list/tree commands
KeybindingsRegistry.registerCommandAndKeybindingRule({
......@@ -1110,6 +1158,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
});
CommandsRegistry.registerCommand(CopyAddressAction.COMMANDPALETTE_ID, CopyAddressAction.commandPaletteHandler());
CommandsRegistry.registerCommand(ChangeLocalPortAction.ID, ChangeLocalPortAction.handler());
CommandsRegistry.registerCommand(MakePortPublicAction.ID, MakePortPublicAction.handler());
CommandsRegistry.registerCommand(MakePortPrivateAction.ID, MakePortPrivateAction.handler());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, ({
command: {
......@@ -1188,6 +1238,24 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
group: '1_manage',
order: 0,
command: {
id: MakePortPublicAction.ID,
title: MakePortPublicAction.LABEL,
},
when: TunnelPrivacyContextKey.isEqualTo(TunnelPrivacy.Private)
}));
MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
group: '1_manage',
order: 0,
command: {
id: MakePortPrivateAction.ID,
title: MakePortPrivateAction.LABEL,
},
when: TunnelPrivacyContextKey.isEqualTo(TunnelPrivacy.Public)
}));
MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
group: '1_manage',
order: 1,
command: {
id: ChangeLocalPortAction.ID,
title: ChangeLocalPortAction.LABEL,
......@@ -1205,7 +1273,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
}));
MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
group: '1_manage',
order: 1,
order: 2,
command: {
id: ClosePortAction.INLINE_ID,
title: ClosePortAction.LABEL,
......
......@@ -36,13 +36,14 @@ export class TunnelFactoryContribution extends Disposable implements IWorkbenchC
// The tunnel factory may give us an inaccessible local address.
// To make sure this doesn't happen, resolve the uri immediately.
localAddress: (await openerService.resolveExternalUri(URI.parse(localAddress))).resolved.toString(),
public: !!tunnel.public,
dispose: async () => { await tunnel.dispose; }
};
resolve(remoteTunnel);
});
});
}
}, environmentService.options?.tunnelProvider?.features ?? { elevation: false }));
}, environmentService.options?.tunnelProvider?.features ?? { elevation: false, public: false }));
remoteExplorerService.setTunnelInformation(undefined);
}
}
......
......@@ -31,7 +31,7 @@ import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewExtensio
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ITunnelProvider, ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { ITunnelProvider, ITunnelService, RemoteTunnel, TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IManualSyncTask, IResourcePreview, ISyncResourceHandle, ISyncTask, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, IUserDataSyncStoreManagementService, SyncResource, SyncStatus, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncAccount, IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
......@@ -560,14 +560,15 @@ class SimpleTunnelService implements ITunnelService {
tunnels: Promise<readonly RemoteTunnel[]> = Promise.resolve([]);
canElevate: boolean = false;
canMakePublic = false;
onTunnelOpened = Event.None;
onTunnelClosed = Event.None;
canTunnel(uri: URI): boolean { return false; }
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined { return undefined; }
async changeTunnelPrivacy(remoteHost: string, remotePort: number, isPublic: boolean): Promise<RemoteTunnel | undefined> { return undefined; }
async closeTunnel(remoteHost: string, remotePort: number): Promise<void> { }
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { return Disposable.None; }
setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable { return Disposable.None; }
}
registerSingleton(ITunnelService, SimpleTunnelService);
......
......@@ -6,7 +6,7 @@
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection';
import { AbstractTunnelService, isPortPrivileged, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { AbstractTunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export class TunnelService extends AbstractTunnelService {
......@@ -17,7 +17,7 @@ export class TunnelService extends AbstractTunnelService {
super(logService);
}
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean): Promise<RemoteTunnel | undefined> | undefined {
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
++existing.refcount;
......@@ -25,14 +25,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: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo);
if (tunnel) {
this.addTunnelToMap(remoteHost, remotePort, tunnel);
}
return tunnel;
this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic);
}
return undefined;
}
......
......@@ -29,6 +29,12 @@ export enum TunnelType {
Add = 'Add'
}
export enum TunnelPrivacy {
ConstantPrivate = 'ConstantPrivate', // private, and changing is unsupported
Private = 'Private',
Public = 'Public'
}
export interface ITunnelItem {
tunnelType: TunnelType;
remoteHost: string;
......@@ -37,6 +43,7 @@ export interface ITunnelItem {
localPort?: number;
name?: string;
closeable?: boolean;
privacy?: TunnelPrivacy;
description?: string;
wideDescription?: string;
readonly icon?: ThemeIcon;
......@@ -50,6 +57,7 @@ export interface Tunnel {
localPort?: number;
name?: string;
closeable?: boolean;
privacy: TunnelPrivacy;
runningProcess: string | undefined;
pid: number | undefined;
source?: string;
......@@ -252,7 +260,8 @@ export class TunnelModel extends Disposable {
localAddress: tunnel.localAddress,
localPort: tunnel.tunnelLocalPort,
runningProcess: matchingCandidate?.detail,
pid: matchingCandidate?.pid
pid: matchingCandidate?.pid,
privacy: this.makeTunnelPrivacy(tunnel.public)
});
this.remoteTunnels.set(key, tunnel);
}
......@@ -271,7 +280,8 @@ export class TunnelModel extends Disposable {
localPort: tunnel.tunnelLocalPort,
closeable: true,
runningProcess: matchingCandidate?.detail,
pid: matchingCandidate?.pid
pid: matchingCandidate?.pid,
privacy: this.makeTunnelPrivacy(tunnel.public)
});
}
this.storeForwarded();
......@@ -288,12 +298,16 @@ export class TunnelModel extends Disposable {
}));
}
private makeTunnelPrivacy(isPublic: boolean) {
return isPublic ? TunnelPrivacy.Public : this.tunnelService.canMakePublic ? TunnelPrivacy.Private : TunnelPrivacy.ConstantPrivate;
}
async restoreForwarded() {
if (this.configurationService.getValue('remote.restoreForwardedPorts')) {
if (this.tunnelRestoreValue) {
(<Tunnel[] | undefined>JSON.parse(this.tunnelRestoreValue))?.forEach(tunnel => {
if (!mapHasAddressLocalhostOrAllInterfaces(this.detected, tunnel.remoteHost, tunnel.remotePort)) {
this.forward({ host: tunnel.remoteHost, port: tunnel.remotePort }, tunnel.localPort, tunnel.name);
this.forward({ host: tunnel.remoteHost, port: tunnel.remotePort }, tunnel.localPort, tunnel.name, undefined, undefined, tunnel.privacy === TunnelPrivacy.Public);
}
});
}
......@@ -306,7 +320,7 @@ export class TunnelModel extends Disposable {
}
}
async forward(remote: { host: string, port: number }, local?: number, name?: string, source?: string, elevateIfNeeded?: boolean): Promise<RemoteTunnel | void> {
async forward(remote: { host: string, port: number }, local?: number, name?: string, source?: string, elevateIfNeeded?: boolean, isPublic?: boolean): Promise<RemoteTunnel | void> {
const existingTunnel = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, remote.host, remote.port);
if (!existingTunnel) {
const authority = this.environmentService.remoteAuthority;
......@@ -316,7 +330,7 @@ export class TunnelModel extends Disposable {
const attributes = this.portsAttributes.getAttributes(local !== undefined ? local : remote.port);
const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, local, (!elevateIfNeeded) ? attributes?.elevateIfNeeded : elevateIfNeeded);
const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, local, (!elevateIfNeeded) ? attributes?.elevateIfNeeded : elevateIfNeeded, isPublic);
if (tunnel && tunnel.localAddress) {
const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), remote.host, remote.port);
const newForward: Tunnel = {
......@@ -328,7 +342,8 @@ export class TunnelModel extends Disposable {
localAddress: tunnel.localAddress,
runningProcess: matchingCandidate?.detail,
pid: matchingCandidate?.pid,
source
source,
privacy: this.makeTunnelPrivacy(tunnel.public)
};
const key = makeAddress(remote.host, remote.port);
this.forwarded.set(key, newForward);
......@@ -380,7 +395,8 @@ export class TunnelModel extends Disposable {
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : makeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
closeable: false,
runningProcess: matchingCandidate?.detail,
pid: matchingCandidate?.pid
pid: matchingCandidate?.pid,
privacy: TunnelPrivacy.ConstantPrivate
});
});
}
......@@ -469,7 +485,7 @@ export interface IRemoteExplorerService {
onDidChangeEditable: Event<ITunnelItem | undefined>;
setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void;
getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined;
forward(remote: { host: string, port: number }, localPort?: number, name?: string, source?: string, elevateIfNeeded?: boolean): Promise<RemoteTunnel | void>;
forward(remote: { host: string, port: number }, localPort?: number, name?: string, source?: string, elevateIfNeeded?: boolean, isPublic?: boolean): Promise<RemoteTunnel | void>;
close(remote: { host: string, port: number }): Promise<void>;
setTunnelInformation(tunnelInformation: TunnelInformation | undefined): void;
setCandidateFilter(filter: ((candidates: CandidatePort[]) => Promise<CandidatePort[]>) | undefined): IDisposable;
......@@ -522,8 +538,8 @@ class RemoteExplorerService implements IRemoteExplorerService {
return this._tunnelModel;
}
forward(remote: { host: string, port: number }, local?: number, name?: string, source?: string, elevateIfNeeded?: boolean): Promise<RemoteTunnel | void> {
return this.tunnelModel.forward(remote, local, name, source, elevateIfNeeded);
forward(remote: { host: string, port: number }, local?: number, name?: string, source?: string, elevateIfNeeded?: boolean, isPublic?: boolean): Promise<RemoteTunnel | void> {
return this.tunnelModel.forward(remote, local, name, source, elevateIfNeeded, isPublic);
}
close(remote: { host: string, port: number }): Promise<void> {
......
......@@ -70,6 +70,8 @@ interface ITunnelOptions {
localAddressPort?: number;
label?: string;
public?: boolean;
}
export interface TunnelCreationOptions {
......@@ -87,6 +89,8 @@ interface ITunnel {
*/
localAddress: string;
public?: boolean;
/**
* Implementers of Tunnel should fire onDidDispose when dispose is called.
*/
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册