ofproto: Implement OFPT_QUEUE_GET_CONFIG_REQUEST for OFPP_ANY in OF1.1+.
[cascardo/ovs.git] / utilities / ovs-ofctl.c
index fbc9da4..b46fd72 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -312,6 +312,9 @@ parse_options(int argc, char *argv[])
         /* Add implicit allowance for OpenFlow 1.4. */
         add_allowed_ofp_versions(ofputil_protocols_to_version_bitmap(
                                      OFPUTIL_P_OF14_OXM));
+        /* Remove all prior versions. */
+        mask_allowed_ofp_versions(ofputil_protocols_to_version_bitmap(
+                                     OFPUTIL_P_OF14_UP));
     }
     versions = get_allowed_ofp_versions();
     version_protocols = ofputil_protocols_from_version_bitmap(versions);
@@ -344,7 +347,7 @@ usage(void)
            "  mod-port SWITCH IFACE ACT   modify port behavior\n"
            "  mod-table SWITCH MOD        modify flow table behavior\n"
            "      OF1.1/1.2 MOD: controller, continue, drop\n"
-           "      OF1.4+ MOD: evict, noevict\n"
+           "      OF1.4+ MOD: evict, noevict, vacancy:low,high, novacancy\n"
            "  get-frags SWITCH            print fragment handling behavior\n"
            "  set-frags SWITCH FRAG_MODE  set fragment handling behavior\n"
            "      FRAG_MODE: normal, drop, reassemble, nx-match\n"
@@ -375,7 +378,7 @@ usage(void)
            "  dump-group-features SWITCH  print group features\n"
            "  dump-groups SWITCH [GROUP]  print group description\n"
            "  dump-group-stats SWITCH [GROUP]  print group statistics\n"
-           "  queue-get-config SWITCH PORT  print queue information for port\n"
+           "  queue-get-config SWITCH [PORT]  print queue config for PORT\n"
            "  add-meter SWITCH METER      add meter described by METER\n"
            "  mod-meter SWITCH METER      modify specific METER\n"
            "  del-meter SWITCH METER      delete METER\n"
@@ -384,9 +387,9 @@ usage(void)
            "  dump-meters SWITCH          print all meter configuration\n"
            "  meter-stats SWITCH [METER]  print meter statistics\n"
            "  meter-features SWITCH       print meter features\n"
-           "  add-geneve-map SWITCH MAP   add Geneve option MAPpings\n"
-           "  del-geneve-map SWITCH [MAP] delete Geneve option MAPpings\n"
-           "  dump-geneve-map SWITCH      print Geneve option mappings\n"
+           "  add-tlv-map SWITCH MAP      add TLV option MAPpings\n"
+           "  del-tlv-map SWITCH [MAP] delete TLV option MAPpings\n"
+           "  dump-tlv-map SWITCH      print TLV option mappings\n"
            "\nFor OpenFlow switches and controllers:\n"
            "  probe TARGET                probe whether TARGET is up\n"
            "  ping TARGET [N]             latency of N-byte echos\n"
@@ -820,145 +823,151 @@ ofctl_dump_table_desc(struct ovs_cmdl_context *ctx)
 }
 
 
-static bool fetch_port_by_stats(struct vconn *,
-                                const char *port_name, ofp_port_t port_no,
-                                struct ofputil_phy_port *);
-
-/* Uses OFPT_FEATURES_REQUEST to attempt to fetch information about the port
- * named 'port_name' or numbered 'port_no' into '*pp'.  Returns true if
- * successful, false on failure.
- *
- * This is only appropriate for OpenFlow 1.0, 1.1, and 1.2, which include a
- * list of ports in OFPT_FEATURES_REPLY. */
 static bool
