]> git.proxmox.com Git - ovs.git/blobdiff - ofproto/ofproto.c
ofproto: Make ofproto_rule_is_hidden() public, and rename.
[ovs.git] / ofproto / ofproto.c
index ea11e5de19bad18bd2c52ca21fd6b5240c289d4b..fb768db5d4ba83040a2a2a9b755f50632299ea1c 100644 (file)
@@ -69,6 +69,11 @@ COVERAGE_DEFINE(ofproto_recv_openflow);
 COVERAGE_DEFINE(ofproto_reinit_ports);
 COVERAGE_DEFINE(ofproto_update_port);
 
+/* Default fields to use for prefix tries in each flow table, unless something
+ * else is configured. */
+const enum mf_field_id default_prefix_fields[2] =
+    { MFF_IPV4_DST, MFF_IPV4_SRC };
+
 enum ofproto_state {
     S_OPENFLOW,                 /* Processing OpenFlow commands. */
     S_EVICT,                    /* Evicting flows from over-limit tables. */
@@ -158,7 +163,6 @@ static void oftable_enable_eviction(struct oftable *,
 static void oftable_remove_rule(struct rule *rule) OVS_REQUIRES(ofproto_mutex);
 static void oftable_remove_rule__(struct ofproto *, struct rule *)
     OVS_REQUIRES(ofproto_mutex);
-static void oftable_insert_rule(struct rule *);
 
 /* 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
@@ -182,11 +186,16 @@ struct eviction_group {
     struct heap rules;          /* Contains "struct rule"s. */
 };
 
-static bool choose_rule_to_evict(struct oftable *table, struct rule **rulep);
-static void ofproto_evict(struct ofproto *) OVS_EXCLUDED(ofproto_mutex);
-static uint32_t rule_eviction_priority(struct ofproto *ofproto, struct rule *);
-static void eviction_group_add_rule(struct rule *);
-static void eviction_group_remove_rule(struct rule *);
+static bool choose_rule_to_evict(struct oftable *table, struct rule **rulep)
+    OVS_REQUIRES(ofproto_mutex);
+static void ofproto_evict(struct ofproto *)
+    OVS_EXCLUDED(ofproto_mutex);
+static uint32_t rule_eviction_priority(struct ofproto *ofproto, struct rule *)
+    OVS_REQUIRES(ofproto_mutex);;
+static void eviction_group_add_rule(struct rule *)
+    OVS_REQUIRES(ofproto_mutex);
+static void eviction_group_remove_rule(struct rule *)
+    OVS_REQUIRES(ofproto_mutex);
 
 /* Criteria that flow_mod and other operations use for selecting rules on
  * which to operate. */
@@ -268,9 +277,6 @@ static bool rule_is_modifiable(const struct rule *rule,
 static enum ofperr add_flow(struct ofproto *, struct ofconn *,
                             struct ofputil_flow_mod *,
                             const struct ofp_header *);
-static void do_add_flow(struct ofproto *, struct ofconn *,
-                        const struct ofp_header *request, uint32_t buffer_id,
-                        struct rule *);
 static enum ofperr modify_flows__(struct ofproto *, struct ofconn *,
                                   struct ofputil_flow_mod *,
                                   const struct ofp_header *,
@@ -299,6 +305,7 @@ static uint64_t pick_fallback_dpid(void);
 static void ofproto_destroy__(struct ofproto *);
 static void update_mtu(struct ofproto *, struct ofport *);
 static void meter_delete(struct ofproto *, uint32_t first, uint32_t last);
+static void meter_insert_rule(struct rule *);
 
 /* unixctl. */
 static void ofproto_unixctl_init(void);
@@ -1336,6 +1343,9 @@ ofproto_destroy__(struct ofproto *ofproto)
 
     hmap_destroy(&ofproto->deletions);
 
+    ovs_assert(hindex_is_empty(&ofproto->cookies));
+    hindex_destroy(&ofproto->cookies);
+
     free(ofproto->vlan_bitmap);
 
     ofproto->ofproto_class->dealloc(ofproto);
@@ -1860,6 +1870,7 @@ flow_mod_init(struct ofputil_flow_mod *fm,
     fm->flags = 0;
     fm->ofpacts = CONST_CAST(struct ofpact *, ofpacts);
     fm->ofpacts_len = ofpacts_len;
+    fm->delete_reason = OFPRR_DELETE;
 }
 
 static int
@@ -1973,47 +1984,6 @@ ofproto_flow_mod(struct ofproto *ofproto, struct ofputil_flow_mod *fm)
     return handle_flow_mod__(ofproto, NULL, fm, NULL);
 }
 
-/* Resets the modified time for 'rule' or an equivalent rule. If 'rule' is not
- * in the classifier, but an equivalent rule is, unref 'rule' and ref the new
- * rule. Otherwise if 'rule' is no longer installed in the classifier,
- * reinstall it.
- *
- * Returns the rule whose modified time has been reset. */
-struct rule *
-ofproto_refresh_rule(struct rule *rule)
-{
-    const struct oftable *table = &rule->ofproto->tables[rule->table_id];
-    const struct cls_rule *cr = &rule->cr;
-    struct rule *r;
-
-    /* do_add_flow() requires that the rule is not installed. We lock the
-     * ofproto_mutex here so that another thread cannot add the flow before
-     * we get a chance to add it.*/
-    ovs_mutex_lock(&ofproto_mutex);
-
-    fat_rwlock_rdlock(&table->cls.rwlock);
-    r = rule_from_cls_rule(classifier_find_rule_exactly(&table->cls, cr));
-    if (r != rule) {
-        ofproto_rule_ref(r);
-    }
-    fat_rwlock_unlock(&table->cls.rwlock);
-
-    if (!r) {
-        do_add_flow(rule->ofproto, NULL, NULL, 0, rule);
-    } else if  (r != rule) {
-        ofproto_rule_unref(rule);
-        rule = r;
-    }
-    ovs_mutex_unlock(&ofproto_mutex);
-
-    /* Refresh the modified time for the rule. */
-    ovs_mutex_lock(&rule->mutex);
-    rule->modified = MAX(rule->modified, time_msec());
-    ovs_mutex_unlock(&rule->mutex);
-
-    return rule;
-}
-
 /* Searches for a rule with matching criteria exactly equal to 'target' in
  * ofproto's table 0 and, if it finds one, deletes it.
  *
@@ -2142,7 +2112,7 @@ dealloc_ofp_port(struct ofproto *ofproto, ofp_port_t ofp_port)
 
 /* Opens and returns a netdev for 'ofproto_port' in 'ofproto', or a null
  * pointer if the netdev cannot be opened.  On success, also fills in
- * 'opp'.  */
+ * '*pp'.  */
 static struct netdev *
 ofport_open(struct ofproto *ofproto,
             struct ofproto_port *ofproto_port,
@@ -2185,7 +2155,7 @@ ofport_open(struct ofproto *ofproto,
 }
 
 /* Returns true if most fields of 'a' and 'b' are equal.  Differences in name,
- * port number, and 'config' bits other than OFPUTIL_PS_LINK_DOWN are
+ * port number, and 'config' bits other than OFPUTIL_PC_PORT_DOWN are
  * disregarded. */
 static bool
 ofport_equal(const struct ofputil_phy_port *a,
@@ -2642,6 +2612,24 @@ ofproto_rule_unref(struct rule *rule)
     }
 }
 
+void
+ofproto_group_ref(struct ofgroup *group)
+{
+    if (group) {
+        ovs_refcount_ref(&group->ref_count);
+    }
+}
+
+void
+ofproto_group_unref(struct ofgroup *group)
+{
+    if (group && ovs_refcount_unref(&group->ref_count) == 1) {
+        group->ofproto->ofproto_class->group_destruct(group);
+        ofputil_bucket_list_destroy(&group->buckets);
+        group->ofproto->ofproto_class->group_dealloc(group);
+    }
+}
+
 static uint32_t get_provider_meter_id(const struct ofproto *,
                                       uint32_t of_meter_id);
 
@@ -2769,17 +2757,6 @@ destroy_rule_executes(struct ofproto *ofproto)
     }
 }
 
