summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS2
-rw-r--r--include/openflow/nicira-ext.h17
-rw-r--r--include/openvswitch/ofp-errors.h8
-rw-r--r--include/openvswitch/ofp-msgs.h16
-rw-r--r--include/openvswitch/ofp-util.h19
-rw-r--r--lib/ofp-print.c92
-rw-r--r--lib/ofp-util.c89
-rw-r--r--lib/rconn.c4
-rw-r--r--ofproto/collectors.c10
-rw-r--r--ofproto/collectors.h2
-rw-r--r--ofproto/ofproto-dpif-ipfix.c190
-rw-r--r--ofproto/ofproto-dpif-ipfix.h2
-rw-r--r--ofproto/ofproto-dpif.c16
-rw-r--r--ofproto/ofproto-provider.h10
-rw-r--r--ofproto/ofproto.c66
-rw-r--r--tests/ofp-print.at79
-rw-r--r--tests/ofproto-dpif.at146
-rw-r--r--utilities/ovs-ofctl.8.in22
-rw-r--r--utilities/ovs-ofctl.c19
19 files changed, 781 insertions, 28 deletions
diff --git a/NEWS b/NEWS
index ba201cf15..08094c52c 100644
--- a/NEWS
+++ b/NEWS
@@ -19,6 +19,8 @@ Post-v2.5.0
* queue-get-config command now allows a queue ID to be specified.
* '--bundle' option can now be used with OpenFlow 1.3.
* New option "--color" to produce colorized output for some commands.
+ * New commands "dump-ipfix-bridge" and "dump-ipfix-flow" to dump bridge
+ IPFIX statistics and flow based IPFIX statistics.
- DPDK:
* New option "n_rxq" for PMD interfaces.
Old 'other_config:n-dpdk-rxqs' is no longer supported.
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 895033512..5ab026c46 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -774,6 +774,23 @@ struct nx_aggregate_stats_request {
*/
};
OFP_ASSERT(sizeof(struct nx_aggregate_stats_request) == 8);
+
+struct nx_ipfix_stats_reply {
+ ovs_be64 total_flows;
+ ovs_be64 current_flows;
+ ovs_be64 pkts;
+ ovs_be64 ipv4_pkts;
+ ovs_be64 ipv6_pkts;
+ ovs_be64 error_pkts;
+ ovs_be64 ipv4_error_pkts;
+ ovs_be64 ipv6_error_pkts;
+ ovs_be64 tx_pkts;
+ ovs_be64 tx_errors;
+ ovs_be32 collector_set_id; /* Range 0 to 4,294,967,295. */
+ uint8_t pad[4]; /* Pad to a multiple of 8 bytes. */
+};
+OFP_ASSERT(sizeof(struct nx_ipfix_stats_reply) == 88);
+
/* NXT_SET_CONTROLLER_ID.
*
diff --git a/include/openvswitch/ofp-errors.h b/include/openvswitch/ofp-errors.h
index f963d2b33..a37890991 100644
--- a/include/openvswitch/ofp-errors.h
+++ b/include/openvswitch/ofp-errors.h
@@ -781,6 +781,14 @@ enum ofperr {
* continuation was generated, or continuation was not generated by this
* Open vSwitch instance. */
OFPERR_NXR_STALE,
+
+/* ## ---------- ## */
+/* ## NXT_STATS ## */
+/* ## ---------- ## */
+
+ /* NX1.0-1.1(1,535), NX1.2+(36). Protocol is not configured on this
+ * Open vSwitch instance. */
+ OFPERR_NXST_NOT_CONFIGURED,
};
const char *ofperr_domain_get_name(enum ofp_version);
diff --git a/include/openvswitch/ofp-msgs.h b/include/openvswitch/ofp-msgs.h
index 560cbe0c3..8dab85894 100644
--- a/include/openvswitch/ofp-msgs.h
+++ b/include/openvswitch/ofp-msgs.h
@@ -467,6 +467,18 @@ enum ofpraw {
/* NXT 1.0+ (28): uint8_t[8][]. */
OFPRAW_NXT_RESUME,
+
+ /* NXST 1.0+ (3): void. */
+ OFPRAW_NXST_IPFIX_BRIDGE_REQUEST,
+
+ /* NXST 1.0+ (3): struct nx_ipfix_stats_reply. */
+ OFPRAW_NXST_IPFIX_BRIDGE_REPLY,
+
+ /* NXST 1.0+ (4): void. */
+ OFPRAW_NXST_IPFIX_FLOW_REQUEST,
+
+ /* NXST 1.0+ (4): struct nx_ipfix_stats_reply[]. */
+ OFPRAW_NXST_IPFIX_FLOW_REPLY,
};
/* Decoding messages into OFPRAW_* values. */
@@ -691,6 +703,10 @@ enum ofptype {
OFPTYPE_NXT_TLV_TABLE_REQUEST, /* OFPRAW_NXT_TLV_TABLE_REQUEST. */
OFPTYPE_NXT_TLV_TABLE_REPLY, /* OFPRAW_NXT_TLV_TABLE_REPLY. */
OFPTYPE_NXT_RESUME, /* OFPRAW_NXT_RESUME. */
+ OFPTYPE_IPFIX_BRIDGE_STATS_REQUEST, /* OFPRAW_NXST_IPFIX_BRIDGE_REQUEST */
+ OFPTYPE_IPFIX_BRIDGE_STATS_REPLY, /* OFPRAW_NXST_IPFIX_BRIDGE_REPLY */
+ OFPTYPE_IPFIX_FLOW_STATS_REQUEST, /* OFPRAW_NXST_IPFIX_FLOW_REQUEST */
+ OFPTYPE_IPFIX_FLOW_STATS_REPLY, /* OFPRAW_NXST_IPFIX_FLOW_REPLY */
/* Flow monitor extension. */
OFPTYPE_FLOW_MONITOR_CANCEL, /* OFPRAW_NXT_FLOW_MONITOR_CANCEL. */
diff --git a/include/openvswitch/ofp-util.h b/include/openvswitch/ofp-util.h
index 854482b3e..7a5c905bf 100644
--- a/include/openvswitch/ofp-util.h
+++ b/include/openvswitch/ofp-util.h
@@ -1139,6 +1139,25 @@ int ofputil_decode_port_stats(struct ofputil_port_stats *, struct ofpbuf *msg);
enum ofperr ofputil_decode_port_stats_request(const struct ofp_header *request,
ofp_port_t *ofp10_port);
+struct ofputil_ipfix_stats {
+ uint32_t collector_set_id; /* Used only for flow-based IPFIX statistics. */
+ uint64_t total_flows; /* Totabl flows of this IPFIX exporter. */
+ uint64_t current_flows; /* Current flows of this IPFIX exporter. */
+ uint64_t pkts; /* Successfully sampled packets. */
+ uint64_t ipv4_pkts; /* Successfully sampled IPV4 packets. */
+ uint64_t ipv6_pkts; /* Successfully sampled IPV6 packets. */
+ uint64_t error_pkts; /* Error packets when sampling. */
+ uint64_t ipv4_error_pkts; /* Error IPV4 packets when sampling. */
+ uint64_t ipv6_error_pkts; /* Error IPV6 packets when sampling. */
+ uint64_t tx_pkts; /* TX IPFIX packets. */
+ uint64_t tx_errors; /* IPFIX packets TX errors. */
+};
+
+void ofputil_append_ipfix_stat(struct ovs_list *replies,
+ const struct ofputil_ipfix_stats *ois);
+size_t ofputil_count_ipfix_stats(const struct ofp_header *);
+int ofputil_pull_ipfix_stats(struct ofputil_ipfix_stats *, struct ofpbuf *msg);
+
struct ofputil_queue_stats_request {
ofp_port_t port_no; /* OFPP_ANY means "all ports". */
uint32_t queue_id;
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 5e49c609e..5747ec68f 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -3240,6 +3240,88 @@ ofp_print_requestforward(struct ds *string, const struct ofp_header *oh)
}
static void
+print_ipfix_stat(struct ds *string, const char *leader, uint64_t stat, int more)
+{
+ ds_put_cstr(string, leader);
+ if (stat != UINT64_MAX) {
+ ds_put_format(string, "%"PRIu64, stat);
+ } else {
+ ds_put_char(string, '?');
+ }
+ if (more) {
+ ds_put_cstr(string, ", ");
+ } else {
+ ds_put_cstr(string, "\n");
+ }
+}
+
+static void
+ofp_print_nxst_ipfix_bridge_reply(struct ds *string, const struct ofp_header *oh)
+{
+ struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
+ for (;;) {
+ struct ofputil_ipfix_stats is;
+ int retval;
+
+ retval = ofputil_pull_ipfix_stats(&is, &b);
+ if (retval) {
+ if (retval != EOF) {
+ ds_put_cstr(string, " ***parse error***");
+ }
+ return;
+ }
+
+ ds_put_cstr(string, "\n bridge ipfix: ");
+ print_ipfix_stat(string, "flows=", is.total_flows, 1);
+ print_ipfix_stat(string, "current flows=", is.current_flows, 1);
+ print_ipfix_stat(string, "sampled pkts=", is.pkts, 1);
+ print_ipfix_stat(string, "ipv4 ok=", is.ipv4_pkts, 1);
+ print_ipfix_stat(string, "ipv6 ok=", is.ipv6_pkts, 1);
+ print_ipfix_stat(string, "tx pkts=", is.tx_pkts, 0);
+ ds_put_cstr(string, " ");
+ print_ipfix_stat(string, "pkts errs=", is.error_pkts, 1);
+ print_ipfix_stat(string, "ipv4 errs=", is.ipv4_error_pkts, 1);
+ print_ipfix_stat(string, "ipv6 errs=", is.ipv6_error_pkts, 1);
+ print_ipfix_stat(string, "tx errs=", is.tx_errors, 0);
+ }
+}
+
+static void
+ofp_print_nxst_ipfix_flow_reply(struct ds *string, const struct ofp_header *oh)
+{
+ ds_put_format(string, " %"PRIuSIZE" ids\n", ofputil_count_ipfix_stats(oh));
+
+ struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
+ for (;;) {
+ struct ofputil_ipfix_stats is;
+ int retval;
+
+ retval = ofputil_pull_ipfix_stats(&is, &b);
+ if (retval) {
+ if (retval != EOF) {
+ ds_put_cstr(string, " ***parse error***");
+ }
+ return;
+ }
+
+ ds_put_cstr(string, " id");
+ ds_put_format(string, " %3"PRIuSIZE": ", (size_t) is.collector_set_id);
+ print_ipfix_stat(string, "flows=", is.total_flows, 1);
+ print_ipfix_stat(string, "current flows=", is.current_flows, 1);
+ print_ipfix_stat(string, "sampled pkts=", is.pkts, 1);
+ print_ipfix_stat(string, "ipv4 ok=", is.ipv4_pkts, 1);
+ print_ipfix_stat(string, "ipv6 ok=", is.ipv6_pkts, 1);
+ print_ipfix_stat(string, "tx pkts=", is.tx_pkts, 0);
+ ds_put_cstr(string, " ");
+ print_ipfix_stat(string, "pkts errs=", is.error_pkts, 1);
+ print_ipfix_stat(string, "ipv4 errs=", is.ipv4_error_pkts, 1);
+ print_ipfix_stat(string, "ipv6 errs=", is.ipv6_error_pkts, 1);
+ print_ipfix_stat(string, "tx errs=", is.tx_errors, 0);
+ }
+}
+
+
+static void
ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
struct ds *string, int verbosity)
{
@@ -3529,6 +3611,16 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
case OFPTYPE_NXT_RESUME:
ofp_print_packet_in(string, msg, verbosity);
break;
+ case OFPTYPE_IPFIX_BRIDGE_STATS_REQUEST:
+ break;
+ case OFPTYPE_IPFIX_BRIDGE_STATS_REPLY:
+ ofp_print_nxst_ipfix_bridge_reply(string, oh);
+ break;
+ case OFPTYPE_IPFIX_FLOW_STATS_REQUEST:
+ break;
+ case OFPTYPE_IPFIX_FLOW_STATS_REPLY:
+ ofp_print_nxst_ipfix_flow_reply(string, oh);
+ break;
}
}
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 2c6fb1fea..21a2574c0 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -7944,6 +7944,91 @@ ofputil_decode_port_stats_request(const struct ofp_header *request,
}
}
+static void
+ofputil_ipfix_stats_to_reply(const struct ofputil_ipfix_stats *ois,
+ struct nx_ipfix_stats_reply *reply)
+{
+ reply->collector_set_id = htonl(ois->collector_set_id);
+ reply->total_flows = htonll(ois->total_flows);
+ reply->current_flows = htonll(ois->current_flows);
+ reply->pkts = htonll(ois->pkts);
+ reply->ipv4_pkts = htonll(ois->ipv4_pkts);
+ reply->ipv6_pkts = htonll(ois->ipv6_pkts);
+ reply->error_pkts = htonll(ois->error_pkts);
+ reply->ipv4_error_pkts = htonll(ois->ipv4_error_pkts);
+ reply->ipv6_error_pkts = htonll(ois->ipv6_error_pkts);
+ reply->tx_pkts = htonll(ois->tx_pkts);
+ reply->tx_errors = htonll(ois->tx_errors);
+}
+
+/* Encode a ipfix stat for 'ois' and append it to 'replies'. */
+void
+ofputil_append_ipfix_stat(struct ovs_list *replies,
+ const struct ofputil_ipfix_stats *ois)
+{
+ struct nx_ipfix_stats_reply *reply = ofpmp_append(replies, sizeof *reply);
+ ofputil_ipfix_stats_to_reply(ois, reply);
+}
+
+static enum ofperr
+ofputil_ipfix_stats_from_nx(struct ofputil_ipfix_stats *is,
+ const struct nx_ipfix_stats_reply *reply)
+{
+ is->collector_set_id = ntohl(reply->collector_set_id);
+ is->total_flows = ntohll(reply->total_flows);
+ is->current_flows = ntohll(reply->current_flows);
+ is->pkts = ntohll(reply->pkts);
+ is->ipv4_pkts = ntohll(reply->ipv4_pkts);
+ is->ipv6_pkts = ntohll(reply->ipv6_pkts);
+ is->error_pkts = ntohll(reply->error_pkts);
+ is->ipv4_error_pkts = ntohll(reply->ipv4_error_pkts);
+ is->ipv6_error_pkts = ntohll(reply->ipv6_error_pkts);
+ is->tx_pkts = ntohll(reply->tx_pkts);
+ is->tx_errors = ntohll(reply->tx_errors);
+
+ return 0;
+}
+
+int
+ofputil_pull_ipfix_stats(struct ofputil_ipfix_stats *is, struct ofpbuf *msg)
+{
+ enum ofperr error;
+ enum ofpraw raw;
+
+ memset(is, 0xFF, sizeof (*is));
+
+ error = (msg->header ? ofpraw_decode(&raw, msg->header)
+ : ofpraw_pull(&raw, msg));
+ if (error) {
+ return error;
+ }
+
+ if (!msg->size) {
+ return EOF;
+ } else if (raw == OFPRAW_NXST_IPFIX_BRIDGE_REPLY ||
+ raw == OFPRAW_NXST_IPFIX_FLOW_REPLY) {
+ struct nx_ipfix_stats_reply *reply;
+
+ reply = ofpbuf_try_pull(msg, sizeof *reply);
+ return ofputil_ipfix_stats_from_nx(is, reply);
+ } else {
+ OVS_NOT_REACHED();
+ }
+}
+
+
+/* Returns the number of ipfix stats elements in
+ * OFPTYPE_IPFIX_BRIDGE_STATS_REPLY or OFPTYPE_IPFIX_FLOW_STATS_REPLY
+ * message 'oh'. */
+size_t
+ofputil_count_ipfix_stats(const struct ofp_header *oh)
+{
+ struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
+ ofpraw_pull_assert(&b);
+
+ return b.size / sizeof(struct ofputil_ipfix_stats);
+}
+
/* Frees all of the "struct ofputil_bucket"s in the 'buckets' list. */
void
ofputil_bucket_list_destroy(struct ovs_list *buckets)
@@ -9828,6 +9913,10 @@ ofputil_is_bundlable(enum ofptype type)
case OFPTYPE_NXT_TLV_TABLE_REQUEST:
case OFPTYPE_NXT_TLV_TABLE_REPLY:
case OFPTYPE_NXT_RESUME:
+ case OFPTYPE_IPFIX_BRIDGE_STATS_REQUEST:
+ case OFPTYPE_IPFIX_BRIDGE_STATS_REPLY:
+ case OFPTYPE_IPFIX_FLOW_STATS_REQUEST:
+ case OFPTYPE_IPFIX_FLOW_STATS_REPLY:
break;
}
diff --git a/lib/rconn.c b/lib/rconn.c
index 8482d4733..51e1b1b3d 100644
--- a/lib/rconn.c
+++ b/lib/rconn.c
@@ -1426,6 +1426,10 @@ is_admitted_msg(const struct ofpbuf *b)
case OFPTYPE_NXT_TLV_TABLE_REQUEST:
case OFPTYPE_NXT_TLV_TABLE_REPLY:
case OFPTYPE_NXT_RESUME:
+ case OFPTYPE_IPFIX_BRIDGE_STATS_REQUEST:
+ case OFPTYPE_IPFIX_BRIDGE_STATS_REPLY:
+ case OFPTYPE_IPFIX_FLOW_STATS_REQUEST:
+ case OFPTYPE_IPFIX_FLOW_STATS_REPLY:
default:
return true;
}
diff --git a/ofproto/collectors.c b/ofproto/collectors.c
index 5b2921236..bc92332b1 100644
--- a/ofproto/collectors.c
+++ b/ofproto/collectors.c
@@ -102,10 +102,13 @@ collectors_destroy(struct collectors *c)
}
}
-/* Sends the 'n'-byte 'payload' to each of the collectors in 'c'. */
-void
+/* Sends the 'n'-byte 'payload' to each of the collectors in 'c'.
+ * Return the number of IPFIX packets which were sent unsuccessfully*/
+size_t
collectors_send(const struct collectors *c, const void *payload, size_t n)
{
+ size_t errors = 0;
+
if (c) {
size_t i;
@@ -116,9 +119,12 @@ collectors_send(const struct collectors *c, const void *payload, size_t n)
VLOG_WARN_RL(&rl, "%s: sending to collector failed (%s)",
s, ovs_strerror(errno));
free(s);
+ errors++;
}
}
}
+
+ return errors;
}
int
diff --git a/ofproto/collectors.h b/ofproto/collectors.h
index 6529b8de5..1e4e96135 100644
--- a/ofproto/collectors.h
+++ b/ofproto/collectors.h
@@ -27,7 +27,7 @@ int collectors_create(const struct sset *targets, uint16_t default_port,
struct collectors **);
void collectors_destroy(struct collectors *);
-void collectors_send(const struct collectors *, const void *, size_t);
+size_t collectors_send(const struct collectors *, const void *, size_t);
int collectors_count(const struct collectors *);
diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
index 79ba234ca..d80ff2d8e 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -33,7 +33,6 @@
#include "sset.h"
#include "util.h"
#include "timeval.h"
-#include "util.h"
#include "openvswitch/vlog.h"
VLOG_DEFINE_THIS_MODULE(ipfix);
@@ -48,6 +47,15 @@ static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
#define BFD_CONTROL_DEST_PORT 3784
#define BFD_ECHO_DEST_PORT 3785
+enum ipfix_sampled_packet_type {
+ IPFIX_SAMPLED_PKT_UNKNOWN = 0x00,
+ IPFIX_SAMPLED_PKT_IPV4_OK = 0x01,
+ IPFIX_SAMPLED_PKT_IPV6_OK = 0x02,
+ IPFIX_SAMPLED_PKT_IPV4_ERROR = 0x03,
+ IPFIX_SAMPLED_PKT_IPV6_ERROR = 0x04,
+ IPFIX_SAMPLED_PKT_OTHERS = 0x05
+};
+
/* The standard layer2SegmentId (ID 351) element is included in vDS to send
* the VxLAN tunnel's VNI. It is 64-bit long, the most significant byte is
* used to indicate the type of tunnel (0x01 = VxLAN, 0x02 = GRE) and the three
@@ -75,6 +83,8 @@ enum dpif_ipfix_tunnel_type {
NUM_DPIF_IPFIX_TUNNEL
};
+typedef struct ofputil_ipfix_stats ofproto_ipfix_stats;
+
struct dpif_ipfix_port {
struct hmap_node hmap_node; /* In struct dpif_ipfix's "tunnel_ports" hmap. */
struct ofport *ofport; /* To retrieve port stats. */
@@ -91,6 +101,8 @@ struct dpif_ipfix_exporter {
struct ovs_list cache_flow_start_timestamp_list; /* ipfix_flow_cache_entry. */
uint32_t cache_active_timeout; /* In seconds. */
uint32_t cache_max_flows;
+
+ ofproto_ipfix_stats stats;
};
struct dpif_ipfix_bridge_exporter {
@@ -984,17 +996,21 @@ ipfix_init_header(uint32_t export_time_sec, uint32_t seq_number,
hdr->obs_domain_id = htonl(obs_domain_id);
}
-static void
+static size_t
ipfix_send_msg(const struct collectors *collectors, struct dp_packet *msg)
{
struct ipfix_header *hdr;
+ size_t tx_errors;
/* Adjust the length in the header. */
hdr = dp_packet_data(msg);
hdr->length = htons(dp_packet_size(msg));
- collectors_send(collectors, dp_packet_data(msg), dp_packet_size(msg));
+ tx_errors = collectors_send(collectors,
+ dp_packet_data(msg), dp_packet_size(msg));
dp_packet_set_size(msg, 0);
+
+ return tx_errors;
}
static uint16_t
@@ -1150,20 +1166,23 @@ ipfix_init_template_msg(void *msg_stub, uint32_t export_time_sec,
set_hdr->set_id = htons(IPFIX_SET_ID_TEMPLATE);
}
-static void
+static size_t
ipfix_send_template_msg(const struct collectors *collectors,
struct dp_packet *msg, size_t set_hdr_offset)
{
struct ipfix_set_header *set_hdr;
+ size_t tx_errors;
/* Send template message. */
set_hdr = (struct ipfix_set_header*)
((uint8_t*)dp_packet_data(msg) + set_hdr_offset);
set_hdr->length = htons(dp_packet_size(msg) - set_hdr_offset);
- ipfix_send_msg(collectors, msg);
+ tx_errors = ipfix_send_msg(collectors, msg);
dp_packet_uninit(msg);
+
+ return tx_errors;
}
static void
@@ -1172,9 +1191,11 @@ ipfix_send_template_msgs(struct dpif_ipfix_exporter *exporter,
{
uint64_t msg_stub[DIV_ROUND_UP(MAX_MESSAGE_LEN, 8)];
struct dp_packet msg;
- size_t set_hdr_offset, tmpl_hdr_offset;
+ size_t set_hdr_offset, tmpl_hdr_offset, error_pkts;
struct ipfix_template_record_header *tmpl_hdr;
uint16_t field_count;
+ size_t tx_packets = 0;
+ size_t tx_errors = 0;
enum ipfix_proto_l2 l2;
enum ipfix_proto_l3 l3;
enum ipfix_proto_l4 l4;
@@ -1199,8 +1220,10 @@ ipfix_send_template_msgs(struct dpif_ipfix_exporter *exporter,
*/
if (dp_packet_size(&msg) >= MAX_MESSAGE_LEN) {
/* Send template message. */
- ipfix_send_template_msg(exporter->collectors,
- &msg, set_hdr_offset);
+ error_pkts = ipfix_send_template_msg(exporter->collectors,
+ &msg, set_hdr_offset);
+ tx_errors += error_pkts;
+ tx_packets += collectors_count(exporter->collectors) - error_pkts;
/* Reinitialize the template msg. */
ipfix_init_template_msg(msg_stub, export_time_sec,
@@ -1224,7 +1247,12 @@ ipfix_send_template_msgs(struct dpif_ipfix_exporter *exporter,
}
/* Send template message. */
- ipfix_send_template_msg(exporter->collectors, &msg, set_hdr_offset);
+ error_pkts = ipfix_send_template_msg(exporter->collectors, &msg, set_hdr_offset);
+ tx_errors += error_pkts;
+ tx_packets += collectors_count(exporter->collectors) - error_pkts;
+
+ exporter->stats.tx_pkts += tx_packets;
+ exporter->stats.tx_errors += tx_errors;
/* XXX: Add Options Template Sets, at least to define a Flow Keys
* Option Template. */
@@ -1326,14 +1354,117 @@ ipfix_cache_aggregate_entries(struct ipfix_flow_cache_entry *from_entry,
}
}
+/* Get statistics */
+static void
+ipfix_get_stats__(const struct dpif_ipfix_exporter *exporter,
+ ofproto_ipfix_stats *stats)
+{
+ memset(stats, 0xff, sizeof *stats);
+
+ if (!exporter) {
+ return;
+ }
+
+ *stats = exporter->stats;
+}
+
+static void
+ipfix_get_bridge_stats(const struct dpif_ipfix_bridge_exporter *exporter,
+ ofproto_ipfix_stats *stats)
+{
+ ipfix_get_stats__(&exporter->exporter, stats);
+}
+
+static void
+ipfix_get_flow_stats(const struct dpif_ipfix_flow_exporter *exporter,
+ ofproto_ipfix_stats *stats)
+{
+ ipfix_get_stats__(&exporter->exporter, stats);
+ stats->collector_set_id = exporter->options->collector_set_id;
+}
+
+int
+dpif_ipfix_get_stats(const struct dpif_ipfix *di,
+ bool bridge_ipfix,
+ struct ovs_list *replies)
+ OVS_EXCLUDED(mutex)
+{
+ struct dpif_ipfix_flow_exporter_map_node *flow_exporter_node;
+ struct ofputil_ipfix_stats ois;
+
+ ovs_mutex_lock(&mutex);
+ if (bridge_ipfix) {
+ if (!di->bridge_exporter.options) {
+ ovs_mutex_unlock(&mutex);
+ return OFPERR_NXST_NOT_CONFIGURED;
+ }
+
+ ipfix_get_bridge_stats(&di->bridge_exporter, &ois);
+ ofputil_append_ipfix_stat(replies, &ois);
+ } else {
+ if (hmap_count(&di->flow_exporter_map) == 0) {
+ ovs_mutex_unlock(&mutex);
+ return OFPERR_NXST_NOT_CONFIGURED;
+ }
+
+ HMAP_FOR_EACH (flow_exporter_node, node,
+ &di->flow_exporter_map) {
+ ipfix_get_flow_stats(&flow_exporter_node->exporter, &ois);
+ ofputil_append_ipfix_stat(replies, &ois);
+ }
+ }
+ ovs_mutex_unlock(&mutex);
+
+ return 0;
+}
+
+/* Update partial ipfix stats */
+static void
+ipfix_update_stats(struct dpif_ipfix_exporter *exporter,
+ bool new_flow,
+ size_t current_flows,
+ enum ipfix_sampled_packet_type sampled_pkt_type)
+{
+ if (new_flow) {
+ exporter->stats.total_flows++;
+ exporter->stats.current_flows = current_flows;
+ }
+ exporter->stats.pkts++;
+
+ switch (sampled_pkt_type) {
+ case IPFIX_SAMPLED_PKT_IPV4_OK:
+ exporter->stats.ipv4_pkts++;
+ break;
+ case IPFIX_SAMPLED_PKT_IPV6_OK:
+ exporter->stats.ipv6_pkts++;
+ break;
+ case IPFIX_SAMPLED_PKT_IPV4_ERROR:
+ exporter->stats.ipv4_error_pkts++;
+ exporter->stats.error_pkts++;
+ break;
+ case IPFIX_SAMPLED_PKT_IPV6_ERROR:
+ exporter->stats.ipv6_error_pkts++;
+ exporter->stats.error_pkts++;
+ break;
+ case IPFIX_SAMPLED_PKT_UNKNOWN:
+ exporter->stats.error_pkts++;
+ break;
+ case IPFIX_SAMPLED_PKT_OTHERS:
+ default:
+ break;
+ }
+}
+
/* Add an entry into a flow cache. The entry is either aggregated into
* an existing entry with the same flow key and free()d, or it is
- * inserted into the cache. */
+ * inserted into the cache. And IPFIX stats will be updated */
static void
ipfix_cache_update(struct dpif_ipfix_exporter *exporter,
- struct ipfix_flow_cache_entry *entry)
+ struct ipfix_flow_cache_entry *entry,
+ enum ipfix_sampled_packet_type sampled_pkt_type)
{
struct ipfix_flow_cache_entry *old_entry;
+ size_t current_flows = 0;
old_entry = ipfix_cache_find_entry(exporter, &entry->flow_key);
@@ -1348,17 +1479,19 @@ ipfix_cache_update(struct dpif_ipfix_exporter *exporter,
&entry->cache_flow_start_timestamp_list_node);
/* Enforce exporter->cache_max_flows limit. */
- if (hmap_count(&exporter->cache_flow_key_map)
- > exporter->cache_max_flows) {
+ current_flows = hmap_count(&exporter->cache_flow_key_map);
+ ipfix_update_stats(exporter, true, current_flows, sampled_pkt_type);
+ if (current_flows > exporter->cache_max_flows) {
dpif_ipfix_cache_expire_now(exporter, false);
}
} else {
ipfix_cache_aggregate_entries(entry, old_entry);
free(entry);
+ ipfix_update_stats(exporter, false, current_flows, sampled_pkt_type);
}
}
-static void
+static enum ipfix_sampled_packet_type
ipfix_cache_entry_init(struct ipfix_flow_cache_entry *entry,
const struct dp_packet *packet, const struct flow *flow,
uint64_t packet_delta_count, uint32_t obs_domain_id,
@@ -1372,6 +1505,7 @@ ipfix_cache_entry_init(struct ipfix_flow_cache_entry *entry,
enum ipfix_proto_l3 l3;
enum ipfix_proto_l4 l4;
enum ipfix_proto_tunnel tunnel = IPFIX_PROTO_NOT_TUNNELED;
+ enum ipfix_sampled_packet_type sampled_pkt_type = IPFIX_SAMPLED_PKT_UNKNOWN;
uint8_t ethernet_header_length;
uint16_t ethernet_total_length;
@@ -1391,12 +1525,15 @@ ipfix_cache_entry_init(struct ipfix_flow_cache_entry *entry,
case IPPROTO_UDP:
case IPPROTO_SCTP:
l4 = IPFIX_PROTO_L4_TCP_UDP_SCTP;
+ sampled_pkt_type = IPFIX_SAMPLED_PKT_IPV4_OK;
break;
case IPPROTO_ICMP:
l4 = IPFIX_PROTO_L4_ICMP;
+ sampled_pkt_type = IPFIX_SAMPLED_PKT_IPV4_OK;
break;
default:
l4 = IPFIX_PROTO_L4_UNKNOWN;
+ sampled_pkt_type = IPFIX_SAMPLED_PKT_IPV4_ERROR;
}
break;
case ETH_TYPE_IPV6:
@@ -1406,17 +1543,21 @@ ipfix_cache_entry_init(struct ipfix_flow_cache_entry *entry,
case IPPROTO_UDP:
case IPPROTO_SCTP:
l4 = IPFIX_PROTO_L4_TCP_UDP_SCTP;
+ sampled_pkt_type = IPFIX_SAMPLED_PKT_IPV6_OK;
break;
case IPPROTO_ICMPV6:
l4 = IPFIX_PROTO_L4_ICMP;
+ sampled_pkt_type = IPFIX_SAMPLED_PKT_IPV6_OK;
break;
default:
l4 = IPFIX_PROTO_L4_UNKNOWN;
+ sampled_pkt_type = IPFIX_SAMPLED_PKT_IPV6_ERROR;
}
break;
default:
l3 = IPFIX_PROTO_L3_UNKNOWN;
l4 = IPFIX_PROTO_L4_UNKNOWN;
+ sampled_pkt_type = IPFIX_SAMPLED_PKT_OTHERS;
}
if (tunnel_port && tunnel_key) {
@@ -1569,6 +1710,8 @@ ipfix_cache_entry_init(struct ipfix_flow_cache_entry *entry,
entry->minimum_ip_total_length = 0;
entry->maximum_ip_total_length = 0;
}
+
+ return sampled_pkt_type;
}
/* Send each single data record in its own data set, to simplify the
@@ -1650,14 +1793,20 @@ ipfix_send_data_msg(struct dpif_ipfix_exporter *exporter,
{
uint64_t msg_stub[DIV_ROUND_UP(MAX_MESSAGE_LEN, 8)];
struct dp_packet msg;
+ size_t tx_errors;
+
dp_packet_use_stub(&msg, msg_stub, sizeof msg_stub);
ipfix_init_header(export_time_sec, exporter->seq_number++,
entry->flow_key.obs_domain_id, &msg);
ipfix_put_data_set(export_time_sec, entry, flow_end_reason, &msg);
- ipfix_send_msg(exporter->collectors, &msg);
+ tx_errors = ipfix_send_msg(exporter->collectors, &msg);
dp_packet_uninit(&msg);
+
+ exporter->stats.current_flows--;
+ exporter->stats.tx_pkts += collectors_count(exporter->collectors) - tx_errors;
+ exporter->stats.tx_errors += tx_errors;
}
static void
@@ -1669,13 +1818,16 @@ dpif_ipfix_sample(struct dpif_ipfix_exporter *exporter,
const struct flow_tnl *tunnel_key)
{
struct ipfix_flow_cache_entry *entry;
+ enum ipfix_sampled_packet_type sampled_packet_type;
/* Create a flow cache entry from the sample. */
entry = xmalloc(sizeof *entry);
- ipfix_cache_entry_init(entry, packet, flow, packet_delta_count,
- obs_domain_id, obs_point_id,
- output_odp_port, tunnel_port, tunnel_key);
- ipfix_cache_update(exporter, entry);
+ sampled_packet_type = ipfix_cache_entry_init(entry, packet,
+ flow, packet_delta_count,
+ obs_domain_id, obs_point_id,
+ output_odp_port, tunnel_port,
+ tunnel_key);
+ ipfix_cache_update(exporter, entry, sampled_packet_type);
}
static bool
diff --git a/ofproto/ofproto-dpif-ipfix.h b/ofproto/ofproto-dpif-ipfix.h
index 2bb0e43ff..c8e459307 100644
--- a/ofproto/ofproto-dpif-ipfix.h
+++ b/ofproto/ofproto-dpif-ipfix.h
@@ -46,6 +46,8 @@ void dpif_ipfix_set_options(
const struct ofproto_ipfix_bridge_exporter_options *,
const struct ofproto_ipfix_flow_exporter_options *, size_t);
+int dpif_ipfix_get_stats(const struct dpif_ipfix *, bool, struct ovs_list *);
+
void dpif_ipfix_bridge_sample(struct dpif_ipfix *, const struct dp_packet *,
const struct flow *,
odp_port_t, odp_port_t, const struct flow_tnl *);
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 91529fe26..8a3c5259c 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1960,6 +1960,21 @@ set_ipfix(
}
static int
+get_ipfix_stats(const struct ofproto *ofproto_,
+ bool bridge_ipfix,
+ struct ovs_list *replies)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+ struct dpif_ipfix *di = ofproto->ipfix;
+
+ if (!di) {
+ return OFPERR_NXST_NOT_CONFIGURED;
+ }
+
+ return dpif_ipfix_get_stats(di, bridge_ipfix, replies);
+}
+
+static int
set_cfm(struct ofport *ofport_, const struct cfm_settings *s)
{
struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
@@ -5555,6 +5570,7 @@ const struct ofproto_class ofproto_dpif_class = {
get_netflow_ids,
set_sflow,
set_ipfix,
+ get_ipfix_stats,
set_cfm,
cfm_status_changed,
get_cfm_status,
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index d7fd50ef3..e139632c0 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -1361,6 +1361,16 @@ struct ofproto_class {
const struct ofproto_ipfix_flow_exporter_options
*flow_exporters_options, size_t n_flow_exporters_options);
+ /* Gets IPFIX stats on 'ofproto' according to the exporter of birdge
+ * IPFIX or flow-based IPFIX.
+ *
+ * OFPERR_NXST_NOT_CONFIGURED as a return value indicates that bridge
+ * IPFIX or flow-based IPFIX is not configured. */
+ int (*get_ipfix_stats)(
+ const struct ofproto *ofproto,
+ bool bridge_ipfix, struct ovs_list *replies
+ );
+
/* Configures connectivity fault management on 'ofport'.
*
* If 'cfm_settings' is nonnull, configures CFM according to its members.
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 835a3976f..2c558a667 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -863,6 +863,64 @@ ofproto_set_ipfix(struct ofproto *ofproto,
}
}
+static int
+ofproto_get_ipfix_stats(struct ofproto *ofproto,
+ bool bridge_ipfix,
+ struct ovs_list *replies)
+{
+ int error;
+
+ if (ofproto->ofproto_class->get_ipfix_stats) {
+ error = ofproto->ofproto_class->get_ipfix_stats(ofproto,
+ bridge_ipfix,
+ replies);
+ } else {
+ error = EOPNOTSUPP;
+ }
+
+ return error;
+}
+
+static enum ofperr
+handle_ipfix_bridge_stats_request(struct ofconn *ofconn,
+ const struct ofp_header *request)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct ovs_list replies;
+ enum ofperr error;
+
+ ofpmp_init(&replies, request);
+ error = ofproto_get_ipfix_stats(ofproto, true, &replies);
+
+ if (!error) {
+ ofconn_send_replies(ofconn, &replies);
+ } else {
+ ofpbuf_list_delete(&replies);
+ }
+
+ return error;
+}
+
+static enum ofperr
+handle_ipfix_flow_stats_request(struct ofconn *ofconn,
+ const struct ofp_header *request)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct ovs_list replies;
+ enum ofperr error;
+
+ ofpmp_init(&replies, request);
+ error = ofproto_get_ipfix_stats(ofproto, false, &replies);
+
+ if (!error) {
+ ofconn_send_replies(ofconn, &replies);
+ } else {
+ ofpbuf_list_delete(&replies);
+ }
+
+ return error;
+}
+
void
ofproto_set_flow_restore_wait(bool flow_restore_wait_db)
{
@@ -7356,6 +7414,12 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
case OFPTYPE_NXT_TLV_TABLE_REQUEST:
return handle_tlv_table_request(ofconn, oh);
+ case OFPTYPE_IPFIX_BRIDGE_STATS_REQUEST:
+ return handle_ipfix_bridge_stats_request(ofconn, oh);
+
+ case OFPTYPE_IPFIX_FLOW_STATS_REQUEST:
+ return handle_ipfix_flow_stats_request(ofconn, oh);
+
case OFPTYPE_HELLO:
case OFPTYPE_ERROR:
case OFPTYPE_FEATURES_REPLY:
@@ -7389,6 +7453,8 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
case OFPTYPE_REQUESTFORWARD:
case OFPTYPE_TABLE_STATUS:
case OFPTYPE_NXT_TLV_TABLE_REPLY:
+ case OFPTYPE_IPFIX_BRIDGE_STATS_REPLY:
+ case OFPTYPE_IPFIX_FLOW_STATS_REPLY:
default:
if (ofpmsg_is_stat_request(oh)) {
return OFPERR_OFPBRC_BAD_STAT;
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index 11337b5cd..ff1df392c 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -3507,3 +3507,82 @@ OFPT_PORT_MOD (OF1.4) (xid=0x3): port: 3: addr:50:54:00:00:00:01
advertise: 10MB-HD
])
AT_CLEANUP
+
+AT_SETUP([NXST_IPFIX_BRIDGE - request])
+AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
+AT_CHECK([ovs-ofctl ofp-print "\
+01 10 00 18 00 00 00 02 \
+ff ff 00 00 00 00 23 20 00 00 00 03 00 00 00 00 \
+"], [0], [dnl
+NXST_IPFIX_BRIDGE request (xid=0x2):
+])
+AT_CLEANUP
+
+AT_SETUP([NXST_IPFIX_BRIDGE - reply])
+AT_KEYWORDS([ofp-print OFPT_STATS_REPLY])
+AT_CHECK([ovs-ofctl ofp-print "\
+01 11 00 70 00 00 00 02 \
+ff ff 00 00 00 00 23 20 00 00 00 03 00 00 00 00\
+00 00 00 00 00 00 00 01 \
+00 00 00 00 00 00 00 10 \
+00 00 00 00 00 00 00 78 \
+00 00 00 00 00 00 00 f0 \
+00 00 00 00 00 00 00 00 \
+00 00 00 00 00 00 00 a0 \
+00 00 00 00 00 00 00 02 \
+00 00 00 00 00 00 00 03 \
+00 00 00 00 00 00 00 04 \
+00 00 00 00 00 00 00 05 \
+00 00 00 00 00 00 00 00 \
+"], [0], [dnl
+NXST_IPFIX_BRIDGE reply (xid=0x2):
+ bridge ipfix: flows=1, current flows=16, sampled pkts=120, ipv4 ok=240, ipv6 ok=0, tx pkts=4
+ pkts errs=160, ipv4 errs=2, ipv6 errs=3, tx errs=5
+])
+AT_CLEANUP
+
+AT_SETUP([NXST_IPFIX_FLOW - request])
+AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
+AT_CHECK([ovs-ofctl ofp-print "\
+01 10 00 18 00 00 00 02 \
+ff ff 00 00 00 00 23 20 00 00 00 04 00 00 00 00 \
+"], [0], [dnl
+NXST_IPFIX_FLOW request (xid=0x2):
+])
+AT_CLEANUP
+
+AT_SETUP([NXST_IPFIX_FLOW - reply])
+AT_KEYWORDS([ofp-print OFPT_STATS_REPLY])
+AT_CHECK([ovs-ofctl ofp-print "\
+01 11 00 C8 00 00 00 02 \
+ff ff 00 00 00 00 23 20 00 00 00 04 00 00 00 00\
+00 00 00 00 00 00 00 01 \
+00 00 00 00 00 00 00 10 \
+00 00 00 00 00 00 00 78 \
+00 00 00 00 00 00 00 f0 \
+00 00 00 00 00 00 00 00 \
+00 00 00 00 00 00 00 a0 \
+00 00 00 10 00 00 00 02 \
+00 00 00 00 00 00 00 03 \
+00 00 00 00 00 00 00 04 \
+00 00 00 00 00 00 00 05 \
+00 00 00 01 00 00 00 00 \
+00 00 00 00 00 00 00 01 \
+00 00 00 00 00 00 00 10 \
+00 00 00 00 00 00 00 78 \
+00 00 00 00 00 00 00 f0 \
+00 00 00 00 00 00 00 00 \
+00 00 00 00 00 00 00 a0 \
+00 00 00 10 00 00 00 02 \
+00 00 00 00 00 00 00 03 \
+00 00 00 00 00 00 00 04 \
+00 00 00 00 00 00 00 05 \
+00 00 00 02 00 00 00 00 \
+"], [0], [dnl
+NXST_IPFIX_FLOW reply (xid=0x2): 2 ids
+ id 1: flows=1, current flows=16, sampled pkts=120, ipv4 ok=240, ipv6 ok=0, tx pkts=4
+ pkts errs=160, ipv4 errs=68719476738, ipv6 errs=3, tx errs=5
+ id 2: flows=1, current flows=16, sampled pkts=120, ipv4 ok=240, ipv6 ok=0, tx pkts=4
+ pkts errs=160, ipv4 errs=68719476738, ipv6 errs=3, tx errs=5
+])
+AT_CLEANUP
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 0057441c4..406bde16b 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -5790,18 +5790,18 @@ CHECK_NETFLOW_ACTIVE_EXPIRATION([[[::1]]])
AT_CLEANUP
dnl In the absence of an IPFIX collector to verify protocol correctness, simply
-dnl configure IPFIX and ensure that sample action generation works at the
+dnl configure bridge IPFIX and ensure that sample action generation works at the
dnl datapath level.
-AT_SETUP([ofproto-dpif - Basic IPFIX sanity check])
+AT_SETUP([ofproto-dpif - Bridge IPFIX sanity check])
OVS_VSWITCHD_START
add_of_ports br0 1 2
-dnl Sample every packet using bridge-based sampling
+dnl Sample every packet using bridge-based sampling.
AT_CHECK([ovs-vsctl -- set bridge br0 ipfix=@fix -- \
--id=@fix create ipfix targets=\"127.0.0.1:4739\" \
sampling=1], [0], [ignore])
-dnl Send some packets that should be sampled
+dnl Send some packets that should be sampled.
for i in `seq 1 3`; do
AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
done
@@ -5810,7 +5810,7 @@ flow-dump from non-dpdk interfaces:
packets:2, bytes:120, used:0.001s, actions:sample(sample=100.0%,actions(userspace(pid=0,ipfix(output_port=4294967295))))
])
-dnl Remove the IPFIX configuration
+dnl Remove the IPFIX configuration.
AT_CHECK([ovs-vsctl clear bridge br0 ipfix])
AT_CHECK([ovs-appctl revalidator/purge])
@@ -5826,6 +5826,142 @@ packets:2, bytes:120, used:0.001s, actions:drop
OVS_VSWITCHD_STOP(["/sending to collector failed/d"])
AT_CLEANUP
+dnl Bridge IPFIX statistics check
+AT_SETUP([ofproto-dpif - Bridge IPFIX statistics check])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2
+
+dnl Negative test check.
+AT_CHECK([ovs-ofctl dump-ipfix-bridge br0], [0], [dnl
+OFPT_ERROR (xid=0x2): NXST_NOT_CONFIGURED
+NXST_IPFIX_BRIDGE request (xid=0x2):
+])
+
+dnl Sample every packet using bridge-based sampling.
+AT_CHECK([ovs-vsctl -- set bridge br0 ipfix=@fix -- \
+ --id=@fix create ipfix targets=\"127.0.0.1:4739\" \
+ sampling=1], [0], [ignore])
+
+dnl Send some packets that should be sampled.
+for i in `seq 1 20`; do
+ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
+done
+
+dnl There are 4 extra IPFIX template packets.
+AT_CHECK([ovs-ofctl dump-ipfix-bridge br0], [0], [dnl
+NXST_IPFIX_BRIDGE reply (xid=0x2):
+ bridge ipfix: flows=20, current flows=0, sampled pkts=20, ipv4 ok=0, ipv6 ok=0, tx pkts=12
+ pkts errs=20, ipv4 errs=20, ipv6 errs=0, tx errs=12
+])
+
+dnl Remove the IPFIX configuration.
+AT_CHECK([ovs-vsctl clear bridge br0 ipfix])
+AT_CHECK([ovs-appctl revalidator/purge])
+
+dnl Send some more packets, to ensure that these are not sampled.
+for i in `seq 1 2`; do
+ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
+done
+AT_CHECK([ovs-ofctl dump-ipfix-bridge br0], [0], [dnl
+OFPT_ERROR (xid=0x2): NXST_NOT_CONFIGURED
+NXST_IPFIX_BRIDGE request (xid=0x2):
+])
+
+OVS_VSWITCHD_STOP(["/sending to collector failed/d"])
+AT_CLEANUP
+
+dnl Flow IPFIX sanity check
+AT_SETUP([ofproto-dpif - Flow IPFIX sanity check])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2
+
+AT_CHECK([ovs-vsctl -- --id=@br0 get Bridge br0 \
+ -- --id=@ipfix create IPFIX targets=\"127.0.0.1:4739\" \
+ -- --id=@cs create Flow_Sample_Collector_Set id=1 bridge=@br0 ipfix=@ipfix],
+ [0], [ignore])
+
+AT_DATA([flows.txt], [dnl
+in_port=1, actions=sample(probability=65535,collector_set_id=1),output:2
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt], [0], [ignore])
+
+dnl Send some packets that should be sampled.
+for i in `seq 1 3`; do
+ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
+done
+AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/.*\(packets:\)/\1/' | sed 's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
+flow-dump from non-dpdk interfaces:
+packets:2, bytes:120, used:0.001s, actions:sample(sample=100.0%,actions(userspace(pid=0,flow_sample(probability=65535,collector_set_id=1,obs_domain_id=0,obs_point_id=0)))),2
+])
+
+dnl Remove the flow which contains sample action.
+AT_CHECK([ovs-ofctl del-flows br0 in_port=1], [0], [ignore])
+AT_CHECK([ovs-appctl revalidator/purge])
+
+dnl Send some more packets, to ensure that these are not sampled.
+for i in `seq 1 3`; do
+ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
+done
+AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/.*\(packets:\)/\1/' | sed 's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
+flow-dump from non-dpdk interfaces:
+packets:2, bytes:120, used:0.001s, actions:drop
+])
+
+OVS_VSWITCHD_STOP(["/sending to collector failed/d"])
+AT_CLEANUP
+
+dnl Flow based IPFIX statistics check
+AT_SETUP([ofproto-dpif - Flow IPFIX statistics check])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2
+
+dnl Negative test check.
+AT_CHECK([ovs-ofctl dump-ipfix-flow br0], [0], [dnl
+OFPT_ERROR (xid=0x2): NXST_NOT_CONFIGURED
+NXST_IPFIX_FLOW request (xid=0x2):
+])
+
+AT_CHECK([ovs-vsctl -- --id=@br0 get Bridge br0 \
+ -- --id=@ipfix create IPFIX targets=\"127.0.0.1:4739\" \
+ -- --id=@cs create Flow_Sample_Collector_Set id=1 bridge=@br0 ipfix=@ipfix],
+ [0], [ignore])
+
+AT_DATA([flows.txt], [dnl
+in_port=1, actions=sample(probability=65535,collector_set_id=1),output:2
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt], [0], [ignore])
+
+dnl Send some packets that should be sampled.
+for i in `seq 1 20`; do
+ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
+done
+
+dnl There are 4 extra IPFIX template packets.
+AT_CHECK([ovs-ofctl dump-ipfix-flow br0], [0], [dnl
+NXST_IPFIX_FLOW reply (xid=0x2): 1 ids
+ id 1: flows=20, current flows=0, sampled pkts=20, ipv4 ok=0, ipv6 ok=0, tx pkts=12
+ pkts errs=20, ipv4 errs=20, ipv6 errs=0, tx errs=12
+])
+
+dnl Remove the flow which contains sample action.
+AT_CHECK([ovs-ofctl del-flows br0 in_port=1], [0], [ignore])
+AT_CHECK([ovs-vsctl destroy Flow_Sample_Collector_Set 1], [0], [ignore])
+AT_CHECK([ovs-appctl revalidator/purge])
+
+dnl Send some more packets, to ensure that these are not sampled.
+for i in `seq 1 3`; do
+ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
+done
+AT_CHECK([ovs-ofctl dump-ipfix-flow br0], [0], [dnl
+OFPT_ERROR (xid=0x2): NXST_NOT_CONFIGURED
+NXST_IPFIX_FLOW request (xid=0x2):
+])
+
+OVS_VSWITCHD_STOP(["/sending to collector failed/d"])
+AT_CLEANUP
+
AT_SETUP([ofproto-dpif - flow stats])
OVS_VSWITCHD_START
AT_CHECK([ovs-ofctl add-flow br0 "ip,actions=NORMAL"])
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index e2e26f7b4..604107cfb 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -255,6 +255,26 @@ This command has limited usefulness, because ports often have no
configured queues and because the OpenFlow protocol provides only very
limited information about the configuration of a queue.
.
+.IP "\fBdump\-ipfix\-bridge \fIswitch
+Prints to the console the statistics of bridge IPFIX for \fIswitch\fR.
+If bridge IPFIX is configured on the \fIswitch\fR, IPFIX statistics
+can be retrieved. Otherwise, error message will be printed.
+.IP
+This command uses an Open vSwitch extension that is only in Open
+vSwitch 2.6 and later.
+.
+.IP "\fBdump\-ipfix\-flow \fIswitch
+Prints to the console the statistics of flow-based IPFIX for
+\fIswitch\fR. If flow-based IPFIX is configured on the \fIswitch\fR,
+statistics of all the collector set ids on the \fIswitch\fR will be
+printed. Otherwise, print error message.
+.IP
+Refer to \fBovs-vswitchd.conf.db\fR(5) for more details on configuring
+flow based IPFIX and collector set ids.
+.IP
+This command uses an Open vSwitch extension that is only in Open
+vSwitch 2.6 and later.
+.
.SS "OpenFlow 1.1+ Group Table Commands"
.
The following commands work only with switches that support OpenFlow
@@ -2302,7 +2322,7 @@ When sending samples to IPFIX collectors, the unsigned 32-bit integer
Observation Point ID sent in every IPFIX flow record. Defaults to 0.
.RE
.IP
-Refer to \fBovs\-vswitchd.conf.db\fR(8) for more details on
+Refer to \fBovs\-vswitchd.conf.db\fR(5) for more details on
configuring sample collector sets.
.IP
This action was added in Open vSwitch 1.10.90.
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 207588b5d..2ae0e81bc 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -425,6 +425,8 @@ usage(void)
" add-tlv-map SWITCH MAP add TLV option MAPpings\n"
" del-tlv-map SWITCH [MAP] delete TLV option MAPpings\n"
" dump-tlv-map SWITCH print TLV option mappings\n"
+ " dump-ipfix-bridge SWITCH print ipfix stats of bridge\n"
+ " dump-ipfix-flow SWITCH print flow ipfix of a bridge\n"
"\nFor OpenFlow switches and controllers:\n"
" probe TARGET probe whether TARGET is up\n"
" ping TARGET [N] latency of N-byte echos\n"
@@ -2437,6 +2439,18 @@ ofctl_benchmark(struct ovs_cmdl_context *ctx)
}
static void
+ofctl_dump_ipfix_bridge(struct ovs_cmdl_context *ctx)
+{
+ dump_trivial_transaction(ctx->argv[1], OFPRAW_NXST_IPFIX_BRIDGE_REQUEST);
+}
+
+static void
+ofctl_dump_ipfix_flow(struct ovs_cmdl_context *ctx)
+{
+ dump_trivial_transaction(ctx->argv[1], OFPRAW_NXST_IPFIX_FLOW_REQUEST);
+}
+
+static void
ofctl_group_mod__(const char *remote, struct ofputil_group_mod *gms,
size_t n_gms, enum ofputil_protocol usable_protocols)
{
@@ -4027,6 +4041,11 @@ static const struct ovs_cmdl_command all_commands[] = {
{ "benchmark", "target n count",
3, 3, ofctl_benchmark },
+ { "dump-ipfix-bridge", "switch",
+ 1, 1, ofctl_dump_ipfix_bridge},
+ { "dump-ipfix-flow", "switch",
+ 1, 1, ofctl_dump_ipfix_flow},
+
{ "ofp-parse", "file",
1, 1, ofctl_ofp_parse },
{ "ofp-parse-pcap", "pcap",