[ovs-dev] [xc 4/4] ofproto-dpif: Track relevant fields for wildcarding and add xout cache.

Justin Pettit jpettit at nicira.com
Wed Jun 5 22:21:39 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
generate an action translation cache for similar flows.  This
yields a roughly 40% improvement in flow set up rates for a
complicated flow table.  (Another 40% improvement should be
trivially achievable by more efficiently deleting facets.)

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

The approach to tracking fields and the idea of caching action
translations was based on an impressive prototype by Ethan Jackson.

Co-authored-by: Ethan Jackson <ethan at nicira.com>
---
 lib/bond.c             |   17 +-
 lib/bond.h             |    5 +-
 lib/bundle.c           |   19 +-
 lib/bundle.h           |    2 +
 lib/flow.c             |   26 ++
 lib/flow.h             |    1 +
 lib/learn.c            |   14 +
 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 |  743 ++++++++++++++++++++++++++++++++++++++----------
 tests/ofproto-dpif.at  |  344 ++++++++++++++++++++++
 tests/test-bundle.c    |    4 +-
 tests/test-multipath.c |    5 +-
 16 files changed, 1045 insertions(+), 167 deletions(-)

diff --git a/lib/bond.c b/lib/bond.c
index aca18a2..c41879a 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);
@@ -608,9 +609,10 @@ bond_check_admissibility(struct bond *bond, const void *slave_,
  */
 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 +1351,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;
 
@@ -1364,12 +1366,17 @@ choose_output_slave(const struct bond *bond, const struct flow *flow,
         return bond->active_slave;
 
     case BM_TCP:
-        if (bond->lacp_status != LACP_NEGOTIATED) {
+        if (bond->lacp_status == LACP_NEGOTIATED && wc) {
+            flow_mask_hash_fields(wc, NX_HASH_FIELDS_SYMMETRIC_L4);
+        } else {
             /* Must have LACP negotiations for TCP balanced bonds. */
             return NULL;
         }
         /* 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 b67f3e0..3cd6e09 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -757,6 +757,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 e52b633..3aeb30f 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -204,6 +204,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..4ed0522 100644
--- a/lib/learn.c
+++ b/lib/learn.c
@@ -374,6 +374,20 @@ learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
     fm->ofpacts_len = ofpacts->size;
 }
 
+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..03e8d64 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 *wc);
 
 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..c0256e7 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_value mask_value;
     union mf_value src_value;
     union mf_value dst_value;
 
+    memset(&mask_value, 0xff, sizeof mask_value);
+    mf_set_flow_value(move->src.field, &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,9 +1432,14 @@ 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 dst_value;
+    union mf_value mask_value;
+
+    memset(&mask_value, 0xff, sizeof mask_value);
+    mf_set_flow_value(push->subfield.field, &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 becdf0a..d126ea0 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -112,9 +112,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);
@@ -226,6 +228,11 @@ struct initial_vals {
 };
 
 struct xlate_out {
+    struct flow_wildcards wc;   /* Relevant wildcards in translation.
+                                 * Any fields that were used to
+                                 * calculate the action must be set for
+                                 * caching and megaflows to work. */
+
     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? */
@@ -359,6 +366,44 @@ static void compose_slow_path(const struct ofproto_dpif *, const struct flow *,
 
 static void xlate_report(struct xlate_ctx *ctx, const char *s);
 
+struct xout_cache {
+    struct cls_rule cr;         /* Rule to find cached entry. */
+
+    struct flow flow;
+    struct initial_vals initial_vals;
+
+    long long int used;         /* Time last used; time created if not used. */
+    uint64_t n_hit;             /* Number of times entry was hit. */
+
+    struct list facets;         /* List of facets using this xout. */
+
+    long long int learn_rl;     /* Rate limiter for facet_learn(). */
+    struct dpif_flow_stats stats;
+
+    struct xlate_out xout;
+};
+
+static void xlate_out_copy(struct xlate_out *dst, const struct xlate_out *src);
+
+static void del_facet_from_xc(struct facet *);
+static void add_facet_to_xc(struct xout_cache *, struct facet *);
+
+static struct xout_cache *create_xc(const struct flow_wildcards *,
+                                    struct initial_vals *,
+                                    struct facet *);
+static void insert_xc(struct ofproto_dpif *, struct xout_cache *,
+                      const struct flow *);
+
+static void destroy_xc(struct ofproto_dpif *, struct xout_cache *);
+
+static void flush_xc(struct ofproto_dpif *);
+static void invalidate_xc_tags(struct ofproto_dpif *,
+                                 const struct tag_set *);
+static void expire_xc(struct ofproto_dpif *);
+
+static struct xout_cache *lookup_xc(struct ofproto_dpif *,
+                                    const struct flow *);
+
 /* A subfacet (see "struct subfacet" below) has three possible installation
  * states:
  *
@@ -437,7 +482,7 @@ static void subfacet_uninstall(struct subfacet *);
 struct facet {
     /* Owners. */
     struct hmap_node hmap_node;  /* In owning ofproto's 'facets' hmap. */
-    struct list list_node;       /* In owning rule's 'facets' list. */
+    struct list rule_list_node;  /* In owning rule's 'facets' list. */
     struct rule_dpif *rule;      /* Owning rule. */
 
     /* Owned data. */
@@ -472,7 +517,8 @@ struct facet {
     struct netflow_flow nf_flow; /* Per-flow NetFlow tracking data. */
     uint8_t tcp_flags;           /* TCP flags seen for this 'rule'. */
 
-    struct xlate_out xout;
+    struct list xc_list_node;    /* Reference for xout cache. */
+    struct xout_cache *xc;
 
     /* Initial values of the packet that may be needed later. */
     struct initial_vals initial_vals;
@@ -483,8 +529,6 @@ struct facet {
      * always be valid, since it could have been removed after newer
      * subfacets were pushed onto the 'subfacets' list.) */
     struct subfacet one_subfacet;
-
-    long long int learn_rl;      /* Rate limiter for facet_learn(). */
 };
 
 static struct facet *facet_create(const struct flow_miss *, uint32_t hash);
@@ -694,6 +738,11 @@ struct ofproto_dpif {
     struct governor *governor;
     long long int consistency_rl;
 
+    /* xlate_out cache. */
+    struct classifier xout_cache;
+    uint64_t xout_cache_hit;
+    uint64_t xout_cache_miss;
+
     /* Revalidation. */
     struct table_dpif tables[N_TABLES];
 
@@ -787,8 +836,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
@@ -1019,9 +1068,17 @@ type_run(const char *type)
                 continue;
             }
 
+            if (need_revalidate) {
+                flush_xc(ofproto);
+            } else {
+                invalidate_xc_tags(ofproto, &revalidate_set);
+            }
+
             HMAP_FOR_EACH_SAFE (facet, next, hmap_node, &ofproto->facets) {
-                if (need_revalidate
-                    || tag_set_intersects(&revalidate_set, facet->xout.tags)) {
+                if (!facet->xc
+                    || need_revalidate
+                    || tag_set_intersects(&revalidate_set,
+                                          facet->xc->xout.tags)) {
                     facet_revalidate(facet);
                     run_fast_rl();
                 }
@@ -1375,6 +1432,10 @@ construct(struct ofproto *ofproto_)
     ofproto->governor = NULL;
     ofproto->consistency_rl = LLONG_MIN;
 
+    classifier_init(&ofproto->xout_cache);
+    ofproto->xout_cache_hit = 0;
+    ofproto->xout_cache_miss = 0;
+
     for (i = 0; i < N_TABLES; i++) {
         struct table_dpif *table = &ofproto->tables[i];
 
@@ -1471,7 +1532,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;
@@ -1553,10 +1614,14 @@ destruct(struct ofproto *ofproto_)
     hmap_destroy(&ofproto->bundles);
     mac_learning_destroy(ofproto->ml);
 
+    flush_xc(ofproto);
+
     hmap_destroy(&ofproto->facets);
     hmap_destroy(&ofproto->subfacets);
     governor_destroy(ofproto->governor);
 
+    classifier_destroy(&ofproto->xout_cache);
+
     hmap_destroy(&ofproto->vlandev_map);
     hmap_destroy(&ofproto->realdev_vid_map);
 
@@ -1639,7 +1704,7 @@ run(struct ofproto *ofproto_)
         facet = CONTAINER_OF(hmap_random_node(&ofproto->facets),
                              struct facet, hmap_node);
         if (!tag_set_intersects(&ofproto->backer->revalidate_set,
-                                facet->xout.tags)) {
+                                facet->xc->xout.tags)) {
             if (!facet_check_consistency(facet)) {
                 ofproto->backer->need_revalidate = REV_INCONSISTENCY;
             }
@@ -1664,6 +1729,8 @@ run(struct ofproto *ofproto_)
         }
     }
 
+    expire_xc(ofproto);
+
     return 0;
 }
 
@@ -3691,21 +3758,26 @@ flow_miss_should_make_facet(struct ofproto_dpif *ofproto,
                                         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.  May add an "execute" operation to 'ops' and increment
+ * '*n_ops'. */
 static void
 handle_flow_miss_without_facet(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 flow_wildcards wc;
+    struct rule_dpif *rule;
     struct ofpbuf *packet;
     struct xlate_in xin;
 
+    flow_wildcards_init_catchall(&wc);
+    rule = rule_dpif_lookup(miss->ofproto, &miss->flow, &wc);
+
     LIST_FOR_EACH (packet, list_node, &miss->packets) {
         struct flow_miss_op *op = &ops[*n_ops];
         struct dpif_flow_stats stats;
+        struct xout_cache *xc;
 
         COVERAGE_INC(facet_suppress);
 
@@ -3714,10 +3786,24 @@ handle_flow_miss_without_facet(struct flow_miss *miss,
         dpif_flow_stats_extract(&miss->flow, packet, now, &stats);
         rule_credit_stats(rule, &stats);
 
-        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);
+        xc = lookup_xc(miss->ofproto, &miss->flow);
+        if (xc) {
+            dpif_flow_stats_extract(&miss->flow, packet, now, &xc->stats);
+            xc->stats.n_packets++;
+            xc->stats.n_bytes += stats.n_bytes;
+            xc->stats.tcp_flags |= stats.tcp_flags;
+            xc->stats.used = MAX(xc->stats.used, stats.used);
+
+            xlate_out_copy(&op->xout, &xc->xout);
+        } else {
+            xlate_in_init(&xin, miss->ofproto, &miss->flow,
+                          &miss->initial_vals, rule, stats.tcp_flags, packet);
+            xin.resubmit_stats = &stats;
+            xc = create_xc(&wc, &miss->initial_vals, NULL);
+            xlate_actions(&xin, &xc->xout);
+            xlate_out_copy(&op->xout, &xc->xout);
+            insert_xc(miss->ofproto, xc, &miss->flow);
+        }
 
         if (op->xout.odp_actions.size) {
             struct dpif_execute *execute = &op->dpif_op.u.execute;
@@ -3754,7 +3840,7 @@ 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 = subfacet->facet->xc->xout.slow ? SF_SLOW_PATH : SF_FAST_PATH;
 
     LIST_FOR_EACH (packet, list_node, &miss->packets) {
         struct flow_miss_op *op = &ops[*n_ops];
@@ -3773,12 +3859,12 @@ handle_flow_miss_with_facet(struct flow_miss *miss, struct facet *facet,
         dpif_flow_stats_extract(&facet->flow, packet, now, &stats);
         subfacet_update_stats(subfacet, &stats);
 
-        if (facet->xout.odp_actions.size) {
+        if (facet->xc->xout.odp_actions.size) {
             struct dpif_execute *execute = &op->dpif_op.u.execute;
 
             init_flow_miss_execute_op(miss, packet, op);
-            execute->actions = facet->xout.odp_actions.data,
-            execute->actions_len = facet->xout.odp_actions.size;
+            execute->actions = facet->xc->xout.odp_actions.data,
+            execute->actions_len = facet->xc->xout.odp_actions.size;
             (*n_ops)++;
         }
     }
@@ -3795,10 +3881,10 @@ handle_flow_miss_with_facet(struct flow_miss *miss, struct facet *facet,
         put->key = miss->key;
         put->key_len = miss->key_len;
         if (want_path == SF_FAST_PATH) {
-            put->actions = facet->xout.odp_actions.data;
-            put->actions_len = facet->xout.odp_actions.size;
+            put->actions = facet->xc->xout.odp_actions.data;
+            put->actions_len = facet->xc->xout.odp_actions.size;
         } else {
-            compose_slow_path(ofproto, &facet->flow, facet->xout.slow,
+            compose_slow_path(ofproto, &facet->flow, facet->xc->xout.slow,
                               op->slow_stub, sizeof op->slow_stub,
                               &put->actions, &put->actions_len);
         }
@@ -4574,8 +4660,8 @@ expire_subfacets(struct ofproto_dpif *ofproto, int dp_max_idle)
                         &ofproto->subfacets) {
         long long int cutoff;
 
-        cutoff = (subfacet->facet->xout.slow & (SLOW_CFM | SLOW_BFD | SLOW_LACP
-                                                | SLOW_STP)
+        cutoff = (subfacet->facet->xc->xout.slow & (SLOW_CFM | SLOW_BFD
+                                                    | SLOW_LACP | SLOW_STP)
                   ? special_cutoff
                   : normal_cutoff);
         if (subfacet->used < cutoff) {
@@ -4626,7 +4712,7 @@ rule_expire(struct rule_dpif *rule)
 
     /* Update stats.  (This is a no-op if the rule expired due to an idle
      * timeout, because that only happens when the rule has no facets left.) */
-    LIST_FOR_EACH_SAFE (facet, next_facet, list_node, &rule->facets) {
+    LIST_FOR_EACH_SAFE (facet, next_facet, rule_list_node, &rule->facets) {
         facet_remove(facet);
     }
 
@@ -4636,7 +4722,7 @@ rule_expire(struct rule_dpif *rule)
 
 /* Facets. */
 
-/* Creates and returns a new facet based on 'miss'.
+/* Creates and returns a new facet based on 'miss' and 'wc'.
  *
  * The caller must already have determined that no facet with an identical
  * 'miss->flow' exists in 'miss->ofproto'.
@@ -4649,6 +4735,8 @@ static struct facet *
 facet_create(const struct flow_miss *miss, uint32_t hash)
 {
     struct ofproto_dpif *ofproto = miss->ofproto;
+    struct xout_cache *xc;
+    struct flow_wildcards wc;
     struct xlate_in xin;
     struct facet *facet;
 
@@ -4656,20 +4744,28 @@ facet_create(const struct flow_miss *miss, uint32_t hash)
     facet->used = time_msec();
     facet->flow = miss->flow;
     facet->initial_vals = miss->initial_vals;
-    facet->rule = rule_dpif_lookup(ofproto, &facet->flow);
-    facet->learn_rl = time_msec() + 500;
+    flow_wildcards_init_catchall(&wc);
+    facet->rule = rule_dpif_lookup(ofproto, &facet->flow, &wc);
 
     hmap_insert(&ofproto->facets, &facet->hmap_node, hash);
-    list_push_back(&facet->rule->facets, &facet->list_node);
+    list_push_back(&facet->rule->facets, &facet->rule_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);
-    facet->nf_flow.output_iface = facet->xout.nf_output_iface;
+    xc = lookup_xc(ofproto, &facet->flow);
+    if (xc) {
+        add_facet_to_xc(xc, facet);
+    } else {
+        xlate_in_init(&xin, ofproto, &facet->flow, &facet->initial_vals,
+                      facet->rule, 0, NULL);
+        xin.may_learn = true;
+        xc = create_xc(&wc, &facet->initial_vals, facet);
+        xlate_actions(&xin, &xc->xout);
+        insert_xc(ofproto, xc, &facet->flow);
+    }
+    facet->xc->learn_rl = time_msec() + 500;
+    facet->nf_flow.output_iface = facet->xc->xout.nf_output_iface;
 
     return facet;
 }
@@ -4678,7 +4774,7 @@ static void
 facet_free(struct facet *facet)
 {
     if (facet) {
-        xlate_out_uninit(&facet->xout);
+        del_facet_from_xc(facet);
         free(facet);
     }
 }
@@ -4736,7 +4832,7 @@ facet_remove(struct facet *facet)
         subfacet_destroy__(subfacet);
     }
     hmap_remove(&ofproto->facets, &facet->hmap_node);
-    list_remove(&facet->list_node);
+    list_remove(&facet->rule_list_node);
     facet_free(facet);
 }
 
@@ -4747,15 +4843,15 @@ facet_learn(struct facet *facet)
 {
     long long int now = time_msec();
 
-    if (!facet->xout.has_fin_timeout && now < facet->learn_rl) {
+    if (!facet->xc->xout.has_fin_timeout && now < facet->xc->learn_rl) {
         return;
     }
 
-    facet->learn_rl = now + 500;
+    facet->xc->learn_rl = now + 500;
 
-    if (!facet->xout.has_learn
-        && !facet->xout.has_normal
-        && (!facet->xout.has_fin_timeout
+    if (!facet->xc->xout.has_learn
+        && !facet->xc->xout.has_normal
+        && (!facet->xc->xout.has_fin_timeout
             || !(facet->tcp_flags & (TCP_FIN | TCP_RST)))) {
         return;
     }
@@ -4772,7 +4868,7 @@ facet_account(struct facet *facet)
     ovs_be16 vlan_tci;
     uint64_t n_bytes;
 
-    if (!facet->xout.has_normal || !ofproto->has_bonded_bundles) {
+    if (!facet->xc->xout.has_normal || !ofproto->has_bonded_bundles) {
         return;
     }
     n_bytes = facet->byte_count - facet->accounted_bytes;
@@ -4786,8 +4882,8 @@ facet_account(struct facet *facet)
      * We use the actions from an arbitrary subfacet because they should all
      * be equally valid for our purpose. */
     vlan_tci = facet->flow.vlan_tci;
-    NL_ATTR_FOR_EACH_UNSAFE (a, left, facet->xout.odp_actions.data,
-                             facet->xout.odp_actions.size) {
+    NL_ATTR_FOR_EACH_UNSAFE (a, left, facet->xc->xout.odp_actions.data,
+                             facet->xc->xout.odp_actions.size) {
         const struct ovs_action_push_vlan *vlan;
         struct ofport_dpif *port;
 
@@ -4905,12 +5001,19 @@ facet_lookup_valid(struct ofproto_dpif *ofproto, const struct flow *flow,
     struct facet *facet;
 
     facet = facet_find(ofproto, flow, hash);
-    if (facet
-        && (ofproto->backer->need_revalidate
-            || tag_set_intersects(&ofproto->backer->revalidate_set,
-                                  facet->xout.tags))
-        && !facet_revalidate(facet)) {
-        return NULL;
+    if (facet) {
+        if (ofproto->backer->need_revalidate) {
+            flush_xc(ofproto);
+        } else {
+            invalidate_xc_tags(ofproto, &ofproto->backer->revalidate_set);
+        }
+
+        if ((!facet->xc || ofproto->backer->need_revalidate
+                || tag_set_intersects(&ofproto->backer->revalidate_set,
+                                      facet->xc->xout.tags))
+            && !facet_revalidate(facet)) {
+            return NULL;
+        }
     }
 
     return facet;
@@ -4930,7 +5033,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;
@@ -4954,25 +5057,25 @@ facet_check_consistency(struct facet *facet)
                   0, NULL);
     xlate_actions(&xin, &xout);
 
-    ok = ofpbuf_equal(&facet->xout.odp_actions, &xout.odp_actions)
-        && facet->xout.slow == xout.slow;
+    ok = ofpbuf_equal(&facet->xc->xout.odp_actions, &xout.odp_actions)
+        && facet->xc->xout.slow == xout.slow;
     if (!ok && !VLOG_DROP_WARN(&rl)) {
         struct ds s = DS_EMPTY_INITIALIZER;
 
         flow_format(&s, &facet->flow);
         ds_put_cstr(&s, ": inconsistency in facet");
 
-        if (!ofpbuf_equal(&facet->xout.odp_actions, &xout.odp_actions)) {
+        if (!ofpbuf_equal(&facet->xc->xout.odp_actions, &xout.odp_actions)) {
             ds_put_cstr(&s, " (actions were: ");
-            format_odp_actions(&s, facet->xout.odp_actions.data,
-                               facet->xout.odp_actions.size);
+            format_odp_actions(&s, facet->xc->xout.odp_actions.data,
+                               facet->xc->xout.odp_actions.size);
             ds_put_cstr(&s, ") (correct actions: ");
             format_odp_actions(&s, xout.odp_actions.data,
                                xout.odp_actions.size);
             ds_put_cstr(&s, ")");
         }
 
-        if (facet->xout.slow != xout.slow) {
+        if (facet->xc->xout.slow != xout.slow) {
             ds_put_format(&s, " slow path incorrect. should be %d", xout.slow);
         }
 
@@ -5001,8 +5104,10 @@ 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 xlate_out xout;
+    struct flow_wildcards wc;
+    struct xout_cache *xc;
     struct xlate_in xin;
+    bool refresh_subfacets = false;
 
     COVERAGE_INC(facet_revalidate);
 
@@ -5025,63 +5130,58 @@ 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.
-     *
-     * We do not modify any 'facet' state yet, because we might need to, e.g.,
-     * emit a NetFlow expiration and, if so, we need to have the old state
-     * around to properly compose it. */
-    xlate_in_init(&xin, ofproto, &facet->flow, &facet->initial_vals, new_rule,
-                  0, NULL);
-    xlate_actions(&xin, &xout);
+    xc = lookup_xc(ofproto, &facet->flow);
+    if (!xc) {
+        ovs_assert(!facet->xc);
+        xlate_in_init(&xin, ofproto, &facet->flow, &facet->initial_vals,
+                      new_rule, 0, NULL);
+        xc = create_xc(&wc, &facet->initial_vals, facet);
+        xlate_actions(&xin, &xc->xout);
+        insert_xc(ofproto, xc, &facet->flow);
+        refresh_subfacets = true;
+    } else if (!facet->xc) {
+        add_facet_to_xc(xc, facet);
+        refresh_subfacets = true;
+    }
 
     /* 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) {
+    if (facet->xc->xout.slow != xc->xout.slow) {
         facet_remove(facet);
-        xlate_out_uninit(&xout);
         return false;
     }
 
-    if (!ofpbuf_equal(&facet->xout.odp_actions, &xout.odp_actions)) {
+    if (refresh_subfacets || facet->xc != xc) {
         LIST_FOR_EACH(subfacet, list_node, &facet->subfacets) {
             if (subfacet->path == SF_FAST_PATH) {
                 struct dpif_flow_stats stats;
 
-                subfacet_install(subfacet, &xout.odp_actions, &stats);
+                subfacet_install(subfacet, &xc->xout.odp_actions, &stats);
                 subfacet_update_stats(subfacet, &stats);
             }
         }
-
         facet_flush_stats(facet);
-
-        ofpbuf_clear(&facet->xout.odp_actions);
-        ofpbuf_put(&facet->xout.odp_actions, xout.odp_actions.data,
-                   xout.odp_actions.size);
     }
 
-    /* Update 'facet' now that we've taken care of all the old state. */
-    facet->xout.tags = xout.tags;
-    facet->xout.slow = xout.slow;
-    facet->xout.has_learn = xout.has_learn;
-    facet->xout.has_normal = xout.has_normal;
-    facet->xout.has_fin_timeout = xout.has_fin_timeout;
-    facet->xout.nf_output_iface = xout.nf_output_iface;
-    facet->xout.mirrors = xout.mirrors;
-    facet->nf_flow.output_iface = facet->xout.nf_output_iface;
+    if (facet->xc != xc) {
+        del_facet_from_xc(facet);
+        add_facet_to_xc(xc, facet);
+    }
+    facet->nf_flow.output_iface = facet->xc->xout.nf_output_iface;
 
     if (facet->rule != new_rule) {
         COVERAGE_INC(facet_changed_rule);
-        list_remove(&facet->list_node);
-        list_push_back(&new_rule->facets, &facet->list_node);
+        list_remove(&facet->rule_list_node);
+        list_push_back(&new_rule->facets, &facet->rule_list_node);
         facet->rule = new_rule;
         facet->used = new_rule->up.created;
         facet->prev_used = facet->used;
     }
 
-    xlate_out_uninit(&xout);
     return true;
 }
 
@@ -5096,25 +5196,54 @@ facet_reset_counters(struct facet *facet)
 }
 
 static void
-facet_push_stats(struct facet *facet, bool may_learn)
+xout_cache_push_stats(struct ofproto_dpif *ofproto, struct xout_cache *xc,
+                      bool may_learn)
+{
+    struct rule_dpif *rule;
+    struct xlate_in xin;
+
+    if (!may_learn && !xc->stats.n_packets) {
+        return;
+    }
+
+    rule = rule_dpif_lookup(ofproto, &xc->flow, NULL);
+
+    xlate_in_init(&xin, ofproto, &xc->flow, &xc->initial_vals,
+                  rule, xc->stats.tcp_flags, NULL);
+    xin.resubmit_stats = &xc->stats;
+    xin.may_learn = may_learn;
+    xlate_actions_for_side_effects(&xin);
+
+    memset(&xc->stats, '\0', sizeof xc->stats);
+}
+
+/* Complete facet-specific work to push 'facet''s stats.  Returns true
+ * if caller should call xout_cache_push_stats() afterwards. */
+static bool
+facet_push_stats__(struct facet *facet, bool may_learn)
 {
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(facet->rule->up.ofproto);
     struct dpif_flow_stats stats;
 
     ovs_assert(facet->packet_count >= facet->prev_packet_count);
     ovs_assert(facet->byte_count >= facet->prev_byte_count);
     ovs_assert(facet->used >= facet->prev_used);
 
+    /* Use local stats instead of 'facet->xc->stats' because this
+     * function may be called multiple times before
+     * xout_cache_push_stats(), which leads to high counts. */
     stats.n_packets = facet->packet_count - facet->prev_packet_count;
     stats.n_bytes = facet->byte_count - facet->prev_byte_count;
     stats.used = facet->used;
     stats.tcp_flags = facet->tcp_flags;
 
-    if (may_learn || stats.n_packets || facet->used > facet->prev_used) {
-        struct ofproto_dpif *ofproto =
-            ofproto_dpif_cast(facet->rule->up.ofproto);
+    facet->xc->stats.n_packets += stats.n_packets;
+    facet->xc->stats.n_bytes += stats.n_bytes;
+    facet->xc->stats.used = MAX(facet->xc->stats.used, stats.used);
+    facet->xc->stats.tcp_flags |= stats.tcp_flags;
 
+    if (may_learn || stats.n_packets || facet->used > facet->prev_used) {
         struct ofport_dpif *in_port;
-        struct xlate_in xin;
 
         facet->prev_packet_count = facet->packet_count;
         facet->prev_byte_count = facet->byte_count;
@@ -5129,14 +5258,23 @@ facet_push_stats(struct facet *facet, bool may_learn)
         netflow_flow_update_time(ofproto->netflow, &facet->nf_flow,
                                  facet->used);
         netflow_flow_update_flags(&facet->nf_flow, facet->tcp_flags);
-        update_mirror_stats(ofproto, facet->xout.mirrors, stats.n_packets,
-                            stats.n_bytes);
+        update_mirror_stats(ofproto, facet->xc->xout.mirrors,
+                            stats.n_packets, stats.n_bytes);
 
-        xlate_in_init(&xin, ofproto, &facet->flow, &facet->initial_vals,
-                      facet->rule, stats.tcp_flags, NULL);
-        xin.resubmit_stats = &stats;
-        xin.may_learn = may_learn;
-        xlate_actions_for_side_effects(&xin);
+        return true;
+    }
+
+    return false;
+}
+
+static void
+facet_push_stats(struct facet *facet, bool may_learn)
+{
+    if (facet_push_stats__(facet, may_learn)) {
+        struct ofproto_dpif *ofproto;
+
+        ofproto = ofproto_dpif_cast(facet->rule->up.ofproto);
+        xout_cache_push_stats(ofproto, facet->xc, may_learn);
     }
 }
 
@@ -5151,10 +5289,17 @@ push_all_stats__(bool run_fast)
     }
 
     HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
+        struct xout_cache *xc, *next_xc;
+        struct cls_cursor cursor;
         struct facet *facet;
 
         HMAP_FOR_EACH (facet, hmap_node, &ofproto->facets) {
-            facet_push_stats(facet, false);
+            facet_push_stats__(facet, false);
+        }
+
+        cls_cursor_init(&cursor, &ofproto->xout_cache, NULL);
+        CLS_CURSOR_FOR_EACH_SAFE (xc, next_xc, cr, &cursor) {
+            xout_cache_push_stats(ofproto, xc, false);
             if (run_fast) {
                 run_fast_rl();
             }
@@ -5321,7 +5466,7 @@ subfacet_install(struct subfacet *subfacet, const struct ofpbuf *odp_actions,
 {
     struct facet *facet = subfacet->facet;
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(facet->rule->up.ofproto);
-    enum subfacet_path path = facet->xout.slow ? SF_SLOW_PATH : SF_FAST_PATH;
+    enum subfacet_path path = facet->xc->xout.slow ? SF_SLOW_PATH : SF_FAST_PATH;
     const struct nlattr *actions = odp_actions->data;
     size_t actions_len = odp_actions->size;
 
@@ -5335,7 +5480,7 @@ subfacet_install(struct subfacet *subfacet, const struct ofpbuf *odp_actions,
     }
 
     if (path == SF_SLOW_PATH) {
-        compose_slow_path(ofproto, &facet->flow, facet->xout.slow,
+        compose_slow_path(ofproto, &facet->flow, facet->xc->xout.slow,
                           slow_path_stub, sizeof slow_path_stub,
                           &actions, &actions_len);
     }
@@ -5419,12 +5564,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;
     }
@@ -5434,7 +5582,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;
@@ -5451,11 +5599,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));
 }
@@ -5523,7 +5674,7 @@ rule_construct(struct rule *rule_)
 
         rule->facets = victim->facets;
         list_moved(&rule->facets);
-        LIST_FOR_EACH (facet, list_node, &rule->facets) {
+        LIST_FOR_EACH (facet, rule_list_node, &rule->facets) {
             /* XXX: We're only clearing our local counters here.  It's possible
              * that quite a few packets are unaccounted for in the datapath
              * statistics.  These will be accounted to the new rule instead of
@@ -5561,7 +5712,7 @@ rule_destruct(struct rule *rule_)
     struct rule_dpif *rule = rule_dpif_cast(rule_);
     struct facet *facet, *next_facet;
 
-    LIST_FOR_EACH_SAFE (facet, next_facet, list_node, &rule->facets) {
+    LIST_FOR_EACH_SAFE (facet, next_facet, rule_list_node, &rule->facets) {
         facet_revalidate(facet);
     }
 
@@ -6101,7 +6252,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);
 
@@ -6482,8 +6634,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 {
@@ -6501,6 +6653,12 @@ xlate_learn_action(struct xlate_ctx *ctx,
     struct ofpbuf ofpacts;
     int error;
 
+    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);
 
@@ -6717,7 +6875,8 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             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:
@@ -6726,7 +6885,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:
@@ -6766,7 +6925,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:
@@ -6780,9 +6940,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
 
         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:
@@ -6818,7 +6976,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);
 
@@ -6887,7 +7046,12 @@ xlate_out_uninit(struct xlate_out *xout)
 }
 
 /* Translates the 'ofpacts_len' bytes of "struct ofpacts" starting at 'ofpacts'
- * into datapath actions in 'odp_actions', using 'ctx'. */
+ * into datapath actions in 'odp_actions', using 'ctx'.
+ *
+ * The caller is responsible for initializing 'xout->wc'.  It will
+ * likely either be the wildcards from a call to rule_dpif_lookup() or
+ * flow_wildcards_init_catchall() if no rule was used.
+ */
 static void
 xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
 {
@@ -6937,6 +7101,36 @@ 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;
 
+    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;
@@ -7053,6 +7247,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'
@@ -7073,6 +7273,174 @@ 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);
+}
+
+static void
+del_facet_from_xc(struct facet *facet)
+{
+    list_remove(&facet->xc_list_node);
+    facet->xc = NULL;
+}
+
+static void
+add_facet_to_xc(struct xout_cache *xc, struct facet *facet)
+{
+    facet->xc = xc;
+    list_push_back(&xc->facets, &facet->xc_list_node);
+}
+
+static struct xout_cache *
+create_xc(const struct flow_wildcards *initial_wc, 
+          struct initial_vals *initial_vals, struct facet *facet)
+{
+    struct xout_cache *xc = xzalloc(sizeof *xc);
+
+    xc->xout.wc = *initial_wc;
+    xc->initial_vals = *initial_vals;
+
+    list_init(&xc->facets);
+    if (facet) {
+        add_facet_to_xc(xc, facet);
+    }
+
+    return xc;
+}
+
+static void
+insert_xc(struct ofproto_dpif *ofproto, struct xout_cache *xc,
+          const struct flow *flow)
+{
+    struct match match;
+
+
+    xc->flow = *flow;
+    match_init(&match, flow, &xc->xout.wc);
+
+    cls_rule_init(&xc->cr, &match, OFP_DEFAULT_PRIORITY);
+    classifier_insert(&ofproto->xout_cache, &xc->cr);
+
+    xc->used = time_msec();
+}
+
+static void
+destroy_xc(struct ofproto_dpif *ofproto, struct xout_cache *xc)
+{
+    struct facet *facet;
+
+    if (list_is_singleton(&xc->facets)) {
+        /* There's only a single facet, so just credit it with xc's
+         * stats to save the expensive xlate_actions(). */
+        facet = CONTAINER_OF(list_front(&xc->facets), struct facet,
+                            xc_list_node);;
+
+        facet->packet_count += xc->stats.n_packets;
+        facet->byte_count += xc->stats.n_bytes;
+        facet->used = MAX(facet->used, xc->stats.used);
+        facet->tcp_flags |= xc->stats.tcp_flags;
+    } else {
+        xout_cache_push_stats(ofproto, xc, false);
+    }
+    LIST_FOR_EACH (facet, xc_list_node, &xc->facets) {
+        facet->xc = NULL;
+    }
+    classifier_remove(&ofproto->xout_cache, &xc->cr);
+    cls_rule_destroy(&xc->cr);
+    xlate_out_uninit(&xc->xout);
+    free(xc);
+}
+
+/* Flush the xout cache.
+ *
+ * The xout cache pointer for all facets will be removed, so if the
+ * facets are still needed, then this call must be immediately followed
+ * by a call to facet_revalidate(). */
+static void
+flush_xc(struct ofproto_dpif *ofproto)
+{
+    struct xout_cache *xc, *next_xc;
+    struct cls_cursor cursor;
+
+    cls_cursor_init(&cursor, &ofproto->xout_cache, NULL);
+    CLS_CURSOR_FOR_EACH_SAFE (xc, next_xc, cr, &cursor) {
+        destroy_xc(ofproto, xc);
+    }
+}
+
+/* Invalidate xout cache entries with tags that overlap with
+ * 'revalidate_set'.
+ *
+ * The xout cache pointer for associated facets will be removed, so if
+ * the facets are still needed, then this call must be immediately
+ * followed by a call to facet_revalidate(). */
+static void
+invalidate_xc_tags(struct ofproto_dpif *ofproto,
+                   const struct tag_set *revalidate_set)
+{
+    struct xout_cache *xc, *next_xc;
+    struct cls_cursor cursor;
+
+    cls_cursor_init(&cursor, &ofproto->xout_cache, NULL);
+    CLS_CURSOR_FOR_EACH_SAFE (xc, next_xc, cr, &cursor) {
+        if (tag_set_intersects(revalidate_set, xc->xout.tags)) {
+            destroy_xc(ofproto, xc);
+        }
+    }
+}
+
+static void
+expire_xc(struct ofproto_dpif *ofproto)
+{
+    long long int cutoff = time_msec() - 20000;
+    struct xout_cache *xc, *next_xc;
+    struct cls_cursor cursor;
+
+    cls_cursor_init(&cursor, &ofproto->xout_cache, NULL);
+    CLS_CURSOR_FOR_EACH_SAFE (xc, next_xc, cr, &cursor) {
+        if (list_is_empty(&xc->facets) && xc->used < cutoff) {
+            destroy_xc(ofproto, xc);
+        }
+    }
+}
+
+static struct xout_cache *
+lookup_xc(struct ofproto_dpif *ofproto, const struct flow *flow)
+{
+    struct cls_rule *cr;
+
+    cr = classifier_lookup(&ofproto->xout_cache, flow, NULL);
+
+    if (cr) {
+        struct xout_cache *xc;
+
+        ofproto->xout_cache_hit++;
+
+        xc = CONTAINER_OF(cr, struct xout_cache, cr);
+        xc->used = time_msec();
+        xc->n_hit++;
+
+        return xc;
+    } else {
+        ofproto->xout_cache_miss++;
+        return NULL;
+    }
+}
 
 /* OFPP_NORMAL implementation. */
 
@@ -7200,7 +7568,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;
@@ -7320,6 +7688,10 @@ add_mirror_actions(struct xlate_ctx *ctx, const struct flow *orig_flow)
 
         m = ofproto->mirrors[mirror_mask_ffs(mirrors) - 1];
 
+        if (m->vlans && bitmap_scan(m->vlans, 0, 4096) < 4096) {
+            ctx->xout->wc.masks.vlan_tci |= htons(VLAN_CFI | VLAN_VID_MASK);
+        }
+
         if (!vlan_is_mirrored(m, vlan)) {
             mirrors = zero_rightmost_1bit(mirrors);
             continue;
@@ -7375,19 +7747,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;
 
@@ -7401,7 +7789,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) {
@@ -7513,7 +7901,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");
@@ -7537,6 +7925,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) {
@@ -7584,7 +7979,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. */
@@ -7815,7 +8211,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->xc->xout.odp_actions,
+                                 &stats);
                 subfacet_update_stats(subfacet, &stats);
             }
         }
@@ -8123,7 +8520,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) {
@@ -8139,8 +8536,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;
@@ -8156,6 +8553,12 @@ ofproto_trace(struct ofproto_dpif *ofproto, const struct flow *flow,
 
         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);
@@ -8427,6 +8830,56 @@ ofproto_unixctl_dpif_show(struct unixctl_conn *conn, int argc,
     ds_destroy(&ds);
 }
 
+/* Dump the xlate_out cache.  This is useful to check the correctness of
+ * the cache.  However, more importantly, it is useful to check the
+ * correctness of flow wildcarding, since the same mechanism is used for
+ * both xlate_out caching and megaflows.
+ *
+ * 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_xc(struct unixctl_conn *conn,
+                             int argc OVS_UNUSED, const char *argv[],
+                             void *aux OVS_UNUSED)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct xout_cache *xc, *next_xc;
+    const struct ofproto_dpif *ofproto;
+    long long int now = time_msec();
+    struct cls_cursor cursor;
+
+    ofproto = ofproto_dpif_lookup(argv[1]);
+    if (!ofproto) {
+        unixctl_command_reply_error(conn, "no such bridge");
+        return;
+    }
+
+    ds_put_format(&ds, "entries:%d, hit:%"PRIu64", miss:%"PRIu64"\n",
+                  ofproto->xout_cache.n_rules,
+                  ofproto->xout_cache_hit,
+                  ofproto->xout_cache_miss);
+
+    cls_cursor_init(&cursor, &ofproto->xout_cache, NULL);
+    CLS_CURSOR_FOR_EACH_SAFE (xc, next_xc, cr, &cursor) {
+        cls_rule_format(&xc->cr, &ds);
+        ds_put_cstr(&ds, ", ");
+        ds_put_format(&ds, "n_hit:%"PRIu64", ", xc->n_hit);
+        ds_put_format(&ds, "n_facets:%zu, ", list_size(&xc->facets));
+        ds_put_format(&ds, "used:%.3fs, ", (now - xc->used) / 1000.0);
+        ds_put_cstr(&ds, "Datapath actions: ");
+        format_odp_actions(&ds, xc->xout.odp_actions.data,
+                           xc->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[],
@@ -8463,18 +8916,18 @@ ofproto_unixctl_dpif_dump_flows(struct unixctl_conn *conn,
         }
 
         ds_put_cstr(&ds, ", actions:");
-        if (facet->xout.slow) {
+        if (facet->xc->xout.slow) {
             uint64_t slow_path_stub[128 / 8];
             const struct nlattr *actions;
             size_t actions_len;
 
-            compose_slow_path(ofproto, &facet->flow, facet->xout.slow,
+            compose_slow_path(ofproto, &facet->flow, facet->xc->xout.slow,
                               slow_path_stub, sizeof slow_path_stub,
                               &actions, &actions_len);
             format_odp_actions(&ds, actions, actions_len);
         } else {
-            format_odp_actions(&ds, facet->xout.odp_actions.data,
-                               facet->xout.odp_actions.size);
+            format_odp_actions(&ds, facet->xc->xout.odp_actions.data,
+                               facet->xc->xout.odp_actions.size);
         }
         ds_put_char(&ds, '\n');
     }
@@ -8534,6 +8987,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-xout-cache", "bridge", 1, 1,
+                             ofproto_unixctl_dpif_dump_xc, 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 94ba9e0..a641d2d 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -2222,3 +2222,347 @@ OFPST_PORT reply (OF1.3) (xid=0x2): 3 ports
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([ofproto-dpif -- xout caching])
+
+# Strips out uninteresting parts of xout cache 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/Datapath actions:.*/Datapath actions: <del>/
+' | sort]])
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:1, hit:1, miss:1
+in_port=1, n_hit:1, n_facets:2, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+in_port=1,dl_src=50:54:00:00:00:09, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+in_port=1,dl_src=50:54:00:00:00:0b, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+icmp,in_port=1,nw_src=10.0.0.2, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+icmp,in_port=1,nw_src=10.0.0.4, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:1, hit:1, miss:1
+icmp,in_port=1,icmp_type=8, n_hit:1, n_facets:2, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a, n_hit:0, n_facets: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_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+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_hit:0, n_facets: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_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a, n_hit:0, n_facets: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_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a, n_hit:0, n_facets: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_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+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_hit:0, n_facets: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_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+ip,in_port=1,dl_src=50:54:00:00:00:09, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,dl_src=50:54:00:00:00:0b, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+ip,in_port=1,dl_src=50:54:00:00:00:09, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,dl_src=50:54:00:00:00:0b, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+ip,in_port=1,dl_src=50:54:00:00:00:09, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,dl_src=50:54:00:00:00:0b, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:1, hit:1, miss:1
+in_port=1, n_hit:1, n_facets:2, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+in_port=1,dl_vlan=11, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+in_port=1,vlan_tci=0x0000/0x1fff, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+ip,in_port=1,nw_src=10.0.0.2, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,nw_src=10.0.0.4, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:2
+ip,in_port=1,nw_src=10.0.0.2, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+ip,in_port=1,nw_src=10.0.0.4, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif xout caching - 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-xout-cache br0 | STRIP_XOUT], [0], [dnl
+entries:2, hit:0, miss:3
+in_port=1,vlan_tci=0x0000/0x0fff,dl_src=50:54:00:00:00:09, n_hit:0, n_facets:1, used:0.0s, Datapath actions: <del>
+in_port=1,vlan_tci=0x0000/0x0fff,dl_src=50:54:00:00:00:0b, n_hit:0, n_facets: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