diff mbox

[ovs-dev,v3,1/2] ovn-controller: Tie OpenFlow and logical flows using OpenFlow cookie.

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

Commit Message

Ben Pfaff Dec. 20, 2016, 4:07 p.m. UTC
This makes it easy to find the logical flow that generated a particular
OpenFlow flow, by running "ovn-sbctl dump-flows <cookie>".

Later, this can be refined (and automated for "ofproto/trace"), but this
is still a significant advance.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 lib/uuid.c                   |  22 +++++++-
 lib/uuid.h                   |   3 +-
 ovn/controller/lflow.c       |   6 +-
 ovn/controller/ofctrl.c      |   6 +-
 ovn/controller/ofctrl.h      |   2 +-
 ovn/controller/physical.c    |  37 +++++++------
 ovn/utilities/ovn-sbctl.8.in |  13 ++++-
 ovn/utilities/ovn-sbctl.c    | 129 ++++++++++++++++++++++++++++++++++++-------
 8 files changed, 171 insertions(+), 47 deletions(-)
diff mbox

Patch

diff --git a/lib/uuid.c b/lib/uuid.c
index 0f2a58e..bd98d40 100644
--- a/lib/uuid.c
+++ b/lib/uuid.c
@@ -1,4 +1,4 @@ 
-/* Copyright (c) 2008, 2009, 2010, 2011, 2013 Nicira, Inc.
+/* Copyright (c) 2008, 2009, 2010, 2011, 2013, 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.
@@ -210,6 +210,26 @@  error:
     uuid_zero(uuid);
     return false;
 }
+
+/* Returns the number of characters at the beginning of 's' that are valid for
+ * a UUID.  For example, the "123" at the beginning of "123xyzzy" could begin a
+ * UUID, so uuid_is_partial_string() would return 3; for "xyzzy", this function
+ * would return 0, since "x" can't start a UUID. */
+int
+uuid_is_partial_string(const char *s)
+{
+    static const char tmpl[UUID_LEN] = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
+    size_t i;
+    for (i = 0; i < UUID_LEN; i++) {
+        if (tmpl[i] == 'x'
+            ? hexit_value(s[i]) < 0
+            : s[i] != '-') {
+            break;
+        }
+    }
+    return i;
+}
+
 
 static void
 sha1_update_int(struct sha1_ctx *sha1_ctx, uintmax_t x)
diff --git a/lib/uuid.h b/lib/uuid.h
index a5792b0..113574c 100644
--- a/lib/uuid.h
+++ b/lib/uuid.h
@@ -1,4 +1,4 @@ 
-/* Copyright (c) 2008, 2009, 2010 Nicira, Inc.
+/* Copyright (c) 2008, 2009, 2010, 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.
@@ -61,6 +61,7 @@  bool uuid_is_zero(const struct uuid *);
 int uuid_compare_3way(const struct uuid *, const struct uuid *);
 bool uuid_from_string(struct uuid *, const char *);
 bool uuid_from_string_prefix(struct uuid *, const char *);
+int uuid_is_partial_string(const char *);
 void uuid_set_bits_v4(struct uuid *);
 
 #endif /* uuid.h */
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index d913998..e3c99ad 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -266,7 +266,7 @@  consider_logical_flow(const struct lport_index *lports,
         }
         if (!m->n) {
             ofctrl_add_flow(flow_table, ptable, lflow->priority, &m->match,
-                            &ofpacts);
+                            lflow->header_.uuid.parts[0], &ofpacts);
         } else {
             uint64_t conj_stubs[64 / 8];
             struct ofpbuf conj;
@@ -282,7 +282,7 @@  consider_logical_flow(const struct lport_index *lports,
                 dst->n_clauses = src->n_clauses;
             }
             ofctrl_add_flow(flow_table, ptable, lflow->priority, &m->match,
-                            &conj);
+                            0, &conj);
             ofpbuf_uninit(&conj);
         }
     }
