[ovs-dev] [trace 3/3] ofproto: Add "ofproto/trace" command to help debugging flow tables.

Ben Pfaff blp at nicira.com
Wed Dec 8 17:10:54 PST 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