diff mbox series

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

Message ID 1543429807-14731-1-git-send-email-ashishvarma.ovs@gmail.com
State Superseded
Headers show
Series [ovs-dev,v1] ofp-monitor: Added support for OpenFlow 1.4+ Flow Monitor | expand

Commit Message

Ashish Varma Nov. 28, 2018, 6:30 p.m. UTC
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@gmail.com>
---
 include/openflow/openflow-1.4.h   |  79 ++++++
 include/openvswitch/ofp-monitor.h |  73 ++++-
 lib/ofp-monitor.c                 | 553 ++++++++++++++++++++++++++++----------
 lib/ofp-print.c                   |  10 +-
 ofproto/connmgr.c                 | 100 ++++---
 ofproto/connmgr.h                 |  11 +-
 ofproto/ofproto-provider.h        |   7 +-
 ofproto/ofproto.c                 |  95 ++++---
 tests/ofproto.at                  | 381 ++++++++++++++++++++++++++
 utilities/ovs-ofctl.8.in          |   3 +
 utilities/ovs-ofctl.c             |  20 +-
 11 files changed, 1099 insertions(+), 233 deletions(-)

Comments

Ben Pfaff Dec. 10, 2018, 9:54 p.m. UTC | #1
On Wed, Nov 28, 2018 at 10:30:07AM -0800, Ashish Varma wrote:
> 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@gmail.com>

Thanks a lot for working on this.  The OF1.4+ flow monitor support was
based on the OVS design for the same feature, so it's a shame that we
didn't implement it early on.

In openflow-1.4.h, please align the comments on members of structs and
enumerations to some common column, as is the usual practice in OVS
code, e.g. instead of this:

    /* struct ofp14_flow_update_full. */
    OFPFME_INITIAL = 0, /* Flow present when flow monitor created. */
    OFPFME_ADDED = 1, /* Flow was added. */
    OFPFME_REMOVED = 2, /* Flow was removed. */
    OFPFME_MODIFIED = 3, /* Flow instructions were changed. */
    /* struct ofp14_flow_update_abbrev. */
    OFPFME_ABBREV = 4, /* Abbreviated reply. */
    /* struct ofp14_flow_update_header. */
    OFPFME_PAUSED = 5, /* Monitoring paused (out of buffer space). */
    OFPFME_RESUMED = 6, /* Monitoring resumed. */

more like this (also adding some blank lines for clarity):

    /* struct ofp14_flow_update_full. */
    OFPFME_INITIAL = 0,         /* Flow present when flow monitor created. */
    OFPFME_ADDED = 1,           /* Flow was added. */
    OFPFME_REMOVED = 2,         /* Flow was removed. */
    OFPFME_MODIFIED = 3,        /* Flow instructions were changed. */

    /* struct ofp14_flow_update_abbrev. */
    OFPFME_ABBREV = 4,          /* Abbreviated reply. */

    /* struct ofp14_flow_update_header. */
    OFPFME_PAUSED = 5,          /* Monitoring paused (out of buffer space). */
    OFPFME_RESUMED = 6,         /* Monitoring resumed. */

Also please use the "14" infix for these enumeration members,
e.g. OFPFME14_INITIAL.

The OFPUTIL_FMF_* flags aren't really abstracted from NXFMF_* and
OFPFMF14_*; they're just a copy of the OFPFMF14_* ones with the names
changed.  I think it might be better to just use OFPFMF14_* with a note
that NXFMF_* doesn't have NXFMF_ONLY_OWN.

It's usually unnecessary to use a union of an ofp_port_t and an
ofp11_port_t, because OVS can usually reject unsupported ports when it
parses them from OpenFlow using ofputil_port_from_ofp11().  Maybe there
is some exception here but that's not obvious to me yet.

I don't see how a piece of code that looks at an
ofputil_flow_monitor_request can tell whether to look at
out_port.ofp_port or out_port.ofp11_port.

Our usual style is to start a comment with a capital letter and end it
with a period:

        /* there is one to one mapping between ofp14_flow_update_event and
         * ofputil_flow_update_event */

The name ofputil_get_util_flow_update_event_from_nx_event() is pretty
long and I don't know what the _util_ part of it is there for.

In new code, please try to declare (and initialize) variables at their
first use, when possible, rather than the older style we used of
declaring variables at the top of a block.

