diff mbox

[ovs-dev,v3,2/2] ovn-trace: New --ovs option to also print OpenFlow flows.

Message ID 20161220160751.20863-3-blp@ovn.org
State Superseded
Headers show

Commit Message

Ben Pfaff Dec. 20, 2016, 4:07 p.m. UTC
Sometimes seeing the OpenFlow flows that back a given logical flow can
provide additional insight.  This commit adds a new --ovs option to
ovn-trace that makes it connect to Open vSwitch over OpenFlow and retrieve
and print the OpenFlow flows behind each logical flow encountered during
a trace.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 NEWS                          |   4 +-
 include/openvswitch/vconn.h   |  15 +++--
 lib/vconn.c                   | 121 ++++++++++++++++++++++++++++++++-
 ovn/utilities/ovn-trace.8.xml |  61 ++++++++++++++++-
 ovn/utilities/ovn-trace.c     |  91 ++++++++++++++++++++++++-
 utilities/ovs-ofctl.c         | 152 +++++++++---------------------------------
 6 files changed, 315 insertions(+), 129 deletions(-)
diff mbox

Patch

diff --git a/NEWS b/NEWS
index 3a08dbc..026f18f 100644
--- a/NEWS
+++ b/NEWS
@@ -7,7 +7,9 @@  Post-v2.6.0
      * DSCP marking is now supported, via the new northbound QoS table.
      * IPAM now supports fixed MAC addresses.
      * Support for source IP address based routing.
-     * ovn-trace can now trace put_dhcp_opts and put_dhcp_optsv6 actions.
+     * ovn-trace:
+       - New --ovs option to also print OpenFlow flows.
+       - put_dhcp_opts and put_dhcp_optsv6 actions may now be traced.
    - Fixed regression in table stats maintenance introduced in OVS
      2.3.0, wherein the number of OpenFlow table hits and misses was
      not accurate.
diff --git a/include/openvswitch/vconn.h b/include/openvswitch/vconn.h
index d2fbd16..acdc79c 100644
--- a/include/openvswitch/vconn.h
+++ b/include/openvswitch/vconn.h
@@ -1,5 +1,5 @@ 
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,9 +18,10 @@ 
 #define OPENVSWITCH_VCONN_H 1
 
 #include <stdbool.h>
-#include <openvswitch/list.h>
-#include <openvswitch/types.h>
-#include <openflow/openflow.h>
+#include "openvswitch/list.h"
+#include "openvswitch/types.h"
+#include "openvswitch/ofp-util.h"
+#include "openflow/openflow.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -31,6 +32,8 @@  struct pvconn;
 struct pvconn_class;
 struct vconn;
 struct vconn_class;
+struct ofputil_flow_stats;
+struct ofputil_flow_stats_request;
 
 void vconn_usage(bool active, bool passive, bool bootstrap);
 
@@ -56,6 +59,10 @@  int vconn_transact_noreply(struct vconn *, struct ofpbuf *, struct ofpbuf **);
 int vconn_transact_multiple_noreply(struct vconn *, struct ovs_list *requests,
                                     struct ofpbuf **replyp);
 
