ofproto: New feature to notify controllers of flow table changes.
[openvswitch] / lib / ofp-print.c
index b1f46df83fb7cacbeb15ffcfbc5a8ba4e079c4cc..48b8daa964570c52e3a0c3e894c1bdb9e727def7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -34,7 +34,9 @@
 #include "learn.h"
 #include "multipath.h"
 #include "meta-flow.h"
+#include "netdev.h"
 #include "nx-match.h"
+#include "ofp-actions.h"
 #include "ofp-errors.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
@@ -112,6 +114,13 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
         }
     }
 
+    if (pin.fmd.metadata_mask) {
+        ds_put_format(string, " metadata=0x%"PRIx64, ntohll(pin.fmd.metadata));
+        if (pin.fmd.metadata_mask != htonll(UINT64_MAX)) {
+            ds_put_format(string, "/0x%"PRIx64, ntohll(pin.fmd.metadata_mask));
+        }
+    }
+
     for (i = 0; i < FLOW_N_REGS; i++) {
         if (pin.fmd.reg_masks[i]) {
             ds_put_format(string, " reg%d=0x%"PRIx32, i, pin.fmd.regs[i]);
@@ -145,276 +154,18 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
     }
 }
 
-static void
-print_note(struct ds *string, const struct nx_action_note *nan)
-{
-    size_t len;
-    size_t i;
-
-    ds_put_cstr(string, "note:");
-    len = ntohs(nan->len) - offsetof(struct nx_action_note, note);
-    for (i = 0; i < len; i++) {
-        if (i) {
-            ds_put_char(string, '.');
-        }
-        ds_put_format(string, "%02"PRIx8, nan->note[i]);
-    }
-}
-
-static void
-ofp_print_action(struct ds *s, const union ofp_action *a,
-                 enum ofputil_action_code code)
-{
-    const struct ofp_action_enqueue *oae;
-    const struct ofp_action_dl_addr *oada;
-    const struct nx_action_set_tunnel64 *nast64;
-    const struct nx_action_set_tunnel *nast;
-    const struct nx_action_set_queue *nasq;
-    const struct nx_action_resubmit *nar;
-    const struct nx_action_reg_move *move;
-    const struct nx_action_reg_load *load;
-    const struct nx_action_multipath *nam;
-    const struct nx_action_autopath *naa;
-    const struct nx_action_output_reg *naor;
-    const struct nx_action_fin_timeout *naft;
-    const struct nx_action_controller *nac;
-    struct mf_subfield subfield;
-    uint16_t port;
-
-    switch (code) {
-    case OFPUTIL_OFPAT_OUTPUT:
-        port = ntohs(a->output.port);
-        if (port < OFPP_MAX) {
-            ds_put_format(s, "output:%"PRIu16, port);
-        } else {
-            ofputil_format_port(port, s);
-            if (port == OFPP_CONTROLLER) {
-                if (a->output.max_len != htons(0)) {
-                    ds_put_format(s, ":%"PRIu16, ntohs(a->output.max_len));
-                } else {
-                    ds_put_cstr(s, ":all");
-                }
-            }
-        }
-        break;
-
-    case OFPUTIL_OFPAT_ENQUEUE:
-        oae = (const struct ofp_action_enqueue *) a;
-        ds_put_format(s, "enqueue:");
-        ofputil_format_port(ntohs(oae->port), s);
-        ds_put_format(s, "q%"PRIu32, ntohl(oae->queue_id));
-        break;
-
-    case OFPUTIL_OFPAT_SET_VLAN_VID:
-        ds_put_format(s, "mod_vlan_vid:%"PRIu16,
-                      ntohs(a->vlan_vid.vlan_vid));
-        break;
-
-    case OFPUTIL_OFPAT_SET_VLAN_PCP:
-        ds_put_format(s, "mod_vlan_pcp:%"PRIu8, a->vlan_pcp.vlan_pcp);
-        break;
-
-    case OFPUTIL_OFPAT_STRIP_VLAN:
-        ds_put_cstr(s, "strip_vlan");
-        break;
-
-    case OFPUTIL_OFPAT_SET_DL_SRC:
-        oada = (const struct ofp_action_dl_addr *) a;
-        ds_put_format(s, "mod_dl_src:"ETH_ADDR_FMT,
-                      ETH_ADDR_ARGS(oada->dl_addr));
-        break;
-
-    case OFPUTIL_OFPAT_SET_DL_DST:
-        oada = (const struct ofp_action_dl_addr *) a;
-        ds_put_format(s, "mod_dl_dst:"ETH_ADDR_FMT,
-                      ETH_ADDR_ARGS(oada->dl_addr));
-        break;
-
-    case OFPUTIL_OFPAT_SET_NW_SRC:
-        ds_put_format(s, "mod_nw_src:"IP_FMT, IP_ARGS(&a->nw_addr.nw_addr));
-        break;
-
-    case OFPUTIL_OFPAT_SET_NW_DST:
-        ds_put_format(s, "mod_nw_dst:"IP_FMT, IP_ARGS(&a->nw_addr.nw_addr));
-        break;
-
-    case OFPUTIL_OFPAT_SET_NW_TOS:
-        ds_put_format(s, "mod_nw_tos:%d", a->nw_tos.nw_tos);
-        break;
-
-    case OFPUTIL_OFPAT_SET_TP_SRC:
-        ds_put_format(s, "mod_tp_src:%d", ntohs(a->tp_port.tp_port));
-        break;
-
-    case OFPUTIL_OFPAT_SET_TP_DST:
-        ds_put_format(s, "mod_tp_dst:%d", ntohs(a->tp_port.tp_port));
-        break;
-
-    case OFPUTIL_NXAST_RESUBMIT:
-        nar = (struct nx_action_resubmit *)a;
-        ds_put_format(s, "resubmit:");
-        ofputil_format_port(ntohs(nar->in_port), s);
-        break;
-
-    case OFPUTIL_NXAST_RESUBMIT_TABLE:
-        nar = (struct nx_action_resubmit *)a;
-        ds_put_format(s, "resubmit(");
-        if (nar->in_port != htons(OFPP_IN_PORT)) {
-            ofputil_format_port(ntohs(nar->in_port), s);
-        }
-        ds_put_char(s, ',');
-        if (nar->table != 255) {
-            ds_put_format(s, "%"PRIu8, nar->table);
-        }
-        ds_put_char(s, ')');
-        break;
-
-    case OFPUTIL_NXAST_SET_TUNNEL:
-        nast = (struct nx_action_set_tunnel *)a;
-        ds_put_format(s, "set_tunnel:%#"PRIx32, ntohl(nast->tun_id));
-        break;
-
-    case OFPUTIL_NXAST_SET_QUEUE:
-        nasq = (struct nx_action_set_queue *)a;
-        ds_put_format(s, "set_queue:%u", ntohl(nasq->queue_id));
-        break;
-
-    case OFPUTIL_NXAST_POP_QUEUE:
-        ds_put_cstr(s, "pop_queue");
-        break;
-
-    case OFPUTIL_NXAST_NOTE:
-        print_note(s, (const struct nx_action_note *) a);
-        break;
-
-    case OFPUTIL_NXAST_REG_MOVE:
-        move = (const struct nx_action_reg_move *) a;
-        nxm_format_reg_move(move, s);
-        break;
-
-    case OFPUTIL_NXAST_REG_LOAD:
-        load = (const struct nx_action_reg_load *) a;
-        nxm_format_reg_load(load, s);
-        break;
-
-    case OFPUTIL_NXAST_SET_TUNNEL64:
-        nast64 = (const struct nx_action_set_tunnel64 *) a;
-        ds_put_format(s, "set_tunnel64:%#"PRIx64,
-                      ntohll(nast64->tun_id));
-        break;
-
-    case OFPUTIL_NXAST_MULTIPATH:
-        nam = (const struct nx_action_multipath *) a;
-        multipath_format(nam, s);
-        break;
-
-    case OFPUTIL_NXAST_AUTOPATH:
-        naa = (const struct nx_action_autopath *)a;
-        ds_put_format(s, "autopath(%u,", ntohl(naa->id));
-        nxm_decode(&subfield, naa->dst, naa->ofs_nbits);
-        mf_format_subfield(&subfield, s);
-        ds_put_char(s, ')');
-        break;
-
-    case OFPUTIL_NXAST_BUNDLE:
-    case OFPUTIL_NXAST_BUNDLE_LOAD:
-        bundle_format((const struct nx_action_bundle *) a, s);
-        break;
-
-    case OFPUTIL_NXAST_OUTPUT_REG:
-        naor = (const struct nx_action_output_reg *) a;
-        ds_put_cstr(s, "output:");
-        nxm_decode(&subfield, naor->src, naor->ofs_nbits);
-        mf_format_subfield(&subfield, s);
-        break;
-
-    case OFPUTIL_NXAST_LEARN:
-        learn_format((const struct nx_action_learn *) a, s);
-        break;
-
-    case OFPUTIL_NXAST_DEC_TTL:
-        ds_put_cstr(s, "dec_ttl");
-        break;
-
-    case OFPUTIL_NXAST_EXIT:
-        ds_put_cstr(s, "exit");
-        break;
-
-    case OFPUTIL_NXAST_FIN_TIMEOUT:
-        naft = (const struct nx_action_fin_timeout *) a;
-        ds_put_cstr(s, "fin_timeout(");
-        if (naft->fin_idle_timeout) {
-            ds_put_format(s, "idle_timeout=%"PRIu16",",
-                          ntohs(naft->fin_idle_timeout));
-        }
-        if (naft->fin_hard_timeout) {
-            ds_put_format(s, "hard_timeout=%"PRIu16",",
-                          ntohs(naft->fin_hard_timeout));
-        }
-        ds_chomp(s, ',');
-        ds_put_char(s, ')');
-        break;
-
-    case OFPUTIL_NXAST_CONTROLLER:
-        nac = (const struct nx_action_controller *) a;
-        ds_put_cstr(s, "controller(");
-        if (nac->reason != OFPR_ACTION) {
-            ds_put_format(s, "reason=%s,",
-                          ofputil_packet_in_reason_to_string(nac->reason));
-        }
-        if (nac->max_len != htons(UINT16_MAX)) {
-            ds_put_format(s, "max_len=%"PRIu16",", ntohs(nac->max_len));
-        }
-        if (nac->controller_id != htons(0)) {
-            ds_put_format(s, "id=%"PRIu16",", ntohs(nac->controller_id));
-        }
-        ds_chomp(s, ',');
-        ds_put_char(s, ')');
-        break;
-
-    default:
-        break;
-    }
-}
-
-void
-ofp_print_actions(struct ds *string, const union ofp_action *actions,
-                  size_t n_actions)
-{
-    const union ofp_action *a;
-    size_t left;
-
-    ds_put_cstr(string, "actions=");
-    if (!n_actions) {
-        ds_put_cstr(string, "drop");
-    }
-
-    OFPUTIL_ACTION_FOR_EACH (a, left, actions, n_actions) {
-        int code = ofputil_decode_action(a);
-        if (code >= 0) {
-            if (a != actions) {
-                ds_put_cstr(string, ",");
-            }
-            ofp_print_action(string, a, code);
-        } else {
-            ofp_print_error(string, -code);
-        }
-    }
-    if (left > 0) {
-        ds_put_format(string, " ***%zu leftover bytes following actions",
-                      left * sizeof *a);
-    }
-}
-
 static void
 ofp_print_packet_out(struct ds *string, const struct ofp_packet_out *opo,
                      int verbosity)
 {
     struct ofputil_packet_out po;
+    struct ofpbuf ofpacts;
     enum ofperr error;
 
-    error = ofputil_decode_packet_out(&po, opo);
+    ofpbuf_init(&ofpacts, 64);
+    error = ofputil_decode_packet_out(&po, opo, &ofpacts);
     if (error) {
+        ofpbuf_uninit(&ofpacts);
         ofp_print_error(string, error);
         return;
     }
@@ -423,7 +174,7 @@ ofp_print_packet_out(struct ds *string, const struct ofp_packet_out *opo,
     ofputil_format_port(po.in_port, string);
 
     ds_put_char(string, ' ');
-    ofp_print_actions(string, po.actions, po.n_actions);
+    ofpacts_format(po.ofpacts, po.ofpacts_len, string);
 
     if (po.buffer_id == UINT32_MAX) {
         ds_put_format(string, " data_len=%zu", po.packet_len);
@@ -437,103 +188,142 @@ ofp_print_packet_out(struct ds *string, const struct ofp_packet_out *opo,
         ds_put_format(string, " buffer=0x%08"PRIx32, po.buffer_id);
     }
     ds_put_char(string, '\n');
+
+    ofpbuf_uninit(&ofpacts);
 }
 
 /* qsort comparison function. */
 static int
 compare_ports(const void *a_, const void *b_)
 {
-    const struct ofp_phy_port *a = a_;
-    const struct ofp_phy_port *b = b_;
-    uint16_t ap = ntohs(a->port_no);
-    uint16_t bp = ntohs(b->port_no);
+    const struct ofputil_phy_port *a = a_;
+    const struct ofputil_phy_port *b = b_;
+    uint16_t ap = a->port_no;
+    uint16_t bp = b->port_no;
 
     return ap < bp ? -1 : ap > bp;
 }
 
-struct bit_name {
-    uint32_t bit;
-    const char *name;
-};
-
 static void
 ofp_print_bit_names(struct ds *string, uint32_t bits,
-                    const struct bit_name bit_names[])
+                    const char *(*bit_to_name)(uint32_t bit),
+                    char separator)
 {
     int n = 0;
+    int i;
 
     if (!bits) {
         ds_put_cstr(string, "0");
         return;
     }
 
-    for (; bits && bit_names->name; bit_names++) {
-        if (bits & bit_names->bit) {
-            if (n++) {
-                ds_put_char(string, ' ');
+    for (i = 0; i < 32; i++) {
+        uint32_t bit = UINT32_C(1) << i;
+
+        if (bits & bit) {
+            const char *name = bit_to_name(bit);
+            if (name) {
+                if (n++) {
+                    ds_put_char(string, separator);
+                }
+                ds_put_cstr(string, name);
+                bits &= ~bit;
             }
-            ds_put_cstr(string, bit_names->name);
-            bits &= ~bit_names->bit;
         }
     }
 
     if (bits) {
-        if (n++) {
-            ds_put_char(string, ' ');
+        if (n) {
+            ds_put_char(string, separator);
         }
         ds_put_format(string, "0x%"PRIx32, bits);
     }
 }
 
+static const char *
+netdev_feature_to_name(uint32_t bit)
+{
+    enum netdev_features f = bit;
+
+    switch (f) {
+    case NETDEV_F_10MB_HD:    return "10MB-HD";
+    case NETDEV_F_10MB_FD:    return "10MB-FD";
+    case NETDEV_F_100MB_HD:   return "100MB-HD";
+    case NETDEV_F_100MB_FD:   return "100MB-FD";
+    case NETDEV_F_1GB_HD:     return "1GB-HD";
+    case NETDEV_F_1GB_FD:     return "1GB-FD";
+    case NETDEV_F_10GB_FD:    return "10GB-FD";
+    case NETDEV_F_40GB_FD:    return "40GB-FD";
+    case NETDEV_F_100GB_FD:   return "100GB-FD";
+    case NETDEV_F_1TB_FD:     return "1TB-FD";
+    case NETDEV_F_OTHER:      return "OTHER";
+    case NETDEV_F_COPPER:     return "COPPER";
+    case NETDEV_F_FIBER:      return "FIBER";
+    case NETDEV_F_AUTONEG:    return "AUTO_NEG";
+    case NETDEV_F_PAUSE:      return "AUTO_PAUSE";
+    case NETDEV_F_PAUSE_ASYM: return "AUTO_PAUSE_ASYM";
+    }
+
+    return NULL;
+}
+
 static void
-ofp_print_port_features(struct ds *string, uint32_t features)
+ofp_print_port_features(struct ds *string, enum netdev_features features)
 {
-    static const struct bit_name feature_bits[] = {
-        { OFPPF_10MB_HD,    "10MB-HD" },
-        { OFPPF_10MB_FD,    "10MB-FD" },
-        { OFPPF_100MB_HD,   "100MB-HD" },
-        { OFPPF_100MB_FD,   "100MB-FD" },
-        { OFPPF_1GB_HD,     "1GB-HD" },
-        { OFPPF_1GB_FD,     "1GB-FD" },
-        { OFPPF_10GB_FD,    "10GB-FD" },
-        { OFPPF_COPPER,     "COPPER" },
-        { OFPPF_FIBER,      "FIBER" },
-        { OFPPF_AUTONEG,    "AUTO_NEG" },
-        { OFPPF_PAUSE,      "AUTO_PAUSE" },
-        { OFPPF_PAUSE_ASYM, "AUTO_PAUSE_ASYM" },
-        { 0,                NULL },
-    };
-
-    ofp_print_bit_names(string, features, feature_bits);
+    ofp_print_bit_names(string, features, netdev_feature_to_name, ' ');
     ds_put_char(string, '\n');
 }
 
+static const char *
+ofputil_port_config_to_name(uint32_t bit)
+{
+    enum ofputil_port_config pc = bit;
+
+    switch (pc) {
+    case OFPUTIL_PC_PORT_DOWN:    return "PORT_DOWN";
+    case OFPUTIL_PC_NO_STP:       return "NO_STP";
+    case OFPUTIL_PC_NO_RECV:      return "NO_RECV";
+    case OFPUTIL_PC_NO_RECV_STP:  return "NO_RECV_STP";
+    case OFPUTIL_PC_NO_FLOOD:     return "NO_FLOOD";
+    case OFPUTIL_PC_NO_FWD:       return "NO_FWD";
+    case OFPUTIL_PC_NO_PACKET_IN: return "NO_PACKET_IN";
+    }
+
+    return NULL;
+}
+
 static void
-ofp_print_port_config(struct ds *string, uint32_t config)
+ofp_print_port_config(struct ds *string, enum ofputil_port_config config)
 {
-    static const struct bit_name config_bits[] = {
-        { OFPPC_PORT_DOWN,    "PORT_DOWN" },
-        { OFPPC_NO_STP,       "NO_STP" },
-        { OFPPC_NO_RECV,      "NO_RECV" },
-        { OFPPC_NO_RECV_STP,  "NO_RECV_STP" },
-        { OFPPC_NO_FLOOD,     "NO_FLOOD" },
-        { OFPPC_NO_FWD,       "NO_FWD" },
-        { OFPPC_NO_PACKET_IN, "NO_PACKET_IN" },
-        { 0,                  NULL },
-    };
-
-    ofp_print_bit_names(string, config, config_bits);
+    ofp_print_bit_names(string, config, ofputil_port_config_to_name, ' ');
     ds_put_char(string, '\n');
 }
 
+static const char *
+ofputil_port_state_to_name(uint32_t bit)
+{
+    enum ofputil_port_state ps = bit;
+
+    switch (ps) {
+    case OFPUTIL_PS_LINK_DOWN: return "LINK_DOWN";
+    case OFPUTIL_PS_BLOCKED:   return "BLOCKED";
+    case OFPUTIL_PS_LIVE:      return "LIVE";
+
+    case OFPUTIL_PS_STP_LISTEN:
+    case OFPUTIL_PS_STP_LEARN:
+    case OFPUTIL_PS_STP_FORWARD:
+    case OFPUTIL_PS_STP_BLOCK:
+        /* Handled elsewhere. */
+        return NULL;
+    }
+
+    return NULL;
+}
+
 static void
-ofp_print_port_state(struct ds *string, uint32_t state)
+ofp_print_port_state(struct ds *string, enum ofputil_port_state state)
 {
-    static const struct bit_name state_bits[] = {
-        { OFPPS_LINK_DOWN, "LINK_DOWN" },
-        { 0,               NULL },
-    };
-    uint32_t stp_state;
+    enum ofputil_port_state stp_state;
 
     /* The STP state is a 2-bit field so it doesn't fit in with the bitmask
      * pattern.  We have to special case it.
@@ -542,25 +332,27 @@ ofp_print_port_state(struct ds *string, uint32_t state)
      * talking to OVS, so we'd always print STP_LISTEN in that case.
      * Therefore, we don't print anything at all if the value is STP_LISTEN, to
      * avoid confusing users. */
-    stp_state = state & OFPPS_STP_MASK;
+    stp_state = state & OFPUTIL_PS_STP_MASK;
     if (stp_state) {
-        ds_put_cstr(string, (stp_state == OFPPS_STP_LEARN ? "STP_LEARN"
-                             : stp_state == OFPPS_STP_FORWARD ? "STP_FORWARD"
-                             : "STP_BLOCK"));
-        state &= ~OFPPS_STP_MASK;
+        ds_put_cstr(string,
+                    (stp_state == OFPUTIL_PS_STP_LEARN ? "STP_LEARN"
+                     : stp_state == OFPUTIL_PS_STP_FORWARD ? "STP_FORWARD"
+                     : "STP_BLOCK"));
+        state &= ~OFPUTIL_PS_STP_MASK;
         if (state) {
-            ofp_print_bit_names(string, state, state_bits);
+            ofp_print_bit_names(string, state, ofputil_port_state_to_name,
+                                ' ');
         }
     } else {
-        ofp_print_bit_names(string, state, state_bits);
+        ofp_print_bit_names(string, state, ofputil_port_state_to_name, ' ');
     }
     ds_put_char(string, '\n');
 }
 
 static void
-ofp_print_phy_port(struct ds *string, const struct ofp_phy_port *port)
+ofp_print_phy_port(struct ds *string, const struct ofputil_phy_port *port)
 {
-    char name[OFP_MAX_PORT_NAME_LEN];
+    char name[sizeof port->name];
     int j;
 
     memcpy(name, port->name, sizeof name);
@@ -572,58 +364,155 @@ ofp_print_phy_port(struct ds *string, const struct ofp_phy_port *port)
     name[j] = '\0';
 
     ds_put_char(string, ' ');
-    ofputil_format_port(ntohs(port->port_no), string);
+    ofputil_format_port(port->port_no, string);
     ds_put_format(string, "(%s): addr:"ETH_ADDR_FMT"\n",
                   name, ETH_ADDR_ARGS(port->hw_addr));
 
     ds_put_cstr(string, "     config:     ");
-    ofp_print_port_config(string, ntohl(port->config));
+    ofp_print_port_config(string, port->config);
 
     ds_put_cstr(string, "     state:      ");
-    ofp_print_port_state(string, ntohl(port->state));
+    ofp_print_port_state(string, port->state);
 
     if (port->curr) {
         ds_put_format(string, "     current:    ");
-        ofp_print_port_features(string, ntohl(port->curr));
+        ofp_print_port_features(string, port->curr);
     }
     if (port->advertised) {
         ds_put_format(string, "     advertised: ");
-        ofp_print_port_features(string, ntohl(port->advertised));
+        ofp_print_port_features(string, port->advertised);
     }
     if (port->supported) {
         ds_put_format(string, "     supported:  ");
-        ofp_print_port_features(string, ntohl(port->supported));
+        ofp_print_port_features(string, port->supported);
     }
     if (port->peer) {
         ds_put_format(string, "     peer:       ");
-        ofp_print_port_features(string, ntohl(port->peer));
+        ofp_print_port_features(string, port->peer);
     }
+    ds_put_format(string, "     speed: %"PRIu32" Mbps now, "
+                  "%"PRIu32" Mbps max\n",
+                  port->curr_speed / UINT32_C(1000),
+                  port->max_speed / UINT32_C(1000));
+}
+
+/* Given a buffer 'b' that contains an array of OpenFlow ports of type
+ * 'ofp_version', writes a detailed description of each port into
+ * 'string'. */
+static void
+ofp_print_phy_ports(struct ds *string, uint8_t ofp_version,
+                    struct ofpbuf *b)
+{
+    size_t n_ports;
+    struct ofputil_phy_port *ports;
+    enum ofperr error;
+    size_t i;
+
+    n_ports = ofputil_count_phy_ports(ofp_version, b);
+
+    ports = xmalloc(n_ports * sizeof *ports);
+    for (i = 0; i < n_ports; i++) {
+        error = ofputil_pull_phy_port(ofp_version, b, &ports[i]);
+        if (error) {
+            ofp_print_error(string, error);
+            goto exit;
+        }
+    }
+    qsort(ports, n_ports, sizeof *ports, compare_ports);
+    for (i = 0; i < n_ports; i++) {
+        ofp_print_phy_port(string, &ports[i]);
+    }
+
+exit:
+    free(ports);
+}
+
+static const char *
+ofputil_capabilities_to_name(uint32_t bit)
+{
+    enum ofputil_capabilities capabilities = bit;
+
+    switch (capabilities) {
+    case OFPUTIL_C_FLOW_STATS:   return "FLOW_STATS";
+    case OFPUTIL_C_TABLE_STATS:  return "TABLE_STATS";
+    case OFPUTIL_C_PORT_STATS:   return "PORT_STATS";
+    case OFPUTIL_C_IP_REASM:     return "IP_REASM";
+    case OFPUTIL_C_QUEUE_STATS:  return "QUEUE_STATS";
+    case OFPUTIL_C_ARP_MATCH_IP: return "ARP_MATCH_IP";
+    case OFPUTIL_C_STP:          return "STP";
+    case OFPUTIL_C_GROUP_STATS:  return "GROUP_STATS";
+    }
+
+    return NULL;
+}
+
+static const char *
+ofputil_action_bitmap_to_name(uint32_t bit)
+{
+    enum ofputil_action_bitmap action = bit;
+
+    switch (action) {
+    case OFPUTIL_A_OUTPUT:         return "OUTPUT";
+    case OFPUTIL_A_SET_VLAN_VID:   return "SET_VLAN_VID";
+    case OFPUTIL_A_SET_VLAN_PCP:   return "SET_VLAN_PCP";
+    case OFPUTIL_A_STRIP_VLAN:     return "STRIP_VLAN";
+    case OFPUTIL_A_SET_DL_SRC:     return "SET_DL_SRC";
+    case OFPUTIL_A_SET_DL_DST:     return "SET_DL_DST";
+    case OFPUTIL_A_SET_NW_SRC:     return "SET_NW_SRC";
+    case OFPUTIL_A_SET_NW_DST:     return "SET_NW_DST";
+    case OFPUTIL_A_SET_NW_ECN:     return "SET_NW_ECN";
+    case OFPUTIL_A_SET_NW_TOS:     return "SET_NW_TOS";
+    case OFPUTIL_A_SET_TP_SRC:     return "SET_TP_SRC";
+    case OFPUTIL_A_SET_TP_DST:     return "SET_TP_DST";
+    case OFPUTIL_A_ENQUEUE:        return "ENQUEUE";
+    case OFPUTIL_A_COPY_TTL_OUT:   return "COPY_TTL_OUT";
+    case OFPUTIL_A_COPY_TTL_IN:    return "COPY_TTL_IN";
+    case OFPUTIL_A_SET_MPLS_LABEL: return "SET_MPLS_LABEL";
+    case OFPUTIL_A_SET_MPLS_TC:    return "SET_MPLS_TC";
+    case OFPUTIL_A_SET_MPLS_TTL:   return "SET_MPLS_TTL";
+    case OFPUTIL_A_DEC_MPLS_TTL:   return "DEC_MPLS_TTL";
+    case OFPUTIL_A_PUSH_VLAN:      return "PUSH_VLAN";
+    case OFPUTIL_A_POP_VLAN:       return "POP_VLAN";
+    case OFPUTIL_A_PUSH_MPLS:      return "PUSH_MPLS";
+    case OFPUTIL_A_POP_MPLS:       return "POP_MPLS";
+    case OFPUTIL_A_SET_QUEUE:      return "SET_QUEUE";
+    case OFPUTIL_A_GROUP:          return "GROUP";
+    case OFPUTIL_A_SET_NW_TTL:     return "SET_NW_TTL";
+    case OFPUTIL_A_DEC_NW_TTL:     return "DEC_NW_TTL";
+    }
+
+    return NULL;
 }
 
 static void
 ofp_print_switch_features(struct ds *string,
                           const struct ofp_switch_features *osf)
 {
-    size_t len = ntohs(osf->header.length);
-    struct ofp_phy_port *port_list;
-    int n_ports;
-    int i;
+    struct ofputil_switch_features features;
+    enum ofperr error;
+    struct ofpbuf b;
 
-    ds_put_format(string, " ver:0x%x, dpid:%016"PRIx64"\n",
-            osf->header.version, ntohll(osf->datapath_id));
-    ds_put_format(string, "n_tables:%d, n_buffers:%d\n", osf->n_tables,
-            ntohl(osf->n_buffers));
-    ds_put_format(string, "features: capabilities:%#x, actions:%#x\n",
-           ntohl(osf->capabilities), ntohl(osf->actions));
+    error = ofputil_decode_switch_features(osf, &features, &b);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
 
-    n_ports = (len - sizeof *osf) / sizeof *osf->ports;
+    ds_put_format(string, " dpid:%016"PRIx64"\n", features.datapath_id);
+    ds_put_format(string, "n_tables:%"PRIu8", n_buffers:%"PRIu32"\n",
+                  features.n_tables, features.n_buffers);
 
-    port_list = xmemdup(osf->ports, len - sizeof *osf);
-    qsort(port_list, n_ports, sizeof *port_list, compare_ports);
-    for (i = 0; i < n_ports; i++) {
-        ofp_print_phy_port(string, &port_list[i]);
-    }
-    free(port_list);
+    ds_put_cstr(string, "capabilities: ");
+    ofp_print_bit_names(string, features.capabilities,
+                        ofputil_capabilities_to_name, ' ');
+    ds_put_char(string, '\n');
+
+    ds_put_cstr(string, "actions: ");
+    ofp_print_bit_names(string, features.actions,
+                        ofputil_action_bitmap_to_name, ' ');
+    ds_put_char(string, '\n');
+
+    ofp_print_phy_ports(string, osf->header.version, &b);
 }
 
 static void
@@ -691,25 +580,25 @@ print_ip_netmask(struct ds *string, const char *leader, ovs_be32 ip,
 }
 
 void
-ofp_print_match(struct ds *f, const struct ofp_match *om, int verbosity)
+ofp10_match_print(struct ds *f, const struct ofp10_match *om, int verbosity)
 {
-    char *s = ofp_match_to_string(om, verbosity);
+    char *s = ofp10_match_to_string(om, verbosity);
     ds_put_cstr(f, s);
     free(s);
 }
 
 char *
-ofp_match_to_string(const struct ofp_match *om, int verbosity)
+ofp10_match_to_string(const struct ofp10_match *om, int verbosity)
 {
     struct ds f = DS_EMPTY_INITIALIZER;
     uint32_t w = ntohl(om->wildcards);
     bool skip_type = false;
     bool skip_proto = false;
 
-    if (!(w & OFPFW_DL_TYPE)) {
+    if (!(w & OFPFW10_DL_TYPE)) {
         skip_type = true;
         if (om->dl_type == htons(ETH_TYPE_IP)) {
-            if (!(w & OFPFW_NW_PROTO)) {
+            if (!(w & OFPFW10_NW_PROTO)) {
                 skip_proto = true;
                 if (om->nw_proto == IPPROTO_ICMP) {
                     ds_put_cstr(&f, "icmp,");
@@ -730,44 +619,46 @@ ofp_match_to_string(const struct ofp_match *om, int verbosity)
             skip_type = false;
         }
     }
-    print_wild(&f, "in_port=", w & OFPFW_IN_PORT, verbosity,
+    print_wild(&f, "in_port=", w & OFPFW10_IN_PORT, verbosity,
                "%d", ntohs(om->in_port));
-    print_wild(&f, "dl_vlan=", w & OFPFW_DL_VLAN, verbosity,
+    print_wild(&f, "dl_vlan=", w & OFPFW10_DL_VLAN, verbosity,
                "%d", ntohs(om->dl_vlan));
-    print_wild(&f, "dl_vlan_pcp=", w & OFPFW_DL_VLAN_PCP, verbosity,
+    print_wild(&f, "dl_vlan_pcp=", w & OFPFW10_DL_VLAN_PCP, verbosity,
                "%d", om->dl_vlan_pcp);
-    print_wild(&f, "dl_src=", w & OFPFW_DL_SRC, verbosity,
+    print_wild(&f, "dl_src=", w & OFPFW10_DL_SRC, verbosity,
                ETH_ADDR_FMT, ETH_ADDR_ARGS(om->dl_src));
-    print_wild(&f, "dl_dst=", w & OFPFW_DL_DST, verbosity,
+    print_wild(&f, "dl_dst=", w & OFPFW10_DL_DST, verbosity,
                ETH_ADDR_FMT, ETH_ADDR_ARGS(om->dl_dst));
     if (!skip_type) {
-        print_wild(&f, "dl_type=", w & OFPFW_DL_TYPE, verbosity,
+        print_wild(&f, "dl_type=", w & OFPFW10_DL_TYPE, verbosity,
                    "0x%04x", ntohs(om->dl_type));
     }
     print_ip_netmask(&f, "nw_src=", om->nw_src,
-                     (w & OFPFW_NW_SRC_MASK) >> OFPFW_NW_SRC_SHIFT, verbosity);
+                     (w & OFPFW10_NW_SRC_MASK) >> OFPFW10_NW_SRC_SHIFT,
+                     verbosity);
     print_ip_netmask(&f, "nw_dst=", om->nw_dst,
-                     (w & OFPFW_NW_DST_MASK) >> OFPFW_NW_DST_SHIFT, verbosity);
+                     (w & OFPFW10_NW_DST_MASK) >> OFPFW10_NW_DST_SHIFT,
+                     verbosity);
     if (!skip_proto) {
         if (om->dl_type == htons(ETH_TYPE_ARP)) {
-            print_wild(&f, "arp_op=", w & OFPFW_NW_PROTO, verbosity,
+            print_wild(&f, "arp_op=", w & OFPFW10_NW_PROTO, verbosity,
                        "%u", om->nw_proto);
         } else {
-            print_wild(&f, "nw_proto=", w & OFPFW_NW_PROTO, verbosity,
+            print_wild(&f, "nw_proto=", w & OFPFW10_NW_PROTO, verbosity,
                        "%u", om->nw_proto);
         }
     }
-    print_wild(&f, "nw_tos=", w & OFPFW_NW_TOS, verbosity,
+    print_wild(&f, "nw_tos=", w & OFPFW10_NW_TOS, verbosity,
                "%u", om->nw_tos);
     if (om->nw_proto == IPPROTO_ICMP) {
-        print_wild(&f, "icmp_type=", w & OFPFW_ICMP_TYPE, verbosity,
+        print_wild(&f, "icmp_type=", w & OFPFW10_ICMP_TYPE, verbosity,
                    "%d", ntohs(om->tp_src));
-        print_wild(&f, "icmp_code=", w & OFPFW_ICMP_CODE, verbosity,
+        print_wild(&f, "icmp_code=", w & OFPFW10_ICMP_CODE, verbosity,
                    "%d", ntohs(om->tp_dst));
     } else {
-        print_wild(&f, "tp_src=", w & OFPFW_TP_SRC, verbosity,
+        print_wild(&f, "tp_src=", w & OFPFW10_TP_SRC, verbosity,
                    "%d", ntohs(om->tp_src));
-        print_wild(&f, "tp_dst=", w & OFPFW_TP_DST, verbosity,
+        print_wild(&f, "tp_dst=", w & OFPFW10_TP_DST, verbosity,
                    "%d", ntohs(om->tp_dst));
     }
     if (ds_last(&f) == ',') {
@@ -781,11 +672,14 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
                    enum ofputil_msg_code code, int verbosity)
 {
     struct ofputil_flow_mod fm;
+    struct ofpbuf ofpacts;
     bool need_priority;
     enum ofperr error;
 
-    error = ofputil_decode_flow_mod(&fm, oh, OFPUTIL_P_OF10_TID);
+    ofpbuf_init(&ofpacts, 64);
+    error = ofputil_decode_flow_mod(&fm, oh, OFPUTIL_P_OF10_TID, &ofpacts);
     if (error) {
+        ofpbuf_uninit(&ofpacts);
         ofp_print_error(s, error);
         return;
     }
@@ -817,7 +711,7 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
     ds_put_char(s, ' ');
     if (verbosity >= 3 && code == OFPUTIL_OFPT_FLOW_MOD) {
         const struct ofp_flow_mod *ofm = (const struct ofp_flow_mod *) oh;
-        ofp_print_match(s, &ofm->match, verbosity);
+        ofp10_match_print(s, &ofm->match, verbosity);
 
         /* ofp_print_match() doesn't print priority. */
         need_priority = true;
@@ -842,8 +736,12 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
     if (ds_last(s) != ' ') {
         ds_put_char(s, ' ');
     }
-    if (fm.cookie != htonll(0)) {
-        ds_put_format(s, "cookie:0x%"PRIx64" ", ntohll(fm.cookie));
+    if (fm.new_cookie != htonll(0)) {
+        ds_put_format(s, "cookie:0x%"PRIx64" ", ntohll(fm.new_cookie));
+    }
+    if (fm.cookie_mask != htonll(0)) {
+        ds_put_format(s, "cookie:0x%"PRIx64"/0x%"PRIx64" ",
+                ntohll(fm.cookie), ntohll(fm.cookie_mask));
     }
     if (fm.idle_timeout != OFP_FLOW_PERMANENT) {
         ds_put_format(s, "idle:%"PRIu16" ", fm.idle_timeout);
@@ -857,6 +755,11 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
     if (fm.buffer_id != UINT32_MAX) {
         ds_put_format(s, "buf:0x%"PRIx32" ", fm.buffer_id);
     }
+    if (fm.out_port != OFPP_NONE) {
+        ds_put_format(s, "out_port:");
+        ofputil_format_port(fm.out_port, s);
+        ds_put_char(s, ' ');
+    }
     if (fm.flags != 0) {
         uint16_t flags = fm.flags;
 
@@ -876,7 +779,8 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
         }
     }
 
-    ofp_print_actions(s, fm.actions, fm.n_actions);
+    ofpacts_format(fm.ofpacts, fm.ofpacts_len, s);
+    ofpbuf_uninit(&ofpacts);
 }
 
 static void
@@ -904,6 +808,10 @@ ofp_flow_removed_reason_to_string(enum ofp_flow_removed_reason reason)
         return "hard";
     case OFPRR_DELETE:
         return "delete";
+    case OFPRR_GROUP_DELETE:
+        return "group_delete";
+    case OFPRR_EVICTION:
+        return "eviction";
     default:
         sprintf(s, "%d", (int) reason);
         return s;
@@ -938,14 +846,29 @@ ofp_print_flow_removed(struct ds *string, const struct ofp_header *oh)
 }
 
 static void
-ofp_print_port_mod(struct ds *string, const struct ofp_port_mod *opm)
+ofp_print_port_mod(struct ds *string, const struct ofp_header *oh)
 {
-    ds_put_format(string, "port: %d: addr:"ETH_ADDR_FMT", config: %#x, mask:%#x\n",
-            ntohs(opm->port_no), ETH_ADDR_ARGS(opm->hw_addr),
-            ntohl(opm->config), ntohl(opm->mask));
+    struct ofputil_port_mod pm;
+    enum ofperr error;
+
+    error = ofputil_decode_port_mod(oh, &pm);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
+
+    ds_put_format(string, "port: %"PRIu16": addr:"ETH_ADDR_FMT"\n",
+                  pm.port_no, ETH_ADDR_ARGS(pm.hw_addr));
+
+    ds_put_format(string, "     config: ");
+    ofp_print_port_config(string, pm.config);
+
+    ds_put_format(string, "     mask:   ");
+    ofp_print_port_config(string, pm.mask);
+
     ds_put_format(string, "     advertise: ");
-    if (opm->advertise) {
-        ofp_print_port_features(string, ntohl(opm->advertise));
+    if (pm.advertise) {
+        ofp_print_port_features(string, pm.advertise);
     } else {
         ds_put_format(string, "UNCHANGED\n");
     }
@@ -992,15 +915,24 @@ ofp_print_error_msg(struct ds *string, const struct ofp_error_msg *oem)
 static void
 ofp_print_port_status(struct ds *string, const struct ofp_port_status *ops)
 {
-    if (ops->reason == OFPPR_ADD) {
+    struct ofputil_port_status ps;
+    enum ofperr error;
+
+    error = ofputil_decode_port_status(ops, &ps);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
+
+    if (ps.reason == OFPPR_ADD) {
         ds_put_format(string, " ADD:");
-    } else if (ops->reason == OFPPR_DELETE) {
+    } else if (ps.reason == OFPPR_DELETE) {
         ds_put_format(string, " DEL:");
-    } else if (ops->reason == OFPPR_MODIFY) {
+    } else if (ps.reason == OFPPR_MODIFY) {
         ds_put_format(string, " MOD:");
     }
 
-    ofp_print_phy_port(string, &ops->desc);
+    ofp_print_phy_port(string, &ps.desc);
 }
 
 static void
@@ -1049,50 +981,58 @@ ofp_print_flow_stats_request(struct ds *string,
     cls_rule_format(&fsr.match, string);
 }
 
+void
+ofp_print_flow_stats(struct ds *string, struct ofputil_flow_stats *fs)
+{
+    ds_put_format(string, " cookie=0x%"PRIx64", duration=",
+                  ntohll(fs->cookie));
+
+    ofp_print_duration(string, fs->duration_sec, fs->duration_nsec);
+    ds_put_format(string, ", table=%"PRIu8", ", fs->table_id);
+    ds_put_format(string, "n_packets=%"PRIu64", ", fs->packet_count);
+    ds_put_format(string, "n_bytes=%"PRIu64", ", fs->byte_count);
+    if (fs->idle_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(string, "idle_timeout=%"PRIu16", ", fs->idle_timeout);
+    }
+    if (fs->hard_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(string, "hard_timeout=%"PRIu16", ", fs->hard_timeout);
+    }
+    if (fs->idle_age >= 0) {
+        ds_put_format(string, "idle_age=%d, ", fs->idle_age);
+    }
+    if (fs->hard_age >= 0 && fs->hard_age != fs->duration_sec) {
+        ds_put_format(string, "hard_age=%d, ", fs->hard_age);
+    }
+
+    cls_rule_format(&fs->rule, string);
+    if (string->string[string->length - 1] != ' ') {
+        ds_put_char(string, ' ');
+    }
+
+    ofpacts_format(fs->ofpacts, fs->ofpacts_len, string);
+}
+
 static void
 ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh)
 {
+    struct ofpbuf ofpacts;
     struct ofpbuf b;
 
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
+    ofpbuf_init(&ofpacts, 64);
     for (;;) {
         struct ofputil_flow_stats fs;
         int retval;
 
-        retval = ofputil_decode_flow_stats_reply(&fs, &b, true);
+        retval = ofputil_decode_flow_stats_reply(&fs, &b, true, &ofpacts);
         if (retval) {
             if (retval != EOF) {
                 ds_put_cstr(string, " ***parse error***");
             }
             break;
         }
-
         ds_put_char(string, '\n');
-
-        ds_put_format(string, " cookie=0x%"PRIx64", duration=",
-                      ntohll(fs.cookie));
-        ofp_print_duration(string, fs.duration_sec, fs.duration_nsec);
-        ds_put_format(string, ", table=%"PRIu8", ", fs.table_id);
-        ds_put_format(string, "n_packets=%"PRIu64", ", fs.packet_count);
-        ds_put_format(string, "n_bytes=%"PRIu64", ", fs.byte_count);
-        if (fs.idle_timeout != OFP_FLOW_PERMANENT) {
-            ds_put_format(string, "idle_timeout=%"PRIu16",", fs.idle_timeout);
-        }
-        if (fs.hard_timeout != OFP_FLOW_PERMANENT) {
-            ds_put_format(string, "hard_timeout=%"PRIu16",", fs.hard_timeout);
-        }
-        if (fs.idle_age >= 0) {
-            ds_put_format(string, "idle_age=%d,", fs.idle_age);
-        }
-        if (fs.hard_age >= 0 && fs.hard_age != fs.duration_sec) {
-            ds_put_format(string, "hard_age=%d,", fs.hard_age);
-        }
-
-        cls_rule_format(&fs.rule, string);
-        if (string->string[string->length - 1] != ' ') {
-            ds_put_char(string, ' ');
-        }
-        ofp_print_actions(string, fs.actions, fs.n_actions);
+        ofp_print_flow_stats(string, &fs);
      }
 }
 
@@ -1245,6 +1185,18 @@ ofp_print_ofpst_queue_reply(struct ds *string, const struct ofp_header *oh,
     }
 }
 
+static void
+ofp_print_ofpst_port_desc_reply(struct ds *string,
+                                const struct ofp_header *oh)
+{
+    struct ofpbuf b;
+
+    ofpbuf_use_const(&b, oh, ntohs(oh->length));
+    ofpbuf_pull(&b, sizeof(struct ofp_stats_msg));
+    ds_put_char(string, '\n');
+    ofp_print_phy_ports(string, oh->version, &b);
+}
+
 static void
 ofp_print_stats_request(struct ds *string, const struct ofp_header *oh)
 {
@@ -1416,6 +1368,157 @@ ofp_print_nxt_set_controller_id(struct ds *string,
     ds_put_format(string, " id=%"PRIu16, ntohs(nci->controller_id));
 }
 
+static void
+ofp_print_nxt_flow_monitor_cancel(struct ds *string,
+                                  const struct ofp_header *oh)
+{
+    ds_put_format(string, " id=%"PRIu32,
+                  ofputil_decode_flow_monitor_cancel(oh));
+}
+
+static const char *
+nx_flow_monitor_flags_to_name(uint32_t bit)
+{
+    enum nx_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";
+    }
+
+    return NULL;
+}
+
+static void
+ofp_print_nxst_flow_monitor_request(struct ds *string,
+                                    const struct ofp_header *oh)
+{
+    struct ofpbuf b;
+
+    ofpbuf_use_const(&b, oh, ntohs(oh->length));
+    for (;;) {
+        struct ofputil_flow_monitor_request request;
+        int retval;
+
+        retval = ofputil_decode_flow_monitor_request(&request, &b);
+        if (retval) {
+            if (retval != EOF) {
+                ofp_print_error(string, retval);
+            }
+            return;
+        }
+
+        ds_put_format(string, "\n id=%"PRIu32" flags=", request.id);
+        ofp_print_bit_names(string, request.flags,
+                            nx_flow_monitor_flags_to_name, ',');
+
+        if (request.out_port != OFPP_NONE) {
+            ds_put_cstr(string, " out_port=");
+            ofputil_format_port(request.out_port, string);
+        }
+
+        if (request.table_id != 0xff) {
+            ds_put_format(string, " table=%"PRIu8, request.table_id);
+        }
+
+        ds_put_char(string, ' ');
+        cls_rule_format(&request.match, string);
+        ds_chomp(string, ' ');
+    }
+}
+
+static void
+ofp_print_nxst_flow_monitor_reply(struct ds *string,
+                                  const struct ofp_header *oh)
+{
+    uint64_t ofpacts_stub[1024 / 8];
+    struct ofpbuf ofpacts;
+    struct ofpbuf b;
+
+    ofpbuf_use_const(&b, oh, ntohs(oh->length));
+    ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
+    for (;;) {
+        struct ofputil_flow_update update;
+        struct cls_rule match;
+        int retval;
+
+        update.match = &match;
+        retval = ofputil_decode_flow_update(&update, &b, &ofpacts);
+        if (retval) {
+            if (retval != EOF) {
+                ofp_print_error(string, retval);
+            }
+            ofpbuf_uninit(&ofpacts);
+            return;
+        }
+
+        ds_put_cstr(string, "\n event=");
+        switch (update.event) {
+        case NXFME_ADDED:
+            ds_put_cstr(string, "ADDED");
+            break;
+
+        case NXFME_DELETED:
+            ds_put_format(string, "DELETED reason=%s",
+                          ofp_flow_removed_reason_to_string(update.reason));
+            break;
+
+        case NXFME_MODIFIED:
+            ds_put_cstr(string, "MODIFIED");
+            break;
+
+        case NXFME_ABBREV:
+            ds_put_format(string, "ABBREV xid=0x%"PRIx32, ntohl(update.xid));
+            continue;
+        }
+
+        ds_put_format(string, " table=%"PRIu8, update.table_id);
+        if (update.idle_timeout != OFP_FLOW_PERMANENT) {
+            ds_put_format(string, " idle_timeout=%"PRIu16,
+                          update.idle_timeout);
+        }
+        if (update.hard_timeout != OFP_FLOW_PERMANENT) {
+            ds_put_format(string, " hard_timeout=%"PRIu16,
+                          update.hard_timeout);
+        }
+        ds_put_format(string, " cookie=%#"PRIx64, ntohll(update.cookie));
+
+        ds_put_char(string, ' ');
+        cls_rule_format(update.match, string);
+
+        if (update.ofpacts_len) {
+            if (string->string[string->length - 1] != ' ') {
+                ds_put_char(string, ' ');
+            }
+            ofpacts_format(update.ofpacts, update.ofpacts_len, string);
+        }
+    }
+}
+
+void
+ofp_print_version(const struct ofp_header *oh,
+                  struct ds *string)
+{
+    switch (oh->version) {
+    case OFP10_VERSION:
+        break;
+    case OFP11_VERSION:
+        ds_put_cstr(string, " (OF1.1)");
+        break;
+    case OFP12_VERSION:
+        ds_put_cstr(string, " (OF1.2)");
+        break;
+    default:
+        ds_put_format(string, " (OF 0x%02"PRIx8")", oh->version);
+        break;
+    }
+    ds_put_format(string, " (xid=0x%"PRIx32"):", ntohl(oh->xid));
+}
+
 static void
 ofp_to_string__(const struct ofp_header *oh,
                 const struct ofputil_msg_type *type, struct ds *string,
@@ -1424,9 +1527,8 @@ ofp_to_string__(const struct ofp_header *oh,
     enum ofputil_msg_code code;
     const void *msg = oh;
 
-    ds_put_format(string, "%s (xid=0x%"PRIx32"):",
-                  ofputil_msg_type_name(type), ntohl(oh->xid));
-
+    ds_put_cstr(string, ofputil_msg_type_name(type));
+    ofp_print_version(oh, string);
     code = ofputil_msg_type_code(type);
     switch (code) {
     case OFPUTIL_MSG_INVALID:
@@ -1499,6 +1601,7 @@ ofp_to_string__(const struct ofp_header *oh,
         break;
 
     case OFPUTIL_OFPST_DESC_REQUEST:
+    case OFPUTIL_OFPST_PORT_DESC_REQUEST:
         ofp_print_stats_request(string, oh);
         break;
 
@@ -1555,6 +1658,11 @@ ofp_to_string__(const struct ofp_header *oh,
         ofp_print_ofpst_aggregate_reply(string, msg);
         break;
 
+    case OFPUTIL_OFPST_PORT_DESC_REPLY:
+        ofp_print_stats_reply(string, oh);
+        ofp_print_ofpst_port_desc_reply(string, oh);
+        break;
+
     case OFPUTIL_NXT_ROLE_REQUEST:
     case OFPUTIL_NXT_ROLE_REPLY:
         ofp_print_nxt_role_message(string, msg);
@@ -1587,6 +1695,22 @@ ofp_to_string__(const struct ofp_header *oh,
         ofp_print_stats_reply(string, oh);
         ofp_print_nxst_aggregate_reply(string, msg);
         break;
+
+    case OFPUTIL_NXT_FLOW_MONITOR_CANCEL:
+        ofp_print_nxt_flow_monitor_cancel(string, msg);
+        break;
+
+    case OFPUTIL_NXT_FLOW_MONITOR_PAUSED:
+    case OFPUTIL_NXT_FLOW_MONITOR_RESUMED:
+        break;
+
+    case OFPUTIL_NXST_FLOW_MONITOR_REQUEST:
+        ofp_print_nxst_flow_monitor_request(string, msg);
+        break;
+
+    case OFPUTIL_NXST_FLOW_MONITOR_REPLY:
+        ofp_print_nxst_flow_monitor_reply(string, msg);
+        break;
     }
 }