@@ -350,7 +350,7 @@  consider_neighbor_flow(const struct lport_index *lports,
     uint64_t stub[1024 / 8];
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
     put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, &match, 0, &ofpacts);
     ofpbuf_uninit(&ofpacts);
 }
 
diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
index 1d8fbf3..7449293 100644
--- a/ovn/controller/ofctrl.c
+++ b/ovn/controller/ofctrl.c
@@ -57,6 +57,7 @@  struct ovn_flow {
     /* Data. */
     struct ofpact *ofpacts;
     size_t ofpacts_len;
+    uint64_t cookie;
 };
 
 static uint32_t ovn_flow_hash(const struct ovn_flow *);
@@ -602,7 +603,8 @@  ofctrl_recv(const struct ofp_header *oh, enum ofptype type)
 void
 ofctrl_add_flow(struct hmap *desired_flows,
                 uint8_t table_id, uint16_t priority,
-                const struct match *match, const struct ofpbuf *actions)
+                const struct match *match, uint64_t cookie,
+                const struct ofpbuf *actions)
 {
     struct ovn_flow *f = xmalloc(sizeof *f);
     f->table_id = table_id;
@@ -611,6 +613,7 @@  ofctrl_add_flow(struct hmap *desired_flows,
     f->ofpacts = xmemdup(actions->data, actions->size);
     f->ofpacts_len = actions->size;
     f->hmap_node.hash = ovn_flow_hash(f);
+    f->cookie = cookie;
 
     if (ovn_flow_lookup(desired_flows, f)) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
@@ -935,6 +938,7 @@  ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
             .table_id = d->table_id,
             .ofpacts = d->ofpacts,
             .ofpacts_len = d->ofpacts_len,
+            .new_cookie = htonll(d->cookie),
             .command = OFPFC_ADD,
         };
         add_flow_mod(&fm, &msgs);
diff --git a/ovn/controller/ofctrl.h b/ovn/controller/ofctrl.h
index 5308b61..67d6c59 100644
--- a/ovn/controller/ofctrl.h
+++ b/ovn/controller/ofctrl.h
@@ -46,7 +46,7 @@  void ofctrl_ct_flush_zone(uint16_t zone_id);
 /* Flow table interfaces to the rest of ovn-controller. */
 void ofctrl_add_flow(struct hmap *desired_flows, uint8_t table_id,
                      uint16_t priority, const struct match *,
-                     const struct ofpbuf *ofpacts);
+                     uint64_t cookie, const struct ofpbuf *ofpacts);
 
 void ofctrl_flow_table_clear(void);
 
diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c
index 600f35a..b7144b4 100644
--- a/ovn/controller/physical.c
+++ b/ovn/controller/physical.c
@@ -219,7 +219,7 @@  put_local_common_flows(uint32_t dp_key, uint32_t port_key,
     /* Resubmit to table 34. */
     put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
     ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
-                    &match, ofpacts_p);
+                    &match, 0, ofpacts_p);
 
     /* Table 34, Priority 100.
      * =======================
@@ -234,7 +234,7 @@  put_local_common_flows(uint32_t dp_key, uint32_t port_key,
     match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, port_key);
     match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
     ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 100,
-                    &match, ofpacts_p);
+                    &match, 0, ofpacts_p);
 
     /* Table 64, Priority 100.
      * =======================
@@ -257,7 +257,8 @@  put_local_common_flows(uint32_t dp_key, uint32_t port_key,
     put_load(0, MFF_IN_PORT, 0, 16, ofpacts_p);
     put_resubmit(OFTABLE_LOG_TO_PHY, ofpacts_p);
     put_stack(MFF_IN_PORT, ofpact_put_STACK_POP(ofpacts_p));
-    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100, &match, ofpacts_p);
+    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100,
+                    &match, 0, ofpacts_p);
 }
 
 static void
@@ -347,7 +348,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         ofpact_finish_CLONE(ofpacts_p, &clone);
 
         ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
         return;
     }
 
@@ -471,7 +472,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         /* Resubmit to first logical ingress pipeline table. */
         put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p);
         ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG,
