summaryrefslogtreecommitdiff
path: root/ofproto
diff options
context:
space:
mode:
authorJesse Gross <jesse@kernel.org>2016-04-19 18:36:04 -0700
committerJesse Gross <jesse@kernel.org>2016-09-19 09:52:22 -0700
commit8d8ab6c2d5743eb229a5e7c27ccd963a2c103adb (patch)
treed198a78ddf968ab119e029ca998eba2337812cd8 /ofproto
parent56fb20c4a1622c0f19bd2865c6688bdc78d5b40f (diff)
downloadopenvswitch-8d8ab6c2d5743eb229a5e7c27ccd963a2c103adb.tar.gz
tun-metadata: Manage tunnel TLV mapping table on a per-bridge basis.
When using tunnel TLVs (at the moment, this means Geneve options), a controller must first map the class and type onto an appropriate OXM field so that it can be used in OVS flow operations. This table is managed using OpenFlow extensions. The original code that added support for TLVs made the mapping table global as a simplification. However, this is not really logically correct as the OpenFlow management commands are operating on a per-bridge basis. This removes the original limitation to make the table per-bridge. One nice result of this change is that it is generally clearer whether the tunnel metadata is in datapath or OpenFlow format. Rather than allowing ad-hoc format changes and trying to handle both formats in the tunnel metadata functions, the format is more clearly separated by function. Datapaths (both kernel and userspace) use datapath format and it is not changed during the upcall process. At the beginning of action translation, tunnel metadata is converted to OpenFlow format and flows and wildcards are translated back at the end of the process. As an additional benefit, this change improves performance in some flow setup situations by keeping the tunnel metadata in the original packet format in more cases. This helps when copies need to be made as the amount of data touched is only what is present in the packet rather than the maximum amount of metadata supported. Co-authored-by: Madhu Challa <challa@noironetworks.com> Signed-off-by: Madhu Challa <challa@noironetworks.com> Signed-off-by: Jesse Gross <jesse@kernel.org> Acked-by: Ben Pfaff <blp@ovn.org>
Diffstat (limited to 'ofproto')
-rw-r--r--ofproto/ofproto-dpif-sflow.c2
-rw-r--r--ofproto/ofproto-dpif-upcall.c9
-rw-r--r--ofproto/ofproto-dpif-xlate.c75
-rw-r--r--ofproto/ofproto-dpif-xlate.h5
-rw-r--r--ofproto/ofproto-dpif.c60
-rw-r--r--ofproto/ofproto-dpif.h1
-rw-r--r--ofproto/ofproto-provider.h10
-rw-r--r--ofproto/ofproto.c39
8 files changed, 168 insertions, 33 deletions
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index c3234ee4a..11d3a53d2 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 eecea5373..d73dc1292 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -1289,8 +1289,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,
@@ -1309,8 +1308,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.
@@ -1932,8 +1930,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 74a4b5b7c..f17cb79ec 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -399,6 +399,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";
}
@@ -2901,6 +2903,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->xin->tables_version;
@@ -2914,6 +2917,8 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
flow->in_port.ofp_port = peer->ofp_port;
flow->metadata = htonll(0);
memset(&flow->tunnel, 0, sizeof flow->tunnel);
+ flow->tunnel.metadata.tab = ofproto_dpif_get_tun_tab(peer->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;
@@ -2982,6 +2987,15 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
/* Restore calling bridge's lookup version. */
ctx->xin->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;
@@ -5047,6 +5061,7 @@ xlate_in_init(struct xlate_in *xin, struct ofproto_dpif *ofproto,
xin->ofproto = ofproto;
xin->tables_version = version;
xin->flow = *flow;
+ xin->upcall_flow = flow;
xin->flow.in_port.ofp_port = in_port;
xin->flow.actset_output = OFPP_UNSET;
xin->packet = packet;
@@ -5453,6 +5468,27 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
goto exit;
}
+ /* Tunnel metadata in udpif format must be normalized before translation. */
+ if (flow->tunnel.flags & FLOW_TNL_F_UDPIF) {
+ const struct tun_table *tun_tab = ofproto_dpif_get_tun_tab(xin->ofproto);
+ int err;
+
+ err = tun_metadata_from_geneve_udpif(tun_tab, &xin->upcall_flow->tunnel,
+ &xin->upcall_flow->tunnel,
+ &flow->tunnel);
+ if (err) {
+ XLATE_REPORT_ERROR(&ctx, "Invalid Geneve tunnel metadata");
+ ctx.error = XLATE_INVALID_TUNNEL_METADATA;
+ goto exit;
+ }
+ } else if (!flow->tunnel.metadata.tab) {
+ /* If the original flow did not come in on a tunnel, then it won't have
+ * FLOW_TNL_F_UDPIF set. However, we still need to have a metadata
+ * table in case we generate tunnel actions. */
+ flow->tunnel.metadata.tab = ofproto_dpif_get_tun_tab(xin->ofproto);
+ }
+ ctx.wc->masks.tunnel.metadata.tab = flow->tunnel.metadata.tab;
+
if (!xin->ofpacts && !ctx.rule) {
ctx.rule = rule_dpif_lookup_from_table(
ctx.xbridge->ofproto, ctx.xin->tables_version, flow, ctx.wc,
@@ -5606,9 +5642,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 1768740ed..24d938256 100644
--- a/ofproto/ofproto-dpif-xlate.h
+++ b/ofproto/ofproto-dpif-xlate.h
@@ -52,6 +52,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;
@@ -197,6 +201,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 0cffca1a4..295bf097b 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -357,6 +357,12 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
/* Initial mappings of port to bridge mappings. */
static struct shash init_ofp_ports = SHASH_INITIALIZER(&init_ofp_ports);
+const struct tun_table *
+ofproto_dpif_get_tun_tab(const struct ofproto_dpif *ofproto)
+{
+ return ofproto_get_tun_tab(&ofproto->up);
+}
+
/* Initialize 'ofm' for a learn action. If the rule already existed, reference
* to that rule is taken, otherwise a new rule is created. 'ofm' keeps the
* rule reference in both cases. */
@@ -4922,7 +4928,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;
@@ -4939,7 +4946,7 @@ trace_format_rule(struct ds *result, int level, const struct rule_dpif *rule)
ds_put_format(result, "Rule: table=%"PRIu8" cookie=%#"PRIx64" ",
rule ? rule->up.table_id : 0, ntohll(cookie));
- cls_rule_format(&rule->up.cr, result);
+ cls_rule_format(&rule->up.cr, ofproto_get_tun_tab(ofproto), result);
ds_put_char(result, '\n');
actions = rule_dpif_get_actions(rule);
@@ -5041,7 +5048,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
@@ -5158,24 +5165,47 @@ parse_flow_and_packet(int argc, const char *argv[],
error = "Invalid datapath flow";
goto exit;
}
+
+ flow->tunnel.metadata.tab = ofproto_dpif_get_tun_tab(*ofprotop);
+
+ /* Convert Geneve options to OpenFlow format now. This isn't actually
+ * required in order to get the right results since the ofproto xlate
+ * actions will handle this for us. However, converting now ensures
+ * that our formatting code will always be able to consistently print
+ * in OpenFlow format, which is what we use here. */
+ if (flow->tunnel.flags & FLOW_TNL_F_UDPIF) {
+ struct flow_tnl tnl;
+ int err;
+
+ memcpy(&tnl, &flow->tunnel, sizeof tnl);
+ err = tun_metadata_from_geneve_udpif(flow->tunnel.metadata.tab,
+ &tnl, &tnl, &flow->tunnel);
+ if (err) {
+ error = "Failed to parse Geneve options";
+ goto exit;
+ }
+ }
} else {
- char *err = parse_ofp_exact_flow(flow, NULL, argv[argc - 1], NULL);
+ char *err;
+ if (argc != 3) {
+ error = "Must specify bridge name";
+ goto exit;
+ }
+
+ *ofprotop = ofproto_dpif_lookup(argv[1]);
+ if (!*ofprotop) {
+ error = "Unknown bridge name";
+ goto exit;
+ }
+
+ err = parse_ofp_exact_flow(flow, NULL,
+ ofproto_dpif_get_tun_tab(*ofprotop),
+ argv[argc - 1], NULL);
if (err) {
m_err = xasprintf("Bad openflow flow syntax: %s", err);
free(err);
goto exit;
- } else {
- if (argc != 3) {
- error = "Must specify bridge name";
- goto exit;
- }
-
- *ofprotop = ofproto_dpif_lookup(argv[1]);
- if (!*ofprotop) {
- error = "Unknown bridge name";
- goto exit;
- }
}
}
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index 67e1a99ac..ba8ea9cf2 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -184,6 +184,7 @@ int ofproto_dpif_delete_internal_flow(struct ofproto_dpif *, struct match *,
int priority);
const struct uuid *ofproto_dpif_get_uuid(const struct ofproto_dpif *);
+const struct tun_table *ofproto_dpif_get_tun_tab(const struct ofproto_dpif *);
/* struct rule_dpif has struct rule as it's first member. */
#define RULE_CAST(RULE) ((struct rule *)RULE)
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index be5632eb7..6a523b46b 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;
@@ -122,6 +123,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);
@@ -1963,4 +1967,10 @@ rule_from_cls_rule(const struct cls_rule *cls_rule)
return cls_rule ? CONTAINER_OF(cls_rule, struct rule, cr) : NULL;
}
+static inline const struct tun_table *
+ofproto_get_tun_tab(const struct ofproto *ofproto)
+{
+ return ovsrcu_get(struct tun_table *, &ofproto->metadata_tab);
+}
+
#endif /* ofproto/ofproto-provider.h */
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index f89f0ef04..26848deeb 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -529,7 +529,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) {
@@ -1549,6 +1549,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);
@@ -3535,7 +3537,9 @@ handle_nxt_resume(struct ofconn *ofconn, const struct ofp_header *oh)
struct ofputil_packet_in_private pin;
enum ofperr error;
- error = ofputil_decode_packet_in_private(oh, false, &pin, NULL, NULL);
+ error = ofputil_decode_packet_in_private(oh, false,
+ ofproto_get_tun_tab(ofproto), &pin,
+ NULL, NULL);
if (error) {
return error;
}
@@ -4238,7 +4242,8 @@ handle_flow_stats_request(struct ofconn *ofconn,
struct ovs_list replies;
enum ofperr error;
- error = ofputil_decode_flow_stats_request(&fsr, request);
+ error = ofputil_decode_flow_stats_request(&fsr, request,
+ ofproto_get_tun_tab(ofproto));
if (error) {
return error;
}
@@ -4292,7 +4297,8 @@ handle_flow_stats_request(struct ofconn *ofconn,
fs.ofpacts_len = actions->ofpacts_len;
fs.flags = flags;
- ofputil_append_flow_stats_reply(&fs, &replies);
+ ofputil_append_flow_stats_reply(&fs, &replies,
+ ofproto_get_tun_tab(ofproto));
}
rule_collection_unref(&rules);
@@ -4304,7 +4310,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;
@@ -4324,7 +4330,7 @@ flow_stats_ds(struct rule *rule, struct ds *results)
ds_put_format(results, "duration=%llds, ", (time_msec() - created) / 1000);
ds_put_format(results, "n_packets=%"PRIu64", ", packet_count);
ds_put_format(results, "n_bytes=%"PRIu64", ", byte_count);
- cls_rule_format(&rule->cr, results);
+ cls_rule_format(&rule->cr, ofproto_get_tun_tab(ofproto), results);
ds_put_char(results, ',');
ds_put_cstr(results, "actions=");
@@ -4344,7 +4350,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);
}
}
}
@@ -4401,7 +4407,8 @@ handle_aggregate_stats_request(struct ofconn *ofconn,
struct ofpbuf *reply;
enum ofperr error;
- error = ofputil_decode_flow_stats_request(&request, oh);
+ error = ofputil_decode_flow_stats_request(&request, oh,
+ ofproto_get_tun_tab(ofproto));
if (error) {
return error;
}
@@ -5680,7 +5687,7 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_protocol(ofconn),
- &ofpacts,
+ ofproto_get_tun_tab(ofproto), &ofpacts,
u16_to_ofp(ofproto->max_ports),
ofproto->n_tables);
if (!error) {
@@ -7680,6 +7687,7 @@ handle_bundle_add(struct ofconn *ofconn, const struct ofp_header *oh)
error = ofputil_decode_flow_mod(&fm, badd.msg,
ofconn_get_protocol(ofconn),
+ ofproto_get_tun_tab(ofproto),
&ofpacts,
u16_to_ofp(ofproto->max_ports),
ofproto->n_tables);
@@ -7720,6 +7728,8 @@ handle_bundle_add(struct ofconn *ofconn, const struct ofp_header *oh)
static enum ofperr
handle_tlv_table_mod(struct ofconn *ofconn, const struct ofp_header *oh)
{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct tun_table *old_tab, *new_tab;
struct ofputil_tlv_table_mod ttm;
enum ofperr error;
@@ -7733,7 +7743,12 @@ handle_tlv_table_mod(struct ofconn *ofconn, const struct ofp_header *oh)
return error;
}
- error = tun_metadata_table_mod(&ttm);
+ old_tab = ovsrcu_get_protected(struct tun_table *, &ofproto->metadata_tab);
+ error = tun_metadata_table_mod(&ttm, old_tab, &new_tab);
+ if (!error) {
+ ovsrcu_set(&ofproto->metadata_tab, new_tab);
+ tun_metadata_postpone_free(old_tab);
+ }
ofputil_uninit_tlv_table(&ttm.mappings);
return error;
@@ -7742,10 +7757,12 @@ handle_tlv_table_mod(struct ofconn *ofconn, const struct ofp_header *oh)
static enum ofperr
handle_tlv_table_request(struct ofconn *ofconn, const struct ofp_header *oh)
{
+ const struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
struct ofputil_tlv_table_reply ttr;
struct ofpbuf *b;
- tun_metadata_table_request(&ttr);
+ tun_metadata_table_request(ofproto_get_tun_tab(ofproto), &ttr);
+
b = ofputil_encode_tlv_table_reply(oh, &ttr);
ofputil_uninit_tlv_table(&ttr.mappings);