[ovs-dev] [next3 4/4] Implement basic multiple table support.

Ben Pfaff blp at nicira.com
Wed Apr 27 21:37:02 UTC 2011


This implements basic multiple table support in ofproto and supporting
libraries and utilities. The design is the same as the one that has been
on the Open vSwitch "wdp" branch for a long time.  There is no support for
multiple tables in the software switch implementation (ofproto-dpif), only
a set of hooks for other switch implementations to use.

To allow controllers to add flows in a particular table, Open vSwitch adds
an OpenFlow 1.0 extension called NXT_FLOW_MOD_TABLE_ID.
---
 include/openflow/nicira-ext.h |   62 ++++++++-
 lib/learning-switch.c         |    1 +
 lib/ofp-parse.c               |   34 +++--
 lib/ofp-parse.h               |    9 +-
 lib/ofp-print.c               |   16 ++-
 lib/ofp-util.c                |   75 +++++++++--
 lib/ofp-util.h                |   10 +-
 ofproto/connmgr.c             |   20 +++
 ofproto/connmgr.h             |    3 +
 ofproto/ofproto-dpif.c        |   57 ++++++++-
 ofproto/ofproto.c             |  289 +++++++++++++++++++++++++++++------------
 ofproto/private.h             |   78 ++++++++++-
 tests/ovs-ofctl.at            |    7 +-
 utilities/ovs-controller.c    |    7 +-
 utilities/ovs-ofctl.8.in      |   27 +++-
 utilities/ovs-ofctl.c         |   27 +++-
 16 files changed, 568 insertions(+), 154 deletions(-)

diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 292a987..7df978b 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -108,9 +108,7 @@ enum nx_flow_mod_failed_code {
     NXFMFC_HARDWARE = 0x100,
 
     /* A nonexistent table ID was specified in the "command" field of struct
-     * ofp_flow_mod, when the nxt_flow_mod_table_id extension is enabled.
-     * (This extension is not yet implemented on this branch of Open
-     * vSwitch.) */
+     * ofp_flow_mod, when the nxt_flow_mod_table_id extension is enabled. */
     NXFMFC_BAD_TABLE_ID = 0x101
 };
 
@@ -146,8 +144,17 @@ enum nicira_type {
     NXT_ROLE_REQUEST,
     NXT_ROLE_REPLY,
 
+    /* Use the upper 8 bits of the 'command' member in struct ofp_flow_mod to
+     * designate the table to which a flow is to be added?  See the big comment
+     * on struct nxt_flow_mod_table_id for more information.
+     *
+     * A screwup caused this extension to be assigned the same value as
+     * NXT_SET_FLOW_FORMAT (see below).  The two extensions do have different
+     * lengths, so they can still be distinguished. */
+    NXT_FLOW_MOD_TABLE_ID,
+
     /* Flexible flow specification (aka NXM = Nicira Extended Match). */
-    NXT_SET_FLOW_FORMAT,        /* Set flow format. */
+    NXT_SET_FLOW_FORMAT = NXT_FLOW_MOD_TABLE_ID, /* Set flow format. */
     NXT_FLOW_MOD,               /* Analogous to OFPT_FLOW_MOD. */
     NXT_FLOW_REMOVED            /* Analogous to OFPT_FLOW_REMOVED. */
 };
@@ -180,6 +187,53 @@ struct nxt_tun_id_cookie {
 };
 OFP_ASSERT(sizeof(struct nxt_tun_id_cookie) == 24);
 
+/* This command enables or disables an Open vSwitch extension that allows a
+ * controller to specify the OpenFlow table to which a flow should be added,
+ * instead of having the switch decide which table is most appropriate as
+ * required by OpenFlow 1.0.  By default, the extension is disabled.
+ *
+ * When this feature is enabled, Open vSwitch treats struct ofp_flow_mod's
+ * 16-bit 'command' member as two separate fields.  The upper 8 bits are used
+ * as the table ID, the lower 8 bits specify the command as usual.  A table ID
+ * of 0xff is treated like a wildcarded table ID.
+ *
+ * The specific treatment of the table ID depends on the type of flow mod:
+ *
+ *    - OFPFC_ADD: Given a specific table ID, the flow is always placed in that
+ *      table.  If an identical flow already exists in that table only, then it
+ *      is replaced.  If the flow cannot be placed in the specified table,
+ *      either because the table is full or because the table cannot support
+ *      flows of the given type, the switch replies with an
+ *      OFPFMFC_ALL_TABLES_FULL error.  (A controller can distinguish these
+ *      cases by comparing the current and maximum number of entries reported
+ *      in ofp_table_stats.)
+ *
+ *      If the table ID is wildcarded, the switch picks an appropriate table
+ *      itself.  If an identical flow already exist in the selected flow table,
+ *      then it is replaced.  The choice of table might depend on the flows
+ *      that are already in the switch; for example, if one table fills up then
+ *      the switch might fall back to another one.
+ *
+ *    - OFPFC_MODIFY, OFPFC_DELETE: Given a specific table ID, only flows
+ *      within that table are matched and modified or deleted.  If the table ID
+ *      is wildcarded, flows within any table may be matched and modified or
+ *      deleted.
+ *
+ *    - OFPFC_MODIFY_STRICT, OFPFC_DELETE_STRICT: Given a specific table ID,
+ *      only a flow within that table may be matched and modified or deleted.
+ *      If the table ID is wildcarded and exactly one flow within any table
+ *      matches, then it is modified or deleted; if flows in more than one
+ *      table match, then none is modified or deleted.
+ */
+struct nxt_flow_mod_table_id {
+    struct ofp_header header;
+    uint32_t vendor;            /* NX_VENDOR_ID. */
+    uint32_t subtype;           /* NXT_FLOW_MOD_TABLE_ID. */
+    uint8_t set;                /* Nonzero to enable, zero to disable. */
+    uint8_t pad[7];
+};
+OFP_ASSERT(sizeof(struct nxt_flow_mod_table_id) == 24);
+
 /* Configures the "role" of the sending controller.  The default role is:
  *
  *    - Other (NX_ROLE_OTHER), which allows the controller access to all
diff --git a/lib/learning-switch.c b/lib/learning-switch.c
index dc9af77..75e04df 100644
--- a/lib/learning-switch.c
+++ b/lib/learning-switch.c
@@ -241,6 +241,7 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn,
     case OFPUTIL_NXT_TUN_ID_FROM_COOKIE:
     case OFPUTIL_NXT_ROLE_REQUEST:
     case OFPUTIL_NXT_ROLE_REPLY:
+    case OFPUTIL_NXT_FLOW_MOD_TABLE_ID:
     case OFPUTIL_NXT_SET_FLOW_FORMAT:
     case OFPUTIL_NXT_FLOW_MOD:
     case OFPUTIL_NXT_FLOW_REMOVED:
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 4fadcf3..c7ff4c3 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -729,17 +729,14 @@ parse_reg_value(struct cls_rule *rule, int reg_idx, const char *value)
  * man page) into 'pf'.  If 'actions' is specified, an action must be in
  * 'string' and may be expanded or reallocated. */
 void