+int vconn_dump_flows(struct vconn *, const struct ofputil_flow_stats_request *,
+                     enum ofputil_protocol,
+                     struct ofputil_flow_stats **fsesp, size_t *n_fsesp);
+
 /* Bundle errors must be free()d by the caller. */
 struct vconn_bundle_error {
     struct ovs_list list_node;
diff --git a/lib/vconn.c b/lib/vconn.c
index d5a17f6..57cf429 100644
--- a/lib/vconn.c
+++ b/lib/vconn.c
@@ -1,5 +1,5 @@ 
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2008-2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -943,6 +943,125 @@  vconn_transact_multiple_noreply(struct vconn *vconn, struct ovs_list *requests,
     return 0;
 }
 
+static int
+recv_flow_stats_reply(struct vconn *vconn, ovs_be32 send_xid,
+                      struct ofpbuf **replyp,
+                      struct ofputil_flow_stats *fs, struct ofpbuf *ofpacts)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+    struct ofpbuf *reply = *replyp;
+
+    for (;;) {
+        int retval;
+        bool more;
+
+        /* Get a flow stats reply message, if we don't already have one. */
+        if (!reply) {
+            enum ofptype type;
+            enum ofperr error;
+
+            do {
+                error = vconn_recv_block(vconn, &reply);
+                if (error) {
+                    return error;
+                }
+            } while (((struct ofp_header *) reply->data)->xid != send_xid);
+
+            error = ofptype_decode(&type, reply->data);
+            if (error || type != OFPTYPE_FLOW_STATS_REPLY) {
+                VLOG_WARN_RL(&rl, "received bad reply: %s",
+                             ofp_to_string(reply->data, reply->size, 1));
+                return EPROTO;
+            }
+        }
+
+        /* Pull an individual flow stats reply out of the message. */
+        retval = ofputil_decode_flow_stats_reply(fs, reply, false, ofpacts);
+        switch (retval) {
+        case 0:
+            *replyp = reply;
+            return 0;
+
+        case EOF:
+            more = ofpmp_more(reply->header);
+            ofpbuf_delete(reply);
+            reply = NULL;
+            if (!more) {
+                *replyp = NULL;
+                return EOF;
+            }
+            break;
+
+        default:
+            VLOG_WARN_RL(&rl, "parse error in reply (%s)",
+                         ofperr_to_string(retval));
+            return EPROTO;
+        }
+    }
+}
+
+/* Send 'request' to 'vconn', encoding it with the given 'protocol', and then
+ * waits for, parses, and accumulates all of the replies into '*fsesp' and
+ * '*n_fsesp'.  The caller is responsible for freeing all of the flows.
+ * Returns 0 if successful, otherwise a positive errno value.  Always frees
+ * 'request'. */
+int
+vconn_dump_flows(struct vconn *vconn,
+                 const struct ofputil_flow_stats_request *fsr,
+                 enum ofputil_protocol protocol,
+                 struct ofputil_flow_stats **fsesp, size_t *n_fsesp)
+{
+    struct ofputil_flow_stats *fses = NULL;
+    size_t n_fses = 0;
+    size_t allocated_fses = 0;
+
+    struct ofpbuf *request = ofputil_encode_flow_stats_request(fsr, protocol);
+    const struct ofp_header *oh = request->data;
+    ovs_be32 send_xid = oh->xid;
+    int error = vconn_send_block(vconn, request);
+    if (error) {
+        goto exit;
+    }
+
+    struct ofpbuf *reply = NULL;
+    struct ofpbuf ofpacts;
+    ofpbuf_init(&ofpacts, 0);
+    for (;;) {
+        if (n_fses >= allocated_fses) {
+            fses = x2nrealloc(fses, &allocated_fses, sizeof *fses);
+        }
+
+        struct ofputil_flow_stats *fs = &fses[n_fses];
+        error = recv_flow_stats_reply(vconn, send_xid, &reply, fs, &ofpacts);
+        if (error) {
+            if (error == EOF) {
+                error = 0;
+            }
+            break;
+        }
+        fs->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
+        n_fses++;
+    }
+    ofpbuf_uninit(&ofpacts);
+    ofpbuf_delete(reply);
+
+    if (error) {
+        for (size_t i = 0; i < n_fses; i++) {
+            free(CONST_CAST(struct ofpact *, fses[i].ofpacts));
+        }
+        free(fses);
+
+        fses = NULL;
+        n_fses = 0;
+    }
+
+exit:
+    *fsesp = fses;
+    *n_fsesp = n_fses;
+    return error;
+}
+
+
 static enum ofperr
 vconn_bundle_reply_validate(struct ofpbuf *reply,
                             struct ofputil_bundle_ctrl_msg *request,
diff --git a/ovn/utilities/ovn-trace.8.xml b/ovn/utilities/ovn-trace.8.xml
index 92dcb17..00d8e4f 100644
--- a/ovn/utilities/ovn-trace.8.xml
+++ b/ovn/utilities/ovn-trace.8.xml
@@ -255,6 +255,65 @@ 
     <dd>
       Selects all three forms of output.
     </dd>
+
+    <dt><code>--ovs</code>[<code>=</code><var>remote</var>]</dt>
+    <dd>
+      <p>
+        Makes <code>ovn-trace</code> attempt to obtain and display the OpenFlow
+        flows that correspond to each OVN logical flow.  To do so,
+        <code>ovn-trace</code> connects to <var>remote</var> (by default,
+        <code>unix:@RUNDIR@/br-int.mgmt</code>) over OpenFlow and retrieves the
+        flows.  If <var>remote</var> is specified, it must be an active
+        OpenFlow connection method described in <code>ovs-ofctl</code>(8).
+      </p>
+
+      <p>
+        Keep in mind a few important aspects of the difference between logical
+        flows and OpenFlow:
+      </p>
+
+      <ul>
+        <li>
+          Each logical flow maps to one or more OpenFlow flows.  An actual
+          packet ordinarily matches only one of these, although in some cases
+          it can match more than one of these flows (which is not a problem
+          because all of them have the same actions).  <code>ovn-trace</code>
+          currently shows all corresponding flows.
+        </li>
+
+        <li>
+          Some logical flows can map to the Open vSwitch ``conjunctive match''
+          extension (see <code>ovs-ofctl</code>(8)).  The OpenFlow match for a
+          conjunctive match shows a match on <code>conj_id</code>.  Currently
+          <code>ovn-trace</code> cannot display the flows with
+          <code>conjunction</code> actions that effectively produce the
+          <code>conj_id</code> match.
+        </li>
+
+        <li>
+          Some logical flows may not be represented in the OpenFlow tables on a
+          given hypervisor, if they could not be used on that hypervisor.  For
+          example, if no VIF in a logical switch resides on a given hypervisor,
+          and the logical switch is not otherwise reachable on that hypervisor
+          (e.g. over a series of hops through logical switches and routers
+          starting from a VIF on the hypervisor), then the logical flow may not
+          be represented there.
+        </li>
+
+        <li>
+          Some OpenFlow flows do not correspond to logical flows, such as
+          OpenFlow flows that map between physical and logical ports.  These
+          flows will never show up in a trace.  See ``Architectural Physical
+          Life Cycle of a Packet'' in <code>ovn-architecture</code>(7) for
+          information on these flows.
+        </li>
+
+        <li>
+          When <code>ovn-trace</code> omits uninteresting logical flows from
+          output, it does not look up the corresponding OpenFlow flows.
+        </li>
+      </ul>
+    </dd>
   </dl>
 
   <h2>Daemon Options</h2>
@@ -266,7 +325,7 @@ 
   <h2>PKI Options</h2>
   <p>
     PKI configuration is required to use SSL for the connection to the
-    database.
+    database (and the switch, if <code>--ovs</code> is specified).
   </p>
   <xi:include href="lib/ssl.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
 
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index 71798f8..6e971bc 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -27,6 +27,8 @@ 
 #include "nx-match.h"
 #include "openvswitch/dynamic-string.h"
 #include "openvswitch/ofp-actions.h"
+#include "openvswitch/ofp-print.h"
+#include "openvswitch/vconn.h"
 #include "openvswitch/vlog.h"
 #include "ovn/actions.h"
 #include "ovn/expr.h"
@@ -63,6 +65,10 @@  static bool summary;
 /* --minimal: Show a trace with only minimal information. */
 static bool minimal;
 
+/* --ovs: OVS instance to contact to get OpenFlow flows. */
+static const char *ovs;
+static struct vconn *vconn;
+
 OVS_NO_RETURN static void usage(void);
 static void parse_options(int argc, char *argv[]);
 static char *trace(const char *datapath, const char *flow);
@@ -143,6 +149,12 @@  main(int argc, char *argv[])
     }
 }
 
