[ovs-dev] [PATCH v2] tun-metadata: Manage tunnel TLV mapping table on a per-bridge basis.

Jesse Gross jesse at kernel.org
Thu Sep 1 19:53:07 UTC 2016


When using tunnel TLVs (at the moment, this means Geneve options), a
controller must first map the class and type onto an appropriate OXM
field so that it can be used in OVS flow operations. This table is
managed using OpenFlow extensions.

The original code that added support for TLVs made the mapping table
global as a simplification. However, this is not really logically
correct as the OpenFlow management commands are operating on a per-bridge
basis. This removes the original limitation to make the table per-bridge.

One nice result of this change is that it is generally clearer whether
the tunnel metadata is in datapath or OpenFlow format. Rather than
allowing ad-hoc format changes and trying to handle both formats in the
tunnel metadata functions, the format is more clearly separated by function.
Datapaths (both kernel and userspace) use datapath format and it is not
changed during the upcall process. At the beginning of action translation,
tunnel metadata is converted to OpenFlow format and flows and wildcards
are translated back at the end of the process.

As an additional benefit, this change improves performance in some flow
setup situations by keeping the tunnel metadata in the original packet
format in more cases. This helps when copies need to be made as the amount
of data touched is only what is present in the packet rather than the
maximum amount of metadata supported.

Co-authored-by: Madhu Challa <challa at noironetworks.com>
Signed-off-by: Madhu Challa <challa at noironetworks.com>
Signed-off-by: Jesse Gross <jesse at kernel.org>
---
v2: Pass 'struct tun_table' instead of 'struct ofproto' for functions that
    need to access tunnel metadata. Rebase on top of ovs-ofctl metadata
    changes.
---
 include/openvswitch/match.h        |   3 +-
 include/openvswitch/ofp-parse.h    |   3 +-
 include/openvswitch/ofp-util.h     |  12 +-
 include/openvswitch/tun-metadata.h |   4 +-
 lib/classifier.c                   |   5 +-
 lib/classifier.h                   |   3 +-
 lib/dpctl.c                        |   5 +-
 lib/dpif-netdev.c                  |  65 +------
 lib/flow.c                         |   1 +
 lib/learning-switch.c              |   3 +-
 lib/match.c                        |   5 +-
 lib/nx-match.c                     |  43 ++--
 lib/nx-match.h                     |  15 +-
 lib/odp-execute.c                  |   2 +-
 lib/odp-util.c                     |  88 ++-------
 lib/odp-util.h                     |  14 +-
 lib/ofp-parse.c                    |   4 +-
 lib/ofp-print.c                    |   6 +-
 lib/ofp-util.c                     |  76 +++++---
 lib/tun-metadata.c                 | 388 ++++++++++++-------------------------
 lib/tun-metadata.h                 |  21 +-
 ofproto/ofproto-dpif-sflow.c       |   2 +-
 ofproto/ofproto-dpif-upcall.c      |   9 +-
 ofproto/ofproto-dpif-xlate.c       |  76 ++++++++
 ofproto/ofproto-dpif-xlate.h       |   5 +
 ofproto/ofproto-dpif.c             |  60 ++++--
 ofproto/ofproto-dpif.h             |   1 +
 ofproto/ofproto-provider.h         |  10 +
 ofproto/ofproto.c                  |  39 ++--
 ovn/controller/pinctrl.c           |   2 +-
 tests/test-odp.c                   |   5 +-
 utilities/ovs-ofctl.8.in           |   4 -
 utilities/ovs-ofctl.c              |  82 ++++----
 33 files changed, 504 insertions(+), 557 deletions(-)

diff --git a/include/openvswitch/match.h b/include/openvswitch/match.h
index 3b7f32f..0b5f050 100644
--- a/include/openvswitch/match.h
+++ b/include/openvswitch/match.h
@@ -211,7 +211,8 @@ bool minimatch_equal(const struct minimatch *a, const struct minimatch *b);
 
 bool minimatch_matches_flow(const struct minimatch *, const struct flow *);
 
-void minimatch_format(const struct minimatch *, struct ds *, int priority);
+void minimatch_format(const struct minimatch *, const struct tun_table *,
+                      struct ds *, int priority);
 char *minimatch_to_string(const struct minimatch *, int priority);
 
 #endif /* match.h */
