src/vs/platform/tunnel/node/tunnelService.ts

265 LOC · 75 covered · 190 uncovered · 14 ranges · 7 concepts · 1 introducers · 6 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

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 related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/platform/remote/common/managedSocket.ts · 145 LOCcommon/managedSocket.tssrc/vs/platform/remote/common/remoteAgentConnection.ts · 839 LOCcommon/remoteAgentConnec…src/vs/platform/remote/common/remoteSocketFactoryService.ts · 60 LOCcommon/remoteSocketFacto…src/vs/platform/sign/common/sign.ts · 22 LOCcommon/sign.tssrc/vs/workbench/api/node/extHostTunnelService.ts · 419 LOCnode/extHostTunnelServic…src/vs/workbench/services/remote/common/tunnelModel.ts · 1042 LOCcommon/tunnelModel.tsextHostTunnelService.ts ×3 · 22 introduced LOCextHostTunnelService.ts …extHostTunnelService.ts ×2 · 21 introduced LOCextHostTunnelService.ts …extHostTunnelService.ts ×4 · 38 introduced LOCextHostTunnelService.ts …extHostTunnelService.ts ×2 · 23 introduced LOCextHostTunnelService.ts …extHostTunnelService.ts ×1 · 9 introduced LOCextHostTunnelService.ts …extHostTunnelService.ts ×1 · 17 introduced LOCextHostTunnelService.ts …remoteAgentConnection.ts ×53 · 823 introduced LOCremoteAgentConnection.ts…extHostTunnelService.test|title=ExtHostTunnelService findPorts|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostTunnelService.test|title=ExtHostTunnelService findPorts|occurrence=1extHostTunnelService.tes…extHostTunnelService.test|title=ExtHostTunnelService getSockets|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostTunnelService.test|title=ExtHostTunnelService getSockets|occurrence=1extHostTunnelService.tes…extHostTunnelService.test|title=ExtHostTunnelService loadConnectionTable|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostTunnelService.test|title=ExtHostTunnelService loadConnectionTable|occurrence=1extHostTunnelService.tes…extHostTunnelService.test|title=ExtHostTunnelService loadListeningPorts|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostTunnelService.test|title=ExtHostTunnelService loadListeningPorts|occurrence=1extHostTunnelService.tes…extHostTunnelService.test|title=ExtHostTunnelService parseIpAddress|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostTunnelService.test|title=ExtHostTunnelService parseIpAddress|occurrence=1extHostTunnelService.tes…extHostTunnelService.test|title=ExtHostTunnelService tryFindRootPorts|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostTunnelService.test|title=ExtHostTunnelService tryFindRootPorts|occurrence=1extHostTunnelService.tes…Focused file · src/vs/platform/tunnel/node/tunnelService.ts · 265 LOCnode/tunnelService.ts

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- remoteAgentConnection.ts ×53
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;
26 for (let attempts = 3; attempts; attempts--) {
27 readyTunnel?.dispose();
28 const tunnel = new NodeRemoteTunnel(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
29 readyTunnel = await tunnel.waitForReady();
30 if ((tunnelLocalPort && BROWSER_RESTRICTED_PORTS[tunnelLocalPort]) || !BROWSER_RESTRICTED_PORTS[readyTunnel.tunnelLocalPort]) {
31 break;
32 }
33 }
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;
58 this._server = net.createServer();
59 this._barrier = new Barrier();
60
61 this._listeningListener = () => this._barrier.open();
62 this._server.on('listening', this._listeningListener);
63
64 this._connectionListener = (socket) => this._onConnection(socket);
65 this._server.on('connection', this._connectionListener);
66
67 // If there is no error listener and there is an error it will crash the whole window
68 this._errorListener = () => { };
69 this._server.on('error', this._errorListener);
70
71 this.tunnelRemotePort = tunnelRemotePort;
72 this.tunnelRemoteHost = tunnelRemoteHost;
73 }
75 > public override async dispose(): Promise<void> {
76 super.dispose();
77 this._server.removeListener('listening', this._listeningListener);
78 this._server.removeListener('connection', this._connectionListener);
79 this._server.removeListener('error', this._errorListener);
80 this._server.close();
81 const disposers = Array.from(this._socketsDispose.values());
82 disposers.forEach(disposer => {
83 disposer();
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';
90 // try to get the same port number as the remote port number...
91 let localPort = await findFreePortFaster(startPort, 2, 1000, hostname);
92
93 // if that fails, the method above returns 0, which works out fine below...
94 let address: string | net.AddressInfo | null = null;
95 this._server.listen(localPort, this.defaultTunnelHost);
96 await this._barrier.wait();
97 address = <net.AddressInfo>this._server.address();
98
99 // It is possible for findFreePortFaster to return a port that there is already a server listening on. This causes the previous listen call to error out.
100 if (!address) {
101 localPort = 0;
102 this._server.listen(localPort, this.defaultTunnelHost);
103 await this._barrier.wait();
104 address = <net.AddressInfo>this._server.address();
105 }
106
107 this.tunnelLocalPort = address.port;
108 this.localAddress = `${this.tunnelRemoteHost === '127.0.0.1' ? '127.0.0.1' : 'localhost'}:${address.port}`;
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();
115
116 const tunnelRemoteHost = (isLocalhost(this.tunnelRemoteHost) || isAllInterfaces(this.tunnelRemoteHost)) ? 'localhost' : this.tunnelRemoteHost;
117 const protocol = await connectRemoteAgentTunnel(this._options, tunnelRemoteHost, this.tunnelRemotePort);
118 const remoteSocket = protocol.getSocket();
119 const dataChunk = protocol.readEntireBuffer();
120 protocol.dispose();
121
122 if (dataChunk.byteLength > 0) {
123 localSocket.write(dataChunk.buffer);
124 }
125
126 localSocket.on('end', () => {
127 if (localSocket.localAddress) {
128 this._socketsDispose.delete(localSocket.localAddress);
129 }
130 remoteSocket.end();
131 });
132 localSocket.on('close', () => remoteSocket.end());
133 localSocket.on('error', () => {
134 if (localSocket.localAddress) {
135 this._socketsDispose.delete(localSocket.localAddress);
136 }
137 if (remoteSocket instanceof NodeSocket) {
138 remoteSocket.socket.destroy();
139 } else {
140 remoteSocket.end();
141 }
142 });
143
144 if (remoteSocket instanceof NodeSocket) {
145 this._mirrorNodeSocket(localSocket, remoteSocket);
146 } else {
147 this._mirrorGenericSocket(localSocket, remoteSocket);
148 }
149
150 if (localSocket.localAddress) {
151 this._socketsDispose.set(localSocket.localAddress, () => {
152 // Need to end instead of unpipe, otherwise whatever is connected locally could end up "stuck" with whatever state it had until manually exited.
153 localSocket.end();
154 remoteSocket.end();
155 });
156 }
157 }
159 > private _mirrorGenericSocket(localSocket: net.Socket, remoteSocket: ISocket) {
160 remoteSocket.onClose(() => localSocket.destroy());
161 remoteSocket.onEnd(() => localSocket.end());
162 remoteSocket.onData(d => localSocket.write(d.buffer));
163 localSocket.on('data', d => remoteSocket.write(VSBuffer.wrap(d)));
164 localSocket.resume();
165 }
167 > private _mirrorNodeSocket(localSocket: net.Socket, remoteNodeSocket: NodeSocket) {
168 const remoteSocket = remoteNodeSocket.socket;
169 remoteSocket.on('end', () => localSocket.end());
170 remoteSocket.on('close', () => localSocket.end());
171 remoteSocket.on('error', () => {
172 localSocket.destroy();
173 });
174
175 remoteSocket.pipe(localSocket);
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,
184 @ISignService private readonly signService: ISignService,
185 @IProductService private readonly productService: IProductService,
186 @IConfigurationService configurationService: IConfigurationService
187 ) {
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) {
198 ++existing.refcount;
199 return existing.value;
200 }
201
202 if (isTunnelProvider(addressOrTunnelProvider)) {
203 return this.createWithProvider(addressOrTunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
204 } else {
205 this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
206 const options: IConnectionOptions = {
207 commit: this.productService.commit,
208 quality: this.productService.quality,
209 addressProvider: addressOrTunnelProvider,
210 remoteSocketFactoryService: this.remoteSocketFactoryService,
211 signService: this.signService,
212 logService: this.logService,
213 ipcLogger: null
214 };
215
216 const tunnel = createRemoteTunnel(options, localHost, remoteHost, remotePort, localPort);
217 this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.');
218 this.addTunnelToMap(remoteHost, remotePort, tunnel);
219 return tunnel;
220 }
221 }
223 >
224 > export class TunnelService extends BaseTunnelService {
225 > public constructor(
226 @IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService,
227 @ILogService logService: ILogService,
228 @ISignService signService: ISignService,
229 @IProductService productService: IProductService,
230 @IConfigurationService configurationService: IConfigurationService
231 ) {
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,
243 @IProductService private readonly productService: IProductService,
244 @ISignService private readonly signService: ISignService,
245 @IConfigurationService private readonly configurationService: IConfigurationService,
246 ) {
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)) {
253 const tunnelService = new TunnelService(this.remoteSocketFactoryService, this.logService, this.signService, this.productService, this.configurationService);
254 this._register(tunnelService);
255 this._tunnelServices.set(authority, tunnelService);
256 tunnelService.onTunnelClosed(async () => {
257 if ((await tunnelService.tunnels).length === 0) {
258 tunnelService.dispose();
259 this._tunnelServices.delete(authority);
260 }
261 });
262 }
263 return this._tunnelServices.get(authority)!.openTunnel(addressProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded, privacy, protocol);
264 }