+static char *
+default_ovs(void)
+{
+    return xasprintf("unix:%s/br-int.mgmt", ovs_rundir());
+}
+
 static void
 parse_options(int argc, char *argv[])
 {
@@ -153,6 +165,7 @@  parse_options(int argc, char *argv[])
         OPT_SUMMARY,
         OPT_MINIMAL,
         OPT_ALL,
+        OPT_OVS,
         DAEMON_OPTION_ENUMS,
         SSL_OPTION_ENUMS,
         VLOG_OPTION_ENUMS
@@ -164,6 +177,7 @@  parse_options(int argc, char *argv[])
         {"summary", no_argument, NULL, OPT_SUMMARY},
         {"minimal", no_argument, NULL, OPT_MINIMAL},
         {"all", no_argument, NULL, OPT_ALL},
+        {"ovs", optional_argument, NULL, OPT_OVS},
         {"help", no_argument, NULL, 'h'},
         {"version", no_argument, NULL, 'V'},
         DAEMON_LONG_OPTIONS,
@@ -207,6 +221,10 @@  parse_options(int argc, char *argv[])
             detailed = summary = minimal = true;
             break;
 
+        case OPT_OVS:
+            ovs = optarg ? optarg : default_ovs();
+            break;
+
         case 'h':
             usage();
 
@@ -245,7 +263,7 @@  usage(void)
 usage: %s [OPTIONS] DATAPATH MICROFLOW\n\
        %s [OPTIONS] --detach\n\
 \n\
-Option format options:\n\
+Output format options:\n\
   --detailed              table-by-table \"backtrace\" (default)\n\
   --summary               less detailed, more parseable\n\
   --minimal               minimum to explain externally visible behavior\n\
@@ -257,11 +275,14 @@  Option format options:\n\
 Other options:\n\
   --db=DATABASE           connect to DATABASE\n\
                           (default: %s)\n\
+  --ovs[=REMOTE]          obtain corresponding OpenFlow flows from REMOTE\n\
+                          (default: %s)\n\
   --unixctl=SOCKET        set control socket name\n\
   -h, --help              display this help message\n\
   -V, --version           display version information\n",
-           default_sb_db());
+           default_sb_db(), default_ovs());
     stream_usage("database", true, true, false);
