[ovs-dev] [tcpdump 4/5] ovs-ofctl: New command "ofp-parse-pcap" to dump OpenFlow from PCAP files.

Ben Pfaff blp at nicira.com
Fri Nov 22 21:37:39 UTC 2013


Based on the number of people who ask about Wireshark support for OpenFlow,
this is likely to be widely useful.

Signed-off-by: Ben Pfaff <blp at nicira.com>
---
 lib/pcap-file.c          |  138 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/pcap-file.h          |    9 +++
 utilities/ovs-ofctl.8.in |   18 +++++-
 utilities/ovs-ofctl.c    |   93 ++++++++++++++++++++++++++++++-
 4 files changed, 255 insertions(+), 3 deletions(-)

diff --git a/lib/pcap-file.c b/lib/pcap-file.c
index f13fe19..0b24f28 100644
--- a/lib/pcap-file.c
+++ b/lib/pcap-file.c
@@ -23,8 +23,12 @@
 #include <sys/stat.h>
 #include "byte-order.h"
 #include "compiler.h"
+#include "flow.h"
+#include "hmap.h"
 #include "ofpbuf.h"
+#include "packets.h"
 #include "timeval.h"
+#include "unaligned.h"
 #include "vlog.h"
 
 VLOG_DEFINE_THIS_MODULE(pcap);
@@ -198,3 +202,137 @@ pcap_write(FILE *file, struct ofpbuf *buf)
     ignore(fwrite(&prh, sizeof prh, 1, file));
     ignore(fwrite(buf->data, buf->size, 1, file));
 }
+
+struct tcp_key {
+    ovs_be32 nw_src, nw_dst;
+    ovs_be16 tp_src, tp_dst;
+};
+
+struct tcp_stream {
+    struct hmap_node hmap_node;
+    struct tcp_key key;
+    uint32_t seq_no;
+    struct ofpbuf payload;
+};
+
+struct tcp_reader {
+    struct hmap streams;
+};
+
+static void
+tcp_stream_destroy(struct tcp_reader *r, struct tcp_stream *stream)
+{
+    hmap_remove(&r->streams, &stream->hmap_node);
+    ofpbuf_uninit(&stream->payload);
+    free(stream);
+}
+
+/* Returns a new data structure for extracting TCP stream data from an
+ * Ethernet packet capture */
+struct tcp_reader *
+tcp_reader_open(void)
+{
+    struct tcp_reader *r;
+
+    r = xmalloc(sizeof *r);
+    hmap_init(&r->streams);
+    return r;
+}
+
+/* Closes and frees 'r'. */
+void
+tcp_reader_close(struct tcp_reader *r)
+{
+    struct tcp_stream *stream, *next_stream;
+
+    HMAP_FOR_EACH_SAFE (stream, next_stream, hmap_node, &r->streams) {
+        tcp_stream_destroy(r, stream);
+    }
+    hmap_destroy(&r->streams);
+    free(r);
+}
+
+static struct tcp_stream *
+tcp_stream_lookup(struct tcp_reader *r, const struct flow *flow)
+{
+    struct tcp_stream *stream;
+    struct tcp_key key;
+    uint32_t hash;
+
+    memset(&key, 0, sizeof key);
+    key.nw_src = flow->nw_src;
+    key.nw_dst = flow->nw_dst;
+    key.tp_src = flow->tp_src;
+    key.tp_dst = flow->tp_dst;
+    hash = hash_bytes(&key, sizeof key, 0);
+
+    HMAP_FOR_EACH_WITH_HASH (stream, hmap_node, hash, &r->streams) {
+        if (!memcmp(&stream->key, &key, sizeof key)) {
+            return stream;
+        }
+    }
+
+    stream = xmalloc(sizeof *stream);
+    hmap_insert(&r->streams, &stream->hmap_node, hash);
+    memcpy(&stream->key, &key, sizeof key);
+    stream->seq_no = 0;
+    ofpbuf_init(&stream->payload, 2048);
+    return stream;
+}
+
+/* Processes 'packet' through TCP reader 'r'.  The caller must have already
+ * extracted the packet's headers into 'flow', using flow_extract().
+ *
+ * If 'packet' is a TCP packet, then the reader attempts to reconstruct the
+ * data stream.  If successful, it returns an ofpbuf that represents the data
+ * stream so far.  The caller may examine the data in the ofpbuf and pull off
+ * any data that it has fully processed.  The remaining data that the caller
+ * does not pull off will be presented again in future calls if more data
+ * arrives in the stream.
+ *
+ * Returns null if 'packet' doesn't add new data to a TCP stream. */
+struct ofpbuf *
+tcp_reader_run(struct tcp_reader *r, const struct flow *flow,
+               const struct ofpbuf *packet)
+{
+    struct tcp_stream *stream;
+    struct tcp_header *tcp;
+    struct ofpbuf *payload;
+    uint32_t seq;
+    uint8_t flags;
+
+    if (flow->dl_type != htons(ETH_TYPE_IP)
+        || flow->nw_proto != IPPROTO_TCP
+        || !packet->l7) {
+        return NULL;
+    }
+
+    stream = tcp_stream_lookup(r, flow);
+    payload = &stream->payload;
+
+    tcp = packet->l4;
+    flags = TCP_FLAGS(tcp->tcp_ctl);
+    seq = ntohl(get_16aligned_be32(&tcp->tcp_seq));
+    if (flags & TCP_SYN) {
+        ofpbuf_clear(payload);
+        stream->seq_no = seq + 1;
+        return NULL;
+    } else if (flags & (TCP_FIN | TCP_RST)) {
+        tcp_stream_destroy(r, stream);
+        return NULL;
+    } else if (seq == stream->seq_no) {
+        size_t length;
+
+        /* Shift all of the existing payload to the very beginning of the
+         * allocated space, so that we reuse allocated space instead of
+         * continually expanding it. */
+        ofpbuf_shift(payload, (char *) payload->base - (char *) payload->data);
+
+        length = (char *) ofpbuf_end(packet) - (char *) packet->l7;
+        ofpbuf_put(payload, packet->l7, length);
+        stream->seq_no += length;
+        return payload;
+    } else {
+        return NULL;
+    }
+}
diff --git a/lib/pcap-file.h b/lib/pcap-file.h
index 7148b18..ef491e5 100644
--- a/lib/pcap-file.h
+++ b/lib/pcap-file.h
@@ -19,12 +19,21 @@
 
 #include <stdio.h>
 