-fetch_port_by_features(struct vconn *vconn,
-                       const char *port_name, ofp_port_t port_no,
-                       struct ofputil_phy_port *pp)
+str_to_ofp(const char *s, ofp_port_t *ofp_port)
 {
-    struct ofputil_switch_features features;
-    const struct ofp_header *oh;
-    struct ofpbuf *request, *reply;
-    enum ofperr error;
-    enum ofptype type;
-    struct ofpbuf b;
-    bool found = false;
+    bool ret;
+    uint32_t port_;
+
+    ret = str_to_uint(s, 10, &port_);
+    *ofp_port = u16_to_ofp(port_);
+    return ret;
+}
+
+struct port_iterator {
+    struct vconn *vconn;
+
+    enum { PI_FEATURES, PI_PORT_DESC } variant;
+    struct ofpbuf *reply;
+    ovs_be32 send_xid;
+    bool more;
+};
+
+static void
+port_iterator_fetch_port_desc(struct port_iterator *pi)
+{
+    pi->variant = PI_PORT_DESC;
+    pi->more = true;
+
+    struct ofpbuf *rq = ofputil_encode_port_desc_stats_request(
+        vconn_get_version(pi->vconn), OFPP_ANY);
+    pi->send_xid = ((struct ofp_header *) rq->data)->xid;
+    send_openflow_buffer(pi->vconn, rq);
+}
+
+static void
+port_iterator_fetch_features(struct port_iterator *pi)
+{
+    pi->variant = PI_FEATURES;
 
     /* Fetch the switch's ofp_switch_features. */
-    request = ofpraw_alloc(OFPRAW_OFPT_FEATURES_REQUEST,
-                           vconn_get_version(vconn), 0);
-    run(vconn_transact(vconn, request, &reply),
-        "talking to %s", vconn_get_name(vconn));
+    enum ofp_version version = vconn_get_version(pi->vconn);
+    struct ofpbuf *rq = ofpraw_alloc(OFPRAW_OFPT_FEATURES_REQUEST, version, 0);
+    run(vconn_transact(pi->vconn, rq, &pi->reply),
+        "talking to %s", vconn_get_name(pi->vconn));
 
-    oh = reply->data;
-    if (ofptype_decode(&type, reply->data)
+    const struct ofp_header *oh = pi->reply->data;
+    enum ofptype type;
+    if (ofptype_decode(&type, pi->reply->data)
         || type != OFPTYPE_FEATURES_REPLY) {
-        ovs_fatal(0, "%s: received bad features reply", vconn_get_name(vconn));
+        ovs_fatal(0, "%s: received bad features reply",
+                  vconn_get_name(pi->vconn));
     }
-    if (!ofputil_switch_features_has_ports(reply)) {
+    if (!ofputil_switch_features_has_ports(pi->reply)) {
         /* The switch features reply does not contain a complete list of ports.
          * Probably, there are more ports than will fit into a single 64 kB
          * OpenFlow message.  Use OFPST_PORT_DESC to get a complete list of
          * ports. */
-        ofpbuf_delete(reply);
-        return fetch_port_by_stats(vconn, port_name, port_no, pp);
+        ofpbuf_delete(pi->reply);
+        pi->reply = NULL;
+        port_iterator_fetch_port_desc(pi);
+        return;
     }
 
-    error = ofputil_decode_switch_features(oh, &features, &b);
+    struct ofputil_switch_features features;
+    enum ofperr error = ofputil_decode_switch_features(oh, &features,
+                                                       pi->reply);
     if (error) {
         ovs_fatal(0, "%s: failed to decode features reply (%s)",
-                  vconn_get_name(vconn), ofperr_to_string(error));
+                  vconn_get_name(pi->vconn), ofperr_to_string(error));
     }
+}
 
-    while (!ofputil_pull_phy_port(oh->version, &b, pp)) {
-        if (port_no != OFPP_NONE
-            ? port_no == pp->port_no
-            : !strcmp(pp->name, port_name)) {
-            found = true;
-            break;
-        }
+/* Initializes 'pi' to prepare for iterating through all of the ports on the
+ * OpenFlow switch to which 'vconn' is connected.
+ *
+ * During iteration, the client should not make other use of 'vconn', because
+ * that can cause other messages to be interleaved with the replies used by the
+ * iterator and thus some ports may be missed or a hang can occur. */
+static void
+port_iterator_init(struct port_iterator *pi, struct vconn *vconn)
+{
+    memset(pi, 0, sizeof *pi);
+    pi->vconn = vconn;
+    if (vconn_get_version(vconn) < OFP13_VERSION) {
+        port_iterator_fetch_features(pi);
+    } else {
+        port_iterator_fetch_port_desc(pi);
     }
-    ofpbuf_delete(reply);
-    return found;
 }
 
-/* Uses a OFPST_PORT_DESC request to attempt to fetch information about the
- * port named 'port_name' or numbered 'port_no' into '*pp'.  Returns true if
- * successful, false on failure.
- *
- * This is most appropriate for OpenFlow 1.3 and later.  Open vSwitch 1.7 and
- * later also implements OFPST_PORT_DESC, as an extension, for OpenFlow 1.0,
- * 1.1, and 1.2, so this can be used as a fallback in those versions when there
- * are too many ports than fit in an OFPT_FEATURES_REPLY. */
+/* Obtains the next port from 'pi'.  On success, initializes '*pp' with the
+ * port's details and returns true, otherwise (if all the ports have already
+ * been seen), returns false.  */
 static bool
-fetch_port_by_stats(struct vconn *vconn,
-                    const char *port_name, ofp_port_t port_no,
-                    struct ofputil_phy_port *pp)
+port_iterator_next(struct port_iterator *pi, struct ofputil_phy_port *pp)
 {
-    struct ofpbuf *request;
-    ovs_be32 send_xid;
-    bool done = false;
-    bool found = false;
-
-    request = ofputil_encode_port_desc_stats_request(vconn_get_version(vconn),
-                                                     port_no);
-    send_xid = ((struct ofp_header *) request->data)->xid;
-
-    send_openflow_buffer(vconn, request);
-    while (!done) {
-        ovs_be32 recv_xid;
-        struct ofpbuf *reply;
-
-        run(vconn_recv_block(vconn, &reply), "OpenFlow packet receive failed");
-        recv_xid = ((struct ofp_header *) reply->data)->xid;
-        if (send_xid == recv_xid) {
-            struct ofp_header *oh = reply->data;
-            enum ofptype type;
-            struct ofpbuf b;
-            uint16_t flags;
-
-            ofpbuf_use_const(&b, oh, ntohs(oh->length));
-            if (ofptype_pull(&type, &b)
-                || type != OFPTYPE_PORT_DESC_STATS_REPLY) {
+    for (;;) {
+        if (pi->reply) {
+            int retval = ofputil_pull_phy_port(vconn_get_version(pi->vconn),
+                                               pi->reply, pp);
+            if (!retval) {
+                return true;
+            } else if (retval != EOF) {
                 ovs_fatal(0, "received bad reply: %s",
-                          ofp_to_string(reply->data, reply->size,
+                          ofp_to_string(pi->reply->data, pi->reply->size,
                                         verbosity + 1));
             }
+        }
 
-            flags = ofpmp_flags(oh);
-            done = !(flags & OFPSF_REPLY_MORE);
-
-            if (found) {
-                /* We've already found the port, but we need to drain
-                 * the queue of any other replies for this request. */
-                continue;
-            }
+        if (pi->variant == PI_FEATURES || !pi->more) {
+            return false;
+        }
 
-            while (!ofputil_pull_phy_port(oh->version, &b, pp)) {
-                if (port_no != OFPP_NONE ? port_no == pp->port_no
-                                         : !strcmp(pp->name, port_name)) {
-                    found = true;
-                    break;
-                }
-            }
-        } else {
-            VLOG_DBG("received reply with xid %08"PRIx32" "
-                     "!= expected %08"PRIx32, recv_xid, send_xid);
+        ovs_be32 recv_xid;
+        do {
+            ofpbuf_delete(pi->reply);
+            run(vconn_recv_block(pi->vconn, &pi->reply),
+                "OpenFlow receive failed");
+            recv_xid = ((struct ofp_header *) pi->reply->data)->xid;
+        } while (pi->send_xid != recv_xid);
+
+        struct ofp_header *oh = pi->reply->data;
+        enum ofptype type;
+        if (ofptype_pull(&type, pi->reply)
+            || type != OFPTYPE_PORT_DESC_STATS_REPLY) {
+            ovs_fatal(0, "received bad reply: %s",
+                      ofp_to_string(pi->reply->data, pi->reply->size,
+                                    verbosity + 1));
         }
-        ofpbuf_delete(reply);
-    }
 
-    return found;
+        pi->more = (ofpmp_flags(oh) & OFPSF_REPLY_MORE) != 0;
+    }
 }
 
-static bool
-str_to_ofp(const char *s, ofp_port_t *ofp_port)
+/* Destroys iterator 'pi'. */
+static void
+port_iterator_destroy(struct port_iterator *pi)
 {
-    bool ret;
-    uint32_t port_;
+    if (pi) {
+        while (pi->variant == PI_PORT_DESC && pi->more) {
+            /* Drain vconn's queue of any other replies for this request. */
+            struct ofputil_phy_port pp;
+            port_iterator_next(pi, &pp);
+        }
 
-    ret = str_to_uint(s, 10, &port_);
-    *ofp_port = u16_to_ofp(port_);
-    return ret;
+        ofpbuf_delete(pi->reply);
+    }
 }
 
 /* Opens a connection to 'vconn_name', fetches the port structure for
@@ -970,7 +979,7 @@ fetch_ofputil_phy_port(const char *vconn_name, const char *port_name,
 {
     struct vconn *vconn;
     ofp_port_t port_no;
-    bool found;
+    bool found = false;
 
     /* Try to interpret the argument as a port number. */
     if (!str_to_ofp(port_name, &port_no)) {
@@ -981,9 +990,16 @@ fetch_ofputil_phy_port(const char *vconn_name, const char *port_name,
      * OFPT_FEATURES_REPLY message.  OpenFlow 1.3 and later versions put it
      * into the OFPST_PORT_DESC reply.  Try it the correct way. */
     open_vconn(vconn_name, &vconn);
-    found = (vconn_get_version(vconn) < OFP13_VERSION
-             ? fetch_port_by_features(vconn, port_name, port_no, pp)
-             : fetch_port_by_stats(vconn, port_name, port_no, pp));
+    struct port_iterator pi;
+    for (port_iterator_init(&pi, vconn); port_iterator_next(&pi, pp); ) {
+        if (port_no != OFPP_NONE
+            ? port_no == pp->port_no
+            : !strcmp(pp->name, port_name)) {
+            found = true;
+            break;
+        }
+    }
+    port_iterator_destroy(&pi);
     vconn_close(vconn);
 
     if (!found) {
@@ -1236,19 +1252,40 @@ static void
 ofctl_queue_get_config(struct ovs_cmdl_context *ctx)
 {
     const char *vconn_name = ctx->argv[1];
-    const char *port_name = ctx->argv[2];
-    enum ofputil_protocol protocol;
-    enum ofp_version version;
-    struct ofpbuf *request;
-    struct vconn *vconn;
-    ofp_port_t port;
+    const char *port_name = ctx->argc >= 3 ? ctx->argv[2] : NULL;
+    ofp_port_t port = (port_name
+                       ? str_to_port_no(vconn_name, port_name)
+                       : OFPP_ANY);
 
-    port = str_to_port_no(vconn_name, port_name);
-
-    protocol = open_vconn(vconn_name, &vconn);
-    version = ofputil_protocol_to_ofp_version(protocol);
-    request = ofputil_encode_queue_get_config_request(version, port);
-    dump_transaction(vconn, request);
+    struct vconn *vconn;
+    enum ofputil_protocol protocol = open_vconn(vconn_name, &vconn);
+    enum ofp_version version = ofputil_protocol_to_ofp_version(protocol);
+    if (port == OFPP_ANY && version == OFP10_VERSION) {
+        /* The user requested all queues on all ports.  OpenFlow 1.0 only
+         * supports getting queues for an individual port, so to implement the
+         * user's request we have to get a list of all the ports.
+         *
+         * We use a second vconn to avoid having to accumulate a list of all of
+         * the ports. */
+        struct vconn *vconn2;
+        enum ofputil_protocol protocol2 = open_vconn(vconn_name, &vconn2);
+        enum ofp_version version2 = ofputil_protocol_to_ofp_version(protocol2);
+
+        struct port_iterator pi;
+        struct ofputil_phy_port pp;
+        for (port_iterator_init(&pi, vconn); port_iterator_next(&pi, &pp); ) {
+            if (ofp_to_u16(pp.port_no) < ofp_to_u16(OFPP_MAX)) {
+                dump_transaction(vconn2,
+                                 ofputil_encode_queue_get_config_request(
+                                     version2, pp.port_no));
+            }
+        }
+        port_iterator_destroy(&pi);
+        vconn_close(vconn2);
+    } else {
+        dump_transaction(vconn, ofputil_encode_queue_get_config_request(
+                             version, port));
+    }
     vconn_close(vconn);
 }
 
@@ -1313,6 +1350,7 @@ bundle_flow_mod__(const char *remote, struct ofputil_flow_mod *fms,
     }
 
     bundle_transact(vconn, &requests, OFPBF_ORDERED | OFPBF_ATOMIC);
+    ofpbuf_list_delete(&requests);
     vconn_close(vconn);
 }
 
@@ -1931,6 +1969,70 @@ found:
     vconn_close(vconn);
 }
 
+/* This function uses OFPMP14_TABLE_DESC request to get the current
+ * table configuration from switch. The function then modifies
+ * only that table-config property, which has been requested. */
+static void
+fetch_table_desc(struct vconn *vconn, struct ofputil_table_mod *tm,
+                 struct ofputil_table_desc *td)
+{
+    struct ofpbuf *request;
+    ovs_be32 send_xid;
+    bool done = false;
+    bool found = false;
+
+    request = ofputil_encode_table_desc_request(vconn_get_version(vconn));
+    send_xid = ((struct ofp_header *) request->data)->xid;
+    send_openflow_buffer(vconn, request);
+    while (!done) {
+        ovs_be32 recv_xid;
+        struct ofpbuf *reply;
+
+        run(vconn_recv_block(vconn, &reply), "OpenFlow packet receive failed");
+        recv_xid = ((struct ofp_header *) reply->data)->xid;
+        if (send_xid == recv_xid) {
+            struct ofp_header *oh = reply->data;
+            enum ofptype type;
+            struct ofpbuf b;
+            uint16_t flags;
+
+            ofpbuf_use_const(&b, oh, ntohs(oh->length));
+            if (ofptype_pull(&type, &b)
+                || type != OFPTYPE_TABLE_DESC_REPLY) {
+                ovs_fatal(0, "received bad reply: %s",
+                          ofp_to_string(reply->data, reply->size,
+                                        verbosity + 1));
+            }
+            flags = ofpmp_flags(oh);
+            done = !(flags & OFPSF_REPLY_MORE);
+            if (found) {
+                /* We've already found the table desc consisting of current
+                 * table configuration, but we need to drain the queue of
+                 * any other replies for this request. */
+                continue;
+            }
+            while (!ofputil_decode_table_desc(&b, td, oh->version)) {
+                if (td->table_id == tm->table_id) {
+                    found = true;
+                    break;
+                }
+            }
+        } else {
+            VLOG_DBG("received reply with xid %08"PRIx32" "
+                     "!= expected %08"PRIx32, recv_xid, send_xid);
+        }
+        ofpbuf_delete(reply);
+    }
+    if (tm->eviction != OFPUTIL_TABLE_EVICTION_DEFAULT) {
+        tm->vacancy = td->vacancy;
+        tm->table_vacancy.vacancy_down = td->table_vacancy.vacancy_down;
+        tm->table_vacancy.vacancy_up = td->table_vacancy.vacancy_up;
+    } else if (tm->vacancy != OFPUTIL_TABLE_VACANCY_DEFAULT) {
+        tm->eviction = td->eviction;
+        tm->eviction_flags = td->eviction_flags;
+    }
+}
+
 static void
 ofctl_mod_table(struct ovs_cmdl_context *ctx)
 {
@@ -1938,6 +2040,7 @@ ofctl_mod_table(struct ovs_cmdl_context *ctx)
     struct ofputil_table_mod tm;
     struct vconn *vconn;
     char *error;
+    int i;
 
     error = parse_ofp_table_mod(&tm, ctx->argv[2], ctx->argv[3],
                                 &usable_versions);
@@ -1948,15 +2051,36 @@ ofctl_mod_table(struct ovs_cmdl_context *ctx)
     uint32_t allowed_versions = get_allowed_ofp_versions();
     if (!(allowed_versions & usable_versions)) {
         struct ds versions = DS_EMPTY_INITIALIZER;
-        ofputil_format_version_bitmap_names(&versions, allowed_versions);
+        ofputil_format_version_bitmap_names(&versions, usable_versions);
         ovs_fatal(0, "table_mod '%s' requires one of the OpenFlow "
-                  "versions %s but none is enabled (use -O)",
+                  "versions %s",
                   ctx->argv[3], ds_cstr(&versions));
     }
     mask_allowed_ofp_versions(usable_versions);
-
     enum ofputil_protocol protocol = open_vconn(ctx->argv[1], &vconn);
-    transact_noreply(vconn, ofputil_encode_table_mod(&tm, protocol));
+
+    /* For OpenFlow 1.4+, ovs-ofctl mod-table should not affect table-config
+     * properties that the user didn't ask to change, so it is necessary to
+     * restore the current configuration of table-config parameters using
+     * OFPMP14_TABLE_DESC request. */
+    if ((allowed_versions & (1u << OFP14_VERSION)) ||
+        (allowed_versions & (1u << OFP15_VERSION))) {
+        struct ofputil_table_desc td;
+
+        if (tm.table_id == OFPTT_ALL) {
+            for (i = 0; i < OFPTT_MAX; i++) {
+                tm.table_id = i;
+                fetch_table_desc(vconn, &tm, &td);
+                transact_noreply(vconn,
+                                 ofputil_encode_table_mod(&tm, protocol));
+            }
+        } else {
+            fetch_table_desc(vconn, &tm, &td);
+            transact_noreply(vconn, ofputil_encode_table_mod(&tm, protocol));
+        }
+    } else {
+        transact_noreply(vconn, ofputil_encode_table_mod(&tm, protocol));
+    }
     vconn_close(vconn);
 }
 
@@ -2380,7 +2504,7 @@ ofctl_dump_group_desc(struct ovs_cmdl_context *ctx)
     open_vconn(ctx->argv[1], &vconn);
 
     if (ctx->argc < 3 || !ofputil_group_from_string(ctx->argv[2], &group_id)) {
-        group_id = OFPG11_ALL;
+        group_id = OFPG_ALL;
     }
 
     request = ofputil_encode_group_desc_request(vconn_get_version(vconn),
@@ -2408,17 +2532,17 @@ ofctl_dump_group_features(struct ovs_cmdl_context *ctx)
 }
 
 static void
-ofctl_geneve_mod(struct ovs_cmdl_context *ctx, uint16_t command)
+ofctl_tlv_mod(struct ovs_cmdl_context *ctx, uint16_t command)
 {
     enum ofputil_protocol usable_protocols;
     enum ofputil_protocol protocol;
-    struct ofputil_geneve_table_mod gtm;
+    struct ofputil_tlv_table_mod ttm;
     char *error;
     enum ofp_version version;
     struct ofpbuf *request;
     struct vconn *vconn;
 
-    error = parse_ofp_geneve_table_mod_str(&gtm, command, ctx->argc > 2 ?
+    error = parse_ofp_tlv_table_mod_str(&ttm, command, ctx->argc > 2 ?
                                            ctx->argv[2] : "",
                                            &usable_protocols);
     if (error) {
@@ -2428,31 +2552,31 @@ ofctl_geneve_mod(struct ovs_cmdl_context *ctx, uint16_t command)
     protocol = open_vconn_for_flow_mod(ctx->argv[1], &vconn, usable_protocols);
     version = ofputil_protocol_to_ofp_version(protocol);
 
-    request = ofputil_encode_geneve_table_mod(version, &gtm);
+    request = ofputil_encode_tlv_table_mod(version, &ttm);
     if (request) {
         transact_noreply(vconn, request);
     }
 
     vconn_close(vconn);
-    ofputil_uninit_geneve_table(&gtm.mappings);
+    ofputil_uninit_tlv_table(&ttm.mappings);
 }
 
 static void
-ofctl_add_geneve_map(struct ovs_cmdl_context *ctx)
+ofctl_add_tlv_map(struct ovs_cmdl_context *ctx)
 {
-    ofctl_geneve_mod(ctx, NXGTMC_ADD);
+    ofctl_tlv_mod(ctx, NXTTMC_ADD);
 }
 
 static void
-ofctl_del_geneve_map(struct ovs_cmdl_context *ctx)
+ofctl_del_tlv_map(struct ovs_cmdl_context *ctx)
 {
-    ofctl_geneve_mod(ctx, ctx->argc > 2 ? NXGTMC_DELETE : NXGTMC_CLEAR);
+    ofctl_tlv_mod(ctx, ctx->argc > 2 ? NXTTMC_DELETE : NXTTMC_CLEAR);
 }
 
 static void
-ofctl_dump_geneve_map(struct ovs_cmdl_context *ctx)
+ofctl_dump_tlv_map(struct ovs_cmdl_context *ctx)
 {
-    dump_trivial_transaction(ctx->argv[1], OFPRAW_NXT_GENEVE_TABLE_REQUEST);
+    dump_trivial_transaction(ctx->argv[1], OFPRAW_NXT_TLV_TABLE_REQUEST);
 }
 
 static void
@@ -2469,6 +2593,45 @@ ofctl_list_commands(struct ovs_cmdl_context *ctx OVS_UNUSED)
 \f
 /* replace-flows and diff-flows commands. */
 
+struct flow_tables {
+    struct classifier tables[OFPTT_MAX + 1];
+};
+
+#define FOR_EACH_TABLE(CLS, TABLES)                               \
+    for ((CLS) = (TABLES)->tables;                                \
+         (CLS) < &(TABLES)->tables[ARRAY_SIZE((TABLES)->tables)]; \
+         (CLS)++)
+
+static void
+flow_tables_init(struct flow_tables *tables)
+{
+    struct classifier *cls;
+
+    FOR_EACH_TABLE (cls, tables) {
+        classifier_init(cls, NULL);
+    }
+}
+
+static void
+flow_tables_defer(struct flow_tables *tables)
+{
+    struct classifier *cls;
+
+    FOR_EACH_TABLE (cls, tables) {
+        classifier_defer(cls);
+    }
+}
+
+static void
+flow_tables_publish(struct flow_tables *tables)
+{
+    struct classifier *cls;
+
+    FOR_EACH_TABLE (cls, tables) {
+        classifier_publish(cls);
+    }
+}
+
 /* A flow table entry, possibly with two different versions. */
 struct fte {
     struct cls_rule rule;       /* Within a "struct classifier". */
@@ -2484,6 +2647,7 @@ struct fte_version {
     uint16_t flags;
     struct ofpact *ofpacts;
     size_t ofpacts_len;
+    uint8_t table_id;
 };
 
 /* Frees 'version' and the data that it owns. */
@@ -2507,6 +2671,7 @@ fte_version_equals(const struct fte_version *a, const struct fte_version *b)
             && a->idle_timeout == b->idle_timeout
             && a->hard_timeout == b->hard_timeout
             && a->importance == b->importance
+            && a->table_id == b->table_id
             && ofpacts_equal(a->ofpacts, a->ofpacts_len,
                              b->ofpacts, b->ofpacts_len));
 }
@@ -2523,6 +2688,9 @@ fte_version_format(const struct fte *fte, int index, struct ds *s)
         return;
     }
 
+    if (version->table_id) {
+        ds_put_format(s, "table=%"PRIu8" ", version->table_id);
+    }
     cls_rule_format(&fte->rule, s);
     if (version->cookie != htonll(0)) {
         ds_put_format(s, " cookie=0x%"PRIx64, ntohll(version->cookie));
@@ -2561,29 +2729,34 @@ fte_free(struct fte *fte)
     }
 }
 
-/* Frees all of the FTEs within 'cls'. */
+/* Frees all of the FTEs within 'tables'. */
 static void
-fte_free_all(struct classifier *cls)
+fte_free_all(struct flow_tables *tables)
 {
-    struct fte *fte;
+    struct classifier *cls;
+
+    FOR_EACH_TABLE (cls, tables) {
+        struct fte *fte;
 
-    classifier_defer(cls);
-    CLS_FOR_EACH (fte, rule, cls) {
-        classifier_remove(cls, &fte->rule);
-        ovsrcu_postpone(fte_free, fte);
+        classifier_defer(cls);
+        CLS_FOR_EACH (fte, rule, cls) {
+            classifier_remove(cls, &fte->rule);
+            ovsrcu_postpone(fte_free, fte);
+        }
+        classifier_destroy(cls);
     }
-    classifier_destroy(cls);
 }
 
-/* Searches 'cls' for an FTE matching 'rule', inserting a new one if
+/* Searches 'tables' for an FTE matching 'rule', inserting a new one if
  * necessary.  Sets 'version' as the version of that rule with the given
  * 'index', replacing any existing version, if any.
  *
  * Takes ownership of 'version'. */
 static void
-fte_insert(struct classifier *cls, const struct match *match,
+fte_insert(struct flow_tables *tables, const struct match *match,
            int priority, struct fte_version *version, int index)
 {
+    struct classifier *cls = &tables->tables[version->table_id];
     struct fte *old, *fte;
 
     fte = xzalloc(sizeof *fte);
@@ -2600,11 +2773,12 @@ fte_insert(struct classifier *cls, const struct match *match,
     }
 }
 
-/* Reads the flows in 'filename' as flow table entries in 'cls' for the version
- * with the specified 'index'.  Returns the flow formats able to represent the
- * flows that were read. */
+/* Reads the flows in 'filename' as flow table entries in 'tables' for the
+ * version with the specified 'index'.  Returns the flow formats able to
+ * represent the flows that were read. */
 static enum ofputil_protocol
-read_flows_from_file(const char *filename, struct classifier *cls, int index)
+read_flows_from_file(const char *filename, struct flow_tables *tables,
+                     int index)
 {
     enum ofputil_protocol usable_protocols;
     int line_number;
@@ -2619,7 +2793,7 @@ read_flows_from_file(const char *filename, struct classifier *cls, int index)
     ds_init(&s);
     usable_protocols = OFPUTIL_P_ANY;
     line_number = 0;
-    classifier_defer(cls);
+    flow_tables_defer(tables);
     while (!ds_get_preprocessed_line(&s, file, &line_number)) {
         struct fte_version *version;
         struct ofputil_flow_mod fm;
@@ -2641,10 +2815,11 @@ read_flows_from_file(const char *filename, struct classifier *cls, int index)
                                      | OFPUTIL_FF_EMERG);
         version->ofpacts = fm.ofpacts;
         version->ofpacts_len = fm.ofpacts_len;
+        version->table_id = fm.table_id != OFPTT_ALL ? fm.table_id : 0;
 
-        fte_insert(cls, &fm.match, fm.priority, version, index);
+        fte_insert(tables, &fm.match, fm.priority, version, index);
     }
-    classifier_publish(cls);
+    flow_tables_publish(tables);
     ds_destroy(&s);
 
     if (file != stdin) {
@@ -2708,12 +2883,12 @@ recv_flow_stats_reply(struct vconn *vconn, ovs_be32 send_xid,
 }
 
 /* Reads the OpenFlow flow table from 'vconn', which has currently active flow
- * format 'protocol', and adds them as flow table entries in 'cls' for the
+ * format 'protocol', and adds them as flow table entries in 'tables' for the
  * version with the specified 'index'. */
 static void
 read_flows_from_switch(struct vconn *vconn,
                        enum ofputil_protocol protocol,
-                       struct classifier *cls, int index)
+                       struct flow_tables *tables, int index)
 {
     struct ofputil_flow_stats_request fsr;
     struct ofputil_flow_stats fs;
@@ -2734,7 +2909,7 @@ read_flows_from_switch(struct vconn *vconn,
 
     reply = NULL;
     ofpbuf_init(&ofpacts, 0);
-    classifier_defer(cls);
+    flow_tables_defer(tables);
     while (recv_flow_stats_reply(vconn, send_xid, &reply, &fs, &ofpacts)) {
         struct fte_version *version;
 
@@ -2746,10 +2921,11 @@ read_flows_from_switch(struct vconn *vconn,
         version->flags = 0;
         version->ofpacts_len = fs.ofpacts_len;
         version->ofpacts = xmemdup(fs.ofpacts, fs.ofpacts_len);
+        version->table_id = fs.table_id;
 
-        fte_insert(cls, &fs.match, fs.priority, version, index);
+        fte_insert(tables, &fs.match, fs.priority, version, index);
     }
-    classifier_publish(cls);
+    flow_tables_publish(tables);
     ofpbuf_uninit(&ofpacts);
 }
 
@@ -2758,24 +2934,24 @@ fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
                   enum ofputil_protocol protocol, struct ovs_list *packets)
 {
     const struct fte_version *version = fte->versions[index];
-    struct ofputil_flow_mod fm;
     struct ofpbuf *ofm;
 
+    struct ofputil_flow_mod fm = {
+        .priority = fte->rule.priority,
+        .new_cookie = version->cookie,
+        .modify_cookie = true,
+        .table_id = version->table_id,
+        .command = command,
+        .idle_timeout = version->idle_timeout,
+        .hard_timeout = version->hard_timeout,
+        .importance = version->importance,
+        .buffer_id = UINT32_MAX,
+        .out_port = OFPP_ANY,
+        .out_group = OFPG_ANY,
+        .flags = version->flags,
+        .delete_reason = OFPRR_DELETE,
+    };
     minimatch_expand(&fte->rule.match, &fm.match);
-    fm.priority = fte->rule.priority;
-    fm.cookie = htonll(0);
-    fm.cookie_mask = htonll(0);
-    fm.new_cookie = version->cookie;
-    fm.modify_cookie = true;
-    fm.table_id = 0xff;
-    fm.command = command;
-    fm.idle_timeout = version->idle_timeout;
-    fm.hard_timeout = version->hard_timeout;
-    fm.importance = version->importance;
-    fm.buffer_id = UINT32_MAX;
-    fm.out_port = OFPP_ANY;
-    fm.out_group = OFPG_ANY;
-    fm.flags = version->flags;
     if (command == OFPFC_ADD || command == OFPFC_MODIFY ||
         command == OFPFC_MODIFY_STRICT) {
         fm.ofpacts = version->ofpacts;
@@ -2784,7 +2960,6 @@ fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
         fm.ofpacts = NULL;
         fm.ofpacts_len = 0;
     }
-    fm.delete_reason = OFPRR_DELETE;
 
     ofm = ofputil_encode_flow_mod(&fm, protocol);
     list_push_back(packets, &ofm->list_node);
@@ -2795,41 +2970,45 @@ ofctl_replace_flows(struct ovs_cmdl_context *ctx)
 {
     enum { FILE_IDX = 0, SWITCH_IDX = 1 };
     enum ofputil_protocol usable_protocols, protocol;
-    struct classifier cls;
+    struct flow_tables tables;
+    struct classifier *cls;
     struct ovs_list requests;
     struct vconn *vconn;
     struct fte *fte;
 
-    classifier_init(&cls, NULL);
-    usable_protocols = read_flows_from_file(ctx->argv[2], &cls, FILE_IDX);
+    flow_tables_init(&tables);
+    usable_protocols = read_flows_from_file(ctx->argv[2], &tables, FILE_IDX);
 
     protocol = open_vconn(ctx->argv[1], &vconn);
     protocol = set_protocol_for_flow_dump(vconn, protocol, usable_protocols);
 
-    read_flows_from_switch(vconn, protocol, &cls, SWITCH_IDX);
+    read_flows_from_switch(vconn, protocol, &tables, SWITCH_IDX);
 
     list_init(&requests);
 
-    /* Delete flows that exist on the switch but not in the file. */
-    CLS_FOR_EACH (fte, rule, &cls) {
-        struct fte_version *file_ver = fte->versions[FILE_IDX];
-        struct fte_version *sw_ver = fte->versions[SWITCH_IDX];
+    FOR_EACH_TABLE (cls, &tables) {
+        /* Delete flows that exist on the switch but not in the file. */
+        CLS_FOR_EACH (fte, rule, cls) {
+            struct fte_version *file_ver = fte->versions[FILE_IDX];
+            struct fte_version *sw_ver = fte->versions[SWITCH_IDX];
 
-        if (sw_ver && !file_ver) {
-            fte_make_flow_mod(fte, SWITCH_IDX, OFPFC_DELETE_STRICT,
-                              protocol, &requests);
+            if (sw_ver && !file_ver) {
+                fte_make_flow_mod(fte, SWITCH_IDX, OFPFC_DELETE_STRICT,
+                                  protocol, &requests);
+            }
         }
-    }
 
-    /* Add flows that exist in the file but not on the switch.
-     * Update flows that exist in both places but differ. */
-    CLS_FOR_EACH (fte, rule, &cls) {
-        struct fte_version *file_ver = fte->versions[FILE_IDX];
-        struct fte_version *sw_ver = fte->versions[SWITCH_IDX];
+        /* Add flows that exist in the file but not on the switch.
+         * Update flows that exist in both places but differ. */
+        CLS_FOR_EACH (fte, rule, cls) {
+            struct fte_version *file_ver = fte->versions[FILE_IDX];
+            struct fte_version *sw_ver = fte->versions[SWITCH_IDX];
 
-        if (file_ver
-            && (readd || !sw_ver || !fte_version_equals(sw_ver, file_ver))) {
-            fte_make_flow_mod(fte, FILE_IDX, OFPFC_ADD, protocol, &requests);
+            if (file_ver &&
+                (readd || !sw_ver || !fte_version_equals(sw_ver, file_ver))) {
+                fte_make_flow_mod(fte, FILE_IDX, OFPFC_ADD, protocol,
+                                  &requests);
+            }
         }
     }
     if (bundle) {
@@ -2837,26 +3016,29 @@ ofctl_replace_flows(struct ovs_cmdl_context *ctx)
     } else {
         transact_multiple_noreply(vconn, &requests);
     }
+
+    ofpbuf_list_delete(&requests);
     vconn_close(vconn);
 
-    fte_free_all(&cls);
+    fte_free_all(&tables);
 }
 
 static void
-read_flows_from_source(const char *source, struct classifier *cls, int index)
+read_flows_from_source(const char *source, struct flow_tables *tables,
+                       int index)
 {
     struct stat s;
 
     if (source[0] == '/' || source[0] == '.'
         || (!strchr(source, ':') && !stat(source, &s))) {
-        read_flows_from_file(source, cls, index);
+        read_flows_from_file(source, tables, index);
     } else {
         enum ofputil_protocol protocol;
         struct vconn *vconn;
 
         protocol = open_vconn(source, &vconn);
         protocol = set_protocol_for_flow_dump(vconn, protocol, OFPUTIL_P_ANY);
-        read_flows_from_switch(vconn, protocol, cls, index);
+        read_flows_from_switch(vconn, protocol, tables, index);
         vconn_close(vconn);
     }
 }
@@ -2865,32 +3047,35 @@ static void
 ofctl_diff_flows(struct ovs_cmdl_context *ctx)
 {
     bool differences = false;
-    struct classifier cls;
+    struct flow_tables tables;
+    struct classifier *cls;
     struct ds a_s, b_s;
     struct fte *fte;
 
-    classifier_init(&cls, NULL);
-    read_flows_from_source(ctx->argv[1], &cls, 0);
-    read_flows_from_source(ctx->argv[2], &cls, 1);
+    flow_tables_init(&tables);
+    read_flows_from_source(ctx->argv[1], &tables, 0);
+    read_flows_from_source(ctx->argv[2], &tables, 1);
 
     ds_init(&a_s);
     ds_init(&b_s);
 
-    CLS_FOR_EACH (fte, rule, &cls) {
-        struct fte_version *a = fte->versions[0];
-        struct fte_version *b = fte->versions[1];
-
-        if (!a || !b || !fte_version_equals(a, b)) {
-            fte_version_format(fte, 0, &a_s);
-            fte_version_format(fte, 1, &b_s);
-            if (strcmp(ds_cstr(&a_s), ds_cstr(&b_s))) {
-                if (a_s.length) {
-                    printf("-%s", ds_cstr(&a_s));
-                }
-                if (b_s.length) {
-                    printf("+%s", ds_cstr(&b_s));
+    FOR_EACH_TABLE (cls, &tables) {
+        CLS_FOR_EACH (fte, rule, cls) {
+            struct fte_version *a = fte->versions[0];
+            struct fte_version *b = fte->versions[1];
+
+            if (!a || !b || !fte_version_equals(a, b)) {
+                fte_version_format(fte, 0, &a_s);
+                fte_version_format(fte, 1, &b_s);
+                if (strcmp(ds_cstr(&a_s), ds_cstr(&b_s))) {
+                    if (a_s.length) {
+                        printf("-%s", ds_cstr(&a_s));
+                    }
+                    if (b_s.length) {
+                        printf("+%s", ds_cstr(&b_s));
+                    }
+                    differences = true;
                 }
-                differences = true;
             }
         }
     }
@@ -2898,7 +3083,7 @@ ofctl_diff_flows(struct ovs_cmdl_context *ctx)
     ds_destroy(&a_s);
     ds_destroy(&b_s);
 
-    fte_free_all(&cls);
+    fte_free_all(&tables);
 
     if (differences) {
         exit(2);
@@ -2955,9 +3140,9 @@ ofctl_meter_request__(const char *bridge, const char *str,
 
     protocol = open_vconn_for_flow_mod(bridge, &vconn, usable_protocols);
     version = ofputil_protocol_to_ofp_version(protocol);
-    transact_noreply(vconn, ofputil_encode_meter_request(version,
-                                                         type,
-                                                         mm.meter.meter_id));
+    dump_stats_transaction(vconn,
+                           ofputil_encode_meter_request(version, type,
+                                                        mm.meter.meter_id));
     vconn_close(vconn);
 }
 
@@ -3245,7 +3430,7 @@ ofctl_parse_actions__(const char *version_s, bool instructions)
             error = ofpacts_check_consistency(ofpacts.data, ofpacts.size,
                                               &flow, OFPP_MAX,
                                               table_id ? atoi(table_id) : 0,
-                                              255, protocol);
+                                              OFPTT_MAX + 1, protocol);
         }
         if (error) {
             printf("bad %s %s: %s\n\n",
@@ -3706,8 +3891,8 @@ static const struct ovs_cmdl_command all_commands[] = {
       1, 2, ofctl_dump_aggregate },
     { "queue-stats", "switch [port [queue]]",
       1, 3, ofctl_queue_stats },
-    { "queue-get-config", "switch port",
-      2, 2, ofctl_queue_get_config },
+    { "queue-get-config", "switch [port]",
+      1, 2, ofctl_queue_get_config },
     { "add-flow", "switch flow",
       2, 2, ofctl_add_flow },
     { "add-flows", "switch file",
@@ -3780,12 +3965,12 @@ static const struct ovs_cmdl_command all_commands[] = {
       1, 2, ofctl_dump_group_stats },
     { "dump-group-features", "switch",
       1, 1, ofctl_dump_group_features },
-    { "add-geneve-map", "switch map",
-      2, 2, ofctl_add_geneve_map },
-    { "del-geneve-map", "switch [map]",
-      1, 2, ofctl_del_geneve_map },
-    { "dump-geneve-map", "switch",
-      1, 1, ofctl_dump_geneve_map },
+    { "add-tlv-map", "switch map",
+      2, 2, ofctl_add_tlv_map },
+    { "del-tlv-map", "switch [map]",
+      1, 2, ofctl_del_tlv_map },
+    { "dump-tlv-map", "switch",
+      1, 1, ofctl_dump_tlv_map },
     { "help", NULL, 0, INT_MAX, ofctl_help },
     { "list-commands", NULL, 0, INT_MAX, ofctl_list_commands },