Introduce an API in dpif-offload to dump all offloaded flows.
This API centralizes flow retrieval and is intended to
eventually replace the existing netdev-offload flow dump
mechanism.

Signed-off-by: Eelco Chaudron <[email protected]>
---
 lib/dpctl.c                 |   8 +-
 lib/dpif-netdev.c           |   7 +-
 lib/dpif-netlink.c          |   9 +-
 lib/dpif-offload-provider.h |  72 ++++++++++++++
 lib/dpif-offload-tc.c       |  48 +++++++++
 lib/dpif-offload.c          | 188 ++++++++++++++++++++++++++++++++++++
 lib/dpif-provider.h         |  35 ++++++-
 lib/dpif.c                  |  26 ++++-
 lib/dpif.h                  |   2 +-
 9 files changed, 373 insertions(+), 22 deletions(-)

diff --git a/lib/dpctl.c b/lib/dpctl.c
index 2a700f24a..1411c5a31 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -964,10 +964,10 @@ determine_dpif_flow_dump_types(struct dump_types 
*dump_types,
                                struct dpif_flow_dump_types *dpif_dump_types)
 {
     dpif_dump_types->ovs_flows = dump_types->ovs || dump_types->non_offloaded;
-    dpif_dump_types->netdev_flows = dump_types->tc || dump_types->offloaded
-                                    || dump_types->non_offloaded
-                                    || dump_types->dpdk
-                                    || dump_types->partially_offloaded;
+    dpif_dump_types->offloaded_flows = dump_types->tc || dump_types->offloaded
+                                       || dump_types->non_offloaded
+                                       || dump_types->dpdk
+                                       || dump_types->partially_offloaded;
 }
 
 static bool
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index f548438d9..710c14447 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -4482,13 +4482,12 @@ dpif_netdev_flow_dump_cast(struct dpif_flow_dump *dump)
 
 static struct dpif_flow_dump *
 dpif_netdev_flow_dump_create(const struct dpif *dpif_, bool terse,
-                             struct dpif_flow_dump_types *types OVS_UNUSED)
+                             struct dpif_flow_dump_types *types)
 {
     struct dpif_netdev_flow_dump *dump;
 
     dump = xzalloc(sizeof *dump);
-    dpif_flow_dump_init(&dump->up, dpif_);
-    dump->up.terse = terse;
+    dpif_flow_dump_init(&dump->up, dpif_, terse, types);
     ovs_mutex_init(&dump->mutex);
 
     return &dump->up;
@@ -4546,7 +4545,7 @@ dpif_netdev_flow_dump_next(struct dpif_flow_dump_thread 
*thread_,
         = dpif_netdev_flow_dump_thread_cast(thread_);
     struct dpif_netdev_flow_dump *dump = thread->dump;
     struct dp_netdev_flow *netdev_flows[FLOW_DUMP_MAX_BATCH];
-    struct dpif_netdev *dpif = dpif_netdev_cast(thread->up.dpif);
+    struct dpif_netdev *dpif = dpif_netdev_cast(thread->up.dump->dpif);
     struct dp_netdev *dp = get_dp_netdev(&dpif->dpif);
     int n_flows = 0;
     int i;
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index 17e938c9f..ff29f9427 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -1618,7 +1618,7 @@ start_netdev_dump(const struct dpif *dpif_,
 {
     ovs_mutex_init(&dump->netdev_lock);
 
-    if (!(dump->types.netdev_flows)) {
+    if (!(dump->types.offloaded_flows)) {
         dump->netdev_dumps_num = 0;
         dump->netdev_dumps = NULL;
         return;
@@ -1639,7 +1639,7 @@ dpif_netlink_populate_flow_dump_types(struct 
dpif_netlink_flow_dump *dump,
 {
     if (!types) {
         dump->types.ovs_flows = true;
-        dump->types.netdev_flows = true;
+        dump->types.offloaded_flows = true;
     } else {
         memcpy(&dump->types, types, sizeof *types);
     }
@@ -1655,7 +1655,7 @@ dpif_netlink_flow_dump_create(const struct dpif *dpif_, 
bool terse,
     struct ofpbuf *buf;
 
     dump = xmalloc(sizeof *dump);
-    dpif_flow_dump_init(&dump->up, dpif_);
+    dpif_flow_dump_init(&dump->up, dpif_, terse, types);
 
     dpif_netlink_populate_flow_dump_types(dump, types);
 
@@ -1672,7 +1672,6 @@ dpif_netlink_flow_dump_create(const struct dpif *dpif_, 
bool terse,
         ofpbuf_delete(buf);
     }
     atomic_init(&dump->status, 0);
-    dump->up.terse = terse;
 
     start_netdev_dump(dpif_, dump);
 
@@ -1882,7 +1881,7 @@ dpif_netlink_flow_dump_next(struct dpif_flow_dump_thread 
*thread_,
     struct dpif_netlink_flow_dump_thread *thread
         = dpif_netlink_flow_dump_thread_cast(thread_);
     struct dpif_netlink_flow_dump *dump = thread->dump;
-    struct dpif_netlink *dpif = dpif_netlink_cast(thread->up.dpif);
+    struct dpif_netlink *dpif = dpif_netlink_cast(thread->up.dump->dpif);
     int n_flows;
 
     ofpbuf_delete(thread->nl_actions);
diff --git a/lib/dpif-offload-provider.h b/lib/dpif-offload-provider.h
index 53754cbda..8d4d8eaaf 100644
--- a/lib/dpif-offload-provider.h
+++ b/lib/dpif-offload-provider.h
@@ -59,6 +59,32 @@ struct dpif_offload {
 };
 
 
+struct dpif_offload_flow_dump {
+    struct dpif_offload *offload;
+    bool terse;
+};
+
+static inline void
+dpif_offload_flow_dump_init(struct dpif_offload_flow_dump *dump,
+                            const struct dpif_offload *offload, bool terse)
+{
+    dump->offload = CONST_CAST(struct dpif_offload *, offload);
+    dump->terse = terse;
+}
+
+struct dpif_offload_flow_dump_thread {
+    struct dpif_offload_flow_dump *dump;
+};
+
+static inline void
+dpif_offload_flow_dump_thread_init(
+    struct dpif_offload_flow_dump_thread *thread,
+    struct dpif_offload_flow_dump *dump)
+{
+    thread->dump = dump;
+}
+
+
 struct dpif_offload_class {
     /* Type of DPIF offload provider in this class, e.g., "tc", "rte_flow",
      * "dummy", etc. */
@@ -131,6 +157,44 @@ struct dpif_offload_class {
      * successful, otherwise returns a positive errno value. */
     int (*flow_flush)(const struct dpif_offload *);
 
+    /* Flow Dumping Interface for dpif-offload.
+     *
+     * This interface mirrors the flow dumping interface found in the dpif
+     * layer.  For a thorough understanding of the design and expectations,
+     * please refer to the documentation in:
+     *   - include/openvswitch/dpif.h
+     *   - include/openvswitch/dpif-provider.h
+     *
+     * The dpif-offload flow dumping interface is intended for use only when
+     * there is a clear separation between traditional dpif flows and offloaded
+     * flows handled by an offload mechanism.
+     *
+     * For example:
+     *   - The 'tc' offload provider installs flow rules via kernel tc without
+     *     creating corresponding kernel datapath (dpif) flows.  In such cases,
+     *     dumping dpif flows would not reflect the actual set of active
+     *      offloaded flows. This interface provides a way to explicitly
+     *      enumerate such offloaded flows.
+     *
+     * 'flow_dump_create' and 'flow_dump_thread_create' must always return
+     * initialized and usable data structures. Specifically, they must
+     * initialize the returned structures using dpif_offload_flow_dump_init()
+     * and dpif_offload_flow_dump_thread_init(), respectively, and defer any
+     * error reporting until flow_dump_destroy() is called. */
+    struct dpif_offload_flow_dump *(*flow_dump_create)(
+        const struct dpif_offload *, bool terse);
+
+    int (*flow_dump_next)(struct dpif_offload_flow_dump_thread *,
+                          struct dpif_flow *, int max_flows);
+
+    int (*flow_dump_destroy)(struct dpif_offload_flow_dump *);
+
+    struct dpif_offload_flow_dump_thread *(*flow_dump_thread_create)(
+        struct dpif_offload_flow_dump *);
+
+    void (*flow_dump_thread_destroy)(
+        struct dpif_offload_flow_dump_thread *);
+
     /* Returns the number of flows offloaded by the offload provider. */
     uint64_t (*flow_get_n_offloaded)(const struct dpif_offload *);
 
@@ -231,6 +295,14 @@ void dpif_offload_port_del(struct dpif *, odp_port_t);
 void dpif_offload_port_set_config(struct dpif *, odp_port_t,
                                   const struct smap *cfg);
 void dpif_offload_set_netdev_offload(struct netdev *, struct dpif_offload *);
+void dpif_offload_flow_dump_create(struct dpif_flow_dump *,
+                                   const struct dpif *, bool terse);
+int dpif_offload_flow_dump_destroy(struct dpif_flow_dump *);
+int dpif_offload_flow_dump_next(struct dpif_flow_dump_thread *,
+                                struct dpif_flow *, int max_flows);
+void dpif_offload_flow_dump_thread_create(struct dpif_flow_dump_thread *,
+                                          struct dpif_flow_dump *);
+void dpif_offload_flow_dump_thread_destroy(struct dpif_flow_dump_thread *);
 
 static inline void dpif_offload_assert_class(
     const struct dpif_offload *dpif_offload,
diff --git a/lib/dpif-offload-tc.c b/lib/dpif-offload-tc.c
index 4ac6f359a..a0e6194a9 100644
--- a/lib/dpif-offload-tc.c
+++ b/lib/dpif-offload-tc.c
@@ -279,6 +279,49 @@ dpif_offload_tc_flow_flush(const struct dpif_offload 
*offload)
     return err;
 }
 
+static struct dpif_offload_flow_dump *
+dpif_offload_tc_flow_dump_create(const struct dpif_offload *offload,
+                                 bool terse)
+{
+    struct dpif_offload_flow_dump *dump;
+
+    dump = xmalloc(sizeof *dump);
+    dpif_offload_flow_dump_init(dump, offload, terse);
+    return dump;
+}
+
+static int
+dpif_offload_tc_flow_dump_next(struct dpif_offload_flow_dump_thread *thread,
+                               struct dpif_flow *flows, int max_flows)
+{
+    ovs_assert(thread && flows && max_flows);
+    return 0;
+}
+
+static int
+dpif_offload_tc_flow_dump_destroy(struct dpif_offload_flow_dump *dump)
+{
+    free(dump);
+    return 0;
+}
+
+static struct dpif_offload_flow_dump_thread *
+dpif_offload_tc_flow_dump_thread_create(struct dpif_offload_flow_dump *dump)
+{
+    struct dpif_offload_flow_dump_thread *thread;
+
+    thread = xmalloc(sizeof *thread);
+    dpif_offload_flow_dump_thread_init(thread, dump);
+    return thread;
+}
+
+static void
+dpif_offload_tc_flow_dump_thread_destroy(
+    struct dpif_offload_flow_dump_thread *thread)
+{
+    free(thread);
+}
+
 struct dpif_offload_class dpif_offload_tc_class = {
     .type = "tc",
     .supported_dpif_types = (const char *const[]) {
@@ -292,6 +335,11 @@ struct dpif_offload_class dpif_offload_tc_class = {
     .port_add = dpif_offload_tc_port_add,
     .port_del = dpif_offload_tc_port_del,
     .flow_flush = dpif_offload_tc_flow_flush,
+    .flow_dump_create = dpif_offload_tc_flow_dump_create,
+    .flow_dump_next = dpif_offload_tc_flow_dump_next,
+    .flow_dump_destroy = dpif_offload_tc_flow_dump_destroy,
+    .flow_dump_thread_create = dpif_offload_tc_flow_dump_thread_create,
+    .flow_dump_thread_destroy = dpif_offload_tc_flow_dump_thread_destroy,
     .flow_get_n_offloaded = dpif_offload_tc_flow_get_n_offloaded,
     .meter_set = dpif_offload_tc_meter_set,
     .meter_get = dpif_offload_tc_meter_get,
diff --git a/lib/dpif-offload.c b/lib/dpif-offload.c
index 786c1d0d1..93be302d7 100644
--- a/lib/dpif-offload.c
+++ b/lib/dpif-offload.c
@@ -153,6 +153,17 @@ dp_offload_initialize(void)
                    && base_dpif_offload_classes[i]->port_add
                    && base_dpif_offload_classes[i]->port_del);
 
+        ovs_assert((base_dpif_offload_classes[i]->flow_dump_create &&
+                    base_dpif_offload_classes[i]->flow_dump_next &&
+                    base_dpif_offload_classes[i]->flow_dump_destroy &&
+                    base_dpif_offload_classes[i]->flow_dump_thread_create &&
+                    base_dpif_offload_classes[i]->flow_dump_thread_destroy) ||
+                   (!base_dpif_offload_classes[i]->flow_dump_create &&
+                    !base_dpif_offload_classes[i]->flow_dump_next &&
+                    !base_dpif_offload_classes[i]->flow_dump_destroy &&
+                    !base_dpif_offload_classes[i]->flow_dump_thread_create &&
+                    !base_dpif_offload_classes[i]->flow_dump_thread_destroy));
+
         dpif_offload_register_provider(base_dpif_offload_classes[i]);
     }
     ovsthread_once_done(&once);
@@ -820,6 +831,183 @@ dpif_offload_meter_del(const struct dpif *dpif, 
ofproto_meter_id meter_id,
     }
 }
 
+/*
+ * Further initializes a 'struct dpif_flow_dump' that was already initialized
+ * by dpif_flow_dump_create(), preparing it for use by
+ * dpif_offload_flow_dump_next().
+ *
+ * For more details, see the documentation of dpif_flow_dump_create(). */
+void
+dpif_offload_flow_dump_create(struct dpif_flow_dump *dump,
+                              const struct dpif *dpif, bool terse)
+{
+    struct dp_offload *dp_offload = dpif_offload_get_dp_offload(dpif);
+    const struct dpif_offload *offload;
+    size_t n_providers = 0;
+    int i = 0;
+
+    if (!dump || !dpif_offload_is_offload_enabled() || !dp_offload) {
+        return;
+    }
+
+    LIST_FOR_EACH (offload, dpif_list_node, &dp_offload->offload_providers) {
+        if (offload->class->flow_dump_create) {
+            n_providers++;
+        }
+    }
+
+    if (!n_providers) {
+        return;
+    }
+
+    dump->offload_dumps = xmalloc(n_providers * sizeof(
+        struct dpif_offload_flow_dump *));
+
+    LIST_FOR_EACH (offload, dpif_list_node, &dp_offload->offload_providers) {
+        if (offload->class->flow_dump_create) {
+            dump->offload_dumps[i++] = offload->class->flow_dump_create(
+                offload, terse);
+        }
+    }
+    dump->n_offload_dumps = i;
+    dump->offload_dump_index = 0;
+}
+
+/* Destroys the 'dump' data associated with the offload flow dump.
+ * This function is called as part of the general dump cleanup
+ * by dpif_flow_dump_destroy().
+ *
+ * Returns 0 if all individual dpif-offload dump operations complete
+ * without error.  If one or more providers return an error, the error
+ * code from the first failing provider is returned as a positive errno
+ * value. */
+int
+dpif_offload_flow_dump_destroy(struct dpif_flow_dump *dump)
+{
+    int error = 0;
+
+    for (int i = 0; i < dump->n_offload_dumps; i++) {
+        struct dpif_offload_flow_dump *offload_dump = dump->offload_dumps[i];
+        const struct dpif_offload *offload = offload_dump->offload;
+        int rc = offload->class->flow_dump_destroy(offload_dump);
+
+        if (rc && rc != EOF) {
+            VLOG_ERR("Failed flow dumping on dpif-offload provider "
+                "%s, error %s", dpif_offload_name(offload),
+                ovs_strerror(rc));
+            if (!error) {
+                error = rc;
+            }
+        }
+    }
+    ovs_mutex_destroy(&dump->offload_dump_mutex);
+    free(dump->offload_dumps);
+    return error;
+}
+
+static void
+dpif_offload_advance_provider_dump(struct dpif_flow_dump_thread *thread)
+{
+    struct dpif_flow_dump *dump = thread->dump;
+
+    ovs_mutex_lock(&dump->offload_dump_mutex);
+
+    /* If we haven't finished (dumped all providers). */
+    if (dump->offload_dump_index < dump->n_offload_dumps) {
+        /* If we are the first to find that current dump is finished
+         * advance it. */
+        if (thread->offload_dump_index == dump->offload_dump_index) {
+            thread->offload_dump_index = ++dump->offload_dump_index;
+            /* Did we just finish the last dump? If so we are done. */
+            if (dump->offload_dump_index == dump->n_offload_dumps) {
+                thread->offload_dump_done = true;
+            }
+        } else {
+            /* otherwise, we are behind, catch up */
+            thread->offload_dump_index = dump->offload_dump_index;
+        }
+    } else {
+        /* Some other thread finished. */
+        thread->offload_dump_done = true;
+    }
+
+    ovs_mutex_unlock(&dump->offload_dump_mutex);
+}
+
+/* This function behaves exactly the same as dpif_flow_dump_next(),
+ * so see its documentation for details. */
+int dpif_offload_flow_dump_next(struct dpif_flow_dump_thread *thread,
+                                struct dpif_flow *flows, int max_flows)
+{
+    int n_flows = 0;
+
+    ovs_assert(max_flows > 0);
+
+    /* The logic here processes all registered offload providers and
+     * dumps all related flows. If done (i.e., it returns 0), continue
+     * with the next offload provider. */
+    while (!thread->offload_dump_done) {
+        struct dpif_offload_flow_dump_thread *offload_thread;
+
+        ovs_assert(thread->offload_dump_index < thread->n_offload_threads);
+        offload_thread = thread->offload_threads[thread->offload_dump_index];
+        n_flows = offload_thread->dump->offload->class->flow_dump_next(
+            offload_thread, flows, max_flows);
+
+        if (n_flows > 0) {
+            /* If we got some flows, we need to return due to the constraint
+             * on returned flows, as explained in dpif_flow_dump_next(). */
+            break;
+        }
+        dpif_offload_advance_provider_dump(thread);
+    }
+    return MAX(n_flows, 0);
+}
+
+/* Further initializes a 'struct dpif_flow_dump_thread' that was already
+ * initialized by dpif_flow_dump_thread_create(), preparing it for use by
+ * dpif_offload_flow_dump_next(). */
+void
+dpif_offload_flow_dump_thread_create(struct dpif_flow_dump_thread *thread,
+                                     struct dpif_flow_dump *dump)
+{
+    if (!dpif_offload_is_offload_enabled() || !dump
+        || !dump->n_offload_dumps) {
+        return;
+    }
+
+    thread->n_offload_threads = dump->n_offload_dumps;
+    thread->offload_dump_done = false;
+    thread->offload_dump_index = 0;
+    thread->offload_threads = xmalloc(thread->n_offload_threads * sizeof(
+        struct dpif_offload_flow_dump_thread *));
+
+    for (int i = 0; i < dump->n_offload_dumps; i++) {
+        struct dpif_offload_flow_dump *offload_dump = dump->offload_dumps[i];
+        const struct dpif_offload *offload = offload_dump->offload;
+
+        thread->offload_threads[i] = offload->class->flow_dump_thread_create(
+            offload_dump);
+    }
+}
+
+/* Destroys the 'thread' data associated with the offload flow dump.
+ * This function is called as part of the general thread cleanup
+ * by dpif_flow_dump_thread_destroy(). */
+void
+dpif_offload_flow_dump_thread_destroy(struct dpif_flow_dump_thread *thread)
+{
+    for (int i = 0; i < thread->n_offload_threads; i++) {
+        struct dpif_offload_flow_dump_thread *offload_thread;
+        const struct dpif_offload *offload;
+
+        offload_thread = thread->offload_threads[i];
+        offload = offload_thread->dump->offload;
+        offload->class->flow_dump_thread_destroy(offload_thread);
+    }
+    free(thread->offload_threads);
+}
+
 
 int
 dpif_offload_netdev_flush_flows(struct netdev *netdev)
diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h
index e99807782..91e7ddcfa 100644
--- a/lib/dpif-provider.h
+++ b/lib/dpif-provider.h
@@ -65,23 +65,52 @@ static inline void dpif_assert_class(const struct dpif 
*dpif,
 struct dpif_flow_dump {
     struct dpif *dpif;
     bool terse;         /* If true, key/mask/actions may be omitted. */
+
+    struct ovs_mutex offload_dump_mutex;
+    struct dpif_offload_flow_dump **offload_dumps;
+    size_t n_offload_dumps;
+    size_t offload_dump_index;
 };
 
+void dpif_offload_flow_dump_create(struct dpif_flow_dump *,
+                                   const struct dpif *, bool terse);
+void dpif_offload_flow_dump_thread_create(struct dpif_flow_dump_thread *,
+                                          struct dpif_flow_dump *);
+
 static inline void
-dpif_flow_dump_init(struct dpif_flow_dump *dump, const struct dpif *dpif)
+dpif_flow_dump_init(struct dpif_flow_dump *dump, const struct dpif *dpif,
+                    bool terse, struct dpif_flow_dump_types *types)
 {
     dump->dpif = CONST_CAST(struct dpif *, dpif);
+    dump->terse = terse;
+    dump->offload_dumps = NULL;
+    dump->n_offload_dumps = 0;
+    dump->offload_dump_index = 0;
+    ovs_mutex_init(&dump->offload_dump_mutex);
+    if (!types || types->offloaded_flows) {
+        dpif_offload_flow_dump_create(dump, dpif, terse);
+    }
 }
 
 struct dpif_flow_dump_thread {
-    struct dpif *dpif;
+    struct dpif_flow_dump *dump;
+
+    struct dpif_offload_flow_dump_thread **offload_threads;
+    size_t n_offload_threads;
+    size_t offload_dump_index;
+    bool offload_dump_done;
 };
 
 static inline void
 dpif_flow_dump_thread_init(struct dpif_flow_dump_thread *thread,
                            struct dpif_flow_dump *dump)
 {
-    thread->dpif = dump->dpif;
+    thread->dump = dump;
+    thread->offload_threads = NULL;
+    thread->n_offload_threads = 0;
+    thread->offload_dump_index = 0;
+    thread->offload_dump_done = true;
+    dpif_offload_flow_dump_thread_create(thread, dump);
 }
 
 struct ct_dpif_dump_state;
diff --git a/lib/dpif.c b/lib/dpif.c
index 9277e96cf..721f6afc7 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1110,7 +1110,15 @@ int
 dpif_flow_dump_destroy(struct dpif_flow_dump *dump)
 {
     const struct dpif *dpif = dump->dpif;
-    int error = dpif->dpif_class->flow_dump_destroy(dump);
+    int error;
+    int offload_error;
+
+    offload_error = dpif_offload_flow_dump_destroy(dump);
+    error = dpif->dpif_class->flow_dump_destroy(dump);
+
+    if (!error || error == EOF) {
+        error = offload_error;
+    }
     log_operation(dpif, "flow_dump_destroy", error);
     return error == EOF ? 0 : error;
 }
@@ -1126,7 +1134,8 @@ dpif_flow_dump_thread_create(struct dpif_flow_dump *dump)
 void
 dpif_flow_dump_thread_destroy(struct dpif_flow_dump_thread *thread)
 {
-    thread->dpif->dpif_class->flow_dump_thread_destroy(thread);
+    dpif_offload_flow_dump_thread_destroy(thread);
+    thread->dump->dpif->dpif_class->flow_dump_thread_destroy(thread);
 }
 
 /* Attempts to retrieve up to 'max_flows' more flows from 'thread'.  Returns 0
@@ -1151,11 +1160,18 @@ int
 dpif_flow_dump_next(struct dpif_flow_dump_thread *thread,
                     struct dpif_flow *flows, int max_flows)
 {
-    struct dpif *dpif = thread->dpif;
-    int n;
+    struct dpif *dpif = thread->dump->dpif;
+    int n = 0;
 
     ovs_assert(max_flows > 0);
-    n = dpif->dpif_class->flow_dump_next(thread, flows, max_flows);
+
+    if (!thread->offload_dump_done) {
+        n = dpif_offload_flow_dump_next(thread, flows, max_flows);
+    }
+    if (n == 0) {
+        n = dpif->dpif_class->flow_dump_next(thread, flows, max_flows);
+    }
+
     if (n > 0) {
         struct dpif_flow *f;
 
diff --git a/lib/dpif.h b/lib/dpif.h
index 7fd3c939c..473eacb2f 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -522,7 +522,7 @@ struct dpif_flow_attrs {
 
 struct dpif_flow_dump_types {
     bool ovs_flows;
-    bool netdev_flows;
+    bool offloaded_flows;
 };
 
 void dpif_flow_stats_extract(const struct flow *, const struct dp_packet 
*packet,
-- 
2.50.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to