Merge 'next' into 'master'.
[sliver-openvswitch.git] / ofproto / ofproto.c
index 0745e12..beab99c 100644 (file)
 
 VLOG_DEFINE_THIS_MODULE(ofproto);
 
-COVERAGE_DEFINE(odp_overflow);
 COVERAGE_DEFINE(ofproto_agg_request);
-COVERAGE_DEFINE(ofproto_costly_flags);
-COVERAGE_DEFINE(ofproto_ctlr_action);
 COVERAGE_DEFINE(ofproto_error);
-COVERAGE_DEFINE(ofproto_expiration);
-COVERAGE_DEFINE(ofproto_expired);
 COVERAGE_DEFINE(ofproto_flows_req);
 COVERAGE_DEFINE(ofproto_flush);
-COVERAGE_DEFINE(ofproto_invalidated);
 COVERAGE_DEFINE(ofproto_no_packet_in);
-COVERAGE_DEFINE(ofproto_ofp2odp);
-COVERAGE_DEFINE(ofproto_packet_in);
 COVERAGE_DEFINE(ofproto_packet_out);
 COVERAGE_DEFINE(ofproto_queue_req);
 COVERAGE_DEFINE(ofproto_recv_openflow);
 COVERAGE_DEFINE(ofproto_reinit_ports);
-COVERAGE_DEFINE(ofproto_unexpected_rule);
 COVERAGE_DEFINE(ofproto_uninstallable);
 COVERAGE_DEFINE(ofproto_update_port);
 
@@ -87,7 +78,6 @@ static void ofproto_flush_flows__(struct ofproto *);
 
 static void ofproto_rule_destroy__(struct rule *);
 static void ofproto_rule_send_removed(struct rule *, uint8_t reason);
-static void ofproto_rule_remove(struct rule *);
 
 static void handle_openflow(struct ofconn *, struct ofpbuf *);
 
@@ -102,7 +92,7 @@ static const struct ofproto_class **ofproto_classes;
 static size_t n_ofproto_classes;
 static size_t allocated_ofproto_classes;
 
-/* Map from dpif name to struct ofproto, for use by unixctl commands. */
+/* Map from datapath name to struct ofproto, for use by unixctl commands. */
 static struct hmap all_ofprotos = HMAP_INITIALIZER(&all_ofprotos);
 
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
@@ -271,7 +261,8 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
     ofproto->netdev_monitor = netdev_monitor_create();
     hmap_init(&ofproto->ports);
     shash_init(&ofproto->port_by_name);
-    classifier_init(&ofproto->cls);
+    ofproto->tables = NULL;
+    ofproto->n_tables = 0;
     ofproto->connmgr = connmgr_create(ofproto, datapath_name, datapath_name);
 
     error = ofproto->ofproto_class->construct(ofproto);
@@ -281,6 +272,7 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
         ofproto_destroy__(ofproto);
         return error;
     }
+    assert(ofproto->n_tables > 0);
 
     ofproto->datapath_id = pick_datapath_id(ofproto);
     VLOG_INFO("using datapath ID %016"PRIx64, ofproto->datapath_id);
@@ -598,6 +590,8 @@ ofproto_get_snoops(const struct ofproto *ofproto, struct sset *snoops)
 static void
 ofproto_destroy__(struct ofproto *ofproto)
 {
+    size_t i;
+
     connmgr_destroy(ofproto->connmgr);
 
     hmap_remove(&all_ofprotos, &ofproto->hmap_node);
@@ -610,7 +604,11 @@ ofproto_destroy__(struct ofproto *ofproto)
     netdev_monitor_destroy(ofproto->netdev_monitor);
     hmap_destroy(&ofproto->ports);
     shash_destroy(&ofproto->port_by_name);
-    classifier_destroy(&ofproto->cls);
+
+    for (i = 0; i < ofproto->n_tables; i++) {
+        classifier_destroy(&ofproto->tables[i]);
+    }
+    free(ofproto->tables);
 
     ofproto->ofproto_class->dealloc(ofproto);
 }
@@ -626,7 +624,6 @@ ofproto_destroy(struct ofproto *p)
 
     ofproto_flush_flows__(p);
     HMAP_FOR_EACH_SAFE (ofport, next_ofport, hmap_node, &p->ports) {
-        hmap_remove(&p->ports, &ofport->hmap_node);
         ofport_destroy(ofport);
     }
 
