2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 01:51:26 +00:00

ovn-trace: New --ovs option to also print OpenFlow flows.

Sometimes seeing the OpenFlow flows that back a given logical flow can
provide additional insight.  This commit adds a new --ovs option to
ovn-trace that makes it connect to Open vSwitch over OpenFlow and retrieve
and print the OpenFlow flows behind each logical flow encountered during
a trace.

Signed-off-by: Ben Pfaff <blp@ovn.org>
Acked-by: Justin Pettit <jpettit@ovn.org>
This commit is contained in:
Ben Pfaff 2016-12-28 09:31:42 -08:00
parent c80eac1f85
commit d444a914fd
6 changed files with 305 additions and 129 deletions

4
NEWS
View File

@ -7,7 +7,9 @@ Post-v2.6.0
* DSCP marking is now supported, via the new northbound QoS table.
* IPAM now supports fixed MAC addresses.
* Support for source IP address based routing.
* ovn-trace can now trace put_dhcp_opts and put_dhcp_optsv6 actions.
* ovn-trace:
- New --ovs option to also print OpenFlow flows.
- put_dhcp_opts and put_dhcp_optsv6 actions may now be traced.
* Support for managing SSL and remote connection configuration in
northbound and southbound databases.
- Fixed regression in table stats maintenance introduced in OVS

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
* Copyright (c) 2008-2016 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,9 +18,10 @@
#define OPENVSWITCH_VCONN_H 1
#include <stdbool.h>
#include <openvswitch/list.h>
#include <openvswitch/types.h>
#include <openflow/openflow.h>
#include "openvswitch/list.h"
#include "openvswitch/types.h"
#include "openvswitch/ofp-util.h"
#include "openflow/openflow.h"
#ifdef __cplusplus
extern "C" {
@ -56,6 +57,10 @@ int vconn_transact_noreply(struct vconn *, struct ofpbuf *, struct ofpbuf **);
int vconn_transact_multiple_noreply(struct vconn *, struct ovs_list *requests,
struct ofpbuf **replyp);
int vconn_dump_flows(struct vconn *, const struct ofputil_flow_stats_request *,
enum ofputil_protocol,
struct ofputil_flow_stats **fsesp, size_t *n_fsesp);
/* Bundle errors must be free()d by the caller. */
struct vconn_bundle_error {
struct ovs_list list_node;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
* Copyright (c) 2008-2016 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -943,6 +943,124 @@ vconn_transact_multiple_noreply(struct vconn *vconn, struct ovs_list *requests,
return 0;
}
static int
recv_flow_stats_reply(struct vconn *vconn, ovs_be32 send_xid,
struct ofpbuf **replyp,
struct ofputil_flow_stats *fs, struct ofpbuf *ofpacts)
{
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
struct ofpbuf *reply = *replyp;
for (;;) {
int retval;
bool more;
/* Get a flow stats reply message, if we don't already have one. */
if (!reply) {
enum ofptype type;
enum ofperr error;
do {
error = vconn_recv_block(vconn, &reply);
if (error) {
return error;
}
} while (((struct ofp_header *) reply->data)->xid != send_xid);
error = ofptype_decode(&type, reply->data);
if (error || type != OFPTYPE_FLOW_STATS_REPLY) {
VLOG_WARN_RL(&rl, "received bad reply: %s",
ofp_to_string(reply->data, reply->size, 1));
return EPROTO;
}
}
/* Pull an individual flow stats reply out of the message. */
retval = ofputil_decode_flow_stats_reply(fs, reply, false, ofpacts);
switch (retval) {
case 0:
*replyp = reply;
return 0;
case EOF:
more = ofpmp_more(reply->header);
ofpbuf_delete(reply);
reply = NULL;
if (!more) {
*replyp = NULL;
return EOF;
}
break;
default:
VLOG_WARN_RL(&rl, "parse error in reply (%s)",
ofperr_to_string(retval));
return EPROTO;
}
}
}
/* Sends 'fsr' to 'vconn', encoding it with the given 'protocol', and then
* waits for, parses, and accumulates all of the replies into '*fsesp' and
* '*n_fsesp'. The caller is responsible for freeing all of the flows.
* Returns 0 if successful, otherwise a positive errno value. */
int
vconn_dump_flows(struct vconn *vconn,
const struct ofputil_flow_stats_request *fsr,
enum ofputil_protocol protocol,
struct ofputil_flow_stats **fsesp, size_t *n_fsesp)
{
struct ofputil_flow_stats *fses = NULL;
size_t n_fses = 0;
size_t allocated_fses = 0;
struct ofpbuf *request = ofputil_encode_flow_stats_request(fsr, protocol);
const struct ofp_header *oh = request->data;
ovs_be32 send_xid = oh->xid;
int error = vconn_send_block(vconn, request);
if (error) {
goto exit;
}
struct ofpbuf *reply = NULL;
struct ofpbuf ofpacts;
ofpbuf_init(&ofpacts, 0);
for (;;) {
if (n_fses >= allocated_fses) {
fses = x2nrealloc(fses, &allocated_fses, sizeof *fses);
}
struct ofputil_flow_stats *fs = &fses[n_fses];
error = recv_flow_stats_reply(vconn, send_xid, &reply, fs, &ofpacts);
if (error) {
if (error == EOF) {
error = 0;
}
break;
}
fs->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
n_fses++;
}
ofpbuf_uninit(&ofpacts);
ofpbuf_delete(reply);
if (error) {
for (size_t i = 0; i < n_fses; i++) {
free(CONST_CAST(struct ofpact *, fses[i].ofpacts));
}
free(fses);
fses = NULL;
n_fses = 0;
}
exit:
*fsesp = fses;
*n_fsesp = n_fses;
return error;
}
static enum ofperr
vconn_bundle_reply_validate(struct ofpbuf *reply,
struct ofputil_bundle_ctrl_msg *request,

View File

@ -255,6 +255,58 @@
<dd>
Selects all three forms of output.
</dd>
<dt><code>--ovs</code>[<code>=</code><var>remote</var>]</dt>
<dd>
<p>
Makes <code>ovn-trace</code> attempt to obtain and display the OpenFlow
flows that correspond to each OVN logical flow. To do so,
<code>ovn-trace</code> connects to <var>remote</var> (by default,
<code>unix:@RUNDIR@/br-int.mgmt</code>) over OpenFlow and retrieves the
flows. If <var>remote</var> is specified, it must be an active
OpenFlow connection method described in <code>ovs-ofctl</code>(8).
</p>
<p>
To make the best use of the output, it is important to understand the
relationship between logical flows and OpenFlow flows.
<code>ovn-architecture</code>(7), under <em>Architectural Physical Life
Cycle of a Packet</em>, describes this relationship. Keep in mind the
following points:
</p>
<ul>
<li>
<code>ovn-trace</code> currently shows all the OpenFlow flows to
which a logical flow corresponds, even though an actual packet
ordinarily matches only one of these.
</li>
<li>
Some logical flows can map to the Open vSwitch ``conjunctive match''
extension (see <code>ovs-ofctl</code>(8)). Currently
<code>ovn-trace</code> cannot display the flows with
<code>conjunction</code> actions that effectively produce the
<code>conj_id</code> match.
</li>
<li>
Some logical flows may not be represented in the OpenFlow tables on a
given hypervisor, if they could not be used on that hypervisor.
</li>
<li>
Some OpenFlow flows do not correspond to logical flows, such as
OpenFlow flows that map between physical and logical ports. These
flows will never show up in a trace.
</li>
<li>
When <code>ovn-trace</code> omits uninteresting logical flows from
output, it does not look up the corresponding OpenFlow flows.
</li>
</ul>
</dd>
</dl>
<h2>Daemon Options</h2>
@ -266,7 +318,7 @@
<h2>PKI Options</h2>
<p>
PKI configuration is required to use SSL for the connection to the
database.
database (and the switch, if <code>--ovs</code> is specified).
</p>
<xi:include href="lib/ssl.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>

View File

@ -27,6 +27,8 @@
#include "nx-match.h"
#include "openvswitch/dynamic-string.h"
#include "openvswitch/ofp-actions.h"
#include "openvswitch/ofp-print.h"
#include "openvswitch/vconn.h"
#include "openvswitch/vlog.h"
#include "ovn/actions.h"
#include "ovn/expr.h"
@ -63,6 +65,10 @@ static bool summary;
/* --minimal: Show a trace with only minimal information. */
static bool minimal;
/* --ovs: OVS instance to contact to get OpenFlow flows. */
static const char *ovs;
static struct vconn *vconn;
OVS_NO_RETURN static void usage(void);
static void parse_options(int argc, char *argv[]);
static char *trace(const char *datapath, const char *flow);
@ -143,6 +149,12 @@ main(int argc, char *argv[])
}
}
static char *
default_ovs(void)
{
return xasprintf("unix:%s/br-int.mgmt", ovs_rundir());
}
static void
parse_options(int argc, char *argv[])
{
@ -153,6 +165,7 @@ parse_options(int argc, char *argv[])
OPT_SUMMARY,
OPT_MINIMAL,
OPT_ALL,
OPT_OVS,
DAEMON_OPTION_ENUMS,
SSL_OPTION_ENUMS,
VLOG_OPTION_ENUMS
@ -164,6 +177,7 @@ parse_options(int argc, char *argv[])
{"summary", no_argument, NULL, OPT_SUMMARY},
{"minimal", no_argument, NULL, OPT_MINIMAL},
{"all", no_argument, NULL, OPT_ALL},
{"ovs", optional_argument, NULL, OPT_OVS},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'},
DAEMON_LONG_OPTIONS,
@ -207,6 +221,10 @@ parse_options(int argc, char *argv[])
detailed = summary = minimal = true;
break;
case OPT_OVS:
ovs = optarg ? optarg : default_ovs();
break;
case 'h':
usage();
@ -245,7 +263,7 @@ usage(void)
usage: %s [OPTIONS] DATAPATH MICROFLOW\n\
%s [OPTIONS] --detach\n\
\n\
Option format options:\n\
Output format options:\n\
--detailed table-by-table \"backtrace\" (default)\n\
--summary less detailed, more parseable\n\
--minimal minimum to explain externally visible behavior\n\
@ -257,11 +275,14 @@ Option format options:\n\
Other options:\n\
--db=DATABASE connect to DATABASE\n\
(default: %s)\n\
--ovs[=REMOTE] obtain corresponding OpenFlow flows from REMOTE\n\
(default: %s)\n\
--unixctl=SOCKET set control socket name\n\
-h, --help display this help message\n\
-V, --version display version information\n",
default_sb_db());
default_sb_db(), default_ovs());
stream_usage("database", true, true, false);
vconn_usage(true, false, false);
exit(EXIT_SUCCESS);
}
@ -303,6 +324,7 @@ struct ovntrace_mcgroup {
enum ovntrace_pipeline { P_INGRESS, P_EGRESS };
struct ovntrace_flow {
struct uuid uuid;
enum ovntrace_pipeline pipeline;
int table_id;
char *stage_name;
@ -644,6 +666,7 @@ read_flows(void)
}
struct ovntrace_flow *flow = xzalloc(sizeof *flow);
flow->uuid = sblf->header_.uuid;
flow->pipeline = (!strcmp(sblf->pipeline, "ingress")
? P_INGRESS
: P_EGRESS);
@ -1393,6 +1416,54 @@ may_omit_stage(const struct ovntrace_flow *f, uint8_t table_id)
&& ovnact_get_NEXT(f->ovnacts)->ltable == table_id + 1);
}
static void
trace_openflow(const struct ovntrace_flow *f, struct ovs_list *super)
{
struct ofputil_flow_stats_request fsr = {
.cookie = htonll(f->uuid.parts[0]),
.cookie_mask = OVS_BE64_MAX,
.out_port = OFPP_ANY,
.out_group = OFPG_ANY,
.table_id = OFPTT_ALL,
};
struct ofputil_flow_stats *fses;
size_t n_fses;
int error = vconn_dump_flows(vconn, &fsr, OFPUTIL_P_OF13_OXM,
&fses, &n_fses);
if (error) {
ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
"*** error obtaining flow stats (%s)",
ovs_strerror(error));
VLOG_WARN("%s: error obtaining flow stats (%s)",
ovs, ovs_strerror(error));
return;
}
if (n_fses) {
struct ds s = DS_EMPTY_INITIALIZER;
for (size_t i = 0; i < n_fses; i++) {
ds_clear(&s);
ofp_print_flow_stats(&s, &fses[i]);
/* ofp_print_flow_stats() indents its output with a space.
* Omit it. */
const char *p = ds_cstr(&s);
p += strspn(p, " ");
ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "%s", p);
}
ds_destroy(&s);
} else {
ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
"*** no OpenFlow flows");
}
for (size_t i = 0; i < n_fses; i++) {
free(CONST_CAST(struct ofpact *, fses[i].ofpacts));
}
free(fses);
}
static void
trace__(const struct ovntrace_datapath *dp, struct flow *uflow,
uint8_t table_id, enum ovntrace_pipeline pipeline,
@ -1417,7 +1488,8 @@ trace__(const struct ovntrace_datapath *dp, struct flow *uflow,
} else if (f->source) {
ds_put_format(&s, "(%s): ", f->source);
}
ds_put_format(&s, "%s, priority %d", f->match_s, f->priority);
ds_put_format(&s, "%s, priority %d, uuid %08x",
f->match_s, f->priority, f->uuid.parts[0]);
} else {
char *stage_name = ovntrace_stage_name(dp, table_id, pipeline);
ds_put_format(&s, "%s%sno match (implicit drop)",
@ -1430,6 +1502,9 @@ trace__(const struct ovntrace_datapath *dp, struct flow *uflow,
ds_destroy(&s);
if (f) {
if (vconn) {
trace_openflow(f, &node->subs);
}
trace_actions(f->ovnacts, f->ovnacts_len, dp, uflow, table_id,
pipeline, &node->subs);
}
@ -1465,6 +1540,13 @@ trace(const char *dp_s, const char *flow_s)
flow_format(&output, &uflow);
ds_put_char(&output, '\n');
if (ovs) {
int retval = vconn_open_block(ovs, 1 << OFP13_VERSION, 0, &vconn);
if (retval) {
VLOG_WARN("%s: connection failed (%s)", ovs, ovs_strerror(retval));
}
}
struct ovs_list root = OVS_LIST_INITIALIZER(&root);
struct ovntrace_node *node = ovntrace_node_append(
&root, OVNTRACE_NODE_PIPELINE, "ingress(dp=\"%s\", inport=\"%s\")",
@ -1496,6 +1578,9 @@ trace(const char *dp_s, const char *flow_s)
ovntrace_node_prune_hard(&root);
ovntrace_node_print_summary(&output, &root, 0);
}
vconn_close(vconn);
return ds_steal_cstr(&output);
}

View File

@ -129,10 +129,6 @@ static const struct ovs_cmdl_command *get_all_commands(void);
OVS_NO_RETURN static void usage(void);
static void parse_options(int argc, char *argv[]);
static bool recv_flow_stats_reply(struct vconn *, ovs_be32 send_xid,
struct ofpbuf **replyp,
struct ofputil_flow_stats *,
struct ofpbuf *ofpacts);
int
main(int argc, char *argv[])
{
@ -1166,14 +1162,14 @@ set_protocol_for_flow_dump(struct vconn *vconn,
static struct vconn *
prepare_dump_flows(int argc, char *argv[], bool aggregate,
struct ofpbuf **requestp)
struct ofputil_flow_stats_request *fsr,
enum ofputil_protocol *protocolp)
{
enum ofputil_protocol usable_protocols, protocol;
struct ofputil_flow_stats_request fsr;
struct vconn *vconn;
char *error;
error = parse_ofp_flow_stats_request_str(&fsr, aggregate,
error = parse_ofp_flow_stats_request_str(fsr, aggregate,
argc > 2 ? argv[2] : "",
&usable_protocols);
if (error) {
@ -1181,19 +1177,19 @@ prepare_dump_flows(int argc, char *argv[], bool aggregate,
}
protocol = open_vconn(argv[1], &vconn);
protocol = set_protocol_for_flow_dump(vconn, protocol, usable_protocols);
*requestp = ofputil_encode_flow_stats_request(&fsr, protocol);
*protocolp = set_protocol_for_flow_dump(vconn, protocol, usable_protocols);
return vconn;
}
static void
ofctl_dump_flows__(int argc, char *argv[], bool aggregate)
{
struct ofpbuf *request;
struct ofputil_flow_stats_request fsr;
enum ofputil_protocol protocol;
struct vconn *vconn;
vconn = prepare_dump_flows(argc, argv, aggregate, &request);
dump_transaction(vconn, request);
vconn = prepare_dump_flows(argc, argv, aggregate, &fsr, &protocol);
dump_transaction(vconn, ofputil_encode_flow_stats_request(&fsr, protocol));
vconn_close(vconn);
}
@ -1270,52 +1266,29 @@ ofctl_dump_flows(struct ovs_cmdl_context *ctx)
ofctl_dump_flows__(ctx->argc, ctx->argv, false);
return;
} else {
struct ofputil_flow_stats *fses;
size_t n_fses, allocated_fses;
struct ofpbuf *request;
struct ofpbuf ofpacts;
struct ofpbuf *reply;
struct ofputil_flow_stats_request fsr;
enum ofputil_protocol protocol;
struct vconn *vconn;
ovs_be32 send_xid;
struct ds s;
size_t i;
vconn = prepare_dump_flows(ctx->argc, ctx->argv, false, &request);
send_xid = ((struct ofp_header *) request->data)->xid;
send_openflow_buffer(vconn, request);
vconn = prepare_dump_flows(ctx->argc, ctx->argv, false,
&fsr, &protocol);
fses = NULL;
n_fses = allocated_fses = 0;
reply = NULL;
ofpbuf_init(&ofpacts, 0);
for (;;) {
struct ofputil_flow_stats *fs;
if (n_fses >= allocated_fses) {
fses = x2nrealloc(fses, &allocated_fses, sizeof *fses);
}
fs = &fses[n_fses];
if (!recv_flow_stats_reply(vconn, send_xid, &reply, fs,
&ofpacts)) {
break;
}
fs->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
n_fses++;
}
ofpbuf_uninit(&ofpacts);
struct ofputil_flow_stats *fses;
size_t n_fses;
run(vconn_dump_flows(vconn, &fsr, protocol, &fses, &n_fses),
"dump flows");
qsort(fses, n_fses, sizeof *fses, compare_flows);
ds_init(&s);
for (i = 0; i < n_fses; i++) {
struct ds s = DS_EMPTY_INITIALIZER;
for (size_t i = 0; i < n_fses; i++) {
ds_clear(&s);
ofp_print_flow_stats(&s, &fses[i]);
puts(ds_cstr(&s));
}
ds_destroy(&s);
for (i = 0; i < n_fses; i++) {
for (size_t i = 0; i < n_fses; i++) {
free(CONST_CAST(struct ofpact *, fses[i].ofpacts));
}
free(fses);
@ -3365,59 +3338,6 @@ read_flows_from_file(const char *filename, struct fte_state *state, int index)
return usable_protocols;
}
static bool
recv_flow_stats_reply(struct vconn *vconn, ovs_be32 send_xid,
struct ofpbuf **replyp,
struct ofputil_flow_stats *fs, struct ofpbuf *ofpacts)
{
struct ofpbuf *reply = *replyp;
for (;;) {
int retval;
bool more;
/* Get a flow stats reply message, if we don't already have one. */
if (!reply) {
enum ofptype type;
enum ofperr error;
do {
run(vconn_recv_block(vconn, &reply),
"OpenFlow packet receive failed");
} while (((struct ofp_header *) reply->data)->xid != send_xid);
error = ofptype_decode(&type, reply->data);
if (error || type != OFPTYPE_FLOW_STATS_REPLY) {
ovs_fatal(0, "received bad reply: %s",
ofp_to_string(reply->data, reply->size,
verbosity + 1));
}
}
/* Pull an individual flow stats reply out of the message. */
retval = ofputil_decode_flow_stats_reply(fs, reply, false, ofpacts);
switch (retval) {
case 0:
*replyp = reply;
return true;
case EOF:
more = ofpmp_more(reply->header);
ofpbuf_delete(reply);
reply = NULL;
if (!more) {
*replyp = NULL;
return false;
}
break;
default:
ovs_fatal(0, "parse error in reply (%s)",
ofperr_to_string(retval));
}
}
}
/* Reads the OpenFlow flow table from 'vconn', which has currently active flow
* format 'protocol', and adds them as flow table entries in 'tables' for the
* version with the specified 'index'. */
@ -3427,11 +3347,6 @@ read_flows_from_switch(struct vconn *vconn,
struct fte_state *state, int index)
{
struct ofputil_flow_stats_request fsr;
struct ofputil_flow_stats fs;
struct ofpbuf *request;
struct ofpbuf ofpacts;
struct ofpbuf *reply;
ovs_be32 send_xid;
fsr.aggregate = false;
match_init_catchall(&fsr.match);
@ -3439,28 +3354,27 @@ read_flows_from_switch(struct vconn *vconn,
fsr.out_group = OFPG_ANY;
fsr.table_id = 0xff;
fsr.cookie = fsr.cookie_mask = htonll(0);
request = ofputil_encode_flow_stats_request(&fsr, protocol);
send_xid = ((struct ofp_header *) request->data)->xid;
send_openflow_buffer(vconn, request);
reply = NULL;
ofpbuf_init(&ofpacts, 0);
while (recv_flow_stats_reply(vconn, send_xid, &reply, &fs, &ofpacts)) {
struct ofputil_flow_stats *fses;
size_t n_fses;
run(vconn_dump_flows(vconn, &fsr, protocol, &fses, &n_fses),
"dump flows");
for (size_t i = 0; i < n_fses; i++) {
const struct ofputil_flow_stats *fs = &fses[i];
struct fte_version *version;
version = xmalloc(sizeof *version);
version->cookie = fs.cookie;
version->idle_timeout = fs.idle_timeout;
version->hard_timeout = fs.hard_timeout;
version->importance = fs.importance;
version->cookie = fs->cookie;
version->idle_timeout = fs->idle_timeout;
version->hard_timeout = fs->hard_timeout;
version->importance = fs->importance;
version->flags = 0;
version->ofpacts_len = fs.ofpacts_len;
version->ofpacts = xmemdup(fs.ofpacts, fs.ofpacts_len);
version->table_id = fs.table_id;
version->ofpacts_len = fs->ofpacts_len;
version->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
version->table_id = fs->table_id;
fte_queue(state, &fs.match, fs.priority, version, index);
fte_queue(state, &fs->match, fs->priority, version, index);
}
ofpbuf_uninit(&ofpacts);
}
static void