[ovs-dev] [of1.1 draft 03/10] Introduce ofputil_protocol, to abstract the protocol in use on a connection.

Ben Pfaff blp at nicira.com
Thu Dec 8 04:17:22 UTC 2011


Open vSwitch already handles a few different protocol variations, but it
does so in a nonuniform manner:

  - OpenFlow 1.0 and NXM flow formats are distinguished using the NXFF_*
    constant values from nicira-ext.h.

  - The "flow_mod_table_id" feature setting is maintained in ofproto as
    part of an OpenFlow connection's (ofconn's) state.

There's no way to easily communicate this state among components.  It's
not much of a problem yet, but as more protocol support is added it seems
better to have an abstract, uniform way to represent protocol versions and
variants.  This commit implements that by introducing a new type
"enum ofputil_protocol".  Each ofputil_protocol value represents a variant
of a protocol version.  Each value is a separate bit, so a single enum
can also represent a set of protocols, which is often useful as well.

This commit needs the following improvements:

  - learning-switch.c should understand how to negotiate the protocol, so
    that it can properly implement the ovs-controller --with-flows option
    based on what the switch supports.

  - ovs-ofctl needs documentation updates, possibly ovs-controller too.
---
 lib/learning-switch.c      |   20 ++-
 lib/learning-switch.h      |    8 +-
 lib/ofp-parse.c            |   70 +++-----
 lib/ofp-parse.h            |   13 +-
 lib/ofp-print.c            |    6 +-
 lib/ofp-util.c             |  433 ++++++++++++++++++++++++++++++++++++--------
 lib/ofp-util.h             |   85 +++++++--
 lib/rconn.c                |    6 +
 lib/rconn.h                |    1 +
 lib/util.h                 |    6 +
 lib/vconn.c                |   21 ++-
 lib/vconn.h                |    1 +
 ofproto/connmgr.c          |   41 ++---
 ofproto/connmgr.h          |    7 +-
 ofproto/ofproto.c          |   32 +++-
 tests/autopath.at          |    8 +-
 tests/learn.at             |   19 ++-
 tests/ovs-ofctl.at         |   65 +++++--
 utilities/ovs-controller.c |   35 +---
 utilities/ovs-ofctl.c      |  421 +++++++++++++++++++++++-------------------
 20 files changed, 844 insertions(+), 454 deletions(-)

diff --git a/lib/learning-switch.c b/lib/learning-switch.c
index c47fcb6..4bf4724 100644
--- a/lib/learning-switch.c
+++ b/lib/learning-switch.c
@@ -137,15 +137,25 @@ lswitch_create(struct rconn *rconn, const struct lswitch_config *cfg)
     send_features_request(sw, rconn);
 
     if (cfg->default_flows) {
-        const struct ofpbuf *b;
+        enum ofputil_protocol protocol;
+        int ofp_version;
+        size_t i;
 
-        LIST_FOR_EACH (b, list_node, cfg->default_flows) {
-            struct ofpbuf *copy = ofpbuf_clone(b);
-            int error = rconn_send(rconn, copy, NULL);
+        /* XXX negotiate protocol */
+
+        ofp_version = rconn_get_version(rconn);
+        protocol = ofputil_protocol_from_ofp_version(ofp_version);
+
+        for (i = 0; i < cfg->n_default_flows; i++) {
+            struct ofpbuf *msg;
+            int error;
+
+            msg = ofputil_encode_flow_mod(&cfg->default_flows[i], protocol);
+            error = rconn_send(rconn, msg, NULL);
             if (error) {
                 VLOG_INFO_RL(&rl, "%s: failed to queue default flows (%s)",
                              rconn_get_name(rconn), strerror(error));
-                ofpbuf_delete(copy);
+                ofpbuf_delete(msg);
                 break;
             }
         }
diff --git a/lib/learning-switch.h b/lib/learning-switch.h
index c6f347e..833b94d 100644
--- a/lib/learning-switch.h
+++ b/lib/learning-switch.h
@@ -46,10 +46,10 @@ struct lswitch_config {
      * OFP_FLOW_PERMANENT: Set up permanent flows. */
     int max_idle;
 
-    /* Optionally, a list of one or more "struct ofpbuf"s containing OpenFlow
-     * messages to send to the switch at time of connection.  Presumably these
-     * will be OFPT_FLOW_MOD requests to set up the flow table. */
-    const struct list *default_flows;
+    /* Optional "flow mod" requests to send to the switch at connection time,
+     * to set up the flow table. */
+    const struct ofputil_flow_mod *default_flows;
+    size_t n_default_flows;
 
     /* The OpenFlow queue to use by default.  Use UINT32_MAX to avoid
      * specifying a particular queue. */
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 4021551..9ce9eb4 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -607,67 +607,49 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
 }
 
 /* Parses 'string' as an OFPT_FLOW_MOD or NXT_FLOW_MOD with command 'command'
- * (one of OFPFC_*) and appends the parsed OpenFlow message to 'packets'.
- * '*cur_format' should initially contain the flow format currently configured
- * on the connection; this function will add a message to change the flow
- * format and update '*cur_format', if this is necessary to add the parsed
- * flow. */
+ * (one of OFPFC_*) into 'fm'. */
 void
-parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur_format,
-                       bool *flow_mod_table_id, char *string, uint16_t command,
-                       bool verbose)
+parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm, char *string,
+                       uint16_t command, bool verbose)
 {
-    enum nx_flow_format min_format, next_format;
     struct cls_rule rule_copy;
-    struct ofpbuf *ofm;
-    struct ofputil_flow_mod fm;
-
-    parse_ofp_str(&fm, command, string, verbose);
 
-    min_format = ofputil_min_flow_format(&fm.cr);
-    next_format = MAX(*cur_format, min_format);
-    if (next_format != *cur_format) {
-        struct ofpbuf *sff = ofputil_make_set_flow_format(next_format);
-        list_push_back(packets, &sff->list_node);
-        *cur_format = next_format;
-    }
+    parse_ofp_str(fm, command, string, verbose);
 
     /* Normalize a copy of the rule.  This ensures that non-normalized flows
      * get logged but doesn't affect what gets sent to the switch, so that the
      * switch can do whatever it likes with the flow. */
-    rule_copy = fm.cr;
-    ofputil_normalize_rule(&rule_copy, next_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);
+    rule_copy = fm->cr;
+    ofputil_normalize_rule(&rule_copy);
 }
 
-/* Similar to parse_ofp_flow_mod_str(), except that the string is read from
- * '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, bool *flow_mod_table_id,
-                        FILE *stream, uint16_t command)
+void
+parse_ofp_flow_mod_file(const char *file_name, uint16_t command,
+                        struct ofputil_flow_mod **fms, size_t *n_fms)
 {
+    size_t allocated_fms;
+    FILE *stream;
     struct ds s;
-    bool ok;
 
+    stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r");
+    if (stream == NULL) {
+        ovs_fatal(errno, "%s: open", file_name);
+    }
+
+    allocated_fms = *n_fms;
     ds_init(&s);
-    ok = ds_get_preprocessed_line(&s, stream) == 0;
-    if (ok) {
-        parse_ofp_flow_mod_str(packets, cur, flow_mod_table_id,
-                               ds_cstr(&s), command, true);
+    while (!ds_get_preprocessed_line(&s, stream)) {
+        if (*n_fms >= allocated_fms) {
+            *fms = x2nrealloc(*fms, &allocated_fms, sizeof **fms);
+        }
+        parse_ofp_flow_mod_str(&(*fms)[*n_fms], ds_cstr(&s), command, false);
+        *n_fms += 1;
     }
     ds_destroy(&s);
 
-    return ok;
+    if (stream != stdin) {
+        fclose(stream);
+    }
 }
 
 void
diff --git a/lib/ofp-parse.h b/lib/ofp-parse.h
index 80fca97..430d7d3 100644
--- a/lib/ofp-parse.h
+++ b/lib/ofp-parse.h
@@ -22,22 +22,17 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
-#include "openflow/nicira-ext.h"
 
-struct list;
-struct ofpbuf;
 struct ofputil_flow_mod;
 struct ofputil_flow_stats_request;
 
 void parse_ofp_str(struct ofputil_flow_mod *, int command, const char *str_,
                    bool verbose);
 
-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 verbose);
-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_mod_str(struct ofputil_flow_mod *, char *string,
+                            uint16_t command, bool verbose);
+void parse_ofp_flow_mod_file(const char *file_name, uint16_t command,
+                             struct ofputil_flow_mod **fms, size_t *n_fms);
 
 void parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *,
                                       bool aggregate, char *string);
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index c231a15..0527a3d 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -755,7 +755,7 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
     bool need_priority;
     enum ofperr error;
 
-    error = ofputil_decode_flow_mod(&fm, oh, true);
+    error = ofputil_decode_flow_mod(&fm, oh, OFPUTIL_P_OF10_TID);
     if (error) {
         ofp_print_error(s, error);
         return;
@@ -1264,8 +1264,8 @@ ofp_print_nxt_set_flow_format(struct ds *string,
     uint32_t format = ntohl(nsff->format);
 
     ds_put_cstr(string, " format=");
-    if (ofputil_flow_format_is_valid(format)) {
-        ds_put_cstr(string, ofputil_flow_format_to_string(format));
+    if (ofputil_nx_flow_format_is_valid(format)) {
+        ds_put_cstr(string, ofputil_nx_flow_format_to_string(format));
     } else {
         ds_put_format(string, "%"PRIu32, format);
     }
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 0bce510..04468fe 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -802,39 +802,253 @@ ofputil_msg_type_code(const struct ofputil_msg_type *type)
     return type->code;
 }
 
-/* Flow formats. */
+/* Protocols. */
 
+struct proto_abbrev {
+    enum ofputil_protocol protocol;
+    const char *name;
+};
+
+/* A map from OFPUTIL_P_* values to abbreviated protocol names that may be
+ * specified by users.
+ *
+ * Some of the differences between protocols really aren't of interest to
+ * users.  These abbreviations avoid exposing users to distinctions that they
+ * don't care about.
+ */
+static const struct proto_abbrev proto_abbrevs[] = {
+    { OFPUTIL_P_ANY,      "any" },
+    { OFPUTIL_P_OF10_ANY, "OpenFlow10" },
+    { OFPUTIL_P_NXM_ANY,  "NXM" },
+};
+#define N_PROTO_ABBREVS ARRAY_SIZE(proto_abbrevs)
+
+enum ofputil_protocol ofputil_flow_dump_protocols[] = {
+    OFPUTIL_P_NXM,
+    OFPUTIL_P_OF10,
+};
+size_t ofputil_n_flow_dump_protocols = ARRAY_SIZE(ofputil_flow_dump_protocols);
+
+/* Returns the ofputil_protocol that is initially in effect on an OpenFlow
+ * connection that has negotiated the given 'version'.  'version' should
+ * normally be an 8-bit OpenFlow version identifier (e.g. 0x01 for OpenFlow
+ * 1.0, 0x02 for OpenFlow 1.1).  Returns 0 if 'version' is not supported or
+ * outside the valid range.  */
+enum ofputil_protocol
+ofputil_protocol_from_ofp_version(int version)
+{
+    switch (version) {
+    case OFP_VERSION: return OFPUTIL_P_OF10;
+    default: return 0;
+    }
+}
+
+/* Returns true if 'protocol' is a single OFPUTIL_P_* value, false
+ * otherwise. */
 bool
