原版OpenFlow Switch Specification Version 1.3.0 (Wire Protocol 0x04) June 25, 2012 包含两部分,前一部分是交换机规范,共6个章节,可以简称为“规范”;后一部分是将OpenFlow协议本身作为附件,可以认为是整体第7章,可以简称为“协议”。
本系列作为中文版,基本忠实原文语句和术语,大部分关键词都是中英文对照。将规范和协议再切分为2部分,共4篇完整呈现。
OpenFlow 交换机规范 1.3.0 第一部分 1-5章
https://www.jianshu.com/p/acfeae1771b3
OpenFlow 交换机规范 1.3.0 第二部分 6章
https://www.jianshu.com/p/82e238eb8d14
OpenFlow协议第一部分
https://www.jianshu.com/p/7eb86d164d26
OpenFlow协议第二部分
https://www.jianshu.com/p/9cc08c698106
A.3.5 多部消息(Multipart Messages)
(多部消息用于编码请求或应答那些可能携带大量数据而且不能装进单个 OpenFlow 消息(仅限于 64 kb)的情况。请求或应答是编码序列与特定的多部分类型的多部分消息 。请求被放在一个或多个 OFPT_MULTIPART_REQUEST 消息)
在系统运行中,控制器使用OFPT_MULTIPART_REQUEST消息从datapath请求状态信息。
struct ofp_multipart_request {
struct ofp_header header;
uint16_t type; /* OFPMP_*之一. */
uint16_t flags; /* OFPMPF_REQ_*标记. */
uint8_t pad[4];
uint8_t body[0]; /* 请求的 body。0或多个字节. */
};
OFP_ASSERT(sizeof(struct ofp_multipart_request) == 16);
enum ofp_multipart_request_flags {
OFPMPF_REQ_MORE= 1 << 0 /* 更多的请求跟随. */
};
交换机用一个或多个 OFPT_MULTIPART_REPLY消息进行响应:
struct ofp_multipart_reply {
struct ofp_header header;
uint16_t type; /* 一个 OFPMP_*常数. */
uint16_t flags; /* OFPMPF_REPLY_*标记. */
uint8_t pad[4];
uint8_t body[0]; /* 回答的 body,0 或更多字节. */
};
OFP_ASSERT(sizeof(struct ofp_multipart_reply) == 16);
enum ofp_multipart_reply_flags {
OFPMPF_REPLY_MORE= 1 << 0 /* 更多回复跟随 */
};
在请求和回复中为 flags 定义的值是唯一的, 是否更多的请求 / 回复会跟随这个 ——这个值就是 0x0001。为了方便实现 , 控制器允许发送请求和交换机允许发送回复时不携带额外的表项( 即一个空的 body)。然而 , 另一个消息必须使用更多的标志设置去跟随一个消息。跨越多个消息 ( 有一个或多个消息设置了更多的标志 ) 的请求或应答, 消息队列中的所有消息必须使用相同的复合类型和事务 id(xid)。如果多个未回答的复合请求或回复同时在传输中,复合请求或应答的消息可能与其他 OpenFlow 消息类型 交替 , 包括其他复合请求或回复 , 但必须有不同的事务 id。回复的事务 id 必须匹配激起他们的请求。
在请求和响应中, type 字段指所传递信息的种类,并决定如何解析 body 字段:
enum ofp_multipart_type {
/* 描述这个 OpenFlow 交换机。
*请求 body 是空的。
*回复主体是 struct ofp_desc. */
OFPMP_DESC= 0,
/* 单独流统计。
* 请求 body 是 ofp_flow_stats_request 结构。
*回复 body 是一个 struct ofp_flow_stats 数组。. */
OFPMP_FLOW= 1,
/* 总的流统计。
* 请求 body 是 ofp_aggregate_stats_request 结构。
*回复 body 是
ofp_aggregate_stats_reply 结构. */
OFPMP_AGGREGATE= 2,
/* 流表统计。
*请求 body 是空的。
*回复 body 是 ofp_table_stats 结构数组。 */
OFPMP_TABLE= 3,
/* 端 口 统 计。
* 请 求 body 是 ofp_port_stats_request 结 构。
* 回 复 body 是ofp_port_stats 结构数组。 */
OFPMP_PORT_STATS= 4,
/* 一个端口的队列统计
*请求的 body 是 ofp_queue_stats_request 结构体.
*回复 body 是 ofp_queue_stats 结构体数组 */
OFPMP_QUEUE=5,
/* 组计数器统计。
*请求的 body 是 ofp_group_stats_request 结构体.
*回复是 ofp_group_stats 结构体数组 */
OFPMP_GROUP=6,
/* 组描述。
*请求 body 是空的。
/* 回复 body 是 ofp_group_desc 结构体数组 */
OFPMP_GROUP_DESC=7,
/* 组特征。
*请求 body 为空。
*回复 body 是 ofp_group_features 结构体。 */
OFPMP_GROUP_FEATURES=8,
/* 计量器统计
*请求 body 是 ofp_meter_multipart_requests 结构体。
*回复 body 是 ofp_meter_stats 结构体数组 */
OFPMP_METER=9,
/* 计量器配置
*请求 body 是 ofp_meter_multipart_requests 结构体.
*回复 body 是 ofp_meter_config 结构体数组。 */
OFPMP_METER_CONFIG==10,
/* 计量器特征
*请求 body 为空。
*回复 body 是 ofp_meter_features 结构体。 */
OFPMP_METER_FEATURES=11,
/* 表特征
*请求 body 是空的或包含 ofp_table_features 结构体数组, 包含控制器所需的交换机视
图。如果交换机不能设置特定的视图,就会返回一个 error.
*回复 body 是一组 ofp_table_features 结构体 .*/
OFPMP_TABLE_FEATURES=12,
/* 端口说明
*请求 body 是空的。
*回复 body 是 ofp_port 结构体数组 */
OFPMP_PORT_DESC=13,
/* 实验者扩展项
*请求 body 和回复 body 都以 ofp_experimenter_multipart_header 结构体开始。
*而请求 body 和回复 body 是由实验者定义的。 */
OFPMP_EXPERIMENTER=0xffff
};
如果一个复合请求跨越多个消息并且扩展到交换机不能缓冲的大小,那么交换机必须回复 一 个 OFPET_BAD_REQUEST类 型 的 错 误 消 息 并 且 编 码 为OFPBRC_MULTIPART_BUFFER_OVERFLOW。如果一个复合请求包含一个不支持的类型,交换机必须回复一个 OFPET_BAD_REQUEST 类型的错误消息并且编码为 OFPBRC_BAD_MUTIPART.
在所有包含统计数字复合应答中,如果在交换机中没有指定的数字计数器,其值必须被设置为字段最大值(无符号数 =-1)。计数器是无符号的并且循环时无溢出指示。
A.3.5.1 说明
关于交换机厂商、硬件修订、软件修订、序列号和描述字段的信息,可以从 OFPMP_DESC multipart 请求类型中获得:
/*OFPMP_DESC请求的回复部分。每个表项都是 NULL结束的 ASCII 码字符串 */
struct ofp_desc {
char mfr_desc[DESC_STR_LEN]; /* 厂商说明. */
char hw_desc[DESC_STR_LEN]; /* 硬件说明. */
char sw_desc[DESC_STR_LEN]; /* 软件说明. */
char serial_num[SERIAL_NUM_LEN]; /* 序列号. */
char dp_desc[DESC_STR_LEN]; /* 可读的数据通道描述. */
};
OFP_ASSERT(sizeof(struct ofp_desc) == 1056);
每个表项都是 ASCII 码格式并且用空字节( \0 )从右边填充。DESC_STR_LEN是 256,SERIAL_NUM_LEN是 32。dp_desc 是一个自由形式的字符串, 调试时用来描述数据通道, 例如:“switch3 in room 3120”。因此,它不能被保证是唯一的并且不应用作数据通道的主要标识—- 使用交换机的 datapath_id 字段来代替。
A.3.5.2 单个的流统计
关于单个流表项的信息使用 OFPMP_FLOW复合请求类型进行请求:
/*OFPMP_FLOW的 ofp_multipart_request 部分*/
struct ofp_flow_stats_request {
uint8_t table_id; /* 要读的 table_ID (from ofp_table_stats),
OFPTT_ALL表示所有表. */
uint8_t pad[3]; /* 对齐到 32 bits. */
uint32_t out_port; /* 作为一个输出端口要求包含这个匹配项. 一个OFPP_ANY值表示没有限制。 */
uint32_t out_group; /* 作为一个输出组要求包含这个匹配项, 一个OFPG_ANY值表示没有限制 */
uint8_t pad2[4]; /* 对齐到 64 bits. */
uint64_t cookie; /* 要求匹配项要包含这个 cookie 值。*/
uint64_t cookie_mask; /* 掩码用来限制那些必须匹配的 cookie bits . 0 表示没有限制*/
Struct ofp_match match; /* 用来匹配的字段,可变大小 */
};
OFP_ASSERT(sizeof(struct ofp_flow_stats_request)==40);
匹配字段包含需匹配的流表项说明,还可含有通配符和掩码字段。这些字段的匹配行为在6.4 中介绍。
Table_id 表示一个将读取的一个流表的索引值, OFPTT_ALL表示所有流表。
Out_port 和out_group 字段可又输出端口和组选择过滤。如果 out_port 或out_group 包含一个分别不同于 OFPP_ANY和OFPG_ANY的值,它会在匹配的时候引进一个约束。这个约束就是流表项必须包含一个指定端口或组的输出行动。其它约束例如 ofp_match结构仍然在使用; 这是纯粹一个额外的约束。注意为了禁止输出过滤, out_port 和out_group 都必须分别设置为OFPP_ANY和OFPG_ANY。
Cookie和cookie_mask字段的用法见 6.4 部分。
回复一个 OFPMP_FLOW复合请求的部分由下列队列组成:
/* 回复一个 OFPMP_FLOW请求的部分。 */
struct ofp_flow_stats {
uint16_t length; /* 这个项的长度 */
uint8_t table_id; /* 流的来源表 ID */
uint8_t pad;
uint32_t duration_sec; /* 流已生成的时间,以秒记 */
uint32_t duration_nsec; /* 流存在的时间超过 duration_sec 的时间,以 ns记 */
uint16_t priority; /* 流表项的优先权 */
uint16_t idle_timeout; /* 在到期之前闲置的秒数 */
uint16_t hard_timeout; /* 到期之前的秒数 */
uint16_t flags; /*OFPFF_* flags 位图. */
uint8_t pad2[4]; /* 对齐到 64-bits. */
uint64_t cookie; /* 控制器发出的不透明标识符 */
uint64_t packet_count; /* 流里的包的数目。 */
uint64_t byte_count; /* 流的字节数. */
struct ofp_match match; /* 字段说明. 可变大小 */
/* 可变大小和填充的匹配总是跟随着指令. */
//struct ofp_instruction instructions[0]; /* 指令集 -- 0 或者更多. */
};
OFP_ASSERT(sizeof(struct ofp_flow_stats) == 56);
由创建流表项的 flow_mod提供的字段(见 A.3.4.1 ),加上插入到流表项中的 table_id,packet_count 和byte_count 对所有被流表项处理的数据包进行计数。
duration_nsec 和duration_sec 字段显示流表项安装在交换机中已过去的时间。 总共持续的时间可用 duration_sec*10^9+duration_nsec 计算出。 实现的话要求提供第二精度; 在能够获得的情况下鼓励用更高的精度。
A.3.5.3 总计的流统计
关于多个的流表项的总计信息用 OFPMP_AGGREGATE复合请求类型来请求:
/*OFPMP_AGGREGATE类型的 ofp_aggregate_atats_request 部分*/
struct ofp_aggregate_stats_request {
uint8_t table_id; /* 要读取的表 ID( 来自ofp_table_stats),OFPTT_ALL为所有的表 */
uint8_t pad[3]; /* 排列到 32 bits. */
uint32_t out_port; /* 包含此项匹配的流表项要求作为一个输出端口. OFPP_ANY值表示没有限制*/
uint32_t out_group; /* 包含此项匹配的流表项要求作为一个输出组, OFPG_ANY值表示没有限制*/
uint8_t pad2[4]; /* 排列到 64 bits. */
uint64_t cookie; /* 要求正在匹配的流表项去包含这些 cookie 值*/
uint64_t cookie_mask;/* 掩码(mask)用来限制那些必须匹配的 cookie 比特, 0表示没有限制.*/
struct ofp_match match; /* 将匹配的字段. 可变大小. */
};
OFP_ASSERT(sizeof(struct ofp_aggregate_stats_request) == 40);
在这个消息的中的字段与在单独的流统计请求( OFPMP_FLOW)里有相同的含义。
回复部分的组成如下:
/*OFPMP_AGGREGATE 请求的回复部分. */
struct ofp_aggregate_stats_reply {
uint64_t packet_count; /* 流中的数据包数. */
uint64_t byte_count; /* 流中的字节数. */
uint32_t flow_count; /* 流的数目. */
uint8_t pad[4]; /* 排列到 64 bits. */
};
OFP_ASSERT(sizeof(struct ofp_aggregate_stats_reply) == 24);
A.3.5.4 表统计
请求关于表的信息使用 OFPMP_TABLE复合请求类型。在请求内不包含任何数据。
回复部分由如下一个数组组成:
/* 回复给 OFPMP_TABLE请求的部分. */
struct ofp_table_stats {
uint8_t table_id; /* 表标识符 . 低数值的表第一个访问 */
uint8_t pad[3]; /* 排列到 32-bits. */
uint32_t active_count; /* 活跃的流表项数量. */
uint64_t lookup_count; /* 表中处理过的数据包数量. */
uint64_t matched_count; /* 表中匹配的数据包数量。 */
};
OFP_ASSERT(sizeof(struct ofp_table_stats) == 24);
在交换机支持的每一个表中,数组都有一个结构。流表项是以数据包通过表的顺序返回的。
A.3.5.5 表特征
OFPMP_TABLE_FEATURES复合类型允许一个控制器去查询现存的表的能力和选择要求交换机去重新配置表去匹配一个已有的配置。一般来讲,表的能力体现了一个表的所有可能的特征,然而一些能力互不兼容,目前的能力结构不允许我们去解决兼容问题。
A.3.5.5.1 表特征请求和回复
如果 OFPMP_TABLE_FEATURES 请求是空的,交换机将返回一组包含当前配置流表能力的ofp_table_features 结构体。
如果请求部分包含一个或更多 ofp_table_features 结构体的队列,交换机将尝试去改变它的流表去匹配被请求的流表配置。这个操作配置整个流水线,流水线里的流表设置必须和请求的设置匹配,否则必须返回一个 error。 尤其是, 如果这个配置成功进行了设置,则给一个或更多交换机支持的流表的请求结构若不包含一个ofp_table_features 结构体,这些流表将从流水线中被移除。配置改变成功会修改请求里面的所有流表的特性,也就是说,要么在请求里被指定的所有流表都修改,要么一个都不改,并且新的流表的功能必须是超集或者和
被请求的功能相同。如果流表配置成功了,已删除的流表或在新旧配置里能力改变的流表,其 流 表 项 都 将 从 流 表 中 删 除。如果交换机不能设置所请求的配置,将返回一个OFPET_TABLE_FEATURES_FAILED 类型错误并带有适当错误代码。
含有ofp_table_features 的请求和回复至少要满足以下要求:
●每个 ofp_table_features 结构的 table_id 字段的值在消息所有的 ofp_table_features 结构里应该是唯一的。
●每个 ofp_table_features 结构的属性字段必须包含一个 ofp_table_feature_prop_type 属性,以及另外两个。 第一, the_MISS后缀可能被忽略,如果它和常规流表项相应的属性相同;
第二, OFPTFPT_EXPERIMENTER 和OFPTFPT_EXPRIMENTER_MISS 类型的属性可能会被多次忽略或使用。排序未规定,但鼓励使用在规范上列出的次序。 (见A.3.5.5.2 )
一个交换机接收到一个请求,若不满足所需请求,应该返回一个OFPET_TABLE_FEATURES_FALLED 类型的错误并带有适当的代码。
下面的结构说明表特性请求和回复的部分:
/* OFPMP_TABLE_FEATURES 类的ofp_multipart_request 部分./
/* 回复OFPMP_TABLE_FEATURES请求的部分. */
struct ofp_table_features {
uint16_t length; /* 长度被填至 64 bits. */
uint8_t table_id; /* 表的标识符. 低编号先询问. */
uint8_t pad[5]; /* 排到64-bits. */
char name[OFP_MAX_TABLE_NAME_LEN];
uint64_t metadata_match; /* 表能匹配元数据的位数. */
uint64_t metadata_write; /* 表能写的元数据位数. */
uint32_t config; /*OFPTC_*值的位图 */
uint32_t max_entries; /* 流表项被支持的最大数. */
/* 表特征属性列举 */
struct ofp_table_feature_prop_header properties[0]; /* 属性举例 */
};
OFP_ASSERT(sizeof(struct ofp_table_features) == 64);
数组有一个结构给每个被交换机支持的流表。 流表项总是按照数据表通过流表的顺序返回。OFP_MAX_TABLLE_NAME_LEN是32。
该metadata_match字段表示流表利用 ofp_match结构体元数据字段时可匹配的元数据字段的位数。值0xFFFFFFFFFFFFFFFF表明表可以匹配整个元数据字段。
该metadata_write 字段显示表可以使用 ofpit_write_metadata 指令写元数据字段的位数。值 0xFFFFFFFFFFFFFFFF表明表可以写整个元数据字段。
配置字段是表的配置,通过表的配置信息对表进行设置(见 A.3.3 )
Max_entries 字段表示能插入表的流表项最大数量。由于现代的硬件的限制,该max_entries 值应考虑建议值和接近表最大努力的性能。不考虑表的高层抽象,在实践中通过一个单一的流表项所消耗的资源是不固定的。例如,一个流表项可能消耗多个流表项的资源,取决于它的匹配参数( e.g.IPv4 vs. IPv6)。而且,在同个 OPENFLOW层不同的表可能实
际上共享相同的底层物理资源。进一步说,基于 OpenFlow 混合交换机,这些表可能与非OpenFlow 功能部分共享。结果是交换机实现者应该报告所支持的总的流表项值,并且控制器的作者不应该把这个值设为固定的,物理常数。
properties字段是表特征属性的一个列表,描叙表的不同性能。
A.3.5.5.2 表特征属性
目前定义的表特征属性类型的清单是:
/* 表特征属性类型.
* 最低位被清除表示一个常规流表项的一个属性.
* 最低位被设置表示 Table-Miss Flow Entry 的一个属性 .*/
enum ofp_table_feature_prop_type {
OFPTFPT_INSTRUCTIONS= 0, /* 指令属性. */
OFPTFPT_INSTRUCTIONS_MISS= 1, /* table-miss 的指令. */
OFPTFPT_NEXT_TABLES= 2, /* Next Table 属性. */
OFPTFPT_NEXT_TABLES_MISS= 3, /* Next Table for table-miss. */
OFPTFPT_WRITE_ACTIONS= 4, /* Write Actions 属性. */
OFPTFPT_WRITE_ACTIONS_MISS= 5, /* Write Actions for table-miss. */
OFPTFPT_APPLY_ACTIONS= 6, /* Apply Actions 属性. */
OFPTFPT_APPLY_ACTIONS_MISS= 7, /* Apply Actions for table-miss. */
OFPTFPT_MATCH= 8, /* 匹配属性. */
OFPTFPT_WILDCARDS= 10, /* Wildcards 属性. */
OFPTFPT_WRITE_SETFIELD= 12, /* Write Set-Field 属性. */
OFPTFPT_WRITE_SETFIELD_MISS= 13, /* Write Set-Field for table-miss. */
OFPTFPT_APPLY_SETFIELD= 14, /* Apply Set-Field 属性. */
OFPTFPT_APPLY_SETFIELD_MISS= 15, /* Apply Set-Field for table-miss. */
OFPTFPT_EXPERIMENTER= 0xFFFE, /* Experimenter 属性. */
OFPTFPT_EXPERIMENTER_MISS= 0xFFFF, /* Experimenter for table-miss. */
};
_MISS后缀的属性描述漏表流表项的功能 (见 5.4 ),而其他属性描述常规流表项的功能。
如果一个指定属性没有任何功能(例如不支持 Set_Field ),一个空列表属性也必须被包含在属性列表里。 当漏表流表项的一个属性和常规流表项相应的属性相同, (i.e. 两个属性都有相同的功能清单),这个漏表属性可以从属性列表里清除。
一个属性定义包含属性类型、长度和关联的数据:
/* 所有表特征属性常用的标头 */
struct ofp_table_feature_prop_header {
uint16_t type; /* OFPTFPT_*中之一. */
uint16_t length; /* 这个属性占字节长度. */
};
OFP_ASSERT(sizeof(struct ofp_table_feature_prop_header) == 4);
OFPTFPT_INSTRUCTIONS 和OFPTFPT_INSTRUCTIONS_MISS 属性使用如下结构和字段:
/* 指令属性 */
struct ofp_table_feature_prop_instructions {
uint16_t type; /*OFPTFPT_INSTRUCTIONS和OFPTFPT_INSTRUCTIONS_MISS 之一. */
uint16_t length; /* 这个属性的字节长度. */
/* 接下来是:
* - 准确地讲 (length - 4) 个字节包含指令的 id, 其次
* - 精确地 (length + 7)/8*8 - (length) (between 0 and 7)
* 全零字节的字节数 */
struct ofp_instruction instruction_ids[0]; /* 指令列表 */
};
OFP_ASSERT(sizeof(struct ofp_table_feature_prop_instructions) == 4);
Instruction_ids 是被这个表支持的指令列表(见 5.9 )。列表里的元素是可变大小的,用于实验者表述指令的,非实验者指令是 4 字节。
OFPTFPT_NEXT_TABLES和 OFPTFPT_NEXT_TABLES_MISS属性使用如下结构和字段:
A.3.5.6 端口统计
关于端口统计的信息使用 OFPMP_PORT_STATS复合请求类型进行请求:
/* Body for ofp_multipart_request of type OFPMP_PORT.*/
struct ofp_port_stats_request {
uint32_t port_no; /* OFPMP_PORT 消息必须请求统计,要么给一个单一的端口 ( 在port_no 中指定的) 或者所有端口 (port_no == OFPP_ANY). */
uint8_t pad[4];
};
OFP_ASSERT(sizeof(struct ofp_port_stats_request) == 8);
port_no 字段 有选择的 过滤统计请求到给定的端口。若访问所有端口的统计 ,port_no 必须设置成 OFPP_ANY.
回复部分由以下数组组成:
/* OFPMP_PORT 请求的回复部分 . 如果一个计数器不被支持 , 设置该字段给所有的( ones). */
struct ofp_port_stats {
uint32_t port_no;
uint8_t pad[4]; /* 排到 64-bits. */
uint64_t rx_packets; /* 收到包的数量. */
uint64_t tx_packets; /* 已传送包的数量 */
uint64_t rx_bytes; /* 收到的字节数. */
uint64_t tx_bytes; /* 传送的字节数. */
uint64_t rx_dropped; /* 被 RX丢弃的包数. */
uint64_t tx_dropped; /* 被TX丢弃的包数. */
uint64_t rx_errors; /* 接受到错误的数量. 这是一个超集( super-set )更具体的接收错误,应该大于或等于所有 rx_ * _err 值的总和。 */
uint64_t tx_errors; /* 传送错误的数量 . 这是一个超集更具体的传输错误,应该大于或等于所有 tx_ * _err 值的总和(没有当前定义的。 )*/
uint64_t rx_frame_err; /* 帧调整的错误数量. */
uint64_t rx_over_err; /* 在RX溢出的数据包数. */
uint64_t rx_crc_err; /* CRC错误数. */
uint64_t collisions; /* 冲突数量. */
uint32_t duration_sec; /* 端口已经生存了的秒数. */
uint32_t duration_nsec; /* 端口已生存的时间超过 duration_sec 的纳秒数. */
};
OFP_ASSERT(sizeof(struct ofp_port_stats) == 112);
duration_sec 和 duration_nsec 字段表示端口已配置成 OpenFlow 通道已过去的时间。
持续的总的纳秒数可以被算成 duration_sec*10^9+duration_nsec. 实现时要求提供第二精度,在能获得的条件下鼓励提供更高的精度。
A.3.5.7 端口描述
端口描述请求 OFPMP_PORT_DESCRIPTION 能使控制器获得支持 OpenFlow系统里所有的端口描述。请求部分是空的。回复部分由如下数组构成:
/* 一个端口的描述 */
struct ofp_port {
uint32_t port_no;
uint8_t pad[4];
uint8_t hw_addr[OFP_ETH_ALEN];
uint8_t pad2[2]; /* 排到64 bits. */
char name[OFP_MAX_PORT_NAME_LEN];/* Null-terminated */
uint32_t config; /* OFPPC_*flags 的位图. */
uint32_t state; /* OFPPS_*flags 的位图. */
/* 描述特性的 OFPPF_*的位图. 如果不支持或不可用的所有位清零 */
uint32_t curr; /* 当前特征 */
uint32_t advertised; /* 端口公布的特性. */
uint32_t supported; /* 端口支持的特性. */
uint32_t peer; /* 对端公布的特性 . */
uint32_t curr_speed; /* 当前端口的比特率, kbps. */
uint32_t max_speed; /* 最大端口比特率 kbps */
};
OFP_ASSERT(sizeof(struct ofp_port) == 64);
这个结构被描述在 A.2.1.
A.3.5.8 队列统计
OFPMP_QUEUE复合请求消息提供队列统计给一个或多个端口和一个或多个队列。请求部分包含一个 port_no 字段为请求的统计标识出 OpenFlow 端口,或者 OFPP_ANY去查阅所有的端口。 queue_id 字段识别优先队列之一。或者 OFPQ_ALL指向在指定端口配置的所有队列。
OFPQ_ALL是 0xffffffff。
struct ofp_queue_stats_request {
uint32_t port_no; /* 如果是 OFPP_ANY表示所有端口. */
uint32_t queue_id; /* 如果是 OFPQ_ALL表示所有队列. */
};
OFP_ASSERT(sizeof(struct ofp_queue_stats_request) == 8);
回复部分由下列结构组成:
struct ofp_queue_stats {
uint32_t port_no;
uint32_t queue_id; /* 队列i.d */
uint64_t tx_bytes; /* 传送的字节数. */
uint64_t tx_packets; /* 传送的包数. */
uint64_t tx_errors; /* 由于溢出丢弃的数据包数量. */
uint32_t duration_sec; /* 队列已经生成的秒数. */
uint32_t duration_nsec; /* 队列已经生成超出 duration_sec 的纳秒数. */
};
OFP_ASSERT(sizeof(struct ofp_queue_stats) == 40);
Duration_sec 和 duration_nsec 字段表示队列已经安装在交换机里过去了的时间。持续的总时间可以用 duration_sec*10^9+duration_nsec. 实现时要求提供第二精度, 在能获得的条件下鼓励提供更高的精度。
A.3.5.9 组统计
OFPMP_GROUP复合请求消息为一个或多个组提供统计。请求部分由 group_id 字段组成。
它也能被设置为 OFPG_ALL表示交换机里所有的组。
/*OFPMP_GROUP 请求部分. */
struct ofp_group_stats_request {
uint32_t group_id; /* 如果是 OFPG_ALL表示所有的表. */
uint8_t pad[4]; /* 拍到 64 bits. */
};
OFP_ASSERT(sizeof(struct ofp_group_stats_request) == 8);
回复部分由如下结构组成:
/* 回复给 OFPMP_GROUP 请求的部分. */
struct ofp_group_stats {
uint16_t length; /* 这个流表项的长度. */
uint8_t pad[2]; /* 排到64 bits. */
uint32_t group_id; /* 组标识符. */
uint32_t ref_count; /* 直接指向该组的流或组的数量 */
uint8_t pad2[4]; /* 排到64 bits. */
uint64_t packet_count; /* 组处理过的包的数量. */
uint64_t byte_count; /* 组处理过的字节数量. */
uint32_t duration_sec; /* 组已经生存的秒数. */
uint32_t duration_nsec; /* 组已经生存的超过 duration_sec 的纳秒数. */
struct ofp_bucket_counter bucket_stats[0]; /* 每个存储段设置一个计数器. */
};
OFP_ASSERT(sizeof(struct ofp_group_stats) == 40);
字段包括创建组的 group_mod 提供的,加上 ref_count 表示计算与组相关的流的数量,packet_count,和 byte_count 计算所有组处理的数据包。
Duration_sec 和 duration_nsec 字段表示组安装在交换机里已经过去的时间。总的持续的纳秒数可以用 duration_sec*10^9+duration_nsec. 实现时要求提供第二精度, 在能获得的条件下鼓励提供更高的精度。
Bucket_stats 字段由一组 ofp_bucket_counter 结构体组成:
/* 用来做组统计回复. */
struct ofp_bucket_counter {
uint64_t packet_count; /* 存储段处理的包数. */
uint64_t byte_count; /* 存储段处理的字节数. */
};
OFP_ASSERT(sizeof(struct ofp_bucket_counter) == 16);
A.3.5.10 组描述
OFPMP_GROUP_DESC复合请求消息提供一个方式将交换机上的组设置列出来, 以及它们相应的存储段行动。请求部分是空的,而回复部分是如下结构的数组:
/* 回复给 OFPMP_GROUP_DESC请求的部分. */
struct ofp_group_desc {
uint16_t length; /* 流表项的长度. */
uint8_t type; /*OFPGT_*中之一. */
uint8_t pad; /* 填充至 64 bits. */
uint32_t group_id; /* 组标识符. */
struct ofp_bucket buckets[0]; /* 存储段列表 -- 0 或更多. */
};
OFP_ASSERT(sizeof(struct ofp_group_desc) == 8);
描述组的字段和那些 ofp_group_mod 结构体使用的一样 ( 见 A.3.4.2)。
A.3.5.11 组特征
OFPMP_GROUP_FEATURES 复合请求消息提供一个方式列出交换机上组的功能。请求部分是空的,而回复部分是如下结构:
/* 回复给 OFPMP_GROUP_FEATURES 请求的部分. 组特征. */
struct ofp_group_features {
uint32_t types; /* OFPGT_*值支持的位图. */
uint32_t capabilities; /* OFPGFC_*功能支持的位图. */
uint32_t max_groups[4]; /* 每个类型的组的最大数量. */
uint32_t actions[4]; /* 支持的 OFPAT_* 的位图. */
};
OFP_ASSERT(sizeof(struct ofp_group_features) == 40);
max_groups字段是每个类型的组的最大数量。 行动是每个组类型支持的行动。 功能使用如下标志的一个组合:
/* 组配置标志 */
enum ofp_group_capabilities {
OFPGFC_SELECT_WEIGHT= 1 << 0, /* 选择组支持的重量 */
OFPGFC_SELECT_LIVENESS= 1 << 1, /* 选择组支持的活跃度 */
OFPGFC_CHAINING= 1 << 2, /* 支持链接组 */
OFPGFC_CHAINING_CHECKS= 1 << 3, /* 为循环检查和删除链接 */
A.3.5.12 计量器统计
OFPMT_METER统计请求消息给一个或多个计量器提供统计。请求部分由一个 meter_id字段组成,它能设置成 OFPM_ALL表示交换机上所有计量器。
/* OFPMP_METER和 OFPMP_METER_CONFIG请求部分 */
struct ofp_meter_multipart_request {
uint32_t meter_id; /* 计量器实例 , 或者是 OFPM_ALL.*/
uint8_t pad[4]; /* 排列到 64 bits. */
};
OFP_ASSERT(sizeof(struct ofp_meter_multipart_request) == 8);
回复部分是如下结构:
/*OFPMP_METER请求的回复部分. 计量器统计. */
struct ofp_meter_stats {
uint32_t meter_id; /* 计量器例子 */
uint16_t len; /* 统计的字节长度. */
uint8_t pad[6];
uint32_t flow_count; /* 跳至计量器的流数量. */
uint64_t packet_in_count; /* 输入数据包的数量. */
uint64_t byte_in_count; /* 输入的字节数. */
uint32_t duration_sec; /* 计量器已经生成了的秒数. */
uint32_t duration_nsec; /* 计量器已经生存超出 duration_sec 的纳秒数. */
struct ofp_meter_band_stats band_stats[0];
/*band_stats 长度是字段长度里推出来的 */
};
OFP_ASSERT(sizeof(struct ofp_meter_stats) == 40);
packet_in_count 和 byte_in_count 对 计 量 器 处 理 过 的 所 有 数 据 包 进 行 计 数。
duration_sec 和duration_nsec 字段表示计量器安装在交换机上过去了的时间。总的持续时间可以用 duration_sec*10^9+duration_nsec。实现时要求提供第二精度,在能获得的条件下鼓励提供更高的精度。
band_stats 字段由 ofp_meter_band_stats 结构组成:
/* 给每个计量带宽的统计 */
struct ofp_meter_band_stats {
uint64_t packet_band_count; /* 带内的数据包数量. */
uint64_t byte_band_count; /* 带内的字节数量. */
};
OFP_ASSERT(sizeof(struct ofp_meter_band_stats) == 16);
packet_band_count 和 byte_band_count 对带宽处理的数据包进行计数。带宽统计的顺序必须和 OFPMT_METER_CONFIG 统计回复里的一样。
A.3.5.13 计量器配置统计
OFPMT_METER_CONFIG统计请求消息给一个或多个计量器提供配置。请求部分由一个meter_id 字段组成 , 它可以被设置成
OFPM_ALL表示交换机上所有的计量器。
/*OFPMP_METER和OFPMP_METER_CONFIG请求部分. */
struct ofp_meter_multipart_request {
uint32_t meter_id; /* 计量器实例 , 或者OFPM_ALL. */
uint8_t pad[4]; /* 对齐到 64 bits. */
};
OFP_ASSERT(sizeof(struct ofp_meter_multipart_request) == 8);
回复部分由一个如下结构组成:
/*OFPMP_METER_CONFIG 请求的回复部分 . 计量器配置 */
struct ofp_meter_config {
uint16_t length; /* 流表项的长度. */
uint16_t flags; /* All OFPMC_*表示申请. */
uint32_t meter_id; /* 计量器实例. */
struct ofp_meter_band_header bands[0]; /* 带宽的长度从字段长度里推导出 */
};
OFP_ASSERT(sizeof(struct ofp_meter_config) == 8);
该字段和用来配置计量器的字段相同 ( 见 A.3.4.4 )
A.3.5.14 仪表功能统计
OFPMT_METER_FEATRUES 统计请求消息提供了计量子系统功能集合。
请求部分是空的,和回复部分由以下结构组成:
/* 回复 ofpmp_meter_features 请求。计量功能 */
struct ofp_meter_features {
uint32_t max_meter; /* 计量器的最大数值. */
uint32_t band_types; /* 支持位图 ofpmbt_ *的值*/
uint32_t capabilities; /*ofp_meter_flags 的位图 */
uint8_t max_bands; /* 每个计量的的最大频带 */
uint8_t max_color; /* 最大的彩色值 */
uint8_t pad[2];
};
OFP_ASSERT(sizeof(struct ofp_meter_features) == 16);
A.3.5.15 实验者复合
实验者特定复合消息通过 OFPMP_EXPERIMENTER复合类型进行请求, 请求的第一个字节和回复部分结构如下:
/*ofp_multipart_request 部分/ 回复OFPMP_EXPERIMENTER. */
struct ofp_experimenter_multipart_header {
uint32_t experimenter;
/* 实验者标识符,与 ofp_experimenter_header 中格式相同 */
uint32_t exp_type; /* 实验者定义 */
/* 实验者自定义附加数据 */
};
OFP_ASSERT(sizeof(struct ofp_experimenter_multipart_header) == 8);
剩余的请求和回复部分是由实验者定义
experimenter 字段是实验者的 ID,与 OFPMP_EXPERIMENTER中的格式相同。
A.3.6 队列配置消息
队列配置超出了 OpenFlow 协议范围,可通过一个命令行工具,或通过一个外部的专用配置协议。
控制器使用下列结构来查询交换机端口上已配置队列:
/* 查询端口队列配置 */
struct ofp_queue_get_config_request {
struct ofp_header header;
uint32_t port; /* 查询的端口;应该指向一个有效的物理端口(或 OFPP_ANY 要求所有配置队列) */
uint8_t pad[4];
};
OFP_ASSERT(sizeof(struct ofp_queue_get_config_request) == 16);
交换机使用 OFP_QUEUE_GET_CONFIG_REPLY 命令回复,其中包含配置队列的列表。
/* 给定端口的队列配置 */
struct ofp_queue_get_config_reply {
struct ofp_header header;
uint32_t port;
uint8_t pad[4];
struct ofp_packet_queue queues[0]; /* 已配置的队列列表 */
};
OFP_ASSERT(sizeof(struct ofp_queue_get_config_reply) == 16);
A.3.7 分组报文
当控制器通过数据通路发送一个数据包,就使用 OFPT_PACKET_OUT 消息:
/* 发送数据包(控制器 ->通路) */
struct ofp_packet_out {
struct ofp_header header;
uint32_t buffer_id; /* 指定的标识符通过数据通路( OFP_NO_BUFFER如果没有) */
uint32_t in_port; /* 分组的输入端口或 OFPP_CONTROLLER*/
uint16_t actions_len; /* 以字节数组的大小的作用 */
uint8_t pad[6];
struct ofp_action_header actions[0]; /* 行动列表 -0 或更多 */
/* 可变大小的行动清单可在后面跟着分组数据。
*如果 buffer_id = -1 目前的数据是有意义的。
/ *uint8_t 数据[ 0 ] ;*/ /* 分组数据 长度是从长度字段的头部计算的 */
};
OFP_ASSERT(sizeof(struct ofp_packet_out) == 24
buffer_id 与 ofp_packet_in 消息中给出的是相同的。 如果 buffer_id 是 OFP_NO_BUFFER,
则数据包位于数据阵列中, 和封装在消息里的数据包由行动的消息进行处理。 OFP_NO_BUFFER
等于 0xffffffff。如果 buffer_id 是有效的,相关的端口被行动消息从缓冲区删除。
in_port 字段表示入端口,端口必须与 OpenFlow 处理数据包相关。它必须设置为一个有效的标准交换机端口(见 4.2 )或 OFPP_CONTROLLER. 例如,当使用 OFPP_TABLE,OFPP_IN_PORT, OFPP_ALL 和组处理数据包时就使用这个字段。
Action 是一个行动列表定义交换机如何处理数据包的字段。可包括包修改, 组处理和一个输出端口。 一个 OFPT_PACKET_OUT 行动清单消息也可以指定 OFPP_TABLE 保留端口作为输出行动来处理 OpenFlow 流水线中的数据包,从第一个流表开始(见 4.5 )。如果OFPP_TABLE 被指定, in_port 作为查找时流表的入端口。
在某些情况下,发送到 OFPP_TABLE 的数据包,经过流表项行动或漏表后,可能转发给控制器。对这样控制器到交换机回路的检测和执行行动不在本规范范围内。一般来说,OpenFlow 的消息不保证是顺序处理。
因此,如果一个使用 OFPP_TABLE 的 OFPP_TABLE 消息依赖于最近发送到交换机( 与 OFPT_FLOW_MOD 消 息 ) 的 流, 一 个 OFPT_BARRIER_REQUEST 消 息 需 要 在OFPT_PACKET_OUT 消息之前,确保在执行 OFPP_TABLE 之前把流表项交给流表。
A.3.8 屏障消息
当 控 制 器 要 想 确 保 消 息 已 送 到 或 想 接 收 到 通 知 来 完 成 操 作, 它 可 以 使 用 一 个OFPT_BARRIER_REQUEST 消息。此消息没有内容。 收到后, 在执行基于 Barrier Request任何消息之前,交换机必须处理完所有先前收到的信息,包括发送相应的回复或错误信息。
当处理完成,交换机必须发送一个消息 OFPT_BARRIER_REPLY 携带 XID 最初的请求。
A.3.9 任务请求消息
当控制器要转变角色,它使用 OFPT_ROLE_REQUEST 消息,结构如下:
/* 请求和回复消息的角色 */
struct ofp_role_request {
struct ofp_header header; /* ofpt_role_request / ofpt_role_reply 型 */
uint32_t role; /* ofpcr_role_ *之一 */
uint8_t pad[4]; /* 对齐到 64 位*/
uint64_t generation_id; /* 主设备选择产生的标识符
*/
};
OFP_ASSERT(sizeof(struct ofp_role_request) == 24);
role 字段就是控制器要担任的新角色,并且可以有以下值:
/* 控制器的角色 */
enum ofp_controller_role {
OFPCR_ROLE_NOCHANGE = 0, /* 不改变目前的角色 */
OFPCR_ROLE_EQUAL= 1, /* 默认角色,完全访问。 */
OFPCR_ROLE_MASTER= 2, /* 完全访问,最多一个主设备。 */
OFPCR_ROLE_SLAVE= 3, /* 只读访问 */
};
如果消息中的角色值是 OFPCR_ROLE_MASTER 或 OFPCR_ROLE_SLAVE,交换机必须验证 generation_id检查过期消息(见6.3.4)。如果验证失败,交换机必须抛弃角色要求和返回OFPET_ROLE_REQUEST_FAILED 类型和 OFPRRFC_STALE 代码的错误消息。
如果角色值是 OFPCR_ROLE_MASTER, 所有其他角色是 OFPCR_ROLE_MASTER 的控制器都要改变为OFPCR_ROLE_SLAVE (见6.3.4)。如果角色值 是OFPCR_ROLE_NOCHANGE, 控制器当前的角色就不改变; 这使控制器查询其目前的角色而无需改变它。
收到一个 OFPT_ROLE_REQUEST 的消息后,如果没有错误,交换机必须返回一个OFPT_ROLE_REPLY 消息。这则消息的结构与 OFPT_ROLE_REQUEST 消息是完全一样的,role字段就是当前控制的角色。generation_id设置为当前generation_id(上一次成功角色请求的 generation_id,角色是OFPCR_ROLE_MASTER 或OFPCR_ROLE_SLAVE ),
如果目前的 generation_id 控制器没有设置,回复中的 generation_id 必须设置为最大字段 (无符号值相当于-1)。
A.3.10 设置异步配置信息
在一个给定的OpenFlow通道上,分别使用OFPT_SET_ASYNC 和OFPT_GET_ASYNC_REQUEST 消息,控制器能设置和查询它想要接收的异步消息(除错误消息)。
交换机使用 OFPT_GET_ASYNC_REPLY 消息响应一个 OFPT_GET_ASYNC_REQUEST消息;它不回复一个设置配置的请求。
基 于 OpenFlow 头 部 的 OFPT_GET_ASYNC_REQUEST 消 息 没 有 body。
OFPT_SET_ASYNC 和 OFPT_GET_ASYNC_REPLY 消息的格式如下:
/* 异步消息的配置。 */
struct ofp_async_config {
struct ofp_header header; /* OFPT_GET_ASYNC_REPLYor OFPT_SET_ASYNC.*/
uint32_t packet_in_mask[2]; /* ofpr_ *值的位掩码 */
uint32_t port_status_mask[2]; /* ofppr_ *值的位掩码。 */
uint32_t flow_removed_mask[2]; /* ofprr_ *值的位掩码。. */
};
OFP_ASSERT(sizeof(struct ofp_async_config) == 32);
结构 struct ofp_async_config 包含三个二维数组。每个数组控制是否接收控制器一个特定 的 枚 举 ofp_type 异 步 消 息。 当 控 制 器 是 OFPCR_ROLE_EQUAL 或OFPCR_ROLE_MASTER 角色时,每个数组中的元素 0 表示感兴趣的消息。当控制器是OFPCR_ROLE_SLAVE 角色时, 元素为 1。每个数组元素的位掩码中的 0 禁止接收与比特位置有关的 reason 代码消息。而 1 则可以接收。例如,在 port_status_mask [1] 中比特值为
2
2
=4, 当 控 制 器 的 角 色 是 OFPCR_ROLE_SLAVE 时, 决 定 控 制 器 是 否 接 收 理 由 是OFPPR_MODIFY (值为 2)的 OFPT_PORT_STATUS 消息。
OFPT_SET_ASYNC 设置控制器是否应该接受一个交换机产生的异步消息。其他的OpenFlow 功能控制是否产生一个给定的消息;例如,在 OFPFF_SEND_FLOW_REM 标志控制交换机在流表项删除时是否产生 OFPT_FLOW_REMOVED 消息。
一个交换机的配置,例如使用 OpenFlow 配置协议,一旦 OpenFlow 建立连接, 就可以设置异步消息的初始配置。交换机没有配置的话,初始配置应:
在“master”或“ equal ”的角色,使能所有的 OFPT_PACKET_IN 消息,除了那些带有 OFPR_INVALID_TTL 原 因 的, 使 能 所 有 的 OFPT_PORT_STATUS 和OFPT_FLOW_REMOVED 消息。
在 “ slave ” 角 色, 使 能 所 有 OFPT_PORT_STATUS 消 息,并禁用所有OFPT_PACKET_IN 和 OFPT_FLOW_REMOVED 的消息。
利用 OFPT_SET_ASYNC 的配置设置是针对一个特定的 OpenFlow 的通道,它不会影响任何其他 OpenFlow 通道,无论是目前建立的或是以后建立的。
配置设置 OFPT_SET_ASYNC 不过滤器或影响的错误消息。
A.4 异步消息
A.4.1 包输入消息
当数据包通过数据通路进行接收和发送给控制器,都使用 OFPT_PACKET_IN 消息:
/* 端口接收到的数据包(数据通路 ->控制器) */
struct ofp_packet_in {
struct ofp_header header;
uint32_t buffer_id; /* 分配的数据通路 ID。 */
uint16_t total_len; /* 帧全长 */
uint8_t reason; /* 包被发送的原因(一个 ofpr_ *)*/
uint8_t table_id; /* 被查询的流表标识符 */
uint64_t cookie; /* 查询的流表项 cookie */
struct ofp_match match; /* 分组元数据。可变尺寸。 */
/* 可变大小和填充匹配总是跟着:
* 2 个全零填充字节,然后是以太网帧,其长度是根据 header.length 推断。
*以太网帧前填充的字节,确保以太网报头后的 IP 头部(如果有的话)是 32 位
*/
//uint8_t pad[2]; /* 对齐 64 位+ 16 位 */
//uint8_t data[0]; /* 以太网帧 */
};
OFP_ASSERT(sizeof(struct ofp_packet_in) == 32);
buffer_id 是个不透明的值,数据通路使用它识别一个缓冲数据包。 当一个数据包进行缓存,消息的某些字节将包含在消息的数据部分。如果数据包是因为“发送给控制器”的行动而发送, 那么流建立请求的ofp_action_output 会发送 max_len 字节。 如果数据包其他原因发送,如无效的 TTL, 然后 OFPT_SET_CONFIG 消息至少发送 miss_send_len 字节。默认miss_send_len是 128 字节。如果数据包没有缓冲 - 要么是因为没有可用的缓冲区,或因明确表示通过 OFPCML_NO_BUFFER 请求- 整个数据包都包含在数据部分, 而且 buffer_id 就是OFP_NO_BUFFER。
实现缓冲的交换机希望通过文档公开,可用的缓冲数量和缓冲区可重复使用前的时间长度。交换机必须妥善处理这样的事件,当一个 packet_in 消息缓冲,而控制器没有反应的情况。交换机应防止缓冲区被重复使用,直到它已由控制器处理,或经过一段时间(文档中说明)。
data 字段包含的数据包本身,或缓冲包的一小部分。包头部反映了前面处理对数据包的任何变化。
reason字段可以是这些值的某个:
/* 为什么这个包被发送到控制器? */
enum ofp_packet_in_reason {
OFPR_NO_MATCH= 0, /* 没有匹配的流(漏表流表项) */
OFPR_ACTION= 1, /* 输出给控制器的行动。 */
OFPR_INVALID_TTL= 2, /* 包有无效的 TTL */
};
OFPR_INVALID_TTL 表明携带无效的 IP TTL 或 MPLS TTL 被 OpenFlow 流水线拒绝并 传 给 控 制 器。 不 需 要 对 每 一 个 数 据 包 都 进 行 无 效 的 TTL 检 查, 但 它 至 少 在 每 次OFPAT_DEC_MPLS_TTL 或OFPAT_DEC_NW_TTL 行动应用到一个包时进行检查。
cookie 字段包含导致包被发送到控制器的流表项 cookie。如果一个 cookie 不能与一个特定的流有关的,这字段必须设置为 -1 (0xffffffffffffffff )。例如,如果包输入是在组存储段里或从行动集产生的。
当有事件触发包输入消息发生,同时包含一组 OXM TLVs,match字段就反映分组的头部和上下午。这个上下文包括与数据包以前处理发生的任何改变,包括已经执行的行动,行动集的任何变化,除了无任何变化的。这OXM TLVs 必须包括上下文字段,也就是,其值不能从数据包确定的字段。标准的上下文字段是OFPXMT_OFB_IN_PORT,
OFPXMT_OFB_IN_PHY_PORT,OFPXMT_OFB_METADATA 和OFPXMT_OFB_TUNNEL_ID. 所有位都是零的字段应忽略。 OXM TLVs 可包括先前从分组中提取的数据包头部字段,也包括那些在处理过程中的任何修改。
当在物理端口直接收到一个包时,而且未被逻辑端口处理, OFPXMT_OFB_IN_PORT和 OFPXMT_OFB_IN_PHY_PORT 有 相 同 的 值, OpenFlow 物 理 端 口 的 port no。
OFPXMT_OFB_IN_PHY_PORT 如果与 OFPXMT_OFB_IN_PORT 有相同的值则应忽略。
当一个包是通过一个物理端口的逻辑端口上接收的, OFPXMT_OFB_IN_PORT 是逻辑端口的端口的 port no,OFPXMT_OFB_IN_PHY_PORT 是物理端口的 port no。例如,考虑一个包在隧道接口上接收,接口是由两个物理端口的链路汇聚组( LAG )进行定义的。如果隧道接口是绑定到 OpenFlow 的逻辑端口,那么OFPXMT_OFB_IN_PORT 是隧道 port no,OFPXMT_OFB_IN_PHY_PORT 是已配置隧道的 LAG 物理端口 port no 成员。
OFPXMT_OFB_IN_PORT TLV 引用的端口必须是用来匹配流表项的端口(见 5.3 ),也必须对 OpenFlow 处理有用(即 OpenFlow 可以转发数据包到该端口,依靠端口标志)。
OFPXMT_OFB_IN_PHY_PORT 不需要有效匹配或进行 OpenFlow 处理。
A.4.2 流删除消息
如 果 控 制 器 请 求 通 知 流 表 项 超 时 或 从 表 中 删 除 ( 见 5.5 ), 数 据 通 路 使 用
OFPT_FLOW_REMOVED 消息:
/* 流删除(数据通路 ->控制器) */
struct ofp_flow_removed {
struct ofp_header header;
uint64_t cookie; /* 不透明的控制器发出标识符。 */
uint16_t priority; /* 流表项优先级。 */
uint8_t reason; /* 一个 ofprr_ *
uint8_t table_id; /* 流表的标识符 */
uint32_t duration_sec; /* Time flow was alive in seconds. */
uint32_t duration_nsec;
/* Time flow was alive in nanoseconds beyond duration_sec. */
uint16_t idle_timeout; /* 从原来的 flow mod 空闲超时 */
uint16_t hard_timeout; /* 从原来的 flow mod硬超时。 */
uint64_t packet_count;
uint64_t byte_count;
struct ofp_match match; /* 字段描述。可变尺寸。 */
};
OFP_ASSERT(sizeof(struct ofp_flow_removed) == 56);
Match,cookie,和 priority 字段与那些在 flow mod 请求里使用的相同。
Reason 字段是下列之一:
/* 为什么流中删除? */
enum ofp_flow_removed_reason {
OFPRR_IDLE_TIMEOUT= 0, /* 流idle_timeout 空闲时间超过 */
OFPRR_HARD_TIMEOUT= 1, /* 时间超过 hard_timeout。 */
OFPRR_DELETE= 2, /* 由 DELETEflow mod消除。 */
OFPRR_GROUP_DELETE= 3, /* 删除的组。 */
};
duration_sec 和 duration_nsec 字段在第 A.3.5.2 描述。
idle_timeout 和 hard_timeout 字段是直接从创建流表项的 flow mod复制。
利用上述三个字段,你可以找到流表项活跃的时间,以及流表项接收信息的时间。
packet_count 和 byte_count 分别表示与流表项相关联的包和字节的数值。这些计数器应像其他统计计数器 (见 A.3.5 );如果没有设置, 它们都是无符号的, 并应设置为字段最大值。
A.4.3 端口的状态信息
当端口被添加,修改,从数据通路中删除,利用 OFPT_PORT_STATUS 消息告知控制器:
/* 数据通路的一个物理端口改变 */
struct ofp_port_status {
struct ofp_header header;
uint8_t reason; /* 一个 ofppr_*/
uint8_t pad[7]; /* 对齐到 64位。 */
struct ofp_port desc;
};
OFP_ASSERT(sizeof(struct ofp_port_status) == 80);
reason可以是下列值之一:
/* 是什么改变了关于物理端口 */
enum ofp_port_reason {
OFPPR_ADD= 0, /* 端口添加。 */
OFPPR_DELETE= 1, /* 端口被删除 */
OFPPR_MODIFY= 2, /* 某些端口的属性已经改变了。 */
};
A.4.4 错误消息
交换机需要把问题通知给控制器。这是通过 OFPT_ERROR_MSG 消息完成:
/* ofpt_error :错误消息(数据通路 ->控制器)。 */
struct ofp_error_msg {
struct ofp_header header;
uint16_t type;
uint16_t code;
uint8_t data[0]; /* 可变长度的数据。用类型和代码区分。无填充 */
};
OFP_ASSERT(sizeof(struct ofp_error_msg) == 12);
Type 的值表示错误的高层次的类型。代码值是在类型基础上解析。 数据是可变长度的并且基于类型和代码解析。除非另有规定 data字段包含至少 64 字节的失败请求,导致错误消息产生,如果失败请求小于 64 字节,它应该是完整的请求而无填充。
如果错误消息是来自控制器的响应,例如, OFPET_BAD_REQUEST,OFPET_BAD_ACTION, OFPET_BAD_INSTRUCTION, OFPET_BAD_MATCH, 或OFPET_FLOW_MOD_FAILED, 那么头部的 xid 字段必须匹配那个引起错误的消息。
以_EPERM 结束的错误代码表示一个允许产生的错误, 例如, 控制器和交换机之间插入一个 OpenFlow 管理程序。
目前定义的错误类型是:
/*ofp_error_message 里的“ type ”值。这些值是不变的:他们不会在未来版本的协议改变(虽然新值可以添加) */
enum ofp_error_type {
OFPET_HELLO_FAILED= 0, /* hello 协议失败 */
OFPET_BAD_REQUEST= 1, /* 请求不被理解 */
OFPET_BAD_ACTION= 2, /* 在行动中描述的错误。 */
OFPET_BAD_INSTRUCTION= 3, /* 指令表中错误。 */
OFPET_BAD_MATCH= 4, /* 错误匹配。 */
OFPET_FLOW_MOD_FAILED= 5, /* 修改流表项问题。 */
OFPET_GROUP_MOD_FAILED= 6, /* 修改组表项问题。 */
OFPET_PORT_MOD_FAILED= 7, /* 端口 属性 请求失败。 */
OFPET_TABLE_MOD_FAILED= 8, /* 流表请求失败 */
OFPET_QUEUE_OP_FAILED= 9, /* 队列操作失败 */
OFPET_SWITCH_CONFIG_FAILED= 10, /* 交换机配置请求失败 */
OFPET_ROLE_REQUEST_FAILED= 11, /* 控制器任务请求失败 */
OFPET_METER_MOD_FAILED= 12, /* 计量器错误 */
OFPET_TABLE_FEATURES_FAILED= 13, /* 设置流表功能失败 */
OFPET_EXPERIMENTER= 0xffff /* 实验者的错误消息 */
};
为 OFPET_HELLO_FAILED 错误 type,下面是目前定义的 codes:
/* 为OFPET_HELLO_FAILED ofp_error_msg “code”的值。 Data包含 ASCII 文本字符串,可以提供详细的故障。 /
enum ofp_hello_failed_code {
OFPHFC_INCOMPATIBLE= 0, / 不兼容的版本 /
OFPHFC_EPERM= 1, / 允许失败 */
};
Data 字段包含 ASCII 文本字符串,加上为什么发生错误的细节。
为 OFPET_BAD_REQUEST 错误 type,下面是目前定义的 code:
/* 为 OFPET_BAD_REQUESTofp_error_msg “code”的值。 Data 至少包含请求失败的第一个 64
字节。 */
enum ofp_bad_request_code {
OFPBRC_BAD_VERSION= 0, /* ofp_header. 版本不支持。 */
OFPBRC_BAD_TYPE= 1, /* ofp_header.type 不支持的类型 */
OFPBRC_BAD_MULTIPART= 2, /* ofp_multipart_request.type 类型不支持 */
OFPBRC_BAD_EXPERIMENTER = 3, /* 不支持的实验者身份 ID(在 ofp_experimenter_header
或 ofp_multipart_request 或 ofp_multipart_reply )。*/
OFPBRC_BAD_EXP_TYPE= 4, /* 实验者类型不支持。 */
OFPBRC_EPERM= 5, /* 允许误差 */
OFPBRC_BAD_LEN= 6, /* 类型错误的请求长度。 */
OFPBRC_BUFFER_EMPTY = 7, /* 已被使用的指定缓冲区。 */
OFPBRC_BUFFER_UNKNOWN = 8, /* 指定的缓冲区不存在。 */
OFPBRC_BAD_TABLE_ID= 9, /* 指定的表标识符无效或不存在。 */
OFPBRC_IS_SLAVE= 10, /* 拒绝因为控制器是从设备 */
OFPBRC_BAD_PORT= 11, /* 无效的端口 */
OFPBRC_BAD_PACKET= 12, /* 包输出里无效的数据包 */
OFPBRC_MULTIPART_BUFFER_OVERFLOW = 13, /* ofp_multipart_request 分配的缓冲区溢出
*/
下面是目前定义 OFPET_BAD_ACTION 错误type 的code:
/* OFPET_BAD_INSTRUCTION ofp_error_msg 'code'的值。数据至少包含请求失败的第一个 64字节*/
enum ofp_bad_action_code {
OFPBAC_BAD_TYPE= 0, /* 未知的行动类型。 */
OFPBAC_BAD_LEN= 1, /* 行动的长度问题。 */
OFPBAC_BAD_EXPERIMENTER = 2, /* 未知的实验者指定标识符 */
OFPBAC_BAD_EXP_TYPE= 3, /* 实验者的标识符。未知的行动 */
OFPBAC_BAD_OUT_PORT = 4, /* 问题有效输出端口。*/
OFPBAC_BAD_ARGUMENT = 5, /* 不良行为的论证 */
OFPBAC_EPERM= 6, /* 允许的错误 */
OFPBAC_TOO_MANY= 7, /* 不能处理这么多的行动。 */
OFPBAC_BAD_QUEUE= 8, /* 问题验证输出队列。*/
OFPBAC_BAD_OUT_GROUP = 9, /* 在转发的动作无效组标识符 */
OFPBAC_MATCH_INCONSISTENT = 10, /* 行动不能申请匹配,或 设置字段丢失的前提。*/
OFPBAC_UNSUPPORTED_ORDER = 11, /* 应用行动指令的行动列表不支持行动排序 */
OFPBAC_BAD_TAG= 12, /* 行动使用不支持的标签 / 封装。 */
OFPBAC_BAD_SET_TYPE= 13, /* 在set_field 行动不支持的类型。 */
OFPBAC_BAD_SET_LEN= 14, /* 在set_field 行动长度问题。 */
OFPBAC_BAD_SET_ARGUMENT = 15, /* 在set_field 行动不好的论据 */
};
下面是目前定义为 OFPET_BAD_INSTRUCTION 错误type 的code:
/* 为OFPET_BAD_INSTRUCTION ofp_error_msg “code”的值。数据至少包含请求失败的
第一个 64字节。 */
enum ofp_bad_instruction_code {
OFPBIC_UNKNOWN_INST= 0, /* 未知的指令 */
OFPBIC_UNSUP_INST= 1, /* 交换机或表不支持的指令。 */
OFPBIC_BAD_TABLE_ID= 2, /* 指定流表标识符无效。 */
OFPBIC_UNSUP_METADATA= 3, /* 元数据值不支持的数据通路。 */
OFPBIC_UNSUP_METADATA_MASK = 4, /* 元数据的掩码值不支持的数据通路。 */
OFPBIC_BAD_EXPERIMENTER= 5, /* 未知的实验者指定标识符。 */
OFPBIC_BAD_EXP_TYPE= 6, /* 实验者未知指令标识符。 */
OFPBIC_BAD_LEN= 7, /* 在指令长度的问题。 */
OFPBIC_EPERM= 8, /* 允许的错误。 */
};
为OFPET_BAD_MATCH 错误type,下面是目前定义的 code:
/* 为OFPET_BAD_MATCH ofp_error_msg “code”的值。数据至少包含请求失败的第一个 64
字节。 */
enum ofp_bad_match_code {
OFPBMC_BAD_TYPE= 0, /* 不支持匹配指定的匹配类型 */
OFPBMC_BAD_LEN= 1, /* 匹配长度问题 */
OFPBMC_BAD_TAG= 2, /* 匹配采用不支持的标签 / 封装。 */
OFPBMC_BAD_DL_ADDR_MASK = 3,
/* 不支持的数据地址掩码–交换机不支持任意链路地址掩码。 */
OFPBMC_BAD_NW_ADDR_MASK = 4,
/* 不支持的网络地址掩码–交换机不支持任意网络地址掩码。 */
OFPBMC_BAD_WILDCARDS = 5, /* 不支持的域掩蔽或在匹配中省略的组合 */
OFPBMC_BAD_FIELD= 6, /* 在匹配中不支持的字段类型。 */
OFPBMC_BAD_VALUE= 7, /* 在匹配域不受支持的值。 */
OFPBMC_BAD_MASK= 8,
/* 不支持匹配中指定的掩码,字段不是 dl_address 或nw_address。 */
OFPBMC_BAD_PREREQ= 9, /* 一个先决条件不满足。 */
OFPBMC_DUP_FIELD= 10, /* 一种字段类型是重复的。 */
OFPBMC_EPERM= 11, /* 允许的错误。 */
};
For the OFPET_FLOW_MOD_FAILEDerror type, the following codes are currently de_ned:
对于 OFPET_TABLE_MOD_FAILED 错误type,下面是目前定义的代码:
/* 对于 OFPET_TABLE_MOD_FAILED 的ofp_error_msg “code”的值。” data 至少包含
请求失败第一个 64字节的。 */
enum ofp_flow_mod_failed_code {
OFPFMFC_UNKNOWN = 0, /* 未指定的错误 */
OFPFMFC_TABLE_FULL= 1, /* 因为表已满,流不能添加。 */
OFPFMFC_BAD_TABLE_ID= 2, /* 流表不存在 */
OFPFMFC_OVERLAP= 3, /* 使用check_overlap 标志尝试添加重叠的流 */
OFPFMFC_EPERM= 4, /* 允许的错误。 */
OFPFMFC_BAD_TIMEOUT= 5, /* 因为不支持空闲 / 硬超时,流表不被添加。 */
OFPFMFC_BAD_COMMAND = 6, /* 不支持的或未知的命令。 */
OFPFMFC_BAD_FLAGS= 7, /* 不支持的或未知的标志。 */
};
对于 OFPET_SWITCH_CONFIG_FAILED 错误type,下面是目前定义的 code:
/* 为OFPET_SWITCH_CONFIG_FAILED 的ofp_error_msg “code”的值。” data 至少包含请求失
败第一个 64字节的 */
enum ofp_group_mod_failed_code {
OFPGMFC_GROUP_EXISTS = 0, /* 组不添加因为组添加试图取代一个已经存在的组 */
OFPGMFC_INVALID_GROUP= 1, /* 组不能添加因为组指定的组是无效的 */
OFPGMFC_WEIGHT_UNSUPPORTED = 2, /* 交换机在所选组中不支持不均等的负荷分担 */
OFPGMFC_OUT_OF_GROUPS = 3, /* 组表已满。 */
OFPGMFC_OUT_OF_BUCKETS = 4, /* 组的行动存储段已超过最大值。 */
OFPGMFC_CHAINING_UNSUPPORTED = 5, /* 交换机不支持需要转发去的组 */
OFPGMFC_WATCH_UNSUPPORTED = 6, /* 这个组不能监视 w指定的 atch_port 或watch_group。*/
OFPGMFC_LOOP= 7, /* 组表项会引起循环 */
OFPGMFC_UNKNOWN_GROUP = 8, /* 组不能修改,因为修改组试图修改一个不存在的组 */
OFPGMFC_CHAINED_GROUP = 9, /* 组不能删除因为另一组是向其转发。 */
OFPGMFC_BAD_TYPE= 10, /* 不支持的或未知的组类型 */
OFPGMFC_BAD_COMMAND = 11, /* 不支持的或未知的命令。 */
OFPGMFC_BAD_BUCKET = 12, /* 存储段里的错误 */
OFPGMFC_BAD_WATCH = 13, /* 在观察端口 / 组的错误。 */
OFPGMFC_EPERM= 14, /* 允许的错误。 */
};
下面是目前定义为 OFPET_PORT_MOD_FAILED 错误type 的代码:
/* OFPET_METER_MOD_FAILED 的 ofp_error_msg “code”的值。Data至少包含请求失败
的第一个 64字节*/
enum ofp_port_mod_failed_code {
OFPPMFC_BAD_PORT= 0, /* 指定的端口号不存在 */
OFPPMFC_BAD_HW_ADDR = 1, /* 指定的硬件地址不匹配端口号 */
OFPPMFC_BAD_CONFIG= 2, /* 指定的配置无效 */
OFPPMFC_BAD_ADVERTISE= 3, /* 指定的标识符是无效的。 */
OFPPMFC_EPERM= 4, /* 允许的错误. */
};
下面是目前定义为 OFPET_METER_MOD_FAILED 错误type 的code:
/* OFPET_METER_MOD_FAILED 的 ofp_error_msg “code”的值。 Data至少包含请求
失败的第一个 64字节*/
enum ofp_table_mod_failed_code {
OFPTMFC_BAD_TABLE= 0, /* 指定的流表不存在 */
OFPTMFC_BAD_CONFIG= 1, /* 指定的配置无效 */
OFPTMFC_EPERM= 2, /* 允许的错误 */
};
下面是目前定义为 OFPET_TABLE_MOD_FAILED 错误type 的code:
/* OFPET_TABLE_MOD_FAILED 的 ofp_error_msg “code”的值。 Data至少包含请求
失败的第一个 64字节*/
enum ofp_queue_op_failed_code {
OFPQOFC_BAD_PORT= 0, /* 无效的端口(或端口不存在) */
OFPQOFC_BAD_QUEUE = 1, /* 队列不存在 */
OFPQOFC_EPERM= 2, /* 允许的错误 */
};
下面是目前定义为 OFPET_SWITCH_CONFIG_FAILED 错误 type 的 code:
/* OFPET_SWITCH_CONFIG_FAILED 的 ofp_error_msg “code”的值。 Data至少包含请求失
败的第一个 64字节*/
enum ofp_switch_config_failed_code {
OFPSCFC_BAD_FLAGS= 0, /* 指定的标志无效 */
OFPSCFC_BAD_LEN= 1, /* 指定的长度无效 */
OFPSCFC_EPERM= 2, /* 允许的错误 */
};
下面是目前定义为 OFPET_ROLE_REQUEST_FAILED 错误type 的code:
/* 对于 OFPET_ROLE_REQUEST_FAILED 的 ofp_error_msg “code”的值。 Data至少包含
请求失败的第一个 64字节*/
enum ofp_role_request_failed_code {
OFPRRFC_STALE= 0, /* 过时消息: 旧的 generation_id. */
OFPRRFC_UNSUP= 1, /* 控制器不支持角色的变化 */
OFPRRFC_BAD_ROLE= 2, /* 无效的角色 */
};
下面是目前定义为 OFPET_METER_MOD_FAILED 错误 type 的 code:
/* 对于 OFPET_METER_MOD_FAILED 的ofp_error_msg “code”的值。 Data至少包含请求
失败的第一个 64字节*/
enum ofp_meter_mod_failed_code {
OFPMMFC_UNKNOWN = 0, /* 未指定的错误. */
OFPMMFC_METER_EXISTS= 1, /* 计量器不加因为试图取代现有的计量器 */
OFPMMFC_INVALID_METER= 2, /* 计量器不加因为指定的计量器是无效的 */
OFPMMFC_UNKNOWN_METER = 3, /* 计量器不修改因为试图修改一个不存在的计量器 */
OFPMMFC_BAD_COMMAND = 4, /* 不支持的或未知的命令 */
OFPMMFC_BAD_FLAGS= 5, /* 配置的标志不支持 */
OFPMMFC_BAD_RATE= 6, /* 速率不支持 */
OFPMMFC_BAD_BURST = 7, /* 不支持的突发大小. */
OFPMMFC_BAD_BAND= 8, /* 频带不支持 */
OFPMMFC_BAD_BAND_VALUE = 9, /* 频带值不被支持 */
OFPMMFC_OUT_OF_METERS = 10, /* 没有更多的计量器有效 */
OFPMMFC_OUT_OF_BANDS = 11, /* 对于计量器的属性数量已超过最大值. */
};
下面是目前定义为 OFPET_TABLE_FEATURES_FAILED 错误 type 的 codes:
/* 对于 OFPET_TABLE_FEATURES_FAILED 的 ofp_error_msg “code”的值。 Data至
少包含请求失败的第一个 64字节*/
enum ofp_table_features_failed_code {
OFPTFFC_BAD_TABLE= 0, /* 指定的流表不存在 */
OFPTFFC_BAD_METADATA= 1, /* 无效的元数据的掩码 */
OFPTFFC_BAD_TYPE= 2, /* 未知的属性类型. */
OFPTFFC_BAD_LEN= 3, /* 属性长度问题 */
OFPTFFC_BAD_ARGUMENT = 4, /* 不支持的属性值 */
OFPTFFC_EPERM= 5, /* 允许的错误 */
};
对于为 OFPET_EXPERIMENTER 错误 type,错误信息由以下结构体和字段定义,后面跟
着实验者定义的数据:
/* OFPET_EXPERIMENTER: 错误消息(数据通路 ->控制器) */
struct ofp_error_experimenter_msg {
struct ofp_header header;
uint16_t type; /* ofpet_experimenter*/
uint16_t exp_type; /* 实验者定义 */
uint32_t experimenter; / 实验者 ID与ofp_experimenter_header 中相同 */
uint8_t data[0]; /* 可变长度的数据。基于类型和代码解析。无填充 */
};
OFP_ASSERT(sizeof(struct ofp_error_experimenter_msg) == 16);
实验者字段是实验者 ID,与 ofp_experimenter 结构体中的相同(见 A.5.4 )。
A.5 对称信息
A.5.1 Hello
OFPT_HELLO 消息由一 OpenFlow头加一组可变尺寸 hello 元素。
/* OFPT_HELLO. 此消息包含零个或多个可变尺寸的元素。未知的元素类型必须忽略 / 跳
过,允许未来的扩展 */
struct ofp_hello {
struct ofp_header header;
/* hello 元素列表 */
struct ofp_hello_elem_header elements[0]; /* 元素列表, 0或更多 */
};
OFP_ASSERT(sizeof(struct ofp_hello) == 8);
Header字段中的 version 字段 (见A.1 )必须设置为发送者 (见6.3.1 )支持的最高 OpenFlow
协议版本。
Elemnets字段是一组 hello 元素, 包含连接进行初次握手时传递的可选数据。 实现时必须
忽略(跳过) Hello 消息中不支持的所有元素。列表中的元素类型定义如下:
/* Hello 元素类型 */
enum ofp_hello_elem_type {
OFPHET_VERSIONBITMAP= 1, /* 支持的版本位图 */
};
元素定义包含元素的类型,长度,和任何相关的数据:
/* 所有hello 元素共同的头 */
struct ofp_hello_elem_header {
uint16_t type; /* OFPHET_*之一*/
uint16_t length; /* 在本单元的字节长度 */
};
OFP_ASSERT(sizeof(struct ofp_hello_elem_header) == 4);
OFPHET_VERSIONBITMAP 元素使用下列结构和字段:
/*hello 元素的版本位图 */
struct ofp_hello_elem_versionbitmap {
uint16_t type; /* OFPHET_VERSIONBITMAP.*/
uint16_t length; /* 在本元素的字节长度 */
/* 其次是:
包含位图的(长度 -4 )字节,然后(长度+ 7)/ 8×8-( 长度)(0 ~7) 的全零字节 */
uint32_t bitmaps[0]; /* 位图列表 - 支持的版本 */
};
OFP_ASSERT(sizeof(struct ofp_hello_elem_versionbitmap) == 4);
Bitmaps字段表示设备支持的 OpenFlow交换协议版本, 可在版本协商后使用 (见6.3.1 )。
对位图的比特通过协议的 ofp_version 数索引;如果左位移数值表示的比特等于设置的ofp_version,就支持这个 OpenFlow版本。字段中包含的位图数值取决于支持的最高版本号:
ofp_version 0-31 在第一个位图中编码, ofp_version 32-63 在第二个位图中编码,以此类推。例如,如果一个交换机只支持 1.0 版( ofp_version= 0x01)和1.3 版(ofp_version= 0x04),第一个位图将被设置为 0x00000012。
A.5.2 回送请求
回送请求消息包括一个 OpenFlow头加一个任意长度的数据字段。数据字段可能是一个消息的时间戳来检查延迟,不同长度来测量带宽,或零大小来验证交换机和控制器之间是否活跃。
A.5.3 回送答复
回送应答消息包括一个 OpenFlow头加上回声请求消息中未修改的数据字段。
一个OpenFlow协议在实现时划分成多个层, 回声请求 / 应答的逻辑应该在 “最深” 的开发层实现。 例如,在 OpenFlow的实现里,用户空间进程靠近内核模块,回声请求 / 应答是则在内核模块实现。接收一个格式正确的回应则表示端到端功能比在用户空间的进程中实现的回声请求/ 应答更可靠,同时也提供了更精确的端到端延迟时间。
A.5.4 实验者
实验者消息的定义如下:
/* 实验者的扩展 */
struct ofp_experimenter_header {
struct ofp_header header; /* ofpt_experimenter*/
uint32_t experimenter; /* 实验者标识符
* MSB0:低位字节由 IEEE OUI定义。
* MSB!= 0:ONF定义的 */
uint32_t exp_type; /* 实验者定义 */
/* 实验者定义任意附加数据 */
};
OFP_ASSERT(sizeof(struct ofp_experimenter_header) == 16);
Experimenter 的字段是一个 32位的值,唯一标识实验者。如果最高位字节是零,接下来的三个字节是实验者的 IEEE OUI。如果最高位字节不为零, 这是一个 ONF分配的值。 如果实验者没有(或希望使用)他们的 OUI,他们应该与 ONF联系获得一个唯一的实验者标识符。
标准的 OpenFlow处理对其余部分不进行解析,可由实验者任意定义。
如果一个交换机不理解实验者的扩充,它必须发送一个带有OFPBRC_BAD_EXPERIMENTER 错误代码和 OFPET_BAD_REQUEST 错误类型的OFPT_ERROR 消息。