Open vSwitch 数据路径开发者文档¶
Open vSwitch 内核模块允许对选定的网络设备上的流级别数据包处理进行灵活的用户空间控制。 它可以用于实现普通的以太网交换机、网络设备绑定、VLAN 处理、网络访问控制、基于流的网络控制等等。
内核模块实现多个“数据路径”(类似于网桥),每个数据路径可以有多个“vports”(类似于网桥中的端口)。 每个数据路径还关联着一个“流表”,用户空间使用“流”填充该表,这些“流”将基于数据包头和元数据的键映射到一组操作。 最常见的操作是将数据包转发到另一个 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,则内核不需要按原始流键对流建立索引。
演化流键的基本规则¶
对于遵循上面“流键兼容性”下列出的规则的应用程序,需要一些注意才能真正保持向前和向后兼容性。
基本规则是显而易见的
==================================================================
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) 解释为 Ethertype,然后停止解析数据包。 忽略元数据,任何带有 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 标头的数据包,该标头指示协议 6 用于 TCP,但在 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 并不常见,因此通常在 vlan 属性中设置 CFI 位(也称为内核中的 VLAN_TAG_PRESENT)以明确允许区分这种情况。 因此,此第二个示例中的流键明确指示缺少或格式错误的 VLAN TCI。
其他规则¶
流键的其他规则就不太微妙了
不允许在给定的嵌套级别上使用重复的属性。
属性的排序并不重要。
当内核向用户空间发送给定的流键时,它总是以相同的方式组成它。 这允许用户空间哈希和比较它可能无法完全解释的整个流键。