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

Benli Ye daniely at vmware.com
Fri May 27 15:35:30 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 pkts=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 pkts=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>
---
 include/openflow/nicira-ext.h    |  16 ++++
 include/openvswitch/ofp-errors.h |   8 ++
 include/openvswitch/ofp-msgs.h   |  16 ++++
 include/openvswitch/ofp-util.h   |  19 ++++
 lib/ofp-print.c                  | 102 ++++++++++++++++++++
 lib/ofp-util.c                   | 102 ++++++++++++++++++++
 lib/rconn.c                      |   4 +
 ofproto/collectors.c             |  10 +-
 ofproto/collectors.h             |   2 +-
 ofproto/ofproto-dpif-ipfix.c     | 201 +++++++++++++++++++++++++++++++++++----
 ofproto/ofproto-dpif-ipfix.h     |   2 +
 ofproto/ofproto-dpif.c           |  20 ++++
 ofproto/ofproto-provider.h       |   9 ++
 ofproto/ofproto.c                |  66 +++++++++++++
 ofproto/ofproto.h                |  13 +++
 utilities/ovs-ofctl.c            |  19 ++++
 16 files changed, 585 insertions(+), 24 deletions(-)

diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 8950335..72f803f 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -774,6 +774,22 @@ struct nx_aggregate_stats_request {
      */
 };
 OFP_ASSERT(sizeof(struct nx_aggregate_stats_request) == 8);
+
+struct nx_ipfix_stats_reply {
+    ovs_be64 collector_set_id; /*range 0 to 4,294,967,295*/
+    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;
+};
+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..c8ad1ed 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..941ee5a 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 {
+    uint64_t collector_set_id; /*range 0 to 4,294,967,295*/
+    uint64_t total_flows;
+    uint64_t current_flows;
+    uint64_t pkts;
+    uint64_t ipv4_pkts;
+    uint64_t ipv6_pkts;
+    uint64_t error_pkts;
+    uint64_t ipv4_error_pkts;
+    uint64_t ipv6_error_pkts;
+    uint64_t tx_pkts;
+    uint64_t 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_decode_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 b21d76f..298bd54 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -3210,6 +3210,96 @@ 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_decode_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_decode_ipfix_stats(&is, &b);
+        if (retval) {
+            if (retval != EOF) {
+                ds_put_cstr(string, " ***parse error***");
+            }
+            return;
+        }
+
+        ds_put_cstr(string, "  id");
+        if ((is.collector_set_id) < 10) {
+            ds_put_char(string, ' ');
+        }
+        if ((is.collector_set_id) < 100) {
+            ds_put_char(string, ' ');
+        }
+
+        ds_put_format(string, " %"PRIuSIZE": ", 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 pkts=", is.tx_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)
 {
@@ -3499,6 +3589,18 @@ 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_stats(string, oh);
+        ofp_print_nxst_ipfix_bridge_reply(string, oh);
+        break;
+    case OFPTYPE_IPFIX_FLOW_STATS_REQUEST:
+        break;
+    case OFPTYPE_IPFIX_FLOW_STATS_REPLY:
+        ofp_print_stats(string, oh);
+        ofp_print_nxst_ipfix_flow_reply(string, oh);
+        break;
     }
 }
 
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 2c6fb1f..318cafe 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -7944,6 +7944,104 @@ 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 = htonll(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 = ntohll(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_decode_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);
+        if (!is) {
+            goto bad_len;
+        }
+        return ofputil_ipfix_stats_from_nx(is, reply);
+    } else {
+        OVS_NOT_REACHED();
+    }
+
+ bad_len:
+    VLOG_WARN_RL(&bad_ofmsg_rl, "NXST_IPFIX reply has %"PRIu32" leftover "
+                 "bytes at end", msg->size);
+    return OFPERR_OFPBRC_BAD_LEN;
+}
+
+
+/* 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);
+
+    for (size_t n = 0; ; n++) {
+        struct ofputil_ipfix_stats is;
+        if (ofputil_decode_ipfix_stats(&is, &b)) {
+            return n;
+        }
+    }
+}
+
 /* Frees all of the "struct ofputil_bucket"s in the 'buckets' list. */
 void
 ofputil_bucket_list_destroy(struct ovs_list *buckets)
