[ovs-dev] [PATCH v2] ofp-monitor: Added support for OpenFlow 1.4+ Flow Monitor

Ashish Varma ashishvarma.ovs at gmail.com
Thu Jan 31 23:49:39 UTC 2019


OVS supports Nicira version of Flow Monitor feature which allows an OpenFlow
controller to keep track of any changes in the flow table. (The changes can
done by the controller itself or by any other controller connected to OVS.)
This patch adds support for the OpenFlow 1.4+ OFPMP_FLOW_MONITOR multipart
message.
Also added support in "ovs-ofctl monitor" to send OpenFlow 1.4+ messages to
OVS.
Added unit test cases to test the OpenFlow version of Flow Monitor messages.

Signed-off-by: Ashish Varma <ashishvarma.ovs at gmail.com>
---
v1-v2:                                  

 Aligned the comments on members of structs and enumerations to  
 a common column.

 Added "14" infix for enumeration members of OpenFlow 1.4.

 Removed the union of an ofp_port_t and an ofp11_port_t as out_port in
 "ofputil_flow_monitor_request". 

 Removed "ofputil_flow_monitor_flags" enum and use "ofp14_flow_monitor_flags" 
 flags directly which is a superset of "nx_flow_monitor_flags".

 Changed comments to start with a capital letter and end with periods.

 Renamed "ofputil_get_util_flow_update_event_from_nx_event()" function to
 "ofputil_get_flow_update_event_from_nx_event".

 Declared and initialized variables at their first use.

 Used OFPUTIL_P_OF10_ANY rather than OFPUTIL_P_OF10_STD_ANY or any specific
 OFPUTIL_P_OF10_* protocol.

 Fixed parsing of the flags in the "delete" command of the Flow Monitor 
 request.

 Fixed the validation of "out_group" in ovs-ofctl monitor command.

 In function "handle_flow_monitor_request", changed "b->data" to "b->head" in 
 "ofconn_send_error(ofconn, b->data, error)" as ofpbuf b would be pulled by 
 the "ofputil_decode_flow_monitor_request" function.

 The current patch adds functionality for Flow Monitoring in OpenFlow version
 1.4 and later. I will have a separate patch for adding support for OpenFlow
 version 1.3 based on the OpenFlow extension pack 2 and would apply the 
 extension to version 1.1 and 1.2 as well. The 1.3 OpenFlow extension pack 2
 is more aligned with the NSXT FLOW_MONITOR implementation.
---
 include/openflow/openflow-1.4.h   |  94 ++++++-
 include/openvswitch/ofp-monitor.h |  49 +++-
 lib/ofp-monitor.c                 | 534 +++++++++++++++++++++++++++-----------
 lib/ofp-print.c                   |  10 +-
 ofproto/connmgr.c                 |  98 ++++---
 ofproto/connmgr.h                 |  11 +-
 ofproto/ofproto-provider.h        |   7 +-
 ofproto/ofproto.c                 | 124 +++++----
 tests/ofproto.at                  | 381 +++++++++++++++++++++++++++
 utilities/ovs-ofctl.8.in          |   3 +
 utilities/ovs-ofctl.c             |  19 +-
 11 files changed, 1079 insertions(+), 251 deletions(-)

diff --git a/include/openflow/openflow-1.4.h b/include/openflow/openflow-1.4.h
index 9399950..04d1290 100644
--- a/include/openflow/openflow-1.4.h
+++ b/include/openflow/openflow-1.4.h
@@ -358,7 +358,7 @@ OFP_ASSERT(sizeof(struct ofp14_flow_monitor_request) == 16);
 
 /* Flow monitor commands */
 enum ofp14_flow_monitor_command {
-    OFPFMC14_ADD = 0, /* New flow monitor. */
+    OFPFMC14_ADD = 0,    /* New flow monitor. */
     OFPFMC14_MODIFY = 1, /* Modify existing flow monitor. */
     OFPFMC14_DELETE = 2, /* Delete/cancel existing flow monitor. */
 };
@@ -367,18 +367,100 @@ enum ofp14_flow_monitor_command {
 enum ofp14_flow_monitor_flags {
     /* When to send updates. */
     /* Common to NX and OpenFlow 1.4 */
-    OFPFMF14_INITIAL = 1 << 0,     /* Initially matching flows. */
-    OFPFMF14_ADD = 1 << 1,         /* New matching flows as they are added. */
-    OFPFMF14_REMOVED = 1 << 2,     /* Old matching flows as they are removed. */
-    OFPFMF14_MODIFY = 1 << 3,      /* Matching flows as they are changed. */
+    OFPFMF14_INITIAL = 1 << 0,    /* Initially matching flows. */
+    OFPFMF14_ADD = 1 << 1,        /* New matching flows as they are added. */
+    OFPFMF14_REMOVED = 1 << 2,    /* Old matching flows as they are removed. */
+    OFPFMF14_MODIFY = 1 << 3,     /* Matching flows as they are changed. */
 
     /* What to include in updates. */
     /* Common to NX and OpenFlow 1.4 */
     OFPFMF14_INSTRUCTIONS = 1 << 4, /* If set, instructions are included. */
     OFPFMF14_NO_ABBREV = 1 << 5,    /* If set, include own changes in full. */
-    /* OpenFlow 1.4 */
+
+    /* Only in OpenFlow 1.4, NXFMF_* version does not have NXFMF_ONLY_OWN */
     OFPFMF14_ONLY_OWN = 1 << 6,     /* If set, don't include other controllers.
                                      */
 };
 
+/* OFPMP_FLOW_MONITOR reply header.
+ *
+ * The body of an OFPMP_FLOW_MONITOR reply is an array of variable-length
+ * structures, each of which begins with this header. The ’length’ member
+ * may be used to traverse the array, and the ’event’ member may be used to
+ * determine the particular structure.
+ *
+ * Every instance is a multiple of 8 bytes long. */
+struct ofp14_flow_update_header {
+    ovs_be16 length;  /* Length of this entry. */
+    ovs_be16 event;   /* One of OFPFME_*. */
+    /* ...other data depending on ’event’... */
+};
+OFP_ASSERT(sizeof(struct ofp14_flow_update_header) == 4);
+
+/* ’event’ values in struct ofp14_flow_update_header. */
+enum ofp14_flow_update_event {
+    /* struct ofp14_flow_update_full. */
+    OFPFME14_INITIAL = 0,    /* Flow present when flow monitor created. */
+    OFPFME14_ADDED = 1,      /* Flow was added. */
+    OFPFME14_REMOVED = 2,    /* Flow was removed. */
+    OFPFME14_MODIFIED = 3,   /* Flow instructions were changed. */
+
+    /* struct ofp14_flow_update_abbrev. */
+    OFPFME14_ABBREV = 4,     /* Abbreviated reply. */
+
+    /* struct ofp14_flow_update_header. */
+    OFPFME14_PAUSED = 5,     /* Monitoring paused (out of buffer space). */
+    OFPFME14_RESUMED = 6,    /* Monitoring resumed. */
+};
+
+/* OFPMP_FLOW_MONITOR reply for OFPFME_INITIAL, OFPFME_ADDED, OFPFME_REMOVED,
+ * and OFPFME_MODIFIED. */
+struct ofp14_flow_update_full {
+    ovs_be16 length;       /* Length is 32 + match + instructions. */
+    ovs_be16 event;        /* One of OFPFME_*. */
+    uint8_t table_id;      /* ID of flow’s table. */
+    uint8_t reason;        /* OFPRR_* for OFPFME_REMOVED, else zero. */
+    ovs_be16 idle_timeout; /* Number of seconds idle before expiration. */
+    ovs_be16 hard_timeout; /* Number of seconds before expiration. */
+    ovs_be16 priority;     /* Priority of the entry. */
+    uint8_t zeros[4];      /* Reserved, currently zeroed. */
+    ovs_be64 cookie;       /* Opaque controller-issued identifier. */
+    /* Followed by:
+     * Fields to match. Variable size.
+     * //struct ofp11_match match;
+     * Instruction set.
+     * If OFPFMF_INSTRUCTIONS was not specified, or ’event’ is
+     * OFPFME_REMOVED, no instructions are included.
+     *
+     * //struct ofp11_instruction instructions[0];
+     */
+};
+OFP_ASSERT(sizeof(struct ofp14_flow_update_full) == 24);
+
+/* OFPMP_FLOW_MONITOR reply for OFPFME_ABBREV.
+ *
+ * When the controller does not specify OFPFMF_NO_ABBREV in a monitor request,
+ * any flow tables changes due to the controller’s own requests (on the same
+ * OpenFlow channel) will be abbreviated, when possible, to this form, which
+ * simply specifies the ’xid’ of the OpenFlow request
+ * (e.g. an OFPT_FLOW_MOD) that caused the change.
+ * Some changes cannot be abbreviated and will be sent in full.
+ */
+struct ofp14_flow_update_abbrev {
+    ovs_be16 length;       /* Length is 8. */
+    ovs_be16 event;        /* OFPFME_ABBREV. */
+    ovs_be32 xid;          /* Controller-specified xid from flow_mod. */
+};
+OFP_ASSERT(sizeof(struct ofp14_flow_update_abbrev) == 8);
+
+/* OFPMP_FLOW_MONITOR reply for OFPFME_PAUSED and OFPFME_RESUMED.
+ */
+struct ofp14_flow_update_paused {
+    ovs_be16 length;       /* Length is 8. */
+    ovs_be16 event;        /* One of OFPFME_*. */
+    uint8_t zeros[4];      /* Reserved, currently zeroed. */
+};
+OFP_ASSERT(sizeof(struct ofp14_flow_update_paused) == 8);
+
+
 #endif /* openflow/openflow-1.4.h */
diff --git a/include/openvswitch/ofp-monitor.h b/include/openvswitch/ofp-monitor.h
index 5951260..0d52a4e 100644
--- a/include/openvswitch/ofp-monitor.h
+++ b/include/openvswitch/ofp-monitor.h
@@ -23,6 +23,9 @@
 #include "openvswitch/ofp-protocol.h"
 #include "openvswitch/ofpbuf.h"
 
+struct vl_mff_map;
+struct tun_table;
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -56,19 +59,27 @@ void ofputil_flow_removed_format(struct ds *,
                                  const struct ofputil_port_map *,
                                  const struct ofputil_table_map *);
 
-/* Abstract nx_flow_monitor_request. */
+/* Abstract nx_flow_monitor_request/ofp14_flow_monitor_request.
+ * Using ofp14_flow_monitor_flags for both nx_ and ofp14_ because
+ * ofp14_flow_monitor_flags is a superset of nx_flow_monitor_flags with only
+ * OFPUTIL_FMF_ONLY_OWN equivalent not present in nx_flow_monitor_flags. */
 struct ofputil_flow_monitor_request {
     uint32_t id;
-    enum nx_flow_monitor_flags flags;
+    enum ofp14_flow_monitor_flags flags;
     ofp_port_t out_port;
+    uint32_t out_group; /* Only in OpenFlow 1.4+ */
     uint8_t table_id;
+    uint8_t command;    /* Only in OpenFlow 1.4+ */
     struct match match;
 };
 
 int ofputil_decode_flow_monitor_request(struct ofputil_flow_monitor_request *,
-                                        struct ofpbuf *msg);
+                                        struct ofpbuf *msg,
+                                        const struct tun_table *tun_table,
+                                        const struct vl_mff_map *vl_mff_map);
 void ofputil_append_flow_monitor_request(
-    const struct ofputil_flow_monitor_request *, struct ofpbuf *msg);
+    const struct ofputil_flow_monitor_request *, struct ofpbuf *msg,
+                                                  enum ofp_version);
 void ofputil_flow_monitor_request_format(
     struct ds *, const struct ofputil_flow_monitor_request *,
     const struct ofputil_port_map *, const struct ofputil_table_map *);
@@ -80,11 +91,27 @@ char *parse_flow_monitor_request(struct ofputil_flow_monitor_request *,
                                  enum ofputil_protocol *usable_protocols)
     OVS_WARN_UNUSED_RESULT;
 
