From f27f21341ab42ad0e898d6c0e1bd4e98af82afda Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Tue, 7 Feb 2012 10:13:52 -0800 Subject: [PATCH] Add information about time left before timeouts to flow dumps. The "learn" action is useful for MAC learning, but until now there has been no way to find out through OpenFlow how much time remains before a MAC learning entry (a learned flow) expires. This commit adds that ability. Feature #7193. Signed-off-by: Ben Pfaff --- NEWS | 2 + include/openflow/nicira-ext.h | 32 +++++++++++-- lib/learning-switch.c | 1 + lib/ofp-print.c | 11 ++++- lib/ofp-util.c | 32 ++++++++++++- lib/ofp-util.h | 8 +++- ofproto/ofproto.c | 27 +++++++++-- tests/ofp-print.at | 16 +++---- tests/ofproto-dpif.at | 90 +++++++++++++++++++++++++++++++++++ tests/ofproto-macros.at | 2 + utilities/ovs-ofctl.8.in | 38 +++++++++------ utilities/ovs-ofctl.c | 2 +- 12 files changed, 225 insertions(+), 36 deletions(-) diff --git a/NEWS b/NEWS index 193cf1c3a..c153370e2 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,8 @@ post-v1.5.0 table, with configurable policy for evicting flows upon overflow. See the Flow_Table table in ovs-vswitch.conf.db(5) for more information. + - OpenFlow: + - NXM flow dumps now include times elapsed toward idle and hard timeouts. - ofproto-provider interface: - "struct rule" has a new member "used" that ofproto implementations should maintain by updating with ofproto_rule_update_used(). diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h index 16fa263d7..b0c8e4eb3 100644 --- a/include/openflow/nicira-ext.h +++ b/include/openflow/nicira-ext.h @@ -108,7 +108,12 @@ enum nicira_type { /* Alternative PACKET_IN message formats. */ NXT_SET_PACKET_IN_FORMAT = 16, /* Set Packet In format. */ - NXT_PACKET_IN = 17 /* Nicira Packet In. */ + NXT_PACKET_IN = 17, /* Nicira Packet In. */ + + /* Are the idle_age and hard_age members in struct nx_flow_stats supported? + * If so, the switch does not reply to this message (which consists only of + * a "struct nicira_header"). If not, the switch sends an error reply. */ + NXT_FLOW_AGE = 18, }; /* Header for Nicira vendor stats request and reply messages. */ @@ -1770,7 +1775,27 @@ struct nx_flow_stats_request { OFP_ASSERT(sizeof(struct nx_flow_stats_request) == 32); /* Body for Nicira vendor stats reply of type NXST_FLOW (analogous to - * OFPST_FLOW reply). */ + * OFPST_FLOW reply). + * + * The values of 'idle_age' and 'hard_age' are only meaningful when talking to + * a switch that implements the NXT_FLOW_AGE extension. Zero means that the + * true value is unknown, perhaps because hardware does not track the value. + * (Zero is also the value that one should ordinarily expect to see talking to + * a switch that does not implement NXT_FLOW_AGE, since those switches zero the + * padding bytes that these fields replaced.) A nonzero value X represents X-1 + * seconds. A value of 65535 represents 65534 or more seconds. + * + * 'idle_age' is the number of seconds that the flow has been idle, that is, + * the number of seconds since a packet passed through the flow. 'hard_age' is + * the number of seconds since the flow was last modified (e.g. OFPFC_MODIFY or + * OFPFC_MODIFY_STRICT). (The 'duration_*' fields are the elapsed time since + * the flow was added, regardless of subsequent modifications.) + * + * For a flow with an idle or hard timeout, 'idle_age' or 'hard_age', + * respectively, will ordinarily be smaller than the timeout, but flow + * expiration times are only approximate and so one must be prepared to + * tolerate expirations that occur somewhat early or late. + */ struct nx_flow_stats { ovs_be16 length; /* Length of this entry. */ uint8_t table_id; /* ID of table flow came from. */ @@ -1783,7 +1808,8 @@ struct nx_flow_stats { ovs_be16 idle_timeout; /* Number of seconds idle before expiration. */ ovs_be16 hard_timeout; /* Number of seconds before expiration. */ ovs_be16 match_len; /* Length of nx_match. */ - uint8_t pad2[4]; /* Align to 64 bits. */ + ovs_be16 idle_age; /* Seconds since last packet, plus one. */ + ovs_be16 hard_age; /* Seconds since last modification, plus one. */ ovs_be64 cookie; /* Opaque controller-issued identifier. */ ovs_be64 packet_count; /* Number of packets, UINT64_MAX if unknown. */ ovs_be64 byte_count; /* Number of bytes, UINT64_MAX if unknown. */ diff --git a/lib/learning-switch.c b/lib/learning-switch.c index 60cf731de..922db543e 100644 --- a/lib/learning-switch.c +++ b/lib/learning-switch.c @@ -264,6 +264,7 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn, case OFPUTIL_NXT_PACKET_IN: case OFPUTIL_NXT_FLOW_MOD: case OFPUTIL_NXT_FLOW_REMOVED: + case OFPUTIL_NXT_FLOW_AGE: case OFPUTIL_NXST_FLOW_REQUEST: case OFPUTIL_NXST_AGGREGATE_REQUEST: case OFPUTIL_NXST_FLOW_REPLY: diff --git a/lib/ofp-print.c b/lib/ofp-print.c index eb4548057..5eb9f8a5b 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -1022,7 +1022,7 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh) struct ofputil_flow_stats fs; int retval; - retval = ofputil_decode_flow_stats_reply(&fs, &b); + retval = ofputil_decode_flow_stats_reply(&fs, &b, true); if (retval) { if (retval != EOF) { ds_put_cstr(string, " ***parse error***"); @@ -1044,6 +1044,12 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh) 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] != ' ') { @@ -1455,6 +1461,9 @@ ofp_to_string__(const struct ofp_header *oh, break; + case OFPUTIL_NXT_FLOW_AGE: + break; + case OFPUTIL_NXST_AGGREGATE_REPLY: ofp_print_stats_reply(string, oh); ofp_print_nxst_aggregate_reply(string, msg); diff --git a/lib/ofp-util.c b/lib/ofp-util.c index ef8c4706c..ed98251e1 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -396,6 +396,10 @@ ofputil_decode_vendor(const struct ofp_header *oh, size_t length, { OFPUTIL_NXT_FLOW_MOD_TABLE_ID, OFP10_VERSION, NXT_FLOW_MOD_TABLE_ID, "NXT_FLOW_MOD_TABLE_ID", sizeof(struct nx_flow_mod_table_id), 0 }, + + { OFPUTIL_NXT_FLOW_AGE, OFP10_VERSION, + NXT_FLOW_AGE, "NXT_FLOW_AGE", + sizeof(struct nicira_header), 0 }, }; static const struct ofputil_msg_category nxt_category = { @@ -1307,11 +1311,18 @@ ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr, * iterates through the replies. The caller must initially leave 'msg''s layer * pointers null and not modify them between calls. * + * Most switches don't send the values needed to populate fs->idle_age and + * fs->hard_age, so those members will usually be set to 0. If the switch from + * which 'msg' originated is known to implement NXT_FLOW_AGE, then pass + * 'flow_age_extension' as true so that the contents of 'msg' determine the + * 'idle_age' and 'hard_age' members in 'fs'. + * * Returns 0 if successful, EOF if no replies were left in this 'msg', * otherwise a positive errno value. */ int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs, - struct ofpbuf *msg) + struct ofpbuf *msg, + bool flow_age_extension) { const struct ofputil_msg_type *type; int code; @@ -1362,6 +1373,8 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs, fs->duration_nsec = ntohl(ofs->duration_nsec); fs->idle_timeout = ntohs(ofs->idle_timeout); fs->hard_timeout = ntohs(ofs->hard_timeout); + fs->idle_age = -1; + fs->hard_age = -1; fs->packet_count = ntohll(get_32aligned_be64(&ofs->packet_count)); fs->byte_count = ntohll(get_32aligned_be64(&ofs->byte_count)); } else if (code == OFPUTIL_NXST_FLOW_REPLY) { @@ -1399,6 +1412,16 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs, fs->duration_nsec = ntohl(nfs->duration_nsec); fs->idle_timeout = ntohs(nfs->idle_timeout); fs->hard_timeout = ntohs(nfs->hard_timeout); + fs->idle_age = -1; + fs->hard_age = -1; + if (flow_age_extension) { + if (nfs->idle_age) { + fs->idle_age = ntohs(nfs->idle_age) - 1; + } + if (nfs->hard_age) { + fs->hard_age = ntohs(nfs->hard_age) - 1; + } + } fs->packet_count = ntohll(nfs->packet_count); fs->byte_count = ntohll(nfs->byte_count); } else { @@ -1467,8 +1490,13 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs, nfs->priority = htons(fs->rule.priority); nfs->idle_timeout = htons(fs->idle_timeout); nfs->hard_timeout = htons(fs->hard_timeout); + nfs->idle_age = htons(fs->idle_age < 0 ? 0 + : fs->idle_age < UINT16_MAX ? fs->idle_age + 1 + : UINT16_MAX); + nfs->hard_age = htons(fs->hard_age < 0 ? 0 + : fs->hard_age < UINT16_MAX ? fs->hard_age + 1 + : UINT16_MAX); nfs->match_len = htons(nx_put_match(msg, &fs->rule, 0, 0)); - memset(nfs->pad2, 0, sizeof nfs->pad2); nfs->cookie = fs->cookie; nfs->packet_count = htonll(fs->packet_count); nfs->byte_count = htonll(fs->byte_count); diff --git a/lib/ofp-util.h b/lib/ofp-util.h index 422c14a7b..f9885a3c2 100644 --- a/lib/ofp-util.h +++ b/lib/ofp-util.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks. + * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira Networks. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,6 +79,7 @@ enum ofputil_msg_code { OFPUTIL_NXT_FLOW_REMOVED, OFPUTIL_NXT_SET_PACKET_IN_FORMAT, OFPUTIL_NXT_PACKET_IN, + OFPUTIL_NXT_FLOW_AGE, /* NXST_* stat requests. */ OFPUTIL_NXST_FLOW_REQUEST, @@ -183,6 +184,8 @@ struct ofputil_flow_stats { uint32_t duration_nsec; uint16_t idle_timeout; uint16_t hard_timeout; + int idle_age; /* Seconds since last packet, -1 if unknown. */ + int hard_age; /* Seconds since last change, -1 if unknown. */ uint64_t packet_count; /* Packet count, UINT64_MAX if unknown. */ uint64_t byte_count; /* Byte count, UINT64_MAX if unknown. */ union ofp_action *actions; @@ -190,7 +193,8 @@ struct ofputil_flow_stats { }; int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *, - struct ofpbuf *msg); + struct ofpbuf *msg, + bool flow_age_extension); void ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *, struct list *replies); diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index c35d44063..473ae41be 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -2174,9 +2174,10 @@ handle_port_stats_request(struct ofconn *ofconn, } static void -calc_flow_duration__(long long int start, uint32_t *sec, uint32_t *nsec) +calc_flow_duration__(long long int start, long long int now, + uint32_t *sec, uint32_t *nsec) { - long long int msecs = time_msec() - start; + long long int msecs = now - start; *sec = msecs / 1000; *nsec = (msecs % 1000) * (1000 * 1000); } @@ -2337,6 +2338,16 @@ collect_rules_strict(struct ofproto *ofproto, uint8_t table_id, return 0; } +/* Returns 'age_ms' (a duration in milliseconds), converted to seconds and + * forced into the range of a uint16_t. */ +static int +age_secs(long long int age_ms) +{ + return (age_ms < 0 ? 0 + : age_ms >= UINT16_MAX * 1000 ? UINT16_MAX + : (unsigned int) age_ms / 1000); +} + static enum ofperr handle_flow_stats_request(struct ofconn *ofconn, const struct ofp_stats_msg *osm) @@ -2362,15 +2373,18 @@ handle_flow_stats_request(struct ofconn *ofconn, ofputil_start_stats_reply(osm, &replies); LIST_FOR_EACH (rule, ofproto_node, &rules) { + long long int now = time_msec(); struct ofputil_flow_stats fs; fs.rule = rule->cr; fs.cookie = rule->flow_cookie; fs.table_id = rule->table_id; - calc_flow_duration__(rule->created, &fs.duration_sec, + calc_flow_duration__(rule->created, now, &fs.duration_sec, &fs.duration_nsec); fs.idle_timeout = rule->idle_timeout; fs.hard_timeout = rule->hard_timeout; + fs.idle_age = age_secs(now - rule->used); + fs.hard_age = age_secs(now - rule->modified); ofproto->ofproto_class->rule_get_stats(rule, &fs.packet_count, &fs.byte_count); fs.actions = rule->actions; @@ -2930,7 +2944,8 @@ ofproto_rule_send_removed(struct rule *rule, uint8_t reason) fr.rule = rule->cr; fr.cookie = rule->flow_cookie; fr.reason = reason; - calc_flow_duration__(rule->created, &fr.duration_sec, &fr.duration_nsec); + calc_flow_duration__(rule->created, time_msec(), + &fr.duration_sec, &fr.duration_nsec); fr.idle_timeout = rule->idle_timeout; rule->ofproto->ofproto_class->rule_get_stats(rule, &fr.packet_count, &fr.byte_count); @@ -3199,6 +3214,10 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg) case OFPUTIL_NXT_FLOW_MOD: return handle_flow_mod(ofconn, oh); + case OFPUTIL_NXT_FLOW_AGE: + /* Nothing to do. */ + return 0; + /* Statistics requests. */ case OFPUTIL_OFPST_DESC_REQUEST: return handle_desc_stats_request(ofconn, msg->data); diff --git a/tests/ofp-print.at b/tests/ofp-print.at index 85562b6aa..259590030 100644 --- a/tests/ofp-print.at +++ b/tests/ofp-print.at @@ -798,7 +798,7 @@ AT_CHECK([ovs-ofctl ofp-print "\ a8 00 02 00 00 0c 01 06 00 00 12 02 09 e7 00 00 \ 14 02 00 00 00 00 00 00 00 00 00 08 00 01 00 00 \ 00 88 00 00 00 00 00 03 32 11 62 00 ff ff 00 05 \ -00 00 00 4c 00 00 00 00 00 00 00 00 00 00 00 00 \ +00 00 00 4c 00 03 00 00 00 00 00 00 00 00 00 00 \ 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 3c \ 00 00 00 02 00 03 00 00 02 06 50 54 00 00 00 06 \ 00 00 04 06 50 54 00 00 00 05 00 00 06 02 08 00 \ @@ -806,7 +806,7 @@ a8 00 02 00 00 0c 01 06 00 00 12 02 09 e7 00 00 \ a8 00 01 00 00 10 04 c0 a8 00 02 00 00 0c 01 06 \ 00 00 12 02 09 e4 00 00 14 02 00 00 00 00 00 00 \ 00 00 00 08 00 01 00 00 00 88 00 00 00 00 00 02 \ -33 f9 aa 00 ff ff 00 05 00 00 00 4c 00 00 00 00 \ +33 f9 aa 00 ff ff 00 05 00 00 00 4c 00 05 00 00 \ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 \ 00 00 00 00 00 00 00 3c 00 00 00 02 00 01 00 00 \ 02 06 50 54 00 00 00 05 00 00 04 06 50 54 00 00 \ @@ -815,7 +815,7 @@ a8 00 01 00 00 10 04 c0 a8 00 02 00 00 0c 01 06 \ a8 00 01 00 00 0c 01 06 00 00 12 02 00 00 00 00 \ 14 02 09 e5 00 00 00 00 00 00 00 08 00 03 00 00 \ 00 88 00 00 00 00 00 04 2d 0f a5 00 ff ff 00 05 \ -00 00 00 4c 00 00 00 00 00 00 00 00 00 00 00 00 \ +00 00 00 4c 00 01 00 00 00 00 00 00 00 00 00 00 \ 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 3c \ 00 00 00 02 00 03 00 00 02 06 50 54 00 00 00 06 \ 00 00 04 06 50 54 00 00 00 05 00 00 06 02 08 00 \ @@ -823,7 +823,7 @@ a8 00 01 00 00 0c 01 06 00 00 12 02 00 00 00 00 \ a8 00 01 00 00 10 04 c0 a8 00 02 00 00 0c 01 06 \ 00 00 12 02 09 e3 00 00 14 02 00 00 00 00 00 00 \ 00 00 00 08 00 01 00 00 00 88 00 00 00 00 00 02 \ -34 73 bc 00 ff ff 00 05 00 00 00 4c 00 00 00 00 \ +34 73 bc 00 ff ff 00 05 00 0a 00 4c 00 03 00 03 \ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 \ 00 00 00 00 00 00 00 3c 00 00 00 02 00 03 00 00 \ 02 06 50 54 00 00 00 06 00 00 04 06 50 54 00 00 \ @@ -920,10 +920,10 @@ ff ff 00 18 00 00 23 20 00 07 00 1f 00 01 00 04 \ "], [0], [[NXST_FLOW reply (xid=0x4): cookie=0x0, duration=1.048s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2535,tp_dst=0 actions=output:1 - cookie=0x0, duration=3.84s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2532,tp_dst=0 actions=output:1 - cookie=0x0, duration=2.872s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2533 actions=output:3 - cookie=0x0, duration=4.756s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2531,tp_dst=0 actions=output:1 - cookie=0x0, duration=2.88s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2533,tp_dst=0 actions=output:1 + cookie=0x0, duration=3.84s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,idle_age=2,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2532,tp_dst=0 actions=output:1 + cookie=0x0, duration=2.872s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,idle_age=4,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2533 actions=output:3 + cookie=0x0, duration=4.756s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,idle_age=0,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2531,tp_dst=0 actions=output:1 + cookie=0x0, duration=2.88s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,hard_timeout=10,idle_age=2,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2533,tp_dst=0 actions=output:1 cookie=0x0, duration=5.672s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2530,tp_dst=0 actions=output:1 cookie=0x0, duration=1.04s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2535 actions=output:3 cookie=0x0, duration=1.952s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2534 actions=output:3 diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index 0f5b1cea7..f5c1358e7 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -1035,3 +1035,93 @@ echo $n_recs AT_CHECK([test $n_recs -ge 13]) AT_CLEANUP + +AT_SETUP([idle_age and hard_age increase over time]) +OVS_VSWITCHD_START + +# get_ages DURATION HARD IDLE +# +# Fetch the flow duration, hard age, and idle age into the variables +# whose names are given as arguments. Rounds DURATION down to the +# nearest integer. If hard_age doesn't appear in the output, sets +# HARD to "none". If idle_age doesn't appear in the output, sets IDLE +# to 0. +get_ages () { + AT_CHECK([ovs-ofctl dump-flows br0], [0], [stdout]) + + duration=`sed -n 's/.*duration=\([[0-9]]*\)\(\.[[0-9]]*\)\{0,1\}s.*/\1/p' stdout` + AT_CHECK([[expr X"$duration" : 'X[0-9][0-9]*$']], [0], [ignore]) + AS_VAR_COPY([$1], [duration]) + + hard=`sed -n 's/.*hard_age=\([[0-9]]*\),.*/\1/p' stdout` + if test X"$hard" = X; then + hard=none + else + AT_CHECK([[expr X"$hard" : 'X[0-9][0-9]*$']], [0], [ignore]) + fi + AS_VAR_COPY([$2], [hard]) + + idle=`sed -n 's/.*idle_age=\([[0-9]]*\),.*/\1/p' stdout` + if test X"$idle" = X; then + idle=0 + else + AT_CHECK([[expr X"$idle" : 'X[0-9][0-9]*$']], [0], [ignore]) + fi + AS_VAR_COPY([$3], [idle]) +} + +# Add a flow and get its initial hard and idle age. +AT_CHECK([ovs-ofctl add-flow br0 hard_timeout=199,idle_timeout=188,actions=drop]) +get_ages duration1 hard1 idle1 + +# Warp time forward by 10 seconds, then modify the flow's actions. +ovs-appctl time/warp 10000 +get_ages duration2 hard2 idle2 +AT_CHECK([ovs-ofctl mod-flows br0 actions=flood]) + +# Warp time forward by 10 seconds. +ovs-appctl time/warp 10000 +get_ages duration3 hard3 idle3 + +# Warp time forward 10 more seconds, then pass some packets through the flow, +# then warp forward a few more times because idle times are only updated +# occasionally. +ovs-appctl time/warp 10000 +ovs-appctl netdev-dummy/receive br0 'in_port(0),eth(src=50:54:00:00:00:07,dst=50:54:00:00:00:05),eth_type(0x0800),ipv4(src=192.168.0.2,dst=192.168.0.1,proto=6,tos=0,ttl=64,frag=no),tcp(src=80,dst=1234)' +ovs-appctl time/warp 1000 +ovs-appctl time/warp 1000 +ovs-appctl time/warp 1000 +get_ages duration4 hard4 idle4 + +printf "duration: %4s => %4s => %4s => %4s\n" $duration1 $duration2 $duration3 $duration4 +printf "hard_age: %4s => %4s => %4s => %4s\n" $hard1 $hard2 $hard3 $hard4 +printf "idle_age: %4s => %4s => %4s => %4s\n" $idle1 $idle2 $idle3 $idle4 + +# Duration should increase steadily over time. +AT_CHECK([test $duration1 -lt $duration2]) +AT_CHECK([test $duration2 -lt $duration3]) +AT_CHECK([test $duration3 -lt $duration4]) + +# Hard age should be "none" initially because it's the same as flow_duration, +# then it should increase. +AT_CHECK([test $hard1 = none]) +AT_CHECK([test $hard2 = none]) +AT_CHECK([test $hard3 != none]) +AT_CHECK([test $hard4 != none]) +AT_CHECK([test $hard3 -lt $hard4]) + +# Idle age should increase from 1 to 2 to 3, then decrease. +AT_CHECK([test $idle1 -lt $idle2]) +AT_CHECK([test $idle2 -lt $idle3]) +AT_CHECK([test $idle3 -gt $idle4]) + +# Check some invariant relationships. +AT_CHECK([test $duration1 = $idle1]) +AT_CHECK([test $duration2 = $idle2]) +AT_CHECK([test $duration3 = $idle3]) +AT_CHECK([test $idle3 -gt $hard3]) +AT_CHECK([test $idle4 -lt $hard4]) +AT_CHECK([test $hard4 -lt $duration4]) + +OVS_VSWITCHD_STOP +AT_CLEANUP diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at index 3656ecf26..1184d2604 100644 --- a/tests/ofproto-macros.at +++ b/tests/ofproto-macros.at @@ -10,6 +10,8 @@ s/ cookie=0x0,// s/ table=0,// s/ n_packets=0,// s/ n_bytes=0,// +s/idle_age=[0-9]*,// +s/hard_age=[0-9]*,// ' }] m4_divert_pop([PREPARE_TESTS]) diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 1c1b70987..d72d8abf7 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -1071,19 +1071,13 @@ If set, a matching flow must include an output action to \fIport\fR. . The \fBdump\-tables\fR and \fBdump\-aggregate\fR commands print information about the entries in a datapath's tables. Each line of output is a -unique flow entry, which begins with some common information: +flow entry as described in \fBFlow Syntax\fR, above, plus some +additional fields: . -.IP \fBduration\fR -The number of seconds the entry has been in the table. -. -.IP \fBtable\fR -The table that contains the flow. When a packet arrives, the switch -begins searching for an entry at the lowest numbered table. Tables are -numbered as shown by the \fBdump\-tables\fR command. -. -.IP \fBpriority\fR -The priority of the entry in relation to other entries within the same -table. A higher value will match before a lower one. +.IP \fBduration=\fIsecs\fR +The time, in seconds, that the entry has been in the table. +\fIsecs\fR includes as much precision as the switch provides, possibly +to nanosecond resolution. . .IP \fBn_packets\fR The number of packets that have matched the entry. @@ -1092,9 +1086,23 @@ The number of packets that have matched the entry. The total number of bytes from packets that have matched the entry. . .PP -The rest of the line consists of a description of the flow entry as -described in \fBFlow Syntax\fR, above. -. +The following additional fields are included only if the switch is +Open vSwitch 1.6 or later and the NXM flow format is used to dump the +flow (see the description of the \fB\-\-flow-format\fR option below). +The values of these additional fields are approximations only and in +particular \fBidle_age\fR will sometimes become nonzero even for busy +flows. +. +.IP \fBhard_age=\fIsecs\fR +The integer number of seconds since the flow was added or modified. +\fBhard_age\fR is displayed only if it differs from the integer part +of \fBduration\fR. (This is separate from \fBduration\fR because +\fBmod\-flows\fR restarts the \fBhard_timeout\fR timer without zeroing +\fBduration\fR.) +. +.IP \fBidle_age=\fIsecs\fR +The integer number of seconds that have passed without any packets +passing through the flow. . .SH OPTIONS .TP diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c index 7df0a90a3..26bc1c3b4 100644 --- a/utilities/ovs-ofctl.c +++ b/utilities/ovs-ofctl.c @@ -1354,7 +1354,7 @@ read_flows_from_switch(struct vconn *vconn, enum nx_flow_format flow_format, struct ofputil_flow_stats fs; int retval; - retval = ofputil_decode_flow_stats_reply(&fs, reply); + retval = ofputil_decode_flow_stats_reply(&fs, reply, false); if (retval) { if (retval != EOF) { ovs_fatal(0, "parse error in reply"); -- 2.20.1