-ofputil_flow_format_is_valid(enum nx_flow_format flow_format)
+ofputil_protocol_is_valid(enum ofputil_protocol protocol)
 {
-    switch (flow_format) {
-    case NXFF_OPENFLOW10:
-    case NXFF_NXM:
-        return true;
+    return protocol & OFPUTIL_P_ANY && is_pow2(protocol);
+}
+
+/* Returns the equivalent of 'protocol' with the Nicira flow_mod_table_id
+ * extension turned on or off if 'enable' is true or false, respectively.
+ *
+ * This extension is only useful for protocols whose "standard" version does
+ * not allow specific tables to be modified.  In particular, this is true of
+ * OpenFlow 1.0.  In later versions of OpenFlow, a flow_mod request always
+ * specifies a table ID and so there is no need for such an extension.  When
+ * 'protocol' is such a protocol that doesn't need a flow_mod_table_id
+ * extension, this function just returns its 'protocol' argument unchanged
+ * regardless of the value of 'enable'.  */
+enum ofputil_protocol
+ofputil_protocol_set_tid(enum ofputil_protocol protocol, bool enable)
+{
+    switch (protocol) {
+    case OFPUTIL_P_OF10:
+    case OFPUTIL_P_OF10_TID:
+        return enable ? OFPUTIL_P_OF10_TID : OFPUTIL_P_OF10;
+
+    case OFPUTIL_P_NXM:
+    case OFPUTIL_P_NXM_TID:
+        return enable ? OFPUTIL_P_NXM_TID : OFPUTIL_P_NXM;
+
+    default:
+        NOT_REACHED();
     }
+}
 
-    return false;
+/* Returns the "base" version of 'protocol'.  That is, if 'protocol' includes
+ * some extension to a standard protocol version, the return value is the
+ * standard version of that protocol without any extension.  If 'protocol' is a
+ * standard protocol version, returns 'protocol' unchanged. */
+enum ofputil_protocol
+ofputil_protocol_to_base(enum ofputil_protocol protocol)
+{
+    return ofputil_protocol_set_tid(protocol, false);
 }
 
-const char *
-ofputil_flow_format_to_string(enum nx_flow_format flow_format)
+/* Returns 'new_base' with any extensions taken from 'cur'. */
+enum ofputil_protocol
+ofputil_protocol_set_base(enum ofputil_protocol cur,
+                          enum ofputil_protocol new_base)
 {
-    switch (flow_format) {
-    case NXFF_OPENFLOW10:
-        return "openflow10";
-    case NXFF_NXM:
-        return "nxm";
+    bool tid = (cur & OFPUTIL_P_TID) != 0;
+
+    switch (new_base) {
+    case OFPUTIL_P_OF10:
+    case OFPUTIL_P_OF10_TID:
+        return ofputil_protocol_set_tid(OFPUTIL_P_OF10, tid);
+
+    case OFPUTIL_P_NXM:
+    case OFPUTIL_P_NXM_TID:
+        return ofputil_protocol_set_tid(OFPUTIL_P_NXM, tid);
+
     default:
         NOT_REACHED();
     }
 }
 
-int
-ofputil_flow_format_from_string(const char *s)
+/* Returns a string form of 'protocol', if a simple form exists (that is, if
+ * 'protocol' is either a single protocol or it is a combination of protocols
+ * that have a single abbreviation).  Otherwise, returns NULL. */
+const char *
+ofputil_protocol_to_string(enum ofputil_protocol protocol)
+{
+    const struct proto_abbrev *p;
+
+    /* Use a "switch" statement for single-bit names so that we get a compiler
+     * warning if we forget any. */
+    switch (protocol) {
+    case OFPUTIL_P_NXM:
+        return "NXM-table_id";
+
+    case OFPUTIL_P_NXM_TID:
+        return "NXM+table_id";
+
+    case OFPUTIL_P_OF10:
+        return "OpenFlow10-table_id";
+
+    case OFPUTIL_P_OF10_TID:
+        return "OpenFlow10+table_id";
+    }
+
+    /* Check abbreviations. */
+    for (p = proto_abbrevs; p < &proto_abbrevs[N_PROTO_ABBREVS]; p++) {
+        if (protocol == p->protocol) {
+            return p->name;
+        }
+    }
+
+    return NULL;
+}
+
+/* Returns a string that represents 'protocols'.  The return value might be a
+ * comma-separated list if 'protocols' doesn't have a simple name.  The return
+ * value is "none" if 'protocols' is 0.
+ *
+ * The caller must free the returned string (with free()). */
+char *
+ofputil_protocols_to_string(enum ofputil_protocol protocols)
+{
+    struct ds s;
+
+    assert(!(protocols & ~OFPUTIL_P_ANY));
+    if (protocols == 0) {
+        return xstrdup("none");
+    }
+
+    ds_init(&s);
+    while (protocols) {
+        const struct proto_abbrev *p;
+        int i;
+
+        if (s.length) {
+            ds_put_char(&s, ',');
+        }
+
+        for (p = proto_abbrevs; p < &proto_abbrevs[N_PROTO_ABBREVS]; p++) {
+            if ((protocols & p->protocol) == p->protocol) {
+                ds_put_cstr(&s, p->name);
+                protocols &= ~p->protocol;
+                goto match;
+            }
+        }
+
+        for (i = 0; i < CHAR_BIT * sizeof(enum ofputil_protocol); i++) {
+            enum ofputil_protocol bit = 1u << i;
+
+            if (protocols & bit) {
+                ds_put_cstr(&s, ofputil_protocol_to_string(bit));
+                protocols &= ~bit;
+                goto match;
+            }
+        }
+        NOT_REACHED();
+
+    match: ;
+    }
+    return ds_steal_cstr(&s);
+}
+
+static enum ofputil_protocol
+ofputil_protocol_from_string__(const char *s, size_t n)
 {
-    return (!strcmp(s, "openflow10") ? NXFF_OPENFLOW10
-            : !strcmp(s, "nxm") ? NXFF_NXM
-            : -1);
+    const struct proto_abbrev *p;
+    int i;
+
+    for (i = 0; i < CHAR_BIT * sizeof(enum ofputil_protocol); i++) {
+        enum ofputil_protocol bit = 1u << i;
+        const char *name = ofputil_protocol_to_string(bit);
+
+        if (name && n == strlen(name) && !strncasecmp(s, name, n)) {
+            return bit;
+        }
+    }
+
+    for (p = proto_abbrevs; p < &proto_abbrevs[N_PROTO_ABBREVS]; p++) {
+        if (n == strlen(p->name) && !strncasecmp(s, p->name, n)) {
+            return p->protocol;
+        }
+    }
+
+    return 0;
+}
+
+/* Returns the nonempty set of protocols represented by 's', which can be a
+ * single protocol name or abbreviation or a comma-separated list of them.
+ *
+ * Aborts the program with an error message if 's' is invalid. */
+enum ofputil_protocol
+ofputil_protocols_from_string(const char *s)
+{
+    const char *orig_s = s;
+    enum ofputil_protocol protocols;
+
+    protocols = 0;
+    while (*s) {
+        enum ofputil_protocol p;
+        size_t n;
+
+        n = strcspn(s, ",");
+        if (n == 0) {
+            s++;
+            continue;
+        }
+
+        p = ofputil_protocol_from_string__(s, n);
+        if (!p) {
+            ovs_fatal(0, "%.*s: unknown flow protocol", (int) n, s);
+        }
+        protocols |= p;
+
+        s += n;
+    }
+
+    if (!protocols) {
+        ovs_fatal(0, "%s: no flow protocol specified", orig_s);
+    }
+    return protocols;
 }
 
 static bool
@@ -850,12 +1064,12 @@ regs_fully_wildcarded(const struct flow_wildcards *wc)
     return true;
 }
 
-/* Returns the minimum nx_flow_format to use for sending 'rule' to a switch
- * (e.g. to add or remove a flow).  Only NXM can handle tunnel IDs, registers,
- * or fixing the Ethernet multicast bit.  Otherwise, it's better to use
- * NXFF_OPENFLOW10 for backward compatibility. */
-enum nx_flow_format
-ofputil_min_flow_format(const struct cls_rule *rule)
+/* Returns a bit-mask of ofputil_protocols that can be used for sending 'rule'
+ * to a switch (e.g. to add or remove a flow).  Only NXM can handle tunnel IDs,
+ * registers, or fixing the Ethernet multicast bit.  Otherwise, it's better to
+ * use OpenFlow 1.0 protocol for backward compatibility. */
+enum ofputil_protocol
+ofputil_usable_protocols(const struct cls_rule *rule)
 {
     const struct flow_wildcards *wc = &rule->wc;
 
@@ -863,68 +1077,95 @@ ofputil_min_flow_format(const struct cls_rule *rule)
 
     /* Only NXM supports separately wildcards the Ethernet multicast bit. */
     if (!(wc->wildcards & FWW_DL_DST) != !(wc->wildcards & FWW_ETH_MCAST)) {
-        return NXFF_NXM;
+        return OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID;
     }
 
     /* Only NXM supports matching ARP hardware addresses. */
     if (!(wc->wildcards & FWW_ARP_SHA) || !(wc->wildcards & FWW_ARP_THA)) {
-        return NXFF_NXM;
+        return OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID;
     }
 
     /* Only NXM supports matching IPv6 traffic. */
     if (!(wc->wildcards & FWW_DL_TYPE)
             && (rule->flow.dl_type == htons(ETH_TYPE_IPV6))) {
-        return NXFF_NXM;
+        return OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID;
     }
 
     /* Only NXM supports matching registers. */
     if (!regs_fully_wildcarded(wc)) {
-        return NXFF_NXM;
+        return OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID;
     }
 
     /* Only NXM supports matching tun_id. */
     if (wc->tun_id_mask != htonll(0)) {
-        return NXFF_NXM;
+        return OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID;
     }
 
     /* Only NXM supports matching fragments. */
     if (wc->nw_frag_mask) {
-        return NXFF_NXM;
+        return OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID;
     }
 
     /* Only NXM supports matching IPv6 flow label. */
     if (!(wc->wildcards & FWW_IPV6_LABEL)) {
-        return NXFF_NXM;
+        return OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID;
     }
 
     /* Only NXM supports matching IP ECN bits. */
     if (!(wc->wildcards & FWW_NW_ECN)) {
-        return NXFF_NXM;
+        return OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID;
     }
 
     /* Only NXM supports matching IP TTL/hop limit. */
     if (!(wc->wildcards & FWW_NW_TTL)) {
-        return NXFF_NXM;
+        return OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID;
     }
 
-    /* Other formats can express this rule. */
-    return NXFF_OPENFLOW10;
+    /* Any protocol can express this rule. */
+    return OFPUTIL_P_ANY;
 }
 
-/* Returns an OpenFlow message that can be used to set the flow format to
- * 'flow_format'.  */
+/* Returns an NXT_SET_FLOW_FORMAT message that can be used to set the flow
+ * format to 'nxff'.  */
 struct ofpbuf *
-ofputil_make_set_flow_format(enum nx_flow_format flow_format)
+ofputil_encode_nx_set_flow_format(enum nx_flow_format nxff)
 {
     struct nx_set_flow_format *sff;
     struct ofpbuf *msg;
 
+    assert(ofputil_nx_flow_format_is_valid(nxff));
+
     sff = make_nxmsg(sizeof *sff, NXT_SET_FLOW_FORMAT, &msg);
-    sff->format = htonl(flow_format);
+    sff->format = htonl(nxff);
 
     return msg;
 }
 
+bool
+ofputil_nx_flow_format_is_valid(enum nx_flow_format flow_format)
+{
+    switch (flow_format) {
+    case NXFF_OPENFLOW10:
+    case NXFF_NXM:
+        return true;
+    default:
+        return false;
+    }
+}
+
+const char *
+ofputil_nx_flow_format_to_string(enum nx_flow_format flow_format)
+{
+    switch (flow_format) {
+    case NXFF_OPENFLOW10:
+        return "openflow10";
+    case NXFF_NXM:
+        return "nxm";
+    default:
+        NOT_REACHED();
+    }
+}
+
 /* 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 *
@@ -942,13 +1183,11 @@ ofputil_make_flow_mod_table_id(bool flow_mod_table_id)
  * flow_mod in 'fm'.  Returns 0 if successful, otherwise an OpenFlow error
  * code.
  *
- * '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. */
 enum ofperr
 ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
-                        const struct ofp_header *oh, bool flow_mod_table_id)
+                        const struct ofp_header *oh,
+                        enum ofputil_protocol protocol)
 {
     const struct ofputil_msg_type *type;
     uint16_t command;
@@ -981,7 +1220,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
 
         /* Translate the rule. */
         ofputil_cls_rule_from_match(&ofm->match, priority, &fm->cr);
-        ofputil_normalize_rule(&fm->cr, NXFF_OPENFLOW10);
+        ofputil_normalize_rule(&fm->cr);
 
         /* Translate the message. */
         fm->cookie = ofm->cookie;
@@ -1020,7 +1259,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
         NOT_REACHED();
     }
 
