[ovs-dev] [PATCH 2/2] ofproto-dpif: Store relevant fields for wildcarding in facet.

Justin Pettit jpettit at nicira.com
Tue Jun 11 08:20:32 UTC 2013


Dynamically determines the flow fields that were relevant in
processing flows based on the OpenFlow flow table and switch
configuration.  The immediate use for this functionality is to
cache action translations for similar flows in facets.  This yields
a roughly 80% improvement in flow set up rates for a complicated
flow table.

More importantly, these wildcards will be used to determine what to
wildcard for the forthcoming kernel wildcard (megaflow) patches
that will allow wildcarding in the kernel, which will provide
significant flow set up improvements.

The approach to tracking fields and caching action translations in
facets was based on an impressive prototype by Ethan Jackson.

Co-authored-by: Ethan Jackson <ethan at nicira.com>
Signed-off-by: Ethan Jackson <ethan at nicira.com>
Signed-off-by: Justin Pettit <jpettit at nicira.com>
---
 NEWS                   |    2 +
 lib/bond.c             |   20 ++-
 lib/bond.h             |    5 +-
 lib/bundle.c           |   19 ++-
 lib/bundle.h           |    2 +
 lib/flow.c             |   26 +++
 lib/flow.h             |    1 +
 lib/learn.c            |   16 ++
 lib/learn.h            |    2 +
 lib/multipath.c        |    7 +-
 lib/multipath.h        |    4 +-
 lib/nx-match.c         |   13 +-
 lib/nx-match.h         |    6 +-
 ofproto/ofproto-dpif.c |  510 +++++++++++++++++++++++++++++++++++------------
 tests/ofproto-dpif.at  |  345 ++++++++++++++++++++++++++++++++
 tests/test-bundle.c    |    4 +-
 tests/test-multipath.c |    5 +-
 17 files changed, 835 insertions(+), 152 deletions(-)

diff --git a/NEWS b/NEWS
index 478f1fa..aae3378 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,8 @@ v1.11.0 - xx xxx xxxx
         in_port to some unused value, such as OFPP_NONE.)
     - ovs-dpctl:
       * New debugging commands "add-flow", "mod-flow", "del-flow".
+    - In dpif-based bridges, cache action translations, which can improve
+      flow set up performance by 80% with a complicated flow table.
     - New syslog format, prefixed with "ovs|", to be easier to filter.
     - RHEL: Removes the default firewall rule that allowed GRE traffic to
       pass through. Any users that relied on this automatic firewall hole