-                        tag ? 150 : 100, &match, ofpacts_p);
+                        tag ? 150 : 100, &match, 0, ofpacts_p);
 
         if (!tag && (!strcmp(binding->type, "localnet")
                      || !strcmp(binding->type, "l2gateway"))) {
@@ -481,7 +482,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
              * action. */
             ofpbuf_pull(ofpacts_p, ofpacts_orig_size);
             match_set_dl_tci_masked(&match, 0, htons(VLAN_CFI));
-            ofctrl_add_flow(flow_table, 0, 100, &match, ofpacts_p);
+            ofctrl_add_flow(flow_table, 0, 100, &match, 0, ofpacts_p);
         }
 
         /* Table 65, Priority 100.
@@ -509,7 +510,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
             ofpact_put_STRIP_VLAN(ofpacts_p);
         }
         ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
     } else if (!tun) {
         /* Remote port connected by localnet port */
         /* Table 33, priority 100.
@@ -532,7 +533,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         /* Resubmit to table 33. */
         put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
     } else {
         /* Remote port connected by tunnel */
 
@@ -557,7 +558,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
 
         /* Resubmit to table 33. */
         put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
-        ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, &match,
+        ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, &match, 0,
                         ofpacts_p);
 
 
@@ -574,7 +575,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         /* Output to tunnel. */
         ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
         ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
     }
 }
 