@@ -873,7 +870,7 @@ ofproto_port_del(struct ofproto *ofproto, uint16_t ofp_port)
     return error;
 }
 
-/* Adds a flow to the OpenFlow flow table in 'p' that matches 'cls_rule' and
+/* Adds a flow to OpenFlow flow table 0 in 'p' that matches 'cls_rule' and
  * performs the 'n_actions' actions in 'actions'.  The new flow will not
  * timeout.
  *
@@ -881,7 +878,9 @@ ofproto_port_del(struct ofproto *ofproto, uint16_t ofp_port)
  * (0...65535, inclusive) then the flow will be visible to OpenFlow
  * controllers; otherwise, it will be hidden.
  *
- * The caller retains ownership of 'cls_rule' and 'actions'. */
+ * The caller retains ownership of 'cls_rule' and 'actions'.
+ *
+ * This is a helper function for in-band control and fail-open. */
 void
 ofproto_add_flow(struct ofproto *p, const struct cls_rule *cls_rule,
                  const union ofp_action *actions, size_t n_actions)
@@ -890,23 +889,24 @@ ofproto_add_flow(struct ofproto *p, const struct cls_rule *cls_rule,
     rule_create(p, cls_rule, actions, n_actions, 0, 0, 0, false, &rule);
 }
 
+/* Searches for a rule with matching criteria exactly equal to 'target' in
+ * ofproto's table 0 and, if it finds one, deletes it.
+ *
+ * This is a helper function for in-band control and fail-open. */
 void
 ofproto_delete_flow(struct ofproto *ofproto, const struct cls_rule *target)
 {
     struct rule *rule;
 
-    rule = rule_from_cls_rule(classifier_find_rule_exactly(&ofproto->cls,
-                                                           target));
-    if (rule) {
-        ofproto_rule_remove(rule);
-    }
+    rule = rule_from_cls_rule(classifier_find_rule_exactly(
+                                  &ofproto->tables[0], target));
+    ofproto_rule_destroy(rule);
 }
 
 static void
 ofproto_flush_flows__(struct ofproto *ofproto)
 {
-    struct rule *rule, *next_rule;
-    struct cls_cursor cursor;
+    size_t i;
 
     COVERAGE_INC(ofproto_flush);
 
@@ -914,12 +914,19 @@ ofproto_flush_flows__(struct ofproto *ofproto)
         ofproto->ofproto_class->flush(ofproto);
     }
 
-    cls_cursor_init(&cursor, &ofproto->cls, NULL);
-    CLS_CURSOR_FOR_EACH_SAFE (rule, next_rule, cr, &cursor) {
-        ofproto_rule_remove(rule);
+    for (i = 0; i < ofproto->n_tables; i++) {
+        struct rule *rule, *next_rule;
+        struct cls_cursor cursor;
+
+        cls_cursor_init(&cursor, &ofproto->tables[i], NULL);
+        CLS_CURSOR_FOR_EACH_SAFE (rule, next_rule, cr, &cursor) {
+            ofproto_rule_destroy(rule);
+        }
     }
 }
 
