diff mbox

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

Message ID 1471987707-26102-1-git-send-email-jesse@kernel.org
State Changes Requested
Headers show

Commit Message

Jesse Gross Aug. 23, 2016, 9:28 p.m. UTC
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@noironetworks.com>
Signed-off-by: Madhu Challa <challa@noironetworks.com>
Signed-off-by: Jesse Gross <jesse@kernel.org>
---
 include/openvswitch/match.h        |   4 +-
 include/openvswitch/ofp-parse.h    |   6 +-
 include/openvswitch/ofp-util.h     |  17 +-
 include/openvswitch/tun-metadata.h |   2 +-
 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                        |   9 +-
 lib/nx-match.c                     |  50 +++--
 lib/nx-match.h                     |  18 +-
 lib/odp-execute.c                  |   2 +-
 lib/odp-util.c                     |  88 ++-------
 lib/odp-util.h                     |  14 +-
 lib/ofp-parse.c                    |  11 +-
 lib/ofp-print.c                    |   6 +-
 lib/ofp-util.c                     |  88 +++++----
 lib/tun-metadata.c                 | 375 ++++++++++++-------------------------
 lib/tun-metadata.h                 |  20 +-
 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             |  59 ++++--
 ofproto/ofproto-dpif.h             |   1 +
 ofproto/ofproto-provider.h         |   4 +
 ofproto/ofproto.c                  |  30 +--
 ovn/controller/pinctrl.c           |   2 +-
 tests/test-odp.c                   |   5 +-
 utilities/ovs-ofctl.8.in           |   4 -
 utilities/ovs-ofctl.c              |  16 +-
 33 files changed, 463 insertions(+), 542 deletions(-)

Comments

Ben Pfaff Aug. 30, 2016, 8:17 p.m. UTC | #1
On Tue, Aug 23, 2016 at 02:28:27PM -0700, Jesse Gross wrote:
> 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@noironetworks.com>
> Signed-off-by: Madhu Challa <challa@noironetworks.com>
> Signed-off-by: Jesse Gross <jesse@kernel.org>

It's not really great to have the library layer looking into the ofproto
layer.  Is there a way to avoid that?  Perhaps callers should pass in a
tun_table instead of an ofproto?
Jesse Gross Sept. 1, 2016, 7:36 p.m. UTC | #2
On Tue, Aug 30, 2016 at 1:17 PM, Ben Pfaff <blp@ovn.org> wrote:
> On Tue, Aug 23, 2016 at 02:28:27PM -0700, Jesse Gross wrote:
>> 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@noironetworks.com>
>> Signed-off-by: Madhu Challa <challa@noironetworks.com>
>> Signed-off-by: Jesse Gross <jesse@kernel.org>
>
> It's not really great to have the library layer looking into the ofproto
> layer.  Is there a way to avoid that?  Perhaps callers should pass in a
> tun_table instead of an ofproto?

I originally thought that passing an ofproto was perhaps a little more
generic and nicer but you're right that this is probably not worth the
layering issues. It's also cleaner to avoid using an ofproto now that
I've rebased this patch on top of my earlier ovs-ofctl changes. Since
those patches made ovs-ofctl need to generate a tunnel metadata table,
with the original version of the patch it would also be necessary to
have a fake ofproto to contain it. If we only pass around the
tun_table, then this isn't needed.

I'll send a rebased version that switches to passing around a struct
tun_table shortly.
diff mbox

Patch

diff --git a/include/openvswitch/match.h b/include/openvswitch/match.h
index 3b7f32f..b483e90 100644
--- a/include/openvswitch/match.h
+++ b/include/openvswitch/match.h
@@ -22,6 +22,7 @@ 
 #include "openvswitch/tun-metadata.h"
 
 struct ds;