I think that ofputil_start_flow_update() should always use
NXST_FLOW_MONITOR_REPLY for OpenFlow 1.0, so it seems to me that it
should check for OFPUTIL_P_OF10_ANY rather than OFPUTIL_P_OF10_STD_ANY.
(Or it could accept an OpenFlow version instead of a protocol.)

I don't understand why ofputil_append_flow_update() checks for
OFPUTIL_P_OF10_STD in particular.  It seems like any OF1.0 protocol
would make sense.  (Or it could accept an OpenFlow version instead of a
protocol.)

In general I'm not sure of the rationale here for how OpenFlow versions
are handled.  Currently, it looks like OVS supports flow monitoring in
OF1.0 only.  The OpenFlow specification defines flow monitoring for
OF1.4 and later and for OF1.3 as an "official extension" in extensions
pack 2 (which you can find at
https://www.opennetworking.org/wp-content/uploads/2014/10/openflow-extensions-1.3.x-pack2.zip).
So we should use the official specs for 1.3 and later.  For 1.1 and 1.2,
we have a choice of supporting our extension or the OpenFlow version.  I
don't have a particular preference.

Thanks!  I'll look forward to the next revision.
Ashish Varma Dec. 11, 2018, 12:51 a.m. UTC | #2
Thanks for the feedback. Would revert back shortly with replies and few
questions followed by the v2 patch.

On Mon, Dec 10, 2018 at 1:54 PM Ben Pfaff <blp@ovn.org> wrote:

> On Wed, Nov 28, 2018 at 10:30:07AM -0800, Ashish Varma wrote:
> > 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@gmail.com>
>
> Thanks a lot for working on this.  The OF1.4+ flow monitor support was
> based on the OVS design for the same feature, so it's a shame that we
> didn't implement it early on.
>
> In openflow-1.4.h, please align the comments on members of structs and
> enumerations to some common column, as is the usual practice in OVS
> code, e.g. instead of this:
>
>     /* struct ofp14_flow_update_full. */
>     OFPFME_INITIAL = 0, /* Flow present when flow monitor created. */
>     OFPFME_ADDED = 1, /* Flow was added. */
>     OFPFME_REMOVED = 2, /* Flow was removed. */
>     OFPFME_MODIFIED = 3, /* Flow instructions were changed. */
>     /* struct ofp14_flow_update_abbrev. */
>     OFPFME_ABBREV = 4, /* Abbreviated reply. */
>     /* struct ofp14_flow_update_header. */
>     OFPFME_PAUSED = 5, /* Monitoring paused (out of buffer space). */
>     OFPFME_RESUMED = 6, /* Monitoring resumed. */
>
> more like this (also adding some blank lines for clarity):
>
>     /* struct ofp14_flow_update_full. */
>     OFPFME_INITIAL = 0,         /* Flow present when flow monitor created.
> */
>     OFPFME_ADDED = 1,           /* Flow was added. */
>     OFPFME_REMOVED = 2,         /* Flow was removed. */
>     OFPFME_MODIFIED = 3,        /* Flow instructions were changed. */
>
>     /* struct ofp14_flow_update_abbrev. */
>     OFPFME_ABBREV = 4,          /* Abbreviated reply. */
>
>     /* struct ofp14_flow_update_header. */
>     OFPFME_PAUSED = 5,          /* Monitoring paused (out of buffer
> space). */
>     OFPFME_RESUMED = 6,         /* Monitoring resumed. */
>
> Also please use the "14" infix for these enumeration members,
> e.g. OFPFME14_INITIAL.
>
> The OFPUTIL_FMF_* flags aren't really abstracted from NXFMF_* and
> OFPFMF14_*; they're just a copy of the OFPFMF14_* ones with the names
> changed.  I think it might be better to just use OFPFMF14_* with a note
> that NXFMF_* doesn't have NXFMF_ONLY_OWN.
>
> It's usually unnecessary to use a union of an ofp_port_t and an
> ofp11_port_t, because OVS can usually reject unsupported ports when it
> parses them from OpenFlow using ofputil_port_from_ofp11().  Maybe there
> is some exception here but that's not obvious to me yet.
>
> I don't see how a piece of code that looks at an
> ofputil_flow_monitor_request can tell whether to look at
> out_port.ofp_port or out_port.ofp11_port.
>
> Our usual style is to start a comment with a capital letter and end it
> with a period:
>
>         /* there is one to one mapping between ofp14_flow_update_event and
>          * ofputil_flow_update_event */
>
> The name ofputil_get_util_flow_update_event_from_nx_event() is pretty
> long and I don't know what the _util_ part of it is there for.
>
> In new code, please try to declare (and initialize) variables at their
> first use, when possible, rather than the older style we used of
> declaring variables at the top of a block.
>
> I think that ofputil_start_flow_update() should always use
> NXST_FLOW_MONITOR_REPLY for OpenFlow 1.0, so it seems to me that it
> should check for OFPUTIL_P_OF10_ANY rather than OFPUTIL_P_OF10_STD_ANY.
> (Or it could accept an OpenFlow version instead of a protocol.)
>
> I don't understand why ofputil_append_flow_update() checks for
> OFPUTIL_P_OF10_STD in particular.  It seems like any OF1.0 protocol
> would make sense.  (Or it could accept an OpenFlow version instead of a
> protocol.)
>
> In general I'm not sure of the rationale here for how OpenFlow versions
> are handled.  Currently, it looks like OVS supports flow monitoring in
> OF1.0 only.  The OpenFlow specification defines flow monitoring for
> OF1.4 and later and for OF1.3 as an "official extension" in extensions
> pack 2 (which you can find at
>
> https://www.opennetworking.org/wp-content/uploads/2014/10/openflow-extensions-1.3.x-pack2.zip
> ).
> So we should use the official specs for 1.3 and later.  For 1.1 and 1.2,
> we have a choice of supporting our extension or the OpenFlow version.  I
> don't have a particular preference.
>
> Thanks!  I'll look forward to the next revision.
>
diff mbox series

Patch

diff --git a/include/openflow/openflow-1.4.h b/include/openflow/openflow-1.4.h
index 9399950..295decb 100644
--- a/include/openflow/openflow-1.4.h
+++ b/include/openflow/openflow-1.4.h
@@ -381,4 +381,83 @@  enum ofp14_flow_monitor_flags {
                                      */
 };
 
+/* 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. */
+    OFPFME_INITIAL = 0, /* Flow present when flow monitor created. */
+    OFPFME_ADDED = 1, /* Flow was added. */
+    OFPFME_REMOVED = 2, /* Flow was removed. */
+    OFPFME_MODIFIED = 3, /* Flow instructions were changed. */
+    /* struct ofp14_flow_update_abbrev. */
+    OFPFME_ABBREV = 4, /* Abbreviated reply. */
+    /* struct ofp14_flow_update_header. */
+    OFPFME_PAUSED = 5, /* Monitoring paused (out of buffer space). */
+    OFPFME_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..1d4ec50 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,51 @@  void ofputil_flow_removed_format(struct ds *,
                                  const struct ofputil_port_map *,
                                  const struct ofputil_table_map *);
 
+/* Abstracted flow monitor request flags
+ * There are some differences between the flags present in
+ * nx_flow_monitor_flags and ofp_flow_monitor_flags, but semantically it is
+ * same till OFPUTIL_FMF_NO_ABBREV (i.e. nx_flow_monitor_flags is a subset of
+ * ofp_flow_monitor_flags) */
+enum ofputil_flow_monitor_flags {
+    /* When to send updates. */
+    OFPUTIL_FMF_INITIAL = 1 << 0, /* Initially matching flows. */
+    OFPUTIL_FMF_ADD = 1 << 1,     /* New matching flows as they are added. */
+    OFPUTIL_FMF_REMOVED = 1 << 2, /* Old matching flows as they are removed. */
+    OFPUTIL_FMF_MODIFY = 1 << 3,  /* Matching flows as they are changed. */
+
+    /* What to include in updates. */
+    OFPUTIL_FMF_INSTRUCTIONS = 1 << 4, /* If set, instructions/actions are
+                                          included. */
+    OFPUTIL_FMF_NO_ABBREV = 1 << 5,    /* If set, include own changes in full.
+                                          This is NXFMF_OWN in NX version */
+    OFPUTIL_FMF_ONLY_OWN = 1 << 6,     /* If set, don’t include other
+                                          controllers. Only in OpenFlow 1.4 +*/
+};
+
+
+union flow_monitor_out_port {
+    ofp_port_t    ofp_port;
+    ofp11_port_t  ofp11_port;
+};
+
 /* Abstract nx_flow_monitor_request. */
 struct ofputil_flow_monitor_request {
     uint32_t id;
-    enum nx_flow_monitor_flags flags;
-    ofp_port_t out_port;
+    enum ofputil_flow_monitor_flags flags;
+    union flow_monitor_out_port 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 +115,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 +146,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..0e143a4 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,156 @@  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;
+    enum ofperr error;
 
-    if (!msg->header) {
-        ofpraw_pull_assert(msg);
+    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;
+
+        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;
+        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.ofp_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;
+
+        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;
+
+        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;
+        }
 
-    rq->id = ntohl(nfmr->id);
-    rq->flags = flags;
-    rq->out_port = u16_to_ofp(ntohs(nfmr->out_port));
-    rq->table_id = nfmr->table_id;
+        flags = ntohs(ofmr->flags);
+        if (!(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;
+        }
+
+        if (ofmr->command > OFPFMC14_DELETE) {
+            VLOG_WARN_RL(&rl, "OFPST14_FLOW_MONITOR request has bad command"
+                         " %"PRIu32" ", ofmr->command);
+            return OFPERR_OFPMOFC_BAD_COMMAND;
+        }
 
-    return nx_pull_match(msg, ntohs(nfmr->match_len), &rq->match, NULL,
-                         NULL, false, NULL, NULL);
+        rq->id = ntohl(ofmr->monitor_id);
+        rq->flags = flags;
+        rq->out_port.ofp11_port = u32_to_ofp11(ntohl(ofmr->out_port));
+        rq->out_group = ntohl(ofmr->out_group);
+        rq->table_id = ofmr->table_id;
+        rq->command = ofmr->command;
+
+        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.ofp_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 = htonl(ofp11_to_u32(rq->out_port.ofp11_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 ofputil_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 OFPUTIL_FMF_INITIAL: return "initial";
+    case OFPUTIL_FMF_ADD: return "add";
+    case OFPUTIL_FMF_REMOVED: return "delete";
+    case OFPUTIL_FMF_MODIFY: return "modify";
+    case OFPUTIL_FMF_INSTRUCTIONS: return "actions";
+    case OFPUTIL_FMF_NO_ABBREV: return "own";
+    case OFPUTIL_FMF_ONLY_OWN: return "only-own";
     }
 
     return NULL;
@@ -434,11 +508,11 @@  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) {
+    if (request->out_port.ofp_port != OFPP_NONE) {
         ds_put_cstr(s, " out_port=");
-        ofputil_format_port(request->out_port, port_map, s);
+        ofputil_format_port(request->out_port.ofp_port, port_map, s);
     }
 
     if (request->table_id != 0xff) {
@@ -464,9 +538,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->out_port = OFPP_NONE;
+    fmr->flags = (OFPUTIL_FMF_INITIAL | OFPUTIL_FMF_ADD | OFPUTIL_FMF_REMOVED
+                  | OFPUTIL_FMF_MODIFY | OFPUTIL_FMF_NO_ABBREV
+                  | OFPUTIL_FMF_INSTRUCTIONS);
+    fmr->out_port.ofp_port = OFPP_NONE;
+    fmr->out_group = OFPG_ANY;
     fmr->table_id = 0xff;
     match_init_catchall(&fmr->match);
 
@@ -475,17 +551,17 @@  parse_flow_monitor_request__(struct ofputil_flow_monitor_request *fmr,
         char *error = NULL;
 
         if (!strcmp(name, "!initial")) {
-            fmr->flags &= ~NXFMF_INITIAL;
+            fmr->flags &= ~OFPUTIL_FMF_INITIAL;
         } else if (!strcmp(name, "!add")) {
-            fmr->flags &= ~NXFMF_ADD;
+            fmr->flags &= ~OFPUTIL_FMF_ADD;
         } else if (!strcmp(name, "!delete")) {
-            fmr->flags &= ~NXFMF_DELETE;
+            fmr->flags &= ~OFPUTIL_FMF_REMOVED;
         } else if (!strcmp(name, "!modify")) {
-            fmr->flags &= ~NXFMF_MODIFY;
+            fmr->flags &= ~OFPUTIL_FMF_MODIFY;
         } else if (!strcmp(name, "!actions")) {
-            fmr->flags &= ~NXFMF_ACTIONS;
+            fmr->flags &= ~OFPUTIL_FMF_INSTRUCTIONS;
         } else if (!strcmp(name, "!own")) {
-            fmr->flags &= ~NXFMF_OWN;
+            fmr->flags &= ~OFPUTIL_FMF_NO_ABBREV;
         } else if (ofp_parse_protocol(name, &p)) {
             match_set_dl_type(&fmr->match, htons(p->dl_type));
             if (p->nw_proto) {
@@ -505,7 +581,9 @@  parse_flow_monitor_request__(struct ofputil_flow_monitor_request *fmr,
                     error = xasprintf("unknown table \"%s\"", value);
                 }
             } else if (!strcmp(name, "out_port")) {
-                fmr->out_port = u16_to_ofp(atoi(value));
+                fmr->out_port.ofp_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 +615,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_util_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 +651,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 +663,171 @@  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 (oh->version == OFP10_VERSION) {
+        struct nx_flow_update_header *nfuh;
 
-    if (update->event == NXFME_ABBREV) {
-        struct nx_flow_update_abbrev *nfua;
+        if (msg->size < sizeof(struct nx_flow_update_header)) {
+            goto bad_len;
+        }
 
-        if (length != sizeof *nfua) {
+        nfuh = msg->data;
+        update->event =
+          ofputil_get_util_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) {
+            struct nx_flow_update_abbrev *nfua;
+
+            if (length != sizeof *nfua) {
+                goto bad_len;
+            }
+
+            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) {
+            struct nx_flow_update_full *nfuf;
+            unsigned int actions_len;
+            unsigned int match_len;
+            enum ofperr error;
+
+            if (length < sizeof *nfuf) {
+                goto bad_len;
+            }
+
+            nfuf = ofpbuf_pull(msg, sizeof *nfuf);
+            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);
+
+            error = nx_pull_match(msg, match_len, &update->match, NULL, NULL,
+                                  false, NULL, NULL);
+            if (error) {
+                return error;
+            }
 
-        if (length < sizeof *nfuf) {
+            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) {
+            struct ofp14_flow_update_full *ofuf;
+            enum ofperr error;
+
+            if (length < sizeof *ofuf) {
+                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;
+            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);
+
+            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) {
+            struct ofp14_flow_update_paused *ofup;
+
+            if (length != sizeof *ofup) {
+                goto bad_len;
+            }
+            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 +852,49 @@  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_STD_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_get_nx_flow_update_event_from_util_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,32 +904,68 @@  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_STD) {
+        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 {
+            struct nx_flow_update_full *nfuf;
+            int match_len;
+
+            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;
+        }
+
+        nfuh = ofpbuf_at_assert(msg, start_ofs, sizeof *nfuh);
+        nfuh->length = htons(msg->size - start_ofs);
+        nfuh->event =
+        htons(ofputil_get_nx_flow_update_event_from_util_event(update->event));
     } else {
-        struct nx_flow_update_full *nfuf;
-        int match_len;
+        struct ofp14_flow_update_header *ofuh;
+        if (update->event == OFPUTIL_FME_ABBREV) {
+            struct ofp14_flow_update_abbrev *ofua;
+
+            ofua = ofpbuf_put_zeros(msg, sizeof *ofua);
+            ofua->xid = update->xid;
+        } else {
+            struct ofp14_flow_update_full *ofuf;
+
+            ofpbuf_put_zeros(msg, sizeof *ofuf);
+            oxm_put_match(msg, &update->match, version);
+            ofpacts_put_openflow_instructions(update->ofpacts,
+                                              update->ofpacts_len,
+                                              msg, version);
+            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;
+        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 +981,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 789b40a..b5e3b9b 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -745,7 +745,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)
@@ -755,7 +755,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;
         }
@@ -766,7 +766,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)
@@ -1151,11 +1151,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 7a7790c..7c0f16b 100644
--- a/ofproto/connmgr.c
+++ b/ofproto/connmgr.c
@@ -124,11 +124,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);
 
@@ -2067,7 +2068,8 @@  ofmonitor_create(const struct ofputil_flow_monitor_request *request,
     hmap_insert(&ofconn->monitors, &m->ofconn_node, hash_int(request->id, 0));
     m->id = request->id;
     m->flags = request->flags;
-    m->out_port = request->out_port;
+    m->out_port = request->out_port.ofp_port;
+    m->out_group = request->out_group;
     m->table_id = request->table_id;
     minimatch_init(&m->match, &request->match);
 
@@ -2103,7 +2105,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)
@@ -2113,39 +2115,42 @@  ofmonitor_report(struct connmgr *mgr, struct rule *rule,
         return;
     }
 
-    enum nx_flow_monitor_flags update;
+    enum ofputil_flow_monitor_flags update;
     switch (event) {
-    case NXFME_ADDED:
-        update = NXFMF_ADD;
+    case OFPUTIL_FME_ADDED:
+        update = OFPUTIL_FMF_ADD;
         rule->add_seqno = rule->modify_seqno = monitor_seqno++;
         break;
 
-    case NXFME_DELETED:
-        update = NXFMF_DELETE;
+    case OFPUTIL_FME_REMOVED:
+        update = OFPUTIL_FMF_REMOVED;
         break;
 
-    case NXFME_MODIFIED:
-        update = NXFMF_MODIFY;
+    case OFPUTIL_FME_MODIFIED:
+        update = OFPUTIL_FMF_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 ofputil_flow_monitor_flags flags = 0;
         struct ofmonitor *m;
         HMAP_FOR_EACH (m, ofconn_node, &ofconn->monitors) {
             if (m->flags & update
@@ -2155,23 +2160,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 & OFPUTIL_FMF_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 & OFPUTIL_FMF_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);
@@ -2182,7 +2194,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 & OFPUTIL_FMF_INSTRUCTIONS) {
                     const struct rule_actions *actions
                         = rule_get_actions(rule);
                     fu.ofpacts = actions->ofpacts;
@@ -2192,14 +2204,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;
             }
@@ -2224,9 +2238,20 @@  ofmonitor_flush(struct connmgr *mgr)
         if (!ofconn->monitor_paused
             && rconn_packet_counter_n_bytes(counter) > 128 * 1024) {
             COVERAGE_INC(ofmonitor_pause);
+            struct ofpbuf *pause;
             ofconn->monitor_paused = monitor_seqno++;
-            struct ofpbuf *pause = ofpraw_alloc_xid(
-                OFPRAW_NXT_FLOW_MONITOR_PAUSED, OFP10_VERSION, htonl(0), 0);
+            if (ofconn->protocol == OFPUTIL_P_OF10_STD) {
+                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(OFPFME_PAUSED);
+            }
             ofconn_send(ofconn, pause, counter);
         }
     }
@@ -2237,18 +2262,31 @@  ofmonitor_resume(struct ofconn *ofconn)
     OVS_REQUIRES(ofproto_mutex)
 {
     struct rule_collection rules;
-    rule_collection_init(&rules);
-
+    struct ofpbuf *resumed;
     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));
+    if (ofconn->protocol == OFPUTIL_P_OF10_STD) {
+        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 11c8f9a..1af2cec 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 ofputil_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 4fd8cb1..a123cc6 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"
@@ -418,7 +419,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 ofputil_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);
 
@@ -479,6 +481,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 dfcbc54..c5bf58e 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -3083,7 +3083,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)
 {
@@ -4910,7 +4910,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);
 
@@ -5311,8 +5311,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);
@@ -5322,7 +5322,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,
@@ -5331,7 +5331,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);
@@ -5608,7 +5608,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);
 
@@ -6012,17 +6013,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 ofputil_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_STD) {
+        fu.event = (flags & (OFPUTIL_FMF_INITIAL | OFPUTIL_FMF_ADD)
+                    ? OFPUTIL_FME_ADDED : OFPUTIL_FME_MODIFIED);
+    } else {
+        fu.event = flags & OFPUTIL_FMF_INITIAL ? OFPUTIL_FME_INITIAL :
+                   flags & OFPUTIL_FMF_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;
@@ -6032,29 +6041,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 & OFPUTIL_FMF_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 ofputil_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);
     }
 }
 
