[ovs-dev] [PATCH v2 3/3] ofp-actions: Add limit to learn action.

Daniele Di Proietto diproiettod at vmware.com
Wed Mar 8 19:24:17 UTC 2017


This commit adds a new feature to the learn actions: the possibility to
limit the number of learned flows.

To be compatible with users of the old learn action, a new structure is
introduced as well as a new OpenFlow raw action number.

There's a small corner case when we have to slowpath the ukey.  This
happens when:
* The learned rule has expired (or has been deleted).
* The ukey that learned the rule is still in the datapath.
* No packets hit the datapath flow recently.
In this case we cannot relearn the rule (because there are no new
packets), and the actions might depend on the learn execution, so the
only option is to slowpath the ukey.  I don't think this has big
performance implications since it's done only for ukey with no traffic.

Signed-off-by: Daniele Di Proietto <diproiettod at vmware.com>
---
 include/openvswitch/ofp-actions.h  |  12 +++
 lib/learn.c                        |  24 +++++
 lib/odp-util.h                     |   6 +-
 lib/ofp-actions.c                  |  88 ++++++++++++++++-
 ofproto/ofproto-dpif-xlate-cache.c |   3 +-
 ofproto/ofproto-dpif-xlate-cache.h |   1 +
 ofproto/ofproto-dpif-xlate.c       |  22 ++++-
 ofproto/ofproto-provider.h         |   3 +-
 ofproto/ofproto.c                  |  46 +++++++--
 tests/learn.at                     | 191 +++++++++++++++++++++++++++++++++++++
 tests/ofp-actions.at               |  14 +++
 utilities/ovs-ofctl.8.in           |  16 ++++
 12 files changed, 412 insertions(+), 14 deletions(-)

diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
index 88f573dcd..63abbb75b 100644
--- a/include/openvswitch/ofp-actions.h
+++ b/include/openvswitch/ofp-actions.h
@@ -652,6 +652,10 @@ struct ofpact_resubmit {
  * If NX_LEARN_F_SEND_FLOW_REM is set, then the learned flows will have their
  * OFPFF_SEND_FLOW_REM flag set.
  *
+ * If NX_LEARN_F_WRITE_RESULT is set, then the actions will write whether the
+ * learn operation succeded on a bit.  If the learn is successful the bit will
+ * be set, otherwise (e.g. if the limit is hit) the bit will be unset.
+ *
  * If NX_LEARN_F_DELETE_LEARNED is set, then removing this action will delete
  * all the flows from the learn action's 'table_id' that have the learn
  * action's 'cookie'.  Important points:
@@ -677,6 +681,7 @@ struct ofpact_resubmit {
 enum nx_learn_flags {
     NX_LEARN_F_SEND_FLOW_REM = 1 << 0,
     NX_LEARN_F_DELETE_LEARNED = 1 << 1,
+    NX_LEARN_F_WRITE_RESULT = 1 << 2,
 };
 
 #define NX_LEARN_N_BITS_MASK    0x3ff
@@ -740,6 +745,13 @@ struct ofpact_learn {
         ovs_be64 cookie;           /* Cookie for new flow. */
         uint16_t fin_idle_timeout; /* Idle timeout after FIN, if nonzero. */
         uint16_t fin_hard_timeout; /* Hard timeout after FIN, if nonzero. */
+        /* If the number of flows on 'table_id' with 'cookie' exceeds this,
+         * the action will not add a new flow. */
+        uint32_t limit;
+        /* Used only if 'flags' has NX_LEARN_F_WRITE_RESULT.  If the execution
+         * failed to install a new flow because 'limit' is exceeded,
+         * result_dst will be set to 0, otherwise to 1. */
+        struct mf_subfield result_dst;
     );
 
     struct ofpact_learn_spec specs[];
diff --git a/lib/learn.c b/lib/learn.c
index ce52c35f2..50a478fe1 100644
--- a/lib/learn.c
+++ b/lib/learn.c
@@ -406,6 +406,22 @@ learn_parse__(char *orig, char *arg, struct ofpbuf *ofpacts)
             learn->flags |= NX_LEARN_F_SEND_FLOW_REM;
         } else if (!strcmp(name, "delete_learned")) {
             learn->flags |= NX_LEARN_F_DELETE_LEARNED;
+        } else if (!strcmp(name, "limit")) {
+            learn->limit = atoi(value);
+        } else if (!strcmp(name, "result_dst")) {
+            char *error;
+            learn->flags |= NX_LEARN_F_WRITE_RESULT;
+            error = mf_parse_subfield(&learn->result_dst, value);
+            if (error) {
+                return error;
+            }
+            if (!learn->result_dst.field->writable) {
+                return xasprintf("%s is read-only", value);
+            }
+            if (learn->result_dst.n_bits != 1) {
+                return xasprintf("result_dst in 'learn' action must be a "
+                                 "single bit");
+            }
         } else {
             struct ofpact_learn_spec *spec;
             char *error;
@@ -487,6 +503,14 @@ learn_format(const struct ofpact_learn *learn, struct ds *s)
         ds_put_format(s, ",%scookie=%s%#"PRIx64,
                       colors.param, colors.end, ntohll(learn->cookie));
     }
+    if (learn->limit != 0) {
+        ds_put_format(s, ",%slimit=%s%"PRIu32,
+                      colors.param, colors.end, learn->limit);
+    }
+    if (learn->flags & NX_LEARN_F_WRITE_RESULT) {
+        ds_put_format(s, ",%sresult_dst=%s", colors.param, colors.end);
+        mf_format_subfield(&learn->result_dst, s);
+    }
 
     OFPACT_LEARN_SPEC_FOR_EACH (spec, learn) {
         unsigned int n_bytes = DIV_ROUND_UP(spec->n_bits, 8);
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 42011bccd..c563492e3 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -41,11 +41,13 @@ struct pkt_metadata;
     SPR(SLOW_BFD,        "bfd",        "Consists of BFD packets")       \
     SPR(SLOW_LACP,       "lacp",       "Consists of LACP packets")      \
     SPR(SLOW_STP,        "stp",        "Consists of STP packets")       \
-    SPR(SLOW_LLDP,       "lldp",       "Consists of LLDP packets")    \
+    SPR(SLOW_LLDP,       "lldp",       "Consists of LLDP packets")      \
     SPR(SLOW_CONTROLLER, "controller",                                  \
         "Sends \"packet-in\" messages to the OpenFlow controller")      \
     SPR(SLOW_ACTION,     "action",                                      \
-        "Uses action(s) not supported by datapath")
+        "Uses action(s) not supported by datapath")                     \
+    SPR(SLOW_MAY_LEARN,      "learn",                                   \
+        "New packets may or may not learn new flows")
 
 /* Indexes for slow-path reasons.  Client code uses "enum slow_path_reason"
  * values instead of these, these are just a way to construct those. */
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 91a7a1ade..6abfca6d5 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -292,6 +292,8 @@ enum ofp_raw_action_type {
 
     /* NX1.0+(16): struct nx_action_learn, ... VLMFF */
     NXAST_RAW_LEARN,
+    /* NX1.0+(44): struct nx_action_learn2, ... VLMFF */
+    NXAST_RAW_LEARN2,
 
     /* NX1.0+(17): void. */
     NXAST_RAW_EXIT,
@@ -4028,7 +4030,7 @@ format_RESUBMIT(const struct ofpact_resubmit *a, struct ds *s)
     }
 }
 
-/* Action structure for NXAST_LEARN.
+/* Action structure for NXAST_LEARN and NXAST_LEARN2.
  *
  * This action adds or modifies a flow in an OpenFlow table, similar to
  * OFPT_FLOW_MOD with OFPFC_MODIFY_STRICT as 'command'.  The new flow has the
@@ -4259,6 +4261,22 @@ struct nx_action_learn {
 };
 OFP_ASSERT(sizeof(struct nx_action_learn) == 32);
 
+struct nx_action_learn2 {
+    struct nx_action_learn up;  /* The wire format includes nx_action_learn. */
+    ovs_be32 limit;             /* Maximum number of learned flows. */
+
+    /* Where to store the result. */
+    ovs_be16 result_dst_ofs;    /* Starting bit offset in destination. */
+
+    ovs_be16 pad2;              /* Must be zero. */
+    /* Followed by:
+     * - OXM/NXM header for destination field (4 or 8 bytes),
+     *   if NX_LEARN_F_WRITE_RESULT is set in 'flags'
+     * - a sequence of flow_mod_spec elements, as described above,
+     *   until the end of the action is reached. */
+};
+OFP_ASSERT(sizeof(struct nx_action_learn2) == 40);
+
 static ovs_be16
 get_be16(const void **pp)
 {
@@ -4444,6 +4462,56 @@ decode_NXAST_RAW_LEARN(const struct nx_action_learn *nal,
                               vl_mff_map, ofpacts);
 }
 