-parse_ofp_str(struct flow_mod *fm, uint8_t *table_idx,
-              struct ofpbuf *actions, char *string)
+parse_ofp_str(struct flow_mod *fm, struct ofpbuf *actions, char *string)
 {
     char *save_ptr = NULL;
     char *name;
 
-    if (table_idx) {
-        *table_idx = 0xff;
-    }
     cls_rule_init_catchall(&fm->cr, OFP_DEFAULT_PRIORITY);
     fm->cookie = htonll(0);
+    fm->table_id = 0xff;
     fm->command = UINT16_MAX;
     fm->idle_timeout = OFP_FLOW_PERMANENT;
     fm->hard_timeout = OFP_FLOW_PERMANENT;
@@ -785,8 +782,8 @@ parse_ofp_str(struct flow_mod *fm, uint8_t *table_idx,
                 ovs_fatal(0, "field %s missing value", name);
             }
 
-            if (table_idx && !strcmp(name, "table")) {
-                *table_idx = atoi(value);
+            if (!strcmp(name, "table")) {
+                fm->table_id = atoi(value);
             } else if (!strcmp(name, "out_port")) {
                 fm->out_port = atoi(value);
             } else if (!strcmp(name, "priority")) {
@@ -844,7 +841,7 @@ parse_ofp_str(struct flow_mod *fm, uint8_t *table_idx,
  * flow. */
 void
 parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur_format,
-                       char *string, uint16_t command)
+                       bool *flow_mod_table_id, char *string, uint16_t command)
 {
     bool is_del = command == OFPFC_DELETE || command == OFPFC_DELETE_STRICT;
     enum nx_flow_format min_format, next_format;
@@ -853,7 +850,7 @@ parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur_format,
     struct flow_mod fm;
 
     ofpbuf_init(&actions, 64);
-    parse_ofp_str(&fm, NULL, is_del ? NULL : &actions, string);
+    parse_ofp_str(&fm, is_del ? NULL : &actions, string);
     fm.command = command;
 
     min_format = ofputil_min_flow_format(&fm.cr, true, fm.cookie);
@@ -864,7 +861,13 @@ parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur_format,
         *cur_format = next_format;
     }
 
-    ofm = ofputil_encode_flow_mod(&fm, *cur_format);
+    if (fm.table_id != 0xff && !*flow_mod_table_id) {
+        struct ofpbuf *sff = ofputil_make_flow_mod_table_id(true);
+        list_push_back(packets, &sff->list_node);
+        *flow_mod_table_id = true;
+    }
+
+    ofm = ofputil_encode_flow_mod(&fm, *cur_format, *flow_mod_table_id);
     list_push_back(packets, &ofm->list_node);
 
     ofpbuf_uninit(&actions);
@@ -874,7 +877,8 @@ parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur_format,
  * 'stream' and the command is always OFPFC_ADD.  Returns false if end-of-file
  * is reached before reading a flow, otherwise true. */
 bool
-parse_ofp_flow_mod_file(struct list *packets, enum nx_flow_format *cur,
+parse_ofp_flow_mod_file(struct list *packets,
+                        enum nx_flow_format *cur, bool *flow_mod_table_id,
                         FILE *stream, uint16_t command)
 {
     struct ds s;
@@ -883,7 +887,8 @@ parse_ofp_flow_mod_file(struct list *packets, enum nx_flow_format *cur,
     ds_init(&s);
     ok = ds_get_preprocessed_line(&s, stream) == 0;
     if (ok) {
-        parse_ofp_flow_mod_str(packets, cur, ds_cstr(&s), command);
+        parse_ofp_flow_mod_str(packets, cur, flow_mod_table_id,
+                               ds_cstr(&s), command);
     }
     ds_destroy(&s);
 
@@ -895,11 +900,10 @@ parse_ofp_flow_stats_request_str(struct flow_stats_request *fsr,
                                  bool aggregate, char *string)
 {
     struct flow_mod fm;
-    uint8_t table_id;
 
-    parse_ofp_str(&fm, &table_id, NULL, string);
+    parse_ofp_str(&fm, NULL, string);
     fsr->aggregate = aggregate;
     fsr->match = fm.cr;
     fsr->out_port = fm.out_port;
-    fsr->table_id = table_id;
+    fsr->table_id = fm.table_id;
 }
diff --git a/lib/ofp-parse.h b/lib/ofp-parse.h
index c8cdade..8209298 100644
--- a/lib/ofp-parse.h
+++ b/lib/ofp-parse.h
@@ -29,12 +29,13 @@ struct flow_stats_request;
 struct list;
 struct ofpbuf;
 
-void parse_ofp_str(struct flow_mod *, uint8_t *table_idx,
-                   struct ofpbuf *actions, char *string);
+void parse_ofp_str(struct flow_mod *, struct ofpbuf *actions, char *string);
 
-void parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur,
+void parse_ofp_flow_mod_str(struct list *packets,
+                            enum nx_flow_format *cur, bool *flow_mod_table_id,
                             char *string, uint16_t command);
-bool parse_ofp_flow_mod_file(struct list *packets, enum nx_flow_format *cur,
+bool parse_ofp_flow_mod_file(struct list *packets,
+                             enum nx_flow_format *cur, bool *flow_mod_table_id,
                              FILE *, uint16_t command);
 
 void parse_ofp_flow_stats_request_str(struct flow_stats_request *,
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index d63ff7c..5b04d19 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -837,7 +837,7 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
     bool need_priority;
     int error;
 
-    error = ofputil_decode_flow_mod(&fm, oh, NXFF_OPENFLOW10);
+    error = ofputil_decode_flow_mod(&fm, oh, NXFF_OPENFLOW10, true);
     if (error) {
         ofp_print_error(s, error);
         return;
@@ -863,6 +863,9 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
     default:
         ds_put_format(s, "cmd:%d", fm.command);
     }
+    if (fm.table_id != 0) {
+        ds_put_format(s, " table_id:%d", fm.table_id);
+    }
 
     ds_put_char(s, ' ');
     if (verbosity >= 3 && code == OFPUTIL_OFPT_FLOW_MOD) {
@@ -1357,6 +1360,13 @@ ofp_print_nxt_role_message(struct ds *string,
 }
 
 static void
+ofp_print_nxt_flow_mod_table_id(struct ds *string,
+                                const struct nxt_flow_mod_table_id *nfmti)
+{
+    ds_put_format(string, " %s", nfmti->set ? "enable" : "disable");
+}
+
+static void
 ofp_print_nxt_set_flow_format(struct ds *string,
                               const struct nxt_set_flow_format *nsff)
 {
@@ -1516,6 +1526,10 @@ ofp_to_string__(const struct ofp_header *oh,
         ofp_print_nxt_role_message(string, msg);
         break;
 
+    case OFPUTIL_NXT_FLOW_MOD_TABLE_ID:
+        ofp_print_nxt_flow_mod_table_id(string, msg);
+        break;
+
     case OFPUTIL_NXT_SET_FLOW_FORMAT:
         ofp_print_nxt_set_flow_format(string, msg);
         break;
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 97a7873..962727c 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -377,16 +377,27 @@ ofputil_lookup_openflow_message(const struct ofputil_msg_category *cat,
                                 const struct ofputil_msg_type **typep)
 {
     const struct ofputil_msg_type *type;
+    bool found;
 
+    found = false;
     for (type = cat->types; type < &cat->types[cat->n_types]; type++) {
         if (type->value == value) {
-            if (!ofputil_length_ok(cat, type, size)) {
-                return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
+            if (ofputil_length_ok(cat, type, size)) {
+                *typep = type;
+                return 0;
             }
-            *typep = type;
-            return 0;
+
+            /* We found a matching command type but it had the wrong length.
+             * Probably this is just an error.  However, a screwup means that
+             * NXT_SET_FLOW_FORMAT and NXT_FLOW_MOD_TABLE_ID have the same
+             * value.  They do have different lengths, so we can distinguish
+             * them that way. */
+            found = true;
         }
     }
+    if (found) {
+        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
+    }
 
     VLOG_WARN_RL(&bad_ofmsg_rl, "received %s of unknown type %"PRIu32,
                  cat->name, value);
@@ -397,6 +408,9 @@ static int
 ofputil_decode_vendor(const struct ofp_header *oh,
                       const struct ofputil_msg_type **typep)
 {
+    BUILD_ASSERT_DECL(sizeof(struct nxt_set_flow_format)
+                      != sizeof(struct nxt_flow_mod_table_id));
+
     static const struct ofputil_msg_type nxt_messages[] = {
         { OFPUTIL_NXT_TUN_ID_FROM_COOKIE,
           NXT_TUN_ID_FROM_COOKIE, "NXT_TUN_ID_FROM_COOKIE",
@@ -414,6 +428,10 @@ ofputil_decode_vendor(const struct ofp_header *oh,
           NXT_SET_FLOW_FORMAT, "NXT_SET_FLOW_FORMAT",
           sizeof(struct nxt_set_flow_format), 0 },
 
+        { OFPUTIL_NXT_FLOW_MOD_TABLE_ID,
+          NXT_FLOW_MOD_TABLE_ID, "NXT_FLOW_MOD_TABLE_ID",
+          sizeof(struct nxt_flow_mod_table_id), 0 },
+
         { OFPUTIL_NXT_FLOW_MOD,
           NXT_FLOW_MOD, "NXT_FLOW_MOD",
           sizeof(struct nx_flow_mod), 8 },
@@ -962,6 +980,19 @@ ofputil_make_set_flow_format(enum nx_flow_format flow_format)
     return msg;
 }
 
+/* Returns an OpenFlow message that can be used to turn the flow_mod_table_id
+ * extension on or off (according to 'flow_mod_table_id'). */
+struct ofpbuf *
+ofputil_make_flow_mod_table_id(bool flow_mod_table_id)
+{
+    struct nxt_flow_mod_table_id *nfmti;
+    struct ofpbuf *msg;
+
+    nfmti = make_nxmsg(sizeof *nfmti, NXT_FLOW_MOD_TABLE_ID, &msg);
+    nfmti->set = flow_mod_table_id;
+    return msg;
+}
+
 /* Converts an OFPT_FLOW_MOD or NXT_FLOW_MOD message 'oh' into an abstract
  * flow_mod in 'fm'.  Returns 0 if successful, otherwise an OpenFlow error
  * code.
@@ -970,12 +1001,17 @@ ofputil_make_set_flow_format(enum nx_flow_format flow_format)
  * at the time when the message was received.  Otherwise 'flow_format' is
  * ignored.
  *
+ * 'flow_mod_table_id' should be true if the NXT_FLOW_MOD_TABLE_ID extension is
+ * enabled, false otherwise.
+ *
  * Does not validate the flow_mod actions. */
 int
 ofputil_decode_flow_mod(struct flow_mod *fm, const struct ofp_header *oh,
-                        enum nx_flow_format flow_format)
+                        enum nx_flow_format flow_format,
+                        bool flow_mod_table_id)
 {
     const struct ofputil_msg_type *type;
+    uint16_t command;
     struct ofpbuf b;
 
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
@@ -1016,7 +1052,7 @@ ofputil_decode_flow_mod(struct flow_mod *fm, const struct ofp_header *oh,
         ofputil_cls_rule_from_match(&match, ntohs(ofm->priority), flow_format,
                                     ofm->cookie, &fm->cr);
         fm->cookie = ofm->cookie;
-        fm->command = ntohs(ofm->command);
+        command = ntohs(ofm->command);
         fm->idle_timeout = ntohs(ofm->idle_timeout);
         fm->hard_timeout = ntohs(ofm->hard_timeout);
         fm->buffer_id = ntohl(ofm->buffer_id);
@@ -1041,7 +1077,7 @@ ofputil_decode_flow_mod(struct flow_mod *fm, const struct ofp_header *oh,
 
         /* Translate the message. */
         fm->cookie = nfm->cookie;
-        fm->command = ntohs(nfm->command);
+        command = ntohs(nfm->command);
         fm->idle_timeout = ntohs(nfm->idle_timeout);
         fm->hard_timeout = ntohs(nfm->hard_timeout);
         fm->buffer_id = ntohl(nfm->buffer_id);
@@ -1051,17 +1087,34 @@ ofputil_decode_flow_mod(struct flow_mod *fm, const struct ofp_header *oh,
         NOT_REACHED();
     }
 
+    if (flow_mod_table_id) {
+        fm->command = command & 0xff;
+        fm->table_id = command >> 8;
+    } else {
+        fm->command = command;
+        fm->table_id = 0xff;
+    }
+
     return 0;
 }
 
 /* Converts 'fm' into an OFPT_FLOW_MOD or NXT_FLOW_MOD message according to
- * 'flow_format' and returns the message. */
+ * 'flow_format' and returns the message.
+ *
+ * 'flow_mod_table_id' should be true if the NXT_FLOW_MOD_TABLE_ID extension is
+ * enabled, false otherwise. */
 struct ofpbuf *
 ofputil_encode_flow_mod(const struct flow_mod *fm,
-                        enum nx_flow_format flow_format)
+                        enum nx_flow_format flow_format,
+                        bool flow_mod_table_id)
 {
     size_t actions_len = fm->n_actions * sizeof *fm->actions;
     struct ofpbuf *msg;
+    uint16_t command;
+
+    command = (flow_mod_table_id
+               ? (fm->command & 0xff) | (fm->table_id << 8)
+               : fm->command);
 
     if (flow_format == NXFF_OPENFLOW10
         || flow_format == NXFF_TUN_ID_FROM_COOKIE) {
@@ -1071,7 +1124,7 @@ ofputil_encode_flow_mod(const struct flow_mod *fm,
         ofm = put_openflow(sizeof *ofm, OFPT_FLOW_MOD, msg);
         ofputil_cls_rule_to_match(&fm->cr, flow_format, &ofm->match,
                                   fm->cookie, &ofm->cookie);
-        ofm->command = htons(fm->command);
+        ofm->command = htons(command);
         ofm->idle_timeout = htons(fm->idle_timeout);
         ofm->hard_timeout = htons(fm->hard_timeout);
         ofm->priority = htons(fm->cr.priority);
@@ -1088,7 +1141,7 @@ ofputil_encode_flow_mod(const struct flow_mod *fm,
 
         nfm = msg->data;
         nfm->cookie = fm->cookie;
-        nfm->command = htons(fm->command);
+        nfm->command = htons(command);
         nfm->idle_timeout = htons(fm->idle_timeout);
         nfm->hard_timeout = htons(fm->hard_timeout);
         nfm->priority = htons(fm->cr.priority);
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index 46216a9..55c2c58 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -75,6 +75,7 @@ enum ofputil_msg_code {
     OFPUTIL_NXT_ROLE_REQUEST,
     OFPUTIL_NXT_ROLE_REPLY,
     OFPUTIL_NXT_SET_FLOW_FORMAT,
+    OFPUTIL_NXT_FLOW_MOD_TABLE_ID,
     OFPUTIL_NXT_FLOW_MOD,
     OFPUTIL_NXT_FLOW_REMOVED,
 
@@ -122,10 +123,14 @@ enum nx_flow_format ofputil_min_flow_format(const struct cls_rule *,
 
 struct ofpbuf *ofputil_make_set_flow_format(enum nx_flow_format);
 
+/* NXT_FLOW_MOD_TABLE_ID extension. */
+struct ofpbuf *ofputil_make_flow_mod_table_id(bool flow_mod_table_id);
+
 /* Flow format independent flow_mod. */
 struct flow_mod {
     struct cls_rule cr;
     ovs_be64 cookie;
+    uint8_t table_id;
     uint16_t command;
     uint16_t idle_timeout;
     uint16_t hard_timeout;
@@ -137,9 +142,10 @@ struct flow_mod {
 };
 
 int ofputil_decode_flow_mod(struct flow_mod *, const struct ofp_header *,
-                            enum nx_flow_format);
+                            enum nx_flow_format, bool flow_mod_table_id);
 struct ofpbuf *ofputil_encode_flow_mod(const struct flow_mod *,
-                                       enum nx_flow_format);
+                                       enum nx_flow_format,
+                                       bool flow_mod_table_id);
 
 /* Flow stats or aggregate stats request, independent of flow format. */
 struct flow_stats_request {
diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
index a6f23dc..d49c069 100644
--- a/ofproto/connmgr.c
+++ b/ofproto/connmgr.c
@@ -49,6 +49,7 @@ struct ofconn {
     struct rconn *rconn;        /* OpenFlow connection. */
     enum ofconn_type type;      /* Type. */
     enum nx_flow_format flow_format; /* Currently selected flow format. */
+    bool flow_mod_table_id;     /* NXT_FLOW_MOD_TABLE_ID enabled? */
 
     /* OFPT_PACKET_IN related data. */
     struct rconn_packet_counter *packet_in_counter; /* # queued on 'rconn'. */
@@ -724,6 +725,24 @@ ofconn_set_flow_format(struct ofconn *ofconn, enum nx_flow_format flow_format)
     ofconn->flow_format = flow_format;
 }
 
+/* Returns true if the NXT_FLOW_MOD_TABLE_ID extension is enabled, false
+ * otherwise.
+ *
+ * By default the extension is not enabled. */
+bool
+ofconn_get_flow_mod_table_id(const struct ofconn *ofconn)
+{
+    return ofconn->flow_mod_table_id;
+}
+
+/* Enables or disables (according to 'enable') the NXT_FLOW_MOD_TABLE_ID
+ * extension on 'ofconn'. */
+void
+ofconn_set_flow_mod_table_id(struct ofconn *ofconn, bool enable)
+{
+    ofconn->flow_mod_table_id = enable;
+}
+
 /* Returns the default miss send length for 'ofconn'. */
 int
 ofconn_get_miss_send_len(const struct ofconn *ofconn)
@@ -773,6 +792,7 @@ ofconn_create(struct connmgr *mgr, struct rconn *rconn, enum ofconn_type type)
     ofconn->rconn = rconn;
     ofconn->type = type;
     ofconn->flow_format = NXFF_OPENFLOW10;
+    ofconn->flow_mod_table_id = false;
     ofconn->role = NX_ROLE_OTHER;
     ofconn->packet_in_counter = rconn_packet_counter_create ();
     ofconn->pktbuf = NULL;
diff --git a/ofproto/connmgr.h b/ofproto/connmgr.h
index 441ecc3..46d2f5d 100644
--- a/ofproto/connmgr.h
+++ b/ofproto/connmgr.h
@@ -80,6 +80,9 @@ void ofconn_set_role(struct ofconn *, enum nx_role);
 enum nx_flow_format ofconn_get_flow_format(struct ofconn *);
 void ofconn_set_flow_format(struct ofconn *, enum nx_flow_format);
 
+bool ofconn_get_flow_mod_table_id(const struct ofconn *);
+void ofconn_set_flow_mod_table_id(struct ofconn *, bool enable);
+
 int ofconn_get_miss_send_len(const struct ofconn *);
 void ofconn_set_miss_send_len(struct ofconn *, int miss_send_len);
 
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 33cefe3..30bf491 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -43,6 +43,7 @@
 #include "ofproto-sflow.h"
 #include "poll-loop.h"
 #include "timer.h"
+#include "unaligned.h"
 #include "unixctl.h"
 #include "vlan-bitmap.h"
 #include "vlog.h"
@@ -307,6 +308,9 @@ struct ofproto_dpif {
     struct dpif *dpif;
     int max_ports;
 
+    /* Statistics. */
+    uint64_t n_matches;
+
     /* Bridging. */
     struct netflow *netflow;
     struct ofproto_sflow *sflow;
@@ -417,6 +421,7 @@ construct(struct ofproto *ofproto_)
     }
 
     ofproto->max_ports = dpif_get_max_ports(ofproto->dpif);
+    ofproto->n_matches = 0;
 
     error = dpif_recv_set_mask(ofproto->dpif,
                                ((1u << DPIF_UC_MISS) |
@@ -445,6 +450,10 @@ construct(struct ofproto *ofproto_)
     ofproto->need_revalidate = false;
     tag_set_init(&ofproto->revalidate_set);
 
+    ofproto->up.tables = xmalloc(sizeof *ofproto->up.tables);
+    classifier_init(&ofproto->up.tables[0]);
+    ofproto->up.n_tables = 1;
+
     ofproto_dpif_unixctl_init();
 
     return 0;
@@ -584,6 +593,39 @@ flush(struct ofproto *ofproto_)
     dpif_flow_flush(ofproto->dpif);
 }
 
+static void
+get_features(struct ofproto *ofproto_ OVS_UNUSED,
+             bool *arp_match_ip, uint32_t *actions)
+{
+    *arp_match_ip = true;
+    *actions = ((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));
+}
+
+static void
+get_tables(struct ofproto *ofproto_, struct ofp_table_stats *ots)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+    struct odp_stats s;
+
+    strcpy(ots->name, "classifier");
+
+    dpif_get_dp_stats(ofproto->dpif, &s);
+    put_32aligned_be64(&ots->lookup_count, htonll(s.n_hit + s.n_missed));
+    put_32aligned_be64(&ots->matched_count,
+                       htonll(s.n_hit + ofproto->n_matches));
+}
+
 static int
 set_netflow(struct ofproto *ofproto_,
             const struct netflow_options *netflow_options)
@@ -1534,6 +1576,7 @@ handle_miss_upcall(struct ofproto_dpif *ofproto, struct dpif_upcall *upcall)
     /* Handle 802.1ag and LACP. */
     if (process_special(ofproto, &flow, upcall->packet)) {
         ofpbuf_delete(upcall->packet);
+        ofproto->n_matches++;
         return;
     }
 
@@ -1588,6 +1631,7 @@ handle_miss_upcall(struct ofproto_dpif *ofproto, struct dpif_upcall *upcall)
 
     facet_execute(ofproto, facet, upcall->packet);
     facet_install(ofproto, facet, false);
+    ofproto->n_matches++;
 }
 
 static void
@@ -1649,7 +1693,7 @@ expire(struct ofproto_dpif *ofproto)
     expire_facets(ofproto, dp_max_idle);
 
     /* Expire OpenFlow flows whose idle_timeout or hard_timeout has passed. */
-    cls_cursor_init(&cursor, &ofproto->up.cls, NULL);
+    cls_cursor_init(&cursor, &ofproto->up.tables[0], NULL);
     CLS_CURSOR_FOR_EACH_SAFE (rule, next_rule, up.cr, &cursor) {
         rule_expire(rule);
     }
@@ -2421,7 +2465,8 @@ static struct rule_dpif *
 rule_dpif_lookup(struct ofproto_dpif *ofproto, const struct flow *flow)
 {
     return rule_dpif_cast(rule_from_cls_rule(
-                              classifier_lookup(&ofproto->up.cls, flow)));
+                              classifier_lookup(&ofproto->up.tables[0],
+                                                flow)));
 }
 
 static struct rule *
@@ -2453,7 +2498,7 @@ rule_construct(struct rule *rule_)
     }
 
     old_rule = rule_dpif_cast(rule_from_cls_rule(classifier_find_rule_exactly(
-                                                     &ofproto->up.cls,
+                                                     &ofproto->up.tables[0],
                                                      &rule->up.cr)));
     if (old_rule) {
         ofproto_rule_destroy(&old_rule->up);
@@ -2463,7 +2508,7 @@ rule_construct(struct rule *rule_)
     rule->packet_count = 0;
     rule->byte_count = 0;
     list_init(&rule->facets);
-    classifier_insert(&ofproto->up.cls, &rule->up.cr);
+    classifier_insert(&ofproto->up.tables[0], &rule->up.cr);
 
     ofproto->need_revalidate = true;
 
@@ -2477,7 +2522,7 @@ rule_destruct(struct rule *rule_)
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
     struct facet *facet, *next_facet;
 
-    classifier_remove(&ofproto->up.cls, &rule->up.cr);
+    classifier_remove(&ofproto->up.tables[0], &rule->up.cr);
     LIST_FOR_EACH_SAFE (facet, next_facet, list_node, &rule->facets) {
         facet_revalidate(ofproto, facet);
     }
@@ -3845,6 +3890,8 @@ const struct ofproto_class ofproto_dpif_class = {
     run,
     wait,
     flush,
+    get_features,
+    get_tables,
     port_alloc,
     port_construct,
     port_destruct,
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 5e270d8..7ff7b5c 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -261,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);
@@ -271,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);
@@ -588,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);
@@ -600,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);
 }
@@ -879,16 +887,15 @@ 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));
+    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);
 
@@ -896,9 +903,14 @@ 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_destroy(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);
+        }
     }
 }
 
@@ -1344,25 +1356,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 = 1;
+    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);
@@ -1643,19 +1652,27 @@ 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(OVSFW_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);
+
+    if (ofconn_get_flow_format(ofconn) == NXFF_OPENFLOW10) {
+        /* OpenFlow 1.0 only supports the OFPFW_* bits. */
+        for (i = 0; i < p->n_tables; i++) {
+            ots[i].wildcards &= htonl(OFPFW_ALL);
+        }
+    }
 
     ofconn_send_reply(ofconn, msg);
     return 0;
@@ -1754,7 +1771,7 @@ 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);
@@ -1771,38 +1788,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, NXFF_OPENFLOW10, 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);
         }
@@ -1857,6 +1904,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;
@@ -1876,11 +1924,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);
         }
@@ -1899,6 +1947,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, "idle=%.3fs, ", (time_msec() - rule->used) / 1000.0);
@@ -1920,12 +1971,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);
+        }
     }
 }
 
