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));