diff --git a/include/openvswitch/ofp-parse.h b/include/openvswitch/ofp-parse.h
index df60b18..b0fcc80 100644
--- a/include/openvswitch/ofp-parse.h
+++ b/include/openvswitch/ofp-parse.h
@@ -63,7 +63,8 @@ char *parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *,
     OVS_WARN_UNUSED_RESULT;
 
 char *parse_ofp_exact_flow(struct flow *flow, struct flow_wildcards *wc,
-                           const char *s, const struct simap *portno_names);
+                           const struct tun_table *tun_table, const char *s,
+                           const struct simap *portno_names);
 
 char *parse_ofp_meter_mod_str(struct ofputil_meter_mod *, const char *string,
                               int command,
diff --git a/include/openvswitch/ofp-util.h b/include/openvswitch/ofp-util.h
index 450e739..db8ce36 100644
--- a/include/openvswitch/ofp-util.h
+++ b/include/openvswitch/ofp-util.h
@@ -220,7 +220,8 @@ void ofputil_normalize_match_quiet(struct match *);
 void ofputil_match_to_ofp10_match(const struct match *, struct ofp10_match *);
 
 /* Work with ofp11_match. */
-enum ofperr ofputil_pull_ofp11_match(struct ofpbuf *, struct match *,
+enum ofperr ofputil_pull_ofp11_match(struct ofpbuf *, const struct tun_table *,
+                                     struct match *,
                                      uint16_t *padded_match_len);
 enum ofperr ofputil_pull_ofp11_mask(struct ofpbuf *, struct match *,
                                     struct mf_bitmap *bm);
@@ -329,6 +330,7 @@ struct ofputil_flow_mod {
 enum ofperr ofputil_decode_flow_mod(struct ofputil_flow_mod *,
                                     const struct ofp_header *,
                                     enum ofputil_protocol,
+                                    const struct tun_table *,
                                     struct ofpbuf *ofpacts,
                                     ofp_port_t max_port,
                                     uint8_t max_table);
@@ -347,7 +349,8 @@ struct ofputil_flow_stats_request {
 };
 
 enum ofperr ofputil_decode_flow_stats_request(
-    struct ofputil_flow_stats_request *, const struct ofp_header *);
+    struct ofputil_flow_stats_request *, const struct ofp_header *,
+    const struct tun_table *);
 struct ofpbuf *ofputil_encode_flow_stats_request(
     const struct ofputil_flow_stats_request *, enum ofputil_protocol);
 
@@ -376,7 +379,8 @@ int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *,
                                     bool flow_age_extension,
                                     struct ofpbuf *ofpacts);
 void ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *,
-                                     struct ovs_list *replies);
+                                     struct ovs_list *replies,
+                                     const struct tun_table *);
 
 /* Aggregate stats reply, independent of protocol. */
 struct ofputil_aggregate_stats {
@@ -450,6 +454,7 @@ struct ofputil_packet_in {
 void ofputil_packet_in_destroy(struct ofputil_packet_in *);
 
 enum ofperr ofputil_decode_packet_in(const struct ofp_header *, bool loose,
+                                     const struct tun_table *,
                                      struct ofputil_packet_in *,
                                      size_t *total_len, uint32_t *buffer_id,
                                      struct ofpbuf *continuation);
@@ -501,6 +506,7 @@ struct ofpbuf *ofputil_encode_packet_in_private(
 
 enum ofperr ofputil_decode_packet_in_private(
     const struct ofp_header *, bool loose,
+    const struct tun_table *,
     struct ofputil_packet_in_private *,
     size_t *total_len, uint32_t *buffer_id);
 
diff --git a/include/openvswitch/tun-metadata.h b/include/openvswitch/tun-metadata.h
index 8e3a13f..1d6b737 100644
--- a/include/openvswitch/tun-metadata.h
+++ b/include/openvswitch/tun-metadata.h
@@ -48,7 +48,7 @@ struct tun_metadata {
         uint64_t map;                      /* 1-bit for each present TLV. */
         uint8_t len;                       /* Length of data in 'opts'. */
     } present;
-    struct tun_table *tab;      /* Types & lengths for 'opts' and 'opt_map'. */
+    const struct tun_table *tab; /* Types & lengths for 'opts' and 'opt_map'. */
 
 #if UINTPTR_MAX == UINT32_MAX
     uint8_t pad[4];             /* Pad to 64-bit boundary. */
@@ -87,7 +87,7 @@ struct tun_metadata_match_entry {
 };
 
 /* Allocation of options inside struct match.  This is important if we don't
- * have access to a global allocation table - either because there isn't one
+ * have access to an allocation table - either because there isn't one
  * (ovs-ofctl) or if we need to keep the allocation outside of packet
  * processing context (Packet-In). These structures never have dynamically
  * allocated memory because the address space is never fragmented. */
diff --git a/lib/classifier.c b/lib/classifier.c
index 0551146..782ef07 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -268,9 +268,10 @@ cls_rule_equal(const struct cls_rule *a, const struct cls_rule *b)
 
 /* Appends a string describing 'rule' to 's'. */
 void
-cls_rule_format(const struct cls_rule *rule, struct ds *s)
+cls_rule_format(const struct cls_rule *rule, const struct tun_table *tun_table,
+                struct ds *s)
 {
-    minimatch_format(&rule->match, s, rule->priority);
+    minimatch_format(&rule->match, tun_table, s, rule->priority);
 }
 
 /* Returns true if 'rule' matches every packet, false otherwise. */
diff --git a/lib/classifier.h b/lib/classifier.h
index 44185a3..c57fa6f 100644
--- a/lib/classifier.h
+++ b/lib/classifier.h
@@ -412,7 +412,8 @@ int classifier_count(const struct classifier *);
 /* Classifier rule properties.  These are RCU protected and may run
  * concurrently with modifiers and each other. */
 bool cls_rule_equal(const struct cls_rule *, const struct cls_rule *);
-void cls_rule_format(const struct cls_rule *, struct ds *);
+void cls_rule_format(const struct cls_rule *, const struct tun_table *,
+                     struct ds *);
 bool cls_rule_is_catchall(const struct cls_rule *);
 bool cls_rule_is_loose_match(const struct cls_rule *rule,
                              const struct minimatch *criteria);
diff --git a/lib/dpctl.c b/lib/dpctl.c
index 28f2f83..edccb7f 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -803,7 +803,7 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
     }
 
     if (filter) {
-        char *err = parse_ofp_exact_flow(&flow_filter, &wc_filter, filter,
+        char *err = parse_ofp_exact_flow(&flow_filter, &wc_filter, NULL, filter,
                                          &names_portno);
         if (err) {
             dpctl_error(dpctl_p, 0, "Failed to parse filter (%s)", err);
@@ -829,8 +829,7 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
             struct minimatch minimatch;
 
             odp_flow_key_to_flow(f.key, f.key_len, &flow);
-            odp_flow_key_to_mask(f.mask, f.mask_len, f.key, f.key_len,
-                                 &wc, &flow);
+            odp_flow_key_to_mask(f.mask, f.mask_len, &wc, &flow);
             match_init(&match, &flow, &wc);
 
             match_init(&match_filter, &flow_filter, &wc);
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index ecc7cea..6e09e44 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -2117,8 +2117,7 @@ dpif_netdev_mask_from_nlattrs(const struct nlattr *key, uint32_t key_len,
 {
     enum odp_key_fitness fitness;
 
-    fitness = odp_flow_key_to_mask_udpif(mask_key, mask_key_len, key,
-                                         key_len, wc, flow);
+    fitness = odp_flow_key_to_mask(mask_key, mask_key_len, wc, flow);
     if (fitness) {
         /* This should not happen: it indicates that
          * odp_flow_key_from_mask() and odp_flow_key_to_mask()
@@ -2149,7 +2148,7 @@ dpif_netdev_flow_from_nlattrs(const struct nlattr *key, uint32_t key_len,
 {
     odp_port_t in_port;
 
-    if (odp_flow_key_to_flow_udpif(key, key_len, flow)) {
+    if (odp_flow_key_to_flow(key, key_len, flow)) {
         /* This should not happen: it indicates that odp_flow_key_from_flow()
          * and odp_flow_key_to_flow() disagree on the acceptable form of a
          * flow.  Log the problem as an error, with enough details to enable
@@ -3784,27 +3783,11 @@ dp_netdev_upcall(struct dp_netdev_pmd_thread *pmd, struct dp_packet *packet_,
                  struct ofpbuf *actions, struct ofpbuf *put_actions)
 {
     struct dp_netdev *dp = pmd->dp;
-    struct flow_tnl orig_tunnel;
-    int err;
 
     if (OVS_UNLIKELY(!dp->upcall_cb)) {
         return ENODEV;
     }
 
-    /* Upcall processing expects the Geneve options to be in the translated
-     * format but we need to retain the raw format for datapath use. */
-    orig_tunnel.flags = flow->tunnel.flags;
-    if (flow->tunnel.flags & FLOW_TNL_F_UDPIF) {
-        orig_tunnel.metadata.present.len = flow->tunnel.metadata.present.len;
-        memcpy(orig_tunnel.metadata.opts.gnv, flow->tunnel.metadata.opts.gnv,
-               flow->tunnel.metadata.present.len);
-        err = tun_metadata_from_geneve_udpif(&orig_tunnel, &orig_tunnel,
-                                             &flow->tunnel);
-        if (err) {
-            return err;
-        }
-    }
-
     if (OVS_UNLIKELY(!VLOG_DROP_DBG(&upcall_rl))) {
         struct ds ds = DS_EMPTY_INITIALIZER;
         char *packet_str;
@@ -3831,48 +3814,8 @@ dp_netdev_upcall(struct dp_netdev_pmd_thread *pmd, struct dp_packet *packet_,
         ds_destroy(&ds);
     }
 
-    err = dp->upcall_cb(packet_, flow, ufid, pmd->core_id, type, userdata,
-                        actions, wc, put_actions, dp->upcall_aux);
-    if (err && err != ENOSPC) {
-        return err;
-    }
-
-    /* Translate tunnel metadata masks to datapath format. */
-    if (wc) {
-        if (wc->masks.tunnel.metadata.present.map) {
-            struct geneve_opt opts[TLV_TOT_OPT_SIZE /
-                                   sizeof(struct geneve_opt)];
-
-            if (orig_tunnel.flags & FLOW_TNL_F_UDPIF) {
-                tun_metadata_to_geneve_udpif_mask(&flow->tunnel,
-                                                  &wc->masks.tunnel,
-                                                  orig_tunnel.metadata.opts.gnv,
-                                                  orig_tunnel.metadata.present.len,
-                                                  opts);
-            } else {
-                orig_tunnel.metadata.present.len = 0;
-            }
-
-            memset(&wc->masks.tunnel.metadata, 0,
-                   sizeof wc->masks.tunnel.metadata);
-            memcpy(&wc->masks.tunnel.metadata.opts.gnv, opts,
-                   orig_tunnel.metadata.present.len);
-        }
-        wc->masks.tunnel.metadata.present.len = 0xff;
-    }
-
-    /* Restore tunnel metadata. We need to use the saved options to ensure
-     * that any unknown options are not lost. The generated mask will have
-     * the same structure, matching on types and lengths but wildcarding
-     * option data we don't care about. */
-    if (orig_tunnel.flags & FLOW_TNL_F_UDPIF) {
-        memcpy(&flow->tunnel.metadata.opts.gnv, orig_tunnel.metadata.opts.gnv,
-               orig_tunnel.metadata.present.len);
-        flow->tunnel.metadata.present.len = orig_tunnel.metadata.present.len;
-        flow->tunnel.flags |= FLOW_TNL_F_UDPIF;
-    }
-
-    return err;
+    return dp->upcall_cb(packet_, flow, ufid, pmd->core_id, type, userdata,
+                         actions, wc, put_actions, dp->upcall_aux);
 }
 
 static inline uint32_t
diff --git a/lib/flow.c b/lib/flow.c
index ba4f8c7..f4ac8b3 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -1298,6 +1298,7 @@ void flow_wildcards_init_for_packet(struct flow_wildcards *wc,
                 wc->masks.tunnel.metadata.present.map =
                                               flow->tunnel.metadata.present.map;
                 WC_MASK_FIELD(wc, tunnel.metadata.opts.u8);
+                WC_MASK_FIELD(wc, tunnel.metadata.tab);
             }
         } else {
             WC_MASK_FIELD(wc, tunnel.metadata.present.len);
diff --git a/lib/learning-switch.c b/lib/learning-switch.c
index 82609e8..30aab12 100644
--- a/lib/learning-switch.c
+++ b/lib/learning-switch.c
@@ -523,7 +523,8 @@ process_packet_in(struct lswitch *sw, const struct ofp_header *oh)
     struct dp_packet pkt;
     struct flow flow;
 
-    error = ofputil_decode_packet_in(oh, true, &pi, NULL, &buffer_id, NULL);
+    error = ofputil_decode_packet_in(oh, true, NULL, &pi, NULL,
+                                     &buffer_id, NULL);
     if (error) {
         VLOG_WARN_RL(&rl, "failed to decode packet-in: %s",
                      ofperr_to_string(error));
diff --git a/lib/match.c b/lib/match.c
index d78e6a1..3fcaec5 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -1463,11 +1463,14 @@ minimatch_matches_flow(const struct minimatch *match,
 /* Appends a string representation of 'match' to 's'.  If 'priority' is
  * different from OFP_DEFAULT_PRIORITY, includes it in 's'. */
 void
-minimatch_format(const struct minimatch *match, struct ds *s, int priority)
+minimatch_format(const struct minimatch *match,
+                 const struct tun_table *tun_table,struct ds *s, int priority)
 {
     struct match megamatch;
 
     minimatch_expand(match, &megamatch);
+    megamatch.flow.tunnel.metadata.tab = tun_table;
+
     match_format(&megamatch, s, priority);
 }
 
diff --git a/lib/nx-match.c b/lib/nx-match.c
index b03ccf2..9201aae 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -474,11 +474,13 @@ nx_pull_match_entry(struct ofpbuf *b, bool allow_cookie,
 
 static enum ofperr
 nx_pull_raw(const uint8_t *p, unsigned int match_len, bool strict,
-            struct match *match, ovs_be64 *cookie, ovs_be64 *cookie_mask)
+            struct match *match, ovs_be64 *cookie, ovs_be64 *cookie_mask,
+            const struct tun_table *tun_table)
 {
     ovs_assert((cookie != NULL) == (cookie_mask != NULL));
 
     match_init_catchall(match);
+    match->flow.tunnel.metadata.tab = tun_table;
     if (cookie) {
         *cookie = *cookie_mask = htonll(0);
     }
@@ -529,13 +531,15 @@ nx_pull_raw(const uint8_t *p, unsigned int match_len, bool strict,
         }
     }
 
+    match->flow.tunnel.metadata.tab = NULL;
     return 0;
 }
 
 static enum ofperr
 nx_pull_match__(struct ofpbuf *b, unsigned int match_len, bool strict,
                 struct match *match,
-                ovs_be64 *cookie, ovs_be64 *cookie_mask)
+                ovs_be64 *cookie, ovs_be64 *cookie_mask,
+                const struct tun_table *tun_table)
 {
     uint8_t *p = NULL;
 
@@ -549,7 +553,8 @@ nx_pull_match__(struct ofpbuf *b, unsigned int match_len, bool strict,
         }
     }
 
-    return nx_pull_raw(p, match_len, strict, match, cookie, cookie_mask);
+    return nx_pull_raw(p, match_len, strict, match, cookie, cookie_mask,
+                       tun_table);
 }
 
 /* Parses the nx_match formatted match description in 'b' with length
@@ -562,9 +567,11 @@ nx_pull_match__(struct ofpbuf *b, unsigned int match_len, bool strict,
  * Returns 0 if successful, otherwise an OpenFlow error code. */
 enum ofperr
 nx_pull_match(struct ofpbuf *b, unsigned int match_len, struct match *match,
-              ovs_be64 *cookie, ovs_be64 *cookie_mask)
+              ovs_be64 *cookie, ovs_be64 *cookie_mask,
+              const struct tun_table *tun_table)
 {
-    return nx_pull_match__(b, match_len, true, match, cookie, cookie_mask);
+    return nx_pull_match__(b, match_len, true, match, cookie, cookie_mask,
+                           tun_table);
 }
 
 /* Behaves the same as nx_pull_match(), but skips over unknown NXM headers,
@@ -572,13 +579,16 @@ nx_pull_match(struct ofpbuf *b, unsigned int match_len, struct match *match,
 enum ofperr
 nx_pull_match_loose(struct ofpbuf *b, unsigned int match_len,
                     struct match *match,
-                    ovs_be64 *cookie, ovs_be64 *cookie_mask)
+                    ovs_be64 *cookie, ovs_be64 *cookie_mask,
+                    const struct tun_table *tun_table)
 {
-    return nx_pull_match__(b, match_len, false, match, cookie, cookie_mask);
+    return nx_pull_match__(b, match_len, false, match, cookie, cookie_mask,
+                           tun_table);
 }
 
 static enum ofperr
-oxm_pull_match__(struct ofpbuf *b, bool strict, struct match *match)
+oxm_pull_match__(struct ofpbuf *b, bool strict,
+                 const struct tun_table *tun_table, struct match *match)
 {
     struct ofp11_match_header *omh = b->data;
     uint8_t *p;
@@ -606,7 +616,7 @@ oxm_pull_match__(struct ofpbuf *b, bool strict, struct match *match)
     }
 
     return nx_pull_raw(p + sizeof *omh, match_len - sizeof *omh,
-                       strict, match, NULL, NULL);
+                       strict, match, NULL, NULL, tun_table);
 }
 
 /* Parses the oxm formatted match description preceded by a struct
@@ -616,17 +626,19 @@ oxm_pull_match__(struct ofpbuf *b, bool strict, struct match *match)
  *
  * Returns 0 if successful, otherwise an OpenFlow error code. */
 enum ofperr
-oxm_pull_match(struct ofpbuf *b, struct match *match)
+oxm_pull_match(struct ofpbuf *b, const struct tun_table *tun_table,
+               struct match *match)
 {
-    return oxm_pull_match__(b, true, match);
+    return oxm_pull_match__(b, true, tun_table, match);
 }
 
 /* Behaves the same as oxm_pull_match() with one exception.  Skips over unknown
  * OXM headers instead of failing with an error when they are encountered. */
 enum ofperr
-oxm_pull_match_loose(struct ofpbuf *b, struct match *match)
+oxm_pull_match_loose(struct ofpbuf *b, const struct tun_table *tun_table,
+                     struct match *match)
 {
-    return oxm_pull_match__(b, false, match);
+    return oxm_pull_match__(b, false, tun_table, match);
 }
 
 /* Parses the OXM match description in the 'oxm_len' bytes in 'oxm'.  Stores
@@ -636,9 +648,10 @@ oxm_pull_match_loose(struct ofpbuf *b, struct match *match)
  *
  * Returns 0 if successful, otherwise an OpenFlow error code. */
 enum ofperr
-oxm_decode_match(const void *oxm, size_t oxm_len, struct match *match)
+oxm_decode_match(const void *oxm, size_t oxm_len,
+                 const struct tun_table *tun_table, struct match *match)
 {
-    return nx_pull_raw(oxm, oxm_len, true, match, NULL, NULL);
+    return nx_pull_raw(oxm, oxm_len, true, match, NULL, NULL, tun_table);
 }
 
 /* Verify an array of OXM TLVs treating value of each TLV as a mask,
diff --git a/lib/nx-match.h b/lib/nx-match.h
index c366a04..a86cceb 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -50,13 +50,18 @@ char *mf_parse_subfield(struct mf_subfield *, const char *s)
 /* Decoding matches. */
 enum ofperr nx_pull_match(struct ofpbuf *, unsigned int match_len,
                           struct match *,
-                          ovs_be64 *cookie, ovs_be64 *cookie_mask);
+                          ovs_be64 *cookie, ovs_be64 *cookie_mask,
+                          const struct tun_table *);
 enum ofperr nx_pull_match_loose(struct ofpbuf *, unsigned int match_len,
                                 struct match *, ovs_be64 *cookie,
-                                ovs_be64 *cookie_mask);
-enum ofperr oxm_pull_match(struct ofpbuf *, struct match *);
-enum ofperr oxm_pull_match_loose(struct ofpbuf *, struct match *);
-enum ofperr oxm_decode_match(const void *, size_t, struct match *);
+                                ovs_be64 *cookie_mask,
+                                const struct tun_table *);
+enum ofperr oxm_pull_match(struct ofpbuf *, const struct tun_table *,
+                           struct match *);
+enum ofperr oxm_pull_match_loose(struct ofpbuf *, const struct tun_table *,
+                                 struct match *);
+enum ofperr oxm_decode_match(const void *, size_t, const struct tun_table *,
+                             struct match *);
 enum ofperr oxm_pull_field_array(const void *, size_t fields_len,
                                  struct field_array *);
 
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 5a43904..65a6fcd 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -150,7 +150,7 @@ odp_set_tunnel_action(const struct nlattr *a, struct flow_tnl *tun_key)
 {
     enum odp_key_fitness fitness;
 
-    fitness = odp_tun_key_from_attr(a, true, tun_key);
+    fitness = odp_tun_key_from_attr(a, tun_key);
     ovs_assert(fitness != ODP_FIT_ERROR);
 }
 
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 6d29b67..332698b 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -1855,10 +1855,8 @@ ovs_frag_type_to_string(enum ovs_frag_type type)
 }
 
 static enum odp_key_fitness
-odp_tun_key_from_attr__(const struct nlattr *attr,
-                        const struct nlattr *flow_attrs, size_t flow_attr_len,
-                        const struct flow_tnl *src_tun, struct flow_tnl *tun,
-                        bool udpif)
+odp_tun_key_from_attr__(const struct nlattr *attr, bool is_mask,
+                        struct flow_tnl *tun)
 {
     unsigned int left;
     const struct nlattr *a;
@@ -1934,10 +1932,7 @@ odp_tun_key_from_attr__(const struct nlattr *attr,
             break;
         }
         case OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS:
-            if (tun_metadata_from_geneve_nlattr(a, flow_attrs, flow_attr_len,
-                                                src_tun, udpif, tun)) {
-                return ODP_FIT_ERROR;
-            }
+            tun_metadata_from_geneve_nlattr(a, is_mask, tun);
             break;
 
         default:
@@ -1958,11 +1953,10 @@ odp_tun_key_from_attr__(const struct nlattr *attr,
 }
 
 enum odp_key_fitness
-odp_tun_key_from_attr(const struct nlattr *attr, bool udpif,
-                      struct flow_tnl *tun)
+odp_tun_key_from_attr(const struct nlattr *attr, struct flow_tnl *tun)
 {
     memset(tun, 0, sizeof *tun);
-    return odp_tun_key_from_attr__(attr, NULL, 0, NULL, tun, udpif);
+    return odp_tun_key_from_attr__(attr, false, tun);
 }
 
 static void
@@ -4573,7 +4567,7 @@ odp_key_to_pkt_metadata(const struct nlattr *key, size_t key_len,
         case OVS_KEY_ATTR_TUNNEL: {
             enum odp_key_fitness res;
 
-            res = odp_tun_key_from_attr(nla, true, &md->tunnel);
+            res = odp_tun_key_from_attr(nla, &md->tunnel);
             if (res == ODP_FIT_ERROR) {
                 memset(&md->tunnel, 0, sizeof md->tunnel);
             } else if (res == ODP_FIT_PERFECT) {
@@ -5084,9 +5078,7 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
 
 static enum odp_key_fitness
 odp_flow_key_to_flow__(const struct nlattr *key, size_t key_len,
-                       const struct nlattr *src_key, size_t src_key_len,
-                       struct flow *flow, const struct flow *src_flow,
-                       bool udpif)
+                       struct flow *flow, const struct flow *src_flow)
 {
     const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1];
     uint64_t expected_attrs;
@@ -5150,10 +5142,8 @@ odp_flow_key_to_flow__(const struct nlattr *key, size_t key_len,
     if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_TUNNEL)) {
         enum odp_key_fitness res;
 
-        res = odp_tun_key_from_attr__(attrs[OVS_KEY_ATTR_TUNNEL],
-                                      is_mask ? src_key : NULL,
-                                      src_key_len, &src_flow->tunnel,
-                                      &flow->tunnel, udpif);
+        res = odp_tun_key_from_attr__(attrs[OVS_KEY_ATTR_TUNNEL], is_mask,
+                                      &flow->tunnel);
         if (res == ODP_FIT_ERROR) {
             return ODP_FIT_ERROR;
         } else if (res == ODP_FIT_PERFECT) {
@@ -5226,20 +5216,21 @@ enum odp_key_fitness
 odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
                      struct flow *flow)
 {
-   return odp_flow_key_to_flow__(key, key_len, NULL, 0, flow, flow, false);
+   return odp_flow_key_to_flow__(key, key_len, flow, flow);
 }
 
-static enum odp_key_fitness
-odp_flow_key_to_mask__(const struct nlattr *mask_key, size_t mask_key_len,
-                       const struct nlattr *flow_key, size_t flow_key_len,
-                       struct flow_wildcards *mask,
-                       const struct flow *src_flow,
-                       bool udpif)
+/* Converts the 'mask_key_len' bytes of OVS_KEY_ATTR_* attributes in 'mask_key'
+ * to a mask structure in 'mask'.  'flow' must be a previously translated flow
+ * corresponding to 'mask' and similarly flow_key/flow_key_len must be the
+ * attributes from that flow.  Returns an ODP_FIT_* value that indicates how
+ * well 'key' fits our expectations for what a flow key should contain. */
+enum odp_key_fitness
+odp_flow_key_to_mask(const struct nlattr *mask_key, size_t mask_key_len,
+                     struct flow_wildcards *mask, const struct flow *src_flow)
 {
     if (mask_key_len) {
         return odp_flow_key_to_flow__(mask_key, mask_key_len,
-                                      flow_key, flow_key_len,
-                                      &mask->masks, src_flow, udpif);
+                                      &mask->masks, src_flow);
 
     } else {
         /* A missing mask means that the flow should be exact matched.
@@ -5249,47 +5240,6 @@ odp_flow_key_to_mask__(const struct nlattr *mask_key, size_t mask_key_len,
         return ODP_FIT_PERFECT;
     }
 }
-/* Converts the 'mask_key_len' bytes of OVS_KEY_ATTR_* attributes in 'mask_key'
- * to a mask structure in 'mask'.  'flow' must be a previously translated flow
- * corresponding to 'mask' and similarly flow_key/flow_key_len must be the
- * attributes from that flow.  Returns an ODP_FIT_* value that indicates how
- * well 'key' fits our expectations for what a flow key should contain. */
-enum odp_key_fitness
-odp_flow_key_to_mask(const struct nlattr *mask_key, size_t mask_key_len,
-                     const struct nlattr *flow_key, size_t flow_key_len,
-                     struct flow_wildcards *mask, const struct flow *flow)
-{
-    return odp_flow_key_to_mask__(mask_key, mask_key_len,
-                                  flow_key, flow_key_len,
-                                  mask, flow, false);
-}
-
-/* These functions are similar to their non-"_udpif" variants but output a
- * 'flow' that is suitable for fast-path packet processing.
- *
- * Some fields have different representation for flow setup and per-
- * packet processing (i.e. different between ofproto-dpif and userspace
- * datapath). In particular, with the non-"_udpif" functions, struct
- * tun_metadata is in the per-flow format (using 'present.map' and 'opts.u8');
- * with these functions, struct tun_metadata is in the per-packet format
- * (using 'present.len' and 'opts.gnv'). */
-enum odp_key_fitness
-odp_flow_key_to_flow_udpif(const struct nlattr *key, size_t key_len,
-                           struct flow *flow)
-{
-   return odp_flow_key_to_flow__(key, key_len, NULL, 0, flow, flow, true);
-}
-
-enum odp_key_fitness
-odp_flow_key_to_mask_udpif(const struct nlattr *mask_key, size_t mask_key_len,
-                           const struct nlattr *flow_key, size_t flow_key_len,
-                           struct flow_wildcards *mask,
-                           const struct flow *flow)
-{
-    return odp_flow_key_to_mask__(mask_key, mask_key_len,
-                                  flow_key, flow_key_len,
-                                  mask, flow, true);
-}
 
 /* Returns 'fitness' as a string, for use in debug messages. */
 const char *
diff --git a/lib/odp-util.h b/lib/odp-util.h
index a41bc76..ccdbf8e 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -150,11 +150,12 @@ struct odputil_keybuf {
     uint32_t keybuf[DIV_ROUND_UP(ODPUTIL_FLOW_KEY_BYTES, 4)];
 };
 
-enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *, bool udpif,
+enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *,
                                            struct flow_tnl *);
 
 int odp_ufid_from_string(const char *s_, ovs_u128 *ufid);
 void odp_format_ufid(const ovs_u128 *ufid, struct ds *);
+
 void odp_flow_format(const struct nlattr *key, size_t key_len,
                      const struct nlattr *mask, size_t mask_len,
                      const struct hmap *portno_names, struct ds *,
@@ -232,20 +233,9 @@ enum odp_key_fitness odp_flow_key_to_flow(const struct nlattr *, size_t,
                                           struct flow *);
 enum odp_key_fitness odp_flow_key_to_mask(const struct nlattr *mask_key,
                                           size_t mask_key_len,
-                                          const struct nlattr *flow_key,
-                                          size_t flow_key_len,
                                           struct flow_wildcards *mask,
                                           const struct flow *flow);
 
-enum odp_key_fitness odp_flow_key_to_flow_udpif(const struct nlattr *, size_t,
-                                                struct flow *);
-enum odp_key_fitness odp_flow_key_to_mask_udpif(const struct nlattr *mask_key,
-                                                size_t mask_key_len,
-                                                const struct nlattr *flow_key,
-                                                size_t flow_key_len,
-                                                struct flow_wildcards *mask,
-                                                const struct flow *flow);
-
 const char *odp_key_fitness_to_string(enum odp_key_fitness);
 
 void commit_odp_tunnel_action(const struct flow *, struct flow *base,
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index d3ef140..d5e1057 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -1122,7 +1122,8 @@ parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
  * problem. */
 char *
 parse_ofp_exact_flow(struct flow *flow, struct flow_wildcards *wc,
-                     const char *s, const struct simap *portno_names)
+                     const struct tun_table *tun_table, const char *s,
+                     const struct simap *portno_names)
 {
     char *pos, *key, *value_s;
     char *error = NULL;
@@ -1132,6 +1133,7 @@ parse_ofp_exact_flow(struct flow *flow, struct flow_wildcards *wc,
     if (wc) {
         memset(wc, 0, sizeof *wc);
     }
+    flow->tunnel.metadata.tab = tun_table;
 
     pos = copy = xstrdup(s);
     while (ofputil_parse_key_value(&pos, &key, &value_s)) {
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 0b3bf01..917dce8 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -118,7 +118,7 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
     size_t total_len;
     enum ofperr error;
 
-    error = ofputil_decode_packet_in_private(oh, true,
+    error = ofputil_decode_packet_in_private(oh, true, NULL,
                                              &pin, &total_len, &buffer_id);
     if (error) {
         ofp_print_error(string, error);
@@ -786,7 +786,7 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh, int verbosity)
     protocol = ofputil_protocol_set_tid(protocol, true);
 
     ofpbuf_init(&ofpacts, 64);
-    error = ofputil_decode_flow_mod(&fm, oh, protocol, &ofpacts,
+    error = ofputil_decode_flow_mod(&fm, oh, protocol, NULL, &ofpacts,
                                     OFPP_MAX, 255);
     if (error) {
         ofpbuf_uninit(&ofpacts);
@@ -1591,7 +1591,7 @@ ofp_print_flow_stats_request(struct ds *string, const struct ofp_header *oh)
     struct ofputil_flow_stats_request fsr;
     enum ofperr error;
 
-    error = ofputil_decode_flow_stats_request(&fsr, oh);
+    error = ofputil_decode_flow_stats_request(&fsr, oh, NULL);
     if (error) {
         ofp_print_error(string, error);
         return;
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 1fa4998..0695d3c 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -277,8 +277,8 @@ ofputil_match_to_ofp10_match(const struct match *match,
 }
 
 enum ofperr
-ofputil_pull_ofp11_match(struct ofpbuf *buf, struct match *match,
-                         uint16_t *padded_match_len)
+ofputil_pull_ofp11_match(struct ofpbuf *buf, const struct tun_table *tun_table,
+                         struct match *match, uint16_t *padded_match_len)
 {
     struct ofp11_match_header *omh = buf->data;
     uint16_t match_len;
@@ -307,7 +307,7 @@ ofputil_pull_ofp11_match(struct ofpbuf *buf, struct match *match,
         if (padded_match_len) {
             *padded_match_len = ROUND_UP(match_len, 8);
         }
-        return oxm_pull_match(buf, match);
+        return oxm_pull_match(buf, tun_table, match);
 
     default:
         return OFPERR_OFPBMC_BAD_TYPE;
@@ -1570,6 +1570,7 @@ enum ofperr
 ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
                         const struct ofp_header *oh,
                         enum ofputil_protocol protocol,
+                        const struct tun_table *tun_table,
                         struct ofpbuf *ofpacts,
                         ofp_port_t max_port, uint8_t max_table)
 {
@@ -1583,7 +1584,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
 
         ofm = ofpbuf_pull(&b, sizeof *ofm);
 
-        error = ofputil_pull_ofp11_match(&b, &fm->match, NULL);
+        error = ofputil_pull_ofp11_match(&b, tun_table, &fm->match, NULL);
         if (error) {
             return error;
         }
@@ -1678,7 +1679,8 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
             /* Dissect the message. */
             nfm = ofpbuf_pull(&b, sizeof *nfm);
             error = nx_pull_match(&b, ntohs(nfm->match_len),
-                                  &fm->match, &fm->cookie, &fm->cookie_mask);
+                                  &fm->match, &fm->cookie, &fm->cookie_mask,
+                                  tun_table);
             if (error) {
                 return error;
             }
@@ -2267,7 +2269,8 @@ ofputil_decode_ofpst10_flow_request(struct ofputil_flow_stats_request *fsr,
 
 static enum ofperr
 ofputil_decode_ofpst11_flow_request(struct ofputil_flow_stats_request *fsr,
-                                    struct ofpbuf *b, bool aggregate)
+                                    struct ofpbuf *b, bool aggregate,
+                                    const struct tun_table *tun_table)
 {
     const struct ofp11_flow_stats_request *ofsr;
     enum ofperr error;
@@ -2282,7 +2285,7 @@ ofputil_decode_ofpst11_flow_request(struct ofputil_flow_stats_request *fsr,
     fsr->out_group = ntohl(ofsr->out_group);
     fsr->cookie = ofsr->cookie;
     fsr->cookie_mask = ofsr->cookie_mask;
-    error = ofputil_pull_ofp11_match(b, &fsr->match, NULL);
+    error = ofputil_pull_ofp11_match(b, tun_table, &fsr->match, NULL);
     if (error) {
         return error;
     }
@@ -2292,14 +2295,15 @@ ofputil_decode_ofpst11_flow_request(struct ofputil_flow_stats_request *fsr,
 
 static enum ofperr
 ofputil_decode_nxst_flow_request(struct ofputil_flow_stats_request *fsr,
-                                 struct ofpbuf *b, bool aggregate)
+                                 struct ofpbuf *b, bool aggregate,
+                                 const struct tun_table *tun_table)
 {
     const struct nx_flow_stats_request *nfsr;
     enum ofperr error;
 
     nfsr = ofpbuf_pull(b, sizeof *nfsr);
     error = nx_pull_match(b, ntohs(nfsr->match_len), &fsr->match,
-                          &fsr->cookie, &fsr->cookie_mask);
+                          &fsr->cookie, &fsr->cookie_mask, tun_table);
     if (error) {
         return error;
     }
@@ -2712,7 +2716,8 @@ ofputil_pull_queue_get_config_reply(struct ofpbuf *msg,
  * successful, otherwise an OpenFlow error code. */
 enum ofperr
 ofputil_decode_flow_stats_request(struct ofputil_flow_stats_request *fsr,
-                                  const struct ofp_header *oh)
+                                  const struct ofp_header *oh,
+                                  const struct tun_table *tun_table)
 {
     struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
     enum ofpraw raw = ofpraw_pull_assert(&b);
@@ -2724,16 +2729,16 @@ ofputil_decode_flow_stats_request(struct ofputil_flow_stats_request *fsr,
         return ofputil_decode_ofpst10_flow_request(fsr, b.data, true);
 
     case OFPRAW_OFPST11_FLOW_REQUEST:
-        return ofputil_decode_ofpst11_flow_request(fsr, &b, false);
+        return ofputil_decode_ofpst11_flow_request(fsr, &b, false, tun_table);
 
     case OFPRAW_OFPST11_AGGREGATE_REQUEST:
-        return ofputil_decode_ofpst11_flow_request(fsr, &b, true);
+        return ofputil_decode_ofpst11_flow_request(fsr, &b, true, tun_table);
 
     case OFPRAW_NXST_FLOW_REQUEST:
-        return ofputil_decode_nxst_flow_request(fsr, &b, false);
+        return ofputil_decode_nxst_flow_request(fsr, &b, false, tun_table);
 
     case OFPRAW_NXST_AGGREGATE_REQUEST:
-        return ofputil_decode_nxst_flow_request(fsr, &b, true);
+        return ofputil_decode_nxst_flow_request(fsr, &b, true, tun_table);
 
     default:
         /* Hey, the caller lied. */
@@ -2877,7 +2882,8 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
             return EINVAL;
         }
 
-        if (ofputil_pull_ofp11_match(msg, &fs->match, &padded_match_len)) {
+        if (ofputil_pull_ofp11_match(msg, NULL, &fs->match,
+                                     &padded_match_len)) {
             VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST_FLOW reply bad match");
             return EINVAL;
         }
@@ -2959,7 +2965,7 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
                          "claims invalid length %"PRIuSIZE, match_len, length);
             return EINVAL;
         }
-        if (nx_pull_match(msg, match_len, &fs->match, NULL, NULL)) {
+        if (nx_pull_match(msg, match_len, &fs->match, NULL, NULL, NULL)) {
             return EINVAL;
         }
         instructions_len = length - sizeof *nfs - ROUND_UP(match_len, 8);
@@ -3015,13 +3021,20 @@ unknown_to_zero(uint64_t count)
  * have been initialized with ofpmp_init(). */
 void
 ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
-                                struct ovs_list *replies)
+                                struct ovs_list *replies,
+                                const struct tun_table *tun_table)
 {
+    struct ofputil_flow_stats *fs_ = CONST_CAST(struct ofputil_flow_stats *,
+                                                fs);
+    const struct tun_table *orig_tun_table;
     struct ofpbuf *reply = ofpbuf_from_list(ovs_list_back(replies));
     size_t start_ofs = reply->size;
     enum ofp_version version = ofpmp_version(replies);
     enum ofpraw raw = ofpmp_decode_raw(replies);
 
+    orig_tun_table = fs->match.flow.tunnel.metadata.tab;
+    fs_->match.flow.tunnel.metadata.tab = tun_table;
+
     if (raw == OFPRAW_OFPST11_FLOW_REPLY || raw == OFPRAW_OFPST13_FLOW_REPLY) {
         struct ofp11_flow_stats *ofs;
 
@@ -3107,6 +3120,7 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
     }
 
     ofpmp_postappend(replies, start_ofs);
+    fs_->match.flow.tunnel.metadata.tab = orig_tun_table;
 }
 
 /* Converts abstract ofputil_aggregate_stats 'stats' into an OFPST_AGGREGATE or
@@ -3170,7 +3184,7 @@ ofputil_decode_flow_removed(struct ofputil_flow_removed *fr,
 
         ofr = ofpbuf_pull(&b, sizeof *ofr);
 
-        error = ofputil_pull_ofp11_match(&b, &fr->match, NULL);
+        error = ofputil_pull_ofp11_match(&b, NULL, &fr->match, NULL);
         if (error) {
             return error;
         }
@@ -3206,7 +3220,7 @@ ofputil_decode_flow_removed(struct ofputil_flow_removed *fr,
         enum ofperr error;
 
         nfr = ofpbuf_pull(&b, sizeof *nfr);
-        error = nx_pull_match(&b, ntohs(nfr->match_len), &fr->match,
+        error = nx_pull_match(&b, ntohs(nfr->match_len), &fr->match, NULL,
                               NULL, NULL);
         if (error) {
             return error;
@@ -3328,6 +3342,7 @@ ofputil_encode_flow_removed(const struct ofputil_flow_removed *fr,
  * arguments needs to be initialized. */
 static enum ofperr
 decode_nx_packet_in2(const struct ofp_header *oh, bool loose,
+                     const struct tun_table *tun_table,
                      struct ofputil_packet_in *pin,
                      size_t *total_len, uint32_t *buffer_id,
                      struct ofpbuf *continuation)
@@ -3382,7 +3397,7 @@ decode_nx_packet_in2(const struct ofp_header *oh, bool loose,
 
         case NXPINT_METADATA:
             error = oxm_decode_match(payload.msg, ofpbuf_msgsize(&payload),
-                                     &pin->flow_metadata);
+                                     tun_table, &pin->flow_metadata);
             break;
 
         case NXPINT_USERDATA:
@@ -3439,6 +3454,7 @@ decode_nx_packet_in2(const struct ofp_header *oh, bool loose,
  * Returns 0 if successful, otherwise an OpenFlow error code. */
 enum ofperr
 ofputil_decode_packet_in(const struct ofp_header *oh, bool loose,
+                         const struct tun_table *tun_table,
                          struct ofputil_packet_in *pin,
                          size_t *total_lenp, uint32_t *buffer_idp,
                          struct ofpbuf *continuation)
@@ -3459,7 +3475,8 @@ ofputil_decode_packet_in(const struct ofp_header *oh, bool loose,
         const ovs_be64 *cookie = (raw == OFPRAW_OFPT13_PACKET_IN
                                   ? ofpbuf_pull(&b, sizeof *cookie)
                                   : NULL);
-        enum ofperr error = oxm_pull_match_loose(&b, &pin->flow_metadata);
+        enum ofperr error = oxm_pull_match_loose(&b, tun_table,
+                                                 &pin->flow_metadata);
         if (error) {
             return error;
         }
@@ -3518,7 +3535,7 @@ ofputil_decode_packet_in(const struct ofp_header *oh, bool loose,
 
         npi = ofpbuf_pull(&b, sizeof *npi);
         error = nx_pull_match_loose(&b, ntohs(npi->match_len),
-                                    &pin->flow_metadata, NULL, NULL);
+                                    &pin->flow_metadata, NULL, NULL, NULL);
         if (error) {
             return error;
         }
@@ -3537,8 +3554,9 @@ ofputil_decode_packet_in(const struct ofp_header *oh, bool loose,
         pin->packet = b.data;
         pin->packet_len = b.size;
     } else if (raw == OFPRAW_NXT_PACKET_IN2 || raw == OFPRAW_NXT_RESUME) {
-        enum ofperr error = decode_nx_packet_in2(oh, loose, pin, &total_len,
-                                                 &buffer_id, continuation);
+        enum ofperr error = decode_nx_packet_in2(oh, loose, tun_table, pin,
+                                                 &total_len, &buffer_id,
+                                                 continuation);
         if (error) {
             return error;
         }
@@ -4015,6 +4033,7 @@ parse_actions_property(struct ofpbuf *property, enum ofp_version version,
  * ofputil_packet_in_private_destroy() to free this data. */
 enum ofperr
 ofputil_decode_packet_in_private(const struct ofp_header *oh, bool loose,
+                                 const struct tun_table *tun_table,
                                  struct ofputil_packet_in_private *pin,
                                  size_t *total_len, uint32_t *buffer_id)
 {
@@ -4022,8 +4041,8 @@ ofputil_decode_packet_in_private(const struct ofp_header *oh, bool loose,
 
     struct ofpbuf continuation;
     enum ofperr error;
-    error = ofputil_decode_packet_in(oh, loose, &pin->public, total_len,
-                                     buffer_id, &continuation);
+    error = ofputil_decode_packet_in(oh, loose, tun_table, &pin->public,
+                                     total_len, buffer_id, &continuation);
     if (error) {
         return error;
     }
@@ -6585,7 +6604,8 @@ ofputil_decode_flow_monitor_request(struct ofputil_flow_monitor_request *rq,
     rq->out_port = u16_to_ofp(ntohs(nfmr->out_port));
     rq->table_id = nfmr->table_id;
 
-    return nx_pull_match(msg, ntohs(nfmr->match_len), &rq->match, NULL, NULL);
+    return nx_pull_match(msg, ntohs(nfmr->match_len), &rq->match, NULL,
+                         NULL, NULL);
 }
 
 void
@@ -6693,7 +6713,7 @@ ofputil_decode_flow_update(struct ofputil_flow_update *update,
         update->cookie = nfuf->cookie;
         update->priority = ntohs(nfuf->priority);
 
-        error = nx_pull_match(msg, match_len, update->match, NULL, NULL);
+        error = nx_pull_match(msg, match_len, update->match, NULL, NULL, NULL);
         if (error) {
             return error;
         }
diff --git a/lib/tun-metadata.c b/lib/tun-metadata.c
index 36006e3..92643b3 100644
--- a/lib/tun-metadata.c
+++ b/lib/tun-metadata.c
@@ -25,7 +25,6 @@
 #include "nx-match.h"
 #include "odp-netlink.h"
 #include "openvswitch/ofp-util.h"
-#include "ovs-thread.h"
 #include "ovs-rcu.h"
 #include "packets.h"
 #include "tun-metadata.h"
@@ -52,14 +51,10 @@ struct tun_table {
 };
 BUILD_ASSERT_DECL(TUN_METADATA_TOT_OPT_SIZE % 4 == 0);
 
-static struct ovs_mutex tab_mutex = OVS_MUTEX_INITIALIZER;
-static OVSRCU_TYPE(struct tun_table *) metadata_tab;
-
 static enum ofperr tun_metadata_add_entry(struct tun_table *map, uint8_t idx,
                                           uint16_t opt_class, uint8_t type,
-                                          uint8_t len) OVS_REQUIRES(tab_mutex);
-static void tun_metadata_del_entry(struct tun_table *map, uint8_t idx)
-            OVS_REQUIRES(tab_mutex);
+                                          uint8_t len);
+static void tun_metadata_del_entry(struct tun_table *map, uint8_t idx);
 static void memcpy_to_metadata(struct tun_metadata *dst, const void *src,
                                const struct tun_metadata_loc *,
                                unsigned int idx);
@@ -86,8 +81,8 @@ tun_key_type(uint32_t key)
 
 /* Returns a newly allocated tun_table.  If 'old_map' is nonnull then the new
  * tun_table is a deep copy of the old one. */
-static struct tun_table *
-table_alloc(const struct tun_table *old_map) OVS_REQUIRES(tab_mutex)
+struct tun_table *
+tun_metadata_alloc(const struct tun_table *old_map)
 {
     struct tun_table *new_map;
 
@@ -120,8 +115,8 @@ table_alloc(const struct tun_table *old_map) OVS_REQUIRES(tab_mutex)
 }
 
 /* Frees 'map' and all the memory it owns. */
-static void
-table_free(struct tun_table *map) OVS_REQUIRES(tab_mutex)
+void
+tun_metadata_free(struct tun_table *map)
 {
     struct tun_meta_entry *entry;
 
@@ -137,74 +132,60 @@ table_free(struct tun_table *map) OVS_REQUIRES(tab_mutex)
     free(map);
 }
 
-/* Creates a global tunnel metadata mapping table, if none already exists. */
 void
-tun_metadata_init(void)
+tun_metadata_postpone_free(struct tun_table *tab)
 {
-    ovs_mutex_lock(&tab_mutex);
-
-    if (!ovsrcu_get_protected(struct tun_table *, &metadata_tab)) {
-        ovsrcu_set(&metadata_tab, table_alloc(NULL));
-    }
-
-    ovs_mutex_unlock(&tab_mutex);
+    ovsrcu_postpone(tun_metadata_free, tab);
 }
 
 enum ofperr
-tun_metadata_table_mod(struct ofputil_tlv_table_mod *ttm)
+tun_metadata_table_mod(struct ofputil_tlv_table_mod *ttm,
+                       const struct tun_table *old_tab,
+                       struct tun_table **new_tab)
 {
-    struct tun_table *old_map, *new_map;
     struct ofputil_tlv_map *ofp_map;
     enum ofperr err = 0;
 
-    ovs_mutex_lock(&tab_mutex);
-
-    old_map = ovsrcu_get_protected(struct tun_table *, &metadata_tab);
-
     switch (ttm->command) {
     case NXTTMC_ADD:
-        new_map = table_alloc(old_map);
+        *new_tab = tun_metadata_alloc(old_tab);
 
         LIST_FOR_EACH (ofp_map, list_node, &ttm->mappings) {
-            err = tun_metadata_add_entry(new_map, ofp_map->index,
+            err = tun_metadata_add_entry(*new_tab, ofp_map->index,
                                          ofp_map->option_class,
                                          ofp_map->option_type,
                                          ofp_map->option_len);
             if (err) {
-                table_free(new_map);
-                goto out;
+                *new_tab = NULL;
+                tun_metadata_free(*new_tab);
+                return err;
             }
         }
         break;
 
     case NXTTMC_DELETE:
-        new_map = table_alloc(old_map);
+        *new_tab = tun_metadata_alloc(old_tab);
 
         LIST_FOR_EACH (ofp_map, list_node, &ttm->mappings) {
-            tun_metadata_del_entry(new_map, ofp_map->index);
+            tun_metadata_del_entry(*new_tab, ofp_map->index);
         }
         break;
 
     case NXTTMC_CLEAR:
-        new_map = table_alloc(NULL);
+        *new_tab = tun_metadata_alloc(NULL);
         break;
 
     default:
         OVS_NOT_REACHED();
     }
 
-    ovsrcu_set(&metadata_tab, new_map);
-    ovsrcu_postpone(table_free, old_map);
-
-out:
-    ovs_mutex_unlock(&tab_mutex);
-    return err;
+    return 0;
 }
 
 void
-tun_metadata_table_request(struct ofputil_tlv_table_reply *ttr)
+tun_metadata_table_request(const struct tun_table *tun_table,
+                           struct ofputil_tlv_table_reply *ttr)
 {
-    struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
     int i;
 
     ttr->max_option_space = TUN_METADATA_TOT_OPT_SIZE;
@@ -212,7 +193,7 @@ tun_metadata_table_request(struct ofputil_tlv_table_reply *ttr)
     ovs_list_init(&ttr->mappings);
 
     for (i = 0; i < TUN_METADATA_NUM_OPTS; i++) {
-        struct tun_meta_entry *entry = &map->entries[i];
+        const struct tun_meta_entry *entry = &tun_table->entries[i];
         struct ofputil_tlv_map *map;
 
         if (!entry->valid) {
@@ -233,16 +214,16 @@ tun_metadata_table_request(struct ofputil_tlv_table_reply *ttr)
  *
  * 'mf' must be an MFF_TUN_METADATA* field.
  *
- * This uses the global tunnel metadata mapping table created by
- * tun_metadata_init().  If no such table has been created or if 'mf' hasn't
- * been allocated in it yet, this just zeros 'value'. */
+ * This uses the tunnel metadata mapping table created by tun_metadata_alloc().
+ * If no such table has been created or if 'mf' hasn't been allocated in it yet,
+ * this just zeros 'value'. */
 void
 tun_metadata_read(const struct flow_tnl *tnl,
                   const struct mf_field *mf, union mf_value *value)
 {
-    struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
+    const struct tun_table *map = tnl->metadata.tab;
     unsigned int idx = mf->id - MFF_TUN_METADATA0;
-    struct tun_metadata_loc *loc;
+    const struct tun_metadata_loc *loc;
 
     if (!map) {
         memset(value->tun_metadata, 0, mf->n_bytes);
@@ -260,16 +241,16 @@ tun_metadata_read(const struct flow_tnl *tnl,
  *
  * 'mf' must be an MFF_TUN_METADATA* field.
  *
- * This uses the global tunnel metadata mapping table created by
- * tun_metadata_init().  If no such table has been created or if 'mf' hasn't
- * been allocated in it yet, this function does nothing. */
+ * This uses the tunnel metadata mapping table created by tun_metadata_alloc().
+ * If no such table has been created or if 'mf' hasn't been allocated in it yet,
+ * this function does nothing. */
 void
 tun_metadata_write(struct flow_tnl *tnl,
                    const struct mf_field *mf, const union mf_value *value)
 {
-    struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
+    const struct tun_table *map = tnl->metadata.tab;
     unsigned int idx = mf->id - MFF_TUN_METADATA0;
-    struct tun_metadata_loc *loc;
+    const struct tun_metadata_loc *loc;
 
     if (!map || !map->entries[idx].valid) {
         return;
@@ -281,7 +262,7 @@ tun_metadata_write(struct flow_tnl *tnl,
 }
 
 static const struct tun_metadata_loc *
-metadata_loc_from_match(struct tun_table *map, struct match *match,
+metadata_loc_from_match(const struct tun_table *map, struct match *match,
                         const char *name, unsigned int idx,
                         unsigned int field_len, bool masked, char **err_str)
 {
@@ -333,11 +314,12 @@ metadata_loc_from_match(struct tun_table *map, struct match *match,
  *
  * 'mf' must be an MFF_TUN_METADATA* field. 'match' must be in non-UDPIF format.
  *
- * If there is global tunnel metadata matching table, this function is
- * effective only if there is already a mapping for 'mf'.  Otherwise, the
- * metadata mapping table integrated into 'match' is used, adding 'mf' to its
- * mapping table if it isn't already mapped (and if there is room).  If 'mf'
- * isn't or can't be mapped, this function returns without modifying 'match'.
+ * If there is a tunnel metadata mapping table associated with the switch,
+ * this function is effective only if there is already a mapping for 'mf'.
+ * Otherwise, the metadata mapping table integrated into 'match' is used,
+ * adding 'mf' to its mapping table if it isn't already mapped (and if there
+ * is room).  If 'mf' isn't or can't be mapped, this function returns without
+ * modifying 'match'.
  *
  * 'value' may be NULL; if so, then 'mf' is made to match on an all-zeros
  * value.
@@ -353,7 +335,7 @@ tun_metadata_set_match(const struct mf_field *mf, const union mf_value *value,
                        const union mf_value *mask, struct match *match,
                        char **err_str)
 {
-    struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
+    const struct tun_table *map = match->flow.tunnel.metadata.tab;
     const struct tun_metadata_loc *loc;
     unsigned int idx = mf->id - MFF_TUN_METADATA0;
     unsigned int field_len;
@@ -361,8 +343,6 @@ tun_metadata_set_match(const struct mf_field *mf, const union mf_value *value,
     unsigned int data_offset;
     union mf_value data;
 
-    ovs_assert(!(match->flow.tunnel.flags & FLOW_TNL_F_UDPIF));
-
     field_len = mf_field_len(mf, value, mask, &is_masked);
     loc = metadata_loc_from_match(map, match, mf->name, idx, field_len,
                                   is_masked, err_str);
@@ -397,64 +377,23 @@ tun_metadata_set_match(const struct mf_field *mf, const union mf_value *value,
                        loc, idx);
 }
 
-static bool
-udpif_to_parsed(const struct flow_tnl *flow, const struct flow_tnl *mask,
-                struct flow_tnl *flow_xlate, struct flow_tnl *mask_xlate)
-{
-    if (flow->flags & FLOW_TNL_F_UDPIF) {
-        int err;
-
-        err = tun_metadata_from_geneve_udpif(flow, flow, flow_xlate);
-        if (err) {
-            return false;
-        }
-
-        if (mask) {
-            tun_metadata_from_geneve_udpif(flow, mask, mask_xlate);
-            if (err) {
-                return false;
-            }
-        }
-    } else {
-        if (flow->metadata.present.map == 0) {
-            /* There is no tunnel metadata, don't bother copying. */
-            return false;
-        }
-
-        memcpy(flow_xlate, flow, sizeof *flow_xlate);
-        if (mask) {
-            memcpy(mask_xlate, mask, sizeof *mask_xlate);
-        }
-
-        if (!flow_xlate->metadata.tab) {
-            flow_xlate->metadata.tab = ovsrcu_get(struct tun_table *,
-                                                  &metadata_tab);
-        }
-    }
-
-    return true;
-}
-
-/* Copies all MFF_TUN_METADATA* fields from 'tnl' to 'flow_metadata'. */
+/* Copies all MFF_TUN_METADATA* fields from 'tnl' to 'flow_metadata'. This
+ * is called during action translation and therefore 'tnl' must be in
+ * non-udpif format. */
 void
 tun_metadata_get_fmd(const struct flow_tnl *tnl, struct match *flow_metadata)
 {
-    struct flow_tnl flow;
     int i;
 
-    if (!udpif_to_parsed(tnl, NULL, &flow, NULL)) {
-        return;
-    }
-
-    ULLONG_FOR_EACH_1 (i, flow.metadata.present.map) {
+    ULLONG_FOR_EACH_1 (i, tnl->metadata.present.map) {
         union mf_value opts;
-        const struct tun_metadata_loc *old_loc = &flow.metadata.tab->entries[i].loc;
+        const struct tun_metadata_loc *old_loc = &tnl->metadata.tab->entries[i].loc;
         const struct tun_metadata_loc *new_loc;
 
         new_loc = metadata_loc_from_match(NULL, flow_metadata, NULL, i,
                                           old_loc->len, false, NULL);
 
-        memcpy_from_metadata(opts.tun_metadata, &flow.metadata, old_loc);
+        memcpy_from_metadata(opts.tun_metadata, &tnl->metadata, old_loc);
         memcpy_to_metadata(&flow_metadata->flow.tunnel.metadata,
                            opts.tun_metadata, new_loc, i);
 
@@ -518,7 +457,6 @@ memcpy_from_metadata(void *dst, const struct tun_metadata *src,
 static int
 tun_metadata_alloc_chain(struct tun_table *map, uint8_t len,
                          struct tun_metadata_loc_chain *loc)
-                         OVS_REQUIRES(tab_mutex)
 {
     int alloc_len = len / 4;
     int scan_start = 0;
@@ -563,7 +501,7 @@ found:
 
 static enum ofperr
 tun_metadata_add_entry(struct tun_table *map, uint8_t idx, uint16_t opt_class,
-                       uint8_t type, uint8_t len) OVS_REQUIRES(tab_mutex)
+                       uint8_t type, uint8_t len)
 {
     struct tun_meta_entry *entry;
     struct tun_metadata_loc_chain *cur_chain, *prev_chain;
@@ -614,7 +552,6 @@ tun_metadata_add_entry(struct tun_table *map, uint8_t idx, uint16_t opt_class,
 
 static void
 tun_metadata_del_entry(struct tun_table *map, uint8_t idx)
-                       OVS_REQUIRES(tab_mutex)
 {
     struct tun_meta_entry *entry;
     struct tun_metadata_loc_chain *chain;
@@ -645,25 +582,49 @@ tun_metadata_del_entry(struct tun_table *map, uint8_t idx)
     memset(&entry->loc, 0, sizeof entry->loc);
 }
 
-static int
-tun_metadata_from_geneve__(const struct tun_metadata *flow_metadata,
-                           const struct geneve_opt *opt,
-                           const struct geneve_opt *flow_opt, int opts_len,
-                           struct tun_metadata *metadata)
+/* Converts from Geneve netlink attributes in 'attr' to tunnel metadata
+ * in 'tun'. In reality, there is very little conversion done since we are
+ * just copying over the tunnel options in the form that they were received
+ * on the wire. By always using UDPIF format, this allows us to process the
+ * flow key without any knowledge of the mapping table. We can do the
+ * conversion later if necessary. */
+void
+tun_metadata_from_geneve_nlattr(const struct nlattr *attr, bool is_mask,
+                                struct flow_tnl *tun)
 {
-    struct tun_table *map;
-    bool is_mask = flow_opt != opt;
+    int attr_len = nl_attr_get_size(attr);
+
+    memcpy(tun->metadata.opts.gnv, nl_attr_get(attr), attr_len);
+    tun->flags |= FLOW_TNL_F_UDPIF;
 
     if (!is_mask) {
-        map = ovsrcu_get(struct tun_table *, &metadata_tab);
-        metadata->tab = map;
+        tun->metadata.present.len = attr_len;
     } else {
-        map = flow_metadata->tab;
+        /* We need to exact match on the length so we don't
+         * accidentally match on sets of options that are the same
+         * at the beginning but with additional options after. */
+        tun->metadata.present.len = 0xff;
     }
+}
 
-    if (!map) {
-        return 0;
-    }
+/* Converts from the flat Geneve options representation extracted directly
+ * from the tunnel header to the representation that maps options to
+ * pre-allocated locations. The original version (in UDPIF form) is passed
+ * in 'src' and the translated form in stored in 'dst'.  To handle masks, the
+ * flow must also be passed in through 'flow' (in the original, raw form). */
+int
+tun_metadata_from_geneve_udpif(const struct tun_table *tun_tab,
+                               const struct flow_tnl *flow,
+                               const struct flow_tnl *src,
+                               struct flow_tnl *dst)
+{
+    const struct geneve_opt *opt = src->metadata.opts.gnv;
+    const struct geneve_opt *flow_opt = flow->metadata.opts.gnv;
+    int opts_len = flow->metadata.present.len;
+
+    dst->metadata.tab = tun_tab;
+    dst->flags = src->flags & ~FLOW_TNL_F_UDPIF;
+    dst->metadata.present.map = 0;
 
     while (opts_len > 0) {
         int len;
@@ -678,13 +639,13 @@ tun_metadata_from_geneve__(const struct tun_metadata *flow_metadata,
             return EINVAL;
         }
 
-        entry = tun_meta_find_key(&map->key_hmap,
+        entry = tun_meta_find_key(&tun_tab->key_hmap,
                                   tun_meta_key(flow_opt->opt_class,
                                                flow_opt->type));
         if (entry) {
             if (entry->loc.len == flow_opt->length * 4) {
-                memcpy_to_metadata(metadata, opt + 1, &entry->loc,
-                                   entry - map->entries);
+                memcpy_to_metadata(&dst->metadata, opt + 1, &entry->loc,
+                                   entry - tun_tab->entries);
             } else {
                 return EINVAL;
             }
@@ -700,115 +661,16 @@ tun_metadata_from_geneve__(const struct tun_metadata *flow_metadata,
     return 0;
 }
 
-static const struct nlattr *
-tun_metadata_find_geneve_key(const struct nlattr *key, uint32_t key_len)
-{
-    const struct nlattr *tnl_key;
-
-    tnl_key = nl_attr_find__(key, key_len, OVS_KEY_ATTR_TUNNEL);
-    if (!tnl_key) {
-        return NULL;
-    }
-
-    return nl_attr_find_nested(tnl_key, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
-}
-
-/* Converts from Geneve netlink attributes in 'attr' to tunnel metadata
- * in 'tun'. The result may either in be UDPIF format or not, as determined
- * by 'udpif'.
- *
- * In the event that a mask is being converted, it is also necessary to
- * pass in flow information. This includes the full set of netlink attributes
- * (i.e. not just the Geneve attribute) in 'flow_attrs'/'flow_attr_len' and
- * the previously converted tunnel metadata 'flow_tun'.
- *
- * If a flow rather than mask is being converted, 'flow_attrs' must be NULL. */
-int
-tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
-                                const struct nlattr *flow_attrs,
-                                size_t flow_attr_len,
-                                const struct flow_tnl *flow_tun, bool udpif,
-                                struct flow_tnl *tun)
-{
-    bool is_mask = !!flow_attrs;
-    int attr_len = nl_attr_get_size(attr);
-    const struct nlattr *flow;
-
-    /* No need for real translation, just copy things over. */
-    if (udpif) {
-        memcpy(tun->metadata.opts.gnv, nl_attr_get(attr), attr_len);
-
-        if (!is_mask) {
-            tun->metadata.present.len = attr_len;
-            tun->flags |= FLOW_TNL_F_UDPIF;
-        } else {
-            /* We need to exact match on the length so we don't
-             * accidentally match on sets of options that are the same
-             * at the beginning but with additional options after. */
-            tun->metadata.present.len = 0xff;
-        }
-
-        return 0;
-    }
-
-    if (is_mask) {
-        flow = tun_metadata_find_geneve_key(flow_attrs, flow_attr_len);
-        if (!flow) {
-            return attr_len ? EINVAL : 0;
-        }
-
-        if (attr_len != nl_attr_get_size(flow)) {
-            return EINVAL;
-        }
-    } else {
-        flow = attr;
-    }
-
-    return tun_metadata_from_geneve__(&flow_tun->metadata, nl_attr_get(attr),
-                                      nl_attr_get(flow), nl_attr_get_size(flow),
-                                      &tun->metadata);
-}
-
-/* Converts from the flat Geneve options representation extracted directly
- * from the tunnel header to the representation that maps options to
- * pre-allocated locations. The original version (in UDPIF form) is passed
- * in 'src' and the translated form in stored in 'dst'.  To handle masks, the
- * flow must also be passed in through 'flow' (in the original, raw form). */
-int
-tun_metadata_from_geneve_udpif(const struct flow_tnl *flow,
-                               const struct flow_tnl *src,
-                               struct flow_tnl *dst)
-{
-    ovs_assert(flow->flags & FLOW_TNL_F_UDPIF);
-
-    if (flow == src) {
-        dst->flags = flow->flags & ~FLOW_TNL_F_UDPIF;
-    } else {
-        dst->metadata.tab = NULL;
-    }
-    dst->metadata.present.map = 0;
-    return tun_metadata_from_geneve__(&flow->metadata, src->metadata.opts.gnv,
-                                      flow->metadata.opts.gnv,
-                                      flow->metadata.present.len,
-                                      &dst->metadata);
-}
-
 static void
 tun_metadata_to_geneve__(const struct tun_metadata *flow, struct ofpbuf *b,
                          bool *crit_opt)
 {
-    struct tun_table *map;
     int i;
 
-    map = flow->tab;
-    if (!map) {
-        map = ovsrcu_get(struct tun_table *, &metadata_tab);
-    }
-
     *crit_opt = false;
 
     ULLONG_FOR_EACH_1 (i, flow->present.map) {
-        struct tun_meta_entry *entry = &map->entries[i];
+        const struct tun_meta_entry *entry = &flow->tab->entries[i];
         struct geneve_opt *opt;
 
         opt = ofpbuf_put_uninit(b, sizeof *opt + entry->loc.len);
@@ -857,8 +719,6 @@ tun_metadata_to_geneve_header(const struct flow_tnl *flow,
 {
     struct ofpbuf b;
 
-    ovs_assert(!(flow->flags & FLOW_TNL_F_UDPIF));
-
     ofpbuf_use_stack(&b, opts, TLV_TOT_OPT_SIZE);
     tun_metadata_to_geneve__(&flow->metadata, &b, crit_opt);
 
@@ -870,19 +730,13 @@ tun_metadata_to_geneve_mask__(const struct tun_metadata *flow,
                               const struct tun_metadata *mask,
                               struct geneve_opt *opt, int opts_len)
 {
-    struct tun_table *map = flow->tab;
-
-    if (!map) {
-        return;
-    }
-
     /* All of these options have already been validated, so no need
      * for sanity checking. */
     while (opts_len > 0) {
         struct tun_meta_entry *entry;
         int len = sizeof(*opt) + opt->length * 4;
 
-        entry = tun_meta_find_key(&map->key_hmap,
+        entry = tun_meta_find_key(&flow->tab->key_hmap,
                                   tun_meta_key(opt->opt_class, opt->type));
         if (entry) {
             memcpy_from_metadata(opt + 1, mask, &entry->loc);
@@ -908,7 +762,7 @@ tun_metadata_to_geneve_nlattr_mask(const struct ofpbuf *key,
                                    const struct flow_tnl *flow,
                                    struct ofpbuf *b)
 {
-    const struct nlattr *geneve_key;
+    const struct nlattr *tnl_key, *geneve_key;
     struct nlattr *geneve_mask;
     struct geneve_opt *opt;
     int opts_len;
@@ -917,7 +771,12 @@ tun_metadata_to_geneve_nlattr_mask(const struct ofpbuf *key,
         return;
     }
 
-    geneve_key = tun_metadata_find_geneve_key(key->data, key->size);
+    tnl_key = nl_attr_find__(key->data, key->size, OVS_KEY_ATTR_TUNNEL);
+    if (!tnl_key) {
+        return;
+    }
+
+    geneve_key = nl_attr_find_nested(tnl_key, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
     if (!geneve_key) {
         return;
     }
@@ -969,17 +828,15 @@ tun_metadata_to_geneve_udpif_mask(const struct flow_tnl *flow_src,
                                   const struct geneve_opt *flow_src_opt,
                                   int opts_len, struct geneve_opt *dst)
 {
-    ovs_assert(!(flow_src->flags & FLOW_TNL_F_UDPIF));
-
     memcpy(dst, flow_src_opt, opts_len);
     tun_metadata_to_geneve_mask__(&flow_src->metadata,
                                   &mask_src->metadata, dst, opts_len);
 }
 
 static const struct tun_metadata_loc *
-metadata_loc_from_match_read(struct tun_table *map, const struct match *match,
-                             unsigned int idx, struct flow_tnl *mask,
-                             bool *is_masked)
+metadata_loc_from_match_read(const struct tun_table *map,
+                             const struct match *match, unsigned int idx,
+                             const struct flow_tnl *mask, bool *is_masked)
 {
     union mf_value mask_opts;
 
@@ -997,63 +854,66 @@ metadata_loc_from_match_read(struct tun_table *map, const struct match *match,
     return &map->entries[idx].loc;
 }
 
+/* Generates NXM formatted matches in 'b' based on the contents of 'match'.
+ * 'match' must be in non-udpif format. */
 void
 tun_metadata_to_nx_match(struct ofpbuf *b, enum ofp_version oxm,
                          const struct match *match)
 {
-    struct flow_tnl flow, mask;
     int i;
 
-    if (!udpif_to_parsed(&match->flow.tunnel, &match->wc.masks.tunnel,
-                         &flow, &mask)) {
-        return;
-    }
-
-    ULLONG_FOR_EACH_1 (i, mask.metadata.present.map) {
+    ULLONG_FOR_EACH_1 (i, match->wc.masks.tunnel.metadata.present.map) {
         const struct tun_metadata_loc *loc;
         bool is_masked;
         union mf_value opts;
         union mf_value mask_opts;
 
-        loc = metadata_loc_from_match_read(flow.metadata.tab, match, i,
-                                           &mask, &is_masked);
-        memcpy_from_metadata(opts.tun_metadata, &flow.metadata, loc);
-        memcpy_from_metadata(mask_opts.tun_metadata, &mask.metadata, loc);
+        loc = metadata_loc_from_match_read(match->flow.tunnel.metadata.tab,
+                                           match, i, &match->wc.masks.tunnel,
+                                           &is_masked);
+        memcpy_from_metadata(opts.tun_metadata, &match->flow.tunnel.metadata,
+                             loc);
+        memcpy_from_metadata(mask_opts.tun_metadata,
+                             &match->wc.masks.tunnel.metadata, loc);
         nxm_put__(b, MFF_TUN_METADATA0 + i, oxm, opts.tun_metadata,
                   is_masked ? mask_opts.tun_metadata : NULL, loc->len);
     }
 }
 
+/* Formatted matches in 's' based on the contents of 'match'. 'match' must be
+ * in non-udpif format. */
 void
 tun_metadata_match_format(struct ds *s, const struct match *match)
 {
-    struct flow_tnl flow, mask;
-    unsigned int i;
+    int i;
 
-    if (!udpif_to_parsed(&match->flow.tunnel, &match->wc.masks.tunnel,
-                         &flow, &mask)) {
+    if (match->flow.tunnel.flags & FLOW_TNL_F_UDPIF ||
+        (!match->flow.tunnel.metadata.tab && !match->tun_md.valid)) {
         return;
     }
 
-    ULLONG_FOR_EACH_1 (i, mask.metadata.present.map) {
+    ULLONG_FOR_EACH_1 (i, match->wc.masks.tunnel.metadata.present.map) {
         const struct tun_metadata_loc *loc;
         bool is_masked;
         union mf_value opts, mask_opts;
 
-        loc = metadata_loc_from_match_read(flow.metadata.tab, match, i,
-                                           &mask, &is_masked);
+        loc = metadata_loc_from_match_read(match->flow.tunnel.metadata.tab,
+                                           match, i, &match->wc.masks.tunnel,
+                                           &is_masked);
 
         ds_put_format(s, "tun_metadata%u", i);
-        memcpy_from_metadata(mask_opts.tun_metadata, &mask.metadata, loc);
+        memcpy_from_metadata(mask_opts.tun_metadata,
+                             &match->wc.masks.tunnel.metadata, loc);
 
-        if (!ULLONG_GET(flow.metadata.present.map, i)) {
+        if (!ULLONG_GET(match->flow.tunnel.metadata.present.map, i)) {
             /* Indicate that we are matching on the field being not present. */
             ds_put_cstr(s, "=NP");
         } else if (!(is_masked &&
                      is_all_zeros(mask_opts.tun_metadata, loc->len))) {
             ds_put_char(s, '=');
 
-            memcpy_from_metadata(opts.tun_metadata, &flow.metadata, loc);
+            memcpy_from_metadata(opts.tun_metadata,
+                                 &match->flow.tunnel.metadata, loc);
             ds_put_hex(s, opts.tun_metadata, loc->len);
 
             if (!is_all_ones(mask_opts.tun_metadata, loc->len)) {
diff --git a/lib/tun-metadata.h b/lib/tun-metadata.h
index 4ce0770..7dad950 100644
--- a/lib/tun-metadata.h
+++ b/lib/tun-metadata.h
@@ -33,10 +33,15 @@ struct ofputil_tlv_table_mod;
 struct ofputil_tlv_table_reply;
 struct tun_table;
 
-void tun_metadata_init(void);
+struct tun_table *tun_metadata_alloc(const struct tun_table *old_map);
+void tun_metadata_free(struct tun_table *);
+void tun_metadata_postpone_free(struct tun_table *);
 
-enum ofperr tun_metadata_table_mod(struct ofputil_tlv_table_mod *);
-void tun_metadata_table_request(struct ofputil_tlv_table_reply *);
+enum ofperr tun_metadata_table_mod(struct ofputil_tlv_table_mod *,
+                                   const struct tun_table *old_tab,
+                                   struct tun_table **new_tab);
+void tun_metadata_table_request(const struct tun_table *,
+                                struct ofputil_tlv_table_reply *);
 
 void tun_metadata_read(const struct flow_tnl *,
                        const struct mf_field *, union mf_value *);
@@ -48,17 +53,15 @@ void tun_metadata_set_match(const struct mf_field *,
                             char **err_str);
 void tun_metadata_get_fmd(const struct flow_tnl *, struct match *flow_metadata);
 
-int tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
-                                    const struct nlattr *flow_attrs,
-                                    size_t flow_attr_len,
-                                    const struct flow_tnl *flow_tun,
-                                    bool udpif, struct flow_tnl *tun);
+void tun_metadata_from_geneve_nlattr(const struct nlattr *attr, bool is_mask,
+                                     struct flow_tnl *tun);
 void tun_metadata_to_geneve_nlattr(const struct flow_tnl *tun,
                                    const struct flow_tnl *flow,
                                    const struct ofpbuf *key,
                                    struct ofpbuf *);
 
-int tun_metadata_from_geneve_udpif(const struct flow_tnl *flow,
+int tun_metadata_from_geneve_udpif(const struct tun_table *,
+                                   const struct flow_tnl *flow,
                                    const struct flow_tnl *src,
                                    struct flow_tnl *dst);
 void tun_metadata_to_geneve_udpif_mask(const struct flow_tnl *flow_src,
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index 1252d39..c1fd26c 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -958,7 +958,7 @@ sflow_read_set_action(const struct nlattr *attr,
             /* Do not handle multi-encap for now. */
             sflow_actions->tunnel_err = true;
         } else {
-            if (odp_tun_key_from_attr(attr, false, &sflow_actions->tunnel)
+            if (odp_tun_key_from_attr(attr, &sflow_actions->tunnel)
                 == ODP_FIT_ERROR) {
                 /* Tunnel parsing error. */
                 sflow_actions->tunnel_err = true;
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index e447308..ddd2ac0 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -1267,8 +1267,7 @@ process_upcall(struct udpif *udpif, struct upcall *upcall,
             memcpy(&cookie, nl_attr_get(userdata), sizeof cookie.ipfix);
 
             if (upcall->out_tun_key) {
-                odp_tun_key_from_attr(upcall->out_tun_key, false,
-                                      &output_tunnel_key);
+                odp_tun_key_from_attr(upcall->out_tun_key, &output_tunnel_key);
             }
             dpif_ipfix_bridge_sample(upcall->ipfix, packet, flow,
                                      flow->in_port.odp_port,
@@ -1287,8 +1286,7 @@ process_upcall(struct udpif *udpif, struct upcall *upcall,
             memcpy(&cookie, nl_attr_get(userdata), sizeof cookie.flow_sample);
 
             if (upcall->out_tun_key) {
-                odp_tun_key_from_attr(upcall->out_tun_key, false,
-                                      &output_tunnel_key);
+                odp_tun_key_from_attr(upcall->out_tun_key, &output_tunnel_key);
             }
 
             /* The flow reflects exactly the contents of the packet.
@@ -1870,8 +1868,7 @@ revalidate_ukey(struct udpif *udpif, struct udpif_key *ukey,
                           odp_actions);
     }
 
-    if (odp_flow_key_to_mask(ukey->mask, ukey->mask_len, ukey->key,
-                             ukey->key_len, &dp_mask, &flow)
+    if (odp_flow_key_to_mask(ukey->mask, ukey->mask_len, &dp_mask, &flow)
         == ODP_FIT_ERROR) {
         goto exit;
     }
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 1e2957c..ff3f564 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -396,6 +396,8 @@ const char *xlate_strerror(enum xlate_error error)
         return "Recirculation conflict";
     case XLATE_TOO_MANY_MPLS_LABELS:
         return "Too many MPLS labels";
+    case XLATE_INVALID_TUNNEL_METADATA:
+        return "Invalid tunnel metadata";
     }
     return "Unknown error";
 }
@@ -3052,6 +3054,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
     if (xport->peer) {
         const struct xport *peer = xport->peer;
         struct flow old_flow = ctx->xin->flow;
+        struct flow_tnl old_flow_tnl_wc = ctx->wc->masks.tunnel;
         bool old_conntrack = ctx->conntracked;
         bool old_was_mpls = ctx->was_mpls;
         ovs_version_t old_version = ctx->tables_version;
@@ -3066,6 +3069,8 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
         flow->in_port.ofp_port = peer->ofp_port;
         flow->metadata = htonll(0);
         memset(&flow->tunnel, 0, sizeof flow->tunnel);
+        flow->tunnel.metadata.tab = ofproto_dpif_get_tun_tab(ctx->xbridge->ofproto);
+        ctx->wc->masks.tunnel.metadata.tab = flow->tunnel.metadata.tab;
         memset(flow->regs, 0, sizeof flow->regs);
         flow->actset_output = OFPP_UNSET;
         ctx->conntracked = false;
@@ -3111,6 +3116,15 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
         /* Restore calling bridge's lookup version. */
         ctx->tables_version = old_version;
 
+        /* Since this packet came in on a patch port (from the perspective of
+         * the peer bridge), it cannot have useful tunnel information. As a
+         * result, any wildcards generated on that tunnel also cannot be valid.
+         * The tunnel wildcards must be restored to their original version since
+         * the peer bridge uses a separate tunnel metadata table and therefore
+         * any generated wildcards will be garbage in the context of our
+         * metadata table. */
+        ctx->wc->masks.tunnel = old_flow_tnl_wc;
+
         /* The peer bridge popping MPLS should have no effect on the original
          * bridge. */
         ctx->was_mpls = old_was_mpls;
@@ -5094,6 +5108,7 @@ xlate_in_init(struct xlate_in *xin, struct ofproto_dpif *ofproto,
 {
     xin->ofproto = ofproto;
     xin->flow = *flow;
+    xin->upcall_flow = flow;
     xin->flow.in_port.ofp_port = in_port;
     xin->flow.actset_output = OFPP_UNSET;
     xin->packet = packet;
@@ -5495,6 +5510,28 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         ctx.error = XLATE_NO_RECIRCULATION_CONTEXT;
         goto exit;
     }
+
+    /* Tunnel metadata in udpif format must be normalized before translation. */
+    if (flow->tunnel.flags & FLOW_TNL_F_UDPIF) {
+        const struct tun_table *tun_tab = ofproto_dpif_get_tun_tab(xin->ofproto);
+        int err;
+
+        err = tun_metadata_from_geneve_udpif(tun_tab, &xin->upcall_flow->tunnel,
+                                             &xin->upcall_flow->tunnel,
+                                             &flow->tunnel);
+        if (err) {
+            XLATE_REPORT_ERROR(&ctx, "Invalid Geneve tunnel metadata");
+            ctx.error = XLATE_INVALID_TUNNEL_METADATA;
+            goto exit;
+        }
+    } else if (!flow->tunnel.metadata.tab) {
+        /* If the original flow did not come in on a tunnel, then it won't have
+         * FLOW_TNL_F_UDPIF set. However, we still need to have a metadata
+         * table in case we generate tunnel actions. */
+        flow->tunnel.metadata.tab = ofproto_dpif_get_tun_tab(xin->ofproto);
+    }
+    ctx.wc->masks.tunnel.metadata.tab = flow->tunnel.metadata.tab;
+
     /* The bridge is now known so obtain its table version. */
     ctx.tables_version = ofproto_dpif_get_tables_version(ctx.xbridge->ofproto);
 
@@ -5651,9 +5688,48 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         }
     }
 
+    /* Translate tunnel metadata masks to udpif format if necessary. */
+    if (xin->upcall_flow->tunnel.flags & FLOW_TNL_F_UDPIF) {
+        if (ctx.wc->masks.tunnel.metadata.present.map) {
+            const struct flow_tnl *upcall_tnl = &xin->upcall_flow->tunnel;
+            struct geneve_opt opts[TLV_TOT_OPT_SIZE /
+                                   sizeof(struct geneve_opt)];
+
+            tun_metadata_to_geneve_udpif_mask(&flow->tunnel,
+                                              &ctx.wc->masks.tunnel,
+                                              upcall_tnl->metadata.opts.gnv,
+                                              upcall_tnl->metadata.present.len,
+                                              opts);
+             memset(&ctx.wc->masks.tunnel.metadata, 0,
+                    sizeof ctx.wc->masks.tunnel.metadata);
+             memcpy(&ctx.wc->masks.tunnel.metadata.opts.gnv, opts,
+                    upcall_tnl->metadata.present.len);
+        }
+        ctx.wc->masks.tunnel.metadata.present.len = 0xff;
+        ctx.wc->masks.tunnel.metadata.tab = NULL;
+        ctx.wc->masks.tunnel.flags |= FLOW_TNL_F_UDPIF;
+    } else if (!xin->upcall_flow->tunnel.metadata.tab) {
+        /* If we didn't have options in UDPIF format and didn't have an existing
+         * metadata table, then it means that there were no options at all when
+         * we started processing and any wildcards we picked up were from
+         * action generation. Without options on the incoming packet, wildcards
+         * aren't meaningful. To avoid them possibly getting misinterpreted,
+         * just clear everything. */
+        if (ctx.wc->masks.tunnel.metadata.present.map) {
+            memset(&ctx.wc->masks.tunnel.metadata, 0,
+                   sizeof ctx.wc->masks.tunnel.metadata);
+        } else {
+            ctx.wc->masks.tunnel.metadata.tab = NULL;
+        }
+    }
+
     xlate_wc_finish(&ctx);
 
 exit:
+    /* Reset the table to what it was when we came in. If we only fetched
+     * it locally, then it has no meaning outside of flow translation. */
+    flow->tunnel.metadata.tab = xin->upcall_flow->tunnel.metadata.tab;
+
     ofpbuf_uninit(&ctx.stack);
     ofpbuf_uninit(&ctx.action_set);
     ofpbuf_uninit(&ctx.frozen_actions);
diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h
index 7808a60..55b4ed7 100644
--- a/ofproto/ofproto-dpif-xlate.h
+++ b/ofproto/ofproto-dpif-xlate.h
@@ -51,6 +51,10 @@ struct xlate_in {
      * this flow when actions change header fields. */
     struct flow flow;
 
+    /* Pointer to the original flow received during the upcall. xlate_actions()
+     * will never modify this flow. */
+    const struct flow *upcall_flow;
+
     /* The packet corresponding to 'flow', or a null pointer if we are
      * revalidating without a packet to refer to. */
     const struct dp_packet *packet;
@@ -194,6 +198,7 @@ enum xlate_error {
     XLATE_NO_RECIRCULATION_CONTEXT,
     XLATE_RECIRCULATION_CONFLICT,
     XLATE_TOO_MANY_MPLS_LABELS,
+    XLATE_INVALID_TUNNEL_METADATA,
 };
 
 const char *xlate_strerror(enum xlate_error error);
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 289e7d6..245a5a3 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -356,6 +356,12 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 /* Initial mappings of port to bridge mappings. */
 static struct shash init_ofp_ports = SHASH_INITIALIZER(&init_ofp_ports);
 
+const struct tun_table *
+ofproto_dpif_get_tun_tab(const struct ofproto_dpif *ofproto)
+{
+    return ofproto_get_tun_tab(&ofproto->up);
+}
+
 /* Executes 'fm'.  The caller retains ownership of 'fm' and everything in
  * it. */
 void
@@ -4707,7 +4713,8 @@ struct trace_ctx {
 };
 
 static void
-trace_format_rule(struct ds *result, int level, const struct rule_dpif *rule)
+trace_format_rule(struct ofproto *ofproto, struct ds *result, int level,
+                  const struct rule_dpif *rule)
 {
     const struct rule_actions *actions;
     ovs_be64 cookie;
@@ -4724,7 +4731,7 @@ trace_format_rule(struct ds *result, int level, const struct rule_dpif *rule)
 
     ds_put_format(result, "Rule: table=%"PRIu8" cookie=%#"PRIx64" ",
                   rule ? rule->up.table_id : 0, ntohll(cookie));
-    cls_rule_format(&rule->up.cr, result);
+    cls_rule_format(&rule->up.cr, ofproto_get_tun_tab(ofproto), result);
     ds_put_char(result, '\n');
 
     actions = rule_dpif_get_actions(rule);
@@ -4826,7 +4833,7 @@ trace_resubmit(struct xlate_in *xin, struct rule_dpif *rule, int indentation)
         trace_format_megaflow(result, indentation, "Resubmitted megaflow",
                               trace);
     }
-    trace_format_rule(result, indentation, rule);
+    trace_format_rule(&xin->ofproto->up, result, indentation, rule);
 }
 
 static void
@@ -4943,24 +4950,47 @@ parse_flow_and_packet(int argc, const char *argv[],
             error = "Invalid datapath flow";
             goto exit;
         }
+
+        flow->tunnel.metadata.tab = ofproto_dpif_get_tun_tab(*ofprotop);
+
+        /* Convert Geneve options to OpenFlow format now. This isn't actually
+         * required in order to get the right results since the ofproto xlate
+         * actions will handle this for us. However, converting now ensures
+         * that our formatting code will always be able to consistently print
+         * in OpenFlow format, which is what we use here. */
+        if (flow->tunnel.flags & FLOW_TNL_F_UDPIF) {
+            struct flow_tnl tnl;
+            int err;
+
+            memcpy(&tnl, &flow->tunnel, sizeof tnl);
+            err = tun_metadata_from_geneve_udpif(flow->tunnel.metadata.tab,
+                                                 &tnl, &tnl, &flow->tunnel);
+            if (err) {
+                error = "Failed to parse Geneve options";
+                goto exit;
+            }
+        }
     } else {
-        char *err = parse_ofp_exact_flow(flow, NULL, argv[argc - 1], NULL);
+        char *err;
 
+        if (argc != 3) {
+            error = "Must specify bridge name";
+            goto exit;
+        }
+
+        *ofprotop = ofproto_dpif_lookup(argv[1]);
+        if (!*ofprotop) {
+            error = "Unknown bridge name";
+            goto exit;
+        }
+
+        err = parse_ofp_exact_flow(flow, NULL,
+                                   ofproto_dpif_get_tun_tab(*ofprotop),
+                                   argv[argc - 1], NULL);
         if (err) {
             m_err = xasprintf("Bad openflow flow syntax: %s", err);
             free(err);
             goto exit;
-        } else {
-            if (argc != 3) {
-                error = "Must specify bridge name";
-                goto exit;
-            }
-
-            *ofprotop = ofproto_dpif_lookup(argv[1]);
-            if (!*ofprotop) {
-                error = "Unknown bridge name";
-                goto exit;
-            }
         }
     }
 
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index a3b1d6b..6e06f0b 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -177,6 +177,7 @@ int ofproto_dpif_delete_internal_flow(struct ofproto_dpif *, struct match *,
                                       int priority);
 
 const struct uuid *ofproto_dpif_get_uuid(const struct ofproto_dpif *);
+const struct tun_table *ofproto_dpif_get_tun_tab(const struct ofproto_dpif *);
 
 /* struct rule_dpif has struct rule as it's first member. */
 #define RULE_CAST(RULE) ((struct rule *)RULE)
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 7f7813e..06abafd 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -50,6 +50,7 @@
 #include "openvswitch/shash.h"
 #include "simap.h"
 #include "timeval.h"
+#include "tun-metadata.h"
 #include "versions.h"
 
 struct match;
@@ -121,6 +122,9 @@ struct ofproto {
     struct cmap groups;               /* Contains "struct ofgroup"s. */
     uint32_t n_groups[4] OVS_GUARDED; /* # of existing groups of each type. */
     struct ofputil_group_features ogf;
+
+     /* Tunnel TLV mapping table. */
+     OVSRCU_TYPE(struct tun_table *) metadata_tab;
 };
 
 void ofproto_init_tables(struct ofproto *, int n_tables);
@@ -1932,4 +1936,10 @@ rule_from_cls_rule(const struct cls_rule *cls_rule)
     return cls_rule ? CONTAINER_OF(cls_rule, struct rule, cr) : NULL;
 }
 
+static inline const struct tun_table *
+ofproto_get_tun_tab(const struct ofproto *ofproto)
+{
+    return ovsrcu_get(struct tun_table *, &ofproto->metadata_tab);
+}
+
 #endif /* ofproto/ofproto-provider.h */
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 9d62b72..4d75d5e 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -526,7 +526,7 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
         ofproto->ogf.max_groups[i] = OFPG_MAX;
         ofproto->ogf.ofpacts[i] = (UINT64_C(1) << N_OFPACTS) - 1;
     }
-    tun_metadata_init();
+    ovsrcu_set(&ofproto->metadata_tab, tun_metadata_alloc(NULL));
 
     error = ofproto->ofproto_class->construct(ofproto);
     if (error) {
@@ -1541,6 +1541,8 @@ ofproto_destroy__(struct ofproto *ofproto)
     cmap_destroy(&ofproto->groups);
 
     hmap_remove(&all_ofprotos, &ofproto->hmap_node);
+    tun_metadata_free(ovsrcu_get_protected(struct tun_table *,
+                                           &ofproto->metadata_tab));
     free(ofproto->name);
     free(ofproto->type);
     free(ofproto->mfr_desc);
@@ -3476,7 +3478,9 @@ handle_nxt_resume(struct ofconn *ofconn, const struct ofp_header *oh)
     struct ofputil_packet_in_private pin;
     enum ofperr error;
 
-    error = ofputil_decode_packet_in_private(oh, false, &pin, NULL, NULL);
+    error = ofputil_decode_packet_in_private(oh, false,
+                                             ofproto_get_tun_tab(ofproto), &pin,
+                                             NULL, NULL);
     if (error) {
         return error;
     }
@@ -4179,7 +4183,8 @@ handle_flow_stats_request(struct ofconn *ofconn,
     struct ovs_list replies;
     enum ofperr error;
 
-    error = ofputil_decode_flow_stats_request(&fsr, request);
+    error = ofputil_decode_flow_stats_request(&fsr, request,
+                                              ofproto_get_tun_tab(ofproto));
     if (error) {
         return error;
     }
@@ -4233,7 +4238,8 @@ handle_flow_stats_request(struct ofconn *ofconn,
         fs.ofpacts_len = actions->ofpacts_len;
 
         fs.flags = flags;
-        ofputil_append_flow_stats_reply(&fs, &replies);
+        ofputil_append_flow_stats_reply(&fs, &replies,
+                                        ofproto_get_tun_tab(ofproto));
     }
 
     rule_collection_unref(&rules);
@@ -4245,7 +4251,7 @@ handle_flow_stats_request(struct ofconn *ofconn,
 }
 
 static void
-flow_stats_ds(struct rule *rule, struct ds *results)
+flow_stats_ds(struct ofproto *ofproto, struct rule *rule, struct ds *results)
 {
     uint64_t packet_count, byte_count;
     const struct rule_actions *actions;
@@ -4265,7 +4271,7 @@ flow_stats_ds(struct rule *rule, struct ds *results)
     ds_put_format(results, "duration=%llds, ", (time_msec() - created) / 1000);
     ds_put_format(results, "n_packets=%"PRIu64", ", packet_count);
     ds_put_format(results, "n_bytes=%"PRIu64", ", byte_count);
-    cls_rule_format(&rule->cr, results);
+    cls_rule_format(&rule->cr, ofproto_get_tun_tab(ofproto), results);
     ds_put_char(results, ',');
 
     ds_put_cstr(results, "actions=");
@@ -4285,7 +4291,7 @@ ofproto_get_all_flows(struct ofproto *p, struct ds *results)
         struct rule *rule;
 
         CLS_FOR_EACH (rule, cr, &table->cls) {
-            flow_stats_ds(rule, results);
+            flow_stats_ds(p, rule, results);
         }
     }
 }
@@ -4342,7 +4348,8 @@ handle_aggregate_stats_request(struct ofconn *ofconn,
     struct ofpbuf *reply;
     enum ofperr error;
 
-    error = ofputil_decode_flow_stats_request(&request, oh);
+    error = ofputil_decode_flow_stats_request(&request, oh,
+                                              ofproto_get_tun_tab(ofproto));
     if (error) {
         return error;
     }
@@ -5403,7 +5410,7 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
 
     ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
     error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_protocol(ofconn),
-                                    &ofpacts,
+                                    ofproto_get_tun_tab(ofproto), &ofpacts,
                                     u16_to_ofp(ofproto->max_ports),
                                     ofproto->n_tables);
     if (!error) {
@@ -7398,6 +7405,7 @@ handle_bundle_add(struct ofconn *ofconn, const struct ofp_header *oh)
         ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
         error = ofputil_decode_flow_mod(&fm, badd.msg,
                                         ofconn_get_protocol(ofconn),
+                                        ofproto_get_tun_tab(ofproto),
                                         &ofpacts,
                                         u16_to_ofp(ofproto->max_ports),
                                         ofproto->n_tables);
@@ -7426,6 +7434,8 @@ handle_bundle_add(struct ofconn *ofconn, const struct ofp_header *oh)
 static enum ofperr
 handle_tlv_table_mod(struct ofconn *ofconn, const struct ofp_header *oh)
 {
+    struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+    struct tun_table *old_tab, *new_tab;
     struct ofputil_tlv_table_mod ttm;
     enum ofperr error;
 
@@ -7439,7 +7449,12 @@ handle_tlv_table_mod(struct ofconn *ofconn, const struct ofp_header *oh)
         return error;
     }
 
-    error = tun_metadata_table_mod(&ttm);
+    old_tab = ovsrcu_get_protected(struct tun_table *, &ofproto->metadata_tab);
+    error = tun_metadata_table_mod(&ttm, old_tab, &new_tab);
+    if (!error) {
+        ovsrcu_set(&ofproto->metadata_tab, new_tab);
+        tun_metadata_postpone_free(old_tab);
+    }
 
     ofputil_uninit_tlv_table(&ttm.mappings);
     return error;
@@ -7448,10 +7463,12 @@ handle_tlv_table_mod(struct ofconn *ofconn, const struct ofp_header *oh)
 static enum ofperr
 handle_tlv_table_request(struct ofconn *ofconn, const struct ofp_header *oh)
 {
+    const struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
     struct ofputil_tlv_table_reply ttr;
     struct ofpbuf *b;
 
-    tun_metadata_table_request(&ttr);
+    tun_metadata_table_request(ofproto_get_tun_tab(ofproto), &ttr);
+
     b = ofputil_encode_tlv_table_reply(oh, &ttr);
     ofputil_uninit_tlv_table(&ttr.mappings);
 
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index d65e213..d88a087 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -658,7 +658,7 @@ process_packet_in(const struct ofp_header *msg)
 
     struct ofputil_packet_in pin;
     struct ofpbuf continuation;
-    enum ofperr error = ofputil_decode_packet_in(msg, true, &pin,
+    enum ofperr error = ofputil_decode_packet_in(msg, true, NULL, &pin,
                                                  NULL, NULL, &continuation);
 
     if (error) {
diff --git a/tests/test-odp.c b/tests/test-odp.c
index dabfb85..31699a5 100644
--- a/tests/test-odp.c
+++ b/tests/test-odp.c
@@ -163,7 +163,7 @@ parse_filter(char *filter_parse)
         memset(&flow_filter, 0, sizeof(flow_filter));
         memset(&wc_filter, 0, sizeof(wc_filter));
 
-        error = parse_ofp_exact_flow(&flow_filter, &wc_filter, filter,
+        error = parse_ofp_exact_flow(&flow_filter, &wc_filter, NULL, filter,
                                      NULL);
         if (error) {
             ovs_fatal(0, "Failed to parse filter (%s)", error);
@@ -196,8 +196,7 @@ parse_filter(char *filter_parse)
             struct minimatch minimatch;
 
             odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow);
-            odp_flow_key_to_mask(odp_mask.data, odp_mask.size, odp_key.data,
-                                 odp_key.size, &wc, &flow);
+            odp_flow_key_to_mask(odp_mask.data, odp_mask.size, &wc, &flow);
             match_init(&match, &flow, &wc);
 
             match_init(&match_filter, &flow_filter, &wc);
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index b56e5b3..5a982e2 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -513,10 +513,6 @@ ovs-ofctl add-flow br0 tun_metadata0=1234,actions=controller
 A mapping should not be changed while it is in active
 use by a flow. The result of doing so is undefined.
 
-Currently, the TLV mapping table is shared between all OpenFlow
-switches in a given instance of Open vSwitch. This restriction will
-be lifted in the future to allow for easier management.
-
 These commands are Nicira extensions to OpenFlow and require Open vSwitch
 2.5 or later.
 
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 306bb78..6980ed8 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -1848,7 +1848,7 @@ monitor_vconn(struct vconn *vconn, bool reply_to_echo_requests,
                     struct ofputil_packet_in pin;
                     struct ofpbuf continuation;
 
-                    error = ofputil_decode_packet_in(b->data, true, &pin,
+                    error = ofputil_decode_packet_in(b->data, true, NULL, &pin,
                                                      NULL, NULL,
                                                      &continuation);
                     if (error) {
@@ -2906,6 +2906,28 @@ struct fte_version {
     uint8_t table_id;
 };
 
+/* A FTE entry that has been queued for later insertion after all
+ * flows have been scanned to correctly allocation tunnel metadata. */
+struct fte_pending {
+    struct match *match;
+    int priority;
+    struct fte_version *version;
+    int index;
+
+    struct ovs_list list_node;
+};
+
+/* Processing state during two stage processing of flow table entries.
+ * Tracks the maximum size seen for each tunnel metadata entry as well
+ * as a list of the pending FTE entries. */
+struct fte_state {
+    int tun_metadata_size[TUN_METADATA_NUM_OPTS];
+    struct ovs_list fte_pending_list;
+
+    /* The final metadata table that we have constructed. */
+    struct tun_table *tun_tab;
+};
+
 /* Frees 'version' and the data that it owns. */
 static void
 fte_version_free(struct fte_version *version)
@@ -2935,7 +2957,8 @@ fte_version_equals(const struct fte_version *a, const struct fte_version *b)
 /* Clears 's', then if 's' has a version 'index', formats 'fte' and version
  * 'index' into 's', followed by a new-line. */
 static void
-fte_version_format(const struct fte *fte, int index, struct ds *s)
+fte_version_format(const struct fte_state *fte_state, const struct fte *fte,
+                   int index, struct ds *s)
 {
     const struct fte_version *version = fte->versions[index];
 
@@ -2947,7 +2970,7 @@ fte_version_format(const struct fte *fte, int index, struct ds *s)
     if (version->table_id) {
         ds_put_format(s, "table=%"PRIu8" ", version->table_id);
     }
-    cls_rule_format(&fte->rule, s);
+    cls_rule_format(&fte->rule, fte_state->tun_tab, s);
     if (version->cookie != htonll(0)) {
         ds_put_format(s, " cookie=0x%"PRIx64, ntohll(version->cookie));
     }
@@ -3029,25 +3052,6 @@ fte_insert(struct flow_tables *tables, const struct match *match,
     }
 }
 
-/* A FTE entry that has been queued for later insertion after all
- * flows have been scanned to correctly allocation tunnel metadata. */
-struct fte_pending {
-    struct match *match;
-    int priority;
-    struct fte_version *version;
-    int index;
-
-    struct ovs_list list_node;
-};
-
-/* Processing state during two stage processing of flow table entries.
- * Tracks the maximum size seen for each tunnel metadata entry as well
- * as a list of the pending FTE entries. */
-struct fte_state {
-    int tun_metadata_size[TUN_METADATA_NUM_OPTS];
-    struct ovs_list fte_pending_list;
-};
-
 /* Given a list of the field sizes for each tunnel metadata entry, install
  * a mapping table for later operations. */
 static void
@@ -3074,7 +3078,7 @@ generate_tun_metadata(struct fte_state *state)
         }
     }
 
-    tun_metadata_table_mod(&ttm);
+    tun_metadata_table_mod(&ttm, NULL, &state->tun_tab);
     ofputil_uninit_tlv_table(&ttm.mappings);
 }
 
@@ -3084,7 +3088,7 @@ generate_tun_metadata(struct fte_state *state)
  * can just read the data from the match and rewrite it. On rewrite, it
  * will use the new table. */
 static void
-remap_match(struct match *match)
+remap_match(struct fte_state *state, struct match *match)
 {
     int i;
 
@@ -3099,6 +3103,9 @@ remap_match(struct match *match)
            sizeof match->wc.masks.tunnel.metadata);
     match->tun_md.valid = false;
 
+    match->flow.tunnel.metadata.tab = state->tun_tab;
+    match->wc.masks.tunnel.metadata.tab = match->flow.tunnel.metadata.tab;
+
     ULLONG_FOR_EACH_1 (i, flow_mask.present.map) {
         const struct mf_field *field = mf_from_id(MFF_TUN_METADATA0 + i);
         int offset = match->tun_md.entry[i].loc.c.offset;
@@ -3146,6 +3153,13 @@ fte_state_init(struct fte_state *state)
     }
 
     ovs_list_init(&state->fte_pending_list);
+    state->tun_tab = NULL;
+}
+
+static void
+fte_state_destroy(struct fte_state *state)
+{
+    tun_metadata_free(state->tun_tab);
 }
 
 /* The first pass of the processing described in the comment about
@@ -3189,7 +3203,7 @@ fte_fill(struct fte_state *state, struct flow_tables *tables)
     flow_tables_defer(tables);
 
     LIST_FOR_EACH_POP(pending, list_node, &state->fte_pending_list) {
-        remap_match(pending->match);
+        remap_match(state, pending->match);
         fte_insert(tables, pending->match, pending->priority, pending->version,
                    pending->index);
         free(pending->match);
@@ -3444,6 +3458,7 @@ ofctl_replace_flows(struct ovs_cmdl_context *ctx)
     vconn_close(vconn);
 
     fte_free_all(&tables);
+    fte_state_destroy(&fte_state);
 }
 
 static void
@@ -3489,8 +3504,8 @@ ofctl_diff_flows(struct ovs_cmdl_context *ctx)
             struct fte_version *b = fte->versions[1];
 
             if (!a || !b || !fte_version_equals(a, b)) {
-                fte_version_format(fte, 0, &a_s);
-                fte_version_format(fte, 1, &b_s);
+                fte_version_format(&fte_state, fte, 0, &a_s);
+                fte_version_format(&fte_state, fte, 1, &b_s);
                 if (strcmp(ds_cstr(&a_s), ds_cstr(&b_s))) {
                     if (a_s.length) {
                         printf("-%s", ds_cstr(&a_s));
@@ -3508,6 +3523,7 @@ ofctl_diff_flows(struct ovs_cmdl_context *ctx)
     ds_destroy(&b_s);
 
     fte_free_all(&tables);
+    fte_state_destroy(&fte_state);
 
     if (differences) {
         exit(2);
@@ -3707,17 +3723,17 @@ ofctl_parse_nxm__(bool oxm, enum ofp_version version)
         /* Convert nx_match to match. */
         if (strict) {
             if (oxm) {
-                error = oxm_pull_match(&nx_match, &match);
+                error = oxm_pull_match(&nx_match, NULL, &match);
             } else {
                 error = nx_pull_match(&nx_match, match_len, &match,
-                                      &cookie, &cookie_mask);
+                                      &cookie, &cookie_mask, NULL);
             }
         } else {
             if (oxm) {
-                error = oxm_pull_match_loose(&nx_match, &match);
+                error = oxm_pull_match_loose(&nx_match, NULL, &match);
             } else {
                 error = nx_pull_match_loose(&nx_match, match_len, &match,
-                                            &cookie, &cookie_mask);
+                                            &cookie, &cookie_mask, NULL);
             }
         }
 
@@ -4127,7 +4143,7 @@ ofctl_check_vlan(struct ovs_cmdl_context *ctx)
     ofpbuf_init(&nxm, 0);
     nxm_match_len = nx_put_match(&nxm, &match, htonll(0), htonll(0));
     nxm_s = nx_match_to_string(nxm.data, nxm_match_len);
-    error = nx_pull_match(&nxm, nxm_match_len, &nxm_match, NULL, NULL);
+    error = nx_pull_match(&nxm, nxm_match_len, &nxm_match, NULL, NULL, NULL);
     printf("NXM: %s -> ", nxm_s);
     if (error) {
         printf("%s\n", ofperr_to_string(error));
@@ -4143,7 +4159,7 @@ ofctl_check_vlan(struct ovs_cmdl_context *ctx)
     ofpbuf_init(&nxm, 0);
     nxm_match_len = oxm_put_match(&nxm, &match, OFP12_VERSION);
     nxm_s = oxm_match_to_string(&nxm, nxm_match_len);
-    error = oxm_pull_match(&nxm, &nxm_match);
+    error = oxm_pull_match(&nxm, NULL, &nxm_match);
     printf("OXM: %s -> ", nxm_s);
     if (error) {
         printf("%s\n", ofperr_to_string(error));
-- 
2.7.4




More information about the dev mailing list