diff --git a/lib/bond.c b/lib/bond.c
index aca18a2..68ac068 100644
--- a/lib/bond.c
+++ b/lib/bond.c
@@ -128,6 +128,7 @@ static struct bond_entry *lookup_bond_entry(const struct bond *,
 static tag_type bond_get_active_slave_tag(const struct bond *);
 static struct bond_slave *choose_output_slave(const struct bond *,
                                               const struct flow *,
+                                              struct flow_wildcards *,
                                               uint16_t vlan, tag_type *tags);
 static void bond_update_fake_slave_stats(struct bond *);
 
@@ -505,7 +506,7 @@ bond_compose_learning_packet(struct bond *bond,
 
     memset(&flow, 0, sizeof flow);
     memcpy(flow.dl_src, eth_src, ETH_ADDR_LEN);
-    slave = choose_output_slave(bond, &flow, vlan, &tags);
+    slave = choose_output_slave(bond, &flow, NULL, vlan, &tags);
 
     packet = ofpbuf_new(0);
     compose_rarp(packet, eth_src);
@@ -605,12 +606,17 @@ bond_check_admissibility(struct bond *bond, const void *slave_,
  * packet belongs to (so for an access port it will be the access port's VLAN).
  *
  * Adds a tag to '*tags' that associates the flow with the returned slave.
+ *
+ * If 'wc' is non-NULL, bitwise-OR's 'wc' with the set of bits that were
+ * significant in the selection.  At some point earlier, 'wc' should
+ * have been initialized (e.g., by flow_wildcards_init_catchall()).
  */
 void *
 bond_choose_output_slave(struct bond *bond, const struct flow *flow,
-                         uint16_t vlan, tag_type *tags)
+                         struct flow_wildcards *wc, uint16_t vlan,
+                         tag_type *tags)
 {
-    struct bond_slave *slave = choose_output_slave(bond, flow, vlan, tags);
+    struct bond_slave *slave = choose_output_slave(bond, flow, wc, vlan, tags);
     if (slave) {
         *tags |= slave->tag;
         return slave->aux;
@@ -1349,7 +1355,7 @@ lookup_bond_entry(const struct bond *bond, const struct flow *flow,
 
 static struct bond_slave *
 choose_output_slave(const struct bond *bond, const struct flow *flow,
-                    uint16_t vlan, tag_type *tags)
+                    struct flow_wildcards *wc, uint16_t vlan, tag_type *tags)
 {
     struct bond_entry *e;
 
@@ -1368,8 +1374,14 @@ choose_output_slave(const struct bond *bond, const struct flow *flow,
             /* Must have LACP negotiations for TCP balanced bonds. */
             return NULL;
         }
+        if (wc) {
+            flow_mask_hash_fields(wc, NX_HASH_FIELDS_SYMMETRIC_L4);
+        }
         /* Fall Through. */
     case BM_SLB:
+        if (wc) {
+            flow_mask_hash_fields(wc, NX_HASH_FIELDS_ETH_SRC);
+        }
         e = lookup_bond_entry(bond, flow, vlan);
         if (!e->slave || !e->slave->enabled) {
             e->slave = CONTAINER_OF(hmap_random_node(&bond->slaves),
diff --git a/lib/bond.h b/lib/bond.h
index 6190081..306cf42 100644
--- a/lib/bond.h
+++ b/lib/bond.h
@@ -88,8 +88,9 @@ enum bond_verdict {
 enum bond_verdict bond_check_admissibility(struct bond *, const void *slave_,
                                            const uint8_t eth_dst[ETH_ADDR_LEN],
                                            tag_type *);
-void *bond_choose_output_slave(struct bond *,
-                               const struct flow *, uint16_t vlan, tag_type *);
+void *bond_choose_output_slave(struct bond *, const struct flow *,
+                               struct flow_wildcards *, uint16_t vlan,
+                               tag_type *);
 
 /* Rebalancing. */
 void bond_account(struct bond *, const struct flow *, uint16_t vlan,
diff --git a/lib/bundle.c b/lib/bundle.c
index 92ac1e1..b3821e8 100644
--- a/lib/bundle.c
+++ b/lib/bundle.c
@@ -52,12 +52,17 @@ execute_ab(const struct ofpact_bundle *bundle,
 }
 
 static uint16_t
-execute_hrw(const struct ofpact_bundle *bundle, const struct flow *flow,
+execute_hrw(const struct ofpact_bundle *bundle,
+            const struct flow *flow, struct flow_wildcards *wc,
             bool (*slave_enabled)(uint16_t ofp_port, void *aux), void *aux)
 {
     uint32_t flow_hash, best_hash;
     int best, i;
 
+    if (bundle->n_slaves > 1) {
+        flow_mask_hash_fields(wc, bundle->fields);
+    }
+
     flow_hash = flow_hash_fields(flow, bundle->fields, bundle->basis);
     best = -1;
     best_hash = 0;
@@ -76,16 +81,18 @@ execute_hrw(const struct ofpact_bundle *bundle, const struct flow *flow,
     return best >= 0 ? bundle->slaves[best] : OFPP_NONE;
 }
 
-/* Executes 'bundle' on 'flow'.  Uses 'slave_enabled' to determine if the slave
- * designated by 'ofp_port' is up.  Returns the chosen slave, or OFPP_NONE if
- * none of the slaves are acceptable. */
+/* Executes 'bundle' on 'flow'.  Sets fields in 'wc' that were used to
+ * calculate the result.  Uses 'slave_enabled' to determine if the slave
+ * designated by 'ofp_port' is up.  Returns the chosen slave, or
+ * OFPP_NONE if none of the slaves are acceptable. */
 uint16_t
-bundle_execute(const struct ofpact_bundle *bundle, const struct flow *flow,
+bundle_execute(const struct ofpact_bundle *bundle,
+               const struct flow *flow, struct flow_wildcards *wc,
                bool (*slave_enabled)(uint16_t ofp_port, void *aux), void *aux)
 {
     switch (bundle->algorithm) {
     case NX_BD_ALG_HRW:
-        return execute_hrw(bundle, flow, slave_enabled, aux);
+        return execute_hrw(bundle, flow, wc, slave_enabled, aux);
 
     case NX_BD_ALG_ACTIVE_BACKUP:
         return execute_ab(bundle, slave_enabled, aux);
diff --git a/lib/bundle.h b/lib/bundle.h
index 5b6bb67..2619aeb 100644
--- a/lib/bundle.h
+++ b/lib/bundle.h
@@ -27,6 +27,7 @@
 
 struct ds;
 struct flow;
+struct flow_wildcards;
 struct ofpact_bundle;
 struct ofpbuf;
 
@@ -35,6 +36,7 @@ struct ofpbuf;
  * See include/openflow/nicira-ext.h for NXAST_BUNDLE specification. */
 
 uint16_t bundle_execute(const struct ofpact_bundle *, const struct flow *,
+                        struct flow_wildcards *wc,
                         bool (*slave_enabled)(uint16_t ofp_port, void *aux),
                         void *aux);
 enum ofperr bundle_from_openflow(const struct nx_action_bundle *,
diff --git a/lib/flow.c b/lib/flow.c
index bf89fbc..d38e3ab 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -777,6 +777,32 @@ flow_hash_symmetric_l4(const struct flow *flow, uint32_t basis)
     return jhash_bytes(&fields, sizeof fields, basis);
 }
 
+/* Masks the fields in 'wc' that are used by the flow hash 'fields'. */
+void
+flow_mask_hash_fields(struct flow_wildcards *wc, enum nx_hash_fields fields)
+{
+    switch (fields) {
+    case NX_HASH_FIELDS_ETH_SRC:
+        memset(&wc->masks.dl_src, 0xff, sizeof wc->masks.dl_src);
+        break;
+
+    case NX_HASH_FIELDS_SYMMETRIC_L4:
+        memset(&wc->masks.dl_src, 0xff, sizeof wc->masks.dl_src);
+        memset(&wc->masks.dl_dst, 0xff, sizeof wc->masks.dl_dst);
+        memset(&wc->masks.dl_type, 0xff, sizeof wc->masks.dl_type);
+        memset(&wc->masks.vlan_tci, 0xff, sizeof wc->masks.vlan_tci);
+        memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
+        memset(&wc->masks.nw_src, 0xff, sizeof wc->masks.nw_src);
+        memset(&wc->masks.nw_dst, 0xff, sizeof wc->masks.nw_dst);
+        memset(&wc->masks.tp_src, 0xff, sizeof wc->masks.tp_src);
+        memset(&wc->masks.tp_dst, 0xff, sizeof wc->masks.tp_dst);
+        break;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
 /* Hashes the portions of 'flow' designated by 'fields'. */
 uint32_t
 flow_hash_fields(const struct flow *flow, enum nx_hash_fields fields,
diff --git a/lib/flow.h b/lib/flow.h
index ff18dc6..b07b9ed 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -207,6 +207,7 @@ bool flow_wildcards_equal(const struct flow_wildcards *,
                           const struct flow_wildcards *);
 uint32_t flow_hash_symmetric_l4(const struct flow *flow, uint32_t basis);
 
+void flow_mask_hash_fields(struct flow_wildcards *, enum nx_hash_fields);
 uint32_t flow_hash_fields(const struct flow *, enum nx_hash_fields,
                           uint16_t basis);
 const char *flow_hash_fields_to_str(enum nx_hash_fields);
diff --git a/lib/learn.c b/lib/learn.c
index ab403be..606ea9e 100644
--- a/lib/learn.c
+++ b/lib/learn.c
@@ -374,6 +374,22 @@ learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
     fm->ofpacts_len = ofpacts->size;
 }
 
+/* Perform a bitwise-OR on 'wc''s fields that are relevant as sources in
+ * the learn action 'learn'. */
+void
+learn_mask(const struct ofpact_learn *learn, struct flow_wildcards *wc)
+{
+    const struct ofpact_learn_spec *spec;
+    union mf_subvalue value;
+
+    memset(&value, 0xff, sizeof value);
+    for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
+        if (spec->src_type == NX_LEARN_SRC_FIELD) {
+            mf_write_subfield_flow(&spec->src, &value, &wc->masks);
+        }
+    }
+}
+
 static void
 learn_parse_load_immediate(const char *s, struct ofpact_learn_spec *spec)
 {
diff --git a/lib/learn.h b/lib/learn.h
index 8ea8dcc..5bceb6e 100644
--- a/lib/learn.h
+++ b/lib/learn.h
@@ -21,6 +21,7 @@
 
 struct ds;
 struct flow;
+struct flow_wildcards;
 struct ofpbuf;
 struct ofpact_learn;
 struct ofputil_flow_mod;
@@ -38,6 +39,7 @@ void learn_to_nxast(const struct ofpact_learn *, struct ofpbuf *openflow);
 
 void learn_execute(const struct ofpact_learn *, const struct flow *,
                    struct ofputil_flow_mod *, struct ofpbuf *ofpacts);
+void learn_mask(const struct ofpact_learn *, struct flow_wildcards *);
 
 void learn_parse(char *, struct ofpbuf *ofpacts);
 void learn_format(const struct ofpact_learn *, struct ds *);
diff --git a/lib/multipath.c b/lib/multipath.c
index f6a1a0a..dbd5704 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -102,15 +102,18 @@ static uint16_t multipath_algorithm(uint32_t hash, enum nx_mp_algorithm,
                                     unsigned int n_links, unsigned int arg);
 
 /* Executes 'mp' based on the current contents of 'flow', writing the results
- * back into 'flow'. */
+ * back into 'flow'.  Sets fields in 'wc' that were used to calculate
+ * the result. */
 void
-multipath_execute(const struct ofpact_multipath *mp, struct flow *flow)
+multipath_execute(const struct ofpact_multipath *mp, struct flow *flow,
+                  struct flow_wildcards *wc)
 {
     /* Calculate value to store. */
     uint32_t hash = flow_hash_fields(flow, mp->fields, mp->basis);
     uint16_t link = multipath_algorithm(hash, mp->algorithm,
                                         mp->max_link + 1, mp->arg);
 
+    flow_mask_hash_fields(wc, mp->fields);
     nxm_reg_load(&mp->dst, link, flow);
 }
 
diff --git a/lib/multipath.h b/lib/multipath.h
index 1b5160d..97b5161 100644
--- a/lib/multipath.h
+++ b/lib/multipath.h
@@ -22,6 +22,7 @@
 
 struct ds;
 struct flow;
+struct flow_wildcards;
 struct nx_action_multipath;
 struct ofpact_multipath;
 struct ofpbuf;
@@ -38,7 +39,8 @@ enum ofperr multipath_check(const struct ofpact_multipath *,
 void multipath_to_nxast(const struct ofpact_multipath *,
                         struct ofpbuf *openflow);
 
-void multipath_execute(const struct ofpact_multipath *, struct flow *);
+void multipath_execute(const struct ofpact_multipath *, struct flow *,
+                       struct flow_wildcards *);
 
 void multipath_parse(struct ofpact_multipath *, const char *);
 void multipath_format(const struct ofpact_multipath *, struct ds *);
diff --git a/lib/nx-match.c b/lib/nx-match.c
index e111000..ecdaa65 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -1279,11 +1279,15 @@ nxm_reg_load_to_nxast(const struct ofpact_reg_load *load,
 
 void
 nxm_execute_reg_move(const struct ofpact_reg_move *move,
-                     struct flow *flow)
+                     struct flow *flow, struct flow_wildcards *wc)
 {
+    union mf_subvalue mask_value;
     union mf_value src_value;
     union mf_value dst_value;
 
+    memset(&mask_value, 0xff, sizeof mask_value);
+    mf_write_subfield_flow(&move->src, &mask_value, &wc->masks);
+
     mf_get_value(move->dst.field, flow, &dst_value);
     mf_get_value(move->src.field, flow, &src_value);
     bitwise_copy(&src_value, move->src.field->n_bytes, move->src.ofs,
@@ -1428,10 +1432,15 @@ nx_stack_pop(struct ofpbuf *stack)
 
 void
 nxm_execute_stack_push(const struct ofpact_stack *push,
-                       const struct flow *flow, struct ofpbuf *stack)
+                       const struct flow *flow, struct flow_wildcards *wc,
+                       struct ofpbuf *stack)
 {
+    union mf_subvalue mask_value;
     union mf_subvalue dst_value;
 
+    memset(&mask_value, 0xff, sizeof mask_value);
+    mf_write_subfield_flow(&push->subfield, &mask_value, &wc->masks);
+
     mf_read_subfield(&push->subfield, flow, &dst_value);
     nx_stack_push(stack, &dst_value);
 }
diff --git a/lib/nx-match.h b/lib/nx-match.h
index 7d316d8..bb63b90 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -80,7 +80,8 @@ void nxm_reg_move_to_nxast(const struct ofpact_reg_move *,
 void nxm_reg_load_to_nxast(const struct ofpact_reg_load *,
                            struct ofpbuf *openflow);
 
-void nxm_execute_reg_move(const struct ofpact_reg_move *, struct flow *);
+void nxm_execute_reg_move(const struct ofpact_reg_move *, struct flow *,
+                          struct flow_wildcards *);
 void nxm_execute_reg_load(const struct ofpact_reg_load *, struct flow *);
 void nxm_reg_load(const struct mf_subfield *, uint64_t src_data,
                   struct flow *);
@@ -105,7 +106,8 @@ void nxm_stack_pop_to_nxast(const struct ofpact_stack *,
                            struct ofpbuf *openflow);
 
 void nxm_execute_stack_push(const struct ofpact_stack *,
-                            const struct flow *, struct ofpbuf *);
+                            const struct flow *, struct flow_wildcards *,
+                            struct ofpbuf *);
 void nxm_execute_stack_pop(const struct ofpact_stack *,
                             struct flow *, struct ofpbuf *);
 
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 380b735..7442642 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -113,9 +113,11 @@ static struct rule_dpif *rule_dpif_cast(const struct rule *rule)
 }
 
 static struct rule_dpif *rule_dpif_lookup(struct ofproto_dpif *,
-                                          const struct flow *);
+                                          const struct flow *,
+                                          struct flow_wildcards *wc);
 static struct rule_dpif *rule_dpif_lookup__(struct ofproto_dpif *,
                                             const struct flow *,
+                                            struct flow_wildcards *wc,
                                             uint8_t table);
 static struct rule_dpif *rule_dpif_miss_rule(struct ofproto_dpif *ofproto,
                                              const struct flow *flow);
@@ -227,6 +229,15 @@ struct initial_vals {
 };
 
 struct xlate_out {
+    /* Wildcards relevant in translation.  Any fields that were used to
+     * calculate the action must be set for caching and kernel
+     * wildcarding to work.  For example, if the flow lookup involved
+     * performing the "normal" action on IPv4 and ARP packets, 'wc'
+     * would have the 'in_port' (always set), 'dl_type' (flow match),
+     * 'vlan_tci' (normal action), and 'dl_dst' (normal action) fields
+     * set. */
+    struct flow_wildcards wc;
+
     tag_type tags;              /* Tags associated with actions. */
     enum slow_path_reason slow; /* 0 if fast path may be used. */
     bool has_learn;             /* Actions include NXAST_LEARN? */
@@ -360,6 +371,8 @@ static void compose_slow_path(const struct ofproto_dpif *, const struct flow *,
 
 static void xlate_report(struct xlate_ctx *ctx, const char *s);
 
+static void xlate_out_copy(struct xlate_out *dst, const struct xlate_out *src);
+
 /* A subfacet (see "struct subfacet" below) has three possible installation
  * states:
  *
@@ -447,7 +460,9 @@ struct facet {
     long long int used;         /* Time last used; time created if not used. */
 
     /* Key. */
-    struct flow flow;
+    struct flow flow;           /* Flow of the creating subfacet. */
+    struct match match;         /* Match containing a flow with wildcards. */
+    struct cls_rule cr;         /* In 'ofproto_dpif's facets classifier. */
 
     /* These statistics:
      *
@@ -489,14 +504,15 @@ struct facet {
     long long int learn_rl;      /* Rate limiter for facet_learn(). */
 };
 
-static struct facet *facet_create(const struct flow_miss *, uint32_t hash);
+static struct facet *facet_create(const struct flow_miss *, struct rule_dpif *,
+                                  struct xlate_out *,
+                                  struct dpif_flow_stats *);
 static void facet_remove(struct facet *);
 static void facet_free(struct facet *);
 
-static struct facet *facet_find(struct ofproto_dpif *,
-                                const struct flow *, uint32_t hash);
+static struct facet *facet_find(struct ofproto_dpif *, const struct flow *);
 static struct facet *facet_lookup_valid(struct ofproto_dpif *,
-                                        const struct flow *, uint32_t hash);
+                                        const struct flow *);
 static bool facet_revalidate(struct facet *);
 static bool facet_check_consistency(struct facet *);
 
@@ -718,7 +734,7 @@ struct ofproto_dpif {
     bool has_bonded_bundles;
 
     /* Facets. */
-    struct hmap facets;
+    struct classifier facets;     /* Contains 'struct facet's. */
     long long int consistency_rl;
 
     /* Revalidation. */
@@ -775,8 +791,8 @@ static void ofproto_trace(struct ofproto_dpif *, const struct flow *,
                           const struct initial_vals *, struct ds *);
 
 /* Packet processing. */
-static void update_learning_table(struct ofproto_dpif *,
-                                  const struct flow *, int vlan,
+static void update_learning_table(struct ofproto_dpif *, const struct flow *,
+                                  struct flow_wildcards *, int vlan,
                                   struct ofbundle *);
 /* Upcalls. */
 #define FLOW_MISS_MAX_BATCH 50
@@ -1004,12 +1020,14 @@ type_run(const char *type)
 
         HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
             struct facet *facet, *next;
+            struct cls_cursor cursor;
 
             if (ofproto->backer != backer) {
                 continue;
             }
 
-            HMAP_FOR_EACH_SAFE (facet, next, hmap_node, &ofproto->facets) {
+            cls_cursor_init(&cursor, &ofproto->facets, NULL);
+            CLS_CURSOR_FOR_EACH_SAFE (facet, next, cr, &cursor) {
                 if (need_revalidate
                     || tag_set_intersects(&revalidate_set, facet->xout.tags)) {
                     facet_revalidate(facet);
@@ -1401,7 +1419,7 @@ construct(struct ofproto *ofproto_)
     }
     ofproto->has_bonded_bundles = false;
 
-    hmap_init(&ofproto->facets);
+    classifier_init(&ofproto->facets);
     ofproto->consistency_rl = LLONG_MIN;
 
     for (i = 0; i < N_TABLES; i++) {
@@ -1487,7 +1505,7 @@ add_internal_flow(struct ofproto_dpif *ofproto, int id,
         return error;
     }
 
-    *rulep = rule_dpif_lookup__(ofproto, &fm.match.flow, TBL_INTERNAL);
+    *rulep = rule_dpif_lookup__(ofproto, &fm.match.flow, NULL, TBL_INTERNAL);
     ovs_assert(*rulep != NULL);
 
     return 0;
@@ -1569,7 +1587,7 @@ destruct(struct ofproto *ofproto_)
     hmap_destroy(&ofproto->bundles);
     mac_learning_destroy(ofproto->ml);
 
-    hmap_destroy(&ofproto->facets);
+    classifier_destroy(&ofproto->facets);
 
     hmap_destroy(&ofproto->vlandev_map);
     hmap_destroy(&ofproto->realdev_vid_map);
@@ -1644,14 +1662,20 @@ run(struct ofproto *ofproto_)
 
     /* Check the consistency of a random facet, to aid debugging. */
     if (time_msec() >= ofproto->consistency_rl
-        && !hmap_is_empty(&ofproto->facets)
+        && !classifier_is_empty(&ofproto->facets)
         && !ofproto->backer->need_revalidate) {
+        struct cls_table *table;
+        struct cls_rule *cr;
         struct facet *facet;
 
         ofproto->consistency_rl = time_msec() + 250;
 
-        facet = CONTAINER_OF(hmap_random_node(&ofproto->facets),
-                             struct facet, hmap_node);
+        table = CONTAINER_OF(hmap_random_node(&ofproto->facets.tables),
+                             struct cls_table, hmap_node);
+        cr = CONTAINER_OF(hmap_random_node(&table->rules), struct cls_rule,
+                          hmap_node);
+        facet = CONTAINER_OF(cr, struct facet, cr);
+
         if (!tag_set_intersects(&ofproto->backer->revalidate_set,
                                 facet->xout.tags)) {
             if (!facet_check_consistency(facet)) {
@@ -1708,11 +1732,14 @@ static void
 get_memory_usage(const struct ofproto *ofproto_, struct simap *usage)
 {
     const struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+    struct cls_cursor cursor;
     size_t n_subfacets = 0;
     struct facet *facet;
 
-    simap_increase(usage, "facets", hmap_count(&ofproto->facets));
-    HMAP_FOR_EACH (facet, hmap_node, &ofproto->facets) {
+    simap_increase(usage, "facets", classifier_count(&ofproto->facets));
+
+    cls_cursor_init(&cursor, &ofproto->facets, NULL);
+    CLS_CURSOR_FOR_EACH (facet, cr, &cursor) {
         n_subfacets += list_size(&facet->subfacets);
     }
     simap_increase(usage, "subfacets", n_subfacets);
@@ -3676,15 +3703,17 @@ handle_flow_miss_common(struct rule_dpif *rule,
 }
 
 /* Figures out whether a flow that missed in 'ofproto', whose details are in
- * 'miss', is likely to be worth tracking in detail in userspace and (usually)
- * installing a datapath flow.  The answer is usually "yes" (a return value of
- * true).  However, for short flows the cost of bookkeeping is much higher than
- * the benefits, so when the datapath holds a large number of flows we impose
- * some heuristics to decide which flows are likely to be worth tracking. */
+ * 'miss' masked by 'wc', is likely to be worth tracking in detail in userspace
+ * and (usually) installing a datapath flow.  The answer is usually "yes" (a
+ * return value of true).  However, for short flows the cost of bookkeeping is
+ * much higher than the benefits, so when the datapath holds a large number of
+ * flows we impose some heuristics to decide which flows are likely to be worth
+ * tracking. */
 static bool
-flow_miss_should_make_facet(struct flow_miss *miss, uint32_t hash)
+flow_miss_should_make_facet(struct flow_miss *miss, struct flow_wildcards *wc)
 {
     struct dpif_backer *backer = miss->ofproto->backer;
+    uint32_t hash;
 
     if (!backer->governor) {
         size_t n_subfacets;
@@ -3697,49 +3726,46 @@ flow_miss_should_make_facet(struct flow_miss *miss, uint32_t hash)
         backer->governor = governor_create();
     }
 
+    hash = flow_hash_in_wildcards(&miss->flow, wc, 0);
     return governor_should_install_flow(backer->governor, hash,
                                         list_size(&miss->packets));
 }
 
-/* Handles 'miss', which matches 'rule', without creating a facet or subfacet
- * or creating any datapath flow.  May add an "execute" operation to 'ops' and
- * increment '*n_ops'. */
+/* Handles 'miss' without creating a facet or subfacet or creating any datapath
+ * flow.  'miss->flow' must have matched 'rule' and been xlated into 'xout'.
+ * May add an "execute" operation to 'ops' and increment '*n_ops'. */
 static void
-handle_flow_miss_without_facet(struct flow_miss *miss,
+handle_flow_miss_without_facet(struct rule_dpif *rule, struct xlate_out *xout,
+                               struct flow_miss *miss,
                                struct flow_miss_op *ops, size_t *n_ops)
 {
-    struct rule_dpif *rule = rule_dpif_lookup(miss->ofproto, &miss->flow);
-    long long int now = time_msec();
     struct ofpbuf *packet;
-    struct xlate_in xin;
 
     LIST_FOR_EACH (packet, list_node, &miss->packets) {
         struct flow_miss_op *op = &ops[*n_ops];
-        struct dpif_flow_stats stats;
 
         COVERAGE_INC(facet_suppress);
 
         handle_flow_miss_common(rule, packet, &miss->flow);
 
-        dpif_flow_stats_extract(&miss->flow, packet, now, &stats);
-        rule_credit_stats(rule, &stats);
+        if (xout->slow) {
+            struct xlate_in xin;
 
-        xlate_in_init(&xin, miss->ofproto, &miss->flow, &miss->initial_vals,
-                      rule, stats.tcp_flags, packet);
-        xin.resubmit_stats = &stats;
-        xlate_actions(&xin, &op->xout);
+            xlate_in_init(&xin, miss->ofproto, &miss->flow,
+                          &miss->initial_vals, rule, 0, packet);
+            xlate_actions_for_side_effects(&xin);
+        }
 
-        if (op->xout.odp_actions.size) {
+        if (xout->odp_actions.size) {
             struct dpif_execute *execute = &op->dpif_op.u.execute;
 
             init_flow_miss_execute_op(miss, packet, op);
+            xlate_out_copy(&op->xout, xout);
             execute->actions = op->xout.odp_actions.data;
             execute->actions_len = op->xout.odp_actions.size;
             op->xout_garbage = true;
 
             (*n_ops)++;
-        } else {
-            xlate_out_uninit(&op->xout);
         }
     }
 }
@@ -3752,10 +3778,12 @@ handle_flow_miss_without_facet(struct flow_miss *miss,
  * here, then the new subfacet or its packets could look (occasionally) as
  * though it was used some time after the facet was used.  That can make a
  * one-packet flow look like it has a nonzero duration, which looks odd in
- * e.g. NetFlow statistics. */
+ * e.g. NetFlow statistics.
+ *
+ * If non-null, 'stats' will be folded into 'facet'. */
 static void
 handle_flow_miss_with_facet(struct flow_miss *miss, struct facet *facet,
-                            long long int now,
+                            long long int now, struct dpif_flow_stats *stats,
                             struct flow_miss_op *ops, size_t *n_ops)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(facet->rule->up.ofproto);
@@ -3764,11 +3792,13 @@ handle_flow_miss_with_facet(struct flow_miss *miss, struct facet *facet,
     struct ofpbuf *packet;
 
     subfacet = subfacet_create(facet, miss, now);
-    want_path = subfacet->facet->xout.slow ? SF_SLOW_PATH : SF_FAST_PATH;
+    want_path = facet->xout.slow ? SF_SLOW_PATH : SF_FAST_PATH;
+    if (stats) {
+        subfacet_update_stats(subfacet, stats);
+    }
 
     LIST_FOR_EACH (packet, list_node, &miss->packets) {
         struct flow_miss_op *op = &ops[*n_ops];
-        struct dpif_flow_stats stats;
 
         handle_flow_miss_common(facet->rule, packet, &miss->flow);
 
@@ -3780,9 +3810,6 @@ handle_flow_miss_with_facet(struct flow_miss *miss, struct facet *facet,
             xlate_actions_for_side_effects(&xin);
         }
 
-        dpif_flow_stats_extract(&facet->flow, packet, now, &stats);
-        subfacet_update_stats(subfacet, &stats);
-
         if (facet->xout.odp_actions.size) {
             struct dpif_execute *execute = &op->dpif_op.u.execute;
 
@@ -3823,33 +3850,54 @@ handle_flow_miss(struct flow_miss *miss, struct flow_miss_op *ops,
                  size_t *n_ops)
 {
     struct ofproto_dpif *ofproto = miss->ofproto;
+    struct dpif_flow_stats stats__;
+    struct dpif_flow_stats *stats = &stats__;
+    struct ofpbuf *packet;
     struct facet *facet;
     long long int now;
-    uint32_t hash;
 
-    /* The caller must ensure that miss->hmap_node.hash contains
-     * flow_hash(miss->flow, 0). */
-    hash = miss->hmap_node.hash;
+    now = time_msec();
+    memset(stats, 0, sizeof *stats);
+    stats->used = now;
+    LIST_FOR_EACH (packet, list_node, &miss->packets) {
+        stats->tcp_flags |= packet_get_tcp_flags(packet, &miss->flow);
+        stats->n_bytes += packet->size;
+        stats->n_packets++;
+    }
 
-    facet = facet_lookup_valid(ofproto, &miss->flow, hash);
+    facet = facet_lookup_valid(ofproto, &miss->flow);
     if (!facet) {
+        struct flow_wildcards wc;
+        struct rule_dpif *rule;
+        struct xlate_out xout;
+        struct xlate_in xin;
+
+        flow_wildcards_init_catchall(&wc);
+        rule = rule_dpif_lookup(ofproto, &miss->flow, &wc);
+        rule_credit_stats(rule, stats);
+
+        xlate_in_init(&xin, ofproto, &miss->flow, &miss->initial_vals, rule,
+                      stats->tcp_flags, NULL);
+        xin.resubmit_stats = stats;
+        xin.may_learn = true;
+        xlate_actions(&xin, &xout);
+        flow_wildcards_or(&xout.wc, &xout.wc, &wc);
+
         /* There does not exist a bijection between 'struct flow' and datapath
          * flow keys with fitness ODP_FIT_TO_LITTLE.  This breaks a fundamental
          * assumption used throughout the facet and subfacet handling code.
          * Since we have to handle these misses in userspace anyway, we simply
          * skip facet creation, avoiding the problem altogether. */
         if (miss->key_fitness == ODP_FIT_TOO_LITTLE
-            || !flow_miss_should_make_facet(miss, hash)) {
-            handle_flow_miss_without_facet(miss, ops, n_ops);
+            || !flow_miss_should_make_facet(miss, &xout.wc)) {
+            handle_flow_miss_without_facet(rule, &xout, miss, ops, n_ops);
             return;
         }
 
-        facet = facet_create(miss, hash);
-        now = facet->used;
-    } else {
-        now = time_msec();
+        facet = facet_create(miss, rule, &xout, stats);
+        stats = NULL;
     }
-    handle_flow_miss_with_facet(miss, facet, now, ops, n_ops);
+    handle_flow_miss_with_facet(miss, facet, now, stats, ops, n_ops);
 }
 
 static struct drop_key *
@@ -4651,34 +4699,40 @@ rule_expire(struct rule_dpif *rule)
  * The caller must already have determined that no facet with an identical
  * 'miss->flow' exists in 'miss->ofproto'.
  *
- * 'hash' must be the return value of flow_hash(miss->flow, 0).
+ * 'rule' and 'xout' must have been created based on 'miss'.
+ *
+ * 'facet'' statistics are initialized based on 'stats'.
  *
  * The facet will initially have no subfacets.  The caller should create (at
  * least) one subfacet with subfacet_create(). */
 static struct facet *
-facet_create(const struct flow_miss *miss, uint32_t hash)
+facet_create(const struct flow_miss *miss, struct rule_dpif *rule,
+             struct xlate_out *xout, struct dpif_flow_stats *stats)
 {
     struct ofproto_dpif *ofproto = miss->ofproto;
-    struct xlate_in xin;
     struct facet *facet;
 
     facet = xzalloc(sizeof *facet);
-    facet->used = time_msec();
+    facet->packet_count = facet->prev_packet_count = stats->n_packets;
+    facet->byte_count = facet->prev_byte_count = stats->n_bytes;
+    facet->tcp_flags = stats->tcp_flags;
+    facet->used = stats->used;
     facet->flow = miss->flow;
     facet->initial_vals = miss->initial_vals;
-    facet->rule = rule_dpif_lookup(ofproto, &facet->flow);
     facet->learn_rl = time_msec() + 500;
+    facet->rule = rule;
 
-    hmap_insert(&ofproto->facets, &facet->hmap_node, hash);
     list_push_back(&facet->rule->facets, &facet->list_node);
     list_init(&facet->subfacets);
     netflow_flow_init(&facet->nf_flow);
     netflow_flow_update_time(ofproto->netflow, &facet->nf_flow, facet->used);
 
-    xlate_in_init(&xin, ofproto, &facet->flow, &facet->initial_vals,
-                  facet->rule, 0, NULL);
-    xin.may_learn = true;
-    xlate_actions(&xin, &facet->xout);
+    xlate_out_copy(&facet->xout, xout);
+
+    match_init(&facet->match, &facet->flow, &facet->xout.wc);
+    cls_rule_init(&facet->cr, &facet->match, OFP_DEFAULT_PRIORITY);
+    classifier_insert(&ofproto->facets, &facet->cr);
+
     facet->nf_flow.output_iface = facet->xout.nf_output_iface;
 
     return facet;
@@ -4713,7 +4767,7 @@ execute_odp_actions(struct ofproto_dpif *ofproto, const struct flow *flow,
     return !error;
 }
 
-/* Remove 'facet' from 'ofproto' and free up the associated memory:
+/* Remove 'facet' from its ofproto and free up the associated memory:
  *
  *   - If 'facet' was installed in the datapath, uninstalls it and updates its
  *     rule's statistics, via subfacet_uninstall().
@@ -4745,7 +4799,8 @@ facet_remove(struct facet *facet)
                         &facet->subfacets) {
         subfacet_destroy__(subfacet);
     }
-    hmap_remove(&ofproto->facets, &facet->hmap_node);
+    classifier_remove(&ofproto->facets, &facet->cr);
+    cls_rule_destroy(&facet->cr);
     list_remove(&facet->list_node);
     facet_free(facet);
 }
@@ -4880,41 +4935,28 @@ facet_flush_stats(struct facet *facet)
     facet->tcp_flags = 0;
 }
 
-/* Searches 'ofproto''s table of facets for one exactly equal to 'flow'.
- * Returns it if found, otherwise a null pointer.
- *
- * 'hash' must be the return value of flow_hash(flow, 0).
+/* Searches 'ofproto''s table of facets for one which would be responsible for
+ * 'flow'.  Returns it if found, otherwise a null pointer.
  *
  * The returned facet might need revalidation; use facet_lookup_valid()
  * instead if that is important. */
 static struct facet *
-facet_find(struct ofproto_dpif *ofproto,
-           const struct flow *flow, uint32_t hash)
+facet_find(struct ofproto_dpif *ofproto, const struct flow *flow)
 {
-    struct facet *facet;
-
-    HMAP_FOR_EACH_WITH_HASH (facet, hmap_node, hash, &ofproto->facets) {
-        if (flow_equal(flow, &facet->flow)) {
-            return facet;
-        }
-    }
-
-    return NULL;
+    struct cls_rule *cr = classifier_lookup(&ofproto->facets, flow, NULL);
+    return cr ? CONTAINER_OF(cr, struct facet, cr) : NULL;
 }
 
 /* Searches 'ofproto''s table of facets for one exactly equal to 'flow'.
  * Returns it if found, otherwise a null pointer.
  *
- * 'hash' must be the return value of flow_hash(flow, 0).
- *
  * The returned facet is guaranteed to be valid. */
 static struct facet *
-facet_lookup_valid(struct ofproto_dpif *ofproto, const struct flow *flow,
-                   uint32_t hash)
+facet_lookup_valid(struct ofproto_dpif *ofproto, const struct flow *flow)
 {
     struct facet *facet;
 
-    facet = facet_find(ofproto, flow, hash);
+    facet = facet_find(ofproto, flow);
     if (facet
         && (ofproto->backer->need_revalidate
             || tag_set_intersects(&ofproto->backer->revalidate_set,
@@ -4940,7 +4982,7 @@ facet_check_consistency(struct facet *facet)
     bool ok;
 
     /* Check the rule for consistency. */
-    rule = rule_dpif_lookup(ofproto, &facet->flow);
+    rule = rule_dpif_lookup(ofproto, &facet->flow, NULL);
     if (rule != facet->rule) {
         if (!VLOG_DROP_WARN(&rl)) {
             struct ds s = DS_EMPTY_INITIALIZER;
@@ -5013,6 +5055,7 @@ facet_revalidate(struct facet *facet)
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(facet->rule->up.ofproto);
     struct rule_dpif *new_rule;
     struct subfacet *subfacet;
+    struct flow_wildcards wc;
     struct xlate_out xout;
     struct xlate_in xin;
 
@@ -5037,7 +5080,8 @@ facet_revalidate(struct facet *facet)
         }
     }
 
-    new_rule = rule_dpif_lookup(ofproto, &facet->flow);
+    flow_wildcards_init_catchall(&wc);
+    new_rule = rule_dpif_lookup(ofproto, &facet->flow, &wc);
 
     /* Calculate new datapath actions.
      *
@@ -5047,11 +5091,18 @@ facet_revalidate(struct facet *facet)
     xlate_in_init(&xin, ofproto, &facet->flow, &facet->initial_vals, new_rule,
                   0, NULL);
     xlate_actions(&xin, &xout);
+    flow_wildcards_or(&xout.wc, &xout.wc, &wc);
 
     /* A facet's slow path reason should only change under dramatic
      * circumstances.  Rather than try to update everything, it's simpler to
-     * remove the facet and start over. */
-    if (facet->xout.slow != xout.slow) {
+     * remove the facet and start over.
+     *
+     * More importantly, if a facet's wildcards change, it will be relatively
+     * difficult to figure out if its subfacets still belong to it, and if not
+     * which facet they may belong to.  Again, to avoid the complexity, we
+     * simply give up instead. */
+    if (facet->xout.slow != xout.slow
+        || memcmp(&facet->xout.wc, &xout.wc, sizeof xout.wc)) {
         facet_remove(facet);
         xlate_out_uninit(&xout);
         return false;
@@ -5163,9 +5214,11 @@ push_all_stats__(bool run_fast)
     }
 
     HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
+        struct cls_cursor cursor;
         struct facet *facet;
 
-        HMAP_FOR_EACH (facet, hmap_node, &ofproto->facets) {
+        cls_cursor_init(&cursor, &ofproto->facets, NULL);
+        CLS_CURSOR_FOR_EACH (facet, cr, &cursor) {
             facet_push_stats(facet, false);
             if (run_fast) {
                 run_fast_rl();
@@ -5431,12 +5484,15 @@ subfacet_update_stats(struct subfacet *subfacet,
 
 /* Rules. */
 
+/* Lookup 'flow' in 'ofproto''s classifier.  If 'wc' is non-null, sets
+ * the fields that were relevant as part of the lookup. */
 static struct rule_dpif *
-rule_dpif_lookup(struct ofproto_dpif *ofproto, const struct flow *flow)
+rule_dpif_lookup(struct ofproto_dpif *ofproto, const struct flow *flow,
+                 struct flow_wildcards *wc)
 {
     struct rule_dpif *rule;
 
-    rule = rule_dpif_lookup__(ofproto, flow, 0);
+    rule = rule_dpif_lookup__(ofproto, flow, wc, 0);
     if (rule) {
         return rule;
     }
@@ -5446,7 +5502,7 @@ rule_dpif_lookup(struct ofproto_dpif *ofproto, const struct flow *flow)
 
 static struct rule_dpif *
 rule_dpif_lookup__(struct ofproto_dpif *ofproto, const struct flow *flow,
-                   uint8_t table_id)
+                   struct flow_wildcards *wc, uint8_t table_id)
 {
     struct cls_rule *cls_rule;
     struct classifier *cls;
@@ -5463,11 +5519,14 @@ rule_dpif_lookup__(struct ofproto_dpif *ofproto, const struct flow *flow,
         struct flow ofpc_normal_flow = *flow;
         ofpc_normal_flow.tp_src = htons(0);
         ofpc_normal_flow.tp_dst = htons(0);
-        cls_rule = classifier_lookup(cls, &ofpc_normal_flow, NULL);
+        cls_rule = classifier_lookup(cls, &ofpc_normal_flow, wc);
     } else if (frag && ofproto->up.frag_handling == OFPC_FRAG_DROP) {
         cls_rule = &ofproto->drop_frags_rule->up.cr;
+        if (wc) {
+            flow_wildcards_init_exact(wc);
+        }
     } else {
-        cls_rule = classifier_lookup(cls, flow, NULL);
+        cls_rule = classifier_lookup(cls, flow, wc);
     }
     return rule_dpif_cast(rule_from_cls_rule(cls_rule));
 }
@@ -6113,7 +6172,8 @@ xlate_table_action(struct xlate_ctx *ctx,
 
         /* Look up a flow with 'in_port' as the input port. */
         ctx->xin->flow.in_port = in_port;
-        rule = rule_dpif_lookup__(ctx->ofproto, &ctx->xin->flow, table_id);
+        rule = rule_dpif_lookup__(ctx->ofproto, &ctx->xin->flow,
+                                  &ctx->xout->wc, table_id);
 
         tag_the_flow(ctx, rule);
 
@@ -6231,6 +6291,13 @@ execute_mpls_push_action(struct xlate_ctx *ctx, ovs_be16 eth_type)
 {
     ovs_assert(eth_type_mpls(eth_type));
 
+    memset(&ctx->xout->wc.masks.dl_type, 0xff,
+               sizeof ctx->xout->wc.masks.dl_type);
+    memset(&ctx->xout->wc.masks.mpls_lse, 0xff,
+               sizeof ctx->xout->wc.masks.mpls_lse);
+    memset(&ctx->xout->wc.masks.mpls_depth, 0xff,
+               sizeof ctx->xout->wc.masks.mpls_depth);
+
     if (ctx->base_flow.mpls_depth) {
         ctx->xin->flow.mpls_lse &= ~htonl(MPLS_BOS_MASK);
         ctx->xin->flow.mpls_depth++;
@@ -6257,6 +6324,13 @@ execute_mpls_pop_action(struct xlate_ctx *ctx, ovs_be16 eth_type)
     ovs_assert(eth_type_mpls(ctx->xin->flow.dl_type));
     ovs_assert(!eth_type_mpls(eth_type));
 
+    memset(&ctx->xout->wc.masks.dl_type, 0xff,
+               sizeof ctx->xout->wc.masks.dl_type);
+    memset(&ctx->xout->wc.masks.mpls_lse, 0xff,
+               sizeof ctx->xout->wc.masks.mpls_lse);
+    memset(&ctx->xout->wc.masks.mpls_depth, 0xff,
+               sizeof ctx->xout->wc.masks.mpls_depth);
+
     if (ctx->xin->flow.mpls_depth) {
         ctx->xin->flow.mpls_depth--;
         ctx->xin->flow.mpls_lse = htonl(0);
@@ -6377,6 +6451,10 @@ xlate_output_reg_action(struct xlate_ctx *ctx,
 {
     uint64_t port = mf_get_subfield(&or->src, &ctx->xin->flow);
     if (port <= UINT16_MAX) {
+        union mf_subvalue value;
+
+        memset(&value, 0xff, sizeof value);
+        mf_write_subfield_flow(&or->src, &value, &ctx->xout->wc.masks);
         xlate_output_action(ctx, port, or->max_len, false);
     }
 }
@@ -6462,8 +6540,8 @@ xlate_bundle_action(struct xlate_ctx *ctx,
 {
     uint16_t port;
 
-    port = bundle_execute(bundle, &ctx->xin->flow, slave_enabled_cb,
-                          ctx->ofproto);
+    port = bundle_execute(bundle, &ctx->xin->flow, &ctx->xout->wc,
+                          slave_enabled_cb, ctx->ofproto);
     if (bundle->dst.field) {
         nxm_reg_load(&bundle->dst, port, &ctx->xin->flow);
     } else {
@@ -6481,6 +6559,14 @@ xlate_learn_action(struct xlate_ctx *ctx,
     struct ofpbuf ofpacts;
     int error;
 
+    ctx->xout->has_learn = true;
+
+    learn_mask(learn, &ctx->xout->wc);
+
+    if (!ctx->xin->may_learn) {
+        return;
+    }
+
     ofpbuf_use_stack(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
     learn_execute(learn, &ctx->xin->flow, &fm, &ofpacts);
 
@@ -6646,12 +6732,16 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_SET_IPV4_SRC:
+            memset(&ctx->xout->wc.masks.dl_type, 0xff,
+                   sizeof ctx->xout->wc.masks.dl_type);
             if (ctx->xin->flow.dl_type == htons(ETH_TYPE_IP)) {
                 ctx->xin->flow.nw_src = ofpact_get_SET_IPV4_SRC(a)->ipv4;
             }
             break;
 
         case OFPACT_SET_IPV4_DST:
+            memset(&ctx->xout->wc.masks.dl_type, 0xff,
+                   sizeof ctx->xout->wc.masks.dl_type);
             if (ctx->xin->flow.dl_type == htons(ETH_TYPE_IP)) {
                 ctx->xin->flow.nw_dst = ofpact_get_SET_IPV4_DST(a)->ipv4;
             }
@@ -6659,6 +6749,8 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
 
         case OFPACT_SET_IPV4_DSCP:
             /* OpenFlow 1.0 only supports IPv4. */
+            memset(&ctx->xout->wc.masks.dl_type, 0xff,
+                   sizeof ctx->xout->wc.masks.dl_type);
             if (ctx->xin->flow.dl_type == htons(ETH_TYPE_IP)) {
                 ctx->xin->flow.nw_tos &= ~IP_DSCP_MASK;
                 ctx->xin->flow.nw_tos |= ofpact_get_SET_IPV4_DSCP(a)->dscp;
@@ -6666,6 +6758,10 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_SET_L4_SRC_PORT:
+            memset(&ctx->xout->wc.masks.dl_type, 0xff,
+                   sizeof ctx->xout->wc.masks.dl_type);
+            memset(&ctx->xout->wc.masks.nw_proto, 0xff,
+                    sizeof ctx->xout->wc.masks.nw_proto);
             if (is_ip_any(&ctx->xin->flow)) {
                 ctx->xin->flow.tp_src =
                     htons(ofpact_get_SET_L4_SRC_PORT(a)->port);
@@ -6673,6 +6769,10 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_SET_L4_DST_PORT:
+            memset(&ctx->xout->wc.masks.dl_type, 0xff,
+                   sizeof ctx->xout->wc.masks.dl_type);
+            memset(&ctx->xout->wc.masks.nw_proto, 0xff,
+                    sizeof ctx->xout->wc.masks.nw_proto);
             if (is_ip_any(&ctx->xin->flow)) {
                 ctx->xin->flow.tp_dst =
                     htons(ofpact_get_SET_L4_DST_PORT(a)->port);
@@ -6693,11 +6793,15 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_POP_QUEUE:
+            memset(&ctx->xout->wc.masks.skb_priority, 0xff,
+                   sizeof ctx->xout->wc.masks.skb_priority);
+
             ctx->xin->flow.skb_priority = ctx->orig_skb_priority;
             break;
 
         case OFPACT_REG_MOVE:
-            nxm_execute_reg_move(ofpact_get_REG_MOVE(a), &ctx->xin->flow);
+            nxm_execute_reg_move(ofpact_get_REG_MOVE(a), &ctx->xin->flow,
+                                 &ctx->xout->wc);
             break;
 
         case OFPACT_REG_LOAD:
@@ -6706,7 +6810,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
 
         case OFPACT_STACK_PUSH:
             nxm_execute_stack_push(ofpact_get_STACK_PUSH(a), &ctx->xin->flow,
-                                   &ctx->stack);
+                                   &ctx->xout->wc, &ctx->stack);
             break;
 
         case OFPACT_STACK_POP:
@@ -6736,6 +6840,8 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_DEC_TTL:
+            memset(&ctx->xout->wc.masks.dl_type, 0xff,
+                   sizeof ctx->xout->wc.masks.dl_type);
             if (compose_dec_ttl(ctx, ofpact_get_DEC_TTL(a))) {
                 goto out;
             }
@@ -6746,7 +6852,8 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_MULTIPATH:
-            multipath_execute(ofpact_get_MULTIPATH(a), &ctx->xin->flow);
+            multipath_execute(ofpact_get_MULTIPATH(a), &ctx->xin->flow,
+                              &ctx->xout->wc);
             break;
 
         case OFPACT_BUNDLE:
@@ -6759,10 +6866,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_LEARN:
-            ctx->xout->has_learn = true;
-            if (ctx->xin->may_learn) {
-                xlate_learn_action(ctx, ofpact_get_LEARN(a));
-            }
+            xlate_learn_action(ctx, ofpact_get_LEARN(a));
             break;
 
         case OFPACT_EXIT:
@@ -6770,6 +6874,10 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_FIN_TIMEOUT:
+            memset(&ctx->xout->wc.masks.dl_type, 0xff,
+                   sizeof ctx->xout->wc.masks.dl_type);
+            memset(&ctx->xout->wc.masks.nw_proto, 0xff,
+                   sizeof ctx->xout->wc.masks.nw_proto);
             ctx->xout->has_fin_timeout = true;
             xlate_fin_timeout(ctx, ofpact_get_FIN_TIMEOUT(a));
             break;
@@ -6798,7 +6906,8 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             ctx->table_id = ogt->table_id;
 
             /* Look up a flow from the new table. */
-            rule = rule_dpif_lookup__(ctx->ofproto, &ctx->xin->flow, ctx->table_id);
+            rule = rule_dpif_lookup__(ctx->ofproto, &ctx->xin->flow,
+                                      &ctx->xout->wc, ctx->table_id);
 
             tag_the_flow(ctx, rule);
 
@@ -6917,6 +7026,37 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
     memset(&ctx.base_flow.tunnel, 0, sizeof ctx.base_flow.tunnel);
     ctx.orig_tunnel_ip_dst = ctx.xin->flow.tunnel.ip_dst;
 
+    flow_wildcards_init_catchall(&ctx.xout->wc);
+    memset(&ctx.xout->wc.masks.in_port, 0xff,
+           sizeof ctx.xout->wc.masks.in_port);
+
+    if (tnl_port_should_receive(&ctx.xin->flow)) {
+        memset(&ctx.xout->wc.masks.tunnel, 0xff,
+               sizeof ctx.xout->wc.masks.tunnel);
+    }
+
+    /* Disable most wildcarding for NetFlow. */
+    if (xin->ofproto->netflow) {
+        memset(&ctx.xout->wc.masks.dl_src, 0xff,
+               sizeof ctx.xout->wc.masks.dl_src);
+        memset(&ctx.xout->wc.masks.dl_dst, 0xff,
+               sizeof ctx.xout->wc.masks.dl_dst);
+        memset(&ctx.xout->wc.masks.dl_type, 0xff,
+               sizeof ctx.xout->wc.masks.dl_type);
+        memset(&ctx.xout->wc.masks.vlan_tci, 0xff,
+               sizeof ctx.xout->wc.masks.vlan_tci);
+        memset(&ctx.xout->wc.masks.nw_proto, 0xff,
+               sizeof ctx.xout->wc.masks.nw_proto);
+        memset(&ctx.xout->wc.masks.nw_src, 0xff,
+               sizeof ctx.xout->wc.masks.nw_src);
+        memset(&ctx.xout->wc.masks.nw_dst, 0xff,
+               sizeof ctx.xout->wc.masks.nw_dst);
+        memset(&ctx.xout->wc.masks.tp_src, 0xff,
+               sizeof ctx.xout->wc.masks.tp_src);
+        memset(&ctx.xout->wc.masks.tp_dst, 0xff,
+               sizeof ctx.xout->wc.masks.tp_dst);
+    }
+
     ctx.xout->tags = 0;
     ctx.xout->slow = 0;
     ctx.xout->has_learn = false;
@@ -7033,6 +7173,12 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
     }
 
     ofpbuf_uninit(&ctx.stack);
+
+    /* Clear the metadata and register wildcard masks, because we won't
+     * use non-header fields as part of the cache. */
+    memset(&ctx.xout->wc.masks.metadata, 0,
+           sizeof ctx.xout->wc.masks.metadata);
+    memset(&ctx.xout->wc.masks.regs, 0, sizeof ctx.xout->wc.masks.regs);
 }
 
 /* Translates the 'ofpacts_len' bytes of "struct ofpact"s starting at 'ofpacts'
@@ -7053,6 +7199,24 @@ xlate_report(struct xlate_ctx *ctx, const char *s)
         ctx->xin->report_hook(ctx, s);
     }
 }
+
+static void
+xlate_out_copy(struct xlate_out *dst, const struct xlate_out *src)
+{
+    dst->wc = src->wc;
+    dst->tags = src->tags;
+    dst->slow = src->slow;
+    dst->has_learn = src->has_learn;
+    dst->has_normal = src->has_normal;
+    dst->has_fin_timeout = src->has_fin_timeout;
+    dst->nf_output_iface = src->nf_output_iface;
+    dst->mirrors = src->mirrors;
+
+    ofpbuf_use_stub(&dst->odp_actions, dst->odp_actions_stub,
+                    sizeof dst->odp_actions_stub);
+    ofpbuf_put(&dst->odp_actions, src->odp_actions.data,
+               src->odp_actions.size);
+}
 
 /* OFPP_NORMAL implementation. */
 
@@ -7180,7 +7344,7 @@ output_normal(struct xlate_ctx *ctx, const struct ofbundle *out_bundle,
         port = ofbundle_get_a_port(out_bundle);
     } else {
         port = bond_choose_output_slave(out_bundle->bond, &ctx->xin->flow,
-                                        vid, &ctx->xout->tags);
+                                        &ctx->xout->wc, vid, &ctx->xout->tags);
         if (!port) {
             /* No slaves enabled, so drop packet. */
             return;
@@ -7300,6 +7464,10 @@ add_mirror_actions(struct xlate_ctx *ctx, const struct flow *orig_flow)
 
         m = ofproto->mirrors[mirror_mask_ffs(mirrors) - 1];
 
+        if (m->vlans) {
+            ctx->xout->wc.masks.vlan_tci |= htons(VLAN_CFI | VLAN_VID_MASK);
+        }
+
         if (!vlan_is_mirrored(m, vlan)) {
             mirrors = zero_rightmost_1bit(mirrors);
             continue;
@@ -7355,19 +7523,35 @@ update_mirror_stats(struct ofproto_dpif *ofproto, mirror_mask_t mirrors,
  * migration.  Older Citrix-patched Linux DomU used gratuitous ARP replies to
  * indicate this; newer upstream kernels use gratuitous ARP requests. */
 static bool
-is_gratuitous_arp(const struct flow *flow)
+is_gratuitous_arp(const struct flow *flow, struct flow_wildcards *wc)
 {
-    return (flow->dl_type == htons(ETH_TYPE_ARP)
-            && eth_addr_is_broadcast(flow->dl_dst)
-            && (flow->nw_proto == ARP_OP_REPLY
-                || (flow->nw_proto == ARP_OP_REQUEST
-                    && flow->nw_src == flow->nw_dst)));
+    memset(&wc->masks.dl_type, 0xff, sizeof wc->masks.dl_type);
+    if (flow->dl_type != htons(ETH_TYPE_ARP)) {
+        return false;
+    }
+
+    memset(&wc->masks.dl_dst, 0xff, sizeof wc->masks.dl_dst);
+    if (!eth_addr_is_broadcast(flow->dl_dst)) {
+        return false;
+    }
+
+    memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
+    if (flow->nw_proto == ARP_OP_REPLY) {
+        return true;
+    } else if (flow->nw_proto == ARP_OP_REQUEST) {
+        memset(&wc->masks.nw_src, 0xff, sizeof wc->masks.nw_src);
+        memset(&wc->masks.nw_dst, 0xff, sizeof wc->masks.nw_dst);
+
+        return flow->nw_src == flow->nw_dst;
+    } else {
+        return false;
+    }
 }
 
 static void
 update_learning_table(struct ofproto_dpif *ofproto,
-                      const struct flow *flow, int vlan,
-                      struct ofbundle *in_bundle)
+                      const struct flow *flow, struct flow_wildcards *wc,
+                      int vlan, struct ofbundle *in_bundle)
 {
     struct mac_entry *mac;
 
@@ -7381,7 +7565,7 @@ update_learning_table(struct ofproto_dpif *ofproto,
     }
 
     mac = mac_learning_insert(ofproto->ml, flow->dl_src, vlan);
-    if (is_gratuitous_arp(flow)) {
+    if (is_gratuitous_arp(flow, wc)) {
         /* We don't want to learn from gratuitous ARP packets that are
          * reflected back over bond slaves so we lock the learning table. */
         if (!in_bundle->bond) {
@@ -7493,7 +7677,7 @@ is_admissible(struct xlate_ctx *ctx, struct ofport_dpif *in_port,
         case BV_DROP_IF_MOVED:
             mac = mac_learning_lookup(ofproto->ml, flow->dl_src, vlan, NULL);
             if (mac && mac->port.p != in_bundle &&
-                (!is_gratuitous_arp(flow)
+                (!is_gratuitous_arp(flow, &ctx->xout->wc)
                  || mac_entry_is_grat_arp_locked(mac))) {
                 xlate_report(ctx, "SLB bond thinks this packet looped back, "
                             "dropping");
@@ -7517,6 +7701,13 @@ xlate_normal(struct xlate_ctx *ctx)
 
     ctx->xout->has_normal = true;
 
+    memset(&ctx->xout->wc.masks.dl_src, 0xff,
+           sizeof ctx->xout->wc.masks.dl_src);
+    memset(&ctx->xout->wc.masks.dl_dst, 0xff,
+           sizeof ctx->xout->wc.masks.dl_dst);
+    memset(&ctx->xout->wc.masks.vlan_tci, 0xff,
+           sizeof ctx->xout->wc.masks.vlan_tci);
+
     in_bundle = lookup_input_bundle(ctx->ofproto, ctx->xin->flow.in_port,
                                     ctx->xin->packet != NULL, &in_port);
     if (!in_bundle) {
@@ -7564,7 +7755,8 @@ xlate_normal(struct xlate_ctx *ctx)
 
     /* Learn source MAC. */
     if (ctx->xin->may_learn) {
-        update_learning_table(ctx->ofproto, &ctx->xin->flow, vlan, in_bundle);
+        update_learning_table(ctx->ofproto, &ctx->xin->flow, &ctx->xout->wc,
+                              vlan, in_bundle);
     }
 
     /* Determine output bundle. */
@@ -7795,7 +7987,8 @@ send_active_timeout(struct ofproto_dpif *ofproto, struct facet *facet)
             if (subfacet->path == SF_FAST_PATH) {
                 struct dpif_flow_stats stats;
 
-                subfacet_install(subfacet, &facet->xout.odp_actions, &stats);
+                subfacet_install(subfacet, &facet->xout.odp_actions,
+                                 &stats);
                 subfacet_update_stats(subfacet, &stats);
             }
         }
@@ -7811,9 +8004,11 @@ send_active_timeout(struct ofproto_dpif *ofproto, struct facet *facet)
 static void
 send_netflow_active_timeouts(struct ofproto_dpif *ofproto)
 {
+    struct cls_cursor cursor;
     struct facet *facet;
 
-    HMAP_FOR_EACH (facet, hmap_node, &ofproto->facets) {
+    cls_cursor_init(&cursor, &ofproto->facets, NULL);
+    CLS_CURSOR_FOR_EACH (facet, cr, &cursor) {
         send_active_timeout(ofproto, facet);
     }
 }
@@ -8103,7 +8298,7 @@ ofproto_trace(struct ofproto_dpif *ofproto, const struct flow *flow,
     flow_format(ds, flow);
     ds_put_char(ds, '\n');
 
-    rule = rule_dpif_lookup(ofproto, flow);
+    rule = rule_dpif_lookup(ofproto, flow, NULL);
 
     trace_format_rule(ds, 0, 0, rule);
     if (rule == ofproto->miss_rule) {
@@ -8119,8 +8314,8 @@ ofproto_trace(struct ofproto_dpif *ofproto, const struct flow *flow,
     if (rule) {
         uint64_t odp_actions_stub[1024 / 8];
         struct ofpbuf odp_actions;
-
         struct trace_ctx trace;
+        struct match match;
         uint8_t tcp_flags;
 
         tcp_flags = packet ? packet_get_tcp_flags(packet, flow) : 0;
@@ -8132,10 +8327,17 @@ ofproto_trace(struct ofproto_dpif *ofproto, const struct flow *flow,
                       packet);
         trace.xin.resubmit_hook = trace_resubmit;
         trace.xin.report_hook = trace_report;
+
         xlate_actions(&trace.xin, &trace.xout);
 
         ds_put_char(ds, '\n');
         trace_format_flow(ds, 0, "Final flow", &trace);
+
+        match_init(&match, flow, &trace.xout.wc);
+        ds_put_cstr(ds, "Relevant fields: ");
+        match_format(&match, ds, OFP_DEFAULT_PRIORITY);
+        ds_put_char(ds, '\n');
+
         ds_put_cstr(ds, "Datapath actions: ");
         format_odp_actions(ds, trace.xout.odp_actions.data,
                            trace.xout.odp_actions.size);
@@ -8190,11 +8392,13 @@ ofproto_dpif_unclog(struct unixctl_conn *conn OVS_UNUSED, int argc OVS_UNUSED,
 static void
 ofproto_dpif_self_check__(struct ofproto_dpif *ofproto, struct ds *reply)
 {
+    struct cls_cursor cursor;
     struct facet *facet;
     int errors;
 
     errors = 0;
-    HMAP_FOR_EACH (facet, hmap_node, &ofproto->facets) {
+    cls_cursor_init(&cursor, &ofproto->facets, NULL);
+    CLS_CURSOR_FOR_EACH (facet, cr, &cursor) {
         if (!facet_check_consistency(facet)) {
             errors++;
         }
@@ -8393,6 +8597,50 @@ ofproto_unixctl_dpif_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
     ds_destroy(&ds);
 }
 
+/* Dump the megaflow (facet) cache.  This is useful to check the
+ * correctness of flow wildcarding, since the same mechanism is used for
+ * both xlate caching and kernel wildcarding.
+ *
+ * It's important to note that in the output the flow description uses
+ * OpenFlow (OFP) ports, but the actions use datapath (ODP) ports.
+ *
+ * This command is only needed for advanced debugging, so it's not
+ * documented in the man page. */
+static void
+ofproto_unixctl_dpif_dump_megaflows(struct unixctl_conn *conn,
+                                    int argc OVS_UNUSED, const char *argv[],
+                                    void *aux OVS_UNUSED)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    const struct ofproto_dpif *ofproto;
+    long long int now = time_msec();
+    struct cls_cursor cursor;
+    struct facet *facet;
+
+    ofproto = ofproto_dpif_lookup(argv[1]);
+    if (!ofproto) {
+        unixctl_command_reply_error(conn, "no such bridge");
+        return;
+    }
+
+    cls_cursor_init(&cursor, &ofproto->facets, NULL);
+    CLS_CURSOR_FOR_EACH (facet, cr, &cursor) {
+        cls_rule_format(&facet->cr, &ds);
+        ds_put_cstr(&ds, ", ");
+        ds_put_format(&ds, "n_subfacets:%"PRIu64", ",
+                      list_size(&facet->subfacets));
+        ds_put_format(&ds, "used:%.3fs, ", (now - facet->used) / 1000.0);
+        ds_put_cstr(&ds, "Datapath actions: ");
+        format_odp_actions(&ds, facet->xout.odp_actions.data,
+                           facet->xout.odp_actions.size);
+        ds_put_cstr(&ds, "\n");
+    }
+
+    ds_chomp(&ds, '\n');
+    unixctl_command_reply(conn, ds_cstr(&ds));
+    ds_destroy(&ds);
+}
+
 static void
 ofproto_unixctl_dpif_dump_flows(struct unixctl_conn *conn,
                                 int argc OVS_UNUSED, const char *argv[],
@@ -8504,6 +8752,8 @@ ofproto_dpif_unixctl_init(void)
                              ofproto_unixctl_dpif_dump_flows, NULL);
     unixctl_command_register("dpif/del-flows", "bridge", 1, 1,
                              ofproto_unixctl_dpif_del_flows, NULL);
+    unixctl_command_register("dpif/dump-megaflows", "bridge", 1, 1,
+                             ofproto_unixctl_dpif_dump_megaflows, NULL);
 }
 
 /* Linux VLAN device support (e.g. "eth0.10" for VLAN 10.)
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index f9cde8e..177742f 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -2209,3 +2209,348 @@ OFPST_PORT reply (OF1.3) (xid=0x2): 3 ports
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([ofproto-dpif -- megaflows])
+
+# Strips out uninteresting parts of megaflow output, as well as parts
+# that vary from one run to another (e.g., timing and bond actions).
+m4_define([STRIP_XOUT], [[sed '
+    s/used:[0-9]*\.[0-9]*/used:0.0/
+    s/miss:[0-9]*/miss:0/
+    s/Datapath actions:.*/Datapath actions: <del>/
+' | sort]])
+
+AT_SETUP([ofproto-dpif megaflow - port classification])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1 actions=output(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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+in_port=1, n_subfacets:2, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - L2 classification])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1,dl_src=50:54:00:00:00:09 actions=output(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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+in_port=1,dl_src=50:54:00:00:00:09, n_subfacets:1, used:0.0s, Datapath actions: <del>
+in_port=1,dl_src=50:54:00:00:00:0b, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - L3 classification])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1,icmp,nw_src=10.0.0.4 actions=output(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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+icmp,in_port=1,nw_src=10.0.0.2, n_subfacets:1, used:0.0s, Datapath actions: <del>
+icmp,in_port=1,nw_src=10.0.0.4, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - L4 classification])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1,icmp,icmp_type=8 actions=output(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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+icmp,in_port=1,icmp_type=8, n_subfacets:2, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - normal])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a, n_subfacets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - mpls])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 dl_src=50:54:00:00:00:09 actions=push_mpls:0x8847,2
+table=0 dl_src=50:54:00:00:00:0b actions=pop_mpls:0x0800,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:09,dst=50:54:00:00:00:0a),eth_type(0x8847),mpls(label=11,tc=3,ttl=64,bos=1)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0a),eth_type(0x8847),mpls(label=11,tc=3,ttl=64,bos=1)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+mpls,in_port=1,dl_src=50:54:00:00:00:09,mpls_label=11,mpls_tc=3,mpls_ttl=64,mpls_bos=1, n_subfacets:1, used:0.0s, Datapath actions: <del>
+mpls,in_port=1,dl_src=50:54:00:00:00:0b,mpls_label=11,mpls_tc=3,mpls_ttl=64,mpls_bos=1, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - netflow])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+
+dnl NetFlow configuration disables wildcarding relevant fields
+ON_EXIT([kill `cat test-netflow.pid`])
+AT_CHECK([test-netflow --log-file --detach --no-chdir --pidfile 0:127.0.0.1 > netflow.log], [0], [], [ignore])
+AT_CAPTURE_FILE([netflow.log])
+NETFLOW_PORT=`parse_listening_port < test-netflow.log`
+ovs-vsctl \
+   set Bridge br0 netflow=@nf -- \
+   --id=@nf create NetFlow targets=\"127.0.0.1:$NETFLOW_PORT\" \
+     engine_id=1 engine_type=2 active_timeout=30 add-id-to-interface=false
+
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+icmp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,icmp_type=8,icmp_code=0, n_subfacets:1, used:0.0s, Datapath actions: <del>
+icmp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c,nw_src=10.0.0.4,nw_dst=10.0.0.3,icmp_type=8,icmp_code=0, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - normal, active-backup bonding])
+OVS_VSWITCHD_START(
+  [add-port br0 p1 -- set Interface p1 type=dummy ofport_request=1 -- \
+   add-bond br0 bond0 p2 p3 bond_mode=active-backup -- \
+   set interface p2 type=dummy ofport_request=2 -- \
+   set interface p3 type=dummy ofport_request=3])
+AT_CHECK([ovs-appctl netdev-dummy/set-admin-state up], 0, [OK
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a, n_subfacets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - normal, balance-slb bonding])
+OVS_VSWITCHD_START(
+  [add-port br0 p1 -- set Interface p1 type=dummy ofport_request=1 -- \
+   add-bond br0 bond0 p2 p3 bond_mode=balance-slb -- \
+   set interface p2 type=dummy ofport_request=2 -- \
+   set interface p3 type=dummy ofport_request=3])
+AT_CHECK([ovs-appctl netdev-dummy/set-admin-state up], 0, [OK
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a, n_subfacets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - normal, balance-tcp bonding])
+# Create bond0 on br0 with interfaces p0 and p1
+#    and bond1 on br1 with interfaces p2 and p3
+# with p0 patched to p2 and p1 patched to p3.
+OVS_VSWITCHD_START(
+  [add-bond br0 bond0 p0 p1 bond_mode=balance-tcp lacp=active \
+                            other-config:lacp-time=fast \
+                            other-config:bond-rebalance-interval=0 -- \
+   set interface p0 type=patch options:peer=p2 ofport_request=1 -- \
+   set interface p1 type=patch options:peer=p3 ofport_request=2 -- \
+   add-br br1 -- \
+   set bridge br1 other-config:hwaddr=aa:66:aa:66:00:00 -- \
+   set bridge br1 datapath-type=dummy other-config:datapath-id=1234 \
+                  fail-mode=secure -- \
+   add-bond br1 bond1 p2 p3 bond_mode=balance-tcp lacp=active \
+                            other-config:lacp-time=fast \
+                            other-config:bond-rebalance-interval=0 -- \
+   set interface p2 type=patch options:peer=p0 ofport_request=3 -- \
+   set interface p3 type=patch options:peer=p1 ofport_request=4 --])
+
+AT_CHECK([ovs-appctl netdev-dummy/set-admin-state up], 0, [OK
+])
+ADD_OF_PORTS([br0], [7])
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-ofctl add-flow br1 action=normal])
+ovs-appctl time/warp 5000
+AT_CHECK([ovs-appctl netdev-dummy/receive p7 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p7 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+icmp,in_port=7,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,icmp_type=8,icmp_code=0, n_subfacets:1, used:0.0s, Datapath actions: <del>
+icmp,in_port=7,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c,nw_src=10.0.0.4,nw_dst=10.0.0.3,icmp_type=8,icmp_code=0, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - resubmit port action])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1,ip actions=resubmit(90)
+table=0 in_port=90,dl_src=50:54:00:00:00:09 actions=output(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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+ip,in_port=1,dl_src=50:54:00:00:00:09, n_subfacets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,dl_src=50:54:00:00:00:0b, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - resubmit table action])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1,ip actions=resubmit(,1)
+table=1 dl_src=50:54:00:00:00:09 actions=output(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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=
+1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+ip,in_port=1,dl_src=50:54:00:00:00:09, n_subfacets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,dl_src=50:54:00:00:00:0b, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - goto_table action])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1,ip actions=goto_table(1)
+table=1 dl_src=50:54:00:00:00:09 actions=output(2)
+])
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-flows br0 flows.txt])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+ip,in_port=1,dl_src=50:54:00:00:00:09, n_subfacets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,dl_src=50:54:00:00:00:0b, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - mirroring, select_all])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2], [3])
+ovs-vsctl \
+        set Bridge br0 mirrors=@m --\
+        --id=@p3 get Port p3 --\
+        --id=@m create Mirror name=mymirror select_all=true output_port=@p3
+
+AT_DATA([flows.txt], [dnl
+in_port=1 actions=output: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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+in_port=1, n_subfacets:2, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - mirroring, select_vlan])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2], [3])
+ovs-vsctl \
+        set Bridge br0 mirrors=@m --\
+        --id=@p2 get Port p2 -- --id=@p3 get Port p3 --\
+        --id=@m create Mirror name=mymirror select_all=true select_vlan=11 output_port=@p3
+
+AT_DATA([flows.txt], [dnl
+in_port=1 actions=output: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:09,dst=50:54:00:00:00:0a),eth_type(0x8100),vlan(vid=11,pcp=7),encap(eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0))'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+in_port=1,dl_vlan=11, n_subfacets:1, used:0.0s, Datapath actions: <del>
+in_port=1,vlan_tci=0x0000/0x1fff, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - move action])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1 ip,actions=move:NXM_OF_IP_SRC[[]]->NXM_NX_REG0[[]],resubmit(90)
+table=0 in_port=90 ip,actions=move:NXM_NX_REG0[[]]->NXM_NX_REG1[[]],resubmit(91)
+table=0 in_port=91 reg0=0x0a000002,actions=output(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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+ip,in_port=1,nw_src=10.0.0.2, n_subfacets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,nw_src=10.0.0.4, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - push action])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1 ip,actions=push:NXM_OF_IP_SRC[[]],output(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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+ip,in_port=1,nw_src=10.0.0.2, n_subfacets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,nw_src=10.0.0.4, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif megaflow - learning])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1 actions=load:2->NXM_NX_REG0[[0..15]],learn(table=1,priority=65535,NXM_OF_ETH_SRC[[]],NXM_OF_VLAN_TCI[[0..11]],output:NXM_NX_REG0[[0..15]]),output: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:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
+dnl The original flow is missing due to a revalidation.
+AT_CHECK([ovs-appctl dpif/dump-megaflows br0 | STRIP_XOUT], [0], [dnl
+in_port=1,vlan_tci=0x0000/0x0fff,dl_src=50:54:00:00:00:09, n_subfacets:1, used:0.0s, Datapath actions: <del>
+in_port=1,vlan_tci=0x0000/0x0fff,dl_src=50:54:00:00:00:0b, n_subfacets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/test-bundle.c b/tests/test-bundle.c
index f5b24b4..c475991 100644
--- a/tests/test-bundle.c
+++ b/tests/test-bundle.c
@@ -187,9 +187,11 @@ main(int argc, char *argv[])
         for (j = 0; j < N_FLOWS; j++) {
             struct flow *flow = &flows[j];
             uint16_t old_slave_id, ofp_port;
+            struct flow_wildcards wc;
 
             old_slave_id = flow->regs[0];
-            ofp_port = bundle_execute(bundle, flow, slave_enabled_cb, &sg);
+            ofp_port = bundle_execute(bundle, flow, &wc, slave_enabled_cb,
+                                      &sg);
             flow->regs[0] = ofp_port;
 
             if (ofp_port != OFPP_NONE) {
diff --git a/tests/test-multipath.c b/tests/test-multipath.c
index 8442bc2..e8aacff 100644
--- a/tests/test-multipath.c
+++ b/tests/test-multipath.c
@@ -57,6 +57,7 @@ main(int argc, char *argv[])
         memset(histogram, 0, sizeof histogram);
         for (i = 0; i < N_FLOWS; i++) {
             int old_link, new_link;
+            struct flow_wildcards wc;
             struct flow flow;
 
             random_bytes(&flow, sizeof flow);
@@ -64,11 +65,11 @@ main(int argc, char *argv[])
             flow.mpls_depth = 0;
 
             mp.max_link = n - 1;
-            multipath_execute(&mp, &flow);
+            multipath_execute(&mp, &flow, &wc);
             old_link = flow.regs[0];
 
             mp.max_link = n;
-            multipath_execute(&mp, &flow);
+            multipath_execute(&mp, &flow, &wc);
             new_link = flow.regs[0];
 
             assert(old_link >= 0 && old_link < n);
-- 
1.7.5.4




More information about the dev mailing list