From bdcc59259553133f25a1c9759fa4bfa302adc365 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 12 Jul 2012 13:32:47 -0700 Subject: [PATCH] ovs-ofctl: Add --sort and --rsort options for "dump-flows" command. Feature #8754. Signed-off-by: Arun Sharma [blp@nicira.com rewrote most of the code] Signed-off-by: Ben Pfaff --- NEWS | 1 + lib/ofp-print.c | 83 +++++++++++-------- lib/ofp-print.h | 9 +- tests/ovs-ofctl.at | 89 ++++++++++++++++++++ utilities/ovs-ofctl.8.in | 37 ++++++++- utilities/ovs-ofctl.c | 172 +++++++++++++++++++++++++++++++++++++-- 6 files changed, 346 insertions(+), 45 deletions(-) diff --git a/NEWS b/NEWS index 84f1ec11..f5190c0f 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ post-v1.7.0 - A new test utility that can create L3 tunnel between two Open vSwitches and detect connectivity issues. - ovs-ofctl: + - New --sort and --rsort options for "dump-flows" command. - "mod-port" command can now control all OpenFlow config flags. - OpenFlow: - Allow general bitwise masking for IPv4 and IPv6 addresses in diff --git a/lib/ofp-print.c b/lib/ofp-print.c index c7c8a190..0050c0a5 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -972,6 +972,37 @@ ofp_print_flow_stats_request(struct ds *string, cls_rule_format(&fsr.match, string); } +void +ofp_print_flow_stats(struct ds *string, struct ofputil_flow_stats *fs) +{ + ds_put_format(string, " cookie=0x%"PRIx64", duration=", + ntohll(fs->cookie)); + + ofp_print_duration(string, fs->duration_sec, fs->duration_nsec); + ds_put_format(string, ", table=%"PRIu8", ", fs->table_id); + ds_put_format(string, "n_packets=%"PRIu64", ", fs->packet_count); + ds_put_format(string, "n_bytes=%"PRIu64", ", fs->byte_count); + if (fs->idle_timeout != OFP_FLOW_PERMANENT) { + ds_put_format(string, "idle_timeout=%"PRIu16", ", fs->idle_timeout); + } + if (fs->hard_timeout != OFP_FLOW_PERMANENT) { + ds_put_format(string, "hard_timeout=%"PRIu16", ", fs->hard_timeout); + } + if (fs->idle_age >= 0) { + ds_put_format(string, "idle_age=%d, ", fs->idle_age); + } + if (fs->hard_age >= 0 && fs->hard_age != fs->duration_sec) { + ds_put_format(string, "hard_age=%d, ", fs->hard_age); + } + + cls_rule_format(&fs->rule, string); + if (string->string[string->length - 1] != ' ') { + ds_put_char(string, ' '); + } + + ofpacts_format(fs->ofpacts, fs->ofpacts_len, string); +} + static void ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh) { @@ -991,35 +1022,9 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh) } break; } - ds_put_char(string, '\n'); - - ds_put_format(string, " cookie=0x%"PRIx64", duration=", - ntohll(fs.cookie)); - ofp_print_duration(string, fs.duration_sec, fs.duration_nsec); - ds_put_format(string, ", table=%"PRIu8", ", fs.table_id); - ds_put_format(string, "n_packets=%"PRIu64", ", fs.packet_count); - ds_put_format(string, "n_bytes=%"PRIu64", ", fs.byte_count); - if (fs.idle_timeout != OFP_FLOW_PERMANENT) { - ds_put_format(string, "idle_timeout=%"PRIu16", ", fs.idle_timeout); - } - if (fs.hard_timeout != OFP_FLOW_PERMANENT) { - ds_put_format(string, "hard_timeout=%"PRIu16", ", fs.hard_timeout); - } - if (fs.idle_age >= 0) { - ds_put_format(string, "idle_age=%d, ", fs.idle_age); - } - if (fs.hard_age >= 0 && fs.hard_age != fs.duration_sec) { - ds_put_format(string, "hard_age=%d, ", fs.hard_age); - } - - cls_rule_format(&fs.rule, string); - if (string->string[string->length - 1] != ' ') { - ds_put_char(string, ' '); - } - ofpacts_format(fs.ofpacts, fs.ofpacts_len, string); - } - ofpbuf_uninit(&ofpacts); + ofp_print_flow_stats(string, &fs); + } } static void @@ -1354,15 +1359,10 @@ ofp_print_nxt_set_controller_id(struct ds *string, ds_put_format(string, " id=%"PRIu16, ntohs(nci->controller_id)); } -static void -ofp_to_string__(const struct ofp_header *oh, - const struct ofputil_msg_type *type, struct ds *string, - int verbosity) +void +ofp_print_version(const struct ofp_header *oh, + struct ds *string) { - enum ofputil_msg_code code; - const void *msg = oh; - - ds_put_cstr(string, ofputil_msg_type_name(type)); switch (oh->version) { case OFP10_VERSION: break; @@ -1377,7 +1377,18 @@ ofp_to_string__(const struct ofp_header *oh, break; } ds_put_format(string, " (xid=0x%"PRIx32"):", ntohl(oh->xid)); +} +static void +ofp_to_string__(const struct ofp_header *oh, + const struct ofputil_msg_type *type, struct ds *string, + int verbosity) +{ + enum ofputil_msg_code code; + const void *msg = oh; + + ds_put_cstr(string, ofputil_msg_type_name(type)); + ofp_print_version(oh, string); code = ofputil_msg_type_code(type); switch (code) { case OFPUTIL_MSG_INVALID: diff --git a/lib/ofp-print.h b/lib/ofp-print.h index 49bcfcde..825e139d 100644 --- a/lib/ofp-print.h +++ b/lib/ofp-print.h @@ -22,9 +22,11 @@ #include #include -struct ofp_flow_mod; -struct ofp10_match; struct ds; +struct ofp10_match; +struct ofp_flow_mod; +struct ofp_header; +struct ofputil_flow_stats; #ifdef __cplusplus extern "C" { @@ -39,6 +41,9 @@ char *ofp_to_string(const void *, size_t, int verbosity); char *ofp10_match_to_string(const struct ofp10_match *, int verbosity); char *ofp_packet_to_string(const void *data, size_t len); +void ofp_print_flow_stats(struct ds *, struct ofputil_flow_stats *); +void ofp_print_version(const struct ofp_header *, struct ds *); + #ifdef __cplusplus } #endif diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at index 97ea703f..d456734b 100644 --- a/tests/ovs-ofctl.at +++ b/tests/ovs-ofctl.at @@ -1357,3 +1357,92 @@ ofp_util|INFO|post: @&t@ OVS_VSWITCHD_STOP AT_CLEANUP +dnl Check that --sort and --rsort works with dump-flows +dnl Default field is 'priority'. Flow entries are displayed based +dnl on field to sort. +AT_SETUP([ovs-ofctl dump-flows with sorting]) +OVS_VSWITCHD_START +AT_KEYWORDS([sort]) +AT_DATA([allflows.txt], [[ +priority=4,in_port=23213 actions=output:42 +priority=5,in_port=1029 actions=output:43 +priority=7,in_port=1029 actions=output:43 +priority=3,in_port=1028 actions=output:44 +priority=1,in_port=1026 actions=output:45 +priority=6,in_port=1027 actions=output:64 +priority=2,in_port=1025 actions=output:47 +priority=8,tcp,tp_src=5 actions=drop +priority=9,tcp,tp_src=6 actions=drop +]]) + +AT_CHECK([ovs-ofctl add-flows br0 allflows.txt +], [0], [ignore]) +AT_CHECK([ovs-ofctl --sort dump-flows br0 | ofctl_strip], [0], [dnl + priority=1,in_port=1026 actions=output:45 + priority=2,in_port=1025 actions=output:47 + priority=3,in_port=1028 actions=output:44 + priority=4,in_port=23213 actions=output:42 + priority=5,in_port=1029 actions=output:43 + priority=6,in_port=1027 actions=output:64 + priority=7,in_port=1029 actions=output:43 + priority=8,tcp,tp_src=5 actions=drop + priority=9,tcp,tp_src=6 actions=drop +]) +AT_CHECK([ovs-ofctl --rsort dump-flows br0 | ofctl_strip], [0], [dnl + priority=9,tcp,tp_src=6 actions=drop + priority=8,tcp,tp_src=5 actions=drop + priority=7,in_port=1029 actions=output:43 + priority=6,in_port=1027 actions=output:64 + priority=5,in_port=1029 actions=output:43 + priority=4,in_port=23213 actions=output:42 + priority=3,in_port=1028 actions=output:44 + priority=2,in_port=1025 actions=output:47 + priority=1,in_port=1026 actions=output:45 +]) +AT_CHECK([ovs-ofctl --sort=in_port dump-flows br0 | ofctl_strip], [0], [dnl + priority=2,in_port=1025 actions=output:47 + priority=1,in_port=1026 actions=output:45 + priority=6,in_port=1027 actions=output:64 + priority=3,in_port=1028 actions=output:44 + priority=7,in_port=1029 actions=output:43 + priority=5,in_port=1029 actions=output:43 + priority=4,in_port=23213 actions=output:42 + priority=9,tcp,tp_src=6 actions=drop + priority=8,tcp,tp_src=5 actions=drop +]) +AT_CHECK([ovs-ofctl --rsort=in_port dump-flows br0 | ofctl_strip], [0], [dnl + priority=4,in_port=23213 actions=output:42 + priority=7,in_port=1029 actions=output:43 + priority=5,in_port=1029 actions=output:43 + priority=3,in_port=1028 actions=output:44 + priority=6,in_port=1027 actions=output:64 + priority=1,in_port=1026 actions=output:45 + priority=2,in_port=1025 actions=output:47 + priority=9,tcp,tp_src=6 actions=drop + priority=8,tcp,tp_src=5 actions=drop +]) +AT_CHECK([ovs-ofctl --sort=tcp_src dump-flows br0 | ofctl_strip], [0], [dnl + priority=8,tcp,tp_src=5 actions=drop + priority=9,tcp,tp_src=6 actions=drop + priority=7,in_port=1029 actions=output:43 + priority=6,in_port=1027 actions=output:64 + priority=5,in_port=1029 actions=output:43 + priority=4,in_port=23213 actions=output:42 + priority=3,in_port=1028 actions=output:44 + priority=2,in_port=1025 actions=output:47 + priority=1,in_port=1026 actions=output:45 +]) +AT_CHECK( + [ovs-ofctl --sort=in_port --sort=tcp_src dump-flows br0 | ofctl_strip], [0], + [ priority=2,in_port=1025 actions=output:47 + priority=1,in_port=1026 actions=output:45 + priority=6,in_port=1027 actions=output:64 + priority=3,in_port=1028 actions=output:44 + priority=7,in_port=1029 actions=output:43 + priority=5,in_port=1029 actions=output:43 + priority=4,in_port=23213 actions=output:42 + priority=8,tcp,tp_src=5 actions=drop + priority=9,tcp,tp_src=6 actions=drop +]) +OVS_VSWITCHD_STOP +AT_CLEANUP diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 3ca217b3..ebfde0fa 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -161,12 +161,18 @@ whether a packet is a fragment and on its fragment offset. Prints to the console all flow entries in \fIswitch\fR's tables that match \fIflows\fR. If \fIflows\fR is omitted, all flows in the switch are retrieved. See \fBFlow Syntax\fR, below, for the -syntax of \fIflows\fR. The output format is described in +syntax of \fIflows\fR. The output format is described in \fBTable Entry Output\fR. . +.IP +By default, \fBovs\-ofctl\fR prints flow entries in the same order +that the switch sends them, which is unlikely to be intuitive or +consistent. See the description of \fB\-\-sort\fR and \fB\-\-rsort\fR, +under \fBOPTIONS\fR below, to influence the display order. +. .TP \fBdump\-aggregate \fIswitch \fR[\fIflows\fR] -Prints to the console aggregate statistics for flows in +Prints to the console aggregate statistics for flows in \fIswitch\fR's tables that match \fIflows\fR. If \fIflows\fR is omitted, the statistics are aggregated across all flows in the switch's flow tables. See \fBFlow Syntax\fR, below, for the syntax of \fIflows\fR. @@ -1317,6 +1323,33 @@ Increases the verbosity of OpenFlow messages printed and logged by \fBovs\-ofctl\fR commands. Specify this option more than once to increase verbosity further. . +.IP \fB\-\-sort\fR[\fB=\fIfield\fR] +.IQ \fB\-\-rsort\fR[\fB=\fIfield\fR] +Display output sorted by flow \fIfield\fR in ascending +(\fB\-\-sort\fR) or descending (\fB\-\-rsort\fR) order, where +\fIfield\fR is any of the fields that are allowed for matching or +\fBpriority\fR to sort by priority. When \fIfield\fR is omitted, the +output is sorted by priority. Specify these options multiple times to +sort by multiple fields. +.IP +Any given flow will not necessarily specify a value for a given +field. This requires special treatement: +.RS +.IP \(bu +A flow that does not specify any part of a field that is used for sorting is +sorted after all the flows that do specify the field. For example, +\fB\-\-sort=tcp_src\fR will sort all the flows that specify a TCP +source port in ascending order, followed by the flows that do not +specify a TCP source port at all. +.IP \(bu +A flow that only specifies some bits in a field is sorted as if the +wildcarded bits were zero. For example, \fB\-\-sort=nw_src\fR would +sort a flow that specifies \fBnw_src=192.168.0.0/24\fR the same as +\fBnw_src=192.168.0.0\fR. +.RE +.IP +These options currently affect only \fBdump\-flows\fR output. +. .ds DD \ \fBovs\-ofctl\fR detaches only when executing the \fBmonitor\fR or \ \fBsnoop\fR commands. diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c index 3a85f57a..1c75f464 100644 --- a/utilities/ovs-ofctl.c +++ b/utilities/ovs-ofctl.c @@ -55,6 +55,8 @@ #include "util.h" #include "vconn.h" #include "vlog.h" +#include "meta-flow.h" +#include "sort.h" VLOG_DEFINE_THIS_MODULE(ofctl); @@ -83,11 +85,24 @@ static int verbosity; * "snoop" command? */ static bool timestamp; +/* --sort, --rsort: Sort order. */ +enum sort_order { SORT_ASC, SORT_DESC }; +struct sort_criterion { + const struct mf_field *field; /* NULL means to sort by priority. */ + enum sort_order order; +}; +static struct sort_criterion *criteria; +static size_t n_criteria, allocated_criteria; + static const struct command all_commands[]; static void usage(void) NO_RETURN; static void parse_options(int argc, char *argv[]); +static bool recv_flow_stats_reply(struct vconn *, ovs_be32 send_xid, + struct ofpbuf **replyp, + struct ofputil_flow_stats *, + struct ofpbuf *ofpacts); int main(int argc, char *argv[]) { @@ -98,6 +113,27 @@ main(int argc, char *argv[]) return 0; } +static void +add_sort_criterion(enum sort_order order, const char *field) +{ + struct sort_criterion *sc; + + if (n_criteria >= allocated_criteria) { + criteria = x2nrealloc(criteria, &allocated_criteria, sizeof *criteria); + } + + sc = &criteria[n_criteria++]; + if (!field || !strcasecmp(field, "priority")) { + sc->field = NULL; + } else { + sc->field = mf_from_name(field); + if (!sc->field) { + ovs_fatal(0, "%s: unknown field name", field); + } + } + sc->order = order; +} + static void parse_options(int argc, char *argv[]) { @@ -105,6 +141,8 @@ parse_options(int argc, char *argv[]) OPT_STRICT = UCHAR_MAX + 1, OPT_READD, OPT_TIMESTAMP, + OPT_SORT, + OPT_RSORT, DAEMON_OPTION_ENUMS, VLOG_OPTION_ENUMS }; @@ -116,6 +154,8 @@ parse_options(int argc, char *argv[]) {"packet-in-format", required_argument, NULL, 'P'}, {"more", no_argument, NULL, 'm'}, {"timestamp", no_argument, NULL, OPT_TIMESTAMP}, + {"sort", optional_argument, NULL, OPT_SORT}, + {"rsort", optional_argument, NULL, OPT_RSORT}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'V'}, DAEMON_LONG_OPTIONS, @@ -183,6 +223,14 @@ parse_options(int argc, char *argv[]) timestamp = true; break; + case OPT_SORT: + add_sort_criterion(SORT_ASC, optarg); + break; + + case OPT_RSORT: + add_sort_criterion(SORT_DESC, optarg); + break; + DAEMON_OPTION_HANDLERS VLOG_OPTION_HANDLERS STREAM_SSL_OPTION_HANDLERS @@ -194,6 +242,12 @@ parse_options(int argc, char *argv[]) abort(); } } + + if (n_criteria) { + /* Always do a final sort pass based on priority. */ + add_sort_criterion(SORT_DESC, "priority"); + } + free(short_options); } @@ -244,6 +298,8 @@ usage(void) " -m, --more be more verbose printing OpenFlow\n" " --timestamp (monitor, snoop) print timestamps\n" " -t, --timeout=SECS give up after SECS seconds\n" + " --sort[=field] sort in ascending order\n" + " --rsort[=field] sort in descending order\n" " -h, --help display this help message\n" " -V, --version display version information\n"); exit(EXIT_SUCCESS); @@ -770,12 +826,12 @@ set_protocol_for_flow_dump(struct vconn *vconn, } } -static void -ofctl_dump_flows__(int argc, char *argv[], bool aggregate) +static struct vconn * +prepare_dump_flows(int argc, char *argv[], bool aggregate, + struct ofpbuf **requestp) { enum ofputil_protocol usable_protocols, protocol; struct ofputil_flow_stats_request fsr; - struct ofpbuf *request; struct vconn *vconn; parse_ofp_flow_stats_request_str(&fsr, aggregate, argc > 2 ? argv[2] : ""); @@ -783,15 +839,121 @@ ofctl_dump_flows__(int argc, char *argv[], bool aggregate) protocol = open_vconn(argv[1], &vconn); protocol = set_protocol_for_flow_dump(vconn, protocol, usable_protocols); - request = ofputil_encode_flow_stats_request(&fsr, protocol); + *requestp = ofputil_encode_flow_stats_request(&fsr, protocol); + return vconn; +} + +static void +ofctl_dump_flows__(int argc, char *argv[], bool aggregate) +{ + struct ofpbuf *request; + struct vconn *vconn; + + vconn = prepare_dump_flows(argc, argv, aggregate, &request); dump_stats_transaction__(vconn, request); vconn_close(vconn); } +static int +compare_flows(const void *afs_, const void *bfs_) +{ + const struct ofputil_flow_stats *afs = afs_; + const struct ofputil_flow_stats *bfs = bfs_; + const struct cls_rule *a = &afs->rule; + const struct cls_rule *b = &bfs->rule; + const struct sort_criterion *sc; + + for (sc = criteria; sc < &criteria[n_criteria]; sc++) { + const struct mf_field *f = sc->field; + int ret; + + if (!f) { + ret = a->priority < b->priority ? -1 : a->priority > b->priority; + } else { + bool ina, inb; + + ina = mf_are_prereqs_ok(f, &a->flow) && !mf_is_all_wild(f, &a->wc); + inb = mf_are_prereqs_ok(f, &b->flow) && !mf_is_all_wild(f, &b->wc); + if (ina != inb) { + /* Skip the test for sc->order, so that missing fields always + * sort to the end whether we're sorting in ascending or + * descending order. */ + return ina ? -1 : 1; + } else { + union mf_value aval, bval; + + mf_get_value(f, &a->flow, &aval); + mf_get_value(f, &b->flow, &bval); + ret = memcmp(&aval, &bval, f->n_bytes); + } + } + + if (ret) { + return sc->order == SORT_ASC ? ret : -ret; + } + } + + return 0; +} + static void ofctl_dump_flows(int argc, char *argv[]) { - return ofctl_dump_flows__(argc, argv, false); + if (!n_criteria) { + return ofctl_dump_flows__(argc, argv, false); + } else { + struct ofputil_flow_stats *fses; + size_t n_fses, allocated_fses; + struct ofpbuf *request; + struct ofpbuf ofpacts; + struct ofpbuf *reply; + struct vconn *vconn; + ovs_be32 send_xid; + struct ds s; + size_t i; + + vconn = prepare_dump_flows(argc, argv, false, &request); + send_xid = ((struct ofp_header *) request->data)->xid; + send_openflow_buffer(vconn, request); + + fses = NULL; + n_fses = allocated_fses = 0; + reply = NULL; + ofpbuf_init(&ofpacts, 0); + for (;;) { + struct ofputil_flow_stats *fs; + + if (n_fses >= allocated_fses) { + fses = x2nrealloc(fses, &allocated_fses, sizeof *fses); + } + + fs = &fses[n_fses]; + if (!recv_flow_stats_reply(vconn, send_xid, &reply, fs, + &ofpacts)) { + break; + } + fs->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len); + n_fses++; + } + ofpbuf_uninit(&ofpacts); + + qsort(fses, n_fses, sizeof *fses, compare_flows); + + ds_init(&s); + for (i = 0; i < n_fses; i++) { + ds_clear(&s); + ofp_print_flow_stats(&s, &fses[i]); + puts(ds_cstr(&s)); + } + ds_destroy(&s); + + for (i = 0; i < n_fses; i++) { + free(fses[i].ofpacts); + } + free(fses); + + vconn_close(vconn); + } } static void -- 2.30.2