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 的方法存在一些缺点:
- 主机上的内核句柄模拟肯定不完美。句柄仅限于单个进程,并且需要额外的无线连接才能在主机上的进程之间移动句柄。某些机制(例如 zx_channel_call)完全缺失。
- 从句柄报告错误时,并不是要考虑由复杂且易失误的网络堆栈支持句柄。Overnet 句柄发生的任何服务中断问题最终都会报告为 PEER_CLOSED,并且没有更多信息。
- 内核句柄不适用于通过网络代理。Overnet 的设计和实现没有经过内核团队的充分咨询。因此,使用当前的 API 无法正确实现某些功能(例如对象信号),并且可能仍然不可能。
- 代理句柄的承诺以及让其在主机上的远程行为与其在目标本地上的行为大体相同,使得支持某些对象(例如 VMO)几乎是完全不可能的。
除了方法方面的问题,Overnet 的具体设计和实现还存在以下显著缺点:
- Overnet 的低级别协议(用于协调数据流)使用 FIDL 编写,但不按照其设计方式使用 FIDL。相反,FIDL 以类似于 protobuf 的角色指定数据包的二进制形状,但在传输方面没有任何角色。这使得 Overnet 容易受到 FIDL 中传输格式变更的影响,而 FIDL 团队无法像对通过正常传输方式运营的 FIDL 用户那样进行管理。
- 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 的 Futures 系统处理异步操作。我们很可能会在该层实现 FDomain 主机端库,而不会提供可让您直接访问非阻塞操作的较低级别的句柄。这会导致对象创建方面存在一些语义差异。此外,通常仅使用原始句柄(而非异步版本)的操作(例如在 FIDL API 中创建端点)无法使用 FDomain 进行这种区分。
- FDomain 句柄必须是远程连接的 FDomain 的一部分。在我们的 API 中代表它们的 RAII 对象将基于连接对象生成,而不是就地构建,并且可能具有反映其生命周期与连接生命周期关联的行为或类型属性。(由于有关设计的这部分存在一些争论,我们将强调此 RFC 在此处没有给出具体建议)。
- FDomain 操作将返回不同且更丰富的错误类型,以反映远程协议可能遇到的更复杂的故障模式(以及我们在远程环境中可以承受的更丰富的错误报告)。Rust 围绕错误处理的编程惯例应能让用户非常轻松地浏览此更改。
设计
从概念上讲,FDomain 是可通过一组操作远程操纵的句柄集合。FDomain 协议通过 FIDL 协议呈现这些操作。
FDomain 连接到主机并由主机操作,FDomain 和主机之间的连接是 FDomain 连接。一个 FDomain 最多只能有一个已连接的主机,这意味着主机绝不应观察它控制的 FDomain 中的句柄,这些句柄是被其他操作方关闭、写入或被读取的。虽然会话恢复可能是未来需要关注的一个方面,但我们假设主机始终连接到一个新的 FDomain,这是一个预先填充的句柄集合,当主机断开连接时,FDomain 会被销毁,其中的句柄也会关闭。
核心协议
由于 FDomain 的要点是通过不支持传输内核句柄的连接提供 FIDL,并且通常传送到也不支持内核句柄的主机,因此 FDomain FIDL 本身无法在其方法参数或返回值中使用句柄。具体而言,resource
关键字绝不能出现在 FDomain 的 FIDL 规范中。
由于 FDomain 的协议消息设计为通过除通道以外的媒体传输,我们假定没有消息大小限制,因此不限制协议内向量和其他结构的大小。
必须启用 Error Reporting
所有方法都必须能够返回错误。由于我们的错误类型是可扩展的,因此我们允许未来为所有方法添加错误条件。
此外,这会强制所有方法均为双向方法,这意味着 (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 维护人员接受可能改变事务标头格式演变压力的任何方式。
实现
目标端 FDomain 支持可以在单个 Rust crate 中提供,第一个集成商可能是遥控器服务。
到设备的传输可以首先作为通过旧版 Overnet 协议分发的套接字提供,也可以作为该协议较低层中的附加线路提供。在这里,我们可以选择通过 SSH 实现直接传输,或者直接实现特定的传输。
目标方面还需要做更多工作。目前通过 Overnet 进行的主机端 FIDL 通信依赖于对位于 fuchsia-async 库的 Fuchsia 内核基元的模拟。FDomain 明确设计用于从非紫红色目标使用,不应依赖于此模拟。为此,我们将要求为 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 连接(目前,无线局域网连接以魔法字符串“CIRCUIT\0”开头,所以我们已经在此位置寻找魔法)。
从这里开始,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)应该可以缓解此类攻击的所有显而易见的向量。由于 FDomain 具有强大的所有权语义,在 Rust 中实现 FDomain 也会使这些攻击更容易防御。
隐私注意事项
FDomain 适用于处于开发阶段的设备。它不应具有独特的隐私问题。即使在意料之外的应用中,除了安全问题外,也不太可能出现独特的隐私权问题。
测试
我们成功利用 Overnet 生成了在单个进程内托管的集成测试,在这种测试中,只需提供从一个实例输出到另一个实例的字节输出,多个协议状态实例即可相互连接。这样,我们应该可以测试各种配置和拓扑。
涉及真实、联网组件的集成测试更加困难,但可能无法增加很多额外价值,并且可能会引入复杂物理测试设置中固有的不稳定风险。我们将在基础架构准备就绪的情况下使用设施进行此类测试,并在可用性发生变化时扩展测试。
文档
除了以常规方式记录实现交付项之外,树中还应存在协议规范的有效形式。这最初主要包含此 RFC 设计部分的文字,但可以根据需要进行调整,以纳入协议扩展。
缺点、替代方案和未知情况
与 Overnet 相比,FDomain 可大幅降低复杂性。它还必须从头开始实现,且不向后兼容 Overnet。
远程工具协议的替代设计空间非常庞大,因此在需要新协议的假设下,枚举替代方案是一项耗时而耗时的任务。我们本可以选择自定义二进制格式,而非 FIDL,因此可能可以更好地控制我们的兼容性故事。或者,我们也可以提供一种完全不表示内核对象的 IPC 机制,而是为工具提供定制的专用接口。
如果 Fuchsia 的用例得到扩展,出于减少重复工作和提供协议的意愿,FDomain 的其他用途很有可能出现。可以设想数据中心内的 Fuchsia 服务器,通过 FDomain 取代 SSH,作为远程管理传输服务,而不仅仅是一个诊断工具。一般来说,此处介绍的 FDomain 协议应该可以用于此类任务,但我们无法确切了解它如何扩展到此类任务。
早期技术和参考资料
如需详细了解该协议,请参阅 Fuchsia 树中的 Overnet 文档。