(首发于 sdnlab: http://www.sdnlab.com/17952.html)
阅读任何一个协议都要注意的一点是这个协议中所定义的专有术语, 对这些术语的理解不到位的话也会造成对协议的理解偏差. 本文想和大家分享几个可能容易混淆的术语.
在 OpenFlow 协议文档中经常会看到这么几个词语: Instruction, Action, Apply-actions, Action Set, Action List, Clear-actions, ... 有点迷惑人, 实际上这里面只有两个实体的概念: Instruction 和 Action. 为了保持后文的易读性, 这两个概念分别用中文 "指令" 和 "动作" 来描述. 下文中的 "指令" 和 "动作" 都特指在 OpenFlow 协议中的含义.
指令这个词, 特指流表表项中的指令, 当某个报文匹配了这个表项之后, 表项中的指令就会被应用于这个报文; 而动作是比指令更细粒度的概念, 但它并不是局限于流表表项的概念, 动作可以独立于指令而存在, 也可以被包含在指令中, 具体说来, 我们在下流表的时候, 可以为某个表项的某种指令指定一些列的动作, 但是动作并不是只有下流表的时候才会被用到.
本文以目前较新的 Openflow 1.4 版本为准, 来分别看一下指令和动作的含义.
指令
每一个流表的表项都包含一系列的指令, 当报文匹配上了这个表项后, 这些指令就会被执行, 这些指令的执行结果有几种: 改变报文, 改变 action set, 改变 pipeline. 这些指令可以按照其执行结果的不同而分类, 不同的流表的表项包含的指令种类也不同, 前面说了指令可以包含动作, 但也并非所有种类的指令都包含动作, 下面我们一起来看一下指令的分类.
指令的分类
OpenFlow 1.4 中规定了 6 种类型的指令, 但并不要求交换机支持所有的类型. 另外要注意的是, 在 OpenFlow 协议文档中指令的类型名字几乎全都以 "actions" 后缀结尾, 我觉得这是非常容易令人混淆的地方, 我们一定要记住指令类型名中的 "actions" 字样和我们上面说的 "动作" 的概念完全没有关系. 然而 OpenFlow 协议文档的这种写法看起来似乎每种指令都包含了一组动作, 而实际上只有几种指令是真正包含动作的, 下面我们来看一下这 6 种指令与动作的关系.
为了避免我们更加混淆, 6 种指令类型的名字我还是保持和 OpenFlow 1.4 的 Spec 一样. 还有一点需要注意的是, 括号里的 "可选", "必选" 字样指的是交换机是否必须支持这个指令, 而不是说下流表时表项中是否必须包含这个指令.
- (可选指令) Meter meter_id, 不包含动作, 行为是将报文送往指定的 meter
- (可选指令) Apply-actions action(s), 这个指令是真正包含动作的指令, 它的行为是立即对报文应用这些指令, 不要改变报文的 action set
- (可选指令) Clear-actions, 这个指令并不包含任何的动作, 它的行为是立即清除报文的 action set 中所有的动作
- (必选指令) Write-actions actions(s), 这个指令真正的包含动作, 它的行为是将自己包含的动作合并到报文的 action set 中
- (可选指令) Write-Metadata metadata / mask, 这个也不包含动作, 用的不多
- (必选指令) Goto-Table next-table-id, 这个指令也不包含动作, 它表示把报文交给后续的哪张流表处理. OpenFlow 协议要求交换机必须支持这个 action, 但有一个例外是假设你的交换机本身就只支持一张流表, 那可以不支持这个 action.
动作
前面说了动作也有分类, 但是相比指令的分类, 动作的分类就比较好理解了, 我们稍加带过, 然后解释下 Action Set 和 Action List 两个概念.
同样, OpenFlow 协议不要求交换机支持所有的动作种类, 我们只看几个常见的:
- (可选) Output, 表示将报文从某个特定的端口送出去
- (必选) Drop, 丢弃报文
- (必选) Group, 表示将报文交给指定的组
- (可选) Change-TTL, 改变报文的 TTL 字段 (可以是 IPv4 TTL, MPLS TTL 或者 Ipv6 Hop Limit)
关于 Action Set
Action set 是一个与报文相关联的概念, 只要提起 action set, 它就一定是报文的 action set, 它包含了当报文离开流表时要附加于这个报文上的动作. 我们前面看到了有一种 Apply-actions 指令, 它是在报文匹配了表项的时候将它包含的动作立即应用到报文上, 而 Write-actions 则是将它包含的动作合并到报文的 action set 中, 另外还有 Clear-actions 指令, 是将报文的 action set 清空. 最终报文走完所有流表时, 其 action set 里面有什么动作, 就执行什么动作, 这就是 action set 的作用了.
关于 Action List
Action list 实际上就是一系列动作的有序序列, 一定要注意其有序性. 在上面说到的流表中的 Apply-actions 指令中, 以及 OpenFlow 协议中同样能够包含动作的 Packet-out 命令中, 都要求所包含的动作被有序执行. 所以就出来了这么个 action list 的概念, 这是与 action set 的一点区别. 另一个区别是 action list 并不是和报文相关联的概念, action list 可以直接夹带在 controller 发给 agent 的消息中, 比如 Packet-out 消息; 也可以存在于流表表项的指令中, 比如 Apply-actions 指令.
协议源代码
说实话, 光看协议 Spec 我是没有理清楚这些个指令与动作的关系的, 真正完全理清楚是看了 OpenFlow 源码之后. 在 OpenFlow 源码中, 指令与动作的结构头分别如下:
struct ofp_instruction_header {
uint16_t type; /* One of OFPIT_*. */
uint16_t len; /* Length of this struct in bytes. */
};
OFP_ASSERT(sizeof(struct ofp_instruction_header) == 4);
struct ofp_action_header {
uint16_t type; /* One of OFPAT_*. /
uint16_t len; / Length of action, including this
header. This is the length of action,
including any padding to make it
64-bit aligned. */
};
OFP_ASSERT(sizeof(struct ofp_action_header) == 4);
Meter, Write-metadata, Goto-Table 这三类指令的结构如下, 它们的前两个字段和 struct ofp_instruction_header
是相同的, 另外可以看出, 它们都不包含 struct ofp_action_header
结构体, 所以这三个指令是不包含动作的.
/* Instruction structure for OFPIT_METER */
struct ofp_instruction_meter {
uint16_t type; /* OFPIT_METER */
uint16_t len; /* Length is 8. */
uint32_t meter_id; /* Meter instance. */
};
OFP_ASSERT(sizeof(struct ofp_instruction_meter) == 8);
/* Instruction structure for OFPIT_GOTO_TABLE /
struct ofp_instruction_goto_table {
uint16_t type; / OFPIT_GOTO_TABLE /
uint16_t len; / Length is 8. /
uint8_t table_id; / Set next table in the lookup pipeline /
uint8_t pad[3]; / Pad to 64 bits. */
};
OFP_ASSERT(sizeof(struct ofp_instruction_goto_table) == 8);
/* Instruction structure for OFPIT_WRITE_METADATA /
struct ofp_instruction_write_metadata {
uint16_t type; / OFPIT_WRITE_METADATA /
uint16_t len; / Length is 24. /
uint8_t pad[4]; / Align to 64-bits /
uint64_t metadata; / Metadata value to write /
uint64_t metadata_mask; / Metadata write bitmask */
};
OFP_ASSERT(sizeof(struct ofp_instruction_write_metadata) == 24);
而 Apply-actions, Clear-actions, Write-actions 三种指令则共用如下的结构体, 可以看到它是包含 struct ofp_action_header
的, 你可能会奇怪 Clear-actions 指令不是也不包含动作吗, 为什么也用了这个结构体, 实际上对于 Clear-actions 指令来说, struct ofp_instruction_actions
结构体的最后一个 actions 字段是大小为 0 的数组.
/* Instruction structure for OFPIT_WRITE/APPLY/CLEAR_ACTIONS */
struct ofp_instruction_actions {
uint16_t type; /* One of OFPIT_*_ACTIONS */
uint16_t len; /* Length is padded to 64 bits. */
uint8_t pad[4]; /* Align to 64-bits */
struct ofp_action_header actions[0]; /* 0 or more actions associated with
OFPIT_WRITE_ACTIONS and
OFPIT_APPLY_ACTIONS */
};
OFP_ASSERT(sizeof(struct ofp_instruction_actions) == 8);
另外上面还说到了一个 Packet-out 消息也是包含动作的, 它的定义如下, actions 字段包含了一个动作列表, 也就是 action list.
/* Send packet (controller -> datapath). */
struct ofp_packet_out {
struct ofp_header header;
uint32_t buffer_id; /* ID assigned by datapath (OFP_NO_BUFFER if none). */
uint32_t in_port; /* Packet’s input port or OFPP_CONTROLLER. */
uint16_t actions_len; /* Size of action array in bytes. */
uint8_t pad[6];
struct ofp_action_header actions[0]; /* Action list - 0 or more. */
/* The variable size action list is optionally followed by packet data.
* This data is only present and meaningful if buffer_id == -1. */
/* uint8_t data[0]; */ /* Packet data. The length is inferred from the length field in the header. */
};
OFP_ASSERT(sizeof(struct ofp_packet_out) == 24);
参考
- OpenFlow Spec v1.4.0
Posted from my blog with SteemPress : http://cifer.me/2016/10/03/%e5%85%b3%e4%ba%8e-openflow-%e5%8d%8f%e8%ae%ae%e4%b8%ad-instruction-action-%e6%a6%82%e5%bf%b5%e7%9a%84%e8%a7%a3%e8%af%bb/