@@ -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);
+}
@@ -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 */
@@ -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;
@@ -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.
@@ -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"