-    if (flow_mod_table_id) {
+    if (protocol & OFPUTIL_P_TID) {
         fm->command = command & 0xff;
         fm->table_id = command >> 8;
     } else {
@@ -1032,26 +1271,28 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
 }
 
 /* Converts 'fm' into an OFPT_FLOW_MOD or NXT_FLOW_MOD message according to
- * 'flow_format' and returns the message.
+ * 'protocol' 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 ofputil_flow_mod *fm,
-                        enum nx_flow_format flow_format,
-                        bool flow_mod_table_id)
+                        enum ofputil_protocol protocol)
 {
     size_t actions_len = fm->n_actions * sizeof *fm->actions;
+    struct ofp_flow_mod *ofm;
+    struct nx_flow_mod *nfm;
     struct ofpbuf *msg;
     uint16_t command;
+    int match_len;
 
-    command = (flow_mod_table_id
+    command = (protocol & OFPUTIL_P_TID
                ? (fm->command & 0xff) | (fm->table_id << 8)
                : fm->command);
 
-    if (flow_format == NXFF_OPENFLOW10) {
-        struct ofp_flow_mod *ofm;
-
+    switch (protocol) {
+    case OFPUTIL_P_OF10:
+    case OFPUTIL_P_OF10_TID:
         msg = ofpbuf_new(sizeof *ofm + actions_len);
         ofm = put_openflow(sizeof *ofm, OFPT_FLOW_MOD, msg);
         ofputil_cls_rule_to_match(&fm->cr, &ofm->match);
@@ -1063,10 +1304,10 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
         ofm->buffer_id = htonl(fm->buffer_id);
         ofm->out_port = htons(fm->out_port);
         ofm->flags = htons(fm->flags);
-    } else if (flow_format == NXFF_NXM) {
-        struct nx_flow_mod *nfm;
-        int match_len;
+        break;
 
+    case OFPUTIL_P_NXM:
+    case OFPUTIL_P_NXM_TID:
         msg = ofpbuf_new(sizeof *nfm + NXM_TYPICAL_LEN + actions_len);
         put_nxmsg(sizeof *nfm, NXT_FLOW_MOD, msg);
         match_len = nx_put_match(msg, &fm->cr);
@@ -1081,7 +1322,9 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
         nfm->out_port = htons(fm->out_port);
         nfm->flags = htons(fm->flags);
         nfm->match_len = htons(match_len);
-    } else {
+        break;
+
+    default:
         NOT_REACHED();
     }
 
@@ -1090,6 +1333,27 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
     return msg;
 }
 
+enum ofputil_protocol
+ofputil_flow_mod_usable_protocols(const struct ofputil_flow_mod *fms,
+                                  size_t n_fms)
+{
+    enum ofputil_protocol usable_protocols;
+    size_t i;
+
+    usable_protocols = OFPUTIL_P_ANY;
+    for (i = 0; i < n_fms; i++) {
+        const struct ofputil_flow_mod *fm = &fms[i];
+
+        usable_protocols &= ofputil_usable_protocols(&fm->cr);
+        if (fm->table_id != 0xff) {
+            usable_protocols &= OFPUTIL_P_TID;
+        }
+    }
+    assert(usable_protocols);
+
+    return usable_protocols;
+}
+
 static enum ofperr
 ofputil_decode_ofpst_flow_request(struct ofputil_flow_stats_request *fsr,
                                   const struct ofp_header *oh,
@@ -1169,14 +1433,16 @@ ofputil_decode_flow_stats_request(struct ofputil_flow_stats_request *fsr,
 
 /* Converts abstract flow_stats_request 'fsr' into an OFPST_FLOW,
  * OFPST_AGGREGATE, NXST_FLOW, or NXST_AGGREGATE request 'oh' according to
- * 'flow_format', and returns the message. */
+ * 'protocol', and returns the message. */
 struct ofpbuf *
 ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr,
-                                  enum nx_flow_format flow_format)
+                                  enum ofputil_protocol protocol)
 {
     struct ofpbuf *msg;
 
-    if (flow_format == NXFF_OPENFLOW10) {
+    switch (protocol) {
+    case OFPUTIL_P_OF10:
+    case OFPUTIL_P_OF10_TID: {
         struct ofp_flow_stats_request *ofsr;
         int type;
 
@@ -1185,7 +1451,11 @@ ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr,
         ofputil_cls_rule_to_match(&fsr->match, &ofsr->match);
         ofsr->table_id = fsr->table_id;
         ofsr->out_port = htons(fsr->out_port);
-    } else if (flow_format == NXFF_NXM) {
+        break;
+    }
+
+    case OFPUTIL_P_NXM:
+    case OFPUTIL_P_NXM_TID: {
         struct nx_flow_stats_request *nfsr;
         int match_len;
         int subtype;
@@ -1198,7 +1468,10 @@ ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr,
         nfsr->out_port = htons(fsr->out_port);
         nfsr->match_len = htons(match_len);
         nfsr->table_id = fsr->table_id;
-    } else {
+        break;
+    }
+
+    default:
         NOT_REACHED();
     }
 
@@ -1385,7 +1658,7 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
 }
 
 /* Converts abstract ofputil_aggregate_stats 'stats' into an OFPST_AGGREGATE or
- * NXST_AGGREGATE reply according to 'flow_format', and returns the message. */
+ * NXST_AGGREGATE reply according to 'protocol', and returns the message. */
 struct ofpbuf *
 ofputil_encode_aggregate_stats_reply(
     const struct ofputil_aggregate_stats *stats,
@@ -1474,15 +1747,17 @@ ofputil_decode_flow_removed(struct ofputil_flow_removed *fr,
 }
 
 /* Converts abstract ofputil_flow_removed 'fr' into an OFPT_FLOW_REMOVED or
- * NXT_FLOW_REMOVED message 'oh' according to 'flow_format', and returns the
+ * NXT_FLOW_REMOVED message 'oh' according to 'protocol', and returns the
  * message. */
 struct ofpbuf *
 ofputil_encode_flow_removed(const struct ofputil_flow_removed *fr,
-                            enum nx_flow_format flow_format)
+                            enum ofputil_protocol protocol)
 {
     struct ofpbuf *msg;
 
-    if (flow_format == NXFF_OPENFLOW10) {
+    switch (protocol) {
+    case OFPUTIL_P_OF10:
+    case OFPUTIL_P_OF10_TID: {
         struct ofp_flow_removed *ofr;
 
         ofr = make_openflow_xid(sizeof *ofr, OFPT_FLOW_REMOVED, htonl(0),
@@ -1496,7 +1771,11 @@ ofputil_encode_flow_removed(const struct ofputil_flow_removed *fr,
         ofr->idle_timeout = htons(fr->idle_timeout);
         ofr->packet_count = htonll(unknown_to_zero(fr->packet_count));
         ofr->byte_count = htonll(unknown_to_zero(fr->byte_count));
-    } else if (flow_format == NXFF_NXM) {
+        break;
+    }
+
+    case OFPUTIL_P_NXM:
+    case OFPUTIL_P_NXM_TID: {
         struct nx_flow_removed *nfr;
         int match_len;
 
@@ -1513,7 +1792,10 @@ ofputil_encode_flow_removed(const struct ofputil_flow_removed *fr,
         nfr->match_len = htons(match_len);
         nfr->packet_count = htonll(fr->packet_count);
         nfr->byte_count = htonll(fr->byte_count);
-    } else {
+        break;
+    }
+
+    default:
         NOT_REACHED();
     }
 
@@ -2505,12 +2787,9 @@ action_outputs_to_port(const union ofp_action *action, ovs_be16 port)
  *       example, Open vSwitch does not understand SCTP, an L4 protocol, so the
  *       L4 fields tp_src and tp_dst must be wildcarded if 'rule' specifies an
  *       SCTP flow.
- *
- * 'flow_format' specifies the format of the flow as received or as intended to
- * be sent.  This is important for IPv6 and ARP, for which NXM supports more
- * detailed matching. */
+ */
 void
-ofputil_normalize_rule(struct cls_rule *rule, enum nx_flow_format flow_format)
+ofputil_normalize_rule(struct cls_rule *rule)
 {
     enum {
         MAY_NW_ADDR     = 1 << 0, /* nw_src, nw_dst */
@@ -2533,8 +2812,7 @@ ofputil_normalize_rule(struct cls_rule *rule, enum nx_flow_format flow_format)
             rule->flow.nw_proto == IPPROTO_ICMP) {
             may_match |= MAY_TP_ADDR;
         }
-    } else if (rule->flow.dl_type == htons(ETH_TYPE_IPV6)
-               && flow_format == NXFF_NXM) {
+    } else if (rule->flow.dl_type == htons(ETH_TYPE_IPV6)) {
         may_match = MAY_NW_PROTO | MAY_IPVx | MAY_IPV6;
         if (rule->flow.nw_proto == IPPROTO_TCP ||
             rule->flow.nw_proto == IPPROTO_UDP) {
@@ -2548,10 +2826,7 @@ ofputil_normalize_rule(struct cls_rule *rule, enum nx_flow_format flow_format)
             }
         }
     } else if (rule->flow.dl_type == htons(ETH_TYPE_ARP)) {
-        may_match = MAY_NW_PROTO | MAY_NW_ADDR;
-        if (flow_format == NXFF_NXM) {
-            may_match |= MAY_ARP_SHA | MAY_ARP_THA;
-        }
+        may_match = MAY_NW_PROTO | MAY_NW_ADDR | MAY_ARP_SHA | MAY_ARP_THA;
     } else {
         may_match = 0;
     }
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index 88e998e..15bd5c3 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -106,29 +106,74 @@ void ofputil_format_port(uint16_t port, struct ds *);
 ovs_be32 ofputil_wcbits_to_netmask(int wcbits);
 int ofputil_netmask_to_wcbits(ovs_be32 netmask);
 
+/* Protocols.
+ *
+ * These are arranged from most portable to least portable, or alternatively
+ * from least powerful to most powerful.  Formats earlier on the list are more
+ * likely to be understood for the purpose of making requests, but formats
+ * later on the list are more likely to accurately describe a flow within a
+ * switch.
+ *
+ * On any given OpenFlow connection, a single protocol is in effect at any
+ * given time.  These values use separate bits only because that makes it easy
+ * to test whether a particular protocol is within a given set of protocols and
+ * to implement set union and intersection.
+ */
+enum ofputil_protocol {
+    /* OpenFlow 1.0-based protocols. */
+    OFPUTIL_P_OF10     = 1 << 0, /* OpenFlow 1.0 flow format. */
+    OFPUTIL_P_OF10_TID = 1 << 1, /* OF1.0 + flow_mod_table_id extension. */
+#define OFPUTIL_P_OF10_ANY (OFPUTIL_P_OF10 | OFPUTIL_P_OF10_TID)
+
+    /* OpenFlow 1.0 with NXM-based flow formats. */
+    OFPUTIL_P_NXM      = 1 << 2, /* Nicira extended match. */
+    OFPUTIL_P_NXM_TID  = 1 << 3, /* NXM + flow_mod_table_id extension. */
+#define OFPUTIL_P_NXM_ANY (OFPUTIL_P_NXM | OFPUTIL_P_NXM_TID)
+
+    /* All protocols. */
+#define OFPUTIL_P_ANY (OFPUTIL_P_OF10_ANY | OFPUTIL_P_NXM_ANY)
+
+    /* Protocols in which a specific table may be specified in flow_mods. */
+#define OFPUTIL_P_TID (OFPUTIL_P_OF10_TID | OFPUTIL_P_NXM_TID)
+};
+
+/* Protocols to use for flow dumps, from most to least preferred. */
+extern enum ofputil_protocol ofputil_flow_dump_protocols[];
+extern size_t ofputil_n_flow_dump_protocols;
+
+enum ofputil_protocol ofputil_protocol_from_ofp_version(int version);
+bool ofputil_protocol_is_valid(enum ofputil_protocol);
+enum ofputil_protocol ofputil_protocol_set_tid(enum ofputil_protocol,
+                                               bool enable);
+enum ofputil_protocol ofputil_protocol_to_base(enum ofputil_protocol);
+enum ofputil_protocol ofputil_protocol_set_base(
+    enum ofputil_protocol cur, enum ofputil_protocol new_base);
+
+const char *ofputil_protocol_to_string(enum ofputil_protocol);
+char *ofputil_protocols_to_string(enum ofputil_protocol);
+enum ofputil_protocol ofputil_protocols_from_string(const char *);
+enum ofputil_protocol ofputil_usable_protocols(const struct cls_rule *);
+
+/* nx_flow_format */
+struct ofpbuf *ofputil_encode_nx_set_flow_format(enum nx_flow_format);
+bool ofputil_nx_flow_format_is_valid(enum nx_flow_format);
+const char *ofputil_nx_flow_format_to_string(enum nx_flow_format);
+
 /* Work with OpenFlow 1.0 ofp_match. */
 void ofputil_wildcard_from_openflow(uint32_t ofpfw, struct flow_wildcards *);
 void ofputil_cls_rule_from_match(const struct ofp_match *,
                                  unsigned int priority, struct cls_rule *);
-void ofputil_normalize_rule(struct cls_rule *, enum nx_flow_format);
+void ofputil_normalize_rule(struct cls_rule *);
 void ofputil_cls_rule_to_match(const struct cls_rule *, struct ofp_match *);
 
 /* dl_type translation between OpenFlow and 'struct flow' format. */
 ovs_be16 ofputil_dl_type_to_openflow(ovs_be16 flow_dl_type);
 ovs_be16 ofputil_dl_type_from_openflow(ovs_be16 ofp_dl_type);
 
-/* Flow formats. */
-bool ofputil_flow_format_is_valid(enum nx_flow_format);
-const char *ofputil_flow_format_to_string(enum nx_flow_format);
-int ofputil_flow_format_from_string(const char *);
-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. */
+/* Protocol-independent flow_mod. */
 struct ofputil_flow_mod {
     struct cls_rule cr;
     ovs_be64 cookie;
@@ -145,12 +190,14 @@ struct ofputil_flow_mod {
 
 enum ofperr ofputil_decode_flow_mod(struct ofputil_flow_mod *,
                                     const struct ofp_header *,
-                                    bool flow_mod_table_id);
+                                    enum ofputil_protocol);
 struct ofpbuf *ofputil_encode_flow_mod(const struct ofputil_flow_mod *,
-                                       enum nx_flow_format,
-                                       bool flow_mod_table_id);
+                                       enum ofputil_protocol);
+
+enum ofputil_protocol ofputil_flow_mod_usable_protocols(
+    const struct ofputil_flow_mod *fms, size_t n_fms);
 
-/* Flow stats or aggregate stats request, independent of flow format. */
+/* Flow stats or aggregate stats request, independent of protocol. */
 struct ofputil_flow_stats_request {
     bool aggregate;             /* Aggregate results? */
     struct cls_rule match;
@@ -161,9 +208,9 @@ struct ofputil_flow_stats_request {
 enum ofperr ofputil_decode_flow_stats_request(
     struct ofputil_flow_stats_request *, const struct ofp_header *);
 struct ofpbuf *ofputil_encode_flow_stats_request(
-    const struct ofputil_flow_stats_request *, enum nx_flow_format);
+    const struct ofputil_flow_stats_request *, enum ofputil_protocol);
 
-/* Flow stats reply, independent of flow format. */
+/* Flow stats reply, independent of protocol. */
 struct ofputil_flow_stats {
     struct cls_rule rule;
     ovs_be64 cookie;
@@ -183,7 +230,7 @@ int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *,
 void ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *,
                                      struct list *replies);
 
-/* Aggregate stats reply, independent of flow format. */
+/* Aggregate stats reply, independent of protocol. */
 struct ofputil_aggregate_stats {
     uint64_t packet_count;      /* Packet count, UINT64_MAX if unknown. */
     uint64_t byte_count;        /* Byte count, UINT64_MAX if unknown. */
@@ -194,7 +241,7 @@ struct ofpbuf *ofputil_encode_aggregate_stats_reply(
     const struct ofputil_aggregate_stats *stats,
     const struct ofp_stats_msg *request);
 
-/* Flow removed message, independent of flow format. */
+/* Flow removed message, independent of protocol. */
 struct ofputil_flow_removed {
     struct cls_rule rule;
     ovs_be64 cookie;
@@ -209,7 +256,7 @@ struct ofputil_flow_removed {
 enum ofperr ofputil_decode_flow_removed(struct ofputil_flow_removed *,
                                         const struct ofp_header *);
 struct ofpbuf *ofputil_encode_flow_removed(const struct ofputil_flow_removed *,
-                                           enum nx_flow_format);
+                                           enum ofputil_protocol);
 
 /* Abstract packet-in message. */
 struct ofputil_packet_in {
diff --git a/lib/rconn.c b/lib/rconn.c
index 1b69b8f..753a073 100644
--- a/lib/rconn.c
+++ b/lib/rconn.c
@@ -731,6 +731,12 @@ rconn_get_local_port(const struct rconn *rconn)
     return rconn->vconn ? vconn_get_local_port(rconn->vconn) : 0;
 }
 
+int
+rconn_get_version(const struct rconn *rconn)
+{
+    return rconn->vconn ? vconn_get_version(rconn->vconn) : -1;
+}
+
 /* Returns the total number of packets successfully received by the underlying
  * vconn.  */
 unsigned int
diff --git a/lib/rconn.h b/lib/rconn.h
index a6c2fa7..d0326e6 100644
--- a/lib/rconn.h
+++ b/lib/rconn.h
@@ -76,6 +76,7 @@ ovs_be32 rconn_get_remote_ip(const struct rconn *);
 ovs_be16 rconn_get_remote_port(const struct rconn *);
 ovs_be32 rconn_get_local_ip(const struct rconn *);
 ovs_be16 rconn_get_local_port(const struct rconn *);
+int rconn_get_version(const struct rconn *);
 
 const char *rconn_get_state(const struct rconn *);
 unsigned int rconn_get_attempted_connections(const struct rconn *);
diff --git a/lib/util.h b/lib/util.h
index 61039be..3113e8f 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -78,6 +78,12 @@ extern const char *program_name;
 /* Returns true if X is a power of 2, otherwise false. */
 #define IS_POW2(X) ((X) && !((X) & ((X) - 1)))
 
+static inline bool
+is_pow2(uintmax_t x)
+{
+    return IS_POW2(x);
+}
+
 #ifndef MIN
 #define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
 #endif
diff --git a/lib/vconn.c b/lib/vconn.c
index 8e6374e..c71c61d 100644
--- a/lib/vconn.c
+++ b/lib/vconn.c
@@ -353,6 +353,17 @@ vconn_get_local_port(const struct vconn *vconn)
     return vconn->local_port;
 }
 
+/* Returns the OpenFlow version negotiated with the peer, or -1 if version
+ * negotiation is not yet complete.
+ *
+ * A vconn that has successfully connected (that is, vconn_connect() or
+ * vconn_send() or vconn_recv() has returned 0) always negotiated a version. */
+int
+vconn_get_version(const struct vconn *vconn)
+{
+    return vconn->version;
+}
+
 static void
 vcs_connecting(struct vconn *vconn)
 {
@@ -471,7 +482,7 @@ vconn_connect(struct vconn *vconn)
 {
     enum vconn_state last_state;
 
-    assert(vconn->min_version >= 0);
+    assert(vconn->min_version > 0);
     do {
         last_state = vconn->state;
         switch (vconn->state) {
@@ -538,14 +549,14 @@ do_recv(struct vconn *vconn, struct ofpbuf **msgp)
         }
 
         oh = ofpbuf_at_assert(*msgp, 0, sizeof *oh);
-        if (oh->version != vconn->version
+        if ((oh->version != vconn->version || oh->version == 0)
             && oh->type != OFPT_HELLO
             && oh->type != OFPT_ERROR
             && oh->type != OFPT_ECHO_REQUEST
             && oh->type != OFPT_ECHO_REPLY
             && oh->type != OFPT_VENDOR)
         {
-            if (vconn->version < 0) {
+            if (vconn->version == 0) {
                 VLOG_ERR_RL(&bad_ofmsg_rl,
                             "%s: received OpenFlow message type %"PRIu8" "
                             "before version negotiation complete",
@@ -995,8 +1006,8 @@ vconn_init(struct vconn *vconn, struct vconn_class *class, int connect_status,
                     : !connect_status ? VCS_SEND_HELLO
                     : VCS_DISCONNECTED);
     vconn->error = connect_status;
-    vconn->version = -1;
-    vconn->min_version = -1;
+    vconn->version = 0;
+    vconn->min_version = 0;
     vconn->remote_ip = 0;
     vconn->remote_port = 0;
     vconn->local_ip = 0;
diff --git a/lib/vconn.h b/lib/vconn.h
index 74b6b49..516e2d3 100644
--- a/lib/vconn.h
+++ b/lib/vconn.h
@@ -40,6 +40,7 @@ ovs_be32 vconn_get_remote_ip(const struct vconn *);
 ovs_be16 vconn_get_remote_port(const struct vconn *);
 ovs_be32 vconn_get_local_ip(const struct vconn *);
 ovs_be16 vconn_get_local_port(const struct vconn *);
+int vconn_get_version(const struct vconn *);
 int vconn_connect(struct vconn *);
 int vconn_recv(struct vconn *, struct ofpbuf **);
 int vconn_send(struct vconn *, struct ofpbuf *);
diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
index 63cb133..1be5a45 100644
--- a/ofproto/connmgr.c
+++ b/ofproto/connmgr.c
@@ -46,7 +46,7 @@ struct ofconn {
     struct list node;           /* In struct connmgr's "all_conns" list. */
     struct rconn *rconn;        /* OpenFlow connection. */
     enum ofconn_type type;      /* Type. */
-    enum nx_flow_format flow_format; /* Currently selected flow format. */
+    enum ofputil_protocol protocol; /* Current protocol variant. */
     bool flow_mod_table_id;     /* NXT_FLOW_MOD_TABLE_ID enabled? */
 
     /* Asynchronous flow table operation support. */
@@ -750,38 +750,23 @@ ofconn_set_role(struct ofconn *ofconn, enum nx_role role)
     ofconn->role = role;
 }
 
-/* Returns the currently configured flow format for 'ofconn', one of NXFF_*.
+/* Returns the currently configured protocol for 'ofconn', one of OFPUTIL_P_*.
  *
- * The default, if no other format has been set, is NXFF_OPENFLOW10. */
-enum nx_flow_format
-ofconn_get_flow_format(struct ofconn *ofconn)
+ * The default, if no other format has been set, is OFPUTIL_P_OPENFLOW10. */
+enum ofputil_protocol
+ofconn_get_protocol(struct ofconn *ofconn)
 {
-    return ofconn->flow_format;
+    return ofconn->protocol;
 }
 
-/* Sets the flow format for 'ofconn' to 'flow_format' (one of NXFF_*). */
-void
-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.
+/* Sets the protocol for 'ofconn' to 'protocol' (one of OFPUTIL_P_*).
  *
- * 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'. */
+ * (This doesn't actually send anything to accomplish this.  Presumably the
+ * caller already did that.) */
 void
-ofconn_set_flow_mod_table_id(struct ofconn *ofconn, bool enable)
+ofconn_set_protocol(struct ofconn *ofconn, enum ofputil_protocol protocol)
 {
-    ofconn->flow_mod_table_id = enable;
+    ofconn->protocol = protocol;
 }
 
 /* Returns the default miss send length for 'ofconn'. */
@@ -899,7 +884,7 @@ ofconn_create(struct connmgr *mgr, struct rconn *rconn, enum ofconn_type type)
     list_push_back(&mgr->all_conns, &ofconn->node);
     ofconn->rconn = rconn;
     ofconn->type = type;
-    ofconn->flow_format = NXFF_OPENFLOW10;
+    ofconn->protocol = OFPUTIL_P_OF10;
     ofconn->flow_mod_table_id = false;
     list_init(&ofconn->opgroups);
     ofconn->role = NX_ROLE_OTHER;
@@ -1139,7 +1124,7 @@ connmgr_send_flow_removed(struct connmgr *mgr,
          * prevents new flows from being added (and expiring).  (It also
          * prevents processing OpenFlow requests that would not add new flows,
          * so it is imperfect.) */
-        msg = ofputil_encode_flow_removed(fr, ofconn->flow_format);
+        msg = ofputil_encode_flow_removed(fr, ofconn->protocol);
         ofconn_send_reply(ofconn, msg);
     }
 }
diff --git a/ofproto/connmgr.h b/ofproto/connmgr.h
index 0040c98..1b49abf 100644
--- a/ofproto/connmgr.h
+++ b/ofproto/connmgr.h
@@ -83,11 +83,8 @@ enum ofconn_type ofconn_get_type(const struct ofconn *);
 enum nx_role ofconn_get_role(const struct ofconn *);
 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);
+enum ofputil_protocol ofconn_get_protocol(struct ofconn *);
+void ofconn_set_protocol(struct ofconn *, enum ofputil_protocol);
 
 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.c b/ofproto/ofproto.c
index fc53bf2..0f028be 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -2722,8 +2722,7 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
         return error;
     }
 
-    error = ofputil_decode_flow_mod(&fm, oh,
-                                    ofconn_get_flow_mod_table_id(ofconn));
+    error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_protocol(ofconn));
     if (error) {
         return error;
     }
@@ -2812,8 +2811,12 @@ handle_nxt_flow_mod_table_id(struct ofconn *ofconn,
 {
     const struct nx_flow_mod_table_id *msg
         = (const struct nx_flow_mod_table_id *) oh;
+    enum ofputil_protocol cur, next;
+
+    cur = ofconn_get_protocol(ofconn);
+    next = ofputil_protocol_set_tid(cur, msg->set != 0);
+    ofconn_set_protocol(ofconn, next);
 
-    ofconn_set_flow_mod_table_id(ofconn, msg->set != 0);
     return 0;
 }
 
@@ -2822,20 +2825,29 @@ handle_nxt_set_flow_format(struct ofconn *ofconn, const struct ofp_header *oh)
 {
     const struct nx_set_flow_format *msg
         = (const struct nx_set_flow_format *) oh;
-    uint32_t format;
+    enum ofputil_protocol cur, next;
+
+    cur = ofconn_get_protocol(ofconn);
+
+    switch (ntohl(msg->format)) {
+    case NXFF_OPENFLOW10:
+        next = ofputil_protocol_set_base(cur, OFPUTIL_P_OF10);
+        break;
 
-    format = ntohl(msg->format);
-    if (format != NXFF_OPENFLOW10 && format != NXFF_NXM) {
+    case NXFF_NXM:
+        next = ofputil_protocol_set_base(cur, OFPUTIL_P_NXM);
+        break;
+
+    default:
         return OFPERR_OFPBRC_EPERM;
     }
 
-    if (format != ofconn_get_flow_format(ofconn)
-        && ofconn_has_pending_opgroups(ofconn)) {
-        /* Avoid sending async messages in surprising flow format. */
+    if (cur != next && ofconn_has_pending_opgroups(ofconn)) {
+        /* Avoid sending async messages in surprising protocol. */
         return OFPROTO_POSTPONE;
     }
 
-    ofconn_set_flow_format(ofconn, format);
+    ofconn_set_protocol(ofconn, next);
     return 0;
 }
 
diff --git a/tests/autopath.at b/tests/autopath.at
index 6b837f8..c3e0b34 100644
--- a/tests/autopath.at
+++ b/tests/autopath.at
@@ -2,10 +2,14 @@ AT_BANNER([autopath link selection])
 
 AT_SETUP([autopath basic])
 AT_CHECK([ovs-ofctl parse-flow 'actions=autopath(1, NXM_NX_REG0[[]])'], [0],
-  [OFPT_FLOW_MOD (xid=0x1): ADD actions=autopath(1,NXM_NX_REG0[[]])
+  [usable protocols: any
+chosen protocol: OpenFlow10-table_id
+OFPT_FLOW_MOD (xid=0x1): ADD actions=autopath(1,NXM_NX_REG0[[]])
 ])
 AT_CHECK([ovs-ofctl parse-flow 'actions=autopath(2, NXM_NX_REG0[[2..30]])'], [0],
-  [OFPT_FLOW_MOD (xid=0x1): ADD actions=autopath(2,NXM_NX_REG0[[2..30]])
+  [usable protocols: any
+chosen protocol: OpenFlow10-table_id
+OFPT_FLOW_MOD (xid=0x1): ADD actions=autopath(2,NXM_NX_REG0[[2..30]])
 ])
 AT_CLEANUP
 
diff --git a/tests/learn.at b/tests/learn.at
index 93192ab..86cfa60 100644
--- a/tests/learn.at
+++ b/tests/learn.at
@@ -7,7 +7,9 @@ actions=learn(NXM_OF_VLAN_TCI[0..11], NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], output:
 actions=learn(table=1,idle_timeout=1, hard_timeout=2, priority=10, cookie=0xfedcba9876543210, in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31])
 ]])
 AT_CHECK([ovs-ofctl parse-flows flows.txt], [0],
-[[OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1)
+[[usable protocols: any
+chosen protocol: OpenFlow10-table_id
+OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1)
 OFPT_FLOW_MOD (xid=0x2): ADD actions=learn(table=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[],load:0x000a->NXM_NX_REG0[5..10])
 OFPT_FLOW_MOD (xid=0x3): ADD actions=learn(table=1,idle_timeout=1,hard_timeout=2,priority=10,cookie=0xfedcba9876543210,in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31])
 ]])
@@ -22,11 +24,12 @@ table=0 actions=learn(table=1,hard_timeout=10, NXM_OF_VLAN_TCI[0..11],output:NXM
 table=1 priority=0 actions=flood
 ]])
 AT_CHECK([ovs-ofctl parse-flows flows.txt], [0],
-[[OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1,in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31])
-OFPT_FLOW_MOD (xid=0x2): ADD actions=learn(table=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[])
-NXT_FLOW_MOD_TABLE_ID (xid=0x3): enable
-OFPT_FLOW_MOD (xid=0x4): ADD actions=learn(table=1,hard_timeout=10,NXM_OF_VLAN_TCI[0..11],output:NXM_OF_IN_PORT[]),resubmit(,1)
-OFPT_FLOW_MOD (xid=0x5): ADD table:1 priority=0 actions=FLOOD
+[[usable protocols: OpenFlow10+table_id,NXM+table_id
+chosen protocol: OpenFlow10+table_id
+OFPT_FLOW_MOD (xid=0x1): ADD table:255 actions=learn(table=1,in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31])
+OFPT_FLOW_MOD (xid=0x2): ADD table:255 actions=learn(table=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[])
+OFPT_FLOW_MOD (xid=0x3): ADD actions=learn(table=1,hard_timeout=10,NXM_OF_VLAN_TCI[0..11],output:NXM_OF_IN_PORT[]),resubmit(,1)
+OFPT_FLOW_MOD (xid=0x4): ADD table:1 priority=0 actions=FLOOD
 ]])
 AT_CLEANUP
 
@@ -37,7 +40,9 @@ ip,actions=learn(load:NXM_OF_IP_DST[]->NXM_NX_REG1[])
 ip,actions=learn(eth_type=0x800,NXM_OF_IP_DST[])
 ]])
 AT_CHECK([ovs-ofctl parse-flows flows.txt], [0],
-[[OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1,eth_type=0x800,load:0x00000005->NXM_OF_IP_DST[])
+[[usable protocols: any
+chosen protocol: OpenFlow10-table_id
+OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1,eth_type=0x800,load:0x00000005->NXM_OF_IP_DST[])
 OFPT_FLOW_MOD (xid=0x2): ADD ip actions=learn(table=1,load:NXM_OF_IP_DST[]->NXM_NX_REG1[])
 OFPT_FLOW_MOD (xid=0x3): ADD ip actions=learn(table=1,eth_type=0x800,NXM_OF_IP_DST[])
 ]])
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index acf5809..f186c99 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -1,6 +1,33 @@
 AT_BANNER([ovs-ofctl])
 
-AT_SETUP([ovs-ofctl parse-flows])
+AT_SETUP([ovs-ofctl parse-flows (OpenFlow 1.0)])
+AT_DATA([flows.txt], [[
+# comment
+tcp,tp_src=123,actions=flood
+in_port=LOCAL dl_vlan=9 dl_src=00:0A:E4:25:6B:B0 actions=drop
+udp dl_vlan_pcp=7 idle_timeout=5 actions=strip_vlan output:0
+tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1
+udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1
+cookie=0x123456789abcdef hard_timeout=10 priority=60000 actions=controller
+actions=note:41.42.43,note:00.01.02.03.04.05.06.07,note
+]])
+
+AT_CHECK([ovs-ofctl parse-flows flows.txt
+], [0], [stdout])
+AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0],
+[[usable protocols: any
+chosen protocol: OpenFlow10-table_id
+OFPT_FLOW_MOD: ADD tcp,tp_src=123 actions=FLOOD
+OFPT_FLOW_MOD: ADD in_port=65534,dl_vlan=9,dl_src=00:0a:e4:25:6b:b0 actions=drop
+OFPT_FLOW_MOD: ADD udp,dl_vlan_pcp=7 idle:5 actions=strip_vlan,output:0
+OFPT_FLOW_MOD: ADD tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1
+OFPT_FLOW_MOD: ADD udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1
+OFPT_FLOW_MOD: ADD priority=60000 cookie:0x123456789abcdef hard:10 actions=CONTROLLER:65535
+OFPT_FLOW_MOD: ADD actions=note:41.42.43.00.00.00,note:00.01.02.03.04.05.06.07.00.00.00.00.00.00,note:00.00.00.00.00.00
+]])
+AT_CLEANUP
+
+AT_SETUP([ovs-ofctl parse-flows (NXM)])
 AT_DATA([flows.txt], [[
 # comment
 tcp,tp_src=123,actions=flood
@@ -31,19 +58,19 @@ actions=output:1,exit,output:2
 
 AT_CHECK([ovs-ofctl parse-flows flows.txt
 ], [0], [stdout])
-AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0], 
-[[OFPT_FLOW_MOD: ADD tcp,tp_src=123 actions=FLOOD
-OFPT_FLOW_MOD: ADD in_port=65534,dl_vlan=9,dl_src=00:0a:e4:25:6b:b0 actions=drop
-OFPT_FLOW_MOD: ADD udp,dl_vlan_pcp=7 idle:5 actions=strip_vlan,output:0
-OFPT_FLOW_MOD: ADD tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1
-OFPT_FLOW_MOD: ADD udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1
-OFPT_FLOW_MOD: ADD priority=60000 cookie:0x123456789abcdef hard:10 actions=CONTROLLER:65535
-OFPT_FLOW_MOD: ADD actions=note:41.42.43.00.00.00,note:00.01.02.03.04.05.06.07.00.00.00.00.00.00,note:00.00.00.00.00.00
-NXT_SET_FLOW_FORMAT: format=nxm
-NXT_FLOW_MOD: ADD tun_id=0x1234 cookie:0x5678 actions=FLOOD
-NXT_FLOW_MOD: ADD actions=set_tunnel:0x1234,set_tunnel64:0x9876,set_tunnel64:0x123456789
-NXT_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])
-NXT_FLOW_MOD_TABLE_ID: enable
+AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0],
+[[usable protocols: NXM+table_id
+chosen protocol: NXM+table_id
+NXT_FLOW_MOD: ADD table:255 tcp,tp_src=123 actions=FLOOD
+NXT_FLOW_MOD: ADD table:255 in_port=65534,dl_vlan=9,dl_src=00:0a:e4:25:6b:b0 actions=drop
+NXT_FLOW_MOD: ADD table:255 udp,dl_vlan_pcp=7 idle:5 actions=strip_vlan,output:0
+NXT_FLOW_MOD: ADD table:255 tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1
+NXT_FLOW_MOD: ADD table:255 udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1
+NXT_FLOW_MOD: ADD table:255 priority=60000 cookie:0x123456789abcdef hard:10 actions=CONTROLLER:65535
+NXT_FLOW_MOD: ADD table:255 actions=note:41.42.43.00.00.00,note:00.01.02.03.04.05.06.07.00.00.00.00.00.00,note:00.00.00.00.00.00
+NXT_FLOW_MOD: ADD table:255 tun_id=0x1234 cookie:0x5678 actions=FLOOD
+NXT_FLOW_MOD: ADD table:255 actions=set_tunnel:0x1234,set_tunnel64:0x9876,set_tunnel64:0x123456789
+NXT_FLOW_MOD: ADD table:255 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])
 NXT_FLOW_MOD: ADD table:1 actions=drop
 NXT_FLOW_MOD: ADD table:255 tun_id=0x1234000056780000/0xffff0000ffff0000 actions=drop
 NXT_FLOW_MOD: ADD table:255 actions=bundle(eth_src,50,active_backup,ofport,slaves:1)
@@ -92,6 +119,8 @@ dl_dst=aa:bb:cc:dd:ee:ff/00:00:00:00:00:00,actions=drop
 ])
 AT_CHECK([ovs-ofctl -F nxm parse-flows flows.txt], [0], [stdout])
 AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0], [dnl
+usable protocols: NXM
+chosen protocol: NXM-table_id
 NXT_FLOW_MOD: ADD tcp,tp_src=123 actions=FLOOD
 NXT_FLOW_MOD: ADD in_port=65534,dl_vlan=9,dl_src=00:0a:e4:25:6b:b0 actions=drop
 NXT_FLOW_MOD: ADD arp,dl_src=00:0a:e4:25:6b:b0,arp_sha=00:0a:e4:25:6b:b0 actions=drop
@@ -149,7 +178,9 @@ vlan_tci=0x1123/0x1fff,actions=drop
 ]])
 AT_CHECK([ovs-ofctl -F nxm -mmm parse-flows flows.txt], [0], [stdout])
 AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0],