@@ -662,7 +663,7 @@  consider_mc_group(enum mf_field_id mff_ovn_geneve,
         put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, ofpacts_p);
 
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
     }
 
     /* Table 32, priority 100.
@@ -700,7 +701,7 @@  consider_mc_group(enum mf_field_id mff_ovn_geneve,
                 put_resubmit(OFTABLE_LOCAL_OUTPUT, remote_ofpacts_p);
             }
             ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100,
-                            &match, remote_ofpacts_p);
+                            &match, 0, remote_ofpacts_p);
         }
     }
     sset_destroy(&remote_chassis);
@@ -900,7 +901,8 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
 
         put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
 
-        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match, &ofpacts);
+        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match, 0,
+                        &ofpacts);
     }
 
     /* Add flows for VXLAN encapsulations.  Due to the limited amount of
@@ -932,7 +934,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
             put_load(1, MFF_LOG_FLAGS, MLF_RCV_FROM_VXLAN_BIT, 1, &ofpacts);
             put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &ofpacts);
 
-            ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match,
+            ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match, 0,
                             &ofpacts);
         }
     }
@@ -946,7 +948,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
     match_init_catchall(&match);
     ofpbuf_clear(&ofpacts);
     put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, &match, 0, &ofpacts);
 
     /* Table 34, Priority 0.
      * =======================
@@ -960,7 +962,8 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
         put_load(0, MFF_REG0 + i, 0, 32, &ofpacts);
     }
     put_resubmit(OFTABLE_LOG_EGRESS_PIPELINE, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 0, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 0, &match, 0,
+                    &ofpacts);
 
     /* Table 64, Priority 0.
      * =======================
@@ -970,7 +973,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
     match_init_catchall(&match);
     ofpbuf_clear(&ofpacts);
     put_resubmit(OFTABLE_LOG_TO_PHY, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, &match, 0, &ofpacts);
 
     ofpbuf_uninit(&ofpacts);
 
diff --git a/ovn/utilities/ovn-sbctl.8.in b/ovn/utilities/ovn-sbctl.8.in
index 458a643..2f274ae 100644
--- a/ovn/utilities/ovn-sbctl.8.in
+++ b/ovn/utilities/ovn-sbctl.8.in
@@ -159,9 +159,18 @@  to unbind logical port that is not bound has no effect.
 .
 .SS "Logical Flow Commands"
 .
-.IP "\fBlflow\-list\fR [\fIlogical-datapath\fR]"
+.IP "\fBlflow\-list\fR [\fIlogical-datapath\fR] [\fIlflow\fR...]"
 List logical flows.  If \fIlogical-datapath\fR is specified, only list
-flows for that logical datapath.
+flows for that logical datapath.  The \fIlogical-datapath\fR may be
+given as a UUID or as a datapath name.
+.IP
+If at least one \fIlflow\fR is given, only matching logical flows, if
+any, are listed.  Each \fIlflow\fR may be specified as a UUID or the
+first few characters of a UUID, optionally prefixed by \fB0x\fR.
+(Because \fBovn\-controller\fR sets OpenFlow flow cookies to the first
+32 bits of the corresponding logical flow's UUID, this makes it easy
+to look up the logical flow that generated a particular OpenFlow
+flow.)
 .
 .IP "\fBdump\-flows\fR [\fIlogical-datapath\fR]"
 Alias for \fBlflow\-list\fB.
diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
index 0763b79..f51a2ed 100644
--- a/ovn/utilities/ovn-sbctl.c
+++ b/ovn/utilities/ovn-sbctl.c
@@ -306,8 +306,8 @@  Port binding commands:\n\
   lsp-unbind PORT             reset the port binding of logical port PORT\n\
 \n\
 Logical flow commands:\n\
-  lflow-list [DATAPATH]       List logical flows for all or a single datapath\n\
-  dump-flows [DATAPATH]       Alias for lflow-list\n\
+  lflow-list [DATAPATH] [LFLOW...] List logical flows for DATAPATH\n\
+  dump-flows [DATAPATH] [LFLOW...] Alias for lflow-list\n\
 \n\
 %s\
 \n\
@@ -687,53 +687,138 @@  lflow_cmp(const void *lf1_, const void *lf2_)
     return 0;
 }
 
+static char *
+parse_partial_uuid(char *s)
+{
+    /* Accept a full or partial UUID. */
+    if (uuid_is_partial_string(s) == strlen(s)) {
+        return s;
+    }
+
+    /* Accept a full or partial UUID prefixed by 0x, since "ovs-ofctl
+     * dump-flows" prints cookies prefixed by 0x. */
+    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
+        && uuid_is_partial_string(s + 2) == strlen(s + 2)) {
+        return s + 2;
+    }
+
+    /* Not a (partial) UUID. */
+    return NULL;
+}
+
+static bool
+is_partial_uuid_match(const struct uuid *uuid, const char *match)
+{
+    char uuid_s[UUID_LEN + 1];
+    snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(uuid));
+
+    return !strncmp(uuid_s, match, strlen(match));
+}
+
+static const struct sbrec_datapath_binding *
+lookup_datapath(struct ovsdb_idl *idl, const char *s)
+{
+    const struct sbrec_datapath_binding *datapath;
+
+    struct uuid uuid;
+    if (uuid_from_string(&uuid, s)) {
+        datapath = sbrec_datapath_binding_get_for_uuid(idl, &uuid);
+        if (datapath) {
+            return datapath;
+        }
+    }
+
+    SBREC_DATAPATH_BINDING_FOR_EACH (datapath, idl) {
+        const char *name = smap_get(&datapath->external_ids, "name");
+        if (name && !strcmp(name, s)) {
+            return datapath;
+        }
+    }
+
+    return NULL;
+}
+
 static void
 cmd_lflow_list(struct ctl_context *ctx)
 {
-    const char *datapath = ctx->argc == 2 ? ctx->argv[1] : NULL;
-    struct uuid datapath_uuid = { .parts = { 0, }};
-    const struct sbrec_logical_flow **lflows;
-    const struct sbrec_logical_flow *lflow;
-    size_t n_flows = 0, n_capacity = 64;
+    const struct sbrec_datapath_binding *datapath = NULL;
+    if (ctx->argc > 1) {
+        datapath = lookup_datapath(ctx->idl, ctx->argv[1]);
+        if (datapath) {
+            ctx->argc--;
+            ctx->argv++;
+        }
+    }
 
-    if (datapath && !uuid_from_string(&datapath_uuid, datapath)) {
-        VLOG_ERR("Invalid format of datapath UUID");
-        return;
+    for (size_t i = 1; i < ctx->argc; i++) {
+        char *s = parse_partial_uuid(ctx->argv[i]);
+        if (!s) {
+            ctl_fatal("%s is not a UUID or the beginning of a UUID",
+                      ctx->argv[i]);
+        }
+        ctx->argv[i] = s;
     }
 
-    lflows = xmalloc(sizeof *lflows * n_capacity);
+    const struct sbrec_logical_flow **lflows = NULL;
+    size_t n_flows = 0;
+    size_t n_capacity = 0;
+    const struct sbrec_logical_flow *lflow;
     SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->idl) {
+        if (datapath && lflow->logical_datapath != datapath) {
+            continue;
+        }
+
         if (n_flows == n_capacity) {
             lflows = x2nrealloc(lflows, &n_capacity, sizeof *lflows);
         }
         lflows[n_flows] = lflow;
         n_flows++;
     }
-
     qsort(lflows, n_flows, sizeof *lflows, lflow_cmp);
 
-    const char *cur_pipeline = "";
-    size_t i;
-    for (i = 0; i < n_flows; i++) {
+    const struct sbrec_logical_flow *prev = NULL;
+    for (size_t i = 0; i < n_flows; i++) {
         lflow = lflows[i];
-        if (datapath && !uuid_equals(&datapath_uuid,
-                                     &lflow->logical_datapath->header_.uuid)) {
+
+        /* Figure out whether to print this particular flow.  By default, we
+         * print all flows, but if any UUIDs were listed on the command line
+         * then we only print the matching ones. */
+        bool include;
+        if (ctx->argc > 1) {
+            include = false;
+            for (size_t j = 1; j < ctx->argc; j++) {
+                if (is_partial_uuid_match(&lflow->header_.uuid,
+                                          ctx->argv[j])) {
+                    include = true;
+                    break;
+                }
+            }
+        } else {
+            include = true;
+        }
+        if (!include) {
             continue;
         }
-        if (strcmp(cur_pipeline, lflow->pipeline)) {
+
+        /* Print a header line for this datapath or pipeline, if we haven't
+         * already done so. */
+        if (!prev
+            || prev->logical_datapath != lflow->logical_datapath
+            || strcmp(prev->pipeline, lflow->pipeline)) {
             printf("Datapath: \"%s\" ("UUID_FMT")  Pipeline: %s\n",
                    smap_get_def(&lflow->logical_datapath->external_ids,
                                 "name", ""),
                    UUID_ARGS(&lflow->logical_datapath->header_.uuid),
                    lflow->pipeline);
-            cur_pipeline = lflow->pipeline;
         }
 
+        /* Print the flow. */
         printf("  table=%-2" PRId64 "(%-19s), priority=%-5" PRId64
                ", match=(%s), action=(%s)\n",
                lflow->table_id,
                smap_get_def(&lflow->external_ids, "stage-name", ""),
                lflow->priority, lflow->match, lflow->actions);
+        prev = lflow;
     }
 
     free(lflows);
@@ -1039,9 +1124,11 @@  static const struct ctl_command_syntax sbctl_commands[] = {
      "--if-exists", RW},
 
     /* Logical flow commands */
-    {"lflow-list", 0, 1, "[DATAPATH]", pre_get_info, cmd_lflow_list, NULL,
+    {"lflow-list", 0, INT_MAX, "[DATAPATH] [LFLOW...]",
+     pre_get_info, cmd_lflow_list, NULL,
      "", RO},
-    {"dump-flows", 0, 1, "[DATAPATH]", pre_get_info, cmd_lflow_list, NULL,
+    {"dump-flows", 0, INT_MAX, "[DATAPATH] [LFLOW...]",
+     pre_get_info, cmd_lflow_list, NULL,
      "", RO}, /* Friendly alias for lflow-list */
 
     /* SSL commands (To Be Added). */