+/* Converts 'nal' into a "struct ofpact_learn" and appends that struct to
+ * 'ofpacts'.  Returns 0 if successful, otherwise an OFPERR_*. */
+static enum ofperr
+decode_NXAST_RAW_LEARN2(const struct nx_action_learn2 *nal,
+                        enum ofp_version ofp_version OVS_UNUSED,
+                        const struct vl_mff_map *vl_mff_map,
+                        struct ofpbuf *ofpacts)
+{
+    struct ofpbuf b = ofpbuf_const_initializer(nal, ntohs(nal->up.len));
+    struct ofpact_learn *learn;
+    enum ofperr error;
+
+    if (nal->pad2) {
+        return OFPERR_NXBAC_MUST_BE_ZERO;
+    }
+
+    learn = ofpact_put_LEARN(ofpacts);
+    error = decode_LEARN_common(&nal->up, learn);
+    if (error) {
+        return error;
+    }
+
+    learn->limit = ntohl(nal->limit);
+
+    if (learn->flags & ~(NX_LEARN_F_SEND_FLOW_REM |
+                         NX_LEARN_F_DELETE_LEARNED |
+                         NX_LEARN_F_WRITE_RESULT)) {
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    }
+
+    ofpbuf_pull(&b, sizeof *nal);
+
+    if (learn->flags & NX_LEARN_F_WRITE_RESULT) {
+        error = nx_pull_header(&b, vl_mff_map, &learn->result_dst.field, NULL);
+        if (error) {
+            return error;
+        }
+        if (!learn->result_dst.field->writable) {
+            return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
+        }
+        learn->result_dst.ofs = ntohs(nal->result_dst_ofs);
+        learn->result_dst.n_bits = 1;
+    } else if (nal->result_dst_ofs) {
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    }
+
+    return decode_LEARN_specs(b.data, (char *) nal + ntohs(nal->up.len),
+                              vl_mff_map, ofpacts);
+}
+
 static void
 put_be16(struct ofpbuf *b, ovs_be16 x)
 {
@@ -4477,7 +4545,19 @@ encode_LEARN(const struct ofpact_learn *learn,
     size_t start_ofs;
 
     start_ofs = out->size;
-    nal = put_NXAST_LEARN(out);
+
+    if (learn->ofpact.raw == NXAST_RAW_LEARN2
+        || learn->limit != 0
+        || learn->flags & NX_LEARN_F_WRITE_RESULT) {
+        struct nx_action_learn2 *nal2;
+
+        nal2 = put_NXAST_LEARN2(out);
+        nal2->limit = htonl(learn->limit);
+        nal2->result_dst_ofs = htons(learn->result_dst.ofs);
+        nal = &nal2->up;
+    } else {
+        nal = put_NXAST_LEARN(out);
+    }
     nal->idle_timeout = htons(learn->idle_timeout);
     nal->hard_timeout = htons(learn->hard_timeout);
     nal->fin_idle_timeout = htons(learn->fin_idle_timeout);
@@ -4487,6 +4567,10 @@ encode_LEARN(const struct ofpact_learn *learn,
     nal->flags = htons(learn->flags);
     nal->table_id = learn->table_id;
 
+    if (learn->flags & NX_LEARN_F_WRITE_RESULT) {
+        nx_put_header(out, learn->result_dst.field->id, 0, false);
+    }
+
     OFPACT_LEARN_SPEC_FOR_EACH (spec, learn) {
         put_u16(out, spec->n_bits | spec->dst_type | spec->src_type);
 
diff --git a/ofproto/ofproto-dpif-xlate-cache.c b/ofproto/ofproto-dpif-xlate-cache.c
index d6341c084..916170159 100644
--- a/ofproto/ofproto-dpif-xlate-cache.c
+++ b/ofproto/ofproto-dpif-xlate-cache.c
@@ -123,7 +123,8 @@ xlate_push_stats_entry(struct xc_entry *entry,
         break;
     case XC_LEARN: {
         enum ofperr error;
-        error = ofproto_flow_mod_learn(entry->learn.ofm, true);
+        error = ofproto_flow_mod_learn(entry->learn.ofm, true,
+                                       entry->learn.limit, NULL);
         if (error) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
             VLOG_WARN_RL(&rl, "xcache LEARN action execution failed.");
diff --git a/ofproto/ofproto-dpif-xlate-cache.h b/ofproto/ofproto-dpif-xlate-cache.h
index a15adbe64..13f7cbc91 100644
--- a/ofproto/ofproto-dpif-xlate-cache.h
+++ b/ofproto/ofproto-dpif-xlate-cache.h
@@ -93,6 +93,7 @@ struct xc_entry {
         } bond;
         struct {
             struct ofproto_flow_mod *ofm;
+            unsigned limit;
         } learn;
         struct {
             struct ofproto_dpif *ofproto;
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 0912ee38c..77033474c 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -4537,17 +4537,35 @@ xlate_learn_action(struct xlate_ctx *ctx, const struct ofpact_learn *learn)
         ofpbuf_uninit(&ofpacts);
 
         if (!error) {
+            bool success = true;
             if (ctx->xin->allow_side_effects) {
-                error = ofproto_flow_mod_learn(ofm, ctx->xin->xcache != NULL);
+                error = ofproto_flow_mod_learn(ofm, ctx->xin->xcache != NULL,
+                                               learn->limit, &success);
+            } else if (learn->limit) {
+                if (!ofm->temp_rule
+                    || ofm->temp_rule->state != RULE_INSERTED) {
+                    ctx->xout->slow |= SLOW_MAY_LEARN;
+                }
             }
 
-            if (ctx->xin->xcache) {
+            if (learn->flags & NX_LEARN_F_WRITE_RESULT) {
+                nxm_reg_load(&learn->result_dst, success ? 1 : 0,
+                             &ctx->xin->flow, ctx->wc);
+                xlate_report_subfield(ctx, &learn->result_dst);
+            }
+
+            if (success && ctx->xin->xcache) {
                 struct xc_entry *entry;
 
                 entry = xlate_cache_add_entry(ctx->xin->xcache, XC_LEARN);
                 entry->learn.ofm = ofm;
+                entry->learn.limit = learn->limit;
                 ofm = NULL;
             }
+
+            if (OVS_UNLIKELY(ctx->xin->trace && !success)) {
+                xlate_report(ctx, OFT_DETAIL, "Limit exceeded, learn failed");
+            }
         }
 
         if (ctx->xin->xcache) {
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 7d3e929f2..43f114e90 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -1929,7 +1929,8 @@ enum ofperr ofproto_flow_mod_init_for_learn(struct ofproto *,
                                             const struct ofputil_flow_mod *,
                                             struct ofproto_flow_mod *)
     OVS_EXCLUDED(ofproto_mutex);
-enum ofperr ofproto_flow_mod_learn(struct ofproto_flow_mod *, bool keep_ref)
+enum ofperr ofproto_flow_mod_learn(struct ofproto_flow_mod *, bool keep_ref,
+                                   unsigned limit, bool *below_limit)
     OVS_EXCLUDED(ofproto_mutex);
 enum ofperr ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm);
 enum ofperr ofproto_flow_mod_learn_start(struct ofproto_flow_mod *ofm)
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index ef4b1d980..1fa6bb81e 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -5062,30 +5062,64 @@ ofproto_flow_mod_learn_finish(struct ofproto_flow_mod *ofm,
  * 'keep_ref' is true, then a reference to the current rule is held, otherwise
  * it is released and 'ofm->temp_rule' is set to NULL.
  *
+ * If 'limit' != 0, insertion will fail if there are more than 'limit' rules
+ * in the same table with the same cookie.  If insertion succeeds,
+ * '*below_limit' will be set to true.  If insertion fails '*below_limit' will
+ * be set to false.
+ *
  * Caller needs to be the exclusive owner of 'ofm' as it is being manipulated
  * during the call. */
 enum ofperr
-ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref)
+ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref,
+                       unsigned limit, bool *below_limit)
     OVS_EXCLUDED(ofproto_mutex)
 {
     enum ofperr error = ofproto_flow_mod_learn_refresh(ofm);
     struct rule *rule = ofm->temp_rule;
+    bool limited = false;
 
     /* Do we need to insert the rule? */
     if (!error && rule->state == RULE_INITIALIZED) {
         ovs_mutex_lock(&ofproto_mutex);
-        ofm->version = rule->ofproto->tables_version + 1;
-        error = ofproto_flow_mod_learn_start(ofm);
-        if (!error) {
-            ofproto_flow_mod_learn_finish(ofm, NULL);
+
+        if (limit) {
+            struct rule_criteria criteria;
+            struct rule_collection rules;
+            struct match match;
+
+            match_init_catchall(&match);
+            rule_criteria_init(&criteria, rule->table_id, &match, 0,
+                               OVS_VERSION_MAX, rule->flow_cookie,
+                               OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
+            rule_criteria_require_rw(&criteria, false);
+            collect_rules_loose(rule->ofproto, &criteria, &rules);
+            if (rule_collection_n(&rules) >= limit) {
+                limited = true;
+            }
+            rule_collection_destroy(&rules);
+            rule_criteria_destroy(&criteria);
+        }
+
+        if (!limited) {
+            ofm->version = rule->ofproto->tables_version + 1;
+
+            error = ofproto_flow_mod_learn_start(ofm);
+            if (!error) {
+                ofproto_flow_mod_learn_finish(ofm, NULL);
+            }
+        } else {
+            ofproto_flow_mod_uninit(ofm);
         }
         ovs_mutex_unlock(&ofproto_mutex);
     }
 
-    if (!keep_ref) {
+    if (!keep_ref && !limited) {
         ofproto_rule_unref(rule);
         ofm->temp_rule = NULL;
     }
+    if (below_limit) {
+        *below_limit = !limited;
+    }
     return error;
 }
 
diff --git a/tests/learn.at b/tests/learn.at
index 3f6fb5a7e..7dd4ca0e0 100644
--- a/tests/learn.at
+++ b/tests/learn.at
@@ -8,6 +8,8 @@ actions=learn(delete_learned)
 actions=learn(send_flow_rem,delete_learned)
 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=10, hard_timeout=20, fin_idle_timeout=5, fin_hard_timeout=10, priority=10, cookie=0xfedcba9876543210, in_port=99,eth_dst=eth_src,load:in_port->reg1[16..31])
+actions=learn(limit=4096)
+actions=learn(limit=4096,result_dst=reg0[0])
 ]])
 AT_CHECK([ovs-ofctl parse-flows flows.txt], [0],
 [[usable protocols: any
@@ -18,6 +20,8 @@ OFPT_FLOW_MOD (xid=0x3): ADD actions=learn(table=1,delete_learned)
 OFPT_FLOW_MOD (xid=0x4): ADD actions=learn(table=1,send_flow_rem,delete_learned)
 OFPT_FLOW_MOD (xid=0x5): 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:0xa->NXM_NX_REG0[5..10])
 OFPT_FLOW_MOD (xid=0x6): 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])
+OFPT_FLOW_MOD (xid=0x7): ADD actions=learn(table=1,limit=4096)
+OFPT_FLOW_MOD (xid=0x8): ADD actions=learn(table=1,limit=4096,result_dst=NXM_NX_REG0[0])
 ]])
 AT_CLEANUP
 
@@ -417,6 +421,18 @@ for i in `seq 1 10`; do
     ovs-appctl time/warp 10
 done
 
+# Check that the first packet of each flow went out port 2 and the rest out
+# port 3.
+AT_CHECK(
+  [(ovs-ofctl dump-ports br0 2; ovs-ofctl dump-ports br0 3) | strip_xids], [0],
+  [OFPST_PORT reply: 1 ports
+  port  2: rx pkts=0, bytes=0, drop=?, errs=?, frame=?, over=?, crc=?
+           tx pkts=1, bytes=54, drop=?, errs=?, coll=?
+OFPST_PORT reply: 1 ports
+  port  3: rx pkts=0, bytes=0, drop=?, errs=?, frame=?, over=?, crc=?
+           tx pkts=9, bytes=486, drop=?, errs=?, coll=?
+])
+
 # Trace some packets arriving.  This is is a different flow from the previous.
 # Note that we advance time by 2 second between each packet here.
 for i in `seq 1 10`; do
@@ -626,3 +642,178 @@ NXST_FLOW reply:
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([learning action - limit])
+OVS_VSWITCHD_START
+AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg])
+add_of_ports br0 1 2
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1 actions=learn(table=1,dl_dst=dl_src,cookie=0x1,limit=1),2
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:01,dst=50:54:00:00:00:ff),eth_type(0x1234)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:02,dst=50:54:00:00:00:ff),eth_type(0x1234)'])
+
+OVS_WAIT_UNTIL([ovs-ofctl dump-ports br0 2 | grep -o 'tx pkts=2' >/dev/null])
+
+AT_CHECK([ovs-ofctl dump-flows br0 table=1 | ofctl_strip | sort], [0], [dnl
+ cookie=0x1, table=1, dl_dst=50:54:00:00:00:01 actions=drop
+NXST_FLOW reply:
+])
+
+dnl Delete the learned flow
+AT_CHECK([ovs-ofctl del-flows br0 table=1])
+
+AT_CHECK([ovs-ofctl dump-flows br0 table=1 | ofctl_strip | sort], [0], [dnl
+NXST_FLOW reply:
+])
+
+ovs-appctl revalidator/wait
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:02,dst=50:54:00:00:00:ff),eth_type(0x1234)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:01,dst=50:54:00:00:00:ff),eth_type(0x1234)'])
+
+OVS_WAIT_UNTIL([ovs-ofctl dump-ports br0 2 | grep -o 'tx pkts=4' >/dev/null])
+
+AT_CHECK([ovs-ofctl dump-flows br0 table=1 | ofctl_strip | sort], [0], [dnl
+ cookie=0x1, table=1, dl_dst=50:54:00:00:00:02 actions=drop
+NXST_FLOW reply:
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([learning action - limit result_dst])
+OVS_VSWITCHD_START
+AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg])
+add_of_ports br0 1
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1 actions=learn(table=1,dl_dst=dl_src,cookie=0x1,limit=1,result_dst=reg0[[0]]),controller
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+AT_CAPTURE_FILE([ofctl_monitor.log])
+AT_CHECK([ovs-ofctl monitor br0 65534 invalid_ttl -P nxt_packet_in --detach --no-chdir --pidfile 2> ofctl_monitor.log])
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:01,dst=50:54:00:00:00:ff),eth_type(0x1234)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:02,dst=50:54:00:00:00:ff),eth_type(0x1234)'])
+
+OVS_WAIT_UNTIL([test `wc -l < ofctl_monitor.log` -ge 4])
+OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
+
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=14 reg0=0x1,in_port=1 (via action) data_len=14 (unbuffered)
+vlan_tci=0x0000,dl_src=50:54:00:00:00:01,dl_dst=50:54:00:00:00:ff,dl_type=0x1234
+NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=14 in_port=1 (via action) data_len=14 (unbuffered)
+vlan_tci=0x0000,dl_src=50:54:00:00:00:02,dl_dst=50:54:00:00:00:ff,dl_type=0x1234
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 table=1 | ofctl_strip | sort], [0], [dnl
+ cookie=0x1, table=1, dl_dst=50:54:00:00:00:01 actions=drop
+NXST_FLOW reply:
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([learning action - different limits])
+OVS_VSWITCHD_START
+AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg])
+add_of_ports br0 1 2 3
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1 udp,actions=learn(table=11,dl_type=0x0800,nw_proto=17,udp_src=udp_dst,limit=1,result_dst=reg0[[0]]),resubmit(,1)
+table=0 in_port=2 udp,actions=learn(table=12,dl_type=0x0800,nw_proto=17,udp_src=udp_dst,limit=10,result_dst=reg0[[0]]),resubmit(,1)
+table=0 in_port=3 udp,actions=learn(table=13,dl_type=0x0800,nw_proto=17,udp_src=udp_dst,limit=20,result_dst=reg0[[0]]),resubmit(,1)
+dnl
+dnl These flows simply counts the packets that executed a successful learn action:
+dnl
+table=1 cookie=1,reg0=1,in_port=1 actions=drop
+table=1 cookie=2,reg0=1,in_port=2 actions=drop
+table=1 cookie=3,reg0=1,in_port=3 actions=drop
+dnl
+dnl These flows simply counts the packets that didn't execute a successful learn action:
+dnl
+table=1 cookie=1,reg0=0,in_port=1 actions=drop
+table=1 cookie=2,reg0=0,in_port=2 actions=drop
+table=1 cookie=3,reg0=0,in_port=3 actions=drop
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+for i in `seq 1001 1030`; do
+    ovs-appctl netdev-dummy/receive p1 "in_port(1),eth(src=50:54:00:00:00:01,dst=50:54:00:00:00:ff),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=17,tos=0,ttl=64,frag=no),udp(src=1,dst=$i)"
+    ovs-appctl netdev-dummy/receive p2 "in_port(2),eth(src=50:54:00:00:00:01,dst=50:54:00:00:00:ff),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=17,tos=0,ttl=64,frag=no),udp(src=1,dst=$i)"
+    ovs-appctl netdev-dummy/receive p3 "in_port(3),eth(src=50:54:00:00:00:01,dst=50:54:00:00:00:ff),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=17,tos=0,ttl=64,frag=no),udp(src=1,dst=$i)"
+done
+
+dnl Check successful counters:
+AT_CHECK([ovs-ofctl dump-flows br0 table=1,reg0=1 | ofctl_strip | sort], [0], [dnl
+ cookie=0x1, table=1, n_packets=1, n_bytes=42, reg0=0x1,in_port=1 actions=drop
+ cookie=0x2, table=1, n_packets=10, n_bytes=420, reg0=0x1,in_port=2 actions=drop
+ cookie=0x3, table=1, n_packets=20, n_bytes=840, reg0=0x1,in_port=3 actions=drop
+NXST_FLOW reply:
+])
+
+dnl Check failed counters:
+AT_CHECK([ovs-ofctl dump-flows br0 table=1,reg0=0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x1, table=1, n_packets=29, n_bytes=1218, reg0=0,in_port=1 actions=drop
+ cookie=0x2, table=1, n_packets=20, n_bytes=840, reg0=0,in_port=2 actions=drop
+ cookie=0x3, table=1, n_packets=10, n_bytes=420, reg0=0,in_port=3 actions=drop
+NXST_FLOW reply:
+])
+
+dnl Check learned flows:
+
+AT_CHECK([ovs-ofctl dump-flows br0 table=13 | ofctl_strip | sort], [0], [dnl
+ table=13, udp,tp_src=1001 actions=drop
+ table=13, udp,tp_src=1002 actions=drop
+ table=13, udp,tp_src=1003 actions=drop
+ table=13, udp,tp_src=1004 actions=drop
+ table=13, udp,tp_src=1005 actions=drop
+ table=13, udp,tp_src=1006 actions=drop
+ table=13, udp,tp_src=1007 actions=drop
+ table=13, udp,tp_src=1008 actions=drop
+ table=13, udp,tp_src=1009 actions=drop
+ table=13, udp,tp_src=1010 actions=drop
+ table=13, udp,tp_src=1011 actions=drop
+ table=13, udp,tp_src=1012 actions=drop
+ table=13, udp,tp_src=1013 actions=drop
+ table=13, udp,tp_src=1014 actions=drop
+ table=13, udp,tp_src=1015 actions=drop
+ table=13, udp,tp_src=1016 actions=drop
+ table=13, udp,tp_src=1017 actions=drop
+ table=13, udp,tp_src=1018 actions=drop
+ table=13, udp,tp_src=1019 actions=drop
+ table=13, udp,tp_src=1020 actions=drop
+NXST_FLOW reply:
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 table=12 | ofctl_strip | sort], [0], [dnl
+ table=12, udp,tp_src=1001 actions=drop
+ table=12, udp,tp_src=1002 actions=drop
+ table=12, udp,tp_src=1003 actions=drop
+ table=12, udp,tp_src=1004 actions=drop
+ table=12, udp,tp_src=1005 actions=drop
+ table=12, udp,tp_src=1006 actions=drop
+ table=12, udp,tp_src=1007 actions=drop
+ table=12, udp,tp_src=1008 actions=drop
+ table=12, udp,tp_src=1009 actions=drop
+ table=12, udp,tp_src=1010 actions=drop
+NXST_FLOW reply:
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 table=11 | ofctl_strip | sort], [0], [dnl
+ table=11, udp,tp_src=1001 actions=drop
+NXST_FLOW reply:
+])
+
+AT_CHECK([ovs-vsctl del-br br0])
+
+ovs-appctl time/warp 500
+ovs-appctl time/warp 500
+ovs-appctl time/warp 500
+ovs-appctl time/warp 500
+
+AT_CHECK([ovs-vsctl add-br br1 -- set b br1 datapath_type=dummy])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at
index 6384c48d9..694afc9f3 100644
--- a/tests/ofp-actions.at
+++ b/tests/ofp-actions.at
@@ -258,6 +258,20 @@ ffff 0020 00002320 002a 000000000000 dnl
 0001 0008 0005 0000 dnl
 0000 0008 000a 0000
 