+/* Deletes all of the flows from all of ofproto's flow tables, then
+ * reintroduces rules required by in-band control and fail open. */
 void
 ofproto_flush_flows(struct ofproto *ofproto)
 {
@@ -1162,21 +1169,25 @@ update_port(struct ofproto *ofproto, const char *name)
     if (netdev) {
         port = ofproto_get_port(ofproto, ofproto_port.ofp_port);
         if (port && !strcmp(netdev_get_name(port->netdev), name)) {
+            struct netdev *old_netdev = port->netdev;
+
             /* 'name' hasn't changed location.  Any properties changed? */
             if (!ofport_equal(&port->opp, &opp)) {
                 ofport_modified(port, &opp);
             }
 
-            /* Install the newly opened netdev in case it has changed. */
+            /* Install the newly opened netdev in case it has changed.
+             * Don't close the old netdev yet in case port_modified has to
+             * remove a retained reference to it.*/
             netdev_monitor_remove(ofproto->netdev_monitor, port->netdev);
             netdev_monitor_add(ofproto->netdev_monitor, netdev);
-
-            netdev_close(port->netdev);
             port->netdev = netdev;
 
             if (port->ofproto->ofproto_class->port_modified) {
                 port->ofproto->ofproto_class->port_modified(port);
             }
+
+            netdev_close(old_netdev);
         } else {
             /* If 'port' is nonnull then its name differs from 'name' and thus
              * we should delete it.  If we think there's a port named 'name'
@@ -1278,15 +1289,14 @@ ofproto_rule_destroy__(struct rule *rule)
     rule->ofproto->ofproto_class->rule_dealloc(rule);
 }
 
-/* Destroys 'rule' and iterates through all of its facets and revalidates them,
- * destroying any that no longer has a rule (which is probably all of them).
- *
- * The caller must have already removed 'rule' from the classifier. */
+/* Destroys 'rule' and removes it from the flow table and the datapath. */
 void
 ofproto_rule_destroy(struct rule *rule)
 {
-    rule->ofproto->ofproto_class->rule_destruct(rule);
-    ofproto_rule_destroy__(rule);
+    if (rule) {
+        rule->ofproto->ofproto_class->rule_destruct(rule);
+        ofproto_rule_destroy__(rule);
+    }
 }
 
 /* Returns true if 'rule' has an OpenFlow OFPAT_OUTPUT or OFPAT_ENQUEUE action
@@ -1310,15 +1320,9 @@ rule_has_out_port(const struct rule *rule, ovs_be16 out_port)
     return false;
 }
 
-struct rule *
-ofproto_rule_lookup(struct ofproto *ofproto, const struct flow *flow)
-{
-    return rule_from_cls_rule(classifier_lookup(&ofproto->cls, flow));
-}
-
 /* Executes the actions indicated by 'rule' on 'packet' and credits 'rule''s
- * statistics (or the statistics for one of its facets) appropriately.
- * 'packet' must have at least sizeof(struct ofp_packet_in) bytes of headroom.
+ * statistics appropriately.  'packet' must have at least sizeof(struct
+ * ofp_packet_in) bytes of headroom.
  *
  * 'packet' doesn't necessarily have to match 'rule'.  'rule' will be credited
  * with statistics for 'packet' either way.
@@ -1335,20 +1339,6 @@ rule_execute(struct rule *rule, uint16_t in_port, struct ofpbuf *packet)
     return rule->ofproto->ofproto_class->rule_execute(rule, &flow, packet);
 }
 
-/* Remove 'rule' from 'ofproto' and free up the associated memory:
- *
- *   - Removes 'rule' from the classifier.
- *
- *   - If 'rule' has facets, revalidates them (and possibly uninstalls and
- *     destroys them), via rule_destroy().
- */
-void
-ofproto_rule_remove(struct rule *rule)
-{
-    rule->ofproto->ofproto_class->rule_remove(rule);
-    ofproto_rule_destroy(rule);
-}
-
 /* Returns true if 'rule' should be hidden from the controller.
  *
  * Rules with priority higher than UINT16_MAX are set up by ofproto itself
@@ -1385,25 +1375,22 @@ handle_features_request(struct ofconn *ofconn, const struct ofp_header *oh)
     struct ofp_switch_features *osf;
     struct ofpbuf *buf;
     struct ofport *port;
+    bool arp_match_ip;
+    uint32_t actions;
+
+    ofproto->ofproto_class->get_features(ofproto, &arp_match_ip, &actions);
+    assert(actions & (1 << OFPAT_OUTPUT)); /* sanity check */
 
     osf = make_openflow_xid(sizeof *osf, OFPT_FEATURES_REPLY, oh->xid, &buf);
     osf->datapath_id = htonll(ofproto->datapath_id);
     osf->n_buffers = htonl(pktbuf_capacity());
-    osf->n_tables = 2;
+    osf->n_tables = ofproto->n_tables;
     osf->capabilities = htonl(OFPC_FLOW_STATS | OFPC_TABLE_STATS |
-                              OFPC_PORT_STATS | OFPC_ARP_MATCH_IP);
-    osf->actions = htonl((1u << OFPAT_OUTPUT) |
-                         (1u << OFPAT_SET_VLAN_VID) |
-                         (1u << OFPAT_SET_VLAN_PCP) |
-                         (1u << OFPAT_STRIP_VLAN) |
-                         (1u << OFPAT_SET_DL_SRC) |
-                         (1u << OFPAT_SET_DL_DST) |
-                         (1u << OFPAT_SET_NW_SRC) |
-                         (1u << OFPAT_SET_NW_DST) |
-                         (1u << OFPAT_SET_NW_TOS) |
-                         (1u << OFPAT_SET_TP_SRC) |
-                         (1u << OFPAT_SET_TP_DST) |
-                         (1u << OFPAT_ENQUEUE));
+                              OFPC_PORT_STATS);
+    if (arp_match_ip) {
+        osf->capabilities |= htonl(OFPC_ARP_MATCH_IP);
+    }
+    osf->actions = htonl(actions);
 
     HMAP_FOR_EACH (port, hmap_node, &ofproto->ports) {
         ofpbuf_put(buf, &port->opp, sizeof port->opp);
@@ -1468,7 +1455,7 @@ handle_set_config(struct ofconn *ofconn, const struct ofp_switch_config *osc)
  *
  * The log message mentions 'msg_type'. */
 static int
-reject_slave_controller(struct ofconn *ofconn, const const char *msg_type)
+reject_slave_controller(struct ofconn *ofconn, const char *msg_type)
 {
     if (ofconn_get_type(ofconn) == OFCONN_PRIMARY
         && ofconn_get_role(ofconn) == NX_ROLE_SLAVE) {
@@ -1684,19 +1671,20 @@ handle_table_stats_request(struct ofconn *ofconn,
     struct ofproto *p = ofconn_get_ofproto(ofconn);
     struct ofp_table_stats *ots;
     struct ofpbuf *msg;
+    size_t i;
+
+    msg = start_ofp_stats_reply(request, sizeof *ots * p->n_tables);
 
-    msg = start_ofp_stats_reply(request, sizeof *ots * 2);
+    ots = ofpbuf_put_zeros(msg, sizeof *ots * p->n_tables);
+    for (i = 0; i < p->n_tables; i++) {
+        ots[i].table_id = i;
+        sprintf(ots[i].name, "table%d", i);
+        ots[i].wildcards = htonl(OFPFW_ALL);
+        ots[i].max_entries = htonl(1000000); /* An arbitrary big number. */
+        ots[i].active_count = htonl(classifier_count(&p->tables[i]));
+    }
 
-    /* Classifier table. */
-    ots = append_ofp_stats_reply(sizeof *ots, ofconn, &msg);
-    memset(ots, 0, sizeof *ots);
-    strcpy(ots->name, "classifier");
-    ots->wildcards = (ofconn_get_flow_format(ofconn) == NXFF_OPENFLOW10
-                      ? htonl(OFPFW_ALL) : htonl(OVSFW_ALL));
-    ots->max_entries = htonl(1024 * 1024); /* An arbitrary big number. */
-    ots->active_count = htonl(classifier_count(&p->cls));
-    put_32aligned_be64(&ots->lookup_count, htonll(0));  /* XXX */
-    put_32aligned_be64(&ots->matched_count, htonll(0)); /* XXX */
+    p->ofproto_class->get_tables(p, ots);
 
     ofconn_send_reply(ofconn, msg);
     return 0;
@@ -1781,7 +1769,6 @@ put_ofp_flow_stats(struct ofconn *ofconn, struct rule *rule,
     struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
     struct ofp_flow_stats *ofs;
     uint64_t packet_count, byte_count;
-    ovs_be64 cookie;
     size_t act_len, len;
 
     if (rule_is_hidden(rule) || !rule_has_out_port(rule, out_port)) {
@@ -1795,11 +1782,10 @@ put_ofp_flow_stats(struct ofconn *ofconn, struct rule *rule,
 
     ofs = append_ofp_stats_reply(len, ofconn, replyp);
     ofs->length = htons(len);
-    ofs->table_id = 0;
+    ofs->table_id = rule->table_id;
     ofs->pad = 0;
-    ofputil_cls_rule_to_match(&rule->cr, ofconn_get_flow_format(ofconn),
-                              &ofs->match, rule->flow_cookie, &cookie);
-    put_32aligned_be64(&ofs->cookie, cookie);
+    ofputil_cls_rule_to_match(&rule->cr, &ofs->match);
+    put_32aligned_be64(&ofs->cookie, rule->flow_cookie);
     calc_flow_duration(rule->created, &ofs->duration_sec, &ofs->duration_nsec);
     ofs->priority = htons(rule->cr.priority);
     ofs->idle_timeout = htons(rule->idle_timeout);
@@ -1812,38 +1798,68 @@ put_ofp_flow_stats(struct ofconn *ofconn, struct rule *rule,
     }
 }
 
-static bool
-is_valid_table(uint8_t table_id)
+static struct classifier *
+first_matching_table(struct ofproto *ofproto, uint8_t table_id)
 {
-    if (table_id == 0 || table_id == 0xff) {
-        return true;
+    if (table_id == 0xff) {
+        return &ofproto->tables[0];
+    } else if (table_id < ofproto->n_tables) {
+        return &ofproto->tables[table_id];
     } else {
         /* It would probably be better to reply with an error but there doesn't
          * seem to be any appropriate value, so that might just be
          * confusing. */
         VLOG_WARN_RL(&rl, "controller asked for invalid table %"PRIu8,
                      table_id);
-        return false;
+        return NULL;
     }
 }
 
+static struct classifier *
+next_matching_table(struct ofproto *ofproto,
+                    struct classifier *cls, uint8_t table_id)
+{
+    return (table_id == 0xff && cls != &ofproto->tables[ofproto->n_tables - 1]
+            ? cls + 1
+            : NULL);
+}
+
+/* Assigns CLS to each classifier table, in turn, that matches TABLE_ID in
+ * OFPROTO:
+ *
+ *   - If TABLE_ID is 0xff, this iterates over every classifier table in
+ *     OFPROTO.
+ *
+ *   - If TABLE_ID is the number of a table in OFPROTO, then the loop iterates
+ *     only once, for that table.
+ *
+ *   - Otherwise, TABLE_ID isn't valid for OFPROTO, so ofproto logs a warning
+ *     and does not enter the loop at all.
+ *
+ * All parameters are evaluated multiple times.
+ */
+#define FOR_EACH_MATCHING_TABLE(CLS, TABLE_ID, OFPROTO)         \
+    for ((CLS) = first_matching_table(OFPROTO, TABLE_ID);       \
+         (CLS) != NULL;                                         \
+         (CLS) = next_matching_table(OFPROTO, CLS, TABLE_ID))
+
 static int
 handle_flow_stats_request(struct ofconn *ofconn, const struct ofp_header *oh)
 {
     const struct ofp_flow_stats_request *fsr = ofputil_stats_body(oh);
     struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+    struct classifier *cls;
+    struct cls_rule target;
     struct ofpbuf *reply;
 
     COVERAGE_INC(ofproto_flows_req);
     reply = start_ofp_stats_reply(oh, 1024);
-    if (is_valid_table(fsr->table_id)) {
+    ofputil_cls_rule_from_match(&fsr->match, 0, &target);
+    FOR_EACH_MATCHING_TABLE (cls, fsr->table_id, ofproto) {
         struct cls_cursor cursor;
-        struct cls_rule target;
         struct rule *rule;
 
-        ofputil_cls_rule_from_match(&fsr->match, 0, NXFF_OPENFLOW10, 0,
-                                    &target);
-        cls_cursor_init(&cursor, &ofproto->cls, &target);
+        cls_cursor_init(&cursor, cls, &target);
         CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
             put_ofp_flow_stats(ofconn, rule, fsr->out_port, &reply);
         }
@@ -1898,6 +1914,7 @@ handle_nxst_flow(struct ofconn *ofconn, const struct ofp_header *oh)
 {
     struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
     struct nx_flow_stats_request *nfsr;
+    struct classifier *cls;
     struct cls_rule target;
     struct ofpbuf *reply;
     struct ofpbuf b;
@@ -1917,11 +1934,11 @@ handle_nxst_flow(struct ofconn *ofconn, const struct ofp_header *oh)
 
     COVERAGE_INC(ofproto_flows_req);
     reply = start_nxstats_reply(&nfsr->nsm, 1024);
-    if (is_valid_table(nfsr->table_id)) {
+    FOR_EACH_MATCHING_TABLE (cls, nfsr->table_id, ofproto) {
         struct cls_cursor cursor;
         struct rule *rule;
 
-        cls_cursor_init(&cursor, &ofproto->cls, &target);
+        cls_cursor_init(&cursor, cls, &target);
         CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
             put_nx_flow_stats(ofconn, rule, nfsr->out_port, &reply);
         }
@@ -1940,6 +1957,9 @@ flow_stats_ds(struct rule *rule, struct ds *results)
     rule->ofproto->ofproto_class->rule_get_stats(rule,
                                                  &packet_count, &byte_count);
 
+    if (rule->table_id != 0) {
+        ds_put_format(results, "table_id=%"PRIu8", ", rule->table_id);
+    }
     ds_put_format(results, "duration=%llds, ",
                   (time_msec() - rule->created) / 1000);
     ds_put_format(results, "priority=%u, ", rule->cr.priority);
@@ -1960,12 +1980,16 @@ flow_stats_ds(struct rule *rule, struct ds *results)
 void
 ofproto_get_all_flows(struct ofproto *p, struct ds *results)
 {
-    struct cls_cursor cursor;
-    struct rule *rule;
+    struct classifier *cls;
+
+    for (cls = &p->tables[0]; cls < &p->tables[p->n_tables]; cls++) {
+        struct cls_cursor cursor;
+        struct rule *rule;
 
-    cls_cursor_init(&cursor, &p->cls, NULL);
-    CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
-        flow_stats_ds(rule, results);
+        cls_cursor_init(&cursor, cls, NULL);
+        CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
+            flow_stats_ds(rule, results);
+        }
     }
 }
 
@@ -1985,15 +2009,16 @@ query_aggregate_stats(struct ofproto *ofproto, struct cls_rule *target,
 {
     uint64_t total_packets = 0;
     uint64_t total_bytes = 0;
+    struct classifier *cls;
     int n_flows = 0;
 
     COVERAGE_INC(ofproto_agg_request);
 
-    if (is_valid_table(table_id)) {
+    FOR_EACH_MATCHING_TABLE (cls, table_id, ofproto) {
         struct cls_cursor cursor;
         struct rule *rule;
 
-        cls_cursor_init(&cursor, &ofproto->cls, target);
+        cls_cursor_init(&cursor, cls, target);
         CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
             if (!rule_is_hidden(rule) && rule_has_out_port(rule, out_port)) {
                 uint64_t packet_count;
@@ -2025,8 +2050,7 @@ handle_aggregate_stats_request(struct ofconn *ofconn,
     struct cls_rule target;
     struct ofpbuf *msg;
 
-    ofputil_cls_rule_from_match(&request->match, 0, NXFF_OPENFLOW10, 0,
-                                &target);
+    ofputil_cls_rule_from_match(&request->match, 0, &target);
 
     msg = start_ofp_stats_reply(oh, sizeof *reply);
     reply = append_ofp_stats_reply(sizeof *reply, ofconn, &msg);
@@ -2177,9 +2201,14 @@ add_flow(struct ofconn *ofconn, struct flow_mod *fm)
     int buf_err;
     int error;
 
-    if (fm->flags & OFPFF_CHECK_OVERLAP
-        && classifier_rule_overlaps(&p->cls, &fm->cr)) {
-        return ofp_mkerr(OFPET_FLOW_MOD_FAILED, OFPFMFC_OVERLAP);
+    if (fm->flags & OFPFF_CHECK_OVERLAP) {
+        struct classifier *cls;
+
+        FOR_EACH_MATCHING_TABLE (cls, fm->table_id, p) {
+            if (classifier_rule_overlaps(cls, &fm->cr)) {
+                return ofp_mkerr(OFPET_FLOW_MOD_FAILED, OFPFMFC_OVERLAP);
+            }
+        }
     }
 
     buf_err = ofconn_pktbuf_retrieve(ofconn, fm->buffer_id, &packet, &in_port);
@@ -2198,10 +2227,35 @@ add_flow(struct ofconn *ofconn, struct flow_mod *fm)
     return buf_err;
 }
 
-static struct rule *
-find_flow_strict(struct ofproto *p, const struct flow_mod *fm)
+/* Searches 'p' for an exact match for 'fm', in the table or tables indicated
+ * by fm->table_id.  Returns 0 if no match was found, 1 if exactly one match
+ * was found, 2 if more than one match was found.  If exactly one match is
+ * found, sets '*rulep' to the match, otherwise to NULL.
+ *
+ * This implements the rules for "strict" matching explained in the comment on
+ * struct nxt_flow_mod_table_id in nicira-ext.h.
+ *
+ * Ignores hidden rules. */
+static int
+find_flow_strict(struct ofproto *p, const struct flow_mod *fm,
+                 struct rule **rulep)
 {
-    return rule_from_cls_rule(classifier_find_rule_exactly(&p->cls, &fm->cr));
+    struct classifier *cls;
+
+    *rulep = NULL;
+    FOR_EACH_MATCHING_TABLE (cls, fm->table_id, p) {
+        struct rule *rule;
+
+        rule = rule_from_cls_rule(classifier_find_rule_exactly(cls, &fm->cr));
+        if (rule && !rule_is_hidden(rule)) {
+            if (*rulep) {
+                *rulep = NULL;
+                return 2;
+            }
+            *rulep = rule;
+        }
+    }
+    return *rulep != NULL;
 }
 
 static int
@@ -2244,19 +2298,23 @@ modify_flows_loose(struct ofconn *ofconn, struct flow_mod *fm)
 {
     struct ofproto *p = ofconn_get_ofproto(ofconn);
     struct rule *match = NULL;
-    struct cls_cursor cursor;
-    struct rule *rule;
+    struct classifier *cls;
     int error;
 
     error = 0;
-    cls_cursor_init(&cursor, &p->cls, &fm->cr);
-    CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
-        if (!rule_is_hidden(rule)) {
-            int retval = modify_flow(fm, rule);
-            if (!retval) {
-                match = rule;
-            } else {
-                error = retval;
+    FOR_EACH_MATCHING_TABLE (cls, fm->table_id, p) {
+        struct cls_cursor cursor;
+        struct rule *rule;
+
+        cls_cursor_init(&cursor, cls, &fm->cr);
+        CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
+            if (!rule_is_hidden(rule)) {
+                int retval = modify_flow(fm, rule);
+                if (!retval) {
+                    match = rule;
+                } else {
+                    error = retval;
+                }
             }
         }
     }
@@ -2283,15 +2341,25 @@ static int
 modify_flow_strict(struct ofconn *ofconn, struct flow_mod *fm)
 {
     struct ofproto *p = ofconn_get_ofproto(ofconn);
-    struct rule *rule = find_flow_strict(p, fm);
-    if (rule && !rule_is_hidden(rule)) {
-        int error = modify_flow(fm, rule);
+    struct rule *rule;
+    int error;
+
+    switch (find_flow_strict(p, fm, &rule)) {
+    case 0:
+        return add_flow(ofconn, fm);
+
+    case 1:
+        error = modify_flow(fm, rule);
         if (!error) {
             error = send_buffered_packet(ofconn, rule, fm->buffer_id);
         }
         return error;
-    } else {
-        return add_flow(ofconn, fm);
+
+    case 2:
+        return 0;
+
+    default:
+        NOT_REACHED();
     }
 }
 
@@ -2336,12 +2404,16 @@ static void delete_flow(struct rule *, ovs_be16 out_port);
 static void
 delete_flows_loose(struct ofproto *p, const struct flow_mod *fm)
 {
-    struct rule *rule, *next_rule;
-    struct cls_cursor cursor;
+    struct classifier *cls;
 
-    cls_cursor_init(&cursor, &p->cls, &fm->cr);
-    CLS_CURSOR_FOR_EACH_SAFE (rule, next_rule, cr, &cursor) {
-        delete_flow(rule, htons(fm->out_port));
+    FOR_EACH_MATCHING_TABLE (cls, fm->table_id, p) {
+        struct rule *rule, *next_rule;
+        struct cls_cursor cursor;
+
+        cls_cursor_init(&cursor, cls, &fm->cr);
+        CLS_CURSOR_FOR_EACH_SAFE (rule, next_rule, cr, &cursor) {
+            delete_flow(rule, htons(fm->out_port));
+        }
     }
 }
 
@@ -2349,8 +2421,8 @@ delete_flows_loose(struct ofproto *p, const struct flow_mod *fm)
 static void
 delete_flow_strict(struct ofproto *p, struct flow_mod *fm)
 {
-    struct rule *rule = find_flow_strict(p, fm);
-    if (rule) {
+    struct rule *rule;
+    if (find_flow_strict(p, fm, &rule) == 1) {
         delete_flow(rule, htons(fm->out_port));
     }
 }
@@ -2375,7 +2447,7 @@ delete_flow(struct rule *rule, ovs_be16 out_port)
     }
 
     ofproto_rule_send_removed(rule, OFPRR_DELETE);
-    ofproto_rule_remove(rule);
+    ofproto_rule_destroy(rule);
 }
 
 static void
@@ -2409,7 +2481,7 @@ ofproto_rule_expire(struct rule *rule, uint8_t reason)
 {
     assert(reason == OFPRR_HARD_TIMEOUT || reason == OFPRR_IDLE_TIMEOUT);
     ofproto_rule_send_removed(rule, reason);
-    ofproto_rule_remove(rule);
+    ofproto_rule_destroy(rule);
 }
 \f
 static int
@@ -2424,7 +2496,8 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
         return error;
     }
 
-    error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_flow_format(ofconn));
+    error = ofputil_decode_flow_mod(&fm, oh,
+                                    ofconn_get_flow_mod_table_id(ofconn));
     if (error) {
         return error;
     }
@@ -2456,23 +2529,14 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
         return 0;
 
     default:
+        if (fm.command > 0xff) {
+            VLOG_WARN_RL(&rl, "flow_mod has explicit table_id but "
+                         "flow_mod_table_id extension is not enabled");
+        }
         return ofp_mkerr(OFPET_FLOW_MOD_FAILED, OFPFMFC_BAD_COMMAND);
     }
 }
 
-static int
-handle_tun_id_from_cookie(struct ofconn *ofconn, const struct ofp_header *oh)
-{
-    const struct nxt_tun_id_cookie *msg
-        = (const struct nxt_tun_id_cookie *) oh;
-    enum nx_flow_format flow_format;
-
-    flow_format = msg->set ? NXFF_TUN_ID_FROM_COOKIE : NXFF_OPENFLOW10;
-    ofconn_set_flow_format(ofconn, flow_format);
-
-    return 0;
-}
-
 static int
 handle_role_request(struct ofconn *ofconn, const struct ofp_header *oh)
 {
@@ -2504,6 +2568,17 @@ handle_role_request(struct ofconn *ofconn, const struct ofp_header *oh)
     return 0;
 }
 
+static int
+handle_nxt_flow_mod_table_id(struct ofconn *ofconn,
+                             const struct ofp_header *oh)
+{
+    const struct nxt_flow_mod_table_id *msg
+        = (const struct nxt_flow_mod_table_id *) oh;
+
+    ofconn_set_flow_mod_table_id(ofconn, msg->set != 0);
+    return 0;
+}
+
 static int
 handle_nxt_set_flow_format(struct ofconn *ofconn, const struct ofp_header *oh)
 {
@@ -2513,7 +2588,6 @@ handle_nxt_set_flow_format(struct ofconn *ofconn, const struct ofp_header *oh)
 
     format = ntohl(msg->format);
     if (format == NXFF_OPENFLOW10
-        || format == NXFF_TUN_ID_FROM_COOKIE
         || format == NXFF_NXM) {
         ofconn_set_flow_format(ofconn, format);
         return 0;
@@ -2578,12 +2652,12 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
         return 0;
 
         /* Nicira extension requests. */
-    case OFPUTIL_NXT_TUN_ID_FROM_COOKIE:
-        return handle_tun_id_from_cookie(ofconn, oh);
-
     case OFPUTIL_NXT_ROLE_REQUEST:
         return handle_role_request(ofconn, oh);
 
+    case OFPUTIL_NXT_FLOW_MOD_TABLE_ID:
+        return handle_nxt_flow_mod_table_id(ofconn, oh);
+
     case OFPUTIL_NXT_SET_FLOW_FORMAT:
         return handle_nxt_set_flow_format(ofconn, oh);