@@ -6064,7 +6074,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 ofputil_flow_monitor_flags update;
 
     if (rule_is_hidden(rule)) {
         return;
@@ -6074,11 +6084,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 = OFPUTIL_FMF_ADD | OFPUTIL_FMF_MODIFY;
         } else if (rule->modify_seqno > seqno) {
-            update = NXFMF_MODIFY;
+            update = OFPUTIL_FMF_MODIFY;
         } else {
             return;
         }
@@ -6087,13 +6101,13 @@  ofproto_collect_ofmonitor_refresh_rule(const struct ofmonitor *m,
             return;
         }
     } else {
-        update = NXFMF_INITIAL;
+        update = OFPUTIL_FMF_INITIAL;
     }
 
     if (!rule->monitor_flags) {
         rule_collection_add(rules, rule);
     }
-    rule->monitor_flags |= update | (m->flags & NXFMF_ACTIONS);
+    rule->monitor_flags |= update | (m->flags & OFPUTIL_FMF_INSTRUCTIONS);
 }
 
 static void
@@ -6122,7 +6136,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 & OFPUTIL_FMF_INITIAL) {
         ofproto_collect_ofmonitor_refresh_rules(m, 0, rules);
     }
 }
@@ -6173,7 +6187,10 @@  handle_flow_monitor_request(struct ofconn *ofconn, const struct ofp_header *oh)
         struct ofmonitor *m;
         int retval;
 