+    vconn_usage(true, false, false);
     exit(EXIT_SUCCESS);
 }
 
@@ -303,6 +324,7 @@  struct ovntrace_mcgroup {
 enum ovntrace_pipeline { P_INGRESS, P_EGRESS };
 
 struct ovntrace_flow {
+    struct uuid uuid;
     enum ovntrace_pipeline pipeline;
     int table_id;
     char *stage_name;
@@ -644,6 +666,7 @@  read_flows(void)
         }
 
         struct ovntrace_flow *flow = xzalloc(sizeof *flow);
+        flow->uuid = sblf->header_.uuid;
         flow->pipeline = (!strcmp(sblf->pipeline, "ingress")
                           ? P_INGRESS
                           : P_EGRESS);
@@ -1394,6 +1417,54 @@  may_omit_stage(const struct ovntrace_flow *f, uint8_t table_id)
 }
 
 static void
+trace_openflow(const struct ovntrace_flow *f, struct ovs_list *super)
+{
+    struct ofputil_flow_stats_request fsr = {
+        .cookie = htonll(f->uuid.parts[0]),
+        .cookie_mask = OVS_BE64_MAX,
+        .out_port = OFPP_ANY,
+        .out_group = OFPG_ANY,
+        .table_id = OFPTT_ALL,
+    };
+
+    struct ofputil_flow_stats *fses;
+    size_t n_fses;
+    int error = vconn_dump_flows(vconn, &fsr, OFPUTIL_P_OF13_OXM,
+                                 &fses, &n_fses);
+    if (error) {
+        ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
+                             "*** error obtaining flow stats (%s)",
+                             ovs_strerror(error));
+        VLOG_WARN("%s: error obtaining flow stats (%s)",
+                  ovs, ovs_strerror(error));
+        return;
+    }
+
+    if (n_fses) {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        for (size_t i = 0; i < n_fses; i++) {
+            ds_clear(&s);
+            ofp_print_flow_stats(&s, &fses[i]);
+
+            /* ofp_print_flow_stats() indents its output with a space.
+             * Omit it. */
+            const char *p = ds_cstr(&s);
+            p += strspn(p, " ");
+            ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "%s", p);
+        }
+        ds_destroy(&s);
+    } else {
+        ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
+                             "*** no OpenFlow flows");
+    }
+
+    for (size_t i = 0; i < n_fses; i++) {
+        free(CONST_CAST(struct ofpact *, fses[i].ofpacts));
+    }
+    free(fses);
+}
+
+static void
 trace__(const struct ovntrace_datapath *dp, struct flow *uflow,
         uint8_t table_id, enum ovntrace_pipeline pipeline,
         struct ovs_list *super)
