testsuite: Add timeout to add_of_br() command.
[cascardo/ovs.git] / lib / tun-metadata.c
index d9ce9f2..ececee5 100644 (file)
@@ -37,7 +37,7 @@ struct tun_meta_entry {
     bool valid;                 /* True if allocated to a class and type. */
 };
 
-/* Maps from Geneve option class+type to positions in a struct tun_metadata's
+/* Maps from TLV option class+type to positions in a struct tun_metadata's
  * 'opts' array.  */
 struct tun_table {
     /* TUN_METADATA<i> is stored in element <i>. */
@@ -60,7 +60,8 @@ static enum ofperr tun_metadata_add_entry(struct tun_table *map, uint8_t idx,
 static void tun_metadata_del_entry(struct tun_table *map, uint8_t idx)
             OVS_REQUIRES(tab_mutex);
 static void memcpy_to_metadata(struct tun_metadata *dst, const void *src,
-                               const struct tun_metadata_loc *);
+                               const struct tun_metadata_loc *,
+                               unsigned int idx);
 static void memcpy_from_metadata(void *dst, const struct tun_metadata *src,
                                  const struct tun_metadata_loc *);
 
@@ -131,6 +132,7 @@ table_free(struct tun_table *map) OVS_REQUIRES(tab_mutex)
         tun_metadata_del_entry(map, entry - map->entries);
     }
 
+    hmap_destroy(&map->key_hmap);
     free(map);
 }
 
@@ -148,21 +150,21 @@ tun_metadata_init(void)
 }
 
 enum ofperr
