From: Ben Pfaff Date: Thu, 9 Dec 2010 23:00:36 +0000 (-0800) Subject: ofproto: Add "ofproto/trace" command to help debugging flow tables. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7aa697ddd2dfe494693be590ae5b03cbec5f9944;p=openvswitch ofproto: Add "ofproto/trace" command to help debugging flow tables. 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 --- diff --git a/debian/openvswitch-switch.install b/debian/openvswitch-switch.install index 7b988da1..08ce5764 100644 --- a/debian/openvswitch-switch.install +++ b/debian/openvswitch-switch.install @@ -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 diff --git a/ofproto/automake.mk b/ofproto/automake.mk index 0c99b490..66307455 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 00000000..72f076dd --- /dev/null +++ b/ofproto/ofproto-unixctl.man @@ -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. diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index cffdf657..00b57311 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -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); } +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); +} + 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 cbe6128b..9a334e33 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 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 diff --git a/utilities/ovs-openflowd.8.in b/utilities/ovs-openflowd.8.in index fcca9810..b84f8e7b 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 00000000..470e251d --- /dev/null +++ b/utilities/ovs-pcap.1.in @@ -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 index 00000000..8ab47561 --- /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.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 index 00000000..e59f6dba --- /dev/null +++ b/utilities/ovs-tcpundump.1.in @@ -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 index 00000000..c0189984 --- /dev/null +++ b/utilities/ovs-tcpundump.in @@ -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: diff --git a/vswitchd/ovs-vswitchd.8.in b/vswitchd/ovs-vswitchd.8.in index 189f2138..822c0f29 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"