| 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 的设计方式。相反,FIDL 扮演着类似于 protobuf 的角色,用于指定数据包的二进制形状,但在传输方面没有任何作用。这使得 Overnet 容易受到 FIDL 中线格式更改的影响,而 FIDL 团队无法像管理通过正常传输运行的 FIDL 用户那样管理 Overnet。
- Overnet 的代码过于复杂,并且使用了已被证明极难调试的异步模式。
- Overnet 被设计为对等网状网络,这会增加复杂性,而对于任何当前的使用场景来说,这种复杂性都是不必要的。
利益相关方
辅导员:
hjfreyer@
审核者:
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 最多只能有一个连接的主机,这意味着主机永远不应观察到其控制的 FDomain 中的句柄被其他 actor 关闭、写入或读取。虽然会话恢复可能是未来研究的一个有趣领域,但本文档假设主机始终连接到新的 FDomain(预先填充了句柄的集合),并且当主机断开连接时,FDomain 会被销毁,其中的句柄也会被关闭。
核心协议
由于 FDomain 的目的是通过不支持传输内核句柄的连接来呈现 FIDL,并且通常是呈现给也不支持内核句柄的主机,因此 FDomain FIDL 本身无法在其方法参数或返回值中的任何位置使用句柄。具体来说,resource 关键字绝不能出现在 FDomain 的 FIDL 规范中。
由于 FDomain 的协议消息旨在通过渠道以外的媒体进行传输,因此我们假设没有消息大小限制,相应地,也不会限制协议中向量和其他结构的大小。
必须提供错误报告
所有方法都必须能够返回错误。由于我们的错误类型是可扩展的,因此我们可以在未来向所有方法添加错误条件。
此外,这会强制所有方法都成为双向方法,这意味着(RFC-0138)RFC-0138 中指定的未知互动处理将始终能够向主机返回有关未知序号的错误。根据“智能发送方,暗淡接收方”原则,所有兼容性错误都将在主机上显示,这应该会使我们的向后兼容性方案更易于实现。
处理 ID 分配
FDomain 中的每个句柄都将通过 ID 由宿主引用。这些 ID 在协议中编码为 32 位无符号整数,但不应是直接向主机公开的实际内核句柄编号。
为了减少协议中执行操作所需的往返次数,我们将尽可能使用主机端 ID 分配。这意味着,如果某项操作会在 FDomain 内生成新句柄,例如当宿主请求创建新渠道或套接字时,宿主会提供新渠道将使用的 ID 编号。这样一来,协议中的流水线处理效果会更好,因为主机可以在同一事务中提交创建句柄的请求以及对该句柄的第一个操作,而无需等待每个操作的回复。
此政策的例外情况是来自渠道的读取,这可能会产生任意数量的句柄。要求宿主为可能通过读取渠道产生的句柄分配 ID,这使得提交读取请求变得笨拙,并且需要指定复杂的阻塞语义才能与流式读取兼容。因此,通过读取渠道生成的句柄将由 FDomain 本身分配 ID。
引导
当主机连接到 FDomain 时,系统会根据上下文假设主机已填充一些起始句柄。这些句柄类似于提供给新启动的组件的句柄。FDomain 将允许宿主以支持该类比的方式检索这些句柄,从而保留用户直觉,即 FDomain 是组件拓扑中的一个节点。
处理操作
FDomain 将支持在 FDomain 内部创建新的套接字、渠道、事件对和事件。它还将支持关闭和复制句柄,以及使用具有不同权限的新版本替换句柄,类似于 zx_handle_replace。
对于可能需要重试(即可能因 ZX_ERR_SHOULD_WAIT 而失败)的读取、写入和其他操作,FDomain 协议将实现挂起获取,并在目标端执行必要的端口等待操作。这样可以避免要求主机手动设置端口所带来的不便和大量往返。
稳定的有线编码
FIDL 的事务格式并非设计为在 Fuchsia 渠道之外传输。如果需要在其他上下文中序列化 FIDL,建议使用 RFC-0120 中所述的持久性标头。
持久性标头通常用于将要静态存储的 FIDL 数据。虽然 FDomain 消息不会像通常那样通过渠道传输,但仍应以常规的请求-响应方式发送和接收。
持久性标头和事务标头都包含一个魔数和一系列兼容性标志。交易标头还添加了“动态标志”字段(用于新的灵活方法协议)以及交易 ID 和方法序号,这两者都是 FDomain 消息所必需的。
最初,该计划是使用持久性标头,并通过手动添加交易 ID 和方法序号来创建新的组合标头格式。FDomain 的所有方法都很灵活,因此动态标志字段的值可以是隐式的。
在审核时,FIDL 团队注意到,组合标头与事务标头非常相似,因此他们建议直接使用正常的 FIDL 事务标头。此 RFC 将采用该提案,前提是 FIDL 维护人员接受此提案可能对交易标头格式的演变压力造成的任何变化。
实现
可以在单个 Rust crate 中提供目标端 FDomain 支持,第一个集成者可能是远程控制服务。
到设备的传输可以首先作为通过旧版 Overnet 协议分发的套接字提供,也可以作为所述协议的较低层中的附加电路提供。然后,我们可以选择通过 SSH 实现直接传输,也可以直接实现自定义传输。
需要从目标端进行更多工作。当前通过 Overnet 进行的宿主端 FIDL 通信依赖于 fuchsia-async 库中 Fuchsia 的内核原语的模拟。FDomain 专门设计用于非 Fuchsia 目标平台,不应依赖此模拟。为了实现这一点,我们将需要一种新的 Rust FIDL 绑定,供 FDomain 用户使用,该绑定基于一组 FDomain 原语运行。这些原语将表示 FDomain 中的各个句柄,但不会尝试完美地模拟 Fuchsia 内核接口。实际上,最好让这些句柄明确表明它们是远程对象的句柄,因为它们的 API 可能会公开与该场景相关的独特故障情况。与 Overnet 相比,在 Overnet 中,传输或内部协议实现的任何失败都会导致用户收到无差别的 ZX_ERR_PEER_CLOSED。
Migration
FDomain 目标端实现将依赖于用于操作句柄的 Zircon API。由于 Overnet 提供这些 API,因此应该可以在 Overnet 节点上运行 FDomain。
这将是我们最初的 FFX 迁移策略。我们将在 FFX daemon 中托管一个 FDomain,该 FDomain 从旧版 Overnet 连接获取 RCS 代理,并在其命名空间中公开该代理。主机可以使用 magic 发起方来请求新的套接字连接成为正常的 Overnet 连接或 FDomain 连接(目前,Overnet 连接以 magic 字符串“CIRCUIT\0”开头,因此我们已经在此位置寻找 magic)。
这样一来,FFX 工具框架将能够为工具提供 Overnet 或 FDomain 代理,如果建立了两个连接,则可能同时提供这两种代理。这样一来,我们应该能够轻松地将各个工具迁移到 FDomain 宿主端代码。从 FDomain 使用在守护程序本身中托管的服务不太方便,但其他架构压力已促使此类服务消失或重新考虑。
由于 FFX 插件通常只使用 FIDL 代理,因此我们应该能够迁移到由目标直接托管的 FDomain 连接,而无需对它们进行进一步更改。我们应该能够同时支持基于 Overnet 的连接和直接 FDomain 连接,就像我们之前在电路交换 Overnet 迁移中所做的那样。
性能
FDomain 的现有应用目前都是由 Overnet 处理的开发者工具。由于开发者工具通常对延迟时间或带宽没有严格的要求,因此从未有人认真尝试对 Overnet 性能进行基准比较。
不过,人们开始对 Overnet 的性能提出疑问,因此至少确定性能预期可能是有价值的。因此,应进行粗略的测试,以检查以下各项:
- 与套接字通信的吞吐量,包括阻塞和流式传输。
- 直接向简单接口发出 FIDL 调用时的延迟时间。
- 在重复进行 FIDL 调用时(其中每次调用都是在作为对上一次调用的回复而发送的通道上进行的)出现的延迟。这将确定句柄创建操作在对性能敏感的应用中的显示方式。
向后兼容性
FDomain 将代表与 Overnet 协议的彻底决裂。在完全停用 Overnet 之前,我们将同时维护这两种协议一段时间,以保持向后兼容性。
安全注意事项
FDomain 核心协议不直接处理身份验证,旨在允许对它控制的句柄集合进行任意访问。应以仅向可安全与访问者共享的句柄授予访问权限的方式进行部署,并且应在授予此类访问权限的环境中进行部署。Overnet 已经考虑了这些因素,因此接管其应用应该可以确保这些方面。
在未来更关注提权问题的应用中(Overnet 以及取代它的任何应用本质上都是最高权限的应用),可以设想这样一类攻击:攻击者会操纵具有 FDomain 的句柄,而这些句柄实际上并不打算位于 FDomain 中。遵循本文档中的准则,即不在 FDomain 协议中使用实际的内核句柄编号作为句柄 ID,应该可以缓解此类攻击的所有明显途径。由于 Rust 语言具有强大的所有权语义,因此在 Rust 中实现 FDomain 也会使这些攻击更易于防御。
隐私注意事项
FDomain 旨在用于开发中的设备。不应有独特的隐私问题。即使在意外的应用中,除了由安全问题引起的隐私问题外,也不太可能存在独特的隐私问题。
测试
我们已成功使用 Overnet 生成在单个进程中托管的集成测试,其中协议状态的多个实例通过将一个实例的字节输出提供给另一个实例的输入来相互连接。这样一来,我们就可以测试各种配置和拓扑。
涉及真实联网组件的集成测试难度更大,但可能不会增加太多额外价值,并且可能会带来复杂物理测试设置中固有的不确定性风险。我们将使用基础设施随时可用的设施进行此类测试,并随着可用性的变化扩大测试范围。
文档
除了以常规方式记录实现交付项之外,树中还应存在协议规范的动态形式。这最初将主要由本 RFC 的设计部分中的文本组成,但可以根据需要进行调整以纳入协议扩展。
缺点、替代方案和未知因素
与 Overnet 相比,FDomain 可显著降低复杂性。它还必须从头开始实现,并且与 Overnet 不向后兼容。
远程工具协议的替代设计空间非常广阔,因此,假设需要新协议的情况下,列举替代方案是一项漫长而低效的任务。我们本可以选择自定义二进制格式而非 FIDL,从而可能更好地控制兼容性。或者,我们也可以提供一种完全不代表内核对象的 IPC 机制,而是为工具提供特定的自定义接口。
如果 Fuchsia 的使用场景扩大,那么出于减少重复工作和提供更少协议的愿望,FDomain 的其他用途可能会浮出水面。可以设想,数据中心内的 Fuchsia 服务器使用 FDomain 代替 SSH 作为远程管理传输,而不仅仅是诊断工具。此处描述的 FDomain 协议应该对这类任务普遍有用,但我们无法确定它是否能扩展到此类任务。
在先技术和参考资料
如需详细了解该协议,请参阅 Fuchsia 树中的 Overnet 文档。