-tun_metadata_table_mod(struct ofputil_geneve_table_mod *gtm)
+tun_metadata_table_mod(struct ofputil_tlv_table_mod *ttm)
 {
     struct tun_table *old_map, *new_map;
-    struct ofputil_geneve_map *ofp_map;
+    struct ofputil_tlv_map *ofp_map;
     enum ofperr err = 0;
 
     ovs_mutex_lock(&tab_mutex);
 
     old_map = ovsrcu_get_protected(struct tun_table *, &metadata_tab);
 
-    switch (gtm->command) {
-    case NXGTMC_ADD:
+    switch (ttm->command) {
+    case NXTTMC_ADD:
         new_map = table_alloc(old_map);
 
-        LIST_FOR_EACH (ofp_map, list_node, &gtm->mappings) {
+        LIST_FOR_EACH (ofp_map, list_node, &ttm->mappings) {
             err = tun_metadata_add_entry(new_map, ofp_map->index,
                                          ofp_map->option_class,
                                          ofp_map->option_type,
@@ -174,15 +176,15 @@ tun_metadata_table_mod(struct ofputil_geneve_table_mod *gtm)
         }
         break;
 
-    case NXGTMC_DELETE:
+    case NXTTMC_DELETE:
         new_map = table_alloc(old_map);
 
-        LIST_FOR_EACH (ofp_map, list_node, &gtm->mappings) {
+        LIST_FOR_EACH (ofp_map, list_node, &ttm->mappings) {
             tun_metadata_del_entry(new_map, ofp_map->index);
         }
         break;
 
-    case NXGTMC_CLEAR:
+    case NXTTMC_CLEAR:
         new_map = table_alloc(NULL);
         break;
 
@@ -199,18 +201,18 @@ out:
 }
 
 void
-tun_metadata_table_request(struct ofputil_geneve_table_reply *gtr)
+tun_metadata_table_request(struct ofputil_tlv_table_reply *ttr)
 {
     struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
     int i;
 
-    gtr->max_option_space = TUN_METADATA_TOT_OPT_SIZE;
-    gtr->max_fields = TUN_METADATA_NUM_OPTS;
-    list_init(&gtr->mappings);
+    ttr->max_option_space = TUN_METADATA_TOT_OPT_SIZE;
+    ttr->max_fields = TUN_METADATA_NUM_OPTS;
+    list_init(&ttr->mappings);
 
     for (i = 0; i < TUN_METADATA_NUM_OPTS; i++) {
         struct tun_meta_entry *entry = &map->entries[i];
-        struct ofputil_geneve_map *map;
+        struct ofputil_tlv_map *map;
 
         if (!entry->valid) {
             continue;
@@ -222,11 +224,11 @@ tun_metadata_table_request(struct ofputil_geneve_table_reply *gtr)
         map->option_len = entry->loc.len;
         map->index = i;
 
-        list_push_back(&gtr->mappings, &map->list_node);
+        list_push_back(&ttr->mappings, &map->list_node);
     }
 }
 
-/* Copies the value of field 'mf' from 'metadata' into 'value'.
+/* Copies the value of field 'mf' from 'tnl' (which must be in non-UDPIF format) * into 'value'.
  *
  * 'mf' must be an MFF_TUN_METADATA* field.
  *
@@ -234,7 +236,7 @@ tun_metadata_table_request(struct ofputil_geneve_table_reply *gtr)
  * tun_metadata_init().  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 tun_metadata *metadata,
+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);
@@ -250,10 +252,10 @@ tun_metadata_read(const struct tun_metadata *metadata,
 
     memset(value->tun_metadata, 0, mf->n_bytes - loc->len);
     memcpy_from_metadata(value->tun_metadata + mf->n_bytes - loc->len,
-                         metadata, loc);
+                         &tnl->metadata, loc);
 }
 
-/* Copies 'value' into field 'mf' in 'metadata'.
+/* Copies 'value' into field 'mf' in 'tnl' (in non-UDPIF format).
  *
  * 'mf' must be an MFF_TUN_METADATA* field.
  *
@@ -261,7 +263,7 @@ tun_metadata_read(const struct tun_metadata *metadata,
  * tun_metadata_init().  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 tun_metadata *metadata,
+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);
@@ -273,18 +275,21 @@ tun_metadata_write(struct tun_metadata *metadata,
     }
 
     loc = &map->entries[idx].loc;
-
-    ULLONG_SET1(metadata->opt_map, idx);
-    memcpy_to_metadata(metadata, value->tun_metadata + mf->n_bytes - loc->len,
-                       loc);
+    memcpy_to_metadata(&tnl->metadata,
+                       value->tun_metadata + mf->n_bytes - loc->len, loc, idx);
 }
 
 static const struct tun_metadata_loc *
 metadata_loc_from_match(struct tun_table *map, struct match *match,
-                        unsigned int idx, unsigned int field_len)
+                        const char *name, unsigned int idx,
+                        unsigned int field_len, bool masked, char **err_str)
 {
     ovs_assert(idx < TUN_METADATA_NUM_OPTS);
 
+    if (err_str) {
+        *err_str = NULL;
+    }
+
     if (map) {
         if (map->entries[idx].valid) {
             return &map->entries[idx].loc;
@@ -293,24 +298,39 @@ metadata_loc_from_match(struct tun_table *map, struct match *match,
         }
     }
 
-    if (match->tun_md.alloc_offset + field_len >= TUN_METADATA_TOT_OPT_SIZE ||
-        match->tun_md.loc[idx].len) {
+    if (match->tun_md.alloc_offset + field_len > TUN_METADATA_TOT_OPT_SIZE) {
+        if (err_str) {
+            *err_str = xasprintf("field %s exceeds maximum size for tunnel "
+                                 "metadata (used %d, max %d)", name,
+                                 match->tun_md.alloc_offset + field_len,
+                                 TUN_METADATA_TOT_OPT_SIZE);
+        }
+
+        return NULL;
+    }
+
+    if (ULLONG_GET(match->wc.masks.tunnel.metadata.present.map, idx)) {
+        if (err_str) {
+            *err_str = xasprintf("field %s set multiple times", name);
+        }
+
         return NULL;
     }
 
-    match->tun_md.loc[idx].len = field_len;
-    match->tun_md.loc[idx].c.offset = match->tun_md.alloc_offset;
-    match->tun_md.loc[idx].c.len = field_len;
-    match->tun_md.loc[idx].c.next = NULL;
+    match->tun_md.entry[idx].loc.len = field_len;
+    match->tun_md.entry[idx].loc.c.offset = match->tun_md.alloc_offset;
+    match->tun_md.entry[idx].loc.c.len = field_len;
+    match->tun_md.entry[idx].loc.c.next = NULL;
+    match->tun_md.entry[idx].masked = masked;
     match->tun_md.alloc_offset += field_len;
     match->tun_md.valid = true;
 
-    return &match->tun_md.loc[idx];
+    return &match->tun_md.entry[idx].loc;
 }
 
 /* Makes 'match' match 'value'/'mask' on field 'mf'.
  *
- * 'mf' must be an MFF_TUN_METADATA* field.
+ * '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
@@ -322,20 +342,29 @@ metadata_loc_from_match(struct tun_table *map, struct match *match,
  * value.
  *
  * 'mask' may be NULL; if so, then 'mf' is made exact-match.
+ *
+ * If non-NULL, 'err_str' returns a malloc'ed string describing any errors
+ * with the request or NULL if there is no error. The caller is reponsible
+ * for freeing the string.
  */
 void
 tun_metadata_set_match(const struct mf_field *mf, const union mf_value *value,
-                       const union mf_value *mask, struct match *match)
+                       const union mf_value *mask, struct match *match,
+                       char **err_str)
 {
     struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
     const struct tun_metadata_loc *loc;
     unsigned int idx = mf->id - MFF_TUN_METADATA0;
     unsigned int field_len;
+    bool is_masked;
     unsigned int data_offset;
     union mf_value data;
 
-    field_len = mf_field_len(mf, value, mask);
-    loc = metadata_loc_from_match(map, match, idx, field_len);
+    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);
     if (!loc) {
         return;
     }
@@ -353,8 +382,8 @@ tun_metadata_set_match(const struct mf_field *mf, const union mf_value *value,
                                    mask->tun_metadata[data_offset + i];
         }
     }
-    ULLONG_SET1(match->flow.tunnel.metadata.opt_map, idx);
-    memcpy_to_metadata(&match->flow.tunnel.metadata, data.tun_metadata, loc);
+    memcpy_to_metadata(&match->flow.tunnel.metadata, data.tun_metadata,
+                       loc, idx);
 
     if (!value) {
         memset(data.tun_metadata, 0, loc->len);
@@ -363,37 +392,74 @@ tun_metadata_set_match(const struct mf_field *mf, const union mf_value *value,
     } else {
         memcpy(data.tun_metadata, mask->tun_metadata + data_offset, loc->len);
     }
-    ULLONG_SET1(match->wc.masks.tunnel.metadata.opt_map, idx);
-    memcpy_to_metadata(&match->wc.masks.tunnel.metadata, data.tun_metadata, loc);
+    memcpy_to_metadata(&match->wc.masks.tunnel.metadata, data.tun_metadata,
+                       loc, idx);
 }
 
-/* Copies all MFF_TUN_METADATA* fields from 'metadata' to 'flow_metadata'. */
+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'. */
 void
-tun_metadata_get_fmd(const struct tun_metadata *metadata,
-                     struct match *flow_metadata)
+tun_metadata_get_fmd(const struct flow_tnl *tnl, struct match *flow_metadata)
 {
-    struct tun_table *map;
+    struct flow_tnl flow;
     int i;
 
-    map = metadata->tab;
-    if (!map) {
-        map = ovsrcu_get(struct tun_table *, &metadata_tab);
+    if (!udpif_to_parsed(tnl, NULL, &flow, NULL)) {
+        return;
     }
 
-    ULLONG_FOR_EACH_1 (i, metadata->opt_map) {
+    ULLONG_FOR_EACH_1 (i, flow.metadata.present.map) {
         union mf_value opts;
-        const struct tun_metadata_loc *old_loc = &map->entries[i].loc;
+        const struct tun_metadata_loc *old_loc = &flow.metadata.tab->entries[i].loc;
         const struct tun_metadata_loc *new_loc;
 
-        new_loc = metadata_loc_from_match(NULL, flow_metadata, i, old_loc->len);
+        new_loc = metadata_loc_from_match(NULL, flow_metadata, NULL, i,
+                                          old_loc->len, false, NULL);
 
-        memcpy_from_metadata(opts.tun_metadata, metadata, old_loc);
+        memcpy_from_metadata(opts.tun_metadata, &flow.metadata, old_loc);
         memcpy_to_metadata(&flow_metadata->flow.tunnel.metadata,
-                           opts.tun_metadata, new_loc);
+                           opts.tun_metadata, new_loc, i);
 
         memset(opts.tun_metadata, 0xff, old_loc->len);
         memcpy_to_metadata(&flow_metadata->wc.masks.tunnel.metadata,
-                           opts.tun_metadata, new_loc);
+                           opts.tun_metadata, new_loc, i);
     }
 }
 
@@ -418,17 +484,19 @@ tun_meta_find_key(const struct hmap *hmap, uint32_t key)
 
 static void
 memcpy_to_metadata(struct tun_metadata *dst, const void *src,
-                   const struct tun_metadata_loc *loc)
+                   const struct tun_metadata_loc *loc, unsigned int idx)
 {
     const struct tun_metadata_loc_chain *chain = &loc->c;
     int addr = 0;
 
     while (chain) {
-        memcpy(dst->opts + loc->c.offset + addr, (uint8_t *)src + addr,
+        memcpy(dst->opts.u8 + loc->c.offset + addr, (uint8_t *)src + addr,
                chain->len);
         addr += chain->len;
         chain = chain->next;
     }
+
+    ULLONG_SET1(dst->present.map, idx);
 }
 
 static void
@@ -439,7 +507,7 @@ memcpy_from_metadata(void *dst, const struct tun_metadata *src,
     int addr = 0;
 
     while (chain) {
-        memcpy((uint8_t *)dst + addr, src->opts + loc->c.offset + addr,
+        memcpy((uint8_t *)dst + addr, src->opts.u8 + loc->c.offset + addr,
                chain->len);
         addr += chain->len;
         chain = chain->next;
@@ -503,12 +571,12 @@ tun_metadata_add_entry(struct tun_table *map, uint8_t idx, uint16_t opt_class,
 
     entry = &map->entries[idx];
     if (entry->valid) {
-        return OFPERR_NXGTMFC_ALREADY_MAPPED;
+        return OFPERR_NXTTMFC_ALREADY_MAPPED;
     }
 
     entry->key = tun_meta_key(htons(opt_class), type);
     if (tun_meta_find_key(&map->key_hmap, entry->key)) {
-        return OFPERR_NXGTMFC_DUP_ENTRY;
+        return OFPERR_NXTTMFC_DUP_ENTRY;
     }
 
     entry->valid = true;
@@ -525,19 +593,17 @@ tun_metadata_add_entry(struct tun_table *map, uint8_t idx, uint16_t opt_class,
 
         if (!cur_chain) {
             cur_chain = xzalloc(sizeof *cur_chain);
+            prev_chain->next = cur_chain;
         }
 
         err = tun_metadata_alloc_chain(map, len, cur_chain);
         if (err) {
             tun_metadata_del_entry(map, idx);
-            return OFPERR_NXGTMFC_TABLE_FULL;
+            return OFPERR_NXTTMFC_TABLE_FULL;
         }
 
         len -= cur_chain->len;
 
-        if (prev_chain) {
-            prev_chain->next = cur_chain;
-        }
         prev_chain = cur_chain;
         cur_chain = NULL;
     }
@@ -578,19 +644,14 @@ tun_metadata_del_entry(struct tun_table *map, uint8_t idx)
     memset(&entry->loc, 0, sizeof entry->loc);
 }
 
-int
-tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
-                                const struct nlattr *flow_attrs,
-                                size_t flow_attr_len,
-                                const struct tun_metadata *flow_metadata,
-                                struct tun_metadata *metadata)
+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)
 {
-    bool is_mask = !!flow_attrs;
     struct tun_table *map;
-    const struct nlattr *flow;
-    int opts_len;
-    const struct geneve_opt *flow_opt;
-    const struct geneve_opt *opt = nl_attr_get(attr);
+    bool is_mask = flow_opt != opt;
 
     if (!is_mask) {
         map = ovsrcu_get(struct tun_table *, &metadata_tab);
@@ -603,30 +664,6 @@ tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
         return 0;
     }
 
-    if (is_mask) {
-        const struct nlattr *tnl_key;
-        int mask_len = nl_attr_get_size(attr);
-
-        tnl_key = nl_attr_find__(flow_attrs, flow_attr_len, OVS_KEY_ATTR_TUNNEL);
-        if (!tnl_key) {
-            return mask_len ? EINVAL : 0;
-        }
-
-        flow = nl_attr_find_nested(tnl_key, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
-        if (!flow) {
-            return mask_len ? EINVAL : 0;
-        }
-
-        if (mask_len != nl_attr_get_size(flow)) {
-            return EINVAL;
-        }
-    } else {
-        flow = attr;
-    }
-
-    opts_len = nl_attr_get_size(flow);
-    flow_opt = nl_attr_get(flow);
-
     while (opts_len > 0) {
         int len;
         struct tun_meta_entry *entry;
@@ -645,8 +682,8 @@ tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
                                                flow_opt->type));
         if (entry) {
             if (entry->loc.len == flow_opt->length * 4) {
-                memcpy_to_metadata(metadata, opt + 1, &entry->loc);
-                ULLONG_SET1(metadata->opt_map, entry - map->entries);
+                memcpy_to_metadata(metadata, opt + 1, &entry->loc,
+                                   entry - map->entries);
             } else {
                 return EINVAL;
             }
@@ -662,29 +699,114 @@ tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
     return 0;
 }
 
-void
-tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow,
-                                   struct ofpbuf *b)
+static const struct nlattr *
+tun_metadata_find_geneve_key(const struct nlattr *key, uint32_t key_len)
 {
-    struct tun_table *map;
-    size_t nlattr_offset;
-    int i;
+    const struct nlattr *tnl_key;
 
-    if (!flow->opt_map) {
-        return;
+    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);
     }
 
-    /* For all intents and purposes, the Geneve options are nested
-     * attributes even if this doesn't show up directly to netlink. It's
-     * similar enough that we can use the same mechanism. */
-    nlattr_offset = nl_msg_start_nested(b, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
+    *crit_opt = false;
 
-    ULLONG_FOR_EACH_1 (i, flow->opt_map) {
+    ULLONG_FOR_EACH_1 (i, flow->present.map) {
         struct tun_meta_entry *entry = &map->entries[i];
         struct geneve_opt *opt;
 
@@ -698,46 +820,63 @@ tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow,
         opt->r3 = 0;
 
         memcpy_from_metadata(opt + 1, flow, &entry->loc);
+        *crit_opt |= !!(opt->type & GENEVE_CRIT_OPT_TYPE);
     }
-
-    nl_msg_end_nested(b, nlattr_offset);
 }
 
-void
-tun_metadata_to_geneve_nlattr_mask(const struct ofpbuf *key,
-                                   const struct tun_metadata *mask,
-                                   const struct tun_metadata *flow,
+static void
+tun_metadata_to_geneve_nlattr_flow(const struct flow_tnl *flow,
                                    struct ofpbuf *b)
 {
-    struct tun_table *map = flow->tab;
-    const struct nlattr *tnl_key, *geneve_key;
-    struct nlattr *geneve_mask;
-    struct geneve_opt *opt;
-    int opts_len;
+    size_t nlattr_offset;
+    bool crit_opt;
 
-    if (!map) {
+    if (!flow->metadata.present.map) {
         return;
     }
 
-    tnl_key = nl_attr_find(key, 0, OVS_KEY_ATTR_TUNNEL);
-    if (!tnl_key) {
-        return;
-    }
+    /* For all intents and purposes, the Geneve options are nested
+     * attributes even if this doesn't show up directly to netlink. It's
+     * similar enough that we can use the same mechanism. */
+    nlattr_offset = nl_msg_start_nested(b, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
 
-    geneve_key = nl_attr_find_nested(tnl_key,
-                                     OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
-    if (!geneve_key) {
+    tun_metadata_to_geneve__(&flow->metadata, b, &crit_opt);
+
+    nl_msg_end_nested(b, nlattr_offset);
+}
+
+/* Converts from processed tunnel metadata information (in non-udpif
+ * format) in 'flow' to a stream of Geneve options suitable for
+ * transmission in 'opts'. Additionally returns whether there were
+ * any critical options in 'crit_opt' as well as the total length of
+ * data. */
+int
+tun_metadata_to_geneve_header(const struct flow_tnl *flow,
+                              struct geneve_opt *opts, bool *crit_opt)
+{
+    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);
+
+    return b.size;
+}
+
+static void
+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;
     }
 
-    geneve_mask = ofpbuf_tail(b);
-    nl_msg_put(b, geneve_key, geneve_key->nla_len);
-
     /* All of these options have already been validated, so no need
      * for sanity checking. */
-    opt = CONST_CAST(struct geneve_opt *, nl_attr_get(geneve_mask));
-    opts_len = nl_attr_get_size(geneve_mask);
-
     while (opts_len > 0) {
         struct tun_meta_entry *entry;
         int len = sizeof(*opt) + opt->length * 4;
@@ -762,14 +901,98 @@ tun_metadata_to_geneve_nlattr_mask(const struct ofpbuf *key,
     }
 }
 
+static void
+tun_metadata_to_geneve_nlattr_mask(const struct ofpbuf *key,
+                                   const struct flow_tnl *mask,
+                                   const struct flow_tnl *flow,
+                                   struct ofpbuf *b)
+{
+    const struct nlattr *geneve_key;
+    struct nlattr *geneve_mask;
+    struct geneve_opt *opt;
+    int opts_len;
+
+    if (!key) {
+        return;
+    }
+
+    geneve_key = tun_metadata_find_geneve_key(key->data, key->size);
+    if (!geneve_key) {
+        return;
+    }
+
+    geneve_mask = ofpbuf_tail(b);
+    nl_msg_put(b, geneve_key, geneve_key->nla_len);
+
+    opt = CONST_CAST(struct geneve_opt *, nl_attr_get(geneve_mask));
+    opts_len = nl_attr_get_size(geneve_mask);
+
+    tun_metadata_to_geneve_mask__(&flow->metadata, &mask->metadata,
+                                  opt, opts_len);
+}
+
+/* Convert from the tunnel metadata in 'tun' to netlink attributes stored
+ * in 'b'. Either UDPIF or non-UDPIF input forms are accepted.
+ *
+ * To assist with parsing, it is necessary to also pass in the tunnel metadata
+ * from the flow in 'flow' as well in the original netlink form of the flow in
+ * 'key'. */
+void
+tun_metadata_to_geneve_nlattr(const struct flow_tnl *tun,
+                              const struct flow_tnl *flow,
+                              const struct ofpbuf *key,
+                              struct ofpbuf *b)
+{
+    bool is_mask = tun != flow;
+
+    if (!(flow->flags & FLOW_TNL_F_UDPIF)) {
+        if (!is_mask) {
+            tun_metadata_to_geneve_nlattr_flow(tun, b);
+        } else {
+            tun_metadata_to_geneve_nlattr_mask(key, tun, flow, b);
+        }
+    } else if (flow->metadata.present.len || is_mask) {
+        nl_msg_put_unspec(b, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS,
+                          tun->metadata.opts.gnv,
+                          flow->metadata.present.len);
+    }
+}
+
+/* Converts 'mask_src' (in non-UDPIF format) to a series of masked options in
+ * 'dst'. 'flow_src' (also in non-UDPIF format) and the  original set of
+ * options 'flow_src_opt'/'opts_len' are needed as a guide to interpret the
+ * mask data. */
+void
+tun_metadata_to_geneve_udpif_mask(const struct flow_tnl *flow_src,
+                                  const struct flow_tnl *mask_src,
+                                  const struct geneve_opt *flow_src_opt,
+                                  int opts_len, struct geneve_opt *dst)
+{
+    ovs_assert(!(flow_src->flags & FLOW_TNL_F_UDPIF));
+
+    memcpy(dst, flow_src_opt, opts_len);
+    tun_metadata_to_geneve_mask__(&flow_src->metadata,
+                                  &mask_src->metadata, dst, opts_len);
+}
+
 static const struct tun_metadata_loc *
 metadata_loc_from_match_read(struct tun_table *map, const struct match *match,
-                             unsigned int idx)
+                             unsigned int idx, struct flow_tnl *mask,
+                             bool *is_masked)
 {
+    union mf_value mask_opts;
+
     if (match->tun_md.valid) {
-        return &match->tun_md.loc[idx];
+        *is_masked = match->tun_md.entry[idx].masked;
+        return &match->tun_md.entry[idx].loc;
     }
 
+    memcpy_from_metadata(mask_opts.tun_metadata, &mask->metadata,
+                         &map->entries[idx].loc);
+
+    *is_masked = map->entries[idx].loc.len == 0 ||
+                 !is_all_ones(mask_opts.tun_metadata,
+                              map->entries[idx].loc.len);
     return &map->entries[idx].loc;
 }
 
@@ -777,46 +1000,65 @@ void
 tun_metadata_to_nx_match(struct ofpbuf *b, enum ofp_version oxm,
                          const struct match *match)
 {
-    struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
-    const struct tun_metadata *metadata = &match->flow.tunnel.metadata;
-    const struct tun_metadata *mask = &match->wc.masks.tunnel.metadata;
+    struct flow_tnl flow, mask;
     int i;
 
-    ULLONG_FOR_EACH_1 (i, mask->opt_map) {
+    if (!udpif_to_parsed(&match->flow.tunnel, &match->wc.masks.tunnel,
+                         &flow, &mask)) {
+        return;
+    }
+
+    ULLONG_FOR_EACH_1 (i, mask.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(map, match, i);
-        memcpy_from_metadata(opts.tun_metadata, metadata, loc);
-        memcpy_from_metadata(mask_opts.tun_metadata, mask, loc);
-        nxm_put(b, MFF_TUN_METADATA0 + i, oxm, opts.tun_metadata,
-                mask_opts.tun_metadata, loc->len);
+        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);
+        nxm_put__(b, MFF_TUN_METADATA0 + i, oxm, opts.tun_metadata,
+                  is_masked ? mask_opts.tun_metadata : NULL, loc->len);
     }
 }
 
 void
 tun_metadata_match_format(struct ds *s, const struct match *match)
 {
-    struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
-    const struct tun_metadata *metadata = &match->flow.tunnel.metadata;
-    const struct tun_metadata *mask = &match->wc.masks.tunnel.metadata;
+    struct flow_tnl flow, mask;
     unsigned int i;
 
-    ULLONG_FOR_EACH_1 (i, mask->opt_map) {
+    if (!udpif_to_parsed(&match->flow.tunnel, &match->wc.masks.tunnel,
+                         &flow, &mask)) {
+        return;
+    }
+
+    ULLONG_FOR_EACH_1 (i, mask.metadata.present.map) {
         const struct tun_metadata_loc *loc;
-        union mf_value opts;
+        bool is_masked;
+        union mf_value opts, mask_opts;
 
-        loc = metadata_loc_from_match_read(map, match, i);
+        loc = metadata_loc_from_match_read(flow.metadata.tab, match, i,
+                                           &mask, &is_masked);
 
-        ds_put_format(s, "tun_metadata%u=", i);
-        memcpy_from_metadata(opts.tun_metadata, metadata, loc);
-        ds_put_hex(s, opts.tun_metadata, loc->len);
+        ds_put_format(s, "tun_metadata%u", i);
+        memcpy_from_metadata(mask_opts.tun_metadata, &mask.metadata, loc);
 
-        memcpy_from_metadata(opts.tun_metadata, mask, loc);
-        if (!is_all_ones(opts.tun_metadata, loc->len)) {
-            ds_put_char(s, '/');
+        if (!ULLONG_GET(flow.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);
             ds_put_hex(s, opts.tun_metadata, loc->len);
+
+            if (!is_all_ones(mask_opts.tun_metadata, loc->len)) {
+                ds_put_char(s, '/');
+                ds_put_hex(s, mask_opts.tun_metadata, loc->len);
+            }
         }
         ds_put_char(s, ',');
     }