]> git.proxmox.com Git - mirror_ovs.git/blobdiff - lib/dpctl.c
dpctl: Fix dpctl process command parameter error.
[mirror_ovs.git] / lib / dpctl.c
index a28ded98f79fae731dfae5ea3d0de844c7e06441..ef8ae7402f4e8790017e2b8d15bfad1750707e08 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008-2017 Nicira, Inc.
+ * Copyright (c) 2008-2019 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  */
 
 #include <config.h>
+#include <sys/types.h>
+#include <netinet/in.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 "dirs.h"
 #include "dpctl.h"
 #include "dpif.h"
+#include "dpif-provider.h"
 #include "openvswitch/dynamic-string.h"
 #include "flow.h"
 #include "openvswitch/match.h"
 #include "netdev.h"
-#include "netdev-dpdk.h"
 #include "netlink.h"
 #include "odp-util.h"
 #include "openvswitch/ofpbuf.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
-#include "openvswitch/ofp-parse.h"
-#include "openvswitch/vlog.h"
-VLOG_DEFINE_THIS_MODULE(dpctl);
+#include "openvswitch/ofp-flow.h"
+#include "openvswitch/ofp-port.h"
+
+enum {
+    DPCTL_FLOWS_ADD = 0,
+    DPCTL_FLOWS_DEL,
+    DPCTL_FLOWS_MOD
+};
 
 typedef int dpctl_command_handler(int argc, const char *argv[],
                                   struct dpctl_params *);
@@ -185,6 +191,68 @@ parsed_dpif_open(const char *arg_, bool create, struct dpif **dpifp)
     return result;
 }
 
+static bool
+dp_exists(const char *queried_dp)
+{
+    char *queried_name, *queried_type;
+    dp_parse_name(queried_dp, &queried_name, &queried_type);
+    struct sset dpif_names = SSET_INITIALIZER(&dpif_names),
+                dpif_types = SSET_INITIALIZER(&dpif_types);
+    dp_enumerate_types(&dpif_types);
+
+    bool found = (sset_contains(&dpif_types, queried_type) &&
+                  !dp_enumerate_names(queried_type, &dpif_names) &&
+                  sset_contains(&dpif_names, queried_name));
+
+    sset_destroy(&dpif_names);
+    sset_destroy(&dpif_types);
+    free(queried_name);
+    free(queried_type);
+    return found;
+}
+
+static bool
+dp_arg_exists(int argc, const char *argv[])
+{
+    return argc > 1 && dp_exists(argv[1]);
+}
+
+/* Open a dpif with an optional name argument.
+ *
+ * The datapath name is not a mandatory parameter for this command.  If it is
+ * not specified, we retrieve it from the current setup, assuming only one
+ * exists.  On success stores the opened dpif in '*dpifp'.  */
+static int
+opt_dpif_open(int argc, const char *argv[], struct dpctl_params *dpctl_p,
+              int max_args, struct dpif **dpifp)
+{
+    char *dpname;
+
+    if (dp_arg_exists(argc, argv)) {
+        dpname = xstrdup(argv[1]);
+    } else if (argc != max_args) {
+        dpname = get_one_dp(dpctl_p);
+    } else {
+        /* If the arguments are the maximum possible number and there is no
+         * valid datapath argument, then we fall into the case of dpname is
+         * NULL, since this is an error. */
+        dpname = NULL;
+    }
+
+    int error = 0;
+    if (!dpname) {
+        error = EINVAL;
+        dpctl_error(dpctl_p, error, "datapath not found");
+    } else {
+        error = parsed_dpif_open(dpname, false, dpifp);
+        free(dpname);
+        if (error) {
+            dpctl_error(dpctl_p, error, "opening datapath");
+        }
+    }
+    return error;
+}
+
 static int
 dpctl_add_dp(int argc, const char *argv[],
              struct dpctl_params *dpctl_p)
@@ -533,14 +601,14 @@ show_dpif(struct dpif *dpif, struct dpctl_params *dpctl_p)
 
     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",
+        dpctl_print(dpctl_p, "  lookups: hit:%"PRIu64" missed:%"PRIu64
+                             " lost:%"PRIu64"\n  flows: %"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
+            dpctl_print(dpctl_p, "  masks: hit:%"PRIu64" total:%"PRIu32
                                  " hit/pkt:%.2f\n",
                         stats.n_mask_hit, stats.n_masks, avg);
         }
@@ -567,7 +635,7 @@ show_dpif(struct dpif *dpif, struct dpctl_params *dpctl_p)
             continue;
         }
 