+# actions=learn(table=2,idle_timeout=10,hard_timeout=20,fin_idle_timeout=2,fin_hard_timeout=4,priority=80,cookie=0x123456789abcdef0,limit=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[])
+ffff 0050 00002320 002c 000a 0014 0050 123456789abcdef0 0000 02 00 0002 0004 00000001 0000 0000 dnl
+000c 00000802 0000 00000802 0000 dnl
+0030 00000406 0000 00000206 0000 dnl
+1010 00000002 0000 dnl
+00000000
+
+# actions=learn(table=2,idle_timeout=10,hard_timeout=20,fin_idle_timeout=2,fin_hard_timeout=4,priority=80,cookie=0x123456789abcdef0,limit=1,result_dst=NXM_NX_REG0[8],NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[])
+ffff 0050 00002320 002c 000a 0014 0050 123456789abcdef0 0004 02 00 0002 0004 00000001 0008 0000 dnl
+00010004 dnl
+000c 00000802 0000 00000802 0000 dnl
+0030 00000406 0000 00000206 0000 dnl
+1010 00000002 0000
+
 # actions=group:5
 ffff 0010 00002320 0028 0000 00000005
 
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 2ee3193d4..59bbd5780 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -1432,6 +1432,22 @@ the specified table with the specified cookie.
 .IP
 This flag was added in Open vSwitch 2.4.
 .
+.IP \fBlimit=\fInumber\fR
+If the number of flows in table \fBtable\fR with cookie id \fBcookie\fR exceeds
+\fInumber\fR, a new flow will not be learned by this action.  By default
+there's no limit.
+.
+.IP
+This flag was added in Open vSwitch 2.8.
+.
+.IP \fBresult_dst=\fIfield\fB[\fIbit\fB]\fR
+If learning failed (because the number of flows exceeds \fBlimit\fR),
+the action sets \fIfield\fB[\fIbit\fB]\fR to 0, otherwise it will be set to 1.
+\fIfield\fB[\fIbit\fB]\fR must be a single bit.
+.
+.IP
+This flag was added in Open vSwitch 2.8.
+.
 .IP \fIfield\fB=\fIvalue\fR
 .IQ \fIfield\fB[\fIstart\fB..\fIend\fB]=\fIsrc\fB[\fIstart\fB..\fIend\fB]\fR
 .IQ \fIfield\fB[\fIstart\fB..\fIend\fB]\fR
-- 
2.11.0



More information about the dev mailing list