remoteAgentConnection.ts ×53

Frontier kind: Code frontier

unlabeled · c_82725d7c29a9

6 tests · 79092 LOC · 274 files · introduces 0 tests · 823 LOC · 7 files

Introduces — evidence that enters the hierarchy at this concept

Code
149 ranges823 lines · 7 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
5397 ranges79092 lines · 274 files · Browse complete extent
All tests (intent)
6 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

7 files ranked by introduced lines: 823 introduced LOC across 149 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/services/remote/common/tunnelModel.ts 270 introduced LOC · 47 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- tunnelModel.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as nls from '../../../../nls.js';
7 > import { debounce } from '../../../../base/common/decorators.js';
8 > import { Emitter, Event } from '../../../../base/common/event.js';
9 > import { hash } from '../../../../base/common/hash.js';
10 > import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
11 > import { URI } from '../../../../base/common/uri.js';
12 > import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
13 > import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
14 > import { ILogService } from '../../../../platform/log/common/log.js';
15 > import { IAddressProvider } from '../../../../platform/remote/common/remoteAgentConnection.js';
16 > import { IRemoteAuthorityResolverService, TunnelDescription } from '../../../../platform/remote/common/remoteAuthorityResolver.js';
17 > import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
18 > import { RemoteTunnel, ITunnelService, TunnelProtocol, TunnelPrivacyId, LOCALHOST_ADDRESSES, ProvidedPortAttributes, PortAttributesProvider, isLocalhost, isAllInterfaces, ProvidedOnAutoForward, ALL_INTERFACES_ADDRESSES } from '../../../../platform/tunnel/common/tunnel.js';
19 > import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
20 > import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
21 > import { IExtensionService } from '../../extensions/common/extensions.js';
22 > import { CancellationToken } from '../../../../base/common/cancellation.js';
23 > import { isNumber, isObject, isString } from '../../../../base/common/types.js';
24 > import { deepClone } from '../../../../base/common/objects.js';
25 > import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
26 >
27 > const MISMATCH_LOCAL_PORT_COOLDOWN = 10 * 1000; // 10 seconds
28 > const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore';
29 > const TUNNELS_TO_RESTORE_EXPIRATION = 'remote.tunnels.toRestoreExpiration';
30 > const RESTORE_EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 14; // 2 weeks
31 > export const ACTIVATION_EVENT = 'onTunnel';
32 > export const forwardedPortsFeaturesEnabled = new RawContextKey<boolean>('forwardedPortsViewEnabled', false, nls.localize('tunnel.forwardedPortsViewEnabled', "Whether the Ports view is enabled."));
33 > export const forwardedPortsViewEnabled = new RawContextKey<boolean>('forwardedPortsViewOnlyEnabled', false, nls.localize('tunnel.forwardedPortsViewEnabled', "Whether the Ports view is enabled."));
34 >
35 > export interface RestorableTunnel {
36 > remoteHost: string;
37 > remotePort: number;
38 > localAddress: string;
39 > localUri: URI;
40 > protocol: TunnelProtocol;
41 > localPort?: number;
42 > name?: string;
43 > source: {
44 > source: TunnelSource;
45 > description: string;
46 > };
47 > }
48 >
49 > export interface Tunnel {
50 > remoteHost: string;
51 > remotePort: number;
52 > localAddress: string;
53 > localUri: URI;
54 > protocol: TunnelProtocol;
55 > localPort?: number;
56 > name?: string;
57 > closeable?: boolean;
58 > privacy: TunnelPrivacyId | string;
59 > runningProcess: string | undefined;
60 > hasRunningProcess?: boolean;
61 > pid: number | undefined;
62 > source: {
63 > source: TunnelSource;
64 > description: string;
65 > };
66 > }
67 >
68 > export function parseAddress(address: string): { host: string; port: number } | undefined {
69 const matches = address.match(/^([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*:)?([0-9]+)$/);
70 if (!matches) {
73 return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) };
74 }
76 > export enum TunnelCloseReason {
77 > Other = 'Other',
78 > User = 'User',
79 > AutoForwardEnd = 'AutoForwardEnd',
80 > }
81 >
82 > export enum TunnelSource {
83 > User,
84 > Auto,
85 > Extension
86 > }
87 >
88 > export const UserTunnelSource = {
89 > source: TunnelSource.User,
90 > description: nls.localize('tunnel.source.user', "User Forwarded")
91 > };
92 > export const AutoTunnelSource = {
93 > source: TunnelSource.Auto,
94 > description: nls.localize('tunnel.source.auto', "Auto Forwarded")
95 > };
96 >
97 > export function mapHasAddress<T>(map: Map<string, T>, host: string, port: number): T | undefined {
98 const initialAddress = map.get(makeAddress(host, port));
99 if (initialAddress) {
121 return undefined;
122 }
124 > export function mapHasAddressLocalhostOrAllInterfaces<T>(map: Map<string, T>, host: string, port: number): T | undefined {
125 const originalAddress = mapHasAddress(map, host, port);
126 if (originalAddress) {
133 return undefined;
134 }
136 >
137 > export function makeAddress(host: string, port: number): string {
138 return host + ':' + port;
139 }
141 > export interface TunnelProperties {
142 > remote: { host: string; port: number };
143 > local?: number;
144 > name?: string;
145 > source?: {
146 > source: TunnelSource;
147 > description: string;
148 > };
149 > elevateIfNeeded?: boolean;
150 > privacy?: string;
151 > }
152 >
153 > export interface CandidatePort {
154 > host: string;
155 > port: number;
156 > detail?: string;
157 > pid?: number;
158 > }
159 >
160 > interface PortAttributes extends Attributes {
161 > key: number | PortRange | RegExp | HostAndPort;
162 > }
163 >
164 > export enum OnPortForward {
165 > Notify = 'notify',
166 > OpenBrowser = 'openBrowser',
167 > OpenBrowserOnce = 'openBrowserOnce',
168 > OpenPreview = 'openPreview',
169 > Silent = 'silent',
170 > Ignore = 'ignore'
171 > }
172 >
173 > export interface Attributes {
174 > label: string | undefined;
175 > onAutoForward: OnPortForward | undefined;
176 > elevateIfNeeded: boolean | undefined;
177 > requireLocalPort: boolean | undefined;
178 > protocol: TunnelProtocol | undefined;
179 > }
180 >
181 > interface PortRange { start: number; end: number }
182 >
183 > interface HostAndPort { host: string; port: number }
184 >
185 > export function isCandidatePort(candidate: any): candidate is CandidatePort {
186 return candidate && 'host' in candidate && typeof candidate.host === 'string'
187 && 'port' in candidate && typeof candidate.port === 'number'
189 && (!('pid' in candidate) || typeof candidate.pid === 'string');
190 }
192 > export class PortsAttributes extends Disposable {
193 > private static SETTING = 'remote.portsAttributes';
194 > private static DEFAULTS = 'remote.otherPortsAttributes';
195 > private static RANGE = /^(\d+)\-(\d+)$/;
196 > private static HOST_AND_PORT = /^([a-z0-9\-]+):(\d{1,5})$/;
197 > private portsAttributes: PortAttributes[] = [];
198 > private defaultPortAttributes: Attributes | undefined;
199 > private _onDidChangeAttributes = this._register(new Emitter<void>());
200 > public readonly onDidChangeAttributes = this._onDidChangeAttributes.event;
201 >
202 > constructor(private readonly configurationService: IConfigurationService) {
203 super();
204 this._register(configurationService.onDidChangeConfiguration(e => {
209 this.updateAttributes();
210 }
212 > private updateAttributes() {
213 this.portsAttributes = this.readSetting();
214 this._onDidChangeAttributes.fire();
215 }
217 > getAttributes(port: number, host: string, commandLine?: string): Attributes | undefined {
218 let index = this.findNextIndex(port, host, commandLine, this.portsAttributes, 0);
219 const attributes: Attributes = {
251 return this.getOtherAttributes();
252 }
254 > private hasStartEnd(value: number | PortRange | RegExp | HostAndPort): value is PortRange {
255 return (value as Partial<PortRange>).start !== undefined && (value as Partial<PortRange>).end !== undefined;
256 }
258 > private hasHostAndPort(value: number | PortRange | RegExp | HostAndPort): value is HostAndPort {
259 return ((value as Partial<HostAndPort>).host !== undefined) && ((value as Partial<HostAndPort>).port !== undefined)
260 && isString((value as Partial<HostAndPort>).host) && isNumber((value as Partial<HostAndPort>).port);
261 }
263 > private findNextIndex(port: number, host: string, commandLine: string | undefined, attributes: PortAttributes[], fromIndex: number): number {
264 if (fromIndex >= attributes.length) {
265 return -1;
281 return foundIndex >= 0 ? foundIndex + fromIndex : -1;
282 }
284 > private readSetting(): PortAttributes[] {
285 const settingValue = this.configurationService.getValue(PortsAttributes.SETTING);
286 if (!settingValue || !isObject(settingValue)) {
342 return this.sortAttributes(attributes);
343 }
345 > private sortAttributes(attributes: PortAttributes[]): PortAttributes[] {
346 function getVal(item: PortAttributes, thisRef: PortsAttributes) {
347 if (isNumber(item.key)) {
360 });
361 }
363 > private getOtherAttributes() {
364 return this.defaultPortAttributes;
365 }
367 > static providedActionToAction(providedAction: ProvidedOnAutoForward | undefined) {
368 switch (providedAction) {
369 case ProvidedOnAutoForward.Notify: return OnPortForward.Notify;
376 }
377 }
379 > public async addAttributes(port: number, attributes: Partial<Attributes>, target: ConfigurationTarget) {
380 const settingValue = this.configurationService.inspect(PortsAttributes.SETTING);
381 const remoteValue: any = settingValue.userRemoteValue;
396 return this.configurationService.updateValue(PortsAttributes.SETTING, newRemoteValue, target);
397 }
398 > } tunnelModel.ts
399 >
400 > export class TunnelModel extends Disposable {
401 > readonly forwarded: Map<string, Tunnel>;
402 > private readonly inProgress: Map<string, true> = new Map();
403 > readonly detected: Map<string, Tunnel>;
404 > private remoteTunnels: Map<string, RemoteTunnel>;
405 > private _onForwardPort = this._register(new Emitter<Tunnel | void>());
406 > public onForwardPort = this._onForwardPort.event;
407 > private _onClosePort = this._register(new Emitter<{ host: string; port: number }>());
408 > public onClosePort = this._onClosePort.event;
409 > private _onPortName = this._register(new Emitter<{ host: string; port: number }>());
410 > public onPortName = this._onPortName.event;
411 > private _candidates: Map<string, CandidatePort> | undefined;
412 > private _onCandidatesChanged = this._register(new Emitter<Map<string, { host: string; port: number }>>());
413 > // onCandidateChanged returns the removed candidates
414 > public onCandidatesChanged = this._onCandidatesChanged.event;
415 > private _candidateFilter: ((candidates: CandidatePort[]) => Promise<CandidatePort[]>) | undefined;
416 > private tunnelRestoreValue: Promise<string | undefined>;
417 > private _onEnvironmentTunnelsSet = this._register(new Emitter<void>());
418 > public onEnvironmentTunnelsSet = this._onEnvironmentTunnelsSet.event;
419 > private _environmentTunnelsSet: boolean = false;
420 > public readonly configPortsAttributes: PortsAttributes;
421 > private restoreListener: DisposableStore | undefined = undefined;
422 > private knownPortsRestoreValue: string | undefined;
423 > private restoreComplete = false;
424 > private onRestoreComplete = this._register(new Emitter<void>());
425 > private unrestoredExtensionTunnels: Map<string, RestorableTunnel> = new Map();
426 > private sessionCachedProperties: Map<string, Partial<TunnelProperties>> = new Map();
427 >
428 > private portAttributesProviders: PortAttributesProvider[] = [];
429 >
430 > constructor(
431 @ITunnelService private readonly tunnelService: ITunnelService,
432 @IStorageService private readonly storageService: IStorageService,
508 this.checkExtensionActivationEvents(false);
509 }
511 > private extensionHasActivationEvent() {
512 if (this.extensionService.extensions.find(extension => extension.activationEvents?.includes(ACTIVATION_EVENT))) {
513 this.contextKeyService.createKey(forwardedPortsViewEnabled.key, true);
516 return false;
517 }
519 > private hasCheckedExtensionsOnTunnelOpened = false;
520 > private checkExtensionActivationEvents(tunnelOpened: boolean) {
521 if (this.hasCheckedExtensionsOnTunnelOpened) {
522 return;
540 }));
541 }
543 > private async onTunnelClosed(address: { host: string; port: number }, reason: TunnelCloseReason) {
544 const key = makeAddress(address.host, address.port);
545 if (this.forwarded.delete(key)) {
548 }
549 }
551 > private makeLocalUri(localAddress: string, attributes?: Attributes) {
552 if (localAddress.startsWith('http')) {
553 return URI.parse(localAddress);
556 return URI.parse(`${protocol}://${localAddress}`);
557 }
559 > private async addStorageKeyPostfix(prefix: string): Promise<string | undefined> {
560 const workspace = this.workspaceContextService.getWorkspace();
561 const workspaceHash = workspace.configuration ? hash(workspace.configuration.path) : (workspace.folders.length > 0 ? hash(workspace.folders[0].uri.path) : undefined);
566 return `${prefix}.${this.environmentService.remoteAuthority}.${workspaceHash}`;
567 }
569 > private async getTunnelRestoreStorageKey(): Promise<string | undefined> {
570 return this.addStorageKeyPostfix(TUNNELS_TO_RESTORE);
571 }
573 > private async getRestoreExpirationStorageKey(): Promise<string | undefined> {
574 return this.addStorageKeyPostfix(TUNNELS_TO_RESTORE_EXPIRATION);
575 }
577 > private async getTunnelRestoreValue(): Promise<string | undefined> {
578 const deprecatedValue = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE);
579 if (deprecatedValue) {
588 return this.storageService.get(storageKey, StorageScope.PROFILE);
589 }
591 > async restoreForwarded() {
592 this.cleanupExpiredTunnelsForRestore();
593 if (this.configurationService.getValue('remote.restoreForwardedPorts')) {
629 }
630 }
632 > private cleanupExpiredTunnelsForRestore() {
633 const keys = this.storageService.keys(StorageScope.PROFILE, StorageTarget.USER).filter(key => key.startsWith(TUNNELS_TO_RESTORE_EXPIRATION));
634 for (const key of keys) {
642 }
643 }
645 > @debounce(1000)
646 > private async storeForwarded() {
647 if (this.configurationService.getValue('remote.restoreForwardedPorts')) {
648 const forwarded = Array.from(this.forwarded.values());
676 }
677 }
679 > private mismatchCooldown = new Date();
680 > private async showPortMismatchModalIfNeeded(tunnel: RemoteTunnel, expectedLocal: number, attributes: Attributes | undefined) {
681 if (!tunnel.tunnelLocalPort || !attributes?.requireLocalPort) {
682 return;
695 return this.dialogService.info(mismatchString);
696 }
698 > async forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise<RemoteTunnel | string | undefined> {
699 if (!this.restoreComplete && this.environmentService.remoteAuthority) {
700 await Event.toPromise(this.onRestoreComplete.event);
702 return this.doForward(tunnelProperties, attributes);
703 }
705 > private async doForward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise<RemoteTunnel | string | undefined> {
706 await this.extensionService.activateByEvent(ACTIVATION_EVENT);
707
762 return noTunnelValue;
763 }
765 > private mergeCachedAndUnrestoredProperties(key: string, tunnelProperties: TunnelProperties): TunnelProperties {
766 const map = this.unrestoredExtensionTunnels.has(key) ? this.unrestoredExtensionTunnels : (this.sessionCachedProperties.has(key) ? this.sessionCachedProperties : undefined);
767 if (map) {
776 return tunnelProperties;
777 }
779 > private async mergeAttributesIntoExistingTunnel(existingTunnel: Tunnel, tunnelProperties: TunnelProperties, attributes: Attributes | undefined) {
780 const newName = attributes?.label ?? tunnelProperties.name;
781 enum MergedAttributeAction {
811 return mapHasAddressLocalhostOrAllInterfaces(this.remoteTunnels, tunnelProperties.remote.host, tunnelProperties.remote.port);
812 }
814 > async name(host: string, port: number, name: string) {
815 const existingForwarded = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, host, port);
816 const key = makeAddress(host, port);
825 }
826 }
828 > async close(host: string, port: number, reason: TunnelCloseReason): Promise<void> {
829 const key = makeAddress(host, port);
830 const oldTunnel = this.forwarded.get(key)!;
839 return this.onTunnelClosed({ host, port }, reason);
840 }
842 > address(host: string, port: number): string | undefined {
843 const key = makeAddress(host, port);
844 return (this.forwarded.get(key) || this.detected.get(key))?.localAddress;
845 }
847 > public get environmentTunnelsSet(): boolean {
848 return this._environmentTunnelsSet;
849 }
851 > addEnvironmentTunnels(tunnels: TunnelDescription[] | undefined): void {
852 if (tunnels) {
853 for (const tunnel of tunnels) {
877 this._onForwardPort.fire();
878 }
880 > setCandidateFilter(filter: ((candidates: CandidatePort[]) => Promise<CandidatePort[]>) | undefined): void {
881 this._candidateFilter = filter;
882 }
884 > async setCandidates(candidates: CandidatePort[]) {
885 let processedCandidates = candidates;
886 if (this._candidateFilter) {
893 this._onCandidatesChanged.fire(removedCandidates);
894 }
896 > // Returns removed candidates
897 > private updateInResponseToCandidates(candidates: CandidatePort[]): Map<string, { host: string; port: number }> {
898 const removedCandidates = this._candidates ?? new Map();
899 const candidatesMap = new Map();
935 return removedCandidates;
936 }
938 > get candidates(): CandidatePort[] {
939 return this._candidates ? Array.from(this._candidates.values()) : [];
940 }
942 > get candidatesOrUndefined(): CandidatePort[] | undefined {
943 return this._candidates ? this.candidates : undefined;
944 }
946 > private async updateAttributes() {
947 // If the label changes in the attributes, we should update it.
948 const tunnels = Array.from(this.forwarded.values());
973 }
974 }
976 > async getAttributes(forwardedPorts: { host: string; port: number }[], checkProviders: boolean = true): Promise<Map<number, Attributes> | undefined> {
977 const matchingCandidates: Map<number, CandidatePort> = new Map();
978 const pidToPortsMapping: Map<number | undefined, number[]> = new Map();
1036 return mergedAttributes;
1037 }
1039 > addAttributesProvider(provider: PortAttributesProvider) {
1040 this.portAttributesProviders.push(provider);
1041 }
1042 > } tunnelModel.ts
src/vs/platform/remote/common/remoteAgentConnection.ts 265 introduced LOC · 53 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- remoteAgentConnection.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancelablePromise, createCancelablePromise, promiseWithResolvers } from '../../../base/common/async.js';
7 > import { VSBuffer } from '../../../base/common/buffer.js';
8 > import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
9 > import { isCancellationError, onUnexpectedError } from '../../../base/common/errors.js';
10 > import { Emitter } from '../../../base/common/event.js';
11 > import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
12 > import { RemoteAuthorities } from '../../../base/common/network.js';
13 > import * as performance from '../../../base/common/performance.js';
14 > import { StopWatch } from '../../../base/common/stopwatch.js';
15 > import { generateUuid } from '../../../base/common/uuid.js';
16 > import { IIPCLogger } from '../../../base/parts/ipc/common/ipc.js';
17 > import { Client, ISocket, PersistentProtocol, ProtocolConstants, SocketCloseEventType } from '../../../base/parts/ipc/common/ipc.net.js';
18 > import { ILogService } from '../../log/common/log.js';
19 > import { RemoteAgentConnectionContext } from './remoteAgentEnvironment.js';
20 > import { RemoteAuthorityResolverError, RemoteConnection } from './remoteAuthorityResolver.js';
21 > import { IRemoteSocketFactoryService } from './remoteSocketFactoryService.js';
22 > import { ISignService } from '../../sign/common/sign.js';
23 >
24 > const RECONNECT_TIMEOUT = 30 * 1000 /* 30s */;
25 >
26 > export const enum ConnectionType {
27 > Management = 1,
28 > ExtensionHost = 2,
29 > Tunnel = 3,
30 > }
31 >
32 function connectionTypeToString(connectionType: ConnectionType): string {
33 switch (connectionType) {
40 }
41 }
43 > export interface AuthRequest {
44 > type: 'auth';
45 > auth: string;
46 > data: string;
47 > }
48 >
49 > export interface SignRequest {
50 > type: 'sign';
51 > data: string;
52 > signedData: string;
53 > }
54 >
55 > export interface ConnectionTypeRequest {
56 > type: 'connectionType';
57 > commit?: string;
58 > signedData: string;
59 > desiredConnectionType?: ConnectionType;
60 > args?: any;
61 > }
62 >
63 > export interface ErrorMessage {
64 > type: 'error';
65 > reason: string;
66 > }
67 >
68 > export interface OKMessage {
69 > type: 'ok';
70 > }
71 >
72 > export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage;
73 >
74 >
75 > interface ISimpleConnectionOptions<T extends RemoteConnection = RemoteConnection> {
76 > commit: string | undefined;
77 > quality: string | undefined;
78 > connectTo: T;
79 > connectionToken: string | undefined;
80 > reconnectionToken: string;
81 > reconnectionProtocol: PersistentProtocol | null;
82 > remoteSocketFactoryService: IRemoteSocketFactoryService;
83 > signService: ISignService;
84 > logService: ILogService;
85 > }
86 >
87 function createTimeoutCancellation(millis: number): CancellationToken {
88 const source = new CancellationTokenSource();
90 return source.token;
91 }
93 function combineTimeoutCancellation(a: CancellationToken, b: CancellationToken): CancellationToken {
94 if (a.isCancellationRequested || b.isCancellationRequested) {
100 return source.token;
101 }
103 > class PromiseWithTimeout<T> {
104 >
105 > private _state: 'pending' | 'resolved' | 'rejected' | 'timedout';
106 > private readonly _disposables: DisposableStore;
107 > public readonly promise: Promise<T>;
108 > private readonly _resolvePromise: (value: T) => void;
109 > private readonly _rejectPromise: (err: any) => void;
110 >
111 > public get didTimeout(): boolean {
112 return (this._state === 'timedout');
113 }
115 > constructor(timeoutCancellationToken: CancellationToken) {
116 this._state = 'pending';
117 this._disposables = new DisposableStore();
125 }
126 }
128 > public registerDisposable(disposable: IDisposable): void {
129 if (this._state === 'pending') {
130 this._disposables.add(disposable);
133 }
134 }
136 > private _timeout(): void {
137 if (this._state !== 'pending') {
138 return;
142 this._rejectPromise(this._createTimeoutError());
143 }
145 > private _createTimeoutError(): Error {
146 const err: any = new Error('Time limit reached');
147 err.code = 'ETIMEDOUT';
149 return err;
150 }
152 > public resolve(value: T): void {
153 if (this._state !== 'pending') {
154 return;
158 this._resolvePromise(value);
159 }
161 > public reject(err: any): void {
162 if (this._state !== 'pending') {
163 return;
167 this._rejectPromise(err);
168 }
170 >
171 function readOneControlMessage<T>(protocol: PersistentProtocol, timeoutCancellationToken: CancellationToken): Promise<T> {
172 const result = new PromiseWithTimeout<T>(timeoutCancellationToken);
182 return result.promise;
183 }
185 function createSocket<T extends RemoteConnection>(logService: ILogService, remoteSocketFactoryService: IRemoteSocketFactoryService, connectTo: T, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
186 const result = new PromiseWithTimeout<ISocket>(timeoutCancellationToken);
208 return result.promise;
209 }
211 function raceWithTimeoutCancellation<T>(promise: Promise<T>, timeoutCancellationToken: CancellationToken): Promise<T> {
212 const result = new PromiseWithTimeout<T>(timeoutCancellationToken);
225 return result.promise;
226 }
228 async function connectToRemoteExtensionHostAgent<T extends RemoteConnection>(options: ISimpleConnectionOptions<T>, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean }> {
229 const logPrefix = connectLogPrefix(options, connectionType);
312 }
313 }
315 > interface IManagementConnectionResult {
316 > protocol: PersistentProtocol;
317 > }
318 >
319 async function connectToRemoteExtensionHostAgentAndReadOneMessage<T>(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; firstMessage: T }> {
320 const startTime = Date.now();
340 return result.promise;
341 }
343 async function doConnectRemoteAgentManagement(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise<IManagementConnectionResult> {
344 const { protocol } = await connectToRemoteExtensionHostAgentAndReadOneMessage(options, ConnectionType.Management, undefined, timeoutCancellationToken);
345 return { protocol };
346 }
348 > export interface IRemoteExtensionHostStartParams {
349 > language: string;
350 > debugId?: string;
351 > break?: boolean;
352 > port?: number | null;
353 > env?: { [key: string]: string | null };
354 > }
355 >
356 > interface IExtensionHostConnectionResult {
357 > protocol: PersistentProtocol;
358 > debugPort?: number;
359 > }
360 >
361 async function doConnectRemoteAgentExtensionHost(options: ISimpleConnectionOptions, startArguments: IRemoteExtensionHostStartParams, timeoutCancellationToken: CancellationToken): Promise<IExtensionHostConnectionResult> {
362 const { protocol, firstMessage } = await connectToRemoteExtensionHostAgentAndReadOneMessage<{ debugPort?: number }>(options, ConnectionType.ExtensionHost, startArguments, timeoutCancellationToken);
364 return { protocol, debugPort };
365 }
367 > export interface ITunnelConnectionStartParams {
368 > host: string;
369 > port: number;
370 > }
371 >
372 async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, startParams: ITunnelConnectionStartParams, timeoutCancellationToken: CancellationToken): Promise<PersistentProtocol> {
373 const startTime = Date.now();
377 return protocol;
378 }
380 > export interface IConnectionOptions<T extends RemoteConnection = RemoteConnection> {
381 > commit: string | undefined;
382 > quality: string | undefined;
383 > addressProvider: IAddressProvider<T>;
384 > remoteSocketFactoryService: IRemoteSocketFactoryService;
385 > signService: ISignService;
386 > logService: ILogService;
387 > ipcLogger: IIPCLogger | null;
388 > }
389 >
390 async function resolveConnectionOptions<T extends RemoteConnection>(options: IConnectionOptions<T>, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise<ISimpleConnectionOptions<T>> {
391 const { connectTo, connectionToken } = await options.addressProvider.getAddress();
402 };
403 }
405 > export interface IAddress<T extends RemoteConnection = RemoteConnection> {
406 > connectTo: T;
407 > connectionToken: string | undefined;
408 > }
409 >
410 > export interface IAddressProvider<T extends RemoteConnection = RemoteConnection> {
411 > getAddress(): Promise<IAddress<T>>;
412 > }
413 >
414 export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise<ManagementPersistentConnection> {
415 return createInitialConnection(
421 );
422 }
424 export async function connectRemoteAgentExtensionHost(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise<ExtensionHostPersistentConnection> {
425 return createInitialConnection(
431 );
432 }
434 > /**
435 > * Will attempt to connect 5 times. If it fails 5 consecutive times, it will give up.
436 > */
437 async function createInitialConnection<T extends PersistentConnection, O extends RemoteConnection>(options: IConnectionOptions<O>, connectionFactory: (simpleOptions: ISimpleConnectionOptions<O>) => Promise<T>): Promise<T> {
438 const MAX_ATTEMPTS = 5;
457 }
458 }
460 export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number): Promise<PersistentProtocol> {
461 const simpleOptions = await resolveConnectionOptions(options, generateUuid(), null);
463 return protocol;
464 }
466 function sleep(seconds: number): CancelablePromise<void> {
467 return createCancelablePromise(token => {
475 });
476 }
478 > export const enum PersistentConnectionEventType {
479 > ConnectionLost,
480 > ReconnectionWait,
481 > ReconnectionRunning,
482 > ReconnectionPermanentFailure,
483 > ConnectionGain
484 > }
485 > export class ConnectionLostEvent {
486 > public readonly type = PersistentConnectionEventType.ConnectionLost;
487 > constructor(
488 public readonly reconnectionToken: string,
489 public readonly millisSinceLastIncomingData: number
490 ) { }
492 > export class ReconnectionWaitEvent {
493 > public readonly type = PersistentConnectionEventType.ReconnectionWait;
494 > constructor(
495 public readonly reconnectionToken: string,
496 public readonly millisSinceLastIncomingData: number,
498 private readonly cancellableTimer: CancelablePromise<void>
499 ) { }
501 > public skipWait(): void {
502 this.cancellableTimer.cancel();
503 }
505 > export class ReconnectionRunningEvent {
506 > public readonly type = PersistentConnectionEventType.ReconnectionRunning;
507 > constructor(
508 public readonly reconnectionToken: string,
509 public readonly millisSinceLastIncomingData: number,
510 public readonly attempt: number
511 ) { }
513 > export class ConnectionGainEvent {
514 > public readonly type = PersistentConnectionEventType.ConnectionGain;
515 > constructor(
516 public readonly reconnectionToken: string,
517 public readonly millisSinceLastIncomingData: number,
518 public readonly attempt: number
519 ) { }
521 > export class ReconnectionPermanentFailureEvent {
522 > public readonly type = PersistentConnectionEventType.ReconnectionPermanentFailure;
523 > constructor(
524 public readonly reconnectionToken: string,
525 public readonly millisSinceLastIncomingData: number,
527 public readonly handled: boolean
528 ) { }
530 > export type PersistentConnectionEvent = ConnectionGainEvent | ConnectionLostEvent | ReconnectionWaitEvent | ReconnectionRunningEvent | ReconnectionPermanentFailureEvent;
531 >
532 > export abstract class PersistentConnection extends Disposable {
533 >
534 > public static triggerPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void {
535 > this._permanentFailure = true;
536 > this._permanentFailureMillisSinceLastIncomingData = millisSinceLastIncomingData;
537 > this._permanentFailureAttempt = attempt;
538 > this._permanentFailureHandled = handled;
539 > this._instances.forEach(instance => instance._gotoPermanentFailure(this._permanentFailureMillisSinceLastIncomingData, this._permanentFailureAttempt, this._permanentFailureHandled));
540 > }
541 >
542 > public static debugTriggerReconnection() {
543 this._instances.forEach(instance => instance._beginReconnecting());
544 }
546 > public static debugPauseSocketWriting() {
547 this._instances.forEach(instance => instance._pauseSocketWriting());
548 }
550 > private static _permanentFailure: boolean = false;
551 > private static _permanentFailureMillisSinceLastIncomingData: number = 0;
552 > private static _permanentFailureAttempt: number = 0;
553 > private static _permanentFailureHandled: boolean = false;
554 > private static _instances: PersistentConnection[] = [];
555 >
556 > private readonly _onDidStateChange = this._register(new Emitter<PersistentConnectionEvent>());
557 > public readonly onDidStateChange = this._onDidStateChange.event;
558 >
559 > private _permanentFailure: boolean = false;
560 > private get _isPermanentFailure(): boolean {
561 return this._permanentFailure || PersistentConnection._permanentFailure;
562 }
564 > private _isReconnecting: boolean = false;
565 > private _isDisposed: boolean = false;
566 > private _reconnectionGraceTime: number = ProtocolConstants.ReconnectionGraceTime;
567 >
568 > constructor(
569 private readonly _connectionType: ConnectionType,
570 protected readonly _options: IConnectionOptions,
613 }
614 }
616 > public updateGraceTime(graceTime: number): void {
617 const sanitizedGrace = sanitizeGraceTime(graceTime, ProtocolConstants.ReconnectionGraceTime);
618 const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, false);
620 this._reconnectionGraceTime = sanitizedGrace;
621 }
623 > public override dispose(): void {
624 super.dispose();
625 this._isDisposed = true;
626 }
628 > private async _beginReconnecting(): Promise<void> {
629 // Only have one reconnection loop active at a time.
630 if (this._isReconnecting) {
638 }
639 }
641 > private async _runReconnectingLoop(): Promise<void> {
642 if (this._isPermanentFailure || this._isDisposed) {
643 // no more attempts!
731 } while (!this._isPermanentFailure && !this._isDisposed);
732 }
734 > private _onReconnectionPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void {
735 if (this._reconnectionFailureIsFatal) {
736 PersistentConnection.triggerPermanentFailure(millisSinceLastIncomingData, attempt, handled);
739 }
740 }
742 > private _gotoPermanentFailure(millisSinceLastIncomingData: number, attempt: number, handled: boolean): void {
743 this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent(this.reconnectionToken, millisSinceLastIncomingData, attempt, handled));
744 safeDisposeProtocolAndSocket(this.protocol);
745 }
747 > private _pauseSocketWriting(): void {
748 this.protocol.pauseSocketWriting();
749 }
751 > protected abstract _reconnect(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise<void>;
752 > }
753 >
754 > export class ManagementPersistentConnection extends PersistentConnection {
755 >
756 > public readonly client: Client<RemoteAgentConnectionContext>;
757 >
758 > constructor(options: IConnectionOptions, remoteAuthority: string, clientId: string, reconnectionToken: string, protocol: PersistentProtocol) {
759 super(ConnectionType.Management, options, reconnectionToken, protocol, /*reconnectionFailureIsFatal*/true);
760 this.client = this._register(new Client<RemoteAgentConnectionContext>(protocol, {
763 }, options.ipcLogger));
764 }
766 > protected async _reconnect(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise<void> {
767 await doConnectRemoteAgentManagement(options, timeoutCancellationToken);
768 }
770 >
771 > export class ExtensionHostPersistentConnection extends PersistentConnection {
772 >
773 > private readonly _startArguments: IRemoteExtensionHostStartParams;
774 > public readonly debugPort: number | undefined;
775 >
776 > constructor(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams, reconnectionToken: string, protocol: PersistentProtocol, debugPort: number | undefined) {
777 super(ConnectionType.ExtensionHost, options, reconnectionToken, protocol, /*reconnectionFailureIsFatal*/false);
778 this._startArguments = startArguments;
779 this.debugPort = debugPort;
780 }
782 > protected async _reconnect(options: ISimpleConnectionOptions, timeoutCancellationToken: CancellationToken): Promise<void> {
783 await doConnectRemoteAgentExtensionHost(options, this._startArguments, timeoutCancellationToken);
784 }
786 >
787 function safeDisposeProtocolAndSocket(protocol: PersistentProtocol): void {
788 try {
795 }
796 }
798 function getErrorFromMessage(msg: any): Error | null {
799 if (msg && msg.type === 'error') {
805 return null;
806 }
808 function sanitizeGraceTime(candidate: number, fallback: number): number {
809 if (typeof candidate !== 'number' || !isFinite(candidate) || candidate < 0) {
815 return Math.floor(candidate);
816 }
818 function stringRightPad(str: string, len: number): string {
819 while (str.length < len) {
822 return str;
823 }
825 function _commonLogPrefix(connectionType: ConnectionType, reconnectionToken: string): string {
826 return `[remote-connection][${stringRightPad(connectionTypeToString(connectionType), 13)}][${reconnectionToken.substr(0, 5)}…]`;
827 }
829 function commonLogPrefix(connectionType: ConnectionType, reconnectionToken: string, isReconnect: boolean): string {
830 return `${_commonLogPrefix(connectionType, reconnectionToken)}[${isReconnect ? 'reconnect' : 'initial'}]`;
831 }
833 function connectLogPrefix(options: ISimpleConnectionOptions, connectionType: ConnectionType): string {
834 return `${commonLogPrefix(connectionType, options.reconnectionToken, !!options.reconnectionProtocol)}[${options.connectTo}]`;
835 }
837 function logElapsed(startTime: number): string {
838 return `${Date.now() - startTime} ms`;
src/vs/workbench/api/node/extHostTunnelService.ts 95 introduced LOC · 20 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extHostTunnelService.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as fs from 'fs';
7 > import { exec } from 'child_process';
8 > import { VSBuffer } from '../../../base/common/buffer.js';
9 > import { Emitter } from '../../../base/common/event.js';
10 > import { DisposableStore } from '../../../base/common/lifecycle.js';
11 > import { MovingAverage } from '../../../base/common/numbers.js';
12 > import { isLinux } from '../../../base/common/platform.js';
13 > import * as resources from '../../../base/common/resources.js';
14 > import { URI } from '../../../base/common/uri.js';
15 > import * as pfs from '../../../base/node/pfs.js';
16 > import { ISocket, SocketCloseEventType } from '../../../base/parts/ipc/common/ipc.net.js';
17 > import { ILogService } from '../../../platform/log/common/log.js';
18 > import { ManagedSocket, RemoteSocketHalf, connectManagedSocket } from '../../../platform/remote/common/managedSocket.js';
19 > import { ManagedRemoteConnection } from '../../../platform/remote/common/remoteAuthorityResolver.js';
20 > import { ISignService } from '../../../platform/sign/common/sign.js';
21 > import { isAllInterfaces, isLocalhost } from '../../../platform/tunnel/common/tunnel.js';
22 > import { NodeRemoteTunnel } from '../../../platform/tunnel/node/tunnelService.js';
23 > import { IExtHostInitDataService } from '../common/extHostInitDataService.js';
24 > import { IExtHostRpcService } from '../common/extHostRpcService.js';
25 > import { ExtHostTunnelService } from '../common/extHostTunnelService.js';
26 > import { CandidatePort, parseAddress } from '../../services/remote/common/tunnelModel.js';
27 > import * as vscode from 'vscode';
28 > import { IExtHostConfiguration } from '../common/extHostConfiguration.js';
29 >
30 > export function getSockets(stdout: string): Record<string, { pid: number; socket: number }> {
31 const lines = stdout.trim().split('\n');
32 const mapped: { pid: number; socket: number }[] = [];
46 return socketMap;
47 }
49 > export function loadListeningPorts(...stdouts: string[]): { socket: number; ip: string; port: number }[] {
50 const table = ([] as Record<string, string>[]).concat(...stdouts.map(loadConnectionTable));
51 return [
63 ];
64 }
66 > export function parseIpAddress(hex: string): string {
67 let result = '';
68 if (hex.length === 8) {
94 return result;
95 }
97 > export function loadConnectionTable(stdout: string): Record<string, string>[] {
98 const lines = stdout.trim().split('\n');
99 const names = lines.shift()!.trim().split(/\s+/)
105 return table;
106 }
108 function knownExcludeCmdline(command: string): boolean {
109 if (command.length > 500) {
114 || (command.indexOf('_productName=VSCode') !== -1);
115 }
117 > export function getRootProcesses(stdout: string) {
118 const lines = stdout.trim().split('\n');
119 const mapped: { pid: number; cmd: string; ppid: number }[] = [];
130 return mapped;
131 }
133 export async function findPorts(connections: { socket: number; ip: string; port: number }[], socketMap: Record<string, { pid: number; socket: number }>, processes: { pid: number; cwd: string; cmd: string }[]): Promise<CandidatePort[]> {
134 const processMap = processes.reduce((m: Record<string, typeof processes[0]>, process) => {
147 return ports;
148 }
150 > export function tryFindRootPorts(connections: { socket: number; ip: string; port: number }[], rootProcessesStdout: string, previousPorts: Map<number, CandidatePort & { ppid: number }>): Map<number, CandidatePort & { ppid: number }> {
151 const ports: Map<number, CandidatePort & { ppid: number }> = new Map();
152 const rootProcesses = getRootProcesses(rootProcessesStdout);
178 return ports;
179 }
181 > export class NodeExtHostTunnelService extends ExtHostTunnelService {
182 > private _initialCandidates: CandidatePort[] | undefined = undefined;
183 > private _foundRootPorts: Map<number, CandidatePort & { ppid: number }> = new Map();
184 > private _candidateFindingEnabled: boolean = false;
185 >
186 > constructor(
187 @IExtHostRpcService extHostRpc: IExtHostRpcService,
188 @IExtHostInitDataService private readonly initData: IExtHostInitDataService,
197 }
198 }
200 > override async $registerCandidateFinder(enable: boolean): Promise<void> {
201 if (enable && this._candidateFindingEnabled) {
202 // already enabled
235 }
236 }
238 > private calculateDelay(movingAverage: number) {
239 // Some local testing indicated that the moving average might be between 50-100 ms.
240 return Math.max(movingAverage * 20, 2000);
241 }
243 > private async setInitialCandidates(): Promise<void> {
244 this._initialCandidates = await this.findCandidatePorts();
245 this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) Initial candidates found: ${this._initialCandidates.map(c => c.port).join(', ')}`);
246 }
248 > private async findCandidatePorts(): Promise<CandidatePort[]> {
249 let tcp: string = '';
250 let tcp6: string = '';
314 });
315 }
317 > private async defaultTunnelHost(): Promise<string> {
318 const settingValue = (await this.configurationService.getConfigProvider()).getConfiguration('remote').get('localPortHost');
319 return (!settingValue || settingValue === 'localhost') ? '127.0.0.1' : '0.0.0.0';
320 }
322 > protected override makeManagedTunnelFactory(authority: vscode.ManagedResolvedAuthority): vscode.RemoteAuthorityResolver['tunnelFactory'] {
323 return async (tunnelOptions) => {
324 const t = new NodeRemoteTunnel(
372 };
373 }
375 >
376 > export class ExtHostManagedSocket extends ManagedSocket {
377 > public static connect(
378 > passing: vscode.ManagedMessagePassing,
379 > path: string, query: string, debugLabel: string,
380 > ): Promise<ExtHostManagedSocket> {
381 > const d = new DisposableStore();
382 > const half: RemoteSocketHalf = {
383 > onClose: d.add(new Emitter()),
384 > onData: d.add(new Emitter()),
385 > onEnd: d.add(new Emitter()),
386 > };
387 >
388 > d.add(passing.onDidReceiveMessage(d => half.onData.fire(VSBuffer.wrap(d))));
389 > d.add(passing.onDidEnd(() => half.onEnd.fire()));
390 > d.add(passing.onDidClose(error => half.onClose.fire({
391 > type: SocketCloseEventType.NodeSocketCloseEvent,
392 > error,
393 > hadError: !!error
394 > })));
395 >
396 > const socket = new ExtHostManagedSocket(passing, debugLabel, half);
397 > socket._register(d);
398 > return connectManagedSocket(socket, path, query, debugLabel, half);
399 > }
400 >
401 > constructor(
402 private readonly passing: vscode.ManagedMessagePassing,
403 debugLabel: string,
src/vs/platform/tunnel/node/tunnelService.ts 75 introduced LOC · 14 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- tunnelService.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as net from 'net';
7 > import * as os from 'os';
8 > import { BROWSER_RESTRICTED_PORTS, findFreePortFaster } from '../../../base/node/ports.js';
9 > import { NodeSocket } from '../../../base/parts/ipc/node/ipc.net.js';
10 >
11 > import { Barrier } from '../../../base/common/async.js';
12 > import { Disposable } from '../../../base/common/lifecycle.js';
13 > import { OS } from '../../../base/common/platform.js';
14 > import { ISocket } from '../../../base/parts/ipc/common/ipc.net.js';
15 > import { IConfigurationService } from '../../configuration/common/configuration.js';
16 > import { ILogService } from '../../log/common/log.js';
17 > import { IProductService } from '../../product/common/productService.js';
18 > import { IAddressProvider, IConnectionOptions, connectRemoteAgentTunnel } from '../../remote/common/remoteAgentConnection.js';
19 > import { IRemoteSocketFactoryService } from '../../remote/common/remoteSocketFactoryService.js';
20 > import { ISignService } from '../../sign/common/sign.js';
21 > import { AbstractTunnelService, ISharedTunnelsService, ITunnelProvider, ITunnelService, RemoteTunnel, TunnelPrivacyId, isAllInterfaces, isLocalhost, isPortPrivileged, isTunnelProvider } from '../common/tunnel.js';
22 > import { VSBuffer } from '../../../base/common/buffer.js';
23 >
24 async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
25 let readyTunnel: NodeRemoteTunnel | undefined;
34 return readyTunnel!;
35 }
37 > export class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
38 >
39 > public readonly tunnelRemotePort: number;
40 > public tunnelLocalPort!: number;
41 > public tunnelRemoteHost: string;
42 > public localAddress!: string;
43 > public readonly privacy = TunnelPrivacyId.Private;
44 >
45 > private readonly _options: IConnectionOptions;
46 > private readonly _server: net.Server;
47 > private readonly _barrier: Barrier;
48 >
49 > private readonly _listeningListener: () => void;
50 > private readonly _connectionListener: (socket: net.Socket) => void;
51 > private readonly _errorListener: () => void;
52 >
53 > private readonly _socketsDispose: Map<string, () => void> = new Map();
54 >
55 > constructor(options: IConnectionOptions, private readonly defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) {
56 super();
57 this._options = options;
72 this.tunnelRemoteHost = tunnelRemoteHost;
73 }
75 > public override async dispose(): Promise<void> {
76 super.dispose();
77 this._server.removeListener('listening', this._listeningListener);
84 });
85 }
87 > public async waitForReady(): Promise<this> {
88 const startPort = this.suggestedLocalPort ?? this.tunnelRemotePort;
89 const hostname = isAllInterfaces(this.defaultTunnelHost) ? '0.0.0.0' : '127.0.0.1';
109 return this;
110 }
112 > private async _onConnection(localSocket: net.Socket): Promise<void> {
113 // pause reading on the socket until we have a chance to forward its data
114 localSocket.pause();
156 }
157 }
159 > private _mirrorGenericSocket(localSocket: net.Socket, remoteSocket: ISocket) {
160 remoteSocket.onClose(() => localSocket.destroy());
161 remoteSocket.onEnd(() => localSocket.end());
164 localSocket.resume();
165 }
167 > private _mirrorNodeSocket(localSocket: net.Socket, remoteNodeSocket: NodeSocket) {
168 const remoteSocket = remoteNodeSocket.socket;
169 remoteSocket.on('end', () => localSocket.end());
176 localSocket.pipe(remoteSocket);
177 }
179 >
180 > export class BaseTunnelService extends AbstractTunnelService {
181 > public constructor(
182 @IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
183 @ILogService logService: ILogService,
188 super(logService, configurationService);
189 }
191 > public isPortPrivileged(port: number): boolean {
192 return isPortPrivileged(port, this.defaultTunnelHost, OS, os.release());
193 }
195 > protected retainOrCreateTunnel(addressOrTunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | string | undefined> | undefined {
196 const existing = this.getTunnelFromMap(remoteHost, remotePort);
197 if (existing) {
220 }
221 }
223 >
224 > export class TunnelService extends BaseTunnelService {
225 > public constructor(
226 @IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService,
227 @ILogService logService: ILogService,
232 super(remoteSocketFactoryService, logService, signService, productService, configurationService);
233 }
235 >
236 > export class SharedTunnelsService extends Disposable implements ISharedTunnelsService {
237 > declare readonly _serviceBrand: undefined;
238 > private readonly _tunnelServices: Map<string, ITunnelService> = new Map();
239 >
240 > public constructor(
241 @IRemoteSocketFactoryService protected readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
242 @ILogService protected readonly logService: ILogService,
247 super();
248 }
250 > async openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | string | undefined> {
251 this.logService.trace(`ForwardedPorts: (SharedTunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`);
252 if (!this._tunnelServices.has(authority)) {
263 return this._tunnelServices.get(authority)!.openTunnel(addressProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded, privacy, protocol);
264 }
src/vs/platform/remote/common/managedSocket.ts 57 introduced LOC · 9 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- managedSocket.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { VSBuffer, encodeBase64 } from '../../../base/common/buffer.js';
7 > import { Emitter, Event, PauseableEmitter } from '../../../base/common/event.js';
8 > import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
9 > import { ISocket, SocketCloseEvent, SocketDiagnostics, SocketDiagnosticsEventType } from '../../../base/parts/ipc/common/ipc.net.js';
10 >
11 > export const makeRawSocketHeaders = (path: string, query: string, deubgLabel: string) => {
12 // https://tools.ietf.org/html/rfc6455#section-4
13 const buffer = new Uint8Array(16);
26 return headers.join('\r\n') + '\r\n\r\n';
27 };
29 > export const socketRawEndHeaderSequence = VSBuffer.fromString('\r\n\r\n');
30 >
31 > export interface RemoteSocketHalf {
32 > onData: Emitter<VSBuffer>;
33 > onClose: Emitter<SocketCloseEvent>;
34 > onEnd: Emitter<void>;
35 > }
36 >
37 > /** Should be called immediately after making a ManagedSocket to make it ready for data flow. */
38 export async function connectManagedSocket<T extends ManagedSocket>(
39 socket: T,
81 }
82 }
84 > export abstract class ManagedSocket extends Disposable implements ISocket {
85 > private readonly pausableDataEmitter = this._register(new PauseableEmitter<VSBuffer>());
86 >
87 > public onData: Event<VSBuffer> = (...args) => {
88 > if (this.pausableDataEmitter.isPaused) {
89 > queueMicrotask(() => this.pausableDataEmitter.resume());
90 > }
91 > return this.pausableDataEmitter.event(...args);
92 > };
93 > public onClose: Event<SocketCloseEvent>;
94 > public onEnd: Event<void>;
95 >
96 > private readonly didDisposeEmitter = this._register(new Emitter<void>());
97 > public onDidDispose = this.didDisposeEmitter.event;
98 >
99 > private ended = false;
100 >
101 > protected constructor(
102 private readonly debugLabel: string,
103 half: RemoteSocketHalf,
111 this.onEnd = this._register(half.onEnd).event;
112 }
114 > /** Pauses data events until a new listener comes in onData() */
115 > public pauseData() {
116 this.pausableDataEmitter.pause();
117 }
119 > /** Flushes data to the socket. */
120 > public drain(): Promise<void> {
121 return Promise.resolve();
122 }
124 > /** Ends the remote socket. */
125 > public end(): void {
126 this.ended = true;
127 this.closeRemote();
128 }
130 > public abstract write(buffer: VSBuffer): void;
131 > protected abstract closeRemote(): void;
132 >
133 > traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | unknown): void {
134 SocketDiagnostics.traceSocketEvent(this, this.debugLabel, type, data);
135 }
137 > override dispose(): void {
138 if (!this.ended) {
139 this.closeRemote();
src/vs/platform/remote/common/remoteSocketFactoryService.ts 39 introduced LOC · 5 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- remoteSocketFactoryService.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
7 > import { ISocket } from '../../../base/parts/ipc/common/ipc.net.js';
8 > import { createDecorator } from '../../instantiation/common/instantiation.js';
9 > import { RemoteConnectionOfType, RemoteConnectionType, RemoteConnection } from './remoteAuthorityResolver.js';
10 >
11 > export const IRemoteSocketFactoryService = createDecorator<IRemoteSocketFactoryService>('remoteSocketFactoryService');
12 >
13 > export interface IRemoteSocketFactoryService {
14 > readonly _serviceBrand: undefined;
15 >
16 > /**
17 > * Register a socket factory for the given message passing type
18 > * @param type passing type to register for
19 > * @param factory function that returns the socket factory, or undefined if
20 > * it can't handle the data.
21 > */
22 > register<T extends RemoteConnectionType>(type: T, factory: ISocketFactory<T>): IDisposable;
23 >
24 > connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket>;
25 > }
26 >
27 > export interface ISocketFactory<T extends RemoteConnectionType> {
28 > supports(connectTo: RemoteConnectionOfType<T>): boolean;
29 > connect(connectTo: RemoteConnectionOfType<T>, path: string, query: string, debugLabel: string): Promise<ISocket>;
30 > }
31 >
32 > export class RemoteSocketFactoryService implements IRemoteSocketFactoryService {
33 declare readonly _serviceBrand: undefined;
34
35 private readonly factories: { [T in RemoteConnectionType]?: ISocketFactory<T>[] } = {};
37 > public register<T extends RemoteConnectionType>(type: T, factory: ISocketFactory<T>): IDisposable {
38 this.factories[type] ??= [];
39 this.factories[type]!.push(factory);
45 });
46 }
48 > private getSocketFactory<T extends RemoteConnectionType>(messagePassing: RemoteConnectionOfType<T>): ISocketFactory<T> | undefined {
49 const factories = (this.factories[messagePassing.type] || []);
50 return factories.find(factory => factory.supports(messagePassing));
51 }
53 > public connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
54 const socketFactory = this.getSocketFactory(connectTo);
55 if (!socketFactory) {
src/vs/platform/sign/common/sign.ts 22 introduced LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- sign.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { createDecorator } from '../../instantiation/common/instantiation.js';
7 >
8 > export const SIGN_SERVICE_ID = 'signService';
9 > export const ISignService = createDecorator<ISignService>(SIGN_SERVICE_ID);
10 >
11 > export interface IMessage {
12 > id: string;
13 > data: string;
14 > }
15 >
16 > export interface ISignService {
17 > readonly _serviceBrand: undefined;
18 >
19 > createNewMessage(value: string): Promise<IMessage>;
20 > validate(message: IMessage, value: string): Promise<boolean>;
21 > sign(value: string): Promise<string>;
22 > }