Open vSwitch 数据路径开发者文档

Open vSwitch 内核模块允许对选定的网络设备上的数据包进行灵活的用户空间流级别控制。它可以用来实现普通的以太网交换机、网络设备绑定、VLAN 处理、网络访问控制、基于流的网络控制等等。

内核模块实现多个“数据路径”(类似于网桥),每个数据路径可以有多个“vport”(类似于网桥内的端口)。每个数据路径还关联一个“流表”,用户空间在其中填充“流”,这些流将基于数据包头和元数据的键映射到一系列操作。最常见的操作是将数据包转发到另一个 vport;还实现了其他操作。

当数据包到达 vport 时,内核模块通过提取其流键并在流表中查找它来处理该数据包。如果存在匹配的流,则执行关联的操作。如果没有匹配项,则将数据包排队到用户空间进行处理(作为其处理的一部分,用户空间可能会设置一个流,以完全在内核中处理同一类型的进一步数据包)。

流键兼容性

网络协议随着时间的推移而发展。新的协议变得重要,而现有的协议则失去了重要性。为了使 Open vSwitch 内核模块保持相关性,较新的版本必须能够解析其他协议作为流键的一部分。将来,甚至可能希望放弃对已过时的协议的解析支持。因此,Open vSwitch 的 Netlink 接口旨在允许精心编写的用户空间应用程序使用任何版本的流键,无论过去还是将来。

为了支持这种向前和向后兼容性,每当内核模块将数据包传递给用户空间时,它还会传递从数据包解析的流键。然后,用户空间从数据包中提取其自己的流键概念,并将其与内核提供的版本进行比较。

  • 如果用户空间的流键概念与内核的流键概念匹配,则不需要任何特殊操作。

  • 如果内核的流键包含比用户空间版本的流键更多的字段,例如,如果内核解码了 IPv6 标头,但用户空间在以太网类型处停止(因为它不理解 IPv6),则同样不需要任何特殊操作。用户空间仍然可以像往常一样设置流,只要它使用内核提供的流键即可。

  • 如果用户空间的流键包含比内核的更多的字段,例如,如果用户空间解码了 IPv6 标头,但内核在以太网类型处停止,则用户空间可以手动转发数据包,而无需在内核中设置流。这种情况对性能不利,因为内核认为属于流的每个数据包都必须进入用户空间,但是转发行为是正确的。(如果用户空间可以确定额外字段的值不会影响转发行为,那么它仍然可以设置流。)

流键如何随时间演变对于使其工作至关重要,因此以下部分将详细介绍。

流键格式

流键作为 Netlink 属性序列通过 Netlink 套接字传递。一些属性表示数据包元数据,定义为无法从数据包本身提取的关于数据包的任何信息,例如接收数据包的 vport。但是,大多数属性是从数据包内的标头中提取的,例如来自以太网、IP 或 TCP 标头的源地址和目标地址。

<linux/openvswitch.h> 头文件定义了流键属性的确切格式。为了在此处进行非正式解释,我们将其写为逗号分隔的字符串,用括号表示参数和嵌套。例如,以下内容可以表示对应于在 vport 1 上接收的 TCP 数据包的流键

in_port(1), eth(src=e0:91:f5:21:d0:b2, dst=00:02:e3:0f:80:a4),
eth_type(0x0800), ipv4(src=172.16.0.20, dst=172.18.0.52, proto=17, tos=0,
frag=no), tcp(src=49163, dst=80)

通常,我们会省略对讨论不重要的参数,例如

in_port(1), eth(...), eth_type(0x0800), ipv4(...), tcp(...)

通配流键格式

通配流使用通过 Netlink 套接字传递的两个 Netlink 属性序列进行描述。一个流键,正如上面所描述的,以及一个可选的相应流掩码。

通配流可以表示一组精确匹配流。掩码中的每个 “1” 位都指定与流键中相应位的精确匹配。“0” 位指定一个不关心的位,它将匹配传入数据包的 “1” 位或 “0” 位。使用通配流可以通过减少用户空间程序需要处理的新流的数量来提高流设置速率。

内核和用户空间程序都可选支持掩码 Netlink 属性。内核可以忽略掩码属性,安装精确匹配流,或者减少内核中不关心的位的数量,使其少于用户空间程序指定的数量。在这种情况下,内核未实现的位中的变化只会导致额外的流设置。内核模块还将与既不支持也不提供流掩码属性的用户空间程序一起工作。

由于内核可能会忽略或修改通配位,因此用户空间程序很难确切知道安装了哪些匹配项。有两种可能的方法:被动地在它们错过内核流表时安装流(因此根本不尝试确定通配符更改),或者使用内核的响应消息来确定已安装的通配符。

