diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 9fd03586d78..82c93b7d5ad 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -126,6 +126,7 @@ export interface ITunnelService { canTunnel(uri: URI): boolean; openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise | undefined; + getExistingTunnel(remoteHost: string, remotePort: number): Promise; setEnvironmentTunnel(remoteHost: string, remotePort: number, localAddress: string, privacy: string, protocol: string): void; closeTunnel(remoteHost: string, remotePort: number): Promise; setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable; @@ -282,6 +283,19 @@ export abstract class AbstractTunnelService implements ITunnelService { })); } + async getExistingTunnel(remoteHost: string, remotePort: number): Promise { + if (isAllInterfaces(remoteHost) || isLocalhost(remoteHost)) { + remoteHost = LOCALHOST_ADDRESSES[0]; + } + + const existing = this.getTunnelFromMap(remoteHost, remotePort); + if (existing) { + ++existing.refcount; + return existing.value; + } + return undefined; + } + openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false, privacy?: string, protocol?: string): Promise | undefined { this.logService.trace(`ForwardedPorts: (TunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`); if (!addressProvider) { diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 24826e7f998..46307d1d7a4 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -769,13 +769,16 @@ export class NativeWindow extends Disposable { return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority; } } : undefined; - const tunnel = await this.tunnelService.openTunnel(addressProvider, portMappingRequest.address, portMappingRequest.port); + let tunnel = await this.tunnelService.getExistingTunnel(portMappingRequest.address, portMappingRequest.port); + if (!tunnel) { + tunnel = await this.tunnelService.openTunnel(addressProvider, portMappingRequest.address, portMappingRequest.port); + } if (tunnel) { const addressAsUri = URI.parse(tunnel.localAddress); const resolved = addressAsUri.scheme.startsWith(uri.scheme) ? addressAsUri : uri.with({ authority: tunnel.localAddress }); return { resolved, - dispose: () => tunnel.dispose(), + dispose: () => tunnel?.dispose(), }; } } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 234f9eb50c0..1993302a228 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -473,6 +473,7 @@ export class TunnelModel extends Disposable { this._register(this.tunnelService.onTunnelOpened(async (tunnel) => { const key = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); if (!mapHasAddressLocalhostOrAllInterfaces(this.forwarded, tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort) + && !mapHasAddressLocalhostOrAllInterfaces(this.detected, tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort) && !mapHasAddressLocalhostOrAllInterfaces(this.inProgress, tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort) && tunnel.localAddress) { const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort);