Skip to content

Commit

Permalink
Enable more than just localhost for port forwarding providers
Browse files Browse the repository at this point in the history
Part of #81388
  • Loading branch information
alexr00 committed Dec 18, 2019
1 parent c7ab68c commit 8e2b304
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 133 deletions.
6 changes: 3 additions & 3 deletions src/vs/platform/remote/common/tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export interface ITunnelService {

readonly tunnels: Promise<readonly RemoteTunnel[]>;
readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<number>;
readonly onTunnelClosed: Event<{ host: string, port: number }>;

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

Expand Down
8 changes: 4 additions & 4 deletions src/vs/platform/remote/common/tunnelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export class NoOpTunnelService implements ITunnelService {
public readonly tunnels: Promise<readonly RemoteTunnel[]> = Promise.resolve([]);
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<number> = new Emitter();
public onTunnelClosed: Event<number> = this._onTunnelClosed.event;
openTunnel(_remotePort: number): Promise<RemoteTunnel> | undefined {
private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter();
public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
openTunnel(_remoteHost: string, _remotePort: number): Promise<RemoteTunnel> | undefined {
return undefined;
}
async closeTunnel(_remotePort: number): Promise<void> {
async closeTunnel(_remoteHost: string, _remotePort: number): Promise<void> {
}
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
throw new Error('Method not implemented.');
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 @@ -22,15 +22,15 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
}

async $openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined> {
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote.port, tunnelOptions.localPort, tunnelOptions.name);
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote, tunnelOptions.localPort, tunnelOptions.name);
if (tunnel) {
return TunnelDto.fromServiceTunnel(tunnel);
}
return undefined;
}

async $closeTunnel(remotePort: number): Promise<void> {
return this.remoteExplorerService.close(remotePort);
async $closeTunnel(remote: { host: string, port: number }): Promise<void> {
return this.remoteExplorerService.close(remote);
}

async $registerCandidateFinder(): Promise<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 @@ -774,7 +774,7 @@ export interface MainThreadWindowShape extends IDisposable {

export interface MainThreadTunnelServiceShape extends IDisposable {
$openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined>;
$closeTunnel(remotePort: number): Promise<void>;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
$registerCandidateFinder(): Promise<void>;
$setTunnelProvider(): Promise<void>;
}
Expand Down Expand Up @@ -1395,7 +1395,7 @@ export interface ExtHostStorageShape {


export interface ExtHostTunnelServiceShape {
$findCandidatePorts(): Promise<{ port: number, detail: string }[]>;
$findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>;
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
}
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHostTunnelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
async makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
return undefined;
}
async $findCandidatePorts(): Promise<{ port: number; detail: string; }[]> {
async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> {
return [];
}
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { return { dispose: () => { } }; }
Expand Down
10 changes: 5 additions & 5 deletions src/vs/workbench/api/node/extHostTunnelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
const tunnel = await this._proxy.$openTunnel(forward);
if (tunnel) {
const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => {
return this._proxy.$closeTunnel(tunnel.remote.port);
return this._proxy.$closeTunnel(tunnel.remote);
});
this._register(disposableTunnel);
return disposableTunnel;
Expand Down Expand Up @@ -95,7 +95,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
this._extensionTunnels.set(tunnelOptions.remote.host, new Map());
}
this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel);
this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote.port)));
this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote)));
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
});
}
Expand All @@ -104,12 +104,12 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}


async $findCandidatePorts(): Promise<{ port: number, detail: string }[]> {
async $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> {
if (!isLinux) {
return [];
}

const ports: { port: number, detail: string }[] = [];
const ports: { host: string, port: number, detail: string }[] = [];
const tcp: string = fs.readFileSync('/proc/net/tcp', 'utf8');
const tcp6: string = fs.readFileSync('/proc/net/tcp6', 'utf8');
const procSockets: string = await (new Promise(resolve => {
Expand Down Expand Up @@ -150,7 +150,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
const command = processMap[socketMap[socket].pid].cmd;
if (!command.match('.*\.vscode\-server\-[a-zA-Z]+\/bin.*') && (command.indexOf('out/vs/server/main.js') === -1)) {
ports.push({ port, detail: processMap[socketMap[socket].pid].cmd });
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd });
}
});

Expand Down
68 changes: 40 additions & 28 deletions src/vs/workbench/contrib/remote/browser/tunnelView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } from 'vs/platform/actions/common/actions';
import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IRemoteExplorerService, TunnelModel } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IRemoteExplorerService, TunnelModel, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
Expand Down Expand Up @@ -105,22 +105,23 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {

get forwarded(): TunnelItem[] {
return Array.from(this.model.forwarded.values()).map(tunnel => {
return new TunnelItem(TunnelType.Forwarded, tunnel.remote, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description);
return new TunnelItem(TunnelType.Forwarded, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description);
});
}

get detected(): TunnelItem[] {
return Array.from(this.model.detected.values()).map(tunnel => {
return new TunnelItem(TunnelType.Detected, tunnel.remote, tunnel.localAddress, false, tunnel.name, tunnel.description);
return new TunnelItem(TunnelType.Detected, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, false, tunnel.name, tunnel.description);
});
}

get candidates(): Promise<TunnelItem[]> {
return this.model.candidates.then(values => {
const candidates: TunnelItem[] = [];
values.forEach(value => {
if (!this.model.forwarded.has(value.port) && !this.model.detected.has(value.port)) {
candidates.push(new TunnelItem(TunnelType.Candidate, value.port, undefined, false, undefined, value.detail));
const key = MakeAddress(value.host, value.port);
if (!this.model.forwarded.has(key) && !this.model.detected.has(key)) {
candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, false, undefined, value.detail));
}
});
return candidates;
Expand Down Expand Up @@ -185,7 +186,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
}

private isTunnelItem(item: ITunnelGroup | ITunnelItem): item is ITunnelItem {
return !!((<ITunnelItem>item).remote);
return !!((<ITunnelItem>item).remotePort);
}

renderElement(element: ITreeNode<ITunnelGroup | ITunnelItem, ITunnelGroup | ITunnelItem>, index: number, templateData: ITunnelTemplateData): void {
Expand All @@ -196,15 +197,15 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
templateData.actionBar.clear();
let editableData: IEditableData | undefined;
if (this.isTunnelItem(node)) {
editableData = this.remoteExplorerService.getEditableData(node.remote);
editableData = this.remoteExplorerService.getEditableData(node.remoteHost, node.remotePort);
if (editableData) {
templateData.iconLabel.element.style.display = 'none';
this.renderInputBox(templateData.container, editableData);
} else {
templateData.iconLabel.element.style.display = 'flex';
this.renderTunnel(node, templateData);
}
} else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined))) {
} else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined, undefined))) {
templateData.iconLabel.element.style.display = 'none';
this.renderInputBox(templateData.container, editableData);
} else {
Expand Down Expand Up @@ -338,7 +339,8 @@ interface ITunnelGroup {

interface ITunnelItem {
tunnelType: TunnelType;
remote: number;
remoteHost: string;
remotePort: number;
localAddress?: string;
name?: string;
closeable?: boolean;
Expand All @@ -349,7 +351,8 @@ interface ITunnelItem {
class TunnelItem implements ITunnelItem {
constructor(
public tunnelType: TunnelType,
public remote: number,
public remoteHost: string,
public remotePort: number,
public localAddress?: string,
public closeable?: boolean,
public name?: string,
Expand All @@ -359,17 +362,17 @@ class TunnelItem implements ITunnelItem {
if (this.name) {
return nls.localize('remote.tunnelsView.forwardedPortLabel0', "{0}", this.name);
} else if (this.localAddress) {
return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remote, this.localAddress);
return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remotePort, this.localAddress);
} else {
return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remote);
return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remotePort);
}
}

get description(): string | undefined {
if (this._description) {
return this._description;
} else if (this.name) {
return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} to {1}", this.remote, this.localAddress);
return nls.localize('remote.tunnelsView.forwardedPortDescription0', "{0} to {1}", this.remotePort, this.localAddress);
}
return undefined;
}
Expand Down Expand Up @@ -474,7 +477,7 @@ export class TunnelPanel extends ViewPane {
}));

this._register(this.remoteExplorerService.onDidChangeEditable(async e => {
const isEditing = !!this.remoteExplorerService.getEditableData(e);
const isEditing = !!this.remoteExplorerService.getEditableData(e.host, e.port);

if (!isEditing) {
dom.removeClass(treeContainer, 'highlight');
Expand Down Expand Up @@ -575,12 +578,12 @@ namespace LabelTunnelAction {
return async (accessor, arg) => {
if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
remoteExplorerService.setEditable(arg.remote, {
remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, {
onFinish: (value, success) => {
if (success) {
remoteExplorerService.tunnelModel.name(arg.remote, value);
remoteExplorerService.tunnelModel.name(arg.remoteHost, arg.remotePort, value);
}
remoteExplorerService.setEditable(arg.remote, null);
remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, null);
},
validationMessage: () => null,
placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"),
Expand All @@ -596,24 +599,32 @@ namespace ForwardPortAction {
export const ID = 'remote.tunnel.forward';
export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port");

function parseInput(value: string): { host: string, port: number } | undefined {
const matches = value.match(/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\:|localhost:)?([0-9]+)$/);
if (!matches) {
return undefined;
}
return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) };
}

export function handler(): ICommandHandler {
return async (accessor, arg) => {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
if (arg instanceof TunnelItem) {
remoteExplorerService.tunnelModel.forward(arg.remote);
remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort });
} else {
const viewsService = accessor.get(IViewsService);
await viewsService.openView(TunnelPanel.ID, true);
remoteExplorerService.setEditable(undefined, {
remoteExplorerService.setEditable(undefined, undefined, {
onFinish: (value, success) => {
if (success) {
remoteExplorerService.tunnelModel.forward(Number(value));
let parsed: { host: string, port: number } | undefined;
if (success && (parsed = parseInput(value))) {
remoteExplorerService.forward({ host: parsed.host, port: parsed.port });
}
remoteExplorerService.setEditable(undefined, null);
remoteExplorerService.setEditable(undefined, undefined, null);
},
validationMessage: (value) => {
const asNumber = Number(value);
if ((value === '') || isNaN(asNumber) || (asNumber < 0) || (asNumber > 65535)) {
if (!parseInput(value)) {
return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid");
}
return null;
Expand All @@ -633,7 +644,7 @@ namespace ClosePortAction {
return async (accessor, arg) => {
if (arg instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
await remoteExplorerService.tunnelModel.close(arg.remote);
await remoteExplorerService.close({ host: arg.remoteHost, port: arg.remotePort });
}
};
}
Expand All @@ -648,9 +659,10 @@ namespace OpenPortInBrowserAction {
if (arg instanceof TunnelItem) {
const model = accessor.get(IRemoteExplorerService).tunnelModel;
const openerService = accessor.get(IOpenerService);
const tunnel = model.forwarded.has(arg.remote) ? model.forwarded.get(arg.remote) : model.detected.get(arg.remote);
const key = MakeAddress(arg.remoteHost, arg.remotePort);
const tunnel = model.forwarded.get(key) || model.detected.get(key);
let address: string | undefined;
if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remote))) {
if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remoteHost, tunnel.remotePort))) {
return openerService.open(URI.parse('http://' + address));
}
return Promise.resolve();
Expand All @@ -668,7 +680,7 @@ namespace CopyAddressAction {
if (arg instanceof TunnelItem) {
const model = accessor.get(IRemoteExplorerService).tunnelModel;
const clipboard = accessor.get(IClipboardService);
const address = model.address(arg.remote);
const address = model.address(arg.remoteHost, arg.remotePort);
if (address) {
await clipboard.writeText(address.toString());
}
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/webview/common/portMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class WebviewPortMappingManager extends Disposable {
if (existing) {
return existing;
}
const tunnel = this.tunnelService.openTunnel(remotePort);
const tunnel = this.tunnelService.openTunnel(undefined, remotePort);
if (tunnel) {
this._tunnels.set(remotePort, tunnel);
}
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/electron-browser/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ export class ElectronWindow extends Disposable {
if (options?.allowTunneling) {
const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri);
if (portMappingRequest) {
const tunnel = await this.tunnelService.openTunnel(portMappingRequest.port);
const tunnel = await this.tunnelService.openTunnel(undefined, portMappingRequest.port);
if (tunnel) {
return {
resolved: uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }),
Expand Down
Loading

0 comments on commit 8e2b304

Please sign in to comment.