ofproto: Add "ofproto/trace" command to help debugging flow tables.
authorBen Pfaff <blp@nicira.com>
Thu, 9 Dec 2010 23:00:36 +0000 (15:00 -0800)
committerBen Pfaff <blp@nicira.com>
Thu, 9 Dec 2010 23:00:36 +0000 (15:00 -0800)
With an appropriate flow table, output from a command like this:

ovs-appctl ofproto/trace system@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
ofproto/automake.mk
ofproto/ofproto-unixctl.man [new file with mode: 0644]
ofproto/ofproto.c
utilities/automake.mk
utilities/ovs-openflowd.8.in
utilities/ovs-pcap.1.in [new file with mode: 0644]
utilities/ovs-pcap.in [new file with mode: 0755]
utilities/ovs-tcpundump.1.in [new file with mode: 0644]
utilities/ovs-tcpundump.in [new file with mode: 0755]
vswitchd/ovs-vswitchd.8.in

index 7b988da19119973834bafaee6f4d41c081a6d28e..08ce576437eba396baeca07e79f050aaccb79f34 100644 (file)
@@ -3,4 +3,6 @@ _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/utilities/ovs-tcpundump usr/bin
 _debian/vswitchd/ovs-vswitchd usr/sbin
index 0c99b4907acd29c7ded4be5816664b40bbda5b7d..66307455462a5738370f89566b6073dbdb60466b 100644 (file)
@@ -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 (file)
index 0000000..72f076d
--- /dev/null
@@ -0,0 +1,26 @@
+.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) and \fBovs\-tcpundump\fR(1)
+utilities provide easier ways.
+.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.
index cffdf657f2188cc218d7f3118b06fc2e41af981d..00b573110e99ed71091d36911f87cff3e718d820 100644 (file)
@@ -116,6 +116,12 @@ struct action_xlate_ctx {
      * revalidating without a packet to refer to. */
     const struct ofpbuf *packet;
 
+    /* If nonnull, called just before executing a resubmit action.
+     *
+     * This is normally null so the client has to set it manually after
+     * calling action_xlate_ctx_init(). */
+    void (*resubmit_hook)(struct action_xlate_ctx *, const struct rule *);
+
 /* xlate_actions() initializes and uses these members.  The client might want
  * to look at them after it returns. */
 
@@ -392,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;
@@ -410,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,
@@ -422,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) {
@@ -503,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;
 }
@@ -1024,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;
@@ -2659,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);
@@ -3015,6 +3036,7 @@ action_xlate_ctx_init(struct action_xlate_ctx *ctx,
     ctx->ofproto = ofproto;
     ctx->flow = *flow;
     ctx->packet = packet;
+    ctx->resubmit_hook = NULL;
 }
 
 static int
@@ -4890,6 +4912,172 @@ pick_fallback_dpid(void)
     return eth_addr_to_uint64(ea);
 }
 \f
+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); /* Get entire rest of line. */
+    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);
+}
+\f
 static bool
 default_normal_ofhook_cb(const struct flow *flow, const struct ofpbuf *packet,
                          struct odp_actions *actions, tag_type *tags,
index cbe6128bc76a3d734445510acdc205777a5236e5..9a334e3390539d84e9077c3d8057c85ecf6801b5 100644 (file)
@@ -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 utilities/ovs-tcpundump
+endif
 noinst_SCRIPTS += utilities/ovs-pki-cgi utilities/ovs-parse-leaks
 
 EXTRA_DIST += \
@@ -20,9 +23,13 @@ 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 \
+       utilities/ovs-tcpundump.1.in \
+       utilities/ovs-tcpundump.in \
        utilities/ovs-vsctl.8.in
 DISTCLEANFILES += \
        utilities/ovs-appctl.8 \
@@ -33,9 +40,13 @@ 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 \
+       utilities/ovs-tcpundump \
+       utilities/ovs-tcpundump.1 \
        utilities/ovs-vsctl.8
 
 man_MANS += \
@@ -47,7 +58,9 @@ 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-tcpundump.1 \
        utilities/ovs-vsctl.8
 
 utilities_ovs_appctl_SOURCES = utilities/ovs-appctl.c
index fcca9810b22156a8333474fd991c3cc935627214..b84f8e7b78fc779b63b83467cc10ef5b31bf8ab5 100644 (file)
@@ -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 (file)
index 0000000..470e251
--- /dev/null
@@ -0,0 +1,26 @@
+.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 ovs\-tcpundump (1),
+.BR tcpdump (8),
+.BR wireshark (8).
diff --git a/utilities/ovs-pcap.in b/utilities/ovs-pcap.in
new file mode 100755 (executable)
index 0000000..8ab4756
--- /dev/null
@@ -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.GetoptException, 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/utilities/ovs-tcpundump.1.in b/utilities/ovs-tcpundump.1.in
new file mode 100644 (file)
index 0000000..e59f6db
--- /dev/null
@@ -0,0 +1,30 @@
+.TH ovs\-tcpundump 1 "December 2010" "Open vSwitch" "Open vSwitch Manual"
+.
+.SH NAME
+ovs\-tcpundump \- convert ``tcpdump \-xx'' output to hex strings
+.
+.SH SYNOPSIS
+\fBovs\-tcpundump < \fIfile\fR
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovs\-tcpundump\fR program reads \fBtcpdump \-xx\fR output on
+stdin, looking for hexadecimal packet data, and dumps each Ethernet as
+a single hexadecimal string on stdout.  This format is suitable for
+use with the \fBofproto/trace\fR command supported by
+\fBovs\-vswitchd\fR(8) and \fBovs-openflowd\fR(8).
+.PP
+At least two \fB\-x\fR or \fB\-X\fR options must be given, otherwise
+the output will omit the Ethernet header, which prevents the output
+from being using with \fBofproto/trace\fR.
+.
+.SH "OPTIONS"
+.so lib/common.man
+.
+.SH "SEE ALSO"
+.
+.BR ovs\-vswitchd (8),
+.BR ovs\-openflowd (8),
+.BR ovs\-pcap (1),
+.BR tcpdump (8),
+.BR wireshark (8).
diff --git a/utilities/ovs-tcpundump.in b/utilities/ovs-tcpundump.in
new file mode 100755 (executable)
index 0000000..c018998
--- /dev/null
@@ -0,0 +1,75 @@
+#! @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 getopt
+import re
+import sys
+
+argv0 = sys.argv[0]
+
+def usage():
+    print """\
+%(argv0)s: print "tcpdump -xx" output as hex
+usage: %(argv0)s < FILE
+where FILE is output from "tcpdump -xx".
+
+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:
+        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) != 0:
+        sys.stderr.write("%s: non-option argument not supported "
+                         "(use --help for help)\n" % argv0)
+        sys.exit(1)
+
+    packet = ''
+    regex = re.compile(r'^\s+0x([0-9a-fA-F]+): ((?: [0-9a-fA-F]{4})+)')
+    while True:
+        line = sys.stdin.readline()
+        if line == "":
+            break
+
+        m = regex.match(line)
+        if m is None or int(m.group(1)) == 0:
+            if packet != '':
+                print packet
+            packet = ''
+        if m:
+            packet += re.sub(r'\s', '', m.group(2), 0)
+    if packet != '':
+        print packet
+
+# Local variables:
+# mode: python
+# End:
index 189f213824bbc2dedafe6f1676c2bf56987a325d..822c0f298eb52d89f4728bcdae55b6a0bc24a4e3 100644 (file)
@@ -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"