最新消息:

vscode remote 实现原理

项目与思路 admin 468浏览 0评论

与 remote 有关的功能,vscode 总共有四条不开源的产品线:

  • vscode live share
  • vscode remote (container, wsl, ssh)
  • visual studio online
  • vscode web

实现的功能都是 vscode 编辑的不是本地的文件,而是远程机器上的文件。vscode remote 来说,远程文件则是你指定的通信方式,比如 ssh,连接过去的文件。而 vscode live share 和 visual studio online 这两种方式都不是直连的,而是通过微软在公网上的代理服务器进行的中转。这个也就导致了 live share 在国内用起来并不是特别快。

vscode web 与 vistual studio online 是一样的实现,只是少了走微软的公网代理这个步骤,直接是连了本地暴露的端口。

从部署架构来说,基本的组件如上图所示。不同的地方就是中间这个网络连接上:

  • vscode web:最简单,网络连接就是 websocket 直连
  • vscode remote:通过ssh中转
  • visual studio online:通过微软公网代理中转 ssh,然后通过 ssh 中转这个 websocket 连接
  • vscode live share:和 visual studio online 都是走公网代理,只是 live share 的 session 是临时性的,不是被记录在案的 environment

其中 vscode remoteAgent 这个进程,与 vscode remoteExtensionHost 这个进程,都是不开源的。所以我们就开源的 vscode 界面部分来看一下这个 vscode remote 是如何实现的。

第一步:构造 workbench 的参数

一个 workbench 就是我们所看见的 vscode 整个界面。构造它需要的参数有

<code class="language-text">interface IWorkbenchConstructionOptions {

	/**
	 * The remote authority is the IP:PORT from where the workbench is served
	 * from. It is for example being used for the websocket connections as address.
	 */
	readonly remoteAuthority?: string;

	/**
	 * The connection token to send to the server.
	 */
	readonly connectionToken?: string;

	/**
	 * An endpoint to serve iframe content ("webview") from. This is required
	 * to provide full security isolation from the workbench host.
	 */
	readonly webviewEndpoint?: string;

	/**
	 * A handler for opening workspaces and providing the initial workspace.
	 */
	readonly workspaceProvider?: IWorkspaceProvider;

	/**
	 * The user data provider is used to handle user specific application
	 * state like settings, keybindings, UI state (e.g. opened editors) and snippets.
	 */
	userDataProvider?: IFileSystemProvider;

	/**
	 * A factory for web sockets.
	 */
	readonly webSocketFactory?: IWebSocketFactory;

	/**
	 * A provider for resource URIs.
	 */
	readonly resourceUriProvider?: IResourceUriProvider;

	/**
	 * The credentials provider to store and retrieve secrets.
	 */
	readonly credentialsProvider?: ICredentialsProvider;

	/**
	 * Add static extensions that cannot be uninstalled but only be disabled.
	 */
	readonly staticExtensions?: ReadonlyArray&lt;IStaticExtension&gt;;

	/**
	 * Support for URL callbacks.
	 */
	readonly urlCallbackProvider?: IURLCallbackProvider;

	/**
	 * Support for update reporting.
	 */
	readonly updateProvider?: IUpdateProvider;

	/**
	 * Support adding additional properties to telemetry.
	 */
	readonly resolveCommonTelemetryProperties?: ICommontTelemetryPropertiesResolver;

	/**
	 * Resolves an external uri before it is opened.
	 */
	readonly resolveExternalUri?: IExternalUriResolver;

	/**
	 * Current logging level. Default is `LogLevel.Info`.
	 */
	readonly logLevel?: LogLevel;

	/**
	 * Whether to enable the smoke test driver.
	 */
	readonly driver?: boolean;
}</code>

其中 remoteAuthority 标记了当前 workbench 是运行在什么 remote 环境中。

第二步:构造 workbench

其中比较关键的几行

<code class="language-text">// Remote
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(this.configuration.resourceUriProvider);
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);

// Remote Agent
const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, environmentService, productService, remoteAuthorityResolverService, signService, logService));
serviceCollection.set(IRemoteAgentService, remoteAgentService);

// Files
const fileService = this._register(new FileService(logService));
serviceCollection.set(IFileService, fileService);
this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, logService, logsPath);</code>

这里就分出了三条支线剧情

  • remoteAuthorityResolverService
  • remoteAgentService
  • fileSystemProvider

第三步:remoteAuthorityResolverService 设置全局变量 RemoteAuthorities

关键的代码

<code class="language-text">	resolveAuthority(authority: string): Promise&lt;ResolverResult&gt; {
		if (authority.indexOf(':') &gt;= 0) {
			const pieces = authority.split(':');
			return Promise.resolve(this._createResolvedAuthority(authority, pieces[0], parseInt(pieces[1], 10)));
		}
		return Promise.resolve(this._createResolvedAuthority(authority, authority, 80));
	}
	private _createResolvedAuthority(authority: string, host: string, port: number): ResolverResult {
		RemoteAuthorities.set(authority, host, port);
		return { authority: { authority, host, port } };
	}</code>

这里通过全局变量 RemoteAuthorities,影响了其他地方的 Uri 的解析。

第四步 remoteAuthorityResolverService 被谁调用的?

是在 RemoteExtensionHostClient 中调用的。顾名思义,这个类就是去连接 remoteExtensionHost 这个进程的,从而间接操作远端机器上运行的插件进程。

关键代码

<code class="language-text">const options: IConnectionOptions = {
    commit: this._productService.commit,
    socketFactory: this._socketFactory,
    addressProvider: {
        getAddress: async () =&gt; {
            const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
            return { host: authority.host, port: authority.port };
        }
    },
    signService: this._signService,
    logService: this._logService
};</code>

remote 主要就四个功能

  • 远程插件执行
  • 文件系统读写
  • terminal
  • port forwarding

这里已经可以找到远程插件执行是怎么过来的了。

第五步:RemoteFileSystemProvider

关键代码

<code class="language-text">// --- forwarding calls

stat(resource: URI): Promise&lt;IStat&gt; {
    return this.channel.call('stat', [resource]);
}

open(resource: URI, opts: FileOpenOptions): Promise&lt;number&gt; {
    return this.channel.call('open', [resource, opts]);
}

close(fd: number): Promise&lt;void&gt; {
    return this.channel.call('close', [fd]);
}</code>

还是非常直白的。直接把对 FileSystemProvider 调用转成 IPC 调用,并跑在 REMOTE_FILE_SYSTEM_CHANNEL_NAME 这个 channel 上。

这里的连接都是用 webSocketFactory 这个最初从 IWorkbenchConstructionOptions 上传进来的参数控制的。所以 remote 的关键,还是怎么用 websocket 和 remoteAgent 连上。这个地方连上了,vscode 就可以在这个连接上转发一切,从文件系统调用,到插件的消息。

服务端

我们只能看到 vscode remote 客户端的部分。websocket 对端的 remoteAgent 是没有提供开源代码的。我们可以自己脑补一下,一个类似冰河木马的进程,在用 websocket 把你的机器暴露出去。在 vscode 的开源部分,并没有提供对这个 socket 的加密机制,而是选择由插件去处理如何加密。

转自:https://zhuanlan.zhihu.com/p/94330221

转载请注明:jinglingshu的博客 » vscode remote 实现原理


Warning: Use of undefined constant PRC - assumed 'PRC' (this will throw an Error in a future version of PHP) in /usr/share/nginx/html/wp-content/themes/d8/comments.php on line 17
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址