[ovs-dev] [PATCH v6] ipfix: add support for exporting ipfix statistics

Benli Ye daniely at vmware.com
Mon Jun 13 03:57:13 UTC 2016


It is meaningful for user to check the stats of IPFIX.
Using IPFIX stats, user can know how much flows the system
can support. It is also can be used for performance check
of IPFIX.

IPFIX stats is added for per IPFIX exporter. If bridge IPFIX is
enabled on the bridge, the whole bridge will have one exporter.
For flow IPFIX, the system keeps per id (column in
Flow_Sample_Collector_Set) per exporter.

1) Add 'ovs-ofctl dump-ipfix-bridge SWITCH' to export IPFIX stats of
   the bridge which enable bridge IPFIX. The output format:
   NXST_IPFIX_BRIDGE reply (xid=0x2):
     bridge ipfix: flows=0, current flows=0, sampled pkts=0, \
                   ipv4 ok=0, ipv6 ok=0, tx pkts=0
                   pkts errs=0, ipv4 errs=0, ipv6 errs=0, tx errs=0
2) Add 'ovs-ofctl dump-ipfix-flow SWITCH' to export IPFIX stats of
   the bridge which enable flow IPFIX. The output format:
   NXST_IPFIX_FLOW reply (xid=0x2): 2 ids
     id   1: flows=4, current flows=4, sampled pkts=14, ipv4 ok=13, \
             ipv6 ok=0, tx pkts=0
             pkts errs=0, ipv4 errs=0, ipv6 errs=0, tx errs=0
     id   2: flows=0, current flows=0, sampled pkts=0, ipv4 ok=0, \
             ipv6 ok=0, tx pkts=0
             pkts errs=0, ipv4 errs=0, ipv6 errs=0, tx errs=0

flows: the number of total flow records, including those exported.
current flows: the number of current flow records cached.
sampled pkts: Successfully sampled packet count.
ipv4 ok: successfully sampled IPv4 flow packet count.
ipv6 ok: Successfully sampled IPv6 flow packet count.
tx pkts: the count of IPFIX exported packets sent  to the collector(s).
pkts errs: count of packets failed when sampling, maybe not supported or other error.
ipv4 errs: Count of IPV4 flow packet in the error packets.
ipv6 errs: Count of IPV6 flow packet in the error packets.
tx errs: the count of IPFIX exported packets failed when sending to the collector(s).

Signed-off-by: Benli Ye <daniely at vmware.com>
---
 NEWS                             |   2 +
 include/openflow/nicira-ext.h    |  17 ++++
 include/openvswitch/ofp-errors.h |   8 ++
 include/openvswitch/ofp-msgs.h   |  16 ++++
 include/openvswitch/ofp-util.h   |  19 ++++
 lib/ofp-print.c                  |  92 +++++++++++++++++++
 lib/ofp-util.c                   |  90 ++++++++++++++++++
 lib/rconn.c                      |   4 +
 ofproto/collectors.c             |  10 +-
 ofproto/collectors.h             |   2 +-
 ofproto/ofproto-dpif-ipfix.c     | 194 ++++++++++++++++++++++++++++++++++-----
 ofproto/ofproto-dpif-ipfix.h     |   2 +
 ofproto/ofproto-dpif.c           |  16 ++++
 ofproto/ofproto-provider.h       |  10 ++
 ofproto/ofproto.c                |  66 +++++++++++++
 tests/ofp-print.at               |  79 ++++++++++++++++
 tests/ofproto-dpif.at            | 146 ++++++++++++++++++++++++++++-
 utilities/ovs-ofctl.8.in         |  22 ++++-
 utilities/ovs-ofctl.c            |  19 ++++
 19 files changed, 784 insertions(+), 30 deletions(-)

diff --git a/NEWS b/NEWS
index ba201cf..08094c5 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 8950335..5ab026c 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 f963d2b..a378909 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 560cbe0..8dab858 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 854482b..7a5c905 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 5e49c60..5747ec6 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 2c6fb1f..0fbf22f 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -7944,6 +7944,92 @@ 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)
+{
+    uint16_t len = ntohs(oh->length);
+    struct ofpbuf b = ofpbuf_const_initializer(oh, len);
+    ofpraw_pull_assert(&b);
+
+    return  (len - sizeof(*oh)) / 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 +9914,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 8482d47..51e1b1b 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 5b29212..bc92332 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 6529b8d..1e4e961 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 79ba234..b692026 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 {
@@ -495,7 +507,7 @@ dpif_ipfix_exporter_init(struct dpif_ipfix_exporter *exporter)
 {
     exporter->collectors = NULL;
     exporter->seq_number = 1;
-    exporter->last_template_set_time = TIME_MIN;
+    exporter->last_template_set_time = 0;
     hmap_init(&exporter->cache_flow_key_map);
     ovs_list_init(&exporter->cache_flow_start_timestamp_list);
     exporter->cache_active_timeout = 0;
@@ -511,7 +523,7 @@ dpif_ipfix_exporter_clear(struct dpif_ipfix_exporter *exporter)
     collectors_destroy(exporter->collectors);
     exporter->collectors = NULL;
     exporter->seq_number = 1;
-    exporter->last_template_set_time = TIME_MIN;
+    exporter->last_template_set_time = 0;
     exporter->cache_active_timeout = 0;
     exporter->cache_max_flows = 0;
 }
@@ -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 2bb0e43..c8e4593 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 91529fe..8a3c525 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 d7fd50e..e139632 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 835a397..2c558a6 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 11337b5..ff1df39 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 638d269..c85838f 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 e2e26f7..604107c 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 207588b..2ae0e81 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",
-- 
1.9.1




More information about the dev mailing list