Proxy TLS sockets

This commit is contained in:
Asher 2019-07-30 17:20:53 -05:00
parent 8ded89e8d4
commit 011530e11b
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
2 changed files with 87 additions and 6 deletions

View File

@ -1,5 +1,4 @@
import * as cp from "child_process"; import * as cp from "child_process";
import * as tls from "tls";
import { getPathFromAmdModule } from "vs/base/common/amd"; import { getPathFromAmdModule } from "vs/base/common/amd";
import { VSBuffer } from "vs/base/common/buffer"; import { VSBuffer } from "vs/base/common/buffer";
@ -62,8 +61,9 @@ export class ExtensionHostConnection extends Connection {
public constructor(protocol: Protocol, buffer: VSBuffer, private readonly log: ILogService) { public constructor(protocol: Protocol, buffer: VSBuffer, private readonly log: ILogService) {
super(protocol); super(protocol);
protocol.dispose(); this.protocol.dispose();
this.process = this.spawn(buffer); this.process = this.spawn(buffer);
this.protocol.getUnderlyingSocket().pause();
} }
protected dispose(): void { protected dispose(): void {
@ -89,7 +89,7 @@ export class ExtensionHostConnection extends Connection {
type: "VSCODE_EXTHOST_IPC_SOCKET", type: "VSCODE_EXTHOST_IPC_SOCKET",
initialDataChunk: (buffer.buffer as Buffer).toString("base64"), initialDataChunk: (buffer.buffer as Buffer).toString("base64"),
skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket, skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket,
}, socket instanceof tls.TLSSocket ? (<any>socket)._parent : socket); }, socket);
} }
private spawn(buffer: VSBuffer): cp.ChildProcess { private spawn(buffer: VSBuffer): cp.ChildProcess {

View File

@ -12,9 +12,10 @@ import * as querystring from "querystring";
import { Emitter } from "vs/base/common/event"; import { Emitter } from "vs/base/common/event";
import { sanitizeFilePath } from "vs/base/common/extpath"; import { sanitizeFilePath } from "vs/base/common/extpath";
import { UriComponents, URI } from "vs/base/common/uri"; import { UriComponents, URI } from "vs/base/common/uri";
import { generateUuid } from "vs/base/common/uuid";
import { getMachineId } from 'vs/base/node/id'; import { getMachineId } from 'vs/base/node/id';
import { IPCServer, ClientConnectionEvent, StaticRouter } from "vs/base/parts/ipc/common/ipc"; import { IPCServer, ClientConnectionEvent, StaticRouter } from "vs/base/parts/ipc/common/ipc";
import { mkdirp } from "vs/base/node/pfs"; import { mkdirp, rimraf } from "vs/base/node/pfs";
import { LogsDataCleaner } from "vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner"; import { LogsDataCleaner } from "vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner";
import { IConfigurationService } from "vs/platform/configuration/common/configuration"; import { IConfigurationService } from "vs/platform/configuration/common/configuration";
import { ConfigurationService } from "vs/platform/configuration/node/configurationService"; import { ConfigurationService } from "vs/platform/configuration/node/configurationService";
@ -56,7 +57,7 @@ import { Connection, ManagementConnection, ExtensionHostConnection } from "vs/se
import { ExtensionEnvironmentChannel, FileProviderChannel , } from "vs/server/src/channel"; import { ExtensionEnvironmentChannel, FileProviderChannel , } from "vs/server/src/channel";
import { TelemetryClient } from "vs/server/src/insights"; import { TelemetryClient } from "vs/server/src/insights";
import { Protocol } from "vs/server/src/protocol"; import { Protocol } from "vs/server/src/protocol";
import { AuthType, getMediaMime, getUriTransformer } from "vs/server/src/util"; import { AuthType, getMediaMime, getUriTransformer, tmpdir } from "vs/server/src/util";
export enum HttpCode { export enum HttpCode {
Ok = 200, Ok = 200,
@ -391,6 +392,11 @@ export class MainServer extends Server {
private readonly services = new ServiceCollection(); private readonly services = new ServiceCollection();
private readonly servicesPromise: Promise<void>; private readonly servicesPromise: Promise<void>;
public readonly _onProxyConnect = new Emitter<net.Socket>();
private proxyPipe = path.join(tmpdir, "tls-proxy");
private _proxyServer?: Promise<net.Server>;
private readonly proxyTimeout = 5000;
public constructor(options: ServerOptions, args: ParsedArgs) { public constructor(options: ServerOptions, args: ParsedArgs) {
super(options); super(options);
this.servicesPromise = this.initializeServices(args); this.servicesPromise = this.initializeServices(args);
@ -407,7 +413,7 @@ export class MainServer extends Server {
} }
protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> { protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
const protocol = new Protocol(socket, { const protocol = new Protocol(await this.createProxy(socket), {
reconnectionToken: <string>parsedUrl.query.reconnectionToken || "", reconnectionToken: <string>parsedUrl.query.reconnectionToken || "",
reconnection: parsedUrl.query.reconnection === "true", reconnection: parsedUrl.query.reconnection === "true",
skipWebSocketFrames: parsedUrl.query.skipWebSocketFrames === "true", skipWebSocketFrames: parsedUrl.query.skipWebSocketFrames === "true",
@ -592,4 +598,79 @@ export class MainServer extends Server {
private async getDebugPort(): Promise<number | undefined> { private async getDebugPort(): Promise<number | undefined> {
return undefined; return undefined;
} }
/**
* Since we can't pass TLS sockets to children, use this to proxy the socket
* and pass a non-TLS socket.
*/
private createProxy = async (socket: net.Socket): Promise<net.Socket> => {
if (!(socket instanceof tls.TLSSocket)) {
return socket;
}
await this.startProxyServer();
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
listener.dispose();
socket.destroy();
proxy.destroy();
reject(new Error("TLS socket proxy timed out"));
}, this.proxyTimeout);
const listener = this._onProxyConnect.event((connection) => {
connection.once("data", (data) => {
if (!socket.destroyed && !proxy.destroyed && data.toString() === id) {
clearTimeout(timeout);
listener.dispose();
[[proxy, socket], [socket, proxy]].forEach(([a, b]) => {
a.pipe(b);
a.on("error", () => b.destroy());
a.on("close", () => b.destroy());
a.on("end", () => b.end());
});
resolve(connection);
}
});
});
const id = generateUuid();
const proxy = net.connect(this.proxyPipe);
proxy.once("connect", () => proxy.write(id));
});
}
private async startProxyServer(): Promise<net.Server> {
if (!this._proxyServer) {
this._proxyServer = new Promise(async (resolve) => {
this.proxyPipe = await this.findFreeSocketPath(this.proxyPipe);
await mkdirp(tmpdir);
await rimraf(this.proxyPipe);
const proxyServer = net.createServer((p) => this._onProxyConnect.fire(p));
proxyServer.once("listening", resolve);
proxyServer.listen(this.proxyPipe);
});
}
return this._proxyServer;
}
private async findFreeSocketPath(basePath: string, maxTries: number = 100): Promise<string> {
const canConnect = (path: string): Promise<boolean> => {
return new Promise((resolve) => {
const socket = net.connect(path);
socket.once("error", () => resolve(false));
socket.once("connect", () => {
socket.destroy();
resolve(true);
});
});
};
let i = 0;
let path = basePath;
while (await canConnect(path) && i < maxTries) {
path = `${basePath}-${++i}`;
}
return path;
}
} }