dpctl: add ovs-appctl dpctl/* commands to talk to dpif-netdev
authorDaniele Di Proietto <ddiproietto@vmware.com>
Fri, 18 Jul 2014 00:26:00 +0000 (17:26 -0700)
committerBen Pfaff <blp@nicira.com>
Mon, 4 Aug 2014 21:32:13 +0000 (14:32 -0700)
This commit introduces multiple appctl commands (dpctl/*)

They are needed to interact with userspace datapaths (dpif-netdev), because the
ovs-dpctl command runs in a separate process and cannot see the userspace
datapaths inside vswitchd.

This change moves most of the code of utilities/ovs-dpctl.c in lib/dpctl.c.

Both the ovs-dpctl command and the ovs-appctl dpctl/* commands make calls to
lib/dpctl.c functions, to interact with datapaths.

The code from utilities/ovs-dpctl.c has been moved to lib/dpctl.c and has been
changed for different reasons:
   - An exit() call in the old code made perfectly sense. Now (since the code
     can be run inside vswitchd) it would terminate the daemon. Same reasoning
     can be applied to ovs_fatal_*() calls.
   - The lib/dpctl.c code _should_ not leak memory.
   - All the print* have been replaced with a function pointer provided by the
     caller, since this code can be run in the ovs-dpctl process (in which
     case we need to print to stdout) or in response to a unixctl request (and
     in this case we need to send everything through a socket, using JSON
     encapsulation).

The syntax is
   ovs-appctl dpctl/(COMMAND) [OPTIONS] [PARAMETERS]
while the ovs-dpctl syntax (which _should_ remain the same after this change)
is
   ovs-dpctl [OPTIONS] (COMMAND) [PARAMETERS]

Signed-off-by: Daniele Di Proietto <ddiproietto@vmware.com>
[blp@nicira.com made stylistic and documentation changes]
Signed-off-by: Ben Pfaff <blp@nicira.com>
NEWS
lib/automake.mk
lib/dpctl.c [new file with mode: 0644]
lib/dpctl.h [new file with mode: 0644]
lib/dpctl.man [new file with mode: 0644]
lib/dpif.c
manpages.mk
ofproto/ofproto-dpif-unixctl.man
utilities/ovs-dpctl.8.in
utilities/ovs-dpctl.c
vswitchd/ovs-vswitchd.8.in

diff --git a/NEWS b/NEWS
index 742ae22..bf7eb2f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,10 @@ Post-v2.3.0
      release. The protocol is documented at
      http://tools.ietf.org/html/draft-gross-geneve-00
    - The OVS database now reports controller rate limiting statistics.
+   - ovs-dpctl functionality is now available for datapaths integrated
+     into ovs-vswitchd, via ovs-appctl.  Some existing ovs-appctl
+     commands are now redundant and will be removed in a future
+     release.  See ovs-vswitchd(8) for details.
    - OpenFlow:
      * OpenFlow 1.5 (draft) extended registers are now supported.
 
index 87a8faa..4628c9b 100644 (file)
@@ -55,6 +55,8 @@ lib_libopenvswitch_la_SOURCES = \
        lib/dummy.h \
        lib/dhparams.h \
        lib/dirs.h \
+       lib/dpctl.c \
+       lib/dpctl.h \
        lib/dpif-netdev.c \
        lib/dpif-netdev.h \
        lib/dpif-provider.h \
@@ -373,6 +375,7 @@ MAN_FRAGMENTS += \
        lib/coverage-unixctl.man \
        lib/daemon.man \
        lib/daemon-syn.man \
+       lib/dpctl.man \
        lib/memory-unixctl.man \
        lib/ofp-version.man \
        lib/ovs.tmac \
diff --git a/lib/dpctl.c b/lib/dpctl.c
new file mode 100644 (file)
index 0000000..623f5b1
--- /dev/null
@@ -0,0 +1,1422 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "dirs.h"
+#include "dpctl.h"
+#include "dpif.h"
+#include "dynamic-string.h"
+#include "flow.h"
+#include "match.h"
+#include "netdev.h"
+#include "netlink.h"
+#include "odp-util.h"
+#include "ofp-parse.h"
+#include "ofpbuf.h"
+#include "packets.h"
+#include "shash.h"
+#include "simap.h"
+#include "smap.h"
+#include "sset.h"
+#include "timeval.h"
+#include "unixctl.h"
+#include "util.h"
+
+static void
+dpctl_puts(struct dpctl_params *dpctl_p, bool error, const char *string)
+{
+    dpctl_p->output(dpctl_p->aux, error, string);
+}
+
+static void
+dpctl_print(struct dpctl_params *dpctl_p, const char *fmt, ...)
+{
+    char *string;
+    va_list args;
+
+    va_start(args, fmt);
+    string = xvasprintf(fmt, args);
+    va_end(args);
+
+    dpctl_puts(dpctl_p, false, string);
+    free(string);
+}
+
+static void
+dpctl_error(struct dpctl_params* dpctl_p, int err_no, const char *fmt, ...)
+{
+    const char *subprogram_name = get_subprogram_name();
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    int save_errno = errno;
+    va_list args;
+
+
+    if (subprogram_name[0]) {
+        ds_put_format(&ds, "%s(%s): ", program_name,subprogram_name);
+    } else {
+        ds_put_format(&ds, "%s: ", program_name);
+    }
+
+    va_start(args, fmt);
+    ds_put_format_valist(&ds, fmt, args);
+    va_end(args);
+
+    if (err_no != 0) {
+        ds_put_format(&ds, " (%s)", ovs_retval_to_string(err_no));
+    }
+    ds_put_cstr(&ds, "\n");
+
+    dpctl_puts(dpctl_p, true, ds_cstr(&ds));
+
+    ds_destroy(&ds);
+
+    errno = save_errno;
+}
+\f
+static int dpctl_add_if(int argc, const char *argv[], struct dpctl_params *);
+
+static int
+if_up(struct netdev *netdev)
+{
+    return netdev_turn_flags_on(netdev, NETDEV_UP, NULL);
+}
+
+/* Retrieve the name of the datapath if exactly one exists.  The caller
+ * is responsible for freeing the returned string.  If there is not one
+ * datapath, aborts with an error message. */
+static char *
+get_one_dp(struct dpctl_params *dpctl_p)
+{
+    struct sset types;
+    const char *type;
+    char *dp_name = NULL;
+    size_t count = 0;
+
+    sset_init(&types);
+    dp_enumerate_types(&types);
+    SSET_FOR_EACH (type, &types) {
+        struct sset names;
+
+        sset_init(&names);
+        if (!dp_enumerate_names(type, &names)) {
+            count += sset_count(&names);
+            if (!dp_name && count == 1) {
+                dp_name = xasprintf("%s@%s", type, SSET_FIRST(&names));
+            }
+        }
+        sset_destroy(&names);
+    }
+    sset_destroy(&types);
+
+    if (!count) {
+        dpctl_error(dpctl_p, 0, "no datapaths exist");
+    } else if (count > 1) {
+        dpctl_error(dpctl_p, 0, "multiple datapaths, specify one");
+        free(dp_name);
+        dp_name = NULL;
+    }
+
+    return dp_name;
+}
+
+static int
+parsed_dpif_open(const char *arg_, bool create, struct dpif **dpifp)
+{
+    int result;
+    char *name, *type;
+
+    dp_parse_name(arg_, &name, &type);
+
+    if (create) {
+        result = dpif_create(name, type, dpifp);
+    } else {
+        result = dpif_open(name, type, dpifp);
+    }
+
+    free(name);
+    free(type);
+    return result;
+}
+
+static int
+dpctl_add_dp(int argc, const char *argv[],
+             struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error;
+
+    error = parsed_dpif_open(argv[1], true, &dpif);
+    if (error) {
+        dpctl_error(dpctl_p, error, "add_dp");
+        return error;
+    }
+    dpif_close(dpif);
+    if (argc > 2) {
+        error = dpctl_add_if(argc, argv, dpctl_p);
+    }
+    return error;
+}
+
+static int
+dpctl_del_dp(int argc OVS_UNUSED, const char *argv[],
+             struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error;
+
+    error = parsed_dpif_open(argv[1], false, &dpif);
+    if (error) {
+        dpctl_error(dpctl_p, error, "opening datapath");
+        return error;
+    }
+    error = dpif_delete(dpif);
+    if (error) {
+        dpctl_error(dpctl_p, error, "del_dp");
+    }
+
+    dpif_close(dpif);
+    return error;
+}
+
+static int
+dpctl_add_if(int argc OVS_UNUSED, const char *argv[],
+             struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int i, error, lasterror = 0;
+
+    error = parsed_dpif_open(argv[1], false, &dpif);
+    if (error) {
+        dpctl_error(dpctl_p, error, "opening datapath");
+        return error;
+    }
+    for (i = 2; i < argc; i++) {
+        const char *name, *type;
+        char *save_ptr = NULL, *argcopy;
+        struct netdev *netdev = NULL;
+        struct smap args;
+        odp_port_t port_no = ODPP_NONE;
+        char *option;
+
+        argcopy = xstrdup(argv[i]);
+        name = strtok_r(argcopy, ",", &save_ptr);
+        type = "system";
+
+        if (!name) {
+            dpctl_error(dpctl_p, 0, "%s is not a valid network device name",
+                        argv[i]);
+            error = EINVAL;
+            goto next;
+        }
+
+        smap_init(&args);
+        while ((option = strtok_r(NULL, ",", &save_ptr)) != NULL) {
+            char *save_ptr_2 = NULL;
+            char *key, *value;
+
+            key = strtok_r(option, "=", &save_ptr_2);
+            value = strtok_r(NULL, "", &save_ptr_2);
+            if (!value) {
+                value = "";
+            }
+
+            if (!strcmp(key, "type")) {
+                type = value;
+            } else if (!strcmp(key, "port_no")) {
+                port_no = u32_to_odp(atoi(value));
+            } else if (!smap_add_once(&args, key, value)) {
+                dpctl_error(dpctl_p, 0, "duplicate \"%s\" option", key);
+            }
+        }
+
+        error = netdev_open(name, type, &netdev);
+        if (error) {
+            dpctl_error(dpctl_p, error, "%s: failed to open network device",
+                        name);
+            goto next_destroy_args;
+        }
+
+        error = netdev_set_config(netdev, &args, NULL);
+        if (error) {
+            goto next_destroy_args;
+        }
+
+        error = dpif_port_add(dpif, netdev, &port_no);
+        if (error) {
+            dpctl_error(dpctl_p, error, "adding %s to %s failed", name,
+                        argv[1]);
+            goto next_destroy_args;
+        }
+
+        error = if_up(netdev);
+        if (error) {
+            dpctl_error(dpctl_p, error, "%s: failed bringing interface up",
+                        name);
+        }
+
+next_destroy_args:
+        netdev_close(netdev);
+        smap_destroy(&args);
+next:
+        free(argcopy);
+        if (error) {
+            lasterror = error;
+        }
+    }
+    dpif_close(dpif);
+
+    return lasterror;
+}
+
+static int
+dpctl_set_if(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int i, error, lasterror = 0;
+
+    error = parsed_dpif_open(argv[1], false, &dpif);
+    if (error) {
+        dpctl_error(dpctl_p, error, "opening datapath");
+        return error;
+    }
+    for (i = 2; i < argc; i++) {
+        struct netdev *netdev = NULL;
+        struct dpif_port dpif_port;
+        char *save_ptr = NULL;
+        char *type = NULL;
+        char *argcopy;
+        const char *name;
+        struct smap args;
+        odp_port_t port_no;
+        char *option;
+        int error = 0;
+
+        argcopy = xstrdup(argv[i]);
+        name = strtok_r(argcopy, ",", &save_ptr);
+        if (!name) {
+            dpctl_error(dpctl_p, 0, "%s is not a valid network device name",
+                        argv[i]);
+            goto next;
+        }
+
+        /* Get the port's type from the datapath. */
+        error = dpif_port_query_by_name(dpif, name, &dpif_port);
+        if (error) {
+            dpctl_error(dpctl_p, error, "%s: failed to query port in %s", name,
+                        argv[1]);
+            goto next;
+        }
+        type = xstrdup(dpif_port.type);
+        port_no = dpif_port.port_no;
+        dpif_port_destroy(&dpif_port);
+
+        /* Retrieve its existing configuration. */
+        error = netdev_open(name, type, &netdev);
+        if (error) {
+            dpctl_error(dpctl_p, error, "%s: failed to open network device",
+                        name);
+            goto next;
+        }
+
+        smap_init(&args);
+        error = netdev_get_config(netdev, &args);
+        if (error) {
+            dpctl_error(dpctl_p, error, "%s: failed to fetch configuration",
+                        name);
+            goto next_destroy_args;
+        }
+
+        /* Parse changes to configuration. */
+        while ((option = strtok_r(NULL, ",", &save_ptr)) != NULL) {
+            char *save_ptr_2 = NULL;
+            char *key, *value;
+
+            key = strtok_r(option, "=", &save_ptr_2);
+            value = strtok_r(NULL, "", &save_ptr_2);
+            if (!value) {
+                value = "";
+            }
+
+            if (!strcmp(key, "type")) {
+                if (strcmp(value, type)) {
+                    dpctl_error(dpctl_p, 0,
+                                "%s: can't change type from %s to %s",
+                                 name, type, value);
+                    error = EINVAL;
+                }
+            } else if (!strcmp(key, "port_no")) {
+                if (port_no != u32_to_odp(atoi(value))) {
+                    dpctl_error(dpctl_p, 0, "%s: can't change port number from"
+                              " %"PRIu32" to %d", name, port_no, atoi(value));
+                    error = EINVAL;
+                }
+            } else if (value[0] == '\0') {
+                smap_remove(&args, key);
+            } else {
+                smap_replace(&args, key, value);
+            }
+        }
+
+        /* Update configuration. */
+        error = netdev_set_config(netdev, &args, NULL);
+        if (error) {
+            goto next_destroy_args;
+        }
+
+next_destroy_args:
+        smap_destroy(&args);
+next:
+        netdev_close(netdev);
+        free(type);
+        free(argcopy);
+        if (error) {
+            lasterror = error;
+        }
+    }
+    dpif_close(dpif);
+
+    return lasterror;
+}
+
+static bool
+get_port_number(struct dpif *dpif, const char *name, odp_port_t *port,
+                struct dpctl_params *dpctl_p)
+{
+    struct dpif_port dpif_port;
+
+    if (!dpif_port_query_by_name(dpif, name, &dpif_port)) {
+        *port = dpif_port.port_no;
+        dpif_port_destroy(&dpif_port);
+        return true;
+    } else {
+        dpctl_error(dpctl_p, 0, "no port named %s", name);
+        return false;
+    }
+}
+
+static int
+dpctl_del_if(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int i, error, lasterror = 0;
+
+    error = parsed_dpif_open(argv[1], false, &dpif);
+    if (error) {
+        dpctl_error(dpctl_p, error, "opening datapath");
+        return error;
+    }
+    for (i = 2; i < argc; i++) {
+        const char *name = argv[i];
+        odp_port_t port;
+
+        if (!name[strspn(name, "0123456789")]) {
+            port = u32_to_odp(atoi(name));
+        } else if (!get_port_number(dpif, name, &port, dpctl_p)) {
+            lasterror = ENOENT;
+            continue;
+        }
+
+        error = dpif_port_del(dpif, port);
+        if (error) {
+            dpctl_error(dpctl_p, error, "deleting port %s from %s failed",
+                        name, argv[1]);
+            lasterror = error;
+        }
+    }
+    dpif_close(dpif);
+    return lasterror;
+}
+
+static void
+print_stat(struct dpctl_params *dpctl_p, const char *leader, uint64_t value)
+{
+    dpctl_print(dpctl_p, "%s", leader);
+    if (value != UINT64_MAX) {
+        dpctl_print(dpctl_p, "%"PRIu64, value);
+    } else {
+        dpctl_print(dpctl_p, "?");
+    }
+}
+
+static void
+print_human_size(struct dpctl_params *dpctl_p, uint64_t value)
+{
+    if (value == UINT64_MAX) {
+        /* Nothing to do. */
+    } else if (value >= 1024ULL * 1024 * 1024 * 1024) {
+        dpctl_print(dpctl_p, " (%.1f TiB)",
+                    value / (1024.0 * 1024 * 1024 * 1024));
+    } else if (value >= 1024ULL * 1024 * 1024) {
+        dpctl_print(dpctl_p, " (%.1f GiB)", value / (1024.0 * 1024 * 1024));
+    } else if (value >= 1024ULL * 1024) {
+        dpctl_print(dpctl_p, " (%.1f MiB)", value / (1024.0 * 1024));
+    } else if (value >= 1024) {
+        dpctl_print(dpctl_p, " (%.1f KiB)", value / 1024.0);
+    }
+}
+
+static void
+show_dpif(struct dpif *dpif, struct dpctl_params *dpctl_p)
+{
+    struct dpif_port_dump dump;
+    struct dpif_port dpif_port;
+    struct dpif_dp_stats stats;
+    struct netdev *netdev;
+
+    dpctl_print(dpctl_p, "%s:\n", dpif_name(dpif));
+    if (!dpif_get_dp_stats(dpif, &stats)) {
+        dpctl_print(dpctl_p, "\tlookups: hit:%"PRIu64" missed:%"PRIu64
+                             " lost:%"PRIu64"\n\tflows: %"PRIu64"\n",
+                    stats.n_hit, stats.n_missed, stats.n_lost, stats.n_flows);
+        if (stats.n_masks != UINT32_MAX) {
+            uint64_t n_pkts = stats.n_hit + stats.n_missed;
+            double avg = n_pkts ? (double) stats.n_mask_hit / n_pkts : 0.0;
+
+            dpctl_print(dpctl_p, "\tmasks: hit:%"PRIu64" total:%"PRIu32
+                                 " hit/pkt:%.2f\n",
+                        stats.n_mask_hit, stats.n_masks, avg);
+        }
+    }
+
+    DPIF_PORT_FOR_EACH (&dpif_port, &dump, dpif) {
+        dpctl_print(dpctl_p, "\tport %u: %s",
+                    dpif_port.port_no, dpif_port.name);
+
+        if (strcmp(dpif_port.type, "system")) {
+            int error;
+
+            dpctl_print(dpctl_p, " (%s", dpif_port.type);
+
+            error = netdev_open(dpif_port.name, dpif_port.type, &netdev);
+            if (!error) {
+                struct smap config;
+
+                smap_init(&config);
+                error = netdev_get_config(netdev, &config);
+                if (!error) {
+                    const struct smap_node **nodes;
+                    size_t i;
+
+                    nodes = smap_sort(&config);
+                    for (i = 0; i < smap_count(&config); i++) {
+                        const struct smap_node *node = nodes[i];
+                        dpctl_print(dpctl_p, "%c %s=%s", i ? ',' : ':',
+                                    node->key, node->value);
+                    }
+                    free(nodes);
+                } else {
+                    dpctl_print(dpctl_p, ", could not retrieve configuration "
+                                         "(%s)",  ovs_strerror(error));
+                }
+                smap_destroy(&config);
+
+                netdev_close(netdev);
+            } else {
+                dpctl_print(dpctl_p, ": open failed (%s)",
+                            ovs_strerror(error));
+            }
+            dpctl_print(dpctl_p, ")");
+        }
+        dpctl_print(dpctl_p, "\n");
+
+        if (dpctl_p->print_statistics) {
+            struct netdev_stats s;
+            int error;
+
+            error = netdev_open(dpif_port.name, dpif_port.type, &netdev);
+            if (error) {
+                dpctl_print(dpctl_p, ", open failed (%s)",
+                            ovs_strerror(error));
+                continue;
+            }
+            error = netdev_get_stats(netdev, &s);
+            if (!error) {
+                netdev_close(netdev);
+                print_stat(dpctl_p, "\t\tRX packets:", s.rx_packets);
+                print_stat(dpctl_p, " errors:", s.rx_errors);
+                print_stat(dpctl_p, " dropped:", s.rx_dropped);
+                print_stat(dpctl_p, " overruns:", s.rx_over_errors);
+                print_stat(dpctl_p, " frame:", s.rx_frame_errors);
+                dpctl_print(dpctl_p, "\n");
+
+                print_stat(dpctl_p, "\t\tTX packets:", s.tx_packets);
+                print_stat(dpctl_p, " errors:", s.tx_errors);
+                print_stat(dpctl_p, " dropped:", s.tx_dropped);
+                print_stat(dpctl_p, " aborted:", s.tx_aborted_errors);
+                print_stat(dpctl_p, " carrier:", s.tx_carrier_errors);
+                dpctl_print(dpctl_p, "\n");
+
+                print_stat(dpctl_p, "\t\tcollisions:", s.collisions);
+                dpctl_print(dpctl_p, "\n");
+
+                print_stat(dpctl_p, "\t\tRX bytes:", s.rx_bytes);
+                print_human_size(dpctl_p, s.rx_bytes);
+                print_stat(dpctl_p, "  TX bytes:", s.tx_bytes);
+                print_human_size(dpctl_p, s.tx_bytes);
+                dpctl_print(dpctl_p, "\n");
+            } else {
+                dpctl_print(dpctl_p, ", could not retrieve stats (%s)",
+                            ovs_strerror(error));
+            }
+        }
+    }
+    dpif_close(dpif);
+}
+
+static int
+dpctl_show(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    int error, lasterror = 0;
+    if (argc > 1) {
+        int i;
+        for (i = 1; i < argc; i++) {
+            const char *name = argv[i];
+            struct dpif *dpif;
+
+            error = parsed_dpif_open(name, false, &dpif);
+            if (!error) {
+                show_dpif(dpif, dpctl_p);
+            } else {
+                dpctl_error(dpctl_p, error, "opening datapath %s failed",
+                            name);
+                lasterror = error;
+            }
+        }
+    } else {
+        struct sset types;
+        const char *type;
+
+        sset_init(&types);
+        dp_enumerate_types(&types);
+        SSET_FOR_EACH (type, &types) {
+            struct sset names;
+            const char *name;
+
+            sset_init(&names);
+            error = dp_enumerate_names(type, &names);
+            if (error) {
+                lasterror = error;
+                goto next;
+            }
+            SSET_FOR_EACH (name, &names) {
+                struct dpif *dpif;
+
+                error = dpif_open(name, type, &dpif);
+                if (!error) {
+                    show_dpif(dpif, dpctl_p);
+                } else {
+                    dpctl_error(dpctl_p, error, "opening datapath %s failed",
+                                name);
+                    lasterror = error;
+                }
+            }
+next:
+            sset_destroy(&names);
+        }
+        sset_destroy(&types);
+    }
+    return lasterror;
+}
+
+static int
+dpctl_dump_dps(int argc OVS_UNUSED, const char *argv[] OVS_UNUSED,
+               struct dpctl_params *dpctl_p)
+{
+    struct sset dpif_names, dpif_types;
+    const char *type;
+    int error, lasterror = 0;
+
+    sset_init(&dpif_names);
+    sset_init(&dpif_types);
+    dp_enumerate_types(&dpif_types);
+
+    SSET_FOR_EACH (type, &dpif_types) {
+        const char *name;
+
+        error = dp_enumerate_names(type, &dpif_names);
+        if (error) {
+            lasterror = error;
+        }
+
+        SSET_FOR_EACH (name, &dpif_names) {
+            struct dpif *dpif;
+            if (!dpif_open(name, type, &dpif)) {
+                dpctl_print(dpctl_p, "%s\n", dpif_name(dpif));
+                dpif_close(dpif);
+            }
+        }
+    }
+
+    sset_destroy(&dpif_names);
+    sset_destroy(&dpif_types);
+    return lasterror;
+}
+
+static int
+dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    struct ds ds;
+    char *name;
+
+    char *filter = NULL;
+    struct flow flow_filter;
+    struct flow_wildcards wc_filter;
+
+    struct dpif_port_dump port_dump;
+    struct dpif_port dpif_port;
+    struct hmap portno_names;
+    struct simap names_portno;
+
+    struct dpif_flow_dump_thread *flow_dump_thread;
+    struct dpif_flow_dump *flow_dump;
+    struct dpif_flow f;
+
+    int error;
+
+    if (argc > 1 && !strncmp(argv[argc - 1], "filter=", 7)) {
+        filter = xstrdup(argv[--argc] + 7);
+    }
+    name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
+    if (!name) {
+        error = EINVAL;
+        goto out_freefilter;
+    }
+
+    error = parsed_dpif_open(name, false, &dpif);
+    free(name);
+    if (error) {
+        dpctl_error(dpctl_p, error, "opening datapath");
+        goto out_freefilter;
+    }
+
+
+    hmap_init(&portno_names);
+    simap_init(&names_portno);
+    DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
+        odp_portno_names_set(&portno_names, dpif_port.port_no, dpif_port.name);
+        simap_put(&names_portno, dpif_port.name,
+                  odp_to_u32(dpif_port.port_no));
+    }
+
+    if (filter) {
+        char *err = parse_ofp_exact_flow(&flow_filter, &wc_filter.masks,
+                                         filter, &names_portno);
+        if (err) {
+            dpctl_error(dpctl_p, 0, "Failed to parse filter (%s)", err);
+            error = EINVAL;
+            goto out_dpifclose;
+        }
+    }
+
+    ds_init(&ds);
+    flow_dump = dpif_flow_dump_create(dpif);
+    flow_dump_thread = dpif_flow_dump_thread_create(flow_dump);
+    while (dpif_flow_dump_next(flow_dump_thread, &f, 1)) {
+        if (filter) {
+            struct flow flow;
+            struct flow_wildcards wc;
+            struct match match, match_filter;
+            struct minimatch minimatch;
+
+            odp_flow_key_to_flow(f.key, f.key_len, &flow);
+            odp_flow_key_to_mask(f.mask, f.mask_len, &wc.masks, &flow);
+            match_init(&match, &flow, &wc);
+
+            match_init(&match_filter, &flow_filter, &wc);
+            match_init(&match_filter, &match_filter.flow, &wc_filter);
+            minimatch_init(&minimatch, &match_filter);
+
+            if (!minimatch_matches_flow(&minimatch, &match.flow)) {
+                minimatch_destroy(&minimatch);
+                continue;
+            }
+            minimatch_destroy(&minimatch);
+        }
+        ds_clear(&ds);
+        odp_flow_format(f.key, f.key_len, f.mask, f.mask_len,
+                        &portno_names, &ds, dpctl_p->verbosity);
+        ds_put_cstr(&ds, ", ");
+
+        dpif_flow_stats_format(&f.stats, &ds);
+        ds_put_cstr(&ds, ", actions:");
+        format_odp_actions(&ds, f.actions, f.actions_len);
+        dpctl_print(dpctl_p, "%s\n", ds_cstr(&ds));
+    }
+    dpif_flow_dump_thread_destroy(flow_dump_thread);
+    error = dpif_flow_dump_destroy(flow_dump);
+
+    if (error) {
+        dpctl_error(dpctl_p, error, "Failed to dump flows from datapath");
+    }
+    ds_destroy(&ds);
+
+out_dpifclose:
+    odp_portno_names_destroy(&portno_names);
+    simap_destroy(&names_portno);
+    hmap_destroy(&portno_names);
+    dpif_close(dpif);
+out_freefilter:
+    free(filter);
+    return error;
+}
+
+static int
+dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
+               struct dpctl_params *dpctl_p)
+{
+    const char *key_s = argv[argc - 2];
+    const char *actions_s = argv[argc - 1];
+    struct dpif_flow_stats stats;
+    struct dpif_port dpif_port;
+    struct dpif_port_dump port_dump;
+    struct ofpbuf actions;
+    struct ofpbuf key;
+    struct ofpbuf mask;
+    struct dpif *dpif;
+    char *dp_name;
+    struct simap port_names;
+    int error;
+
+    dp_name = argc == 4 ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
+    if (!dp_name) {
+        return EINVAL;
+    }
+    error = parsed_dpif_open(dp_name, false, &dpif);
+    free(dp_name);
+    if (error) {
+        dpctl_error(dpctl_p, error, "opening datapath");
+        return error;
+    }
+
+
+    simap_init(&port_names);
+    DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
+        simap_put(&port_names, dpif_port.name, odp_to_u32(dpif_port.port_no));
+    }
+
+    ofpbuf_init(&key, 0);
+    ofpbuf_init(&mask, 0);
+    error = odp_flow_from_string(key_s, &port_names, &key, &mask);
+    simap_destroy(&port_names);
+    if (error) {
+        dpctl_error(dpctl_p, error, "parsing flow key");
+        goto out_freekeymask;
+    }
+
+    ofpbuf_init(&actions, 0);
+    error = odp_actions_from_string(actions_s, NULL, &actions);
+    if (error) {
+        dpctl_error(dpctl_p, error, "parsing actions");
+        goto out_freeactions;
+    }
+    error = dpif_flow_put(dpif, flags,
+                          ofpbuf_data(&key), ofpbuf_size(&key),
+                          ofpbuf_size(&mask) == 0 ? NULL : ofpbuf_data(&mask),
+                          ofpbuf_size(&mask),
+                          ofpbuf_data(&actions), ofpbuf_size(&actions),
+                          dpctl_p->print_statistics ? &stats : NULL);
+    if (error) {
+        dpctl_error(dpctl_p, error, "updating flow table");
+        goto out_freeactions;
+    }
+
+    if (dpctl_p->print_statistics) {
+        struct ds s;
+
+        ds_init(&s);
+        dpif_flow_stats_format(&stats, &s);
+        dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
+        ds_destroy(&s);
+    }
+
+out_freeactions:
+    ofpbuf_uninit(&actions);
+out_freekeymask:
+    ofpbuf_uninit(&mask);
+    ofpbuf_uninit(&key);
+    dpif_close(dpif);
+    return error;
+}
+
+static int
+dpctl_add_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    return dpctl_put_flow(argc, argv, DPIF_FP_CREATE, dpctl_p);
+}
+
+static int
+dpctl_mod_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    enum dpif_flow_put_flags flags;
+
+    flags = DPIF_FP_MODIFY;
+    if (dpctl_p->may_create) {
+        flags |= DPIF_FP_CREATE;
+    }
+    if (dpctl_p->zero_statistics) {
+        flags |= DPIF_FP_ZERO_STATS;
+    }
+
+    return dpctl_put_flow(argc, argv, flags, dpctl_p);
+}
+
+static int
+dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    const char *key_s = argv[argc - 1];
+    struct dpif_flow_stats stats;
+    struct dpif_port dpif_port;
+    struct dpif_port_dump port_dump;
+    struct ofpbuf key;
+    struct ofpbuf mask; /* To be ignored. */
+    struct dpif *dpif;
+    char *dp_name;
+    struct simap port_names;
+    int error;
+
+    dp_name = argc == 3 ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
+    if (!dp_name) {
+        return EINVAL;
+    }
+    error = parsed_dpif_open(dp_name, false, &dpif);
+    free(dp_name);
+    if (error) {
+        dpctl_error(dpctl_p, error, "opening datapath");
+        return error;
+    }
+
+    simap_init(&port_names);
+    DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
+        simap_put(&port_names, dpif_port.name, odp_to_u32(dpif_port.port_no));
+    }
+
+    ofpbuf_init(&key, 0);
+    ofpbuf_init(&mask, 0);
+
+    error = odp_flow_from_string(key_s, &port_names, &key, &mask);
+    if (error) {
+        dpctl_error(dpctl_p, error, "parsing flow key");
+        goto out;
+    }
+
+    error = dpif_flow_del(dpif,
+                          ofpbuf_data(&key), ofpbuf_size(&key),
+                          dpctl_p->print_statistics ? &stats : NULL);
+    if (error) {
+        dpctl_error(dpctl_p, error, "deleting flow");
+        goto out;
+    }
+
+    if (dpctl_p->print_statistics) {
+        struct ds s;
+
+        ds_init(&s);
+        dpif_flow_stats_format(&stats, &s);
+        dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
+        ds_destroy(&s);
+    }
+
+out:
+    ofpbuf_uninit(&mask);
+    ofpbuf_uninit(&key);
+    simap_destroy(&port_names);
+    dpif_close(dpif);
+    return error;
+}
+
+static int
+dpctl_del_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    char *name;
+    int error;
+
+    name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
+    if (!name) {
+        return EINVAL;
+    }
+    error = parsed_dpif_open(name, false, &dpif);
+    free(name);
+    if (error) {
+        dpctl_error(dpctl_p, error, "opening datapath");
+        return error;
+    }
+
+    error = dpif_flow_flush(dpif);
+    if (error) {
+        dpctl_error(dpctl_p, error, "deleting all flows");
+    }
+    dpif_close(dpif);
+    return error;
+}
+
+static int
+dpctl_help(int argc OVS_UNUSED, const char *argv[] OVS_UNUSED,
+           struct dpctl_params *dpctl_p)
+{
+    if (dpctl_p->usage) {
+        dpctl_p->usage(dpctl_p->aux);
+    }
+    return 0;
+}
+\f
+/* Undocumented commands for unit testing. */
+
+static int
+dpctl_parse_actions(int argc, const char *argv[], struct dpctl_params* dpctl_p)
+{
+    int i, error = 0;
+
+    for (i = 1; i < argc; i++) {
+        struct ofpbuf actions;
+        struct ds s;
+
+        ofpbuf_init(&actions, 0);
+        error = odp_actions_from_string(argv[i], NULL, &actions);
+
+        if (error) {
+            ofpbuf_uninit(&actions);
+            dpctl_error(dpctl_p, error, "odp_actions_from_string");
+            return error;
+        }
+
+        ds_init(&s);
+        format_odp_actions(&s, ofpbuf_data(&actions), ofpbuf_size(&actions));
+        dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
+        ds_destroy(&s);
+
+        ofpbuf_uninit(&actions);
+    }
+
+    return error;
+}
+
+struct actions_for_flow {
+    struct hmap_node hmap_node;
+    struct flow flow;
+    struct ofpbuf actions;
+};
+
+static struct actions_for_flow *
+get_actions_for_flow(struct hmap *actions_per_flow, const struct flow *flow)
+{
+    uint32_t hash = flow_hash(flow, 0);
+    struct actions_for_flow *af;
+
+    HMAP_FOR_EACH_WITH_HASH (af, hmap_node, hash, actions_per_flow) {
+        if (flow_equal(&af->flow, flow)) {
+            return af;
+        }
+    }
+
+    af = xmalloc(sizeof *af);
+    af->flow = *flow;
+    ofpbuf_init(&af->actions, 0);
+    hmap_insert(actions_per_flow, &af->hmap_node, hash);
+    return af;
+}
+
+static int
+compare_actions_for_flow(const void *a_, const void *b_)
+{
+    struct actions_for_flow *const *a = a_;
+    struct actions_for_flow *const *b = b_;
+
+    return flow_compare_3way(&(*a)->flow, &(*b)->flow);
+}
+
+static int
+compare_output_actions(const void *a_, const void *b_)
+{
+    const struct nlattr *a = a_;
+    const struct nlattr *b = b_;
+    uint32_t a_port = nl_attr_get_u32(a);
+    uint32_t b_port = nl_attr_get_u32(b);
+
+    return a_port < b_port ? -1 : a_port > b_port;
+}
+
+static void
+sort_output_actions__(struct nlattr *first, struct nlattr *end)
+{
+    size_t bytes = (uint8_t *) end - (uint8_t *) first;
+    size_t n = bytes / NL_A_U32_SIZE;
+
+    ovs_assert(bytes % NL_A_U32_SIZE == 0);
+    qsort(first, n, NL_A_U32_SIZE, compare_output_actions);
+}
+
+static void
+sort_output_actions(struct nlattr *actions, size_t length)
+{
+    struct nlattr *first_output = NULL;
+    struct nlattr *a;
+    int left;
+
+    NL_ATTR_FOR_EACH (a, left, actions, length) {
+        if (nl_attr_type(a) == OVS_ACTION_ATTR_OUTPUT) {
+            if (!first_output) {
+                first_output = a;
+            }
+        } else {
+            if (first_output) {
+                sort_output_actions__(first_output, a);
+                first_output = NULL;
+            }
+        }
+    }
+    if (first_output) {
+        uint8_t *end = (uint8_t *) actions + length;
+        sort_output_actions__(first_output,
+                              ALIGNED_CAST(struct nlattr *, end));
+    }
+}
+
+/* usage: "ovs-dpctl normalize-actions FLOW ACTIONS" where FLOW and ACTIONS
+ * have the syntax used by "ovs-dpctl dump-flows".
+ *
+ * This command prints ACTIONS in a format that shows what happens for each
+ * VLAN, independent of the order of the ACTIONS.  For example, there is more
+ * than one way to output a packet on VLANs 9 and 11, but this command will
+ * print the same output for any form.
+ *
+ * The idea here generalizes beyond VLANs (e.g. to setting other fields) but
+ * so far the implementation only covers VLANs. */
+static int
+dpctl_normalize_actions(int argc, const char *argv[],
+                        struct dpctl_params *dpctl_p)
+{
+    struct simap port_names;
+    struct ofpbuf keybuf;
+    struct flow flow;
+    struct ofpbuf odp_actions;
+    struct hmap actions_per_flow;
+    struct actions_for_flow **afs;
+    struct actions_for_flow *af;
+    struct nlattr *a;
+    size_t n_afs;
+    struct ds s;
+    int left;
+    int i, error;
+
+    ds_init(&s);
+
+    simap_init(&port_names);
+    for (i = 3; i < argc; i++) {
+        char name[16];
+        int number;
+
+        if (ovs_scan(argv[i], "%15[^=]=%d", name, &number)) {
+            uintptr_t n = number;
+            simap_put(&port_names, name, n);
+        } else {
+            dpctl_error(dpctl_p, 0, "%s: expected NAME=NUMBER", argv[i]);
+            error = EINVAL;
+            goto out;
+        }
+    }
+
+    /* Parse flow key. */
+    ofpbuf_init(&keybuf, 0);
+    error = odp_flow_from_string(argv[1], &port_names, &keybuf, NULL);
+    if (error) {
+        dpctl_error(dpctl_p, error, "odp_flow_key_from_string");
+        goto out_freekeybuf;
+    }
+
+    ds_clear(&s);
+    odp_flow_format(ofpbuf_data(&keybuf), ofpbuf_size(&keybuf), NULL, 0, NULL,
+                    &s, dpctl_p->verbosity);
+    dpctl_print(dpctl_p, "input flow: %s\n", ds_cstr(&s));
+
+    error = odp_flow_key_to_flow(ofpbuf_data(&keybuf), ofpbuf_size(&keybuf),
+                                 &flow);
+    if (error) {
+        dpctl_error(dpctl_p, error, "odp_flow_key_to_flow");
+        goto out_freekeybuf;
+    }
+
+    /* Parse actions. */
+    ofpbuf_init(&odp_actions, 0);
+    error = odp_actions_from_string(argv[2], &port_names, &odp_actions);
+    if (error) {
+        dpctl_error(dpctl_p, error, "odp_actions_from_string");
+        goto out_freeactions;
+    }
+
+    if (dpctl_p->verbosity) {
+        ds_clear(&s);
+        format_odp_actions(&s, ofpbuf_data(&odp_actions),
+                           ofpbuf_size(&odp_actions));
+        dpctl_print(dpctl_p, "input actions: %s\n", ds_cstr(&s));
+    }
+
+    hmap_init(&actions_per_flow);
+    NL_ATTR_FOR_EACH (a, left, ofpbuf_data(&odp_actions),
+                      ofpbuf_size(&odp_actions)) {
+        const struct ovs_action_push_vlan *push;
+        switch(nl_attr_type(a)) {
+        case OVS_ACTION_ATTR_POP_VLAN:
+            flow.vlan_tci = htons(0);
+            continue;
+
+        case OVS_ACTION_ATTR_PUSH_VLAN:
+            push = nl_attr_get_unspec(a, sizeof *push);
+            flow.vlan_tci = push->vlan_tci;
+            continue;
+        }
+
+        af = get_actions_for_flow(&actions_per_flow, &flow);
+        nl_msg_put_unspec(&af->actions, nl_attr_type(a),
+                          nl_attr_get(a), nl_attr_get_size(a));
+    }
+
+    n_afs = hmap_count(&actions_per_flow);
+    afs = xmalloc(n_afs * sizeof *afs);
+    i = 0;
+    HMAP_FOR_EACH (af, hmap_node, &actions_per_flow) {
+        afs[i++] = af;
+    }
+
+    ovs_assert(i == n_afs);
+    hmap_destroy(&actions_per_flow);
+
+    qsort(afs, n_afs, sizeof *afs, compare_actions_for_flow);
+
+    for (i = 0; i < n_afs; i++) {
+        struct actions_for_flow *af = afs[i];
+
+        sort_output_actions(ofpbuf_data(&af->actions),
+                            ofpbuf_size(&af->actions));
+
+        if (af->flow.vlan_tci != htons(0)) {
+            dpctl_print(dpctl_p, "vlan(vid=%"PRIu16",pcp=%d): ",
+                        vlan_tci_to_vid(af->flow.vlan_tci),
+                        vlan_tci_to_pcp(af->flow.vlan_tci));
+        } else {
+            dpctl_print(dpctl_p, "no vlan: ");
+        }
+
+        if (eth_type_mpls(af->flow.dl_type)) {
+            dpctl_print(dpctl_p, "mpls(label=%"PRIu32",tc=%d,ttl=%d): ",
+                        mpls_lse_to_label(af->flow.mpls_lse[0]),
+                        mpls_lse_to_tc(af->flow.mpls_lse[0]),
+                        mpls_lse_to_ttl(af->flow.mpls_lse[0]));
+        } else {
+            dpctl_print(dpctl_p, "no mpls: ");
+        }
+
+        ds_clear(&s);
+        format_odp_actions(&s, ofpbuf_data(&af->actions),
+                           ofpbuf_size(&af->actions));
+        dpctl_print(dpctl_p, ds_cstr(&s));
+
+        ofpbuf_uninit(&af->actions);
+        free(af);
+    }
+    free(afs);
+
+
+out_freeactions:
+    ofpbuf_uninit(&odp_actions);
+out_freekeybuf:
+    ofpbuf_uninit(&keybuf);
+out:
+    simap_destroy(&port_names);
+    ds_destroy(&s);
+
+    return error;
+}
+\f
+typedef int dpctl_command_handler(int argc, const char *argv[],
+                                  struct dpctl_params *);
+struct dpctl_command {
+    const char *name;
+    int min_args;
+    int max_args;
+    dpctl_command_handler *handler;
+};
+
+static const struct dpctl_command all_commands[] = {
+    { "add-dp", 1, INT_MAX, dpctl_add_dp },
+    { "del-dp", 1, 1, dpctl_del_dp },
+    { "add-if", 2, INT_MAX, dpctl_add_if },
+    { "del-if", 2, INT_MAX, dpctl_del_if },
+    { "set-if", 2, INT_MAX, dpctl_set_if },
+    { "dump-dps", 0, 0, dpctl_dump_dps },
+    { "show", 0, INT_MAX, dpctl_show },
+    { "dump-flows", 0, 2, dpctl_dump_flows },
+    { "add-flow", 2, 3, dpctl_add_flow },
+    { "mod-flow", 2, 3, dpctl_mod_flow },
+    { "del-flow", 1, 2, dpctl_del_flow },
+    { "del-flows", 0, 1, dpctl_del_flows },
+    { "help", 0, INT_MAX, dpctl_help },
+
+    /* Undocumented commands for testing. */
+    { "parse-actions", 1, INT_MAX, dpctl_parse_actions },
+    { "normalize-actions", 2, INT_MAX, dpctl_normalize_actions },
+
+    { NULL, 0, 0, NULL },
+};
+
+/* Runs the command designated by argv[0] within the command table specified by
+ * 'commands', which must be terminated by a command whose 'name' member is a
+ * null pointer. */
+int
+dpctl_run_command(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    const struct dpctl_command *p;
+
+    if (argc < 1) {
+        dpctl_error(dpctl_p, 0, "missing command name; use --help for help");
+        return EINVAL;
+    }
+
+    for (p = all_commands; p->name != NULL; p++) {
+        if (!strcmp(p->name, argv[0])) {
+            int n_arg = argc - 1;
+            if (n_arg < p->min_args) {
+                dpctl_error(dpctl_p, 0,
+                            "'%s' command requires at least %d arguments",
+                            p->name, p->min_args);
+                return EINVAL;
+            } else if (n_arg > p->max_args) {
+                dpctl_error(dpctl_p, 0,
+                            "'%s' command takes at most %d arguments",
+                            p->name, p->max_args);
+                return EINVAL;
+            } else {
+                return p->handler(argc, argv, dpctl_p);
+            }
+        }
+    }
+
+    dpctl_error(dpctl_p, 0, "unknown command '%s'; use --help for help",
+                argv[0]);
+    return EINVAL;
+}
+\f
+static void
+dpctl_unixctl_print(void *userdata, bool error OVS_UNUSED, const char *msg)
+{
+    struct ds *ds = userdata;
+    ds_put_cstr(ds, msg);
+}
+
+static void
+dpctl_unixctl_handler(struct unixctl_conn *conn, int argc, const char *argv[],
+                      void *aux)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct dpctl_params dpctl_p;
+    bool opt_parse_err = false;
+
+    dpctl_command_handler *handler = (dpctl_command_handler *) aux;
+
+    dpctl_p.print_statistics = false;
+    dpctl_p.zero_statistics = false;
+    dpctl_p.may_create = false;
+    dpctl_p.verbosity = 0;
+
+    /* Parse options (like getopt). Unfortunately it does
+     * not seem a good idea to call getopt_long() here, since it uses global
+     * variables */
+    while (argc > 1 && !opt_parse_err) {
+        const char *arg = argv[1];
+        if (!strncmp(arg, "--", 2)) {
+            /* Long option */
+            if (!strcmp(arg, "--statistics")) {
+                dpctl_p.print_statistics = true;
+            } else if (!strcmp(arg, "--clear")) {
+                dpctl_p.zero_statistics = true;
+            } else if (!strcmp(arg, "--may-create")) {
+                dpctl_p.may_create = true;
+            } else if (!strcmp(arg, "--more")) {
+                dpctl_p.verbosity++;
+            } else {
+                ds_put_format(&ds, "Unrecognized option %s", argv[1]);
+                opt_parse_err = true;
+            }
+        } else if (arg[0] == '-' && arg[1] != '\0') {
+            /* Short option[s] */
+            const char *opt = &arg[1];
+
+            while (*opt && !opt_parse_err) {
+                switch (*opt) {
+                case 'm':
+                    dpctl_p.verbosity++;
+                    break;
+                case 's':
+                    dpctl_p.print_statistics = true;
+                    break;
+                default:
+                    ds_put_format(&ds, "Unrecognized option -%c", *opt);
+                    opt_parse_err = true;
+                    break;
+                }
+                opt++;
+            }
+        } else {
+            /* Doesn't start with -, not an option */
+            break;
+        }
+
+        if (opt_parse_err) {
+            break;
+        }
+        argv++;
+        argc--;
+    }
+
+    if (!opt_parse_err) {
+        dpctl_p.usage = NULL;
+        dpctl_p.output = dpctl_unixctl_print;
+        dpctl_p.aux = &ds;
+
+        handler(argc, argv, &dpctl_p);
+    }
+
+    unixctl_command_reply(conn, ds_cstr(&ds));
+
+    ds_destroy(&ds);
+}
+
+void
+dpctl_unixctl_register(void)
+{
+    const struct dpctl_command *p;
+
+    for (p = all_commands; p->name != NULL; p++) {
+        char *cmd_name = xasprintf("dpctl/%s", p->name);
+        unixctl_command_register(cmd_name, "", p->min_args, p->max_args,
+                                 dpctl_unixctl_handler, p->handler);
+        free(cmd_name);
+    }
+}
diff --git a/lib/dpctl.h b/lib/dpctl.h
new file mode 100644 (file)
index 0000000..41186f6
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2014 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef DPCTL_H
+#define DPCTL_H 1
+
+#include <stdbool.h>
+
+#include "compiler.h"
+
+struct dpctl_params {
+    /* -s, --statistics: Print port/flow statistics? */
+    bool print_statistics;
+
+    /* --clear: Reset existing statistics to zero when modifying a flow? */
+    bool zero_statistics;
+
+    /* --may-create: Allow mod-flows command to create a new flow? */
+    bool may_create;
+
+    /* -m, --more: Increase output verbosity. */
+    int verbosity;
+
+    /* Callback for printing.  This function is called from dpctl_run_command()
+     * to output data.  The 'aux' parameter is set to the 'aux'
+     * member.  The 'error' parameter is true if 'string' is an error
+     * message, false otherwise */
+    void (*output)(void *aux, bool error, const char *string);
+    void *aux;
+
+    /* 'usage' (if != NULL) gets called for the "help" command. */
+    void (*usage)(void *aux);
+};
+
+int dpctl_run_command(int argc, const char *argv[],
+                      struct dpctl_params *dpctl_p);
+
+void dpctl_unixctl_register(void);
+
+#endif /* dpctl.h */
diff --git a/lib/dpctl.man b/lib/dpctl.man
new file mode 100644 (file)
index 0000000..c678576
--- /dev/null
@@ -0,0 +1,147 @@
+.TP
+\*(DX\fBadd\-dp \fIdp\fR [\fInetdev\fR[\fB,\fIoption\fR]...]
+Creates datapath \fIdp\fR, with a local port also named \fIdp\fR.
+This will fail if a network device \fIdp\fR already exists.
+.IP
+If \fInetdev\fRs are specified, \fB\*(PN\fR adds them to the
+new datapath, just as if \fBadd\-if\fR was specified.
+.
+.TP
+\*(DX\fBdel\-dp \fIdp\fR
+Deletes datapath \fIdp\fR.  If \fIdp\fR is associated with any network
+devices, they are automatically removed.
+.
+.TP
+\*(DX\fBadd\-if \fIdp netdev\fR[\fB,\fIoption\fR]...
+Adds each \fInetdev\fR to the set of network devices datapath
+\fIdp\fR monitors, where \fIdp\fR is the name of an existing
+datapath, and \fInetdev\fR is the name of one of the host's
+network devices, e.g. \fBeth0\fR.  Once a network device has been added
+to a datapath, the datapath has complete ownership of the network device's
+traffic and the network device appears silent to the rest of the
+system.
+.IP
+A \fInetdev\fR may be followed by a comma-separated list of options.
+The following options are currently supported:
+.
+.RS
+.IP "\fBtype=\fItype\fR"
+Specifies the type of port to add.  The default type is \fBsystem\fR.
+.IP "\fBport_no=\fIport\fR"
+Requests a specific port number within the datapath.  If this option is
+not specified then one will be automatically assigned.
+.IP "\fIkey\fB=\fIvalue\fR"
+Adds an arbitrary key-value option to the port's configuration.
+.RE
+.IP
+\fBovs\-vswitchd.conf.db\fR(5) documents the available port types and
+options.
+.
+.IP "\*(DX\fBset\-if \fIdp port\fR[\fB,\fIoption\fR]..."
+Reconfigures each \fIport\fR in \fIdp\fR as specified.  An
+\fIoption\fR of the form \fIkey\fB=\fIvalue\fR adds the specified
+key-value option to the port or overrides an existing key's value.  An
+\fIoption\fR of the form \fIkey\fB=\fR, that is, without a value,
+deletes the key-value named \fIkey\fR.  The type and port number of a
+port cannot be changed, so \fBtype\fR and \fBport_no\fR are only allowed if
+they match the existing configuration.
+.TP
+\*(DX\fBdel\-if \fIdp netdev\fR...
+Removes each \fInetdev\fR from the list of network devices datapath
+\fIdp\fR monitors.
+.
+.TP
+\*(DX\fBdump\-dps\fR
+Prints the name of each configured datapath on a separate line.
+.
+.TP
+.DO "[\fB\-s\fR | \fB\-\-statistics\fR]" "\*(DX\fBshow" "\fR[\fIdp\fR...]"
+Prints a summary of configured datapaths, including their datapath
+numbers and a list of ports connected to each datapath.  (The local
+port is identified as port 0.)  If \fB\-s\fR or \fB\-\-statistics\fR
+is specified, then packet and byte counters are also printed for each
+port.
+.IP
+The datapath numbers consists of flow stats and mega flow mask stats.
+.IP
+The "lookups" row displays three stats related to flow lookup triggered
+by processing incoming packets in the datapath. "hit" displays number
+of packets matches existing flows. "missed" displays the number of
+packets not matching any existing flow and require user space processing.
+"lost" displays number of packets destined for user space process but
+subsequently dropped before reaching userspace. The sum of "hit" and "miss"
+equals to the total number of packets datapath processed.
+.IP
+The "flows" row displays the number of flows in datapath.
+.IP
+The "masks" row displays the mega flow mask stats. This row is omitted
+for datapath not implementing mega flow. "hit" displays the total number
+of masks visited for matching incoming packets. "total" displays number of
+masks in the datapath. "hit/pkt" displays the average number of masks
+visited per packet; the ratio between "hit" and total number of
+packets processed by the datapath".
+.IP
+If one or more datapaths are specified, information on only those
+datapaths are displayed.  Otherwise, \fB\*(PN\fR displays information
+about all configured datapaths.
+.SS "DATAPATH FLOW TABLE DEBUGGING COMMANDS"
+The following commands are primarily useful for debugging Open
+vSwitch.  The flow table entries (both matches and actions) that they
+work with are not OpenFlow flow entries.  Instead, they are different
+and considerably simpler flows maintained by the Open vSwitch kernel
+module.  Use \fBovs\-ofctl\fR(8), instead, to work with OpenFlow flow
+entries.
+.
+.PP
+The \fIdp\fR argument to each of these commands is optional when
+exactly one datapath exists, in which case that datapath is the
+default.  When multiple datapaths exist, then a datapath name is
+required.
+.
+.TP
+.DO "[\fB\-m \fR| \fB\-\-more\fR]" \*(DX\fBdump\-flows\fR "[\fIdp\fR] [\fBfilter=\fIfilter\fR]"
+Prints to the console all flow entries in datapath \fIdp\fR's flow
+table.  Without \fB\-m\fR or \fB\-\-more\fR, output omits match fields
+that a flow wildcards entirely; with \fB\-m\fR or \fB\-\-more\fR,
+output includes all wildcarded fields.
+.IP
+If \fBfilter=\fIfilter\fR is specified, only displays the flows
+that match the \fIfilter\fR. \fIfilter\fR is a flow in the form similiar
+to that accepted by \fBovs\-ofctl\fR(8)'s \fBadd\-flow\fR command. (This is
+not an OpenFlow flow: besides other differences, it never contains wildcards.)
+The \fIfilter\fR is also useful to match wildcarded fields in the datapath
+flow. As an example, \fBfilter='tcp,tp_src=100'\fR will match the
+datapath flow containing '\fBtcp(src=80/0xff00,dst=8080/0xff)\fR'.
+.
+.IP "\*(DX\fBadd\-flow\fR [\fIdp\fR] \fIflow actions\fR"
+.TP
+.DO "[\fB\-\-clear\fR] [\fB\-\-may-create\fR] [\fB\-s\fR | \fB\-\-statistics\fR]" "\*(DX\fBmod\-flow\fR" "[\fIdp\fR] \fIflow actions\fR"
+Adds or modifies a flow in \fIdp\fR's flow table that, when a packet
+matching \fIflow\fR arrives, causes \fIactions\fR to be executed.
+.IP
+The \fBadd\-flow\fR command succeeds only if \fIflow\fR does not
+already exist in \fIdp\fR.  Contrariwise, \fBmod\-flow\fR without
+\fB\-\-may\-create\fR only modifies the actions for an existing flow.
+With \fB\-\-may\-create\fR, \fBmod\-flow\fR will add a new flow or
+modify an existing one.
+.IP
+If \fB\-s\fR or \fB\-\-statistics\fR is specified, then
+\fBmod\-flows\fR prints the modified flow's statistics.  A flow's
+statistics are the number of packets and bytes that have passed
+through the flow, the elapsed time since the flow last processed a
+packet (if ever), and (for TCP flows) the union of the TCP flags
+processed through the flow.
+.IP
+With \fB\-\-clear\fR, \fBmod\-flows\fR zeros out the flow's
+statistics.  The statistics printed if \fB\-s\fR or
+\fB\-\-statistics\fR is also specified are those from just before
+clearing the statistics.
+.
+.TP
+.DO "[\fB\-s\fR | \fB\-\-statistics\fR]" "\*(DX\fBdel\-flow\fR" "[\fIdp\fR] \fIflow\fR"
+Deletes the flow from \fIdp\fR's flow table that matches \fIflow\fR.
+If \fB\-s\fR or \fB\-\-statistics\fR is specified, then
+\fBmod\-flows\fR prints the deleted flow's statistics.
+.
+.IP "\*(DX\fBdel\-flows\fR [\fIdp\fR]"
+Deletes all flow entries from datapath \fIdp\fR's flow table.
index 3501569..43141df 100644 (file)
@@ -24,6 +24,7 @@
 #include <string.h>
 
 #include "coverage.h"
+#include "dpctl.h"
 #include "dynamic-string.h"
 #include "flow.h"
 #include "netdev.h"
@@ -109,6 +110,7 @@ dp_initialize(void)
         for (i = 0; i < ARRAY_SIZE(base_dpif_classes); i++) {
             dp_register_provider(base_dpif_classes[i]);
         }
+        dpctl_unixctl_register();
         ovsthread_once_done(&once);
     }
 }
index a60e611..446b30a 100644 (file)
@@ -127,9 +127,11 @@ utilities/ovs-dpctl-top.8.in:
 utilities/ovs-dpctl.8: \
        utilities/ovs-dpctl.8.in \
        lib/common.man \
+       lib/dpctl.man \
        lib/vlog.man
 utilities/ovs-dpctl.8.in:
 lib/common.man:
+lib/dpctl.man:
 lib/vlog.man:
 
 utilities/ovs-l3ping.8: \
@@ -239,6 +241,7 @@ vswitchd/ovs-vswitchd.8: \
        lib/common.man \
        lib/coverage-unixctl.man \
        lib/daemon.man \
+       lib/dpctl.man \
        lib/memory-unixctl.man \
        lib/service.man \
        lib/ssl-bootstrap.man \
@@ -253,6 +256,7 @@ vswitchd/ovs-vswitchd.8.in:
 lib/common.man:
 lib/coverage-unixctl.man:
 lib/daemon.man:
+lib/dpctl.man:
 lib/memory-unixctl.man:
 lib/service.man:
 lib/ssl-bootstrap.man:
index 14e9ff2..bbf7fbb 100644 (file)
@@ -1,6 +1,9 @@
-.SS "DATAPATH COMMANDS"
-These commands manage logical datapaths.  They are are similar to the
-equivalent \fBovs\-dpctl\fR commands.
+.SS "DATAPATH DEBUGGING COMMANDS"
+These commands query and modify datapaths.  They are are similar to
+\fBovs\-dpctl\fR(8) commands.  \fBdpif/show\fR has the additional
+functionality, beyond \fBdpctl/show\fR of printing OpenFlow port
+numbers.  The other commands are redundant and will be removed in a
+future release.
 .
 .IP "\fBdpif/dump\-dps\fR"
 Prints the name of each configured datapath on a separate line.
index 5a0dd70..84407f0 100644 (file)
@@ -37,150 +37,11 @@ default provider \fBsystem\fR is assumed.
 .PP
 The following commands manage datapaths.
 .
-.TP
-\fBadd\-dp \fIdp\fR [\fInetdev\fR[\fB,\fIoption\fR]...]
-Creates datapath \fIdp\fR, with a local port also named \fIdp\fR.
-This will fail if a network device \fIdp\fR already exists.
-.IP
-If \fInetdev\fRs are specified, \fBovs\-dpctl\fR adds them to the
-new datapath, just as if \fBadd\-if\fR was specified.
-.
-.TP
-\fBdel\-dp \fIdp\fR
-Deletes datapath \fIdp\fR.  If \fIdp\fR is associated with any network
-devices, they are automatically removed.
-.
-.TP
-\fBadd\-if \fIdp netdev\fR[\fB,\fIoption\fR]...
-Adds each \fInetdev\fR to the set of network devices datapath
-\fIdp\fR monitors, where \fIdp\fR is the name of an existing
-datapath, and \fInetdev\fR is the name of one of the host's
-network devices, e.g. \fBeth0\fR.  Once a network device has been added
-to a datapath, the datapath has complete ownership of the network device's
-traffic and the network device appears silent to the rest of the
-system.
-.IP
-A \fInetdev\fR may be followed by a comma-separated list of options.
-The following options are currently supported:
-.
-.RS
-.IP "\fBtype=\fItype\fR"
-Specifies the type of port to add.  The default type is \fBsystem\fR.
-.IP "\fBport_no=\fIport\fR"
-Requests a specific port number within the datapath.  If this option is
-not specified then one will be automatically assigned.
-.IP "\fIkey\fB=\fIvalue\fR"
-Adds an arbitrary key-value option to the port's configuration.
-.RE
-.IP
-\fBovs\-vswitchd.conf.db\fR(5) documents the available port types and
-options.
-.
-.IP "\fBset\-if \fIdp port\fR[\fB,\fIoption\fR]..."
-Reconfigures each \fIport\fR in \fIdp\fR as specified.  An
-\fIoption\fR of the form \fIkey\fB=\fIvalue\fR adds the specified
-key-value option to the port or overrides an existing key's value.  An
-\fIoption\fR of the form \fIkey\fB=\fR, that is, without a value,
-deletes the key-value named \fIkey\fR.  The type and port number of a
-port cannot be changed, so \fBtype\fR and \fBport_no\fR are only allowed if
-they match the existing configuration.
-.TP
-\fBdel\-if \fIdp netdev\fR...
-Removes each \fInetdev\fR from the list of network devices datapath
-\fIdp\fR monitors.
-.
-.TP
-\fBdump\-dps\fR
-Prints the name of each configured datapath on a separate line.
-.
-.TP
-[\fB\-s\fR | \fB\-\-statistics\fR] \fBshow \fR[\fIdp\fR...]
-Prints a summary of configured datapaths, including their datapath
-numbers and a list of ports connected to each datapath.  (The local
-port is identified as port 0.)  If \fB\-s\fR or \fB\-\-statistics\fR
-is specified, then packet and byte counters are also printed for each
-port.
-.IP
-The datapath numbers consists of flow stats and mega flow mask stats.
-.IP
-The "lookups" row displays three stats related to flow lookup triggered
-by processing incoming packets in the datapath. "hit" displays number
-of packets matches existing flows. "missed" displays the number of
-packets not matching any existing flow and require user space processing.
-"lost" displays number of packets destined for user space process but
-subsequently dropped before reaching userspace. The sum of "hit" and "miss"
-equals to the total number of packets datapath processed.
-.IP
-The "flows" row displays the number of flows in datapath.
-.IP
-The "masks" row displays the mega flow mask stats. This row is omitted
-for datapath not implementing mega flow. "hit" displays the total number
-of masks visited for matching incoming packets. "total" displays number of
-masks in the datapath. "hit/pkt" displays the average number of masks
-visited per packet; the ratio between "hit" and total number of
-packets processed by the datapath".
-.IP
-If one or more datapaths are specified, information on only those
-datapaths are displayed.  Otherwise, \fBovs\-dpctl\fR displays information
-about all configured datapaths.
-.SS "DEBUGGING COMMANDS"
-The following commands are primarily useful for debugging Open
-vSwitch.  The flow table entries (both matches and actions) that they
-work with are not OpenFlow flow entries.  Instead, they are different
-and considerably simpler flows maintained by the Open vSwitch kernel
-module.  Use \fBovs\-ofctl\fR(8), instead, to work with OpenFlow flow
-entries.
-.
-.PP
-The \fIdp\fR argument to each of these commands is optional when
-exactly one datapath exists, in which case that datapath is the
-default.  When multiple datapaths exist, then a datapath name is
-required.
-.
-.IP "[\fB\-m \fR| \fB\-\-more\fR] \fBdump\-flows\fR [\fIdp\fR] [\fBfilter=\fIfilter\fR]"
-Prints to the console all flow entries in datapath \fIdp\fR's flow
-table.  Without \fB\-m\fR or \fB\-\-more\fR, output omits match fields
-that a flow wildcards entirely; with \fB\-m\fR or \fB\-\-more\fR,
-output includes all wildcarded fields.
-.IP
-If \fBfilter=\fIfilter\fR is specified, only displays the flows
-that match the \fIfilter\fR. \fIfilter\fR is a flow in the form similiar
-to that accepted by \fBovs\-ofctl\fR(8)'s \fBadd\-flow\fR command. (This is
-not an OpenFlow flow: besides other differences, it never contains wildcards.)
-The \fIfilter\fR is also useful to match wildcarded fields in the datapath
-flow. As an example, \fBfilter='tcp,tp_src=100'\fR will match the
-datapath flow containing '\fBtcp(src=80/0xff00,dst=8080/0xff)\fR'.
-.
-.IP "\fBadd\-flow\fR [\fIdp\fR] \fIflow actions\fR"
-.IQ "[\fB\-\-clear\fR] [\fB\-\-may-create\fR] [\fB\-s\fR | \fB\-\-statistics\fR] \fBmod\-flow\fR [\fIdp\fR] \fIflow actions\fR"
-Adds or modifies a flow in \fIdp\fR's flow table that, when a packet
-matching \fIflow\fR arrives, causes \fIactions\fR to be executed.
-.IP
-The \fBadd\-flow\fR command succeeds only if \fIflow\fR does not
-already exist in \fIdp\fR.  Contrariwise, \fBmod\-flow\fR without
-\fB\-\-may\-create\fR only modifies the actions for an existing flow.
-With \fB\-\-may\-create\fR, \fBmod\-flow\fR will add a new flow or
-modify an existing one.
-.IP
-If \fB\-s\fR or \fB\-\-statistics\fR is specified, then
-\fBmod\-flows\fR prints the modified flow's statistics.  A flow's
-statistics are the number of packets and bytes that have passed
-through the flow, the elapsed time since the flow last processed a
-packet (if ever), and (for TCP flows) the union of the TCP flags
-processed through the flow.
-.IP
-With \fB\-\-clear\fR, \fBmod\-flows\fR zeros out the flow's
-statistics.  The statistics printed if \fB\-s\fR or
-\fB\-\-statistics\fR is also specified are those from just before
-clearing the statistics.
-.
-.IP "[\fB\-s\fR | \fB\-\-statistics\fR] \fBdel\-flow\fR [\fIdp\fR] \fIflow\fR"
-Deletes the flow from \fIdp\fR's flow table that matches \fIflow\fR.
-If \fB\-s\fR or \fB\-\-statistics\fR is specified, then
-\fBmod\-flows\fR prints the deleted flow's statistics.
-.
-.IP "\fBdel\-flows\fR [\fIdp\fR]"
-Deletes all flow entries from datapath \fIdp\fR's flow table.
+.ds DX
+.de DO
+\\$1 \\$2 \\$3
+..
+.so lib/dpctl.man
 .
 .SH OPTIONS
 .IP "\fB\-s\fR"
index 62fc1dd..6c25bfd 100644 (file)
 #include "command-line.h"
 #include "compiler.h"
 #include "dirs.h"
-#include "dpif.h"
-#include "dynamic-string.h"
+#include "dpctl.h"
 #include "fatal-signal.h"
-#include "flow.h"
-#include "match.h"
-#include "netdev.h"
-#include "netlink.h"
 #include "odp-util.h"
 #include "ofp-parse.h"
-#include "ofpbuf.h"
 #include "packets.h"
-#include "shash.h"
-#include "simap.h"
-#include "smap.h"
-#include "sset.h"
 #include "timeval.h"
 #include "util.h"
 #include "vlog.h"
 
-/* -s, --statistics: Print port/flow statistics? */
-static bool print_statistics;
+static struct dpctl_params dpctl_p;
 
-/* --clear: Reset existing statistics to zero when modifying a flow? */
-static bool zero_statistics;
-
-/* --may-create: Allow mod-flows command to create a new flow? */
-static bool may_create;
-
-/* -m, --more: Increase output verbosity. */
-static int verbosity;
-
-static const struct command *get_all_commands(void);
-
-static void usage(void) NO_RETURN;
+static void usage(void *userdata OVS_UNUSED) NO_RETURN;
 static void parse_options(int argc, char *argv[]);
 
+static void
+dpctl_print(void *userdata OVS_UNUSED, bool error, const char *msg)
+{
+    FILE *outfile = error ? stderr : stdout;
+    fputs(msg, outfile);
+}
+
 int
 main(int argc, char *argv[])
 {
+    int error;
     set_program_name(argv[0]);
     parse_options(argc, argv);
     fatal_ignore_sigpipe();
-    run_command(argc - optind, argv + optind, get_all_commands());
-    return 0;
+
+    dpctl_p.output = dpctl_print;
+    dpctl_p.usage = usage;
+
+    error = dpctl_run_command(argc - optind, (const char **) argv + optind,
+                              &dpctl_p);
+    return error ? EXIT_FAILURE : EXIT_SUCCESS;
 }
 
 static void
@@ -111,19 +102,19 @@ parse_options(int argc, char *argv[])
 
         switch (c) {
         case 's':
-            print_statistics = true;
+            dpctl_p.print_statistics = true;
             break;
 
         case OPT_CLEAR:
-            zero_statistics = true;
+            dpctl_p.zero_statistics = true;
             break;
 
         case OPT_MAY_CREATE:
-            may_create = true;
+            dpctl_p.may_create = true;
             break;
 
         case 'm':
-            verbosity++;
+            dpctl_p.verbosity++;
             break;
 
         case 't':
@@ -137,7 +128,7 @@ parse_options(int argc, char *argv[])
             break;
 
         case 'h':
-            usage();
+            usage(NULL);
 
         case 'V':
             ovs_print_version(0, 0);
@@ -156,7 +147,7 @@ parse_options(int argc, char *argv[])
 }
 
 static void
-usage(void)
+usage(void *userdata OVS_UNUSED)
 {
     printf("%s: Open vSwitch datapath management utility\n"
            "usage: %s [OPTIONS] COMMAND [ARG...]\n"
@@ -194,1047 +185,3 @@ usage(void)
     exit(EXIT_SUCCESS);
 }
 
-static void run(int retval, const char *message, ...)
-    PRINTF_FORMAT(2, 3);
-
-static void run(int retval, const char *message, ...)
-{
-    if (retval) {
-        va_list args;
-
-        va_start(args, message);
-        ovs_fatal_valist(retval, message, args);
-    }
-}
-\f
-static void dpctl_add_if(int argc, char *argv[]);
-
-static int if_up(const char *netdev_name)
-{
-    struct netdev *netdev;
-    int retval;
-
-    retval = netdev_open(netdev_name, "system", &netdev);
-    if (!retval) {
-        retval = netdev_turn_flags_on(netdev, NETDEV_UP, NULL);
-        netdev_close(netdev);
-    }
-    return retval;
-}
-
-/* Retrieve the name of the datapath if exactly one exists.  The caller
- * is responsible for freeing the returned string.  If there is not one
- * datapath, aborts with an error message. */
-static char *
-get_one_dp(void)
-{
-    struct sset types;
-    const char *type;
-    char *dp_name = NULL;
-    size_t count = 0;
-
-    sset_init(&types);
-    dp_enumerate_types(&types);
-    SSET_FOR_EACH (type, &types) {
-        struct sset names;
-
-        sset_init(&names);
-        if (!dp_enumerate_names(type, &names)) {
-            count += sset_count(&names);
-            if (!dp_name && count == 1) {
-                dp_name = xasprintf("%s@%s", type, SSET_FIRST(&names));
-            }
-        }
-        sset_destroy(&names);
-    }
-    sset_destroy(&types);
-
-    if (!count) {
-        ovs_fatal(0, "no datapaths exist");
-    } else if (count > 1) {
-        ovs_fatal(0, "multiple datapaths, specify one");
-    }
-
-    return dp_name;
-}
-
-static int
-parsed_dpif_open(const char *arg_, bool create, struct dpif **dpifp)
-{
-    int result;
-    char *name, *type;
-
-    dp_parse_name(arg_, &name, &type);
-
-    if (create) {
-        result = dpif_create(name, type, dpifp);
-    } else {
-        result = dpif_open(name, type, dpifp);
-    }
-
-    free(name);
-    free(type);
-    return result;
-}
-
-static void
-dpctl_add_dp(int argc OVS_UNUSED, char *argv[])
-{
-    struct dpif *dpif;
-    run(parsed_dpif_open(argv[1], true, &dpif), "add_dp");
-    dpif_close(dpif);
-    if (argc > 2) {
-        dpctl_add_if(argc, argv);
-    }
-}
-
-static void
-dpctl_del_dp(int argc OVS_UNUSED, char *argv[])
-{
-    struct dpif *dpif;
-    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
-    run(dpif_delete(dpif), "del_dp");
-    dpif_close(dpif);
-}
-
-static void
-dpctl_add_if(int argc OVS_UNUSED, char *argv[])
-{
-    bool failure = false;
-    struct dpif *dpif;
-    int i;
-
-    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
-    for (i = 2; i < argc; i++) {
-        const char *name, *type;
-        char *save_ptr = NULL;
-        struct netdev *netdev = NULL;
-        struct smap args;
-        odp_port_t port_no = ODPP_NONE;
-        char *option;
-        int error;
-
-        name = strtok_r(argv[i], ",", &save_ptr);
-        type = "system";
-
-        if (!name) {
-            ovs_error(0, "%s is not a valid network device name", argv[i]);
-            failure = true;
-            continue;
-        }
-
-        smap_init(&args);
-        while ((option = strtok_r(NULL, ",", &save_ptr)) != NULL) {
-            char *save_ptr_2 = NULL;
-            char *key, *value;
-
-            key = strtok_r(option, "=", &save_ptr_2);
-            value = strtok_r(NULL, "", &save_ptr_2);
-            if (!value) {
-                value = "";
-            }
-
-            if (!strcmp(key, "type")) {
-                type = value;
-            } else if (!strcmp(key, "port_no")) {
-                port_no = u32_to_odp(atoi(value));
-            } else if (!smap_add_once(&args, key, value)) {
-                ovs_error(0, "duplicate \"%s\" option", key);
-            }
-        }
-
-        error = netdev_open(name, type, &netdev);
-        if (error) {
-            ovs_error(error, "%s: failed to open network device", name);
-            goto next;
-        }
-
-        error = netdev_set_config(netdev, &args, NULL);
-        if (error) {
-            goto next;
-        }
-
-        error = dpif_port_add(dpif, netdev, &port_no);
-        if (error) {
-            ovs_error(error, "adding %s to %s failed", name, argv[1]);
-            goto next;
-        }
-
-        error = if_up(name);
-
-next:
-        netdev_close(netdev);
-        if (error) {
-            failure = true;
-        }
-    }
-    dpif_close(dpif);
-    if (failure) {
-        exit(EXIT_FAILURE);
-    }
-}
-
-static void
-dpctl_set_if(int argc, char *argv[])
-{
-    bool failure = false;
-    struct dpif *dpif;
-    int i;
-
-    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
-    for (i = 2; i < argc; i++) {
-        struct netdev *netdev = NULL;
-        struct dpif_port dpif_port;
-        char *save_ptr = NULL;
-        char *type = NULL;
-        const char *name;
-        struct smap args;
-        odp_port_t port_no;
-        char *option;
-        int error;
-
-        name = strtok_r(argv[i], ",", &save_ptr);
-        if (!name) {
-            ovs_error(0, "%s is not a valid network device name", argv[i]);
-            failure = true;
-            continue;
-        }
-
-        /* Get the port's type from the datapath. */
-        error = dpif_port_query_by_name(dpif, name, &dpif_port);
-        if (error) {
-            ovs_error(error, "%s: failed to query port in %s", name, argv[1]);
-            goto next;
-        }
-        type = xstrdup(dpif_port.type);
-        port_no = dpif_port.port_no;
-        dpif_port_destroy(&dpif_port);
-
-        /* Retrieve its existing configuration. */
-        error = netdev_open(name, type, &netdev);
-        if (error) {
-            ovs_error(error, "%s: failed to open network device", name);
-            goto next;
-        }
-
-        smap_init(&args);
-        error = netdev_get_config(netdev, &args);
-        if (error) {
-            ovs_error(error, "%s: failed to fetch configuration", name);
-            goto next;
-        }
-
-        /* Parse changes to configuration. */
-        while ((option = strtok_r(NULL, ",", &save_ptr)) != NULL) {
-            char *save_ptr_2 = NULL;
-            char *key, *value;
-
-            key = strtok_r(option, "=", &save_ptr_2);
-            value = strtok_r(NULL, "", &save_ptr_2);
-            if (!value) {
-                value = "";
-            }
-
-            if (!strcmp(key, "type")) {
-                if (strcmp(value, type)) {
-                    ovs_error(0, "%s: can't change type from %s to %s",
-                              name, type, value);
-                    failure = true;
-                }
-            } else if (!strcmp(key, "port_no")) {
-                if (port_no != u32_to_odp(atoi(value))) {
-                    ovs_error(0, "%s: can't change port number from "
-                              "%"PRIu32" to %d",
-                              name, port_no, atoi(value));
-                    failure = true;
-                }
-            } else if (value[0] == '\0') {
-                smap_remove(&args, key);
-            } else {
-                smap_replace(&args, key, value);
-            }
-        }
-
-        /* Update configuration. */
-        error = netdev_set_config(netdev, &args, NULL);
-        smap_destroy(&args);
-        if (error) {
-            goto next;
-        }
-
-next:
-        free(type);
-        netdev_close(netdev);
-        if (error) {
-            failure = true;
-        }
-    }
-    dpif_close(dpif);
-    if (failure) {
-        exit(EXIT_FAILURE);
-    }
-}
-
-static bool
-get_port_number(struct dpif *dpif, const char *name, odp_port_t *port)
-{
-    struct dpif_port dpif_port;
-
-    if (!dpif_port_query_by_name(dpif, name, &dpif_port)) {
-        *port = dpif_port.port_no;
-        dpif_port_destroy(&dpif_port);
-        return true;
-    } else {
-        ovs_error(0, "no port named %s", name);
-        return false;
-    }
-}
-
-static void
-dpctl_del_if(int argc OVS_UNUSED, char *argv[])
-{
-    bool failure = false;
-    struct dpif *dpif;
-    int i;
-
-    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
-    for (i = 2; i < argc; i++) {
-        const char *name = argv[i];
-        odp_port_t port;
-        int error;
-
-        if (!name[strspn(name, "0123456789")]) {
-            port = u32_to_odp(atoi(name));
-        } else if (!get_port_number(dpif, name, &port)) {
-            failure = true;
-            continue;
-        }
-
-        error = dpif_port_del(dpif, port);
-        if (error) {
-            ovs_error(error, "deleting port %s from %s failed", name, argv[1]);
-            failure = true;
-        }
-    }
-    dpif_close(dpif);
-    if (failure) {
-        exit(EXIT_FAILURE);
-    }
-}
-
-static void
-print_stat(const char *leader, uint64_t value)
-{
-    fputs(leader, stdout);
-    if (value != UINT64_MAX) {
-        printf("%"PRIu64, value);
-    } else {
-        putchar('?');
-    }
-}
-
-static void
-print_human_size(uint64_t value)
-{
-    if (value == UINT64_MAX) {
-        /* Nothing to do. */
-    } else if (value >= 1024ULL * 1024 * 1024 * 1024) {
-        printf(" (%.1f TiB)", value / (1024.0 * 1024 * 1024 * 1024));
-    } else if (value >= 1024ULL * 1024 * 1024) {
-        printf(" (%.1f GiB)", value / (1024.0 * 1024 * 1024));
-    } else if (value >= 1024ULL * 1024) {
-        printf(" (%.1f MiB)", value / (1024.0 * 1024));
-    } else if (value >= 1024) {
-        printf(" (%.1f KiB)", value / 1024.0);
-    }
-}
-
-static void
-show_dpif(struct dpif *dpif)
-{
-    struct dpif_port_dump dump;
-    struct dpif_port dpif_port;
-    struct dpif_dp_stats stats;
-    struct netdev *netdev;
-
-    printf("%s:\n", dpif_name(dpif));
-    if (!dpif_get_dp_stats(dpif, &stats)) {
-        printf("\tlookups: hit:%"PRIu64" missed:%"PRIu64" lost:%"PRIu64"\n"
-               "\tflows: %"PRIu64"\n",
-               stats.n_hit, stats.n_missed, stats.n_lost, stats.n_flows);
-        if (stats.n_masks != UINT32_MAX) {
-            uint64_t n_pkts = stats.n_hit + stats.n_missed;
-            double avg = n_pkts ? (double) stats.n_mask_hit / n_pkts : 0.0;
-
-            printf("\tmasks: hit:%"PRIu64" total:%"PRIu32" hit/pkt:%.2f\n",
-                   stats.n_mask_hit, stats.n_masks, avg);
-        }
-    }
-
-    DPIF_PORT_FOR_EACH (&dpif_port, &dump, dpif) {
-        printf("\tport %u: %s", dpif_port.port_no, dpif_port.name);
-
-        if (strcmp(dpif_port.type, "system")) {
-            int error;
-
-            printf (" (%s", dpif_port.type);
-
-            error = netdev_open(dpif_port.name, dpif_port.type, &netdev);
-            if (!error) {
-                struct smap config;
-
-                smap_init(&config);
-                error = netdev_get_config(netdev, &config);
-                if (!error) {
-                    const struct smap_node **nodes;
-                    size_t i;
-
-                    nodes = smap_sort(&config);
-                    for (i = 0; i < smap_count(&config); i++) {
-                        const struct smap_node *node = nodes[i];
-                        printf("%c %s=%s", i ? ',' : ':', node->key,
-                               node->value);
-                    }
-                    free(nodes);
-                } else {
-                    printf(", could not retrieve configuration (%s)",
-                           ovs_strerror(error));
-                }
-                smap_destroy(&config);
-
-                netdev_close(netdev);
-            } else {
-                printf(": open failed (%s)", ovs_strerror(error));
-            }
-            putchar(')');
-        }
-        putchar('\n');
-
-        if (print_statistics) {
-            struct netdev_stats s;
-            int error;
-
-            error = netdev_open(dpif_port.name, dpif_port.type, &netdev);
-            if (error) {
-                printf(", open failed (%s)", ovs_strerror(error));
-                continue;
-            }
-            error = netdev_get_stats(netdev, &s);
-            if (error) {
-                printf(", could not retrieve stats (%s)", ovs_strerror(error));
-                continue;
-            }
-
-            netdev_close(netdev);
-            print_stat("\t\tRX packets:", s.rx_packets);
-            print_stat(" errors:", s.rx_errors);
-            print_stat(" dropped:", s.rx_dropped);
-            print_stat(" overruns:", s.rx_over_errors);
-            print_stat(" frame:", s.rx_frame_errors);
-            printf("\n");
-
-            print_stat("\t\tTX packets:", s.tx_packets);
-            print_stat(" errors:", s.tx_errors);
-            print_stat(" dropped:", s.tx_dropped);
-            print_stat(" aborted:", s.tx_aborted_errors);
-            print_stat(" carrier:", s.tx_carrier_errors);
-            printf("\n");
-
-            print_stat("\t\tcollisions:", s.collisions);
-            printf("\n");
-
-            print_stat("\t\tRX bytes:", s.rx_bytes);
-            print_human_size(s.rx_bytes);
-            print_stat("  TX bytes:", s.tx_bytes);
-            print_human_size(s.tx_bytes);
-            printf("\n");
-        }
-    }
-    dpif_close(dpif);
-}
-
-static void
-dpctl_show(int argc, char *argv[])
-{
-    bool failure = false;
-    if (argc > 1) {
-        int i;
-        for (i = 1; i < argc; i++) {
-            const char *name = argv[i];
-            struct dpif *dpif;
-            int error;
-
-            error = parsed_dpif_open(name, false, &dpif);
-            if (!error) {
-                show_dpif(dpif);
-            } else {
-                ovs_error(error, "opening datapath %s failed", name);
-                failure = true;
-            }
-        }
-    } else {
-        struct sset types;
-        const char *type;
-
-        sset_init(&types);
-        dp_enumerate_types(&types);
-        SSET_FOR_EACH (type, &types) {
-            struct sset names;
-            const char *name;
-
-            sset_init(&names);
-            if (dp_enumerate_names(type, &names)) {
-                failure = true;
-                continue;
-            }
-            SSET_FOR_EACH (name, &names) {
-                struct dpif *dpif;
-                int error;
-
-                error = dpif_open(name, type, &dpif);
-                if (!error) {
-                    show_dpif(dpif);
-                } else {
-                    ovs_error(error, "opening datapath %s failed", name);
-                    failure = true;
-                }
-            }
-            sset_destroy(&names);
-        }
-        sset_destroy(&types);
-    }
-    if (failure) {
-        exit(EXIT_FAILURE);
-    }
-}
-
-static void
-dpctl_dump_dps(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
-{
-    struct sset dpif_names, dpif_types;
-    const char *type;
-    int error = 0;
-
-    sset_init(&dpif_names);
-    sset_init(&dpif_types);
-    dp_enumerate_types(&dpif_types);
-
-    SSET_FOR_EACH (type, &dpif_types) {
-        const char *name;
-        int retval;
-
-        retval = dp_enumerate_names(type, &dpif_names);
-        if (retval) {
-            error = retval;
-        }
-
-        SSET_FOR_EACH (name, &dpif_names) {
-            struct dpif *dpif;
-            if (!dpif_open(name, type, &dpif)) {
-                printf("%s\n", dpif_name(dpif));
-                dpif_close(dpif);
-            }
-        }
-    }
-
-    sset_destroy(&dpif_names);
-    sset_destroy(&dpif_types);
-    if (error) {
-        exit(EXIT_FAILURE);
-    }
-}
-
-static void
-dpctl_dump_flows(int argc, char *argv[])
-{
-    struct dpif *dpif;
-    struct ds ds;
-    char *name;
-
-    char *filter = NULL;
-    struct flow flow_filter;
-    struct flow_wildcards wc_filter;
-
-    struct dpif_port_dump port_dump;
-    struct dpif_port dpif_port;
-    struct hmap portno_names;
-    struct simap names_portno;
-
-    struct dpif_flow_dump_thread *flow_dump_thread;
-    struct dpif_flow_dump *flow_dump;
-    struct dpif_flow f;
-
-    int error;
-
-    if (argc > 1 && !strncmp(argv[argc - 1], "filter=", 7)) {
-        filter = xstrdup(argv[--argc] + 7);
-    }
-    name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp();
-
-    run(parsed_dpif_open(name, false, &dpif), "opening datapath");
-    free(name);
-
-    hmap_init(&portno_names);
-    simap_init(&names_portno);
-    DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
-        odp_portno_names_set(&portno_names, dpif_port.port_no, dpif_port.name);
-        simap_put(&names_portno, dpif_port.name,
-                  odp_to_u32(dpif_port.port_no));
-    }
-
-    if (filter) {
-        char *err = parse_ofp_exact_flow(&flow_filter, &wc_filter.masks,
-                                         filter, &names_portno);
-        if (err) {
-            ovs_fatal(0, "Failed to parse filter (%s)", err);
-        }
-    }
-
-    ds_init(&ds);
-    flow_dump = dpif_flow_dump_create(dpif);
-    flow_dump_thread = dpif_flow_dump_thread_create(flow_dump);
-    while (dpif_flow_dump_next(flow_dump_thread, &f, 1)) {
-        if (filter) {
-            struct flow flow;
-            struct flow_wildcards wc;
-            struct match match, match_filter;
-            struct minimatch minimatch;
-
-            odp_flow_key_to_flow(f.key, f.key_len, &flow);
-            odp_flow_key_to_mask(f.mask, f.mask_len, &wc.masks, &flow);
-            match_init(&match, &flow, &wc);
-
-            match_init(&match_filter, &flow_filter, &wc);
-            match_init(&match_filter, &match_filter.flow, &wc_filter);
-            minimatch_init(&minimatch, &match_filter);
-
-            if (!minimatch_matches_flow(&minimatch, &match.flow)) {
-                minimatch_destroy(&minimatch);
-                continue;
-            }
-            minimatch_destroy(&minimatch);
-        }
-        ds_clear(&ds);
-        odp_flow_format(f.key, f.key_len, f.mask, f.mask_len,
-                        &portno_names, &ds, verbosity);
-        ds_put_cstr(&ds, ", ");
-
-        dpif_flow_stats_format(&f.stats, &ds);
-        ds_put_cstr(&ds, ", actions:");
-        format_odp_actions(&ds, f.actions, f.actions_len);
-        printf("%s\n", ds_cstr(&ds));
-    }
-    dpif_flow_dump_thread_destroy(flow_dump_thread);
-    error = dpif_flow_dump_destroy(flow_dump);
-
-    if (error) {
-        ovs_fatal(error, "Failed to dump flows from datapath");
-    }
-    free(filter);
-    odp_portno_names_destroy(&portno_names);
-    hmap_destroy(&portno_names);
-    simap_destroy(&names_portno);
-    ds_destroy(&ds);
-    dpif_close(dpif);
-}
-
-static void
-dpctl_put_flow(int argc, char *argv[], enum dpif_flow_put_flags flags)
-{
-    const char *key_s = argv[argc - 2];
-    const char *actions_s = argv[argc - 1];
-    struct dpif_flow_stats stats;
-    struct dpif_port dpif_port;
-    struct dpif_port_dump port_dump;
-    struct ofpbuf actions;
-    struct ofpbuf key;
-    struct ofpbuf mask;
-    struct dpif *dpif;
-    struct ds s;
-    char *dp_name;
-    struct simap port_names;
-
-    dp_name = argc == 4 ? xstrdup(argv[1]) : get_one_dp();
-    run(parsed_dpif_open(dp_name, false, &dpif), "opening datapath");
-    free(dp_name);
-
-
-    simap_init(&port_names);
-    DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
-        simap_put(&port_names, dpif_port.name, odp_to_u32(dpif_port.port_no));
-    }
-
-    ds_init(&s);
-    ofpbuf_init(&key, 0);
-    ofpbuf_init(&mask, 0);
-    run(odp_flow_from_string(key_s, &port_names, &key, &mask),
-        "parsing flow key");
-
-    simap_destroy(&port_names);
-
-    ofpbuf_init(&actions, 0);
-    run(odp_actions_from_string(actions_s, NULL, &actions), "parsing actions");
-
-    run(dpif_flow_put(dpif, flags,
-                      ofpbuf_data(&key), ofpbuf_size(&key),
-                      ofpbuf_size(&mask) == 0 ? NULL : ofpbuf_data(&mask),
-                      ofpbuf_size(&mask),
-                      ofpbuf_data(&actions), ofpbuf_size(&actions),
-                      print_statistics ? &stats : NULL),
-        "updating flow table");
-
-    ofpbuf_uninit(&key);
-    ofpbuf_uninit(&mask);
-    ofpbuf_uninit(&actions);
-
-    if (print_statistics) {
-        struct ds s;
-
-        ds_init(&s);
-        dpif_flow_stats_format(&stats, &s);
-        puts(ds_cstr(&s));
-        ds_destroy(&s);
-    }
-}
-
-static void
-dpctl_add_flow(int argc, char *argv[])
-{
-    dpctl_put_flow(argc, argv, DPIF_FP_CREATE);
-}
-
-static void
-dpctl_mod_flow(int argc OVS_UNUSED, char *argv[])
-{
-    enum dpif_flow_put_flags flags;
-
-    flags = DPIF_FP_MODIFY;
-    if (may_create) {
-        flags |= DPIF_FP_CREATE;
-    }
-    if (zero_statistics) {
-        flags |= DPIF_FP_ZERO_STATS;
-    }
-
-    dpctl_put_flow(argc, argv, flags);
-}
-
-static void
-dpctl_del_flow(int argc, char *argv[])
-{
-    const char *key_s = argv[argc - 1];
-    struct dpif_flow_stats stats;
-    struct dpif_port dpif_port;
-    struct dpif_port_dump port_dump;
-    struct ofpbuf key;
-    struct ofpbuf mask; /* To be ignored. */
-    struct dpif *dpif;
-    char *dp_name;
-    struct simap port_names;
-
-    dp_name = argc == 3 ? xstrdup(argv[1]) : get_one_dp();
-    run(parsed_dpif_open(dp_name, false, &dpif), "opening datapath");
-    free(dp_name);
-
-    simap_init(&port_names);
-    DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
-        simap_put(&port_names, dpif_port.name, odp_to_u32(dpif_port.port_no));
-    }
-
-    ofpbuf_init(&key, 0);
-    ofpbuf_init(&mask, 0);
-    run(odp_flow_from_string(key_s, &port_names, &key, &mask), "parsing flow key");
-
-    run(dpif_flow_del(dpif,
-                      ofpbuf_data(&key), ofpbuf_size(&key),
-                      print_statistics ? &stats : NULL), "deleting flow");
-
-    simap_destroy(&port_names);
-    ofpbuf_uninit(&key);
-    ofpbuf_uninit(&mask);
-
-    if (print_statistics) {
-        struct ds s;
-
-        ds_init(&s);
-        dpif_flow_stats_format(&stats, &s);
-        puts(ds_cstr(&s));
-        ds_destroy(&s);
-    }
-}
-
-static void
-dpctl_del_flows(int argc, char *argv[])
-{
-    struct dpif *dpif;
-    char *name;
-
-    name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp();
-    run(parsed_dpif_open(name, false, &dpif), "opening datapath");
-    free(name);
-
-    run(dpif_flow_flush(dpif), "deleting all flows");
-    dpif_close(dpif);
-}
-
-static void
-dpctl_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
-{
-    usage();
-}
-\f
-/* Undocumented commands for unit testing. */
-
-static void
-dpctl_parse_actions(int argc, char *argv[])
-{
-    int i;
-
-    for (i = 1; i < argc; i++) {
-        struct ofpbuf actions;
-        struct ds s;
-
-        ofpbuf_init(&actions, 0);
-        run(odp_actions_from_string(argv[i], NULL, &actions),
-            "odp_actions_from_string");
-
-        ds_init(&s);
-        format_odp_actions(&s, ofpbuf_data(&actions), ofpbuf_size(&actions));
-        puts(ds_cstr(&s));
-        ds_destroy(&s);
-
-        ofpbuf_uninit(&actions);
-    }
-}
-
-struct actions_for_flow {
-    struct hmap_node hmap_node;
-    struct flow flow;
-    struct ofpbuf actions;
-};
-
-static struct actions_for_flow *
-get_actions_for_flow(struct hmap *actions_per_flow, const struct flow *flow)
-{
-    uint32_t hash = flow_hash(flow, 0);
-    struct actions_for_flow *af;
-
-    HMAP_FOR_EACH_WITH_HASH (af, hmap_node, hash, actions_per_flow) {
-        if (flow_equal(&af->flow, flow)) {
-            return af;
-        }
-    }
-
-    af = xmalloc(sizeof *af);
-    af->flow = *flow;
-    ofpbuf_init(&af->actions, 0);
-    hmap_insert(actions_per_flow, &af->hmap_node, hash);
-    return af;
-}
-
-static int
-compare_actions_for_flow(const void *a_, const void *b_)
-{
-    struct actions_for_flow *const *a = a_;
-    struct actions_for_flow *const *b = b_;
-
-    return flow_compare_3way(&(*a)->flow, &(*b)->flow);
-}
-
-static int
-compare_output_actions(const void *a_, const void *b_)
-{
-    const struct nlattr *a = a_;
-    const struct nlattr *b = b_;
-    uint32_t a_port = nl_attr_get_u32(a);
-    uint32_t b_port = nl_attr_get_u32(b);
-
-    return a_port < b_port ? -1 : a_port > b_port;
-}
-
-static void
-sort_output_actions__(struct nlattr *first, struct nlattr *end)
-{
-    size_t bytes = (uint8_t *) end - (uint8_t *) first;
-    size_t n = bytes / NL_A_U32_SIZE;
-
-    ovs_assert(bytes % NL_A_U32_SIZE == 0);
-    qsort(first, n, NL_A_U32_SIZE, compare_output_actions);
-}
-
-static void
-sort_output_actions(struct nlattr *actions, size_t length)
-{
-    struct nlattr *first_output = NULL;
-    struct nlattr *a;
-    int left;
-
-    NL_ATTR_FOR_EACH (a, left, actions, length) {
-        if (nl_attr_type(a) == OVS_ACTION_ATTR_OUTPUT) {
-            if (!first_output) {
-                first_output = a;
-            }
-        } else {
-            if (first_output) {
-                sort_output_actions__(first_output, a);
-                first_output = NULL;
-            }
-        }
-    }
-    if (first_output) {
-        uint8_t *end = (uint8_t *) actions + length;
-        sort_output_actions__(first_output,
-                              ALIGNED_CAST(struct nlattr *, end));
-    }
-}
-
-/* usage: "ovs-dpctl normalize-actions FLOW ACTIONS" where FLOW and ACTIONS
- * have the syntax used by "ovs-dpctl dump-flows".
- *
- * This command prints ACTIONS in a format that shows what happens for each
- * VLAN, independent of the order of the ACTIONS.  For example, there is more
- * than one way to output a packet on VLANs 9 and 11, but this command will
- * print the same output for any form.
- *
- * The idea here generalizes beyond VLANs (e.g. to setting other fields) but
- * so far the implementation only covers VLANs. */
-static void
-dpctl_normalize_actions(int argc, char *argv[])
-{
-    struct simap port_names;
-    struct ofpbuf keybuf;
-    struct flow flow;
-    struct ofpbuf odp_actions;
-    struct hmap actions_per_flow;
-    struct actions_for_flow **afs;
-    struct actions_for_flow *af;
-    struct nlattr *a;
-    size_t n_afs;
-    struct ds s;
-    int left;
-    int i;
-
-    ds_init(&s);
-
-    simap_init(&port_names);
-    for (i = 3; i < argc; i++) {
-        char name[16];
-        int number;
-
-        if (ovs_scan(argv[i], "%15[^=]=%d", name, &number)) {
-            uintptr_t n = number;
-            simap_put(&port_names, name, n);
-        } else {
-            ovs_fatal(0, "%s: expected NAME=NUMBER", argv[i]);
-        }
-    }
-
-    /* Parse flow key. */
-    ofpbuf_init(&keybuf, 0);
-    run(odp_flow_from_string(argv[1], &port_names, &keybuf, NULL),
-        "odp_flow_key_from_string");
-
-    ds_clear(&s);
-    odp_flow_format(ofpbuf_data(&keybuf), ofpbuf_size(&keybuf), NULL, 0, NULL, &s, verbosity);
-    printf("input flow: %s\n", ds_cstr(&s));
-
-    run(odp_flow_key_to_flow(ofpbuf_data(&keybuf), ofpbuf_size(&keybuf), &flow),
-        "odp_flow_key_to_flow");
-    ofpbuf_uninit(&keybuf);
-
-    /* Parse actions. */
-    ofpbuf_init(&odp_actions, 0);
-    run(odp_actions_from_string(argv[2], &port_names, &odp_actions),
-        "odp_actions_from_string");
-    simap_destroy(&port_names);
-
-    if (verbosity) {
-        ds_clear(&s);
-        format_odp_actions(&s, ofpbuf_data(&odp_actions), ofpbuf_size(&odp_actions));
-        printf("input actions: %s\n", ds_cstr(&s));
-    }
-
-    hmap_init(&actions_per_flow);
-    NL_ATTR_FOR_EACH (a, left, ofpbuf_data(&odp_actions), ofpbuf_size(&odp_actions)) {
-        const struct ovs_action_push_vlan *push;
-        switch(nl_attr_type(a)) {
-        case OVS_ACTION_ATTR_POP_VLAN:
-            flow.vlan_tci = htons(0);
-            continue;
-
-        case OVS_ACTION_ATTR_PUSH_VLAN:
-            push = nl_attr_get_unspec(a, sizeof *push);
-            flow.vlan_tci = push->vlan_tci;
-            continue;
-        }
-
-        af = get_actions_for_flow(&actions_per_flow, &flow);
-        nl_msg_put_unspec(&af->actions, nl_attr_type(a),
-                          nl_attr_get(a), nl_attr_get_size(a));
-    }
-
-    n_afs = hmap_count(&actions_per_flow);
-    afs = xmalloc(n_afs * sizeof *afs);
-    i = 0;
-    HMAP_FOR_EACH (af, hmap_node, &actions_per_flow) {
-        afs[i++] = af;
-    }
-    ovs_assert(i == n_afs);
-
-    qsort(afs, n_afs, sizeof *afs, compare_actions_for_flow);
-
-    for (i = 0; i < n_afs; i++) {
-        const struct actions_for_flow *af = afs[i];
-
-        sort_output_actions(ofpbuf_data(&af->actions), ofpbuf_size(&af->actions));
-
-        if (af->flow.vlan_tci != htons(0)) {
-            printf("vlan(vid=%"PRIu16",pcp=%d): ",
-                   vlan_tci_to_vid(af->flow.vlan_tci),
-                   vlan_tci_to_pcp(af->flow.vlan_tci));
-        } else {
-            printf("no vlan: ");
-        }
-
-        if (eth_type_mpls(af->flow.dl_type)) {
-            printf("mpls(label=%"PRIu32",tc=%d,ttl=%d): ",
-                   mpls_lse_to_label(af->flow.mpls_lse[0]),
-                   mpls_lse_to_tc(af->flow.mpls_lse[0]),
-                   mpls_lse_to_ttl(af->flow.mpls_lse[0]));
-        } else {
-            printf("no mpls: ");
-        }
-
-        ds_clear(&s);
-        format_odp_actions(&s, ofpbuf_data(&af->actions), ofpbuf_size(&af->actions));
-        puts(ds_cstr(&s));
-    }
-    ds_destroy(&s);
-}
-
-static const struct command all_commands[] = {
-    { "add-dp", 1, INT_MAX, dpctl_add_dp },
-    { "del-dp", 1, 1, dpctl_del_dp },
-    { "add-if", 2, INT_MAX, dpctl_add_if },
-    { "del-if", 2, INT_MAX, dpctl_del_if },
-    { "set-if", 2, INT_MAX, dpctl_set_if },
-    { "dump-dps", 0, 0, dpctl_dump_dps },
-    { "show", 0, INT_MAX, dpctl_show },
-    { "dump-flows", 0, 2, dpctl_dump_flows },
-    { "add-flow", 2, 3, dpctl_add_flow },
-    { "mod-flow", 2, 3, dpctl_mod_flow },
-    { "del-flow", 1, 2, dpctl_del_flow },
-    { "del-flows", 0, 1, dpctl_del_flows },
-    { "help", 0, INT_MAX, dpctl_help },
-
-    /* Undocumented commands for testing. */
-    { "parse-actions", 1, INT_MAX, dpctl_parse_actions },
-    { "normalize-actions", 2, INT_MAX, dpctl_normalize_actions },
-
-    { NULL, 0, 0, NULL },
-};
-
-static const struct command *get_all_commands(void)
-{
-    return all_commands;
-}
index e5713e7..0fd13e3 100644 (file)
@@ -216,6 +216,21 @@ whether it is attached or detached, port id and priority, actor
 information, and partner information.  If \fIport\fR is not specified,
 then displays detailed information about all interfaces with CFM
 enabled.
+.SS "DPCTL DATAPATH DEBUGGING COMMANDS"
+The primary way to configure \fBovs\-vswitchd\fR is through the Open
+vSwitch database, e.g. using \fBovs\-vsctl\fR(8).  These commands
+provide a debugging interface for managing datapaths.  They implement
+the same features (and syntax) as \fBovs\-dpctl\fR(8).  Unlike
+\fBovs\-dpctl\fR(8), these commands work with datapaths that are
+integrated into \fBovs\-vswitchd\fR (e.g. the \fBnetdev\fR datapath
+type).
+.PP
+.
+.ds DX \fBdpctl/\fR
+.de DO
+\\$2 \\$1 \\$3
+..
+.so lib/dpctl.man
 .
 .so ofproto/ofproto-dpif-unixctl.man
 .so ofproto/ofproto-unixctl.man