#include "smap.h"
#include "sset.h"
#include "timeval.h"
+#include "tun-metadata.h"
#include "unaligned.h"
#include "unixctl.h"
#include "openvswitch/vlog.h"
static void oftable_set_name(struct oftable *, const char *name);
-static enum ofperr evict_rules_from_table(struct oftable *,
- unsigned int extra_space)
+static enum ofperr evict_rules_from_table(struct oftable *)
OVS_REQUIRES(ofproto_mutex);
static void oftable_disable_eviction(struct oftable *);
static void oftable_enable_eviction(struct oftable *,
const struct mf_subfield *fields,
size_t n_fields);
-static void oftable_remove_rule(struct rule *rule) OVS_REQUIRES(ofproto_mutex);
-
/* A set of rules within a single OpenFlow table (oftable) that have the same
* values for the oftable's eviction_fields. A rule to be evicted, when one is
* needed, is taken from the eviction group that contains the greatest number
static void rule_criteria_init(struct rule_criteria *, uint8_t table_id,
const struct match *match, int priority,
+ cls_version_t version,
ovs_be64 cookie, ovs_be64 cookie_mask,
ofp_port_t out_port, uint32_t out_group);
static void rule_criteria_require_rw(struct rule_criteria *,
};
/* rule. */
-static void ofproto_rule_send_removed(struct rule *, uint8_t reason);
+static void ofproto_rule_send_removed(struct rule *)
+ OVS_EXCLUDED(ofproto_mutex);
static bool rule_is_readonly(const struct rule *);
static void ofproto_rule_insert__(struct ofproto *, struct rule *)
OVS_REQUIRES(ofproto_mutex);
};
/* OpenFlow. */
-static enum ofperr modify_flow_check__(struct ofproto *,
+static enum ofperr replace_rule_create(struct ofproto *,
struct ofputil_flow_mod *,
- const struct rule *)
+ struct cls_rule *cr, uint8_t table_id,
+ struct rule *old_rule,
+ struct rule **new_rule)
+ OVS_REQUIRES(ofproto_mutex);
+
+static void replace_rule_start(struct ofproto *,
+ struct rule *old_rule,
+ struct rule *new_rule,
+ struct cls_conjunction *, size_t n_conjs)
+ OVS_REQUIRES(ofproto_mutex);
+
+static void replace_rule_revert(struct ofproto *, struct rule *old_rule,
+ struct rule *new_rule)
OVS_REQUIRES(ofproto_mutex);
-static void modify_flow__(struct ofproto *, struct ofputil_flow_mod *,
- const struct flow_mod_requester *, struct rule *,
- struct ovs_list *dead_cookies)
+
+static void replace_rule_finish(struct ofproto *, struct ofputil_flow_mod *,
+ const struct flow_mod_requester *,
+ struct rule *old_rule, struct rule *new_rule,
+ struct ovs_list *dead_cookies)
OVS_REQUIRES(ofproto_mutex);
-static void delete_flows__(const struct rule_collection *,
+static void delete_flows__(struct rule_collection *,
enum ofp_flow_removed_reason,
const struct flow_mod_requester *)
OVS_REQUIRES(ofproto_mutex);
OVS_EXCLUDED(ofproto->groups_rwlock);
static enum ofperr add_group(struct ofproto *, struct ofputil_group_mod *);
static void handle_openflow(struct ofconn *, const struct ofpbuf *);
-static enum ofperr do_bundle_flow_mod_begin(struct ofproto *,
+static enum ofperr do_bundle_flow_mod_start(struct ofproto *,
struct ofputil_flow_mod *,
struct ofp_bundle_entry *)
OVS_REQUIRES(ofproto_mutex);
return class ? class->enumerate_names(type, names) : EAFNOSUPPORT;
}
+static void
+ofproto_bump_tables_version(struct ofproto *ofproto)
+{
+ ++ofproto->tables_version;
+ ofproto->ofproto_class->set_tables_version(ofproto,
+ ofproto->tables_version);
+}
+
int
ofproto_create(const char *datapath_name, const char *datapath_type,
struct ofproto **ofprotop)
ofproto->eviction_group_timer = LLONG_MIN;
ofproto->tables = NULL;
ofproto->n_tables = 0;
+ ofproto->tables_version = CLS_MIN_VERSION;
hindex_init(&ofproto->cookies);
hmap_init(&ofproto->learned_cookies);
list_init(&ofproto->expirable);
ofproto->ogf.max_groups[i] = OFPG_MAX;
ofproto->ogf.ofpacts[i] = (UINT64_C(1) << N_OFPACTS) - 1;
}
+ tun_metadata_init();
error = ofproto->ofproto_class->construct(ofproto);
if (error) {
ofproto->meters = xzalloc((ofproto->meter_features.max_meters + 1)
* sizeof(struct meter *));
+ /* Set the initial tables version. */
+ ofproto_bump_tables_version(ofproto);
+
*ofprotop = ofproto;
return 0;
}
}
ovs_mutex_lock(&ofproto_mutex);
- evict_rules_from_table(table, 0);
+ evict_rules_from_table(table);
ovs_mutex_unlock(&ofproto_mutex);
}
\f
* switch is being deleted and any OpenFlow channels have been or soon will
* be killed. */
ovs_mutex_lock(&ofproto_mutex);
- oftable_remove_rule(rule);
- ofproto->ofproto_class->rule_delete(rule);
+
+ if (!rule->removed) {
+ /* Make sure there is no postponed removal of the rule. */
+ ovs_assert(cls_rule_visible_in_version(&rule->cr, CLS_MAX_VERSION));
+
+ if (!classifier_remove(&rule->ofproto->tables[rule->table_id].cls,
+ &rule->cr)) {
+ OVS_NOT_REACHED();
+ }
+ ofproto_rule_remove__(rule->ofproto, rule);
+ ofproto->ofproto_class->rule_delete(rule);
+ ofproto_rule_unref(rule);
+ }
ovs_mutex_unlock(&ofproto_mutex);
}
rule_collection_add(&rules, rule);
}
delete_flows__(&rules, OFPRR_DELETE, NULL);
- rule_collection_destroy(&rules);
}
/* XXX: Concurrent handler threads may insert new learned flows based on
* learn actions of the now deleted flows right after we release
ofproto->ofproto_class->dealloc(ofproto);
}
+/* Destroying rules is doubly deferred, must have 'ofproto' around for them.
+ * - 1st we defer the removal of the rules from the classifier
+ * - 2nd we defer the actual destruction of the rules. */
+static void
+ofproto_destroy_defer__(struct ofproto *ofproto)
+ OVS_EXCLUDED(ofproto_mutex)
+{
+ ovsrcu_postpone(ofproto_destroy__, ofproto);
+}
+
void
ofproto_destroy(struct ofproto *p)
OVS_EXCLUDED(ofproto_mutex)
connmgr_destroy(p->connmgr);
/* Destroying rules is deferred, must have 'ofproto' around for them. */
- ovsrcu_postpone(ofproto_destroy__, p);
+ ovsrcu_postpone(ofproto_destroy_defer__, p);
}
/* Destroys the datapath with the respective 'name' and 'type'. With the Linux
continue;
}
- if (classifier_count(&table->cls) > 100000) {
+ if (table->n_flows > 100000) {
static struct vlog_rate_limit count_rl =
VLOG_RATE_LIMIT_INIT(1, 1);
VLOG_WARN_RL(&count_rl, "Table %"PRIuSIZE" has an excessive"
- " number of rules: %d", i,
- classifier_count(&table->cls));
+ " number of rules: %d", i, table->n_flows);
}
ovs_mutex_lock(&ofproto_mutex);
n_rules = 0;
OFPROTO_FOR_EACH_TABLE (table, ofproto) {
- n_rules += classifier_count(&table->cls);
+ n_rules += table->n_flows;
}
simap_increase(usage, "rules", n_rules);
/* First do a cheap check whether the rule we're looking for already exists
* with the actions that we want. If it does, then we're done. */
rule = rule_from_cls_rule(classifier_find_match_exactly(
- &ofproto->tables[0].cls, match, priority));
+ &ofproto->tables[0].cls, match, priority,
+ CLS_MAX_VERSION));
if (rule) {
const struct rule_actions *actions = rule_get_actions(rule);
must_add = !ofpacts_equal(actions->ofpacts, actions->ofpacts_len,
struct rule *rule;
bool done = false;
- rule = rule_from_cls_rule(classifier_find_match_exactly(&table->cls,
- &fm->match,
- fm->priority));
+ rule = rule_from_cls_rule(classifier_find_match_exactly(
+ &table->cls, &fm->match, fm->priority,
+ CLS_MAX_VERSION));
if (rule) {
/* Reading many of the rule fields and writing on 'modified'
* requires the rule->mutex. Also, rule->actions may change
/* First do a cheap check whether the rule we're looking for has already
* been deleted. If so, then we're done. */
- rule = rule_from_cls_rule(classifier_find_match_exactly(cls, target,
- priority));
+ rule = rule_from_cls_rule(classifier_find_match_exactly(
+ cls, target, priority, CLS_MAX_VERSION));
if (!rule) {
return;
}
rule->ofproto->ofproto_class->rule_dealloc(rule);
}
-/* Create a new rule based on attributes in 'fm', match in 'cr', and
- * 'table_id'. Note that the rule is NOT inserted into a any data structures
- * yet. Takes ownership of 'cr'. */
-static enum ofperr
-ofproto_rule_create(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
- struct cls_rule *cr, uint8_t table_id,
- struct rule **rulep)
- OVS_REQUIRES(ofproto_mutex)
-{
- struct rule *rule;
- enum ofperr error;
-
- /* Allocate new rule. */
- 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 OFPERR_OFPFMFC_UNKNOWN;
- }
-
- /* 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->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;
- rule->importance = fm->importance;
- ovs_mutex_unlock(&rule->mutex);
-
- *CONST_CAST(uint8_t *, &rule->table_id) = table_id;
- rule->flags = fm->flags & OFPUTIL_FF_STATE;
- ovsrcu_set_hidden(&rule->actions,
- rule_actions_create(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. */
- error = ofproto->ofproto_class->rule_construct(rule);
- if (error) {
- ofproto_rule_destroy__(rule);
- return error;
- }
-
- *rulep = rule;
- return 0;
-}
-
static void
rule_destroy_cb(struct rule *rule)
+ OVS_NO_THREAD_SAFETY_ANALYSIS
{
+ /* Send rule removed if needed. */
+ if (rule->flags & OFPUTIL_FF_SEND_FLOW_REM
+ && rule->removed_reason != OVS_OFPRR_NONE
+ && !rule_is_hidden(rule)) {
+ ofproto_rule_send_removed(rule);
+ }
rule->ofproto->ofproto_class->rule_destruct(rule);
ofproto_rule_destroy__(rule);
}
}
}
+static void
+remove_rule_rcu__(struct rule *rule)
+ OVS_REQUIRES(ofproto_mutex)
+{
+ struct ofproto *ofproto = rule->ofproto;
+ struct oftable *table = &ofproto->tables[rule->table_id];
+
+ ovs_assert(!cls_rule_visible_in_version(&rule->cr, CLS_MAX_VERSION));
+ if (!classifier_remove(&table->cls, &rule->cr)) {
+ OVS_NOT_REACHED();
+ }
+ ofproto->ofproto_class->rule_delete(rule);
+ ofproto_rule_unref(rule);
+}
+
+static void
+remove_rule_rcu(struct rule *rule)
+ OVS_EXCLUDED(ofproto_mutex)
+{
+ ovs_mutex_lock(&ofproto_mutex);
+ remove_rule_rcu__(rule);
+ ovs_mutex_unlock(&ofproto_mutex);
+}
+
+/* Removes and deletes rules from a NULL-terminated array of rule pointers. */
+static void
+remove_rules_rcu(struct rule **rules)
+ OVS_EXCLUDED(ofproto_mutex)
+{
+ struct rule **orig_rules = rules;
+
+ if (*rules) {
+ struct ofproto *ofproto = rules[0]->ofproto;
+ unsigned long tables[BITMAP_N_LONGS(256)];
+ struct rule *rule;
+ size_t table_id;
+
+ memset(tables, 0, sizeof tables);
+
+ ovs_mutex_lock(&ofproto_mutex);
+ while ((rule = *rules++)) {
+ /* Defer once for each new table. This defers the subtable cleanup
+ * until later, so that when removing large number of flows the
+ * operation is faster. */
+ if (!bitmap_is_set(tables, rule->table_id)) {
+ struct classifier *cls = &ofproto->tables[rule->table_id].cls;
+
+ bitmap_set1(tables, rule->table_id);
+ classifier_defer(cls);
+ }
+ remove_rule_rcu__(rule);
+ }
+
+ BITMAP_FOR_EACH_1(table_id, 256, tables) {
+ struct classifier *cls = &ofproto->tables[table_id].cls;
+
+ classifier_publish(cls);
+ }
+ ovs_mutex_unlock(&ofproto_mutex);
+ }
+
+ free(orig_rules);
+}
+
void
ofproto_group_ref(struct ofgroup *group)
{
struct match match;
match_init_catchall(&match);
- rule_criteria_init(&criteria, c->table_id, &match, 0,
+ rule_criteria_init(&criteria, c->table_id, &match, 0, CLS_MAX_VERSION,
c->cookie, OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
rule_criteria_require_rw(&criteria, false);
collect_rules_loose(ofproto, &criteria, &rules);
- delete_flows__(&rules, OFPRR_DELETE, NULL);
rule_criteria_destroy(&criteria);
- rule_collection_destroy(&rules);
+ delete_flows__(&rules, OFPRR_DELETE, NULL);
free(c);
}
stats = *statsp = xcalloc(ofproto->n_tables, sizeof *stats);
for (i = 0; i < ofproto->n_tables; i++) {
struct ofputil_table_stats *s = &stats[i];
- struct classifier *cls = &ofproto->tables[i].cls;
s->table_id = i;
- s->active_count = classifier_count(cls);
+ s->active_count = ofproto->tables[i].n_flows;
if (i == 0) {
s->active_count -= connmgr_count_hidden_rules(
ofproto->connmgr);
}
static enum ofperr
-handle_port_mod(struct ofconn *ofconn, const struct ofp_header *oh)
+port_mod_start(struct ofconn *ofconn, struct ofputil_port_mod *pm,
+ struct ofport **port)
{
struct ofproto *p = ofconn_get_ofproto(ofconn);
+
+ *port = ofproto_get_port(p, pm->port_no);
+ if (!*port) {
+ return OFPERR_OFPPMFC_BAD_PORT;
+ }
+ if (!eth_addr_equals((*port)->pp.hw_addr, pm->hw_addr)) {
+ return OFPERR_OFPPMFC_BAD_HW_ADDR;
+ }
+ return 0;
+}
+
+static void
+port_mod_finish(struct ofconn *ofconn, struct ofputil_port_mod *pm,
+ struct ofport *port)
+{
+ update_port_config(ofconn, port, pm->config, pm->mask);
+ if (pm->advertise) {
+ netdev_set_advertisements(port->netdev, pm->advertise);
+ }
+}
+
+static enum ofperr
+handle_port_mod(struct ofconn *ofconn, const struct ofp_header *oh)
+{
struct ofputil_port_mod pm;
struct ofport *port;
enum ofperr error;
return error;
}
- port = ofproto_get_port(p, pm.port_no);
- if (!port) {
- return OFPERR_OFPPMFC_BAD_PORT;
- } else if (!eth_addr_equals(port->pp.hw_addr, pm.hw_addr)) {
- return OFPERR_OFPPMFC_BAD_HW_ADDR;
- } else {
- update_port_config(ofconn, port, pm.config, pm.mask);
- if (pm.advertise) {
- netdev_set_advertisements(port->netdev, pm.advertise);
- }
+ error = port_mod_start(ofconn, &pm, &port);
+ if (!error) {
+ port_mod_finish(ofconn, &pm, port);
}
- return 0;
+ return error;
}
static enum ofperr
static void
rule_criteria_init(struct rule_criteria *criteria, uint8_t table_id,
const struct match *match, int priority,
- ovs_be64 cookie, ovs_be64 cookie_mask,
- ofp_port_t out_port, uint32_t out_group)
+ cls_version_t version, ovs_be64 cookie,
+ ovs_be64 cookie_mask, ofp_port_t out_port,
+ uint32_t out_group)
{
criteria->table_id = table_id;
- cls_rule_init(&criteria->cr, match, priority);
+ cls_rule_init(&criteria->cr, match, priority, version);
criteria->cookie = cookie;
criteria->cookie_mask = cookie_mask;
criteria->out_port = out_port;
}
}
+/* Returns a NULL-terminated array of rule pointers,
+ * destroys 'rules'. */
+static struct rule **
+rule_collection_detach(struct rule_collection *rules)
+{
+ struct rule **rule_array;
+
+ rule_collection_add(rules, NULL);
+
+ if (rules->rules == rules->stub) {
+ rules->rules = xmemdup(rules->rules, rules->n * sizeof *rules->rules);
+ }
+
+ rule_array = rules->rules;
+ rule_collection_init(rules);
+
+ return rule_array;
+}
+
void
rule_collection_destroy(struct rule_collection *rules)
{
rule_collection_init(rules);
}
+/* Schedules postponed removal of rules, destroys 'rules'. */
+static void
+rule_collection_remove_postponed(struct rule_collection *rules)
+ OVS_REQUIRES(ofproto_mutex)
+{
+ if (rules->n > 0) {
+ if (rules->n == 1) {
+ ovsrcu_postpone(remove_rule_rcu, rules->rules[0]);
+ } else {
+ ovsrcu_postpone(remove_rules_rcu, rule_collection_detach(rules));
+ }
+ }
+}
+
/* Checks whether 'rule' matches 'c' and, if so, adds it to 'rules'. This
* function verifies most of the criteria in 'c' itself, but the caller must
* check 'c->cr' itself.
*
- * Rules that have already been marked as 'to_be_removed' are not collected.
+ * Rules that have already been marked for removal are not collected.
*
* Increments '*n_readonly' if 'rule' wasn't added because it's read-only (and
* 'c' only includes modifiable rules). */
&& ofproto_rule_has_out_group(rule, c->out_group)
&& !((rule->flow_cookie ^ c->cookie) & c->cookie_mask)
&& (!rule_is_hidden(rule) || c->include_hidden)
- && !rule->cr.to_be_removed) {
+ && cls_rule_visible_in_version(&rule->cr, c->cr.version)) {
/* Rule matches all the criteria... */
if (!rule_is_readonly(rule) || c->include_readonly) {
/* ...add it. */
return error;
}
- rule_criteria_init(&criteria, fsr.table_id, &fsr.match, 0, fsr.cookie,
- fsr.cookie_mask, fsr.out_port, fsr.out_group);
+ rule_criteria_init(&criteria, fsr.table_id, &fsr.match, 0, CLS_MAX_VERSION,
+ fsr.cookie, fsr.cookie_mask, fsr.out_port,
+ fsr.out_group);
ovs_mutex_lock(&ofproto_mutex);
error = collect_rules_loose(ofproto, &criteria, &rules);
}
rule_criteria_init(&criteria, request.table_id, &request.match, 0,
- request.cookie, request.cookie_mask,
+ CLS_MAX_VERSION, request.cookie, request.cookie_mask,
request.out_port, request.out_group);
ovs_mutex_lock(&ofproto_mutex);
}
static enum ofperr
-evict_rules_from_table(struct oftable *table, unsigned int extra_space)
+evict_rules_from_table(struct oftable *table)
OVS_REQUIRES(ofproto_mutex)
{
enum ofperr error = 0;
struct rule_collection rules;
- unsigned int count = classifier_count(&table->cls) + extra_space;
+ unsigned int count = table->n_flows;
unsigned int max_flows = table->max_flows;
rule_collection_init(&rules);
}
}
delete_flows__(&rules, OFPRR_EVICTION, NULL);
- rule_collection_destroy(&rules);
return error;
}
-static bool
-is_conjunction(const struct ofpact *ofpacts, size_t ofpacts_len)
-{
- return ofpacts_len > 0 && ofpacts->type == OFPACT_CONJUNCTION;
-}
-
static void
get_conjunctions(const struct ofputil_flow_mod *fm,
struct cls_conjunction **conjsp, size_t *n_conjsp)
struct cls_conjunction *conjs = NULL;
int n_conjs = 0;
- if (is_conjunction(fm->ofpacts, fm->ofpacts_len)) {
- const struct ofpact *ofpact;
- int i;
-
- n_conjs = 0;
- OFPACT_FOR_EACH (ofpact, fm->ofpacts, fm->ofpacts_len) {
+ const struct ofpact *ofpact;
+ OFPACT_FOR_EACH (ofpact, fm->ofpacts, fm->ofpacts_len) {
+ if (ofpact->type == OFPACT_CONJUNCTION) {
n_conjs++;
+ } else if (ofpact->type != OFPACT_NOTE) {
+ /* "conjunction" may appear with "note" actions but not with any
+ * other type of actions. */
+ ovs_assert(!n_conjs);
+ break;
}
+ }
+ if (n_conjs) {
+ int i = 0;
conjs = xzalloc(n_conjs * sizeof *conjs);
- i = 0;
OFPACT_FOR_EACH (ofpact, fm->ofpacts, fm->ofpacts_len) {
- struct ofpact_conjunction *oc = ofpact_get_CONJUNCTION(ofpact);
- conjs[i].clause = oc->clause;
- conjs[i].n_clauses = oc->n_clauses;
- conjs[i].id = oc->id;
- i++;
+ if (ofpact->type == OFPACT_CONJUNCTION) {
+ struct ofpact_conjunction *oc = ofpact_get_CONJUNCTION(ofpact);
+ conjs[i].clause = oc->clause;
+ conjs[i].n_clauses = oc->n_clauses;
+ conjs[i].id = oc->id;
+ i++;
+ }
}
}
*n_conjsp = n_conjs;
}
-static void
-set_conjunctions(struct rule *rule, const struct cls_conjunction *conjs,
- size_t n_conjs)
- OVS_REQUIRES(ofproto_mutex)
-{
- struct cls_rule *cr = CONST_CAST(struct cls_rule *, &rule->cr);
-
- cls_rule_set_conjunctions(cr, conjs, n_conjs);
-}
-
/* Implements OFPFC_ADD and the cases for OFPFC_MODIFY and OFPFC_MODIFY_STRICT
* in which no matching flow already exists in the flow table.
*
*
* The caller retains ownership of 'fm->ofpacts'. */
static enum ofperr
-add_flow_begin(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
- struct rule **rulep, bool *modify)
+add_flow_start(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
+ struct rule **old_rule, struct rule **new_rule)
OVS_REQUIRES(ofproto_mutex)
{
struct oftable *table;
struct cls_rule cr;
struct rule *rule;
uint8_t table_id;
- enum ofperr error = 0;
+ struct cls_conjunction *conjs;
+ size_t n_conjs;
+ enum ofperr error;
if (!check_table_id(ofproto, fm->table_id)) {
error = OFPERR_OFPBRC_BAD_TABLE_ID;
return OFPERR_OFPBRC_EPERM;
}
- cls_rule_init(&cr, &fm->match, fm->priority);
+ cls_rule_init(&cr, &fm->match, fm->priority, ofproto->tables_version + 1);
/* Check for the existence of an identical rule.
- * This will not return rules earlier marked as 'to_be_removed'. */
+ * This will not return rules earlier marked for removal. */
rule = rule_from_cls_rule(classifier_find_rule_exactly(&table->cls, &cr));
- if (rule) {
- /* Transform "add" into "modify" of an existing identical flow. */
- cls_rule_destroy(&cr);
-
- fm->modify_cookie = true;
- error = modify_flow_check__(ofproto, fm, rule);
- if (error) {
- return error;
- }
-
- *modify = true;
- } else { /* New rule. */
- struct cls_conjunction *conjs;
- size_t n_conjs;
-
+ *old_rule = rule;
+ if (!rule) {
/* Check for overlap, if requested. */
if (fm->flags & OFPUTIL_FF_CHECK_OVERLAP
&& classifier_rule_overlaps(&table->cls, &cr)) {
}
/* If necessary, evict an existing rule to clear out space. */
- error = evict_rules_from_table(table, 1);
- if (error) {
- cls_rule_destroy(&cr);
- return error;
- }
-
- /* Allocate new rule. */
- error = ofproto_rule_create(ofproto, fm, &cr, table - ofproto->tables,
- &rule);
- if (error) {
- return error;
- }
-
- /* Insert flow to the classifier, so that later flow_mods may relate
- * to it. This is reversible, in case later errors require this to
- * be reverted. */
- ofproto_rule_insert__(ofproto, rule);
- /* Make the new rule invisible for classifier lookups. */
- classifier_defer(&table->cls);
- get_conjunctions(fm, &conjs, &n_conjs);
- classifier_insert(&table->cls, &rule->cr, conjs, n_conjs);
- free(conjs);
-
- error = ofproto->ofproto_class->rule_insert(rule);
- if (error) {
- oftable_remove_rule(rule);
- ofproto_rule_unref(rule);
- return error;
+ if (table->n_flows >= table->max_flows) {
+ if (!choose_rule_to_evict(table, &rule)) {
+ error = OFPERR_OFPFMFC_TABLE_FULL;
+ cls_rule_destroy(&cr);
+ return error;
+ }
+ eviction_group_remove_rule(rule);
+ /* Marks '*old_rule' as an evicted rule rather than replaced rule.
+ */
+ fm->delete_reason = OFPRR_EVICTION;
+ *old_rule = rule;
}
+ } else {
+ fm->modify_cookie = true;
+ }
- *modify = false;
+ /* Allocate new rule. */
+ error = replace_rule_create(ofproto, fm, &cr, table - ofproto->tables,
+ rule, new_rule);
+ if (error) {
+ return error;
}
- *rulep = rule;
+ get_conjunctions(fm, &conjs, &n_conjs);
+ replace_rule_start(ofproto, rule, *new_rule, conjs, n_conjs);
+ free(conjs);
+
return 0;
}
-/* Revert the effects of add_flow_begin().
- * 'new_rule' must be passed in as NULL, if no new rule was allocated and
- * inserted to the classifier.
- * Note: evictions cannot be reverted. */
+/* Revert the effects of add_flow_start(). */
static void
-add_flow_revert(struct ofproto *ofproto, struct rule *new_rule)
+add_flow_revert(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
+ struct rule *old_rule, struct rule *new_rule)
OVS_REQUIRES(ofproto_mutex)
{
- /* Old rule was not changed yet, only need to revert a new rule. */
- if (new_rule) {
- struct oftable *table = &ofproto->tables[new_rule->table_id];
-
- if (!classifier_remove(&table->cls, &new_rule->cr)) {
- OVS_NOT_REACHED();
- }
- classifier_publish(&table->cls);
-
- ofproto_rule_remove__(ofproto, new_rule);
- ofproto->ofproto_class->rule_delete(new_rule);
- ofproto_rule_unref(new_rule);
+ if (old_rule && fm->delete_reason == OFPRR_EVICTION) {
+ /* Revert the eviction. */
+ eviction_group_add_rule(old_rule);
}
+
+ replace_rule_revert(ofproto, old_rule, new_rule);
}
+/* To be called after version bump. */
static void
add_flow_finish(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
const struct flow_mod_requester *req,
- struct rule *rule, bool modify)
+ struct rule *old_rule, struct rule *new_rule)
OVS_REQUIRES(ofproto_mutex)
{
- if (modify) {
- struct ovs_list dead_cookies = OVS_LIST_INITIALIZER(&dead_cookies);
-
- modify_flow__(ofproto, fm, req, rule, &dead_cookies);
- learned_cookies_flush(ofproto, &dead_cookies);
- } else {
- struct oftable *table = &ofproto->tables[rule->table_id];
-
- cls_rule_make_visible(&rule->cr);
- classifier_publish(&table->cls);
+ struct ovs_list dead_cookies = OVS_LIST_INITIALIZER(&dead_cookies);
- learned_cookies_inc(ofproto, rule_get_actions(rule));
+ replace_rule_finish(ofproto, fm, req, old_rule, new_rule, &dead_cookies);
+ learned_cookies_flush(ofproto, &dead_cookies);
- if (minimask_get_vid_mask(&rule->cr.match.mask) == VLAN_VID_MASK) {
+ if (old_rule) {
+ ovsrcu_postpone(remove_rule_rcu, old_rule);
+ } else {
+ if (minimask_get_vid_mask(&new_rule->cr.match.mask) == VLAN_VID_MASK) {
if (ofproto->vlan_bitmap) {
- uint16_t vid = miniflow_get_vid(&rule->cr.match.flow);
+ uint16_t vid = miniflow_get_vid(&new_rule->cr.match.flow);
if (!bitmap_is_set(ofproto->vlan_bitmap, vid)) {
bitmap_set1(ofproto->vlan_bitmap, vid);
}
}
- ofmonitor_report(ofproto->connmgr, rule, NXFME_ADDED, 0,
+ ofmonitor_report(ofproto->connmgr, new_rule, NXFME_ADDED, 0,
req ? req->ofconn : NULL,
req ? req->request->xid : 0, NULL);
}
- send_buffered_packet(req, fm->buffer_id, rule);
+ send_buffered_packet(req, fm->buffer_id, new_rule);
}
\f
/* OFPFC_MODIFY and OFPFC_MODIFY_STRICT. */
-/* Checks if the 'rule' can be modified to match 'fm'.
- *
- * Returns 0 on success, otherwise an OpenFlow error code. */
+/* Create a new rule based on attributes in 'fm', match in 'cr', 'table_id',
+ * and 'old_rule'. Note that the rule is NOT inserted into a any data
+ * structures yet. Takes ownership of 'cr'. */
static enum ofperr
-modify_flow_check__(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
- const struct rule *rule)
- OVS_REQUIRES(ofproto_mutex)
-{
- if (ofproto->ofproto_class->rule_premodify_actions) {
- return ofproto->ofproto_class->rule_premodify_actions(
- rule, fm->ofpacts, fm->ofpacts_len);
- }
- return 0;
-}
-
-/* Checks if the rules listed in 'rules' can have their actions changed to
- * match those in 'fm'.
- *
- * Returns 0 on success, otherwise an OpenFlow error code. */
-static enum ofperr
-modify_flows_check__(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
- const struct rule_collection *rules)
- OVS_REQUIRES(ofproto_mutex)
+replace_rule_create(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
+ struct cls_rule *cr, uint8_t table_id,
+ struct rule *old_rule, struct rule **new_rule)
{
+ struct rule *rule;
enum ofperr error;
- size_t i;
- if (ofproto->ofproto_class->rule_premodify_actions) {
- for (i = 0; i < rules->n; i++) {
- error = modify_flow_check__(ofproto, fm, rules->rules[i]);
- if (error) {
- return error;
- }
- }
+ /* Allocate new rule. */
+ 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 OFPERR_OFPFMFC_UNKNOWN;
}
- return 0;
-}
-
-/* Modifies the 'rule', changing them to match 'fm'. */
-static void
-modify_flow__(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
- const struct flow_mod_requester *req, struct rule *rule,
- struct ovs_list *dead_cookies)
- OVS_REQUIRES(ofproto_mutex)
-{
- enum nx_flow_update_event event = fm->command == OFPFC_ADD
- ? NXFME_ADDED : NXFME_MODIFIED;
-
- /* 'fm' says that */
- bool change_cookie = (fm->modify_cookie
- && fm->new_cookie != OVS_BE64_MAX
- && fm->new_cookie != rule->flow_cookie);
-
- const struct rule_actions *actions = rule_get_actions(rule);
- bool change_actions = !ofpacts_equal(fm->ofpacts, fm->ofpacts_len,
- actions->ofpacts,
- actions->ofpacts_len);
-
- bool reset_counters = (fm->flags & OFPUTIL_FF_RESET_COUNTS) != 0;
+ /* 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->flow_cookie = fm->new_cookie;
+ rule->created = rule->modified = time_msec();
- long long int now = time_msec();
+ ovs_mutex_init(&rule->mutex);
+ ovs_mutex_lock(&rule->mutex);
+ rule->idle_timeout = fm->idle_timeout;
+ rule->hard_timeout = fm->hard_timeout;
+ rule->importance = fm->importance;
+ rule->removed_reason = OVS_OFPRR_NONE;
- if (change_cookie) {
- cookies_remove(ofproto, rule);
- }
+ *CONST_CAST(uint8_t *, &rule->table_id) = table_id;
+ rule->flags = fm->flags & OFPUTIL_FF_STATE;
+ *CONST_CAST(const struct rule_actions **, &rule->actions)
+ = rule_actions_create(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;
- ovs_mutex_lock(&rule->mutex);
- if (fm->command == OFPFC_ADD) {
- rule->idle_timeout = fm->idle_timeout;
- rule->hard_timeout = fm->hard_timeout;
- rule->importance = fm->importance;
- rule->flags = fm->flags & OFPUTIL_FF_STATE;
- rule->created = now;
- }
- if (change_cookie) {
- rule->flow_cookie = fm->new_cookie;
+ /* Copy values from old rule for modify semantics. */
+ if (old_rule && fm->delete_reason != OFPRR_EVICTION) {
+ /* 'fm' says that */
+ bool change_cookie = (fm->modify_cookie
+ && fm->new_cookie != OVS_BE64_MAX
+ && fm->new_cookie != old_rule->flow_cookie);
+
+ ovs_mutex_lock(&old_rule->mutex);
+ if (fm->command != OFPFC_ADD) {
+ rule->idle_timeout = old_rule->idle_timeout;
+ rule->hard_timeout = old_rule->hard_timeout;
+ rule->importance = old_rule->importance;
+ rule->flags = old_rule->flags;
+ rule->created = old_rule->created;
+ }
+ if (!change_cookie) {
+ rule->flow_cookie = old_rule->flow_cookie;
+ }
+ ovs_mutex_unlock(&old_rule->mutex);
}
- rule->modified = now;
ovs_mutex_unlock(&rule->mutex);
- if (change_cookie) {
- cookies_insert(ofproto, rule);
- }
- if (fm->command == OFPFC_ADD) {
- if (fm->idle_timeout || fm->hard_timeout || fm->importance) {
- if (!rule->eviction_group) {
- eviction_group_add_rule(rule);
- }
- } else {
- eviction_group_remove_rule(rule);
- }
+ /* Construct rule, initializing derived state. */
+ error = ofproto->ofproto_class->rule_construct(rule);
+ if (error) {
+ ofproto_rule_destroy__(rule);
+ return error;
}
- if (change_actions) {
- /* We have to change the actions. The rule's conjunctive match set
- * is a function of its actions, so we need to update that too. The
- * conjunctive match set is used in the lookup process to figure
- * which (if any) collection of conjunctive sets the packet matches
- * with. However, a rule with conjunction actions is never to be
- * returned as a classifier lookup result. To make sure a rule with
- * conjunction actions is not returned as a lookup result, we update
- * them in a carefully chosen order:
- *
- * - If we're adding a conjunctive match set where there wasn't one
- * before, we have to make the conjunctive match set available to
- * lookups before the rule's actions are changed, as otherwise
- * rule with a conjunction action could be returned as a lookup
- * result.
- *
- * - To clear some nonempty conjunctive set, we set the rule's
- * actions first, so that a lookup can't return a rule with
- * conjunction actions.
- *
- * - Otherwise, order doesn't matter for changing one nonempty
- * conjunctive match set to some other nonempty set, since the
- * rule's actions are not seen by the classifier, and hence don't
- * matter either before or after the change. */
- struct cls_conjunction *conjs;
- size_t n_conjs;
- get_conjunctions(fm, &conjs, &n_conjs);
+ rule->removed = true; /* Not yet in ofproto data structures. */
- if (n_conjs) {
- set_conjunctions(rule, conjs, n_conjs);
- }
- ovsrcu_set(&rule->actions, rule_actions_create(fm->ofpacts,
- fm->ofpacts_len));
- if (!conjs) {
- set_conjunctions(rule, conjs, n_conjs);
- }
+ *new_rule = rule;
+ return 0;
+}
- free(conjs);
- }
+static void
+replace_rule_start(struct ofproto *ofproto,
+ struct rule *old_rule, struct rule *new_rule,
+ struct cls_conjunction *conjs, size_t n_conjs)
+{
+ struct oftable *table = &ofproto->tables[new_rule->table_id];
- if (change_actions || reset_counters) {
- ofproto->ofproto_class->rule_modify_actions(rule, reset_counters);
+ /* 'old_rule' may be either an evicted rule or replaced rule. */
+ if (old_rule) {
+ /* Mark the old rule for removal in the next version. */
+ cls_rule_make_invisible_in_version(&old_rule->cr,
+ ofproto->tables_version + 1);
+ } else {
+ table->n_flows++;
}
+ /* Insert flow to the classifier, so that later flow_mods may relate
+ * to it. This is reversible, in case later errors require this to
+ * be reverted. */
+ ofproto_rule_insert__(ofproto, new_rule);
+ /* Make the new rule visible for classifier lookups only from the next
+ * version. */
+ classifier_insert(&table->cls, &new_rule->cr, conjs, n_conjs);
+}
+
+static void replace_rule_revert(struct ofproto *ofproto,
+ struct rule *old_rule, struct rule *new_rule)
+{
+ struct oftable *table = &ofproto->tables[new_rule->table_id];
- if (event != NXFME_MODIFIED || change_actions || change_cookie) {
- ofmonitor_report(ofproto->connmgr, rule, event, 0,
- req ? req->ofconn : NULL, req ? req->request->xid : 0,
- change_actions ? actions : NULL);
+ if (old_rule) {
+ /* Restore the original visibility of the old rule. */
+ cls_rule_restore_visibility(&old_rule->cr);
+ } else {
+ /* Restore table's rule count. */
+ table->n_flows--;
}
- if (change_actions) {
- learned_cookies_inc(ofproto, rule_get_actions(rule));
- learned_cookies_dec(ofproto, actions, dead_cookies);
- rule_actions_destroy(actions);
+ /* Remove the new rule immediately. It was never visible to lookups. */
+ if (!classifier_remove(&table->cls, &new_rule->cr)) {
+ OVS_NOT_REACHED();
}
+ ofproto_rule_remove__(ofproto, new_rule);
+ /* The rule was not inserted to the ofproto provider, so we can
+ * release it without deleting it from the ofproto provider. */
+ ofproto_rule_unref(new_rule);
}
-/* Modifies the rules listed in 'rules', changing their actions to match those
- * in 'fm'.
- *
- * 'req' is used to retrieve the packet buffer specified in fm->buffer_id,
- * if any. */
+/* Adds the 'new_rule', replacing the 'old_rule'. */
static void
-modify_flows__(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
- const struct flow_mod_requester *req,
- const struct rule_collection *rules)
+replace_rule_finish(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
+ const struct flow_mod_requester *req,
+ struct rule *old_rule, struct rule *new_rule,
+ struct ovs_list *dead_cookies)
OVS_REQUIRES(ofproto_mutex)
{
- struct ovs_list dead_cookies = OVS_LIST_INITIALIZER(&dead_cookies);
- size_t i;
+ bool forward_stats = !(fm->flags & OFPUTIL_FF_RESET_COUNTS);
+ struct rule *replaced_rule;
+
+ replaced_rule = fm->delete_reason != OFPRR_EVICTION ? old_rule : NULL;
+
+ /* Insert the new flow to the ofproto provider. A non-NULL 'replaced_rule'
+ * is a duplicate rule the 'new_rule' is replacing. The provider should
+ * link the stats from the old rule to the new one if 'forward_stats' is
+ * 'true'. The 'replaced_rule' will be deleted right after this call. */
+ ofproto->ofproto_class->rule_insert(new_rule, replaced_rule,
+ forward_stats);
+ learned_cookies_inc(ofproto, rule_get_actions(new_rule));
+
+ if (old_rule) {
+ const struct rule_actions *old_actions = rule_get_actions(old_rule);
+
+ /* Remove the old rule from data structures. Removal from the
+ * classifier and the deletion of the rule is RCU postponed by the
+ * caller. */
+ ofproto_rule_remove__(ofproto, old_rule);
+ learned_cookies_dec(ofproto, old_actions, dead_cookies);
+
+ if (replaced_rule) {
+ enum nx_flow_update_event event = fm->command == OFPFC_ADD
+ ? NXFME_ADDED : NXFME_MODIFIED;
+
+ bool change_cookie = (fm->modify_cookie
+ && fm->new_cookie != OVS_BE64_MAX
+ && fm->new_cookie != old_rule->flow_cookie);
+
+ bool change_actions = !ofpacts_equal(fm->ofpacts,
+ fm->ofpacts_len,
+ old_actions->ofpacts,
+ old_actions->ofpacts_len);
+
+ if (event != NXFME_MODIFIED || change_actions || change_cookie) {
+ ofmonitor_report(ofproto->connmgr, new_rule, event, 0,
+ req ? req->ofconn : NULL,
+ req ? req->request->xid : 0,
+ change_actions ? old_actions : NULL);
+ }
+ } else {
+ /* XXX: This is slight duplication with delete_flows_finish__() */
- for (i = 0; i < rules->n; i++) {
- modify_flow__(ofproto, fm, req, rules->rules[i], &dead_cookies);
+ old_rule->removed_reason = OFPRR_EVICTION;
+
+ ofmonitor_report(ofproto->connmgr, old_rule, NXFME_DELETED,
+ OFPRR_EVICTION,
+ req ? req->ofconn : NULL,
+ req ? req->request->xid : 0, NULL);
+ }
}
- learned_cookies_flush(ofproto, &dead_cookies);
}
static enum ofperr
-modify_flows_begin__(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
- struct rule_collection *rules)
+modify_flows_start__(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
+ struct rule_collection *old_rules,
+ struct rule_collection *new_rules)
OVS_REQUIRES(ofproto_mutex)
{
enum ofperr error;
- if (rules->n > 0) {
- error = modify_flows_check__(ofproto, fm, rules);
+ rule_collection_init(new_rules);
+
+ if (old_rules->n > 0) {
+ struct cls_conjunction *conjs;
+ size_t n_conjs;
+ size_t i;
+
+ /* Create a new 'modified' rule for each old rule. */
+ for (i = 0; i < old_rules->n; i++) {
+ struct rule *old_rule = old_rules->rules[i];
+ struct rule *new_rule;
+ struct cls_rule cr;
+
+ cls_rule_clone_in_version(&cr, &old_rule->cr,
+ ofproto->tables_version + 1);
+ error = replace_rule_create(ofproto, fm, &cr, old_rule->table_id,
+ old_rule, &new_rule);
+ if (!error) {
+ rule_collection_add(new_rules, new_rule);
+ } else {
+ rule_collection_unref(new_rules);
+ rule_collection_destroy(new_rules);
+ return error;
+ }
+ }
+ ovs_assert(new_rules->n == old_rules->n);
+
+ get_conjunctions(fm, &conjs, &n_conjs);
+ for (i = 0; i < old_rules->n; i++) {
+ replace_rule_start(ofproto, old_rules->rules[i],
+ new_rules->rules[i], conjs, n_conjs);
+ }
+ free(conjs);
} else if (!(fm->cookie_mask != htonll(0)
|| fm->new_cookie == OVS_BE64_MAX)) {
- bool modify;
-
- error = add_flow_begin(ofproto, fm, &rules->rules[0], &modify);
+ /* No match, add a new flow. */
+ error = add_flow_start(ofproto, fm, &old_rules->rules[0],
+ &new_rules->rules[0]);
if (!error) {
- ovs_assert(!modify);
+ ovs_assert(fm->delete_reason == OFPRR_EVICTION
+ || !old_rules->rules[0]);
}
+ new_rules->n = 1;
} else {
- rules->rules[0] = NULL;
error = 0;
}
+
return error;
}
* 'ofconn' is used to retrieve the packet buffer specified in fm->buffer_id,
* if any. */
static enum ofperr
-modify_flows_begin_loose(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
- struct rule_collection *rules)
+modify_flows_start_loose(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
+ struct rule_collection *old_rules,
+ struct rule_collection *new_rules)
OVS_REQUIRES(ofproto_mutex)
{
struct rule_criteria criteria;
enum ofperr error;
- rule_criteria_init(&criteria, fm->table_id, &fm->match, 0,
+ rule_criteria_init(&criteria, fm->table_id, &fm->match, 0, CLS_MAX_VERSION,
fm->cookie, fm->cookie_mask, OFPP_ANY, OFPG11_ANY);
rule_criteria_require_rw(&criteria,
(fm->flags & OFPUTIL_FF_NO_READONLY) != 0);
- error = collect_rules_loose(ofproto, &criteria, rules);
+ error = collect_rules_loose(ofproto, &criteria, old_rules);
rule_criteria_destroy(&criteria);
if (!error) {
- error = modify_flows_begin__(ofproto, fm, rules);
+ error = modify_flows_start__(ofproto, fm, old_rules, new_rules);
}
if (error) {
- rule_collection_destroy(rules);
+ rule_collection_destroy(old_rules);
}
return error;
}
static void
-modify_flows_revert(struct ofproto *ofproto, struct rule_collection *rules)
+modify_flows_revert(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
+ struct rule_collection *old_rules,
+ struct rule_collection *new_rules)
OVS_REQUIRES(ofproto_mutex)
{
- /* Old rules were not changed yet, only need to revert a new rule. */
- if (rules->n == 0 && rules->rules[0] != NULL) {
- add_flow_revert(ofproto, rules->rules[0]);
+ /* Old rules were not changed yet, only need to revert new rules. */
+ if (old_rules->n == 0 && new_rules->n == 1) {
+ add_flow_revert(ofproto, fm, old_rules->rules[0], new_rules->rules[0]);
+ } else if (old_rules->n > 0) {
+ for (size_t i = 0; i < old_rules->n; i++) {
+ replace_rule_revert(ofproto, old_rules->rules[i],
+ new_rules->rules[i]);
+ }
+ rule_collection_destroy(new_rules);
+ rule_collection_destroy(old_rules);
}
- rule_collection_destroy(rules);
}
static void
modify_flows_finish(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
const struct flow_mod_requester *req,
- struct rule_collection *rules)
+ struct rule_collection *old_rules,
+ struct rule_collection *new_rules)
OVS_REQUIRES(ofproto_mutex)
{
- if (rules->n > 0) {
- modify_flows__(ofproto, fm, req, rules);
- send_buffered_packet(req, fm->buffer_id, rules->rules[0]);
- } else if (rules->rules[0] != NULL) {
- add_flow_finish(ofproto, fm, req, rules->rules[0], false);
+ if (old_rules->n == 0 && new_rules->n == 1) {
+ add_flow_finish(ofproto, fm, req, old_rules->rules[0],
+ new_rules->rules[0]);
+ } else if (old_rules->n > 0) {
+ struct ovs_list dead_cookies = OVS_LIST_INITIALIZER(&dead_cookies);
+
+ ovs_assert(new_rules->n == old_rules->n);
+
+ for (size_t i = 0; i < old_rules->n; i++) {
+ replace_rule_finish(ofproto, fm, req, old_rules->rules[i],
+ new_rules->rules[i], &dead_cookies);
+ }
+ learned_cookies_flush(ofproto, &dead_cookies);
+ rule_collection_remove_postponed(old_rules);
+
+ send_buffered_packet(req, fm->buffer_id, new_rules->rules[0]);
+ rule_collection_destroy(new_rules);
}
- rule_collection_destroy(rules);
}
/* Implements OFPFC_MODIFY_STRICT. Returns 0 on success or an OpenFlow error
* code on failure. */
static enum ofperr
-modify_flow_begin_strict(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
- struct rule_collection *rules)
+modify_flow_start_strict(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
+ struct rule_collection *old_rules,
+ struct rule_collection *new_rules)
OVS_REQUIRES(ofproto_mutex)
{
struct rule_criteria criteria;
enum ofperr error;
rule_criteria_init(&criteria, fm->table_id, &fm->match, fm->priority,
- fm->cookie, fm->cookie_mask, OFPP_ANY, OFPG11_ANY);
+ CLS_MAX_VERSION, fm->cookie, fm->cookie_mask, OFPP_ANY,
+ OFPG11_ANY);
rule_criteria_require_rw(&criteria,
(fm->flags & OFPUTIL_FF_NO_READONLY) != 0);
- error = collect_rules_strict(ofproto, &criteria, rules);
+ error = collect_rules_strict(ofproto, &criteria, old_rules);
rule_criteria_destroy(&criteria);
if (!error) {
/* collect_rules_strict() can return max 1 rule. */
- error = modify_flows_begin__(ofproto, fm, rules);
+ error = modify_flows_start__(ofproto, fm, old_rules, new_rules);
}
if (error) {
- rule_collection_destroy(rules);
+ rule_collection_destroy(old_rules);
}
return error;
}
\f
/* OFPFC_DELETE implementation. */
-/* Deletes the rules listed in 'rules'. */
static void
-delete_flows__(const struct rule_collection *rules,
- enum ofp_flow_removed_reason reason,
- const struct flow_mod_requester *req)
+delete_flows_start__(struct ofproto *ofproto,
+ const struct rule_collection *rules)
+ OVS_REQUIRES(ofproto_mutex)
+{
+ for (size_t i = 0; i < rules->n; i++) {
+ struct rule *rule = rules->rules[i];
+ struct oftable *table = &ofproto->tables[rule->table_id];
+
+ table->n_flows--;
+ cls_rule_make_invisible_in_version(&rule->cr,
+ ofproto->tables_version + 1);
+ }
+}
+
+static void
+delete_flows_finish__(struct ofproto *ofproto,
+ struct rule_collection *rules,
+ enum ofp_flow_removed_reason reason,
+ const struct flow_mod_requester *req)
OVS_REQUIRES(ofproto_mutex)
{
if (rules->n) {
struct ovs_list dead_cookies = OVS_LIST_INITIALIZER(&dead_cookies);
- struct ofproto *ofproto = rules->rules[0]->ofproto;
- struct rule *rule, *next;
- size_t i;
- for (i = 0, next = rules->rules[0];
- rule = next, next = (++i < rules->n) ? rules->rules[i] : NULL,
- rule; ) {
- struct classifier *cls = &ofproto->tables[rule->table_id].cls;
- uint8_t next_table = next ? next->table_id : UINT8_MAX;
+ for (size_t i = 0; i < rules->n; i++) {
+ struct rule *rule = rules->rules[i];
- ofproto_rule_send_removed(rule, reason);
+ /* This value will be used to send the flow removed message right
+ * before the rule is actually destroyed. */
+ rule->removed_reason = reason;
ofmonitor_report(ofproto->connmgr, rule, NXFME_DELETED, reason,
req ? req->ofconn : NULL,
req ? req->request->xid : 0, NULL);
-
- if (next_table == rule->table_id) {
- classifier_defer(cls);
- }
- classifier_remove(cls, &rule->cr);
- if (next_table != rule->table_id) {
- classifier_publish(cls);
- }
ofproto_rule_remove__(ofproto, rule);
-
- ofproto->ofproto_class->rule_delete(rule);
-
learned_cookies_dec(ofproto, rule_get_actions(rule),
&dead_cookies);
-
- ofproto_rule_unref(rule);
}
+ rule_collection_remove_postponed(rules);
+
learned_cookies_flush(ofproto, &dead_cookies);
+ }
+}
+
+/* Deletes the rules listed in 'rules'.
+ * The deleted rules will become invisible to the lookups in the next version.
+ * Destroys 'rules'. */
+static void
+delete_flows__(struct rule_collection *rules,
+ enum ofp_flow_removed_reason reason,
+ const struct flow_mod_requester *req)
+ OVS_REQUIRES(ofproto_mutex)
+{
+ if (rules->n) {
+ struct ofproto *ofproto = rules->rules[0]->ofproto;
+
+ delete_flows_start__(ofproto, rules);
+ ofproto_bump_tables_version(ofproto);
+ delete_flows_finish__(ofproto, rules, reason, req);
ofmonitor_flush(ofproto->connmgr);
}
}
/* Implements OFPFC_DELETE. */
static enum ofperr
-delete_flows_begin_loose(struct ofproto *ofproto,
+delete_flows_start_loose(struct ofproto *ofproto,
const struct ofputil_flow_mod *fm,
struct rule_collection *rules)
OVS_REQUIRES(ofproto_mutex)
struct rule_criteria criteria;
enum ofperr error;
- rule_criteria_init(&criteria, fm->table_id, &fm->match, 0,
- fm->cookie, fm->cookie_mask,
- fm->out_port, fm->out_group);
+ rule_criteria_init(&criteria, fm->table_id, &fm->match, 0, CLS_MAX_VERSION,
+ fm->cookie, fm->cookie_mask, fm->out_port,
+ fm->out_group);
rule_criteria_require_rw(&criteria,
(fm->flags & OFPUTIL_FF_NO_READONLY) != 0);
error = collect_rules_loose(ofproto, &criteria, rules);
rule_criteria_destroy(&criteria);
if (!error) {
- for (size_t i = 0; i < rules->n; i++) {
- struct rule *rule = rules->rules[i];
-
- CONST_CAST(struct cls_rule *, &rule->cr)->to_be_removed = true;
- }
+ delete_flows_start__(ofproto, rules);
}
return error;
}
static void
-delete_flows_revert(struct rule_collection *rules)
+delete_flows_revert(struct ofproto *ofproto,
+ struct rule_collection *rules)
OVS_REQUIRES(ofproto_mutex)
{
for (size_t i = 0; i < rules->n; i++) {
struct rule *rule = rules->rules[i];
+ struct oftable *table = &ofproto->tables[rule->table_id];
- CONST_CAST(struct cls_rule *, &rule->cr)->to_be_removed = false;
+ /* Restore table's rule count. */
+ table->n_flows++;
+
+ /* Restore the original visibility of the rule. */
+ cls_rule_restore_visibility(&rule->cr);
}
rule_collection_destroy(rules);
}
static void
-delete_flows_finish(const struct ofputil_flow_mod *fm,
+delete_flows_finish(struct ofproto *ofproto,
+ const struct ofputil_flow_mod *fm,
const struct flow_mod_requester *req,
struct rule_collection *rules)
OVS_REQUIRES(ofproto_mutex)
{
- delete_flows__(rules, fm->delete_reason, req);
- rule_collection_destroy(rules);
+ delete_flows_finish__(ofproto, rules, fm->delete_reason, req);
}
/* Implements OFPFC_DELETE_STRICT. */
static enum ofperr
-delete_flow_begin_strict(struct ofproto *ofproto,
+delete_flow_start_strict(struct ofproto *ofproto,
const struct ofputil_flow_mod *fm,
struct rule_collection *rules)
OVS_REQUIRES(ofproto_mutex)
enum ofperr error;
rule_criteria_init(&criteria, fm->table_id, &fm->match, fm->priority,
- fm->cookie, fm->cookie_mask,
+ CLS_MAX_VERSION, fm->cookie, fm->cookie_mask,
fm->out_port, fm->out_group);
rule_criteria_require_rw(&criteria,
(fm->flags & OFPUTIL_FF_NO_READONLY) != 0);
rule_criteria_destroy(&criteria);
if (!error) {
- for (size_t i = 0; i < rules->n; i++) {
- struct rule *rule = rules->rules[i];
-
- CONST_CAST(struct cls_rule *, &rule->cr)->to_be_removed = true;
- }
+ delete_flows_start__(ofproto, rules);
}
return error;
}
+/* This may only be called by rule_destroy_cb()! */
static void
-ofproto_rule_send_removed(struct rule *rule, uint8_t reason)
- OVS_REQUIRES(ofproto_mutex)
+ofproto_rule_send_removed(struct rule *rule)
+ OVS_EXCLUDED(ofproto_mutex)
{
struct ofputil_flow_removed fr;
long long int used;
- if (rule_is_hidden(rule) ||
- !(rule->flags & OFPUTIL_FF_SEND_FLOW_REM)) {
- return;
- }
-
minimatch_expand(&rule->cr.match, &fr.match);
fr.priority = rule->cr.priority;
+
+ ovs_mutex_lock(&ofproto_mutex);
fr.cookie = rule->flow_cookie;
- fr.reason = reason;
+ fr.reason = rule->removed_reason;
fr.table_id = rule->table_id;
calc_duration(rule->created, time_msec(),
&fr.duration_sec, &fr.duration_nsec);
ovs_mutex_unlock(&rule->mutex);
rule->ofproto->ofproto_class->rule_get_stats(rule, &fr.packet_count,
&fr.byte_count, &used);
-
connmgr_send_flow_removed(rule->ofproto->connmgr, &fr);
+ ovs_mutex_unlock(&ofproto_mutex);
}
/* Sends an OpenFlow "flow removed" message with the given 'reason' (either
enum ofperr error;
ovs_mutex_lock(&ofproto_mutex);
- error = do_bundle_flow_mod_begin(ofproto, fm, &be);
+ error = do_bundle_flow_mod_start(ofproto, fm, &be);
if (!error) {
+ ofproto_bump_tables_version(ofproto);
do_bundle_flow_mod_finish(ofproto, fm, req, &be);
}
ofmonitor_flush(ofproto->connmgr);
const struct oftable *table;
struct cls_rule target;
- cls_rule_init_from_minimatch(&target, &m->match, 0);
+ cls_rule_init_from_minimatch(&target, &m->match, 0, CLS_MAX_VERSION);
FOR_EACH_MATCHING_TABLE (table, m->table_id, ofproto) {
struct rule *rule;
meter_delete(ofproto, first, last);
ovs_mutex_unlock(&ofproto_mutex);
- rule_collection_destroy(&rules);
return error;
}
uint32_t count;
match_init_catchall(&match);
- rule_criteria_init(&criteria, 0xff, &match, 0, htonll(0), htonll(0),
- OFPP_ANY, group->group_id);
+ rule_criteria_init(&criteria, 0xff, &match, 0, CLS_MAX_VERSION, htonll(0),
+ htonll(0), OFPP_ANY, group->group_id);
ovs_mutex_lock(&ofproto_mutex);
error = collect_rules_loose(ofproto, &criteria, &rules);
ovs_mutex_unlock(&ofproto_mutex);
}
static enum ofperr
-do_bundle_flow_mod_begin(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
+do_bundle_flow_mod_start(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
struct ofp_bundle_entry *be)
OVS_REQUIRES(ofproto_mutex)
{
switch (fm->command) {
case OFPFC_ADD:
- return add_flow_begin(ofproto, fm, &be->rule, &be->modify);
-
+ return add_flow_start(ofproto, fm, &be->old_rules.stub[0],
+ &be->new_rules.stub[0]);
case OFPFC_MODIFY:
- return modify_flows_begin_loose(ofproto, fm, &be->rules);
-
+ return modify_flows_start_loose(ofproto, fm, &be->old_rules,
+ &be->new_rules);
case OFPFC_MODIFY_STRICT:
- return modify_flow_begin_strict(ofproto, fm, &be->rules);
-
+ return modify_flow_start_strict(ofproto, fm, &be->old_rules,
+ &be->new_rules);
case OFPFC_DELETE:
- return delete_flows_begin_loose(ofproto, fm, &be->rules);
+ return delete_flows_start_loose(ofproto, fm, &be->old_rules);
case OFPFC_DELETE_STRICT:
- return delete_flow_begin_strict(ofproto, fm, &be->rules);
+ return delete_flow_start_strict(ofproto, fm, &be->old_rules);
}
return OFPERR_OFPFMFC_BAD_COMMAND;
{
switch (fm->command) {
case OFPFC_ADD:
- add_flow_revert(ofproto, be->modify ? NULL : be->rule);
+ add_flow_revert(ofproto, fm, be->old_rules.stub[0],
+ be->new_rules.stub[0]);
break;
case OFPFC_MODIFY:
case OFPFC_MODIFY_STRICT:
- modify_flows_revert(ofproto, &be->rules);
+ modify_flows_revert(ofproto, fm, &be->old_rules, &be->new_rules);
break;
case OFPFC_DELETE:
case OFPFC_DELETE_STRICT:
- delete_flows_revert(&be->rules);
+ delete_flows_revert(ofproto, &be->old_rules);
break;
default:
{
switch (fm->command) {
case OFPFC_ADD:
- add_flow_finish(ofproto, fm, req, be->rule, be->modify);
+ add_flow_finish(ofproto, fm, req, be->old_rules.stub[0],
+ be->new_rules.stub[0]);
break;
case OFPFC_MODIFY:
case OFPFC_MODIFY_STRICT:
- modify_flows_finish(ofproto, fm, req, &be->rules);
+ modify_flows_finish(ofproto, fm, req, &be->old_rules, &be->new_rules);
break;
case OFPFC_DELETE:
case OFPFC_DELETE_STRICT:
- delete_flows_finish(fm, req, &be->rules);
+ delete_flows_finish(ofproto, fm, req, &be->old_rules);
break;
default:
/* Commit phases (all while locking ofproto_mutex):
*
- * 1. Gather resources - do not send any events or notifications.
- *
- * add: Check conflicts, check for a displaced flow. If no displaced flow
- * exists, add the new flow, but mark it as "invisible".
- * mod: Collect affected flows, Do not modify yet.
- * del: Collect affected flows, Do not delete yet.
+ * 1. Begin: Gather resources and make changes visible in the next version.
+ * - Mark affected rules for removal in the next version.
+ * - Create new replacement rules, make visible in the next
+ * version.
+ * - Do not send any events or notifications.
*
- * 2a. Fail if any errors are found. After this point no errors are possible.
- * No visible changes were made, so rollback is minimal (remove added invisible
- * flows, revert 'to_be_removed' status of flows).
+ * 2. Revert: Fail if any errors are found. After this point no errors are
+ * possible. No visible changes were made, so rollback is minimal (remove
+ * added invisible rules, restore visibility of rules marked for removal).
*
- * 2b. Commit the changes
- *
- * add: if have displaced flow, modify it, otherwise mark the new flow as
- * "visible".
- * mod: Modify the collected flows.
- * del: Delete the collected flows.
+ * 3. Finish: Make the changes visible for lookups. Insert replacement rules to
+ * the ofproto provider. Remove replaced and deleted rules from ofproto data
+ * structures, and Schedule postponed removal of deleted rules from the
+ * classifier. Send notifications, buffered packets, etc.
*/
static enum ofperr
do_bundle_commit(struct ofconn *ofconn, uint32_t id, uint16_t flags)
{
struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ cls_version_t visible_version = ofproto->tables_version;
struct ofp_bundle *bundle;
struct ofp_bundle_entry *be;
enum ofperr error;
if (bundle->flags != flags) {
error = OFPERR_OFPBFC_BAD_FLAGS;
} else {
+ bool prev_is_port_mod = false;
+
error = 0;
ovs_mutex_lock(&ofproto_mutex);
+
+ /* 1. Begin. */
LIST_FOR_EACH (be, node, &bundle->msg_list) {
if (be->type == OFPTYPE_PORT_MOD) {
- /* Not supported yet. */
- error = OFPERR_OFPBFC_MSG_FAILED;
+ /* Our port mods are not atomic. */
+ if (flags & OFPBF_ATOMIC) {
+ error = OFPERR_OFPBFC_MSG_FAILED;
+ } else {
+ prev_is_port_mod = true;
+ error = port_mod_start(ofconn, &be->pm, &be->port);
+ }
} else if (be->type == OFPTYPE_FLOW_MOD) {
- error = do_bundle_flow_mod_begin(ofproto, &be->fm, be);
+ /* Flow mods between port mods are applied as a single
+ * version, but the versions are published only after
+ * we know the commit is successful. */
+ if (prev_is_port_mod) {
+ ++ofproto->tables_version;
+ }
+ prev_is_port_mod = false;
+ error = do_bundle_flow_mod_start(ofproto, &be->fm, be);
} else {
OVS_NOT_REACHED();
}
if (error) {
break;
+ } else {
+ /* Store the version in which the changes should take
+ * effect. */
+ be->version = ofproto->tables_version + 1;
}
}
+
if (error) {
/* Send error referring to the original message. */
if (error) {
error = OFPERR_OFPBFC_MSG_FAILED;
}
- /* Revert all previous entires. */
+ /* 2. Revert. Undo all the changes made above. */
LIST_FOR_EACH_REVERSE_CONTINUE(be, node, &bundle->msg_list) {
if (be->type == OFPTYPE_FLOW_MOD) {
do_bundle_flow_mod_revert(ofproto, &be->fm, be);
}
+ /* Nothing needs to be reverted for a port mod. */
}
} else {
- /* Finish the changes. */
+ /* 4. Finish. */
LIST_FOR_EACH (be, node, &bundle->msg_list) {
+ /* Bump the lookup version to the one of the current message.
+ * This makes all the changes in the bundle at this version
+ * visible to lookups at once. */
+ if (visible_version < be->version) {
+ visible_version = be->version;
+ ofproto->ofproto_class->set_tables_version(
+ ofproto, visible_version);
+ }
if (be->type == OFPTYPE_FLOW_MOD) {
struct flow_mod_requester req = { ofconn, be->ofp_msg };
do_bundle_flow_mod_finish(ofproto, &be->fm, &req, be);
+ } else if (be->type == OFPTYPE_PORT_MOD) {
+ /* Perform the actual port mod. This is not atomic, i.e.,
+ * the effects will be immediately seen by upcall
+ * processing regardless of the lookup version. It should
+ * be noted that port configuration changes can originate
+ * also from OVSDB changes asynchronously to all upcall
+ * processing. */
+ port_mod_finish(ofconn, &be->pm, be->port);
}
}
}
+
+ /* Reset the tables_version. */
+ ofproto->tables_version = visible_version;
+
ofmonitor_flush(ofproto->connmgr);
ovs_mutex_unlock(&ofproto_mutex);
if (error) {
return error;
}
- /* Atomic updates not supported yet. */
- if (bctrl.flags & OFPBF_ATOMIC) {
- return OFPERR_OFPBFC_BAD_FLAGS;
- }
reply.flags = 0;
reply.bundle_id = bctrl.bundle_id;
return error;
}
+static enum ofperr
+handle_geneve_table_mod(struct ofconn *ofconn, const struct ofp_header *oh)
+{
+ struct ofputil_geneve_table_mod gtm;
+ enum ofperr error;
+
+ error = reject_slave_controller(ofconn);
+ if (error) {
+ return error;
+ }
+
+ error = ofputil_decode_geneve_table_mod(oh, >m);
+ if (error) {
+ return error;
+ }
+
+ error = tun_metadata_table_mod(>m);
+
+ ofputil_uninit_geneve_table(>m.mappings);
+ return error;
+}
+
+static enum ofperr
+handle_geneve_table_request(struct ofconn *ofconn, const struct ofp_header *oh)
+{
+ struct ofputil_geneve_table_reply gtr;
+ struct ofpbuf *b;
+
+ tun_metadata_table_request(>r);
+ b = ofputil_encode_geneve_table_reply(oh, >r);
+ ofputil_uninit_geneve_table(>r.mappings);
+
+ ofconn_send_reply(ofconn, b);
+ return 0;
+}
+
static enum ofperr
handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
OVS_EXCLUDED(ofproto_mutex)
case OFPTYPE_BUNDLE_ADD_MESSAGE:
return handle_bundle_add(ofconn, oh);
+ case OFPTYPE_NXT_GENEVE_TABLE_MOD:
+ return handle_geneve_table_mod(ofconn, oh);
+
+ case OFPTYPE_NXT_GENEVE_TABLE_REQUEST:
+ return handle_geneve_table_request(ofconn, oh);
+
case OFPTYPE_HELLO:
case OFPTYPE_ERROR:
case OFPTYPE_FEATURES_REPLY:
case OFPTYPE_METER_FEATURES_STATS_REPLY:
case OFPTYPE_TABLE_FEATURES_STATS_REPLY:
case OFPTYPE_ROLE_STATUS:
+ case OFPTYPE_NXT_GENEVE_TABLE_REPLY:
default:
if (ofpmsg_is_stat_request(oh)) {
return OFPERR_OFPBRC_BAD_STAT;
memset(table, 0, sizeof *table);
classifier_init(&table->cls, flow_segment_u64s);
table->max_flows = UINT_MAX;
+ table->n_flows = 0;
atomic_init(&table->miss_config, OFPUTIL_TABLE_MISS_DEFAULT);
classifier_set_prefix_fields(&table->cls, default_prefix_fields,
{
const struct rule_actions *actions = rule_get_actions(rule);
+ ovs_assert(rule->removed);
+
if (rule->hard_timeout || rule->idle_timeout) {
list_insert(&ofproto->expirable, &rule->expirable);
}
if (actions->has_meter) {
meter_insert_rule(rule);
}
+ rule->removed = false;
}
-/* Removes 'rule' from the ofproto data structures AFTER caller has removed
- * it from the classifier. */
+/* Removes 'rule' from the ofproto data structures. Caller may have deferred
+ * the removal from the classifier. */
static void
ofproto_rule_remove__(struct ofproto *ofproto, struct rule *rule)
OVS_REQUIRES(ofproto_mutex)
{
+ ovs_assert(!rule->removed);
+
cookies_remove(ofproto, rule);
eviction_group_remove_rule(rule);
list_remove(&rule->meter_list_node);
list_init(&rule->meter_list_node);
}
-}
-static void
-oftable_remove_rule(struct rule *rule)
- OVS_REQUIRES(ofproto_mutex)
-{
- struct classifier *cls = &rule->ofproto->tables[rule->table_id].cls;
-
- if (classifier_remove(cls, &rule->cr)) {
- ofproto_rule_remove__(rule->ofproto, rule);
- }
+ rule->removed = true;
}
\f
/* unixctl commands. */
match_init_catchall(&match);
match_set_vlan_vid_masked(&match, htons(VLAN_CFI), htons(VLAN_CFI));
- cls_rule_init(&target, &match, 0);
+ cls_rule_init(&target, &match, 0, CLS_MAX_VERSION);
free(ofproto->vlan_bitmap);
ofproto->vlan_bitmap = bitmap_allocate(4096);