From f3dd1419291242caa29c65826dc0beef33f7f087 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 22 Nov 2013 13:17:23 -0800 Subject: [PATCH] ovs-ofctl: New command "ofp-parse-pcap" to dump OpenFlow from PCAP files. 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 --- 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 99dff237d..4e3e7db92 100644 --- a/lib/pcap-file.c +++ b/lib/pcap-file.c @@ -23,8 +23,12 @@ #include #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); @@ -199,3 +203,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 7148b185a..ef491e547 100644 --- a/lib/pcap-file.h +++ b/lib/pcap-file.h @@ -19,12 +19,21 @@ #include +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 411ad6479..9a8fd3343 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 @@ -2011,7 +2026,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 2988fb60a..9b02b25ca 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 }, -- 2.20.1