@@ -1417,7 +1488,8 @@  trace__(const struct ovntrace_datapath *dp, struct flow *uflow,
         } else if (f->source) {
             ds_put_format(&s, "(%s): ", f->source);
         }
-        ds_put_format(&s, "%s, priority %d", f->match_s, f->priority);
+        ds_put_format(&s, "%s, priority %d, uuid %08x",
+                      f->match_s, f->priority, f->uuid.parts[0]);
     } else {
         char *stage_name = ovntrace_stage_name(dp, table_id, pipeline);
         ds_put_format(&s, "%s%sno match (implicit drop)",
@@ -1430,6 +1502,9 @@  trace__(const struct ovntrace_datapath *dp, struct flow *uflow,
     ds_destroy(&s);
 
     if (f) {
+        if (vconn) {
+            trace_openflow(f, &node->subs);
+        }
         trace_actions(f->ovnacts, f->ovnacts_len, dp, uflow, table_id,
                       pipeline, &node->subs);
     }
@@ -1463,6 +1538,13 @@  trace(const char *dp_s, const char *flow_s)
     flow_format(&output, &uflow);
     ds_put_char(&output, '\n');
 
+    if (ovs) {
+        int retval = vconn_open_block(ovs, 1 << OFP13_VERSION, 0, &vconn);
+        if (retval) {
+            VLOG_WARN("%s: connection failed (%s)", ovs, ovs_strerror(retval));
+        }
+    }
+
     struct ovs_list root = OVS_LIST_INITIALIZER(&root);
     struct ovntrace_node *node = ovntrace_node_append(
         &root, OVNTRACE_NODE_PIPELINE, "ingress(dp=\"%s\", inport=\"%s\")",
@@ -1494,6 +1576,9 @@  trace(const char *dp_s, const char *flow_s)
         ovntrace_node_prune_hard(&root);
         ovntrace_node_print_summary(&output, &root, 0);
     }
+
+    vconn_close(vconn);
+
     return ds_steal_cstr(&output);
 }
 
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 7f5bb61..061b42a 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -129,10 +129,6 @@  static const struct ovs_cmdl_command *get_all_commands(void);
 OVS_NO_RETURN static void usage(void);
 static void parse_options(int argc, char *argv[]);
 
-static bool recv_flow_stats_reply(struct vconn *, ovs_be32 send_xid,
-                                  struct ofpbuf **replyp,
-                                  struct ofputil_flow_stats *,
-                                  struct ofpbuf *ofpacts);
 int
 main(int argc, char *argv[])
 {
@@ -1166,14 +1162,14 @@  set_protocol_for_flow_dump(struct vconn *vconn,
 
 static struct vconn *
 prepare_dump_flows(int argc, char *argv[], bool aggregate,
-                   struct ofpbuf **requestp)
+                   struct ofputil_flow_stats_request *fsr,
+                   enum ofputil_protocol *protocolp)
 {
     enum ofputil_protocol usable_protocols, protocol;
-    struct ofputil_flow_stats_request fsr;
     struct vconn *vconn;
     char *error;
 
-    error = parse_ofp_flow_stats_request_str(&fsr, aggregate,
+    error = parse_ofp_flow_stats_request_str(fsr, aggregate,
                                              argc > 2 ? argv[2] : "",
                                              &usable_protocols);
     if (error) {
@@ -1181,19 +1177,19 @@  prepare_dump_flows(int argc, char *argv[], bool aggregate,
     }
 
     protocol = open_vconn(argv[1], &vconn);
-    protocol = set_protocol_for_flow_dump(vconn, protocol, usable_protocols);
-    *requestp = ofputil_encode_flow_stats_request(&fsr, protocol);
+    *protocolp = set_protocol_for_flow_dump(vconn, protocol, usable_protocols);
     return vconn;
 }
 
 static void
 ofctl_dump_flows__(int argc, char *argv[], bool aggregate)
 {
-    struct ofpbuf *request;
+    struct ofputil_flow_stats_request fsr;
+    enum ofputil_protocol protocol;
     struct vconn *vconn;
 
-    vconn = prepare_dump_flows(argc, argv, aggregate, &request);
-    dump_transaction(vconn, request);
+    vconn = prepare_dump_flows(argc, argv, aggregate, &fsr, &protocol);
+    dump_transaction(vconn, ofputil_encode_flow_stats_request(&fsr, protocol));
     vconn_close(vconn);
 }
 
@@ -1270,52 +1266,29 @@  ofctl_dump_flows(struct ovs_cmdl_context *ctx)
         ofctl_dump_flows__(ctx->argc, ctx->argv, false);
         return;
     } else {
-        struct ofputil_flow_stats *fses;
-        size_t n_fses, allocated_fses;
-        struct ofpbuf *request;
-        struct ofpbuf ofpacts;
-        struct ofpbuf *reply;
+        struct ofputil_flow_stats_request fsr;
+        enum ofputil_protocol protocol;
         struct vconn *vconn;
-        ovs_be32 send_xid;
-        struct ds s;
-        size_t i;
-
-        vconn = prepare_dump_flows(ctx->argc, ctx->argv, false, &request);
-        send_xid = ((struct ofp_header *) request->data)->xid;
-        send_openflow_buffer(vconn, request);
-
-        fses = NULL;
-        n_fses = allocated_fses = 0;
-        reply = NULL;
-        ofpbuf_init(&ofpacts, 0);
-        for (;;) {
-            struct ofputil_flow_stats *fs;
 
-            if (n_fses >= allocated_fses) {
-                fses = x2nrealloc(fses, &allocated_fses, sizeof *fses);
-            }
+        vconn = prepare_dump_flows(ctx->argc, ctx->argv, false,
+                                   &fsr, &protocol);
 
-            fs = &fses[n_fses];
-            if (!recv_flow_stats_reply(vconn, send_xid, &reply, fs,
-                                       &ofpacts)) {
-                break;
-            }
-            fs->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
-            n_fses++;
-        }
-        ofpbuf_uninit(&ofpacts);
+        struct ofputil_flow_stats *fses;
+        size_t n_fses;
+        run(vconn_dump_flows(vconn, &fsr, protocol, &fses, &n_fses),
+            "dump flows");
 
         qsort(fses, n_fses, sizeof *fses, compare_flows);
 
-        ds_init(&s);
-        for (i = 0; i < n_fses; i++) {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        for (size_t i = 0; i < n_fses; i++) {
             ds_clear(&s);
             ofp_print_flow_stats(&s, &fses[i]);
             puts(ds_cstr(&s));
         }
         ds_destroy(&s);
 
-        for (i = 0; i < n_fses; i++) {
+        for (size_t i = 0; i < n_fses; i++) {
             free(CONST_CAST(struct ofpact *, fses[i].ofpacts));
         }
         free(fses);
@@ -3365,59 +3338,6 @@  read_flows_from_file(const char *filename, struct fte_state *state, int index)
     return usable_protocols;
 }
 
-static bool
-recv_flow_stats_reply(struct vconn *vconn, ovs_be32 send_xid,
-                      struct ofpbuf **replyp,
-                      struct ofputil_flow_stats *fs, struct ofpbuf *ofpacts)
-{
-    struct ofpbuf *reply = *replyp;
-
-    for (;;) {
-        int retval;
-        bool more;
-
-        /* Get a flow stats reply message, if we don't already have one. */
-        if (!reply) {
-            enum ofptype type;
-            enum ofperr error;
-
-            do {
-                run(vconn_recv_block(vconn, &reply),
-                    "OpenFlow packet receive failed");
-            } while (((struct ofp_header *) reply->data)->xid != send_xid);
-
-            error = ofptype_decode(&type, reply->data);
-            if (error || type != OFPTYPE_FLOW_STATS_REPLY) {
-                ovs_fatal(0, "received bad reply: %s",
-                          ofp_to_string(reply->data, reply->size,
-                                        verbosity + 1));
-            }
-        }
-
-        /* Pull an individual flow stats reply out of the message. */
-        retval = ofputil_decode_flow_stats_reply(fs, reply, false, ofpacts);
-        switch (retval) {
-        case 0:
-            *replyp = reply;
-            return true;
-
-        case EOF:
-            more = ofpmp_more(reply->header);
-            ofpbuf_delete(reply);
-            reply = NULL;
-            if (!more) {
-                *replyp = NULL;
-                return false;
-            }
-            break;
-
-        default:
-            ovs_fatal(0, "parse error in reply (%s)",
-                      ofperr_to_string(retval));
-        }
-    }
-}
-
 /* Reads the OpenFlow flow table from 'vconn', which has currently active flow
  * format 'protocol', and adds them as flow table entries in 'tables' for the
  * version with the specified 'index'. */
@@ -3427,11 +3347,6 @@  read_flows_from_switch(struct vconn *vconn,
                        struct fte_state *state, int index)
 {
     struct ofputil_flow_stats_request fsr;
-    struct ofputil_flow_stats fs;
-    struct ofpbuf *request;
-    struct ofpbuf ofpacts;
-    struct ofpbuf *reply;
-    ovs_be32 send_xid;
 
     fsr.aggregate = false;
     match_init_catchall(&fsr.match);
@@ -3439,28 +3354,27 @@  read_flows_from_switch(struct vconn *vconn,
     fsr.out_group = OFPG_ANY;
     fsr.table_id = 0xff;
     fsr.cookie = fsr.cookie_mask = htonll(0);
-    request = ofputil_encode_flow_stats_request(&fsr, protocol);
-    send_xid = ((struct ofp_header *) request->data)->xid;
-    send_openflow_buffer(vconn, request);
 
-    reply = NULL;
-    ofpbuf_init(&ofpacts, 0);
-    while (recv_flow_stats_reply(vconn, send_xid, &reply, &fs, &ofpacts)) {
+    struct ofputil_flow_stats *fses;
+    size_t n_fses;
+    run(vconn_dump_flows(vconn, &fsr, protocol, &fses, &n_fses),
+        "dump flows");
+    for (size_t i = 0; i < n_fses; i++) {
+        const struct ofputil_flow_stats *fs = &fses[i];
         struct fte_version *version;
 
         version = xmalloc(sizeof *version);
-        version->cookie = fs.cookie;
-        version->idle_timeout = fs.idle_timeout;
-        version->hard_timeout = fs.hard_timeout;
-        version->importance = fs.importance;
+        version->cookie = fs->cookie;
+        version->idle_timeout = fs->idle_timeout;
+        version->hard_timeout = fs->hard_timeout;
+        version->importance = fs->importance;
         version->flags = 0;
-        version->ofpacts_len = fs.ofpacts_len;
-        version->ofpacts = xmemdup(fs.ofpacts, fs.ofpacts_len);
-        version->table_id = fs.table_id;
+        version->ofpacts_len = fs->ofpacts_len;
+        version->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
+        version->table_id = fs->table_id;
 
-        fte_queue(state, &fs.match, fs.priority, version, index);
+        fte_queue(state, &fs->match, fs->priority, version, index);
     }
-    ofpbuf_uninit(&ofpacts);
 }
 
 static void