-/* Returns true if 'rule' should be hidden from the controller.
- *
- * Rules with priority higher than UINT16_MAX are set up by ofproto itself
- * (e.g. by in-band control) and are intentionally hidden from the
- * controller. */
-static bool
-ofproto_rule_is_hidden(const struct rule *rule)
-{
-    return (rule->cr.priority > UINT16_MAX);
-}
-
 static bool
 oftable_is_modifiable(const struct oftable *table,
                       enum ofputil_flow_mod_flags flags)
@@ -3170,52 +3147,62 @@ append_port_stat(struct ofport *port, struct list *replies)
     ofputil_append_port_stat(replies, &ops);
 }
 
-static enum ofperr
-handle_port_stats_request(struct ofconn *ofconn,
-                          const struct ofp_header *request)
+static void
+handle_port_request(struct ofconn *ofconn,
+                    const struct ofp_header *request, ofp_port_t port_no,
+                    void (*cb)(struct ofport *, struct list *replies))
 {
-    struct ofproto *p = ofconn_get_ofproto(ofconn);
+    struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
     struct ofport *port;
     struct list replies;
-    ofp_port_t port_no;
-    enum ofperr error;
-
-    error = ofputil_decode_port_stats_request(request, &port_no);
-    if (error) {
-        return error;
-    }
 
     ofpmp_init(&replies, request);
     if (port_no != OFPP_ANY) {
-        port = ofproto_get_port(p, port_no);
+        port = ofproto_get_port(ofproto, port_no);
         if (port) {
-            append_port_stat(port, &replies);
+            cb(port, &replies);
         }
     } else {
-        HMAP_FOR_EACH (port, hmap_node, &p->ports) {
-            append_port_stat(port, &replies);
+        HMAP_FOR_EACH (port, hmap_node, &ofproto->ports) {
+            cb(port, &replies);
         }
     }
 
     ofconn_send_replies(ofconn, &replies);
-    return 0;
+}
+
+static enum ofperr
+handle_port_stats_request(struct ofconn *ofconn,
+                          const struct ofp_header *request)
+{
+    ofp_port_t port_no;
+    enum ofperr error;
+
+    error = ofputil_decode_port_stats_request(request, &port_no);
+    if (!error) {
+        handle_port_request(ofconn, request, port_no, append_port_stat);
+    }
+    return error;
+}
+
+static void
+append_port_desc(struct ofport *port, struct list *replies)
+{
+    ofputil_append_port_desc_stats_reply(&port->pp, replies);
 }
 
 static enum ofperr
 handle_port_desc_stats_request(struct ofconn *ofconn,
                                const struct ofp_header *request)
 {
-    struct ofproto *p = ofconn_get_ofproto(ofconn);
-    struct ofport *port;
-    struct list replies;
+    ofp_port_t port_no;
+    enum ofperr error;
 
-    ofpmp_init(&replies, request);
-    HMAP_FOR_EACH (port, hmap_node, &p->ports) {
-        ofputil_append_port_desc_stats_reply(&port->pp, &replies);
+    error = ofputil_decode_port_desc_stats_request(request, &port_no);
+    if (!error) {
+        handle_port_request(ofconn, request, port_no, append_port_desc);
     }
-
-    ofconn_send_replies(ofconn, &replies);
-    return 0;
+    return error;
 }
 
 static uint32_t
@@ -3423,7 +3410,7 @@ collect_rule(struct rule *rule, const struct rule_criteria *c,
      * rules to be selected.  (This doesn't allow OpenFlow clients to meddle
      * with hidden flows because OpenFlow uses only a 16-bit field to specify
      * priority.) */
-    if (ofproto_rule_is_hidden(rule) && c->cr.priority <= UINT16_MAX) {
+    if (rule_is_hidden(rule) && c->cr.priority <= UINT16_MAX) {
         return 0;
     } else if (rule->pending) {
         return OFPROTO_POSTPONE;
@@ -3967,6 +3954,8 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
          struct ofputil_flow_mod *fm, const struct ofp_header *request)
     OVS_REQUIRES(ofproto_mutex)
 {
+    const struct rule_actions *actions;
+    struct ofopgroup *group;
     struct oftable *table;
     struct cls_rule cr;
     struct rule *rule;
@@ -4088,8 +4077,8 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
 
     *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));
+    actions = rule_actions_create(ofproto, fm->ofpacts, fm->ofpacts_len);
+    ovsrcu_set(&rule->actions, actions);
     list_init(&rule->meter_list_node);
     rule->eviction_group = NULL;
     list_init(&rule->expirable);
@@ -4104,26 +4093,25 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
         return error;
     }
 
-    /* Insert rule. */
-    do_add_flow(ofproto, ofconn, request, fm->buffer_id, rule);
-
-    return error;
-}
-
-static void
-do_add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
-            const struct ofp_header *request, uint32_t buffer_id,
-            struct rule *rule)
-    OVS_REQUIRES(ofproto_mutex)
-{
-    struct ofopgroup *group;
+    if (fm->hard_timeout || fm->idle_timeout) {
+        list_insert(&ofproto->expirable, &rule->expirable);
+    }
+    cookies_insert(ofproto, rule);
+    eviction_group_add_rule(rule);
+    if (actions->provider_meter_id != UINT32_MAX) {
+        meter_insert_rule(rule);
+    }
 
-    oftable_insert_rule(rule);
+    fat_rwlock_wrlock(&table->cls.rwlock);
+    classifier_insert(&table->cls, CONST_CAST(struct cls_rule *, &rule->cr));
+    fat_rwlock_unlock(&table->cls.rwlock);
 
-    group = ofopgroup_create(ofproto, ofconn, request, buffer_id);
+    group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id);
     ofoperation_create(group, rule, OFOPERATION_ADD, 0);
     ofproto->ofproto_class->rule_insert(rule);
     ofopgroup_submit(group);