+struct ofproto;
 
 /* A flow classification match.
  *
@@ -211,7 +212,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 ofproto *, const struct minimatch *,
+                      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..3fcd9ba 100644
--- a/include/openvswitch/ofp-parse.h
+++ b/include/openvswitch/ofp-parse.h
@@ -27,6 +27,7 @@ 
 
 struct flow;
 struct ofpbuf;
+struct ofproto;
 struct ofputil_flow_mod;
 struct ofputil_flow_monitor_request;
 struct ofputil_flow_stats_request;
@@ -62,8 +63,9 @@  char *parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *,
                                        enum ofputil_protocol *usable_protocols)
     OVS_WARN_UNUSED_RESULT;
 
-char *parse_ofp_exact_flow(struct flow *flow, struct flow_wildcards *wc,
-                           const char *s, const struct simap *portno_names);
+char *parse_ofp_exact_flow(struct ofproto *, struct flow *flow,
+                           struct flow_wildcards *wc, 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 177bf2b..96e781e 100644
--- a/include/openvswitch/ofp-util.h
+++ b/include/openvswitch/ofp-util.h
@@ -221,7 +221,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 ofproto *,struct ofpbuf *,
+                                     struct match *,
                                      uint16_t *padded_match_len);
 enum ofperr ofputil_pull_ofp11_mask(struct ofpbuf *, struct match *,
                                     struct mf_bitmap *bm);
@@ -327,7 +328,8 @@  struct ofputil_flow_mod {
     size_t ofpacts_len;      /* Length of ofpacts, in bytes. */
 };
 