-[[NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800), NXM_OF_IP_PROTO(06), NXM_OF_TCP_SRC(007b) actions=FLOOD
+[[usable protocols: NXM
+chosen protocol: NXM-table_id
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800), NXM_OF_IP_PROTO(06), NXM_OF_TCP_SRC(007b) actions=FLOOD
 NXT_FLOW_MOD: ADD NXM_OF_IN_PORT(fffe), NXM_OF_ETH_SRC(000ae4256bb0), NXM_OF_VLAN_TCI_W(1009/1fff) actions=drop
 NXT_FLOW_MOD: ADD NXM_OF_ETH_SRC(000ae4256bb0), NXM_OF_ETH_TYPE(0806), NXM_NX_ARP_SHA(000ae4256bb0) actions=drop
 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_LABEL(00012345) actions=output:2
@@ -561,7 +592,7 @@ dnl Check that "-F openflow10" rejects a flow_mod with a tun_id, since
 dnl OpenFlow 1.0 doesn't support tunnels.
 AT_SETUP([ovs-ofctl -F option and tun_id])
 AT_CHECK([ovs-ofctl -F openflow10 add-flow dummy tun_id=123,actions=drop],
-  [1], [], [ovs-ofctl: flow cannot be expressed in flow format openflow10 (flow format nxm or better is required)
+  [1], [], [ovs-ofctl: none of the usable flow formats (NXM) is among the allowed flow formats (OpenFlow10)
 ])
 AT_CLEANUP
 
@@ -596,7 +627,7 @@ dnl can't be represented in OpenFlow 1.0.
 AT_SETUP([ovs-ofctl dump-flows rejects bad -F option])
 OVS_VSWITCHD_START
 AT_CHECK([ovs-ofctl -F openflow10 dump-flows unix:br0.mgmt reg0=0xabcdef], [1], [],
-  [ovs-ofctl: unix:br0.mgmt: cannot use requested flow format nxm for specified flow
+  [ovs-ofctl: none of the usable flow formats (NXM) is among the allowed flow formats (OpenFlow10)
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
diff --git a/utilities/ovs-controller.c b/utilities/ovs-controller.c
index cb70e4f..25deaff 100644
--- a/utilities/ovs-controller.c
+++ b/utilities/ovs-controller.c
@@ -78,9 +78,9 @@ static uint32_t default_queue = UINT32_MAX;
 /* -Q, --port-queue: map from port name to port number (cast to void *). */
 static struct shash port_queues = SHASH_INITIALIZER(&port_queues);
 
-/* --with-flows: Flows to send to switch, or an empty list not to send any
- * default flows. */
-static struct list default_flows = LIST_INITIALIZER(&default_flows);
+/* --with-flows: Flows to send to switch. */
+static struct ofputil_flow_mod *default_flows;
+static size_t n_default_flows;
 
 /* --unixctl: Name of unixctl socket, or null to use the default. */
 static char *unixctl_path = NULL;
@@ -230,7 +230,8 @@ new_switch(struct switch_ *sw, struct vconn *vconn)
                 : LSW_FLOOD);
     cfg.wildcards = wildcards;
     cfg.max_idle = set_up_flows ? max_idle : -1;
-    cfg.default_flows = &default_flows;
+    cfg.default_flows = default_flows;
+    cfg.n_default_flows = n_default_flows;
     cfg.default_queue = default_queue;
     cfg.port_queues = &port_queues;
     sw->lswitch = lswitch_create(sw->rconn, &cfg);
@@ -259,29 +260,6 @@ do_switching(struct switch_ *sw)
 }
 
 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");
-    if (!stream) {
-        ovs_fatal(errno, "%s: open", name);
-    }
-
-    flow_format = NXFF_OPENFLOW10;
-    flow_mod_table_id = false;
-    while (parse_ofp_flow_mod_file(&default_flows,
-                                   &flow_format, &flow_mod_table_id,
-                                   stream, OFPFC_ADD)) {
-        continue;
-    }
-
-    fclose(stream);
-}
-
-static void
 add_port_queue(char *s)
 {
     char *save_ptr = NULL;
@@ -386,7 +364,8 @@ parse_options(int argc, char *argv[])
             break;
 
         case OPT_WITH_FLOWS:
-            read_flow_file(optarg);
+            parse_ofp_flow_mod_file(optarg, OFPFC_ADD, &default_flows,
+                                    &n_default_flows);
             break;
 
         case OPT_UNIXCTL:
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index bd184b8..c31b452 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -60,9 +60,9 @@ static bool strict;
  * (to reset flow counters). */
 static bool readd;
 
-/* -F, --flow-format: Flow format to use.  Either one of NXFF_* to force a
- * particular flow format or -1 to let ovs-ofctl choose intelligently. */
-static int preferred_flow_format = -1;
+/* -F, --flow-format: Allowed protocols.  By default, any protocol is
+ * allowed. */
+static enum ofputil_protocol allowed_protocols = OFPUTIL_P_ANY;
 
 /* -m, --more: Additional verbosity for ofp-print functions. */
 static int verbosity;
@@ -125,9 +125,9 @@ parse_options(int argc, char *argv[])
             break;
 
         case 'F':
-            preferred_flow_format = ofputil_flow_format_from_string(optarg);
-            if (preferred_flow_format < 0) {
-                ovs_fatal(0, "unknown flow format `%s'", optarg);
+            allowed_protocols = ofputil_protocols_from_string(optarg);
+            if (!allowed_protocols) {
+                ovs_fatal(0, "%s: invalid flow format(s)", optarg);
             }
             break;
 
@@ -231,12 +231,14 @@ open_vconn_socket(const char *name, struct vconn **vconnp)
     free(vconn_name);
 }
 
-static void
+static enum ofputil_protocol
 open_vconn__(const char *name, const char *default_suffix,
              struct vconn **vconnp)
 {
     char *datapath_name, *datapath_type, *socket_name;
+    enum ofputil_protocol protocol;
     char *bridge_path;
+    int ofp_version;
     struct stat s;
 
     bridge_path = xasprintf("%s/%s.%s", ovs_rundir(), name, default_suffix);
@@ -266,9 +268,17 @@ open_vconn__(const char *name, const char *default_suffix,
 
     free(bridge_path);
     free(socket_name);
+
+    ofp_version = vconn_get_version(*vconnp);
+    protocol = ofputil_protocol_from_ofp_version(ofp_version);
+    if (!protocol) {
+        ovs_fatal(0, "%s: unsupported OpenFlow version 0x%02x",
+                  name, ofp_version);
+    }
+    return protocol;
 }
 
-static void
+static enum ofputil_protocol
 open_vconn(const char *name, struct vconn **vconnp)
 {
     return open_vconn__(name, "mgmt", vconnp);
@@ -511,18 +521,17 @@ str_to_port_no(const char *vconn_name, const char *port_name)
 }
 
 static bool
-try_set_flow_format(struct vconn *vconn, enum nx_flow_format flow_format)
+set_nx_flow_format(struct vconn *vconn, enum nx_flow_format nxff)
 {
     struct ofpbuf *sff, *reply;
 
-    sff = ofputil_make_set_flow_format(flow_format);
+    sff = ofputil_encode_nx_set_flow_format(nxff);
     run(vconn_transact_noreply(vconn, sff, &reply),
         "talking to %s", vconn_get_name(vconn));
     if (reply) {
         char *s = ofp_to_string(reply->data, reply->size, 2);
-        VLOG_DBG("%s: failed to set flow format %s, controller replied: %s",
-                 vconn_get_name(vconn),
-                 ofputil_flow_format_to_string(flow_format),
+        VLOG_DBG("%s: failed to set flow format %s, switch replied: %s",
+                 vconn_get_name(vconn), ofputil_nx_flow_format_to_string(nxff),
                  s);
         free(s);
         ofpbuf_delete(reply);
@@ -531,64 +540,113 @@ try_set_flow_format(struct vconn *vconn, enum nx_flow_format flow_format)
     return true;
 }
 
-static void
-set_flow_format(struct vconn *vconn, enum nx_flow_format flow_format)
+static bool
+set_flow_mod_table_id(struct vconn *vconn, bool enable)
 {
-    struct ofpbuf *sff = ofputil_make_set_flow_format(flow_format);
-    transact_noreply(vconn, sff);
-    VLOG_DBG("%s: using user-specified flow format %s",
-             vconn_get_name(vconn),
-             ofputil_flow_format_to_string(flow_format));
+    struct ofpbuf *sfmti, *reply;
+
+    sfmti = ofputil_make_flow_mod_table_id(enable);
+    run(vconn_transact_noreply(vconn, sfmti, &reply),
+        "talking to %s", vconn_get_name(vconn));
+    if (reply) {
+        char *s = ofp_to_string(reply->data, reply->size, 2);
+        VLOG_DBG("%s: failed to %s flow mod table ID extension, "
+                 "switch replied: %s",
+                 vconn_get_name(vconn),
+                 enable ? "enable" : "disable", s);
+        free(s);
+        ofpbuf_delete(reply);
+        return false;
+    }
+    return true;
 }
 
-static enum nx_flow_format
-negotiate_highest_flow_format(struct vconn *vconn,
-                              enum nx_flow_format min_format)
+static bool
+try_set_protocol(struct vconn *vconn, enum ofputil_protocol next,
+                    enum ofputil_protocol *cur)
 {
-    if (preferred_flow_format != -1) {
-        if (preferred_flow_format < min_format) {
-            ovs_fatal(0, "%s: cannot use requested flow format %s for "
-                      "specified flow", vconn_get_name(vconn),
-                      ofputil_flow_format_to_string(min_format));
+    if (*cur == next) {
+        return true;
+    }
+
+    if (*cur & (OFPUTIL_P_OF10 | OFPUTIL_P_OF10_TID) &&
+        next & (OFPUTIL_P_NXM  | OFPUTIL_P_NXM_TID)) {
+        if (!set_nx_flow_format(vconn, NXFF_NXM)) {
+            return false;
+        }
+        *cur = *cur == OFPUTIL_P_OF10 ? OFPUTIL_P_NXM : OFPUTIL_P_NXM_TID;
+    } else if (*cur & (OFPUTIL_P_NXM  | OFPUTIL_P_NXM_TID) &&
+               next & (OFPUTIL_P_OF10 | OFPUTIL_P_OF10_TID)) {
+        if (!set_nx_flow_format(vconn, NXFF_OPENFLOW10)) {
+            return false;
         }
+        *cur = *cur == OFPUTIL_P_NXM ? OFPUTIL_P_OF10 : OFPUTIL_P_OF10_TID;
+    }
 
-        set_flow_format(vconn, preferred_flow_format);
-        return preferred_flow_format;
-    } else {
-        enum nx_flow_format flow_format;
+    if (*cur == next) {
+        return true;
+    }
 
-        if (try_set_flow_format(vconn, NXFF_NXM)) {
-            flow_format = NXFF_NXM;
-        } else {
-            flow_format = NXFF_OPENFLOW10;
+    if (!(*cur & OFPUTIL_P_TID) && next & OFPUTIL_P_TID) {
+        if (!set_flow_mod_table_id(vconn, true)) {
+            return false;
+        }
+        *cur = *cur == OFPUTIL_P_OF10 ? OFPUTIL_P_OF10_TID : OFPUTIL_P_NXM_TID;
+    } else if (*cur & OFPUTIL_P_TID && !(next & OFPUTIL_P_TID)) {
+        if (!set_flow_mod_table_id(vconn, false)) {
+            return false;
         }
+        *cur = *cur == OFPUTIL_P_OF10_TID ? OFPUTIL_P_OF10 : OFPUTIL_P_NXM;
+
+    }
+
+    return *cur == next;
+}
 
-        if (flow_format < min_format) {
-            ovs_fatal(0, "%s: cannot use switch's most advanced flow format "
-                      "%s for specified flow", vconn_get_name(vconn),
-                      ofputil_flow_format_to_string(min_format));
+/* Checks that at least one of the 'protocols' is acceptable as a flow
+ * format after a flow_mod operation, given the global
+ * 'allowed_protocols'. */
+static enum ofputil_protocol
+set_protocol_for_flow_dump(struct vconn *vconn,
+                           enum ofputil_protocol cur_protocol,
+                           enum ofputil_protocol usable_protocols)
+{
+    char *usable_s;
+    int i;
+
+    for (i = 0; i < ofputil_n_flow_dump_protocols; i++) {
+        enum ofputil_protocol f = ofputil_flow_dump_protocols[i];
+        if (f & usable_protocols & allowed_protocols
+            && try_set_protocol(vconn, f, &cur_protocol)) {
+            return f;
         }
+    }
 
-        VLOG_DBG("%s: negotiated flow format %s", vconn_get_name(vconn),
-                 ofputil_flow_format_to_string(flow_format));
-        return flow_format;
+    usable_s = ofputil_protocols_to_string(usable_protocols);
+    if (usable_protocols & allowed_protocols) {
+        ovs_fatal(0, "switch does not support any of the usable flow "
+                  "formats (%s)", usable_s);
+    } else {
+        char *allowed_s = ofputil_protocols_to_string(allowed_protocols);
+        ovs_fatal(0, "none of the usable flow formats (%s) is among the "
+                  "allowed flow formats (%s)", usable_s, allowed_s);
     }
 }
 
 static void
 do_dump_flows__(int argc, char *argv[], bool aggregate)
 {
-    enum nx_flow_format min_flow_format, flow_format;
+    enum ofputil_protocol usable_protocols, protocol;
     struct ofputil_flow_stats_request fsr;
     struct ofpbuf *request;
     struct vconn *vconn;
 
     parse_ofp_flow_stats_request_str(&fsr, aggregate, argc > 2 ? argv[2] : "");
+    usable_protocols = ofputil_usable_protocols(&fsr.match);
 
-    open_vconn(argv[1], &vconn);
-    min_flow_format = ofputil_min_flow_format(&fsr.match);
-    flow_format = negotiate_highest_flow_format(vconn, min_flow_format);
-    request = ofputil_encode_flow_stats_request(&fsr, flow_format);
+    protocol = open_vconn(argv[1], &vconn);
+    protocol = set_protocol_for_flow_dump(vconn, protocol, usable_protocols);
+    request = ofputil_encode_flow_stats_request(&fsr, protocol);
     dump_stats_transaction(argv[1], request);
     vconn_close(vconn);
 }
@@ -629,122 +687,110 @@ do_queue_stats(int argc, char *argv[])
     dump_stats_transaction(argv[1], request);
 }
 
-/* Sets up the flow format for a vconn that will be used to modify the flow
- * table.  Returns the flow format used, after possibly adding an OpenFlow
- * request to 'requests'.
- *
- * If 'preferred_flow_format' is -1, returns NXFF_OPENFLOW10 without modifying
- * 'requests', since NXFF_OPENFLOW10 is the default flow format for any
- * OpenFlow connection.
- *
- * If 'preferred_flow_format' is a specific format, adds a request to set that
- * format to 'requests' and returns the format. */
-static enum nx_flow_format
-set_initial_format_for_flow_mod(struct list *requests)
+static enum ofputil_protocol
+open_vconn_for_flow_mod(const char *remote,
+                        const struct ofputil_flow_mod *fms, size_t n_fms,
+                        struct vconn **vconnp)
 {
-    if (preferred_flow_format < 0) {
-        return NXFF_OPENFLOW10;
-    } else {
-        struct ofpbuf *sff;
+    enum ofputil_protocol usable_protocols;
+    enum ofputil_protocol cur_protocol;
+    char *usable_s;
+    int i;
 
-        sff = ofputil_make_set_flow_format(preferred_flow_format);
-        list_push_back(requests, &sff->list_node);
-        return preferred_flow_format;
+    /* Figure out what flow formats will work. */
+    usable_protocols = ofputil_flow_mod_usable_protocols(fms, n_fms);
+    if (!(usable_protocols & allowed_protocols)) {
+        char *allowed_s = ofputil_protocols_to_string(allowed_protocols);
+        usable_s = ofputil_protocols_to_string(usable_protocols);
+        ovs_fatal(0, "none of the usable flow formats (%s) is among the "
+                  "allowed flow formats (%s)", usable_s, allowed_s);
     }
-}
 
-/* Checks that 'flow_format' is acceptable as a flow format after a flow_mod
- * operation, given the global 'preferred_flow_format'. */
-static void
-check_final_format_for_flow_mod(enum nx_flow_format flow_format)
-{
-    if (preferred_flow_format >= 0 && flow_format > preferred_flow_format) {
-        ovs_fatal(0, "flow cannot be expressed in flow format %s "
-                  "(flow format %s or better is required)",
-                  ofputil_flow_format_to_string(preferred_flow_format),
-                  ofputil_flow_format_to_string(flow_format));
+    /* If the initial flow format is allowed and usable, keep it. */
+    cur_protocol = open_vconn(remote, vconnp);
+    if (usable_protocols & allowed_protocols & cur_protocol) {
+        return cur_protocol;
+    }
+
+    /* Otherwise try each flow format in turn. */
+    for (i = 0; i < sizeof(enum ofputil_protocol) * CHAR_BIT; i++) {
+        enum ofputil_protocol f = 1 << i;
+
+        if (f != cur_protocol
+            && f & usable_protocols & allowed_protocols
+            && try_set_protocol(*vconnp, f, &cur_protocol)) {
+            return f;
+        }
     }
+
+    usable_s = ofputil_protocols_to_string(usable_protocols);
+    ovs_fatal(0, "switch does not support any of the usable flow "
+              "formats (%s)", usable_s);
 }
 
 static void
-do_flow_mod_file__(int argc OVS_UNUSED, char *argv[], uint16_t command)
+do_flow_mod__(const char *remote, struct ofputil_flow_mod *fms, size_t n_fms)
 {
-    enum nx_flow_format flow_format;
-    bool flow_mod_table_id;
-    struct list requests;
+    enum ofputil_protocol protocol;
     struct vconn *vconn;
-    FILE *file;
+    size_t i;
 
-    file = !strcmp(argv[2], "-") ? stdin : fopen(argv[2], "r");
-    if (file == NULL) {
-        ovs_fatal(errno, "%s: open", argv[2]);
-    }
+    protocol = open_vconn_for_flow_mod(remote, fms, n_fms, &vconn);
 
-    list_init(&requests);
-    flow_format = set_initial_format_for_flow_mod(&requests);
-    flow_mod_table_id = false;
+    for (i = 0; i < n_fms; i++) {
+        struct ofputil_flow_mod *fm = &fms[i];
 
-    open_vconn(argv[1], &vconn);
-    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);
+        transact_noreply(vconn, ofputil_encode_flow_mod(fm, protocol));
+        free(fm->actions);
     }
     vconn_close(vconn);
-
-    if (file != stdin) {
-        fclose(file);
-    }
 }
 
 static void
-do_flow_mod__(int argc, char *argv[], uint16_t command)
+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;
+    struct ofputil_flow_mod *fms = NULL;
+    size_t n_fms = 0;
+
+    parse_ofp_flow_mod_file(argv[2], command, &fms, &n_fms);
+    do_flow_mod__(argv[1], fms, n_fms);
+    free(fms);
+}
 
+static void
+do_flow_mod(int argc, char *argv[], uint16_t command)
+{
     if (argc > 2 && !strcmp(argv[2], "-")) {
-        do_flow_mod_file__(argc, argv, command);
-        return;
+        do_flow_mod_file(argc, argv, command);
+    } else {
+        struct ofputil_flow_mod fm;
+        parse_ofp_flow_mod_str(&fm, argc > 2 ? argv[2] : "", command, false);
+        do_flow_mod__(argv[1], &fm, 1);
     }
-
-    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, &flow_mod_table_id,
-                           argc > 2 ? argv[2] : "", command, false);
-    check_final_format_for_flow_mod(flow_format);
-
-    open_vconn(argv[1], &vconn);
-    transact_multiple_noreply(vconn, &requests);
-    vconn_close(vconn);
 }
 
 static void
 do_add_flow(int argc, char *argv[])
 {
-    do_flow_mod__(argc, argv, OFPFC_ADD);
+    do_flow_mod(argc, argv, OFPFC_ADD);
 }
 
 static void
 do_add_flows(int argc, char *argv[])
 {
-    do_flow_mod_file__(argc, argv, OFPFC_ADD);
+    do_flow_mod_file(argc, argv, OFPFC_ADD);
 }
 
 static void
 do_mod_flows(int argc, char *argv[])
 {
-    do_flow_mod__(argc, argv, strict ? OFPFC_MODIFY_STRICT : OFPFC_MODIFY);
+    do_flow_mod(argc, argv, strict ? OFPFC_MODIFY_STRICT : OFPFC_MODIFY);
 }
 
 static void
 do_del_flows(int argc, char *argv[])
 {
-    do_flow_mod__(argc, argv, strict ? OFPFC_DELETE_STRICT : OFPFC_DELETE);
+    do_flow_mod(argc, argv, strict ? OFPFC_DELETE_STRICT : OFPFC_DELETE);
 }
 
 static void
@@ -1116,12 +1162,12 @@ fte_insert(struct classifier *cls, const struct cls_rule *rule,
 }
 
 /* Reads the flows in 'filename' as flow table entries in 'cls' for the version
- * with the specified 'index'.  Returns the minimum flow format required to
- * represent the flows that were read. */
-static enum nx_flow_format
+ * 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)
 {
-    enum nx_flow_format min_flow_format;
+    enum ofputil_protocol usable_protocols;
     struct ds s;
     FILE *file;
 
@@ -1131,11 +1177,10 @@ read_flows_from_file(const char *filename, struct classifier *cls, int index)
     }
 
     ds_init(&s);
-    min_flow_format = NXFF_OPENFLOW10;
+    usable_protocols = OFPUTIL_P_ANY;
     while (!ds_get_preprocessed_line(&s, file)) {
         struct fte_version *version;
         struct ofputil_flow_mod fm;
-        enum nx_flow_format min_ff;
 
         parse_ofp_str(&fm, OFPFC_ADD, ds_cstr(&s), true);
 
@@ -1147,9 +1192,7 @@ read_flows_from_file(const char *filename, struct classifier *cls, int index)
         version->actions = fm.actions;
         version->n_actions = fm.n_actions;
 
-        min_ff = ofputil_min_flow_format(&fm.cr);
-        min_flow_format = MAX(min_flow_format, min_ff);
-        check_final_format_for_flow_mod(min_flow_format);
+        usable_protocols &= ofputil_usable_protocols(&fm.cr);
 
         fte_insert(cls, &fm.cr, version, index);
     }
@@ -1159,14 +1202,15 @@ read_flows_from_file(const char *filename, struct classifier *cls, int index)
         fclose(file);
     }
 
-    return min_flow_format;
+    return usable_protocols;
 }
 
 /* Reads the OpenFlow flow table from 'vconn', which has currently active flow
- * format 'flow_format', and adds them as flow table entries in 'cls' for the
+ * format 'protocol', and adds them as flow table entries in 'cls' for the
  * version with the specified 'index'. */
 static void
-read_flows_from_switch(struct vconn *vconn, enum nx_flow_format flow_format,
+read_flows_from_switch(struct vconn *vconn,
+                       enum ofputil_protocol protocol,
                        struct classifier *cls, int index)
 {
     struct ofputil_flow_stats_request fsr;
@@ -1178,7 +1222,7 @@ read_flows_from_switch(struct vconn *vconn, enum nx_flow_format flow_format,
     cls_rule_init_catchall(&fsr.match, 0);
     fsr.out_port = OFPP_NONE;
     fsr.table_id = 0xff;
-    request = ofputil_encode_flow_stats_request(&fsr, flow_format);
+    request = ofputil_encode_flow_stats_request(&fsr, protocol);
     send_xid = ((struct ofp_header *) request->data)->xid;
     send_openflow_buffer(vconn, request);
 
@@ -1242,7 +1286,7 @@ read_flows_from_switch(struct vconn *vconn, enum nx_flow_format flow_format,
 
 static void
 fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
-                  enum nx_flow_format flow_format, struct list *packets)
+                  enum ofputil_protocol protocol, struct list *packets)
 {
     const struct fte_version *version = fte->versions[index];
     struct ofputil_flow_mod fm;
@@ -1266,7 +1310,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, false);
+    ofm = ofputil_encode_flow_mod(&fm, protocol);
     list_push_back(packets, &ofm->list_node);
 }
 
@@ -1274,7 +1318,7 @@ static void
 do_replace_flows(int argc OVS_UNUSED, char *argv[])
 {
     enum { FILE_IDX = 0, SWITCH_IDX = 1 };
-    enum nx_flow_format min_flow_format, flow_format;
+    enum ofputil_protocol usable_protocols, protocol;
     struct cls_cursor cursor;
     struct classifier cls;
     struct list requests;
@@ -1282,11 +1326,12 @@ do_replace_flows(int argc OVS_UNUSED, char *argv[])
     struct fte *fte;
 
     classifier_init(&cls);
-    min_flow_format = read_flows_from_file(argv[2], &cls, FILE_IDX);
+    usable_protocols = read_flows_from_file(argv[2], &cls, FILE_IDX);
 
-    open_vconn(argv[1], &vconn);
-    flow_format = negotiate_highest_flow_format(vconn, min_flow_format);
-    read_flows_from_switch(vconn, flow_format, &cls, SWITCH_IDX);
+    protocol = open_vconn(argv[1], &vconn);
+    protocol = set_protocol_for_flow_dump(vconn, protocol, usable_protocols);
+
+    read_flows_from_switch(vconn, protocol, &cls, SWITCH_IDX);
 
     list_init(&requests);
 
@@ -1298,7 +1343,7 @@ do_replace_flows(int argc OVS_UNUSED, char *argv[])
 
         if (sw_ver && !file_ver) {
             fte_make_flow_mod(fte, SWITCH_IDX, OFPFC_DELETE_STRICT,
-                              flow_format, &requests);
+                              protocol, &requests);
         }
     }
 
@@ -1311,8 +1356,7 @@ do_replace_flows(int argc OVS_UNUSED, char *argv[])
 
         if (file_ver
             && (readd || !sw_ver || !fte_version_equals(sw_ver, file_ver))) {
-            fte_make_flow_mod(fte, FILE_IDX, OFPFC_ADD, flow_format,
-                              &requests);
+            fte_make_flow_mod(fte, FILE_IDX, OFPFC_ADD, protocol, &requests);
         }
     }
     transact_multiple_noreply(vconn, &requests);
@@ -1330,12 +1374,12 @@ read_flows_from_source(const char *source, struct classifier *cls, int index)
         || (!strchr(source, ':') && !stat(source, &s))) {
         read_flows_from_file(source, cls, index);
     } else {
-        enum nx_flow_format flow_format;
+        enum ofputil_protocol protocol;
         struct vconn *vconn;
 
-        open_vconn(source, &vconn);
-        flow_format = negotiate_highest_flow_format(vconn, NXFF_OPENFLOW10);
-        read_flows_from_switch(vconn, flow_format, cls, index);
+        protocol = open_vconn(source, &vconn);
+        protocol = set_protocol_for_flow_dump(vconn, protocol, OFPUTIL_P_ANY);
+        read_flows_from_switch(vconn, protocol, cls, index);
         vconn_close(vconn);
     }
 }
@@ -1383,14 +1427,39 @@ do_diff_flows(int argc OVS_UNUSED, char *argv[])
 /* Undocumented commands for unit testing. */
 
 static void
-print_packet_list(struct list *packets)
+do_parse_flows__(struct ofputil_flow_mod *fms, size_t n_fms)
 {
-    struct ofpbuf *packet, *next;
+    enum ofputil_protocol usable_protocols;
+    enum ofputil_protocol protocol = 0;
+    char *usable_s;
+    size_t i;
+
+    usable_protocols = ofputil_flow_mod_usable_protocols(fms, n_fms);
+    usable_s = ofputil_protocols_to_string(usable_protocols);
+    printf("usable protocols: %s\n", usable_s);
+    free(usable_s);
 
-    LIST_FOR_EACH_SAFE (packet, next, list_node, packets) {
-        ofp_print(stdout, packet->data, packet->size, verbosity);
-        list_remove(&packet->list_node);
-        ofpbuf_delete(packet);
+    if (!(usable_protocols & allowed_protocols)) {
+        ovs_fatal(0, "no usable protocol");
+    }
+    for (i = 0; i < sizeof(enum ofputil_protocol) * CHAR_BIT; i++) {
+        protocol = 1 << i;
+        if (protocol & usable_protocols & allowed_protocols) {
+            break;
+        }
+    }
+
+    printf("chosen protocol: %s\n", ofputil_protocol_to_string(protocol));
+
+    for (i = 0; i < n_fms; i++) {
+        struct ofputil_flow_mod *fm = &fms[i];
+        struct ofpbuf *msg;
+
+        msg = ofputil_encode_flow_mod(fm, protocol);
+        ofp_print(stdout, msg->data, msg->size, verbosity);
+        ofpbuf_delete(msg);
+
+        free(fm->actions);
     }
 }
 
@@ -1399,20 +1468,10 @@ print_packet_list(struct list *packets)
 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;
+    struct ofputil_flow_mod fm;
 
-    list_init(&packets);
-    parse_ofp_flow_mod_str(&packets, &flow_format, &flow_mod_table_id,
-                           argv[1], OFPFC_ADD, false);
-    print_packet_list(&packets);
+    parse_ofp_flow_mod_str(&fm, argv[1], OFPFC_ADD, false);
+    do_parse_flows__(&fm, 1);
 }
 
 /* "parse-flows FILENAME": reads the named file as a sequence of flows (like
@@ -1420,28 +1479,12 @@ do_parse_flow(int argc OVS_UNUSED, char *argv[])
 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;
+    struct ofputil_flow_mod *fms = NULL;
+    size_t n_fms = 0;
 
-    file = fopen(argv[1], "r");
-    if (file == NULL) {
-        ovs_fatal(errno, "%s: open", argv[1]);
-    }
-
-    flow_format = NXFF_OPENFLOW10;
-    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, &flow_mod_table_id,
-                                   file, OFPFC_ADD)) {
-        print_packet_list(&packets);
-    }
-    fclose(file);
+    parse_ofp_flow_mod_file(argv[1], OFPFC_ADD, &fms, &n_fms);
+    do_parse_flows__(fms, n_fms);
+    free(fms);
 }
 
 /* "parse-nx-match": reads a series of nx_match specifications as strings from
-- 
1.7.2.5




More information about the dev mailing list