+
+    return error;
 }
 \f
 /* OFPFC_MODIFY and OFPFC_MODIFY_STRICT. */
@@ -4146,6 +4134,20 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn,
     enum ofperr error;
     size_t i;
 
+    if (ofproto->ofproto_class->rule_premodify_actions) {
+        for (i = 0; i < rules->n; i++) {
+            struct rule *rule = rules->rules[i];
+
+            if (rule_is_modifiable(rule, fm->flags)) {
+                error = ofproto->ofproto_class->rule_premodify_actions(
+                    rule, fm->ofpacts, fm->ofpacts_len);
+                if (error) {
+                    return error;
+                }
+            }
+        }
+    }
+
     type = fm->command == OFPFC_ADD ? OFOPERATION_REPLACE : OFOPERATION_MODIFY;
     group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id);
     error = OFPERR_OFPBRC_EPERM;
@@ -4201,8 +4203,7 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn,
 
             ovsrcu_set(&rule->actions, new_actions);
 
-            rule->ofproto->ofproto_class->rule_modify_actions(rule,
-                                                              reset_counters);
+            ofproto->ofproto_class->rule_modify_actions(rule, reset_counters);
         } else {
             ofoperation_complete(op, 0);
         }
@@ -4343,7 +4344,8 @@ delete_flows_loose(struct ofproto *ofproto, struct ofconn *ofconn,
     rule_criteria_destroy(&criteria);
 
     if (!error && rules.n > 0) {
-        error = delete_flows__(ofproto, ofconn, request, &rules, OFPRR_DELETE);
+        error = delete_flows__(ofproto, ofconn, request, &rules,
+                               fm->delete_reason);
     }
     rule_collection_destroy(&rules);
 
@@ -4368,7 +4370,8 @@ delete_flow_strict(struct ofproto *ofproto, struct ofconn *ofconn,
     rule_criteria_destroy(&criteria);
 
     if (!error && rules.n > 0) {
-        error = delete_flows__(ofproto, ofconn, request, &rules, OFPRR_DELETE);
+        error = delete_flows__(ofproto, ofconn, request, &rules,
+                               fm->delete_reason);
     }
     rule_collection_destroy(&rules);
 
@@ -4382,8 +4385,7 @@ ofproto_rule_send_removed(struct rule *rule, uint8_t reason)
     struct ofputil_flow_removed fr;
     long long int used;
 
-    if (ofproto_rule_is_hidden(rule) ||
-        !(rule->flags & OFPUTIL_FF_SEND_FLOW_REM)) {
+    if (rule_is_hidden(rule) || !(rule->flags & OFPUTIL_FF_SEND_FLOW_REM)) {
         return;
     }
 
@@ -4809,7 +4811,7 @@ ofproto_collect_ofmonitor_refresh_rule(const struct ofmonitor *m,
 {
     enum nx_flow_monitor_flags update;
 
-    if (ofproto_rule_is_hidden(rule)) {
+    if (rule_is_hidden(rule)) {
         return;
     }
 
@@ -4999,8 +5001,6 @@ handle_flow_monitor_cancel(struct ofconn *ofconn, const struct ofp_header *oh)
 /* Meters implementation.
  *
  * Meter table entry, indexed by the OpenFlow meter_id.
- * These are always dynamically allocated to allocate enough space for
- * the bands.
  * 'created' is used to compute the duration for meter stats.
  * 'list rules' is needed so that we can delete the dependent rules when the
  * meter table entry is deleted.
@@ -5032,6 +5032,18 @@ get_provider_meter_id(const struct ofproto *ofproto, uint32_t of_meter_id)
     return UINT32_MAX;
 }
 
+/* Finds the meter invoked by 'rule''s actions and adds 'rule' to the meter's
+ * list of rules. */
+static void
+meter_insert_rule(struct rule *rule)
+{
+    const struct rule_actions *a = rule_get_actions(rule);
+    uint32_t meter_id = ofpacts_get_meter(a->ofpacts, a->ofpacts_len);
+    struct meter *meter = rule->ofproto->meters[meter_id];
+
+    list_insert(&meter->rules, &rule->meter_list_node);
+}
+
 static void
 meter_update(struct meter *meter, const struct ofputil_meter_config *config)
 {
@@ -5319,47 +5331,38 @@ handle_meter_request(struct ofconn *ofconn, const struct ofp_header *request,
     return 0;
 }
 
-bool
-ofproto_group_lookup(const struct ofproto *ofproto, uint32_t group_id,
-                     struct ofgroup **group)
-    OVS_TRY_RDLOCK(true, (*group)->rwlock)
+static bool
+ofproto_group_lookup__(const struct ofproto *ofproto, uint32_t group_id,
+                       struct ofgroup **group)
+    OVS_REQ_RDLOCK(ofproto->groups_rwlock)
 {
-    ovs_rwlock_rdlock(&ofproto->groups_rwlock);
     HMAP_FOR_EACH_IN_BUCKET (*group, hmap_node,
                              hash_int(group_id, 0), &ofproto->groups) {
         if ((*group)->group_id == group_id) {
-            ovs_rwlock_rdlock(&(*group)->rwlock);
-            ovs_rwlock_unlock(&ofproto->groups_rwlock);
             return true;
         }
     }
-    ovs_rwlock_unlock(&ofproto->groups_rwlock);
+
     return false;
 }
 
-void
-ofproto_group_release(struct ofgroup *group)
-    OVS_RELEASES(group->rwlock)
+/* If the group exists, this function increments the groups's reference count.
+ *
+ * Make sure to call ofproto_group_unref() after no longer needing to maintain
+ * a reference to the group. */
+bool
+ofproto_group_lookup(const struct ofproto *ofproto, uint32_t group_id,
+                     struct ofgroup **group)
 {
-    ovs_rwlock_unlock(&group->rwlock);
-}
+    bool found;
 
-static bool
-ofproto_group_write_lookup(const struct ofproto *ofproto, uint32_t group_id,
-                           struct ofgroup **group)
-    OVS_TRY_WRLOCK(true, ofproto->groups_rwlock)
-    OVS_TRY_WRLOCK(true, (*group)->rwlock)
-{
-    ovs_rwlock_wrlock(&ofproto->groups_rwlock);
-    HMAP_FOR_EACH_IN_BUCKET (*group, hmap_node,
-                             hash_int(group_id, 0), &ofproto->groups) {
-        if ((*group)->group_id == group_id) {
-            ovs_rwlock_wrlock(&(*group)->rwlock);
-            return true;
-        }
+    ovs_rwlock_rdlock(&ofproto->groups_rwlock);
+    found = ofproto_group_lookup__(ofproto, group_id, group);
+    if (found) {
+        ofproto_group_ref(*group);
     }
     ovs_rwlock_unlock(&ofproto->groups_rwlock);
-    return false;
+    return found;
 }
 
 static bool
@@ -5394,7 +5397,7 @@ static uint32_t
 group_get_ref_count(struct ofgroup *group)
     OVS_EXCLUDED(ofproto_mutex)
 {
-    struct ofproto *ofproto = group->ofproto;
+    struct ofproto *ofproto = CONST_CAST(struct ofproto *, group->ofproto);
     struct rule_criteria criteria;
     struct rule_collection rules;
     struct match match;
@@ -5417,10 +5420,9 @@ group_get_ref_count(struct ofgroup *group)
 
 static void
 append_group_stats(struct ofgroup *group, struct list *replies)
-    OVS_REQ_RDLOCK(group->rwlock)
 {
     struct ofputil_group_stats ogs;
-    struct ofproto *ofproto = group->ofproto;
+    const struct ofproto *ofproto = group->ofproto;
     long long int now = time_msec();
     int error;
 
@@ -5448,64 +5450,64 @@ append_group_stats(struct ofgroup *group, struct list *replies)
     free(ogs.bucket_stats);
 }
 
-static enum ofperr
-handle_group_stats_request(struct ofconn *ofconn,
-                           const struct ofp_header *request)
+static void
+handle_group_request(struct ofconn *ofconn,
+                     const struct ofp_header *request, uint32_t group_id,
+                     void (*cb)(struct ofgroup *, struct list *replies))
 {
     struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
-    struct list replies;
-    enum ofperr error;
     struct ofgroup *group;
-    uint32_t group_id;
-
-    error = ofputil_decode_group_stats_request(request, &group_id);
-    if (error) {
-        return error;
-    }
+    struct list replies;
 
     ofpmp_init(&replies, request);
-
     if (group_id == OFPG_ALL) {
         ovs_rwlock_rdlock(&ofproto->groups_rwlock);
         HMAP_FOR_EACH (group, hmap_node, &ofproto->groups) {
-            ovs_rwlock_rdlock(&group->rwlock);
-            append_group_stats(group, &replies);
-            ovs_rwlock_unlock(&group->rwlock);
+            cb(group, &replies);
         }
         ovs_rwlock_unlock(&ofproto->groups_rwlock);
     } else {
         if (ofproto_group_lookup(ofproto, group_id, &group)) {
-            append_group_stats(group, &replies);
-            ofproto_group_release(group);
+            cb(group, &replies);
+            ofproto_group_unref(group);
         }
     }
-
     ofconn_send_replies(ofconn, &replies);
-
-    return 0;
 }
 
 static enum ofperr
-handle_group_desc_stats_request(struct ofconn *ofconn,
-                                const struct ofp_header *request)
+handle_group_stats_request(struct ofconn *ofconn,
+                           const struct ofp_header *request)
 {
-    struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
-    struct list replies;
-    struct ofputil_group_desc gds;
-    struct ofgroup *group;
-
-    ofpmp_init(&replies, request);
+    uint32_t group_id;
+    enum ofperr error;
 
-    ovs_rwlock_rdlock(&ofproto->groups_rwlock);
-    HMAP_FOR_EACH (group, hmap_node, &ofproto->groups) {
-        gds.group_id = group->group_id;
-        gds.type = group->type;
-        ofputil_append_group_desc_reply(&gds, &group->buckets, &replies);
+    error = ofputil_decode_group_stats_request(request, &group_id);
+    if (error) {
+        return error;
     }
-    ovs_rwlock_unlock(&ofproto->groups_rwlock);
 
-    ofconn_send_replies(ofconn, &replies);
+    handle_group_request(ofconn, request, group_id, append_group_stats);
+    return 0;
+}
+
+static void
+append_group_desc(struct ofgroup *group, struct list *replies)
+{
+    struct ofputil_group_desc gds;
 
+    gds.group_id = group->group_id;
+    gds.type = group->type;
+    ofputil_append_group_desc_reply(&gds, &group->buckets, replies);
+}
+
+static enum ofperr
+handle_group_desc_stats_request(struct ofconn *ofconn,
+                                const struct ofp_header *request)
+{
+    handle_group_request(ofconn, request,
+                         ofputil_decode_group_desc_request(request),
+                         append_group_desc);
     return 0;
 }
 
@@ -5567,24 +5569,12 @@ handle_queue_get_config_request(struct ofconn *ofconn,
    return 0;
 }
 
-/* Implements OFPGC11_ADD
- * in which no matching flow already exists in the flow table.
- *
- * Adds the flow specified by 'ofm', which is followed by 'n_actions'
- * ofp_actions, to the ofproto's flow table.  Returns 0 on success, an OpenFlow
- * error code on failure, or OFPROTO_POSTPONE if the operation cannot be
- * initiated now but may be retried later.
- *
- * Upon successful return, takes ownership of 'fm->ofpacts'.  On failure,
- * ownership remains with the caller.
- *
- * 'ofconn' is used to retrieve the packet buffer specified in ofm->buffer_id,
- * if any. */
 static enum ofperr
-add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
+init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
+           struct ofgroup **ofgroup)
 {
-    struct ofgroup *ofgroup;
     enum ofperr error;
+    const long long int now = time_msec();
 
     if (gm->group_id > OFPG_MAX) {
         return OFPERR_OFPGMFC_INVALID_GROUP;
@@ -5593,26 +5583,45 @@ add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
         return OFPERR_OFPGMFC_BAD_TYPE;
     }
 
-    /* Allocate new group and initialize it. */
-    ofgroup = ofproto->ofproto_class->group_alloc();
-    if (!ofgroup) {
-        VLOG_WARN_RL(&rl, "%s: failed to create group", ofproto->name);
+    *ofgroup = ofproto->ofproto_class->group_alloc();
+    if (!*ofgroup) {
+        VLOG_WARN_RL(&rl, "%s: failed to allocate group", ofproto->name);
         return OFPERR_OFPGMFC_OUT_OF_GROUPS;
     }
 
-    ovs_rwlock_init(&ofgroup->rwlock);
-    ofgroup->ofproto  = ofproto;
-    ofgroup->group_id = gm->group_id;
-    ofgroup->type     = gm->type;
-    ofgroup->created = ofgroup->modified = time_msec();
+    (*ofgroup)->ofproto = ofproto;
+    *CONST_CAST(uint32_t *, &((*ofgroup)->group_id)) = gm->group_id;
+    *CONST_CAST(enum ofp11_group_type *, &(*ofgroup)->type) = gm->type;
+    *CONST_CAST(long long int *, &((*ofgroup)->created)) = now;
+    *CONST_CAST(long long int *, &((*ofgroup)->modified)) = now;
+    ovs_refcount_init(&(*ofgroup)->ref_count);
 
-    list_move(&ofgroup->buckets, &gm->buckets);
-    ofgroup->n_buckets = list_size(&ofgroup->buckets);
+    list_move(&(*ofgroup)->buckets, &gm->buckets);
+    *CONST_CAST(uint32_t *, &(*ofgroup)->n_buckets) =
+        list_size(&(*ofgroup)->buckets);
 
     /* Construct called BEFORE any locks are held. */
-    error = ofproto->ofproto_class->group_construct(ofgroup);
+    error = ofproto->ofproto_class->group_construct(*ofgroup);
+    if (error) {
+        ofputil_bucket_list_destroy(&(*ofgroup)->buckets);
+        ofproto->ofproto_class->group_dealloc(*ofgroup);
+    }
+    return error;
+}
+
+/* Implements the OFPGC11_ADD operation specified by 'gm', adding a group to
+ * 'ofproto''s group table.  Returns 0 on success or an OpenFlow error code on
+ * failure. */
+static enum ofperr
+add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
+{
+    struct ofgroup *ofgroup;
+    enum ofperr error;
+
+    /* Allocate new group and initialize it. */
+    error = init_group(ofproto, gm, &ofgroup);
     if (error) {
-        goto free_out;
+        return error;
     }
 
     /* We wrlock as late as possible to minimize the time we jam any other
@@ -5642,76 +5651,66 @@ add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
  unlock_out:
     ovs_rwlock_unlock(&ofproto->groups_rwlock);
     ofproto->ofproto_class->group_destruct(ofgroup);
- free_out:
     ofputil_bucket_list_destroy(&ofgroup->buckets);
     ofproto->ofproto_class->group_dealloc(ofgroup);
 
     return error;
 }
 
-/* Implements OFPFC_MODIFY.  Returns 0 on success or an OpenFlow error code on
- * failure.
+/* Implements OFPGC11_MODIFY.  Returns 0 on success or an OpenFlow error code
+ * on failure.
  *
- * 'ofconn' is used to retrieve the packet buffer specified in fm->buffer_id,
- * if any. */
+ * Note that the group is re-created and then replaces the old group in
+ * ofproto's ofgroup hash map. Thus, the group is never altered while users of
+ * the xlate module hold a pointer to the group. */
 static enum ofperr
 modify_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
 {
-    struct ofgroup *ofgroup;
-    struct ofgroup *victim;
+    struct ofgroup *ofgroup, *new_ofgroup, *retiring;
     enum ofperr error;
 
-    if (gm->group_id > OFPG_MAX) {
-        return OFPERR_OFPGMFC_INVALID_GROUP;
-    }
-
-    if (gm->type > OFPGT11_FF) {
-        return OFPERR_OFPGMFC_BAD_TYPE;
+    error = init_group(ofproto, gm, &new_ofgroup);
+    if (error) {
+        return error;
     }
 
-    victim = ofproto->ofproto_class->group_alloc();
-    if (!victim) {
-        VLOG_WARN_RL(&rl, "%s: failed to allocate group", ofproto->name);
-        return OFPERR_OFPGMFC_OUT_OF_GROUPS;
-    }
+    retiring = new_ofgroup;
 
-    if (!ofproto_group_write_lookup(ofproto, gm->group_id, &ofgroup)) {
+    ovs_rwlock_wrlock(&ofproto->groups_rwlock);
+    if (!ofproto_group_lookup__(ofproto, gm->group_id, &ofgroup)) {
         error = OFPERR_OFPGMFC_UNKNOWN_GROUP;
-        goto free_out;
+        goto out;
     }
-    /* Both group's and its container's write locks held now.
-     * Also, n_groups[] is protected by ofproto->groups_rwlock. */
+
+    /* Ofproto's group write lock is held now. */
     if (ofgroup->type != gm->type
         && ofproto->n_groups[gm->type] >= ofproto->ogf.max_groups[gm->type]) {
         error = OFPERR_OFPGMFC_OUT_OF_GROUPS;
-        goto unlock_out;
+        goto out;
     }
 
-    *victim = *ofgroup;
-    list_move(&victim->buckets, &ofgroup->buckets);
+    /* The group creation time does not change during modification. */
+    *CONST_CAST(long long int *, &(new_ofgroup->created)) = ofgroup->created;
+    *CONST_CAST(long long int *, &(new_ofgroup->modified)) = time_msec();
 
-    ofgroup->type = gm->type;
-    list_move(&ofgroup->buckets, &gm->buckets);
-    ofgroup->n_buckets = list_size(&ofgroup->buckets);
-
-    error = ofproto->ofproto_class->group_modify(ofgroup, victim);
-    if (!error) {
-        ofputil_bucket_list_destroy(&victim->buckets);
-        ofproto->n_groups[victim->type]--;
-        ofproto->n_groups[ofgroup->type]++;
-        ofgroup->modified = time_msec();
-    } else {
-        ofputil_bucket_list_destroy(&ofgroup->buckets);
+    error = ofproto->ofproto_class->group_modify(new_ofgroup);
+    if (error) {
+        goto out;
+    }
 
-        *ofgroup = *victim;
-        list_move(&ofgroup->buckets, &victim->buckets);
+    retiring = ofgroup;
+    /* Replace ofgroup in ofproto's groups hash map with new_ofgroup. */
+    hmap_remove(&ofproto->groups, &ofgroup->hmap_node);
+    hmap_insert(&ofproto->groups, &new_ofgroup->hmap_node,
+                hash_int(new_ofgroup->group_id, 0));
+    if (ofgroup->type != new_ofgroup->type) {
+        ofproto->n_groups[ofgroup->type]--;
+        ofproto->n_groups[new_ofgroup->type]++;
     }
 
- unlock_out:
-    ovs_rwlock_unlock(&ofgroup->rwlock);
+out:
+    ofproto_group_unref(retiring);
     ovs_rwlock_unlock(&ofproto->groups_rwlock);
- free_out:
-    ofproto->ofproto_class->group_dealloc(victim);
     return error;
 }
 
@@ -5725,25 +5724,18 @@ delete_group__(struct ofproto *ofproto, struct ofgroup *ofgroup)
     /* Delete all flow entries containing this group in a group action */
     match_init_catchall(&match);
     flow_mod_init(&fm, &match, 0, NULL, 0, OFPFC_DELETE);
+    fm.delete_reason = OFPRR_GROUP_DELETE;
     fm.out_group = ofgroup->group_id;
     handle_flow_mod__(ofproto, NULL, &fm, NULL);
 
-    /* Must wait until existing readers are done,
-     * while holding the container's write lock at the same time. */
-    ovs_rwlock_wrlock(&ofgroup->rwlock);
     hmap_remove(&ofproto->groups, &ofgroup->hmap_node);
     /* No-one can find this group any more. */
     ofproto->n_groups[ofgroup->type]--;
     ovs_rwlock_unlock(&ofproto->groups_rwlock);
-
-    ofproto->ofproto_class->group_destruct(ofgroup);
-    ofputil_bucket_list_destroy(&ofgroup->buckets);
-    ovs_rwlock_unlock(&ofgroup->rwlock);
-    ovs_rwlock_destroy(&ofgroup->rwlock);
-    ofproto->ofproto_class->group_dealloc(ofgroup);
+    ofproto_group_unref(ofgroup);
 }
 
-/* Implements OFPGC_DELETE. */
+/* Implements OFPGC11_DELETE. */
 static void
 delete_group(struct ofproto *ofproto, uint32_t group_id)
 {
@@ -6256,12 +6248,10 @@ ofopgroup_complete(struct ofopgroup *group)
 
               - The operation's only effect was to update rule->modified. */
         if (!(op->error
-              || ofproto_rule_is_hidden(rule)
+              || rule_is_hidden(rule)
               || (op->type == OFOPERATION_MODIFY
-                  && op->actions
+                  && !op->actions
                   && rule->flow_cookie == op->flow_cookie))) {
-            /* Check that we can just cast from ofoperation_type to
-             * nx_flow_update_event. */
             enum nx_flow_update_event event_type;
 
             switch (op->type) {
@@ -6288,6 +6278,7 @@ ofopgroup_complete(struct ofopgroup *group)
 
         rule->pending = NULL;
 
+        ovs_assert(!op->error || op->type == OFOPERATION_ADD);
         switch (op->type) {
         case OFOPERATION_ADD:
             if (!op->error) {
@@ -6312,42 +6303,22 @@ ofopgroup_complete(struct ofopgroup *group)
             break;
 
         case OFOPERATION_DELETE:
-            ovs_assert(!op->error);
             ofproto_rule_unref(rule);
             op->rule = NULL;
             break;
 
         case OFOPERATION_MODIFY:
-        case OFOPERATION_REPLACE:
-            if (!op->error) {
-                long long int now = time_msec();
+        case OFOPERATION_REPLACE: {
+            long long now = time_msec();
 
-                ovs_mutex_lock(&rule->mutex);
-                rule->modified = now;
-                if (op->type == OFOPERATION_REPLACE) {
-                    rule->created = now;
-                }
-                ovs_mutex_unlock(&rule->mutex);
-            } else {
-                ofproto_rule_change_cookie(ofproto, rule, op->flow_cookie);
-                ovs_mutex_lock(&rule->mutex);
-                rule->idle_timeout = op->idle_timeout;
-                rule->hard_timeout = op->hard_timeout;
-                ovs_mutex_unlock(&rule->mutex);
-                if (op->actions) {
-                    const struct rule_actions *old_actions;
-
-                    ovs_mutex_lock(&rule->mutex);
-                    old_actions = rule_get_actions(rule);
-                    ovsrcu_set(&rule->actions, op->actions);
-                    ovs_mutex_unlock(&rule->mutex);
-
-                    op->actions = NULL;
-                    rule_actions_destroy(old_actions);
-                }
-                rule->flags = op->flags;
+            ovs_mutex_lock(&rule->mutex);
+            rule->modified = now;
+            if (op->type == OFOPERATION_REPLACE) {
+                rule->created = now;
             }
+            ovs_mutex_unlock(&rule->mutex);
             break;
+        }
 
         default:
             OVS_NOT_REACHED();
@@ -6439,19 +6410,12 @@ ofoperation_destroy(struct ofoperation *op)
  * If 'error' is 0, indicating success, the operation will be committed
  * permanently to the flow table.
  *
- * If 'error' is nonzero, then generally the operation will be rolled back:
- *
- *   - If 'op' is an "add flow" operation, ofproto removes the new rule or
- *     restores the original rule.  The caller must have uninitialized any
- *     derived state in the new rule, as in step 5 of in the "Life Cycle" in
- *     ofproto/ofproto-provider.h.  ofoperation_complete() performs steps 6 and
- *     and 7 for the new rule, calling its ->rule_dealloc() function.
- *
- *   - If 'op' is a "modify flow" operation, ofproto restores the original
- *     actions.
- *
- *   - 'op' must not be a "delete flow" operation.  Removing a rule is not
- *     allowed to fail.  It must always succeed.
+ * Flow modifications and deletions must always succeed.  Flow additions may
+ * fail, indicated by nonzero 'error'.  If an "add flow" operation fails, this
+ * function removes the new rule.  The caller must have uninitialized any
+ * derived state in the new rule, as in step 5 of in the "Life Cycle" in
+ * ofproto/ofproto-provider.h.  ofoperation_complete() performs steps 6 and and
+ * 7 for the new rule, calling its ->rule_dealloc() function.
  *
  * Please see the large comment in ofproto/ofproto-provider.h titled
  * "Asynchronous Operation Support" for more information. */
@@ -6461,7 +6425,7 @@ ofoperation_complete(struct ofoperation *op, enum ofperr error)
     struct ofopgroup *group = op->group;
 
     ovs_assert(group->n_running > 0);
-    ovs_assert(!error || op->type != OFOPERATION_DELETE);
+    ovs_assert(!error || op->type == OFOPERATION_ADD);
 
     op->error = error;
     if (!--group->n_running && !list_is_empty(&group->ofproto_node)) {
@@ -6773,6 +6737,14 @@ oftable_init(struct oftable *table)
     classifier_init(&table->cls, flow_segment_u32s);
     table->max_flows = UINT_MAX;
     atomic_init(&table->config, (unsigned int)OFPROTO_TABLE_MISS_DEFAULT);
+
+    fat_rwlock_wrlock(&table->cls.rwlock);
+    classifier_set_prefix_fields(&table->cls, default_prefix_fields,
+                                 ARRAY_SIZE(default_prefix_fields));
+    fat_rwlock_unlock(&table->cls.rwlock);
+
+    atomic_init(&table->n_matched, 0);
+    atomic_init(&table->n_missed, 0);
 }
 
 /* Destroys 'table', including its classifier and eviction groups.
@@ -6902,40 +6874,6 @@ oftable_remove_rule(struct rule *rule)
 {
     oftable_remove_rule__(rule->ofproto, rule);
 }
-
-/* Inserts 'rule' into its oftable, which must not already contain any rule for
- * the same cls_rule. */
-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);
-
-    if (may_expire) {
-        list_insert(&ofproto->expirable, &rule->expirable);
-    }
-
-    cookies_insert(ofproto, rule);
-
-    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);
-    }
-    fat_rwlock_wrlock(&table->cls.rwlock);
-    classifier_insert(&table->cls, CONST_CAST(struct cls_rule *, &rule->cr));
-    fat_rwlock_unlock(&table->cls.rwlock);
-    eviction_group_add_rule(rule);
-}
 \f
 /* unixctl commands. */
 
@@ -7021,6 +6959,8 @@ ofproto_get_vlan_usage(struct ofproto *ofproto, unsigned long int *vlan_bitmap)
         }
         fat_rwlock_unlock(&oftable->cls.rwlock);
     }
+
+    cls_rule_destroy(&target);
 }
 
 /* Returns true if new VLANs have come into use by the flow table since the