Add requireLocalPort property to portsAttributes

Fixes microsoft/vscode-remote-release#4958
This commit is contained in:
Alex Ross 2021-05-14 16:01:40 +02:00
parent 6aec850c75
commit c8b4656197
No known key found for this signature in database
GPG key ID: 89DDDBA66CBA7840
5 changed files with 129 additions and 13 deletions

View file

@ -54,6 +54,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"default": {
@ -100,6 +105,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"defaultSnippets": [{ "body": { "onAutoForward": "ignore" } }],

View file

@ -162,6 +162,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"default": {
@ -215,6 +220,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"defaultSnippets": [
@ -461,6 +471,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"default": {
@ -514,6 +529,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"defaultSnippets": [
@ -736,6 +756,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"default": {
@ -789,6 +814,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"defaultSnippets": [
@ -977,6 +1007,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"default": {
@ -1030,6 +1065,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"defaultSnippets": [
@ -1187,6 +1227,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"default": {
@ -1240,6 +1285,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"defaultSnippets": [
@ -1329,4 +1379,4 @@
"additionalProperties": false
}
]
}
}

View file

@ -68,6 +68,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"default": {
@ -120,6 +125,11 @@
"type": "string",
"description": "Label that will be shown in the UI for this port.",
"default": "Application"
},
"requireLocalPort": {
"type": "boolean",
"markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.",
"default": false
}
},
"defaultSnippets": [

View file

@ -178,6 +178,11 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
type: 'string',
description: localize('remote.portsAttributes.label', "Label that will be shown in the UI for this port."),
default: localize('remote.portsAttributes.labelDefault', "Application")
},
'requireLocalPort': {
type: 'boolean',
markdownDescription: localize('remote.portsAttributes.requireLocalPort', "When true, a modal dialog will show if the chosen local port isn't used for forwarding."),
default: false
}
},
default: {
@ -216,6 +221,11 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
type: 'string',
description: localize('remote.portsAttributes.label', "Label that will be shown in the UI for this port."),
default: localize('remote.portsAttributes.labelDefault', "Application")
},
'requireLocalPort': {
type: 'boolean',
markdownDescription: localize('remote.portsAttributes.requireLocalPort', "When true, a modal dialog will show if the chosen local port isn't used for forwarding."),
default: false
}
},
defaultSnippets: [{ body: { onAutoForward: 'ignore' } }],

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@ -21,6 +22,8 @@ import { hash } from 'vs/base/common/hash';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { flatten } from 'vs/base/common/arrays';
import Severity from 'vs/base/common/severity';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('remoteExplorerService');
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
@ -141,10 +144,11 @@ export enum OnPortForward {
Ignore = 'ignore'
}
interface Attributes {
export interface Attributes {
label: string | undefined;
onAutoForward: OnPortForward | undefined,
elevateIfNeeded: boolean | undefined;
requireLocalPort: boolean | undefined;
}
interface PortRange { start: number, end: number }
@ -182,7 +186,8 @@ export class PortsAttributes extends Disposable {
const attributes: Attributes = {
label: undefined,
onAutoForward: undefined,
elevateIfNeeded: undefined
elevateIfNeeded: undefined,
requireLocalPort: undefined
};
while (index >= 0) {
const found = this.portsAttributes[index];
@ -190,15 +195,18 @@ export class PortsAttributes extends Disposable {
attributes.onAutoForward = found.onAutoForward ?? attributes.onAutoForward;
attributes.elevateIfNeeded = (found.elevateIfNeeded !== undefined) ? found.elevateIfNeeded : attributes.elevateIfNeeded;
attributes.label = found.label ?? attributes.label;
attributes.requireLocalPort = found.requireLocalPort;
} else {
// It's a range or regex, which means that if the attribute is already set, we keep it
attributes.onAutoForward = attributes.onAutoForward ?? found.onAutoForward;
attributes.elevateIfNeeded = (attributes.elevateIfNeeded !== undefined) ? attributes.elevateIfNeeded : found.elevateIfNeeded;
attributes.label = attributes.label ?? found.label;
attributes.requireLocalPort = (attributes.requireLocalPort !== undefined) ? attributes.requireLocalPort : undefined;
}
index = this.findNextIndex(port, commandLine, this.portsAttributes, index + 1);
}
if (attributes.onAutoForward !== undefined || attributes.elevateIfNeeded !== undefined || attributes.label !== undefined) {
if (attributes.onAutoForward !== undefined || attributes.elevateIfNeeded !== undefined
|| attributes.label !== undefined || attributes.requireLocalPort !== undefined) {
return attributes;
}
@ -265,7 +273,8 @@ export class PortsAttributes extends Disposable {
key: key,
elevateIfNeeded: setting.elevateIfPrivileged,
onAutoForward: setting.onAutoForward,
label: setting.label
label: setting.label,
requireLocalPort: setting.requireLocalPort
});
}
@ -274,7 +283,8 @@ export class PortsAttributes extends Disposable {
this.defaultPortAttributes = {
elevateIfNeeded: defaults.elevateIfNeeded,
label: defaults.label,
onAutoForward: defaults.onAutoForward
onAutoForward: defaults.onAutoForward,
requireLocalPort: defaults.requireLocalPort
};
}
@ -313,6 +323,8 @@ export class PortsAttributes extends Disposable {
}
}
const MISMATCH_LOCAL_PORT_COOLDOWN = 10 * 1000; // 10 seconds
export class TunnelModel extends Disposable {
readonly forwarded: Map<string, Tunnel>;
readonly detected: Map<string, Tunnel>;
@ -344,7 +356,8 @@ export class TunnelModel extends Disposable {
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@ILogService private readonly logService: ILogService
@ILogService private readonly logService: ILogService,
@IDialogService private readonly dialogService: IDialogService
) {
super();
this.configPortsAttributes = new PortsAttributes(configurationService);
@ -458,10 +471,29 @@ export class TunnelModel extends Disposable {
}
}
private mismatchCooldown = new Date();
private async showPortMismatchModalIfNeeded(tunnel: RemoteTunnel, expectedLocal: number, attributes: Attributes | undefined) {
if (!tunnel.tunnelLocalPort || !attributes?.requireLocalPort) {
return;
}
if (tunnel.tunnelLocalPort === expectedLocal) {
return;
}
const newCooldown = new Date();
if ((this.mismatchCooldown.getTime() + MISMATCH_LOCAL_PORT_COOLDOWN) > newCooldown.getTime()) {
return;
}
this.mismatchCooldown = newCooldown;
const mismatchString = nls.localize('remote.localPortMismatch.single', "Local port {0} could not be used for forwarding to remote port {1}.\n\nThis usually happens when there is already another process using local port {0}.\n\nPort number {2} has been used instead.",
expectedLocal, tunnel.tunnelRemotePort, tunnel.tunnelLocalPort);
return this.dialogService.show(Severity.Info, mismatchString, [nls.localize('remote.localPortMismatch.Ok', "Ok")]);
}
async forward(remote: { host: string, port: number }, local?: number, name?: string, source?: string, elevateIfNeeded?: boolean, isPublic?: boolean, restore: boolean = true): Promise<RemoteTunnel | void> {
const existingTunnel = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, remote.host, remote.port);
const port = local !== undefined ? local : remote.port;
const attributes = (await this.getAttributes([port]))?.get(port);
const attributes = (await this.getAttributes([remote.port]))?.get(remote.port);
const localPort = (local !== undefined) ? local : remote.port;
if (!existingTunnel) {
const authority = this.environmentService.remoteAuthority;
@ -469,7 +501,7 @@ export class TunnelModel extends Disposable {
getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; }
} : undefined;
const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, local, (!elevateIfNeeded) ? attributes?.elevateIfNeeded : elevateIfNeeded, isPublic);
const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, localPort, (!elevateIfNeeded) ? attributes?.elevateIfNeeded : elevateIfNeeded, isPublic);
if (tunnel && tunnel.localAddress) {
const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces<CandidatePort>(this._candidates ?? new Map(), remote.host, remote.port);
const newForward: Tunnel = {
@ -490,6 +522,7 @@ export class TunnelModel extends Disposable {
this.forwarded.set(key, newForward);
this.remoteTunnels.set(key, tunnel);
await this.storeForwarded();
await this.showPortMismatchModalIfNeeded(tunnel, localPort, attributes);
this._onForwardPort.fire(newForward);
return tunnel;
}
@ -682,7 +715,8 @@ export class TunnelModel extends Disposable {
mergedAttributes.set(port, {
elevateIfNeeded: config?.elevateIfNeeded,
label: config?.label,
onAutoForward: config?.onAutoForward ?? PortsAttributes.providedActionToAction(provider?.autoForwardAction)
onAutoForward: config?.onAutoForward ?? PortsAttributes.providedActionToAction(provider?.autoForwardAction),
requireLocalPort: config?.requireLocalPort
});
});
@ -742,9 +776,11 @@ class RemoteExplorerService implements IRemoteExplorerService {
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@ILogService logService: ILogService
@ILogService logService: ILogService,
@IDialogService dialogService: IDialogService
) {
this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService, environmentService, remoteAuthorityResolverService, workspaceContextService, logService);
this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService, environmentService,
remoteAuthorityResolverService, workspaceContextService, logService, dialogService);
}
set targetType(name: string[]) {