mirror of https://git.tuxpa.in/a/code-server.git
Proxy TLS sockets
This commit is contained in:
parent
8ded89e8d4
commit
011530e11b
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue