diff options
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | include/openflow/nicira-ext.h | 17 | ||||
-rw-r--r-- | include/openvswitch/ofp-errors.h | 8 | ||||
-rw-r--r-- | include/openvswitch/ofp-msgs.h | 16 | ||||
-rw-r--r-- | include/openvswitch/ofp-util.h | 19 | ||||
-rw-r--r-- | lib/ofp-print.c | 92 | ||||
-rw-r--r-- | lib/ofp-util.c | 89 | ||||
-rw-r--r-- | lib/rconn.c | 4 | ||||
-rw-r--r-- | ofproto/collectors.c | 10 | ||||
-rw-r--r-- | ofproto/collectors.h | 2 | ||||
-rw-r--r-- | ofproto/ofproto-dpif-ipfix.c | 190 | ||||
-rw-r--r-- | ofproto/ofproto-dpif-ipfix.h | 2 | ||||
-rw-r--r-- | ofproto/ofproto-dpif.c | 16 | ||||
-rw-r--r-- | ofproto/ofproto-provider.h | 10 | ||||
-rw-r--r-- | ofproto/ofproto.c | 66 | ||||
-rw-r--r-- | tests/ofp-print.at | 79 | ||||
-rw-r--r-- | tests/ofproto-dpif.at | 146 | ||||
-rw-r--r-- | utilities/ovs-ofctl.8.in | 22 | ||||
-rw-r--r-- | utilities/ovs-ofctl.c | 19 |
19 files changed, 781 insertions, 28 deletions
@@ -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", |