+struct flow;
 struct ofpbuf;
 
+/* PCAP file reading and writing. */
 FILE *pcap_open(const char *file_name, const char *mode);
 int pcap_read_header(FILE *);
 void pcap_write_header(FILE *);
 int pcap_read(FILE *, struct ofpbuf **, long long int *when);
 void pcap_write(FILE *, struct ofpbuf *);
+
+/* Extracting TCP stream data from an Ethernet packet capture. */
+
+struct tcp_reader *tcp_reader_open(void);
+void tcp_reader_close(struct tcp_reader *);
+struct ofpbuf *tcp_reader_run(struct tcp_reader *, const struct flow *,
+                              const struct ofpbuf *);
 
 #endif /* pcap-file.h */
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index e5e488a..823ea7f 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -487,6 +487,21 @@ series of OpenFlow messages in the binary format used on an OpenFlow
 connection, and prints them to the console.  This can be useful for
 printing OpenFlow messages captured from a TCP stream.
 .
+.IP "\fBofp\-parse\-pcap\fR \fIfile\fR [\fIport\fR...]"
+Reads \fIfile\fR, which must be in the PCAP format used by network
+capture tools such as \fBtcpdump\fR or \fBwireshark\fR, extracts all
+the TCP streams for OpenFlow connections, and prints the OpenFlow
+messages in those connections in human-readable format on
+\fBstdout\fR.
+.IP
+OpenFlow connections are distinguished by TCP port number.
+Non-OpenFlow packets are ignored.  By default, data on TCP ports 6633
+and 6653 are considered to be OpenFlow.  Specify one or more
+\fIport\fR arguments to override the default.
+.IP
+This command cannot usefully print SSL encrypted traffic.  It does not
+understand IPv6.
+.
 .SS "Flow Syntax"
 .PP
 Some \fBovs\-ofctl\fR commands accept an argument that describes a flow or
@@ -2003,7 +2018,8 @@ affects the \fBmonitor\fR command.
 .
 .IP "\fB\-\-timestamp\fR"
 Print a timestamp before each received packet.  This option only
-affects the \fBmonitor\fR and \fBsnoop\fR commands.
+affects the \fBmonitor\fR, \fBsnoop\fR, and \fBofp\-parse\-pcap\fR
+commands.
 .
 .IP "\fB\-m\fR"
 .IQ "\fB\-\-more\fR"
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index d7c2a12..4500591 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -325,7 +325,8 @@ usage(void)
            "  benchmark TARGET N COUNT    bandwidth of COUNT N-byte echos\n"
            "SWITCH or TARGET is an active OpenFlow connection method.\n"
            "\nOther commands:\n"