-        retval = ofputil_decode_flow_monitor_request(&request, &b);
+        retval =
+             ofputil_decode_flow_monitor_request(&request, &b,
+                                                 ofproto_get_tun_tab(ofproto),
+                                                 &ofproto->vl_mff_map);
         if (retval == EOF) {
             break;
         } else if (retval) {
@@ -6187,16 +6204,31 @@  handle_flow_monitor_request(struct ofconn *ofconn, const struct ofp_header *oh)
             goto error;
         }
 
-        error = ofmonitor_create(&request, ofconn, &m);
+        if (request.command == OFPFMC14_ADD) {
+            error = ofmonitor_create(&request, ofconn, &m);
+        }
+        else if (request.command == OFPFMC14_DELETE) {
+            error = flow_monitor_delete(ofconn, request.id);
+        }
+        else if (request.command == OFPFMC14_MODIFY) {
+            error = flow_monitor_delete(ofconn, request.id);
+
+            if (!error) {
+                error = ofmonitor_create(&request, ofconn, &m);
+            }
+        }
+
         if (error) {
             goto error;
         }
 
-        if (n_monitors >= allocated_monitors) {
-            monitors = x2nrealloc(monitors, &allocated_monitors,
-                                  sizeof *monitors);
+        if (request.command != OFPFMC14_DELETE) {
+            if (n_monitors >= allocated_monitors) {
+                monitors = x2nrealloc(monitors, &allocated_monitors,
+                                      sizeof *monitors);
+            }
+            monitors[n_monitors++] = m;
         }
-        monitors[n_monitors++] = m;
     }
 
     struct rule_collection rules;
@@ -6207,7 +6239,8 @@  handle_flow_monitor_request(struct ofconn *ofconn, const struct ofp_header *oh)
 
     struct ovs_list replies;
     ofpmp_init(&replies, oh);
-    ofmonitor_compose_refresh_updates(&rules, &replies);
+    ofmonitor_compose_refresh_updates(&rules, &replies,
+                               ofputil_protocol_from_ofp_version(oh->version));
     ovs_mutex_unlock(&ofproto_mutex);
 
     rule_collection_destroy(&rules);
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 6a2cf27..86c8f82 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -5069,6 +5069,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
@@ -5109,6 +5487,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 ea55008..c0cf454 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -613,6 +613,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 8a713e3..877223f 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -2257,6 +2257,23 @@  ofctl_monitor(struct ovs_cmdl_context *ctx)
             mask_allowed_ofp_versions(usable_versions);
             break;
         }
+        /* out_group is limited to OpenFlow1.4+ */
+        if (!strncmp(ctx->argv[i], "watch:out_group", 15)) {
+            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);
+            break;
+        }
     }
 
     open_vconn(ctx->argv[1], &vconn);
@@ -2286,7 +2303,8 @@  ofctl_monitor(struct ovs_cmdl_context *ctx)
             }
 
             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")) {