网络
规定 virtio-net 网络设备的数据路径、静态网络身份、包转发、出站策略、宿主本地访问与入站、DNS 以及有界状态与快照恢复行为。
译自microsoft/nvx · doc/design/3.5-network.md。技术细节以英文原文为准。
如图 3.3 所示,网络设备是一个可选的 virtio-net 网卡,由 VMM 内的进程内用户态网络栈支撑。该栈把客户机流量转换为普通的宿主套接字操作。它不会把客户机挂接到物理网卡、TAP 接口、网桥或宿主虚拟交换机上,宿主也不需要持有客户机网关地址的接口。
图 3.3. 网络数据路径。
图中结构(自上而下排列,↔ 表示双向交互):
- 客户机应用与 Linux TCP/IP 栈(Guest application and Linux TCP/IP stack)
- ↔ 客户机 virtio-net 驱动(Guest virtio-net driver)
- ↔ 客户机 RAM 中的 TX/RX virtqueue(TX/RX virtqueues in guest RAM),含 MMIO 通知与中断
- ↔ VMM virtio-net 设备(VMM virtio-net device),含客户机 TX 头部预过滤
- ↔ 进程内网络端点(In-process network endpoint),负责最终的客户机 TX 授权
- ↔ 宿主套接字与 DNS 解析器(Host sockets and DNS resolver)
- ↔ 宿主操作系统路由、防火墙和网络(Host OS routing, firewall, and network)
- ↔ 远程服务器或被允许的宿主本地服务(Remote server or permitted host-local service)
- ↔ 宿主操作系统路由、防火墙和网络(Host OS routing, firewall, and network)
- ↔ 宿主套接字与 DNS 解析器(Host sockets and DNS resolver)
- ↔ 进程内网络端点(In-process network endpoint),负责最终的客户机 TX 授权
- ↔ VMM virtio-net 设备(VMM virtio-net device),含客户机 TX 头部预过滤
- ↔ 客户机 RAM 中的 TX/RX virtqueue(TX/RX virtqueues in guest RAM),含 MMIO 通知与中断
- ↔ 客户机 virtio-net 驱动(Guest virtio-net driver)
3.5.1 设备与网络身份
该网卡占据固定的网络插槽,只有一对 RX/TX 队列,并公布一个精确的特性掩码:MAC 地址特性和 virtio 版本 1。它的中断是机器契约中唯一与后端相关的值:KVM 和 MSHV 上为 IRQ 10,WHP 上为 IRQ 5。可移植的网络配置文件是必需的,并在每个后端上选择相同的进程内数据平面。
表 3.12 展示了宿主所拥有的静态网络身份示例。该身份由以下部分组成:
- 一个从
/1到/30的 IPv4 前缀,其第一个可用地址就是网关。 - 由最后三个 IPv4 八位组派生的确定性客户机与网关 MAC 地址。
- 一个派生的 IPv6 身份,它把客户机与网关的 IPv4 地址嵌入
fd00::/96,前缀长度为 96 加上 IPv4 前缀长度,其网关与 IPv4 网关共享同一个 MAC 地址。
表 3.12. 网络身份示例。
| 设置 | 示例 |
|---|---|
| 客户机 IPv4 地址 | 10.0.0.2/24 |
| IPv4 网关 | 10.0.0.1 |
| 客户机 IPv6 地址 | fd00::a00:2/120 |
| IPv6 网关 | fd00::a00:1 |
客户机根据机器配置文件所掌管的命令行令牌配置该身份及其默认路由,不使用 DHCP、路由器通告、地址自动配置或重复地址检测。它由内核派生的链路本地地址是它唯一的其他 IPv6 地址,出站策略仅允许该地址作为邻居请求的源地址。该身份不公布任何可路由的 IPv6 前缀,因此外部 IPv6 可达性取决于宿主自身的路由和地址族支持。
3.5.2 数据包转发
客户机发送遵循以下路径:
- 客户机网络栈构造一个以太网帧,驱动把它的缓冲区放入 TX virtqueue。
- 驱动通知设备,设备读取描述符并针对出站策略预过滤帧头。
- 端点把完整的帧复制到一个自有缓冲区,对这些确切字节进行授权,并把被允许的帧交给网络栈。
- 对于新的 TCP 连接,栈把一个宿主 TCP 套接字连接到目的地。对于 UDP,它通过一个宿主 UDP 套接字发送数据报的负载。
TCP 转发是套接字代理,而不是数据包改写。进程内栈终止面向客户机的连接,而面向远端的连接由宿主内核拥有,两者各自拥有独立的序列号、确认和缓冲。远端对端通常看到的是宿主的网络身份。宿主路由、VPN 配置和宿主防火墙规则仍然管辖这些宿主套接字,宿主不需要 IP 转发。
到达宿主套接字的数据会与其面向客户机的流关联,被重新构造成以太网、IP 和 TCP 或 UDP 数据包,并写入客户机在 RX virtqueue 上提交的缓冲区,随后发出已用缓冲区中断。
ARP 和 IPv6 邻居发现都在本地应答,而不是在物理局域网上广播。网络配置文件提供基于 IPv4 和 IPv6 的出站 TCP 与 UDP。端点自己应答发往任一网关的回显请求,通过宿主 ICMP 套接字中继其他 IPv4 回显请求,并且不把 ICMPv6 回显中继到其他目的地。它确定性地拒绝分片的 IPv4 数据包、IPv6 扩展头以及 IPv4 映射的 IPv6 目的地。
3.5.3 出站策略
默认情况下允许客户机出站,并拒绝一般的宿主入站。在任何对外可见的传输之前,都会应用一份规范化的出站策略。通用策略有一个明确的默认动作(allow 或 deny),以及至多 256 条允许规则和 256 条拒绝规则。每条规则按 IPv4 或 IPv6 地址或 CIDR 选择目的地,可选地限定为 TCP、UDP 或 ICMP,并且对 TCP 或 UDP 还可以限定到目的端口或一个包含两端的端口范围。求值结果与规则顺序无关:
- 匹配的拒绝规则拒绝该数据包。
- 否则,匹配的允许规则允许它。
- 否则,由默认动作决定。
一条规则只匹配与其自身地址族相同的目的地。规范化策略还支持仅限 IPv4 的宿主允许列表、宿主阻止列表和精确 TCP 端点允许列表;这些形式拒绝所有 IPv6 流量,并且不与通用规则组合使用。
只有当策略允许一切时过滤才不生效。否则,数据包检查会强制执行绑定的客户机 MAC 和 IP 身份,并校验头部(而不仅仅是目的地),端口特定的规则对格式错误或分片的流量失败关闭。仅靠设备的早期头部检查是不够的,因为在它之后客户机内存仍然可修改,因此端点会在帧的完整自有副本上重复授权。于是客户机无法把已检查的帧改写成另一个目的地。被拒绝的帧会被丢弃,其 TX 描述符在没有宿主传输的情况下完成。
这些规则是基于目的地的 L3 和 L4 规则,而不是主机名或内容过滤器。它们适用于客户机发送的每一个帧,包括被转发连接上的回复,但它们不是接收防火墙,也不是宿主级防火墙。在宿主发布客户机监听器期间,该策略还允许客户机在端点所承载的流上的回复,正如宿主本地访问与入站所描述的。
3.5.4 宿主本地访问与入站
默认情况下,并在出站策略约束下,网关把客户机 TCP 和 UDP 流映射到宿主回环:IPv4 网关映射到 127.0.0.1,IPv6 网关映射到 ::1。这些流上的回复来自客户机所寻址的网关地址和端口,这是已连接 UDP 套接字的要求。客户机自身的回环仍然是客户机回环。
其他宿主本地目的地(例如链路本地地址)同样可以到达宿主套接字,除非宿主回环被拒绝。
拒绝宿主回环会禁用网关到回环的映射,阻止回环、未指定和链路本地目的地,并禁止所有转发。仍可保留一个精确的 IPv4 网关 TCP 代理端点。它被绑定进策略摘要,在策略模式求值之前被准入,并被转换到宿主 127.0.0.1 上的同一端口,由启动器在那里提供代理服务。
出站连接绝不会让客户机服务可从宿主或其网络到达。如表 3.13 所示,入站访问是宿主的一项独立决定。
表 3.13. 对客户机服务的入站访问。
| 入站访问 | 客户机监听器发布在 | 准入的客户端 |
|---|---|---|
| 无,默认情形 | 不发布到任何宿主地址 | 无 |
| 宿主回环,在显式的宿主回环允许下 | 宿主回环,127.0.0.1 和 ::1 |
宿主 |
| 私有网络,在入站允许下 | 每个宿主地址 | 宿主私有网络上的对端,以及在显式宿主回环允许下的宿主 |
显式的宿主回环允许会让宿主回环访问变成双向的:除了网关映射之外,它还在宿主回环上发布客户机的监听器,并准入来自宿主 127.0.0.1 端口到客户机端口的显式 TCP 或 UDP 转发,至多 64 条。两者都不会绑定面向局域网的监听器。
宿主的这项决定不指定任何端口,而端点本身也不拥有监听器,因此发布遵循客户机实际监听的端口。机器配置文件通过调用方无法提供的启动令牌把入站模式传给客户机,随后客户机代理通告客户机的监听器。它把每条通告作为单个 UDP 数据报,从一个特权源端口发往 IPv4 网关的固定端口;工作负载不持有任何能力,因此无法绑定该端口,也就无法伪造通告。通告携带一个魔数、一个版本、一个标记被省略监听器的标志,以及至多 64 个按协议和端口严格递增排列的条目,每个条目指明协议(TCP 或 UDP)、地址族和端口。端点在出站策略之前消费这些通告,出站策略既不授权也不转发它们,并且每条通告都会替换已发布的集合。对每个已发布的端口和地址族,端点都在客户机的端口号上保留一个宿主监听器:
- 私有网络入站在每个宿主地址上监听,并且只中继处于非全局可达范围内的对端:IPv4 的专用、共享、链路本地、基准测试和文档地址段,以及 IPv6 的唯一本地、站点本地、文档和基准测试地址段。它拒绝位于客户机自身子网内的对端,因为客户机会把这些对端当作自己的网关或邻居。位于宿主自身地址上的对端(包括回环)需要显式的宿主回环允许。
- 宿主回环发布在没有入站允许时,只在该地址族的回环地址上监听,并且只准入宿主。它绝不会顶替已经占用该端口的宿主监听器,无论该监听器在回环上还是在每个宿主地址上。
宿主无法绑定的端口(例如已被宿主进程占用)会在下一条通告时重试。在客户机的监听器被发布期间,出站策略还准入针对客户机子网的 ARP 请求和邻居请求,其中网关呈现宿主对端。当某个帧延续端点所承载的流时,即使规则拒绝它,该策略也会准入它:初始 SYN 之外的 TCP 段,或者从已发布端口发往最近曾向其发送过数据报的对端的 UDP 数据报(该数据报经由该端口的宿主监听器离开)。
端点把其他宿主对端(例如已发布监听器的宿主客户端)呈现为位于客户机各地址族子网内的合成地址,从该子网的最后一个可用地址开始向下分配,例如对于表 3.12 的身份是 10.0.0.254 和 fd00::a00:ff。因此显式的宿主回环访问要求 IPv4 网络为 /29 或更大:/30 只容纳网关和客户机,没有留给宿主对端的地址。客户机通过其网卡而不是自身回环把回复路由到合成地址,并从该子网中它自己的地址作答,而端点在发送时把合成地址转换回去。出站策略针对合成地址所代表的宿主地址评估发往合成地址的客户机流量,针对宿主回环则评估其地址族的网关,因此合成地址不会到达那些规则在网关处拒绝的宿主回环端口。设备的早期头部检查并不了解合成地址,把它们留给端点处理。
3.5.5 DNS
网关在 UDP 和 TCP 的 53 端口上提供 DNS 代理。当出站策略允许对 IPv4 网关使用 DNS 时,客户机被指向 IPv4 网关;否则,当通用规则允许在 IPv6 网关使用 DNS 时,客户机被指向 IPv6 网关。精确端点策略从不指定网关,而在没有被允许的网关时客户机得不到任何 DNS 服务器。
客户机的查询在网关处理它之前先通过常规出站检查。上游解析使用宿主解析器,其上游服务器不是客户机的数据包目的地,因此客户机规则不检查它们。客户机直接向外部 DNS 服务器发起的查询走普通出站通路,需要针对该服务器的许可。DNS 许可与连接许可彼此独立:解析一个名称绝不授权对解析结果的连接。拒绝宿主回环不会禁用出站策略所允许的网关 DNS。
3.5.6 有界状态、快照与恢复
流、DNS、缓冲区和队列状态都是有界的。同时最多有 128 条 TCP、256 条 UDP 和 16 条 ICMP 客户机流处于活跃状态,超出的流在创建宿主套接字之前就被拒绝。最多可以有 256 个 DNS 请求处于待处理状态。解析器失败,或超出该上限的 UDP 查询,会收到 SERVFAIL;超出该上限的 TCP 查询会重置其连接。
快照记录网络配置文件、身份和策略摘要。恢复会重建一个全新的端点,并要求相同的网络配置文件和策略;原生套接字、DNS 请求和流绝不会被序列化。已发布的客户机监听器和显式的宿主转发属于进程级附件,因此捕获和恢复会拒绝私有网络入站和显式宿主回环访问,因为它们会创建这些附件。
捕获会静默端点、排空完成所有权,并要求已保存的队列中不包含任何未被表示的 RX 或 TX 数据包。它保存队列生命周期、协商出的特性、链路状态和端点世代,但不保存在途的数据包负载。恢复之后,捕获前的数据包不会被重放,早先的 TCP、UDP、ICMP 和 DNS 状态无效,流量会建立全新的流。