-           "  ofp-parse FILE              print messages read from FILE\n",
+           "  ofp-parse FILE              print messages read from FILE\n"
+           "  ofp-parse-pcap PCAP         print OpenFlow read from PCAP\n",
            program_name, program_name);
     vconn_usage(true, false, false);
     daemon_usage();
@@ -1825,6 +1826,92 @@ ofctl_ofp_parse(int argc OVS_UNUSED, char *argv[])
     }
 }
 
+static bool
+is_openflow_port(ovs_be16 port_, char *ports[])
+{
+    uint16_t port = ntohs(port_);
+    if (ports[0]) {
+        int i;
+
+        for (i = 0; ports[i]; i++) {
+            if (port == atoi(ports[i])) {
+                return true;
+            }
+        }
+        return false;
+    } else {
+        return port == OFP_PORT || port == OFP_OLD_PORT;
+    }
+}
+
+static void
+ofctl_ofp_parse_pcap(int argc OVS_UNUSED, char *argv[])
+{
+    struct tcp_reader *reader;
+    FILE *file;
+    int error;
+    bool first;
+
+    file = pcap_open(argv[1], "rb");
+    if (!file) {
+        ovs_fatal(errno, "%s: open failed", argv[1]);
+    }
+
+    reader = tcp_reader_open();
+    first = true;
+    for (;;) {
+        struct ofpbuf *packet;
+        long long int when;
+        struct flow flow;
+
+        error = pcap_read(file, &packet, &when);
+        if (error) {
+            break;
+        }
+        flow_extract(packet, 0, 0, NULL, NULL, &flow);
+        if (flow.dl_type == htons(ETH_TYPE_IP)
+            && flow.nw_proto == IPPROTO_TCP
+            && (is_openflow_port(flow.tp_src, argv + 2) ||
+                is_openflow_port(flow.tp_dst, argv + 2))) {
+            struct ofpbuf *payload = tcp_reader_run(reader, &flow, packet);
+            if (payload) {
+                while (payload->size >= sizeof(struct ofp_header)) {
+                    const struct ofp_header *oh;
+                    int length;
+
+                    /* Align OpenFlow on 8-byte boundary for safe access. */
+                    ofpbuf_shift(payload, -((intptr_t) payload->data & 7));
+
+                    oh = payload->data;
+                    length = ntohs(oh->length);
+                    if (payload->size < length) {
+                        break;
+                    }
+
+                    if (!first) {
+                        putchar('\n');
+                    }
+                    first = false;
+
+                    if (timestamp) {
+                        char *s = xastrftime_msec("%H:%M:%S.### ", when, true);
+                        fputs(s, stdout);
+                        free(s);
+                    }
+
+                    printf(IP_FMT".%"PRIu16" > "IP_FMT".%"PRIu16":\n",
+                           IP_ARGS(flow.nw_src), ntohs(flow.tp_src),
+                           IP_ARGS(flow.nw_dst), ntohs(flow.tp_dst));
+                    ofp_print(stdout, payload->data, length, verbosity + 1);
+                    ofpbuf_pull(payload, length);
+                }
+            }
+        }
+        ofpbuf_delete(packet);
+    }
+    tcp_reader_close(reader);
+}
+
 static void
 ofctl_ping(int argc, char *argv[])
 {
@@ -3365,11 +3452,13 @@ static const struct command all_commands[] = {
     { "mod-table", 3, 3, ofctl_mod_table },
     { "get-frags", 1, 1, ofctl_get_frags },
     { "set-frags", 2, 2, ofctl_set_frags },
-    { "ofp-parse", 1, 1, ofctl_ofp_parse },
     { "probe", 1, 1, ofctl_probe },
     { "ping", 1, 2, ofctl_ping },
     { "benchmark", 3, 3, ofctl_benchmark },
 
+    { "ofp-parse", 1, 1, ofctl_ofp_parse },
+    { "ofp-parse-pcap", 1, INT_MAX, ofctl_ofp_parse_pcap },
+
     { "add-group", 1, 2, ofctl_add_group },
     { "add-groups", 1, 2, ofctl_add_groups },
     { "mod-group", 1, 2, ofctl_mod_group },
-- 
1.7.10.4




More information about the dev mailing list