@@ -1945,15 +2000,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;
@@ -2137,9 +2193,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);
@@ -2158,10 +2219,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
@@ -2204,19 +2290,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;
+                }
             }
         }
     }
@@ -2243,15 +2333,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();
     }
 }
 
@@ -2296,12 +2396,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));
+        }
     }
 }
 
@@ -2309,8 +2413,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));
     }
 }
@@ -2384,7 +2488,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_format(ofconn),
+                                    ofconn_get_flow_mod_table_id(ofconn));
     if (error) {
         return error;
     }
@@ -2416,6 +2521,10 @@ 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);
     }
 }
@@ -2465,6 +2574,17 @@ handle_role_request(struct ofconn *ofconn, const struct ofp_header *oh)
 }
 
 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)
 {
     const struct nxt_set_flow_format *msg
@@ -2544,6 +2664,9 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
     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);
 
diff --git a/ofproto/private.h b/ofproto/private.h
index 67893ba..19e5c03 100644
--- a/ofproto/private.h
+++ b/ofproto/private.h
@@ -49,8 +49,9 @@ struct ofproto {
     struct hmap ports;          /* Contains "struct ofport"s. */
     struct shash port_by_name;
 
-    /* Flow table. */
-    struct classifier cls;      /* Contains "struct rule"s. */
+    /* Flow tables. */
+    struct classifier *tables;  /* Each classifier contains "struct rule"s. */
+    int n_tables;
 
     /* OpenFlow connections. */
     struct connmgr *connmgr;
@@ -84,6 +85,7 @@ struct rule {
     long long int created;       /* Creation time. */
     uint16_t idle_timeout;       /* In seconds from time of last use. */
     uint16_t hard_timeout;       /* In seconds from time of creation. */
+    uint8_t table_id;            /* Index in ofproto's 'tables' array. */
     bool send_flow_removed;      /* Send a flow removed message? */
 
     union ofp_action *actions;   /* OpenFlow actions. */
@@ -235,12 +237,17 @@ struct ofproto_class {
 
     /* Life-cycle functions for an "ofproto" (see "Life Cycle" above).
      *
-     * ->construct() should not modify any base members of the ofproto, even
-     * though it may be tempting in a few cases.  In particular, the client
-     * will initialize the ofproto's 'ports' member after construction is
-     * complete.  An ofproto's flow table should be initially empty, so
-     * ->construct() should delete flows from the underlying datapath, if
-     * necessary, rather than populating the ofproto's 'cls'.
+     * ->construct() should not modify most base members of the ofproto.  In
+     * particular, the client will initialize the ofproto's 'ports' member
+     * after construction is complete.
+     *
+     * ->construct() should initialize the base 'n_tables' member to the number
+     * of flow tables supported by the datapath (between 1 and 254, inclusive),
+     * initialize the base 'tables' membeer with space for one classifier per
+     * table, and initialize each classifier with classifier_init.  Each flow
+     * table should be initially empty, so ->construct() should delete flows
+     * from the underlying datapath, if necessary, rather than populating the
+     * tables.
      *
      * Only one ofproto instance needs to be supported for any given datapath.
      * If a datapath is already open as part of one "ofproto", then another
@@ -284,6 +291,61 @@ struct ofproto_class {
      * than to do it one by one. */
     void (*flush)(struct ofproto *ofproto);
 
+    /* Helper for the OpenFlow OFPT_FEATURES_REQUEST request.
+     *
+     * The implementation should store true in '*arp_match_ip' if the switch
+     * supports matching IP addresses inside ARP requests and replies, false
+     * otherwise.
+     *
+     * The implementation should store in '*actions' a bitmap of the supported
+     * OpenFlow actions: the bit with value (1 << n) should be set to 1 if the
+     * implementation supports the action with value 'n', and to 0 otherwise.
+     * For example, if the implementation supports the OFPAT_OUTPUT and
+     * OFPAT_ENQUEUE actions, but no others, it would set '*actions' to (1 <<
+     * OFPAT_OUTPUT) | (1 << OFPAT_ENQUEUE).  Vendor actions are not included
+     * in '*actions'. */
+    void (*get_features)(struct ofproto *ofproto,
+                         bool *arp_match_ip, uint32_t *actions);
+
+    /* Helper for the OpenFlow OFPST_TABLE statistics request.
+     *
+     * The 'ots' array contains 'ofproto->n_tables' elements.  Each element is
+     * initialized as:
+     *
+     *   - 'table_id' to the array index.
+     *
+     *   - 'name' to "table#" where # is the table ID.
+     *
+     *   - 'wildcards' to OVSFW_ALL.
+     *
+     *   - 'max_entries' to 1,000,000.
+     *
+     *   - 'active_count' to the classifier_count() for the table.
+     *
+     *   - 'lookup_count' and 'matched_count' to 0.
+     *
+     * The implementation should update any members in each element for which
+     * it has better values:
+     *
+     *   - 'name' to a more meaningful name.
+     *
+     *   - 'wildcards' to the set of wildcards actually supported by the table
+     *     (if it doesn't support all OpenFlow wildcards).
+     *
+     *   - 'max_entries' to the maximum number of flows actually supported by
+     *     the hardware.
+     *
+     *   - 'lookup_count' to the number of packets looked up in this flow table
+     *     so far.
+     *
+     *   - 'matched_count' to the number of packets looked up in this flow
+     *     table so far that matched one of the flow entries.
+     *
+     * Keep in mind that all of the members of struct ofp_table_stats are in
+     * network byte order.
+     */
+    void (*get_tables)(struct ofproto *ofproto, struct ofp_table_stats *ots);
+
 /* ## ---------------- ## */
 /* ## ofport Functions ## */
 /* ## ---------------- ## */
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index ae3f70b..a9c3c20 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -14,7 +14,7 @@ actions=note:41.42.43,note:00.01.02.03.04.05.06.07,note
 tun_id=0x1234,cookie=0x5678,actions=flood
 actions=set_tunnel:0x1234,set_tunnel64:0x9876,set_tunnel:0x123456789
 actions=multipath(eth_src, 50, hrw, 12, 0, NXM_NX_REG0[0..3]),multipath(symmetric_l4, 1024, iter_hash, 5000, 5050, NXM_NX_REG0[0..12])
-actions=drop
+table=1,actions=drop
 tun_id=0x1234000056780000/0xffff0000ffff0000,actions=drop
 ]])
 AT_CHECK([ovs-ofctl parse-flows flows.txt
@@ -32,9 +32,10 @@ NXT_TUN_ID_FROM_COOKIE: set=1
 OFPT_FLOW_MOD: ADD cookie:0x123400005678 actions=FLOOD
 OFPT_FLOW_MOD: ADD actions=set_tunnel:0x1234,set_tunnel64:0x9876,set_tunnel64:0x123456789
 OFPT_FLOW_MOD: ADD actions=multipath(eth_src,50,hrw,12,0,NXM_NX_REG0[0..3]),multipath(symmetric_l4,1024,iter_hash,5000,5050,NXM_NX_REG0[0..12])
-OFPT_FLOW_MOD: ADD actions=drop
+NXT_FLOW_MOD_TABLE_ID: enable
+OFPT_FLOW_MOD: ADD table_id:1 actions=drop
 NXT_SET_FLOW_FORMAT: format=nxm
-NXT_FLOW_MOD: ADD tun_id=0x1234000056780000/0xffff0000ffff0000 actions=drop
+NXT_FLOW_MOD: ADD table_id:255 tun_id=0x1234000056780000/0xffff0000ffff0000 actions=drop
 ]])
 AT_CHECK([sed 's/.*|//' stderr], [0], [dnl
 normalization changed ofp_match, details:
diff --git a/utilities/ovs-controller.c b/utilities/ovs-controller.c
index 27c8e47..f131917 100644
--- a/utilities/ovs-controller.c
+++ b/utilities/ovs-controller.c
@@ -261,6 +261,7 @@ static void
 read_flow_file(const char *name)
 {
     enum nx_flow_format flow_format;
+    bool flow_mod_table_id;
     FILE *stream;
 
     stream = fopen(optarg, "r");
@@ -269,8 +270,10 @@ read_flow_file(const char *name)
     }
 
     flow_format = NXFF_OPENFLOW10;
-    while (parse_ofp_flow_mod_file(&default_flows, &flow_format, stream,
-                                   OFPFC_ADD)) {
+    flow_mod_table_id = false;
+    while (parse_ofp_flow_mod_file(&default_flows,
+                                   &flow_format, &flow_mod_table_id,
+                                   stream, OFPFC_ADD)) {
         continue;
     }
 
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index d9742c8..7452e70 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -365,6 +365,25 @@ specified as a decimal number between 0 and 255, inclusive.
 When \fBdl_type\fR and \fBnw_proto\fR take other values, the values of
 these settings are ignored (see \fBFlow Syntax\fR above).
 .
+.IP \fBtable=\fInumber\fR
+If specified, limits the flow manipulation and flow dump commands to
+only apply to the table with the given \fInumber\fR.
+\fInumber\fR is a number between 0 and 254, inclusive.
+.
+Behavior varies if \fBtable\fR is not specified.  For flow table
+modification commands without \fB\-\-strict\fR, the switch will choose
+the table for these commands to operate on.  For flow table
+modification commands with \fB\-\-strict\fR, the command will operate
+on any single matching flow in any table; it will do nothing if there
+are matches in more than one table.  The \fBdump-flows\fR and
+\fBdump-aggregate\fR commands will gather statistics about flows from
+all tables.
+.IP
+When this field is specified in \fBadd-flow\fR, \fBadd-flows\fR,
+\fBmod-flows\fR and \fBdel-flows\fR commands, it activates a Nicira
+extension to OpenFlow, which as of this writing is only known to be
+implemented by Open vSwitch.
+.
 .PP
 The following shorthand notations are also available:
 .
@@ -713,14 +732,6 @@ If set, a matching flow must include an output action to \fIport\fR.
 The \fBdump\-flows\fR and \fBdump\-aggregate\fR commands support an
 additional optional field:
 .
-.IP \fBtable=\fInumber\fR
-If specified, limits the flows about which statistics are gathered to
-those in the table with the given \fInumber\fR.  Tables are numbered
-as shown by the \fBdump\-tables\fR command.
-.
-If this field is not specified, or if \fInumber\fR is given as
-\fB255\fR, statistics are gathered about flows from all tables.
-.
 .SS "Table Entry Output"
 .
 The \fBdump\-tables\fR and \fBdump\-aggregate\fR commands print information 
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index b829ac7..11fcc3e 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -615,6 +615,7 @@ static void
 do_flow_mod_file__(int argc OVS_UNUSED, char *argv[], uint16_t command)
 {
     enum nx_flow_format flow_format;
+    bool flow_mod_table_id;
     struct list requests;
     struct vconn *vconn;
     FILE *file;
@@ -626,9 +627,11 @@ do_flow_mod_file__(int argc OVS_UNUSED, char *argv[], uint16_t command)
 
     list_init(&requests);
     flow_format = set_initial_format_for_flow_mod(&requests);
+    flow_mod_table_id = false;
 
     open_vconn(argv[1], &vconn);
-    while (parse_ofp_flow_mod_file(&requests, &flow_format, file, command)) {
+    while (parse_ofp_flow_mod_file(&requests, &flow_format, &flow_mod_table_id,
+                                   file, command)) {
         check_final_format_for_flow_mod(flow_format);
         transact_multiple_noreply(vconn, &requests);
     }
@@ -643,6 +646,7 @@ static void
 do_flow_mod__(int argc, char *argv[], uint16_t command)
 {
     enum nx_flow_format flow_format;
+    bool flow_mod_table_id;
     struct list requests;
     struct vconn *vconn;
 
@@ -653,9 +657,10 @@ do_flow_mod__(int argc, char *argv[], uint16_t command)
 
     list_init(&requests);
     flow_format = set_initial_format_for_flow_mod(&requests);
+    flow_mod_table_id = false;
 
-    parse_ofp_flow_mod_str(&requests, &flow_format, argc > 2 ? argv[2] : "",
-                           command);
+    parse_ofp_flow_mod_str(&requests, &flow_format, &flow_mod_table_id,
+                           argc > 2 ? argv[2] : "", command);
     check_final_format_for_flow_mod(flow_format);
 
     open_vconn(argv[1], &vconn);
@@ -1030,10 +1035,9 @@ read_flows_from_file(const char *filename, struct classifier *cls, int index)
         enum nx_flow_format min_ff;
         struct ofpbuf actions;
         struct flow_mod fm;
-        uint8_t table_idx;
 
         ofpbuf_init(&actions, 64);
-        parse_ofp_str(&fm, &table_idx, &actions, ds_cstr(&s));
+        parse_ofp_str(&fm, &actions, ds_cstr(&s));
 
         version = xmalloc(sizeof *version);
         version->cookie = fm.cookie;
@@ -1147,6 +1151,7 @@ fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
 
     fm.cr = fte->rule;
     fm.cookie = version->cookie;
+    fm.table_id = 0xff;
     fm.command = command;
     fm.idle_timeout = version->idle_timeout;
     fm.hard_timeout = version->hard_timeout;
@@ -1162,7 +1167,7 @@ fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
         fm.n_actions = 0;
     }
 
-    ofm = ofputil_encode_flow_mod(&fm, flow_format);
+    ofm = ofputil_encode_flow_mod(&fm, flow_format, false);
     list_push_back(packets, &ofm->list_node);
 }
 
@@ -1295,15 +1300,18 @@ static void
 do_parse_flow(int argc OVS_UNUSED, char *argv[])
 {
     enum nx_flow_format flow_format;
+    bool flow_mod_table_id;
     struct list packets;
 
     flow_format = NXFF_OPENFLOW10;
     if (preferred_flow_format > 0) {
         flow_format = preferred_flow_format;
     }
+    flow_mod_table_id = false;
 
     list_init(&packets);
-    parse_ofp_flow_mod_str(&packets, &flow_format, argv[1], OFPFC_ADD);
+    parse_ofp_flow_mod_str(&packets, &flow_format, &flow_mod_table_id,
+                           argv[1], OFPFC_ADD);
     print_packet_list(&packets);
 }
 
@@ -1313,6 +1321,7 @@ static void
 do_parse_flows(int argc OVS_UNUSED, char *argv[])
 {
     enum nx_flow_format flow_format;
+    bool flow_mod_table_id;
     struct list packets;
     FILE *file;
 
@@ -1325,9 +1334,11 @@ do_parse_flows(int argc OVS_UNUSED, char *argv[])
     if (preferred_flow_format > 0) {
         flow_format = preferred_flow_format;
     }
+    flow_mod_table_id = false;
 
     list_init(&packets);
-    while (parse_ofp_flow_mod_file(&packets, &flow_format, file, OFPFC_ADD)) {
+    while (parse_ofp_flow_mod_file(&packets, &flow_format, &flow_mod_table_id,
+                                   file, OFPFC_ADD)) {
         print_packet_list(&packets);
     }
     fclose(file);
-- 
1.7.4.4




More information about the dev mailing list