在与用户空间交互时,内核应保持键的匹配部分与最初安装时完全相同。这将提供一个句柄来标识所有未来操作的流。但是,在报告已安装流的掩码时,掩码应包括内核施加的任何限制。

使用重叠的通配流时的行为未定义。用户空间程序有责任确保任何传入数据包最多可以匹配一个流,无论是通配流还是非通配流。当前的实现执行尽力检测重叠的通配流,并且可能会拒绝某些但不是全部。但是,此行为可能会在以后的版本中更改。

唯一流标识符

使用键的原始匹配部分作为流标识的句柄的替代方法是唯一流标识符或 “UFID”。UFID 对于内核和用户空间程序都是可选的。

支持 UFID 的用户空间程序应在流设置期间提供 UFID 以及流,然后使用 UFID 引用所有未来操作的流。如果指定了 UFID,则内核不需要通过原始流键索引流。

演变流键的基本规则

要真正为遵循上面 “流键兼容性” 下列出的规则的应用程序维护向前和向后兼容性,需要一些谨慎。

基本规则很明显

==================================================================
New network protocol support must only supplement existing flow
key attributes.  It must not change the meaning of already defined
flow key attributes.
==================================================================

此规则确实具有不太明显的后果,因此值得研究一些示例。例如,假设内核模块尚未实现 VLAN 解析。相反,它只是将 802.1Q TPID (0x8100) 解释为以太网类型,然后停止解析数据包。对于任何带有 802.1Q 标头的数据包,流键看起来基本像这样,忽略元数据

eth(...), eth_type(0x8100)

天真地,要添加 VLAN 支持,添加一个新的 “vlan” 流键属性以包含 VLAN 标签,然后继续使用现有字段定义解码 VLAN 标签之外的封装标头是有意义的。通过此更改,VLAN 10 中的 TCP 数据包将具有类似于这样的流键

eth(...), vlan(vid=10, pcp=0), eth_type(0x0800), ip(proto=6, ...), tcp(...)

但是此更改会对尚未更新以理解新的 “vlan” 流键属性的用户空间应用程序产生负面影响。该应用程序可以按照上面的流兼容性规则,忽略它不理解的 “vlan” 属性,因此假设该流包含 IP 数据包。这是一个错误的假设(仅当解析并跳过 802.1Q 标头时,该流才包含 IP 数据包),并且即使它遵循兼容性规则,也可能导致应用程序的行为在内核版本之间发生变化。

解决方案是使用一组嵌套属性。例如,这就是 802.1Q 支持使用嵌套属性的原因。VLAN 10 中的 TCP 数据包实际上表示为

eth(...), eth_type(0x8100), vlan(vid=10, pcp=0), encap(eth_type(0x0800),
ip(proto=6, ...), tcp(...)))

请注意,“eth_type”、“ip” 和 “tcp” 流键属性如何嵌套在 “encap” 属性内。因此,不理解 “vlan” 键的应用程序将看不到这些属性中的任何一个,因此不会误解它们。(此外,外部的 eth_type 仍然是 0x8100,而不是更改为 0x0800。)

处理格式错误的数据包

不要因为协议头格式错误、校验和错误等原因而在内核中丢弃数据包。这将阻止用户空间实现转发每个数据包的简单以太网交换机。

相反,在这种情况下,包含一个带有 “空” 内容的属性。只要这些值在实践中很少出现,空内容是否可能是有效的协议值都没关系,因为用户空间始终可以将所有具有这些值的数据包转发到用户空间并单独处理它们。

例如,考虑一个包含 IP 标头的数据包,该 IP 标头指示 TCP 的协议 6,但在 IP 标头之后被截断,因此缺少 TCP 标头。此数据包的流键将包含一个 src 和 dst 都为零的 tcp 属性,如下所示

eth(...), eth_type(0x0800), ip(proto=6, ...), tcp(src=0, dst=0)

作为另一个示例,考虑一个以太网类型为 0x8100 的数据包,表示应跟随 VLAN TCI,但在以太网类型之后被截断。此数据包的流键将包含一个全零位的 vlan 和一个空的 encap 属性,如下所示

eth(...), eth_type(0x8100), vlan(0), encap()

与源端口和目标端口都为 0 的 TCP 数据包不同,全零比特的 VLAN TCI 并非罕见,因此,CFI 位(在内核中也称为 VLAN_TAG_PRESENT)通常在 vlan 属性中显式设置,以区分这种情况。因此,第二个示例中的流密钥明确指示 VLAN TCI 缺失或格式错误。

其他规则

流密钥的其他规则则不那么微妙

  • 在给定的嵌套级别中,不允许重复的属性。

  • 属性的顺序并不重要。

  • 当内核向用户空间发送给定的流密钥时,它始终以相同的方式组成它。这允许用户空间对其可能无法完全解释的整个流密钥进行哈希和比较。