/*
- * Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include "ofproto/ofproto.h"
#include "openflow/nicira-ext.h"
#include "openflow/openflow.h"
+#include "packets.h"
#include "poll-loop.h"
#include "random.h"
#include "stream-ssl.h"
*/
static bool strict;
-/* --readd: If ture, on replace-flows, re-add even flows that have not changed
+/* --readd: If true, on replace-flows, re-add even flows that have not changed
* (to reset flow counters). */
static bool readd;
/* -m, --more: Additional verbosity for ofp-print functions. */
static int verbosity;
+/* --timestamp: Print a timestamp before each received packet on "monitor" and
+ * "snoop" command? */
+static bool timestamp;
+
static const struct command all_commands[];
static void usage(void) NO_RETURN;
enum {
OPT_STRICT = UCHAR_MAX + 1,
OPT_READD,
+ OPT_TIMESTAMP,
DAEMON_OPTION_ENUMS,
VLOG_OPTION_ENUMS
};
{"flow-format", required_argument, NULL, 'F'},
{"packet-in-format", required_argument, NULL, 'P'},
{"more", no_argument, NULL, 'm'},
+ {"timestamp", no_argument, NULL, OPT_TIMESTAMP},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'},
DAEMON_LONG_OPTIONS,
readd = true;
break;
+ case OPT_TIMESTAMP:
+ timestamp = true;
+ break;
+
DAEMON_OPTION_HANDLERS
VLOG_OPTION_HANDLERS
STREAM_SSL_OPTION_HANDLERS
" mod-flows SWITCH FLOW modify actions of matching FLOWs\n"
" del-flows SWITCH [FLOW] delete matching FLOWs\n"
" replace-flows SWITCH FILE replace flows with those in FILE\n"
- " monitor SWITCH [MISSLEN] print packets received from SWITCH\n"
+ " diff-flows SOURCE1 SOURCE2 compare flows from two sources\n"
+ " packet-out SWITCH IN_PORT ACTIONS PACKET...\n"
+ " execute ACTIONS on PACKET\n"
+ " monitor SWITCH [MISSLEN] [invalid_ttl]\n"
+ " print packets received from SWITCH\n"
+ " snoop SWITCH snoop on SWITCH and its controller\n"
"\nFor OpenFlow switches and controllers:\n"
" probe TARGET probe whether TARGET is up\n"
" ping TARGET [N] latency of N-byte echos\n"
" -F, --flow-format=FORMAT force particular flow format\n"
" -P, --packet-in-format=FRMT force particular packet in format\n"
" -m, --more be more verbose printing OpenFlow\n"
+ " --timestamp (monitor, snoop) print timestamps\n"
" -t, --timeout=SECS give up after SECS seconds\n"
" -h, --help display this help message\n"
" -V, --version display version information\n");
{
bool *exiting = exiting_;
*exiting = true;
- unixctl_command_reply(conn, 200, "");
+ unixctl_command_reply(conn, NULL);
}
static void run(int retval, const char *message, ...)
config = reply->data;
*config_ = *config;
+
+ ofpbuf_delete(reply);
}
static void
return 0;
}
+/* Converts hex digits in 'hex' to an OpenFlow message in '*msgp'. The
+ * caller must free '*msgp'. On success, returns NULL. On failure, returns
+ * an error message and stores NULL in '*msgp'. */
+static const char *
+openflow_from_hex(const char *hex, struct ofpbuf **msgp)
+{
+ struct ofp_header *oh;
+ struct ofpbuf *msg;
+
+ msg = ofpbuf_new(strlen(hex) / 2);
+ *msgp = NULL;
+
+ if (ofpbuf_put_hex(msg, hex, NULL)[0] != '\0') {
+ ofpbuf_delete(msg);
+ return "Trailing garbage in hex data";
+ }
+
+ if (msg->size < sizeof(struct ofp_header)) {
+ ofpbuf_delete(msg);
+ return "Message too short for OpenFlow";
+ }
+
+ oh = msg->data;
+ if (msg->size != ntohs(oh->length)) {
+ ofpbuf_delete(msg);
+ return "Message size does not match length in OpenFlow header";
+ }
+
+ *msgp = msg;
+ return NULL;
+}
+
static void
-monitor_vconn(struct vconn *vconn)
+ofctl_send(struct unixctl_conn *conn, int argc,
+ const char *argv[], void *vconn_)
{
- struct unixctl_server *server;
- bool exiting = false;
- int error, fd;
+ struct vconn *vconn = vconn_;
+ struct ds reply;
+ bool ok;
+ int i;
- if (preferred_packet_in_format >= 0) {
- set_packet_in_format(vconn, preferred_packet_in_format);
- } else {
- struct ofpbuf *spif, *reply;
+ ok = true;
+ ds_init(&reply);
+ for (i = 1; i < argc; i++) {
+ const char *error_msg;
+ struct ofpbuf *msg;
+ int error;
+
+ error_msg = openflow_from_hex(argv[i], &msg);
+ if (error_msg) {
+ ds_put_format(&reply, "%s\n", error_msg);
+ ok = false;
+ continue;
+ }
- spif = ofputil_make_set_packet_in_format(NXPIF_NXM);
- run(vconn_transact_noreply(vconn, spif, &reply),
- "talking to %s", vconn_get_name(vconn));
- if (reply) {
- char *s = ofp_to_string(reply->data, reply->size, 2);
- VLOG_DBG("%s: failed to set packet in format to nxm, controller"
- " replied: %s. Falling back to the switch default.",
- vconn_get_name(vconn), s);
- free(s);
- ofpbuf_delete(reply);
+ fprintf(stderr, "send: ");
+ ofp_print(stderr, msg->data, msg->size, verbosity);
+
+ error = vconn_send_block(vconn, msg);
+ if (error) {
+ ofpbuf_delete(msg);
+ ds_put_format(&reply, "%s\n", strerror(error));
+ ok = false;
+ } else {
+ ds_put_cstr(&reply, "sent\n");
}
}
- /* Daemonization will close stderr but we really want to keep it, so make a
- * copy. */
- fd = dup(STDERR_FILENO);
+ if (ok) {
+ unixctl_command_reply(conn, ds_cstr(&reply));
+ } else {
+ unixctl_command_reply_error(conn, ds_cstr(&reply));
+ }
+ ds_destroy(&reply);
+}
+
+struct barrier_aux {
+ struct vconn *vconn; /* OpenFlow connection for sending barrier. */
+ struct unixctl_conn *conn; /* Connection waiting for barrier response. */
+};
+
+static void
+ofctl_barrier(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[] OVS_UNUSED, void *aux_)
+{
+ struct barrier_aux *aux = aux_;
+ struct ofpbuf *msg;
+ int error;
+
+ if (aux->conn) {
+ unixctl_command_reply_error(conn, "already waiting for barrier reply");
+ return;
+ }
+
+ msg = ofputil_encode_barrier_request();
+ fprintf(stderr, "send: ");
+ ofp_print(stderr, msg->data, msg->size, verbosity);
+
+ error = vconn_send_block(aux->vconn, msg);
+ if (error) {
+ ofpbuf_delete(msg);
+ unixctl_command_reply_error(conn, strerror(error));
+ } else {
+ aux->conn = conn;
+ }
+}
+
+static void
+ofctl_set_output_file(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[], void *aux OVS_UNUSED)
+{
+ int fd;
+
+ fd = open(argv[1], O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (fd < 0) {
+ unixctl_command_reply_error(conn, strerror(errno));
+ return;
+ }
+
+ fflush(stderr);
+ dup2(fd, STDERR_FILENO);
+ close(fd);
+ unixctl_command_reply(conn, NULL);
+}
+
+static void
+monitor_vconn(struct vconn *vconn)
+{
+ struct barrier_aux barrier_aux = { vconn, NULL };
+ struct unixctl_server *server;
+ bool exiting = false;
+ int error;
+ daemon_save_fd(STDERR_FILENO);
daemonize_start();
error = unixctl_server_create(NULL, &server);
if (error) {
ovs_fatal(error, "failed to create unixctl server");
}
unixctl_command_register("exit", "", 0, 0, ofctl_exit, &exiting);
+ unixctl_command_register("ofctl/send", "OFMSG...", 1, INT_MAX,
+ ofctl_send, vconn);
+ unixctl_command_register("ofctl/barrier", "", 0, 0,
+ ofctl_barrier, &barrier_aux);
+ unixctl_command_register("ofctl/set-output-file", "FILE", 1, 1,
+ ofctl_set_output_file, NULL);
daemonize_complete();
- /* Now get stderr back. */
- dup2(fd, STDERR_FILENO);
-
for (;;) {
struct ofpbuf *b;
int retval;
unixctl_server_run(server);
for (;;) {
+ uint8_t msg_type;
+
retval = vconn_recv(vconn, &b);
if (retval == EAGAIN) {
break;
}
+ msg_type = ((const struct ofp_header *) b->data)->type;
run(retval, "vconn_recv");
+ if (timestamp) {
+ time_t now = time_wall();
+ char s[32];
+
+ strftime(s, sizeof s, "%Y-%m-%d %H:%M:%S: ", localtime(&now));
+ fputs(s, stderr);
+ }
ofp_print(stderr, b->data, b->size, verbosity + 2);
ofpbuf_delete(b);
+
+ if (barrier_aux.conn && msg_type == OFPT_BARRIER_REPLY) {
+ unixctl_command_reply(barrier_aux.conn, NULL);
+ barrier_aux.conn = NULL;
+ }
}
if (exiting) {
unixctl_server_wait(server);
poll_block();
}
+ vconn_close(vconn);
+ unixctl_server_destroy(server);
}
static void
monitor_set_invalid_ttl_to_controller(vconn);
}
}
+ if (preferred_packet_in_format >= 0) {
+ set_packet_in_format(vconn, preferred_packet_in_format);
+ } else {
+ struct ofpbuf *spif, *reply;
+
+ spif = ofputil_make_set_packet_in_format(NXPIF_NXM);
+ run(vconn_transact_noreply(vconn, spif, &reply),
+ "talking to %s", vconn_get_name(vconn));
+ if (reply) {
+ char *s = ofp_to_string(reply->data, reply->size, 2);
+ VLOG_DBG("%s: failed to set packet in format to nxm, controller"
+ " replied: %s. Falling back to the switch default.",
+ vconn_get_name(vconn), s);
+ free(s);
+ ofpbuf_delete(reply);
+ }
+ }
+
monitor_vconn(vconn);
}
vconn_close(vconn);
}
+static void
+do_packet_out(int argc, char *argv[])
+{
+ struct ofputil_packet_out po;
+ struct ofpbuf actions;
+ struct vconn *vconn;
+ int i;
+
+ ofpbuf_init(&actions, sizeof(union ofp_action));
+ parse_ofp_actions(argv[3], &actions);
+
+ po.buffer_id = UINT32_MAX;
+ po.in_port = (!strcasecmp(argv[2], "none") ? OFPP_NONE
+ : !strcasecmp(argv[2], "local") ? OFPP_LOCAL
+ : str_to_port_no(argv[1], argv[2]));
+ po.actions = actions.data;
+ po.n_actions = actions.size / sizeof(union ofp_action);
+
+ open_vconn(argv[1], &vconn);
+ for (i = 4; i < argc; i++) {
+ struct ofpbuf *packet, *opo;
+ const char *error_msg;
+
+ error_msg = eth_from_hex(argv[i], &packet);
+ if (error_msg) {
+ ovs_fatal(0, "%s", error_msg);
+ }
+
+ po.packet = packet->data;
+ po.packet_len = packet->size;
+ opo = ofputil_encode_packet_out(&po);
+ transact_noreply(vconn, opo);
+ ofpbuf_delete(packet);
+ }
+ vconn_close(vconn);
+}
+
static void
do_mod_port(int argc OVS_UNUSED, char *argv[])
{
struct ofputil_flow_stats fs;
int retval;
- retval = ofputil_decode_flow_stats_reply(&fs, reply);
+ retval = ofputil_decode_flow_stats_reply(&fs, reply, false);
if (retval) {
if (retval != EOF) {
ovs_fatal(0, "parse error in reply");
{ "del-flows", 1, 2, do_del_flows },
{ "replace-flows", 2, 2, do_replace_flows },
{ "diff-flows", 2, 2, do_diff_flows },
+ { "packet-out", 4, INT_MAX, do_packet_out },
{ "dump-ports", 1, 2, do_dump_ports },
{ "mod-port", 3, 3, do_mod_port },
{ "get-frags", 1, 1, do_get_frags },