+/*
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include "bitmap.h"
+#include "compiler.h"
+#include "hmap.h"
+#include "match.h"
+#include "nx-match.h"
+#include "odp-netlink.h"
+#include "ofp-util.h"
+#include "ovs-thread.h"
+#include "ovs-rcu.h"
+#include "packets.h"
+#include "tun-metadata.h"
+
+struct tun_meta_entry {
+ struct hmap_node node; /* In struct tun_table's key_hmap. */
+ uint32_t key; /* (class << 16) | type. */
+ struct tun_metadata_loc loc;
+ bool valid; /* True if allocated to a class and type. */
+};
+
+/* Maps from Geneve option class+type to positions in a struct tun_metadata's
+ * 'opts' array. */
+struct tun_table {
+ /* TUN_METADATA<i> is stored in element <i>. */
+ struct tun_meta_entry entries[TUN_METADATA_NUM_OPTS];
+
+ /* Each bit represents 4 bytes of space, 0-bits are free space. */
+ unsigned long alloc_map[BITMAP_N_LONGS(TUN_METADATA_TOT_OPT_SIZE / 4)];
+
+ /* The valid elements in entries[], indexed by class+type. */
+ struct hmap key_hmap;
+};
+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);
+static void memcpy_to_metadata(struct tun_metadata *dst, const void *src,
+ const struct tun_metadata_loc *);
+static void memcpy_from_metadata(void *dst, const struct tun_metadata *src,
+ const struct tun_metadata_loc *);
+
+static uint32_t
+tun_meta_key(ovs_be16 class, uint8_t type)
+{
+ return (OVS_FORCE uint16_t)class << 8 | type;
+}
+
+static ovs_be16
+tun_key_class(uint32_t key)
+{
+ return (OVS_FORCE ovs_be16)(key >> 8);
+}
+
+static uint8_t
+tun_key_type(uint32_t key)
+{
+ return key & 0xff;
+}
+
+/* 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 *new_map;
+
+ new_map = xzalloc(sizeof *new_map);
+
+ if (old_map) {
+ struct tun_meta_entry *entry;
+
+ *new_map = *old_map;
+ hmap_init(&new_map->key_hmap);
+
+ HMAP_FOR_EACH (entry, node, &old_map->key_hmap) {
+ struct tun_meta_entry *new_entry;
+ struct tun_metadata_loc_chain *chain;
+
+ new_entry = &new_map->entries[entry - old_map->entries];
+ hmap_insert(&new_map->key_hmap, &new_entry->node, entry->node.hash);
+
+ chain = &new_entry->loc.c;
+ while (chain->next) {
+ chain->next = xmemdup(chain->next, sizeof *chain->next);
+ chain = chain->next;
+ }
+ }
+ } else {
+ hmap_init(&new_map->key_hmap);
+ }
+
+ return new_map;
+}
+
+/* Frees 'map' and all the memory it owns. */
+static void
+table_free(struct tun_table *map) OVS_REQUIRES(tab_mutex)
+{
+ struct tun_meta_entry *entry;
+
+ if (!map) {
+ return;
+ }
+
+ HMAP_FOR_EACH (entry, node, &map->key_hmap) {
+ tun_metadata_del_entry(map, entry - map->entries);
+ }
+
+ 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_geneve_table_mod *gtm)
+{
+ struct tun_table *old_map, *new_map;
+ struct ofputil_geneve_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:
+ new_map = table_alloc(old_map);
+
+ LIST_FOR_EACH (ofp_map, list_node, >m->mappings) {
+ err = tun_metadata_add_entry(new_map, ofp_map->index,
+ ofp_map->option_class,
+ ofp_map->option_type,
+ ofp_map->option_len);
+ if (err) {
+ table_free(new_map);
+ goto out;
+ }
+ }
+ break;
+
+ case NXGTMC_DELETE:
+ new_map = table_alloc(old_map);
+
+ LIST_FOR_EACH (ofp_map, list_node, >m->mappings) {
+ tun_metadata_del_entry(new_map, ofp_map->index);
+ }
+ break;
+
+ case NXGTMC_CLEAR:
+ new_map = table_alloc(NULL);
+ break;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+
+ ovsrcu_set(&metadata_tab, new_map);
+ ovsrcu_postpone(table_free, old_map);
+
+out:
+ ovs_mutex_unlock(&tab_mutex);
+ return err;
+}
+
+void
+tun_metadata_table_request(struct ofputil_geneve_table_reply *gtr)
+{
+ 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(>r->mappings);
+
+ for (i = 0; i < TUN_METADATA_NUM_OPTS; i++) {
+ struct tun_meta_entry *entry = &map->entries[i];
+ struct ofputil_geneve_map *map;
+
+ if (!entry->valid) {
+ continue;
+ }
+
+ map = xmalloc(sizeof *map);
+ map->option_class = ntohs(tun_key_class(entry->key));
+ map->option_type = tun_key_type(entry->key);
+ map->option_len = entry->loc.len;
+ map->index = i;
+
+ list_push_back(>r->mappings, &map->list_node);
+ }
+}
+
+/* Copies the value of field 'mf' from 'metadata' into 'value'.
+ *
+ * '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'. */
+void
+tun_metadata_read(const struct tun_metadata *metadata,
+ const struct mf_field *mf, union mf_value *value)
+{
+ struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
+ unsigned int idx = mf->id - MFF_TUN_METADATA0;
+ struct tun_metadata_loc *loc;
+
+ if (!map) {
+ memset(value->tun_metadata, 0, mf->n_bytes);
+ return;
+ }
+
+ loc = &map->entries[idx].loc;
+
+ memset(value->tun_metadata, 0, mf->n_bytes - loc->len);
+ memcpy_from_metadata(value->tun_metadata + mf->n_bytes - loc->len,
+ metadata, loc);
+}
+
+/* Copies 'value' into field 'mf' in 'metadata'.
+ *
+ * '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. */
+void
+tun_metadata_write(struct tun_metadata *metadata,
+ const struct mf_field *mf, const union mf_value *value)
+{
+ struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
+ unsigned int idx = mf->id - MFF_TUN_METADATA0;
+ struct tun_metadata_loc *loc;
+
+ if (!map || !map->entries[idx].valid) {
+ return;
+ }
+
+ loc = &map->entries[idx].loc;
+
+ ULLONG_SET1(metadata->opt_map, idx);
+ memcpy_to_metadata(metadata, value->tun_metadata + mf->n_bytes - loc->len,
+ loc);
+}
+
+static const struct tun_metadata_loc *
+metadata_loc_from_match(struct tun_table *map, struct match *match,
+ unsigned int idx, unsigned int field_len)
+{
+ ovs_assert(idx < TUN_METADATA_NUM_OPTS);
+
+ if (map) {
+ if (map->entries[idx].valid) {
+ return &map->entries[idx].loc;
+ } else {
+ return NULL;
+ }
+ }
+
+ if (match->tun_md.alloc_offset + field_len >= TUN_METADATA_TOT_OPT_SIZE ||
+ match->tun_md.loc[idx].len) {
+ 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.alloc_offset += field_len;
+ match->tun_md.valid = true;
+
+ return &match->tun_md.loc[idx];
+}
+
+/* Makes 'match' match 'value'/'mask' on field 'mf'.
+ *
+ * 'mf' must be an MFF_TUN_METADATA* field.
+ *
+ * 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'.
+ *
+ * 'value' may be NULL; if so, then 'mf' is made to match on an all-zeros
+ * value.
+ *
+ * 'mask' may be NULL; if so, then 'mf' is made exact-match.
+ */
+void
+tun_metadata_set_match(const struct mf_field *mf, const union mf_value *value,
+ const union mf_value *mask, struct match *match)
+{
+ 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;
+ 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);
+ if (!loc) {
+ return;
+ }
+
+ data_offset = mf->n_bytes - loc->len;
+
+ if (!value) {
+ memset(data.tun_metadata, 0, loc->len);
+ } else if (!mask) {
+ memcpy(data.tun_metadata, value->tun_metadata + data_offset, loc->len);
+ } else {
+ int i;
+ for (i = 0; i < loc->len; i++) {
+ data.tun_metadata[i] = value->tun_metadata[data_offset + i] &
+ 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);
+
+ if (!value) {
+ memset(data.tun_metadata, 0, loc->len);
+ } else if (!mask) {
+ memset(data.tun_metadata, 0xff, loc->len);
+ } 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);
+}
+
+/* Copies all MFF_TUN_METADATA* fields from 'metadata' to 'flow_metadata'. */
+void
+tun_metadata_get_fmd(const struct tun_metadata *metadata,
+ struct match *flow_metadata)
+{
+ struct tun_table *map;
+ int i;
+
+ map = metadata->tab;
+ if (!map) {
+ map = ovsrcu_get(struct tun_table *, &metadata_tab);
+ }
+
+ ULLONG_FOR_EACH_1 (i, metadata->opt_map) {
+ union mf_value opts;
+ const struct tun_metadata_loc *old_loc = &map->entries[i].loc;
+ const struct tun_metadata_loc *new_loc;
+
+ new_loc = metadata_loc_from_match(NULL, flow_metadata, i, old_loc->len);
+
+ memcpy_from_metadata(opts.tun_metadata, metadata, old_loc);
+ memcpy_to_metadata(&flow_metadata->flow.tunnel.metadata,
+ opts.tun_metadata, new_loc);
+
+ memset(opts.tun_metadata, 0xff, old_loc->len);
+ memcpy_to_metadata(&flow_metadata->wc.masks.tunnel.metadata,
+ opts.tun_metadata, new_loc);
+ }
+}
+
+static uint32_t
+tun_meta_hash(uint32_t key)
+{
+ return hash_int(key, 0);
+}
+
+static struct tun_meta_entry *
+tun_meta_find_key(const struct hmap *hmap, uint32_t key)
+{
+ struct tun_meta_entry *entry;
+
+ HMAP_FOR_EACH_IN_BUCKET (entry, node, tun_meta_hash(key), hmap) {
+ if (entry->key == key) {
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+static void
+memcpy_to_metadata(struct tun_metadata *dst, const void *src,
+ const struct tun_metadata_loc *loc)
+{
+ 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,
+ chain->len);
+ addr += chain->len;
+ chain = chain->next;
+ }
+}
+
+static void
+memcpy_from_metadata(void *dst, const struct tun_metadata *src,
+ const struct tun_metadata_loc *loc)
+{
+ const struct tun_metadata_loc_chain *chain = &loc->c;
+ int addr = 0;
+
+ while (chain) {
+ memcpy((uint8_t *)dst + addr, src->opts + loc->c.offset + addr,
+ chain->len);
+ addr += chain->len;
+ chain = chain->next;
+ }
+}
+
+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;
+ int scan_end = TUN_METADATA_TOT_OPT_SIZE / 4;
+ int pos_start, pos_end, pos_len;
+ int best_start = 0, best_len = 0;
+
+ while (true) {
+ pos_start = bitmap_scan(map->alloc_map, 0, scan_start, scan_end);
+ if (pos_start == scan_end) {
+ break;
+ }
+
+ pos_end = bitmap_scan(map->alloc_map, 1, pos_start,
+ MIN(pos_start + alloc_len, scan_end));
+ pos_len = pos_end - pos_start;
+ if (pos_len == alloc_len) {
+ goto found;
+ }
+
+ if (pos_len > best_len) {
+ best_start = pos_start;
+ best_len = pos_len;
+ }
+ scan_start = pos_end + 1;
+ }
+
+ if (best_len == 0) {
+ return ENOSPC;
+ }
+
+ pos_start = best_start;
+ pos_len = best_len;
+
+found:
+ bitmap_set_multiple(map->alloc_map, pos_start, pos_len, 1);
+ loc->offset = pos_start * 4;
+ loc->len = pos_len * 4;
+
+ return 0;
+}
+
+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)
+{
+ struct tun_meta_entry *entry;
+ struct tun_metadata_loc_chain *cur_chain, *prev_chain;
+
+ ovs_assert(idx < TUN_METADATA_NUM_OPTS);
+
+ entry = &map->entries[idx];
+ if (entry->valid) {
+ return OFPERR_NXGTMFC_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;
+ }
+
+ entry->valid = true;
+ hmap_insert(&map->key_hmap, &entry->node,
+ tun_meta_hash(entry->key));
+
+ entry->loc.len = len;
+ cur_chain = &entry->loc.c;
+ memset(cur_chain, 0, sizeof *cur_chain);
+ prev_chain = NULL;
+
+ while (len) {
+ int err;
+
+ if (!cur_chain) {
+ cur_chain = xzalloc(sizeof *cur_chain);
+ }
+
+ err = tun_metadata_alloc_chain(map, len, cur_chain);
+ if (err) {
+ tun_metadata_del_entry(map, idx);
+ return OFPERR_NXGTMFC_TABLE_FULL;
+ }
+
+ len -= cur_chain->len;
+
+ if (prev_chain) {
+ prev_chain->next = cur_chain;
+ }
+ prev_chain = cur_chain;
+ cur_chain = NULL;
+ }
+
+ return 0;
+}
+
+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;
+
+ if (idx >= TUN_METADATA_NUM_OPTS) {
+ return;
+ }
+
+ entry = &map->entries[idx];
+ if (!entry->valid) {
+ return;
+ }
+
+ chain = &entry->loc.c;
+ while (chain) {
+ struct tun_metadata_loc_chain *next = chain->next;
+
+ bitmap_set_multiple(map->alloc_map, chain->offset / 4,
+ chain->len / 4, 0);
+ if (chain != &entry->loc.c) {
+ free(chain);
+ }
+ chain = next;
+ }
+
+ entry->valid = false;
+ hmap_remove(&map->key_hmap, &entry->node);
+ 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)
+{
+ 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);
+
+ if (!is_mask) {
+ map = ovsrcu_get(struct tun_table *, &metadata_tab);
+ metadata->tab = map;
+ } else {
+ map = flow_metadata->tab;
+ }
+
+ if (!map) {
+ 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;
+
+ if (opts_len < sizeof(*opt)) {
+ return EINVAL;
+ }
+
+ len = sizeof(*opt) + flow_opt->length * 4;
+ if (len > opts_len) {
+ return EINVAL;
+ }
+
+ entry = tun_meta_find_key(&map->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);
+ ULLONG_SET1(metadata->opt_map, entry - map->entries);
+ } else {
+ return EINVAL;
+ }
+ } else if (flow_opt->type & GENEVE_CRIT_OPT_TYPE) {
+ return EINVAL;
+ }
+
+ opt = opt + len / sizeof(*opt);
+ flow_opt = flow_opt + len / sizeof(*opt);
+ opts_len -= len;
+ }
+
+ return 0;
+}
+
+void
+tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow,
+ struct ofpbuf *b)
+{
+ struct tun_table *map;
+ size_t nlattr_offset;
+ int i;
+
+ if (!flow->opt_map) {
+ return;
+ }
+
+ 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);
+
+ ULLONG_FOR_EACH_1 (i, flow->opt_map) {
+ struct tun_meta_entry *entry = &map->entries[i];
+ struct geneve_opt *opt;
+
+ opt = ofpbuf_put_uninit(b, sizeof *opt + entry->loc.len);
+
+ opt->opt_class = tun_key_class(entry->key);
+ opt->type = tun_key_type(entry->key);
+ opt->length = entry->loc.len / 4;
+ opt->r1 = 0;
+ opt->r2 = 0;
+ opt->r3 = 0;
+
+ memcpy_from_metadata(opt + 1, flow, &entry->loc);
+ }
+
+ 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,
+ 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;
+
+ if (!map) {
+ return;
+ }
+
+ tnl_key = nl_attr_find(key, 0, 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;
+ }
+
+ 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;
+
+ entry = tun_meta_find_key(&map->key_hmap,
+ tun_meta_key(opt->opt_class, opt->type));
+ if (entry) {
+ memcpy_from_metadata(opt + 1, mask, &entry->loc);
+ } else {
+ memset(opt + 1, 0, opt->length * 4);
+ }
+
+ opt->opt_class = htons(0xffff);
+ opt->type = 0xff;
+ opt->length = 0x1f;
+ opt->r1 = 0;
+ opt->r2 = 0;
+ opt->r3 = 0;
+
+ opt = opt + len / sizeof(*opt);
+ opts_len -= len;
+ }
+}
+
+static const struct tun_metadata_loc *
+metadata_loc_from_match_read(struct tun_table *map, const struct match *match,
+ unsigned int idx)
+{
+ if (match->tun_md.valid) {
+ return &match->tun_md.loc[idx];
+ }
+
+ return &map->entries[idx].loc;
+}
+
+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;
+ int i;
+
+ ULLONG_FOR_EACH_1 (i, mask->opt_map) {
+ const struct tun_metadata_loc *loc;
+ 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);
+ }
+}
+
+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;
+ unsigned int i;
+
+ ULLONG_FOR_EACH_1 (i, mask->opt_map) {
+ const struct tun_metadata_loc *loc;
+ union mf_value opts;
+
+ loc = metadata_loc_from_match_read(map, match, i);
+
+ 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);
+
+ memcpy_from_metadata(opts.tun_metadata, mask, loc);
+ if (!is_all_ones(opts.tun_metadata, loc->len)) {
+ ds_put_char(s, '/');
+ ds_put_hex(s, opts.tun_metadata, loc->len);
+ }
+ ds_put_char(s, ',');
+ }
+}