diff --git a/lib/ct-dpif.c b/lib/ct-dpif.c
index 8f0b4ed..f8d2cf1 100644
--- a/lib/ct-dpif.c
+++ b/lib/ct-dpif.c
@@ -409,3 +409,17 @@ ct_dpif_format_helper(struct ds *ds, const char *title,
         ds_put_cstr(ds, helper->name);
     }
 }
+
+uint8_t
+ct_dpif_coalesce_tcp_state(uint8_t state)
+{
+    return coalesce_tcp_state(state);
+}
+
+void
+ct_dpif_format_tcp_stat(struct ds * ds, int tcp_state, int conn_per_state)
+{
+    ct_dpif_format_enum(ds, "\t  [", tcp_state, ct_dpif_tcp_state_string);
+    ds_put_cstr(ds, "]");
+    ds_put_format(ds, "=%u", conn_per_state);
+}
diff --git a/lib/ct-dpif.h b/lib/ct-dpif.h
index e8e159a..cd35f3e 100644
--- a/lib/ct-dpif.h
+++ b/lib/ct-dpif.h
@@ -70,7 +70,8 @@ struct ct_dpif_timestamp {
     CT_DPIF_TCP_STATE(CLOSING) \
     CT_DPIF_TCP_STATE(LAST_ACK) \
     CT_DPIF_TCP_STATE(FIN_WAIT_2) \
-    CT_DPIF_TCP_STATE(TIME_WAIT)
+    CT_DPIF_TCP_STATE(TIME_WAIT) \
+    CT_DPIF_TCP_STATE(MAX_NUM)
 
 enum ct_dpif_tcp_state {
 #define CT_DPIF_TCP_STATE(STATE) CT_DPIF_TCPS_##STATE,
@@ -170,6 +171,19 @@ struct ct_dpif_entry {
     uint32_t mark;
 };
 
+enum {
+    CT_STATS_UDP,
+    CT_STATS_TCP,
+    CT_STATS_SCTP,
+    CT_STATS_ICMP,
+    CT_STATS_ICMPV6,
+    CT_STATS_UDPLITE,
+    CT_STATS_DCCP,
+    CT_STATS_IGMP,
+    CT_STATS_OTHER,
+    CT_STATS_MAX,
+};
+
 struct dpif;
 
 struct ct_dpif_dump_state {
@@ -185,5 +199,7 @@ void ct_dpif_entry_uninit(struct ct_dpif_entry *);
 void ct_dpif_format_entry(const struct ct_dpif_entry *, struct ds *,
                           bool verbose, bool print_stats);
 void ct_dpif_format_tuple(struct ds *, const struct ct_dpif_tuple *);
+uint8_t ct_dpif_coalesce_tcp_state(uint8_t state);
+void ct_dpif_format_tcp_stat(struct ds *, int, int);
 
 #endif /* CT_DPIF_H */
diff --git a/lib/dpctl.c b/lib/dpctl.c
index cde5341..4cca9c8 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -1308,6 +1308,141 @@ dpctl_flush_conntrack(int argc, const char *argv[],
     dpif_close(dpif);
     return error;
 }
+
+static int
+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;
+    bool verbose = false;
+    int lastargc = 0;
+
+    int proto_stats[CT_STATS_MAX];
+    int tcp_conn_per_states[CT_DPIF_TCPS_MAX_NUM];
+    int error;
+
+    while (argc > 1 && lastargc != argc) {
+        lastargc = argc;
+        if (!strncmp(argv[argc - 1], "verbose", 7)) {
+            verbose = true;
+            argc--;
+        } else if (!strncmp(argv[argc - 1], "zone=", 5)) {
+            if (ovs_scan(argv[argc - 1], "zone=%"SCNu16, &zone)) {
+                pzone = &zone;
+                argc--;
+            }
+        }
+    }
+
+    name = (argc > 1) ? 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;
+    }
+
+    memset(proto_stats, 0, sizeof(proto_stats));
+    memset(tcp_conn_per_states, 0, sizeof(tcp_conn_per_states));
+    error = ct_dpif_dump_start(dpif, &dump, pzone);
+    if (error) {
+        dpctl_error(dpctl_p, error, "starting conntrack dump");
+        dpif_close(dpif);
+        return error;
+    }
+
+    int tot_conn = 0;
+    while (!ct_dpif_dump_next(dump, &cte)) {
+        ct_dpif_entry_uninit(&cte);
+        tot_conn++;
+        switch (cte.tuple_orig.ip_proto) {
+        case IPPROTO_ICMP:
+            proto_stats[CT_STATS_ICMP]++;
+            break;
+        case IPPROTO_ICMPV6:
+            proto_stats[CT_STATS_ICMPV6]++;
+            break;
+        case IPPROTO_TCP:
+            proto_stats[CT_STATS_TCP]++;
+            uint8_t tcp_state;
+            /* We keep two separate tcp states, but we print just one. The Linux
+             * kernel connection tracker internally keeps only one state, so
+             * 'state_orig' and 'state_reply', will be the same. */
+            tcp_state = MAX(cte.protoinfo.tcp.state_orig, cte.protoinfo.tcp.state_reply);
+            tcp_state = ct_dpif_coalesce_tcp_state(tcp_state);
+            tcp_conn_per_states[tcp_state]++;
+            break;
+        case IPPROTO_UDP:
+            proto_stats[CT_STATS_UDP]++;
+            break;
+        case IPPROTO_SCTP:
+            proto_stats[CT_STATS_SCTP]++;
+            break;
+        case IPPROTO_UDPLITE:
+            proto_stats[CT_STATS_UDPLITE]++;
+            break;
+        case IPPROTO_DCCP:
+            proto_stats[CT_STATS_DCCP]++;
+            break;
+        case IPPROTO_IGMP:
+            proto_stats[CT_STATS_IGMP]++;
+            break;
+        default:
+            proto_stats[CT_STATS_OTHER]++;
+            break;
+        }
+    }
+
+    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]);
+        if (verbose) {
+            dpctl_print(dpctl_p, "\t  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;
+                    ct_dpif_format_tcp_stat(&s, i, tcp_conn_per_states[i]);
+                    dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
+                    ds_destroy(&s);
+                }
+            }
+        }
+    }
+    if (proto_stats[CT_STATS_UDP]) {
+        dpctl_print(dpctl_p, "\tUDP: %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]);
+    }
+    if (proto_stats[CT_STATS_SCTP]) {
+        dpctl_print(dpctl_p, "\tSCTP: %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]);
+    }
+    if (proto_stats[CT_STATS_DCCP]) {
+        dpctl_print(dpctl_p, "\tDCCP: %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]);
+    }
+    if (proto_stats[CT_STATS_OTHER]) {
+        dpctl_print(dpctl_p, "\tOther: %d\n", proto_stats[CT_STATS_OTHER]);
+    }
+
+    ct_dpif_dump_done(dump);
+    dpif_close(dpif);
+    return error;
+}
 
 /* Undocumented commands for unit testing. */
 
@@ -1602,6 +1737,8 @@ static const struct dpctl_command all_commands[] = {
     { "del-flows", "[dp]", 0, 1, dpctl_del_flows, DP_RW },
     { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack, DP_RO },
     { "flush-conntrack", "[dp] [zone=N]", 0, 2, dpctl_flush_conntrack, DP_RW },
+    { "ct-stats-show", "[dp] [zone=N] [verbose]",
+            0, 3, dpctl_ct_stats_show, DP_RO },
     { "help", "", 0, INT_MAX, dpctl_help, DP_RO },
     { "list-commands", "", 0, INT_MAX, dpctl_list_commands, DP_RO },
 
@@ -1625,7 +1762,6 @@ 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;
diff --git a/lib/dpctl.man b/lib/dpctl.man
index f6e4a7a..cccc574 100644
--- a/lib/dpctl.man
+++ b/lib/dpctl.man
@@ -220,3 +220,10 @@ added to the output.
 Flushes all the connection entries in the tracker used by \fIdp\fR.
 If \fBzone=\fIzone\fR is specified, only flushes the connections in
 \fBzone\fR.
+.
+.TP
+\*(DX\fBct\-stats\-show\fR [\fIdp\fR] [\fBzone=\fIzone\fR] [\fBverbose\fR]
+Displays the number of connections grouped by protocol used by \fIdp\fR.
+If \fBzone=\fIzone\fR is specified, numbers refer to the connections in
+\fBzone\fR. The \fBverbose\fR option allows to group by connection state
+for each protocol.
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 1cc92f5..ea75c0c 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -181,6 +181,8 @@ usage(void *userdata OVS_UNUSED)
                "display conntrack entries for ZONE\n"
            "  flush-conntrack [DP] [zone=ZONE] " \
                "delete all conntrack entries in ZONE\n"
+           "  ct-stats-show [DP] [zone=ZONE] [verbose] " \
+               "CT connections grouped by protocol\n"
            "Each IFACE on add-dp, add-if, and set-if may be followed by\n"
            "comma-separated options.  See ovs-dpctl(8) for syntax, or the\n"
            "Interface table in ovs-vswitchd.conf.db(5) for an options list.\n"
