最近两周一直在研究flow_mod这个消息,flow_mod这个消息是openflow中最重要的消息,没有之一,所以花在它的时间上比较多,而且里面涉及的内容也比较复杂。社区有一篇博文对我帮助还是很大。因此这边可能和他的文章有一部分冲突,但是对于学习和总结无所谓啦!!
我们在上一篇中有介绍了,OpenvSwitch是如何进行不同openflow协议版本的控制的,也知道了入口函数是handle_openflow__,本文的主题是flow_mod消息,因此我们直接进入flow_mod处理函数。
众所周知,flow_mod是用于下发流表的。可以简单理解为一个flow_mod就是就是一个流表项。如果有谁不是很了解flow_mod,请自行阅读openflow协议。
通过入口函数openflow_handle__可以知道flow_mod处理函数是,为了描述相对清晰,我会在代码中直接进行注释,除了需要特别指出或者着重描写才在博文中单独写出来。
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相对较长,因此采用分段描述,层层深入方式进行剖析。
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分支中主要是针对这些字段进行解析。
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开始进行分析,分析如下:
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,如下面代码所示:
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层 ## */
/* ## ---- ## */
{…
}
}
/* 数组结束 */
|
我们来看一下蓝色字体,这个宏定义
枚举:
1
2
3
4
5
6
7
|
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函数。
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:
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),让它帮我们展开宏定义。由于展开后代码变得非常庞大,因此我只相关代码展示出来:
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函数,函数主体代码如下:
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操作主要是有五类操作,增加、修改、严格修改、删除、严格删除。代码如下,对于上面报文是添加操作:
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
|
static
enum
ofperr
handle_flow_mod__
(
struct
ofproto
*
ofproto
,
struct
ofconn
*
ofconn
,
struct
ofputil_flow_mod
*
fm
,
const
struct
ofp_header
*
oh
)
OVS_EXCLUDED
(
ofproto_mutex
)
{
enum
ofperr
error
;
ovs_mutex_lock
(
&
ofproto_mutex
)
;
if
(
ofproto
->
n_pending
<
50
)
{
switch
(
fm
->
command
)
{
case
OFPFC_ADD
:
error
=
add_flow
(
ofproto
,
ofconn
,
fm
,
oh
)
;
/* 将上面解析出来match、instructions保存到ofproto的flow中 */
break
;
case
OFPFC_MODIFY
:
…
case
OFPFC_MODIFY_STRICT
:
…
case
OFPFC_DELETE
:
…
case
OFPFC_DELETE_STRICT
:
…
default
:
…
}
}
else
{
ovs_assert
(
!
list_is_empty
(
&
ofproto
->
pending
)
)
;
error
=
OFPROTO_POSTPONE
;
}
ovs_mutex_unlock
(
&
ofproto_mutex
)
;
/* 执行rule。此函数比较简单,通过函数指针,进行下发到datapath。
* 主要通过netlink协议。
*/
run_rule_executes
(
ofproto
)
;
return
error
;
}
|
我们现在来看一下add_flow这个函数,这个函数比较大,需要我们耐心分析。这个函数主要是对解析成功match、instructions再次进行抽象,抽象成openvswitch中rule。
虽然add_flow这个函数比较长,但是函数逻辑还是比较清晰:如果已经存在rule则进行修改操作,否则创建(这个是openflow标准)。针对我们这个flow_mod,显然是创建,所以只需要关心创建流程即可,其他流程可以以后再进行详细分析。
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
|
static
enum
ofperr
add_flow
(
struct
ofproto
*
ofproto
,
struct
ofconn
*
ofconn
,
struct
ofputil_flow_mod
*
fm
,
const
struct
ofp_header
*
request
)
OVS_REQUIRES
(
ofproto_mutex
)
{
struct
oftable
*
table
;
struct
cls_rule
cr
;
struct
rule
*
rule
;
uint8_t
table_id
;
int
error
=
0
;
if
(
!
check_table_id
(
ofproto
,
fm
->
table_id
)
)
{
error
=
OFPERR_OFPBRC_BAD_TABLE_ID
;
return
error
;
}
/* Pick table. */
if
(
fm
->
table_id
==
0xff
)
{
//不关心,报文中table_id是0
}
else
if
(
fm
->
table_id
<
ofproto
->
n_tables
)
{
table_id
=
fm
->
table_id
;
/* 设置table_id,用于保存rule */
}
else
{
return
OFPERR_OFPBRC_BAD_TABLE_ID
;
}
/* 选择table对象 */
table
=
&
ofproto
->
tables
[
table_id
]
;
/* 校验是否有权限例如:只读 */
if
(
!
oftable_is_modifiable
(
table
,
fm
->
flags
)
)
{
return
OFPERR_OFPBRC_EPERM
;
}
if
(
!
(
fm
->
flags
&
OFPUTIL_FF_HIDDEN_FIELDS
)
)
{
/* 如果没有设置这个标志位则进入if分支 */
if
(
!
match_has_default_hidden_fields
(
&
fm
->
match
)
)
{
VLOG_WARN_RL
(
&
rl
,
"%s: (add_flow) only internal flows can set "
"non-default values to hidden fields"
,
ofproto
->
name
)
;
return
OFPERR_OFPBRC_EPERM
;
}
}
|
以上这些操作,对于我们分析代码流程不是很关键,我们只需要知道,通过报文中的table_id字段,能够获取OpenvSwitch中定义的table对象即可,其他内容无需深入研究。下面这部分代码,主要是用于修改操作,即如果存在rule则进行修改,修改完成后直接退出函数。
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
|
/* 规则分类器初始化 */
cls_rule_init
(
&
cr
,
&
fm
->
match
,
fm
->
priority
)
;
/* Transform "add" into "modify" if there's an existing identical flow.
* 如果flow存在则add动作变成修改动作对于rule操作必须是线程安全的
* 对于第一次添加,下面通过查找,返回的rule肯定是null
* classifier_find_rule_exactly严格查找
*/
fat_rwlock_rdlock
(
&
table
->
cls
.
rwlock
)
;
rule
=
rule_from_cls_rule
(
classifier_find_rule_exactly
(
&
table
->
cls
,
&
cr
)
)
;
/* 查找操作,如果存在返回rule对象,反之为null。*/
fat_rwlock_unlock
(
&
table
->
cls
.
rwlock
)
;
if
(
rule
)
{
/* flow存在则进行修改操作 */
cls_rule_destroy
(
&
cr
)
;
if
(
!
rule_is_modifiable
(
rule
,
fm
->
flags
)
)
{
return
OFPERR_OFPBRC_EPERM
;
}
else
if
(
rule
->
pending
)
{
return
OFPROTO_POSTPONE
;
}
else
{
struct
rule_collection
rules
;
rule_collection_init
(
&
rules
)
;
rule_collection_add
(
&
rules
,
rule
)
;
fm
->
modify_cookie
=
true
;
error
=
modify_flows__
(
ofproto
,
ofconn
,
fm
,
request
,
&
rules
)
;
/* 修改*/
rule_collection_destroy
(
&
rules
)
;
return
error
;
}
}
|
针对我们的报文来说,肯定是不存在rule,所以不会进入这个修改分支。因此不进行深入分析,而且修改操作是基于添加后进行,所以个人认为,如果把添加流程搞清楚后,修改操作也会非常简单。
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
|
/* Serialize against pending deletion. */
if
(
is_flow_deletion_pending
(
ofproto
,
&
cr
,
table_id
)
)
{
// 不关心
}
/* Check for overlap, if requested. 检查rule是否重叠 */
if
(
fm
->
flags
&
OFPUTIL_FF_CHECK_OVERLAP
)
{
//不关心
}
/* 每个table包的rule是有限。取决于变量table->max_flows,默认UINT_MAX(即最大)
* 如果现有rule数量,已经达到最大,则需要删除一些rule,以保证能够把新rule
* 插入到table中。
*/
error
=
evict_rules_from_table
(
ofproto
,
table
,
1
)
;
if
(
error
)
{
cls_rule_destroy
(
&
cr
)
;
return
error
;
}
/* Allocate new rule.
* ofproto_class 指向 ofproto_dpif_class。最终调用的函数是rule_alloc
*/
rule
=
ofproto
->
ofproto_class
->
rule_alloc
(
)
;
if
(
!
rule
)
{
cls_rule_destroy
(
&
cr
)
;
VLOG_WARN_RL
(
&
rl
,
"%s: failed to allocate a rule."
,
ofproto
->
name
)
;
return
ENOMEM
;
}
/* Initialize base state. 初始化一些基本数据*/
*
CONST_CAST
(
struct
ofproto
*
*
,
&
rule
->
ofproto
)
=
ofproto
;
cls_rule_move
(
CONST_CAST
(
struct
cls_rule
*
,
&
rule
->
cr
)
,
&
cr
)
;
ovs_refcount_init
(
&
rule
->
ref_count
)
;
rule
->
pending
=
NULL
;
rule
->
flow_cookie
=
fm
->
new_cookie
;
rule
->
created
=
rule
->
modified
=
time_msec
(
)
;
ovs_mutex_init
(
&
rule
->
mutex
)
;
ovs_mutex_lock
(
&
rule
->
mutex
)
;
rule
->
idle_timeout
=
fm
->
idle_timeout
;
rule
->
hard_timeout
=
fm
->
hard_timeout
;
ovs_mutex_unlock
(
&
rule
->
mutex
)
;
*
CONST_CAST
(
uint8_t
*
,
&
rule
->
table_id
)
=
table
-
ofproto
->
tables
;
rule
->
flags
=
fm
->
flags
&
OFPUTIL_FF_STATE
;
ovsrcu_set
(
&
rule
->
actions
,
rule_actions_create
(
ofproto
,
fm
->
ofpacts
,
fm
->
ofpacts_len
)
)
;
list_init
(
&
rule
->
meter_list_node
)
;
rule
->
eviction_group
=
NULL
;
list_init
(
&
rule
->
expirable
)
;
rule
->
monitor_flags
=
0
;
rule
->
add_seqno
=
0
;
rule
->
modify_seqno
=
0
;
/* Construct rule, initializing derived state.
* ofproto_class 指向 ofproto_dpif_class。最终调用函数是rule_construct
* 主要初始化rule的成员变量
*/
error
=
ofproto
->
ofproto_class
->
rule_construct
(
rule
)
;
if
(
error
)
{
ofproto_rule_destroy__
(
rule
)
;
return
error
;
}
/* 当上面完成操作后,则将rule插入到table中。*/
do_add_flow
(
ofproto
,
ofconn
,
request
,
fm
->
buffer_id
,
rule
)
;
return
error
;
}
|
通过上面的分析,已经完成对fm进一步抽象,现在我们回到函数handle_flow_mod__,看一下此rule执行操作,即下发到datapath。我们知道用户态vswitchd和内核态datapath是通过netlink进行通信,我们在函数run_rule_executes中,将会看到netlink应用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static
void
run_rule_executes
(
struct
ofproto
*
ofproto
)
OVS_EXCLUDED
(
ofproto_mutex
)
{
struct
rule_execute
*
e
,
*
next
;
struct
list
executes
;
/* 链表迁移 */
guarded_list_pop_all
(
&
ofproto
->
rule_executes
,
&
executes
)
;
/* 遍历链表 */
LIST_FOR_EACH_SAFE
(
e
,
next
,
list_node
,
&
executes
)
{
struct
flow
flow
;
/* 从packet中提取flow */
flow_extract
(
e
->
packet
,
NULL
,
&
flow
)
;
flow
.
in_port
.
ofp_port
=
e
->
in_port
;
ofproto
->
ofproto_class
->
rule_execute
(
e
->
rule
,
&
flow
,
e
->
packet
)
;
rule_execute_destroy
(
e
)
;
}
}
|
通过上面代码可知,最终调用函数是rule_execute,函数调用关系如下:
这些函数就不分析了,主要原因是我对netlink不熟悉,而且我们的目的是,知道是什么样的流程下发到datapath即可。
上面有一个函数我们分析,这里单独分析一下:do_add_flow。这个插入rule的流程比较繁琐,主要原因是规则很多。这里函数里面会调用到函数oftable_insert_rule,
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
|
static
void
oftable_insert_rule
(
struct
rule
*
rule
)
OVS_REQUIRES
(
ofproto_mutex
)
{
struct
ofproto
*
ofproto
=
rule
->
ofproto
;
struct
oftable
*
table
=
&
ofproto
->
tables
[
rule
->
table_id
]
;
const
struct
rule_actions
*
actions
;
bool
may_expire
;
ovs_mutex_lock
(
&
rule
->
mutex
)
;
may_expire
=
rule
->
hard_timeout
||
rule
->
idle_timeout
;
ovs_mutex_unlock
(
&
rule
->
mutex
)
;
/*
* 插入规则1:如果存在定时器,则将rule插入到定时器链表中
*/
if
(
may_expire
)
{
list_insert
(
&
ofproto
->
expirable
,
&
rule
->
expirable
)
;
}
/*
* 插入规则2:根据cookie值,插入hmap中。
*/
cookies_insert
(
ofproto
,
rule
)
;
/*
* 插入规则3 :根据meter id值,插入链表中。
*/
actions
=
rule_get_actions
(
rule
)
;
if
(
actions
->
provider_meter_id
!=
UINT32_MAX
)
{
uint32_t
meter_id
=
ofpacts_get_meter
(
actions
->
ofpacts
,
actions
->
ofpacts_len
)
;
struct
meter
*
meter
=
ofproto
->
meters
[
meter_id
]
;
list_insert
(
&
meter
->
rules
,
&
rule
->
meter_list_node
)
;
}
/*
* 插入规则4:插入分类器中的subtable。
* 这个函数是重点内容,因此在修改的时候也会处理subtable。
*/
fat_rwlock_wrlock
(
&
table
->
cls
.
rwlock
)
;
classifier_insert
(
&
table
->
cls
,
CONST_CAST
(
struct
cls_rule
*
,
&
rule
->
cr
)
)
;
fat_rwlock_unlock
(
&
table
->
cls
.
rwlock
)
;
/*
* 插入规则5:插入到eviction_group中。
*/
eviction_group_add_rule
(
rule
)
;
}
|
其实对于上面这多插入规则,只能从代码中理解表面意思,至于为什么这样做,还不是很理解。从文章篇幅可知,flow_mod这个消息是最复杂的消息,这里写的只是个人观点,有些地方可能理解不到位或者描述不到位,希望读者能够指点出来,以便进行修改。今天突然下载了github中最新代码,发现很多函数都没有了,所以这里指出,文章分析的函数是以ovs-2.3.2版本为准。终于结束,写这篇博客断断续续用了一周的时间,希望能够帮助大家理解。
作者简介:
徐小冰:毕业于河北大学,主要从事嵌入式软件开发,虚拟化,SDN。目前基于ODL和Open vSwitch进行二次开发,希望与广大网友一起探讨学习。作者系OpenDaylihgt群(194240432)资深活跃用户,@IT难人。
- 声明:本文明来自 SDNLAB