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难人。