-        dpctl_print(dpctl_p, "\tport %u: %s",
+        dpctl_print(dpctl_p, "  port %u: %s",
                     dpif_port.port_no, dpif_port.name);
 
         if (strcmp(dpif_port.type, "system")) {
@@ -618,24 +686,24 @@ show_dpif(struct dpif *dpif, struct dpctl_params *dpctl_p)
             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, "    RX 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, "    TX 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);
+                print_stat(dpctl_p, "    collisions:", s.collisions);
                 dpctl_print(dpctl_p, "\n");
 
-                print_stat(dpctl_p, "\t\tRX bytes:", s.rx_bytes);
+                print_stat(dpctl_p, "    RX 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);
@@ -744,7 +812,7 @@ dpctl_dump_dps(int argc OVS_UNUSED, const char *argv[] OVS_UNUSED,
 
 static void
 format_dpif_flow(struct ds *ds, const struct dpif_flow *f, struct hmap *ports,
-                 char *type, struct dpctl_params *dpctl_p)
+                 struct dpctl_params *dpctl_p)
 {
     if (dpctl_p->verbosity && f->ufid_present) {
         odp_format_ufid(&f->ufid, ds);
@@ -755,18 +823,122 @@ format_dpif_flow(struct ds *ds, const struct dpif_flow *f, struct hmap *ports,
     ds_put_cstr(ds, ", ");
 
     dpif_flow_stats_format(&f->stats, ds);
-    if (dpctl_p->verbosity && !type && f->offloaded) {
-        ds_put_cstr(ds, ", offloaded:yes");
+    if (dpctl_p->verbosity && f->attrs.offloaded) {
+        if (f->attrs.dp_layer && !strcmp(f->attrs.dp_layer, "ovs")) {
+            ds_put_cstr(ds, ", offloaded:partial");
+        } else {
+            ds_put_cstr(ds, ", offloaded:yes");
+        }
+    }
+    if (dpctl_p->verbosity && f->attrs.dp_layer) {
+        ds_put_format(ds, ", dp:%s", f->attrs.dp_layer);
     }
     ds_put_cstr(ds, ", actions:");
     format_odp_actions(ds, f->actions, f->actions_len, ports);
+    if (dpctl_p->verbosity && f->attrs.dp_extra_info) {
+        ds_put_format(ds, ", dp-extra-info:%s", f->attrs.dp_extra_info);
+    }
 }
 
-static char *supported_dump_types[] = {
-    "offloaded",
-    "ovs",
+struct dump_types {
+    bool ovs;
+    bool tc;
+    bool dpdk;
+    bool offloaded;
+    bool non_offloaded;
+    bool partially_offloaded;
 };
 
+static void
+enable_all_dump_types(struct dump_types *dump_types)
+{
+    dump_types->ovs = true;
+    dump_types->tc = true;
+    dump_types->dpdk = true;
+    dump_types->offloaded = true;
+    dump_types->non_offloaded = true;
+    dump_types->partially_offloaded = true;
+}
+
+static int
+populate_dump_types(char *types_list, struct dump_types *dump_types,
+                    struct dpctl_params *dpctl_p)
+{
+    if (!types_list) {
+        enable_all_dump_types(dump_types);
+        return 0;
+    }
+
+    char *current_type;
+
+    while (types_list && types_list[0] != '\0') {
+        current_type = types_list;
+        size_t type_len = strcspn(current_type, ",");
+
+        types_list += type_len + (types_list[type_len] != '\0');
+        current_type[type_len] = '\0';
+
+        if (!strcmp(current_type, "ovs")) {
+            dump_types->ovs = true;
+        } else if (!strcmp(current_type, "tc")) {
+            dump_types->tc = true;
+        } else if (!strcmp(current_type, "dpdk")) {
+            dump_types->dpdk = true;
+        } else if (!strcmp(current_type, "offloaded")) {
+            dump_types->offloaded = true;
+        } else if (!strcmp(current_type, "non-offloaded")) {
+            dump_types->non_offloaded = true;
+        } else if (!strcmp(current_type, "partially-offloaded")) {
+            dump_types->partially_offloaded = true;
+        } else if (!strcmp(current_type, "all")) {
+            enable_all_dump_types(dump_types);
+        } else {
+            dpctl_error(dpctl_p, EINVAL, "Failed to parse type (%s)",
+                        current_type);
+            return EINVAL;
+        }
+    }
+    return 0;
+}
+
+static void
+determine_dpif_flow_dump_types(struct dump_types *dump_types,
+                               struct dpif_flow_dump_types *dpif_dump_types)
+{
+    dpif_dump_types->ovs_flows = dump_types->ovs || dump_types->non_offloaded;
+    dpif_dump_types->netdev_flows = dump_types->tc || dump_types->offloaded
+                                    || dump_types->non_offloaded
+                                    || dump_types->dpdk
+                                    || dump_types->partially_offloaded;
+}
+
+static bool
+flow_passes_type_filter(const struct dpif_flow *f,
+                        struct dump_types *dump_types)
+{
+    if (dump_types->ovs && !strcmp(f->attrs.dp_layer, "ovs")) {
+        return true;
+    }
+    if (dump_types->tc && !strcmp(f->attrs.dp_layer, "tc")) {
+        return true;
+    }
+    if (dump_types->dpdk && !strcmp(f->attrs.dp_layer, "dpdk")) {
+        return true;
+    }
+    if (dump_types->offloaded && f->attrs.offloaded &&
+        strcmp(f->attrs.dp_layer, "ovs")) {
+        return true;
+    }
+    if (dump_types->partially_offloaded && f->attrs.offloaded &&
+        !strcmp(f->attrs.dp_layer, "ovs")) {
+        return true;
+    }
+    if (dump_types->non_offloaded && !(f->attrs.offloaded)) {
+        return true;
+    }
+    return false;
+}
+
 static struct hmap *
 dpctl_get_portno_names(struct dpif *dpif, const struct dpctl_params *dpctl_p)
 {
@@ -802,17 +974,19 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
 {
     struct dpif *dpif;
     struct ds ds;
-    char *name;
 
     char *filter = NULL;
-    char *type = NULL;
     struct flow flow_filter;
     struct flow_wildcards wc_filter;
+    char *types_list = NULL;
+    struct dump_types dump_types;
+    struct dpif_flow_dump_types dpif_dump_types;
 
     struct dpif_flow_dump_thread *flow_dump_thread;
     struct dpif_flow_dump *flow_dump;
     struct dpif_flow f;
     int pmd_id = PMD_ID_NULL;
+    bool pmd_id_filter = false;
     int lastargc = 0;
     int error;
 
@@ -820,24 +994,30 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
         lastargc = argc;
         if (!strncmp(argv[argc - 1], "filter=", 7) && !filter) {
             filter = xstrdup(argv[--argc] + 7);
-        } else if (!strncmp(argv[argc - 1], "type=", 5) && !type) {
-            type = xstrdup(argv[--argc] + 5);
-        }
-    }
+        } else if (!strncmp(argv[argc - 1], "type=", 5) && !types_list) {
+            if (!dpctl_p->is_appctl) {
+                dpctl_error(dpctl_p, 0,
+                            "Invalid argument 'type'. "
+                            "Use 'ovs-appctl dpctl/dump-flows' instead.");
+                error = EINVAL;
+                goto out_free;
+            }
+            types_list = xstrdup(argv[--argc] + 5);
+        } else if (!strncmp(argv[argc - 1], "pmd=", 4)) {
+            if (!ovs_scan(argv[--argc], "pmd=%d", &pmd_id)) {
+                error = EINVAL;
+                goto out_free;
+            }
 
-    /* The datapath name is not a mandatory parameter for this command.
-     * If it is not specified - so argc == 1 - we retrieve it from the
-     * current setup, assuming only one exists. */
-    name = (argc > 1) ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
-    if (!name) {
-        error = EINVAL;
-        goto out_free;
+            if (pmd_id == -1) {
+                pmd_id = NON_PMD_CORE_ID;
+            }
+            pmd_id_filter = true;
+        }
     }
 
-    error = parsed_dpif_open(name, false, &dpif);
-    free(name);
+    error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
     if (error) {
-        dpctl_error(dpctl_p, error, "opening datapath");
         goto out_free;
     }
 
@@ -865,19 +1045,12 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
         }
     }
 
-    if (type) {
-        error = EINVAL;
-        for (int i = 0; i < ARRAY_SIZE(supported_dump_types); i++) {
-            if (!strcmp(supported_dump_types[i], type)) {
-                error = 0;
-                break;
-            }
-        }
-        if (error) {
-            dpctl_error(dpctl_p, error, "Failed to parse type (%s)", type);
-            goto out_free;
-        }
+    memset(&dump_types, 0, sizeof dump_types);
+    error = populate_dump_types(types_list, &dump_types, dpctl_p);
+    if (error) {
+        goto out_dpifclose;
     }
+    determine_dpif_flow_dump_types(&dump_types, &dpif_dump_types);
 
     /* Make sure that these values are different. PMD_ID_NULL means that the
      * pmd is unspecified (e.g. because the datapath doesn't have different
@@ -887,7 +1060,7 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
 
     ds_init(&ds);
     memset(&f, 0, sizeof f);
-    flow_dump = dpif_flow_dump_create(dpif, false, (type ? type : "dpctl"));
+    flow_dump = dpif_flow_dump_create(dpif, false, &dpif_dump_types);
     flow_dump_thread = dpif_flow_dump_thread_create(flow_dump);
     while (dpif_flow_dump_next(flow_dump_thread, &f, 1)) {
         if (filter) {
@@ -896,8 +1069,8 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
             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, &flow);
+            odp_flow_key_to_flow(f.key, f.key_len, &flow, NULL);
+            odp_flow_key_to_mask(f.mask, f.mask_len, &wc, &flow, NULL);
             match_init(&match, &flow, &wc);
 
             match_init(&match_filter, &flow_filter, &wc);
@@ -914,18 +1087,20 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
         /* If 'pmd_id' is specified, overlapping flows could be dumped from
          * different pmd threads.  So, separates dumps from different pmds
          * by printing a title line. */
-        if (pmd_id != f.pmd_id) {
+        if (!pmd_id_filter && pmd_id != f.pmd_id) {
             if (f.pmd_id == NON_PMD_CORE_ID) {
-                ds_put_format(&ds, "flow-dump from non-dpdk interfaces:\n");
+                ds_put_format(&ds, "flow-dump from the main thread:\n");
             } else {
                 ds_put_format(&ds, "flow-dump from pmd on cpu core: %d\n",
                               f.pmd_id);
             }
             pmd_id = f.pmd_id;
         }
-        format_dpif_flow(&ds, &f, portno_names, type, dpctl_p);
-
-        dpctl_print(dpctl_p, "%s\n", ds_cstr(&ds));
+        if (pmd_id == f.pmd_id &&
+            flow_passes_type_filter(&f, &dump_types)) {
+            format_dpif_flow(&ds, &f, portno_names, dpctl_p);
+            dpctl_print(dpctl_p, "%s\n", ds_cstr(&ds));
+        }
     }
     dpif_flow_dump_thread_destroy(flow_dump_thread);
     error = dpif_flow_dump_destroy(flow_dump);
@@ -940,43 +1115,27 @@ out_dpifclose:
     dpif_close(dpif);
 out_free:
     free(filter);
-    free(type);
+    free(types_list);
     return error;
 }
 
 static int
-dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
-               struct dpctl_params *dpctl_p)
+dpctl_put_flow_dpif(struct dpif *dpif, const char *key_s,
+                    const char *actions_s,
+                    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;
     ovs_u128 ufid;
     bool ufid_present;
-    char *dp_name;
     struct simap port_names;
     int n, error;
 
-    /* The datapath name is not a mandatory parameter for this command.
-     * If it is not specified - so argc < 4 - we retrieve it from the
-     * current setup, assuming only one exists. */
-    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;
-    }
-
     ufid_present = false;
     n = odp_ufid_from_string(key_s, &ufid);
     if (n < 0) {
@@ -994,10 +1153,12 @@ dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
 
     ofpbuf_init(&key, 0);
     ofpbuf_init(&mask, 0);
-    error = odp_flow_from_string(key_s, &port_names, &key, &mask);
+    char *error_s;
+    error = odp_flow_from_string(key_s, &port_names, &key, &mask, &error_s);
     simap_destroy(&port_names);
     if (error) {
-        dpctl_error(dpctl_p, error, "parsing flow key");
+        dpctl_error(dpctl_p, error, "parsing flow key (%s)", error_s);
+        free(error_s);
         goto out_freekeymask;
     }
 
@@ -1008,6 +1169,16 @@ dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
         goto out_freeactions;
     }
 
+    if (!ufid_present && dpctl_p->is_appctl) {
+        /* Generating UFID for this flow so it could be offloaded to HW.  We're
+         * not doing that if invoked from ovs-dpctl utility because
+         * odp_flow_key_hash() uses randomly generated base for flow hashes
+         * that will be different for each invocation.  And, anyway, offloading
+         * is only available via appctl. */
+        odp_flow_key_hash(key.data, key.size, &ufid);
+        ufid_present = true;
+    }
+
     /* The flow will be added on all pmds currently in the datapath. */
     error = dpif_flow_put(dpif, flags,
                           key.data, key.size,
@@ -1036,6 +1207,24 @@ out_freeactions:
 out_freekeymask:
     ofpbuf_uninit(&mask);
     ofpbuf_uninit(&key);
+    return error;
+}
+
+static int
+dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
+               struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error;
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
+    if (error) {
+        return error;
+    }
+
+    error = dpctl_put_flow_dpif(dpif, argv[argc - 2], argv[argc - 1], flags,
+                                dpctl_p);
+
     dpif_close(dpif);
     return error;
 }
@@ -1068,24 +1257,14 @@ dpctl_get_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
     const char *key_s = argv[argc - 1];
     struct dpif_flow flow;
     struct dpif *dpif;
-    char *dp_name;
     ovs_u128 ufid;
     struct ofpbuf buf;
     uint64_t stub[DPIF_FLOW_BUFSIZE / 8];
     struct ds ds;
     int n, error;
 
-    /* The datapath name is not a mandatory parameter for this command.
-     * If it is not specified - so argc < 3 - we retrieve it from the
-     * current setup, assuming only one exists. */
-    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);
+    error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
     if (error) {
-        dpctl_error(dpctl_p, error, "opening datapath");
         return error;
     }
 
@@ -1107,7 +1286,7 @@ dpctl_get_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
     }
 
     ds_init(&ds);
