我们继续衔接上一篇。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难人。