[ovs-dev] [trace 3/3] ofproto: Add "ofproto/trace" command to help debugging flow tables.
Ben Pfaff
blp at nicira.com
Thu Dec 9 01:10:54 UTC 2010
With an appropriate flow table, output from a command like this:
ovs-appctl ofproto/trace system at dp0 0 0 ffffffffffff000c29f49d5c080600010
80006040001000c29f49d5cac10008a000000000000ac1004df00000000000000000000000000000
0000000
resembles the following:
Packet: -8:00:00.000000 00:0c:29:f4:9d:5c > ff:ff:ff:ff:ff:ff, ethertype ARP (0x
0806), length 60: arp who-has 172.16.4.223 tell 172.16.0.138
Flow: tunnel0:in_port0000:tci(0) mac00:0c:29:f4:9d:5c->ff:ff:ff:ff:ff:ff type080
6 proto1 tos0 ip172.16.0.138->172.16.4.223 port0->0
Rule: cookie=0 in_port=65534
OpenFlow actions=resubmit:1,mod_vlan_vid:5,resubmit:2,mod_vlan_pcp:6,strip_vlan
Resubmitted flow: unchanged
Rule: cookie=0 in_port=1
OpenFlow actions=resubmit:3,resubmit:4
Resubmitted flow: unchanged
No match
Resubmitted flow: unchanged
No match
Resubmitted flow: tunnel0:in_port0000:tci(vlan5,pcp0) mac00:0c:29:f4:9d:
5c->ff:ff:ff:ff:ff:ff type0806 proto1 tos0 ip172.16.0.138->172.16.4.223 port0->0
No match
Final flow: tunnel0:in_port0000:tci(0) mac00:0c:29:f4:9d:5c->ff:ff:ff:ff:ff:ff t
ype0806 proto1 tos0 ip172.16.0.138->172.16.4.223 port0->0
Datapath actions: set_tci(vid=5,pcp=0),set_tci(vid=5,pcp=6),strip_vlan
---
debian/openvswitch-switch.install | 1 +
ofproto/automake.mk | 4 +-
ofproto/ofproto-unixctl.man | 25 +++++
ofproto/ofproto.c | 181 +++++++++++++++++++++++++++++++++++++
utilities/automake.mk | 8 ++
utilities/ovs-openflowd.8.in | 1 +
utilities/ovs-pcap.1.in | 25 +++++
utilities/ovs-pcap.in | 104 +++++++++++++++++++++
vswitchd/ovs-vswitchd.8.in | 1 +
9 files changed, 349 insertions(+), 1 deletions(-)
create mode 100644 ofproto/ofproto-unixctl.man
create mode 100644 utilities/ovs-pcap.1.in
create mode 100755 utilities/ovs-pcap.in
diff --git a/debian/openvswitch-switch.install b/debian/openvswitch-switch.install
index 7b988da..d358a28 100644
--- a/debian/openvswitch-switch.install
+++ b/debian/openvswitch-switch.install
@@ -3,4 +3,5 @@ _debian/utilities/ovs-discover usr/sbin
_debian/utilities/ovs-dpctl usr/sbin
_debian/utilities/ovs-kill usr/sbin
_debian/utilities/ovs-vsctl usr/sbin
+_debian/utilities/ovs-pcap usr/bin
_debian/vswitchd/ovs-vswitchd usr/sbin
diff --git a/ofproto/automake.mk b/ofproto/automake.mk
index 0c99b49..6630745 100644
--- a/ofproto/automake.mk
+++ b/ofproto/automake.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
#
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
@@ -27,3 +27,5 @@ ofproto_libofproto_a_SOURCES = \
ofproto/pinsched.h \
ofproto/status.c \
ofproto/status.h
+
+EXTRA_DIST += ofproto/ofproto-unixctl.man
diff --git a/ofproto/ofproto-unixctl.man b/ofproto/ofproto-unixctl.man
new file mode 100644
index 0000000..36f0b16
--- /dev/null
+++ b/ofproto/ofproto-unixctl.man
@@ -0,0 +1,25 @@
+.SS "OFPROTO COMMANDS"
+These commands manage the core OpenFlow switch implementation (called
+\fBofproto\fR).
+.IP "\fBofproto/list\fR"
+Lists the names of the running ofproto instances. These are the names
+that may be used on \fBofproto/trace\fR.
+.IP "\fBofproto/trace \fItun_id in_port packet\fR"
+Traces the path of an imaginary packet through ofproto. The arguments
+are:
+.RS
+.IP "\fItun_id\fR"
+The tunnel ID on which the packet arrived. Use
+\fB0\fR if the packet did not arrive through a tunnel.
+.IP "\fIin_port\fR"
+The OpenFlow port on which the packet arrived. Use \fB65534\fR if the
+packet arrived on \fBOFPP_LOCAL\fR, the local port.
+.IP "\fIpacket\fR"
+A sequence of hex digits specifying the packet's contents. An
+Ethernet frame is at least 14 bytes long, so there must be at least 28
+hex digits. Obviously, it is inconvenient to type in the hex digits
+by hand, so the \fBovs\-pcap\fR(1) utility provides an easier way.
+.RS
+\fB\*(PN\fR will respond with extensive information on how the packet
+would be handled if it were to be received. The packet will not
+actually be sent.
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 5098cea..fbb4435 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -398,6 +398,9 @@ struct ofproto {
struct mac_learning *ml;
};
+/* Map from dpif name to struct ofproto, for use by unixctl commands. */
+static struct shash all_ofprotos = SHASH_INITIALIZER(&all_ofprotos);
+
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
static const struct ofhooks default_ofhooks;
@@ -416,6 +419,8 @@ static void update_port(struct ofproto *, const char *devname);
static int init_ports(struct ofproto *);
static void reinit_ports(struct ofproto *);
+static void ofproto_unixctl_init(void);
+
int
ofproto_create(const char *datapath, const char *datapath_type,
const struct ofhooks *ofhooks, void *aux,
@@ -428,6 +433,8 @@ ofproto_create(const char *datapath, const char *datapath_type,
*ofprotop = NULL;
+ ofproto_unixctl_init();
+
/* Connect to datapath and start listening for messages. */
error = dpif_open(datapath, datapath_type, &dpif);
if (error) {
@@ -509,6 +516,8 @@ ofproto_create(const char *datapath, const char *datapath_type,
p->datapath_id = pick_datapath_id(p);
VLOG_INFO("using datapath ID %016"PRIx64, p->datapath_id);
+ shash_add_once(&all_ofprotos, dpif_name(p->dpif), p);
+
*ofprotop = p;
return 0;
}
@@ -1030,6 +1039,8 @@ ofproto_destroy(struct ofproto *p)
return;
}
+ shash_find_and_delete(&all_ofprotos, dpif_name(p->dpif));
+
/* Destroy fail-open and in-band early, since they touch the classifier. */
fail_open_destroy(p->fail_open);
p->fail_open = NULL;
@@ -2665,6 +2676,10 @@ xlate_table_action(struct action_xlate_ctx *ctx, uint16_t in_port)
rule = rule_lookup(ctx->ofproto, &ctx->flow);
ctx->flow.in_port = old_in_port;
+ if (ctx->resubmit_hook) {
+ ctx->resubmit_hook(ctx, rule);
+ }
+
if (rule) {
ctx->recurse++;
do_xlate_actions(rule->actions, rule->n_actions, ctx);
@@ -4905,6 +4920,172 @@ pick_fallback_dpid(void)
return eth_addr_to_uint64(ea);
}
+static void
+ofproto_unixctl_list(struct unixctl_conn *conn, const char *arg OVS_UNUSED,
+ void *aux OVS_UNUSED)
+{
+ const struct shash_node *node;
+ struct ds results;
+
+ ds_init(&results);
+ SHASH_FOR_EACH (node, &all_ofprotos) {
+ ds_put_format(&results, "%s\n", node->name);
+ }
+ unixctl_command_reply(conn, 200, ds_cstr(&results));
+ ds_destroy(&results);
+}
+
+struct ofproto_trace {
+ struct action_xlate_ctx ctx;
+ struct flow flow;
+ struct ds *result;
+};
+
+static void
+trace_format_rule(struct ds *result, int level, const struct rule *rule)
+{
+ ds_put_char_multiple(result, '\t', level);
+ if (!rule) {
+ ds_put_cstr(result, "No match\n");
+ return;
+ }
+
+ ds_put_format(result, "Rule: cookie=%#"PRIx64" ",
+ ntohll(rule->flow_cookie));
+ cls_rule_format(&rule->cr, result);
+ ds_put_char(result, '\n');
+
+ ds_put_char_multiple(result, '\t', level);
+ ds_put_cstr(result, "OpenFlow ");
+ ofp_print_actions(result, (const struct ofp_action_header *) rule->actions,
+ rule->n_actions * sizeof *rule->actions);
+ ds_put_char(result, '\n');
+}
+
+static void
+trace_format_flow(struct ds *result, int level, const char *title,
+ struct ofproto_trace *trace)
+{
+ ds_put_char_multiple(result, '\t', level);
+ ds_put_format(result, "%s: ", title);
+ if (flow_equal(&trace->ctx.flow, &trace->flow)) {
+ ds_put_cstr(result, "unchanged");
+ } else {
+ flow_format(result, &trace->ctx.flow);
+ trace->flow = trace->ctx.flow;
+ }
+ ds_put_char(result, '\n');
+}
+
+static void
+trace_resubmit(struct action_xlate_ctx *ctx, const struct rule *rule)
+{
+ struct ofproto_trace *trace = CONTAINER_OF(ctx, struct ofproto_trace, ctx);
+ struct ds *result = trace->result;
+
+ ds_put_char(result, '\n');
+ trace_format_flow(result, ctx->recurse + 1, "Resubmitted flow", trace);
+ trace_format_rule(result, ctx->recurse + 1, rule);
+}
+
+static void
+ofproto_unixctl_trace(struct unixctl_conn *conn, const char *args_,
+ void *aux OVS_UNUSED)
+{
+ char *dpname, *in_port_s, *tun_id_s, *packet_s;
+ char *args = xstrdup(args_);
+ char *save_ptr = NULL;
+ struct ofproto *ofproto;
+ struct ofpbuf packet;
+ struct rule *rule;
+ struct ds result;
+ struct flow flow;
+ uint16_t in_port;
+ ovs_be32 tun_id;
+ char *s;
+
+ ofpbuf_init(&packet, strlen(args) / 2);
+ ds_init(&result);
+
+ dpname = strtok_r(args, " ", &save_ptr);
+ tun_id_s = strtok_r(NULL, " ", &save_ptr);
+ in_port_s = strtok_r(NULL, " ", &save_ptr);
+ packet_s = strtok_r(NULL, "", &save_ptr);
+ if (!dpname || !in_port_s || !packet_s) {
+ unixctl_command_reply(conn, 501, "Bad command syntax");
+ goto exit;
+ }
+
+ ofproto = shash_find_data(&all_ofprotos, dpname);
+ if (!ofproto) {
+ unixctl_command_reply(conn, 501, "Unknown ofproto (use ofproto/list "
+ "for help)");
+ goto exit;
+ }
+
+ tun_id = ntohl(strtoul(tun_id_s, NULL, 10));
+ in_port = ofp_port_to_odp_port(atoi(in_port_s));
+
+ packet_s = ofpbuf_put_hex(&packet, packet_s, NULL);
+ packet_s += strspn(packet_s, " ");
+ if (*packet_s != '\0') {
+ unixctl_command_reply(conn, 501, "Trailing garbage in command");
+ goto exit;
+ }
+ if (packet.size < ETH_HEADER_LEN) {
+ unixctl_command_reply(conn, 501, "Packet data too short for Ethernet");
+ goto exit;
+ }
+
+ ds_put_cstr(&result, "Packet: ");
+ s = ofp_packet_to_string(packet.data, packet.size, packet.size);
+ ds_put_cstr(&result, s);
+ free(s);
+
+ flow_extract(&packet, tun_id, in_port, &flow);
+ ds_put_cstr(&result, "Flow: ");
+ flow_format(&result, &flow);
+ ds_put_char(&result, '\n');
+
+ rule = rule_lookup(ofproto, &flow);
+ trace_format_rule(&result, 0, rule);
+ if (rule) {
+ struct ofproto_trace trace;
+
+ trace.result = &result;
+ trace.flow = flow;
+ action_xlate_ctx_init(&trace.ctx, ofproto, &flow, &packet);
+ trace.ctx.resubmit_hook = trace_resubmit;
+ xlate_actions(&trace.ctx, rule->actions, rule->n_actions);
+
+ ds_put_char(&result, '\n');
+ trace_format_flow(&result, 0, "Final flow", &trace);
+ ds_put_cstr(&result, "Datapath actions: ");
+ format_odp_actions(&result,
+ trace.ctx.out.actions, trace.ctx.out.n_actions);
+ }
+
+ unixctl_command_reply(conn, 200, ds_cstr(&result));
+
+exit:
+ ds_destroy(&result);
+ ofpbuf_uninit(&packet);
+ free(args);
+}
+
+static void
+ofproto_unixctl_init(void)
+{
+ static bool registered;
+ if (registered) {
+ return;
+ }
+ registered = true;
+
+ unixctl_command_register("ofproto/list", ofproto_unixctl_list, NULL);
+ unixctl_command_register("ofproto/trace", ofproto_unixctl_trace, NULL);
+}
+
static bool
default_normal_ofhook_cb(const struct flow *flow, const struct ofpbuf *packet,
struct odp_actions *actions, tag_type *tags,
diff --git a/utilities/automake.mk b/utilities/automake.mk
index cbe6128..cea3fd7 100644
--- a/utilities/automake.mk
+++ b/utilities/automake.mk
@@ -8,6 +8,9 @@ bin_PROGRAMS += \
utilities/ovs-openflowd \
utilities/ovs-vsctl
bin_SCRIPTS += utilities/ovs-pki utilities/ovs-vsctl
+if HAVE_PYTHON
+bin_SCRIPTS += utilities/ovs-pcap
+endif
noinst_SCRIPTS += utilities/ovs-pki-cgi utilities/ovs-parse-leaks
EXTRA_DIST += \
@@ -20,6 +23,8 @@ EXTRA_DIST += \
utilities/ovs-openflowd.8.in \
utilities/ovs-parse-leaks.8 \
utilities/ovs-parse-leaks.in \
+ utilities/ovs-pcap.1.in \
+ utilities/ovs-pcap.in \
utilities/ovs-pki-cgi.in \
utilities/ovs-pki.8.in \
utilities/ovs-pki.in \
@@ -33,6 +38,8 @@ DISTCLEANFILES += \
utilities/ovs-ofctl.8 \
utilities/ovs-openflowd.8 \
utilities/ovs-parse-leaks \
+ utilities/ovs-pcap \
+ utilities/ovs-pcap.1 \
utilities/ovs-pki \
utilities/ovs-pki-cgi \
utilities/ovs-pki.8 \
@@ -47,6 +54,7 @@ man_MANS += \
utilities/ovs-ofctl.8 \
utilities/ovs-openflowd.8 \
utilities/ovs-parse-leaks.8 \
+ utilities/ovs-pcap.1 \
utilities/ovs-pki.8 \
utilities/ovs-vsctl.8
diff --git a/utilities/ovs-openflowd.8.in b/utilities/ovs-openflowd.8.in
index fcca981..b84f8e7 100644
--- a/utilities/ovs-openflowd.8.in
+++ b/utilities/ovs-openflowd.8.in
@@ -446,6 +446,7 @@ described below.
These commands are specific to \fBovs\-openflowd\fR.
.IP "\fBexit\fR"
Causes \fBovs\-openflowd\fR to gracefully terminate.
+.so ofproto/ofproto-unixctl.man
.so lib/vlog-unixctl.man
.
.SH "SEE ALSO"
diff --git a/utilities/ovs-pcap.1.in b/utilities/ovs-pcap.1.in
new file mode 100644
index 0000000..a345a2e
--- /dev/null
+++ b/utilities/ovs-pcap.1.in
@@ -0,0 +1,25 @@
+.TH ovs\-pcap 1 "December 2010" "Open vSwitch" "Open vSwitch Manual"
+.
+.SH NAME
+ovs\-pcap \- print packets from a pcap file as hex
+.
+.SH SYNOPSIS
+\fBovs\-pcap\fR \fIfile\fR
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovs\-pcap\fR program reads the pcap \fIfile\fR named on the
+command line and prints each packet's contents as a sequence of hex
+digits on a line of its own. This format is suitable for use with the
+\fBofproto/trace\fR command supported by \fBovs\-vswitchd\fR(8) and
+\fBovs-openflowd\fR(8).
+.
+.SH "OPTIONS"
+.so lib/common.man
+.
+.SH "SEE ALSO"
+.
+.BR ovs\-vswitchd (8),
+.BR ovs\-openflowd (8),
+.BR tcpdump (8),
+.BR wireshark (8).
diff --git a/utilities/ovs-pcap.in b/utilities/ovs-pcap.in
new file mode 100755
index 0000000..06cbf39
--- /dev/null
+++ b/utilities/ovs-pcap.in
@@ -0,0 +1,104 @@
+#! @PYTHON@
+#
+# Copyright (c) 2010 Nicira Networks.
+#
+# 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.
+
+import binascii
+import getopt
+import struct
+import sys
+
+class PcapException(Exception):
+ pass
+
+class PcapReader(object):
+ def __init__(self, file_name):
+ self.file = open(file_name, "rb")
+ header = self.file.read(24)
+ if len(header) != 24:
+ raise PcapException("end of file reading pcap header")
+ magic, version, thiszone, sigfigs, snaplen, network = \
+ struct.unpack(">6I", header)
+ if magic == 0xa1b2c3d4:
+ self.header_format = ">4I"
+ elif magic == 0xd4c3b2a1:
+ self.header_format = "<4I"
+ else:
+ raise PcapException("bad magic %u reading pcap file "
+ "(expected 0xa1b2c3d4 or 0xd4c3b2a1)" % magic)
+
+ def read(self):
+ header = self.file.read(16)
+ if len(header) == 0:
+ return None
+ elif len(header) != 16:
+ raise PcapException("end of file within pcap record header")
+
+ ts_sec, ts_usec, incl_len, orig_len = struct.unpack(self.header_format,
+ header)
+ packet = self.file.read(incl_len)
+ if len(packet) != incl_len:
+ raise PcapException("end of file reading pcap packet data")
+ return packet
+argv0 = sys.argv[0]
+
+def usage():
+ print """\
+%(argv0)s: print pcap file packet data as hex
+usage: %(argv0)s FILE
+where FILE is a PCAP file.
+
+The following options are also available:
+ -h, --help display this help message
+ -V, --version display version information\
+""" % {'argv0': argv0}
+ sys.exit(0)
+
+if __name__ == "__main__":
+ try:
+ try:
+ options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
+ ['help', 'version'])
+ except getopt.GetoptPcapException, geo:
+ sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
+ sys.exit(1)
+
+ for key, value in options:
+ if key in ['-h', '--help']:
+ usage()
+ elif key in ['-V', '--version']:
+ print "ovs-pcap (Open vSwitch) @VERSION@"
+ else:
+ sys.exit(0)
+
+ if len(args) != 1:
+ sys.stderr.write("%s: exactly 1 non-option argument required "
+ "(use --help for help)\n" % argv0)
+ sys.exit(1)
+
+ reader = PcapReader(args[0])
+ while True:
+ packet = reader.read()
+ if packet is None:
+ break
+
+ print binascii.hexlify(packet)
+
+ except PcapException, e:
+ sys.stderr.write("%s: %s\n" % (argv0, e))
+ sys.exit(1)
+
+# Local variables:
+# mode: python
+# End:
diff --git a/vswitchd/ovs-vswitchd.8.in b/vswitchd/ovs-vswitchd.8.in
index 189f213..822c0f2 100644
--- a/vswitchd/ovs-vswitchd.8.in
+++ b/vswitchd/ovs-vswitchd.8.in
@@ -183,6 +183,7 @@ status of \fIslave\fR changes.
Returns the hash value which would be used for \fImac\fR with \fIvlan\fR
if specified.
.
+.so ofproto/ofproto-unixctl.man
.so lib/vlog-unixctl.man
.so lib/stress-unixctl.man
.SH "SEE ALSO"
--
1.7.1
More information about the dev
mailing list