From: Ben Pfaff Date: Wed, 15 Feb 2012 18:37:03 +0000 (-0800) Subject: Implement new "fin_timeout" action and "learn" feature. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0e553d9c1063be047824c6f1afce9ffc6db6c671;p=openvswitch Implement new "fin_timeout" action and "learn" feature. The "learn" action can create matching return flows. If those have a long timeout then it's a good idea to have a way to notice when in fact the flows have terminated. This new action and matching "learn" feature provides that way. Feature #8603. Signed-off-by: Ben Pfaff --- diff --git a/NEWS b/NEWS index a9cb1af9..b1761309 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,8 @@ post-v1.5.0 - Added an OpenFlow extension NXT_SET_ASYNC_CONFIG that allows controllers more precise control over which OpenFlow messages they receive asynchronously. + - New "fin_timeout" action. + - Added "fin_timeout" support to "learn" action. - The default MAC learning timeout has been increased from 60 seconds to 300 seconds. The MAC learning timeout is now configurable. - Logging to console and file will have UTC timestamp as a default for all diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h index ff957969..042d304a 100644 --- a/include/openflow/nicira-ext.h +++ b/include/openflow/nicira-ext.h @@ -335,6 +335,7 @@ enum nx_action_subtype { NXAST_LEARN, /* struct nx_action_learn */ NXAST_EXIT, /* struct nx_action_header */ NXAST_DEC_TTL, /* struct nx_action_header */ + NXAST_FIN_TIMEOUT, /* struct nx_action_fin_timeout */ }; /* Header for Nicira-defined actions. */ @@ -802,6 +803,13 @@ enum nx_mp_algorithm { * prepared to handle this by flooding (which can be implemented as a * low-priority flow). * + * If a learned flow matches a single TCP stream with a relatively long + * timeout, one may make the best of resource constraints by setting + * 'fin_idle_timeout' or 'fin_hard_timeout' (both measured in seconds), or + * both, to shorter timeouts. When either of these is specified as a nonzero + * value, OVS adds a NXAST_FIN_TIMEOUT action, with the specified timeouts, to + * the learned flow. + * * Examples * -------- * @@ -905,7 +913,9 @@ struct nx_action_learn { ovs_be64 cookie; /* Cookie for new flow. */ ovs_be16 flags; /* Either 0 or OFPFF_SEND_FLOW_REM. */ uint8_t table_id; /* Table to insert flow entry. */ - uint8_t pad[5]; /* Must be zero. */ + uint8_t pad; /* Must be zero. */ + ovs_be16 fin_idle_timeout; /* Idle timeout after FIN, if nonzero. */ + ovs_be16 fin_hard_timeout; /* Hard timeout after FIN, if nonzero. */ /* Followed by a sequence of flow_mod_spec elements, as described above, * until the end of the action is reached. */ }; @@ -922,6 +932,40 @@ OFP_ASSERT(sizeof(struct nx_action_learn) == 32); #define NX_LEARN_DST_OUTPUT (2 << 11) /* Add OFPAT_OUTPUT action. */ #define NX_LEARN_DST_RESERVED (3 << 11) /* Not yet defined. */ #define NX_LEARN_DST_MASK (3 << 11) + +/* Action structure for NXAST_FIN_TIMEOUT. + * + * This action changes the idle timeout or hard timeout, or both, of this + * OpenFlow rule when the rule matches a TCP packet with the FIN or RST flag. + * When such a packet is observed, the action reduces the rule's idle timeout + * to 'fin_idle_timeout' and its hard timeout to 'fin_hard_timeout'. This + * action has no effect on an existing timeout that is already shorter than the + * one that the action specifies. A 'fin_idle_timeout' or 'fin_hard_timeout' + * of zero has no effect on the respective timeout. + * + * 'fin_idle_timeout' and 'fin_hard_timeout' are measured in seconds. + * 'fin_hard_timeout' specifies time since the flow's creation, not since the + * receipt of the FIN or RST. + * + * This is useful for quickly discarding learned TCP flows that otherwise will + * take a long time to expire. + * + * This action is intended for use with an OpenFlow rule that matches only a + * single TCP flow. If the rule matches multiple TCP flows (e.g. it wildcards + * all TCP traffic, or all TCP traffic to a particular port), then any FIN or + * RST in any of those flows will cause the entire OpenFlow rule to expire + * early, which is not normally desirable. + */ +struct nx_action_fin_timeout { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* 16. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_FIN_TIMEOUT. */ + ovs_be16 fin_idle_timeout; /* New idle timeout, if nonzero. */ + ovs_be16 fin_hard_timeout; /* New hard timeout, if nonzero. */ + ovs_be16 pad; /* Must be zero. */ +}; +OFP_ASSERT(sizeof(struct nx_action_fin_timeout) == 16); /* Action structure for NXAST_AUTOPATH. * diff --git a/lib/learn.c b/lib/learn.c index 8d947a31..d749ce2c 100644 --- a/lib/learn.c +++ b/lib/learn.c @@ -125,7 +125,7 @@ learn_check(const struct nx_action_learn *learn, const struct flow *flow) cls_rule_init_catchall(&rule, 0); if (learn->flags & ~htons(OFPFF_SEND_FLOW_REM) - || !is_all_zeros(learn->pad, sizeof learn->pad) + || learn->pad || learn->table_id == 0xff) { return OFPERR_OFPBAC_BAD_ARGUMENT; } @@ -209,6 +209,14 @@ learn_execute(const struct nx_action_learn *learn, const struct flow *flow, ofpbuf_init(&actions, 64); + if (learn->fin_idle_timeout || learn->fin_hard_timeout) { + struct nx_action_fin_timeout *naft; + + naft = ofputil_put_NXAST_FIN_TIMEOUT(&actions); + naft->fin_idle_timeout = learn->fin_idle_timeout; + naft->fin_hard_timeout = learn->fin_hard_timeout; + } + for (p = learn + 1, end = (char *) learn + ntohs(learn->len); p != end; ) { uint16_t header = ntohs(get_be16(&p)); int n_bits = header & NX_LEARN_N_BITS_MASK; @@ -431,6 +439,10 @@ learn_parse(struct ofpbuf *b, char *arg, const struct flow *flow) learn->idle_timeout = htons(atoi(value)); } else if (!strcmp(name, "hard_timeout")) { learn->hard_timeout = htons(atoi(value)); + } else if (!strcmp(name, "fin_idle_timeout")) { + learn->fin_idle_timeout = htons(atoi(value)); + } else if (!strcmp(name, "fin_hard_timeout")) { + learn->fin_hard_timeout = htons(atoi(value)); } else if (!strcmp(name, "cookie")) { learn->cookie = htonll(strtoull(value, NULL, 0)); } else { @@ -522,6 +534,14 @@ learn_format(const struct nx_action_learn *learn, struct ds *s) if (learn->hard_timeout != htons(OFP_FLOW_PERMANENT)) { ds_put_format(s, ",hard_timeout=%"PRIu16, ntohs(learn->hard_timeout)); } + if (learn->fin_idle_timeout) { + ds_put_format(s, ",fin_idle_timeout=%"PRIu16, + ntohs(learn->fin_idle_timeout)); + } + if (learn->fin_hard_timeout) { + ds_put_format(s, ",fin_hard_timeout=%"PRIu16, + ntohs(learn->fin_hard_timeout)); + } if (learn->priority != htons(OFP_DEFAULT_PRIORITY)) { ds_put_format(s, ",priority=%"PRIu16, ntohs(learn->priority)); } @@ -535,7 +555,7 @@ learn_format(const struct nx_action_learn *learn, struct ds *s) if (learn->cookie != htonll(0)) { ds_put_format(s, ",cookie=0x%"PRIx64, ntohll(learn->cookie)); } - if (!is_all_zeros(learn->pad, sizeof learn->pad)) { + if (learn->pad != 0) { ds_put_cstr(s, ",***nonzero pad***"); } diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c index f96f8170..24f6876b 100644 --- a/lib/ofp-parse.c +++ b/lib/ofp-parse.c @@ -249,6 +249,24 @@ parse_note(struct ofpbuf *b, const char *arg) nan->len = htons(b->size - start_ofs); } +static void +parse_fin_timeout(struct ofpbuf *b, char *arg) +{ + struct nx_action_fin_timeout *naft; + char *key, *value; + + naft = ofputil_put_NXAST_FIN_TIMEOUT(b); + while (ofputil_parse_key_value(&arg, &key, &value)) { + if (!strcmp(key, "idle_timeout")) { + naft->fin_idle_timeout = htons(str_to_u16(value, key)); + } else if (!strcmp(key, "hard_timeout")) { + naft->fin_hard_timeout = htons(str_to_u16(value, key)); + } else { + ovs_fatal(0, "invalid key '%s' in 'fin_timeout' argument", key); + } + } +} + static void parse_named_action(enum ofputil_action_code code, const struct flow *flow, struct ofpbuf *b, char *arg) @@ -367,6 +385,10 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow, case OFPUTIL_NXAST_DEC_TTL: ofputil_put_NXAST_DEC_TTL(b); break; + + case OFPUTIL_NXAST_FIN_TIMEOUT: + parse_fin_timeout(b, arg); + break; } } diff --git a/lib/ofp-print.c b/lib/ofp-print.c index 8df439de..f1eeebb4 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -194,6 +194,7 @@ ofp_print_action(struct ds *s, const union ofp_action *a, const struct nx_action_multipath *nam; const struct nx_action_autopath *naa; const struct nx_action_output_reg *naor; + const struct nx_action_fin_timeout *naft; struct mf_subfield subfield; uint16_t port; @@ -356,6 +357,21 @@ ofp_print_action(struct ds *s, const union ofp_action *a, ds_put_cstr(s, "exit"); break; + case OFPUTIL_NXAST_FIN_TIMEOUT: + naft = (const struct nx_action_fin_timeout *) a; + ds_put_cstr(s, "fin_timeout("); + if (naft->fin_idle_timeout) { + ds_put_format(s, "idle_timeout=%"PRIu16",", + ntohs(naft->fin_idle_timeout)); + } + if (naft->fin_hard_timeout) { + ds_put_format(s, "hard_timeout=%"PRIu16",", + ntohs(naft->fin_hard_timeout)); + } + ds_chomp(s, ','); + ds_put_char(s, ')'); + break; + default: break; } diff --git a/lib/ofp-util.c b/lib/ofp-util.c index e9366937..ffdccda4 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -2508,6 +2508,7 @@ validate_actions(const union ofp_action *actions, size_t n_actions, case OFPUTIL_NXAST_SET_TUNNEL64: case OFPUTIL_NXAST_EXIT: case OFPUTIL_NXAST_DEC_TTL: + case OFPUTIL_NXAST_FIN_TIMEOUT: break; } diff --git a/lib/ofp-util.def b/lib/ofp-util.def index d05ec9dc..27e0f264 100644 --- a/lib/ofp-util.def +++ b/lib/ofp-util.def @@ -37,4 +37,5 @@ NXAST_ACTION(NXAST_OUTPUT_REG, nx_action_output_reg, 0, NULL) NXAST_ACTION(NXAST_LEARN, nx_action_learn, 1, "learn") NXAST_ACTION(NXAST_EXIT, nx_action_header, 0, "exit") NXAST_ACTION(NXAST_DEC_TTL, nx_action_header, 0, "dec_ttl") +NXAST_ACTION(NXAST_FIN_TIMEOUT, nx_action_fin_timeout, 0, "fin_timeout") #undef NXAST_ACTION diff --git a/lib/ofp-util.h b/lib/ofp-util.h index 816532e0..e64be514 100644 --- a/lib/ofp-util.h +++ b/lib/ofp-util.h @@ -352,6 +352,9 @@ bool ofputil_frag_handling_from_string(const char *, enum ofp_config_flags *); * OFPUTIL_NXAST_BUNDLE_LOAD * OFPUTIL_NXAST_RESUBMIT_TABLE * OFPUTIL_NXAST_OUTPUT_REG + * OFPUTIL_NXAST_LEARN + * OFPUTIL_NXAST_DEC_TTL + * OFPUTIL_NXAST_FIN_TIMEOUT * * (The above list helps developers who want to "grep" for these definitions.) */ diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 8903a7f4..1fe5da68 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -216,6 +216,11 @@ struct action_xlate_ctx { /* The rule that we are currently translating, or NULL. */ struct rule_dpif *rule; + /* Union of the set of TCP flags seen so far in this flow. (Used only by + * NXAST_FIN_TIMEOUT. Set to zero to avoid updating updating rules' + * timeouts.) */ + uint8_t tcp_flags; + /* If nonnull, called just before executing a resubmit action. * * This is normally null so the client has to set it manually after @@ -231,6 +236,7 @@ struct action_xlate_ctx { * be reassessed for every packet. */ bool has_learn; /* Actions include NXAST_LEARN? */ bool has_normal; /* Actions output to OFPP_NORMAL? */ + bool has_fin_timeout; /* Actions include NXAST_FIN_TIMEOUT? */ uint16_t nf_output_iface; /* Output interface index for NetFlow. */ mirror_mask_t mirrors; /* Bitmap of associated mirrors. */ @@ -250,7 +256,7 @@ struct action_xlate_ctx { static void action_xlate_ctx_init(struct action_xlate_ctx *, struct ofproto_dpif *, const struct flow *, ovs_be16 initial_tci, struct rule_dpif *, - const struct ofpbuf *); + uint8_t tcp_flags, const struct ofpbuf *); static struct ofpbuf *xlate_actions(struct action_xlate_ctx *, const union ofp_action *in, size_t n_in); @@ -304,6 +310,7 @@ struct facet { /* Accounting. */ uint64_t accounted_bytes; /* Bytes processed by facet_account(). */ struct netflow_flow nf_flow; /* Per-flow NetFlow tracking data. */ + uint8_t tcp_flags; /* TCP flags seen for this 'rule'. */ /* Properties of datapath actions. * @@ -314,6 +321,7 @@ struct facet { bool may_install; /* Reassess actions for every packet? */ bool has_learn; /* Actions include NXAST_LEARN? */ bool has_normal; /* Actions output to OFPP_NORMAL? */ + bool has_fin_timeout; /* Actions include NXAST_FIN_TIMEOUT? */ tag_type tags; /* Tags that would require revalidation. */ mirror_mask_t mirrors; /* Bitmap of dependent mirrors. */ }; @@ -2954,6 +2962,8 @@ update_stats(struct ofproto_dpif *p) subfacet->dp_packet_count = stats->n_packets; subfacet->dp_byte_count = stats->n_bytes; + facet->tcp_flags |= stats->tcp_flags; + subfacet_update_time(subfacet, stats->used); facet_account(facet); facet_push_stats(facet); @@ -3225,12 +3235,14 @@ facet_account(struct facet *facet) /* Feed information from the active flows back into the learning table to * ensure that table is always in sync with what is actually flowing * through the datapath. */ - if (facet->has_learn || facet->has_normal) { + if (facet->has_learn || facet->has_normal + || (facet->has_fin_timeout + && facet->tcp_flags & (TCP_FIN | TCP_RST))) { struct action_xlate_ctx ctx; action_xlate_ctx_init(&ctx, ofproto, &facet->flow, facet->flow.vlan_tci, - facet->rule, NULL); + facet->rule, facet->tcp_flags, NULL); ctx.may_learn = true; ofpbuf_delete(xlate_actions(&ctx, facet->rule->up.actions, facet->rule->up.n_actions)); @@ -3324,6 +3336,7 @@ facet_flush_stats(struct facet *facet) facet_reset_counters(facet); netflow_flow_clear(&facet->nf_flow); + facet->tcp_flags = 0; } /* Searches 'ofproto''s table of facets for one exactly equal to 'flow'. @@ -3420,7 +3433,7 @@ facet_check_consistency(struct facet *facet) bool should_install; action_xlate_ctx_init(&ctx, ofproto, &facet->flow, - subfacet->initial_tci, rule, NULL); + subfacet->initial_tci, rule, 0, NULL); odp_actions = xlate_actions(&ctx, rule->up.actions, rule->up.n_actions); @@ -3539,7 +3552,7 @@ facet_revalidate(struct facet *facet) bool should_install; action_xlate_ctx_init(&ctx, ofproto, &facet->flow, - subfacet->initial_tci, new_rule, NULL); + subfacet->initial_tci, new_rule, 0, NULL); odp_actions = xlate_actions(&ctx, new_rule->up.actions, new_rule->up.n_actions); actions_changed = (subfacet->actions_len != odp_actions->size @@ -3581,6 +3594,7 @@ facet_revalidate(struct facet *facet) facet->may_install = ctx.may_set_up_flow; facet->has_learn = ctx.has_learn; facet->has_normal = ctx.has_normal; + facet->has_fin_timeout = ctx.has_fin_timeout; facet->mirrors = ctx.mirrors; if (new_actions) { i = 0; @@ -3690,7 +3704,7 @@ flow_push_stats(struct rule_dpif *rule, ofproto_rule_update_used(&rule->up, used); action_xlate_ctx_init(&push.ctx, ofproto, flow, flow->vlan_tci, rule, - NULL); + 0, NULL); push.ctx.resubmit_hook = push_resubmit; ofpbuf_delete(xlate_actions(&push.ctx, rule->up.actions, rule->up.n_actions)); @@ -3835,12 +3849,13 @@ subfacet_make_actions(struct subfacet *subfacet, const struct ofpbuf *packet) struct action_xlate_ctx ctx; action_xlate_ctx_init(&ctx, ofproto, &facet->flow, subfacet->initial_tci, - rule, packet); + rule, 0, packet); odp_actions = xlate_actions(&ctx, rule->up.actions, rule->up.n_actions); facet->tags = ctx.tags; facet->may_install = ctx.may_set_up_flow; facet->has_learn = ctx.has_learn; facet->has_normal = ctx.has_normal; + facet->has_fin_timeout = ctx.has_fin_timeout; facet->nf_flow.output_iface = ctx.nf_output_iface; facet->mirrors = ctx.mirrors; @@ -3960,6 +3975,7 @@ subfacet_update_stats(struct subfacet *subfacet, subfacet_update_time(subfacet, stats->used); facet->packet_count += stats->n_packets; facet->byte_count += stats->n_bytes; + facet->tcp_flags |= stats->tcp_flags; facet_push_stats(facet); netflow_flow_update_flags(&facet->nf_flow, stats->tcp_flags); } @@ -4115,7 +4131,7 @@ rule_execute(struct rule *rule_, const struct flow *flow, size_t size; action_xlate_ctx_init(&ctx, ofproto, flow, flow->vlan_tci, - rule, packet); + rule, packet_get_tcp_flags(packet, flow), packet); odp_actions = xlate_actions(&ctx, rule->up.actions, rule->up.n_actions); size = packet->size; if (execute_odp_actions(ofproto, flow, odp_actions->data, @@ -4709,6 +4725,28 @@ xlate_learn_action(struct action_xlate_ctx *ctx, free(fm.actions); } +/* Reduces '*timeout' to no more than 'max'. A value of zero in either case + * means "infinite". */ +static void +reduce_timeout(uint16_t max, uint16_t *timeout) +{ + if (max && (!*timeout || *timeout > max)) { + *timeout = max; + } +} + +static void +xlate_fin_timeout(struct action_xlate_ctx *ctx, + const struct nx_action_fin_timeout *naft) +{ + if (ctx->tcp_flags & (TCP_FIN | TCP_RST) && ctx->rule) { + struct rule_dpif *rule = ctx->rule; + + reduce_timeout(ntohs(naft->fin_idle_timeout), &rule->up.idle_timeout); + reduce_timeout(ntohs(naft->fin_hard_timeout), &rule->up.hard_timeout); + } +} + static bool may_receive(const struct ofport_dpif *port, struct action_xlate_ctx *ctx) { @@ -4914,6 +4952,11 @@ do_xlate_actions(const union ofp_action *in, size_t n_in, case OFPUTIL_NXAST_EXIT: ctx->exit = true; break; + + case OFPUTIL_NXAST_FIN_TIMEOUT: + ctx->has_fin_timeout = true; + xlate_fin_timeout(ctx, (const struct nx_action_fin_timeout *) ia); + break; } } @@ -4933,7 +4976,7 @@ static void action_xlate_ctx_init(struct action_xlate_ctx *ctx, struct ofproto_dpif *ofproto, const struct flow *flow, ovs_be16 initial_tci, struct rule_dpif *rule, - const struct ofpbuf *packet) + uint8_t tcp_flags, const struct ofpbuf *packet) { ctx->ofproto = ofproto; ctx->flow = *flow; @@ -4943,6 +4986,7 @@ action_xlate_ctx_init(struct action_xlate_ctx *ctx, ctx->rule = rule; ctx->packet = packet; ctx->may_learn = packet != NULL; + ctx->tcp_flags = tcp_flags; ctx->resubmit_hook = NULL; } @@ -4960,6 +5004,7 @@ xlate_actions(struct action_xlate_ctx *ctx, ctx->may_set_up_flow = true; ctx->has_learn = false; ctx->has_normal = false; + ctx->has_fin_timeout = false; ctx->nf_output_iface = NF_OUT_DROP; ctx->mirrors = 0; ctx->recurse = 0; @@ -5730,7 +5775,7 @@ packet_out(struct ofproto *ofproto_, struct ofpbuf *packet, odp_flow_key_from_flow(&key, flow); action_xlate_ctx_init(&push.ctx, ofproto, flow, flow->vlan_tci, NULL, - packet); + packet_get_tcp_flags(packet, flow), packet); /* Ensure that resubmits in 'ofp_actions' get accounted to their * matching rules. */ @@ -6032,11 +6077,13 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, int argc, const char *argv[], if (rule) { struct ofproto_trace trace; struct ofpbuf *odp_actions; + uint8_t tcp_flags; + tcp_flags = packet ? packet_get_tcp_flags(packet, &flow) : 0; trace.result = &result; trace.flow = flow; action_xlate_ctx_init(&trace.ctx, ofproto, &flow, initial_tci, - rule, packet); + rule, tcp_flags, packet); trace.ctx.resubmit_hook = trace_resubmit; odp_actions = xlate_actions(&trace.ctx, rule->up.actions, rule->up.n_actions); diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h index 3927a2e9..9ba62de1 100644 --- a/ofproto/ofproto-provider.h +++ b/ofproto/ofproto-provider.h @@ -841,9 +841,10 @@ struct ofproto_class { * 'flow' reflects the flow information for 'packet'. All of the * information in 'flow' is extracted from 'packet', except for * flow->tun_id and flow->in_port, which are assigned the correct values - * for the incoming packet. The register values are zeroed. + * for the incoming packet. The register values are zeroed. 'packet''s + * header pointers (e.g. packet->l3) are appropriately initialized. * - * The statistics for 'packet' should be included in 'rule'. + * The implementation should add the statistics for 'packet' into 'rule'. * * Returns 0 if successful, otherwise an OpenFlow error code. */ enum ofperr (*rule_execute)(struct rule *rule, const struct flow *flow, diff --git a/tests/learn.at b/tests/learn.at index 943a7c88..a9701718 100644 --- a/tests/learn.at +++ b/tests/learn.at @@ -4,12 +4,12 @@ AT_SETUP([learning action - parsing and formatting]) AT_DATA([flows.txt], [[ actions=learn() actions=learn(NXM_OF_VLAN_TCI[0..11], NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], output:NXM_OF_IN_PORT[], load:10->NXM_NX_REG0[5..10]) -actions=learn(table=1,idle_timeout=1, hard_timeout=2, priority=10, cookie=0xfedcba9876543210, in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31]) +actions=learn(table=1,idle_timeout=10, hard_timeout=20, fin_idle_timeout=5, fin_hard_timeout=10, priority=10, cookie=0xfedcba9876543210, in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31]) ]]) AT_CHECK([ovs-ofctl parse-flows flows.txt], [0], [[OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1) OFPT_FLOW_MOD (xid=0x2): ADD actions=learn(table=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[],load:0x000a->NXM_NX_REG0[5..10]) -OFPT_FLOW_MOD (xid=0x3): ADD actions=learn(table=1,idle_timeout=1,hard_timeout=2,priority=10,cookie=0xfedcba9876543210,in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31]) +OFPT_FLOW_MOD (xid=0x3): ADD actions=learn(table=1,idle_timeout=10,hard_timeout=20,fin_idle_timeout=5,fin_hard_timeout=10,priority=10,cookie=0xfedcba9876543210,in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31]) ]]) AT_CLEANUP @@ -106,3 +106,17 @@ NXST_FLOW reply: ]) OVS_VSWITCHD_STOP AT_CLEANUP + +AT_SETUP([learning action - fin_timeout feature]) +# This is a totally artificial use of the "learn" action. The only purpose +# is to check that specifying fin_idle_timeout or fin_hard_timeout causes +# a corresponding fin_timeout action to end up in the learned flows. +OVS_VSWITCHD_START +AT_CHECK([[ovs-ofctl add-flow br0 'actions=learn(fin_hard_timeout=10, fin_idle_timeout=5, NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], output:NXM_OF_IN_PORT[])']]) +AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=192.168.0.1,tip=192.168.0.2,op=1,sha=50:54:00:00:00:05,tha=00:00:00:00:00:00)' -generate], [0], [ignore]) +AT_CHECK([ovs-ofctl dump-flows br0 table=1 | ofctl_strip], [0], +[NXST_FLOW reply: + table=1, dl_dst=50:54:00:00:00:05 actions=fin_timeout(idle_timeout=5,hard_timeout=10),output:1 +]) +OVS_VSWITCHD_STOP +AT_CLEANUP diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index a21d179c..a3f32c06 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -1136,3 +1136,40 @@ AT_CHECK([test $hard4 -lt $duration4]) OVS_VSWITCHD_STOP AT_CLEANUP + +AT_SETUP([ofproto-dpif - fin_timeout]) +OVS_VSWITCHD_START +AT_DATA([flows.txt], [dnl +in_port=1 actions=output:2 +in_port=2 actions=mod_vlan_vid:17,output:1 +]) +AT_CHECK([ovs-ofctl add-flow br0 'idle_timeout=60,actions=fin_timeout(idle_timeout=5)']) +AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip], [0], +[NXST_FLOW reply: + idle_timeout=60, actions=fin_timeout(idle_timeout=5) +]) +# Check that a TCP SYN packet does not change the timeout. (Because +# flow stats updates are mainly what implements the fin_timeout +# feature, we warp forward a couple of times to ensure that flow stats +# run before re-checking the flow table.) +AT_CHECK([ovs-appctl netdev-dummy/receive br0 0021853763af0026b98cb0f908004500003c2e2440004006465dac11370dac11370b828b0016751e267b00000000a00216d017360000020405b40402080a2d25085f0000000001030307], [0], [success +]) +AT_CHECK([ovs-appctl time/warp 1000 && ovs-appctl time/warp 1000], [0], [warped +warped +]) +AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip], [0], +[NXST_FLOW reply: + n_packets=1, n_bytes=74, idle_timeout=60, actions=fin_timeout(idle_timeout=5) +]) +# Check that a TCP FIN packet does change the timeout. +AT_CHECK([ovs-appctl netdev-dummy/receive br0 0021853763af0026b98cb0f90800451000342e3e40004006463bac11370dac11370b828b0016751e319dfc96399b801100717ae800000101080a2d250a9408579588], [0], [success +]) +AT_CHECK([ovs-appctl time/warp 1000 && ovs-appctl time/warp 1000], [0], [warped +warped +]) +AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip], [0], +[NXST_FLOW reply: + n_packets=2, n_bytes=140, idle_timeout=5, actions=fin_timeout(idle_timeout=5) +]) +OVS_VSWITCHD_STOP +AT_CLEANUP diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at index 066a6272..8eba261f 100644 --- a/tests/ovs-ofctl.at +++ b/tests/ovs-ofctl.at @@ -27,6 +27,7 @@ actions=output:1,bundle_load(eth_src,0,hrw,ofport,NXM_NX_REG0[16..31],slaves:1), actions=resubmit:1,resubmit(2),resubmit(,3),resubmit(2,3) send_flow_rem,actions=output:1,output:NXM_NX_REG0[],output:2,output:NXM_NX_REG1[16..31],output:3 check_overlap,actions=output:1,exit,output:2 +actions=fin_timeout(idle_timeout=5,hard_timeout=15) ]]) AT_CHECK([ovs-ofctl parse-flows flows.txt @@ -58,6 +59,7 @@ NXT_FLOW_MOD: ADD table:255 actions=output:1,bundle_load(eth_src,0,hrw,ofport,NX NXT_FLOW_MOD: ADD table:255 actions=resubmit:1,resubmit:2,resubmit(,3),resubmit(2,3) NXT_FLOW_MOD: ADD table:255 send_flow_rem actions=output:1,output:NXM_NX_REG0[],output:2,output:NXM_NX_REG1[16..31],output:3 NXT_FLOW_MOD: ADD table:255 check_overlap actions=output:1,exit,output:2 +NXT_FLOW_MOD: ADD table:255 actions=fin_timeout(idle_timeout=5,hard_timeout=15) ]]) AT_CLEANUP diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 455298e3..8de6af94 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -956,6 +956,11 @@ specified. These key-value pairs have the same meaning as in the usual \fBovs\-ofctl\fR flow syntax. . +.IP \fBfin_idle_timeout=\fIseconds\fR +.IQ \fBfin_hard_timeout=\fIseconds\fR +Adds a \fBfin_timeout\fR action with the specified arguments to the +new flow. This feature was added in Open vSwitch 1.5.90. +. .IP \fBtable=\fInumber\fR The table in which the new flow should be inserted. Specify a decimal number between 0 and 254. The default, if \fBtable\fR is unspecified, @@ -1006,6 +1011,27 @@ with no match criteria. (This is why the default \fBtable\fR is 1, to keep the learned flows separate from the primary flow table 0.) .RE . +.IP "\fBfin_timeout(\fIargument\fR[\fB,\fIargument\fR]\fB)" +This action changes the idle timeout or hard timeout, or both, of this +OpenFlow rule when the rule matches a TCP packet with the FIN or RST +flag. When such a packet is observed, the action reduces the rule's +timeouts to those specified on the action. If the rule's existing +timeout is already shorter than the one that the action specifies, +then that timeout is unaffected. +.IP +\fIargument\fR takes the following forms: +.RS +.IP "\fBidle_timeout=\fIseconds\fR" +Causes the flow to expire after the given number of seconds of +inactivity. +. +.IP "\fBhard_timeout=\fIseconds\fR" +Causes the flow to expire after the given number of seconds, +regardless of activity. (\fIseconds\fR specifies time since the +flow's creation, not since the receipt of the FIN or RST.) +.RE +.IP +This action was added in Open vSwitch 1.5.90. .IP "\fBexit\fR" This action causes Open vSwitch to immediately halt execution of further actions. Those actions which have already been executed are unaffected. Any