L2 扩展流管理支持¶
https://bugs.launchpad.net/neutron/+bug/1563967
Mitaka 中合并的 l2-agent-extension-api 允许 Open vSwitch 代理的扩展操作 Open vSwitch OpenFlow 表。
这引入了扩展创建流之间的互操作性挑战。
问题描述¶
由于 Open vSwitch 流表根据分配给流的优先级顺序匹配流量到流,因此不可避免地会导致两个扩展使用会阻塞使用较低优先级流的扩展的流。
因此,需要一个流管理器或流管道,以一致且相互兼容的方式协调多个扩展的流。
提议的变更¶
为了使扩展在 OpenFlow 规则的上下文中可选、可插拔和可互操作,我们需要定义一个管道模型和一个 l2 扩展 API 接口,其主要目的是避免两个扩展操作同一个表,最终插入不兼容的流,并让扩展
声明 OpenFlow 函数,其中 OpenFlow 函数是一组实现扩展行为的表,可以插入到出口或入口管道中。
声明需要在同一扩展内引用其他 OpenFlow 函数的需求(例如,扩展可能需要在管道入口/出口的不同阶段将流学习到其自身的另一个函数中,这通常的例子是 mac-learning 入口函数学习到出口函数)。
声明 OpenFlow 寄存器,这些寄存器将在函数内部使用,有时也可以用作扩展之间交换元数据的媒介。
声明对特定 OpenFlow 寄存器的需求,通常所需的寄存器由固定块填充或接收,但最终扩展或不同的扩展函数可能需要通过在寄存器中交换元数据来互操作。
其他扩展可以访问公共寄存器或函数,但不能访问非公共寄存器。这为扩展互操作性设置了最低基础,在有意义的情况下。
将为扩展和其他 OpenFlow 机制(例如 OpenFlow 防火墙驱动程序或提议的固定块 - 参见下文 -)提供三个类来声明它们的 OpenFlow 函数
OpenFlow 函数
OpenFlow 表
OpenFlow 寄存器
代码大致如下所示
@openflow_pipeline.register_openflow_function
class EgressTrafficClassifier(openflow_pipeline.OpenFlowFunction):
extension = "FIXED_BLOCKS"
name = 'EGRESS_TRAFFIC_CLASSIFIER'
bridge = openflow_pipeline.INTEGRATION_BRIDGE
declares_registers = [OpenFlowRegister(bits=16, name='CLASSIFIER_MARK',
public=True)]
declares_tables = [OpenFlowTable(name='CLASSIFIER', default=NEXT)]
needs_registers = [OpenFlowRegister(name='OUTPUT_PORT')]
needs_function = [OpenFlowfunction(name='INGRESS_TRAFFIC_X'),
OpenFlowfunction(name='INPUT_TAGGING')]
# The openflow manager will register the following attributes on the
# class once it has resolved all dependencies and creations
# <name>_table
# <name>_reg
# <name>_function
#
# for all the declared and needed registers, so in this example
# we would have the following class attributes:
# - classifier_mark_reg
# - classifier_table
# - output_port_reg
# - ingress_traffic_x_function
# - input_tagging_function
一个顶级 OpenFlow 管理器,由 l2 扩展管理器使用,将负责获取所有声明的 OpenFlow 函数,解析顺序,并为每个函数分配表号和寄存器。
OpenFlow 函数可以选择桥接或它们想要驻留的位置(提供商网络桥接、集成桥接、隧道桥接),我们从小处着手,首先支持想要驻留在集成桥接中的函数,但桥接意图应在函数中声明,以允许未来的可扩展性。
OpenFlowManager 将检查声明的表的默认操作,可以是
NEXT,插入到表中的最低优先级规则跳转到下一个 OpenFlow 函数。
DROP,插入到表中的最低优先级规则丢弃数据包。
集成桥接管道的描述¶
从 VM 实例的 neutron 端口的角度来看,管道分为两条路径
出口路径(离开 VM/端口的流量)
入口路径(到达 VM/端口的流量)
管道如下所示
+----------+
+<--+ vm-tap-a <---------------+
| +----------+ |
| |
| +----------+ |
|<--+ vm-tap-b <-----+ |
| +--+-------+ | |
| | |
0+----v------------+ 253+--+---------+--+
| initial_tagging | | input_stage |
+-----------+-----+ +--------^--------+
| |
+-----------v-----+ +--------+--------+
E | [ function A ] | | [ function .. ] |
G +-----------+-----+ +--------^--------+
R | |
E +-----------v-----+ +--------+--------+
S | [ function ..] | | [ function F ] |
S +-----------+-----+ +--------^--------+
| |
| +-----------v-----+ +--------+--------+ .
| | egress filter | | ingress filter | /|\
\|/ +-----------+-----+ +--------^--------+ |
° | | |
+-----------v-----+ +--------+--------+
| [ function C ] | | [ function .. ] | I
+-----------+-----+ +--------^--------+ N
| | G
+-----------v-----+ +--------+--------+ R
| [ function ..] | ^ [ function E ] | E
+-----------+-----+ /+--------^--------+ S
| / | S
+-----------v-----+ 0+--------+--------+
|ingress deflector| | initial_tagging |
+----------+------+ +---------^-------+
| |
+----------v------+ |
| output_stage +------+ |
+-----------+-----+ | |
| |
+-v-----+-------+
| patch-br-xxx |
+---------------+
注意事项
vm-tap-a 和 vm-tap-b:是连接到桥接的 VM 或服务端口。
patch-br-xxx:是连接到桥接 br-xxx 的补丁端口
函数可以由多个表组成,函数有责任在其自身表中跳转或在处理完成后跳转到下一个函数。
OpenFlow 函数功能¶
OpenFlow 函数有权在以下情况下添加/删除流来正常操作数据包
丢弃数据包
更改数据包详细信息
设置队列
将数据推入/弹出堆栈(堆栈大小在函数结束时不得更改)
学习同一扩展中其他函数的动作
…
等等
需要对以下情况进行特殊处理
NORMAL 操作:不应立即通过操作执行 normal 操作,而是会标记一个寄存器,以便在输出阶段使用“NORMAL”处理特定的数据包。
修改入口路径上的目标地址:必须重置输入标记详细信息,并根据数据包目标的本地性/外部性,更新输入标记字段(使用辅助程序)。
输出数据包的克隆:可以使用“output:X,goto_table:<next-function-first-table>”。
将数据包重定向到新的目标地址并停止管道处理(对于希望将数据包移动到辅助桥接并且完全确定桥接上不应发生进一步处理的扩展)将使用 output:X 而不重新提交到下一个函数。
将为诸如“NORMAL”或数据包克隆的情况提供辅助程序,我们可能希望考虑一些过滤器来强制执行操作的合理性。
注意事项
插入的流必须由扩展通过在必要时调用其 openflow 函数重新插入,例如,当 openvswitch 已重置并且需要重新创建流时。
我们可以使用 DSL 来定义为每个函数创建流的方式,但这会增加其复杂性,我们更喜欢朝着扩展之间更好的集成迈出小步。在未来,我们可以考虑这种选择,我们已经奠定了第一个 openflow 管道的基础。
OpenFlow 函数固定块(集成桥接)¶
Open vSwitch 代理提供了一些常见的 OpenFlow 函数,代理将声明并注册这些函数
INITIAL_TAGGING:负责识别数据包的源或目标,在寄存器中标记网络,并将数据包发送到出口或入口路径(当数据包的源和目标都是本地时,优先考虑出口路径)
一些扩展可能希望更改目标地址或使用新的目标地址复制数据包。应为这种操作提供辅助程序,以便寄存器与 INITIAL_TAGGING 函数执行的相同设置相匹配。
出口路径优先,以涵盖数据包同时在同一桥接上是出口和入口的情况(源端口和目标端口都位于 hypervisor 的本地)。
INGRESS_DEFLECTOR:对于在本地 hypervisor 上同时是出口和入口的数据包,此阶段负责将数据包从出口路径的末尾移动到入口路径的开头,以便在将数据包最终发送到输出端口之前进行任何入口过滤或数据包处理。
INGRESS_FILTER、EGRESS_FILTER:由安全组防火墙驱动程序(openvswitch 防火墙)提供,如果未提供 OpenFlow 过滤器,则管道的此阶段保留用于混合防火墙的 L2 过滤,以克服 iptables/ebtables 的限制,用于 L2 MAC 过滤。
INPUT_STAGE:在入口路径之后,数据包被定向到其目标端口,或者根据数据包详细信息执行 NORMAL 规则。在未来的修订版中,我们可以删除所有 NORMAL 规则,这样,透明 VLAN 和端口汇聚可以作为简单的 OpenFlow 函数实现,而无需单独的桥接。
OUTPUT_STAGE:在出口路径之后,数据包被定向到其目标补丁端口或使用 NORMAL,当去往另一个桥接时。
Open vSwitch 代理的每个 l2 代理扩展可以声明一个或多个功能块来插入。
每个功能块可以由一个或多个 OpenFlow 表组成。
每个功能块都有一个入口点和至少一个出口点(下一个函数)。如果表“default”是 NEXT,则管理器将插入一个默认跳转到下一个函数的最低优先级规则。
函数排序¶
功能块的排序在管理器中定义,并在核心 neutron 中维护。如果有人想在管道中声明一个新函数,他们可以来社区讨论其插入点,或者我们将包括可以立即使用的常见扩展点。
COMMON 前缀的点可以被多个扩展使用,并且扩展函数之间的排序不受保证。
需要多个插入点的扩展应将所有这些插入点声明为单独的 OpenFlow 函数,并根据需要使用它们(请注意下面的 <>_<DIR>_PREFILTER / <>_<DIR>_POSTFILTER 示例)。
每个管道的功能块示例可能是
EGRESS_PIPELINE = [COMMON_EGRESS_PREFILTER0,
TAAS_EGRESS_PREFILTER,
BGPVPN_EGRESS_PREFILTER,
SFC_EGRESS_PREFILTER,
COMMON_EGRESS_PREFILTER1,
COMMON_EGRESS_FILTER0,
EGRESS_FILTER,
COMMON_EGRESS_FILTER1,
COMMON_EGRESS_POSTFILTER0,
QOS_EGRESS_MIN_BW,
QOS_EGRESS_DSCP,
SFC_EGRESS_POSTFILTER,
TAAS_EGRESS_POSTFILTER,
COMMON_EGRESS_POSTFILTER1]
INGRESS_PIPELINE = [COMMON_INGRESS_PREFILTER0,
TAAS_INGRESS_PREFILTER,
SFC_INGRESS_PREFILTER,
COMMON_INGRESS_PREFILTER1,
COMMON_INGRESS_FILTER0,
INGRESS_FILTER,
COMMON_INGRESS_FILTER1,
COMMON_INGRESS_POSTFILTER0,
TAAS_INGRESS_POSTFILTER,
SFC_INGRESS_POSTFILTER,
COMMON_INGRESS_POSTFILTER1]
请注意,固定块(INITIAL_TAGGING、INPUT_STAGE、OUTPUT_STAGE、INGRESS_DEFLECTOR)未包含在配置定义中,因为它们也不是入口或出口管道,而是将流量引导到正确管道的块。