-    format_dpif_flow(&ds, &flow, portno_names, NULL, dpctl_p);
+    format_dpif_flow(&ds, &flow, portno_names, dpctl_p);
     dpctl_print(dpctl_p, "%s\n", ds_cstr(&ds));
     ds_destroy(&ds);
 
@@ -1119,35 +1298,21 @@ out:
 }
 
 static int
-dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+dpctl_del_flow_dpif(struct dpif *dpif, const char *key_s,
+                    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;
+
     ovs_u128 ufid;
+    bool ufid_generated;
     bool ufid_present;
-    char *dp_name;
     struct simap port_names;
     int n, error;
 
-    /* The datapath name is not a mandatory parameter for this command.
-     * If it is not specified - so argc < 3 - we retrieve it from the
-     * current setup, assuming only one exists. */
-    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;
-    }
-
     ufid_present = false;
     n = odp_ufid_from_string(key_s, &ufid);
     if (n < 0) {
@@ -1166,12 +1331,22 @@ dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
     ofpbuf_init(&key, 0);
     ofpbuf_init(&mask, 0);
 
-    error = odp_flow_from_string(key_s, &port_names, &key, &mask);
+    char *error_s;
+    error = odp_flow_from_string(key_s, &port_names, &key, &mask, &error_s);
     if (error) {
-        dpctl_error(dpctl_p, error, "parsing flow key");
+        dpctl_error(dpctl_p, error, "%s", error_s);
+        free(error_s);
         goto out;
     }
 
+    if (!ufid_present && dpctl_p->is_appctl) {
+        /* While adding flow via appctl we're generating UFID to make HW
+         * offloading possible.  Generating UFID here to be sure that such
+         * flows could be removed the same way they were added. */
+        odp_flow_key_hash(key.data, key.size, &ufid);
+        ufid_present = ufid_generated = true;
+    }
+
     /* The flow will be deleted from all pmds currently in the datapath. */
     error = dpif_flow_del(dpif, key.data, key.size,
                           ufid_present ? &ufid : NULL, PMD_ID_NULL,
@@ -1179,7 +1354,7 @@ dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
 
     if (error) {
         dpctl_error(dpctl_p, error, "deleting flow");
-        if (error == ENOENT && !ufid_present) {
+        if (error == ENOENT && (!ufid_present || ufid_generated)) {
             struct ds s;
 
             ds_init(&s);
@@ -1203,28 +1378,158 @@ out:
     ofpbuf_uninit(&mask);
     ofpbuf_uninit(&key);
     simap_destroy(&port_names);
+    return error;
+}
+
+static int
+dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    const char *key_s = argv[argc - 1];
+    struct dpif *dpif;
+    int error;
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
+    if (error) {
+        return error;
+    }
+
+    error = dpctl_del_flow_dpif(dpif, key_s, dpctl_p);
+
     dpif_close(dpif);
     return error;
 }
 
+static int
+dpctl_parse_flow_line(int command, struct ds *s, char **flow, char **action)
+{
+    const char *line = ds_cstr(s);
+    size_t len;
+
+    /* First figure out the command, or fallback to FLOWS_ADD. */
+    line += strspn(line, " \t\r\n");
+    len = strcspn(line, ", \t\r\n");
+
+    if (!strncmp(line, "add", len)) {
+         command = DPCTL_FLOWS_ADD;
+    } else if (!strncmp(line, "delete", len)) {
+        command = DPCTL_FLOWS_DEL;
+    } else if (!strncmp(line, "modify", len)) {
+        command = DPCTL_FLOWS_MOD;
+    } else {
+        len = 0;
+    }
+    line += len;
+
+    /* Isolate flow and action (for add/modify). */
+    line += strspn(line, " \t\r\n");
+    len = strcspn(line, " \t\r\n");
+
+    if (len == 0) {
+        *flow = NULL;
+        *action = NULL;
+        return command;
+    }
+
+    *flow = xzalloc(len + 1);
+    ovs_strlcpy(*flow, line, len + 1);
+
+    line += len;
+    line += strspn(line, " \t\r\n");
+    if (strlen(line)) {
+        *action = xstrdup(line);
+    } else {
+        *action = NULL;
+    }
+
+    return command;
+}
+
+static int
+dpctl_process_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    const char *file_name = argv[argc - 1];
+    int line_number = 0;
+    struct dpif *dpif;
+    struct ds line;
+    FILE *stream;
+    int error;
+    int def_cmd = DPCTL_FLOWS_ADD;
+
+    if (strstr(argv[0], "mod-flows")) {
+        def_cmd = DPCTL_FLOWS_MOD;
+    } else if (strstr(argv[0], "del-flows")) {
+        def_cmd = DPCTL_FLOWS_DEL;
+    }
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
+    if (error) {
+        return error;
+    }
+
+    stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r");
+    if (!stream) {
+        error = errno;
+        dpctl_error(dpctl_p, error, "Opening file \"%s\" failed", file_name);
+        goto out_close_dpif;
+    }
+
+    ds_init(&line);
+    while (!ds_get_preprocessed_line(&line, stream, &line_number)) {
+        /* We do not process all the lines first and then execute the actions
+         * as we would like to take commands as a continuous stream of
+         * commands from stdin.
+         */
+        char *flow = NULL;
+        char *action = NULL;
+        int cmd = dpctl_parse_flow_line(def_cmd, &line, &flow, &action);
+
+        if ((!flow && !action)
+            || ((cmd == DPCTL_FLOWS_ADD || cmd == DPCTL_FLOWS_MOD) && !action)
+            || (cmd == DPCTL_FLOWS_DEL && action)) {
+            dpctl_error(dpctl_p, 0,
+                        "Failed parsing line number %u, skipped!",
+                        line_number);
+        } else {
+            switch (cmd) {
+            case DPCTL_FLOWS_ADD:
+                dpctl_put_flow_dpif(dpif, flow, action,
+                                    DPIF_FP_CREATE, dpctl_p);
+                break;
+            case DPCTL_FLOWS_MOD:
+                dpctl_put_flow_dpif(dpif, flow, action,
+                                    DPIF_FP_MODIFY, dpctl_p);
+                break;
+            case DPCTL_FLOWS_DEL:
+                dpctl_del_flow_dpif(dpif, flow, dpctl_p);
+                break;
+            }
+        }
+
+        free(flow);
+        free(action);
+    }
+
+    ds_destroy(&line);
+    if (stream != stdin) {
+        fclose(stream);
+    }
+out_close_dpif:
+    dpif_close(dpif);
+    return 0;
+}
+
 static int
 dpctl_del_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
 {
     struct dpif *dpif;
-    char *name;
     int error;
 
-    /* The datapath name is not a mandatory parameter for this command.
-     * If it is not specified - so argc < 2 - we retrieve it from the
-     * current setup, assuming only one exists. */
-    name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
-    if (!name) {
-        return EINVAL;
+    if ((!dp_arg_exists(argc, argv) && argc == 2) || argc > 2) {
+        return dpctl_process_flows(argc, argv, dpctl_p);
     }
-    error = parsed_dpif_open(name, false, &dpif);
-    free(name);
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
     if (error) {
-        dpctl_error(dpctl_p, error, "opening datapath");
         return error;
     }
 
@@ -1258,6 +1563,10 @@ dpctl_list_commands(int argc OVS_UNUSED, const char *argv[] OVS_UNUSED,
     for (; commands->name; commands++) {
         const struct dpctl_command *c = commands;
 
+        if (dpctl_p->is_appctl && !strcmp(c->name, "help")) {
+            continue;
+        }
+
         ds_put_format(&ds, "  %s%-23s %s\n", dpctl_p->is_appctl ? "dpctl/" : "",
                       c->name, c->usage);
     }
@@ -1266,6 +1575,7 @@ dpctl_list_commands(int argc OVS_UNUSED, const char *argv[] OVS_UNUSED,
 
     return 0;
 }
+\f
 
 static int
 dpctl_dump_conntrack(int argc, const char *argv[],
@@ -1276,24 +1586,15 @@ dpctl_dump_conntrack(int argc, const char *argv[],
     uint16_t zone, *pzone = NULL;
     int tot_bkts;
     struct dpif *dpif;
-    char *name;
     int error;
 
     if (argc > 1 && ovs_scan(argv[argc - 1], "zone=%"SCNu16, &zone)) {
         pzone = &zone;
         argc--;
     }
-    /* The datapath name is not a mandatory parameter for this command.
-     * If it is not specified - so argc < 2 - we retrieve it from the
-     * current setup, assuming only one exists. */
-    name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
-    if (!name) {
-        return EINVAL;
-    }
-    error = parsed_dpif_open(name, false, &dpif);
-    free(name);
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
     if (error) {
-        dpctl_error(dpctl_p, error, "opening datapath");
         return error;
     }
 
@@ -1330,61 +1631,35 @@ static int
 dpctl_flush_conntrack(int argc, const char *argv[],
                       struct dpctl_params *dpctl_p)
 {
-    struct dpif *dpif;
+    struct dpif *dpif = NULL;
     struct ct_dpif_tuple tuple, *ptuple = NULL;
     struct ds ds = DS_EMPTY_INITIALIZER;
     uint16_t zone, *pzone = NULL;
-    char *name;
-    int error, i = 1;
-    bool got_dpif = false;
-
-    /* Parse datapath name. It is not a mandatory parameter for this command.
-     * If it is not specified, we retrieve it from the current setup,
-     * assuming only one exists. */
-    if (argc >= 2) {
-        error = parsed_dpif_open(argv[i], false, &dpif);
-        if (!error) {
-            got_dpif = true;
-            i++;
-        } else if (argc == 4) {
-            dpctl_error(dpctl_p, error, "invalid datapath");
-            return error;
-        }
-    }
-    if (!got_dpif) {
-        name = 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;
-        }
+    int error;
+    int args = argc - 1;
+
+    /* Parse ct tuple */
+    if (args && ct_dpif_parse_tuple(&tuple, argv[args], &ds)) {
+        ptuple = &tuple;
+        args--;
     }
 
     /* Parse zone */
-    if (argc > i && ovs_scan(argv[i], "zone=%"SCNu16, &zone)) {
+    if (args && ovs_scan(argv[args], "zone=%"SCNu16, &zone)) {
         pzone = &zone;
-        i++;
+        args--;
     }
+
     /* Report error if there are more than one unparsed argument. */
-    if (argc - i > 1) {
-        ds_put_cstr(&ds, "invalid zone");
+    if (args > 1) {
+        ds_put_cstr(&ds, "invalid arguments");
         error = EINVAL;
         goto error;
     }
 
-    /* Parse ct tuple */
-    if (argc > i && ct_dpif_parse_tuple(&tuple, argv[i], &ds)) {
-        ptuple = &tuple;
-        i++;
-    }
-    /* Report error if there is an unparsed argument. */
-    if (argc - i) {
-        error = EINVAL;
-        goto error;
+    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
+    if (error) {
+        return error;
     }
 
     error = ct_dpif_flush(dpif, pzone, ptuple);
@@ -1407,22 +1682,22 @@ dpctl_ct_stats_show(int argc, const char *argv[],
                      struct dpctl_params *dpctl_p)
 {
     struct dpif *dpif;
-    char *name;
-
     struct ct_dpif_dump_state *dump;
     struct ct_dpif_entry cte;
     uint16_t zone, *pzone = NULL;
     int tot_bkts;
-    bool verbose = false;
     int lastargc = 0;
 
     int proto_stats[CT_STATS_MAX];
     int tcp_conn_per_states[CT_DPIF_TCPS_MAX_NUM];
     int error;
 
+    bool verbose = dpctl_p->verbosity;
+
     while (argc > 1 && lastargc != argc) {
         lastargc = argc;
         if (!strncmp(argv[argc - 1], "verbose", 7)) {
+            /* Support "verbose" argument for backwards compatibility. */
             verbose = true;
             argc--;
         } else if (!strncmp(argv[argc - 1], "zone=", 5)) {
@@ -1432,18 +1707,9 @@ dpctl_ct_stats_show(int argc, const char *argv[],
             }
         }
     }
-    /* The datapath name is not a mandatory parameter for this command.
-     * If it is not specified - so argc == 1 - we retrieve it from the
-     * current setup, assuming only one exists. */
-    name = (argc > 1) ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
-    if (!name) {
-        return EINVAL;
-    }
 
-    error = parsed_dpif_open(name, false, &dpif);
-    free(name);
+    error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
     if (error) {
-        dpctl_error(dpctl_p, error, "opening datapath");
         return error;
     }
 
@@ -1508,9 +1774,9 @@ dpctl_ct_stats_show(int argc, const char *argv[],
 
     dpctl_print(dpctl_p, "Connections Stats:\n    Total: %d\n", tot_conn);
     if (proto_stats[CT_STATS_TCP]) {
-        dpctl_print(dpctl_p, "\tTCP: %d\n", proto_stats[CT_STATS_TCP]);
+        dpctl_print(dpctl_p, "  TCP: %d\n", proto_stats[CT_STATS_TCP]);
         if (verbose) {
-            dpctl_print(dpctl_p, "\t  Conn per TCP states:\n");
+            dpctl_print(dpctl_p, "    Conn per TCP states:\n");
             for (int i = 0; i < CT_DPIF_TCPS_MAX_NUM; i++) {
                 if (tcp_conn_per_states[i]) {
                     struct ds s = DS_EMPTY_INITIALIZER;
@@ -1522,25 +1788,25 @@ dpctl_ct_stats_show(int argc, const char *argv[],
         }
     }
     if (proto_stats[CT_STATS_UDP]) {
-        dpctl_print(dpctl_p, "\tUDP: %d\n", proto_stats[CT_STATS_UDP]);
+        dpctl_print(dpctl_p, "  UDP: %d\n", proto_stats[CT_STATS_UDP]);
     }
     if (proto_stats[CT_STATS_UDPLITE]) {
-        dpctl_print(dpctl_p, "\tUDPLITE: %d\n", proto_stats[CT_STATS_UDPLITE]);
+        dpctl_print(dpctl_p, "  UDPLITE: %d\n", proto_stats[CT_STATS_UDPLITE]);
     }
     if (proto_stats[CT_STATS_SCTP]) {
-        dpctl_print(dpctl_p, "\tSCTP: %d\n", proto_stats[CT_STATS_SCTP]);
+        dpctl_print(dpctl_p, "  SCTP: %d\n", proto_stats[CT_STATS_SCTP]);
     }
     if (proto_stats[CT_STATS_ICMP]) {
-        dpctl_print(dpctl_p, "\tICMP: %d\n", proto_stats[CT_STATS_ICMP]);
+        dpctl_print(dpctl_p, "  ICMP: %d\n", proto_stats[CT_STATS_ICMP]);
     }
     if (proto_stats[CT_STATS_DCCP]) {
-        dpctl_print(dpctl_p, "\tDCCP: %d\n", proto_stats[CT_STATS_DCCP]);
+        dpctl_print(dpctl_p, "  DCCP: %d\n", proto_stats[CT_STATS_DCCP]);
     }
     if (proto_stats[CT_STATS_IGMP]) {
-        dpctl_print(dpctl_p, "\tIGMP: %d\n", proto_stats[CT_STATS_IGMP]);
+        dpctl_print(dpctl_p, "  IGMP: %d\n", proto_stats[CT_STATS_IGMP]);
     }
     if (proto_stats[CT_STATS_OTHER]) {
-        dpctl_print(dpctl_p, "\tOther: %d\n", proto_stats[CT_STATS_OTHER]);
+        dpctl_print(dpctl_p, "  Other: %d\n", proto_stats[CT_STATS_OTHER]);
     }
 
     ct_dpif_dump_done(dump);
@@ -1554,8 +1820,6 @@ dpctl_ct_bkts(int argc, const char *argv[],
                      struct dpctl_params *dpctl_p)
 {
     struct dpif *dpif;
-    char *name;
-
     struct ct_dpif_dump_state *dump;
     struct ct_dpif_entry cte;
     uint16_t gt = 0; /* Threshold: display value when greater than gt. */
@@ -1569,18 +1833,8 @@ dpctl_ct_bkts(int argc, const char *argv[],
         }
     }
 
-    /* The datapath name is not a mandatory parameter for this command.
-     * If it is not specified - so argc < 2 - we retrieve it from the
-     * current setup, assuming only one exists. */
-    name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
-    if (!name) {
-        return EINVAL;
-    }
-
-    error = parsed_dpif_open(name, false, &dpif);
-    free(name);
+    error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
     if (error) {
-        dpctl_error(dpctl_p, error, "opening datapath");
         return error;
     }
 
@@ -1654,6 +1908,498 @@ dpctl_ct_bkts(int argc, const char *argv[],
     return error;
 }
 \f
+static int
+dpctl_ct_set_maxconns(int argc, const char *argv[],
+                      struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
+    if (!error) {
+        uint32_t maxconns;
+        if (ovs_scan(argv[argc - 1], "%"SCNu32, &maxconns)) {
+            error = ct_dpif_set_maxconns(dpif, maxconns);
+
+            if (!error) {
+                dpctl_print(dpctl_p, "setting maxconns successful");
+            } else {
+                dpctl_error(dpctl_p, error, "ct set maxconns failed");
+            }
+        } else {
+            error = EINVAL;
+            dpctl_error(dpctl_p, error, "maxconns missing or malformed");
+        }
+        dpif_close(dpif);
+    }
+
+    return error;
+}
+
+static int
+dpctl_ct_get_maxconns(int argc, const char *argv[],
+                    struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
+    if (!error) {
+        uint32_t maxconns;
+        error = ct_dpif_get_maxconns(dpif, &maxconns);
+
+        if (!error) {
+            dpctl_print(dpctl_p, "%u\n", maxconns);
+        } else {
+            dpctl_error(dpctl_p, error, "maxconns could not be retrieved");
+        }
+        dpif_close(dpif);
+    }
+
+    return error;
+}
+
+static int
+dpctl_ct_get_nconns(int argc, const char *argv[],
+                    struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
+    if (!error) {
+        uint32_t nconns;
+        error = ct_dpif_get_nconns(dpif, &nconns);
+
+        if (!error) {
+            dpctl_print(dpctl_p, "%u\n", nconns);
+        } else {
+            dpctl_error(dpctl_p, error, "nconns could not be retrieved");
+        }
+        dpif_close(dpif);
+    }
+
+    return error;
+}
+
+static int
+dpctl_ct_set_tcp_seq_chk__(int argc, const char *argv[],
+                           struct dpctl_params *dpctl_p, bool enabled)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
+    if (!error) {
+        error = ct_dpif_set_tcp_seq_chk(dpif, enabled);
+        if (!error) {
+            dpctl_print(dpctl_p,
+                        "%s TCP sequence checking successful",
+                        enabled ? "enabling" : "disabling");
+        } else {
+            dpctl_error(dpctl_p, error,
+                        "%s TCP sequence checking failed",
+                        enabled ? "enabling" : "disabling");
+        }
+        dpif_close(dpif);
+    }
+    return error;
+}
+
+static int
+dpctl_ct_enable_tcp_seq_chk(int argc, const char *argv[],
+                            struct dpctl_params *dpctl_p)
+{
+    return dpctl_ct_set_tcp_seq_chk__(argc, argv, dpctl_p, true);
+}
+
+static int
+dpctl_ct_disable_tcp_seq_chk(int argc, const char *argv[],
+                             struct dpctl_params *dpctl_p)
+{
+    return dpctl_ct_set_tcp_seq_chk__(argc, argv, dpctl_p, false);
+}
+
+static int
+dpctl_ct_get_tcp_seq_chk(int argc, const char *argv[],
+                         struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
+    if (!error) {
+        bool enabled;
+        error = ct_dpif_get_tcp_seq_chk(dpif, &enabled);
+        if (!error) {
+            dpctl_print(dpctl_p, "TCP sequence checking: %s\n",
+                        enabled ? "enabled" : "disabled");
+        } else {
+            dpctl_error(dpctl_p, error, "TCP sequence checking query failed");
+        }
+        dpif_close(dpif);
+    }
+    return error;
+}
+
+static int
+dpctl_ct_set_limits(int argc, const char *argv[],
+                    struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    int i =  dp_arg_exists(argc, argv) ? 2 : 1;
+    uint32_t default_limit, *p_default_limit = NULL;
+    struct ovs_list zone_limits = OVS_LIST_INITIALIZER(&zone_limits);
+
+    int error = opt_dpif_open(argc, argv, dpctl_p, INT_MAX, &dpif);
+    if (error) {
+        return error;
+    }
+
+    /* Parse default limit */
+    if (!strncmp(argv[i], "default=", 8)) {
+        if (ovs_scan(argv[i], "default=%"SCNu32, &default_limit)) {
+            p_default_limit = &default_limit;
+            i++;
+        } else {
+            ds_put_cstr(&ds, "invalid default limit");
+            error = EINVAL;
+            goto error;
+        }
+    }
+
+    /* Parse ct zone limit tuples */
+    while (i < argc) {
+        uint16_t zone;
+        uint32_t limit;
+        if (!ct_dpif_parse_zone_limit_tuple(argv[i++], &zone, &limit, &ds)) {
+            error = EINVAL;
+            goto error;
+        }
+        ct_dpif_push_zone_limit(&zone_limits, zone, limit, 0);
+    }
+
+    error = ct_dpif_set_limits(dpif, p_default_limit, &zone_limits);
+    if (!error) {
+        ct_dpif_free_zone_limits(&zone_limits);
+        dpif_close(dpif);
+        return 0;
+    } else {
+        ds_put_cstr(&ds, "failed to set conntrack limit");
+    }
+
+error:
+    dpctl_error(dpctl_p, error, "%s", ds_cstr(&ds));
+    ds_destroy(&ds);
+    ct_dpif_free_zone_limits(&zone_limits);
+    dpif_close(dpif);
+    return error;
+}
+
+static int
+parse_ct_limit_zones(const char *argv, struct ovs_list *zone_limits,
+                     struct ds *ds)
+{
+    char *save_ptr = NULL, *argcopy, *next_zone;
+    uint16_t zone;
+
+    if (strncmp(argv, "zone=", 5)) {
+        ds_put_format(ds, "invalid argument %s", argv);
+        return EINVAL;
+    }
+
+    argcopy = xstrdup(argv + 5);
+    next_zone = strtok_r(argcopy, ",", &save_ptr);
+
+    do {
+        if (ovs_scan(next_zone, "%"SCNu16, &zone)) {
+            ct_dpif_push_zone_limit(zone_limits, zone, 0, 0);
+        } else {
+            ds_put_cstr(ds, "invalid zone");
+            free(argcopy);
+            return EINVAL;
+        }
+    } while ((next_zone = strtok_r(NULL, ",", &save_ptr)) != NULL);
+
+    free(argcopy);
+    return 0;
+}
+
+static int
+dpctl_ct_del_limits(int argc, const char *argv[],
+                    struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    int error;
+    int i =  dp_arg_exists(argc, argv) ? 2 : 1;
+    struct ovs_list zone_limits = OVS_LIST_INITIALIZER(&zone_limits);
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
+    if (error) {
+        return error;
+    }
+
+    error = parse_ct_limit_zones(argv[i], &zone_limits, &ds);
+    if (error) {
+        goto error;
+    }
+
+    error = ct_dpif_del_limits(dpif, &zone_limits);
+    if (!error) {
+        goto out;
+    } else {
+        ds_put_cstr(&ds, "failed to delete conntrack limit");
+    }
+
+error:
+    dpctl_error(dpctl_p, error, "%s", ds_cstr(&ds));
+    ds_destroy(&ds);
+out:
+    ct_dpif_free_zone_limits(&zone_limits);
+    dpif_close(dpif);
+    return error;
+}
+
+static int
+dpctl_ct_get_limits(int argc, const char *argv[],
+                    struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    uint32_t default_limit;
+    int i =  dp_arg_exists(argc, argv) ? 2 : 1;
+    struct ovs_list list_query = OVS_LIST_INITIALIZER(&list_query);
+    struct ovs_list list_reply = OVS_LIST_INITIALIZER(&list_reply);
+
+    int error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
+    if (error) {
+        return error;
+    }
+
+    if (argc > i) {
+        error = parse_ct_limit_zones(argv[i], &list_query, &ds);
+        if (error) {
+            goto error;
+        }
+    }
+
+    error = ct_dpif_get_limits(dpif, &default_limit, &list_query,
+                               &list_reply);
+    if (!error) {
+        ct_dpif_format_zone_limits(default_limit, &list_reply, &ds);
+        dpctl_print(dpctl_p, "%s\n", ds_cstr(&ds));
+        goto out;
+    } else {
+        ds_put_format(&ds, "failed to get conntrack limit %s",
+                      ovs_strerror(error));
+    }
+
+error:
+    dpctl_error(dpctl_p, error, "%s", ds_cstr(&ds));
+out:
+    ds_destroy(&ds);
+    ct_dpif_free_zone_limits(&list_query);
+    ct_dpif_free_zone_limits(&list_reply);
+    dpif_close(dpif);
+    return error;
+}
+
+static int
+ipf_set_enabled__(int argc, const char *argv[], struct dpctl_params *dpctl_p,
+                  bool enabled)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
+    if (!error) {
+        char v4_or_v6[3] = {0};
+        if (ovs_scan(argv[argc - 1], "%2s", v4_or_v6) &&
+            (!strncmp(v4_or_v6, "v4", 2) || !strncmp(v4_or_v6, "v6", 2))) {
+            error = ct_dpif_ipf_set_enabled(
+                        dpif, !strncmp(v4_or_v6, "v6", 2), enabled);
+            if (!error) {
+                dpctl_print(dpctl_p,
+                            "%s fragmentation reassembly successful",
+                            enabled ? "enabling" : "disabling");
+            } else {
+                dpctl_error(dpctl_p, error,
+                            "%s fragmentation reassembly failed",
+                            enabled ? "enabling" : "disabling");
+            }
+        } else {
+            error = EINVAL;
+            dpctl_error(dpctl_p, error,
+                        "parameter missing: 'v4' for IPv4 or 'v6' for IPv6");
+        }
+        dpif_close(dpif);
+    }
+    return error;
+}
+
+static int
+dpctl_ipf_set_enabled(int argc, const char *argv[],
+                      struct dpctl_params *dpctl_p)
+{
+    return ipf_set_enabled__(argc, argv, dpctl_p, true);
+}
+
+static int
+dpctl_ipf_set_disabled(int argc, const char *argv[],
+                       struct dpctl_params *dpctl_p)
+{
+    return ipf_set_enabled__(argc, argv, dpctl_p, false);
+}
+
+static int
+dpctl_ipf_set_min_frag(int argc, const char *argv[],
+                       struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
+    if (!error) {
+        char v4_or_v6[3] = {0};
+        if (ovs_scan(argv[argc - 2], "%2s", v4_or_v6) &&
+            (!strncmp(v4_or_v6, "v4", 2) || !strncmp(v4_or_v6, "v6", 2))) {
+            uint32_t min_fragment;
+            if (ovs_scan(argv[argc - 1], "%"SCNu32, &min_fragment)) {
+                error = ct_dpif_ipf_set_min_frag(
+                            dpif, !strncmp(v4_or_v6, "v6", 2), min_fragment);
+                if (!error) {
+                    dpctl_print(dpctl_p,
+                                "setting minimum fragment size successful");
+                } else {
+                    dpctl_error(dpctl_p, error,
+                                "requested minimum fragment size too small;"
+                                " see documentation");
+                }
+            } else {
+                error = EINVAL;
+                dpctl_error(dpctl_p, error,
+                            "parameter missing for minimum fragment size");
+            }
+        } else {
+            error = EINVAL;
+            dpctl_error(dpctl_p, error,
+                        "parameter missing: v4 for IPv4 or v6 for IPv6");
+        }
+        dpif_close(dpif);
+    }
+
+    return error;
+}
+
+static int
+dpctl_ipf_set_max_nfrags(int argc, const char *argv[],
+                         struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
+    if (!error) {
+        uint32_t nfrags_max;
+        if (ovs_scan(argv[argc - 1], "%"SCNu32, &nfrags_max)) {
+            error = ct_dpif_ipf_set_max_nfrags(dpif, nfrags_max);
+            if (!error) {
+                dpctl_print(dpctl_p,
+                            "setting maximum fragments successful");
+            } else {
+                dpctl_error(dpctl_p, error,
+                            "setting maximum fragments failed");
+            }
+        } else {
+            error = EINVAL;
+            dpctl_error(dpctl_p, error,
+                        "parameter missing for maximum fragments");
+        }
+        dpif_close(dpif);
+    }
+
+    return error;
+}
+
+static void
+dpctl_dump_ipf(struct dpif *dpif, struct dpctl_params *dpctl_p)
+{
+    struct ipf_dump_ctx *dump_ctx;
+    char *dump;
+
+    int error = ct_dpif_ipf_dump_start(dpif, &dump_ctx);
+    if (error) {
+        dpctl_error(dpctl_p, error, "starting ipf list dump");
+        /* Nothing to clean up, just return. */
+        return;
+    }
+
+    dpctl_print(dpctl_p, "\n        Fragment Lists:\n\n");
+    while (!(error = ct_dpif_ipf_dump_next(dpif, dump_ctx, &dump))) {
+        dpctl_print(dpctl_p, "%s\n", dump);
+        free(dump);
+    }
+
+    if (error && error != EOF) {
+        dpctl_error(dpctl_p, error, "dumping ipf lists failed");
+    }
+
+    ct_dpif_ipf_dump_done(dpif, dump_ctx);
+}
+
+static int
+dpctl_ct_ipf_get_status(int argc, const char *argv[],
+                        struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
+
+    if (!error) {
+        struct dpif_ipf_status dpif_ipf_status;
+        error = ct_dpif_ipf_get_status(dpif, &dpif_ipf_status);
+
+        if (!error) {
+            dpctl_print(dpctl_p, "        Fragmentation Module Status\n");
+            dpctl_print(dpctl_p, "        ---------------------------\n");
+            dpctl_print(dpctl_p, "        v4 enabled: %u\n",
+                        dpif_ipf_status.v4.enabled);
+            dpctl_print(dpctl_p, "        v6 enabled: %u\n",
+                        dpif_ipf_status.v6.enabled);
+            dpctl_print(dpctl_p, "        max num frags (v4/v6): %u\n",
+                        dpif_ipf_status.nfrag_max);
+            dpctl_print(dpctl_p, "        num frag: %u\n",
+                        dpif_ipf_status.nfrag);
+            dpctl_print(dpctl_p, "        min v4 frag size: %u\n",
+                        dpif_ipf_status.v4.min_frag_size);
+            dpctl_print(dpctl_p, "        v4 frags accepted: %"PRIu64"\n",
+                        dpif_ipf_status.v4.nfrag_accepted);
+            dpctl_print(dpctl_p, "        v4 frags completed: %"PRIu64"\n",
+                        dpif_ipf_status.v4.nfrag_completed_sent);
+            dpctl_print(dpctl_p, "        v4 frags expired: %"PRIu64"\n",
+                        dpif_ipf_status.v4.nfrag_expired_sent);
+            dpctl_print(dpctl_p, "        v4 frags too small: %"PRIu64"\n",
+                        dpif_ipf_status.v4.nfrag_too_small);
+            dpctl_print(dpctl_p, "        v4 frags overlapped: %"PRIu64"\n",
+                        dpif_ipf_status.v4.nfrag_overlap);
+            dpctl_print(dpctl_p, "        v4 frags purged: %"PRIu64"\n",
+                        dpif_ipf_status.v4.nfrag_purged);
+
+            dpctl_print(dpctl_p, "        min v6 frag size: %u\n",
+                        dpif_ipf_status.v6.min_frag_size);
+            dpctl_print(dpctl_p, "        v6 frags accepted: %"PRIu64"\n",
+                        dpif_ipf_status.v6.nfrag_accepted);
+            dpctl_print(dpctl_p, "        v6 frags completed: %"PRIu64"\n",
+                        dpif_ipf_status.v6.nfrag_completed_sent);
+            dpctl_print(dpctl_p, "        v6 frags expired: %"PRIu64"\n",
+                        dpif_ipf_status.v6.nfrag_expired_sent);
+            dpctl_print(dpctl_p, "        v6 frags too small: %"PRIu64"\n",
+                        dpif_ipf_status.v6.nfrag_too_small);
+            dpctl_print(dpctl_p, "        v6 frags overlapped: %"PRIu64"\n",
+                        dpif_ipf_status.v6.nfrag_overlap);
+            dpctl_print(dpctl_p, "        v6 frags purged: %"PRIu64"\n",
+                        dpif_ipf_status.v6.nfrag_purged);
+        } else {
+            dpctl_error(dpctl_p, error,
+                        "ipf status could not be retrieved");
+            return error;
+        }
+
+        if (dpctl_p->verbosity) {
+            dpctl_dump_ipf(dpif, dpctl_p);
+        }
+
+        dpif_close(dpif);
+    }
+
+    return error;
+}
+
 /* Undocumented commands for unit testing. */
 
 static int
@@ -1813,9 +2559,12 @@ dpctl_normalize_actions(int argc, const char *argv[],
 
     /* Parse flow key. */
     ofpbuf_init(&keybuf, 0);
-    error = odp_flow_from_string(argv[1], &port_names, &keybuf, NULL);
+    char *error_s;
+    error = odp_flow_from_string(argv[1], &port_names, &keybuf, NULL,
+                                 &error_s);
     if (error) {
-        dpctl_error(dpctl_p, error, "odp_flow_key_from_string");
+        dpctl_error(dpctl_p, error, "odp_flow_key_from_string (%s)", error_s);
+        free(error_s);
         goto out_freekeybuf;
     }
 
@@ -1824,9 +2573,11 @@ dpctl_normalize_actions(int argc, const char *argv[],
                     &s, dpctl_p->verbosity);
     dpctl_print(dpctl_p, "input flow: %s\n", ds_cstr(&s));
 
-    error = odp_flow_key_to_flow(keybuf.data, keybuf.size, &flow);
+    error = odp_flow_key_to_flow(keybuf.data, keybuf.size, &flow, &error_s);
     if (error) {
-        dpctl_error(dpctl_p, error, "odp_flow_key_to_flow");
+        dpctl_error(dpctl_p, error, "odp_flow_key_to_flow failed (%s)",
+                    error_s ? error_s : "reason unknown");
+        free(error_s);
         goto out_freekeybuf;
     }
 
@@ -1936,20 +2687,46 @@ static const struct dpctl_command all_commands[] = {
     { "del-if", "dp iface...", 2, INT_MAX, dpctl_del_if, DP_RW },
     { "set-if", "dp iface...", 2, INT_MAX, dpctl_set_if, DP_RW },
     { "dump-dps", "", 0, 0, dpctl_dump_dps, DP_RO },
-    { "show", "[dp...]", 0, INT_MAX, dpctl_show, DP_RO },
-    { "dump-flows", "[dp] [filter=..] [type=..]",
-      0, 3, dpctl_dump_flows, DP_RO },
+    { "show", "[-s] [dp...]", 0, INT_MAX, dpctl_show, DP_RO },
+    { "dump-flows", "[-m] [--names] [dp] [filter=..] [type=..] [pmd=..]",
+      0, 6, dpctl_dump_flows, DP_RO },
     { "add-flow", "[dp] flow actions", 2, 3, dpctl_add_flow, DP_RW },
     { "mod-flow", "[dp] flow actions", 2, 3, dpctl_mod_flow, DP_RW },
     { "get-flow", "[dp] ufid", 1, 2, dpctl_get_flow, DP_RO },
     { "del-flow", "[dp] flow", 1, 2, dpctl_del_flow, DP_RW },
-    { "del-flows", "[dp]", 0, 1, dpctl_del_flows, DP_RW },
-    { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack, DP_RO },
+    { "add-flows", "[dp] file", 1, 2, dpctl_process_flows, DP_RW },
+    { "mod-flows", "[dp] file", 1, 2, dpctl_process_flows, DP_RW },
+    { "del-flows", "[dp] [file]", 0, 2, dpctl_del_flows, DP_RW },
+    { "dump-conntrack", "[-m] [-s] [dp] [zone=N]",
+      0, 4, dpctl_dump_conntrack, DP_RO },
     { "flush-conntrack", "[dp] [zone=N] [ct-tuple]", 0, 3,
       dpctl_flush_conntrack, DP_RW },
-    { "ct-stats-show", "[dp] [zone=N] [verbose]",
+    { "ct-stats-show", "[dp] [zone=N]",
       0, 3, dpctl_ct_stats_show, DP_RO },
     { "ct-bkts", "[dp] [gt=N]", 0, 2, dpctl_ct_bkts, DP_RO },
+    { "ct-set-maxconns", "[dp] maxconns", 1, 2, dpctl_ct_set_maxconns,
+       DP_RW },
+    { "ct-get-maxconns", "[dp]", 0, 1, dpctl_ct_get_maxconns, DP_RO },
+    { "ct-get-nconns", "[dp]", 0, 1, dpctl_ct_get_nconns, DP_RO },
+    { "ct-enable-tcp-seq-chk", "[dp]", 0, 1, dpctl_ct_enable_tcp_seq_chk,
+       DP_RW },
+    { "ct-disable-tcp-seq-chk", "[dp]", 0, 1, dpctl_ct_disable_tcp_seq_chk,
+       DP_RW },
+    { "ct-get-tcp-seq-chk", "[dp]", 0, 1, dpctl_ct_get_tcp_seq_chk, DP_RO },
+    { "ct-set-limits", "[dp] [default=L] [zone=N,limit=L]...", 1, INT_MAX,
+        dpctl_ct_set_limits, DP_RO },
+    { "ct-del-limits", "[dp] zone=N1[,N2]...", 1, 2, dpctl_ct_del_limits,
+        DP_RO },
+    { "ct-get-limits", "[dp] [zone=N1[,N2]...]", 0, 2, dpctl_ct_get_limits,
+        DP_RO },
+    { "ipf-set-enabled", "[dp] v4|v6", 1, 2, dpctl_ipf_set_enabled, DP_RW },
+    { "ipf-set-disabled", "[dp] v4|v6", 1, 2, dpctl_ipf_set_disabled, DP_RW },
+    { "ipf-set-min-frag", "[dp] v4|v6 minfragment", 2, 3,
+       dpctl_ipf_set_min_frag, DP_RW },
+    { "ipf-set-max-nfrags", "[dp] maxfrags", 1, 2,
+       dpctl_ipf_set_max_nfrags, DP_RW },
+    { "ipf-get-status", "[dp]", 0, 1, dpctl_ct_ipf_get_status,
+       DP_RO },
     { "help", "", 0, INT_MAX, dpctl_help, DP_RO },
     { "list-commands", "", 0, INT_MAX, dpctl_list_commands, DP_RO },
 
@@ -2087,8 +2864,6 @@ dpctl_unixctl_handler(struct unixctl_conn *conn, int argc, const char *argv[],
     if (!set_names) {
         dpctl_p.names = dpctl_p.verbosity > 0;
     }
-    VLOG_INFO("set_names=%d verbosity=%d names=%d", set_names,
-              dpctl_p.verbosity, dpctl_p.names);
 
     if (!error) {
         dpctl_command_handler *handler = (dpctl_command_handler *) aux;