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