|
最近两周一直在研究flow_mod这个消息,flow_mod这个消息是openflow中最重要的消息,没有之一,所以花在它的时间上比较多,而且里面涉及的内容也比较复杂。社区有一篇博文对我帮助还是很大。因此这边可能和他的文章有一部分冲突,但是对于学习和总结无所谓啦!!
我们在上一篇中有介绍了,OpenvSwitch是如何进行不同openflow协议版本的控制的,也知道了入口函数是handle_openflow__,本文的主题是flow_mod消息,因此我们直接进入flow_mod处理函数。
众所周知,flow_mod是用于下发流表的。可以简单理解为一个flow_mod就是就是一个流表项。如果有谁不是很了解flow_mod,请自行阅读openflow协议。
通过入口函数openflow_handle__可以知道flow_mod处理函数是,为了描述相对清晰,我会在代码中直接进行注释,除了需要特别指出或者着重描写才在博文中单独写出来。
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
static enum ofperr
handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
OVS_EXCLUDED(ofproto_mutex)
{
/* 每一个ofproto代表一个交换机,并且挂在ofconn连接中,
* 如果有谁不知道其中关联关系,请参考我的第三篇文章。
* 因此第一步就需要获取openflow交换机对象,即ofproto
*/
struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
struct ofputil_flow_mod fm; /* flow mod 消息 */
uint64_t ofpacts_stub[1024 / 8];
struct ofpbuf ofpacts;
enum ofperr error;
error = reject_slave_controller(ofconn); /* 角色权限验证我们可以忽略。 */
if (error) {
goto exit;
}
/* 将stub挂在ofpbuf中这个地方经常见到不再详细分析有不明白的可参考前面几篇*/
ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
/* flow_mod解码解码过程其实很简单,只要按照标准协议格式一个字段一个字段
* 解析就可以啦!!这里我们按照某一种报文进行讲解,这样对于代码理解帮助非常有
* 意义,也不至于会有这样的疑问,为什么这样写??哈哈哈
*/
error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_protocol(ofconn),
&ofpacts,
u16_to_ofp(ofproto->max_ports),
ofproto->n_tables);
/* 校验action内容 */
if (!error) {
error = ofproto_check_ofpacts(ofproto, fm.ofpacts, fm.ofpacts_len);
}
if (!error) {
error = handle_flow_mod__(ofproto, ofconn, &fm, oh); /* 真正处理flow_mod函数 */
}
if (error) {
goto exit_free_ofpacts;
}
/*
* 修改统计值大概是为了控制器下发统计请求用的吧!!
*/
ofconn_report_flow_mod(ofconn, fm.command); /* 释放内存,这里不会真正调用free函数的 */
exit_free_ofpacts:
ofpbuf_uninit(&ofpacts);
exit:
return error;
}
|
上面是flow_mod函数处理框架,下面我们进行逐个分析,我们进行分析时候会结构flow_mod消息,因此有具体的消息内容,我们在分析代码的时候不至于那么抽象。
下面报文就是我抓取的,比较有针对性,控制器下发的流表是从host1到host2,出端口是交换机的2。具体报文和网络拓扑如下:
注:红色线就是打通的通道。
由于函数ofputil_decode_flow_mod相对较长,因此采用分段描述,层层深入方式进行剖析。
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/* Converts an OFPT_FLOW_MOD or NXT_FLOW_MOD message 'oh' into an abstract
* flow_mod in 'fm'. Returns 0 if successful, otherwise an OpenFlow error
* code.
*
* Uses 'ofpacts' to store the abstract OFPACT_* version of 'oh''s actions.
* The caller must initialize 'ofpacts' and retains ownership of it.
* 'fm->ofpacts' will point into the 'ofpacts' buffer.
*
* Does not validate the flow_mod actions. The caller should do that, with
* ofpacts_check.
* OpenvSwitch支持两种flow格式,一种标准flow_mod,一种NXT flow_mod.
* 对于OpenvSwitch来说无论是哪种flow_mod,经过ovs抽象之后都会转成
* struct ofputil_flow_mod进行存储。
*/
enum ofperr
ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
const struct ofp_header *oh,
enum ofputil_protocol protocol,
struct ofpbuf *ofpacts,
ofp_port_t max_port, uint8_t max_table)
{
ovs_be16 raw_flags;
enum ofperr error;
struct ofpbuf b;
enum ofpraw raw;
/* 将flow_mod消息挂载在ofpbuf的b中 */
ofpbuf_use_const(&b, oh, ntohs(oh->length));
/* 解析消息头前面有一篇介绍OpenvSwitch是如何进行版本兼容。*/
raw = ofpraw_pull_assert(&b);
|
上面内容相对简单,也不用多说些。下面是我们进行着重分析的。由于我们基于openflow1.3进行连接的,因此raw实际值是OFPRAW_OFPT11_FLOW_MOD。(opeflow1.1之后版本flow_mod没有变化。)在这里就有if-else判断,所以我们会着重分析if分支。 一般情况下,flow_mod消息主要是由openflow固定字段,match字段,instruction字段(可以没有),然而match字段中可以包含多个oxm字段(可以没有oxm),instruction字段中可以包含多个action字段。其中oxm格式是TLV(Type-Length-Value)。针对上面的报文来说,oxm有两个,action只有一个。所以我们能够预测if分支中主要是针对这些字段进行解析。
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
if (raw == OFPRAW_OFPT11_FLOW_MOD) {
/* Standard OpenFlow 1.1+ flow_mod. Openflow1.1之后版本flow_mod无变化*/
const struct ofp11_flow_mod *ofm;
/* 获取flow_mod固定长度字段头其实是通过指针偏移指向buf,并非申请内存
* 主要是标准flow_mod, 在下面会把ofm相关值赋值到fm中。
*/
ofm = ofpbuf_pull(&b, sizeof *ofm); /* 从b中移出大小为sizeof(*ofm) */
/* 获取flow_mod中的match数据赋值到fm->match中
* 如果存在多个oxm的话,也会同时解析出来的,而且所有的
* oxm做为一个flow流结构
*/
error = ofputil_pull_ofp11_match(&b, &fm->match, NULL);
if (error) {
return error;
}
/* 解析instruction数据主要是解析action,将报文中action保存在ofpacts中*/
error = ofpacts_pull_openflow_instructions(&b, ofpbuf_size(&b), oh->version,
ofpacts);
if (error) {
return error;
}
/* Translate the message. Flow_mod固定字段解析。这个地方其实没有什么需要讲解,纯粹赋值。*/
fm->priority = ntohs(ofm->priority);
if (ofm->command == OFPFC_ADD
|| (oh->version == OFP11_VERSION
&& (ofm->command == OFPFC_MODIFY ||
ofm->command == OFPFC_MODIFY_STRICT)
&& ofm->cookie_mask == htonll(0))) {
/* In OpenFlow 1.1 only, a "modify" or "modify-strict" that does
* not match on the cookie is treated as an "add" if there is no
* match. */
fm->cookie = htonll(0);
fm->cookie_mask = htonll(0);
fm->new_cookie = ofm->cookie;
} else {
fm->cookie = ofm->cookie;
fm->cookie_mask = ofm->cookie_mask;
fm->new_cookie = OVS_BE64_MAX;
}
fm->modify_cookie = false;
fm->command = ofm->command;
/* Get table ID.
*
* OF1.1 entirely forbids table_id == OFPTT_ALL.
* OF1.2+ allows table_id == OFPTT_ALL only for deletes. */
fm->table_id = ofm->table_id;
if (fm->table_id == OFPTT_ALL
&& (oh->version == OFP11_VERSION
|| (ofm->command != OFPFC_DELETE &&
ofm->command != OFPFC_DELETE_STRICT))) {
return OFPERR_OFPFMFC_BAD_TABLE_ID;
}
fm->idle_timeout = ntohs(ofm->idle_timeout);
fm->hard_timeout = ntohs(ofm->hard_timeout);
fm->buffer_id = ntohl(ofm->buffer_id);
error = ofputil_port_from_ofp11(ofm->out_port, &fm->out_port);
if (error) {
return error;
}
fm->out_group = (ofm->command == OFPFC_DELETE ||
ofm->command == OFPFC_DELETE_STRICT
? ntohl(ofm->out_group)
: OFPG11_ANY);
raw_flags = ofm->flags;
} else {
/* 这个else是针对openflow1.0的flow_mod消息进行解析的,我们可以忽略掉 */
}
最后一部分代码,主要内容就是常规校验,我也没有太深入阅读,这里为了保证一致性还是顺便提一下。
/*
* 保存actions。因为fm是抽象层flow_mod,在最后生成流表项的时候需要这个。
*/
fm->ofpacts = ofpbuf_data(ofpacts);
fm->ofpacts_len = ofpbuf_size(ofpacts);
error=ofputil_decode_flow_mod_flags(raw_flags,fm->command,oh->version, &fm->flags);
if (error) {
return error;
}
if (fm->flags & OFPUTIL_FF_EMERG) {
/* We do not support the OpenFlow 1.0 emergency flow cache, which
* is not required in OpenFlow 1.0.1 and removed from OpenFlow 1.1.
*
* OpenFlow 1.0 specifies the error code to use when idle_timeout
* or hard_timeout is nonzero. Otherwise, there is no good error
* code, so just state that the flow table is full. */
return (fm->hard_timeout || fm->idle_timeout
? OFPERR_OFPFMFC_BAD_EMERG_TIMEOUT
: OFPERR_OFPFMFC_TABLE_FULL);
}
/* 校验action */
return ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
&fm->match.flow, max_port,
fm->table_id, max_table, protocol);
} /* 整个函数结束 */
|
以上就是flow_mod解析函数大体框架,下面我们来看一下match、instruction这两个解析函数。首先我们来看一下函数调用关系图:
根据关系图,从函数3、函数4内容都比较短小,函数也比较容易理解,我们从函数5开始进行分析,分析如下:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
/*
* strict – 是否需要严格检查
* b – 保存match的原始报文buf
* match – 保存解析后的match 输出参数
*/
static enum ofperr
oxm_pull_match__(struct ofpbuf *b, bool strict, struct match *match)
{
struct ofp11_match_header *omh = ofpbuf_data(b); /* 获取match head指针 */
uint8_t *p;
uint16_t match_len;
if (ofpbuf_size(b) < sizeof *omh) {
return OFPERR_OFPBMC_BAD_LEN;
}
match_len = ntohs(omh->length);
if (match_len < sizeof *omh) {
return OFPERR_OFPBMC_BAD_LEN;
}
if (omh->type != htons(OFPMT_OXM)) {
return OFPERR_OFPBMC_BAD_TYPE;
}
/* 尝试将缓冲区b中移除大小为match_len数据
* 经过下面这个函数处理之后,缓冲区b里面已经没有match相关数据,
* 剩下就是instruction域
*/
p = ofpbuf_try_pull(b, ROUND_UP(match_len, 8));
if (!p) {
VLOG_DBG_RL(&rl, "oxm length %u, rounded up to a "
"multiple of 8, is longer than space in message (max "
"length %"PRIu32")", match_len, ofpbuf_size(b));
return OFPERR_OFPBMC_BAD_LEN;
}
/*
* 解析一下这个函数实参:
* p + sizeof *omh == 跳过match头部
* match_len - sizeof *omh == 总长度减去match头部长度,即实际报文内容长度。
*/
return nx_pull_raw(p + sizeof *omh, match_len - sizeof *omh,
strict, match, NULL, NULL);
}
|
在介绍nx_pull_raw函数之前,我们先看来看一下这个全局数组const struct mf_field mf_fields[MFF_N_IDS](在文件meta-flow.c),这个数组在下面的函数会用到mf_from_nxm_header。所先把分析一下。
这个虽然数组很长,但是里面的注释对于理解还是有帮助的,数组中是按照网络层次进行划分的,主要是metadata, L2, L2 .5,L3, L4, L5。也就是说根据报文中match字段可得出位于数组的哪层。例如上面报文,是针对物理地址(源/宿mac)那么我们可以推断出是位于数组的L2,如下面代码所示:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
const struct mf_field mf_fields[MFF_N_IDS] = {
/* ## -------- ## */
/* ## metadata ## */
/* ## -------- ## */
{
MFF_DP_HASH, "dp_hash", NULL,
MF_FIELD_SIZES(be32),
MFM_FULLY,
MFS_HEXADECIMAL,
MFP_NONE,
false,
NXM_NX_DP_HASH, "NXM_NX_DP_HASH",
NXM_NX_DP_HASH, "NXM_NX_DP_HASH", 0,
OFPUTIL_P_NXM_OXM_ANY,
OFPUTIL_P_NXM_OXM_ANY,
-1,
}, {…
}
/* ## -- ## */
/* ## L2层以太网层 ## */
/* ## -- ## */
{/*源mac地址 */
MFF_ETH_SRC, "eth_src", "dl_src",
MF_FIELD_SIZES(mac),
MFM_FULLY,
MFS_ETHERNET,
MFP_NONE,
true,
NXM_OF_ETH_SRC, "NXM_OF_ETH_SRC",
OXM_OF_ETH_SRC, "OXM_OF_ETH_SRC", OFP12_VERSION,
OFPUTIL_P_ANY,
OFPUTIL_P_NXM_OF11_UP, /* Bitwise masking only with NXM and OF11+! */
-1,
}, {/* 目的mac地址 */
MFF_ETH_DST, "eth_dst", "dl_dst",
MF_FIELD_SIZES(mac),
MFM_FULLY,
MFS_ETHERNET,
MFP_NONE,
true,
NXM_OF_ETH_DST, "NXM_OF_ETH_DST",
OXM_OF_ETH_DST, "OXM_OF_ETH_DST", OFP12_VERSION,
OFPUTIL_P_ANY,
OFPUTIL_P_NXM_OF11_UP, /* Bitwise masking only with NXM and OF11+! */
-1,
}, {/*以太网类型*/
MFF_ETH_TYPE, "eth_type", "dl_type",
MF_FIELD_SIZES(be16),
MFM_NONE,
MFS_HEXADECIMAL,
MFP_NONE,
false,
NXM_OF_ETH_TYPE, "NXM_OF_ETH_TYPE",
OXM_OF_ETH_TYPE, "OXM_OF_ETH_TYPE", OFP12_VERSION,
OFPUTIL_P_ANY,
OFPUTIL_P_NONE,
-1,
},{…}
/* ## ---- ## */
/* ## L2.5层 ## */
/* ## ---- ## */
{…}
/* ## ---- ## */
/* ## L3层 ## */
/* ## ---- ## */
{…}
} /* 数组结束 */
|
我们来看一下蓝色字体,这个宏定义 枚举:
C
OFPXMT12_OFB_ETH_SRC = 4
OFPXMT12_OFB_ETH_DST = 3
OFPXMC12_OPENFLOW_BASIC = 0x8000
#define OXM_OF_ETH_SRC OXM_HEADER (OFPXMT12_OFB_ETH_SRC, 6)
#define OXM_OF_ETH_DST OXM_HEADER (OFPXMT12_OFB_ETH_DST, 6)
#define OXM_HEADER(FIELD, LENGTH) \
NXM_HEADER(OFPXMC12_OPENFLOW_BASIC, FIELD, LENGTH)
通过上面代码罗列可得知,OXM_HEADER是用于构造报文OXM头部的宏,而OXM是以TLV格式进行封装的。针对上面报文和宏定义就有如下对应(图片蓝色下划线):
经过函数nxm_do_init处理,会把这个数组中成员挂在全局变量all_fields,这个变量是一个hmap结构,至于这里具体存储关系是怎么关联的,这里不进行详细解析(可以参考函数nxm_do_init)。如果有不理解hmap的网友可以去看一下我之前文章。下面我们继续分析nx_pull_raw函数。
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
/*
* p -- 指向第一个oxm字段起始内存位置
* match_len = 整个match字段-match头部长度即所有oxm字段长度,不包括padding
* strict 是否需要严格检查
* match 存储match数据(出参)
*/
static enum ofperr
nx_pull_raw(const uint8_t *p, unsigned int match_len, bool strict,
struct match *match, ovs_be64 *cookie, ovs_be64 *cookie_mask)
{
uint32_t header;
ovs_assert((cookie != NULL) == (cookie_mask != NULL));
match_init_catchall(match); /* init match. */
if (cookie) {
*cookie = *cookie_mask = htonll(0);
}
if (!match_len) {
return 0;
}
/* 我们分析一下for的条件
* 1、可能有多个oxm字段因此需要一个一个遍历
* 标准里面oxm头部只能说类tlv格式,为什么说是类tlv呢?
* 标准tlv格式 t和l是两个字节(有的是4个字节),而oxm中tlv
* t = 3个字节,l = 1个字节其中t中前两个字节是表示类型,
* 最后一个字节是又被分成两部分,高7位表示子类型,最后一位代表是否存在mask (根据上面报文可以知道)
*
* 2、for循环中魔鬼数字4表示tlv中header字段长度,也就是说header是固长度
* 3、函数nx_entry_ok 其实是拷贝oxm header部分(4个字节)到变量uint32 header中,对于上面报文中第一个oxm,这个header=0x80000606。
* 4、宏NXM_LENGTH是用于获取tlv中length值。就是header&0xff。针对上面header是0x80000606,与oxff与操作后正好是6。通过报文查看length的确是6。
* 5、每解析一次oxm,p就要指向下一个oxm起始位置,match_len就要减去对应长度。
*/
for (;
(header = nx_entry_ok(p, match_len)) != 0;
p += 4 + NXM_LENGTH(header), match_len -= 4 + NXM_LENGTH(header)) {
const struct mf_field *mf;
enum ofperr error;
mf = mf_from_nxm_header(header); /* 从hmap中获取mf,mf这个数组在上面已经介绍过!!这里就不详细介绍了*/
if (!mf) {
if (strict) {
error = OFPERR_OFPBMC_BAD_FIELD;
} else {
continue;
}
} else if (!mf_are_prereqs_ok(mf, &match->flow)) {
error = OFPERR_OFPBMC_BAD_PREREQ;
} else if (!mf_is_all_wild(mf, &match->wc)) {
error = OFPERR_OFPBMC_DUP_FIELD;
} else {
unsigned int width = mf->n_bytes;
union mf_value value; /* 这个地方的value是一个联合,设计非常巧妙,可以同时满足各种类型。 */
memcpy(&value, p + 4, width); /* 将tlv(match)中的value字段赋值到value变量*/
if (!mf_is_value_valid(mf, &value)) {
error = OFPERR_OFPBMC_BAD_VALUE;
} else if (!NXM_HASMASK(header)) {/* 没有设置mask标志会进入此分支针对上面报文,会进入这个分支的!!*/
error = 0;
mf_set_value(mf, &value, match); /* 上面已经说过了value是一个联合类型,因此在mf赋值的时候需要进行类型判断,才能正确赋值。 */
} else {/* 设置了mask标志才会进入else分支 */
union mf_value mask;
memcpy(&mask, p + 4 + width, width);
if (!mf_is_mask_valid(mf, &mask)) {
error = OFPERR_OFPBMC_BAD_MASK;
} else {
error = 0;
check_mask_consistency(p, mf);
mf_set(mf, &value, &mask, match);
}
}
}
/* 下面都一些基本校验,对于业务逻辑没有影响我们不进行深入分析 */
/* Check if the match is for a cookie rather than a classifier rule. */
if ((header == NXM_NX_COOKIE || header == NXM_NX_COOKIE_W) && cookie) {
if (*cookie_mask) {
error = OFPERR_OFPBMC_DUP_FIELD;
} else {
unsigned int width = sizeof *cookie;
memcpy(cookie, p + 4, width);
if (NXM_HASMASK(header)) {
memcpy(cookie_mask, p + 4 + width, width);
} else {
*cookie_mask = OVS_BE64_MAX;
}
error = 0;
}
}
if (error) {
VLOG_DBG_RL(&rl, "bad nxm_entry %#08"PRIx32" (vendor=%"PRIu32", "
"field=%"PRIu32", hasmask=%"PRIu32", len=%"PRIu32"), "
"(%s)", header,
NXM_VENDOR(header), NXM_FIELD(header),
NXM_HASMASK(header), NXM_LENGTH(header),
ofperr_to_string(error));
return error;
}
}
return match_len ? OFPERR_OFPBMC_BAD_LEN : 0;
}
|
上面分析比较混乱,这里进行一下总结:
1、通过上面这个函数处理(核心代码是for循环),就会把报文中两个oxm字段解析成功并且保存在入参match里面。
2、我们知道oxm表现形式是一个tlv格式,OpenvSwitch会把value字段赋值到match结构体中的,struct flow(flow.h)结构体。flow这个结构体和mf_fields是有关联关系的。
3、通过上面解析后,flow中字段数据时这样的(实际内存中没有冒号):
flow.dl_dst[6] = 00:00:00:00:00:02
flow.dl_src[6] = 00:00:00:00:00:01
其他字段是默认值,大部分是0。
通过上面的一系列分析,openflow中的match字段就结束了。现在我们返回到函数ofputil_decode_flow_mod中,下面应该解析instructions。
基于目前OpenvSwitch实现主要支持这6中actions,分别是:openflow1.3中meter表,openflow1.1中apply_action,openflow1.1中clear_action,openflow1.1中write_action,openflow1.1中write_metadata,openflow1.1中goto_table。针对当前的flowmod中action是apply_action,如下图所示:
在介绍函数之前,我们先来看一下函数中即将用到指针数组,const struct ofp11_instruction *insts[N_OVS_INSTRUCTIONS],通过查看宏定义可知, N_OVS_INSTRUCTIONS=6
默认指针数组中保存地址是null,经过处理后会把其中某个数组存储单元修改为有效地址。针对上面那个报文,instructions的类型是apply_actions,因此经过函数decode_openflow11_instructions会把数组下标为OVSINST_OFPIT11_APPLY_ACTIONS的值修改为有效地址(即上图中红色字体)。
现在我们来看一下这个函数代码,下面红色字体就是那6种action:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
enum ofperr
ofpacts_pull_openflow_instructions(struct ofpbuf *openflow,
unsigned int instructions_len,
enum ofp_version version,
struct ofpbuf *ofpacts)
{
const struct ofp11_instruction *instructions;
/* 通过查看宏定义可知, N_OVS_INSTRUCTIONS=6 */
const struct ofp11_instruction *insts[N_OVS_INSTRUCTIONS];
enum ofperr error;
ofpbuf_clear(ofpacts); /* 清空缓冲区输出参数保留action */
if (instructions_len % OFP11_INSTRUCTION_ALIGN != 0) {
VLOG_WARN_RL(&rl, "OpenFlow message instructions length %u is not a "
"multiple of %d",
instructions_len, OFP11_INSTRUCTION_ALIGN);
error = OFPERR_OFPBIC_BAD_LEN;
goto exit;
}
/* 移除instructions_len大小内存数据instructions 保存数据起始位置*/
instructions = ofpbuf_try_pull(openflow, instructions_len);
if (instructions == NULL) {
VLOG_WARN_RL(&rl, "OpenFlow message instructions length %u exceeds "
"remaining message length (%"PRIu32")",
instructions_len, ofpbuf_size(openflow));
error = OFPERR_OFPBIC_BAD_LEN;
goto exit;
}
/*
* 解析openflow中instructions,并保存在指针数组out中
* 这个函数介绍重点。我们放到后面进行详细解说!!
* 这里说一下insts这个指针数组,即每个数组元素都是一个指针,指针类型就是
* 标准instruction头部,其大小固定是6。这里insts就是上面表格,经过这个函数
* 处理之后就会把某一个数组单元设置成有效地址。
*/
error = decode_openflow11_instructions(
instructions, instructions_len / OFP11_INSTRUCTION_ALIGN,
insts);
if (error) {
goto exit;
}
/*
* 针对上面的解析结构即对out指针数组进行action解析
* 解析meter表
*/
if (insts[OVSINST_OFPIT13_METER]) {…
}
/*
* 应用action--解析,当前flowmod就这个!着重说一下这个,
*其他无关代码已经删除
*/
if (insts[OVSINST_OFPIT11_APPLY_ACTIONS]) {
const union ofp_action *actions;
size_t max_actions;
/*
* 可以有多个action
* actions变量保存报文actions指针,这是一个联合结构。
* max_actions变量保存action个数
*/
get_actions_from_instruction(insts[OVSINST_OFPIT11_APPLY_ACTIONS],
&actions, &max_actions);
/*
* 解析action 保存在ofpacts
* 通过不同类型判断,是openflow1.0还是openflow1.1。对于上面报文来说,
* 最终调用解析函数是openflow1.1的函数即ofpact_from_openflow11。
* 注:actions这个变量时一个联合,这个地方涉及非常巧妙。
*/
error = ofpacts_from_openflow(actions, max_actions, version, ofpacts);
if (error) {
goto exit;
}
}
/*
* 清空action--解析
*/
if (insts[OVSINST_OFPIT11_CLEAR_ACTIONS]) {…
}
/*
* 重写action--解析
*/
if (insts[OVSINST_OFPIT11_WRITE_ACTIONS]) {…
}
/*
* 重写元数据--解析
*/
if (insts[OVSINST_OFPIT11_WRITE_METADATA]) {…
}
/*
* 跳转流表--解析
*/
if (insts[OVSINST_OFPIT11_GOTO_TABLE]) {…
}
error = ofpacts_verify(ofpbuf_data(ofpacts), ofpbuf_size(ofpacts));
exit:
if (error) {
ofpbuf_clear(ofpacts);
}
return error;
}
|
我们现在来分析一下这两个函数:decode_openflow11_instructions(解析instructions函数)和ofpacts_from_openflow(解析actions函数)。
函数decode_openflow11_instructions主要是循环遍历,不断解析instructions,内部最终处理函数是decode_openflow11_instruction,对于这个函数,比较难看懂,主要是因为里面switch-case子句是通过宏定义的,这样不便于理解。然而我们可以借助编译器(GCC),让它帮我们展开宏定义。由于展开后代码变得非常庞大,因此我只相关代码展示出来:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
static enum ofperr
decode_openflow11_instruction(const struct ofp11_instruction *inst,
enum ovs_instruction_type *type)
{
uint16_t len = ntohs(inst->len);
switch (inst->type) {
case CONSTANT_HTONS(OFPIT11_EXPERIMENTER):
return OFPERR_OFPBIC_BAD_EXPERIMENTER;
/* 该报文相关的case子句—apply_actions */
case CONSTANT_HTONS( OFPIT11_APPLY_ACTIONS ):
if ( true ? len >= sizeof(struct ofp11_instruction_actions) : len == sizeof(struct ofp11_instruction_actions) )
{
*type = OVSINST_OFPIT11_APPLY_ACTIONS;/* 返回枚举定义*/
return(0);
}
else
{
return(OFPERR_OFPBIC_BAD_LEN);
}
default:
return OFPERR_OFPBIC_UNKNOWN_INST;
}
}
|
我们再来看一下actions解析函数,上面已经说过了,对于openflow1.3的actions报文,最终会进入这个ofpact_from_openflow11函数,函数主体代码如下:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
static enum ofperr
ofpact_from_openflow11(const union ofp_action *a, enum ofp_version version,
struct ofpbuf *out)
{
enum ofputil_action_code code;
enum ofperr error;
struct ofpact_vlan_vid *vlan_vid;
struct ofpact_vlan_pcp *vlan_pcp;
error = decode_openflow11_action(a, &code); /* 这个函数和上面那个函数类似,也是通过宏定义构建的,也可以通过同样方式获取宏展开后代码。 */
if (error) {
return error;
}
/*
* openflow1.2+ 中部分消息将废弃掉
*/
if (version >= OFP12_VERSION) {
…
}
}
switch (code) {
case OFPUTIL_ACTION_INVALID:
#define OFPAT10_ACTION(ENUM, STRUCT, NAME) case OFPUTIL_##ENUM:
#define OFPAT13_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM:
#include "ofp-util.def"
OVS_NOT_REACHED;
case OFPUTIL_OFPAT11_OUTPUT: /* 针对上面报来说,最终会进入这个case子句 */
return output_from_openflow11(&a->ofp11_output, out); /* 将ofp11_output中端口信息在保存out中。 */
…
}//end switch 函数结束
}
static enum ofperr
output_from_openflow11(const struct ofp11_action_output *oao,
struct ofpbuf *out)
{
struct ofpact_output *output;
enum ofperr error;
/* 这个函数是宏定义函数,最终调用是调用ofpact_put。宏定义位置:ofp_actions.h,689行 */
output = ofpact_put_OUTPUT(out);
output->max_len = ntohs(oao->max_len);
error = ofputil_port_from_ofp11(oao->port, &output->port); /* 将端口保存在output->port中。 */
if (error) {
return error;
}
return ofpact_check_output_port(output->port, OFPP_MAX); /* 验证端口 */
}
|
至此,基本上flow_mod消息就解析完成了,那么我们接下来做什么呢?下发到数据平面(datapath),通过handle_flow_mod__进行下发。请参考下一篇。
作者简介:
徐小冰:毕业于河北大学,主要从事嵌入式软件开发,虚拟化,SDN。目前基于ODL和Open vSwitch进行二次开发,希望与广大网友一起探讨学习。作者系OpenDaylihgt群(194240432)资深活跃用户,@IT难人。
声明:本文转载自网络。版权归原作者所有,如有侵权请联系删除。 |
扫描并关注51学通信微信公众号,获取更多精彩通信课程分享。
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|