-enum ofperr ofputil_decode_flow_mod(struct ofputil_flow_mod *,
+enum ofperr ofputil_decode_flow_mod(struct ofproto *,
+                                    struct ofputil_flow_mod *,
                                     const struct ofp_header *,
                                     enum ofputil_protocol,
                                     struct ofpbuf *ofpacts,
@@ -347,7 +349,7 @@  struct ofputil_flow_stats_request {
     uint8_t table_id;
 };
 
-enum ofperr ofputil_decode_flow_stats_request(
+enum ofperr ofputil_decode_flow_stats_request(struct ofproto *,
     struct ofputil_flow_stats_request *, const struct ofp_header *);
 struct ofpbuf *ofputil_encode_flow_stats_request(
     const struct ofputil_flow_stats_request *, enum ofputil_protocol);
@@ -376,7 +378,8 @@  int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *,
                                     struct ofpbuf *msg,
                                     bool flow_age_extension,
                                     struct ofpbuf *ofpacts);
-void ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *,
+void ofputil_append_flow_stats_reply(struct ofproto *,
+                                     const struct ofputil_flow_stats *,
                                      struct ovs_list *replies);
 
 /* Aggregate stats reply, independent of protocol. */
@@ -450,8 +453,9 @@  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,
-                                     struct ofputil_packet_in *,
+enum ofperr ofputil_decode_packet_in(struct ofproto *,
+                                     const struct ofp_header *,
+                                     bool loose, struct ofputil_packet_in *,
                                      size_t *total_len, uint32_t *buffer_id,
                                      struct ofpbuf *continuation);
 
@@ -502,6 +506,7 @@  struct ofpbuf *ofputil_encode_packet_in_private(
     uint16_t max_len, struct pktbuf *);
 
 enum ofperr ofputil_decode_packet_in_private(
+    struct ofproto *,
     const struct ofp_header *, bool loose,
     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..0feb8a6 100644
--- a/include/openvswitch/tun-metadata.h
+++ b/include/openvswitch/tun-metadata.h
@@ -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..087bc45 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 ofproto *ofproto, const struct cls_rule *rule,
+                struct ds *s)
 {
-    minimatch_format(&rule->match, s, rule->priority);
+    minimatch_format(ofproto, &rule->match, s, rule->priority);
 }
 
 /* Returns true if 'rule' matches every packet, false otherwise. */
diff --git a/lib/classifier.h b/lib/classifier.h
index 44185a3..c2a2121 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 ofproto *, const struct cls_rule *,
+                     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..b422a06 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(NULL, &flow_filter, &wc_filter, 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..840de6e 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(NULL, oh, true, &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..664cc3c 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -24,6 +24,8 @@ 
 #include "openvswitch/ofp-util.h"
 #include "packets.h"
 #include "tun-metadata.h"
+#include "ovs-rcu.h"
+#include "ofproto/ofproto-provider.h"
 
 /* Converts the flow in 'flow' into a match in 'match', with the given
  * 'wildcards'. */
@@ -1463,11 +1465,16 @@  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 ofproto *ofproto, const struct minimatch *match,
+                 struct ds *s, int priority)
 {
     struct match megamatch;
 
     minimatch_expand(match, &megamatch);
+    if (ofproto) {
+        megamatch.flow.tunnel.metadata.tab =
+            ovsrcu_get_protected(struct tun_table *, &ofproto->metadata_tab);
+    }
     match_format(&megamatch, s, priority);
 }
 
diff --git a/lib/nx-match.c b/lib/nx-match.c
index b03ccf2..d14a446 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -22,6 +22,7 @@ 
 
 #include "classifier.h"
 #include "colors.h"
+#include "ofproto/ofproto-provider.h"
 #include "openvswitch/hmap.h"
 #include "openflow/nicira-ext.h"
 #include "openvswitch/dynamic-string.h"
@@ -473,12 +474,17 @@  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)
+nx_pull_raw(struct ofproto *ofproto, const uint8_t *p, unsigned int match_len,
+            bool strict, struct match *match, ovs_be64 *cookie,
+            ovs_be64 *cookie_mask)
 {
     ovs_assert((cookie != NULL) == (cookie_mask != NULL));
 
     match_init_catchall(match);
+    if (ofproto) {
+        match->flow.tunnel.metadata.tab =
+            ovsrcu_get_protected(struct tun_table *, &ofproto->metadata_tab);
+    }
     if (cookie) {
         *cookie = *cookie_mask = htonll(0);
     }
@@ -529,12 +535,13 @@  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,
+nx_pull_match__(struct ofproto *ofproto, struct ofpbuf *b,
+                unsigned int match_len, bool strict, struct match *match,
                 ovs_be64 *cookie, ovs_be64 *cookie_mask)
 {
     uint8_t *p = NULL;
@@ -549,7 +556,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(ofproto, p, match_len, strict, match, cookie,
+                       cookie_mask);
 }
 
 /* Parses the nx_match formatted match description in 'b' with length
@@ -561,24 +569,28 @@  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,
+nx_pull_match(struct ofproto *ofproto, struct ofpbuf *b,
+              unsigned int match_len, struct match *match,
               ovs_be64 *cookie, ovs_be64 *cookie_mask)
 {
-    return nx_pull_match__(b, match_len, true, match, cookie, cookie_mask);
+    return nx_pull_match__(ofproto, b, match_len, true, match, cookie,
+                           cookie_mask);
 }
 
 /* Behaves the same as nx_pull_match(), but skips over unknown NXM headers,
  * instead of failing with an error. */
 enum ofperr
-nx_pull_match_loose(struct ofpbuf *b, unsigned int match_len,
-                    struct match *match,
+nx_pull_match_loose(struct ofproto *ofproto, struct ofpbuf *b,
+                    unsigned int match_len, struct match *match,
                     ovs_be64 *cookie, ovs_be64 *cookie_mask)
 {
-    return nx_pull_match__(b, match_len, false, match, cookie, cookie_mask);
+    return nx_pull_match__(ofproto, b, match_len, false, match, cookie,
+                           cookie_mask);
 }
 
 static enum ofperr
-oxm_pull_match__(struct ofpbuf *b, bool strict, struct match *match)
+oxm_pull_match__(struct ofproto *ofproto, struct ofpbuf *b, bool strict,
+                 struct match *match)
 {
     struct ofp11_match_header *omh = b->data;
     uint8_t *p;
@@ -605,7 +617,7 @@  oxm_pull_match__(struct ofpbuf *b, bool strict, struct match *match)
         return OFPERR_OFPBMC_BAD_LEN;
     }
 
-    return nx_pull_raw(p + sizeof *omh, match_len - sizeof *omh,
+    return nx_pull_raw(ofproto, p + sizeof *omh, match_len - sizeof *omh,
                        strict, match, NULL, NULL);
 }
 
@@ -616,17 +628,18 @@  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 ofproto *ofproto, struct ofpbuf *b, struct match *match)
 {
-    return oxm_pull_match__(b, true, match);
+    return oxm_pull_match__(ofproto, b, true, 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 ofproto *ofproto, struct ofpbuf *b,
+                     struct match *match)
 {
-    return oxm_pull_match__(b, false, match);
+    return oxm_pull_match__(ofproto, b, false, match);
 }
 
 /* Parses the OXM match description in the 'oxm_len' bytes in 'oxm'.  Stores
@@ -636,9 +649,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(struct ofproto *ofproto, const void *oxm, size_t oxm_len,
+                 struct match *match)
 {
-    return nx_pull_raw(oxm, oxm_len, true, match, NULL, NULL);
+    return nx_pull_raw(ofproto, oxm, oxm_len, true, match, NULL, NULL);
 }
 
 /* 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..5ffccab 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -48,15 +48,17 @@  char *mf_parse_subfield(struct mf_subfield *, const char *s)
     OVS_WARN_UNUSED_RESULT;
 
 /* Decoding matches. */
-enum ofperr nx_pull_match(struct ofpbuf *, unsigned int match_len,
-                          struct match *,
+enum ofperr nx_pull_match(struct ofproto *, struct ofpbuf *,
+                          unsigned int match_len, struct match *,
                           ovs_be64 *cookie, ovs_be64 *cookie_mask);
-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 *);
+enum ofperr nx_pull_match_loose(struct ofproto *, struct ofpbuf *,
+                                unsigned int match_len, struct match *,
+                                ovs_be64 *cookie, ovs_be64 *cookie_mask);
+enum ofperr oxm_pull_match(struct ofproto *, struct ofpbuf *, struct match *);
+enum ofperr oxm_pull_match_loose(struct ofproto *, struct ofpbuf *,
+                                 struct match *);
+enum ofperr oxm_decode_match(struct ofproto *, const void *, size_t,
+                             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..a4ec8e5 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -26,6 +26,7 @@ 
 #include "multipath.h"
 #include "netdev.h"
 #include "nx-match.h"
+#include "ofproto/ofproto-provider.h"
 #include "openflow/openflow.h"
 #include "openvswitch/dynamic-string.h"
 #include "openvswitch/meta-flow.h"
@@ -1121,8 +1122,9 @@  parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
  * Returns NULL on success, otherwise a malloc()'d string that explains the
  * problem. */
 char *
-parse_ofp_exact_flow(struct flow *flow, struct flow_wildcards *wc,
-                     const char *s, const struct simap *portno_names)
+parse_ofp_exact_flow(struct ofproto *ofproto, struct flow *flow,
+                     struct flow_wildcards *wc, const char *s,
+                     const struct simap *portno_names)
 {
     char *pos, *key, *value_s;
     char *error = NULL;
@@ -1133,6 +1135,11 @@  parse_ofp_exact_flow(struct flow *flow, struct flow_wildcards *wc,
         memset(wc, 0, sizeof *wc);
     }
 
+    if (ofproto) {
+        flow->tunnel.metadata.tab = ovsrcu_get_protected(struct tun_table *,
+                                                        &ofproto->metadata_tab);
+    }
+
     pos = copy = xstrdup(s);
     while (ofputil_parse_key_value(&pos, &key, &value_s)) {
         const struct protocol *p;
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 0b3bf01..b52d249 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(NULL, oh, true,
                                              &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(NULL, &fm, oh, protocol, &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(NULL, &fsr, oh);
     if (error) {
         ofp_print_error(string, error);
         return;
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index ccb06fe..14885e6 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -31,6 +31,7 @@ 
 #include "netdev.h"
 #include "nx-match.h"
 #include "id-pool.h"
+#include "ofproto/ofproto-provider.h"
 #include "openflow/netronome-ext.h"
 #include "openvswitch/dynamic-string.h"
 #include "openvswitch/meta-flow.h"
@@ -278,8 +279,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 ofproto *ofproto, struct ofpbuf *buf,
+                         struct match *match, uint16_t *padded_match_len)
 {
     struct ofp11_match_header *omh = buf->data;
     uint16_t match_len;
@@ -308,7 +309,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(ofproto, buf, match);
 
     default:
         return OFPERR_OFPBMC_BAD_TYPE;
@@ -1568,7 +1569,8 @@  ofputil_encode_flow_mod_flags(enum ofputil_flow_mod_flags flags,
  * Does not validate the flow_mod actions.  The caller should do that, with
  * ofpacts_check(). */
 enum ofperr
-ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
+ofputil_decode_flow_mod(struct ofproto *ofproto,
+                        struct ofputil_flow_mod *fm,
                         const struct ofp_header *oh,
                         enum ofputil_protocol protocol,
                         struct ofpbuf *ofpacts,
@@ -1584,7 +1586,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(ofproto, &b, &fm->match, NULL);
         if (error) {
             return error;
         }
@@ -1678,7 +1680,7 @@  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),
+            error = nx_pull_match(ofproto, &b, ntohs(nfm->match_len),
                                   &fm->match, &fm->cookie, &fm->cookie_mask);
             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,
+ofputil_decode_ofpst11_flow_request(struct ofproto *ofproto,
+                                    struct ofputil_flow_stats_request *fsr,
                                     struct ofpbuf *b, bool aggregate)
 {
     const struct ofp11_flow_stats_request *ofsr;
@@ -2283,7 +2286,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(ofproto, b, &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,
+ofputil_decode_nxst_flow_request(struct ofproto *ofproto,
+                                 struct ofputil_flow_stats_request *fsr,
                                  struct ofpbuf *b, bool aggregate)
 {
     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,
+    error = nx_pull_match(ofproto, b, ntohs(nfsr->match_len), &fsr->match,
                           &fsr->cookie, &fsr->cookie_mask);
     if (error) {
         return error;
@@ -2712,7 +2716,8 @@  ofputil_pull_queue_get_config_reply(struct ofpbuf *msg,
  * request 'oh', into an abstract flow_stats_request in 'fsr'.  Returns 0 if
  * successful, otherwise an OpenFlow error code. */
 enum ofperr
-ofputil_decode_flow_stats_request(struct ofputil_flow_stats_request *fsr,
+ofputil_decode_flow_stats_request(struct ofproto *ofproto,
+                                  struct ofputil_flow_stats_request *fsr,
                                   const struct ofp_header *oh)
 {
     struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
@@ -2725,16 +2730,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(ofproto, fsr, &b, false);
 
     case OFPRAW_OFPST11_AGGREGATE_REQUEST:
-        return ofputil_decode_ofpst11_flow_request(fsr, &b, true);
+        return ofputil_decode_ofpst11_flow_request(ofproto, fsr, &b, true);
 
     case OFPRAW_NXST_FLOW_REQUEST:
-        return ofputil_decode_nxst_flow_request(fsr, &b, false);
+        return ofputil_decode_nxst_flow_request(ofproto, fsr, &b, false);
 
     case OFPRAW_NXST_AGGREGATE_REQUEST:
-        return ofputil_decode_nxst_flow_request(fsr, &b, true);
+        return ofputil_decode_nxst_flow_request(ofproto, fsr, &b, true);
 
     default:
         /* Hey, the caller lied. */
@@ -2878,7 +2883,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(NULL, msg, &fs->match,
+                                     &padded_match_len)) {
             VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST_FLOW reply bad match");
             return EINVAL;
         }
@@ -2960,7 +2966,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(NULL, msg, match_len, &fs->match, NULL, NULL)) {
             return EINVAL;
         }
         instructions_len = length - sizeof *nfs - ROUND_UP(match_len, 8);
@@ -3015,14 +3021,22 @@  unknown_to_zero(uint64_t count)
  * those already present in the list of ofpbufs in 'replies'.  'replies' should
  * have been initialized with ofpmp_init(). */
 void
-ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
+ofputil_append_flow_stats_reply(struct ofproto *ofproto,
+                                const struct ofputil_flow_stats *fs,
                                 struct ovs_list *replies)
 {
+    struct ofputil_flow_stats *fs_ = CONST_CAST(struct ofputil_flow_stats *,
+                                                fs);
+    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 =
+              ovsrcu_get_protected(struct tun_table *, &ofproto->metadata_tab);
+
     if (raw == OFPRAW_OFPST11_FLOW_REPLY || raw == OFPRAW_OFPST13_FLOW_REPLY) {
         struct ofp11_flow_stats *ofs;
 
@@ -3108,6 +3122,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
@@ -3171,7 +3186,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(NULL, &b, &fr->match, NULL);
         if (error) {
             return error;
         }
@@ -3207,7 +3222,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(NULL, &b, ntohs(nfr->match_len), &fr->match,
                               NULL, NULL);
         if (error) {
             return error;
@@ -3328,8 +3343,8 @@  ofputil_encode_flow_removed(const struct ofputil_flow_removed *fr,
 /* The caller has done basic initialization of '*pin'; the other output
  * arguments needs to be initialized. */
 static enum ofperr
-decode_nx_packet_in2(const struct ofp_header *oh, bool loose,
-                     struct ofputil_packet_in *pin,
+decode_nx_packet_in2(struct ofproto *ofproto, const struct ofp_header *oh,
+                     bool loose, struct ofputil_packet_in *pin,
                      size_t *total_len, uint32_t *buffer_id,
                      struct ofpbuf *continuation)
 {
@@ -3382,7 +3397,8 @@  decode_nx_packet_in2(const struct ofp_header *oh, bool loose,
         }
 
         case NXPINT_METADATA:
-            error = oxm_decode_match(payload.msg, ofpbuf_msgsize(&payload),
+            error = oxm_decode_match(ofproto, payload.msg,
+                                     ofpbuf_msgsize(&payload),
                                      &pin->flow_metadata);
             break;
 
@@ -3439,8 +3455,8 @@  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,
-                         struct ofputil_packet_in *pin,
+ofputil_decode_packet_in(struct ofproto *ofproto, const struct ofp_header *oh,
+                         bool loose, struct ofputil_packet_in *pin,
                          size_t *total_lenp, uint32_t *buffer_idp,
                          struct ofpbuf *continuation)
 {
@@ -3460,7 +3476,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(ofproto, &b,
+                                                 &pin->flow_metadata);
         if (error) {
             return error;
         }
@@ -3518,7 +3535,7 @@  ofputil_decode_packet_in(const struct ofp_header *oh, bool loose,
         int error;
 
         npi = ofpbuf_pull(&b, sizeof *npi);
-        error = nx_pull_match_loose(&b, ntohs(npi->match_len),
+        error = nx_pull_match_loose(ofproto, &b, ntohs(npi->match_len),
                                     &pin->flow_metadata, NULL, NULL);
         if (error) {
             return error;
@@ -3538,8 +3555,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(ofproto, oh, loose, pin,
+                                                 &total_len, &buffer_id,
+                                                 continuation);
         if (error) {
             return error;
         }
@@ -4046,7 +4064,8 @@  parse_actions_property(struct ofpbuf *property, enum ofp_version version,
  * When successful, 'pin' contains some dynamically allocated data.  Call
  * ofputil_packet_in_private_destroy() to free this data. */
 enum ofperr
-ofputil_decode_packet_in_private(const struct ofp_header *oh, bool loose,
+ofputil_decode_packet_in_private(struct ofproto *ofproto,
+                                 const struct ofp_header *oh, bool loose,
                                  struct ofputil_packet_in_private *pin,
                                  size_t *total_len, uint32_t *buffer_id)
 {
@@ -4054,8 +4073,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(ofproto, oh, loose, &pin->public,
+                                     total_len, buffer_id, &continuation);
     if (error) {
         return error;
     }
@@ -6617,7 +6636,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(NULL, msg, ntohs(nfmr->match_len), &rq->match,
+                         NULL, NULL);
 }
 
 void
@@ -6725,7 +6745,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(NULL, msg, match_len, update->match, NULL, NULL);
         if (error) {
             return error;
         }
diff --git a/lib/tun-metadata.c b/lib/tun-metadata.c
index 36006e3..d9e589f 100644
--- a/lib/tun-metadata.c
+++ b/lib/tun-metadata.c
@@ -28,6 +28,7 @@ 
 #include "ovs-thread.h"
 #include "ovs-rcu.h"
 #include "packets.h"
+#include "ofproto/ofproto-provider.h"
 #include "tun-metadata.h"
 #include "util.h"
 
@@ -52,14 +53,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 +83,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 +117,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,33 +134,19 @@  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)
-{
-    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);
-}
-
 enum ofperr
-tun_metadata_table_mod(struct ofputil_tlv_table_mod *ttm)
+tun_metadata_table_mod(struct ofproto *ofproto,
+                       struct ofputil_tlv_table_mod *ttm)
 {
     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);
+    old_map = ovsrcu_get_protected(struct tun_table *, &ofproto->metadata_tab);
 
     switch (ttm->command) {
     case NXTTMC_ADD:
-        new_map = table_alloc(old_map);
+        new_map = tun_metadata_alloc(old_map);
 
         LIST_FOR_EACH (ofp_map, list_node, &ttm->mappings) {
             err = tun_metadata_add_entry(new_map, ofp_map->index,
@@ -171,14 +154,14 @@  tun_metadata_table_mod(struct ofputil_tlv_table_mod *ttm)
                                          ofp_map->option_type,
                                          ofp_map->option_len);
             if (err) {
-                table_free(new_map);
-                goto out;
+                tun_metadata_free(new_map);
+                return err;
             }
         }
         break;
 
     case NXTTMC_DELETE:
-        new_map = table_alloc(old_map);
+        new_map = tun_metadata_alloc(old_map);
 
         LIST_FOR_EACH (ofp_map, list_node, &ttm->mappings) {
             tun_metadata_del_entry(new_map, ofp_map->index);
@@ -186,27 +169,28 @@  tun_metadata_table_mod(struct ofputil_tlv_table_mod *ttm)
         break;
 
     case NXTTMC_CLEAR:
-        new_map = table_alloc(NULL);
+        new_map = tun_metadata_alloc(NULL);
         break;
 
     default:
         OVS_NOT_REACHED();
     }
 
-    ovsrcu_set(&metadata_tab, new_map);
-    ovsrcu_postpone(table_free, old_map);
+    ovsrcu_set(&ofproto->metadata_tab, new_map);
+    ovsrcu_postpone(tun_metadata_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(struct ofproto *ofproto,
+                           struct ofputil_tlv_table_reply *ttr)
 {
-    struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
+    struct tun_table *map;
     int i;
 
+    map = ovsrcu_get_protected(struct tun_table *, &ofproto->metadata_tab);
+
     ttr->max_option_space = TUN_METADATA_TOT_OPT_SIZE;
     ttr->max_fields = TUN_METADATA_NUM_OPTS;
     ovs_list_init(&ttr->mappings);
@@ -233,14 +217,14 @@  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);
+    struct tun_table *map = tnl->metadata.tab;
     unsigned int idx = mf->id - MFF_TUN_METADATA0;
     struct tun_metadata_loc *loc;
 
@@ -260,14 +244,14 @@  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);
+    struct tun_table *map = tnl->metadata.tab;
     unsigned int idx = mf->id - MFF_TUN_METADATA0;
     struct tun_metadata_loc *loc;
 
@@ -333,11 +317,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 +338,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);
+    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 +346,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 +380,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 +460,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 +504,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 +555,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 +585,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(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 +642,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 +664,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];
+        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 +722,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 +733,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 +765,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 +774,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,8 +831,6 @@  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);
@@ -978,7 +838,7 @@  tun_metadata_to_geneve_udpif_mask(const struct flow_tnl *flow_src,
 
 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,
+                             unsigned int idx, const struct flow_tnl *mask,
                              bool *is_masked)
 {
     union mf_value mask_opts;
@@ -997,63 +857,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..a987977 100644
--- a/lib/tun-metadata.h
+++ b/lib/tun-metadata.h
@@ -32,11 +32,15 @@  union mf_value;
 struct ofputil_tlv_table_mod;
 struct ofputil_tlv_table_reply;
 struct tun_table;
+struct ofproto;
 
-void tun_metadata_init(void);
+struct tun_table *tun_metadata_alloc(const struct tun_table *old_map);
+void tun_metadata_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 ofproto *,
+                                   struct ofputil_tlv_table_mod *);
+void tun_metadata_table_request(struct ofproto *,
+                                struct ofputil_tlv_table_reply *);
 
 void tun_metadata_read(const struct flow_tnl *,
                        const struct mf_field *, union mf_value *);
@@ -48,17 +52,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(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 042a50a..780b042 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 893c033..6adb1bf 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_metadata_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;
@@ -5093,6 +5107,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;
@@ -5494,6 +5509,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) {
+        struct tun_table *tun_tab = ofproto_dpif_get_metadata_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_metadata_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);
 
@@ -5650,9 +5687,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 fd2c2bd..32b4136 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);
 
+struct tun_table *
+ofproto_dpif_get_metadata_tab(const struct ofproto_dpif *ofproto)
+{
+    return ovsrcu_get(struct tun_table *, &ofproto->up.metadata_tab);
+}
+
 /* Executes 'fm'.  The caller retains ownership of 'fm' and everything in
  * it. */
 void
@@ -4725,7 +4731,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;
@@ -4742,7 +4749,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(ofproto, &rule->up.cr, result);
     ds_put_char(result, '\n');
 
     actions = rule_dpif_get_actions(rule);
@@ -4844,7 +4851,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
@@ -4961,24 +4968,46 @@  parse_flow_and_packet(int argc, const char *argv[],
             error = "Invalid datapath flow";
             goto exit;
         }
+
+        flow->tunnel.metadata.tab = ofproto_dpif_get_metadata_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(&(*ofprotop)->up, flow, NULL,
+                                   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 f1e1209..78c18b6 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -178,6 +178,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 *);
+struct tun_table *ofproto_dpif_get_metadata_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 401d1b5..1fdf3e3 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;
@@ -129,6 +130,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);
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index aec4837..9855e3b 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -544,7 +544,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) {
@@ -1562,6 +1562,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);
@@ -3548,7 +3550,8 @@  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(ofproto, oh, false, &pin,
+                                             NULL, NULL);
     if (error) {
         return error;
     }
@@ -4251,7 +4254,7 @@  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(ofproto, &fsr, request);
     if (error) {
         return error;
     }
@@ -4305,7 +4308,7 @@  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(ofproto, &fs, &replies);
     }
 
     rule_collection_unref(&rules);
@@ -4317,7 +4320,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;
@@ -4337,7 +4340,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(ofproto, &rule->cr, results);
     ds_put_char(results, ',');
 
     ds_put_cstr(results, "actions=");
@@ -4357,7 +4360,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);
         }
     }
 }
@@ -4414,7 +4417,7 @@  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(ofproto, &request, oh);
     if (error) {
         return error;
     }
@@ -5484,8 +5487,8 @@  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,
+    error = ofputil_decode_flow_mod(ofproto, &fm, oh,
+                                    ofconn_get_protocol(ofconn), &ofpacts,
                                     u16_to_ofp(ofproto->max_ports),
                                     ofproto->n_tables);
     if (!error) {
@@ -7473,7 +7476,7 @@  handle_bundle_add(struct ofconn *ofconn, const struct ofp_header *oh)
         uint64_t ofpacts_stub[1024 / 8];
 
         ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
-        error = ofputil_decode_flow_mod(&fm, badd.msg,
+        error = ofputil_decode_flow_mod(ofproto, &fm, badd.msg,
                                         ofconn_get_protocol(ofconn),
                                         &ofpacts,
                                         u16_to_ofp(ofproto->max_ports),
@@ -7516,7 +7519,7 @@  handle_tlv_table_mod(struct ofconn *ofconn, const struct ofp_header *oh)
         return error;
     }
 
-    error = tun_metadata_table_mod(&ttm);
+    error = tun_metadata_table_mod(ofconn_get_ofproto(ofconn), &ttm);
 
     ofputil_uninit_tlv_table(&ttm.mappings);
     return error;
@@ -7528,7 +7531,8 @@  handle_tlv_table_request(struct ofconn *ofconn, const struct ofp_header *oh)
     struct ofputil_tlv_table_reply ttr;
     struct ofpbuf *b;
 
-    tun_metadata_table_request(&ttr);
+    tun_metadata_table_request(ofconn_get_ofproto(ofconn), &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 2737467..adec4d2 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(NULL, msg, true, &pin,
                                                  NULL, NULL, &continuation);
 
     if (error) {
diff --git a/tests/test-odp.c b/tests/test-odp.c
index dabfb85..63fbf13 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(NULL, &flow_filter, &wc_filter, 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 6fd3818..7c16ca2 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -1828,7 +1828,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(NULL, b->data, true, &pin,
                                                      NULL, NULL,
                                                      &continuation);
                     if (error) {
@@ -2927,7 +2927,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(NULL, &fte->rule, s);
     if (version->cookie != htonll(0)) {
         ds_put_format(s, " cookie=0x%"PRIx64, ntohll(version->cookie));
     }
@@ -3518,16 +3518,16 @@  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(NULL, &nx_match, &match);
             } else {
-                error = nx_pull_match(&nx_match, match_len, &match,
+                error = nx_pull_match(NULL, &nx_match, match_len, &match,
                                       &cookie, &cookie_mask);
             }
         } else {
             if (oxm) {
-                error = oxm_pull_match_loose(&nx_match, &match);
+                error = oxm_pull_match_loose(NULL, &nx_match, &match);
             } else {
-                error = nx_pull_match_loose(&nx_match, match_len, &match,
+                error = nx_pull_match_loose(NULL, &nx_match, match_len, &match,
                                             &cookie, &cookie_mask);
             }
         }
@@ -3938,7 +3938,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(NULL, &nxm, nxm_match_len, &nxm_match, NULL, NULL);
     printf("NXM: %s -> ", nxm_s);
     if (error) {
         printf("%s\n", ofperr_to_string(error));
@@ -3954,7 +3954,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(NULL, &nxm, &nxm_match);
     printf("OXM: %s -> ", nxm_s);
     if (error) {
         printf("%s\n", ofperr_to_string(error));