-/* Abstract nx_flow_update. */
+enum ofputil_flow_update_event {
+   OFPUTIL_FME_INITIAL = 0,   /* Flow present when flow monitor created.
+                               * Only in OpenFlow 1.4+ */
+   OFPUTIL_FME_ADDED = 1,     /* Flow was added. For NXST_FLOW_MONITOR reply,
+                               * this is used for both created and added. */
+   OFPUTIL_FME_REMOVED = 2,   /* Flow was removed. */
+   OFPUTIL_FME_MODIFIED = 3,  /* Flow instructions were changed. */
+   OFPUTIL_FME_ABBREV = 4,    /* Abbreviated reply. */
+
+   OFPUTIL_FME_PAUSED = 5,    /* Monitoring paused (out of buffer space).
+                               * Only in OpenFlow 1.4+ */
+   OFPUTIL_FME_RESUMED = 6,   /* Monitoring resumed.
+                               * Only in OpenFlow 1.4+ */
+};
+
+/* Abstract flow_update. */
 struct ofputil_flow_update {
-    enum nx_flow_update_event event;
+    enum ofputil_flow_update_event event;
 
-    /* Used only for NXFME_ADDED, NXFME_DELETED, NXFME_MODIFIED. */
+    /* Used only for OFPUTIL_FME_INITIAL, OFPUTIL_FME_ADDED,
+     * OFPUTIL_FME_REMOVED, OFPUTIL_FME_MODIFIED. */
     enum ofp_flow_removed_reason reason;
     uint16_t idle_timeout;
     uint16_t hard_timeout;
@@ -95,16 +122,18 @@ struct ofputil_flow_update {
     const struct ofpact *ofpacts;
     size_t ofpacts_len;
 
-    /* Used only for NXFME_ABBREV. */
+    /* Used only for OFPUTIL_FME_ABBREV. */
     ovs_be32 xid;
 };
 
 int ofputil_decode_flow_update(struct ofputil_flow_update *,
                                struct ofpbuf *msg, struct ofpbuf *ofpacts);
-void ofputil_start_flow_update(struct ovs_list *replies);
+void ofputil_start_flow_update(struct ovs_list *replies,
+                               enum ofputil_protocol ofconn_protocol);
 void ofputil_append_flow_update(const struct ofputil_flow_update *,
                                 struct ovs_list *replies,
-                                const struct tun_table *);
+                                const struct tun_table *,
+                                enum ofputil_protocol ofconn_protocol);
 void ofputil_flow_update_format(struct ds *,
                                 const struct ofputil_flow_update *,
                                 const struct ofputil_port_map *,
diff --git a/lib/ofp-monitor.c b/lib/ofp-monitor.c
index 4c15d11..e790c44 100644
--- a/lib/ofp-monitor.c
+++ b/lib/ofp-monitor.c
@@ -333,11 +333,12 @@ ofputil_flow_removed_format(struct ds *s,
 
 /* ofputil_flow_monitor_request */
 
-/* Converts an NXST_FLOW_MONITOR request in 'msg' into an abstract
- * ofputil_flow_monitor_request in 'rq'.
+/* Converts an NXST_FLOW_MONITOR or OFPST14_FLOW_MONITOR request in 'msg' into
+ * an abstract ofputil_flow_monitor_request in 'rq'.
  *
- * Multiple NXST_FLOW_MONITOR requests can be packed into a single OpenFlow
- * message.  Calling this function multiple times for a single 'msg' iterates
+ * Multiple NXST_FLOW_MONITOR or OFPST14_FLOW_MONITOR requests can be packed
+ * into a single OpenFlow message.
+ * Calling this function multiple times for a single 'msg' iterates
  * through the requests.  The caller must initially leave 'msg''s layer
  * pointers null and not modify them between calls.
  *
@@ -345,83 +346,157 @@ ofputil_flow_removed_format(struct ds *s,
  * otherwise an OFPERR_* value. */
 int
 ofputil_decode_flow_monitor_request(struct ofputil_flow_monitor_request *rq,
-                                    struct ofpbuf *msg)
+                                    struct ofpbuf *msg,
+                                    const struct tun_table *tun_table,
+                                    const struct vl_mff_map *vl_mff_map)
 {
-    struct nx_flow_monitor_request *nfmr;
     uint16_t flags;
+    enum ofpraw raw;
 
-    if (!msg->header) {
-        ofpraw_pull_assert(msg);
+    enum ofperr error = (msg->header ? ofpraw_decode(&raw, msg->header)
+             : ofpraw_pull(&raw, msg));
+
+    if (error) {
+        return error;
     }
 
     if (!msg->size) {
         return EOF;
     }
 
-    nfmr = ofpbuf_try_pull(msg, sizeof *nfmr);
-    if (!nfmr) {
-        VLOG_WARN_RL(&rl, "NXST_FLOW_MONITOR request has %"PRIu32" "
-                     "leftover bytes at end", msg->size);
-        return OFPERR_OFPBRC_BAD_LEN;
-    }
+    /* NXST version does not have a command, the message implicitly means
+     * an ADD. */
+    rq->command = OFPFMC14_ADD;
 
-    flags = ntohs(nfmr->flags);
-    if (!(flags & (NXFMF_ADD | NXFMF_DELETE | NXFMF_MODIFY))
-        || flags & ~(NXFMF_INITIAL | NXFMF_ADD | NXFMF_DELETE
-                     | NXFMF_MODIFY | NXFMF_ACTIONS | NXFMF_OWN)) {
-        VLOG_WARN_RL(&rl, "NXST_FLOW_MONITOR has bad flags %#"PRIx16, flags);
-        return OFPERR_OFPMOFC_BAD_FLAGS;
-    }
+    if (raw == OFPRAW_NXST_FLOW_MONITOR_REQUEST) {
+        struct nx_flow_monitor_request *nfmr =
+                               ofpbuf_try_pull(msg, sizeof *nfmr);
+        if (!nfmr) {
+            VLOG_WARN_RL(&rl, "NXST_FLOW_MONITOR request has %"PRIu32" "
+                         "leftover bytes at end", msg->size);
+            return OFPERR_OFPBRC_BAD_LEN;
+        }
+
+        flags = ntohs(nfmr->flags);
+        if (!(flags & (NXFMF_ADD | NXFMF_DELETE | NXFMF_MODIFY))
+            || flags & ~(NXFMF_INITIAL | NXFMF_ADD | NXFMF_DELETE
+                         | NXFMF_MODIFY | NXFMF_ACTIONS | NXFMF_OWN)) {
+            VLOG_WARN_RL(&rl, "NXST_FLOW_MONITOR has bad flags %#"PRIx16,
+                         flags);
+            return OFPERR_OFPMOFC_BAD_FLAGS;
+        }
+
+        if (!is_all_zeros(nfmr->zeros, sizeof nfmr->zeros)) {
+            return OFPERR_NXBRC_MUST_BE_ZERO;
+        }
+
+        rq->id = ntohl(nfmr->id);
+        rq->flags = flags;
+        rq->out_port = u16_to_ofp(ntohs(nfmr->out_port));
+        /* NX version does not have out_group, hence match on any group */
+        rq->out_group = OFPG_ANY;
+        rq->table_id = nfmr->table_id;
 
-    if (!is_all_zeros(nfmr->zeros, sizeof nfmr->zeros)) {
-        return OFPERR_NXBRC_MUST_BE_ZERO;
+        return nx_pull_match(msg, ntohs(nfmr->match_len), &rq->match, NULL,
+                             NULL, false, NULL, NULL);
     }
+    else if (raw == OFPRAW_OFPST14_FLOW_MONITOR_REQUEST) {
+        struct ofp14_flow_monitor_request *ofmr =
+                                    ofpbuf_try_pull(msg, sizeof *ofmr);
+        if (!ofmr) {
+            VLOG_WARN_RL(&rl, "OFPST14_FLOW_MONITOR request has %"PRIu32" "
+                         "leftover bytes at end", msg->size);
+            return OFPERR_OFPBRC_BAD_LEN;
+        }
+
+        flags = ntohs(ofmr->flags);
+        if (ofmr->command != OFPFMC14_DELETE &&
+            (!(flags & (OFPFMF14_ADD | OFPFMF14_REMOVED | OFPFMF14_MODIFY))
+             || flags & ~(OFPFMF14_INITIAL | OFPFMF14_ADD | OFPFMF14_REMOVED
+                         | OFPFMF14_MODIFY | OFPFMF14_INSTRUCTIONS
+                         | OFPFMF14_NO_ABBREV | OFPFMF14_ONLY_OWN))) {
+            VLOG_WARN_RL(&rl, "OFPST14_FLOW_MONITOR has bad flags %#"PRIx16,
+                         flags);
+            return OFPERR_OFPMOFC_BAD_FLAGS;
+        }
 
-    rq->id = ntohl(nfmr->id);
-    rq->flags = flags;
-    rq->out_port = u16_to_ofp(ntohs(nfmr->out_port));
-    rq->table_id = nfmr->table_id;
+        if (ofmr->command > OFPFMC14_DELETE) {
+            VLOG_WARN_RL(&rl, "OFPST14_FLOW_MONITOR request has bad command"
+                         " %"PRIu32" ", ofmr->command);
+            return OFPERR_OFPMOFC_BAD_COMMAND;
+        }
+
+        rq->id = ntohl(ofmr->monitor_id);
+        rq->flags = flags;
+        error = ofputil_port_from_ofp11(ofmr->out_port, &rq->out_port);
+        if (error) {
+            return error;
+        }
+        rq->out_group = ntohl(ofmr->out_group);
+        rq->table_id = ofmr->table_id;
+        rq->command = ofmr->command;
 
-    return nx_pull_match(msg, ntohs(nfmr->match_len), &rq->match, NULL,
-                         NULL, false, NULL, NULL);
+        return ofputil_pull_ofp11_match(msg, tun_table, vl_mff_map, &rq->match,
+                                         NULL);
+    } else {
+        OVS_NOT_REACHED();
+    }
 }
 
 void
 ofputil_append_flow_monitor_request(
-    const struct ofputil_flow_monitor_request *rq, struct ofpbuf *msg)
+    const struct ofputil_flow_monitor_request *rq, struct ofpbuf *msg,
+                                         enum ofp_version ofp_version)
 {
-    struct nx_flow_monitor_request *nfmr;
     size_t start_ofs;
     int match_len;
 
     if (!msg->size) {
-        ofpraw_put(OFPRAW_NXST_FLOW_MONITOR_REQUEST, OFP10_VERSION, msg);
+        ofp_version == OFP10_VERSION ?
+           ofpraw_put(OFPRAW_NXST_FLOW_MONITOR_REQUEST, OFP10_VERSION, msg) :
+           ofpraw_put(OFPRAW_OFPST14_FLOW_MONITOR_REQUEST, ofp_version, msg);
     }
 
     start_ofs = msg->size;
-    ofpbuf_put_zeros(msg, sizeof *nfmr);
-    match_len = nx_put_match(msg, &rq->match, htonll(0), htonll(0));
-
-    nfmr = ofpbuf_at_assert(msg, start_ofs, sizeof *nfmr);
-    nfmr->id = htonl(rq->id);
-    nfmr->flags = htons(rq->flags);
-    nfmr->out_port = htons(ofp_to_u16(rq->out_port));
-    nfmr->match_len = htons(match_len);
-    nfmr->table_id = rq->table_id;
+    if (ofp_version == OFP10_VERSION) {
+        struct nx_flow_monitor_request *nfmr;
+        ofpbuf_put_zeros(msg, sizeof *nfmr);
+        match_len = nx_put_match(msg, &rq->match, htonll(0), htonll(0));
+
+        nfmr = ofpbuf_at_assert(msg, start_ofs, sizeof *nfmr);
+        nfmr->id = htonl(rq->id);
+        nfmr->flags = htons(rq->flags);
+        nfmr->out_port = htons(ofp_to_u16(rq->out_port));
+        nfmr->match_len = htons(match_len);
+        nfmr->table_id = rq->table_id;
+    } else {
+        struct ofp14_flow_monitor_request *ofmr;
+        ofpbuf_put_zeros(msg, sizeof *ofmr);
+        match_len = oxm_put_match(msg, &rq->match, ofp_version);
+
+        ofmr = ofpbuf_at_assert(msg, start_ofs, sizeof *ofmr);
+        ofmr->monitor_id = htonl(rq->id);
+        ofmr->out_port = ofputil_port_to_ofp11(rq->out_port);
+        ofmr->out_group = htonl(rq->out_group);
+        ofmr->flags = htons(rq->flags);
+        ofmr->table_id = rq->table_id;
+        ofmr->command = OFPFMC14_ADD;
+    }
 }
 
 static const char *
-nx_flow_monitor_flags_to_name(uint32_t bit)
+flow_monitor_flags_to_name(uint32_t bit)
 {
-    enum nx_flow_monitor_flags fmf = bit;
+    enum ofp14_flow_monitor_flags fmf = bit;
 
     switch (fmf) {
-    case NXFMF_INITIAL: return "initial";
-    case NXFMF_ADD: return "add";
-    case NXFMF_DELETE: return "delete";
-    case NXFMF_MODIFY: return "modify";
-    case NXFMF_ACTIONS: return "actions";
-    case NXFMF_OWN: return "own";
+    case OFPFMF14_INITIAL: return "initial";
+    case OFPFMF14_ADD: return "add";
+    case OFPFMF14_REMOVED: return "delete";
+    case OFPFMF14_MODIFY: return "modify";
+    case OFPFMF14_INSTRUCTIONS: return "actions";
+    case OFPFMF14_NO_ABBREV: return "own";
+    case OFPFMF14_ONLY_OWN: return "only-own";
     }
 
     return NULL;
@@ -434,7 +509,7 @@ ofputil_flow_monitor_request_format(
     const struct ofputil_table_map *table_map)
 {
     ds_put_format(s, "\n id=%"PRIu32" flags=", request->id);
-    ofp_print_bit_names(s, request->flags, nx_flow_monitor_flags_to_name, ',');
+    ofp_print_bit_names(s, request->flags, flow_monitor_flags_to_name, ',');
 
     if (request->out_port != OFPP_NONE) {
         ds_put_cstr(s, " out_port=");
@@ -464,9 +539,11 @@ parse_flow_monitor_request__(struct ofputil_flow_monitor_request *fmr,
 
     fmr->id = atomic_count_inc(&id);
 
-    fmr->flags = (NXFMF_INITIAL | NXFMF_ADD | NXFMF_DELETE | NXFMF_MODIFY
-                  | NXFMF_OWN | NXFMF_ACTIONS);
+    fmr->flags = (OFPFMF14_INITIAL | OFPFMF14_ADD | OFPFMF14_REMOVED
+                  | OFPFMF14_MODIFY | OFPFMF14_NO_ABBREV
+                  | OFPFMF14_INSTRUCTIONS);
     fmr->out_port = OFPP_NONE;
+    fmr->out_group = OFPG_ANY;
     fmr->table_id = 0xff;
     match_init_catchall(&fmr->match);
 
@@ -475,17 +552,17 @@ parse_flow_monitor_request__(struct ofputil_flow_monitor_request *fmr,
         char *error = NULL;
 
         if (!strcmp(name, "!initial")) {
-            fmr->flags &= ~NXFMF_INITIAL;
+            fmr->flags &= ~OFPFMF14_INITIAL;
         } else if (!strcmp(name, "!add")) {
-            fmr->flags &= ~NXFMF_ADD;
+            fmr->flags &= ~OFPFMF14_ADD;
         } else if (!strcmp(name, "!delete")) {
-            fmr->flags &= ~NXFMF_DELETE;
+            fmr->flags &= ~OFPFMF14_REMOVED;
         } else if (!strcmp(name, "!modify")) {
-            fmr->flags &= ~NXFMF_MODIFY;
+            fmr->flags &= ~OFPFMF14_MODIFY;
         } else if (!strcmp(name, "!actions")) {
-            fmr->flags &= ~NXFMF_ACTIONS;
+            fmr->flags &= ~OFPFMF14_INSTRUCTIONS;
         } else if (!strcmp(name, "!own")) {
-            fmr->flags &= ~NXFMF_OWN;
+            fmr->flags &= ~OFPFMF14_NO_ABBREV;
         } else if (ofp_parse_protocol(name, &p)) {
             match_set_dl_type(&fmr->match, htons(p->dl_type));
             if (p->nw_proto) {
@@ -506,6 +583,8 @@ parse_flow_monitor_request__(struct ofputil_flow_monitor_request *fmr,
                 }
             } else if (!strcmp(name, "out_port")) {
                 fmr->out_port = u16_to_ofp(atoi(value));
+            } else if (!strcmp(name, "out_group")) {
+                fmr->out_group = atoi(value);
             } else {
                 return xasprintf("%s: unknown keyword %s", str_, name);
             }
@@ -537,14 +616,30 @@ parse_flow_monitor_request(struct ofputil_flow_monitor_request *fmr,
     return error;
 }
 
-/* Converts an NXST_FLOW_MONITOR reply (also known as a flow update) in 'msg'
+static enum ofputil_flow_update_event
+ofputil_get_flow_update_event_from_nx_event
+                                      (enum nx_flow_update_event event)
+{
+    switch (event) {
+    case NXFME_ADDED: return OFPUTIL_FME_ADDED;
+    case NXFME_DELETED: return OFPUTIL_FME_REMOVED;
+    case NXFME_MODIFIED: return OFPUTIL_FME_MODIFIED;
+    case NXFME_ABBREV: return OFPUTIL_FME_ABBREV;
+    }
+
+    return -1;
+}
+
+
+/* Converts an NXST_FLOW_MONITOR/OFPST14_FLOW_MONITOR reply
+ * (also known as a flow update) in 'msg'
  * into an abstract ofputil_flow_update in 'update'.  The caller must have
  * initialized update->match to point to space allocated for a match.
  *
  * Uses 'ofpacts' to store the abstract OFPACT_* version of the update's
- * actions (except for NXFME_ABBREV, which never includes actions).  The caller
- * must initialize 'ofpacts' and retains ownership of it.  'update->ofpacts'
- * will point into the 'ofpacts' buffer.
+ * actions (except for NXFME_ABBREV/OFPFME_ABBREV, which never includes
+ * actions). The caller must initialize 'ofpacts' and retains ownership of it.
+ * 'update->ofpacts' will point into the 'ofpacts' buffer.
  *
  * Multiple flow updates can be packed into a single OpenFlow message.  Calling
  * this function multiple times for a single 'msg' iterates through the
@@ -557,7 +652,6 @@ int
 ofputil_decode_flow_update(struct ofputil_flow_update *update,
                            struct ofpbuf *msg, struct ofpbuf *ofpacts)
 {
-    struct nx_flow_update_header *nfuh;
     unsigned int length;
     struct ofp_header *oh;
 
@@ -570,79 +664,161 @@ ofputil_decode_flow_update(struct ofputil_flow_update *update,
         return EOF;
     }
 
-    if (msg->size < sizeof(struct nx_flow_update_header)) {
-        goto bad_len;
-    }
-
     oh = msg->header;
 
-    nfuh = msg->data;
-    update->event = ntohs(nfuh->event);
-    length = ntohs(nfuh->length);
-    if (length > msg->size || length % 8) {
-        goto bad_len;
-    }
-
-    if (update->event == NXFME_ABBREV) {
-        struct nx_flow_update_abbrev *nfua;
+    if (oh->version == OFP10_VERSION) {
+        if (msg->size < sizeof(struct nx_flow_update_header)) {
+            goto bad_len;
+        }
 
-        if (length != sizeof *nfua) {
+        struct nx_flow_update_header *nfuh = msg->data;
+        update->event =
+          ofputil_get_flow_update_event_from_nx_event(ntohs(nfuh->event));
+        length = ntohs(nfuh->length);
+        if (length > msg->size || length % 8) {
             goto bad_len;
         }
 
-        nfua = ofpbuf_pull(msg, sizeof *nfua);
-        update->xid = nfua->xid;
-        return 0;
-    } else if (update->event == NXFME_ADDED
-               || update->event == NXFME_DELETED
-               || update->event == NXFME_MODIFIED) {
-        struct nx_flow_update_full *nfuf;
-        unsigned int actions_len;
-        unsigned int match_len;
-        enum ofperr error;
+        if (update->event == OFPUTIL_FME_ABBREV) {
+            if (length != sizeof(struct nx_flow_update_abbrev)) {
+                goto bad_len;
+            }
 
-        if (length < sizeof *nfuf) {
+            struct nx_flow_update_abbrev *nfua =
+                                            ofpbuf_pull(msg, sizeof *nfua);
+            update->xid = nfua->xid;
+            return 0;
+        } else if (update->event == OFPUTIL_FME_ADDED
+                   || update->event == OFPUTIL_FME_REMOVED
+                   || update->event == OFPUTIL_FME_MODIFIED) {
+            if (length < sizeof(struct nx_flow_update_full)) {
+                goto bad_len;
+            }
+
+            struct nx_flow_update_full *nfuf = ofpbuf_pull(msg, sizeof *nfuf);
+            unsigned int match_len = ntohs(nfuf->match_len);
+            if (sizeof *nfuf + match_len > length) {
+                goto bad_len;
+            }
+
+            update->reason = ntohs(nfuf->reason);
+            update->idle_timeout = ntohs(nfuf->idle_timeout);
+            update->hard_timeout = ntohs(nfuf->hard_timeout);
+            update->table_id = nfuf->table_id;
+            update->cookie = nfuf->cookie;
+            update->priority = ntohs(nfuf->priority);
+
+            enum ofperr error = nx_pull_match(msg, match_len, &update->match,
+                                              NULL, NULL, false, NULL, NULL);
+            if (error) {
+                return error;
+            }
+
+            unsigned int actions_len =
+                               length - sizeof *nfuf - ROUND_UP(match_len, 8);
+            error = ofpacts_pull_openflow_actions(msg, actions_len,
+                                                  oh->version,
+                                                  NULL, NULL, ofpacts);
+            if (error) {
+                return error;
+            }
+
+            update->ofpacts = ofpacts->data;
+            update->ofpacts_len = ofpacts->size;
+
+            return 0;
+        } else {
+            VLOG_WARN_RL(&rl, "NXST_FLOW_MONITOR reply has bad event %"PRIu16,
+                         ntohs(nfuh->event));
+            return OFPERR_NXBRC_FM_BAD_EVENT;
+        }
+
+    } else {
+        struct ofp14_flow_update_header *ofuh;
+        uint16_t instructions_len, padded_match_len;
+
+        if (msg->size < sizeof(struct ofp14_flow_update_header)) {
             goto bad_len;
         }
 
-        nfuf = ofpbuf_pull(msg, sizeof *nfuf);
-        match_len = ntohs(nfuf->match_len);
-        if (sizeof *nfuf + match_len > length) {
+        ofuh = msg->data;
+        /* there is one to one mapping between ofp14_flow_update_event and
+         * ofputil_flow_update_event */
+        update->event = ntohs(ofuh->event);
+        length = ntohs(ofuh->length);
+        if (length > msg->size || length % 8) {
             goto bad_len;
         }
 
-        update->reason = ntohs(nfuf->reason);
-        update->idle_timeout = ntohs(nfuf->idle_timeout);
-        update->hard_timeout = ntohs(nfuf->hard_timeout);
-        update->table_id = nfuf->table_id;
-        update->cookie = nfuf->cookie;
-        update->priority = ntohs(nfuf->priority);
+        if (update->event == OFPUTIL_FME_ABBREV) {
+            struct ofp14_flow_update_abbrev *ofua;
 
-        error = nx_pull_match(msg, match_len, &update->match, NULL, NULL,
-                              false, NULL, NULL);
-        if (error) {
-            return error;
-        }
+            if (length != sizeof *ofua) {
+                goto bad_len;
+            }
 
-        actions_len = length - sizeof *nfuf - ROUND_UP(match_len, 8);
-        error = ofpacts_pull_openflow_actions(msg, actions_len, oh->version,
-                                              NULL, NULL, ofpacts);
-        if (error) {
-            return error;
-        }
+            ofua = ofpbuf_pull(msg, sizeof *ofua);
+            update->xid = ofua->xid;
+            return 0;
+        } else if (update->event == OFPUTIL_FME_INITIAL
+                   || update->event == OFPUTIL_FME_ADDED
+                   || update->event == OFPUTIL_FME_REMOVED
+                   || update->event == OFPUTIL_FME_MODIFIED) {
+            if (length < sizeof(struct ofp14_flow_update_full)) {
+                goto bad_len;
+            }
 
-        update->ofpacts = ofpacts->data;
-        update->ofpacts_len = ofpacts->size;
-        return 0;
-    } else {
-        VLOG_WARN_RL(&rl, "NXST_FLOW_MONITOR reply has bad event %"PRIu16,
-                     ntohs(nfuh->event));
-        return OFPERR_NXBRC_FM_BAD_EVENT;
+            struct ofp14_flow_update_full *ofuf =
+                              ofpbuf_pull(msg, sizeof *ofuf);
+            update->reason = ofuf->reason;
+            update->idle_timeout = ntohs(ofuf->idle_timeout);
+            update->hard_timeout = ntohs(ofuf->hard_timeout);
+            update->table_id = ofuf->table_id;
+            update->cookie = ofuf->cookie;
+            update->priority = ntohs(ofuf->priority);
+
+            enum ofperr error = ofputil_pull_ofp11_match(msg, NULL, NULL,
+                                      &update->match, &padded_match_len);
+            if (error) {
+                return error;
+            }
+
+            instructions_len = length - sizeof *ofuf - padded_match_len;
+            error = ofpacts_pull_openflow_instructions(msg, instructions_len,
+                                                       oh->version,
+                                                       NULL,
+                                                       NULL,
+                                                       ofpacts);
+            if (error) {
+                return error;
+            }
+
+            update->ofpacts = ofpacts->data;
+            update->ofpacts_len = ofpacts->size;
+
+            return 0;
+        } else if (update->event == OFPUTIL_FME_PAUSED
+                   || update->event == OFPUTIL_FME_RESUMED) {
+            if (length != sizeof(struct ofp14_flow_update_paused)) {
+                goto bad_len;
+            }
+            struct ofp14_flow_update_paused *ofup =
+                                      ofpbuf_pull(msg, sizeof *ofup);
+
+            return 0;
+
+        } else {
+            VLOG_WARN_RL(&rl, "OFPMP_FLOW_MONITOR reply has bad event %"PRIu16,
+                         ntohs(ofuh->event));
+            return OFPERR_NXBRC_FM_BAD_EVENT;
+        }
     }
 
 bad_len:
-    VLOG_WARN_RL(&rl, "NXST_FLOW_MONITOR reply has %"PRIu32" "
-                 "leftover bytes at end", msg->size);
+    VLOG_WARN_RL(&rl, "%s reply has %"PRIu32" "
+                 "leftover bytes at end",
+                 oh->version == OFP10_VERSION ? "NXST_FLOW_MONITOR" :
+                 "OFPMP_FLOW_MONITOR", msg->size);
     return OFPERR_OFPBRC_BAD_LEN;
 }
 
@@ -667,27 +843,48 @@ ofputil_encode_flow_monitor_cancel(uint32_t id)
 }
 
 void
-ofputil_start_flow_update(struct ovs_list *replies)
+ofputil_start_flow_update(struct ovs_list *replies,
+                          enum ofputil_protocol ofconn_protocol)
 {
     struct ofpbuf *msg;
 
-    msg = ofpraw_alloc_xid(OFPRAW_NXST_FLOW_MONITOR_REPLY, OFP10_VERSION,
+    msg = ofpraw_alloc_xid(ofconn_protocol & OFPUTIL_P_OF10_ANY ?
+                           OFPRAW_NXST_FLOW_MONITOR_REPLY :
+                           OFPRAW_OFPST14_FLOW_MONITOR_REPLY,
+                           ofputil_protocol_to_ofp_version(ofconn_protocol),
                            htonl(0), 1024);
 
     ovs_list_init(replies);
     ovs_list_push_back(replies, &msg->list_node);
 }
 
+static enum nx_flow_update_event
+ofputil_flow_update_event_to_nx_event(enum ofputil_flow_update_event event)
+{
+    switch (event) {
+    case OFPUTIL_FME_INITIAL:
+    case OFPUTIL_FME_ADDED: return NXFME_ADDED;
+    case OFPUTIL_FME_REMOVED: return NXFME_DELETED;
+    case OFPUTIL_FME_MODIFIED: return NXFME_MODIFIED;
+    case OFPUTIL_FME_ABBREV: return NXFME_ABBREV;
+
+    case OFPUTIL_FME_PAUSED:
+    case OFPUTIL_FME_RESUMED:
+        OVS_NOT_REACHED();
+    }
+    return -1;
+}
+
 void
 ofputil_append_flow_update(const struct ofputil_flow_update *update,
                            struct ovs_list *replies,
-                           const struct tun_table *tun_table)
+                           const struct tun_table *tun_table,
+                           enum ofputil_protocol ofconn_protocol)
 {
     struct ofputil_flow_update *update_ =
         CONST_CAST(struct ofputil_flow_update *, update);
     const struct tun_table *orig_tun_table;
     enum ofp_version version = ofpmp_version(replies);
-    struct nx_flow_update_header *nfuh;
     struct ofpbuf *msg;
     size_t start_ofs;
 
@@ -697,33 +894,64 @@ ofputil_append_flow_update(const struct ofputil_flow_update *update,
     msg = ofpbuf_from_list(ovs_list_back(replies));
     start_ofs = msg->size;
 
-    if (update->event == NXFME_ABBREV) {
-        struct nx_flow_update_abbrev *nfua;
+    if (ofconn_protocol & OFPUTIL_P_OF10_ANY) {
+        struct nx_flow_update_header *nfuh;
+        if (update->event == OFPUTIL_FME_ABBREV) {
+            struct nx_flow_update_abbrev *nfua;
 
-        nfua = ofpbuf_put_zeros(msg, sizeof *nfua);
-        nfua->xid = update->xid;
+            nfua = ofpbuf_put_zeros(msg, sizeof *nfua);
+            nfua->xid = update->xid;
+        } else {
+            ofpbuf_put_zeros(msg, sizeof(struct nx_flow_update_full));
+            int match_len = nx_put_match(msg, &update->match, htonll(0),
+                                                         htonll(0));
+            ofpacts_put_openflow_actions(update->ofpacts, update->ofpacts_len,
+                                         msg, version);
+            struct nx_flow_update_full *nfuf =
+                             ofpbuf_at_assert(msg, start_ofs, sizeof *nfuf);
+            nfuf->reason = htons(update->reason);
+            nfuf->priority = htons(update->priority);
+            nfuf->idle_timeout = htons(update->idle_timeout);
+            nfuf->hard_timeout = htons(update->hard_timeout);
+            nfuf->match_len = htons(match_len);
+            nfuf->table_id = update->table_id;
+            nfuf->cookie = update->cookie;
+        }
+
+        nfuh = ofpbuf_at_assert(msg, start_ofs, sizeof *nfuh);
+        nfuh->length = htons(msg->size - start_ofs);
+        nfuh->event =
+        htons(ofputil_flow_update_event_to_nx_event(update->event));
     } else {
-        struct nx_flow_update_full *nfuf;
-        int match_len;
+        if (update->event == OFPUTIL_FME_ABBREV) {
+            struct ofp14_flow_update_abbrev *ofua =
+                              ofpbuf_put_zeros(msg, sizeof *ofua);
+            ofua->xid = update->xid;
+        } else {
+            ofpbuf_put_zeros(msg, sizeof(struct ofp14_flow_update_full));
+            oxm_put_match(msg, &update->match, version);
+            ofpacts_put_openflow_instructions(update->ofpacts,
+                                              update->ofpacts_len,
+                                              msg, version);
+            struct ofp14_flow_update_full *ofuf =
+                     ofpbuf_at_assert(msg, start_ofs, sizeof *ofuf);
+            ofuf->reason = update->reason;
+            ofuf->priority = htons(update->priority);
+            ofuf->idle_timeout = htons(update->idle_timeout);
+            ofuf->hard_timeout = htons(update->hard_timeout);
+            ofuf->table_id = update->table_id;
+            ofuf->cookie = update->cookie;
+        }
 
-        ofpbuf_put_zeros(msg, sizeof *nfuf);
-        match_len = nx_put_match(msg, &update->match, htonll(0), htonll(0));
-        ofpacts_put_openflow_actions(update->ofpacts, update->ofpacts_len, msg,
-                                     version);
-        nfuf = ofpbuf_at_assert(msg, start_ofs, sizeof *nfuf);
-        nfuf->reason = htons(update->reason);
-        nfuf->priority = htons(update->priority);
-        nfuf->idle_timeout = htons(update->idle_timeout);
-        nfuf->hard_timeout = htons(update->hard_timeout);
-        nfuf->match_len = htons(match_len);
-        nfuf->table_id = update->table_id;
-        nfuf->cookie = update->cookie;
+        struct ofp14_flow_update_header *ofuh =
+                       ofpbuf_at_assert(msg, start_ofs, sizeof *ofuh);
+        /* Length is 32 + match + instructions. */
+        ofuh->length = htons(msg->size - start_ofs);
+        /* There is a one to one mapping between ofputil_flow_update_event
+         * and ofp14_flow_update_event */
+        ofuh->event = htons(update->event);
     }
 
-    nfuh = ofpbuf_at_assert(msg, start_ofs, sizeof *nfuh);
-    nfuh->length = htons(msg->size - start_ofs);
-    nfuh->event = htons(update->event);
-
     ofpmp_postappend(replies, start_ofs);
     update_->match.flow.tunnel.metadata.tab = orig_tun_table;
 }
@@ -738,24 +966,36 @@ ofputil_flow_update_format(struct ds *s,
 
     ds_put_cstr(s, "\n event=");
     switch (update->event) {
-    case NXFME_ADDED:
+    case OFPUTIL_FME_INITIAL:
+        ds_put_cstr(s, "INITIAL");
+        break;
+
+    case OFPUTIL_FME_ADDED:
         ds_put_cstr(s, "ADDED");
         break;
 
-    case NXFME_DELETED:
+    case OFPUTIL_FME_REMOVED:
         ds_put_format(s, "DELETED reason=%s",
                       ofp_flow_removed_reason_to_string(update->reason,
                                                         reasonbuf,
                                                         sizeof reasonbuf));
         break;
 
-    case NXFME_MODIFIED:
+    case OFPUTIL_FME_MODIFIED:
         ds_put_cstr(s, "MODIFIED");
         break;
 
-    case NXFME_ABBREV:
+    case OFPUTIL_FME_ABBREV:
         ds_put_format(s, "ABBREV xid=0x%"PRIx32, ntohl(update->xid));
         return;
+
+    case OFPUTIL_FME_PAUSED:
+        ds_put_cstr(s, "PAUSED");
+        return;
+
+    case OFPUTIL_FME_RESUMED:
+        ds_put_cstr(s, "RESUMED");
+        return;
     }
 
     ds_put_format(s, " table=");
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 9c88515..ea0a5fd 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -744,7 +744,7 @@ ofp_print_nxt_flow_monitor_cancel(struct ds *string,
 }
 
 static enum ofperr
-ofp_print_nxst_flow_monitor_request(struct ds *string,
+ofp_print_flow_monitor_request(struct ds *string,
                                     const struct ofp_header *oh,
                                     const struct ofputil_port_map *port_map,
                                     const struct ofputil_table_map *table_map)
@@ -754,7 +754,7 @@ ofp_print_nxst_flow_monitor_request(struct ds *string,
         struct ofputil_flow_monitor_request request;
         int retval;
 
-        retval = ofputil_decode_flow_monitor_request(&request, &b);
+        retval = ofputil_decode_flow_monitor_request(&request, &b, NULL, NULL);
         if (retval) {
             return retval != EOF ? retval : 0;
         }
@@ -765,7 +765,7 @@ ofp_print_nxst_flow_monitor_request(struct ds *string,
 }
 
 static enum ofperr
-ofp_print_nxst_flow_monitor_reply(struct ds *string,
+ofp_print_flow_monitor_reply(struct ds *string,
                                   const struct ofp_header *oh,
                                   const struct ofputil_port_map *port_map,
                                   const struct ofputil_table_map *table_map)
@@ -1150,11 +1150,11 @@ ofp_to_string__(const struct ofp_header *oh,
         break;
 
     case OFPTYPE_FLOW_MONITOR_STATS_REQUEST:
-        return ofp_print_nxst_flow_monitor_request(string, msg, port_map,
+        return ofp_print_flow_monitor_request(string, msg, port_map,
                                                    table_map);
 
     case OFPTYPE_FLOW_MONITOR_STATS_REPLY:
-        return ofp_print_nxst_flow_monitor_reply(string, msg, port_map,
+        return ofp_print_flow_monitor_reply(string, msg, port_map,
                                                  table_map);
 
     case OFPTYPE_BUNDLE_CONTROL:
diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
index 684f77c..6119667 100644
--- a/ofproto/connmgr.c
+++ b/ofproto/connmgr.c
@@ -127,11 +127,12 @@ struct ofconn {
     /* State of monitors for a single ongoing flow_mod.
      *
      * 'updates' is a list of "struct ofpbuf"s that contain
-     * NXST_FLOW_MONITOR_REPLY messages representing the changes made by the
-     * current flow_mod.
+     * NXST_FLOW_MONITOR_REPLY/OFPST14_FLOW_MONITOR_REPLY messages representing
+     * the changes made by the current flow_mod.
      *
      * When 'updates' is nonempty, 'sent_abbrev_update' is true if 'updates'
-     * contains an update event of type NXFME_ABBREV and false otherwise.. */
+     * contains an update event of type NXFME_ABBREV/OFPFME_ABBREV and
+     * false otherwise.. */
     struct ovs_list updates OVS_GUARDED_BY(ofproto_mutex);
     bool sent_abbrev_update OVS_GUARDED_BY(ofproto_mutex);
 
@@ -2093,6 +2094,7 @@ ofmonitor_create(const struct ofputil_flow_monitor_request *request,
     m->id = request->id;
     m->flags = request->flags;
     m->out_port = request->out_port;
+    m->out_group = request->out_group;
     m->table_id = request->table_id;
     minimatch_init(&m->match, &request->match);
 
@@ -2128,7 +2130,7 @@ ofmonitor_destroy(struct ofmonitor *m)
 
 void
 ofmonitor_report(struct connmgr *mgr, struct rule *rule,
-                 enum nx_flow_update_event event,
+                 enum ofputil_flow_update_event event,
                  enum ofp_flow_removed_reason reason,
                  const struct ofconn *abbrev_ofconn, ovs_be32 abbrev_xid,
                  const struct rule_actions *old_actions)
@@ -2138,39 +2140,42 @@ ofmonitor_report(struct connmgr *mgr, struct rule *rule,
         return;
     }
 
-    enum nx_flow_monitor_flags update;
+    enum ofp14_flow_monitor_flags update;
     switch (event) {
-    case NXFME_ADDED:
-        update = NXFMF_ADD;
+    case OFPUTIL_FME_ADDED:
+        update = OFPFMF14_ADD;
         rule->add_seqno = rule->modify_seqno = monitor_seqno++;
         break;
 
-    case NXFME_DELETED:
-        update = NXFMF_DELETE;
+    case OFPUTIL_FME_REMOVED:
+        update = OFPFMF14_REMOVED;
         break;
 
-    case NXFME_MODIFIED:
-        update = NXFMF_MODIFY;
+    case OFPUTIL_FME_MODIFIED:
+        update = OFPFMF14_MODIFY;
         rule->modify_seqno = monitor_seqno++;
         break;
 
     default:
-    case NXFME_ABBREV:
+    case OFPUTIL_FME_INITIAL:
+    case OFPUTIL_FME_ABBREV:
+    case OFPUTIL_FME_PAUSED:
+    case OFPUTIL_FME_RESUMED:
         OVS_NOT_REACHED();
     }
 
     struct ofconn *ofconn;
     LIST_FOR_EACH (ofconn, node, &mgr->all_conns) {
         if (ofconn->monitor_paused) {
-            /* Only send NXFME_DELETED notifications for flows that were added
-             * before we paused. */
-            if (event != NXFME_DELETED
+            /* Only send OFPUTIL_FME_REMOVED notifications for flows that were
+             * added before we paused. */
+            if (event != OFPUTIL_FME_REMOVED
                 || rule->add_seqno > ofconn->monitor_paused) {
                 continue;
             }
         }
 
-        enum nx_flow_monitor_flags flags = 0;
+        enum ofp14_flow_monitor_flags flags = 0;
         struct ofmonitor *m;
         HMAP_FOR_EACH (m, ofconn_node, &ofconn->monitors) {
             if (m->flags & update
@@ -2180,23 +2185,30 @@ ofmonitor_report(struct connmgr *mgr, struct rule *rule,
                         && ofpacts_output_to_port(old_actions->ofpacts,
                                                   old_actions->ofpacts_len,
                                                   m->out_port)))
+                && ofproto_rule_has_out_group(rule, m->out_group)
                 && cls_rule_is_loose_match(&rule->cr, &m->match)) {
+
+                if ((m->flags & OFPFMF14_ONLY_OWN) &&
+                    (ofconn != abbrev_ofconn)) {
+                    continue;
+                }
                 flags |= m->flags;
             }
         }
 
         if (flags) {
             if (ovs_list_is_empty(&ofconn->updates)) {
-                ofputil_start_flow_update(&ofconn->updates);
+                ofputil_start_flow_update(&ofconn->updates,
+                                          ofconn_get_protocol(ofconn));
                 ofconn->sent_abbrev_update = false;
             }
 
-            if (flags & NXFMF_OWN || ofconn != abbrev_ofconn
+            if (flags & OFPFMF14_NO_ABBREV || ofconn != abbrev_ofconn
                 || ofconn->monitor_paused) {
                 struct ofputil_flow_update fu;
 
                 fu.event = event;
-                fu.reason = event == NXFME_DELETED ? reason : 0;
+                fu.reason = event == OFPUTIL_FME_REMOVED ? reason : 0;
                 fu.table_id = rule->table_id;
                 fu.cookie = rule->flow_cookie;
                 minimatch_expand(&rule->cr.match, &fu.match);
@@ -2207,7 +2219,7 @@ ofmonitor_report(struct connmgr *mgr, struct rule *rule,
                 fu.hard_timeout = rule->hard_timeout;
                 ovs_mutex_unlock(&rule->mutex);
 
-                if (flags & NXFMF_ACTIONS) {
+                if (flags & OFPFMF14_INSTRUCTIONS) {
                     const struct rule_actions *actions
                         = rule_get_actions(rule);
                     fu.ofpacts = actions->ofpacts;
@@ -2217,14 +2229,16 @@ ofmonitor_report(struct connmgr *mgr, struct rule *rule,
                     fu.ofpacts_len = 0;
                 }
                 ofputil_append_flow_update(&fu, &ofconn->updates,
-                                           ofproto_get_tun_tab(rule->ofproto));
+                                           ofproto_get_tun_tab(rule->ofproto),
+                                           ofconn_get_protocol(ofconn));
             } else if (!ofconn->sent_abbrev_update) {
                 struct ofputil_flow_update fu;
 
-                fu.event = NXFME_ABBREV;
+                fu.event = OFPUTIL_FME_ABBREV;
                 fu.xid = abbrev_xid;
                 ofputil_append_flow_update(&fu, &ofconn->updates,
-                                           ofproto_get_tun_tab(rule->ofproto));
+                                           ofproto_get_tun_tab(rule->ofproto),
+                                           ofconn_get_protocol(ofconn));
 
                 ofconn->sent_abbrev_update = true;
             }
@@ -2250,8 +2264,19 @@ ofmonitor_flush(struct connmgr *mgr)
             && rconn_packet_counter_n_bytes(counter) > 128 * 1024) {
             COVERAGE_INC(ofmonitor_pause);
             ofconn->monitor_paused = monitor_seqno++;
-            struct ofpbuf *pause = ofpraw_alloc_xid(
-                OFPRAW_NXT_FLOW_MONITOR_PAUSED, OFP10_VERSION, htonl(0), 0);
+            struct ofpbuf *pause;
+            if (ofconn->protocol & OFPUTIL_P_OF10_ANY) {
+                pause = ofpraw_alloc_xid(OFPRAW_NXT_FLOW_MONITOR_PAUSED,
+                                         OFP10_VERSION, htonl(0), 0);
+            } else {
+                struct ofp14_flow_update_paused *fup;
+                pause = ofpraw_alloc_xid(OFPRAW_OFPST14_FLOW_MONITOR_REPLY,
+                         ofputil_protocol_to_ofp_version(ofconn->protocol),
+                                         htonl(0), 8);
+                fup = ofpbuf_put_zeros(pause, sizeof *fup);
+                fup->length = htons(sizeof *fup);
+                fup->event = htons(OFPFME14_PAUSED);
+            }
             ofconn_send(ofconn, pause, counter);
         }
     }
@@ -2262,18 +2287,31 @@ ofmonitor_resume(struct ofconn *ofconn)
     OVS_REQUIRES(ofproto_mutex)
 {
     struct rule_collection rules;
-    rule_collection_init(&rules);
-
     struct ofmonitor *m;
+
+    rule_collection_init(&rules);
     HMAP_FOR_EACH (m, ofconn_node, &ofconn->monitors) {
         ofmonitor_collect_resume_rules(m, ofconn->monitor_paused, &rules);
     }
 
     struct ovs_list msgs = OVS_LIST_INITIALIZER(&msgs);
-    ofmonitor_compose_refresh_updates(&rules, &msgs);
 
-    struct ofpbuf *resumed = ofpraw_alloc_xid(OFPRAW_NXT_FLOW_MONITOR_RESUMED,
-                                              OFP10_VERSION, htonl(0), 0);
+    ofmonitor_compose_refresh_updates(&rules, &msgs,
+                                      ofconn_get_protocol(ofconn));
+    struct ofpbuf *resumed;
+    if (ofconn->protocol  & OFPUTIL_P_OF10_ANY) {
+        resumed = ofpraw_alloc_xid(OFPRAW_NXT_FLOW_MONITOR_RESUMED,
+                                   OFP10_VERSION, htonl(0), 0);
+    } else {
+        struct ofp14_flow_update_paused *fup;
+        resumed = ofpraw_alloc_xid(OFPRAW_OFPST14_FLOW_MONITOR_REPLY,
+                         ofputil_protocol_to_ofp_version(ofconn->protocol),
+                                   htonl(0), sizeof *fup);
+        fup = ofpbuf_put_zeros(resumed, sizeof *fup);
+        fup->length = htons(sizeof *fup);
+        fup->event = htons(OFPUTIL_FME_RESUMED);
+    }
+
     ovs_list_push_back(&msgs, &resumed->list_node);
     ofconn_send_replies(ofconn, &msgs);
 
diff --git a/ofproto/connmgr.h b/ofproto/connmgr.h
index 7ca7fe6..e10155f 100644
--- a/ofproto/connmgr.h
+++ b/ofproto/connmgr.h
@@ -188,11 +188,10 @@ struct ofmonitor {
     struct ofconn *ofconn;      /* Owning 'ofconn'. */
     struct hmap_node ofconn_node; /* In ofconn's 'monitors' hmap. */
     uint32_t id;
-
-    enum nx_flow_monitor_flags flags;
-
+    enum ofp14_flow_monitor_flags flags;
     /* Matching. */
     ofp_port_t out_port;
+    uint32_t out_group;
     uint8_t table_id;
     struct minimatch match;
 };
@@ -208,7 +207,8 @@ void ofmonitor_destroy(struct ofmonitor *)
     OVS_REQUIRES(ofproto_mutex);
 
 void ofmonitor_report(struct connmgr *, struct rule *,
-                      enum nx_flow_update_event, enum ofp_flow_removed_reason,
+                      enum ofputil_flow_update_event,
+                      enum ofp_flow_removed_reason,
                       const struct ofconn *abbrev_ofconn, ovs_be32 abbrev_xid,
                       const struct rule_actions *old_actions)
     OVS_REQUIRES(ofproto_mutex);
@@ -220,7 +220,8 @@ void ofmonitor_collect_resume_rules(struct ofmonitor *, uint64_t seqno,
                                     struct rule_collection *)
     OVS_REQUIRES(ofproto_mutex);
 void ofmonitor_compose_refresh_updates(struct rule_collection *rules,
-                                       struct ovs_list *msgs)
+                                       struct ovs_list *msgs,
+                                       enum ofputil_protocol ofconn_protocol)
     OVS_REQUIRES(ofproto_mutex);
 
 void connmgr_send_table_status(struct connmgr *,
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index d1a87a5..9a55532 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -49,6 +49,7 @@
 #include "openvswitch/ofp-port.h"
 #include "openvswitch/ofp-switch.h"
 #include "openvswitch/ofp-table.h"
+#include "openvswitch/ofp-monitor.h"
 #include "ovs-atomic.h"
 #include "ovs-rcu.h"
 #include "ovs-thread.h"
@@ -419,7 +420,8 @@ struct rule {
      * 'add_seqno' is the sequence number when this rule was created.
      * 'modify_seqno' is the sequence number when this rule was last modified.
      * See 'monitor_seqno' in connmgr.c for more information. */
-    enum nx_flow_monitor_flags monitor_flags OVS_GUARDED_BY(ofproto_mutex);
+    enum ofp14_flow_monitor_flags
+                            monitor_flags OVS_GUARDED_BY(ofproto_mutex);
     uint64_t add_seqno OVS_GUARDED_BY(ofproto_mutex);
     uint64_t modify_seqno OVS_GUARDED_BY(ofproto_mutex);
 
@@ -480,6 +482,9 @@ const struct rule_actions *rule_actions_create(const struct ofpact *, size_t);
 void rule_actions_destroy(const struct rule_actions *);
 bool ofproto_rule_has_out_port(const struct rule *, ofp_port_t port)
     OVS_REQUIRES(ofproto_mutex);
+bool
+ofproto_rule_has_out_group(const struct rule *rule, uint32_t group_id)
+    OVS_REQUIRES(ofproto_mutex);
 
 #define DECL_OFPROTO_COLLECTION(TYPE, NAME)                             \
     DECL_OBJECT_COLLECTION(TYPE, NAME)                                  \
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index d63d476..6c8d547 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -3085,7 +3085,7 @@ ofproto_rule_has_out_port(const struct rule *rule, ofp_port_t port)
 }
 
 /* Returns true if 'rule' has group and equals group_id. */
-static bool
+bool
 ofproto_rule_has_out_group(const struct rule *rule, uint32_t group_id)
     OVS_REQUIRES(ofproto_mutex)
 {
@@ -5118,7 +5118,7 @@ add_flow_finish(struct ofproto *ofproto, struct ofproto_flow_mod *ofm,
     if (old_rule) {
         ovsrcu_postpone(remove_rule_rcu, old_rule);
     } else {
-        ofmonitor_report(ofproto->connmgr, new_rule, NXFME_ADDED, 0,
+        ofmonitor_report(ofproto->connmgr, new_rule, OFPUTIL_FME_ADDED, 0,
                          req ? req->ofconn : NULL,
                          req ? req->request->xid : 0, NULL);
 
@@ -5519,8 +5519,8 @@ replace_rule_finish(struct ofproto *ofproto, struct ofproto_flow_mod *ofm,
         learned_cookies_dec(ofproto, old_actions, dead_cookies);
 
         if (replaced_rule) {
-            enum nx_flow_update_event event = ofm->command == OFPFC_ADD
-                ? NXFME_ADDED : NXFME_MODIFIED;
+            enum ofputil_flow_update_event event = ofm->command == OFPFC_ADD
+                ? OFPUTIL_FME_ADDED : OFPUTIL_FME_MODIFIED;
 
             bool changed_cookie = (new_rule->flow_cookie
                                    != old_rule->flow_cookie);
@@ -5530,7 +5530,7 @@ replace_rule_finish(struct ofproto *ofproto, struct ofproto_flow_mod *ofm,
                                                   old_actions->ofpacts,
                                                   old_actions->ofpacts_len);
 
-            if (event != NXFME_MODIFIED || changed_actions
+            if (event != OFPUTIL_FME_MODIFIED || changed_actions
                 || changed_cookie) {
                 ofmonitor_report(ofproto->connmgr, new_rule, event, 0,
                                  req ? req->ofconn : NULL,
@@ -5539,7 +5539,7 @@ replace_rule_finish(struct ofproto *ofproto, struct ofproto_flow_mod *ofm,
             }
         } else {
             /* XXX: This is slight duplication with delete_flows_finish__() */
-            ofmonitor_report(ofproto->connmgr, old_rule, NXFME_DELETED,
+            ofmonitor_report(ofproto->connmgr, old_rule, OFPUTIL_FME_REMOVED,
                              OFPRR_EVICTION,
                              req ? req->ofconn : NULL,
                              req ? req->request->xid : 0, NULL);
@@ -5816,7 +5816,8 @@ delete_flows_finish__(struct ofproto *ofproto,
              * before the rule is actually destroyed. */
             rule->removed_reason = reason;
 
-            ofmonitor_report(ofproto->connmgr, rule, NXFME_DELETED, reason,
+            ofmonitor_report(ofproto->connmgr, rule,
+                             OFPUTIL_FME_REMOVED, reason,
                              req ? req->ofconn : NULL,
                              req ? req->request->xid : 0, NULL);
 
@@ -6220,17 +6221,25 @@ handle_barrier_request(struct ofconn *ofconn, const struct ofp_header *oh)
 
 static void
 ofproto_compose_flow_refresh_update(const struct rule *rule,
-                                    enum nx_flow_monitor_flags flags,
+                                    enum ofp14_flow_monitor_flags flags,
                                     struct ovs_list *msgs,
-                                    const struct tun_table *tun_table)
+                                    const struct tun_table *tun_table,
+                                    enum ofputil_protocol ofconn_protocol)
     OVS_REQUIRES(ofproto_mutex)
 {
     const struct rule_actions *actions;
     struct ofputil_flow_update fu;
 
-    fu.event = (flags & (NXFMF_INITIAL | NXFMF_ADD)
-                ? NXFME_ADDED : NXFME_MODIFIED);
-    fu.reason = 0;
+    if (ofconn_protocol & OFPUTIL_P_OF10_ANY) {
+        fu.event = (flags & (OFPFMF14_INITIAL | OFPFMF14_ADD)
+                    ? OFPUTIL_FME_ADDED : OFPUTIL_FME_MODIFIED);
+    } else {
+        fu.event = flags & OFPFMF14_INITIAL ? OFPUTIL_FME_INITIAL :
+                   flags & OFPFMF14_ADD ?
+                             OFPUTIL_FME_ADDED : OFPUTIL_FME_MODIFIED;
+    }
+
+    fu.reason = 0;  /* because this function is not called for flow delete */
     ovs_mutex_lock(&rule->mutex);
     fu.idle_timeout = rule->idle_timeout;
     fu.hard_timeout = rule->hard_timeout;
@@ -6240,29 +6249,30 @@ ofproto_compose_flow_refresh_update(const struct rule *rule,
     minimatch_expand(&rule->cr.match, &fu.match);
     fu.priority = rule->cr.priority;
 
-    actions = flags & NXFMF_ACTIONS ? rule_get_actions(rule) : NULL;
+    actions = flags & OFPFMF14_INSTRUCTIONS ? rule_get_actions(rule) : NULL;
     fu.ofpacts = actions ? actions->ofpacts : NULL;
     fu.ofpacts_len = actions ? actions->ofpacts_len : 0;
 
     if (ovs_list_is_empty(msgs)) {
-        ofputil_start_flow_update(msgs);
+        ofputil_start_flow_update(msgs, ofconn_protocol);
     }
-    ofputil_append_flow_update(&fu, msgs, tun_table);
+    ofputil_append_flow_update(&fu, msgs, tun_table, ofconn_protocol);
 }
 
 void
 ofmonitor_compose_refresh_updates(struct rule_collection *rules,
-                                  struct ovs_list *msgs)
+                                  struct ovs_list *msgs,
+                                  enum ofputil_protocol ofconn_protocol)
     OVS_REQUIRES(ofproto_mutex)
 {
     struct rule *rule;
 
     RULE_COLLECTION_FOR_EACH (rule, rules) {
-        enum nx_flow_monitor_flags flags = rule->monitor_flags;
+        enum ofp14_flow_monitor_flags flags = rule->monitor_flags;
         rule->monitor_flags = 0;
 
         ofproto_compose_flow_refresh_update(rule, flags, msgs,
-                ofproto_get_tun_tab(rule->ofproto));
+                ofproto_get_tun_tab(rule->ofproto), ofconn_protocol);
     }
 }
 
@@ -6272,7 +6282,7 @@ ofproto_collect_ofmonitor_refresh_rule(const struct ofmonitor *m,
                                        struct rule_collection *rules)
     OVS_REQUIRES(ofproto_mutex)
 {
-    enum nx_flow_monitor_flags update;
+    enum ofp14_flow_monitor_flags update;
 
     if (rule_is_hidden(rule)) {
         return;
@@ -6282,11 +6292,15 @@ ofproto_collect_ofmonitor_refresh_rule(const struct ofmonitor *m,
         return;
     }
 
+    if (!ofproto_rule_has_out_group(rule, m->out_group)) {
+        return;
+    }
+
     if (seqno) {
         if (rule->add_seqno > seqno) {
-            update = NXFMF_ADD | NXFMF_MODIFY;
+            update = OFPFMF14_ADD | OFPFMF14_MODIFY;
         } else if (rule->modify_seqno > seqno) {
-            update = NXFMF_MODIFY;
+            update = OFPFMF14_MODIFY;
         } else {
             return;
         }
@@ -6295,13 +6309,13 @@ ofproto_collect_ofmonitor_refresh_rule(const struct ofmonitor *m,
             return;
         }
     } else {
-        update = NXFMF_INITIAL;
+        update = OFPFMF14_INITIAL;
     }
 
     if (!rule->monitor_flags) {
         rule_collection_add(rules, rule);
     }
-    rule->monitor_flags |= update | (m->flags & NXFMF_ACTIONS);
+    rule->monitor_flags |= update | (m->flags & OFPFMF14_INSTRUCTIONS);
 }
 
 static void
@@ -6330,7 +6344,7 @@ ofproto_collect_ofmonitor_initial_rules(struct ofmonitor *m,
                                         struct rule_collection *rules)
     OVS_REQUIRES(ofproto_mutex)
 {
-    if (m->flags & NXFMF_INITIAL) {
+    if (m->flags & OFPFMF14_INITIAL) {
         ofproto_collect_ofmonitor_refresh_rules(m, 0, rules);
     }
 }
@@ -6370,15 +6384,18 @@ handle_flow_monitor_request(struct ofconn *ofconn, const struct ovs_list *msgs)
     struct ofmonitor **monitors = NULL;
     size_t allocated_monitors = 0;
     size_t n_monitors = 0;
+    enum ofperr error = 0;
 
     ovs_mutex_lock(&ofproto_mutex);
     struct ofpbuf *b;
     LIST_FOR_EACH (b, list_node, msgs) {
         for (;;) {
-            enum ofperr error;
-
+            struct ofmonitor *m;
             struct ofputil_flow_monitor_request request;
-            int retval = ofputil_decode_flow_monitor_request(&request, b);
+            int retval =
+                 ofputil_decode_flow_monitor_request(&request, b,
+                                                  ofproto_get_tun_tab(ofproto),
+                                                  &ofproto->vl_mff_map);
             if (retval == EOF) {
                 break;
             } else if (retval) {
@@ -6392,29 +6409,31 @@ handle_flow_monitor_request(struct ofconn *ofconn, const struct ovs_list *msgs)
                 goto error;
             }
 
-            struct ofmonitor *m;
-            error = ofmonitor_create(&request, ofconn, &m);
-            if (error) {
-                goto error;
+            if (request.command == OFPFMC14_ADD) {
+                error = ofmonitor_create(&request, ofconn, &m);
             }
-
-            if (n_monitors >= allocated_monitors) {
-                monitors = x2nrealloc(monitors, &allocated_monitors,
-                                      sizeof *monitors);
+            else if (request.command == OFPFMC14_DELETE) {
+                error = flow_monitor_delete(ofconn, request.id);
             }
-            monitors[n_monitors++] = m;
-            continue;
+            else if (request.command == OFPFMC14_MODIFY) {
+                error = flow_monitor_delete(ofconn, request.id);
 
-        error:
-            ofconn_send_error(ofconn, b->data, error);
+                if (!error) {
+                    error = ofmonitor_create(&request, ofconn, &m);
+                }
+            }
 
-            for (size_t i = 0; i < n_monitors; i++) {
-                ofmonitor_destroy(monitors[i]);
+            if (error) {
+                goto error;
             }
-            free(monitors);
-            ovs_mutex_unlock(&ofproto_mutex);
 
-            return error;
+            if (request.command != OFPFMC14_DELETE) {
+                if (n_monitors >= allocated_monitors) {
+                    monitors = x2nrealloc(monitors, &allocated_monitors,
+                                          sizeof *monitors);
+                }
+                monitors[n_monitors++] = m;
+            }
         }
     }
 
@@ -6425,8 +6444,11 @@ handle_flow_monitor_request(struct ofconn *ofconn, const struct ovs_list *msgs)
     }
 
     struct ovs_list replies;
-    ofpmp_init(&replies, ofpbuf_from_list(ovs_list_back(msgs))->header);
-    ofmonitor_compose_refresh_updates(&rules, &replies);
+    struct ofp_header *oh = ofpbuf_from_list(ovs_list_back(msgs))->header;
+    ofpmp_init(&replies, oh);
+
+    ofmonitor_compose_refresh_updates(&rules, &replies,
+                               ofputil_protocol_from_ofp_version(oh->version));
     ovs_mutex_unlock(&ofproto_mutex);
 
     rule_collection_destroy(&rules);
@@ -6435,6 +6457,16 @@ handle_flow_monitor_request(struct ofconn *ofconn, const struct ovs_list *msgs)
     free(monitors);
 
     return 0;
+
+error:
+    ofconn_send_error(ofconn, b->header, error);
+    for (size_t i = 0; i < n_monitors; i++) {
+        ofmonitor_destroy(monitors[i]);
+    }
+    free(monitors);
+    ovs_mutex_unlock(&ofproto_mutex);
+
+    return error;
 }
 
 static enum ofperr
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 3021f93..af187fe 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -5173,6 +5173,384 @@ NXT_FLOW_MONITOR_RESUMED:
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([ofproto - OpenFlow14 flow monitoring])
+AT_KEYWORDS([monitor])
+OVS_VSWITCHD_START
+
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=123,actions=output:1
+
+# Start a monitor watching the flow table and check the initial reply.
+ovs-ofctl -OOpenFlow14 monitor br0 watch: --detach --no-chdir --pidfile >monitor.log 2>&1
+AT_CAPTURE_FILE([monitor.log])
+ovs-appctl -t ovs-ofctl ofctl/barrier
+AT_CHECK([sed 's/ (xid=0x[[1-9a-fA-F]][[0-9a-fA-F]]*)//' monitor.log], [0],
+  [OFPST_FLOW_MONITOR reply (OF1.4):
+ event=INITIAL table=0 cookie=0 in_port=0,dl_vlan=123 actions=output:1
+OFPT_BARRIER_REPLY (OF1.4):
+])
+
+# Add, delete, and modify some flows and check the updates.
+ovs-appctl -t ovs-ofctl ofctl/set-output-file monitor.log
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=124,actions=output:2
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=123,actions=output:5
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=123,dl_vlan_pcp=0,actions=output:6
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=123,dl_vlan_pcp=1,actions=output:7
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=123,actions=output:8
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=65535,dl_vlan_pcp=0,actions=output:9
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=65535,dl_vlan_pcp=1,actions=output:10
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=65535,actions=output:11
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=8191,dl_vlan_pcp=0,actions=output:12
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=8191,dl_vlan_pcp=1,actions=output:13
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=8191,actions=output:14
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=0,dl_vlan_pcp=0,actions=output:15
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=0,dl_vlan_pcp=1,actions=output:16
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=0,actions=output:17
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=0,dl_vlan_pcp=0,actions=output:18
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=0,dl_vlan_pcp=1,actions=output:19
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=0,actions=output:20
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan_pcp=0,actions=output:21
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan_pcp=1,actions=output:22
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,actions=output:23
+ovs-ofctl -OOpenFlow14 mod-flows br0 dl_vlan=123,actions=output:3
+ovs-ofctl -OOpenFlow14 del-flows br0 dl_vlan=123
+ovs-ofctl -OOpenFlow14 del-flows br0
+ovs-appctl -t ovs-ofctl ofctl/barrier
+AT_CHECK([sed 's/ (xid=0x[[1-9a-fA-F]][[0-9a-fA-F]]*)//' monitor.log | multiline_sort], [0],
+[OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=124 actions=output:2
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=123 actions=output:5
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=123,dl_vlan_pcp=0 actions=output:6
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=123,dl_vlan_pcp=1 actions=output:7
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=123 actions=output:8
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=0,dl_vlan_pcp=0 actions=output:9
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=0,dl_vlan_pcp=1 actions=output:10
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,vlan_tci=0x0000/0x1fff actions=output:11
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=4095,dl_vlan_pcp=0 actions=output:12
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=4095,dl_vlan_pcp=1 actions=output:13
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=4095 actions=output:14
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=0,dl_vlan_pcp=0 actions=output:15
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=0,dl_vlan_pcp=1 actions=output:16
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=0 actions=output:17
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=0,dl_vlan_pcp=0 actions=output:18
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=0,dl_vlan_pcp=1 actions=output:19
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=0 actions=output:20
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan_pcp=0 actions=output:21
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan_pcp=1 actions=output:22
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0 actions=output:23
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=MODIFIED table=0 cookie=0 in_port=0,dl_vlan=123 actions=output:3
+ event=MODIFIED table=0 cookie=0 in_port=0,dl_vlan=123,dl_vlan_pcp=0 actions=output:3
+ event=MODIFIED table=0 cookie=0 in_port=0,dl_vlan=123,dl_vlan_pcp=1 actions=output:3
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=123 actions=output:3
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=123,dl_vlan_pcp=0 actions=output:3
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=123,dl_vlan_pcp=1 actions=output:3
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=DELETED reason=delete table=0 cookie=0 in_port=0 actions=output:23
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=0 actions=output:20
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=0,dl_vlan_pcp=0 actions=output:18
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=0,dl_vlan_pcp=1 actions=output:19
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=124 actions=output:2
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=4095 actions=output:14
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=4095,dl_vlan_pcp=0 actions=output:12
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan=4095,dl_vlan_pcp=1 actions=output:13
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan_pcp=0 actions=output:21
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,dl_vlan_pcp=1 actions=output:22
+ event=DELETED reason=delete table=0 cookie=0 in_port=0,vlan_tci=0x0000/0x1fff actions=output:11
+OFPT_BARRIER_REPLY (OF1.4):
+])
+
+# Check that our own changes are reported as full updates.
+ovs-appctl -t ovs-ofctl ofctl/set-output-file monitor.log
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=1,actions=output:2
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=2,actions=output:1
+ovs-appctl -t ovs-ofctl ofctl/barrier
+ovs-appctl -t ovs-ofctl ofctl/send 050e003800000050000000000000000000000000000000000003000000000000ffffffffffffffffffffffff000000000001000400000000
+ovs-appctl -t ovs-ofctl ofctl/barrier
+AT_CHECK([ovs-ofctl -OOpenFlow14 dump-flows br0 | ofctl_strip], [0], [OFPST_FLOW reply (OF1.4):
+])
+AT_CHECK([sed 's/ (xid=0x[[1-9a-fA-F]][[0-9a-fA-F]]*)//' monitor.log | multiline_sort], [0],
+[OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=1 actions=output:2
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=2 actions=output:1
+OFPT_BARRIER_REPLY (OF1.4):
+send: OFPT_FLOW_MOD (OF1.4): DEL priority=0 actions=drop
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=DELETED reason=delete table=0 cookie=0 in_port=1 actions=output:2
+ event=DELETED reason=delete table=0 cookie=0 in_port=2 actions=output:1
+OFPT_BARRIER_REPLY (OF1.4):
+])
+
+OVS_APP_EXIT_AND_WAIT([ovs-ofctl])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto - OpenFlow14 flow monitoring with !own])
+AT_KEYWORDS([monitor])
+OVS_VSWITCHD_START
+
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=123,actions=output:1
+
+# Start a monitor watching the flow table and check the initial reply.
+ovs-ofctl -OOpenFlow14 monitor br0 watch:\!own --detach --no-chdir --pidfile >monitor.log 2>&1
+AT_CAPTURE_FILE([monitor.log])
+ovs-appctl -t ovs-ofctl ofctl/barrier
+AT_CHECK([sed 's/ (xid=0x[[1-9a-fA-F]][[0-9a-fA-F]]*)//' monitor.log], [0],
+  [OFPST_FLOW_MONITOR reply (OF1.4):
+ event=INITIAL table=0 cookie=0 in_port=0,dl_vlan=123 actions=output:1
+OFPT_BARRIER_REPLY (OF1.4):
+])
+
+# Check that our own changes are reported as abbreviations.
+ovs-appctl -t ovs-ofctl ofctl/set-output-file monitor.log
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=1,actions=output:2
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=2,actions=output:1
+ovs-appctl -t ovs-ofctl ofctl/barrier
+ovs-appctl -t ovs-ofctl ofctl/send 050e003812345678000000000000000000000000000000000003000000000000ffffffffffffffffffffffff000000000001000400000000
+ovs-appctl -t ovs-ofctl ofctl/barrier
+AT_CHECK([ovs-ofctl -OOpenFlow14 dump-flows br0 | ofctl_strip], [0], [OFPST_FLOW reply (OF1.4):
+])
+AT_CHECK([sed 's/ (xid=0x[[1-9a-fA-F]][[0-9a-fA-F]]*)//' monitor.log], [0],
+[OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=1 actions=output:2
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=2 actions=output:1
+OFPT_BARRIER_REPLY (OF1.4):
+send: OFPT_FLOW_MOD (OF1.4): DEL priority=0 actions=drop
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ABBREV xid=0x12345678
+OFPT_BARRIER_REPLY (OF1.4):
+])
+
+OVS_APP_EXIT_AND_WAIT([ovs-ofctl])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto - OpenFlow14 flow monitoring with out_port])
+AT_KEYWORDS([monitor])
+OVS_VSWITCHD_START
+
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=121,actions=output:1
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=122,actions=output:1
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=123,actions=output:2
+
+# Start a monitor watching the flow table and check the initial reply.
+ovs-ofctl -OOpenFlow14 monitor br0 watch:out_port=2 --detach --no-chdir --pidfile >monitor.log 2>&1
+AT_CAPTURE_FILE([monitor.log])
+ovs-appctl -t ovs-ofctl ofctl/barrier
+AT_CHECK([sed 's/ (xid=0x[[1-9a-fA-F]][[0-9a-fA-F]]*)//' monitor.log], [0],
+  [OFPST_FLOW_MONITOR reply (OF1.4):
+ event=INITIAL table=0 cookie=0 in_port=0,dl_vlan=123 actions=output:2
+OFPT_BARRIER_REPLY (OF1.4):
+])
+
+ovs-appctl -t ovs-ofctl ofctl/set-output-file monitor.log
+
+# Add, modify flows and check the updates.
+ovs-ofctl -OOpenFlow14 mod-flows br0 dl_vlan=121,actions=drop
+ovs-ofctl -OOpenFlow14 mod-flows br0 dl_vlan=122,actions=output:1,output:2
+ovs-appctl -t ovs-ofctl ofctl/barrier
+
+ovs-ofctl -OOpenFlow14 mod-flows br0 dl_vlan=123,actions=output:1,output:2
+ovs-appctl -t ovs-ofctl ofctl/barrier
+
+ovs-ofctl -OOpenFlow14 mod-flows br0 dl_vlan=122,actions=output:1
+ovs-appctl -t ovs-ofctl ofctl/barrier
+ovs-ofctl -OOpenFlow14 mod-flows br0 dl_vlan=123,actions=output:2
+ovs-appctl -t ovs-ofctl ofctl/barrier
+
+AT_CHECK([sed 's/ (xid=0x[[1-9a-fA-F]][[0-9a-fA-F]]*)//' monitor.log], [0],
+[OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=MODIFIED table=0 cookie=0 in_port=0,dl_vlan=122 actions=output:1,output:2
+OFPT_BARRIER_REPLY (OF1.4):
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=MODIFIED table=0 cookie=0 in_port=0,dl_vlan=123 actions=output:1,output:2
+OFPT_BARRIER_REPLY (OF1.4):
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=MODIFIED table=0 cookie=0 in_port=0,dl_vlan=122 actions=output:1
+OFPT_BARRIER_REPLY (OF1.4):
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=MODIFIED table=0 cookie=0 in_port=0,dl_vlan=123 actions=output:2
+OFPT_BARRIER_REPLY (OF1.4):
+])
+
+OVS_APP_EXIT_AND_WAIT([ovs-ofctl])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+
+AT_SETUP([ofproto - OpenFlow14 flow monitoring with out_group])
+AT_KEYWORDS([monitor])
+OVS_VSWITCHD_START
+
+AT_CHECK([ovs-ofctl -O OpenFlow14 add-group br0 group_id=1,type=all,bucket=output:1])
+AT_CHECK([ovs-ofctl -O OpenFlow14 add-group br0 group_id=2,type=all,bucket=output:2])
+
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=121,actions=output:1
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=122,actions=group:1
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=123,actions=group:2
+
+# Start a monitor watching the flow table and check the initial reply.
+ovs-ofctl -OOpenFlow14 monitor br0 watch:out_group=2 --detach --no-chdir --pidfile >monitor.log 2>&1
+AT_CAPTURE_FILE([monitor.log])
+ovs-appctl -t ovs-ofctl ofctl/barrier
+AT_CHECK([sed 's/ (xid=0x[[1-9a-fA-F]][[0-9a-fA-F]]*)//' monitor.log], [0],
+  [OFPST_FLOW_MONITOR reply (OF1.4):
+ event=INITIAL table=0 cookie=0 in_port=0,dl_vlan=123 actions=group:2
+OFPT_BARRIER_REPLY (OF1.4):
+])
+
+ovs-appctl -t ovs-ofctl ofctl/set-output-file monitor.log
+
+# Add, modify flows and check the updates.
+ovs-ofctl -OOpenFlow14 mod-flows br0 dl_vlan=121,actions=group:2
+ovs-ofctl -OOpenFlow14 mod-flows br0 dl_vlan=122,actions=group:2
+ovs-ofctl -OOpenFlow14 mod-flows br0 dl_vlan=123,actions=group:1
+ovs-appctl -t ovs-ofctl ofctl/barrier
+ovs-ofctl -OOpenFlow14 add-flow br0 in_port=0,dl_vlan=124,actions=group:2
+
+AT_CHECK([sed 's/ (xid=0x[[1-9a-fA-F]][[0-9a-fA-F]]*)//' monitor.log], [0],
+[OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=MODIFIED table=0 cookie=0 in_port=0,dl_vlan=121 actions=group:2
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=MODIFIED table=0 cookie=0 in_port=0,dl_vlan=122 actions=group:2
+OFPT_BARRIER_REPLY (OF1.4):
+OFPST_FLOW_MONITOR reply (OF1.4) (xid=0x0):
+ event=ADDED table=0 cookie=0 in_port=0,dl_vlan=124 actions=group:2
+])
+
+OVS_APP_EXIT_AND_WAIT([ovs-ofctl])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+
+AT_SETUP([ofproto - OpenFlow14 flow monitoring pause and resume])
+AT_KEYWORDS([monitor])
+
+# The maximum socket receive buffer size is important for this test, which
+# tests behavior when the receive buffer overflows.
+if test -e /proc/sys/net/core/rmem_max; then
+    # Linux
+    rmem_max=`cat /proc/sys/net/core/rmem_max`
+elif rmem_max=`sysctl -n net.inet.tcp.recvbuf_max 2>/dev/null`; then
+    : # FreeBSD, NetBSD
+else
+    # Don't know how to get maximum socket receive buffer on this OS
+    AT_SKIP_IF([:])
+fi
+# Calculate the total amount of queuing: rmem_max in the kernel, 128 kB
+# in ofproto sending userspace (see ofmonitor_flush() in connmgr.c).
+queue_size=`expr $rmem_max + 128 \* 1024`
+echo rmem_max=$rmem_max queue_size=$queue_size
+
+# If there's too much queuing skip the test to avoid timing out.
+AT_SKIP_IF([test $rmem_max -gt 1048576])
+
+# Each flow update message takes up at least 48 bytes of space in queues
+# and in practice more than that.
+n_msgs=`expr $queue_size / 48`
+echo n_msgs=$n_msgs
+
+OVS_VSWITCHD_START
+
+# Start a monitor watching the flow table, then make it block.
+on_exit 'kill `cat ovs-ofctl.pid`'
+ovs-ofctl -OOpenFlow14 monitor br0 watch: --detach --no-chdir --pidfile >monitor.log 2>&1
+AT_CAPTURE_FILE([monitor.log])
+ovs-appctl -t ovs-ofctl ofctl/block
+
+# Add $n_msgs flows.
+(echo "in_port=2,cookie=2,actions=output:2"
+$PYTHON -c '
+for i in range('$n_msgs'):
+    print("cookie=1,reg1=%d,actions=drop" % i)
+') > flows.txt
+AT_CHECK([ovs-ofctl -OOpenFlow14 add-flows br0 flows.txt])
+# Check that multipart flow dumps work properly:
+AT_CHECK([ovs-ofctl -OOpenFlow14 diff-flows br0 flows.txt])
+AT_CHECK([ovs-ofctl -OOpenFlow14 add-flow br0 in_port=1,cookie=3,actions=drop])
+AT_CHECK([ovs-ofctl -OOpenFlow14 mod-flows br0 in_port=2,actions=drop])
+AT_CHECK([ovs-ofctl -OOpenFlow14 del-flows br0 cookie=1/-1])
+
+ovs-appctl -t ovs-ofctl ofctl/unblock
+
+# Wait for the connection resumed.
+# A barrier doesn't work for this purpose.
+#    https://www.mail-archive.com/dev@openvswitch.org/msg27013.html
+#    https://www.mail-archive.com/dev@openvswitch.org/msg27675.html
+OVS_WAIT_UNTIL([grep event=RESUMED monitor.log])
+
+OVS_APP_EXIT_AND_WAIT([ovs-ofctl])
+
+# Check that the flow monitor reported the same number of flows
+# added and deleted, but fewer than we actually added and deleted.
+adds=`grep -c 'ADDED.*reg1=' monitor.log`
+deletes=`grep -c 'DELETED.*reg1=' monitor.log`
+echo adds=$adds deletes=$deletes
+AT_CHECK([test $adds -gt 100 && test $adds -lt $n_msgs])
+AT_CHECK([test $adds = $deletes])
+
+# Check that the flow monitor reported everything in the expected order:
+#
+#     event=ADDED table=0 cookie=0x1 reg1=0x22
+# ...
+#     event=PAUSED:
+# ...
+#     event=DELETED reason=delete table=0 cookie=0x1 reg1=0x22
+# ...
+#     event=ADDED table=0 cookie=0x3 in_port=1
+#     event=MODIFIED table=0 cookie=0x2 in_port=2 actions=output:2
+#     event=RESUMED:
+#
+# except that, between the PAUSED and RESUMED, the order of the ADDED
+# and MODIFIED lines lines depends on hash order, that is, it varies
+# as we change the hash function or change architecture.  Therefore,
+# we use a couple of tests below to accept both orders.
+AT_CHECK([ofctl_strip < monitor.log | sed -n -e '
+/reg1=0x22$/p
+/cookie=0x[[23]]/p
+/event=PAUSED/p
+/event=RESUMED/p
+' > monitor.log.subset])
+AT_CHECK([grep -v MODIFIED monitor.log.subset], [0], [dnl
+ event=ADDED table=0 cookie=0x2 in_port=2 actions=output:2
+ event=ADDED table=0 cookie=0x1 reg1=0x22
+ event=PAUSED
+ event=DELETED reason=delete table=0 cookie=0x1 reg1=0x22
+ event=ADDED table=0 cookie=0x3 in_port=1
+ event=RESUMED
+])
+AT_CHECK([grep -v ADDED monitor.log.subset], [0], [dnl
+ event=PAUSED
+ event=DELETED reason=delete table=0 cookie=0x1 reg1=0x22
+ event=MODIFIED table=0 cookie=0x2 in_port=2
+ event=RESUMED
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+
 AT_SETUP([ofproto - event filtering (OpenFlow 1.3)])
 AT_KEYWORDS([monitor])
 OVS_VSWITCHD_START
@@ -5213,6 +5591,9 @@ OFPT_BARRIER_REPLY (OF1.3):
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+
+
+
 AT_SETUP([ofproto - ofport_request])
 OVS_VSWITCHD_START
 add_of_ports br0 1 2 3
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index c7b1565..e466486 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -625,6 +625,9 @@ monitored.
 If set, only flows that output to \fIport\fR are monitored.  The
 \fIport\fR may be an OpenFlow port number or keyword
 (e.g. \fBLOCAL\fR).
+.IP "\fBout_group=\fIgroup\fR"
+If set, only flows that output to \fIgroup\fR number are monitored.
+This field requires OpenFlow 1.4 (-OOpenFlow14) or later.
 .IP "\fIfield\fB=\fIvalue\fR"
 Monitors only flows that have \fIfield\fR specified as the given
 \fIvalue\fR.  Any syntax valid for matching on \fBdump\-flows\fR may
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index aab5a5c..78009bf 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -2285,9 +2285,26 @@ ofctl_monitor(struct ovs_cmdl_context *ctx)
             if (error) {
                 ovs_fatal(0, "%s", error);
             }
+            /* out_group is limited to OpenFlow1.4+ */
+            if (fmr.out_group != OFPG_ANY) {
+                uint32_t usable_versions = ((1u << OFP14_VERSION) |
+                                            (1u << OFP15_VERSION) |
+                                            (1u << OFP16_VERSION));
+                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,
+                                                        usable_versions);
+                    ovs_fatal(0, "watch:out_group requires one of the OpenFlow"
+                              " versions %s but none is enabled (use -O)",
+                              ds_cstr(&versions));
+                }
+                mask_allowed_ofp_versions(usable_versions);
+            }
 
             msg = ofpbuf_new(0);
-            ofputil_append_flow_monitor_request(&fmr, msg);
+            ofputil_append_flow_monitor_request(&fmr, msg,
+                                                vconn_get_version(vconn));
             dump_transaction(vconn, msg);
             fflush(stdout);
         } else if (!strcmp(arg, "resume")) {
-- 
2.7.4



More information about the dev mailing list