@@ -9828,6 +9926,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 063eda8..139bc27 100644
--- a/lib/rconn.c
+++ b/lib/rconn.c
@@ -1422,6 +1422,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..a17dd61 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*/
+uint16_t
 collectors_send(const struct collectors *c, const void *payload, size_t n)
 {
+    uint16_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..254032e 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);
+uint16_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 59cd884..0477578 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);
@@ -41,6 +40,10 @@ VLOG_DEFINE_THIS_MODULE(ipfix);
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
 
+/* id in Flow_Sample_Collector_Set table ranges from 0 to 4,294,967,295
+ * COLLECTOR_SET_ID_NONE means the value of id is unused */
+#define COLLECTOR_SET_ID_UNUSED 0xffffffffffffffff
+
 /* Cf. IETF RFC 5101 Section 10.3.4. */
 #define IPFIX_DEFAULT_COLLECTOR_PORT 4739
 
@@ -48,6 +51,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
@@ -91,6 +103,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;
+
+    struct ofproto_ipfix_stats stats;
 };
 
 struct dpif_ipfix_bridge_exporter {
@@ -903,7 +917,7 @@ bool
 dpif_ipfix_get_bridge_exporter_input_sampling(const struct dpif_ipfix *di)
     OVS_EXCLUDED(mutex)
 {
-    bool ret = true;
+    bool ret = false;
     ovs_mutex_lock(&mutex);
     if (di->bridge_exporter.options) {
         ret = di->bridge_exporter.options->enable_input_sampling;
@@ -916,7 +930,7 @@ bool
 dpif_ipfix_get_bridge_exporter_output_sampling(const struct dpif_ipfix *di)
     OVS_EXCLUDED(mutex)
 {
-    bool ret = true;
+    bool ret = false;
     ovs_mutex_lock(&mutex);
     if (di->bridge_exporter.options) {
         ret = di->bridge_exporter.options->enable_output_sampling;
@@ -984,17 +998,21 @@ ipfix_init_header(uint32_t export_time_sec, uint32_t seq_number,
     hdr->obs_domain_id = htonl(obs_domain_id);
 }
 
-static void
+static uint16_t
 ipfix_send_msg(const struct collectors *collectors, struct dp_packet *msg)
 {
     struct ipfix_header *hdr;
+    uint16_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 +1168,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 uint16_t
 ipfix_send_template_msg(const struct collectors *collectors,
                         struct dp_packet *msg, size_t set_hdr_offset)
 {
     struct ipfix_set_header *set_hdr;
+    uint16_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
@@ -1174,7 +1195,9 @@ ipfix_send_template_msgs(struct dpif_ipfix_exporter *exporter,
     struct dp_packet msg;
     size_t set_hdr_offset, tmpl_hdr_offset;
     struct ipfix_template_record_header *tmpl_hdr;
-    uint16_t field_count;
+    uint16_t field_count, error_pkts;
+    uint16_t tx_packets = 0;
+    uint16_t tx_errors = 0;
     enum ipfix_proto_l2 l2;
     enum ipfix_proto_l3 l3;
     enum ipfix_proto_l4 l4;
@@ -1199,8 +1222,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) - tx_errors;
 
                         /* Reinitialize the template msg. */
                         ipfix_init_template_msg(msg_stub, export_time_sec,
@@ -1224,7 +1249,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) - tx_errors;
+
+    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,18 +1356,126 @@ ipfix_cache_aggregate_entries(struct ipfix_flow_cache_entry *from_entry,
     }
 }
 
+static void
+ipfix_stats_construct(uint64_t id,
+                      struct ofproto_ipfix_stats stats,
+                      struct ofputil_ipfix_stats *ois)
+{
+    ois->collector_set_id = id;
+    ois->total_flows = stats.total_flows;
+    ois->current_flows = stats.current_flows;
+    ois->pkts = stats.pkts;
+    ois->ipv4_pkts = stats.ipv4_pkts;
+    ois->ipv6_pkts = stats.ipv6_pkts;
+    ois->error_pkts = stats.error_pkts;
+    ois->ipv4_error_pkts = stats.ipv4_error_pkts;
+    ois->ipv6_error_pkts = stats.ipv6_error_pkts;
+    ois->tx_pkts = stats.tx_pkts;
+    ois->tx_errors = stats.tx_errors;
+}
+
+/* Get statistics */
+static void
+ipfix_get_stats__(const struct dpif_ipfix_exporter *exporter,
+                  struct ofproto_ipfix_stats *stats)
+{
+    memset(stats, 0xFF, sizeof *stats);
+
+    if (!exporter) {
+        return;
+    }
+
+    memcpy(stats, &exporter->stats, sizeof(*stats));
+}
+
+int
+dpif_ipfix_get_stats(const struct dpif_ipfix *di,
+                     const bool bridge_ipfix,
+                     struct ovs_list *replies)
+{
+    struct dpif_ipfix_flow_exporter_map_node *flow_exporter_node;
+    struct ofproto_ipfix_stats stats;
+    struct ofputil_ipfix_stats ois;
+
+    if (bridge_ipfix) {
+        if (!di->bridge_exporter.options) {
+            return OFPERR_NXST_NOT_CONFIGURED;
+        }
+
+        ipfix_get_stats__(&di->bridge_exporter.exporter, &stats);
+        ipfix_stats_construct(COLLECTOR_SET_ID_UNUSED,
+                              stats, &ois);
+        ofputil_append_ipfix_stat(replies, &ois);
+    } else {
+        if (hmap_count(&di->flow_exporter_map) == 0) {
+            return OFPERR_NXST_NOT_CONFIGURED;
+        }
+
+        HMAP_FOR_EACH (flow_exporter_node, node,
+                       &di->flow_exporter_map) {
+            ipfix_get_stats__(&flow_exporter_node->exporter.exporter, &stats);
+            ipfix_stats_construct(flow_exporter_node->exporter.options->collector_set_id,
+                                  stats, &ois);
+            ofputil_append_ipfix_stat(replies, &ois);
+        }
+    }
+
+    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;
+    bool new_flow = false;
 
     old_entry = ipfix_cache_find_entry(exporter, &entry->flow_key);
 
     if (old_entry == NULL) {
+        new_flow = true;
         hmap_insert(&exporter->cache_flow_key_map, &entry->flow_key_map_node,
                     ipfix_hash_flow_key(&entry->flow_key, 0));
 
@@ -1348,17 +1486,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);
+        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, new_flow, 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 +1512,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 +1532,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 +1550,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 +1717,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 +1800,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;
+    uint16_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 +1825,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..3648a6f 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 *, const 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 df4a632..c031bdb 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -77,6 +77,10 @@ COVERAGE_DEFINE(packet_in_overflow);
 
 struct flow_miss;
 
+/* id in Flow_Sample_Collector_Set table ranges from 0 to 4,294,967,295
+ * COLLECTOR_SET_ID_NONE means the value of id is unused */
+#define COLLECTOR_SET_ID_NONE -1
+
 struct rule_dpif {
     struct rule up;
 
@@ -1959,6 +1963,21 @@ set_ipfix(
 }
 
 static int
+get_ipfix_stats(const struct ofproto *ofproto_,
+                  const 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 +5574,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..6893903 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -1361,6 +1361,15 @@ struct ofproto_class {
         const struct ofproto_ipfix_flow_exporter_options
             *flow_exporters_options, size_t n_flow_exporters_options);
 
+    /*
+     * Get IPFIX stats
+     **/
+    int (*get_ipfix_stats)(
+        const struct ofproto *ofproto,
+        const 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..edd1aa4 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,
+                        const bool bridge_ipfx,
+                        struct ovs_list *replies)
+{
+    int error;
+
+    if (ofproto->ofproto_class->get_ipfix_stats) {
+        error = ofproto->ofproto_class->get_ipfix_stats(ofproto,
+                                                          bridge_ipfx,
+                                                          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/ofproto/ofproto.h b/ofproto/ofproto.h
index 8588872..fbea8ea 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -91,6 +91,19 @@ struct ofproto_ipfix_flow_exporter_options {
     uint32_t cache_max_flows;
 };
 
+struct ofproto_ipfix_stats {
+    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 */
+};
+
 struct ofproto_rstp_status {
     bool enabled;               /* If false, ignore other members. */
     rstp_identifier root_id;
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index a8116d9..5ce082a 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