RFC-0228:FDomain:对 Fuchsia 目标进行远程控制 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 引入了一种新协议,用于在远程 Fuchsia 设备上执行任意 FIDL |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2023-05-22 |
审核日期(年-月-日) | 2023-10-04 |
摘要
此提案概述了 FDomain,这是一种从开发主机与 Fuchsia 目标上的 FIDL 服务通信的新机制。FDomain 将支持 FFX 命令行工具的功能,取代目前使用的 Overnet 协议。
设计初衷
FFX 对组件层次结构中公开的 FIDL 协议提供了或多或少的任意访问权限。这使得开发各种各样的工具和插件变得非常容易,并且具有实现自动化集成测试和类似于 sl4f 的功能的巨大潜力。
FFX 通过 Overnet 与设备端 FIDL 通信。Overnet 是一种点对点协议,允许通过网络共享 Fuchsia 内核句柄。Fuchsia 设备可以通过 overnet 将通道发送到开发者主机,而主机端 Fuchsia 模拟库将允许该通道尽可能像在设备本身上一样使用。Rust 的 FIDL 绑定可以使用这些通道在主机上编译,因此 FIDL 可用于在主机和目标之间进行通信。
Overnet 的方法存在以下几个缺点:
- 在主机上模拟内核句柄必然不完美。句柄仅限于单个进程,并且需要额外的 Overnet 连接才能在宿主上的进程之间移动它们。某些机制(例如 zx_channel_call)完全缺失。
- 通过句柄报告错误的设计并未考虑到句柄由复杂且易出错的网络堆栈支持。与 Overnet 句柄相关的任何服务中断问题最终都会报告为 PEER_CLOSED,且没有其他信息。
- 内核句柄并非设计为通过网络进行代理。在设计和实现 Overnet 时,未充分咨询内核团队。因此,目前的 API 无法正确实现某些功能(例如对象信号),未来也可能无法实现。
- 由于我们致力于代理句柄,并让它们在主机上的远程行为与在目标上的本地行为大致相同,因此几乎完全不可能支持某些对象(例如 VMO)。
除了该方法存在的问题外,Overnet 的具体设计和实现也存在明显缺点:
- Overnet 的低级协议(用于协调数据流)是使用 FIDL 编写的,但并未按照其设计方式使用 FIDL。而是扮演类似 protobuf 的角色,指定数据包的二进制形状,但在传输中没有任何作用。这使得 Overnet 容易受到 FIDL 中的线格格式更改的影响,而 FIDL 团队无法像管理通过常规传输运行的 FIDL 用户那样管理这些更改。
- Overnet 的代码过于复杂,并且使用了已证明极难调试的异步模式。
- Overnet 被设计为点对点网状网络,这是一个额外的复杂层,目前的任何用例都不需要它。
利益相关方
教员:
hjfreyer@
Reviewers:
abarth@ cpu@ ianloic@ mgnb@ mkember@ slgrady@ wilkinsonclay@
社交:
我们在 Fuchsia 工具团队中讨论了 FDomain 的设计,并向内核团队和 FIDL 团队展示了一份基本提案,这两个团队都提供了反馈。
要求
FDomain 应允许主机连接到 Fuchsia 目标,并在配置后,与服务通过 FIDL 通信,与目标上的组件通信方式大致相同。与 Overnet 一样,这将通过支持与更低级别的内核基元(具体而言是通道)进行通信来实现,然后可以在该通信之上实现 FIDL 绑定。Overnet 目前支持代理通道、套接字、事件和事件对,因此可用于与不转移这些类型之外的句柄的任何 FIDL 协议进行通信。此处将指定 FDomain 支持同一组句柄类型,但必须有一个明确的扩展路径,以支持一组尽可能多的 Fuchsia 句柄类型,希望能够支持所有句柄类型。VMO 对多种应用特别有用,未来可能会成为我们着力研究的领域。请注意,并非所有类型的所有操作都适合在远程上下文中呈现(例如,远程映射和取消映射 VMO 没有用处),但 FDomain 会尽可能针对每种类型的句柄提供尽可能多的功能。
与上述内容相关,FDomain 应提供一种通过线程的抽象,允许类似于直接使用内核接口的功能,但不会破坏这些接口的设计假设。Overnet 的许多限制都是由于其向主机呈现句柄的传输和代理模型与其原始支持集之外的句柄类型不兼容。
FDomain 的设计将假定能够在目标和主机之间交换可靠的有序数据报。在 TCP 或 SSH 连接上添加一个小型额外协议层,或在 USB 批量端点上添加一个小型额外协议层,即可轻松实现此功能。这些传输的具体细节超出了核心协议的范围。
与 Overnet 不同,FDomain 不会提供任何用于自动发现或建立与目标的连接的设施。FFX 已不再依赖于 Overnet 对这些函数的实现。与 Overnet 不同,FDomain 纯粹是一种端到端协议,而非网状网络协议。
FDomain 应能够检测协议版本不兼容性,以便工具能够轻松显示由此产生的问题。很难预见未来可能会出现哪些兼容性问题,但一般来说,FDomain 应尽可能与不兼容的版本进行互操作。如果 FDomain 直接使用 FIDL,则必须使用 FIDL 团队自行维护的设施,以确保线格格式兼容性,并在可用时进行回退。
对用户 API 的要求
虽然 FDomain 协议及其直接公开的语义对于正确设计至关重要,但直接向用户呈现的 API 也存在重要的限制。这些函数将采用主机端 Rust crate 的形式,提供从主机连接到 FDomain 的函数。
虽然 FDomain 故意不提供 Zircon 的完全模拟,但它还必须支持不会破坏 Zircon 本身“编程模型”的绑定。也就是说,从主机处理 FDomain 中的句柄与处理 Zircon 代码中的句柄应该没有细微或令人惊讶的差异。
确定 Zircon 和 FDomain 主机端 API 之间可以允许的确切差异是 API 审核的主题,超出了本 RFC 的范围。不过,我们会注意到一些预期的差异,这些差异对于 FDomain 实现其目标至关重要:
- 对句柄执行的 FDomain 操作通常需要 IO,因此我们无法实现 Zircon API 的非阻塞语义。Zircon 句柄的 Rust 绑定包含每个句柄的更高级别的“异步”版本,这些版本使用 Rust 的 Future 系统处理异步操作。我们可能会在此层实现 FDomain 主机端库,而不会提供用于直接访问非阻塞操作的更低级别的句柄。这将导致对象创建方面的一些语义差异。此外,通常仅采用原始句柄(而非异步版本)的操作(例如在 FIDL API 中创建端点)将无法使用 FDomain 进行此区分。
- FDomain 句柄必然是远程连接的 FDomain 的一部分。在我们的 API 中代表它们的 RAII 对象将由连接对象生成,而不是就地构建,并且可能具有行为或类型属性,这些属性反映了它们的生命周期与连接的生命周期相关联这一事实。(由于关于此部分设计存在一些争论,我们在此强调,此 RFC 不会在此处提供任何具体建议)。
- FDomain 操作将返回不同的、更丰富的错误类型,反映远程协议可能会遇到的更复杂的失败模式(以及我们可以在远程情境中提供的更丰富的错误报告)。Rust 在错误处理方面的编程惯例应该能让用户轻松应对这项变更。
设计
从概念上讲,FDomain 是一组句柄,可通过一组操作进行远程操作。FDomain 协议通过 FIDL 协议提供这些操作。
FDomain 连接到主机并由主机操控,FDomain 与主机之间的连接是 FDomain 连接。FDomain 最多只能有一个连接的主机,这意味着主机绝不应观察由其他 actor 关闭、写入或读取其控制的 FDomain 中的句柄。虽然会话恢复可能是未来工作的一个有趣领域,但在本文档中,我们假定主机始终连接到新的 FDomain(即预先填充的句柄集合),并且当主机断开连接时,FDomain 会被销毁,其中的句柄也会关闭。
核心协议
由于 FDomain 的目的是通过不支持内核句柄传输的连接(通常是到也不支持内核句柄传输的主机)呈现 FIDL,因此 FDomain FIDL 本身无法在其方法参数或返回值中的任何位置使用句柄。具体而言,resource
关键字绝不能出现在 FDomain 的 FIDL 规范中。
由于 FDomain 的协议消息旨在通过渠道以外的媒体传输,因此我们假定没有消息大小限制,因此也不限制协议中的矢量和其他结构的大小。
必须报告错误
所有方法都必须能够返回错误。由于我们的错误类型是可扩展的,因此我们日后可以向所有方法添加错误条件。
此外,这会强制所有方法都采用双向方法,这意味着 (RFC-0138)RFC-0138 中指定的未知互动处理功能将始终能够向主机返回有关未知序列号的错误。这应该有助于我们更轻松地实现向后兼容性,因为根据“智能发送器、模糊接收器”原则,所有兼容性错误都会显示在主机上。
句柄 ID 分配
主机将通过 ID 引用 FDomain 中的每个句柄。这些 ID 在协议中编码为 32 位无符号整数,但不应是直接公开给主机的实际内核句柄编号。
为了减少执行协议中操作所需的往返次数,我们将尽可能使用主机端 ID 分配。这意味着,如果某个操作会导致 FDomain 中产生新的句柄(例如,当主机请求创建新通道或套接字时),则主机会提供新通道将自行使用的 ID 编号。这样一来,协议中的流水线处理会更加顺畅,因为主机可以在同一事务中提交创建句柄的请求以及对该句柄的首次操作,而无需等待每个操作的回复。
此政策的例外情况是从通道读取,这可能会产生任意数量的句柄。要求主机为可能由读取通道生成的句柄分配 ID 会导致提交读取请求变得繁琐,并且需要指定复杂的阻塞语义才能与流式读取兼容。因此,由读取通道生成的此类句柄将由 FDomain 本身分配 ID。
引导
当主机连接到 FDomain 时,系统会假定上下文中填充了一些起始句柄。这些句柄类似于将提供给新启动的组件的句柄。FDomain 将允许主机以支持该类比的方式检索这些句柄,从而保留用户直观的理解,即 FDomain 是组件拓扑中的节点。
处理操作
FDomain 将支持在 FDomain 中创建新的套接字、通道、事件对和事件。它还将支持关闭和复制句柄,以及将句柄替换为具有不同权限的新版本,与 zx_handle_replace
类似。
对于可能需要重试的读取、写入和其他操作(即可能会因 ZX_ERR_SHOULD_WAIT
而失败),FDomain 协议将实现挂起的 get 并在目标端执行必要的端口等待操作。这样可以避免要求主机手动设置端口时出现的笨拙和大量往返。
稳定的线缆编码
FIDL 的交易格式并非设计为在 Fuchsia 通道之外传输。当需要在其他上下文中序列化 FIDL 时,建议使用 RFC-0120 中所述的持久性标头。
持久性标头的常见用途是用于将在休眠时存储的 FIDL 数据。虽然 FDomain 消息不会像通常那样通过渠道传输,但仍应以常规的请求-响应方式发送和接收。
永久性标头和事务标头都包含一个魔法数字和一系列兼容性标志。事务标头还添加了一个“动态标志”字段(用于新的灵活方法协议),以及一个事务 ID 和方法序数,我们需要这两个字段来处理 FDomain 消息。
最初,计划是使用持久性标头,并手动添加事务 ID 和方法序数,以创建新的组合标头格式。FDomain 的所有方法都很灵活,因此动态标志字段的值可以是隐式的。
在审核过程中,FIDL 团队发现组合标头与事务标头非常相似,因此建议只使用常规的 FIDL 事务标头。本 RFC 将采用该提案,前提是 FIDL 维护者接受这可能会改变事务标头格式演变压力的任何方式。
实现
目标端 FDomain 支持可在单个 Rust crate 中提供,第一个集成商可能为 Remote Control Service。
首先,可以将传输到设备的传输作为通过旧版 Overnet 协议分发的套接字提供,也可以作为该协议下层中的额外电路提供。然后,我们可以选择通过 SSH 实现直接传输,也可以直接实现自定义传输。
在目标平台方面,需要进行更多工作。目前,通过 Overnet 进行的主机端 FIDL 通信依赖于对 Fuchsia 内核基元(位于 fuchsia-async 库中)的模拟。FDomain 专门用于在非 Fuchsia 目标平台上使用,不应依赖于此模拟。为此,我们需要为 FDomain 用户提供一种新的 Rust FIDL 绑定,该绑定可在 FDomain 基元集之上运行。这些基元将代表 FDomain 中的各个句柄,但不会尝试完美地模拟 Fuchsia 内核接口。事实上,最好明确说明这些句柄是远程对象的句柄,因为它们的 API 可能会显示与该场景相关的唯一失败条件相关的错误。与 Overnet 相比,如果传输或内部协议实现出现任何故障,都会导致用户获得不区分类型的 ZX_ERR_PEER_CLOSED
。
Migration
FDomain 目标端实现将依赖于 Zircon API 来操控句柄。由于 Overnet 提供了这些 API,因此应该能够在 Overnet 节点上运行 FDomain。
这是我们在 FFX 中进行迁移的初始策略。我们将在 FFX 守护程序中托管 FDomain,该守护程序会从旧版 Overnet 连接获取 RCS 代理,并在其命名空间中公开该代理。主机可以使用魔法发起器请求将新的套接字连接转换为普通 Overnet 连接或 FDomain 连接(目前,Overnet 连接以魔法字符串“CIRCUIT\0”开头,因此我们已经在此位置查找魔法字符串)。
从这里开始,FFX 工具框架将能够向工具提供 Overnet 或 FDomain 代理,如果建立了两个连接,则可能同时提供这两种代理。这样一来,我们就可以轻松将各个工具迁移到 FDomain 宿主端代码。在守护程序本身中托管的服务不便从 FDomain 使用,但其他架构压力已经促使此类服务被废弃或重新考虑。
由于 FFX 插件通常只使用 FIDL 代理,因此我们应该能够迁移到由目标直接托管的 FDomain 连接,而无需对其进行进一步更改。我们应该能够并行支持 Overnet 上的连接和直接 FDomain 连接,就像我们在进行电路切换型 Overnet 迁移时所做的那样。
性能
FDomain 的现有应用都是目前由 Overnet 处理的开发者工具。由于开发者工具通常没有严格的延迟时间或大带宽要求,因此从未认真尝试对 Overnet 性能进行基准测试。
尽管如此,关于 Overnet 性能的问题已经开始出现,至少确定性能预期可能很有价值。因此,应进行粗略的测试来检查以下各项:
- 与套接字通信、阻塞和流式传输时的吞吐量。
- 直接向简单接口发出 FIDL 调用时的延迟时间。
- 重复进行 FIDL 调用的延迟时间,其中每次调用都是在响应上一个调用时发送的通道上进行的。这将确定如何在对性能敏感的应用中显示创建操作的处理方式。
向后兼容性
FDomain 将代表与 Overnet 协议的彻底决裂。在完全拆除 Overnet 之前,我们会保留这两种协议一段时间,以便保持向后兼容性。
安全注意事项
FDomain 核心协议不会直接处理身份验证,并且旨在允许对其控制的句柄集合进行任意访问。应以这样的方式部署,以便仅向可安全地与访问者共享的句柄授予访问权限,并且在授予此类访问权限的环境中是安全的。我们已考虑过这些问题,因此接管其应用后,这些问题应该会得到解决。
在未来的应用中,提权问题会更加严重(Overnet 及其替代品本质上都是特许权限应用),我们可以想象这样一类攻击:攻击者使用 FDomain 操控实际上不应在 FDomain 中的句柄。遵循本文档中的准则,即在 FDomain 协议中不使用实际内核句柄编号作为句柄 ID,应该可以减少此类攻击的所有明显途径。由于 Rust 语言具有强大的所有权语义,因此在 Rust 中实现 FDomain 还可以更轻松地防范这些攻击。
隐私注意事项
FDomain 适用于开发中的设备。不应存在独特的隐私问题。即使在意外的应用中,除安全问题外,也不太可能存在独特的隐私问题。
测试
我们已成功使用 Overnet 生成在单个进程中托管的集成测试,其中协议状态的多个实例只需将一个实例的字节输出提供给另一个实例的输入,即可相互连接。这样,我们就可以测试各种配置和拓扑。
涉及真实联网组件的集成测试更为困难,但可能不会带来太多额外价值,并且可能会引入复杂的实际测试设置固有的不稳定风险。我们将在基础架构可用的情况下使用相应设施进行此类测试,并随着可用性变化扩大此类测试的范围。
文档
除了以常规方式记录实现交付成果之外,树中还应包含协议规范的实际形式。此部分最初将主要由本 RFC 的设计部分的文本组成,但可以根据需要调整以纳入协议扩展。
缺点、替代方案和未知情况
与 Overnet 相比,FDomain 大大降低了复杂性。此外,它必须从头开始实现,并且与 Overnet 不兼容。
远程工具协议的替代设计空间非常广阔,因此,假设需要新协议,列举替代方案是一项耗时且低效的任务。我们本可以选择自定义二进制格式而非 FIDL,从而有望更好地控制兼容性。或者,我们也可以提供一种 IPC 机制,该机制根本不代表内核对象,而是为工具提供特定的定制接口。
如果 Fuchsia 的用例扩展,FDomain 的其他用途可能会随之出现,因为我们希望减少重复工作并提供更少的协议。我们可以想象在数据中心内,FDomain 取代 SSH 作为远程管理传输协议,而不仅仅是诊断工具。这里介绍的 FDomain 协议应该对此类任务普遍有用,但我们无法确定它如何扩展到此类任务。
在先技术和参考文档
如需详细了解该协议,请参阅 Fuchsia 树中的 Overnet 文档。